在做图像相关的应用时,在 Python 环境下通常会使用到 OpenCV,PIL,PyTorch 等库,这些库在读取到图像时,涉及到的存储格式也会有所不同。
1. 彼此差异
严格来讲,OpenCV 和 PIL 是直接处理图片的两个 Python 库,PyTorch 而是直接处理 Tensor 数据(Tensor 是一种数据结构)。首先看一下 OpenCV 和 PIL 两个库是如何读取图片的,以之前的数据集中的一张图为例:
两个库在读取图片时,分别用到了 imread()
方法和 open()
方法:
import cv2
img_cv2 = cv2.imread("maksssksksss5.png")
print(type(img_cv2))
from PIL import Image
img_pil = Image.open("maksssksksss5.png")
print(type(img_pil))
读取到的图片的存储的数据格式也存在不同,分别为:
<class 'numpy.ndarray'>
<class 'PIL.PngImagePlugin.PngImageFile'>
在 PIL 库中设计了一种用于存储图片的数据格式,针对不同格式的图片,存储的数据格式也不一样,如 JPG 的图片的数据格式则为 <class 'PIL.JpegImagePlugin.JpegImageFile'>
,这些类的父类是 PIL.Image.Image
。另外一点很重要的是:OpenCV 在读取到图片后,默认的通道顺序是 BGR,而通常的通道顺序是 RGB,这一点尤其与其他的库不一样。
涉及到的数据格式总结为:numpy.ndarray
,PIL.Image.Image
(此处不再细分各种编码方式),Tensor
。
2. 相互转换
2.1. OpenCV -> PIL
OpenCV 到 PIL 的转换实质上是 numpy.ndarray
到 PIL
的转换,但要注意的是 OpenCV 读取到的图像的通道顺序是 BGR,而其他库的要求是 RGB,因此在做转换之前先要调整通道顺序。调整通道顺序的函数为:cv2.cvtColor()
,从
import cv2
from PIL import Image
img_cv2 = cv2.imread("maksssksksss5.png")
print(type(img_cv2))
img_cv2_rgb = cv2.cvtColor(img_cv2, cv2.COLOR_BGR2RGB)
img_pil = Image.fromarray(img_cv2_rgb)
print(type(img_pil))
2.2. PIL -> OpenCV
反过来,从 PIL
到 numpy.ndarray
的转换相对较为简单,直接使用 numpy
将 Image.Image
转换成 array,再使用 cv2.cvtColor()
将 RGB 转换为 BGR。
import cv2
from PIL import Image
import numpy as np
img_pil = Image.open("maksssksksss5.png")
print(type(img_pil))
img_array = np.array(img_pil)
img_cv2 = cv2.cvtColor(img_array, cv2.COLOR_RGB2BGR)
print(type(img_cv2))
2.3. PIL -> PyTorch
这里所指的转换并不是一种严格意义上的转换,而是转换成 Tensor
的数据结构,在官网[1]中对这种转换中使用的函数 transforms.ToTensor()
有详细的说明:在 Tensor 中存储的图像大小为 [colour_channels, height, width]
,同时输入可以为 Image.Image
或者 numpy.ndarray
,但需要注意的是在 numpy.ndarray
中图像大小为 [height, width, colour_channels]
,如下代码:
import cv2
img_cv2 = cv2.imread("maksssksksss5.png")
print(type(img_cv2))
print(img_cv2.shape)
得到的结果为:
<class 'numpy.ndarray'>
(266, 400, 3)
具体的图可参考文献[2]:
from PIL import Image
from torchvision import transforms
img_pil = Image.open("maksssksksss5.png")
print(type(img_pil))
transform = transforms.Compose([
transforms.ToTensor()])
img_tensor = transform(img_pil)
print(img_tensor.shape)
得到的 shape 为:[4, 266, 400]
。此处略去了 OpenCV 到 Tensor 的转换。
注意:上述的
ToTensor()
方法实际上会做三件事:
- 将数据格式由
numpy.ndarray
或PIL.Image.Image
转为torch.tensor
,数据类型为torch.FloatTensor
- 把像素值从
0~255
变换到0~1
之间- 将 shape 由
[H, W, C]
转为为[C, H, W]
2.4. PyTorch -> PIL
要想将 Tensor 直接转换成 PIL,可以对等的使用 transforms.ToPILImage()
方法:
transform = transforms.Compose([
transforms.ToPILImage()])
img_pil = transform(img_tensor)
print(type(img_pil))
3 特殊的 Base64
至此,基本的图像数据类型之间的转换已经介绍清楚,但是还有一类在网络请求中通常使用的字符串类型 base64
。这里需要两个函数:
base64.b64encode()
:base64 编码base64.b64decode()
:base64 解码
对于图像转 base64,直接如下所示:
def file_to_base64(file_path):
with open(file_path, "rb") as file:
# 读取文件内容
file_data = file.read()
# 使用base64编码
base64_encoded = base64.b64encode(file_data)
# 将bytes对象转换为字符串
base64_string = base64_encoded.decode("utf-8")
return base64_string
那么网络传输后,如何在服务端将 base64 编码的数据转换成 PIL.Image.Image
,此时使用到 io.BytesIO
库,BytesIO 库是 io 模块中的一个类,其核心作用是在内存中创建二进制数据的流式接口,允许开发者像操作文件一样读写字节数据。base64 编码的字符串通过 base64.b64decode()
解码为原始的二进制字节数据(bytes类型),这个时候就需要能像操作文件一样操作这些个二进制字节数据。
# 将 base64 的图像转换成图像
img_b64decode = base64.b64decode(image_base64) # base64解码
image_stream = io.BytesIO(img_b64decode)
cropped_image = Image.open(image_stream)
4. 总结
综合以上的结论:
OpenCV | PIL | PyTorch | |
---|---|---|---|
数据存储格式 | numpy.ndarray |
PIL.Image.Image |
Tensor |
-> OpenCV | np.array -> cv2.cvtColor | transforms.ToPILImage -> np.array -> cv2.cvtColor | |
-> PIL | cv2.cvtColor -> Image.fromarray | transforms.ToPILImage | |
-> PyTorch | np.array -> cv2.cvtColor -> transforms.ToTensor | transforms.ToTensor |