天天看點

純Python綜合圖像處理小工具(4)自定義像素級處理(剪紙濾鏡)

<背景> 

上一節介紹了python PIL庫自帶的10種濾鏡處理,現成的庫函數雖然用起來友善,但是對于圖像處理的各種實際需求,還需要開發者開發自定義的濾鏡算法。本文将給大家介紹如何使用PIL對圖像進行自定義的像素級操作。

<效果> 

本文以剪紙風格圖像處理作為例子:(算法借鑒了殘陽似血的部落格http://qinxuye.me/,特此鳴謝。)

原圖:

處理後: 

<怎麼做>

1.首先将處理參數預先設定好。設定門檻值threshold,該門檻值會用來區分作為目标顔色的前景色和将要被去除掉的的背景色的分界線。同時設定處理後前景色和後景色的顔色,用以呈現最終的分割效果。

    threshold = 150

    bg_color = (255, 255, 255, 0)

    fg_color = (255, 0, 0, 255)

    if len(sys.argv) >= 2:

        path  = sys.argv[1]

    if len(sys.argv) == 3:

        threshold = int(sys.argv[2])

    if len(sys.argv) == 5:

        bg_color = tuple(sys.argv[3])

        fg_color = tuple(sys.argv[4])

在這一步中,如果門檻值threshold設定不同數值,圖檔會呈現不同的二值分界效果,如下圖: 

如果門檻值過小,則圖像中的斑馬,會有部分顔色較淺的前景色,即灰色斑紋被誤判成背景色而被濾除掉,進而造成前景圖像輪廓的殘缺。

如果門檻值過大,則與上述情況相反,會有背景色被誤判成前景色,造成斑紋的邊緣膨脹,視覺上看也類似膨脹的效果(雖然原理完全不同)。

是以,選擇一個合适的門檻值,對圖像的分割效果至關重要。而這一點,又是随着圖像不同而變化的,門檻值要适應圖像内容。

對于這個問題,可以嘗試使用動态門檻值,結合圖像統計來實作自比對,本文未涉及,有興趣的童鞋可以自行嘗試。

2.将圖檔轉換成可以做像素操作的二值像素集合,然後使用設定好的門檻值threshold進行前景後景分割:

def Img2bin_arr(img, threshold):

    '''

    @将位圖流轉化為二維二值數組

    @param img: instance of Image

    @param threshold: 大小範圍[0, 255]

    '''

    threshold = max(0, threshold)

    threshold = min(255, threshold)

    if img.mode != 'L':

        img = img.convert('L')

    width, height = img.size

    pix = img.load()

    get_val = lambda p: 255 if p >= threshold else 0

    return [[get_val(pix[w, h]) for w in xrange(width)] for h in xrange(height)]

3.将分割好的像素使用預先設定的顔色上色,然後将像素集合重新封裝成圖檔格式,然後傳回這個圖檔,用于儲存或顯示。

def bin_arr2Img(matrix, bg_color, fg_color):

    '''

    @将二維二值數組轉化為位圖流

    @param img: instance of Image

    @param bg_color: 背景色,元組類型,格式:(L)(灰階),(R, G, B),或者(R, G, B, A)

    @param fg_color: 前景色

    '''

    def ensure_color(color):

        if len(color) == 1:

            return (color, color, color, 255)

        elif len(color) == 3:

            color = list(color)

            color.append(255)

            return tuple(color)

        elif len(color) == 4:

            return color

        else:

            raise ValueError, 'len(color) cannot be %d' % len(color)

    bg_color = ensure_color(bg_color)

    fg_color = ensure_color(fg_color)

    height, width = len(matrix), len(matrix[0])

    dst_img = Image.new("RGBA", (width, height))

    dst_pix = dst_img.load()

    for w in xrange(width):

        for h in xrange(height):

            if matrix[h][w] < 128:

                dst_pix[w, h] = fg_color

            else:

                dst_pix[w, h] = bg_color

    return dst_img

總結:使用python進行像素級圖像處理,同其他平台的像素處理類似,整個過程非常清晰,大體上都是三步,拆包-處理-封包。鑒于python的處理速度實在是不敢恭維,是以它是一個很好的算法效果驗證平台。 

<源碼分享> 

完整代碼分享如下:

#start

# -*- coding: cp936 -*-

import Image

img = Image.open("1.jpg")

def Img2bin_arr(img, threshold):

    '''

    @将位圖流轉化為二維二值數組

    @param img: instance of Image

    @param threshold: 大小範圍[0, 255]

    '''

    threshold = max(0, threshold)

    threshold = min(255, threshold)

    if img.mode != 'L':

        img = img.convert('L')

    width, height = img.size

    pix = img.load()

    get_val = lambda p: 255 if p >= threshold else 0

    return [[get_val(pix[w, h]) for w in xrange(width)] for h in xrange(height)]

def bin_arr2Img(matrix, bg_color, fg_color):

    '''

    @将二維二值數組轉化為位圖流

    @param img: instance of Image

    @param bg_color: 背景色,元組類型,格式:(L)(灰階),(R, G, B),或者(R, G, B, A)

    @param fg_color: 前景色

    '''

    def ensure_color(color):

        if len(color) == 1:

            return (color, color, color, 255)

        elif len(color) == 3:

            color = list(color)

            color.append(255)

            return tuple(color)

        elif len(color) == 4:

            return color

        else:

            raise ValueError, 'len(color) cannot be %d' % len(color)

    bg_color = ensure_color(bg_color)

    fg_color = ensure_color(fg_color)

    height, width = len(matrix), len(matrix[0])

    dst_img = Image.new("RGBA", (width, height))

    dst_pix = dst_img.load()

    for w in xrange(width):

        for h in xrange(height):

            if matrix[h][w] < 128:

                dst_pix[w, h] = fg_color

            else:

                dst_pix[w, h] = bg_color

    return dst_img

def paper_cut(img, threshold, bg_color, fg_color):

    '''

    @效果:剪紙

    @param img: instance of Image

    @param threshold: 大小範圍[0, 255]

    @param bg_color: 背景色,元組類型,格式:(L)(灰階),(R, G, B),或者(R, G, B, A)

    @param fg_color: 前景色

    @return: instance of Image

    '''

    matrix = Img2bin_arr(img, threshold) # 位圖轉化為二維二值數組

    return bin_arr2Img(matrix, bg_color, fg_color) # 二維二值數組轉化為位圖

if __name__ == "__main__":

    import sys, os, time

    path = os.path.dirname(__file__) + os.sep.join(['', '1.jpg'])

    threshold = 150

    bg_color = (255, 255, 255, 0)

    fg_color = (255, 0, 0, 255)

    if len(sys.argv) >= 2:

        path  = sys.argv[1]

    if len(sys.argv) == 3:

        threshold = int(sys.argv[2])

    if len(sys.argv) == 5:

        bg_color = tuple(sys.argv[3])

        fg_color = tuple(sys.argv[4])

    start = time.time()

    img = Image.open(path)

    img = paper_cut(img, threshold, bg_color, fg_color)

    img.save(os.path.splitext(path)[0]+'.papercut_'+str(threshold)+'.png', 'PNG')

    end = time.time()

    print 'It all spends %f seconds time' % (end-start) 

#end