OpenCV实现连通区域填充种⼦填充法OpenCV实现连通区域填充
前⾔
本博客主要解决的问题来源于数据结构⽼师的⼀次作业,作业内容如下图所⽰。
要处理的图像如下:
环境配置
VS2019
C++
OpenCV-4.1.0
第⼀部分:使⽤轮廓查和漫⽔填充的⽅法实现区域染⾊
流程图:
源程序代码:
void deal_test_1()
{
Mat test_1_gray, test_1_threshold, test_1_gauss;
Mat test_1_sobelx, test_1_sobely, test_1_sobelxy;
Mat test_1_origin = imread("C:\\Users\\17513\\Desktop\\数据结构报告\\栈和队列\\test.jpg");
Mat test_1_copy = test_1_origin.clone();
/*转换为灰度图*/
cvtColor(test_1_origin, test_1_gray, COLOR_BGR2GRAY);
/
*⾼斯滤波*/
GaussianBlur(test_1_gray, test_1_gauss, Size(5, 5), 0, 0);
/*⼆值化*/
threshold(test_1_gauss, test_1_threshold, 127, 255, THRESH_BINARY);
/*Sobel算⼦*/
Sobel(test_1_threshold, test_1_sobelx, CV_64F, 1, 0, 3);
convertScaleAbs(test_1_sobelx, test_1_sobelx);
Sobel(test_1_threshold, test_1_sobely, CV_64F, 0, 1, 3);
convertScaleAbs(test_1_sobely, test_1_sobely);
addWeighted(test_1_sobelx, 1, test_1_sobely, 1, 0, test_1_sobelxy);
/*再次⼆值化*/
threshold(test_1_sobelxy, test_1_threshold, 127, 255, THRESH_BINARY);
/*寻轮廓*/
vector<vector<Point>> contours;
findContours(test_1_threshold, contours, RETR_EXTERNAL, CHAIN_APPROX_NONE);
/*最⼩外接矩形*/
Point2f rect[4];
vector<Rect> boundRect(contours.size());  //定义外接矩形集合
vector<RotatedRect> box(contours.size()); //定义最⼩外接矩形集合
srand((int)time(0));
for (int i = 0; i < contours.size(); i++)
{
box[i] = minAreaRect(Mat(contours[i]));  //计算每个轮廓最⼩外接矩形
box[i].points(rect);  //把最⼩外接矩形四个端点复制给rect数组
floodFill(test_1_copy, Point(box[i].center.x, box[i].center.y), Scalar(rand() % 255, rand() & 255, rand() % 255), &boundRect[i], Scalar(20, 20, 20), Scal ar(20, 20, 20));
}
cv_show("666", test_1_copy);
}
代码分析:
1. 这部分代码⽐较基础。⾸先对图⽚进⾏基本的处理,再使⽤轮廓查的⽅式的图形的轮廓。再通过轮廓算出其最⼩外接矩形,这样
就可以⼤致确定每个图形所在的区域,也就是ROI区域。
2. 在获取ROI区域后的难点是如何对图像进⾏染⾊,⽽且还要保证每个图形染的颜⾊是不同的。
对于染⾊⽅法我这⾥使⽤的是漫⽔填充的算法。这个算法参考
具体函数的使⽤⽅法也⽐较简单。函数需要提供seedPoint即漫⽔填充算法的起点,在程序中这个点我给的是每个图像最⼩外接矩形的中⼼点。此外还需要提供填充的颜⾊,为了保证颜⾊的不同,采⽤随机数的⽅式选择不⽤的BGR颜⾊,随机数的范围是0到255.
效果图:
程序最终效果还可以接受,但有些图形的外边明显没有进⾏染⾊,这可能是因为图像经过基本处理后发⽣改变与原图像不同导致。
第⼆部分:使⽤队列实现种⼦填充法
流程图:
源程序代码:
基本的图像处理:
Mat labelImg;
Mat colorLabelImg;
Mat test_1_gray, test_1_threshold, test_1_gauss;
Mat test_1_origin = imread("C:\\Users\\17513\\Desktop\\数据结构报告\\栈和队列\\test.jpg");    Mat test_1_copy;
cvtColor(test_1_origin, test_1_gray, COLOR_BGR2GRAY);
GaussianBlur(test_1_gray, test_1_gauss, Size(5, 5), 0, 0);
threshold(test_1_gauss, test_1_threshold, 127, 255, THRESH_BINARY);
代码分析:
⼀些基本的图像处理。灰度图、⾼斯滤波和⼆值化。
种⼦填充法相关代码:
void SeedFillOld(const Mat& binImg, Mat& lableImg)
{
if (pty() || pe() != CV_8UC1)
{
return;
}
int label = 1;
rectangle函数opencvint rows = ws;
int cols = ls;
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
if (lableImg.at<int>(i, j) == 255)
{
queue<pair<int, int>> neighborPixels;
neighborPixels.push(pair<int, int>(i, j));    // 像素位置: <i,j>
++label;
while (!pty())
{
pair<int, int> curPixel = neighborPixels.front();
int curX = curPixel.first;
int curY = curPixel.second;
if (lableImg.at<int>(curX, curY) != label)
{
lableImg.at<int>(curX, curY) = label;
neighborPixels.pop();
if (lableImg.at<int>(curX, curY - 1) == 255)
{
neighborPixels.push(std::pair<int, int>(curX, curY - 1));                        }
if (lableImg.at<int>(curX, curY + 1) == 255)
{
neighborPixels.push(std::pair<int, int>(curX, curY + 1));                        }
if (lableImg.at<int>(curX - 1, curY) == 255)
{
neighborPixels.push(std::pair<int, int>(curX - 1, curY));                        }
if (lableImg.at<int>(curX + 1, curY) == 255)
{
neighborPixels.push(std::pair<int, int>(curX + 1, curY));                        }
}
else
{
neighborPixels.pop();
}
}
}
}
}
}
代码分析:
1. 种⼦填充法
参考博客:
在上⾯这两个博客中的种⼦填充法都是使⽤堆栈来实现的,因此在本程序中需要考虑换成队列。
2. 算法的简单分析:
(1) ⾸先需要获取原图像的列数和⾏数⽅便后⾯对每个像素点的访问。
(2) 通过遍历访问像素点,如果像素点(i, j)的值等于255(⽩⾊点)则将其坐标点存⼊neighborPixels队列中,并且标签label加1。
(3) 如果neighborPixels队列⾮空,则取出neighborPixels队列的队头。判断队头代表的像素点是否与当前label相等,如果相等则直接删除并重复步骤(3),否则进⾏步骤(4)。如果neighborPixels队列为空则执⾏步骤(2)。
(4) 将队头点赋值为label并从队列中删除。对队头点进⾏4领域判断。上下左右四个点,哪个点的像素值为255哪个值就⼊队。重复步骤(3)。
(5) 当所有像素点被遍历完之后种⼦填充法结束。
3. 种⼦填充法⽐较容易理解,我认为难点在于将原本代码中的堆栈转换为队列。经过仔细分析,发现如果仅仅是把堆栈换成队列会导致
代码重复,及会出现⼀个像素点被多次访问的情况。为了解决这个问题,我在程序中多加了⼀个判断
(第135⾏)。因为⼀个像素点被访问后会被“贴上”值为label的“标签”,所以对像素点的“标签”进⾏判断就可以知道这个点有没有被访问过。如果访问过则直接删除,否则正常执⾏程序即可。
4. 本程序中使⽤的是4领域,还可以换成8领域,不过我没试过,不知道效果怎么样。
第三部分:对图像染⾊
源程序代码:

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。