add first version

This commit is contained in:
2026-03-02 22:57:35 +08:00
parent d517195df7
commit d9631fbb80
17 changed files with 1631 additions and 93 deletions

View File

@@ -0,0 +1,14 @@
{
"permissions": {
"allow": [
"Bash(cd:*)",
"Bash(cd /c/Users/xdedmi/Desktop/aisee/aisee_flutter && /c/src/flutter/bin/flutter pub get 2>&1)",
"Bash(cmd.exe:*)",
"Bash(cd /c/Users/xdedmi/Desktop/aisee/aisee_flutter && /c/Users/xdedmi/flutter/bin/flutter pub get 2>&1)",
"Bash(cd /c/Users/xdedmi/Desktop/aisee/aisee_flutter && /c/Users/xdedmi/flutter/bin/flutter devices 2>&1)",
"Bash(cd /c/Users/xdedmi/Desktop/aisee/aisee_flutter && /c/Users/xdedmi/flutter/bin/flutter emulators 2>&1)",
"Bash(adb devices:*)",
"Bash(/c/Users/xdedmi/flutter/bin/flutter doctor:*)"
]
}
}

View File

@@ -0,0 +1,103 @@
# 快速启动 Android 模拟器
## 当前状态
✅ Flutter 已安装:`C:\Users\xdedmi\flutter`
✅ 项目依赖已安装
❌ 没有 Android 模拟器
## 创建 Android 模拟器
### 方法 1使用 Android Studio推荐
1. **打开 Android Studio**
2. **打开 Device Manager**
- 点击右上角的设备图标
- 或 Tools -> Device Manager
3. **创建新设备**
- 点击 "Create Device"
- 选择设备Pixel 6 Pro
- 选择系统镜像API 34 (Android 14)
- 点击 "Finish"
4. **启动模拟器**
- 点击播放按钮启动模拟器
- 等待 2-3 分钟启动完成
### 方法 2使用命令行
```bash
# 1. 查看可用的系统镜像
C:\Users\xdedmi\flutter\bin\flutter doctor
# 2. 如果 Android Studio 已安装,创建模拟器
# 打开 Android Studio -> Device Manager -> Create Device
```
## 启动项目Android
模拟器启动后:
```bash
cd C:\Users\xdedmi\Desktop\aisee\aisee_flutter
# 检查设备
C:\Users\xdedmi\flutter\bin\flutter devices
# 运行项目
C:\Users\xdedmi\flutter\bin\flutter run
```
## 使用真机(更快)
如果有 Android 手机:
1. **开启开发者选项**
- 设置 -> 关于手机
- 连续点击"版本号" 7 次
2. **开启 USB 调试**
- 设置 -> 开发者选项
- 开启 "USB 调试"
3. **连接手机**
- USB 连接手机到电脑
- 手机上允许 USB 调试授权
4. **运行项目**
```bash
C:\Users\xdedmi\flutter\bin\flutter devices
C:\Users\xdedmi\flutter\bin\flutter run
```
## 当前运行状态
项目正在 Windows 桌面模式编译运行,你可以先看到界面效果。
**注意**:相机功能需要 Android 设备才能使用。
## 快捷脚本
我已经创建了启动脚本,但需要更新 Flutter 路径。
编辑 `install.bat` 和 `run.bat`,在开头添加:
```batch
@echo off
set PATH=C:\Users\xdedmi\flutter\bin;%PATH%
```
然后就可以双击运行了。
## 下一步
1. ⏳ 等待 Windows 版本编译完成(首次约 5-10 分钟)
2. 📱 创建 Android 模拟器或连接真机
3. 🚀 在 Android 设备上运行项目
4. 📷 测试相机功能
---
需要帮助创建模拟器吗?

View File

@@ -1,12 +1,109 @@
# aisee_flutter
# AISee Flutter 实时相机
A new Flutter project.
实时相机图像捕获和传输演示项目。
## Getting Started
## 功能特性
This project is a starting point for a Flutter application.
- ✅ 实时相机预览
- ✅ 图像自动压缩640x480质量 85%
- ✅ 定时捕获(每 500ms 一帧)
- ✅ 异步上传到后端
- ✅ 单张拍照并分析
- ✅ 前后摄像头切换
- ✅ 实时状态显示
A few resources to get you started if this is your first Flutter project:
## 快速开始
### 1. 安装依赖
```bash
flutter pub get
```
### 2. 配置后端地址
编辑 `lib/utils/app_config.dart`
```dart
// Android 模拟器
static const String apiBaseUrl = 'http://10.0.2.2:8000';
// 真机(改为你的电脑 IP
static const String apiBaseUrl = 'http://192.168.1.100:8000';
```
### 3. 运行项目
```bash
flutter run
```
## 项目结构
```
lib/
├── main.dart # 入口和首页
├── screens/
│ └── camera_screen.dart # 相机页面
├── services/
│ ├── camera_service.dart # 相机服务
│ └── api_service.dart # API 服务
└── utils/
└── app_config.dart # 配置
```
## 使用说明
1. **打开相机**:点击首页"打开相机"按钮
2. **拍照分析**:拍摄单张照片并上传分析
3. **开始传输**:每 500ms 自动捕获并上传
4. **停止传输**:停止实时捕获
5. **切换摄像头**:前后摄像头切换
## 配置参数
`lib/utils/app_config.dart` 中调整:
```dart
static const int imageMaxWidth = 640; // 图像宽度
static const int imageMaxHeight = 480; // 图像高度
static const int imageQuality = 85; // JPEG 质量
static const Duration captureInterval = Duration(milliseconds: 500); // 捕获间隔
```
## 后端 API
需要实现以下端点:
```
POST /api/v1/images/upload # 上传图像
POST /api/v1/analysis/analyze # 分析图像
```
详细文档见项目根目录的技术方案文档。
## 依赖
- camera: ^0.10.5+9
- image: ^4.1.7
- dio: ^5.4.1
- permission_handler: ^11.3.0
- provider: ^6.1.1
## 常见问题
**Q: 相机权限被拒绝?**
A: 在手机设置中授予相机权限并重启应用。
**Q: 网络连接失败?**
A: 检查后端服务是否启动,确认 IP 地址配置正确。
**Q: 上传速度慢?**
A: 降低图像分辨率或增加捕获间隔。
---
更多信息请查看项目文档。
- [Learn Flutter](https://docs.flutter.dev/get-started/learn-flutter)
- [Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)

View File

@@ -1,4 +1,13 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 添加权限 -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<uses-feature android:name="android.hardware.camera" android:required="false" />
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
<application
android:label="aisee_flutter"
android:name="${applicationName}"

13
aisee_flutter/install.bat Normal file
View File

@@ -0,0 +1,13 @@
@echo off
set PATH=C:\Users\xdedmi\flutter\bin;%PATH%
echo 正在安装依赖...
cd /d %~dp0
flutter pub get
echo.
echo 依赖安装完成!
echo.
echo 下一步:
echo 1. 连接 Android 设备或启动模拟器
echo 2. 双击 run.bat 运行项目
echo.
pause

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'screens/camera_screen.dart';
void main() {
runApp(const MyApp());
@@ -7,116 +8,96 @@ void main() {
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
title: 'AISee Camera',
theme: ThemeData(
// This is the theme of your application.
//
// TRY THIS: Try running your application with "flutter run". You'll see
// the application has a purple toolbar. Then, without quitting the app,
// try changing the seedColor in the colorScheme below to Colors.green
// and then invoke "hot reload" (save your changes or press the "hot
// reload" button in a Flutter-supported IDE, or press "r" if you used
// the command line to start the app).
//
// Notice that the counter didn't reset back to zero; the application
// state is not lost during the reload. To reset the state, use hot
// restart instead.
//
// This works for code too, not just values: Most code changes can be
// tested with just a hot reload.
colorScheme: .fromSeed(seedColor: Colors.deepPurple),
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
useMaterial3: true,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
home: const HomeScreen(),
debugShowCheckedModeBanner: false,
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
// This widget is the home page of your application. It is stateful, meaning
// that it has a State object (defined below) that contains fields that affect
// how it looks.
// This class is the configuration for the state. It holds the values (in this
// case the title) provided by the parent (in this case the App widget) and
// used by the build method of the State. Fields in a Widget subclass are
// always marked "final".
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
// This call to setState tells the Flutter framework that something has
// changed in this State, which causes it to rerun the build method below
// so that the display can reflect the updated values. If we changed
// _counter without calling setState(), then the build method would not be
// called again, and so nothing would appear to happen.
_counter++;
});
}
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance as done
// by the _incrementCounter method above.
//
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.
return Scaffold(
appBar: AppBar(
// TRY THIS: Try changing the color here to a specific color (to
// Colors.amber, perhaps?) and trigger a hot reload to see the AppBar
// change color while the other colors stay the same.
title: const Text('AISee'),
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text(widget.title),
),
body: Center(
// Center is a layout widget. It takes a single child and positions it
// in the middle of the parent.
child: Column(
// Column is also a layout widget. It takes a list of children and
// arranges them vertically. By default, it sizes itself to fit its
// children horizontally, and tries to be as tall as its parent.
//
// Column has various properties to control how it sizes itself and
// how it positions its children. Here we use mainAxisAlignment to
// center the children vertically; the main axis here is the vertical
// axis because Columns are vertical (the cross axis would be
// horizontal).
//
// TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint"
// action in the IDE, or press "p" in the console), to see the
// wireframe for each widget.
mainAxisAlignment: .center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('You have pushed the button this many times:'),
Text(
'$_counter',
style: Theme.of(context).textTheme.headlineMedium,
const Icon(
Icons.visibility,
size: 100,
color: Colors.blue,
),
const SizedBox(height: 24),
const Text(
'AISee 视觉辅助',
style: TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
const Text(
'实时相机图像传输演示',
style: TextStyle(
fontSize: 16,
color: Colors.grey,
),
),
const SizedBox(height: 48),
ElevatedButton.icon(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const CameraScreen(),
),
);
},
icon: const Icon(Icons.camera_alt, size: 28),
label: const Text(
'打开相机',
style: TextStyle(fontSize: 18),
),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(
horizontal: 32,
vertical: 16,
),
),
),
const SizedBox(height: 24),
const Padding(
padding: EdgeInsets.symmetric(horizontal: 32),
child: Text(
'功能说明:\n'
'• 拍照分析:拍摄单张照片并上传分析\n'
'• 开始传输:每 500ms 自动捕获并上传\n'
'• 切换:切换前后摄像头',
textAlign: TextAlign.left,
style: TextStyle(
fontSize: 14,
color: Colors.black87,
height: 1.5,
),
),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}

View File

@@ -0,0 +1,290 @@
import 'package:flutter/material.dart';
import 'package:camera/camera.dart';
import 'package:provider/provider.dart';
import 'package:permission_handler/permission_handler.dart';
import '../services/camera_service.dart';
import '../services/api_service.dart';
class CameraScreen extends StatefulWidget {
const CameraScreen({super.key});
@override
State<CameraScreen> createState() => _CameraScreenState();
}
class _CameraScreenState extends State<CameraScreen> {
late CameraService _cameraService;
late ApiService _apiService;
bool _isProcessing = false;
String _statusMessage = '准备就绪';
int _frameCount = 0;
int _uploadCount = 0;
@override
void initState() {
super.initState();
_cameraService = CameraService();
_apiService = ApiService();
_initializeCamera();
}
Future<void> _initializeCamera() async {
// 请求相机权限
final status = await Permission.camera.request();
if (!status.isGranted) {
setState(() {
_statusMessage = '相机权限被拒绝';
});
return;
}
// 初始化相机
await _cameraService.initialize();
if (mounted) {
setState(() {
_statusMessage = '相机已就绪';
});
}
}
/// 开始实时传输
void _startStreaming() {
if (_cameraService.isStreaming) return;
setState(() {
_statusMessage = '实时传输中...';
_frameCount = 0;
_uploadCount = 0;
});
_cameraService.startStreaming((imageBytes) async {
setState(() {
_frameCount++;
});
// 上传到后端(异步,不阻塞捕获)
_uploadToBackend(imageBytes);
});
}
/// 停止实时传输
void _stopStreaming() {
_cameraService.stopStreaming();
setState(() {
_statusMessage = '已停止传输';
});
}
/// 上传图像到后端
Future<void> _uploadToBackend(imageBytes) async {
if (_isProcessing) return; // 防止并发上传
_isProcessing = true;
try {
// 上传图像
final result = await _apiService.uploadImage(imageBytes);
setState(() {
_uploadCount++;
_statusMessage = '已上传 $_uploadCount';
});
print('上传成功: ${result['image_id']}');
} catch (e) {
print('上传失败: $e');
setState(() {
_statusMessage = '上传失败: $e';
});
} finally {
_isProcessing = false;
}
}
/// 拍摄单张照片并分析
Future<void> _captureAndAnalyze() async {
setState(() {
_statusMessage = '正在拍照...';
});
final imageBytes = await _cameraService.takePicture();
if (imageBytes == null) {
setState(() {
_statusMessage = '拍照失败';
});
return;
}
setState(() {
_statusMessage = '正在分析...';
});
try {
// 上传并分析
final result = await _apiService.uploadAndAnalyze(imageBytes);
setState(() {
_statusMessage = '分析完成';
});
// 显示结果
_showAnalysisResult(result);
} catch (e) {
setState(() {
_statusMessage = '分析失败: $e';
});
}
}
/// 显示分析结果
void _showAnalysisResult(Map<String, dynamic> result) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('分析结果'),
content: SingleChildScrollView(
child: Text(result.toString()),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('关闭'),
),
],
),
);
}
@override
void dispose() {
_cameraService.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('实时相机'),
backgroundColor: Colors.black,
),
body: _cameraService.isInitialized
? _buildCameraView()
: const Center(child: CircularProgressIndicator()),
);
}
Widget _buildCameraView() {
return Stack(
children: [
// 相机预览
Positioned.fill(
child: CameraPreview(_cameraService.controller!),
),
// 状态信息
Positioned(
top: 16,
left: 16,
right: 16,
child: Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.7),
borderRadius: BorderRadius.circular(8),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
_statusMessage,
style: const TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Text(
'捕获帧数: $_frameCount | 上传帧数: $_uploadCount',
style: const TextStyle(
color: Colors.white70,
fontSize: 14,
),
),
],
),
),
),
// 控制按钮
Positioned(
bottom: 32,
left: 0,
right: 0,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
// 拍照并分析按钮
_buildControlButton(
icon: Icons.camera_alt,
label: '拍照分析',
onPressed: _captureAndAnalyze,
color: Colors.blue,
),
// 开始/停止实时传输按钮
_buildControlButton(
icon: _cameraService.isStreaming ? Icons.stop : Icons.play_arrow,
label: _cameraService.isStreaming ? '停止传输' : '开始传输',
onPressed: _cameraService.isStreaming ? _stopStreaming : _startStreaming,
color: _cameraService.isStreaming ? Colors.red : Colors.green,
),
// 切换摄像头按钮
_buildControlButton(
icon: Icons.flip_camera_android,
label: '切换',
onPressed: () => _cameraService.switchCamera(),
color: Colors.orange,
),
],
),
),
],
);
}
Widget _buildControlButton({
required IconData icon,
required String label,
required VoidCallback onPressed,
required Color color,
}) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
FloatingActionButton(
onPressed: onPressed,
backgroundColor: color,
child: Icon(icon, size: 32),
),
const SizedBox(height: 8),
Text(
label,
style: const TextStyle(
color: Colors.white,
fontSize: 12,
fontWeight: FontWeight.bold,
shadows: [
Shadow(
color: Colors.black,
blurRadius: 4,
),
],
),
),
],
);
}
}

View File

@@ -0,0 +1,79 @@
import 'dart:typed_data';
import 'package:dio/dio.dart';
import '../utils/app_config.dart';
class ApiService {
late final Dio _dio;
ApiService() {
_dio = Dio(BaseOptions(
baseUrl: AppConfig.apiBaseUrl,
connectTimeout: const Duration(seconds: 10),
receiveTimeout: const Duration(seconds: 10),
headers: {
'Content-Type': 'application/json',
},
));
// 添加日志拦截器
_dio.interceptors.add(LogInterceptor(
requestBody: false, // 不打印请求体(图片太大)
responseBody: true,
error: true,
));
}
/// 上传图像到后端
Future<Map<String, dynamic>> uploadImage(Uint8List imageBytes) async {
try {
final formData = FormData.fromMap({
'file': MultipartFile.fromBytes(
imageBytes,
filename: 'image_${DateTime.now().millisecondsSinceEpoch}.jpg',
),
});
final response = await _dio.post(
'/api/v1/images/upload',
data: formData,
);
return response.data;
} catch (e) {
print('上传图像失败: $e');
rethrow;
}
}
/// 请求 AI 分析
Future<Map<String, dynamic>> analyzeImage(String imageUrl) async {
try {
final response = await _dio.post(
'/api/v1/analysis/analyze',
data: {'image_url': imageUrl},
);
return response.data;
} catch (e) {
print('分析图像失败: $e');
rethrow;
}
}
/// 一步到位:上传并分析
Future<Map<String, dynamic>> uploadAndAnalyze(Uint8List imageBytes) async {
try {
// 1. 上传图像
final uploadResult = await uploadImage(imageBytes);
final imageUrl = uploadResult['url'];
// 2. 请求分析
final analysisResult = await analyzeImage(imageUrl);
return analysisResult;
} catch (e) {
print('上传并分析失败: $e');
rethrow;
}
}
}

View File

@@ -0,0 +1,158 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:camera/camera.dart';
import 'package:flutter/foundation.dart';
import 'package:image/image.dart' as img;
import '../utils/app_config.dart';
class CameraService extends ChangeNotifier {
CameraController? _controller;
List<CameraDescription> _cameras = [];
bool _isInitialized = false;
bool _isStreaming = false;
Timer? _captureTimer;
CameraController? get controller => _controller;
bool get isInitialized => _isInitialized;
bool get isStreaming => _isStreaming;
/// 初始化相机
Future<void> initialize() async {
try {
_cameras = await availableCameras();
if (_cameras.isEmpty) {
print('没有可用的相机');
return;
}
// 使用后置摄像头
final camera = _cameras.firstWhere(
(camera) => camera.lensDirection == CameraLensDirection.back,
orElse: () => _cameras.first,
);
_controller = CameraController(
camera,
ResolutionPreset.medium, // 中等分辨率,平衡质量和性能
enableAudio: false,
imageFormatGroup: ImageFormatGroup.jpeg,
);
await _controller!.initialize();
_isInitialized = true;
notifyListeners();
print('相机初始化成功');
} catch (e) {
print('相机初始化失败: $e');
_isInitialized = false;
}
}
/// 拍摄单张照片
Future<Uint8List?> takePicture() async {
if (!_isInitialized || _controller == null) return null;
try {
final XFile image = await _controller!.takePicture();
final bytes = await image.readAsBytes();
// 压缩图像
final compressedBytes = await _compressImage(bytes);
return compressedBytes;
} catch (e) {
print('拍照失败: $e');
return null;
}
}
/// 开始实时捕获(定时拍照)
void startStreaming(Function(Uint8List) onImageCaptured) {
if (_isStreaming) return;
_isStreaming = true;
notifyListeners();
_captureTimer = Timer.periodic(AppConfig.captureInterval, (timer) async {
if (!_isStreaming) {
timer.cancel();
return;
}
final imageBytes = await takePicture();
if (imageBytes != null) {
onImageCaptured(imageBytes);
}
});
print('开始实时捕获,间隔: ${AppConfig.captureInterval.inMilliseconds}ms');
}
/// 停止实时捕获
void stopStreaming() {
_isStreaming = false;
_captureTimer?.cancel();
_captureTimer = null;
notifyListeners();
print('停止实时捕获');
}
/// 压缩图像
Future<Uint8List> _compressImage(Uint8List bytes) async {
return await compute(_compressImageIsolate, bytes);
}
/// 在独立 Isolate 中压缩图像(避免阻塞 UI
static Uint8List _compressImageIsolate(Uint8List bytes) {
// 解码图像
img.Image? image = img.decodeImage(bytes);
if (image == null) return bytes;
// 调整大小
if (image.width > AppConfig.imageMaxWidth ||
image.height > AppConfig.imageMaxHeight) {
image = img.copyResize(
image,
width: AppConfig.imageMaxWidth,
height: AppConfig.imageMaxHeight,
);
}
// 压缩为 JPEG
final compressed = img.encodeJpg(image, quality: AppConfig.imageQuality);
print('图像压缩: ${bytes.length} bytes -> ${compressed.length} bytes');
return Uint8List.fromList(compressed);
}
/// 切换摄像头
Future<void> switchCamera() async {
if (_cameras.length < 2) return;
final currentLens = _controller?.description.lensDirection;
final newCamera = _cameras.firstWhere(
(camera) => camera.lensDirection != currentLens,
orElse: () => _cameras.first,
);
await _controller?.dispose();
_controller = CameraController(
newCamera,
ResolutionPreset.medium,
enableAudio: false,
imageFormatGroup: ImageFormatGroup.jpeg,
);
await _controller!.initialize();
notifyListeners();
}
/// 释放资源
@override
void dispose() {
stopStreaming();
_controller?.dispose();
super.dispose();
}
}

View File

@@ -0,0 +1,13 @@
class AppConfig {
// API 配置
static const String apiBaseUrl = 'http://10.0.2.2:8000'; // Android 模拟器访问本机
// 如果使用真机,改为你的电脑 IP例如'http://192.168.1.100:8000'
// 图像配置
static const int imageMaxWidth = 640;
static const int imageMaxHeight = 480;
static const int imageQuality = 85;
// 实时传输配置
static const Duration captureInterval = Duration(milliseconds: 500); // 每 500ms 捕获一帧
}

View File

@@ -1,6 +1,14 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
archive:
dependency: transitive
description:
name: archive
sha256: a96e8b390886ee8abb49b7bd3ac8df6f451c621619f52a26e815fdcf568959ff
url: "https://pub.dev"
source: hosted
version: "4.0.9"
async:
dependency: transitive
description:
@@ -17,6 +25,46 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.2"
camera:
dependency: "direct main"
description:
name: camera
sha256: dfa8fc5a1adaeb95e7a54d86a5bd56f4bb0e035515354c8ac6d262e35cec2ec8
url: "https://pub.dev"
source: hosted
version: "0.10.6"
camera_android:
dependency: transitive
description:
name: camera_android
sha256: "27761a1e317bc072266dc1975f3a04c7fa02ff6123f18b1d89d9968951a4f002"
url: "https://pub.dev"
source: hosted
version: "0.10.10+15"
camera_avfoundation:
dependency: transitive
description:
name: camera_avfoundation
sha256: "11b4aee2f5e5e038982e152b4a342c749b414aa27857899d20f4323e94cb5f0b"
url: "https://pub.dev"
source: hosted
version: "0.9.23+2"
camera_platform_interface:
dependency: transitive
description:
name: camera_platform_interface
sha256: "98cfc9357e04bad617671b4c1f78a597f25f08003089dd94050709ae54effc63"
url: "https://pub.dev"
source: hosted
version: "2.12.0"
camera_web:
dependency: transitive
description:
name: camera_web
sha256: "57f49a635c8bf249d07fb95eb693d7e4dda6796dedb3777f9127fb54847beba7"
url: "https://pub.dev"
source: hosted
version: "0.3.5+3"
characters:
dependency: transitive
description:
@@ -33,6 +81,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.1.2"
code_assets:
dependency: transitive
description:
name: code_assets
sha256: "83ccdaa064c980b5596c35dd64a8d3ecc68620174ab9b90b6343b753aa721687"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
collection:
dependency: transitive
description:
@@ -41,6 +97,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.19.1"
cross_file:
dependency: transitive
description:
name: cross_file
sha256: "28bb3ae56f117b5aec029d702a90f57d285cd975c3c5c281eaca38dbc47c5937"
url: "https://pub.dev"
source: hosted
version: "0.3.5+2"
crypto:
dependency: transitive
description:
name: crypto
sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
url: "https://pub.dev"
source: hosted
version: "3.0.7"
cupertino_icons:
dependency: "direct main"
description:
@@ -49,6 +121,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.8"
dio:
dependency: "direct main"
description:
name: dio
sha256: aff32c08f92787a557dd5c0145ac91536481831a01b4648136373cddb0e64f8c
url: "https://pub.dev"
source: hosted
version: "5.9.2"
dio_web_adapter:
dependency: transitive
description:
name: dio_web_adapter
sha256: "2f9e64323a7c3c7ef69567d5c800424a11f8337b8b228bad02524c9fb3c1f340"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
fake_async:
dependency: transitive
description:
@@ -57,6 +145,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.3"
ffi:
dependency: transitive
description:
name: ffi
sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
file:
dependency: transitive
description:
name: file
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
url: "https://pub.dev"
source: hosted
version: "7.0.1"
flutter:
dependency: "direct main"
description: flutter
@@ -70,11 +174,56 @@ packages:
url: "https://pub.dev"
source: hosted
version: "6.0.0"
flutter_plugin_android_lifecycle:
dependency: transitive
description:
name: flutter_plugin_android_lifecycle
sha256: ee8068e0e1cd16c4a82714119918efdeed33b3ba7772c54b5d094ab53f9b7fd1
url: "https://pub.dev"
source: hosted
version: "2.0.33"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
flutter_web_plugins:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
glob:
dependency: transitive
description:
name: glob
sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de
url: "https://pub.dev"
source: hosted
version: "2.1.3"
hooks:
dependency: transitive
description:
name: hooks
sha256: "7a08a0d684cb3b8fb604b78455d5d352f502b68079f7b80b831c62220ab0a4f6"
url: "https://pub.dev"
source: hosted
version: "1.0.1"
http_parser:
dependency: transitive
description:
name: http_parser
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
url: "https://pub.dev"
source: hosted
version: "4.1.2"
image:
dependency: "direct main"
description:
name: image
sha256: f9881ff4998044947ec38d098bc7c8316ae1186fa786eddffdb867b9bc94dfce
url: "https://pub.dev"
source: hosted
version: "4.8.0"
leak_tracker:
dependency: transitive
description:
@@ -107,6 +256,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "6.1.0"
logging:
dependency: transitive
description:
name: logging
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
url: "https://pub.dev"
source: hosted
version: "1.3.0"
matcher:
dependency: transitive
description:
@@ -131,6 +288,38 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.17.0"
mime:
dependency: transitive
description:
name: mime
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
url: "https://pub.dev"
source: hosted
version: "2.0.0"
native_toolchain_c:
dependency: transitive
description:
name: native_toolchain_c
sha256: "89e83885ba09da5fdf2cdacc8002a712ca238c28b7f717910b34bcd27b0d03ac"
url: "https://pub.dev"
source: hosted
version: "0.17.4"
nested:
dependency: transitive
description:
name: nested
sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
objective_c:
dependency: transitive
description:
name: objective_c
sha256: "100a1c87616ab6ed41ec263b083c0ef3261ee6cd1dc3b0f35f8ddfa4f996fe52"
url: "https://pub.dev"
source: hosted
version: "9.3.0"
path:
dependency: transitive
description:
@@ -139,6 +328,150 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.9.1"
path_provider:
dependency: "direct main"
description:
name: path_provider
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
url: "https://pub.dev"
source: hosted
version: "2.1.5"
path_provider_android:
dependency: transitive
description:
name: path_provider_android
sha256: f2c65e21139ce2c3dad46922be8272bb5963516045659e71bb16e151c93b580e
url: "https://pub.dev"
source: hosted
version: "2.2.22"
path_provider_foundation:
dependency: transitive
description:
name: path_provider_foundation
sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699"
url: "https://pub.dev"
source: hosted
version: "2.6.0"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
url: "https://pub.dev"
source: hosted
version: "2.2.1"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
url: "https://pub.dev"
source: hosted
version: "2.3.0"
permission_handler:
dependency: "direct main"
description:
name: permission_handler
sha256: "59adad729136f01ea9e35a48f5d1395e25cba6cea552249ddbe9cf950f5d7849"
url: "https://pub.dev"
source: hosted
version: "11.4.0"
permission_handler_android:
dependency: transitive
description:
name: permission_handler_android
sha256: d3971dcdd76182a0c198c096b5db2f0884b0d4196723d21a866fc4cdea057ebc
url: "https://pub.dev"
source: hosted
version: "12.1.0"
permission_handler_apple:
dependency: transitive
description:
name: permission_handler_apple
sha256: f000131e755c54cf4d84a5d8bd6e4149e262cc31c5a8b1d698de1ac85fa41023
url: "https://pub.dev"
source: hosted
version: "9.4.7"
permission_handler_html:
dependency: transitive
description:
name: permission_handler_html
sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24"
url: "https://pub.dev"
source: hosted
version: "0.1.3+5"
permission_handler_platform_interface:
dependency: transitive
description:
name: permission_handler_platform_interface
sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878
url: "https://pub.dev"
source: hosted
version: "4.3.0"
permission_handler_windows:
dependency: transitive
description:
name: permission_handler_windows
sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e"
url: "https://pub.dev"
source: hosted
version: "0.2.1"
petitparser:
dependency: transitive
description:
name: petitparser
sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675"
url: "https://pub.dev"
source: hosted
version: "7.0.2"
platform:
dependency: transitive
description:
name: platform
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
url: "https://pub.dev"
source: hosted
version: "3.1.6"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
url: "https://pub.dev"
source: hosted
version: "2.1.8"
posix:
dependency: transitive
description:
name: posix
sha256: "185ef7606574f789b40f289c233efa52e96dead518aed988e040a10737febb07"
url: "https://pub.dev"
source: hosted
version: "6.5.0"
provider:
dependency: "direct main"
description:
name: provider
sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272"
url: "https://pub.dev"
source: hosted
version: "6.1.5+1"
pub_semver:
dependency: transitive
description:
name: pub_semver
sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
sky_engine:
dependency: transitive
description: flutter
@@ -168,6 +501,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.4"
stream_transform:
dependency: transitive
description:
name: stream_transform
sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871
url: "https://pub.dev"
source: hosted
version: "2.1.1"
string_scanner:
dependency: transitive
description:
@@ -192,6 +533,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.7.9"
typed_data:
dependency: transitive
description:
name: typed_data
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
url: "https://pub.dev"
source: hosted
version: "1.4.0"
vector_math:
dependency: transitive
description:
@@ -208,6 +557,38 @@ packages:
url: "https://pub.dev"
source: hosted
version: "15.0.2"
web:
dependency: transitive
description:
name: web
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
url: "https://pub.dev"
source: hosted
version: "1.1.1"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
xml:
dependency: transitive
description:
name: xml
sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025"
url: "https://pub.dev"
source: hosted
version: "6.6.1"
yaml:
dependency: transitive
description:
name: yaml
sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
url: "https://pub.dev"
source: hosted
version: "3.1.3"
sdks:
dart: ">=3.11.0 <4.0.0"
flutter: ">=3.18.0-18.0.pre.54"
flutter: ">=3.38.4"

View File

@@ -35,6 +35,24 @@ dependencies:
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.8
# 相机
camera: ^0.10.5+9
# 图像处理
image: ^4.1.7
# 网络请求
dio: ^5.4.1
# 权限管理
permission_handler: ^11.3.0
# 状态管理
provider: ^6.1.1
# 路径
path_provider: ^2.1.2
dev_dependencies:
flutter_test:
sdk: flutter

21
aisee_flutter/run.bat Normal file
View File

@@ -0,0 +1,21 @@
@echo off
set PATH=C:\Users\xdedmi\flutter\bin;%PATH%
echo ========================================
echo AISee Flutter 启动脚本
echo ========================================
echo.
cd /d %~dp0
echo 正在检查设备...
flutter devices
echo.
echo 如果看到 Android 设备,按任意键继续运行...
echo 如果没有设备,请先启动模拟器或连接真机
pause
echo.
echo 正在启动应用...
flutter run
pause

View File

@@ -6,6 +6,9 @@
#include "generated_plugin_registrant.h"
#include <permission_handler_windows/permission_handler_windows_plugin.h>
void RegisterPlugins(flutter::PluginRegistry* registry) {
PermissionHandlerWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
}

View File

@@ -3,6 +3,7 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
permission_handler_windows
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST

View File

@@ -0,0 +1,253 @@
# 启动 AISee Flutter 项目指南
## 前提条件检查
### 1. 检查 Flutter 是否安装
打开命令提示符CMD或 PowerShell运行
```bash
flutter --version
```
**如果显示版本信息**,说明 Flutter 已安装,跳到步骤 2。
**如果提示"命令不存在"**,需要先安装 Flutter参考`Flutter开发环境搭建.md`
### 2. 检查设备连接
```bash
flutter devices
```
应该看到:
- Android 模拟器,或
- 连接的 Android 真机
**如果没有设备**
- 启动 Android 模拟器Android Studio -> Device Manager
- 或连接真机并开启 USB 调试
## 启动步骤
### 方法 1使用脚本推荐
1. **安装依赖**
- 双击 `install.bat`
- 等待依赖下载完成
2. **运行项目**
- 双击 `run.bat`
- 等待编译和安装
### 方法 2使用命令行
打开命令提示符,进入项目目录:
```bash
cd C:\Users\xdedmi\Desktop\aisee\aisee_flutter
# 1. 安装依赖
flutter pub get
# 2. 检查设备
flutter devices
# 3. 运行项目
flutter run
```
### 方法 3使用 VS Code
1. 用 VS Code 打开项目文件夹
2.`F5` 或点击右上角的运行按钮
3. 选择设备(模拟器或真机)
4. 等待编译和安装
## 配置后端地址
在运行前,需要配置后端 API 地址。
编辑文件:`lib/utils/app_config.dart`
```dart
class AppConfig {
// 如果使用 Android 模拟器
static const String apiBaseUrl = 'http://10.0.2.2:8000';
// 如果使用真机(改为你的电脑 IP 地址)
// static const String apiBaseUrl = 'http://192.168.1.100:8000';
// ...
}
```
### 如何获取电脑 IP 地址?
**Windows:**
```bash
ipconfig
# 查找 "IPv4 地址"例如192.168.1.100
```
**确保手机和电脑在同一 WiFi 网络!**
## 首次运行
首次运行需要:
1. 下载 Gradle 依赖(约 5-10 分钟)
2. 编译 Android 应用(约 2-3 分钟)
3. 安装到设备
**请耐心等待,不要中断!**
## 运行成功标志
看到以下信息说明成功:
```
✓ Built build/app/outputs/flutter-apk/app-debug.apk.
Installing build/app/outputs/flutter-apk/app.apk...
Syncing files to device...
Flutter run key commands.
r Hot reload.
R Hot restart.
```
应用会自动在设备上打开。
## 使用应用
1. **授予权限**
- 首次打开会请求相机权限
- 点击"允许"
2. **打开相机**
- 点击首页的"打开相机"按钮
3. **开始传输**
- 点击"开始传输"按钮
- 观察状态栏的帧数统计
4. **查看效果**
- 捕获帧数:相机捕获的总帧数
- 上传帧数:成功上传到后端的帧数
## 常见问题
### 1. Flutter 命令不存在
**问题**`'flutter' 不是内部或外部命令`
**解决**
1. 检查 Flutter 是否安装
2. 检查环境变量是否配置
3. 重启命令提示符
4. 参考 `Flutter开发环境搭建.md`
### 2. 没有可用设备
**问题**`No devices found`
**解决**
- 启动 Android 模拟器
- 或连接真机并开启 USB 调试
### 3. Gradle 下载慢
**问题**:卡在 `Running Gradle task 'assembleDebug'...`
**解决**
1. 配置国内镜像(见环境搭建文档)
2. 使用代理
3. 耐心等待(首次需要 10-30 分钟)
### 4. 编译错误
**问题**`FAILURE: Build failed with an exception`
**解决**
```bash
# 清理项目
flutter clean
# 重新获取依赖
flutter pub get
# 重新运行
flutter run
```
### 5. 相机权限被拒绝
**问题**:应用显示"相机权限被拒绝"
**解决**
1. 进入手机设置 -> 应用管理 -> aisee_flutter
2. 权限 -> 相机 -> 允许
3. 重启应用
### 6. 网络连接失败
**问题**:上传图像失败
**解决**
1. 确保后端服务已启动
2. 检查 `app_config.dart` 中的 IP 地址
3. 模拟器用 `10.0.2.2`,真机用电脑 IP
4. 确保手机和电脑在同一网络
5. 关闭防火墙(测试时)
## 热重载
代码修改后,无需重新编译:
```bash
# 在运行的终端按:
r # 热重载(保留状态)
R # 热重启(重置状态)
q # 退出
```
或在 VS Code 中:
- `Ctrl + S` 保存后自动热重载
- `Ctrl + F5` 热重启
## 调试
### 查看日志
```bash
flutter logs
```
### 查看性能
```bash
flutter run --profile
```
### 查看设备信息
```bash
flutter doctor -v
```
## 下一步
项目运行成功后:
1. 测试拍照功能
2. 测试实时传输
3. 调整配置参数(分辨率、帧率)
4. 开发后端 API
5. 集成 AI 模型
---
**需要帮助?**
查看项目根目录的其他文档:
- `Flutter开发环境搭建.md` - 环境配置
- `Flutter项目初始化.md` - 项目结构
- `技术方案.md` - 技术架构

View File

@@ -0,0 +1,104 @@
# 手机连接问题解决方案
## 问题诊断
✅ 手机已连接并开启 USB 调试
❌ Flutter 无法识别 Android 设备
❌ 原因Android SDK 未安装或未配置
## 解决方案
### 方案 1安装 Android Studio推荐
1. **下载 Android Studio**
- 官网https://developer.android.com/studio
- 下载最新版本
2. **安装 Android Studio**
- 双击安装包
- 选择 "Standard" 安装
- 等待下载 Android SDK约 2-3GB
3. **配置 Flutter**
```bash
# Android Studio 安装完成后Flutter 会自动检测
C:\Users\xdedmi\flutter\bin\flutter doctor
```
4. **重新连接手机**
- 拔掉 USB 重新连接
- 手机上重新授权 USB 调试
- 运行:`flutter devices`
### 方案 2仅安装 Android SDK 命令行工具(快速)
1. **下载 SDK 命令行工具**
- https://developer.android.com/studio#command-tools
- 下载 "Command line tools only"
2. **解压到固定位置**
```
C:\Android\cmdline-tools\latest\
```
3. **配置环境变量**
```
ANDROID_HOME=C:\Android
Path 添加:
C:\Android\cmdline-tools\latest\bin
C:\Android\platform-tools
```
4. **安装必要组件**
```bash
sdkmanager "platform-tools" "platforms;android-34"
```
5. **配置 Flutter**
```bash
C:\Users\xdedmi\flutter\bin\flutter config --android-sdk C:\Android
```
## 快速验证
安装完成后运行:
```bash
# 1. 检查环境
C:\Users\xdedmi\flutter\bin\flutter doctor
# 2. 检查设备
C:\Users\xdedmi\flutter\bin\flutter devices
# 3. 应该看到你的手机
# 例如SM G9980 (mobile) • xxxxxx • android-arm64 • Android 14
```
## 临时解决方案:使用 Web 版本
如果暂时无法配置 Android可以先在浏览器中测试但相机功能不可用
```bash
cd C:\Users\xdedmi\Desktop\aisee\aisee_flutter
C:\Users\xdedmi\flutter\bin\flutter run -d chrome
```
## 我的建议
**推荐安装 Android Studio**,因为:
1. 一键安装所有必要组件
2. 包含模拟器管理器
3. 提供完整的开发工具
4. 自动配置环境变量
安装时间:约 30-60 分钟(包括下载)
## 下一步
1. 选择一个方案开始安装
2. 安装完成后告诉我
3. 我会帮你验证并启动项目
---
你想选择哪个方案?我推荐方案 1Android Studio