块匹配运动估计原理与实现:从视频编码到稳像的工程实践
发布时间:2026/10/12 13:31:22 · 信平特种工考证网

1. 运动估计到底在解决什么问题1.1 从一段真实需求说起前阵子帮一个做视频剪辑的朋友处理一批手持拍摄的素材画面抖得厉害他想要一种把抖动抹平的效果。我第一反应不是去开防抖插件而是先想清楚一件事要抹平抖动就得先知道每一帧相对上一帧到底动了多少。这个动了多少的量化过程就是运动估计。运动估计这四个字听起来很学术但它的本质非常朴素——给定两帧图像找出它们之间的位移关系。这个位移关系可以是一个全局的平移量也可以是每个像素各自的移动向量。前者叫全局运动估计后者叫稠密光流。介于两者之间的是把画面切成一个个小块每块单独估计一个运动向量这就是块匹配运动估计也是视频编码领域用得最多的一种。我在实验八里主要折腾的就是块匹配这条路线。原因很简单它足够直观代码量可控而且能直接对应到视频压缩里的运动补偿逻辑。你如果只是想理解运动估计的核心思想块匹配是最好的切入点如果你要做视频稳像全局运动估计更合适如果你要做目标跟踪或者自动驾驶里的场景流那稠密光流才是正路。三条路各有各的适用场景选错了方向后面调参调到怀疑人生也没用。1.2 为什么这个实验值得认真做一遍很多人学数字图像处理学到运动估计这一章就开始犯迷糊因为前面的滤波、边缘检测、形态学都是对单张图做处理到了运动估计突然变成对两张图之间的关系做处理思维上要转个弯。但这个弯转过来之后你会发现很多实际问题都能用同一套框架去拆解。比如视频压缩为什么同样一段视频H.264能比MJPEG小那么多核心就在于它不存每一帧的完整画面而是存这一帧相对上一帧动了多少残差是多少。这个动了多少就是运动估计的输出。再比如视频稳像为什么手机拍出来的画面那么稳因为它实时估计了相机的全局运动然后把反向的补偿量施加回去。还有慢动作插帧两帧之间补出来的中间帧位置就是靠运动估计插出来的。所以这个实验表面上是在做一道题实际上是在给你一把钥匙后面视频编码、稳像、插帧、目标跟踪这些方向用的都是同一套底层逻辑。我建议你在做这个实验的时候不要只满足于跑出一个结果图而是把每一步的中间产物都可视化出来看看运动向量场长什么样残差图长什么样匹配代价的分布长什么样。这些中间产物才是真正帮你建立直觉的东西。1.3 这个实验适合谁来参考如果你正在上数字图像处理这门课实验八大概率是绕不过去的那这篇内容可以直接当你的操作手册用。如果你已经工作了但一直对视频处理这块似懂非懂想找个切入点补一补块匹配运动估计是最容易上手的一个。如果你是想做视频稳像或者插帧的开发者前面几节的原理部分可以帮你把概念理清楚后面的实操部分可以帮你把参数调明白。需要提前说明的是这个实验对编程基础有一定要求至少得会写循环、会操作数组、会读写图像。语言上Python和MATLAB都行我个人更推荐Python因为后面如果要接视频处理的库会更顺。但如果你学校要求用MATLAB也没问题核心逻辑是一样的只是语法不同。2. 块匹配运动估计的核心原理拆解2.1 为什么要把画面切成块块匹配的思路说白了就是化整为零。整帧图像的运动太复杂了前景在动、背景在动、相机也在动你没法用一个统一的公式描述。但如果我把画面切成16x16的小块然后假设每个小块内部的运动是一致的那问题就简化成这一小块在上一帧的哪个位置。这个假设当然不完美。如果块正好切在物体边缘上块内一半是前景一半是背景那这个块的运动就没法用单一向量描述这就是所谓的块效应。但工程上我们接受这个不完美因为它的计算量可控而且块切得越小块效应越弱但计算量越大这是一个需要权衡的参数。我在实验里用的块大小是16x16搜索范围是±16像素。这个组合是比较经典的视频编码标准里也常用。如果你要做更精细的估计可以把块缩小到8x8但搜索范围也要相应调整不然大运动就搜不到了。2.2 匹配准则怎么选块匹配的核心是怎么判断两个块像不像。常用的准则有三个SAD绝对差之和、SSD平方差之和、NCC归一化互相关。SAD是最简单的就是把两个块对应位置的像素值相减取绝对值然后全部加起来。值越小说明越像。它的优点是计算快没有乘法全是加法和绝对值硬件实现特别友好。视频编码里基本都用SAD。SSD是把差值平方再求和。它对大差值更敏感所以匹配结果在数值上更平滑但计算量比SAD大因为多了乘法。如果你对匹配精度要求高可以用SSD。NCC是归一化互相关它对亮度变化不敏感适合两帧之间曝光有变化的场景。但计算量最大而且对纹理少的块容易出问题。我在实验里主要用SAD因为它是块匹配的标准答案而且后面如果要扩展到视频编码SAD是绕不开的。但我也建议你把SSD和NCC都实现一遍对比一下结果你会对匹配准则这个概念有更具体的感受。2.3 搜索策略的取舍最笨的搜索方法是全搜索就是把搜索范围内每一个可能的位置都试一遍取SAD最小的那个。这个方法一定能找到全局最优但计算量巨大。如果搜索范围是±16那每个块要试33x331089个位置每个位置要算256次减法一个块就是27万次运算。一帧如果有几千个块那运算量就上亿了。所以工程上会用快速搜索算法比如三步搜索、菱形搜索、六边形搜索。这些算法的思路都是先大步跳再小步精调用少量候选点逼近全局最优。它们不一定能找到真正的最优但通常能找到接近最优的解而且速度快很多。我在实验里两种都实现了。全搜索用来做基准验证快速搜索的结果对不对。快速搜索用来感受用精度换速度这件事到底划不划算。实测下来菱形搜索在大多数情况下能找到和全搜索一样的结果但候选点数量只有全搜索的十分之一左右。这个性价比是非常高的。2.4 运动向量场怎么理解运动估计的输出是一堆运动向量每个块一个。把这些向量画在图上就是运动向量场。箭头方向表示运动方向箭头长度表示运动幅度。看运动向量场有几个要点。第一背景区域的向量通常是一致的因为相机运动是全局的背景跟着一起动。第二前景物体的向量和背景不一样差异越大说明物体相对背景运动越剧烈。第三如果某个区域的向量杂乱无章那大概率是纹理太少匹配不可靠出现了误匹配。我在实验里特意找了一段有平移相机的视频还有一段有行人走过的视频分别看它们的运动向量场。平移相机的向量场是整齐的行人区域的向量场是突变的。这个对比非常直观建议你也找两段不同特点的素材对比着看。3. 实操过程与核心环节实现3.1 实验环境与素材准备我用的环境是Python 3.9主要依赖numpy做矩阵运算opencv做图像读写和可视化matplotlib做绘图。如果你用MATLAB对应的函数是imread、imshow、quiver这些逻辑一样。素材方面我准备了三组。第一组是合成的平移图像就是把一张图整体向右下各移5个像素这样我知道真实运动是多少可以用来验证算法对不对。第二组是真实的手持拍摄视频取相邻两帧用来感受真实场景的运动。第三组是包含独立运动物体的视频用来观察前景背景运动不一致的情况。提示合成数据非常重要。很多人一上来就拿真实视频调结果算法对不对都不知道。先用合成数据把流程跑通确认能恢复出已知的运动量再去处理真实数据这样排查问题的时候才有参照。3.2 块匹配的完整实现步骤第一步是读取两帧图像转成灰度图。运动估计通常在灰度图上做因为彩色三个通道分别估计再合并计算量翻三倍而且结果不一定更好。灰度化用标准的加权公式就行。第二步是确定块的大小和搜索范围。我用的块大小是16搜索范围是16。这意味着我把当前帧切成16x16的块然后在参考帧的对应位置周围±16像素的范围内找最像的块。第三步是遍历每个块对搜索范围内的每个候选位置计算SAD。这里有个细节要注意块在图像边缘的时候搜索范围可能会超出图像边界。我的处理方式是把超出边界的部分裁掉只计算有效区域。另一种方式是给图像补零或者镜像填充但那样会引入虚假边缘影响匹配结果。我倾向于裁掉虽然会损失一点信息但不会引入错误。第四步是记录每个块的最佳匹配位置和对应的运动向量。运动向量就是最佳匹配位置相对于块原位置的偏移量。第五步是把所有运动向量画出来同时把残差图也画出来。残差就是当前块减去最佳匹配块之后的差值残差越小说明匹配越好。3.3 关键参数的计算与选择块大小怎么定我试了8、16、32三档。8x8的块运动向量场更精细但向量数量多而且纹理少的块容易误匹配。32x32的块向量场很平滑但块效应明显物体边缘的运动会被平均掉。16x16是比较平衡的选择也是视频编码里的常用值。搜索范围怎么定这个取决于你的素材里运动有多快。如果相机移动很慢±8就够了。如果运动快可能要±32甚至更大。但搜索范围每扩大一倍计算量大概翻四倍。我的建议是先估计一下最大运动量然后搜索范围取最大运动量的1.5倍左右留一点余量。匹配准则用SAD还是SSD我实测下来在光照稳定的情况下SAD和SSD的结果差异很小但SAD快不少。如果两帧之间亮度有变化SSD会稍微好一点但也好不了太多。真正对亮度变化鲁棒的是NCC但计算量太大。所以我的默认选择是SAD特殊情况再换。3.4 快速搜索算法的实现要点菱形搜索是我最推荐的快速算法。它的流程是这样的先在大菱形上取9个点中心加周围8个找到SAD最小的点。如果最小点在中心说明已经收敛搜索结束。如果最小点在菱形边上就以那个点为中心再取一个小菱形继续搜。直到最小点落在中心为止。这个算法的巧妙之处在于它假设匹配代价的分布是单峰的而且越靠近最优值代价越小。大多数情况下这个假设成立所以菱形搜索能很快收敛。但如果代价分布有多个局部极小值菱形搜索可能会陷进去。这时候可以先用三步搜索做粗定位再用菱形搜索精调。我在实验里对比了全搜索、三步搜索、菱形搜索三种方法。在合成数据上三种方法都能找到正确结果。在真实视频上菱形搜索偶尔会比全搜索差一两个像素但速度是十倍以上。这个取舍在工程上是完全可以接受的。3.5 运动向量场的可视化技巧直接画箭头图有时候会太密看不清。我的做法是每隔几个块画一个箭头或者把箭头长度按比例缩放。另外可以用颜色表示运动方向用亮度表示运动幅度这样一张图就能同时表达两个维度的信息。还有一个技巧是把运动向量场叠加在原图上这样能直观看到哪个物体的运动向量是什么样的。我通常会把背景的向量画成一种颜色前景的向量画成另一种颜色对比非常明显。残差图也值得一看。如果残差图整体很暗说明匹配得好。如果某个区域残差很亮说明那个区域匹配得不好可能是纹理太少也可能是运动太复杂超出了搜索范围。残差图是排查问题的好帮手。4. 常见问题与排查技巧实录4.1 匹配结果全是零向量怎么办这是最常见的问题尤其是新手。现象是运动向量场里所有箭头都是零看起来好像没有运动。原因通常是搜索范围设成了0或者循环写错了根本没遍历候选位置。排查方法很简单打印一下搜索范围的循环变量看看是不是真的在遍历。另外检查一下SAD的计算如果两个块完全一样SAD是0那零向量确实是最优的。但如果所有块都是零向量那大概率是代码逻辑有问题不是数据的问题。还有一种可能是图像读取的时候出了问题两帧读成了同一帧。检查一下两帧的像素值是不是完全一样如果是那说明读取逻辑有问题。4.2 运动向量场杂乱无章怎么调如果向量场看起来像一团乱麻没有明显的规律通常是以下几个原因。第一是块太小纹理太少。16x16的块如果落在纯色区域匹配结果就是随机的。解决办法是增大块大小或者对纹理少的块做特殊处理比如直接继承周围块的运动向量。第二是搜索范围太小真实运动超出了搜索范围算法只能在边界上找一个次优解。解决办法是增大搜索范围或者先用全局运动估计粗估一下运动量再设定搜索范围。第三是匹配准则不合适。如果两帧之间亮度变化大SAD会失效。这时候换成NCC试试。第四是图像噪声太大。噪声会干扰匹配尤其是SAD对噪声很敏感。可以先做一次高斯滤波再估计。4.3 边缘块的匹配为什么总是不准边缘块的问题在于它的搜索范围会超出图像边界。如果处理不当就会引入虚假的匹配结果。我的处理方式是裁掉超出边界的部分只计算有效区域。但这样会导致边缘块的SAD计算用的像素数比中间块少数值上不可比。一个改进方法是把SAD除以有效像素数得到平均绝对差这样不同块之间就可比了。另一个方法是给图像做边界填充但填充方式要选好镜像填充比补零好因为补零会引入强边缘。我在实验里对比了裁掉和镜像填充两种方式发现镜像填充在边缘块上的结果更稳定但计算量稍大。如果你对边缘块的结果要求高建议用镜像填充。4.4 快速搜索陷入局部最优怎么破菱形搜索和三步搜索都有可能陷入局部最优尤其是当匹配代价的分布不是单峰的时候。判断方法是把全搜索的结果和快速搜索的结果对比如果差异很大说明快速搜索陷进去了。解决办法有几个。一是增大搜索步长让算法先跳到更远的地方看看。二是用多种快速搜索算法取它们结果中SAD最小的那个。三是先用全局运动估计得到一个粗略的运动量然后以这个运动量为中心做小范围搜索这样能大大降低陷入局部最优的概率。我在实验里用的是第三种方法效果最好。全局运动估计可以用相位相关法速度快而且对全局平移估计得很准。有了这个粗估值后面的块匹配就只需要在粗估值附近微调既快又准。4.5 常见问题速查表问题现象可能原因排查方法解决思路全是零向量搜索范围为零或循环错误打印循环变量检查搜索范围设置和循环逻辑向量场杂乱块太小或纹理太少查看残差图增大块大小或对弱纹理块做平滑边缘块不准边界处理不当对比中间块和边缘块用镜像填充或平均绝对差快速搜索效果差陷入局部最优与全搜索对比用全局估计做粗定位匹配速度太慢用了全搜索统计候选点数量换菱形搜索或三步搜索亮度变化时失效SAD对亮度敏感检查两帧亮度直方图换NCC或先做亮度归一化4.6 几个我踩过的坑第一个坑是图像数据类型。numpy里图像默认是uint8做减法的时候如果出现负数会回绕成很大的正数导致SAD计算完全错误。解决办法是先把图像转成int16或者float32再做运算。这个坑我踩过不止一次每次都是查半天才发现是数据类型的问题。第二个坑是运动向量的坐标系。图像坐标是行在前列在后但画箭头的时候x轴是列y轴是行如果不注意就会画反。我建议在代码里明确注释清楚哪个是行哪个是列画图的时候再转换。第三个坑是搜索范围的奇偶性。如果搜索范围是偶数那候选位置的数量是奇数中心点正好在正中间。如果搜索范围是奇数候选位置数量是偶数中心点就不在正中间了。这个细节在实现快速搜索的时候会影响步长的计算建议统一用偶数搜索范围。第四个坑是残差图的显示。残差有正有负直接显示会很难看。我通常取绝对值再显示或者把残差归一化到0-255。另外残差图通常整体偏暗可以做一个伽马变换增强对比度这样更容易看出问题区域。5. 从实验到工程的延伸思考5.1 运动估计在视频编码里的角色视频编码里的运动估计和实验里做的块匹配核心逻辑是一样的但工程上做了大量优化。比如块的大小不是固定的而是分层的先估大块再估小块。搜索策略也不是单一的而是多种算法组合。匹配准则除了SAD还有SATD就是先做哈达玛变换再算绝对差对频域特性更敏感。如果你对这个方向感兴趣可以去看一下H.264或者H.265的参考软件里运动估计是怎么实现的。代码量很大但核心思想还是实验里这些。把实验做扎实了再看工程代码就不会觉得无从下手。5.2 全局运动估计与视频稳像视频稳像的核心是估计相机的全局运动然后施加反向补偿。全局运动估计不需要像块匹配那样逐块估计而是用整帧图像估计一个统一的运动模型比如平移、旋转、缩放或者仿射变换。常用的方法是特征点匹配加模型拟合。先在两帧里找特征点然后匹配特征点再用RANSAC拟合运动模型。这个方法比块匹配更鲁棒因为它不依赖块内运动一致的假设。但它的计算量也更大而且对特征点少的场景效果不好。我在实验里用块匹配的运动向量做了一个简单的全局运动估计就是把所有块的运动向量做平均。这个方法很粗糙但在相机平移的场景下效果还行。如果要处理旋转和缩放就得用更复杂的模型。5.3 光流法与块匹配的区别光流法估计的是每个像素的运动比块匹配精细得多。但光流法有两个经典假设亮度恒定和小运动。亮度恒定假设两帧之间同一个物体的亮度不变小运动假设相邻帧之间运动很小。这两个假设在实际场景中经常不成立所以光流法需要配合金字塔分层来扩大运动估计范围。块匹配不需要亮度恒定假设因为它比较的是块的相似度不是像素的亮度。但块匹配的输出是稀疏的每个块一个向量不是每个像素一个向量。如果你需要稠密的运动场光流法更合适。如果你只需要知道每个块动了多少块匹配更简单直接。5.4 深度学习时代的运动估计这几年深度学习在运动估计上用得很多比如FlowNet、PWC-Net这些网络直接输入两帧图像输出稠密光流。它们的精度比传统方法高很多尤其是处理大运动和遮挡的时候。但它们的计算量也大而且需要GPU不适合嵌入式场景。传统方法并没有被完全取代。在视频编码这种对速度要求极高的场景里块匹配依然是主流。在手机稳像这种对功耗敏感的场景里轻量级的传统方法也还在用。我的看法是传统方法帮你建立直觉深度学习方法帮你提升精度两者不是替代关系而是互补关系。6. 一些实操心得与建议做这个实验最大的收获不是学会了某个具体的算法而是建立了一种从运动的角度看图像的思维方式。以前看视频就是看视频现在看视频会下意识地想这一帧相对上一帧哪些区域动了动了多少是全局运动还是局部运动。这种思维方式一旦建立起来后面学视频编码、稳像、跟踪都会顺很多。如果你时间有限我建议把重点放在块匹配的完整实现上把全搜索和菱形搜索都跑通把运动向量场和残差图都画出来。快速搜索的细节可以后面再补但块匹配的核心流程一定要亲手写一遍。看别人的代码和自己写一遍理解深度完全不一样。参数调优这块我的经验是不要一上来就追求完美。先用一组保守的参数把流程跑通然后每次只调一个参数观察结果的变化。比如先把块大小固定成16只调搜索范围看看搜索范围对结果的影响。然后再固定搜索范围只调块大小。这样你才能清楚地知道每个参数在起什么作用。最后分享一个小技巧如果你觉得运动向量场太密看不清可以把向量场降采样每隔4个块画一个箭头。或者用颜色编码运动方向用饱和度编码运动幅度这样一张图就能表达四维信息位置x、位置y、方向、幅度。这个可视化方法在论文里很常见自己做实验的时候用起来也很方便。
拿不准这条消息跟你有没有关系?
工种不同、批次不同,要求可能差很多。打电话把你的情况说清楚,我们按信阳、平顶山本地的口径给你捋一遍。