iOSアプリにペンギン種別判定AIを組み込む — Create ML × Core ML × Vision の実装全解説

はじめに

mapengu は「日本でペンギンに会える場所」を地図で探せるiOSアプリです。
v1.6 で追加したペンギン判定カメラは、スマホをペンギンに向けるだけで種類をリアルタイム判定し、そのまま飼育施設を地図で探せる機能です。

この記事では、モデルの学習から iOS への組み込み・UI 設計まで、実装のすべてを解説します。


全体の構成

[Create ML でモデル学習]
        ↓ .mlpackage
[Xcode プロジェクトに組み込み]
        ↓
[AVCaptureSession] → カメラ映像取得
        ↓ 30フレームに1回
[VNCoreMLRequest] → ペンギン種別を推論
        ↓ ラベル + 信頼度
[SwiftUI] → 結果カード表示 + 地図検索へ遷移

Step 1 — Create ML でモデルを学習する

タスク種別

Xcode 付属の Create ML(バージョン 6.2)を使います。
タスク種別は Image Classifier(画像分類)を選択します。

対象クラスの選定

動物園・水族館でよく見られる 11 種を選びました。

英語名日本語名
Emperor Penguinコウテイペンギン
King Penguinキングペンギン
Gentoo Penguinジェンツーペンギン
Adelie Penguinアデリーペンギン
Chinstrap Penguinヒゲペンギン
Rockhopper Penguinイワトビペンギン
Macaroni Penguinマカロニペンギン
African Penguinケープペンギン
Humboldt Penguinフンボルトペンギン
Magellanic Penguinマゼランペンギン
Little Penguinコガタペンギン

データセットの構成

ディレクトリ構造はシンプルです。クラス名をそのままフォルダ名にします。

penguin_dataset/
├── Emperor Penguin/
│   ├── 0001.jpg
│   ├── 0002.jpg
│   └── ...
├── King Penguin/
│   └── ...
└── ...(11クラス)

学習は 2 回試みました。

バージョン枚数/クラス合計収束
v1100枚1,100枚19イテレーション
v2500枚5,500枚26イテレーション

v1 は枚数が少なく特定クラスの精度が不安定だったため、v2 でデータを 5 倍に増やして再学習しました。
Create ML の転移学習ベースのため、Mac 単体で数十分以内に完了します。

モデルの出力

学習後、Export Model から .mlpackage 形式で書き出し、Xcode プロジェクトにドラッグ&ドロップするだけで組み込めます。コンパイルは Xcode ビルド時に自動で行われます。


Step 2 — iOS への組み込み(PenguinIdentifierViewModel

カメラのセットアップ

private func configureCaptureSession() {
    captureSession.beginConfiguration()
    captureSession.sessionPreset = .high  // 高解像度で取得

    guard let device = AVCaptureDevice.default(
              .builtInWideAngleCamera, for: .video, position: .back),
          let input = try? AVCaptureDeviceInput(device: device)
    else { return }

    captureSession.addInput(input)
    videoOutput.setSampleBufferDelegate(self, queue: visionQueue)
    videoOutput.alwaysDiscardsLateVideoFrames = true
    captureSession.addOutput(videoOutput)

    captureSession.commitConfiguration()
    captureSession.startRunning()
}

推論はすべて専用の visionQueue(QoS: .userInitiated)で行い、メインスレッドをブロックしません。

推論頻度の制御

func captureOutput(_ output: AVCaptureOutput,
                   didOutput sampleBuffer: CMSampleBuffer,
                   from connection: AVCaptureConnection) {
    frameCount += 1
    guard frameCount % 30 == 0 else { return }  // 30フレームに1回だけ推論

    guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer),
          let request = visionRequest else { return }

    let handler = VNImageRequestHandler(cvPixelBuffer: pixelBuffer, orientation: .right)
    try? handler.perform([request])
}

60fps のカメラなら約 2 回/秒の推論。体感的に十分なレスポンスを保ちながら、CPU・バッテリー消費を大幅に抑えられます。

カスタムモデル優先 + フォールバック設計

カスタムモデルが Bundle に存在すれば優先し、なければ Apple 標準モデルで代替します。

private func setupVisionRequest() {
    if let coreMLRequest = loadCoreMLRequest() {
        visionRequest = coreMLRequest
        usingCustomModel = true
    } else {
        visionRequest = makeBuiltinRequest()  // VNClassifyImageRequest にフォールバック
    }
}

private func loadCoreMLRequest() -> VNCoreMLRequest? {
    // .mlmodelc(コンパイル済み)と .mlpackage の両方を探す
    guard let modelURL = Bundle.main.url(forResource: "PenguinClassifier", withExtension: "mlmodelc")
                      ?? Bundle.main.url(forResource: "PenguinClassifier", withExtension: "mlpackage"),
          let mlModel = try? MLModel(contentsOf: modelURL),
          let vnModel = try? VNCoreMLModel(for: mlModel)
    else { return nil }

    let request = VNCoreMLRequest(model: vnModel) { [weak self] req, _ in
        guard let results = req.results as? [VNClassificationObservation] else { return }
        let top = results.first { $0.confidence >= 0.50 }  // 信頼度 50% 以上
        DispatchQueue.main.async { self?.applyResult(label: top?.identifier, confidence: top?.confidence) }
    }
    request.imageCropAndScaleOption = .centerCrop  // ガイド枠に合わせたクロップ
    return request
}

信頼度の閾値設計

モデル閾値設計の理由
カスタムモデル(PenguinClassifier)50%11クラス専用なので誤検知を抑えるために高めに設定
Apple 標準(VNClassifyImageRequest)5%数千クラスの中から “penguin” を含むラベルを拾うため低く設定

標準モデルは閾値を低くしてでも「ペンギンっぽいもの」を拾いにいきます。カスタムモデルの有無でフォールバック挙動が自然に切り替わります。

判定ラベルをアプリデータと照合する

モデルが返すラベル(例: "Emperor Penguin")をアプリ内の Penguin マスターデータと紐づけます。

private func matchPenguin(label: String) -> Penguin? {
    let lower = label.lowercased()
    return penguins.first { penguin in
        let words = penguin.name.lowercased().split(separator: " ")
        // 全単語一致を優先、部分一致もカバー
        return words.allSatisfy { lower.contains($0) }
            || (words.first.map { lower.contains($0) } ?? false)
    }
}

モデルのラベルとマスターデータの表記ゆれ(例: "African Penguin" vs "Cape Penguin")に対応するため、全単語一致 → 先頭単語一致の順で照合しています。


Step 3 — UI(PenguinCameraView

レイアウト構成

ZStack
├── Color.black(背景)
├── CameraPreviewView(フルスクリーン)
└── VStack
    ├── Spacer
    ├── RoundedRectangle 260×260(判定枠ガイド)
    ├── Spacer
    └── classificationResultCard(判定結果カード)

260×260 の白枠はユーザーにカメラの向け方を示すガイドです。centerCrop と合わせて「枠の中に入れれば判定される」という直感的な UX を実現しています。

判定結果カード

VStack(spacing: 12) {
    if let result = viewModel.penguinResult {
        HStack {
            VStack(alignment: .leading) {
                Text(result.penguin?.name_jp ?? "ペンギン")  // 日本語名
                    .font(.title3.bold())
                Text(result.penguin?.name ?? result.label)   // 英語名
                    .font(.subheadline)
            }
            Spacer()
            Text("\(Int(result.confidence * 100))%")         // 信頼度
                .font(.title2.bold())
                .foregroundColor(.blue)
        }

        if let penguin = result.penguin {
            NavigationLink {
                PenguinSearch(penguinCode: penguin.penguin_code, ...)
            } label: {
                Label("このペンギンがいる場所を探す", systemImage: "map.fill")
                    // → 地図検索画面へ遷移
            }
        }
    } else {
        Text("カメラをペンギンに向けてください")
    }
}
.background(.ultraThinMaterial)
.cornerRadius(16)

判定結果が出た瞬間にボタンが現れて地図検索へ遷移できます。「見たペンギンを、今すぐ他の場所でも探す」という動線を最短にしました。

ツールバーのモデルバッジ

Label(
    viewModel.usingCustomModel ? "カスタムモデル" : "標準モデル",
    systemImage: viewModel.usingCustomModel ? "brain.filled.head.profile" : "cpu"
)
.foregroundColor(viewModel.usingCustomModel ? .green : .yellow)

開発中のデバッグにも役立ちつつ、ユーザーへの透明性も確保しています。


まとめ

項目採用技術・数値
モデル学習ツールCreate ML 6.2
タスクImage Classifier(転移学習)
クラス数11種
データ枚数5,500枚(500枚/クラス)
収束イテレーション26
推論エンジンCore ML + Vision(VNCoreMLRequest)
フォールバックVNClassifyImageRequest(Apple 標準)
推論頻度30フレームに1回(約2回/秒)
信頼度閾値カスタム 50% / 標準 5%

Create ML × Core ML × Vision のスタックはサーバー不要・オフライン動作・Neural Engine フル活用という三拍子が揃っています。ペンギン以外の動物図鑑・博物館ガイド・野鳥識別など、似た構造のアプリにそのまま応用できるはずです。


参考リンク

Tags:

No responses yet

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

Latest Comments

表示できるコメントはありません。