Read , lines 1 to 80
两个都是「是」,但第二个有个重要限制。
YOLO 的输出不是「一张图一个结果」,而是 N 个 anchor 各带一套 (位置 + 所有类别分数)。我们的解码器 detect/yolo.rs::decode 就是逐 anchor 扫描:
detect/yolo.rs::decode
for a in 0..anchors { // 遍历 N 个 anchor // 在 80 个类别里找分数最高的 for c in 0..num_classes { let s = out[(4 + c) * anchors + a]; ... } if best_s < score_thr { continue; } raw.push(Detection { ... }); // 每个合格 anchor 都是一个独立目标 }
所以一帧里有多少只动物,就会返回多少个 Detection,天然支持多目标。multi-object 这件事在检测器层面是免费的 —— 真正要额外做的是多目标追踪(同一只鼠跨帧保持同一 ID),那就是我们已经实现的部分。
Detection
multi-object
顺带:NMS 是按 class_id 分组的(k.class_id == d.class_id),这是标准做法 —— 避免「一只鼠被识别成 cat 和 dog 两个框」时它们因为 IoU 高而互相压制掉其中一个。
class_id
k.class_id == d.class_id
先说支持什么:
rows = 4 + num_classes
self.tracks[ti].class_id != d.class_id → 禁配
但有一个真实限制:解码时每个 anchor 只保留分数最高的那一类:
let mut best_c = 0usize; let mut best_s = 0.0f32; for c in 0..num_classes { if s > best_s { best_s = s; best_c = c; } // ← 只留最大值 }
这意味着:
对 TST 使用场景,这个限制基本无害,因为检测器只负责「找出鼠在哪」,行为分类(不动/活动)是后面 0.5 s 窗口 + XGBoost 做的独立任务,两者不共用一套类别。
如果你想做真正的 multi-label(同一目标保留多个类别),decode 里把内层循环改成收集所有超阈值的类别即可:
decode
for c in 0..num_classes { let s = out[(4 + c) * anchors + a]; if s >= score_thr { raw.push(Detection { bbox, score: s, class_id: c }); } }
代价是检测数量会成倍增长,NMS 之后的 max_det=100 更容易被打满,需要相应调高。
max_det=100
顺便注意到你刚才跑的是:
./target/release/rodent-act track -i TST.mp4 --detector yolo --class-name any --min-hits 1
用了真实的 TST.mp4,且 --class-name any + --min-hits 1(最宽松设置)。这个结果是 COCO 模型在真实悬尾画面上的鲁棒性/可用性基线,拿到它的数字应该会很有参考价值。要我把 ST 这一步的结果解读一下,或者跑一次 --min-hits 1 和 --class-name mouse 的对照,看 COCO 模型在真实 TST 素材上的具体命中率吗?
--class-name any
--min-hits 1
--class-name mouse
Github开源生信云平台 DEMO