M20=∑x∑yx2⋅I(x,y)
M02=∑x∑yy2⋅I(x,y)
M11=∑x∑yx⋅y⋅I(x,y)
M20和M02分别表示图像围绕通过重心的垂直和水平轴线的惯性矩。
M30和M03可以度量图像对于垂直和水平轴线的对称性等。
物体形状的方向:
θ=arctan(b,(a−c))2=arctan(b/(a−c))2,θ∈[−90。,90。]
其中:
根据一阶矩的质心C=(M10M00,M01M00)有
a=M20M00−C20
b=2(M11M00−C0C1)
c=M02M00−C21
2、质心原理
在图像处理中,一阶矩与形状有关,二阶矩显示曲线围绕直线平均值到扩展程度,三阶矩是关于平均值到对称性到测量.由二阶矩和三阶矩可以导出一组共7个不变矩.而不变矩是图像到统计特性,满足平移,伸缩,旋转均不变到不变性.
3、中心距函数moments()
Calculates all of the moments up to the third order of a polygon or rasterized shape.
C++
: Moments moments(InputArray array, bool binaryImage=false )
Python
: cv2.moments(array[, binaryImage]) → retval
C
: void cvMoments(const CvArr* arr, CvMoments* moments, int binary=0 )
Python
: cv.Moments(arr, binary=0) → moments
Parameters:
array
– Raster image (single-channel, 8-bit or floating-point 2D array) or an array ( 1 \times N or N \times 1 ) of 2D points (Point or Point2f ).
binaryImage
– If it is true, all non-zero image pixels are treated as 1’s. The parameter is used for images only.
moments
– Output moments.
class Moments
{public: Moments();
Moments(double m00, double m10, double m01, double m20, double m11, double m02, double m30, double m21, double m12, double m03 );
Moments( const CvMoments& moments ); operator CvMoments() const; // 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;
}
4、中心矩示例代码
4.1 C++版代码
#include <QCoreApplication>#include <opencv2/opencv.hpp>// Qt Creator 4.2.0(Based on Qt 5.7.1)// OpenCV 2.4.13using namespace cv;using namespace std;#define name1 "原图"#define name2 "效果图"cv::Mat img, gray;int nThresh = 100;int nMaxThresh = 255;
cv::RNG rng(12345); //产生一个随机数cv::Mat cannyImg;std::vector<std::vector<cv::Point>> contours;std::vector<cv::Vec4i> hierarchy;//void on_ThreshChange( int, void* ){// //canny边缘检测// cv::Canny( gray, cannyImg, nThresh, nThresh*2, 3 );// //找轮廓// cv::findContours( cannyImg, contours, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE, cv::Point( 0, 0 ) );// //计算矩// std::vector<cv::Moments> mu( contours.size() );// for(unsigned int i = 0; i < contours.size(); i++){// mu[i] = cv::moments( contours[i], false);// }// //计算中心矩// std::vector<cv::Point2f> mc( contours.size() );// for( unsigned int i = 0; i < contours.size(); i++ ){// mc[i] = cv::Point2f( static_cast<float>(mu[i].m10 / mu[i].m00), static_cast<float>(mu[i].m01 / mu[i].m00));// }// //画轮廓// cv::Mat drawing = cv::Mat::zeros( cannyImg.size(), CV_8UC3);// for( unsigned int i = 0; i < contours.size(); i++ ){// cv::Scalar color = cv::Scalar( rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255) );// cv::drawContours( drawing, contours, i, color, 2, 8, hierarchy, 0, cv::Point() );// cv::circle( drawing, mc[i], 4, color, -1, 8, 0 );// }// cv::namedWindow( name2, cv::WINDOW_NORMAL);// cv::imshow( name2, drawing );// std::cout << "输出内容: 面积和轮廓长度 \n" << std::endl;// for(unsigned int i = 0; i < contours.size(); i++ ){// std::cout << ">通过m00计算出轮廓[" << i << "]的面积:(M_00) =" << mu[i].m00 << "\n OpenCV 函数计算出的面积 = " << cv::contourArea(contours[i]) << "长度:" << cv::arcLength( contours[i], true) << "\n\n" << std::endl;// cv::Scalar color = cv::Scalar( rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255) );// cv::drawContours(drawing, contours, i, color, 2, 8, hierarchy, 0, cv::Point() );// cv::circle( drawing, mc[i], 4, color, -1, 8, 0 );// }//}int main(){
img = cv::imread( "/home/jason/jason2/photo/1.jpg" );
cv::cvtColor( img, gray, cv::COLOR_BGR2GRAY );
cv::blur( gray, gray, cv::Size(3, 3) );
cv::namedWindow( name1, cv::WINDOW_NORMAL );
cv::imshow( name1, img );// cv::createTrackbar( "阈值", name1, &nThresh, nMaxThresh, on_ThreshChange );// on_ThreshChange( 0, 0 );
//canny边缘检测
cv::Canny( gray, cannyImg, nThresh, nThresh*2, 3 ); //找轮廓
cv::findContours( cannyImg, contours, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE, cv::Point( 0, 0 ) ); //计算矩
std::vector<cv::Moments> mu( contours.size() ); for(unsigned int i = 0; i < contours.size(); i++){
mu[i] = cv::moments( contours[i], false);
} //计算中心矩
std::vector<cv::Point2f> mc( contours.size() ); for( unsigned int i = 0; i < contours.size(); i++ ){
mc[i] = cv::Point2f( static_cast<float>(mu[i].m10 / mu[i].m00), static_cast<float>(mu[i].m01 / mu[i].m00));
} //画轮廓
cv::Mat drawing = cv::Mat::zeros( cannyImg.size(), CV_8UC3); for( unsigned int i = 0; i < contours.size(); i++ ){
cv::Scalar color = cv::Scalar( rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255) );
cv::drawContours( drawing, contours, i, color, 2, 8, hierarchy, 0, cv::Point() );
cv::circle( drawing, mc[i], 4, color, -1, 8, 0 );
}
cv::namedWindow( name2, cv::WINDOW_NORMAL);
cv::imshow( name2, drawing ); std::cout << "输出内容: 面积和轮廓长度 \n" << std::endl; for(unsigned int i = 0; i < contours.size(); i++ ){ std::cout << ">通过m00计算出轮廓[" << i << "]的面积:(M_00) =" << mu[i].m00 << "\n OpenCV 函数计算出的面积 = " << cv::contourArea(contours[i]) << "长度:" << cv::arcLength( contours[i], true) << "\n\n" << std::endl;
cv::Scalar color = cv::Scalar( rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255) );
cv::drawContours(drawing, contours, i, color, 2, 8, hierarchy, 0, cv::Point() );
cv::circle( drawing, mc[i], 4, color, -1, 8, 0 );
}
cv::waitKey(0); return 0;
}
原图:

效果图:

部分打印结果:

4.2 Python版代码
# -*- coding: utf-8 -*-"""
Created on Sun Mar 26 18:36:19 2017
@author: lql0716
"""import cv2import numpy as np
nThresh = 100nMaxThresh = 255img = cv2.imread('D:/photo/04.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
gray = cv2.blur(gray, (3,3))
cv2.namedWindow('img', cv2.WINDOW_NORMAL)
cv2.imshow('img', img)
cannyImg = cv2.Canny(gray, nThresh, nThresh*2, 3)
contours, hierarchy = cv2.findContours(cannyImg, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
mu = []
mc = []
retval = np.array([])for i in range(0, np.array(contours).shape[0]):
retval = cv2.moments(contours[i], False)
mu.append(retval)
mu = np.array(mu)#print mu[0]['m10']thetas = []for i in range(0, np.array(contours).shape[0]): if mu[i]['m00'] == 0.0:
a=0
b=0
else:
a = mu[i]['m10'] / mu[i]['m00'] #质心x坐标
b = mu[i]['m01'] / mu[i]['m00'] #质心y坐标
#根据二阶矩计算物体形状的方向
r1 = mu[i]['m20'] / mu[i]['m00'] - a*a
r2 = 2.0*(mu[i]['m11'] / mu[i]['m00'] - a*b)
r3 = mu[i]['m02'] / mu[i]['m00'] - b*b# print r1-r3
if r1-r3==0:
theta = np.pi / 2
else:
theta = np.arctan(r2/(r1-r3)) / 2
thetas.append(theta)
mc.append([a,b])
mc = np.array(mc)
drawing = np.zeros(img.shape, dtype = np.uint8)for i in range(0, mc.shape[0]):
c1 = np.random.randint(0, 256)
c2 = np.random.randint(0, 256)
c3 = np.random.randint(0, 256)
cv2.drawContours(drawing, contours, i, (c1, c2, c3), 2, 8)
cv2.circle(drawing, (int(round(mc[i][0])), int(round(mc[i][1]))), 4, (c1, c2, c3), -1, 8, 0)
cv2.namedWindow('img2', cv2.WINDOW_NORMAL)
cv2.imshow('img2', drawing)
cv2.waitKey(0)
原图:

效果图:

原点矩:
mpq=∑x−1M∑y−1Nxpyqf(x,y)
中心距:
μpq=∑x−1M∑y−1N(x−x0)p(y−y0)qf(x,y)
归一化中心距:
ηpq=μpqμr00
其中 r=p+q+22,p+q=2,3,...
当图像变化时,mpq也变化,而μpq具有平移不变形,但对旋转依然敏感。如果用归一化中心距,则可以保持平移不变性、伸缩不变性。
Hu矩利用二阶、三阶中心距构造了7个不变矩,它们在连续图像条件下可保持平移、旋转、伸缩不变,公式如下:
M1=η20+η02
M2=(η20−η02)2+4η211
M3=(η30−3η12)2+(3η21−η03)2
M4=(η30+η12)2+(η21+η03)2
M5=(η30−3η12)(η30+η12)((η30+η12)2−3(η21+η03)2)+(3η21−η03)(η21+η03)(3(η30+η12)2−(η21+η03)2)
M6=(η20−η02)((η30+η12)2−(η21+η03)2)+4η11(η30+η12)(η21+η03)
M7=(3η21−η03)(η30+η12)((η30+η12)2−3(η21+η03)2)−(η30−3η12)(η21+η03)(3(η30+η12)2−(η21+η03)2)
以下公式为官方文档的公式:

在对图片识别过程中,只有M1和M2不变性保持的比较好,其他几个不变矩带来的误差比较大,有学者认为只有基于二阶矩的不变矩对二维物体的描述才真正具有旋转,缩放,平移不变性(M1和M2均为二阶矩构成).
Hu矩的优势:速度快
Hu矩的劣势:识别率低
Hu矩一般用来识别图像中大的物体,对物体的形状描述得比较好,图像的纹理特征不能太复杂,如识别水果形状或车牌字符的效果较好.
1、图像的几何矩浅析
2、opencv官方文档-moments()
3、形状描述与识别
4、第三章 二值图像分析