OpenCV
,去背
,圖像分割
,grabcut
,bitwise
目標:
將我家弟弟送到月球上
原圖如下:
步驟1 :先去背
outputmask, bgModel, fgModel = cv2.grabCut(image, mask, rect,bgModel,fgModel, iterCount, mode)
output的部分有三塊,最重要的是outputmask:
input的部分
1.image-目標照片
2.bgModel,fgModel-就是兩個尺寸(1,65)的array,給算法存模型參數用的。
下面兩個參數,就是要告訴算式,你打算怎麼分前後景
3.mask-你可以用一張跟輸入圖像一樣大的二元圖像當設定值
4.rect-你也可以用框選的方式當設定值
5.iterCount-計算過程你想要跑多少次 (try & erro去試)
6.mode-你可以用這個參數,決定你要用前面的mask當初始值,還是用rect當初始值,比如你用rect當初始值,model就知道主題在rect裡面,所以外面的部分先自動變成背景了。
cv2.GC_INIT_WITH_RECT
cv2.GC_INIT_WITH_MASK
grabcut會把算出來的結果存回去 mask、bgdModel、fgdModel
##############################試試看調整iterCount,看看結果
import cv2
import numpy as np
img_le = cv2.imread("lele2.jpg")
img_le = cv2.resize(img_le, (0,0), fx=0.3, fy=0.3)#照片太大縮小一下尺寸
#print(img_le.shape)
imgCopy_le = img_le.copy() #複製一張
mask_le = np.zeros(img_le.shape[:2], np.uint8)#跟上面作法不同(這個先做一個空的mask)
bgd_Model = np.zeros((1,65),np.float64)#背景
fgd_Model = np.zeros((1,65),np.float64)#前景
#bgdModel——背景模型,如果為null,函式內部會自動建立一個bgdModel;bgdModel必須是單通道浮點型(CV_32FC1)影象,且行數只能為1,列數只能為13x5;
#fgdModel——前景模型,如果為null,函式內部會自動建立一個fgdModel;fgdModel必須是單通道浮點型(CV_32FC1)影象,且行數只能為1,列數只能為13x5;
#選擇要去背的部分
rect_le = cv2.selectROI(img_le)#選擇有興趣的區域 框住然後按空白鍵
cv2.destroyAllWindows()#彈出視窗
for i in range(6,11,2): #調整iterCount,結果都會不一樣,可以多試幾次 (利用變數i去帶不同參數)
mask_le = np.zeros(img_le.shape[:2], np.uint8)
bgd_Model = np.zeros((1,65),np.float64)
fgd_Model = np.zeros((1,65),np.float64)
#請出grabCut #0:背景、1:前景,2:可能屬於背景,3:可能屬於前景 (前景代表人或物件)
cv2.grabCut(img_le,mask_le,rect_le,bgd_Model,fgd_Model,i,cv2.GC_INIT_WITH_RECT)
mask_le2 = np.where((mask_le==0)|(mask_le==2),0,1).astype('uint8') ## 把判定成背景(代碼0的)或是可能是背景的(代碼2的)都歸0 ,"|"==>代表or,做一個只有0 & 1新的mask
# print(np.unique(mask_le2))
img_copy_le = img_le.copy()
img_grabcut_le = img_copy_le*mask_le2[:,:,np.newaxis] #新增軸位,2d結構轉換成3d結構,才能與原圖做相乘,透過相乘,乘0的歸0,乘1的留下來
cv2.imshow(f"Image{i}",img_grabcut_le)#show圖
cv2.imwrite(f"Image{i}_lele.png",img_grabcut_le)#存檔
cv2.imshow("Original",img_le)
cv2.waitKey(0)
cv2.destroyAllWindows()
可以看到下圖結果不盡理想,跟想要的差很多!
所以進入第二階段,帶入筆刷的概念
步驟2 :利用滑鼠動作進行細部的調整修改
import cv2
import numpy as np
img = cv2.imread("Image6_lele.png")
#img = cv2.resize(img, (0,0), fx=0.4, fy=0.4)
imgCopy = img.copy()
mask = np.zeros(img.shape[:2], np.uint8)
bgdModel = np.zeros((1,65),np.float64)
fgdModel = np.zeros((1,65),np.float64)
rect = cv2.selectROI(img)
x,y,w,h = rect
cv2.grabCut(img,mask,rect,bgdModel,fgdModel,3,cv2.GC_INIT_WITH_RECT)
mask2 = np.where((mask==2)|(mask==0),0,1).astype('uint8')
imgResult = img*mask2[:,:,np.newaxis]
#把圖各準備一份
img_mask = img.copy()
mask2 = mask2*255
mask_copy = mask2.copy()
步驟3.開一個Sketcher class,來處理我們的"筆刷事件"
class Sketcher:
def __init__(self, windowname, dests, colors_func):
#視窗名稱
self.windowname = windowname
#顯示的圖片目標
#會塞兩張圖進來 顯示cut圖和mask圖
self.dests = dests
#顯示的顏色
self.colors_func = colors_func
#一啟動就要顯示結果
self.show()
#要記錄滑鼠座標位置
self.prev_pt = None
#把滑鼠的callback function裝上來
#windowname是設定要把function裝在哪個視窗
#on_mouse是下面設計的function,這個function是要給cv2.setMouseCallback呼叫的,每逢有滑鼠訊號,cv2就會把參數傳給我們的on_mouse
cv2.setMouseCallback(self.windowname, self.on_mouse)
def show(self):
#就是顯示圖
#會塞兩張圖進來 顯示cut圖和mask圖
cv2.imshow(self.windowname , self.dests[0])
cv2.imshow(self.windowname + ": mask" , self.dests[1])
# onMouse function for Mouse Handling
# 處理滑鼠動作的指令
def on_mouse(self, event, x, y, flags, param):
#point座標
pt = (x, y)
#當滑鼠剛按下去,就先記錄一下這個點的座標,代表畫線任務開始,只要prev_pt不是None 下面的if判斷式就會進行
if event == cv2.EVENT_LBUTTONDOWN:
self.prev_pt = pt
elif event == cv2.EVENT_LBUTTONUP: #鬆開就忘記座標,歸0,那下面線就畫不出來了,代表這個畫線任務結束
self.prev_pt = None
#self.prev_pt如果沒有歸零,就代表是同一個畫線任務仍在進行中
#CV_EVENT_FLAG_LBUTTON 的意思是 有左鍵拖曳的事件發生
#cv2.line(影像, 開始座標, 結束座標, 顏色, 線條寬度)
if self.prev_pt and cv2.EVENT_FLAG_LBUTTON:
#針對兩個圖分別處理標示
for dst, color in zip(self.dests, self.colors_func()):
#((0,0,255), 0)
#隨著你畫線,會開始在兩張圖裡面開始標註color,第一張圖就會配你設計的(0,0,255),如果第二張圖就會用後面那個0了
cv2.line(dst, self.prev_pt, pt, color, 5)
#每次畫了線就更新一下起點,那你就會畫出一條線,如果這句刪掉會怎樣?就會每次原點出發
self.prev_pt = pt
self.show()
while True:
#把蒐集到鍵盤的訊號存起來,下面就開始根據不同的訊號,開始不同的處理
ch = cv2.waitKey()
#Quit
#按esc
if ch == 27:
print("exiting...")
#存照片,不接受中文名稱
cv2.imwrite("cut_result.png",img_mask)
cv2.imwrite("cutmask_result.png",mask2)
break
# 按r的時候Reset
elif ch == ord('r'):
print("重新開始...")
img_mask = img.copy()
mask2 = mask_copy.copy()
#處理成0或1
mask2 = mask2//255
#切割、重分類
#沒有設定rect,改用None
cv2.grabCut(img,mask2,None,bgdModel,fgdModel,5,cv2.GC_INIT_WITH_MASK)
mask2 = np.where((mask2==2)|(mask2==0),0,1).astype('uint8')
#準備兩個要顯示的圖
img_mask = img*mask2[:,:,np.newaxis]
mask2 = mask2*255
#'image'是sketcher show的時候,視窗的名稱
# [img_mask, mask2] 是等一下要顯示的cut圖和mask圖
# lambda是等一下sketch顯示的顏色
sketch = Sketcher('image', [img_mask, mask2], lambda : ((255,0,0), 255))
sketch.show()
# 按了b,表示改成背景選取
elif ch == ord('b'):
print("處理背景...")
sketch = Sketcher('image', [img_mask, mask2], lambda : ((0,0,255), 0)) #這裡lambda的設定就是(img_mask的顏色,mask2的顏色),因為這是畫背景,所以mask2的顏色是0
sketch.show()
# 按了f,表示改成前景選取
elif ch == ord('f'):
print("處理前景...")
sketch = Sketcher('image', [img_mask, mask2], lambda : ((255,0,0), 255)) #這裡lambda的設定就是(img_mask的顏色,mask2的顏色),因為這是畫前景,所以mask2的顏色是1
sketch.show()
else:
# print("進行 grabcut...")
#處理成0或1
mask2 = mask2//255
#沒有設定rect,改用None
cv2.grabCut(img,mask2,None,bgdModel,fgdModel,5,cv2.GC_INIT_WITH_MASK)
mask2 = np.where((mask2==2)|(mask2==0),0,1).astype('uint8')
img_mask = img*mask2[:,:,np.newaxis]
mask2 = mask2*255
# print("處理好了...前台看看...")
sketch = Sketcher('image', [img_mask, mask2], lambda : ((255,0,0), 255))
sketch.show()
cv2.destroyAllWindows()
結果咧結果咧
登愣~去背成功!!
緊接著要把我家弟弟送上月球去了
原圖:空無一人的月球表面,凝望著地球
步驟4. 準備拿個mask挖洞再把弟弟給送上外太空
#因為找到的月球照,像數很小,所以把我家弟弟圖片先縮小一些
import cv2
import numpy as np
image = cv2.imread('cut_result.png')
image_new = cv2.resize(image, (0,0), fx=0.5, fy=0.5)
cv2.imshow('image ', image_new )
cv2.imwrite('small_grabcut_lele.png', image_new)
#lele on moon
import cv2
import numpy as np
# Load image:
image = cv2.imread('moon3.jpg')
face_mask= cv2.imread('small_grabcut_lele.png')
gray = cv2.cvtColor(image , cv2.COLOR_BGR2GRAY)
rect = cv2.selectROI(gray)
x,y,w,h = rect
#確定目標區域
frame_roi = image[y:y+h, x:x+w]
#調整lele大小
face_mask_lele = cv2.resize(face_mask, (w,h),interpolation=cv2.INTER_AREA)
#做出lele的遮罩
gray_mask = cv2.cvtColor(face_mask_lele, cv2.COLOR_BGR2GRAY)
ret, mask1 = cv2.threshold(gray_mask, 10, 255, cv2.THRESH_BINARY)
#依照遮罩,把lele挖出來
masked_face = cv2.bitwise_and(face_mask_lele, face_mask_lele, mask=mask1)
#依照遮罩,把ROI的地方挖出洞來
mask1_inv = cv2.bitwise_not(mask1) #用mask1做顏色反轉
masked_frame = cv2.bitwise_and(frame_roi, frame_roi, mask=mask1_inv)
#合體
image[y:y+h, x:x+w] = cv2.add(masked_face, masked_frame)
cv2.imshow('Moon2_image ', image )
cv2.imwrite('Moon2_lele.png', image)
cv2.waitKey(0)
cv2.destroyAllWindows()
OK! That's it! 登月成功! 看我家弟弟笑的多開心呢~~~