[數字影像學筆記] 4.直方圖變換2

Orlando Chen發表於2020-12-07

分段函式(Piecewise Functions)

在前一章《[數字影像學筆記] 4.直方圖變換1》中所提到幾種針對直方圖的變化方法,基本上都是針對 [ 0 , 255 ] [0, 255] [0,255] 全部灰度值的變化,然後我們在實際的使用中也許並不需要對全部的灰度值進行變化,比如我們只想調整其中一部分的強度,或者只顯示其中的一部分。

那麼這種方法就是分段線性變化,有的地方也稱為分段濾波函式,或者直接稱呼它的數學定義分段函式。依據我們小學知道的數學定義,分段函式可以有多個不同的函式組成。

所以寫成公式的話,就表示成如下的樣子:

f ( X ) = { F 1 ( x 0 ) 0 ≤ x 0 < I 0 F 2 ( x 1 ) I 0 ≤ x 1 < I 1 ⋯ ⋯ F n ( x n ) I n ≤ x n ≤ 255 f(X) = \left\{\begin{matrix} F_1 \left ( x_0 \right ) & 0 \leq x_0 < I_0 \\ F_2 \left ( x_1 \right ) & I_0 \leq x_1 < I_1 \\ \cdots & \cdots \\ F_n \left ( x_n \right ) & I_n \leq x_n \leq 255 \end{matrix}\right. f(X)=F1(x0)F2(x1)Fn(xn)0x0<I0I0x1<I1Inxn255

分段線性變化函式(Piecewise Transformation Function)

區域性對比度拉伸(Contrast Stretching)

在岡薩雷斯的教材裡,提到了第一個簡單的Sample,好像是把花粉還是種子的圖片進行對比度拉伸。原圖是一張對比度非常低的圖片,很多圖片中的細節看不清楚,因此我們使用一個簡單的分段函式,對原圖進行對比度拉伸。

f ( X ) = { 0.25 ∗ x 0 ≤ x < 90 1.25 ∗ x 90 ≤ x < 160 0.25 ∗ x 160 ≤ x n ≤ 255 f(X) = \left\{\begin{matrix} 0.25 * x & 0 \leq x < 90 \\ 1.25 * x & 90 \leq x < 160 \\ 0.25 * x & 160 \leq x_n \leq 255 \end{matrix}\right. f(X)=0.25x1.25x0.25x0x<9090x<160160xn255

def piecewise_transformation(image):
    row, col, shape = image.shape
    out_img = np.zeros((row, col))

    # image conversion
    for r in range(row):
        for l in range(col):
            val = image[r, l, 0]
            if val < 90:
                out_img[r, l] = val * 0.25
            elif 90 <= val < 160:
                out_img[r, l] = val * 1.25
            else:
                out_img[r, l] = val * 0.25

    return out_img

在這裡插入圖片描述
通過拉伸以後,我們還可以發現很多細節,但是如果想進一步拉伸對比度,由於原圖中細節已經缺失很多,所以已經不太可能得到更多的細節了。

灰度級分層(Intensity-level slicing)

說到底這個方法也是分段線性函式的一種變形形式,也就是把我們感興趣的灰度範圍高亮、增強,把不喜歡的隱藏掉或者降低它的強度值。

f ( X ) = { F 1 ( x 0 ) 0 ≤ x 0 < I 0 F 2 ( x 1 ) I 0 ≤ x 1 < I 1 ⋯ ⋯ F n ( x n ) I n ≤ x n ≤ 255 f(X) = \left\{\begin{matrix} F_1 \left ( x_0 \right ) & 0 \leq x_0 < I_0 \\ F_2 \left ( x_1 \right ) & I_0 \leq x_1 < I_1 \\ \cdots & \cdots \\ F_n \left ( x_n \right ) & I_n \leq x_n \leq 255 \end{matrix}\right. f(X)=F1(x0)F2(x1)Fn(xn)0x0<I0I0x1<I1Inxn255

假設,比如對於分段函式來說,我們令範圍 [ 0 , I 0 ] [0, I_0] [0,I0] F m ∈ [ I m , I n ] F_m \in [I_m, I_n] Fm[Im,In] [ I n , 255 ] [I_n, 255] [In,255] 為0,也就是強調其中的 [ I m − 1 , I m ] [I_{m-1}, I_m] [Im1,Im]這一段,以上的分段函式就可以寫作:

f ( X ) = { F 1 ( x 0 ) = 0 0 ≤ x 0 < I 0 ⋯ F m − 1 ( x m ) = f m I m − 1 ≤ x m − 1 < I m ⋯ F n ( x n ) = 0 I n ≤ x n ≤ 255 f(X) = \left\{\begin{matrix} F_1 \left ( x_0 \right ) = 0 & 0 \leq x_0 < I_0 \\ \cdots \\ F_{m-1} \left ( x_m \right ) = f_m & I_{m-1} \leq x_{m-1} < I_m \\ \cdots \\ F_n \left ( x_n \right ) = 0 & I_n \leq x_n \leq 255 \end{matrix}\right. f(X)=F1(x0)=0Fm1(xm)=fmFn(xn)=00x0<I0Im1xm1<ImInxn255

當然也可以只增強興趣區域的值,所以你能看到兩個不同的分段增強函式的對映曲線,這裡我就不寫程式碼示例了,其實實現方法和上面的那個拉伸方法是相似的。

位元面分層(Bit-plane slicing)

這個簡單的說一下,主要是用於影像壓縮的一種方法。對於一個灰度畫素,它的數字通常是由8個位元組成的,你可以想象是由包含8個位元的數字組成的:

[ ( 2 7 ) n 7 , ( 2 6 ) n 6 , ( 2 5 ) n 5 , ( 2 4 ) n 4 , ( 2 3 ) n 3 , ( 2 2 ) n 2 , ( 2 1 ) n 1 , ( 2 0 ) n 0 ] [(2^7)n_7, (2^6)n_6, (2^5)n_5, (2^4)n_4, (2^3)n_3, (2^2)n_2, (2^1)n_1, (2^0)n_0 ] [(27)n7,(26)n6,(25)n5,(24)n4,(23)n3,(22)n2,(21)n1,(20)n0]

其中的 n n n 表示的是一個數位開關,它只有開合兩種狀態,因此對於一個在 [ 0 , 255 ] [0, 255] [0,255]範圍的數來說,就有8個位元位,如果我們完整的記錄一個位元數,從儲存空間上來說,就需要記錄8個開關的狀態,比如[1, 0, 0, 1, 1, 0, 0, 1]。

對一個灰度的還原方法因此就成了這樣

P = ( 2 7 ) n 7 + ( 2 6 ) n 6 + ( 2 5 ) n 5 + ( 2 4 ) n 4 + ( 2 3 ) n 3 + ( 2 2 ) n 2 + ( 2 1 ) n 1 + ( 2 0 ) n 0 P = (2^7)n_7 + (2^6)n_6 + (2^5)n_5+ (2^4)n_4 + (2^3)n_3 + (2^2)n_2 + (2^1)n_1 + (2^0)n_0 P=(27)n7+(26)n6+(25)n5+(24)n4+(23)n3+(22)n2+(21)n1+(20)n0

實際上對於一張圖片來說,它有時候保留的有效開關資訊其實很少,比如說一張天空的照片,它的有效畫素資訊可能幾種在 ( n 0 , n 1 , n 4 ) (n_0, n_1, n_4) (n0,n1,n4),而在 n 7 n_7 n7有少量資訊,而其他位上則基本沒有資訊。如果為了獲得最大壓縮率,那麼其實我們就可以只保留 ( n 0 , n 1 , n 4 ) (n_0, n_1, n_4) (n0,n1,n4)這三個開關的資訊,為了更好的說明,我這裡用矩陣表示一下這個過程:

I = [ n 3 n 2 n 1 n 0 0 0 1 1 0 1 0 1 0 1 1 1 0 0 1 1 ] I = \begin{bmatrix} n_3 & n_2 & n_1 & n_0 \\ 0 & 0 & 1 & 1 \\ 0 & 1 & 0 & 1 \\ 0 & 1 & 1 & 1 \\ 0 & 0 & 1 & 1 \end{bmatrix} I=n30000n20110n11011n01111

例如以上的矩陣,一共有4個點, n 3 n_3 n3 由於沒有資料,所以在對資料進行分層的時候,我們就可以把這一層捨棄,而不損失任何精度和細節。如果我們只保留 n 1 n_1 n1 n 0 n_0 n0兩層位元層,如果在保留一定的細節情況下,那麼就可以在理論上把資料壓縮一半:

I = [ n 1 n 0 1 1 0 1 1 1 1 1 ] I = \begin{bmatrix} n_1 & n_0 \\ 1 & 1 \\ 0 & 1 \\ 1 & 1 \\ 1 & 1 \end{bmatrix} I=n11011n01111

對於高階語言來說,要把某個數轉化位位元,通常需要與運算(and operation),具體這裡就不實現了。

取樣函式(Sampling Functions)

從上面的這些例子中,我們發現,能夠對於原影像的灰度圖的修改,很大程度上用到了某種函式,無論是拉伸,還是其他,我們都是通過 I o = I i ⨀ f s I_o = I_i \bigodot f_s Io=Iifs 這樣一種形式進行的擴充套件。這個式子中的 f s f_s fs 就是取樣函式。

⨀ \bigodot 在這裡,我們不明確指明原始資料與取樣函式的計算方式,它可以是點乘,可以是矩陣乘,也可以是卷積,甚至就像上面介紹的這些分段函式一樣,或者單純的加一些常數。

在這裡插入圖片描述

例如對於原始資料,在圖中表示紅色曲線的部分,其取樣函式 f s f_s fs為正態分佈曲線,數學上寫作:

f s = 1 2 π δ 2 e ( − ( x − μ ) 2 2 δ 2 ) f_s = \frac{1}{\sqrt{2 \pi \delta^2}}e(-\frac{(x-\mu)^2}{2\delta^2}) fs=2πδ2 1e(2δ2(xμ)2)

那麼對於這樣一個資料,當 ⨀ \bigodot 表示為元素乘的時候,取樣函式為正態分佈,每一個畫素點的新輸出的計算方式即:
I 1 ′ = I 1 ⋅ f s ( P 1 ) I 2 ′ = I 2 ⋅ f s ( P 2 ) ⋯ I n ′ = I n ⋅ f s ( P n ) {I_1}'= I_1 \cdot f_s(P_1) \\ {I_2}'= I_2 \cdot f_s(P_2) \\ \cdots \\ {I_n}'= I_n \cdot f_s(P_n) \\ I1=I1fs(P1)I2=I2fs(P2)In=Infs(Pn)

取樣函式通常可以用於影像去噪,或者增強某一些興趣區間(ROI:region of interest),有時候我們會在一個應用中使用多種取樣函式,而不僅僅是其中一種,以獲得我們想要的資料。當然這部分屬於比較高階的內容了,我會在後面進行介紹。

人比較懶,我就不太想實現對影像的直方圖用這個正態分佈取樣後,會得到什麼的效果。如果你剛剛接觸這一部分內容不妨挑戰一下自己,當作作業。上一篇文章加上這篇文章介紹的,包括我所展示的程式碼,你能花費大概不超過一個小時的時間,體會一下采樣函式的神奇之處。

為了驗證你的取樣函式是否正常,你可以把原影像的直方圖,和取樣後得到的新的直方圖都繪製出來,進行比對。

相關文章