图像的连通域是指图像中具有相同像素值并且位置相邻的像素组成的区域,连通域分析是指在图像中寻找出彼此互相独立的连通域并将其标记出来。提取图像中不同的连通域是图像处理中较为常用的方法,例如在车牌识别、文字识别、目标检测等领域对感兴趣区域分割与识别。一般情况下,一个连通域内只包含一个像素值,因此为了防止像素值波动对提取不同连通域的影响,连通域分析常处理的是二值化后的图像。
在了解图像连通域分析方法之前,首先需要了解图像邻域的概念。图像中两个像素相邻有两种定义方式,分别是4-邻域和8-邻域,这两种领域的定义方式在图6-7给出。4-邻域的定义方式如图6-7中的左侧所示,在这种定义下,两个像素相邻必须在水平和垂直方向上相邻,相邻的两个像素坐标必须只有一位不同而且只能相差1个像素。
常用的图像邻域分析法有两遍扫描法和种子填充法。两遍扫描法会遍历两次图像,第一次遍历图像时会给每一个非0像素赋予一个数字标签,当某个像素的上方和左侧邻域内的像素已经有数字标签时,取两者中的最小值作为当前像素的标签,否则赋予当前像素一个新的数字标签。第一次遍历图像的时候同一个连通域可能会被赋予一个或者多个不同的标签,如图6-8所示,因此第二次遍历需要将这些属于同一个连通域的不同标签合并,最后实现同一个邻域内的所有像素具有相同的标签。种子填充法源于计算机图像学,常用于对某些图形进行填充。该方法首先将所有非0像素放到一个集合中,之后在集合中随机选出一个像素作为种子像素,根据邻域关系不断扩充种子像素所在的连通域,并在集合中删除掉扩充出的像素,直到种子像素所在的连通域无法扩充,之后再从集合中随机选取一个像素作为新的种子像素,重复上述过程直到集合中没有像素。
OpenCV 4提供了用于提取图像中不同连通域的connectedComponents()函数,该函数有两个函数原型,第一种函数原型在代码清单6-4中给出。
代码清单6-4 connectedComponents()函数原型1 1. int cv::connectedComponents(InputArray image, 2. OutputArray labels, 3. int connectivity, 4. int ltype, 5. int ccltype 6. )
- image:待标记不同连通域的单通道图像,数据类型必须为CV_8U。
- labels:标记不同连通域后的输出图像,与输入图像具有相同的尺寸。
- connectivity:标记连通域时使用的邻域种类,4表示4-邻域,8表示8-邻域。
- ltype:输出图像的数据类型,目前支持CV_32S和CV_16U两种数据类型。
- ccltype:标记连通域时使用的算法类型标志,可以选择的参数及含义在表6-3中给出。
标志参数 | 简记 | 作用 |
---|---|---|
CCL_WU | 0 | 8-邻域使用SAUF算法,4-邻域用SAUF算法 |
CCL_DEFAULT | -1 | 8-邻域使用BBDT算法,4-邻域用SAUF算法 |
CCL_GRANA | 1 | 8-邻域使用BBDT算法,4-邻域用SAUF算法 |
该函数用于计算二值图像中连通域的个数,并在图像中将不同的连通域用不同的数字标签标记出,其中标签0表示图像中的背景区域,同时函数具有一个int类型的返回数据,用于表示图像中连通域的数目。函数的第一个参数是待标记连通域的输入图像,函数要求输入图像必须是数据类型为CV_8U的单通道灰度图像,而且最好是经过二值化的二值图像。函数第二个参数是标记连通域后的输出图像,图像尺寸与第一个参数的输入图像尺寸相同,图像的数据类型与函数的第四个参数相关。函数第三个参数是统计连通域时选择的邻域种类,函数支持两种邻域,分别用4表示4-邻域,8表示8-邻域。函数第四个参数为输出图像的数据类型,可以选择的参数为CV_32S和CV_16U两种。函数的最后一个参数是标记连通域时使用算法的标志,可以选择的参数及含义在表6-3给出,目前只支持Grana(BBDT)和Wu(SAUF)两种算法。
上述函数原型的所有参数都没有默认值,在调用时需要设置全部参数,增加了使用的复杂程度,因此OpenCV 4提供了connectedComponents()函数的简易原型,减少了参数数量以及为部分参数增加了默认值,简易原型在代码清单6-5中给出。
代码清单6-5 connectedComponents()函数原型2 1.int cv::connectedComponents(InputArray image, 2. OutputArray labels, 3. int connectivity = 8, 4. int ltype = CV_32S 5. )
- image:待标记不同连通域的图像单通道,数据类型必须为CV_8U。
- labels:标记不同连通域后的输出图像,与输入图像具有相同的尺寸。
- connectivity:标记连通域时使用的邻域种类,4表示4-邻域,8表示8-邻域,默认参数为8。
- ltype:输出图像的数据类型,目前支持CV_32S和CV_16U两种数据类型,默认参数为CV_32S。
该函数原型只有四个参数,前两个参数分别表示输入图像和输出图像,第三个参数表示统计连通域时选择的邻域种类,分别用4表示4-邻域,8表示8-邻域,参数的默认值为8。最后一个参数表示输出图像的数据类型,可以选择的参数为CV_32S和CV_16U两种,参数的默认值为CV_32S。该函数原型有两个参数具有默认值,在使用时最少只需要两个参数,极大的方便了函数的调用。
代码清单6-6 myConnectedComponents.cpp图像连通域计算 1.#include <opencv2\opencv.hpp> 2.#include <iostream> 3.#include <vector> 4. 5.using namespace cv; 6.using namespace std; 7. 8.int main() 9.{ 10. //对图像进行距离变换 11. Mat img = imread("rice.png"); 12. if (img.empty()) 13. { 14. cout << "请确认图像文件名称是否正确" << endl; 15. return -1; 16. } 17. Mat rice, riceBW; 18. 19. //将图像转成二值图像,用于统计连通域 20. cvtColor(img, rice, COLOR_BGR2GRAY); 21. threshold(rice, riceBW, 50, 255, THRESH_BINARY); 22. 23. //生成随机颜色,用于区分不同连通域 24. RNG rng(10086); 25. Mat out; 26. int number = connectedComponents(riceBW, out, 8, CV_16U); //统计图像中连通域的个数 27. vector<Vec3b> colors; 28. for (int i = 0; i < number; i++) 29. { 30. //使用均匀分布的随机数确定颜色 31. Vec3b vec3 = Vec3b(rng.uniform(0,256),rng.uniform(0,256),rng.uniform(0,256)); 32. colors.push_back(vec3); 33. } 34. 35. //以不同颜色标记出不同的连通域 36. Mat result = Mat::zeros(rice.size(), img.type()); 37. int w = result.cols; 38. int h = result.rows; 39. for (int row = 0; row < h; row++) 40. { 41. for (int col = 0; col < w; col++) 42. { 43. int label = out.at<uint16_t>(row, col); 44. if (label == 0) //背景的黑色不改变 45. { 46. continue; 47. } 48. result.at<Vec3b>(row, col) = colors[label]; 49. } 50. } 51. 52. //显示结果 53. imshow("原图", img); 54. imshow("标记后的图像", result); 55. 56. waitKey(0); 57. return 0; 58.}