1
This commit is contained in:
@@ -80,7 +80,7 @@ class _CameraScreenState extends State<CameraScreen> {
|
||||
const SizedBox(height: 8),
|
||||
const Text(
|
||||
'阈值越低识别越灵敏(低分框越多,误报也可能增加);'
|
||||
'野鸡模型置信度普遍在 10%~20%,场景识别不到时可适当调低。',
|
||||
'环颈雉鸡模型置信度普遍在 10%~20%,场景识别不到时可适当调低。',
|
||||
style: TextStyle(color: Colors.white54, fontSize: 12),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -39,7 +39,7 @@ class CameraUiState {
|
||||
|
||||
/// 检测结果置信度分级与轨迹确认。
|
||||
///
|
||||
/// - [highConf](0.35):高于此分直接确认显示;真实野鸡多为 0.1~0.2,
|
||||
/// - [highConf](0.35):高于此分直接确认显示;真实环颈雉鸡多为 0.1~0.2,
|
||||
/// 高于 0.35 视为强证据。
|
||||
/// - 低于 0.35 的框:需要多帧稳定([confirmFrames] 帧)或 活动证据
|
||||
/// (运动区域/背景新出现区域重叠)才确认显示。
|
||||
@@ -105,7 +105,7 @@ class CameraViewModel extends ChangeNotifier {
|
||||
visible.add(r.copyWith(confirmed: t.confirmed));
|
||||
}
|
||||
|
||||
// 提醒:仅新确认的野鸡轨迹(确认瞬间触发一次,10s 同类冷却在 Reminder 内)
|
||||
// 提醒:仅新确认的环颈雉鸡轨迹(确认瞬间触发一次,10s 同类冷却在 Reminder 内)
|
||||
for (final t in _tracks.values) {
|
||||
if (t.label != 'pheasant' || !t.confirmed || t.reminded) continue;
|
||||
final age = now - t.firstSeenMs;
|
||||
@@ -152,7 +152,7 @@ class CameraViewModel extends ChangeNotifier {
|
||||
for (final t in _tracks.values) {
|
||||
if (matched.contains(t.id)) continue;
|
||||
final d = _centerDist(t.result, r);
|
||||
// 同标签宽松匹配;跨标签(野鸡↔疑似 抖动)收紧到 60%
|
||||
// 同标签宽松匹配;跨标签(环颈雉鸡↔疑似 抖动)收紧到 60%
|
||||
final limit = t.label == r.label ? bestD : associateRadius * 0.6;
|
||||
if (d < limit) {
|
||||
bestD = d;
|
||||
@@ -181,7 +181,7 @@ class CameraViewModel extends ChangeNotifier {
|
||||
|
||||
/// 显示判定(按类别策略):
|
||||
/// - 疑似(生境预警):设计意图是常驻静态预警,始终显示(渲染侧弱化)
|
||||
/// - 野鸡:确认轨迹直接显示;未确认的只有在高分或活动证据时才显示
|
||||
/// - 环颈雉鸡:确认轨迹直接显示;未确认的只有在高分或活动证据时才显示
|
||||
bool _shouldDisplay(_Track t, List<MotionRegion> motionRegions,
|
||||
List<MotionRegion> noveltyRegions) {
|
||||
if (t.label == 'suspect') return true;
|
||||
@@ -196,7 +196,7 @@ class CameraViewModel extends ChangeNotifier {
|
||||
motionRegions.any((m) => MotionAggregator.centerInRegion(r, m)) ||
|
||||
noveltyRegions.any((m) => MotionAggregator.centerInRegion(r, m));
|
||||
|
||||
/// 物理合理性过滤:宽高比与相对尺寸(野鸡 20-100px@720 量级,参照标注脚本)
|
||||
/// 物理合理性过滤:宽高比与相对尺寸(环颈雉鸡 20-100px@720 量级,参照标注脚本)
|
||||
bool _plausible(DetectionResult r) {
|
||||
final h = r.height;
|
||||
final w = r.width;
|
||||
|
||||
@@ -6,8 +6,8 @@ import '../detection/coordinate_mapper.dart';
|
||||
import '../detection/detection_result.dart';
|
||||
|
||||
/// 检测框绘制分级:
|
||||
/// - 野鸡 confirmed:红色实线 3px(强证据)
|
||||
/// - 野鸡 candidate:红色虚线 2px 半透明(待确认,弱提示)
|
||||
/// - 环颈雉鸡 confirmed:红色实线 3px(强证据)
|
||||
/// - 环颈雉鸡 candidate:红色虚线 2px 半透明(待确认,弱提示)
|
||||
/// - 疑似(生境预警):黄色虚线 2px 半透明(常驻静态预警,弱化渲染)
|
||||
/// 标签附带距离估计(针孔模型 焦距px×参考体型/框高px)。
|
||||
class DetectionOverlay extends StatelessWidget {
|
||||
@@ -48,9 +48,9 @@ class _OverlayPainter extends CustomPainter {
|
||||
'pheasant': Color(0xFFE53935),
|
||||
'suspect': Color(0xFFFDD835),
|
||||
};
|
||||
static const _labels = {'pheasant': '野鸡', 'suspect': '疑似'};
|
||||
static const _labels = {'pheasant': '环颈雉鸡', 'suspect': '疑似'};
|
||||
|
||||
/// 参考体型(米):野鸡身高 / 植被高度(参照旧 DistanceEstimator)
|
||||
/// 参考体型(米):环颈雉鸡身高 / 植被高度(参照旧 DistanceEstimator)
|
||||
static const _refSizeM = {'pheasant': 0.45, 'suspect': 0.50};
|
||||
|
||||
/// iPhone 13 主摄在 1280 高预览下的估算焦距 px(5.1mm / 5.30mm 传感器),
|
||||
|
||||
@@ -8,7 +8,7 @@ import 'motion_aggregator.dart';
|
||||
/// 分块聚合为新颖区域(novelty)。
|
||||
///
|
||||
/// 固定机位下,常驻物体(键盘/石头/文字)永远属于背景、不产生新颖区域;
|
||||
/// 走进画面的目标(野鸡移动/新出现)才会触发。比相邻帧差分更强的证据:
|
||||
/// 走进画面的目标(环颈雉鸡移动/新出现)才会触发。比相邻帧差分更强的证据:
|
||||
/// 风吹草动是持续的背景更新,不会长期标记为新颖。
|
||||
class BackgroundModel {
|
||||
final int maxWidth;
|
||||
|
||||
@@ -389,7 +389,7 @@ Future<void> _workerMain(SendPort mainPort) async {
|
||||
results.addAll(dets);
|
||||
}
|
||||
results = mergeAcrossModels(results, TfliteDetector.iouThreshold);
|
||||
// 低分野鸡框过视觉先验(颜色/位置),减少户外误报
|
||||
// 低分环颈雉鸡框过视觉先验(颜色/位置),减少户外误报
|
||||
results = VisualPrior.filter(
|
||||
results,
|
||||
planes: planes,
|
||||
@@ -480,7 +480,7 @@ Future<void> _workerMain(SendPort mainPort) async {
|
||||
}
|
||||
|
||||
/// 多模型结果合并:按类别分组,组内 NMS(不同模型检出同一目标时取高分)。
|
||||
/// 各模型类别体系独立(如野鸡/疑似 vs 野兔/疑似),不同类别互不压制。
|
||||
/// 各模型类别体系独立(如环颈雉鸡/疑似 vs 野兔/疑似),不同类别互不压制。
|
||||
List<DetectionResult> mergeAcrossModels(
|
||||
List<DetectionResult> all, double iouThreshold) {
|
||||
if (all.length <= 1) return all;
|
||||
|
||||
@@ -14,7 +14,7 @@ class TfliteDetector {
|
||||
// 输入尺寸取自模型本身(ultralytics litert 导出 NCHW [1,3,H,W],各数据集
|
||||
// 训练 imgsz 可不同),默认 704 兜底
|
||||
static const int defaultInputSize = 704;
|
||||
// 野鸡数据置信度普遍偏低(0.1~0.2 量级),保留低分池供运动检测提升;
|
||||
// 环颈雉鸡数据置信度普遍偏低(0.1~0.2 量级),保留低分池供运动检测提升;
|
||||
// 可运行时调整(设置页滑块),默认 0.10
|
||||
double minScore = 0.10;
|
||||
static const double iouThreshold = 0.45;
|
||||
|
||||
@@ -3,14 +3,14 @@ import 'dart:typed_data';
|
||||
|
||||
import 'detection_result.dart';
|
||||
|
||||
/// 运行时视觉先验:对低置信度野鸡框做多线索过滤,降低户外误报。
|
||||
/// 运行时视觉先验:对低置信度环颈雉鸡框做多线索过滤,降低户外误报。
|
||||
///
|
||||
/// 仅对 score < [maxScore](0.35)的 pheasant 框生效;高分框与
|
||||
/// suspect(生境预警)不参与过滤,避免误杀。
|
||||
///
|
||||
/// 线索:
|
||||
/// - 颜色:绿色主导(草/叶)、蓝色主导(天空/水)、平坦低饱和(键盘/石头/文字)
|
||||
/// - 位置:中心在画面上部 15%(天空区)——野鸡是地栖动物,不会出现在天空
|
||||
/// - 位置:中心在画面上部 15%(天空区)——环颈雉鸡是地栖动物,不会出现在天空
|
||||
///
|
||||
/// 采样在原始 planes 上进行(后台 isolate 内,不依赖 UI 线程)。
|
||||
class VisualPrior {
|
||||
@@ -85,7 +85,7 @@ class VisualPrior {
|
||||
/// 读取单像素 RGB(0~255)。
|
||||
/// 8888 单平面按实际字节序取通道:BGRA=[b,g,r,a](iOS 插件)、
|
||||
/// RGBA=[r,g,b,a](Android 自写原生通道)——字节序写死会让 Android
|
||||
/// 低分框采样到 R/B 互换的颜色(橙色野鸡身被误判成"蓝色")整批误杀;
|
||||
/// 低分框采样到 R/B 互换的颜色(橙色环颈雉鸡身被误判成"蓝色")整批误杀;
|
||||
/// YUV:y 平面 + 4:2:0 半分辨率 U/V(NV12 交错或 I420 分离)。
|
||||
static (double, double, double) _pixel(List<Uint8List> planes,
|
||||
List<int> strides, int x, int y, int width, int height, bool isBgra,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: observer
|
||||
description: "视野 - 动物实时识别 (野鸡/生境), YOLOv8 + 充值付费"
|
||||
description: "视野 - 动物实时识别 (环颈雉鸡/生境), YOLOv8 + 充值付费"
|
||||
publish_to: 'none'
|
||||
|
||||
version: 1.0.4+5
|
||||
|
||||
@@ -16,12 +16,12 @@ Map<String, dynamic> _catalog(List<Map<String, dynamic>> models) =>
|
||||
|
||||
Map<String, dynamic> _item({String version = 'v1.0.0', String sha = ''}) => {
|
||||
'datasetId': 7,
|
||||
'datasetName': '野鸡数据集',
|
||||
'datasetName': '环颈雉鸡数据集',
|
||||
'version': version,
|
||||
'labels': ['pheasant', 'suspect'],
|
||||
'sizeBytes': _modelBytes.length,
|
||||
'sha256': sha.isEmpty ? _shaHex(_modelBytes) : sha,
|
||||
'downloadUrl': '/download/models/野鸡数据集/latest.tflite',
|
||||
'downloadUrl': '/download/models/环颈雉鸡数据集/latest.tflite',
|
||||
};
|
||||
|
||||
void main() {
|
||||
@@ -60,7 +60,7 @@ void main() {
|
||||
expect(m.ready, isTrue);
|
||||
expect(m.error, isNull);
|
||||
expect(m.models.length, 1);
|
||||
expect(m.models.first.datasetName, '野鸡数据集');
|
||||
expect(m.models.first.datasetName, '环颈雉鸡数据集');
|
||||
expect(m.models.first.bytes, _modelBytes);
|
||||
expect(downloadHits, 1);
|
||||
|
||||
@@ -112,7 +112,7 @@ void main() {
|
||||
|
||||
expect(m2.models.length, 0, reason: '校验失败的模型不应加载');
|
||||
expect(m2.error, isNotNull);
|
||||
expect(m2.error, contains('野鸡数据集'));
|
||||
expect(m2.error, contains('环颈雉鸡数据集'));
|
||||
});
|
||||
|
||||
test('目录下线:清理本地并清空模型', () async {
|
||||
|
||||
@@ -17,9 +17,9 @@ DetectionResult box(String label, double score, double x, double y,
|
||||
|
||||
void main() {
|
||||
test('不同模型同标签重复框:NMS 去重取高分', () {
|
||||
// 野鸡模型与野兔模型都检出了同一只"野鸡"(不同模型对同一目标的重复框)
|
||||
// 环颈雉鸡模型与野兔模型都检出了同一只"环颈雉鸡"(不同模型对同一目标的重复框)
|
||||
final all = [
|
||||
box('pheasant', 0.18, 0.3, 0.3, modelId: 1, modelName: '野鸡模型'),
|
||||
box('pheasant', 0.18, 0.3, 0.3, modelId: 1, modelName: '环颈雉鸡模型'),
|
||||
box('pheasant', 0.55, 0.31, 0.3, modelId: 2, modelName: '野兔模型'),
|
||||
];
|
||||
final merged = mergeAcrossModels(all, 0.45);
|
||||
@@ -39,8 +39,8 @@ void main() {
|
||||
|
||||
test('同模型内部与跨模型合并一致:远处不重叠保留', () {
|
||||
final all = [
|
||||
box('pheasant', 0.2, 0.1, 0.1, modelId: 1, modelName: '野鸡模型'),
|
||||
box('pheasant', 0.3, 0.8, 0.8, modelId: 1, modelName: '野鸡模型'),
|
||||
box('pheasant', 0.2, 0.1, 0.1, modelId: 1, modelName: '环颈雉鸡模型'),
|
||||
box('pheasant', 0.3, 0.8, 0.8, modelId: 1, modelName: '环颈雉鸡模型'),
|
||||
];
|
||||
final merged = mergeAcrossModels(all, 0.45);
|
||||
expect(merged.length, 2);
|
||||
|
||||
Reference in New Issue
Block a user