从本节开始,开始介绍图像空域增强的内容。本节为上篇,介绍一类基础图像空域增强技术:面向灰度级的点运算。
通过本节的学习,读者将初步了解图像空域增强中常用的灰度级点运算,比如Gamma校正、幂次变换、对比度拉伸、反色变换等等。这一部分主要用到了Skimage中的exposure模块。
目录
5.1 概述
从本节开始,进入到图像处理中的一个重要而基础的领域:图像空域增强(Spatial Enhancement)。该领域是伴随着数字图像处理学科的开始涌现出的一个基本问题。
图像增强的目的是提高图像的视觉质量,也可以是使特定的特征显得更加突出。这类技术更多的是从主观感受出发,通常不会涉及图像质量退化模型,这点是与后面章节讨论的图像复原最明显的区别。从处理结果出发,典型的图像增强技术包括对比度增强、图像平滑和锐化等。
下图所示为labview软件提供的各类经典图像增强技术的分类图,同样适用于本教程。
(经典图像增强技术分类,来自labview介绍文档)
参照上图,简要介绍经典图像增强技术的分类。
- 按照作用域不同,分为空域增强和频域增强两类,两者比较,空域增强占主导地位;
- 频域增强可按照功能进行划分(如低通、高通、带阻等),也可按照频域类型划分(如傅里叶域、小波域等);
- 空域增强技术,按照是否使用邻域信息,可分为点运算和邻域滤波,点运算有可进一步分为灰度变换和直方图变换等。
各种增强技术有自身的特点,有的技术实现简单,但性能单一,有的技术功能强大,但计算复杂。我们将参考上图的分类方式,逐一介绍几种有代表性技术基于Skimage的具体实现。至于各类技术的工作原理,不再详细介绍,请各位参考相关文献。
5.2 基于点运算的图像增强
点运算是对输入图像的各个像素应用变换函数,对应输出图像的像素点为
,上述过程可表示为
,该变换用不到位置信息
,因此该变换可改写为
,其中r和s分别代表输入图像灰度值和对应的输出图像灰度值。 因此,各种点运算的区别在于T的定义方式的不同。
按照灰度映射函数的性质,灰度变换可以分为线性变换、分段线性和非线性变换,非线性变换中对数变换、指数变换和幂律变换(n次幂、n次根)最为常用。
5.2.1 反色变换(图像反转)
图像反色(Invert)变换,又称为图像反转,是将深色像素点变浅色,浅色像素点变深色。广义的反色变换也可以应用于彩色图像,即对所有像素点取补。
图像的反转处理可以增强暗色区域中的白色或灰色细节,对应的变换定义为:
如果待处理图像的灰度级范围是[0,1],则反色变换为。
在Skimage中,使用util.invert直接完成反色处理。
invert函数完成反色处理。
看一下warp函数的声明:
skimage.util.invert(image, signed_float)
部分参数说明:
- image:输入图像。
- signed_float:实型标志,默认为False。如果设为True,则范围为[-1,1],否则,根据图像数据类型来确定范围。
返回值:
- inverted:变换后的图像,与输入图像相同。
对于不同数据类型的图像,反色处理方式略有不同:
- 无符号整型:用灰度值的理论最大值减去当前灰度值,对于[0,255]范围内的的灰度或彩色图像,s=255-r。
- 有符号整型:此时图像灰度值取值范围是[-128,127],对应的反色处理是s=r*(-1)-1,但这类情况很少用到。
- 实型:如果signed_float标志位是True,则s=1-r,否则,s=r*(-1)。
给出一段基于invert函数的代码,读者可自行确认处理结果。
img1 = np.array([[100, 0, 200], [ 0, 50, 0], [ 30, 0, 255]], np.uint8)
invert(img1)
print(img1)
img2 = np.array([[ -2, 0, -128], [127, 0, 5]], np.int8)
invert(img2)
print(img2)
img3 = np.array([[ 0., 1., 0.5, 0.75]])
invert(img3)
print(img3)
img4 = np.array([[ 0., 1., -1., -0.25]])
invert(img4, signed_float=True)
print(img4)
四种反色处理输出结果为:
array([[155, 255, 55], [255, 205, 255],[225, 255, 0]], dtype=uint8)
array([[1, -1, 127], [-128, -1, -6]], dtype=int8)
array([[1. , 0. , 0.5 , 0.25]])
array([[-0. , -1. , 1. , 0.25]])
5.2.2 对数(反对数)变换
图像的对数变换可以增强暗色区域中的白色或灰色细节,对应的变换定义为:
常数𝑐用来调整放缩倍数,“+1”操作是为了避免𝑟=0的情况,注意,对数变换是以2为底的,不是e或10。
在Skimage中,使用adjust_gamma函数完成反色处理。
adjust_gamma函数用于完成对数/反对数变换。
看一下adjust_gamma函数的声明:
skimage.exposure.adjust_log(image, gain, inv)
部分参数说明:
- image:输入图像。
- gain:增益值,对应于c,默认值为1。
- inv:操作标志位,默认为False,对应着对数比那换。如果设为True,则为反对数变换。
返回值:
- out:对数变换后的图像。
以下是有关对数变换功能的说明,可参考:
- 对数曲线在像素值较低的区域斜率大,在像素值较高的区域斜率小。对数变换将输入中范围较窄的低灰度值映射为范围较宽的灰度级,输入中的高灰度值则被映射为范围较窄的灰度级。对数变换后,较暗区域的对比度提升,可以增强图像的暗部细节。
- 对数变换实现了扩展低灰度值而压缩高灰度值的效果,广泛应用于频谱图像的显示中。对数变换的典型应用是傅立叶频谱的动态范围很宽,直接显示时受显示设备动态范围的限制而丢失大量的暗部细节;使用对数变换将图像的动态范围进行非线性压缩后,就可以清晰地显示。
5.2.3 幂次变换(Gamma变换)
图像的幂次变换(Power Law),又称为Gamma变换,可以提升暗部细节,对发白(曝光过度)或过暗(曝光不足)的图片进行矫正。
幂次变换定义为:
常数𝑐用来调整放缩倍数,幂次𝛾值的大小用于调整变换的功能,详情如下:
- 当 𝛾>1时,压缩低灰度级,扩展高灰度级,图像整体变暗;
- 当 𝛾<1时,扩展低灰度级,压缩高灰度级,图像整体变亮;
(幂次变换函数说明,改编自参考文献1)
在Skimage中,使用adjust_gamma函数完成反色处理。
adjust_gamma函数用于完成幂次变换。
看一下warp函数的声明:
skimage.exposure.adjust_gamma(image, gamma, gain)
部分参数说明:
- image:输入图像。
- gamma:非负实数,默认值为1.0,不同的gamma值,对应不同功能,见前面的注释。
- gain:增益值,默认值为1.0。
返回值:
- inverted:变换后的图像,与输入图像相同。
幂次变换通过非线性变换对人类视觉特性进行补偿,最大化地利用有效的灰度级带宽。很多拍摄、显示、打印设备的亮度曲线都符合幂律曲线,因此幂次变换广泛应用于各种设备显示效果的调校,称为伽马校正。
5.2.4 Sigmoid变换
无论是对数(反对数)变换,还是幂次变换,当参数确定后,都只能压缩(或拉伸)低灰度级,不能完成中间区域灰度级的拉伸。基于这种情况,Skimage在exposure模块中引入了sigmoid函数,完成上述功能。
标准的Sigmoid函数定义为:,但该函数是关于x=0对称的。假设图像灰度级范围归一化到[0,1]之间,则一种基于Sigmoid函数的灰度级拉伸函数可定义为:
式中,c为增益值,用于控制变换函数的形状。
SKimage通过adjust_sigmoid函数完成相应的变换。
adjust_sigmoid函数用于完成Sigmoid变换。
看一下warp函数的声明:
skimage.exposure.adjust_sigmoid(image, cutoff, gain, inv)
部分参数说明:
- image:输入图像。
- cutoff:对应水平方向上灰度值的偏移量,默认值为0.5。
- gain:增益值,用于控制sigmoid函数的变化快慢,默认值为10.0。
- inv:操作标志位,默认为False,对应着正Sigmoid变换;如果设为True,则为负Sigmoid变换(注意,不是逆变换)。
返回值:
- out:Sigmoid变换后的图像。
5.2.5 灰度级拉伸变换
前面介绍的各种灰度级点运算中,除了反色变换,其他都可归为非线性变换。Skimage还提供了一种简单有效的分段线性变换函数,完成常用的灰度级拉伸功能。
SKimage通过rescale_intensity函数完成相应的变换。
adjust_sigmoid函数用于完成Sigmoid变换。
看一下warp函数的声明:
skimage.exposure.rescale_intensity(image, in_range, out_range)
部分参数说明:
- image:输入图像。
- in_range:输入图像范围[in_min, in_max],有四种设置方式,参考后面。
- out_range:输出图像显示范围[out_min, in_out],参数设置方式如下:
'image':Use image min/max as the intensity range.
'dtype': Use min/max of the image’s dtype as the intensity range.
dtype-name:Use intensity range based on desired dtype. Must be valid key in DTYPE_RANGE.
2-tuple:Use range_values as explicit min/max intensitie
返回值:
- out:灰度级拉伸变换后的图像。
有如下语句:
rescale_intensity(image, in_range=(in_min, in_max), out_range=(out_min, out_max))
该函数完成的功能是将原图像像素值先裁剪到 in_range 范围内(即将小于in_min的灰度级都赋值为in_min, 大于in_max的灰度值, 赋值in_max),再进行归一化处理,即(image-in_min) / in_max - in_min) 后, 然后再放大至out_range 区间内[out_min, out_max]。一般out_range设置为[0,255]。
经过上述步骤处理之后,原图像小于in_min灰度值会被设置为out_min,大于in_max的灰度值会被设置为out_max,[in_min, in_max]之间的灰度值,会被线性拉伸至[out_min, out_max]范围内。
5.2.6 综合实例
以下是改编自官方文档的实例,分别完成对数变换、幂次变换、Sigmoid变换和线性拉伸。(此例还需要补充后面两种变换的代码)
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
from skimage import data, img_as_float
from skimage import exposure
# Load an example image
img = data.moon()
# Gamma
gamma_corrected = exposure.adjust_gamma(img, 2)
# Logarithmic
logarithmic_corrected = exposure.adjust_log(img, 1)
# Display results
fig = plt.figure(figsize=(8, 5))
axes = np.zeros((2, 3), dtype=object)
axes[0, 0] = plt.subplot(2, 3, 1)
axes[0, 1] = plt.subplot(2, 3, 2, sharex=axes[0, 0], sharey=axes[0, 0])
axes[0, 2] = plt.subplot(2, 3, 3, sharex=axes[0, 0], sharey=axes[0, 0])
ax_img, ax_hist, ax_cdf = plot_img_and_hist(img, axes[:, 0])
ax_img.set_title('Low contrast image')
ax_img, ax_hist, ax_cdf = plot_img_and_hist(gamma_corrected, axes[:, 1])
ax_img.set_title('Gamma correction')
ax_img, ax_hist, ax_cdf = plot_img_and_hist(logarithmic_corrected, axes[:, 2])
ax_img.set_title('Logarithmic correction')
# prevent overlap of y-axis labels
fig.tight_layout()
plt.show()
上图从左至右分别是:原图、对数变换结果图、幂次变换结果图。由处理及过可见,由于𝛾>1,经幂次变换后,低灰度级被压缩,图像整体变暗;与之相反,经对数变换后,低灰度级被扩展,图像整体变亮。
(本节初稿完成时间:2024-02-11)
(欢迎对DIP+python算法开发感兴趣的初学者,尤其是相关专业本科和低年级研究生关注,本专栏完将持续更新,总篇数不会少于50篇,每篇不会少于5000字,专栏完成之前(估计至少半年)完全免费阅读,敬请关注)