これからの時代はエッジコンピューティング来るでしょと思い、
まずは画像処理系のスキルを身につけるべくFirebaseのML Kitを触ってみたのでメモ。
ML Kitとは
Googleで開発されている機械学習を用いた機能を、モバイルアプリへ簡単に組み込むことができるSDK。
AndroidとiOS向けのライブラリが公開されている。
オンデバイスで動作するAPIとクラウドベースのAPIを利用することができる。
オンデバイスAPIを使えばネットワークの状態がなくても動作させることができる。
公式HP MLKit説明
ML Kitでできること
2021/02時点で以下の機能が公開されている。
機能 | オンデバイス | クラウド | その他メモ |
---|---|---|---|
テキスト認識 | ○ | ○ | 日本語はクラウドのみ、オンデバイスはラテン文字の認識 |
顔検出 | ○ | ||
バーコードスキャン | ○ | ||
画像のラベル付 | ○ | ○ | |
オブジェクトの検出とトラッキング | ○ | ||
ランドマーク認識 | ○ | ||
言語認識 | ○ | ||
翻訳 | ○ | ||
スマートリプライ | ○ | 英語のみ対応、会話履歴を解析して回答案を作成する | |
AutoML推論 | ○ | ||
カスタム推論 | ○ | ||
Pose Detection(Beta) | ○ | 人体の顔や手足の動きを追跡できる | |
Entity Extraction(Beta) | ○ | テキストの表記から種別(住所、電話番号、日付、etc)を判定する |
利用料金は?
オンデバイスは無料。
クラウドは有料。ただし、毎月1000リスクエストまで無料枠が設定されている。
実際にやってみた
以下の5つのインタフェースを検証してみた。
- テキスト認識(クラウド)
- テキスト認識(オンデバイス)
- バーコードスキャン
- 画像のラベル付け
- 翻訳(英語→日本語)
出力結果はこんな感じ
※最強の資格を持つ花子さん(免許センターのサンプル)と最近某家電量販店で価格調査したNASの写真(QRコードと1次元バーコード)を利用して検証。
1. テキスト認識(クラウド)
FirebaseVisionImage visionImage = FirebaseVisionImage.fromBitmap(bitmap);
// Recognize Text (Support Japanese)
FirebaseVisionDocumentTextRecognizer detector = FirebaseVision.getInstance().getCloudDocumentTextRecognizer();
Task<FirebaseVisionDocumentText> result = detector.processImage(visionImage)
.addOnSuccessListener(new OnSuccessListener<FirebaseVisionDocumentText>() {
@Override
public void onSuccess(FirebaseVisionDocumentText result) {
// Task completed successfully
// TextBlock単位で情報取得。
List<FirebaseVisionDocumentText.Block> blocks = result.getBlocks();
・・・中略・・・
for (int i = 0; i < blocks.size(); i++) {
// per Block
FirebaseVisionDocumentText.Block block = blocks.get(i);
// テキスト解析結果
String recognizedText = block.getText();
// blockの矩形情報を取得
Rect rect = block.getBoundingBox();
//corner top left
String.format("(%d, %d)",rect.left, rect.top);
//corner top right
String.format("(%d, %d)",rect.right, rect.top);
//corner bottom right
String.format("(%d, %d)",rect.right, rect.bottom);
//corner bottom left
String.format("(%d, %d)",rect.left, rect.bottom);
}
try {
detector.close(); //close処理
} catch (Exception e) {
e.printStackTrace();
}
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
・・・中略・・・
}
});
どういう単位で区切られているかは掴めず。
2. テキスト認識(オンデバイス)
ラテン語は取得してくれるそうな。日本語は取れない。
InputImage bundleImage = InputImage.fromBitmap(bitmap, 0);
// Recognize text https://developers.google.com/ml-kit/vision/text-recognition/android
// Non-support Japanese,Chinese,Korean. You want recognize that, you need to use CloudApi.
TextRecognizer recognizer = TextRecognition.getClient();
Task<Text> textRecognizedResult =
recognizer.process(bundleImage)
.addOnSuccessListener(new OnSuccessListener<Text>() {
@Override
public void onSuccess(Text visionText) {
// TextBlock単位で情報取得。
List<Text.TextBlock> blocks = visionText.getTextBlocks();
・・・中略・・・
for (int i = 0; i < blocks.size(); i++) {
// per Block Block要素はText.LineのListを持つのでList<Text.Line> lines = blocks.get(i).getLines();といった感じでLine単位での処理も可能
// Point配列は、0:左上、1:右上、2:右下、3:左下で構成される
Point[] point = blocks.get(i).getCornerPoints();
// pointはnullを許容するのでnullチェック
if (point != null) {
String recognizedText = blocks.get(i).getText();
//corner top left
String.format("(%d, %d)",point[0].x, point[0].y);
//corner top right
String.format("(%d, %d)",point[1].x, point[1].y);
//corner bottom right
String.format("(%d, %d)",point[2].x, point[2].y);
//corner bottom left
String.format("(%d, %d)",point[3].x, point[3].y);
}
}
recognizer.close();// close処理
}
})
.addOnFailureListener(
new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
・・・中略・・・
}
});
3. バーコードスキャン(オンデバイス)
ML KitのバーコードタイプがEnumで実装されていないので自作クラスを作成。
1ページに複数のバーコードがあってもとってくれる。
// Scan barcode https://developers.google.com/ml-kit/vision/barcode-scanning/android#java
// set paramater
BarcodeScannerOptions options =
new BarcodeScannerOptions.Builder()
/** スキャン対象のバーコードの型を設定しておくことが性能面で推奨されている。未指定の場合は自動判定してくれる。
.setBarcodeFormats(
Barcode.FORMAT_QR_CODE, //QR Code
Barcode.FORMAT_CODABAR, //Codabar
Barcode.FORMAT_CODE_128,
Barcode.FORMAT_CODE_93,
Barcode.FORMAT_CODE_39,
Barcode.FORMAT_EAN_8, // JAN Code
Barcode.FORMAT_EAN_13) // JAN Code
**/
.build();
BarcodeScanner scanner = BarcodeScanning.getClient(options);
Task<List<Barcode>> barcodeScanResult = scanner.process(bundleImage)
.addOnSuccessListener(new OnSuccessListener<List<Barcode>>() {
@Override
public void onSuccess(List<Barcode> barcodes) { //複数バーコードが取得可能
for (int i = 0; i < barcodes.size(); i++) {
// per Block
Point[] point = barcodes.get(i).getCornerPoints();
if (point != null) {
String displayValue = barcodes.get(i).getDisplayValue();
String barcodeType = BarcodeType.getById(barcodes.get(i).getFormat()).getLabel();
・・・中略。Pointの取り方はTextと同じ・・・
}
}
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
・・・中略・・・
}
});
// https://developers.google.com/android/reference/com/google/mlkit/vision/barcode/Barcode.BarcodeFormat
public enum BarcodeType {
FORMAT_UNKNOWN("FORMAT_UNKNOWN",-1),
FORMAT_ALL_FORMATS("FORMAT_ALL_FORMATS",0),
FORMAT_CODE_128("FORMAT_CODE_128",1),
FORMAT_CODE_39("FORMAT_CODE_39",2),
FORMAT_CODE_93("FORMAT_CODE_93",4),
FORMAT_CODABAR("FORMAT_CODABAR",8),
FORMAT_DATA_MATRIX("FORMAT_DATA_MATRIX",16),
FORMAT_EAN_13("FORMAT_EAN_13",32),
FORMAT_EAN_8("FORMAT_EAN_8",64),
FORMAT_ITF("FORMAT_ITF",128),
FORMAT_QR_CODE("FORMAT_QR_CODE",256),
FORMAT_UPC_A("FORMAT_UPC_A",512),
FORMAT_UPC_E("FORMAT_UPC_E",1024),
FORMAT_PDF417("FORMAT_PDF417",2048),
FORMAT_AZTEC("FORMAT_AZTEC",4096),
TYPE_UNKNOWN("TYPE_UNKNOWN",0),
TYPE_CONTACT_INFO("TYPE_CONTACT_INFO",1),
TYPE_EMAIL("TYPE_EMAIL",2),
TYPE_ISBN("TYPE_ISBN",3),
TYPE_PHONE("TYPE_PHONE",4),
TYPE_PRODUCT("TYPE_PRODUCT",5),
TYPE_SMS("TYPE_SMS",6),
TYPE_TEXT("TYPE_TEXT",7),
TYPE_URL("TYPE_URL",8),
TYPE_WIFI("TYPE_WIFI",9),
TYPE_GEO("TYPE_GEO",10),
TYPE_CALENDAR_EVENT("TYPE_CALENDAR_EVENT",11),
TYPE_DRIVER_LICENSE("TYPE_DRIVER_LICENSE",12);
private String label;
private int id;
private BarcodeType(String label, int id) {
this.label = label;
this.id = id;
}
public String getLabel() {
return label;
}
public int getId() {
return id;
}
public static BarcodeType getById(int id) {
for( BarcodeType barcodeType : BarcodeType.values() ) {
if( barcodeType.getId() == id ) {
return barcodeType;
}
}
return null;
}
}
4. 画像ラベル付け(オンデバイス)
HumanとかChildとかPaperとかHandとか。色々ラベルつけてくれる。
// Image labeling
ImageLabeler labeler = ImageLabeling.getClient(ImageLabelerOptions.DEFAULT_OPTIONS);
labeler.process(bundleImage)
.addOnSuccessListener(new OnSuccessListener<List<ImageLabel>>() {
@Override
public void onSuccess(List<ImageLabel> labels) { //複数のラベル取得
// Task completed successfully
for (int i = 0; i < labels.size(); i++) {
String label = labels.get(i).getText();
String confidence = String.format("%f",labels.get(i).getConfidence());
}
labeler.close(); //close処理
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
・・・中略・・・
}
});
5. 翻訳(オンデバイス)
This is a pen. → これはペンです。 の翻訳結果は確認できた。
// Create an English-JAPANESE translator:
TranslatorOptions translationOptions =
new TranslatorOptions.Builder()
.setSourceLanguage(TranslateLanguage.ENGLISH)
.setTargetLanguage(TranslateLanguage.JAPANESE)
.build();
final Translator englishJapaneseTranslator = Translation.getClient(translationOptions);
if (!hasDownlodTranslataMode) {
DownloadConditions conditions = new DownloadConditions.Builder().requireWifi().build();
englishJapaneseTranslator.downloadModelIfNeeded(conditions)
.addOnSuccessListener(
new OnSuccessListener() {
@Override
public void onSuccess(Object o) {
// Model downloaded successfully. Okay to start translating. 30MBくらいの学習モデルを端末にダウンロードする処理が必要。サンプルなので変数に状態をセット。
hasDownlodTranslataMode = true;
}
})
.addOnFailureListener(
new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
// Model couldn’t be downloaded or other internal error.
// ...
}
});
}
// 学習モデルがダウンロード終わっていれば翻訳処理を実施
if (hasDownlodTranslataMode) {
englishJapaneseTranslator.translate("This is a pen.")
.addOnSuccessListener(
new OnSuccessListener() {
@Override
public void onSuccess(@NonNull Object result) {
// Translation successful.
String translatedText = (String) result; // 「これはペンです。」が出力される。
}
})
.addOnFailureListener(
new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
・・・中略・・
}
});
}
Github URL
感想
- 実装は数行レベル。日本語の解析単位のクセを見抜くのがポイントかも。
- 本当はやりたかった、「この矩形位置のOCR結果を返してくれる」は自前での実装が必要っぽい。
- オンデバイスのテキスト解析は日本語未対応(翻訳→翻訳で無理やりできる??)
- テキスト解析(クラウド)は日本語であってもそこそこ解析してくれる
参考
環境構築は以下のサイトを参考にさせていただきました。
ML Kit For Firebaseを使ってスマホで色々検出してみた