1. 程式人生 > >Python中的影象處理

Python中的影象處理

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

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

1.1 PIL:Python影象處理類庫

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

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

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

  1. from PIL importImage
  2. pil_im =Image.open(’empire.jpg’)

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

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

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

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

1.1.1 轉換影象格式

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

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

  1. from PIL importImage
  2. import os
  3. for infile in filelist:
  4. outfile = os.path.splitext(infile)[0]+".jpg"
  5. if infile != outfile:
  6. try:
  7. Image.open(infile).save(outfile)
  8. exceptIOError:
  9. print"cannot convert", infile

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

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

  1. import os
  2. def get_imlist(path):
  3. """ 返回目錄中所有JPG 影象的檔名列表"""
  4. return[os.path.join(path,f)for f in os.listdir(path)if f.endswith('.jpg')]

現在,回到 PIL。

1.1.2 建立縮圖

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

  1. pil_im.thumbnail((128,128))

1.1.3 複製和貼上影象區域

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

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

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

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

1.1.4 調整尺寸和旋轉

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

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

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

  1. out= pil_im.rotate(45)

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

1.2 Matplotlib

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

1.2.1 繪製圖像、點和線

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

  1. from PIL importImage
  2. from pylab import*
  3. # 讀取影象到陣列中
  4. im = array(Image.open('empire.jpg'))
  5. # 繪製圖像
  6. imshow(im)
  7. # 一些點
  8. x =[100,100,400,400]
  9. y =[200,500,200,500]
  10. # 使用紅色星狀標記繪製點
  11. plot(x,y,'r*')
  12. # 繪製連線前兩個點的線
  13. plot(x[:2],y[:2])
  14. # 新增標題,顯示繪製的影象
  15. title('Plotting: "empire.jpg"')
  16. show()

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

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

  1. axis('off')

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

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

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

  1. plot(x,y)# 預設為藍色實線
  2. plot(x,y,'r*')# 紅色星狀標記
  3. plot(x,y,'go-')# 帶有圓圈標記的綠線
  4. 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] 的畫素值施加同一個閾值,所以首先需要將影象灰度化:

  1. from PIL importImage
  2. from pylab import*
  3. # 讀取影象到陣列中
  4. im = array(Image.open('empire.jpg').convert('L'))
  5. # 新建一個影象
  6. figure()
  7. # 不使用顏色資訊
  8. gray()
  9. # 在原點的左上角顯示輪廓影象
  10. contour(im, origin='image')
  11. axis('equal')
  12. axis('off')

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

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

  1. figure()
  2. hist(im.flatten(),128)
  3. show()

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

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

1.2.3 互動式標註

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

  1. from PIL importImage
  2. from pylab import*
  3. im = array(Image.open('empire.jpg'))
  4. imshow(im)
  5. print'Please click 3 points'
  6. x = ginput(3)
  7. print'you clicked:',x
  8. show()

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

1.3 NumPy

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

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

1.3.1 影象陣列表示

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

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

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

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

  1. (800,569,3) uint8
  2. (800,569) float32

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

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

  1. value = im[i,j,k]

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

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

注意,示例僅僅使用一個下標訪問陣列。如果僅使用一個下標,則該下標為行下標。注意,在最後幾個例子中,負數切片表示從最後一個元素逆向計數。我們將會頻繁地使用切片技術訪問畫素值,這也是一個很重要的思想。

我們有很多操作和方法來處理陣列物件。本書將在使用到的地方逐一介紹。你可以查閱線上文件或者開源圖書 [24] 獲取更多資訊。

1.3.2 灰度變換

將影象讀入 NumPy 陣列物件後,我們可以對它們執行任意數學操作。一個簡單的例子就是影象的灰度變換。考慮任意函式 f,它將 0…255 區間(或者 0…1 區間)對映到自身(意思是說,輸出區間的範圍和輸入區間的範圍相同)。下面是關於灰度變換的一些例子:

  1. from PIL importImage
  2. from numpy import*
  3. im = array(Image.open('empire.jpg').convert('L'))
  4. im2 =255- im # 對影象進行反相處理
  5. im3 =(100.0/255)* im +100# 將影象畫素值變換到100...200 區間
  6. im4 =255.0*(im/255.0)**2# 對影象畫素值求平方後得到的影象

第一個例子將灰度影象進行反相處理;第二個例子將影象的畫素值變換到 100…200 區間;第三個例子對影象使用二次函式變換,使較暗的畫素值變得更小。圖 1-4 為所使用的變換函式影象。圖 1-5 是輸出的影象結果。你可以使用下面的命令檢視影象中的最小和最大畫素值:

  1. printint(im.min()),int(im.max())

圖 1-4:灰度變換示例。三個例子中所使用函式的影象,其中虛線表示恆等變換

圖 1-5:灰度變換。對影象應用圖 1-4 中的函式:f(x)=255-x 對影象進行反相處理(左);f(x)=(100/255)x+100 對影象進行變換(中);f(x)=255(x/255)2 對影象做二次變換(右)

如果試著對上面例子檢視最小值和最大值,可以得到下面的輸出結果:

  1. 2255
  2. 0253
  3. 100200
  4. 0255

array() 變換的相反操作可以使用 PIL 的 fromarray() 函式完成:

  1. pil_im =Image.fromarray(im)

如果你通過一些操作將“uint8”資料型別轉換為其他資料型別,比如之前例子中的 im3 或者 im4,那麼在建立 PIL 影象之前,需要將資料型別轉換回來:

  1. pil_im =Image.fromarray(uint8(im))

如果你並不十分確定輸入資料的型別,安全起見,應該先轉換回來。注意,NumPy 總是將陣列資料型別轉換成能夠表示資料的“最低”資料型別。對浮點數做乘積或除法操作會使整數型別的陣列變成浮點型別。

1.3.3 影象縮放

NumPy 的陣列物件是我們處理影象和資料的主要工具。想要對影象進行縮放處理沒有現成簡單的方法。我們可以使用之前 PIL 對影象物件轉換的操作,寫一個簡單的用於影象縮放的函式。把下面的函式新增到 imtool.py 檔案裡:

  1. def imresize(im,sz):
  2. """ 使用PIL 物件重新定義影象陣列的大小"""
  3. pil_im =Image.fromarray(uint8(im))
  4. return array(pil_im.resize(sz))

我們將會在接下來的內容中使用這個函式。

1.3.4 直方圖均衡化

影象灰度變換中一個非常有用的例子就是直方圖均衡化。直方圖均衡化是指將一幅影象的灰度直方圖變平,使變換後的影象中每個灰度值的分佈概率都相同。在對影象做進一步處理之前,直方圖均衡化通常是對影象灰度值進行歸一化的一個非常好的方法,並且可以增強影象的對比度。

在這種情況下,直方圖均衡化的變換函式是影象中畫素值的累積分佈函式(cumulative distribution function,簡寫為 cdf,將畫素值的範圍對映到目標範圍的歸一化操作)。

下面的函式是直方圖均衡化的具體實現。將這個函式新增到 imtool.py 裡:

  1. def histeq(im,nbr_bins=256):
  2. """ 對一幅灰度影象進行直方圖均衡化"""
  3. # 計算影象的直方圖
  4. imhist,bins = histogram(im.flatten(),nbr_bins,normed=True)
  5. cdf = imhist.cumsum()# cumulative distribution function
  6. cdf =255* cdf / cdf[-1]# 歸一化
  7. # 使用累積分佈函式的線性插值,計算新的畫素值
  8. im2 = interp(im.flatten(),bins[:-1],cdf)
  9. return im2.reshape(im.shape), cdf

該函式有兩個輸入引數,一個是灰度影象,一個是直方圖中使用小區間的數目。函式返回直方圖均衡化後的影象,以及用來做畫素值對映的累積分佈函式。注意,函式中使用到累積分佈函式的最後一個元素(下標為 -1),目的是將其歸一化到 0…1 範圍。你可以像下面這樣使用該函式:

  1. from PIL importImage
  2. from numpy import*
  3. im = array(Image.open('AquaTermi_lowcontrast.jpg').convert('L'))
  4. im2,cdf = imtools.histeq(im)

圖 1-6 和圖 1-7 為上面直方圖均衡化例子的結果。上面一行顯示的分別是直方圖均衡化之前和之後的灰度直方圖,以及累積概率分佈函式對映影象。可以看到,直方圖均衡化後圖像的對比度增強了,原先影象灰色區域的細節變得清晰。

圖 1-6:直方圖均衡化示例。左側為原始影象和直方圖,中間圖為灰度變換函式,右側為直方圖均衡化後的影象和相應直方圖

圖 1-7:直方圖均衡化示例。左側為原始影象和直方圖,中間圖為灰度變換函式,右側為直方圖均衡化後的影象和相應直方圖

1.3.5 影象平均

影象平均操作是減少影象噪聲的一種簡單方式,通常用於藝術特效。我們可以簡單地從影象列表中計算出一幅平均影象。假設所有的影象具有相同的大小,我們可以將這些影象簡單地相加,然後除以影象的數目,來計算平均影象。下面的函式可以用於計算平均影象,將其新增到 imtool.py 檔案裡:

  1. def compute_average(imlist):
  2. """ 計算影象列表的平均影象"""
  3. # 開啟第一幅影象,將其儲存在浮點型陣列中
  4. averageim = array(Image.open(imlist[0]),'f')
  5. for imname in imlist[1:]:
  6. try:
  7. averageim += array(Image.open(imname))
  8. except:
  9. print imname +'...skipped'
  10. averageim /= len(imlist)
  11. # 返回uint8 型別的平均影象
  12. return array(averageim,'uint8')

該函式包括一些基本的異常處理技巧,可以自動跳過不能開啟的影象。我們還可以使用 mean() 函式計算平均影象。mean() 函式需要將所有的影象堆積到一個數組中;也就是說,如果有很多影象,該處理方式需要佔用很多記憶體。我們將會在下一節中使用該函式。

1.3.6 影象的主成分分析(PCA)

PCA(Principal Component Analysis,主成分分析)是一個非常有用的降維技巧。它可以在使用盡可能少維數的前提下,儘量多地保持訓練資料的資訊,在此意義上是一個最佳技巧。即使是一幅 100×100 畫素的小灰度影象,也有 10 000 維,可以看成 10 000 維空間中的一個點。一兆畫素的影象具有百萬維。由於影象具有很高的維數,在許多計算機視覺應用中,我們經常使用降維操作。PCA 產生的投影矩陣可以被視為將原始座標變換到現有的座標系,座標系中的各個座標按照重要性遞減排列。

為了對影象資料進行 PCA 變換,影象需要轉換成一維向量表示。我們可以使用 NumPy 類庫中的 flatten() 方法進行變換。

將變平的影象堆積起來,我們可以得到一個矩陣,矩陣的一行表示一幅影象。在計算主方向之前,所有的行影象按照平均影象進行了中心化。我們通常使用 SVD(Singular Value Decomposition,奇異值分解)方法來計算主成分;但當矩陣的維數很大時,SVD 的計算非常慢,所以此時通常不使用 SVD 分解。下面就是 PCA 操作的程式碼:

  1. from PIL importImage
  2. from numpy import*
  3. def pca(X):
  4. """ 主成分分析:
  5. 輸入:矩陣X ,其中該矩陣中儲存訓練資料,每一行為一條訓練資料
  6. 返回:投影矩陣(按照維度的重要性排序)、方差和均值"""
  7. # 獲取維數
  8. num_data,dim = X.shape
  9. # 資料中心化
  10. mean_X = X.mean(axis=0)
  11. X = X - mean_X
  12. if dim