quick add of default_{model|threshold} to settings, face_refimg_link now stores model_used and face_distance AND working implementation of own face_distance algorithm rather than compareAI(), removed older AI code it would no longer work with DB structures anyway, tweaked viewer to remove coords of unmatched faces for now
This commit is contained in:
@@ -190,9 +190,11 @@ class Settings(Base):
|
||||
import_path = Column(String)
|
||||
storage_path = Column(String)
|
||||
recycle_bin_path = Column(String)
|
||||
default_model = Column(Integer,ForeignKey('ai_model.id'), unique=True, nullable=False)
|
||||
default_threshold = Column(Integer)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<id: {self.id}, import_path: {self.import_path}, recycle_bin_path: {self.recycle_bin_path}>"
|
||||
return f"<id: {self.id}, import_path: {self.import_path}, recycle_bin_path: {self.recycle_bin_path}, default_model: {self.default_model}, default_threshold: {self.default_threshold}>"
|
||||
|
||||
class PersonRefimgLink(Base):
|
||||
__tablename__ = "person_refimg_link"
|
||||
@@ -459,8 +461,6 @@ def RunJob(job):
|
||||
JobMoveFiles(job)
|
||||
elif job.name == "restore_files":
|
||||
JobRestoreFiles(job)
|
||||
elif job.name == "processai":
|
||||
JobProcessAI(job)
|
||||
elif job.name == "run_ai_on":
|
||||
JobRunAIOn(job)
|
||||
elif job.name == "rotate_image":
|
||||
@@ -959,26 +959,11 @@ def RunFuncOnFilesInPath( job, path, file_func, count_dirs ):
|
||||
return
|
||||
|
||||
|
||||
def JobProcessAI(job):
|
||||
path=[jex.value for jex in job.extra if jex.name == "path"][0]
|
||||
path_prefix=[jex.value for jex in job.extra if jex.name == "path_prefix"][0]
|
||||
path = SymlinkName(path_prefix, path, '/')
|
||||
p = session.query(Path).filter(Path.path_prefix==path).first()
|
||||
job.num_files=p.num_files
|
||||
|
||||
RunFuncOnFilesInPath( job, path, ProcessAI, True )
|
||||
|
||||
FinishJob(job, "Finished Processesing AI")
|
||||
return
|
||||
|
||||
def WrapperForScanFileForPerson(job, entry):
|
||||
which_person=[jex.value for jex in job.extra if jex.name == "person"][0]
|
||||
|
||||
if entry.type.name == 'Image':
|
||||
if DEBUG:
|
||||
AddLogForJob( job, f'INFO: processing File: {entry.name}' )
|
||||
for pid in job.ppl:
|
||||
ScanFileForPerson( job, entry, pid, force=False)
|
||||
ScanFileForPerson( job, entry, force=False)
|
||||
# processed this file, add 1 to count
|
||||
job.current_file_num+=1
|
||||
return
|
||||
@@ -992,9 +977,9 @@ def JobRunAIOn(job):
|
||||
AddLogForJob(job, f"INFO: Starting looking For faces in files job...")
|
||||
which_person=[jex.value for jex in job.extra if jex.name == "person"][0]
|
||||
if which_person == "all":
|
||||
ppl=session.query(Person).all()
|
||||
job.refimgs = session.query(Refimg).all()
|
||||
else:
|
||||
ppl=session.query(Person).filter(Person.tag==which_person).all()
|
||||
job.refimgs=session.query(Refimg).join(PersonRefimgLink).join(Person).filter(Person.tag==which_person).all()
|
||||
|
||||
# start by working out how many images in this selection we will need face match on
|
||||
job.num_files = 0
|
||||
@@ -1011,13 +996,8 @@ def JobRunAIOn(job):
|
||||
job.current_file_num = 0
|
||||
session.commit()
|
||||
|
||||
ppl_lst=[]
|
||||
for person in ppl:
|
||||
ppl_lst.append(person.id)
|
||||
|
||||
job.ppl = ppl_lst
|
||||
|
||||
for jex in job.extra:
|
||||
print( jex )
|
||||
if 'eid-' in jex.name:
|
||||
entry=session.query(Entry).get(jex.value)
|
||||
if entry.type.name == 'Directory':
|
||||
@@ -1027,8 +1007,7 @@ def JobRunAIOn(job):
|
||||
which_file=session.query(Entry).join(File).filter(Entry.id==jex.value).first()
|
||||
if DEBUG:
|
||||
AddLogForJob( job, f'INFO: processing File: {entry.name}' )
|
||||
for person in ppl:
|
||||
ScanFileForPerson( job, which_file, person.id, force=False)
|
||||
ScanFileForPerson( job, which_file, force=False)
|
||||
# processed this file, add 1 to count
|
||||
job.current_file_num+=1
|
||||
else:
|
||||
@@ -1081,46 +1060,6 @@ def GenHashAndThumb(job, e):
|
||||
e.file_details.last_hash_date = time.time()
|
||||
return
|
||||
|
||||
def ProcessAI(job, e):
|
||||
if e.type.name != 'Image':
|
||||
job.current_file_num+=1
|
||||
return
|
||||
|
||||
file = e.FullPathOnFS()
|
||||
stat = os.stat(file)
|
||||
# find if file is newer than when we found faces before (fyi: first time faces_created_on == 0)
|
||||
if stat.st_ctime > e.file_details.faces_created_on:
|
||||
session.add(e)
|
||||
im_orig = Image.open(file)
|
||||
im = ImageOps.exif_transpose(im_orig)
|
||||
|
||||
faces = generateUnknownEncodings(im)
|
||||
e.file_details.faces_created_on=time.time()
|
||||
if faces:
|
||||
flat_faces = numpy.array(faces)
|
||||
e.file_details.faces = flat_faces.tobytes()
|
||||
else:
|
||||
e.file_details.faces = None
|
||||
job.current_file_num+=1
|
||||
return
|
||||
else:
|
||||
if not e.file_details.faces:
|
||||
print("OPTIM: This image has no faces, skip it")
|
||||
job.current_file_num+=1
|
||||
return
|
||||
recover=numpy.frombuffer(e.file_details.faces,dtype=numpy.float64)
|
||||
real_recover=numpy.reshape(recover,(-1,128))
|
||||
l=[]
|
||||
for el in real_recover:
|
||||
l.append(numpy.array(el))
|
||||
faces = l
|
||||
people = session.query(Person).all()
|
||||
for unknown_encoding in faces:
|
||||
for person in people:
|
||||
lookForPersonInImage(job, person, unknown_encoding, e)
|
||||
ProcessFileForJob(job, f"Finished processing {e.name}", e.name )
|
||||
return
|
||||
|
||||
def lookForPersonInImage(job, person, unknown_encoding, e):
|
||||
FinishJob( job, "THIS CODE HAS BEEN REMOVED, need to use new Face* tables, and rethink", "Failed" )
|
||||
return
|
||||
@@ -1428,8 +1367,10 @@ def DelFacesForFile( eid ):
|
||||
session.commit()
|
||||
return
|
||||
|
||||
def MatchRefimgToFace( refimg_id, face_id ):
|
||||
rfl = FaceRefimgLink( refimg_id = refimg_id, face_id = face_id )
|
||||
def MatchRefimgToFace( refimg_id, face_id, model, face_dist ):
|
||||
# remove any match to this face from previous attempts, and 'replace' with new one
|
||||
session.query(FaceRefimgLink).filter(FaceRefimgLink.face_id==face_id).delete()
|
||||
rfl = FaceRefimgLink( refimg_id = refimg_id, face_id = face_id, model_used=model, face_distance=face_dist )
|
||||
session.add(rfl)
|
||||
session.commit()
|
||||
return
|
||||
@@ -1438,7 +1379,18 @@ def UnmatchedFacesForFile( eid ):
|
||||
rows = session.execute( f"select f.* from face f left join face_refimg_link frl on f.id = frl.face_id join face_file_link ffl on f.id = ffl.face_id where ffl.file_eid = {eid} and frl.refimg_id is null" )
|
||||
return rows
|
||||
|
||||
def ScanFileForPerson( job, e, person_id, force=False ):
|
||||
def BestFaceMatch(dist, fid, threshold):
|
||||
# 1 is not a match (0 is perfect match)
|
||||
lowest=1.0
|
||||
which=None
|
||||
for who in dist:
|
||||
if who in dist and fid in dist[who] and dist[who][fid][0] < lowest and dist[who][fid][0] <= threshold:
|
||||
lowest=dist[who][fid][0]
|
||||
which=who
|
||||
print( f"bfm: return {which}, {lowest} for {fid}" )
|
||||
return which, lowest
|
||||
|
||||
def ScanFileForPerson( job, e, force=False ):
|
||||
file_h = session.query(File).get( e.id )
|
||||
# if we are forcing this, delete any old faces (this will also delete linked tables), and reset faces_created_on to None
|
||||
if force:
|
||||
@@ -1446,12 +1398,12 @@ def ScanFileForPerson( job, e, person_id, force=False ):
|
||||
DelFacesForFile( e.id )
|
||||
file_h.faces_created_on = 0
|
||||
|
||||
# optimise: dont rescan if we already have faces (we are just going to try
|
||||
# to match (maybe?) a refimg
|
||||
# optimise: dont rescan if we already have faces
|
||||
if file_h.faces_created_on == 0:
|
||||
if DEBUG:
|
||||
AddLogForJob( job, f"DEBUG: {e.name} is missing unknown faces, generating them" )
|
||||
im = face_recognition.load_image_file(e.FullPathOnFS())
|
||||
# TODO: use setting to use model
|
||||
face_locations = face_recognition.face_locations(im)
|
||||
unknown_encodings = face_recognition.face_encodings(im, known_face_locations=face_locations)
|
||||
for locn, face in zip( face_locations, unknown_encodings ):
|
||||
@@ -1459,22 +1411,35 @@ def ScanFileForPerson( job, e, person_id, force=False ):
|
||||
file_h.faces_created_on = time.time()
|
||||
session.commit()
|
||||
|
||||
## now look for person
|
||||
refimgs = session.query(Refimg).join(PersonRefimgLink).filter(PersonRefimgLink.person_id==person_id).all()
|
||||
uf = UnmatchedFacesForFile( e.id )
|
||||
if DEBUG and not uf:
|
||||
AddLogForJob( job, "DEBUG: {e.name} all faces already matched - finished" )
|
||||
|
||||
for face in uf:
|
||||
for r in refimgs:
|
||||
# get default_model from settings (test this)
|
||||
settings = session.query(Settings).first()
|
||||
model=settings.default_model
|
||||
threshold = settings.default_threshold
|
||||
|
||||
faces = session.query(Face).join(FaceFileLink).filter(FaceFileLink.file_eid==e.id).all()
|
||||
# if there are no faces for this file, then dont go any futher
|
||||
if not faces:
|
||||
return
|
||||
|
||||
dist={}
|
||||
name={}
|
||||
for r in job.refimgs:
|
||||
dist[r.id]={}
|
||||
name[r.id]=r.fname
|
||||
for face in faces:
|
||||
for r in job.refimgs:
|
||||
unknown_face_data = numpy.frombuffer(face.face, dtype=numpy.float64)
|
||||
refimg_face_data = numpy.frombuffer(r.face, dtype=numpy.float64)
|
||||
match = compareAI(refimg_face_data, unknown_face_data)
|
||||
if match[0]:
|
||||
AddLogForJob(job, f'WE MATCHED: {r.fname} with file: {e.name} ')
|
||||
MatchRefimgToFace( r.id, face.id )
|
||||
# no need to keep looking for this face, we found it, go to next unknown face
|
||||
break
|
||||
dist[r.id][face.id] = face_recognition.face_distance(unknown_face_data, [refimg_face_data])
|
||||
|
||||
# if you need to check face distances, uncomment this: print( f"dist={dist}" )
|
||||
faces = session.execute( f"select f.* from face f join face_file_link ffl on f.id = ffl.face_id where ffl.file_eid = {e.id}" )
|
||||
for face in faces:
|
||||
who, fd = BestFaceMatch(dist, face.id, threshold )
|
||||
if who != None:
|
||||
MatchRefimgToFace( who, face.id, model, fd )
|
||||
AddLogForJob(job, f'WE MATCHED: {name[who]} with file: {e.name} - face distance of {fd}')
|
||||
del( dist[who] )
|
||||
return
|
||||
|
||||
|
||||
@@ -1482,7 +1447,7 @@ if __name__ == "__main__":
|
||||
print("INFO: PA job manager starting - listening on {}:{}".format( PA_JOB_MANAGER_HOST, PA_JOB_MANAGER_PORT) )
|
||||
|
||||
InitialValidationChecks()
|
||||
|
||||
|
||||
HandleJobs()
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||
s.bind((PA_JOB_MANAGER_HOST, PA_JOB_MANAGER_PORT))
|
||||
|
||||
Reference in New Issue
Block a user