VNDetectTextRectanglesRequest: 機械学習を用いて日本語の単語の領域を検出します。

<img width="260" alt="Screen Shot 2020-05-12 at 1.06.19 PM.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/2626f13e-5012-b4fd-86d4-c1613bbf21df.png">

## 「Vision」について：

「Vision」は、画像や動画などの入力を処理できる多くの機械学習アルゴリズムを含むフレームワークです。テキストを処理する既存の関数「VNRecognizeTextRequest」がありますが、これは英語しかサポートしませんのでご注意ください。また、「Qiita」で「VNRecognizeTextRequest」を検索すると、それに関する記事がすでに出ています。

この記事では、検出された日本語テキストの周りに長方形を描く方法が説明されています。

## フレームワークのコンポーネント

#### `VNDocumentCameraViewController` (VisionKit):

これはiOSフレームワークに組み込まれているスキャナーサポートで、用紙の長方形の領域を自動的に検出します。それからユーザーはボタンをクリックしてドキュメントの画像を撮影し、その画像データをデリゲートで受け取ることができます。

#### `VNDetectTextRectanglesRequest `

「Vision」フレームワークの関数で、テキストがある領域の境界ボックスを取得できます。

#### `VNDetectTextRectanglesRequest` を使用する理由

1. `VNRecognizeTextRequest` は日本語をサポートしていないためです
2. `VNDetectTextRectanglesRequest` で認識された領域を切り抜き、その結果を独自のテキスト認識アルゴリズムに入力できます。この場合、`VNDetectTextRectanglesRequest` に文字ごとに結果を表示するように要求すれば、各文字を認識できます。

## さあ始めましょう。

### ドキュメントをスキャンする

`VNDocumentCameraViewController` の実装は簡単です：

```swift
import VisionKit
```

```swift
@IBAction func actionPresentVision(){
    let documentCameraViewController = VNDocumentCameraViewController()
    documentCameraViewController.delegate = self
    present(documentCameraViewController, animated: true)
    //以前に追加したレイヤーを削除する
    for layer in imageView.layer.sublayers ?? [] {
        layer.removeFromSuperlayer()
    }
}
```

ユーザーが撮影した画像を受け取るプログラムデリゲートも実装する必要があります：

```swift
extension ViewController: VNDocumentCameraViewControllerDelegate {

    func documentCameraViewController(_ controller: VNDocumentCameraViewController, didFinishWith scan: VNDocumentCameraScan) {
        if let firstImage = scan.imageOfPage(at: 0).cgImage {
            //TODO
            processImage(input: firstImage)
            controller.dismiss(animated: true, completion: nil)
        }
    }
    
    func documentCameraViewControllerDidCancel(_ controller: VNDocumentCameraViewController) {
        controller.dismiss(animated: true, completion: nil)
    }

}
```

押すと "actionPresentVision" を呼び出す `UIButton` を Storyboard に追加します。そして画面に画像を表示する `processImage` 関数を設定します。

```swift
@IBOutlet weak var imageView: UIImageView!

override func viewDidLoad() {
    super.viewDidLoad()
    imageView.contentMode = .scaleToFill
}

func processImage(input: CGImage) {
    let image = UIImage(cgImage: input)
    imageView.image = image
}
```

![ezgif-7-b472f5ddaf87.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/293b6e76-115c-cdfd-3746-e36c309f413a.gif)

(Keep Scan -> Save)

画像の部分ができたので、機械学習の部分にとりかかりましょう：

### 機械学習を使ってテキストを認識する

まず、フレームワークをインポートします：

```swift
import Vision
```
次に、前回の記事で書いたように、機械学習による結果を処理するためのハンドラーを設定し、`VNImageRequestHandler` を使って画像データをリクエストにインプットする必要があります。

[アニメ画像の昼/夜認識システムの作成：(2/3) そのモデルと「Vision」フレームワークを用いて新規画像からラベルを取得する。](https://qiita.com/MaShunzhe/items/c829b3219b1afe335391)

前述の記事では `VNCoreMLRequest` を 使っていますが、ここでは `VNDetectTextRectanglesRequest` を使うことにもご注意ください：

```swift
func processImage(input: CGImage) {
    let request = VNDetectTextRectanglesRequest { (request, error) in
        //ここに結果が表示されます
        if let results = request.results as? [VNTextObservation] {
            print(results)
        }
    }
    //次にリクエストに画像を受け渡します
    let handler = VNImageRequestHandler(cgImage: input, options: [:])
    DispatchQueue.global(qos: .userInteractive).async {
        do {
            try handler.perform([request])
        } catch {
             print(error)
        }
    }
}
```

こちらが得られた結果です：

```
[

<VNTextObservation: 0x282a34fa0> CCD5518B-FC57-42E2-8475-7E4883F3782A requestRevision=1 confidence=1.000000 boundingBox=[0.186391, 0.678969, 0.568047, 0.0543175], 

<VNTextObservation: 0x282a348c0> DCEC8A13-FC2F-4D55-A2C0-D88EAEAF304C requestRevision=1 confidence=1.000000 boundingBox=[0.363905, 0.550836, 0.224852, 0.0557103], 

<VNTextObservation: 0x282a34d20> 93FEE50E-D6B6-49F4-983E-C8025D45A4EC requestRevision=1 confidence=1.000000 boundingBox=[0.381657, 0.39624, 0.198225, 0.051532], 

<VNTextObservation: 0x282a34aa0> 2215CEAB-09B5-40E2-86C0-5DC33F354057 requestRevision=1 confidence=1.000000 boundingBox=[0.372781, 0.236072, 0.210059, 0.051532], 

<VNTextObservation: 0x282a34c80> E88AA596-EF6E-4CD1-B19B-AC7EE7A30936 requestRevision=1 confidence=1.000000 boundingBox=[0.372781, 0.0759053, 0.215976, 0.0501393]

]
```
紙にはちょうど5つの単語が書かれています！各「VNTextObservation」は画像内の各単語のバウンディングボックスを表しています。

あとは、結果を画面上に視覚的に表示できるようにするだけです。:

### 視覚的に表示

```swift
func drawBoundingBox(forResult: VNTextObservation) {
    let outline = CALayer()
    //バウンディングボックスの座標はパーセンテージとして与えられます。実際の画面の座標に変換する必要があります
    let x = forResult.topLeft.x * imageView.frame.width
    let y = (1 - forResult.topLeft.y) * imageView.frame.height
    ///横幅と高さは「boundingBox」から取得できます
    let width = forResult.boundingBox.width * imageView.frame.width
    let height = forResult.boundingBox.height * imageView.frame.height
    outline.frame = CGRect(x: x, y: y, width: width, height: height)
    outline.borderColor = UIColor.green.cgColor
    outline.borderWidth = 3
    imageView.layer.addSublayer(outline)
}
```

また、「drawBoundingBox」を呼び出すようにリクエストを変更するのを忘れないでください。

```swift
let request = VNDetectTextRectanglesRequest { (request, error) in
    if let results = request.results as? [VNTextObservation] {
        for result in results {
            DispatchQueue.main.async {
                self.imageView.image = UIImage(cgImage: input)
                self.drawBoundingBox(forResult: result)
            }
        }
    }
}
```

こちらが結果です。:

<img width="350" alt="Screen Shot 2020-05-12 at 1.06.19 PM.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/2626f13e-5012-b4fd-86d4-c1613bbf21df.png">

パラメーターを「forResult.characterBoxes」に変更すれば、「drawBoundingBox」の同じコードを使って各文字のバウンディングボックスを描画することもできます。

### 一部のバウンディングボックスが表示されない場合がありますが、これは正常です。機械学習には限界があり、光の当たり具合が適切ではないと不正確なデータを返してしまうことがあるからです。

### コード

コードはこちらにあります: https://github.com/mszopensource/VisionTextDetection

### 次のステップ

日本語の文字を認識する機械学習モデルをトレーニングし、「forResult.characterBoxes」の各文字の画像を用いて各文字を認識させることができます。その方法は既に別記事で執筆済みです:

1.[「Core ML」モデルを「Create ML」で既存のラベル付けされたアニメ画像を入力として用いてトレーニングする。](https://qiita.com/MaShunzhe/items/9933eab603781154232f)
2. [そのモデルと「Vision」フレームワークを用いて新規画像からラベルを取得する。](https://qiita.com/MaShunzhe/items/c829b3219b1afe335391)

---

:relaxed: [Twitter @MszPro](https://twitter.com/MszPro)

:sunny: [私の公開されているQiita記事のリストをカテゴリー別にご覧いただけます。](http://mszpro.com/ioskiji/)
<img width="150" src="https://cdn.mszmagic.com/static-web-content/AppClipImage_small.png">