1.1 PIL:Python图像处理类库
PIL(Python Imaging Library Python,图像处理类库)提供了通用的图像处理功能,以及大量有用的基本图像操作,比如图像缩放、裁剪、旋转、颜色转换等。
1.1.1转换图像格式
通过 save() 方法,PIL 可以将图像保存成多种格式的文件。
save(filename,format)
代码:
from PIL import Image
im = Image. open ( 'JMU1.jpg' , "r" )
im1 = im. save( 'JMU1.png' , 'png' )
结果: 分析: 通过 save() 方法成功将jpg图像重新保存成png格式。
1.1.2创建缩略图
thumbnail()方法接受一个元组参数(该参数指定生成缩略图的大小),然后将图像转换成符合元组参数指定大小的缩率图。 例如,创建最长边为128像素的缩略图,可以使用下列命令:
pil_im.thumbnail((128,128))
代码1:
from PIL import Image
from pylab import *
im = Image. open ( 'JMU1.jpg' , "r" )
subplot( 121 )
title( '(a)' )
axis( 'off' )
imshow( im)
im. save( 'JMU1.png' , 'png' )
im. thumbnail( ( 128 , 128 ) , resample= Image. BICUBIC)
subplot( 122 )
title( '(b)' )
axis( 'off' )
imshow( im)
show( )
结果1: 代码2:
from PIL import Image
im = Image. open ( 'JMU1.jpg' , "r" )
im. save( 'JMU1.png' , 'png' )
im. show( )
im. thumbnail( ( 128 , 128 ) , resample= Image. BICUBIC)
im. show( )
结果2: 分析: 从结果1中可以看出,图像在同样大小的情况下展示,会变得模糊;从结果2中可以看出,正常情况下展示指定像素的缩略图,图像会变小。
1.1.3复制和粘贴图像区域
使用crop()方法可以从一幅图像中裁剪指定区域:
box=(100,100,400,400) region=pil_im.crop(box)
该区域使用四元组来指定。四元组的坐标依次是(左,上,右,下)。PIL中指定坐标系的左上角坐标为(0,0)。我们可以旋转上面代码中获取的区域,然后使用paste()方法将该区域放回去,具体实现如下:
region=region.transpose(Image.ROTATE_180) pil_im.paste(region,box)
代码:
from PIL import Image
from pylab import *
im = Image. open ( 'JMU1.jpg' , "r" )
subplot( 131 )
title( '(a)' )
axis( 'off' )
imshow( im)
box = ( 200 , 350 , 400 , 450 )
subplot( 132 )
title( '(b)' )
axis( 'off' )
imshow( im. crop( box) )
region = im. crop( box)
im. paste( region, ( 400 , 200 ) , None )
subplot( 133 )
title( '(c)' )
axis( 'off' )
imshow( im)
show( )
结果: 分析: 从图像中裁剪的区域box = (200,350,400,450)表示裁剪区域左上角的x坐标为200,左上角y坐标为350,右下角x坐标为400,右下角y坐标为450,如结果中的(b)所示。使用paste()方法将裁剪的区域粘贴在左上角位置为(400,200)的区域。
1.1.4调整尺寸和旋转
调用resize()方法调整一幅图像的尺寸。
out=pil_im.resize((128,128))
调用rotate()方法旋转一幅图像,使用逆时针方式表示旋转角度。
out=pil_im.rotate(45)
代码:
from PIL import Image
from pylab import *
im = Image. open ( 'boy.jpg' , "r" )
subplot( 331 )
title( '(a)' )
axis( 'off' )
imshow( im)
Image. FLIP_LEFT_RIGHT = im. transpose( Image. FLIP_LEFT_RIGHT)
subplot( 332 )
title( '(b)' )
axis( 'off' )
imshow( Image. FLIP_LEFT_RIGHT)
Image. FLIP_TOP_BOTTOM = im. transpose( Image. FLIP_TOP_BOTTOM)
subplot( 333 )
title( '(c)' )
axis( 'off' )
imshow( Image. FLIP_TOP_BOTTOM)
Image. ROTATE_45 = im. rotate( 45 )
subplot( 334 )
title( '(d)' )
axis( 'off' )
imshow( Image. ROTATE_45)
Image. ROTATE_90 = im. transpose( Image. ROTATE_90)
subplot( 335 )
title( '(e)' )
axis( 'off' )
imshow( Image. ROTATE_90)
Image. ROTATE_180 = im. transpose( Image. ROTATE_180)
subplot( 336 )
title( '(f)' )
axis( 'off' )
imshow( Image. ROTATE_180)
Image. ROTATE_270 = im. transpose( Image. ROTATE_270)
subplot( 337 )
title( '(g)' )
axis( 'off' )
imshow( Image. ROTATE_270)
Image. TRANSPOSE = im. transpose( Image. TRANSPOSE)
subplot( 338 )
title( '(h)' )
axis( 'off' )
imshow( Image. TRANSPOSE)
Image. TRANSVERSE = im. transpose( Image. TRANSVERSE)
subplot( 339 )
title( '(i)' )
axis( 'off' )
imshow( Image. TRANSVERSE)
show( )
结果: 分析: (b)将图像左右翻转,(c)将图像上下翻转,(d)将图像逆时针旋转45°,(e)将图像逆时针旋转90°,(f)将图像逆时针旋转180°,(g)将图像逆时针旋转270°,(h)将图像进行转置(相当于顺时针旋转90°),(i)将图像进行转置,再水平翻转。
1.2Matplotlib
处理数学运算、绘制图表,或者在图像上绘制点、直线和曲线时,Matplotlib是个很好的类库,具有比PIL更强大的绘图功能。
1.2.1绘制图像、点和线
代码:
from PIL import Image
from pylab import *
# 添加中文字体支持
from matplotlib. font_manager import FontProperties
font = FontProperties( fname= r"c:\windows\fonts\SimSun.ttc" , size= 14 )
# 读取图像到数组中
im = array( Image. open ( 'JMU3.jpg' ) )
figure( )
# 绘制有坐标轴的
subplot( 121 )
imshow( im)
x = [ 100 , 100 , 400 , 400 ]
y = [ 200 , 500 , 200 , 500 ]
# 使用红色星状标记绘制点
plot( x, y, 'r*' )
# 绘制连接两个点的线(默认为蓝色)
plot( x[ : 2 ] , y[ : 2 ] )
title( u'带坐标轴' , fontproperties= font)
# 不显示坐标轴的
subplot( 122 )
imshow( im)
x = [ 100 , 100 , 400 , 400 ]
y = [ 200 , 500 , 200 , 500 ]
plot( x, y, 'r*' )
plot( x[ : 2 ] , y[ : 2 ] )
axis( 'off' )
title( u'不带坐标轴' , fontproperties= font)
show( )
# show()命令首先打开图形用户界面(GUI),然后新建一个窗口,该图形用户界面会循环阻断脚本,然后暂停,
# 直到最后一个图像窗口关闭。每个脚本里,只能调用一次show()命令,通常相似脚本的结尾调用。
结果: axis(‘off’) 命令可以使坐标轴不显示。 在绘图时,有很多选项可以控制图像的颜色和样式。最有用的一些短命令如表1-1、表1-2和表1-3所示。使用方法见下面的例子:
1.2.2图像轮廓和直方图
用PIL的convert()方法将图像转化为灰度图像。用hist()函数绘制(灰度)图像的直方图。 代码:
from PIL import Image
from pylab import *
# 添加中文字体支持
from matplotlib. font_manager import FontProperties
font = FontProperties( fname= r"c:\windows\fonts\SimSun.ttc" , size= 14 )
im = array( Image. open ( "JMU2.jpg" ) . convert( 'L' ) ) # 打开图像,并转成灰度图像
figure( )
subplot( 131 )
axis( 'off' )
imshow( im)
axis( 'equal' )
axis( 'off' )
title( u'(a)原图像' , fontproperties= font)
# 使用颜色信息
subplot( 132 )
gray( )
# 在原点的左上角显示轮廓图像
contour( im, origin= 'image' )
axis( 'equal' )
axis( 'off' )
title( u'(b)图像轮廓' , fontproperties= font)
subplot( 133 )
hist( im. flatten( ) , 128 )
title( u'(c)图像直方图' , fontproperties= font)
plt. xlim( [ 0 , 260 ] )
plt. ylim( [ 0 , 11000 ] )
show( )
结果:
1.2.3交互式标注
有时用户需要和某些应用交互,例如在一幅图像中标记一些点,或者标注一些训练数据。PyLab库中的ginput()函数就可以实现交互式标注。 代码:
from PIL import Image
from pylab import *
im = array( Image. open ( 'JMU2.jpg' ) )
imshow( im)
print ( 'Please click 3 points' )
x = ginput( 3 )
print ( 'you clicked:' , x)
show( )
结果: 分析: 用ginput()交互注释,代码中设置的交互注释数据点设置为3个,显示读取的图像后,然后注释,会将注释点的坐标打印出来。
1.3NumPy
NumPy是非常有名的 Python 科学计算工具包,其中包含了大量有用的思想,比如数组对象(用来表示向量、矩阵、图像等)以及线性代数函数。NumPy中的数组对象可以帮助实现数组中重要的操作,比如矩阵乘积、转置、解方程系统、向量乘积和归一化, 这为图像变形、对变化进行建模、图像分类、图像聚类等提供了基础。
1.3.1图像数组表示
NumPy中的数组对象是多维的,可以用来表示向量、矩阵和图像。一个数组对象很像一个列表(或者是列表的列表),但是数组中所有的元素必须具有相同的数据类型。除非创建数组对象时指定数据类型,否则数据类型会按照数据的类型自动确定。 代码:
from PIL import Image
from pylab import *
im = array( Image. open ( 'JMU2.jpg' ) )
print ( im. shape, im. dtype)
im = array( Image. open ( 'JMU2.jpg' ) . convert( 'L' ) , 'f' )
print ( im. shape, im. dtype)
结果: 分析: 每行的第一个元组表示图像数组的大小(行、列、颜色通道),紧接着的字符串表 示数组元素的数据类型。因为图像通常被编码成无符号八位整数(uint8),所以在第一种情况下,载入图像并将其转换到数组中,数组的数据类型为“uint8”。在第二种情况下,对图像进行灰度化处理,并且在创建数组时使用额外的参数“f”;该参数将数据类型转换为浮点型。
1.3.2灰度变换
将图像读入 NumPy 数组对象后,我们可以对它们执行任意数学操作。一个简单的例子就是图像的灰度变换。 代码:
from PIL import Image
from numpy import *
from pylab import *
im = array( Image. open ( "JMU3.jpg" ) . convert( 'L' ) )
print ( int ( im. min ( ) ) , int ( im. max ( ) ) )
im2 = 255 - im # 对图像进行反相处理
print ( int ( im2. min ( ) ) , int ( im2. max ( ) ) )
im3 = ( 100.0 / 255 ) * im + 100 # 将图像像素值变换到 100...200 区间
print ( int ( im3. min ( ) ) , int ( im3. max ( ) ) )
im4 = 255.0 * ( im/ 255.0 ) ** 2 # 对图像像素值求平方后得到的图像
print ( int ( im4. min ( ) ) , int ( im4. max ( ) ) )
figure( )
gray( )
subplot( 1 , 3 , 1 )
imshow( im2)
axis( 'off' )
title( r'(a) $f(x)=255-x$' )
subplot( 1 , 3 , 2 )
imshow( im3)
axis( 'off' )
title( r'(b) $f(x)=\frac{100}{255}x+100$' )
subplot( 1 , 3 , 3 )
imshow( im4)
axis( 'off' )
title( r'(c) $f(x)=255(\frac{x}{255})^2$' )
show( )
结果: 分析: (a)对图像进行反相处理,(b)将图像的像素值变换到100…200区间,(c)对图像像素值求平方后得到的图像。
1.3.3直方图均衡化
直方图均衡化是指将一幅图像的灰度直方图变平,使变换后的图像中每个灰度值的分布概率都相同。 代码:
import cv2
import matplotlib. pyplot as plt
import numpy as np
gray_level = 256 # 灰度级
def pixel_probability ( img) :
assert isinstance ( img, np. ndarray)
prob = np. zeros( shape= ( 256 ) )
for rv in img:
for cv in rv:
prob[ cv] += 1
r, c = img. shape
prob = prob / ( r * c)
return prob
def probability_to_histogram ( img, prob) :
prob = np. cumsum( prob) # 累计概率
img_map = [ int ( i * prob[ i] ) for i in range ( 256 ) ] # 像素值映射
# 像素值替换
assert isinstance ( img, np. ndarray)
r, c = img. shape
for ri in range ( r) :
for ci in range ( c) :
img[ ri, ci] = img_map[ img[ ri, ci] ]
return img
def plot ( y, name) :
plt. figure( num= name)
plt. bar( [ i for i in range ( gray_level) ] , y, width= 1 )
if __name__ == '__main__' :
img = cv2. imread( "cat.jpg" , 0 ) # 读取灰度图
prob = pixel_probability( img)
plot( prob, "原图直方图" )
# 直方图均衡化
img = probability_to_histogram( img, prob)
cv2. imwrite( "source_hist.jpg" , img) # 保存图像
prob = pixel_probability( img)
plot( prob, "直方图均衡化结果" )
plt. show( )
结果:
分析: 均衡化之后,图像的亮度变得比较均衡,图像一些细节也得以观察到。
1.3.4图像平均
图像平均操作是减少图像噪声的一种简单方式,通常用于艺术特效。可以简单地从图像列表中计算出一幅平均图像。假设所有的图像具有相同的大小,我们可以将这些图像简单地相加,然后除以图像的数目,来计算平均图像。
1.3.5使用pickle模块
如果想要保存一些结果或者数据以方便后续使用,Python中的pickle模块非常有用。pickle模块可以接受几乎所有的Python对象,并且将其转换成字符串表示,该过程叫做封装。从字符串表示中重构该对象,称为拆封。 这些字符串表示可以方便地存储和传输。
1.4SciPy
SciPy是建立在NumPy基础上,用于数值运算的开源工具包。SciPy提供很多高效的操作,可以实现数值积分、优化、统计、信号处理,以及对我们来说最重要的图像处理功能。
1.4.1图像模糊
图像的高斯模糊是非常经典的图像卷积例子。本质上,图像模糊就是将(灰度)图像
I
I
I 和一个高斯核进行卷积操作:
I
σ
=
I
∗
G
σ
I_{\sigma }=I*G_{\sigma}
I σ = I ∗ G σ 其中
∗
*
∗ 表示卷积操作:
G
σ
G_{\sigma}
G σ 是标准差为
σ
\sigma
σ 的二维高斯核,定义为:
G
σ
=
1
2
π
σ
e
−
(
x
2
+
y
2
)
/
2
σ
2
G_{\sigma} =\frac{1}{2\pi \sigma }e^{-(x^{2}+y^{2})/2\sigma ^{2}}
G σ = 2 πσ 1 e − ( x 2 + y 2 ) /2 σ 2 代码:
from PIL import Image
from pylab import *
from scipy. ndimage import filters
# 添加中文字体支持
from matplotlib. font_manager import FontProperties
font = FontProperties( fname= r"c:\\windows\\fonts\\SimSun.ttc" , size= 14 )
im = array( Image. open ( 'JMU2.jpg' ) . convert( 'L' ) )
figure( )
gray( )
axis( 'off' )
subplot( 1 , 4 , 1 )
axis( 'off' )
title( u'原图' , fontproperties= font)
imshow( im)
for bi, blur in enumerate ( [ 2 , 5 , 10 ] ) :
im2 = zeros( im. shape)
im2 = filters. gaussian_filter( im, blur)
im2 = np. uint8( im2)
imNum= str ( blur)
subplot( 1 , 4 , 2 + bi)
axis( 'off' )
title( u'标准差为' + imNum, fontproperties= font)
imshow( im2)
show( )
结果: 分析: 随着标准差
σ
\sigma
σ 的增大,图像的模糊程度也增大,图像丢失的细节也越多。
1.4.2图像导数
强度的变化可以用灰度图像
I
I
I 的
x
和
y
x和y
x 和 y 方向导数
I
x
和
I
y
I_{x}和I_{y}
I x 和 I y 进行描述。 图像的梯度向量为
▽
I
=
[
I
x
,
I
y
]
T
\triangledown I=[I_{x},I_{y}]^{T}
▽ I = [ I x , I y ] T 。梯度有两个重要的属性,一是梯度的大小:
∣
▽
I
∣
=
I
x
2
+
I
y
2
\left | \triangledown I \right |=\sqrt{I_{x}^{2}+I_{y}^{2}}
∣ ▽ I ∣ = I x 2 + I y 2
它描述了图像强度变化的强弱,一是梯度的角度:
α
=
a
r
c
t
a
n
2
(
I
y
,
I
x
)
\alpha =arctan2(I_{y},I_{x})
α = a rc t an 2 ( I y , I x ) 描述了图像中在每个点(像素)上强度变化最大的方向。 我们可以用离散近视的方式来计算图像的导数。图像导数大多数可以通过卷积简单地实现:
I
x
=
I
∗
D
x
和
I
y
=
I
∗
D
y
I_{x}=I*D_{x}和I_{y}=I*D_{y}
I x = I ∗ D x 和 I y = I ∗ D y 对于
D
x
和
D
y
D_{x}和D_{y}
D x 和 D y ,通常选择Prewitt滤波器或者Sobel滤波器。这些导数滤波器可以使用scipy.ndimage.filters模块的标准卷积操作来简单地实现。 代码:
from PIL import Image
from pylab import *
from scipy. ndimage import filters
import numpy
# 添加中文字体支持
from matplotlib. font_manager import FontProperties
font = FontProperties( fname= r"c:\windows\fonts\SimSun.ttc" , size= 14 )
im = array( Image. open ( 'JMU3.jpg' ) . convert( 'L' ) )
gray( )
subplot( 1 , 4 , 1 )
axis( 'off' )
title( u'(a)原始灰度图像' , fontproperties= font)
imshow( im)
# Sobel derivative filters
imx = zeros( im. shape)
filters. sobel( im, 1 , imx)
subplot( 1 , 4 , 2 )
axis( 'off' )
title( u'(b)x导数图像' , fontproperties= font)
imshow( imx)
imy = zeros( im. shape)
filters. sobel( im, 0 , imy)
subplot( 1 , 4 , 3 )
axis( 'off' )
title( u'(c)y导数图像' , fontproperties= font)
imshow( imy)
mag = 255 - numpy. sqrt( imx** 2 + imy** 2 )
subplot( 1 , 4 , 4 )
title( u'(d)梯度大小图像' , fontproperties= font)
axis( 'off' )
imshow( mag)
show( )
结果: 分析: 在这个方法中,滤波器的尺度需要随着图像分辨率的变化而变化。为了在图像噪声方面更稳健,以及在任意尺度上计算导数,我们可以使用高斯导数滤波器 。处理方法如下。 代码:
# -*- coding: utf-8 -*-
from PIL import Image
from pylab import *
from scipy. ndimage import filters
import numpy
def imx ( im, sigma) :
imgx = zeros( im. shape)
filters. gaussian_filter( im, sigma, ( 0 , 1 ) , imgx)
return imgx
def imy ( im, sigma) :
imgy = zeros( im. shape)
filters. gaussian_filter( im, sigma, ( 1 , 0 ) , imgy)
return imgy
def mag ( im, sigma) :
imgmag = 255 - numpy. sqrt( imgx ** 2 + imgy ** 2 )
return imgmag
im = array( Image. open ( 'JMU3.jpg' ) . convert( 'L' ) )
figure( )
gray( )
sigma = [ 2 , 5 , 10 ]
for i in sigma:
subplot( 3 , 4 , 4 * ( sigma. index( i) ) + 1 )
axis( 'off' )
imshow( im)
imgx= imx( im, i)
subplot( 3 , 4 , 4 * ( sigma. index( i) ) + 2 )
axis( 'off' )
imshow( imgx)
imgy= imy( im, i)
subplot( 3 , 4 , 4 * ( sigma. index( i) ) + 3 )
axis( 'off' )
imshow( imgy)
imgmag= mag( im, i)
subplot( 3 , 4 , 4 * ( sigma. index( i) ) + 4 )
axis( 'off' )
imshow( imgmag)
show( )
结果:
1.4.3形态学:对象计数
形态学(或数学形态学)是度量和分析基本形状的图像处理方法的基本框架与集合。 形态学通常用于处理二值图像,但是也能够用于灰度图像。二值图像是指图像的每 个像素只能取两个值,通常是 0 和 1。二值图像通常是,在计算物体的数目,或者度量其大小时,对一幅图像进行阈值化后的结果。 代码:
from PIL import Image
from scipy. ndimage import measurements, morphology
from pylab import *
""" This is the morphology counting objects example in Section 1.4. """
# 添加中文字体支持
from matplotlib. font_manager import FontProperties
font = FontProperties( fname= r"c:\windows\fonts\SimSun.ttc" , size= 14 )
figure( )
gray( )
im = array( Image. open ( 'JMU2.jpg' ) . convert( 'L' ) )
subplot( 221 )
imshow( im)
axis( 'off' )
title( u'(a)原始二值图像' , fontproperties= font)
im = ( im < 128 )
labels, nbr_objects = measurements. label( im)
print ( "Number of objects:" , nbr_objects)
subplot( 222 )
imshow( labels)
axis( 'off' )
title( u'(b)原始图像的标签图像' , fontproperties= font)
im_open = morphology. binary_opening( im, ones( ( 9 , 5 ) ) , iterations= 2 )
subplot( 223 )
imshow( im_open)
axis( 'off' )
title( u'(c)开操作后的二值图像' , fontproperties= font)
labels_open, nbr_objects_open = measurements. label( im_open)
print ( "Number of objects:" , nbr_objects_open)
subplot( 224 )
imshow( labels_open)
axis( 'off' )
title( u'(d)开操作后图像的标签图像' , fontproperties= font)
show( )
结果: 分析: 通过阈值化方式来确保该图像是二值图像。通过和 1 相乘,脚本将布尔数组转换成二进制表示。然后,我们使用 label()函数寻找单个的物体,并且按照它们属于哪个对象将整数标签给像素赋值。
1.5总结
本章讲解了操作和处理图像的基础知识,这些知识在《数字图像处理》课程中已经多次使用过了。现在又通过实验对这些知识更进一步学习了。