【从零学习OpenCV 4】图像矩的计算与应用
经过几个月的努力,小白终于完成了市面上第一本OpenCV 4入门书籍《从零学习OpenCV 4》。为了更让小伙伴更早的了解最新版的OpenCV 4,小白与出版社沟通,提前在公众号上连载部分内容,请持续关注小白。 |
矩是描述图像特征的算子,被广泛用于图像检索和识别、图像匹配、图像重建、图像压缩以及运动图像序列分析等领域。本节中将介绍几何矩与Hu矩的计算方法以及应用Hu矩实现图像轮廓的匹配。
几何矩与中心矩
图像几何矩的计算方式如式(7.8)所示:
其中是像素处的像素值。当x和y同时取值0时称为零阶矩,零阶矩可以用于计算某个形状的质心,当x和y分别取值0和1时被称为一阶矩,以此类推。图像质心的计算公式如(7.9)所示:
图像中心距计算方式如式(7.10)所示:
图像归一化几何矩计算方式如式所示:
OpenCV 4提供了计算图像矩的moments()函数,该函数的函数原型在代码清单7-28中给出。
代码清单7-28 moments()函数原型
Moments cv::moments(InputArray array,
bool binaryImage = false
)
array:计算矩的区域2D像素坐标集合或者单通道的CV_8U图像 binaryImage:是否将所有非0像素值视为1的标志。
该函数用于计算图像连通域的几何矩和中心距以及归一化的几何矩。函数第一个参数是待计算矩的输入图像或者2D坐标集合。函数第二个参数为是否将所有非0像素值视为1的标志,该标志只在第一个参数输入为图像类型的数据时才会有作用。函数会返回一个Moments类的变量,Moments类中含有几何矩、中心距以及归一化的几何矩的数值属性,例如Moments.m00是零阶矩,Moments.m01和Moments.m10是一阶矩。Moments类中所有的属性在表7-5给出。
表7-5 Moments类的属性
种类 | 属性 |
---|---|
spatial moments | m00、m10、m01、m20、m11、m02、m30、m21、m12、m03 |
central moments | mu20、mu11、mu02、mu30、mu21、mu12、mu03 |
central normalized moments | nu20、nu11、nu02、nu30、nu21、nu12、nu03 |
为了了解函数的使用方法,在代码清单7-29中给出了计算图像矩和读取每一种矩数值方法的示例程序,程序的部分运行结果如图7-24所示。
代码清单7-29 myMoments.cpp计算图像的矩
#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>
using namespace cv;
using namespace std;
int main()
{
Mat img = imread("approx.png");
// 二值化
Mat gray, binary;
cvtColor(img, gray, COLOR_BGR2GRAY);
threshold(gray, binary, 105, 255, THRESH_BINARY);
//开运算消除细小区域
Mat k = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
morphologyEx(binary, binary, MORPH_OPEN, k);
// 轮廓发现
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(binary, contours, hierarchy, 0, 2, Point());
for (int n = 0; n < contours.size(); n++)
{
Moments M;
M = moments(contours[n], true);
cout << "spatial moments:" << endl
<< "m00:" << M.m00 << " m01:" << M.m01 << " m10:" << M.m10 << endl
<< "m11:" << M.m11 << " m02:" << M.m02 << " m20:" << M.m20 << endl
<< "m12:" << M.m12 << " m21:" << M.m21 << " m03:" << M.m03 << " m30:"<< M.m30 << endl;
cout << "central moments:" << endl
<< "mu20:" << M.mu20 << " mu02:" << M.mu02 << " mu11:" << M.mu11 << endl
<< "mu30:" << M.mu30 << " mu21:" << M.mu21 << " mu12:" << M.mu12 << " mu03:" << M.mu03 << endl;
cout << "central normalized moments:" << endl
<< "nu20:" << M.nu20 << " nu02:" << M.nu02 << " nu11:" << M.nu11 << endl
<< "nu30:" << M.nu30 << " nu21:" << M.nu21 << " nu12:" << M.nu12 << " nu03:" << M.nu03 << endl;
}
return 0;
}
图7-24 myMoments.cpp程序部分运行结果
Hu矩
Hu矩具有旋转、平移和缩放不变性,因此在图像具有旋转和放缩的情况下Hu矩具有更广泛的应用领域。Hu矩是由二阶和三阶中心距计算得到七个不变矩,具体计算公式如式(7.12)所示:
OpenCV 4提供了用于计算Hu矩的HuMoments()函数,根据参数类型的不同该函数具有两种原型。在代码清单7-30中给出这两种函数原型。
代码清单7-30 HuMoments()函数原型
void cv::HuMoments(const Moments & moments,
double hu[7]
)
void cv::HuMoments(const Moments & m,
OutputArray hu
)
moments:输入的图像矩 hu[7]:输出Hu矩的七个值 m:输入的图像矩 hu:输出Hu矩的矩阵
该函数可以根据图像的中心距计算图像的Hu矩。两个函数原型只有第二个参数的数据类型不同,第一个参数是输入图的Moments类的图像矩,第二个参数是输出的Hu矩,第一种函数原型输出值存放在长度为7的double类型数组中,第二种函数原型输出值为Mat类型。
为了了解函数的使用方法,在代码清单7-31中给出了计算图像Hu的示例程序,程序的部分运行结果如图7-25所示。
代码清单7-31 myHuMoments.cpp计算图像的Hu矩
#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>
using namespace cv;
using namespace std;
int main()
{
system("color F0"); //更改输出界面颜色
Mat img = imread("approx.png");
if (img.empty())
{
cout << "请确认图像文件名称是否正确" << endl;
return -1;
}
// 二值化
Mat gray, binary;
cvtColor(img, gray, COLOR_BGR2GRAY);
threshold(gray, binary, 105, 255, THRESH_BINARY);
//开运算消除细小区域
Mat k = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
morphologyEx(binary, binary, MORPH_OPEN, k);
// 轮廓发现
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(binary, contours, hierarchy, 0, 2, Point());
for (int n = 0; n < contours.size(); n++)
{
Moments M;
M = moments(contours[n], true);
Mat hu;
HuMoments(M, hu); //计算Hu矩
cout << hu << endl;
}
return 0;
}
图7-25 myHuMoments.cpp程序部分运行结果
基于Hu矩的轮廓匹配
Hu矩具有旋转、平移和比例不变性,因此可以通过Hu实现图像轮廓的匹配。OpenCV 4提供了利用Hu矩进行轮廓匹配的matchShapes()函数,该函数的函数原型在代码清单7-32中给出。
代码清单7-32 matchShapes()函数原型
double cv::matchShapes(InputArray contour1,
InputArray contour2,
int method,
double parameter
)
contour1:原灰度图像或者轮廓 contour2:模板图像或者轮廓 method:匹配方法的标志,可以选择的参数及含义在表7-6给出。 parameter:特定于方法的参数(现在不支持)
该函数用于实现在图像或者轮廓中寻找与模板图像或者轮廓像素匹配的区域。函数的第一个参数是原灰度图像或者轮廓,第二个参数是模板图像或者轮廓。函数第三个参数是两个轮廓Hu矩匹配的计算方法标志,可以选择的参数和每种方法相似性计算公式在表7-6给出。函数最后一个参数在目前的OpenCV 4版本中没有意义,可以将参数设置为0。
表7-6 matchShapes()函数中匹配方法的标志
标志参数 | 简记 | 原理 |
---|---|---|
CONTOURS_MATCH_I1 | 1 | |
CONTOURS_MATCH_I2 | 2 | |
CONTOURS_MATCH_I3 | 3 |
为了了解函数的用法,在代码清单7-33中给出了利用Hu矩实现模板与原图像或者轮廓之间匹配的示例程序。程序中原图像有三个字母,模板图像有一个字母,并且模板图像中字母的尺寸小于原图像中字母的尺寸。通过对两张图像提取轮廓并计算每个轮廓的Hu矩,之后寻找原图像和模板图像中Hu矩最相似的两个轮廓,并在原图像中绘制出相似轮廓,程序运行结果在图7-26给出。
代码清单7-33 myMatchShapes.cpp基于Hu矩的轮廓匹配
#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>
using namespace cv;
using namespace std;
void findcontours(Mat &image, vector<vector<Point>> &contours)
{
Mat gray, binary;
vector<Vec4i> hierarchy;
//图像灰度化
cvtColor(image, gray, COLOR_BGR2GRAY);
//图像二值化
threshold(gray, binary, 0, 255, THRESH_BINARY | THRESH_OTSU);
//寻找轮廓
findContours(binary, contours, hierarchy, 0, 2);
}
int main()
{
Mat img = imread("ABC.png");
Mat img_B = imread("B.png");
if (img.empty() || img_B.empty())
{
cout << "请确认图像文件名称是否正确" << endl;
return -1;
}
resize(img_B, img_B, Size(), 0.5, 0.5);
imwrite("B.png", img_B);
imshow("B", img_B);
// 轮廓提取
vector<vector<Point>> contours1;
vector<vector<Point>> contours2;
findcontours(img, contours1);
findcontours(img_B, contours2);
// hu矩计算
Moments mm2 = moments(contours2[0]);
Mat hu2;
HuMoments(mm2, hu2);
// 轮廓匹配
for (int n = 0; n < contours1.size(); n++)
{
Moments mm = moments(contours1[n]);
Mat hum;
HuMoments(mm, hum);
//Hu矩匹配
double dist;
dist = matchShapes(hum, hu2, CONTOURS_MATCH_I1, 0);
if (dist < 1)
{
drawContours(img, contours1, n, Scalar(0, 0, 255), 3, 8);
}
}
imshow("match result", img);
waitKey(0);
return 0;
}
图7-26 myMatchShapes.cpp程序运行结果