天天看點

Python圖像處理(Pillow/PIL)入門

##Pillow概況

PIL是Python的一種圖像處理工具。

PIL支援大部分的圖像格式,高效并強大。

核心庫設計用來高速通路基于基于像素的資料存儲,給這個通用的圖像處理工具提供了堅實的基礎。

來看下這個庫的一般用途:

###圖像歸檔

PIL是較為理想的圖檔歸檔和批處理應用。你可以使用這個庫去生成縮略圖、轉換圖檔格式、列印圖像等。

目前版本可以識别和讀取大量的格式。寫操作被限制用于大多數通用的轉換處理和顯示格式上。

###圖像展示

目前發行版本包含Tk​​

​PhotoImage​

​​和​

​BitmapImage​

​​接口,這個和​

​Windows DIB interface​

​​一樣,可以用于​

​PythonWin​

​​和其他基于視窗的程式。許多其他的GUI程式都是基于PIL。

為了調試友善,PIL提供了​​

​show()​

​,用來儲存圖檔到硬碟和調用外部圖檔檢視器。

###圖形處理

PIL包含基本的圖像處理功能,包括像素操作、一套内置卷積核的濾鏡、色空間轉換。

PIL支援圖像縮放、旋轉、任意角度的轉換。

PIL支援用于統計圖像的直方圖。這個可以用來自動自動對比增強、全局統計分析。

##安裝

使用PIP安裝

pip install Pillow      

##使用

###使用Image類

Image類是PIL最核心的類,定義在同名的子產品中。你可以建立這個類執行個體,可以通過加載圖檔檔案、處理其他Image執行個體、或建立一個空的Image執行個體。

為了從圖檔檔案加載Image類,需要使用​​

​Image​

​​子產品中的​

​open()​

​方法。

>>> from PIL import Image
>>> im = Image.open("hopper.ppm")      

如果加載成功,這個函數會傳回一個Image對象。你可以使用執行個體屬性去檢視檔案内容。

>>> from __future__ import print_function
>>> print(im.format, im.size, im.mode)
PPM (512, 512) RGB      

​format​

​​屬性定義了圖檔來源格式。如果執行個體沒有讀取檔案,​

​format​

​​就是​

​None​

​。size屬性是一個兩個元素的元組,包含像素計的寬高。mode屬性定義了圖像通道的編号和名字,以及像素類型和深度。常用模式有用于灰階圖的"L"(luminance)、用于真彩圖的"RGB"、用于預印刷圖的"CMYK"模式。

如果你沒有打開檔案,會觸發一個​

​IOError​

​異常。

一旦你有了一個​

​Image​

​執行個體,你就可以使用類裡的方法來處理和維護圖像了。例如,讓我們顯示圖像,我們僅僅加載如下:

>>> im.show()      
這個版本的​

​show()​

​不是非常高效,因為他是儲存圖像到一個臨時檔案,然後調用xv協助去顯示圖像。如果你沒有安裝xv,他将不會工作。盡管他工作了,也會非常難以調試和測試。

下一節介紹PIL提供的幾種不用的方法。

###讀寫圖像

PIL支援廣泛的圖檔格式。為了從硬碟讀取檔案,需要使用​​

​Image​

​​子產品中的​

​open()​

​方法。你不必知道檔案格式就可以打開檔案。PIL會根據檔案内容自動判斷檔案格式。

可以使用​

​Image​

​​子產品裡的​

​save()​

​方法來儲存檔案。儲存檔案的時候,命名會很重要。除非你規定了格式,PIL會用檔案擴充名來存儲。

####轉換圖檔為JPEG格式

from __future__ import print_function
import os, sys
from PIL import Image

for infile in sys.argv[1:]:
    f, e = os.path.splitext(infile)
    outfile = f + ".jpg"
    if infile != outfile:
        try:
            Image.open(infile).save(outfile)
        except IOError:
            print("cannot convert", infile)      

第二個參數被應用到​

​save()​

​​方法裡,準确定義一個檔案格式。如果你使用了一個非标準的擴充,你必須通過這種方式來定義格式:

####建立JPEG縮略圖

from __future__ import print_function
import os, sys
from PIL import Image

size = (128, 128)

for infile in sys.argv[1:]:
    outfile = os.path.splitext(infile)[0] + ".thumbnail"
    if infile != outfile:
        try:
            im = Image.open(infile)
            im.thumbnail(size)
            im.save(outfile, "JPEG")
        except IOError:
            print("cannot create thumbnail for", infile)      

PIL不解碼加載光栅資料,除非必須這樣。當你打開一個檔案,在檔案頭裡就可以判斷出檔案格式,并抽取出像模式、大小和其他需要解碼檔案的屬性,但是檔案的其他部分不會被處理,除非以後需要。

這個意思就是打開圖像檔案是一個很快速的操作,這個跟檔案大小和壓縮方式無關。這裡舉幾個簡單的例子,快速識别一批圖像檔案。

####識别圖像檔案

from __future__ import print_function
import sys
from PIL import Image

for infile in sys.argv[1:]:
    try:
        with Image.open(infile) as im:
            print(infile, im.format, "%dx%d" % im.size, im.mode)
    except IOError:
        pass      

###剪切粘貼和合并圖像

​​

​Image​

​​類包含維護圖像裡區塊的方法。為了從圖像裡抽取一個子矩形,使用​

​crop()​

​​方法。

####複制一個子矩形

box = (100, 100, 400, 400)
region = im.crop(box)      

使用包含4個元素的元組來定義區塊,相比對的坐标是(左上右下)。PIL使用一個坐标系統,原點位于左上角。坐标系統使用像素來引用位置,是以上例中的區塊大小是300x300像素。

現在這個區塊可以以某種方式處理和粘貼了。

####處理子矩形并粘貼

region = region.transpose(Image.ROTATE_180)
im.paste(region, box)      

當粘貼回的時候,區塊大小必須精确比對已給的區塊大小。另外,這個區塊不能擴充到外部圖像。但是,原始圖像的模式必須和區塊的模式比對。如果他們比比對,區塊會提前動轉換為比對的模式。

附件例子:

####旋轉圖像

def roll(image, delta):
    "Roll an image sideways"

    xsize, ysize = image.size

    delta = delta % xsize
    if delta == 0: return image

    part1 = image.crop((0, 0, delta, ysize))
    part2 = image.crop((delta, 0, xsize, ysize))
    part1.load()
    part2.load()
    image.paste(part2, (0, 0, xsize-delta, ysize))
    image.paste(part1, (xsize-delta, 0, xsize, ysize))

    return image      

當從​

​crop()​

​​粘貼回的時候,要先調用​

​load()​

​​。這是因為剪切是一個惰性操作。如果​

​load()​

​​沒有被調用,剪切操作就不會執行,直到有粘貼指令執行。這就意味着​

​part1​

​​會被首先修改的​

​image​

​剪切。

對于更進階的技巧,粘貼指令可以将透明遮罩作為可選參數。在這個遮罩裡面,255表明在這個位置上粘貼的圖像是不透明的。0表明粘貼的圖像是完全透明的。在這兩個值之間的數值表明不同級别的透明度。例如,粘貼一張RGBA圖像,并使用它作為遮罩,将會粘貼圖像不透明的部分,而不是他的透明背景。

PIL也可以讓你處理單個或多個通道圖像,例如RGB圖像。​

​split​

​​方法建立了一套新的圖像,每一個包含一個來源于原始多通道圖像裡的單個通道。​

​merge​

​方法采用一種模式和一組圖像,然後合并他們魏一張新圖像。下面的樣本代碼交換了RGB圖像的三種通道。

####分割合并通道

r, g, b = im.split()
im = Image.merge("RGB", (b, g, r))      

對于單通道圖像 ,​

​split()​

​會傳回圖像本身。為作用于單色通道,你可以先轉換圖像為"RGB"模式。

###幾何變換

​​

​PIL.Image.Image​

​​類包含一些變形和旋轉圖像方法​

​resize()​

​​、​

​rotate()​

​​。前者以一個元組為新尺寸,後者以逆時針的角度為角度。

####簡單的幾何變換

out = im.resize((128, 128))
out = im.rotate(45) # degrees counter-clockwise      

旋轉圖像90度的步驟裡,你可以使用​

​rotate()​

​​方法或​

​transpose()​

​方法。後者還可以用于垂直或水準反轉圖像。

####轉換圖像

out = im.transpose(Image.FLIP_LEFT_RIGHT)
out = im.transpose(Image.FLIP_TOP_BOTTOM)
out = im.transpose(Image.ROTATE_90)
out = im.transpose(Image.ROTATE_180)
out = im.transpose(Image.ROTATE_270)      

​transpose(ROTATE)​

​​也可以執行相同于​

​rotate()​

​​的功能,如果​

​expand​

​标簽為真,則圖像尺寸有相同的更改。

更詳細的圖像轉換,可以查閱​

​transform()​

​方法說明。

顔色轉換

PIL可以使用​

​convert()​

​方法在不同的像素表示之間轉換。

轉換圖像模式

Python圖像處理(Pillow/PIL)入門
im = Image.open("hopper.ppm").convert("L")      

該庫支援每個受支援的模式、“L”和“RGB”模式之間的轉換。

要在其他模式之間轉換,您可能需要使用中間圖像(通常是“RGB”圖像)。

# 将不同模式的圖檔列印出shape 和 [0, 0]像素點的值
from PIL import Image
import matplotlib.pyplot as plt
image = Image.open('images/tower.jpg') # 本地一個檔案
mode_list = ['1', 'L', 'I', 'F', 'P', 'RGB', 'RGBA', 'CMYK', 'YCbCr' ]
for mode in mode_list:
    img = image.convert(mode)
    img_data = np.array(img)
    print('img_{:>1}.shape: {}' .format(mode, img_data.shape))
    print('img_{:>}_data[0, 0]: {}'.format(mode, img_data[0, 0]))
    print('---')      

RGB 為真色彩模式, 可組合為 256 x 256 x256 種, 列印需要更改為 CMYK模式, 需要注意數值溢出的問題。

HSB 模式(本篇沒有涉及),建立基于人類感覺顔色的方式,将顔色分為色相(Hue),飽和度(Saturation),明亮度(Brightness),這裡不詳細展開。

CMYK模式,應用在印刷領域,4個字母意思是青、洋紅、黃、黑,因為不能保證純度,是以需要黑。

位圖模式,見1, 顔色由黑和白表示(True, False)。

灰階模式,隻有灰階, 所有顔色轉化為灰階值,見L,I,F。

雙色調模式(未有涉及),節約成本将可使用雙色調。

Lab模式(未涉及,ps内置),由3通道組成(亮度,a,b)組成,作為RGB到CMYK的過渡。

多通道模式,删除RGB,CMYK,Lab中某一個通道後,會轉變為多通道,多通道用于處理特殊列印,它的每個通道都為256級灰階通道。

索引顔色模式,用在多媒體和網頁,通過顔色表查取,沒有則就近取,僅支援單通道,(8位/像素)。

P模式利用了重映射的思想,事先準備一個調色闆,然後圖檔資料裡儲存的是調色闆的位置,而不是具體的色值,色值都儲存在調色闆裡。

###圖象增強

PIL提供了許多方法和子產品,可用于增強圖像。

####濾鏡

​​

​ImageFilter​

​​子產品包含一些預定義的增強濾鏡,可以與​

​filter()​

​方法一起使用。

####應用濾鏡

from PIL import ImageFilter
out = im.filter(ImageFilter.DETAIL)      

####點運算

​​

​point()​

​方法可以用來轉換圖像的像素值(例如圖像對比操作)。在大多數情況下,期望一個參數的函數對象可以傳遞給這個方法。根據這個函數處理每個像素:

####應用點轉換

# multiply each pixel by 1.2
out = im.point(lambda i: i * 1.2)      

使用上面的技術,您可以快速地将任何簡單的表達式應用于圖像。您還可以組合​

​point()​

​​和​

​paste()​

​方法來選擇性地修改圖像:

####處理單通道

# split the image into individual bands
source = im.split()

R, G, B = 0, 1, 2

# select regions where red is less than 100
mask = source[R].point(lambda i: i < 100 and 255)

# process the green band
out = source[G].point(lambda i: i * 0.7)

# paste the processed band back, but only where red was < 100
source[G].paste(out, None, mask)

# build a new multiband image
im = Image.merge(im.mode, source)      

注意用于建立掩碼的文法:

imout = im.point(lambda i: expression and 255)      

Python隻對邏輯表達式的部分進行評估,以确定結果,并傳回所檢查的最後一個值作為表達式的結果。是以,如果上面的表達式是假(0),那麼Python不會檢視第二個操作數,進而傳回0。否則,它将傳回255。

####增強

對于更進階的圖像增強,您可以使用​​

​ImageEnhance​

​子產品中的類。一旦從圖像中建立了,就可以使用增強對象快速嘗試不同的設定。

你可以通過這種方式調整對比度、亮度、色彩平衡和清晰度。

####增強圖像

from PIL import ImageEnhance

enh = ImageEnhance.Contrast(im)
enh.enhance(1.3).show("30% more contrast")      

###圖像序列

PIL包含一些對圖像序列的基本支援(也可以叫做動畫格式)。支援的序列格式包括FLI/FLC、GIF和幾個實驗格式。TIFF檔案還可以包含多個幀。

當您打開一個序列檔案時,它會自動加載序列中的第一個幀。您可以使用seek和tell方法在不同的幀之間移動:

####讀取學列

from PIL import Image

im = Image.open("animation.gif")
im.seek(1) # skip to the second frame

try:
    while 1:
        im.seek(im.tell()+1)
        # do something to im
except EOFError:
    pass # end of sequence      

如本例中所示,當序列結束時,您将得到一個EOFError異常。

注意,目前版本庫中的大多數驅動程式隻允許您尋找下一幀(如上面示例中所示)。要重新修改檔案,您可能需要重新打開它。

下面的類讓您使用for語句來對序列進行循環:

####使用ImageSequence疊代器類

from PIL import ImageSequence
for frame in ImageSequence.Iterator(im):
    # ...do something to frame...      

###Postscript列印

PIL包括列印圖像、文字和在Postscript列印機上繪圖的功能。這是一個簡單的例子:

####畫Postscript

from PIL import Image
from PIL import PSDraw

im = Image.open("hopper.ppm")
title = "hopper"
box = (1*72, 2*72, 7*72, 10*72) # in points

ps = PSDraw.PSDraw() # default is sys.stdout
ps.begin_document(title)

# draw the image (75 dpi)
ps.image(box, im, 75)
ps.rectangle(box)

# draw title
ps.setfont("HelveticaNarrow-Bold", 36)
ps.text((3*72, 4*72), title)

ps.end_document()      

###更多關于讀取圖像

如前所述,​​

​Image​

​​子產品的​

​open()​

​函數用于打開圖像檔案。在大多數情況下,您隻需将檔案名作為參數傳過去:

im = Image.open("hopper.ppm")      

如果一切順利,結果就是一個​

​PIL.Image.Image​

​對象。否則,将抛出IOError異常。

您可以使用類似檔案的對象而不是檔案名。對象必須實作​

​read()​

​​、​

​seek()​

​​和​

​tell()​

​方法,并以二進制模式打開。

####從一個打開的檔案讀取

fp = open("hopper.ppm", "rb")
im = Image.open(fp)      

要從字元串資料中讀取圖像,使用​

​StringIO​

​類:

####從字元串讀取

import StringIO
im = Image.open(StringIO.StringIO(buffer))      

請注意,在讀取圖像頭之前,庫會重新調整檔案(使用​

​seek(0)​

​​)。此外,還可以在讀取圖像資料時使用seek(通過load方法)。

如果圖像檔案嵌入到較大的檔案中,如tar檔案,則可以使用​​

​ContainerIO​

​​或​

​TarIO​

​子產品來通路它。

####從tar壓縮包裡讀取

from PIL import Image, TarIO

fp = TarIO.TarIO("Tests/images/hopper.tar", "hopper.jpg")
im = Image.open(fp)      

###控制解碼器

一些解碼器允許您在從檔案中讀取圖像時對圖像進行操作。在建立縮略圖(通常速度比品質更重要)和列印到一個單色雷射列印機(當需要隻需要一個灰階版本的圖像時),這通常可以用于加速解碼。

​draft()​

​方法操作一個已打開但尚未加載的圖像,以便盡可能地比對給定的模式和大小。這是通過重新配置圖像解碼器來完成的。

####以草稿模式讀取

這隻适用于JPEG和MPO檔案。

from PIL import Image
from __future__ import print_function
im = Image.open(file)
print("original =", im.mode, im.size)

im.draft("L", (100, 100))
print("draft =", im.mode, im.size)      

這個列印之類的:

original = RGB (512, 512)
draft = L (128, 128)      

注意,生成的圖像可能與請求的模式和大小不完全比對。為了確定圖像不大于給定的大小,可以使用縮略圖方法。

im=Image.open('dog.jpg')  
draw=ImageDraw.Draw(im)  
newfont=ImageFont.truetype('simkai.ttf',40)  #設定字型,simkai為楷體,字型大小40,truetype相關知識可百度  
draw.text((200,100),'you are so good!',(255,255,0),font=newfont) #第一個tuple表示要寫在哪裡,(left,up),之後寫的#文字,顔色為黃色,三通道設定可以百度,第<span style="white-space:pre">                               </span> #一個是紅色,第二個綠色,第三個是藍色,從0到255,最後設定字型  
im.show()
im.save('target.jpg')