視覚によるテキスト認識


このポストでは、私はどのようにOCRを実装したかを示します、イメージの上で、または、カメラ・フィードからのライブはVisionを使用することによって、そして、同じビュー・コントローラを使用して、バーコードのライブ・スキャンを実行します.私は既にバーコードについて書いたin the past それで、私はここでその部分に関する詳細に入りません.
私のパッケージはgflivescannerと呼ばれますGitHub そして、あるsample project パッケージをインポートして実験しました.

ビジョン
The Vision フレームワークは、IOSの11で導入され、開発者は、顔とテキストの検出、バーコードの認識と追跡を実行することができます.これは、特定のオブジェクトを認識するためのモデルを提供するためにcoremlで使用することができますが、これはこのポストの目的ではありません.あなたがそれらの話題に興味があるならばthis WWDC video 2019年から.
私たちがビジョンから必要とするものは、イメージのOCRを実行するその能力です、そして、我々はVNRegiizeTextQuest(IOS 13で利用できる)を使用します.この章のコードはthis class 私のパッケージの.
private func processOCRRequest(_ request:GFOcrHelperRequest) {
    var requestHandler:VNImageRequestHandler
    if let orientation = request.orientation {
        requestHandler = VNImageRequestHandler(cgImage: request.image,
                                               orientation: orientation,
                                               options: [:])
    }
    else {
        requestHandler = VNImageRequestHandler(cgImage: request.image)
    }
    let visionRequest = VNRecognizeTextRequest(completionHandler: recognizeTextHandler)
    visionRequest.recognitionLevel = useFastRecognition ? .fast : .accurate
    do {
        try requestHandler.perform([visionRequest])
    } catch {
        print("Error while performing vision request: \(error).")
        currentRequestProcessed(strings: nil)
    }
}
これはリクエストを処理するための関数です.イメージを含んでいる構造体、そのオプションのオリエンテーションと認識されたテキストを受け取るコールバックがあります.
最初に、vnImageReceithAndlerが作成されます.これは、イメージに対する要求を実行する責任があるオブジェクトです.私たちの例では、OCRリクエストをパスしますが、もっと前に述べたように.
見ることができるように、我々はイメージの向きでリクエストハンドラーを初期化することができます、したがって、我々が我々のイメージが逆さまにあるということを知っているならば、我々はそれを指定することができて、認識プロセスを助けることができます.
ハンドラは、同じイメージに複数のリクエストを実行することができます.
この例では、OCRだけを必要とするので、以下のようにして完了するハンドラを使用してVNRegiizeTextQuestを作成します.リクエストには、設定可能なRecognitionLevelオプションがあります.高速場合はできるだけ早く(必要に応じてライブキャプチャしたい場合)結果をしたい.あなたは少し長く待つことができる場合は、より良い結果を得る正確.
また、言語の配列を指定することも可能ですので、OCRはあなたが設定した言語を優先します.
private func recognizeTextHandler(request: VNRequest, error: Error?) {
    guard let observations = request.results as? [VNRecognizedTextObservation] else {
        currentRequestProcessed(strings: nil)
        return
    }
    let recognizedStrings = observations.compactMap { observation in
        return observation.topCandidates(1).first?.string
    }
    currentRequestProcessed(strings: recognizedStrings)
}
これはVNRegiizeTextQuestに設定された補完ハンドラであり、リクエストが成功したかエラーが発生した後に呼び出されます.
結果の配列は、その後、compactmapを介して処理され、各観測に対して、先頭の候補を文字列として取得します.TopIdentity関数は10までのパラメータで呼び出すことができますので、1つの観測から可能な文字列を持つことができます.1でそれを呼び出すことで、単一の観測から最も可能性の高い文字列を取得するだけで、複数の可能な値を扱う必要はありません.
最後に、指定されたイメージから検出された文字列の配列でコールバックが呼び出されます.

ライブOCR
私のパッケージは静的なイメージのOCRを実行するために使用することができます、あなたはUImagePickerによって得られるイメージでヘルパークラスを呼ぶことができました、しかし、私はそれをOfrまたはバーコード認識を提供するために生きている走査を実行するその主な目的としてgflivescannerと呼びました.
ライブスキャンを実行するにはGFLiveScannerViewController またはあなたのビューに子供として追加します.
ビューコントローラは、フルスクリーン、または閉じるボタンを使用してツールバーを設定するように構成することができます.VCの使い方の例はthis project .
私は全体のビューコントローラをここで説明しません、しかし、私はライブ走査部分について話すだけです、それで、カメラからのフィードを得る方法はOCRにそれを渡して、スクリーンでプレビューを示します、それで、ユーザーはカメラを正しい方向にテキストを見つけるために指すことができます.

Avoundationでカメラにアクセス
ライブキャプチャのためのカメラにアクセスするにはAVFoundation フレームワーク.
私たちはAvFoundationで行うことができますが、それだけでなく、画像のためだけではなく、例えば、音声、記録、再生オーディオ、およびビデオを編集するテキストを実行することができます多くのことができます.
我々の焦点はcamera capture , 我々は、セットアップセッションを取得し、プレビュー層を取得し、OCRやバーコードの認識を実行するためにライブキャプチャから画像を取得します.
このポストでは、私はバーコードよりOCR走査に集中します、Gflivescannerプロトコルを実行している2つのクラスがあります、そして、この章は約ですGFLiveOcrViewController .
private func configureCaptureSession() {
    guard let device = AVCaptureDevice.default(.builtInWideAngleCamera,
                                               for: .video,
                                               position: .back),
          let input = try? AVCaptureDeviceInput(device: device) else {
        return
    }
    let session = AVCaptureSession()
    session.addInput(input)
    let output = AVCaptureVideoDataOutput()
    output.videoSettings = [String(kCVPixelBufferPixelFormatTypeKey): Int(kCVPixelFormatType_32BGRA)]
    output.setSampleBufferDelegate(self, queue: DispatchQueue.main)
    session.addOutput(output)
    self.captureSession = session
}
まず、入力デバイスを取得する必要があります.私はワイドカメラを使用しますが、あなたはそれらのカメラとモデルのための超ワイドまたは望遠からキャプチャすることを選択することがあります.また、深さの情報をキャプチャする必要がある場合は、正面カメラ、またはTrueDepthカメラからキャプチャする前からキャプチャすることがあります.
次に、我々は我々が入力として前に選んだカメラでavccaptureessionを作成します.
次に、AvcaptureVideoDataOutputをセッションに追加し、クラスをSampleBufferDirecateに設定します.このデリゲートは、CMSampleBufferの形式で処理できるようになるたびに呼び出されます.このオブジェクトは、セッションに応じてビデオ、オーディオまたはイメージを含んでいるかもしれません.

キャプチャセッションから画像を取得する
キャプチャセッションを設定すると、私たちのデリゲート関数captureoutputにCMSampleBuffer
func captureOutput(_ output: AVCaptureOutput,
                   didOutput sampleBuffer: CMSampleBuffer,
                   from connection: AVCaptureConnection) {
    guard let image = GFLiveScannerUtils.getCGImageFromSampleBuffer(sampleBuffer) else {
        return
    }
    let orientation = GFLiveScannerUtils.imageOrientationForCurrentOrientation()
    ocrHelper.getTextFromImage(image, orientation:orientation) { success, strings in
        if let strings = strings {
            self.delegate?.capturedStrings(strings:strings)
            self.capturedStrings = strings
        }
    }
}
一旦イメージを持っていれば、OCRヘルパークラスを呼び出してテキストを取得することができます.したがって、CMSampleBufferからCGImageを取得する方法を見てみましょう.コードはGFLiveScannerUtils .
class func getCGImageFromSampleBuffer(_ sampleBuffer:CMSampleBuffer) -> CGImage? {
    guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
        return nil
    }
    CVPixelBufferLockBaseAddress(pixelBuffer, .readOnly)
    let baseAddress = CVPixelBufferGetBaseAddress(pixelBuffer)
    let width = CVPixelBufferGetWidth(pixelBuffer)
    let height = CVPixelBufferGetHeight(pixelBuffer)
    let bytesPerRow = CVPixelBufferGetBytesPerRow(pixelBuffer)
    let colorSpace = CGColorSpaceCreateDeviceRGB()
    let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedFirst.rawValue | CGBitmapInfo.byteOrder32Little.rawValue)
    guard let context = CGContext(data: baseAddress, width: width,
                                  height: height, bitsPerComponent: 8, bytesPerRow: bytesPerRow,
                                  space: colorSpace, bitmapInfo: bitmapInfo.rawValue) else {
        return nil
    }
    let cgImage = context.makeImage()

    return cgImage
}
私は右の設定を見つけるために私はしばらくかかったので、私はあなたとそれらを共有して満足している.
私はあなたが上記のパラメータを使用してこの仕事をすることができました、そして、AvcaptureVideoDataOutputのためにビデオ形式としてKCVPixelformitypeThank 32 BGRAを設定することによって.

プレビューを表示する
我々の使用がOCRまたはバーコードのために何かをスキャンしなければならないならば、彼は明らかに、彼が興味を持っている物に電話を向けるために、カメラ・フィードの生のプレビューを見る必要があります.
AvFoundationはAvcaptureessionからCallayerを取得する機能を与えるので、我々は我々のビューの1つにこの層を追加し、プレビューを表示することができます.
private func configurePreview() {
    guard let session = captureSession else {return}
    if self.previewLayer == nil {
        let previewLayer = AVCaptureVideoPreviewLayer(session: session)
        previewLayer.frame = cameraView.layer.bounds
        previewLayer.videoGravity = .resizeAspectFill
        cameraView.layer.addSublayer(previewLayer)
        self.previewLayer = previewLayer
    }
}
これは、我々がセッションからCallayerを得ることができる方法です.VideoGravityプロパティは、ResizeEditまたはResizeSpectFillに設定できます.両方のアスペクト比を維持し、フィットしたり、ビューの範囲内のビデオを埋める.

バーコードをスキャンする
私は以前のプロジェクトからバーコードスキャナーのためのコードのほとんどを取ったthis post あなたがより発見したいならば.
主な違いはキャプチャセッションを設定することです
let metadataOutput = AVCaptureMetadataOutput()
session.addOutput(metadataOutput)
metadataOutput.setMetadataObjectsDelegate(self, queue: queue)
ビデオ出力に加えて、avapturemetadataoutputをセッションに加えます.
public func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
    let codes = getBarcodeStringFromCapturedObjects(metadataObjects: metadataObjects)
    delegate?.capturedStrings(strings: codes)

private func getBarcodeStringFromCapturedObjects(metadataObjects:[AVMetadataObject]) -> [String] {
    var rectangles = [CGRect]()
    var codes:[String] = []
    for metadata in metadataObjects {
        if let object = metadata as? AVMetadataMachineReadableCodeObject,
            let stringValue = object.stringValue {
            codes.append(stringValue)
            if drawRectangles {
                rectangles.append(object.bounds)
            }
        }
    }
    if drawRectangles {
        drawRectangles(rectangles)
    }
    return codes
}
AddCapataOutputは、Avcaptureessionから利用できる新しいメタデータがあるたびに呼び出されます.各メタデータを反復処理することができますので、キャストを試みるにはAVMetadataChanneAddableOdDealオブジェクトとして文字列値を追加します.
あなたがこのポストを面白いと思っていたことを願って、迅速なパッケージマネージャを介して私のパッケージをインポートしてください.これはmy article あなたが助けを必要とするならば、あなたのプロジェクトでSPMを使うことについて.
ハッピーコーディング🙂
Original post