Matplotlib可以处理数学运算,绘制图表,或者在图像上绘制点,直线和曲线时, Matplotlib是比PIL更强大的绘图工具,可以绘制出高质量的图表。
点和线可以表示一些事物,比如对应点,兴趣点以及检测的物体
from PIL import Image
from pylab import *
#1.2.1绘制图像点和线
# 读取图像到数组中
im =array(Image.open('D:\PyCharmProjects\one\image\JMU.jpg'))
#绘制图像
imshow(im)
#一些点
x=[100,100,400,400]
y=[200,500,200,500]
#使用红色星状标记绘制点
plot(x,y,'r*')
#绘制连接前两个点的线
plot(x[:2],y[:2])
#添加标题,显示绘制的图像
title('Plotting:"JMU.jpg"')
# # 坐标不显示
# axis('off')
show()
运行结果
绘图时,可以控制图像的颜色和样式,一些短命令如下面三个表
表1:用PyLab库绘图的基本颜色格式命令
表2:用PyLab库绘图的基本线型格式命令
绘制轮廓需要对每个坐标[x,y]的像素值施加同一个阈值,所以要先灰度化
from PIL import Image
from pylab import *
##1.2.2图形轮廓和直方图
# 读取图像到数组中,并转成灰度图像
im =array(Image.open('D:\PyCharmProjects\one\image\JMU.jpg').convert('L'))
#新建一个图像
figure()
#不使用颜色信息
gray()
#在原点的左上角显示轮廓图像
contour(im,origin='image')
axis('equal')
axis('off')
figure()
hist(im.flatten(),128)
show()
结果如下
这里用PIL的convert()方法将图像转换成灰度图像。
图像的直方图用来表征该图像像素值的分布情况。用一定数目的小区间(bin)来指定表征像素值的范围,每个小区间会得到落入该小区间表示范围的像素数目。该(灰度)图像的直方图可以使用 hist() 函数绘制:
figure()
hist(im.flatten(),128)
show()
hist() 函数的第二个参数指定小区间的数目。需要注意的是,因为 hist() 只接受一维数组作为输入,所以我们在绘制图像直方图之前,必须先对图像进行压平处理。flatten() 方法将任意数组按照行优先准则转换成一维数组。图为等轮廓线和直方图图像。
用户和某些应用交互,如一副图像中标记一些点,或者标注一些训练数据等。
from PIL import Image
from pylab import *
##1.2.3交互式标注
im=array(Image.open('D:\PyCharmProjects\one\image\JMU.jpg'))
imshow(im)
print('Please click 3 points')
x=ginput(3)
print('you clicked:',x)
show()
NumPy是Python科学计算工具包,如数组对象(用来表示向量,矩阵,图像等)以及线性代数函数。
以及数组对象(如矩阵乘积,转置,解方程系统,向量乘积和归一化),这为图像变形,对变化进行建模,图像分类,图像聚类等提供了基础。
当载入图像时,我们通过调用array()方法将图像转换成NumPy的数组对象(多维的,可以用来表示向量,矩阵和图像)。一个数组对象很像一个列表(或者列表的列表),但是数组中所有元素必须具有相同的数据类型。
from PIL import Image
from pylab import *
#1.3.1图像数组表示
im=array(Image.open('D:\PyCharmProjects\one\image\JMU.jpg'))
print(im.shape,im.dtype)
im=array(Image.open('D:\PyCharmProjects\one\image\JMU.jpg').convert('L'),'f')
print(im.shape,im.dtype)
结果如下
图像载入并将其转换成数组中,数组类型是uint8。
对图像进行灰度处理,在创建数组时使用额外的参数"f",可以将数据类型转换成浮点型
考虑任意f,将0…255区间(或者0…1区间)映射到自身(输出区间的范围和输入区间的范围相同)
from PIL import Image
from pylab import *
#1.3.2灰度变换
im=array(Image.open('D:\PyCharmProjects\one\image\JMU.jpg').convert('L'))
im2=255-im #对图像进行反向处理
im3=(100.0/255)*im+100 # 将图像像素值变换到100-200区间
im4=255.0*(im/255.0)**2 # 对图像像素值求平方后得到的图像
print (int(im.min()),int(im.max()))
from PIL import Image
from pylab import *
# 1.3.3图像缩放
def imressize(im,sz):
pil_im=Image.fromarray(uint8(im))
return array(pil_im.resize(sz))
直方图均衡化是指将一副图像的灰度直方图变平,使变换后的图像中每个灰度值的分布概率都相同。在对图像做进一步处理之前,直方图均衡化通常是对图像灰度值进行归一化的一个非常好的方法,并且可以增强图像的对比度。
在这种情况下,直方图均衡化的变换函数是图像中像素值的累计分布函数(cdf,将像素的范围映射到目标范围的归一化操作)。
from PIL import Image
import matplotlib.pyplot as plt
import numpy as np
def histogram_equalization(img: np, nbr_bins=256):
imhist, bins = np.histogram(img.flatten())
cdf = imhist.cumsum() # 累计分布函数
# 归一化
cdf = 255 * cdf / cdf[-1]
# 使用累积分布函数进行线性插值,计算新的像素值
img2 = np.interp(img.flatten(), bins[:-1], cdf)
return img2.reshape(img.shape), cdf
img = Image.open(r"D:\PyCharmProjects\one\image\JMU.jpg").convert('L')
img2, cdf = histogram_equalization(np.array(img))
plt.figure()
plt.gray()
# 绘制子图
plt.subplot(232)
# 变换函数
plt.plot(cdf)
plt.subplot(231)
plt.hist(np.array(img).flatten(), 256)
# 关闭坐标轴,对上一个子图有效
plt.axis('off')
plt.subplot(233)
plt.hist(np.array(img2).flatten(), 256)
plt.axis('off')
plt.subplot(234)
plt.imshow(img)
plt.axis('off')
plt.subplot(236)
plt.imshow(img2)
plt.axis('off')
# 保存绘制图像
plt.savefig("D:\PyCharmProjects\one\image\JMUU.jpg")
plt.show()
结果如下
左侧为原始图像和直方图,中间为灰度变换函数,右侧为直方图均衡化后的图像和相应直方图
图像平均操作是减少图像噪声的一种简单操作
from PIL import Image
from pylab import *
# 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)
# 返回unit8类型的平均像素
return array(averageim,'uint8')
图像的主成分分析(PCA)是一个非常有用的降维技巧。它可以在使用尽可能少维数的前提下,尽量
多地保持训练数据的信息,在此意义上是一个最佳技巧。即使是一幅100×100像素的小灰度图像,也有10000维,
可以看10000维空间中的一个点。一兆像素的图像具有百万维。由于图像具有很高的维数,在许多计算机视觉应用中,我们经常使用降维操作。PCA产生的投影矩阵可以被视为将原始坐标变换到现有的坐标系,坐标系中的各个坐标按照重要性递减排列。
为了对图像数据进行PCA 变换,图像需要转换成一维向量表示。我们可以使用NumPy 类库中的 flatten()方法进行变换。
将变平的图像堆积起来,我们可以得到一个矩阵,矩阵的一行表示一副图像。在计算主方向之前
前,所有的行图像按照平均图像进行了中心化。我们通常使用SVD方法来计算主成分。但当矩阵的
维数很大时,SVD 的计算非常慢,所以此时通常不使用 SVD分解。
下面是PCA操作代码
from PIL import Image
from pylab import *
def pca(X):
"""主成分分析:
输入:矩阵X,其中该矩阵中存储训练数据,每一行为一条训数据
返回:投影矩阵(按照维度的重要性排序),方差和均值
"""
#获取维数
num_data,dim=X.shape
# 数据中心化
mean_X=X.mean(axios=0)
X=X-mean_X
# PCA使用紧致技巧
if dim>num_data:
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
# PCA-使用SVD方法
else:
U,S,V=linalg.svd(X)
V=V[:num_data]# 仅仅返回前num_data维的数据才合理
# 返回投影矩阵,方差和均值
return V,S,mean_X
该函数首先通过减去每一维的均值将数据中心化,然后计算协方差矩阵对应最大特征值的特征向量,此时可以使用简明的技巧或者 SVD 分解。这里我们使用了 range() 函数,该函数的输入参数为一个整数 n,函数返回整数 0…(n-1) 的一个列表。你也可以使用 arange() 函数来返回一个数组,或者使用 xrange() 函数返回一个产生器(可能会提升速度)。我们在本书中贯穿使用 range() 函数。
如果数据个数小于向量的维数,我们不用 SVD 分解,而是计算维数更小的协方差矩阵 XXT 的特征向量。通过仅计算对应前 k(k 是降维后的维数)最大特征值的特征向量,可以使上面的 PCA 操作更快。由于篇幅所限,有兴趣的读者可以自行探索。矩阵 V 的每行向量都是正交的,并且包含了训练数据方差依次减少的坐标方向。
接下来对字体图像进行PCA变换
from PIL import Image
from numpy import *
from pylab import *
import pca
im = array(Image.open(imlist[0])) # 打开一幅图像,获取其大小
m,n = im.shape[0:2] # 获取图像的大小
imnbr = len(imlist) # 获取图像的数目
# 创建矩阵,保存所有压平后的图像数据
immatrix = array([array(Image.open(im)).flatten()
for im in imlist],'f')
# 执行 PCA 操作
V,S,immean = pca.pca(immatrix)
# 显示一些图像(均值图像和前 7 个模式)
figure()
gray()
subplot(2,4,1)
imshow(immean.reshape(m,n))
for i in range(7):
subplot(2,4,i+2)
imshow(V[i].reshape(m,n))
show()
注意,图像需要从一维表示重新转换成二维图像;可以使用 reshape() 函数
图像的高斯模糊是非常经典的图像卷积例子。本质上,图像模糊就是将(灰度)图像I 和一个高斯核进行卷积操作:
其中, ∗ 表示卷积操作;G 表示标准差为 σ 的二维高斯核,定义为:
高斯模糊通常是其他图像处理操作的一部分,比如图像插值操作,兴趣点计算以及很多其他应用。
from PIL import Image
from numpy import *
from scipy.ndimage import filters
import matplotlib.pyplot as plt #使用 matplotlib 来显示图片,可以让图片显示到jupyter的页面
im = array(Image.open('D:\PyCharmProjects\one\image\JMU.jpg').convert('L'))
im2 = filters.gaussian_filter(im,2)
im3 = filters.gaussian_filter(im,5)
fig = plt.figure()
ax1 = fig.add_subplot(131)
ax1.imshow(im)
ax2 = fig.add_subplot(132)
ax2.imshow(im2)
ax3 = fig.add_subplot(133)
ax3.imshow(im3)
plt.show()
结果如下
左边是原始图像,中间的是σ=2的高斯滤波器,右边为σ=5的高斯滤波器
对于 Dx 和 Dy,可以选择 Sobel 滤波器:
或者Prewitt 滤波器:
# 1.4.2图像导数
from PIL import Image
from pylab import *
from scipy.ndimage import filters
import numpy
im = array(Image.open('D:\PyCharmProjects\one\image\JMU.jpg').convert('L'))
gray()
subplot(1, 4, 1)
axis('off')
title('(a)')
imshow(im)
# Sobel 导数滤波器
imx = zeros(im.shape)
filters.sobel(im, 1, imx)
subplot(1, 4, 2)
axis('off')
title('(b)')
imshow(imx)
imy = zeros(im.shape)
filters.sobel(im, 0, imy)
subplot(1, 4, 3)
axis('off')
title('(c)')
imshow(imy)
mag = 255-numpy.sqrt(imx**2 + imy**2)
subplot(1, 4, 4)
title('(d)')
axis('off')
imshow(mag)
show()
结果如下
使用Sobel导数滤波器计算导数图像:(a)原始灰度图像;(b)x导数图像;(c)y导数图像;(d)梯度大小图像
图像去噪是在去除图像噪声的同时,尽可能地保留图像细节和结构地处理技术,以下给出使用ROF去噪模型,
ROF去噪模型的性质:使处理后的图像更平滑,同时保持图像边缘和结构信息。
一幅灰度图像I的全变差(TV)定义为梯度范数之和。在连续表示的情况下,全变差表示为:
在离散表示的情况下,全变差表示为:
其中,上面的式子是在所有图像坐标x=[x,y]上取和。在Chambolle提出的ROF模型里,目标函数为寻找降噪后的图像U,使下式最小
其中范数|| I-U ||是去噪后图像U和原始图像I差异的度量。本质上该模型使去噪后的图像像素值“平坦”变化,但在图像区域的边缘上,允许去噪后的图像像素值“跳跃”变化。
from PIL import Image
import matplotlib.pyplot as plt
import numpy as np
from scipy.ndimage import filters
def de_noise(img, U_init, tolerance=0.1, tau=0.125, tv_weight=100):
"""使用A.Chambolle(2005)在公式(11)中的计算步骤实现Rudin-Osher-Fatemi(ROF)去噪模型
输入:含有噪声的输入图像(灰度图像),U的初始值,TV正则项权值,步长,停业条件
输出:去噪和去除纹理后的图像,纹理残留
"""
U = U_init
# 对偶域的X,Y分量
Px = Py = img
error = 1
while error > tolerance:
Uold = U
# 原始变量梯度
gradUx = np.roll(U, -1, axis=1)-U # 变量U梯度的x分量
gradUy = np.roll(U, -1, axis=0)-U # 变量U梯度的y分量
# 更新对偶变量
PxNew = Px + (tau/tv_weight)*gradUx
PyNew = Py + (tau/tv_weight)*gradUy
NormNew = np.maximum(1, np.sqrt(PxNew**2+PyNew**2))
# 更新x,y分量(对偶)
Px = PxNew / NormNew
Py = PyNew / NormNew
# 更新原始变量
RxPx = np.roll(Px, 1, axis=1) # 将x分量向x轴正方向平移
RyPy = np.roll(Py, 1, axis=0) # 将y分量向y轴正方向平移
DivP = (Px - RxPx) + (Py - RyPy) # 对偶域散度
U = img + tv_weight * DivP #更新原始变量
# 更新误差
error = np.linalg.norm(U - Uold)/np.sqrt(img.shape[0] * img.shape[1])
# 去噪后的图像和纹理残余
return U, img-U
if __name__ == '__main__':
im = np.zeros((500, 500))
im[100:400,100:400] = 128
im[200:300, 200:300] = 255
im = im + 30 * np.random.standard_normal((500, 500))
U, T = de_noise(im, im)
G = filters.gaussian_filter(im, 10)
plt.figure()
plt.gray()
plt.subplot(221).set_title("Original image")
plt.axis('off')
plt.imshow(im)
plt.subplot(222).set_title("Gauss blurred image")
plt.axis('off')
plt.imshow(G)
plt.subplot(223).set_title("ROF")
plt.axis('off')
plt.imshow(U)
plt.savefig('tmp.jpg')
plt.show()