opencv中分水嶺算法--cvWatershed


Watershed就是傳說中的分水嶺算法, 它將一幅圖像看成是一塊有湖泊和山川組成的地形。 圖像灰度值大的像素對應海拔高的山地, 灰度值低的像素對應於海拔低的盆地。Watershed分割是模擬湖水上漲並在湖泊相遇處築壩的過程。一般水是從湖泊的最低處灌進去,最低點對應於圖像的局部最低點。 但確定局部最低點的自動話算法得到的結果往往不盡如人意, 所以常常要手動指定marker點。

函數原型
void cvWatershed(Iplimage *src_image, CvArr* markers)
#include<iostream>
#include <cv.h>
#include<highgui.h>
#include<cxcore.h>
#include <stdio.h>
#include <stdlib.h>

IplImage* marker_mask = 0;
IplImage* markers = 0;
IplImage* img0 = 0, *img = 0, *img_gray = 0, *wshed = 0;
CvPoint prev_pt = {-1,-1};

void on_mouse(int event, int x, int y, int flags, void* param)
{
    if(!img)
        return;

    if(event == CV_EVENT_LBUTTONUP || !(flags & CV_EVENT_FLAG_LBUTTON))
      // 如果鼠標左鍵彈起或鼠標左鍵沒有按下
        prev_pt = cvPoint(-1,-1);
    else if(event == CV_EVENT_LBUTTONDOWN)
      // 如果鼠標左鍵按下
        prev_pt = cvPoint(x,y);
    else if(event == CV_EVENT_MOUSEMOVE && (flags & CV_EVENT_FLAG_LBUTTON))
      // 如果鼠標移動且左鍵按下
    {
        CvPoint pt = cvPoint(x,y);
        if( prev_pt.x < 0 )
            prev_pt = pt;
        cvLine(marker_mask, prev_pt, pt, cvScalarAll(255), 5, 8, 0);
      // 實際標記 marker_mask 纔會被算法用到
        cvLine(img, prev_pt, pt, cvScalarAll(255), 5, 8, 0);
      // img標記只便於用戶觀察
        prev_pt = pt;
        cvShowImage("image", img);
    }
}


int main()
{
    printf( "Hot keys: \n"
            "\tESC - quit the program\n"
            "\tr - restore the original image\n"
            "\tw or ENTER - run watershed algorithm\n"
            "\t\t(before running it, roughly mark the areas on the image)\n"
            "\t  (before that, roughly outline several markers on the image)\n");

   char* filename = "lena.jpg";
   CvRNG rng = cvRNG(-1);
   // 定義一個隨機化生成器並初始化爲-1,配合下面cvRandInt(&rng)生成隨機數,現在知道爲什麼會變顏色了吧

   if((img0 = cvLoadImage("F:\\bb1.jpg",1)) == 0)
      return 0;
    
    cvNamedWindow("image", 1);
    cvNamedWindow("watershed transform", 1);
   
    img = cvCloneImage(img0);                  
   // 用於顯示的原圖像
    img_gray = cvCloneImage(img0);               
   // 用於和分割出的顏色塊進行混合
    wshed = cvCreateImage(cvGetSize(img), 8, 3);   
   // 用於存儲分割出的顏色塊和最後的效果圖
    marker_mask = cvCreateImage(cvGetSize(img), 8, 1);
   // 用於記錄用戶標記區域的畫布,並在此基礎上製作用於分水嶺算法使用的markers
    markers = cvCreateImage(cvGetSize(img), IPL_DEPTH_32S, 1);
    cvCvtColor(img, marker_mask, CV_BGR2GRAY);
    cvCvtColor(marker_mask, img_gray, CV_GRAY2BGR);   
   
    cvZero(marker_mask);
    cvZero( wshed );
    cvShowImage( "image", img );
    cvShowImage( "watershed transform", wshed );

    cvSetMouseCallback("image", on_mouse, 0);   
   // 從這兒開始實現鼠標標記功能,具體可查ICVL
   
    for(;;)
    {
        char c = cvWaitKey();
      
        if( c == 27 )
            break;

        if( c == 'r' )
        {
            cvZero(marker_mask);
            cvCopy(img0, img);
            cvShowImage("image", img);
        }

        if(c == 'w')
        {
            CvMemStorage* storage = cvCreateMemStorage(0);
            CvSeq* contours = 0;
            
            int comp_count = 1;
         // 粗看以爲這是記錄輪廓數目呢,其實不然,他將把每個輪廓設爲同一像素值
            cvFindContours( marker_mask, storage, &contours, sizeof(CvContour),
                            CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE );
         
            cvZero( markers );
            for( ; contours != 0; contours = contours->h_next, comp_count++)
            {
                cvDrawContours( markers, contours, cvScalarAll(comp_count+1),
                                cvScalarAll(comp_count+1), -1, -1, 8, cvPoint(0,0) );
            }
         // 上面這些最後得到markers 將會是一些數值塊,每個輪廓區域內都有同一像素值
         // 到此時Watershed 終於得到了它如飢似渴的 markers 這個markers 中記錄了剛剛
         // 用戶用鼠標勾勒的感興趣區域
         
         CvMat* color_tab;
            color_tab = cvCreateMat(1, comp_count, CV_8UC3);
         // 構造一個一維8bit無符號3通道元素類型的矩陣,用來記錄一些隨機的顏色
            for(int i = 0; i < comp_count; i++)
            {
                uchar* ptr = color_tab->data.ptr + i*3;
                ptr[0] = (uchar)(cvRandInt(&rng)%180 + 50);
                ptr[1] = (uchar)(cvRandInt(&rng)%180 + 50);
                ptr[2] = (uchar)(cvRandInt(&rng)%180 + 50);
            }

         {// 千呼萬喚始出來的cvWatershed
            double t = (double)cvGetTickCount();
            cvWatershed(img0, markers);
            t = (double)cvGetTickCount() - t;
            printf( "exec time = %gms\n", t/(cvGetTickFrequency()*1000.) );
         // 上面的t用來計算此算法運行時間

         /************************************************************************/
         /* markers中包含了一些用戶感興趣的區域,每個區域用1、2、3。。一些像素值標註,經過
            此算法後,markers會變成什麼樣呢?要知道markers中標註的只是用戶用鼠標輕描淡寫的
            一些區域,把這些區域想像成一些湖泊,如果只有一個區域,則代表整幅圖將會被這一個
            湖泊淹沒,上面color_tab 正是用來記錄每個湖泊的顏色。如果用戶標註了兩個區域,則
            湖泊會沿着這兩個區域蔓延,直到把圖片分成兩個湖泊,這兩個湖泊不是無規律的,而是
            儘可能把圖像的輪廓分隔開。如標註多個區域,則將形成多種顏色的湖泊,此算法會把把
            每個湖泊的分水嶺賦爲 -1,即用來分隔這些湖泊,下面圖片展示了這些湖泊把整幅圖都分
            隔開了                                                 */
         /************************************************************************/
         }

            // paint the watershed image
            for(int i = 0; i < markers->height; i++)
                for(int j = 0; j < markers->width; j++)
                {
                    int idx = CV_IMAGE_ELEM( markers, int, i, j );
               // idx得到了markers 在(i, j)座標的的像素值,這個值對應color_tab中的一種顏色
               // 因爲markers 中的像素值就是用1-comp_count 的像素值標註的
                    uchar* dst = &CV_IMAGE_ELEM( wshed, uchar, i, j*3 );
               // dst得到了wshed圖像 (i, j)像素數據的首地址,因爲乘3是因爲3通道

                    if( idx == -1 )
                  // 在wshed圖像中將markers 中得到的分水嶺標記爲白色,原先-1將顯示黑色
                        dst[0] = dst[1] = dst[2] = (uchar)255;
                    else if( idx <= 0 || idx > comp_count )
                        dst[0] = dst[1] = dst[2] = (uchar)0; // should not get here
                    else
                    {
                        uchar* ptr = color_tab->data.ptr + (idx-1)*3;
                  // 指向idx 所對應的顏色通道,這些顏色是上面隨機生成的
                        dst[0] = ptr[0]; dst[1] = ptr[1]; dst[2] = ptr[2];
                  // 把對應的像素值賦給wshed 圖像
                    }
                }

            cvAddWeighted( wshed, 0.5, img_gray, 0.5, 0, wshed );
         // 可以註釋掉看下效果
            cvShowImage( "watershed transform", wshed );
            cvReleaseMemStorage( &storage );
            cvReleaseMat( &color_tab );
        }
    }

    return 1;
}


下面的代碼通過交互式的方式演示了watershed的過程,先在圖像的不同區域畫線,然後按w鍵運行算法。 下圖左邊是原圖和指定的markers, 右邊是分割的mask圖像。
5. OpenCV圖像分割-watershed - robin - Tiger  Pi



發佈了8 篇原創文章 · 獲贊 3 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章