Files
facefusion/tests/test_face_selector.py
Henry Ruhs e7a2848a88 Feat/reference groups (#1258)
* revamp reference system

* revamp reference system

* revamp reference system

* revamp reference system

* rename to reference face maps

* rename to reference face maps

* rename to reference face maps

* fix ci

* fix logic part1

* fix logic part2

* fix logic part2

* fix logic part3

* sort out ini, program and normalize

* sort out ini, program and normalize

* fix lint

* get rid of matrix reader

* revamp groups again

* revamp groups again part2

* revamp groups again part2a

* revamp groups again part2a

* minor typo

* switch to selector map to support source averaging

* save map different in state

* adjust the normalize of maps

* refactor

* refactor

* refactor

* refactor

* better test

* resolve find_match_group_index

* minor improvements

* find closes faces

* find closes faces

* fix according to code review
2026-10-30 16:28:00 +01:00

338 lines
13 KiB
Python

from typing import List
import cv2
import numpy
import pytest
from facefusion import face_aligner, face_classifier, face_detector, face_recognizer, face_store, ffmpeg, ffmpeg_builder, inference_manager, process_manager, state_manager
from facefusion.common_helper import get_first, get_last
from facefusion.download import conditional_download
from facefusion.face_creator import get_static_faces
from facefusion.face_selector import calculate_face_distance, collect_source_faces, compare_faces, decode_face_selector_map, decode_face_selector_maps, filter_faces_by_age, filter_faces_by_gender, filter_faces_by_race, find_match_faces, select_faces, sort_and_filter_faces, sort_faces_by_order
from facefusion.types import Face
from facefusion.vision import read_static_image
from tests.assert_helper import get_test_example_file, get_test_examples_directory
@pytest.fixture(scope = 'module', autouse = True)
def before_all() -> None:
state_manager.init()
inference_manager.init()
face_store.init()
process_manager.start()
conditional_download(get_test_examples_directory(),
[
'https://github.com/facefusion/facefusion-assets/releases/download/examples-3.0.0/source.jpg',
'https://github.com/facefusion/facefusion-assets/releases/download/examples-3.0.0/target-240p.mp4'
])
ffmpeg.run_ffmpeg(
ffmpeg_builder.chain(
ffmpeg_builder.set_input(get_test_example_file('target-240p.mp4')),
[
'-vframes',
'1'
],
ffmpeg_builder.set_output(get_test_example_file('target-240p.jpg'))
)
).wait()
state_manager.init_item('execution_device_ids', [ 0 ])
state_manager.init_item('execution_providers', [ 'cpu' ])
state_manager.init_item('download_providers', [ 'github' ])
state_manager.init_item('face_detector_angles', [ 0 ])
state_manager.init_item('face_detector_model', 'many')
state_manager.init_item('face_detector_size', '640x640')
state_manager.init_item('face_detector_margin', (0, 0, 0, 0))
state_manager.init_item('face_detector_score', 0.5)
state_manager.init_item('face_aligner_model', 'many')
state_manager.init_item('face_aligner_score', 0.5)
state_manager.init_item('face_tracker_score', 0)
state_manager.init_item('reference_face_distance', 0.3)
face_classifier.pre_check()
face_detector.pre_check()
face_aligner.pre_check()
face_recognizer.pre_check()
def create_faces() -> List[Face]:
landmark_generator = numpy.random.default_rng(0)
return\
[
Face(
origin = 'detect',
bounding_box = numpy.array([ 0, 0, 10, 10 ]),
score_set =
{
'detector': 0.6,
'aligner': 0.0
},
landmark_set =
{
'5': landmark_generator.random((5, 2)),
'5/68': landmark_generator.random((5, 2)),
'68': landmark_generator.random((68, 2))
},
angle = 0,
embedding = numpy.array([ 1.0, 0.0 ]),
embedding_norm = numpy.array([ 1.0, 0.0 ]),
age = range(20, 29),
gender = 'female',
race = 'white'
),
Face(
origin = 'detect',
bounding_box = numpy.array([ 50, 10, 80, 40 ]),
score_set =
{
'detector': 0.9,
'aligner': 0.0
},
landmark_set =
{
'5': landmark_generator.random((5, 2)),
'5/68': landmark_generator.random((5, 2)),
'68': landmark_generator.random((68, 2))
},
angle = 0,
embedding = numpy.array([ 0.0, 1.0 ]),
embedding_norm = numpy.array([ 0.0, 1.0 ]),
age = range(30, 39),
gender = 'male',
race = 'black'
),
Face(
origin = 'detect',
bounding_box = numpy.array([ 20, 60, 25, 65 ]),
score_set =
{
'detector': 0.7,
'aligner': 0.0
},
landmark_set =
{
'5': landmark_generator.random((5, 2)),
'5/68': landmark_generator.random((5, 2)),
'68': landmark_generator.random((68, 2))
},
angle = 0,
embedding = numpy.array([ -1.0, 0.0 ]),
embedding_norm = numpy.array([ -1.0, 0.0 ]),
age = range(60, 69),
gender = 'female',
race = 'asian'
),
Face(
origin = 'detect',
bounding_box = numpy.array([ 30, 30, 50, 50 ]),
score_set =
{
'detector': 0.5,
'aligner': 0.0
},
landmark_set =
{
'5': landmark_generator.random((5, 2)),
'5/68': landmark_generator.random((5, 2)),
'68': landmark_generator.random((68, 2))
},
angle = 0,
embedding = numpy.array([ 0.6, 0.8 ]),
embedding_norm = numpy.array([ 0.6, 0.8 ]),
age = range(10, 19),
gender = 'male',
race = 'white'
)
]
def test_select_faces() -> None:
source_vision_frame = read_static_image(get_test_example_file('source.jpg'))
target_vision_frame = read_static_image(get_test_example_file('target-240p.jpg'))
reference_vision_frame = numpy.hstack([ cv2.resize(source_vision_frame, (226, 226)), target_vision_frame ])
state_manager.set_item('face_selector_mode', 'many')
state_manager.set_item('face_selector_maps', [ 'invalid' ])
source_faces, target_faces = select_faces([], [ source_vision_frame ], [ source_vision_frame ])
assert len(source_faces) == 1
assert len(target_faces) == 1
state_manager.set_item('face_selector_mode', 'reference')
state_manager.set_item('face_selector_maps', [ '0:0 to 0:0', '1:1 to 1:1' ])
source_faces, target_faces = select_faces([ source_vision_frame, reference_vision_frame ], [ source_vision_frame, reference_vision_frame ], [ reference_vision_frame ])
assert compare_faces(get_first(source_faces), get_first(get_static_faces([ source_vision_frame ])), 0.3) is True
assert compare_faces(get_last(source_faces), get_first(get_static_faces([ target_vision_frame ])), 0.3) is True
assert compare_faces(get_first(target_faces), get_first(get_static_faces([ source_vision_frame ])), 0.3) is True
assert compare_faces(get_last(target_faces), get_first(get_static_faces([ target_vision_frame ])), 0.3) is True
state_manager.set_item('face_selector_maps', [ '1:1 to 0:0', '0:0 to 1:1' ])
source_faces, target_faces = select_faces([ source_vision_frame, reference_vision_frame ], [ source_vision_frame, reference_vision_frame ], [ reference_vision_frame ])
assert compare_faces(get_first(source_faces), get_first(get_static_faces([ target_vision_frame ])), 0.3) is True
assert compare_faces(get_last(source_faces), get_first(get_static_faces([ source_vision_frame ])), 0.3) is True
assert compare_faces(get_first(target_faces), get_first(get_static_faces([ source_vision_frame ])), 0.3) is True
assert compare_faces(get_last(target_faces), get_first(get_static_faces([ target_vision_frame ])), 0.3) is True
state_manager.set_item('face_selector_maps', [ '0:0 to 0:0', '1:1 to 1:1' ])
state_manager.set_item('reference_face_distance', 1.0)
source_faces, target_faces = select_faces([ source_vision_frame, reference_vision_frame ], [ source_vision_frame, reference_vision_frame ], [ reference_vision_frame ])
assert len(source_faces) == 2
assert len(target_faces) == 2
assert compare_faces(get_first(source_faces), get_first(get_static_faces([ source_vision_frame ])), 0.3) is True
assert compare_faces(get_last(source_faces), get_first(get_static_faces([ target_vision_frame ])), 0.3) is True
assert compare_faces(get_first(target_faces), get_first(get_static_faces([ source_vision_frame ])), 0.3) is True
assert compare_faces(get_last(target_faces), get_first(get_static_faces([ target_vision_frame ])), 0.3) is True
state_manager.set_item('face_selector_maps', [ '0:2 to 0:1' ])
state_manager.set_item('reference_face_distance', 0.3)
source_faces, target_faces = select_faces([ reference_vision_frame ], [ reference_vision_frame ], [ reference_vision_frame ])
assert source_faces == [ None ]
assert compare_faces(get_first(target_faces), get_first(get_static_faces([ target_vision_frame ])), 0.3) is True
state_manager.set_item('face_selector_maps', [ '0:1 to 0:2' ])
source_faces, target_faces = select_faces([ reference_vision_frame ], [ reference_vision_frame ], [ reference_vision_frame ])
assert source_faces == []
assert target_faces == []
state_manager.clear_item('face_selector_mode')
state_manager.clear_item('face_selector_maps')
def test_decode_face_selector_maps() -> None:
assert decode_face_selector_maps([ '0:0 1:0 to 100:1', '2:0' ]) == [ { 'source_faces': [ { 'frame_index': 0, 'face_position': 0 }, { 'frame_index': 1, 'face_position': 0 } ], 'reference_faces': [ { 'frame_index': 100, 'face_position': 1 } ] }, { 'source_faces': [ { 'frame_index': 2, 'face_position': 0 } ], 'reference_faces': [] } ]
assert decode_face_selector_maps([ 'to 100:1', 'invalid' ]) == [ { 'source_faces': [], 'reference_faces': [ { 'frame_index': 100, 'face_position': 1 } ] } ]
assert decode_face_selector_maps([]) == []
assert decode_face_selector_maps(None) == []
def test_decode_face_selector_map() -> None:
assert decode_face_selector_map('0:0 to 100:1 200:0') == { 'source_faces': [ { 'frame_index': 0, 'face_position': 0 } ], 'reference_faces': [ { 'frame_index': 100, 'face_position': 1 }, { 'frame_index': 200, 'face_position': 0 } ] }
assert decode_face_selector_map('0:0 1 1,2 -1:0 0:0:0 to') == { 'source_faces': [ { 'frame_index': 0, 'face_position': 0 } ], 'reference_faces': [] }
def test_collect_source_faces() -> None:
assert collect_source_faces(decode_face_selector_maps([ '0:0 to 100:1', '1:0 2:1 to 300:0' ])) == [ { 'frame_index': 0, 'face_position': 0 }, { 'frame_index': 1, 'face_position': 0 }, { 'frame_index': 2, 'face_position': 1 } ]
assert collect_source_faces(decode_face_selector_maps([ 'to 0:0' ])) == []
def test_find_match_faces() -> None:
faces = face_a, face_b, face_c, face_d = create_faces()
assert find_match_faces([ face_a ], faces, 0.3) == [ face_a, face_d ]
assert find_match_faces([ face_a ], faces, 0.6) == [ face_a, face_b, face_d ]
assert find_match_faces([ face_a, face_b ], [ face_a, face_b, face_c ], 0.1) == [ face_a, face_b ]
assert find_match_faces([ face_a ], [], 0.3) == []
def test_compare_faces() -> None:
face_a, face_b, face_c, face_d = create_faces()
assert compare_faces(face_a, face_a, 0.1) is True
assert compare_faces(face_a, face_d, 0.3) is True
assert compare_faces(face_a, face_d, 0.2) is False
assert compare_faces(face_a, face_b, 0.5) is False
assert compare_faces(face_a, face_b, 0.6) is True
assert compare_faces(face_a, face_c, 1.0) is False
def test_calculate_face_distance() -> None:
face_a, face_b, face_c, face_d = create_faces()
assert calculate_face_distance(face_a, face_a) == 0.0
assert calculate_face_distance(face_a, face_b) == 1.0
assert calculate_face_distance(face_a, face_c) == 2.0
assert calculate_face_distance(face_d, face_a) == 0.4
def test_sort_and_filter_faces() -> None:
faces = face_a, face_b, face_c, face_d = create_faces()
assert sort_and_filter_faces([ face_a ], []) == []
assert sort_and_filter_faces([], faces) == faces
state_manager.set_item('face_selector_order', 'left-right')
assert sort_and_filter_faces([], faces) == [ face_a, face_c, face_d, face_b ]
state_manager.set_item('face_selector_gender', 'female')
assert sort_and_filter_faces([], faces) == [ face_a, face_c ]
state_manager.set_item('face_selector_gender', 'auto')
assert sort_and_filter_faces([ face_c, face_b ], faces) == [ face_d, face_b ]
assert sort_and_filter_faces([], faces) == [ face_a, face_c, face_d, face_b ]
state_manager.clear_item('face_selector_gender')
state_manager.set_item('face_selector_race', 'white')
assert sort_and_filter_faces([], faces) == [ face_a, face_d ]
state_manager.set_item('face_selector_race', 'auto')
assert sort_and_filter_faces([ face_c, face_b ], faces) == [ face_b ]
assert sort_and_filter_faces([], faces) == [ face_a, face_c, face_d, face_b ]
state_manager.clear_item('face_selector_race')
state_manager.set_item('face_selector_order', 'large-small')
state_manager.set_item('face_selector_age_start', 25)
state_manager.set_item('face_selector_age_end', 35)
assert sort_and_filter_faces([], faces) == [ face_b, face_a ]
def test_sort_faces_by_order() -> None:
faces = face_a, face_b, face_c, face_d = create_faces()
assert sort_faces_by_order(faces, 'left-right') == [ face_a, face_c, face_d, face_b ]
assert sort_faces_by_order(faces, 'right-left') == [ face_b, face_d, face_c, face_a ]
assert sort_faces_by_order(faces, 'top-bottom') == [ face_a, face_b, face_d, face_c ]
assert sort_faces_by_order(faces, 'bottom-top') == [ face_c, face_d, face_b, face_a ]
assert sort_faces_by_order(faces, 'small-large') == [ face_c, face_a, face_d, face_b ]
assert sort_faces_by_order(faces, 'large-small') == [ face_b, face_d, face_a, face_c ]
assert sort_faces_by_order(faces, 'best-worst') == [ face_b, face_c, face_a, face_d ]
assert sort_faces_by_order(faces, 'worst-best') == [ face_d, face_a, face_c, face_b ]
assert sort_faces_by_order(faces, 'invalid') == faces #type:ignore[arg-type]
def test_filter_faces_by_gender() -> None:
faces = face_a, face_b, face_c, face_d = create_faces()
assert filter_faces_by_gender(faces, 'female') == [ face_a, face_c ]
assert filter_faces_by_gender(faces, 'male') == [ face_b, face_d ]
assert filter_faces_by_gender([ face_a, face_c ], 'male') == []
def test_filter_faces_by_age() -> None:
faces = face_a, face_b, face_c, face_d = create_faces()
assert filter_faces_by_age(faces, 25, 35) == [ face_a, face_b ]
assert filter_faces_by_age(faces, 0, 100) == faces
assert filter_faces_by_age(faces, 0, 10) == []
assert filter_faces_by_age(faces, 0, 11) == [ face_d ]
assert filter_faces_by_age(faces, 68, 100) == [ face_c ]
assert filter_faces_by_age(faces, 69, 100) == []
def test_filter_faces_by_race() -> None:
faces = face_a, face_b, face_c, face_d = create_faces()
assert filter_faces_by_race(faces, 'white') == [ face_a, face_d ]
assert filter_faces_by_race(faces, 'asian') == [ face_c ]
assert filter_faces_by_race(faces, 'indian') == []