Spaces:
Paused
Paused
Upload 3 files
Browse files- basicsr/metrics/__init__.py +19 -0
- basicsr/metrics/metric_util.py +45 -0
- basicsr/metrics/psnr_ssim.py +128 -0
basicsr/metrics/__init__.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from copy import deepcopy
|
| 2 |
+
|
| 3 |
+
from basicsr.utils.registry import METRIC_REGISTRY
|
| 4 |
+
from .psnr_ssim import calculate_psnr, calculate_ssim
|
| 5 |
+
|
| 6 |
+
__all__ = ['calculate_psnr', 'calculate_ssim']
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def calculate_metric(data, opt):
|
| 10 |
+
"""Calculate metric from data and options.
|
| 11 |
+
|
| 12 |
+
Args:
|
| 13 |
+
opt (dict): Configuration. It must constain:
|
| 14 |
+
type (str): Model type.
|
| 15 |
+
"""
|
| 16 |
+
opt = deepcopy(opt)
|
| 17 |
+
metric_type = opt.pop('type')
|
| 18 |
+
metric = METRIC_REGISTRY.get(metric_type)(**data, **opt)
|
| 19 |
+
return metric
|
basicsr/metrics/metric_util.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
|
| 3 |
+
from basicsr.utils.matlab_functions import bgr2ycbcr
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
def reorder_image(img, input_order='HWC'):
|
| 7 |
+
"""Reorder images to 'HWC' order.
|
| 8 |
+
|
| 9 |
+
If the input_order is (h, w), return (h, w, 1);
|
| 10 |
+
If the input_order is (c, h, w), return (h, w, c);
|
| 11 |
+
If the input_order is (h, w, c), return as it is.
|
| 12 |
+
|
| 13 |
+
Args:
|
| 14 |
+
img (ndarray): Input image.
|
| 15 |
+
input_order (str): Whether the input order is 'HWC' or 'CHW'.
|
| 16 |
+
If the input image shape is (h, w), input_order will not have
|
| 17 |
+
effects. Default: 'HWC'.
|
| 18 |
+
|
| 19 |
+
Returns:
|
| 20 |
+
ndarray: reordered image.
|
| 21 |
+
"""
|
| 22 |
+
|
| 23 |
+
if input_order not in ['HWC', 'CHW']:
|
| 24 |
+
raise ValueError(f'Wrong input_order {input_order}. Supported input_orders are ' "'HWC' and 'CHW'")
|
| 25 |
+
if len(img.shape) == 2:
|
| 26 |
+
img = img[..., None]
|
| 27 |
+
if input_order == 'CHW':
|
| 28 |
+
img = img.transpose(1, 2, 0)
|
| 29 |
+
return img
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def to_y_channel(img):
|
| 33 |
+
"""Change to Y channel of YCbCr.
|
| 34 |
+
|
| 35 |
+
Args:
|
| 36 |
+
img (ndarray): Images with range [0, 255].
|
| 37 |
+
|
| 38 |
+
Returns:
|
| 39 |
+
(ndarray): Images with range [0, 255] (float type) without round.
|
| 40 |
+
"""
|
| 41 |
+
img = img.astype(np.float32) / 255.
|
| 42 |
+
if img.ndim == 3 and img.shape[2] == 3:
|
| 43 |
+
img = bgr2ycbcr(img, y_only=True)
|
| 44 |
+
img = img[..., None]
|
| 45 |
+
return img * 255.
|
basicsr/metrics/psnr_ssim.py
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import cv2
|
| 2 |
+
import numpy as np
|
| 3 |
+
|
| 4 |
+
from basicsr.metrics.metric_util import reorder_image, to_y_channel
|
| 5 |
+
from basicsr.utils.registry import METRIC_REGISTRY
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
@METRIC_REGISTRY.register()
|
| 9 |
+
def calculate_psnr(img1, img2, crop_border, input_order='HWC', test_y_channel=False):
|
| 10 |
+
"""Calculate PSNR (Peak Signal-to-Noise Ratio).
|
| 11 |
+
|
| 12 |
+
Ref: https://en.wikipedia.org/wiki/Peak_signal-to-noise_ratio
|
| 13 |
+
|
| 14 |
+
Args:
|
| 15 |
+
img1 (ndarray): Images with range [0, 255].
|
| 16 |
+
img2 (ndarray): Images with range [0, 255].
|
| 17 |
+
crop_border (int): Cropped pixels in each edge of an image. These
|
| 18 |
+
pixels are not involved in the PSNR calculation.
|
| 19 |
+
input_order (str): Whether the input order is 'HWC' or 'CHW'.
|
| 20 |
+
Default: 'HWC'.
|
| 21 |
+
test_y_channel (bool): Test on Y channel of YCbCr. Default: False.
|
| 22 |
+
|
| 23 |
+
Returns:
|
| 24 |
+
float: psnr result.
|
| 25 |
+
"""
|
| 26 |
+
|
| 27 |
+
assert img1.shape == img2.shape, (f'Image shapes are differnet: {img1.shape}, {img2.shape}.')
|
| 28 |
+
if input_order not in ['HWC', 'CHW']:
|
| 29 |
+
raise ValueError(f'Wrong input_order {input_order}. Supported input_orders are ' '"HWC" and "CHW"')
|
| 30 |
+
img1 = reorder_image(img1, input_order=input_order)
|
| 31 |
+
img2 = reorder_image(img2, input_order=input_order)
|
| 32 |
+
img1 = img1.astype(np.float64)
|
| 33 |
+
img2 = img2.astype(np.float64)
|
| 34 |
+
|
| 35 |
+
if crop_border != 0:
|
| 36 |
+
img1 = img1[crop_border:-crop_border, crop_border:-crop_border, ...]
|
| 37 |
+
img2 = img2[crop_border:-crop_border, crop_border:-crop_border, ...]
|
| 38 |
+
|
| 39 |
+
if test_y_channel:
|
| 40 |
+
img1 = to_y_channel(img1)
|
| 41 |
+
img2 = to_y_channel(img2)
|
| 42 |
+
|
| 43 |
+
mse = np.mean((img1 - img2)**2)
|
| 44 |
+
if mse == 0:
|
| 45 |
+
return float('inf')
|
| 46 |
+
return 20. * np.log10(255. / np.sqrt(mse))
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def _ssim(img1, img2):
|
| 50 |
+
"""Calculate SSIM (structural similarity) for one channel images.
|
| 51 |
+
|
| 52 |
+
It is called by func:`calculate_ssim`.
|
| 53 |
+
|
| 54 |
+
Args:
|
| 55 |
+
img1 (ndarray): Images with range [0, 255] with order 'HWC'.
|
| 56 |
+
img2 (ndarray): Images with range [0, 255] with order 'HWC'.
|
| 57 |
+
|
| 58 |
+
Returns:
|
| 59 |
+
float: ssim result.
|
| 60 |
+
"""
|
| 61 |
+
|
| 62 |
+
C1 = (0.01 * 255)**2
|
| 63 |
+
C2 = (0.03 * 255)**2
|
| 64 |
+
|
| 65 |
+
img1 = img1.astype(np.float64)
|
| 66 |
+
img2 = img2.astype(np.float64)
|
| 67 |
+
kernel = cv2.getGaussianKernel(11, 1.5)
|
| 68 |
+
window = np.outer(kernel, kernel.transpose())
|
| 69 |
+
|
| 70 |
+
mu1 = cv2.filter2D(img1, -1, window)[5:-5, 5:-5]
|
| 71 |
+
mu2 = cv2.filter2D(img2, -1, window)[5:-5, 5:-5]
|
| 72 |
+
mu1_sq = mu1**2
|
| 73 |
+
mu2_sq = mu2**2
|
| 74 |
+
mu1_mu2 = mu1 * mu2
|
| 75 |
+
sigma1_sq = cv2.filter2D(img1**2, -1, window)[5:-5, 5:-5] - mu1_sq
|
| 76 |
+
sigma2_sq = cv2.filter2D(img2**2, -1, window)[5:-5, 5:-5] - mu2_sq
|
| 77 |
+
sigma12 = cv2.filter2D(img1 * img2, -1, window)[5:-5, 5:-5] - mu1_mu2
|
| 78 |
+
|
| 79 |
+
ssim_map = ((2 * mu1_mu2 + C1) * (2 * sigma12 + C2)) / ((mu1_sq + mu2_sq + C1) * (sigma1_sq + sigma2_sq + C2))
|
| 80 |
+
return ssim_map.mean()
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
@METRIC_REGISTRY.register()
|
| 84 |
+
def calculate_ssim(img1, img2, crop_border, input_order='HWC', test_y_channel=False):
|
| 85 |
+
"""Calculate SSIM (structural similarity).
|
| 86 |
+
|
| 87 |
+
Ref:
|
| 88 |
+
Image quality assessment: From error visibility to structural similarity
|
| 89 |
+
|
| 90 |
+
The results are the same as that of the official released MATLAB code in
|
| 91 |
+
https://ece.uwaterloo.ca/~z70wang/research/ssim/.
|
| 92 |
+
|
| 93 |
+
For three-channel images, SSIM is calculated for each channel and then
|
| 94 |
+
averaged.
|
| 95 |
+
|
| 96 |
+
Args:
|
| 97 |
+
img1 (ndarray): Images with range [0, 255].
|
| 98 |
+
img2 (ndarray): Images with range [0, 255].
|
| 99 |
+
crop_border (int): Cropped pixels in each edge of an image. These
|
| 100 |
+
pixels are not involved in the SSIM calculation.
|
| 101 |
+
input_order (str): Whether the input order is 'HWC' or 'CHW'.
|
| 102 |
+
Default: 'HWC'.
|
| 103 |
+
test_y_channel (bool): Test on Y channel of YCbCr. Default: False.
|
| 104 |
+
|
| 105 |
+
Returns:
|
| 106 |
+
float: ssim result.
|
| 107 |
+
"""
|
| 108 |
+
|
| 109 |
+
assert img1.shape == img2.shape, (f'Image shapes are differnet: {img1.shape}, {img2.shape}.')
|
| 110 |
+
if input_order not in ['HWC', 'CHW']:
|
| 111 |
+
raise ValueError(f'Wrong input_order {input_order}. Supported input_orders are ' '"HWC" and "CHW"')
|
| 112 |
+
img1 = reorder_image(img1, input_order=input_order)
|
| 113 |
+
img2 = reorder_image(img2, input_order=input_order)
|
| 114 |
+
img1 = img1.astype(np.float64)
|
| 115 |
+
img2 = img2.astype(np.float64)
|
| 116 |
+
|
| 117 |
+
if crop_border != 0:
|
| 118 |
+
img1 = img1[crop_border:-crop_border, crop_border:-crop_border, ...]
|
| 119 |
+
img2 = img2[crop_border:-crop_border, crop_border:-crop_border, ...]
|
| 120 |
+
|
| 121 |
+
if test_y_channel:
|
| 122 |
+
img1 = to_y_channel(img1)
|
| 123 |
+
img2 = to_y_channel(img2)
|
| 124 |
+
|
| 125 |
+
ssims = []
|
| 126 |
+
for i in range(img1.shape[2]):
|
| 127 |
+
ssims.append(_ssim(img1[..., i], img2[..., i]))
|
| 128 |
+
return np.array(ssims).mean()
|