OpenCV,PIL,Tensor 及 Base64 之间的格式转化

在做图像相关的应用时,在 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.ndarrayPIL.Image.Image(此处不再细分各种编码方式),Tensor

2. 相互转换

2.1. OpenCV -> PIL

OpenCV 到 PIL 的转换实质上是 numpy.ndarrayPIL 的转换,但要注意的是 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

反过来,从 PILnumpy.ndarray 的转换相对较为简单,直接使用 numpyImage.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() 方法实际上会做三件事:

  1. 将数据格式由 numpy.ndarrayPIL.Image.Image 转为 torch.tensor,数据类型为 torch.FloatTensor
  2. 把像素值从 0~255 变换到 0~1 之间
  3. 将 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。这里需要两个函数:

  1. base64.b64encode():base64 编码
  2. 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

参考

[1] https://docs.pytorch.org/vision/stable/generated/torchvision.transforms.ToTensor.html?highlight=torchvision+transforms+totensor#torchvision.transforms.ToTensor

[2] https://www.learnpytorch.io/00_pytorch_fundamentals/