国外课栈 - 国外电子信息技术视角栈

聚焦Python和OpenCV滤镜

 二维码 23

滤镜

创建黑白铅笔素描

为了获得相机框架的铅笔素描(即黑白图),我们将使用两种图像融合技术,即躲避和刻录。 这些术语是指在传统摄影的打印过程中使用的技术; 在这里,摄影师可以控制暗室照片某些区域的曝光时间,以使其变暗或变暗。 躲避使图像变亮,而刻录使图像变暗。 不应该进行更改的区域用掩膜保护。

如今,现代图像编辑程序(例如Photoshop和Gimp)提供了在数字图像中模拟这些效果的方法。 例如,掩模仍然被用来模仿改变图像的曝光时间的效果,其中具有相对强的值的掩模的区域将使图像更多地曝光,从而使图像变亮。 OpenCV不提供实现这些技术的本机功能。 但是,有了一点见识和一些技巧,我们将得出我们自己的有效实现,可以用来产生漂亮的铅笔素描效果。

如果在Internet上搜索,可能会偶然发现以下通用过程,从RGB(红色,绿色和蓝色)彩色图像中获得铅笔素描:

  1. 首先,将彩色图像转换为灰度。
  1. 然后,将灰度图像反转以获得负片。
  1. 将高斯模糊应用于步骤2中的负片。
  1. 通过使用彩色减淡功能,将灰度图像(来自步骤1)与模糊负片(来自步骤3)混合。

第1步到第3步很简单,而第4步可能有些棘手。 让我们先解决这个问题。

理解躲避和烧录

减淡可以减少我们希望在图像A中变得比以前更亮的图像区域的曝光。在图像处理中,我们通常选择或指定需要使用蒙版更改的图像区域。掩膜B是与可应用掩膜的图像尺寸相同的数组(可将其视为一张纸,用于覆盖其中有孔的图像)。 纸上的“孔”在不透明的区域中用零表示255(如果在0-1范围内,则用1表示)。

在诸如Photoshop之类的现代图像编辑工具中,通过使用下面的三元语句(使用索引i作用于每个像素)来实现图像A与掩膜B的颜色减淡:

((B[i] == 255) ? B[i] :
 min(255, ((A[i] << 8) / (255 - B[i]))))

前面的代码实质上是将A [i]图像像素的值除以B [i]掩膜像素值的倒数(范围为0-255),同时确保所得的像素值将为 (0,255)的范围,并且我们不除以0。

我们可以将先前看起来复杂的表达式或代码转换为以下朴素的Python函数,该函数接受两个OpenCV矩阵(图像和掩膜)并返回混合图像:

def dodge_naive(image, mask):
 # determine the shape of the input image
 width, height = image.shape[:2]
 # prepare output argument with same size as image
 blend = np.zeros((width, height), np.uint8)
 for c in range(width):
   for r in range(height):
   # shift image pixel value by 8 bits
   # divide by the inverse of the mask
   result = (image[c, r] << 8) / (255 - mask[c, r])
   # make sure resulting value stays within bounds
   blend[c, r] = min(255, result)
 return blend

正如您可能已经猜到的那样,尽管先前的代码在功能上可能是正确的,但毫无疑问,它会非常慢。 首先,该函数使用for循环,这在Python中几乎总是一个坏主意。 其次,NumPy数组(Python中OpenCV图像的基本格式)已针对数组计算进行了优化,因此分别访问和修改每个image [c,r]像素将非常慢。

相反,我们应该意识到<< 8运算与将像素值乘以282^8(= 256)相同,并且可以使用cv2.divide函数实现按像素划分。 因此,利用矩阵乘法(更快)的躲避函数的改进版本如下所示:

import cv2
def dodge(image, mask):
 return cv2.divide(image, 255 - mask, scale=256)

在这里,我们将躲避函数简化为一行! 新的躲避函数产生的结果与dodge_naive相同,但是它比朴素的版本快几个数量级。 除此之外,cv2.divide会自动处理零除,使结果为零,其中255-mask为零。

这是Lena.png的躲避版本,其中我们在正方形范围内躲避,像素范围为(100:300,100:300):

如您所见,在正确的照片中,变亮的区域非常明显,因为转变非常清晰。

用二维卷积实现高斯模糊

高斯模糊是通过将图像与高斯值的内核进行卷积来实现的。 二维卷积在图像处理中被广泛使用。 通常,我们有一幅大图(让我们看一下该特定图像的5 x 5子部分),并且我们有一个内核(或过滤器),它是另一个较小尺寸的矩阵(在我们的示例中为3 x 3)。

为了获得卷积值,我们假设要在位置(2,3)处获得该值。 我们将内核置于位置(2,3)的中心,然后计算覆盖矩阵与内核的点积(突出显示的区域,在下图(红色)中),并取总和。 结果值(即158.4)是我们在位置(2,3)的另一个矩阵上写入的值。

请注意,对于边界上的点,内核未与矩阵对齐,因此我们必须找出一种为这些点提供值的策略。 没有一个适用于所有事物的好的战略; 一些方法是用零扩展边界或用边界值扩展。

应用铅笔素描变换

以下过程显示了如何将彩色图像转换为灰度图像。 之后,我们的目标是将灰度图像与其模糊负片混合:

  • 首先,我们将RGB图像(imgRGB)转换为灰度:
 img_gray = cv2.cvtColor(img_rgb, cv2.COLOR_RGB2GRAY)

如您所见,我们将cv2.COLOR_RGB2GRAY用作cv2.cvtColor函数的参数,该函数更改了色彩空间。 请注意,输入图像是RGB还是BGR(这是OpenCV的默认设置)都没有关系。 最后,我们将获得一个不错的灰度图像。

  • 然后,我们反转图像并使用大小为(21,21)的大高斯核对其进行模糊处理:
inv_gray = 255 - gray_image
blurred_image = cv2.GaussianBlur(inv_gray, (21, 21), 0, 0)
  • 我们使用闪避将原始灰度图像与模糊逆混合在一起:
gray_sketch = cv2.divide(gray_image, 255 - blurred_image,
 scale=256)

图像结果如下:

您是否注意到我们的代码可以进一步优化? 接下来让我们看看如何使用OpenCV进行优化。

使用高斯模糊的优化版本

高斯模糊本质上是具有高斯函数的卷积。卷积的特征之一就是它们的关联属性。 这意味着我们先反转图像然后对其进行模糊处理,还是先模糊图像然后对其进行反转都没有关系。

如果我们从模糊的图像开始并将其逆传递给躲避函数,则在该函数内,图像将再次反转(255掩膜部分),实质上产生了原始图像。 如果我们摆脱了这些多余的操作,则经过优化的convert_to_pencil_sketch函数将如下所示:

def convert_to_pencil_sketch(rgb_image):
 gray_image = cv2.cvtColor(rgb_image, cv2.COLOR_RGB2GRAY)
 blurred_image = cv2.GaussianBlur(gray_image, (21, 21), 0, 0)
 gray_sketch = cv2.divide(gray_image, blurred_image, scale=256)
 return cv2.cvtColor(gray_sketch, cv2.COLOR_GRAY2RGB)

为了形象化,我们想将转换后的图像(img_sketch)与背景图像(画布)轻轻混合,使其看起来就像我们在画布上绘制图像一样。 因此,在返回之前,我们想与画布混合(如果存在):

if canvas is not None:
 gray_sketch = cv2.multiply(gray_sketch, canvas, scale=1 / 256)

我们将最终函数命名为pencil_sketch_on_canvas,它看起来像这样(连同优化):

def pencil_sketch_on_canvas(rgb_image, canvas=None):
 gray_image = cv2.cvtColor(rgb_image, cv2.COLOR_RGB2GRAY)
 blurred_image = cv2.GaussianBlur(gray_image, (21, 21), 0, 0)
 gray_sketch = cv2.divide(gray_image, blurred_image, scale=256)
 if canvas is not None:
 gray_sketch = cv2.multiply(gray_sketch, canvas, scale=1 / 256)
 return cv2.cvtColor(gray_sketch, cv2.COLOR_GRAY2RGB)

这只是我们的convert_to_pencil_sketch函数,带有可选的canvas参数,可以为铅笔素描增加艺术感。

最终结果如下:

添加暖色和冷色过滤

当我们感知图像时,我们的大脑会从许多微妙的线索中提取出有关场景的重要细节。 例如,在明亮的日光下,高光可能会因为在阳光直射下而具有淡黄色的色彩,而阴影可能会由于蓝天的环境光而显得略带蓝色。 当我们查看具有这种颜色属性的图像时,我们可能会立即想到晴天。

这种效果对于摄影师而言并非是个谜,他们有时会故意操纵图像的白平衡来传达某种情绪。 暖色通常被认为更令人愉悦,而冷色则与夜晚和昏暗有关。

为了操纵图像的感知色温,我们将实现曲线过滤器。 这些滤镜控制颜色过渡如何在图像的不同区域之间出现,从而使我们可以巧妙地改变色谱图,而不会给图像增加看起来不自然的整体色调。

通过曲线平移使用颜色操作

曲线滤镜本质上是一个函数y = f(x),它将输入像素值x映射到输出像素值y。 该曲线由一组n+1个锚点进行参数化,如下所示:

{(x0,y0),(x1,y1),...,(xn,yn)}\left\{\left(x_{0,}y_0 \right),\left(x_1,y_1\right),...,\left(x_n,y_n\right)\right\}

在这里,每个锚点都是一对数字,分别代表输入和输出像素值。 例如,对(30,90)表示将输入像素值30增加到输出值90。锚点之间的值沿着平滑曲线进行插值。

这样的滤镜可以应用于任何图像通道,无论是单个灰度通道还是RGB彩色图像的R(红色),G(绿色)和B(蓝色)通道。 因此,出于我们的目的,x和y的所有值必须保持在0到255之间。

例如,如果我们想使灰度图像稍微亮一些,可以使用带有以下控制点集的曲线滤镜:

{(0,0),(128,192),...,(255,255)}\left\{\left(0,0 \right),\left(128,192\right),...,\left(255,255\right)\right\}

这意味着除0和255外的所有输入像素值都会略微增加,从而对图像产生整体增亮效果。

如果我们希望这样的滤镜产生看起来自然的图像,则必须遵守以下两个规则:

  • 每组锚点都应包括(0,0)和(255,255)。 这对于防止图像看起来好像具有整体色调非常重要,因为黑色保持黑色,白色保持白色。
  • f(x)函数应单调增加。 换句话说,通过增加x,f(x)可以保持不变或增加(即,从不减少)。 这对于确保阴影仍然是阴影而高光仍然是高光非常重要。

使用查找表实现曲线滤镜

设计暖色和冷色效果

卡通图像

使用双边滤镜进行边缘感知平滑

检测并着重显著边缘

组合颜色和轮廓制作卡通

整合


阅读完整文档

文章分类: 编程碎语PythonOpenCV