如何在 Swift 6 中将 Vision 文本识别结果标记为 Sendable

问题描述 投票:0回答:1

我有以下代码。它只需运行 Vision API 即可从图像中获取文本。我使用非常简单的 GCD 将繁重的 Vision 操作分派到后台队列,然后将其分派回主队列

completion
:

public struct TextRecognitionResult {
  let observation: VNRecognizedTextObservation
  let text: VNRecognizedText
  let rect: CGRect
}

public enum TextRecognitionUtil {
  
  private static let queue = DispatchQueue(label: "text_recognition", qos: .userInitiated)

  public static func process(
    image: UIImage,
    recognitionLevel: VNRequestTextRecognitionLevel,
    completion: @Sendable @escaping ([TextRecognitionResult]) -> Void)
  {
    guard let cgImage = image.cgImage else {
      completion([])
      return
    }
    let request = VNRecognizeTextRequest { (request, error) in
      guard
        error == nil,
        let observations = request.results as? [VNRecognizedTextObservation]
      else {
        DispatchQueue.main.async {
          completion([])
        }
        return
      }
      
      var results = [TextRecognitionResult]()
      
      // Vision's origin is on bottom left
      let transform = CGAffineTransform.identity
        .scaledBy(x: 1, y: -1)
        .translatedBy(x: 0, y: -image.size.height)
        .scaledBy(x: image.size.width, y: image.size.height)

      for observation in observations {
        guard let text = observation.topCandidates(1).first else { continue }
        let rect = observation.boundingBox.applying(transform)
        results += [TextRecognitionResult(observation: observation, text: text, rect: rect)]
      }
      DispatchQueue.main.async {
        completion(results)
      }
    }
    
    request.recognitionLevel = recognitionLevel
    self.queue.async {
      let handler = VNImageRequestHandler(cgImage: cgImage, options: [:])
      try? handler.perform([request])
    }
  }
}

此代码违反了 Swift 6 并发性,因为

TextRecognitionResult 
不是
Sendable
:

Sending 'results' risks causing data races; this is an error in the Swift 6 language mode

但是,我的

TextRecognitionResult
不能直接标记为Sendable,因为
VNRecognizedTextObservation
VNRecognizedText
不是Sendable,而且它们都是Vision中定义的类型,我无法更改。这是 GCD 中非常常见的做法。我不知道在这里做什么。

ios swift concurrency
1个回答
0
投票

一些观察:

  1. 我会将您的

    struct
    声明为
    Sendable
    :

    public struct TextRecognitionResult: Sendable {
        let observation: VNRecognizedTextObservation
        let text: VNRecognizedText
        let rect: CGRect
    }
    
  2. 我会将

    Vision
    导入为
    @preconcurrency
    :

    @preconcurrency import Vision
    
  3. 我会让

    results
    不可变(使用
    compactMap
    而不是
    for
    循环):

    public enum TextRecognitionUtil {
        enum TextRecognitionUtilError: Error {
            case noCgImage
            case notVNRecognizedTextObservation
        }
    
        private static let queue = DispatchQueue(label: "text_recognition", qos: .userInitiated)
    
        public static func process(
            image: UIImage,
            recognitionLevel: VNRequestTextRecognitionLevel = .accurate,
            completion: @Sendable @escaping (Result<[TextRecognitionResult], Error>) -> Void)
        {
            guard let cgImage = image.cgImage else {
                completion(.failure(TextRecognitionUtilError.noCgImage))
                return
            }
    
            let request = VNRecognizeTextRequest { request, error in
                guard
                    error == nil,
                    let observations = request.results as? [VNRecognizedTextObservation]
                else {
                    DispatchQueue.main.async {
                        completion(.failure(error ?? TextRecognitionUtilError.notVNRecognizedTextObservation))
                    }
                    return
                }
    
                // Vision's origin is on bottom left
                let transform = CGAffineTransform.identity
                    .scaledBy(x: 1, y: -1)
                    .translatedBy(x: 0, y: -image.size.height)
                    .scaledBy(x: image.size.width, y: image.size.height)
    
                let results = observations.compactMap { (observation) -> TextRecognitionResult? in
                    guard let text = observation.topCandidates(1).first else { return nil }
                    let rect = observation.boundingBox.applying(transform)
                    return TextRecognitionResult(observation: observation, text: text, rect: rect)
                }
                DispatchQueue.main.async {
                    completion(.success(results))
                }
            }
    
            request.recognitionLevel = recognitionLevel
            self.queue.async {
                let handler = VNImageRequestHandler(cgImage: cgImage)
            }
        }
    }
    

    我还将闭包返回类型更改为

    Result
    ,以便调用者可以消除错误和无识别结果之间的歧义。

Swift 6 可能会稍微简化一下,但以上内容适用于 Xcode 15.4。

© www.soinside.com 2019 - 2024. All rights reserved.