OpenCV 实现图像卡通化,DIY动漫大作

2,836 阅读3分钟

「这是我参与11月更文挑战的第25天,活动详情查看:2021最后一次更文挑战

前言

OpenCV 中,可以应用 cv2.bilateralFilter() 来减少噪声,同时保留锐利的边缘。但是,此滤波器会在滤波后的图像中产生阶梯效应和虚假边线。虽然可以通过改进双边滤波来减少这一缺陷,但此效应可以用于创建很酷的卡通化图像。在本文中,我们就一起来探索下如何在 OpenCV 中实现图像的卡通化吧。

图像卡通化

要实现图像的卡通化,首先需要根据图像的边缘构造图像的草图。

构造图像的草图使用 sketch_image() 函数,首先将彩色图片转化为灰度图像,然后通过 cv2.medianBlur() 中值滤波器平滑图像来降低噪声:

img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
img_gray = cv2.medianBlur(img_gray, 5)

接下来,使用拉普拉斯算子 cv2.Laplacian() 作为边缘检测器:

edges = cv2.Laplacian(img_gray, cv2.CV_8U, ksize=5)

接下来,利用图像的边缘创建图像的草图,通过应用 cv2.threshold() 对生成的图像进行阈值处理(之后会专门介绍阈值技术,这里我们仅需要知道这个函数为我们提供了一个来自给定灰度图像的二进制图像,作为 sketch_image() 函数的输出)。可以使用不同的阈值来控制出现在结果图像中的黑色像素(即检测到的边缘)的数量,如果阈值很小(例如 10 ),则会出现许多黑色像素,反之如果该值较大(例如 200 ),那么就会得到很少的黑色像素,可以通过此值获取自己满意的结果:

ret, threshold = cv2.threshold(edges, 145, 255, cv2.THRESH_BINARY_INV)

接下来,我们就可以通过编写 cartonize_image() 函数来实现卡通化图像的功能。

为了获得卡通化效果,首先使用较大值调用 cv2.bilateralFilter() 函数,例如:

cv2.bilateralFilter(img, 10, 250, 250) 

第二步也是最后一步,使用 cv2.bitwise_and() 函数(按位运算)将草图图像和双边滤波的输出结合在一起,草图图像作为掩膜,以确保这些值的输出。如果需要,输出也可以转换为灰度图像:

cartoonized = cv2.bitwise_and(filtered, filtered, mask=threshold)

if gray_mode:
    return cv2.cvtColor(cartoonized, cv2.COLOR_BGR2GRAY)

除了上述自定义函数实现图像卡通化外,OpenCV 也内置提供了类似的函数,可以使用以下滤波器实现相同的功能:

  1. cv2.pencilSketch():此过滤器生成铅笔素描线图(类似于自定义的 sketch_image() 函数)
  2. cv2.stylization() :此滤镜可用于产生各种非真实感的效果,可以应用 cv2.stylization() 获得卡通化效果(类似于自定义的 cartonize_image() 函数)

代码运行结果如下图所示:

卡通化效果

完整代码

完整代码如下所示:

import cv2
import matplotlib.pyplot as plt

def show_with_matplotlib(color_img, title, pos):
    img_RGB = color_img[:, :, ::-1]

    ax = plt.subplot(2, 4, pos)
    plt.imshow(img_RGB)
    plt.title(title, fontsize=10)
    plt.axis('off')

def sketch_image(img):
    img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    img_gray = cv2.medianBlur(img_gray, 5)
    edges = cv2.Laplacian(img_gray, cv2.CV_8U, ksize=5)
    ret, threshold = cv2.threshold(edges, 145, 255, cv2.THRESH_BINARY_INV)
    return threshold

# 自定义函数实现卡通化
def cartonize_image(img, gray_mode=False):
    # 提取图像的中的边
    threshold = sketch_image(img)
    # 双边滤波
    filtered = cv2.bilateralFilter(img, 10, 250, 250)
    # 卡通化
    cartoonized = cv2.bitwise_and(filtered, filtered, mask=threshold)

    if gray_mode:
        return cv2.cvtColor(cartoonized, cv2.COLOR_BGR2GRAY)
    return cartoonized

plt.figure(figsize=(14, 6))
plt.suptitle("Cartoonizing images", fontsize=14, fontweight='bold')

image = cv2.imread('sigonghuiye.jpeg')

custom_sketch_image = sketch_image(image)
custom_cartonized_image = cartonize_image(image)
custom_cartonized_image_gray = cartonize_image(image, True)

# 使用OpenCV函数实现卡通化
sketch_gray, sketch_color = cv2.pencilSketch(image, sigma_s=20, sigma_r=0.1, shade_factor=0.1)
stylizated_image = cv2.stylization(image, sigma_s=60, sigma_r=0.07)

show_with_matplotlib(image, "image", 1)
show_with_matplotlib(cv2.cvtColor(custom_sketch_image, cv2.COLOR_GRAY2BGR), 'custom sketch', 2)
show_with_matplotlib(cv2.cvtColor(sketch_gray, cv2.COLOR_GRAY2BGR), 'sketch gray cv2.pencilSketch()', 3)
show_with_matplotlib(sketch_color, 'sketch color cv2.pencilSketch()', 4)
show_with_matplotlib(stylizated_image, 'cartoonized cv2.stylization()', 5)
show_with_matplotlib(custom_cartonized_image, 'custom cartoonized', 6)
show_with_matplotlib(cv2.cvtColor(custom_cartonized_image_gray, cv2.COLOR_GRAY2BGR), 'custom cartoonized gray', 7)

plt.show()

更多卡通化效果展示

我们可以通过修改源图像或函数参数来获得令人满意的卡通化效果:

卡通化效果

卡通化效果

动手实战做起来吧。