SPPAS 4.20

Module sppas.src.imgdata

Class sppasImagesSimilarity

Description

Estimate similarity between images to identify objects.

This class store several sets of images and estimates similarity

measures to identify which, among the known sets, is represented

in a new image.

Various solutions are implemented to estimate similarity:

  • the faster is to compare coordinates;
  • the slower is to compare image contents (colors, size, ...)
  • the most generic is to use the OpenCV recognition system.

Constructor

Create an instance.

View Source
def __init__(self):
    """Create an instance.

    """
    self.__kids = collections.OrderedDict()
    self.__score_level = 0.4
    self.__fr = False
    self.__recognizer = None
    self.__id_idx = 1

Public functions

get_known_identifiers

Return the list of known identifiers.

View Source
def get_known_identifiers(self):
    """Return the list of known identifiers."""
    return list(self.__kids.keys())
create_identifier

Create a new identifier and add it in the list of known ones.

Returns
  • (str) new identifier name
Parameters
  • nb_img
View Source
def create_identifier(self, nb_img=ImagesFIFO.DEFAULT_QUEUE_SIZE):
    """Create a new identifier and add it in the list of known ones.

        :return: (str) new identifier name

        """
    pid = 'id{:03d}'.format(self.__id_idx)
    self.__kids[pid] = ImagesFIFO(nb_img)
    self.__id_idx += 1
    return pid
remove_identifier

Remove an identifier of the list of known ones.

Parameters
  • kid: (str) An identifier
View Source
def remove_identifier(self, kid):
    """Remove an identifier of the list of known ones.

        :param kid: (str) An identifier

        """
    if kid in self.__kids:
        del self.__kids[kid]
add_image

Add an image to the known ones of the given identifier.

Parameters
  • kid: (str) An identifier
  • image: (sppasImage)
  • reference: (bool) This is the reference image for this kid
Raises

KeyError, TypeError

View Source
def add_image(self, kid, image, reference=False):
    """Add an image to the known ones of the given identifier.

        :param kid: (str) An identifier
        :param image: (sppasImage)
        :param reference: (bool) This is the reference image for this kid
        :raise: KeyError, TypeError

        """
    if kid not in self.__kids:
        raise sppasKeyError(kid, 'dict(ImagesFIFO)')
    self.__kids[kid].add(image, reference)
get_nb_images

Return the number of known images of the given identifier.

Parameters
  • kid: (str) An identifier
Returns
  • (int)
View Source
def get_nb_images(self, kid):
    """Return the number of known images of the given identifier.

        :param kid: (str) An identifier
        :return: (int)

        """
    if kid not in self.__kids:
        raise sppasKeyError(kid, 'dict(ImagesFIFO)')
    return len(self.__kids[kid])
get_ref_image

Return the reference image of the given identifier.

Parameters
  • kid: (str) An identifier
Returns
  • (sppasImage or None)
View Source
def get_ref_image(self, kid):
    """Return the reference image of the given identifier.

        :param kid: (str) An identifier
        :return: (sppasImage or None)

        """
    if kid not in self.__kids:
        raise sppasKeyError(kid, 'dict(ImagesFIFO)')
    return self.__kids[kid].get_ref_image()
get_cur_coords

Return the current coordinates of an identifier or None if unknown.

Parameters
  • kid: (str) An identifier
Raises

KeyError

Returns
  • (sppasCoords or None)
View Source
def get_cur_coords(self, kid):
    """Return the current coordinates of an identifier or None if unknown.

        :param kid: (str) An identifier
        :raise: KeyError

        :return: (sppasCoords or None)

        """
    if kid not in self.__kids:
        raise sppasKeyError(kid, 'dict(ImagesFIFO)')
    return self.__kids[kid].get_cur_coords()
set_cur_coords

Set the current coordinates for an identifier.

Parameters
  • kid: (str) An identifier
  • coords: (sppasCoords)
Raises

KeyError, TypeError

View Source
def set_cur_coords(self, kid, coords):
    """Set the current coordinates for an identifier.

        :param kid: (str) An identifier
        :param coords: (sppasCoords)
        :raise: KeyError, TypeError

        """
    if kid not in self.__kids:
        raise sppasKeyError(kid, 'dict(ImagesFIFO)')
    self.__kids[kid].set_cur_coords(coords)
get_ref_coords

Return the reference coordinates of an identifier or None if unknown.

Parameters
  • kid: (str) An identifier
Raises

KeyError

View Source
def get_ref_coords(self, kid):
    """Return the reference coordinates of an identifier or None if unknown.

        :param kid: (str) An identifier
        :raise: KeyError

        """
    if kid not in self.__kids:
        raise sppasKeyError(kid, 'dict(ImagesFIFO)')
    return self.__kids[kid].get_ref_coords()
set_ref_coords

Set the reference coordinates of an identifier.

Parameters
  • kid: (str) An identifier
  • coords: (sppasCoords)
Raises

KeyError, TypeError

View Source
def set_ref_coords(self, kid, coords):
    """Set the reference coordinates of an identifier.

        :param kid: (str) An identifier
        :param coords: (sppasCoords)
        :raise: KeyError, TypeError

        """
    if kid not in self.__kids:
        raise sppasKeyError(kid, 'dict(ImagesFIFO)')
    self.__kids[kid].set_ref_coords(coords)
enable_face_recognition

Enable or disable the automatic recognizer.

Parameters
  • value: (bool)
View Source
def enable_face_recognition(self, value=True):
    """Enable or disable the automatic recognizer.

        :param value: (bool)

        """
    self.__fr = bool(value)
set_score_level

Fix threshold score for the identification measure.

Parameters
  • value: (float) Value in range [0., 1.]
View Source
def set_score_level(self, value):
    """Fix threshold score for the identification measure.

        :param value: (float) Value in range [0., 1.]

        """
    value = float(value)
    if value < 0.0 or value > 1.0:
        raise IntervalRangeException(value, 0, 1)
    self.__score_level = value
compare_kids_coords

Return a similarity score between two known identifiers.

Returns
  • (float) Return 0. if no similarity or if eval not done
Parameters
  • kid1
  • kid2
View Source
def compare_kids_coords(self, kid1, kid2):
    """Return a similarity score between two known identifiers.

        :return: (float) Return 0. if no similarity or if eval not done

        """
    r1 = self.__kids[kid1].get_ref_coords()
    r2 = self.__kids[kid2].get_ref_coords()
    if r1 is not None and r2 is not None:
        cc = sppasCoordsCompare(r1, r2)
        ccr = cc.compare_coords()
    else:
        ccr = 0.0
    l1 = self.__kids[kid1].get_cur_coords()
    l2 = self.__kids[kid2].get_cur_coords()
    if l1 is not None and l2 is not None:
        cc = sppasCoordsCompare(l1, l2)
        ccl = cc.compare_coords()
    else:
        ccl = 0.0
    return (ccr + ccl) / 2.0
compare_kids_images

Return a similarity score between two known identifiers.

Returns
  • (float) Return 0. if no similarity or if eval not done
Parameters
  • kid1
  • kid2
View Source
def compare_kids_images(self, kid1, kid2):
    """Return a similarity score between two known identifiers.

        :return: (float) Return 0. if no similarity or if eval not done

        """
    r1 = self.__kids[kid1].get_ref_image()
    r2 = self.__kids[kid2].get_ref_image()
    if r1 is not None and r2 is not None:
        ic = sppasImageCompare(r1, r2)
        return ic.score()
    return 0.0
identify

Among the known identifiers, who matches the given image/coords.

Should return None if none of the known ids is recognized.

Parameters
  • image: (sppasImage)
  • coords: (sppasCoords)
Returns
  • (kid, score) or (None, 0.)
View Source
def identify(self, image=None, coords=None):
    """Among the known identifiers, who matches the given image/coords.

        Should return None if none of the known ids is recognized.

        :param image: (sppasImage)
        :param coords: (sppasCoords)
        :return: (kid, score) or (None, 0.)

        """
    kid = None
    score = 0.0
    if len(self.__kids) > 0:
        if image is not None:
            if self.__recognizer is not None:
                kid, score = self.predict_recognizer(image)
            if kid is None:
                kid, score = self.predict_compare_images(image)
        if coords is not None:
            p, s = self.predict_compare_coords(coords)
            if kid is None:
                kid = p
                score = s
            elif p is not None:
                if kid == p:
                    score = max(score, s)
                elif score > s:
                    score = score / 2.0
                else:
                    kid = p
                    score = s / 2.0
    return (kid, score)
predict_compare_images

Compare the given image to the existing ones.

Evaluate similarity of image contents (very very low).

Parameters
  • image: (sppasImage) The image to compare with
Returns
  • tuple(kid, score) or (None, 0.)
View Source
def predict_compare_images(self, image):
    """Compare the given image to the existing ones.

        Evaluate similarity of image contents (very very low).

        :param image: (sppasImage) The image to compare with
        :return: tuple(kid, score) or (None, 0.)

        """
    scores = dict()
    sc = None
    for ref_name in self.__kids:
        ref_img = self.__kids[ref_name].get_ref_image()
        if ref_img is not None:
            cmp = sppasImageCompare(image, ref_img)
            areas = cmp.compare_areas()
            sizes = cmp.compare_sizes()
            mse = cmp.compare_with_mse()
            kld = cmp.compare_with_kld()
            scx = 0.4 * mse + 0.3 * kld + 0.15 * areas + 0.15 * sizes
            if sc is not None:
                sc = (sc + scx) / 2.0
            else:
                sc = scx
        if sc is None:
            sc = 0.0
        scores[ref_name] = sc
    scores = collections.Counter(scores)
    sorted_scores = scores.most_common()
    if sorted_scores[0][1] > self.__score_level:
        return sorted_scores[0]
    return (None, 0.0)
predict_compare_coords

Compare the given coords to the existing ones.

Parameters
  • coords: (sppasCoords) The coords to compare with
Returns
  • tuple(kid, score) or (None, 0.)
View Source
def predict_compare_coords(self, coords):
    """Compare the given coords to the existing ones.

        :param coords: (sppasCoords) The coords to compare with
        :return: tuple(kid, score) or (None, 0.)

        """
    scores = dict()
    for ref_name in self.__kids:
        ref_coords = self.__kids[ref_name].get_ref_coords()
        cur_coords = self.__kids[ref_name].get_cur_coords()
        sc1 = sc2 = None
        if ref_coords is not None:
            ccr = sppasCoordsCompare(ref_coords, coords)
            sc1 = ccr.compare_coords()
        if cur_coords is not None:
            ccr = sppasCoordsCompare(cur_coords, coords)
            sc2 = ccr.compare_coords()
        if sc1 is None or sc2 is None:
            if sc1 is None:
                sc = sc2
            else:
                sc = sc1
        else:
            sc = 0.4 * sc1 + 0.6 * sc2
        if sc is None:
            sc = 0.0
        scores[ref_name] = sc
    scores = collections.Counter(scores)
    sorted_scores = scores.most_common()
    if sorted_scores[0][1] > self.__score_level:
        return sorted_scores[0]
    return (None, 0.0)
train_recognizer

Train the recognizer from the current set of images of known ids.

View Source
def train_recognizer(self):
    """Train the recognizer from the current set of images of known ids.

        """
    images = list()
    labels = list()
    for i, kid in enumerate(self.__kids):
        for img in self.__kids[kid]:
            gray = cv2.cvtColor(img.iresize(160, 160), cv2.COLOR_BGR2GRAY)
            images.append(gray)
            labels.append(i)
        img = self.__kids[kid].get_ref_image()
        gray = cv2.cvtColor(img.iresize(160, 160), cv2.COLOR_BGR2GRAY)
        images.append(gray)
        labels.append(len(images))
    try:
        self.__recognizer = cv2.face.LBPHFaceRecognizer_create()
    except AttributeError as e:
        raise Exception('Error with OpenCV. You probably need to uninstall opencv-python, and/or to re-install opencv-contrib-python: {:s}'.format(str(e)))
    self.__recognizer.train(images, numpy.array(labels))
predict_recognizer

Compare the given image to the existing ones.

Parameters
  • image: (sppasImage)
Returns
  • tuple(kid, score) or (None, 0.)
View Source
def predict_recognizer(self, image):
    """Compare the given image to the existing ones.

        :param image: (sppasImage)
        :return: tuple(kid, score) or (None, 0.)

        """
    if image.shape == () or image.width * image.height == 0:
        logging.error('Invalid image to predict a face with the recognizer.')
        return (None, 0)
    if self.__recognizer is not None:
        resized = image.iresize(160, 160)
        gray_img = cv2.cvtColor(resized, cv2.COLOR_BGR2GRAY)
        kidx, dist = self.__recognizer.predict(gray_img)
        for i, kid in enumerate(self.__kids):
            if i == kidx:
                confidence = 1.0 - float(dist) / 100.0
                if confidence > self.__score_level:
                    return (kid, confidence)
                else:
                    return (None, 0.0)
    return (None, 0.0)
write

Save the images of the known identities to the given folder.

Parameters
  • folder: Place to save images
View Source
def write(self, folder):
    """Save the images of the known identities to the given folder.

        :param folder: Place to save images

        """
    if os.path.exists(folder) is True:
        logging.warning('Folder {:s} is already existing. It is moved into the Trash of SPPAS.'.format(folder))
        try:
            sppasTrash().put_folder_into(folder)
        except PermissionError:
            logging.error("Access denied. The folder can't be deleted.")
    if os.path.exists(folder) is False:
        os.mkdir(folder)
    for kid in self.__kids:
        for i, image in enumerate(self.__kids[kid]):
            filename = '{:s}_{:02d}.png'.format(kid, i)
            image.write(os.path.join(folder, filename))
        img = self.__kids[kid].get_ref_image()
        filename = '{:s}_ref.png'.format(kid)
        img.write(os.path.join(folder, filename))

Overloads

__str__
View Source
def __str__(self):
    return self.__class__.__name__
__repr__
View Source
def __repr__(self):
    return self.__class__.__name__
__format__
View Source
def __format__(self, fmt):
    return str(self).__format__(fmt)
__len__

Return the number of kids.

View Source
def __len__(self):
    """Return the number of kids."""
    return len(self.__kids)
__iter__

Browse the current kids.

View Source
def __iter__(self):
    """Browse the current kids."""
    for kid in self.__kids:
        yield kid
__getitem__

Return the ImagesFIFO() of a given kid.

Parameters
  • item
View Source
def __getitem__(self, item):
    """Return the ImagesFIFO() of a given kid. """
    if item not in self.__kids:
        raise sppasKeyError(item, 'dict(ImagesFIFO)')
    return self.__kids[item]
__contains__

Return true if item in kids.

Parameters
  • item
View Source
def __contains__(self, item):
    """Return true if item in kids. """
    return item in self.__kids