---
# System prepended metadata

title: OpenCV 專題實作篇part 3-"失敗的麵"現正熱映中(惡搞小孩又來惹)
tags: [圖像分割, OpenCV, bitwise, 去背, grabcut]

---

###### tags: `OpenCV`,`去背`,`圖像分割`,`grabcut`,`bitwise`
# OpenCV 專題實作篇part 3-"失敗的麵"現正熱映中(惡搞小孩又來惹)

目標:讓我家小孩去當演員

歡迎,我們這次的主角,失敗的麵主角~~~

![](https://i.imgur.com/HEyf8DS.jpg)

```python=
import cv2
import numpy as np

img_le = cv2.imread("spider_lele.jpg")
img = cv2.resize(img_le, (0,0), fx=0.2, fy=0.2)#圖片過大,縮小一下
print(img.shape)
mask = np.zeros(img.shape[:2], np.uint8)#這個先做一個空的mask
bgdModel = np.zeros((1,65),np.float64)
fgdModel = np.zeros((1,65),np.float64)

rect = cv2.selectROI(img)#選取失敗的麵主角
cv2.destroyAllWindows()

for i in range(3,10,2): #可以透過這個去調整,看哪個效果好
    mask = np.zeros(img.shape[:2], np.uint8)
    bgdModel = np.zeros((1,65),np.float64)
    fgdModel = np.zeros((1,65),np.float64)
    cv2.grabCut(img,mask,rect,bgdModel,fgdModel,i,cv2.GC_INIT_WITH_RECT)
    mask2 = np.where((mask==0)|(mask==2),0,1).astype('uint8')
#     print(np.unique(mask2))
    img_copy = img.copy()
    img_copy = img_copy*mask2[:,:,np.newaxis]#原本mask為2d結構,現在把資料格式變成3d結構
    cv2.imshow(f"Image{i}_spider",img_copy)
    cv2.imwrite(f"Image{i}_spider.png",img_copy)

cv2.waitKey(0)    
cv2.destroyAllWindows()
```

run完結果效果也是沒有很好,只好再次出動一次筆刷功能啦!!
![](https://i.imgur.com/YACaZXz.jpg)

```python=
import cv2
import numpy as np
img = cv2.imread("Image5_spider.png")
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')
cv2.imshow("Mask2",mask2*255)
cv2.waitKey(0)
cv2.destroyAllWindows()

```
可以稍微看一下ROI的結果
![](https://i.imgur.com/n9TbmXm.jpg)

```python=
#將mask還原為3d結構
imgResult = img*mask2[:,:,np.newaxis]
cv2.imshow("Image",imgResult)
cv2.waitKey(0)
cv2.destroyAllWindows()
```
![](https://i.imgur.com/8sbW7Zg.jpg)

```python=
#把圖各準備一份
img_mask = img.copy()
mask2 = mask2*255
mask_copy = mask2.copy()
```
準備筆刷工具

```python
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()

```
```python
while True:
    #把蒐集到鍵盤的訊號存起來，下面就開始根據不同的訊號，開始不同的處理
    ch = cv2.waitKey()
    
    #Quit
    #按esc
    if ch == 27:
        print("exiting...")
        #存照片，不接受中文名稱
        cv2.imwrite("spyderman_cut.png",img_mask)
        cv2.imwrite("spyderman_cutmask.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()
```
可以看一下程式run的過程,利用鍵盤按"b"進行背景處理,這時會觸動滑鼠按鍵function,滑鼠塗紅色的區域就是要去背的部分,可以按一下空白鍵,會在顯示修改過後的結果!
![](https://i.imgur.com/idWGYZu.jpg)

![](https://i.imgur.com/fP43Az6.jpg)


結果滿意後記得按下esc鍵,才會進行修改後檔案的儲存功能喔!!

已儲存至資料夾中啦!!
![](https://i.imgur.com/8PbgsHE.jpg)

緊接著就來找張電影廣告海報啦!!
就決定是你了(丟寶貝球)~~~~ 去吧~~~
![](https://i.imgur.com/EN0nnTN.jpg)

```python
#lele movie star
import cv2
import numpy as np

# Load image:
image = cv2.imread('spiderman.jpg')
face_mask= cv2.imread('spyderman_cut.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, 2, 255, cv2.THRESH_BINARY)  #threshold修改為2(接近黑色的地方)
#依照遮罩，把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('Spider_image', image )
cv2.imwrite('Spider_lele2.png', image)
cv2.waitKey(0)
cv2.destroyAllWindows()
```
來囉來囉!


![](https://i.imgur.com/rWsNrlj.jpg)

這次腳在水面上,決定再將"spyderman_cut.png",照片黑色的部分再修改少一些,這樣人物會再大一些,角度也可以在微調一下,再重新run一次程式!

![](https://i.imgur.com/RgCOmxg.png)

重run後的效果更好了!!
**電影結束囉~謝謝您的收看!!**

