基本的图像操作与处理
1 PIL:Python图像处理类库
PIL(Python Imaging Library,图像处理类库)提供了通用的图像处理功能,以及大量有用的基本图像操作,比如图像缩放、裁剪、旋转、颜色转换等。
下面我们从头开始学习PIL中的函数:
open(Image, mode) #读取一张图片
convert(mode,matrix,dither,palette,colors) #图像的颜色转换
编写代码
from PIL import Image
import matplotlib.pyplot as plt
if __name__=='__main__':
pil_im = Image.open('raccoon.jpg')
plt.gray()
plt.subplot(121)
plt.title('(a) Original Image')
plt.axis('off')
plt.imshow(pil_im)
pli_il = Image.open('raccoon.jpg').convert('L')
plt.subplot(122)
plt.title('(b) Grayscale Image')
plt.axis('off')
plt.imshow(pli_il)
plt.show()
结果如下:
1.1 转换图像格式
通过save方法,PIL可以将图像保存成多种格式的文件。
函数:
save(filename,format) #保存文件并指定保存格式
编写代码:
from PIL import Image
if __name__=='__main__':
im = Image.open('D:\\123\图像处理\Image Processing\Image Processing\\raccoon.jpg','r')
raccoon = im.save('D:\\123\图像处理\Image Processing\Image Processing\\raccoon.png', 'png')
1.2 创建缩略图
使用PIL可以很方便地创建缩略图。thumbnail()方法接受一个元祖参数(该参数指定生成缩略图大小),然后将图像转换成符合元组参数指定大小的缩略图。
pil_im.thumbnail((128,128)) #创建最长边为128像素的缩略图
代码:
from PIL import Image
import matplotlib.pyplot as plt
if __name__=='__main__':
pil_im = Image.open('D:\\123\图像处理\Image Processing\Image Processing\\raccoon.jpg','r')
plt.subplot(121)
plt.title('(a) Original Image')
plt.axis('off')
plt.imshow(pil_im)
pil_im.thumbnail((128, 128), resample=Image.BICUBIC)
plt.subplot(122)
plt.title('(b) Thumbnail Image')
plt.axis('off')
plt.imshow(pil_im)
plt.show()
结果如下:
这里缩略图变得模糊是由于matplot在呈现是默认将两幅图片放缩到一样的大小,实质上的缩略图是128×128像素的,将其拉伸变大就变得模糊了。
1.3 复制和粘贴图像区域
使用crop方法可以从一幅图像中裁剪指定区域:
box = (100,100,400,400)
region = pil_im.crop(box)
该区域使用四元组来指定。四元组的坐标依次是(左,上,右,下)。PIL中指定坐标系的左上角坐标为(0,0)。我们可以旋转上面代码获得的区域,然后使用paste方法将该区域放回去:
region = region.transpose(Image.ROTATE_180)
pil_im.paste(region,box)
代码:
from PIL import Image
import matplotlib.pyplot as plt
if __name__=='__main__':
pil_im = Image.open('D:\\123\图像处理\Image Processing\Image Processing\\raccoon.jpg')
plt.subplot(121)
plt.title('(a) Original Image')
plt.axis('off')
plt.imshow(pil_im)
box = (100,100,400,400)
region = pil_im.crop(box)
region = region.transpose(Image.ROTATE_180)
pil_im.paste(region,box)
plt.subplot(122)
plt.title('(b) Rotate Image')
plt.axis('off')
plt.imshow(pil_im)
plt.show()
结果如下:
1.4 调整尺寸和旋转
要调整一幅图像的尺寸,我们可以调用resize方法。该方法的参数是一个元祖,用来指定新图像的大小:
out = pil.resize((128,128))
要旋转一幅图像,可以使用逆时针方式表示旋转角度,然后调用rotate方法:
out = pil_im.rotate(45)
代码:
from PIL import Image
import matplotlib.pyplot as plt
if __name__=='__main__':
pil_im = Image.open('D:\\123\图像处理\Image Processing\Image Processing\\raccoon.jpg')
plt.subplot(131)
plt.title('(a) Original Image')
plt.axis('off')
plt.imshow(pil_im)
out1 = pil_im.resize((200,200))
plt.subplot(132)
plt.title('(b) Resize Image')
plt.axis('off')
plt.imshow(out1)
out2 = pil_im.rotate(90)
plt.subplot(133)
plt.title('(c) Rotate Image')
plt.axis('off')
plt.imshow(out2)
plt.show()
结果如下:
2 Matplotlib
我们处理数学运算、绘制图表,或者在图像上绘制点、直线和曲线时,Matplotlib是个很好的类库,具有比PIL更强大的绘图功能。
2.1 绘制图像、点和线
尽管Matplotlib可以绘制出较好的条形图、饼状图、散点图等,但是对于大多数计算机视觉应用来说,仅仅需要用到几个绘图指令。最重要的是,我们想用点和线来表示一些事物,比如兴趣点、对应点以及检测出的物体。下面是用几个点和一条线绘制图像的例子:
from PIL import Image
import matplotlib.pyplot as plt
from pylab import *
if __name__=='__main__':
im = array(Image.open('D:\\123\图像处理\Image Processing\Image Processing\jimei2.jpg'))
imshow(im)
x = [100,100,400,400]
y = [200,500,200,500]
plot(x,y,'r*') #红色星状线绘制标记点
plot(x[:2],y[:2]) #连接前两个点的线
title('Plotting:"jimei.jpg"')
show()
绘图时,有很多选项可以控制图像的颜色和样式。例如:
plot(x,y) #默认为蓝色实线
plot(x,y,'r*') #红色星状标记
plot(x,y,'go-') #带有圆圈标记的绿线
plot(x,y,'ks:') #带有正方形标记的黑色点线
2.2 图像轮廓和直方图
绘制图像轮廓(或其他二维函数的等轮廓线)在工作中非常有用。因为绘制轮廓需要对每个坐标[x,y]的像素值施加同一个阈值,所以首先需要将图像灰度化:
from PIL import Image
from pylab import *
if __name__=='__main__':
im = array(Image.open('D:\\123\图像处理\Image Processing\Image Processing\jimei2.jpg').convert('L'))
figure()
gray()
contour(im,origin='image')
axis('equal')
axis('off')
figure()
hist(im.flatten(),128)
show()
图像直方图用来表征该图像像素值的分布情况。用一定数目的小区间(bin)来指定表征像素值的范围,每个小区间会得到落入该小区间表示范围的像素数目。hist函数的第二个参数指定小区间的数目。这里需要注意的是,因为hist只接受一维数组作为输入。所以我们在绘制图像直方图之前,必须先对图像进行压平处理。flatten方法将任意数组按照行优先准则转换成一维数组。
2.3 交互式标注
有时用户需要和某些应用交互,例如在一幅图像中标记一些点,或者标注一些训练数据。函数ginput就可以实现交互性标注,下面例子首先绘制一幅图像,然后等待用户在绘图窗口的图像区域点击三次。程序将这些点击点坐标[x,y]自动保存在x列表里。
from PIL import Image
from pylab import *
if __name__=='__main__':
im = array(Image.open('D:\\123\图像处理\Image Processing\Image Processing\jimei2.jpg'))
imshow(im)
print('Please click 3 points')
x = ginput(3)
print('you clicked',x)
show()
3 Numpy
Numpy是非常有名的Python科学计算工具包,其中包含了大量有用的思想,比如数组对象(用来表示向量、矩阵、图像等)以及线性代数函数。Numpy中的数组对象几乎贯穿用于本书中的所有例子数组对象可以帮助你实现数组中重要的操作,比如矩阵乘积、转置、解方程系统、向量乘积和归一化,这为图像变形、对变化建模、图像分类、图像聚类等提供了基础。
3.1 图像数组表示
在先前的例子中,当载入图像时,我们通过调用array方法将图像转换为Numpy的数组对象,但当时并没有进行详细的介绍。Numpy中的数组对象是多维的,可以用来表示向量、矩阵和图像。一个数组对象很想一个列表(或者是列表的列表),但是数组中所有元素必须具有相同的数据类型。
对于图像数据,下面的例子阐述了这一点:
from PIL import Image
from pylab import *
if __name__=='__main__':
im = array(Image.open('D:\\123\图像处理\Image Processing\Image Processing\jimei2.jpg'))
print(im.shape, im.dtype)
im = array(Image.open('D:\\123\图像处理\Image Processing\Image Processing\jimei2.jpg').convert('L'),'f')
print(im.shape, im.dtype)
输出结果:
这里每一行元组表示图像数组的大小(行、列、颜色通道),紧接着的字符串表示数组元素的数据类型。因为图像通常被编码成无符号八位整数(uint8),所以再第一种情况下,载入图像并将其转换到数组中,数组的数据类型为“uint8”。在第二种情况下,对图像进行灰度化处理,并且在穿件数组时使用额外的参数f;该参数将数据类型转换为浮点型。
3.2 灰度变换
将图像读入Numpy数组对象后,我们可以对他们执行任意数学操作。一个简单的例子就是图像的灰度变换。
from PIL import Image
from numpy import *
from pylab import *
if __name__=='__main__':
im = array(Image.open('D:\\123\图像处理\Image Processing\Image Processing\jimei2.jpg').convert('L'))
im2 = 255 - im #对图像进行反相处理
im3 = (100.0/255) * im + 100 #将图像像素值变换到100-200区间
im4 = 255.0 * (im/255.0)**2 #对图像像素值求平方后得到图像
figure()
gray()
subplot(1, 3, 1)
imshow(im2)
axis('off')
title(r'(a) $f(x)=255-x$')
subplot(1, 3, 2)
imshow(im3)
axis('off')
title(r'(b) $f(x)=\frac{100}{255}x+100$')
subplot(1, 3, 3)
imshow(im4)
axis('off')
title(r'(c) $f(x)=255(\frac{x}{255})^2$')
show()
3.3 直方图均衡化
图像灰度变换中一个非常有用的例子就是直方图均衡化。直方图均衡化是指将一幅图像的灰度直方图变平,使变换后的图像中每个灰度值的分布概率都相同。在对图像做进一步处理之前,直方图均衡化通常是对图像灰度值进行归一化的一个非常好 的方法,并且可以增强图像的对比度。
在这种情况下,直方图均衡化的变换函数是图像中像素的累积分布函数(简称cdf,将像素值的范围映射到目标范围的归一化操作)。
下面函数是直方图均衡化的具体实现:
def histeq(im, nbr_bins=256):
"""对一幅灰度图像进行直方图均衡化"""
#计算图像的直方图
imhist,bins = histogram(im.flatten(), nbr_bins, normed=True)
cdf = imhist.cumsum() #累积分布函数
cdf = 255 * cdf / cdf[-1] #归一化
#使用累积分布函数的线性插值,计算新的像素值
im2 = interp(im.flatten(), bins[:,-1], cdf)
return im2.reshape(im.shape), cdf
直方图均衡代码:
from PIL import Image
from pylab import *
from numpy import *
import imtools
if __name__=='__main__':
im = array(Image.open('D:\\123\图像处理\Image Processing\Image Processing\jimei2.jpg').convert('L'))
im2, cdf = imtools.histeq(im)
figure()
subplot(2, 2, 1)
axis('off')
gray()
title('(a)Original Image')
imshow(im)
subplot(2, 2, 2)
axis('off')
title('(b)Image After Histogram Equalization')
imshow(im2)
subplot(2, 2, 3)
axis('off')
title('(c)Histogram Of Original Image')
hist(im.flatten(), 128, normed=True)
subplot(2, 2, 4)
axis('off')
title('(d)Histogram After Equalization')
hist(im2.flatten(), 128, normed=True)
show()
3.4 图像的主成分分析(PCA)
PCA(Principal Component Analysis,主成分分析)是一个非常有用的降维技巧。它可以在使用尽可能少维数的前提下,尽量多地保持训练数据的信息,在此意义上是一个最佳技巧。由于图像具有很高的维数,在许多计算机视觉应用中,我们经常使用降维操作。PCA产生的投影矩阵可以被视为将原始坐标变换到现有的坐标系,坐标系中的各个坐标按照重要性递减排列。
为了对图像数据进行PCA变换,图像需要转换为一维向量表示。我们可以使用Numpy类库中的flatten方法进行变换。
将扁平的图像堆积起来,我们可以得到一个矩阵,矩阵的一行表示一幅图像。在计算主方向之前,所有的行图像按照平均图像进行了中心化。我们通常使用SVD(Singular Value Decomposition,奇异值分解)方法来计算主成分;但当矩阵的维数很大时,SVD的计算非常慢,所以此时通常不使用SVD分解。下面就是PCA操作的代码:
def pca(X):
"""主成分分析:
输入:矩阵X,其中该矩阵中存储训练数据,每一行为一条训练数据
返回:投影矩阵(按照维度的重要性排序)、方差和均值"""
#获取维数
num_data, dim = X.shape
#数据中心化
mean_X = X.mean(axis=0)
X = X - mean_X
if dim>num_data:
#PCA使用紧致技巧
M = dot(X, X.T) #协方差矩阵
e, EV = linalg.eigh(M) #特征值和特征向量
tmp = dot(X.T,EV).T #紧致技巧
V = tmp[::-1] #由于最后的特征向量使我们需要的,所以将其逆转
S = sqrt(e)[::-1] #由于特征值是按照递增顺序排列的,所以将其逆转
for i in range(V.shape[1]):
V[:,i] /= S
else:
#PCA使用SVD方法
U,S,V = linalg.svd(X)
V = V[:num_data] #仅返回前num_data维数据才合理
#返回投影矩阵
return V,S,mean_X
3.5 使用pickle模块
如果想保存一些结果或者数据以方便后续使用,Python中的pickle模块非常有用。pickle模块可以接受几乎所有的Python对象,并且将其转换为字符串表示,该过程叫做封装。从字符串重构对象,称为拆封,这些字符串表示可以方便地存储和传输。
import pickle
if __name__=='__main__':
dataList = [[1, 1, 'yes'],
[1, 1, 'yes'],
[1, 0, 'no'],
[0, 1, 'no'],
[0, 1, 'no']]
dataDic = {0: [1, 2, 3, 4],
1: ('a', 'b'),
2: {'c': 'yes', 'd': 'no'}}
# 使用dump()将数据序列化到文件中
fw = open('dataFile.txt', 'wb')
pickle.dump(dataList, fw, -1)
pickle.dump(dataDic, fw)
fw.close()
# 使用load()将数据从文件中序列化读出
fr = open('dataFile.txt', 'rb')
data1 = pickle.load(fr)
print(data1)
data2 = pickle.load(fr)
print(data2)
fr.close()
# 使用dumps()和loads()举例
p = pickle.dumps(dataList)
print(pickle.loads(p))
p = pickle.dumps(dataDic)
print(pickle.loads(p))
运行结果如下:
4 Scipy
Scipy是建立在Numpy基础上,用于数值运算的开源工具包。Scipy提供很多高效操作,可以实现数值积分、优化、统计、信号处理,以及对我们来说最重要的图像处理功能。
4.1 图像模糊
图像的高斯模糊是非常经典的图像卷积例子。本质上,图像模糊就是将(灰度)图像I和一个高斯核进行卷积操作: 其中∗表示卷积操作;Gσ是标准差为σ的二维高斯核,定义为: 高斯模糊通常是其他图像处理操作的一部分,比如图像插值操作、兴趣点计算以及其他应用。
from PIL import Image
from numpy import *
from scipy.ndimage import filters
from pylab import *
if __name__=='__main__':
im = array(Image.open('D:\\123\图像处理\Image Processing\Image Processing\\raccoon.jpg').convert('L'))
gray()
subplot(1,4,1)
axis('off')
title('Original Image')
imshow(im)
for bi, blur in enumerate([2, 5, 10]):
im2 = zeros(im.shape)
im2 = filters.gaussian_filter(im, blur)
im2 = np.uint8(im2)
imNum = str(blur)
subplot(1, 4, 2 + bi)
axis('off')
title('GaussVar = ' + imNum)
imshow(im2)
show()
4.2 图像导数
在很多应用中图像强度的变化情况是非常重要的信息。强度的变化可以用灰度图像I(对于彩色图像,通常对每个颜色通道分别计算导数)的x和y方向导数与梯度进行描述。
from PIL import Image
from numpy import *
from scipy.ndimage import filters
from pylab import *
if __name__=='__main__':
im = array(Image.open('D:\\123\图像处理\Image Processing\Image Processing\\raccoon.jpg').convert('L'))
gray()
subplot(141)
title('Original Image')
axis('off')
imshow(im)
imx = zeros(im.shape)
filters.sobel(im,1,imx)
subplot(142)
title('Derivatives In X Axis')
axis('off')
imshow(imx)
imy = zeros(im.shape)
filters.sobel(im, 0, imy)
subplot(143)
title('Derivatives In Y Axis')
axis('off')
imshow(imy)
mag = 255 - sqrt(imx ** 2 + imy ** 2)
subplot(1, 4, 4)
title('Gradient Image')
axis('off')
imshow(mag)
show()
结果如下
上述计算图像导数的方法有一些缺陷:在该方法中,滤波器的尺度需要随着图像分 辨率的变化而变化。为了在图像噪声方面更稳健,以及在任意尺度上计算导数,我 们可以使用高斯导数滤波器:
其中,Gσx 和 Gσy 表示 Gσ 在 x 和 y 方向上的导数,Gσ 为标准差为 σ 的高斯函数。
from PIL import Image
from pylab import *
from scipy.ndimage import filters
import numpy
def imx(im, sigma):
imgx = zeros(im.shape)
filters.gaussian_filter(im, sigma, (0, 1), imgx)
return imgx
def imy(im, sigma):
imgy = zeros(im.shape)
filters.gaussian_filter(im, sigma, (1, 0), imgy)
return imgy
def mag(im, sigma):
imgmag = 255 - numpy.sqrt(imgx ** 2 + imgy ** 2)
return imgmag
if __name__=='__main__':
im = array(Image.open('D:\\123\图像处理\Image Processing\Image Processing\\raccoon.jpg').convert('L'))
figure()
gray()
sigma = [2, 5, 10]
for i in sigma:
subplot(3, 4, 4*(sigma.index(i))+1)
axis('off')
imshow(im)
imgx=imx(im, i)
subplot(3, 4, 4*(sigma.index(i))+2)
axis('off')
imshow(imgx)
imgy=imy(im, i)
subplot(3, 4, 4*(sigma.index(i))+3)
axis('off')
imshow(imgy)
imgmag=mag(im, i)
subplot(3, 4, 4*(sigma.index(i))+4)
axis('off')
imshow(imgmag)
show()
4.3 形态学:对象计数
形态学(或数学形态学)是度量和分析基本形状的图像处理方法的基本框架与集合。形态学通常用于处理二值图像,但是也能够用于灰度图像。
计算图像中的对象个数与图像开操作可以通过下面的代码实现:
from PIL import Image
from numpy import *
from scipy.ndimage import measurements, morphology
from pylab import *
if __name__=='__main__':
im = array(Image.open('FigP0918.tif').convert('L'))
gray()
subplot(221)
imshow(im)
axis('off')
title('(a)Original Image')
im = (im > 128)
labels, nbr_objects = measurements.label(im)
print("Number of objects:", nbr_objects)
subplot(222)
imshow(labels)
axis('off')
title('(b)Tag Image')
im_open = morphology.binary_opening(im, ones((9, 5)), iterations=2)
subplot(223)
imshow(im_open)
axis('off')
title('(c)Original Image After Open Operation')
labels_open, nbr_objects_open = measurements.label(im_open)
print
"Number of objects:", nbr_objects_open
subplot(224)
imshow(labels_open)
axis('off')
title('(d)Tag Image After Open Operation')
show()
binary_opening函数第二个参数指定一个数组结构元素。该数组表示以一个像素为中心时,使用哪些相邻像素。