⚠️本記事はMLKitの公式チュートリアルを参考にしています
過去の記事で取り上げてきたFlutter×Firebaseの連携とMLKitの機能を使い、簡単なアプリケーションを作ってみようと思います🤔
Firebase各種設定方法とMLKitについては下記の記事に詳しく書いたので、確認してみて下さい👇
作成するアプリ
✅ 写真フォルダorカメラ撮影した写真を選択できる
✅ 選択した写真に写っている人物の笑顔の確率をMLKitを使い算出し、タイムラインに投稿
✅ タイムラインをクリックすると投稿した画像が見れる
ソースコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 |
// <main.dart> import 'dart:io'; import 'timeline_page.dart'; import 'package:flutter/rendering.dart'; import 'package:intl/intl.dart'; // 時間を文字列に変換 import 'package:uuid/uuid.dart'; // uniqueなIDの生成(今回は画像のURLを生成) import 'package:path/path.dart'; // pathの操作に必要なライブラリ import 'package:image_picker/image_picker.dart'; import 'package:flutter/material.dart'; import 'package:firebase_core/firebase_core.dart'; //firebaseに必要なライブラリ import 'package:firebase_ml_vision/firebase_ml_vision.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:firebase_storage/firebase_storage.dart'; void main() async { WidgetsFlutterBinding .ensureInitialized(); // runApp()を呼び出す前にFlutter Engineの機能を利用したい場合にコール await Firebase.initializeApp(); runApp(MyAIApp()); } class MyAIApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'SMILE SNS App', theme: ThemeData( primarySwatch: Colors.green, visualDensity: VisualDensity.adaptivePlatformDensity, ), home: MainForm(), ); } } class MainForm extends StatefulWidget { @override _MainFormState createState() => _MainFormState(); } // MainPage class _MainFormState extends State<MainForm> { String _name = ""; String _processingMessage = ""; final FaceDetector _faceDetector = FirebaseVision.instance.faceDetector( FaceDetectorOptions( mode: FaceDetectorMode .accurate, // Option for controlling additional accuracy / speed trade-offs. enableLandmarks: true, // Whether to detect FaceLandmarks. 目や鼻などの検出を可能にする enableClassification: true, // characterizing attributes such as "smiling" and "eyes open". ), ); final ImagePicker _picker = ImagePicker(); void _getImageAndFindFace( BuildContext context, ImageSource imageSource) async { setState(() { _processingMessage = "Wait a minutes, Processing..."; }); final PickedFile pickedImage = await _picker.getImage(source: imageSource); final File imageFile = File(pickedImage.path); // 写真がimageFileに格納されたタイミングでfaceDetectorで笑顔の確率の計算&データベースとストレージに写真を保存 if (imageFile != null) { final FirebaseVisionImage visionImage = FirebaseVisionImage.fromFile( imageFile); // FaceDetectorに渡せるようにFirebaseVisionImage型に変換 List<Face> faces = await _faceDetector.processImage(visionImage); if (faces.length > 0) { String imagePath = "/images/" + Uuid().v1() + basename(pickedImage.path); // Uuid.v1:時刻に基づくユニークなid StorageReference ref = FirebaseStorage.instance .ref() .child(imagePath); // Google Cloud Storageへの参照を表す final StorageTaskSnapshot storedImage = await ref .putFile(imageFile) .onComplete; // StorageTaskSnapshot:Storageへの操作の際に型宣言する if (storedImage.error == null) { final String downloadUrl = await storedImage.ref.getDownloadURL(); //Urlを付与 Face largestFace = findLargestFace(faces); // Firestoreデータベースにsmilesコレクションを追加し以下の設定で保存する FirebaseFirestore.instance.collection("smiles").add({ "name": _name, //UIのテキスト入力が写真のnameとなる "image_url": downloadUrl, //データベースから写真を取得する際のURを設定 "date": Timestamp.now(), "smile_prob": largestFace.smilingProbability, //笑顔の確率 }); // Firebaseストレージに写真が追加されたら、TimelinePageに遷移する Navigator.push( context, MaterialPageRoute(builder: (context) => TimelinePage())); } } } setState(() { _processingMessage = ""; }); } //取得した顔のリストの中から最も大きい顔を検出する関数 Face findLargestFace(List<Face> faces) { Face largestFace = faces[0]; for (Face face in faces) { if (face.boundingBox.height + face.boundingBox.width > largestFace.boundingBox.height + largestFace.boundingBox.width) { largestFace = face; } } return largestFace; } // MainPageのUI @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text("Smile SNS")), body: Column( mainAxisAlignment: MainAxisAlignment.start, children: <Widget>[ Padding(padding: EdgeInsets.all(20)), // ある方向のみの場合はEdgeInsets.only Text(_processingMessage, style: TextStyle( color: Colors.lightBlue, fontSize: 20, )), TextFormField( style: TextStyle(fontSize: 23), decoration: InputDecoration( icon: Icon( Icons.person_add_alt, size: 40, ), labelText: "YOUR NAME", hintText: "Please input your name", ), onChanged: (text) { setState(() { _name = text; }); }, ), ], ), floatingActionButton: Column( mainAxisAlignment: MainAxisAlignment.end, children: <Widget>[ FloatingActionButton( tooltip: "screen change sns", heroTag: "sns", child: Icon(Icons.timeline_outlined), onPressed: () { Navigator.push(context, MaterialPageRoute(builder: (context) => TimelinePage())); }, ), Padding(padding: EdgeInsets.all(10)), FloatingActionButton( tooltip: "Select image", heroTag: "gallery", child: Icon(Icons.add_photo_alternate), onPressed: () { _getImageAndFindFace(context, ImageSource.gallery); }, ), Padding(padding: EdgeInsets.all(10)), FloatingActionButton( tooltip: "Take photo", heroTag: "camera", child: Icon(Icons.add_a_photo), onPressed: () { _getImageAndFindFace(context, ImageSource.camera); }, ), ], ), ); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
// <timeline_page.dart> // TimelinePage(SNSタイムライン) import 'image_page.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; // 時間を文字列に変換 import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:firebase_storage/firebase_storage.dart'; class TimelinePage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Smile SNS"), ), body: Container( child: _buildBody(context), ), ); } Widget _buildBody(BuildContext context) { return StreamBuilder<QuerySnapshot>( stream: FirebaseFirestore.instance .collection("smiles") .orderBy("date", descending: true) .limit(20) .snapshots(), builder: (context, snapshot) { if (!snapshot.hasData) return LinearProgressIndicator(); return _buildList(context, snapshot.data!.docs); }, ); } Widget _buildList(BuildContext context, List<DocumentSnapshot> snapList) { return ListView.builder( padding: EdgeInsets.all(10), itemCount: snapList.length, itemBuilder: (context, i) { return _buildListItem(context, snapList[i]); }); } Widget _buildListItem(BuildContext context, DocumentSnapshot snap) { Map<String, dynamic> _data = snap.data(); DateTime _datetime = _data["date"].toDate(); var _formatter = DateFormat("dd//MM HH:mm"); // 2021表記はy String postDate = _formatter.format(_datetime); return Padding( padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Container( decoration: BoxDecoration( border: Border.all(color: Colors.grey), borderRadius: BorderRadius.circular(15), ), child: ListTile( title: Text( _data["name"], style: TextStyle(fontSize: 15), ), leading: Text(postDate), subtitle: Text( "は" + (_data["smile_prob"] * 100).toStringAsFixed(1) + "%の笑顔です"), trailing: Text(_getEmozi(_data["smile_prob"]), style: TextStyle(fontSize: 30)), onTap: () { Navigator.push( context, MaterialPageRoute( builder: (context) => ImagePage(_data["image_url"]), )); }, ), ), ); } // Firebaseのデータベースのsmile_probの値を顔文字で表現 String _getEmozi(double smileProb) { String emozi; if (smileProb < 0.2) { emozi = "😯"; } else if (smileProb < 0.4) { emozi = "😌"; } else if (smileProb < 0.6) { emozi = "😀"; } else if (smileProb < 0.8) { emozi = "😄"; } else { emozi = "😆"; } return emozi; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
// <image_page.dart> // ImagePage(SNSタイムラインからリストをクリックすると写真を表示する) import 'package:flutter/material.dart'; class ImagePage extends StatelessWidget { String _imageUrl = ""; String _name = ""; ImagePage(String imageUrl, String name) { this._imageUrl = imageUrl; this._name = name; } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Smile SNS"), ), body: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text(_name, style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)), Center( child: Image.network(_imageUrl), ), ], ), ); } } |
コメントを残す