Duplicated faces on rescan faces
Face recognition is not deterministic and will sometimes create slightly different location points for a face. If you rescan faces, then this will lead to duplicates of the same face.

To reduce the number of duplications, this function should check all faces for an image. If none match the new faces within a certain margin (+-5%), then add the face. Otherwise, skip.

Maybe this is possible to do within the getOrCreate call, but I haven't checked up on the documentation yet.

https://github.com/LibrePhotos/librephotos/blob/8d834d9721b56656441be74b4c5bacb7200698f2/api/models/photo.py#L320-L364
diff --git a/api/models/face.py b/api/models/face.py
index c1befce..ec0e81a 100644
--- a/api/models/face.py
+++ b/api/models/face.py
@@ -1,4 +1,7 @@
+import os
+
 from django.db import models
+from django.dispatch import receiver
 
 from api.models.person import Person, get_unknown_person
 from api.models.photo import Photo
@@ -26,3 +29,11 @@ class Face(models.Model):
 
     def __str__(self):
         return "%d" % self.id
+
+
+# From: https://stackoverflow.com/questions/16041232/django-delete-filefield
+@receiver(models.signals.post_delete, sender=Face)
+def auto_delete_file_on_delete(sender, instance, **kwargs):
+    if instance.image:
+        if os.path.isfile(instance.image.path):
+            os.remove(instance.image.path)
diff --git a/api/models/photo.py b/api/models/photo.py
index d813b5a..ac92fb4 100644
--- a/api/models/photo.py
+++ b/api/models/photo.py
@@ -444,7 +444,23 @@ class Photo(models.Model):
 
                 image_path = self.image_hash + "_" + str(idx_face) + ".jpg"
 
-                face, created = api.models.face.Face.objects.get_or_create(
+                margin = 2
+                existing_faces = api.models.face.Face.objects.filter(
+                    photo=self,
+                    location_top__lte    = face_location[0]+margin,
+                    location_top__gte    = face_location[0]-margin,
+                    location_right__lte  = face_location[1]+margin,
+                    location_right__gte  = face_location[1]-margin,
+                    location_bottom__lte = face_location[2]+margin,
+                    location_bottom__gte = face_location[2]-margin,
+                    location_left__lte   = face_location[3]+margin,
+                    location_left__gte   = face_location[3]-margin,
+                )
+
+                if existing_faces.count() != 0:
+                    continue
+
+                face = api.models.face.Face(
                     image_path=image_path,
                     photo=self,
                     location_top=face_location[0],
@@ -452,7 +468,7 @@ class Photo(models.Model):
                     location_bottom=face_location[2],
                     location_left=face_location[3],
                     encoding=face_encoding.tobytes().hex(),
-                    defaults={"person": unknown_person},
+                    person=unknown_person,
                 )
 
                 face_io = BytesIO()
['api/models/face.py', 'api/models/photo.py']