|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import numpy as np |
|
from scipy import signal |
|
|
|
def color_space_transform(input_color, fromSpace2toSpace): |
|
dim = input_color.shape |
|
|
|
if fromSpace2toSpace == "srgb2linrgb": |
|
limit = 0.04045 |
|
transformed_color = np.where(input_color > limit, np.power((input_color + 0.055) / 1.055, 2.4), input_color / 12.92) |
|
|
|
elif fromSpace2toSpace == "linrgb2srgb": |
|
limit = 0.0031308 |
|
transformed_color = np.where(input_color > limit, 1.055 * (input_color ** (1.0 / 2.4)) - 0.055, 12.92 * input_color) |
|
|
|
elif fromSpace2toSpace == "linrgb2xyz" or fromSpace2toSpace == "xyz2linrgb": |
|
|
|
|
|
a11 = 10135552 / 24577794 |
|
a12 = 8788810 / 24577794 |
|
a13 = 4435075 / 24577794 |
|
a21 = 2613072 / 12288897 |
|
a22 = 8788810 / 12288897 |
|
a23 = 887015 / 12288897 |
|
a31 = 1425312 / 73733382 |
|
a32 = 8788810 / 73733382 |
|
a33 = 70074185 / 73733382 |
|
A = np.array([[a11, a12, a13], |
|
[a21, a22, a23], |
|
[a31, a32, a33]]) |
|
|
|
input_color = np.transpose(input_color, (2, 0, 1)) |
|
if fromSpace2toSpace == "xyz2linrgb": |
|
A = np.linalg.inv(A) |
|
transformed_color = np.matmul(A, input_color) |
|
transformed_color = np.transpose(transformed_color, (1, 2, 0)) |
|
|
|
elif fromSpace2toSpace == "xyz2ycxcz": |
|
reference_illuminant = color_space_transform(np.ones(dim), 'linrgb2xyz') |
|
input_color = np.divide(input_color, reference_illuminant) |
|
y = 116 * input_color[1:2, :, :] - 16 |
|
cx = 500 * (input_color[0:1, :, :] - input_color[1:2, :, :]) |
|
cz = 200 * (input_color[1:2, :, :] - input_color[2:3, :, :]) |
|
transformed_color = np.concatenate((y, cx, cz), 0) |
|
|
|
elif fromSpace2toSpace == "ycxcz2xyz": |
|
y = (input_color[0:1, :, :] + 16) / 116 |
|
cx = input_color[1:2, :, :] / 500 |
|
cz = input_color[2:3, :, :] / 200 |
|
|
|
x = y + cx |
|
z = y - cz |
|
transformed_color = np.concatenate((x, y, z), 0) |
|
|
|
reference_illuminant = color_space_transform(np.ones(dim), 'linrgb2xyz') |
|
transformed_color = np.multiply(transformed_color, reference_illuminant) |
|
|
|
elif fromSpace2toSpace == "xyz2lab": |
|
reference_illuminant = color_space_transform(np.ones(dim), 'linrgb2xyz') |
|
input_color = np.divide(input_color, reference_illuminant) |
|
delta = 6 / 29 |
|
limit = 0.00885 |
|
|
|
input_color = np.where(input_color > limit, np.power(input_color, 1 / 3), (input_color / (3 * delta * delta)) + (4 / 29)) |
|
|
|
l = 116 * input_color[1:2, :, :] - 16 |
|
a = 500 * (input_color[0:1,:, :] - input_color[1:2, :, :]) |
|
b = 200 * (input_color[1:2, :, :] - input_color[2:3, :, :]) |
|
|
|
transformed_color = np.concatenate((l, a, b), 0) |
|
|
|
elif fromSpace2toSpace == "lab2xyz": |
|
y = (input_color[0:1, :, :] + 16) / 116 |
|
a = input_color[1:2, :, :] / 500 |
|
b = input_color[2:3, :, :] / 200 |
|
|
|
x = y + a |
|
z = y - b |
|
|
|
xyz = np.concatenate((x, y, z), 0) |
|
delta = 6 / 29 |
|
xyz = np.where(xyz > delta, xyz ** 3, 3 * delta ** 2 * (xyz - 4 / 29)) |
|
|
|
reference_illuminant = color_space_transform(np.ones(dim), 'linrgb2xyz') |
|
transformed_color = np.multiply(xyz, reference_illuminant) |
|
|
|
elif fromSpace2toSpace == "srgb2xyz": |
|
transformed_color = color_space_transform(input_color, 'srgb2linrgb') |
|
transformed_color = color_space_transform(transformed_color,'linrgb2xyz') |
|
elif fromSpace2toSpace == "srgb2ycxcz": |
|
transformed_color = color_space_transform(input_color, 'srgb2linrgb') |
|
transformed_color = color_space_transform(transformed_color, 'linrgb2xyz') |
|
transformed_color = color_space_transform(transformed_color, 'xyz2ycxcz') |
|
elif fromSpace2toSpace == "linrgb2ycxcz": |
|
transformed_color = color_space_transform(input_color, 'linrgb2xyz') |
|
transformed_color = color_space_transform(transformed_color, 'xyz2ycxcz') |
|
elif fromSpace2toSpace == "srgb2lab": |
|
transformed_color = color_space_transform(input_color, 'srgb2linrgb') |
|
transformed_color = color_space_transform(transformed_color, 'linrgb2xyz') |
|
transformed_color = color_space_transform(transformed_color, 'xyz2lab') |
|
elif fromSpace2toSpace == "linrgb2lab": |
|
transformed_color = color_space_transform(input_color, 'linrgb2xyz') |
|
transformed_color = color_space_transform(transformed_color, 'xyz2lab') |
|
elif fromSpace2toSpace == "ycxcz2linrgb": |
|
transformed_color = color_space_transform(input_color, 'ycxcz2xyz') |
|
transformed_color = color_space_transform(transformed_color, 'xyz2linrgb') |
|
elif fromSpace2toSpace == "lab2srgb": |
|
transformed_color = color_space_transform(input_color, 'lab2xyz') |
|
transformed_color = color_space_transform(transformed_color, 'xyz2linrgb') |
|
transformed_color = color_space_transform(transformed_color, 'linrgb2srgb') |
|
elif fromSpace2toSpace == "ycxcz2lab": |
|
transformed_color = color_space_transform(input_color, 'ycxcz2xyz') |
|
transformed_color = color_space_transform(transformed_color, 'xyz2lab') |
|
else: |
|
print('The color transform is not defined!') |
|
transformed_color = input_color |
|
|
|
return transformed_color |
|
|
|
def generate_spatial_filter(pixels_per_degree, channel): |
|
a1_A = 1 |
|
b1_A = 0.0047 |
|
a2_A = 0 |
|
b2_A = 1e-5 |
|
a1_rg = 1 |
|
b1_rg = 0.0053 |
|
a2_rg = 0 |
|
b2_rg = 1e-5 |
|
a1_by = 34.1 |
|
b1_by = 0.04 |
|
a2_by = 13.5 |
|
b2_by = 0.025 |
|
if channel == "A": |
|
a1 = a1_A |
|
b1 = b1_A |
|
a2 = a2_A |
|
b2 = b2_A |
|
elif channel == "RG": |
|
a1 = a1_rg |
|
b1 = b1_rg |
|
a2 = a2_rg |
|
b2 = b2_rg |
|
elif channel == "BY": |
|
a1 = a1_by |
|
b1 = b1_by |
|
a2 = a2_by |
|
b2 = b2_by |
|
|
|
|
|
max_scale_parameter = max([b1_A, b2_A, b1_rg, b2_rg, b1_by, b2_by]) |
|
r = np.ceil(3 * np.sqrt(max_scale_parameter / (2 * np.pi**2)) * pixels_per_degree) |
|
r = int(r) |
|
deltaX = 1.0 / pixels_per_degree |
|
x, y = np.meshgrid(range(-r, r + 1), range(-r, r + 1)) |
|
z = (x * deltaX)**2 + (y * deltaX)**2 |
|
|
|
|
|
g = a1 * np.sqrt(np.pi / b1) * np.exp(-np.pi**2 * z / b1) + a2 * np.sqrt(np.pi / b2) * np.exp(-np.pi**2 * z / b2) |
|
g = g / np.sum(g) |
|
|
|
return g, r |
|
|
|
def spatial_filter(img, s_a, s_rg, s_by, radius): |
|
|
|
|
|
|
|
dim = img.shape |
|
|
|
img_pad_a = np.pad(img[0:1, :, :], ((0, 0), (radius, radius), (radius, radius)), mode='edge') |
|
img_pad_rg = np.pad(img[1:2, :, :], ((0, 0), (radius, radius), (radius, radius)), mode='edge') |
|
img_pad_by = np.pad(img[2:3, :, :], ((0, 0), (radius, radius), (radius, radius)), mode='edge') |
|
|
|
|
|
img_tilde_opponent = np.zeros((dim[0], dim[1], dim[2])) |
|
img_tilde_opponent[0:1, :, :] = signal.convolve2d(img_pad_a.squeeze(0), s_a, mode='valid') |
|
img_tilde_opponent[1:2, :, :] = signal.convolve2d(img_pad_rg.squeeze(0), s_rg, mode='valid') |
|
img_tilde_opponent[2:3, :, :] = signal.convolve2d(img_pad_by.squeeze(0), s_by, mode='valid') |
|
|
|
|
|
img_tilde_linear_rgb = color_space_transform(img_tilde_opponent, 'ycxcz2linrgb') |
|
|
|
|
|
return np.clip(img_tilde_linear_rgb, 0.0, 1.0) |
|
|
|
def hunt_adjustment(img): |
|
|
|
|
|
|
|
L = img[0:1, :, :] |
|
|
|
|
|
img_h = np.zeros(img.shape) |
|
img_h[0:1, :, :] = L |
|
img_h[1:2, :, :] = np.multiply((0.01 * L), img[1:2, :, :]) |
|
img_h[2:3, :, :] = np.multiply((0.01 * L), img[2:3, :, :]) |
|
|
|
return img_h |
|
|
|
def hyab(reference, test): |
|
|
|
delta = reference - test |
|
return abs(delta[0:1, :, :]) + np.linalg.norm(delta[1:3, :, :], axis=0) |
|
|
|
def redistribute_errors(power_deltaE_hyab, cmax): |
|
|
|
pc = 0.4 |
|
pt = 0.95 |
|
|
|
|
|
|
|
|
|
deltaE_c = np.zeros(power_deltaE_hyab.shape) |
|
pccmax = pc * cmax |
|
deltaE_c = np.where(power_deltaE_hyab < pccmax, (pt / pccmax) * power_deltaE_hyab, pt + ((power_deltaE_hyab - pccmax) / (cmax - pccmax)) * (1.0 - pt)) |
|
|
|
return deltaE_c |
|
|
|
def feature_detection(imgy, pixels_per_degree, feature_type): |
|
|
|
|
|
|
|
|
|
w = 0.082 |
|
|
|
|
|
sd = 0.5 * w * pixels_per_degree |
|
radius = int(np.ceil(3 * sd)) |
|
|
|
|
|
[x, y] = np.meshgrid(range(-radius, radius+1), range(-radius, radius+1)) |
|
g = np.exp(-(x ** 2 + y ** 2) / (2 * sd * sd)) |
|
|
|
if feature_type == 'edge': |
|
|
|
Gx = np.multiply(-x, g) |
|
else: |
|
|
|
Gx = np.multiply(x ** 2 / (sd * sd) - 1, g) |
|
|
|
|
|
negative_weights_sum = -np.sum(Gx[Gx < 0]) |
|
positive_weights_sum = np.sum(Gx[Gx > 0]) |
|
Gx = np.where(Gx < 0, Gx / negative_weights_sum, Gx / positive_weights_sum) |
|
|
|
|
|
imgy_pad = np.pad(imgy, ((0, 0), (radius, radius), (radius, radius)), mode='edge').squeeze(0) |
|
featuresX = signal.convolve2d(imgy_pad, Gx, mode='valid') |
|
featuresY = signal.convolve2d(imgy_pad, np.transpose(Gx), mode='valid') |
|
|
|
return np.stack((featuresX, featuresY)) |
|
|
|
def compute_flip(reference, test, pixels_per_degree): |
|
assert reference.shape == test.shape |
|
|
|
|
|
qc = 0.7 |
|
qf = 0.5 |
|
|
|
|
|
reference = color_space_transform(reference, 'srgb2ycxcz') |
|
test = color_space_transform(test, 'srgb2ycxcz') |
|
|
|
|
|
|
|
s_a, radius_a = generate_spatial_filter(pixels_per_degree, 'A') |
|
s_rg, radius_rg = generate_spatial_filter(pixels_per_degree, 'RG') |
|
s_by, radius_by = generate_spatial_filter(pixels_per_degree, 'BY') |
|
radius = max(radius_a, radius_rg, radius_by) |
|
filtered_reference = spatial_filter(reference, s_a, s_rg, s_by, radius) |
|
filtered_test = spatial_filter(test, s_a, s_rg, s_by, radius) |
|
|
|
|
|
preprocessed_reference = hunt_adjustment(color_space_transform(filtered_reference, 'linrgb2lab')) |
|
preprocessed_test = hunt_adjustment(color_space_transform(filtered_test, 'linrgb2lab')) |
|
|
|
|
|
deltaE_hyab = hyab(preprocessed_reference, preprocessed_test) |
|
hunt_adjusted_green = hunt_adjustment(color_space_transform(np.array([[[0.0]], [[1.0]], [[0.0]]]), 'linrgb2lab')) |
|
hunt_adjusted_blue = hunt_adjustment(color_space_transform(np.array([[[0.0]], [[0.0]], [[1.0]]]), 'linrgb2lab')) |
|
cmax = np.power(hyab(hunt_adjusted_green, hunt_adjusted_blue), qc) |
|
deltaE_c = redistribute_errors(np.power(deltaE_hyab, qc), cmax) |
|
|
|
|
|
|
|
reference_y = (reference[0:1, :, :] + 16) / 116 |
|
test_y = (test[0:1, :, :] + 16) / 116 |
|
|
|
|
|
edges_reference = feature_detection(reference_y, pixels_per_degree, 'edge') |
|
points_reference = feature_detection(reference_y, pixels_per_degree, 'point') |
|
edges_test = feature_detection(test_y, pixels_per_degree, 'edge') |
|
points_test = feature_detection(test_y, pixels_per_degree, 'point') |
|
|
|
|
|
deltaE_f = np.maximum(abs(np.linalg.norm(edges_reference, axis=0) - np.linalg.norm(edges_test, axis=0)), abs(np.linalg.norm(points_test, axis=0) - np.linalg.norm(points_reference, axis=0))) |
|
deltaE_f = np.power(((1 / np.sqrt(2)) * deltaE_f), qf) |
|
|
|
|
|
return np.power(deltaE_c, 1 - deltaE_f) |
|
|