读取一幅图像,我们可以使用
from PIL import Image
pil_im=Image.open('empire.jpg')
上述代码的返回值pil_im是一个PIL图像对象。
读取图像同时转化为灰度图像,转换代码如下
pil_im=Image.open('empire.jpg').convert('L')
下面的例子从文件名列表(filelist)中读取所有的图像文件,并转换成 JPEG 格式。实现代码如下
from PIL import Image
import os
for infile infilelist:
outfile=os.path.splitext(infile)[0]+".jpg"
if infile !=outfile:
try:
Image.open(infile).save(outfile)
except IOError:
print "cannot convert",infile
同时使用 PIL 可以很方便地创建图像的缩略图,thumbnail() 方法接受一个元组参数,然后将图像转换成符合元组参数指定大小的缩略图。
实现如下
pil_im.thumbnail((128,128))
使用 crop() 方法可以从一幅图像中裁剪指定区域:
box = (100,100,400,400)
region = pil_im.crop(box)
该区域使用四元组来指定。四元组坐标依次是(左,上,右,下),PIL 中指定
坐标系的左上角坐标为(0,0)。
要调整一幅图像的尺寸,我们可以调用 resize() 方法,该方法的参数是一个元组,
用来指定新图像的大小。
调整尺寸
out = pil_im.resize((128,128))
要旋转一幅图像,可以使用逆时针方式表示旋转角度,然后调用 rotate() 方法:
out = pil_im.rotate(45)
我们处理数学运算、绘制图表,或者在图像上绘制点、直线和曲线时,Matplotlib
是个很好的类库,具有比 PIL 更强大的绘图功能。Matplotlib 可以绘制出高质量的
图表,就像本书中的许多插图一样。
下面我们用一些点和线来绘制图像的例子,
from PIL import Image
from pylab import *
# 读取图像到数组中
im = array(Image.open())
# 绘制图像
imshow(im)
# 一些点
x = [1000,4000,1000,4000]
y = [500,500,3000,3000]
# 使用红色星状标记绘制点
plot(x,y,'r*')
# 绘制连接前两个点的线
plot(x[:2],y[:2])
# 添加标题,显示绘制的图像
title('Plotting: "empire.jpg"')
show()
每个脚本只能调用一次show()命令,而且通常是在脚本的结尾调用。
图像的轮廓和直方图,绘制图像轮廓:
from PIL import Image
from pylab import *
# 读取图像到数组中
im = array(Image.open('2.jpg').convert('L'))
# 新建一个图像
figure()
# 不使用颜色信息
gray()
# 在原点的左上角显示轮廓图像
contour(im, origin='image')
axis('equal')
show()
hist()只接受一维数组作为输入,所以在绘制图像直方图之前,必须先对图像进行压平处理。
绘制一幅图像,然后等待用户点击三次,依序将这些点击的坐标[x,y]自动保存在x列表里:
实现代码如下
from PIL import Image
from pylab import *
im = array(Image.open('8.jpg'))
imshow(im)
print ('Please click 3 points')
x = ginput(3)
print ('you clicked:',x)
show()
是非常有名的 Python 科学计算工具包,其中包含了大量有用的思想,NumPy 中的数组对象几乎贯穿用于本书的所有例子中 1 数组对象可以帮助你实现数组中重要的操作。
Numpy中的数组对象是多维的,但是元素必须有相同的数据类型。
im = array(Image.open('empire.jpg'))
print im.shape, im.dtype
im = array(Image.open('empire.jpg').convert('L'),'f')
print im.shape, im.dtype
将图像读入 NumPy 数组对象后,我们可以对它们执行任意数学操作。一个简单的例
子就是图像的灰度变换。
下面介绍一些简单的灰度变换的例子
from PIL import Image
from numpy import *
from pylab import *
im=array(Image.open('').convert('L'))
print(int(im.min()),int(im.max()))
im2=255-im #对图像进行反向处理
print(int(im2.min()),int(im2.max())) #查看最大/最小元素
im3=(100.0/255)*im+100 #将图像像素值变换到100...200区间
print(int(im3.min()),int(im3.max()))
im4=255.0*(im/255.0)**2 #对像素值求平方后得到的图像
print(int(im4.min()),int(im4.max()))
figure()
gray()
subplot(131)
imshow(im2)
axis('off')
title(r'$f(x)=255-x$')
subplot(132)
imshow(im3)
axis('off')
title(r'$f(x)=\frac{100}{255}x+100$')
subplot(133)
imshow(im4)
axis('off')
title(r'$f(x)=255(\frac{x}{255})^2$')
show()
实验结果如下所示
第一个例子将灰度图像进行反相处理;第二个例子将图像的像素值变换;第三个例子对图像使用二次函数变换。
图像中像素的最小值和最大值
NumPy 的数组对象是我们处理图像和数据的主要工具。想要对图像进行缩放处理没
有现成简单的方法。我们可以使用之前 PIL 对图像对象转换的操作,写一个简单的
用于图像缩放的函数。
def imresize(im,sz):
""" Resize an image array using PIL. """
pil_im = Image.fromarray(uint8(im))
return array(pil_im.resize(sz))
图像灰度变换中一个非常有用的例子就是直方图均衡化,直方图均衡化是指将一幅
图像的灰度直方图变平,使变换后的图像中每个灰度值的分布概率都相同。
from PIL import Image
from pylab import *
from PCV.tools import imtools
# 添加中文字体支持
from matplotlib.font_manager import FontProperties
font = FontProperties(fname=r"", size=14)
im = array(Image.open('14.jpg').convert('L'))
# 打开图像,并转成灰度图像
#im = array(Image.open('').convert('L'))
im2, cdf = imtools.histeq(im)
figure()
subplot(2, 2, 1)
axis('off')
gray()
title(u'原始图像', fontproperties=font)
imshow(im)
subplot(2, 2, 2)
axis('off')
title(u'直方图均衡化后的图像', fontproperties=font)
imshow(im2)
subplot(2, 2, 3)
axis('off')
title(u'原始直方图', fontproperties=font)
#hist(im.flatten(), 128, cumulative=True, normed=True)
hist(im.flatten(), 128, density=True)
subplot(2, 2, 4)
axis('off')
title(u'均衡化后的直方图', fontproperties=font)
#hist(im2.flatten(), 128, cumulative=True, normed=True)
hist(im2.flatten(), 128, density=True)
show()
图像平均操作是减少图像噪声的一种简单方式,通常用于艺术特效。我们可以简单地从图像列表中计算出一幅平均图像。我们可以将这些图像简单地相加,然后除以图像的数目,来计算平均图像。
def compute_average(imlist):
""" 计算图像列表的平均图像"""
# 打开第一幅图像,将其存储在浮点型数组中
averageim = array(Image.open(imlist[0]), 'f')
for imname in imlist[1:]:
try:
averageim += array(Image.open(imname))
except:
print imname + '...skipped'
averageim /= len(imlist)
# 返回uint8 类型的平均图像
return array(averageim, 'uint8')
PCA(Principal Component Analysis,主成分分析)是一个非常有用的降维技巧。它可以在使用尽可能少维数的前提下,尽量多地保持训练数据的信息,在此意义上是一个最佳技巧。即使是一幅 100×100像素的小灰度图像,也有10000维,可以看成10000维空间中的一个点。一兆像素的图像具有百万维。由于图像具有很高的维数,在许多计算机视觉应用中,我们经常使用降维操作。PCA 产生的投影矩阵可以被视为将原始坐标变换到现有的坐标系,坐标系中的各个坐标按照重要性递减排列。
为了对图像数据进行 PCA 变换,图像需要转换成一维向量表示。我们可以使用 NumPy 类库中的flatten()方法进行变换。
将变平的图像堆积起来,我们可以得到一个矩阵,矩阵的一行表示一幅图像。在计算主方向之前,所有的行图像按照平均图像进行了中心化。我们通常使用 SVD(Singular Value Decomposition,奇异值分解)方法来计算主成分;但当矩阵的维数很大时,SVD 的计算非常慢,所以此时通常不使用 SVD 分解。
该函数首先通过减去每一维的均值将数据中心化,然后计算协方差矩阵对应最大特征值的特征向量,此时可以使用简明的技巧或者 SVD 分解。这里我们使用了 range() 函数,该函数的输入参数为一个整数 n,函数返回整数 0…(n-1) 的一个列表。你也可以使用 arange() 函数来返回一个数组,或者使用 xrange() 函数返回一个产生器(可能会提升速度)。我们在本书中贯穿使用range() 函数。
如果数据个数小于向量的维数,我们不用 SVD 分解,而是计算维数更小的协方差矩阵 XXT 的特征向量。通过仅计算对应前 k(k 是降维后的维数)最大特征值的特征向量,可以使上面的 PCA 操作更快。由于篇幅所限,有兴趣的读者可以自行探索。矩阵 V 的每行向量都是正交的,并且包含了训练数据方差依次减少的坐标方向。
是建立在 NumPy 基础上,用于数值运算的开源工具包。SciPy 提供很多高效的操作,可以实现数值积分、优化、统计、信号处理,以及对我们来说最重要的图像处理功能。
图像的高斯模糊是非常经典的图像卷积例子。本质上,图像模糊就是将(灰度)图
像 I 和一个高斯核进行卷积操作:
其中*表示卷积操作;Gδ是标准差为δ的二位高斯核。
from PIL import Image
from numpy import *
from pylab import *
from scipy.ndimage import filters
# 添加中文字体支持
from matplotlib.font_manager import FontProperties
font=FontProperties(fname=r"c:\windows\fonts\SimSun.ttc",size=14)
im=array(Image.open('14.jpg').convert('L'))
figure()
gray()
axis('off')
subplot(141)
axis('off')
title(u'原图',fontproperties=font)
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(u'标准差为'+imNum,fontproperties=font)
imshow(im2)
show()
由实验可以看出,随着δ值增加,图像逐渐变模糊,图像细节丢失越多。
整本书中可以看到,在很多应用中图像强度的变化情况是非常重要的信息。强度的
变化可以用灰度图像 I(对于彩色图像,通常对每个颜色通道分别计算导数)的 x 和 y 方向导数 Ix 和 Iy 进行描述。
梯度大小描述了图像强度变化的强弱,
度角度描述了图像在每个点(像素)上强度变化最大的方向。
我们可以用离散近似的方式来计算图像的导数。图像导数大多数可以通过卷积简单地实现:
prewitt滤波器:
sobel滤波器:
导数滤波器可以使用scipy.ndimage.filters模块的标准卷积操作来简单地实现,
比如,使用Sobel滤波器计算x和y方向导数以及梯度大小。
实现代码如下
from PIL import Image
from pylab import *
from scipy.ndimage import filters
import numpy
# 添加中文字体支持
from matplotlib.font_manager import FontProperties
font=FontProperties(fname=r"c:\windows\fonts\SimSun.ttc",size=14)
im=array(Image.open('14.jpg').convert('L'))
gray()
subplot(141)
axis('off')
title(u'(a)原图',fontproperties=font)
imshow(im)
# sobel derivative filters
imx=zeros(im.shape)
filters.sobel(im,1,imx)
subplot(142)
axis('off')
title(u'(b)x方向差分',fontproperties=font)
imshow(imx)
imy=zeros(im.shape)
filters.sobel(im,0,imy)
subplot(143)
axis('off')
title(u'(c)y方向差分',fontproperties=font)
imshow(imy)
mag=255-numpy.sqrt(imx**2+imy**2)
subplot(144)
title(u'(d)梯度幅值',fontproperties=font)
axis('off')
imshow(mag)
show()
在导数图像中,正导数显示为亮的像素,负倒数显示为暗的像素,灰色区域表示导数的值接近于零。
形态学(或数学形态学)是度量和分析基本形状的图像处理方法的基本框架与集合。形态学通常用于处理二值图像,但是也能够用于灰度图像。二值图像是指图像的每个像素只能取两个值,通常是0和1。二值图像通常是,在计算物体的数目,或者度量其大小时,对一幅图像进行阈值化后的结果。
scipy.ndimage中的morphology模块可以实现形态学操作,可以使用scipy.ndimage中的measurements模块来实现二值图像的计数和度量功能。
首先载入该图像,通过阈值化方式确保该图像是二值图像。通过和1相乘,脚本将布尔数组转换成二进制表示。
其次使用label()函数寻找单个的物体,并且按照它们属于哪个对象将证书标签给像素赋值。
再然后图像灰度值表示对象的标签。可以看到,在一些对象之间是有一些小的连接。进行二进制开操作,我们可以将其移除:
from scipy.ndimage import measurements,morphology
# 载入图像,然后使用阈值化操作,以保证处理的图像为二值图像
im = array(Image.open('houses.png').convert('L'))
im = 1*(im<128)
labels, nbr_objects = measurements.label(im)
print "Number of objects:", nbr_objects
binary_opening()函数的第二个参数指定一个数组元素。该数组表示以一个像素为中心时,使用哪些相邻像素。参数iterations决定执行该操作的次数。
# 形态学开操作更好地分离各个对象
im_open = morphology.binary_opening(im,ones((9,5)),iterations=2)
labels_open, nbr_objects_open = measurements.label(im_open)
print "Number of objects:", nbr_objects_open
通过程序来实现功能
from PIL import Image
from numpy import *
from scipy.ndimage import measurements, morphology
from pylab import *
""" This is the morphology counting objects example in Section 1.4. """
# 添加中文字体支持
from matplotlib.font_manager import FontProperties
font = FontProperties(fname=r"c:\windows\fonts\SimSun.ttc", size=14)
# load image and threshold to make sure it is binary
figure()
gray()
im = array(Image.open('jishu.jpg').convert('L'))
subplot(221)
imshow(im)
axis('off')
title(u'原图', fontproperties=font)
im = (im < 128)
labels, nbr_objects = measurements.label(im)
print ("Number of objects:", nbr_objects)
subplot(222)
imshow(labels)
axis('off')
title(u'标记后的图', fontproperties=font)
# morphology - opening to separate objects better
im_open = morphology.binary_opening(im, ones((9, 5)), iterations=2)
subplot(223)
imshow(im_open)
axis('off')
title(u'开运算后的图像', fontproperties=font)
labels_open, nbr_objects_open = measurements.label(im_open)
print ("Number of objects:", nbr_objects_open)
subplot(224)
imshow(labels_open)
axis('off')
title(u'开运算后进行标记后的图像', fontproperties=font)
show()
实验分析,可见随着迭代次数iterations值增加,对象数目逐渐减少。