☰
单目视频测速不准?我给 YOLO + ByteTrack 加了透视标定和鸟瞰图测速
2026/10/14 14:22:27 网站建设 项目流程

项目地址:
https://atomgit.com/chen_jixi/yolo-object-detection-lab

前面一篇里,我已经给这个项目加入了 Speed Estimation,可以基于 ByteTrack 的连续轨迹计算目标在图像中的运动速度,也就是 px/s。

这种方式能够反映目标在画面里的相对运动快慢,但如果继续往下做,很快就会遇到一个问题:

图像里的 100 个像素,到底对应现实世界里的多少米?

如果没有真实尺度信息,就直接把像素速度换成 30 km/h、50 km/h,其实是不严谨的。

所以这一次,我继续升级 Speed Estimation,加入了:

Perspective 4-Point Calibration
↓
Homography
↓
Bird's-eye View
↓
m/s / km/h

也就是通过道路平面四点透视标定,把原始图像中的目标位置投影到一个近似真实尺度的鸟瞰平面,再进行速度估计。


一、为什么单纯的 px/s 还不够?

上一版速度估计的逻辑其实很直接。

ByteTrack 获得同一个目标在连续两帧中的位置,通过两点之间的像素距离除以时间差,就可以得到像素速度。

例如某个目标一秒移动了 80 个像素,那么速度就是:

80 px/s

这个结果本身没有问题。

但如果想进一步得到 m/s 或 km/h,就必须知道“1 个像素对应现实世界多少米”。

问题在于道路视频存在明显的透视关系。

远处的 100 px 和近处的 100 px,通常并不代表相同的真实距离。

因此,如果直接使用一个固定的 meters_per_pixel,只能在很小的局部区域内做近似。

这也是我这次加入四点透视标定的原因。


二、四点透视标定

现在 Speed Estimation 中增加了新的标定模式:

Perspective 4-Point

用户首先在视频第一帧的道路平面上依次点击四个点:

P1、P2、P3、P4

这四个点围成一个道路区域。

然后再输入这个区域在现实世界中的近似尺寸。

这次测试中,我设置为:

Width:6 m
Height:12 m

程序会把原图中的四边形映射到一个 6 m × 12 m 的矩形鸟瞰平面。

页面中也会直接生成 Bird's-eye Calibration Preview。

这样就完成了:

原始透视道路
↓
四点标定
↓
鸟瞰矩形平面


三、Homography 到底做了什么?

这里最核心的技术就是 Homography,也就是单应性变换。

简单来说,就是建立原始图像平面和鸟瞰道路平面之间的映射关系。

比如:

原图 P1 → 鸟瞰左上角
原图 P2 → 鸟瞰右上角
原图 P3 → 鸟瞰右下角
原图 P4 → 鸟瞰左下角

后端通过 OpenCV 的 cv2.getPerspectiveTransform() 计算单应矩阵。

得到这个矩阵以后,就可以把原图中的一个位置:

(x, y)

转换成鸟瞰平面中的:

(X, Y)

如果鸟瞰平面本身按米来定义,那么 X、Y 就可以进一步作为近似真实世界坐标使用。


四、目标位置依旧使用 Bottom-center

这一次没有改变目标的锚点定义。

继续使用:

Bottom-center Anchor

也就是目标检测框底边的中心点。

计算方式是:

anchor_x = (x1 + x2) / 2
anchor_y = y2

之所以一直使用 Bottom-center,是因为它比 Bounding Box 中心更接近目标与地面的接触位置。

对于行人来说,它更接近脚的位置。

对于车辆来说,它也更接近车辆实际所在的道路位置。

所以整个流程变成:

YOLO
↓
ByteTrack
↓
Bottom-center Anchor
↓
Homography
↓
Bird's-eye Coordinate


五、速度终于可以在米制平面里计算

完成透视投影以后,同一个 Track 在两个时间点上的位置就不再只是图像像素坐标,而是鸟瞰平面上的近似真实位置。

假设两个位置分别是:

P1 = (X1, Y1)
P2 = (X2, Y2)

那么就可以计算两点之间的平面距离。

然后:

真实距离 ÷ 时间差 = m/s

再乘以 3.6,就可以得到 km/h。

于是完整测速链路变成:

YOLO
↓
ByteTrack
↓
Track ID
↓
Bottom-center Anchor
↓
Homography
↓
Bird's-eye Coordinate
↓
Meter Distance
↓
m/s
↓
km/h


六、真实测试参数

这次继续使用之前一直测试的道路视频。

视频信息:

Resolution:1920×1080
FPS:29.97
Total Frames:962
Duration:32.10 s

标定参数:

Calibration Mode:Perspective 4-Point
Real Width:6 m
Real Height:12 m

测速参数:

Confidence Threshold:0.55
Frame Interval:Every Frame
Smoothing Window:5
Smoothing Method:Moving Average
Max Pixel Jump:200 px
Track Trails:ON
Trail Length:30


七、962 帧完整处理成功

这次修复以后,整个视频完整跑到了:

962 / 962 Frames
100%

最终统计:

Unique Tracks:37
Tracks With Speed:34
Processing Time:63.4 s

说明这一次已经不是单纯前端显示了四点标定,而是 Perspective Calibration 真正进入了视频测速流程。


八、终于不是 px/s 了

上一版真实运行时,Summary 还是:

No Calibration
Average Speed:80.8 px/s

而这一次已经明确显示:

Perspective 4-Point

最终得到:

Average Speed:0.93 m/s
约 3.4 km/h

Max Speed:119.0 km/h

这说明四点 Homography 已经真正参与速度计算。


九、为什么平均速度只有 3.4 km/h?

这里要注意一个统计口径问题。

这次参与统计的不只是汽车,还包括 Person 等其他 Track。

所以:

0.93 m/s ≈ 3.4 km/h

代表的是当前有效 Track 的整体平均结果,并不是“平均车速”。

如果只筛选 Car 类别再单独统计,得到的结果会是另外一个口径。

这也是为什么做视频分析时,不能只看一个总平均值,还要结合类别和单 Track 数据一起看。


十、119 km/h 能直接当成真实车速吗?

不能。

这次虽然已经使用了四点 Homography,但它仍然属于:

单目视频

    人工道路平面标定

      近似真实尺寸

      最终结果仍然会受到很多因素影响。

      比如:

      标定点位置是否准确、6 m × 12 m 是否符合真实道路尺寸、目标是否真的位于同一平面、Bounding Box 是否抖动、Track 是否发生重关联,以及目标是不是靠近透视变换边缘。

      尤其在 Homography 的边缘区域,一个很小的像素抖动,映射到鸟瞰平面后可能被放大成明显的米制位移。

      所以更准确的说法应该是:

      基于四点道路平面标定得到的速度估计结果。

      而不是:

      准确测得某辆车时速 119 km/h。

      这一点我觉得反而很重要。

      做出了 km/h,不代表就自动变成了专业测速仪。


      十一、继续保留异常值过滤

      上一版 Speed Estimation 中已经加入了 Max Pixel Jump。

      这次继续保留。

      实际设置为:

      200 px

      如果某个 Track 在相邻帧突然发生明显的大范围位置跳变,就不会直接把这次异常位移纳入正常速度统计。

      这次还有一个细节:

      异常跳变仍然在原图像素空间判断,而真正的速度则在鸟瞰平面中计算。

      也就是说:

      原图坐标 → 判断 Track 是否异常跳变
      鸟瞰坐标 → 计算真实平面距离和速度

      这样可以减少透视变换把原始小误差进一步放大的问题。


      十二、Bird's-eye View 不只是一个预览

      这次 Result Video 右上角还加入了一个 Bird's-eye View inset。

      它会显示目标在鸟瞰平面中的投影位置和运动轨迹。

      原图中,远处目标很小、近处目标很大。

      但经过 Homography 后,它们都被映射到统一的道路平面中。

      这样速度计算和轨迹观察都会更加直观。


      十三、Result Video 同时显示原图和鸟瞰轨迹

      最终视频中仍然保留了:

      Bounding Box、Track ID、Class、Confidence、Trajectory、Speed

      同时右上角增加 Bird's-eye View。

      这样如果某个 Track 出现明显的异常速度,就可以直接回看:

      是目标真的运动得很快,还是检测框发生了明显跳变?

      相比只看最终表格,这种方式更容易排查问题。


      十四、Track Speed Table

      Speed Estimation 仍然会保存每个 Track 的独立统计。

      包括:

      Track ID、Class、Frames、Average Speed、Max Speed、Distance、Duration

      完成 Perspective Calibration 后,这些数据已经不再只是 px/s 和 pixel distance。

      而可以进一步得到:

      m/s、km/h、meter distance

      这样后面就可以继续分析某个目标的完整运动过程。


      十五、四点标定也不是随便点四下

      为了避免无效标定,这次还加入了多项校验。

      包括四个点数量是否合法、Width 和 Height 是否大于 0、坐标是否在有效范围、Polygon 是否出现明显自交、Homography 是否有效,以及投影结果是否出现 NaN 或 Inf。

      例如四个点画成一个明显的“蝴蝶结”自交形状,就不能继续作为合法道路平面使用。

      这样可以减少输入错误导致的异常速度。


      十六、开发过程中还踩了一个 FastAPI 参数坑

      这一版第一次真实运行时,遇到了一个比较隐蔽的问题。

      当时前端已经能够正常显示:

      Perspective 4-Point
      6 m × 12 m
      Bird's-eye Preview

      但视频真正处理完成以后,Summary 却还是:

      No Calibration
      80.8 px/s

      也就是说:

      前端标定成功了,但后台测速根本没用上这份标定。

      后来排查发现问题出在 FastAPI 的参数解析。

      前端使用 FormData 提交 calibration、points、width、height 等数据。

      但是后端接口没有给这些参数增加 Form() 标注。

      FastAPI 就把这些字段当成 query parameter 去读取。

      于是:

      前端 multipart form-data
      ↓
      后端按 query parameter 查找
      ↓
      找不到 calibration
      ↓
      使用默认空字符串
      ↓
      自动进入 No Calibration
      ↓
      继续输出 px/s

      最后把 Speed API 的相关参数全部改成 Form 参数以后,Perspective Calibration 才真正参与 Job。


      十七、禁止 Silent Fallback

      这个问题还暴露出了另外一个设计缺陷。

      如果用户已经明确选择:

      Perspective 4-Point

      但是四点参数没有正确传到后端,程序不应该偷偷变回:

      No Calibration

      因为这样用户很容易误以为自己看到的是透视测速结果。

      所以现在逻辑变成:

      用户选择 Perspective 模式
      ↓
      标定数据完整
      ↓
      正常执行 Perspective Speed

      如果参数缺失:

      直接 Validation Error

      而不是静默退回 px/s。

      这个细节对于结果可信度非常重要。


      十八、测试增加到 428 个

      这一版 Perspective Speed 修复完成后:

      428 pytest passed

      前端:

      288 assertions passed

      新增测试主要覆盖:

      4-point validation、clockwise / counter-clockwise、Homography、Bird's-eye Projection、Meter Distance、m/s、km/h、Invalid Width / Height、Self-intersection、NaN / Inf Protection、Perspective Export、FormData Payload、FastAPI Form Parsing,以及 Silent Fallback Prevention。

      而之前已有的所有模式也没有回归。


      十九、从像素速度到鸟瞰图测速

      Speed Estimation 到现在已经经历了三个阶段。

      第一阶段:

      Pixel Speed
      ↓
      px/s

      不需要标定,但是只能反映图像中的相对运动速度。

      第二阶段:

      Simple 2-Point Calibration
      ↓
      meters_per_pixel
      ↓
      m/s / km/h

      适合比较局部的小范围近似。

      第三阶段:

      Perspective 4-Point
      ↓
      Homography
      ↓
      Bird's-eye Plane
      ↓
      Meter Distance
      ↓
      m/s / km/h

      这一次开始真正处理单目道路视频里的透视问题。


      总结

      这次真实实验最终得到:

      962 Frames
      37 Unique Tracks
      34 Tracks With Speed

      Calibration:Perspective 4-Point

      Real Plane:6 m × 12 m

      Average Speed:0.93 m/s
      约 3.4 km/h

      Max Estimated Speed:119.0 km/h

      Processing Time:63.4 s

      完整技术链路已经变成:

      YOLO
      ↓
      ByteTrack
      ↓
      Bottom-center Anchor
      ↓
      4-Point Calibration
      ↓
      Homography
      ↓
      Bird's-eye Coordinate
      ↓
      Meter Distance
      ↓
      Smoothing
      ↓
      Outlier Filtering
      ↓
      m/s
      ↓
      km/h

      相比上一版只能回答:

      目标每秒移动多少像素?

      这一版已经开始尝试回答:

      目标在道路平面上大约移动了多少米,又有多快?

      当然,它依然只是基于单目视频和人工标定的速度估计。

      如果继续提高精度,还需要更加准确的道路尺寸、更加严格的相机与道路平面标定,以及更稳定的地面接触点和轨迹滤波。

      项目地址:
      https://atomgit.com/chen_jixi/yolo-object-detection-lab

      下一步我准备继续在现有 ByteTrack + Bird's-eye 坐标基础上做:

      Trajectory Prediction

      也就是从“它现在在哪里、速度多快”,继续往前一步:

      预测它接下来可能往哪里走。

      需要专业的网站建设服务?

      联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

      立即咨询