前置作業
請確認你已經完成以下安裝:
- 安裝 Flutter SDK
- 安裝 Android Studio 或 Visual Studio Code(搭配 Flutter plugin)
- 設定好
flutter doctor(在終端機執行以下指令):
flutter doctor確保所有項目都打勾 ✅
建立 Flutter 專案
使用終端機執行以下指令:flutter create my_app你可以將 my_app 換成你想要的專案名稱。
建立後的資料夾結構
建立完成後會看到如下資料夾:
my_app/
├── android/ ← Android 原生專案
├── ios/ ← iOS 原生專案
├── lib/ ← 主要開發目錄,寫 Dart 程式的地方
│ └── main.dart ← App 的進入點
├── test/ ← 測試程式
├── pubspec.yaml ← 套件依賴與資源設定
執行 App
- 接上模擬器或手機
- 移動到專案目錄
cd my_app- 執行專案
flutter run影像辨識
由於我們要做的是一個影像辨識APP流程圖如下
系統流程圖
Flutter App
↓ 圖片上傳
Flask API ←→ PyTorch 模型
↓ 傳回結果
顯示結果 + 儲存紀錄 (MySQL)
Flutter撰寫前端邏輯
flutter_cnn\my_app\lib\main.dart
import 'package:flutter/material.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({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '影像辨識 App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const ImageRecognitionPage(),
);
}
}
class ImageRecognitionPage extends StatefulWidget {
const ImageRecognitionPage({super.key});
@override
State<ImageRecognitionPage> createState() => _ImageRecognitionPageState();
}
class _ImageRecognitionPageState extends State<ImageRecognitionPage> {
File? _image;
String _result = '';
bool _loading = false;
Future<void> _pickImage(ImageSource source) async {
final picker = ImagePicker();
final pickedFile = await picker.pickImage(source: source);
if (pickedFile != null) {
setState(() {
_image = File(pickedFile.path);
_result = '';
});
await _uploadImage(_image!);
}
}
Future<void> _uploadImage(File image) async {
setState(() => _loading = true);
var request = http.MultipartRequest(
'POST',
Uri.parse('http://10.0.2.2:5000/predict'),
);
request.files.add(await http.MultipartFile.fromPath('image', image.path));
var response = await request.send();
if (response.statusCode == 200) {
final respStr = await response.stream.bytesToString();
final data = jsonDecode(respStr);
setState(() {
_result = data['result'] ?? '未知結果';
});
} else {
setState(() {
_result = '辨識失敗,請再試一次';
});
}
setState(() => _loading = false);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('影像辨識 App')),
body: Center(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_image == null
? const Text('請選擇一張圖片')
: Image.file(_image!, height: 200),
const SizedBox(height: 16),
_loading
? const CircularProgressIndicator()
: Text(_result, style: const TextStyle(fontSize: 18)),
const SizedBox(height: 16),
ElevatedButton.icon(
onPressed: () => _pickImage(ImageSource.camera),
icon: const Icon(Icons.camera_alt),
label: const Text('拍照'),
),
const SizedBox(height: 8),
ElevatedButton.icon(
onPressed: () => _pickImage(ImageSource.gallery),
icon: const Icon(Icons.photo),
label: const Text('從相簿選擇'),
),
],
),
),
),
);
}
}
}
注意
- 模擬器的 localhost 不一定指向你的電腦
- Android 模擬器中,127.0.0.1 指的是模擬器本身,不是你的電腦主機。
- 你應該用 http://10.0.2.2:5000/predict (Android Emulator 對宿主機的特殊映射IP)。
- iOS 模擬器的 localhost 通常是指你的電腦本機,可以用 127.0.0.1。
- 真實裝置測試
- 如果你用真機測試,127.0.0.1 指手機自己,不會連到電腦。
- 這時候你要用電腦的局域網 IP(像是 192.168.x.x),並確保 Flask 伺服器允許外部連線(app.run(host='0.0.0.0'))。
撰寫後端邏輯
flutter_cnn\flask_backend\app.py
from flask import Flask, request, jsonify
from flask_cors import CORS
import torch
from torchvision import models, transforms
from PIL import Image
import io
app = Flask(__name__)
CORS(app)
# 使用 MobileNetV2 作為輕量模型
model = models.mobilenet_v2(pretrained=True)
model.eval()
# 類別名稱(可根據實際情境替換)
with open("imagenet_classes.txt") as f:
classes = [line.strip() for line in f.readlines()]
# 圖像預處理流程
transform = transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize(
mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225]
)
])
@app.route('/predict', methods=['POST'])
def predict():
if 'image' not in request.files:
return jsonify({'error': 'No image provided'}), 400
image_file = request.files['image']
image_bytes = image_file.read()
image = Image.open(io.BytesIO(image_bytes)).convert('RGB')
input_tensor = transform(image).unsqueeze(0)
with torch.no_grad():
outputs = model(input_tensor)
_, predicted = outputs.max(1)
predicted_class = classes[predicted.item()]
return jsonify({'result': predicted_class})
if __name__ == '__main__':
app.run(host='127.0.0.1', port=5000)
Flutter run
執行
flutter run
flutter run -d <機器名稱> #也可以這樣子

Flask RUN
執行
python app.py #你的flask app檔案

最終結果















