原文链接: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得到的平滑图像上得到了卡通化图像。