如何使用 Python(NumPy 和 OpenCV)对图像进行 Funkify
作者|Luke Tambakis
编译|Flin
来源|medium
在这篇博客中,我将解释如何制作一个 Python 脚本来使用 Python 代码“funkify”图像。该程序速度足够快,甚至可以处理实时视频(无需 GPU)!像这样:

如果你对枯燥的代码解释不感兴趣,只想自己尝试一下,最简单的方法是使用FunkyCam存储库。它会向你展示如何在几行代码中安装和运行它:https://github.com/LTambam/FunkyCam
怎么运行
代码的主要功能是将图像作为输入并返回 funkified 图像作为输出:
def funkify(self, img):
edges = self.edge_mask(img)
blur = cv2.GaussianBlur(img, (self.color_blur_val, self.color_blur_val),
sigmaX=0, sigmaY=0)
indices = self.pick_color(blur.reshape((-1, 3)),
self.lightness, self.n_colors)
recolored = np.uint8(self.colors[indices].reshape(blur.shape))
cartoon = cv2.bitwise_and(recolored, recolored, mask=edges)
return cartoon
现在,我将以此图为例将其逐行分解。

示例图片
第1步:增加边缘宽度
该项目基于此博客(https://github.com/Sudarshana2000/cartoonization)。我想重新利用这些代码,以便它可以在网络摄像头上实时运行。但他们的方法证明是不可能的,但他们的一些代码仍然非常出色。特别是用于查找图像边缘并增加边缘宽度的函数。
加厚边缘的目的是使其看起来像卡通或动漫中的墨线。获取边缘是由代码中的第一行完成的。
edges = self.edge_mask(img)
edge_mask() 函数调用以下代码。
def edge_mask(self, img):
# get the edges of the image
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
gray_blur = cv2.GaussianBlur(gray,
(self.edge_blur_val, self.edge_blur_val),
-1)
edges = cv2.adaptiveThreshold(gray_blur, 255,
cv2.ADAPTIVE_THRESH_MEAN_C,
cv2.THRESH_BINARY,
self.block_size,
2)
return edges
第一和第二行对图像进行预处理,为自适应阈值函数做准备。图像首先从彩色转换为灰度,然后使用高斯核进行模糊处理,以使自适应阈值函数生成噪音较少的输出。
第三行是主要部分。自适应阈值化是一种二值化函数,这意味着它将图像中的每个像素分类为黑色(0)或白色(1)。有许多二值化算法。自适应阈值化的独特之处在于它根据相邻像素的强度而不是整个图像来分类每个像素。这使它在不同光照条件下表现更好。

自适应阈值和全局阈值比较
使用此功能,可以快速找到图像的边缘,这使其非常适合我们实时运行的版本。这是应用于我们的示例图像的函数。

第 2 步:对图像重新着色
下一步是对图像重新着色。使某些东西看起来卡通化的原因之一是图像中的颜色较少。例如,由于光照的原因,真实图像会具有数千种不同的颜色和色调,但卡通图像只有几种颜色。这可以使用卡通着色器来完成,这通常在视频游戏中完成。

在我基于该项目的代码(https://github.com/Sudarshana2000/cartoonization)中,他们使用 K-means 将像素聚类为一定数量的颜色。然后每个像素都更改为其组的平均颜色。虽然这种方法效果很好,但对于实时使用来说太慢了。此外,这有点无聊。
相反,颜色是手动选择的,然后根据它们在亮度上最接近的颜色,将所有像素都覆盖为这些颜色。这消除了查找颜色的需求,而且实际上可以根据需要使图像看起来很酷。之所以选择亮度(亮度的定量测量)是因为它保留了原始图像的光照效果。
在主函数中重新着色的代码包括以下三行:
blur = cv2.GaussianBlur(img, (self.color_blur_val, self.color_blur_val),
sigmaX=0, sigmaY=0)
indices = self.pick_color(blur.reshape((-1, 3)),
self.luminance, self.n_colors)
recolored = np.uint8(self.colors[indices].reshape(blur.shape))
第一行只是对图像进行模糊处理,就像在边缘增宽时一样。其目的是使最终图像中的颜色看起来更平滑。
第二行是最重要的部分。pick_color() 函数是用来计算图像中每个像素的颜色的函数。它运行以下代码:
def pick_color(self, img, color_lums, n_colors):
# reassign pixel colors based on luminance
# get luminance of pixels
lum_mult = [0.114, 0.587, 0.299]
img_lum = np.sum(np.multiply(img, lum_mult), axis=1)
# create list of conditions for each color
condlist = []
choicelist = []
for i in range(n_colors):
choicelist.append(i)
if i < n_colors-1:
condlist.append(img_lum < (color_lums[i]+color_lums[i+1])/2)
else:
condlist.append(img_lum > (color_lums[i]+color_lums[i-1])/2)
# get index of new color for each pixel
inds = np.select(condlist, choicelist)
return inds
首先,该函数使用此帖(https://stackoverflow.com/questions/596216/formula-to-determine-perceived-brightness-of-rgb-color)中的公式根据 RGB 值计算每个像素的亮度。
然后,创建了一个条件列表,用于根据亮度确定选择哪种颜色。通过创建这个条件列表,该函数可以适应用户想要使用的任何颜色数量,而不是硬编码一个固定数量。np.select() 函数用于实际从这个列表中进行选择。它实际上是一系列像这样的“if语句”:
# e.g. for 3 colors
if pix_lum < (color_lums[0] + color_lums[1])/2:
pix_ind = 0
else if pix_lum < (color_lums[1] + color_lums[2])/2:
pix_ind = 1
else:
pix_ind = 2
第三行根据我们从pick_color()函数获得的索引对图像重新着色。它只是通过对图像数组进行切片,然后将其转换为 uint_8 来实现此目的,以便可以正确显示。
该过程的输出如下所示。

第 3 步:合并
主要功能的最后一部分是将重新着色的图像和粗边缘结合起来。
cartoon = cv2.bitwise_and(recolored, recolored, mask=edges)
最终结果:

看起来很酷,对吧?
它的运行速度非常快,你可以将其与网络摄像头实时结合使用,甚至不使用 GPU 处理。你还可以转换视频,如下所示:
☆ END ☆
如果看到这里,说明你喜欢这篇文章,请转发、点赞。微信搜索「uncle_pn」,欢迎添加小编微信「 woshicver」,每日朋友圈更新一篇高质量博文。
↓扫描二维码添加小编↓