目录一、前置知识什么是光流 金字塔 LK 算法1. 光流基础概念2. Lucas-KanadeLK算法约束条件3. 整体实现流程二、完整可运行源码带原注释 补充修复三、分模块逐行深度解析模块 1视频初始化与第一帧预处理模块 2Shi-Tomasi 角点检测参数 goodFeaturesToTrack详解字典参数解释函数返回值p0模块 3轨迹画布 mask 初始化模块 4金字塔 LK 光流核心参数模块 5主循环读取帧 灰度转换模块 6核心光流函数 calcOpticalFlowPyrLK 逐参数拆解输入参数说明三个返回值跟踪核心模块 7筛选有效跟踪点 绘制运动轨迹模块 8轨迹叠加与画面展示模块 9更新跟踪参考点持续循环模块 10资源释放收尾四、运行前置依赖与踩坑解决1. 安装依赖库2. 常见报错 解决方案报错 1视频读取为空画面全黑报错 2calcOpticalFlowPyrLK坐标维度不匹配报错 3轨迹不显示mask 全黑报错 4运动速度过快特征点频繁丢失五、拓展优化方向六、总结一、前置知识什么是光流 金字塔 LK 算法1. 光流基础概念光流是视频中相邻两帧像素的瞬时运动矢量通过像素灰度变化推导物体移动方向与距离分为两类稠密光流计算图像每一个像素的运动矢量计算量大稀疏光流只跟踪少量高质量特征角点速度快、工业场景常用本文采用该方案。前提1亮度恒定同一点随着时间的变化其亮度不会发生改变。2小运动随着时间的变化不会引起位置的剧烈变化只有小运动情况下才能用前后帧之间单位位置变化引起的灰度变化去近似灰度对位置的偏导数。3空间一致一个场景上邻近的点投影到图像上也是邻近点且邻近点速度一致。因为光流法基本方程约束只有一个而要求xy方向的速度有两个未知变量。所以需要连立n多个方程求解。2. Lucas-KanadeLK算法约束条件LK 算法成立依赖 3 个假设亮度恒定同一特征点前后帧灰度不变小运动两帧之间物体位移很小基础 LK 缺陷快速运动容易跟丢空间一致性相邻像素运动方向一致。3. 整体实现流程读取视频第一帧灰度化Shi-Tomasi 检测高质量角点作为初始跟踪特征创建空白 mask 画布用于永久绘制运动轨迹循环读取视频每一帧灰度化calcOpticalFlowPyrLK计算前后帧角点光流筛选跟踪成功的特征点在 mask 上绘制轨迹线段mask 轨迹叠加原图可视化ESC 键退出循环更新前一帧图像与特征点持续跟踪。二、完整可运行源码带原注释 补充修复import numpy as np import cv2 # ----------------------1. 初始化视频读取---------------------- # 打开视频文件传入0则读取摄像头实时画面 cap cv2.VideoCapture(test.avi) # 随机生成100组RGB颜色每个特征点分配独立颜色区分不同运动轨迹 color np.random.randint(0, 255, (100, 3)) # 读取视频第一帧作为光流计算的初始参考帧 ret, old_frame cap.read() # 将彩色BGR帧转为灰度图光流、角点检测仅支持单通道灰度图 old_gray cv2.cvtColor(old_frame, cv2.COLOR_BGR2GRAY) # ----------------------2. Shi-Tomasi角点检测参数配置---------------------- feature_params dict( maxCorners100, # 最大角点数量限制跟踪点总数避免画面杂乱 qualityLevel0.3, # 角点质量阈值最大角点响应值 * 0.3低于该值直接丢弃 minDistance7 # 两个角点之间最小欧式距离避免特征点扎堆 ) # goodFeaturesToTrackShi-Tomasi角点检测函数提取第一帧跟踪特征点 # 参数灰度图、掩码(None全图检测)、字典解包传入检测参数 p0 cv2.goodFeaturesToTrack(old_gray, maskNone,** feature_params) # 创建和原图尺寸完全一致的全黑空白掩码专门用来绘制永久运动轨迹 mask np.zeros_like(old_frame) # ----------------------3. 金字塔LK光流计算参数配置---------------------- lk_params dict( winSize(15, 15), # 光流搜索窗口大小窗口越大抗噪越强速度越慢 maxLevel2 # 金字塔层数0不使用金字塔数值越大处理大运动能力越强 ) # ----------------------4. 视频帧循环跟踪主逻辑---------------------- while True: # 读取下一帧画面 ret, frame cap.read() # retFalse代表视频读取完毕跳出循环 if not ret: break # 当前帧转为灰度单通道图满足光流计算输入要求 frame_gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # ----------------------核心函数金字塔LK光流计算---------------------- # calcOpticalFlowPyrLK(前帧灰度图, 当前帧灰度图, 前帧特征点, 输出点容器, 光流参数) # 返回值p1当前帧特征点坐标、st跟踪状态、err匹配误差 p1, st, err cv2.calcOpticalFlowPyrLK(old_gray, frame_gray, p0, nextPtsNone, **lk_params) # 筛选跟踪成功的特征点st1代表该点成功匹配0代表丢失/遮挡 good_new p1[st 1] good_old p0[st 1] # 遍历每一组成功跟踪的新旧特征点绘制运动轨迹 for i, (new, old) in enumerate(zip(good_new, good_old)): a, b new # 当前帧特征点坐标 (x,y) c, d old # 上一帧特征点坐标 (x,y) # OpenCV绘图函数坐标必须是整数浮点坐标强制转换int a, b, c, d int(a), int(b), int(c), int(d) # 在黑色mask画布绘制线段连接前后帧同一点永久保留轨迹 mask cv2.line(mask, pt1(a, b), pt2(c, d), colorcolor[i].tolist(), thickness2) # 单独展示轨迹画布可选调试用 cv2.imshow(mask, mask) # 将轨迹mask与原始彩色帧叠加实现原图彩色轨迹可视化 img cv2.add(frame, mask) # 展示最终跟踪画面 cv2.imshow(frame, img) # 等待150ms控制视频播放速度检测ESC按键(键值27)退出 k cv2.waitKey(150) if k 27: break # 更新循环变量当前帧变为下一轮的前帧当前特征点变为上一轮点 old_gray frame_gray.copy() # reshape重构数组维度(N,2) → (N,1,2)适配calcOpticalFlowPyrLK输入格式 p0 good_new.reshape(-1, 1, 2) # 循环结束释放视频资源、销毁所有窗口 cap.release() cv2.destroyAllWindows()这里在下面这行代码里的cv2.VideoCapture(test.avi)函数内放入需要进行光流估计的视频即可视频名字改为test.avi之后放入当前文件夹内cap cv2.VideoCapture(test.avi)下面是运行效果三、分模块逐行深度解析模块 1视频初始化与第一帧预处理cap cv2.VideoCapture(test.avi) color np.random.randint(0, 255, (100, 3)) ret, old_frame cap.read() old_gray cv2.cvtColor(old_frame, cv2.COLOR_BGR2GRAY)cv2.VideoCapture(test.avi)读取本地视频参数填0可调用电脑摄像头实时跟踪np.random.randint(0,255,(100,3))生成 100 组随机 RGB 三通道颜色每个特征点一条专属彩色轨迹区分不同物体运动cap.read()读取一帧视频画面返回ret(读取状态布尔值)、old_frame(BGR彩色图像)cv2.COLOR_BGR2GRAY彩色转灰度角点检测、光流算法仅支持单通道灰度图三通道彩色无法直接计算。模块 2Shi-Tomasi 角点检测参数 goodFeaturesToTrack详解feature_params dict(maxCorners100, qualityLevel0.3, minDistance7) p0 cv2.goodFeaturesToTrack(old_gray, maskNone,** feature_params)字典参数解释参数作用调参技巧maxCorners100最多检测 100 个角点画面运动物体多则调大画面简单调小减少计算量qualityLevel0.3角点质量筛选系数数值越大筛选越严格角点越少0.3 为通用平衡值minDistance7角点最小间隔像素防止大量角点聚集在同一小块区域保证特征均匀分布函数返回值p0输出数组形状(N,1,2)N 是检测到的角点数量每个元素存储[x,y]像素坐标是光流跟踪的初始输入点。模块 3轨迹画布 mask 初始化mask np.zeros_like(old_frame)np.zeros_like(old_frame)创建和原图宽高、通道数完全一致的全黑数组核心作用单独存储所有运动轨迹线段不会随帧刷新消失每一帧只新增线段历史轨迹永久保留最后通过cv2.add叠加到原图实现可视化。模块 4金字塔 LK 光流核心参数lk_params dict(winSize(15, 15), maxLevel2)这里注意maxLevel 数值怎么选maxLevel0不使用金字塔只用原图只能跟踪微小移动maxLevel1~3日常视频、摄像头跟踪最常用数值越大能跟踪更大幅度的运动但计算速度变慢、消耗更多内存。参数含义调参场景winSize(15,15)局部搜索窗口尺寸物体运动模糊、噪声大时调大 (21,21)小物体精细跟踪调小 (7,7)maxLevel2金字塔层数0 关闭金字塔仅微小运动可用2~3 适配大部分视频快速运动场景层数越高速度越慢模块 5主循环读取帧 灰度转换ret, frame cap.read() if not ret: break frame_gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)if not ret视频文件读取到末尾时retFalse直接退出循环避免空图报错每帧必须转灰度前后帧灰度图是calcOpticalFlowPyrLK强制输入要求。模块 6核心光流函数calcOpticalFlowPyrLK逐参数拆解p1, st, err cv2.calcOpticalFlowPyrLK(old_gray, frame_gray, p0, nextPtsNone, **lk_params)输入参数说明old_gray上一帧灰度图像金字塔 LK 前帧frame_gray当前帧灰度图像金字塔 LK 后帧p0上一帧检测到的特征角点数组形状(N,1,2)nextPtsNone输出容器填 None 时函数自动创建数组存储当前帧匹配点**lk_params解包传入金字塔、搜索窗口等光流配置。三个返回值跟踪核心p1当前帧匹配成功的特征点坐标格式同p0st状态数组长度等于特征点数量st[i]1表示第 i 个点跟踪匹配成功0表示点丢失、遮挡、超出画面err每个特征点灰度匹配误差误差越大匹配可信度越低可用于二次过滤噪声点。模块 7筛选有效跟踪点 绘制运动轨迹good_new p1[st 1] good_old p0[st 1] for i, (new, old) in enumerate(zip(good_new, good_old)): a, b new c, d old a, b, c, d int(a), int(b), int(c), int(d) mask cv2.line(mask, (a,b), (c,d), color[i].tolist(), thickness2)p1[st 1]布尔索引筛选只保留跟踪成功的点剔除丢失失效角点zip(good_new, good_old)一一配对当前帧、上一帧同一特征点int(a),int(b)光流输出坐标是浮点小数OpenCV 绘图 API 只接收整数像素坐标必须强制转换cv2.line(mask,...)在黑色 mask 画布绘制线段连接前后帧同一特征点实现轨迹留存不同点使用独立随机颜色区分运动目标。模块 8轨迹叠加与画面展示img cv2.add(frame, mask) cv2.imshow(frame, img) k cv2.waitKey(150) if k 27: breakcv2.add(frame, mask)像素级加法融合原图 轨迹 mask黑色 mask 像素值为 0仅绘制过线段的彩色像素会叠加在原图上cv2.waitKey(150)帧间隔 150ms控制视频播放速度数值越大播放越慢返回键盘按键 ASCII 码k 27ESC 键 ASCII 码为 27按下直接跳出跟踪循环。模块 9更新跟踪参考点持续循环old_gray frame_gray.copy() p0 good_new.reshape(-1, 1, 2)old_gray.copy()将当前帧灰度图复制作为下一轮循环的 “前一帧参考图”reshape(-1,1,2)核心原因是calcOpticalFlowPyrLK 函数要求输入的特征点坐标是 (N, 1, 2) 格式而经过筛选后的 good_new 是 (N, 2) 格式。模块 10资源释放收尾cap.release() cv2.destroyAllWindows()cap.release()释放视频读取流释放内存资源cv2.destroyAllWindows()关闭所有cv2.imshow创建的窗口防止程序后台残留窗口进程。四、运行前置依赖与踩坑解决1. 安装依赖库pip install opencv-python opencv-contrib-python numpy2. 常见报错 解决方案报错 1视频读取为空画面全黑原因 1test.avi视频文件不存在、路径错误解决把视频和代码放在同一文件夹或填写完整绝对路径想用摄像头直接替换cap cv2.VideoCapture(0)。报错 2calcOpticalFlowPyrLK坐标维度不匹配原因未执行reshape(-1,1,2)特征点数组维度为(N,2)解决每次循环末尾必须对good_new重构三维维度。报错 3轨迹不显示mask 全黑原因 1st1筛选后good_new为空所有特征点全部丢失解决调小qualityLevel、增大maxCorners增加初始角点数量降低maxLevel适配微小运动场景。原因 2视频画面几乎无纹理纯白墙壁goodFeaturesToTrack检测不到任何角点解决更换纹理丰富的视频素材。报错 4运动速度过快特征点频繁丢失解决调高maxLevel3扩大金字塔层数调大winSize(21,21)搜索窗口。五、拓展优化方向周期性重新检测角点长时间跟踪后特征点会大量丢失每 30 帧重新调用goodFeaturesToTrack补充新角点绘制特征点圆点在每一帧画面用cv2.circle标记当前跟踪角点直观看到点位置基于运动矢量过滤静止点计算good_new - good_old位移剔除静止背景点只跟踪移动物体保存跟踪视频使用cv2.VideoWriter将带轨迹的画面保存为新视频文件误差过滤结合err误差数组剔除匹配误差过大的噪声跟踪点提升稳定性。六、总结本文完整实现工业界轻量化稀疏光流跟踪方案 —— 金字塔 Lucas-Kanade 算法相比稠密光流具备计算速度快、资源占用低优势适合视频目标轨迹追踪、SLAM 前端特征跟踪、摄像头运动分析等场景。核心分为两步Shi-Tomasi 高质量角点提取 金字塔分层光流迭代匹配搭配独立 mask 画布实现永久运动轨迹可视化代码可直接落地修改用于摄像头实时跟踪、视频分析项目。后续会继续更新进阶内容欢迎关注、点赞、收藏持续学习