本文的主要内容主要来自于这里,我觉得总结的不错,所以特地转载过来,并添加了一下自己的注解。
最近在研究深度学习视觉相关的东西,经常需要写python代码搭建深度学习模型。比如写CNN模型相关代码时,我们需要借助python图像库来读取图像并进行一系列的图像处理工作。我最常用的图像库当然是opencv,很强大很好用,但是opencv也有一些坑,不注意的话也会搞出大麻烦。近期我也在看一些别人写的代码,因为个人习惯不一样,他们在做深度学习时用于图片读取的图像库各不相同,从opencv到PIL再到skimage等等各种库都有,有些库读进来的图片存储方式也不太一样,如果不好好总结这些主流图像读写库特点的话,以后看代码写代码都会遇坑无数。这篇文章就总结了以下主流Python图像库的一些基本使用方法和需要注意的地方。
opencv
opencv作为我最常用的图像处理库,当然第一个介绍,并且介绍得比较全面。毋庸置疑,opencv是今天介绍得所有图像库中最全面也最强大的库,如果我们只想掌握一个图像库,我觉得opencv库肯定是最适合不过了。
图片读取操作
1 2 3 4 5 6 7 8 9 10 11
| import cv2 import numpy as np
img = cv2.imread('1.jpg') cv2.imshow('src',img) print(img.shape) print(img.size) print(img.dtype) print(img) cv2.waitKey()
|
值得注意的是,opencv读进来的图片已经是一个numpy矩阵了,彩色图片维度是(高度,宽度,通道数)。数据类型是uint8。
1 2 3 4 5 6 7 8 9 10
|
src = cv2.imread('1.jpg') gray = cv2.cvtColor(src,cv2.COLOR_BGR2GRAY) cv2.imshow('gray',gray) print(gray.shape) print(gray.size) print(gray) cv2.waitKey()
|
上面提到了两种获取灰度图的方式,读进来的灰度图的矩阵格式是(高度,宽度)。
1 2 3
| img2 = cv2.imread('2.jpg') print(img2)
|
1 2 3 4
| img2 = cv2.imread('2.jpg') if img2 == None: print('fail to load image!')
|
图片矩阵变换
opencv读入图片的矩阵格式是:(height,width,channels)。而在深度学习中,因为要对不同通道应用卷积,所以会采取另一种方式:(channels,height,width)。为了应对该要求,我们可以这么做
1 2 3 4 5
|
print(img.shape) img = img.transpose(2,0,1) print(img.shape)
|
在深度学习搭建CNN时,往往要做相应的图像数据处理,比如图像要扩展维度,比如扩展成(batch_size,channels,height,width)。
对于这种要求,我们可以这么做。
1 2 3
| img = np.expand_dims(img, axis=0) print(img.shape)
|
上面提到的是预测阶段时预测单张图片的扩展维度的操作,如果是训练阶段,构建batch,即得到这种形式:(batch_size,channels,height,width)。我一般喜欢这么做
1 2 3 4 5
| data_list = [] loop: im = cv2.imread('xxx.png') data_list.append(im) data_arr = np.array(data_list)
|
这样子就能构造成我们想要的形式了。
图片Resize
我们 习惯的坐标表示 是 先 x 横坐标,再 y 纵坐标。在图像处理中,这种惯性思维尤其需要担心。
因为在计算机中,图像是以矩阵的形式保存的,先行后列。所以,一张 宽×高×颜色通道=480×256×3 的图片会保存在一个 256×480×3 的三维张量中。图像处理时也是按照这种思想进行计算的(其中就包括 OpenCV 下的图像处理),即 高×宽×颜色通道。
但是问题来了,cv2.resize这个api却是个小例外。因为它的参数输入却是 宽×高×颜色通道。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import cv2 import numpy as np import random
seq = [random.randint(0, 255) for _ in range(256*480*3)] mat = np.resize(seq, new_shape=[256, 480, 3]) print ('mat.shape = {}'.format(mat.shape)) cv2.imwrite('origin_pic.jpg', mat) origin_pic = cv2.imread('./origin_pic.jpg') print ('origin_pic.shape = {}'.format(origin_pic.shape)) resize_pic = cv2.resize(src=origin_pic, dsize=(int(origin_pic.shape[1] * 2), int(origin_pic.shape[0] * 1)) ) print ('resize_pic.shape = {}'.format(resize_pic.shape)) cv2.imshow('resize_pic', resize_pic) cv2.waitKey(0) cv2.destroyAllWindows()
|
Output:
1 2 3
| mat.shape = (256, 480, 3) origin_pic.shape = (256, 480, 3) resize_pic.shape = (256, 960, 3)
|
图片归一化
1 2 3 4 5
| img3 = cv2.imread('1.jpg') img3 = img3.astype("float") / 255.0 print(img3.dtype) print(img3)
|
存储图片
1 2 3 4 5
| cv2.imwrite('test1.jpg',img3)
img3 = img3 * 255 cv2.imwrite('test2.jpg',img3)
|
opencv大坑之BGR
opencv对于读进来的图片的通道排列是BGR,而不是主流的RGB!谨记!
1 2 3
| img4 = cv2.imread('1.jpg') img4 = cv2.cvtColor(img4,cv2.COLOR_BGR2RGB)
|
访问像素
1 2 3 4 5 6 7
| print(img4[10,10]) print(gray[10,10]) img4[10,10] = [255,255,255] gray[10,10] = 255 print(img4[10,10]) print(gray[10,10])
|
ROI操作
1 2 3 4
| roi = img4[200:550,100:450,:] cv2.imshow('roi',roi) cv2.waitKey()
|
通道操作
1 2 3 4 5 6 7
| img5 = cv2.imread('1.jpg') b,g,r = cv2.split(img5)
img5 = cv2.merge((b,g,r))
img5[:,:,2] = 0
|
PIL
图片读取
1 2
| from PIL import Image import numpy as np
|
PIL即Python Imaging Library,也即为我们所称的Pillow,是一个很流行的图像库,它比opencv更为轻巧,正因如此,它深受大众的喜爱。
图像读写
PIL读进来的图像是一个对象,而不是我们所熟知的numpy 矩阵。彩色图片维度是(宽度,高度)。注意Opencv第一个维度为高度,第二个维度为宽度,这里前两个维度与之相反。
1 2 3 4 5
| img = Image.open('1.jpg') print(img.format) print(img.size) print(img.mode) img.show()
|
灰度图的获取
1 2
| gray = Image.open('1.jpg').convert('L') gray.show()
|
1 2 3 4 5
| try: img2 = Image.open('2.jpg') except IOError: print('fail to load image!')
|
1 2 3 4 5
| arr = np.array(img3) print(arr.shape) print(arr.dtype) print(arr)
|
灰度图的转化与彩图转化一样
1 2 3 4
| arr_gray = np.array(gray) print(arr_gray.shape) print(arr_gray.dtype) print(arr_gray)
|
存储图片
1 2 3
| new_im = Image.fromarray(arr) new_im.save('3.png')
|
图像操作
1 2 3 4
| r, g, b = img.split() img = Image.merge("RGB", (b, g, r)) img = img.copy()
|
图像缩放
1 2 3 4 5 6 7 8 9
| from PIL import Image
imagepath = r'.\img\numbertest1.png' # 读取图片 image = Image.open(imagepath) # 改变图片大小 new_im = image.resize((width, height), Image.ANTIALIAS) # anti-alias抗锯齿,可以使缩放图片更加清晰 # 保存图片 new_im.save('new.png', 'JPEG', quality=95) # 'JPEG', quality=95可以省略
|
ROI获取
1 2 3
| img3 = Image.open('1.jpg') roi = img3.crop((0,0,300,300)) roi.show()
|
matplotlib
matplotlib是一个科学绘图神器,用的人非常多。
1 2 3 4 5
| import matplotlib.pyplot as plt import numpy as np image = plt.imread('1.jpg') plt.imshow(image) plt.show()
|
1 2 3 4 5
| image = plt.imread('1.jpg') plt.imshow(image) plt.axis('off') plt.show()
|
1 2 3 4 5
| print(image.shape) print(image.size) print(image.dtype) print(image)
|
1 2 3 4 5 6
| im_r = image[:,:,0] plt.imshow(im_r) plt.show()
plt.imshow(im_r,cmap='Greys_r') plt.show()
|
1 2 3 4 5 6 7 8 9 10 11 12
| import cv2 im2 = cv2.imread('1.jpg') plt.imshow(im2) plt.axis('off') plt.show()
im2 = cv2.cvtColor(im2,cv2.COLOR_BGR2RGB) plt.imshow(im2) plt.axis('off') plt.show()
|
1 2 3 4 5 6 7 8 9 10
| from PIL import Image im3 = Image.open('1.jpg') im3 = np.array(im3) plt.figure(1) plt.imshow(im3) plt.axis('off')
plt.savefig('timo.jpg') plt.show()
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| im_lol1 = plt.imread('lol.jpg') im_lol2 = plt.imread('1.jpg') figure = plt.figure(figsize=(20,10)) ''' figsize参数:指定绘图对象的宽度和高度,单位为英寸;dpi参数指定绘图对象的分辨率, 即每英寸多少个像素,缺省值为80。因此本例中所创建的图表窗口的宽度为8*80 = 640像素 ''' plt.axis("off") ax = figure.add_subplot(121) plt.axis('off') ax.imshow(im_lol1) ax.set_title('lol image 1') ax = figure.add_subplot(122) plt.axis('off') ax.imshow(im_lol2) ax.set_title('lol image 2')
plt.savefig('twp.jpg') plt.show()
|
scipy.misc
1 2 3 4 5 6 7 8 9 10
| from scipy import misc import matplotlib.pyplot as plt im = misc.imread('1.jpg') print(im.dtype) print(im.size) print(im.shape) misc.imsave('misc1.png',im) plt.imshow(im) plt.show() print(im)
|
可以看到,有warining,提示我们imread和imsave在后来的版本将会被弃用,叫我们使用imageio.imread和imageio.imwrite。
我们根据她的提示,使用imageio模块进行图片读写,warning也就没有了。
1 2 3 4 5 6 7 8 9
| import imageio im2 = imageio.imread('1.jpg') print(im2.dtype) print(im2.size) print(im2.shape) plt.imshow(im) plt.show() print(im2) imageio.imsave('imageio.png',im2)
|
skimage
1 2 3 4 5 6 7 8 9
| from skimage import io
im = io.imread('1.jpg') print(im.shape) print(im.dtype) print(im.size) io.imshow(im) io.imsave('sk.png',im) print(im)
|
图像也是以numpy array形式读入。
灰度图的获取方式:
1 2 3 4 5 6 7 8
| im2 = io.imread('1.jpg',as_grey=True) print(im2.dtype) print(im2.size) print(im2.shape) io.imshow(im2) io.imsave('sk_gray.png',im2) io.show() print(im2)
|
可以看到,灰度图像的矩阵的值被归一化了,注意注意!
也可以以这种方式获得灰度图:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| from skimage import color im3 = io.imread('1.jpg') im3 = color.rgb2grey(im3) print(im3.dtype) print(im3.size) print(im3.shape) io.imshow(im3) io.show()
''' skimage.color.rgb2grey(rgb) skimage.color.rgb2hsv(rgb) skimage.color.rgb2lab(rgb) skimage.color.gray2rgb(image) skimage.color.hsv2rgb(hsv) skimage.color.lab2rgb(lab)
'''
|
对图像进行resize后,发现图像范围也变成了[0-1]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| image = io.imread('test.jpg',as_grey=False)
image = transform.resize(image,(100, 200),order=1)
print image.dtype print image.shape print image ''' array([[[ 0.56078431, 0.77647059, 0.78823529], [ 0.56078431, 0.77647059, 0.78823529], [ 0.56078431, 0.77647059, 0.78823529], ..., ...]]) '''
|
OpenCV与PIL.Image格式互转
OpenCV转换成PIL.Image格式:
1 2 3 4 5 6 7 8 9
| import cv2 from PIL import Image import numpy img = cv2.imread("plane.jpg") cv2.imshow("OpenCV",img) image = Image.fromarray(cv2.cvtColor(img,cv2.COLOR_BGR2RGB)) image.show() cv2.waitKey()
|
PIL.Image转换成OpenCV格式:
1 2 3 4 5 6 7 8 9
| import cv2 from PIL import Image import numpy image = Image.open("plane.jpg") image.show() img = cv2.cvtColor(numpy.asarray(image),cv2.COLOR_RGB2BGR) cv2.imshow("OpenCV",img) cv2.waitKey()
|
总结
- 除了opencv读入的彩色图片以BGR顺序存储外,其他所有图像库读入彩色图片都以RGB存储。
- 除了PIL读入的图片是img类之外,其他库读进来的图片都是以numpy 矩阵。
- 除了PIL三个通道顺序为(宽度w,高度h,通道数c),其余均为(高度h、宽度w、通道数c)。值得注意的是使用Numpy将PIL对象转为矩阵时,通道数会变为(高度h、宽度w、通道数c)。
- resize的时候参数PIL 和Opencv要填(w,h),而其余填(h,w)。值得注意的是,skimage库读入灰度图片或者 resize 图片均会将图片归一化到
[0-1]
- 各大图像库的性能,老大哥当属opencv,无论是速度还是图片操作的全面性,都属于碾压的存在,毕竟他是一个巨大的cv专用库。下面那张图就是我从知乎盗来的一张关于各个主流图像库的一些性能比较图,从测试结果看来,opencv确实胜出太多了。
参考
Python各类图像库的图片读写方式总结
opencv: cv2.resize 探究(源码)
OpenCV、Skimage、PIL图像处理的细节差异
Python OpenCV格式和PIL.Image格式 互转