Files
observer/flutter_app/README.md
T

99 lines
6.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# observer
动物实时识别 AppFlutter 版)。Android / iOS 一套代码,后端接口与支付见
[`docs/PaymentApi.md`](docs/PaymentApi.md)。
## Android 打包
```bash
./build_apk.sh # 用当前 pubspec 版本构建,产物 build/app/outputs/flutter-apk/observer-x.y.z.apk
./build_apk.sh --bump # 自动递增 patch+1、build+1 后构建
./build_apk.sh 1.0.7 # 用指定版本(versionName)构建,build+1
```
产物命名 `observer-x.y.z.apk`(管理端上传版本号从文件名识别);构建的
`app-release.apk` 中间产物由脚本清理,只保留规范命名文件。
## iOS 真机部署(iPhone
### 构建与安装
```bash
# 生产包直接构建即可:默认 API_BASE_URL 为线上域名(lib/config/app_config.dart),无需传参
flutter build ios --release
# 仅本地联调(后端跑在 Mac 上、手机连同一 Wi-Fi)时才覆盖为 Mac 局域网 IP:
# flutter build ios --release --dart-define=API_BASE_URL=http://<Mac局域网IP>:8080
# 安装到真机(UDID 可用 `xcrun devicectl list devices` 查询)
xcrun devicectl device install app --device <UDID> build/ios/iphoneos/Runner.app
# 启动并抓控制台日志(--terminate-existing 先杀掉旧实例)
xcrun devicectl device process launch --console --terminate-existing \
--device <UDID> com.observer.app
```
### 注意事项(踩过的坑)
- **debug 构建不能在真机上从桌面图标启动**:iOS 14+ 会提示
"In iOS 14+, debug mode Flutter apps can only be launched from Flutter tooling"。
debug 调试必须用 `flutter run -d <设备ID>` 或 Xcode IDE 启动(`flutter devices` 查设备ID);
从图标启动只对 release 构建有效。
- **`flutter run` 真机 debug 附接失败(errno 49,多次复现)**:Xcode 构建、安装、
启动都成功,attach 阶段报 `OS Error: Can't assign requested address, errno = 49`
工具即退出——与本机 VPN(utun 隧道)环境相关,断 VPN 后可恢复。
需要真机验证时一律用上方 release + devicectl 流程(不依赖 attach);只有需要
热重载/看 debugPrint 才用 `flutter run`,遇 errno 49 先断 VPN 重试。
## 端侧推理加速(GPU / CoreML
识别慢的根因是 yolov8s@1280 推理量大(CPU 4 线程约每秒不到 1 帧),
`TfliteDetector.fromBuffer` 加载模型时按平台挂加速 delegate,均为**浮点计算不降精度**
(区别于 int8 量化掉点):
| 平台 | delegate | 说明 |
|---|---|---|
| Android | `GpuDelegateV2` | TFLite GPU delegate;依赖 `libtensorflowlite_gpu_jni.so`,已 vendor 到 `android/app/src/main/jniLibs/arm64-v8a/`AAR 因 AGP 9 namespace 冲突保持排除,升级 tflite_flutter 时需同步换 .so,版本对齐 base 2.11.0 |
| iOS | `CoreMlDelegate` | Core ML(苹果 ANE/GPU,插件 pod 自带 TensorFlowLiteSwift/CoreML,无需额外依赖) |
delegate 初始化失败(老设备/驱动/符号缺失)**自动回退纯 CPU 4 线程**,最后才返回 null
(仅预览不识别)。生效与否看日志:加载模型时输出
`[TfliteDetector] 加速生效 model=xxx (CoreML|GPU)`,回退输出 `回退 CPU` 及原因。
GPU delegate 默认允许 FP16 计算(YOLO 类精度损失可忽略);如需全精度改为传
`GpuDelegateOptionsV2(isPrecisionLossAllowed: false)`
## 模型热更新(多数据集模型)
模型与 APK 更新走**独立通道**:启动时拉取 `GET /api/v1/app/update` 随附的
`models` 目录(公开接口,无需登录),与 `UpdateChecker` 的 APK 检查并行。
- **目录条目**`{datasetId, datasetName, variant, version, labels[], sizeBytes, sha256,
downloadUrl, coverUrl}`——**双档位(2026-09-03)**:每数据集至多 2 条 = 高识别 s
(@1280 精度优先,默认)+ 高性能 n(@704 速度优先)各自的当前版本,条目带 `variant`
(s/n);服务器未发布模型时不返回 `models` 字段,App 无模型可用,
相机页仅预览不识别。
- **下载入口**:相机页设置弹层「模型清单」按需下载/使用(封面缩略图 2 列网格,
每数据集两档各占一格并带档位角标;未下载点击「使用」显示进度,完成自动激活;
已激活再次点击取消;下载中可取消)。**档位切换**:弹层顶部「识别模式」分段控件
(s 高识别 / n 高性能,默认 s)持久化本地,切换即热加载新档位已激活模型。
- **存储**:应用私有目录 `models/<datasetId>/<variant>/`(双档位 2026-09-03
原无 variant 目录与存量 s 档一致——s 档复用 `models/<datasetId>/` 同级读取,
目录键 = 档位标识符),含 `model.tflite`、`labels.json`、`meta.json`
meta 记录 `{version, sha256}`)。版本与摘要都未变化时跳过下载;变化则下载到
`.part` 临时文件、sha256 校验通过后原子 rename 替换,失败重试一次并保留旧模型,
下次启动再试——检查记账按 `(datasetId, variant)` 独立。
- **清理**:服务器下线的数据集下次同步时删除本地对应目录(两档都无条目时才删)。
- **并行推理合并**:识别时加载**当前识别档位**下全部已激活模型(`DetectorWorker`
isolate 内逐模型加载,单个失败不影响其他),同帧各模型独立推理后按类别分组做
**跨模型 NMS**(同类别不同模型检出同一目标取高分去重,不同类别互不压制),
结果叠加 `modelName`(数据集名+档位)标注来源。
实现:`lib/models/model_manager.dart`(下载/校验/持久化,`ModelManager`
单例 + ChangeNotifier,条目身份含档位)、`lib/detection/detector_worker.dart`
(多模型并行推理与 `mergeAcrossModels`)、`lib/camera/camera_screen.dart`
(启动同步 + 设置弹层「识别模式」切换 + 诊断行展示模型列表)。
- **模型输入是 NHWC**:训练导出的模型需做字节级手术(开头 TRANSPOSE→RESHAPE
输入 [1,320,320,3])再发布给 App,否则 iOS 报
"Node number 0 (TRANSPOSE) failed to prepare"。
- **模拟器黑屏**:本机 iOS 模拟器 Impeller 渲染黑屏,验证 UI 用 VM service
`flutter run` 输出里的 DevTools 地址),或直接真机验证。