1
This commit is contained in:
@@ -8,7 +8,7 @@ import 'motion_aggregator.dart';
|
||||
/// 分块聚合为新颖区域(novelty)。
|
||||
///
|
||||
/// 固定机位下,常驻物体(键盘/石头/文字)永远属于背景、不产生新颖区域;
|
||||
/// 走进画面的目标(环颈雉鸡移动/新出现)才会触发。比相邻帧差分更强的证据:
|
||||
/// 走进画面的目标(动物移动/新出现)才会触发。比相邻帧差分更强的证据:
|
||||
/// 风吹草动是持续的背景更新,不会长期标记为新颖。
|
||||
class BackgroundModel {
|
||||
final int maxWidth;
|
||||
|
||||
@@ -61,7 +61,14 @@ class DetectorWorker {
|
||||
try {
|
||||
if (models == null || models.isEmpty) return null;
|
||||
final payload = <List<Object?>>[
|
||||
for (final m in models) [m.bytes, m.labels, m.datasetId, m.datasetName],
|
||||
for (final m in models)
|
||||
[
|
||||
m.bytes,
|
||||
m.labels,
|
||||
m.datasetId,
|
||||
m.datasetName,
|
||||
m.variant,
|
||||
],
|
||||
];
|
||||
|
||||
final responses = ReceivePort();
|
||||
@@ -222,11 +229,15 @@ Future<void> _workerMain(SendPort mainPort) async {
|
||||
for (final entry in list[1] as List) {
|
||||
final e = entry as List;
|
||||
final name = e.length > 3 ? e[3] as String : '';
|
||||
// 模型名带档位标识(数据集名+档位,框来源可辨 s/n)
|
||||
final variant = e.length > 4 ? e[4] as String : '';
|
||||
final displayName =
|
||||
variant.isEmpty ? name : '$name($variant)';
|
||||
final d = await TfliteDetector.fromBuffer(
|
||||
e[0] as Uint8List,
|
||||
(e[1] as List).cast<String>(),
|
||||
modelId: (e[2] as num).toInt(),
|
||||
modelName: name,
|
||||
modelName: displayName,
|
||||
);
|
||||
if (d == null) {
|
||||
failures.add(name.isEmpty ? 'unknown' : name);
|
||||
@@ -369,7 +380,7 @@ Future<void> _workerMain(SendPort mainPort) async {
|
||||
results.addAll(dets);
|
||||
}
|
||||
results = mergeAcrossModels(results, TfliteDetector.iouThreshold);
|
||||
// 低分环颈雉鸡框过视觉先验(颜色/位置),减少户外误报
|
||||
// 低分目标框过视觉先验(颜色/位置),减少户外误报
|
||||
results = VisualPrior.filter(
|
||||
results,
|
||||
planes: planes,
|
||||
|
||||
@@ -7,15 +7,16 @@ import 'package:tflite_flutter/tflite_flutter.dart';
|
||||
import 'detection_result.dart';
|
||||
import 'nms.dart';
|
||||
|
||||
/// YOLOv8s 端侧推理实现(对应 Kotlin TFLiteDetector)。
|
||||
/// YOLO 端侧推理实现(对应 Kotlin TFLiteDetector)。
|
||||
/// 模型输出布局(ultralytics litert 导出):[1, 4 + nc, anchors],
|
||||
/// cx/cy/w/h 已归一化,类别得分已过 sigmoid;按 out[dim][anchor] 索引。
|
||||
/// 输入为 NCHW [1, 3, 1280, 1280](litert 导出保留 torch 布局)。
|
||||
/// 输入为 NCHW [1, 3, H, W](litert 导出保留 torch 布局),H/W 随模型档位:
|
||||
/// s 高识别 @1280、n 高性能 @704,输入尺寸取自模型自身。
|
||||
class TfliteDetector {
|
||||
// 输入尺寸取自模型本身(ultralytics litert 导出 NCHW [1,3,H,W],各数据集
|
||||
// 训练 imgsz 可不同),默认 1280 兜底
|
||||
static const int defaultInputSize = 1280;
|
||||
// 环颈雉鸡数据置信度普遍偏低(0.1~0.2 量级),保留低分池供运动检测提升;
|
||||
// 目标数据置信度普遍偏低(0.1~0.2 量级),保留低分池供运动检测提升;
|
||||
// 可运行时调整(设置页滑块),默认 0.10
|
||||
double minScore = 0.10;
|
||||
static const double iouThreshold = 0.45;
|
||||
|
||||
@@ -3,14 +3,15 @@ import 'dart:typed_data';
|
||||
|
||||
import 'detection_result.dart';
|
||||
|
||||
/// 运行时视觉先验:对低置信度环颈雉鸡框做多线索过滤,降低户外误报。
|
||||
/// 运行时视觉先验:对低置信度目标物种框(class 0)做多线索过滤,降低户外误报。
|
||||
/// 识别哪些类为目标物种由模型训练决定,本先验不依赖任何具体物种名。
|
||||
///
|
||||
/// 仅对 score < [maxScore](0.35)的 pheasant 框生效;高分框与
|
||||
/// 仅对 score < [maxScore](0.35)的目标框生效;高分框与
|
||||
/// suspect(生境预警)不参与过滤,避免误杀。
|
||||
///
|
||||
/// 线索:
|
||||
/// - 颜色:绿色主导(草/叶)、蓝色主导(天空/水)、平坦低饱和(键盘/石头/文字)
|
||||
/// - 位置:中心在画面上部 15%(天空区)——环颈雉鸡是地栖动物,不会出现在天空
|
||||
/// - 位置:中心在画面上部 15%(天空区)——目标物种为地面活动,不会出现在天空
|
||||
///
|
||||
/// 采样在原始 planes 上进行(后台 isolate 内,不依赖 UI 线程)。
|
||||
class VisualPrior {
|
||||
@@ -39,9 +40,9 @@ class VisualPrior {
|
||||
if (results.isEmpty || width <= 0 || height <= 0) return results;
|
||||
final kept = <DetectionResult>[];
|
||||
for (final r in results) {
|
||||
final lowConfPheasant =
|
||||
(r.classId == 0 || r.label == 'pheasant') && r.score < maxScore;
|
||||
if (lowConfPheasant &&
|
||||
// class 0 即目标物种(各数据集标签统一:0 目标 / 其余 suspect)
|
||||
final lowConfTarget = r.classId == 0 && r.score < maxScore;
|
||||
if (lowConfTarget &&
|
||||
_reject(r, planes, strides, width, height, isBgra, rgbaOrder)) {
|
||||
continue;
|
||||
}
|
||||
@@ -86,7 +87,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,
|
||||
|
||||
Reference in New Issue
Block a user