资讯动态

Halcon光流分析实战:optical_flow_bicycle例程详解与参数调优

发布时间:2026/10/26 5:11:58 来源:尧图企业网站定制
光流分析这个词第一次听的人容易联想到流体力学或者某种光学实验但在机器视觉圈子里它其实是一个非常接地气的运动估计工具。Halcon 自带的optical_flow_bicycle.hdev这个例子名字里带了个 bicycle讲的就是怎么用光流法去分析一段骑行画面里的运动信息。我最早接触这个例程是在做一个传送带物料跟踪的项目当时试过帧差法、背景减除效果都不太理想后来翻到光流这块才把问题解决掉。这个例程虽然短但它把光流从概念到落地的整条链路都串起来了特别适合那些想搞明白“像素怎么变成运动矢量”的人。不管你是刚上手 Halcon 的新人还是已经写过一些 blob 分析、模板匹配、想做运动分析的老手这个例子都值得花时间拆一遍。下面我就按自己复现和改造这个例程的实际过程把里面的设计思路、算子细节、参数调优和踩过的坑完整讲一遍。1. 光流分析的整体设计与方案选型1.1 为什么这个场景要选光流而不是帧差或模板匹配先把这个例程要解决的问题说清楚。optical_flow_bicycle.hdev处理的是一段连续图像序列画面里有一辆自行车在运动目标是估计出画面中各个像素点的运动方向和速度。这类需求在工业现场非常常见比如传送带上的物料位移测量、振动台的微小形变监测、运动机构的轨迹跟踪。摆在面前的技术路线其实有好几条帧差法、背景减除、模板匹配、光流法每条路都有自己的适用边界。帧差法是最容易想到的两帧相减非零区域就是运动区域。它的优点是计算量小、实现简单但缺点也很致命它只能告诉你“这里动了”没法告诉你“往哪个方向动、动得多快”。而且帧差法对运动速度很敏感物体动得太快两帧之间没有重叠差出来的是一片空心轮廓动得太慢两帧几乎一样差出来接近零。背景减除需要先建背景模型对光照变化和背景扰动很敏感自行车这种非刚性目标加上复杂背景建模成本很高。模板匹配倒是能给出位移但它本质上是找整块区域的刚性对应自行车骑行过程中轮子在转、腿在蹬形状一直在变模板匹配的相似度会掉得很厉害。光流法的思路完全不一样。它不关心“这块区域是不是同一个物体”而是逐像素去估计瞬时速度矢量。自行车轮子转、腿在蹬每个像素点的运动方向都不一样光流恰好能把这种非刚性、密集的运动场刻画出来。这就是选型的核心逻辑当目标运动是非刚性的、需要逐像素速度信息、且帧间位移较小的时候光流是比帧差和模板匹配更合适的选择。这个例程选光流不是因为它高级而是因为它的输出形态密集矢量场和问题的需求非刚性运动估计对上了。1.2 稠密光流与稀疏光流的取舍光流本身还分两大流派稠密光流和稀疏光流。稠密光流给画面里每一个像素都算一个速度矢量输出是一张和原图同尺寸的矢量场稀疏光流只跟踪一批特征点比如角点输出是若干条轨迹。这个例程走的是稠密光流路线用的是 Halcon 的optical_flow_mg算子。为什么选稠密因为自行车这个目标上可跟踪的稳定角点其实不多轮辐在转、背景纹理也一般稀疏光流很容易跟丢。稠密光流虽然计算量大但它不依赖特征点检测的质量对弱纹理区域也能给出估计鲁棒性更好。代价就是速度慢所以这个例程在实现上做了不少工程上的折中比如用多尺度金字塔来加速收敛这个后面会细讲。你在实际项目里如果对实时性要求高可以先降分辨率再算光流或者只在 ROI 里算没必要整幅图全铺开。1.3 例程的整体流程骨架把例程从头到尾捋一遍它的流程其实很清晰分成四段读图序列、预处理、光流计算、结果可视化。读图部分用read_image配合循环或者open_framegrabber拿连续帧预处理主要是灰度转换和必要的平滑核心是optical_flow_mg算矢量场最后用vector_field_to_real把矢量场拆成幅值和方向两张实值图再用dev_display叠加显示。这个骨架看起来简单但每一段都有讲究。比如预处理为什么只做灰度转换不做锐化因为光流算法依赖的是亮度守恒假设锐化会引入高频噪声反而破坏这个假设。再比如结果可视化为什么要把幅值和方向分开因为矢量场直接画箭头在密集区域会糊成一团拆成幅值图亮度表示速度大小和方向图色相表示方向才能看清。这些设计选择背后都有明确的理由不是随手写的。2. 核心算子与关键细节解析2.1 optical_flow_mg 算子的参数到底怎么理解整个例程的核心就一个算子optical_flow_mg。它的签名大致是这样输入前后两帧图像、输出一个矢量场 VectorField。但真正决定效果的是一堆可选参数这些参数在例程里可能只是几个数字但每个数字背后都对应一个算法行为。我按自己的理解把它们拆开讲。Sigma是高斯平滑的尺度作用是在算梯度之前先对图像做平滑抑制噪声。这个值给大了细小运动会被抹掉给小了噪声会被当成运动。经验上从 1.0 到 2.0 之间试纹理丰富的场景可以小一点噪声大的场景要大一点。Rho是平滑项权重光流算法本质上是在解一个能量泛函数据项保证亮度守恒平滑项保证相邻像素的运动矢量不要跳变太剧烈。Rho 越大结果越平滑但细节越少Rho 越小细节多但对噪声敏感。例程里这个值通常设得中等偏大因为自行车场景里背景占大头需要平滑来压制背景的伪运动。Sigma和Rho这两个参数是最需要调的剩下的MinLevel、MaxLevel、NumLevels控制的是多尺度金字塔。光流在大位移下容易失败金字塔的思路是先在最粗的尺度上估计大运动再逐级细化到原始分辨率。NumLevels就是金字塔层数层数越多能处理的最大位移越大但计算量也越大。例程里一般设 3 到 4 层对应能处理十几到几十像素的帧间位移。如果你的场景运动很快帧率又低就得加层数或者提高帧率这是硬约束。2.2 灰度转换与预处理的隐藏门道例程里读进来的图如果是彩色的第一步肯定是rgb1_to_gray。这一步看起来平淡无奇但有个细节很多人忽略不同颜色通道对运动的敏感度不一样。自行车场景里如果车身颜色和背景在某个通道上对比度高转灰度后这个对比度会被平均掉。我实测过某些场景下用单一通道比如只取绿色通道算光流效果反而比标准灰度好因为那个通道的纹理更清晰。当然这是进阶玩法例程用标准灰度是为了通用性。预处理里还有一个容易被跳过的东西图像尺寸。光流计算量跟像素数成正比如果原图是两三千像素宽直接算会非常慢。例程里的图尺寸适中所以没做缩放。你在自己项目里如果图很大建议先zoom_image_factor降到一半或四分之一再算光流算完把矢量场按比例放大回去。这个操作对精度的影响远小于对速度的提升是性价比很高的优化。注意降分辨率再算光流时矢量场的坐标和幅值都要按缩放因子换算幅值要乘以缩放因子的倒数否则速度估计会系统性偏小。这个换算很容易漏漏了之后数据全错。2.3 矢量场的表示与拆分逻辑optical_flow_mg输出的 VectorField 是一个特殊的数据结构Halcon 里不能直接当图像显示。例程用vector_field_to_real把它拆成两个 Real 图像一个存幅值也就是速度大小一个存方向角度。这个拆分不是为了好看而是因为后续处理需要的是标量场。幅值图可以直接做阈值分割找出运动剧烈的区域方向图可以做直方图统计判断整体运动趋势。比如传送带场景方向图应该集中在某一个角度附近如果方向分布很散说明有干扰或者算法没收敛。这个拆分思路在工程上非常实用等于把矢量问题降维成两个标量问题分别处理。我后来做振动监测就是靠幅值图的时间序列变化来判断异常方向图基本不看因为振动方向是固定的。2.4 多尺度金字塔的收敛机制前面提到NumLevels这里展开说一下金字塔为什么能解决大位移问题。光流的基本假设是帧间位移小泰勒展开的一阶近似才成立。位移一大近似就崩了。金字塔的做法是把图像逐级降采样在最顶层最小图上原本几十像素的位移被缩小到几像素一阶近似重新成立算出一个粗略的光流然后把这个结果上采样作为下一层的初值逐级修正。这个过程有点像先看地图找大方向再放大看细节。层数选多少取决于你的最大帧间位移。粗略的估算方法是如果原始分辨率下最大位移是 D 像素金字塔有 L 层那么最顶层看到的位移大约是 D 除以 2 的 L 次方。要让这个值落在算法能处理的范围内通常几个像素就能反推需要的层数。例程里自行车运动不算剧烈3 到 4 层足够。你要是拍高速旋转的飞轮层数得往上加或者干脆提高采集帧率后者往往更省事。3. 完整实操过程与关键环节实现3.1 环境准备与图像序列的获取先把运行环境说清楚。Halcon 版本建议 18.11 以上optical_flow_mg这个算子在老版本里行为有差异新版本对多尺度的支持更稳。例程自带的图像序列在安装目录的examples/images/下面文件名类似bicycle_01到bicycle_10这样一串。如果你手上没有这组图用自己拍的连续帧也完全可以只要保证帧间位移不要太大。读图序列有两种写法。一种是预先知道文件名规律用循环拼字符串for Index : 1 to 10 by 1 FileName : bicycle_ Index$02d read_image (Image, FileName) * 后续处理 endfor另一种是用list_files拿到目录下所有文件再遍历适合文件名不规律的情况。我一般用后者因为实际项目里图像往往是从相机流里存的命名不一定规整。读的时候注意图像格式Halcon 对 png、jpg、tiff 都支持但 jpg 有压缩伪影光流对块效应比较敏感能用无损格式就别用 jpg。3.2 逐帧光流计算的代码骨架核心循环的逻辑是保存上一帧读当前帧两帧一起送进optical_flow_mg得到矢量场然后拆分显示。代码骨架大概是这样read_image (Image1, bicycle_01) rgb1_to_gray (Image1, Gray1) for Index : 2 to 10 by 1 read_image (Image2, bicycle_ Index$02d) rgb1_to_gray (Image2, Gray2) optical_flow_mg (Gray1, Gray2, VectorField, default, default, \ Sigma, Rho, 3, 1, 5) vector_field_to_real (VectorField, Magnitude, Direction) * 显示与统计 copy_obj (Gray2, Gray1, 1, -1) endfor这里optical_flow_mg的参数顺序要特别注意不同 Halcon 版本参数位置可能有微调写之前查一下当前版本的算子文档。default那两个字符串是算法变体和插值方式的占位一般不用改。后面几个数字分别是 Sigma、Rho、金字塔层数、最小层、最大层具体值要按场景调例程给的是通用起点。3.3 参数调优的实操记录我拿例程的图跑过几组参数记录一下对比。Sigma 从 1.0 调到 2.0幅值图的噪声明显减少但自行车轮辐的细节开始糊Rho 从 10 调到 50背景区域的伪运动被压下去了但车身边缘的运动矢量变得过于平滑速度峰值被削低。最后我用的组合是 Sigma1.5、Rho20、金字塔 4 层这个组合在细节和稳定性之间比较平衡。调参的时候有个技巧先固定 Rho 调 Sigma再固定 Sigma 调 Rho。两个一起调你会搞不清是谁在起作用。判断标准看幅值图理想状态下运动目标的幅值应该连成一片背景区域接近零。如果背景一片雪花说明 Sigma 太小或 Rho 太小如果目标糊成一团没有内部结构说明 Sigma 太大或 Rho 太大。这个判断方法比看数字直观得多。3.4 结果可视化与定量统计可视化部分例程用了dev_display叠加矢量场但矢量场直接画箭头在密集区域会糊。我的做法是分两张图看幅值图用伪彩色显示速度大的地方亮速度小的地方暗方向图用色相环映射不同方向不同颜色。Halcon 里可以用scale_image把幅值拉伸到 0 到 255再用dev_display配合 colormap 显示。定量统计更有价值。对幅值图做threshold找出运动区域用region_features算面积和重心重心的帧间位移就是目标的整体运动速度。这个数据可以直接喂给后续的跟踪或控制逻辑。方向图可以做gray_histo看主方向落在哪个角度区间判断运动是否一致。我在传送带项目里就是靠重心位移做速度闭环靠方向直方图的集中度做异常检测方向一散就说明有物料翻滚或者卡料。4. 常见问题与排查技巧实录4.1 光流结果全是噪声或全为零这是最常见的两个极端。全噪声通常是 Sigma 和 Rho 都设太小算法把噪声当成了运动。解决办法是把 Sigma 提到 2.0 以上Rho 提到 30 以上先看能不能压住。全为零则相反可能是两帧图像完全一样读图逻辑写错了重复读了同一帧也可能是 Sigma 太大把真实运动抹平了。排查时先打印两帧的均值差差接近零就是读图问题差正常就是参数问题。还有一种隐蔽情况图像是彩色的但没转灰度就送进算子某些版本会静默失败或者给出无意义结果。养成习惯送进optical_flow_mg之前确认是单通道图用count_channels检查一下一秒钟的事能省很多排查时间。4.2 大位移场景下光流跟丢自行车骑得快的时候轮子边缘的帧间位移可能超过金字塔能处理的范围表现为轮子区域的光流方向乱跳或者幅值异常大。解决办法有三条加金字塔层数、提高采集帧率、降低分辨率。三条里提高帧率最治本因为光流的根本假设就是帧间位移小帧率上去了位移自然小。加层数是软件层面的补救层数太多会让最顶层的图小到没有纹理反而算不准。降分辨率是折中牺牲精度换稳定。我踩过一个坑加了层数但没改MinLevel和MaxLevel结果金字塔实际没生效。这两个参数要和NumLevels配合MaxLevel要大于等于NumLevels否则层数被截断。这个细节文档里写得不显眼但错了之后现象很迷惑光流看起来在算就是大位移跟不住。4.3 矢量场坐标与图像坐标对不上vector_field_to_real拆出来的幅值图和方向图尺寸和原图一致但如果你之前做了降分辨率矢量场的坐标是缩放后的坐标系。直接拿去做区域分析位置会偏。换算方法是把区域坐标乘以缩放因子或者把矢量场上采样回原尺寸再分析。我建议后者虽然多一步vector_field_to_real之后的zoom_image_factor但逻辑清晰不容易错。4.4 常见问题速查表现象可能原因排查与解决幅值图全是雪花噪声Sigma、Rho 过小Sigma 提到 2.0Rho 提到 30 以上幅值图全黑无运动读图重复同一帧或 Sigma 过大检查帧间均值差调小 Sigma大位移区域方向乱跳金字塔层数不足或未生效加 NumLevels确认 MaxLevel 匹配运动区域位置偏移降分辨率后坐标未换算矢量场上采样回原尺寸再分析算子报错或结果异常输入非单通道图用 count_channels 确认灰度图背景区域伪运动多Rho 偏小平滑不足提高 Rho或先做背景抑制提示调参时每次只动一个参数改完立刻看幅值图的变化不要一次改好几个再一起看否则出了问题根本定位不到是哪个参数引起的。5. 从例程到项目的改造经验5.1 把逐帧光流改成 ROI 内计算例程是整幅图算光流实际项目里往往只关心某个区域。整幅图算既慢又容易被无关背景干扰。改造方法是先用gen_rectangle1或者reduce_domain圈出 ROI把两帧都裁到 ROI 再送进算子。注意 ROI 要留一点余量因为光流在区域边界处的估计不准边界效应会往内污染几个像素。我一般把 ROI 往外扩 10 到 20 像素算完再裁回目标区域。这个改造对速度的提升非常明显ROI 占全图四分之一的话计算时间大致也降到四分之一。而且背景干扰少了参数可以调得更激进细节保留更好。这是性价比最高的一处改造建议所有实际项目都做。5.2 光流结果与 blob 分析联动光流给的是运动信息blob 分析给的是形状和位置信息两者结合能做出很实用的东西。比如先对幅值图做阈值分割得到运动区域再对原图做 blob 分析得到目标轮廓两者求交集就能把“正在运动的目标”从“静止的目标”里筛出来。自行车场景里静止的树和运动的自行车在灰度图上可能混在一起但幅值图一分割就分开了。联动的时候注意阈值的选择。幅值图的阈值不能固定因为不同场景速度差异大。我的做法是用min_max_gray先看幅值范围取最大值的 20% 到 30% 作为阈值起点再微调。固定阈值在换场景时必然翻车这个坑我踩过不止一次。5.3 时序平滑与异常判定单帧光流的结果有抖动直接拿去做控制会抖得厉害。加一个简单的时序平滑比如对连续几帧的幅值图做平均或者对重心位移做滑动平均稳定性提升很明显。滑动窗口长度取 3 到 5 帧太长了响应迟钝太短了平滑不够。异常判定可以基于方向一致性。正常运动时方向直方图集中出现异常比如目标翻滚、遮挡时方向会突然发散。设定一个集中度指标比如主方向区间占比低于某个阈值就报警。这个思路在无人值守的监测场景里很管用比单纯看幅值阈值更抗干扰。6. 光流分析的适用边界与选型建议6.1 什么场景适合用这个例程的思路光流分析最适合的场景有几个共同特征目标运动是非刚性的或者形状在变、需要逐像素的速度信息、帧间位移不大、对实时性要求不是极端苛刻。传送带物料跟踪、振动监测、流体表面运动分析、运动机构轨迹估计都属于这一类。这些场景里帧差和模板匹配要么给不出方向要么对形变不鲁棒光流的密集矢量场正好补上这个缺口。反过来如果你的需求只是判断“有没有动”帧差法又快又简单没必要上光流。如果目标是刚性的、只需要整体位移模板匹配或者相关性跟踪更直接。如果实时性要求是毫秒级稠密光流基本没戏得考虑稀疏光流或者专用硬件。选型的时候先问自己三个问题要不要方向信息、目标是不是非刚性、帧间位移大不大三个答案能帮你快速排除不合适的路线。6.2 计算成本与实时性的现实考量稠密光流的计算量摆在那里optical_flow_mg在普通工控机上跑 640x480 的图单帧大概几十毫秒量级具体看参数和硬件。要做到实时比如 30 帧每秒要么降分辨率要么缩 ROI要么上更强的硬件。我的经验是 ROI 加降分辨率组合拳能把单帧压到十毫秒以内勉强够用。如果还不够就得考虑算法层面的优化比如只在关键区域算光流其他区域用帧差粗筛。这里有个容易忽略的点光流计算的时间和金字塔层数、迭代次数强相关而这些参数又跟场景绑定。调参的时候要把时间也纳入考量不能只看效果不看耗时。我见过为了追求效果把层数和迭代拉满结果单帧几百毫秒整个系统直接不可用的案例。效果和速度的平衡点得在真实硬件上实测出来纸上算不准。6.3 从例程学到的工程思维这个例程最值得学的其实不是某个算子怎么用而是它把复杂问题拆解的方式。光流本身是个数学上很重的东西但例程把它包装成“读图、算、拆、显示”四步每一步都有明确的输入输出中间不藏黑箱。这种拆解方式让调试变得可行哪一步出问题就查哪一步不用面对一个整体黑盒干瞪眼。另外例程对可视化的重视也值得借鉴。矢量场这种高维数据直接看数字根本看不出对错必须转成图看。幅值图和方向图的拆分本质上是一种降维可视化把四维信息x 方向速度、y 方向速度、位置、时间压到二维图上人眼才能判断。做视觉项目可视化做得好调试效率能翻倍这个体会我越来越深。最后分享一个我在实际项目里养成的小习惯每次调完光流参数把当前参数和对应的幅值图截图存一份标注上场景条件。时间长了就攒出一套自己的参数经验库下次遇到类似场景直接查表起步比从零试快得多。这个习惯看起来笨但真的省时间。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑