天天看點

Python-OpenCV-基于GrabCut算法的互動式可選區域前景提取

翻譯自:基于GrabCut算法的互動式前景提取

理論:

GrabCut算法由英國劍橋微軟研究院的Carsten Rother,Vladimir Kolmogorov和Andrew Blake設計。

從使用者的角度來看它是如何工作的?最初使用者在前景區域周圍繪制一個矩形(前景區域應該完全在矩形内)。然後算法疊代地對其進行分段以獲得最佳結果,完成。但在某些情況下,分割将不會很好,例如,它可能已将某些前景區域标記為背景,反之亦然。在這種情況下,使用者需要進行精細的修飾。隻需對圖像進行一些描述,其中存在一些錯誤結果。筆劃基本上說*“嘿,這個區域應該是前景,你标記它的背景,在下一次疊代中糾正它”*或它的背景相反。然後在下一次疊代中,您将獲得更好的結果。

見下圖。第一名球員和足球被包圍在一個藍色矩形中。然後進行一些具有白色筆劃(表示前景)和黑色筆劃(表示背景)的最終修飾。我們得到了一個很好的結果。

Python-OpenCV-基于GrabCut算法的互動式可選區域前景提取

那麼背景會發生什麼?

  • 使用者輸入矩形。這個矩形之外的所有東西都将被視為确定的背景(這就是之前提到的矩形應包括所有對象的原因)。矩形内的一切都是未知的。類似地,任何指定前景和背景的使用者輸入都被視為硬标簽,這意味着它們不會在過程中發生變化。
  • 計算機根據我們提供的資料進行初始标記。它标記前景和背景像素(或硬标簽)。
  • 現在,高斯混合模型(GMM)用于模拟前景和背景。
  • 根據我們提供的資料,GMM學習并建立新的像素分布。也就是說,未知像素被标記為可能的前景或可能的背景,這取決于其在顔色統計方面與其他硬标記像素的關系(它就像聚類一樣)。
  • 從該像素分布建構圖形。圖中的節點是像素。添加了另外兩個節點,Source節點和Sink節點。每個前景像素都連接配接到Source節點,每個背景像素都連接配接到Sink節點。
  • 将像素連接配接到源節點/端節點的邊的權重由像素是前景/背景的機率來定義。像素之間的權重由邊緣資訊或像素相似性定義。如果像素顔色存在較大差異,則它們之間的邊緣将獲得較低的權重。
  • 然後使用mincut算法來分割圖形。它将圖形切割成兩個分離源節點和彙聚節點,具有最小的成本函數。成本函數是被切割邊緣的所有權重的總和。切割後,連接配接到Source節點的所有像素都變為前景,連接配接到Sink節點的像素變為背景。
  • 該過程一直持續到分類收斂為止。
Python-OpenCV-基于GrabCut算法的互動式可選區域前景提取

現在我們使用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()
           
Python-OpenCV-基于GrabCut算法的互動式可選區域前景提取

哎呀,梅西的頭發不見了。沒有頭發誰喜歡梅西?我們需要把它帶回來。是以,我們将為其提供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()
           
Python-OpenCV-基于GrabCut算法的互動式可選區域前景提取

代碼改進:

由于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()
           
Python-OpenCV-基于GrabCut算法的互動式可選區域前景提取

結束!