图像处理入门:如何处理不同类型的图像
本节将讨论不同的图像处理函数(使用点变换和几何变换),以及如何处理不同类型的图像。
1.5.1 处理不同的文件格式和图像类型
图像可以以不同的文件格式和不同的模式(类型)保存。接下来我们将讨论如何使用Python库来处理不同文件格式和类型的图像。
1.文件格式
图像文件可以有不同的格式,其中一些流行的格式包括BMP(8位、24位、32位)、PNG、JPG(JPEG)、GIF、PPM、PNM和TIFF。读者不需要担心图像文件的特定格式(如何存储元数据)以及从中提取数据。
Python图像处理库将读取图像,并提取数据和一些其他有用的信息(例如图像尺寸、类型/模式和数据类型)。
从一种文件格式转换为另一种文件格式 使用PIL可以读取一种文件格式的图像并将其保存为另一种文件格式,将PNG格式的图像保存为JPG格式的图像的代码如下:
im = Image.open("../images/parrot.png")
print(im.mode)
# RGB
im.save("../images/parrot.jpg")
但如果PNG文件是在RGBA模式下,则读者在将其保存为JPG格式之前需要将其转换为RGB模式,否则将报错。以下代码展示了先转换再保存的方法:
im = Image.open("../images/hill.png")
print(im.mode)
# RGBA
im.convert('RGB').save("../images/hill.jpg") # first convert to RGB mode
2.图像类型(模式)
图像可以是以下不同的类型。
- 单通道图像:每个像素由单个值表示,二值(单色)图像(每个像素由一个0~1位表示)和灰度图像(每个像素由8位表示,其值通常在0~255内)都是单通道图像。
- 多通道图像:每个像素由一组值表示。
- 三通道图像。RGB图像和HSV图像都是三通道图像。RGB图像的每个像素由三元组( r , g , b )值表示,这三个值分别表示每个像素的红色、绿色和蓝色的通道颜色值。HSV图像的每个像素由三元组( h , s , v )值表示,这三个值分别表示每个像素的色调(颜色)、饱和度(色彩,即颜色与白色的混合程度)和值(亮度,即颜色与黑色的混合程度)的通道颜色值。HSV模型描述颜色的方式与人眼感知颜色的方式是相似的。
- 四通道图像。例如,RGBA图像的每个像素由四元组( r , g , b , a )值表示,其中最后一个通道表示透明度。
一种图像模式转换为另一种图像模式 读者可以在读取图像本身的同时将RGB图像转换为灰度图像,如下面的代码所示:
im = imread("images/parrot.png", as_gray=True)
print(im.shape)
#(362L, 486L)
请注意,在将某些彩色图像转换为灰度图像时,可能会丢失一些信息。以下代码显示了使用石原板(Ishihara plate)检测色盲这样一个例子,这里使用color模块中的rgb2gray ()函数,彩色和灰度图像并排显示。
im = imread("../images/Ishihara.png")
im_g = color.rgb2gray(im)
plt.subplot(121), plt.imshow(im, cmap='gray'), plt.axis('off')
plt.subplot(122), plt.imshow(im_g, cmap='gray'), plt.axis('off')
plt.show()
运行上述代码,输出结果如图1-15所示。可以看到,彩色图像转换成了灰度图像。在彩色图像中,数字8是可见的,而在转换后的灰色版本图像中,数字8几乎不可见。
图1-15 彩色图像至灰色图像的转换
3.一些颜色空间(通道)
图像的几个常用通道/颜色空间包括RGB、HSV、XYZ、YUV、YIQ、YPbPr、YCbCr和YDbDr。我们可以使用仿射映射将图像从一个颜色空间转换到另一个颜色空间。RGB颜色空间与YIQ颜色空间的线性映射关系矩阵如图1-16所示。
图1-16 RGB颜色空间与YIQ颜色空间映射关系矩阵
从一个颜色空间转换到另一个颜色空间 读者可以使用库函数从一个颜色空间转换到另一个颜色空间,例如将图像从RGB颜色空间转换为HSV颜色空间,代码如下所示:
im = imread("../images/parrot.png")
im_hsv = color.rgb2hsv(im)
plt.gray()
plt.figure(figsize=(10,8))
plt.subplot(221), plt.imshow(im_hsv[...,0]), plt.title('h', size=20),
plt.axis('off')
plt.subplot(222), plt.imshow(im_hsv[...,1]), plt.title('s', size=20),
plt.axis('off')
plt.subplot(223), plt.imshow(im_hsv[...,2]), plt.title('v', size=20),
plt.axis('off')
plt.subplot(224), plt.axis('off')
plt.show()
上述代码的运行结果如图1-17所示,即创建的鹦鹉HSV图像的H(hue或color:色调)、S(饱和度或浓度)和V(明度)通道。类似地,读者可以使用rgb2yuv()函数将图像转换到YUV颜色空间。
图1-17 从RGB颜色空间转换为HSV颜色空间的鹦鹉图像
4.由于存储图像的数据结构
如前所述,PIL使用Image对象存储图像,而scikit-image使用numpy ndarray数据结构存储图像数据。接下来,我们将描述如何在这两个数据结构之间进行转换。
转换图像数据结构 如下代码将展示如何从PIL的Image对象转换为numpy ndarray(由scikit-image使用):
im = Image.open('../images/flowers.png') # read image into an Image object with PIL
im = np.array(im) # create a numpy ndarray from the Image object
imshow(im) # use skimage imshow to display the image
plt.axis('off'), show()
运行上述代码,输出结果如图1-18所示,可以看到,输出的是一幅花的图像。
图1-18 从PIL的image对像转换为numpy ndarray对象
如下代码将展示如何将numpy ndarray转换为PIL image对象。运行代码后,会看到与图1-18相同的输出。
im = imread('../images/flowers.png') # read image into numpy ndarray with skimage
im = Image.fromarray(im) # create a PIL Image object from the numpy ndarray
im.show() # display the image with PIL Image.show() method
1.5.2 基本的图像操作
不同的Python库都可以用于基本的图像操作。几乎所有库都以numpy ndarray存储图像(例如灰度图像的二维数组和RGB图像的三维数组)。彩色Lena图像的正 x 和 y 方向(原点是图像二维数组的左上角)如图1-19所示。
图1-19 Lena彩色图像的坐标方向
1.使用numpy数组的切片进行图像处理
如下代码显示了如何使用numpy数组的切片和掩模在Lena图像上创建圆形掩模:
lena = mpimg.imread("../images/lena.jpg") # read the image from disk as a numpy ndarray
print(lena[0, 40])
# [180 76 83]
# print(lena[10:13, 20:23,0:1]) # slicing
lx, ly, _ = lena.shape
X, Y = np.ogrid[0:lx, 0:ly]
mask = (X - lx / 2) ** 2 + (Y - ly / 2) ** 2 > lx * ly / 4
lena[mask,:] = 0 # masks
plt.figure(figsize=(10,10))
plt.imshow(lena), plt.axis('off'), plt.show()
运行上述代码,输出结果如图1-20所示。
图1-20 Lena图像的圆形掩模
简单的图像变形—使用交叉溶解的两个图像的 α 混合 如下代码展示了通过使用如下公式给出的两张图像numpy ndarrays的线性组合,如何从一张人脸图像( image 1是梅西的脸)开始,以另一张人脸图像( image 2是C罗的脸)结束。
通过迭代地将 α 从0增加到1来实现图像的变形效果。
im1 = mpimg.imread("../images/messi.jpg") / 255 # scale RGB values in [0,1]
im2 = mpimg.imread("../images/ronaldo.jpg") / 255
i = 1
plt.figure(figsize=(18,15))
for alpha in np.linspace(0,1,20):
plt.subplot(4,5,i)
plt.imshow((1-alpha)*im1 + alpha*im2)
plt.axis('off')
i += 1
plt.subplots_adjust(wspace=0.05, hspace=0.05)
plt.show()
运行上述代码,输出结果如图1-21所示,这是 α -混合图像的序列。本书后续章节将介绍更加高级的图像变形技术。
2.使用PIL进行图像处理
PIL提供了许多进行图像处理的函数,例如,使用点变换来更改像素值或对图像实现几何变换。使用PIL进行图像处理之前,加载鹦鹉的PNG图像,如下面的代码所示:
im = Image.open("../images/parrot.png") # open the image, provide the correct path
print(im.width, im.height, im.mode, im.format) # print image size, mode and format
# 486 362 RGB PNG
接下来将介绍如何使用PIL执行不同类型的图像操作。
(1)裁剪图像。 可以使用带有所需矩形参数的crop()函数从图像中裁剪相应的区域,如下面的代码所示。
im_c = im.crop((175,75,320,200)) # crop the rectangle given by (left, top,right,
bottom) from the image
im_c.show()
运行上述代码,输出结果如图1-21所示,此图即所创建的裁剪图像。
图1-21 裁剪的鹦鹉图像
(2)调整图像尺寸。 为了增大或缩小图像的尺寸,可以使用resize()函数,该函数可在内部分别对图像进行上采样或下采样。这部分内容将在下一章中详细讨论。
① 调整为较大的图像。从尺寸为149×97的小时钟图像开始,创建一个更大尺寸的图像。如下代码展示了将要处理的小时钟图像。
im = Image.open("../images/clock.jpg")
print(im.width, im.height)
# 107 105
im.show()
运行上述代码,输出结果如图1-22所示,此图即小时钟图像。
图1-22 小时钟图像
下一行代码说明了通过使用双线性插值(一种上采样技术),如何使用resize()函数放大先前的输入时钟图像(放大5倍),以获得比输入图像放大25倍的输出图像。
有关此技术如何工作的详细信息将在第2章中讲述。
im_large = im.resize((im.width*5, im.height*5), Image.BILINEAR) # bi-linear interpolation
② 调整为较小的图像。现在让我们来做相反的事情:从维多利亚纪念堂的大图像(尺寸为720×540)开始,创建一个尺寸较小的图像。如下代码显示了将开始处理的大图像。
im = Image.open("../images/victoria_memorial.png")
print(im.width, im.height)
# 720 540
im.show()
运行上述代码,输出结果如图1-23所示,图为维多利亚纪念堂的大图像。
图1-23 维多利亚纪念堂图像
下面一行代码说明了如何使用resize()函数来缩小维多利亚纪念堂的当前图像,通过使用抗锯齿(一种高质量的下采样技术),将图像的宽度和高度都调整为原宽度和调试的1/5。读者将在第2章中看到它是如何实现的。
im_small = im.resize((im.width//5, im.height//5), Image.ANTIALIAS)
(3)图像负片。可以使用point()函数,用单参数函数来转换每个像素值。可以使用它来生成图像负片,如下面的代码所示。像素值用1字节无符号整数表示,因此,从最大可能值中减去该值将是每个像素上获得图像反转所需的精确点操作。
im = Image.open("../images/parrot.png")
im_t = im.point(lambda x: 255 - x)
im_t.show()
运行上述代码,输出结果如图1-24所示,图中即是鹦鹉负片图像。
图1-24 鹦鹉图像的负片
(4)将图像转换为灰度图。 可以使用带有“L”参数的convert()函数将RGB彩色图像更改为灰度图像,如下面的代码所示:
im_g = im.convert('L') # convert the RGB color image to a grayscale image
本书将在接下来的几个灰度转换中使用这个图像。
(5)某些灰度级变换。 在这里将探讨两个变换。其中一个使用一个函数,将输入图像中的每个像素值变换为输出图像的相应像素值。函数point()可用于此操作。每个像素的值介于0到255之间(含0和255)。
① 对数变换。对数变换可以有效地压缩具有动态像素值范围的图像。下面的代码使用了点变换进行对数变换:
im_g.point(lambda x: 255*np.log(1+x/255)).show()
图1-25显示了鹦鹉图像的对数变换,可以看到,像素值的范围缩小,输入图像中较亮的像素变暗,较暗的像素变亮,从而缩小了像素值的范围。
图1-25 鹦鹉图像的对数变换
② 幂律变换。幂律变换用作图像的 γ 校正。下面一行代码说明了如何使用point()函数进行幂律变换,其中 γ =0.6:
im_g.point(lambda x: 255*(x/255)**0.6).show()
图1-26显示了运行上述代码生成的鹦鹉的幂律变换图像。
图1-26 鹦鹉图像的幂律变换
(6)一些几何变换。本节中将讨论另一组变换,这些变换是通过将适当的矩阵(通常用齐次坐标表示)与图像矩阵相乘来完成的。由于这些变换会改变图像的几何方向,因此称这些变换为几何变换。
① 镜像图像。可以使用transpose()函数和水平或垂直镜像图像,代码如下所示:
im.transpose(Image.FLIP_LEFT_RIGHT).show() # reflect about the vertical axis
运行上述代码,得到图1-27所示的鹦鹉图像的镜像。
图1-27 鹦鹉图像的镜像
② 旋转图像。可以使用rotate()函数将图像旋转一个角度(以度为单位),代码如下所示:
im_45 = im.rotate(45) #rotate the image by 45 degrees
im_45.show() #show the retated image
运行上述代码,鹦鹉图像的旋转变换如图1-28所示。
图1-28 鹦鹉图像的旋转变换
③ 在图像上应用仿射变换。二维仿射变换矩阵 T 可以应用于图像的每个像素(在齐次坐标中),以进行仿射变换,这种变换通常通过反向映射(扭曲)来实现。
如下代码所示的是用shear(剪切)变换矩阵变换输入图像时得到的输出图像。transform()函数中的数据参数是一个六元组( a , b , c , d , e , f ),其中包含来自仿射变换矩阵(affine transform matrix)的前两行。对于输出图像中的每个像素( x , y ),新值取自输入图像中的位置( ax + by + c , dx + ey + f ),该位置四舍五入为最接近的像素。transform()函数可用于缩放、平移、旋转和剪切原始图像。
im = Image.open("../images/parrot.png")
im.transform((int(1.4*im.width), im.height), Image.AFFINE,
data=(1,-0.5,0,0,1,0)).show() # shear
运行上述代码,所输出的剪切变换图像如图1-29所示。
图1-29 鹦鹉图像的剪切变换
④ 透视变换。通过使用Image.PERSPECTIVE参数,可以使用transform()函数对图像进行透视变换,如下面的代码所示:
params = [1, 0.1, 0, -0.1, 0.5, 0, -0.005, -0.001]
im1 = im.transform((im.width//3, im.height), Image.PERSPECTIVE, params,Image.BICUBIC)
im1.show()
运行上述代码,透视投影之后获得的图像如图1-30所示。
图1-30 鹦鹉图像的透视变换
(7)更改图像的像素值。可以使用putpixel()函数更改图像中的像素值。接着讨论使用函数向图像中添加噪声的主流应用。
可以通过从图像中随机选择几个像素,然后将这些像素值的一半设置为黑色,另一半设置为白色,来为图像添加一些椒盐噪声(salt-and-pepper noise)。如下代码展示了如何添加噪声:
# choose 5000 random locations inside image
im1 = im.copy() # keep the original image, create a copy
n = 5000
x, y = np.random.randint(0, im.width, n), np.random.randint(0, im.height,n)
for (x,y) in zip(x,y):
im1.putpixel((x, y), ((0,0,0)
if np.random.rand() < 0.5 else (255,255,255))) # salt-and-pepper noise
im1.show()
运行上述代码,输出噪声图像,如图1-31所示。
图1-31 添加了椒盐噪声的鹦鹉图像
(8)在图像上绘制图形。可以用PIL.ImageDraw模块中的函数在图像上绘制线条或其他几何图形(例如ellipse()函数用于绘制椭圆),如下面的代码所示:
im = Image.open("../images/parrot.png")
draw = ImageDraw.Draw(im)
draw.ellipse((125, 125, 200, 250), fill=(255,255,255,128))
del draw
im.show()
运行上述代码,输出图像如图1-32所示。
图1-32 在鹦鹉图像上绘制椭圆
(9)在图像上添加文本。可以使用PIL.ImageDraw模块中的text()函数向图像中添加文本,如下面的代码所示。
draw = ImageDraw.Draw(im)
font = ImageFont.truetype("arial.ttf", 23) # use a truetype font
draw.text((10, 5), "Welcome to image processing with python", font=font)
del draw
im.show()
运行上述代码,输出图像如图1-33所示。
图1-33 在鹦鹉图像上绘制文本
(10)创建缩略图。可以使用thumbnail()函数创建图像的缩略图,如下面的代码所示:
im_thumbnail = im.copy() # need to copy the original image first
im_thumbnail.thumbnail((100,100))
# now paste the thumbnail on the image
im.paste(im_thumbnail, (10,10))
im.save("../images/parrot_thumb.jpg")
im.show()
运行上述代码,输出图像如图1-34所示。
图1-34 鹦鹉图像的缩略图
(11)计算图像的基本统计信息。可以使用stat模块来计算一幅图像的基本统计信息(不同通道像素值的平均值、中值、标准差等),如下面的代码所示:
s = stat.Stat(im)
print(s.extrema) # maximum and minimum pixel values for each channel R, G,B
# [(4, 255), (0, 255), (0, 253)]
print(s.count)
# [154020, 154020, 154020]
print(s.mean)
# [125.41305674587716, 124.43517724970783, 68.38463186599142]
print(s.median)
# [117, 128, 63]