Если используется какая-либо PyTorch-модель, предварительно обученная на датасете ImageNet (например, модель ResNet50), то чтобы подать на данную модель изображение — требуется его предварительно подготовить с помощью операций трансформации, которые указываются в Weights.transforms (см. Models and pre-trained weights).
Например, для ResNet50_Weights:
transforms=partial(ImageClassification, crop_size=224)
,где ImageClassification выполняет resize до 256, затем crop до 224, а потом нормирует картинку с параметрами:
mean: Tuple[float, ...] = (0.485, 0.456, 0.406), std: Tuple[float, ...] = (0.229, 0.224, 0.225),
— это усреднённые значения средних и отклонений для изображений ImageNet-а.
Таким образом, чтобы привести изображение к нужному формату — требуется выполнить преобразование нормализации с данными параметрами:
image_transform = torchvision.transforms.Compose( [ torchvision.transforms.ToTensor(), torchvision.transforms.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)), ] )
Применив это преобразование к изображению:
norm_image = image_transform(cv_rgb_image)
— получим нормализованный тензор, который уже можно подавать на вход нейронной сети.
Сама процедура нормализации, фактически, сводится к простой формуле:
Z = (X - mean)/std (1)
Это означает, что если нам вдруг потребуется сделать обратную процедуру для torchvision.transforms.Normalize, то нужно сделать следующее:
X = Z*std + mean (2)
X = (Z + mean/std)*std (3)
X = (Z - (mean/std)) / (1/std) (4)
Таким образом, мы можем использовать тот же метод нормализации — torchvision.transforms.Normalize, но с параметрами, которые можно получить из формулы 4:
invert_normalize = torchvision.transforms.Compose( [ torchvision.transforms.Normalize(mean=[-0.485/0.229, -0.456/0.224, -0.406/0.225], std=[1/0.229, 1/0.224, 1/0.225]), ] )
Теперь, если выполнить:
invert_norm_image = invert_normalize(norm_image) print(invert_norm_image.shape) plt.imshow(invert_norm_image.permute(1, 2, 0)) plt.show() print(invert_norm_image)
, то можно увидеть исходную картинку (правда со значениями в диапазоне от 0 до 1).
Метод permute(1, 2, 0) здесь нужен чтобы перевести тензор из формата CWH в привычный нам WHC.
Чтобы получить нормальное изображение со значениями пикселей от 0 до 255 — нужно только домножить тензор на 255 и привести его к байтовому типу:
invert_norm_image2 = invert_norm_image.permute(1, 2, 0)*255.0 invert_norm_image2 = invert_norm_image2.numpy().astype(np.uint8) print(invert_norm_image2.shape) plt.imshow(invert_norm_image2) plt.show() print(invert_norm_image2)
Забавно, но если проверить разницу между исходной и полученной картинками:
img_diff = cv_rgb_image.astype(np.float32) - invert_norm_image2.astype(np.float32) print(img_diff) print(np.count_nonzero(img_diff))
, то можно обнаружить, что значения некоторых пикселей отличаются на 1. Это получается из-за проблем с округлением float-ов. Если вместо 255 — проверить умножение на 256, то картинки получаются идентичными.
Ссылки