1.2.2 图像轮廓和直方图
下面来看两个特别的绘图示例:图像的轮廓
和直方图
。
图像的轮廓
绘制图像的轮廓(或者其他二维函数的等轮廓线)在工作中非常有用。因为绘制轮廓需要对每个坐标 [x, y] 的
像素值施加同一个阈值,所以首先需要将图像灰度化:
from PIL import Image from pylab import * # 读取图像到数组中 im = array(Image.open('data/empire.jpg').convert('L')) # 新建一个图像 figure() # 不使用颜色信息 gray() # 在原点的左上角显示轮廓图像 contour(im, origin='image') axis('equal') axis('off') show()
像之前的例子一样,这里用 PIL 的 convert() 方法将图像转换成灰度图像。
直方图
图像的直方图用来表征该图像像素值的分布情况。用一定数目的小区间(bin)来指定表征像素值的范围,每个小区间会得到落入该小区间表示范围的像素数目。该(灰度)图像的直方图可以使用 hist() 函数绘制:
figure() hist(im.flatten(),128) show()
hist() 函数的第二个参数指定小区间的数目。需要注意的是,因为 hist() 只接受一维数组作为输入,所以我们在绘制图像直方图之前,必须先对图像进行压平处理。
flatten() 方法将任意数组按照行优先准则转换成一维数组。图 1-3 为等轮廓线和直方图图像。
图 1-3:用 Matplotlib 绘制图像等轮廓线和直方图
1.2.3【交互式标注】
有时用户需要和某些应用交互,例如在一幅图像中标记一些点,或者标注一些训练数据。PyLab 库中的 ginput() 函数就可以实现交互式标注。
下面是一个简短的例子:
from PIL import Image from pylab import * im = array(Image.open('data/empire.jpg')) imshow(im) print('Please click 3 points\n') x = ginput(3) x_str=[''.join(str(i)) for i in x] print('\nyou clicked:'.join(x_str)) show()
上面的脚本首先绘制一幅图像,然后等待用户在绘图窗口的图像区域点击三次。程序将这些点击的坐标 [x, y] 自动保存在 x 列表里。
1.3 NumPy
NumPy(http://www.scipy.org/NumPy/)是非常有名的 Python 科学计算工具包,其中包含了大量有用的思想,比如数组对象(用来表示向量、矩阵、图像等)以及线性代数函数。NumPy 中的数组对象几乎贯穿用于本书的所有例子中 1 数组对象可以帮助你实现数组中重要的操作,比如矩阵乘积、转置、解方程系统、向量乘积和归一化,这为图像变形、对变化进行建模、图像分类、图像聚类等提供了基础。
NumPy 可以从 http://www.scipy.org/Download 免费下载,在线说明文档(http://docs.scipy.org/doc/numpy/)包含了你可能遇到的大多数问题的答案。关于 NumPy 的更多
内容,请参考开源书籍 [24]。
1.3.1 图像数组表示
在先前的例子中,当载入图像时,我们通过调用 array() 方法将图像转换成 NumPy 的数组对象,但当时并没有进行详细介绍。NumPy 中的数组对象是多维的,可以用来表示向量、矩阵和图像。一个数组对象很像一个列表(或者是列表的列表),但是数组中所有的元素必须具有相同的数据类型。除非创建数组对象时指定数据类型,否则数据类型会按照数据的类型自动确定。
对于图像数据,下面的例子阐述了这一点:
from PIL import Image from pylab import * im = array(Image.open('data/empire.jpg')) print(str(im.shape)+","+str(im.dtype)) imshow(im) im = array(Image.open('data/empire.jpg').convert('L'),'f') print(str(im.shape)+","+str(im.dtype)) imshow(im) show()
控制台输出结果如下所示:
(800, 569, 3) uint8 (800, 569) float32
每行的第一个元组表示图像数组的大小(行、列、颜色通道),紧接着的字符串表示数组元素的数据类型。
因为图像通常被编码成无符号八位整数(uint8),所以在第一种情况
下,载入图像并将其转换到数组中,数组的数据类型为“uint8”。
在第二种情况
下,对图像进行灰度化处理,并且在创建数组时使用额外的参数“f”;该参数将数据类型转换为浮点型。关于更多数据类型选项,可以参考图书 [24]。
注意,由于灰度图像没有颜色信息,所以在形状元组中,它只有两个数值。
数组中的元素可以使用下标访问。位于坐标 i、j,以及颜色通道 k 的像素值可以像下面这样访问:
value = im[i,j,k]
注 1: PyLab 实际上包含 NumPy 的一些内容,如数组类型。这也是我们能够在 1.2 节使用数组类型的原因。
多个数组元素可以使用数组切片方式访问。切片方式返回的是以指定间隔下标访问该数组的元素值。下面是有关灰度图像的一些例子:
im[i,:] = im[j,:] # 将第 j 行的数值赋值给第 i 行 im[:,i] = 100 # 将第 i 列的所有数值设为 100 im[:100,:50].sum() # 计算前 100 行、前 50 列所有数值的和 im[50:100,50:100] # 50~100 行,50~100 列(不包括第 100 行和第 100 列) im[i].mean() # 第 i 行所有数值的平均值 im[:,-1] # 最后一列 im[-2,:] (or im[-2]) # 倒数第二行
注意,示例仅仅使用一个下标访问数组。如果仅使用一个下标,则该下标为行下标。
注意,在最后几个例子中,负数切片表示从最后一个元素逆向计数。我们将会频繁地使用切片技术访问像素值,这也是一个很重要的思想。我们有很多操作和方法来处理数组对象。本书将在使用到的地方逐一介绍。你可以查阅在线文档或者开源图书 [24] 获取更多信息。
1.3.2 灰度变换
将图像读入 NumPy 数组对象后,我们可以对它们执行任意数学操作。一个简单的例子就是图像的灰度变换。考虑任意函数 f,它将 0…255 区间(或者 0…1 区间)映射到自身(意思是说,输出区间的范围和输入区间的范围相同)。
下面是关于灰度变换的一些例子:
from PIL import Image from numpy import * im = array(Image.open('empire.jpg').convert('L')) im2 = 255 - im # 对图像进行反相处理 im3 = (100.0/255) * im + 100 # 将图像像素值变换到 100...200 区间 im4 = 255.0 * (im/255.0)**2 # 对图像像素值求平方后得到的图像
第一个例子将灰度图像进行反相处理;第二个例子将图像的像素值变换到 100…200
区间;第三个例子对图像使用二次函数变换,使较暗的像素值变得更小。
图 1-4 为所使用的变换函数图像。
图 1-5 是输出的图像结果。你可以使用下面的命令查看图像中的最小和最大像素值:
print int(im.min()), int(im.max())
完整代码:
from PIL import Image from numpy import * from pylab import * im = array(Image.open('data/empire.jpg').convert('L')) print(int(im.min()), int(im.max())) im2 = 255 - im # invert image print(int(im2.min()), int(im2.max())) im3 = (100.0/255) * im + 100 # clamp to interval 100...200 print(int(im3.min()), int(im3.max())) im4 = 255.0 * (im/255.0)**2 # squared print(int(im4.min()), int(im4.max())) figure() gray() subplot(1, 3, 1) imshow(im2) axis('off') title(r'$f(x)=255-x$') subplot(1, 3, 2) imshow(im3) axis('off') title(r'$f(x)=\frac{100}{255}x+100$') subplot(1, 3, 3) imshow(im4) axis('off') title(r'$f(x)=255(\frac{x}{255})^2$') show()
1-5:灰度变换。对图像应用图1-4中的函数:f (x)=255-x 对图像进行反相处理(左);f (x)=(100/255)
x+100 对图像进行变换(中);f (x)=255(x/255)2 对图像做二次变换(右)
如果试着对上面例子查看最小值和最大值,可以得到下面的输出结果:
2 255 0 253 100 200 0 255
array() 变换的相反操作可以使用 PIL 的 fromarray() 函数完成:
pil_im = Image.fromarray(im)
如果你通过一些操作将“uint8”数据类型转换为其他数据类型,比如之前例子中的im3 或者 im4,那么在创建 PIL 图像之前,需要将数据类型转换回来:
pil_im = Image.fromarray(uint8(im))
如果你并不十分确定输入数据的类型,安全起见,应该先转换回来。
注意,NumPy总是将数组数据类型转换成能够表示数据的“最低”数据类型。对浮点数做乘积或除法操作会使整数类型的数组变成浮点类型。
1.3.3 图像缩放
NumPy 的数组对象是我们处理图像和数据的主要工具。想要对图像进行缩放处理没有现成简单的方法。我们可以使用之前 PIL 对图像对象转换的操作,写一个简单的用于图像缩放的函数。
把下面的函数添加到 imtool.py 文件里:
def imresize(im,sz): """ 使用 PIL 对象重新定义图像数组的大小 """ pil_im = Image.fromarray(uint8(im)) return array(pil_im.resize(sz))
我们将会在接下来的内容中使用这个函数。
1.3.4 直方图均衡化
图像灰度变换中一个非常有用的例子就是直方图均衡化。
直方图均衡化
是指将一幅图像的灰度直方图变平,使变换后的图像中每个灰度值的分布概率都相同。在对图像做进一步处理之前,直方图均衡化通常是对图像灰度值进行归一化的一个非常好的方法,并且可以增强图像的对比度。
在这种情况下,直方图均衡化的变换函数是图像中像素值的累积分布函数(cumulative distribution function
,简写为 cdf,将像素值的范围映射到目标范围的归一化操作)。
下面的函数是直方图均衡化的具体实现。
将这个函数添加到 imtool.py 里:
def histeq(im,nbr_bins=256): """ 对一幅灰度图像进行直方图均衡化 """ # 计算图像的直方图 imhist,bins = histogram(im.flatten(),nbr_bins,normed=True) cdf = imhist.cumsum() # cumulative distribution function cdf = 255 * cdf / cdf[-1] # 归一化 # 使用累积分布函数的线性插值,计算新的像素值 im2 = interp(im.flatten(),bins[:-1],cdf) return im2.reshape(im.shape), cdf
该函数有两个输入参数,一个是灰度图像
,一个是直方图中使用小区间的数目
。
函数返回直方图均衡化后的图像,以及用来做像素值映射的累积分布函数。注意,函数中使用到累积分布函数的最后一个元素(下标为 -1),目的是将其归一化到 0…1范围。
你可以像下面这样使用该函数:
#imtools.py from PIL import Image from numpy import * def histeq(im,nbr_bins=256): """ 对一幅灰度图像进行直方图均衡化 """ # 计算图像的直方图 imhist,bins = histogram(im.flatten(),nbr_bins,normed=True) cdf = imhist.cumsum() # cumulative distribution function cdf = 255 * cdf / cdf[-1] # 归一化 # 使用累积分布函数的线性插值,计算新的像素值 im2 = interp(im.flatten(),bins[:-1],cdf) return im2.reshape(im.shape), cdf def imresize(im,sz): """ 使用 PIL 对象重新定义图像数组的大小 """ pil_im = Image.fromarray(uint8(im)) return array(pil_im.resize(sz)) #test01_03.py from PIL import Image from numpy import * from pylab import * import imtools # im = array(Image.open('data/AquaTermi_lowcontrast.jpg').convert('L')) im = array(Image.open('data/empire.jpg').convert('L')) im2,cdf = imtools.histeq(im) imshow(im2) gray() show()
图 1-6 和图 1-7 为上面直方图均衡化例子的结果。
上面一行显示的分别是直方图均衡化之前和之后的灰度直方图,以及累积概率分布函数映射图像。
可以看到,直方图均衡化后图像的对比度增强了,原先图像灰色区域的细节变得清晰。
图 1-7:直方图均衡化示例。左侧为原始图像和直方图,中间图为灰度变换函数,右侧为直
方图均衡化后的图像和相应直方图