<背景>
上一節介紹了python PIL庫自帶的10種濾鏡處理,現成的庫函數雖然用起來方便,但是對於圖像處理的各種實際需求,還需要開發者開發自定義的濾鏡算法。本文將給大家介紹如何使用PIL對圖像進行自定義的像素級操作。
<效果>
本文以剪紙風格圖像處理作為例子:(算法借鑒了殘陽似血的博客http://qinxuye.me/,特此鳴謝。)
原圖:
處理后:
<怎么做>
1.首先將處理參數預先設定好。設定閾值threshold,該閾值會用來區分作為目標顏色的前景色和將要被去除掉的的背景色的分界線。同時設置處理后前景色和后景色的顏色,用以呈現最終的分割效果。
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.將分割好的像素使用預先設定的顏色上色,然后將像素集合重新封裝成圖片格式,然后返回這個圖片,用於保存或顯示。
'''
@將二維二值數組轉化為位圖流
@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的處理速度實在是不敢恭維,所以它是一個很好的算法效果驗證平台。
<源碼分享>
完整代碼分享如下:
# -*- 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