第一章 基本操作与处理
前言
本专栏按《python计算机视觉编程 ——Jan Erik Solem》一书为参考,第一章介绍学习计算机视觉编程的基本操作和知识,并介绍了相关的基本工具如图像转换缩放、画图等,之后的章节都会使用这些基本工具
1.1 PIL
PIL(Python Imaging Library)是一个用于处理图像的Python库,它提供了丰富的功能,包括图像的加载、编辑、保存等操作。从PIL版本1.1.7开始,该库由Pillow库取代,Pillow是PIL的一个分支,提供了更多功能和更新维护,在使用它之前需要在终端用以下代码安装pillow库
pip install Pillow #根据python版本选择pip或者pip3
然后用以下代码打开图像文件并显示,同时可以使用convert('L')
将原图转换为灰度图像
from PIL import Image
filelist = Image.open('filelist/PIL1.jpg')
L_im = Image.open('filelist/PIL1.jpg').convert('L')
L_im.show()
原图 | 灰度图 |
---|---|
1.1.1 图像转换格式
通过splitext
划分文件名再加上后缀格式名.jpeg
就能达到,结果用save()
保存,其中filelist
是自己创建的文件名列表,里面是要转换的图片路径列表
filelist = ['filelist/PIL1.jpg', 'filelist/PIL4.jpg', 'filelist/PIL5.jpg']
for file in filelist:
outfile = os.path.splitext(file)[0] + ".jpeg"
if file != outfile:
try:
Image.open(file).save(outfile)
except IOError:
print("cannot convert", file)
效果就是在同一文件下多了不同格式的同名图片,此处不放图
1.1.2 创建缩略图
L_im.thumbnail((128,128))
L_im.show()
然后就得到了一幅缩略图,为便于对比调整成大小相同,和原图相比糊了很多
原图 | 缩略图 |
---|---|
1.1.3 复制粘贴图像区域
设置一个box
选择要复制的区域,复制是crop
,粘贴是paste
,旋转是ROTATE_90
这里是90度,也可以是rotate(90)
的方式,前者还需要transpose
。同时需要调整图像尺寸使用resize
box = (200,200,600,600)
region = L_im.crop(box)
region = region.transpose(Image.ROTATE_90)
L_im.paste(region,box)
效果如下
原图 | 复制粘贴旋转图 |
---|---|
1.2 Matplotlib
Matplotlib是一个功能强大的Python数据可视化库,用于创建高质量、交互式的图表和可视化。它提供了广泛的绘图选项,支持折线图、散点图、柱状图、饼图、等高线图、3D图等多种图表类型。Matplotlib可以用于展示数据分布、趋势、模式以及其他统计信息,使得数据分析和探索更加直观和易于理解
1.2.1 绘制图像、点和线
先用imshow
绘制图像,再加上一些点并连接某两个点,选择点时可以像plot(x[:2],y[:2])
这样使用列表切片来提取点坐标,也可以像下面代码一样直接指定两个点的坐标
im = array(Image.open('filelist/PIL2.jpg'))
imshow(im)
x = [400,400,1200,1200]
y = [1200,2400,1200,2400]
plot(x,y,'ys')
plot([x[0], x[3]], [y[0], y[3]], 'b-')
title('Plotting: "PIL2.jpg"')
axis('off')
show()
其中点的颜色和形状'ys'
、线的颜色和类型'b-'
都可以自己选择其他喜欢的样式,详情请查看最后的官网连接
1.2.2 图像轮廓和直方图
在实际工作中绘制图像的轮廓非常重要,可以利用阈值对灰度图进行轮廓的提取,再通过flatten
使灰度图由按行转换成一维数组,并利用hist
画出直方图,128表示直方图将图像像素值范围划分为128个区间
im = array(Image.open('filelist/PIL2.jpg').convert('L'))
figure()
gray()
contour(im,origin='image')
axis('equal')
axis('off')
figure()
hist(im.flatten(),128)
show()
轮廓图 | 直方图 |
---|---|
1.2.3 交互式标注
当用户需要进行点击标注等交互操作时就可以利用ginput
进行交互式标注,下面代码允许用户在图像上点击三次,并保存每次点击的坐标到x列表中。由于截图原因最后一个点没有截上
im = array(Image.open('filelist/PIL2.jpg'))
axis('off')
imshow(im)
print('请在图上点三个点')
x = ginput(3)
print('你点击了:',x)
axis('off')
show()
1.3 Numpy
NumPy(Numerical Python)是一个开源的Python科学计算库,它为Python提供了高效的多维数组对象(ndarray),并提供了丰富的数组操作函数和数学函数,使得在Python中进行向量化计算和数值运算变得更加简单和高效
1.3.1 数组表示
在Numpy中表示图像、矩阵和向量的都是多维数组,使用array
就可以转换了。用shape
和dtype
就能查看元组的大小和类型,一般的彩色图像有红绿蓝三种颜色通道,变成灰度图之后就没有颜色信息了。这里彩色图像的第四种表示alpha透明度通道 。同时图像通常被编码成无符号八位整数unit8
,灰度图同理,但是可以在array后面加上想转换的数组类型
im = array(Image.open('filelist/PIL2.jpg'))
L_im = array(Image.open('filelist/PIL2.jpg').convert('L'))# convert('L').'f'表示转换为浮点数
print(im.shape, im.dtype)
print(L_im.shape, L_im.dtype)
数组元素使用下标来表示,多个数组元素用切片会更方便,这里不赘述,就举几个例子
# 多维数组的步长切片
arr = np.array([
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
[13, 14, 15, 16]
])
print(arr[::2, ::2])
# 输出:
# [[ 1 3]
# [ 9 11]]
# 使用布尔数组进行切片:
arr = np.array([1, 2, 3, 4, 5])
mask = np.array([True, False, True, False, True])
print(arr[mask]) # 输出: [1 3 5]
1.3.2 灰度变换
读入numpy数组对象后可以进行灰度转换,下面给些例子
L_im = array(Image.open('filelist/PIL2.jpg').convert('L'))
im2 = 255 - L_im # 反相处理
im3 = (100.0/255)*L_im+200 # 将图像像素值变换至200-300
im4 = 255.0*(L_im/255.0)**2 # 像素值求平方
im5 = np.uint8(255 * np.log1p(L_im) / np.log1p(256)) # 对数变换
# 每张图像的最小和最大像素值
# 0 255
# 200 300
# 0 255
# 0 254
在数据类型转换时,为了不出现意外,最好在转换前通过uint8(im)
将图像转换回来
pil_im = Image.fromarray(uint8(im))
1.3.4 图像缩放和直方图均衡化
图像缩放函数如下
def imresize(im,sz):
pil_im = Image.fromarray(uint8(im))
return array(pil_im.resize(sz))
直方图均衡化代码如下
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
im2, cdf = imtools.histeq(L_im)
1.3.5 主成分分析(PCA)
PCA的主要目标是通过线性变换,将高维数据映射到一个较低维度的新特征空间,同时保留尽可能多的原始数据信息。这种降维可以去除数据中的冗余和噪声,帮助更好地理解数据、可视化数据以及在后续的机器学习任务中提高计算效率和模型性能
PCA的工作原理如下:
- 计算数据的协方差矩阵:对于n个样本和m个特征的数据集,首先计算协方差矩阵,它描述了不同特征之间的相关性
- 计算协方差矩阵的特征值和特征向量:对协方差矩阵进行特征值分解,得到特征值和对应的特征向量
- 选择主成分:根据特征值的大小,选择前k个特征向量,这些特征向量对应的特征值较大,含有数据中较多的信息。这些特征向量构成了新的特征空间的基,称为主成分
- 投影数据:将原始数据点映射到选定的k个主成分上,得到降维后的数据
其余内容在之前的博客中已经有详细的介绍,此处不再赘述
1.3.6 pickle模块
pickle模块是用来储存python对象并转化成字符串表示,这个过程被称为封装,从字符串中重构对象的过程被称为拆封。其中字符串能够方便的保存和传输,如保存和加载某个参数A或者数据B可以使用pickle.dump
和pickle.load
实现,但要注意保存和加载的顺序要一致
# 保存操作
f = open('filename.pkl','wb')
pickle.dump(A,f)
pickle.dump(B,f)
f.close()
# 加载操作
f = open('any.pkl','rb')
A = pickle.load(f)
B = pickle.load(f)
f.close()
再打开文件时可以用with open()
方式,这种方式能自动打开和关闭文件。同时numpy的读写函数savetxt/loadtxt
也很有用
# `with open()`方式
with open('filename.pkl','wb') as f:
# 读写函数
savetxt('test.txt',x,'%i')
x = loadtxt('test.txt)
1.4 Scipy
1.4.1 图像模糊
图像模糊就是将灰度图像
I
I
I和一个卷积核
G
G
G进行卷积,常用的卷积核是高斯核,卷积操作定义为
I
σ
=
I
∗
G
σ
I_{\sigma} = I\ast G_{\sigma}
Iσ=I∗Gσ其中
G
σ
G_{\sigma}
Gσ是标准差为
σ
\sigma
σ的高斯卷积核
G
σ
=
1
2
π
σ
2
e
−
(
x
2
+
y
2
)
/
2
σ
2
G_{\sigma}=\frac{1}{2\pi\sigma^2}e^{-(x^2+y^2)/2\sigma^2}
Gσ=2πσ21e−(x2+y2)/2σ2高斯模糊是其他图像操作的一部分,如图像差值、兴趣点计算等。scipy.ndimage.filters
就是使用快速一维分离方式来滤波的模块,代码如下
# 其他库之前已有
from scipy.ndimage import filters
# 分别打开灰度图像
L_im = array(Image.open('filelist/PIL2.jpg').convert('L'))
im2 = gaussian_filter(L_im,5)
im3 = gaussian_filter(L_im,10)
im4 = gaussian_filter(L_im,15)
plt.subplot(2, 2, 1)
plt.imshow(L_im, cmap='gray')
axis('off')
plt.title('原始灰度图',fontproperties=font)
plt.subplot(2, 2, 2)
plt.imshow(im2, cmap='gray')
axis('off')
plt.title('sigma=5滤波图',fontproperties=font)
plt.subplot(2, 2, 3)
plt.imshow(im3, cmap='gray')
axis('off')
plt.title('sigma=10滤波图',fontproperties=font)
plt.subplot(2, 2, 4)
plt.imshow(im4, cmap='gray')
axis('off')
plt.title('sigma=15滤波图',fontproperties=font)
show()
就得到了以下四幅灰度图,可以看到随着 σ \sigma σ的增大图像变得越来越模糊
1.4.2 图像导数
图像强度的变换可以用灰度图像
I
I
I的
x
x
x和
y
y
y方向的导数
I
x
I_x
Ix核
I
y
I_y
Iy进行描述,图像的梯度向量被定义为
∇
I
=
[
I
x
,
I
y
]
T
\nabla I=[I_x,I_y]^T
∇I=[Ix,Iy]T它有大小和角度两个属性,分别描述图像强度变化的强弱和图像在每个像素上强度变化最大的方向,分别定义为
∣
∇
I
∣
=
I
x
2
+
I
y
2
|\nabla I|=\sqrt{{I_x}^2+{I_y}^2}
∣∇I∣=Ix2+Iy2
α
=
arctan
2
(
I
y
,
I
x
)
\alpha=\arctan2(I_y,I_x)
α=arctan2(Iy,Ix)对图像导数的计算通常和模糊一样都是通过卷积实现的,通常选择Prewitt和Sobel滤波器,一下分别是导数计算的定义和两个滤波器的矩阵定义
I
x
=
I
∗
D
x
和
I
y
=
I
∗
D
y
I_x= I\ast D_x和I_y= I\ast D_y
Ix=I∗Dx和Iy=I∗Dy
D
x
=
[
−
1
0
1
−
1
0
1
−
1
0
1
]
和
D
y
=
[
−
1
−
1
−
1
0
0
0
1
1
1
]
D_x=\begin{bmatrix} -1 & 0 & 1 \\ -1 & 0 & 1 \\ -1 & 0 & 1 \end{bmatrix}和D_y=\begin{bmatrix} -1 & -1 & -1\\ 0 & 0 & 0 \\ 1 & 1 & 1 \end{bmatrix}
Dx=
−1−1−1000111
和Dy=
−101−101−101
D
x
=
[
−
1
0
1
−
2
0
2
−
1
0
1
]
和
D
y
=
[
−
1
−
2
11
0
0
0
1
2
1
]
D_x=\begin{bmatrix} -1 & 0 & 1 \\ -2 & 0 & 2 \\ -1 & 0 & 1 \end{bmatrix}和D_y=\begin{bmatrix} -1 & -2 & 11 \\ 0 & 0 & 0 \\ 1 & 2 & 1 \end{bmatrix}
Dx=
−1−2−1000121
和Dy=
−101−2021101
使用sobel滤波进行滤波的操作如下,先分别计算x和y方向的导数,再求整体的梯度导数
imx = zeros(L_im.shape)
sobel(L_im,1,imx)
imy = zeros(L_im.shape)
sobel(L_im,0,imy)
magnitude = sqrt(imx**2+imy**2)
但是上述方法中滤波器大小要随着图像分辨率的变化而变化,因此可以使用高斯导数滤波器操作,这种滤波器更加文件名并能在任意尺度上计算导数,通过改变
σ
\sigma
σ的值即可
sigma = 5
imx = zeros(L_im.shape)
gaussian_filter(L_im,(sigma,sigma),(0,1),imx)
imy = zeros(L_im.shape)
gaussian_filter(L_im,(sigma,sigma),(1,0),imy)
下面分别是x导数图像(第一行)、y导数图像(第二行)和梯度大小图像(第三行),自左到右依次是
σ
\sigma
σ的值为5、10、15的效果,随着值的增大线条变化越来越粗,同模糊程度类似
1.4.3 形态学:对象计数
在scipy库中,形态学(morphology)是一组图像处理方法,用于对二值图像进行形状和结构的操作。这些方法可以用于图像分割、边缘检测、去除噪声等应用。主要涉及的函数位于scipy.ndimage.morphology模块中,比如膨胀、腐蚀、距离变换、骨架化等函数操作,这些函数主要适用于处理二值图像,即图像中只有两种像素值(通常为0和1)在之前博客已详细介绍,这里给出二值图像的计数度量功能代码
im = array(Image.open('filelist/PIL6.jpg').convert('L'))
im = 1 * (im < 128)
label, nbr_objects = measurements.label(im)
print('对象数量为:', nbr_objects)
im_open = morphology.binary_opening(im, ones((9, 5)), iterations=2)
labels, nbr_objects_open = measurements.label(im_open)
print('对象数量为:', nbr_objects_open)
效果如上,第一个有835个对象,进行开操作后由于 ones((9, 5))
结构元素有点大了,导致好多字都被开掉了,计数只有13个了,可以调整结构元素进行更好的优化
其他Scipy模块
除了之前的几个模块,还有其他实用的的输入输出模块如io
和misc
io
模块用来读写Matlab格式的文件,或者保存这类格式的文件
# 读取
data = scipy.io.loadmat('test.mat')
# 保存
data = {}
data['x'] = x
scipy.io.savement('test.mat',data)
misc
模块用来以图像的形式保存数组
from scipy.misc import imsave
imsave('test.jpg',im)
lena = scipy.misc.lena()
1.5 图像去噪示例
图像去噪应该不用解释了,前面博客中有说明,这里使用ROF去噪模型,ROF(Rudin-Osher-Fatemi)模型是一种图像去噪模型,它是一种基于变分方法的模型,用于去除数字图像中的噪声,并同时保持图像边缘的清晰度。
ROF模型的主要目标是通过最小化总变差(Total Variation,TV)来实现去噪。总变差是图像梯度的L1范数,在ROF模型中,它被用作正则化项,以使图像具有更加平滑的特性。ROF模型的数学形式如下:
min
u
E
(
u
)
=
1
2
∫
Ω
(
u
−
f
)
2
d
Ω
λ
∫
Ω
∥
∇
u
∥
d
Ω
\min_uE(u)=\frac{1}{2}\int_{\Omega}(u-f)^2d\Omega\lambda\int_{\Omega}\|\nabla u\|d\Omega
uminE(u)=21∫Ω(u−f)2dΩλ∫Ω∥∇u∥dΩ
其中:
- u u u 是待求解的去噪后图像
- f f f 是带噪声的输入图像
- Ω \Omega Ω 是图像的空间域
- λ \lambda λ 是平衡项,控制平滑性和去噪程度的权重
ROF模型的求解通常使用变分法和梯度下降等数值方法。通过最小化上述能量函数,ROF模型能够去除噪声,同时保持图像边缘的清晰度,使得图像具有更好的视觉效果和信息保留
im = zeros((500, 500))
im[100:400, 100:400] = 128
im[200:300, 200:300] = 255
im = im + 30*random.standard_normal((500,500))
U, T = rof.denoise(im, im)
G = gaussian_filter(im, 10)
# imageio.imsave('synth_rof.pdf', U)
# imageio.imsave('synth_gaussian.pdf', G)
subplot(1, 3, 1)
imshow(im, cmap='gray')
axis('off')
title('噪声原始图像', fontproperties=font)
subplot(1, 3, 2)
imshow(G, cmap='gray')
axis('off')
title('高斯模糊图像', fontproperties=font)
subplot(1, 3, 3)
imshow(U, cmap='gray')
axis('off')
title('经ROF去噪后图像', fontproperties=font)
show()
上述代码合成了一个含有噪声的图像,并经过高斯和ROF模型进行模糊和去噪处理
实际使用情况如下,使用ROF模型去噪后明显感觉到图像中的纹理都少了,保留了边缘和图像的结构信息,模糊了噪声