You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用VNRecognizeTextRequest识别PKCanvasView手写文字失败求助

解决PKCanvasView手写文字无法被Vision Kit识别的问题

我在SwiftUI App中使用Vision Kit识别PKCanvasView上的手写文字(支持Apple Pencil或手指输入),但点击“Recognize Text”按钮始终返回“No text recognized.”。测试环境为Xcode 16.x、iPadOS 18(真机和模拟器均测试),完整代码如下:

import SwiftUI
import PencilKit
import Vision

@main
struct PencilVisionApp: App {
    var body: some Scene {
        WindowGroup {
          PencilDrawingView()
        }
    }
}

struct PencilDrawingView: View {
    @State private var canvasView = PKCanvasView()
    @State private var currentTool: PKTool = PKInkingTool(.pen, color: .black, width: 5)
    @State private var recognizedText: String = ""

    var body: some View {
        VStack(spacing: 16) {
            Text("Draw with Apple Pencil")
                .font(.headline)

            PencilCanvas(canvasView: $canvasView, tool: $currentTool)
                .frame(height: 300)
                .background(Color(.systemGray6))
                .cornerRadius(10)
                .overlay(RoundedRectangle(cornerRadius: 10).stroke(Color.gray))

            HStack(spacing: 12) {
                Button("Undo") {
                    canvasView.undoManager?.undo()
                }
                Button("Redo") {
                    canvasView.undoManager?.redo()
                }
                Button("Eraser") {
                    currentTool = PKEraserTool(.vector)
                }
                Button("Pen") {
                    currentTool = PKInkingTool(.pen, color: .black, width: 5)
                }
                Button("Recognize Text") {
                    recognizeTextFromDrawing()
                }
            }

            if !recognizedText.isEmpty {
                VStack(alignment: .leading, spacing: 8) {
                    Text("Recognized Text:")
                        .font(.headline)
                    ScrollView {
                        Text(recognizedText)
                            .frame(maxWidth: .infinity, alignment: .leading)
                            .padding()
                            .background(Color(.secondarySystemBackground))
                            .cornerRadius(8)
                    }
                }
            }

            Spacer()
        }
        .padding()
    }

    private func recognizeTextFromDrawing() {
        guard !canvasView.drawing.strokes.isEmpty else {
            recognizedText = "Canvas is empty — nothing to recognize."
            return
        }

        let bounds = canvasView.drawing.bounds.insetBy(dx: -10, dy: -10)
        let image = canvasView.drawing.image(from: bounds, scale: 3.0)

        guard let cgImage = image.cgImage else {
            recognizedText = "Failed to generate image."
            return
        }

        let request = VNRecognizeTextRequest { request, error in
            if let error = error {
                recognizedText = "Recognition failed: \(error.localizedDescription)"
                return
            }

            let observations = request.results as? [VNRecognizedTextObservation] ?? []
            let text = observations.compactMap { $0.topCandidates(1).first?.string }.joined(separator: "\n")

//            DispatchQueue.main.async {
                recognizedText = text.isEmpty ? "No text recognized." : text
//            }
        }

      request.recognitionLevel = .accurate
      request.usesLanguageCorrection = false
      request.recognitionLanguages = ["en-US"]

        let handler = VNImageRequestHandler(cgImage: cgImage, options: [:])
        DispatchQueue.global(qos: .userInitiated).async {
            try? handler.perform([request])
        }
    }
}

struct PencilCanvas: UIViewRepresentable {
    @Binding var canvasView: PKCanvasView
    @Binding var tool: PKTool

    func makeUIView(context: Context) -> PKCanvasView {
        canvasView.drawingPolicy = .anyInput
        canvasView.tool = tool
        return canvasView
    }

    func updateUIView(_ uiView: PKCanvasView, context: Context) {
        uiView.tool = tool
    }
}

解决建议

  • 修复UI更新线程问题
    Vision请求在后台队列执行,回调中更新@State变量recognizedText必须回到主线程,否则无法正确刷新UI,甚至导致识别结果赋值失败。取消注释原代码中的主线程包裹:

    DispatchQueue.main.async {
        recognizedText = text.isEmpty ? "No text recognized." : text
    }
    
  • 优化图像生成逻辑
    当前图像生成的边界可能裁剪了手写内容,且背景对比度不足,影响Vision识别效果。修改图像生成代码:

    // 使用canvasView的完整边界,设置白色背景增强对比度
    let image = canvasView.drawing.image(from: canvasView.bounds, scale: UIScreen.main.scale, backgroundColor: .white)
    

    避免使用drawing.bounds.insetBy,确保所有手写内容都被包含在生成的图像中。

  • 调整Vision识别参数
    针对手写场景优化请求配置,提升识别成功率:

    request.recognitionLevel = .fast // 手写识别优先选择快速模式,准确率足够
    request.usesLanguageCorrection = true // 开启语言修正,适配手写的不规则性
    request.recognitionLanguages = ["en-US"] // 根据实际需求添加目标语言
    
  • 完善PKCanvasView配置
    在PencilCanvas的makeUIView方法中添加背景设置,确保画布背景与手写颜色对比度足够:

    canvasView.backgroundColor = .white
    canvasView.isOpaque = true
    
  • 调试图像生成结果
    可以临时添加代码将生成的图像保存到相册,验证图像是否正确包含手写内容:

    UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
    

    注意需要在Info.plist中添加NSPhotoLibraryAddUsageDescription权限描述。

内容的提问来源于stack exchange,提问作者Phantom59

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 08:40:58