【opencv学习之三十八】图像的分水岭算法

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/abcvincent/article/details/79342233

分水岭算法主要根据图像梯度将图像分割成“山”和“谷”;一般图像噪声经常干扰分水岭算法的分割,所以一般采用标记的方法来给分水岭算法提供灰度级参考,来更换的分割图像;从效果来说比普通的灰度阈值分割效果要好;算法的具体原理和实现可参考网上的详解;

原函数及解释:

CV_EXPORTS_W void watershed( InputArray image, InputOutputArray markers );

image :输入8比特3通道图像。 

markers :输入或输出的32比特单通道标记图像。 

函数Watershed实现在[Meyer92]描述的变量分水岭,基于非参数标记的分割算法中的一种。在把图像传给函数之前,用户需要用正指标大致勾画出图像标记的感兴趣区域。比如,每一个区域都表示成一个或者多个像素值1,2,3的互联部分。这些部分将作为将来图像区域的种子。标记中所有的其他像素,他们和勾画出的区域关系不明并且应由算法定义,应当被置0。这个函数的输出则是标记区域所有像素被置为某个种子部分的值,或者在区域边界则置-1。 

下面是两个应用例子:

1.rice的分割;

///////////////////分水岭

Vec3b RandomColor(int value) //生成随机颜色函数
{
    value=value%255;  //生成0~255的随机数
    RNG rng;
    int aa=rng.uniform(0,value);
    int bb=rng.uniform(0,value);
    int cc=rng.uniform(0,value);
    return Vec3b(aa,bb,cc);
}

void imgWatershed2()//分水岭1
{
    Mat image= imread("D:/ImageTest/Rice.png");   //载入RGB彩色图像
    imshow("Source Image",image);

    //灰度化,滤波,Canny边缘检测
    Mat imageGray;
    cvtColor(image,imageGray,CV_RGB2GRAY);//灰度转换
    GaussianBlur(imageGray,imageGray,Size(5,5),2);   //高斯滤波
    imshow("Gray Image",imageGray);
    Canny(imageGray,imageGray,80,150);
    imshow("Canny Image",imageGray);

    //查找轮廓
    vector<vector<Point>> contours;
    vector<Vec4i> hierarchy;
    findContours(imageGray,contours,hierarchy,RETR_TREE,CHAIN_APPROX_SIMPLE,Point());
    Mat imageContours=Mat::zeros(image.size(),CV_8UC1);  //轮廓
    Mat marks(image.size(),CV_32S);   //Opencv分水岭第二个矩阵参数
    marks=Scalar::all(0);
    int index = 0;
    int compCount = 0;
    for( ; index >= 0; index = hierarchy[index][0], compCount++ )
    {
        //对marks进行标记,对不同区域的轮廓进行编号,相当于设置注水点,有多少轮廓,就有多少注水点
        drawContours(marks, contours, index, Scalar::all(compCount+1), 1, 8, hierarchy);
        drawContours(imageContours,contours,index,Scalar(255),1,8,hierarchy);
    }

    //我们来看一下传入的矩阵marks里是什么东西
    Mat marksShows;
    convertScaleAbs(marks,marksShows);
    imshow("marksShow",marksShows);
    imshow("contours",imageContours);
    watershed(image,marks);

    //我们再来看一下分水岭算法之后的矩阵marks里是什么东西
    Mat afterWatershed;
    convertScaleAbs(marks,afterWatershed);
    imshow("After Watershed",afterWatershed);

    //对每一个区域进行颜色填充
    Mat PerspectiveImage=Mat::zeros(image.size(),CV_8UC3);
    for(int i=0;i<marks.rows;i++)
    {
        for(int j=0;j<marks.cols;j++)
        {
            int index=marks.at<int>(i,j);
            if(marks.at<int>(i,j)==-1)
            {
                PerspectiveImage.at<Vec3b>(i,j)=Vec3b(255,255,255);
            }
            else
            {
                PerspectiveImage.at<Vec3b>(i,j) =RandomColor(index);
            }
        }
    }
    imshow("After ColorFill",PerspectiveImage);

    //分割并填充颜色的结果跟原始图像融合
    Mat wshed;
    addWeighted(image,0.4,PerspectiveImage,0.6,0,wshed);
    imshow("AddWeighted Image",wshed);

    waitKey(0);
}

效果:


2.用鼠标选择mark的方法进行分水岭:


//////////////////////////用鼠标选择ROI进行分水岭
Mat img_Watershed = imread("D:/ImageTest/222.jpg",CV_LOAD_IMAGE_COLOR);
Mat temp_W;
Mat imgClone;
Point pt2_W;//记录鼠标左键按下时的坐标
int x_p,y_p;
bool flag_W = false;////flag用来标记鼠标移动是否画矩形
int  numbers;
//1.定义标记图像markers
Mat markers2(img_Watershed.size(),CV_8UC1,Scalar(0));

///////////////////////////
void OnMouseW(int event, int x, int y, int flag_W, void* param);
void imgWatershedShow();//分水岭3

void imgWatershed3()//32分水岭3
{
    numbers=0;
    imgClone= img_Watershed.clone();
    namedWindow("Watershed", CV_WINDOW_AUTOSIZE);
    setMouseCallback("Watershed", OnMouseW,  &imgClone);   //设置鼠标回调函数
    while(1)
    {
        imshow("Watershed",imgClone); //每10ms刷新一次图像,不在onMouse()里显示图像,因为太快会有拖动重影
        if(27 == waitKey(10))  //Esc跳出循环
            break;
    }
}

void OnMouseW(int event, int x, int y, int flag_W, void* param)
{
    switch(event)
    {
    case CV_EVENT_LBUTTONDOWN:  //鼠标左键按下
        //cout<<"left button down"<<endl;
        flag_W = true;//flag标记置为真,触发鼠标移动画矩形
        pt2_W.x = x; //记录当前点坐标
        pt2_W.y = y;
        break;
    case CV_EVENT_MOUSEMOVE:   //鼠标移动
        //cout<<"mouse move"<<endl;
        if(flag_W)
        {
            temp_W=imgClone.clone();
            rectangle(temp_W, pt2_W, Point(x, y), Scalar(0, 0, 255), 2, 8);//画矩形
            imshow("Watershed",temp_W);
        }
        break;
    case CV_EVENT_LBUTTONUP:   //鼠标左键弹起
        flag_W = false;//flag标记置为假,关闭鼠标移动画矩形
        rectangle(imgClone,pt2_W, Point(x, y), Scalar(0, 0, 255), 2, 8);
        x_p=x;
        y_p=y;
        imgWatershedShow();
        imshow("Watershed",imgClone); //显示
        break;
    default:
        break;
    }
}

void imgWatershedShow()//分水岭3
{
    //1.定义标记图像markers
    Mat markers(img_Watershed.size(),CV_8UC1,Scalar(0));

    if(numbers<3)
    {
        if(numbers==0){
            rectangle(markers,pt2_W, Point(x_p, y_p),Scalar(255),-1,8);
            rectangle(markers2,pt2_W, Point(x_p, y_p),Scalar(255),-1,8);

            ////添加文字--背景色
            QString str1="background";//qt方法
            QByteArray cStr1 = str1.toLocal8Bit(); // 注意,这个QByteArray 对象一定要建立
            char *p1 = cStr1.data();
            putText(markers,p1, Point(30, 30),CV_FONT_HERSHEY_COMPLEX, 0.8, Scalar(255, 255, 255), 1, 8);
            imshow("markers-input",markers);
        }
        if(numbers==1){
            rectangle(markers, pt2_W, Point(x_p, y_p), Scalar(128), -1, 8);
              rectangle(markers2, pt2_W, Point(x_p, y_p), Scalar(128), -1, 8);
            ////添加文字--第一个
            QString str1="first";//qt方法
            QByteArray cStr1 = str1.toLocal8Bit(); // 注意,这个QByteArray 对象一定要建立
            char *p1 = cStr1.data();
            putText(markers,p1, Point(30, 30),CV_FONT_HERSHEY_COMPLEX, 0.8, Scalar(255, 255, 255), 1, 8);
            imshow("markers-input",markers);
        }
        if(numbers==2){
            rectangle(markers, pt2_W, Point(x_p, y_p), Scalar(64), -1, 8);
             rectangle(markers2, pt2_W, Point(x_p, y_p), Scalar(64), -1, 8);
            ////添加文字--第二个
            QString str1="second";//qt方法
            QByteArray cStr1 = str1.toLocal8Bit(); // 注意,这个QByteArray 对象一定要建立
            char *p1 = cStr1.data();
            putText(markers,p1, Point(30, 30),CV_FONT_HERSHEY_COMPLEX, 0.8, Scalar(255, 255, 255), 1, 8);
            imshow("markers-input",markers);
        }
        numbers=numbers+1;

    }
    else
    {
        Mat copyImg=img_Watershed.clone();
        //基于标记图像的分水岭算法
        //1将markers2转换成32位单通道图像(分水岭函数要求)
        markers2.convertTo(markers2,CV_32S);
        watershed(copyImg,markers2);    //分水岭算法
        markers2.convertTo(markers2, CV_8UC1);    //将markers转换成8位单通道
        //2.提取轮廓并绘制轮廓
        Mat mark1, mark2,mark3;
        mark1 = markers2.clone();//提取轮廓1
        mark2 = markers2.clone();//提取轮廓2
        mark3 = markers2.clone();//提取背景轮廓
        //阈值化,黑中找白
        threshold(mark1,mark1,129,255,CV_THRESH_TOZERO_INV);
        threshold(mark1,mark1,120,255,CV_THRESH_TOZERO);
        //阈值化,黑中找白
        threshold(mark2, mark2, 65, 255, CV_THRESH_TOZERO_INV);
        //阈值化,黑中找白,找背景
        threshold(mark3, mark3, 129, 255, CV_THRESH_BINARY);
        imshow("mark1", mark1);
        imshow("mark2", mark2);
        imshow("mark3", mark3);
        //寻找背景轮廓并绘制
        vector<vector<Point>> contours3;
        findContours(mark3, contours3, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);
        drawContours(copyImg, contours3, -1, Scalar(0, 0, 255), -1, 8);
        //寻找第一轮廓并绘制
        vector<vector<Point>> contours1;
        findContours(mark1,contours1,CV_RETR_EXTERNAL,CV_CHAIN_APPROX_NONE);
        drawContours(copyImg,contours1,-1,Scalar(255,0,0),-1,8);
        //寻找第二轮廓并绘制
        vector<vector<Point>> contours2;
        findContours(mark2, contours2, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);
        drawContours(copyImg, contours2, -1, Scalar(0, 255, 0), -1, 8);
        imshow("copyImg",copyImg);
        //3.与原图像叠加
        Mat result = img_Watershed*0.5 + copyImg*0.5;
        imshow("result", result);

        numbers=0;
        imgClone=img_Watershed.clone();
    }
}

效果1.用鼠标选择3处:


选择第3处后再次选择将进行分水岭处理:




猜你喜欢

转载自blog.csdn.net/abcvincent/article/details/79342233