我们在图像处理的任务中,常常需要对某些形状区域进行描述,比如形状的质心、面积、方向等等。还需要为形状选取合适的特征描述符,用于进行形状的分类任务等等。
图像矩就是用于分析、描述分割后的形状的一种经典方法。
所以,本文会整理下OpenCV是如何定义矩、如何计算矩、如何应用矩的相关知识点,并配上原理公式和代码,方便你用的时候进行查阅。
首先,你需要理解,矩的定义是什么?
在图像处理、计算机视觉和相关领域中,图像矩(image moments)定义为图像像素强度的某个特定加权平均值(矩),或者是此类矩的函数。
图像矩通常用于分析、描述分割后的形状。
通过图像矩可以获取一些形状的简单属性,比如面积、质心和方向信息。
对于像素强度为 I ( x , y ) I ( x , y ) I(x,y) 的标量(灰度)图像,原始图像矩 M i j M_{ij} Mij 由下式计算:
M i j = ∑ x ∑ y x i y i I ( x , y ) M_{ij} = \sum _{x}\sum_{y} {x^i y^i I ( x , y )} Mij=x∑y∑xiyiI(x,y)
图像的中心矩定义为:
μ p q = ∑ x ∑ y ( x − x ˉ ) p ( y − y ˉ ) q I ( x , y ) \mu_{pq}=\sum_x \sum_y (x-\bar{x})^p(y-\bar{y})^q I(x,y) μpq=x∑y∑(x−xˉ)p(y−yˉ)qI(x,y)
通过证明可得:中心矩 μ p q \mu_{pq} μpq 是平移不变的。
你现在了解了矩的计算原理,那么在实际应用中,我们可以利用 OpenCV 的现成函数直接计算得出矩。
OpenCV 采用以下函数计算向量形状或栅格形状的矩,结果返回在结构cv::Moments中。
轮廓的矩以相同的方式定义,但使用格林公式计算。
C++:
#include
Moments cv::moments(InputArray array,bool binaryImage = false)
Python:
import cv2 as cv
cv.moments( array[, binaryImage] ) -> retval
参数
array:栅格图,单通道、8位或2D点阵列(Point2f)
binaryImage:如果为true,图像的非零像素会被当做1,这个参数仅在处理images时使用。
返回
moments.
OpenCV中的 Moments
包含以下矩定义:
// 空间矩 spatial moments
double m00, m10, m01, m20, m11, m02, m30, m21, m12, m03;
// 中心矩 central moments
double mu20, mu11, mu02, mu30, mu21, mu12, mu03;
// 中心归一化矩 central normalized moments
double nu20, nu11, nu02, nu30, nu21, nu12, nu03;
空间矩的计算:
m j i = ∑ x , y ( array ( x , y ) ⋅ x j ⋅ y i ) \texttt{m} _{ji}= \sum _{x,y} \left ( \texttt{array} (x,y) \cdot x^j \cdot y^i \right ) mji=x,y∑(array(x,y)⋅xj⋅yi)
中心矩的计算:
mu j i = ∑ x , y ( array ( x , y ) ⋅ ( x − x ˉ ) j ⋅ ( y − y ˉ ) i ) \texttt{mu} _{ji}= \sum _{x,y} \left ( \texttt{array} (x,y) \cdot (x - \bar{x} )^j \cdot (y - \bar{y} )^i \right ) muji=x,y∑(array(x,y)⋅(x−xˉ)j⋅(y−yˉ)i)
任何阶的中心矩都有平移不变性。
中心归一化矩的计算:
nu j i = mu j i m 00 ( i + j ) / 2 + 1 \texttt{nu} _{ji}= \frac{\texttt{mu}_{ji}}{\texttt{m}_{00}^{(i+j)/2+1}} nuji=m00(i+j)/2+1muji
中心归一化矩还具有尺度不变形。
Hu不变矩 (Hu invariants) 是对平移、缩放、镜像和旋转都不敏感的7个二维不变矩的集合。常被用作于特征描述符。
C++:
void HuMoments(const Moments& m, OutputArray hu)
void HuMoments(const Moments& moments, double hu[7])
Python:
import cv2 as cv
cv.HuMoments(m[, hu]) → hu
7个二维不变矩的定义如下:
h u [ 0 ] = η 20 + η 02 h u [ 1 ] = ( η 20 − η 02 ) 2 + 4 η 11 2 h u [ 2 ] = ( η 30 − 3 η 12 ) 2 + ( 3 η 21 − η 03 ) 2 h u [ 3 ] = ( η 30 + η 12 ) 2 + ( η 21 + η 03 ) 2 h u [ 4 ] = ( η 30 − 3 η 12 ) ( η 30 + η 12 ) [ ( η 30 + η 12 ) 2 − 3 ( η 21 + η 03 ) 2 ] + ( 3 η 21 − η 03 ) ( η 21 + η 03 ) [ 3 ( η 30 + η 12 ) 2 − ( η 21 + η 03 ) 2 ] h u [ 5 ] = ( η 20 − η 02 ) [ ( η 30 + η 12 ) 2 − ( η 21 + η 03 ) 2 ] + 4 η 11 ( η 30 + η 12 ) ( η 21 + η 03 ) h u [ 6 ] = ( 3 η 21 − η 03 ) ( η 21 + η 03 ) [ 3 ( η 30 + η 12 ) 2 − ( η 21 + η 03 ) 2 ] − ( η 30 − 3 η 12 ) ( η 21 + η 03 ) [ 3 ( η 30 + η 12 ) 2 − ( η 21 + η 03 ) 2 ] \begin{array}{l} hu[0]= \eta _{20}+ \eta _{02} \\ hu[1]=( \eta _{20}- \eta _{02})^{2}+4 \eta _{11}^{2} \\ hu[2]=( \eta _{30}-3 \eta _{12})^{2}+ (3 \eta _{21}- \eta _{03})^{2} \\ hu[3]=( \eta _{30}+ \eta _{12})^{2}+ ( \eta _{21}+ \eta _{03})^{2} \\ hu[4]=( \eta _{30}-3 \eta _{12})( \eta _{30}+ \eta _{12})[( \eta _{30}+ \eta _{12})^{2}-3( \eta _{21}+ \eta _{03})^{2}]+(3 \eta _{21}- \eta _{03})( \eta _{21}+ \eta _{03})[3( \eta _{30}+ \eta _{12})^{2}-( \eta _{21}+ \eta _{03})^{2}] \\ hu[5]=( \eta _{20}- \eta _{02})[( \eta _{30}+ \eta _{12})^{2}- ( \eta _{21}+ \eta _{03})^{2}]+4 \eta _{11}( \eta _{30}+ \eta _{12})( \eta _{21}+ \eta _{03}) \\ hu[6]=(3 \eta _{21}- \eta _{03})( \eta _{21}+ \eta _{03})[3( \eta _{30}+ \eta _{12})^{2}-( \eta _{21}+ \eta _{03})^{2}]-( \eta _{30}-3 \eta _{12})( \eta _{21}+ \eta _{03})[3( \eta _{30}+ \eta _{12})^{2}-( \eta _{21}+ \eta _{03})^{2}] \\ \end{array} hu[0]=η20+η02hu[1]=(η20−η02)2+4η112hu[2]=(η30−3η12)2+(3η21−η03)2hu[3]=(η30+η12)2+(η21+η03)2hu[4]=(η30−3η12)(η30+η12)[(η30+η12)2−3(η21+η03)2]+(3η21−η03)(η21+η03)[3(η30+η12)2−(η21+η03)2]hu[5]=(η20−η02)[(η30+η12)2−(η21+η03)2]+4η11(η30+η12)(η21+η03)hu[6]=(3η21−η03)(η21+η03)[3(η30+η12)2−(η21+η03)2]−(η30−3η12)(η21+η03)[3(η30+η12)2−(η21+η03)2]
其中, η j i η_{ji} ηji 表示 Moments:: n u j i nu_{ji} nuji。
h u [ 0 ] hu[0] hu[0] 类似于图像质心周围的惯性矩,其中像素的强度类似于物理密度。
h u [ 0 ] hu[0] hu[0] 到 h u [ 5 ] hu[5] hu[5] 是反射对称的,即如果图像变成镜像,值也不会改变。
h u [ 6 ] hu[6] hu[6] 是反射反对称的,即在反射下会改变符号。
比较常用的是质心、面积、方向:
(1) 质心坐标:
x ˉ = m 10 m 00 , y ˉ = m 01 m 00 \bar{x} = \frac{\texttt{m}_{10}}{\texttt{m}_{00}} , \; \bar{y} = \frac{\texttt{m}_{01}}{\texttt{m}_{00}} xˉ=m00m10,yˉ=m00m01
(2) 面积:
a r e a = m 00 area = \texttt{m}_{00} area=m00
(3)方向:
图像的方向信息可以通过二阶中心矩构造协方差矩阵获得
μ 20 ′ = μ 20 / μ 00 = M 20 / M 00 − x ˉ 2 \mu_{20}'=\mu_{20}/\mu_{00}=M_{20}/M_{00}-\bar{x}^2 μ20′=μ20/μ00=M20/M00−xˉ2
μ 02 ′ = μ 02 / μ 00 = M 02 / M 00 − y ˉ 2 \mu_{02}'=\mu_{02}/\mu_{00}=M_{02}/M_{00}-\bar{y}^2 μ02′=μ02/μ00=M02/M00−yˉ2
μ 11 ′ = μ 11 / μ 00 = M 11 / M 00 − x ˉ y ˉ \mu_{11}'=\mu_{11}/\mu_{00}=M_{11}/M_{00}-\bar{x}\bar{y} μ11′=μ11/μ00=M11/M00−xˉyˉ
图像的协方差矩阵
c o v [ I ( x , y ) ] = [ μ 20 ′ μ 11 ′ μ 11 ′ μ 02 ′ ] cov[I(x,y)]=\begin{bmatrix} \mu_{20}' & \mu_{11}' \\ \mu_{11}'& \mu_{02}' \end{bmatrix} cov[I(x,y)]=[μ20′μ11′μ11′μ02′]
该矩阵的特征向量对应于图像强度的长轴和短轴。
角度可以由下式给出:
θ = 1 2 a r c t a n ( 2 μ 11 ′ μ 20 ′ − μ 02 ′ ) \theta=\frac{1}{2} arctan(\frac{2\mu_{11}'}{\mu_{20}'-\mu_{02}'}) θ=21arctan(μ20′−μ02′2μ11′)
特征值大小的相对差异表示图像的偏心率,或图像的拉长程度,偏心度可以由下式给出:
1 − λ 2 λ 1 \sqrt{1-\frac{\lambda_2}{\lambda_1}} 1−λ1λ2
获取图像中某些区域的轮廓,并计算每个轮廓形状的面积和质心。
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
import math
# 获取亮度高于阈值的区域
select = (srcMat >= thresh)
select_img = select.astype(np.uint8)
# 膨胀,填补孔洞
kernel = cv.getStructuringElement(cv.MORPH_RECT, (2, 2))
dilate_img = cv.dilate(select_img, kernel)
# 提取轮廓
_, contours, hierarchy = cv.findContours(dilate_img.copy(), cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
# 准备可视化
vis = dilate_img.copy()*150
vis = cv.merge([vis,vis,vis])
title = ""
# 遍历轮廓
for cnt in contours:
# 跳过点特别少的轮廓
if cnt.shape[0] < 8:
continue
# 质心
M = cv.moments(cnt)
cx = int(M['m10'] / M['m00'])
cy = int(M['m01'] / M['m00'])
# 面积
area = M['m00']
# 方向
mu20 = M['m20']/M['m00']-cx**2
mu02 = M['m02']/M['m00']-cy**2
mu11 = M['m11']/M['m00']-cx*cy
angle = 1/2*math.atan(2*mu11/(mu20-mu02))
degree = math.degrees(angle)
cv.circle(vis, (cx,cy), 1, (0, 255, 0), -1)
print(f"area={area},center=({cx},{cy})")
plt.title(title)
plt.imshow(vis)
https://docs.opencv.org/3.4/d8/d23/classcv_1_1Moments.html#a8b1b4917d1123abc3a3c16b007a7319b
Hu. Visual Pattern Recognition by Moment Invariants, IRE Transactions on Information Theory, 8:2, pp. 179-187, 1962.
http://en.wikipedia.org/wiki/Image_moment