基础图像操作和处理
文章目录
一、python图像处理类库
PIL(Python Imaging Library)图像处理类库提供了通用的图像处理功能、基本图像操作:缩放、裁剪、旋转、颜色转换等
通过 save() 方法,PIL 可以将图像保存成多种格式的文件
使用 crop() 方法可以从一幅图像中裁剪指定区域
from PIL import Image
img= Image.open('empire.jpg') #读取图像,返回PIL图像对象
#只是转换不修改img的值
img.convert('L') # convert('L')转换图像颜色,L为灰度图片
img.convert('1') #转二值图像
img.convert('P') #8位色彩图
img.convert('RGBA') #带有透明度的彩色图像
img= Image.open('test.jpg').convert('L') #修改为灰度图片,丢失颜色
#转换图像格式
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
def get_imlist(path):
"""返回目录中所有jpg图像的文件名列表"""
return [os.path.join(path,f) for f in os.listdir(path) if f.endswith('.jpg')]
# 创建缩略图
img.thumbnail((128,128)) #指定图片的缩略图大小
#复制粘贴图像区域
box = (100,100,200,300) # 四元组的坐标依次是(左,上,右,下)
region = img.crop(box) #裁剪
region = region.transpose(Image.ROTATE_180) #指定图像区域旋转180°
img.paste(region,box) #粘贴
# 调整尺寸和旋转
out = img.resize((128,128)) # 调整图片大小
out = img.rotate(45) #逆时针旋转
二、Matplotlib
1、绘制图像、点和线
from PIL import Image
from pylab import *
# 读取图像到数组中
im = array(Image.open('empire.jpg'))
# 绘制图像
imshow(im)
# 一些点
x = [100,100,400,400]
y = [200,500,200,500]
# 使用红色星状标记绘制点
plot(x,y,'r*')
# 绘制连接前两个点的线
plot(x[:2],y[:2])
plot(x,y) # 默认为蓝色实线
plot(x,y,'go-') # 带有圆圈标记的绿线
plot(x,y,'ks:') # 带有正方形标记的黑色点线
# 添加标题,显示绘制的图像
title('Plotting: "empire.jpg"')
axis('off') # 坐标轴不显示
show()
(1)用PyLab库绘图的基本颜色格式命令
字符 | 颜色 |
---|---|
‘b’ | 蓝色 |
‘g’ | 绿色 |
‘r’ | 红色 |
‘c’ | 青色 |
‘m’ | 品红 |
‘y’ | 黄色 |
‘k’ | 黑色 |
‘w’ | 白色 |
(2)用PyLab库绘图的基本线型格式命令
字符 | 线形 |
---|---|
‘-’ | 实线 |
‘–’ | 虚线 |
‘:’ | 点线 |
(3)用PyLab库绘图的基本绘制标记格式命令
字符 | 标记 |
---|---|
‘.’ | 点 |
‘o’ | 圆圈 |
‘s’ | 正方形 |
‘*’ | 星形 |
‘+’ | 加号 |
‘x’ | 叉号 |
2、图像轮廓和直方图
from PIL import Image
from pylab import *
# 读取图像到数组中
im = array(Image.open('empire.jpg').convert('L'))
# 新建一个图像
figure()
# 不使用颜色信息
gray()
# 在原点的左上角显示轮廓图像
contour(im, origin='image')
axis('equal')
axis('off')
figure()
# hist只接受一维数组,bins=128,直方图箱子或分组的数量
# flatten()按照行优先准则转为一维数组
hist(im.flatten(),128)
show()
3、交互式标注
import matplotlib
#指定使用交互式后端
matplotlib.use('TkAgg')
from PIL import Image
from pylab import *
im = array(Image.open('empire.jpg'))
imshow(im)
print('Please click 3 points')
# 点击图像3次
x = ginput(3)
# 打印三次点击的坐标
print('you clicked:',x)
show()
三、Numpy
Numpy包含数组对象(矩阵、向量、图像)以及线性代数等,可以实现矩阵乘积、转置、解方程系统、向量乘积和归一化,为图像变形、对变化进行建模、图像分类、图像聚类提供了基础
NumPy 中的数组对象是多维的,但是Numpy数组中的元素必须具有相同的数据类型
1、图像数组表示
im = array(Image.open('test.jpg'))
print(im.shape, im.dtype)
#f将数据类型转为float
im = array(Image.open('test.jpg').convert('L'),'f')
print(im.shape, im.dtype)
"""
(1512, 1080, 3) uint8
(1512, 1080) float32
"""
""" 数组中的元素可以使用下标访问。
位于坐标 i、j,以及颜色通道 k 的像素值可以通过value = im[i,j,k]访问
"""
# 带有颜色信息时结果为array([ 69, 109, 121], dtype=uint8)
# 灰度图像为一个数字,也就是灰度值
value = im[100,200]
# 带有颜色信息可用,1为通道编号,结果为109
value = im[100,200,1]
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]) # 倒数第二行
2、灰度变换
将图像读入 NumPy 数组对象后,可以对它们执行任意数学操作
NumPy总是将数组数据类型转换成能够表示数据的“最低”数据类型。对浮点数做乘积或
除法操作会使整数类型的数组变成浮点类型。
from PIL import Image
from numpy import *
im = array(Image.open('empire.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()))
#转为数组后,灰度图片为绿色,需要指定指定灰度颜色映射
imshow(im,cmap='gray')
show()
#array() 变换的相反操作可以使用 PIL 的 fromarray() 函数完成
pil_im = Image.fromarray(255-im)
# 转换数据类型
pil_im = Image.fromarray(uint8(im))
3、图像缩放
def imresize(im,sz):
""" 使用 PIL 对象重新定义图像数组的大小
im为数组,sz为图片大小size:[x,y]或(x,y) """
pil_im = Image.fromarray(uint8(im))
return array(pil_im.resize(sz))
# fromarray将数组转为PIL图像对象
Image.fromarray(imresize(im,[128,256])).show()
4、直方图均衡化
直方图均衡化是指将一幅图像的灰度直方图变平,使变换后的图像中每个灰度值的分布概率都相同,对图像灰度值进行归一化,增强图像的对比度。
def histeq(im,nbr_bins=256):
"""对灰度图像进行直方图均衡化"""
# normed被淘汰了,nbr_bins箱子数量,imhist数组,包含了每个箱中的像素数
imhist,bins = histogram(im.flatten(),nbr_bins)
cdf = imhist.cumsum() # 积累分布函数,将像素值范围映射到目标范围的归一化
cdf = 255 * cdf / cdf[-1] # 归一化0~255, cdf[-1]图像像素总和
# 使用积累分布函数的线性插值,计算新的像素值,bins[:-1]为x,cdf为y
im2 = interp(im.flatten(),bins[:-1],cdf)
return im2.reshape(im.shape), cdf
from PIL import Image
from numpy import *
im = array(Image.open('test.jpg').convert('L'))
im2,cdf = histeq(im)
5、图像平均
减少图像噪音的简单方式。从图像列表中计算出平均图像,假设所有图像大小相同,将像素简单相加,除以图像的数目。
还可以使用mean计算,但很多图像会占用很多内存
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')
6、图像的主成分分析(PCA)
降维,尽可能减少维度的情况下,尽量多的保留训练数据的信息。
通常使用SVD(Singular Value Decomposition,奇异值分解)计算主成分,但当矩阵维度很大时,计算非常慢。
PCA产生的投影矩阵可以将被视为原始坐标变换到现有坐标系,坐标系中的各个坐标按照重要性递减排列。
使用flatten()是图像变成一维向量,图像堆积得到矩阵,一行表示一幅图像。
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- 使用紧致技巧
# cov_matrix = np.cov(X, rowvar=False)
#rowvar=False意味着每列是一个变量,每行是一个观测。
M = dot(X,X.T) # 协方差矩阵
e,EV = linalg.eigh(M) # 特征值和特征向量
tmp = dot(X.T,EV).T # 这就是紧致技巧
V = tmp[::-1] # 由于最后的特征向量是我们所需要的,所以需要将其逆转
S = sqrt(e)[::-1] # 由于特征值是按照递增顺序排列的,所以需要将其逆转
# 归一化特征向量,图像的个数也就是dim
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
以下是个简单的应用
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()
7、使用pickle模块
pickle 模块可以接受几乎所有的 Python 对象,并且将其转换成字符串表示,该
过程叫做封装(pickling);从字符串表示中重构该对象,称为拆封(unpickling)
字符串表示可以方便地存储和传输
文件不确定的话,最好以二进制文件的形式读取和写入
# 保存均值和主成分数据
# 打开文件并保存
with open('font_pca_modes.pkl', 'wb') as f:
pickle.dump(immean,f)
pickle.dump(V,f)
# 载入均值和主成分数据
# 打开文件并载入
with open('font_pca_modes.pkl', 'rb') as f:
immean = pickle.load(f)
V = pickle.load(f)
# 保存一个数组 x 到文件中, fmt='%i'表示使用整数,delimiter=','元素之间用逗号分隔,默认为空格
savetxt('test.txt',x,'%i')
x = loadtxt('test.txt')
四、Scipy
SciPy是建立在 NumPy 基础上,用于数值运算,提供很多高效的操作,可以实现数值积分、优化、统计、信号处理,图像处理功能
1、图像模糊
图像模糊就是将(灰度)图像I 和一个高斯核进行卷积操作:
其中 * 表示卷积操作;Gσ 是标准差为 σ 的二维高斯核,定义为 :
高斯模糊通常是其他图像处理操作的一部分,比如图像插值操作、兴趣点计算以及
很多其他应用。
SciPy 有用来做滤波操作的 scipy.ndimage.filters 模块(应该已经被淘汰弃用了,各种操作可能直接定义在 scipy.ndimage模块中)。该模块使用快速一维分离
的方式来计算卷积。
一维分离卷积的基本思想是将二维卷积分解为两个连续的一维卷积;计算效率更高,占用存储空间更少,算法更简单容易实现,不涉及索引和边界处理。
基于卷积的交换律和结合律,即卷积操作可以重新排序或以不同的方式组合而不改变最终结果,
# 灰度图像
from PIL import Image
from numpy import *
# filters已弃用
from scipy.ndimage import gaussian_filter
im = array(Image.open('empire.jpg').convert('L'))
# 标准差为5,标准差σ越大越模糊
im2 = gaussian_filter(im,5)
#彩色图像,对每个颜色通道进行高斯模糊
im = array(Image.open('empire.jpg'))
im2 = zeros(im.shape)
for i in range(3):
im2[:,:,i] = gaussian_filter(im[:,:,i],5)
# im2 = array(im2,'uint8')也可以
im2 = uint8(im2)
Image.fromarray(im2).show()
2、图像导数
图像强度的变化可以用灰度图像 I(对于彩色图像,通常对每个颜色通道分别计算导数)的x和 y方向导数 Ix和Iy进行描述。
图像的梯度向量为
梯度有两个重要的属性,梯度的大小,描述了图像强度变化的强弱:
梯度的角度,描述了图像中在每个像素点上强度变化的方向:
NumPy 中的 arctan2() 函数返回弧度表示的有符号角度,角度的变化区间为 -π…π。
图像导数大多数可以通过卷积简单地实现:Ix=IDx 和 Iy=IDy
对于 Dx 和 Dy,通常选择 Prewitt 滤波器:
或者 Sobel 滤波器(边缘检测):
from PIL import Image
from numpy import *
from scipy.ndimage import sobel
im = array(Image.open('empire.jpg').convert('L'))
# Sobel 导数滤波器
# sobel() 函数的第二个参数表示选择 x 或者 y 方向导数,第三个参数保存输出的变量。
imx = zeros(im.shape)
sobel(im,1,imx)
imy = zeros(im.shape)
sobel(im,0,imy)
magnitude = sqrt(imx**2+imy**2)
在上述方法中,滤波器的尺度需要随着图像分辨率的变化而变化,而高斯导数滤波器在图像噪声方面更稳健,可以在任意尺寸上计算导数。
高斯导数滤波器,Gσx 和 Gσy 表示 Gσ 在 x 和 y 方向上的导数,Gσ 为标准差为 σ 的高斯函数:
sigma = 5 # 标准差
imx = zeros(im.shape)
gaussian_filter(im, (sigma,sigma), (0,1), imx)
imy = zeros(im.shape)
gaussian_filter(im, (sigma,sigma), (1,0), imy)
3、形态学:对象计数
形态学(或数学形态学)是度量和分析基本形状的图像处理方法的基本框架与集合
形态学通常用于处理二值图像,能够用于灰度图像。
二值图像是指图像的每个像素只能取两个值,通常是 0 和 1。
scipy.ndimage 中 的 label模 块 可 以 实 现 形 态 学 操 作。 你 可 以 使 用 scipy.ndimage 中的 binary_opening模块来实现二值图像的计数和度量功能。
binary_opening()函数用于对二值图像进行开运算(Opening Operation。开运算它通过对图像先腐蚀再膨胀来实现,主要用于去除图像中的小对象(如噪声点、小颗粒等),同时尽量保持图像中较大对象的形状和大小不变。
binary_closing() 函数用于对二值图像进行闭运算(Closing Operation)。闭运算它通过对图像先进行膨胀后腐蚀来实现,主要用于填充图像内部的小孔和缺口,使图像边缘更加平滑,同时保留图像的整体形状。
的操作。
from scipy.ndimage import label,binary_opening
# 载入图像,然后使用阈值化操作,以保证处理的图像为二值图像
im = array(Image.open('houses.png').convert('L'))
im = 1*(im<128)
labels, nbr_objects = label(im)
print("Number of objects:", nbr_objects)
# 形态学开操作更好地分离各个对象
im_open = binary_opening(im,ones((9,5)),iterations=2)
labels_open, nbr_objects_open = label(im_open)
print("Number of objects:", nbr_objects_open)
总结
图像处理,和数据可视化完全不同,理论和实践的难度升级,目前只能慢慢啃,如有错误,欢迎指正。
后续也许会有一些例子和练习
参考文献
《Python计算机视觉编程》朱文涛 袁勇译
matplotlib后端(backends)概述