赞
踩
【C++介绍】
关于opencv实现有比较好的算法,可以参考这个博客OpenCV去除面积较小的连通域_c#opencv 筛选小面积区域-CSDN博客
但是没有对应opencvsharp实现同类算法,为了照顾懂C#编程同学们,因此将 去除面积较小的连通域算法转成C#代码。
方法一流程:
- //=======函数实现=====================================================================
- void RemoveSmallRegion(Mat &Src, Mat &Dst, int AreaLimit, int CheckMode, int NeihborMode)
- {
- int RemoveCount = 0;
- //新建一幅标签图像初始化为0像素点,为了记录每个像素点检验状态的标签,0代表未检查,1代表正在检查,2代表检查不合格(需要反转颜色),3代表检查合格或不需检查
- //初始化的图像全部为0,未检查
- Mat PointLabel = Mat::zeros(Src.size(), CV_8UC1);
- if (CheckMode == 1)//去除小连通区域的白色点
- {
- //cout << "去除小连通域.";
- for (int i = 0; i < Src.rows; i++)
- {
- for (int j = 0; j < Src.cols; j++)
- {
- if (Src.at<uchar>(i, j) < 10)
- {
- PointLabel.at<uchar>(i, j) = 3;//将背景黑色点标记为合格,像素为3
- }
- }
- }
- }
- else//去除孔洞,黑色点像素
- {
- //cout << "去除孔洞";
- for (int i = 0; i < Src.rows; i++)
- {
- for (int j = 0; j < Src.cols; j++)
- {
- if (Src.at<uchar>(i, j) > 10)
- {
- PointLabel.at<uchar>(i, j) = 3;//如果原图是白色区域,标记为合格,像素为3
- }
- }
- }
- }
-
-
- vector<Point2i>NeihborPos;//将邻域压进容器
- NeihborPos.push_back(Point2i(-1, 0));
- NeihborPos.push_back(Point2i(1, 0));
- NeihborPos.push_back(Point2i(0, -1));
- NeihborPos.push_back(Point2i(0, 1));
- if (NeihborMode == 1)
- {
- //cout << "Neighbor mode: 8邻域." << endl;
- NeihborPos.push_back(Point2i(-1, -1));
- NeihborPos.push_back(Point2i(-1, 1));
- NeihborPos.push_back(Point2i(1, -1));
- NeihborPos.push_back(Point2i(1, 1));
- }
- else int a = 0;//cout << "Neighbor mode: 4邻域." << endl;
- int NeihborCount = 4 + 4 * NeihborMode;
- int CurrX = 0, CurrY = 0;
- //开始检测
- for (int i = 0; i < Src.rows; i++)
- {
- for (int j = 0; j < Src.cols; j++)
- {
- if (PointLabel.at<uchar>(i, j) == 0)//标签图像像素点为0,表示还未检查的不合格点
- { //开始检查
- vector<Point2i>GrowBuffer;//记录检查像素点的个数
- GrowBuffer.push_back(Point2i(j, i));
- PointLabel.at<uchar>(i, j) = 1;//标记为正在检查
- int CheckResult = 0;
-
- for (int z = 0; z < GrowBuffer.size(); z++)
- {
- for (int q = 0; q < NeihborCount; q++)
- {
- CurrX = GrowBuffer.at(z).x + NeihborPos.at(q).x;
- CurrY = GrowBuffer.at(z).y + NeihborPos.at(q).y;
- if (CurrX >= 0 && CurrX<Src.cols&&CurrY >= 0 && CurrY<Src.rows) //防止越界
- {
- if (PointLabel.at<uchar>(CurrY, CurrX) == 0)
- {
- GrowBuffer.push_back(Point2i(CurrX, CurrY)); //邻域点加入buffer
- PointLabel.at<uchar>(CurrY, CurrX) = 1; //更新邻域点的检查标签,避免重复检查
- }
- }
- }
- }
- if (GrowBuffer.size()>AreaLimit) //判断结果(是否超出限定的大小),1为未超出,2为超出
- CheckResult = 2;
- else
- {
- CheckResult = 1;
- RemoveCount++;//记录有多少区域被去除
- }
-
- for (int z = 0; z < GrowBuffer.size(); z++)
- {
- CurrX = GrowBuffer.at(z).x;
- CurrY = GrowBuffer.at(z).y;
- PointLabel.at<uchar>(CurrY, CurrX) += CheckResult;//标记不合格的像素点,像素值为2
- }
- //********结束该点处的检查**********
- }
- }
- }
- CheckMode = 255 * (1 - CheckMode);
- //开始反转面积过小的区域
- for (int i = 0; i < Src.rows; ++i)
- {
- for (int j = 0; j < Src.cols; ++j)
- {
- if (PointLabel.at<uchar>(i, j) == 2)
- {
- Dst.at<uchar>(i, j) = CheckMode;
- }
- else if (PointLabel.at<uchar>(i, j) == 3)
- {
- Dst.at<uchar>(i, j) = Src.at<uchar>(i, j);
-
- }
- }
- }
- //cout << RemoveCount << " objects removed." << endl;
- }
- //=======函数实现=====================================================================
-
- //=======调用函数=====================================================================
- Mat img;
- img = imread("D:\\1_1.jpg", 0);//读取图片
- threshold(img, img, 128, 255, CV_THRESH_BINARY_INV);
- imshow("去除前", img);
- Mat img1;
- RemoveSmallRegion(img, img, 200, 0, 1);
- imshow("去除后", img);
- waitKey(0);
- //=======调用函数=====================================================================
此段代码包含一个名为RemoveSmallRegion
的函数,其功能是从给定的二值图像中移除符合条件的小连通区域。函数接受五个参数:
Mat &Src
: 输入的原始二值图像(单通道,通常为黑白图像)。Mat &Dst
: 输出的目标图像,存储经过处理后的结果。int AreaLimit
: 面积阈值,低于该阈值的连通区域会被移除。int CheckMode
: 检查模式,决定要移除的是图像中的小连通白区还是小连通黑区。
CheckMode == 1
: 移除小连通白区(白色像素点构成的区域)。CheckMode == 0
: 移除小连通黑区(黑色像素点构成的区域)。int NeihborMode
: 邻域模式,决定采用4邻域还是8邻域算法进行连通区域扩展。
NeihborMode == 1
: 使用8邻域算法(包括上下左右和四个对角方向相邻的像素)。NeihborMode == 0
: 使用4邻域算法(仅考虑上下左右相邻的像素)。函数的具体实现步骤如下:
初始化RemoveCount
变量记录移除的连通区域数量,创建与输入图像相同大小的PointLabel
矩阵作为标签图像,用于记录每个像素点的检验状态(0:未检查;1:正在检查;2:检查不合格;3:检查合格或无需检查)。
根据CheckMode
确定移除目标,分别针对小连通白区和小连通黑区对PointLabel
进行初始化。对于不需要移除的像素点(即背景或前景),将其标签设为3,表示已检查且合格。
定义NeihborPos
容器存储邻域位置,并根据NeihborMode
选择使用4邻域或8邻域。
使用两层嵌套循环遍历输入图像的所有像素点。对于未检查的像素点(标签为0),执行以下操作:
GrowBuffer
容器,用于记录当前连通区域内的像素点。GrowBuffer
中的像素点及其邻域像素,将未检查的邻域像素加入GrowBuffer
并标记为正在检查。GrowBuffer
的大小与AreaLimit
比较,判断该连通区域是否应被移除。GrowBuffer
内所有像素点在PointLabel
上的标签为2(检查不合格)或保持为1(检查合格)。得到最终的PointLabel
后,根据CheckMode
对255
取反(即255 * (1 - CheckMode)
),用于后续翻转图像像素值。遍历Src
和PointLabel
,将标签为2的像素点在Dst
中翻转颜色(即将白变黑或黑变白),标签为3的像素点保持原色不变。
最后,代码提供了对RemoveSmallRegion
函数的调用示例:
RemoveSmallRegion
函数,移除面积小于200的黑区(CheckMode = 0
),使用8邻域算法(NeihborMode = 1
)。方法二流程:
- //测试
- void CCutImageVS2013Dlg::OnBnClickedTestButton1()
- {
- vector<vector<Point> > contours; //轮廓数组
- vector<Point2d> centers; //轮廓质心坐标
- vector<vector<Point> >::iterator itr; //轮廓迭代器
- vector<Point2d>::iterator itrc; //质心坐标迭代器
- vector<vector<Point> > con; //当前轮廓
-
-
- double area;
- double minarea = 1000;
- double maxarea = 0;
- Moments mom; // 轮廓矩
- Mat image, gray, edge, dst;
-
- image = imread("D:\\66.png");
- cvtColor(image, gray, COLOR_BGR2GRAY);
- Mat rgbImg(gray.size(), CV_8UC3); //创建三通道图
- blur(gray, edge, Size(3, 3)); //模糊去噪
- threshold(edge, edge, 200, 255, THRESH_BINARY_INV); //二值化处理,黑底白字
-
- //--------去除较小轮廓,并寻找最大轮廓--------------------------
- findContours(edge, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE); //寻找轮廓
- itr = contours.begin(); //使用迭代器去除噪声轮廓
- while (itr != contours.end())
- {
- area = contourArea(*itr); //获得轮廓面积
- if (area<minarea) //删除较小面积的轮廓
- {
- itr = contours.erase(itr); //itr一旦erase,需要重新赋值
- }
- else
- {
- itr++;
- }
- if (area>maxarea) //寻找最大轮廓
- {
- maxarea = area;
- }
- }
-
- dst = Mat::zeros(image.rows, image.cols, CV_8UC3);
- /*绘制连通区域轮廓,计算质心坐标*/
- Point2d center;
- itr = contours.begin();
- while (itr != contours.end())
- {
- area = contourArea(*itr);
- con.push_back(*itr); //获取当前轮廓
- if (area == maxarea)
- {
- vector<Rect> boundRect(1); //定义外接矩形集合
- boundRect[0] = boundingRect(Mat(*itr));
- cvtColor(gray, rgbImg, COLOR_GRAY2BGR);
- Rect select;
- select.x = boundRect[0].x;
- select.y = boundRect[0].y;
- select.width = boundRect[0].width;
- select.height = boundRect[0].height;
- rectangle(rgbImg, select, Scalar(0, 255, 0), 3, 2); //用矩形画矩形窗
-
-
- drawContours(dst, con, -1, Scalar(0, 0, 255), 2); //最大面积红色绘制
- }
- else
- drawContours(dst, con, -1, Scalar(255, 0, 0), 2); //其它面积蓝色绘制
-
- con.pop_back();
-
- //计算质心
- mom = moments(*itr);
- center.x = (int)(mom.m10 / mom.m00);
- center.y = (int)(mom.m01 / mom.m00);
- centers.push_back(center);
-
- itr++;
- }
-
- imshow("rgbImg", rgbImg);
- //imshow("gray", gray);
- //imshow("edge", edge);
- imshow("origin", image);
- imshow("connected_region", dst);
- waitKey(0);
- return;
-
- }
提供的代码为一个使用OpenCV库对输入图像"D:\66.png"进行处理的C++实现,执行以下任务:
图像预处理:
cvtColor
)。3x3
的核来减少噪声(blur
)。200
,将高于该值的像素设置为白色,其余为黑色(threshold
)。轮廓检测与筛选:
findContours
函数在二值化图像上查找外部轮廓,存储在contours
容器中。contourArea
函数计算每个轮廓的面积。
minarea
(初始设定为1000
)的噪声轮廓,使用迭代器itr
进行动态删除。maxarea
。绘制轮廓与计算质心:
Mat
对象dst
,用于绘制处理结果。centers
向量,用于存储各个轮廓的质心坐标。con
中。maxarea
,则执行以下操作:
rgbImg
上绘制。dst
上以红色绘制当前轮廓。dst
上以蓝色绘制当前轮廓。moments
函数计算当前轮廓的矩,进而得到质心坐标,并将其添加到centers
向量。con
中的当前轮廓。rgbImg
(仅包含最大轮廓的绿色外接矩形)。gray
(注释掉未显示)。edge
(注释掉未显示)。【C#版本效果展示】
方法一使用opencvsharp效果:
方法二opencvsharp效果:
可见已经用opencvsharp复刻C++版本算法。
【测试环境】
vs2019
netframework4.7.2
opencvsharp4.8.0
【源码下载地址】
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。