我有以下代码。它只需运行 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 中非常常见的做法。我不知道在这里做什么。
一些观察:
我会将您的
struct
声明为 Sendable
:
public struct TextRecognitionResult: Sendable {
let observation: VNRecognizedTextObservation
let text: VNRecognizedText
let rect: CGRect
}
我会将
Vision
导入为 @preconcurrency
:
@preconcurrency import Vision
我会让
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。