SPPAS 4.22

https://sppas.org/

Module sppas.src.imgdata

Class sppasImageCompare

Description

Very basic scoring to compare images.

How to quantify difference between two images?

  • Are images of the same area and dimension?
  • Is lightness/contrast the same? Is color information important?

Constructor

View Source
def __init__(self, img1, img2):
    self.__img1 = img1
    self.__img2 = img2

Public functions

compare_with_distance

Return a score based on the Euclidian distance.

DOES NOT WORK: can't get the max distance !!!!

View Source
def compare_with_distance(self):
    """Return a score based on the Euclidian distance.

        DOES NOT WORK: can't get the max distance !!!!

        """
    w1, h1 = self.__img1.size()
    w2, h2 = self.__img2.size()
    img1 = self.__img1.iresize(width=min(w1, w2), height=min(h1, h2))
    img2 = self.__img2.iresize(width=min(w1, w2), height=min(h1, h2))
    dist = img1.euclidian_distance(img2)
    black = img1.blank_image()
    white = img1.blank_image(white=True)
    dist_max = black.euclidian_distance(white)
    return 1.0 - dist / dist_max

score

Mix all comparison scores to return a single one.

Linear interpolation with empirically fixed weights.

View Source
def score(self):
    """Mix all comparison scores to return a single one.

        Linear interpolation with empirically fixed weights.

        """
    s1 = self.compare_areas()
    s2 = self.compare_sizes()
    s3 = self.compare_with_mse()
    s4 = self.compare_with_kld()
    return 0.2 * s1 + 0.2 * s2 + 0.4 * s3 + 0.2 * s4

compare_areas

Return a score on how image areas are similar.

Returns
  • (float) value between 0. and 1.
View Source
def compare_areas(self):
    """Return a score on how image areas are similar.

        :return: (float) value between 0. and 1.

        """
    w1, h1 = self.__img1.size()
    w2, h2 = self.__img2.size()
    area1 = w1 * h1
    area2 = w2 * h2
    min_area = min(area1, area2)
    max_area = max(area1, area2)
    if max_area < 2:
        return 0.0
    return float(min_area) / float(max_area)

compare_sizes

Return a score on how image sizes are similar.

Returns
  • (float) value between 0. and 1.
View Source
def compare_sizes(self):
    """Return a score on how image sizes are similar.

        :return: (float) value between 0. and 1.

        """
    w1, h1 = self.__img1.size()
    w2, h2 = self.__img2.size()
    w_min = min(w1, w2)
    w_max = max(w1, w2)
    h_min = min(h1, h2)
    h_max = max(h1, h2)
    w_ratio = 0.0
    if w_max > 2:
        w_ratio = float(w_min) / float(w_max)
    h_ratio = 0.0
    if h_max > 2:
        h_ratio = float(h_min) / float(h_max)
    return (w_ratio + h_ratio) / 2.0

compare_with_mse

Return a score to compare images with the Mean Squared Error.

Returns
  • (float) value between 0. and 1.
View Source
def compare_with_mse(self):
    """Return a score to compare images with the Mean Squared Error.

        :return: (float) value between 0. and 1.

        """
    r1 = sppasImageCompare(self.__img1, self.__img2.ired()).mse()
    r2 = sppasImageCompare(self.__img1.ired(), self.__img2).mse()
    b1 = sppasImageCompare(self.__img1, self.__img2.iblue()).mse()
    b2 = sppasImageCompare(self.__img1.iblue(), self.__img2).mse()
    g1 = sppasImageCompare(self.__img1, self.__img2.igreen()).mse()
    g2 = sppasImageCompare(self.__img1.igreen(), self.__img2).mse()
    total = r1 + r2 + b1 + b2 + g1 + g2
    mini = min((r1, r2, b1, b2, g1, g2))
    if round(total, 2) == 0.0:
        return 1.0
    return min(1.0, max(0.0, 1.0 - self.mse() / mini))

mse

Return the Mean Squared Error between the two images.

Returns
  • (float) The lower the error, the more similar the images are
View Source
def mse(self):
    """Return the Mean Squared Error between the two images.

        :return: (float) The lower the error, the more similar the images are

        """
    img1_gray = self.__img1.igray()
    img2_gray = self.__img2.igray()
    w1, h1 = self.__img1.size()
    w2, h2 = self.__img2.size()
    img1 = img1_gray.iresize(width=min(w1, w2), height=min(h1, h2))
    img2 = img2_gray.iresize(width=min(w1, w2), height=min(h1, h2))
    err = numpy.sum((img1.astype('float') - img2.astype('float')) ** 2)
    err /= float(img1.shape[0] * img2.shape[1])
    return float(err)

compare_with_kld

Return a score to compare images with the Kullback-Leibler Dist.

Returns
  • (float) value between 0. and 1.
View Source
def compare_with_kld(self):
    """Return a score to compare images with the Kullback-Leibler Dist.

        :return: (float) value between 0. and 1.

        """
    w1, h1 = self.__img1.size()
    w2, h2 = self.__img2.size()
    w = max(w1, w2)
    h = max(h1, h2)
    img1 = self.__img1 // 8
    img2 = self.__img2 // 8
    rgb1 = self.img_to_rgb_values(img1)
    rgb2 = self.img_to_rgb_values(img2)
    obs1 = rgb1.tolist()
    obs2 = rgb2.tolist()
    neg_img1 = img1.inegative()
    neg_img2 = img2.inegative()
    neg_rgb1 = self.img_to_rgb_values(neg_img1)
    neg_rgb2 = self.img_to_rgb_values(neg_img2)
    neg_obs1 = neg_rgb1.tolist()
    neg_obs2 = neg_rgb2.tolist()
    model = self.img_to_rgb_dict(rgb1)
    kl = sppasKullbackLeibler(model=model)
    kl.set_epsilon(1.0 / float(w * h * 2))
    kl.set_observations(neg_obs1)
    dist_max1 = kl.eval_kld()
    kl.set_observations(obs2)
    dist1 = kl.eval_kld()
    model = self.img_to_rgb_dict(rgb2)
    kl = sppasKullbackLeibler(model=model)
    kl.set_epsilon(1.0 / float(w * h * 2))
    kl.set_observations(neg_obs2)
    dist_max2 = kl.eval_kld()
    kl.set_observations(obs1)
    dist2 = kl.eval_kld()
    norm = (dist1 + dist2) / (dist_max1 + dist_max2)
    return min(1.0, max(0.0, 1.0 - norm))

kld

Return the Kullback-Leibler Distance between the two images.

Returns
  • (float) The lower the distance, the more similar the images are
View Source
def kld(self):
    """Return the Kullback-Leibler Distance between the two images.

        :return: (float) The lower the distance, the more similar the images are

        """
    img1 = self.__img1 // 16
    img2 = self.__img2 // 16
    w1, h1 = img1.size()
    w2, h2 = img1.size()
    model = self.img_to_rgb_dict(img1)
    rgb2 = self.img_to_rgb_values(img2)
    obs = rgb2.tolist()
    kl = sppasKullbackLeibler(model=model, observations=obs)
    kl.set_epsilon(1.0 / (2.0 * (w1 * h1 + w2 * h2)))
    return kl.eval_kld()

img_to_rgb_dict

View Source
@staticmethod
def img_to_rgb_dict(img):
    if isinstance(img, sppasImage):
        rgb = sppasImageCompare.img_to_rgb_values(img)
    else:
        rgb = img
    unique, counts = numpy.unique(rgb, return_counts=True)
    occ1 = dict(zip(unique, counts))
    model = dict()
    n1 = len(rgb)
    for obs in occ1:
        model[obs] = float(occ1[obs]) / float(n1)
    return model

img_to_rgb_values

Return a numpy.array representing a unique value of the colors.

Parameters
  • img: (sppasImage)
View Source
@staticmethod
def img_to_rgb_values(img):
    """Return a numpy.array representing a unique value of the colors.

        :param img: (sppasImage)

        """
    b = numpy.array(img[:, :, 0], dtype=int).reshape(-1)
    g = numpy.array(img[:, :, 1], dtype=int).reshape(-1)
    g = g * 255
    r = numpy.array(img[:, :, 2], dtype=int).reshape(-1)
    r = r * 65535
    return r + g + b