From bf1825257f3fcf22e10f8d1ce78aaf0e5dec8070 Mon Sep 17 00:00:00 2001 From: Adam Geitgey Date: Mon, 7 Aug 2017 10:43:31 -0700 Subject: [PATCH] Add --show-distance paramter to CLI --- README.md | 18 ++++++++++++++++- face_recognition/cli.py | 36 ++++++++++++++++++++++++---------- tests/test_face_recognition.py | 13 +++++++++++- 3 files changed, 55 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index aedf9a2..c652afa 100644 --- a/README.md +++ b/README.md @@ -130,6 +130,8 @@ with the filename and the name of the person found. An `unknown_person` is a face in the image that didn't match anyone in your folder of known people. +##### Adjusting Tolerance / Sensitivity + If you are getting multiple matches for the same person, it might be that the people in your photos look very similar and a lower tolerance value is needed to make face comparisons more strict. @@ -144,6 +146,18 @@ $ face_recognition --tolerance 0.54 ./pictures_of_people_i_know/ ./unknown_pictu /face_recognition_test/unknown_pictures/unknown.jpg,unknown_person ``` +If you want to see the face distance calculated for each match in order +to adjust the tolerance setting, you can use `--show-distance true`: + +```bash +$ face_recognition --show-distance true ./pictures_of_people_i_know/ ./unknown_pictures/ + +/unknown_pictures/unknown.jpg,Barack Obama,0.378542298956785 +/face_recognition_test/unknown_pictures/unknown.jpg,unknown_person,None +``` + +##### More Examples + If you simply want to know the names of the people in each photograph but don't care about file names, you could do this: @@ -154,7 +168,9 @@ Barack Obama unknown_person ``` -Face recognition can also be done in parallel if you have a computer with +##### Speeding up Face Recognition + +Face recognition can be done in parallel if you have a computer with multiple CPU cores. For example if your system has 4 CPU cores, you can process about 4 times as many images in the same amount of time by using all your CPU cores in parallel. diff --git a/face_recognition/cli.py b/face_recognition/cli.py index ff72408..fabf4be 100644 --- a/face_recognition/cli.py +++ b/face_recognition/cli.py @@ -32,7 +32,14 @@ def scan_known_people(known_people_folder): return known_names, known_face_encodings -def test_image(image_to_check, known_names, known_face_encodings, tolerance=0.6): +def print_result(filename, name, distance, show_distance=False): + if show_distance: + print("{},{},{}".format(filename, name, distance)) + else: + print("{},{}".format(filename, name)) + + +def test_image(image_to_check, known_names, known_face_encodings, tolerance=0.6, show_distance=False): unknown_image = face_recognition.load_image_file(image_to_check) # Scale down image if it's giant so things run a little faster @@ -45,19 +52,20 @@ def test_image(image_to_check, known_names, known_face_encodings, tolerance=0.6) unknown_encodings = face_recognition.face_encodings(unknown_image) for unknown_encoding in unknown_encodings: - result = face_recognition.compare_faces(known_face_encodings, unknown_encoding, tolerance=tolerance) + distances = face_recognition.face_distance(known_face_encodings, unknown_encoding) + result = list(distances <= tolerance) if True in result: - [print("{},{}".format(image_to_check, name)) for is_match, name in zip(result, known_names) if is_match] + [print_result(image_to_check, name, distance, show_distance) for is_match, name, distance in zip(result, known_names, distances) if is_match] else: - print("{},unknown_person".format(image_to_check)) + print_result(image_to_check, "unknown_person", None, show_distance) def image_files_in_folder(folder): return [os.path.join(folder, f) for f in os.listdir(folder) if re.match(r'.*\.(jpg|jpeg|png)', f, flags=re.I)] -def process_images_in_process_pool(images_to_check, known_names, known_face_encodings, number_of_cpus, tolerance): +def process_images_in_process_pool(images_to_check, known_names, known_face_encodings, number_of_cpus, tolerance, show_distance): if number_of_cpus == -1: processes = None else: @@ -69,7 +77,14 @@ def process_images_in_process_pool(images_to_check, known_names, known_face_enco context = multiprocessing.get_context("forkserver") pool = context.Pool(processes=processes) - function_parameters = zip(images_to_check, itertools.repeat(known_names), itertools.repeat(known_face_encodings), itertools.repeat(tolerance)) + + function_parameters = zip( + images_to_check, + itertools.repeat(known_names), + itertools.repeat(known_face_encodings), + itertools.repeat(tolerance), + itertools.repeat(show_distance) + ) pool.starmap(test_image, function_parameters) @@ -79,7 +94,8 @@ def process_images_in_process_pool(images_to_check, known_names, known_face_enco @click.argument('image_to_check') @click.option('--cpus', default=1, help='number of CPU cores to use in parallel (can speed up processing lots of images). -1 means "use all in system"') @click.option('--tolerance', default=0.6, help='Tolerance for face comparisons. Default is 0.6. Lower this if you get multiple matches for the same person.') -def main(known_people_folder, image_to_check, cpus, tolerance): +@click.option('--show-distance', default=False, type=bool, help='Output face distance. Useful for tweaking tolerance setting.') +def main(known_people_folder, image_to_check, cpus, tolerance, show_distance): known_names, known_face_encodings = scan_known_people(known_people_folder) # Multi-core processing only supported on Python 3.4 or greater @@ -89,11 +105,11 @@ def main(known_people_folder, image_to_check, cpus, tolerance): if os.path.isdir(image_to_check): if cpus == 1: - [test_image(image_file, known_names, known_face_encodings, tolerance) for image_file in image_files_in_folder(image_to_check)] + [test_image(image_file, known_names, known_face_encodings, tolerance, show_distance) for image_file in image_files_in_folder(image_to_check)] else: - process_images_in_process_pool(image_files_in_folder(image_to_check), known_names, known_face_encodings, cpus, tolerance) + process_images_in_process_pool(image_files_in_folder(image_to_check), known_names, known_face_encodings, cpus, tolerance, show_distance) else: - test_image(image_to_check, known_names, known_face_encodings) + test_image(image_to_check, known_names, known_face_encodings, tolerance, show_distance) if __name__ == "__main__": diff --git a/tests/test_face_recognition.py b/tests/test_face_recognition.py index a2eef02..b131187 100644 --- a/tests/test_face_recognition.py +++ b/tests/test_face_recognition.py @@ -183,7 +183,7 @@ class Test_face_recognition(unittest.TestCase): self.assertListEqual(match_results, []) def test_command_line_interface_options(self): - target_string = '--help Show this message and exit.' + target_string = 'Show this message and exit.' runner = CliRunner() help_result = runner.invoke(cli.main, ['--help']) self.assertEqual(help_result.exit_code, 0) @@ -210,3 +210,14 @@ class Test_face_recognition(unittest.TestCase): self.assertEqual(result.exit_code, 0) self.assertTrue(target_string in result.output) + + def test_command_line_interface_show_distance(self): + target_string = 'obama.jpg,obama,0.0' + runner = CliRunner() + image_folder = os.path.join(os.path.dirname(__file__), 'test_images') + image_file = os.path.join(os.path.dirname(__file__), 'test_images', 'obama.jpg') + + result = runner.invoke(cli.main, args=[image_folder, image_file, "--show-distance", "1"]) + + self.assertEqual(result.exit_code, 0) + self.assertTrue(target_string in result.output) -- GitLab