OpenCV 基于轮廓提取的二值图像分析与连通区域标记算法

1.      入门

OpenCV 中提供了一个功能强大的在二值图像中查找轮廓的函数cv::findContours(),通过这个函数的输出的轮廓,在结合辅助的函数arcLength(),boundingRect(),contourArea(),minAreaRect(),minEnclosingCircle()等可以完成绝大多数的二值图像分析工作。下面对这个函数使用做简要介绍:

1)  轮廓查找函数有两种接口:

一种是相对简单的接口:

std::vector> contours;

cv::findContours(image, contours, CV_RETR_EXTERNAL,CV_CHAIN_APPOX_NONE);

一种是包含了图像轮廓拓扑结构的接口:

std::vector> contours;

std::vector hierarchy;

cv::findContours(image, contours, hierarchy, CV_RETR_TREE,CV_CHAIN_APPOX_NONE);


2)  辅助函数功能:

arcLength() 计算轮廓长度

boundingRect() 计算最小矩形

contourArea() 计算轮廓面积

minAreaRect() 计算最小包围盒

minEnclosingCircle() 计算最小圆

convexHull() 计算凸包

fitEllipse() 拟合椭圆

2.      进阶

但以笔者的经验看来,这个函数的接口定义可能有些缺憾的是没有输出二值图像连通区域标记的结果,以至于有些时候通过现有的函数接口实现面积计算功能时不一定的是性能最好或者最合适的方案,比如:

1)  我需要从一幅包含有大量二值区域的大图像中滤除一部分面积较小的连通区域,这个时候如果利用OpenCV提供的接口,需要在找到所有轮廓后,逐个调用contourArea()来计算面积,这个过程其实是一个扫描填充的过程,相对来说比较耗时。

2)  我需要计算面积的连通区域包含很多“洞”,或者叫内轮廓,那么这个时候计算真实的面积就会比较麻烦了。

对于那些了解这个函数的实现原理(查资料发现该函数的实现是基于Chang Fu的一篇关于连区域标记的文章),同时又追求极限性能的偏执狂来说,这是不能忍受的。于是乎,笔者就自己实现了一个二值图像连通区域标记的算法,在轮廓查找完毕后即可同时输出各个连通区域标记的结果,在标记好的图像中计算面积那是最简单的道理O(∩_∩)O~。

 

void LabelBinaryImage(BYTE* pBinary, int nWidth, int nHeight, int* pLabel)
{
	int nWS = nWidth;
	
	int nEWidth = nWidth + 2;
	int nEHeight = nHeight + 2;
	BYTE* pESource = new BYTE[nEWidth * nEHeight];
	
	memset(pESource, 255, nEWidth);
	BYTE* pEBinary = pESource + nEWidth - 1;
	
	for (int s = 1; s < nHeight; ++ s){
		pEBinary[0] = 255;
		pEBinary[1] = 255;
		memcpy(pEBinary + 2, pBinary, nWidth);
		pEBinary += nEWidth;
		pBinary += nWS;
	}
	memset(pEBinary + 1, 255, nEWidth);
	
	BYTE* pBinaryUp = pESource;
	BYTE* pBinaryCurr = pBinaryUp + nEWidth;
	BYTE* pBinaryDown = pBinaryCurr + nEWidth;
	
	int* pResult = new int[nEWidth * nEHeight];
	memset(pResult, 0, nEWidth * nEHeight * sizeof(int));
	int* pIndexCurr = pResult + nEWidth;
	int* pIndexDown = pIndexCurr + nEWidth;
	
	int nLabelNum = 1;
	BYTE background = 255;
	int i;
	for (i = 1; i < nEHeight - 1; ++ i){
		for (int j = 1;j < nEWidth - 1; ++ j){
			if (pBinaryCurr[j] == background)
				continue;	
			if (pBinaryUp[j] != background && pBinaryDown[j] != background){
				if (pIndexCurr[j] < 1)
					pIndexCurr[j] = pIndexCurr[j - 1];
			}
			else{
				if (pBinaryUp[j] == background && pIndexCurr[j] == 0){
					pIndexCurr[j] = nLabelNum;	
					++ nLabelNum;
					
					CLabelTracer oExternalTracer(pBinaryCurr + j, nEWidth, 7, pIndexCurr + j);
					if (!oExternalTracer.IsIsolated())
						oExternalTracer.LabelTrace();
				}
				if (pBinaryDown[j] == background){
					if (pIndexCurr[j] == 0)
						pIndexCurr[j] = pIndexCurr[j - 1];
					if (pIndexDown[j] == 0){
						CLabelTracer oInernalTracer(pBinaryCurr + j, nEWidth, 3, pIndexCurr + j);
						if (!oInernalTracer.IsIsolated())
							oInernalTracer.LabelTrace();
					}
				}
			}
		}
		pIndexCurr += nEWidth;
		pIndexDown += nEWidth;
		pBinaryUp  += nEWidth;
		pBinaryCurr+= nEWidth;
		pBinaryDown+= nEWidth;
	}
	
	int* pI = pResult + nEWidth + 1;
	int* pL = pLabel;
	for (i = 0; i < nHeight; ++ i){
		for (int j = 0; j < nWidth; ++ j)
			pL[j] = pI[j] < 1 ? -1 : pI[j];
		
		pL += (nEWidth - 2);
		pI += nEWidth;
	}
	
	delete[] pResult;
	delete[] pESource;
}

你可能感兴趣的:(OpenCV,拾遗)