CV对于图片预处理方式种类繁多, 如果使用不同的图片预处理方式经过模型推理后,得到的结果差距较大。如何能训练好CV模型,首先需要对齐CV模型训练、推理过程的预处理方式。
预处理方式 | 接收图片格式 | 读取数据类型 |
OpenCV | 通道颜色 - BGR;通道顺序 - HWC | numpy数组;uint8的整数类型,范围0 - 255 |
Pillow(PIL) | 通道颜色 - RGB;通道顺序 - HWC | 特有的数据结构(可以转换成numpy) |
torchvision(Pytorch) | ||
matplotlib | 通道颜色 - RGB;通道顺序 - HWC | numpy数组;uint8的整数类型,范围0 - 255 |
代码参考:opencv、matplotlib、pillow和pytorch读取数据的通道顺序
import cv2
import matplotlib.pyplot as plt
from PIL import Image
import numpy as np
import torch
from torchvision import transforms
# opencv读取数据通道顺序,默认颜色通道BGR, 数据通道顺序HWC
def opencv_channel(img_path):
image = cv2.imread(img_path)
print("cv2 type is ", type(image))
print("cv2 shape is ", image.shape)
cv2.imshow("image", image)
# cv2.waitKey(0)
#opencv将BGR颜色通道顺序 更改为 RGB颜色通道顺序
# 1.
cvColor_image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
# 2.
b, g, r = cv2.split(image)
cvColor_image = cv2.merge([r, g, b])
# 3.
cvColor_image = image[:, :, :: -1]
# matplotlib读取数据通道顺序, 默认颜色通道是RGB, 数据通道顺序是HWC
def plt_channel(img_path):
image = plt.imread(img_path)
print("matplotlib type is ", type(image))
print("matplotlib shape is ", image.shape)
plt.imshow(image)
plt.title("plt image")
plt.show()
# 将numpy数据转换成pillow数据
pil_image = Image.fromarray(image)
print("PIL type is ", type(pil_image))
pil_image.show()
def pillow_channel(img_path):
image = Image.open(img_path)
print("PIL type is ", type(image))
print("PIL shape is ", image.size)
image.show()
# 将pillow数据转换成numpy数据
np_image = np.array(image)
print("matplotlib type is ", type(np_image))
print("matplotlib shape is ", np_image.shape)
plt.imshow(np_image)
plt.title("pillow convert to numpy type")
plt.show()
if __name__ == "__main__":
img_path = "./bee.jpg"
pillow_channel(img_path)
plt_channel(img_path)
opencv_channel(img_path)
CV模型推理前包含多种预处理的方式,详细介绍部分常用的,且不同预处理方式存在diff的方法【模型预处理方式主要使用gocv、torchvision】
参考:
Python Pillow 和 OpenCV 中 resize 的区别 - 知乎
Pillow vs cv2 resize
cv2.resize()原理详解-CSDN博客
作用:Resize() 修改图片的大小
OpenCV
作用:ToTensor() 数据归一化 + 图像HWC转换成CHW
作用:Normalize() 数据标准化 【提高数据计算效率、模型准确性】
def CV_Process(img_path):
# ToTensor() 归一化到(0,1)之间; (H, W, C)的numpy.ndarray 转换为 (C, H, W)的tensor
# Normalize() 再 (x-mean)/std 标准化到(-1,1)
image = cv2.imread(img_path)
print(image.shape)
transform_2 = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize(mean = [0.485, 0.456, 0.406], std = [0.229, 0.224, 0.225])
# 很多代码预处理Normalize中的参数值是从ImageNet训练集抽样计算而来
])
transform_3 = transforms.Compose([
transforms.ToTensor()
])
image2 = transform_2(image)
image3 = torch.from_numpy(image.transpose((2, 0, 1)))
image3 = image3.float().div(255)
channel_1 = image3[0].sub_(0.485).div_(0.229).unsqueeze(0)
channel_2 = image3[1].sub_(0.456).div_(0.224).unsqueeze(0)
channel_3 = image3[2].sub_(0.406).div_(0.225).unsqueeze(0)
image3 = torch.cat([channel_1, channel_2, channel_3], dim = 0)
print(image2.shape)
print(image3.shape)
print(image2.equal(image3)) # output: True
1. torch.div & torch.div_ 区别
torch.div和torch.div_的主要区别在于返回值和是否进行就地操作(in-place operations)。
torch.div(a, b)的作用是对两个张量a和b逐元素地做除法操作,返回一个新的张量。如果a和b的shape不一致,torch会尝试将它们广播到一个合适的shape使得操作符合规则。
torch.div_(a, b)的作用和torch.div类似,但是它会就地修改a的值,即直接将结果存储在a中。由于是就地操作,torch.div_只能对可写的(writable)张量使用。
因此,torch.div和torch.div_在实现上略有不同,但作用是一致的,都是进行张量间的逐元素除法操作。一般来说,如果不需要就地修改操作,推荐直接使用torch.div,这样能保留原始张量的值,并且可读性更好。如果需要就地修改操作,需要使用torch.div_。