2012年12月7日 星期五

EmguCV Image Process: Transforming Images with Morphological Operations part 5

20060907001.jpg
EmguCV Image Process: Transforming Images with Morphological Operations

參考OpenCV 2 Computer Vision Application Programming Cookbook第五章

介紹內容如下:

Eroding and dilating images using morphological filters

Opening and closing images using morphological filters

Detecting edges and corners using morphological filters

Segmenting images using watersheds

Extracting foreground objects with the GrabCut algorithm

上一篇介紹了分水嶺演算法可用來切割影像

這篇要來介紹另一個也很普遍的切割方法:GrabCut

此方法其實並非基於形態學運算

但由於跟分水嶺演算法有同樣的切割效果

因此在這裡介紹,方便來做比較

GrabCut的運算複雜度比之於分水嶺演算法高很多

但有比較精確的效果

如果是要擷取、切割影像中的單一前景物件

GrabCut將會是非常適合的方法
(像是photoshop中的去背功能!!)


幸運的是EmguCV就直接在Image中實作了這個方法

所以用起來非常簡單

在使用此方法時

必須先給定一些必要的資訊

如同分水嶺演算法一樣

在這邊你必須把你要切割的前景

用一個矩形框起來:
//Read input image
Image<Bgr, Byte> image = new Image<Bgr, Byte>("image.jpg");
//define bounding rectangle
//the pixels outside this rectangle
//will be labeled as background
Rectangle rect = new Rectangle(205, 80, 415, 370);
//GrabCut segmentation
Image<Gray, Byte> mask = image.GrabCut(rect, 5);
設定這個矩形的範圍代表的意義是

程式會把矩形外的pixels視為背景

然後不斷的去計算矩形內那些部分是屬於前景,哪些是屬於背景

GrabCut後面帶的整數就是iterations的次數

框選的範圍如下圖所示:

 這是矩形框選的範圍,把兩個人給框住

這是跑出來的結果!!

當看到這個結果影像的時候是不是有點傻眼

如果你的螢幕夠高級、眼力夠好

你其實會發現,出來的結果並不是全黑的影像喔!!!

//Generate output image
Image<Bgr, Byte> result = image.Copy(mask);
這時候會想說竟然結果是張mask

那就把原圖跟mask做運算來看看

會發現只是把剛剛框選的區域變成前景而已~

這其中一定是有什麼誤會!!

如果去分析一下mask內pixels的值

會發現裡面除了全黑的0外還有2、3等值!!

這裡用暴力法來把mask裡面2、3的值轉換成100、150的值

方便肉眼觀察!!

for (int w = 0; w < mask.Width; w++)
{
    for (int h = 0; h < mask.Height; h++)
    {
        if (mask[h, w].Intensity ==1)
        {
            mask[h, w] = new Gray(50);
        }
        else if (mask[h, w].Intensity == 2)
        {
            mask[h, w] = new Gray(100);
        }
        else if (mask[h, w].Intensity == 3)
        {
            mask[h, w] = new Gray(150);
        }
    }
}
用兩層的for loop來偵測mask影像中的值

如果為1則轉換成50,2轉成100,3轉乘150

結果如下:

這時候是不是比較有fu了!!

其實在經過GrabCut的處理後,影像的結果會分成四種:

GC_BGD:屬於背景的部分,灰階值為0,在此範例中就是非框選的區域
GC_FGD:屬於前景的部分,灰階值為1,在此範例中不會出現
GC_PR_BGD:可能是背景的部分,灰階值為2,在此範例就是深灰色的區塊
GC_PR_FGD:可能是前景的部分,灰階值為3,在此範例就是淺灰色的區塊

這樣是不是比較清楚

這裡可以看出設計此對應的一些巧思

0、2屬於背景,1、3屬於前景

對應二進制來說,這兩邊的差異就只在第一個位元:

0:00000000
2:00000010
1:00000001
3:00000011

所以透過簡單的and就可以取出前景部分:

//Get the pixels marked as likely foreground
mask = mask.And(new Gray(1));
//Generate output image
Image<Bgr, Byte> result = image.Copy(mask);
這樣就可以把背景的部分去除

只保留灰階值在第一個位元是1的pixels,也就是前景部分

結果如下:

這就是GrabCut取出前景的結果!!


由於這邊一次圈選的區塊相當大

記得一開始有說過,GrabCut適合切割一個前景影像

所以這裡把框選的區塊縮小到剩下一個人的大小

只把一個人圈選出來

執行的效果是不是相當驚人呢!!


想要更進階一點的

就要透過OpenCV提供的函式:CvInvoke.CvGrabCut

這裡的概念會稍微複雜一些

可以不只以單純的矩形區塊來做設定

若要精確的切割出前景

就要採用mask的方法來做設定

有點類似分水嶺演算法的設定方式

並在矩形區塊中加入一些參考線

如下圖:

 我們把之前的結果,但還是屬於背景的部分

用一些線段來做參考

畫上綠色線段的部分,就是加入的參考線,告訴程式這也是屬於背景

實際執行程式的部分如下:

Image<Bgr, Byte> image = new Image<Bgr, Byte>("image.jpg");
//define bounding rectangle
//the pixels outside this rectangle
//will be labeled as background
Rectangle rect = new Rectangle(205, 80, 415, 370);
//Create reference mask image
Image<Gray, Byte> mask = new Image<Gray, byte>(image.Width, image.Height);
//rectangle containing pixels probably belonging to the foreground (GC_PR_FGD)
mask.Draw(rect, new Gray(3), 0);
//Line meaning background (GC_BGD)
LineSegment2D bgLine = new LineSegment2D(new Point(487, 126), new Point(438, 366));
mask.Draw(bgLine, new Gray(0), 5);
bgLine = new LineSegment2D(new Point(248, 356), new Point(441, 424));
mask.Draw(bgLine, new Gray(0), 5);
bgLine = new LineSegment2D(new Point(303, 307), new Point(456, 380));
mask.Draw(bgLine, new Gray(0), 5);
bgLine = new LineSegment2D(new Point(562, 313), new Point(607, 387));
mask.Draw(bgLine, new Gray(0), 5);
bgLine = new LineSegment2D(new Point(527, 375), new Point(594, 445));
mask.Draw(bgLine, new Gray(0), 5);
//the models (internally used)
Matrix<double> bgModel = new Matrix<double>(1, 13 * 5);
Matrix<double> fgModel = new Matrix<double>(1, 13 * 5);
//initialization with mask
CvInvoke.CvGrabCut(image.Ptr, mask.Ptr, ref rect, bgModel.Ptr, fgModel.Ptr, 1, Emgu.CV.CvEnum.GRABCUT_INIT_TYPE.INIT_WITH_MASK);
//GrabCut segmentation
CvInvoke.CvGrabCut(image.Ptr, mask.Ptr, ref rect, bgModel.Ptr, fgModel.Ptr, 5, Emgu.CV.CvEnum.GRABCUT_INIT_TYPE.EVAL);
//Get the pixels marked as likely foreground
mask = mask.And(new Gray(1));
//Generate output image
Image<Bgr, Byte> result = image.Copy(mask);
一開始先將mask用可能是前景(GC_PR_FGD)所代表的灰階值3來填滿矩形區塊

然後加入設定為背景(GC_BGD)的線段,用灰階值0來表示

然後在初始化GrabCut時,採用Emgu.CV.CvEnum.GRABCUT_INIT_TYPE.INIT_WITH_MASK

然後再執行

取得結果如下:

這兩個人幾乎是相當漂亮的被切割出來了

透過參考線的設定

會讓效果變得更好

參考線越多越精細,效果就會越好!!


如果加上良好的UI設計

是不是就能夠自己實作去背功能嘍~!!!

沒有留言:

張貼留言