理論
GranCut算法是Carsten Rother, Vladimir Kolmogorov & Andrew Blake from Microsoft Research Cambridge, UK在他們的論文“GrabCut”: interactive foreground extraction using iterated graph cuts裡設計的。使用最小程度的用戶交互來分解前景。
從用戶角度來看是怎麼工作的呢?開始用戶畫一個矩形方塊把前景圖圈起來,前景區域應該完全在矩形內,然後算法反復進行分割以達到最好效果。但是有些情況下,分割的不是很好,比如把前景給標稱背景了等。在這種情況下用戶需要再潤色,就在圖像上有缺陷的點給幾筆。這幾筆的意思是說“嘿,這個區域應該是前景,你把它標成背景了,下次迭代改過來”或者是反過來。那麼在下次迭代,結果會更好。
看下面的圖像,首先球員和足球杯包在藍色矩形框裡,然後用白色筆(指出前景)和黑色筆(指出背景)來做一些潤色
後台發生了什麼?
·用戶輸入矩形,矩形外的所有東西都被確認是背景。所有矩形內的東西都是未知的,同樣的任何用戶輸入指定前景和背景的也都被認為是硬標記,在處理過程中不會變。
·計算機會根據我們給的數據做初始標記,它會標記出前景和背景像素。
·現在回使用高斯混合模型(GMM)來為前景和背景建模
·根據我們給的數據,GMM學習和創建新的像素分佈。未知像素被標為可能的前景或可能的背景(根據其他硬標記像素的顏色統計和他們之間的關係)
·根據這個像素分佈創建一個圖,圖中的節點是像素,另外還有兩個節點,源節點和匯節點,每個前景像素和源節點相連,每個背景像素和匯節點相連。
·源節點和匯節點連接的像素的邊的權重由像素是前景或者背景的概率決定。像素之間的權重是由邊的信息或者像素的相似度決定。如果像素顏色有很大差異,他們之間的邊的權重就比較低。
·mincut算法是用來分割圖的,它用最小成本函數把圖切成兩個分開的源點和匯點,成本函數是被切的邊的權重之和。切完以後,所有連到源節點的像素稱為前景,所有連到匯節點的稱為背景。
·過程持續直到分類覆蓋。
Demo
現在我們用OpenCV來做grabcut算法。OpenCV有個函數cv2.grabCut()來做這個,我們先看看它的參數:
·img - 輸入圖像
·mask - 這是掩圖,我們指定哪個區域是背景,前景以及可能是背景或者前景。由下面的標誌位:cv2.GC_BGD, cv2.GC_FGD, cv2.PR_BGD, cv2.GC_PR_FGD, 或者簡單傳入0, 1, 2, 3.
·rect - 包含前景對象的矩形的坐標,格式(x, y, w, h).
·bdgModel, fgdModel - 算法內部使用的數組, 你創建兩個np.float64 類型的0數組,大小是(1, 65)
·iterCount - 算法運行的迭代次數
·mode - 應該是cv2.GC_INIT_WITH_RECT 或者cv2.GC_INIT_WITH_MASK或者兩者合併,決定我們是否畫矩形或者最終的潤色。
首先讓我們看看矩形模式,我們加載圖片,創建一個類似的掩圖。我們創建fgdModel和bgdModel。我們給矩形參數。這些都很直接。讓算法運行5個迭代。Mode應該是cv2.GC_INIT_WITH_RECT因為我們用了矩形。然後運行grabcut。它修改掩圖。在新的掩圖裡,像素會被標記為四種標誌,來指明他們是背景/前景等。所以我們修改掩圖,所有的0-像素和2-像素被置0(背景)而所有的1-像素和3-像素被置1(前景像素)。現在我們最終的掩圖就緒。
- import numpy as np
- import cv2
- from matplotlib import pyplot as plt
- img = cv2.imread('messi5.jpg')
- mask = np.zeros(img.shape[:2],np.uint8)
- bgdModel = np.zeros((1,65),np.float64)
- fgdModel = np.zeros((1,65),np.float64)
- rect = (50,50,450,290)
- cv2.grabCut(img,mask,rect,bgdModel,fgdModel,5,cv2.GC_INIT_WITH_RECT)
- mask2 = np.where((mask==2)|(mask==0),0,1).astype('uint8')
- img = img*mask2[:,:,np.newaxis]
- plt.imshow(img),plt.colorbar(),plt.show()
複製代碼
可以看到梅西的頭髮沒了,所以我們潤色一下,用1-像素確認是前景,同時有些地皮成了前景,還有logo,我們需要移除他們,我們給一些0-像素潤色(確認是背景)。我們修改我們的結果掩圖。
實際上我做的是,我用繪圖軟件打開輸入圖片,添加了另外一層,使用畫刷工具,把丟失的前景(頭髮,謝,球等)用白色,不要的背景(logo,地面)用黑色畫在這個新層上。然後把剩下的背景用灰色填充。然後在OpenCV裡加載這個掩圖,編輯原始掩圖,代碼:
- # newmask is the mask image I manually labelled
- newmask = cv2.imread('newmask.png',0)
- # whereever it is marked white (sure foreground), change mask=1
- # whereever it is marked black (sure background), change mask=0
- mask[newmask == 0] = 0
- mask[newmask == 255] = 1
- mask, bgdModel, fgdModel = cv2.grabCut(img,mask,None,bgdModel,fgdModel,5,cv2.GC_INIT_WITH_MASK)
- mask = np.where((mask==2)|(mask==0),0,1).astype('uint8')
- img = img*mask[:,:,np.newaxis]
- plt.imshow(img ),plt.colorbar(),plt.show()
複製代碼
也可以不用矩形初始化而直接用掩圖模式,用2-像素和3-像素(可能是背景/前景)標記矩形區域,然後把我們確認前景的標為1-像素,然後直接應用grabCut函數,用mask 模式。
文章出處
|