文章目录
前言
基本的图像操作和处理
参考书:《Python计算机视觉编程》
基本的图像操作和处理
PIL
from PIL import Image
import os
读取图片
pil_im = Image.open('01.png')
# type(pil_im) # 类型: PIL 图像对象
转换成灰度图
pil_im = pil_im.convert('L')
转换成缩略图
pil_im.thumbnail((128,64)) # w,h 长宽比锁定
裁剪指定区域
box = (50,50,100,100) # 以原图左上角为(0,0),前两个值为裁剪图的左上角坐标,后两个为右下角
region = pil_im.crop(box) # 从图像中裁剪指定区域
region = region.transpose(Image.Transpose.ROTATE_180) # 将裁剪区域旋转180度
pil_im.paste(region,box) # 放回原图
调整图像的尺寸
pil_im.resize((64,128)) # w,h 长宽比可以改变
旋转图像
pil_im.rotate(45) # 逆时针
查看和保存
pil_im.show() # 在图像查看器中显示
# pil_im.save('02.jpg') # save保存处理后的图片
# return [os.path.join(path,f) for f in os.listdir(path) if f.endswith('.jpg')] # 返回目录中所有 JPG 图像的文件名列表
Matplotlib
from PIL import Image
from pylab import *
matplotlib.use('TkAgg')
在图像上画点、线
# 读取图像到数组中
im = array(Image.open('01.png'))
# 绘制图像
imshow(im)
# 4个点
x = [100,100,400,400]
y = [200,500,200,500]
# 使用红色星状标记绘制点
plot(x,y,'r*')
# 绘制连接前两个点的线,默认为蓝色
# '-' 实线,'--' 虚线,':' 点线;'.' 点,'o' 圆圈,'s' 正方形,'*' 星形,'+' 加号,'x' 叉号
plot(x[:2],y[:2]) # x列表,y列表,而非(x0,y0),(x1,y1)...
plot(x[1:3],y[1:3],'go-') # 带有圆圈标记的绿线
plot(x[2:],y[2:],'ks:') # 带有正方形标记的黑色点线
# 添加标题,显示绘制的图像
title('Plotting')
axis('off') # 去除坐标轴
show() # 每个脚本只能调用一次
获取图像轮廓和直方图
# 将图像灰度化
im = array(Image.open('01.png').convert('L'))
# 新建一个图像
figure() # Figure 是图形窗口的抽象
subplot(211) # 创建子图
gray() # 不使用颜色信息
# 在原点的左上角显示轮廓图像
contour(im, origin='image')
axis('equal') # 设置 x 和 y 轴的比例相等
axis('off')
subplot(212)
# 绘制直方图: 该图像像素值的分布情况
# hist()只接受一维数组作为输入,flatten()将任意数组按照行优先转换成一维数组
# 第二个参数指定灰度区间的数目,值越小长方形越宽
hist(im.flatten(),64)
show()
交互式标注
im = array(Image.open('01.png'))
imshow(im)
print ('Please click 3 points')
x = ginput(3)
print ('you clicked:',x)
show()
# 报错:当前的用户接口后端不支持前端显示并交互
# 解决:matplotlib.use('TkAgg') # 显式指明使用的后端
NumPy
from PIL import Image
from numpy import *
from pylab import *
用数组表示图像
多维数组像是列表的列表,数组内的元素数据类型相同
im = array(Image.open('01.png'))
print (im.shape, im.dtype) #(行、列、颜色通道),png多一个透明通道(𝛼通道)
im = array(Image.open('01.png').convert('L'),'f') # 指定'f',将数据类型转换为浮点型
print (im.shape, im.dtype)
灰度变换
图片灰度通常为0-255(8位灰度图像),0表示黑色
im = array(Image.open('01.png').convert('L'))
# print (int(im.min()), int(im.max())) # 输出图像中的最小和最大像素值
h,w = im.shape[0:2] # 获取图像的大小
im2 = 255 - im # 对图像进行反相处理
im3 = (100.0/255) * im + 100 # 将图像像素值变换到 100...200 区间
im4 = 255.0 * (im/255.0)**2 # 对图像像素值求平方后得到的图像
数组转回图像
pil_im = Image.fromarray(im4) # 将uint8数组转换为PIL,array()的反操作
# pil_im = Image.fromarray(uint8(im)) # 不确定数据的类型时,先转为uint8
pil_im.show()
# imshow(im2.reshape(h,w)) # 或使用 reshape()将一维数组转为二维图像
# show()
直方图均衡化
使变换后的图像中每个灰度值的分布概率都相同
增强图像的对比度,图像灰色区域的细节变得清晰
def histeq(im,nbr_bins=256):
""" 对一幅灰度图像进行直方图均衡化:
input: im,灰度图; nbr_bins,灰度区间的数目
output: im2,直方图均衡化后的图像; cdf,用来做像素值映射的累积分布函数 """
# 计算图像的直方图
imhist,bins = histogram(im.flatten(),nbr_bins)
cdf = imhist.cumsum() # 累积分布函数
cdf = 255 * cdf / cdf[-1] # 归一化
# 使用累积分布函数的线性插值,计算新的像素值
im2 = interp(im.flatten(),bins[:-1],cdf)
return im2.reshape(im.shape), cdf
im = array(Image.open('01.png').convert('L'))
im2,cdf = histeq(im)
图像平均
图像大小相同,则直接相加,然后除以图像的数目;减少噪声
# 打开第一幅图像,将其存储在浮点型数组中
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)
主成分分析
主成分:协方差矩阵对应最大特征值的特征向量, 对数据影响最大的特征
投影矩阵 V 的每行向量都是正交的,包含了训练数据方差依次减少的坐标方向
def pca(X):
""" 主成分分析:
输入:矩阵 X ,其中该矩阵中存储训练数据,每一行为一条训练数据
一行表示一幅图像
返回:投影矩阵(按照维度的重要性排序)、方差和均值 """
# 获取维数
num_data,dim = X.shape # num_data数据点的数量,dim数据的维度
# 数据中心化
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]): # range()返回一个列表,arange()返回一个数组,xrange()返回一个产生器
V[:,i] /= S
else:
# PCA-使用SVD方法(奇异值分解)
U,S,V = linalg.svd(X)
V = V[:num_data] # 仅仅返回前 nun_data 维的数据才合理
# 返回投影矩阵、方差和均值
return V,S,mean_X
使用pickle模块
封装:接受python对象,将其转换为字符串表示(写入.pkl文件)
拆封:从字符串表示中重构该对象
import pickle
# 保存均值和主成分数据
V,S,immean = pca(X)
with open('font_pca_modes.pkl', 'wb') as f: # w写,b二进制,r读,a文件末尾
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)
读写文本文件
savetxt('test.txt',x,'%i')
x = loadtxt('test.txt')
SciPy
图像模糊
高斯模糊:将(灰度)图像I和一个高斯核进行卷积操作
\qquad $ I_{\sigma}=I\ast G_{\sigma}$
\qquad $ G_{\sigma}=\frac{1}{2\pi{\sigma}2}e{-(x2+y2)/2{\sigma}^2} ,标准差为 ,标准差为 ,标准差为\sigma$的二维高斯核
\qquad σ \sigma σ越大,处理后的图像细节丢失越多
from PIL import Image
from numpy import *
from scipy import ndimage
im = array(Image.open('01.png'))
im2 = ndimage.gaussian_filter(im,2) # guassian_filter()的最后一个参数表示标准差𝜎
# 报错:使用scipy.ndimage.gaussian_filter而非scipy.ndimage.filters.gaussian_filter
# 解决:filters模块已不再使用,scipy.ndimage.gaussian_filter直接对彩色图像进行高斯模糊,不需要逐通道遍历操作
图像导数
图像 I I I的梯度向量: ∇ I = [ I x , I y ] T \nabla{I}={[I_x,I_y]}^T ∇I=[Ix,Iy]T
\qquad 梯度的大小: ∣ ∇ I ∣ = I x 2 + I y 2 |\nabla{I}|=\sqrt{{I_x}^2+{I_y}^2} ∣∇I∣=Ix2+Iy2,描述图像强度大小的变化
\qquad 梯度的角度: α = arctan 2 ( I y , I x ) \alpha=\arctan2(I_y,I_x) α=arctan2(Iy,Ix),描述图像中在每个点(像素)上强度变化最大的方向
通过卷积以离散近似的方式计算图像的导数:
\qquad I x = I ∗ D x I_x=I\ast D_x Ix=I∗Dx, I y = I ∗ D y I_y=I\ast D_y Iy=I∗Dy
\qquad 对于 D x D_x Dx和 D y D_y Dy,通常选择 Prewitt 滤波器,或者 Sobel 滤波器
上述滤波器的尺度需要随着图像分辨率的变化而变化,要在任意尺度上计算导数,可以使用高斯导数滤波器:
\qquad I x = I ∗ G σ x I_x=I\ast G_{\sigma x} Ix=I∗Gσx, I y = I ∗ G σ y I_y=I\ast G_{\sigma y} Iy=I∗Gσy
im = array(Image.open('01.png').convert('L'))
# Sobel 导数滤波器
imx = zeros(im.shape)
ndimage.sobel(im,1,imx) # 1表示x方向
imy = zeros(im.shape)
ndimage.sobel(im,0,imy) # 0表示y方向
# 高斯导数滤波器
sigma = 1 # 标准差
imx = zeros(im.shape)
ndimage.gaussian_filter(im, (sigma,sigma), (0,1), imx)
imy = zeros(im.shape)
ndimage.gaussian_filter(im, (sigma,sigma), (1,0), imy)
magnitude = sqrt(imx**2+imy**2)
pil_im = Image.fromarray(magnitude)
pil_im.show()
形态学:对象计数
形态学:度量和分析基本形状,通常用于处理二值图像
二值图像:图像的每个像素只能取两个值,通常是 0 和 1
im = array(Image.open('01.png').convert('L'))
im = 1*(im<128) # 通过阈值化方式来确保该图像是二值图像
labels, nbr_objects = ndimage.label(im)
print ("Number of objects:", nbr_objects)
# 开操作 更好地分离各个对象
# (y,x)表示以一个像素为中心时,使用哪些相邻像素,此处为在y方向上使用9个像素(上面4个、像素本身、下面 4个),在x方向上使用5个
# iterations 决定执行该操作的次数
im_open = ndimage.binary_opening(im,ones((9,5)),iterations=2)
labels_open, nbr_objects_open = ndimage.label(im_open)
print ("Number of objects:", nbr_objects_open)
读写.mat文件
from scipy.io import loadmat,savemat
# 读,返回字典类型dic
data = loadmat('test.mat')
# 写
savemat('test.mat',data)
以图像形式保存数组
from scipy.misc import imsave
imsave('test.jpg',im)
图像去噪
ROF模型
(灰度)图像 I I I的全变差定义为梯度范数之和
\qquad 连续表示: J ( I ) = ∫ ∣ ∇ I ∣ d x J(I)=\int{|\nabla I|}dx J(I)=∫∣∇I∣dx
\qquad 离散表示: J ( I ) = ∑ x ∣ ∇ I ∣ J(I)=\sum_{x} {|\nabla I|} J(I)=∑x∣∇I∣
ROF 模型的目标函数: min U { ∣ ∣ I − U ∣ ∣ 2 + 2 λ J ( U ) } \min_{U}\{{||I-U||}^2+2\lambda J(U)\} minU{∣∣I−U∣∣2+2λJ(U)}
\qquad 范数 ∣ ∣ I − U ∣ ∣ ||I-U|| ∣∣I−U∣∣度量去噪后图像 U U U和原始图像 I I I的差异
\qquad 本质上该模型使去噪后的图像像素值“平坦”变化,但是在图像区域的边缘上,允许去噪后的图像像素值“跳跃”变化