1. 程式人生 > >【opencv學習之三十八】影象的分水嶺演算法

【opencv學習之三十八】影象的分水嶺演算法

分水嶺演算法主要根據影象梯度將影象分割成“山”和“谷”;一般影象噪聲經常干擾分水嶺演算法的分割,所以一般採用標記的方法來給分水嶺演算法提供灰度級參考,來更換的分割影象;從效果來說比普通的灰度閾值分割效果要好;演算法的具體原理和實現可參考網上的詳解;

原函式及解釋:

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處後再次選擇將進行分水嶺處理: