わんぱく Flutter! 第四回 いきなりっ! 画像認識をしてみよう。
これまでで、基本的な Flutter の使い方をやってきました。あとは、たくさん用意されている Widgets を使いこなすだけですが、Flutter の魅力の一つに、パッケージがあります。Flutter も Python と同じようにパッケージを読み込むことで、簡単に機能拡張を行うことができます。今回は、Google が買収した Firebase に用意されている機械学習(Machine Learning)機能のひとつ、画像認識をしてみようと思います。
1. Firebase を使えるようにする
Flutter から Firebase を使えるようにするには、firebase_core を pubspec.yaml に追加して、"pub get" コマンドでインストールする必要がありますが、ここら辺はたくさんのウェブサイトが用意されていますので、何かあればそちらを参照するといいでしょう。公式ウェブサイトは、こちらです。
日々更新されているので、少し古い記事になると、バージョン番号が変わっていたり、API 名が変わっていたり、無意味につまずく事になるので、なるべく新しい記事、或いは、公式アナウンスを確認しながら進めるのがいいでしょう。 Google のアカウントは既に持っていると思いますので、下記 Firebase の Top ページ右上の「コンソールへ移動」を押します。Google のアカウントがない人は早速作成しましょう。
そうすると、新しいプロジェクトを作成する画面が現れるので、ボタンを押して一つ作成します。名前やアナリクティクスの設定をして続行します。アナリクティクスはデフォルトのままでいいでしょう。作成されると、プロジェクト名の横にプランの変更ボタンがあります。最初は「Spark プラン」になっていますが、機械学習は従量制プランから使える機能なので、従量制プランである「Blaze」に切り替えておきます。切り替えには、クレジットカードの登録が必要です。従量制プランでは、一定量(機械学習では、1000/月アクセス)までは課金が掛かりませんが、それを越えると課金されるので、注意しましょう。
そうしたら、今回作成する画像認識用の Flutter アプリケーションのベースにする新しいプロジェクトを準備しておきましょう。android studio から、新規にプロジェクトを作成します。
Flutter の準備ができたら、Firebase のプロジェクトのウェブサイトに戻ります。プロジェクトの画面からアンドロイドマーク、今回は android 用アプリケーションを作成しているので、アンドロイドのマークをクリックすると、android のアプリケーションを開発するのに必要な設定を説明した画面が表示されます。
「アプリの登録」では、android パッケージ名や、ニックネーム、デバッグ用の署名証明書の登録を行いますが、パッケージ名以外は省略可能です。パッケージ名は、Flutter アプリケーションの app レベル build.grade に設定されている applicationId (※下記 figure 02 参照)と同じものを設定します。
apply plugin: 'com.android.application'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
apply plugin: 'com.google.gms.google-services' // <<== Google サービスプラグイン
android {
compileSdkVersion 30
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.wanpaku.mlvision" // <<== applicationId
minSdkVersion 29
targetSdkVersion 30
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
}
}
dependencies {
api 'com.google.firebase:firebase-ml-vision-image-label-model:20.0.2' // <<== 画像認識用API
}
}
パッケージ名を登録すると、google-service.json を download するボタンが現れます。このファイルも指示通り download して、 app レベル build.grade と同じディレクトリに保存します。
次は、Firebase SDK の設定です。 app レベルの build.grade に Google サービスプラグイン(※上記参照)を追加します。
apply plugin: 'com.google.gms.google-services'
そして、画像認識用 API も追加します。
dependencies {
api 'com.google.firebase:firebase-ml-vision-image-label-model:20.0.2'
}
また、もう一つの build.grade であるプロジェクトレベルの build.grade を開き、classpath を追加します。この build.grade は、先程の app レベルの build.grade の一つ上のディレクトリにあります。
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.1.0'
classpath 'com.google.gms:google-services:4.3.5' // <<== classpath
}
}
allprojects {
repositories {
google()
jcenter()
}
}
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(':app')
}
task clean(type: Delete) {
delete rootProject.buildDir
}
これで、android から Firebase へアクセスできるようになりました。後は、パッケージを読み込んでくるだけです。今回作成した Flutter プロジェクトの一番上のディレクトリに、pubspec.yaml というファイルがあります。ここに Firebase 用のパッケージ firebase_core: ^1.0.3 を追加します。
dependencies:
flutter:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2
firebase_core: ^1.0.3 # <<== firebase_core
firebase_ml_vision: ^0.11.0+1
image_picker: ^0.7.4
dev_dependencies:
flutter_test:
sdk: flutter
追加したら忘れずに Pub get を行って下さい。これは、pubspec.yaml に記述したパッケージを download してくるコマンドです。 android studio の場合は、pubspec.yaml を開いている画面の上方に Pub get コマンドが表示されています。コマンドラインから行う場合は、プロジェクトのディレクトリに移動して、flutter pub get コマンドを実行します。
Flutter のパッケージは、頻繁に更新されているので、最新の情報を確認しておく必要が有ります。パッケージ名の後ろの数字がバージョン番号ですが、2021年4月13日現在、firebase_core は、1.0.3 が最新バージョンです。
一通り設定が終わったら、Flutter のプロジェクトが作成したサンプルアプリケーションを実行して、正常に動作することを確認しておきます。
2. 機械学習パッケージ firebase_ml_vision を導入する
上述の pubspec.yaml には既に記載されているので気づいている人も多いと思いますが、firebase_ml_vision パッケージを導入していきます。また、画像を選択したり、写真を撮ったりするパッケージである image_picker も同時に導入します。
これは、Flutter から firebase の機械学習機能を利用するためのパッケージです。pubspec.yaml の firebase_core の直ぐ下に firebase_ml_vision: ^0.11.0+1 を追加して下さい。
image_picker も同様に image_picker: 0.7.4 を追加します。pubspec.yaml へ記述したら忘れずに Pub get を実行し、download して下さい。
image_picker は、非常によく利用されているパッケージですが、最新の Android SDK であるバージョン 30 で利用すると、画像や Camera へのアクセス権限の関連で正常に動作しないことがあります。そこで、忘れずにリソースへのアクセス権限の設定もしておきます。
数年前の firebase_ml_vision に関する記事では、この Android SDK 30 の仕様変更について考慮されていない為、最近、改めて記事の通りに設定しても、上手く動かないものがほとんどでした。また、私のこのソースでは、compileSdkVersion 30, minSdkVersion 29, targetSdkVersion 30 としています。
一つは、app レベルの build.grade ファイルのあるディレクトリの src/debug にある AndroidManifest.xml に <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/> を下記の通り追加します。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="systemquality.mlvision2">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
</manifest>
また同様に、src/main にある AndroidManifest.xml に android:requestLegacyExternalStorage="true" を追加します。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.wanpaku.mlvision">
<application
android:label="mlvision2"
android:requestLegacyExternalStorage="true"
android:icon="@mipmap/ic_launcher">
3. Firebase 機械学習による画像認識
今回は、cloud ベースの画像認識機能を使っているので、Firebase のプロジェクト概要ページの左側メニューにある「Machine Learning」の「始める」ボタンから、「Cloud ベースの API を有効化」スイッチを入れて、使えるようにしておきます。
そうしたら、main.dart のサンプルプログラムを以下のプログラムで丸ごと置き換えて下さい。
import 'dart:io';
import 'package:firebase_ml_vision/firebase_ml_vision.dart';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'sample of firebase_ml_vision'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
File _image;
String _text = "No Image";
String _entityId = "";
double _confidence = 0.0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
centerTitle: true,
),
body: Center(
child: Stack(
children: [
_image != null ? Image.file(_image) : Text("") ,
Container(
child: TextFormField(
decoration: InputDecoration(
labelText: _text,
fillColor: Colors.white,
filled: true,
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(25.0),
borderSide: BorderSide(color: Colors.blue)
)
)
),
),
]
),
),
floatingActionButton: FloatingActionButton(
onPressed: () async {
await _recognition();
},
child: Icon(
Icons.add_a_photo,
color: Colors.white,
),
),
);
}
void _recognition() async {
// final file = await ImagePicker().getImage(source: ImageSource.gallery);
final file = await ImagePicker().getImage(source: ImageSource.camera);
_image = File(file.path);
final FirebaseVisionImage visionImage = FirebaseVisionImage.fromFile(_image);
/*
final ImageLabeler labeler = FirebaseVision.instance.imageLabeler();
final List<ImageLabel> labels = await labeler.processImage(visionImage);
setState(() {
_text = "";
for (ImageLabel label in labels) {
_text += label.text.toString() + ",";
}
});
labeler.close();
*/
final ImageLabeler cloudLabeler = FirebaseVision.instance.cloudImageLabeler();
final List<ImageLabel> cloudLabels = await cloudLabeler.processImage(visionImage);
setState(() {
_text = "";
for (ImageLabel label in cloudLabels) {
_text += label.text.toString() + ",";
}
});
cloudLabeler.close();
}
}
_recognition() 以外は、flutter のサンプルプログラムとまったく一緒なので、説明の必要はないと思いますが、画像ファイルの表示に使用している Image wedget は、三項演算子による分岐処理となっており、_image が null ではない時だけ Image.file() を処理し、 null の場合は、Text("") を表示するようになっています。
_image != null ? Image.file(_image) : Text("") ,
また、_recognition() では、imge_picker による画像取得と、
final file = await ImagePicker().getImage(source: ImageSource.camera);
取得した画像を firebase_ml_vision の cloud 画像認識 API で処理する部分が記述されています。_image のイメージ形式を変換し、インスタンス作成、そして API 呼び出しです。
final FirebaseVisionImage visionImage = FirebaseVisionImage.fromFile(_image);
final ImageLabeler cloudLabeler = FirebaseVision.instance.cloudImageLabeler();
final List<ImageLabel> cloudLabels = await cloudLabeler.processImage(visionImage);
灰色でコメントアウトされている部分と、直ぐ下の処理を変更すると、cloud 側で画像認識するのではなく、android 端末側で処理する API になります。プログラムの重要な部分は、これだけです。それでは実行してみましょう。
"Food,Clementine,Fruit,Valencia ..." だいたい合ってますね。 Clementine は、Cambride Dictionary によると、"a fruit like a small orange" となっていますので、確かにその通りです。
以上
この記事は、下記のウェブサイトを参考に作成しました。 firebase_ml_vision を使用した顔認証については、下記ウェブサイトが大変参考になります。ぜひ、ご参照下さい。
こちらは、Camera の Permission について書かれたウェブサイトです。新しい Android SDK で開発する場合は、必須となってきますので、注意が必要です。