1. 程式人生 > 實用技巧 >Python影象處理(一)基本操作

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 方向導數 IxIy 描述。


  影象導數大多數可以通過卷積簡單的實現:

      Ix=I*DxIy = 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σxIy = 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計算機視覺程式設計》