4. 运动一致性检测基础:基于光流的运动场分析
各位同学好,我是老张。今天咱们聊一个在动态SLAM里绕不开的话题——运动一致性检测。
说白了,就是怎么区分「相机自己在动」和「场景里的东西自己在动」。这个问题我当年刚入行时踩过不少坑,今天把经验掰开揉碎了讲给你听。
4.1 光流法:运动场的「眼睛」
先问个问题:你盯着一个移动的物体看,眼睛会怎么反应?
对,眼球会追着它转。但SLAM里的相机没这么智能,它只能老老实实拍下每一帧。这时候,光流就派上用场了。
光流,说白了就是图像中像素点的运动矢量。每个像素点从第t帧跑到第t+1帧,位移了多少、方向如何,这就是光流。
核心思想:如果场景是静态的,光流应该完全由相机运动引起,呈现一种「全局一致性」的模式。如果某个区域的光流模式跟周围不一样,那大概率是独立运动的物体。
我在项目里遇到过这种情况:一个行人从镜头前走过,他的光流方向跟背景完全相反。嗯,这就是典型的运动不一致。
4.2 Lucas-Kanade光流:稀疏但精准
Lucas-Kanade(LK)光流,我习惯叫它「点追踪器」。它不关心整张图,只盯着几个关键点。
它的假设很简单:
- 亮度恒定:同一个点,亮度不变
- 小运动:相邻帧之间,运动量很小
- 空间一致性:邻域内的点,运动一致
你想想看,这三个假设其实挺强的。尤其是「小运动」这条,如果物体跑得飞快,LK就抓瞎了。所以实际使用时,我们通常会配合图像金字塔——从粗到细,逐层追踪。
我的经验:LK适合追踪角点、边缘等特征明显的点。如果你用ORB特征点,LK光流是绝配。我在一个室内机器人项目里,用LK追踪天花板上的纹理点,效果出奇的好。
代码实现也不复杂,OpenCV里直接调:
import cv2
import numpy as np
# 读取两帧图像
prev = cv2.imread('frame1.jpg', cv2.IMREAD_GRAYSCALE)
next = cv2.imread('frame2.jpg', cv2.IMREAD_GRAYSCALE)
# 检测特征点(比如Shi-Tomasi角点)
prev_pts = cv2.goodFeaturesToTrack(prev, maxCorners=100,
qualityLevel=0.01, minDistance=10)
# LK光流追踪
next_pts, status, err = cv2.calcOpticalFlowPyrLK(prev, next,
prev_pts, None)
# 筛选有效点
good_pts = prev_pts[status == 1]
good_next = next_pts[status == 1]
# 计算运动矢量
flow_vectors = good_next - good_pts
这段代码跑完后,你就得到了每个特征点的运动矢量。接下来,就是判断这些矢量是否「一致」了。
4.3 Farneback光流:稠密但计算量大
LK只能给你稀疏的几个点,但有时候我们需要全图的运动信息。比如,你想知道整张图里哪些区域在动,哪些是静止的。
这时候,Farneback光流就登场了。它计算每个像素点的光流,输出一张稠密的光流场。
Farneback的原理稍微复杂一些,它用多项式展开来近似每个像素邻域的亮度变化。我简单说下:
- 对每个像素,用一个二次多项式拟合它的邻域
- 通过比较两帧的多项式系数,算出位移
- 再通过全局优化,得到平滑的光流场
注意:Farneback的计算量比LK大得多。我在一个嵌入式项目里试过,640x480的图像,跑一次要几十毫秒。如果你用实时系统,得掂量掂量。
OpenCV调用同样简单:
import cv2
prev = cv2.imread('frame1.jpg', cv2.IMREAD_GRAYSCALE)
next = cv2.imread('frame2.jpg', cv2.IMREAD_GRAYSCALE)
# Farneback稠密光流
flow = cv2.calcOpticalFlowFarneback(prev, next, None,
pyr_scale=0.5, levels=3,
winsize=15, iterations=3,
poly_n=5, poly_sigma=1.2,
flags=0)
# flow是一个两通道的矩阵,分别表示x和y方向的位移
fx, fy = flow[..., 0], flow[..., 1]
输出的是一个跟原图一样大的矩阵,每个像素点都有一个(x, y)位移量。有了这个,你就可以做很多事了。
4.4 如何区分相机运动与物体运动?
好,现在我们有光流了。怎么判断哪些是相机运动引起的,哪些是物体独立运动?
我总结了一套三步走的方法:
- 全局运动估计:用所有光流点,拟合一个全局运动模型(比如仿射变换或单应性矩阵)。这个模型代表「相机自身的运动」。
- 残差计算:每个点的实际光流,减去全局模型预测的光流,得到残差。
- 一致性判断:残差大的点,就是运动不一致的点,很可能是独立运动的物体。
关键点:全局模型拟合时,要用RANSAC剔除异常值。因为独立运动的物体本身就是异常值,如果不剔除,模型会被带偏。
举个例子。假设相机在向右平移,那么背景上的点都应该向左移动(相对运动)。但一个行人从右向左走,他的光流方向可能跟背景相反。用RANSAC拟合时,行人的点会被当作外点剔除,剩下的点拟合出的模型就是纯相机运动。
我曾经在一个自动驾驶项目里用过这个方法。当时车辆在行驶,路边的行人、自行车都在动。我们用Farneback光流加RANSAC,成功把动态物体标记出来,然后丢给跟踪模块做避障。效果还不错,就是计算量有点大。
4.5 实战中的坑与技巧
讲几个我踩过的坑:
- 光照变化:光流假设亮度恒定,但实际场景中光照会变。比如太阳被云遮住,整张图变暗,光流就会出错。我建议在预处理时做直方图均衡化。
- 大运动:如果物体运动太快,LK和Farneback都会失效。解决办法是用图像金字塔,或者降低帧率。
- 纹理缺失:白墙、天空这种区域,光流算不出来。我一般会加一个置信度阈值,低于阈值的点直接丢弃。
我的习惯:在实际项目中,我会把LK和Farneback结合起来用。先用LK追踪特征点,快速判断哪些区域可能有动态物体。然后在这些区域用Farneback做稠密光流,精确定位。这样既省计算量,又保证了精度。
4.6 本章小结
运动一致性检测,说白了就是找「不听话」的像素点。光流法给了我们一个直观的工具:
- LK光流:稀疏、快速、适合特征点追踪
- Farneback光流:稠密、全面、适合区域分析
- 全局模型+残差:区分相机运动与物体运动的核心思路
下一章,我们会深入讲怎么用这些光流信息做动态物体的检测与剔除。嗯,到时候会涉及一些数学推导,但我会尽量讲得通俗些。
公众号:蓝海资料掘金营,微信deep3321