zoukankan      html  css  js  c++  java
  • 用PIL实现滤镜(一)——素描、铅笔画效果

    在计算机图形学发展史中,真实感绘制一直是主旋律。不过从20实际90年代中期开始,非真实感图像绘制(Non-Photorealistic Rendering,NPR)逐渐成为一个研究热点。说白了,真实感绘制目标是像照片般真实地再现客观世界,而非真实感图像绘制专注于图形个性化和艺术化的表达,它主要用来表现图形的艺术特质,以及模拟艺术作品(甚至包括作品中的缺陷)。

    在介绍完非真实感图像绘制之后,我们再来提及一下PIL——Python Imaging Library(官方网址)。相信使用python的朋友们都不会陌生,因为在web应用中我们常常用它来生成缩略图。从名字也可以看出,PIL主要用来处理图片,它支持多种图片格式,并提供强大的图片和图像处理能力。详细的关于PIL的内容大家可以参阅手册

    这个系列,我们就主要使用PIL来进行滤镜方面的处理,包括素描、铅笔画、油画等等滤镜效果的实现。在以后我会把代码托管出来。

    PIL已经内置一些滤镜效果,详细见文档这篇文章

    我们从素描效果开始。在PIL中,基础的类是Image类,首先有必要讲一下图片的mode。它有以下几种:

    • 1 (1-bit pixels, black and white, stored with one pixel per byte)
    • L (8-bit pixels, black and white):用来表示灰度图
    • P (8-bit pixels, mapped to any other mode using a colour palette)
    • RGB (3x8-bit pixels, true colour)
    • RGBA (4x8-bit pixels, true colour with transparency mask)
    • CMYK (4x8-bit pixels, colour separation)
    • YCbCr (3x8-bit pixels, colour video format)
    • I (32-bit signed integer pixels)
    • F (32-bit floating point pixels)

    在开始素描效果之前,我们需要首先进行灰度图像预处理。所幸的是,用PIL非常容易实现。设img是Image类的实例,我们只要用convert函数强制转换为L模式即可。

    1
    img = img.convert("L")

    不过还是有必要讲一下灰度预处理。何谓图像灰度化呢?图像灰度化即是使色彩的三种颜色分量的R,G,B的分量值相等,由于R,G,B的取值范围是[0, 255],所以灰度图像能够表示256种灰度颜色******像灰度法主要有三种算法:

    1. 最大值法(Maximum):使R、G、B的值等于三个色彩分量中的最大的一个分量值,即:R=G=B=Max(R,G,B)。
    2. 平均值法(Average):使R、G、B的值等于三个色彩分量的三个色彩分量的平均值,即:R=G=B= (R+G+B)/3。 
    3. 加权平均值法(Weight Average):在这里我给R、G、B三分量分别附上不同的权值,表示为:R=G=B=WR*R+WG*G+WB*B ,其中WR,WG,WB分别是R、G、B的权值。在这里考虑由于人眼对绿色的敏感度最高,红色次之,对蓝色的敏感度最低,因此,当权值 WG > WR > WB时,所产生的灰度图像更符合人眼的视觉感受。PIL库使用ITU-R 601-2 luma transform:
      L = R * 299/1000 + G * 587/1000 + B * 114/1000
      即 WR=29.9%,WG=58.7%,WB=11.4%。

    素描滤镜的处理关键是对边缘的查找。通过对边缘的查找可以得到物体的线条感。在对图像进行灰度化处理后,我们首先定义一个阈值(threshold)。我们知道素描主要强调的是明暗度的变化,绘制时是斜向方向,通过经验,我们将每个像素点的灰度值与其右下角的灰度值进行比较,当大于这个阈值时,就判断其是轮廓并绘制。

    以下是素描滤镜的主函数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    from PIL import Image
     
    def sketch(img, threshold):
        '''
        素描
        param img: Image实例
        param threshold: 介于0到100
        '''
        if threshold < 0: threshold = 0
        if threshold > 100: threshold = 100
         
        width, height = img.size
        img = img.convert('L') # convert to grayscale mode
        pix = img.load() # get pixel matrix
     
        for w in xrange(width):
            for h in xrange(height):
                if w == width-1 or h == height-1:
                    continue
                 
                src = pix[w, h]
                dst = pix[w+1, h+1]
     
                diff = abs(src - dst)
     
                if diff >= threshold:
                    pix[w, h] = 0
                else:
                    pix[w, h] = 255
     
        return img

    接着,我们写一个测试部分来看看效果:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    if __name__ == "__main__":
        import sys, os
     
        path = os.path.dirname(__file__) + os.sep.join(['', 'images', 'lam.jpg'])
        threshold = 15
         
        if len(sys.argv) == 2:
            try:
                threshold = int(sys.argv[1])
            except ValueError:
                path  = sys.argv[1]
        elif len(sys.argv) == 3:
            path = sys.argv[1]
            threshold = int(sys.argv[2])
     
        img = Image.open(path)
        img = sketch(img, threshold)
        img.save(os.path.splitext(path)[0]+'.sketch.jpg', 'JPEG')

    可以在命令行中指定文件名和阈值,或者只指定阈值,或者不带参数。我的测试图片为:

    lam

    效果图片:

    lam.sketch

    不同的阈值,生成的效果不同。阈值越小,绘制的像素点就越多。

    对于铅笔画来说,原理和素描十分相似,但是大家学过画画的就知道,素描强调的是阴影的效果,是斜向作画,而铅笔画主要是勾勒轮廓。因此在对每个像素点的处理上,就和素描产生变化。对于任意一个像素点,求出这个像素点的R、G、B三个分量与周围8个点的相应分量的平均值的差,如果这三个差都大于或者等于某个阈值,就画出线条。最后,铅笔画的作画不是单调的一种颜色,因此加入Alpha分量,大小等于对应点的alpha分量即可。于是,代码如下;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    def pencil(img, threshold):
        '''
        铅笔画
        param img: instance of Image
        param threshold
        '''
        if threshold < 0: threshold = 0
        if threshold > 100: threshold = 100
     
        width, height = img.size
        dst_img = Image.new("RGBA", (width, height))
     
        if img.mode != "RGBA":
            img = img.convert("RGBA")
     
        pix = img.load()
        dst_pix = dst_img.load()
     
        for w in xrange(width):
            for h in xrange(height):
                if w == 0 or w == width - 1
                   or h == 0 or h == height - 1:
                    continue
     
                # 包括当前像素周围共9个像素点
                around_wh_pixels = [pix[i, j][:3] for j in xrange(h-1, h+2) for i in xrange(w-1, w+2)]
                # 排除当前像素点
                exclude_wh_pixels = tuple(around_wh_pixels[:4] + around_wh_pixels[5:])
                # 把各个像素点的各个分量求平均值         
                RGB = map(lambda l: int(sum(l) / len(l)), zip(*exclude_wh_pixels))
                 
                cr_p = pix[i, j] # 当前像素点
     
                cr_draw = all([abs(cr_p[i] - RGB[i]) >= threshold for i in range(3)])
                
                if cr_draw:
                    dst_pix[w, h] = 0, 0, 0, cr_p[3]
                else:
                    dst_pix[w, h] = 255, 255, 255, cr_p[3]
     
        return dst_img

    效果如图:

    lam.pencil

    来自:http://qinxuye.me/article/implement-sketch-and-pencil-with-pil/

  • 相关阅读:
    C++003类的析构函数
    C++002类的构造函数
    C++001类
    simulink与控制系统仿真01自动控制原理简介
    telnet
    WEB
    Python_包
    Python_装饰器
    Pycharm+Tensorflow安装和使用出现的问题集合
    HTML+CSS综合使用
  • 原文地址:https://www.cnblogs.com/zhn620/p/9251192.html
Copyright © 2011-2022 走看看