Opencv轮廓矩【判断形态方向、匹配度】

轮廓矩


原理部分:

一、概率论上的定义

 看到矩这个字,很容易联想到概率论,在概率论中,定义如下:

或者说:

设 X 和 Y 是随机变量,c 为常数,k 为正整数, 
如果E(|X−c|^k)E(|X−c|^k)存在,则称E(|X−c|^k)E(|X−c|^k)为 X 关于点 c 的 k 阶矩。

  • c = 0 时, 称为 k 阶原点矩;
  • c = E(x) 时,称为 k 阶中心矩。

如果E(|X−c1|^p⋅|Y−c2|^q)存在,则称其为 X,Y 关于 c 点 p+q 阶矩。

其余基本概率论知识可参考:https://www.cnblogs.com/wyuzl/p/7845948.html 

 

 二、在图像学上的定义

一幅M×N的数字图像f(i,j),其p+q阶几何矩m_{pq}和中心矩\mu _{pq}为:

                                                             m_{pq}= \sum_{i=1}^{M}\sum_{j=1}^{N}i^{p}j^{q}f(i,j)

                                                            \mu _{pq}= \sum_{i=1}^{M}\sum_{j=1}^{N}(i-\bar{i})^{p}(j-\bar{j})^{q}f(i,j)

其中:

  • f(i,j)为图像在坐标点(i,j)处的灰度值。
  • 重心:\bar{i}=\frac{m_{10}}{m_{00}},\bar{j}=\frac{m_{01}}{m_{00}},也是图像的一阶矩

 

三、几何矩m_{pq}的基本意义

(1)零阶矩

                                                            m_{00}= \sum_{i=1}^{M}\sum_{j=1}^{N}f(i,j)

可以发现,当图像为二值图时,m_{00}就是这个图像上白色区域的总和,因此,m_{00}可以用来求二值图像(轮廓,连通域)的面积。

(2)一阶矩

                                                           m_{10}= \sum_{i=1}^{M}\sum_{j=1}^{N}i\cdot f(i,j)

                                                           m_{01}= \sum_{i=1}^{M}\sum_{j=1}^{N}j\cdot f(i,j)

当图像为二值图时,m_{10}就是白色像素关于x坐标的累加和,而m_{01}则是y坐标的累加和

由此,可获得图像的重心:\bar{i}=\frac{m_{10}}{m_{00}},\bar{j}=\frac{m_{01}}{m_{00}},也就是前文提到的。

(3)二阶矩

                                                           m_{20}= \sum_{i=1}^{M}\sum_{j=1}^{N}i^{2}\cdot f(i,j)

                                                           m_{02}= \sum_{i=1}^{M}\sum_{j=1}^{N}j^{2}\cdot f(i,j)

                                                           m_{11}= \sum_{i=1}^{M}\sum_{j=1}^{N}i\cdot j \cdot f(i,j)

二阶矩可以用来求物体形状的方向。 

后续会有介绍,具体可参考:https://blog.csdn.net/qq826309057/article/details/70039397

 

四、由几何矩可表示出中心距如下:

摘自:https://blog.csdn.net/keith_bb/article/details/70197104

为了消除图像比例变化带来的影响,定义规格化中心矩如下:

利用二阶和三阶规格中心矩可以导出下面7个不变矩组(Φ1 Φ7),它们在图像平移、旋转和比例变化时保持不变


Opencv应用部分

核心函数:

(1)求矩

Moments moments(inputArray array, bool binaryImage=false) 
 
 
  • 输入参数,可以是光栅图像(单通道,8位或浮点的二维数组)或二维数组(1N或N1)
  • 默认值false,若此参数取true,则所有非零像素为1.此参数仅对图像使用

(2)计算轮廓面积

double contourArea(inputArray contour, bool oriented=false) 
 
 
  • 输入的向量,二维点(轮廓顶点)
  • 面向区域标识符,若为true,该函数返回一个带符号的面积值,其正负取决于轮廓的方向(顺时针还是逆时针)。根据这个特性我们可以根据面积的符号来确定轮廓的位置。需要注意的是,这个参数有默认值false,表示以绝对值返回,不带符号。

(3) 计算轮廓长度

double arcLength(inputArray curve,bool closed) 
 
 
  •  输入的二维点集
  • 一个用于指示曲线是否封闭的标识符,默认值closed,表示曲线封闭

 

一、求图像的重心和方向

前面提到二阶矩可以用来求物体形状的方向。 

其中:

a=\frac{m_{20}}{m_{00}}-\bar{i}^{2},

b=\frac{m_{10}}{m_{00}}-\bar{i}\cdot \bar{j}

c=\frac{m_{02}}{m_{00}}- \bar{j}^2
fastAtan2()为opencv的函数,输入向量,返回一个0-360的角度。 

个人认为:关于fastAtan2()的推算如下:

 fastAtan2(y,x)=\left\{\begin{matrix} \frac{1}{2}arctan(\frac{y}{x}) &&if((y>0)and(x>0)) \\90+\frac{1}{2}arctan(\frac{y}{x}) &&if(x<0) \\180++\frac{1}{2}arctan(\frac{y}{x}) &&if((y<0)and(x>0)) \end{matrix}\right.

当然这只是个人推导,有兴趣的同学可以验证一下。 

另外,fastAtan2()返回的角度指自然坐标系下x轴正半轴按顺时针到图像轴的角,如下图的α角:

【注意:实际处理时,需把目标部分变成白色,背景为黑色。这里只是为了方便看才把目标变成黑色而已】

测试代码:


 
 
  1. #include<opencv2/imgproc/imgproc.hpp>
  2. #include<opencv2/highgui/highgui.hpp>
  3. #include<opencv2/features2d/features2d.hpp>
  4. #include<stdlib.h>
  5. #include<stdio.h>
  6. #include<iostream>
  7. using namespace std;
  8. using namespace cv;
  9. void main()
  10. {
  11. Mat srcImg;
  12. srcImg = imread( "F:\\opencv_re_learn\\flash.jpg");
  13. if (!srcImg.data){
  14. cout<< "failed to read" << endl;
  15. system( "pause");
  16. return;
  17. }
  18. Mat srcGray;
  19. cvtColor(srcImg, srcGray, CV_BGR2GRAY);
  20. Mat thresh;
  21. threshold(srcGray, thresh, 100, 255, CV_THRESH_BINARY_INV |
  22. CV_THRESH_OTSU); //二值化时主要要让目标部分是白色像素
  23. Moments m = moments(thresh, true); //moments()函数计算出三阶及一下的矩
  24. Point2d center(m.m10 / m.m00, m.m01 / m.m00); //此为重心
  25. //计算方向
  26. double a = m.m20 / m.m00 - center.x*center.x;
  27. double b = m.m11 / m.m00 - center.x*center.y;
  28. double c = m.m02 / m.m00 - center.y*center.y;
  29. double theta = fastAtan2( 2 * b, (a - c)) / 2; //此为形状的方向
  30. cout << 2 * b << endl;
  31. cout << a - c << endl;
  32. cout << "角度是:"<< theta << endl;
  33. //绘制重心
  34. circle(srcImg, center, 2, Scalar( 0, 0, 255), 2);
  35. imshow( "src", srcImg);
  36. imshow( "Gray", srcGray);
  37. imshow( "Thresh", thresh);
  38. waitKey( 0);
  39. }

 

 

二、轮廓匹配

轮廓匹配并没有直接用到Moments ,但是是基于轮廓矩的7个不变形矩进行比较的。

相关函数:


 
 
  1. double MatchShapes(const void* object1, const void* object2,
  2. int method, double parameter = 0);
  • 第一个参数是待匹配的物体1
  • 第二个是待匹配的物体2
  • 第三个参数method有三种输入:【即三种不同的判定物体相似的方法】
  1. CV_CONTOURS_MATCH_I1
  2. CV_CONTOURS_MATCH_I2
  3. CV_CONTOURS_MATCH_I3

测试代码:


 
 
  1. #include<opencv2/highgui/highgui.hpp>
  2. #include<opencv2/imgproc/imgproc.hpp>
  3. #include<opencv2/core/core.hpp>
  4. #include<iostream>
  5. using namespace std;
  6. using namespace cv;
  7. RNG rng(12345);
  8. Mat init_src(string filename)
  9. {
  10. Mat srcImg = imread(filename);
  11. if (!srcImg.data){
  12. cout << "filed to read" << endl;
  13. system( "pause");
  14. return Mat::zeros( 100, 100, CV_8UC1);
  15. }
  16. return srcImg;
  17. }
  18. vector< vector<Point>> find_contour(Mat thresh, string window_name)
  19. {
  20. Mat canny_output;
  21. vector< vector<Point>>contours;
  22. vector<Vec4i> hierarchy;
  23. medianBlur(thresh, thresh, 5);
  24. //canny边缘检测
  25. //如果图片的轮廓分明,建议可以不进行canny边缘检测,可直接对二值化图像进行findcontours
  26. //Canny(thresh, thresh, 10, 10 * 2, 3);
  27. //寻找轮廓
  28. findContours(thresh, contours, hierarchy,
  29. CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE, Point( 0, 0));
  30. Mat drawing = Mat::zeros(thresh.size(), CV_8UC3);
  31. for ( int i = 0; i < contours.size(); i++){
  32. //定义随机颜色
  33. Scalar color = Scalar(rng.uniform( 0, 255),
  34. rng.uniform( 0, 255), rng.uniform( 0, 255));
  35. //绘制
  36. drawContours(drawing, contours, i, color,
  37. 2, 8, hierarchy, 0, Point());
  38. }
  39. //显示
  40. namedWindow(window_name, CV_WINDOW_AUTOSIZE);
  41. imshow(window_name, drawing);
  42. return contours;
  43. }
  44. void main()
  45. {
  46. //图片读取、预处理
  47. Mat srcImg1 = init_src( "F:\\opencv_re_learn\\angle_test2.jpg");
  48. Mat srcImg2 = init_src( "F:\\opencv_re_learn\\angle_test.jpg");
  49. Mat srcGray1, srcGray2;
  50. cvtColor(srcImg1, srcGray1, CV_BGR2GRAY);
  51. cvtColor(srcImg2, srcGray2, CV_BGR2GRAY);
  52. Mat thresh1, thresh2;
  53. threshold(srcGray1, thresh1, 100, 255, CV_THRESH_BINARY_INV |
  54. CV_THRESH_OTSU);
  55. threshold(srcGray2, thresh2, 100, 255, CV_THRESH_BINARY_INV |
  56. CV_THRESH_OTSU);
  57. //imshow("thresh1", thresh1);
  58. //imshow("thresh2", thresh2);
  59. //获取轮廓
  60. vector< vector<Point>> contours1 = find_contour(thresh1, "contour1");
  61. vector< vector<Point>> contours2 = find_contour(thresh2, "contour2");
  62. //轮廓比较
  63. cout << "first method to match, result:" <<
  64. matchShapes(contours1[ 0], contours2[ 0], CV_CONTOURS_MATCH_I1, 0) << endl;;
  65. cout << "second method to match, result:" <<
  66. matchShapes(contours1[ 0], contours2[ 0], CV_CONTOURS_MATCH_I2, 0) << endl;
  67. cout << "third method to match, result:" <<
  68. matchShapes(contours1[ 0], contours2[ 0], CV_CONTOURS_MATCH_I2, 0)<< endl;
  69. imshow( "src1", srcImg1);
  70. imshow( "src2", srcImg2);
  71. waitKey( 0);
  72. }

应该只需修改图片的路径即可进行调试

1)读入两张相同的图片,三种方法匹配的结果都是0,表示完全匹配

2)还是读入上面的两张图片,但是其中一张经过旋转

结果:三种方法匹配的结果都很接近0,表示这两个轮廓还是基本匹配的

3)读入两张不同的图片

 结果明显偏离0,但是可以说还是有一点相似性吧。。。



 参考文章:

https://blog.csdn.net/qq_31531635/article/details/73692611

https://blog.csdn.net/qq826309057/article/details/70039397

https://blog.csdn.net/keith_bb/article/details/70197104

https://blog.csdn.net/mingzhentanwo/article/details/45155307

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值