Mobile Visionの「Text Recognition」(文字認識)についてです。
が、しかし2017.10現在未だ日本語に対応していません。やはり日本語や中国語のように文字の種類が多い言語は未だ難しいみたいです。
なので、今回は英語で試してみたいと思います。

実際にこちらで作成したサンプルアプリで試した結果は以下のようになります。
文字認識した結果を元に文字を囲んでいますが、その囲んだ部分を拡大すると以下のようになります。
概ね合っているようにも見えますが、段落の分割が間違っていたり、単語の区切りが間違っていたりします。未だ英語でも完璧とはいかないようです。

環境設定

Text RecognitionもMobile Visionの一部なので、以下のようにapp/build.gradleに依存関係を追加することが設定が完了します。
app/build.gradle
dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.google.android.gms:play-services-vision:11.0.0'    // ★追加
    compile 'com.android.support:appcompat-v7:25.3.1'
    compile 'com.android.support.constraint:constraint-layout:1.0.2'
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:recyclerview-v7:25.3.1'
}
これを設定することで、Mobile Vision APIが使用可能となります。

実装

動画については、先の顔認識のブログにも書かせてもらったように、動画をJCodecでフレーム分割して認識させるだけなので、今回は静止画とカメラからの入力映像についてのみ記載します。

静止画に対する文字認識

実装の流れは「Mobile Vision API - Face Detect(静止画)」と同じように以下のようになります。
  1. TextRecognizer.Builderインスタンスを生成し、それから文字認識条件が設定されたTextRecognizerインスタンスを生成
  2. Frame.Builderインスタンスを生成し、それから静止画像が設定されたFrameインスタンスを生成
  3. TextRecognizerインスタンスにFrameインスタンスを設定して、文字認識を実施
文字認識が完了すると、TextBlockSparseArrayに格納されてきます。
TextBlockは段落程度の文章に対して解析した結果が格納されて、その中に細かく分割された文章や言葉が保持されています。

1. TextRecognizer.Builderインスタンスを生成し、それから文字認識条件が設定されたTextRecognizerインスタンスを生成

顔認識のFaceDetectorとFaceDetector.Buildと同じような構成がとられています。ただし、2017.09現在、TextRecognizer.Builderはbuildメソッド以外公開されていないので、将来の拡張用といった感じとなります。
app/src/main/java/jp/eq_inc/testmobilevision/fragment/TextRecognizeFromPhotoFragment.java - TextRecognizeTask.onPreExecute
mTextRecognizer = new TextRecognizer.Builder(activity).build();

2. Frame.Builderインスタンスを生成し、それから静止画像が設定されたFrameインスタンスを生成

この辺は「Mobile Vision API - Face Detect(静止画)」と同じになります。
app/src/main/java/jp/eq_inc/testmobilevision/fragment/TextRecognizeFromPhotoFragment.java - TextRecognizeTask.doInBackground
Frame fullImageFrame = new Frame.Builder().setBitmap(fullImage).setRotation(rotation).build();

3. TextRecognizerインスタンスにFrameインスタンスを設定して、文字認識を実施

この辺も「Mobile Vision API - Face Detect(静止画)」と同じになります。
app/src/main/java/jp/eq_inc/testmobilevision/fragment/TextRecognizeFromPhotoFragment.java - TextRecognizeTask.doInBackground
SparseArray<TextBlock> detectedFaceArray = mTextRecognizer.detect(fullImageFrame);

これで、個々の段落毎のTextBlockが取得できたので、より細かい文章やワード単位に分割したい場合、TextBlock.getComponents()にて、Textを実装したクラス"Line"に分割されます。
さらにLine.getComponentsメソッドにて、Textを実装したクラス"Element"に分割されます。
以下のイメージは分割した際のイメージ図です。大きな黒枠で囲まれた部分が"TextBlock"、黄色枠で囲まれた部分が"Line"、緑色枠で囲まれた部分が"Element"となります。

カメラからの入力映像に対する文字認識

実装の流れは「Mobile Vision API - Face Detect(カメラ)」と同じように以下のようになります。
  1. カメラプレビューを表示するためのSurfaceViewを用意
  2. TextRecognizerインスタンスを生成
  3. Processorインスタンスを生成し、TextRecognizerに設定
  4. CameraSourceインスタンスを生成し、startメソッドをコール

1. カメラプレビューを表示するためのSurfaceViewを用意

厳密には必ずしもSurfaceViewである必要はありませんが、SurfaceHolderが必要になるので、多分SurfaceViewを使用するのが簡単でしょう。
後述するCameraSourceにSurfaceHolderを指定することで、カメラからの入力を、指定されたSurfaceに描画してくれます。
ただし、カメラプレビューを表示したくない場合は、SurfaceViewは不要です。
※ カメラプレビューを表示しない場合は、顔認識開始時にSurfaceHolderを指定しないCameraSource.start(引数なし)メソッドをコールすることになります。

2. TextRecognizerインスタンスを生成

2017.09現在、TextRecognizer.Builderはbuildメソッドしか公開されていないので、以下のようにbuildメソッドをコールするのみとなります。
app/src/main/java/jp/eq_inc/testmobilevision/fragment/TextRecognizeFromCameraFragment.java - initTextRecognizer
mTextRecognizer = new TextRecognizer.Builder(activity).build();

3. Processorインスタンスを生成し、TextRecognizerに設定

Processorインスタンスは以下のものが用意されています。これらを使用するか、Processorを実装したクラスを用意する必要があります。
Processor名称概要備考
FocusingProcessorアプリ側でTrackerに通知して欲しい解析結果を選択するProcessor顔、バーコード、文字認識の全て使用できる
MultiProcessor解析結果全てをTrackerに通知してくれるProcessor顔、バーコード、文字認識の全て使用できる
LargestFaceFocusingProcessorFrameに写っている顔で、最も大きいもののみをTrackerに通知してくれるProcessor顔認識にしか使用できない
FocusingProcessor.selectFocusで返却する値は、以下のパラメータで指定されたDetector.Detectionsから取得できるSparseArrayのkeyを返却する必要があります。
app/src/main/java/jp/eq_inc/testmobilevision/fragment/TextRecognizeFromCameraFragment.java - initTextRecognizer
// use multi processor
SwitchCompat tempSwitch = (SwitchCompat) activity.findViewById(R.id.scUseMultiProcessor);
if (tempSwitch.isChecked()) {
    MultiProcessor.Builder<TextBlock> multiProcessorBuilder = new MultiProcessor.Builder<TextBlock>(mMultiProcessFactory);
    mTextRecognizer.setProcessor(multiProcessorBuilder.build());
} else {
    FocusingProcessor<TextBlock> focusingProcessor = new FocusingProcessor<TextBlock>(mTextRecognizer, new TextTracker()) {
        @Override
        public int selectFocus(Detector.Detections<TextBlock> detections) {
            SparseArray<TextBlock> detectedItems = detections.getDetectedItems();
            int selectedItem = 0;
            int largestSize = 0;
            for (int i = 0, size = detectedItems.size(); i < size; i++) {
                TextBlock detectedItem = detectedItems.valueAt(i);
                Rect bounds = detectedItem.getBoundingBox();
                int tempBoundsSize = bounds.width() * bounds.height();
                if (largestSize < tempBoundsSize) {
                    largestSize = tempBoundsSize;
                    selectedItem = detectedItems.keyAt(i);  // ★返却値の候補としてkeyを設定
                }
            }

            return selectedItem;  // ★フォーカスしたいTextBlockに対応するSparceArray上のkeyを返却
        }
    };
    mTextRecognizer.setProcessor(focusingProcessor);
}

4. CameraSourceインスタンスを生成し、startメソッドをコール

カメラの操作を隠蔽してくれるCameraSourceインスタンスを生成します。生成時にTextRecognizerをリンクするので、カメラからの入力映像がそのままTextRecognizerに渡され、逐次解析されていきます。
app/src/main/java/jp/eq_inc/testmobilevision/fragment/TextRecognizeFromCameraFragment.java - initTextRecognizer
CameraSource.Builder builder = new CameraSource.Builder(activity, mTextRecognizer);

// previewサイズ
builder.setRequestedPreviewSize(mCameraPreview.getWidth(), mCameraPreview.getHeight());

// auto focus
SwitchCompat tempSwitch = (SwitchCompat) activity.findViewById(R.id.scAutoFocus);
builder.setAutoFocusEnabled(tempSwitch.isChecked());

// facing
tempSwitch = (SwitchCompat) activity.findViewById(R.id.scFacing);
if (tempSwitch.isChecked()) {
    builder.setFacing(CameraSource.CAMERA_FACING_FRONT);
} else {
    builder.setFacing(CameraSource.CAMERA_FACING_BACK);
}

// detect fps
try {
    EditText etDetectFps = (EditText) activity.findViewById(R.id.etDetectFps);
    float detectFps = Float.parseFloat(etDetectFps.getText().toString());
    builder.setRequestedFps(detectFps);
} catch (NumberFormatException e) {

}

mCameraSource = builder.build();

app/src/main/java/jp/eq_inc/testmobilevision/fragment/TextRecognizeFromCameraFragment.java - start
try {
    // プレビューを表示したいSurfaceViewのSurfaceHolderを指定
    mCameraSource.start(mCameraPreview.getHolder());
    mRealPreviewSize = mCameraSource.getPreviewSize();
    ret = true;
} catch (IOException e) {
    e.printStackTrace();
}
これで、カメラの入力映像で文字を認識すると、Processorに指定したTrackerが動作します。
private class TextTracker extends Tracker {
    private Paint mLinePaint;
    private boolean mVisible = false;

    public TextTracker() {
        super();
        mLinePaint = new Paint();
        mLinePaint.setStrokeWidth(getResources().getDimensionPixelSize(R.dimen.face_line_width));
        mLinePaint.setARGB(100, 255, 0, 0);
    }

    @Override
    public void onNewItem(int i, TextBlock textBlock) {
        LogUtil.d("", "onNewItem: " + textBlock.getValue());

        super.onNewItem(i, textBlock);
        mVisible = true;
        mDrawTextBlockMap.put(this, textBlock);
        mPreviewOverlay.postInvalidate();
    }

    @Override
    public void onUpdate(Detector.Detections detections, TextBlock textBlock) {
        super.onUpdate(detections, textBlock);

        mVisible = true;
        mDrawTextBlockMap.put(this, textBlock);
        mPreviewOverlay.postInvalidate();
    }

    @Override
    public void onMissing(Detector.Detections detections) {
        if (mDrawTextBlockMap.containsKey(this)) {
            LogUtil.d("", "onMissing: " + mDrawTextBlockMap.get(this).getValue());
        }

        super.onMissing(detections);
        mVisible = false;
        mPreviewOverlay.postInvalidate();
    }

    @Override
    public void onDone() {
        if (mDrawTextBlockMap.containsKey(this)) {
            LogUtil.d("", "onDone: " + mDrawTextBlockMap.get(this).getValue());
        }

        super.onDone();
        mDrawTextBlockMap.remove(this);
    }
}

サンプルアプリ

ここにサンプルアプリを置きました。 試してみる場合は、以下の手順で確認してみてください。
  1. ここから環境をクローン
  2. タグ"v0.4.1"をチェックアウト
  3. ビルドしてAPKをインストール
  4. "Text Recognize from Photo"または"Text Recognize from Movie"または"Text Recognize from Camera"を選択
    • "Text Recognize from Photo"を選択した場合:
    • デバイスに格納されている静止画が表示されるので、文字認識したい静止画をタッチ
    • "Text Recognize from Movie"を選択した場合:
    • デバイスに格納されている動画が表示されるので、文字認識したい動画をタッチ
      タッチすると、動画を1秒間隔のフレームに分割して表示するので、文字認識したいフレームをタッチ
    • "Text Recognize from Camera"を選択した場合:
    • カメラプレビューが表示されるので、英文を表示

参考サイト

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

コメントの投稿