关于 axiom-vision-diag
面向使用 Apple Vision 框架的开发者的故障排查指南,按环境、置信度、线程、坐标和 API 可用性定位检测、识别及性能问题,并提供诊断步骤和修复模式。
主要功能
- 01诊断 Vision 检测问题
- 02检查置信度和运行线程
- 03排查坐标转换错误
- 04处理 API 可用性差异
- 05优化实时处理性能
适用场景
- 01调查手部或人体姿态未识别
- 02修复界面叠加位置偏差
- 03排查文本或条码识别失败
- 04降低实时图像处理卡顿
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ charleswiltgen/ axiom --skill axiom-vision-diag安装后在 Claude Code 中输入/axiom-vision-diag确认已加载。
详细介绍
Vision 框架诊断
系统地排查 Vision 框架问题:检测不到主体、缺少关键点、置信度低、性能问题、坐标不匹配、文本识别失败、条形码检测问题以及文档扫描问题。
概述
核心原则:Vision 无法正常工作时,问题通常出在:
- 环境(光照、遮挡、画面边缘)——40%
- 置信度阈值(忽略低置信度数据)——30%
- 线程(阻塞主线程导致界面冻结)——15%
- 坐标(混淆左下角和左上角原点)——10%
- API 可用性(在较旧设备上使用 iOS 17+ API)——5%
调试代码之前,务必先检查环境和置信度。
危险信号
表明存在 Vision 特定问题的症状:
| 症状 | 可能原因 |
|---|---|
| 完全检测不到主体 | 画面边缘、光照不足、主体太小 |
| 手部关键点间歇性地为 nil | 手靠近边缘、与相机平行、手套或遮挡 |
| 身体姿势跳过若干帧 | 人物弯腰、倒立、穿着飘动的衣物 |
| 处理期间界面冻结 | 在主线程上运行 Vision |
| 覆盖内容位置错误 | 坐标转换(左下角与左上角) |
| 在较旧设备上崩溃 | 使用 iOS 17+ API,但未检查 @available |
| 人物分割漏掉人物 | 场景中有 >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% |
| 每帧执行多个请求 | 批处理或交替执行请求 | 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 min
模式 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-5x |
| 启用了语言校正 | 对代码禁用语言校正 | 20-30% |
| 处理完整图像 | 使用 regionOfInterest | 2-4x |
| 处理每一帧 | 跳过帧 | 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 min
模式 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 | 只能识别一个代码 | 要识别多个代码,请使用修订版 2+ |
小型条形码的修复方法:
// Crop to barcode region for better detection
let croppedHandler = VNImageRequestHandler(
cgImage: croppedImage,
options: [:]
)
修复耗时:20 min
模式 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 min
模式 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 min
模式 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 min
模式 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 min
模式 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 说明: “App Store 审核被拒,原因是 Vision 在主线程上处理。已通过移至后台队列修复(行业标准做法)。在 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