1. Introduction
#### PIL(Python Image Library)是python的第三方圖像處理庫,但是由於其強大的功能與眾多的使用人數,幾乎已經被認為是python官方圖像處理庫了。其官方主頁為:[PIL](http://pythonware.com/products/pil/)。 PIL歷史悠久,原來是只支持python2.x的版本的,后來出現了移植到python3的庫[pillow](http://python-pillow.org/),pillow號稱是`friendly fork for PIL`,其功能和PIL差不多,但是支持python3。本文主要介紹PIL那些最常用的特性與用法,主要參考自:[http://www.effbot.org/imagingbook](http://www.effbot.org/imagingbook)。
2. What PIL can do?
#### PIL可以做很多和圖像處理相關的事情: - **圖像歸檔(Image Archives)**。PIL非常適合於圖像歸檔以及圖像的批處理任務。你可以使用PIL創建縮略圖,轉換圖像格式,打印圖像等等。 - **圖像展示(Image Display)**。PIL較新的版本支持包括Tk PhotoImage,BitmapImage還有Windows DIB等接口。PIL支持眾多的GUI框架接口,可以用於圖像展示。 - **圖像處理(Image Processing)**。PIL包括了基礎的圖像處理函數,包括對點的處理,使用眾多的卷積核(convolution kernels)做過濾(filter),還有顏色空間的轉換。PIL庫同樣支持圖像的大小轉換,圖像旋轉,以及任意的仿射變換。PIL還有一些直方圖的方法,允許你展示圖像的一些統計特性。這個可以用來實現圖像的自動對比度增強,還有全局的統計分析等。
3. How to use PIL?
3.1 Image class
#### Image類是PIL中的核心類,你有很多種方式來對它進行初始化,比如從文件中加載一張圖像,處理其他形式的圖像,或者是從頭創造一張圖像等。下面是PIL Image類中常用的方法: - **open(filename,mode)**(打開一張圖像)。下面的代碼演示了如何從文件打開一張圖像: ```python >>> from PIL import Image >>> Image.open("dog.jpg","r")
>>> im = Image.open("dog.jpg","r") >>> print(im.size,im.format,im.mode) (296, 299) JPEG RGB ``` `Image.open`返回一個Image對象,該對象有`size,format,mode`等屬性,其中`size`表示圖像的寬度和高度(像素表示);`format`表示圖像的格式,常見的包括JPEG,PNG等格式;`mode`表示圖像的模式,定義了像素類型還有圖像深度等,常見的有RGB,HSV等。一般來說'L'(luminance)表示灰度圖像,'RGB'表示真彩圖像,'CMYK'表示預先壓縮的圖像。一旦你得到了打開的Image對象之后,就可以使用其眾多的方法對圖像進行處理了,比如使用`im.show()`可以展示上面得到的圖像。 - **save(filename,format)**(保存指定格式的圖像) ```python >>> im.save("dog.png",'png') ``` 上面的代碼將圖像重新保存成png格式。 - **thumbnail(size,resample)**(創建縮略圖) ```python >>> im.thumbnail((50,50),resample=Image.BICUBIC) >>> im.show() ``` 上面的代碼可以創建一個指定大小(size)的縮略圖,需要注意的是,thumbnail方法是原地操作,返回值是None。第一個參數是指定的縮略圖的大小,第二個是采樣的,有`Image.BICUBIC`,`PIL.Image.LANCZOS`,`PIL.Image.BILINEAR`,`PIL.Image.NEAREST`這四種采樣方法。默認是`Image.BICUBIC`。 - **crop(box)**(裁剪矩形區域) ```python >>> im = Image.open("dog.jpg","r") >>> box = (100,100,200,200) >>> region = im.crop(box) >>> region.show() im.crop() ``` 上面的代碼在im圖像上裁剪了一個box矩形區域,然后顯示出來。box是一個有四個數字的元組(upper_left_x,upper_left_y,lower_right_x,lower_right_y),分別表示裁剪矩形區域的左上角x,y坐標,右下角的x,y坐標,規定圖像的最左上角的坐標為原點(0,0),寬度的方向為x軸,高度的方向為y軸,每一個像素代表一個坐標單位。crop()返回的仍然是一個Image對象。 - **transpose(method)**(圖像翻轉或者旋轉) ```python >>> im_rotate_180 = im.transpose(Image.ROTATE_180) >>> im_rotate_180.show() ``` 上面的代碼將im逆時針旋轉180°,然后顯示出來,`method`是transpose的參數,表示選擇什么樣的翻轉或者旋轉方式,可以選擇的值有: - Image.FLIP_LEFT_RIGHT,表示將圖像左右翻轉 - Image.FLIP_TOP_BOTTOM,表示將圖像上下翻轉 - Image.ROTATE_90,表示將圖像逆時針旋轉90° - Image.ROTATE_180,表示將圖像逆時針旋轉180° - Image.ROTATE_270,表示將圖像逆時針旋轉270° - Image.TRANSPOSE,表示將圖像進行轉置(相當於順時針旋轉90°) - Image.TRANSVERSE,表示將圖像進行轉置,再水平翻轉 - **paste(region,box,mask)(將一個圖像粘貼到另一個圖像)** ```python >>> im.paste(region,(100,100),None) >>> im.show() ``` 上面的代碼將region圖像粘貼到左上角為(100,100)的位置。region是要粘貼的Image對象,box是要粘貼的位置,可以是一個兩個元素的元組,表示粘貼區域的左上角坐標,也可以是一個四個元素的元組,表示左上角和右下角的坐標。如果是四個元素元組的話,box的size必須要和region的size保持一致,否則將會被convert成和region一樣的size。 - **split()**(顏色通道分離) ```python >>> r,g,b = im.split() >>> r.show() >>> g.show() >>> b.show() ``` split()方法可以原來圖像的各個通道分離,比如對於RGB圖像,可以將其R,G,B三個顏色通道分離。 - **merge(mode,channels)**(顏色通道合並) ```python >>> im_merge = Image.merge("RGB",[b,r,g]) >>> im_merge.show() ``` merge方法和split方法是相對的,其將多個單一通道的序列合並起來,組成一個多通道的圖像,mode是合並之后圖像的模式,比如"RGB",channels是多個單一通道組成的序列。 - **resize(size,resample,box)** ```python >>> im_resize = im.resize((200,200)) >>> im_resize
>>> im_resize.show() >>> im_resize_box = im.resize((100,100),box = (0,0,50,50)) >>> im_resize_box.show() ``` resize方法可以將原始的圖像轉換大小,size是轉換之后的大小,resample是重新采樣使用的方法,仍然有`Image.BICUBIC`,`PIL.Image.LANCZOS`,`PIL.Image.BILINEAR`,`PIL.Image.NEAREST`這四種采樣方法,默認是`PIL.Image.NEAREST`,box是指定的要resize的圖像區域,是一個用四個元組指定的區域(含義和上面所述box一致)。 - **convert(mode,matrix,dither,palette,colors)**(mode轉換) ```python >>> im_L = im.convert("L") >>> im_L.show() >>> im_rgb = im_L.convert("RGB") >>> im_rgb.show() >>> im_L.mode 'L' >>> im_rgb.mode 'RGB' ``` convert方法可以改變圖像的mode,一般是在'RGB'(真彩圖)、'L'(灰度圖)、'CMYK'(壓縮圖)之間轉換。上面的代碼就是首先將圖像轉化為灰度圖,再從灰度圖轉化為真彩圖。值得注意的是,從灰度圖轉換為真彩圖,雖然理論上確實轉換成功了,但是實際上是很難恢復成原來的真彩模式的(不唯一)。 - **filter(filter)**(應用過濾器) ```python >>> im = Image.open("dog.jpg","r") >>> from PIL import ImageFilter >>> im_blur = im.filter(ImageFilter.BLUR) >>> im_blur.show() >>> im_find_edges = im.filter(ImageFilter.FIND_EDGES) >>> im_find_edges.show() >>> im_find_edges.save("find_edges.jpg") >>> im_blur.save("blur.jpg") ``` filter方法可以將一些過濾器操作應用於原始圖像,比如模糊操作,查找邊、角點操作等。filter是過濾器函數,在`PIL.ImageFilter`函數中定義了大量內置的filter函數,比如`BLUR`(模糊操作),`GaussianBlur`(高斯模糊),`MedianFilter`(中值過濾器),`FIND_EDGES`(查找邊)等。上面得到原始圖像dog.jpg,find_edges.jpg以及blur.jpg從左到右如下圖1所示:
圖1 從左到右分別是:dog.jpg,find_edges.jpg以及blur.jpg
>>> im_point = im.point(lambda x:x*1.5)
>>> im_point.show()
>>> im_point.save("im_point.jpg")
point方法可以對圖像進行單個像素的操作,上面的代碼對point方法傳入了一個匿名函數,表示將圖像的每個像素點大小都乘以1.5,mode是返回的圖像的模式,默認是和原來圖像的mode是一樣的。圖2是原來的dog.jpg和point操作之后的im_point.jpg之間的對比。
圖2 dog.jpg和point操作之后的im_point.jpg
下面是一個結合了`point`函數,`split`函數,`paste`函數以及`merge`函數的小例子。 ```python >>> source = im.split() >>> R,G,B = 0,1,2 >>> mask = source[R].point(lambda x: x<100 and 255) >>> # x<100,return 255,otherwise return 0 >>> out_G = source[G].point(lambda x:x*0.7) >>> # 將out_G粘貼回來,但是只保留'R'通道像素值<100的部分 >>> source[G].paste(out_G,None,mask) >>> # 合並成新的圖像 >>> im_new = Image.merge(im.mode,source) >>> im_new.show() >>> im.show() ``` - **ImageEnhance()**(圖像增強) ```python >>> from PIL import ImageEnhance >>> brightness = ImageEnhanBce.Brightness(im) >>> im_brightness = brightness.enhance(1.5) >>> im_brightness.show() >>> im_contrast = ImageEnhance.Contrast(im) >>> im_contrast.enhance(1.5)
>>> im_contrast.enhance(1.5).show() ``` ImageEnhance是PIL下的一個子類,主要用於圖像增強,比如增加亮度(Brightness),增加對比度(Contrast)等。上面的代碼將原來圖像的亮度增加50%,將對比度也增加了50%。 - **ImageSequence()**(處理圖像序列) 下面的代碼可以遍歷gif圖像中的所有幀,並分別保存為圖像 ```python >>> from PIL import ImageSequence >>> from PIL import Image >>> gif = Image.open("pipixia.gif") >>> for i,frame in enumerate(ImageSequence.Iterator(gif),1): ... if frame.mode == 'JPEG': ... frame.save("%d.jpg" %i) ... else: ... frame.save("%d.png" % i) ``` 除了上面使用迭代器的方式以外,還可以一幀一幀讀取gif,比如下面的代碼: ```python >>> index = 0 >>> while 1: ... try: ... gif.seek(index) ... gif.save("%d.%s" %(index,'jpg' if gif.mode == 'JPEG' else 'png')) ... index += 1 ... except EOFError: ... print("Reach the end of gif sequence!") ... break ``` 上面的代碼在讀取到gif的最后一幀之后,會throw 一個 EOFError,所以我們只要捕獲這個異常就可以了。