This commit is contained in:
2026-09-03 17:50:23 +08:00
parent 7bfafc7be3
commit 77d3aad6fc
45 changed files with 1398 additions and 1088 deletions
@@ -34,6 +34,9 @@ class DetectionResult {
double get centerX => (left + right) / 2;
double get centerY => (top + bottom) / 2;
/// 疑似(生境预警)类别:类别索引 >0 或训练标签为 suspect
bool get isSuspect => classId > 0 || label == 'suspect';
DetectionResult copyWith({
double? score,
double? left,
+20 -6
View File
@@ -229,15 +229,12 @@ 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)';
// modelName 仅标注来源数据集名档位码 s/n 内部记账,不展示给用户
final d = await TfliteDetector.fromBuffer(
e[0] as Uint8List,
(e[1] as List).cast<String>(),
modelId: (e[2] as num).toInt(),
modelName: displayName,
modelName: name,
);
if (d == null) {
failures.add(name.isEmpty ? 'unknown' : name);
@@ -475,8 +472,25 @@ Future<void> _workerMain(SendPort mainPort) async {
/// 实测多个模型会对同一目标检出不同类别(误检/歧义),若异类别互不压制
/// 会出现重叠框;2026-09-01 用户实测定案:所有模型的框统一按 IoU 去重,
/// 重叠时取高分(远处真实的多目标互不重叠,正常保留)。
///
/// 2026-09-03 补跨模型同目标窗口:小框被大框覆盖 > [iouThreshold] 直接去重;
/// 覆盖不足、但两框来自**不同模型**且 [sameTarget](小框中心在大框内、
/// 覆盖 ≥ 30%)也去重——不同输入分辨率模型对同一目标的框几何有系统性偏移,
/// 纯阈值会漏判。同模型框对(模型内已做过类内 NMS)不套用该窗口。
List<DetectionResult> mergeAcrossModels(
List<DetectionResult> all, double iouThreshold) {
if (all.length <= 1) return all;
return nms(all, iouThreshold);
final sorted = [...all]..sort((a, b) => b.score.compareTo(a.score));
final kept = <DetectionResult>[];
for (final b in sorted) {
final dup = kept.any((k) {
if (boxOverlap(k, b) > iouThreshold) return true;
if (k.modelId == b.modelId || k.modelId < 0 || b.modelId < 0) {
return false; // 同模型/无来源:类内 NMS 已处理,不补窗
}
return sameTarget(k, b);
});
if (!dup) kept.add(b);
}
return kept;
}
+19
View File
@@ -35,3 +35,22 @@ List<DetectionResult> nms(List<DetectionResult> boxes, double iouThreshold) {
}
return kept;
}
/// 同目标判定(2026-09-03 用户实测修订:同一标注位置/重叠位置,同/异模型
/// 检出的物种只保留高置信度框)。不同模型对同一目标的框紧致度/偏移系统性
/// 不同,纯 boxOverlap 阈值(0.45)会漏判「几何明显指向同一位置」的偏移框;
/// 补判条件:小框被大框覆盖 ≥ [sameTargetMinCover] 且小框中心落在大框内
/// (相邻独立目标的中心不会落在对方框内,不会被误并)。
const double sameTargetMinCover = 0.3;
bool sameTarget(DetectionResult a, DetectionResult b) {
final cover = boxOverlap(a, b);
if (cover < sameTargetMinCover) return false;
final aBigger = a.width * a.height >= b.width * b.height;
final big = aBigger ? a : b;
final small = aBigger ? b : a;
return small.centerX >= big.left &&
small.centerX <= big.right &&
small.centerY >= big.top &&
small.centerY <= big.bottom;
}
@@ -11,7 +11,7 @@ import 'nms.dart';
/// 模型输出布局(ultralytics litert 导出):[1, 4 + nc, anchors]
/// cx/cy/w/h 已归一化,类别得分已过 sigmoid;按 out[dim][anchor] 索引。
/// 输入为 NCHW [1, 3, H, W]litert 导出保留 torch 布局),H/W 随模型档位:
/// s 高识别 @1280、n 高性能 @704,输入尺寸取自模型自身。
/// s 高精度 @1280、n 高性能 @704,输入尺寸取自模型自身。
class TfliteDetector {
// 输入尺寸取自模型本身(ultralytics litert 导出 NCHW [1,3,H,W],各数据集
// 训练 imgsz 可不同),默认 1280 兜底