数字图像学笔记——5. 直方图变换、局部变换、灰度分级、比特分层

深碍√TFBOYSˉ_ 2022-12-24 15:50 343阅读 0赞

文章目录

  • 分段函数(Piecewise Functions)
  • 分段线性变化函数(Piecewise Transformation Function)
    • 局部对比度拉伸(Contrast Stretching)
    • 灰度级分层(Intensity-level slicing)
    • 比特面分层(Bit-plane slicing)
  • 采样函数(Sampling Functions)

分段函数(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​)​0≤x0​<I0​I0​≤x1​<I1​⋯In​≤xn​≤255​

分段线性变化函数(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.25∗x1.25∗x0.25∗x​0≤x<9090≤x<160160≤xn​≤255​

  1. def piecewise_transformation(image):
  2. row, col, shape = image.shape
  3. out_img = np.zeros((row, col))
  4. # image conversion
  5. for r in range(row):
  6. for l in range(col):
  7. val = image[r, l, 0]
  8. if val < 90:
  9. out_img[r, l] = val * 0.25
  10. elif 90 <= val < 160:
  11. out_img[r, l] = val * 1.25
  12. else:
  13. out_img[r, l] = val * 0.25
  14. 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​)​0≤x0​<I0​I0​≤x1​<I1​⋯In​≤xn​≤255​

假设,比如对于分段函数来说,我们令范围 [ 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] [Im−1​,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​)=0⋯Fm−1​(xm​)=fm​⋯Fn​(xn​)=0​0≤x0​<I0​Im−1​≤xm−1​<Im​In​≤xn​≤255​

当然也可以只增强兴趣区域的值,所以你能看到两个不同的分段增强函数的映射曲线,这里我就不写代码示例了,其实实现方法和上面的那个拉伸方法是相似的。

比特面分层(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=⎣⎢⎢⎢⎢⎡​n3​0000​n2​0110​n1​1011​n0​1111​⎦⎥⎥⎥⎥⎤​

例如以上的矩阵,一共有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=⎣⎢⎢⎢⎢⎡​n1​1011​n0​1111​⎦⎥⎥⎥⎥⎤​

对于高级语言来说,要把某个数转化位比特,通常需要与运算(and operation),具体这里就不实现了。

采样函数(Sampling Functions)

从上面的这些例子中,我们发现,能够对于原图像的灰度图的修改,很大程度上用到了某种函数,无论是拉伸,还是其他,我们都是通过 I o = I i ⨀ f s I_o = I_i \bigodot f_s Io​=Ii​⨀fs​ 这样一种形式进行的扩展。这个式子中的 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​1​e(−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​′=I1​⋅fs​(P1​)I2​′=I2​⋅fs​(P2​)⋯In​′=In​⋅fs​(Pn​)

采样函数通常可以用于图像去噪,或者增强某一些兴趣区间(ROI:region of interest),有时候我们会在一个应用中使用多种采样函数,而不仅仅是其中一种,以获得我们想要的数据。当然这部分属于比较高级的内容了,我会在后面进行介绍。

人比较懒,我就不太想实现对图像的直方图用这个正态分布采样后,会得到什么的效果。如果你刚刚接触这一部分内容不妨挑战一下自己,当作作业。上一篇文章加上这篇文章介绍的,包括我所展示的代码,你能花费大概不超过一个小时的时间,体会一下采样函数的神奇之处。

为了验证你的采样函数是否正常,你可以把原图像的直方图,和采样后得到的新的直方图都绘制出来,进行比对。

发表评论

表情:
评论列表 (有 0 条评论,343人围观)

还没有评论,来说两句吧...

相关阅读