Mobile Vision Barcode APIを用いたバーコード機能の実装方法

はじめに

こんばんは!

お久しぶりです。なかなか時間を取れず、更新ができていませんでした。

申し訳ありません。

 

ようやく、大学を卒業し晴れて春から社会人なのでこれからはもっと質を上げていきたいと思います。

 

今作成している、価格の相場を見る アプリにバーコード検索機能を実装しようと思いまして、そういえば去年の1月くらいにバーコードの機能を一生懸命ドキュメント読みながら作ったなぁと、思ったので今日は復習を兼ねてサンプルをもとに作成方法を紹介していきます。

 

実際に作成したサンプルは以下になります。

   

 

今回使用するライブラリはこちらのライブラリになります。

Mobile Vision

 

Googleが提供しているもので、顔認識や、バーコード読み取り、文字認識などの機能を使うことができます。

 

今回は、バーコードの読み取り機能を作成していきます。

今回参考にしているリンクは以下になります。

Barcode API Overview  |  Mobile Vision  |  Google Developers

 

まず初めに、BarcodeAPIを使用できるように変更する appのgradleの中で、dependenciesに下記の一文を追加してください。

2018年3月現在で、最新のバージョンは11.0.4です。

これでBarcode APIの導入は以上です。

 

次にカメラの許可をAndroidManifestに追加する 今回は端末のカメラの機能を用いるので、次の一行をManifestに追加してください。

<uses-permission android:name="android.permission.CAMERA"/>

タグの中なら大丈夫です。

これでカメラの許可を追加できました。

 

次にスキャナーのレイアウトの作成をしていきましょう スキャン用のレイアウトを組んでいきます。

下記のbarcodeのサンプルを参考に作ります。

Barcode

 

 レイアウトのxmlを読んでいくとbarcode_capture.xmlというところでバーコードの検出を行う用のレイアウトを組んでいることが分かります。

 

android-vision/barcode_capture.xml at master · googlesamples/android-vision · GitHub

 

このコードの中身を読むと、次のようになっています。

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout
 xmlns:android="http://schemas.android.com/apk/res/android"
 android:id="@+id/topLayout"
 android:orientation="vertical"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:keepScreenOn="true">

 <com.google.android.gms.samples.vision.barcodereader.ui.camera.CameraSourcePreview
    android:id="@+id/preview"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.google.android.gms.samples.vision.barcodereader.ui.camera.GraphicOverlay
        android:id="@+id/graphicOverlay"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

 </com.google.android.gms.samples.vision.barcodereader.ui.camera.CameraSourcePreview>

</LinearLayout>

となっており、CameraSourcePreviewというカスタムしたViewを用意して、それをGraphicOverlayという形で画面上に表示しているのだろうと思います。

 

それでは、CameraSourcePreviewの中身を見てみましょう。

以下のリンクから見ることができます。

googlesamples/android-vision

 

中身を見ていくと、ViewGroupを継承してSurfaceViewにて良しなにやっていることが分かりましたので、今回は表示させるレイアウトを取り合えずそのままのSurfaceViewを用いて実装していこうと思います。

 

SurfaceViewについて、わからない、知らないという場合は次のリンクを参照。

AndroidのSurfaceViewの基礎 - Qiita

今回はテスト的にOverlayで表示させるのではなく、TextViewに結果を表示させていくので次のようなレイアウトになっています。

 

 

実際に用意したレイアウトxmlはこちらになります。

 

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:tools="http://schemas.android.com/tools">

    <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:id="@+id/test_result"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:textSize="@dimen/text_size_xxx_large"
            android:textColor="@color/makepura_gray_00"
            android:background="@color/makepura_white"
            android:text="検索中…"
            app:layout_constraintVertical_weight="0.1"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toTopOf="@+id/test_sub_result"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"/>

        <TextView
            android:id="@+id/test_sub_result"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:textSize="@dimen/text_size_xxx_large"
            android:textColor="@color/makepura_gray_00"
            android:background="@color/makepura_white"
            android:text="検索中…"
            app:layout_constraintVertical_weight="0.1"
            app:layout_constraintTop_toBottomOf="@+id/test_result"
            app:layout_constraintBottom_toTopOf="@+id/scanner"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"/>

        <SurfaceView
            android:id="@+id/scanner"
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:layout_constraintVertical_weight="0.8"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/test_sub_result" />

    </android.support.constraint.ConstraintLayout>
</layout>

結果を表示するテキストをSurfaceViewの上に二つ用意して、SurfaceViewはカメラで読み取るように用意しました。

ここまでで、とりあえずレイアウトの作成は終了になります。

 

バーコードの機能をサンプルを参考に作っていく 今回はFragmentでスキャナー機能を実装します。

onCreateViewの中で下処理を始めに行っていきます。

 

最初にカメラのパーミッションの許可を求めるコードを呼び出します。

val permission = Manifest.permission.CAMERA
if (context?.let { ContextCompat.checkSelfPermission(it, permission) } != PackageManager.PERMISSION_GRANTED) {
    activity?.let { ActivityCompat.requestPermissions(it, arrayOf(permission), REQUEST_CAMERA_CODE) }
}

アプリで初回にカメラを利用する際にパーミッションの許可を求めるようにします。

これが許可されることでカメラを起動状態に出来ます。

 

BarcodeAPIには、Detectorという名前の通りなのですが、検知する部分を作成する必要があります。

まずは、BarcodeDetectorのインスタンスを用意します。

val barcodeDetector = BarcodeDetector
        .Builder(context)
        .setBarcodeFormats(Barcode.EAN_13)
        .build()

setBarcodeFormatsでバーコードのフォーマットを指定できます。

 

今回はEANコードを読み取りたいので、こちらを指定しています。

EANコードについては下記参照。

EANコード - Wikipedia

 

公式によると対応しているフォーマットは次のとおりで、様々用意されています。

 

次に、バーコードを読み取った際に処理させたい内容を書いていきましょう。

barcodeDetector.setProcessor(object : Detector.Processor<Barcode> {
    override fun release() {
    }

    override fun receiveDetections(detections: Detector.Detections<Barcode>) {
        if (detections.detectedItems.size() != 0)
            runOnUiThread {
                val contents = detections.detectedItems.valueAt(0).rawValue
                Timber.i("barcode scan : $contents")
                binding.testResult.text = contents
                if (contents.startsWith("192")) binding.testSubResult.text = contents
            }
    }
})

receiveDetectionsでは、検出したバーコードの内容をもとに値を直接取り出して、そのバーコードを前にSurfaceViewの上につけた二つのTextViewに表示するようにしています。

二段目のテキストだけ、192から始まる書籍JANコードの分類と価格を表すものにだけ反応しテキストを入れるようにしています。

 

そして、カメラをコントロールするCameraSourceにDetectorを次の通りにセットしてやります。

cameraSource = CameraSource.Builder(context, barcodeDetector)
        .setAutoFocusEnabled(true)
        .build()

この際、オートフォーカスを行うようにsetAutoFocusEnabledをtrueにしています。

 

Detectorはカメラを起動している間、デフォルトで30fpsで読み取り続けます。

もしfpsを変更したい場合は、setRequestedFps(15.0f)のようにして変更することも可能です。

 

次に、SurfaceViewのコールバックを実装していきます。

ここでは、CameraSourceをスタートさせるタイミングと、リリースするタイミングを実装していきます。

 

Barcodeは常に読み取り続けているので、onResumeの中で以下のように実装します。

 

if (cameraSource == null) {
    initialize()
}
scanner?.holder?.addCallback(object : SurfaceHolder.Callback {
    override fun surfaceCreated(holder: SurfaceHolder?) {
        try {
            if (context?.let { ContextCompat.checkSelfPermission(it, Manifest.permission.CAMERA) } == PackageManager.PERMISSION_GRANTED) {
                cameraSource?.start(holder)
            }
        } catch (e: IOException) {
            Timber.e(e, e.message)
        }
    }

    override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) {
    }

    override fun surfaceDestroyed(holder: SurfaceHolder?) {
        if (cameraSource != null) {
            cameraSource?.release()
            cameraSource = null
        }
    }
})

cameraSourceがnullだった場合は最初に初期化するようにしています。

Surfaceの用意ができた段階で、カメラの許可が下りていたら、cameraSourceを開始させます。

SurfaceがDestroyされるタイミング(タブの切り替えなど)で、cameraSourceを終了させ初期化するようにしています。

 

以上で初めに紹介したバーコードの機能を実装することができます。

 

おわりに

上手く言語化できているかわからないですが、自分もあまり参考になる記事が少なくて取り合えずドキュメントを読んでソースコードを追って実装していきました。

 

あまり触れられていないライブラリを実際に取り入れていくには、まずはサンプルを動かしてデバッグしながら流れを把握していったり、中身を読んでどうなってるのかなーというところを追っていく必要があります。

 

Mobile Vision Barcode APIは導入しやすく、今回はただ検出した値を表示しただけでしたが、検出した値を用いてRxを使ってAPI叩いて非同期でデータを取得して処理していくという流れでアプリに反映させることもできます。

 

僕自身もまだまだ勉強中の身なので、指摘や質問等あればコメントしていただけると嬉しいです!

 

次回はFluxについて書きたいー・・・