57 lines
2.4 KiB
Dart
57 lines
2.4 KiB
Dart
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;
|
||
}
|