原文鏈接:http://www.juzicode.com/python-funny-opencv-cartoon-profile-photo/
hello,大家好,我是桔子菌。
桔子菌前面發布了一些OpenCV方面的教程文章,稍顯枯燥乏味了些,今天我們用OpenCV做個好玩的東東,看看怎么將普通的照片變成卡通化一些。
我們先來觀察下卡通圖像的特點,再根據卡通圖像的特點從普通圖像反推處理過程。
上圖是一張典型的卡通人物頭像,從圖像可以看到人臉的輪廓非常分明,臉頰、下巴等和背景、頭發區分明顯,但是人臉內部則顏色比較均勻一致,轉換成圖像處理的行話就是邊界明顯、內部平滑。
所以拿到一張圖像首先是要做邊緣檢測找到邊界,然后就是平滑處理,最后將二者結合,下面介紹下具體的處理過程。
如果沒有安裝過OpenCV第三方庫,先用pip命令進行安裝:
pip install opencv-python
首先是一些預處理,從文件中獲取圖像,然后對圖片做縮放,圖像高度統一縮放到一定的高度值,寬度則做同比例縮放。
#讀取圖片
img_raw = cv2.imread(fn_raw)
#縮放圖片
HEIGHT_MAX = 500
height,width,_ = img_raw.shape
ration = HEIGHT_MAX/height
width = int(width * ration)
height = HEIGHT_MAX
img_resize = cv2.resize(img_raw,(width,height))
然后轉換成灰度圖,做一次中值濾波(平滑),去除掉一些噪聲,避免邊界點太多,為接下來的邊緣檢測做准備。
#灰度圖
img_gray = cv2.cvtColor(img_resize,cv2.COLOR_BGR2GRAY)
#去噪
img_blur = cv2.medianBlur(img_gray,5)
cv2.imshow('img_blur',img_blur)
接下來就是找邊界,我們可以用Canny,Laplacian、Sobel等方法,這里嘗試下各種不同的邊緣檢測算法最后對比下效果如何。
#邊緣檢測
if edge == 'Laplacian':
img_edge = cv2.Laplacian(img_blur,cv2.CV_8U,ksize=5)
elif edge == 'Scharr':
img_edge1=cv2.Scharr(img_gray,cv2.CV_8U,1,0,1,0)
img_edge2=cv2.Scharr(img_gray,cv2.CV_8U,0,1,1,0)
img_edge = cv2.add(img_edge1,img_edge2)
elif edge == 'Sobel':
img_edge1=cv2.Sobel(img_gray,cv2.CV_8U,1,0,ksize=3)
img_edge2=cv2.Sobel(img_gray,cv2.CV_8U,0,1,ksize=3)
img_edge = cv2.add(img_edge1,img_edge2)
else:
img_edge = cv2.Canny(img_blur,80,160,apertureSize=3)
cv2.imshow('img_edge',img_edge)
再將找到的邊界做二值化,再轉換為彩色圖像暫存,轉換為彩色圖像是因為后面進行與操作時需要保證和原圖通道數一致,如果原圖是單通道的,這里就沒有必要做色彩空間轉換。
#計算mask
_,img_mask= cv2.threshold(img_edge,100,255,cv2.THRESH_BINARY_INV)
img_mask = cv2.cvtColor(img_mask,cv2.COLOR_GRAY2BGR)
cv2.imshow('img_mask',img_mask)
然后就是對圖像做平滑處理,這里用到了雙邊平滑算法,並且連續做了多次,也可以實驗測試下medianBlur等其他的平滑算法。
#平滑處理
img_cartoon = img_resize
for x in range(11):
ksize,sigma_color,sigma_space = 21,11,11
img_cartoon = cv2.bilateralFilter(img_cartoon,ksize,sigma_color,sigma_space)
接下來就是將平滑后的圖像和前面得到的邊緣進行與操作,凸顯出邊界來,得到最后的卡通圖像:
#掩碼
img_cartoon = cv2.bitwise_and(img_cartoon,img_mask)
cv2.imshow('cartoon-'+edge,img_cartoon)
下面是原圖和Laplacian、Sobel、Canny等幾種不同邊緣檢測算法的對比:
完整的代碼如下:
import cv2
import os,sys,time
def cartoon(fn_raw,edge='Canny'):
#讀取圖片
img_raw = cv2.imread(fn_raw)
#縮放圖片
HEIGHT_MAX = 500
height,width,_ = img_raw.shape
ration = HEIGHT_MAX/height
width = int(width * ration)
height = HEIGHT_MAX
print('width,height:',width,height)
img_resize = cv2.resize(img_raw,(width,height))
cv2.imshow('img_resize',img_resize)
#灰度圖
img_gray = cv2.cvtColor(img_resize,cv2.COLOR_BGR2GRAY)
#去噪
img_blur = cv2.medianBlur(img_gray,5)
#img_blur = cv2.blur(img_gray,(5,5))
cv2.imshow('img_blur',img_blur)
#邊緣檢測
if edge == 'Laplacian':
img_edge = cv2.Laplacian(img_blur,cv2.CV_8U,ksize=5)
elif edge == 'Scharr':
img_edge1=cv2.Scharr(img_gray,cv2.CV_8U,1,0,1,0)
img_edge2=cv2.Scharr(img_gray,cv2.CV_8U,0,1,1,0)
img_edge = cv2.add(img_edge1,img_edge2)
elif edge == 'Sobel':
img_edge1=cv2.Sobel(img_gray,cv2.CV_8U,1,0,ksize=3)
img_edge2=cv2.Sobel(img_gray,cv2.CV_8U,0,1,ksize=3)
img_edge = cv2.add(img_edge1,img_edge2)
else: #Canny
img_edge = cv2.Canny(img_blur,80,160,apertureSize=3)
cv2.imshow('img_edge',img_edge)
#計算mask
_,img_mask= cv2.threshold(img_edge,100,255,cv2.THRESH_BINARY_INV)
img_mask = cv2.cvtColor(img_mask,cv2.COLOR_GRAY2BGR)
cv2.imshow('img_mask',img_mask)
#平滑處理
img_cartoon = img_resize
print('img_cartoon.shape:',img_cartoon.shape)
for x in range(11):
ksize,sigma_color,sigma_space = 21,11,11
img_cartoon = cv2.bilateralFilter(img_cartoon,ksize,sigma_color,sigma_space)
#cv2.imshow('cartoon-temp',img_cartoon)
#cv2.waitKey(100)
#掩碼
img_cartoon = cv2.bitwise_and(img_cartoon,img_mask)
cv2.imshow('cartoon-'+edge,img_cartoon)
cv2.imwrite("cartoon-" + fn_raw , img_cartoon)
cv2.waitKey()
cv2.destroyAllWindows()
if __name__ == '__main__':
cartoon('lvyi11.jpg',edge='Canny')
總結起來整個處理過程如下:
step1:原圖轉換為灰度圖像,利用邊緣檢測算法得到圖像的邊緣,二值化后得到一張掩碼圖;
step2:用雙邊濾波器多次處理原圖得到平滑后的圖像,圖像看起來更“絲滑”;
step3:將step1得到的掩碼圖作用到step2得到的平滑圖像上得到了卡通化圖像。