那一帧 0.709:一个深夜排障记

这天晚上,本来应该结束了。

交付包已经打好,模型已经换好,板子已经测过,HTTP 接口也通了。仓库干净,交付目录里有 deb、manifest、使用说明。按理说,这个夜晚应该属于睡觉。

但现实不是按理说。

客户群又响了。

他们说:识别不到。

于是我又坐回电脑前,重新打开视频,重新拿电脑上的 YOLO 跑了一遍。视频里有火,模型能识别,而且识别得很准。不是边缘情况,不是玄学误差,不是模型能力不够。火就在那里,框也在那里,分数也在那里。

这时对方发来了日志。

我打开日志,看了几行,血压直接上来了。

推理启动正常。RTSP 拉流正常。解码正常。预处理正常。NPU 推理正常。后处理正常。服务状态写入正常。/api/status HTTP 返回正常。

然后,在 20:27:21,日志里出现了这一行:

1
"detections":{"items":[{"score":0.7093907}],"per_frame":0}

我看到这里,差点气笑。

这不是没识别。

这是已经识别了。

低频 1 秒轮询都能捞到一次 score=0.7093907 的结果,你告诉我识别不到?

rkyolo 已经把火识别出来,通过 /api/status 返回给业务侧了。

但是最后,业务侧总结:

1
2
detected=false
message=轮询超时未检测到烟火

这句话就像在已经拿到快递的情况下,系统告诉你”快递未送达”。

更离谱的是,他们说:按照文档,做了防抖,连续 3 次才报警。

我查了文档。

没有。

没有”防抖”。

没有”连续 3 次”。

没有”报警策略”。

没有”debounce”。

rkyolo 文档写的是推理服务接口,告诉你怎么启动模型、怎么查状态、怎么看 detections.items。至于你业务侧要不要连续 3 次,要不要报警,那是你自己的策略。

结果现在,业务侧自己加了”连续 3 次才报警”,又用 1 秒轮询去采一个 20 FPS 的推理流。

20 FPS 是什么概念?大约 50ms 一帧。他们 1 秒查一次。也就是说,中间大约 20 帧都没看。

然后他们要求连续 3 次。

这就像拿一个每秒闪 20 次的灯,用每秒一次的相机去拍,还要求连续拍到 3 张亮灯,否则就说灯没亮。

灯亮了。

你没拍到。

但日志里甚至已经拍到了一次。

然后你说没亮。

我真的服了。

更有意思的是,日志里的 items 只有:

1
{"score":0.7093907}

而 rkyolo 正常返回的检测项应该包含 classscorepoints。中间 Java DTO 把 classpoints 丢了,只剩一个 score。然后业务侧还没有按 items 非空判断,不知道看的什么字段。日志里 items 明明有值,最后却 detected=false

这已经不是模型问题。

这是接入逻辑问题。

这也不是技术问题。

这是责任边界问题。

模型训练是我干的。ONNX 导出是我干的。RKNN 量化是我干的。RXC 封装是我干的。deb 打包是我干的。板端验证是我干的。文档是我写的。

客户日志是我看的。

连 Java 侧应该怎么判 items 非空,我都写了。

然后对面说:识别不到。

日志里明明写着:

1
score=0.7093907

这个夜晚,真正没被识别出来的不是火。

是责任边界。