📖

Flutter×Flaskで物体検出するアプリ作ってみた!

2022/05/17に公開

はじめに

はじめまして! わかなおです。現在は大学院で農業×ITの研究をしています。

今回は研究でPythonを使用して植物の物体検出をする機会があって、それをFlutterと繋げることができたらおもろそうやない?と思ったので、Flutter×Flaskで植物をの葉っぱ部分を検出するアプリを作ってみました!

いわゆるバックエンド部分(Flask)に触れることや、フロントとバックエンドを繋げるということがほとんど初めてだったので、非常に勉強になりました。
何事も恐れずにやってみるって、素敵やん?

記事の全体像

  1. 作成したアプリ
  2. 構成
  3. フロント側
  4. サーバー側
  5. 動かす

作成したアプリ

画像選択 物体検出中 予測結果表示

アプリとしてはとてもシンプルで
画像を選択して「送るボタン」を押すと植物の葉を検出してくれる 
というものです。

構成

全体の処理の流れとしてはこんな感じになります
構成図

リポジトリ

Flutter
https://github.com/naokiwakata/flutter_detect_leaf
Flask
https://github.com/naokiwakata/detect_leaf_backend3/tree/feature/flutter_mac

フロント側

使用したパッケージ

pubspec.yaml
dependencies:
  image_picker: ^0.8.5
  http: ^0.13.4

を追加しpub get !!
image_pickerはスマホから画像を選択,httpは画像をFlask側に送信するために使用しました。

main.dart

全体はざっとこんな感じです!

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. 画像を選択
  2. 画像を送信して返ってきた予測画像を表示

について詳しく書いていきます! 画像の表示部分などについては少し長くなってしまうのでリポジトリを参照してください!

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について気になる方は、こちらの記事を参照してみてください!
https://zenn.dev/wakanao/articles/6a97e3e7966ed8

main.py
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を用いて植物を検出する簡単なアプリを作ってみました。
サーバー側をほとんど初めて作り、フロントとサーバー側どちらも作ることができたら相互の理解が深まってめっちゃくちゃ面白いな!!!と感じることができました。今後はサーバーサイド側も少し触れていこうと思います!
最後まで読んでいただきありがとうございました!!!

参考記事

https://qiita.com/Pu-of-Parari/items/f3733f0ba48e1df50667

Flutter大学

Discussion