18.1 概述
图像分割(Image Segmentation)是对图像中指定对象进行定位轮廓分割。图像分割的目的是将图像中像素根据一定规则分为若干个簇(cluster)集合,每个集合包办一类像素。根据算法可分为监督学习和无监督学习。
距离变换:可以参考点与多边形的关系,可以算出每个像素点到多边形轮廓的距离得到距离图像。距离变换常见的算法有:不断膨胀/腐蚀得到和基于倒角距离两种。定义是计算一个图像中非零像素点到最近的零像素点的距离,也就是到零像素点的最短距离。距离变换将二值图像变换为灰度图像(其中每个像素的灰度值等于他到最近目标点的距离)。
分水岭变换:可以将像素灰度值比拟为山地拓扑图,灰度值的大小相当于海拔高度。常见算法是基于浸泡理论实现的。
18.2 相关API
·void distanceTransform( InputArray src, OutputArray det, OutputArray labels, int distanceType, int maskSize, int labelType=DIST_LABEL_CCOMP)
参数 | 含义 | |
作用 | 距离变换,用于计算图像中每一个非零点像素与其最近的零点像素之间的距离,输出的是保存每一个非零点与最近零点的距离信息 | |
输入 | src | 多边形轮廓 |
dst | 输出距离图像,8位整型会32位浮点型单通道 | |
labels | 输出的二维标签(离散维诺图),类型CV_32SC1 | |
distanceType | 距离类型: | |
maskSize | 距离掩膜的大小3或5,推荐用3 | |
labelType | 输出二维数组的类型 | |
返回值 | void |
·void watershed( InputArray image, InputOutputArray markers)
参数 | 含义 | |
作用 | 分水岭算法 | |
输入 | image | 原图 |
markers | 分水岭的种子信息,包括前景,背景,和未知区域 | |
返回值 | void |
18.3 分水岭处理流程
18.3.1 图像二值化(得到准确的目标位置)
不同的图像获取二值图的方式可能需要根据图像的实际情况进行图像的预处理,已得到目标物的区域二值图。
vector<Mat> sigleImgs;
split(src, sigleImgs);
Mat bImg = sigleImgs[0];
Mat gImg = sigleImgs[1];
Mat rImg = sigleImgs[2];
//二值化,分两段进行阈值处理
Mat binaryImg1, binaryImg2, binaryImg3;
Mat grayImg;
cvtColor(src, grayImg,COLOR_BGR2GRAY);
imshow("grayImg", grayImg);
threshold(rImg, binaryImg1,190,255,THRESH_BINARY);
//金色硬币的灰度值在整体灰度值中间,进行分段处理
threshold(grayImg, binaryImg2, 137, 255, THRESH_TOZERO_INV);
threshold(binaryImg2, binaryImg3, 100, 255, THRESH_BINARY);
//利用闭、开操作实现填充和非目标物的去除
Mat closeImg1, openImg1;;
Mat closeKernel1 = getStructuringElement(MORPH_RECT, Size(5, 5));
Mat openKernel1 = getStructuringElement(MORPH_RECT, Size(19, 19));
morphologyEx(binaryImg1, closeImg1, MORPH_CLOSE, closeKernel1);
morphologyEx(closeImg1, openImg1, MORPH_OPEN, openKernel1);
Mat closeImg, openImg;
Mat closeKernel = getStructuringElement(MORPH_RECT,Size(7,7));
Mat openKernel = getStructuringElement(MORPH_RECT, Size(29, 29));
morphologyEx(binaryImg3, closeImg, MORPH_CLOSE,closeKernel);
morphologyEx(closeImg, openImg, MORPH_OPEN,openKernel);
//合并图像
Mat dstBinary;
bitwise_or(openImg1, openImg, dstBinary);
imshow("dstBinary", dstBinary);
18.3.2 找到背景
通过形态学膨胀,放大目标区域,确定背景位置;
//膨胀,使边缘区域变大
Mat backImg=Mat(dstBinary.size(),CV_8UC1);
Mat dilateKernel = getStructuringElement(MORPH_RECT, Size(5, 5));
dilate(dstBinary, backImg, dilateKernel);
//imshow("backImg", backImg);
18.3.3 距离变化
//距离变换
Mat distImg;
distanceTransform(dstBinary, distImg,DIST_L2,3);
//归一化
normalize(distImg, distImg,0,1,NORM_MINMAX);
imshow("distImg", distImg)
18.3.4 找到前景盆底
在距离变换的基础上找到每个目标的盆地中心,得到前景
//再次二值化,前景盆地
threshold(distImg, distImg,0.3*1,1,THRESH_BINARY);
normalize(distImg, distImg, 0, 255, NORM_MINMAX);
Mat k1 = Mat::ones(3, 3, CV_8UC1);
erode(distImg, distImg,k1,Point(-1,-1));
imshow("distImg00", distImg);
18.3.5 获取边缘可能位置
//求边缘的可能区域
Mat dist8u;
distImg.convertTo(dist8u, CV_8U);
//前景背景相减
Mat unknowPos;
subtract(backImg, dist8u, unknowPos);
imshow("unknowPos", unknowPos);
18.3.5 创建markers
//计算连通域,构造marker
Mat markersTemp;
int num = connectedComponents(dist8u, markersTemp);
markersTemp = markersTemp + 1;
for (int i = 0; i < dist8u.rows; i++)
{
for (int j = 0; j < dist8u.cols; j++)
{
if (unknowPos.at<uchar>(i, j) == 255)
{
markersTemp.at<int>(i, j) = 0;
}
else;
}
}
18.3.6 分水岭分割
//分水岭算法
watershed(src, markersTemp);
18.3.7 区域标注
// 随机生成num-1种颜色,1背景
vector<Vec3b> colors;
for (int i = 1; i < num; i++)
{
int b = rng.uniform(0, 255);
int g = rng.uniform(0, 255);
int r = rng.uniform(0, 255);
colors.push_back(Vec3b((uchar)b, (uchar)g, (uchar)r));
}
Mat resultImg = Mat(src.size(), CV_8UC3); //显示图像
//src.copyTo(resultImg);
for (int i = 0; i < resultImg.rows; i++)
{
for (int j = 0; j < resultImg.cols; j++)
{
// 绘制每个区域的颜色
int index = markersTemp.at<int>(i, j);
if (index == -1) // 区域间的值被置为-1(边界)
{
resultImg.at<Vec3b>(i, j) = Vec3b(0, 0, 255);
}
else if (index > 1&& index <9)
{
resultImg.at<Vec3b>(i, j) = colors[index - 2];
}
else;
}
}
resultImg = resultImg*0.6 + src*0.4;
imshow("resultImg", resultImg);
18.4 完整代码示例
int main(int argc, char** argv)
{
RNG rng(12345);
//源图像
src = imread("D:\\testimg\\CSet12\\yingbi.png");
if (src.empty())
{
printf("Could not load the image...\n");
return -1;
}
else;
namedWindow("inputImg", WINDOW_AUTOSIZE);
imshow("inputImg", src);
namedWindow(OUTPUTWIN, WINDOW_AUTOSIZE);
vector<Mat> sigleImgs;
split(src, sigleImgs);
Mat bImg = sigleImgs[0];
Mat gImg = sigleImgs[1];
Mat rImg = sigleImgs[2];
//二值化
Mat binaryImg1, binaryImg2, binaryImg3;
Mat grayImg;
cvtColor(src, grayImg,COLOR_BGR2GRAY);
imshow("grayImg", grayImg);
threshold(rImg, binaryImg1,190,255,THRESH_BINARY);
threshold(grayImg, binaryImg2, 137, 255, THRESH_TOZERO_INV);
threshold(binaryImg2, binaryImg3, 100, 255, THRESH_BINARY);
Mat closeImg1, openImg1;;
Mat closeKernel1 = getStructuringElement(MORPH_RECT, Size(5, 5));
Mat openKernel1 = getStructuringElement(MORPH_RECT, Size(19, 19));
morphologyEx(binaryImg1, closeImg1, MORPH_CLOSE, closeKernel1);
morphologyEx(closeImg1, openImg1, MORPH_OPEN, openKernel1);
//imshow("closeImg1", closeImg1);
imshow("openImg1", openImg1);
Mat closeImg, openImg;
Mat closeKernel = getStructuringElement(MORPH_RECT,Size(7,7));
Mat openKernel = getStructuringElement(MORPH_RECT, Size(29, 29));
morphologyEx(binaryImg3, closeImg, MORPH_CLOSE,closeKernel);
morphologyEx(closeImg, openImg, MORPH_OPEN,openKernel);
//imshow("closeImg", closeImg);
imshow("openImg", openImg);
Mat dstBinary;
bitwise_or(openImg1, openImg, dstBinary);
imshow("dstBinary", dstBinary);
//膨胀,使边缘区域变大
Mat backImg=Mat(dstBinary.size(),CV_8UC1);
Mat dilateKernel = getStructuringElement(MORPH_RECT, Size(5, 5));
dilate(dstBinary, backImg, dilateKernel);
imshow("backImg", backImg);
//距离变换
Mat distImg;
distanceTransform(dstBinary, distImg,DIST_L2,3);
//归一化
normalize(distImg, distImg,0,1,NORM_MINMAX);
imshow("distImg", distImg);
//再次二值化,前景盆地
threshold(distImg, distImg,0.3*1,1,THRESH_BINARY);
normalize(distImg, distImg, 0, 255, NORM_MINMAX);
Mat k1 = Mat::ones(3, 3, CV_8UC1);
erode(distImg, distImg,k1,Point(-1,-1));
imshow("distImg00", distImg);
//marker标记
Mat dist8u;
distImg.convertTo(dist8u, CV_8U);
//求边缘的可能区域
Mat unknowPos;
subtract(backImg, dist8u, unknowPos);
imshow("unknowPos", unknowPos);
//计算连通域,构造marker
Mat markersTemp;
int num = connectedComponents(dist8u, markersTemp);
markersTemp = markersTemp + 1;
for (int i = 0; i < dist8u.rows; i++)
{
for (int j = 0; j < dist8u.cols; j++)
{
if (unknowPos.at<uchar>(i, j) == 255)
{
markersTemp.at<int>(i, j) = 0;
}
else;
}
}
//分水岭算法
watershed(src, markersTemp);
// 随机生成num-1种颜色,1背景
vector<Vec3b> colors;
for (int i = 1; i < num; i++)
{
int b = rng.uniform(0, 255);
int g = rng.uniform(0, 255);
int r = rng.uniform(0, 255);
colors.push_back(Vec3b((uchar)b, (uchar)g, (uchar)r));
}
Mat resultImg = Mat(src.size(), CV_8UC3); //显示图像
//src.copyTo(resultImg);
for (int i = 0; i < resultImg.rows; i++)
{
for (int j = 0; j < resultImg.cols; j++)
{
// 绘制每个区域的颜色
int index = markersTemp.at<int>(i, j);
if (index == -1) // 区域间的值被置为-1(边界)
{
resultImg.at<Vec3b>(i, j) = Vec3b(0, 0, 255);
}
else if (index > 1&& index <9)
{
resultImg.at<Vec3b>(i, j) = colors[index - 2];
}
else;
}
}
resultImg = resultImg*0.6 + src*0.4;
imshow("resultImg", resultImg);
waitKey(0);
return 0;
}