当前位置:   article > 正文

[C#]使用OpencvSharp去除面积较小的连通域

[C#]使用OpencvSharp去除面积较小的连通域

【C++介绍】

关于opencv实现有比较好的算法,可以参考这个博客OpenCV去除面积较小的连通域_c#opencv 筛选小面积区域-CSDN博客

但是没有对应opencvsharp实现同类算法,为了照顾懂C#编程同学们,因此将 去除面积较小的连通域算法转成C#代码。

方法一流程:

  1. //=======函数实现=====================================================================
  2. void RemoveSmallRegion(Mat &Src, Mat &Dst, int AreaLimit, int CheckMode, int NeihborMode)
  3. {
  4. int RemoveCount = 0;
  5. //新建一幅标签图像初始化为0像素点,为了记录每个像素点检验状态的标签,0代表未检查,1代表正在检查,2代表检查不合格(需要反转颜色),3代表检查合格或不需检查
  6. //初始化的图像全部为0,未检查
  7. Mat PointLabel = Mat::zeros(Src.size(), CV_8UC1);
  8. if (CheckMode == 1)//去除小连通区域的白色点
  9. {
  10. //cout << "去除小连通域.";
  11. for (int i = 0; i < Src.rows; i++)
  12. {
  13. for (int j = 0; j < Src.cols; j++)
  14. {
  15. if (Src.at<uchar>(i, j) < 10)
  16. {
  17. PointLabel.at<uchar>(i, j) = 3;//将背景黑色点标记为合格,像素为3
  18. }
  19. }
  20. }
  21. }
  22. else//去除孔洞,黑色点像素
  23. {
  24. //cout << "去除孔洞";
  25. for (int i = 0; i < Src.rows; i++)
  26. {
  27. for (int j = 0; j < Src.cols; j++)
  28. {
  29. if (Src.at<uchar>(i, j) > 10)
  30. {
  31. PointLabel.at<uchar>(i, j) = 3;//如果原图是白色区域,标记为合格,像素为3
  32. }
  33. }
  34. }
  35. }
  36. vector<Point2i>NeihborPos;//将邻域压进容器
  37. NeihborPos.push_back(Point2i(-1, 0));
  38. NeihborPos.push_back(Point2i(1, 0));
  39. NeihborPos.push_back(Point2i(0, -1));
  40. NeihborPos.push_back(Point2i(0, 1));
  41. if (NeihborMode == 1)
  42. {
  43. //cout << "Neighbor mode: 8邻域." << endl;
  44. NeihborPos.push_back(Point2i(-1, -1));
  45. NeihborPos.push_back(Point2i(-1, 1));
  46. NeihborPos.push_back(Point2i(1, -1));
  47. NeihborPos.push_back(Point2i(1, 1));
  48. }
  49. else int a = 0;//cout << "Neighbor mode: 4邻域." << endl;
  50. int NeihborCount = 4 + 4 * NeihborMode;
  51. int CurrX = 0, CurrY = 0;
  52. //开始检测
  53. for (int i = 0; i < Src.rows; i++)
  54. {
  55. for (int j = 0; j < Src.cols; j++)
  56. {
  57. if (PointLabel.at<uchar>(i, j) == 0)//标签图像像素点为0,表示还未检查的不合格点
  58. { //开始检查
  59. vector<Point2i>GrowBuffer;//记录检查像素点的个数
  60. GrowBuffer.push_back(Point2i(j, i));
  61. PointLabel.at<uchar>(i, j) = 1;//标记为正在检查
  62. int CheckResult = 0;
  63. for (int z = 0; z < GrowBuffer.size(); z++)
  64. {
  65. for (int q = 0; q < NeihborCount; q++)
  66. {
  67. CurrX = GrowBuffer.at(z).x + NeihborPos.at(q).x;
  68. CurrY = GrowBuffer.at(z).y + NeihborPos.at(q).y;
  69. if (CurrX >= 0 && CurrX<Src.cols&&CurrY >= 0 && CurrY<Src.rows) //防止越界
  70. {
  71. if (PointLabel.at<uchar>(CurrY, CurrX) == 0)
  72. {
  73. GrowBuffer.push_back(Point2i(CurrX, CurrY)); //邻域点加入buffer
  74. PointLabel.at<uchar>(CurrY, CurrX) = 1; //更新邻域点的检查标签,避免重复检查
  75. }
  76. }
  77. }
  78. }
  79. if (GrowBuffer.size()>AreaLimit) //判断结果(是否超出限定的大小),1为未超出,2为超出
  80. CheckResult = 2;
  81. else
  82. {
  83. CheckResult = 1;
  84. RemoveCount++;//记录有多少区域被去除
  85. }
  86. for (int z = 0; z < GrowBuffer.size(); z++)
  87. {
  88. CurrX = GrowBuffer.at(z).x;
  89. CurrY = GrowBuffer.at(z).y;
  90. PointLabel.at<uchar>(CurrY, CurrX) += CheckResult;//标记不合格的像素点,像素值为2
  91. }
  92. //********结束该点处的检查**********
  93. }
  94. }
  95. }
  96. CheckMode = 255 * (1 - CheckMode);
  97. //开始反转面积过小的区域
  98. for (int i = 0; i < Src.rows; ++i)
  99. {
  100. for (int j = 0; j < Src.cols; ++j)
  101. {
  102. if (PointLabel.at<uchar>(i, j) == 2)
  103. {
  104. Dst.at<uchar>(i, j) = CheckMode;
  105. }
  106. else if (PointLabel.at<uchar>(i, j) == 3)
  107. {
  108. Dst.at<uchar>(i, j) = Src.at<uchar>(i, j);
  109. }
  110. }
  111. }
  112. //cout << RemoveCount << " objects removed." << endl;
  113. }
  114. //=======函数实现=====================================================================
  115. //=======调用函数=====================================================================
  116. Mat img;
  117. img = imread("D:\\1_1.jpg", 0);//读取图片
  118. threshold(img, img, 128, 255, CV_THRESH_BINARY_INV);
  119. imshow("去除前", img);
  120. Mat img1;
  121. RemoveSmallRegion(img, img, 200, 0, 1);
  122. imshow("去除后", img);
  123. waitKey(0);
  124. //=======调用函数=====================================================================

此段代码包含一个名为RemoveSmallRegion的函数,其功能是从给定的二值图像中移除符合条件的小连通区域。函数接受五个参数:

  1. Mat &Src: 输入的原始二值图像(单通道,通常为黑白图像)。
  2. Mat &Dst: 输出的目标图像,存储经过处理后的结果。
  3. int AreaLimit: 面积阈值,低于该阈值的连通区域会被移除。
  4. int CheckMode: 检查模式,决定要移除的是图像中的小连通白区还是小连通黑区。
    • CheckMode == 1: 移除小连通白区(白色像素点构成的区域)。
    • CheckMode == 0: 移除小连通黑区(黑色像素点构成的区域)。
  5. int NeihborMode: 邻域模式,决定采用4邻域还是8邻域算法进行连通区域扩展。
    • NeihborMode == 1: 使用8邻域算法(包括上下左右和四个对角方向相邻的像素)。
    • NeihborMode == 0: 使用4邻域算法(仅考虑上下左右相邻的像素)。

函数的具体实现步骤如下:

  1. 初始化RemoveCount变量记录移除的连通区域数量,创建与输入图像相同大小的PointLabel矩阵作为标签图像,用于记录每个像素点的检验状态(0:未检查;1:正在检查;2:检查不合格;3:检查合格或无需检查)。

  2. 根据CheckMode确定移除目标,分别针对小连通白区和小连通黑区对PointLabel进行初始化。对于不需要移除的像素点(即背景或前景),将其标签设为3,表示已检查且合格。

  3. 定义NeihborPos容器存储邻域位置,并根据NeihborMode选择使用4邻域或8邻域。

  4. 使用两层嵌套循环遍历输入图像的所有像素点。对于未检查的像素点(标签为0),执行以下操作:

    • 初始化GrowBuffer容器,用于记录当前连通区域内的像素点。
    • 将当前像素点标记为正在检查(标签设为1),并启动基于邻域扩展的生长过程。
    • 使用广度优先搜索(BFS)策略,依次访问GrowBuffer中的像素点及其邻域像素,将未检查的邻域像素加入GrowBuffer并标记为正在检查。
    • 当遍历完所有邻域像素后,根据GrowBuffer的大小与AreaLimit比较,判断该连通区域是否应被移除。
    • 根据判断结果更新GrowBuffer内所有像素点在PointLabel上的标签为2(检查不合格)或保持为1(检查合格)。
  5. 得到最终的PointLabel后,根据CheckMode255取反(即255 * (1 - CheckMode)),用于后续翻转图像像素值。遍历SrcPointLabel,将标签为2的像素点在Dst中翻转颜色(即将白变黑或黑变白),标签为3的像素点保持原色不变。

最后,代码提供了对RemoveSmallRegion函数的调用示例:

  • 读取图像"D:\1_1.jpg",并对其进行二值化处理(阈值为128,反相)。
  • 显示二值化处理后的原始图像。
  • 调用RemoveSmallRegion函数,移除面积小于200的黑区(CheckMode = 0),使用8邻域算法(NeihborMode = 1)。
  • 显示经过处理后的图像。
  • 等待用户按键后关闭窗口。

方法二流程:

  1. //测试
  2. void CCutImageVS2013Dlg::OnBnClickedTestButton1()
  3. {
  4. vector<vector<Point> > contours; //轮廓数组
  5. vector<Point2d> centers; //轮廓质心坐标
  6. vector<vector<Point> >::iterator itr; //轮廓迭代器
  7. vector<Point2d>::iterator itrc; //质心坐标迭代器
  8. vector<vector<Point> > con; //当前轮廓
  9. double area;
  10. double minarea = 1000;
  11. double maxarea = 0;
  12. Moments mom; // 轮廓矩
  13. Mat image, gray, edge, dst;
  14. image = imread("D:\\66.png");
  15. cvtColor(image, gray, COLOR_BGR2GRAY);
  16. Mat rgbImg(gray.size(), CV_8UC3); //创建三通道图
  17. blur(gray, edge, Size(3, 3)); //模糊去噪
  18. threshold(edge, edge, 200, 255, THRESH_BINARY_INV); //二值化处理,黑底白字
  19. //--------去除较小轮廓,并寻找最大轮廓--------------------------
  20. findContours(edge, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE); //寻找轮廓
  21. itr = contours.begin(); //使用迭代器去除噪声轮廓
  22. while (itr != contours.end())
  23. {
  24. area = contourArea(*itr); //获得轮廓面积
  25. if (area<minarea) //删除较小面积的轮廓
  26. {
  27. itr = contours.erase(itr); //itr一旦erase,需要重新赋值
  28. }
  29. else
  30. {
  31. itr++;
  32. }
  33. if (area>maxarea) //寻找最大轮廓
  34. {
  35. maxarea = area;
  36. }
  37. }
  38. dst = Mat::zeros(image.rows, image.cols, CV_8UC3);
  39. /*绘制连通区域轮廓,计算质心坐标*/
  40. Point2d center;
  41. itr = contours.begin();
  42. while (itr != contours.end())
  43. {
  44. area = contourArea(*itr);
  45. con.push_back(*itr); //获取当前轮廓
  46. if (area == maxarea)
  47. {
  48. vector<Rect> boundRect(1); //定义外接矩形集合
  49. boundRect[0] = boundingRect(Mat(*itr));
  50. cvtColor(gray, rgbImg, COLOR_GRAY2BGR);
  51. Rect select;
  52. select.x = boundRect[0].x;
  53. select.y = boundRect[0].y;
  54. select.width = boundRect[0].width;
  55. select.height = boundRect[0].height;
  56. rectangle(rgbImg, select, Scalar(0, 255, 0), 3, 2); //用矩形画矩形窗
  57. drawContours(dst, con, -1, Scalar(0, 0, 255), 2); //最大面积红色绘制
  58. }
  59. else
  60. drawContours(dst, con, -1, Scalar(255, 0, 0), 2); //其它面积蓝色绘制
  61. con.pop_back();
  62. //计算质心
  63. mom = moments(*itr);
  64. center.x = (int)(mom.m10 / mom.m00);
  65. center.y = (int)(mom.m01 / mom.m00);
  66. centers.push_back(center);
  67. itr++;
  68. }
  69. imshow("rgbImg", rgbImg);
  70. //imshow("gray", gray);
  71. //imshow("edge", edge);
  72. imshow("origin", image);
  73. imshow("connected_region", dst);
  74. waitKey(0);
  75. return;
  76. }

提供的代码为一个使用OpenCV库对输入图像"D:\66.png"进行处理的C++实现,执行以下任务:

  1. 图像预处理:

    • 读取图像并将其从BGR色彩空间转换为灰度图像(cvtColor)。
    • 应用高斯模糊,使用大小为3x3的核来减少噪声(blur)。
    • 对模糊后的图像执行二值阈值处理,阈值设为200,将高于该值的像素设置为白色,其余为黑色(threshold)。
  2. 轮廓检测与筛选:

    • 使用findContours函数在二值化图像上查找外部轮廓,存储在contours容器中。
    • 遍历所有轮廓,通过contourArea函数计算每个轮廓的面积。
      • 删除面积小于最小阈值minarea(初始设定为1000)的噪声轮廓,使用迭代器itr进行动态删除。
      • 同时记录下当前遍历到的最大轮廓面积maxarea
    • 最后保留下来的轮廓为满足面积条件的有效轮廓。
  3. 绘制轮廓与计算质心:

    • 创建一个新的Mat对象dst,用于绘制处理结果。
    • 初始化一个空的centers向量,用于存储各个轮廓的质心坐标。
    • 再次遍历有效轮廓:
      • 将当前轮廓添加到临时向量con中。
      • 计算当前轮廓面积。
      • 如果面积等于最大面积maxarea,则执行以下操作:
        • 计算当前轮廓的外接矩形,并用绿色边框在RGB图像rgbImg上绘制。
        • 在最终输出图像dst上以红色绘制当前轮廓。
      • 否则,在dst上以蓝色绘制当前轮廓。
      • 使用moments函数计算当前轮廓的矩,进而得到质心坐标,并将其添加到centers向量。
      • 清除临时向量con中的当前轮廓。
    • 显示各阶段处理结果:
      • RGB图像rgbImg(仅包含最大轮廓的绿色外接矩形)。
      • 原始灰度图像gray(注释掉未显示)。
      • 二值边缘图像edge(注释掉未显示)。

【C#版本效果展示】

方法一使用opencvsharp效果:

方法二opencvsharp效果:

可见已经用opencvsharp复刻C++版本算法。

【测试环境】

vs2019

netframework4.7.2

opencvsharp4.8.0

【源码下载地址】

https://download.csdn.net/download/FL1623863129/89074335

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/380041
推荐阅读
相关标签
  

闽ICP备14008679号