初始化 observer 项目:纯代码,不含权重与训练数据
This commit is contained in:
+318
@@ -0,0 +1,318 @@
|
||||
# Observer(野视)· 野生动物实时识别 Android App — 技术方案
|
||||
|
||||
| 项目 | 内容 |
|
||||
| --- | --- |
|
||||
| 文档版本 | v1.1 |
|
||||
| 编写日期 | 2026-08-17 |
|
||||
| 状态 | 初稿(v1.1:确认功能范围为"仅实时识别",移除拍照留存) |
|
||||
| 适用产品 | 纯 Android 原生 App |
|
||||
|
||||
---
|
||||
|
||||
## 1. 项目概述
|
||||
|
||||
### 1.1 项目背景
|
||||
|
||||
野生动物观察爱好者在户外需要一款便携工具:打开手机相机,即可从实时画面中识别野鸡,通过检测框与提醒辅助快速发现。
|
||||
|
||||
### 1.2 项目目标
|
||||
|
||||
- 端侧实时目标检测,**全程离线可用**,不依赖网络;
|
||||
- 野鸡实时框选识别,展示类别、置信度与**大致距离**;
|
||||
- 检测到目标时通过震动 / 声音提醒用户,辅助快速发现;
|
||||
- 纯 Android 原生 App(Kotlin),兼容 Android 7.0+ 主流机型,中端机流畅运行;
|
||||
- **不做拍照、记录、统计等留存功能**,专注实时识别这一核心体验。
|
||||
|
||||
### 1.3 名词术语
|
||||
|
||||
| 术语 | 说明 |
|
||||
| --- | --- |
|
||||
| ImageAnalysis | CameraX 的帧分析用例,用于逐帧回调图像数据 |
|
||||
| TFLite | TensorFlow Lite,Google 端侧推理框架 |
|
||||
| YOLO | You Only Look Once,单阶段目标检测算法 |
|
||||
| NMS | Non-Maximum Suppression,非极大值抑制 |
|
||||
| GPU Delegate | TFLite 的 GPU 加速委托,将算子下发 GPU 执行 |
|
||||
| mAP | mean Average Precision,目标检测平均精度指标 |
|
||||
| IoU | Intersection over Union,交并比 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 需求概述
|
||||
|
||||
### 2.1 目标用户
|
||||
|
||||
| 用户群 | 典型诉求 |
|
||||
| --- | --- |
|
||||
| 野生动物观察爱好者 | 户外实时识别画面中的野鸡,不惊扰、近距离观察 |
|
||||
| 户外徒步 / 摄影人群 | 快速发现野鸡,辅助取景构图 |
|
||||
| 自然教育、科普工作者 | 物种识别辅助教学 |
|
||||
|
||||
### 2.2 核心场景
|
||||
|
||||
| 编号 | 场景 | 描述 |
|
||||
| --- | --- | --- |
|
||||
| S1 | 野外实时识别 | 徒步 / 观鸟时打开相机,实时框选画面中的野鸡,显示类别、置信度与大致距离 |
|
||||
| S2 | 快速扫视寻找 | 移动取景快速扫视,检测到野鸡立即震动 / 声音提醒,无需停留操作 |
|
||||
|
||||
### 2.3 功能需求摘要
|
||||
|
||||
实时识别(P0)、识别结果叠加展示(P0)、检测提醒(P1)、生境区域预警(P1)、设置(P1)、合规提示(P0)、低光增强(P2)。详见《项目功能文档》。
|
||||
|
||||
### 2.4 非功能需求摘要
|
||||
|
||||
| 指标 | 目标 |
|
||||
| --- | --- |
|
||||
| 识别延迟 | 中端机单帧 ≤ 80ms |
|
||||
| 预览流畅度 | 连续模式 2~3 帧检测一次,预览不卡顿 |
|
||||
| 耗电 | 连续使用 1 小时耗电 ≤ 15% |
|
||||
| 兼容性 | Android 7.0+(minSdk 24),覆盖主流国产机与三星 |
|
||||
| 稳定性 | 崩溃率 ≤ 0.5%,启动成功率 ≥ 99% |
|
||||
|
||||
---
|
||||
|
||||
## 3. 总体架构
|
||||
|
||||
### 3.1 架构图
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
subgraph UI层["UI 层 · Jetpack Compose"]
|
||||
MainScreen["相机主界面<br/>预览 + 检测叠加层"]
|
||||
SettingsScreen["设置界面"]
|
||||
end
|
||||
|
||||
subgraph ViewModel层["ViewModel 层"]
|
||||
CameraViewModel["CameraViewModel"]
|
||||
SettingsViewModel["SettingsViewModel"]
|
||||
end
|
||||
|
||||
subgraph 领域层["领域层"]
|
||||
Detector接口["Detector 接口"]
|
||||
end
|
||||
|
||||
subgraph 基础设施层["基础设施层"]
|
||||
CameraX["CameraX<br/>Preview / ImageAnalysis"]
|
||||
TFLite["TFLite 推理引擎<br/>YOLOv8n 模型 + GPU Delegate"]
|
||||
DataStore["DataStore 设置存储"]
|
||||
end
|
||||
|
||||
UI层 --> ViewModel层
|
||||
ViewModel层 --> 领域层
|
||||
领域层 --> 基础设施层
|
||||
```
|
||||
|
||||
### 3.2 分层说明
|
||||
|
||||
| 层 | 职责 |
|
||||
| --- | --- |
|
||||
| UI 层 | Compose 页面渲染、检测叠加层绘制、交互 |
|
||||
| ViewModel 层 | 页面状态管理、识别结果分发、提醒触发 |
|
||||
| 领域层 | 检测器抽象接口 |
|
||||
| 基础设施层 | CameraX、TFLite、DataStore 等能力实现 |
|
||||
|
||||
### 3.3 核心数据流(识别链路)
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["CameraX 相机帧"] --> B["ImageAnalysis 帧分析"]
|
||||
B --> C["预处理<br/>缩放 320×320 / 归一化 / 旋转校正"]
|
||||
C --> D["TFLite 推理<br/>YOLOv8n"]
|
||||
D --> E["后处理<br/>解码 / NMS / 阈值过滤"]
|
||||
E --> F["叠加层渲染<br/>边框 + 类别 + 置信度"]
|
||||
F --> G["UI 展示"]
|
||||
E --> H["结果提醒<br/>震动 / 提示音"]
|
||||
```
|
||||
|
||||
### 3.4 部署形态
|
||||
|
||||
- **本地优先**:识别全部离线完成,运行期无网络请求;
|
||||
- **云端(V2 可选)**:仅用于模型版本更新下发。
|
||||
|
||||
---
|
||||
|
||||
## 4. 技术选型
|
||||
|
||||
| 类别 | 选型 | 理由 |
|
||||
| --- | --- | --- |
|
||||
| 开发语言 | Kotlin | Android 官方推荐,协程生态成熟 |
|
||||
| UI 框架 | Jetpack Compose(Material 3) | 声明式 UI,开发效率高,叠加层绘制灵活 |
|
||||
| 相机 | CameraX(Preview / ImageAnalysis) | Jetpack 官方库,生命周期安全,机型兼容性最佳 |
|
||||
| 目标检测模型 | YOLOv8n(自定义 4 类训练) | 精度/速度均衡,端侧部署方案成熟 |
|
||||
| 推理框架 | TensorFlow Lite 2.16+(GPU Delegate) | 官方支持 GPU 加速;备选 NCNN / MNN |
|
||||
| 配置存储 | DataStore Preferences | 阈值、开关等设置 |
|
||||
| 异步 | Kotlin Coroutines + Flow | 主线程安全,生命周期感知 |
|
||||
| 构建 | Gradle(Kotlin DSL)+ AGP 8.x | 现代构建配置 |
|
||||
|
||||
---
|
||||
|
||||
## 5. 核心功能技术方案
|
||||
|
||||
### 5.1 实时识别管线
|
||||
|
||||
- CameraX 组合:`Preview`(取景)+ `ImageAnalysis`(识别);
|
||||
- `ImageAnalysis` 使用 `STRATEGY_KEEP_ONLY_LATEST` 背压策略,保证不积压帧;
|
||||
- 输出格式使用 `OUTPUT_IMAGE_FORMAT_RGBA_8888`(CameraX 1.3+),免去 YUV→RGB 手动转换;
|
||||
- **帧节流**:连续模式每 2~3 帧检测一次;标准模式 300ms 一次;省电模式每秒一次(依据设置);
|
||||
- 推理在独立检测线程执行,单例互斥锁防止重叠推理;分析线程不阻塞主线程。
|
||||
|
||||
### 5.2 检测叠加层
|
||||
|
||||
- Compose Canvas 绘制检测框(矩形 + 类别标签 + 置信度),颜色按类别区分;
|
||||
- 检测框内同时标注**大致距离**(如"约 25m"),随检测框实时更新;
|
||||
- 距离标注与检测框同生共灭,展示逻辑一致(≥ 0.5s 出现、消失 2s 后移除);
|
||||
- 坐标映射链路:模型归一化坐标 → 旋转校正(sensor rotation)→ 预览视图坐标(含 FIT_CENTER 裁剪偏移修正);
|
||||
- 目标保持 ≥ 0.5s 才展示,避免单帧误检闪烁。
|
||||
|
||||
### 5.3 距离标注(单目估计)
|
||||
|
||||
- 采用单目针孔模型:`距离 ≈ 焦距px × 物种参考体型 / 检测框像素高度`;
|
||||
- 焦距 px 由相机内参换算(`LENS_INFO_AVAILABLE_FOCAL_LENGTHS` × `SENSOR_INFO_PHYSICAL_SIZE`),个别机型回退用视场角推算;
|
||||
- 物种参考体型内置表(见 6.1),每类一个平均体型值;
|
||||
- 展示为"约 X m";5~50m 范围误差预期 ≤ ±30%;
|
||||
- 纯算术运算,无额外模型与算力开销;V2 可引入地面平面法(设备俯仰角 + 相机高度)提升精度。
|
||||
|
||||
### 5.4 检测提醒
|
||||
|
||||
- 检测到目标时触发震动 / 提示音,用户可开关;
|
||||
- 防重复打扰:10s 内同类目标只提醒一次;
|
||||
- 目标消失后 2s 内不闪断显示,避免频繁提醒。
|
||||
|
||||
### 5.5 低光增强(P2)
|
||||
|
||||
- 简单直方图均衡 / 亮度增益提升低光帧可见度;
|
||||
- V2 可引入夜视增强网络思路。
|
||||
|
||||
---
|
||||
|
||||
## 6. AI 模型方案(核心)
|
||||
|
||||
### 6.1 目标类别定义
|
||||
|
||||
| 类别 ID | 英文标签 | 中文名 | 涵盖范围 | 参考体型(距离估计用) |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 0 | pheasant | 野鸡 | 环颈雉等雉类 | ≈ 0.45m(身高) |
|
||||
|
||||
### 6.2 模型选型对比
|
||||
|
||||
| 模型 | 输入尺寸 | 中端机单帧耗时(GPU) | 精度 | 说明 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| **YOLOv8n(推荐)** | 320~640 | 约 30~80ms | 高 | 支持自定义类别训练,精度/速度平衡 |
|
||||
| EfficientDet-Lite0/2 | 320 | 约 20~50ms | 中 | 仅 COCO 预置类别 |
|
||||
| YOLOv5s | 640 | 约 80~150ms | 高 | 体积与耗电偏大 |
|
||||
| SSD MobileNetV2 | 300 | 约 15~30ms | 低 | 小目标与远距离效果差 |
|
||||
|
||||
**结论**:选用 **YOLOv8n**,使用预训练权重迁移学习自定义训练,导出 TFLite 端侧部署。
|
||||
|
||||
### 6.3 数据集方案
|
||||
|
||||
| 项 | 方案 |
|
||||
| --- | --- |
|
||||
| 数据量 | 1000~2000 张(首版可 500+ 起步,滚动补充) |
|
||||
| 多样性 | 覆盖不同季节、晨昏/正午/逆光、远近距离、姿态、遮挡、背景(草丛/农田/林地/雪地) |
|
||||
| 标注 | Roboflow 或 labelImg,YOLO 格式(class, cx, cy, w, h) |
|
||||
| 数据增强 | Mosaic、MixUp、HSV 扰动、随机翻转、随机缩放裁剪 |
|
||||
| 数据划分 | train 80% / val 10% / test 10% |
|
||||
| 负样本 | 补充无目标场景图,控制误检 |
|
||||
|
||||
### 6.4 训练方案
|
||||
|
||||
- 框架:ultralytics YOLOv8,预训练权重 `yolov8n.pt` 迁移学习;
|
||||
- 超参:imgsz=640(训练)、epochs 100~200(早停)、batch 16~32(视 GPU 而定);
|
||||
- 评估指标:
|
||||
- mAP@0.5 ≥ **0.85**(达标线 0.80);
|
||||
- mAP@0.5:0.95 ≥ **0.55**;
|
||||
- 负样本误检率 ≤ **2%**;
|
||||
- 远距离小目标(高度 ≤ 20px)召回率 ≥ **60%**。
|
||||
|
||||
### 6.5 模型转换与量化
|
||||
|
||||
- 导出命令:`yolo export model=best.pt format=tflite imgsz=320`;
|
||||
- 量化策略:
|
||||
- 优先 **fp16**(配合 GPU Delegate,精度损失小);
|
||||
- 低端机 / CPU 场景使用 **int8** 量化(体积更小、CPU 更快);
|
||||
- 输入尺寸:320×320(连续检测)/ 416×416(远距离模式,V2)。
|
||||
|
||||
### 6.6 推理优化
|
||||
|
||||
- GPU Delegate 优先,初始化失败自动回退 CPU(NNAPI 可选);
|
||||
- 推理线程 4 线程,首次推理前执行预热(dummy run);
|
||||
- 输入输出 ByteBuffer / Bitmap 复用,避免热路径频繁分配。
|
||||
|
||||
### 6.7 精度与速度目标
|
||||
|
||||
| 指标 | 目标 |
|
||||
| --- | --- |
|
||||
| 检测阈值(默认) | confidence ≥ 0.40,IoU-NMS = 0.45 |
|
||||
| 单帧推理延迟 | ≤ 80ms(中端机,320 输入,GPU) |
|
||||
| 模型体积 | fp16 ≤ 8MB,int8 ≤ 4MB |
|
||||
| 识别类别数 | 1 类(野鸡) |
|
||||
| 距离标注 | "约 X m",5~50m 误差 ≤ ±30%,计算开销可忽略 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 性能指标与优化
|
||||
|
||||
| 指标 | 目标值 | 主要优化手段 |
|
||||
| --- | --- | --- |
|
||||
| 启动到相机预览 | ≤ 1.5s | 启动即初始化 CameraProvider,懒加载非核心模块 |
|
||||
| 单帧检测延迟 | ≤ 80ms | GPU Delegate、320 输入、线程复用 |
|
||||
| 预览帧率 | ≥ 25fps | 帧节流、KEEP_ONLY_LATEST、避免主线程工作 |
|
||||
| 内存峰值 | ≤ 200MB | Bitmap/ByteBuffer 复用,无大对象常驻 |
|
||||
| 连续 1 小时耗电 | ≤ 15% | 检测帧节流、省电模式、后台自动释放相机 |
|
||||
| App 体积 | ≤ 40MB | ABI 拆分、模型量化、R8 混淆 |
|
||||
|
||||
---
|
||||
|
||||
## 8. 数据与隐私
|
||||
|
||||
- 无账号、无埋点、无广告 SDK,**不采集任何用户数据**;
|
||||
- 不保存照片、不记录位置,运行期无网络请求;
|
||||
- 模型与标签随 APK 打包,识别数据仅存在于内存中,随进程结束自动释放。
|
||||
|
||||
---
|
||||
|
||||
## 9. 安全与合规
|
||||
|
||||
- 产品定位为**野生动物观察、识别工具**,不提供任何猎捕、诱捕、伤害野生动物的功能或指导;
|
||||
- 遵守《中华人民共和国野生动物保护法》《陆生野生动物保护实施条例》等法律法规;
|
||||
- 野鸡(雉类)属"三有"保护动物,猎捕须依法取得许可,App 不鼓励、不协助非法猎捕;
|
||||
- App 内置观察伦理提示:保持安全距离、不惊扰动物、遵守保护区管理规定(首次启动展示,功能文档 F07)。
|
||||
|
||||
---
|
||||
|
||||
## 10. 开发计划(里程碑)
|
||||
|
||||
| 阶段 | 周期 | 交付内容 |
|
||||
| --- | --- | --- |
|
||||
| M0 准备 | 1 周 | 需求确认、数据集启动采集、Android 工程脚手架、模型基线 |
|
||||
| M1 MVP | 3~4 周 | 相机预览 + 实时检测 + 叠加层 + 检测提醒 + 设置 + 合规提示 |
|
||||
| M2 打磨发布 | 1~2 周 | 性能优化、真机矩阵测试、混淆加固、上架准备 |
|
||||
|
||||
总计约 **5~7 周**(不含数据采集并行时间)。
|
||||
|
||||
---
|
||||
|
||||
## 11. 风险与应对
|
||||
|
||||
| 风险 | 影响 | 应对 |
|
||||
| --- | --- | --- |
|
||||
| 训练数据不足 / 类别相近 | 精度低、误检 | 持续滚动采集数据;增加难例挖掘;灰度发布迭代模型 |
|
||||
| 小目标(远处动物) | 漏检 | 提高输入分辨率;帧节流换取算力;提示用户靠近/变焦;V2 引入 SAHI/tiling 或专用小目标模型 |
|
||||
| 低光 / 夜间场景 | 漏检 | 低光增强预处理;V2 夜视模式 |
|
||||
| 单目距离估计精度有限 | 距离显示不准 | 标注为"约"并明示误差预期;体型表持续校准;V2 地面平面法 + 水下折射修正 |
|
||||
| 生境区域误报偏多 | 提醒频繁、体验差 | 独立低阈值 + 区分提醒方式 + 防重复机制;生境阈值可调;模型迭代降低误报 |
|
||||
| 中低端机型性能不足 | 帧率低、发热 | 动态分辨率、降频检测、GPU 回退策略、省电模式 |
|
||||
| CameraX 个别机型异常 | 黑屏/闪退 | 机型兼容测试矩阵、崩溃监控、失败回退(重试/默认配置) |
|
||||
| 合规风险(被用于非法猎捕) | 法律风险 | 产品定位为观察工具、内置合规提示、不提供猎捕辅助功能 |
|
||||
|
||||
---
|
||||
|
||||
## 12. 验收标准
|
||||
|
||||
1. 野鸡测试集 mAP@0.5 ≥ 0.85,负样本误检率 ≤ 2%;
|
||||
2. 中端机(如骁龙 7 系)单帧检测 ≤ 80ms,预览流畅无卡顿;
|
||||
3. 连续使用 1 小时耗电 ≤ 15%,无异常发热;
|
||||
4. 主流机型(小米 / 华为 / OPPO / vivo / 三星,各 ≥ 2 台)启动成功率 ≥ 99%,崩溃率 ≤ 0.5%;
|
||||
5. 飞行模式下实时识别全流程(预览、检测、提醒、设置)可用;
|
||||
6. 检测框正确显示类别、置信度与距离;5~50m 范围距离误差 ≤ ±30%;
|
||||
7. 通过应用商店合规审核(隐私政策、权限声明完整)。
|
||||
@@ -0,0 +1,193 @@
|
||||
# Observer(野视)· 项目功能文档
|
||||
|
||||
| 项目 | 内容 |
|
||||
| --- | --- |
|
||||
| 文档版本 | v1.1 |
|
||||
| 编写日期 | 2026-08-17 |
|
||||
| 状态 | 初稿(v1.1:确认功能范围为"仅实时识别",移除拍照留存) |
|
||||
| 配套文档 | 《01-技术方案》《03-技术实现文档》 |
|
||||
|
||||
---
|
||||
|
||||
## 1. 产品概述
|
||||
|
||||
### 1.1 产品定位
|
||||
|
||||
一款**纯 Android 原生**的野生动物实时识别 App。用户打开相机,即可从实时画面中识别野鸡,通过彩色检测框(含类别、置信度、距离标注)与震动 / 声音提醒辅助快速发现。**无拍照、无记录、无统计**,专注实时识别这一核心体验,**全程离线可用**。
|
||||
|
||||
### 1.2 目标用户
|
||||
|
||||
- 野生动物观察爱好者(观鸟 / 观兽);
|
||||
- 户外徒步、摄影人群;
|
||||
- 自然教育、科普工作者。
|
||||
|
||||
### 1.3 价值主张
|
||||
|
||||
- **即时发现**:打开相机即识别,检测到目标立即提醒,免去翻图鉴、猜物种;
|
||||
- **零门槛**:不依赖网络,不依赖外设,一部手机即可;
|
||||
- **纯净体验**:无留存功能、无账号、无广告,即开即用。
|
||||
|
||||
---
|
||||
|
||||
## 2. 功能架构(功能树)
|
||||
|
||||
```
|
||||
Observer
|
||||
├── 实时识别
|
||||
│ ├── 相机实时预览(F01)
|
||||
│ ├── 目标实时检测(F02)
|
||||
│ ├── 识别结果展示与距离标注(F03)
|
||||
│ ├── 检测提醒(F04)
|
||||
│ └── 低光增强(F06,P2)
|
||||
├── 设置
|
||||
│ ├── 识别参数(F05)
|
||||
│ └── 提醒设置(F04 子项)
|
||||
└── 合规提示(F07)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 功能需求清单
|
||||
|
||||
优先级定义:**P0**=必须,**P1**=V1.0,**P2**=后续版本。
|
||||
|
||||
| 编号 | 功能 | 功能描述 | 优先级 |
|
||||
| --- | --- | --- | --- |
|
||||
| F01 | 相机实时预览 | 全屏取景,默认后置,支持自动对焦、双指缩放、点击对焦、前后摄切换 | P0 |
|
||||
| F02 | 目标实时检测 | 对野鸡实时检测框选 | P0 |
|
||||
| F03 | 识别结果展示与距离标注 | 检测框 + 类别名 + 置信度百分比 + 大致距离(约 X m);四类目标使用不同颜色边框 | P0 |
|
||||
| F04 | 检测提醒 | 检测到目标时震动 / 提示音;支持开关与 10s 防重复 | P1 |
|
||||
| F05 | 设置 | 置信度阈值(0.2~0.7)、检测频率(连续 / 标准 / 省电)、提醒开关、低光增强开关 | P1 |
|
||||
| F06 | 低光增强 | 低光环境下自动亮度 / 对比度增强,提高识别率 | P2 |
|
||||
| F07 | 合规提示 | 首次启动展示《观察伦理与法律提示》,需用户确认 | P0 |
|
||||
| F08 | 生境区域预警 | 画面中未检测到目标时,识别草丛 / 灌木 / 水面等疑似生境区域,以黄色虚线框展示(含距离标注)并触发预警(独立阈值与提醒方式) | P1 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 核心业务流程
|
||||
|
||||
### 4.1 首次启动与授权
|
||||
|
||||
```
|
||||
启动 App → 合规提示页(F07,用户确认)
|
||||
→ 请求相机权限
|
||||
├─ 授权 → 进入相机主界面
|
||||
└─ 拒绝 → 引导页说明用途,可重新授权
|
||||
```
|
||||
|
||||
### 4.2 实时识别
|
||||
|
||||
```
|
||||
相机主界面(默认后置)
|
||||
→ 画面持续检测(连续 / 标准 / 省电频率)
|
||||
→ 检测到目标:显示彩色检测框 + 类别 + 置信度 + 距离
|
||||
├─ 触发提醒(震动 / 声音,10s 防重复)
|
||||
└─ 目标停留 ≥ 0.5s 保持显示,消失后 2s 内不闪断
|
||||
→ 未检测到目标时:识别疑似生境区域(草丛 / 灌木 / 水面)
|
||||
├─ 黄色虚线框展示,标注"疑似区域"与距离
|
||||
└─ 触发预警(提醒方式与动物检测区分)
|
||||
→ 用户可点击检测框查看识别详情(物种、置信度、时间)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 界面设计
|
||||
|
||||
### 5.1 界面总览
|
||||
|
||||
仅两个界面:**相机主界面**(默认全屏展示)+ **设置页**(右上角齿轮入口)。
|
||||
|
||||
### 5.2 相机主界面(核心界面)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────┐
|
||||
│ [低光] [频率] [前/后摄] │ ← 顶部工具
|
||||
│ 相机预览画面(全屏) │
|
||||
│ ┌─────────────────────────────┐ │
|
||||
│ │ ┌───────┐ │ │
|
||||
│ │ │ 野鸡 86% │ │ │ ← 检测框(按类别着色)
|
||||
│ │ │ 约 25m │ │ │
|
||||
│ │ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ │ │ ← 疑似生境区域(黄色虚线,预警)
|
||||
│ │ 疑似区域 · 约 20m │ │
|
||||
│ │ └───────┘ │ │
|
||||
│ └─────────────────────────────┘ │
|
||||
│ [提醒开关] [设置] │ ← 底部操作
|
||||
└─────────────────────────────────┘
|
||||
```
|
||||
|
||||
交互说明:
|
||||
|
||||
- 顶部:检测频率切换(连续 / 标准 / 省电)、低光增强开关(P2)、前后摄像头切换;
|
||||
- 中部:全屏预览,检测框 + 类别 + 置信度实时叠加;
|
||||
- 底部:提醒开关(震动 / 声音)、设置入口;
|
||||
- 支持双指缩放、点击对焦;
|
||||
- 检测到目标时边框高亮 + 震动 / 声音提醒(10s 防重复);
|
||||
- 检测框内显示类别、置信度与大致距离(约 X m),随目标实时更新;
|
||||
- 未检测到目标时,疑似生境区域以黄色虚线框展示(含距离标注)并预警(提醒方式与动物检测区分)。
|
||||
|
||||
### 5.3 识别结果卡片(点击检测框弹出)
|
||||
|
||||
| 元素 | 说明 |
|
||||
| --- | --- |
|
||||
| 物种名称 | 中文名 + 英文标签(如:野鸡 pheasant) |
|
||||
| 置信度 | 百分比进度条 |
|
||||
| 时间 | 检测时刻 |
|
||||
| 距离 | 约 25m(单目估算,误差 ±30%) |
|
||||
|
||||
仅展示信息,无保存操作。
|
||||
|
||||
疑似生境区域(黄色虚线框)点击后展示:区域类别、置信度、距离(约 X m,按参考植被高度估算,误差较大)。
|
||||
|
||||
### 5.4 设置页
|
||||
|
||||
| 分组 | 设置项 | 默认值 |
|
||||
| --- | --- | --- |
|
||||
| 识别 | 置信度阈值 | 0.40 |
|
||||
| 识别 | 检测频率 | 连续 |
|
||||
| 识别 | 低光增强 | 关(P2) |
|
||||
| 识别 | 距离标注 | 开 |
|
||||
| 识别 | 生境区域阈值 | 0.35 |
|
||||
| 提醒 | 震动提醒 / 提示音 / 防重复时长 | 开 / 开 / 10s |
|
||||
| 提醒 | 生境区域预警 | 开 |
|
||||
| 关于 | 版本信息 / 隐私说明 | — |
|
||||
|
||||
---
|
||||
|
||||
## 6. 权限设计
|
||||
|
||||
| 权限 | 用途 | 时机 | 备注 |
|
||||
| --- | --- | --- | --- |
|
||||
| CAMERA | 实时预览与识别 | 首次启动 | 必须 |
|
||||
|
||||
原则:**最小权限、按需申请**。仅申请相机权限;拒绝后提供说明引导页(可跳转系统设置重新授权)。不申请定位、存储等任何其他权限。
|
||||
|
||||
---
|
||||
|
||||
## 7. 非功能需求
|
||||
|
||||
| 类别 | 要求 |
|
||||
| --- | --- |
|
||||
| 兼容性 | Android 7.0+(minSdk 24),竖屏为主,支持暗色模式 |
|
||||
| 性能 | 见《技术方案》第 7 章(延迟 ≤ 80ms、启动 ≤ 1.5s、内存 ≤ 200MB) |
|
||||
| 离线 | 飞行模式下实时识别全流程完整可用 |
|
||||
| 稳定性 | 崩溃率 ≤ 0.5%,启动成功率 ≥ 99%,异常自动降级(GPU 回退 CPU) |
|
||||
| 耗电 | 连续使用 1 小时 ≤ 15%;省电模式 ≤ 8% |
|
||||
| 隐私 | 无广告 SDK、无埋点、不保存任何数据,识别数据仅存内存 |
|
||||
| 无障碍 | 关键操作支持 TalkBack 描述;检测结果支持语音播报(V2) |
|
||||
|
||||
---
|
||||
|
||||
## 8. 版本规划
|
||||
|
||||
| 版本 | 范围 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| MVP / V1.0 | F01~F05、F07、F08 | 相机 + 实时识别 + 生境预警 + 提醒 + 设置,即完整核心体验 |
|
||||
| V2.0 | +F06 及增强 | 低光增强、更多物种、夜视模式、模型远程更新、语音播报、距离精度增强(地面平面法) |
|
||||
|
||||
---
|
||||
|
||||
## 9. 合规与安全说明
|
||||
|
||||
- App 定位为**野生动物观察、识别工具**,不提供猎捕、诱捕、伤害野生动物的功能或指导;
|
||||
- 首次启动展示合规提示:遵守《中华人民共和国野生动物保护法》,野鸡属"三有"保护动物,猎捕须依法许可;观察时保持距离、不惊扰动物、遵守保护区规定;
|
||||
- 上架需提供隐私政策,声明仅使用相机权限、不采集与存储任何用户数据。
|
||||
@@ -0,0 +1,512 @@
|
||||
# Observer(野视)· 技术实现文档
|
||||
|
||||
| 项目 | 内容 |
|
||||
| --- | --- |
|
||||
| 文档版本 | v1.1 |
|
||||
| 编写日期 | 2026-08-17 |
|
||||
| 状态 | 初稿(v1.1:确认功能范围为"仅实时识别",移除拍照、记录、相册识别、定位相关实现) |
|
||||
| 配套文档 | 《01-技术方案》《02-项目功能文档》 |
|
||||
|
||||
---
|
||||
|
||||
## 1. 项目结构
|
||||
|
||||
```
|
||||
app/
|
||||
├── src/main/
|
||||
│ ├── java/com/example/observer/
|
||||
│ │ ├── MainActivity.kt // 单 Activity 入口
|
||||
│ │ ├── camera/
|
||||
│ │ │ ├── CameraController.kt // CameraX 生命周期绑定与用例组合
|
||||
│ │ │ └── FrameAnalyzer.kt // ImageAnalysis 帧分析器(节流 + 调度)
|
||||
│ │ ├── detection/
|
||||
│ │ │ ├── Detector.kt // 检测器抽象接口
|
||||
│ │ │ ├── TFLiteDetector.kt // TFLite 实现(YOLOv8n)
|
||||
│ │ │ ├── DetectionResult.kt // 检测结果数据类
|
||||
│ │ │ ├── Nms.kt // NMS 后处理
|
||||
│ │ │ └── CoordinateMapper.kt // 模型坐标 → 视图坐标映射
|
||||
│ │ ├── distance/
|
||||
│ │ │ └── DistanceEstimator.kt // 单目距离估计(针孔模型)
|
||||
│ │ ├── overlay/
|
||||
│ │ │ └── DetectionOverlay.kt // Compose 叠加层(Canvas 绘制检测框)
|
||||
│ │ ├── reminder/
|
||||
│ │ │ └── Reminder.kt // 震动 / 提示音提醒(含防重复)
|
||||
│ │ ├── ui/
|
||||
│ │ │ ├── camera/ CameraScreen.kt / CameraViewModel.kt
|
||||
│ │ │ └── settings/ SettingsScreen.kt / SettingsViewModel.kt
|
||||
│ │ └── util/
|
||||
│ │ └── BitmapUtils.kt // 缩放 / 旋转 / 复用
|
||||
│ ├── assets/
|
||||
│ │ ├── model.tflite // YOLOv8n 四类模型
|
||||
│ │ └── labels.txt // 类别标签(按 ID 顺序)
|
||||
│ └── res/
|
||||
└── build.gradle.kts
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 技术栈与版本
|
||||
|
||||
| 组件 | 版本(以最新稳定为准) | 用途 |
|
||||
| --- | --- | --- |
|
||||
| Kotlin | 2.x | 开发语言 |
|
||||
| AGP | 8.x | Android Gradle 插件 |
|
||||
| Jetpack Compose | BOM 2024.x+(Material 3) | UI |
|
||||
| CameraX | 1.4.x | Preview / ImageAnalysis |
|
||||
| TensorFlow Lite | 2.16.x | 端侧推理 |
|
||||
| tensorflow-lite-gpu | 2.16.x | GPU 加速 |
|
||||
| DataStore | 1.1.x | 设置存储 |
|
||||
|
||||
依赖(build.gradle.kts 关键片段):
|
||||
|
||||
```kotlin
|
||||
dependencies {
|
||||
implementation("androidx.camera:camera-core:1.4.1")
|
||||
implementation("androidx.camera:camera-camera2:1.4.1")
|
||||
implementation("androidx.camera:camera-lifecycle:1.4.1")
|
||||
implementation("androidx.camera:camera-view:1.4.1")
|
||||
|
||||
implementation("org.tensorflow:tensorflow-lite:2.16.1")
|
||||
implementation("org.tensorflow:tensorflow-lite-gpu:2.16.1")
|
||||
|
||||
implementation("androidx.datastore:datastore-preferences:1.1.1")
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 模块设计
|
||||
|
||||
### 3.1 camera 模块 — CameraController.kt
|
||||
|
||||
职责:创建并绑定 CameraX 用例(Preview + ImageAnalysis),统一生命周期。
|
||||
|
||||
```kotlin
|
||||
class CameraController(
|
||||
private val lifecycleOwner: LifecycleOwner,
|
||||
private val analysisExecutor: Executor,
|
||||
) {
|
||||
private lateinit var cameraProvider: ProcessCameraProvider
|
||||
private lateinit var imageAnalysis: ImageAnalysis
|
||||
|
||||
fun start(previewView: PreviewView, analyzer: ImageAnalysis.Analyzer) {
|
||||
val future = ProcessCameraProvider.getInstance(previewView.context)
|
||||
future.addListener({
|
||||
cameraProvider = future.get()
|
||||
|
||||
val preview = Preview.Builder().build().also {
|
||||
it.surfaceProvider = previewView.surfaceProvider
|
||||
}
|
||||
|
||||
imageAnalysis = ImageAnalysis.Builder()
|
||||
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
|
||||
.setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_RGBA_8888)
|
||||
.build()
|
||||
imageAnalysis.setAnalyzer(analysisExecutor, analyzer)
|
||||
|
||||
cameraProvider.bindToLifecycle(
|
||||
lifecycleOwner,
|
||||
CameraSelector.DEFAULT_BACK_CAMERA,
|
||||
preview, imageAnalysis,
|
||||
)
|
||||
}, ContextCompat.getMainExecutor(previewView.context))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
要点:
|
||||
|
||||
- `OUTPUT_IMAGE_FORMAT_RGBA_8888`(CameraX 1.3+)直接得到 RGBA 图像,免 YUV 转换;
|
||||
- `STRATEGY_KEEP_ONLY_LATEST`:分析器忙时丢弃旧帧,不积压;
|
||||
- 相机权限检查与请求在进入本模块前完成;
|
||||
- 前后摄切换:重新以对应 `CameraSelector` 执行 `bindToLifecycle`(解绑旧用例)。
|
||||
|
||||
### 3.2 camera 模块 — FrameAnalyzer.kt
|
||||
|
||||
职责:帧节流、旋转获取、调度检测、结果回调。
|
||||
|
||||
```kotlin
|
||||
class FrameAnalyzer(
|
||||
private val detector: Detector,
|
||||
private val onResult: (List<DetectionResult>, Int) -> Unit, // 结果 + 旋转角
|
||||
) : ImageAnalysis.Analyzer {
|
||||
|
||||
private var lastDetectMs = 0L
|
||||
|
||||
override fun analyze(imageProxy: ImageProxy) {
|
||||
val now = SystemClock.elapsedRealtime()
|
||||
val interval = frameIntervalMs() // 依据设置:连续/标准/省电
|
||||
if (now - lastDetectMs < interval) {
|
||||
imageProxy.close(); return
|
||||
}
|
||||
lastDetectMs = now
|
||||
|
||||
val bitmap = imageProxy.toBitmap() // RGBA_8888 直接转 Bitmap
|
||||
val rotation = imageProxy.imageInfo.rotationDegrees
|
||||
try {
|
||||
val results = detector.detect(bitmap)
|
||||
onResult(results, rotation)
|
||||
} finally {
|
||||
imageProxy.close() // 必须关闭,否则阻塞流
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 detection 模块 — Detector 接口
|
||||
|
||||
```kotlin
|
||||
interface Detector {
|
||||
/** 输入 RGBA 位图,输出归一化坐标检测结果(0..1) */
|
||||
fun detect(bitmap: Bitmap): List<DetectionResult>
|
||||
}
|
||||
```
|
||||
|
||||
### 3.4 detection 模块 — TFLiteDetector.kt
|
||||
|
||||
```kotlin
|
||||
class TFLiteDetector(
|
||||
context: Context,
|
||||
private val inputSize: Int = 320,
|
||||
private val confThreshold: Float = 0.40f, // 动物类阈值
|
||||
private val habitatThreshold: Float = 0.35f, // 生境区域阈值
|
||||
private val iouThreshold: Float = 0.45f,
|
||||
gpuEnabled: Boolean = true,
|
||||
) : Detector {
|
||||
|
||||
private val labels: List<String> =
|
||||
context.assets.open("labels.txt").bufferedReader().readLines()
|
||||
|
||||
private val interpreter: Interpreter = Interpreter(
|
||||
loadModelFile(context, "model.tflite"),
|
||||
Interpreter.Options().apply {
|
||||
setNumThreads(4)
|
||||
if (gpuEnabled) {
|
||||
try { addDelegate(GpuDelegate()) } catch (_: Exception) { /* 回退 CPU */ }
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
private val inputBuffer: ByteBuffer = ByteBuffer.allocateDirect(
|
||||
1 * inputSize * inputSize * 3 * 4 // float32
|
||||
).order(ByteOrder.nativeOrder())
|
||||
|
||||
private val outputBuffer: ByteBuffer = ByteBuffer.allocateDirect(
|
||||
OUTPUT_ELEMENTS * 4
|
||||
).order(ByteOrder.nativeOrder())
|
||||
|
||||
override fun detect(bitmap: Bitmap): List<DetectionResult> {
|
||||
preprocess(bitmap, inputBuffer) // 缩放 + RGB 归一化 → 输入缓冲
|
||||
interpreter.run(inputBuffer, outputBuffer) // 单张推理
|
||||
return postprocess(outputBuffer, bitmap.width, bitmap.height)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
输入 / 输出规格(YOLOv8n 四类,320 输入):
|
||||
|
||||
- 输入:`[1, 320, 320, 3]`,RGB,float32 归一化 0~1;
|
||||
- 输出:`[1, 9, 8400]`(8400 = 各尺度 anchor 数,9 = 4 个框坐标 cx/cy/w/h + 5 类得分),展平为 `9 * 8400` 个 float。
|
||||
|
||||
### 3.5 detection 模块 — 后处理(解码 + NMS)
|
||||
|
||||
```kotlin
|
||||
private fun postprocess(raw: FloatArray, imgW: Int, imgH: Int): List<DetectionResult> {
|
||||
val numAnchor = raw.size / 9
|
||||
val boxes = mutableListOf<DetectionResult>()
|
||||
|
||||
for (a in 0 until numAnchor) {
|
||||
val cx = raw[a]; val cy = raw[9 + a]
|
||||
val w = raw[18 + a]; val h = raw[27 + a]
|
||||
var bestCls = 0; var bestScore = 0f
|
||||
for (c in 0 until 5) {
|
||||
val s = raw[36 + c * numAnchor + a] // 类别得分按 anchor 平铺
|
||||
if (s > bestScore) { bestScore = s; bestCls = c }
|
||||
}
|
||||
if (bestScore < confThreshold) continue
|
||||
boxes += DetectionResult(
|
||||
label = labels[bestCls],
|
||||
score = bestScore,
|
||||
// 归一化坐标(裁剪到 [0,1])
|
||||
left = (cx - w / 2).coerceIn(0f, 1f),
|
||||
top = (cy - h / 2).coerceIn(0f, 1f),
|
||||
right = (cx + w / 2).coerceIn(0f, 1f),
|
||||
bottom = (cy + h / 2).coerceIn(0f, 1f),
|
||||
)
|
||||
}
|
||||
return nms(boxes, iouThreshold)
|
||||
}
|
||||
|
||||
fun nms(boxes: List<DetectionResult>, iouThreshold: Float): List<DetectionResult> {
|
||||
val sorted = boxes.sortedByDescending { it.score }
|
||||
val kept = mutableListOf<DetectionResult>()
|
||||
for (b in sorted) {
|
||||
if (kept.none { iou(b, it) > iouThreshold }) kept += b
|
||||
}
|
||||
return kept
|
||||
}
|
||||
```
|
||||
|
||||
注意:以上取数索引为示意,**必须与模型导出时的输出布局(yolov8 tflite 为 9×8400,按列平铺)核对一致**,建议训练导出后用 Python 脚本先对单图做一致性校验再接入 App。
|
||||
|
||||
### 3.6 distance 模块 — DistanceEstimator.kt
|
||||
|
||||
```kotlin
|
||||
class DistanceEstimator(context: Context) {
|
||||
|
||||
private val cameraManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
|
||||
private var focalPxCache = -1f
|
||||
|
||||
// 物种参考体型(米),用于针孔模型估算
|
||||
private val speciesSizeM = mapOf(
|
||||
"pheasant" to 0.45f, // 身高
|
||||
)
|
||||
|
||||
/** 距离 = 焦距px × 参考体型 / 框高px(在分析图像分辨率下计算) */
|
||||
fun estimate(label: String, boxHeightNorm: Float, imageHeightPx: Int): Float? {
|
||||
val realH = speciesSizeM[label] ?: return null
|
||||
val boxH = boxHeightNorm * imageHeightPx
|
||||
if (boxH < 8f) return null // 过小目标不估算
|
||||
val focalPx = focalPx(imageHeightPx)
|
||||
if (focalPx <= 0) return null
|
||||
return round(focalPx * realH / boxH)
|
||||
}
|
||||
|
||||
/** focal_px = focal_mm × (imageHeightPx / sensorHeightMm);视场角作回退 */
|
||||
private fun focalPx(imageHeightPx: Int): Float {
|
||||
if (focalPxCache > 0) return focalPxCache
|
||||
val c = cameraManager.getCameraCharacteristics(cameraManager.cameraIdList.first())
|
||||
val focalMm = c.get(CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS)?.firstOrNull()
|
||||
val sensor = c.get(CameraCharacteristics.SENSOR_INFO_PHYSICAL_SIZE)
|
||||
val fov = c.get(CameraCharacteristics.LENS_INFO_HORIZONTAL_VIEW_ANGLE)
|
||||
focalPxCache = when {
|
||||
focalMm != null && sensor != null -> focalMm * imageHeightPx / sensor.height
|
||||
fov != null -> (imageHeightPx / 2f) / tan(fov / 2f)
|
||||
else -> -1f
|
||||
}
|
||||
return focalPxCache
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- 前后摄切换时需按当前相机重新获取焦距(缓存按相机 ID 区分);
|
||||
- 距离估算仅供参考,实际距离可能因环境因素有所偏差。
|
||||
|
||||
### 3.7 detection 模块 — CoordinateMapper.kt
|
||||
|
||||
模型输出为归一化坐标(相对分析图像,竖屏方向),需转换到预览视图坐标:
|
||||
|
||||
```kotlin
|
||||
class CoordinateMapper(private val view: PreviewView) {
|
||||
|
||||
/** 归一化坐标 → 预览视图像素坐标(考虑传感器旋转与 FIT_CENTER 裁剪) */
|
||||
fun mapToView(norm: DetectionResult, rotation: Int): RectF {
|
||||
// 1) 旋转校正:把"图像方向"归一化坐标转到"竖屏视图方向"
|
||||
val (x0, y0, x1, y1) = rotate(norm, rotation)
|
||||
// 2) 处理 FIT_CENTER 的裁剪偏移与缩放
|
||||
val viewW = view.width; val viewH = view.height
|
||||
val scale = min(viewW / bitmapW, viewH / bitmapH) // bitmap 尺寸
|
||||
val offsetX = (viewW - bitmapW * scale) / 2f
|
||||
val offsetY = (viewH - bitmapH * scale) / 2f
|
||||
return RectF(
|
||||
x0 * bitmapW * scale + offsetX,
|
||||
y0 * bitmapH * scale + offsetY,
|
||||
x1 * bitmapW * scale + offsetX,
|
||||
y1 * bitmapH * scale + offsetY,
|
||||
)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.8 overlay 模块 — DetectionOverlay.kt
|
||||
|
||||
Compose Canvas 叠加层:
|
||||
|
||||
```kotlin
|
||||
@Composable
|
||||
fun DetectionOverlay(results: List<DetectionResult>, rotation: Int, modifier: Modifier) {
|
||||
val mapper = remember { CoordinateMapper(view) }
|
||||
Canvas(modifier = modifier) {
|
||||
results.forEach { r ->
|
||||
val rect = mapper.mapToView(r, rotation)
|
||||
drawRect(
|
||||
color = colorOf(r.label),
|
||||
topLeft = Offset(rect.left, rect.top),
|
||||
size = Size(rect.width(), rect.height()),
|
||||
style = Stroke(6.dp.toPx()),
|
||||
)
|
||||
// 距离由 ViewModel 计算后写入 DetectionResult.distance(Float?)
|
||||
val dist = r.distance?.let { " · 约${it}m" } ?: ""
|
||||
drawText("${r.label} ${(r.score * 100).toInt()}%$dist")
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
防闪烁:ViewModel 中对结果做"目标停留 ≥ 0.5s 才显示、消失 2s 后移除"的平滑处理。
|
||||
|
||||
### 3.9 reminder 模块 — Reminder.kt
|
||||
|
||||
```kotlin
|
||||
class Reminder(private val context: Context) {
|
||||
private val vibrator = context.getSystemService(Vibrator::class.java)
|
||||
private var lastAlertAt = 0L
|
||||
private var lastAlertLabel: String? = null
|
||||
|
||||
/** 同类目标 10s 内只提醒一次 */
|
||||
fun onDetected(label: String) {
|
||||
val now = SystemClock.elapsedRealtime()
|
||||
if (label == lastAlertLabel && now - lastAlertAt < 10_000) return
|
||||
lastAlertAt = now
|
||||
lastAlertLabel = label
|
||||
vibrator?.vibrate(VibrationEffect.createOneShot(200, VibrationEffect.DEFAULT_AMPLITUDE))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.10 设置存储 — DataStore
|
||||
|
||||
使用 DataStore Preferences 保存:`conf_threshold`(默认 0.40)、`detect_mode`(连续/标准/省电)、`vibrate_enabled`、`sound_enabled`、`low_light_enhance`(P2)、`show_distance`(默认开)。`SettingsViewModel` 以 Flow 暴露,`FrameAnalyzer` 与 `TFLiteDetector` 读取最新值。
|
||||
|
||||
---
|
||||
|
||||
## 4. 关键流程实现
|
||||
|
||||
### 4.1 启动流程
|
||||
|
||||
```
|
||||
MainActivity.onCreate
|
||||
→ 检查/请求 CAMERA 权限
|
||||
→ 初始化 TFLiteDetector(后台线程,含预热推理)
|
||||
→ CameraController.start(previewView, analyzer)
|
||||
→ ViewModel 订阅检测结果 → 叠加层渲染 + 提醒触发
|
||||
```
|
||||
|
||||
预热:加载模型后执行一次空推理(全零输入),避免首帧卡顿。
|
||||
|
||||
### 4.2 帧节流与并发控制
|
||||
|
||||
- 检测间隔:连续 80~100ms / 标准 300ms / 省电 1000ms(由设置决定);
|
||||
- 推理在单线程 Executor(HandlerThread)串行执行,天然互斥;
|
||||
- 分析线程只做"取帧 → 判断节流 → 提交任务",不做推理,保证预览流畅。
|
||||
|
||||
### 4.3 提醒、距离标注与防闪烁
|
||||
|
||||
- 检测到目标(置信度 ≥ 阈值,生境区域用独立阈值)→ `Reminder.onDetected(label)`,10s 防重复,动物与生境预警提醒方式区分;
|
||||
- 叠加层展示逻辑:目标连续出现 ≥ 0.5s 才绘制;消失后 2s 内不清除,避免闪烁;
|
||||
- 低置信度结果(阈值以下)不展示、不提醒;
|
||||
- 距离标注:结果到达后调用 `DistanceEstimator` 估算"约 X m",随检测框渲染(FrameAnalyzer 回调携带分析图像高度);生境区域同样估算(按参考植被高度,误差较大);框高过小(< 8px)或无法获取焦距显示"--"。
|
||||
|
||||
---
|
||||
|
||||
## 5. 模型集成细节
|
||||
|
||||
| 项 | 说明 |
|
||||
| --- | --- |
|
||||
| 模型文件 | `app/src/main/assets/model.tflite`,随 APK 打包 |
|
||||
| 标签文件 | `assets/labels.txt`,每行一个类别,顺序与训练一致(pheasant) |
|
||||
| 量化 | 优先 fp16(GPU);int8 用于 CPU/低端机回退 |
|
||||
| 加载 | `Interpreter(loadAssetFile(...))`,进程内单例 |
|
||||
| 预热 | 初始化后执行一次 dummy run |
|
||||
| 校验 | 发布前用 Python 脚本(tflite-runtime)对测试集抽样验证模型输出布局与 App 解析一致 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 性能优化实践
|
||||
|
||||
| 手段 | 说明 |
|
||||
| --- | --- |
|
||||
| GPU Delegate | 优先启用;`addDelegate` 抛异常或首帧异常时回退 CPU,并上报降级日志 |
|
||||
| 输入缓冲复用 | ByteBuffer 复用,避免每次检测重新分配 |
|
||||
| 帧节流 | 按模式降频,KEEP_ONLY_LATEST 不积压 |
|
||||
| 位图复用 | ImageProxy → Bitmap 走共享缓冲;叠加层仅绘制检测框,不复制整帧 |
|
||||
| 线程模型 | 检测线程单线程串行;UI 线程零推理 |
|
||||
| 低分辨率检测 | 320 输入检测;远距离模式可切 416(V2) |
|
||||
| 距离标注 | 纯算术运算开销可忽略;焦距 px 按相机缓存复用,避免重复查询 CameraCharacteristics |
|
||||
| 生命周期 | 后台自动 `unbind` 相机释放资源,避免耗电 |
|
||||
| 省电模式 | 降到 1 帧/秒 + 低分辨率 + 关闭提醒以外的动画 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 异常与降级策略
|
||||
|
||||
| 异常场景 | 处理 |
|
||||
| --- | --- |
|
||||
| GPU 不可用 / 模型加载失败 | 回退 CPU 线程数调整;失败则提示"识别不可用"但不影响相机预览 |
|
||||
| 相机初始化失败(个别机型) | 重试一次 → 失败提示并引导检查权限/重启 |
|
||||
| 权限被拒 | 引导页说明用途,提供跳转系统设置 |
|
||||
| 低内存 | 主动释放缓存位图,降低检测分辨率 |
|
||||
| 推理超时(> 500ms) | 丢弃该帧结果,恢复下一帧,保证预览流畅 |
|
||||
| 无法读取焦距 / 视场角(个别机型) | 距离显示"--",识别不受影响 |
|
||||
|
||||
---
|
||||
|
||||
## 8. 测试方案
|
||||
|
||||
### 8.1 单元测试(JUnit)
|
||||
|
||||
- `NmsTest`:NMS 正确性(重叠/多类别/边界框);
|
||||
- `CoordinateMapperTest`:四向旋转映射、FIT_CENTER 裁剪偏移;
|
||||
- `ThresholdTest`:置信度阈值过滤逻辑;
|
||||
- `ReminderTest`:10s 防重复提醒逻辑、动物与生境提醒方式区分;
|
||||
- `DistanceEstimatorTest`:焦距换算、距离公式、过小目标不估算、生境区域按植被高度估算。
|
||||
|
||||
### 8.2 模型评估(Python 脚本,独立于 App)
|
||||
|
||||
- 对测试集计算 mAP@0.5、mAP@0.5:0.95、各类别 AP、负样本误检率;
|
||||
- 输出混淆矩阵,分析检测精度。
|
||||
|
||||
### 8.3 仪器测试(androidTest)
|
||||
|
||||
- 模拟帧注入:将测试图片经 `ImageProxy` 注入分析器,断言结果(不依赖真机取景);
|
||||
- 权限拒绝 / 恢复场景;
|
||||
- 冷启动到预览的时间基准测试。
|
||||
|
||||
### 8.4 真机测试矩阵
|
||||
|
||||
| 机型档位 | 代表机型 | 验证项 |
|
||||
| --- | --- | --- |
|
||||
| 旗舰 | 骁龙 8 系 / 麒麟 9 系 | 全功能、GPU 路径、长时间发热 |
|
||||
| 中端 | 骁龙 7 系 / 天玑 8 系 | 延迟 ≤ 80ms、耗电 ≤ 15%/h |
|
||||
| 低端 | 骁龙 4 系 / 天玑 6 系 | CPU 回退路径、省电模式可用性 |
|
||||
|
||||
---
|
||||
|
||||
## 9. 构建与发布
|
||||
|
||||
```kotlin
|
||||
android {
|
||||
compileSdk = 35
|
||||
defaultConfig {
|
||||
applicationId = "com.example.observer"
|
||||
minSdk = 24
|
||||
targetSdk = 35
|
||||
ndk { abiFilters += listOf("arm64-v8a") } // 主发 arm64;如需兼容 32 位另发包
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = true
|
||||
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- R8 规则:保留 TFLite 相关类(`-keep class org.tensorflow.** { *; }`),assets 模型不混淆;
|
||||
- 签名:正式签名 + Gradle 管理(或环境变量注入),不上传 keystore 到仓库;
|
||||
- 上架检查:隐私政策(仅相机权限、不采集与存储数据声明)、权限用途说明。
|
||||
|
||||
---
|
||||
|
||||
## 10. 后续演进(V2 方向)
|
||||
|
||||
- 更多物种类别(哺乳类、鸟类细分),支持模型远程更新;
|
||||
- 小目标优化:SAHI 切图推理 / 专用小目标模型 / 数字变焦辅助;
|
||||
- 夜视 / 红外增强模式;
|
||||
- 检测结果语音播报,提升无障碍体验;
|
||||
- 距离精度增强:基于设备俯仰角与相机高度的地面平面法、镜头畸变校正、水下折射修正;
|
||||
- 生境类别细分与精度提升(湿地、林缘等),生境预警策略优化;
|
||||
- 若未来需要留存能力(拍照、记录),可基于现有检测链路平滑扩展。
|
||||
Reference in New Issue
Block a user