本节将介绍基于Skimage的图像基本操作,包括文件数据的载入、读取、显示、保存等内容。这部分的功能可以通过其他扩展库实现,因此如果你已经掌握了使用其他库(比如Matplotlib或OpenCV)完成上述类似的功能,则可快速浏览本节。当然如果你是新手,或者想了解一下Skimage在这些方面有何不同之处,则欢迎阅读。
目录
3.1 图像数据的加载
Skimage与其他面向Python的DIP扩展库有一处非常明显的不同,它本身可提供类型丰富的图像数据库。举个例子,该数据集中包含有一个多通道医学图像集,它对于我们初学者研究医学图像处理提供了很大便利。虽然该图像库中图像的数量并不很多,但其类型以及内容几乎涵盖了CV的大多数领域。
该数据库是通过data模块来组织的。我们在本专栏文章002篇:“Skimage各模块初探(上)”中曾简单介绍过该模块的组成,但受限于篇幅,没有全面介绍,在此补齐相关内容。
3.1.1 下载图像数据集
可通过调用ski.data.download_all()一次性下载所有图像,下面简要介绍官网上给出的下载说明。
首先,要确认已正常安装了scikit-image扩展库(可参考本专栏文章001篇:“环境搭建”)。
然后,通过命令窗口,进入到python运行窗口,依次运行下面两行代码:
import skimage as ski
ski.data.download_all()
如果顺利的话,只需不到1分钟,应该就能看到下载成功的提示。为何说是应该呢?因为我试过多次,从来没成功完成下载。
3.2 一些基本概念
首先简要给出一些与图像格式相关的术语,便于读者特别是没有接触过DIP的读者快速了解。
3.2.1 数字图像
数字图像(Digital image)是相对于模拟图像而言的,你可以简单的认为是“数字化图像”,其中数字化过程包括采样(Sampling)和量化(Quantization)两个过程。数字图像中的每一个点都可以看作是一个具有四维特征空间上的一个点,比如我们用来表示。其中,m和n表示空间坐标特征量,它们通常成对出现;c表示图像通道的序号;f则表示该点的某种属性,比如灰度值等等。在数字图像中,每一个点有一个专门的名称,称为像素(pixel),它是picture+element的组合。为何用picture,而不用image?那是因为在图像处理的发展初期,研究者习惯使用picture而不是image一词来表示图像的,这是因为早期处理的图像还是模拟图像,主要以照片形式出现的直到了上世纪80年代,digital image的名称才逐渐确定下来。
采样指的是将图像中各个像素点的坐标系进行数字化处理,比如我们说一幅图像的尺寸是,此处M和N分别代表图像的高度(对应行的数目)和宽度(对应列的数目),则该图像中任意一个像素点的坐标值可表示为,其中且。不能看出,经过采样处理后,图像的像素点变成了有限可数个,且每一个点对应着一个唯一的坐标对。
图像通道序号c与采样无关,取决于图像的类别。比如,我们经常见到的灰度(Gray-level)图像和二值(Binary)图像,一般都是单通道的,即c=1。而常见的彩色(Color)图像,则是3通道或者4通道的,即c=3或4。某些特定类型的图像,如医学图像、遥感图像等,通道数可能更多。
量化过程是针对图像的最后一维特征而言的。我们在用像素点表示信息时,除了位置和通道信息,更重要的是通过f值来表示某种特定的含义。比如,光线的明暗、颜色类别、前景/背景、透射量的大小,等等。这些特征量通常是连续分布的,为了方便保存和显示,需要将它们进行量化处理,变成离散并可数的,这就是量化的含义。举个例子,常见的灰度图像的灰度级通常取256,即f的取值范围是,对于二值图像,这个数值变成了。
(图像的数字化示例,来自“数字图像处理与分析 中科院刘定生”)
上图是一幅图像数字化过程的示例,其中左图在水平和垂直方向被划分为一个个小方块,这对应着采样过程,采用的是最常见的水平/垂直等间隔采样方式。右图中每一个小方块对应一个像素点(pixel),每个像素点对应的整数值,是其灰度强度值(Intensity),即前面提到的f。此图没有体现出多通道的信息。
3.2.2 数字图像的类型
在此我们提到的类型,不是从图像语义内容角度划分的(如自然图像、医学图像、红外图像等等),而是从图像数据的组织和表达方式划分的。
按照图像数据的表达形式,主要分为位图和矢量图两类:
- 位图(bitmap,也称为点阵图):由多个像素点按照特定的点阵结构组成的图像。位图的特点是可以精确地表示每一个像素的颜色和透明度,但在尺寸进行放缩时,会存在形变。本教程主要讨论位图,我们前面提到的灰度图、二值图和彩色图像,都是指的位图。
- 矢量图(也称为向量图):这种类型的图像是由数学曲线和形状构成的,不依赖于分辨率或尺寸。矢量图形可以通过缩放而不失真,适用于需要清晰边缘和高对比度的设计。这类图像通常在设计领域使用,比如各类字体,用visio绘制的设计图等等。本教程不涉及此类图像。
对于位图,可进一步划分为四种图像,即:二值图像、灰度图像、彩色图像和索引图像。
- 二值图像:这类图像中像素点的取值只有0和1两种情况,但不一定1就代表有效的对象信息。典型的例子是,用于光学字符识别(OCR)图像。
- 灰度图像:灰度图像像素点的取值范围通常可以取任意正整数,但考虑到人眼对灰度变化的范围有限,且便于在计算机内容以二进制存储,通常将灰度级数目设为,其中k称为“位(bit)”。比如,8位灰度图像,其L=256,f的取值范围是。但要注意k取取值完全可以是其他值,比如在某些医学图像k=12。
- 彩色图像:彩色图像是由多个(通常是3~4个)单通道图像的组合而成的。不同通道代表不同的颜色值或者信息值。我们最常见的一种彩色图像称为:RGB图像,它由红色(R)、绿色(G)和蓝色(B)是三个通道组成。除此之外,还有很多种基于其他彩色空间的彩色图像。
- 索引图像:索引图像使用颜色索引来表示像素的颜色。颜色索引通过查找一个颜色索引矩阵(MAP)来实现的,MAP中包含了每种颜色各通道的单色值。
除了上述信息之外,还有与图像压缩格式相关的内容,图像文件的后缀(如jpg、png、tif等)就代表着该图像所采用的压缩格式。但本教程不涉及任何图像编码的内容,因此不再此单独介绍了,各位可参考网上其他资料。
3.3 用Skiamge读取、显示和保存图像
Scikit-image分别用io.imread、io.imsave和io.imshow这三个函数完成图像的读、写和显示功能。
3.3.1 读图像
先看一下io.imread的声明:
skimage.io.imread(fname, as_gray=False, plugin=None, **plugin_args)
参数说明:
- fname:字符串型变量,可以用相对路径或者绝对路径,用双引号或者单引号引出字符串。比如,要读取当前路径下Pics文件夹中的input.jpg图像,则设为'./Pics/input.jpg'。
- as_gray:bool型变量, 可选项,默认为False,如果设为True,且输入是彩色图像,则将其转换为灰度图像 (数据用64位浮点型);如果输入图像是灰度图,则不做任何处理。
- plugin:字符串型变量,可选项,默认为空,通常是给特定类型图像数据提供专用插件之用,很少用到。
返回值:
img_array:读取到的图像数据,ndarray数据格式,可以是单通道、3通道或者4通道。
可以调用以下语句,显示各类图像信息,如下:
- type(img):显示类型
- img.shape:显示尺寸
- img.shape[0]):图片宽度
- img.shape[1] :图片高度
- img.shape[2] :图片通道数
- img.size :显示总像素个数
- img.max() :最大像素值
- img.min() :最小像素值
- img.mean() :像素平均值
3.3.2 保存图像文件
先看一下io.save的声明:
skimage.io.imsave(fname, arr, plugin=None, check_contrast=True, **plugin_args)
参数说明:
- fname:字符串型变量,可以用相对路径或者绝对路径,要求和读文件要求相同。
- arr:图像数据,可以是单通道、3通道或4通道图像。
- plugin:字符串型变量,可选项,默认为None,通常是在保存文件时所用到的特定类型图像数据专用插件,一般不用指定。
- check_contrast:bool型, 可选项,默认为True,即检查图像是否为低对比度图像,如果是,或打印警报,很有意思的小功能,不知道用的什么技术。
返回值:无返回值
3.3.3 显示图像文件
先看一下io.save的声明:
skimage.io.save(arr, plugin=None, **plugin_args)
参数说明:
- arr:需要显示的图像数据(narray格式),或文件名(字符串型)。
- plugin:字符串型变量,可选项,默认为None,通常是在显示文件时所用到的插件,一般不用。
返回值:无返回值
小结一下,如同其他扩展库一样,Skimage提供了自己配套的读、写、显示(三件套)图像操作函数,但其中的显示功能较简单,因此io.imshow并不是完成图像显示的首选函数。
本教程使用的图像显示函数是Matplotlib提供的imshow,该函数功能十分强大,具体的使用说明大家可参考官方文档。在此针对其中几个重要的参数加以说明,供大家参考。
看一下Matplotlib imshow函数的声明,居然有长长的将近20个参数,可见功能之强大。
imshow(X, cmap, norm, aspect, interpolation, alpha, vmin, vmax, origin, extent, shape, filternorm, filterrad, imlim, resample, url, ...)
重要参数说明:
- cmp:全称是color mapping:即颜色映射。用于控制图像中不同数值所对应的颜色。可以选择内置的颜色映射,如gray(灰度)、hot(热图)等,也可以自定义颜色映射。如果不设定,默认为彩色图像。
- norm:用于控制数值的归一化方式。可以选择Normalize、LogNorm等归一化方法。
- aspect:控制图像纵横比(aspect ratio)。可以设置为auto或一个数字。
- interpolation:插值方法。用于控制图像的平滑程度和细节程度。可以选择nearest、bilinear、bicubic等插值方法。
- origin:坐标轴原点的位置。可以设置为upper或lower。
- extent:控制显示的数据范围,即只显示图像中的某个矩形区域,可以设置为[xmin, xmax, ymin, ymax]。
- vmin、vmax:控制颜色映射的值域范围,注意,此处是灰度值范围的百分比,而不是灰度值,在按照灰度图进行显示时经常用到。
3.3.4 实例分析
下面给出一段代码,简单演示上面几个函数的功能。具体的功能参考注释。
%matplotlib inline
%config InlineBackend.figure_format = 'retina'
import numpy as np
from matplotlib import pyplot as plt
from skimage import io
plt.rcParams['font.sans-serif'] = 'KaiTi' #显示图形中的文字类型
上述几行代码,完成的是载入必要的模块,并设置matplotlib部分显示环境。需要说明的是,上述显示设置针对Jupyter notebook提供的交互式计算环境创建“.ipynb”交互式文件的。建议将这几行代码段放在具体的处理过程之前。
下面的代码演示了图像的读取、显示和保存的实例。
# 读取本地图像文件
img_color = io.imread('./input.jpg',0) # 按照原始图像格式读取(不强制进行灰度化)
img_gray = io.imread('./input.jpg',1) # 按照灰度图像格式读取
# 存储灰度图像
# io.imsave('gray.jpg', img_gray)
print('彩色图像素点强度值范围:')
print(img_color.min(),img_color.max())
print('灰度图的尺寸:')
print(img_gray.shape)
# 显示图像
plt.figure(figsize=(8,4))
plt.subplot(141),plt.axis('off')
plt.title('彩色图像'),plt.imshow(img)
plt.subplot(142),plt.axis('off')
plt.title("热力图"),plt.imshow(img_gray,cmap="hot")
plt.subplot(143),plt.axis('off')
plt.title('灰度图像'),plt.imshow(img_gray,cmap='gray')
plt.subplot(144),plt.axis('off')
plt.title("灰度图像"),plt.imshow(img_gray,cmap="gray", vmin=0.25, vmax=0.75)
# 让图像显示的更紧凑
plt.tight_layout()
观察上述结果不难发现,如果读入图像是彩色图像,直接调用matplotlib的imshow函数是无法正常显示为灰度图的。解决方法是直接读入灰度图(参数设为1),或者调用彩色空间转换函数完成彩彩色图像的灰度化处理。另外,在显示灰度图时,可以通过vmin和vmax设置显示范围,但要注意,vmin和vmax要设置为0~1之间的实数值,比如[0.2, 0.8]。
为了正确运行本教程的代码,以便观察处理结果,需要各位用Jupyter notebook建立一个新的文件,然后将上述代码段复制粘贴到新文件中,然后点击运行,即可运行该程序段,并将结果显示在代码下方。需要提示两处,一是为了节省篇幅,下面四行与显示相关的设置python语句不再每个程序段中重复给出,大家在创建的jupyter notebook文件的最上方运行一遍即可。第二点,文件读取和保存操作时,一定要注意设置正确的路径,比如,用‘./’表示当前路径等。
(本节初稿完成时间:2024-02-01)
(欢迎对DIP+python算法开发感兴趣的初学者,尤其是相关专业本科和低年级研究生关注,本专栏完将持续更新,总篇数不会少于50篇,每篇不会少于5000字,专栏完成之前(估计至少半年)完全免费阅读,敬请关注)