Files
2026-09-03 17:50:23 +08:00

57 lines
2.4 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'detection_result.dart';
double iou(DetectionResult a, DetectionResult b) {
final x0 = a.left > b.left ? a.left : b.left;
final y0 = a.top > b.top ? a.top : b.top;
final x1 = a.right < b.right ? a.right : b.right;
final y1 = a.bottom < b.bottom ? a.bottom : b.bottom;
if (x1 <= x0 || y1 <= y0) return 0;
final inter = (x1 - x0) * (y1 - y0);
final union = a.width * a.height + b.width * b.height - inter;
return union <= 0 ? 0 : inter / union;
}
/// 交叠/较小框面积(minIoU):同一目标的一大一小两框时比 IoU 更能命中
/// IoU = 小/大 会漏判;YOLO 同目标常输出大小两框,与 server 标注端
/// localAi.overlapThreshold 同思路,2026-09-01 用户实测修订)。
double boxOverlap(DetectionResult a, DetectionResult b) {
final x0 = a.left > b.left ? a.left : b.left;
final y0 = a.top > b.top ? a.top : b.top;
final x1 = a.right < b.right ? a.right : b.right;
final y1 = a.bottom < b.bottom ? a.bottom : b.bottom;
if (x1 <= x0 || y1 <= y0) return 0;
final inter = (x1 - x0) * (y1 - y0);
final minArea = a.width * a.height < b.width * b.height
? a.width * a.height
: b.width * b.height;
return minArea <= 0 ? 0 : inter / minArea;
}
List<DetectionResult> nms(List<DetectionResult> boxes, double iouThreshold) {
final sorted = [...boxes]..sort((a, b) => b.score.compareTo(a.score));
final kept = <DetectionResult>[];
for (final b in sorted) {
if (!kept.any((k) => boxOverlap(b, k) > iouThreshold)) kept.add(b);
}
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;
}