用python进行图像处理中分别用到过matplotlib.pyplot、PIL、cv2三种库,这三种库图像读取和保存方法各异,并且图像读取时顺序也有差异,如plt.imread和PIL.Image.open读入的都是RGB顺序,而cv2.imread读入的是BGR顺序。使用时需要倍加注意。
1. cv2,matplotlib,PIL比较
- 读取图像
1.cv2.imread
opencv读进来的是numpy数组,是uint8类型,0-255范围,图像形状是(H,W,C),读入的顺序是BGR
-
img = cv2.imread(img_path)
-
print(
'cv2',img.shape)
#(H,W,C)
2.matplotlib.pyplot.imread
matplotlib读取进来的图片是numpy数组,是unit8类型,0-255范围,图像形状是(H,W,C),读入的顺序是RGB
-
img = plt.imread(img_path)
-
print(
'plt',img.shape)
#(H,W,C)
3.PIL.image.open
PIL是有自己的数据结构的,类型是<class 'PIL.Image.Image'>;但是可以转换成numpy数组,转换后的数组为unit8,0-255范围,图像形状是(H,W,C),读入的顺序是RGB
-
'''PIL转成numpy数组:'''
-
-
img = Image.
open(img_path)
-
print(
type(img))
#<class 'PIL.Image.Image'>
-
img = np.array(img)
-
print(
type(img))
#<class 'numpy.ndarray'>
-
print(
'PIL',img.shape)
#(H,W,C)
-
'''numpy 转成 PIL'''
-
-
#input img is numpy type
-
img = Image.fromarray(img.astype(
'uint8')).convert(
'RGB')
-
print(
type(img))
#<class 'PIL.Image.Image'>
- 显示图像
这三者均可以用plt.imshow(img),如代码:
-
import PIL.Image
as Image
-
import matplotlib.pyplot
as plt
-
import cv2
-
-
#cv2
-
img = cv2.imread(img_path)
-
plt.subplot(
1,
3,
1)
-
plt.title(
'cv2')
-
plt.imshow(img)
-
-
#plt
-
img = plt.imread(img_path)
-
plt.subplot(
1,
3,
2)
-
plt.title(
'plt')
-
plt.imshow(img)
-
-
#PIL
-
img = Image.
open(img_path)
-
plt.subplot(
1,
3,
3)
-
plt.title(
'PIL')
-
plt.imshow(img)
-
-
#show
-
plt.show()
效果图:
可以看到,第一幅cv2图变色了,因为opencv读取进来的是BGR顺序呢的,而imshow需要的是RGB顺序,因此需要把cv2读的顺序转成RGB顺序
第一种方法:
b,g,r = cv2.split(img) img = cv2.merge([r,g,b])
img = cv2.imread(img_path) #======================= #convert BGR to RGB b,g,r = cv2.split(img) img = cv2.merge([r,g,b]) #======================== plt.title( 'cv2_RGB') plt.imshow(img) plt.show()第二种方法:
img= img[:,:,::-1]
img = cv2.imread(img_path) #======================= #convert BGR to RGB img = img[:,:,::- 1] #======================== plt.title( 'cv2_RGB') plt.imshow(img) plt.show()两种方法效果图都一样:haha,这样就正常了,逍遥哥哥回来了......
- 保存图像
1)cv2.imwrite - 保存numpy格式的图片
cv2.imwrite("cv2.jpg",img)
2)plt.imsave - 保存numpy格式的图片
plt.imsave('plt.jpg',img)
3)PIL.image - 保存PIL格式的图片
img.save("PIL.jpg")
===============================================================================================
2. PIL.Image / numpy.ndarray与Tensor的相互转换
以上就是图像的基本操作,为了方便进行图像数据的操作,pytorch团队提供了一个torchvision.transforms包,我们可以用transforms进行以下操作:
- PIL.Image / numpy.ndarray与Tensor的相互转化;
- 归一化;
- 对PIL.Image进行裁剪、缩放等操作。
通常,在使用torchvision.transforms,我们通常使用transforms.Compose将transforms组合在一起。
- 1)PIL.Image / numpy.ndarray与Tensor的相互转化
PIL.Image / numpy.ndarray转化为Tensor,常常用在训练模型阶段的数据读取,而Tensor转化为PIL.Image / numpy.ndarray则用在验证模型阶段的数据输出。
注意:Tensor的形状是[C,H,W],而cv2,plt,PIL形状都是[H,W,C]
我们可以使用 transforms.ToTensor() 将 PIL.Image/numpy.ndarray 数据进转化为torch.FloadTensor,并归一化到[0, 1.0]:
- 取值范围为[0, 255]的PIL.Image,转换成形状为[C, H, W],取值范围是[0, 1.0]的torch.FloadTensor;
- 形状为[H, W, C]的numpy.ndarray,转换成形状为[C, H, W],取值范围是[0, 1.0]的torch.FloadTensor。
而transforms.ToPILImage则是将Tensor转化为PIL.Image。如果,我们要将Tensor转化为numpy,只需要使用 .numpy() 即可。如下:
-
import torchvision
-
import torchvision.transforms
as transforms
-
import matplotlib.pyplot
as plt
-
import numpy
as np
-
-
img_path =
'your graph path'
-
-
# transforms.ToTensor()
-
transform1 = transforms.Compose([
-
transforms.ToTensor(),
# range [0, 255] -> [0.0,1.0] and convert [H,W,C] to [C,H,W]
-
])
-
img = plt.imread(img_path)
-
print(
'plt',img.shape)
#(H,W,C)
-
img = transform1(img)
# 归一化到 [0.0,1.0],并转成[C,H,W]
-
print(img.shape)
#torch.Size([C,H,W])
-
# 转化为numpy.ndarray并显示
-
img_arr = img.numpy() *
255
#use np.numpy(): convert Tensor to numpy
-
img_arr = img_arr.astype(
'uint8')
#convert Float to Int
-
print(img_arr.shape)
#[C,H,W]
-
img_new = np.transpose(img_arr, (
1,
2,
0))
#use np.transpose() convert [C,H,W] to [H,W,C]
-
plt.imshow(img_new)
-
plt.show()
- 2)归一化
归一化对神经网络的训练是非常重要的,那么我们如何归一化到[-1.0, -1.0]呢?只需要将上面的transform1改为如下所示:
-
#归一化
-
transform2 = transforms.Compose([
-
transforms.ToTensor(),
-
transforms.Normalize(mean = (
0.5,
0.5,
0.5), std = (
0.5,
0.5,
0.5))
-
]
-
)
(1)transforms.Compose就是将transforms组合在一起;
(2)transforms.Normalize使用如下公式进行归一化:
channel=(channel-mean)/ std
这样一来,我们的数据中的每个值就变成了[-1,1]的数了。来看下将上面transform1改为transform2所运行的效果图如下:
看上图已经出现失真了,原因是什么呢?
当上面操作已经将像素值转成[-1,1]时,执行代码img_arr = img.numpy() * 255时,举个例子,图像的某一行像素值变成
[ 207. 203. 203. ... -173. -175. -175. ],再执行代码img_arr = img_arr.astype('uint8')时,像素值变成
[207 203 203 ... 83 81 81]。就拿其中的 -173 变成 83 来说,首先我们要明确:符号数值在计算机里是用补码存储。
173原码=10101101,由于是负数,-173补码=01010010,计算机里储存的就是这个数值。那么当img_arr = img_arr.astype('uint8')读作无符号数uint8时,01010010连同符号位一起计算=83.故出现上面失真情况。
- 3)PIL.Image的缩放裁剪等操作
此外,transforms还提供了裁剪,缩放等操作,以便进行数据增强。下面就看一个随机裁剪的例子,这个例子中,仍然使用 Compose 将 transforms 组合在一起,如下:
-
transform4 = transforms.Compose([
-
transforms.ToTensor(),
-
transforms.ToPILImage(),
-
transforms.RandomCrop((
300,
300)),
-
])
-
img = Image.
open(img_path).convert(
'RGB')
-
-
img3 = transform4(img)
-
-
img3.show()
运行结果如下:
其他操作就不一一列举了。具体看http://pillow.readthedocs.io/en/5.2.x/handbook/tutorial.html#cutting-pasting-and-merging-images