ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

图像矩与Hu矩详解:从数学原理到OpenCV形状识别实践

2026/10/2 13:05:52 拓冰建站 浏览量
图像矩与Hu矩详解:从数学原理到OpenCV形状识别实践 做图像识别这几年我遇到过不少“一眼能看出两个形状像不像却不知道该怎么告诉机器”的需求。最典型的是工厂里的零件分拣圆形垫片、方形法兰、三角支架混在传送带上人眼一扫就知道该往哪个框里放但要让程序也具备这种判断力就得给形状找一个数值化的衡量标准。图像矩image moments就是这套标准里最经典、也最耐用的底子而由它派生出来的 Hu矩更是很多轻量级形状识别项目的首选特征。这篇文章我就把自己从原理到工程踩坑的完整记录整理出来希望能帮你在自己的项目里少走弯路。1. 矩到底是什么从概率公式到图像世界的映射1.1 一个公式看懂矩很多人一听到“矩”这个字就发怵其实它没那么玄。学过概率论的应该记得一个随机变量 X 的 k 阶原点矩定义为 E[X^k]。一阶矩就是均值二阶矩就是方差这些都是矩的退化形式。放到物理里一个平面物体的 pq 阶矩是下面这个积分$$ M_{pq} \iint x^p y^q \rho(x,y), dx, dy $$这个式子描述的是质量密度 ρ 在空间上的分布情况。图像矩做的事特别直白把里面的“质量密度 ρ(x,y)”换成“像素灰度值 I(x,y)”把积分换成求和就得到了数字图像的矩。在二值图像里I(x,y) 只有 0 和 1 两个值公式可以进一步简化成$$ M_{ij} \sum_{x} \sum_{y} x^i y^j I(x,y) $$这里的下标 i、j 表示对横纵坐标分别取几次幂。ij0 时M₀₀ 数的是所有像素值之和对二值图来说它就是白色区域的面积严格说是像素个数。i1, j0 时M₁₀ 是每个非零像素的 x 坐标加起来i0, j1 时M₀₁ 是每个非零像素的 y 坐标加起来。听着可能有点绕但一旦你意识到“图像矩”本质上是在对像素做加权求和后面所有东西都顺了。我拿一个最简单的 3×3 十字形例子算给你看。假设二值图是这样0 1 0 1 1 1 0 1 0坐标从 0 开始那么非零像素点分别是 (1,0)、(0,1)、(1,1)、(2,1)、(1,2)。M₀₀ 是 5就是有 5 个点。M₁₀ 10121 5M₀₁ 01112 5。所以质心坐标就是 (5/5, 5/5) (1,1)正好落在这个十字的中心。是不是很有道理1.2 把“质量”换成“像素”就是图像矩图像矩最巧妙的地方在于它把一个形状的几何信息压缩成了若干个数值。你可以把它理解成“给形状拍了一张身份照”——拍完之后形状长什么样不重要了重要的是照片上的特征数据。这里顺带解释一个概念转换很多资料会把图像的矩分成“原点矩”“中心矩”“归一化中心矩”三类。原点矩就是我们上面写的 Mij计算时以图像左上角为原点。中心矩则先把坐标原点搬到物体质心上去这样物体的位置信息就被“局部化”了平移不会改变中心矩的值。中心矩公式长这样$$ \mu_{pq} \sum_{x} \sum_{y} (x-\bar{x})^p (y-\bar{y})^q I(x,y) $$其中$$ \bar{x} \frac{M_{10}}{M_{00}}, \quad \bar{y} \frac{M_{01}}{M_{00}} $$这就是质心坐标。你可以这样理解先把整个坐标系跟着物体一起挪让坐标系原点钉在物体的质心上然后再计算所有权重。物体在画面里无论怎么平移质心都会跟着动相对坐标不会变所以中心矩天然具有平移不变性。这一步是我们后面构造 Hu 矩的第一块积木。1.3 为什么是“矩”而不是直方图或边缘你可能想问判断两个形状像不像直方图、边缘检测、模板匹配不都能做吗为什么还要引入矩直方图的问题在于它彻底丢掉了空间信息。一张左边是黑右边是白的图和一张左边白右边黑的图直方图完全一样但形状完全不同。边缘检测和轮廓匹配虽然保留了空间结构但要处理的数据量大而且对旋转、缩放很敏感同一个方形零件转个 45 度逐点比对基本就废了。矩作为全局特征用一个低维向量把形状的整体统计特性“压扁”出来计算速度极快还能通过后续的归一化构造出平移、旋转、尺度不变的特征。它适合的场景不是精细比对而是“粗筛”先把一堆形状分成圆形、方形、三角形后面再做细分类。在实际工程里这种粗筛往往是性价比最高的第一步。2. 图像矩的实操拆解面积、质心、方向2.1 先从原点矩到中心矩让坐标跟着物体走原点矩有一个很明显的缺点它把图像左上角当原点同一个物体放在画面左上角和右下角M₁₀ 差出十万八千里。所以实际计算中我们几乎不会直接用原点矩去描述形状而是先求质心再算中心矩。具体操作很简单第一步用 M₁₀/M₀₀ 求出质心第二步用 (x-x̄)、(y-ȳ) 替换 x、y 计算中心矩。这样就实现了“平移不敏感”。不过中心矩仍然受物体尺寸影响同一个形状放大两倍所有中心矩的值都会变。这就要引入归一化。2.2 归一化中心距把尺寸差异抹平归一化中心距的公式是$$ \eta_{pq} \frac{\mu_{pq}}{\mu_{00}^{;1 (pq)/2}} $$很多初学者会死记分母上的指数其实推导一遍就忘不了。假设物体在 x 和 y 方向都放大 s 倍那么新图像中每个非零像素的坐标都变成原来的 s 倍面积变成原来的 s² 倍。中心矩 μpq 中的每个 (x-x̄)^p 贡献一个因子 s^p每个 (y-ȳ)^q 贡献一个因子 s^q整个求和再乘上面积因子 s²所以 μpq 会变成原来的 s^(pq2) 倍。为了让这个值在缩放前后不变就需要除以一个同样能以 s 次幂变化的量。μ₀₀ 是面积缩放后变成 s² 倍那么 μ₀₀ 的 1(pq)/2 次幂正好是 s^(pq2) 倍。除完之后缩放因子完全抵消尺度不变性就成立了。这里指数的含义其实很好记忆分母是 μ₀₀ 的 1 加上“阶数的一半”。pq2 时就是 2 次方pq3 时就是 2.5 次方。2.3 用椭圆拟合理解二阶矩二阶中心矩 μ₂₀、μ₀₂、μ₁₁ 组合起来可以构成一个协方差矩阵$$ C \begin{bmatrix} \mu_{20} \mu_{11} \ \mu_{11} \mu_{02} \end{bmatrix} $$这个矩阵可以看成是“物体的惯性矩矩阵”。它的两个特征值对应椭圆的长轴和短轴特征向量对应椭圆的主轴方向。换句话说任何形状都可以被一个等价的椭圆近似长短轴比例反映了形状的扁长程度主轴方向反映了物体的朝向。从协方差矩阵还能直接计算出主轴方向角$$ \theta \frac{1}{2} \arctan!\left( \frac{2\mu_{11}}{\mu_{20} - \mu_{02}} \right) $$这个公式在工业视觉里非常常用比如芯片定位、工件方向识别都需要先算这个角度。我做过一个 PCB 板定位的项目就是用这个公式求出板子的旋转角再配合仿射变换把板子摆正误差控制在 0.2 度以内。2.4 三阶矩与形状走向二阶矩描述的是“扁平度”三阶矩则开始描述形状的“偏斜”和“不对称性”。比如一个左宽右窄的梯形它的三阶矩会有一个明显的非零值而一个完全对称的矩形某些三阶中心矩理论上为零。Hu 矩里的高阶项正是靠这些信息区分那些二阶矩几乎一样、但几何形态不同的形状。不过这里要提醒一句阶数越高对噪声越敏感。像素的微小抖动在二次幂下影响不大到了三次、四次幂就可能被放大得很明显。所以工程上一般用到三阶矩即 Hu 矩的最高阶对应 pq3就足够了很少会去硬算四阶以上的矩。某些文献里的 Zernike 矩能表达更高阶细节但那是另一套正交矩体系计算量也上去了。3. Hu矩详解七个不变量的来龙去脉3.1 七个公式逐条看1962 年Ming-Kuei Hu 在论文里提出了 7 个由归一化中心距组合出来的不变矩后人直接叫 Hu 矩。这 7 个量对平移、缩放、旋转都不敏感。公式如下$$ h_1 \eta_{20} \eta_{02} $$$$ h_2 (\eta_{20} - \eta_{02})^2 4\eta_{11}^2 $$$$ h_3 (\eta_{30} - 3\eta_{12})^2 (3\eta_{21} - \eta_{03})^2 $$$$ h_4 (\eta_{30} \eta_{12})^2 (\eta_{21} \eta_{03})^2 $$$$ h_5 (\eta_{30} - 3\eta_{12})(\eta_{30} \eta_{12}) \left[ (\eta_{30} \eta_{12})^2 - 3(\eta_{21} \eta_{03})^2 \right] (3\eta_{21} - \eta_{03})(\eta_{21} \eta_{03}) \left[ 3(\eta_{30} \eta_{12})^2 - (\eta_{21} \eta_{03})^2 \right] $$$$ h_6 (\eta_{20} - \eta_{02}) \left[ (\eta_{30} \eta_{12})^2 - (\eta_{21} \eta_{03})^2 \right] 4\eta_{11}(\eta_{30} \eta_{12})(\eta_{21} \eta_{03}) $$$$ h_7 (3\eta_{21} - \eta_{03})(\eta_{30} \eta_{12}) \left[ (\eta_{30} \eta_{12})^2 - 3(\eta_{21} \eta_{03})^2 \right] - (\eta_{30} - 3\eta_{12})(\eta_{21} \eta_{03}) \left[ 3(\eta_{30} \eta_{12})^2 - (\eta_{21} \eta_{03})^2 \right] $$看到这一堆公式先别慌。实际用的时候几乎不会手推OpenCV 一行cv2.HuMoments()就能算完。但我建议你有空还是把这些公式抄一遍因为只有亲手展开过你才会明白它们到底在“凑”什么前两个式子跟二阶矩有关后五个式子基本围绕三阶矩的几组组合在转。3.2 为什么能做到平移、旋转、尺度不变平移不变性来自中心矩尺度不变性来自归一化中心距这两个前面已经解释过。真正难理解的是旋转不变性。我提供一个比较直观的理解方式看 h₁ η₂₀ η₀₂。这其实是协方差矩阵的迹。在线性代数里矩阵的迹在正交变换旋转下是保持不变的。你把物体旋转任意角度协方差矩阵在旋转下做相似变换迹不变。再看 h₂ (η₂₀ − η₀₂)² 4η₁₁²。如果把 η₂₀ − η₀₂ 想象成某个向量的 x 分量2η₁₁ 想象成 y 分量那么旋转 θ 角后这个向量会变成角度 2θ 的旋转向量而向量的模长是不变的。平方和恰好就是模长的平方。类似地η₃₀ η₁₂ 和 η₂₁ η₀₃ 这一组组合在旋转时也遵循类似的“倍角旋转”规律所以用它们的平方和、乘积组合可以构造出旋转不变量。Hu 当年就是用代数方法找到了这些正交不变组合。这个解释虽然没有给出严格推导但足够让你理解这些公式的骨架。你不需要证明每一个恒等式只需要知道这些式子不是拍脑袋凑出来的它们对应的是形状在旋转下不会改变的一组统计特征。3.3 第 7 个矩的特殊之处镜像检测7 个 Hu 矩里前 6 个对于镜像比如把“L”翻成“Γ”也是不变的但第 7 个 h₇ 会在镜像时改变符号。这一点可以用来做镜像形状的区分。不过实际工程中要小心h₇ 的数值往往比较小在噪声影响下符号可能不稳定最好结合其他特征一起判断不要单独依赖它。网上还有一种说法是“h₇ 对旋转具有反对称性”严格说不够准确。它的真正价值在于提供了一个额外的区分维度两个形状所有低阶矩都相同但一个是另一个的镜像h₇ 就能把它们分开。我做过一次左右手轮廓分类就吃过这个符号的亏后面会在踩坑部分细说。4. Hu矩的落地运用从形状匹配到项目调优4.1 一个最实用的场景形状匹配Hu 矩最常见的应用就是形状匹配。给你两个轮廓判断它们属于同一类形状的概率有多大。这种场景在工业零件分拣、交通标志识别、二维码定位、商标查重里都很常见。它的优势非常突出不需要训练集不需要 GPU只需要把每个形状算成 7 个数值然后计算两个 7 维向量之间的距离。相比之下深度学习特征要收集大量样本、训练模型、部署推理在做“二值化后的几何形状粗分类”这种需求时属于杀鸡用牛刀。Hu 矩虽然不如深度特征那么全能但在速度和可解释性上完胜。4.2 实战代码用 OpenCV 计算图像矩与 Hu 矩下面这段代码是我平时最常用的模板直接用 OpenCV 计算图像矩和 Hu 矩。注释我都写清楚了方便你直接抄。import cv2 import numpy as np # 读取灰度图并做二值化 img cv2.imread(part.png, cv2.IMREAD_GRAYSCALE) _, bin_img cv2.threshold(img, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU) # 计算图像矩 # binaryImageTrue 表示把所有非零像素当作 1 处理忽略灰度级差异 M cv2.moments(bin_img, binaryImageTrue) # 0阶矩就是面积像素个数 area M[m00] print(面积:, area) if area 0: # 质心 cx M[m10] / area cy M[m01] / area print(质心: ({:.2f}, {:.2f}).format(cx, cy)) # 二阶中心矩归一化 m20 M[mu20] / area**2 m02 M[mu02] / area**2 m11 M[mu11] / area**2 # 主轴方向角 theta 0.5 * np.arctan2(2 * m11, m20 - m02) print(主轴方向角(弧度): {:.4f}.format(theta)) # 计算Hu矩结果是 (7,1) 的列向量 hu cv2.HuMoments(M).flatten() print(Hu矩原始值:, hu) # 工程上一般取对数压缩后使用 log_hu -np.sign(hu) * np.log10(np.abs(hu) 1e-10) print(log压缩后的Hu矩:, log_hu)代码里最容易踩的坑是binaryImage参数。默认情况下cv2.moments会把每个像素的灰度值当作权重参与计算。如果你传入的是 0~255 的二值图不显式指定binaryImageTrue那么面积、质心、Hu 矩都会被放大 255 倍左右形状特征比例倒是不变但调试时看到数值会很困惑。所以我建议对二值图计算时永远把binaryImage设为True。4.3 matchShapes 的三种度量方式怎么选OpenCV 还提供了一个更高层的函数cv2.matchShapes可以直接比较两个轮廓的相似度底层就是用 Hu 矩做的。# cnt1, cnt2 是 cv2.findContours 得到的轮廓 ret cv2.matchShapes(cnt1, cnt2, cv2.CONTOURS_MATCH_I2, 0) print(相似度:, ret) # 值越小越相似matchShapes 支持三种不同的度量方式很多人文档看十遍也分不清我直接整理成表格方法计算公式特点CONTOURS_MATCH_I1Σ |1/mᵢᵃ − 1/mᵢᵇ|对数值小的分量更敏感容易放大噪声CONTOURS_MATCH_I2Σ |mᵢᵃ − mᵢᵇ|最常用直观误差均匀CONTOURS_MATCH_I3Σ |mᵢᵃ − mᵢᵇ| / |mᵢᵃ|做了归一化对尺度差异更稳健这里的 mᵢ 是内部对 Hu 矩取了对数后的值不是原始 Hu 矩。我自己做项目时默认选CONTOURS_MATCH_I2它在绝大多数情况下表现最稳定。I1 在轮廓质量很差、噪声很大的时候会给出不太合理的大数值I3 则适合两个形状本身尺度差异很大的情况。4.4 提升匹配效果的前处理技巧直接把原图算 Hu 矩效果往往会打折扣。根据我的经验前处理至少占整个项目的 70% 工作量。下面几条是我总结出来的硬经验第一计算矩之前一定要保证对象是完整的、单个的。如果画面里同时出现好几个物体先用连通域分析把它们分开再用面积或周长做一次粗筛过滤掉太小的噪点。第二二值化要选对方法。环境光照稳定的场景固定阈值就行光照不均匀的场景建议用 Otsu 自适应阈值或者局部阈值。阈值选错了形状边缘往内缩一圈或往外扩一圈Hu 矩的值会有明显偏差尤其是高阶矩。第三内部孔洞要不要填充会影响结果。如果零件本身是个“回”字你想识别的是外轮廓形状那应该把内部孔洞填掉再算矩如果你想区分这种“空心”结构就不能填。这个选择没有对错但要保证同一批数据保持一致。第四如果想识别的是“形状”而不是“点集”最好把轮廓画成一个 mask再用 mask 去计算矩。直接对findContours返回的轮廓点调moments虽然也可以但它的积分路径依赖轮廓的顶点顺序某些情况下结果会和 mask 方式差一点统一用 mask 方式更不容易出 bug。5. 常见问题与排查经验5.1 Hu矩数值差异过大怎么办这是新手最容易困惑的问题。原始 Hu 矩中h₁ 可能是 0.01 量级h₇ 可能是 1e-8 量级直接把 7 个值拼成一个特征向量去做欧氏距离h₁ 的差异会完全淹没 h₇ 的差异。解决办法是取对数压缩。OpenCV 文档里给出的转换是$$ m_i -\text{sign}(h_i) \cdot \log_{10}(|h_i|) $$取完对数后不同阶的数值基本落在同一个量级特征距离才有可比性。如果还是觉得某一维贡献过大可以继续做归一化比如每个维度除以训练集里的标准差。5.2 旋转不变性在某些图上失效Hu 矩的旋转不变性是在连续数学域上严格成立的但一旦数字化成像素问题就来了一个物体旋转 30 度后再栅格化边缘会出现锯齿像素面积也会有微小变化。比如一个 5×5 的方块旋转 45 度后栅格化出来的形状会比原来“胖”一点算出的 Hu 矩自然不可能完全一样。我踩过的坑是在仿真数据上验证时旋转 0 度和旋转 45 度算出的 Hu 矩误差在 1% 以内感觉没问题但用真实相机拍同一块工件不同角度后误差放大到了 5% 到 8%。原因是镜头畸变、透视变形、光照变化一起掺了进来。这时候不能只依赖 Hu 矩建议先做一个粗略的主方向对齐或者把形状标准化到一个统一的边界框内再做匹配。说到底Hu 矩的不变性是“数学上的理想承诺”工程上要给它留出容差。5.3 遮挡和噪声的坑Hu 矩是全局特征——它把所有像素都加到了一起。这意味着任何一个局部遮挡或噪声点都会影响最终结果。遮挡面积达到 20% 时Hu 矩可能已经面目全非。所以在真实场景里我用 Hu 矩之前一般先做几何粗筛比较面积、周长、宽高比、圆形度等指标只有这些粗指标都接近才用 Hu 矩做细比较。不要试图让 Hu 矩一步到位解决所有问题。另外图像噪声也要先处理掉尤其是椒盐噪声它们产生的孤立点对高阶矩的影响非常大。5.4 轮廓还是填充区域一句话总结有人问“图像矩到底是算轮廓的矩还是算区域的矩”我的结论是在 OpenCV 里两者都能算但语义不同你必须想清楚自己要描述什么。要描述“外边界形状”用轮廓矩。它对内部纹理不敏感适合工件分类。要描述“整体区域”用二值 mask 的矩。它会包含内部孔洞的贡献适合区分空心和实心结构。同一个“回”字零件外轮廓的 Hu 矩和一个实心方块的 Hu 矩可能非常接近但用 mask 填充内部孔洞后两者的差异立刻拉大。具体用哪种取决于业务上“长什么样才算一类”。5.5 镜像检测的一个反直觉案例前文说到 h₇ 在镜像时会变号。我实际做左右手轮廓识别时本以为光靠 h₇ 的符号就能区分结果试下来发现当图像分辨率不高、轮廓边缘锯齿严重时h₇ 在接近 0 的位置来回抖符号不稳定。后来我把方法改成先把 h₇ 的绝对值计算出来再用 h₆ 或者形状的凸包方向辅助判断左右。也就是说h₇ 作为辅助特征用单独用来下结论风险很大。如果你也用 Hu 矩做镜像检测建议把 h₇ 和其他几何特征组合成一个投票机制别让它一言九鼎。6. 我对Hu矩选型的一些个人体会做视觉项目这么多年我的体会是选特征不要盲目追新要按场景匹配。当你拿到一个“形状分类”的任务先别急着上神经网络先问问自己样本量有多少环境光照是否可控目标物体是否完整如果样本就几百张、光照稳定、目标能被干净地从背景里分割出来Hu矩配合 kNN 或者规则阈值往往效果已经能满足需求而且部署成本极低。我在一个嵌入式设备上做过工件分拣CPU 主频只有 1GHz跑一帧 640×480 的图像提取轮廓加 Hu 矩再分类整个流程只要十几毫秒。同样的问题用深度学习模型就算能在边缘设备上跑模型转换、内存占用、算力调度都是一堆麻烦。胡矩这种经典特征在资源受限的场景里依然非常能打。最后再分享一个小技巧如果后续发现 Hu 矩的区分度不够不要急于换掉整套方案可以先在特征向量里加入面积、周长、宽高比、圆形度这些几何指标组成一个 10 到 12 维的特征向量再喂给一个简单的分类器。我做过好几个项目都是这样从“只有 Hu 矩”升级到“Hu 矩几何粗特征”准确率一下就上来了。这个思路比直接换特征体系要稳得多也快得多。