翻譯自:基于GrabCut算法的互動式前景提取
理論:
GrabCut算法由英國劍橋微軟研究院的Carsten Rother,Vladimir Kolmogorov和Andrew Blake設計。
從使用者的角度來看它是如何工作的?最初使用者在前景區域周圍繪制一個矩形(前景區域應該完全在矩形内)。然後算法疊代地對其進行分段以獲得最佳結果,完成。但在某些情況下,分割将不會很好,例如,它可能已将某些前景區域标記為背景,反之亦然。在這種情況下,使用者需要進行精細的修飾。隻需對圖像進行一些描述,其中存在一些錯誤結果。筆劃基本上說*“嘿,這個區域應該是前景,你标記它的背景,在下一次疊代中糾正它”*或它的背景相反。然後在下一次疊代中,您将獲得更好的結果。
見下圖。第一名球員和足球被包圍在一個藍色矩形中。然後進行一些具有白色筆劃(表示前景)和黑色筆劃(表示背景)的最終修飾。我們得到了一個很好的結果。
那麼背景會發生什麼?
- 使用者輸入矩形。這個矩形之外的所有東西都将被視為确定的背景(這就是之前提到的矩形應包括所有對象的原因)。矩形内的一切都是未知的。類似地,任何指定前景和背景的使用者輸入都被視為硬标簽,這意味着它們不會在過程中發生變化。
- 計算機根據我們提供的資料進行初始标記。它标記前景和背景像素(或硬标簽)。
- 現在,高斯混合模型(GMM)用于模拟前景和背景。
- 根據我們提供的資料,GMM學習并建立新的像素分布。也就是說,未知像素被标記為可能的前景或可能的背景,這取決于其在顔色統計方面與其他硬标記像素的關系(它就像聚類一樣)。
- 從該像素分布建構圖形。圖中的節點是像素。添加了另外兩個節點,Source節點和Sink節點。每個前景像素都連接配接到Source節點,每個背景像素都連接配接到Sink節點。
- 将像素連接配接到源節點/端節點的邊的權重由像素是前景/背景的機率來定義。像素之間的權重由邊緣資訊或像素相似性定義。如果像素顔色存在較大差異,則它們之間的邊緣将獲得較低的權重。
- 然後使用mincut算法來分割圖形。它将圖形切割成兩個分離源節點和彙聚節點,具有最小的成本函數。成本函數是被切割邊緣的所有權重的總和。切割後,連接配接到Source節點的所有像素都變為前景,連接配接到Sink節點的像素變為背景。
- 該過程一直持續到分類收斂為止。
現在我們使用OpenCV進行抓取算法。OpenCV具有此功能,cv.grabCut()。參數:
- img - 輸入圖像
- mask - 這是一個蒙版圖像,我們指定哪些區域是背景,前景或可能的背景/前景等。它由以下标志cv.GC_BGD(背景:0),cv.GC_FGD(前景:1),cv.GC_PR_BGD(可能的背景:2),cv.GC_PR_FGD(可能的前景:3)組成,也可以輸入0,1,2,3。
- rect - 矩形的坐标,包括格式為(x,y,w,h)的前景對象
- bdgModel,fgdModel - 這些是内部算法使用的數組。您隻需建立兩個大小為(1,65)的np.float64類型的零數組。
- iterCount - 算法應運作的疊代次數。
- mode - 它應該是cv.GC_INIT_WITH_RECT或cv.GC_INIT_WITH_MASK或組合,它決定我們是繪制矩形還是蒙版圖像。
代碼示例:
首先讓我們看看矩形模式。我們加載圖像,建立一個類似的蒙版圖像。我們建立了fgdModel和bgdModel。我們給出矩形參數。這一切都是直截了當的。讓算法運作5次疊代。模式應該是cv.GC_INIT_WITH_RECT,因為我們使用矩形。然後運作抓取。它修改了蒙版圖像。在新的掩模圖像中,像素将被标記為表示背景/前景的四個标記,如上所述。是以,我們修改掩模,使得所有0像素和2像素都被置為0(即背景),并且所有1像素和3像素被置為1(即前景像素)。現在我們的最後面具準備好了。隻需将其與輸入圖像相乘即可得到分割後的圖像。
注:np.where(condition, x, y):滿足條件(condition),輸出x,不滿足輸出y。
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.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)
cv.grabCut(img,mask,rect,bgdModel,fgdModel,5,cv.GC_INIT_WITH_RECT)
#代碼中将0和2合并為背景 1和3合并為前景
mask2 = np.where((mask==2)|(mask==0),0,1).astype('uint8')
img = img*mask2[:,:,np.newaxis]
plt.imshow(img),plt.colorbar(),plt.show()
哎呀,梅西的頭發不見了。沒有頭發誰喜歡梅西?我們需要把它帶回來。是以,我們将為其提供1像素(确定前景)的精細修飾。與此同時,有些地方已經出現了我們不想要的圖檔,還有一些辨別。我們需要删除它們。在那裡我們提供一些0像素的修飾(确定背景)。是以,正如我們現在所說的那樣,我們在之前的案
我實際上做的是,我在繪圖應用程式中打開輸入圖像,并在圖像中添加了另一層。在畫中使用畫筆工具,我在這個新圖層上标記了帶有黑色的白色和不需要的背景(如徽标,地面等)的前景(頭發,鞋子,球等)。然後用灰色填充剩餘的背景。然後在OpenCV中加載該掩模圖像,編輯我們在新添加的掩模圖像中使用相應值的原始掩模圖像。檢查以下代碼:
# newmask是我手動标記的圖像
newmask = cv.imread('newmask.png',0)
#标記為白色的地方(确定前景),更改掩碼= 1
#标記為黑色的地方(确定背景),更改掩碼= 0
mask[newmask == 0] = 0
mask[newmask == 255] = 1
mask, bgdModel, fgdModel = cv.grabCut(img,mask,None,bgdModel,fgdModel,5,cv.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()
代碼改進:
由于rect很難确定正确的位置,我決定用OpenCV的滑鼠事件來選取處理的背景區域。
滑鼠左鍵點選确定起始坐标。
按住左鍵拖動滑鼠來選取矩形區域。
滑鼠左鍵放開确定結束坐标。
至此rect = (x,y,width,height)四個參數就由滑鼠确定。
import cv2
import numpy as np
def mouseEvent(event, x, y, flags, param):
global img, position1, position2
image = img.copy()
if event == cv2.EVENT_LBUTTONDOWN: #按下左鍵
position1 = (x,y) #擷取滑鼠的坐标(起始位置)
elif event == cv2.EVENT_MOUSEMOVE and flags == cv2.EVENT_FLAG_LBUTTON: #按住左鍵拖曳不放開
cv2.rectangle(image, position1, (x,y), (0,255,0), 3) #畫出矩形標明框
cv2.imshow('image', image)
elif event == cv2.EVENT_LBUTTONUP: #放開左鍵
position2 = (x,y) #擷取滑鼠的最終位置
cv2.rectangle(image, position1, position2, (0,0,255), 3) #畫出最終的矩形
cv2.imshow('image', image)
min_x = min(position1[0],position2[0]) #獲得最小的坐标,因為可以由下往上拖動標明框
min_y = min(position1[1],position2[1])
width = abs(position1[0] - position2[0]) #為了适配rect的格式
height = abs(position1[1] - position2[1])
mask = np.zeros(img.shape[:2],np.uint8) #初始化蒙版圖像
bgdModel = np.zeros((1,65),np.float64) #内算法使用的零數組
fgdModel = np.zeros((1,65),np.float64)
rect = (min_x,min_y,width,height) #標明的前景區域
cv2.grabCut(image,mask,rect,bgdModel,fgdModel,5,cv2.GC_INIT_WITH_RECT) #函數傳回值為mask,bgdModel,fgdModel
mask2 = np.where((mask==2)|(mask==0),0,1).astype('uint8') #代碼中将0和2合并為背景 1和3合并為前景
img1 = image*mask2[:,:,np.newaxis] #使用蒙闆來擷取前景區域
cv2.imshow('GMM',img1)
def main():
global img
img = cv2.imread(r'C:\Users\x\Desktop\87.jpg',cv2.IMREAD_ANYCOLOR)
cv2.namedWindow('image')
cv2.setMouseCallback('image', mouseEvent)
cv2.imshow('image', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
if __name__ == '__main__':
main()