def __start_stage(self, count): log.info( f'Stage {self.__status["state"]} for {count} steps started') self.__status['count'] = count self.__status['current'] = 0 self.__status['faces_count'] = 0 self.__status['faces_per_second'] = 0 self.__status['starttime'] = time.time()
def createCacheDB(cfg): cachedb_file = cfg.get_path('files', 'cachedb') if cachedb_file: log.info(f'Using cachedb: {cachedb_file}') return CacheDB(cachedb_file) else: log.info(f'Not using cachedb') return None
def __save(self): log.info('Patterns saving') data = {'files': self.__files, 'persons': self.__persons} dump = pickle.dumps(data) with open(self.__pickle_file, 'wb') as f: f.write(dump) log.info(f'Patterns done: {self.__pickle_file} ({len(dump)} bytes)')
def __check_basename(self, filename): basename = os.path.basename(filename) if basename in self.__basenames: dup_filename = self.fullpath(self.__basenames[basename]) log.info(f'Duplicate file detected: {basename}') self.__remove_file(dup_filename) try: os.remove(dup_filename) except FileNotFoundError: log.exception('Skip file removing') self.__save()
def add_file_data(self, name, filename, data, bad=False): out_folder = os.path.join(self.__folder, name) if bad: out_folder = os.path.join(out_folder, BAD_FOLDERNAME) os.makedirs(out_folder, exist_ok=True) out_filename = os.path.join(out_folder, self.__calc_out_filename(filename)) self.__check_basename(out_filename) log.info(f'adding data of {filename} to {out_filename}') with open(out_filename, 'wb') as f: f.write(data)
def recognize_image(self, filename): log.info(f'recognize image: {filename}') image = tools.LazyImage(filename, self.__max_size) encoded_faces = self.encode_faces(image.get()) if self.__match_faces(encoded_faces): return encoded_faces, image else: return [], None
def __analyze_duplicates(self, print_out): log.info(f'Analyze duplicates') fset = {} for f in self.__files: f = self.fullpath(f) filename = os.path.split(f)[1] if filename in fset: log.warning(f'Duplicate pattern file: {f} ({fset[filename]})') if print_out: print(f) print(fset[filename]) else: fset[filename] = f
def reencode_image(self, filename, encoded_faces): log.info(f'reencode image: {filename}') image = tools.LazyImage(filename, self.__max_size) boxes = [f['box'] for f in encoded_faces] encodings, landmarks, profile_angles = self.__encoder.encode( image.get(), boxes) for i in range(len(encoded_faces)): encoded_faces[i]['encoding'] = encodings[i] encoded_faces[i]['landmarks'] = landmarks[i] encoded_faces[i]['profile_angle'] = profile_angles[i]
def __create_tags(self): for name in self.__names: tag = TAG_PREFIX + name if not self.__plexdb.tag_exists(tag, plexdb.TAG_TYPE_PHOTO): self.__plexdb.create_tag(tag, plexdb.TAG_TYPE_PHOTO, commit=False) log.info(f'Photo tag "{tag}" added to plex db') if not self.__plexdb.tag_exists(tag, plexdb.TAG_TYPE_VIDEO): self.__plexdb.create_tag(tag, plexdb.TAG_TYPE_VIDEO, commit=False) log.info(f'Video tag "{tag}" added to plex db') self.__plexdb.commit()
def run(self): os.nice(10) try: from face_rec_tools import recdb # noqa from face_rec_tools import config # noqa from face_rec_tools import cachedb # noqa from face_rec_tools import patterns # noqa from face_rec_tools import recognizer # noqa cfg = config.Config(self.__config) patterns = patterns.createPatterns(cfg) patterns.load() db = recdb.RecDB(cfg.get_path('files', 'db')) cdb = cachedb.createCacheDB(cfg) cuda_memory_limit = int(cfg['processing']['cuda_memory_limit']) log.info(f'Run in process: {self.__method}{self.__args}') if self.__method == 'recognize_folder': tools.cuda_init(cuda_memory_limit) recognizer = recognizer.createRecognizer( patterns, cfg, cdb, db, self.__status) recognizer.recognize_folder(*self.__args) elif self.__method == 'match': tools.cuda_init(cuda_memory_limit) recognizer = recognizer.createRecognizer( patterns, cfg, cdb, db, self.__status) recognizer.match(*self.__args) elif self.__method == 'clusterize': recognizer = recognizer.createRecognizer( patterns, cfg, cdb, db, self.__status) recognizer.clusterize(*self.__args) elif self.__method == 'save_faces': recognizer = recognizer.createRecognizer( patterns, cfg, cdb, db, self.__status) recognizer.save_faces(*self.__args) elif self.__method == 'get_faces_by_face': tools.cuda_init(cuda_memory_limit) recognizer = recognizer.createRecognizer( patterns, cfg, cdb, db, self.__status) recognizer.get_faces_by_face(*self.__args) log.info(f'Process done: {self.__method}') self.__status['state'] = 'done' except Exception as ex: log.exception(ex) self.__status['state'] = 'error' self.__status['error'] = str(ex)
def update_filepaths(self, oldfolder, newfolder): newfiles = self.__filenames_to_dict( tools.list_files(newfolder, tools.IMAGE_EXTS)) oldfiles = self.__filenames_to_dict(self.get_files(oldfolder)) for name, oldpath in oldfiles.items(): if name not in newfiles: old = os.path.join(oldpath, name) log.info(f'removing unexists file: {old}') self.remove(old, commit=False) elif newfiles[name] != oldpath: old = os.path.join(oldpath, name) new = os.path.join(newfiles[name], name) log.info(f'move file {old} to {new}') self.move(old, new, commit=False) self.commit()
def remove_folder(self, folder): if self.__init_stage('remove_folder', locals()): return count, files_faces = self.__db.get_folder(folder) for ff in files_faces: log.info(f"remove from DB: {ff['filename']}") self.__db.remove(ff['filename'], False) if self.__cdb is not None: for face in ff['faces']: self.__cdb.remove_face(face['face_id']) # delete files without faces files = self.__db.get_files(folder) for f in files: log.info(f"remove image: {f}") self.__db.remove(f, False) self.__end_stage()
def optimize(self): encoder = self.__get_encoder() # get encodings and reverse to preserve old patterns encs, names, files = self.encodings() encs.reverse() names.reverse() files.reverse() # convert to numpy array and get length for optimization reasons encs = np.array(encs) encs_len = len(encs) to_remove = [] while 0 < encs_len: log.debug(f'to optimize check: {encs_len}') name = names.pop() fname = files.pop() # numpy array pop() enc, encs = encs[-1], encs[:-1] encs_len -= 1 dists = encoder.distance(encs, enc) i = 0 while i < encs_len: if dists[i] < self.__threshold_equal: if name != names[i]: fn1 = self.fullpath(fname) fn2 = self.fullpath(files[i]) log.warning( f'Different persons {dists[i]} "{fn1}" "{fn2}"') else: to_remove.append(self.fullpath(files[i])) log.info(f'eq: {fname} {files[i]}') names.pop(i) files.pop(i) encs = np.delete(encs, i, axis=0) dists = np.delete(dists, i, axis=0) encs_len -= 1 i += 1 self.remove_files(to_remove) log.info(f'{len(to_remove)} files was optimized.')
def __save_faces(self, files_faces, debug_out_folder): for ff in files_faces: if self.__step_stage(): break filename = ff['filename'] log.info(f"save faces from image: {filename}") media = tools.load_media(filename, self.__max_size, self.__max_video_frames, self.__video_frames_step) debug_out_file_name = self.__extract_filename(filename) is_video = tools.get_low_ext(filename) in tools.VIDEO_EXTS self.__save_debug_images( ff['faces'], media, debug_out_folder, debug_out_file_name, is_video=is_video) self.__step_stage_face(len(ff['faces'])) self.__end_stage()
def add_files(self, name, filenames, new=False, move=False, bad=False): out_folder = os.path.join(self.__folder, name) if bad: out_folder = os.path.join(out_folder, BAD_FOLDERNAME) if new: os.makedirs(out_folder, exist_ok=True) else: if not os.path.exists(out_folder): raise Exception(f"Name {name} not exists") for filename in filenames: filename = self.__calc_filename(filename) out_filename = os.path.join(out_folder, self.__calc_out_filename(filename)) log.info(f'adding {filename} to {out_filename}') shutil.copyfile(filename, out_filename) self.__check_basename(out_filename) if move: os.remove(filename)
def __analyze_encodings_size(self, print_out): log.info(f'Analyze encodings') dct = collections.defaultdict(list) for f, (enc, name, time, tp) in self.__files.items(): f = self.fullpath(f) dct[len(enc)].append(f) if len(dct) != 1: log.warning('Inconsistent encoding: ' + str(dct.keys())) max_key = list(dct.keys())[0] for key in dct: if len(dct[max_key]) < len(dct[key]): max_key = key del dct[max_key] for lst in dct.values(): for f in lst: log.warning(f'wrong encoding: {f}') if print_out: print(f)
def recognize_video(self, filename): log.info(f'recognize video: {filename}') video = tools.LazyVideo(filename, self.__max_size, self.__max_video_frames, self.__video_frames_step) all_frames = list(video.frames().items()) batched_encoded_faces = [] cnt = 0 while cnt < len(all_frames): if self.__step_stage(step=0): return [], None frame_numbers, frames = zip( *all_frames[cnt: cnt + self.__video_batch_size]) batched_boxes = face_recognition.batch_face_locations( list(frames), batch_size=len(frames)) for image, boxes, frame_num in zip(frames, batched_boxes, frame_numbers): if self.__step_stage_face(len(boxes)): return [], None encodings, landmarks, profile_angles = self.__encoder.encode( image, boxes) encoded_faces = [ {'encoding': e, 'box': b, 'frame': frame_num, 'landmarks': l, 'profile_angle': pa} for e, l, b, pa in zip(encodings, landmarks, boxes, profile_angles)] encoded_faces = self.__filter_encoded_faces(encoded_faces) self.__match_faces(encoded_faces) batched_encoded_faces += encoded_faces cnt += 1 log.info(f'done {cnt} frames: {filename}') return batched_encoded_faces, video
def set_tags(self, resync=False): log.info(f'Set tags started ({resync})') self.__create_tags() if resync: count, files_faces = self.__recdb.get_all() else: count, files_faces = self.__recdb.get_unsynced() images_count = 0 faces_count = 0 for ff in tools.reduce_faces_from_videos(files_faces, self.__min_video_face_count): filename = ff['filename'] self.__plexdb.clean_tags(filename, tag_prefix=TAG_PREFIX, commit=False) tags = [] for face in ff['faces']: name = face['name'] if name in self.__names: tags.append(TAG_PREFIX + name) log.debug(f"sync tags for image: {filename}: " + str(tags)) if len(tags) != 0: ext = tools.get_low_ext(filename) if ext in tools.IMAGE_EXTS: self.__plexdb.set_tags(filename, tags, plexdb.TAG_TYPE_PHOTO, commit=False) elif ext in tools.VIDEO_EXTS: self.__plexdb.set_tags(filename, tags, plexdb.TAG_TYPE_VIDEO, commit=False) self.__recdb.mark_as_synced(filename, commit=False) images_count += 1 faces_count += len(tags) self.__plexdb.commit() self.__recdb.commit() log.info(f'Set tags done: images={images_count} faces={faces_count}')
def __analyze_landmarks(self, print_out): log.info(f'Analyze landmarks') for f in self.__files: f = self.fullpath(f) descr = tools.load_face_description(f)[0] if descr is None: log.warning(f'missed description: {f}') if print_out: print(f) continue if 'landmarks' not in descr: log.warning(f'missed landmarks: {f}') if print_out: print(f) continue if not tools.test_landmarks(descr['landmarks']): log.warning(f'wrong landmarks: {f}') if print_out: print(f)
def cuda_init(tf_memory_limit=0): if not has_cuda(): log.debug('cuda disabled') return try: import tensorflow as tf log.debug('cuda init') gpu = tf.config.experimental.list_physical_devices('GPU')[0] if tf.config.experimental.get_memory_growth(gpu): return tf.config.experimental.set_memory_growth(gpu, True) if tf_memory_limit: log.debug(f'set cuda memory limit: {tf_memory_limit}') tf.config.experimental.set_virtual_device_configuration( gpu, [tf.config.experimental.VirtualDeviceConfiguration( memory_limit=tf_memory_limit)]) except ModuleNotFoundError as ex: log.info('tensorflow disabled, skip initialisation')
def main(): args = args_parse() cfg = config.Config(args.config) if args.logfile: logfile = args.logfile else: logfile = cfg.get_path('server', 'log_file') log.initLogger(logfile) try: server = FaceRecServer(cfg) log.info("Face rec server up.") server.serve_forever() except KeyboardInterrupt: server.stop(False) server.status() server.server_close() log.info("Face rec server down.")
def sync_deleted(self, folders): log.info(f'Sync deleted started') for folder in folders: log.info(f'Check {folder}') plex_files = set(self.__plexdb.get_files(folder)) rec_files = set(self.__recdb.get_files(folder)) filenames = sorted(rec_files - plex_files) if len(filenames) != 0: for filename in filenames: log.info(f'removing from recdb: {filename}') self.__recdb.remove(filename, commit=False) else: log.info(f'No files to remove from {folder}') self.__recdb.commit()
def __end_stage(self): if self.__status.get('save', True): log.info(f'Commit transaction') if self.__db is not None: self.__db.commit() if self.__cdb is not None: self.__cdb.commit() else: log.info(f'Rollback transaction') if self.__db is not None: self.__db.rollback() if self.__cdb is not None: self.__cdb.rollback() self.__status['stop'] = False self.__status['endtime'] = time.time() log.info(f'end stage: {self.__status}')
def __match_files_faces( self, files_faces, debug_out_folder, save_all_faces=False, skip_face_gen=False): cnt_all = 0 cnt_changed = 0 for ff in files_faces: if self.__step_stage(): break filename = ff['filename'] log.info(f"match image: {filename}") is_video = tools.get_low_ext(filename) in tools.VIDEO_EXTS if not self.__match_faces(ff['faces']): continue for face in ff['faces']: if self.__step_stage_face(): break cnt_all += 1 changed = False if 'oldname' in face and face['oldname'] != face['name']: self.__db.set_name(face['face_id'], face['name'], face['dist'], face['pattern'], commit=False) cnt_changed += 1 changed = True log.info( f"face {face['face_id']} in file '{ff['filename']}' " + f"changed '{face['oldname']}' -> '{face['name']}'") if debug_out_folder and (changed or save_all_faces): media = tools.load_media(filename, self.__max_size, self.__max_video_frames, self.__video_frames_step) debug_out_file_name = self.__extract_filename(filename) self.__save_debug_images( (face,), media, debug_out_folder, debug_out_file_name, is_video=is_video, skip_face_gen=skip_face_gen) self.__end_stage() log.info(f'match done: count: {cnt_all}, changed: {cnt_changed}')
def sync_new(self, cfg, patt, folders, exts): log.info(f'Sync new started') cachedb_file = cfg.get_path('files', 'cachedb') cache_path = cfg.get_path('server', 'face_cache_path') if cachedb_file: cdb = cachedb.CacheDB(cachedb_file) else: cdb = None rec = recognizer.createRecognizer(patt, cfg, cdb, self.__recdb) for folder in folders: plex_files = self.__plexdb.get_files(folder) plex_files = set( filter(lambda f: tools.get_low_ext(f) in exts, plex_files)) rec_files = set(self.__recdb.get_files(folder)) filenames = sorted(plex_files - rec_files) count = len(filenames) if count != 0: log.info(f'Adding {count} files from {folder}') rec.recognize_files(filenames, cache_path) else: log.info(f'No files to add from {folder}') self.set_tags()
def generate(self, regenerate=False): log.info(f'Patterns generation: {self.__folder} ({regenerate})') image_files = {} for image_file in tools.list_files(self.__folder, tools.IMAGE_EXTS): if os.path.split(image_file)[1] == FACE_FILENAME: continue image_files[image_file] = os.stat(image_file).st_mtime if not regenerate: self.load() filtered = {} for image_file in image_files: filename_exsists = self.relpath(image_file) in self.__files if not filename_exsists or \ self.__files[self.relpath(image_file)][self.FILES_TIME] != \ image_files[image_file]: filtered[image_file] = image_files[image_file] if filename_exsists: self.__remove_file(image_file) if len(filtered) == 0: log.info('Nothing changed') return image_files = filtered for (i, image_file) in enumerate(image_files): splitted = image_file.split(os.path.sep) name = splitted[-2] if name == BAD_FOLDERNAME: name = splitted[-3] tp = PATTERN_TYPE_BAD elif name == OTHER_FOLDERNAME: name = splitted[-3] tp = PATTERN_TYPE_OTHER else: tp = PATTERN_TYPE_GOOD log.info(f'{i + 1}/{len(image_files)} file: {image_file}') descr, thumbnail = tools.load_face_description(image_file) try: encoding = descr['encoding'] except Exception: encoding = None if encoding is None: import face_recognition try: image = tools.read_image(image_file, self.__max_size) except Exception: log.exception(f'read_image failed') continue boxes = face_recognition.face_locations(image, model=self.__model) if len(boxes) != 1: log.warning( f'{len(boxes)} faces detected in {image_file}. Skip.') continue encodings, landmarks = \ self.__get_encoder().encode(image, boxes) if not tools.test_landmarks(landmarks[0]): log.warning(f'bad face detected in {image_file}. Skip.') continue encoding = encodings[0] self.__files[self.relpath(image_file)] = \ [encoding, name, image_files[image_file], tp] self.__init_basenames() self.__persons = self.__calc_persons() self.__save()
def stop(self, save=False): log.info(f'Stop called ({save})') self.__status['stop'] = True self.__status['save'] = save
def stop(self, save): log.info(f'Runner stop called ({save})') self.__status['stop'] = True self.__status['save'] = save
def remove_tags(self): log.info(f'Remove tags started') self.__plexdb.delete_tags(TAG_PREFIX, cleanup=True) log.info(f'Remove tags done')
def analyze(self, print_out): self.__analyze_duplicates(print_out) self.__analyze_encodings_size(print_out) self.__analyze_landmarks(print_out) log.info(f'Analyze done')