1. 程式人生 > >基本影象操作和處理(python)

基本影象操作和處理(python)

PIL提供了通用的影象處理功能,以及大量的基本影象操作,如影象縮放、裁剪、旋轉、顏色轉換等。

Matplotlib提供了強大的繪圖功能,其下的pylab/pyplot介面包含很多方便使用者建立影象的函式。

為了觀察和進一步處理影象資料,首先需要載入影象檔案,並且為了檢視影象資料,我們需要將其繪製出來。

from PIL import Image
import matplotlib.pyplot as plt
import numpy as np

# 載入影象
img = Image.open("tmp.jpg")
# 轉為陣列
img_data = np.array(img)
# 視覺化
plt.imshow(img_data)
plt.show()

對於影象,我們常見的操作有調整影象尺寸,旋轉影象以及灰度變換

from PIL import Image
import matplotlib.pyplot as plt

img = Image.open("girl.jpg")

plt.figure()
# 子圖
plt.subplot(221)
# 原圖
plt.imshow(img)
plt.subplot(222)
# 將影象縮放至 256 * 256
plt.imshow(img.resize((256, 256)))
plt.subplot(223)
# 將影象轉為灰度圖
plt.imshow(img.convert('L'))
plt.subplot(224)
# 旋轉影象
plt.imshow(img.rotate(45))
# 儲存影象
plt.savefig("tmp.jpg")
plt.show()

效果演示 :

在平常的使用中,繪製圖像的輪廓也經常被使用,因為繪製輪廓需要對每個座標(x, y)的像數值施加同一個闕值,所以需要將影象灰度化

from PIL import Image
import matplotlib.pyplot as plt
import numpy as np

img = Image.open("girl.jpg")

gray_img = np.array(img.convert('L'))
plt.figure()
# 繪製圖像灰度化
plt.gray()
# 關閉座標軸
plt.axis('off')
# 繪製灰度影象
plt.contour(gray_img, origin='image')
plt.figure()
# 繪製直方圖,flatten()表示將陣列展平
plt.hist(gray_img.flatten(), 128)
plt.show()

輪廓圖及直方圖:

影象的直方圖用來表徵該影象的畫素值的分佈情況。用一定數目的小區間來指定表徵畫素值的範圍,每個小區間會得到落入該小區間表示範圍的畫素數目。hist()函式用於繪製圖像的直方圖,其只接受一維陣列作為第一個引數輸入,其第二個引數用於指定小區間的數目。

有時使用者需要和應用進行互動,如在一幅影象中標記一些點。Pylab/pyplot庫中的ginput()函式就可以實現互動式標註

from PIL import Image
import matplotlib.pyplot as plt


img = Image.open(r"girl.jpg")
plt.imshow(img)
x = plt.ginput(3)
print("clicked point: ", x)

注:該互動在整合編譯環境(pyCharm)中如果不能調出互動視窗則無法進行點選,可以在命令視窗下成功執行。

以上我們通過numpy的array()函式將Image物件轉換成了陣列,以下將展示如何從陣列轉換成Image物件

from PIL import Image
import numpy as np


img = Image.open(r"girl.jpg")
img_array = np.array(img)
img = Image.fromarray(img_array)

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

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

from PIL import Image
import matplotlib.pyplot as plt
import numpy as np


def histogram_equalization(img: np, nbr_bins=256):
    imhist, bins = np.histogram(img.flatten())
    cdf = imhist.cumsum() # 累計分佈函式
    # 歸一化
    cdf = 255 * cdf / cdf[-1]
    # 使用累積分佈函式進行線性插值,計算新的畫素值
    img2 = np.interp(img.flatten(), bins[:-1], cdf)
    return img2.reshape(img.shape), cdf


img = Image.open(r"girl.jpg").convert('L')
img2, cdf = histogram_equalization(np.array(img))
plt.figure()
plt.gray()
# 繪製子圖
plt.subplot(232)
# 變換函式
plt.plot(cdf)
plt.subplot(231)
plt.hist(np.array(img).flatten(), 256)
# 關閉座標軸,對上一個子圖有效
plt.axis('off')
plt.subplot(233)
plt.hist(np.array(img2).flatten(), 256)
plt.axis('off')
plt.subplot(234)
plt.imshow(img)
plt.axis('off')
plt.subplot(236)
plt.imshow(img2)
plt.axis('off')
# 儲存繪製圖像
plt.savefig("tmp.jpg")
plt.show()

處理結果

可見,直方圖均衡化的影象的對比度增強了,原先影象灰色區域的斜街變得清晰。
PCA(Principal Component Analysis, 主成分分析)是一個非常有用的降維技巧,它可以在使用盡可能少的維數的前提下,儘可能多地保持訓練資料的資訊。詳細介紹及使用見我的另一篇文章:PCA降維

SciPy是建立在Numpy基礎上,用於數值運算的開源工具包。Scipy提供很多高效的操作,可以實現數值積分、優化、統計、訊號處理,以及對我們來說最為重要的影象處理功能。

影象的高斯模糊是非常經典的影象卷積例子。本質上,影象模糊就是將(灰度)影象 \(I\) 和一個高斯核進行卷積操作:
\[ I_\sigma = I * G_\sigma \]
其中, \(*\) 表示卷積操作;\(G\) 表示標準差為 \(\sigma\) 的二維高斯核,定義為:
\[ G_\sigma = \frac{1}{2\pi \sigma^2} e^{-(x^2+y^2) / 2 \sigma^2} \]
高斯模糊通常是其他影象處理操作的一部分,比如影象插值操作、興趣點計算以及其他應用。

Scipy有用來做濾波操作的scipy.ndimage.filters模組。該模組使用快速一維分離的方式來計算卷積。使用方式:

from PIL import Image
import numpy as np
from scipy.ndimage import filters


img = Image.open(r"girl.jpg").convert('L')
img = np.array(img)
img2 = filters.gaussian_filter(img, 2)
img3 = filters.gaussian_filter(img, 5)
img4 = filters.gaussian_filter(img, 10)

繪製結果

上面使用的gaussian_filter()函式中的後一個引數表示標準差 \(\sigma\) ,可見隨著 \(\sigma\) 的增加,影象變得越來越模糊。 \(\sigma\) 越大,處理後圖像細節丟失越多。如果是打算模糊一幅彩色影象,只需要簡單地對每一個顏色通道進行高斯模糊:

from PIL import Image
import numpy as np
from scipy.ndimage import filters


img = Image.open(r"girl.jpg")
img = np.array(img)
img2 = np.zeros(img.shape)
for i in range(img2.shape[2]):
    img2[:, :, i] = filters.gaussian_filter(img[:, :, i], 5)
# 將畫素值用八位表示
img2 = np.array(img2, 'uint8')

模糊結果:

在很多應用中,影象強度的變化情況是非常重要的,強度的變化可以使用灰度影象的 \(x\) 和 \(y\) 方向導數 \(I_x\) 和 \(I_y\)進行描述

影象的梯度向量為 \(\bigtriangledown I = [I_x, I_y]^T\)。梯度有兩個重要屬性,一是梯度的大小:
\[ | \bigtriangledown I | = \sqrt{I_x^2 + I_y^2} \]
它描述了影象強度變化的強弱,另一個是影象的角度:
\[ \alpha = arctan2(I_x, I_y) \]
它描述了影象在每個點上強度變化最大的方向。Numpy中的arctan2()函式返回弧度表示的有符號角度,角度的變化區間為 \((-\pi, \pi)\)
可以使用離散近似的方式來計算影象的導數。影象倒數大多數可以通過卷積簡單地實現:
\[ I_x = I*D_x 和 I_y = I*D_y \]
對於 \(D_x\) 和 \(D_y\),通常選擇Prewitt濾波器:
\[ D_x = \left[ \begin{matrix} -1 & 0 & 1 \\ -1 & 0 & 1 \\ -1 & 0 & 1 \end{matrix} \right] \]

\[ D_y = \left[ \begin{matrix} -1 & -1 & -1 \\ 0 & 0 & 0 \\ 1 & 1 & 1 \end{matrix} \right] \]
或者Sobel濾波器
\[ D_x = \left[ \begin{matrix} -1 & 0 & 1 \\ -2 & 0 & 2 \\ -1 & 0 & 1 \end{matrix} \right] \]

\[ D_y = \left[ \begin{matrix} -1 & -2 & -1 \\ 0 & 0 & 0 \\ 1 & 2 & 1 \end{matrix} \right] \]
這些導數濾波器可以使用scipy.ndimage.filters模組地標準卷積操作來簡單地實現

from PIL import Image
import numpy as np
from scipy.ndimage import filters


img = Image.open(r"girl.jpg").convert('L')
img = np.array(img)
imgx = np.zeros(img.shape)
# Sobel導數濾波器
filters.sobel(img, 1, imgx)

imgy = np.zeros(img.shape)
filters.sobel(img, 0, imgy)

magnitude = np.sqrt(imgx**2+imgy**2)

sobel()函式的第二個引數選擇 \(x\) 或 \(y\) 方向的導數,第三個引數儲存輸出變數。在影象中,正導數顯示為亮的畫素,負導數顯示為暗的畫素,灰色區域表示導數的值接近零。

上面計算影象導數的方法存在缺陷:在該方法中,濾波器的尺度需要隨著影象解析度的變化而變化(?)。為了在影象噪聲方面更穩健,以及在任意尺度上計算導數,我們可以使用高斯導數濾波器:
\[ I_x = I * G_{\sigma x} 和 I_y = I*G_{\sigma y} \]
其中,\(G_{\sigma x}\) 和\(G_{\sigma y}\)表示\(G_\sigma\) 在 \(x\) 和 \(y\) 方向上的導數,\(G_\sigma\) 表示標準差為 \(\sigma\) 的高斯函式。以下給出使用樣例:

from PIL import Image
import matplotlib.pyplot as plt
import numpy as np
from scipy.ndimage import filters

img = Image.open(r"girl.jpg").convert('L')
img = np.array(img)

sigma = 2
imgx = np.zeros(img.shape)
imgy = np.zeros(img.shape)
filters.gaussian_filter(img, (sigma, sigma), (0, 1), imgx)
filters.gaussian_filter(img, (sigma, sigma), (1, 0), imgy)

magnitude = np.sqrt(imgx**2+imgy**2)

結果演示:

在對影象進行處理時,去噪也是很重要的一環。影象去噪是在去除影象噪聲的同時,儘可能地保留影象細節和結構地處理技術,以下給出使用ROF去噪模型地Demo:

from PIL import Image
import matplotlib.pyplot as plt
import numpy as np
from scipy.ndimage import filters


def de_noise(img, U_init, tolerance=0.1, tau=0.125, tv_weight=100):
    U = U_init
    Px = Py = img
    error = 1
    while error > tolerance:
        Uold = U
        # 變數U梯度的x分量
        gradUx = np.roll(U, -1, axis=1)-U
        # 變數U梯度的y分量
        gradUy = np.roll(U, -1, axis=0)-U

        # 更新對偶變數
        PxNew = Px + (tau/tv_weight)*gradUx
        PyNew = Py + (tau/tv_weight)*gradUy
        NormNew = np.maximum(1, np.sqrt(PxNew**2+PyNew**2))

        # 更新x,y分量
        Px = PxNew / NormNew
        Py = PyNew / NormNew

        # 更新原始變數
        RxPx = np.roll(Px, 1, axis=1)  # 將x分量向x軸正方向平移
        RyPy = np.roll(Py, 1, axis=0)  # 將y分量向y軸正方向平移

        DivP = (Px - RxPx) + (Py - RyPy)  # 對偶域散度
        U = img + tv_weight * DivP

        error = np.linalg.norm(U - Uold)/np.sqrt(img.shape[0] * img.shape[1])

        return U, img-U


if __name__ == '__main__':
    im = np.zeros((500, 500))
    im[100:400,100:400] = 128
    im[200:300, 200:300] = 255
    im = im + 30 * np.random.standard_normal((500, 500))

    U, T = de_noise(im, im)
    G = filters.gaussian_filter(im, 10)
    plt.figure()
    plt.gray()
    plt.subplot(221).set_title("Original image")
    plt.axis('off')
    plt.imshow(im)
    plt.subplot(222).set_title("Gauss blurred image")
    plt.axis('off')
    plt.imshow(G)
    plt.subplot(223).set_title("ROF")
    plt.axis('off')
    plt.imshow(U)
    plt.savefig('tmp.jpg')
    plt.show()

結果演示

ROF去噪後的影象保留了邊緣和影象的結構資訊,同時模糊了“噪聲”。

np.roll()函式可以迴圈滾動元素,np.linalg.norm()用於衡量兩個陣列間的差異。

之後有空將補充影象去噪

參考書籍
Python計算機視