OpenCV 中的矩(moments)和 Hu不变矩(HuMoments)
图像矩通常用于分析、描述分割后的形状。
引言
我们在图像处理的任务中,常常需要对某些形状区域进行描述,比如形状的质心、面积、方向等等。还需要为形状选取合适的特征描述符,用于进行形状的分类任务等等。
图像矩就是用于分析、描述分割后的形状的一种经典方法。
所以,本文会整理下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中的矩(moments)
OpenCV 采用以下函数计算向量形状或栅格形状的矩,结果返回在结构cv::Moments中。
轮廓的矩以相同的方式定义,但使用格林公式计算。
C++:
#include <opencv2/imgproc.hpp>
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
中心归一化矩还具有尺度不变形。
OpenCV中的Hu不变矩(HuMoments)
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
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)