【opencv學習之三十八】影象的分水嶺演算法
阿新 • • 發佈:2019-01-02
分水嶺演算法主要根據影象梯度將影象分割成“山”和“谷”;一般影象噪聲經常干擾分水嶺演算法的分割,所以一般採用標記的方法來給分水嶺演算法提供灰度級參考,來更換的分割影象;從效果來說比普通的灰度閾值分割效果要好;演算法的具體原理和實現可參考網上的詳解;
原函式及解釋:
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處後再次選擇將進行分水嶺處理: