現在我們看看OpenCV中如何使用分水嶺算法。
首先我們打開一副圖像:
// 打開另一幅圖像
cv::Mat image= cv::imread("../tower.jpg");
if (!image.data)
{
cout<<"不能打開圖像!"<<endl;
return 0;
}
接下來,我們要創建mark圖像。mark圖像格式是有符號整數,其中沒有被mark的部分用0表示,其它不同區域的mark標記,我們用非零值表示,通常為1-255,但也可以為其它值,比如大於255的值,不同mark區域甚至可以用同樣的值,這個值大小對最后分割可能沒有影響(也可能影響),最好不同mark區域還是用不同的值表示,這樣能夠確保結果正確,之所以用有符號整數,是因為opencv在分水嶺算法內部,要用-1,-2等來標記注水區域,最終在mark圖像中生成的分水嶺線就是用-1表示。
我們通常會創建uchar格式的灰度圖,指定mark區域,然后轉化為有符號整數的圖像格式。
首先對整個背景區域我們創建一個mark域,是下圖中白色框框住的部分,其灰度值為255,第二個選擇mark域為塔,就是黑色框框住的一塊區域,其灰度值為64,最后就是樹mark域,藍色框的部分,其灰度值為128。在分水嶺算法時候,會分別對這個3個區域來進行注水操作,如果兩個注水盆地被一個mark域覆蓋,則它們之間不會有分水嶺線產生。
對於mark圖像,opencv分水嶺算法在初始化時候,會把最外圈的值置為-1,作為整個圖像的邊界,所以我們第一個mark區域,選擇倒數第2外圈,因為設置到最外圈,最后還是會被沖掉。
// 標示背景圖像
cv::Mat imageMask(image.size(),CV_8U,cv::Scalar(0));
cv::rectangle(imageMask,cv::Point(1,1),cv::Point(image.cols-2,image.rows-2),cv::Scalar(255),1);
// 表示塔
cv::rectangle(imageMask,cv::Point(image.cols/2-10,image.rows/2-10),
cv::Point(image.cols/2+10,image.rows/2+10),cv::Scalar(64),10);
//樹
cv::rectangle(imageMask,cv::Point(64,284),
cv::Point(68,300),cv::Scalar(128),5);
mark圖像:
注意:mark圖像是32bit的有符號整數,所以在使用分水嶺算法前,我們先對mark圖像做一個轉化。算法執行完后,再轉化為0-255的灰度圖。
imageMask.convertTo(imageMask,CV_32S);
// 設置marker和處理圖像
cv::watershed(image,imageMask);
cv::Mat mark1;
imageMask.convertTo(mark1,CV_8U);
cv::namedWindow("marker");
cv::imshow("marker",mark1);
此時imageMask圖像從無符號整數轉化為uchar后,如下圖所示,第一個mask區域注水,將會使得整個圖像為白色,之后分別在第二個,第三個區域的盆地注水,會產生相應的注水圖,注水的區域的值即為mark的值,128和64, 分水嶺線則為0,注:在轉化前分水嶺線的值為-1,轉化后成為0。
我們使用一個轉化函數把分水嶺線轉化為黑色,其它的部分都白黑色,轉化函數的公式為:
imageMask(x,y) = saturate_cast<uchar>(255*imageMask(x,y)+255)
imageMask.convertTo(imageMask,CV_8U,255, 255);
最后顯示分水嶺線,得到下圖:(注:在轉化前,分水嶺線的值為-1)
源碼:工程FirstOpenCV7
下載:稍后提供