基于距离变换与分水岭的图像分割(基于图像的拓扑结构)
1、图像分割概念;
2、距离变换和分水岭介绍;
3、相关API;
4、Code;
图像分割(Image Segmentation)
1、图像分割: 将对象从背景中分离出来;常见的图像分割算法实现有很多;
2、图像分割的目标是将图像中的像素根据一定的规则(算法)分为若干个(cluster)集合,每个集合包含一类像素;
3、图像分割算法可以分为监督学习方法和无监督学习方法,图像分割算法多数都是无监督学习方法(Kmeans); 属于数据处理与机器学习范围;
距离变换与分水岭介绍
1、距离变换:
2、分水岭:(与水淹山头现象大概类似);理论为分水岭浸泡理论;
3、距离变换算法的两种原理:①不断膨胀腐蚀(dilate,erode)得到;②基于倒角距离;分水岭变换常见算法:①基于浸泡理论实现;
相关API
1、距离变换API :cv::distanceTransform()
;
labels : 距离变换出来的相同距离的分类;
distancetype : 距离类型;
maskSize : 腐蚀和膨胀的掩膜
2、分水岭变换API : cv::watershed()
; shed : 分水岭
markers : 输出分割标线;
使用流程
1、将白色背景改为黑色,为变换做准备;
2、使用filter2D与Laplace算子实现图像对比度的提高;sharp锐化
3、通过threshold()转换为二值图像;
4、距离变换;
5、对距离变换的结果归一化到0-1之间;
6、使用阈值,再次二值化,得到标记;
7、腐蚀(erode)得到每个Peak;(peak : 山峰,山顶 )
8、发现轮廓;
9、绘制轮廓;
10、分水岭变换;
11、对每个分割区域着色输出结果;
Code
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;
const char* input_win = "input image";
const char* output_win = "watershed segementation demo";
int main(int argc, char** argv)
{
Mat src = imread("C:\\Users\\hello\\Desktop\\39.jpg");
if (src.empty())
{
cout << "could not load the image..." << endl;
return -1;
}
namedWindow(input_win, WINDOW_AUTOSIZE);
namedWindow(output_win, WINDOW_AUTOSIZE);
imshow(input_win, src);
//1、改变背景色
for (int row = 0; row < src.rows; row++)
{
for (int col = 0; col < src.cols; col++)
{
if (src.at<Vec3b>(row, col) == Vec3b(255, 255, 255))
{
src.at<Vec3b>(row, col)[0] = 0;
src.at<Vec3b>(row, col)[1] = 0;
src.at<Vec3b>(row, col)[2] = 0;
}
}
}
imshow("black backbround", src);
//2、锐化Sharp --- Sharpen
Mat kernel = (Mat_<float>(3, 3) << 1, 1, 1, 1, -8, 1, 1, 1, 1); //定义掩膜
Mat imgLaplace;
Mat sharpenImg ;
filter2D(src, imgLaplace, CV_32F, kernel, Point(-1, -1),0,BORDER_DEFAULT); //imgLaplace 的位图深度应为32F
src.convertTo(sharpenImg, CV_32F);
Mat resultImg = sharpenImg - imgLaplace; //加减必须类型一致
//锐化图像转换
resultImg.convertTo(resultImg, CV_8UC3);
imgLaplace.convertTo(imgLaplace, CV_8UC3);
imshow("sharpen image", resultImg);
src = resultImg; //copy back
//3、二值图像变换 convert to binary
Mat binaryImg;
cvtColor(src, resultImg, CV_BGR2GRAY);
threshold(resultImg, binaryImg, 40, 255, THRESH_OTSU | THRESH_BINARY); //产生二值图像,自动寻找阈值
imshow("binary image", binaryImg);
//4、距离变换
Mat distImg;
distanceTransform(binaryImg, distImg, DIST_L1, 3, 5); //距离变换
//5、归一化
normalize(distImg, distImg, 0, 1, NORM_MINMAX, -1, Mat()); //归一化
imshow("distance result", distImg);
//6、再次阈值化
threshold(distImg, distImg, 0.4, 1, THRESH_BINARY); //阈值0.4是自己提前试出来的
imshow("distance binary image", distImg);
//7、腐蚀
Mat k1 = Mat::zeros(3, 3, CV_8UC1);
erode(distImg, distImg, k1, Point(-1, -1));
imshow("erode image", distImg);
//8、mask标记
Mat dist_8u;
distImg.convertTo(dist_8u, CV_8U);
vector<vector<Point>> contours;
findContours(dist_8u, contours, RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0));
Mat markers = Mat::zeros(src.size(), CV_32SC1);
for (size_t i = 0; i < contours.size(); i++)
{
drawContours(markers, contours, static_cast<int>(i), Scalar::all(static_cast<int>(i) + 1),-1); //thicknesss : -1代表填充
}
circle(markers, Point(5, 5), 3, Scalar(255, 255, 255), -1);
imshow("my markers", markers * 1000); //markers像素值很低,*1000放大
//根据markers对src进行分水岭
watershed(src, markers); //计算值会放到markers中
Mat mark = Mat::zeros(markers.size(), CV_8UC1);
markers.convertTo(mark, CV_8UC1);
bitwise_not(mark, mark, Mat());
imshow("watershed image", mark); //分水岭图像为灰度图像,不太清晰
//产生随机颜色填充
vector<Vec3b> colors;
for (size_t i = 0; i < contours.size(); i++)
{
int r = theRNG().uniform(0, 255);
int g = theRNG().uniform(0, 255);
int b = theRNG().uniform(0, 255);
colors.push_back(Vec3b((uchar)b, (uchar)g, (uchar)r));
}
//着色
Mat dst = Mat::zeros(markers.size(), CV_8UC3);
for (int row = 0; row < markers.rows; row++)
{
for (int col = 0; col < markers.cols; col++)
{
int index = markers.at<int>(row, col);
if (index > 0 && index <= static_cast<int>(contours.size()))
{
dst.at<Vec3b>(row, col) = colors[index - 1];
}
else
{
dst.at<Vec3b>(row, col) = Vec3b(0, 0, 0);
}
}
}
imshow("final result", dst);
waitKey(0);
return 0;
}
效果
锐化图:
二值图:
距离变换:
再次二值化:
腐蚀:
Mark标记图像:
最终分水岭图像:
着***r>