watershed图像自动分割的实现步骤:
函数api
void watershed( InputArray image, InputOutputArray markers );
参数介绍
均值漂移函数api
void pyrMeanShiftFiltering( InputArray src,
OutputArray dst,
double sp,
double sr,
int maxLevel=1,
TermCriteria termcrit=TermCriteria(
TermCriteria::MAX_ITER+TermCriteria::EPS,5,1) );
参数介绍
函数介绍
meanShfit
均值漂移算法是一种通用的聚类算法,它的基本原理是:对于给定的一定数量样本,任选其中一个样本,以该样本为中心点划定一个圆形区域,求取该圆形区域内样本的质心,即密度最大处的点,再以该点为中心继续执行上述迭代过程,直至最终收敛。
可以利用均值偏移算法的这个特性,实现彩色图像分割,Opencv中对应的函数是pyrMeanShiftFiltering
。这个函数严格来说并不是图像的分割,而是图像在色彩层面的平滑滤波,它可以中和色彩分布相近的颜色,平滑色彩细节,侵蚀掉面积较小的颜色区域,所以在Opencv中它的后缀是滤波“Filter”,而不是分割“segment”。先列一下这个函数,再说一下它“分割”彩色图像的实现过程。
pyrMeanShiftFiltering函数的执行过程是这样的:
迭代空间构建:
以输入图像上src上任一点P0为圆心,建立物理空间上半径为sp,色彩空间上半径为sr的球形空间,物理空间上坐标2个—x、y,色彩空间上坐标3个—R、G、B(或HSV),构成一个5维的空间球体。其中物理空间的范围x和y是图像的长和宽,色彩空间的范围R、G、B分别是0~255。
求取迭代空间的向量并移动迭代空间球体后重新计算向量,直至收敛:
在1中构建的球形空间中,求得所有点相对于中心点的色彩向量之和后,移动迭代空间的中心点到该向量的终点,并再次计算该球形空间中所有点的向量之和,如此迭代,直到在最后一个空间球体中所求得的向量和的终点就是该空间球体的中心点Pn,迭代结束。
更新输出图像dst上对应的初始原点P0的色彩值为本轮迭代的终点Pn的色彩值,如此完成一个点的色彩均值漂移。
对输入图像src上其他点,依次执行步骤1,、2、3,遍历完所有点位后,整个均值偏移色彩滤波完成,这里忽略对金字塔的讨论。
在这个过程中,关键参数是sp和sr的设置,二者设置的值越大,对图像色彩的平滑效果越明显,同时函数耗时也越多
#include
#include
#include
using namespace std;
using namespace cv;
using namespace cv::ml;
#define PIC_PATH "/work/opencv_pic/"
#define PIC_NAME "man_face.jpg"
int main(void)
{
Mat src;
//获取完整的图片路径及名称
string pic = string(PIC_PATH)+string(PIC_NAME);
//打印图片路径
cout << "pic path is :"<<pic<<endl;
//读取图片
src = imread(pic);
//判断图片是否存在
if(src.empty())
{
cout<<"pic is not exist!!!!"<<endl;
return -1;
}
//显示图片
namedWindow("src pic",WINDOW_AUTOSIZE);
imshow("src pic",src);
Mat gray_src,binaryimg;
//转化为灰度图
cvtColor(src,gray_src,COLOR_BGR2GRAY);
//二值化
threshold(gray_src,binaryimg,0,255,THRESH_BINARY | THRESH_OTSU);
imshow("binary",binaryimg);
//形态学操作 消除噪点 防止小的尖峰影响分水岭检测
Mat kernel = getStructuringElement(MORPH_RECT,Size(3,3));
morphologyEx(binaryimg,binaryimg,MORPH_OPEN,kernel);
//距离变换 寻找水坝基础
Mat dist;
distanceTransform(binaryimg,dist,DIST_L2,3,CV_32F);
normalize(dist,dist,0,1,NORM_MINMAX);
imshow("dist",dist);
//绘制轮廓
threshold(dist,dist,0.1,1,THRESH_BINARY); //水坝定位
imshow("distimg",dist);
//准备marks roi的轮廓信息 也就是分水岭的水坝
dist.convertTo(dist,CV_8UC1);
vector<vector<Point>> contours;
vector<Vec4i> hireachy;
findContours(dist,contours,hireachy,RETR_TREE,CHAIN_APPROX_SIMPLE);
if(contours.empty())
return -1;
//创建maker
Mat markers = Mat::zeros(src.size(),CV_32S);
markers = Scalar::all(0);
for(size_t i=0;i<contours.size();i++)
{
drawContours(markers,contours,i,Scalar(i+1),-1,8,hireachy,INTER_MAX); //绘制轮廓 每条轮廓颜色一定一定要区分开
}
circle(markers,Point(5,5),3,Scalar(255),-1); //mark做一个小标记
int index = 0;
//打印轮廓数据 有值的均为轮廓线
for(int row=0;row<markers.rows;row++)
for(int col=0;col<markers.cols;col++)
{
index = markers.at<int>(row,col);
cout << index <<",";
}
//进行分水岭变换
watershed(src,markers);
int num_segments = contours.size(); //获取分割区块数
vector<Vec3b> colors; //准备着色候选
for(int i=0;i<num_segments;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++)
{
index = markers.at<int>(row,col);
//打印区域数据 我们可以看到markers的数据已经变为块状数据 值为-1的为块状的边界线
cout << index <<",";
if(index>0 && index <=num_segments)
{
dst.at<Vec3b>(row,col) = colors[index-1];
}else
{
dst.at<Vec3b>(row,col) = Vec3b(0,0,0);
}
}
imshow("分水岭分割演示",dst);
waitKey(0);
destroyAllWindows();
return 0;
}
可以看到数据是每条不同颜色的轮廓线