MediaPipe 光流估计完整指南:5 个核心 API 让视频运动捕捉精确到亚像素
【免费下载链接】mediapipeCross-platform, customizable ML solutions for live and streaming media.项目地址: https://gitcode.com/GitHub_Trending/med/mediapipe
处理连续视频帧时,一个绕不开的问题是:某个像素在下一帧"跑到了哪里"。监控回放、动作分析、AR 特效里那些糊成拖影的目标,本质上都卡在光流估计(Optical Flow Estimation)这一步——也就是逐帧求出每个像素的二维位移场。MediaPipe 用OpticalFlowField这个类把稠密光流场封装成了一个既可视化、又能序列化、还能做遮挡判定的数据结构,配合Tvl1OpticalFlowCalculator计算节点即可在图里跑通一条像素级运动捕捉管道。
光流估计到底是什么
光流描述的是"同一块场景在两帧之间移动了多少、往哪个方向移"。它有两类形态,MediaPipe 都覆盖到了:
| 稠密光流 | 稀疏光流 | |
|---|---|---|
| 覆盖范围 | 每个像素一个位移 | 只挑特征点 |
| MediaPipe 对应 | OpticalFlowField(framework/formats/motion/) | RegionFlowFeature(util/tracking/region_flow.h) |
| 典型用途 | 像素级运动场、可视化 | 相机/物体轨迹、相机标定 |
本文聚焦稠密光流。你可以把它想象成一张"箭头地图":图上每个格子都存着一个(dx, dy)箭头,箭头指向该像素在下一帧的去向,箭头长短代表移动幅度。
5 分钟跑通最小光流管道
MediaPipe 里光流不是孤立的函数,而是由计算节点(Calculator)串成的图(Graph)。仓库里就带一条可直接参考的完整配置:tvl1_flow_and_rgb_from_file.pbtxt。
先拿到代码:
# 克隆仓库 git clone https://gitcode.com/GitHub_Trending/med/mediapipe cd mediapipe # 依赖见 requirements.txt;C++ 侧用 Bazel 构建管道的核心是两个节点——先把视频序列错开一帧形成"当前帧 / 下一帧"配对,再算前向光流:
# 把序列整体错开一帧(packet_offset: -1),凑出相邻帧对 node { calculator: "SequenceShiftCalculator" input_stream: "scaled_frames" output_stream: "shifted_scaled_frames" node_options { [type.googleapis.com/mediapipe.SequenceShiftCalculatorOptions]: { packet_offset: -1 } } } # 用 DualTVL1 算法算出前向光流,max_in_flight 控制并行度 node { calculator: "Tvl1OpticalFlowCalculator" input_stream: "FIRST_FRAME:first_frames" input_stream: "SECOND_FRAME:second_frames" output_stream: "FORWARD_FLOW:forward_flow" max_in_flight: 32 }下面这张来自仓库测试数据的画面,就是这类管道会作为输入喂给计算节点的一帧视频帧:
OpticalFlowField 如何工作
所有光流逻辑都挂在OpticalFlowField上,它的底层就是一张cv::Mat_<cv::Point2f>矩阵,每个像素存一对(dx, dy)。构造与使用非常直白(这段取自仓库单元测试里的真实用法):
// 用一张 (dx,dy) 二维位移矩阵构造光流场 cv::Mat_<cv::Point2f> raw(15, 17); for (int y = 0; y < raw.rows; ++y) for (int x = 0; x < raw.cols; ++x) raw(y, x) = cv::Point2f(3 * x + y, 2 * x - y); // 每像素位移 OpticalFlowField flow(raw); // 预测某点经当前光流后的位置,内部自动做双线性插值 float nx, ny; flow.FollowFlow(4.9f, 3.2f, &nx, &ny);几个关键机制值得一看:
- 双线性插值:
FollowFlow/InterpolatedFlowAt对亚像素坐标取四个邻域像素加权,所以哪怕传4.9, 3.2这种小数坐标也能拿到平滑位移,这就是"亚像素级"的来源。 - 鲁棒最大幅度:
GetRobustMaximumMagnitude()会先剔除>1e9的异常值(源码里kHugeToIgnore)再取最大,避免个别坏点把整场拉爆。 - 前向-后向一致性:
EstimateMotionConsistencyOcclusions(forward, backward, threshold, ...)把一个点"跟着去程光流走到下一帧、再乘回程光流走回来",若往返位移超过阈值就判为遮挡/去遮挡像素。
它能做什么
轨迹预测:FollowFlow 的亚像素追踪
拿到光流后,最直接的用途是预测位置。安防或多目标追踪场景里,把检测框中心点丢进FollowFlow,即可得到下一帧的预测中心,再交给目标跟踪器做关联:
float nx, ny; if (flow.FollowFlow(bbox_cx, bbox_cy, &nx, &ny)) bbox.NextCenter(nx, ny); // 用光流把当前检测框外推到下一帧配合ConvertToCorrespondences()(返回(x+dx, y+dy)的对应矩阵),还能一次性拿到整幅画面所有像素的匹配位置,用于形变或重投影。
遮挡与去遮挡标记
前向光流加反向光流做往返校验,EstimateMotionConsistencyOcclusions能直接给出两张 mask:第一帧里被挡住、第二帧里露出来的像素分别标为非零值。这对"目标被另一目标临时遮住后如何重新找回"非常有用,阈值由你按场景给(仓库单测里用的是0.5像素)。
可视化:HSV 色轮渲染
GetVisualization()把位移场转成一张彩色图——色相(Hue)编码方向、饱和度(Saturation)编码相对幅度,全静止的画面会渲染成纯白。GetVisualizationSaturatedAt(max)允许你固定一个幅度上限,用来高亮"运动过快"的区域。
跨格式序列化:Tensor / Proto 互转
光流常在模型输出(TensorFlow 张量)与存储之间流转。CopyFromTensor接受H×W×2的 float 张量直接填场;ConvertToProto/SetFromProto则与OpticalFlowFieldData(proto 定义)互转,proto 里就是把dx、dy按光栅顺序 packed 存储,便于落盘或传输。
分辨率、并行与量化:真实配置参数
下面这些取值全部来自仓库图配置与源码,不是估算值,可作为你调参的起点:
| 参数 | 作用 | 仓库示例值 | 出处 |
|---|---|---|---|
target_height | 输入帧缩放目标高度 | 256 | ScaleImageCalculator 节点 |
max_in_flight | 并行计算光流的帧对数 | 32 | Tvl1OpticalFlowCalculator |
num_threads | 整条图的线程数 | 32 | 同上 pbtxt |
min_value/max_value | 光流量化到 0–255 的取值区间 | -20 ~ 20 | FlowToImageCalculator |
| 遮挡判定阈值 | 往返位移超过该值判为遮挡 | 0.5 像素 | EstimateMotionConsistencyOcclusions单测 |
调优要点:分辨率是精度与速度的第一杠杆,仓库默认压到 256 高;并行度上DualTVL1对象本身非线程安全,源码用带锁的对象池(tvl1_computers_)复用,所以直接调大max_in_flight/num_threads就能摊薄单帧耗时;把min/max_value收窄可让可视化对比度更突出。
接下来往哪走
OpticalFlowField目前给的是"两帧之间"的像素位移场,往上叠多帧融合、相机运动分解或稀疏-稠密联合,就能覆盖更广的运动理解场景;计算侧也可以把Tvl1OpticalFlowCalculator换成学习型算法以适配大位移场景。想深入实现细节,建议直接读 光流场源码 与 media sequence 图配置,那里有从解码、错帧、算流到编码落盘的一条完整可运行链路。
【免费下载链接】mediapipeCross-platform, customizable ML solutions for live and streaming media.项目地址: https://gitcode.com/GitHub_Trending/med/mediapipe
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考