[iOS9]CITextFeatureで文字領域検出＋TesseractOCRiOSで文字認識してみた

## 概要
* 画像内のテキスト位置を検出してみる
* フリーのライブラリでOCRにかけてみる

結論から言うと、まだ手放しには使えなそうかなーと。

## CITextFeatureについて
iOSにはCoreImageという画像をあれこれするためのFrameworkがあって、
その中に**CIDetector**という、画像内の顔検出などができる機能がありました。
iOS9において、これに**画像内のテキスト位置を検出する**機能が付加されています。

## 実装
文字領域を取得する場合、CIDetectorを以下のように初期化します。

```
CIDetector *ciDetector = [CIDetector detectorOfType:CIDetectorTypeText
                                            context:nil
                                            options:nil];
```

各領域（feature）は、以下のようにArrayに取得します。

```
// optionsにCIDetectorReturnSubFeaturesを設定することでsubfeatureが取得できる
NSArray *features = [ciDetector featuresInImage:ciImage
                                        options:@{CIDetectorReturnSubFeatures: @YES}];
```

CITextFeatureは認識した文字の矩形情報を含んでいますが、
さらにその矩形内の子となる文字の矩形情報を含んでいます。
要は、１文字単位の情報もまとめて取得してくれるということです。
この子要素の情報は、**subFeature**というpropertyからアクセスすることができます。

これらを使って、文字領域の矩形を囲むよう線を引くコードです。

```title:テキストを検出して線で囲む
- (UIImage *) detectText:(UIImage *)inputImage {
    CIImage *ciImage = [CIImage imageWithCGImage:inputImage.CGImage];

    CIDetector *ciDetector = [CIDetector detectorOfType:CIDetectorTypeText
                                                context:nil
                                                options:nil];
    
    NSArray *features = [ciDetector featuresInImage:ciImage
                                            options:@{CIDetectorReturnSubFeatures: @YES}];

    UIGraphicsBeginImageContextWithOptions(inputImage.size, NO, 0.0);
    [inputImage drawInRect:CGRectMake(0, 0, inputImage.size.width, inputImage.size.height)];
    
    for (CITextFeature *feature in features) {
        CGContextRef drawContext = UIGraphicsGetCurrentContext();
        CGContextSetLineWidth(drawContext, 3.f);

        // Y座標が逆になるので変換
        CGRect textRect = feature.bounds;
        textRect.origin.y = inputImage.size.height - (textRect.origin.y + textRect.size.height);
                       
        // 線でテキストを囲む
        CGContextSetStrokeColorWithColor(drawContext, [UIColor blueColor].CGColor);
        CGContextStrokeRect(drawContext,textRect);

        // subfeatureのチェック
        CGContextSetStrokeColorWithColor(drawContext, [UIColor greenColor].CGColor);
        for (CITextFeature *subfeature in feature.subFeatures) {
            CGRect subTextRect = subfeature.bounds;
            subTextRect.origin.y = inputImage.size.height - (subTextRect.origin.y + subTextRect.size.height);
            CGContextStrokeRect(drawContext,subTextRect);            
        }
    }

    UIImage *drawedImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    return drawedImage;
}
```

## さっそくやってみる
画像はKeynoteで適当に作りました。横幅640pxです。
<img width="320" alt="text05_640.png" src="https://qiita-image-store.s3.amazonaws.com/0/71259/828f05ef-ac68-0240-2959-803a590a00c6.png">

### 結果
青枠が、UITextFeatureに検出された文字領域。
赤枠が、その検出された文字領域が含む子要素です。
ちょっと微妙な結果になりました。
![subfeature.png](https://qiita-image-store.s3.amazonaws.com/0/71259/1d27215e-b7eb-3143-5ff9-ef7171d399a3.png)

* あかさたなが取れていない
* 文章によって縦幅が変わっている
* subfeatureの誤認識が多い

( ˘ω˘)うーん…
取れている部分は割と綺麗に矩形が取れていますね。
ひらがなは文字中に隙間が多いので向いていないのかも…？

## TesseractOCRiOSで文字を読み取ってみる
折角なので文字認識まで。
目標として、CITextFeatureで取得した領域に絞ってOCRスキャンをかけると
どうなるのかな？と思ったので試してみました。（下記参考にさせて頂きました）
[【Swift】文字認識ライブラリ、TesseractOCR for iOSを試してみた](http://qiita.com/ktanaka117/items/8dfb6912780c468011d1)

PodsでTesseractOCRiOSを追加してください。
Objective-Cだとこんな感じ。（スキャンに地味に数秒かかります）

```
- (void) analyze:(UIImage *)image {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
        G8Tesseract *tesseract = [[G8Tesseract alloc] initWithLanguage:@"jpn"];
        tesseract.delegate = self;
        tesseract.image = image;

        if ([tesseract recognize]){
            NSLog(@"text : %@", tesseract.recognizedText);
        }
    });
}
```

### 認識させてみた
まずは**画像全体**をスキャンした結果。

```
あかさたな はまやらわ
える しっているか
しにがみは りんごしか たベない
```

綺麗に認識できています。

次に、**CIDetectorから取得できた文字領域を対象に**スキャンを行います。
今回は、上の方で取得できていた青い矩形の領域３つに対し処理を行いました。

```title:textRectに対しOCRをかける
    ...

    CGImageRef srcImageRef = inputImage.CGImage;
    CGImageRef trimmedImageRef = CGImageCreateWithImageInRect(srcImageRef, textRect);
    UIImage *trimmedImage = [UIImage imageWithCGImage:trimmedImageRef];
    [self analyze:trimmedImage];

    ...
```

以下、その結果です。

```
はまやらわ

える しつているか

し にがみは り ん ご しか たベない
```

* 小文字が大文字になってしまっている
* ３行目の区切りが多い

(ヽ´ω`)うーーん……
領域をせばめたせいで、文字内の隙間がよくない感じに認識されているような。
あかさたなはCIDetectorの時点で省かれてしまっていますし微妙ですね。

## まとめ
なんだか結果としては微妙でした。サンプルの文字列が悪かったのかも知れません。
コントラストを上げて文字をはっきりさせたり、
画像の解像度を上げるなどで改善の可能性はあるかと思います。

OCRの速度によっては、指定の文字の領域は塗りつぶしてしまって伏せ字に…とかできないかなあと思いました。