📷
SwiftUIとVision Framework、Google Gemini APIで作る名刺読み取りアプリ
はじめに
名刺の情報を手動で連絡先に入力するのは面倒な作業ですよね。そこで今回は、SwiftUIを使って、Vision FrameworkとGoogle Gemini APIを組み合わせた高精度な名刺読み取りアプリを開発しました。
単純なOCRだけでなく、AIによる情報の構造化まで行うことで、実用的な名刺読み取り機能を実現しています。
完成したアプリの特徴
主な機能
- リアルタイム名刺検出: カメラでリアルタイムに名刺の輪郭を認識
- 自動フォーカス: 名刺が適切な位置に来ると自動で撮影
- 高精度OCR: Vision Frameworkによる日本語文字認識
- AI構造化: Google Gemini APIで抽出した文字列を構造化データ(JSON)に変換
- 白飛び検出: 画像品質チェックで読み取り精度を向上
UX/UI設計のポイント
- フルスクリーンカメラで没入感のある体験
- オーバーレイ表示で読み取り領域を明確化
- 視覚的フィードバック(赤→水色のライン変化)
- 触覚フィードバック(撮影完了時のバイブレーション)
技術スタック
SwiftUI + Vision Framework + Google Generative AI
- フレームワーク: SwiftUI
- 画像処理: Vision Framework(Rectangle Detection + OCR)
- AI連携: Google Gemini API (gemini-1.5-flash)
- 開発言語: Swift
- 対応OS: iOS 14.0以上
アーキテクチャ設計
データフロー
MVVMパターンの採用
ContentView.swift
// ViewModelでデータ管理
class CardViewModel: ObservableObject, MyDataReceiverDelegate {
@Published var cards: [Card] = [...] // 名刺情報
@Published var image: Image = Image("Download") // 読み取った画像
func imageReceived(data: UIImage) { ... }
func mapReceived(data: [Card]) { ... }
}
// Viewは状態変化に自動反応
struct ContentView: View {
@StateObject private var viewModel = CardViewModel()
// ...
}
実装のポイント
1. リアルタイム名刺検出
Vision FrameworkのVNDetectRectanglesRequest
を使用して、カメラフレームから名刺型の四角形を検出します。
MedicalCardScannerViewController.swift
private func findBestRectangleMatch(_ rectangles: [VNRectangleObservation]) -> VNRectangleObservation? {
let idealAspectRatio: CGFloat = 91.0 / 55.0 // 名刺の標準的なアスペクト比
return rectangles.min(by: { rect1, rect2 in
let aspectRatio1 = rect1.boundingBox.width / rect1.boundingBox.height
let aspectRatio2 = rect2.boundingBox.width / rect2.boundingBox.height
return abs(aspectRatio1 - idealAspectRatio) < abs(aspectRatio2 - idealAspectRatio)
})
}
2. 安定した自動撮影
名刺が適切な位置に1秒間維持されたときに自動撮影する仕組みを実装。
MedicalCardScannerViewController.swift
private func checkForMatch(_ rectangle: VNRectangleObservation, pixelBuffer: CVPixelBuffer) {
// サイズ比較で位置の安定性をチェック
let isWithinSizeThreshold = widthDifference <= matchPercentageThreshold ||
heightDifference <= matchPercentageThreshold
if isWithinSizeThreshold {
if matchingStartTime == nil {
matchingStartTime = Date()
overlayView.setMatchStatus(true) // 水色に変更
}
// 1秒間安定したら撮影実行
if let startTime = matchingStartTime,
Date().timeIntervalSince(startTime) >= matchThresholdSeconds {
// 撮影処理...
}
}
}
3. 高精度OCR設定
日本語に最適化されたOCR設定で文字認識精度を向上。
MedicalCardScannerViewController.swift
func ocrRequest(_ image: UIImage) {
let request = VNRecognizeTextRequest { ... }
request.recognitionLanguages = ["ja-jp"]
request.recognitionLevel = .accurate // 高精度モード
request.usesLanguageCorrection = true // 言語補正を有効化
}
4. Gemini APIでの構造化
OCRで抽出した文字列をGoogle Gemini APIで構造化データに変換。
MedicalCardScannerViewController.swift
func callGemini(original: String) async -> String {
let model = GenerativeModel(name: "gemini-1.5-flash", apiKey: APIKey.default)
let prompt = """
以下の文字列は日本語の名刺をOCRで読み取った結果です。
文字列には、'会社名'、'部署名'、'役職名'、'郵便番号'、'住所'、'電話番号'、'内線番号'、'携帯番号'、'FAX番号'、'URL'、'メールアドレス'が含まれています。
'郵便番号'は先頭に'〒'がついている場合があります。
'電話番号'、'携帯番号'、'FAX番号'は数字で、区切り記号として' '、'-'、'('、')'が使われることがあります。
'電話番号'で検出された'b'は'6'に変換してください。
'電話番号'で検出された'D'は'0'に変換してください。
'電話番号'で検出された'S'は'5'に変換してください。
抽出された項目をJSON文字列に変換してください。
JSONの項目名を、'会社名'は'company'、'部署名'は'division'、'役職名'は'title'、'郵便番号'は'zipcode'、'住所'は'address'、'電話番号'は'tel'、'内線番号'は'ext'、'携帯番号'は'mobile'、'FAX番号'は'fax'、'URL'は'url'、'メールアドレス'は'email'にしてください。
'\(original)'
"""
let response = try await model.generateContent(prompt)
return response.text ?? ""
}
5. 画像品質チェック
白飛びを検出して、読み取り精度の低下を防ぐ機能も実装。
MedicalCardScannerViewController.swift
func detectBlownHighlightsByLuminance(in image: UIImage, luminanceThreshold: Double = 250.0) -> [CGPoint] {
// RGBから輝度計算
let luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b
if luminance >= luminanceThreshold {
blownOutPoints.append(CGPoint(x: x, y: y))
}
}
SwiftUIとUIKitの連携
カメラ機能はUIKitで実装し、SwiftUIから呼び出す構成になっています。
ContentView.swift
struct NameCardViewControllerWrapper: UIViewControllerRepresentable {
var delegate: MyDataReceiverDelegate
func makeUIViewController(context: Context) -> MedicalCardScannerViewController {
return MedicalCardScannerViewController(delegate: delegate)
}
func updateUIViewController(_ uiViewController: MedicalCardScannerViewController, context: Context) {}
}
データの受け渡しはDelegateパターンで実装し、ViewModelが受け取った結果をSwiftUIのViewに反映します。
パフォーマンス最適化
1. 非同期処理の活用
- カメラ処理:バックグラウンドキューで実行
- OCR処理:グローバルキューで実行
- UI更新:メインキューで実行
2. メモリ管理
- CVPixelBufferの適切な管理
- Core Imageのコンテキスト再利用
セットアップ方法
1. Google Gemini API キーの設定
2. カメラ権限設定
Info.plist
<key>NSCameraUsageDescription</key>
<string>名刺を読み取るためにカメラを使用します</string>
アプリ画面の紹介
今後の改善点
- CoreMLモデルの導入: オフラインでの文字認識
- 連絡先アプリとの連携: 読み取り結果の直接保存
- 複数言語対応: 英語名刺の読み取り
- バッチ処理: 複数枚の名刺一括処理
まとめ
Vision FrameworkとGoogle Gemini APIを組み合わせることで、高精度な名刺読み取りアプリを開発できました。
特に以下の点が実用性向上に寄与しています:
- リアルタイム検出による直感的なUX
- AI構造化による高精度なデータ抽出
- 品質チェックによる読み取りエラーの削減
SwiftUIとVision Frameworkの組み合わせは、このような画像処理アプリの開発において非常に強力です。ぜひ参考にして、独自の画像認識アプリを開発してみてください。
参考リンク
この記事が少しでもお役に立てば幸いです。質問やフィードバックがあれば、お気軽にコメントください!
Discussion