文章目录
- 一、概念
- 1.什么叫图像的连通区域
- 2.提取连通区域的函数
- 二、简单应用
- 1.原始素材
- 2.代码
- 3.运行结果
- 4.连通区域上色
一、概念
1.什么叫图像的连通区域
图像的连通域是指图像中具有相同像素值并且位置相邻的像素组成的区域,连通域分析是指在图像中寻找出彼此互相独立的连通域并将其标记出来。
最简单的理解:灰度图中,黑色背景值为0,前景多是大于0的,当有一片区域值相同,而且像素点彼此相连,就是一个连通区域。比如下面这张图(默认是二值化图形),就有7个连通区域,分别是字母E、m、g、u、C、V和符号"."
它有什么用呢,经常进行算法设计的同学们应该能够理解,在图像分割、目标检测、形状分析都能用的到。
2.提取连通区域的函数
Emgu.CV中提取连通区域的函数是:
public static ConnectedComponentsWithStats
(IInputArray image, // 输入图像IOutputArray labels, // 输出标记图像,与输入图像大小一样,对应的联通区域会标记上对应的数字0为背景,其它数字为对应的联通区域IOutputArray stats, // 每一连通域的信息,表示每个连通区域的外接矩形(起始点的x、y、宽和高)和面积IOutputArray centroids, // 连通区域的中心点LineType connectivity = LineType.EightConnected, // 线形DepthType labelType = DepthType.Cv32S, // 输出标记图像的类型ConnectedComponentsAlgorithmsTypes cclType = ConnectedComponentsAlgorithmsTypes.Default
)
函数返回的是一个int类型数值,代表有几个连通区域。
二、简单应用
1.原始素材
原始素材srcMat如下图:
图像宽737,长349。
2.代码
代码如下:
Mat tempMat = srcMat.Clone();
Mat dstMat = srcMat.Clone();
Mat gray = new Mat();
int threshold = 40;// 1.转换图像
CvInvoke.CvtColor(tempMat, gray, ColorConversion.Bgr2Gray);
CvInvoke.Threshold(gray, gray, threshold, 255, ThresholdType.Binary);// 2.利用ConnectedComponentsWithStats计算连通区域参数
Mat labels = new Mat(); // 对原始图中的每一个像素都打上标签,背景为0,连通域打上1,2,3。。。的标签,同一个连通域的像素打上同样的标签。相当与对每一个像素进行了分类(分割)
var stats = new Mat(); // 每一连通域的信息,表示每个连通区域的外接矩形(起始点的x、y、宽和高)和面积
var centroids = new Mat(); // 连通区域的中心点int a = CvInvoke.ConnectedComponentsWithStats(gray, labels, stats, centroids, LineType.EightConnected, DepthType.Cv16U);
Int32[,] intStatus = (Int32[,])stats.GetData(); // x,y,width,height,area四个参数有可能超过255,所以要转换成int32数组,不能转成Image<Gray, byte> img
3.运行结果
运行结果:
int a = 8;
Int32[,] intStatus如下所示:
a是代表连通区域个数,应该是7个,为什么结果是8呢???请看intStatus[0, 0]、intStatus[0, 1]、intStatus[0, 2]、intStatus[0, 3]的值,它代表了第0个连通区域的外接矩形,从左上角的[0,0]点开始,宽度737,长度349。intStatus[0, 4]好像是代表外接矩形面积,它不就是原始图片的尺寸吗??所以Int32[,] intStatus得出来的矩形组合,第一个不能用,其余的才是。
intStatus[1, 0]、intStatus[1, 1]、intStatus[1, 2]、intStatus[1, 3]代表从点[503,123]开始,宽度90,长度108的矩形,读者们可以试一试,它其实框起来的是字母C。
4.连通区域上色
根据网上最流行的说法,连通区域找到以后,最简单的一个应用就是给不同的区域上色,找连通区域的代码就是上面的,上色的代码就更简单了,如下:
// 接上面的连通区域查找
List<int[]> listColors = new List<int[]>();
Random random = new Random(); // 定义一个随机类的对象,目的是每个连通区域都有不同的颜色
for (int i = 0; i < a; i++)
{int[] color = new int[] { random.Next(0, 255), random.Next(0, 255), random.Next(0, 255) };listColors.Add(color);
}// 以不同颜色标记出不同的连通域
Mat result = new Mat(tempMat.Size, DepthType.Cv8U, 3);
result.SetTo(new MCvScalar(0, 0, 0));
Image<Bgr, int> img = result.ToImage<Bgr, int>();Int16[,] intLabels = (Int16[,])labels.GetData();
int w = result.Cols;
int h = result.Rows;
for (int i = 0; i < h; i++)
{for (int j = 0; j < w; j++){int label = intLabels[i, j];if (label == 0 || label >= listColors.Count) // 背景的黑色不改变{continue;}img.Data[i, j, 0] = listColors[label][0];img.Data[i, j, 1] = listColors[label][1];img.Data[i, j, 2] = listColors[label][2];}
}result = img.Mat;
result.ConvertTo(result, DepthType.Cv8U);CvInvoke.Imshow("gray, " + gray.Size.ToString(), gray);
CvInvoke.Imshow("Final result image, " + result.Size.ToString(), result);
这里注意的就是这句话:
Int16[,] intLabels = (Int16[,])labels.GetData();
labels是连通区域查找中,返回的标签记号,也就是这个函数
int a = CvInvoke.ConnectedComponentsWithStats(gray, labels, stats, centroids, LineType.EightConnected, DepthType.Cv16U);
它的大小就是图像大小,作用就是对像素的每一个像素都打上标签,背景为0,连通域打上1,2,3… …,同一个连通域的像素打上同样的标签。相当与对每一个像素进行了分类,利用这个值,遍历图像的时候,判断它的值是多少,就在listColors中选择对应颜色。最终实现原始图像连通区域上色。效果如下:
原创不易,请勿抄袭。共同进步,相互学习。