axiom-vision-diag について
AppleのVisionフレームワークを使う開発者向けのトラブルシューティングガイドです。検出や認識、座標、スレッド、APIの利用可否、パフォーマンスの問題を診断する手順と対処法を示します。
主な機能
- 01Visionの検出問題を診断
- 02信頼度と処理スレッドを確認
- 03座標変換の問題を調査
- 04APIの利用可否の違いに対応
- 05リアルタイム処理を最適化
活用シーン
- 01手や人体のポーズが検出されない原因を調査
- 02UIオーバーレイの位置ずれを修正
- 03テキストやバーコード認識の失敗を調査
- 04リアルタイム画像処理の遅延を軽減
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ charleswiltgen/ axiom --skill axiom-vision-diagインストール後、Claude Code で/axiom-vision-diagを入力して読み込まれたか確認します。
詳細
Vision Framework の診断
Vision framework の問題を体系的にトラブルシューティングします。被写体が検出されない、ランドマークが見つからない、信頼度が低い、パフォーマンスに問題がある、座標が一致しない、テキスト認識に失敗する、バーコード検出に問題がある、書類スキャンに問題がある場合に対応します。
概要
基本原則:Vision が動作しない場合、通常は次のいずれかが原因です。
- 環境(照明、遮蔽、フレームの端)- 40%
- 信頼度のしきい値(信頼度の低いデータを無視している)- 30%
- スレッド処理(メインスレッドをブロックして UI がフリーズする)- 15%
- 座標(左下原点と左上原点を混在させている)- 10%
- API の利用可否(古いデバイスで iOS 17 以降の API を使用している)- 5%
コードをデバッグする前に、必ず環境と信頼度を確認してください。
注意すべき兆候
Vision 固有の問題を示す症状:
| 症状 | 考えられる原因 |
|---|---|
| 被写体がまったく検出されない | フレームの端、照明不足、被写体が非常に小さい |
| 手のランドマークが断続的に nil になる | 手がフレームの端にある、カメラに対して平行、手袋/遮蔽 |
| 身体のポーズでフレームがスキップされる | 人物が前屈している、上下逆さま、ゆったりした服 |
| 処理中に UI がフリーズする | メインスレッドで Vision を実行している |
| オーバーレイの位置がずれている | 座標変換(左下原点と左上原点) |
| 古いデバイスでクラッシュする | @available チェックなしで iOS 17 以降の API を使用している |
| 人物セグメンテーションで人物が検出されない | シーン内に人物が 4 人を超えている(インスタンスマスクの上限) |
| カメラ映像の FPS が低い | maximumHandCount が高すぎる、フレームを間引いていない |
| テキストがまったく認識されない | 画像がぼやけている、装飾フォント、認識レベルが不適切 |
| テキストの読み取りを誤る(文字が違う) | 言語補正が無効、カスタム単語が不足している |
| バーコードが検出されない | シンボル体系が不適切、コードが小さすぎる、まぶしさ/反射 |
| DataScanner の画面が真っ白になる | カメラへのアクセスが拒否されている、デバイスがサポートされていない |
| 書類の端が検出されない | コントラスト不足、矩形ではない、まぶしさ |
| リアルタイムスキャンが遅すぎる | すべてのフレームを処理している、領域が大きすぎる |
必須の初手
コードを調べる前に、次の診断を実行してください。
ステップ 1:診断コードで検出を確認する
let request = VNGenerateForegroundInstanceMaskRequest() // Or hand/body pose
let handler = VNImageRequestHandler(cgImage: testImage)
do {
try handler.perform([request])
if let results = request.results {
print("✅ Request succeeded")
print("Result count: \(results.count)")
if let observation = results.first as? VNInstanceMaskObservation {
print("All instances: \(observation.allInstances)")
print("Instance count: \(observation.allInstances.count)")
}
} else {
print("⚠️ Request succeeded but no results")
}
} catch {
print("❌ Request failed: \(error)")
}
想定される出力:
- ✅ リクエスト成功、インスタンス数 > 0 → 検出は動作しています
- ⚠️ リクエスト成功、インスタンス数 = 0 → 何も検出されていません(判断ツリーを参照)
- ❌ リクエスト失敗 → API の利用可否に問題があります
ステップ 2:信頼度スコアを確認する
// For hand/body pose
if let observation = request.results?.first as? VNHumanHandPoseObservation {
let allPoints = try observation.recognizedPoints(.all)
for (key, point) in allPoints {
print("\(key): confidence \(point.confidence)")
if point.confidence < 0.3 {
print(" ⚠️ LOW CONFIDENCE - unreliable")
}
}
}
想定される出力:
- ほとんどのランドマークの信頼度が > 0.5 → 良好な検出
- 多くのランドマークの信頼度が < 0.3 → 照明不足、遮蔽、またはフレームの端
ステップ 3:スレッド処理を確認する
print("🧵 Thread: \(Thread.current)")
if Thread.isMainThread {
print("❌ Running on MAIN THREAD - will block UI!")
} else {
print("✅ Running on background thread")
}
想定される出力:
- ✅ バックグラウンドスレッド → 正常
- ❌ メインスレッド →
DispatchQueue.global()に移動してください
判断ツリー
Vision not working as expected?
│
├─ No results returned?
│ ├─ Check Step 1 output
│ │ ├─ "Request failed" → See Pattern 1a (API availability)
│ │ ├─ "No results" → See Pattern 1b (nothing detected)
│ │ └─ Results but count = 0 → See Pattern 1c (edge of frame)
│
├─ Landmarks have nil/low confidence?
│ ├─ Hand pose → See Pattern 2 (hand detection issues)
│ ├─ Body pose → See Pattern 3 (body detection issues)
│ └─ Face detection → See Pattern 4 (face detection issues)
│
├─ UI freezing/slow?
│ ├─ Check Step 3 (threading)
│ │ ├─ Main thread → See Pattern 5a (move to background)
│ │ └─ Background thread → See Pattern 5b (performance tuning)
│
├─ Overlays in wrong position?
│ └─ See Pattern 6 (coordinate conversion)
│
├─ Person segmentation missing people?
│ └─ See Pattern 7 (crowded scenes)
│
├─ VisionKit not working?
│ └─ See Pattern 8 (VisionKit specific)
│
├─ Text recognition issues?
│ ├─ No text detected → See Pattern 9a (image quality)
│ ├─ Wrong characters → See Pattern 9b (language/correction)
│ └─ Too slow → See Pattern 9c (recognition level)
│
├─ Barcode detection issues?
│ ├─ Barcode not detected → See Pattern 10a (symbology/size)
│ └─ Wrong payload → See Pattern 10b (barcode quality)
│
├─ DataScannerViewController issues?
│ ├─ Blank screen → See Pattern 11a (availability check)
│ └─ Items not detected → See Pattern 11b (data types)
│
└─ Document scanning issues?
├─ Edges not detected → See Pattern 12a (contrast/shape)
└─ Perspective wrong → See Pattern 12b (corner points)
診断パターン
パターン 1a:リクエスト失敗(API の利用可否)
症状:try handler.perform([request]) がエラーをスローする
よくあるエラー:
"VNGenerateForegroundInstanceMaskRequest is only available on iOS 17.0 or newer"
"VNDetectHumanBodyPose3DRequest is only available on iOS 17.0 or newer"
根本原因:古いデプロイメントターゲットで iOS 17 以降の API を使用している
修正方法:
if #available(iOS 17.0, *) {
let request = VNGenerateForegroundInstanceMaskRequest()
// ...
} else {
// Fallback for iOS 14-16
let request = VNGeneratePersonSegmentationRequest()
// ...
}
予防策:実装前に axiom-vision-ref で API の利用可否を確認してください
修正にかかる時間:10 分
パターン 1b:結果がない(何も検出されない)
症状:request.results == nil または results.isEmpty
診断:
// 1. Save debug image to Photos
UIImageWriteToSavedPhotosAlbum(debugImage, nil, nil, nil)
// 2. Inspect visually
// - Is subject too small? (< 10% of image)
// - Is subject blurry?
// - Poor contrast with background?
よくある原因:
- 被写体が小さすぎる(サイズを変更するか、近くで切り抜く)
- 被写体がぼやけすぎている(照明を強くする、カメラを固定する)
- コントラストが低い(被写体と背景の色が同じ)
修正方法:
// Crop image to focus on region of interest
let croppedImage = cropImage(sourceImage, to: regionOfInterest)
let handler = VNImageRequestHandler(cgImage: croppedImage)
修正にかかる時間:30 分
パターン 1c:フレームの端に関する問題
症状:被写体がフレーム内を移動すると、検出されたりされなかったりする
根本原因:被写体が画像の端に触れたときに部分的に遮蔽される
診断:
// Check if subject is near edges
if let observation = results.first as? VNInstanceMaskObservation {
let mask = try observation.createScaledMask(
for: observation.allInstances,
croppedToInstancesContent: true
)
let bounds = calculateMaskBounds(mask)
if bounds.minX < 0.1 || bounds.maxX > 0.9 ||
bounds.minY < 0.1 || bounds.maxY > 0.9 {
print("⚠️ Subject too close to edge")
}
}
修正方法:
// Add padding to capture area
let paddedRect = captureRect.insetBy(dx: -20, dy: -20)
// OR guide user with on-screen overlay
overlayView.addSubview(guideBox) // Visual boundary
修正にかかる時間:20 分
パターン 2:手のポーズに関する問題
症状:VNDetectHumanHandPoseRequest が nil を返す、または信頼度の低いランドマークを返す
診断:
if let observation = request.results?.first as? VNHumanHandPoseObservation {
let thumbTip = try? observation.recognizedPoint(.thumbTip)
let wrist = try? observation.recognizedPoint(.wrist)
print("Thumb confidence: \(thumbTip?.confidence ?? 0)")
print("Wrist confidence: \(wrist?.confidence ?? 0)")
// Check hand orientation
if let thumb = thumbTip, let wristPoint = wrist {
let angle = atan2(
thumb.location.y - wristPoint.location.y,
thumb.location.x - wristPoint.location.x
)
print("Hand angle: \(angle * 180 / .pi) degrees")
if abs(angle) > 80 && abs(angle) < 100 {
print("⚠️ Hand parallel to camera (hard to detect)")
}
}
}
一般的な原因:
| 原因 | 信頼度のパターン | 対処法 |
|---|---|---|
| 手が端に近い | 指先の信頼度が低い | フレーミングを調整する |
| 手がカメラと平行 | すべてのランドマークの信頼度が低い | 手を回転させるようユーザーに促す |
| 手袋/遮蔽 | 指の信頼度が低く、手首の信頼度が高い | 手袋を外すか、照明を変更する |
| 足を手として検出 | 想定外の手を検出 | chirality チェックを追加するか、無視する |
平行な手への対処法:
// Detect and warn user
if avgConfidence < 0.4 {
showWarning("Rotate your hand toward the camera")
}
修正所要時間:45 分
パターン 3:身体のポーズに関する問題
症状:VNDetectHumanBodyPoseRequest がフレームをスキップする、または信頼度の低い結果を返す
診断:
if let observation = request.results?.first as? VNHumanBodyPoseObservation {
let nose = try? observation.recognizedPoint(.nose)
let root = try? observation.recognizedPoint(.root)
if let nosePoint = nose, let rootPoint = root {
let bodyAngle = atan2(
nosePoint.location.y - rootPoint.location.y,
nosePoint.location.x - rootPoint.location.x
)
let angleFromVertical = abs(bodyAngle - .pi / 2)
if angleFromVertical > .pi / 4 {
print("⚠️ Person bent over or upside down")
}
}
}
一般的な原因:
| 原因 | 解決策 |
|---|---|
| 人物が前かがみ | 直立するようユーザーに促す |
| 逆さま(逆立ち) | 代わりに ARKit を使用する(動的なポーズに適しています) |
| ひらひらした服 | コントラストを上げるか、体にフィットした服を使用する |
| 複数の人物が重なっている | 人物インスタンスセグメンテーションを使用する |
修正所要時間:1 時間
パターン 4:顔検出に関する問題
症状:VNDetectFaceRectanglesRequest が顔を見逃す、または誤った数の顔を返す
診断:
if let faces = request.results as? [VNFaceObservation] {
print("Detected \(faces.count) faces")
for face in faces {
print("Face bounds: \(face.boundingBox)")
print("Confidence: \(face.confidence)")
if face.boundingBox.width < 0.1 {
print("⚠️ Face too small")
}
}
}
一般的な原因:
- 顔が画像の 10% 未満(近くに寄って撮影する)
- 横顔(代わりに顔のランドマークリクエストを使用する)
- 照明が不十分(露出を上げる)
修正所要時間:30 分
パターン 5a:UI のフリーズ(メインスレッド)
症状:Vision リクエストの実行時にアプリがフリーズする
診断(上記のステップ 3 でメインスレッド上にあることを確認)
対処法:
// BEFORE (wrong)
let request = VNGenerateForegroundInstanceMaskRequest()
try handler.perform([request]) // Blocks UI
// AFTER (correct)
DispatchQueue.global(qos: .userInitiated).async {
let request = VNGenerateForegroundInstanceMaskRequest()
try? handler.perform([request])
DispatchQueue.main.async {
// Update UI
}
}
修正所要時間:15 分
パターン 5b:パフォーマンスの問題(バックグラウンドスレッド)
症状:すでにバックグラウンドスレッド上にあるが、処理が遅い/フレームがドロップする
診断:
let start = CFAbsoluteTimeGetCurrent()
try handler.perform([request])
let elapsed = CFAbsoluteTimeGetCurrent() - start
print("Request took \(elapsed * 1000)ms")
if elapsed > 0.2 { // 200ms = too slow for real-time
print("⚠️ Request too slow for real-time processing")
}
一般的な原因と対処法:
| 原因 | 対処法 | 短縮時間 |
|---|---|---|
maximumHandCount = 10 | 必要な数(例:2)に設定する | 50~70% |
| すべてのフレームを処理 | フレームをスキップする(3 フレームごとに処理) | 66% |
| フル解像度の画像 | 1280x720 に縮小する | 40~60% |
| 1 フレームにつき複数のリクエスト | リクエストをまとめるか、交互に実行する | 30~50% |
リアルタイムカメラへの対処法:
// Skip frames
frameCount += 1
guard frameCount % 3 == 0 else { return }
// OR downscale
let scaledImage = resizeImage(sourceImage, to: CGSize(width: 1280, height: 720))
// OR set lower hand count
request.maximumHandCount = 2 // Instead of default
修正所要時間:1 時間
パターン 6:座標変換
症状:UI オーバーレイが誤った位置に表示される
診断:
// Vision point (lower-left origin, normalized)
let visionPoint = recognizedPoint.location
print("Vision point: \(visionPoint)") // e.g., (0.5, 0.8)
// Convert to UIKit
let uiX = visionPoint.x * imageWidth
let uiY = (1 - visionPoint.y) * imageHeight // FLIP Y
print("UIKit point: (\(uiX), \(uiY))")
// Verify overlay
overlayView.center = CGPoint(x: uiX, y: uiY)
よくある間違い:
// ❌ WRONG (no Y flip)
let uiPoint = CGPoint(
x: axiom-visionPoint.x * width,
y: axiom-visionPoint.y * height
)
// ❌ WRONG (forgot to scale from normalized)
let uiPoint = CGPoint(
x: axiom-visionPoint.x,
y: 1 - visionPoint.y
)
// ✅ CORRECT
let uiPoint = CGPoint(
x: axiom-visionPoint.x * width,
y: (1 - visionPoint.y) * height
)
修正所要時間:20 分
パターン 7:混雑したシーン(4 人超)
症状:VNGeneratePersonInstanceMaskRequest が人物を見逃す、または人物をまとめて検出する
診断:
// Count faces
let faceRequest = VNDetectFaceRectanglesRequest()
try handler.perform([faceRequest])
let faceCount = faceRequest.results?.count ?? 0
print("Detected \(faceCount) faces")
// Person instance segmentation
let personRequest = VNGeneratePersonInstanceMaskRequest()
try handler.perform([personRequest])
let personCount = (personRequest.results?.first as? VNInstanceMaskObservation)?.allInstances.count ?? 0
print("Detected \(personCount) people")
if faceCount > 4 && personCount <= 4 {
print("⚠️ Crowded scene - some people combined or missing")
}
対処法:
if faceCount > 4 {
// Fallback: Use single mask for all people
let singleMaskRequest = VNGeneratePersonSegmentationRequest()
try handler.perform([singleMaskRequest])
// OR guide user
showWarning("Please reduce number of people in frame (max 4)")
}
修正所要時間:30 分
パターン 8:VisionKit 固有の問題
症状:ImageAnalysisInteraction で被写体の切り抜き UI が表示されない
診断:
// 1. Check interaction types
print("Interaction types: \(interaction.preferredInteractionTypes)")
// 2. Check if analysis is set
print("Analysis: \(interaction.analysis != nil ? "set" : "nil")")
// 3. Check if view supports interaction
if let view = interaction.view {
print("View: \(view)")
} else {
print("❌ View not set")
}
一般的な原因:
| 症状 | 原因 | 対処法 |
|---|---|---|
| UI が表示されない | analysis が設定されていない | analyzer.analyze() を呼び出し、結果を設定する |
| UI は表示されるが、被写体の切り抜きができない | インタラクションの種類が誤っている | .imageSubject または .automatic を設定する |
| インタラクション時にクラッシュする | インタラクション前にビューが削除されている | ビューをメモリ上に保持する |
対処法:
// Ensure analysis is set
let analyzer = ImageAnalyzer()
let analysis = try await analyzer.analyze(image, configuration: config)
interaction.analysis = analysis // Required!
interaction.preferredInteractionTypes = .imageSubject
修正所要時間:20 分
パターン 9a:テキストが検出されない(画像品質)
症状:VNRecognizeTextRequest が結果を返さない、または空の文字列を返す
診断:
let request = VNRecognizeTextRequest()
request.recognitionLevel = .accurate
try handler.perform([request])
if request.results?.isEmpty ?? true {
print("❌ No text detected")
// Check image quality
print("Image size: \(image.size)")
print("Minimum text height: \(request.minimumTextHeight)")
}
for obs in request.results as? [VNRecognizedTextObservation] ?? [] {
let top = obs.topCandidates(3)
for candidate in top {
print("'\(candidate.string)' confidence: \(candidate.confidence)")
}
}
一般的な原因:
| 原因 | 症状 | 対処法 |
|---|---|---|
| 画像がぼやけている | 結果が返らない | 照明を改善し、カメラを安定させる |
| テキストが小さすぎる | 結果が返らない | minimumTextHeight を小さくするか、近くに寄って撮影する |
| 装飾的なフォント | 誤読または結果が返らない | .accurate 認識レベルを試す |
| コントラストが低い | 結果が部分的 | 照明を改善し、画像のコントラストを上げる |
| 回転したテキスト | .fast では結果が返らない | .accurate を使用する(回転に対応) |
小さなテキストへの対処法:
// Lower minimum text height (default ignores very small text)
request.minimumTextHeight = 0.02 // 2% of image height
修正所要時間:30 分
パターン 9b:文字が誤って認識される(言語/補正)
症状:テキストは検出されますが、文字が誤っています(例:「C001」→「COOL」)
診断:
// Check all candidates, not just first
for observation in results {
let candidates = observation.topCandidates(5)
for (i, candidate) in candidates.enumerated() {
print("Candidate \(i): '\(candidate.string)' (\(candidate.confidence))")
}
}
よくある原因:
| 入力タイプ | 問題 | 対処方法 |
|---|---|---|
| シリアル番号 | 言語補正によって「修正」される | usesLanguageCorrection を無効にします |
| 技術コード | 単語として誤読される | customWords に追加します |
| 英語以外の言語 | ML モデルが誤っている | 正しい recognitionLanguages を設定します |
| 番地 | 装飾された文字が誤読される | 上位候補だけでなく、すべての候補を確認します |
コード/シリアル番号の対処方法:
let request = VNRecognizeTextRequest()
request.usesLanguageCorrection = false // Don't "fix" codes
// Post-process with domain knowledge
func correctSerialNumber(_ text: String) -> String {
text.replacingOccurrences(of: "O", with: "0")
.replacingOccurrences(of: "l", with: "1")
.replacingOccurrences(of: "S", with: "5")
}
修正時間:30 分
パターン 9c:テキスト認識が遅すぎる
症状:テキスト認識に 500ms 以上かかり、リアルタイムカメラでフレームがドロップする
診断:
let start = CFAbsoluteTimeGetCurrent()
try handler.perform([request])
let elapsed = CFAbsoluteTimeGetCurrent() - start
print("Recognition took \(elapsed * 1000)ms")
print("Recognition level: \(request.recognitionLevel == .fast ? "fast" : "accurate")")
print("Language correction: \(request.usesLanguageCorrection)")
よくある原因と対処方法:
| 原因 | 対処方法 | 速度向上 |
|---|---|---|
リアルタイム処理で .accurate を使用している | .fast に切り替えます | 3~5 倍 |
| 言語補正が有効になっている | コードの場合は無効にします | 20~30% |
| 画像全体を処理している | regionOfInterest を使用します | 2~4 倍 |
| すべてのフレームを処理している | フレームをスキップします | 50~70% |
リアルタイム処理の対処方法:
request.recognitionLevel = .fast
request.usesLanguageCorrection = false
request.regionOfInterest = CGRect(x: 0.1, y: 0.3, width: 0.8, height: 0.4)
// Skip frames
frameCount += 1
guard frameCount % 3 == 0 else { return }
修正時間:30 分
パターン 10a:バーコードが検出されない(シンボル体系/サイズ)
症状:VNDetectBarcodesRequest が結果を返さない
診断:
let request = VNDetectBarcodesRequest()
// Don't specify symbologies to detect all types
try handler.perform([request])
if let results = request.results as? [VNBarcodeObservation] {
print("Found \(results.count) barcodes")
for barcode in results {
print("Type: \(barcode.symbology)")
print("Payload: \(barcode.payloadStringValue ?? "nil")")
print("Bounds: \(barcode.boundingBox)")
}
} else {
print("❌ No barcodes detected")
}
よくある原因:
| 原因 | 症状 | 対処方法 |
|---|---|---|
| シンボル体系が誤っている | 検出されない | フィルターを指定しないか、正しいタイプを追加します |
| バーコードが小さすぎる | 検出されない | カメラを近づけるか、画像をトリミングします |
| まぶしさ/反射 | 検出されない | 角度を変えるか、照明を改善します |
| バーコードが破損している | 部分的に検出される/検出されない | バーコードをきれいにするか、画像を改善します |
| リビジョン 1 を使用している | コードが 1 つしか検出されない | 複数検出にはリビジョン 2 以降を使用します |
小さなバーコードの対処方法:
// Crop to barcode region for better detection
let croppedHandler = VNImageRequestHandler(
cgImage: croppedImage,
options: [:]
)
修正時間:20 分
パターン 10b:バーコードのペイロードが誤っている
症状:バーコードは検出されますが、payloadStringValue が誤っているか nil になっている
診断:
if let barcode = results.first {
print("String payload: \(barcode.payloadStringValue ?? "nil")")
print("Raw payload: \(barcode.payloadData ?? Data())")
print("Symbology: \(barcode.symbology)")
print("Confidence: Implicit (always 1.0 for barcodes)")
}
よくある原因:
| 原因 | 対処方法 |
|---|---|
| バイナリバーコード(文字列ではない) | 代わりに payloadData を使用します |
| コードが破損している | 再スキャンするか、バーコードをきれいにします |
| 想定したシンボル体系が誤っている | 実際の symbology 値を確認します |
修正時間:15 分
パターン 11a:DataScanner の画面が真っ暗
症状:表示すると DataScannerViewController が黒い画面または空白の画面になる
診断:
// Check support first
print("isSupported: \(DataScannerViewController.isSupported)")
print("isAvailable: \(DataScannerViewController.isAvailable)")
// Check camera permission
let status = AVCaptureDevice.authorizationStatus(for: .video)
print("Camera access: \(status.rawValue)")
よくある原因:
| 症状 | 原因 | 対処方法 |
|---|---|---|
isSupported = false | デバイスにカメラまたはチップがない | 表示前に確認します |
isAvailable = false | ペアレンタルコントロールまたはアクセス拒否 | カメラの使用許可をリクエストします |
| 黒い画面 | 別のアプリがカメラを使用中 | カメラを独占的に使用できるようにします |
| 表示時にクラッシュする | エンタイトルメントが不足している | カメラ使用目的の説明を追加します |
対処方法:
guard DataScannerViewController.isSupported else {
showError("Scanning not supported on this device")
return
}
guard DataScannerViewController.isAvailable else {
// Request camera access
AVCaptureDevice.requestAccess(for: .video) { granted in
// Retry after access granted
}
return
}
修正時間:15 分
パターン 11b:DataScanner で項目が検出されない
症状:DataScanner にカメラは表示されますが、項目が認識されない
診断:
// Check recognized data types
print("Data types: \(scanner.recognizedDataTypes)")
// Add delegate to see what's happening
func dataScanner(_ scanner: DataScannerViewController,
didAdd items: [RecognizedItem],
allItems: [RecognizedItem]) {
print("Added \(items.count) items, total: \(allItems.count)")
for item in items {
switch item {
case .text(let text): print("Text: \(text.transcript)")
case .barcode(let barcode): print("Barcode: \(barcode.payloadStringValue ?? "")")
@unknown default: break
}
}
}
よくある原因:
| 原因 | 対処方法 |
|---|---|
| データタイプが誤っている | 正しい .barcode(symbologies:) または .text() を追加します |
| テキスト内容タイプのフィルター | フィルターを削除するか、正しいタイプを使用します |
| カメラが近すぎる/遠すぎる | 距離を調整します |
| 照明が不十分 | 照明を改善します |
修正時間:20 分
パターン 12a:書類の端が検出されない
症状:VNDetectDocumentSegmentationRequest が結果を返さない
診断:
let request = VNDetectDocumentSegmentationRequest()
try handler.perform([request])
if let observation = request.results?.first {
print("Document found at: \(observation.boundingBox)")
print("Corners: TL=\(observation.topLeft), TR=\(observation.topRight)")
} else {
print("❌ No document detected")
}
よくある原因:
| 原因 | 対処方法 |
|---|---|
| コントラストが低い | 背景とのコントラストを高くします |
| 長方形ではない | ML は長方形の書類を想定しています |
| まぶしさ/反射 | 照明の角度を変えます |
| 書類がフレームいっぱいに写っている | 背景が少し見えるようにする必要があります |
対処方法:ライブフィードバック付きのガイド付きユーザー体験を提供するには、VNDocumentCameraViewController を使用します。
修正時間:15 分
パターン 12b:遠近補正が正しくない
症状:ドキュメントは抽出されるものの、歪んでいます
診断:
// Verify corner order
print("TopLeft: \(observation.topLeft)")
print("TopRight: \(observation.topRight)")
print("BottomLeft: \(observation.bottomLeft)")
print("BottomRight: \(observation.bottomRight)")
// Check if corners are in expected positions
// TopLeft should have larger Y than BottomLeft (Vision uses lower-left origin)
よくある原因:
| 原因 | 修正方法 |
|---|---|
| 角の順序が正しくない | Vision では左上から反時計回りに指定します |
| 座標系 | 正規化座標をピクセル座標に変換します |
| フィルターパラメーターが正しくない | CIPerspectiveCorrection のパラメーターを確認します |
修正方法:
// Scale normalized to image coordinates
func scaled(_ point: CGPoint, to size: CGSize) -> CGPoint {
CGPoint(x: point.x * size.width, y: point.y * size.height)
}
修正にかかる時間:20 分
本番環境での危機的状況
状況:「分析ボタンをタップするとアプリがフリーズする」として App Store の審査で却下されました
トリアージ(5 分):
- Vision がメインスレッドで実行されていることを確認 → パターン 5a
- 古いデバイス(iPhone 12)で確認 → フリーズします
- プロファイリングを確認:メインスレッドで 800ms
修正(15 分):
@IBAction func analyzeTapped(_ sender: UIButton) {
showLoadingIndicator()
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
let request = VNGenerateForegroundInstanceMaskRequest()
// ... perform request
DispatchQueue.main.async {
self?.hideLoadingIndicator()
self?.updateUI(with: results)
}
}
}
PM への連絡: 「メインスレッドでの Vision 処理が原因で App Store の審査に却下されました。バックグラウンドキューに移すことで修正済みです(業界標準)。iPhone 12 でのテストで修正を確認しました。再提出して問題ありません。」
クイックリファレンステーブル
| 症状 | 考えられる原因 | 最初に確認する項目 | パターン | 推定時間 |
|---|---|---|---|---|
| 結果がない | 何も検出されていない | ステップ 1 の出力 | 1b/1c | 30 分 |
| 検出が不安定 | フレームの端にある | 被写体の位置 | 1c | 20 分 |
| 手のランドマークが欠落 | 信頼度が低い | ステップ 2(信頼度) | 2 | 45 分 |
| 人体のポーズがスキップされる | 人が前かがみになっている | 体の角度 | 3 | 1 時間 |
| UI がフリーズする | メインスレッド | ステップ 3(スレッド処理) | 5a | 15 分 |
| 処理が遅い | パフォーマンスの調整 | リクエストのタイミング | 5b | 1 時間 |
| オーバーレイの位置が正しくない | 座標 | ポイントを出力する | 6 | 20 分 |
| 人物が欠落(4 人超) | 混雑した場面 | 顔の数 | 7 | 30 分 |
| VisionKit に UI が表示されない | 分析が設定されていない | インタラクションの状態 | 8 | 20 分 |
| テキストが検出されない | 画像品質 | 結果の数 | 9a | 30 分 |
| 文字が正しくない | 言語設定 | 候補の一覧 | 9b | 30 分 |
| テキスト認識が遅い | 認識レベル | タイミング | 9c | 30 分 |
| バーコードが検出されない | シンボル体系/サイズ | 結果のダンプ | 10a | 20 分 |
| バーコードのペイロードが正しくない | 破損/バイナリ | ペイロードデータ | 10b | 15 分 |
| DataScanner に何も表示されない | 利用可能性 | isSupported/isAvailable | 11a | 15 分 |
| DataScanner で項目が見つからない | データタイプ | recognizedDataTypes | 11b | 20 分 |
| ドキュメントのエッジが欠落 | コントラスト/形状 | 結果の確認 | 12a | 15 分 |
| 遠近補正が正しくない | 角の順序 | 角の位置 | 12b | 20 分 |
リソース
WWDC:2019-234, 2021-10041, 2022-10024, 2022-10025, 2025-272, 2023-10176, 2020-10653
ドキュメント:/vision, /vision/vnrecognizetextrequest, /visionkit
スキル:axiom-vision, axiom-vision-ref