1
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 兜底
|
||||
|
||||
Reference in New Issue
Block a user