一、图像分割之漫水填充
1、漫水填充方法介绍
漫水填充算法步骤:
- 选择种子点
- 以种子为中心,判断4-邻域或者8-邻域的像素值与种子点像素值的差值,将差值小于阈值的像素点添加进区域内
- 将新加入的像素点作为新的种子点,反复执行Step2,直到没有新的像素点被添加进该区域
2、相关函数
cpp
/* 用途:用于从指定种子点开始,
将与该点颜色相近且连通的区域
替换为新的颜色 */
int cvfloodFill( InputOutputArray image, InputOutputArray mask,
Point seedPoint, Scalar newVal, CV_OUT Rect* rect=0,
Scalar loDiff = Scalar(), Scalar upDiff = Scalar(),
int flags = 4 );
/*
image:输入/输出图像,图像数据类型可以为CV_8U或者CV_32F的单通道或者三通道图像
mask:掩码矩阵,尺寸必须比image宽和高各大2个像素,用于标记漫水填充的区域
seedPoint:种子点坐标,从该点开始进行区域生长与填充
newVal:归入种子点区域内像素点的新像素值
rect:种子点漫水填充区域的最小矩形边界,默认值为0,表示不输出边界
loDiff:添加进种子点区域条件的下界差值
upDiff:添加进种子点区域条件的上界差值
flags:填充方式控制标志,由多个二进制位共同组成
低8位(0~7位):用于控制区域连通方式
4:表示4邻域填充,只考虑当前像素上下左右的相邻像素
8:表示8邻域填充,除了上下左右外,还考虑左上、右上、左下、右下四个对角方向像素
中间8位(8~15位):用于指定mask掩码图像中填充值
如果该部分值为0,
mask默认使用1填充
高8位(16~23位):用于控制填充策略
FLOODFILL_FIXED_RANGE:固定范围模式,当前像素与"种子点像素"比较颜色差值
默认模式:浮动范围模式,当前像素与"邻域像素"比较颜色差值,
因此区域可能不断扩张
FLOODFILL_MASK_ONLY:只填充mask掩码图像,不修改原始图像,此时newVal参数会被忽略
*/
3、示例代码
cpp
QString imgPath = QApplication::applicationDirPath() + "/Images";
cv::String s_imgPath = imgPath.toLocal8Bit().data();
Mat img = imread(s_imgPath + "/lena.jpg");
if (img.empty())
{
qDebug() << "图片加载失败, 请确认图像文件名称是否正确";
return;
}
RNG rng(10086);/*随机数,用于随机生成像素*/
/*设置操作标志flag*/
int connectivity = 4;/*连通邻域方式*/
int maskVal = 255;/*掩码图像的数值*/
int flags = connectivity | (maskVal << 8) | FLOODFILL_FIXED_RANGE;/*漫水填充操作方式标志*/
/*设置与选中像素点的差值*/
Scalar loDiff = Scalar(20, 20, 20);
Scalar upDiff = Scalar(20, 20, 20);
/*声明掩膜矩阵变量*/
Mat mask = Mat::zeros(img.rows + 2, img.cols + 2, CV_8UC1);
while (true)
{
/*随机产生图像中某一像素点*/
int py = rng.uniform(0, img.rows - 1);
int px = rng.uniform(0, img.cols - 1);
Point point = Point(px, py);
/*彩色图像中填充的像素值*/
Scalar newVal = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
Rect rect;
/*漫水填充函数*/
int area = floodFill(img, mask, point, newVal, &rect, loDiff, upDiff, flags);
/*输出像素点和填充的像素数目*/
cout << "Pixel point x: " << point.x << " y: " << point.y
<< " Number of filled pixels: " << area << endl;
/*输出填充的图像结果*/
imshow("img", img);
imshow("mask", mask);
int c = waitKey(0);
if ((c & 255) == 27)
{
break;
}
}
waitKey(0);
destroyAllWindows();
一、图像分割之分水岭法
1、分水岭方法介绍
分水岭算法步骤:
- 排序过程,首先对图像像素的灰度级进行排序,确定灰度值较小的像素点,该像素点即为开始注水点
- 淹没过程,对每个最低点开始不断注水,不断掩膜周围的像素点,不同注水点的水汇集在一起,形成分割线
2、相关函数
cpp
/* 用途:用于基于"分水岭算法"
对图像进行区域分割 */
void cv::watershed( InputArray image, InputOutputArray markers );
/*
image:输入图像,数据类型为CV_8U的三通道图像
markers:输入/输出CV_32S的单通道图像的标记结果,与原图像具有相同的尺寸
*/
3、示例代码
cpp
QString imgPath = QApplication::applicationDirPath() + "/Images";
cv::String s_imgPath = imgPath.toLocal8Bit().data();
Mat img, imgGray, imgMask, img_;
Mat maskWaterShed;/*watershed()函数的参数*/
img = imread(s_imgPath + "/lenaw.jpg");/*含有标记的图像*/
img_ = imread(s_imgPath + "/lena.jpg");/*原图像*/
cvtColor(img, imgGray, COLOR_BGR2GRAY);
/*二值化并开运算*/
threshold(imgGray, imgMask, 254, 255, THRESH_BINARY);
Mat k = getStructuringElement(0, Size(3, 3));
morphologyEx(imgMask, imgMask, MORPH_OPEN, k);
imshow("mark img", img);
imshow("img", img_);
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(imgMask, contours, hierarchy, RETR_CCOMP, CHAIN_APPROX_SIMPLE);
/*在maskWaterShed上绘制轮廓,用于输入分水岭算法*/
maskWaterShed = Mat::zeros(imgMask.size(), CV_32S);
for (int index = 0; index < contours.size(); index++)
{
drawContours(maskWaterShed, contours, index, Scalar::all(index + 1), -1, 8, hierarchy, INT_MAX);
}
/*分水岭算法 需要对原图像进行处理*/
watershed(img_, maskWaterShed);
vector<Vec3b> colors;/*随机生成几种颜色*/
for (int i = 0; i < contours.size(); i++)
{
int b = theRNG().uniform(0, 255);
int g = theRNG().uniform(0, 255);
int r = theRNG().uniform(0, 255);
colors.push_back(Vec3b((uchar)b, (uchar)g, (uchar)r));
}
Mat resultImg = Mat(img.size(), CV_8UC3);/*显示图像*/
for (int i = 0; i < imgMask.rows; i++)
{
for (int j = 0; j < imgMask.cols; j++)
{
/*绘制每个区域的颜色*/
int index = maskWaterShed.at<int>(i, j);
if (index == -1)/*区域间的值被置为-1(边界)*/
{
resultImg.at<Vec3b>(i, j) = Vec3b(255, 255, 255);
}
else if (index <= 0 || index > contours.size())/*没有标记清除的区域被置为0*/
{
resultImg.at<Vec3b>(i, j) = Vec3b(0, 0, 0);
}
else/*其他每个区域的值保持不变*/
{
resultImg.at<Vec3b>(i, j) = colors[index - 1];/*把这些区域绘制成不同颜色*/
}
}
}
imshow("resultImg", resultImg);
resultImg = resultImg * 0.8 + img_ * 0.2;
waitKey(0);
imshow("watershed resultImg", resultImg);
/*绘制每个区域的图像*/
for (int n = 1; n <= contours.size(); n++)
{
Mat resImage1 = Mat(img.size(), CV_8UC3);/*声明一个最后要显示的图像*/
for (int i = 0; i < imgMask.cols; i++)
{
for (int j = 0; j < imgMask.rows; j++)
{
int index = maskWaterShed.at<int>(i, j);
if (index == n)
{
resImage1.at<Vec3b>(i, j) = img_.at<Vec3b>(i, j);
}
else
{
resImage1.at<Vec3b>(i, j) = Vec3b(0, 0, 0);
}
}
}
/*显示图像*/
imshow(to_string(n), resImage1);
}
waitKey(0);
destroyAllWindows();