目录

1.1 PIL:Python图像处理类库

1.1.1 转换图像格式

1.1.2 创建缩略图

1.1.3 复制和粘贴图像区域

1.1.4 调整尺寸和旋转

1.2 Matplotlib

1.2.1 绘制图像、点和线

1.2.2 图像轮廓和直方图 

1.2.3 交互式标注 

1.3 NumPy

1.3.1 图像数组表示

1.3.2 灰度变换

1.3.3 图像缩放 

1.3.4 直方图均衡化

1.3.5 图像平均

1.3.6 图像的主成分分析(PCA)

1.3.7 使用pickle模块

1.4 SciPy

1.4.1 图像模糊

1.4.2 图像导数

1.4.3 形态学:对象计数


        本章讲解操作和处理图像的基础知识,将通过大量示例介绍处理图像所需的Python工具包,并介绍用于读取图像、图像转换和缩放、计算导数、画图和保存结果等的基本工具。

1.1 PIL:Python图像处理类库

        PIL(Python Imaging Library Python,图像处理类库)提供了通用的图像处理功能,以及大量有用的基本图像操作,比如图像缩放、裁剪、旋转、颜色转换等

        PIL 中最重要的模块为Image。要读取一幅图像,可以使用:

from PIL import Image
pil_im = Image.open('empire.jpg')

上述代码的返回值pil_im是一个PIL图像对象。

        要读取一幅图像,并将其转换成灰度图像,只需要加上convert('L')

pil_im=Image.open('empire.jpg').convert('L')

1.1.1 转换图像格式

        通过save() 方法,PIL 可以将图像保存成多种格式的文件。

        下面的例子从文件名列表(filelist)中读取所有的图像文件,并转换成JPEG 格式:

from PIL import Image
import os
for infile in filelist:
    outfile = os.path.splitext(infile)[0] + ".jpg"
    if infile != outfile:
        try:
            Image.open(infile).save(outfile)
        except IOError:
            print "cannot convert", infile

        PIL的open() 函数用于创建PIL 图像对象,save() 方法用于保存图像到具有指定文件名的文件

        PIL 是个足够智能的类库,可以根据文件扩展名来判定图像的格式。PIL 函数会进行简单的检查,如果文件不是JPEG 格式,会自动将其转换成JPEG 格式;如果转换失败,它会在控制台输出一条报告失败的消息。

1.1.2 创建缩略图

        使用PIL 可以很方便地创建图像的缩略图。thumbnail() 方法接受一个元组参数(该参数指定生成缩略图的大小),然后将图像转换成符合元组参数指定大小的缩略图。

pil_im.thumbnail((128,128))

1.1.3 复制和粘贴图像区域

        使用crop() 方法可以从一幅图像中裁剪指定区域:

box = (100,100,400,400)
region = pil_im.crop(box)

        该区域使用四元组来指定。四元组的坐标依次是(左,上,右,下)。PIL 中指定坐标系的左上角坐标为(0,0)

1.1.4 调整尺寸和旋转

        要调整一幅图像的尺寸,我们可以调用resize() 方法。该方法的参数是一个元组,用来指定新图像的大小:

out = pil_im.resize((128,128))

        要旋转一幅图像,可以使用逆时针方式表示旋转角度,然后调用rotate() 方法:

out = pil_im.rotate(45)

1.2 Matplotlib

        Matplotlib 可以绘制出高质量的图表,就像本书中的许多插图一样。

1.2.1 绘制图像、点和线

         下面是用几个点和一条线绘制图像的例子:

from PIL import Image
from pylab import *
# 读取图像到数组中
im = array(Image.open(r'C:\Users\23675\Pictures\ren.jpg'))
# 绘制图像
imshow(im)
# 一些点
x = [100,100,400,400]
y = [200,500,200,500]
# 使用红色星状标记绘制点
plot(x,y,'r*')
# 绘制连接前两个点的线
plot(x[:2],y[:2])
# 添加标题,显示绘制的图像
title('Plotting: "ren.jpg"')
show()

        上面的代码首先绘制出原始图像,然后在x 和y 列表中给定点的x 坐标和y 坐标上绘制出红色星状标记点,最后在两个列表表示的前两个点之间绘制一条线段(默认为蓝色)。 

        show() 命令首先打开图形用户界面(GUI),然后新建一个图像窗口。该图形用户界面会循环阻断脚本,然后暂停,直到最后一个图像窗口关闭。在每个脚本里,你只能调用一次show() 命令,而且通常是在脚本的结尾调用。

        注意,在PyLab 库中,我们约定图像的左上角为坐标原点。

        加上下列命令可以使坐标轴不显示:

axis('off')

 

控制图像的颜色和样式:

1.2.2 图像轮廓和直方图 

        图像的直方图用来表征该图像像素值的分布情况。

        因为绘制轮廓需要对每个坐标[x, y] 的像素值施加同一个阈值,所以首先需要将图像灰度化:

from PIL import Image
from pylab import *
# 读取图像到数组中
im = array(Image.open(r'C:\Users\23675\Pictures\ren.jpg').convert('L'))
# 新建一个图像
figure()
# 不使用颜色信息
gray()
# 在原点的左上角显示轮廓图像
contour(im, origin='image')
axis('equal')
axis('off')

figure()
hist(im.flatten(),128)
show()

1.2.3 交互式标注 

        PyLab 库中的ginput() 函数就可以实现交互式标注。

from PIL import Image
from pylab import *
im = array(Image.open(r'C:\Users\23675\Pictures\ren.jpg'))
imshow(im)
print ('Please click 3 points')
x = ginput(3)
print ('you clicked:',x)
show()

        上面的脚本首先绘制一幅图像,然后等待用户在绘图窗口的图像区域点击三次。程序将这些点击的坐标[x, y] 自动保存在x列表里。 

1.3 NumPy

        是非常有名的Python 科学计算工具包,其中包含了大量有用的思想,比如数组对象(用来表示向量、矩阵、图像等)以及线性代数函数。

1.3.1 图像数组表示

        NumPy 中的数组对象是多维的,可以用来表示向量、矩阵和图像。

        一个数组对象很像一个列表(或者是列表的列表),但是数组中所有的元素必须具有相同的数据类型。

from PIL import Image
from numpy import *

im = array(Image.open(r'C:\Users\23675\Pictures\ren.jpg'))
print (im.shape, im.dtype)
im = array(Image.open(r'C:\Users\23675\Pictures\ren.jpg').convert('L'),'f')
print (im.shape, im.dtype)

        每行的第一个元组表示图像数组的大小(行、列、颜色通道),紧接着的字符串表示数组元素的数据类型。

        图像通常被编码成无符号八位整数(uint8)

        位于坐标i、j,以及颜色通道k 的像素值可以像下面这样访问:

value = im[i,j,k]

        多个数组元素可以使用数组切片方式访问。切片方式返回的是以指定间隔下标访问该数组的元素值。下面是有关灰度图像的一些例子:

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]) # 倒数第二行

1.3.2 灰度变换

        将图像读入NumPy 数组对象后,我们可以对它们执行任意数学操作。一个简单的例子就是图像的灰度变换。

from PIL import Image
from numpy import *
from pylab import *
 
im=array(Image.open(r'C:\Users\23675\Pictures\ren.jpg').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(141)
imshow(im)
axis('off')
title(r'$f(x)=x$')

subplot(142)
imshow(im2)
axis('off')
title(r'$f(x)=255-x$')
 
subplot(143)
imshow(im3)
axis('off')
title(r'$f(x)=\frac{100}{255}x+100$')
 
subplot(144)
imshow(im4)
axis('off')
title(r'$f(x)=255(\frac{x}{255})^2$')
 
show()

        第一个例子将灰度图像进行反相处理;第二个例子将图像的像素值变换;第三个例子对图像使用二次函数变换。图像中像素的最小值和最大值 

1.3.3 图像缩放 

        NumPy 的数组对象是我们处理图像和数据的主要工具。想要对图像进行缩放处理没有现成简单的方法。我们可以使用之前PIL 对图像对象转换的操作,写一个简单的用于图像缩放的函数。

def imresize(im,sz):
    """ 使用PIL 对象重新定义图像数组的大小"""
    pil_im = Image.fromarray(uint8(im))
    return array(pil_im.resize(sz))

1.3.4 直方图均衡化

        直方图均衡化是指将一幅图像的灰度直方图变平,使变换后的图像中每个灰度值的分布概率都相同。

        直方图均衡化通常是对图像灰度值进行归一化的一个非常好的方法,并且可以增强图像的对比度。

        直方图均衡化的变换函数是图像中像素值的累积分布函数(cumulative distribution function,简写为cdf,将像素值的范围映射到目标范围的归一化操作)。

        下面的函数是直方图均衡化的具体实现:

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

        该函数有两个输入参数,一个是灰度图像,一个是直方图中使用小区间的数目。

from PIL import Image
from pylab import *
from PCV.tools import imtools
# 添加中文字体支持
from matplotlib.font_manager import FontProperties
font = FontProperties(fname=r"C:\Windows\Fonts\simsun.ttc", size=14)


im = array(Image.open(r'C:\Users\23675\Pictures\ren.jpg').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()

1.3.5 图像平均

         图像平均操作是减少图像噪声的一种简单方式。可以简单地从图像列表中计算出一幅平均图像。假设所有的图像具有相同的大小,我们可以将这些图像简单地相加,然后除以图像的数目,来计算平均图像。

        用于计算平均图像:

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')

1.3.6 图像的主成分分析(PCA)

        PCA(Principal Component Analysis,主成分分析)是一个非常有用的降维技巧。它可以在使用尽可能少维数的前提下,尽量多地保持训练数据的信息,在此意义上是一个最佳技巧。

        PCA 产生的投影矩阵可以被视为将原始坐标变换到现有的坐标系,坐标系中的各个坐标按照重要性递减排列。

        为了对图像数据进行PCA 变换,图像需要转换成一维向量表示。我们可以使用NumPy 类库中的flatten() 方法进行变换。

        PCA 操作的代码:

from PIL import Image
from numpy import *

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] # 仅仅返回前nun_data 维的数据才合理
    # 返回投影矩阵、方差和均值
    return V,S,mean_X

        该函数首先通过减去每一维的均值将数据中心化,然后计算协方差矩阵对应最大特征值的特征向量,此时可以使用简明的技巧或者SVD 分解。

        range() 函数,该函数的输入参数为一个整数n,函数返回整数0...(n-1) 的一个列表。

        如果数据个数小于向量的维数,我们不用SVD分解,而是计算维数更小的协方差矩阵\(XX^T\)的特征向量。通过仅计算对应前k(k 是降维后的维数)最大特征值的特征向量,可以使上面的PCA 操作更快。

1.3.7 使用pickle模块

        pickle 模块可以接受几乎所有的Python 对象,并且将其转换成字符串表示,该过程叫做封装(pickling)。从字符串表示中重构该对象,称为拆封(unpickling)。这些字符串表示可以方便地存储和传输。

1.4 SciPy

        SciPy是建立在NumPy 基础上, 用于数值运算的开源工具包。SciPy 提供很多高效的操作,可以实现数值积分、优化、统计、信号处理,以及图像处理功能。

1.4.1 图像模糊

        图像的高斯模糊是非常经典的图像卷积例子。本质上,图像模糊就是将(灰度)图像\(I\)和一个高斯核进行卷积操作

\(I_σ=I*G_σ\)

        其中* 表示卷积操作;\(G_σ\)是标准差为σ 的二维高斯核, 定义为:

\(G_{\sigma}=\frac{1}{2\pi\sigma}e^{-(x^{2}+y^{2})/2\sigma^{2}}\)

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(r'c:users\23675\pictures\s.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()

        由实验可以看出,随着δ值增加,图像逐渐变模糊,图像细节丢失越多。 

1.4.2 图像导数

         整本书中可以看到,在很多应用中图像强度的变化情况是非常重要的信息。强度的变化可以用灰度图像\(I\)(对于彩色图像,通常对每个颜色通道分别计算导数)的x和y方向导数\(I_x\)和\(I_y\)进行描述。

图像的梯度向量为\(\nabla I=|[I_{x},I_{y}]|^{r}\)

梯度的大小:描述了图像强度变化的强弱,\(\begin{vmatrix}\nabla I\end{vmatrix}=\sqrt{I_x^2+I_y^2}\)

梯度的角度:描述了图像中在每个点(像素)上强度变化最大的方向,\(\alpha=arctan2(I_y,I_x)\)

        可以用离散近似的方式来计算图像的导数。图像导数大多数可以通过卷积简单地实现: 

\(I_x=I*D_x\)和\(I_y=I*D_y\)

 对于\(D_x\)和\(D_y\),通常选择Prewitt 滤波器:

\(D_x=\begin{bmatrix}-1&0&1\\[2ex]-1&0&1\\[2ex]-1&0&1\end{bmatrix}\)和\(D_{y}=\begin{bmatrix}-1&-1&-1\\0&0&0\\1&1&1\end{bmatrix}\)

 或者Sobel 滤波器

\(D_x=\begin{bmatrix}-1&0&1\\[0.3em]-2&0&2\\[0.3em]-1&0&1\end{bmatrix}\)和\(D_y=\begin{bmatrix}-1&-2&-1\\0&0&0\\1&2&1\end{bmatrix}\)

        这些导数滤波器可以使用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(r'c:users\23675\pictures\s.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()

        在导数图像中,正导数显示为亮的像素,负倒数显示为暗的像素,灰色区域表示导数的值接近于零。 

1.4.3 形态学:对象计数

        形态学(或数学形态学)是度量和分析基本形状的图像处理方法的基本框架与集合。形态学通常用于处理二值图像,但是也能够用于灰度图像。二值图像是指图像的每个像素只能取两个值,通常是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 open)操作,我们可以将其移除: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(r'c:users\23675\pictures\s.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()

 

更多推荐