将Vision的VNTextObservation转换为字符串

58

你找到了什么吗? - Mohammad Zaid Pathan
1
@ZaidPathan 还没有。如果能够解决这个问题,这些课程似乎有很大的潜力。 - Adrian
甚至无法以100%的准确度检测身份证(例如护照)上的单词,这让人感到困难重重。更别提将其转换为文本有多困难了 =.=' - user1872384
1
看起来这将在iOS 13中推出!https://www.hackingwithswift.com/example-code/vision/how-to-use-vnrecognizetextrequests-optical-character-recognition-to-detect-text-in-an-image - Adrian
8个回答

15

这就是如何做到它的方式...

    //
//  ViewController.swift
//


import UIKit
import Vision
import CoreML

class ViewController: UIViewController {

    //HOLDS OUR INPUT
    var  inputImage:CIImage?

    //RESULT FROM OVERALL RECOGNITION
    var  recognizedWords:[String] = [String]()

    //RESULT FROM RECOGNITION
    var recognizedRegion:String = String()


    //OCR-REQUEST
    lazy var ocrRequest: VNCoreMLRequest = {
        do {
            //THIS MODEL IS TRAINED BY ME FOR FONT "Inconsolata" (Numbers 0...9 and UpperCase Characters A..Z)
            let model = try VNCoreMLModel(for:OCR().model)
            return VNCoreMLRequest(model: model, completionHandler: self.handleClassification)
        } catch {
            fatalError("cannot load model")
        }
    }()

    //OCR-HANDLER
    func handleClassification(request: VNRequest, error: Error?)
    {
        guard let observations = request.results as? [VNClassificationObservation]
            else {fatalError("unexpected result") }
        guard let best = observations.first
            else { fatalError("cant get best result")}

        self.recognizedRegion = self.recognizedRegion.appending(best.identifier)
    }

    //TEXT-DETECTION-REQUEST
    lazy var textDetectionRequest: VNDetectTextRectanglesRequest = {
        return VNDetectTextRectanglesRequest(completionHandler: self.handleDetection)
    }()

    //TEXT-DETECTION-HANDLER
    func handleDetection(request:VNRequest, error: Error?)
    {
        guard let observations = request.results as? [VNTextObservation]
            else {fatalError("unexpected result") }

       // EMPTY THE RESULTS
        self.recognizedWords = [String]()

        //NEEDED BECAUSE OF DIFFERENT SCALES
        let  transform = CGAffineTransform.identity.scaledBy(x: (self.inputImage?.extent.size.width)!, y:  (self.inputImage?.extent.size.height)!)

        //A REGION IS LIKE A "WORD"
        for region:VNTextObservation in observations
        {
            guard let boxesIn = region.characterBoxes else {
                continue
            }

            //EMPTY THE RESULT FOR REGION
            self.recognizedRegion = ""

            //A "BOX" IS THE POSITION IN THE ORIGINAL IMAGE (SCALED FROM 0... 1.0)
            for box in boxesIn
            {
                //SCALE THE BOUNDING BOX TO PIXELS
                let realBoundingBox = box.boundingBox.applying(transform)

                //TO BE SURE
                guard (inputImage?.extent.contains(realBoundingBox))!
                    else { print("invalid detected rectangle"); return}

                //SCALE THE POINTS TO PIXELS
                let topleft = box.topLeft.applying(transform)
                let topright = box.topRight.applying(transform)
                let bottomleft = box.bottomLeft.applying(transform)
                let bottomright = box.bottomRight.applying(transform)

                //LET'S CROP AND RECTIFY
                let charImage = inputImage?
                    .cropped(to: realBoundingBox)
                    .applyingFilter("CIPerspectiveCorrection", parameters: [
                        "inputTopLeft" : CIVector(cgPoint: topleft),
                        "inputTopRight" : CIVector(cgPoint: topright),
                        "inputBottomLeft" : CIVector(cgPoint: bottomleft),
                        "inputBottomRight" : CIVector(cgPoint: bottomright)
                        ])

                //PREPARE THE HANDLER
                let handler = VNImageRequestHandler(ciImage: charImage!, options: [:])

                //SOME OPTIONS (TO PLAY WITH..)
                self.ocrRequest.imageCropAndScaleOption = VNImageCropAndScaleOption.scaleFill

                //FEED THE CHAR-IMAGE TO OUR OCR-REQUEST - NO NEED TO SCALE IT - VISION WILL DO IT FOR US !!
                do {
                    try handler.perform([self.ocrRequest])
                }  catch { print("Error")}

            }

            //APPEND RECOGNIZED CHARS FOR THAT REGION
            self.recognizedWords.append(recognizedRegion)
        }

        //THATS WHAT WE WANT - PRINT WORDS TO CONSOLE
        DispatchQueue.main.async {
            self.PrintWords(words: self.recognizedWords)
        }
    }

    func PrintWords(words:[String])
    {
        // VOILA'
        print(recognizedWords)

    }

    func doOCR(ciImage:CIImage)
    {
        //PREPARE THE HANDLER
        let handler = VNImageRequestHandler(ciImage: ciImage, options:[:])

        //WE NEED A BOX FOR EACH DETECTED CHARACTER
        self.textDetectionRequest.reportCharacterBoxes = true
        self.textDetectionRequest.preferBackgroundProcessing = false

        //FEED IT TO THE QUEUE FOR TEXT-DETECTION
        DispatchQueue.global(qos: .userInteractive).async {
            do {
                try  handler.perform([self.textDetectionRequest])
            } catch {
                print ("Error")
            }
        }

    }

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        //LETS LOAD AN IMAGE FROM RESOURCE
        let loadedImage:UIImage = UIImage(named: "Sample1.png")! //TRY Sample2, Sample3 too

        //WE NEED A CIIMAGE - NOT NEEDED TO SCALE
        inputImage = CIImage(image:loadedImage)!

        //LET'S DO IT
        self.doOCR(ciImage: inputImage!)


    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

你可以在这里找到完整的项目,其中包括已训练好的模型!


3
你是怎么训练这个模型的?使用TensorFlow吗? - ricardopereira

15

苹果公司最终更新了Vision来进行OCR。打开playground并将一些测试图片放入Resources文件夹中。在我的例子中,我称它们为“demoDocument.jpg”和“demoLicensePlate.jpg”。

新的类名为VNRecognizeTextRequest。将其放入playground并试一下:

import Vision

enum DemoImage: String {
    case document = "demoDocument"
    case licensePlate = "demoLicensePlate"
}

class OCRReader {
    func performOCR(on url: URL?, recognitionLevel: VNRequestTextRecognitionLevel)  {
        guard let url = url else { return }
        let requestHandler = VNImageRequestHandler(url: url, options: [:])

        let request = VNRecognizeTextRequest  { (request, error) in
            if let error = error {
                print(error)
                return
            }

            guard let observations = request.results as? [VNRecognizedTextObservation] else { return }

            for currentObservation in observations {
                let topCandidate = currentObservation.topCandidates(1)
                if let recognizedText = topCandidate.first {
                    print(recognizedText.string)
                }
            }
        }
        request.recognitionLevel = recognitionLevel

        try? requestHandler.perform([request])
    }
}

func url(for image: DemoImage) -> URL? {
    return Bundle.main.url(forResource: image.rawValue, withExtension: "jpg")
}

let ocrReader = OCRReader()
ocrReader.performOCR(on: url(for: .document), recognitionLevel: .fast)

这里有一个来自WWDC19的深入讨论


1
注意:此类适用于iOS 13.0 Beta及以上版本。 - Newsonic
目前仅支持 en-US。 - NSDeveloper

13

SwiftOCR

我刚刚成功地使用SwiftOCR处理了小型文本集。

https://github.com/garnele007/SwiftOCR

使用了Swift-AI的NeuralNet-MNIST模型进行文本识别。

待做事项:VNTextObservation > SwiftOCR

一旦我把它链接到另一个方面,我会发布一个示例,演示它如何使用VNTextObservation。

OpenCV + Tesseract OCR

我试图使用OpenCV + Tesseract,但是出现了编译错误,后来发现了SwiftOCR。

另请参阅:Google Vision iOS

请注意,Google Vision Text Recognition-Android SDK具有文本检测功能,但也有iOS Cocoapod版本。因此,请保持关注,因为它最终应该会添加文本识别功能到iOS中。

https://developers.google.com/vision/text-overview

//更正:我刚刚尝试过它,但只有Android版本的SDK支持文本检测。

https://developers.google.com/vision/text-overview

如果您订阅了发布版本,点击“SUBSCRIBE TO RELEASES”,您可以看到什么时候将文本检测添加到Cocoapod的iOS部分。


还没有机会去尝试,但我认为你的想法很不错。你可以获取矩形并对子图进行OCR处理。https://dev59.com/fG855IYBdhLWcg3wNhZ1#42497332 - Adrian
Google Vision OCR目前处于测试阶段,只能通过REST从iOS访问,不包含在iOS SDK中。https://cloud.google.com/vision/docs/ocr - brian.clear
你能否将 VNTextObservation 和 SwiftOCR 连接起来了吗? - Jordan H
我看到微软认知服务现在可以读取图像中的文本。https://azure.microsoft.com/zh-cn/services/cognitive-services/computer-vision/ - brian.clear
好的,我刚刚在研究这个问题,似乎现在已经支持iOS了:https://developers.google.com/vision/ios/text-overview - Hammad Tariq

10

补充一下我的进展,如果有更好的解决方案,请告诉我:

我已经成功地在屏幕上绘制出区域框和字符框。苹果的视觉 API 实际上非常高效。您需要将视频的每一帧转换为图像,并将其提供给识别器。这比直接从相机馈送像素缓冲区要准确得多。

 if #available(iOS 11.0, *) {
            guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {return}

            var requestOptions:[VNImageOption : Any] = [:]

            if let camData = CMGetAttachment(sampleBuffer, kCMSampleBufferAttachmentKey_CameraIntrinsicMatrix, nil) {
                requestOptions = [.cameraIntrinsics:camData]
            }

            let imageRequestHandler = VNImageRequestHandler(cvPixelBuffer: pixelBuffer,
                                                            orientation: 6,
                                                            options: requestOptions)

            let request = VNDetectTextRectanglesRequest(completionHandler: { (request, _) in
                guard let observations = request.results else {print("no result"); return}
                let result = observations.map({$0 as? VNTextObservation})
                DispatchQueue.main.async {
                    self.previewLayer.sublayers?.removeSubrange(1...)
                    for region in result {
                        guard let rg = region else {continue}
                        self.drawRegionBox(box: rg)
                        if let boxes = region?.characterBoxes {
                            for characterBox in boxes {
                                self.drawTextBox(box: characterBox)
                            }
                        }
                    }
                }
            })
            request.reportCharacterBoxes = true
            try? imageRequestHandler.perform([request])
        }
    }

现在我正在尝试识别文本。苹果没有提供任何内置的OCR模型。我想使用CoreML来实现这一点,所以我正在尝试将Tesseract训练数据模型转换为CoreML。

您可以在此处找到Tesseract模型:https://github.com/tesseract-ocr/tessdata,我认为下一步是编写一个支持这些类型输入并输出.coreML文件的coremltools转换器。

或者,您可以直接链接到TesseractiOS,并尝试使用从Vision API获取的区域框和字符框进行输入。


14
你是否成功将Tesseract转换为Core ML模型? - user3746428
1
有进展吗?我正在研究这个问题,可能最终会使用视觉API来查找字符,然后以某种方式将其馈送到Tesseract iOS SDK。我宁愿使用CoreML进行支持/加速,但我可能不得不妥协。 - Tyler Kelly
使用这段代码,我如何在屏幕中央拥有一个矩形框,并且只检测该区域内的文本并用方框标记?矩形框外的任何内容都不应被标记。 - Tony Merritt
4
在使用CoreML检测矩形框中的实际字符/字符串方面有进展吗?谢谢。 - Libor Zapletal
你好!在iOS 12中,是否有内置工具来检测实际字符/字符串并使用CoreML 2? - user3191334

3
感谢 GitHub 用户提供的帮助,您可以测试以下示例:https://gist.github.com/Koze/e59fa3098388265e578dee6b3ce89dd8。请注意保留 HTML 标记。
- (void)detectWithImageURL:(NSURL *)URL
{
    VNImageRequestHandler *handler = [[VNImageRequestHandler alloc] initWithURL:URL options:@{}];
    VNDetectTextRectanglesRequest *request = [[VNDetectTextRectanglesRequest alloc] initWithCompletionHandler:^(VNRequest * _Nonnull request, NSError * _Nullable error) {
        if (error) {
            NSLog(@"%@", error);
        }
        else {
            for (VNTextObservation *textObservation in request.results) {
//                NSLog(@"%@", textObservation);
//                NSLog(@"%@", textObservation.characterBoxes);
                NSLog(@"%@", NSStringFromCGRect(textObservation.boundingBox));
                for (VNRectangleObservation *rectangleObservation in textObservation.characterBoxes) {
                    NSLog(@" |-%@", NSStringFromCGRect(rectangleObservation.boundingBox));
                }
            }
        }
    }];
    request.reportCharacterBoxes = YES;
    NSError *error;
    [handler performRequests:@[request] error:&error];
    if (error) {
        NSLog(@"%@", error);
    }
}

问题是,结果是每个检测到的字符的边界框数组。根据我从Vision的会话中了解到的信息,我认为您应该使用CoreML来检测实际字符。
推荐观看WWDC 2017演讲:Vision Framework: Building on Core ML(我也还没有完全观看),在25:50处查看名为MNISTVision的类似示例。
这里还有另一个巧妙的应用程序,演示了使用Keras(Tensorflow)通过CoreML训练MNIST模型进行手写识别Github

3
尝试了SwiftOCR,不是很满意。在应用程序中包含的样本图像转字符串时遇到了问题,所以对于它没有经过训练的图像中的文本会更糟糕。人工智能单一性计划推迟到下周! :) - brian.clear
请参见上面的评论,Google Vision OCR处于beta测试阶段,可以通过REST访问。目前尚未包含在iOS SDK中。https://cloud.google.com/vision/docs/ocr - brian.clear

2

对于那些仍在寻找解决方案的人,我写了一个快速的来完成这个任务。它同时使用了Vision API和Tesseract,并可以用一个方法实现问题所描述的任务。

func sliceaAndOCR(image: UIImage, charWhitelist: String, charBlackList: String = "", completion: @escaping ((_: String, _: UIImage) -> Void))

这个方法会在你的图片中查找文本,返回被发现的字符串和一个原始图片的切片,显示出文本被发现的位置。


2
Firebase ML Kit可以通过其本地Vision API在iOS(和Android)上实现此功能,并且其表现优于Tesseract和SwiftOCR。

这个支持IOS吗?如果是的话,请建议参考链接或示例,我可以继续前进。提前致谢。 - Dhaval Bhadania
阅读我的答案,你会找到你需要的。记得点赞。 - Foti Dim

2
我正在使用Google的Tesseract OCR引擎将图像转换为实际字符串。您需要使用cocoapods将其添加到您的Xcode项目中。尽管Tesseract即使只是将包含文本的图像提供给它,也会执行OCR,但更好/更快地使其运行的方法是使用检测到的文本矩形来提供实际包含文本的图像部分,这就是Apple的Vision框架派上用场的地方。这里是引擎的链接: Tesseract OCR 这是我已经实现了文本检测+ OCR的项目当前阶段的链接: Out Loud - 相机转语音 希望这些对您有所帮助。祝好运!

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接