文章目录

  • 一、欧拉数定义
  • 二、获取轮廓层次信息
  • 三、欧拉数的计算方法
  • 四、运行测试
  • 1.ABC字母示例
  • 2.汽车轮毂示例
  • 五、完整代码


一、欧拉数定义

二值图像分析中欧拉数重要的拓扑特征之一,在图像分析与几何对象识别中有着十分重要的作用,二值图像的欧拉数计算公式表示如下:

E = N – H ,其中

E 表示计算得到欧拉数

N 表示联通组件的数目

H 表示在联通组件内部的洞的数目

我们对二值化的图像进行分析就可以得到相应的欧拉数~

对字母A来说它的内部有一个黑色孔洞,所以它的H=1,其本身是一个联通组件所以N =1,最终计算得到欧拉数为 E = 1 - 1 = 0,同样可以计算B与C它们的欧拉数分布为-1与1,可见通过欧拉数属性可以轻而易举的区分A、B、C三个英文字母。

欧拉操作系统 修改mysql data路径 欧拉数据_OpenCV

二、获取轮廓层次信息

这里面,我们主要采用的是OpenCV中Vec4i的结构体中findContours()函数。

void cv::findContours(

InputOutputArray image,

OutputArrayOfArrays contours,

OutputArray hierarchy,

intmode,

intmethod,

Point offset = Point()

)

image 参数表示输入的二值图像

contours 表示所有的轮廓信息,每个轮廓是一系列的点集合

hierarchy 表示对应的每个轮廓的层次信息,我们就是要用它实现对最大轮廓欧拉数的分析

mode 表示寻找轮廓拓扑的方法,如果要寻找完整的层次信息,要选择参数RETR_TREE

method 表示轮廓的编码方式,一般选择简单链式编码,参数CHAIN_APPROX_SIMPLE

offset 表示是否有位移,一般默认是 0

三、欧拉数的计算方法

有了轮廓的层次信息与每个轮廓的信息之后,我们尝试遍历每个轮廓,首先通过调用findContours()就可以获取二值图像的轮廓层次信息,然后遍历每个轮廓,进行层次遍历,获得每层子轮廓的总数,最终根据轮廓层级不同分为孔洞与连接轮廓的计数,二者相减得到每个独立外层轮廓的欧拉数。

二值化与轮廓发现的代码如下:

Mat gray, binary;
   
   cvtColor(src, gray, COLOR_BGR2GRAY);
   
   threshold(gray, binary, 0, 255, THRESH_BINARY | THRESH_OTSU);
   
   vector<Vec4i> hireachy;
   
   vector< vector<Point>> contours;
   
   findContours(binary, contours, hireachy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point());

获取同层轮廓的代码如下:

vector <int>current _layer_holes(vector <Vec4i>layers, int index) {
   
   int next = layers[ index][ 0];
   
   vector <int>indexes;
   
   indexes.push_back(index);
   
   while (next >= 0) {
   
   indexes.push_back(next);
   
   next = layers[ next][ 0];
   
   }
   
   return indexes;
   
   }

使用队列迭代寻找遍历每层的代码如下:

while(!nodes.empty()) {
  
  // 当前层总数目
  
  if(index % 2== 0) { // 联通组件对象
  
  n_total += nodes.size();
  
  }
  
  else{ // 孔洞对象
  
  h_total += nodes.size();
  
  }
  
  index++;
  
  // 计算下一层所有孩子节点
  
  intcurr_ndoes = nodes.size();
  
  for( intn = 0; n < curr_ndoes; n++) {
  
  intvalue= nodes.front();
  
  nodes.pop();
  
   
   // 获取下一层节点第一个孩子
   
   intchild = hireachy[ value][ 2];
   
   if(child >= 0) {
   
   nodes.push(child);
   
   }
   
   }
   
   }

四、运行测试

1.ABC字母示例

欧拉操作系统 修改mysql data路径 欧拉数据_轮廓层次分析_02


欧拉操作系统 修改mysql data路径 欧拉数据_欧拉数_03


欧拉操作系统 修改mysql data路径 欧拉数据_图像处理_04

2.汽车轮毂示例

欧拉操作系统 修改mysql data路径 欧拉数据_孔洞计数_05


欧拉操作系统 修改mysql data路径 欧拉数据_图像处理_06

五、完整代码

测试平台为:VS2017 + opencv3.30

#include "pch.h"

#include "opencv2/core/core.hpp" 

#include "opencv2/highgui/highgui.hpp" 

#include "opencv2/imgproc/imgproc.hpp" 

#include <opencv.hpp>  

#include<iostream>

using namespace cv;

using namespace std;

vector< int> current_layer_holes(vector<Vec4i> layers, int index);

int main(int argc, char** argv)
{

	Mat src = imread("E:/Car_wheels/Image Gallery/ABC.bmp");

	if (src.empty()) {

		printf("could not load image...n");

		return-1;

	}

	namedWindow("input", CV_WINDOW_AUTOSIZE);

	imshow("input", src);

	Mat gray, binary;

	cvtColor(src, gray, COLOR_BGR2GRAY);

	threshold(gray, binary, 0, 255, THRESH_BINARY | THRESH_OTSU);

	vector<Vec4i> hireachy;

	vector< vector<Point>> contours;

	findContours(binary, contours, hireachy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point());

	Mat result = Mat::zeros(src.size(), src.type());

	int t = 0;

	for (int size_tt = 0;  t < contours.size(); t++) {

		int next = hireachy[t][0]; // next at the same hierarchical level

		int prev = hireachy[t][1]; // prev at the same hierarchical level

		int child = hireachy[t][2]; // first child

		int parent = hireachy[t][3]; // parent

		printf("next %d, previous %d, children : %d, parent : %dn", next, prev, child, parent);

		drawContours(result, contours, t, Scalar(0, 255, 0), 2, 8);

		// start calculate euler number

		int h_total = 0;

		int n_total = 1;

		int index = 1;

		vector< int> all_children;

		if (child >= 0 && parent < 0) {

			// 计算当前层

			queue< int> nodes;

			vector< int> indexes = current_layer_holes(hireachy, child);

			for (int i = 0; i < indexes.size(); i++) {

				nodes.push(indexes[i]);

			}

			while (!nodes.empty()) {

				// 当前层总数目

				if (index % 2 == 0) { // 联通组件对象

					n_total += nodes.size();

				}

				else { // 孔洞对象

					h_total += nodes.size();

				}

				index++;

				// 计算下一层所有孩子节点

				int curr_ndoes = nodes.size();

				for (int n = 0; n < curr_ndoes; n++) {

					int value = nodes.front();

					nodes.pop();

					// 获取下一层节点第一个孩子

					int child = hireachy[value][2];

					if (child >= 0) {

						nodes.push(child);

					}

				}

			}

			printf("hole number : %dn", h_total);

			printf("connection number : %dn", n_total);

			// 计算欧拉数

			int euler_num = n_total - h_total;

			printf("number of euler : %d n", euler_num);

			drawContours(result, contours, t, Scalar(0, 0, 255), 2, 8);

			// 显示欧拉数

			Rect rect = boundingRect(contours[t]);

			putText(result, format("euler: %d", euler_num), rect.tl(), FONT_HERSHEY_SIMPLEX, 1.0, Scalar(255, 255, 0), 2, 8);

		}

		if (child < 0 && parent < 0) {

			printf("hole number : %dn", h_total);

			printf("connection number : %dn", n_total);

			int euler_num = n_total - h_total;

			printf("number of euler : %d n", euler_num);

			drawContours(result, contours, t, Scalar(255, 0, 0), 2, 8);

			Rect rect = boundingRect(contours[t]);

			putText(result, format("euler: %d", euler_num), rect.tl(), FONT_HERSHEY_SIMPLEX, 1.0, Scalar(255, 255, 0), 2, 8);

		}

	}

	imshow("result", result);

	imwrite("E:/Car_wheels/Image Gallery/result.png", result);

	waitKey(0);

	return 0;

}

vector< int> current_layer_holes(vector<Vec4i> layers, int index) {

	int next = layers[index][0];

	vector< int> indexes;

	indexes.push_back(index);

	while (next >= 0) {

		indexes.push_back(next);

		next = layers[next][0];

	}

	return indexes;

}

如有错误欢迎批评指正,共同探讨交流!