赞
踩
《Python计算机视觉编程》
计算机视觉是一门对图像中信息进行自动提取的学科。
计算机视觉有时试图模拟人类视觉,有时使用数据和统计方法,而有时几何是解决问题的关键。
PIL( Python Imaging Library Python,图像处理类库)提供了通用的图像处理功能,以及大量有用的基本图像操作。
from PIL import Image from time import * def main(): # 读取一幅图像 pil_im = Image.open('empire.jpg') # 读取一幅图像,并将其转换成灰度图像, pil_imL = Image.open('empire.jpg').convert('L') pil_im.show() pil_imL.show() return if __name__ == '__main__': timeStart = time() main() timeOver = time() print("program running time is :",timeOver - timeStart)
原图如下:
程序运行后输出结果如下:
一张为正常彩色图片,一张为转换后的灰度图片。
我使用的Python版本为3.8所以书中的代码没法直接使用,需要调整一下。
def changeFormat(): """ 函数功能:修改图像格式 参数说明:无 函数返回:无 """ filelist = get_imlist(r"D:\python\exercise_data\ComputerVision\ch01\figure") # 遍历整个文件夹查找出路径中符合条件的图像文件 for infile in filelist: # 对原图像文件的格式进行修改 outfile = os.path.splitext(infile)[0] + '.png' # 判断是否修改成功,成功则进行save()方法 if infile !=outfile: try: Image.open(infile).save(outfile) except IOError: print("cannot convert",infile) return def main(): # readAndShow() changeFormat() return if __name__ == '__main__': timeStart = time() main() timeOver = time() print("program running time is :",timeOver - timeStart)
程序实际上是将图片读取,然后修改名称后缀,之后保存。
def thumbnail(): """ 函数功能:生成缩略图 参数说明:无 函数返回:无 """ op_image = Image.open(r'./figure/empire.jpg') plt.subplot(121) plt.imshow(op_image) plt.title("original") # 输出原图 op_image.thumbnail((128, 128)) #图像缩略处理,指定大小为128*128 plt.subplot(122) plt.imshow(op_image) plt.title("result") plt.show() return
输出如下:
对比上图,不难发现缩略后的图片看起来比较模糊,而且坐标尺寸也小于原图。
def copyAndPaste(op_image): """ 函数功能:复制粘贴一幅图片中的部分内容 参数说明:op_image__已打开的图片 函数返回:无 """ box = (50, 50, 200, 200) # 元组给出裁剪区域 re_img = op_image.crop(box) # 利用crop方法进行图片裁剪 axes = subplot(121) imshow(op_image), title("original") subplot(122) imshow(re_img), title("result") show() return def main(): op_image = Image.open(r'./figure/empire.jpg') # readAndShow() # changeFormat() # thumbnail() copyAndPaste(op_image) return
上图可以看到输出结果为原图的部分,对比坐标就是 (50, 50,200, 200) 四条线包围的部分。
def adjustAndRotate(or_image): """ 函数功能:调整图片尺寸和旋转 参数说明:or_image__已打开的图片 函数返回:无 """ size = (128,128) # resize()用以调整图片的大小,参数为元组 out_image1 = or_image.resize(size) # rotate()用以对图像进行旋转,参数为(0-360) out_image2 = or_image.rotate(180) subplot(131) imshow(or_image), title('original') subplot(132) imshow(out_image1), title('resize') subplot(133) imshow(out_image2), title('rotate') show() return
上图分别展示的是原图、调整了尺寸的图像,可以看到坐标有差异。最后是将图片旋转了90度后的图片。
Matplotlib是个很好的类库,具有比 PIL 更强大的绘图功能。
def plotPointAndLine(or_image): """ 函数功能:在绘制的图片上绘制线 参数说明:or_image__已打开的图片 函数返回:无 """ # 将图像转换为数组 img = array(or_image) imshow(img) x = [10, 50, 100, 200] y = [20, 50, 150, 180] # 根据给出的坐标绘制红色点 plot(x, y, "r*") # 绘制连接4个点的线 plot(x[:4], y[:4]) title('plotting') show() return
如上图所示,底图是“蛋生”,蛋生上的线条就是我们绘制的线条。
加了坐标轴看起来可能不舒服,我们就使用 axis(‘off’)来关闭坐标轴。
# 将图像转换为数组
img = array(or_image)
imshow(img)
x = [10, 50, 100, 200]
y = [20, 50, 150, 180]
# 根据给出的坐标绘制红色点
plot(x, y, "r*")
# 绘制连接4个点的线
plot(x[:4], y[:4])
title('plotting')
axis('off')
show()
使用还是之前的代码,增加一行就可以。输出结果如下:
此时照片就不含有坐标轴。
def outlineAndHistogram(fileName): """ 函数功能:绘制图像轮廓和直方图 参数说明:fileName__图像存储路径 函数返回:无 """ # 读取图像至数组中,并将其转为灰度图像 im = array(Image.open(fileName).convert('L')) re_im = Image.open(fileName) subplot(131) imshow(re_im), title('original') subplot(132) gray() # 不使用颜色信息 contour(im, origin='image') # 在原点的左上角显示轮廓图像 axis('off') axis('equal') subplot(133) # 直方图绘制 # flatten() 方法将任意数组按照行优先准则转换成一维数组。 hist(im.flatten(), 128) show() return
输出如下:
最左侧的是原图,中间的是原图对应的轮廓线,我们可以清晰的看到。最右侧的是图像对饮过得直方图的统计。可以大致得到图像灰度值的分部。
def markPoint(fileName): """ 函数功能:给出点击点的坐标 参数说明:fileName__图像存储路径 函数返回:无 """ im = array(Image.open(fileName)) imshow(im) while(True): print("please click 3 points") x = ginput(3) print("you click:", x) matData = mat(x) # 这里取得是列所以要转 axis_x = matData[:,0] axis_y = matData[:,1] # [item[i] for item in a] plot(axis_x.flatten(), axis_y.flatten(), "o") # 三个点的横坐标过近时,就退出 pointDistance = axis_x[0][0]-axis_x[1][0]+axis_x[2][0]-axis_x[1][0] if pointDistance < 100: print('quit :',pointDistance) break show() return
输出结果如下:
图像上三个桔绿蓝的点是我们点点击的位置,程序输出的位置为:
[(74.50865800865802, 69.19696969696969), (142.04112554112555, 95.17099567099567), (83.59956709956711, 121.57792207792207)]
目测一下坐标是对的上的。
Numpy是非常有名的 Python 科学计算工具包,其中包含了大量有用的思想,比如数组对象(用来表示向量、矩阵、图像等)以及线性代数函数。
def imageArray(fileName): """ 函数功能:图像数组表示 参数说明:fileName__图像存储路径 函数返回:无 """ img0 = array(Image.open(fileName)) print(img0.shape, img0.dtype) img = array(Image.open(fileName).convert('RGB')) # RGB print(img.shape, img.dtype) img1 = array(Image.open(fileName).convert('1')) # 二值图像非0即1,故数据类型为bool型 print(img1.shape, img1.dtype) img2 = array(Image.open(fileName).convert('L')) # 灰度图,灰度值在0-255之间,且为设定其数据类型,array自动判定为uint8型 print(img2.shape, img2.dtype) img3 = array(Image.open(fileName).convert('L'), 'f') # 灰度图,‘f’设定其数据类型为float类型,故显示为float32 print(img3.shape, img3.dtype) return
输出与注释一致。就是将图片信息按照我们的要求读取出来后,存储到一个数组中,彩色图像是三维数组,因为包含RGB三色。灰度图像就是二维数组。每一个点代表图像中的每一个像素点。所以我们访问图片数组就是在访问每一个像素点。
def grayChange(fileName): """ 函数功能:图像灰度变换 参数说明:fileName__图像存储路径 函数返回:无 """ or_img = array(Image.open(fileName)) # 灰度变换 gr_img = array(Image.open(fileName).convert('L')) # 对灰度图进行反相处理 gr_img1 = 255 - gr_img # 将图像的像素转变到100-200区间 gr_img2 = (100.0/255) * gr_img + 100 # 将灰度图像的像素求平方 gr_img3 = 255.0 * (gr_img/255.0)**2 subplot(231) imshow(or_img), title('origin') subplot(232) gray() imshow(gr_img), title('gray') subplot(233) gray() imshow(gr_img1), title('gray_re') subplot(234) gray() imshow(gr_img2), title('gray(100-200)') subplot(235) imshow(gr_img3), title('gray**2') show() return
输出如下:
如上图所示,展示的为从原图变换后得到的各种灰度图。按从左往右,从上往下的顺序,原图之后是灰度图,直接调用程序中的转换函数得到。接下来是反相,可以看到在灰度图中暗的部分现在变成了亮色,而原本亮的地方这里变成了黑色。接下来是对灰度值进行了一个归一化,变化到100-200范围内,可以发现图片的对比度有了提升,原本暗的地方更暗,亮的地方也更亮。最后一个是对灰度值进行了平方操作,整体图像更加偏暗了。
直方图均衡化是指将一幅图像的灰度直方图变平,使变换后的图像中每个灰度值的分布概率都相同。在对图像做进一步处理之前,直方图均衡化通常是对图像灰度值进行归一化的一个非常好的方法,并且可以增强图像的对比度。之前的灰度变换中将灰度值变到100-200就能看到。
直方图均衡化的变换函数是图像中像素值的累积分布函数( cumulative distribution function, 简写为 cdf,将像素值的范围映射到目标范围的归一化操作)。
def histequal(fileName): """ 函数功能:直方图均衡化 参数说明:fileName__图像存储路径 函数返回:无 """ img = array(Image.open(fileName).convert('L')) img2, cdf = histeq(img) subplot(231) hist(img.flatten(), 128) subplot(234) gray() imshow(img), title('gray') subplot(233) hist(img2.flatten(), 128) subplot(232) gray() plot(cdf, 'b-') subplot(236) gray() imshow(img2), title('gray_ch') show() return
输出如下:
左侧为原始图像和直方图,中间图为灰度变换函数,右侧为直方图均衡化后的图像和相应直方图。明显差异就是均衡过后的图像对比度变大。
图像平均操作是减少图像噪声的一种简单方式,通常用于艺术特效。
PCA( Principal Component Analysis,主成分分析)是一个非常有用的降维技巧。它可以在使用尽可能少维数的前提下,尽量多地保持训练数据的信息
这部分可以参考《机器学习实战》利用PCA来简化数据这里面有PCA更详细的说明。区别在于我们现在在分析图片,而里面是对于矩阵的处理。
SciPy是建立在 NumPy 基础上,用于数值运算的开源工具包。SciPy 提供很多高效的操作,可以实现数值积分、优化、统计、信号处理,以及对我们来说最重要的图像处理功能。
图像的高斯模糊是非常经典的图像卷积例子。本质上,图像模糊就是将(灰度)图像 I 和一个高斯核进行卷积操作:
其中 * 表示卷积操作; Gσ 是标准差为 σ 的二维高斯核,定义为 :
I
σ
=
I
∗
G
σ
\boldsymbol{I}_{\sigma}=\boldsymbol{I} * G_{\sigma}
Iσ=I∗Gσ
其中 * 表示卷积操作;
G
σ
G_σ
Gσ 是标准差为 σ 的二维高斯核,定义为 :
G
σ
=
1
2
π
σ
e
−
(
x
2
+
y
2
)
/
2
σ
2
G_{\sigma}=\frac{1}{2 \pi \sigma} \mathrm{e}^{-\left(x^{2}+y^{2}\right) / 2 \sigma^{2}}
Gσ=2πσ1e−(x2+y2)/2σ2
def dimImage(fileName): """ 函数功能:直方图均衡化 参数说明:fileName__图像存储路径 函数返回:无 """ # 灰度图像的模糊处理 img = array(Image.open(fileName).convert('L')) img_re0 = filters.gaussian_filter(img, 2) img_re1 = filters.gaussian_filter(img, 5) img_re2 = filters.gaussian_filter(img, 10) # 彩色图像的模糊处理 img_or = array(Image.open(fileName)) img_or2 = zeros(img_or.shape) # guassian_filter() 函数的最后一个参数表示标准差 img_or2= filters.gaussian_filter(img_or, 1) img_or2 = uint8(img_or2) subplot(231) imshow(img_or), title('original') subplot(233) imshow(img_or2), title('rgb——1') subplot(234) gray() imshow(img_re1), title('gray——2') subplot(235) gray() imshow(img_re2), title('gray——5') subplot(236) gray() imshow(img_re2), title('gray——10') show()
输出如下:
从上图可知随着 σ 的增加,一幅图像被模糊的程度越大。σ越大,处理后的图像细节丢失越多。
在很多应用中图像强度的变化情况是非常重要的信息。强度的变化可以用灰度图像
I
I
I(对于彩色图像,通常对每个颜色通道分别计算导数)的x和y方向导数
I
x
I_x
Ix和
I
y
I_y
Iy进行描述。图像的梯度向量为
∇
I
=
[
I
x
,
I
y
]
T
\nabla \boldsymbol{I}=\left[\boldsymbol{I}_{x}, \boldsymbol{I}_{y}\right]^{T}
∇I=[Ix,Iy]T 。梯度有两个重要的属性,一是梯度的大小:
∣
∇
I
∣
=
I
x
2
+
I
y
2
|\nabla \boldsymbol{I}|=\sqrt{\boldsymbol{I}_{x}^{2}+\boldsymbol{I}_{y}^{2}}
∣∇I∣=Ix2+Iy2
它描述了图像强度变化的强弱,一是梯度的角度:
α
=
arctan
2
(
I
y
,
I
x
)
\alpha=\arctan 2\left(\boldsymbol{I}_{y}, \boldsymbol{I}_{x}\right)
α=arctan2(Iy,Ix)
描述了图像中在每个点(像素)上强度变化最大的方向。
我们可以用离散近似的方式来计算图像的导数。图像导数大多数可以通过卷积简单地实现: I x = I ∗ D x 和 I y = I ∗ D y \boldsymbol{I}_{x}=\boldsymbol{I} * D_{x} \text { 和 } \boldsymbol{I}_{y}=\boldsymbol{I} * D_{y} Ix=I∗Dx 和 Iy=I∗Dy
对于
D
x
D_x
Dx和
D
y
D_y
Dy ,通常选择 Prewitt 滤波器:
D
x
=
[
−
1
0
1
−
1
0
1
−
1
0
1
]
和
D
y
=
[
−
1
−
1
−
1
0
0
0
1
1
1
]
D_{x}=\left[
或者 Sobel 滤波器:
D
x
=
[
−
1
0
1
−
2
0
2
−
1
0
1
]
和
D
y
=
[
−
1
−
2
−
1
0
0
0
1
2
1
]
D_{x}=\left[
def representativeFilters(fileName): """ 函数功能:典型滤波 参数说明:fileName__图像存储路径 函数返回:无 """ img = array(Image.open(fileName).convert('L')) # Sobel滤波器 img_x = zeros(img.shape) # sobel() 函数的第二个参数表示选择 x 或者 y 方向导数,第三个参数保存输出的变量。 imgX = filters.sobel(img, 1, img_x) img_y = zeros(img.shape) imgY = filters.sobel(img, 0, img_y) # 梯度 magnitude = sqrt(img_x**2+img_y**2) subplot(221) imshow(img, cmap='gray'), title('original') subplot(222) imshow(imgX, cmap='gray'), title('x') subplot(223) imshow(imgY, cmap='gray'), title('y') subplot(224) imshow(magnitude, cmap='gray'), title('magnitude') show() return
输出如下:
上图从左往右从上往下依次为原始灰度图像;x方向导数图像;y方向导数图像;梯度大小图像。在两个导数图像中,正导数显示为亮的像素,负导数显示为暗的像素。灰色区域表示导数的值接近于零。
可以看到亮的地方大多为有明显对比色差的地方。
为了在图像噪声方面更稳健,以及在任意尺度上计算导数,我们可以使用高斯导数滤波器: I x = I ∗ G σ x 和 I y = I ∗ G σ y \boldsymbol{I}_{x}=\boldsymbol{I}^{*} G_{\sigma x} \text { 和 } \boldsymbol{I}_{y}=\boldsymbol{I}^{*} G_{\sigma y} Ix=I∗Gσx 和 Iy=I∗Gσy
G σ x G_σx Gσx 和 G σ y G_σy Gσy表示 G σ G_σ Gσ在 x 和 y 方向上的导数, G σ G_σ Gσ 为标准差为 σ 的高斯函数。
def gaussian(img, sigma): """ 函数功能:高斯滤波 参数说明:img__图像 sigma__高斯滤波参数 函数返回:无 """ img_x = zeros(img.shape) imgx = filters.gaussian_filter(img, (sigma, sigma),(0, 1), img_x) img_y = zeros(img.shape) imgy = filters.gaussian_filter(img, (sigma, sigma),(1, 0), img_y) magnitude = sqrt(img_x ** 2 + img_y ** 2) return imgx, imgy, magnitude def guassianFilter(fileName): """ 函数功能:高斯滤波对比 参数说明:fileName__图像存储路径 函数返回:无 """ img_or = array(Image.open(fileName).convert('L')) gray() imgx1, imgy1, magnitude1 = gaussian(img_or, 2) imgx2, imgy2, magnitude2 = gaussian(img_or, 5) imgx3, imgy3, magnitude3 = gaussian(img_or, 10) subplot(341) imshow(img_or), title('a'), axis('off') subplot(342) imshow(imgx1), title('b'), axis('off') subplot(343) imshow(imgx2), title('c'), axis('off') subplot(344) imshow(imgx3), title('d'), axis('off') subplot(345) imshow(img_or), title(''), axis('off') subplot(346) imshow(imgy1), title(''), axis('off') subplot(347) imshow(imgy2), title(''), axis('off') subplot(348) imshow(imgy3), title(''), axis('off') subplot(349) imshow(img_or), title(''), axis('off') subplot(3, 4, 10) imshow(magnitude1), title(''), axis('off') subplot(3, 4, 11) imshow(magnitude2), title(''), axis('off') subplot(3, 4, 12) imshow(magnitude3), title(''), axis('off') show() return
输出如下:
使用高斯导数计算图像导数: x 导数图像(上), y 导数图像(中),以及梯度大小图像(下);( a)为原始灰度图像,( b)为使用 σ=2 的高斯导数滤波器处理后的图像,( c)为使用 σ=5 的高斯导数滤波器处理后的图像,( d)为使用 σ=10 的高斯导数滤波器处理后的图像
形态学(或数学形态学)是度量和分析基本形状的图像处理方法的基本框架与集合。形态学通常用于处理二值图像,但是也能够用于灰度图像。 二值图像是指图像的每个像素只能取两个值,通常是 0 和 1。二值图像通常是,在计算物体的数目,或者度量其大小时,对一幅图像进行阈值化后的结果。
def binaryImage(fileName): """ 函数功能:二值图像 参数说明:fileName__图像存储路径 函数返回:无 """ img = array(Image.open(fileName).convert('L')) gray() subplot(221) imshow(img), title('original') img = 1*(img < 128) # ndimage.measurements.label(input, structure=None, output=None) # 输入一个数组,数组中非0值被认为是目标区域,0值是背景区域 # 输出:label:一个被已经分好连通域的label图像: # num_feartures:统计一共有多少种连通分量的数量,例如数字5代表一共有5种,ID分别是1,2,3,4,5 labels, nbr_objects = measurements.label(img) print("number of objects:", nbr_objects) subplot(222) imshow(labels), title('the picture after label') # binary_opening() 函数的第二个参数指定一个数组结构元素。该数组表示以一个像素为中心时, # 使用哪些相邻像素。在这种情况下,我们在 y 方向上使用 9 个像素(上面 4 个像素、像素本身、下面 4 个像素), # 在 x 方向上使用 5 个像素。 # iterations 决定执行该操作的次数。 img_open = morphology.binary_opening(img, ones((9, 5)), iterations=2) subplot(223) imshow(img_open), title('open') labels_open, nbr_objects_open = measurements.label(img_open) print("number of objects:",nbr_objects_open) subplot(224) imshow(labels_open), title('result') show() return
输出结果如下:
程序识别出的连通区域有48个。
Matlab的 .mat 文件格式存储,那么可以使用 scipy.io 模块进行读取
data = scipy.io.loadmat('test.mat')
data 对象包含一个字典,字典中的键对应于保存在原始 .mat 文件中的变量名。由于这些变量是数组格式的,因此可以很方便地保存到 .mat 文件中。你仅需创建一个字典(其中要包含你想要保存的所有变量),然后使用savemat() 函数:
data = {}
data['x'] = x
scipy.io.savemat('test.mat',data)
因为我们需要对图像进行操作,并且需要使用数组对象来做运算,所以将数组直接保存为图像文件非常有用。imsave() 函数可以从 scipy.misc 模块中载入。要将数组 im 保存到文件中,可以使用
下面的命令:
from scipy.misc import imsave
imsave('test.jpg',im)
#该脚本返回一个 512× 512 的灰度图像数组。
lena = scipy.misc.lena()
图像去噪是在去除图像噪声的同时,尽可能地保留图像细节和结构的处理技术。我们这里使用 ROF( Rudin-Osher-Fatemi) 去噪模型。ROF 模型具有很好的性质:使处理后的图像更平滑,同时保持图像边缘和结构信息。
一幅(灰度)图像 I I I的全变差(Total Variation,TV)定义为梯度范数之和。在连续表示的情况下,全变差表示为: J ( I ) = ∫ ∣ ∇ I ∣ d x J(\boldsymbol{I})=\int|\nabla \boldsymbol{I}| \mathrm{d} \mathbf{x} J(I)=∫∣∇I∣dx
在离散表示的情况下,全变差表示为:
J
(
I
)
=
∑
x
∣
∇
I
∣
J(\boldsymbol{I})=\sum_{\mathbf{x}}|\nabla \boldsymbol{I}|
J(I)=x∑∣∇I∣
其中,上面的式子是在所有图像坐标 x=[x, y] 上取和。
在 Chambolle 提出的 ROF 模型里,目标函数为寻找降噪后的图像 U,使下式最小: min U ∥ I − U ∥ 2 + 2 λ J ( U ) \min _{U}\|\boldsymbol{I}-\boldsymbol{U}\|^{2}+2 \lambda J(\boldsymbol{U}) Umin∥I−U∥2+2λJ(U)
其中范数|| I − U I-U I−U || 是去噪后图像 U 和原始图像 I I I差异的度量。也就是说,本质上该模型使去噪后的图像像素值“平坦”变化,但是在图像区域的边缘上,允许去噪后的图像像素值“跳跃”变化。
ROF模型去噪代码实现:
def imageFilter(fileName): """ 函数功能:图像去噪 参数说明:fileName__图像存储路径 函数返回:无 """ img = array(Image.open(fileName).convert('L')) gray() # 添加噪声 # 括号里的参数要换成和图片一样大小的。 img = img + 30*standard_normal((200,234)) U, T = denoise(img, img) G = filters.gaussian_filter(img, 5) subplot(131) imshow(img), title('original'),axis('off') subplot(132) imshow(U), title('rof'),axis('off') subplot(133) imshow(G), title('gaussian'),axis('off') show() return
输出如下:
对比rof滤波和告诉滤波。rof整体图像虽然也是糊的,但是他的边缘信息保留的比较突出。对比高斯滤波整个图形都是模糊的。
本章内容很杂,介绍图像读取、显示、局部操作、保存、滤波等等基本操作。虽然繁多但是都是最基本的操作。是我们后续进行对图片进一步处理的基础。
本章用到的代码(我使用的Python是3.8的,有需要自取):
链接:https://pan.baidu.com/s/1SjJn4a6P7GgpvKS0uXqRhQ?pwd=jbmf
提取码:jbmf
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。