数字图像学笔记——5. 直方图变换、局部变换、灰度分级、比特分层
文章目录
- 分段函数(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<I0I0≤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∗x0≤x<9090≤x<160160≤xn≤255
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)0≤x0<I0I0≤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)=00≤x0<I0Im−1≤xm−1<ImIn≤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=⎣⎢⎢⎢⎢⎡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=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πδ21e(−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),有时候我们会在一个应用中使用多种采样函数,而不仅仅是其中一种,以获得我们想要的数据。当然这部分属于比较高级的内容了,我会在后面进行介绍。
人比较懒,我就不太想实现对图像的直方图用这个正态分布采样后,会得到什么的效果。如果你刚刚接触这一部分内容不妨挑战一下自己,当作作业。上一篇文章加上这篇文章介绍的,包括我所展示的代码,你能花费大概不超过一个小时的时间,体会一下采样函数的神奇之处。
为了验证你的采样函数是否正常,你可以把原图像的直方图,和采样后得到的新的直方图都绘制出来,进行比对。
还没有评论,来说两句吧...