Python影象處理(一)基本操作
(僅個人學習摘抄)
指令碼命名千萬千萬不要用python裡面的庫還有什麼檔名一樣,會出錯!!!
1. PIL
注意:開啟圖片的時候,地址斜槓再Python中有其他含義,所以要避免歧義
更多函式使用:https://effbot.org/imagingbook/image.htm
2. Matplotlib
2.1 繪製圖像、點和線
輸出的圖片,y 軸的原點是從上面往下的增加的,和其他的座標系顯示有點不一樣。因為在 PyLab 庫中,左上角是座標原點。
影象中,點標記和線預設的顏色是藍色。
不顯示座標軸:axis('off')
matplotlib_1.py
2.2 影象輪廓和直方圖
繪製圖像的輪廓(或者其他二位函式的等輪廓線),首先需要將影象灰度化:
from PIL import Image
from pylab import *
# 讀取影象到陣列
im = array(Image.open(r'D:\test\pic\test.jpg').convert('L'))
# 新建一個影象
figure()
# 不使用顏色資訊
gray()
# 在原點的左上角顯示輪廓影象
contour(im, origin='image')
axis('equal')
axis('off')
# 直方圖
figure()
hist(im.flatten(),128)
show()
影象的直方圖用來表徵影象畫素值的分佈情況。用一定數目的小區間(bin)來指定表徵畫素值的範圍,每個小區間會得到落入該小區間表示範圍的畫素數目。影象的直方圖可以使用 hist() 函式繪製:
figure()
hist(im.flatten(),128)
show()
hist() 函式的第二個引數指定小區間的數目。因為 hist() 只接受一維陣列作為輸入,所以在繪製直方圖之前,必須先對影象進行壓平處理。flatten() 將任意陣列按照行優先準則轉換成一維陣列。
2.3 互動式標註
通過 ginput() 函式實現互動式標註
# 互動式標註
from PIL import Image
from pylab import *
im = array(Image.open(r'D:\test\pic\test.jpg'))
imshow(im)
print ('Please click 3 plots')
x = ginput(3)
print('you click:', x)
show()
在繪圖區域單擊三次,將會顯示點選的座標。
3. NumPy
3.1 影象陣列表示
呼叫 array() 方法將影象轉換成 NumPy 的陣列物件,NumPy 中的陣列十多維的,可以用來表示向量、矩陣和影象。
每行的第一個元組表示影象陣列的大小(行、列、顏色通道),緊接著的字串表示陣列元素的資料型別。因為影象通常被編碼成無符號八位整數(uint8),所以在第一種情況下,對影象進行灰度化處理,並且在建立陣列時使用額外的引數 “f”;該引數將資料型別轉換為浮點型。注意:由於灰度影象沒有顏色資訊,所以在形狀元組中,它只有兩個數值。
陣列中的元素可以使用下標訪問。位於座標 i、j,以及顏色通道 k 的畫素值可以如下訪問:
value = im[i, j, k]
多個數組元素可以使用陣列切片方式訪問。切片方式返回的是以指定間隔下標訪問該陣列的元素值。
如果僅使用一個下標,則該下標為行下標。
3.2 灰度變換
array() 變換的相反操作用 PIL 的 fromarray() 函式完成:
pil_im = Image.fromarray(im)
如果之前一些操作將“uint8”資料型別轉換為其他資料型別,那麼在建立 PIL 影象之前,需要將資料型別轉換回來:
pil_im = Image.fromarray(uint8(im))
如果不確定輸入的資料型別,安全起見,先轉換回來。注意,NumPy 總是將陣列資料型別轉換成能夠表示資料的“最低”資料型別。對浮點數做乘積或除法操作會使整數型別的陣列變成浮點型。
3.3 影象縮放
3.4 直方圖均衡化
直方圖均衡化是指將一幅影象的灰度直方圖變平,使變換後的影象中每個灰度值的分佈概率都相同。直方圖均衡化是對影象灰度值進行歸一化的非常好的方法,並且可以增強影象的對比度。
直方圖均衡化的變換函式是影象中畫素值的累積分佈函式(cumulative distribution function,簡稱為 cdf,將畫素值的範圍對映到目標範圍的歸一化操作)。
interp() 函式有兩個輸入引數,一個是灰度影象,一個是直方圖中使用小區間的數目。函式返回直方圖均衡化後的影象,和用來做畫素值對映的累積分佈函式。cdf[-1] 是為了歸一化到 0~1 範圍。
直方圖均衡化後圖像的對比度增強了,原先影象灰色區域的細節變得清晰。
呼叫其他 .py 檔案中的函式,如上例:
① import imtools
imtools.histeq(im)
② from imtools import histeq
histeq(im)
3.5 影象平均
影象平均是減少噪聲的一種簡單的方式。
該函式可以直接跳過不能開啟的影象。還可以使用 mean() 函式計算平均影象。mean() 函式需要將所有的影象堆積到一個數組中,比較佔記憶體。
3.6 影象的主成分分析(PCA)
PCA(Principal Component Analysis,主成分分析)是一個非常有用的降維技巧。它可以在使用盡可能少維數的前提下,儘量多的保持訓練資料的資訊。一幅 100 X 100 畫素的小灰度影象,也有 10000維,可以看成 10000 維空間中的一個點。PCA 產生的投影矩陣可以被視為將原始座標變換到現有的座標系,座標系中的各個座標按照重要性遞減排列。
為了對影象資料進行 PCA 變換,影象需要轉換成一維向量表示,可以用 NumPy 類庫中的 flatten() 進行變換。
將變平的影象堆積起來,可以得到一個矩陣,矩陣的一行表示一幅影象。在計算主方向之前,所有的行影象按照平均影象進行了中心化。我們通常使用 SVD(Singular Value Decomposition,奇異值分解)方法來計算主成分;但當矩陣維數很大時,SVD 的計算非常慢,此時通常不用 SVD 分解。
PCA 操作程式碼:
from PIL import Image
from numpy import *
def pca(x):
''' 主成分分析:
輸入:矩陣 X,其中該矩陣中儲存訓練資料,每一行為一條訓練資料
返回:投影矩陣(按照維度的重要性排序)、方差和均值'''
# 獲取維數
num_data,dim = X.shape
# 資料中心化
mean_X = X.mean(axis=0)
X = X - mean_X
if dim>num_data:
# PCA-使用緊緻技巧
M = dot(X,X.T) # 協方差矩陣
e,EV = linalg.eigh(M) # 特徵值和特徵向量
tmp = dot(X.T,EV).T # 這就是緊緻技巧
V = tmp[::-1] # 由於最後的特徵向量是我們所需要的,所以需要將其逆轉
S = aqrt(e)[::-1] # 由於特徵值是按照遞增順序排列的,所以需要將其逆轉
for i in range(V.shape[1]):
V[:i] /= S
else:
# PCA- 使用 SVD 方法
U,S,V = linalg.svd(X)
V = V[:num_data] # 僅僅返回前 num_data 維資料才合理
# 返回投影矩陣、方差和均值
return V,S,mean_X
該函式首先通過減去每一維的均值將資料中心化,然後計算協方差矩陣對應最大特徵值的特徵向量,此時可以使用簡明的技巧或者 SVD 分解。這裡使用 range() 函式,該函式的輸入引數為一個整數 n,函式返回整數 0~(n-1) 的一個列表。也可以使用 arange() 函式返回一個數組,xrange() 函式返回一個產生器(可能會提升速度)。
如果資料個數小於向量的維數,不用 SVD 分解,而是計算維數更小的協方差矩陣 XXT 的特徵向量。通過計算對應前 k(k 是降維後的維數)最大特徵值的特徵向量,可以使上面的 PCA 操作更快。
對影象進行 PCA 變換,影象的名稱儲存在列表 imlist 中,呼叫 pca.py 檔案。
影象從一維表示轉換成二維影象,使用 reshape() 函式。
3.7 pickle 模組
pickle 模組可以接受幾乎所有的 Python 物件,並將其轉換成字串表示,該過程叫做封裝(pickling)。從字串表示中重構該物件,稱為拆封(unpickling)。這些字串表示可以方便的儲存和傳輸。
例如儲存上面的影象的平均影象和主成分:
在上例中,許多物件可以儲存在同一個檔案中。pickle 模組中有很多不同的協議可以生成 ,pkl 檔案;如果不確定的話,最好以二進位制檔案的形式讀取和寫入。在其他 Python 會話中載入資料,只需要使用 load() 方法:
注意,載入物件的順序必須和先前儲存的一樣。Python 中有一個用 C 語言寫的優化版本,叫做 cpickle 模組,該模組和標準 pickle 模組完全相容。
使用 with 語句處理檔案的讀寫操作,可以自動開啟和關閉檔案(即使在檔案開啟時發生錯誤)。
4. SciPy
4.1 影象模糊
影象的高斯模糊是非常經典的影象卷積例子。本質上,圖形模糊就是將(灰度)影象 I 和一個高斯核進行卷積操作:
Iσ = I*Gσ
其中 * 表示卷積操作;Gσ 是標準差為σ 的二維高斯核,定義為:
高斯模糊通常是影象處理操作的一部分,比如影象插值操作,興趣點計算等。
SciPy 有用來做濾波操作的 scipy.ndimage.filters 模組。該模組使用快速一維分離的方式來計算卷積。
程式碼:scipy_1.py
4.2 影象導數
影象強度變化情況可以用灰度影象 I 的 x 和 y 方向導數 Ix 和 Iy 描述。
影象導數大多數可以通過卷積簡單的實現:
Ix=I*Dx 和 Iy = I*Dy
對於 Dx 和 Dy,通常選擇 Prewitt 濾波器:
Sobel 濾波器:
from PIL import Image
from numpy import *
from pylab import *
from scipy.ndimage import filters
im = array(Image.open(r'D:\test\pic\test.jpg').convert('L'))
# Sobel 導數濾波器
imx = zeros(im.shape)
filters.sobel(im,1,imx)
imy = zeros(im.shape)
filters.sobel(im,0,imy)
magnitude = sqrt(imx**2+imy**2)
Sobel() 函式第二個引數用來表示選擇 x 或者 y 方向導數(1:x 方向;0:y 方向),第三個引數儲存輸出的變數。正導數顯示為亮的畫素,負導數顯示為暗的畫素,灰色區域表示導數的值接近於零。
缺點:濾波器的尺寸需要隨著影象解析度的變化而變化。為了在噪聲方面更加穩健,以及在任意尺度上計算導數,使用高斯導數濾波器。
Ix=I*Gσx 和 Iy = I*Gσy
其中,Gσx 和 Gσy 表示Gσ 在 x 和 y 方向上的導數,Gσ 為標準差為σ的高斯函式。
該函式的第三個引數指定對每個方向計算哪種型別的導數,第二個引數為使用的標準差。
4.3 形態學:物件計數
形態學(或數學形態學)是度量和分析基本形狀的影象處理方法的基本框架與集合。形態學通常用於處理二值影象,但是也能夠用於灰度影象。二值影象是指影象的每個畫素只能取兩個值,通常是 0 和 1。二值影象通常是,在計算物體的數目,或者度量其大小時,對一幅影象進行閾值化後的結果。
4.4 一些有用的 SciPy 模組
1、讀寫 .mat 檔案
讀取 Matlab 的 .mat 檔案,使用 scipy.io 模組:
data = scipy.io.loadmat('test.mat')
data 物件包含一個字典,字典中的鍵對應於儲存在原始 ,mat 檔案中的變數名。由於這些變數是陣列格式的,因此可以很方便的儲存到 ,mat 檔案中。只需建立一個字典(其中包含你想要儲存的所有變數),然後使用 savemat() 函式:
data = {}
data['X'] = x
scipy.io.savemat('test.mat',data)
上面儲存的是陣列 x,所以當讀到 Matlab 中時,變數的名字仍為 x。
2、以圖象形式儲存陣列
imsave() 函式可以從 scipy.misc 模組中載入。要將陣列 im 儲存到檔案中,可以使用下面的命令:
from scipy.misc import imsave
imsave('test.jpg',im)
scipy.misc 模組同樣包含了著名的 Lena 測試影象:
lena = scipy.misc.lena()
返回一個 512X512 的灰度影象陣列。
5. 影象去噪
ROF(Rudin-Osher-Fatemi)去噪模型。ROF 模型具有很好的性質:處理後的影象更平滑,同時保持影象邊緣和結構資訊。
ROF 模型去噪:
from numpy import *
def denoise(im,U_init,tolerance=0.1,tau=0.125,tv_weight=100):
''' 使用 A.Chanbolle(2005)在公式(11)中的計算步驟實現 Rudin-Osher-Fatemi(ROF)去噪模型
輸入:含有噪聲的輸入影象(灰度影象)、U的初始值、YV正則項權值、步長、停業條件
輸出:去噪和去除紋理後的影象、紋理殘留'''
m,n = im.shape # 噪聲影象的大小
# 初始化
U = U_init
Px = im # 對偶域的 x 分量
Py = im # 對偶域的 y 分量
error = 1
while(error > tolerance):
Uold = U
# 原始變數的梯度
GradUx = roll(U,-1,axis=1)-U # 變數 U 梯度的 x 分量
GradUy = roll(U,-1,axis=0)-U # 變數 U 梯度的 y 分量
# 更新對偶變數
PxNew = Px + (tau/tv_weight)*GradUx
PyNew = Py + (tau/tv_weight)*GradUy
NormNew = maximum(1,sqrt(PxNew**2+PyNew**2))
Px = PxNew/NormNew # 更新 x 分量(對偶)
Py = PyNew/NormNew # 更新 y 分量(對偶)
# 更新原始變數
RxPx = roll(Px,1,axis=1) # 對 x 分量進行向右 x 軸平移
RyPy = roll(Py,1,axis=0) # 對 y 分量進行向右 y 軸平移
DivP = (Px-RxPx)+(Py-RyPy) # 對偶域的散度
U = im + tv_weight*DivP # 更新原始變數
# 更新誤差
error = linalg.norm(U-Uold)/sqrt(n*m)
return U,im-U # 去噪後的影象和紋理殘留
利用一個合成影象呼叫該函式:
from PIL import Image
from numpy import *
from numpy import random
from scipy.ndimage import filters
import rof
# 使用噪聲建立合成影象
im = zeros((500,500))
im[100:400,100:400] = 128
im[200:300,200:300] = 255
im = im + 30*random.standard_normal((500,500))
G = filters.gaussian_filter(im,10)
U,T = rof.denoise(im,im)
im = Image.fromarray(im)
im.show()
G = Image.fromarray(G)
G.show()
U = Image.fromarray(U)
U.show()
ROF 模型去噪後的影象保留了邊緣和影象的結構資訊,同時模糊了“噪聲”。
學習書目《Python計算機視覺程式設計》