Flutter×Flaskで物体検出するアプリ作ってみた!
はじめに
はじめまして! わかなおです。現在は大学院で農業×ITの研究をしています。
今回は研究でPythonを使用して植物の物体検出をする機会があって、それをFlutterと繋げることができたらおもろそうやない?と思ったので、Flutter×Flaskで植物をの葉っぱ部分を検出するアプリを作ってみました!
いわゆるバックエンド部分(Flask)に触れることや、フロントとバックエンドを繋げるということがほとんど初めてだったので、非常に勉強になりました。
何事も恐れずにやってみるって、素敵やん?
記事の全体像
- 作成したアプリ
- 構成
- フロント側
- サーバー側
- 動かす
作成したアプリ
画像選択 | 物体検出中 | 予測結果表示 |
---|---|---|
アプリとしてはとてもシンプルで
画像を選択して「送るボタン」を押すと植物の葉を検出してくれる
というものです。
構成
全体の処理の流れとしてはこんな感じになります
リポジトリ
Flutter
Flaskフロント側
使用したパッケージ
dependencies:
image_picker: ^0.8.5
http: ^0.13.4
を追加しpub get !!
image_picker
はスマホから画像を選択,http
は画像をFlask側に送信するために使用しました。
main.dart
全体はざっとこんな感じです!
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:http/http.dart';
import 'package:image_picker/image_picker.dart';
import 'dart:io';
import 'package:http/http.dart' as http;
import 'dart:convert';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final ImagePicker _picker = ImagePicker();
File? _pickedImage; //選択した画像
Image? _predictedImage; //返ってきた画像(予測結果)
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('機械学習'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
///
/// 画像表示
///
ElevatedButton(
onPressed: () async {
/// 1,画像を選択
},
child: const Text('選ぶ'),
),
ElevatedButton(
onPressed: () async {
/// 2.画像を送信して返ってきた予測画像を表示
},
child: const Text('送る'),
),
],
),
),
);
}
}
- 画像を選択
- 画像を送信して返ってきた予測画像を表示
について詳しく書いていきます! 画像の表示部分などについては少し長くなってしまうのでリポジトリを参照してください!
1.画像を選択
ElevatedButton(
onPressed: () async {
final pickedFile =
await _picker.pickImage(source: ImageSource.gallery);
if (pickedFile == null) {
return;
}
setState(() {
_pickedImage = File(pickedFile.path);
});
},
child: const Text('選ぶ'),
),
ImagePicker
で画像を選択して表示させています!
2.画像を送信して返ってきた予測画像を表示
ElevatedButton(
onPressed: () async {
/// file -> base64
//画像ファイルをバイトのリストとして読み込む
List<int> imageBytes = _pickedImage!.readAsBytesSync();
//base64にエンコード
String base64Image = base64Encode(imageBytes);
//サーバー側で設定してあるURLを選択
Uri url = Uri.parse('http://127.0.0.1:5000/trimming');
String body = json.encode({
'post_img': base64Image,
});
/// send to backend
// サーバーにデータをPOST,予測画像をbase64に変換したものを格納したJSONで返ってくる
Response response = await http.post(url, body: body);
/// base64 -> file
final data = json.decode(response.body);
String imageBase64 = data['result'];
//バイトのリストに変換
Uint8List bytes = base64Decode(imageBase64);
//バイトから画像を生成
Image image = Image.memory(bytes);
//画像を表示
setState(() {
_predictedImage = image;
});
},
child: const Text('送る'),
),
基本的には
main.py
で立てたhttp://127.0.0.1:5000/trimming
に画像をbase64に変換してPOSTします。
そしてサーバー側で処理されて返ってきたbase64の値を画像に戻して表示させています!
サーバー側
サーバー側の構成は以下のようになってます
backend/
├─ main.py
├─ predictor.py
├─ PumpkinLeaf
└─ detectron2
植物の物体検出にはdetectron2というライブラリを使用しています!
ローカルでこれを動かすには少々めんどうでした。
Detectron2について気になる方は、こちらの記事を参照してみてください!
import cv2
from flask import Flask, render_template, request, make_response, jsonify
import numpy as np
from predictor import Predictor
from flask_cors import CORS
import base64
app = Flask(__name__)
CORS(app)
predictor = Predictor()
@app.route("/trimming", methods=['GET', 'POST'])
def predict():
if(request.is_json):
data = request.get_json()
post_img = data['post_img']
img_base64 = post_img.split(',')[1]
else:
data= request.get_data().decode()
temp = data.split('"')
img_base64 = temp[3]
#data -> {'post_img' : 'data:image/png;base64,iVBORw0KGgoAAAANS....'}
#img_base64 -> 'iVBORw0KGgoAAAANS....'
#base64から画像に変換
img_binary = base64.b64decode(img_base64)
img_array = np.asarray(bytearray(img_binary), dtype=np.uint8)
img = cv2.imdecode(img_array, 1)
#予測
predictor.predict(img=img)
predicted_img = predictor.img
cv2.imwrite('result.jpg', predicted_img)
with open('result.jpg', "rb") as f:
img_base64 = base64.b64encode(f.read()).decode('utf-8')
response = {'result': img_base64}
return make_response(jsonify(response))
if __name__ == "__main__":
app.debug = True
app.run(host='127.0.0.1', port=5000)
@app.route("/trimming", methods=['GET', 'POST'])
部分でフロントから画像データを受け取り、予測した後、予測画像をフロントに返す処理をしています。
動かす
以下のコマンドを実行して動かす!!
サーバー側
python main.py
フロント側
flutter run
さいごに
今回はFlutterとFlaskを用いて植物を検出する簡単なアプリを作ってみました。
サーバー側をほとんど初めて作り、フロントとサーバー側どちらも作ることができたら相互の理解が深まってめっちゃくちゃ面白いな!!!と感じることができました。今後はサーバーサイド側も少し触れていこうと思います!
最後まで読んでいただきありがとうございました!!!
参考記事
Discussion