1. 程式人生 > >python PIL 影象處理 (二)

python PIL 影象處理 (二)

第 1 章 基本的影象操作和處理

本章講解操作和處理影象的基礎知識,將通過大量示例介紹處理影象所需的 Python 工具包,並介紹用於讀取影象、影象轉換和縮放、計算導數、畫圖和儲存結果等的基本工具。這些工具的使用將貫穿本書的剩餘章節。

1.1 PIL:Python影象處理類庫

PIL(Python Imaging Library Python,影象處理類庫)提供了通用的影象處理功能,以及大量有用的基本影象操作,比如影象縮放、裁剪、旋轉、顏色轉換等。PIL 是免費的,可以從 http://www.pythonware.com/products/pil/ 下載。

利用 PIL 中的函式,我們可以從大多數影象格式的檔案中讀取資料,然後寫入最常見的影象格式檔案中。PIL 中最重要的模組為 Image

 。要讀取一幅影象,可以使用:

from PIL import Image

pil_im = Image.open('empire.jpg')

上述程式碼的返回值 pil_im 是一個 PIL 影象物件。

影象的顏色轉換可以使用 convert() 方法來實現。要讀取一幅影象,並將其轉換成灰度影象,只需要加上 convert('L') ,如下所示:

pil_im = Image.open('empire.jpg').convert('L')

圖 1-1:用 PIL 處理影象的例子

1.1.1 轉換影象格式

通過 save() 方法,PIL 可以將影象儲存成多種格式的檔案。下面的例子從檔名列表( filelist

 )中讀取所有的影象檔案,並轉換成 JPEG 格式:

from PIL import Image
import os

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

PIL 的 open() 函式用於建立 PIL 影象物件, save()

 方法用於儲存影象到具有指定檔名的檔案。除了字尾變為“.jpg”,上述程式碼的新檔名和原檔名相同。PIL 是個足夠智慧的類庫,可以根據副檔名來判定影象的格式。PIL 函式會進行簡單的檢查,如果檔案不是 JPEG 格式,會自動將其轉換成 JPEG 格式;如果轉換失敗,它會在控制檯輸出一條報告失敗的訊息。

本書會處理大量影象列表。下面將建立一個包含資料夾中所有影象檔案的檔名列表。首先新建一個檔案,命名為 imtools.py,來儲存一些經常使用的影象操作,然後將下面的函式新增進去:

import os
def get_imlist(path):

""" 返回目錄中所有JPG 影象的檔名列表"""

return [os.path.join(path,f) for f in os.listdir(path) if f.endswith('.jpg')]

現在,回到 PIL。

1.1.2 建立縮圖

使用 PIL 可以很方便地建立影象的縮圖。 thumbnail() 方法接受一個元組引數(該引數指定生成縮圖的大小),然後將影象轉換成符合元組引數指定大小的縮圖。例如,建立最長邊為 128 畫素的縮圖,可以使用下列命令:

pil_im.thumbnail((128,128))

1.1.3 複製和貼上影象區域

使用 crop() 方法可以從一幅影象中裁剪指定區域:

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

該區域使用四元組來指定。四元組的座標依次是(左,上,右,下)。PIL 中指定座標系的左上角座標為(0,0)。我們可以旋轉上面程式碼中獲取的區域,然後使用 paste() 方法將該區域放回去,具體實現如下:

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

1.1.4 調整尺寸和旋轉

要調整一幅影象的尺寸,我們可以呼叫 resize() 方法。該方法的引數是一個元組,用來指定新影象的大小:

out = pil_im.resize((128,128))

要旋轉一幅影象,可以使用逆時針方式表示旋轉角度,然後呼叫 rotate() 方法:

out = pil_im.rotate(45)

上述例子的輸出結果如圖 1-1 所示。最左端是原始影象,然後是灰度影象、貼上有旋轉後裁剪影象的原始影象,最後是縮圖。

1.2 Matplotlib

我們處理數學運算、繪製圖表,或者在影象上繪製點、直線和曲線時, Matplotlib 是個很好的類庫,具有比 PIL 更強大的繪圖功能。 Matplotlib 可以繪製出高質量的圖表,就像本書中的許多插圖一樣。 Matplotlib 中的 PyLab 介面包含很多方便使用者建立影象的函式。 Matplotlib 是開源工具,可以從 http://matplotlib.sourceforge.net/ 免費下載。該連結中包含非常詳盡的使用說明和教程。下面的例子展示了本書中需要使用的大部分函式。

1.2.1 繪製圖像、點和線

儘管 Matplotlib 可以繪製出較好的條形圖、餅狀圖、散點圖等,但是對於大多數計算機視覺應用來說,僅僅需要用到幾個繪圖命令。最重要的是,我們想用點和線來表示一些事物,比如興趣點、對應點以及檢測出的物體。下面是用幾個點和一條線繪製圖像的例子:

from PIL import Image
from pylab import *

# 讀取影象到陣列中
im = array(Image.open('empire.jpg'))

# 繪製圖像
imshow(im)

# 一些點
x = [100,100,400,400]
y = [200,500,200,500]

# 使用紅色星狀標記繪製點
plot(x,y,'r*')

# 繪製連線前兩個點的線
plot(x[:2],y[:2])

# 新增標題,顯示繪製的影象
title('Plotting: "empire.jpg"')
show()

上面的程式碼首先繪製出原始影象,然後在 x 和 y 列表中給定點的 x 座標和 y 座標上繪製出紅色星狀標記點,最後在兩個列表表示的前兩個點之間繪製一條線段(預設為藍色)。該例子的繪製結果如圖 1-2 所示。 show() 命令首先開啟圖形使用者介面(GUI),然後新建一個影象視窗。該圖形使用者介面會迴圈阻斷指令碼,然後暫停,直到最後一個影象視窗關閉。在每個腳本里,你只能呼叫一次 show() 命令,而且通常是在指令碼的結尾呼叫。注意,在 PyLab 庫中,我們約定影象的左上角為座標原點。

影象的座標軸是一個很有用的除錯工具;但是,如果你想繪製出較美觀的影象,加上下列命令可以使座標軸不顯示:

axis('off')

上面的命令將繪製出如圖 1-2 右邊所示的影象。

圖 1-2: Matplotlib 繪圖示例。帶有座標軸和不帶座標軸的包含點和一條線段的影象

在繪圖時,有很多選項可以控制影象的顏色和樣式。最有用的一些短命令如表 1-1、表 1-2 和表 1-3 所示。使用方法見下面的例子:

plot(x,y)         # 預設為藍色實線
plot(x,y,'r*')    # 紅色星狀標記
plot(x,y,'go-')   # 帶有圓圈標記的綠線
plot(x,y,'ks:')   # 帶有正方形標記的黑色虛線

表1-1:用 PyLab 庫繪圖的基本顏色格式命令

顏色

'b'

藍色

'g'

綠色

'r'

紅色

'c'

青色

'm'

品紅

'y'

黃色

'k'

黑色

'w'

白色

表1-2:用 PyLab 庫繪圖的基本線型格式命令

線型

'-'

實線

'--'

虛線

':'

點線

表1-3:用 PyLab 庫繪圖的基本繪製標記格式命令

標記

'.'

'o'

圓圈

's'

正方形

'*'

星形

'+'

加號

'x'

叉號

1.2.2 影象輪廓和直方圖

下面來看兩個特別的繪圖示例:影象的輪廓和直方圖。繪製圖像的輪廓(或者其他二維函式的等輪廓線)在工作中非常有用。因為繪製輪廓需要對每個座標 [ x, y ] 的畫素值施加同一個閾值,所以首先需要將影象灰度化:

from PIL import Image
from pylab import *

# 讀取影象到陣列中
im = array(Image.open('empire.jpg').convert('L'))

# 新建一個影象
figure()
# 不使用顏色資訊
gray()
# 在原點的左上角顯示輪廓影象
contour(im, origin='image')
axis('equal')
axis('off')

像之前的例子一樣,這裡用 PIL 的 convert() 方法將影象轉換成灰度影象。

影象的直方圖用來表徵該影象畫素值的分佈情況。用一定數目的小區間(bin)來指定表徵畫素值的範圍,每個小區間會得到落入該小區間表示範圍的畫素數目。該(灰度)影象的直方圖可以使用 hist() 函式繪製:

figure()
hist(im.flatten(),128)
show()

hist() 函式的第二個引數指定小區間的數目。需要注意的是,因為 hist() 只接受一維陣列作為輸入,所以我們在繪製圖像直方圖之前,必須先對影象進行壓平處理。 flatten() 方法將任意陣列按照行優先準則轉換成一維陣列。圖 1-3 為等輪廓線和直方圖影象。

圖 1-3:用 Matplotlib 繪製圖像等輪廓線和直方圖

1.2.3 互動式標註

有時使用者需要和某些應用互動,例如在一幅影象中標記一些點,或者標註一些訓練資料。 PyLab 庫中的 ginput() 函式就可以實現互動式標註。下面是一個簡短的例子:

from PIL import Image
from pylab import *

im = array(Image.open('empire.jpg'))
imshow(im)
print 'Please click 3 points'
x = ginput(3)
print 'you clicked:',x
show()

上面的指令碼首先繪製一幅影象,然後等待使用者在繪圖視窗的影象區域點選三次。程式將這些點選的座標 [ x, y ] 自動儲存在 x 列表裡。

1.3 NumPy

NumPy ( http://www.scipy.org/NumPy/ )是非常有名的 Python 科學計算工具包,其中包含了大量有用的思想,比如陣列物件(用來表示向量、矩陣、影象等)以及線性代數函式。 NumPy中的陣列物件幾乎貫穿用於本書的所有例子中 1 陣列物件可以幫助你實現陣列中重要的操作,比如矩陣乘積、轉置、解方程系統、向量乘積和歸一化,這為影象變形、對變化進行建模、影象分類、影象聚類等提供了基礎。

PyLab 實際上包含 NumPy 的一些內容,如陣列型別。這也是我們能夠在 1.2 節使用陣列型別的原因。

1.3.1 影象陣列表示

在先前的例子中,當載入影象時,我們通過呼叫 array() 方法將影象轉換成 NumPy 的陣列物件,但當時並沒有進行詳細介紹。 NumPy 中的陣列物件是多維的,可以用來表示向量、矩陣和影象。一個數組物件很像一個列表(或者是列表的列表),但是陣列中所有的元素必須具有相同的資料型別。除非建立陣列物件時指定資料型別,否則資料型別會按照資料的型別自動確定。

對於影象資料,下面的例子闡述了這一點:

im = array(Image.open('empire.jpg'))
print im.shape, im.dtype

im = array(Image.open('empire.jpg').convert('L'),'f')
print im.shape, im.dtype

控制檯輸出結果如下所示:

(800, 569, 3) uint8
(800, 569) float32

每行的第一個元組表示影象陣列的大小(行、列、顏色通道),緊接著的字串表示陣列元素的資料型別。因為影象通常被編碼成無符號八位整數(uint8),所以在第一種情況下,載入影象並將其轉換到陣列中,陣列的資料型別為“uint8”。在第二種情況下,對影象進行灰度化處理,並且在建立陣列時使用額外的引數“f”;該引數將資料型別轉換為浮點型。關於更多資料型別選項,可以參考圖書 [24]。注意,由於灰度影象沒有顏色資訊,所以在形狀元組中,它只有兩個數值。

陣列中的元素可以使用下標訪問。位於座標 i 、 j ,以及顏色通道 k 的畫素值可以像下面這樣訪問:

value = im[i,j,k]

多個數組元素可以使用陣列切片方式訪問。 切片方式 返回的是以指定間隔下標訪問該陣列的元素值。下面是有關灰度影象的一些例子:

im[i,:] = im[j,:]      # 將第 j 行的數值賦值給第 i 行
im[:,i] = 100          # 將第 i 列的所有數值設為100
im[:100,:50].sum()     # 計算前100 行、前 50 列所有數值的和
im[50:100,50:100]      # 50~100 行,50~100 列(不包括第 100 行和第 100 列)
im[i].mean()           # 第 i 行所有數值的平均值
im[:,-1]               # 最後一列
im[-2,:] (or im[-2])   # 倒數第二行