Fix huge problem in multiple face swapping. Start working on api

pull/152/head
Tran Xen 2023-07-19 19:30:32 +02:00
parent 51f284748d
commit 5bf91b8b49
10 changed files with 311 additions and 227 deletions

View File

@ -1,39 +0,0 @@
from PIL import Image
import numpy as np
from fastapi import FastAPI, Body
from fastapi.exceptions import HTTPException
from modules.api.models import *
from modules.api import api
from scripts.roop_globals import VERSION_FLAG
import gradio as gr
def encode_to_base64(image):
if type(image) is str:
return image
elif type(image) is Image.Image:
return api.encode_pil_to_base64(image)
elif type(image) is np.ndarray:
return encode_np_to_base64(image)
else:
return ""
def encode_np_to_base64(image):
pil = Image.fromarray(image)
return api.encode_pil_to_base64(pil)
def roop_api(_: gr.Blocks, app: FastAPI):
@app.get("/roop/version")
async def version():
return {"version": VERSION_FLAG}
@app.get("/roop/swap_face")
async def swap_face():
return {"version": VERSION_FLAG}
try:
import modules.script_callbacks as script_callbacks
script_callbacks.on_app_started(roop_api)
except:
pass

View File

@ -1,5 +1,6 @@
import importlib
from scripts.roop_utils.models_utils import get_models, get_face_checkpoints
from scripts.roop_api import roop_api
from scripts.roop_utils.models_utils import get_current_model, get_face_checkpoints
from scripts import (roop_globals, roop_logging, faceswap_settings, faceswap_tab, faceswap_unit_ui)
from scripts.roop_swapping import swapper
@ -9,6 +10,7 @@ from scripts.roop_postprocessing import upscaling
from pprint import pprint
import numpy as np
import logging
from copy import deepcopy
#Reload all the modules when using "apply and restart"
#This is mainly done for development purposes
@ -20,6 +22,7 @@ importlib.reload(upscaling)
importlib.reload(faceswap_settings)
importlib.reload(models_utils)
importlib.reload(faceswap_unit_ui)
importlib.reload(roop_api)
import base64
import io
@ -41,7 +44,6 @@ from modules.shared import opts
from PIL import Image
from scripts.roop_utils.imgutils import (pil_to_cv2,convert_to_sd)
from scripts.roop_logging import logger, save_img_debug
from scripts.roop_globals import VERSION_FLAG
from scripts.roop_postprocessing.postprocessing_options import PostProcessingOptions
@ -55,6 +57,12 @@ EXTENSION_PATH=os.path.join("extensions","sd-webui-roop")
# Register the tab, done here to prevent it from being added twice
script_callbacks.on_ui_tabs(faceswap_tab.on_ui_tabs)
try:
import modules.script_callbacks as script_callbacks
script_callbacks.on_app_started(roop_api.roop_api)
except:
pass
class FaceSwapScript(scripts.Script):
@ -79,22 +87,18 @@ class FaceSwapScript(scripts.Script):
"""Return True if any unit is enabled and the state is not interupted"""
return any([u.enable for u in self.units]) and not shared.state.interrupted
@property
def model(self) -> str :
model = opts.data.get("roop_model", None)
if model is None :
models = get_models()
model = models[0] if len(models) else None
logger.info("Try to use model : %s", model)
if not os.path.isfile(model):
logger.error("The model %s cannot be found or loaded", model)
raise FileNotFoundError("No faceswap model found. Please add it to the roop directory.")
return model
@property
def keep_original_images(self) :
return opts.data.get("roop_keep_original", False)
@property
def swap_in_generated_units(self) :
return [u for u in self.units if u.swap_in_generated and u.enable]
@property
def swap_in_source_units(self) :
return [u for u in self.units if u.swap_in_source and u.enable]
def title(self):
return f"roop"
@ -155,138 +159,53 @@ class FaceSwapScript(scripts.Script):
#If is instance of img2img, we check if face swapping in source is required.
if isinstance(p, StableDiffusionProcessingImg2Img):
if self.enabled:
init_images = p.init_images
for i, unit in enumerate(self.units):
if unit.enable and unit.swap_in_source :
blend_config = unit.blend_faces # store blend config
unit.blend_faces = True # force blending
(init_images, result_infos) = self.process_images_unit(unit, init_images, upscaled_swapper=self.upscaled_swapper_in_source)
logger.info(f"unit {i+1}> processed init images: {len(init_images)}, {len(result_infos)}")
unit.blend_faces = blend_config #restore blend config
init_images : List[Tuple[Image.Image, str]] = [(img,None) for img in p.init_images]
new_inits = swapper.process_images_units(get_current_model(), self.swap_in_source_units,images=init_images, upscaled_swapper=self.upscaled_swapper_in_source,force_blend=True)
logger.info(f"processed init images: {len(init_images)}")
p.init_images = [img[0] for img in new_inits]
p.init_images = init_images
def process_image_unit(self, unit : FaceSwapUnitSettings, image: Image.Image, info = None, upscaled_swapper = False) -> List:
"""Process one image and return a List of (image, info) (one if blended, many if not).
Args:
unit : the current unit
image : the image where to apply swapping
info : The info
Returns:
List of tuple of (image, info) where image is the image where swapping has been applied and info is the image info with similarity infos.
"""
results = []
if unit.enable :
if convert_to_sd(image) :
return [(image, info)]
if not unit.blend_faces :
src_faces = unit.faces
logger.info(f"will generate {len(src_faces)} images")
else :
logger.info("blend all faces together")
src_faces = [unit.blended_faces]
assert(not np.array_equal(unit.reference_face.embedding,src_faces[0].embedding) if len(unit.faces)>1 else True), "Reference face cannot be the same as blended"
for i,src_face in enumerate(src_faces):
logger.info(f"Process face {i}")
if unit.reference_face is not None :
reference_face = unit.reference_face
else :
logger.info("Use source face as reference face")
reference_face = src_face
save_img_debug(image, "Before swap")
result: swapper.ImageResult = swapper.swap_face(
reference_face,
src_face,
image,
faces_index=unit.faces_index,
model=self.model,
same_gender=unit.same_gender,
upscaled_swapper=upscaled_swapper,
compute_similarity=unit.compute_similarity
)
save_img_debug(result.image, "After swap")
if result.image is None :
logger.error("Result image is None")
if (not unit.check_similarity) or result.similarity and all([result.similarity.values()!=0]+[x >= unit.min_sim for x in result.similarity.values()]) and all([result.ref_similarity.values()!=0]+[x >= unit.min_ref_sim for x in result.ref_similarity.values()]):
results.append((result.image, f"{info}, similarity = {result.similarity}, ref_similarity = {result.ref_similarity}"))
else:
logger.warning(
f"skip, similarity to low, sim = {result.similarity} (target {unit.min_sim}) ref sim = {result.ref_similarity} (target = {unit.min_ref_sim})"
)
return results
def process_images_unit(self, unit : FaceSwapUnitSettings, images : List[Image.Image], infos = None, upscaled_swapper = False) -> Tuple[List[Image.Image], List[str]] :
if unit.enable :
result_images : List[Image.Image] = []
result_infos : List[str]= []
if not infos :
# this allows the use of zip afterwards if no infos are present
# we make sure infos size is the same as images size
infos = [None] * len(images)
for i, (img, info) in enumerate(zip(images, infos)):
swapped_images = self.process_image_unit(unit, img, info, upscaled_swapper)
for result_image, result_info in swapped_images :
result_images.append(result_image)
result_infos.append(result_info)
logger.info(f"{len(result_images)} images processed")
return (result_images, result_infos)
return (images, infos)
def postprocess(self, p : StableDiffusionProcessing, processed: Processed, *args):
if self.enabled :
# Get the original images without the grid
orig_images = processed.images[processed.index_of_first_image:]
orig_infotexts = processed.infotexts[processed.index_of_first_image:]
orig_images : List[Image.Image] = processed.images[processed.index_of_first_image:]
orig_infotexts : List[str] = processed.infotexts[processed.index_of_first_image:]
keep_original = self.keep_original_images
# These are were images and infos of swapped images will be stored
images = []
infotexts = []
if (len(self.swap_in_generated_units))>0 :
for i,(img,info) in enumerate(zip(orig_images, orig_infotexts)):
batch_index = i%p.batch_size
swapped_images = swapper.process_images_units(get_current_model(), self.swap_in_generated_units, images=[(img,info)], upscaled_swapper=self.upscaled_swapper_in_generated)
logger.info(f"{len(swapped_images)} images swapped")
for swp_img, new_info in swapped_images :
img = swp_img # Will only swap the last image in the batch in next units (FIXME : hard to fix properly but not really critical)
for i,(img,info) in enumerate(zip(orig_images, orig_infotexts)):
if any([u.enable for u in self.units]):
for unit_i, unit in enumerate(self.units):
#convert image position to batch index
#this should work (not completely confident)
batch_index = i%p.batch_size
if unit.enable :
if unit.swap_in_generated :
img_to_swap = img
swapped_images = self.process_image_unit(image=img_to_swap, unit=unit, info=info, upscaled_swapper=self.upscaled_swapper_in_generated)
logger.info(f"{len(swapped_images)} images swapped")
for swp_img, new_info in swapped_images :
logger.info(f"unit {unit_i+1}> processed")
if swp_img is not None :
if swp_img is not None :
save_img_debug(swp_img,"Before apply mask")
swp_img = imgutils.apply_mask(swp_img, p, batch_index)
save_img_debug(swp_img,"After apply mask")
save_img_debug(swp_img,"Before apply mask")
swp_img = imgutils.apply_mask(swp_img, p, batch_index)
save_img_debug(swp_img,"After apply mask")
try :
if self.postprocess_options is not None:
swp_img = enhance_image(swp_img, self.postprocess_options)
except Exception as e:
logger.error("Failed to upscale : %s", e)
try :
if self.postprocess_options is not None:
swp_img = enhance_image(swp_img, self.postprocess_options)
except Exception as e:
logger.error("Failed to upscale : %s", e)
logger.info("Add swp image to processed")
images.append(swp_img)
infotexts.append(new_info)
if p.outpath_samples and opts.samples_save :
save_image(swp_img, p.outpath_samples, "", p.all_seeds[batch_index], p.all_prompts[batch_index], opts.samples_format,info=new_info, p=p, suffix="-swapped")
else :
logger.error("swp image is None")
elif unit.swap_in_source and not self.keep_original_images :
# if images were swapped in source, but we don't keep original
# no images will be showned unless we add it as a swap image :
images.append(img)
infotexts.append(new_info)
logger.info("Add swp image to processed")
images.append(swp_img)
infotexts.append(new_info)
if p.outpath_samples and opts.samples_save :
save_image(swp_img, p.outpath_samples, "", p.all_seeds[batch_index], p.all_prompts[batch_index], opts.samples_format,info=new_info, p=p, suffix="-swapped")
else :
logger.error("swp image is None")
else :
keep_original=True
# Generate grid :
@ -299,7 +218,7 @@ class FaceSwapScript(scripts.Script):
grid.info["parameters"] = text
images.insert(0, grid)
if self.keep_original_images:
if keep_original:
# If we want to keep original images, we add all existing (including grid this time)
images += processed.images
infotexts += processed.infotexts

View File

@ -18,7 +18,7 @@ class FaceSwapUnitSettings:
# The checkpoint file
source_face : str
# The batch source images
_batch_files: gr.components.File
_batch_files: Union[gr.components.File,List[Image.Image]]
# Will blend faces if True
blend_faces: bool
# Enable this unit
@ -118,7 +118,11 @@ class FaceSwapUnitSettings:
if self.batch_files is not None and not hasattr(self,"_faces") :
self._faces = [self.reference_face] if self.reference_face is not None else []
for file in self.batch_files :
img = Image.open(file.name)
if isinstance(file, Image.Image) :
img = file
else :
img = Image.open(file.name)
face = swapper.get_or_default(swapper.get_faces(pil_to_cv2(img)), 0, None)
if face is not None :
self._faces.append(face)

View File

@ -0,0 +1,68 @@
from PIL import Image
import numpy as np
from fastapi import FastAPI, Body
from fastapi.exceptions import HTTPException
from modules.api.models import *
from modules.api import api
from scripts.roop_api.roop_api_types import FaceSwapUnit, FaceSwapRequest
from scripts.roop_globals import VERSION_FLAG
import gradio as gr
from typing import Dict, List, Set, Tuple, Union, Optional
import json
from scripts.roop_swapping import swapper
from scripts.faceswap_unit_settings import FaceSwapUnitSettings
from scripts.roop_utils.imgutils import (pil_to_cv2,convert_to_sd, base64_to_pil)
from scripts.roop_utils.models_utils import get_current_model
def encode_to_base64(image):
if type(image) is str:
return image
elif type(image) is Image.Image:
return api.encode_pil_to_base64(image)
elif type(image) is np.ndarray:
return encode_np_to_base64(image)
else:
return ""
def encode_np_to_base64(image):
pil = Image.fromarray(image)
return api.encode_pil_to_base64(pil)
def roop_api(_: gr.Blocks, app: FastAPI):
@app.get("/roop/version")
async def version():
return {"version": VERSION_FLAG}
# use post as we consider the method non idempotent (which is debatable)
@app.post("/roop/swap_face")
async def swap_face(request : FaceSwapRequest) -> List[FaceSwapUnit]:
units : List[FaceSwapUnitSettings]= []
src_image = base64_to_pil(request.image)
for u in request.units:
units.append(
FaceSwapUnitSettings(source_img=base64_to_pil(u.source_img),
source_face = u.source_face,
_batch_files = u.get_batch_images(),
blend_faces= u.blend_faces,
enable = True,
same_gender = u.same_gender,
check_similarity=u.check_similarity,
_compute_similarity=u.compute_similarity,
min_ref_sim= u.min_ref_sim,
min_sim= u.min_sim,
_faces_index = ",".join([str(i) for i in (u.faces_index)]),
swap_in_generated=True,
swap_in_source=False
)
)
# result_images = []
# for unit_i, unit in enumerate(units):
# swapped_images = swapper.process_image_unit(get_current_model(), image=, unit=unit, info=info, upscaled_swapper=self.upscaled_swapper_in_generated)
return request.units

View File

@ -0,0 +1,72 @@
from scripts.roop_swapping import swapper
import numpy as np
import base64
import io
from dataclasses import dataclass, fields
from typing import Dict, List, Set, Tuple, Union, Optional
import dill as pickle
import gradio as gr
from insightface.app.common import Face
from PIL import Image
from scripts.roop_utils.imgutils import (pil_to_cv2,convert_to_sd, base64_to_pil)
from scripts.roop_logging import logger
from pydantic import BaseModel, Field
from scripts.roop_postprocessing.postprocessing_options import InpaintingWhen
class FaceSwapUnit(BaseModel) :
# The image given in reference
source_img: str = Field(description='base64 reference image', examples=["data:image/jpeg;base64,/9j/4AAQSkZJRgABAQECWAJYAAD...."], default=None)
# The checkpoint file
source_face : str = Field(description='face checkpoint (from models/roop/faces)',examples=["my_face.pkl"], default=None)
# base64 batch source images
batch_images: Tuple[str] = Field(description='list of base64 batch source images',examples=["data:image/jpeg;base64,/9j/4AAQSkZJRgABAQECWAJYAAD....", "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQECWAJYAAD...."], default=None)
# Will blend faces if True
blend_faces: bool = Field(description='Will blend faces if True', default=True)
# Use same gender filtering
same_gender: bool = Field(description='Use same gender filtering', default=True)
# If True, discard images with low similarity
check_similarity : bool = Field(description='If True, discard images with low similarity', default=False)
# if True will compute similarity and add it to the image info
compute_similarity : bool = Field(description='If True will compute similarity and add it to the image info', default=False)
# Minimum similarity against the used face (reference, batch or checkpoint)
min_sim: float = Field(description='Minimum similarity against the used face (reference, batch or checkpoint)', default=0.0)
# Minimum similarity against the reference (reference or checkpoint if checkpoint is given)
min_ref_sim: float = Field(description='Minimum similarity against the reference (reference or checkpoint if checkpoint is given)', default=0.0)
# The face index to use for swapping
faces_index: Tuple[int] = Field(description='The face index to use for swapping, list of face numbers starting from 0', default=(0,))
def get_batch_images(self) -> List[Image.Image] :
images = []
if self.batch_images :
for img in self.batch_images :
images.append(base64_to_pil(img))
return images
class PostProcessingOptions (BaseModel):
face_restorer_name: str = Field(description='face restorer name', default=None)
restorer_visibility: float = Field(description='face restorer visibility', default=1, le=1, ge=0)
codeformer_weight: float = Field(description='face restorer codeformer weight', default=1, le=1, ge=0)
upscaler_name: str = Field(description='upscaler name', default=None)
scale: float = Field(description='upscaling scale', default=1, le=10, ge=0)
upscale_visibility: float = Field(description='upscaler visibility', default=1, le=1, ge=0)
inpainting_denoising_strengh : float = Field(description='Inpainting denoising strenght', default=0.02, lt=1, ge=0)
inpainting_prompt : str = Field(description='Inpainting denoising strenght',examples=["Portrait of a [gender]"], default="Portrait of a [gender]")
inpainting_negative_prompt : str = Field(description='Inpainting denoising strenght',examples=["Deformed, blurry, bad anatomy, disfigured, poorly drawn face, mutation"], default="")
inpainting_steps : int = Field(description='Inpainting steps',examples=["Portrait of a [gender]"], ge=1, le=150)
inpainting_sampler : str = Field(description='Inpainting sampler',examples=["Euler"], default="Euler")
inpainting_when : InpaintingWhen = Field(description='When inpainting happens', examples=[e.value for e in InpaintingWhen.__members__.values()], default=InpaintingWhen.BEFORE_UPSCALING)
class FaceSwapRequest(BaseModel) :
image : str = Field(description='base64 reference image', examples=["data:image/jpeg;base64,/9j/4AAQSkZJRgABAQECWAJYAAD...."], default=None)
units : List[FaceSwapUnit]
postprocessing : PostProcessingOptions

View File

@ -1,46 +0,0 @@
from scripts.roop_swapping import swapper
import numpy as np
import base64
import io
from dataclasses import dataclass, fields
from typing import Dict, List, Set, Tuple, Union, Optional
import dill as pickle
import gradio as gr
from insightface.app.common import Face
from PIL import Image
from scripts.roop_utils.imgutils import (pil_to_cv2,convert_to_sd)
from scripts.roop_logging import logger
@dataclass
class FaceSwapUnitDTO :
# The image given in reference
source_img: str
# The checkpoint file
source_face : str
# base64 batch source images
batch_files: List[str]
# Will blend faces if True
blend_faces: bool
# Enable this unit
enable: bool
# Use same gender filtering
same_gender: bool
# If True, discard images with low similarity
check_similarity : bool
# if True will compute similarity and add it to the image info
compute_similarity :bool
# Minimum similarity against the used face (reference, batch or checkpoint)
min_sim: float
# Minimum similarity against the reference (reference or checkpoint if checkpoint is given)
min_ref_sim: float
# The face index to use for swapping
faces_index: int
# Swap in the source image in img2img (before processing)
swap_in_source: bool
# Swap in the generated image in img2img (always on for txt2img)
swap_in_generated: bool

View File

@ -3,7 +3,7 @@ import os
MODELS_DIR = os.path.abspath(os.path.join("models","roop"))
ANALYZER_DIR = os.path.abspath(os.path.join(MODELS_DIR, "analysers"))
VERSION_FLAG = "v0.3.0"
VERSION_FLAG = "v1.0.0"
EXTENSION_PATH=os.path.join("extensions","sd-webui-roop")
SD_CONVERT_SCORE = 0.7

View File

@ -13,11 +13,13 @@ from PIL import Image
from sklearn.metrics.pairwise import cosine_similarity
from scripts.roop_swapping import upscaled_inswapper
from scripts.roop_utils.imgutils import cv2_to_pil, pil_to_cv2
from scripts.roop_logging import logger
from scripts.roop_utils.imgutils import cv2_to_pil, pil_to_cv2, convert_to_sd
from scripts.roop_logging import logger, save_img_debug
from scripts import roop_globals
from modules.shared import opts
from functools import lru_cache
from scripts.faceswap_unit_settings import FaceSwapUnitSettings
providers = ["CPUExecutionProvider"]
@ -335,4 +337,82 @@ def swap_face(
except Exception as e :
logger.error("Conversion failed %s", e)
raise e
return return_result
return return_result
def process_image_unit(model, unit : FaceSwapUnitSettings, image: Image.Image, info = None, upscaled_swapper = False, force_blend = False) -> List:
"""Process one image and return a List of (image, info) (one if blended, many if not).
Args:
unit : the current unit
image : the image where to apply swapping
info : The info
Returns:
List of tuple of (image, info) where image is the image where swapping has been applied and info is the image info with similarity infos.
"""
results = []
if unit.enable :
if convert_to_sd(image) :
return [(image, info)]
if not unit.blend_faces and not force_blend :
src_faces = unit.faces
logger.info(f"will generate {len(src_faces)} images")
else :
logger.info("blend all faces together")
src_faces = [unit.blended_faces]
assert(not np.array_equal(unit.reference_face.embedding,src_faces[0].embedding) if len(unit.faces)>1 else True), "Reference face cannot be the same as blended"
for i,src_face in enumerate(src_faces):
logger.info(f"Process face {i}")
if unit.reference_face is not None :
reference_face = unit.reference_face
else :
logger.info("Use source face as reference face")
reference_face = src_face
save_img_debug(image, "Before swap")
result: ImageResult = swap_face(
reference_face,
src_face,
image,
faces_index=unit.faces_index,
model=model,
same_gender=unit.same_gender,
upscaled_swapper=upscaled_swapper,
compute_similarity=unit.compute_similarity
)
save_img_debug(result.image, "After swap")
if result.image is None :
logger.error("Result image is None")
if (not unit.check_similarity) or result.similarity and all([result.similarity.values()!=0]+[x >= unit.min_sim for x in result.similarity.values()]) and all([result.ref_similarity.values()!=0]+[x >= unit.min_ref_sim for x in result.ref_similarity.values()]):
results.append((result.image, f"{info}, similarity = {result.similarity}, ref_similarity = {result.ref_similarity}"))
else:
logger.warning(
f"skip, similarity to low, sim = {result.similarity} (target {unit.min_sim}) ref sim = {result.ref_similarity} (target = {unit.min_ref_sim})"
)
logger.debug("process_image_unit : Unit produced %s results", len(results))
return results
def process_images_units(model, units : List[FaceSwapUnitSettings], images: List[Tuple[Image.Image, str]], upscaled_swapper = False, force_blend = False) -> List:
if len(units) == 0 :
logger.info("Finished processing image, return %s images", len(images))
return None
logger.debug("%s more units", len(units))
processed_images = []
for i,(image, info) in enumerate(images) :
logger.debug("Processing image %s", i)
swapped = process_image_unit(model,units[0],image, info, upscaled_swapper, force_blend)
logger.debug("Image %s -> %s images", i, len(swapped))
nexts = process_images_units(model,units[1:],swapped, upscaled_swapper,force_blend)
if nexts :
processed_images.extend(nexts)
else :
processed_images.extend(swapped)
return processed_images

View File

@ -1,3 +1,5 @@
import io
from typing import Optional
from PIL import Image, ImageChops, ImageOps,ImageFilter
import cv2
import numpy as np
@ -6,7 +8,7 @@ import torch
from ifnude import detect
from scripts.roop_globals import SD_CONVERT_SCORE
from modules import processing
import base64
def convert_to_sd(img):
shapes = []
@ -168,4 +170,15 @@ def prepare_mask(
#FIXME : Properly fix blur
# if getattr(p, "mask_blur", 0) > 0:
# mask = mask.filter(ImageFilter.GaussianBlur(p.mask_blur))
return mask
return mask
def base64_to_pil(base64str : Optional[str]) :
if base64str is None :
return None
if 'base64,' in base64str: # check if the base64 string has a data URL scheme
base64_data = base64str.split('base64,')[-1]
img_bytes = base64.b64decode(base64_data)
else:
# if no data URL scheme, just decode
img_bytes = base64.b64decode(base64str)
return Image.open(io.BytesIO(img_bytes))

View File

@ -4,6 +4,8 @@ import os
import modules.scripts as scripts
from modules import scripts
from scripts.roop_globals import EXTENSION_PATH
from modules.shared import opts
from scripts.roop_logging import logger
def get_models():
"""
@ -27,6 +29,17 @@ def get_models():
return models
def get_current_model() -> str :
model = opts.data.get("roop_model", None)
if model is None :
models = get_models()
model = models[0] if len(models) else None
logger.info("Try to use model : %s", model)
if not os.path.isfile(model):
logger.error("The model %s cannot be found or loaded", model)
raise FileNotFoundError("No faceswap model found. Please add it to the roop directory.")
return model
def get_face_checkpoints():
"""
Retrieve a list of face checkpoint paths.