From 3d7e2707f26dc11461c2cfd2d670f2e4b7d0a646 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=A5=E6=B5=AA?= <757078144@qq.com> Date: Tue, 28 Nov 2023 17:29:27 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E9=A3=8E=E6=A0=BC=E9=80=89?= =?UTF-8?q?=E6=8B=A9=E5=B7=A5=E5=85=B7=EF=BC=8C=E5=88=A0=E9=99=A4=E5=A4=9A?= =?UTF-8?q?=E4=BD=99=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __init__.py | 14 +- js/EasyPromptSelecto.js | 17 - js/StyleSelecto.js | 239 ++ py/AspectRatio.py | 42 + py/StyleSelecto.py | 112 + scripts/swapper.py | 43 - src/audio2exp_models/audio2exp.py | 41 - src/audio2exp_models/networks.py | 74 - src/audio2pose_models/audio2pose.py | 94 - src/audio2pose_models/audio2pose_two.py | 94 - src/audio2pose_models/audio_encoder.py | 64 - src/audio2pose_models/audio_encoder_two.py | 64 - src/audio2pose_models/cvae.py | 149 - src/audio2pose_models/discriminator.py | 76 - src/audio2pose_models/networks.py | 140 - src/audio2pose_models/res_unet.py | 65 - src/config/auido2exp.yaml | 58 - src/config/auido2pose.yaml | 49 - src/config/facerender.yaml | 45 - src/config/facerender_still.yaml | 45 - src/config/similarity_Lm3D_all.mat | Bin 994 -> 0 bytes src/dain_model/__init__.py | 0 src/dain_model/base_predictor.py | 66 - src/dain_model/dain_predictor.py | 312 -- src/face3d/data/__init__.py | 116 - src/face3d/data/base_dataset.py | 125 - src/face3d/data/flist_dataset.py | 125 - src/face3d/data/image_folder.py | 66 - src/face3d/data/template_dataset.py | 75 - src/face3d/extract_kp_videos.py | 108 - src/face3d/extract_kp_videos_safe.py | 126 - src/face3d/models/__init__.py | 67 - src/face3d/models/arcface_torch/README.md | 164 - .../arcface_torch/backbones/__init__.py | 25 - .../models/arcface_torch/backbones/iresnet.py | 187 -- .../arcface_torch/backbones/iresnet2060.py | 176 - .../arcface_torch/backbones/mobilefacenet.py | 130 - .../models/arcface_torch/configs/3millions.py | 23 - .../arcface_torch/configs/3millions_pfc.py | 23 - .../models/arcface_torch/configs/__init__.py | 0 .../models/arcface_torch/configs/base.py | 56 - .../arcface_torch/configs/glint360k_mbf.py | 26 - .../arcface_torch/configs/glint360k_r100.py | 26 - .../arcface_torch/configs/glint360k_r18.py | 26 - .../arcface_torch/configs/glint360k_r34.py | 26 - .../arcface_torch/configs/glint360k_r50.py | 26 - .../arcface_torch/configs/ms1mv3_mbf.py | 26 - .../arcface_torch/configs/ms1mv3_r18.py | 26 - .../arcface_torch/configs/ms1mv3_r2060.py | 26 - .../arcface_torch/configs/ms1mv3_r34.py | 26 - .../arcface_torch/configs/ms1mv3_r50.py | 26 - .../models/arcface_torch/configs/speed.py | 23 - src/face3d/models/arcface_torch/dataset.py | 124 - src/face3d/models/arcface_torch/docs/eval.md | 31 - .../models/arcface_torch/docs/install.md | 51 - .../models/arcface_torch/docs/modelzoo.md | 0 .../arcface_torch/docs/speed_benchmark.md | 93 - .../models/arcface_torch/eval/__init__.py | 0 .../models/arcface_torch/eval/verification.py | 407 --- src/face3d/models/arcface_torch/eval_ijbc.py | 483 --- src/face3d/models/arcface_torch/inference.py | 35 - src/face3d/models/arcface_torch/losses.py | 42 - .../models/arcface_torch/onnx_helper.py | 250 -- src/face3d/models/arcface_torch/onnx_ijbc.py | 267 -- src/face3d/models/arcface_torch/partial_fc.py | 222 -- .../models/arcface_torch/requirement.txt | 5 - src/face3d/models/arcface_torch/run.sh | 2 - src/face3d/models/arcface_torch/torch2onnx.py | 59 - src/face3d/models/arcface_torch/train.py | 141 - .../models/arcface_torch/utils/__init__.py | 0 src/face3d/models/arcface_torch/utils/plot.py | 72 - .../models/arcface_torch/utils/utils_amp.py | 88 - .../arcface_torch/utils/utils_callbacks.py | 117 - .../arcface_torch/utils/utils_config.py | 16 - .../arcface_torch/utils/utils_logging.py | 41 - .../models/arcface_torch/utils/utils_os.py | 0 src/face3d/models/base_model.py | 316 -- src/face3d/models/bfm.py | 331 -- src/face3d/models/facerecon_model.py | 220 -- src/face3d/models/losses.py | 113 - src/face3d/models/networks.py | 521 --- src/face3d/models/template_model.py | 100 - src/face3d/options/__init__.py | 1 - src/face3d/options/base_options.py | 169 - src/face3d/options/inference_options.py | 23 - src/face3d/options/test_options.py | 21 - src/face3d/options/train_options.py | 53 - src/face3d/util/BBRegressorParam_r.mat | Bin 22019 -> 0 bytes src/face3d/util/__init__.py | 3 - src/face3d/util/detect_lm68.py | 106 - src/face3d/util/generate_list.py | 34 - src/face3d/util/html.py | 86 - src/face3d/util/load_mats.py | 120 - src/face3d/util/nvdiffrast.py | 126 - src/face3d/util/preprocess.py | 103 - src/face3d/util/skin_mask.py | 125 - src/face3d/util/test_mean_face.txt | 136 - src/face3d/util/util.py | 208 -- src/face3d/util/visualizer.py | 227 -- src/face3d/visualize.py | 48 - src/facerender/animate.py | 257 -- src/facerender/animate_two.py | 172 - src/facerender/modules/dense_motion.py | 121 - src/facerender/modules/discriminator.py | 90 - src/facerender/modules/generator.py | 255 -- src/facerender/modules/keypoint_detector.py | 179 -- src/facerender/modules/make_animation.py | 170 - src/facerender/modules/make_animation_two.py | 127 - src/facerender/modules/mapping.py | 47 - src/facerender/modules/util.py | 564 ---- src/facerender/sync_batchnorm/__init__.py | 12 - src/facerender/sync_batchnorm/batchnorm.py | 315 -- src/facerender/sync_batchnorm/comm.py | 137 - src/facerender/sync_batchnorm/replicate.py | 94 - src/facerender/sync_batchnorm/unittest.py | 29 - src/generate_batch.py | 120 - src/generate_batch_two.py | 101 - src/generate_facerender_batch.py | 136 - src/generate_facerender_batch_two.py | 124 - src/gradio_demo.py | 242 -- src/inference_utils.py | 46 - src/test_audio2coeff.py | 123 - src/test_audio2coeff_two.py | 104 - src/utils/audio.py | 136 - src/utils/croper.py | 144 - src/utils/croper_two.py | 296 -- src/utils/face_enhancer.py | 124 - src/utils/hparams.py | 160 - src/utils/init_path.py | 47 - src/utils/model2safetensor.py | 141 - src/utils/paste_pic.py | 69 - src/utils/paste_pic_two.py | 70 - src/utils/preprocess.py | 170 - src/utils/preprocess_two.py | 149 - src/utils/safetensor_helper.py | 8 - src/utils/text2speech.py | 20 - src/utils/videoio.py | 33 - src/utils/videoio_two.py | 42 - styles/sdxl_styles_diva.json | 397 +++ styles/sdxl_styles_fooocus.json | 30 + styles/sdxl_styles_mre.json | 112 + styles/sdxl_styles_sai.json | 87 + styles/sdxl_styles_twri.json | 447 +++ styles/zh_CN.json | 443 +++ third_part/GFPGAN/LICENSE | 351 -- third_part/GFPGAN/gfpgan/__init__.py | 10 - third_part/GFPGAN/gfpgan/archs/__init__.py | 51 - .../GFPGAN/gfpgan/archs/arcface_arch.py | 245 -- .../gfpgan/archs/gfpgan_bilinear_arch.py | 312 -- .../GFPGAN/gfpgan/archs/gfpganv1_arch.py | 439 --- .../gfpgan/archs/gfpganv1_clean_arch.py | 324 -- .../gfpgan/archs/stylegan2_bilinear_arch.py | 613 ---- .../gfpgan/archs/stylegan2_clean_arch.py | 368 --- third_part/GFPGAN/gfpgan/data/__init__.py | 10 - .../gfpgan/data/ffhq_degradation_dataset.py | 230 -- third_part/GFPGAN/gfpgan/models/__init__.py | 10 - .../GFPGAN/gfpgan/models/gfpgan_model.py | 580 ---- third_part/GFPGAN/gfpgan/train.py | 11 - third_part/GFPGAN/gfpgan/utils.py | 144 - third_part/GFPGAN/gfpgan/version.py | 5 - third_part/GFPGAN/gfpgan/weights/README.md | 3 - third_part/GFPGAN/options/train_gfpgan_v1.yml | 216 -- .../GFPGAN/options/train_gfpgan_v1_simple.yml | 182 -- third_part/GPEN/align_faces.py | 271 -- third_part/GPEN/face_detect/.DS_Store | Bin 12292 -> 0 bytes .../GPEN/face_detect/data/FDDB/img_list.txt | 2845 ----------------- third_part/GPEN/face_detect/data/__init__.py | 3 - third_part/GPEN/face_detect/data/config.py | 42 - .../GPEN/face_detect/data/data_augment.py | 237 -- .../GPEN/face_detect/data/wider_face.py | 101 - .../GPEN/face_detect/facemodels/__init__.py | 0 third_part/GPEN/face_detect/facemodels/net.py | 137 - .../GPEN/face_detect/facemodels/retinaface.py | 127 - .../GPEN/face_detect/layers/__init__.py | 2 - .../face_detect/layers/functions/prior_box.py | 34 - .../face_detect/layers/modules/__init__.py | 3 - .../layers/modules/multibox_loss.py | 125 - .../GPEN/face_detect/retinaface_detection.py | 193 -- third_part/GPEN/face_detect/utils/__init__.py | 0 .../GPEN/face_detect/utils/box_utils.py | 330 -- .../GPEN/face_detect/utils/nms/__init__.py | 0 .../GPEN/face_detect/utils/nms/py_cpu_nms.py | 38 - third_part/GPEN/face_detect/utils/timer.py | 40 - third_part/GPEN/face_model/face_gan.py | 55 - third_part/GPEN/face_model/gpen_model.py | 746 ----- third_part/GPEN/face_model/op/__init__.py | 2 - third_part/GPEN/face_model/op/fused_act.py | 96 - .../GPEN/face_model/op/fused_bias_act.cpp | 21 - .../face_model/op/fused_bias_act_kernel.cu | 99 - third_part/GPEN/face_model/op/upfirdn2d.cpp | 23 - third_part/GPEN/face_model/op/upfirdn2d.py | 194 -- .../GPEN/face_model/op/upfirdn2d_kernel.cu | 272 -- third_part/GPEN/face_morpher/README.rst | 260 -- .../GPEN/face_morpher/facemorpher/__init__.py | 9 - .../GPEN/face_morpher/facemorpher/aligner.py | 102 - .../GPEN/face_morpher/facemorpher/averager.py | 123 - .../GPEN/face_morpher/facemorpher/blender.py | 133 - .../GPEN/face_morpher/facemorpher/locator.py | 112 - .../GPEN/face_morpher/facemorpher/morpher.py | 153 - .../GPEN/face_morpher/facemorpher/plotter.py | 90 - .../GPEN/face_morpher/facemorpher/videoer.py | 36 - .../GPEN/face_morpher/facemorpher/warper.py | 139 - third_part/GPEN/face_morpher/requirements.txt | 5 - .../GPEN/face_morpher/scripts/make_docs.sh | 18 - .../face_morpher/scripts/publish_ghpages.sh | 29 - third_part/GPEN/face_morpher/setup.cfg | 7 - third_part/GPEN/face_morpher/setup.py | 30 - third_part/GPEN/face_parse/blocks.py | 127 - third_part/GPEN/face_parse/face_parsing.py | 148 - third_part/GPEN/face_parse/mask.png | Bin 9303 -> 0 bytes third_part/GPEN/face_parse/model.py | 283 -- third_part/GPEN/face_parse/parse_model.py | 77 - third_part/GPEN/face_parse/resnet.py | 109 - third_part/GPEN/face_parse/test.png | Bin 178505 -> 0 bytes third_part/GPEN/gpen_face_enhancer.py | 137 - 215 files changed, 1910 insertions(+), 26561 deletions(-) create mode 100644 js/StyleSelecto.js create mode 100644 py/AspectRatio.py create mode 100644 py/StyleSelecto.py delete mode 100644 scripts/swapper.py delete mode 100644 src/audio2exp_models/audio2exp.py delete mode 100644 src/audio2exp_models/networks.py delete mode 100644 src/audio2pose_models/audio2pose.py delete mode 100644 src/audio2pose_models/audio2pose_two.py delete mode 100644 src/audio2pose_models/audio_encoder.py delete mode 100644 src/audio2pose_models/audio_encoder_two.py delete mode 100644 src/audio2pose_models/cvae.py delete mode 100644 src/audio2pose_models/discriminator.py delete mode 100644 src/audio2pose_models/networks.py delete mode 100644 src/audio2pose_models/res_unet.py delete mode 100644 src/config/auido2exp.yaml delete mode 100644 src/config/auido2pose.yaml delete mode 100644 src/config/facerender.yaml delete mode 100644 src/config/facerender_still.yaml delete mode 100644 src/config/similarity_Lm3D_all.mat delete mode 100644 src/dain_model/__init__.py delete mode 100644 src/dain_model/base_predictor.py delete mode 100644 src/dain_model/dain_predictor.py delete mode 100644 src/face3d/data/__init__.py delete mode 100644 src/face3d/data/base_dataset.py delete mode 100644 src/face3d/data/flist_dataset.py delete mode 100644 src/face3d/data/image_folder.py delete mode 100644 src/face3d/data/template_dataset.py delete mode 100644 src/face3d/extract_kp_videos.py delete mode 100644 src/face3d/extract_kp_videos_safe.py delete mode 100644 src/face3d/models/__init__.py delete mode 100644 src/face3d/models/arcface_torch/README.md delete mode 100644 src/face3d/models/arcface_torch/backbones/__init__.py delete mode 100644 src/face3d/models/arcface_torch/backbones/iresnet.py delete mode 100644 src/face3d/models/arcface_torch/backbones/iresnet2060.py delete mode 100644 src/face3d/models/arcface_torch/backbones/mobilefacenet.py delete mode 100644 src/face3d/models/arcface_torch/configs/3millions.py delete mode 100644 src/face3d/models/arcface_torch/configs/3millions_pfc.py delete mode 100644 src/face3d/models/arcface_torch/configs/__init__.py delete mode 100644 src/face3d/models/arcface_torch/configs/base.py delete mode 100644 src/face3d/models/arcface_torch/configs/glint360k_mbf.py delete mode 100644 src/face3d/models/arcface_torch/configs/glint360k_r100.py delete mode 100644 src/face3d/models/arcface_torch/configs/glint360k_r18.py delete mode 100644 src/face3d/models/arcface_torch/configs/glint360k_r34.py delete mode 100644 src/face3d/models/arcface_torch/configs/glint360k_r50.py delete mode 100644 src/face3d/models/arcface_torch/configs/ms1mv3_mbf.py delete mode 100644 src/face3d/models/arcface_torch/configs/ms1mv3_r18.py delete mode 100644 src/face3d/models/arcface_torch/configs/ms1mv3_r2060.py delete mode 100644 src/face3d/models/arcface_torch/configs/ms1mv3_r34.py delete mode 100644 src/face3d/models/arcface_torch/configs/ms1mv3_r50.py delete mode 100644 src/face3d/models/arcface_torch/configs/speed.py delete mode 100644 src/face3d/models/arcface_torch/dataset.py delete mode 100644 src/face3d/models/arcface_torch/docs/eval.md delete mode 100644 src/face3d/models/arcface_torch/docs/install.md delete mode 100644 src/face3d/models/arcface_torch/docs/modelzoo.md delete mode 100644 src/face3d/models/arcface_torch/docs/speed_benchmark.md delete mode 100644 src/face3d/models/arcface_torch/eval/__init__.py delete mode 100644 src/face3d/models/arcface_torch/eval/verification.py delete mode 100644 src/face3d/models/arcface_torch/eval_ijbc.py delete mode 100644 src/face3d/models/arcface_torch/inference.py delete mode 100644 src/face3d/models/arcface_torch/losses.py delete mode 100644 src/face3d/models/arcface_torch/onnx_helper.py delete mode 100644 src/face3d/models/arcface_torch/onnx_ijbc.py delete mode 100644 src/face3d/models/arcface_torch/partial_fc.py delete mode 100644 src/face3d/models/arcface_torch/requirement.txt delete mode 100644 src/face3d/models/arcface_torch/run.sh delete mode 100644 src/face3d/models/arcface_torch/torch2onnx.py delete mode 100644 src/face3d/models/arcface_torch/train.py delete mode 100644 src/face3d/models/arcface_torch/utils/__init__.py delete mode 100644 src/face3d/models/arcface_torch/utils/plot.py delete mode 100644 src/face3d/models/arcface_torch/utils/utils_amp.py delete mode 100644 src/face3d/models/arcface_torch/utils/utils_callbacks.py delete mode 100644 src/face3d/models/arcface_torch/utils/utils_config.py delete mode 100644 src/face3d/models/arcface_torch/utils/utils_logging.py delete mode 100644 src/face3d/models/arcface_torch/utils/utils_os.py delete mode 100644 src/face3d/models/base_model.py delete mode 100644 src/face3d/models/bfm.py delete mode 100644 src/face3d/models/facerecon_model.py delete mode 100644 src/face3d/models/losses.py delete mode 100644 src/face3d/models/networks.py delete mode 100644 src/face3d/models/template_model.py delete mode 100644 src/face3d/options/__init__.py delete mode 100644 src/face3d/options/base_options.py delete mode 100644 src/face3d/options/inference_options.py delete mode 100644 src/face3d/options/test_options.py delete mode 100644 src/face3d/options/train_options.py delete mode 100644 src/face3d/util/BBRegressorParam_r.mat delete mode 100644 src/face3d/util/__init__.py delete mode 100644 src/face3d/util/detect_lm68.py delete mode 100644 src/face3d/util/generate_list.py delete mode 100644 src/face3d/util/html.py delete mode 100644 src/face3d/util/load_mats.py delete mode 100644 src/face3d/util/nvdiffrast.py delete mode 100644 src/face3d/util/preprocess.py delete mode 100644 src/face3d/util/skin_mask.py delete mode 100644 src/face3d/util/test_mean_face.txt delete mode 100644 src/face3d/util/util.py delete mode 100644 src/face3d/util/visualizer.py delete mode 100644 src/face3d/visualize.py delete mode 100644 src/facerender/animate.py delete mode 100644 src/facerender/animate_two.py delete mode 100644 src/facerender/modules/dense_motion.py delete mode 100644 src/facerender/modules/discriminator.py delete mode 100644 src/facerender/modules/generator.py delete mode 100644 src/facerender/modules/keypoint_detector.py delete mode 100644 src/facerender/modules/make_animation.py delete mode 100644 src/facerender/modules/make_animation_two.py delete mode 100644 src/facerender/modules/mapping.py delete mode 100644 src/facerender/modules/util.py delete mode 100644 src/facerender/sync_batchnorm/__init__.py delete mode 100644 src/facerender/sync_batchnorm/batchnorm.py delete mode 100644 src/facerender/sync_batchnorm/comm.py delete mode 100644 src/facerender/sync_batchnorm/replicate.py delete mode 100644 src/facerender/sync_batchnorm/unittest.py delete mode 100644 src/generate_batch.py delete mode 100644 src/generate_batch_two.py delete mode 100644 src/generate_facerender_batch.py delete mode 100644 src/generate_facerender_batch_two.py delete mode 100644 src/gradio_demo.py delete mode 100644 src/inference_utils.py delete mode 100644 src/test_audio2coeff.py delete mode 100644 src/test_audio2coeff_two.py delete mode 100644 src/utils/audio.py delete mode 100644 src/utils/croper.py delete mode 100644 src/utils/croper_two.py delete mode 100644 src/utils/face_enhancer.py delete mode 100644 src/utils/hparams.py delete mode 100644 src/utils/init_path.py delete mode 100644 src/utils/model2safetensor.py delete mode 100644 src/utils/paste_pic.py delete mode 100644 src/utils/paste_pic_two.py delete mode 100644 src/utils/preprocess.py delete mode 100644 src/utils/preprocess_two.py delete mode 100644 src/utils/safetensor_helper.py delete mode 100644 src/utils/text2speech.py delete mode 100644 src/utils/videoio.py delete mode 100644 src/utils/videoio_two.py create mode 100644 styles/sdxl_styles_diva.json create mode 100644 styles/sdxl_styles_fooocus.json create mode 100644 styles/sdxl_styles_mre.json create mode 100644 styles/sdxl_styles_sai.json create mode 100644 styles/sdxl_styles_twri.json create mode 100644 styles/zh_CN.json delete mode 100644 third_part/GFPGAN/LICENSE delete mode 100644 third_part/GFPGAN/gfpgan/__init__.py delete mode 100644 third_part/GFPGAN/gfpgan/archs/__init__.py delete mode 100644 third_part/GFPGAN/gfpgan/archs/arcface_arch.py delete mode 100644 third_part/GFPGAN/gfpgan/archs/gfpgan_bilinear_arch.py delete mode 100644 third_part/GFPGAN/gfpgan/archs/gfpganv1_arch.py delete mode 100644 third_part/GFPGAN/gfpgan/archs/gfpganv1_clean_arch.py delete mode 100644 third_part/GFPGAN/gfpgan/archs/stylegan2_bilinear_arch.py delete mode 100644 third_part/GFPGAN/gfpgan/archs/stylegan2_clean_arch.py delete mode 100644 third_part/GFPGAN/gfpgan/data/__init__.py delete mode 100644 third_part/GFPGAN/gfpgan/data/ffhq_degradation_dataset.py delete mode 100644 third_part/GFPGAN/gfpgan/models/__init__.py delete mode 100644 third_part/GFPGAN/gfpgan/models/gfpgan_model.py delete mode 100644 third_part/GFPGAN/gfpgan/train.py delete mode 100644 third_part/GFPGAN/gfpgan/utils.py delete mode 100644 third_part/GFPGAN/gfpgan/version.py delete mode 100644 third_part/GFPGAN/gfpgan/weights/README.md delete mode 100644 third_part/GFPGAN/options/train_gfpgan_v1.yml delete mode 100644 third_part/GFPGAN/options/train_gfpgan_v1_simple.yml delete mode 100644 third_part/GPEN/align_faces.py delete mode 100644 third_part/GPEN/face_detect/.DS_Store delete mode 100644 third_part/GPEN/face_detect/data/FDDB/img_list.txt delete mode 100644 third_part/GPEN/face_detect/data/__init__.py delete mode 100644 third_part/GPEN/face_detect/data/config.py delete mode 100644 third_part/GPEN/face_detect/data/data_augment.py delete mode 100644 third_part/GPEN/face_detect/data/wider_face.py delete mode 100644 third_part/GPEN/face_detect/facemodels/__init__.py delete mode 100644 third_part/GPEN/face_detect/facemodels/net.py delete mode 100644 third_part/GPEN/face_detect/facemodels/retinaface.py delete mode 100644 third_part/GPEN/face_detect/layers/__init__.py delete mode 100644 third_part/GPEN/face_detect/layers/functions/prior_box.py delete mode 100644 third_part/GPEN/face_detect/layers/modules/__init__.py delete mode 100644 third_part/GPEN/face_detect/layers/modules/multibox_loss.py delete mode 100644 third_part/GPEN/face_detect/retinaface_detection.py delete mode 100644 third_part/GPEN/face_detect/utils/__init__.py delete mode 100644 third_part/GPEN/face_detect/utils/box_utils.py delete mode 100644 third_part/GPEN/face_detect/utils/nms/__init__.py delete mode 100644 third_part/GPEN/face_detect/utils/nms/py_cpu_nms.py delete mode 100644 third_part/GPEN/face_detect/utils/timer.py delete mode 100644 third_part/GPEN/face_model/face_gan.py delete mode 100644 third_part/GPEN/face_model/gpen_model.py delete mode 100644 third_part/GPEN/face_model/op/__init__.py delete mode 100644 third_part/GPEN/face_model/op/fused_act.py delete mode 100644 third_part/GPEN/face_model/op/fused_bias_act.cpp delete mode 100644 third_part/GPEN/face_model/op/fused_bias_act_kernel.cu delete mode 100644 third_part/GPEN/face_model/op/upfirdn2d.cpp delete mode 100644 third_part/GPEN/face_model/op/upfirdn2d.py delete mode 100644 third_part/GPEN/face_model/op/upfirdn2d_kernel.cu delete mode 100644 third_part/GPEN/face_morpher/README.rst delete mode 100644 third_part/GPEN/face_morpher/facemorpher/__init__.py delete mode 100644 third_part/GPEN/face_morpher/facemorpher/aligner.py delete mode 100644 third_part/GPEN/face_morpher/facemorpher/averager.py delete mode 100644 third_part/GPEN/face_morpher/facemorpher/blender.py delete mode 100644 third_part/GPEN/face_morpher/facemorpher/locator.py delete mode 100644 third_part/GPEN/face_morpher/facemorpher/morpher.py delete mode 100644 third_part/GPEN/face_morpher/facemorpher/plotter.py delete mode 100644 third_part/GPEN/face_morpher/facemorpher/videoer.py delete mode 100644 third_part/GPEN/face_morpher/facemorpher/warper.py delete mode 100644 third_part/GPEN/face_morpher/requirements.txt delete mode 100644 third_part/GPEN/face_morpher/scripts/make_docs.sh delete mode 100644 third_part/GPEN/face_morpher/scripts/publish_ghpages.sh delete mode 100644 third_part/GPEN/face_morpher/setup.cfg delete mode 100644 third_part/GPEN/face_morpher/setup.py delete mode 100644 third_part/GPEN/face_parse/blocks.py delete mode 100644 third_part/GPEN/face_parse/face_parsing.py delete mode 100644 third_part/GPEN/face_parse/mask.png delete mode 100644 third_part/GPEN/face_parse/model.py delete mode 100644 third_part/GPEN/face_parse/parse_model.py delete mode 100644 third_part/GPEN/face_parse/resnet.py delete mode 100644 third_part/GPEN/face_parse/test.png delete mode 100644 third_part/GPEN/gpen_face_enhancer.py diff --git a/__init__.py b/__init__.py index 3800e6e..31874b1 100644 --- a/__init__.py +++ b/__init__.py @@ -9,25 +9,13 @@ NODE_DISPLAY_NAME_MAPPINGS = {} if init(): py = get_ext_dir("py") - #用os取目录下所有py文件 - #files = glob.glob(os.path.join(py, "*.py"), recursive=False) files = os.listdir(py) for file in files: if not file.endswith(".py"): continue - #取文件名 name = os.path.splitext(file)[0] imported_module = importlib.import_module(".py.{}".format(name), __name__) NODE_CLASS_MAPPINGS = {**NODE_CLASS_MAPPINGS, **imported_module.NODE_CLASS_MAPPINGS} NODE_DISPLAY_NAME_MAPPINGS = {**NODE_DISPLAY_NAME_MAPPINGS, **imported_module.NODE_DISPLAY_NAME_MAPPINGS} - # spec = importlib.util.spec_from_file_location(name, file) - # module = importlib.util.module_from_spec(spec) - # sys.modules[name] = module - # spec.loader.exec_module(module) - # if hasattr(module, "NODE_CLASS_MAPPINGS") and getattr(module, "NODE_CLASS_MAPPINGS") is not None: - # NODE_CLASS_MAPPINGS.update(module.NODE_CLASS_MAPPINGS) - # if hasattr(module, "NODE_DISPLAY_NAME_MAPPINGS") and getattr(module, "NODE_DISPLAY_NAME_MAPPINGS") is not None: - # NODE_DISPLAY_NAME_MAPPINGS.update(module.NODE_DISPLAY_NAME_MAPPINGS) - -#WEB_DIRECTORY = "./js" + __all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"] diff --git a/js/EasyPromptSelecto.js b/js/EasyPromptSelecto.js index 489f61a..925fd8f 100644 --- a/js/EasyPromptSelecto.js +++ b/js/EasyPromptSelecto.js @@ -251,23 +251,6 @@ app.registerExtension({ return r; }; - // When the node is executed we will be sent the input text, display this in the widget - const onExecuted = nodeType.prototype.onExecuted; - nodeType.prototype.onExecuted = function(message) { - onExecuted?.apply(this, arguments); - for (let i = 0; i < this.widgets.length; i++) { - if(this.widgets[i].name=='StatusInfo'){ - if(message?.text){ - this.widgets[i].value = message.text.join(''); - } - break; - } - } - - if (this.size[1] < 200) { - this.setSize([this.size[0], 200]); - } - }; } }, }); \ No newline at end of file diff --git a/js/StyleSelecto.js b/js/StyleSelecto.js new file mode 100644 index 0000000..e56ed13 --- /dev/null +++ b/js/StyleSelecto.js @@ -0,0 +1,239 @@ +import { app } from "/scripts/app.js"; +import { $el } from "../../../scripts/ui.js"; +import { ComfyWidgets } from "/scripts/widgets.js"; +import { api } from "../../../scripts/api.js"; + +$el("style", { + textContent: ` + .lam-model-info { + color: white; + font-family: sans-serif; + max-width: 90vw; + } + .lam-model-content { + display: flex; + flex-direction: column; + overflow: hidden; + } + .lam-model-info h2 { + text-align: center; + margin: 0 0 10px 0; + } + .lam-model-info p { + margin: 5px 0; + } + .lam-model-info a { + color: dodgerblue; + } + .lam-model-info a:hover { + text-decoration: underline; + } + .lam-model-tags-list { + display: flex; + flex-wrap: wrap; + list-style: none; + gap: 10px; + min-height: 100px; + max-height: 400px; + overflow: auto; + margin: 10px 0; + padding: 0; + } + .lam-model-tag { + background-color: rgb(128, 213, 247); + color: #000; + display: flex; + align-items: center; + gap: 5px; + border-radius: 5px; + padding: 2px 5px; + cursor: pointer; + } + .lam-model-tag--selected span::before { + content: "✅"; + position: absolute; + background-color: dodgerblue; + top: 0; + right: 0; + bottom: 0; + text-align: center; + } + .lam-model-tag:hover { + outline: 2px solid dodgerblue; + } + .lam-model-tag p { + margin: 0; + } + .lam-model-tag span { + text-align: center; + border-radius: 5px; + background-color: dodgerblue; + color: #fff; + padding: 2px; + position: relative; + min-width: 20px; + overflow: hidden; + } + + .lam-model-metadata .comfy-modal-content { + max-width: 100%; + } + .lam-model-metadata label { + margin-right: 1ch; + color: #ccc; + } + + .lam-model-metadata span { + color: dodgerblue; + } + + .lam-preview { + max-width: 50%; + margin-left: 10px; + position: relative; + } + .lam-preview img { + max-height: 300px; + } + .lam-preview button { + position: absolute; + font-size: 12px; + bottom: 10px; + right: 10px; + } + .lam-model-notes { + background-color: rgba(0, 0, 0, 0.25); + padding: 5px; + margin-top: 5px; + } + .lam-model-notes:empty { + display: none; + } + +`, +parent: document.body, +}) + +let pb_cache = {}; +async function getStyles(name) { + if(pb_cache[name]) + return pb_cache[name]; + else { + const resp = await api.fetchApi(`/lam/getStyles?name=${name}`); + if (resp.status === 200) { + let data = await resp.json(); + pb_cache[name] = data; + return data; + } + return undefined; + } + +} +function getTagList(tags) { + let rlist=[] + tags.forEach((k,i) => { + let t=[ k['zhName'],k['name']] + rlist.push($el( + "li.lam-model-tag", + { + dataset: { + tag: t[1], + }, + $: (el) => { + el.onclick = () => { + el.classList.toggle("lam-model-tag--selected"); + }; + }, + }, + [ + $el("p", { + textContent: t[0], + }), + $el("span", { + textContent: i, + }), + ] + )) + }); + return rlist +} +// Displays input text on a node +app.registerExtension({ + name: "StyleSelecto", + async beforeRegisterNodeDef(nodeType, nodeData, app) { + var names=['StyleSelecto'] + if (names.indexOf(nodeData.name)>=0) { + // When the node is created we want to add a readonly text widget to display the text + const onNodeCreated = nodeType.prototype.onNodeCreated; + nodeType.prototype.onNodeCreated = function() { + const r = onNodeCreated?.apply(this, arguments); + let style_type = this.widgets[this.widgets.findIndex(obj => obj.name === 'style_type')]; + this.setProperty("values", []) + //stylesEl.inputEl.classList.add("lam-model-notes"); + const list = $el("ol.lam-model-tags-list",[]); + let styles=this.addDOMWidget('styles',"list",list) + let st_values='' + Object.defineProperty(style_type, "value", { + set: (x) => { + st_values=x + if(st_values){ + getStyles(st_values); + styles.element.innerHTML='' + if(pb_cache[st_values]){ + let list =getTagList(pb_cache[st_values]); + styles.element.append(...list) + styles.element.querySelectorAll(".lam-model-tag").forEach(el => { + if(this.properties["values"].includes(el.dataset.tag)){ + el.classList.add("lam-model-tag--selected"); + } + this.setSize([500, 400]); + }); + } + + } + }, + get: () => { + if(pb_cache[st_values]&&styles.element.children.length==0){ + let list =getTagList(pb_cache[st_values]); + styles.element.append(...list) + styles.element.querySelectorAll(".lam-model-tag").forEach(el => { + if(this.properties["values"].includes(el.dataset.tag)){ + el.classList.add("lam-model-tag--selected"); + } + this.setSize([500, 400]); + }); + } + return st_values; + } + }); + let stylesValue='' + Object.defineProperty(styles, "value", { + set: (x) => { + + }, + get: () => { + styles.element.querySelectorAll(".lam-model-tag").forEach(el => { + if(el.classList.value.indexOf("lam-model-tag--selected")>=0){ + if(!this.properties["values"].includes(el.dataset.tag)){ + this.properties["values"].push(el.dataset.tag); + stylesValue = this.properties["values"].join(','); + } + }else{ + if(this.properties["values"].includes(el.dataset.tag)){ + this.properties["values"]=this.properties["values"].filter(v=>v!=el.dataset.tag); + stylesValue = this.properties["values"].join(','); + } + } + + }); + return stylesValue; + } + }); + + this.setSize([500, 400]); + return r; + }; + + } + }, +}); \ No newline at end of file diff --git a/py/AspectRatio.py b/py/AspectRatio.py new file mode 100644 index 0000000..60554af --- /dev/null +++ b/py/AspectRatio.py @@ -0,0 +1,42 @@ + +class AspectRatio: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "aspect_ratio": (['704×1408 ∣ 1:2','704×1344 ∣ 11:21','768×1344 ∣ 4:7','768×1280 ∣ 3:5' + ,'832×1216 ∣ 13:19','832×1152 ∣ 13:18','896×1152 ∣ 7:9','896×1088 ∣ 14:17' + ,'960×1088 ∣ 15:17','960×1024 ∣ 15:16','1024×1024 ∣ 1:1','1024×960 ∣ 16:15' + ,'1088×960 ∣ 17:15','1088×896 ∣ 17:14','1152×896 ∣ 9:7','1152×832 ∣ 18:13' + ,'1216×832 ∣ 19:13','1280×768 ∣ 5:3','1344×768 ∣ 7:4','1344×704 ∣ 21:11' + ,'1408×704 ∣ 2:1','1472×704 ∣ 23:11','1536×640 ∣ 12:5','1600×640 ∣ 5:2' + ,'1664×576 ∣ 26:9','1728×576 ∣ 3:1'], {"default": "1152×896 ∣ 9:7"}), + "width": ("INT", {"default": 0, "min": 0, "max": 99999}), + "height": ("INT", {"default": 0, "min": 0, "max": 99999}), + } + } + RETURN_TYPES = ("INT","INT",) + RETURN_NAMES = ("宽","高",) + FUNCTION = "aspect_ratio" + + CATEGORY = "lam" + + def aspect_ratio(self,aspect_ratio,width,height): + #判断不为自定义,提前宽高 + if width==0 and height==0: + aspect_ratio = aspect_ratio.split("∣") + width = int(aspect_ratio[0].split("×")[0]) + height = int(aspect_ratio[0].split("×")[1]) + return (width,height,) + +NODE_CLASS_MAPPINGS = { + "AspectRatio": AspectRatio +} + +# A dictionary that contains the friendly/humanly readable titles for the nodes +NODE_DISPLAY_NAME_MAPPINGS = { + "AspectRatio": "宽高比" +} diff --git a/py/StyleSelecto.py b/py/StyleSelecto.py new file mode 100644 index 0000000..722d83f --- /dev/null +++ b/py/StyleSelecto.py @@ -0,0 +1,112 @@ +import server +from aiohttp import web +import os +import json + + +dir = os.path.abspath(os.path.join(__file__, "../../styles")) +if not os.path.exists(dir): + os.mkdir(dir) + +@server.PromptServer.instance.routes.get("/lam/getStyles") +def getStyles(request): + if "name" in request.rel_url.query: + name = request.rel_url.query["name"] + file = os.path.join(dir, name+'.json') + zhFile = os.path.join(dir, 'zh_CN.json') + if os.path.isfile(zhFile): + f = open(zhFile,'r', encoding='utf-8') + zhData = json.load(f) + f.close() + + if os.path.isfile(file): + f = open(file,'r', encoding='utf-8') + data = json.load(f) + f.close() + if data: + ndata=[] + if zhData: + for d in data: + nd={} + name=d['name'].replace('-',' ') + words=name.split(' ') + key=' '.join(word.upper() if word in ['mre','sai'] else word.capitalize() for word in words) + nd['zhName']=zhData[key] if key in zhData else key + nd["name"]=d['name'] + ndata.append(nd) + return web.json_response(ndata) + return web.Response(status=404) + +class StyleSelecto: + """ + 提示词选择工具 + """ + def __init__(self): + dir = os.path.abspath(os.path.join(__file__, "../../styles")) + if not os.path.exists(dir): + os.mkdir(dir) + self.dir=dir + + @classmethod + def INPUT_TYPES(self): + dir = os.path.abspath(os.path.join(__file__, "../../styles")) + #获取目录全部yml文件名 + files_name=[] + for root, dirs, files in os.walk(dir): + for file in files: + if file.endswith(".json") and file.split(".")[0].find("styles")!=-1: + files_name.append(file.split(".")[0]) + + return { + "required": { + "prompt": ("STRING", {"forceInput": True}), + "style_type":(files_name, ), + + }, + "optional": { + "negative_prompt":("STRING",{"forceInput": True}), + }, + "hidden": {"extra_pnginfo": "EXTRA_PNGINFO", "unique_id": "UNIQUE_ID"}, + } + + RETURN_TYPES = ("STRING","STRING",) + RETURN_NAMES = ("正向提示词","反向提示词",) + + FUNCTION = "translate" + + #OUTPUT_NODE = False + + CATEGORY = "lam" + + def translate(self,prompt,style_type,extra_pnginfo, unique_id,negative_prompt=""): + values = [] + for node in extra_pnginfo["workflow"]["nodes"]: + if node["id"] == int(unique_id): + values = node["properties"]["values"] + break + print(values,'================') + for root, dirs, files in os.walk(self.dir): + for file in files: + if file.endswith(".json") and file.split(".")[0].find("styles")!=-1: + f = open(os.path.join(root, file), 'r', encoding='utf-8') + data = json.load(f) + f.close() + for d in data: + if d['name'] in values: + prompt=d['prompt'].format(prompt=prompt) + negative_prompt+=','+d['negative_prompt'] + break + print(prompt,'================',negative_prompt) + + return (prompt,negative_prompt) + +# A dictionary that contains all nodes you want to export with their names +# NOTE: names should be globally unique +NODE_CLASS_MAPPINGS = { + "StyleSelecto": StyleSelecto +} + +# A dictionary that contains the friendly/humanly readable titles for the nodes +NODE_DISPLAY_NAME_MAPPINGS = { + "StyleSelecto": "风格选择器" +} diff --git a/scripts/swapper.py b/scripts/swapper.py deleted file mode 100644 index b8c9c3f..0000000 --- a/scripts/swapper.py +++ /dev/null @@ -1,43 +0,0 @@ -import insightface -import numpy as np -import cv2 -import threading -from typing import Any, Optional, List - -providers = ["CPUExecutionProvider"] -THREAD_LOCK = threading.Lock() -FACE_ANALYSER = None - -def getFaceSwapModel(model_path: str): - - FS_MODEL = insightface.model_zoo.get_model(model_path, providers=providers) - return FS_MODEL - -def get_face_analyser() -> Any: - global FACE_ANALYSER - - with THREAD_LOCK: - if FACE_ANALYSER is None: - FACE_ANALYSER = insightface.app.FaceAnalysis(name='buffalo_l', providers=providers) - return FACE_ANALYSER - -def getFaceAnalyser(): - face_analyser=insightface.app.FaceAnalysis(name="buffalo_l", providers=providers) - return face_analyser - -def get_face_single(img_data: np.ndarray,face_index=0, det_size=(640, 640)): - face_analyser = get_face_analyser() - face_analyser.prepare(ctx_id=0, det_size=det_size) - face = face_analyser.get(img_data) - - if len(face) == 0 and det_size[0] > 320 and det_size[1] > 320: - det_size_half = (det_size[0] // 2, det_size[1] // 2) - return get_face_single(img_data, face_index=face_index, det_size=det_size_half) - - try: - return sorted(face, key=lambda x: x.bbox[0])[face_index] - except IndexError: - return None - - - diff --git a/src/audio2exp_models/audio2exp.py b/src/audio2exp_models/audio2exp.py deleted file mode 100644 index 9e79a92..0000000 --- a/src/audio2exp_models/audio2exp.py +++ /dev/null @@ -1,41 +0,0 @@ -from tqdm import tqdm -import torch -from torch import nn - - -class Audio2Exp(nn.Module): - def __init__(self, netG, cfg, device, prepare_training_loss=False): - super(Audio2Exp, self).__init__() - self.cfg = cfg - self.device = device - self.netG = netG.to(device) - - def test(self, batch): - - mel_input = batch['indiv_mels'] # bs T 1 80 16 - bs = mel_input.shape[0] - T = mel_input.shape[1] - - exp_coeff_pred = [] - - for i in tqdm(range(0, T, 10),'audio2exp:'): # every 10 frames - - current_mel_input = mel_input[:,i:i+10] - - #ref = batch['ref'][:, :, :64].repeat((1,current_mel_input.shape[1],1)) #bs T 64 - ref = batch['ref'][:, :, :64][:, i:i+10] - ratio = batch['ratio_gt'][:, i:i+10] #bs T - - audiox = current_mel_input.view(-1, 1, 80, 16) # bs*T 1 80 16 - - curr_exp_coeff_pred = self.netG(audiox, ref, ratio) # bs T 64 - - exp_coeff_pred += [curr_exp_coeff_pred] - - # BS x T x 64 - results_dict = { - 'exp_coeff_pred': torch.cat(exp_coeff_pred, axis=1) - } - return results_dict - - diff --git a/src/audio2exp_models/networks.py b/src/audio2exp_models/networks.py deleted file mode 100644 index f052e18..0000000 --- a/src/audio2exp_models/networks.py +++ /dev/null @@ -1,74 +0,0 @@ -import torch -import torch.nn.functional as F -from torch import nn - -class Conv2d(nn.Module): - def __init__(self, cin, cout, kernel_size, stride, padding, residual=False, use_act = True, *args, **kwargs): - super().__init__(*args, **kwargs) - self.conv_block = nn.Sequential( - nn.Conv2d(cin, cout, kernel_size, stride, padding), - nn.BatchNorm2d(cout) - ) - self.act = nn.ReLU() - self.residual = residual - self.use_act = use_act - - def forward(self, x): - out = self.conv_block(x) - if self.residual: - out += x - - if self.use_act: - return self.act(out) - else: - return out - -class SimpleWrapperV2(nn.Module): - def __init__(self) -> None: - super().__init__() - self.audio_encoder = nn.Sequential( - Conv2d(1, 32, kernel_size=3, stride=1, padding=1), - Conv2d(32, 32, kernel_size=3, stride=1, padding=1, residual=True), - Conv2d(32, 32, kernel_size=3, stride=1, padding=1, residual=True), - - Conv2d(32, 64, kernel_size=3, stride=(3, 1), padding=1), - Conv2d(64, 64, kernel_size=3, stride=1, padding=1, residual=True), - Conv2d(64, 64, kernel_size=3, stride=1, padding=1, residual=True), - - Conv2d(64, 128, kernel_size=3, stride=3, padding=1), - Conv2d(128, 128, kernel_size=3, stride=1, padding=1, residual=True), - Conv2d(128, 128, kernel_size=3, stride=1, padding=1, residual=True), - - Conv2d(128, 256, kernel_size=3, stride=(3, 2), padding=1), - Conv2d(256, 256, kernel_size=3, stride=1, padding=1, residual=True), - - Conv2d(256, 512, kernel_size=3, stride=1, padding=0), - Conv2d(512, 512, kernel_size=1, stride=1, padding=0), - ) - - #### load the pre-trained audio_encoder - #self.audio_encoder = self.audio_encoder.to(device) - ''' - wav2lip_state_dict = torch.load('/apdcephfs_cq2/share_1290939/wenxuazhang/checkpoints/wav2lip.pth')['state_dict'] - state_dict = self.audio_encoder.state_dict() - - for k,v in wav2lip_state_dict.items(): - if 'audio_encoder' in k: - print('init:', k) - state_dict[k.replace('module.audio_encoder.', '')] = v - self.audio_encoder.load_state_dict(state_dict) - ''' - - self.mapping1 = nn.Linear(512+64+1, 64) - #self.mapping2 = nn.Linear(30, 64) - #nn.init.constant_(self.mapping1.weight, 0.) - nn.init.constant_(self.mapping1.bias, 0.) - - def forward(self, x, ref, ratio): - x = self.audio_encoder(x).view(x.size(0), -1) - ref_reshape = ref.reshape(x.size(0), -1) - ratio = ratio.reshape(x.size(0), -1) - - y = self.mapping1(torch.cat([x, ref_reshape, ratio], dim=1)) - out = y.reshape(ref.shape[0], ref.shape[1], -1) #+ ref # resudial - return out diff --git a/src/audio2pose_models/audio2pose.py b/src/audio2pose_models/audio2pose.py deleted file mode 100644 index d13c5c5..0000000 --- a/src/audio2pose_models/audio2pose.py +++ /dev/null @@ -1,94 +0,0 @@ -import torch -from torch import nn -from custom_nodes.ComfyUI_Lam.src.audio2pose_models.cvae import CVAE -from custom_nodes.ComfyUI_Lam.src.audio2pose_models.discriminator import PoseSequenceDiscriminator -from custom_nodes.ComfyUI_Lam.src.audio2pose_models.audio_encoder import AudioEncoder - -class Audio2Pose(nn.Module): - def __init__(self, cfg, wav2lip_checkpoint, device='cuda'): - super().__init__() - self.cfg = cfg - self.seq_len = cfg.MODEL.CVAE.SEQ_LEN - self.latent_dim = cfg.MODEL.CVAE.LATENT_SIZE - self.device = device - - self.audio_encoder = AudioEncoder(wav2lip_checkpoint, device) - self.audio_encoder.eval() - for param in self.audio_encoder.parameters(): - param.requires_grad = False - - self.netG = CVAE(cfg) - self.netD_motion = PoseSequenceDiscriminator(cfg) - - - def forward(self, x): - - batch = {} - coeff_gt = x['gt'].cuda().squeeze(0) #bs frame_len+1 73 - batch['pose_motion_gt'] = coeff_gt[:, 1:, 64:70] - coeff_gt[:, :1, 64:70] #bs frame_len 6 - batch['ref'] = coeff_gt[:, 0, 64:70] #bs 6 - batch['class'] = x['class'].squeeze(0).cuda() # bs - indiv_mels= x['indiv_mels'].cuda().squeeze(0) # bs seq_len+1 80 16 - - # forward - audio_emb_list = [] - audio_emb = self.audio_encoder(indiv_mels[:, 1:, :, :].unsqueeze(2)) #bs seq_len 512 - batch['audio_emb'] = audio_emb - batch = self.netG(batch) - - pose_motion_pred = batch['pose_motion_pred'] # bs frame_len 6 - pose_gt = coeff_gt[:, 1:, 64:70].clone() # bs frame_len 6 - pose_pred = coeff_gt[:, :1, 64:70] + pose_motion_pred # bs frame_len 6 - - batch['pose_pred'] = pose_pred - batch['pose_gt'] = pose_gt - - return batch - - def test(self, x): - - batch = {} - ref = x['ref'] #bs 1 70 - batch['ref'] = x['ref'][:,0,-6:] - batch['class'] = x['class'] - bs = ref.shape[0] - - indiv_mels= x['indiv_mels'] # bs T 1 80 16 - indiv_mels_use = indiv_mels[:, 1:] # we regard the ref as the first frame - num_frames = x['num_frames'] - num_frames = int(num_frames) - 1 - - # - div = num_frames//self.seq_len - re = num_frames%self.seq_len - audio_emb_list = [] - pose_motion_pred_list = [torch.zeros(batch['ref'].unsqueeze(1).shape, dtype=batch['ref'].dtype, - device=batch['ref'].device)] - - for i in range(div): - z = torch.randn(bs, self.latent_dim).to(ref.device) - batch['z'] = z - audio_emb = self.audio_encoder(indiv_mels_use[:, i*self.seq_len:(i+1)*self.seq_len,:,:,:]) #bs seq_len 512 - batch['audio_emb'] = audio_emb - batch = self.netG.test(batch) - pose_motion_pred_list.append(batch['pose_motion_pred']) #list of bs seq_len 6 - - if re != 0: - z = torch.randn(bs, self.latent_dim).to(ref.device) - batch['z'] = z - audio_emb = self.audio_encoder(indiv_mels_use[:, -1*self.seq_len:,:,:,:]) #bs seq_len 512 - if audio_emb.shape[1] != self.seq_len: - pad_dim = self.seq_len-audio_emb.shape[1] - pad_audio_emb = audio_emb[:, :1].repeat(1, pad_dim, 1) - audio_emb = torch.cat([pad_audio_emb, audio_emb], 1) - batch['audio_emb'] = audio_emb - batch = self.netG.test(batch) - pose_motion_pred_list.append(batch['pose_motion_pred'][:,-1*re:,:]) - - pose_motion_pred = torch.cat(pose_motion_pred_list, dim = 1) - batch['pose_motion_pred'] = pose_motion_pred - - pose_pred = ref[:, :1, -6:] + pose_motion_pred # bs T 6 - - batch['pose_pred'] = pose_pred - return batch diff --git a/src/audio2pose_models/audio2pose_two.py b/src/audio2pose_models/audio2pose_two.py deleted file mode 100644 index 1a8d658..0000000 --- a/src/audio2pose_models/audio2pose_two.py +++ /dev/null @@ -1,94 +0,0 @@ -import torch -from torch import nn -from custom_nodes.ComfyUI_Lam.src.audio2pose_models.cvae import CVAE -from custom_nodes.ComfyUI_Lam.src.audio2pose_models.discriminator import PoseSequenceDiscriminator -from custom_nodes.ComfyUI_Lam.src.audio2pose_models.audio_encoder import AudioEncoder - -class Audio2Pose(nn.Module): - def __init__(self, cfg, wav2lip_checkpoint, device='cuda'): - super().__init__() - self.cfg = cfg - self.seq_len = cfg.MODEL.CVAE.SEQ_LEN - self.latent_dim = cfg.MODEL.CVAE.LATENT_SIZE - self.device = device - - self.audio_encoder = AudioEncoder(wav2lip_checkpoint, device) - self.audio_encoder.eval() - for param in self.audio_encoder.parameters(): - param.requires_grad = False - - self.netG = CVAE(cfg) - self.netD_motion = PoseSequenceDiscriminator(cfg) - - - def forward(self, x): - - batch = {} - coeff_gt = x['gt'].cuda().squeeze(0) #bs frame_len+1 73 - batch['pose_motion_gt'] = coeff_gt[:, 1:, -9:-3] - coeff_gt[:, :1, -9:-3] #bs frame_len 6 - batch['ref'] = coeff_gt[:, 0, -9:-3] #bs 6 - batch['class'] = x['class'].squeeze(0).cuda() # bs - indiv_mels= x['indiv_mels'].cuda().squeeze(0) # bs seq_len+1 80 16 - - # forward - audio_emb_list = [] - audio_emb = self.audio_encoder(indiv_mels[:, 1:, :, :].unsqueeze(2)) #bs seq_len 512 - batch['audio_emb'] = audio_emb - batch = self.netG(batch) - - pose_motion_pred = batch['pose_motion_pred'] # bs frame_len 6 - pose_gt = coeff_gt[:, 1:, -9:-3].clone() # bs frame_len 6 - pose_pred = coeff_gt[:, :1, -9:-3] + pose_motion_pred # bs frame_len 6 - - batch['pose_pred'] = pose_pred - batch['pose_gt'] = pose_gt - - return batch - - def test(self, x): - - batch = {} - ref = x['ref'] #bs 1 70 - batch['ref'] = x['ref'][:,0,-6:] - batch['class'] = x['class'] - bs = ref.shape[0] - - indiv_mels= x['indiv_mels'] # bs T 1 80 16 - indiv_mels_use = indiv_mels[:, 1:] # we regard the ref as the first frame - num_frames = x['num_frames'] - num_frames = int(num_frames) - 1 - - # - div = num_frames//self.seq_len - re = num_frames%self.seq_len - audio_emb_list = [] - pose_motion_pred_list = [torch.zeros(batch['ref'].unsqueeze(1).shape, dtype=batch['ref'].dtype, - device=batch['ref'].device)] - - for i in range(div): - z = torch.randn(bs, self.latent_dim).to(ref.device) - batch['z'] = z - audio_emb = self.audio_encoder(indiv_mels_use[:, i*self.seq_len:(i+1)*self.seq_len,:,:,:]) #bs seq_len 512 - batch['audio_emb'] = audio_emb - batch = self.netG.test(batch) - pose_motion_pred_list.append(batch['pose_motion_pred']) #list of bs seq_len 6 - - if re != 0: - z = torch.randn(bs, self.latent_dim).to(ref.device) - batch['z'] = z - audio_emb = self.audio_encoder(indiv_mels_use[:, -1*self.seq_len:,:,:,:]) #bs seq_len 512 - if audio_emb.shape[1] != self.seq_len: - pad_dim = self.seq_len-audio_emb.shape[1] - pad_audio_emb = audio_emb[:, :1].repeat(1, pad_dim, 1) - audio_emb = torch.cat([pad_audio_emb, audio_emb], 1) - batch['audio_emb'] = audio_emb - batch = self.netG.test(batch) - pose_motion_pred_list.append(batch['pose_motion_pred'][:,-1*re:,:]) - - pose_motion_pred = torch.cat(pose_motion_pred_list, dim = 1) - batch['pose_motion_pred'] = pose_motion_pred - - pose_pred = ref[:, :1, -6:] + pose_motion_pred # bs T 6 - - batch['pose_pred'] = pose_pred - return batch diff --git a/src/audio2pose_models/audio_encoder.py b/src/audio2pose_models/audio_encoder.py deleted file mode 100644 index 6279d20..0000000 --- a/src/audio2pose_models/audio_encoder.py +++ /dev/null @@ -1,64 +0,0 @@ -import torch -from torch import nn -from torch.nn import functional as F - -class Conv2d(nn.Module): - def __init__(self, cin, cout, kernel_size, stride, padding, residual=False, *args, **kwargs): - super().__init__(*args, **kwargs) - self.conv_block = nn.Sequential( - nn.Conv2d(cin, cout, kernel_size, stride, padding), - nn.BatchNorm2d(cout) - ) - self.act = nn.ReLU() - self.residual = residual - - def forward(self, x): - out = self.conv_block(x) - if self.residual: - out += x - return self.act(out) - -class AudioEncoder(nn.Module): - def __init__(self, wav2lip_checkpoint, device): - super(AudioEncoder, self).__init__() - - self.audio_encoder = nn.Sequential( - Conv2d(1, 32, kernel_size=3, stride=1, padding=1), - Conv2d(32, 32, kernel_size=3, stride=1, padding=1, residual=True), - Conv2d(32, 32, kernel_size=3, stride=1, padding=1, residual=True), - - Conv2d(32, 64, kernel_size=3, stride=(3, 1), padding=1), - Conv2d(64, 64, kernel_size=3, stride=1, padding=1, residual=True), - Conv2d(64, 64, kernel_size=3, stride=1, padding=1, residual=True), - - Conv2d(64, 128, kernel_size=3, stride=3, padding=1), - Conv2d(128, 128, kernel_size=3, stride=1, padding=1, residual=True), - Conv2d(128, 128, kernel_size=3, stride=1, padding=1, residual=True), - - Conv2d(128, 256, kernel_size=3, stride=(3, 2), padding=1), - Conv2d(256, 256, kernel_size=3, stride=1, padding=1, residual=True), - - Conv2d(256, 512, kernel_size=3, stride=1, padding=0), - Conv2d(512, 512, kernel_size=1, stride=1, padding=0),) - - #### load the pre-trained audio_encoder, we do not need to load wav2lip model here. - # wav2lip_state_dict = torch.load(wav2lip_checkpoint, map_location=torch.device(device))['state_dict'] - # state_dict = self.audio_encoder.state_dict() - - # for k,v in wav2lip_state_dict.items(): - # if 'audio_encoder' in k: - # state_dict[k.replace('module.audio_encoder.', '')] = v - # self.audio_encoder.load_state_dict(state_dict) - - - def forward(self, audio_sequences): - # audio_sequences = (B, T, 1, 80, 16) - B = audio_sequences.size(0) - - audio_sequences = torch.cat([audio_sequences[:, i] for i in range(audio_sequences.size(1))], dim=0) - - audio_embedding = self.audio_encoder(audio_sequences) # B, 512, 1, 1 - dim = audio_embedding.shape[1] - audio_embedding = audio_embedding.reshape((B, -1, dim, 1, 1)) - - return audio_embedding.squeeze(-1).squeeze(-1) #B seq_len+1 512 diff --git a/src/audio2pose_models/audio_encoder_two.py b/src/audio2pose_models/audio_encoder_two.py deleted file mode 100644 index ea9095a..0000000 --- a/src/audio2pose_models/audio_encoder_two.py +++ /dev/null @@ -1,64 +0,0 @@ -import torch -from torch import nn -from torch.nn import functional as F - -class Conv2d(nn.Module): - def __init__(self, cin, cout, kernel_size, stride, padding, residual=False, *args, **kwargs): - super().__init__(*args, **kwargs) - self.conv_block = nn.Sequential( - nn.Conv2d(cin, cout, kernel_size, stride, padding), - nn.BatchNorm2d(cout) - ) - self.act = nn.ReLU() - self.residual = residual - - def forward(self, x): - out = self.conv_block(x) - if self.residual: - out += x - return self.act(out) - -class AudioEncoder(nn.Module): - def __init__(self, wav2lip_checkpoint, device): - super(AudioEncoder, self).__init__() - - self.audio_encoder = nn.Sequential( - Conv2d(1, 32, kernel_size=3, stride=1, padding=1), - Conv2d(32, 32, kernel_size=3, stride=1, padding=1, residual=True), - Conv2d(32, 32, kernel_size=3, stride=1, padding=1, residual=True), - - Conv2d(32, 64, kernel_size=3, stride=(3, 1), padding=1), - Conv2d(64, 64, kernel_size=3, stride=1, padding=1, residual=True), - Conv2d(64, 64, kernel_size=3, stride=1, padding=1, residual=True), - - Conv2d(64, 128, kernel_size=3, stride=3, padding=1), - Conv2d(128, 128, kernel_size=3, stride=1, padding=1, residual=True), - Conv2d(128, 128, kernel_size=3, stride=1, padding=1, residual=True), - - Conv2d(128, 256, kernel_size=3, stride=(3, 2), padding=1), - Conv2d(256, 256, kernel_size=3, stride=1, padding=1, residual=True), - - Conv2d(256, 512, kernel_size=3, stride=1, padding=0), - Conv2d(512, 512, kernel_size=1, stride=1, padding=0),) - - #### load the pre-trained audio_encoder - wav2lip_state_dict = torch.load(wav2lip_checkpoint, map_location=torch.device(device))['state_dict'] - state_dict = self.audio_encoder.state_dict() - - for k,v in wav2lip_state_dict.items(): - if 'audio_encoder' in k: - state_dict[k.replace('module.audio_encoder.', '')] = v - self.audio_encoder.load_state_dict(state_dict) - - - def forward(self, audio_sequences): - # audio_sequences = (B, T, 1, 80, 16) - B = audio_sequences.size(0) - - audio_sequences = torch.cat([audio_sequences[:, i] for i in range(audio_sequences.size(1))], dim=0) - - audio_embedding = self.audio_encoder(audio_sequences) # B, 512, 1, 1 - dim = audio_embedding.shape[1] - audio_embedding = audio_embedding.reshape((B, -1, dim, 1, 1)) - - return audio_embedding.squeeze(-1).squeeze(-1) #B seq_len+1 512 diff --git a/src/audio2pose_models/cvae.py b/src/audio2pose_models/cvae.py deleted file mode 100644 index d33f752..0000000 --- a/src/audio2pose_models/cvae.py +++ /dev/null @@ -1,149 +0,0 @@ -import torch -import torch.nn.functional as F -from torch import nn -from custom_nodes.ComfyUI_Lam.src.audio2pose_models.res_unet import ResUnet - -def class2onehot(idx, class_num): - - assert torch.max(idx).item() < class_num - onehot = torch.zeros(idx.size(0), class_num).to(idx.device) - onehot.scatter_(1, idx, 1) - return onehot - -class CVAE(nn.Module): - def __init__(self, cfg): - super().__init__() - encoder_layer_sizes = cfg.MODEL.CVAE.ENCODER_LAYER_SIZES - decoder_layer_sizes = cfg.MODEL.CVAE.DECODER_LAYER_SIZES - latent_size = cfg.MODEL.CVAE.LATENT_SIZE - num_classes = cfg.DATASET.NUM_CLASSES - audio_emb_in_size = cfg.MODEL.CVAE.AUDIO_EMB_IN_SIZE - audio_emb_out_size = cfg.MODEL.CVAE.AUDIO_EMB_OUT_SIZE - seq_len = cfg.MODEL.CVAE.SEQ_LEN - - self.latent_size = latent_size - - self.encoder = ENCODER(encoder_layer_sizes, latent_size, num_classes, - audio_emb_in_size, audio_emb_out_size, seq_len) - self.decoder = DECODER(decoder_layer_sizes, latent_size, num_classes, - audio_emb_in_size, audio_emb_out_size, seq_len) - def reparameterize(self, mu, logvar): - std = torch.exp(0.5 * logvar) - eps = torch.randn_like(std) - return mu + eps * std - - def forward(self, batch): - batch = self.encoder(batch) - mu = batch['mu'] - logvar = batch['logvar'] - z = self.reparameterize(mu, logvar) - batch['z'] = z - return self.decoder(batch) - - def test(self, batch): - ''' - class_id = batch['class'] - z = torch.randn([class_id.size(0), self.latent_size]).to(class_id.device) - batch['z'] = z - ''' - return self.decoder(batch) - -class ENCODER(nn.Module): - def __init__(self, layer_sizes, latent_size, num_classes, - audio_emb_in_size, audio_emb_out_size, seq_len): - super().__init__() - - self.resunet = ResUnet() - self.num_classes = num_classes - self.seq_len = seq_len - - self.MLP = nn.Sequential() - layer_sizes[0] += latent_size + seq_len*audio_emb_out_size + 6 - for i, (in_size, out_size) in enumerate(zip(layer_sizes[:-1], layer_sizes[1:])): - self.MLP.add_module( - name="L{:d}".format(i), module=nn.Linear(in_size, out_size)) - self.MLP.add_module(name="A{:d}".format(i), module=nn.ReLU()) - - self.linear_means = nn.Linear(layer_sizes[-1], latent_size) - self.linear_logvar = nn.Linear(layer_sizes[-1], latent_size) - self.linear_audio = nn.Linear(audio_emb_in_size, audio_emb_out_size) - - self.classbias = nn.Parameter(torch.randn(self.num_classes, latent_size)) - - def forward(self, batch): - class_id = batch['class'] - pose_motion_gt = batch['pose_motion_gt'] #bs seq_len 6 - ref = batch['ref'] #bs 6 - bs = pose_motion_gt.shape[0] - audio_in = batch['audio_emb'] # bs seq_len audio_emb_in_size - - #pose encode - pose_emb = self.resunet(pose_motion_gt.unsqueeze(1)) #bs 1 seq_len 6 - pose_emb = pose_emb.reshape(bs, -1) #bs seq_len*6 - - #audio mapping - print(audio_in.shape) - audio_out = self.linear_audio(audio_in) # bs seq_len audio_emb_out_size - audio_out = audio_out.reshape(bs, -1) - - class_bias = self.classbias[class_id] #bs latent_size - x_in = torch.cat([ref, pose_emb, audio_out, class_bias], dim=-1) #bs seq_len*(audio_emb_out_size+6)+latent_size - x_out = self.MLP(x_in) - - mu = self.linear_means(x_out) - logvar = self.linear_means(x_out) #bs latent_size - - batch.update({'mu':mu, 'logvar':logvar}) - return batch - -class DECODER(nn.Module): - def __init__(self, layer_sizes, latent_size, num_classes, - audio_emb_in_size, audio_emb_out_size, seq_len): - super().__init__() - - self.resunet = ResUnet() - self.num_classes = num_classes - self.seq_len = seq_len - - self.MLP = nn.Sequential() - input_size = latent_size + seq_len*audio_emb_out_size + 6 - for i, (in_size, out_size) in enumerate(zip([input_size]+layer_sizes[:-1], layer_sizes)): - self.MLP.add_module( - name="L{:d}".format(i), module=nn.Linear(in_size, out_size)) - if i+1 < len(layer_sizes): - self.MLP.add_module(name="A{:d}".format(i), module=nn.ReLU()) - else: - self.MLP.add_module(name="sigmoid", module=nn.Sigmoid()) - - self.pose_linear = nn.Linear(6, 6) - self.linear_audio = nn.Linear(audio_emb_in_size, audio_emb_out_size) - - self.classbias = nn.Parameter(torch.randn(self.num_classes, latent_size)) - - def forward(self, batch): - - z = batch['z'] #bs latent_size - bs = z.shape[0] - class_id = batch['class'] - ref = batch['ref'] #bs 6 - audio_in = batch['audio_emb'] # bs seq_len audio_emb_in_size - #print('audio_in: ', audio_in[:, :, :10]) - - audio_out = self.linear_audio(audio_in) # bs seq_len audio_emb_out_size - #print('audio_out: ', audio_out[:, :, :10]) - audio_out = audio_out.reshape([bs, -1]) # bs seq_len*audio_emb_out_size - class_bias = self.classbias[class_id] #bs latent_size - - z = z + class_bias - x_in = torch.cat([ref, z, audio_out], dim=-1) - x_out = self.MLP(x_in) # bs layer_sizes[-1] - x_out = x_out.reshape((bs, self.seq_len, -1)) - - #print('x_out: ', x_out) - - pose_emb = self.resunet(x_out.unsqueeze(1)) #bs 1 seq_len 6 - - pose_motion_pred = self.pose_linear(pose_emb.squeeze(1)) #bs seq_len 6 - - batch.update({'pose_motion_pred':pose_motion_pred}) - return batch diff --git a/src/audio2pose_models/discriminator.py b/src/audio2pose_models/discriminator.py deleted file mode 100644 index 339c38e..0000000 --- a/src/audio2pose_models/discriminator.py +++ /dev/null @@ -1,76 +0,0 @@ -import torch -import torch.nn.functional as F -from torch import nn - -class ConvNormRelu(nn.Module): - def __init__(self, conv_type='1d', in_channels=3, out_channels=64, downsample=False, - kernel_size=None, stride=None, padding=None, norm='BN', leaky=False): - super().__init__() - if kernel_size is None: - if downsample: - kernel_size, stride, padding = 4, 2, 1 - else: - kernel_size, stride, padding = 3, 1, 1 - - if conv_type == '2d': - self.conv = nn.Conv2d( - in_channels, - out_channels, - kernel_size, - stride, - padding, - bias=False, - ) - if norm == 'BN': - self.norm = nn.BatchNorm2d(out_channels) - elif norm == 'IN': - self.norm = nn.InstanceNorm2d(out_channels) - else: - raise NotImplementedError - elif conv_type == '1d': - self.conv = nn.Conv1d( - in_channels, - out_channels, - kernel_size, - stride, - padding, - bias=False, - ) - if norm == 'BN': - self.norm = nn.BatchNorm1d(out_channels) - elif norm == 'IN': - self.norm = nn.InstanceNorm1d(out_channels) - else: - raise NotImplementedError - nn.init.kaiming_normal_(self.conv.weight) - - self.act = nn.LeakyReLU(negative_slope=0.2, inplace=False) if leaky else nn.ReLU(inplace=True) - - def forward(self, x): - x = self.conv(x) - if isinstance(self.norm, nn.InstanceNorm1d): - x = self.norm(x.permute((0, 2, 1))).permute((0, 2, 1)) # normalize on [C] - else: - x = self.norm(x) - x = self.act(x) - return x - - -class PoseSequenceDiscriminator(nn.Module): - def __init__(self, cfg): - super().__init__() - self.cfg = cfg - leaky = self.cfg.MODEL.DISCRIMINATOR.LEAKY_RELU - - self.seq = nn.Sequential( - ConvNormRelu('1d', cfg.MODEL.DISCRIMINATOR.INPUT_CHANNELS, 256, downsample=True, leaky=leaky), # B, 256, 64 - ConvNormRelu('1d', 256, 512, downsample=True, leaky=leaky), # B, 512, 32 - ConvNormRelu('1d', 512, 1024, kernel_size=3, stride=1, padding=1, leaky=leaky), # B, 1024, 16 - nn.Conv1d(1024, 1, kernel_size=3, stride=1, padding=1, bias=True) # B, 1, 16 - ) - - def forward(self, x): - x = x.reshape(x.size(0), x.size(1), -1).transpose(1, 2) - x = self.seq(x) - x = x.squeeze(1) - return x \ No newline at end of file diff --git a/src/audio2pose_models/networks.py b/src/audio2pose_models/networks.py deleted file mode 100644 index 8aa0b13..0000000 --- a/src/audio2pose_models/networks.py +++ /dev/null @@ -1,140 +0,0 @@ -import torch.nn as nn -import torch - - -class ResidualConv(nn.Module): - def __init__(self, input_dim, output_dim, stride, padding): - super(ResidualConv, self).__init__() - - self.conv_block = nn.Sequential( - nn.BatchNorm2d(input_dim), - nn.ReLU(), - nn.Conv2d( - input_dim, output_dim, kernel_size=3, stride=stride, padding=padding - ), - nn.BatchNorm2d(output_dim), - nn.ReLU(), - nn.Conv2d(output_dim, output_dim, kernel_size=3, padding=1), - ) - self.conv_skip = nn.Sequential( - nn.Conv2d(input_dim, output_dim, kernel_size=3, stride=stride, padding=1), - nn.BatchNorm2d(output_dim), - ) - - def forward(self, x): - - return self.conv_block(x) + self.conv_skip(x) - - -class Upsample(nn.Module): - def __init__(self, input_dim, output_dim, kernel, stride): - super(Upsample, self).__init__() - - self.upsample = nn.ConvTranspose2d( - input_dim, output_dim, kernel_size=kernel, stride=stride - ) - - def forward(self, x): - return self.upsample(x) - - -class Squeeze_Excite_Block(nn.Module): - def __init__(self, channel, reduction=16): - super(Squeeze_Excite_Block, self).__init__() - self.avg_pool = nn.AdaptiveAvgPool2d(1) - self.fc = nn.Sequential( - nn.Linear(channel, channel // reduction, bias=False), - nn.ReLU(inplace=True), - nn.Linear(channel // reduction, channel, bias=False), - nn.Sigmoid(), - ) - - def forward(self, x): - b, c, _, _ = x.size() - y = self.avg_pool(x).view(b, c) - y = self.fc(y).view(b, c, 1, 1) - return x * y.expand_as(x) - - -class ASPP(nn.Module): - def __init__(self, in_dims, out_dims, rate=[6, 12, 18]): - super(ASPP, self).__init__() - - self.aspp_block1 = nn.Sequential( - nn.Conv2d( - in_dims, out_dims, 3, stride=1, padding=rate[0], dilation=rate[0] - ), - nn.ReLU(inplace=True), - nn.BatchNorm2d(out_dims), - ) - self.aspp_block2 = nn.Sequential( - nn.Conv2d( - in_dims, out_dims, 3, stride=1, padding=rate[1], dilation=rate[1] - ), - nn.ReLU(inplace=True), - nn.BatchNorm2d(out_dims), - ) - self.aspp_block3 = nn.Sequential( - nn.Conv2d( - in_dims, out_dims, 3, stride=1, padding=rate[2], dilation=rate[2] - ), - nn.ReLU(inplace=True), - nn.BatchNorm2d(out_dims), - ) - - self.output = nn.Conv2d(len(rate) * out_dims, out_dims, 1) - self._init_weights() - - def forward(self, x): - x1 = self.aspp_block1(x) - x2 = self.aspp_block2(x) - x3 = self.aspp_block3(x) - out = torch.cat([x1, x2, x3], dim=1) - return self.output(out) - - def _init_weights(self): - for m in self.modules(): - if isinstance(m, nn.Conv2d): - nn.init.kaiming_normal_(m.weight) - elif isinstance(m, nn.BatchNorm2d): - m.weight.data.fill_(1) - m.bias.data.zero_() - - -class Upsample_(nn.Module): - def __init__(self, scale=2): - super(Upsample_, self).__init__() - - self.upsample = nn.Upsample(mode="bilinear", scale_factor=scale) - - def forward(self, x): - return self.upsample(x) - - -class AttentionBlock(nn.Module): - def __init__(self, input_encoder, input_decoder, output_dim): - super(AttentionBlock, self).__init__() - - self.conv_encoder = nn.Sequential( - nn.BatchNorm2d(input_encoder), - nn.ReLU(), - nn.Conv2d(input_encoder, output_dim, 3, padding=1), - nn.MaxPool2d(2, 2), - ) - - self.conv_decoder = nn.Sequential( - nn.BatchNorm2d(input_decoder), - nn.ReLU(), - nn.Conv2d(input_decoder, output_dim, 3, padding=1), - ) - - self.conv_attn = nn.Sequential( - nn.BatchNorm2d(output_dim), - nn.ReLU(), - nn.Conv2d(output_dim, 1, 1), - ) - - def forward(self, x1, x2): - out = self.conv_encoder(x1) + self.conv_decoder(x2) - out = self.conv_attn(out) - return out * x2 \ No newline at end of file diff --git a/src/audio2pose_models/res_unet.py b/src/audio2pose_models/res_unet.py deleted file mode 100644 index 4d69a87..0000000 --- a/src/audio2pose_models/res_unet.py +++ /dev/null @@ -1,65 +0,0 @@ -import torch -import torch.nn as nn -from custom_nodes.ComfyUI_Lam.src.audio2pose_models.networks import ResidualConv, Upsample - - -class ResUnet(nn.Module): - def __init__(self, channel=1, filters=[32, 64, 128, 256]): - super(ResUnet, self).__init__() - - self.input_layer = nn.Sequential( - nn.Conv2d(channel, filters[0], kernel_size=3, padding=1), - nn.BatchNorm2d(filters[0]), - nn.ReLU(), - nn.Conv2d(filters[0], filters[0], kernel_size=3, padding=1), - ) - self.input_skip = nn.Sequential( - nn.Conv2d(channel, filters[0], kernel_size=3, padding=1) - ) - - self.residual_conv_1 = ResidualConv(filters[0], filters[1], stride=(2,1), padding=1) - self.residual_conv_2 = ResidualConv(filters[1], filters[2], stride=(2,1), padding=1) - - self.bridge = ResidualConv(filters[2], filters[3], stride=(2,1), padding=1) - - self.upsample_1 = Upsample(filters[3], filters[3], kernel=(2,1), stride=(2,1)) - self.up_residual_conv1 = ResidualConv(filters[3] + filters[2], filters[2], stride=1, padding=1) - - self.upsample_2 = Upsample(filters[2], filters[2], kernel=(2,1), stride=(2,1)) - self.up_residual_conv2 = ResidualConv(filters[2] + filters[1], filters[1], stride=1, padding=1) - - self.upsample_3 = Upsample(filters[1], filters[1], kernel=(2,1), stride=(2,1)) - self.up_residual_conv3 = ResidualConv(filters[1] + filters[0], filters[0], stride=1, padding=1) - - self.output_layer = nn.Sequential( - nn.Conv2d(filters[0], 1, 1, 1), - nn.Sigmoid(), - ) - - def forward(self, x): - # Encode - x1 = self.input_layer(x) + self.input_skip(x) - x2 = self.residual_conv_1(x1) - x3 = self.residual_conv_2(x2) - # Bridge - x4 = self.bridge(x3) - - # Decode - x4 = self.upsample_1(x4) - x5 = torch.cat([x4, x3], dim=1) - - x6 = self.up_residual_conv1(x5) - - x6 = self.upsample_2(x6) - x7 = torch.cat([x6, x2], dim=1) - - x8 = self.up_residual_conv2(x7) - - x8 = self.upsample_3(x8) - x9 = torch.cat([x8, x1], dim=1) - - x10 = self.up_residual_conv3(x9) - - output = self.output_layer(x10) - - return output \ No newline at end of file diff --git a/src/config/auido2exp.yaml b/src/config/auido2exp.yaml deleted file mode 100644 index 7369dbf..0000000 --- a/src/config/auido2exp.yaml +++ /dev/null @@ -1,58 +0,0 @@ -DATASET: - TRAIN_FILE_LIST: /apdcephfs_cq2/share_1290939/wenxuazhang/code/file_list/train.txt - EVAL_FILE_LIST: /apdcephfs_cq2/share_1290939/wenxuazhang/code/file_list/val.txt - TRAIN_BATCH_SIZE: 32 - EVAL_BATCH_SIZE: 32 - EXP: True - EXP_DIM: 64 - FRAME_LEN: 32 - COEFF_LEN: 73 - NUM_CLASSES: 46 - AUDIO_ROOT_PATH: /apdcephfs_cq2/share_1290939/wenxuazhang/voxceleb1/wav - COEFF_ROOT_PATH: /apdcephfs_cq2/share_1290939/wenxuazhang/voxceleb1/wav2lip_3dmm - LMDB_PATH: /apdcephfs_cq2/share_1290939/shadowcun/datasets/VoxCeleb/v1/imdb - DEBUG: True - NUM_REPEATS: 2 - T: 40 - - -MODEL: - FRAMEWORK: V2 - AUDIOENCODER: - LEAKY_RELU: True - NORM: 'IN' - DISCRIMINATOR: - LEAKY_RELU: False - INPUT_CHANNELS: 6 - CVAE: - AUDIO_EMB_IN_SIZE: 512 - AUDIO_EMB_OUT_SIZE: 128 - SEQ_LEN: 32 - LATENT_SIZE: 256 - ENCODER_LAYER_SIZES: [192, 1024] - DECODER_LAYER_SIZES: [1024, 192] - - -TRAIN: - MAX_EPOCH: 300 - GENERATOR: - LR: 2.0e-5 - DISCRIMINATOR: - LR: 1.0e-5 - LOSS: - W_FEAT: 0 - W_COEFF_EXP: 2 - W_LM: 1.0e-2 - W_LM_MOUTH: 0 - W_REG: 0 - W_SYNC: 0 - W_COLOR: 0 - W_EXPRESSION: 0 - W_LIPREADING: 0.01 - W_LIPREADING_VV: 0 - W_EYE_BLINK: 4 - -TAG: - NAME: small_dataset - - diff --git a/src/config/auido2pose.yaml b/src/config/auido2pose.yaml deleted file mode 100644 index bc61f94..0000000 --- a/src/config/auido2pose.yaml +++ /dev/null @@ -1,49 +0,0 @@ -DATASET: - TRAIN_FILE_LIST: /apdcephfs_cq2/share_1290939/wenxuazhang/code/audio2pose_unet_noAudio/dataset/train_33.txt - EVAL_FILE_LIST: /apdcephfs_cq2/share_1290939/wenxuazhang/code/audio2pose_unet_noAudio/dataset/val.txt - TRAIN_BATCH_SIZE: 64 - EVAL_BATCH_SIZE: 1 - EXP: True - EXP_DIM: 64 - FRAME_LEN: 32 - COEFF_LEN: 73 - NUM_CLASSES: 46 - AUDIO_ROOT_PATH: /apdcephfs_cq2/share_1290939/wenxuazhang/voxceleb1/wav - COEFF_ROOT_PATH: /apdcephfs_cq2/share_1290939/shadowcun/datasets/VoxCeleb/v1/imdb - DEBUG: True - - -MODEL: - AUDIOENCODER: - LEAKY_RELU: True - NORM: 'IN' - DISCRIMINATOR: - LEAKY_RELU: False - INPUT_CHANNELS: 6 - CVAE: - AUDIO_EMB_IN_SIZE: 512 - AUDIO_EMB_OUT_SIZE: 6 - SEQ_LEN: 32 - LATENT_SIZE: 64 - ENCODER_LAYER_SIZES: [192, 128] - DECODER_LAYER_SIZES: [128, 192] - - -TRAIN: - MAX_EPOCH: 150 - GENERATOR: - LR: 1.0e-4 - DISCRIMINATOR: - LR: 1.0e-4 - LOSS: - LAMBDA_REG: 1 - LAMBDA_LANDMARKS: 0 - LAMBDA_VERTICES: 0 - LAMBDA_GAN_MOTION: 0.7 - LAMBDA_GAN_COEFF: 0 - LAMBDA_KL: 1 - -TAG: - NAME: cvae_UNET_useAudio_usewav2lipAudioEncoder - - diff --git a/src/config/facerender.yaml b/src/config/facerender.yaml deleted file mode 100644 index 9494ef8..0000000 --- a/src/config/facerender.yaml +++ /dev/null @@ -1,45 +0,0 @@ -model_params: - common_params: - num_kp: 15 - image_channel: 3 - feature_channel: 32 - estimate_jacobian: False # True - kp_detector_params: - temperature: 0.1 - block_expansion: 32 - max_features: 1024 - scale_factor: 0.25 # 0.25 - num_blocks: 5 - reshape_channel: 16384 # 16384 = 1024 * 16 - reshape_depth: 16 - he_estimator_params: - block_expansion: 64 - max_features: 2048 - num_bins: 66 - generator_params: - block_expansion: 64 - max_features: 512 - num_down_blocks: 2 - reshape_channel: 32 - reshape_depth: 16 # 512 = 32 * 16 - num_resblocks: 6 - estimate_occlusion_map: True - dense_motion_params: - block_expansion: 32 - max_features: 1024 - num_blocks: 5 - reshape_depth: 16 - compress: 4 - discriminator_params: - scales: [1] - block_expansion: 32 - max_features: 512 - num_blocks: 4 - sn: True - mapping_params: - coeff_nc: 70 - descriptor_nc: 1024 - layer: 3 - num_kp: 15 - num_bins: 66 - diff --git a/src/config/facerender_still.yaml b/src/config/facerender_still.yaml deleted file mode 100644 index 6b4d66d..0000000 --- a/src/config/facerender_still.yaml +++ /dev/null @@ -1,45 +0,0 @@ -model_params: - common_params: - num_kp: 15 - image_channel: 3 - feature_channel: 32 - estimate_jacobian: False # True - kp_detector_params: - temperature: 0.1 - block_expansion: 32 - max_features: 1024 - scale_factor: 0.25 # 0.25 - num_blocks: 5 - reshape_channel: 16384 # 16384 = 1024 * 16 - reshape_depth: 16 - he_estimator_params: - block_expansion: 64 - max_features: 2048 - num_bins: 66 - generator_params: - block_expansion: 64 - max_features: 512 - num_down_blocks: 2 - reshape_channel: 32 - reshape_depth: 16 # 512 = 32 * 16 - num_resblocks: 6 - estimate_occlusion_map: True - dense_motion_params: - block_expansion: 32 - max_features: 1024 - num_blocks: 5 - reshape_depth: 16 - compress: 4 - discriminator_params: - scales: [1] - block_expansion: 32 - max_features: 512 - num_blocks: 4 - sn: True - mapping_params: - coeff_nc: 73 - descriptor_nc: 1024 - layer: 3 - num_kp: 15 - num_bins: 66 - diff --git a/src/config/similarity_Lm3D_all.mat b/src/config/similarity_Lm3D_all.mat deleted file mode 100644 index a0e23588302bc71fc899eef53ff06df5f4df4c1d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 994 zcmeZu4DoSvQZUssQ1EpO(M`+DN!3vZ$Vn_o%P-2cQV4Jk_w+L}(NS%&~3PuKo779cH1{h%U^yLS#qnH^OD(2`;s`d#kmpS_W zm6s{gG`5rt>^v@aE7lz3@v45m^M>N#7YBSI#AfiN8&0@*#&eF*g$$J&ev#IUg?f=n zC4$^*v{D#v{r-E`GoU zC1jDhroOOxw@$_0d%Yi*xr)dIb?$WJ-}1~haaU`v{42dIt`g0kb(-J0*DpCb|M%8c z-*pb1`ILO{a#g#LbL^5$R|3D3M~c)Q@d@s)eldTM&lM{_$M`2la!#27)9)YH@=-%;^`nm|^J6QFzx92ayIA*9Z-<1GO@vpg zZttb|e-nE)OjCO};c&<5WZowI`sHR`AA3Vm|4Qur78VyN<=`-I%ro}9gfXM|TK z799?I=6UDjmG8w7X6qAA?tT=R#&W!fFJ8s@5?j{4zdY|NcS(!(c|K9sKfK9H?;GD~ zm&+GyxYETEEcQBW`TaDhLQ;L1-0p6dYT;vlX9v8zuRh}%_oWNF6~AbhOnURHbjybX z-MJgsC-2-Pc=5Xa!pF?kZu!?9PmcGw@cgP$@*265!evwL>|ptq#=J}{dt>UEmg<7w z@7v9atk-_I_#|?cGL^mQ=1N7$-F8oxiuYf?u=QuwN{b}V6XrQb zYmQI4a6M4fbbqY#JLb5mUt4>g%5kQB-+cInXVmF$+grI~B{xcx$Q`kaRDQ-QzMyAU zgJ3SR+}m{rWcISHPU|(;_le6-{hsUL#8!()Ii+*8x4PC<^LTw0*b&HPw^sCM_OX`U zt4gqF<|Svr&XN;9*RYtbe7m8QC;9jr zji%(bgZHzVwLiRk*^>5Q<=qb-g|>Y!Nq&%HeC1nFMxNz?=id~{ZZ+<_WF!+^E2$SO OzH0{ae@62?6Bqz)3e6Az diff --git a/src/dain_model/__init__.py b/src/dain_model/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/dain_model/base_predictor.py b/src/dain_model/base_predictor.py deleted file mode 100644 index 4ef3c5c..0000000 --- a/src/dain_model/base_predictor.py +++ /dev/null @@ -1,66 +0,0 @@ -import os -import numpy as np -from PIL import Image -import paddle - - -class BasePredictor(object): - def __init__(self): - pass - - def build_inference_model(self): - if paddle.in_dynamic_mode(): - # todo self.model = build_model(self.cfg) - pass - else: - place = paddle.get_device() - self.exe = paddle.static.Executor(place) - file_names = os.listdir(self.weight_path) - for file_name in file_names: - if file_name.find('model') > -1: - model_file = file_name - elif file_name.find('param') > -1: - param_file = file_name - - self.program, self.feed_names, self.fetch_targets = paddle.static.load_inference_model( - self.weight_path, - executor=self.exe, - model_filename=model_file, - params_filename=param_file) - - def base_forward(self, inputs): - if paddle.in_dynamic_mode(): - out = self.model(inputs) - else: - feed_dict = {} - if isinstance(inputs, dict): - feed_dict = inputs - elif isinstance(inputs, (list, tuple)): - for i, feed_name in enumerate(self.feed_names): - feed_dict[feed_name] = inputs[i] - else: - feed_dict[self.feed_names[0]] = inputs - - out = self.exe.run(self.program, - fetch_list=self.fetch_targets, - feed=feed_dict) - - return out - - def is_image(self, input): - try: - if isinstance(input, (np.ndarray, Image.Image)): - return True - elif isinstance(input, str): - if not os.path.isfile(input): - raise ValueError('input must be a file') - img = Image.open(input) - _ = img.size - return True - else: - return False - except: - return False - - def run(self): - raise NotImplementedError diff --git a/src/dain_model/dain_predictor.py b/src/dain_model/dain_predictor.py deleted file mode 100644 index a438f51..0000000 --- a/src/dain_model/dain_predictor.py +++ /dev/null @@ -1,312 +0,0 @@ -import os, sys -import cv2 -import glob -import shutil -import numpy as np -from tqdm import tqdm -from imageio import imread, imsave - -from custom_nodes.ComfyUI_Lam.src.dain_model.base_predictor import BasePredictor - -DAIN_WEIGHT_URL = 'https://paddlegan.bj.bcebos.com/applications/DAIN_weight.tar' - - -def video2frames(video_path, outpath, **kargs): - def _dict2str(kargs): - cmd_str = '' - for k, v in kargs.items(): - cmd_str += (' ' + str(k) + ' ' + str(v)) - return cmd_str - - ffmpeg = ['ffmpeg ', ' -y -loglevel ', ' error '] - vid_name = os.path.basename(video_path).split('.')[0] - out_full_path = os.path.join(outpath, vid_name) - - if not os.path.exists(out_full_path): - os.makedirs(out_full_path) - - # video file name - outformat = os.path.join(out_full_path, '%08d.png') - - cmd = ffmpeg + [' -i ', video_path, ' -start_number ', ' 0 ', outformat] - - cmd = ''.join(cmd) + _dict2str(kargs) - - if os.system(cmd) != 0: - raise RuntimeError('ffmpeg process video: {} error'.format(vid_name)) - - sys.stdout.flush() - return out_full_path - - -def frames2video(frame_path, video_path, r, w, h): - out = cv2.VideoWriter(video_path, cv2.VideoWriter_fourcc(*'DIVX'), int(r), (w, h)) - filelist = sorted(glob.glob(os.path.join(frame_path, '*.png'))) - for img_path in filelist: - img = cv2.imread(img_path) - out.write(img) - out.release() - - -class DAINPredictor(BasePredictor): - def __init__(self, - output='output', - weight_path=None, - time_step=None, - use_gpu=True, - remove_duplicates=False): - self.output_path = os.path.join(output, 'DAIN') - self.weight_path = weight_path - self.time_step = time_step - self.key_frame_thread = 0 - self.remove_duplicates = remove_duplicates - - self.build_inference_model() - - def run(self, video_path): - cmd='rm -rf {}/*'.format(self.output_path) - os.system(cmd) - frame_path_input = os.path.join(self.output_path, 'frames-input') - frame_path_interpolated = os.path.join(self.output_path, - 'frames-interpolated') - frame_path_combined = os.path.join(self.output_path, 'frames-combined') - video_path_output = os.path.join(self.output_path, 'videos-output') - - if not os.path.exists(self.output_path): - os.makedirs(self.output_path) - if not os.path.exists(frame_path_input): - os.makedirs(frame_path_input) - if not os.path.exists(frame_path_interpolated): - os.makedirs(frame_path_interpolated) - if not os.path.exists(frame_path_combined): - os.makedirs(frame_path_combined) - if not os.path.exists(video_path_output): - os.makedirs(video_path_output) - - timestep = self.time_step - num_frames = int(1.0 / timestep) - 1 - - cap = cv2.VideoCapture(video_path) - fps = cap.get(cv2.CAP_PROP_FPS) - print("Old fps (frame rate): ", fps) - - times_interp = int(1.0 / timestep) - r2 = str(int(fps) * times_interp) - print("New fps (frame rate): ", r2) - - out_path = video2frames(video_path, frame_path_input) - - vidname = os.path.basename(video_path).split('.')[0] - - frames = sorted(glob.glob(os.path.join(out_path, '*.png'))) - - if self.remove_duplicates: - frames = self.remove_duplicate_frames(out_path) - - img = imread(frames[0]) - - int_width = img.shape[1] - int_height = img.shape[0] - channel = img.shape[2] - if not channel == 3: - return - - if int_width != ((int_width >> 7) << 7): - int_width_pad = (((int_width >> 7) + 1) << 7) # more than necessary - padding_left = int((int_width_pad - int_width) / 2) - padding_right = int_width_pad - int_width - padding_left - else: - padding_left = 32 - padding_right = 32 - - if int_height != ((int_height >> 7) << 7): - int_height_pad = ( - ((int_height >> 7) + 1) << 7) # more than necessary - padding_top = int((int_height_pad - int_height) / 2) - padding_bottom = int_height_pad - int_height - padding_top - else: - padding_top = 32 - padding_bottom = 32 - - frame_num = len(frames) - - if not os.path.exists(os.path.join(frame_path_interpolated, vidname)): - os.makedirs(os.path.join(frame_path_interpolated, vidname)) - if not os.path.exists(os.path.join(frame_path_combined, vidname)): - os.makedirs(os.path.join(frame_path_combined, vidname)) - - for i in tqdm(range(frame_num - 1)): - first = frames[i] - second = frames[i + 1] - first_index = int(first.split(os.sep)[-1].split('.')[-2]) - second_index = int(second.split(os.sep)[-1].split('.')[-2]) - - img_first = imread(first) - img_second = imread(second) - - X0 = img_first.astype('float32').transpose((2, 0, 1)) / 255 - X1 = img_second.astype('float32').transpose((2, 0, 1)) / 255 - - assert (X0.shape[1] == X1.shape[1]) - assert (X0.shape[2] == X1.shape[2]) - - X0 = np.pad(X0, ((0, 0), (padding_top, padding_bottom), \ - (padding_left, padding_right)), mode='edge') - X1 = np.pad(X1, ((0, 0), (padding_top, padding_bottom), \ - (padding_left, padding_right)), mode='edge') - - X0 = np.expand_dims(X0, axis=0) - X1 = np.expand_dims(X1, axis=0) - - X0 = np.expand_dims(X0, axis=0) - X1 = np.expand_dims(X1, axis=0) - - X = np.concatenate((X0, X1), axis=0) - - o = self.base_forward(X) - - y_ = o[0] - - y_ = [ - np.transpose( - 255.0 * item.clip( - 0, 1.0)[0, :, padding_top:padding_top + int_height, - padding_left:padding_left + int_width], - (1, 2, 0)) for item in y_ - ] - if self.remove_duplicates: - num_frames = times_interp * (second_index - first_index) - 1 - time_offsets = [ - kk * timestep for kk in range(1, 1 + num_frames, 1) - ] - start = times_interp * first_index + 1 - for item, time_offset in zip(y_, time_offsets): - out_dir = os.path.join(frame_path_interpolated, vidname, - "{:08d}.png".format(start)) - imsave(out_dir, np.round(item).astype(np.uint8)) - start = start + 1 - - else: - time_offsets = [ - kk * timestep for kk in range(1, 1 + num_frames, 1) - ] - - count = 1 - for item, time_offset in zip(y_, time_offsets): - out_dir = os.path.join( - frame_path_interpolated, vidname, - "{:0>6d}_{:0>4d}.png".format(i, count)) - count = count + 1 - imsave(out_dir, np.round(item).astype(np.uint8)) - - input_dir = os.path.join(frame_path_input, vidname) - interpolated_dir = os.path.join(frame_path_interpolated, vidname) - combined_dir = os.path.join(frame_path_combined, vidname) - - if self.remove_duplicates: - self.combine_frames_with_rm(input_dir, interpolated_dir, - combined_dir, times_interp) - - else: - num_frames = int(1.0 / timestep) - 1 - self.combine_frames(input_dir, interpolated_dir, combined_dir, - num_frames) - - frame_pattern_combined = os.path.join(frame_path_combined, vidname) - # video_pattern_output = os.path.join(video_path_output, vidname + '.mp4') - video_pattern_output = os.path.join(video_path_output, vidname + '.avi') - if os.path.exists(video_pattern_output): - os.remove(video_pattern_output) - frames2video(frame_pattern_combined, video_pattern_output, r2, int_width, int_height) - - return frame_pattern_combined, video_pattern_output - - def combine_frames(self, input, interpolated, combined, num_frames): - frames1 = sorted(glob.glob(os.path.join(input, '*.png'))) - frames2 = sorted(glob.glob(os.path.join(interpolated, '*.png'))) - num1 = len(frames1) - num2 = len(frames2) - - for i in range(num1): - src = frames1[i] - imgname = int(src.split(os.sep)[-1].split('.')[-2]) - assert i == imgname - dst = os.path.join(combined, - '{:08d}.png'.format(i * (num_frames + 1))) - shutil.copy2(src, dst) - if i < num1 - 1: - try: - for k in range(num_frames): - src = frames2[i * num_frames + k] - dst = os.path.join( - combined, - '{:08d}.png'.format(i * (num_frames + 1) + k + 1)) - shutil.copy2(src, dst) - except Exception as e: - print(e) - - def combine_frames_with_rm(self, input, interpolated, combined, - times_interp): - frames1 = sorted(glob.glob(os.path.join(input, '*.png'))) - frames2 = sorted(glob.glob(os.path.join(interpolated, '*.png'))) - num1 = len(frames1) - num2 = len(frames2) - - for i in range(num1): - src = frames1[i] - index = int(src.split(os.sep)[-1].split('.')[-2]) - dst = os.path.join(combined, - '{:08d}.png'.format(times_interp * index)) - shutil.copy2(src, dst) - - for i in range(num2): - src = frames2[i] - imgname = src.split(os.sep)[-1] - dst = os.path.join(combined, imgname) - shutil.copy2(src, dst) - - def remove_duplicate_frames(self, paths): - def dhash(image, hash_size=8): - gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) - resized = cv2.resize(gray, (hash_size + 1, hash_size)) - diff = resized[:, 1:] > resized[:, :-1] - return sum([2 ** i for (i, v) in enumerate(diff.flatten()) if v]) - - hashes = {} - max_interp = 9 - image_paths = sorted(glob.glob(os.path.join(paths, '*.png'))) - for image_path in image_paths: - image = cv2.imread(image_path) - h = dhash(image) - p = hashes.get(h, []) - p.append(image_path) - hashes[h] = p - - for (h, hashed_paths) in hashes.items(): - if len(hashed_paths) > 1: - first_index = int(hashed_paths[0].split( - os.sep)[-1].split('.')[-2]) - last_index = int(hashed_paths[-1].split( - os.sep)[-1].split('.')[-2]) + 1 - gap = 2 * (last_index - first_index) - 1 - if gap > 2 * max_interp: - cut1 = len(hashed_paths) // 3 - cut2 = cut1 * 2 - for p in hashed_paths[1:cut1 - 1]: - os.remove(p) - for p in hashed_paths[cut1 + 1:cut2]: - os.remove(p) - for p in hashed_paths[cut2 + 1:]: - os.remove(p) - if gap > max_interp: - mid = len(hashed_paths) // 2 - for p in hashed_paths[1:mid - 1]: - os.remove(p) - for p in hashed_paths[mid + 1:]: - os.remove(p) - else: - for p in hashed_paths[1:]: - os.remove(p) - - frames = sorted(glob.glob(os.path.join(paths, '*.png'))) - return frames diff --git a/src/face3d/data/__init__.py b/src/face3d/data/__init__.py deleted file mode 100644 index 9a9761c..0000000 --- a/src/face3d/data/__init__.py +++ /dev/null @@ -1,116 +0,0 @@ -"""This package includes all the modules related to data loading and preprocessing - - To add a custom dataset class called 'dummy', you need to add a file called 'dummy_dataset.py' and define a subclass 'DummyDataset' inherited from BaseDataset. - You need to implement four functions: - -- <__init__>: initialize the class, first call BaseDataset.__init__(self, opt). - -- <__len__>: return the size of dataset. - -- <__getitem__>: get a data point from data loader. - -- : (optionally) add dataset-specific options and set default options. - -Now you can use the dataset class by specifying flag '--dataset_mode dummy'. -See our template dataset class 'template_dataset.py' for more details. -""" -import numpy as np -import importlib -import torch.utils.data -from face3d.data.base_dataset import BaseDataset - - -def find_dataset_using_name(dataset_name): - """Import the module "data/[dataset_name]_dataset.py". - - In the file, the class called DatasetNameDataset() will - be instantiated. It has to be a subclass of BaseDataset, - and it is case-insensitive. - """ - dataset_filename = "data." + dataset_name + "_dataset" - datasetlib = importlib.import_module(dataset_filename) - - dataset = None - target_dataset_name = dataset_name.replace('_', '') + 'dataset' - for name, cls in datasetlib.__dict__.items(): - if name.lower() == target_dataset_name.lower() \ - and issubclass(cls, BaseDataset): - dataset = cls - - if dataset is None: - raise NotImplementedError("In %s.py, there should be a subclass of BaseDataset with class name that matches %s in lowercase." % (dataset_filename, target_dataset_name)) - - return dataset - - -def get_option_setter(dataset_name): - """Return the static method of the dataset class.""" - dataset_class = find_dataset_using_name(dataset_name) - return dataset_class.modify_commandline_options - - -def create_dataset(opt, rank=0): - """Create a dataset given the option. - - This function wraps the class CustomDatasetDataLoader. - This is the main interface between this package and 'train.py'/'test.py' - - Example: - >>> from data import create_dataset - >>> dataset = create_dataset(opt) - """ - data_loader = CustomDatasetDataLoader(opt, rank=rank) - dataset = data_loader.load_data() - return dataset - -class CustomDatasetDataLoader(): - """Wrapper class of Dataset class that performs multi-threaded data loading""" - - def __init__(self, opt, rank=0): - """Initialize this class - - Step 1: create a dataset instance given the name [dataset_mode] - Step 2: create a multi-threaded data loader. - """ - self.opt = opt - dataset_class = find_dataset_using_name(opt.dataset_mode) - self.dataset = dataset_class(opt) - self.sampler = None - print("rank %d %s dataset [%s] was created" % (rank, self.dataset.name, type(self.dataset).__name__)) - if opt.use_ddp and opt.isTrain: - world_size = opt.world_size - self.sampler = torch.utils.data.distributed.DistributedSampler( - self.dataset, - num_replicas=world_size, - rank=rank, - shuffle=not opt.serial_batches - ) - self.dataloader = torch.utils.data.DataLoader( - self.dataset, - sampler=self.sampler, - num_workers=int(opt.num_threads / world_size), - batch_size=int(opt.batch_size / world_size), - drop_last=True) - else: - self.dataloader = torch.utils.data.DataLoader( - self.dataset, - batch_size=opt.batch_size, - shuffle=(not opt.serial_batches) and opt.isTrain, - num_workers=int(opt.num_threads), - drop_last=True - ) - - def set_epoch(self, epoch): - self.dataset.current_epoch = epoch - if self.sampler is not None: - self.sampler.set_epoch(epoch) - - def load_data(self): - return self - - def __len__(self): - """Return the number of data in the dataset""" - return min(len(self.dataset), self.opt.max_dataset_size) - - def __iter__(self): - """Return a batch of data""" - for i, data in enumerate(self.dataloader): - if i * self.opt.batch_size >= self.opt.max_dataset_size: - break - yield data diff --git a/src/face3d/data/base_dataset.py b/src/face3d/data/base_dataset.py deleted file mode 100644 index 1bd57d0..0000000 --- a/src/face3d/data/base_dataset.py +++ /dev/null @@ -1,125 +0,0 @@ -"""This module implements an abstract base class (ABC) 'BaseDataset' for datasets. - -It also includes common transformation functions (e.g., get_transform, __scale_width), which can be later used in subclasses. -""" -import random -import numpy as np -import torch.utils.data as data -from PIL import Image -import torchvision.transforms as transforms -from abc import ABC, abstractmethod - - -class BaseDataset(data.Dataset, ABC): - """This class is an abstract base class (ABC) for datasets. - - To create a subclass, you need to implement the following four functions: - -- <__init__>: initialize the class, first call BaseDataset.__init__(self, opt). - -- <__len__>: return the size of dataset. - -- <__getitem__>: get a data point. - -- : (optionally) add dataset-specific options and set default options. - """ - - def __init__(self, opt): - """Initialize the class; save the options in the class - - Parameters: - opt (Option class)-- stores all the experiment flags; needs to be a subclass of BaseOptions - """ - self.opt = opt - # self.root = opt.dataroot - self.current_epoch = 0 - - @staticmethod - def modify_commandline_options(parser, is_train): - """Add new dataset-specific options, and rewrite default values for existing options. - - Parameters: - parser -- original option parser - is_train (bool) -- whether training phase or test phase. You can use this flag to add training-specific or test-specific options. - - Returns: - the modified parser. - """ - return parser - - @abstractmethod - def __len__(self): - """Return the total number of images in the dataset.""" - return 0 - - @abstractmethod - def __getitem__(self, index): - """Return a data point and its metadata information. - - Parameters: - index - - a random integer for data indexing - - Returns: - a dictionary of data with their names. It ususally contains the data itself and its metadata information. - """ - pass - - -def get_transform(grayscale=False): - transform_list = [] - if grayscale: - transform_list.append(transforms.Grayscale(1)) - transform_list += [transforms.ToTensor()] - return transforms.Compose(transform_list) - -def get_affine_mat(opt, size): - shift_x, shift_y, scale, rot_angle, flip = 0., 0., 1., 0., False - w, h = size - - if 'shift' in opt.preprocess: - shift_pixs = int(opt.shift_pixs) - shift_x = random.randint(-shift_pixs, shift_pixs) - shift_y = random.randint(-shift_pixs, shift_pixs) - if 'scale' in opt.preprocess: - scale = 1 + opt.scale_delta * (2 * random.random() - 1) - if 'rot' in opt.preprocess: - rot_angle = opt.rot_angle * (2 * random.random() - 1) - rot_rad = -rot_angle * np.pi/180 - if 'flip' in opt.preprocess: - flip = random.random() > 0.5 - - shift_to_origin = np.array([1, 0, -w//2, 0, 1, -h//2, 0, 0, 1]).reshape([3, 3]) - flip_mat = np.array([-1 if flip else 1, 0, 0, 0, 1, 0, 0, 0, 1]).reshape([3, 3]) - shift_mat = np.array([1, 0, shift_x, 0, 1, shift_y, 0, 0, 1]).reshape([3, 3]) - rot_mat = np.array([np.cos(rot_rad), np.sin(rot_rad), 0, -np.sin(rot_rad), np.cos(rot_rad), 0, 0, 0, 1]).reshape([3, 3]) - scale_mat = np.array([scale, 0, 0, 0, scale, 0, 0, 0, 1]).reshape([3, 3]) - shift_to_center = np.array([1, 0, w//2, 0, 1, h//2, 0, 0, 1]).reshape([3, 3]) - - affine = shift_to_center @ scale_mat @ rot_mat @ shift_mat @ flip_mat @ shift_to_origin - affine_inv = np.linalg.inv(affine) - return affine, affine_inv, flip - -def apply_img_affine(img, affine_inv, method=Image.BICUBIC): - return img.transform(img.size, Image.AFFINE, data=affine_inv.flatten()[:6], resample=Image.BICUBIC) - -def apply_lm_affine(landmark, affine, flip, size): - _, h = size - lm = landmark.copy() - lm[:, 1] = h - 1 - lm[:, 1] - lm = np.concatenate((lm, np.ones([lm.shape[0], 1])), -1) - lm = lm @ np.transpose(affine) - lm[:, :2] = lm[:, :2] / lm[:, 2:] - lm = lm[:, :2] - lm[:, 1] = h - 1 - lm[:, 1] - if flip: - lm_ = lm.copy() - lm_[:17] = lm[16::-1] - lm_[17:22] = lm[26:21:-1] - lm_[22:27] = lm[21:16:-1] - lm_[31:36] = lm[35:30:-1] - lm_[36:40] = lm[45:41:-1] - lm_[40:42] = lm[47:45:-1] - lm_[42:46] = lm[39:35:-1] - lm_[46:48] = lm[41:39:-1] - lm_[48:55] = lm[54:47:-1] - lm_[55:60] = lm[59:54:-1] - lm_[60:65] = lm[64:59:-1] - lm_[65:68] = lm[67:64:-1] - lm = lm_ - return lm diff --git a/src/face3d/data/flist_dataset.py b/src/face3d/data/flist_dataset.py deleted file mode 100644 index c0b6945..0000000 --- a/src/face3d/data/flist_dataset.py +++ /dev/null @@ -1,125 +0,0 @@ -"""This script defines the custom dataset for Deep3DFaceRecon_pytorch -""" - -import os.path -from data.base_dataset import BaseDataset, get_transform, get_affine_mat, apply_img_affine, apply_lm_affine -from data.image_folder import make_dataset -from PIL import Image -import random -import util.util as util -import numpy as np -import json -import torch -from scipy.io import loadmat, savemat -import pickle -from util.preprocess import align_img, estimate_norm -from util.load_mats import load_lm3d - - -def default_flist_reader(flist): - """ - flist format: impath label\nimpath label\n ...(same to caffe's filelist) - """ - imlist = [] - with open(flist, 'r') as rf: - for line in rf.readlines(): - impath = line.strip() - imlist.append(impath) - - return imlist - -def jason_flist_reader(flist): - with open(flist, 'r') as fp: - info = json.load(fp) - return info - -def parse_label(label): - return torch.tensor(np.array(label).astype(np.float32)) - - -class FlistDataset(BaseDataset): - """ - It requires one directories to host training images '/path/to/data/train' - You can train the model with the dataset flag '--dataroot /path/to/data'. - """ - - def __init__(self, opt): - """Initialize this dataset class. - - Parameters: - opt (Option class) -- stores all the experiment flags; needs to be a subclass of BaseOptions - """ - BaseDataset.__init__(self, opt) - - self.lm3d_std = load_lm3d(opt.bfm_folder) - - msk_names = default_flist_reader(opt.flist) - self.msk_paths = [os.path.join(opt.data_root, i) for i in msk_names] - - self.size = len(self.msk_paths) - self.opt = opt - - self.name = 'train' if opt.isTrain else 'val' - if '_' in opt.flist: - self.name += '_' + opt.flist.split(os.sep)[-1].split('_')[0] - - - def __getitem__(self, index): - """Return a data point and its metadata information. - - Parameters: - index (int) -- a random integer for data indexing - - Returns a dictionary that contains A, B, A_paths and B_paths - img (tensor) -- an image in the input domain - msk (tensor) -- its corresponding attention mask - lm (tensor) -- its corresponding 3d landmarks - im_paths (str) -- image paths - aug_flag (bool) -- a flag used to tell whether its raw or augmented - """ - msk_path = self.msk_paths[index % self.size] # make sure index is within then range - img_path = msk_path.replace('mask/', '') - lm_path = '.'.join(msk_path.replace('mask', 'landmarks').split('.')[:-1]) + '.txt' - - raw_img = Image.open(img_path).convert('RGB') - raw_msk = Image.open(msk_path).convert('RGB') - raw_lm = np.loadtxt(lm_path).astype(np.float32) - - _, img, lm, msk = align_img(raw_img, raw_lm, self.lm3d_std, raw_msk) - - aug_flag = self.opt.use_aug and self.opt.isTrain - if aug_flag: - img, lm, msk = self._augmentation(img, lm, self.opt, msk) - - _, H = img.size - M = estimate_norm(lm, H) - transform = get_transform() - img_tensor = transform(img) - msk_tensor = transform(msk)[:1, ...] - lm_tensor = parse_label(lm) - M_tensor = parse_label(M) - - - return {'imgs': img_tensor, - 'lms': lm_tensor, - 'msks': msk_tensor, - 'M': M_tensor, - 'im_paths': img_path, - 'aug_flag': aug_flag, - 'dataset': self.name} - - def _augmentation(self, img, lm, opt, msk=None): - affine, affine_inv, flip = get_affine_mat(opt, img.size) - img = apply_img_affine(img, affine_inv) - lm = apply_lm_affine(lm, affine, flip, img.size) - if msk is not None: - msk = apply_img_affine(msk, affine_inv, method=Image.BILINEAR) - return img, lm, msk - - - - - def __len__(self): - """Return the total number of images in the dataset. - """ - return self.size diff --git a/src/face3d/data/image_folder.py b/src/face3d/data/image_folder.py deleted file mode 100644 index efadc2e..0000000 --- a/src/face3d/data/image_folder.py +++ /dev/null @@ -1,66 +0,0 @@ -"""A modified image folder class - -We modify the official PyTorch image folder (https://github.com/pytorch/vision/blob/master/torchvision/datasets/folder.py) -so that this class can load images from both current directory and its subdirectories. -""" -import numpy as np -import torch.utils.data as data - -from PIL import Image -import os -import os.path - -IMG_EXTENSIONS = [ - '.jpg', '.JPG', '.jpeg', '.JPEG', - '.png', '.PNG', '.ppm', '.PPM', '.bmp', '.BMP', - '.tif', '.TIF', '.tiff', '.TIFF', -] - - -def is_image_file(filename): - return any(filename.endswith(extension) for extension in IMG_EXTENSIONS) - - -def make_dataset(dir, max_dataset_size=float("inf")): - images = [] - assert os.path.isdir(dir) or os.path.islink(dir), '%s is not a valid directory' % dir - - for root, _, fnames in sorted(os.walk(dir, followlinks=True)): - for fname in fnames: - if is_image_file(fname): - path = os.path.join(root, fname) - images.append(path) - return images[:min(max_dataset_size, len(images))] - - -def default_loader(path): - return Image.open(path).convert('RGB') - - -class ImageFolder(data.Dataset): - - def __init__(self, root, transform=None, return_paths=False, - loader=default_loader): - imgs = make_dataset(root) - if len(imgs) == 0: - raise(RuntimeError("Found 0 images in: " + root + "\n" - "Supported image extensions are: " + ",".join(IMG_EXTENSIONS))) - - self.root = root - self.imgs = imgs - self.transform = transform - self.return_paths = return_paths - self.loader = loader - - def __getitem__(self, index): - path = self.imgs[index] - img = self.loader(path) - if self.transform is not None: - img = self.transform(img) - if self.return_paths: - return img, path - else: - return img - - def __len__(self): - return len(self.imgs) diff --git a/src/face3d/data/template_dataset.py b/src/face3d/data/template_dataset.py deleted file mode 100644 index bfdf16b..0000000 --- a/src/face3d/data/template_dataset.py +++ /dev/null @@ -1,75 +0,0 @@ -"""Dataset class template - -This module provides a template for users to implement custom datasets. -You can specify '--dataset_mode template' to use this dataset. -The class name should be consistent with both the filename and its dataset_mode option. -The filename should be _dataset.py -The class name should be Dataset.py -You need to implement the following functions: - -- : Add dataset-specific options and rewrite default values for existing options. - -- <__init__>: Initialize this dataset class. - -- <__getitem__>: Return a data point and its metadata information. - -- <__len__>: Return the number of images. -""" -from data.base_dataset import BaseDataset, get_transform -# from data.image_folder import make_dataset -# from PIL import Image - - -class TemplateDataset(BaseDataset): - """A template dataset class for you to implement custom datasets.""" - @staticmethod - def modify_commandline_options(parser, is_train): - """Add new dataset-specific options, and rewrite default values for existing options. - - Parameters: - parser -- original option parser - is_train (bool) -- whether training phase or test phase. You can use this flag to add training-specific or test-specific options. - - Returns: - the modified parser. - """ - parser.add_argument('--new_dataset_option', type=float, default=1.0, help='new dataset option') - parser.set_defaults(max_dataset_size=10, new_dataset_option=2.0) # specify dataset-specific default values - return parser - - def __init__(self, opt): - """Initialize this dataset class. - - Parameters: - opt (Option class) -- stores all the experiment flags; needs to be a subclass of BaseOptions - - A few things can be done here. - - save the options (have been done in BaseDataset) - - get image paths and meta information of the dataset. - - define the image transformation. - """ - # save the option and dataset root - BaseDataset.__init__(self, opt) - # get the image paths of your dataset; - self.image_paths = [] # You can call sorted(make_dataset(self.root, opt.max_dataset_size)) to get all the image paths under the directory self.root - # define the default transform function. You can use ; You can also define your custom transform function - self.transform = get_transform(opt) - - def __getitem__(self, index): - """Return a data point and its metadata information. - - Parameters: - index -- a random integer for data indexing - - Returns: - a dictionary of data with their names. It usually contains the data itself and its metadata information. - - Step 1: get a random image path: e.g., path = self.image_paths[index] - Step 2: load your data from the disk: e.g., image = Image.open(path).convert('RGB'). - Step 3: convert your data to a PyTorch tensor. You can use helpder functions such as self.transform. e.g., data = self.transform(image) - Step 4: return a data point as a dictionary. - """ - path = 'temp' # needs to be a string - data_A = None # needs to be a tensor - data_B = None # needs to be a tensor - return {'data_A': data_A, 'data_B': data_B, 'path': path} - - def __len__(self): - """Return the total number of images.""" - return len(self.image_paths) diff --git a/src/face3d/extract_kp_videos.py b/src/face3d/extract_kp_videos.py deleted file mode 100644 index 21616a3..0000000 --- a/src/face3d/extract_kp_videos.py +++ /dev/null @@ -1,108 +0,0 @@ -import os -import cv2 -import time -import glob -import argparse -import face_alignment -import numpy as np -from PIL import Image -from tqdm import tqdm -from itertools import cycle - -from torch.multiprocessing import Pool, Process, set_start_method - -class KeypointExtractor(): - def __init__(self, device): - self.detector = face_alignment.FaceAlignment(face_alignment.LandmarksType._2D, - device=device) - - def extract_keypoint(self, images, name=None, info=True): - if isinstance(images, list): - keypoints = [] - if info: - i_range = tqdm(images,desc='landmark Det:') - else: - i_range = images - - for image in i_range: - current_kp = self.extract_keypoint(image) - if np.mean(current_kp) == -1 and keypoints: - keypoints.append(keypoints[-1]) - else: - keypoints.append(current_kp[None]) - - keypoints = np.concatenate(keypoints, 0) - np.savetxt(os.path.splitext(name)[0]+'.txt', keypoints.reshape(-1)) - return keypoints - else: - while True: - try: - keypoints = self.detector.get_landmarks_from_image(np.array(images))[0] - break - except RuntimeError as e: - if str(e).startswith('CUDA'): - print("Warning: out of memory, sleep for 1s") - time.sleep(1) - else: - print(e) - break - except TypeError: - print('No face detected in this image') - shape = [68, 2] - keypoints = -1. * np.ones(shape) - break - if name is not None: - np.savetxt(os.path.splitext(name)[0]+'.txt', keypoints.reshape(-1)) - return keypoints - -def read_video(filename): - frames = [] - cap = cv2.VideoCapture(filename) - while cap.isOpened(): - ret, frame = cap.read() - if ret: - frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) - frame = Image.fromarray(frame) - frames.append(frame) - else: - break - cap.release() - return frames - -def run(data): - filename, opt, device = data - os.environ['CUDA_VISIBLE_DEVICES'] = device - kp_extractor = KeypointExtractor() - images = read_video(filename) - name = filename.split('/')[-2:] - os.makedirs(os.path.join(opt.output_dir, name[-2]), exist_ok=True) - kp_extractor.extract_keypoint( - images, - name=os.path.join(opt.output_dir, name[-2], name[-1]) - ) - -if __name__ == '__main__': - set_start_method('spawn') - parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) - parser.add_argument('--input_dir', type=str, help='the folder of the input files') - parser.add_argument('--output_dir', type=str, help='the folder of the output files') - parser.add_argument('--device_ids', type=str, default='0,1') - parser.add_argument('--workers', type=int, default=4) - - opt = parser.parse_args() - filenames = list() - VIDEO_EXTENSIONS_LOWERCASE = {'mp4'} - VIDEO_EXTENSIONS = VIDEO_EXTENSIONS_LOWERCASE.union({f.upper() for f in VIDEO_EXTENSIONS_LOWERCASE}) - extensions = VIDEO_EXTENSIONS - - for ext in extensions: - os.listdir(f'{opt.input_dir}') - print(f'{opt.input_dir}/*.{ext}') - filenames = sorted(glob.glob(f'{opt.input_dir}/*.{ext}')) - print('Total number of videos:', len(filenames)) - pool = Pool(opt.workers) - args_list = cycle([opt]) - device_ids = opt.device_ids.split(",") - device_ids = cycle(device_ids) - for data in tqdm(pool.imap_unordered(run, zip(filenames, args_list, device_ids))): - None diff --git a/src/face3d/extract_kp_videos_safe.py b/src/face3d/extract_kp_videos_safe.py deleted file mode 100644 index 6a76b41..0000000 --- a/src/face3d/extract_kp_videos_safe.py +++ /dev/null @@ -1,126 +0,0 @@ -import os -import cv2 -import time -import glob -import argparse -import numpy as np -from PIL import Image -import torch -from tqdm import tqdm -from itertools import cycle -from facexlib.alignment import init_alignment_model, landmark_98_to_68 -from facexlib.detection import init_detection_model -from torch.multiprocessing import Pool, Process, set_start_method -import folder_paths - - -class KeypointExtractor(): - def __init__(self, device='cuda'): - root_path = 'gfpgan/weights' - self.detector = init_alignment_model('awing_fan',device=device, model_rootpath=root_path) - self.det_net = init_detection_model('retinaface_resnet50', half=False,device=device, model_rootpath=root_path) - - def extract_keypoint(self, images, name=None, info=True): - if isinstance(images, list): - keypoints = [] - if info: - i_range = tqdm(images,desc='landmark Det:') - else: - i_range = images - - for image in i_range: - current_kp = self.extract_keypoint(image) - # current_kp = self.detector.get_landmarks(np.array(image)) - if np.mean(current_kp) == -1 and keypoints: - keypoints.append(keypoints[-1]) - else: - keypoints.append(current_kp[None]) - - keypoints = np.concatenate(keypoints, 0) - np.savetxt(os.path.splitext(name)[0]+'.txt', keypoints.reshape(-1)) - return keypoints - else: - while True: - try: - with torch.no_grad(): - # face detection -> face alignment. - img = np.array(images) - bboxes = self.det_net.detect_faces(images, 0.97) - - bboxes = bboxes[0] - img = img[int(bboxes[1]):int(bboxes[3]), int(bboxes[0]):int(bboxes[2]), :] - - keypoints = landmark_98_to_68(self.detector.get_landmarks(img)) # [0] - - #### keypoints to the original location - keypoints[:,0] += int(bboxes[0]) - keypoints[:,1] += int(bboxes[1]) - - break - except RuntimeError as e: - if str(e).startswith('CUDA'): - print("Warning: out of memory, sleep for 1s") - time.sleep(1) - else: - print(e) - break - except TypeError: - print('No face detected in this image') - shape = [68, 2] - keypoints = -1. * np.ones(shape) - break - if name is not None: - np.savetxt(os.path.splitext(name)[0]+'.txt', keypoints.reshape(-1)) - return keypoints - -def read_video(filename): - frames = [] - cap = cv2.VideoCapture(filename) - while cap.isOpened(): - ret, frame = cap.read() - if ret: - frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) - frame = Image.fromarray(frame) - frames.append(frame) - else: - break - cap.release() - return frames - -def run(data): - filename, opt, device = data - os.environ['CUDA_VISIBLE_DEVICES'] = device - kp_extractor = KeypointExtractor() - images = read_video(filename) - name = filename.split('/')[-2:] - os.makedirs(os.path.join(opt.output_dir, name[-2]), exist_ok=True) - kp_extractor.extract_keypoint( - images, - name=os.path.join(opt.output_dir, name[-2], name[-1]) - ) - -if __name__ == '__main__': - set_start_method('spawn') - parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) - parser.add_argument('--input_dir', type=str, help='the folder of the input files') - parser.add_argument('--output_dir', type=str, help='the folder of the output files') - parser.add_argument('--device_ids', type=str, default='0,1') - parser.add_argument('--workers', type=int, default=4) - - opt = parser.parse_args() - filenames = list() - VIDEO_EXTENSIONS_LOWERCASE = {'mp4'} - VIDEO_EXTENSIONS = VIDEO_EXTENSIONS_LOWERCASE.union({f.upper() for f in VIDEO_EXTENSIONS_LOWERCASE}) - extensions = VIDEO_EXTENSIONS - - for ext in extensions: - os.listdir(f'{opt.input_dir}') - print(f'{opt.input_dir}/*.{ext}') - filenames = sorted(glob.glob(f'{opt.input_dir}/*.{ext}')) - print('Total number of videos:', len(filenames)) - pool = Pool(opt.workers) - args_list = cycle([opt]) - device_ids = opt.device_ids.split(",") - device_ids = cycle(device_ids) - for data in tqdm(pool.imap_unordered(run, zip(filenames, args_list, device_ids))): - None diff --git a/src/face3d/models/__init__.py b/src/face3d/models/__init__.py deleted file mode 100644 index 3604ba2..0000000 --- a/src/face3d/models/__init__.py +++ /dev/null @@ -1,67 +0,0 @@ -"""This package contains modules related to objective functions, optimizations, and network architectures. - -To add a custom model class called 'dummy', you need to add a file called 'dummy_model.py' and define a subclass DummyModel inherited from BaseModel. -You need to implement the following five functions: - -- <__init__>: initialize the class; first call BaseModel.__init__(self, opt). - -- : unpack data from dataset and apply preprocessing. - -- : produce intermediate results. - -- : calculate loss, gradients, and update network weights. - -- : (optionally) add model-specific options and set default options. - -In the function <__init__>, you need to define four lists: - -- self.loss_names (str list): specify the training losses that you want to plot and save. - -- self.model_names (str list): define networks used in our training. - -- self.visual_names (str list): specify the images that you want to display and save. - -- self.optimizers (optimizer list): define and initialize optimizers. You can define one optimizer for each network. If two networks are updated at the same time, you can use itertools.chain to group them. See cycle_gan_model.py for an usage. - -Now you can use the model class by specifying flag '--model dummy'. -See our template model class 'template_model.py' for more details. -""" - -import importlib -from custom_nodes.ComfyUI_Lam.src.face3d.models.base_model import BaseModel - - -def find_model_using_name(model_name): - """Import the module "models/[model_name]_model.py". - - In the file, the class called DatasetNameModel() will - be instantiated. It has to be a subclass of BaseModel, - and it is case-insensitive. - """ - model_filename = "face3d.models." + model_name + "_model" - modellib = importlib.import_module(model_filename) - model = None - target_model_name = model_name.replace('_', '') + 'model' - for name, cls in modellib.__dict__.items(): - if name.lower() == target_model_name.lower() \ - and issubclass(cls, BaseModel): - model = cls - - if model is None: - print("In %s.py, there should be a subclass of BaseModel with class name that matches %s in lowercase." % (model_filename, target_model_name)) - exit(0) - - return model - - -def get_option_setter(model_name): - """Return the static method of the model class.""" - model_class = find_model_using_name(model_name) - return model_class.modify_commandline_options - - -def create_model(opt): - """Create a model given the option. - - This function warps the class CustomDatasetDataLoader. - This is the main interface between this package and 'train.py'/'test.py' - - Example: - >>> from models import create_model - >>> model = create_model(opt) - """ - model = find_model_using_name(opt.model) - instance = model(opt) - print("model [%s] was created" % type(instance).__name__) - return instance diff --git a/src/face3d/models/arcface_torch/README.md b/src/face3d/models/arcface_torch/README.md deleted file mode 100644 index 2ee63a8..0000000 --- a/src/face3d/models/arcface_torch/README.md +++ /dev/null @@ -1,164 +0,0 @@ -# Distributed Arcface Training in Pytorch - -This is a deep learning library that makes face recognition efficient, and effective, which can train tens of millions -identity on a single server. - -## Requirements - -- Install [pytorch](http://pytorch.org) (torch>=1.6.0), our doc for [install.md](docs/install.md). -- `pip install -r requirements.txt`. -- Download the dataset - from [https://github.com/deepinsight/insightface/tree/master/recognition/_datasets_](https://github.com/deepinsight/insightface/tree/master/recognition/_datasets_) - . - -## How to Training - -To train a model, run `train.py` with the path to the configs: - -### 1. Single node, 8 GPUs: - -```shell -python -m torch.distributed.launch --nproc_per_node=8 --nnodes=1 --node_rank=0 --master_addr="127.0.0.1" --master_port=1234 train.py configs/ms1mv3_r50 -``` - -### 2. Multiple nodes, each node 8 GPUs: - -Node 0: - -```shell -python -m torch.distributed.launch --nproc_per_node=8 --nnodes=2 --node_rank=0 --master_addr="ip1" --master_port=1234 train.py train.py configs/ms1mv3_r50 -``` - -Node 1: - -```shell -python -m torch.distributed.launch --nproc_per_node=8 --nnodes=2 --node_rank=1 --master_addr="ip1" --master_port=1234 train.py train.py configs/ms1mv3_r50 -``` - -### 3.Training resnet2060 with 8 GPUs: - -```shell -python -m torch.distributed.launch --nproc_per_node=8 --nnodes=1 --node_rank=0 --master_addr="127.0.0.1" --master_port=1234 train.py configs/ms1mv3_r2060.py -``` - -## Model Zoo - -- The models are available for non-commercial research purposes only. -- All models can be found in here. -- [Baidu Yun Pan](https://pan.baidu.com/s/1CL-l4zWqsI1oDuEEYVhj-g): e8pw -- [onedrive](https://1drv.ms/u/s!AswpsDO2toNKq0lWY69vN58GR6mw?e=p9Ov5d) - -### Performance on [**ICCV2021-MFR**](http://iccv21-mfr.com/) - -ICCV2021-MFR testset consists of non-celebrities so we can ensure that it has very few overlap with public available face -recognition training set, such as MS1M and CASIA as they mostly collected from online celebrities. -As the result, we can evaluate the FAIR performance for different algorithms. - -For **ICCV2021-MFR-ALL** set, TAR is measured on all-to-all 1:1 protocal, with FAR less than 0.000001(e-6). The -globalised multi-racial testset contains 242,143 identities and 1,624,305 images. - -For **ICCV2021-MFR-MASK** set, TAR is measured on mask-to-nonmask 1:1 protocal, with FAR less than 0.0001(e-4). -Mask testset contains 6,964 identities, 6,964 masked images and 13,928 non-masked images. -There are totally 13,928 positive pairs and 96,983,824 negative pairs. - -| Datasets | backbone | Training throughout | Size / MB | **ICCV2021-MFR-MASK** | **ICCV2021-MFR-ALL** | -| :---: | :--- | :--- | :--- |:--- |:--- | -| MS1MV3 | r18 | - | 91 | **47.85** | **68.33** | -| Glint360k | r18 | 8536 | 91 | **53.32** | **72.07** | -| MS1MV3 | r34 | - | 130 | **58.72** | **77.36** | -| Glint360k | r34 | 6344 | 130 | **65.10** | **83.02** | -| MS1MV3 | r50 | 5500 | 166 | **63.85** | **80.53** | -| Glint360k | r50 | 5136 | 166 | **70.23** | **87.08** | -| MS1MV3 | r100 | - | 248 | **69.09** | **84.31** | -| Glint360k | r100 | 3332 | 248 | **75.57** | **90.66** | -| MS1MV3 | mobilefacenet | 12185 | 7.8 | **41.52** | **65.26** | -| Glint360k | mobilefacenet | 11197 | 7.8 | **44.52** | **66.48** | - -### Performance on IJB-C and Verification Datasets - -| Datasets | backbone | IJBC(1e-05) | IJBC(1e-04) | agedb30 | cfp_fp | lfw | log | -| :---: | :--- | :--- | :--- | :--- |:--- |:--- |:--- | -| MS1MV3 | r18 | 92.07 | 94.66 | 97.77 | 97.73 | 99.77 |[log](https://raw.githubusercontent.com/anxiangsir/insightface_arcface_log/master/ms1mv3_arcface_r18_fp16/training.log)| -| MS1MV3 | r34 | 94.10 | 95.90 | 98.10 | 98.67 | 99.80 |[log](https://raw.githubusercontent.com/anxiangsir/insightface_arcface_log/master/ms1mv3_arcface_r34_fp16/training.log)| -| MS1MV3 | r50 | 94.79 | 96.46 | 98.35 | 98.96 | 99.83 |[log](https://raw.githubusercontent.com/anxiangsir/insightface_arcface_log/master/ms1mv3_arcface_r50_fp16/training.log)| -| MS1MV3 | r100 | 95.31 | 96.81 | 98.48 | 99.06 | 99.85 |[log](https://raw.githubusercontent.com/anxiangsir/insightface_arcface_log/master/ms1mv3_arcface_r100_fp16/training.log)| -| MS1MV3 | **r2060**| 95.34 | 97.11 | 98.67 | 99.24 | 99.87 |[log](https://raw.githubusercontent.com/anxiangsir/insightface_arcface_log/master/ms1mv3_arcface_r2060_fp16/training.log)| -| Glint360k |r18-0.1 | 93.16 | 95.33 | 97.72 | 97.73 | 99.77 |[log](https://raw.githubusercontent.com/anxiangsir/insightface_arcface_log/master/glint360k_cosface_r18_fp16_0.1/training.log)| -| Glint360k |r34-0.1 | 95.16 | 96.56 | 98.33 | 98.78 | 99.82 |[log](https://raw.githubusercontent.com/anxiangsir/insightface_arcface_log/master/glint360k_cosface_r34_fp16_0.1/training.log)| -| Glint360k |r50-0.1 | 95.61 | 96.97 | 98.38 | 99.20 | 99.83 |[log](https://raw.githubusercontent.com/anxiangsir/insightface_arcface_log/master/glint360k_cosface_r50_fp16_0.1/training.log)| -| Glint360k |r100-0.1 | 95.88 | 97.32 | 98.48 | 99.29 | 99.82 |[log](https://raw.githubusercontent.com/anxiangsir/insightface_arcface_log/master/glint360k_cosface_r100_fp16_0.1/training.log)| - -[comment]: <> (More details see [model.md](docs/modelzoo.md) in docs.) - - -## [Speed Benchmark](docs/speed_benchmark.md) - -**Arcface Torch** can train large-scale face recognition training set efficiently and quickly. When the number of -classes in training sets is greater than 300K and the training is sufficient, partial fc sampling strategy will get same -accuracy with several times faster training performance and smaller GPU memory. -Partial FC is a sparse variant of the model parallel architecture for large sacle face recognition. Partial FC use a -sparse softmax, where each batch dynamicly sample a subset of class centers for training. In each iteration, only a -sparse part of the parameters will be updated, which can reduce a lot of GPU memory and calculations. With Partial FC, -we can scale trainset of 29 millions identities, the largest to date. Partial FC also supports multi-machine distributed -training and mixed precision training. - -![Image text](https://github.com/anxiangsir/insightface_arcface_log/blob/master/partial_fc_v2.png) - -More details see -[speed_benchmark.md](docs/speed_benchmark.md) in docs. - -### 1. Training speed of different parallel methods (samples / second), Tesla V100 32GB * 8. (Larger is better) - -`-` means training failed because of gpu memory limitations. - -| Number of Identities in Dataset | Data Parallel | Model Parallel | Partial FC 0.1 | -| :--- | :--- | :--- | :--- | -|125000 | 4681 | 4824 | 5004 | -|1400000 | **1672** | 3043 | 4738 | -|5500000 | **-** | **1389** | 3975 | -|8000000 | **-** | **-** | 3565 | -|16000000 | **-** | **-** | 2679 | -|29000000 | **-** | **-** | **1855** | - -### 2. GPU memory cost of different parallel methods (MB per GPU), Tesla V100 32GB * 8. (Smaller is better) - -| Number of Identities in Dataset | Data Parallel | Model Parallel | Partial FC 0.1 | -| :--- | :--- | :--- | :--- | -|125000 | 7358 | 5306 | 4868 | -|1400000 | 32252 | 11178 | 6056 | -|5500000 | **-** | 32188 | 9854 | -|8000000 | **-** | **-** | 12310 | -|16000000 | **-** | **-** | 19950 | -|29000000 | **-** | **-** | 32324 | - -## Evaluation ICCV2021-MFR and IJB-C - -More details see [eval.md](docs/eval.md) in docs. - -## Test - -We tested many versions of PyTorch. Please create an issue if you are having trouble. - -- [x] torch 1.6.0 -- [x] torch 1.7.1 -- [x] torch 1.8.0 -- [x] torch 1.9.0 - -## Citation - -``` -@inproceedings{deng2019arcface, - title={Arcface: Additive angular margin loss for deep face recognition}, - author={Deng, Jiankang and Guo, Jia and Xue, Niannan and Zafeiriou, Stefanos}, - booktitle={Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition}, - pages={4690--4699}, - year={2019} -} -@inproceedings{an2020partical_fc, - title={Partial FC: Training 10 Million Identities on a Single Machine}, - author={An, Xiang and Zhu, Xuhan and Xiao, Yang and Wu, Lan and Zhang, Ming and Gao, Yuan and Qin, Bin and - Zhang, Debing and Fu Ying}, - booktitle={Arxiv 2010.05222}, - year={2020} -} -``` diff --git a/src/face3d/models/arcface_torch/backbones/__init__.py b/src/face3d/models/arcface_torch/backbones/__init__.py deleted file mode 100644 index 55bd4c5..0000000 --- a/src/face3d/models/arcface_torch/backbones/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -from .iresnet import iresnet18, iresnet34, iresnet50, iresnet100, iresnet200 -from .mobilefacenet import get_mbf - - -def get_model(name, **kwargs): - # resnet - if name == "r18": - return iresnet18(False, **kwargs) - elif name == "r34": - return iresnet34(False, **kwargs) - elif name == "r50": - return iresnet50(False, **kwargs) - elif name == "r100": - return iresnet100(False, **kwargs) - elif name == "r200": - return iresnet200(False, **kwargs) - elif name == "r2060": - from .iresnet2060 import iresnet2060 - return iresnet2060(False, **kwargs) - elif name == "mbf": - fp16 = kwargs.get("fp16", False) - num_features = kwargs.get("num_features", 512) - return get_mbf(fp16=fp16, num_features=num_features) - else: - raise ValueError() \ No newline at end of file diff --git a/src/face3d/models/arcface_torch/backbones/iresnet.py b/src/face3d/models/arcface_torch/backbones/iresnet.py deleted file mode 100644 index c6d3b9c..0000000 --- a/src/face3d/models/arcface_torch/backbones/iresnet.py +++ /dev/null @@ -1,187 +0,0 @@ -import torch -from torch import nn - -__all__ = ['iresnet18', 'iresnet34', 'iresnet50', 'iresnet100', 'iresnet200'] - - -def conv3x3(in_planes, out_planes, stride=1, groups=1, dilation=1): - """3x3 convolution with padding""" - return nn.Conv2d(in_planes, - out_planes, - kernel_size=3, - stride=stride, - padding=dilation, - groups=groups, - bias=False, - dilation=dilation) - - -def conv1x1(in_planes, out_planes, stride=1): - """1x1 convolution""" - return nn.Conv2d(in_planes, - out_planes, - kernel_size=1, - stride=stride, - bias=False) - - -class IBasicBlock(nn.Module): - expansion = 1 - def __init__(self, inplanes, planes, stride=1, downsample=None, - groups=1, base_width=64, dilation=1): - super(IBasicBlock, self).__init__() - if groups != 1 or base_width != 64: - raise ValueError('BasicBlock only supports groups=1 and base_width=64') - if dilation > 1: - raise NotImplementedError("Dilation > 1 not supported in BasicBlock") - self.bn1 = nn.BatchNorm2d(inplanes, eps=1e-05,) - self.conv1 = conv3x3(inplanes, planes) - self.bn2 = nn.BatchNorm2d(planes, eps=1e-05,) - self.prelu = nn.PReLU(planes) - self.conv2 = conv3x3(planes, planes, stride) - self.bn3 = nn.BatchNorm2d(planes, eps=1e-05,) - self.downsample = downsample - self.stride = stride - - def forward(self, x): - identity = x - out = self.bn1(x) - out = self.conv1(out) - out = self.bn2(out) - out = self.prelu(out) - out = self.conv2(out) - out = self.bn3(out) - if self.downsample is not None: - identity = self.downsample(x) - out += identity - return out - - -class IResNet(nn.Module): - fc_scale = 7 * 7 - def __init__(self, - block, layers, dropout=0, num_features=512, zero_init_residual=False, - groups=1, width_per_group=64, replace_stride_with_dilation=None, fp16=False): - super(IResNet, self).__init__() - self.fp16 = fp16 - self.inplanes = 64 - self.dilation = 1 - if replace_stride_with_dilation is None: - replace_stride_with_dilation = [False, False, False] - if len(replace_stride_with_dilation) != 3: - raise ValueError("replace_stride_with_dilation should be None " - "or a 3-element tuple, got {}".format(replace_stride_with_dilation)) - self.groups = groups - self.base_width = width_per_group - self.conv1 = nn.Conv2d(3, self.inplanes, kernel_size=3, stride=1, padding=1, bias=False) - self.bn1 = nn.BatchNorm2d(self.inplanes, eps=1e-05) - self.prelu = nn.PReLU(self.inplanes) - self.layer1 = self._make_layer(block, 64, layers[0], stride=2) - self.layer2 = self._make_layer(block, - 128, - layers[1], - stride=2, - dilate=replace_stride_with_dilation[0]) - self.layer3 = self._make_layer(block, - 256, - layers[2], - stride=2, - dilate=replace_stride_with_dilation[1]) - self.layer4 = self._make_layer(block, - 512, - layers[3], - stride=2, - dilate=replace_stride_with_dilation[2]) - self.bn2 = nn.BatchNorm2d(512 * block.expansion, eps=1e-05,) - self.dropout = nn.Dropout(p=dropout, inplace=True) - self.fc = nn.Linear(512 * block.expansion * self.fc_scale, num_features) - self.features = nn.BatchNorm1d(num_features, eps=1e-05) - nn.init.constant_(self.features.weight, 1.0) - self.features.weight.requires_grad = False - - for m in self.modules(): - if isinstance(m, nn.Conv2d): - nn.init.normal_(m.weight, 0, 0.1) - elif isinstance(m, (nn.BatchNorm2d, nn.GroupNorm)): - nn.init.constant_(m.weight, 1) - nn.init.constant_(m.bias, 0) - - if zero_init_residual: - for m in self.modules(): - if isinstance(m, IBasicBlock): - nn.init.constant_(m.bn2.weight, 0) - - def _make_layer(self, block, planes, blocks, stride=1, dilate=False): - downsample = None - previous_dilation = self.dilation - if dilate: - self.dilation *= stride - stride = 1 - if stride != 1 or self.inplanes != planes * block.expansion: - downsample = nn.Sequential( - conv1x1(self.inplanes, planes * block.expansion, stride), - nn.BatchNorm2d(planes * block.expansion, eps=1e-05, ), - ) - layers = [] - layers.append( - block(self.inplanes, planes, stride, downsample, self.groups, - self.base_width, previous_dilation)) - self.inplanes = planes * block.expansion - for _ in range(1, blocks): - layers.append( - block(self.inplanes, - planes, - groups=self.groups, - base_width=self.base_width, - dilation=self.dilation)) - - return nn.Sequential(*layers) - - def forward(self, x): - with torch.cuda.amp.autocast(self.fp16): - x = self.conv1(x) - x = self.bn1(x) - x = self.prelu(x) - x = self.layer1(x) - x = self.layer2(x) - x = self.layer3(x) - x = self.layer4(x) - x = self.bn2(x) - x = torch.flatten(x, 1) - x = self.dropout(x) - x = self.fc(x.float() if self.fp16 else x) - x = self.features(x) - return x - - -def _iresnet(arch, block, layers, pretrained, progress, **kwargs): - model = IResNet(block, layers, **kwargs) - if pretrained: - raise ValueError() - return model - - -def iresnet18(pretrained=False, progress=True, **kwargs): - return _iresnet('iresnet18', IBasicBlock, [2, 2, 2, 2], pretrained, - progress, **kwargs) - - -def iresnet34(pretrained=False, progress=True, **kwargs): - return _iresnet('iresnet34', IBasicBlock, [3, 4, 6, 3], pretrained, - progress, **kwargs) - - -def iresnet50(pretrained=False, progress=True, **kwargs): - return _iresnet('iresnet50', IBasicBlock, [3, 4, 14, 3], pretrained, - progress, **kwargs) - - -def iresnet100(pretrained=False, progress=True, **kwargs): - return _iresnet('iresnet100', IBasicBlock, [3, 13, 30, 3], pretrained, - progress, **kwargs) - - -def iresnet200(pretrained=False, progress=True, **kwargs): - return _iresnet('iresnet200', IBasicBlock, [6, 26, 60, 6], pretrained, - progress, **kwargs) - diff --git a/src/face3d/models/arcface_torch/backbones/iresnet2060.py b/src/face3d/models/arcface_torch/backbones/iresnet2060.py deleted file mode 100644 index 21d1122..0000000 --- a/src/face3d/models/arcface_torch/backbones/iresnet2060.py +++ /dev/null @@ -1,176 +0,0 @@ -import torch -from torch import nn - -assert torch.__version__ >= "1.8.1" -from torch.utils.checkpoint import checkpoint_sequential - -__all__ = ['iresnet2060'] - - -def conv3x3(in_planes, out_planes, stride=1, groups=1, dilation=1): - """3x3 convolution with padding""" - return nn.Conv2d(in_planes, - out_planes, - kernel_size=3, - stride=stride, - padding=dilation, - groups=groups, - bias=False, - dilation=dilation) - - -def conv1x1(in_planes, out_planes, stride=1): - """1x1 convolution""" - return nn.Conv2d(in_planes, - out_planes, - kernel_size=1, - stride=stride, - bias=False) - - -class IBasicBlock(nn.Module): - expansion = 1 - - def __init__(self, inplanes, planes, stride=1, downsample=None, - groups=1, base_width=64, dilation=1): - super(IBasicBlock, self).__init__() - if groups != 1 or base_width != 64: - raise ValueError('BasicBlock only supports groups=1 and base_width=64') - if dilation > 1: - raise NotImplementedError("Dilation > 1 not supported in BasicBlock") - self.bn1 = nn.BatchNorm2d(inplanes, eps=1e-05, ) - self.conv1 = conv3x3(inplanes, planes) - self.bn2 = nn.BatchNorm2d(planes, eps=1e-05, ) - self.prelu = nn.PReLU(planes) - self.conv2 = conv3x3(planes, planes, stride) - self.bn3 = nn.BatchNorm2d(planes, eps=1e-05, ) - self.downsample = downsample - self.stride = stride - - def forward(self, x): - identity = x - out = self.bn1(x) - out = self.conv1(out) - out = self.bn2(out) - out = self.prelu(out) - out = self.conv2(out) - out = self.bn3(out) - if self.downsample is not None: - identity = self.downsample(x) - out += identity - return out - - -class IResNet(nn.Module): - fc_scale = 7 * 7 - - def __init__(self, - block, layers, dropout=0, num_features=512, zero_init_residual=False, - groups=1, width_per_group=64, replace_stride_with_dilation=None, fp16=False): - super(IResNet, self).__init__() - self.fp16 = fp16 - self.inplanes = 64 - self.dilation = 1 - if replace_stride_with_dilation is None: - replace_stride_with_dilation = [False, False, False] - if len(replace_stride_with_dilation) != 3: - raise ValueError("replace_stride_with_dilation should be None " - "or a 3-element tuple, got {}".format(replace_stride_with_dilation)) - self.groups = groups - self.base_width = width_per_group - self.conv1 = nn.Conv2d(3, self.inplanes, kernel_size=3, stride=1, padding=1, bias=False) - self.bn1 = nn.BatchNorm2d(self.inplanes, eps=1e-05) - self.prelu = nn.PReLU(self.inplanes) - self.layer1 = self._make_layer(block, 64, layers[0], stride=2) - self.layer2 = self._make_layer(block, - 128, - layers[1], - stride=2, - dilate=replace_stride_with_dilation[0]) - self.layer3 = self._make_layer(block, - 256, - layers[2], - stride=2, - dilate=replace_stride_with_dilation[1]) - self.layer4 = self._make_layer(block, - 512, - layers[3], - stride=2, - dilate=replace_stride_with_dilation[2]) - self.bn2 = nn.BatchNorm2d(512 * block.expansion, eps=1e-05, ) - self.dropout = nn.Dropout(p=dropout, inplace=True) - self.fc = nn.Linear(512 * block.expansion * self.fc_scale, num_features) - self.features = nn.BatchNorm1d(num_features, eps=1e-05) - nn.init.constant_(self.features.weight, 1.0) - self.features.weight.requires_grad = False - - for m in self.modules(): - if isinstance(m, nn.Conv2d): - nn.init.normal_(m.weight, 0, 0.1) - elif isinstance(m, (nn.BatchNorm2d, nn.GroupNorm)): - nn.init.constant_(m.weight, 1) - nn.init.constant_(m.bias, 0) - - if zero_init_residual: - for m in self.modules(): - if isinstance(m, IBasicBlock): - nn.init.constant_(m.bn2.weight, 0) - - def _make_layer(self, block, planes, blocks, stride=1, dilate=False): - downsample = None - previous_dilation = self.dilation - if dilate: - self.dilation *= stride - stride = 1 - if stride != 1 or self.inplanes != planes * block.expansion: - downsample = nn.Sequential( - conv1x1(self.inplanes, planes * block.expansion, stride), - nn.BatchNorm2d(planes * block.expansion, eps=1e-05, ), - ) - layers = [] - layers.append( - block(self.inplanes, planes, stride, downsample, self.groups, - self.base_width, previous_dilation)) - self.inplanes = planes * block.expansion - for _ in range(1, blocks): - layers.append( - block(self.inplanes, - planes, - groups=self.groups, - base_width=self.base_width, - dilation=self.dilation)) - - return nn.Sequential(*layers) - - def checkpoint(self, func, num_seg, x): - if self.training: - return checkpoint_sequential(func, num_seg, x) - else: - return func(x) - - def forward(self, x): - with torch.cuda.amp.autocast(self.fp16): - x = self.conv1(x) - x = self.bn1(x) - x = self.prelu(x) - x = self.layer1(x) - x = self.checkpoint(self.layer2, 20, x) - x = self.checkpoint(self.layer3, 100, x) - x = self.layer4(x) - x = self.bn2(x) - x = torch.flatten(x, 1) - x = self.dropout(x) - x = self.fc(x.float() if self.fp16 else x) - x = self.features(x) - return x - - -def _iresnet(arch, block, layers, pretrained, progress, **kwargs): - model = IResNet(block, layers, **kwargs) - if pretrained: - raise ValueError() - return model - - -def iresnet2060(pretrained=False, progress=True, **kwargs): - return _iresnet('iresnet2060', IBasicBlock, [3, 128, 1024 - 128, 3], pretrained, progress, **kwargs) diff --git a/src/face3d/models/arcface_torch/backbones/mobilefacenet.py b/src/face3d/models/arcface_torch/backbones/mobilefacenet.py deleted file mode 100644 index 8773149..0000000 --- a/src/face3d/models/arcface_torch/backbones/mobilefacenet.py +++ /dev/null @@ -1,130 +0,0 @@ -''' -Adapted from https://github.com/cavalleria/cavaface.pytorch/blob/master/backbone/mobilefacenet.py -Original author cavalleria -''' - -import torch.nn as nn -from torch.nn import Linear, Conv2d, BatchNorm1d, BatchNorm2d, PReLU, Sequential, Module -import torch - - -class Flatten(Module): - def forward(self, x): - return x.view(x.size(0), -1) - - -class ConvBlock(Module): - def __init__(self, in_c, out_c, kernel=(1, 1), stride=(1, 1), padding=(0, 0), groups=1): - super(ConvBlock, self).__init__() - self.layers = nn.Sequential( - Conv2d(in_c, out_c, kernel, groups=groups, stride=stride, padding=padding, bias=False), - BatchNorm2d(num_features=out_c), - PReLU(num_parameters=out_c) - ) - - def forward(self, x): - return self.layers(x) - - -class LinearBlock(Module): - def __init__(self, in_c, out_c, kernel=(1, 1), stride=(1, 1), padding=(0, 0), groups=1): - super(LinearBlock, self).__init__() - self.layers = nn.Sequential( - Conv2d(in_c, out_c, kernel, stride, padding, groups=groups, bias=False), - BatchNorm2d(num_features=out_c) - ) - - def forward(self, x): - return self.layers(x) - - -class DepthWise(Module): - def __init__(self, in_c, out_c, residual=False, kernel=(3, 3), stride=(2, 2), padding=(1, 1), groups=1): - super(DepthWise, self).__init__() - self.residual = residual - self.layers = nn.Sequential( - ConvBlock(in_c, out_c=groups, kernel=(1, 1), padding=(0, 0), stride=(1, 1)), - ConvBlock(groups, groups, groups=groups, kernel=kernel, padding=padding, stride=stride), - LinearBlock(groups, out_c, kernel=(1, 1), padding=(0, 0), stride=(1, 1)) - ) - - def forward(self, x): - short_cut = None - if self.residual: - short_cut = x - x = self.layers(x) - if self.residual: - output = short_cut + x - else: - output = x - return output - - -class Residual(Module): - def __init__(self, c, num_block, groups, kernel=(3, 3), stride=(1, 1), padding=(1, 1)): - super(Residual, self).__init__() - modules = [] - for _ in range(num_block): - modules.append(DepthWise(c, c, True, kernel, stride, padding, groups)) - self.layers = Sequential(*modules) - - def forward(self, x): - return self.layers(x) - - -class GDC(Module): - def __init__(self, embedding_size): - super(GDC, self).__init__() - self.layers = nn.Sequential( - LinearBlock(512, 512, groups=512, kernel=(7, 7), stride=(1, 1), padding=(0, 0)), - Flatten(), - Linear(512, embedding_size, bias=False), - BatchNorm1d(embedding_size)) - - def forward(self, x): - return self.layers(x) - - -class MobileFaceNet(Module): - def __init__(self, fp16=False, num_features=512): - super(MobileFaceNet, self).__init__() - scale = 2 - self.fp16 = fp16 - self.layers = nn.Sequential( - ConvBlock(3, 64 * scale, kernel=(3, 3), stride=(2, 2), padding=(1, 1)), - ConvBlock(64 * scale, 64 * scale, kernel=(3, 3), stride=(1, 1), padding=(1, 1), groups=64), - DepthWise(64 * scale, 64 * scale, kernel=(3, 3), stride=(2, 2), padding=(1, 1), groups=128), - Residual(64 * scale, num_block=4, groups=128, kernel=(3, 3), stride=(1, 1), padding=(1, 1)), - DepthWise(64 * scale, 128 * scale, kernel=(3, 3), stride=(2, 2), padding=(1, 1), groups=256), - Residual(128 * scale, num_block=6, groups=256, kernel=(3, 3), stride=(1, 1), padding=(1, 1)), - DepthWise(128 * scale, 128 * scale, kernel=(3, 3), stride=(2, 2), padding=(1, 1), groups=512), - Residual(128 * scale, num_block=2, groups=256, kernel=(3, 3), stride=(1, 1), padding=(1, 1)), - ) - self.conv_sep = ConvBlock(128 * scale, 512, kernel=(1, 1), stride=(1, 1), padding=(0, 0)) - self.features = GDC(num_features) - self._initialize_weights() - - def _initialize_weights(self): - for m in self.modules(): - if isinstance(m, nn.Conv2d): - nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu') - if m.bias is not None: - m.bias.data.zero_() - elif isinstance(m, nn.BatchNorm2d): - m.weight.data.fill_(1) - m.bias.data.zero_() - elif isinstance(m, nn.Linear): - nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu') - if m.bias is not None: - m.bias.data.zero_() - - def forward(self, x): - with torch.cuda.amp.autocast(self.fp16): - x = self.layers(x) - x = self.conv_sep(x.float() if self.fp16 else x) - x = self.features(x) - return x - - -def get_mbf(fp16, num_features): - return MobileFaceNet(fp16, num_features) \ No newline at end of file diff --git a/src/face3d/models/arcface_torch/configs/3millions.py b/src/face3d/models/arcface_torch/configs/3millions.py deleted file mode 100644 index c9edc2f..0000000 --- a/src/face3d/models/arcface_torch/configs/3millions.py +++ /dev/null @@ -1,23 +0,0 @@ -from easydict import EasyDict as edict - -# configs for test speed - -config = edict() -config.loss = "arcface" -config.network = "r50" -config.resume = False -config.output = None -config.embedding_size = 512 -config.sample_rate = 1.0 -config.fp16 = True -config.momentum = 0.9 -config.weight_decay = 5e-4 -config.batch_size = 128 -config.lr = 0.1 # batch size is 512 - -config.rec = "synthetic" -config.num_classes = 300 * 10000 -config.num_epoch = 30 -config.warmup_epoch = -1 -config.decay_epoch = [10, 16, 22] -config.val_targets = [] diff --git a/src/face3d/models/arcface_torch/configs/3millions_pfc.py b/src/face3d/models/arcface_torch/configs/3millions_pfc.py deleted file mode 100644 index 77caafd..0000000 --- a/src/face3d/models/arcface_torch/configs/3millions_pfc.py +++ /dev/null @@ -1,23 +0,0 @@ -from easydict import EasyDict as edict - -# configs for test speed - -config = edict() -config.loss = "arcface" -config.network = "r50" -config.resume = False -config.output = None -config.embedding_size = 512 -config.sample_rate = 0.1 -config.fp16 = True -config.momentum = 0.9 -config.weight_decay = 5e-4 -config.batch_size = 128 -config.lr = 0.1 # batch size is 512 - -config.rec = "synthetic" -config.num_classes = 300 * 10000 -config.num_epoch = 30 -config.warmup_epoch = -1 -config.decay_epoch = [10, 16, 22] -config.val_targets = [] diff --git a/src/face3d/models/arcface_torch/configs/__init__.py b/src/face3d/models/arcface_torch/configs/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/face3d/models/arcface_torch/configs/base.py b/src/face3d/models/arcface_torch/configs/base.py deleted file mode 100644 index 78e4b36..0000000 --- a/src/face3d/models/arcface_torch/configs/base.py +++ /dev/null @@ -1,56 +0,0 @@ -from easydict import EasyDict as edict - -# make training faster -# our RAM is 256G -# mount -t tmpfs -o size=140G tmpfs /train_tmp - -config = edict() -config.loss = "arcface" -config.network = "r50" -config.resume = False -config.output = "ms1mv3_arcface_r50" - -config.dataset = "ms1m-retinaface-t1" -config.embedding_size = 512 -config.sample_rate = 1 -config.fp16 = False -config.momentum = 0.9 -config.weight_decay = 5e-4 -config.batch_size = 128 -config.lr = 0.1 # batch size is 512 - -if config.dataset == "emore": - config.rec = "/train_tmp/faces_emore" - config.num_classes = 85742 - config.num_image = 5822653 - config.num_epoch = 16 - config.warmup_epoch = -1 - config.decay_epoch = [8, 14, ] - config.val_targets = ["lfw", ] - -elif config.dataset == "ms1m-retinaface-t1": - config.rec = "/train_tmp/ms1m-retinaface-t1" - config.num_classes = 93431 - config.num_image = 5179510 - config.num_epoch = 25 - config.warmup_epoch = -1 - config.decay_epoch = [11, 17, 22] - config.val_targets = ["lfw", "cfp_fp", "agedb_30"] - -elif config.dataset == "glint360k": - config.rec = "/train_tmp/glint360k" - config.num_classes = 360232 - config.num_image = 17091657 - config.num_epoch = 20 - config.warmup_epoch = -1 - config.decay_epoch = [8, 12, 15, 18] - config.val_targets = ["lfw", "cfp_fp", "agedb_30"] - -elif config.dataset == "webface": - config.rec = "/train_tmp/faces_webface_112x112" - config.num_classes = 10572 - config.num_image = "forget" - config.num_epoch = 34 - config.warmup_epoch = -1 - config.decay_epoch = [20, 28, 32] - config.val_targets = ["lfw", "cfp_fp", "agedb_30"] diff --git a/src/face3d/models/arcface_torch/configs/glint360k_mbf.py b/src/face3d/models/arcface_torch/configs/glint360k_mbf.py deleted file mode 100644 index 46ae777..0000000 --- a/src/face3d/models/arcface_torch/configs/glint360k_mbf.py +++ /dev/null @@ -1,26 +0,0 @@ -from easydict import EasyDict as edict - -# make training faster -# our RAM is 256G -# mount -t tmpfs -o size=140G tmpfs /train_tmp - -config = edict() -config.loss = "cosface" -config.network = "mbf" -config.resume = False -config.output = None -config.embedding_size = 512 -config.sample_rate = 0.1 -config.fp16 = True -config.momentum = 0.9 -config.weight_decay = 2e-4 -config.batch_size = 128 -config.lr = 0.1 # batch size is 512 - -config.rec = "/train_tmp/glint360k" -config.num_classes = 360232 -config.num_image = 17091657 -config.num_epoch = 20 -config.warmup_epoch = -1 -config.decay_epoch = [8, 12, 15, 18] -config.val_targets = ["lfw", "cfp_fp", "agedb_30"] diff --git a/src/face3d/models/arcface_torch/configs/glint360k_r100.py b/src/face3d/models/arcface_torch/configs/glint360k_r100.py deleted file mode 100644 index 93d0701..0000000 --- a/src/face3d/models/arcface_torch/configs/glint360k_r100.py +++ /dev/null @@ -1,26 +0,0 @@ -from easydict import EasyDict as edict - -# make training faster -# our RAM is 256G -# mount -t tmpfs -o size=140G tmpfs /train_tmp - -config = edict() -config.loss = "cosface" -config.network = "r100" -config.resume = False -config.output = None -config.embedding_size = 512 -config.sample_rate = 1.0 -config.fp16 = True -config.momentum = 0.9 -config.weight_decay = 5e-4 -config.batch_size = 128 -config.lr = 0.1 # batch size is 512 - -config.rec = "/train_tmp/glint360k" -config.num_classes = 360232 -config.num_image = 17091657 -config.num_epoch = 20 -config.warmup_epoch = -1 -config.decay_epoch = [8, 12, 15, 18] -config.val_targets = ["lfw", "cfp_fp", "agedb_30"] diff --git a/src/face3d/models/arcface_torch/configs/glint360k_r18.py b/src/face3d/models/arcface_torch/configs/glint360k_r18.py deleted file mode 100644 index 7a8db34..0000000 --- a/src/face3d/models/arcface_torch/configs/glint360k_r18.py +++ /dev/null @@ -1,26 +0,0 @@ -from easydict import EasyDict as edict - -# make training faster -# our RAM is 256G -# mount -t tmpfs -o size=140G tmpfs /train_tmp - -config = edict() -config.loss = "cosface" -config.network = "r18" -config.resume = False -config.output = None -config.embedding_size = 512 -config.sample_rate = 1.0 -config.fp16 = True -config.momentum = 0.9 -config.weight_decay = 5e-4 -config.batch_size = 128 -config.lr = 0.1 # batch size is 512 - -config.rec = "/train_tmp/glint360k" -config.num_classes = 360232 -config.num_image = 17091657 -config.num_epoch = 20 -config.warmup_epoch = -1 -config.decay_epoch = [8, 12, 15, 18] -config.val_targets = ["lfw", "cfp_fp", "agedb_30"] diff --git a/src/face3d/models/arcface_torch/configs/glint360k_r34.py b/src/face3d/models/arcface_torch/configs/glint360k_r34.py deleted file mode 100644 index fda2701..0000000 --- a/src/face3d/models/arcface_torch/configs/glint360k_r34.py +++ /dev/null @@ -1,26 +0,0 @@ -from easydict import EasyDict as edict - -# make training faster -# our RAM is 256G -# mount -t tmpfs -o size=140G tmpfs /train_tmp - -config = edict() -config.loss = "cosface" -config.network = "r34" -config.resume = False -config.output = None -config.embedding_size = 512 -config.sample_rate = 1.0 -config.fp16 = True -config.momentum = 0.9 -config.weight_decay = 5e-4 -config.batch_size = 128 -config.lr = 0.1 # batch size is 512 - -config.rec = "/train_tmp/glint360k" -config.num_classes = 360232 -config.num_image = 17091657 -config.num_epoch = 20 -config.warmup_epoch = -1 -config.decay_epoch = [8, 12, 15, 18] -config.val_targets = ["lfw", "cfp_fp", "agedb_30"] diff --git a/src/face3d/models/arcface_torch/configs/glint360k_r50.py b/src/face3d/models/arcface_torch/configs/glint360k_r50.py deleted file mode 100644 index 37e7922..0000000 --- a/src/face3d/models/arcface_torch/configs/glint360k_r50.py +++ /dev/null @@ -1,26 +0,0 @@ -from easydict import EasyDict as edict - -# make training faster -# our RAM is 256G -# mount -t tmpfs -o size=140G tmpfs /train_tmp - -config = edict() -config.loss = "cosface" -config.network = "r50" -config.resume = False -config.output = None -config.embedding_size = 512 -config.sample_rate = 1.0 -config.fp16 = True -config.momentum = 0.9 -config.weight_decay = 5e-4 -config.batch_size = 128 -config.lr = 0.1 # batch size is 512 - -config.rec = "/train_tmp/glint360k" -config.num_classes = 360232 -config.num_image = 17091657 -config.num_epoch = 20 -config.warmup_epoch = -1 -config.decay_epoch = [8, 12, 15, 18] -config.val_targets = ["lfw", "cfp_fp", "agedb_30"] diff --git a/src/face3d/models/arcface_torch/configs/ms1mv3_mbf.py b/src/face3d/models/arcface_torch/configs/ms1mv3_mbf.py deleted file mode 100644 index b8a00d6..0000000 --- a/src/face3d/models/arcface_torch/configs/ms1mv3_mbf.py +++ /dev/null @@ -1,26 +0,0 @@ -from easydict import EasyDict as edict - -# make training faster -# our RAM is 256G -# mount -t tmpfs -o size=140G tmpfs /train_tmp - -config = edict() -config.loss = "arcface" -config.network = "mbf" -config.resume = False -config.output = None -config.embedding_size = 512 -config.sample_rate = 1.0 -config.fp16 = True -config.momentum = 0.9 -config.weight_decay = 2e-4 -config.batch_size = 128 -config.lr = 0.1 # batch size is 512 - -config.rec = "/train_tmp/ms1m-retinaface-t1" -config.num_classes = 93431 -config.num_image = 5179510 -config.num_epoch = 30 -config.warmup_epoch = -1 -config.decay_epoch = [10, 20, 25] -config.val_targets = ["lfw", "cfp_fp", "agedb_30"] diff --git a/src/face3d/models/arcface_torch/configs/ms1mv3_r18.py b/src/face3d/models/arcface_torch/configs/ms1mv3_r18.py deleted file mode 100644 index eb4e0d3..0000000 --- a/src/face3d/models/arcface_torch/configs/ms1mv3_r18.py +++ /dev/null @@ -1,26 +0,0 @@ -from easydict import EasyDict as edict - -# make training faster -# our RAM is 256G -# mount -t tmpfs -o size=140G tmpfs /train_tmp - -config = edict() -config.loss = "arcface" -config.network = "r18" -config.resume = False -config.output = None -config.embedding_size = 512 -config.sample_rate = 1.0 -config.fp16 = True -config.momentum = 0.9 -config.weight_decay = 5e-4 -config.batch_size = 128 -config.lr = 0.1 # batch size is 512 - -config.rec = "/train_tmp/ms1m-retinaface-t1" -config.num_classes = 93431 -config.num_image = 5179510 -config.num_epoch = 25 -config.warmup_epoch = -1 -config.decay_epoch = [10, 16, 22] -config.val_targets = ["lfw", "cfp_fp", "agedb_30"] diff --git a/src/face3d/models/arcface_torch/configs/ms1mv3_r2060.py b/src/face3d/models/arcface_torch/configs/ms1mv3_r2060.py deleted file mode 100644 index 23ad81e..0000000 --- a/src/face3d/models/arcface_torch/configs/ms1mv3_r2060.py +++ /dev/null @@ -1,26 +0,0 @@ -from easydict import EasyDict as edict - -# make training faster -# our RAM is 256G -# mount -t tmpfs -o size=140G tmpfs /train_tmp - -config = edict() -config.loss = "arcface" -config.network = "r2060" -config.resume = False -config.output = None -config.embedding_size = 512 -config.sample_rate = 1.0 -config.fp16 = True -config.momentum = 0.9 -config.weight_decay = 5e-4 -config.batch_size = 64 -config.lr = 0.1 # batch size is 512 - -config.rec = "/train_tmp/ms1m-retinaface-t1" -config.num_classes = 93431 -config.num_image = 5179510 -config.num_epoch = 25 -config.warmup_epoch = -1 -config.decay_epoch = [10, 16, 22] -config.val_targets = ["lfw", "cfp_fp", "agedb_30"] diff --git a/src/face3d/models/arcface_torch/configs/ms1mv3_r34.py b/src/face3d/models/arcface_torch/configs/ms1mv3_r34.py deleted file mode 100644 index 5f78337..0000000 --- a/src/face3d/models/arcface_torch/configs/ms1mv3_r34.py +++ /dev/null @@ -1,26 +0,0 @@ -from easydict import EasyDict as edict - -# make training faster -# our RAM is 256G -# mount -t tmpfs -o size=140G tmpfs /train_tmp - -config = edict() -config.loss = "arcface" -config.network = "r34" -config.resume = False -config.output = None -config.embedding_size = 512 -config.sample_rate = 1.0 -config.fp16 = True -config.momentum = 0.9 -config.weight_decay = 5e-4 -config.batch_size = 128 -config.lr = 0.1 # batch size is 512 - -config.rec = "/train_tmp/ms1m-retinaface-t1" -config.num_classes = 93431 -config.num_image = 5179510 -config.num_epoch = 25 -config.warmup_epoch = -1 -config.decay_epoch = [10, 16, 22] -config.val_targets = ["lfw", "cfp_fp", "agedb_30"] diff --git a/src/face3d/models/arcface_torch/configs/ms1mv3_r50.py b/src/face3d/models/arcface_torch/configs/ms1mv3_r50.py deleted file mode 100644 index 08ba55d..0000000 --- a/src/face3d/models/arcface_torch/configs/ms1mv3_r50.py +++ /dev/null @@ -1,26 +0,0 @@ -from easydict import EasyDict as edict - -# make training faster -# our RAM is 256G -# mount -t tmpfs -o size=140G tmpfs /train_tmp - -config = edict() -config.loss = "arcface" -config.network = "r50" -config.resume = False -config.output = None -config.embedding_size = 512 -config.sample_rate = 1.0 -config.fp16 = True -config.momentum = 0.9 -config.weight_decay = 5e-4 -config.batch_size = 128 -config.lr = 0.1 # batch size is 512 - -config.rec = "/train_tmp/ms1m-retinaface-t1" -config.num_classes = 93431 -config.num_image = 5179510 -config.num_epoch = 25 -config.warmup_epoch = -1 -config.decay_epoch = [10, 16, 22] -config.val_targets = ["lfw", "cfp_fp", "agedb_30"] diff --git a/src/face3d/models/arcface_torch/configs/speed.py b/src/face3d/models/arcface_torch/configs/speed.py deleted file mode 100644 index 45e9523..0000000 --- a/src/face3d/models/arcface_torch/configs/speed.py +++ /dev/null @@ -1,23 +0,0 @@ -from easydict import EasyDict as edict - -# configs for test speed - -config = edict() -config.loss = "arcface" -config.network = "r50" -config.resume = False -config.output = None -config.embedding_size = 512 -config.sample_rate = 1.0 -config.fp16 = True -config.momentum = 0.9 -config.weight_decay = 5e-4 -config.batch_size = 128 -config.lr = 0.1 # batch size is 512 - -config.rec = "synthetic" -config.num_classes = 100 * 10000 -config.num_epoch = 30 -config.warmup_epoch = -1 -config.decay_epoch = [10, 16, 22] -config.val_targets = [] diff --git a/src/face3d/models/arcface_torch/dataset.py b/src/face3d/models/arcface_torch/dataset.py deleted file mode 100644 index 96bbb8b..0000000 --- a/src/face3d/models/arcface_torch/dataset.py +++ /dev/null @@ -1,124 +0,0 @@ -import numbers -import os -import queue as Queue -import threading - -import mxnet as mx -import numpy as np -import torch -from torch.utils.data import DataLoader, Dataset -from torchvision import transforms - - -class BackgroundGenerator(threading.Thread): - def __init__(self, generator, local_rank, max_prefetch=6): - super(BackgroundGenerator, self).__init__() - self.queue = Queue.Queue(max_prefetch) - self.generator = generator - self.local_rank = local_rank - self.daemon = True - self.start() - - def run(self): - torch.cuda.set_device(self.local_rank) - for item in self.generator: - self.queue.put(item) - self.queue.put(None) - - def next(self): - next_item = self.queue.get() - if next_item is None: - raise StopIteration - return next_item - - def __next__(self): - return self.next() - - def __iter__(self): - return self - - -class DataLoaderX(DataLoader): - - def __init__(self, local_rank, **kwargs): - super(DataLoaderX, self).__init__(**kwargs) - self.stream = torch.cuda.Stream(local_rank) - self.local_rank = local_rank - - def __iter__(self): - self.iter = super(DataLoaderX, self).__iter__() - self.iter = BackgroundGenerator(self.iter, self.local_rank) - self.preload() - return self - - def preload(self): - self.batch = next(self.iter, None) - if self.batch is None: - return None - with torch.cuda.stream(self.stream): - for k in range(len(self.batch)): - self.batch[k] = self.batch[k].to(device=self.local_rank, non_blocking=True) - - def __next__(self): - torch.cuda.current_stream().wait_stream(self.stream) - batch = self.batch - if batch is None: - raise StopIteration - self.preload() - return batch - - -class MXFaceDataset(Dataset): - def __init__(self, root_dir, local_rank): - super(MXFaceDataset, self).__init__() - self.transform = transforms.Compose( - [transforms.ToPILImage(), - transforms.RandomHorizontalFlip(), - transforms.ToTensor(), - transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]), - ]) - self.root_dir = root_dir - self.local_rank = local_rank - path_imgrec = os.path.join(root_dir, 'train.rec') - path_imgidx = os.path.join(root_dir, 'train.idx') - self.imgrec = mx.recordio.MXIndexedRecordIO(path_imgidx, path_imgrec, 'r') - s = self.imgrec.read_idx(0) - header, _ = mx.recordio.unpack(s) - if header.flag > 0: - self.header0 = (int(header.label[0]), int(header.label[1])) - self.imgidx = np.array(range(1, int(header.label[0]))) - else: - self.imgidx = np.array(list(self.imgrec.keys)) - - def __getitem__(self, index): - idx = self.imgidx[index] - s = self.imgrec.read_idx(idx) - header, img = mx.recordio.unpack(s) - label = header.label - if not isinstance(label, numbers.Number): - label = label[0] - label = torch.tensor(label, dtype=torch.long) - sample = mx.image.imdecode(img).asnumpy() - if self.transform is not None: - sample = self.transform(sample) - return sample, label - - def __len__(self): - return len(self.imgidx) - - -class SyntheticDataset(Dataset): - def __init__(self, local_rank): - super(SyntheticDataset, self).__init__() - img = np.random.randint(0, 255, size=(112, 112, 3), dtype=np.int32) - img = np.transpose(img, (2, 0, 1)) - img = torch.from_numpy(img).squeeze(0).float() - img = ((img / 255) - 0.5) / 0.5 - self.img = img - self.label = 1 - - def __getitem__(self, index): - return self.img, self.label - - def __len__(self): - return 1000000 diff --git a/src/face3d/models/arcface_torch/docs/eval.md b/src/face3d/models/arcface_torch/docs/eval.md deleted file mode 100644 index dd1d9e2..0000000 --- a/src/face3d/models/arcface_torch/docs/eval.md +++ /dev/null @@ -1,31 +0,0 @@ -## Eval on ICCV2021-MFR - -coming soon. - - -## Eval IJBC -You can eval ijbc with pytorch or onnx. - - -1. Eval IJBC With Onnx -```shell -CUDA_VISIBLE_DEVICES=0 python onnx_ijbc.py --model-root ms1mv3_arcface_r50 --image-path IJB_release/IJBC --result-dir ms1mv3_arcface_r50 -``` - -2. Eval IJBC With Pytorch -```shell -CUDA_VISIBLE_DEVICES=0,1 python eval_ijbc.py \ ---model-prefix ms1mv3_arcface_r50/backbone.pth \ ---image-path IJB_release/IJBC \ ---result-dir ms1mv3_arcface_r50 \ ---batch-size 128 \ ---job ms1mv3_arcface_r50 \ ---target IJBC \ ---network iresnet50 -``` - -## Inference - -```shell -python inference.py --weight ms1mv3_arcface_r50/backbone.pth --network r50 -``` diff --git a/src/face3d/models/arcface_torch/docs/install.md b/src/face3d/models/arcface_torch/docs/install.md deleted file mode 100644 index 6314a40..0000000 --- a/src/face3d/models/arcface_torch/docs/install.md +++ /dev/null @@ -1,51 +0,0 @@ -## v1.8.0 -### Linux and Windows -```shell -# CUDA 11.0 -pip --default-timeout=100 install torch==1.8.0+cu111 torchvision==0.9.0+cu111 torchaudio==0.8.0 -f https://download.pytorch.org/whl/torch_stable.html - -# CUDA 10.2 -pip --default-timeout=100 install torch==1.8.0 torchvision==0.9.0 torchaudio==0.8.0 - -# CPU only -pip --default-timeout=100 install torch==1.8.0+cpu torchvision==0.9.0+cpu torchaudio==0.8.0 -f https://download.pytorch.org/whl/torch_stable.html - -``` - - -## v1.7.1 -### Linux and Windows -```shell -# CUDA 11.0 -pip install torch==1.7.1+cu110 torchvision==0.8.2+cu110 torchaudio==0.7.2 -f https://download.pytorch.org/whl/torch_stable.html - -# CUDA 10.2 -pip install torch==1.7.1 torchvision==0.8.2 torchaudio==0.7.2 - -# CUDA 10.1 -pip install torch==1.7.1+cu101 torchvision==0.8.2+cu101 torchaudio==0.7.2 -f https://download.pytorch.org/whl/torch_stable.html - -# CUDA 9.2 -pip install torch==1.7.1+cu92 torchvision==0.8.2+cu92 torchaudio==0.7.2 -f https://download.pytorch.org/whl/torch_stable.html - -# CPU only -pip install torch==1.7.1+cpu torchvision==0.8.2+cpu torchaudio==0.7.2 -f https://download.pytorch.org/whl/torch_stable.html -``` - - -## v1.6.0 - -### Linux and Windows -```shell -# CUDA 10.2 -pip install torch==1.6.0 torchvision==0.7.0 - -# CUDA 10.1 -pip install torch==1.6.0+cu101 torchvision==0.7.0+cu101 -f https://download.pytorch.org/whl/torch_stable.html - -# CUDA 9.2 -pip install torch==1.6.0+cu92 torchvision==0.7.0+cu92 -f https://download.pytorch.org/whl/torch_stable.html - -# CPU only -pip install torch==1.6.0+cpu torchvision==0.7.0+cpu -f https://download.pytorch.org/whl/torch_stable.html -``` \ No newline at end of file diff --git a/src/face3d/models/arcface_torch/docs/modelzoo.md b/src/face3d/models/arcface_torch/docs/modelzoo.md deleted file mode 100644 index e69de29..0000000 diff --git a/src/face3d/models/arcface_torch/docs/speed_benchmark.md b/src/face3d/models/arcface_torch/docs/speed_benchmark.md deleted file mode 100644 index 055aee0..0000000 --- a/src/face3d/models/arcface_torch/docs/speed_benchmark.md +++ /dev/null @@ -1,93 +0,0 @@ -## Test Training Speed - -- Test Commands - -You need to use the following two commands to test the Partial FC training performance. -The number of identites is **3 millions** (synthetic data), turn mixed precision training on, backbone is resnet50, -batch size is 1024. -```shell -# Model Parallel -python -m torch.distributed.launch --nproc_per_node=8 --nnodes=1 --node_rank=0 --master_addr="127.0.0.1" --master_port=1234 train.py configs/3millions -# Partial FC 0.1 -python -m torch.distributed.launch --nproc_per_node=8 --nnodes=1 --node_rank=0 --master_addr="127.0.0.1" --master_port=1234 train.py configs/3millions_pfc -``` - -- GPU Memory - -``` -# (Model Parallel) gpustat -i -[0] Tesla V100-SXM2-32GB | 64'C, 94 % | 30338 / 32510 MB -[1] Tesla V100-SXM2-32GB | 60'C, 99 % | 28876 / 32510 MB -[2] Tesla V100-SXM2-32GB | 60'C, 99 % | 28872 / 32510 MB -[3] Tesla V100-SXM2-32GB | 69'C, 99 % | 28872 / 32510 MB -[4] Tesla V100-SXM2-32GB | 66'C, 99 % | 28888 / 32510 MB -[5] Tesla V100-SXM2-32GB | 60'C, 99 % | 28932 / 32510 MB -[6] Tesla V100-SXM2-32GB | 68'C, 100 % | 28916 / 32510 MB -[7] Tesla V100-SXM2-32GB | 65'C, 99 % | 28860 / 32510 MB - -# (Partial FC 0.1) gpustat -i -[0] Tesla V100-SXM2-32GB | 60'C, 95 % | 10488 / 32510 MB │······················· -[1] Tesla V100-SXM2-32GB | 60'C, 97 % | 10344 / 32510 MB │······················· -[2] Tesla V100-SXM2-32GB | 61'C, 95 % | 10340 / 32510 MB │······················· -[3] Tesla V100-SXM2-32GB | 66'C, 95 % | 10340 / 32510 MB │······················· -[4] Tesla V100-SXM2-32GB | 65'C, 94 % | 10356 / 32510 MB │······················· -[5] Tesla V100-SXM2-32GB | 61'C, 95 % | 10400 / 32510 MB │······················· -[6] Tesla V100-SXM2-32GB | 68'C, 96 % | 10384 / 32510 MB │······················· -[7] Tesla V100-SXM2-32GB | 64'C, 95 % | 10328 / 32510 MB │······················· -``` - -- Training Speed - -```python -# (Model Parallel) trainging.log -Training: Speed 2271.33 samples/sec Loss 1.1624 LearningRate 0.2000 Epoch: 0 Global Step: 100 -Training: Speed 2269.94 samples/sec Loss 0.0000 LearningRate 0.2000 Epoch: 0 Global Step: 150 -Training: Speed 2272.67 samples/sec Loss 0.0000 LearningRate 0.2000 Epoch: 0 Global Step: 200 -Training: Speed 2266.55 samples/sec Loss 0.0000 LearningRate 0.2000 Epoch: 0 Global Step: 250 -Training: Speed 2272.54 samples/sec Loss 0.0000 LearningRate 0.2000 Epoch: 0 Global Step: 300 - -# (Partial FC 0.1) trainging.log -Training: Speed 5299.56 samples/sec Loss 1.0965 LearningRate 0.2000 Epoch: 0 Global Step: 100 -Training: Speed 5296.37 samples/sec Loss 0.0000 LearningRate 0.2000 Epoch: 0 Global Step: 150 -Training: Speed 5304.37 samples/sec Loss 0.0000 LearningRate 0.2000 Epoch: 0 Global Step: 200 -Training: Speed 5274.43 samples/sec Loss 0.0000 LearningRate 0.2000 Epoch: 0 Global Step: 250 -Training: Speed 5300.10 samples/sec Loss 0.0000 LearningRate 0.2000 Epoch: 0 Global Step: 300 -``` - -In this test case, Partial FC 0.1 only use1 1/3 of the GPU memory of the model parallel, -and the training speed is 2.5 times faster than the model parallel. - - -## Speed Benchmark - -1. Training speed of different parallel methods (samples/second), Tesla V100 32GB * 8. (Larger is better) - -| Number of Identities in Dataset | Data Parallel | Model Parallel | Partial FC 0.1 | -| :--- | :--- | :--- | :--- | -|125000 | 4681 | 4824 | 5004 | -|250000 | 4047 | 4521 | 4976 | -|500000 | 3087 | 4013 | 4900 | -|1000000 | 2090 | 3449 | 4803 | -|1400000 | 1672 | 3043 | 4738 | -|2000000 | - | 2593 | 4626 | -|4000000 | - | 1748 | 4208 | -|5500000 | - | 1389 | 3975 | -|8000000 | - | - | 3565 | -|16000000 | - | - | 2679 | -|29000000 | - | - | 1855 | - -2. GPU memory cost of different parallel methods (GB per GPU), Tesla V100 32GB * 8. (Smaller is better) - -| Number of Identities in Dataset | Data Parallel | Model Parallel | Partial FC 0.1 | -| :--- | :--- | :--- | :--- | -|125000 | 7358 | 5306 | 4868 | -|250000 | 9940 | 5826 | 5004 | -|500000 | 14220 | 7114 | 5202 | -|1000000 | 23708 | 9966 | 5620 | -|1400000 | 32252 | 11178 | 6056 | -|2000000 | - | 13978 | 6472 | -|4000000 | - | 23238 | 8284 | -|5500000 | - | 32188 | 9854 | -|8000000 | - | - | 12310 | -|16000000 | - | - | 19950 | -|29000000 | - | - | 32324 | diff --git a/src/face3d/models/arcface_torch/eval/__init__.py b/src/face3d/models/arcface_torch/eval/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/face3d/models/arcface_torch/eval/verification.py b/src/face3d/models/arcface_torch/eval/verification.py deleted file mode 100644 index 253343b..0000000 --- a/src/face3d/models/arcface_torch/eval/verification.py +++ /dev/null @@ -1,407 +0,0 @@ -"""Helper for evaluation on the Labeled Faces in the Wild dataset -""" - -# MIT License -# -# Copyright (c) 2016 David Sandberg -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - - -import datetime -import os -import pickle - -import mxnet as mx -import numpy as np -import sklearn -import torch -from mxnet import ndarray as nd -from scipy import interpolate -from sklearn.decomposition import PCA -from sklearn.model_selection import KFold - - -class LFold: - def __init__(self, n_splits=2, shuffle=False): - self.n_splits = n_splits - if self.n_splits > 1: - self.k_fold = KFold(n_splits=n_splits, shuffle=shuffle) - - def split(self, indices): - if self.n_splits > 1: - return self.k_fold.split(indices) - else: - return [(indices, indices)] - - -def calculate_roc(thresholds, - embeddings1, - embeddings2, - actual_issame, - nrof_folds=10, - pca=0): - assert (embeddings1.shape[0] == embeddings2.shape[0]) - assert (embeddings1.shape[1] == embeddings2.shape[1]) - nrof_pairs = min(len(actual_issame), embeddings1.shape[0]) - nrof_thresholds = len(thresholds) - k_fold = LFold(n_splits=nrof_folds, shuffle=False) - - tprs = np.zeros((nrof_folds, nrof_thresholds)) - fprs = np.zeros((nrof_folds, nrof_thresholds)) - accuracy = np.zeros((nrof_folds)) - indices = np.arange(nrof_pairs) - - if pca == 0: - diff = np.subtract(embeddings1, embeddings2) - dist = np.sum(np.square(diff), 1) - - for fold_idx, (train_set, test_set) in enumerate(k_fold.split(indices)): - if pca > 0: - print('doing pca on', fold_idx) - embed1_train = embeddings1[train_set] - embed2_train = embeddings2[train_set] - _embed_train = np.concatenate((embed1_train, embed2_train), axis=0) - pca_model = PCA(n_components=pca) - pca_model.fit(_embed_train) - embed1 = pca_model.transform(embeddings1) - embed2 = pca_model.transform(embeddings2) - embed1 = sklearn.preprocessing.normalize(embed1) - embed2 = sklearn.preprocessing.normalize(embed2) - diff = np.subtract(embed1, embed2) - dist = np.sum(np.square(diff), 1) - - # Find the best threshold for the fold - acc_train = np.zeros((nrof_thresholds)) - for threshold_idx, threshold in enumerate(thresholds): - _, _, acc_train[threshold_idx] = calculate_accuracy( - threshold, dist[train_set], actual_issame[train_set]) - best_threshold_index = np.argmax(acc_train) - for threshold_idx, threshold in enumerate(thresholds): - tprs[fold_idx, threshold_idx], fprs[fold_idx, threshold_idx], _ = calculate_accuracy( - threshold, dist[test_set], - actual_issame[test_set]) - _, _, accuracy[fold_idx] = calculate_accuracy( - thresholds[best_threshold_index], dist[test_set], - actual_issame[test_set]) - - tpr = np.mean(tprs, 0) - fpr = np.mean(fprs, 0) - return tpr, fpr, accuracy - - -def calculate_accuracy(threshold, dist, actual_issame): - predict_issame = np.less(dist, threshold) - tp = np.sum(np.logical_and(predict_issame, actual_issame)) - fp = np.sum(np.logical_and(predict_issame, np.logical_not(actual_issame))) - tn = np.sum( - np.logical_and(np.logical_not(predict_issame), - np.logical_not(actual_issame))) - fn = np.sum(np.logical_and(np.logical_not(predict_issame), actual_issame)) - - tpr = 0 if (tp + fn == 0) else float(tp) / float(tp + fn) - fpr = 0 if (fp + tn == 0) else float(fp) / float(fp + tn) - acc = float(tp + tn) / dist.size - return tpr, fpr, acc - - -def calculate_val(thresholds, - embeddings1, - embeddings2, - actual_issame, - far_target, - nrof_folds=10): - assert (embeddings1.shape[0] == embeddings2.shape[0]) - assert (embeddings1.shape[1] == embeddings2.shape[1]) - nrof_pairs = min(len(actual_issame), embeddings1.shape[0]) - nrof_thresholds = len(thresholds) - k_fold = LFold(n_splits=nrof_folds, shuffle=False) - - val = np.zeros(nrof_folds) - far = np.zeros(nrof_folds) - - diff = np.subtract(embeddings1, embeddings2) - dist = np.sum(np.square(diff), 1) - indices = np.arange(nrof_pairs) - - for fold_idx, (train_set, test_set) in enumerate(k_fold.split(indices)): - - # Find the threshold that gives FAR = far_target - far_train = np.zeros(nrof_thresholds) - for threshold_idx, threshold in enumerate(thresholds): - _, far_train[threshold_idx] = calculate_val_far( - threshold, dist[train_set], actual_issame[train_set]) - if np.max(far_train) >= far_target: - f = interpolate.interp1d(far_train, thresholds, kind='slinear') - threshold = f(far_target) - else: - threshold = 0.0 - - val[fold_idx], far[fold_idx] = calculate_val_far( - threshold, dist[test_set], actual_issame[test_set]) - - val_mean = np.mean(val) - far_mean = np.mean(far) - val_std = np.std(val) - return val_mean, val_std, far_mean - - -def calculate_val_far(threshold, dist, actual_issame): - predict_issame = np.less(dist, threshold) - true_accept = np.sum(np.logical_and(predict_issame, actual_issame)) - false_accept = np.sum( - np.logical_and(predict_issame, np.logical_not(actual_issame))) - n_same = np.sum(actual_issame) - n_diff = np.sum(np.logical_not(actual_issame)) - # print(true_accept, false_accept) - # print(n_same, n_diff) - val = float(true_accept) / float(n_same) - far = float(false_accept) / float(n_diff) - return val, far - - -def evaluate(embeddings, actual_issame, nrof_folds=10, pca=0): - # Calculate evaluation metrics - thresholds = np.arange(0, 4, 0.01) - embeddings1 = embeddings[0::2] - embeddings2 = embeddings[1::2] - tpr, fpr, accuracy = calculate_roc(thresholds, - embeddings1, - embeddings2, - np.asarray(actual_issame), - nrof_folds=nrof_folds, - pca=pca) - thresholds = np.arange(0, 4, 0.001) - val, val_std, far = calculate_val(thresholds, - embeddings1, - embeddings2, - np.asarray(actual_issame), - 1e-3, - nrof_folds=nrof_folds) - return tpr, fpr, accuracy, val, val_std, far - -@torch.no_grad() -def load_bin(path, image_size): - try: - with open(path, 'rb') as f: - bins, issame_list = pickle.load(f) # py2 - except UnicodeDecodeError as e: - with open(path, 'rb') as f: - bins, issame_list = pickle.load(f, encoding='bytes') # py3 - data_list = [] - for flip in [0, 1]: - data = torch.empty((len(issame_list) * 2, 3, image_size[0], image_size[1])) - data_list.append(data) - for idx in range(len(issame_list) * 2): - _bin = bins[idx] - img = mx.image.imdecode(_bin) - if img.shape[1] != image_size[0]: - img = mx.image.resize_short(img, image_size[0]) - img = nd.transpose(img, axes=(2, 0, 1)) - for flip in [0, 1]: - if flip == 1: - img = mx.ndarray.flip(data=img, axis=2) - data_list[flip][idx][:] = torch.from_numpy(img.asnumpy()) - if idx % 1000 == 0: - print('loading bin', idx) - print(data_list[0].shape) - return data_list, issame_list - -@torch.no_grad() -def test(data_set, backbone, batch_size, nfolds=10): - print('testing verification..') - data_list = data_set[0] - issame_list = data_set[1] - embeddings_list = [] - time_consumed = 0.0 - for i in range(len(data_list)): - data = data_list[i] - embeddings = None - ba = 0 - while ba < data.shape[0]: - bb = min(ba + batch_size, data.shape[0]) - count = bb - ba - _data = data[bb - batch_size: bb] - time0 = datetime.datetime.now() - img = ((_data / 255) - 0.5) / 0.5 - net_out: torch.Tensor = backbone(img) - _embeddings = net_out.detach().cpu().numpy() - time_now = datetime.datetime.now() - diff = time_now - time0 - time_consumed += diff.total_seconds() - if embeddings is None: - embeddings = np.zeros((data.shape[0], _embeddings.shape[1])) - embeddings[ba:bb, :] = _embeddings[(batch_size - count):, :] - ba = bb - embeddings_list.append(embeddings) - - _xnorm = 0.0 - _xnorm_cnt = 0 - for embed in embeddings_list: - for i in range(embed.shape[0]): - _em = embed[i] - _norm = np.linalg.norm(_em) - _xnorm += _norm - _xnorm_cnt += 1 - _xnorm /= _xnorm_cnt - - acc1 = 0.0 - std1 = 0.0 - embeddings = embeddings_list[0] + embeddings_list[1] - embeddings = sklearn.preprocessing.normalize(embeddings) - print(embeddings.shape) - print('infer time', time_consumed) - _, _, accuracy, val, val_std, far = evaluate(embeddings, issame_list, nrof_folds=nfolds) - acc2, std2 = np.mean(accuracy), np.std(accuracy) - return acc1, std1, acc2, std2, _xnorm, embeddings_list - - -def dumpR(data_set, - backbone, - batch_size, - name='', - data_extra=None, - label_shape=None): - print('dump verification embedding..') - data_list = data_set[0] - issame_list = data_set[1] - embeddings_list = [] - time_consumed = 0.0 - for i in range(len(data_list)): - data = data_list[i] - embeddings = None - ba = 0 - while ba < data.shape[0]: - bb = min(ba + batch_size, data.shape[0]) - count = bb - ba - - _data = nd.slice_axis(data, axis=0, begin=bb - batch_size, end=bb) - time0 = datetime.datetime.now() - if data_extra is None: - db = mx.io.DataBatch(data=(_data,), label=(_label,)) - else: - db = mx.io.DataBatch(data=(_data, _data_extra), - label=(_label,)) - model.forward(db, is_train=False) - net_out = model.get_outputs() - _embeddings = net_out[0].asnumpy() - time_now = datetime.datetime.now() - diff = time_now - time0 - time_consumed += diff.total_seconds() - if embeddings is None: - embeddings = np.zeros((data.shape[0], _embeddings.shape[1])) - embeddings[ba:bb, :] = _embeddings[(batch_size - count):, :] - ba = bb - embeddings_list.append(embeddings) - embeddings = embeddings_list[0] + embeddings_list[1] - embeddings = sklearn.preprocessing.normalize(embeddings) - actual_issame = np.asarray(issame_list) - outname = os.path.join('temp.bin') - with open(outname, 'wb') as f: - pickle.dump((embeddings, issame_list), - f, - protocol=pickle.HIGHEST_PROTOCOL) - - -# if __name__ == '__main__': -# -# parser = argparse.ArgumentParser(description='do verification') -# # general -# parser.add_argument('--data-dir', default='', help='') -# parser.add_argument('--model', -# default='../model/softmax,50', -# help='path to load model.') -# parser.add_argument('--target', -# default='lfw,cfp_ff,cfp_fp,agedb_30', -# help='test targets.') -# parser.add_argument('--gpu', default=0, type=int, help='gpu id') -# parser.add_argument('--batch-size', default=32, type=int, help='') -# parser.add_argument('--max', default='', type=str, help='') -# parser.add_argument('--mode', default=0, type=int, help='') -# parser.add_argument('--nfolds', default=10, type=int, help='') -# args = parser.parse_args() -# image_size = [112, 112] -# print('image_size', image_size) -# ctx = mx.gpu(args.gpu) -# nets = [] -# vec = args.model.split(',') -# prefix = args.model.split(',')[0] -# epochs = [] -# if len(vec) == 1: -# pdir = os.path.dirname(prefix) -# for fname in os.listdir(pdir): -# if not fname.endswith('.params'): -# continue -# _file = os.path.join(pdir, fname) -# if _file.startswith(prefix): -# epoch = int(fname.split('.')[0].split('-')[1]) -# epochs.append(epoch) -# epochs = sorted(epochs, reverse=True) -# if len(args.max) > 0: -# _max = [int(x) for x in args.max.split(',')] -# assert len(_max) == 2 -# if len(epochs) > _max[1]: -# epochs = epochs[_max[0]:_max[1]] -# -# else: -# epochs = [int(x) for x in vec[1].split('|')] -# print('model number', len(epochs)) -# time0 = datetime.datetime.now() -# for epoch in epochs: -# print('loading', prefix, epoch) -# sym, arg_params, aux_params = mx.model.load_checkpoint(prefix, epoch) -# # arg_params, aux_params = ch_dev(arg_params, aux_params, ctx) -# all_layers = sym.get_internals() -# sym = all_layers['fc1_output'] -# model = mx.mod.Module(symbol=sym, context=ctx, label_names=None) -# # model.bind(data_shapes=[('data', (args.batch_size, 3, image_size[0], image_size[1]))], label_shapes=[('softmax_label', (args.batch_size,))]) -# model.bind(data_shapes=[('data', (args.batch_size, 3, image_size[0], -# image_size[1]))]) -# model.set_params(arg_params, aux_params) -# nets.append(model) -# time_now = datetime.datetime.now() -# diff = time_now - time0 -# print('model loading time', diff.total_seconds()) -# -# ver_list = [] -# ver_name_list = [] -# for name in args.target.split(','): -# path = os.path.join(args.data_dir, name + ".bin") -# if os.path.exists(path): -# print('loading.. ', name) -# data_set = load_bin(path, image_size) -# ver_list.append(data_set) -# ver_name_list.append(name) -# -# if args.mode == 0: -# for i in range(len(ver_list)): -# results = [] -# for model in nets: -# acc1, std1, acc2, std2, xnorm, embeddings_list = test( -# ver_list[i], model, args.batch_size, args.nfolds) -# print('[%s]XNorm: %f' % (ver_name_list[i], xnorm)) -# print('[%s]Accuracy: %1.5f+-%1.5f' % (ver_name_list[i], acc1, std1)) -# print('[%s]Accuracy-Flip: %1.5f+-%1.5f' % (ver_name_list[i], acc2, std2)) -# results.append(acc2) -# print('Max of [%s] is %1.5f' % (ver_name_list[i], np.max(results))) -# elif args.mode == 1: -# raise ValueError -# else: -# model = nets[0] -# dumpR(ver_list[0], model, args.batch_size, args.target) diff --git a/src/face3d/models/arcface_torch/eval_ijbc.py b/src/face3d/models/arcface_torch/eval_ijbc.py deleted file mode 100644 index 9c5a650..0000000 --- a/src/face3d/models/arcface_torch/eval_ijbc.py +++ /dev/null @@ -1,483 +0,0 @@ -# coding: utf-8 - -import os -import pickle - -import matplotlib -import pandas as pd - -matplotlib.use('Agg') -import matplotlib.pyplot as plt -import timeit -import sklearn -import argparse -import cv2 -import numpy as np -import torch -from skimage import transform as trans -from backbones import get_model -from sklearn.metrics import roc_curve, auc - -from menpo.visualize.viewmatplotlib import sample_colours_from_colourmap -from prettytable import PrettyTable -from pathlib import Path - -import sys -import warnings - -sys.path.insert(0, "../") -warnings.filterwarnings("ignore") - -parser = argparse.ArgumentParser(description='do ijb test') -# general -parser.add_argument('--model-prefix', default='', help='path to load model.') -parser.add_argument('--image-path', default='', type=str, help='') -parser.add_argument('--result-dir', default='.', type=str, help='') -parser.add_argument('--batch-size', default=128, type=int, help='') -parser.add_argument('--network', default='iresnet50', type=str, help='') -parser.add_argument('--job', default='insightface', type=str, help='job name') -parser.add_argument('--target', default='IJBC', type=str, help='target, set to IJBC or IJBB') -args = parser.parse_args() - -target = args.target -model_path = args.model_prefix -image_path = args.image_path -result_dir = args.result_dir -gpu_id = None -use_norm_score = True # if Ture, TestMode(N1) -use_detector_score = True # if Ture, TestMode(D1) -use_flip_test = True # if Ture, TestMode(F1) -job = args.job -batch_size = args.batch_size - - -class Embedding(object): - def __init__(self, prefix, data_shape, batch_size=1): - image_size = (112, 112) - self.image_size = image_size - weight = torch.load(prefix) - resnet = get_model(args.network, dropout=0, fp16=False).cuda() - resnet.load_state_dict(weight) - model = torch.nn.DataParallel(resnet) - self.model = model - self.model.eval() - src = np.array([ - [30.2946, 51.6963], - [65.5318, 51.5014], - [48.0252, 71.7366], - [33.5493, 92.3655], - [62.7299, 92.2041]], dtype=np.float32) - src[:, 0] += 8.0 - self.src = src - self.batch_size = batch_size - self.data_shape = data_shape - - def get(self, rimg, landmark): - - assert landmark.shape[0] == 68 or landmark.shape[0] == 5 - assert landmark.shape[1] == 2 - if landmark.shape[0] == 68: - landmark5 = np.zeros((5, 2), dtype=np.float32) - landmark5[0] = (landmark[36] + landmark[39]) / 2 - landmark5[1] = (landmark[42] + landmark[45]) / 2 - landmark5[2] = landmark[30] - landmark5[3] = landmark[48] - landmark5[4] = landmark[54] - else: - landmark5 = landmark - tform = trans.SimilarityTransform() - tform.estimate(landmark5, self.src) - M = tform.params[0:2, :] - img = cv2.warpAffine(rimg, - M, (self.image_size[1], self.image_size[0]), - borderValue=0.0) - img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) - img_flip = np.fliplr(img) - img = np.transpose(img, (2, 0, 1)) # 3*112*112, RGB - img_flip = np.transpose(img_flip, (2, 0, 1)) - input_blob = np.zeros((2, 3, self.image_size[1], self.image_size[0]), dtype=np.uint8) - input_blob[0] = img - input_blob[1] = img_flip - return input_blob - - @torch.no_grad() - def forward_db(self, batch_data): - imgs = torch.Tensor(batch_data).cuda() - imgs.div_(255).sub_(0.5).div_(0.5) - feat = self.model(imgs) - feat = feat.reshape([self.batch_size, 2 * feat.shape[1]]) - return feat.cpu().numpy() - - -# 将一个list尽量均分成n份,限制len(list)==n,份数大于原list内元素个数则分配空list[] -def divideIntoNstrand(listTemp, n): - twoList = [[] for i in range(n)] - for i, e in enumerate(listTemp): - twoList[i % n].append(e) - return twoList - - -def read_template_media_list(path): - # ijb_meta = np.loadtxt(path, dtype=str) - ijb_meta = pd.read_csv(path, sep=' ', header=None).values - templates = ijb_meta[:, 1].astype(np.int) - medias = ijb_meta[:, 2].astype(np.int) - return templates, medias - - -# In[ ]: - - -def read_template_pair_list(path): - # pairs = np.loadtxt(path, dtype=str) - pairs = pd.read_csv(path, sep=' ', header=None).values - # print(pairs.shape) - # print(pairs[:, 0].astype(np.int)) - t1 = pairs[:, 0].astype(np.int) - t2 = pairs[:, 1].astype(np.int) - label = pairs[:, 2].astype(np.int) - return t1, t2, label - - -# In[ ]: - - -def read_image_feature(path): - with open(path, 'rb') as fid: - img_feats = pickle.load(fid) - return img_feats - - -# In[ ]: - - -def get_image_feature(img_path, files_list, model_path, epoch, gpu_id): - batch_size = args.batch_size - data_shape = (3, 112, 112) - - files = files_list - print('files:', len(files)) - rare_size = len(files) % batch_size - faceness_scores = [] - batch = 0 - img_feats = np.empty((len(files), 1024), dtype=np.float32) - - batch_data = np.empty((2 * batch_size, 3, 112, 112)) - embedding = Embedding(model_path, data_shape, batch_size) - for img_index, each_line in enumerate(files[:len(files) - rare_size]): - name_lmk_score = each_line.strip().split(' ') - img_name = os.path.join(img_path, name_lmk_score[0]) - img = cv2.imread(img_name) - lmk = np.array([float(x) for x in name_lmk_score[1:-1]], - dtype=np.float32) - lmk = lmk.reshape((5, 2)) - input_blob = embedding.get(img, lmk) - - batch_data[2 * (img_index - batch * batch_size)][:] = input_blob[0] - batch_data[2 * (img_index - batch * batch_size) + 1][:] = input_blob[1] - if (img_index + 1) % batch_size == 0: - print('batch', batch) - img_feats[batch * batch_size:batch * batch_size + - batch_size][:] = embedding.forward_db(batch_data) - batch += 1 - faceness_scores.append(name_lmk_score[-1]) - - batch_data = np.empty((2 * rare_size, 3, 112, 112)) - embedding = Embedding(model_path, data_shape, rare_size) - for img_index, each_line in enumerate(files[len(files) - rare_size:]): - name_lmk_score = each_line.strip().split(' ') - img_name = os.path.join(img_path, name_lmk_score[0]) - img = cv2.imread(img_name) - lmk = np.array([float(x) for x in name_lmk_score[1:-1]], - dtype=np.float32) - lmk = lmk.reshape((5, 2)) - input_blob = embedding.get(img, lmk) - batch_data[2 * img_index][:] = input_blob[0] - batch_data[2 * img_index + 1][:] = input_blob[1] - if (img_index + 1) % rare_size == 0: - print('batch', batch) - img_feats[len(files) - - rare_size:][:] = embedding.forward_db(batch_data) - batch += 1 - faceness_scores.append(name_lmk_score[-1]) - faceness_scores = np.array(faceness_scores).astype(np.float32) - # img_feats = np.ones( (len(files), 1024), dtype=np.float32) * 0.01 - # faceness_scores = np.ones( (len(files), ), dtype=np.float32 ) - return img_feats, faceness_scores - - -# In[ ]: - - -def image2template_feature(img_feats=None, templates=None, medias=None): - # ========================================================== - # 1. face image feature l2 normalization. img_feats:[number_image x feats_dim] - # 2. compute media feature. - # 3. compute template feature. - # ========================================================== - unique_templates = np.unique(templates) - template_feats = np.zeros((len(unique_templates), img_feats.shape[1])) - - for count_template, uqt in enumerate(unique_templates): - - (ind_t,) = np.where(templates == uqt) - face_norm_feats = img_feats[ind_t] - face_medias = medias[ind_t] - unique_medias, unique_media_counts = np.unique(face_medias, - return_counts=True) - media_norm_feats = [] - for u, ct in zip(unique_medias, unique_media_counts): - (ind_m,) = np.where(face_medias == u) - if ct == 1: - media_norm_feats += [face_norm_feats[ind_m]] - else: # image features from the same video will be aggregated into one feature - media_norm_feats += [ - np.mean(face_norm_feats[ind_m], axis=0, keepdims=True) - ] - media_norm_feats = np.array(media_norm_feats) - # media_norm_feats = media_norm_feats / np.sqrt(np.sum(media_norm_feats ** 2, -1, keepdims=True)) - template_feats[count_template] = np.sum(media_norm_feats, axis=0) - if count_template % 2000 == 0: - print('Finish Calculating {} template features.'.format( - count_template)) - # template_norm_feats = template_feats / np.sqrt(np.sum(template_feats ** 2, -1, keepdims=True)) - template_norm_feats = sklearn.preprocessing.normalize(template_feats) - # print(template_norm_feats.shape) - return template_norm_feats, unique_templates - - -# In[ ]: - - -def verification(template_norm_feats=None, - unique_templates=None, - p1=None, - p2=None): - # ========================================================== - # Compute set-to-set Similarity Score. - # ========================================================== - template2id = np.zeros((max(unique_templates) + 1, 1), dtype=int) - for count_template, uqt in enumerate(unique_templates): - template2id[uqt] = count_template - - score = np.zeros((len(p1),)) # save cosine distance between pairs - - total_pairs = np.array(range(len(p1))) - batchsize = 100000 # small batchsize instead of all pairs in one batch due to the memory limiation - sublists = [ - total_pairs[i:i + batchsize] for i in range(0, len(p1), batchsize) - ] - total_sublists = len(sublists) - for c, s in enumerate(sublists): - feat1 = template_norm_feats[template2id[p1[s]]] - feat2 = template_norm_feats[template2id[p2[s]]] - similarity_score = np.sum(feat1 * feat2, -1) - score[s] = similarity_score.flatten() - if c % 10 == 0: - print('Finish {}/{} pairs.'.format(c, total_sublists)) - return score - - -# In[ ]: -def verification2(template_norm_feats=None, - unique_templates=None, - p1=None, - p2=None): - template2id = np.zeros((max(unique_templates) + 1, 1), dtype=int) - for count_template, uqt in enumerate(unique_templates): - template2id[uqt] = count_template - score = np.zeros((len(p1),)) # save cosine distance between pairs - total_pairs = np.array(range(len(p1))) - batchsize = 100000 # small batchsize instead of all pairs in one batch due to the memory limiation - sublists = [ - total_pairs[i:i + batchsize] for i in range(0, len(p1), batchsize) - ] - total_sublists = len(sublists) - for c, s in enumerate(sublists): - feat1 = template_norm_feats[template2id[p1[s]]] - feat2 = template_norm_feats[template2id[p2[s]]] - similarity_score = np.sum(feat1 * feat2, -1) - score[s] = similarity_score.flatten() - if c % 10 == 0: - print('Finish {}/{} pairs.'.format(c, total_sublists)) - return score - - -def read_score(path): - with open(path, 'rb') as fid: - img_feats = pickle.load(fid) - return img_feats - - -# # Step1: Load Meta Data - -# In[ ]: - -assert target == 'IJBC' or target == 'IJBB' - -# ============================================================= -# load image and template relationships for template feature embedding -# tid --> template id, mid --> media id -# format: -# image_name tid mid -# ============================================================= -start = timeit.default_timer() -templates, medias = read_template_media_list( - os.path.join('%s/meta' % image_path, - '%s_face_tid_mid.txt' % target.lower())) -stop = timeit.default_timer() -print('Time: %.2f s. ' % (stop - start)) - -# In[ ]: - -# ============================================================= -# load template pairs for template-to-template verification -# tid : template id, label : 1/0 -# format: -# tid_1 tid_2 label -# ============================================================= -start = timeit.default_timer() -p1, p2, label = read_template_pair_list( - os.path.join('%s/meta' % image_path, - '%s_template_pair_label.txt' % target.lower())) -stop = timeit.default_timer() -print('Time: %.2f s. ' % (stop - start)) - -# # Step 2: Get Image Features - -# In[ ]: - -# ============================================================= -# load image features -# format: -# img_feats: [image_num x feats_dim] (227630, 512) -# ============================================================= -start = timeit.default_timer() -img_path = '%s/loose_crop' % image_path -img_list_path = '%s/meta/%s_name_5pts_score.txt' % (image_path, target.lower()) -img_list = open(img_list_path) -files = img_list.readlines() -# files_list = divideIntoNstrand(files, rank_size) -files_list = files - -# img_feats -# for i in range(rank_size): -img_feats, faceness_scores = get_image_feature(img_path, files_list, - model_path, 0, gpu_id) -stop = timeit.default_timer() -print('Time: %.2f s. ' % (stop - start)) -print('Feature Shape: ({} , {}) .'.format(img_feats.shape[0], - img_feats.shape[1])) - -# # Step3: Get Template Features - -# In[ ]: - -# ============================================================= -# compute template features from image features. -# ============================================================= -start = timeit.default_timer() -# ========================================================== -# Norm feature before aggregation into template feature? -# Feature norm from embedding network and faceness score are able to decrease weights for noise samples (not face). -# ========================================================== -# 1. FaceScore (Feature Norm) -# 2. FaceScore (Detector) - -if use_flip_test: - # concat --- F1 - # img_input_feats = img_feats - # add --- F2 - img_input_feats = img_feats[:, 0:img_feats.shape[1] // - 2] + img_feats[:, img_feats.shape[1] // 2:] -else: - img_input_feats = img_feats[:, 0:img_feats.shape[1] // 2] - -if use_norm_score: - img_input_feats = img_input_feats -else: - # normalise features to remove norm information - img_input_feats = img_input_feats / np.sqrt( - np.sum(img_input_feats ** 2, -1, keepdims=True)) - -if use_detector_score: - print(img_input_feats.shape, faceness_scores.shape) - img_input_feats = img_input_feats * faceness_scores[:, np.newaxis] -else: - img_input_feats = img_input_feats - -template_norm_feats, unique_templates = image2template_feature( - img_input_feats, templates, medias) -stop = timeit.default_timer() -print('Time: %.2f s. ' % (stop - start)) - -# # Step 4: Get Template Similarity Scores - -# In[ ]: - -# ============================================================= -# compute verification scores between template pairs. -# ============================================================= -start = timeit.default_timer() -score = verification(template_norm_feats, unique_templates, p1, p2) -stop = timeit.default_timer() -print('Time: %.2f s. ' % (stop - start)) - -# In[ ]: -save_path = os.path.join(result_dir, args.job) -# save_path = result_dir + '/%s_result' % target - -if not os.path.exists(save_path): - os.makedirs(save_path) - -score_save_file = os.path.join(save_path, "%s.npy" % target.lower()) -np.save(score_save_file, score) - -# # Step 5: Get ROC Curves and TPR@FPR Table - -# In[ ]: - -files = [score_save_file] -methods = [] -scores = [] -for file in files: - methods.append(Path(file).stem) - scores.append(np.load(file)) - -methods = np.array(methods) -scores = dict(zip(methods, scores)) -colours = dict( - zip(methods, sample_colours_from_colourmap(methods.shape[0], 'Set2'))) -x_labels = [10 ** -6, 10 ** -5, 10 ** -4, 10 ** -3, 10 ** -2, 10 ** -1] -tpr_fpr_table = PrettyTable(['Methods'] + [str(x) for x in x_labels]) -fig = plt.figure() -for method in methods: - fpr, tpr, _ = roc_curve(label, scores[method]) - roc_auc = auc(fpr, tpr) - fpr = np.flipud(fpr) - tpr = np.flipud(tpr) # select largest tpr at same fpr - plt.plot(fpr, - tpr, - color=colours[method], - lw=1, - label=('[%s (AUC = %0.4f %%)]' % - (method.split('-')[-1], roc_auc * 100))) - tpr_fpr_row = [] - tpr_fpr_row.append("%s-%s" % (method, target)) - for fpr_iter in np.arange(len(x_labels)): - _, min_index = min( - list(zip(abs(fpr - x_labels[fpr_iter]), range(len(fpr))))) - tpr_fpr_row.append('%.2f' % (tpr[min_index] * 100)) - tpr_fpr_table.add_row(tpr_fpr_row) -plt.xlim([10 ** -6, 0.1]) -plt.ylim([0.3, 1.0]) -plt.grid(linestyle='--', linewidth=1) -plt.xticks(x_labels) -plt.yticks(np.linspace(0.3, 1.0, 8, endpoint=True)) -plt.xscale('log') -plt.xlabel('False Positive Rate') -plt.ylabel('True Positive Rate') -plt.title('ROC on IJB') -plt.legend(loc="lower right") -fig.savefig(os.path.join(save_path, '%s.pdf' % target.lower())) -print(tpr_fpr_table) diff --git a/src/face3d/models/arcface_torch/inference.py b/src/face3d/models/arcface_torch/inference.py deleted file mode 100644 index 3e5156e..0000000 --- a/src/face3d/models/arcface_torch/inference.py +++ /dev/null @@ -1,35 +0,0 @@ -import argparse - -import cv2 -import numpy as np -import torch - -from backbones import get_model - - -@torch.no_grad() -def inference(weight, name, img): - if img is None: - img = np.random.randint(0, 255, size=(112, 112, 3), dtype=np.uint8) - else: - img = cv2.imread(img) - img = cv2.resize(img, (112, 112)) - - img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) - img = np.transpose(img, (2, 0, 1)) - img = torch.from_numpy(img).unsqueeze(0).float() - img.div_(255).sub_(0.5).div_(0.5) - net = get_model(name, fp16=False) - net.load_state_dict(torch.load(weight)) - net.eval() - feat = net(img).numpy() - print(feat) - - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description='PyTorch ArcFace Training') - parser.add_argument('--network', type=str, default='r50', help='backbone network') - parser.add_argument('--weight', type=str, default='') - parser.add_argument('--img', type=str, default=None) - args = parser.parse_args() - inference(args.weight, args.network, args.img) diff --git a/src/face3d/models/arcface_torch/losses.py b/src/face3d/models/arcface_torch/losses.py deleted file mode 100644 index 87aeaa1..0000000 --- a/src/face3d/models/arcface_torch/losses.py +++ /dev/null @@ -1,42 +0,0 @@ -import torch -from torch import nn - - -def get_loss(name): - if name == "cosface": - return CosFace() - elif name == "arcface": - return ArcFace() - else: - raise ValueError() - - -class CosFace(nn.Module): - def __init__(self, s=64.0, m=0.40): - super(CosFace, self).__init__() - self.s = s - self.m = m - - def forward(self, cosine, label): - index = torch.where(label != -1)[0] - m_hot = torch.zeros(index.size()[0], cosine.size()[1], device=cosine.device) - m_hot.scatter_(1, label[index, None], self.m) - cosine[index] -= m_hot - ret = cosine * self.s - return ret - - -class ArcFace(nn.Module): - def __init__(self, s=64.0, m=0.5): - super(ArcFace, self).__init__() - self.s = s - self.m = m - - def forward(self, cosine: torch.Tensor, label): - index = torch.where(label != -1)[0] - m_hot = torch.zeros(index.size()[0], cosine.size()[1], device=cosine.device) - m_hot.scatter_(1, label[index, None], self.m) - cosine.acos_() - cosine[index] += m_hot - cosine.cos_().mul_(self.s) - return cosine diff --git a/src/face3d/models/arcface_torch/onnx_helper.py b/src/face3d/models/arcface_torch/onnx_helper.py deleted file mode 100644 index ca922ca..0000000 --- a/src/face3d/models/arcface_torch/onnx_helper.py +++ /dev/null @@ -1,250 +0,0 @@ -from __future__ import division -import datetime -import os -import os.path as osp -import glob -import numpy as np -import cv2 -import sys -import onnxruntime -import onnx -import argparse -from onnx import numpy_helper -from insightface.data import get_image - -class ArcFaceORT: - def __init__(self, model_path, cpu=False): - self.model_path = model_path - # providers = None will use available provider, for onnxruntime-gpu it will be "CUDAExecutionProvider" - self.providers = ['CPUExecutionProvider'] if cpu else None - - #input_size is (w,h), return error message, return None if success - def check(self, track='cfat', test_img = None): - #default is cfat - max_model_size_mb=1024 - max_feat_dim=512 - max_time_cost=15 - if track.startswith('ms1m'): - max_model_size_mb=1024 - max_feat_dim=512 - max_time_cost=10 - elif track.startswith('glint'): - max_model_size_mb=1024 - max_feat_dim=1024 - max_time_cost=20 - elif track.startswith('cfat'): - max_model_size_mb = 1024 - max_feat_dim = 512 - max_time_cost = 15 - elif track.startswith('unconstrained'): - max_model_size_mb=1024 - max_feat_dim=1024 - max_time_cost=30 - else: - return "track not found" - - if not os.path.exists(self.model_path): - return "model_path not exists" - if not os.path.isdir(self.model_path): - return "model_path should be directory" - onnx_files = [] - for _file in os.listdir(self.model_path): - if _file.endswith('.onnx'): - onnx_files.append(osp.join(self.model_path, _file)) - if len(onnx_files)==0: - return "do not have onnx files" - self.model_file = sorted(onnx_files)[-1] - print('use onnx-model:', self.model_file) - try: - session = onnxruntime.InferenceSession(self.model_file, providers=self.providers) - except: - return "load onnx failed" - input_cfg = session.get_inputs()[0] - input_shape = input_cfg.shape - print('input-shape:', input_shape) - if len(input_shape)!=4: - return "length of input_shape should be 4" - if not isinstance(input_shape[0], str): - #return "input_shape[0] should be str to support batch-inference" - print('reset input-shape[0] to None') - model = onnx.load(self.model_file) - model.graph.input[0].type.tensor_type.shape.dim[0].dim_param = 'None' - new_model_file = osp.join(self.model_path, 'zzzzrefined.onnx') - onnx.save(model, new_model_file) - self.model_file = new_model_file - print('use new onnx-model:', self.model_file) - try: - session = onnxruntime.InferenceSession(self.model_file, providers=self.providers) - except: - return "load onnx failed" - input_cfg = session.get_inputs()[0] - input_shape = input_cfg.shape - print('new-input-shape:', input_shape) - - self.image_size = tuple(input_shape[2:4][::-1]) - #print('image_size:', self.image_size) - input_name = input_cfg.name - outputs = session.get_outputs() - output_names = [] - for o in outputs: - output_names.append(o.name) - #print(o.name, o.shape) - if len(output_names)!=1: - return "number of output nodes should be 1" - self.session = session - self.input_name = input_name - self.output_names = output_names - #print(self.output_names) - model = onnx.load(self.model_file) - graph = model.graph - if len(graph.node)<8: - return "too small onnx graph" - - input_size = (112,112) - self.crop = None - if track=='cfat': - crop_file = osp.join(self.model_path, 'crop.txt') - if osp.exists(crop_file): - lines = open(crop_file,'r').readlines() - if len(lines)!=6: - return "crop.txt should contain 6 lines" - lines = [int(x) for x in lines] - self.crop = lines[:4] - input_size = tuple(lines[4:6]) - if input_size!=self.image_size: - return "input-size is inconsistant with onnx model input, %s vs %s"%(input_size, self.image_size) - - self.model_size_mb = os.path.getsize(self.model_file) / float(1024*1024) - if self.model_size_mb > max_model_size_mb: - return "max model size exceed, given %.3f-MB"%self.model_size_mb - - input_mean = None - input_std = None - if track=='cfat': - pn_file = osp.join(self.model_path, 'pixel_norm.txt') - if osp.exists(pn_file): - lines = open(pn_file,'r').readlines() - if len(lines)!=2: - return "pixel_norm.txt should contain 2 lines" - input_mean = float(lines[0]) - input_std = float(lines[1]) - if input_mean is not None or input_std is not None: - if input_mean is None or input_std is None: - return "please set input_mean and input_std simultaneously" - else: - find_sub = False - find_mul = False - for nid, node in enumerate(graph.node[:8]): - print(nid, node.name) - if node.name.startswith('Sub') or node.name.startswith('_minus'): - find_sub = True - if node.name.startswith('Mul') or node.name.startswith('_mul') or node.name.startswith('Div'): - find_mul = True - if find_sub and find_mul: - print("find sub and mul") - #mxnet arcface model - input_mean = 0.0 - input_std = 1.0 - else: - input_mean = 127.5 - input_std = 127.5 - self.input_mean = input_mean - self.input_std = input_std - for initn in graph.initializer: - weight_array = numpy_helper.to_array(initn) - dt = weight_array.dtype - if dt.itemsize<4: - return 'invalid weight type - (%s:%s)' % (initn.name, dt.name) - if test_img is None: - test_img = get_image('Tom_Hanks_54745') - test_img = cv2.resize(test_img, self.image_size) - else: - test_img = cv2.resize(test_img, self.image_size) - feat, cost = self.benchmark(test_img) - batch_result = self.check_batch(test_img) - batch_result_sum = float(np.sum(batch_result)) - if batch_result_sum in [float('inf'), -float('inf')] or batch_result_sum != batch_result_sum: - print(batch_result) - print(batch_result_sum) - return "batch result output contains NaN!" - - if len(feat.shape) < 2: - return "the shape of the feature must be two, but get {}".format(str(feat.shape)) - - if feat.shape[1] > max_feat_dim: - return "max feat dim exceed, given %d"%feat.shape[1] - self.feat_dim = feat.shape[1] - cost_ms = cost*1000 - if cost_ms>max_time_cost: - return "max time cost exceed, given %.4f"%cost_ms - self.cost_ms = cost_ms - print('check stat:, model-size-mb: %.4f, feat-dim: %d, time-cost-ms: %.4f, input-mean: %.3f, input-std: %.3f'%(self.model_size_mb, self.feat_dim, self.cost_ms, self.input_mean, self.input_std)) - return None - - def check_batch(self, img): - if not isinstance(img, list): - imgs = [img, ] * 32 - if self.crop is not None: - nimgs = [] - for img in imgs: - nimg = img[self.crop[1]:self.crop[3], self.crop[0]:self.crop[2], :] - if nimg.shape[0] != self.image_size[1] or nimg.shape[1] != self.image_size[0]: - nimg = cv2.resize(nimg, self.image_size) - nimgs.append(nimg) - imgs = nimgs - blob = cv2.dnn.blobFromImages( - images=imgs, scalefactor=1.0 / self.input_std, size=self.image_size, - mean=(self.input_mean, self.input_mean, self.input_mean), swapRB=True) - net_out = self.session.run(self.output_names, {self.input_name: blob})[0] - return net_out - - - def meta_info(self): - return {'model-size-mb':self.model_size_mb, 'feature-dim':self.feat_dim, 'infer': self.cost_ms} - - - def forward(self, imgs): - if not isinstance(imgs, list): - imgs = [imgs] - input_size = self.image_size - if self.crop is not None: - nimgs = [] - for img in imgs: - nimg = img[self.crop[1]:self.crop[3],self.crop[0]:self.crop[2],:] - if nimg.shape[0]!=input_size[1] or nimg.shape[1]!=input_size[0]: - nimg = cv2.resize(nimg, input_size) - nimgs.append(nimg) - imgs = nimgs - blob = cv2.dnn.blobFromImages(imgs, 1.0/self.input_std, input_size, (self.input_mean, self.input_mean, self.input_mean), swapRB=True) - net_out = self.session.run(self.output_names, {self.input_name : blob})[0] - return net_out - - def benchmark(self, img): - input_size = self.image_size - if self.crop is not None: - nimg = img[self.crop[1]:self.crop[3],self.crop[0]:self.crop[2],:] - if nimg.shape[0]!=input_size[1] or nimg.shape[1]!=input_size[0]: - nimg = cv2.resize(nimg, input_size) - img = nimg - blob = cv2.dnn.blobFromImage(img, 1.0/self.input_std, input_size, (self.input_mean, self.input_mean, self.input_mean), swapRB=True) - costs = [] - for _ in range(50): - ta = datetime.datetime.now() - net_out = self.session.run(self.output_names, {self.input_name : blob})[0] - tb = datetime.datetime.now() - cost = (tb-ta).total_seconds() - costs.append(cost) - costs = sorted(costs) - cost = costs[5] - return net_out, cost - - -if __name__ == '__main__': - parser = argparse.ArgumentParser(description='') - # general - parser.add_argument('workdir', help='submitted work dir', type=str) - parser.add_argument('--track', help='track name, for different challenge', type=str, default='cfat') - args = parser.parse_args() - handler = ArcFaceORT(args.workdir) - err = handler.check(args.track) - print('err:', err) diff --git a/src/face3d/models/arcface_torch/onnx_ijbc.py b/src/face3d/models/arcface_torch/onnx_ijbc.py deleted file mode 100644 index 05b50bf..0000000 --- a/src/face3d/models/arcface_torch/onnx_ijbc.py +++ /dev/null @@ -1,267 +0,0 @@ -import argparse -import os -import pickle -import timeit - -import cv2 -import mxnet as mx -import numpy as np -import pandas as pd -import prettytable -import skimage.transform -from sklearn.metrics import roc_curve -from sklearn.preprocessing import normalize - -from onnx_helper import ArcFaceORT - -SRC = np.array( - [ - [30.2946, 51.6963], - [65.5318, 51.5014], - [48.0252, 71.7366], - [33.5493, 92.3655], - [62.7299, 92.2041]] - , dtype=np.float32) -SRC[:, 0] += 8.0 - - -class AlignedDataSet(mx.gluon.data.Dataset): - def __init__(self, root, lines, align=True): - self.lines = lines - self.root = root - self.align = align - - def __len__(self): - return len(self.lines) - - def __getitem__(self, idx): - each_line = self.lines[idx] - name_lmk_score = each_line.strip().split(' ') - name = os.path.join(self.root, name_lmk_score[0]) - img = cv2.cvtColor(cv2.imread(name), cv2.COLOR_BGR2RGB) - landmark5 = np.array([float(x) for x in name_lmk_score[1:-1]], dtype=np.float32).reshape((5, 2)) - st = skimage.transform.SimilarityTransform() - st.estimate(landmark5, SRC) - img = cv2.warpAffine(img, st.params[0:2, :], (112, 112), borderValue=0.0) - img_1 = np.expand_dims(img, 0) - img_2 = np.expand_dims(np.fliplr(img), 0) - output = np.concatenate((img_1, img_2), axis=0).astype(np.float32) - output = np.transpose(output, (0, 3, 1, 2)) - output = mx.nd.array(output) - return output - - -def extract(model_root, dataset): - model = ArcFaceORT(model_path=model_root) - model.check() - feat_mat = np.zeros(shape=(len(dataset), 2 * model.feat_dim)) - - def batchify_fn(data): - return mx.nd.concat(*data, dim=0) - - data_loader = mx.gluon.data.DataLoader( - dataset, 128, last_batch='keep', num_workers=4, - thread_pool=True, prefetch=16, batchify_fn=batchify_fn) - num_iter = 0 - for batch in data_loader: - batch = batch.asnumpy() - batch = (batch - model.input_mean) / model.input_std - feat = model.session.run(model.output_names, {model.input_name: batch})[0] - feat = np.reshape(feat, (-1, model.feat_dim * 2)) - feat_mat[128 * num_iter: 128 * num_iter + feat.shape[0], :] = feat - num_iter += 1 - if num_iter % 50 == 0: - print(num_iter) - return feat_mat - - -def read_template_media_list(path): - ijb_meta = pd.read_csv(path, sep=' ', header=None).values - templates = ijb_meta[:, 1].astype(np.int) - medias = ijb_meta[:, 2].astype(np.int) - return templates, medias - - -def read_template_pair_list(path): - pairs = pd.read_csv(path, sep=' ', header=None).values - t1 = pairs[:, 0].astype(np.int) - t2 = pairs[:, 1].astype(np.int) - label = pairs[:, 2].astype(np.int) - return t1, t2, label - - -def read_image_feature(path): - with open(path, 'rb') as fid: - img_feats = pickle.load(fid) - return img_feats - - -def image2template_feature(img_feats=None, - templates=None, - medias=None): - unique_templates = np.unique(templates) - template_feats = np.zeros((len(unique_templates), img_feats.shape[1])) - for count_template, uqt in enumerate(unique_templates): - (ind_t,) = np.where(templates == uqt) - face_norm_feats = img_feats[ind_t] - face_medias = medias[ind_t] - unique_medias, unique_media_counts = np.unique(face_medias, return_counts=True) - media_norm_feats = [] - for u, ct in zip(unique_medias, unique_media_counts): - (ind_m,) = np.where(face_medias == u) - if ct == 1: - media_norm_feats += [face_norm_feats[ind_m]] - else: # image features from the same video will be aggregated into one feature - media_norm_feats += [np.mean(face_norm_feats[ind_m], axis=0, keepdims=True), ] - media_norm_feats = np.array(media_norm_feats) - template_feats[count_template] = np.sum(media_norm_feats, axis=0) - if count_template % 2000 == 0: - print('Finish Calculating {} template features.'.format( - count_template)) - template_norm_feats = normalize(template_feats) - return template_norm_feats, unique_templates - - -def verification(template_norm_feats=None, - unique_templates=None, - p1=None, - p2=None): - template2id = np.zeros((max(unique_templates) + 1, 1), dtype=int) - for count_template, uqt in enumerate(unique_templates): - template2id[uqt] = count_template - score = np.zeros((len(p1),)) - total_pairs = np.array(range(len(p1))) - batchsize = 100000 - sublists = [total_pairs[i: i + batchsize] for i in range(0, len(p1), batchsize)] - total_sublists = len(sublists) - for c, s in enumerate(sublists): - feat1 = template_norm_feats[template2id[p1[s]]] - feat2 = template_norm_feats[template2id[p2[s]]] - similarity_score = np.sum(feat1 * feat2, -1) - score[s] = similarity_score.flatten() - if c % 10 == 0: - print('Finish {}/{} pairs.'.format(c, total_sublists)) - return score - - -def verification2(template_norm_feats=None, - unique_templates=None, - p1=None, - p2=None): - template2id = np.zeros((max(unique_templates) + 1, 1), dtype=int) - for count_template, uqt in enumerate(unique_templates): - template2id[uqt] = count_template - score = np.zeros((len(p1),)) # save cosine distance between pairs - total_pairs = np.array(range(len(p1))) - batchsize = 100000 # small batchsize instead of all pairs in one batch due to the memory limiation - sublists = [total_pairs[i:i + batchsize] for i in range(0, len(p1), batchsize)] - total_sublists = len(sublists) - for c, s in enumerate(sublists): - feat1 = template_norm_feats[template2id[p1[s]]] - feat2 = template_norm_feats[template2id[p2[s]]] - similarity_score = np.sum(feat1 * feat2, -1) - score[s] = similarity_score.flatten() - if c % 10 == 0: - print('Finish {}/{} pairs.'.format(c, total_sublists)) - return score - - -def main(args): - use_norm_score = True # if Ture, TestMode(N1) - use_detector_score = True # if Ture, TestMode(D1) - use_flip_test = True # if Ture, TestMode(F1) - assert args.target == 'IJBC' or args.target == 'IJBB' - - start = timeit.default_timer() - templates, medias = read_template_media_list( - os.path.join('%s/meta' % args.image_path, '%s_face_tid_mid.txt' % args.target.lower())) - stop = timeit.default_timer() - print('Time: %.2f s. ' % (stop - start)) - - start = timeit.default_timer() - p1, p2, label = read_template_pair_list( - os.path.join('%s/meta' % args.image_path, - '%s_template_pair_label.txt' % args.target.lower())) - stop = timeit.default_timer() - print('Time: %.2f s. ' % (stop - start)) - - start = timeit.default_timer() - img_path = '%s/loose_crop' % args.image_path - img_list_path = '%s/meta/%s_name_5pts_score.txt' % (args.image_path, args.target.lower()) - img_list = open(img_list_path) - files = img_list.readlines() - dataset = AlignedDataSet(root=img_path, lines=files, align=True) - img_feats = extract(args.model_root, dataset) - - faceness_scores = [] - for each_line in files: - name_lmk_score = each_line.split() - faceness_scores.append(name_lmk_score[-1]) - faceness_scores = np.array(faceness_scores).astype(np.float32) - stop = timeit.default_timer() - print('Time: %.2f s. ' % (stop - start)) - print('Feature Shape: ({} , {}) .'.format(img_feats.shape[0], img_feats.shape[1])) - start = timeit.default_timer() - - if use_flip_test: - img_input_feats = img_feats[:, 0:img_feats.shape[1] // 2] + img_feats[:, img_feats.shape[1] // 2:] - else: - img_input_feats = img_feats[:, 0:img_feats.shape[1] // 2] - - if use_norm_score: - img_input_feats = img_input_feats - else: - img_input_feats = img_input_feats / np.sqrt(np.sum(img_input_feats ** 2, -1, keepdims=True)) - - if use_detector_score: - print(img_input_feats.shape, faceness_scores.shape) - img_input_feats = img_input_feats * faceness_scores[:, np.newaxis] - else: - img_input_feats = img_input_feats - - template_norm_feats, unique_templates = image2template_feature( - img_input_feats, templates, medias) - stop = timeit.default_timer() - print('Time: %.2f s. ' % (stop - start)) - - start = timeit.default_timer() - score = verification(template_norm_feats, unique_templates, p1, p2) - stop = timeit.default_timer() - print('Time: %.2f s. ' % (stop - start)) - save_path = os.path.join(args.result_dir, "{}_result".format(args.target)) - if not os.path.exists(save_path): - os.makedirs(save_path) - score_save_file = os.path.join(save_path, "{}.npy".format(args.model_root)) - np.save(score_save_file, score) - files = [score_save_file] - methods = [] - scores = [] - for file in files: - methods.append(os.path.basename(file)) - scores.append(np.load(file)) - methods = np.array(methods) - scores = dict(zip(methods, scores)) - x_labels = [10 ** -6, 10 ** -5, 10 ** -4, 10 ** -3, 10 ** -2, 10 ** -1] - tpr_fpr_table = prettytable.PrettyTable(['Methods'] + [str(x) for x in x_labels]) - for method in methods: - fpr, tpr, _ = roc_curve(label, scores[method]) - fpr = np.flipud(fpr) - tpr = np.flipud(tpr) - tpr_fpr_row = [] - tpr_fpr_row.append("%s-%s" % (method, args.target)) - for fpr_iter in np.arange(len(x_labels)): - _, min_index = min( - list(zip(abs(fpr - x_labels[fpr_iter]), range(len(fpr))))) - tpr_fpr_row.append('%.2f' % (tpr[min_index] * 100)) - tpr_fpr_table.add_row(tpr_fpr_row) - print(tpr_fpr_table) - - -if __name__ == '__main__': - parser = argparse.ArgumentParser(description='do ijb test') - # general - parser.add_argument('--model-root', default='', help='path to load model.') - parser.add_argument('--image-path', default='', type=str, help='') - parser.add_argument('--result-dir', default='.', type=str, help='') - parser.add_argument('--target', default='IJBC', type=str, help='target, set to IJBC or IJBB') - main(parser.parse_args()) diff --git a/src/face3d/models/arcface_torch/partial_fc.py b/src/face3d/models/arcface_torch/partial_fc.py deleted file mode 100644 index 17e2d25..0000000 --- a/src/face3d/models/arcface_torch/partial_fc.py +++ /dev/null @@ -1,222 +0,0 @@ -import logging -import os - -import torch -import torch.distributed as dist -from torch.nn import Module -from torch.nn.functional import normalize, linear -from torch.nn.parameter import Parameter - - -class PartialFC(Module): - """ - Author: {Xiang An, Yang Xiao, XuHan Zhu} in DeepGlint, - Partial FC: Training 10 Million Identities on a Single Machine - See the original paper: - https://arxiv.org/abs/2010.05222 - """ - - @torch.no_grad() - def __init__(self, rank, local_rank, world_size, batch_size, resume, - margin_softmax, num_classes, sample_rate=1.0, embedding_size=512, prefix="./"): - """ - rank: int - Unique process(GPU) ID from 0 to world_size - 1. - local_rank: int - Unique process(GPU) ID within the server from 0 to 7. - world_size: int - Number of GPU. - batch_size: int - Batch size on current rank(GPU). - resume: bool - Select whether to restore the weight of softmax. - margin_softmax: callable - A function of margin softmax, eg: cosface, arcface. - num_classes: int - The number of class center storage in current rank(CPU/GPU), usually is total_classes // world_size, - required. - sample_rate: float - The partial fc sampling rate, when the number of classes increases to more than 2 millions, Sampling - can greatly speed up training, and reduce a lot of GPU memory, default is 1.0. - embedding_size: int - The feature dimension, default is 512. - prefix: str - Path for save checkpoint, default is './'. - """ - super(PartialFC, self).__init__() - # - self.num_classes: int = num_classes - self.rank: int = rank - self.local_rank: int = local_rank - self.device: torch.device = torch.device("cuda:{}".format(self.local_rank)) - self.world_size: int = world_size - self.batch_size: int = batch_size - self.margin_softmax: callable = margin_softmax - self.sample_rate: float = sample_rate - self.embedding_size: int = embedding_size - self.prefix: str = prefix - self.num_local: int = num_classes // world_size + int(rank < num_classes % world_size) - self.class_start: int = num_classes // world_size * rank + min(rank, num_classes % world_size) - self.num_sample: int = int(self.sample_rate * self.num_local) - - self.weight_name = os.path.join(self.prefix, "rank_{}_softmax_weight.pt".format(self.rank)) - self.weight_mom_name = os.path.join(self.prefix, "rank_{}_softmax_weight_mom.pt".format(self.rank)) - - if resume: - try: - self.weight: torch.Tensor = torch.load(self.weight_name) - self.weight_mom: torch.Tensor = torch.load(self.weight_mom_name) - if self.weight.shape[0] != self.num_local or self.weight_mom.shape[0] != self.num_local: - raise IndexError - logging.info("softmax weight resume successfully!") - logging.info("softmax weight mom resume successfully!") - except (FileNotFoundError, KeyError, IndexError): - self.weight = torch.normal(0, 0.01, (self.num_local, self.embedding_size), device=self.device) - self.weight_mom: torch.Tensor = torch.zeros_like(self.weight) - logging.info("softmax weight init!") - logging.info("softmax weight mom init!") - else: - self.weight = torch.normal(0, 0.01, (self.num_local, self.embedding_size), device=self.device) - self.weight_mom: torch.Tensor = torch.zeros_like(self.weight) - logging.info("softmax weight init successfully!") - logging.info("softmax weight mom init successfully!") - self.stream: torch.cuda.Stream = torch.cuda.Stream(local_rank) - - self.index = None - if int(self.sample_rate) == 1: - self.update = lambda: 0 - self.sub_weight = Parameter(self.weight) - self.sub_weight_mom = self.weight_mom - else: - self.sub_weight = Parameter(torch.empty((0, 0)).cuda(local_rank)) - - def save_params(self): - """ Save softmax weight for each rank on prefix - """ - torch.save(self.weight.data, self.weight_name) - torch.save(self.weight_mom, self.weight_mom_name) - - @torch.no_grad() - def sample(self, total_label): - """ - Sample all positive class centers in each rank, and random select neg class centers to filling a fixed - `num_sample`. - - total_label: tensor - Label after all gather, which cross all GPUs. - """ - index_positive = (self.class_start <= total_label) & (total_label < self.class_start + self.num_local) - total_label[~index_positive] = -1 - total_label[index_positive] -= self.class_start - if int(self.sample_rate) != 1: - positive = torch.unique(total_label[index_positive], sorted=True) - if self.num_sample - positive.size(0) >= 0: - perm = torch.rand(size=[self.num_local], device=self.device) - perm[positive] = 2.0 - index = torch.topk(perm, k=self.num_sample)[1] - index = index.sort()[0] - else: - index = positive - self.index = index - total_label[index_positive] = torch.searchsorted(index, total_label[index_positive]) - self.sub_weight = Parameter(self.weight[index]) - self.sub_weight_mom = self.weight_mom[index] - - def forward(self, total_features, norm_weight): - """ Partial fc forward, `logits = X * sample(W)` - """ - torch.cuda.current_stream().wait_stream(self.stream) - logits = linear(total_features, norm_weight) - return logits - - @torch.no_grad() - def update(self): - """ Set updated weight and weight_mom to memory bank. - """ - self.weight_mom[self.index] = self.sub_weight_mom - self.weight[self.index] = self.sub_weight - - def prepare(self, label, optimizer): - """ - get sampled class centers for cal softmax. - - label: tensor - Label tensor on each rank. - optimizer: opt - Optimizer for partial fc, which need to get weight mom. - """ - with torch.cuda.stream(self.stream): - total_label = torch.zeros( - size=[self.batch_size * self.world_size], device=self.device, dtype=torch.long) - dist.all_gather(list(total_label.chunk(self.world_size, dim=0)), label) - self.sample(total_label) - optimizer.state.pop(optimizer.param_groups[-1]['params'][0], None) - optimizer.param_groups[-1]['params'][0] = self.sub_weight - optimizer.state[self.sub_weight]['momentum_buffer'] = self.sub_weight_mom - norm_weight = normalize(self.sub_weight) - return total_label, norm_weight - - def forward_backward(self, label, features, optimizer): - """ - Partial fc forward and backward with model parallel - - label: tensor - Label tensor on each rank(GPU) - features: tensor - Features tensor on each rank(GPU) - optimizer: optimizer - Optimizer for partial fc - - Returns: - -------- - x_grad: tensor - The gradient of features. - loss_v: tensor - Loss value for cross entropy. - """ - total_label, norm_weight = self.prepare(label, optimizer) - total_features = torch.zeros( - size=[self.batch_size * self.world_size, self.embedding_size], device=self.device) - dist.all_gather(list(total_features.chunk(self.world_size, dim=0)), features.data) - total_features.requires_grad = True - - logits = self.forward(total_features, norm_weight) - logits = self.margin_softmax(logits, total_label) - - with torch.no_grad(): - max_fc = torch.max(logits, dim=1, keepdim=True)[0] - dist.all_reduce(max_fc, dist.ReduceOp.MAX) - - # calculate exp(logits) and all-reduce - logits_exp = torch.exp(logits - max_fc) - logits_sum_exp = logits_exp.sum(dim=1, keepdims=True) - dist.all_reduce(logits_sum_exp, dist.ReduceOp.SUM) - - # calculate prob - logits_exp.div_(logits_sum_exp) - - # get one-hot - grad = logits_exp - index = torch.where(total_label != -1)[0] - one_hot = torch.zeros(size=[index.size()[0], grad.size()[1]], device=grad.device) - one_hot.scatter_(1, total_label[index, None], 1) - - # calculate loss - loss = torch.zeros(grad.size()[0], 1, device=grad.device) - loss[index] = grad[index].gather(1, total_label[index, None]) - dist.all_reduce(loss, dist.ReduceOp.SUM) - loss_v = loss.clamp_min_(1e-30).log_().mean() * (-1) - - # calculate grad - grad[index] -= one_hot - grad.div_(self.batch_size * self.world_size) - - logits.backward(grad) - if total_features.grad is not None: - total_features.grad.detach_() - x_grad: torch.Tensor = torch.zeros_like(features, requires_grad=True) - # feature gradient all-reduce - dist.reduce_scatter(x_grad, list(total_features.grad.chunk(self.world_size, dim=0))) - x_grad = x_grad * self.world_size - # backward backbone - return x_grad, loss_v diff --git a/src/face3d/models/arcface_torch/requirement.txt b/src/face3d/models/arcface_torch/requirement.txt deleted file mode 100644 index f72c1b3..0000000 --- a/src/face3d/models/arcface_torch/requirement.txt +++ /dev/null @@ -1,5 +0,0 @@ -tensorboard -easydict -mxnet -onnx -sklearn diff --git a/src/face3d/models/arcface_torch/run.sh b/src/face3d/models/arcface_torch/run.sh deleted file mode 100644 index 61af4b4..0000000 --- a/src/face3d/models/arcface_torch/run.sh +++ /dev/null @@ -1,2 +0,0 @@ -CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 python -m torch.distributed.launch --nproc_per_node=8 --nnodes=1 --node_rank=0 --master_addr="127.0.0.1" --master_port=1234 train.py configs/ms1mv3_r50 -ps -ef | grep "train" | grep -v grep | awk '{print "kill -9 "$2}' | sh diff --git a/src/face3d/models/arcface_torch/torch2onnx.py b/src/face3d/models/arcface_torch/torch2onnx.py deleted file mode 100644 index fc26ab8..0000000 --- a/src/face3d/models/arcface_torch/torch2onnx.py +++ /dev/null @@ -1,59 +0,0 @@ -import numpy as np -import onnx -import torch - - -def convert_onnx(net, path_module, output, opset=11, simplify=False): - assert isinstance(net, torch.nn.Module) - img = np.random.randint(0, 255, size=(112, 112, 3), dtype=np.int32) - img = img.astype(np.float) - img = (img / 255. - 0.5) / 0.5 # torch style norm - img = img.transpose((2, 0, 1)) - img = torch.from_numpy(img).unsqueeze(0).float() - - weight = torch.load(path_module) - net.load_state_dict(weight) - net.eval() - torch.onnx.export(net, img, output, keep_initializers_as_inputs=False, verbose=False, opset_version=opset) - model = onnx.load(output) - graph = model.graph - graph.input[0].type.tensor_type.shape.dim[0].dim_param = 'None' - if simplify: - from onnxsim import simplify - model, check = simplify(model) - assert check, "Simplified ONNX model could not be validated" - onnx.save(model, output) - - -if __name__ == '__main__': - import os - import argparse - from backbones import get_model - - parser = argparse.ArgumentParser(description='ArcFace PyTorch to onnx') - parser.add_argument('input', type=str, help='input backbone.pth file or path') - parser.add_argument('--output', type=str, default=None, help='output onnx path') - parser.add_argument('--network', type=str, default=None, help='backbone network') - parser.add_argument('--simplify', type=bool, default=False, help='onnx simplify') - args = parser.parse_args() - input_file = args.input - if os.path.isdir(input_file): - input_file = os.path.join(input_file, "backbone.pth") - assert os.path.exists(input_file) - model_name = os.path.basename(os.path.dirname(input_file)).lower() - params = model_name.split("_") - if len(params) >= 3 and params[1] in ('arcface', 'cosface'): - if args.network is None: - args.network = params[2] - assert args.network is not None - print(args) - backbone_onnx = get_model(args.network, dropout=0) - - output_path = args.output - if output_path is None: - output_path = os.path.join(os.path.dirname(__file__), 'onnx') - if not os.path.exists(output_path): - os.makedirs(output_path) - assert os.path.isdir(output_path) - output_file = os.path.join(output_path, "%s.onnx" % model_name) - convert_onnx(backbone_onnx, input_file, output_file, simplify=args.simplify) diff --git a/src/face3d/models/arcface_torch/train.py b/src/face3d/models/arcface_torch/train.py deleted file mode 100644 index 55eca2d..0000000 --- a/src/face3d/models/arcface_torch/train.py +++ /dev/null @@ -1,141 +0,0 @@ -import argparse -import logging -import os - -import torch -import torch.distributed as dist -import torch.nn.functional as F -import torch.utils.data.distributed -from torch.nn.utils import clip_grad_norm_ - -import losses -from backbones import get_model -from dataset import MXFaceDataset, SyntheticDataset, DataLoaderX -from partial_fc import PartialFC -from utils.utils_amp import MaxClipGradScaler -from utils.utils_callbacks import CallBackVerification, CallBackLogging, CallBackModelCheckpoint -from utils.utils_config import get_config -from utils.utils_logging import AverageMeter, init_logging - - -def main(args): - cfg = get_config(args.config) - try: - world_size = int(os.environ['WORLD_SIZE']) - rank = int(os.environ['RANK']) - dist.init_process_group('nccl') - except KeyError: - world_size = 1 - rank = 0 - dist.init_process_group(backend='nccl', init_method="tcp://127.0.0.1:12584", rank=rank, world_size=world_size) - - local_rank = args.local_rank - torch.cuda.set_device(local_rank) - os.makedirs(cfg.output, exist_ok=True) - init_logging(rank, cfg.output) - - if cfg.rec == "synthetic": - train_set = SyntheticDataset(local_rank=local_rank) - else: - train_set = MXFaceDataset(root_dir=cfg.rec, local_rank=local_rank) - - train_sampler = torch.utils.data.distributed.DistributedSampler(train_set, shuffle=True) - train_loader = DataLoaderX( - local_rank=local_rank, dataset=train_set, batch_size=cfg.batch_size, - sampler=train_sampler, num_workers=2, pin_memory=True, drop_last=True) - backbone = get_model(cfg.network, dropout=0.0, fp16=cfg.fp16, num_features=cfg.embedding_size).to(local_rank) - - if cfg.resume: - try: - backbone_pth = os.path.join(cfg.output, "backbone.pth") - backbone.load_state_dict(torch.load(backbone_pth, map_location=torch.device(local_rank))) - if rank == 0: - logging.info("backbone resume successfully!") - except (FileNotFoundError, KeyError, IndexError, RuntimeError): - if rank == 0: - logging.info("resume fail, backbone init successfully!") - - backbone = torch.nn.parallel.DistributedDataParallel( - module=backbone, broadcast_buffers=False, device_ids=[local_rank]) - backbone.train() - margin_softmax = losses.get_loss(cfg.loss) - module_partial_fc = PartialFC( - rank=rank, local_rank=local_rank, world_size=world_size, resume=cfg.resume, - batch_size=cfg.batch_size, margin_softmax=margin_softmax, num_classes=cfg.num_classes, - sample_rate=cfg.sample_rate, embedding_size=cfg.embedding_size, prefix=cfg.output) - - opt_backbone = torch.optim.SGD( - params=[{'params': backbone.parameters()}], - lr=cfg.lr / 512 * cfg.batch_size * world_size, - momentum=0.9, weight_decay=cfg.weight_decay) - opt_pfc = torch.optim.SGD( - params=[{'params': module_partial_fc.parameters()}], - lr=cfg.lr / 512 * cfg.batch_size * world_size, - momentum=0.9, weight_decay=cfg.weight_decay) - - num_image = len(train_set) - total_batch_size = cfg.batch_size * world_size - cfg.warmup_step = num_image // total_batch_size * cfg.warmup_epoch - cfg.total_step = num_image // total_batch_size * cfg.num_epoch - - def lr_step_func(current_step): - cfg.decay_step = [x * num_image // total_batch_size for x in cfg.decay_epoch] - if current_step < cfg.warmup_step: - return current_step / cfg.warmup_step - else: - return 0.1 ** len([m for m in cfg.decay_step if m <= current_step]) - - scheduler_backbone = torch.optim.lr_scheduler.LambdaLR( - optimizer=opt_backbone, lr_lambda=lr_step_func) - scheduler_pfc = torch.optim.lr_scheduler.LambdaLR( - optimizer=opt_pfc, lr_lambda=lr_step_func) - - for key, value in cfg.items(): - num_space = 25 - len(key) - logging.info(": " + key + " " * num_space + str(value)) - - val_target = cfg.val_targets - callback_verification = CallBackVerification(2000, rank, val_target, cfg.rec) - callback_logging = CallBackLogging(50, rank, cfg.total_step, cfg.batch_size, world_size, None) - callback_checkpoint = CallBackModelCheckpoint(rank, cfg.output) - - loss = AverageMeter() - start_epoch = 0 - global_step = 0 - grad_amp = MaxClipGradScaler(cfg.batch_size, 128 * cfg.batch_size, growth_interval=100) if cfg.fp16 else None - for epoch in range(start_epoch, cfg.num_epoch): - train_sampler.set_epoch(epoch) - for step, (img, label) in enumerate(train_loader): - global_step += 1 - features = F.normalize(backbone(img)) - x_grad, loss_v = module_partial_fc.forward_backward(label, features, opt_pfc) - if cfg.fp16: - features.backward(grad_amp.scale(x_grad)) - grad_amp.unscale_(opt_backbone) - clip_grad_norm_(backbone.parameters(), max_norm=5, norm_type=2) - grad_amp.step(opt_backbone) - grad_amp.update() - else: - features.backward(x_grad) - clip_grad_norm_(backbone.parameters(), max_norm=5, norm_type=2) - opt_backbone.step() - - opt_pfc.step() - module_partial_fc.update() - opt_backbone.zero_grad() - opt_pfc.zero_grad() - loss.update(loss_v, 1) - callback_logging(global_step, loss, epoch, cfg.fp16, scheduler_backbone.get_last_lr()[0], grad_amp) - callback_verification(global_step, backbone) - scheduler_backbone.step() - scheduler_pfc.step() - callback_checkpoint(global_step, backbone, module_partial_fc) - dist.destroy_process_group() - - -if __name__ == "__main__": - torch.backends.cudnn.benchmark = True - parser = argparse.ArgumentParser(description='PyTorch ArcFace Training') - parser.add_argument('config', type=str, help='py config file') - parser.add_argument('--local_rank', type=int, default=0, help='local_rank') - main(parser.parse_args()) diff --git a/src/face3d/models/arcface_torch/utils/__init__.py b/src/face3d/models/arcface_torch/utils/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/face3d/models/arcface_torch/utils/plot.py b/src/face3d/models/arcface_torch/utils/plot.py deleted file mode 100644 index ccc588e..0000000 --- a/src/face3d/models/arcface_torch/utils/plot.py +++ /dev/null @@ -1,72 +0,0 @@ -# coding: utf-8 - -import os -from pathlib import Path - -import matplotlib.pyplot as plt -import numpy as np -import pandas as pd -from menpo.visualize.viewmatplotlib import sample_colours_from_colourmap -from prettytable import PrettyTable -from sklearn.metrics import roc_curve, auc - -image_path = "/data/anxiang/IJB_release/IJBC" -files = [ - "./ms1mv3_arcface_r100/ms1mv3_arcface_r100/ijbc.npy" -] - - -def read_template_pair_list(path): - pairs = pd.read_csv(path, sep=' ', header=None).values - t1 = pairs[:, 0].astype(np.int) - t2 = pairs[:, 1].astype(np.int) - label = pairs[:, 2].astype(np.int) - return t1, t2, label - - -p1, p2, label = read_template_pair_list( - os.path.join('%s/meta' % image_path, - '%s_template_pair_label.txt' % 'ijbc')) - -methods = [] -scores = [] -for file in files: - methods.append(file.split('/')[-2]) - scores.append(np.load(file)) - -methods = np.array(methods) -scores = dict(zip(methods, scores)) -colours = dict( - zip(methods, sample_colours_from_colourmap(methods.shape[0], 'Set2'))) -x_labels = [10 ** -6, 10 ** -5, 10 ** -4, 10 ** -3, 10 ** -2, 10 ** -1] -tpr_fpr_table = PrettyTable(['Methods'] + [str(x) for x in x_labels]) -fig = plt.figure() -for method in methods: - fpr, tpr, _ = roc_curve(label, scores[method]) - roc_auc = auc(fpr, tpr) - fpr = np.flipud(fpr) - tpr = np.flipud(tpr) # select largest tpr at same fpr - plt.plot(fpr, - tpr, - color=colours[method], - lw=1, - label=('[%s (AUC = %0.4f %%)]' % - (method.split('-')[-1], roc_auc * 100))) - tpr_fpr_row = [] - tpr_fpr_row.append("%s-%s" % (method, "IJBC")) - for fpr_iter in np.arange(len(x_labels)): - _, min_index = min( - list(zip(abs(fpr - x_labels[fpr_iter]), range(len(fpr))))) - tpr_fpr_row.append('%.2f' % (tpr[min_index] * 100)) - tpr_fpr_table.add_row(tpr_fpr_row) -plt.xlim([10 ** -6, 0.1]) -plt.ylim([0.3, 1.0]) -plt.grid(linestyle='--', linewidth=1) -plt.xticks(x_labels) -plt.yticks(np.linspace(0.3, 1.0, 8, endpoint=True)) -plt.xscale('log') -plt.xlabel('False Positive Rate') -plt.ylabel('True Positive Rate') -plt.title('ROC on IJB') -plt.legend(loc="lower right") -print(tpr_fpr_table) diff --git a/src/face3d/models/arcface_torch/utils/utils_amp.py b/src/face3d/models/arcface_torch/utils/utils_amp.py deleted file mode 100644 index 9ac2a03..0000000 --- a/src/face3d/models/arcface_torch/utils/utils_amp.py +++ /dev/null @@ -1,88 +0,0 @@ -from typing import Dict, List - -import torch - -if torch.__version__ < '1.9': - Iterable = torch._six.container_abcs.Iterable -else: - import collections - - Iterable = collections.abc.Iterable -from torch.cuda.amp import GradScaler - - -class _MultiDeviceReplicator(object): - """ - Lazily serves copies of a tensor to requested devices. Copies are cached per-device. - """ - - def __init__(self, master_tensor: torch.Tensor) -> None: - assert master_tensor.is_cuda - self.master = master_tensor - self._per_device_tensors: Dict[torch.device, torch.Tensor] = {} - - def get(self, device) -> torch.Tensor: - retval = self._per_device_tensors.get(device, None) - if retval is None: - retval = self.master.to(device=device, non_blocking=True, copy=True) - self._per_device_tensors[device] = retval - return retval - - -class MaxClipGradScaler(GradScaler): - def __init__(self, init_scale, max_scale: float, growth_interval=100): - GradScaler.__init__(self, init_scale=init_scale, growth_interval=growth_interval) - self.max_scale = max_scale - - def scale_clip(self): - if self.get_scale() == self.max_scale: - self.set_growth_factor(1) - elif self.get_scale() < self.max_scale: - self.set_growth_factor(2) - elif self.get_scale() > self.max_scale: - self._scale.fill_(self.max_scale) - self.set_growth_factor(1) - - def scale(self, outputs): - """ - Multiplies ('scales') a tensor or list of tensors by the scale factor. - - Returns scaled outputs. If this instance of :class:`GradScaler` is not enabled, outputs are returned - unmodified. - - Arguments: - outputs (Tensor or iterable of Tensors): Outputs to scale. - """ - if not self._enabled: - return outputs - self.scale_clip() - # Short-circuit for the common case. - if isinstance(outputs, torch.Tensor): - assert outputs.is_cuda - if self._scale is None: - self._lazy_init_scale_growth_tracker(outputs.device) - assert self._scale is not None - return outputs * self._scale.to(device=outputs.device, non_blocking=True) - - # Invoke the more complex machinery only if we're treating multiple outputs. - stash: List[_MultiDeviceReplicator] = [] # holds a reference that can be overwritten by apply_scale - - def apply_scale(val): - if isinstance(val, torch.Tensor): - assert val.is_cuda - if len(stash) == 0: - if self._scale is None: - self._lazy_init_scale_growth_tracker(val.device) - assert self._scale is not None - stash.append(_MultiDeviceReplicator(self._scale)) - return val * stash[0].get(val.device) - elif isinstance(val, Iterable): - iterable = map(apply_scale, val) - if isinstance(val, list) or isinstance(val, tuple): - return type(val)(iterable) - else: - return iterable - else: - raise ValueError("outputs must be a Tensor or an iterable of Tensors") - - return apply_scale(outputs) diff --git a/src/face3d/models/arcface_torch/utils/utils_callbacks.py b/src/face3d/models/arcface_torch/utils/utils_callbacks.py deleted file mode 100644 index bd2f56c..0000000 --- a/src/face3d/models/arcface_torch/utils/utils_callbacks.py +++ /dev/null @@ -1,117 +0,0 @@ -import logging -import os -import time -from typing import List - -import torch - -from eval import verification -from utils.utils_logging import AverageMeter - - -class CallBackVerification(object): - def __init__(self, frequent, rank, val_targets, rec_prefix, image_size=(112, 112)): - self.frequent: int = frequent - self.rank: int = rank - self.highest_acc: float = 0.0 - self.highest_acc_list: List[float] = [0.0] * len(val_targets) - self.ver_list: List[object] = [] - self.ver_name_list: List[str] = [] - if self.rank is 0: - self.init_dataset(val_targets=val_targets, data_dir=rec_prefix, image_size=image_size) - - def ver_test(self, backbone: torch.nn.Module, global_step: int): - results = [] - for i in range(len(self.ver_list)): - acc1, std1, acc2, std2, xnorm, embeddings_list = verification.test( - self.ver_list[i], backbone, 10, 10) - logging.info('[%s][%d]XNorm: %f' % (self.ver_name_list[i], global_step, xnorm)) - logging.info('[%s][%d]Accuracy-Flip: %1.5f+-%1.5f' % (self.ver_name_list[i], global_step, acc2, std2)) - if acc2 > self.highest_acc_list[i]: - self.highest_acc_list[i] = acc2 - logging.info( - '[%s][%d]Accuracy-Highest: %1.5f' % (self.ver_name_list[i], global_step, self.highest_acc_list[i])) - results.append(acc2) - - def init_dataset(self, val_targets, data_dir, image_size): - for name in val_targets: - path = os.path.join(data_dir, name + ".bin") - if os.path.exists(path): - data_set = verification.load_bin(path, image_size) - self.ver_list.append(data_set) - self.ver_name_list.append(name) - - def __call__(self, num_update, backbone: torch.nn.Module): - if self.rank is 0 and num_update > 0 and num_update % self.frequent == 0: - backbone.eval() - self.ver_test(backbone, num_update) - backbone.train() - - -class CallBackLogging(object): - def __init__(self, frequent, rank, total_step, batch_size, world_size, writer=None): - self.frequent: int = frequent - self.rank: int = rank - self.time_start = time.time() - self.total_step: int = total_step - self.batch_size: int = batch_size - self.world_size: int = world_size - self.writer = writer - - self.init = False - self.tic = 0 - - def __call__(self, - global_step: int, - loss: AverageMeter, - epoch: int, - fp16: bool, - learning_rate: float, - grad_scaler: torch.cuda.amp.GradScaler): - if self.rank == 0 and global_step > 0 and global_step % self.frequent == 0: - if self.init: - try: - speed: float = self.frequent * self.batch_size / (time.time() - self.tic) - speed_total = speed * self.world_size - except ZeroDivisionError: - speed_total = float('inf') - - time_now = (time.time() - self.time_start) / 3600 - time_total = time_now / ((global_step + 1) / self.total_step) - time_for_end = time_total - time_now - if self.writer is not None: - self.writer.add_scalar('time_for_end', time_for_end, global_step) - self.writer.add_scalar('learning_rate', learning_rate, global_step) - self.writer.add_scalar('loss', loss.avg, global_step) - if fp16: - msg = "Speed %.2f samples/sec Loss %.4f LearningRate %.4f Epoch: %d Global Step: %d " \ - "Fp16 Grad Scale: %2.f Required: %1.f hours" % ( - speed_total, loss.avg, learning_rate, epoch, global_step, - grad_scaler.get_scale(), time_for_end - ) - else: - msg = "Speed %.2f samples/sec Loss %.4f LearningRate %.4f Epoch: %d Global Step: %d " \ - "Required: %1.f hours" % ( - speed_total, loss.avg, learning_rate, epoch, global_step, time_for_end - ) - logging.info(msg) - loss.reset() - self.tic = time.time() - else: - self.init = True - self.tic = time.time() - - -class CallBackModelCheckpoint(object): - def __init__(self, rank, output="./"): - self.rank: int = rank - self.output: str = output - - def __call__(self, global_step, backbone, partial_fc, ): - if global_step > 100 and self.rank == 0: - path_module = os.path.join(self.output, "backbone.pth") - torch.save(backbone.module.state_dict(), path_module) - logging.info("Pytorch Model Saved in '{}'".format(path_module)) - - if global_step > 100 and partial_fc is not None: - partial_fc.save_params() diff --git a/src/face3d/models/arcface_torch/utils/utils_config.py b/src/face3d/models/arcface_torch/utils/utils_config.py deleted file mode 100644 index 0c02eaf..0000000 --- a/src/face3d/models/arcface_torch/utils/utils_config.py +++ /dev/null @@ -1,16 +0,0 @@ -import importlib -import os.path as osp - - -def get_config(config_file): - assert config_file.startswith('configs/'), 'config file setting must start with configs/' - temp_config_name = osp.basename(config_file) - temp_module_name = osp.splitext(temp_config_name)[0] - config = importlib.import_module("configs.base") - cfg = config.config - config = importlib.import_module("configs.%s" % temp_module_name) - job_cfg = config.config - cfg.update(job_cfg) - if cfg.output is None: - cfg.output = osp.join('work_dirs', temp_module_name) - return cfg \ No newline at end of file diff --git a/src/face3d/models/arcface_torch/utils/utils_logging.py b/src/face3d/models/arcface_torch/utils/utils_logging.py deleted file mode 100644 index c787b6a..0000000 --- a/src/face3d/models/arcface_torch/utils/utils_logging.py +++ /dev/null @@ -1,41 +0,0 @@ -import logging -import os -import sys - - -class AverageMeter(object): - """Computes and stores the average and current value - """ - - def __init__(self): - self.val = None - self.avg = None - self.sum = None - self.count = None - self.reset() - - def reset(self): - self.val = 0 - self.avg = 0 - self.sum = 0 - self.count = 0 - - def update(self, val, n=1): - self.val = val - self.sum += val * n - self.count += n - self.avg = self.sum / self.count - - -def init_logging(rank, models_root): - if rank == 0: - log_root = logging.getLogger() - log_root.setLevel(logging.INFO) - formatter = logging.Formatter("Training: %(asctime)s-%(message)s") - handler_file = logging.FileHandler(os.path.join(models_root, "training.log")) - handler_stream = logging.StreamHandler(sys.stdout) - handler_file.setFormatter(formatter) - handler_stream.setFormatter(formatter) - log_root.addHandler(handler_file) - log_root.addHandler(handler_stream) - log_root.info('rank_id: %d' % rank) diff --git a/src/face3d/models/arcface_torch/utils/utils_os.py b/src/face3d/models/arcface_torch/utils/utils_os.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/face3d/models/base_model.py b/src/face3d/models/base_model.py deleted file mode 100644 index cfe64a7..0000000 --- a/src/face3d/models/base_model.py +++ /dev/null @@ -1,316 +0,0 @@ -"""This script defines the base network model for Deep3DFaceRecon_pytorch -""" - -import os -import numpy as np -import torch -from collections import OrderedDict -from abc import ABC, abstractmethod -from . import networks - - -class BaseModel(ABC): - """This class is an abstract base class (ABC) for models. - To create a subclass, you need to implement the following five functions: - -- <__init__>: initialize the class; first call BaseModel.__init__(self, opt). - -- : unpack data from dataset and apply preprocessing. - -- : produce intermediate results. - -- : calculate losses, gradients, and update network weights. - -- : (optionally) add model-specific options and set default options. - """ - - def __init__(self, opt): - """Initialize the BaseModel class. - - Parameters: - opt (Option class)-- stores all the experiment flags; needs to be a subclass of BaseOptions - - When creating your custom class, you need to implement your own initialization. - In this fucntion, you should first call - Then, you need to define four lists: - -- self.loss_names (str list): specify the training losses that you want to plot and save. - -- self.model_names (str list): specify the images that you want to display and save. - -- self.visual_names (str list): define networks used in our training. - -- self.optimizers (optimizer list): define and initialize optimizers. You can define one optimizer for each network. If two networks are updated at the same time, you can use itertools.chain to group them. See cycle_gan_model.py for an example. - """ - self.opt = opt - self.isTrain = False - self.device = torch.device('cpu') - self.save_dir = " " # os.path.join(opt.checkpoints_dir, opt.name) # save all the checkpoints to save_dir - self.loss_names = [] - self.model_names = [] - self.visual_names = [] - self.parallel_names = [] - self.optimizers = [] - self.image_paths = [] - self.metric = 0 # used for learning rate policy 'plateau' - - @staticmethod - def dict_grad_hook_factory(add_func=lambda x: x): - saved_dict = dict() - - def hook_gen(name): - def grad_hook(grad): - saved_vals = add_func(grad) - saved_dict[name] = saved_vals - return grad_hook - return hook_gen, saved_dict - - @staticmethod - def modify_commandline_options(parser, is_train): - """Add new model-specific options, and rewrite default values for existing options. - - Parameters: - parser -- original option parser - is_train (bool) -- whether training phase or test phase. You can use this flag to add training-specific or test-specific options. - - Returns: - the modified parser. - """ - return parser - - @abstractmethod - def set_input(self, input): - """Unpack input data from the dataloader and perform necessary pre-processing steps. - - Parameters: - input (dict): includes the data itself and its metadata information. - """ - pass - - @abstractmethod - def forward(self): - """Run forward pass; called by both functions and .""" - pass - - @abstractmethod - def optimize_parameters(self): - """Calculate losses, gradients, and update network weights; called in every training iteration""" - pass - - def setup(self, opt): - """Load and print networks; create schedulers - - Parameters: - opt (Option class) -- stores all the experiment flags; needs to be a subclass of BaseOptions - """ - if self.isTrain: - self.schedulers = [networks.get_scheduler(optimizer, opt) for optimizer in self.optimizers] - - if not self.isTrain or opt.continue_train: - load_suffix = opt.epoch - self.load_networks(load_suffix) - - - # self.print_networks(opt.verbose) - - def parallelize(self, convert_sync_batchnorm=True): - if not self.opt.use_ddp: - for name in self.parallel_names: - if isinstance(name, str): - module = getattr(self, name) - setattr(self, name, module.to(self.device)) - else: - for name in self.model_names: - if isinstance(name, str): - module = getattr(self, name) - if convert_sync_batchnorm: - module = torch.nn.SyncBatchNorm.convert_sync_batchnorm(module) - setattr(self, name, torch.nn.parallel.DistributedDataParallel(module.to(self.device), - device_ids=[self.device.index], - find_unused_parameters=True, broadcast_buffers=True)) - - # DistributedDataParallel is not needed when a module doesn't have any parameter that requires a gradient. - for name in self.parallel_names: - if isinstance(name, str) and name not in self.model_names: - module = getattr(self, name) - setattr(self, name, module.to(self.device)) - - # put state_dict of optimizer to gpu device - if self.opt.phase != 'test': - if self.opt.continue_train: - for optim in self.optimizers: - for state in optim.state.values(): - for k, v in state.items(): - if isinstance(v, torch.Tensor): - state[k] = v.to(self.device) - - def data_dependent_initialize(self, data): - pass - - def train(self): - """Make models train mode""" - for name in self.model_names: - if isinstance(name, str): - net = getattr(self, name) - net.train() - - def eval(self): - """Make models eval mode""" - for name in self.model_names: - if isinstance(name, str): - net = getattr(self, name) - net.eval() - - def test(self): - """Forward function used in test time. - - This function wraps function in no_grad() so we don't save intermediate steps for backprop - It also calls to produce additional visualization results - """ - with torch.no_grad(): - self.forward() - self.compute_visuals() - - def compute_visuals(self): - """Calculate additional output images for visdom and HTML visualization""" - pass - - def get_image_paths(self, name='A'): - """ Return image paths that are used to load current data""" - return self.image_paths if name =='A' else self.image_paths_B - - def update_learning_rate(self): - """Update learning rates for all the networks; called at the end of every epoch""" - for scheduler in self.schedulers: - if self.opt.lr_policy == 'plateau': - scheduler.step(self.metric) - else: - scheduler.step() - - lr = self.optimizers[0].param_groups[0]['lr'] - print('learning rate = %.7f' % lr) - - def get_current_visuals(self): - """Return visualization images. train.py will display these images with visdom, and save the images to a HTML""" - visual_ret = OrderedDict() - for name in self.visual_names: - if isinstance(name, str): - visual_ret[name] = getattr(self, name)[:, :3, ...] - return visual_ret - - def get_current_losses(self): - """Return traning losses / errors. train.py will print out these errors on console, and save them to a file""" - errors_ret = OrderedDict() - for name in self.loss_names: - if isinstance(name, str): - errors_ret[name] = float(getattr(self, 'loss_' + name)) # float(...) works for both scalar tensor and float number - return errors_ret - - def save_networks(self, epoch): - """Save all the networks to the disk. - - Parameters: - epoch (int) -- current epoch; used in the file name '%s_net_%s.pth' % (epoch, name) - """ - if not os.path.isdir(self.save_dir): - os.makedirs(self.save_dir) - - save_filename = 'epoch_%s.pth' % (epoch) - save_path = os.path.join(self.save_dir, save_filename) - - save_dict = {} - for name in self.model_names: - if isinstance(name, str): - net = getattr(self, name) - if isinstance(net, torch.nn.DataParallel) or isinstance(net, - torch.nn.parallel.DistributedDataParallel): - net = net.module - save_dict[name] = net.state_dict() - - - for i, optim in enumerate(self.optimizers): - save_dict['opt_%02d'%i] = optim.state_dict() - - for i, sched in enumerate(self.schedulers): - save_dict['sched_%02d'%i] = sched.state_dict() - - torch.save(save_dict, save_path) - - def __patch_instance_norm_state_dict(self, state_dict, module, keys, i=0): - """Fix InstanceNorm checkpoints incompatibility (prior to 0.4)""" - key = keys[i] - if i + 1 == len(keys): # at the end, pointing to a parameter/buffer - if module.__class__.__name__.startswith('InstanceNorm') and \ - (key == 'running_mean' or key == 'running_var'): - if getattr(module, key) is None: - state_dict.pop('.'.join(keys)) - if module.__class__.__name__.startswith('InstanceNorm') and \ - (key == 'num_batches_tracked'): - state_dict.pop('.'.join(keys)) - else: - self.__patch_instance_norm_state_dict(state_dict, getattr(module, key), keys, i + 1) - - def load_networks(self, epoch): - """Load all the networks from the disk. - - Parameters: - epoch (int) -- current epoch; used in the file name '%s_net_%s.pth' % (epoch, name) - """ - if self.opt.isTrain and self.opt.pretrained_name is not None: - load_dir = os.path.join(self.opt.checkpoints_dir, self.opt.pretrained_name) - else: - load_dir = self.save_dir - load_filename = 'epoch_%s.pth' % (epoch) - load_path = os.path.join(load_dir, load_filename) - state_dict = torch.load(load_path, map_location=self.device) - print('loading the model from %s' % load_path) - - for name in self.model_names: - if isinstance(name, str): - net = getattr(self, name) - if isinstance(net, torch.nn.DataParallel): - net = net.module - net.load_state_dict(state_dict[name]) - - if self.opt.phase != 'test': - if self.opt.continue_train: - print('loading the optim from %s' % load_path) - for i, optim in enumerate(self.optimizers): - optim.load_state_dict(state_dict['opt_%02d'%i]) - - try: - print('loading the sched from %s' % load_path) - for i, sched in enumerate(self.schedulers): - sched.load_state_dict(state_dict['sched_%02d'%i]) - except: - print('Failed to load schedulers, set schedulers according to epoch count manually') - for i, sched in enumerate(self.schedulers): - sched.last_epoch = self.opt.epoch_count - 1 - - - - - def print_networks(self, verbose): - """Print the total number of parameters in the network and (if verbose) network architecture - - Parameters: - verbose (bool) -- if verbose: print the network architecture - """ - print('---------- Networks initialized -------------') - for name in self.model_names: - if isinstance(name, str): - net = getattr(self, name) - num_params = 0 - for param in net.parameters(): - num_params += param.numel() - if verbose: - print(net) - print('[Network %s] Total number of parameters : %.3f M' % (name, num_params / 1e6)) - print('-----------------------------------------------') - - def set_requires_grad(self, nets, requires_grad=False): - """Set requies_grad=Fasle for all the networks to avoid unnecessary computations - Parameters: - nets (network list) -- a list of networks - requires_grad (bool) -- whether the networks require gradients or not - """ - if not isinstance(nets, list): - nets = [nets] - for net in nets: - if net is not None: - for param in net.parameters(): - param.requires_grad = requires_grad - - def generate_visuals_for_evaluation(self, data, mode): - return {} diff --git a/src/face3d/models/bfm.py b/src/face3d/models/bfm.py deleted file mode 100644 index c8bf165..0000000 --- a/src/face3d/models/bfm.py +++ /dev/null @@ -1,331 +0,0 @@ -"""This script defines the parametric 3d face model for Deep3DFaceRecon_pytorch -""" - -import numpy as np -import torch -import torch.nn.functional as F -from scipy.io import loadmat -from custom_nodes.ComfyUI_Lam.src.face3d.util.load_mats import transferBFM09 -import os - -def perspective_projection(focal, center): - # return p.T (N, 3) @ (3, 3) - return np.array([ - focal, 0, center, - 0, focal, center, - 0, 0, 1 - ]).reshape([3, 3]).astype(np.float32).transpose() - -class SH: - def __init__(self): - self.a = [np.pi, 2 * np.pi / np.sqrt(3.), 2 * np.pi / np.sqrt(8.)] - self.c = [1/np.sqrt(4 * np.pi), np.sqrt(3.) / np.sqrt(4 * np.pi), 3 * np.sqrt(5.) / np.sqrt(12 * np.pi)] - - - -class ParametricFaceModel: - def __init__(self, - bfm_folder='./BFM', - recenter=True, - camera_distance=10., - init_lit=np.array([ - 0.8, 0, 0, 0, 0, 0, 0, 0, 0 - ]), - focal=1015., - center=112., - is_train=True, - default_name='BFM_model_front.mat'): - - if not os.path.isfile(os.path.join(bfm_folder, default_name)): - transferBFM09(bfm_folder) - - model = loadmat(os.path.join(bfm_folder, default_name)) - # mean face shape. [3*N,1] - self.mean_shape = model['meanshape'].astype(np.float32) - # identity basis. [3*N,80] - self.id_base = model['idBase'].astype(np.float32) - # expression basis. [3*N,64] - self.exp_base = model['exBase'].astype(np.float32) - # mean face texture. [3*N,1] (0-255) - self.mean_tex = model['meantex'].astype(np.float32) - # texture basis. [3*N,80] - self.tex_base = model['texBase'].astype(np.float32) - # face indices for each vertex that lies in. starts from 0. [N,8] - self.point_buf = model['point_buf'].astype(np.int64) - 1 - # vertex indices for each face. starts from 0. [F,3] - self.face_buf = model['tri'].astype(np.int64) - 1 - # vertex indices for 68 landmarks. starts from 0. [68,1] - self.keypoints = np.squeeze(model['keypoints']).astype(np.int64) - 1 - - if is_train: - # vertex indices for small face region to compute photometric error. starts from 0. - self.front_mask = np.squeeze(model['frontmask2_idx']).astype(np.int64) - 1 - # vertex indices for each face from small face region. starts from 0. [f,3] - self.front_face_buf = model['tri_mask2'].astype(np.int64) - 1 - # vertex indices for pre-defined skin region to compute reflectance loss - self.skin_mask = np.squeeze(model['skinmask']) - - if recenter: - mean_shape = self.mean_shape.reshape([-1, 3]) - mean_shape = mean_shape - np.mean(mean_shape, axis=0, keepdims=True) - self.mean_shape = mean_shape.reshape([-1, 1]) - - self.persc_proj = perspective_projection(focal, center) - self.device = 'cpu' - self.camera_distance = camera_distance - self.SH = SH() - self.init_lit = init_lit.reshape([1, 1, -1]).astype(np.float32) - - - def to(self, device): - self.device = device - for key, value in self.__dict__.items(): - if type(value).__module__ == np.__name__: - setattr(self, key, torch.tensor(value).to(device)) - - - def compute_shape(self, id_coeff, exp_coeff): - """ - Return: - face_shape -- torch.tensor, size (B, N, 3) - - Parameters: - id_coeff -- torch.tensor, size (B, 80), identity coeffs - exp_coeff -- torch.tensor, size (B, 64), expression coeffs - """ - batch_size = id_coeff.shape[0] - id_part = torch.einsum('ij,aj->ai', self.id_base, id_coeff) - exp_part = torch.einsum('ij,aj->ai', self.exp_base, exp_coeff) - face_shape = id_part + exp_part + self.mean_shape.reshape([1, -1]) - return face_shape.reshape([batch_size, -1, 3]) - - - def compute_texture(self, tex_coeff, normalize=True): - """ - Return: - face_texture -- torch.tensor, size (B, N, 3), in RGB order, range (0, 1.) - - Parameters: - tex_coeff -- torch.tensor, size (B, 80) - """ - batch_size = tex_coeff.shape[0] - face_texture = torch.einsum('ij,aj->ai', self.tex_base, tex_coeff) + self.mean_tex - if normalize: - face_texture = face_texture / 255. - return face_texture.reshape([batch_size, -1, 3]) - - - def compute_norm(self, face_shape): - """ - Return: - vertex_norm -- torch.tensor, size (B, N, 3) - - Parameters: - face_shape -- torch.tensor, size (B, N, 3) - """ - - v1 = face_shape[:, self.face_buf[:, 0]] - v2 = face_shape[:, self.face_buf[:, 1]] - v3 = face_shape[:, self.face_buf[:, 2]] - e1 = v1 - v2 - e2 = v2 - v3 - face_norm = torch.cross(e1, e2, dim=-1) - face_norm = F.normalize(face_norm, dim=-1, p=2) - face_norm = torch.cat([face_norm, torch.zeros(face_norm.shape[0], 1, 3).to(self.device)], dim=1) - - vertex_norm = torch.sum(face_norm[:, self.point_buf], dim=2) - vertex_norm = F.normalize(vertex_norm, dim=-1, p=2) - return vertex_norm - - - def compute_color(self, face_texture, face_norm, gamma): - """ - Return: - face_color -- torch.tensor, size (B, N, 3), range (0, 1.) - - Parameters: - face_texture -- torch.tensor, size (B, N, 3), from texture model, range (0, 1.) - face_norm -- torch.tensor, size (B, N, 3), rotated face normal - gamma -- torch.tensor, size (B, 27), SH coeffs - """ - batch_size = gamma.shape[0] - v_num = face_texture.shape[1] - a, c = self.SH.a, self.SH.c - gamma = gamma.reshape([batch_size, 3, 9]) - gamma = gamma + self.init_lit - gamma = gamma.permute(0, 2, 1) - Y = torch.cat([ - a[0] * c[0] * torch.ones_like(face_norm[..., :1]).to(self.device), - -a[1] * c[1] * face_norm[..., 1:2], - a[1] * c[1] * face_norm[..., 2:], - -a[1] * c[1] * face_norm[..., :1], - a[2] * c[2] * face_norm[..., :1] * face_norm[..., 1:2], - -a[2] * c[2] * face_norm[..., 1:2] * face_norm[..., 2:], - 0.5 * a[2] * c[2] / np.sqrt(3.) * (3 * face_norm[..., 2:] ** 2 - 1), - -a[2] * c[2] * face_norm[..., :1] * face_norm[..., 2:], - 0.5 * a[2] * c[2] * (face_norm[..., :1] ** 2 - face_norm[..., 1:2] ** 2) - ], dim=-1) - r = Y @ gamma[..., :1] - g = Y @ gamma[..., 1:2] - b = Y @ gamma[..., 2:] - face_color = torch.cat([r, g, b], dim=-1) * face_texture - return face_color - - - def compute_rotation(self, angles): - """ - Return: - rot -- torch.tensor, size (B, 3, 3) pts @ trans_mat - - Parameters: - angles -- torch.tensor, size (B, 3), radian - """ - - batch_size = angles.shape[0] - ones = torch.ones([batch_size, 1]).to(self.device) - zeros = torch.zeros([batch_size, 1]).to(self.device) - x, y, z = angles[:, :1], angles[:, 1:2], angles[:, 2:], - - rot_x = torch.cat([ - ones, zeros, zeros, - zeros, torch.cos(x), -torch.sin(x), - zeros, torch.sin(x), torch.cos(x) - ], dim=1).reshape([batch_size, 3, 3]) - - rot_y = torch.cat([ - torch.cos(y), zeros, torch.sin(y), - zeros, ones, zeros, - -torch.sin(y), zeros, torch.cos(y) - ], dim=1).reshape([batch_size, 3, 3]) - - rot_z = torch.cat([ - torch.cos(z), -torch.sin(z), zeros, - torch.sin(z), torch.cos(z), zeros, - zeros, zeros, ones - ], dim=1).reshape([batch_size, 3, 3]) - - rot = rot_z @ rot_y @ rot_x - return rot.permute(0, 2, 1) - - - def to_camera(self, face_shape): - face_shape[..., -1] = self.camera_distance - face_shape[..., -1] - return face_shape - - def to_image(self, face_shape): - """ - Return: - face_proj -- torch.tensor, size (B, N, 2), y direction is opposite to v direction - - Parameters: - face_shape -- torch.tensor, size (B, N, 3) - """ - # to image_plane - face_proj = face_shape @ self.persc_proj - face_proj = face_proj[..., :2] / face_proj[..., 2:] - - return face_proj - - - def transform(self, face_shape, rot, trans): - """ - Return: - face_shape -- torch.tensor, size (B, N, 3) pts @ rot + trans - - Parameters: - face_shape -- torch.tensor, size (B, N, 3) - rot -- torch.tensor, size (B, 3, 3) - trans -- torch.tensor, size (B, 3) - """ - return face_shape @ rot + trans.unsqueeze(1) - - - def get_landmarks(self, face_proj): - """ - Return: - face_lms -- torch.tensor, size (B, 68, 2) - - Parameters: - face_proj -- torch.tensor, size (B, N, 2) - """ - return face_proj[:, self.keypoints] - - def split_coeff(self, coeffs): - """ - Return: - coeffs_dict -- a dict of torch.tensors - - Parameters: - coeffs -- torch.tensor, size (B, 256) - """ - id_coeffs = coeffs[:, :80] - exp_coeffs = coeffs[:, 80: 144] - tex_coeffs = coeffs[:, 144: 224] - angles = coeffs[:, 224: 227] - gammas = coeffs[:, 227: 254] - translations = coeffs[:, 254:] - return { - 'id': id_coeffs, - 'exp': exp_coeffs, - 'tex': tex_coeffs, - 'angle': angles, - 'gamma': gammas, - 'trans': translations - } - def compute_for_render(self, coeffs): - """ - Return: - face_vertex -- torch.tensor, size (B, N, 3), in camera coordinate - face_color -- torch.tensor, size (B, N, 3), in RGB order - landmark -- torch.tensor, size (B, 68, 2), y direction is opposite to v direction - Parameters: - coeffs -- torch.tensor, size (B, 257) - """ - coef_dict = self.split_coeff(coeffs) - face_shape = self.compute_shape(coef_dict['id'], coef_dict['exp']) - rotation = self.compute_rotation(coef_dict['angle']) - - - face_shape_transformed = self.transform(face_shape, rotation, coef_dict['trans']) - face_vertex = self.to_camera(face_shape_transformed) - - face_proj = self.to_image(face_vertex) - landmark = self.get_landmarks(face_proj) - - face_texture = self.compute_texture(coef_dict['tex']) - face_norm = self.compute_norm(face_shape) - face_norm_roted = face_norm @ rotation - face_color = self.compute_color(face_texture, face_norm_roted, coef_dict['gamma']) - - return face_vertex, face_texture, face_color, landmark - - def compute_for_render_woRotation(self, coeffs): - """ - Return: - face_vertex -- torch.tensor, size (B, N, 3), in camera coordinate - face_color -- torch.tensor, size (B, N, 3), in RGB order - landmark -- torch.tensor, size (B, 68, 2), y direction is opposite to v direction - Parameters: - coeffs -- torch.tensor, size (B, 257) - """ - coef_dict = self.split_coeff(coeffs) - face_shape = self.compute_shape(coef_dict['id'], coef_dict['exp']) - #rotation = self.compute_rotation(coef_dict['angle']) - - - #face_shape_transformed = self.transform(face_shape, rotation, coef_dict['trans']) - face_vertex = self.to_camera(face_shape) - - face_proj = self.to_image(face_vertex) - landmark = self.get_landmarks(face_proj) - - face_texture = self.compute_texture(coef_dict['tex']) - face_norm = self.compute_norm(face_shape) - face_norm_roted = face_norm # @ rotation - face_color = self.compute_color(face_texture, face_norm_roted, coef_dict['gamma']) - - return face_vertex, face_texture, face_color, landmark - - -if __name__ == '__main__': - transferBFM09() \ No newline at end of file diff --git a/src/face3d/models/facerecon_model.py b/src/face3d/models/facerecon_model.py deleted file mode 100644 index 175de48..0000000 --- a/src/face3d/models/facerecon_model.py +++ /dev/null @@ -1,220 +0,0 @@ -"""This script defines the face reconstruction model for Deep3DFaceRecon_pytorch -""" - -import numpy as np -import torch -from custom_nodes.ComfyUI_Lam.src.face3d.models.base_model import BaseModel -from custom_nodes.ComfyUI_Lam.src.face3d.models import networks -from custom_nodes.ComfyUI_Lam.src.face3d.models.bfm import ParametricFaceModel -from custom_nodes.ComfyUI_Lam.src.face3d.models.losses import perceptual_loss, photo_loss, reg_loss, reflectance_loss, landmark_loss -from custom_nodes.ComfyUI_Lam.src.face3d.util import util -from custom_nodes.ComfyUI_Lam.src.face3d.util.nvdiffrast import MeshRenderer -# from src.face3d.util.preprocess import estimate_norm_torch - -import trimesh -from scipy.io import savemat - -class FaceReconModel(BaseModel): - - @staticmethod - def modify_commandline_options(parser, is_train=False): - """ Configures options specific for CUT model - """ - # net structure and parameters - parser.add_argument('--net_recon', type=str, default='resnet50', choices=['resnet18', 'resnet34', 'resnet50'], help='network structure') - parser.add_argument('--init_path', type=str, default='./checkpoints/init_model/resnet50-0676ba61.pth') - parser.add_argument('--use_last_fc', type=util.str2bool, nargs='?', const=True, default=False, help='zero initialize the last fc') - parser.add_argument('--bfm_folder', type=str, default='./checkpoints/BFM_Fitting/') - parser.add_argument('--bfm_model', type=str, default='BFM_model_front.mat', help='bfm model') - - # renderer parameters - parser.add_argument('--focal', type=float, default=1015.) - parser.add_argument('--center', type=float, default=112.) - parser.add_argument('--camera_d', type=float, default=10.) - parser.add_argument('--z_near', type=float, default=5.) - parser.add_argument('--z_far', type=float, default=15.) - - if is_train: - # training parameters - parser.add_argument('--net_recog', type=str, default='r50', choices=['r18', 'r43', 'r50'], help='face recog network structure') - parser.add_argument('--net_recog_path', type=str, default='checkpoints/recog_model/ms1mv3_arcface_r50_fp16/backbone.pth') - parser.add_argument('--use_crop_face', type=util.str2bool, nargs='?', const=True, default=False, help='use crop mask for photo loss') - parser.add_argument('--use_predef_M', type=util.str2bool, nargs='?', const=True, default=False, help='use predefined M for predicted face') - - - # augmentation parameters - parser.add_argument('--shift_pixs', type=float, default=10., help='shift pixels') - parser.add_argument('--scale_delta', type=float, default=0.1, help='delta scale factor') - parser.add_argument('--rot_angle', type=float, default=10., help='rot angles, degree') - - # loss weights - parser.add_argument('--w_feat', type=float, default=0.2, help='weight for feat loss') - parser.add_argument('--w_color', type=float, default=1.92, help='weight for loss loss') - parser.add_argument('--w_reg', type=float, default=3.0e-4, help='weight for reg loss') - parser.add_argument('--w_id', type=float, default=1.0, help='weight for id_reg loss') - parser.add_argument('--w_exp', type=float, default=0.8, help='weight for exp_reg loss') - parser.add_argument('--w_tex', type=float, default=1.7e-2, help='weight for tex_reg loss') - parser.add_argument('--w_gamma', type=float, default=10.0, help='weight for gamma loss') - parser.add_argument('--w_lm', type=float, default=1.6e-3, help='weight for lm loss') - parser.add_argument('--w_reflc', type=float, default=5.0, help='weight for reflc loss') - - opt, _ = parser.parse_known_args() - parser.set_defaults( - focal=1015., center=112., camera_d=10., use_last_fc=False, z_near=5., z_far=15. - ) - if is_train: - parser.set_defaults( - use_crop_face=True, use_predef_M=False - ) - return parser - - def __init__(self, opt): - """Initialize this model class. - - Parameters: - opt -- training/test options - - A few things can be done here. - - (required) call the initialization function of BaseModel - - define loss function, visualization images, model names, and optimizers - """ - BaseModel.__init__(self, opt) # call the initialization method of BaseModel - - self.visual_names = ['output_vis'] - self.model_names = ['net_recon'] - self.parallel_names = self.model_names + ['renderer'] - - self.facemodel = ParametricFaceModel( - bfm_folder=opt.bfm_folder, camera_distance=opt.camera_d, focal=opt.focal, center=opt.center, - is_train=self.isTrain, default_name=opt.bfm_model - ) - - fov = 2 * np.arctan(opt.center / opt.focal) * 180 / np.pi - self.renderer = MeshRenderer( - rasterize_fov=fov, znear=opt.z_near, zfar=opt.z_far, rasterize_size=int(2 * opt.center) - ) - - if self.isTrain: - self.loss_names = ['all', 'feat', 'color', 'lm', 'reg', 'gamma', 'reflc'] - - self.net_recog = networks.define_net_recog( - net_recog=opt.net_recog, pretrained_path=opt.net_recog_path - ) - # loss func name: (compute_%s_loss) % loss_name - self.compute_feat_loss = perceptual_loss - self.comupte_color_loss = photo_loss - self.compute_lm_loss = landmark_loss - self.compute_reg_loss = reg_loss - self.compute_reflc_loss = reflectance_loss - - self.optimizer = torch.optim.Adam(self.net_recon.parameters(), lr=opt.lr) - self.optimizers = [self.optimizer] - self.parallel_names += ['net_recog'] - # Our program will automatically call to define schedulers, load networks, and print networks - - def set_input(self, input): - """Unpack input data from the dataloader and perform necessary pre-processing steps. - - Parameters: - input: a dictionary that contains the data itself and its metadata information. - """ - self.input_img = input['imgs'].to(self.device) - self.atten_mask = input['msks'].to(self.device) if 'msks' in input else None - self.gt_lm = input['lms'].to(self.device) if 'lms' in input else None - self.trans_m = input['M'].to(self.device) if 'M' in input else None - self.image_paths = input['im_paths'] if 'im_paths' in input else None - - def forward(self, output_coeff, device): - self.facemodel.to(device) - self.pred_vertex, self.pred_tex, self.pred_color, self.pred_lm = \ - self.facemodel.compute_for_render(output_coeff) - self.pred_mask, _, self.pred_face = self.renderer( - self.pred_vertex, self.facemodel.face_buf, feat=self.pred_color) - - self.pred_coeffs_dict = self.facemodel.split_coeff(output_coeff) - - - def compute_losses(self): - """Calculate losses, gradients, and update network weights; called in every training iteration""" - - assert self.net_recog.training == False - trans_m = self.trans_m - if not self.opt.use_predef_M: - trans_m = estimate_norm_torch(self.pred_lm, self.input_img.shape[-2]) - - pred_feat = self.net_recog(self.pred_face, trans_m) - gt_feat = self.net_recog(self.input_img, self.trans_m) - self.loss_feat = self.opt.w_feat * self.compute_feat_loss(pred_feat, gt_feat) - - face_mask = self.pred_mask - if self.opt.use_crop_face: - face_mask, _, _ = self.renderer(self.pred_vertex, self.facemodel.front_face_buf) - - face_mask = face_mask.detach() - self.loss_color = self.opt.w_color * self.comupte_color_loss( - self.pred_face, self.input_img, self.atten_mask * face_mask) - - loss_reg, loss_gamma = self.compute_reg_loss(self.pred_coeffs_dict, self.opt) - self.loss_reg = self.opt.w_reg * loss_reg - self.loss_gamma = self.opt.w_gamma * loss_gamma - - self.loss_lm = self.opt.w_lm * self.compute_lm_loss(self.pred_lm, self.gt_lm) - - self.loss_reflc = self.opt.w_reflc * self.compute_reflc_loss(self.pred_tex, self.facemodel.skin_mask) - - self.loss_all = self.loss_feat + self.loss_color + self.loss_reg + self.loss_gamma \ - + self.loss_lm + self.loss_reflc - - - def optimize_parameters(self, isTrain=True): - self.forward() - self.compute_losses() - """Update network weights; it will be called in every training iteration.""" - if isTrain: - self.optimizer.zero_grad() - self.loss_all.backward() - self.optimizer.step() - - def compute_visuals(self): - with torch.no_grad(): - input_img_numpy = 255. * self.input_img.detach().cpu().permute(0, 2, 3, 1).numpy() - output_vis = self.pred_face * self.pred_mask + (1 - self.pred_mask) * self.input_img - output_vis_numpy_raw = 255. * output_vis.detach().cpu().permute(0, 2, 3, 1).numpy() - - if self.gt_lm is not None: - gt_lm_numpy = self.gt_lm.cpu().numpy() - pred_lm_numpy = self.pred_lm.detach().cpu().numpy() - output_vis_numpy = util.draw_landmarks(output_vis_numpy_raw, gt_lm_numpy, 'b') - output_vis_numpy = util.draw_landmarks(output_vis_numpy, pred_lm_numpy, 'r') - - output_vis_numpy = np.concatenate((input_img_numpy, - output_vis_numpy_raw, output_vis_numpy), axis=-2) - else: - output_vis_numpy = np.concatenate((input_img_numpy, - output_vis_numpy_raw), axis=-2) - - self.output_vis = torch.tensor( - output_vis_numpy / 255., dtype=torch.float32 - ).permute(0, 3, 1, 2).to(self.device) - - def save_mesh(self, name): - - recon_shape = self.pred_vertex # get reconstructed shape - recon_shape[..., -1] = 10 - recon_shape[..., -1] # from camera space to world space - recon_shape = recon_shape.cpu().numpy()[0] - recon_color = self.pred_color - recon_color = recon_color.cpu().numpy()[0] - tri = self.facemodel.face_buf.cpu().numpy() - mesh = trimesh.Trimesh(vertices=recon_shape, faces=tri, vertex_colors=np.clip(255. * recon_color, 0, 255).astype(np.uint8)) - mesh.export(name) - - def save_coeff(self,name): - - pred_coeffs = {key:self.pred_coeffs_dict[key].cpu().numpy() for key in self.pred_coeffs_dict} - pred_lm = self.pred_lm.cpu().numpy() - pred_lm = np.stack([pred_lm[:,:,0],self.input_img.shape[2]-1-pred_lm[:,:,1]],axis=2) # transfer to image coordinate - pred_coeffs['lm68'] = pred_lm - savemat(name,pred_coeffs) - - - diff --git a/src/face3d/models/losses.py b/src/face3d/models/losses.py deleted file mode 100644 index 09d6a85..0000000 --- a/src/face3d/models/losses.py +++ /dev/null @@ -1,113 +0,0 @@ -import numpy as np -import torch -import torch.nn as nn -from kornia.geometry import warp_affine -import torch.nn.functional as F - -def resize_n_crop(image, M, dsize=112): - # image: (b, c, h, w) - # M : (b, 2, 3) - return warp_affine(image, M, dsize=(dsize, dsize), align_corners=True) - -### perceptual level loss -class PerceptualLoss(nn.Module): - def __init__(self, recog_net, input_size=112): - super(PerceptualLoss, self).__init__() - self.recog_net = recog_net - self.preprocess = lambda x: 2 * x - 1 - self.input_size=input_size - def forward(imageA, imageB, M): - """ - 1 - cosine distance - Parameters: - imageA --torch.tensor (B, 3, H, W), range (0, 1) , RGB order - imageB --same as imageA - """ - - imageA = self.preprocess(resize_n_crop(imageA, M, self.input_size)) - imageB = self.preprocess(resize_n_crop(imageB, M, self.input_size)) - - # freeze bn - self.recog_net.eval() - - id_featureA = F.normalize(self.recog_net(imageA), dim=-1, p=2) - id_featureB = F.normalize(self.recog_net(imageB), dim=-1, p=2) - cosine_d = torch.sum(id_featureA * id_featureB, dim=-1) - # assert torch.sum((cosine_d > 1).float()) == 0 - return torch.sum(1 - cosine_d) / cosine_d.shape[0] - -def perceptual_loss(id_featureA, id_featureB): - cosine_d = torch.sum(id_featureA * id_featureB, dim=-1) - # assert torch.sum((cosine_d > 1).float()) == 0 - return torch.sum(1 - cosine_d) / cosine_d.shape[0] - -### image level loss -def photo_loss(imageA, imageB, mask, eps=1e-6): - """ - l2 norm (with sqrt, to ensure backward stabililty, use eps, otherwise Nan may occur) - Parameters: - imageA --torch.tensor (B, 3, H, W), range (0, 1), RGB order - imageB --same as imageA - """ - loss = torch.sqrt(eps + torch.sum((imageA - imageB) ** 2, dim=1, keepdims=True)) * mask - loss = torch.sum(loss) / torch.max(torch.sum(mask), torch.tensor(1.0).to(mask.device)) - return loss - -def landmark_loss(predict_lm, gt_lm, weight=None): - """ - weighted mse loss - Parameters: - predict_lm --torch.tensor (B, 68, 2) - gt_lm --torch.tensor (B, 68, 2) - weight --numpy.array (1, 68) - """ - if not weight: - weight = np.ones([68]) - weight[28:31] = 20 - weight[-8:] = 20 - weight = np.expand_dims(weight, 0) - weight = torch.tensor(weight).to(predict_lm.device) - loss = torch.sum((predict_lm - gt_lm)**2, dim=-1) * weight - loss = torch.sum(loss) / (predict_lm.shape[0] * predict_lm.shape[1]) - return loss - - -### regulization -def reg_loss(coeffs_dict, opt=None): - """ - l2 norm without the sqrt, from yu's implementation (mse) - tf.nn.l2_loss https://www.tensorflow.org/api_docs/python/tf/nn/l2_loss - Parameters: - coeffs_dict -- a dict of torch.tensors , keys: id, exp, tex, angle, gamma, trans - - """ - # coefficient regularization to ensure plausible 3d faces - if opt: - w_id, w_exp, w_tex = opt.w_id, opt.w_exp, opt.w_tex - else: - w_id, w_exp, w_tex = 1, 1, 1, 1 - creg_loss = w_id * torch.sum(coeffs_dict['id'] ** 2) + \ - w_exp * torch.sum(coeffs_dict['exp'] ** 2) + \ - w_tex * torch.sum(coeffs_dict['tex'] ** 2) - creg_loss = creg_loss / coeffs_dict['id'].shape[0] - - # gamma regularization to ensure a nearly-monochromatic light - gamma = coeffs_dict['gamma'].reshape([-1, 3, 9]) - gamma_mean = torch.mean(gamma, dim=1, keepdims=True) - gamma_loss = torch.mean((gamma - gamma_mean) ** 2) - - return creg_loss, gamma_loss - -def reflectance_loss(texture, mask): - """ - minimize texture variance (mse), albedo regularization to ensure an uniform skin albedo - Parameters: - texture --torch.tensor, (B, N, 3) - mask --torch.tensor, (N), 1 or 0 - - """ - mask = mask.reshape([1, mask.shape[0], 1]) - texture_mean = torch.sum(mask * texture, dim=1, keepdims=True) / torch.sum(mask) - loss = torch.sum(((texture - texture_mean) * mask)**2) / (texture.shape[0] * torch.sum(mask)) - return loss - diff --git a/src/face3d/models/networks.py b/src/face3d/models/networks.py deleted file mode 100644 index ead9cdc..0000000 --- a/src/face3d/models/networks.py +++ /dev/null @@ -1,521 +0,0 @@ -"""This script defines deep neural networks for Deep3DFaceRecon_pytorch -""" - -import os -import numpy as np -import torch.nn.functional as F -from torch.nn import init -import functools -from torch.optim import lr_scheduler -import torch -from torch import Tensor -import torch.nn as nn -try: - from torch.hub import load_state_dict_from_url -except ImportError: - from torch.utils.model_zoo import load_url as load_state_dict_from_url -from typing import Type, Any, Callable, Union, List, Optional -from .arcface_torch.backbones import get_model -from kornia.geometry import warp_affine - -def resize_n_crop(image, M, dsize=112): - # image: (b, c, h, w) - # M : (b, 2, 3) - return warp_affine(image, M, dsize=(dsize, dsize), align_corners=True) - -def filter_state_dict(state_dict, remove_name='fc'): - new_state_dict = {} - for key in state_dict: - if remove_name in key: - continue - new_state_dict[key] = state_dict[key] - return new_state_dict - -def get_scheduler(optimizer, opt): - """Return a learning rate scheduler - - Parameters: - optimizer -- the optimizer of the network - opt (option class) -- stores all the experiment flags; needs to be a subclass of BaseOptions.  - opt.lr_policy is the name of learning rate policy: linear | step | plateau | cosine - - For other schedulers (step, plateau, and cosine), we use the default PyTorch schedulers. - See https://pytorch.org/docs/stable/optim.html for more details. - """ - if opt.lr_policy == 'linear': - def lambda_rule(epoch): - lr_l = 1.0 - max(0, epoch + opt.epoch_count - opt.n_epochs) / float(opt.n_epochs + 1) - return lr_l - scheduler = lr_scheduler.LambdaLR(optimizer, lr_lambda=lambda_rule) - elif opt.lr_policy == 'step': - scheduler = lr_scheduler.StepLR(optimizer, step_size=opt.lr_decay_epochs, gamma=0.2) - elif opt.lr_policy == 'plateau': - scheduler = lr_scheduler.ReduceLROnPlateau(optimizer, mode='min', factor=0.2, threshold=0.01, patience=5) - elif opt.lr_policy == 'cosine': - scheduler = lr_scheduler.CosineAnnealingLR(optimizer, T_max=opt.n_epochs, eta_min=0) - else: - return NotImplementedError('learning rate policy [%s] is not implemented', opt.lr_policy) - return scheduler - - -def define_net_recon(net_recon, use_last_fc=False, init_path=None): - return ReconNetWrapper(net_recon, use_last_fc=use_last_fc, init_path=init_path) - -def define_net_recog(net_recog, pretrained_path=None): - net = RecogNetWrapper(net_recog=net_recog, pretrained_path=pretrained_path) - net.eval() - return net - -class ReconNetWrapper(nn.Module): - fc_dim=257 - def __init__(self, net_recon, use_last_fc=False, init_path=None): - super(ReconNetWrapper, self).__init__() - self.use_last_fc = use_last_fc - if net_recon not in func_dict: - return NotImplementedError('network [%s] is not implemented', net_recon) - func, last_dim = func_dict[net_recon] - backbone = func(use_last_fc=use_last_fc, num_classes=self.fc_dim) - if init_path and os.path.isfile(init_path): - state_dict = filter_state_dict(torch.load(init_path, map_location='cpu')) - backbone.load_state_dict(state_dict) - print("loading init net_recon %s from %s" %(net_recon, init_path)) - self.backbone = backbone - if not use_last_fc: - self.final_layers = nn.ModuleList([ - conv1x1(last_dim, 80, bias=True), # id layer - conv1x1(last_dim, 64, bias=True), # exp layer - conv1x1(last_dim, 80, bias=True), # tex layer - conv1x1(last_dim, 3, bias=True), # angle layer - conv1x1(last_dim, 27, bias=True), # gamma layer - conv1x1(last_dim, 2, bias=True), # tx, ty - conv1x1(last_dim, 1, bias=True) # tz - ]) - for m in self.final_layers: - nn.init.constant_(m.weight, 0.) - nn.init.constant_(m.bias, 0.) - - def forward(self, x): - x = self.backbone(x) - if not self.use_last_fc: - output = [] - for layer in self.final_layers: - output.append(layer(x)) - x = torch.flatten(torch.cat(output, dim=1), 1) - return x - - -class RecogNetWrapper(nn.Module): - def __init__(self, net_recog, pretrained_path=None, input_size=112): - super(RecogNetWrapper, self).__init__() - net = get_model(name=net_recog, fp16=False) - if pretrained_path: - state_dict = torch.load(pretrained_path, map_location='cpu') - net.load_state_dict(state_dict) - print("loading pretrained net_recog %s from %s" %(net_recog, pretrained_path)) - for param in net.parameters(): - param.requires_grad = False - self.net = net - self.preprocess = lambda x: 2 * x - 1 - self.input_size=input_size - - def forward(self, image, M): - image = self.preprocess(resize_n_crop(image, M, self.input_size)) - id_feature = F.normalize(self.net(image), dim=-1, p=2) - return id_feature - - -# adapted from https://github.com/pytorch/vision/edit/master/torchvision/models/resnet.py -__all__ = ['ResNet', 'resnet18', 'resnet34', 'resnet50', 'resnet101', - 'resnet152', 'resnext50_32x4d', 'resnext101_32x8d', - 'wide_resnet50_2', 'wide_resnet101_2'] - - -model_urls = { - 'resnet18': 'https://download.pytorch.org/models/resnet18-f37072fd.pth', - 'resnet34': 'https://download.pytorch.org/models/resnet34-b627a593.pth', - 'resnet50': 'https://download.pytorch.org/models/resnet50-0676ba61.pth', - 'resnet101': 'https://download.pytorch.org/models/resnet101-63fe2227.pth', - 'resnet152': 'https://download.pytorch.org/models/resnet152-394f9c45.pth', - 'resnext50_32x4d': 'https://download.pytorch.org/models/resnext50_32x4d-7cdf4587.pth', - 'resnext101_32x8d': 'https://download.pytorch.org/models/resnext101_32x8d-8ba56ff5.pth', - 'wide_resnet50_2': 'https://download.pytorch.org/models/wide_resnet50_2-95faca4d.pth', - 'wide_resnet101_2': 'https://download.pytorch.org/models/wide_resnet101_2-32ee1156.pth', -} - - -def conv3x3(in_planes: int, out_planes: int, stride: int = 1, groups: int = 1, dilation: int = 1) -> nn.Conv2d: - """3x3 convolution with padding""" - return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride, - padding=dilation, groups=groups, bias=False, dilation=dilation) - - -def conv1x1(in_planes: int, out_planes: int, stride: int = 1, bias: bool = False) -> nn.Conv2d: - """1x1 convolution""" - return nn.Conv2d(in_planes, out_planes, kernel_size=1, stride=stride, bias=bias) - - -class BasicBlock(nn.Module): - expansion: int = 1 - - def __init__( - self, - inplanes: int, - planes: int, - stride: int = 1, - downsample: Optional[nn.Module] = None, - groups: int = 1, - base_width: int = 64, - dilation: int = 1, - norm_layer: Optional[Callable[..., nn.Module]] = None - ) -> None: - super(BasicBlock, self).__init__() - if norm_layer is None: - norm_layer = nn.BatchNorm2d - if groups != 1 or base_width != 64: - raise ValueError('BasicBlock only supports groups=1 and base_width=64') - if dilation > 1: - raise NotImplementedError("Dilation > 1 not supported in BasicBlock") - # Both self.conv1 and self.downsample layers downsample the input when stride != 1 - self.conv1 = conv3x3(inplanes, planes, stride) - self.bn1 = norm_layer(planes) - self.relu = nn.ReLU(inplace=True) - self.conv2 = conv3x3(planes, planes) - self.bn2 = norm_layer(planes) - self.downsample = downsample - self.stride = stride - - def forward(self, x: Tensor) -> Tensor: - identity = x - - out = self.conv1(x) - out = self.bn1(out) - out = self.relu(out) - - out = self.conv2(out) - out = self.bn2(out) - - if self.downsample is not None: - identity = self.downsample(x) - - out += identity - out = self.relu(out) - - return out - - -class Bottleneck(nn.Module): - # Bottleneck in torchvision places the stride for downsampling at 3x3 convolution(self.conv2) - # while original implementation places the stride at the first 1x1 convolution(self.conv1) - # according to "Deep residual learning for image recognition"https://arxiv.org/abs/1512.03385. - # This variant is also known as ResNet V1.5 and improves accuracy according to - # https://ngc.nvidia.com/catalog/model-scripts/nvidia:resnet_50_v1_5_for_pytorch. - - expansion: int = 4 - - def __init__( - self, - inplanes: int, - planes: int, - stride: int = 1, - downsample: Optional[nn.Module] = None, - groups: int = 1, - base_width: int = 64, - dilation: int = 1, - norm_layer: Optional[Callable[..., nn.Module]] = None - ) -> None: - super(Bottleneck, self).__init__() - if norm_layer is None: - norm_layer = nn.BatchNorm2d - width = int(planes * (base_width / 64.)) * groups - # Both self.conv2 and self.downsample layers downsample the input when stride != 1 - self.conv1 = conv1x1(inplanes, width) - self.bn1 = norm_layer(width) - self.conv2 = conv3x3(width, width, stride, groups, dilation) - self.bn2 = norm_layer(width) - self.conv3 = conv1x1(width, planes * self.expansion) - self.bn3 = norm_layer(planes * self.expansion) - self.relu = nn.ReLU(inplace=True) - self.downsample = downsample - self.stride = stride - - def forward(self, x: Tensor) -> Tensor: - identity = x - - out = self.conv1(x) - out = self.bn1(out) - out = self.relu(out) - - out = self.conv2(out) - out = self.bn2(out) - out = self.relu(out) - - out = self.conv3(out) - out = self.bn3(out) - - if self.downsample is not None: - identity = self.downsample(x) - - out += identity - out = self.relu(out) - - return out - - -class ResNet(nn.Module): - - def __init__( - self, - block: Type[Union[BasicBlock, Bottleneck]], - layers: List[int], - num_classes: int = 1000, - zero_init_residual: bool = False, - use_last_fc: bool = False, - groups: int = 1, - width_per_group: int = 64, - replace_stride_with_dilation: Optional[List[bool]] = None, - norm_layer: Optional[Callable[..., nn.Module]] = None - ) -> None: - super(ResNet, self).__init__() - if norm_layer is None: - norm_layer = nn.BatchNorm2d - self._norm_layer = norm_layer - - self.inplanes = 64 - self.dilation = 1 - if replace_stride_with_dilation is None: - # each element in the tuple indicates if we should replace - # the 2x2 stride with a dilated convolution instead - replace_stride_with_dilation = [False, False, False] - if len(replace_stride_with_dilation) != 3: - raise ValueError("replace_stride_with_dilation should be None " - "or a 3-element tuple, got {}".format(replace_stride_with_dilation)) - self.use_last_fc = use_last_fc - self.groups = groups - self.base_width = width_per_group - self.conv1 = nn.Conv2d(3, self.inplanes, kernel_size=7, stride=2, padding=3, - bias=False) - self.bn1 = norm_layer(self.inplanes) - self.relu = nn.ReLU(inplace=True) - self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1) - self.layer1 = self._make_layer(block, 64, layers[0]) - self.layer2 = self._make_layer(block, 128, layers[1], stride=2, - dilate=replace_stride_with_dilation[0]) - self.layer3 = self._make_layer(block, 256, layers[2], stride=2, - dilate=replace_stride_with_dilation[1]) - self.layer4 = self._make_layer(block, 512, layers[3], stride=2, - dilate=replace_stride_with_dilation[2]) - self.avgpool = nn.AdaptiveAvgPool2d((1, 1)) - - if self.use_last_fc: - self.fc = nn.Linear(512 * block.expansion, num_classes) - - for m in self.modules(): - if isinstance(m, nn.Conv2d): - nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu') - elif isinstance(m, (nn.BatchNorm2d, nn.GroupNorm)): - nn.init.constant_(m.weight, 1) - nn.init.constant_(m.bias, 0) - - - - # Zero-initialize the last BN in each residual branch, - # so that the residual branch starts with zeros, and each residual block behaves like an identity. - # This improves the model by 0.2~0.3% according to https://arxiv.org/abs/1706.02677 - if zero_init_residual: - for m in self.modules(): - if isinstance(m, Bottleneck): - nn.init.constant_(m.bn3.weight, 0) # type: ignore[arg-type] - elif isinstance(m, BasicBlock): - nn.init.constant_(m.bn2.weight, 0) # type: ignore[arg-type] - - def _make_layer(self, block: Type[Union[BasicBlock, Bottleneck]], planes: int, blocks: int, - stride: int = 1, dilate: bool = False) -> nn.Sequential: - norm_layer = self._norm_layer - downsample = None - previous_dilation = self.dilation - if dilate: - self.dilation *= stride - stride = 1 - if stride != 1 or self.inplanes != planes * block.expansion: - downsample = nn.Sequential( - conv1x1(self.inplanes, planes * block.expansion, stride), - norm_layer(planes * block.expansion), - ) - - layers = [] - layers.append(block(self.inplanes, planes, stride, downsample, self.groups, - self.base_width, previous_dilation, norm_layer)) - self.inplanes = planes * block.expansion - for _ in range(1, blocks): - layers.append(block(self.inplanes, planes, groups=self.groups, - base_width=self.base_width, dilation=self.dilation, - norm_layer=norm_layer)) - - return nn.Sequential(*layers) - - def _forward_impl(self, x: Tensor) -> Tensor: - # See note [TorchScript super()] - x = self.conv1(x) - x = self.bn1(x) - x = self.relu(x) - x = self.maxpool(x) - - x = self.layer1(x) - x = self.layer2(x) - x = self.layer3(x) - x = self.layer4(x) - - x = self.avgpool(x) - if self.use_last_fc: - x = torch.flatten(x, 1) - x = self.fc(x) - return x - - def forward(self, x: Tensor) -> Tensor: - return self._forward_impl(x) - - -def _resnet( - arch: str, - block: Type[Union[BasicBlock, Bottleneck]], - layers: List[int], - pretrained: bool, - progress: bool, - **kwargs: Any -) -> ResNet: - model = ResNet(block, layers, **kwargs) - if pretrained: - state_dict = load_state_dict_from_url(model_urls[arch], - progress=progress) - model.load_state_dict(state_dict) - return model - - -def resnet18(pretrained: bool = False, progress: bool = True, **kwargs: Any) -> ResNet: - r"""ResNet-18 model from - `"Deep Residual Learning for Image Recognition" `_. - - Args: - pretrained (bool): If True, returns a model pre-trained on ImageNet - progress (bool): If True, displays a progress bar of the download to stderr - """ - return _resnet('resnet18', BasicBlock, [2, 2, 2, 2], pretrained, progress, - **kwargs) - - -def resnet34(pretrained: bool = False, progress: bool = True, **kwargs: Any) -> ResNet: - r"""ResNet-34 model from - `"Deep Residual Learning for Image Recognition" `_. - - Args: - pretrained (bool): If True, returns a model pre-trained on ImageNet - progress (bool): If True, displays a progress bar of the download to stderr - """ - return _resnet('resnet34', BasicBlock, [3, 4, 6, 3], pretrained, progress, - **kwargs) - - -def resnet50(pretrained: bool = False, progress: bool = True, **kwargs: Any) -> ResNet: - r"""ResNet-50 model from - `"Deep Residual Learning for Image Recognition" `_. - - Args: - pretrained (bool): If True, returns a model pre-trained on ImageNet - progress (bool): If True, displays a progress bar of the download to stderr - """ - return _resnet('resnet50', Bottleneck, [3, 4, 6, 3], pretrained, progress, - **kwargs) - - -def resnet101(pretrained: bool = False, progress: bool = True, **kwargs: Any) -> ResNet: - r"""ResNet-101 model from - `"Deep Residual Learning for Image Recognition" `_. - - Args: - pretrained (bool): If True, returns a model pre-trained on ImageNet - progress (bool): If True, displays a progress bar of the download to stderr - """ - return _resnet('resnet101', Bottleneck, [3, 4, 23, 3], pretrained, progress, - **kwargs) - - -def resnet152(pretrained: bool = False, progress: bool = True, **kwargs: Any) -> ResNet: - r"""ResNet-152 model from - `"Deep Residual Learning for Image Recognition" `_. - - Args: - pretrained (bool): If True, returns a model pre-trained on ImageNet - progress (bool): If True, displays a progress bar of the download to stderr - """ - return _resnet('resnet152', Bottleneck, [3, 8, 36, 3], pretrained, progress, - **kwargs) - - -def resnext50_32x4d(pretrained: bool = False, progress: bool = True, **kwargs: Any) -> ResNet: - r"""ResNeXt-50 32x4d model from - `"Aggregated Residual Transformation for Deep Neural Networks" `_. - - Args: - pretrained (bool): If True, returns a model pre-trained on ImageNet - progress (bool): If True, displays a progress bar of the download to stderr - """ - kwargs['groups'] = 32 - kwargs['width_per_group'] = 4 - return _resnet('resnext50_32x4d', Bottleneck, [3, 4, 6, 3], - pretrained, progress, **kwargs) - - -def resnext101_32x8d(pretrained: bool = False, progress: bool = True, **kwargs: Any) -> ResNet: - r"""ResNeXt-101 32x8d model from - `"Aggregated Residual Transformation for Deep Neural Networks" `_. - - Args: - pretrained (bool): If True, returns a model pre-trained on ImageNet - progress (bool): If True, displays a progress bar of the download to stderr - """ - kwargs['groups'] = 32 - kwargs['width_per_group'] = 8 - return _resnet('resnext101_32x8d', Bottleneck, [3, 4, 23, 3], - pretrained, progress, **kwargs) - - -def wide_resnet50_2(pretrained: bool = False, progress: bool = True, **kwargs: Any) -> ResNet: - r"""Wide ResNet-50-2 model from - `"Wide Residual Networks" `_. - - The model is the same as ResNet except for the bottleneck number of channels - which is twice larger in every block. The number of channels in outer 1x1 - convolutions is the same, e.g. last block in ResNet-50 has 2048-512-2048 - channels, and in Wide ResNet-50-2 has 2048-1024-2048. - - Args: - pretrained (bool): If True, returns a model pre-trained on ImageNet - progress (bool): If True, displays a progress bar of the download to stderr - """ - kwargs['width_per_group'] = 64 * 2 - return _resnet('wide_resnet50_2', Bottleneck, [3, 4, 6, 3], - pretrained, progress, **kwargs) - - -def wide_resnet101_2(pretrained: bool = False, progress: bool = True, **kwargs: Any) -> ResNet: - r"""Wide ResNet-101-2 model from - `"Wide Residual Networks" `_. - - The model is the same as ResNet except for the bottleneck number of channels - which is twice larger in every block. The number of channels in outer 1x1 - convolutions is the same, e.g. last block in ResNet-50 has 2048-512-2048 - channels, and in Wide ResNet-50-2 has 2048-1024-2048. - - Args: - pretrained (bool): If True, returns a model pre-trained on ImageNet - progress (bool): If True, displays a progress bar of the download to stderr - """ - kwargs['width_per_group'] = 64 * 2 - return _resnet('wide_resnet101_2', Bottleneck, [3, 4, 23, 3], - pretrained, progress, **kwargs) - - -func_dict = { - 'resnet18': (resnet18, 512), - 'resnet50': (resnet50, 2048) -} diff --git a/src/face3d/models/template_model.py b/src/face3d/models/template_model.py deleted file mode 100644 index dac7b33..0000000 --- a/src/face3d/models/template_model.py +++ /dev/null @@ -1,100 +0,0 @@ -"""Model class template - -This module provides a template for users to implement custom models. -You can specify '--model template' to use this model. -The class name should be consistent with both the filename and its model option. -The filename should be _dataset.py -The class name should be Dataset.py -It implements a simple image-to-image translation baseline based on regression loss. -Given input-output pairs (data_A, data_B), it learns a network netG that can minimize the following L1 loss: - min_ ||netG(data_A) - data_B||_1 -You need to implement the following functions: - : Add model-specific options and rewrite default values for existing options. - <__init__>: Initialize this model class. - : Unpack input data and perform data pre-processing. - : Run forward pass. This will be called by both and . - : Update network weights; it will be called in every training iteration. -""" -import numpy as np -import torch -from .base_model import BaseModel -from . import networks - - -class TemplateModel(BaseModel): - @staticmethod - def modify_commandline_options(parser, is_train=True): - """Add new model-specific options and rewrite default values for existing options. - - Parameters: - parser -- the option parser - is_train -- if it is training phase or test phase. You can use this flag to add training-specific or test-specific options. - - Returns: - the modified parser. - """ - parser.set_defaults(dataset_mode='aligned') # You can rewrite default values for this model. For example, this model usually uses aligned dataset as its dataset. - if is_train: - parser.add_argument('--lambda_regression', type=float, default=1.0, help='weight for the regression loss') # You can define new arguments for this model. - - return parser - - def __init__(self, opt): - """Initialize this model class. - - Parameters: - opt -- training/test options - - A few things can be done here. - - (required) call the initialization function of BaseModel - - define loss function, visualization images, model names, and optimizers - """ - BaseModel.__init__(self, opt) # call the initialization method of BaseModel - # specify the training losses you want to print out. The program will call base_model.get_current_losses to plot the losses to the console and save them to the disk. - self.loss_names = ['loss_G'] - # specify the images you want to save and display. The program will call base_model.get_current_visuals to save and display these images. - self.visual_names = ['data_A', 'data_B', 'output'] - # specify the models you want to save to the disk. The program will call base_model.save_networks and base_model.load_networks to save and load networks. - # you can use opt.isTrain to specify different behaviors for training and test. For example, some networks will not be used during test, and you don't need to load them. - self.model_names = ['G'] - # define networks; you can use opt.isTrain to specify different behaviors for training and test. - self.netG = networks.define_G(opt.input_nc, opt.output_nc, opt.ngf, opt.netG, gpu_ids=self.gpu_ids) - if self.isTrain: # only defined during training time - # define your loss functions. You can use losses provided by torch.nn such as torch.nn.L1Loss. - # We also provide a GANLoss class "networks.GANLoss". self.criterionGAN = networks.GANLoss().to(self.device) - self.criterionLoss = torch.nn.L1Loss() - # define and initialize optimizers. You can define one optimizer for each network. - # If two networks are updated at the same time, you can use itertools.chain to group them. See cycle_gan_model.py for an example. - self.optimizer = torch.optim.Adam(self.netG.parameters(), lr=opt.lr, betas=(opt.beta1, 0.999)) - self.optimizers = [self.optimizer] - - # Our program will automatically call to define schedulers, load networks, and print networks - - def set_input(self, input): - """Unpack input data from the dataloader and perform necessary pre-processing steps. - - Parameters: - input: a dictionary that contains the data itself and its metadata information. - """ - AtoB = self.opt.direction == 'AtoB' # use to swap data_A and data_B - self.data_A = input['A' if AtoB else 'B'].to(self.device) # get image data A - self.data_B = input['B' if AtoB else 'A'].to(self.device) # get image data B - self.image_paths = input['A_paths' if AtoB else 'B_paths'] # get image paths - - def forward(self): - """Run forward pass. This will be called by both functions and .""" - self.output = self.netG(self.data_A) # generate output image given the input data_A - - def backward(self): - """Calculate losses, gradients, and update network weights; called in every training iteration""" - # caculate the intermediate results if necessary; here self.output has been computed during function - # calculate loss given the input and intermediate results - self.loss_G = self.criterionLoss(self.output, self.data_B) * self.opt.lambda_regression - self.loss_G.backward() # calculate gradients of network G w.r.t. loss_G - - def optimize_parameters(self): - """Update network weights; it will be called in every training iteration.""" - self.forward() # first call forward to calculate intermediate results - self.optimizer.zero_grad() # clear network G's existing gradients - self.backward() # calculate gradients for network G - self.optimizer.step() # update gradients for network G diff --git a/src/face3d/options/__init__.py b/src/face3d/options/__init__.py deleted file mode 100644 index e7eedeb..0000000 --- a/src/face3d/options/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""This package options includes option modules: training options, test options, and basic options (used in both training and test).""" diff --git a/src/face3d/options/base_options.py b/src/face3d/options/base_options.py deleted file mode 100644 index d8f921d..0000000 --- a/src/face3d/options/base_options.py +++ /dev/null @@ -1,169 +0,0 @@ -"""This script contains base options for Deep3DFaceRecon_pytorch -""" - -import argparse -import os -from util import util -import numpy as np -import torch -import face3d.models as models -import face3d.data as data - - -class BaseOptions(): - """This class defines options used during both training and test time. - - It also implements several helper functions such as parsing, printing, and saving the options. - It also gathers additional options defined in functions in both dataset class and model class. - """ - - def __init__(self, cmd_line=None): - """Reset the class; indicates the class hasn't been initailized""" - self.initialized = False - self.cmd_line = None - if cmd_line is not None: - self.cmd_line = cmd_line.split() - - def initialize(self, parser): - """Define the common options that are used in both training and test.""" - # basic parameters - parser.add_argument('--name', type=str, default='face_recon', help='name of the experiment. It decides where to store samples and models') - parser.add_argument('--gpu_ids', type=str, default='0', help='gpu ids: e.g. 0 0,1,2, 0,2. use -1 for CPU') - parser.add_argument('--checkpoints_dir', type=str, default='./checkpoints', help='models are saved here') - parser.add_argument('--vis_batch_nums', type=float, default=1, help='batch nums of images for visulization') - parser.add_argument('--eval_batch_nums', type=float, default=float('inf'), help='batch nums of images for evaluation') - parser.add_argument('--use_ddp', type=util.str2bool, nargs='?', const=True, default=True, help='whether use distributed data parallel') - parser.add_argument('--ddp_port', type=str, default='12355', help='ddp port') - parser.add_argument('--display_per_batch', type=util.str2bool, nargs='?', const=True, default=True, help='whether use batch to show losses') - parser.add_argument('--add_image', type=util.str2bool, nargs='?', const=True, default=True, help='whether add image to tensorboard') - parser.add_argument('--world_size', type=int, default=1, help='batch nums of images for evaluation') - - # model parameters - parser.add_argument('--model', type=str, default='facerecon', help='chooses which model to use.') - - # additional parameters - parser.add_argument('--epoch', type=str, default='latest', help='which epoch to load? set to latest to use latest cached model') - parser.add_argument('--verbose', action='store_true', help='if specified, print more debugging information') - parser.add_argument('--suffix', default='', type=str, help='customized suffix: opt.name = opt.name + suffix: e.g., {model}_{netG}_size{load_size}') - - self.initialized = True - return parser - - def gather_options(self): - """Initialize our parser with basic options(only once). - Add additional model-specific and dataset-specific options. - These options are defined in the function - in model and dataset classes. - """ - if not self.initialized: # check if it has been initialized - parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) - parser = self.initialize(parser) - - # get the basic options - if self.cmd_line is None: - opt, _ = parser.parse_known_args() - else: - opt, _ = parser.parse_known_args(self.cmd_line) - - # set cuda visible devices - os.environ['CUDA_VISIBLE_DEVICES'] = opt.gpu_ids - - # modify model-related parser options - model_name = opt.model - model_option_setter = models.get_option_setter(model_name) - parser = model_option_setter(parser, self.isTrain) - if self.cmd_line is None: - opt, _ = parser.parse_known_args() # parse again with new defaults - else: - opt, _ = parser.parse_known_args(self.cmd_line) # parse again with new defaults - - # modify dataset-related parser options - if opt.dataset_mode: - dataset_name = opt.dataset_mode - dataset_option_setter = data.get_option_setter(dataset_name) - parser = dataset_option_setter(parser, self.isTrain) - - # save and return the parser - self.parser = parser - if self.cmd_line is None: - return parser.parse_args() - else: - return parser.parse_args(self.cmd_line) - - def print_options(self, opt): - """Print and save options - - It will print both current options and default values(if different). - It will save options into a text file / [checkpoints_dir] / opt.txt - """ - message = '' - message += '----------------- Options ---------------\n' - for k, v in sorted(vars(opt).items()): - comment = '' - default = self.parser.get_default(k) - if v != default: - comment = '\t[default: %s]' % str(default) - message += '{:>25}: {:<30}{}\n'.format(str(k), str(v), comment) - message += '----------------- End -------------------' - print(message) - - # save to the disk - expr_dir = os.path.join(opt.checkpoints_dir, opt.name) - util.mkdirs(expr_dir) - file_name = os.path.join(expr_dir, '{}_opt.txt'.format(opt.phase)) - try: - with open(file_name, 'wt') as opt_file: - opt_file.write(message) - opt_file.write('\n') - except PermissionError as error: - print("permission error {}".format(error)) - pass - - def parse(self): - """Parse our options, create checkpoints directory suffix, and set up gpu device.""" - opt = self.gather_options() - opt.isTrain = self.isTrain # train or test - - # process opt.suffix - if opt.suffix: - suffix = ('_' + opt.suffix.format(**vars(opt))) if opt.suffix != '' else '' - opt.name = opt.name + suffix - - - # set gpu ids - str_ids = opt.gpu_ids.split(',') - gpu_ids = [] - for str_id in str_ids: - id = int(str_id) - if id >= 0: - gpu_ids.append(id) - opt.world_size = len(gpu_ids) - # if len(opt.gpu_ids) > 0: - # torch.cuda.set_device(gpu_ids[0]) - if opt.world_size == 1: - opt.use_ddp = False - - if opt.phase != 'test': - # set continue_train automatically - if opt.pretrained_name is None: - model_dir = os.path.join(opt.checkpoints_dir, opt.name) - else: - model_dir = os.path.join(opt.checkpoints_dir, opt.pretrained_name) - if os.path.isdir(model_dir): - model_pths = [i for i in os.listdir(model_dir) if i.endswith('pth')] - if os.path.isdir(model_dir) and len(model_pths) != 0: - opt.continue_train= True - - # update the latest epoch count - if opt.continue_train: - if opt.epoch == 'latest': - epoch_counts = [int(i.split('.')[0].split('_')[-1]) for i in model_pths if 'latest' not in i] - if len(epoch_counts) != 0: - opt.epoch_count = max(epoch_counts) + 1 - else: - opt.epoch_count = int(opt.epoch) + 1 - - - self.print_options(opt) - self.opt = opt - return self.opt diff --git a/src/face3d/options/inference_options.py b/src/face3d/options/inference_options.py deleted file mode 100644 index c453965..0000000 --- a/src/face3d/options/inference_options.py +++ /dev/null @@ -1,23 +0,0 @@ -from face3d.options.base_options import BaseOptions - - -class InferenceOptions(BaseOptions): - """This class includes test options. - - It also includes shared options defined in BaseOptions. - """ - - def initialize(self, parser): - parser = BaseOptions.initialize(self, parser) # define shared options - parser.add_argument('--phase', type=str, default='test', help='train, val, test, etc') - parser.add_argument('--dataset_mode', type=str, default=None, help='chooses how datasets are loaded. [None | flist]') - - parser.add_argument('--input_dir', type=str, help='the folder of the input files') - parser.add_argument('--keypoint_dir', type=str, help='the folder of the keypoint files') - parser.add_argument('--output_dir', type=str, default='mp4', help='the output dir to save the extracted coefficients') - parser.add_argument('--save_split_files', action='store_true', help='save split files or not') - parser.add_argument('--inference_batch_size', type=int, default=8) - - # Dropout and Batchnorm has different behavior during training and test. - self.isTrain = False - return parser diff --git a/src/face3d/options/test_options.py b/src/face3d/options/test_options.py deleted file mode 100644 index 4ff3ad1..0000000 --- a/src/face3d/options/test_options.py +++ /dev/null @@ -1,21 +0,0 @@ -"""This script contains the test options for Deep3DFaceRecon_pytorch -""" - -from .base_options import BaseOptions - - -class TestOptions(BaseOptions): - """This class includes test options. - - It also includes shared options defined in BaseOptions. - """ - - def initialize(self, parser): - parser = BaseOptions.initialize(self, parser) # define shared options - parser.add_argument('--phase', type=str, default='test', help='train, val, test, etc') - parser.add_argument('--dataset_mode', type=str, default=None, help='chooses how datasets are loaded. [None | flist]') - parser.add_argument('--img_folder', type=str, default='examples', help='folder for test images.') - - # Dropout and Batchnorm has different behavior during training and test. - self.isTrain = False - return parser diff --git a/src/face3d/options/train_options.py b/src/face3d/options/train_options.py deleted file mode 100644 index 1337bfd..0000000 --- a/src/face3d/options/train_options.py +++ /dev/null @@ -1,53 +0,0 @@ -"""This script contains the training options for Deep3DFaceRecon_pytorch -""" - -from .base_options import BaseOptions -from util import util - -class TrainOptions(BaseOptions): - """This class includes training options. - - It also includes shared options defined in BaseOptions. - """ - - def initialize(self, parser): - parser = BaseOptions.initialize(self, parser) - # dataset parameters - # for train - parser.add_argument('--data_root', type=str, default='./', help='dataset root') - parser.add_argument('--flist', type=str, default='datalist/train/masks.txt', help='list of mask names of training set') - parser.add_argument('--batch_size', type=int, default=32) - parser.add_argument('--dataset_mode', type=str, default='flist', help='chooses how datasets are loaded. [None | flist]') - parser.add_argument('--serial_batches', action='store_true', help='if true, takes images in order to make batches, otherwise takes them randomly') - parser.add_argument('--num_threads', default=4, type=int, help='# threads for loading data') - parser.add_argument('--max_dataset_size', type=int, default=float("inf"), help='Maximum number of samples allowed per dataset. If the dataset directory contains more than max_dataset_size, only a subset is loaded.') - parser.add_argument('--preprocess', type=str, default='shift_scale_rot_flip', help='scaling and cropping of images at load time [shift_scale_rot_flip | shift_scale | shift | shift_rot_flip ]') - parser.add_argument('--use_aug', type=util.str2bool, nargs='?', const=True, default=True, help='whether use data augmentation') - - # for val - parser.add_argument('--flist_val', type=str, default='datalist/val/masks.txt', help='list of mask names of val set') - parser.add_argument('--batch_size_val', type=int, default=32) - - - # visualization parameters - parser.add_argument('--display_freq', type=int, default=1000, help='frequency of showing training results on screen') - parser.add_argument('--print_freq', type=int, default=100, help='frequency of showing training results on console') - - # network saving and loading parameters - parser.add_argument('--save_latest_freq', type=int, default=5000, help='frequency of saving the latest results') - parser.add_argument('--save_epoch_freq', type=int, default=1, help='frequency of saving checkpoints at the end of epochs') - parser.add_argument('--evaluation_freq', type=int, default=5000, help='evaluation freq') - parser.add_argument('--save_by_iter', action='store_true', help='whether saves model by iteration') - parser.add_argument('--continue_train', action='store_true', help='continue training: load the latest model') - parser.add_argument('--epoch_count', type=int, default=1, help='the starting epoch count, we save the model by , +, ...') - parser.add_argument('--phase', type=str, default='train', help='train, val, test, etc') - parser.add_argument('--pretrained_name', type=str, default=None, help='resume training from another checkpoint') - - # training parameters - parser.add_argument('--n_epochs', type=int, default=20, help='number of epochs with the initial learning rate') - parser.add_argument('--lr', type=float, default=0.0001, help='initial learning rate for adam') - parser.add_argument('--lr_policy', type=str, default='step', help='learning rate policy. [linear | step | plateau | cosine]') - parser.add_argument('--lr_decay_epochs', type=int, default=10, help='multiply by a gamma every lr_decay_epochs epoches') - - self.isTrain = True - return parser diff --git a/src/face3d/util/BBRegressorParam_r.mat b/src/face3d/util/BBRegressorParam_r.mat deleted file mode 100644 index 1430a94ed2ab570a09f9d980d3585e8aaa933084..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22019 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 Jc;w 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 diff --git a/src/face3d/util/__init__.py b/src/face3d/util/__init__.py deleted file mode 100644 index 1611582..0000000 --- a/src/face3d/util/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -"""This package includes a miscellaneous collection of useful helper functions.""" -from custom_nodes.ComfyUI_Lam.src.face3d.util import * - diff --git a/src/face3d/util/detect_lm68.py b/src/face3d/util/detect_lm68.py deleted file mode 100644 index b7e4099..0000000 --- a/src/face3d/util/detect_lm68.py +++ /dev/null @@ -1,106 +0,0 @@ -import os -import cv2 -import numpy as np -from scipy.io import loadmat -import tensorflow as tf -from util.preprocess import align_for_lm -from shutil import move - -mean_face = np.loadtxt('util/test_mean_face.txt') -mean_face = mean_face.reshape([68, 2]) - -def save_label(labels, save_path): - np.savetxt(save_path, labels) - -def draw_landmarks(img, landmark, save_name): - landmark = landmark - lm_img = np.zeros([img.shape[0], img.shape[1], 3]) - lm_img[:] = img.astype(np.float32) - landmark = np.round(landmark).astype(np.int32) - - for i in range(len(landmark)): - for j in range(-1, 1): - for k in range(-1, 1): - if img.shape[0] - 1 - landmark[i, 1]+j > 0 and \ - img.shape[0] - 1 - landmark[i, 1]+j < img.shape[0] and \ - landmark[i, 0]+k > 0 and \ - landmark[i, 0]+k < img.shape[1]: - lm_img[img.shape[0] - 1 - landmark[i, 1]+j, landmark[i, 0]+k, - :] = np.array([0, 0, 255]) - lm_img = lm_img.astype(np.uint8) - - cv2.imwrite(save_name, lm_img) - - -def load_data(img_name, txt_name): - return cv2.imread(img_name), np.loadtxt(txt_name) - -# create tensorflow graph for landmark detector -def load_lm_graph(graph_filename): - with tf.gfile.GFile(graph_filename, 'rb') as f: - graph_def = tf.GraphDef() - graph_def.ParseFromString(f.read()) - - with tf.Graph().as_default() as graph: - tf.import_graph_def(graph_def, name='net') - img_224 = graph.get_tensor_by_name('net/input_imgs:0') - output_lm = graph.get_tensor_by_name('net/lm:0') - lm_sess = tf.Session(graph=graph) - - return lm_sess,img_224,output_lm - -# landmark detection -def detect_68p(img_path,sess,input_op,output_op): - print('detecting landmarks......') - names = [i for i in sorted(os.listdir( - img_path)) if 'jpg' in i or 'png' in i or 'jpeg' in i or 'PNG' in i] - vis_path = os.path.join(img_path, 'vis') - remove_path = os.path.join(img_path, 'remove') - save_path = os.path.join(img_path, 'landmarks') - if not os.path.isdir(vis_path): - os.makedirs(vis_path) - if not os.path.isdir(remove_path): - os.makedirs(remove_path) - if not os.path.isdir(save_path): - os.makedirs(save_path) - - for i in range(0, len(names)): - name = names[i] - print('%05d' % (i), ' ', name) - full_image_name = os.path.join(img_path, name) - txt_name = '.'.join(name.split('.')[:-1]) + '.txt' - full_txt_name = os.path.join(img_path, 'detections', txt_name) # 5 facial landmark path for each image - - # if an image does not have detected 5 facial landmarks, remove it from the training list - if not os.path.isfile(full_txt_name): - move(full_image_name, os.path.join(remove_path, name)) - continue - - # load data - img, five_points = load_data(full_image_name, full_txt_name) - input_img, scale, bbox = align_for_lm(img, five_points) # align for 68 landmark detection - - # if the alignment fails, remove corresponding image from the training list - if scale == 0: - move(full_txt_name, os.path.join( - remove_path, txt_name)) - move(full_image_name, os.path.join(remove_path, name)) - continue - - # detect landmarks - input_img = np.reshape( - input_img, [1, 224, 224, 3]).astype(np.float32) - landmark = sess.run( - output_op, feed_dict={input_op: input_img}) - - # transform back to original image coordinate - landmark = landmark.reshape([68, 2]) + mean_face - landmark[:, 1] = 223 - landmark[:, 1] - landmark = landmark / scale - landmark[:, 0] = landmark[:, 0] + bbox[0] - landmark[:, 1] = landmark[:, 1] + bbox[1] - landmark[:, 1] = img.shape[0] - 1 - landmark[:, 1] - - if i % 100 == 0: - draw_landmarks(img, landmark, os.path.join(vis_path, name)) - save_label(landmark, os.path.join(save_path, txt_name)) diff --git a/src/face3d/util/generate_list.py b/src/face3d/util/generate_list.py deleted file mode 100644 index 943d906..0000000 --- a/src/face3d/util/generate_list.py +++ /dev/null @@ -1,34 +0,0 @@ -"""This script is to generate training list files for Deep3DFaceRecon_pytorch -""" - -import os - -# save path to training data -def write_list(lms_list, imgs_list, msks_list, mode='train',save_folder='datalist', save_name=''): - save_path = os.path.join(save_folder, mode) - if not os.path.isdir(save_path): - os.makedirs(save_path) - with open(os.path.join(save_path, save_name + 'landmarks.txt'), 'w') as fd: - fd.writelines([i + '\n' for i in lms_list]) - - with open(os.path.join(save_path, save_name + 'images.txt'), 'w') as fd: - fd.writelines([i + '\n' for i in imgs_list]) - - with open(os.path.join(save_path, save_name + 'masks.txt'), 'w') as fd: - fd.writelines([i + '\n' for i in msks_list]) - -# check if the path is valid -def check_list(rlms_list, rimgs_list, rmsks_list): - lms_list, imgs_list, msks_list = [], [], [] - for i in range(len(rlms_list)): - flag = 'false' - lm_path = rlms_list[i] - im_path = rimgs_list[i] - msk_path = rmsks_list[i] - if os.path.isfile(lm_path) and os.path.isfile(im_path) and os.path.isfile(msk_path): - flag = 'true' - lms_list.append(rlms_list[i]) - imgs_list.append(rimgs_list[i]) - msks_list.append(rmsks_list[i]) - print(i, rlms_list[i], flag) - return lms_list, imgs_list, msks_list diff --git a/src/face3d/util/html.py b/src/face3d/util/html.py deleted file mode 100644 index cc3262a..0000000 --- a/src/face3d/util/html.py +++ /dev/null @@ -1,86 +0,0 @@ -import dominate -from dominate.tags import meta, h3, table, tr, td, p, a, img, br -import os - - -class HTML: - """This HTML class allows us to save images and write texts into a single HTML file. - - It consists of functions such as (add a text header to the HTML file), - (add a row of images to the HTML file), and (save the HTML to the disk). - It is based on Python library 'dominate', a Python library for creating and manipulating HTML documents using a DOM API. - """ - - def __init__(self, web_dir, title, refresh=0): - """Initialize the HTML classes - - Parameters: - web_dir (str) -- a directory that stores the webpage. HTML file will be created at /index.html; images will be saved at 0: - with self.doc.head: - meta(http_equiv="refresh", content=str(refresh)) - - def get_image_dir(self): - """Return the directory that stores images""" - return self.img_dir - - def add_header(self, text): - """Insert a header to the HTML file - - Parameters: - text (str) -- the header text - """ - with self.doc: - h3(text) - - def add_images(self, ims, txts, links, width=400): - """add images to the HTML file - - Parameters: - ims (str list) -- a list of image paths - txts (str list) -- a list of image names shown on the website - links (str list) -- a list of hyperref links; when you click an image, it will redirect you to a new page - """ - self.t = table(border=1, style="table-layout: fixed;") # Insert a table - self.doc.add(self.t) - with self.t: - with tr(): - for im, txt, link in zip(ims, txts, links): - with td(style="word-wrap: break-word;", halign="center", valign="top"): - with p(): - with a(href=os.path.join('images', link)): - img(style="width:%dpx" % width, src=os.path.join('images', im)) - br() - p(txt) - - def save(self): - """save the current content to the HMTL file""" - html_file = '%s/index.html' % self.web_dir - f = open(html_file, 'wt') - f.write(self.doc.render()) - f.close() - - -if __name__ == '__main__': # we show an example usage here. - html = HTML('web/', 'test_html') - html.add_header('hello world') - - ims, txts, links = [], [], [] - for n in range(4): - ims.append('image_%d.png' % n) - txts.append('text_%d' % n) - links.append('image_%d.png' % n) - html.add_images(ims, txts, links) - html.save() diff --git a/src/face3d/util/load_mats.py b/src/face3d/util/load_mats.py deleted file mode 100644 index f9a6fcc..0000000 --- a/src/face3d/util/load_mats.py +++ /dev/null @@ -1,120 +0,0 @@ -"""This script is to load 3D face model for Deep3DFaceRecon_pytorch -""" - -import numpy as np -from PIL import Image -from scipy.io import loadmat, savemat -from array import array -import os.path as osp - -# load expression basis -def LoadExpBasis(bfm_folder='BFM'): - n_vertex = 53215 - Expbin = open(osp.join(bfm_folder, 'Exp_Pca.bin'), 'rb') - exp_dim = array('i') - exp_dim.fromfile(Expbin, 1) - expMU = array('f') - expPC = array('f') - expMU.fromfile(Expbin, 3*n_vertex) - expPC.fromfile(Expbin, 3*exp_dim[0]*n_vertex) - Expbin.close() - - expPC = np.array(expPC) - expPC = np.reshape(expPC, [exp_dim[0], -1]) - expPC = np.transpose(expPC) - - expEV = np.loadtxt(osp.join(bfm_folder, 'std_exp.txt')) - - return expPC, expEV - - -# transfer original BFM09 to our face model -def transferBFM09(bfm_folder='BFM'): - print('Transfer BFM09 to BFM_model_front......') - original_BFM = loadmat(osp.join(bfm_folder, '01_MorphableModel.mat')) - shapePC = original_BFM['shapePC'] # shape basis - shapeEV = original_BFM['shapeEV'] # corresponding eigen value - shapeMU = original_BFM['shapeMU'] # mean face - texPC = original_BFM['texPC'] # texture basis - texEV = original_BFM['texEV'] # eigen value - texMU = original_BFM['texMU'] # mean texture - - expPC, expEV = LoadExpBasis(bfm_folder) - - # transfer BFM09 to our face model - - idBase = shapePC*np.reshape(shapeEV, [-1, 199]) - idBase = idBase/1e5 # unify the scale to decimeter - idBase = idBase[:, :80] # use only first 80 basis - - exBase = expPC*np.reshape(expEV, [-1, 79]) - exBase = exBase/1e5 # unify the scale to decimeter - exBase = exBase[:, :64] # use only first 64 basis - - texBase = texPC*np.reshape(texEV, [-1, 199]) - texBase = texBase[:, :80] # use only first 80 basis - - # our face model is cropped along face landmarks and contains only 35709 vertex. - # original BFM09 contains 53490 vertex, and expression basis provided by Guo et al. contains 53215 vertex. - # thus we select corresponding vertex to get our face model. - - index_exp = loadmat(osp.join(bfm_folder, 'BFM_front_idx.mat')) - index_exp = index_exp['idx'].astype(np.int32) - 1 # starts from 0 (to 53215) - - index_shape = loadmat(osp.join(bfm_folder, 'BFM_exp_idx.mat')) - index_shape = index_shape['trimIndex'].astype( - np.int32) - 1 # starts from 0 (to 53490) - index_shape = index_shape[index_exp] - - idBase = np.reshape(idBase, [-1, 3, 80]) - idBase = idBase[index_shape, :, :] - idBase = np.reshape(idBase, [-1, 80]) - - texBase = np.reshape(texBase, [-1, 3, 80]) - texBase = texBase[index_shape, :, :] - texBase = np.reshape(texBase, [-1, 80]) - - exBase = np.reshape(exBase, [-1, 3, 64]) - exBase = exBase[index_exp, :, :] - exBase = np.reshape(exBase, [-1, 64]) - - meanshape = np.reshape(shapeMU, [-1, 3])/1e5 - meanshape = meanshape[index_shape, :] - meanshape = np.reshape(meanshape, [1, -1]) - - meantex = np.reshape(texMU, [-1, 3]) - meantex = meantex[index_shape, :] - meantex = np.reshape(meantex, [1, -1]) - - # other info contains triangles, region used for computing photometric loss, - # region used for skin texture regularization, and 68 landmarks index etc. - other_info = loadmat(osp.join(bfm_folder, 'facemodel_info.mat')) - frontmask2_idx = other_info['frontmask2_idx'] - skinmask = other_info['skinmask'] - keypoints = other_info['keypoints'] - point_buf = other_info['point_buf'] - tri = other_info['tri'] - tri_mask2 = other_info['tri_mask2'] - - # save our face model - savemat(osp.join(bfm_folder, 'BFM_model_front.mat'), {'meanshape': meanshape, 'meantex': meantex, 'idBase': idBase, 'exBase': exBase, 'texBase': texBase, - 'tri': tri, 'point_buf': point_buf, 'tri_mask2': tri_mask2, 'keypoints': keypoints, 'frontmask2_idx': frontmask2_idx, 'skinmask': skinmask}) - - -# load landmarks for standard face, which is used for image preprocessing -def load_lm3d(bfm_folder): - - Lm3D = loadmat(osp.join(bfm_folder, 'similarity_Lm3D_all.mat')) - Lm3D = Lm3D['lm'] - - # calculate 5 facial landmarks using 68 landmarks - lm_idx = np.array([31, 37, 40, 43, 46, 49, 55]) - 1 - Lm3D = np.stack([Lm3D[lm_idx[0], :], np.mean(Lm3D[lm_idx[[1, 2]], :], 0), np.mean( - Lm3D[lm_idx[[3, 4]], :], 0), Lm3D[lm_idx[5], :], Lm3D[lm_idx[6], :]], axis=0) - Lm3D = Lm3D[[1, 2, 0, 3, 4], :] - - return Lm3D - - -if __name__ == '__main__': - transferBFM09() \ No newline at end of file diff --git a/src/face3d/util/nvdiffrast.py b/src/face3d/util/nvdiffrast.py deleted file mode 100644 index f324585..0000000 --- a/src/face3d/util/nvdiffrast.py +++ /dev/null @@ -1,126 +0,0 @@ -"""This script is the differentiable renderer for Deep3DFaceRecon_pytorch - Attention, antialiasing step is missing in current version. -""" -import pytorch3d.ops -import torch -import torch.nn.functional as F -import kornia -from kornia.geometry.camera import pixel2cam -import numpy as np -from typing import List -from scipy.io import loadmat -from torch import nn - -from pytorch3d.structures import Meshes -from pytorch3d.renderer import ( - look_at_view_transform, - FoVPerspectiveCameras, - DirectionalLights, - RasterizationSettings, - MeshRenderer, - MeshRasterizer, - SoftPhongShader, - TexturesUV, -) - -# def ndc_projection(x=0.1, n=1.0, f=50.0): -# return np.array([[n/x, 0, 0, 0], -# [ 0, n/-x, 0, 0], -# [ 0, 0, -(f+n)/(f-n), -(2*f*n)/(f-n)], -# [ 0, 0, -1, 0]]).astype(np.float32) - -class MeshRenderer(nn.Module): - def __init__(self, - rasterize_fov, - znear=0.1, - zfar=10, - rasterize_size=224): - super(MeshRenderer, self).__init__() - - # x = np.tan(np.deg2rad(rasterize_fov * 0.5)) * znear - # self.ndc_proj = torch.tensor(ndc_projection(x=x, n=znear, f=zfar)).matmul( - # torch.diag(torch.tensor([1., -1, -1, 1]))) - self.rasterize_size = rasterize_size - self.fov = rasterize_fov - self.znear = znear - self.zfar = zfar - - self.rasterizer = None - - def forward(self, vertex, tri, feat=None): - """ - Return: - mask -- torch.tensor, size (B, 1, H, W) - depth -- torch.tensor, size (B, 1, H, W) - features(optional) -- torch.tensor, size (B, C, H, W) if feat is not None - - Parameters: - vertex -- torch.tensor, size (B, N, 3) - tri -- torch.tensor, size (B, M, 3) or (M, 3), triangles - feat(optional) -- torch.tensor, size (B, N ,C), features - """ - device = vertex.device - rsize = int(self.rasterize_size) - # ndc_proj = self.ndc_proj.to(device) - # trans to homogeneous coordinates of 3d vertices, the direction of y is the same as v - if vertex.shape[-1] == 3: - vertex = torch.cat([vertex, torch.ones([*vertex.shape[:2], 1]).to(device)], dim=-1) - vertex[..., 0] = -vertex[..., 0] - - - # vertex_ndc = vertex @ ndc_proj.t() - if self.rasterizer is None: - self.rasterizer = MeshRasterizer() - print("create rasterizer on device cuda:%d"%device.index) - - # ranges = None - # if isinstance(tri, List) or len(tri.shape) == 3: - # vum = vertex_ndc.shape[1] - # fnum = torch.tensor([f.shape[0] for f in tri]).unsqueeze(1).to(device) - # fstartidx = torch.cumsum(fnum, dim=0) - fnum - # ranges = torch.cat([fstartidx, fnum], axis=1).type(torch.int32).cpu() - # for i in range(tri.shape[0]): - # tri[i] = tri[i] + i*vum - # vertex_ndc = torch.cat(vertex_ndc, dim=0) - # tri = torch.cat(tri, dim=0) - - # for range_mode vetex: [B*N, 4], tri: [B*M, 3], for instance_mode vetex: [B, N, 4], tri: [M, 3] - tri = tri.type(torch.int32).contiguous() - - # rasterize - cameras = FoVPerspectiveCameras( - device=device, - fov=self.fov, - znear=self.znear, - zfar=self.zfar, - ) - - raster_settings = RasterizationSettings( - image_size=rsize - ) - - # print(vertex.shape, tri.shape) - mesh = Meshes(vertex.contiguous()[...,:3], tri.unsqueeze(0).repeat((vertex.shape[0],1,1))) - - fragments = self.rasterizer(mesh, cameras = cameras, raster_settings = raster_settings) - rast_out = fragments.pix_to_face.squeeze(-1) - depth = fragments.zbuf - - # render depth - depth = depth.permute(0, 3, 1, 2) - mask = (rast_out > 0).float().unsqueeze(1) - depth = mask * depth - - - image = None - if feat is not None: - attributes = feat.reshape(-1,3)[mesh.faces_packed()] - image = pytorch3d.ops.interpolate_face_attributes(fragments.pix_to_face, - fragments.bary_coords, - attributes) - # print(image.shape) - image = image.squeeze(-2).permute(0, 3, 1, 2) - image = mask * image - - return mask, depth, image - diff --git a/src/face3d/util/preprocess.py b/src/face3d/util/preprocess.py deleted file mode 100644 index b77a3a4..0000000 --- a/src/face3d/util/preprocess.py +++ /dev/null @@ -1,103 +0,0 @@ -"""This script contains the image preprocessing code for Deep3DFaceRecon_pytorch -""" - -import numpy as np -from scipy.io import loadmat -from PIL import Image -import cv2 -import os -from skimage import transform as trans -import torch -import warnings -warnings.filterwarnings("ignore", category=np.VisibleDeprecationWarning) -warnings.filterwarnings("ignore", category=FutureWarning) - - -# calculating least square problem for image alignment -def POS(xp, x): - npts = xp.shape[1] - - A = np.zeros([2*npts, 8]) - - A[0:2*npts-1:2, 0:3] = x.transpose() - A[0:2*npts-1:2, 3] = 1 - - A[1:2*npts:2, 4:7] = x.transpose() - A[1:2*npts:2, 7] = 1 - - b = np.reshape(xp.transpose(), [2*npts, 1]) - - k, _, _, _ = np.linalg.lstsq(A, b) - - R1 = k[0:3] - R2 = k[4:7] - sTx = k[3] - sTy = k[7] - s = (np.linalg.norm(R1) + np.linalg.norm(R2))/2 - t = np.stack([sTx, sTy], axis=0) - - return t, s - -# resize and crop images for face reconstruction -def resize_n_crop_img(img, lm, t, s, target_size=224., mask=None): - w0, h0 = img.size - w = (w0*s).astype(np.int32) - h = (h0*s).astype(np.int32) - left = (w/2 - target_size/2 + float((t[0] - w0/2)*s)).astype(np.int32) - right = left + target_size - up = (h/2 - target_size/2 + float((h0/2 - t[1])*s)).astype(np.int32) - below = up + target_size - - img = img.resize((w, h), resample=Image.BICUBIC) - img = img.crop((left, up, right, below)) - - if mask is not None: - mask = mask.resize((w, h), resample=Image.BICUBIC) - mask = mask.crop((left, up, right, below)) - - lm = np.stack([lm[:, 0] - t[0] + w0/2, lm[:, 1] - - t[1] + h0/2], axis=1)*s - lm = lm - np.reshape( - np.array([(w/2 - target_size/2), (h/2-target_size/2)]), [1, 2]) - - return img, lm, mask - -# utils for face reconstruction -def extract_5p(lm): - lm_idx = np.array([31, 37, 40, 43, 46, 49, 55]) - 1 - lm5p = np.stack([lm[lm_idx[0], :], np.mean(lm[lm_idx[[1, 2]], :], 0), np.mean( - lm[lm_idx[[3, 4]], :], 0), lm[lm_idx[5], :], lm[lm_idx[6], :]], axis=0) - lm5p = lm5p[[1, 2, 0, 3, 4], :] - return lm5p - -# utils for face reconstruction -def align_img(img, lm, lm3D, mask=None, target_size=224., rescale_factor=102.): - """ - Return: - transparams --numpy.array (raw_W, raw_H, scale, tx, ty) - img_new --PIL.Image (target_size, target_size, 3) - lm_new --numpy.array (68, 2), y direction is opposite to v direction - mask_new --PIL.Image (target_size, target_size) - - Parameters: - img --PIL.Image (raw_H, raw_W, 3) - lm --numpy.array (68, 2), y direction is opposite to v direction - lm3D --numpy.array (5, 3) - mask --PIL.Image (raw_H, raw_W, 3) - """ - - w0, h0 = img.size - if lm.shape[0] != 5: - lm5p = extract_5p(lm) - else: - lm5p = lm - - # calculate translation and scale factors using 5 facial landmarks and standard landmarks of a 3D face - t, s = POS(lm5p.transpose(), lm3D.transpose()) - s = rescale_factor/s - - # processing the image - img_new, lm_new, mask_new = resize_n_crop_img(img, lm, t, s, target_size=target_size, mask=mask) - trans_params = np.array([w0, h0, s, t[0], t[1]]) - - return trans_params, img_new, lm_new, mask_new diff --git a/src/face3d/util/skin_mask.py b/src/face3d/util/skin_mask.py deleted file mode 100644 index a8a74e4..0000000 --- a/src/face3d/util/skin_mask.py +++ /dev/null @@ -1,125 +0,0 @@ -"""This script is to generate skin attention mask for Deep3DFaceRecon_pytorch -""" - -import math -import numpy as np -import os -import cv2 - -class GMM: - def __init__(self, dim, num, w, mu, cov, cov_det, cov_inv): - self.dim = dim # feature dimension - self.num = num # number of Gaussian components - self.w = w # weights of Gaussian components (a list of scalars) - self.mu= mu # mean of Gaussian components (a list of 1xdim vectors) - self.cov = cov # covariance matrix of Gaussian components (a list of dimxdim matrices) - self.cov_det = cov_det # pre-computed determinet of covariance matrices (a list of scalars) - self.cov_inv = cov_inv # pre-computed inverse covariance matrices (a list of dimxdim matrices) - - self.factor = [0]*num - for i in range(self.num): - self.factor[i] = (2*math.pi)**(self.dim/2) * self.cov_det[i]**0.5 - - def likelihood(self, data): - assert(data.shape[1] == self.dim) - N = data.shape[0] - lh = np.zeros(N) - - for i in range(self.num): - data_ = data - self.mu[i] - - tmp = np.matmul(data_,self.cov_inv[i]) * data_ - tmp = np.sum(tmp,axis=1) - power = -0.5 * tmp - - p = np.array([math.exp(power[j]) for j in range(N)]) - p = p/self.factor[i] - lh += p*self.w[i] - - return lh - - -def _rgb2ycbcr(rgb): - m = np.array([[65.481, 128.553, 24.966], - [-37.797, -74.203, 112], - [112, -93.786, -18.214]]) - shape = rgb.shape - rgb = rgb.reshape((shape[0] * shape[1], 3)) - ycbcr = np.dot(rgb, m.transpose() / 255.) - ycbcr[:, 0] += 16. - ycbcr[:, 1:] += 128. - return ycbcr.reshape(shape) - - -def _bgr2ycbcr(bgr): - rgb = bgr[..., ::-1] - return _rgb2ycbcr(rgb) - - -gmm_skin_w = [0.24063933, 0.16365987, 0.26034665, 0.33535415] -gmm_skin_mu = [np.array([113.71862, 103.39613, 164.08226]), - np.array([150.19858, 105.18467, 155.51428]), - np.array([183.92976, 107.62468, 152.71820]), - np.array([114.90524, 113.59782, 151.38217])] -gmm_skin_cov_det = [5692842.5, 5851930.5, 2329131., 1585971.] -gmm_skin_cov_inv = [np.array([[0.0019472069, 0.0020450759, -0.00060243998],[0.0020450759, 0.017700525, 0.0051420014],[-0.00060243998, 0.0051420014, 0.0081308950]]), - np.array([[0.0027110141, 0.0011036990, 0.0023122299],[0.0011036990, 0.010707724, 0.010742856],[0.0023122299, 0.010742856, 0.017481629]]), - np.array([[0.0048026871, 0.00022935172, 0.0077668377],[0.00022935172, 0.011729696, 0.0081661865],[0.0077668377, 0.0081661865, 0.025374353]]), - np.array([[0.0011989699, 0.0022453172, -0.0010748957],[0.0022453172, 0.047758564, 0.020332102],[-0.0010748957, 0.020332102, 0.024502251]])] - -gmm_skin = GMM(3, 4, gmm_skin_w, gmm_skin_mu, [], gmm_skin_cov_det, gmm_skin_cov_inv) - -gmm_nonskin_w = [0.12791070, 0.31130761, 0.34245777, 0.21832393] -gmm_nonskin_mu = [np.array([99.200851, 112.07533, 140.20602]), - np.array([110.91392, 125.52969, 130.19237]), - np.array([129.75864, 129.96107, 126.96808]), - np.array([112.29587, 128.85121, 129.05431])] -gmm_nonskin_cov_det = [458703648., 6466488., 90611376., 133097.63] -gmm_nonskin_cov_inv = [np.array([[0.00085371657, 0.00071197288, 0.00023958916],[0.00071197288, 0.0025935620, 0.00076557708],[0.00023958916, 0.00076557708, 0.0015042332]]), - np.array([[0.00024650150, 0.00045542428, 0.00015019422],[0.00045542428, 0.026412144, 0.018419769],[0.00015019422, 0.018419769, 0.037497383]]), - np.array([[0.00037054974, 0.00038146760, 0.00040408765],[0.00038146760, 0.0085505722, 0.0079136286],[0.00040408765, 0.0079136286, 0.010982352]]), - np.array([[0.00013709733, 0.00051228428, 0.00012777430],[0.00051228428, 0.28237113, 0.10528370],[0.00012777430, 0.10528370, 0.23468947]])] - -gmm_nonskin = GMM(3, 4, gmm_nonskin_w, gmm_nonskin_mu, [], gmm_nonskin_cov_det, gmm_nonskin_cov_inv) - -prior_skin = 0.8 -prior_nonskin = 1 - prior_skin - - -# calculate skin attention mask -def skinmask(imbgr): - im = _bgr2ycbcr(imbgr) - - data = im.reshape((-1,3)) - - lh_skin = gmm_skin.likelihood(data) - lh_nonskin = gmm_nonskin.likelihood(data) - - tmp1 = prior_skin * lh_skin - tmp2 = prior_nonskin * lh_nonskin - post_skin = tmp1 / (tmp1+tmp2) # posterior probability - - post_skin = post_skin.reshape((im.shape[0],im.shape[1])) - - post_skin = np.round(post_skin*255) - post_skin = post_skin.astype(np.uint8) - post_skin = np.tile(np.expand_dims(post_skin,2),[1,1,3]) # reshape to H*W*3 - - return post_skin - - -def get_skin_mask(img_path): - print('generating skin masks......') - names = [i for i in sorted(os.listdir( - img_path)) if 'jpg' in i or 'png' in i or 'jpeg' in i or 'PNG' in i] - save_path = os.path.join(img_path, 'mask') - if not os.path.isdir(save_path): - os.makedirs(save_path) - - for i in range(0, len(names)): - name = names[i] - print('%05d' % (i), ' ', name) - full_image_name = os.path.join(img_path, name) - img = cv2.imread(full_image_name).astype(np.float32) - skin_img = skinmask(img) - cv2.imwrite(os.path.join(save_path, name), skin_img.astype(np.uint8)) diff --git a/src/face3d/util/test_mean_face.txt b/src/face3d/util/test_mean_face.txt deleted file mode 100644 index 3a46d4d..0000000 --- a/src/face3d/util/test_mean_face.txt +++ /dev/null @@ -1,136 +0,0 @@ --5.228591537475585938e+01 -2.078247070312500000e-01 --5.064269638061523438e+01 --1.315765380859375000e+01 --4.952939224243164062e+01 --2.592591094970703125e+01 --4.793047332763671875e+01 --3.832135772705078125e+01 --4.512159729003906250e+01 --5.059623336791992188e+01 --3.917720794677734375e+01 --6.043736648559570312e+01 --2.929953765869140625e+01 --6.861183166503906250e+01 --1.719801330566406250e+01 --7.572736358642578125e+01 --1.961936950683593750e+00 --7.862001037597656250e+01 -1.467941284179687500e+01 --7.607844543457031250e+01 -2.744073486328125000e+01 --6.915261840820312500e+01 -3.855677795410156250e+01 --5.950350570678710938e+01 -4.478240966796875000e+01 --4.867547225952148438e+01 -4.714337158203125000e+01 --3.800830078125000000e+01 -4.940315246582031250e+01 --2.496297454833984375e+01 -5.117234802246093750e+01 --1.241538238525390625e+01 -5.190507507324218750e+01 -8.244247436523437500e-01 --4.150688934326171875e+01 -2.386329650878906250e+01 --3.570307159423828125e+01 -3.017010498046875000e+01 --2.790358734130859375e+01 -3.212951660156250000e+01 --1.941773223876953125e+01 -3.156523132324218750e+01 --1.138106536865234375e+01 -2.841992187500000000e+01 -5.993263244628906250e+00 -2.895182800292968750e+01 -1.343590545654296875e+01 -3.189880371093750000e+01 -2.203153991699218750e+01 -3.302221679687500000e+01 -2.992478942871093750e+01 -3.099150085449218750e+01 -3.628388977050781250e+01 -2.765748596191406250e+01 --1.933914184570312500e+00 -1.405374145507812500e+01 --2.153038024902343750e+00 -5.772636413574218750e+00 --2.270050048828125000e+00 --2.121643066406250000e+00 --2.218330383300781250e+00 --1.068978118896484375e+01 --1.187252044677734375e+01 --1.997912597656250000e+01 --6.879402160644531250e+00 --2.143579864501953125e+01 --1.227821350097656250e+00 --2.193494415283203125e+01 -4.623237609863281250e+00 --2.152721405029296875e+01 -9.721397399902343750e+00 --1.953671264648437500e+01 --3.648714447021484375e+01 -9.811126708984375000e+00 --3.130242919921875000e+01 -1.422447967529296875e+01 --2.212834930419921875e+01 -1.493019866943359375e+01 --1.500880432128906250e+01 -1.073588562011718750e+01 --2.095037078857421875e+01 -9.054298400878906250e+00 --3.050099182128906250e+01 -8.704177856445312500e+00 -1.173237609863281250e+01 -1.054329681396484375e+01 -1.856353759765625000e+01 -1.535009765625000000e+01 -2.893331909179687500e+01 -1.451992797851562500e+01 -3.452944946289062500e+01 -1.065280151367187500e+01 -2.875990295410156250e+01 -8.654792785644531250e+00 -1.942100524902343750e+01 -9.422447204589843750e+00 --2.204488372802734375e+01 --3.983994293212890625e+01 --1.324458312988281250e+01 --3.467377471923828125e+01 --6.749649047851562500e+00 --3.092894744873046875e+01 --9.183349609375000000e-01 --3.196458435058593750e+01 -4.220649719238281250e+00 --3.090406036376953125e+01 -1.089889526367187500e+01 --3.497008514404296875e+01 -1.874589538574218750e+01 --4.065438079833984375e+01 -1.124106597900390625e+01 --4.438417816162109375e+01 -5.181709289550781250e+00 --4.649170684814453125e+01 --1.158607482910156250e+00 --4.680406951904296875e+01 --7.918922424316406250e+00 --4.671575164794921875e+01 --1.452505493164062500e+01 --4.416526031494140625e+01 --2.005007171630859375e+01 --3.997841644287109375e+01 --1.054919433593750000e+01 --3.849683380126953125e+01 --1.051826477050781250e+00 --3.794863128662109375e+01 -6.412681579589843750e+00 --3.804645538330078125e+01 -1.627674865722656250e+01 --4.039697265625000000e+01 -6.373878479003906250e+00 --4.087213897705078125e+01 --8.551712036132812500e-01 --4.157129669189453125e+01 --1.014953613281250000e+01 --4.128469085693359375e+01 diff --git a/src/face3d/util/util.py b/src/face3d/util/util.py deleted file mode 100644 index 0d689ca..0000000 --- a/src/face3d/util/util.py +++ /dev/null @@ -1,208 +0,0 @@ -"""This script contains basic utilities for Deep3DFaceRecon_pytorch -""" -from __future__ import print_function -import numpy as np -import torch -from PIL import Image -import os -import importlib -import argparse -from argparse import Namespace -import torchvision - - -def str2bool(v): - if isinstance(v, bool): - return v - if v.lower() in ('yes', 'true', 't', 'y', '1'): - return True - elif v.lower() in ('no', 'false', 'f', 'n', '0'): - return False - else: - raise argparse.ArgumentTypeError('Boolean value expected.') - - -def copyconf(default_opt, **kwargs): - conf = Namespace(**vars(default_opt)) - for key in kwargs: - setattr(conf, key, kwargs[key]) - return conf - -def genvalconf(train_opt, **kwargs): - conf = Namespace(**vars(train_opt)) - attr_dict = train_opt.__dict__ - for key, value in attr_dict.items(): - if 'val' in key and key.split('_')[0] in attr_dict: - setattr(conf, key.split('_')[0], value) - - for key in kwargs: - setattr(conf, key, kwargs[key]) - - return conf - -def find_class_in_module(target_cls_name, module): - target_cls_name = target_cls_name.replace('_', '').lower() - clslib = importlib.import_module(module) - cls = None - for name, clsobj in clslib.__dict__.items(): - if name.lower() == target_cls_name: - cls = clsobj - - assert cls is not None, "In %s, there should be a class whose name matches %s in lowercase without underscore(_)" % (module, target_cls_name) - - return cls - - -def tensor2im(input_image, imtype=np.uint8): - """"Converts a Tensor array into a numpy image array. - - Parameters: - input_image (tensor) -- the input image tensor array, range(0, 1) - imtype (type) -- the desired type of the converted numpy array - """ - if not isinstance(input_image, np.ndarray): - if isinstance(input_image, torch.Tensor): # get the data from a variable - image_tensor = input_image.data - else: - return input_image - image_numpy = image_tensor.clamp(0.0, 1.0).cpu().float().numpy() # convert it into a numpy array - if image_numpy.shape[0] == 1: # grayscale to RGB - image_numpy = np.tile(image_numpy, (3, 1, 1)) - image_numpy = np.transpose(image_numpy, (1, 2, 0)) * 255.0 # post-processing: tranpose and scaling - else: # if it is a numpy array, do nothing - image_numpy = input_image - return image_numpy.astype(imtype) - - -def diagnose_network(net, name='network'): - """Calculate and print the mean of average absolute(gradients) - - Parameters: - net (torch network) -- Torch network - name (str) -- the name of the network - """ - mean = 0.0 - count = 0 - for param in net.parameters(): - if param.grad is not None: - mean += torch.mean(torch.abs(param.grad.data)) - count += 1 - if count > 0: - mean = mean / count - print(name) - print(mean) - - -def save_image(image_numpy, image_path, aspect_ratio=1.0): - """Save a numpy image to the disk - - Parameters: - image_numpy (numpy array) -- input numpy array - image_path (str) -- the path of the image - """ - - image_pil = Image.fromarray(image_numpy) - h, w, _ = image_numpy.shape - - if aspect_ratio is None: - pass - elif aspect_ratio > 1.0: - image_pil = image_pil.resize((h, int(w * aspect_ratio)), Image.BICUBIC) - elif aspect_ratio < 1.0: - image_pil = image_pil.resize((int(h / aspect_ratio), w), Image.BICUBIC) - image_pil.save(image_path) - - -def print_numpy(x, val=True, shp=False): - """Print the mean, min, max, median, std, and size of a numpy array - - Parameters: - val (bool) -- if print the values of the numpy array - shp (bool) -- if print the shape of the numpy array - """ - x = x.astype(np.float64) - if shp: - print('shape,', x.shape) - if val: - x = x.flatten() - print('mean = %3.3f, min = %3.3f, max = %3.3f, median = %3.3f, std=%3.3f' % ( - np.mean(x), np.min(x), np.max(x), np.median(x), np.std(x))) - - -def mkdirs(paths): - """create empty directories if they don't exist - - Parameters: - paths (str list) -- a list of directory paths - """ - if isinstance(paths, list) and not isinstance(paths, str): - for path in paths: - mkdir(path) - else: - mkdir(paths) - - -def mkdir(path): - """create a single empty directory if it didn't exist - - Parameters: - path (str) -- a single directory path - """ - if not os.path.exists(path): - os.makedirs(path) - - -def correct_resize_label(t, size): - device = t.device - t = t.detach().cpu() - resized = [] - for i in range(t.size(0)): - one_t = t[i, :1] - one_np = np.transpose(one_t.numpy().astype(np.uint8), (1, 2, 0)) - one_np = one_np[:, :, 0] - one_image = Image.fromarray(one_np).resize(size, Image.NEAREST) - resized_t = torch.from_numpy(np.array(one_image)).long() - resized.append(resized_t) - return torch.stack(resized, dim=0).to(device) - - -def correct_resize(t, size, mode=Image.BICUBIC): - device = t.device - t = t.detach().cpu() - resized = [] - for i in range(t.size(0)): - one_t = t[i:i + 1] - one_image = Image.fromarray(tensor2im(one_t)).resize(size, Image.BICUBIC) - resized_t = torchvision.transforms.functional.to_tensor(one_image) * 2 - 1.0 - resized.append(resized_t) - return torch.stack(resized, dim=0).to(device) - -def draw_landmarks(img, landmark, color='r', step=2): - """ - Return: - img -- numpy.array, (B, H, W, 3) img with landmark, RGB order, range (0, 255) - - - Parameters: - img -- numpy.array, (B, H, W, 3), RGB order, range (0, 255) - landmark -- numpy.array, (B, 68, 2), y direction is opposite to v direction - color -- str, 'r' or 'b' (red or blue) - """ - if color =='r': - c = np.array([255., 0, 0]) - else: - c = np.array([0, 0, 255.]) - - _, H, W, _ = img.shape - img, landmark = img.copy(), landmark.copy() - landmark[..., 1] = H - 1 - landmark[..., 1] - landmark = np.round(landmark).astype(np.int32) - for i in range(landmark.shape[1]): - x, y = landmark[:, i, 0], landmark[:, i, 1] - for j in range(-step, step): - for k in range(-step, step): - u = np.clip(x + j, 0, W - 1) - v = np.clip(y + k, 0, H - 1) - for m in range(landmark.shape[0]): - img[m, v[m], u[m]] = c - return img diff --git a/src/face3d/util/visualizer.py b/src/face3d/util/visualizer.py deleted file mode 100644 index 4023a6d..0000000 --- a/src/face3d/util/visualizer.py +++ /dev/null @@ -1,227 +0,0 @@ -"""This script defines the visualizer for Deep3DFaceRecon_pytorch -""" - -import numpy as np -import os -import sys -import ntpath -import time -from . import util, html -from subprocess import Popen, PIPE -from torch.utils.tensorboard import SummaryWriter - -def save_images(webpage, visuals, image_path, aspect_ratio=1.0, width=256): - """Save images to the disk. - - Parameters: - webpage (the HTML class) -- the HTML webpage class that stores these imaegs (see html.py for more details) - visuals (OrderedDict) -- an ordered dictionary that stores (name, images (either tensor or numpy) ) pairs - image_path (str) -- the string is used to create image paths - aspect_ratio (float) -- the aspect ratio of saved images - width (int) -- the images will be resized to width x width - - This function will save images stored in 'visuals' to the HTML file specified by 'webpage'. - """ - image_dir = webpage.get_image_dir() - short_path = ntpath.basename(image_path[0]) - name = os.path.splitext(short_path)[0] - - webpage.add_header(name) - ims, txts, links = [], [], [] - - for label, im_data in visuals.items(): - im = util.tensor2im(im_data) - image_name = '%s/%s.png' % (label, name) - os.makedirs(os.path.join(image_dir, label), exist_ok=True) - save_path = os.path.join(image_dir, image_name) - util.save_image(im, save_path, aspect_ratio=aspect_ratio) - ims.append(image_name) - txts.append(label) - links.append(image_name) - webpage.add_images(ims, txts, links, width=width) - - -class Visualizer(): - """This class includes several functions that can display/save images and print/save logging information. - - It uses a Python library tensprboardX for display, and a Python library 'dominate' (wrapped in 'HTML') for creating HTML files with images. - """ - - def __init__(self, opt): - """Initialize the Visualizer class - - Parameters: - opt -- stores all the experiment flags; needs to be a subclass of BaseOptions - Step 1: Cache the training/test options - Step 2: create a tensorboard writer - Step 3: create an HTML object for saveing HTML filters - Step 4: create a logging file to store training losses - """ - self.opt = opt # cache the option - self.use_html = opt.isTrain and not opt.no_html - self.writer = SummaryWriter(os.path.join(opt.checkpoints_dir, 'logs', opt.name)) - self.win_size = opt.display_winsize - self.name = opt.name - self.saved = False - if self.use_html: # create an HTML object at /web/; images will be saved under /web/images/ - self.web_dir = os.path.join(opt.checkpoints_dir, opt.name, 'web') - self.img_dir = os.path.join(self.web_dir, 'images') - print('create web directory %s...' % self.web_dir) - util.mkdirs([self.web_dir, self.img_dir]) - # create a logging file to store training losses - self.log_name = os.path.join(opt.checkpoints_dir, opt.name, 'loss_log.txt') - with open(self.log_name, "a") as log_file: - now = time.strftime("%c") - log_file.write('================ Training Loss (%s) ================\n' % now) - - def reset(self): - """Reset the self.saved status""" - self.saved = False - - - def display_current_results(self, visuals, total_iters, epoch, save_result): - """Display current results on tensorboad; save current results to an HTML file. - - Parameters: - visuals (OrderedDict) - - dictionary of images to display or save - total_iters (int) -- total iterations - epoch (int) - - the current epoch - save_result (bool) - - if save the current results to an HTML file - """ - for label, image in visuals.items(): - self.writer.add_image(label, util.tensor2im(image), total_iters, dataformats='HWC') - - if self.use_html and (save_result or not self.saved): # save images to an HTML file if they haven't been saved. - self.saved = True - # save images to the disk - for label, image in visuals.items(): - image_numpy = util.tensor2im(image) - img_path = os.path.join(self.img_dir, 'epoch%.3d_%s.png' % (epoch, label)) - util.save_image(image_numpy, img_path) - - # update website - webpage = html.HTML(self.web_dir, 'Experiment name = %s' % self.name, refresh=0) - for n in range(epoch, 0, -1): - webpage.add_header('epoch [%d]' % n) - ims, txts, links = [], [], [] - - for label, image_numpy in visuals.items(): - image_numpy = util.tensor2im(image) - img_path = 'epoch%.3d_%s.png' % (n, label) - ims.append(img_path) - txts.append(label) - links.append(img_path) - webpage.add_images(ims, txts, links, width=self.win_size) - webpage.save() - - def plot_current_losses(self, total_iters, losses): - # G_loss_collection = {} - # D_loss_collection = {} - # for name, value in losses.items(): - # if 'G' in name or 'NCE' in name or 'idt' in name: - # G_loss_collection[name] = value - # else: - # D_loss_collection[name] = value - # self.writer.add_scalars('G_collec', G_loss_collection, total_iters) - # self.writer.add_scalars('D_collec', D_loss_collection, total_iters) - for name, value in losses.items(): - self.writer.add_scalar(name, value, total_iters) - - # losses: same format as |losses| of plot_current_losses - def print_current_losses(self, epoch, iters, losses, t_comp, t_data): - """print current losses on console; also save the losses to the disk - - Parameters: - epoch (int) -- current epoch - iters (int) -- current training iteration during this epoch (reset to 0 at the end of every epoch) - losses (OrderedDict) -- training losses stored in the format of (name, float) pairs - t_comp (float) -- computational time per data point (normalized by batch_size) - t_data (float) -- data loading time per data point (normalized by batch_size) - """ - message = '(epoch: %d, iters: %d, time: %.3f, data: %.3f) ' % (epoch, iters, t_comp, t_data) - for k, v in losses.items(): - message += '%s: %.3f ' % (k, v) - - print(message) # print the message - with open(self.log_name, "a") as log_file: - log_file.write('%s\n' % message) # save the message - - -class MyVisualizer: - def __init__(self, opt): - """Initialize the Visualizer class - - Parameters: - opt -- stores all the experiment flags; needs to be a subclass of BaseOptions - Step 1: Cache the training/test options - Step 2: create a tensorboard writer - Step 3: create an HTML object for saveing HTML filters - Step 4: create a logging file to store training losses - """ - self.opt = opt # cache the optio - self.name = opt.name - self.img_dir = os.path.join(opt.checkpoints_dir, opt.name, 'results') - - if opt.phase != 'test': - self.writer = SummaryWriter(os.path.join(opt.checkpoints_dir, opt.name, 'logs')) - # create a logging file to store training losses - self.log_name = os.path.join(opt.checkpoints_dir, opt.name, 'loss_log.txt') - with open(self.log_name, "a") as log_file: - now = time.strftime("%c") - log_file.write('================ Training Loss (%s) ================\n' % now) - - - def display_current_results(self, visuals, total_iters, epoch, dataset='train', save_results=False, count=0, name=None, - add_image=True): - """Display current results on tensorboad; save current results to an HTML file. - - Parameters: - visuals (OrderedDict) - - dictionary of images to display or save - total_iters (int) -- total iterations - epoch (int) - - the current epoch - dataset (str) - - 'train' or 'val' or 'test' - """ - # if (not add_image) and (not save_results): return - - for label, image in visuals.items(): - for i in range(image.shape[0]): - image_numpy = util.tensor2im(image[i]) - if add_image: - self.writer.add_image(label + '%s_%02d'%(dataset, i + count), - image_numpy, total_iters, dataformats='HWC') - - if save_results: - save_path = os.path.join(self.img_dir, dataset, 'epoch_%s_%06d'%(epoch, total_iters)) - if not os.path.isdir(save_path): - os.makedirs(save_path) - - if name is not None: - img_path = os.path.join(save_path, '%s.png' % name) - else: - img_path = os.path.join(save_path, '%s_%03d.png' % (label, i + count)) - util.save_image(image_numpy, img_path) - - - def plot_current_losses(self, total_iters, losses, dataset='train'): - for name, value in losses.items(): - self.writer.add_scalar(name + '/%s'%dataset, value, total_iters) - - # losses: same format as |losses| of plot_current_losses - def print_current_losses(self, epoch, iters, losses, t_comp, t_data, dataset='train'): - """print current losses on console; also save the losses to the disk - - Parameters: - epoch (int) -- current epoch - iters (int) -- current training iteration during this epoch (reset to 0 at the end of every epoch) - losses (OrderedDict) -- training losses stored in the format of (name, float) pairs - t_comp (float) -- computational time per data point (normalized by batch_size) - t_data (float) -- data loading time per data point (normalized by batch_size) - """ - message = '(dataset: %s, epoch: %d, iters: %d, time: %.3f, data: %.3f) ' % ( - dataset, epoch, iters, t_comp, t_data) - for k, v in losses.items(): - message += '%s: %.3f ' % (k, v) - - print(message) # print the message - with open(self.log_name, "a") as log_file: - log_file.write('%s\n' % message) # save the message diff --git a/src/face3d/visualize.py b/src/face3d/visualize.py deleted file mode 100644 index cf9c4f7..0000000 --- a/src/face3d/visualize.py +++ /dev/null @@ -1,48 +0,0 @@ -# check the sync of 3dmm feature and the audio -import cv2 -import numpy as np -from custom_nodes.ComfyUI_Lam.src.face3d.models.bfm import ParametricFaceModel -from custom_nodes.ComfyUI_Lam.src.face3d.models.facerecon_model import FaceReconModel -import torch -import subprocess, platform -import scipy.io as scio -from tqdm import tqdm - -# draft -def gen_composed_video(args, device, first_frame_coeff, coeff_path, audio_path, save_path, exp_dim=64): - - coeff_first = scio.loadmat(first_frame_coeff)['full_3dmm'] - - coeff_pred = scio.loadmat(coeff_path)['coeff_3dmm'] - - coeff_full = np.repeat(coeff_first, coeff_pred.shape[0], axis=0) # 257 - - coeff_full[:, 80:144] = coeff_pred[:, 0:64] - coeff_full[:, 224:227] = coeff_pred[:, 64:67] # 3 dim translation - coeff_full[:, 254:] = coeff_pred[:, 67:] # 3 dim translation - - tmp_video_path = '/tmp/face3dtmp.mp4' - - facemodel = FaceReconModel(args) - - video = cv2.VideoWriter(tmp_video_path, cv2.VideoWriter_fourcc(*'mp4v'), 25, (224, 224)) - - for k in tqdm(range(coeff_pred.shape[0]), 'face3d rendering:'): - cur_coeff_full = torch.tensor(coeff_full[k:k+1], device=device) - - facemodel.forward(cur_coeff_full, device) - - predicted_landmark = facemodel.pred_lm # TODO. - predicted_landmark = predicted_landmark.cpu().numpy().squeeze() - - rendered_img = facemodel.pred_face - rendered_img = 255. * rendered_img.cpu().numpy().squeeze().transpose(1,2,0) - out_img = rendered_img[:, :, :3].astype(np.uint8) - - video.write(np.uint8(out_img[:,:,::-1])) - - video.release() - - command = 'ffmpeg -v quiet -y -i {} -i {} -strict -2 -q:v 1 {}'.format(audio_path, tmp_video_path, save_path) - subprocess.call(command, shell=platform.system() != 'Windows') - diff --git a/src/facerender/animate.py b/src/facerender/animate.py deleted file mode 100644 index e96adcd..0000000 --- a/src/facerender/animate.py +++ /dev/null @@ -1,257 +0,0 @@ -import os -import cv2 -import yaml -import numpy as np -import warnings -from skimage import img_as_ubyte -import safetensors -import safetensors.torch -warnings.filterwarnings('ignore') - - -import imageio -import torch -import torchvision - - -from custom_nodes.ComfyUI_Lam.src.facerender.modules.keypoint_detector import HEEstimator, KPDetector -from custom_nodes.ComfyUI_Lam.src.facerender.modules.mapping import MappingNet -from custom_nodes.ComfyUI_Lam.src.facerender.modules.generator import OcclusionAwareGenerator, OcclusionAwareSPADEGenerator -from custom_nodes.ComfyUI_Lam.src.facerender.modules.make_animation import make_animation - -from pydub import AudioSegment -#from custom_nodes.ComfyUI_Lam.src.utils.face_enhancer import enhancer_generator_with_len, enhancer_list -from custom_nodes.ComfyUI_Lam.src.utils.paste_pic import paste_pic -from custom_nodes.ComfyUI_Lam.src.utils.videoio import save_video_with_watermark - -try: - import webui # in webui - in_webui = True -except: - in_webui = False - -class AnimateFromCoeff(): - - def __init__(self, sadtalker_path, device): - - with open(sadtalker_path['facerender_yaml']) as f: - config = yaml.safe_load(f) - - generator = OcclusionAwareSPADEGenerator(**config['model_params']['generator_params'], - **config['model_params']['common_params']) - kp_extractor = KPDetector(**config['model_params']['kp_detector_params'], - **config['model_params']['common_params']) - he_estimator = HEEstimator(**config['model_params']['he_estimator_params'], - **config['model_params']['common_params']) - mapping = MappingNet(**config['model_params']['mapping_params']) - - generator.to(device) - kp_extractor.to(device) - he_estimator.to(device) - mapping.to(device) - for param in generator.parameters(): - param.requires_grad = False - for param in kp_extractor.parameters(): - param.requires_grad = False - for param in he_estimator.parameters(): - param.requires_grad = False - for param in mapping.parameters(): - param.requires_grad = False - - if sadtalker_path is not None: - if 'checkpoint' in sadtalker_path: # use safe tensor - self.load_cpk_facevid2vid_safetensor(sadtalker_path['checkpoint'], kp_detector=kp_extractor, generator=generator, he_estimator=None) - else: - self.load_cpk_facevid2vid(sadtalker_path['free_view_checkpoint'], kp_detector=kp_extractor, generator=generator, he_estimator=he_estimator) - else: - raise AttributeError("Checkpoint should be specified for video head pose estimator.") - - if sadtalker_path['mappingnet_checkpoint'] is not None: - self.load_cpk_mapping(sadtalker_path['mappingnet_checkpoint'], mapping=mapping) - else: - raise AttributeError("Checkpoint should be specified for video head pose estimator.") - - self.kp_extractor = kp_extractor - self.generator = generator - self.he_estimator = he_estimator - self.mapping = mapping - - self.kp_extractor.eval() - self.generator.eval() - self.he_estimator.eval() - self.mapping.eval() - - self.device = device - - def load_cpk_facevid2vid_safetensor(self, checkpoint_path, generator=None, - kp_detector=None, he_estimator=None, - device="cpu"): - - checkpoint = safetensors.torch.load_file(checkpoint_path) - - if generator is not None: - x_generator = {} - for k,v in checkpoint.items(): - if 'generator' in k: - x_generator[k.replace('generator.', '')] = v - generator.load_state_dict(x_generator) - if kp_detector is not None: - x_generator = {} - for k,v in checkpoint.items(): - if 'kp_extractor' in k: - x_generator[k.replace('kp_extractor.', '')] = v - kp_detector.load_state_dict(x_generator) - if he_estimator is not None: - x_generator = {} - for k,v in checkpoint.items(): - if 'he_estimator' in k: - x_generator[k.replace('he_estimator.', '')] = v - he_estimator.load_state_dict(x_generator) - - return None - - def load_cpk_facevid2vid(self, checkpoint_path, generator=None, discriminator=None, - kp_detector=None, he_estimator=None, optimizer_generator=None, - optimizer_discriminator=None, optimizer_kp_detector=None, - optimizer_he_estimator=None, device="cpu"): - checkpoint = torch.load(checkpoint_path, map_location=torch.device(device)) - if generator is not None: - generator.load_state_dict(checkpoint['generator']) - if kp_detector is not None: - kp_detector.load_state_dict(checkpoint['kp_detector']) - if he_estimator is not None: - he_estimator.load_state_dict(checkpoint['he_estimator']) - if discriminator is not None: - try: - discriminator.load_state_dict(checkpoint['discriminator']) - except: - print ('No discriminator in the state-dict. Dicriminator will be randomly initialized') - if optimizer_generator is not None: - optimizer_generator.load_state_dict(checkpoint['optimizer_generator']) - if optimizer_discriminator is not None: - try: - optimizer_discriminator.load_state_dict(checkpoint['optimizer_discriminator']) - except RuntimeError as e: - print ('No discriminator optimizer in the state-dict. Optimizer will be not initialized') - if optimizer_kp_detector is not None: - optimizer_kp_detector.load_state_dict(checkpoint['optimizer_kp_detector']) - if optimizer_he_estimator is not None: - optimizer_he_estimator.load_state_dict(checkpoint['optimizer_he_estimator']) - - return checkpoint['epoch'] - - def load_cpk_mapping(self, checkpoint_path, mapping=None, discriminator=None, - optimizer_mapping=None, optimizer_discriminator=None, device='cpu'): - checkpoint = torch.load(checkpoint_path, map_location=torch.device(device)) - if mapping is not None: - mapping.load_state_dict(checkpoint['mapping']) - if discriminator is not None: - discriminator.load_state_dict(checkpoint['discriminator']) - if optimizer_mapping is not None: - optimizer_mapping.load_state_dict(checkpoint['optimizer_mapping']) - if optimizer_discriminator is not None: - optimizer_discriminator.load_state_dict(checkpoint['optimizer_discriminator']) - - return checkpoint['epoch'] - - def generate(self, x, video_save_dir, pic_path, crop_info, enhancer=None, background_enhancer=None, preprocess='crop', img_size=256): - - source_image=x['source_image'].type(torch.FloatTensor) - source_semantics=x['source_semantics'].type(torch.FloatTensor) - target_semantics=x['target_semantics_list'].type(torch.FloatTensor) - source_image=source_image.to(self.device) - source_semantics=source_semantics.to(self.device) - target_semantics=target_semantics.to(self.device) - if 'yaw_c_seq' in x: - yaw_c_seq = x['yaw_c_seq'].type(torch.FloatTensor) - yaw_c_seq = x['yaw_c_seq'].to(self.device) - else: - yaw_c_seq = None - if 'pitch_c_seq' in x: - pitch_c_seq = x['pitch_c_seq'].type(torch.FloatTensor) - pitch_c_seq = x['pitch_c_seq'].to(self.device) - else: - pitch_c_seq = None - if 'roll_c_seq' in x: - roll_c_seq = x['roll_c_seq'].type(torch.FloatTensor) - roll_c_seq = x['roll_c_seq'].to(self.device) - else: - roll_c_seq = None - - frame_num = x['frame_num'] - - predictions_video = make_animation(source_image, source_semantics, target_semantics, - self.generator, self.kp_extractor, self.he_estimator, self.mapping, - yaw_c_seq, pitch_c_seq, roll_c_seq, use_exp = True) - - predictions_video = predictions_video.reshape((-1,)+predictions_video.shape[2:]) - predictions_video = predictions_video[:frame_num] - - video = [] - for idx in range(predictions_video.shape[0]): - image = predictions_video[idx] - image = np.transpose(image.data.cpu().numpy(), [1, 2, 0]).astype(np.float32) - video.append(image) - result = img_as_ubyte(video) - - ### the generated video is 256x256, so we keep the aspect ratio, - original_size = crop_info[0] - if original_size: - result = [ cv2.resize(result_i,(img_size, int(img_size * original_size[1]/original_size[0]) )) for result_i in result ] - - video_name = x['video_name'] + '.mp4' - path = os.path.join(video_save_dir, 'temp_'+video_name) - - imageio.mimsave(path, result, fps=float(25)) - - av_path = os.path.join(video_save_dir, video_name) - return_path = av_path - - audio_path = x['audio_path'] - audio_name = os.path.splitext(os.path.split(audio_path)[-1])[0] - new_audio_path = os.path.join(video_save_dir, audio_name+'.wav') - start_time = 0 - # cog will not keep the .mp3 filename - sound = AudioSegment.from_file(audio_path) - frames = frame_num - end_time = start_time + frames*1/25*1000 - word1=sound.set_frame_rate(16000) - word = word1[start_time:end_time] - word.export(new_audio_path, format="wav") - - save_video_with_watermark(path, new_audio_path, av_path, watermark= False) - print(f'生成的头像视频名为 {video_save_dir}/{video_name}') - - if 'full' in preprocess.lower(): - # only add watermark to the full image. - video_name_full = x['video_name'] + '_full.mp4' - full_video_path = os.path.join(video_save_dir, video_name_full) - return_path = full_video_path - paste_pic(path, pic_path, crop_info, new_audio_path, full_video_path, extended_crop= True if 'ext' in preprocess.lower() else False) - print(f'The generated video is named {video_save_dir}/{video_name_full}') - else: - full_video_path = av_path - - #### paste back then enhancers - #if enhancer: - # video_name_enhancer = x['video_name'] + '_enhanced.mp4' - # enhanced_path = os.path.join(video_save_dir, 'temp_'+video_name_enhancer) - # av_path_enhancer = os.path.join(video_save_dir, video_name_enhancer) - # return_path = av_path_enhancer - - # try: - # enhanced_images_gen_with_len = enhancer_generator_with_len(full_video_path, method=enhancer, bg_upsampler=background_enhancer) - # imageio.mimsave(enhanced_path, enhanced_images_gen_with_len, fps=float(25)) - # except: - # enhanced_images_gen_with_len = enhancer_list(full_video_path, method=enhancer, bg_upsampler=background_enhancer) - # imageio.mimsave(enhanced_path, enhanced_images_gen_with_len, fps=float(25)) - - # save_video_with_watermark(enhanced_path, new_audio_path, av_path_enhancer, watermark= False) - # print(f'The generated video is named {video_save_dir}/{video_name_enhancer}') - # os.remove(enhanced_path) - - os.remove(path) - os.remove(new_audio_path) - - return return_path - diff --git a/src/facerender/animate_two.py b/src/facerender/animate_two.py deleted file mode 100644 index 3cf9055..0000000 --- a/src/facerender/animate_two.py +++ /dev/null @@ -1,172 +0,0 @@ -import os -import cv2 -import yaml -import numpy as np -import warnings -from skimage import img_as_ubyte -import imageio -import torch -from custom_nodes.ComfyUI_Lam.src.facerender.modules.keypoint_detector import HEEstimator, KPDetector -from custom_nodes.ComfyUI_Lam.src.facerender.modules.mapping import MappingNet -from custom_nodes.ComfyUI_Lam.src.facerender.modules.generator import OcclusionAwareSPADEGenerator -from custom_nodes.ComfyUI_Lam.src.facerender.modules.make_animation_two import make_animation -from pydub import AudioSegment -from custom_nodes.ComfyUI_Lam.src.utils.paste_pic_two import paste_pic -from custom_nodes.ComfyUI_Lam.src.utils.videoio_two import save_video_with_watermark - -warnings.filterwarnings('ignore') - - -class AnimateFromCoeff(): - - def __init__(self, free_view_checkpoint, mapping_checkpoint, - config_path, device): - - with open(config_path) as f: - config = yaml.safe_load(f) - - generator = OcclusionAwareSPADEGenerator(**config['model_params']['generator_params'], - **config['model_params']['common_params']) - kp_extractor = KPDetector(**config['model_params']['kp_detector_params'], - **config['model_params']['common_params']) - he_estimator = HEEstimator(**config['model_params']['he_estimator_params'], - **config['model_params']['common_params']) - mapping = MappingNet(**config['model_params']['mapping_params']) - - generator.to(device) - kp_extractor.to(device) - he_estimator.to(device) - mapping.to(device) - for param in generator.parameters(): - param.requires_grad = False - for param in kp_extractor.parameters(): - param.requires_grad = False - for param in he_estimator.parameters(): - param.requires_grad = False - for param in mapping.parameters(): - param.requires_grad = False - - if free_view_checkpoint is not None: - self.load_cpk_facevid2vid(free_view_checkpoint, kp_detector=kp_extractor, generator=generator, - he_estimator=he_estimator) - else: - raise AttributeError("Checkpoint should be specified for video head pose estimator.") - - if mapping_checkpoint is not None: - self.load_cpk_mapping(mapping_checkpoint, mapping=mapping) - else: - raise AttributeError("Checkpoint should be specified for video head pose estimator.") - - self.kp_extractor = kp_extractor - self.generator = generator - self.he_estimator = he_estimator - self.mapping = mapping - - self.kp_extractor.eval() - self.generator.eval() - self.he_estimator.eval() - self.mapping.eval() - - self.device = device - - def load_cpk_facevid2vid(self, checkpoint_path, generator=None, discriminator=None, - kp_detector=None, he_estimator=None, optimizer_generator=None, - optimizer_discriminator=None, optimizer_kp_detector=None, - optimizer_he_estimator=None, device="cpu"): - checkpoint = torch.load(checkpoint_path, map_location=torch.device(device)) - if generator is not None: - generator.load_state_dict(checkpoint['generator']) - if kp_detector is not None: - kp_detector.load_state_dict(checkpoint['kp_detector']) - if he_estimator is not None: - he_estimator.load_state_dict(checkpoint['he_estimator']) - if discriminator is not None: - try: - discriminator.load_state_dict(checkpoint['discriminator']) - except: - print('No discriminator in the state-dict. Dicriminator will be randomly initialized') - if optimizer_generator is not None: - optimizer_generator.load_state_dict(checkpoint['optimizer_generator']) - if optimizer_discriminator is not None: - try: - optimizer_discriminator.load_state_dict(checkpoint['optimizer_discriminator']) - except RuntimeError as e: - print('No discriminator optimizer in the state-dict. Optimizer will be not initialized') - if optimizer_kp_detector is not None: - optimizer_kp_detector.load_state_dict(checkpoint['optimizer_kp_detector']) - if optimizer_he_estimator is not None: - optimizer_he_estimator.load_state_dict(checkpoint['optimizer_he_estimator']) - - return checkpoint['epoch'] - - def load_cpk_mapping(self, checkpoint_path, mapping=None, discriminator=None, - optimizer_mapping=None, optimizer_discriminator=None, device='cpu'): - checkpoint = torch.load(checkpoint_path, map_location=torch.device(device)) - if mapping is not None: - mapping.load_state_dict(checkpoint['mapping']) - if discriminator is not None: - discriminator.load_state_dict(checkpoint['discriminator']) - if optimizer_mapping is not None: - optimizer_mapping.load_state_dict(checkpoint['optimizer_mapping']) - if optimizer_discriminator is not None: - optimizer_discriminator.load_state_dict(checkpoint['optimizer_discriminator']) - - return checkpoint['epoch'] - - def generate(self, x, video_save_dir, pic_path, crop_info, restorer_model, enhancer_model, enhancer_region): - source_image = x['source_image'] - source_semantics = x['source_semantics'].type(torch.FloatTensor) - target_semantics = x['target_semantics_list'].type(torch.FloatTensor) - source_semantics = source_semantics.to(self.device) - target_semantics = target_semantics.to(self.device) - - frame_num = x['frame_num'] - if len(source_image) < frame_num: - frame_num = len(source_image) - predictions_video = make_animation(source_image, source_semantics, target_semantics, - self.generator, self.kp_extractor, self.mapping, frame_num) - - predictions_video = predictions_video.reshape((-1,) + predictions_video.shape[2:]) - predictions_video = predictions_video[:frame_num] - - video = [] - for idx in range(predictions_video.shape[0]): - image = predictions_video[idx] - image = np.transpose(image.data.cpu().numpy(), [1, 2, 0]).astype(np.float32) - video.append(image) - result = img_as_ubyte(video) - - ### the generated video is 256x256, so we keep the aspect ratio, - original_size = crop_info[0] - if original_size: - result = [cv2.resize(result_i, (256, int(256.0 * original_size[1] / original_size[0]))) for result_i in - result] - video_name = x['video_name'] + '.mp4' - path = os.path.join(video_save_dir, 'temp_' + video_name) - imageio.mimsave(path, result, fps=float(25)) - - av_path = os.path.join(video_save_dir, video_name) - - audio_path = x['audio_path'] - audio_name = os.path.splitext(os.path.split(audio_path)[-1])[0] - new_audio_path = os.path.join(video_save_dir, audio_name + '.wav') - start_time = 0 - sound = AudioSegment.from_mp3(audio_path) - frames = frame_num - end_time = start_time + frames * 1 / 25 * 1000 - word1 = sound.set_frame_rate(16000) - word = word1[start_time:end_time] - word.export(new_audio_path, format="wav") - - save_video_with_watermark(path, new_audio_path, av_path, watermark=False) - print(f'The generated video is named {video_name} in {video_save_dir}') - - video_name_full = x['video_name'] + '_full.mp4' - full_video_path = os.path.join(video_save_dir, video_name_full) - return_path = full_video_path - tmp_path, new_audio_path = paste_pic(path, pic_path, crop_info, new_audio_path, - full_video_path, restorer_model, enhancer_model, - enhancer_region) - print(f'The generated video is named {video_save_dir}/{video_name_full}') - - return tmp_path, new_audio_path, return_path diff --git a/src/facerender/modules/dense_motion.py b/src/facerender/modules/dense_motion.py deleted file mode 100644 index 3d19bc5..0000000 --- a/src/facerender/modules/dense_motion.py +++ /dev/null @@ -1,121 +0,0 @@ -from torch import nn -import torch.nn.functional as F -import torch -from custom_nodes.ComfyUI_Lam.src.facerender.modules.util import Hourglass, make_coordinate_grid, kp2gaussian - -from custom_nodes.ComfyUI_Lam.src.facerender.sync_batchnorm import SynchronizedBatchNorm3d as BatchNorm3d - - -class DenseMotionNetwork(nn.Module): - """ - Module that predicting a dense motion from sparse motion representation given by kp_source and kp_driving - """ - - def __init__(self, block_expansion, num_blocks, max_features, num_kp, feature_channel, reshape_depth, compress, - estimate_occlusion_map=False): - super(DenseMotionNetwork, self).__init__() - # self.hourglass = Hourglass(block_expansion=block_expansion, in_features=(num_kp+1)*(feature_channel+1), max_features=max_features, num_blocks=num_blocks) - self.hourglass = Hourglass(block_expansion=block_expansion, in_features=(num_kp+1)*(compress+1), max_features=max_features, num_blocks=num_blocks) - - self.mask = nn.Conv3d(self.hourglass.out_filters, num_kp + 1, kernel_size=7, padding=3) - - self.compress = nn.Conv3d(feature_channel, compress, kernel_size=1) - self.norm = BatchNorm3d(compress, affine=True) - - if estimate_occlusion_map: - # self.occlusion = nn.Conv2d(reshape_channel*reshape_depth, 1, kernel_size=7, padding=3) - self.occlusion = nn.Conv2d(self.hourglass.out_filters*reshape_depth, 1, kernel_size=7, padding=3) - else: - self.occlusion = None - - self.num_kp = num_kp - - - def create_sparse_motions(self, feature, kp_driving, kp_source): - bs, _, d, h, w = feature.shape - identity_grid = make_coordinate_grid((d, h, w), type=kp_source['value'].type()) - identity_grid = identity_grid.view(1, 1, d, h, w, 3) - coordinate_grid = identity_grid - kp_driving['value'].view(bs, self.num_kp, 1, 1, 1, 3) - - # if 'jacobian' in kp_driving: - if 'jacobian' in kp_driving and kp_driving['jacobian'] is not None: - jacobian = torch.matmul(kp_source['jacobian'], torch.inverse(kp_driving['jacobian'])) - jacobian = jacobian.unsqueeze(-3).unsqueeze(-3).unsqueeze(-3) - jacobian = jacobian.repeat(1, 1, d, h, w, 1, 1) - coordinate_grid = torch.matmul(jacobian, coordinate_grid.unsqueeze(-1)) - coordinate_grid = coordinate_grid.squeeze(-1) - - - driving_to_source = coordinate_grid + kp_source['value'].view(bs, self.num_kp, 1, 1, 1, 3) # (bs, num_kp, d, h, w, 3) - - #adding background feature - identity_grid = identity_grid.repeat(bs, 1, 1, 1, 1, 1) - sparse_motions = torch.cat([identity_grid, driving_to_source], dim=1) #bs num_kp+1 d h w 3 - - # sparse_motions = driving_to_source - - return sparse_motions - - def create_deformed_feature(self, feature, sparse_motions): - bs, _, d, h, w = feature.shape - feature_repeat = feature.unsqueeze(1).unsqueeze(1).repeat(1, self.num_kp+1, 1, 1, 1, 1, 1) # (bs, num_kp+1, 1, c, d, h, w) - feature_repeat = feature_repeat.view(bs * (self.num_kp+1), -1, d, h, w) # (bs*(num_kp+1), c, d, h, w) - sparse_motions = sparse_motions.view((bs * (self.num_kp+1), d, h, w, -1)) # (bs*(num_kp+1), d, h, w, 3) !!!! - sparse_deformed = F.grid_sample(feature_repeat, sparse_motions) - sparse_deformed = sparse_deformed.view((bs, self.num_kp+1, -1, d, h, w)) # (bs, num_kp+1, c, d, h, w) - return sparse_deformed - - def create_heatmap_representations(self, feature, kp_driving, kp_source): - spatial_size = feature.shape[3:] - gaussian_driving = kp2gaussian(kp_driving, spatial_size=spatial_size, kp_variance=0.01) - gaussian_source = kp2gaussian(kp_source, spatial_size=spatial_size, kp_variance=0.01) - heatmap = gaussian_driving - gaussian_source - - # adding background feature - zeros = torch.zeros(heatmap.shape[0], 1, spatial_size[0], spatial_size[1], spatial_size[2]).type(heatmap.type()) - heatmap = torch.cat([zeros, heatmap], dim=1) - heatmap = heatmap.unsqueeze(2) # (bs, num_kp+1, 1, d, h, w) - return heatmap - - def forward(self, feature, kp_driving, kp_source): - bs, _, d, h, w = feature.shape - - feature = self.compress(feature) - feature = self.norm(feature) - feature = F.relu(feature) - - out_dict = dict() - sparse_motion = self.create_sparse_motions(feature, kp_driving, kp_source) - deformed_feature = self.create_deformed_feature(feature, sparse_motion) - - heatmap = self.create_heatmap_representations(deformed_feature, kp_driving, kp_source) - - input_ = torch.cat([heatmap, deformed_feature], dim=2) - input_ = input_.view(bs, -1, d, h, w) - - # input = deformed_feature.view(bs, -1, d, h, w) # (bs, num_kp+1 * c, d, h, w) - - prediction = self.hourglass(input_) - - - mask = self.mask(prediction) - mask = F.softmax(mask, dim=1) - out_dict['mask'] = mask - mask = mask.unsqueeze(2) # (bs, num_kp+1, 1, d, h, w) - - zeros_mask = torch.zeros_like(mask) - mask = torch.where(mask < 1e-3, zeros_mask, mask) - - sparse_motion = sparse_motion.permute(0, 1, 5, 2, 3, 4) # (bs, num_kp+1, 3, d, h, w) - deformation = (sparse_motion * mask).sum(dim=1) # (bs, 3, d, h, w) - deformation = deformation.permute(0, 2, 3, 4, 1) # (bs, d, h, w, 3) - - out_dict['deformation'] = deformation - - if self.occlusion: - bs, c, d, h, w = prediction.shape - prediction = prediction.view(bs, -1, h, w) - occlusion_map = torch.sigmoid(self.occlusion(prediction)) - out_dict['occlusion_map'] = occlusion_map - - return out_dict diff --git a/src/facerender/modules/discriminator.py b/src/facerender/modules/discriminator.py deleted file mode 100644 index d4459b0..0000000 --- a/src/facerender/modules/discriminator.py +++ /dev/null @@ -1,90 +0,0 @@ -from torch import nn -import torch.nn.functional as F -from facerender.modules.util import kp2gaussian -import torch - - -class DownBlock2d(nn.Module): - """ - Simple block for processing video (encoder). - """ - - def __init__(self, in_features, out_features, norm=False, kernel_size=4, pool=False, sn=False): - super(DownBlock2d, self).__init__() - self.conv = nn.Conv2d(in_channels=in_features, out_channels=out_features, kernel_size=kernel_size) - - if sn: - self.conv = nn.utils.spectral_norm(self.conv) - - if norm: - self.norm = nn.InstanceNorm2d(out_features, affine=True) - else: - self.norm = None - self.pool = pool - - def forward(self, x): - out = x - out = self.conv(out) - if self.norm: - out = self.norm(out) - out = F.leaky_relu(out, 0.2) - if self.pool: - out = F.avg_pool2d(out, (2, 2)) - return out - - -class Discriminator(nn.Module): - """ - Discriminator similar to Pix2Pix - """ - - def __init__(self, num_channels=3, block_expansion=64, num_blocks=4, max_features=512, - sn=False, **kwargs): - super(Discriminator, self).__init__() - - down_blocks = [] - for i in range(num_blocks): - down_blocks.append( - DownBlock2d(num_channels if i == 0 else min(max_features, block_expansion * (2 ** i)), - min(max_features, block_expansion * (2 ** (i + 1))), - norm=(i != 0), kernel_size=4, pool=(i != num_blocks - 1), sn=sn)) - - self.down_blocks = nn.ModuleList(down_blocks) - self.conv = nn.Conv2d(self.down_blocks[-1].conv.out_channels, out_channels=1, kernel_size=1) - if sn: - self.conv = nn.utils.spectral_norm(self.conv) - - def forward(self, x): - feature_maps = [] - out = x - - for down_block in self.down_blocks: - feature_maps.append(down_block(out)) - out = feature_maps[-1] - prediction_map = self.conv(out) - - return feature_maps, prediction_map - - -class MultiScaleDiscriminator(nn.Module): - """ - Multi-scale (scale) discriminator - """ - - def __init__(self, scales=(), **kwargs): - super(MultiScaleDiscriminator, self).__init__() - self.scales = scales - discs = {} - for scale in scales: - discs[str(scale).replace('.', '-')] = Discriminator(**kwargs) - self.discs = nn.ModuleDict(discs) - - def forward(self, x): - out_dict = {} - for scale, disc in self.discs.items(): - scale = str(scale).replace('-', '.') - key = 'prediction_' + scale - feature_maps, prediction_map = disc(x[key]) - out_dict['feature_maps_' + scale] = feature_maps - out_dict['prediction_map_' + scale] = prediction_map - return out_dict diff --git a/src/facerender/modules/generator.py b/src/facerender/modules/generator.py deleted file mode 100644 index 0d70427..0000000 --- a/src/facerender/modules/generator.py +++ /dev/null @@ -1,255 +0,0 @@ -import torch -from torch import nn -import torch.nn.functional as F -from custom_nodes.ComfyUI_Lam.src.facerender.modules.util import ResBlock2d, SameBlock2d, UpBlock2d, DownBlock2d, ResBlock3d, SPADEResnetBlock -from custom_nodes.ComfyUI_Lam.src.facerender.modules.dense_motion import DenseMotionNetwork - - -class OcclusionAwareGenerator(nn.Module): - """ - Generator follows NVIDIA architecture. - """ - - def __init__(self, image_channel, feature_channel, num_kp, block_expansion, max_features, num_down_blocks, reshape_channel, reshape_depth, - num_resblocks, estimate_occlusion_map=False, dense_motion_params=None, estimate_jacobian=False): - super(OcclusionAwareGenerator, self).__init__() - - if dense_motion_params is not None: - self.dense_motion_network = DenseMotionNetwork(num_kp=num_kp, feature_channel=feature_channel, - estimate_occlusion_map=estimate_occlusion_map, - **dense_motion_params) - else: - self.dense_motion_network = None - - self.first = SameBlock2d(image_channel, block_expansion, kernel_size=(7, 7), padding=(3, 3)) - - down_blocks = [] - for i in range(num_down_blocks): - in_features = min(max_features, block_expansion * (2 ** i)) - out_features = min(max_features, block_expansion * (2 ** (i + 1))) - down_blocks.append(DownBlock2d(in_features, out_features, kernel_size=(3, 3), padding=(1, 1))) - self.down_blocks = nn.ModuleList(down_blocks) - - self.second = nn.Conv2d(in_channels=out_features, out_channels=max_features, kernel_size=1, stride=1) - - self.reshape_channel = reshape_channel - self.reshape_depth = reshape_depth - - self.resblocks_3d = torch.nn.Sequential() - for i in range(num_resblocks): - self.resblocks_3d.add_module('3dr' + str(i), ResBlock3d(reshape_channel, kernel_size=3, padding=1)) - - out_features = block_expansion * (2 ** (num_down_blocks)) - self.third = SameBlock2d(max_features, out_features, kernel_size=(3, 3), padding=(1, 1), lrelu=True) - self.fourth = nn.Conv2d(in_channels=out_features, out_channels=out_features, kernel_size=1, stride=1) - - self.resblocks_2d = torch.nn.Sequential() - for i in range(num_resblocks): - self.resblocks_2d.add_module('2dr' + str(i), ResBlock2d(out_features, kernel_size=3, padding=1)) - - up_blocks = [] - for i in range(num_down_blocks): - in_features = max(block_expansion, block_expansion * (2 ** (num_down_blocks - i))) - out_features = max(block_expansion, block_expansion * (2 ** (num_down_blocks - i - 1))) - up_blocks.append(UpBlock2d(in_features, out_features, kernel_size=(3, 3), padding=(1, 1))) - self.up_blocks = nn.ModuleList(up_blocks) - - self.final = nn.Conv2d(block_expansion, image_channel, kernel_size=(7, 7), padding=(3, 3)) - self.estimate_occlusion_map = estimate_occlusion_map - self.image_channel = image_channel - - def deform_input(self, inp, deformation): - _, d_old, h_old, w_old, _ = deformation.shape - _, _, d, h, w = inp.shape - if d_old != d or h_old != h or w_old != w: - deformation = deformation.permute(0, 4, 1, 2, 3) - deformation = F.interpolate(deformation, size=(d, h, w), mode='trilinear') - deformation = deformation.permute(0, 2, 3, 4, 1) - return F.grid_sample(inp, deformation) - - def forward(self, source_image, kp_driving, kp_source): - # Encoding (downsampling) part - out = self.first(source_image) - for i in range(len(self.down_blocks)): - out = self.down_blocks[i](out) - out = self.second(out) - bs, c, h, w = out.shape - # print(out.shape) - feature_3d = out.view(bs, self.reshape_channel, self.reshape_depth, h ,w) - feature_3d = self.resblocks_3d(feature_3d) - - # Transforming feature representation according to deformation and occlusion - output_dict = {} - if self.dense_motion_network is not None: - dense_motion = self.dense_motion_network(feature=feature_3d, kp_driving=kp_driving, - kp_source=kp_source) - output_dict['mask'] = dense_motion['mask'] - - if 'occlusion_map' in dense_motion: - occlusion_map = dense_motion['occlusion_map'] - output_dict['occlusion_map'] = occlusion_map - else: - occlusion_map = None - deformation = dense_motion['deformation'] - out = self.deform_input(feature_3d, deformation) - - bs, c, d, h, w = out.shape - out = out.view(bs, c*d, h, w) - out = self.third(out) - out = self.fourth(out) - - if occlusion_map is not None: - if out.shape[2] != occlusion_map.shape[2] or out.shape[3] != occlusion_map.shape[3]: - occlusion_map = F.interpolate(occlusion_map, size=out.shape[2:], mode='bilinear') - out = out * occlusion_map - - # output_dict["deformed"] = self.deform_input(source_image, deformation) # 3d deformation cannot deform 2d image - - # Decoding part - out = self.resblocks_2d(out) - for i in range(len(self.up_blocks)): - out = self.up_blocks[i](out) - out = self.final(out) - out = F.sigmoid(out) - - output_dict["prediction"] = out - - return output_dict - - -class SPADEDecoder(nn.Module): - def __init__(self): - super().__init__() - ic = 256 - oc = 64 - norm_G = 'spadespectralinstance' - label_nc = 256 - - self.fc = nn.Conv2d(ic, 2 * ic, 3, padding=1) - self.G_middle_0 = SPADEResnetBlock(2 * ic, 2 * ic, norm_G, label_nc) - self.G_middle_1 = SPADEResnetBlock(2 * ic, 2 * ic, norm_G, label_nc) - self.G_middle_2 = SPADEResnetBlock(2 * ic, 2 * ic, norm_G, label_nc) - self.G_middle_3 = SPADEResnetBlock(2 * ic, 2 * ic, norm_G, label_nc) - self.G_middle_4 = SPADEResnetBlock(2 * ic, 2 * ic, norm_G, label_nc) - self.G_middle_5 = SPADEResnetBlock(2 * ic, 2 * ic, norm_G, label_nc) - self.up_0 = SPADEResnetBlock(2 * ic, ic, norm_G, label_nc) - self.up_1 = SPADEResnetBlock(ic, oc, norm_G, label_nc) - self.conv_img = nn.Conv2d(oc, 3, 3, padding=1) - self.up = nn.Upsample(scale_factor=2) - - def forward(self, feature): - seg = feature - x = self.fc(feature) - x = self.G_middle_0(x, seg) - x = self.G_middle_1(x, seg) - x = self.G_middle_2(x, seg) - x = self.G_middle_3(x, seg) - x = self.G_middle_4(x, seg) - x = self.G_middle_5(x, seg) - x = self.up(x) - x = self.up_0(x, seg) # 256, 128, 128 - x = self.up(x) - x = self.up_1(x, seg) # 64, 256, 256 - - x = self.conv_img(F.leaky_relu(x, 2e-1)) - # x = torch.tanh(x) - x = F.sigmoid(x) - - return x - - -class OcclusionAwareSPADEGenerator(nn.Module): - - def __init__(self, image_channel, feature_channel, num_kp, block_expansion, max_features, num_down_blocks, reshape_channel, reshape_depth, - num_resblocks, estimate_occlusion_map=False, dense_motion_params=None, estimate_jacobian=False): - super(OcclusionAwareSPADEGenerator, self).__init__() - - if dense_motion_params is not None: - self.dense_motion_network = DenseMotionNetwork(num_kp=num_kp, feature_channel=feature_channel, - estimate_occlusion_map=estimate_occlusion_map, - **dense_motion_params) - else: - self.dense_motion_network = None - - self.first = SameBlock2d(image_channel, block_expansion, kernel_size=(3, 3), padding=(1, 1)) - - down_blocks = [] - for i in range(num_down_blocks): - in_features = min(max_features, block_expansion * (2 ** i)) - out_features = min(max_features, block_expansion * (2 ** (i + 1))) - down_blocks.append(DownBlock2d(in_features, out_features, kernel_size=(3, 3), padding=(1, 1))) - self.down_blocks = nn.ModuleList(down_blocks) - - self.second = nn.Conv2d(in_channels=out_features, out_channels=max_features, kernel_size=1, stride=1) - - self.reshape_channel = reshape_channel - self.reshape_depth = reshape_depth - - self.resblocks_3d = torch.nn.Sequential() - for i in range(num_resblocks): - self.resblocks_3d.add_module('3dr' + str(i), ResBlock3d(reshape_channel, kernel_size=3, padding=1)) - - out_features = block_expansion * (2 ** (num_down_blocks)) - self.third = SameBlock2d(max_features, out_features, kernel_size=(3, 3), padding=(1, 1), lrelu=True) - self.fourth = nn.Conv2d(in_channels=out_features, out_channels=out_features, kernel_size=1, stride=1) - - self.estimate_occlusion_map = estimate_occlusion_map - self.image_channel = image_channel - - self.decoder = SPADEDecoder() - - def deform_input(self, inp, deformation): - _, d_old, h_old, w_old, _ = deformation.shape - _, _, d, h, w = inp.shape - if d_old != d or h_old != h or w_old != w: - deformation = deformation.permute(0, 4, 1, 2, 3) - deformation = F.interpolate(deformation, size=(d, h, w), mode='trilinear') - deformation = deformation.permute(0, 2, 3, 4, 1) - return F.grid_sample(inp, deformation) - - def forward(self, source_image, kp_driving, kp_source): - # Encoding (downsampling) part - out = self.first(source_image) - for i in range(len(self.down_blocks)): - out = self.down_blocks[i](out) - out = self.second(out) - bs, c, h, w = out.shape - # print(out.shape) - feature_3d = out.view(bs, self.reshape_channel, self.reshape_depth, h ,w) - feature_3d = self.resblocks_3d(feature_3d) - - # Transforming feature representation according to deformation and occlusion - output_dict = {} - if self.dense_motion_network is not None: - dense_motion = self.dense_motion_network(feature=feature_3d, kp_driving=kp_driving, - kp_source=kp_source) - output_dict['mask'] = dense_motion['mask'] - - # import pdb; pdb.set_trace() - - if 'occlusion_map' in dense_motion: - occlusion_map = dense_motion['occlusion_map'] - output_dict['occlusion_map'] = occlusion_map - else: - occlusion_map = None - deformation = dense_motion['deformation'] - out = self.deform_input(feature_3d, deformation) - - bs, c, d, h, w = out.shape - out = out.view(bs, c*d, h, w) - out = self.third(out) - out = self.fourth(out) - - # occlusion_map = torch.where(occlusion_map < 0.95, 0, occlusion_map) - - if occlusion_map is not None: - if out.shape[2] != occlusion_map.shape[2] or out.shape[3] != occlusion_map.shape[3]: - occlusion_map = F.interpolate(occlusion_map, size=out.shape[2:], mode='bilinear') - out = out * occlusion_map - - # Decoding part - out = self.decoder(out) - - output_dict["prediction"] = out - - return output_dict \ No newline at end of file diff --git a/src/facerender/modules/keypoint_detector.py b/src/facerender/modules/keypoint_detector.py deleted file mode 100644 index c8d325d..0000000 --- a/src/facerender/modules/keypoint_detector.py +++ /dev/null @@ -1,179 +0,0 @@ -from torch import nn -import torch -import torch.nn.functional as F - -from custom_nodes.ComfyUI_Lam.src.facerender.sync_batchnorm import SynchronizedBatchNorm2d as BatchNorm2d -from custom_nodes.ComfyUI_Lam.src.facerender.modules.util import KPHourglass, make_coordinate_grid, AntiAliasInterpolation2d, ResBottleneck - - -class KPDetector(nn.Module): - """ - Detecting canonical keypoints. Return keypoint position and jacobian near each keypoint. - """ - - def __init__(self, block_expansion, feature_channel, num_kp, image_channel, max_features, reshape_channel, reshape_depth, - num_blocks, temperature, estimate_jacobian=False, scale_factor=1, single_jacobian_map=False): - super(KPDetector, self).__init__() - - self.predictor = KPHourglass(block_expansion, in_features=image_channel, - max_features=max_features, reshape_features=reshape_channel, reshape_depth=reshape_depth, num_blocks=num_blocks) - - # self.kp = nn.Conv3d(in_channels=self.predictor.out_filters, out_channels=num_kp, kernel_size=7, padding=3) - self.kp = nn.Conv3d(in_channels=self.predictor.out_filters, out_channels=num_kp, kernel_size=3, padding=1) - - if estimate_jacobian: - self.num_jacobian_maps = 1 if single_jacobian_map else num_kp - # self.jacobian = nn.Conv3d(in_channels=self.predictor.out_filters, out_channels=9 * self.num_jacobian_maps, kernel_size=7, padding=3) - self.jacobian = nn.Conv3d(in_channels=self.predictor.out_filters, out_channels=9 * self.num_jacobian_maps, kernel_size=3, padding=1) - ''' - initial as: - [[1 0 0] - [0 1 0] - [0 0 1]] - ''' - self.jacobian.weight.data.zero_() - self.jacobian.bias.data.copy_(torch.tensor([1, 0, 0, 0, 1, 0, 0, 0, 1] * self.num_jacobian_maps, dtype=torch.float)) - else: - self.jacobian = None - - self.temperature = temperature - self.scale_factor = scale_factor - if self.scale_factor != 1: - self.down = AntiAliasInterpolation2d(image_channel, self.scale_factor) - - def gaussian2kp(self, heatmap): - """ - Extract the mean from a heatmap - """ - shape = heatmap.shape - heatmap = heatmap.unsqueeze(-1) - grid = make_coordinate_grid(shape[2:], heatmap.type()).unsqueeze_(0).unsqueeze_(0) - value = (heatmap * grid).sum(dim=(2, 3, 4)) - kp = {'value': value} - - return kp - - def forward(self, x): - if self.scale_factor != 1: - x = self.down(x) - - feature_map = self.predictor(x) - prediction = self.kp(feature_map) - - final_shape = prediction.shape - heatmap = prediction.view(final_shape[0], final_shape[1], -1) - heatmap = F.softmax(heatmap / self.temperature, dim=2) - heatmap = heatmap.view(*final_shape) - - out = self.gaussian2kp(heatmap) - - if self.jacobian is not None: - jacobian_map = self.jacobian(feature_map) - jacobian_map = jacobian_map.reshape(final_shape[0], self.num_jacobian_maps, 9, final_shape[2], - final_shape[3], final_shape[4]) - heatmap = heatmap.unsqueeze(2) - - jacobian = heatmap * jacobian_map - jacobian = jacobian.view(final_shape[0], final_shape[1], 9, -1) - jacobian = jacobian.sum(dim=-1) - jacobian = jacobian.view(jacobian.shape[0], jacobian.shape[1], 3, 3) - out['jacobian'] = jacobian - - return out - - -class HEEstimator(nn.Module): - """ - Estimating head pose and expression. - """ - - def __init__(self, block_expansion, feature_channel, num_kp, image_channel, max_features, num_bins=66, estimate_jacobian=True): - super(HEEstimator, self).__init__() - - self.conv1 = nn.Conv2d(in_channels=image_channel, out_channels=block_expansion, kernel_size=7, padding=3, stride=2) - self.norm1 = BatchNorm2d(block_expansion, affine=True) - self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1) - - self.conv2 = nn.Conv2d(in_channels=block_expansion, out_channels=256, kernel_size=1) - self.norm2 = BatchNorm2d(256, affine=True) - - self.block1 = nn.Sequential() - for i in range(3): - self.block1.add_module('b1_'+ str(i), ResBottleneck(in_features=256, stride=1)) - - self.conv3 = nn.Conv2d(in_channels=256, out_channels=512, kernel_size=1) - self.norm3 = BatchNorm2d(512, affine=True) - self.block2 = ResBottleneck(in_features=512, stride=2) - - self.block3 = nn.Sequential() - for i in range(3): - self.block3.add_module('b3_'+ str(i), ResBottleneck(in_features=512, stride=1)) - - self.conv4 = nn.Conv2d(in_channels=512, out_channels=1024, kernel_size=1) - self.norm4 = BatchNorm2d(1024, affine=True) - self.block4 = ResBottleneck(in_features=1024, stride=2) - - self.block5 = nn.Sequential() - for i in range(5): - self.block5.add_module('b5_'+ str(i), ResBottleneck(in_features=1024, stride=1)) - - self.conv5 = nn.Conv2d(in_channels=1024, out_channels=2048, kernel_size=1) - self.norm5 = BatchNorm2d(2048, affine=True) - self.block6 = ResBottleneck(in_features=2048, stride=2) - - self.block7 = nn.Sequential() - for i in range(2): - self.block7.add_module('b7_'+ str(i), ResBottleneck(in_features=2048, stride=1)) - - self.fc_roll = nn.Linear(2048, num_bins) - self.fc_pitch = nn.Linear(2048, num_bins) - self.fc_yaw = nn.Linear(2048, num_bins) - - self.fc_t = nn.Linear(2048, 3) - - self.fc_exp = nn.Linear(2048, 3*num_kp) - - def forward(self, x): - out = self.conv1(x) - out = self.norm1(out) - out = F.relu(out) - out = self.maxpool(out) - - out = self.conv2(out) - out = self.norm2(out) - out = F.relu(out) - - out = self.block1(out) - - out = self.conv3(out) - out = self.norm3(out) - out = F.relu(out) - out = self.block2(out) - - out = self.block3(out) - - out = self.conv4(out) - out = self.norm4(out) - out = F.relu(out) - out = self.block4(out) - - out = self.block5(out) - - out = self.conv5(out) - out = self.norm5(out) - out = F.relu(out) - out = self.block6(out) - - out = self.block7(out) - - out = F.adaptive_avg_pool2d(out, 1) - out = out.view(out.shape[0], -1) - - yaw = self.fc_roll(out) - pitch = self.fc_pitch(out) - roll = self.fc_yaw(out) - t = self.fc_t(out) - exp = self.fc_exp(out) - - return {'yaw': yaw, 'pitch': pitch, 'roll': roll, 't': t, 'exp': exp} - diff --git a/src/facerender/modules/make_animation.py b/src/facerender/modules/make_animation.py deleted file mode 100644 index 3360c53..0000000 --- a/src/facerender/modules/make_animation.py +++ /dev/null @@ -1,170 +0,0 @@ -from scipy.spatial import ConvexHull -import torch -import torch.nn.functional as F -import numpy as np -from tqdm import tqdm - -def normalize_kp(kp_source, kp_driving, kp_driving_initial, adapt_movement_scale=False, - use_relative_movement=False, use_relative_jacobian=False): - if adapt_movement_scale: - source_area = ConvexHull(kp_source['value'][0].data.cpu().numpy()).volume - driving_area = ConvexHull(kp_driving_initial['value'][0].data.cpu().numpy()).volume - adapt_movement_scale = np.sqrt(source_area) / np.sqrt(driving_area) - else: - adapt_movement_scale = 1 - - kp_new = {k: v for k, v in kp_driving.items()} - - if use_relative_movement: - kp_value_diff = (kp_driving['value'] - kp_driving_initial['value']) - kp_value_diff *= adapt_movement_scale - kp_new['value'] = kp_value_diff + kp_source['value'] - - if use_relative_jacobian: - jacobian_diff = torch.matmul(kp_driving['jacobian'], torch.inverse(kp_driving_initial['jacobian'])) - kp_new['jacobian'] = torch.matmul(jacobian_diff, kp_source['jacobian']) - - return kp_new - -def headpose_pred_to_degree(pred): - device = pred.device - idx_tensor = [idx for idx in range(66)] - idx_tensor = torch.FloatTensor(idx_tensor).type_as(pred).to(device) - pred = F.softmax(pred) - degree = torch.sum(pred*idx_tensor, 1) * 3 - 99 - return degree - -def get_rotation_matrix(yaw, pitch, roll): - yaw = yaw / 180 * 3.14 - pitch = pitch / 180 * 3.14 - roll = roll / 180 * 3.14 - - roll = roll.unsqueeze(1) - pitch = pitch.unsqueeze(1) - yaw = yaw.unsqueeze(1) - - pitch_mat = torch.cat([torch.ones_like(pitch), torch.zeros_like(pitch), torch.zeros_like(pitch), - torch.zeros_like(pitch), torch.cos(pitch), -torch.sin(pitch), - torch.zeros_like(pitch), torch.sin(pitch), torch.cos(pitch)], dim=1) - pitch_mat = pitch_mat.view(pitch_mat.shape[0], 3, 3) - - yaw_mat = torch.cat([torch.cos(yaw), torch.zeros_like(yaw), torch.sin(yaw), - torch.zeros_like(yaw), torch.ones_like(yaw), torch.zeros_like(yaw), - -torch.sin(yaw), torch.zeros_like(yaw), torch.cos(yaw)], dim=1) - yaw_mat = yaw_mat.view(yaw_mat.shape[0], 3, 3) - - roll_mat = torch.cat([torch.cos(roll), -torch.sin(roll), torch.zeros_like(roll), - torch.sin(roll), torch.cos(roll), torch.zeros_like(roll), - torch.zeros_like(roll), torch.zeros_like(roll), torch.ones_like(roll)], dim=1) - roll_mat = roll_mat.view(roll_mat.shape[0], 3, 3) - - rot_mat = torch.einsum('bij,bjk,bkm->bim', pitch_mat, yaw_mat, roll_mat) - - return rot_mat - -def keypoint_transformation(kp_canonical, he, wo_exp=False): - kp = kp_canonical['value'] # (bs, k, 3) - yaw, pitch, roll= he['yaw'], he['pitch'], he['roll'] - yaw = headpose_pred_to_degree(yaw) - pitch = headpose_pred_to_degree(pitch) - roll = headpose_pred_to_degree(roll) - - if 'yaw_in' in he: - yaw = he['yaw_in'] - if 'pitch_in' in he: - pitch = he['pitch_in'] - if 'roll_in' in he: - roll = he['roll_in'] - - rot_mat = get_rotation_matrix(yaw, pitch, roll) # (bs, 3, 3) - - t, exp = he['t'], he['exp'] - if wo_exp: - exp = exp*0 - - # keypoint rotation - kp_rotated = torch.einsum('bmp,bkp->bkm', rot_mat, kp) - - # keypoint translation - t[:, 0] = t[:, 0]*0 - t[:, 2] = t[:, 2]*0 - t = t.unsqueeze(1).repeat(1, kp.shape[1], 1) - kp_t = kp_rotated + t - - # add expression deviation - exp = exp.view(exp.shape[0], -1, 3) - kp_transformed = kp_t + exp - - return {'value': kp_transformed} - - - -def make_animation(source_image, source_semantics, target_semantics, - generator, kp_detector, he_estimator, mapping, - yaw_c_seq=None, pitch_c_seq=None, roll_c_seq=None, - use_exp=True, use_half=False): - with torch.no_grad(): - predictions = [] - - kp_canonical = kp_detector(source_image) - he_source = mapping(source_semantics) - kp_source = keypoint_transformation(kp_canonical, he_source) - - for frame_idx in tqdm(range(target_semantics.shape[1]), 'Face Renderer:'): - # still check the dimension - # print(target_semantics.shape, source_semantics.shape) - target_semantics_frame = target_semantics[:, frame_idx] - he_driving = mapping(target_semantics_frame) - if yaw_c_seq is not None: - he_driving['yaw_in'] = yaw_c_seq[:, frame_idx] - if pitch_c_seq is not None: - he_driving['pitch_in'] = pitch_c_seq[:, frame_idx] - if roll_c_seq is not None: - he_driving['roll_in'] = roll_c_seq[:, frame_idx] - - kp_driving = keypoint_transformation(kp_canonical, he_driving) - - kp_norm = kp_driving - out = generator(source_image, kp_source=kp_source, kp_driving=kp_norm) - ''' - source_image_new = out['prediction'].squeeze(1) - kp_canonical_new = kp_detector(source_image_new) - he_source_new = he_estimator(source_image_new) - kp_source_new = keypoint_transformation(kp_canonical_new, he_source_new, wo_exp=True) - kp_driving_new = keypoint_transformation(kp_canonical_new, he_driving, wo_exp=True) - out = generator(source_image_new, kp_source=kp_source_new, kp_driving=kp_driving_new) - ''' - predictions.append(out['prediction']) - predictions_ts = torch.stack(predictions, dim=1) - return predictions_ts - -class AnimateModel(torch.nn.Module): - """ - Merge all generator related updates into single model for better multi-gpu usage - """ - - def __init__(self, generator, kp_extractor, mapping): - super(AnimateModel, self).__init__() - self.kp_extractor = kp_extractor - self.generator = generator - self.mapping = mapping - - self.kp_extractor.eval() - self.generator.eval() - self.mapping.eval() - - def forward(self, x): - - source_image = x['source_image'] - source_semantics = x['source_semantics'] - target_semantics = x['target_semantics'] - yaw_c_seq = x['yaw_c_seq'] - pitch_c_seq = x['pitch_c_seq'] - roll_c_seq = x['roll_c_seq'] - - predictions_video = make_animation(source_image, source_semantics, target_semantics, - self.generator, self.kp_extractor, - self.mapping, use_exp = True, - yaw_c_seq=yaw_c_seq, pitch_c_seq=pitch_c_seq, roll_c_seq=roll_c_seq) - - return predictions_video \ No newline at end of file diff --git a/src/facerender/modules/make_animation_two.py b/src/facerender/modules/make_animation_two.py deleted file mode 100644 index 89eccb3..0000000 --- a/src/facerender/modules/make_animation_two.py +++ /dev/null @@ -1,127 +0,0 @@ -from scipy.spatial import ConvexHull -import torch -import torch.nn.functional as F -import numpy as np -from tqdm import tqdm - - -def normalize_kp(kp_source, kp_driving, kp_driving_initial, adapt_movement_scale=False, - use_relative_movement=False, use_relative_jacobian=False): - if adapt_movement_scale: - source_area = ConvexHull(kp_source['value'][0].data.cpu().numpy()).volume - driving_area = ConvexHull(kp_driving_initial['value'][0].data.cpu().numpy()).volume - adapt_movement_scale = np.sqrt(source_area) / np.sqrt(driving_area) - else: - adapt_movement_scale = 1 - - kp_new = {k: v for k, v in kp_driving.items()} - - if use_relative_movement: - kp_value_diff = (kp_driving['value'] - kp_driving_initial['value']) - kp_value_diff *= adapt_movement_scale - kp_new['value'] = kp_value_diff + kp_source['value'] - - if use_relative_jacobian: - jacobian_diff = torch.matmul(kp_driving['jacobian'], torch.inverse(kp_driving_initial['jacobian'])) - kp_new['jacobian'] = torch.matmul(jacobian_diff, kp_source['jacobian']) - - return kp_new - - -def headpose_pred_to_degree(pred): - device = pred.device - idx_tensor = [idx for idx in range(66)] - idx_tensor = torch.FloatTensor(idx_tensor).to(device) - pred = F.softmax(pred) - degree = torch.sum(pred * idx_tensor, 1) * 3 - 99 - return degree - - -def get_rotation_matrix(yaw, pitch, roll): - yaw = yaw / 180 * 3.14 - pitch = pitch / 180 * 3.14 - roll = roll / 180 * 3.14 - - roll = roll.unsqueeze(1) - pitch = pitch.unsqueeze(1) - yaw = yaw.unsqueeze(1) - - pitch_mat = torch.cat([torch.ones_like(pitch), torch.zeros_like(pitch), torch.zeros_like(pitch), - torch.zeros_like(pitch), torch.cos(pitch), -torch.sin(pitch), - torch.zeros_like(pitch), torch.sin(pitch), torch.cos(pitch)], dim=1) - pitch_mat = pitch_mat.view(pitch_mat.shape[0], 3, 3) - - yaw_mat = torch.cat([torch.cos(yaw), torch.zeros_like(yaw), torch.sin(yaw), - torch.zeros_like(yaw), torch.ones_like(yaw), torch.zeros_like(yaw), - -torch.sin(yaw), torch.zeros_like(yaw), torch.cos(yaw)], dim=1) - yaw_mat = yaw_mat.view(yaw_mat.shape[0], 3, 3) - - roll_mat = torch.cat([torch.cos(roll), -torch.sin(roll), torch.zeros_like(roll), - torch.sin(roll), torch.cos(roll), torch.zeros_like(roll), - torch.zeros_like(roll), torch.zeros_like(roll), torch.ones_like(roll)], dim=1) - roll_mat = roll_mat.view(roll_mat.shape[0], 3, 3) - - rot_mat = torch.einsum('bij,bjk,bkm->bim', pitch_mat, yaw_mat, roll_mat) - - return rot_mat - - -def keypoint_transformation(kp_canonical, he, wo_exp=False): - kp = kp_canonical['value'] # (bs, k, 3) - yaw, pitch, roll = he['yaw'], he['pitch'], he['roll'] - yaw = headpose_pred_to_degree(yaw) - pitch = headpose_pred_to_degree(pitch) - roll = headpose_pred_to_degree(roll) - - if 'yaw_in' in he: - yaw = he['yaw_in'] - if 'pitch_in' in he: - pitch = he['pitch_in'] - if 'roll_in' in he: - roll = he['roll_in'] - - rot_mat = get_rotation_matrix(yaw, pitch, roll) # (bs, 3, 3) - - t, exp = he['t'], he['exp'] - if wo_exp: - exp = exp * 0 - - # keypoint rotation - kp_rotated = torch.einsum('bmp,bkp->bkm', rot_mat, kp) - - # keypoint translation - t[:, 0] = t[:, 0] * 0 - t[:, 2] = t[:, 2] * 0 - t = t.unsqueeze(1).repeat(1, kp.shape[1], 1) - kp_t = kp_rotated + t - - # add expression deviation - exp = exp.view(exp.shape[0], -1, 3) - kp_transformed = kp_t + exp - - return {'value': kp_transformed} - - -def make_animation(source_image, source_semantics, target_semantics, generator, kp_detector, mapping, frame_num): - with torch.no_grad(): - predictions = [] - kp_canonical = [] - kp_source = [] - for single_image in source_image: - kp_single = kp_detector(single_image) - kp_canonical.append(kp_single) - he_source_single = mapping(source_semantics) - kp_single = keypoint_transformation(kp_single, he_source_single) - kp_source.append(kp_single) - - for frame_idx in tqdm(range(target_semantics.shape[1]), 'Face Renderer:'): - if frame_idx >= frame_num: - break - target_semantics_frame = target_semantics[:, frame_idx] - he_driving = mapping(target_semantics_frame) - kp_driving = keypoint_transformation(kp_canonical[frame_idx], he_driving) - kp_norm = kp_driving - out = generator(source_image[frame_idx], kp_source=kp_source[frame_idx], kp_driving=kp_norm) - predictions.append(out['prediction']) - predictions_ts = torch.stack(predictions, dim=1) - return predictions_ts diff --git a/src/facerender/modules/mapping.py b/src/facerender/modules/mapping.py deleted file mode 100644 index 0e3a1c2..0000000 --- a/src/facerender/modules/mapping.py +++ /dev/null @@ -1,47 +0,0 @@ -import numpy as np - -import torch -import torch.nn as nn -import torch.nn.functional as F - - -class MappingNet(nn.Module): - def __init__(self, coeff_nc, descriptor_nc, layer, num_kp, num_bins): - super( MappingNet, self).__init__() - - self.layer = layer - nonlinearity = nn.LeakyReLU(0.1) - - self.first = nn.Sequential( - torch.nn.Conv1d(coeff_nc, descriptor_nc, kernel_size=7, padding=0, bias=True)) - - for i in range(layer): - net = nn.Sequential(nonlinearity, - torch.nn.Conv1d(descriptor_nc, descriptor_nc, kernel_size=3, padding=0, dilation=3)) - setattr(self, 'encoder' + str(i), net) - - self.pooling = nn.AdaptiveAvgPool1d(1) - self.output_nc = descriptor_nc - - self.fc_roll = nn.Linear(descriptor_nc, num_bins) - self.fc_pitch = nn.Linear(descriptor_nc, num_bins) - self.fc_yaw = nn.Linear(descriptor_nc, num_bins) - self.fc_t = nn.Linear(descriptor_nc, 3) - self.fc_exp = nn.Linear(descriptor_nc, 3*num_kp) - - def forward(self, input_3dmm): - out = self.first(input_3dmm) - for i in range(self.layer): - model = getattr(self, 'encoder' + str(i)) - out = model(out) + out[:,:,3:-3] - out = self.pooling(out) - out = out.view(out.shape[0], -1) - #print('out:', out.shape) - - yaw = self.fc_yaw(out) - pitch = self.fc_pitch(out) - roll = self.fc_roll(out) - t = self.fc_t(out) - exp = self.fc_exp(out) - - return {'yaw': yaw, 'pitch': pitch, 'roll': roll, 't': t, 'exp': exp} \ No newline at end of file diff --git a/src/facerender/modules/util.py b/src/facerender/modules/util.py deleted file mode 100644 index b048aa2..0000000 --- a/src/facerender/modules/util.py +++ /dev/null @@ -1,564 +0,0 @@ -from torch import nn - -import torch.nn.functional as F -import torch - -from custom_nodes.ComfyUI_Lam.src.facerender.sync_batchnorm import SynchronizedBatchNorm2d as BatchNorm2d -from custom_nodes.ComfyUI_Lam.src.facerender.sync_batchnorm import SynchronizedBatchNorm3d as BatchNorm3d - -import torch.nn.utils.spectral_norm as spectral_norm - - -def kp2gaussian(kp, spatial_size, kp_variance): - """ - Transform a keypoint into gaussian like representation - """ - mean = kp['value'] - - coordinate_grid = make_coordinate_grid(spatial_size, mean.type()) - number_of_leading_dimensions = len(mean.shape) - 1 - shape = (1,) * number_of_leading_dimensions + coordinate_grid.shape - coordinate_grid = coordinate_grid.view(*shape) - repeats = mean.shape[:number_of_leading_dimensions] + (1, 1, 1, 1) - coordinate_grid = coordinate_grid.repeat(*repeats) - - # Preprocess kp shape - shape = mean.shape[:number_of_leading_dimensions] + (1, 1, 1, 3) - mean = mean.view(*shape) - - mean_sub = (coordinate_grid - mean) - - out = torch.exp(-0.5 * (mean_sub ** 2).sum(-1) / kp_variance) - - return out - -def make_coordinate_grid_2d(spatial_size, type): - """ - Create a meshgrid [-1,1] x [-1,1] of given spatial_size. - """ - h, w = spatial_size - x = torch.arange(w).type(type) - y = torch.arange(h).type(type) - - x = (2 * (x / (w - 1)) - 1) - y = (2 * (y / (h - 1)) - 1) - - yy = y.view(-1, 1).repeat(1, w) - xx = x.view(1, -1).repeat(h, 1) - - meshed = torch.cat([xx.unsqueeze_(2), yy.unsqueeze_(2)], 2) - - return meshed - - -def make_coordinate_grid(spatial_size, type): - d, h, w = spatial_size - x = torch.arange(w).type(type) - y = torch.arange(h).type(type) - z = torch.arange(d).type(type) - - x = (2 * (x / (w - 1)) - 1) - y = (2 * (y / (h - 1)) - 1) - z = (2 * (z / (d - 1)) - 1) - - yy = y.view(1, -1, 1).repeat(d, 1, w) - xx = x.view(1, 1, -1).repeat(d, h, 1) - zz = z.view(-1, 1, 1).repeat(1, h, w) - - meshed = torch.cat([xx.unsqueeze_(3), yy.unsqueeze_(3), zz.unsqueeze_(3)], 3) - - return meshed - - -class ResBottleneck(nn.Module): - def __init__(self, in_features, stride): - super(ResBottleneck, self).__init__() - self.conv1 = nn.Conv2d(in_channels=in_features, out_channels=in_features//4, kernel_size=1) - self.conv2 = nn.Conv2d(in_channels=in_features//4, out_channels=in_features//4, kernel_size=3, padding=1, stride=stride) - self.conv3 = nn.Conv2d(in_channels=in_features//4, out_channels=in_features, kernel_size=1) - self.norm1 = BatchNorm2d(in_features//4, affine=True) - self.norm2 = BatchNorm2d(in_features//4, affine=True) - self.norm3 = BatchNorm2d(in_features, affine=True) - - self.stride = stride - if self.stride != 1: - self.skip = nn.Conv2d(in_channels=in_features, out_channels=in_features, kernel_size=1, stride=stride) - self.norm4 = BatchNorm2d(in_features, affine=True) - - def forward(self, x): - out = self.conv1(x) - out = self.norm1(out) - out = F.relu(out) - out = self.conv2(out) - out = self.norm2(out) - out = F.relu(out) - out = self.conv3(out) - out = self.norm3(out) - if self.stride != 1: - x = self.skip(x) - x = self.norm4(x) - out += x - out = F.relu(out) - return out - - -class ResBlock2d(nn.Module): - """ - Res block, preserve spatial resolution. - """ - - def __init__(self, in_features, kernel_size, padding): - super(ResBlock2d, self).__init__() - self.conv1 = nn.Conv2d(in_channels=in_features, out_channels=in_features, kernel_size=kernel_size, - padding=padding) - self.conv2 = nn.Conv2d(in_channels=in_features, out_channels=in_features, kernel_size=kernel_size, - padding=padding) - self.norm1 = BatchNorm2d(in_features, affine=True) - self.norm2 = BatchNorm2d(in_features, affine=True) - - def forward(self, x): - out = self.norm1(x) - out = F.relu(out) - out = self.conv1(out) - out = self.norm2(out) - out = F.relu(out) - out = self.conv2(out) - out += x - return out - - -class ResBlock3d(nn.Module): - """ - Res block, preserve spatial resolution. - """ - - def __init__(self, in_features, kernel_size, padding): - super(ResBlock3d, self).__init__() - self.conv1 = nn.Conv3d(in_channels=in_features, out_channels=in_features, kernel_size=kernel_size, - padding=padding) - self.conv2 = nn.Conv3d(in_channels=in_features, out_channels=in_features, kernel_size=kernel_size, - padding=padding) - self.norm1 = BatchNorm3d(in_features, affine=True) - self.norm2 = BatchNorm3d(in_features, affine=True) - - def forward(self, x): - out = self.norm1(x) - out = F.relu(out) - out = self.conv1(out) - out = self.norm2(out) - out = F.relu(out) - out = self.conv2(out) - out += x - return out - - -class UpBlock2d(nn.Module): - """ - Upsampling block for use in decoder. - """ - - def __init__(self, in_features, out_features, kernel_size=3, padding=1, groups=1): - super(UpBlock2d, self).__init__() - - self.conv = nn.Conv2d(in_channels=in_features, out_channels=out_features, kernel_size=kernel_size, - padding=padding, groups=groups) - self.norm = BatchNorm2d(out_features, affine=True) - - def forward(self, x): - out = F.interpolate(x, scale_factor=2) - out = self.conv(out) - out = self.norm(out) - out = F.relu(out) - return out - -class UpBlock3d(nn.Module): - """ - Upsampling block for use in decoder. - """ - - def __init__(self, in_features, out_features, kernel_size=3, padding=1, groups=1): - super(UpBlock3d, self).__init__() - - self.conv = nn.Conv3d(in_channels=in_features, out_channels=out_features, kernel_size=kernel_size, - padding=padding, groups=groups) - self.norm = BatchNorm3d(out_features, affine=True) - - def forward(self, x): - # out = F.interpolate(x, scale_factor=(1, 2, 2), mode='trilinear') - out = F.interpolate(x, scale_factor=(1, 2, 2)) - out = self.conv(out) - out = self.norm(out) - out = F.relu(out) - return out - - -class DownBlock2d(nn.Module): - """ - Downsampling block for use in encoder. - """ - - def __init__(self, in_features, out_features, kernel_size=3, padding=1, groups=1): - super(DownBlock2d, self).__init__() - self.conv = nn.Conv2d(in_channels=in_features, out_channels=out_features, kernel_size=kernel_size, - padding=padding, groups=groups) - self.norm = BatchNorm2d(out_features, affine=True) - self.pool = nn.AvgPool2d(kernel_size=(2, 2)) - - def forward(self, x): - out = self.conv(x) - out = self.norm(out) - out = F.relu(out) - out = self.pool(out) - return out - - -class DownBlock3d(nn.Module): - """ - Downsampling block for use in encoder. - """ - - def __init__(self, in_features, out_features, kernel_size=3, padding=1, groups=1): - super(DownBlock3d, self).__init__() - ''' - self.conv = nn.Conv3d(in_channels=in_features, out_channels=out_features, kernel_size=kernel_size, - padding=padding, groups=groups, stride=(1, 2, 2)) - ''' - self.conv = nn.Conv3d(in_channels=in_features, out_channels=out_features, kernel_size=kernel_size, - padding=padding, groups=groups) - self.norm = BatchNorm3d(out_features, affine=True) - self.pool = nn.AvgPool3d(kernel_size=(1, 2, 2)) - - def forward(self, x): - out = self.conv(x) - out = self.norm(out) - out = F.relu(out) - out = self.pool(out) - return out - - -class SameBlock2d(nn.Module): - """ - Simple block, preserve spatial resolution. - """ - - def __init__(self, in_features, out_features, groups=1, kernel_size=3, padding=1, lrelu=False): - super(SameBlock2d, self).__init__() - self.conv = nn.Conv2d(in_channels=in_features, out_channels=out_features, - kernel_size=kernel_size, padding=padding, groups=groups) - self.norm = BatchNorm2d(out_features, affine=True) - if lrelu: - self.ac = nn.LeakyReLU() - else: - self.ac = nn.ReLU() - - def forward(self, x): - out = self.conv(x) - out = self.norm(out) - out = self.ac(out) - return out - - -class Encoder(nn.Module): - """ - Hourglass Encoder - """ - - def __init__(self, block_expansion, in_features, num_blocks=3, max_features=256): - super(Encoder, self).__init__() - - down_blocks = [] - for i in range(num_blocks): - down_blocks.append(DownBlock3d(in_features if i == 0 else min(max_features, block_expansion * (2 ** i)), - min(max_features, block_expansion * (2 ** (i + 1))), - kernel_size=3, padding=1)) - self.down_blocks = nn.ModuleList(down_blocks) - - def forward(self, x): - outs = [x] - for down_block in self.down_blocks: - outs.append(down_block(outs[-1])) - return outs - - -class Decoder(nn.Module): - """ - Hourglass Decoder - """ - - def __init__(self, block_expansion, in_features, num_blocks=3, max_features=256): - super(Decoder, self).__init__() - - up_blocks = [] - - for i in range(num_blocks)[::-1]: - in_filters = (1 if i == num_blocks - 1 else 2) * min(max_features, block_expansion * (2 ** (i + 1))) - out_filters = min(max_features, block_expansion * (2 ** i)) - up_blocks.append(UpBlock3d(in_filters, out_filters, kernel_size=3, padding=1)) - - self.up_blocks = nn.ModuleList(up_blocks) - # self.out_filters = block_expansion - self.out_filters = block_expansion + in_features - - self.conv = nn.Conv3d(in_channels=self.out_filters, out_channels=self.out_filters, kernel_size=3, padding=1) - self.norm = BatchNorm3d(self.out_filters, affine=True) - - def forward(self, x): - out = x.pop() - # for up_block in self.up_blocks[:-1]: - for up_block in self.up_blocks: - out = up_block(out) - skip = x.pop() - out = torch.cat([out, skip], dim=1) - # out = self.up_blocks[-1](out) - out = self.conv(out) - out = self.norm(out) - out = F.relu(out) - return out - - -class Hourglass(nn.Module): - """ - Hourglass architecture. - """ - - def __init__(self, block_expansion, in_features, num_blocks=3, max_features=256): - super(Hourglass, self).__init__() - self.encoder = Encoder(block_expansion, in_features, num_blocks, max_features) - self.decoder = Decoder(block_expansion, in_features, num_blocks, max_features) - self.out_filters = self.decoder.out_filters - - def forward(self, x): - return self.decoder(self.encoder(x)) - - -class KPHourglass(nn.Module): - """ - Hourglass architecture. - """ - - def __init__(self, block_expansion, in_features, reshape_features, reshape_depth, num_blocks=3, max_features=256): - super(KPHourglass, self).__init__() - - self.down_blocks = nn.Sequential() - for i in range(num_blocks): - self.down_blocks.add_module('down'+ str(i), DownBlock2d(in_features if i == 0 else min(max_features, block_expansion * (2 ** i)), - min(max_features, block_expansion * (2 ** (i + 1))), - kernel_size=3, padding=1)) - - in_filters = min(max_features, block_expansion * (2 ** num_blocks)) - self.conv = nn.Conv2d(in_channels=in_filters, out_channels=reshape_features, kernel_size=1) - - self.up_blocks = nn.Sequential() - for i in range(num_blocks): - in_filters = min(max_features, block_expansion * (2 ** (num_blocks - i))) - out_filters = min(max_features, block_expansion * (2 ** (num_blocks - i - 1))) - self.up_blocks.add_module('up'+ str(i), UpBlock3d(in_filters, out_filters, kernel_size=3, padding=1)) - - self.reshape_depth = reshape_depth - self.out_filters = out_filters - - def forward(self, x): - out = self.down_blocks(x) - out = self.conv(out) - bs, c, h, w = out.shape - out = out.view(bs, c//self.reshape_depth, self.reshape_depth, h, w) - out = self.up_blocks(out) - - return out - - - -class AntiAliasInterpolation2d(nn.Module): - """ - Band-limited downsampling, for better preservation of the input signal. - """ - def __init__(self, channels, scale): - super(AntiAliasInterpolation2d, self).__init__() - sigma = (1 / scale - 1) / 2 - kernel_size = 2 * round(sigma * 4) + 1 - self.ka = kernel_size // 2 - self.kb = self.ka - 1 if kernel_size % 2 == 0 else self.ka - - kernel_size = [kernel_size, kernel_size] - sigma = [sigma, sigma] - # The gaussian kernel is the product of the - # gaussian function of each dimension. - kernel = 1 - meshgrids = torch.meshgrid( - [ - torch.arange(size, dtype=torch.float32) - for size in kernel_size - ] - ) - for size, std, mgrid in zip(kernel_size, sigma, meshgrids): - mean = (size - 1) / 2 - kernel *= torch.exp(-(mgrid - mean) ** 2 / (2 * std ** 2)) - - # Make sure sum of values in gaussian kernel equals 1. - kernel = kernel / torch.sum(kernel) - # Reshape to depthwise convolutional weight - kernel = kernel.view(1, 1, *kernel.size()) - kernel = kernel.repeat(channels, *[1] * (kernel.dim() - 1)) - - self.register_buffer('weight', kernel) - self.groups = channels - self.scale = scale - inv_scale = 1 / scale - self.int_inv_scale = int(inv_scale) - - def forward(self, input): - if self.scale == 1.0: - return input - - out = F.pad(input, (self.ka, self.kb, self.ka, self.kb)) - out = F.conv2d(out, weight=self.weight, groups=self.groups) - out = out[:, :, ::self.int_inv_scale, ::self.int_inv_scale] - - return out - - -class SPADE(nn.Module): - def __init__(self, norm_nc, label_nc): - super().__init__() - - self.param_free_norm = nn.InstanceNorm2d(norm_nc, affine=False) - nhidden = 128 - - self.mlp_shared = nn.Sequential( - nn.Conv2d(label_nc, nhidden, kernel_size=3, padding=1), - nn.ReLU()) - self.mlp_gamma = nn.Conv2d(nhidden, norm_nc, kernel_size=3, padding=1) - self.mlp_beta = nn.Conv2d(nhidden, norm_nc, kernel_size=3, padding=1) - - def forward(self, x, segmap): - normalized = self.param_free_norm(x) - segmap = F.interpolate(segmap, size=x.size()[2:], mode='nearest') - actv = self.mlp_shared(segmap) - gamma = self.mlp_gamma(actv) - beta = self.mlp_beta(actv) - out = normalized * (1 + gamma) + beta - return out - - -class SPADEResnetBlock(nn.Module): - def __init__(self, fin, fout, norm_G, label_nc, use_se=False, dilation=1): - super().__init__() - # Attributes - self.learned_shortcut = (fin != fout) - fmiddle = min(fin, fout) - self.use_se = use_se - # create conv layers - self.conv_0 = nn.Conv2d(fin, fmiddle, kernel_size=3, padding=dilation, dilation=dilation) - self.conv_1 = nn.Conv2d(fmiddle, fout, kernel_size=3, padding=dilation, dilation=dilation) - if self.learned_shortcut: - self.conv_s = nn.Conv2d(fin, fout, kernel_size=1, bias=False) - # apply spectral norm if specified - if 'spectral' in norm_G: - self.conv_0 = spectral_norm(self.conv_0) - self.conv_1 = spectral_norm(self.conv_1) - if self.learned_shortcut: - self.conv_s = spectral_norm(self.conv_s) - # define normalization layers - self.norm_0 = SPADE(fin, label_nc) - self.norm_1 = SPADE(fmiddle, label_nc) - if self.learned_shortcut: - self.norm_s = SPADE(fin, label_nc) - - def forward(self, x, seg1): - x_s = self.shortcut(x, seg1) - dx = self.conv_0(self.actvn(self.norm_0(x, seg1))) - dx = self.conv_1(self.actvn(self.norm_1(dx, seg1))) - out = x_s + dx - return out - - def shortcut(self, x, seg1): - if self.learned_shortcut: - x_s = self.conv_s(self.norm_s(x, seg1)) - else: - x_s = x - return x_s - - def actvn(self, x): - return F.leaky_relu(x, 2e-1) - -class audio2image(nn.Module): - def __init__(self, generator, kp_extractor, he_estimator_video, he_estimator_audio, train_params): - super().__init__() - # Attributes - self.generator = generator - self.kp_extractor = kp_extractor - self.he_estimator_video = he_estimator_video - self.he_estimator_audio = he_estimator_audio - self.train_params = train_params - - def headpose_pred_to_degree(self, pred): - device = pred.device - idx_tensor = [idx for idx in range(66)] - idx_tensor = torch.FloatTensor(idx_tensor).to(device) - pred = F.softmax(pred) - degree = torch.sum(pred*idx_tensor, 1) * 3 - 99 - - return degree - - def get_rotation_matrix(self, yaw, pitch, roll): - yaw = yaw / 180 * 3.14 - pitch = pitch / 180 * 3.14 - roll = roll / 180 * 3.14 - - roll = roll.unsqueeze(1) - pitch = pitch.unsqueeze(1) - yaw = yaw.unsqueeze(1) - - roll_mat = torch.cat([torch.ones_like(roll), torch.zeros_like(roll), torch.zeros_like(roll), - torch.zeros_like(roll), torch.cos(roll), -torch.sin(roll), - torch.zeros_like(roll), torch.sin(roll), torch.cos(roll)], dim=1) - roll_mat = roll_mat.view(roll_mat.shape[0], 3, 3) - - pitch_mat = torch.cat([torch.cos(pitch), torch.zeros_like(pitch), torch.sin(pitch), - torch.zeros_like(pitch), torch.ones_like(pitch), torch.zeros_like(pitch), - -torch.sin(pitch), torch.zeros_like(pitch), torch.cos(pitch)], dim=1) - pitch_mat = pitch_mat.view(pitch_mat.shape[0], 3, 3) - - yaw_mat = torch.cat([torch.cos(yaw), -torch.sin(yaw), torch.zeros_like(yaw), - torch.sin(yaw), torch.cos(yaw), torch.zeros_like(yaw), - torch.zeros_like(yaw), torch.zeros_like(yaw), torch.ones_like(yaw)], dim=1) - yaw_mat = yaw_mat.view(yaw_mat.shape[0], 3, 3) - - rot_mat = torch.einsum('bij,bjk,bkm->bim', roll_mat, pitch_mat, yaw_mat) - - return rot_mat - - def keypoint_transformation(self, kp_canonical, he): - kp = kp_canonical['value'] # (bs, k, 3) - yaw, pitch, roll = he['yaw'], he['pitch'], he['roll'] - t, exp = he['t'], he['exp'] - - yaw = self.headpose_pred_to_degree(yaw) - pitch = self.headpose_pred_to_degree(pitch) - roll = self.headpose_pred_to_degree(roll) - - rot_mat = self.get_rotation_matrix(yaw, pitch, roll) # (bs, 3, 3) - - # keypoint rotation - kp_rotated = torch.einsum('bmp,bkp->bkm', rot_mat, kp) - - - - # keypoint translation - t = t.unsqueeze_(1).repeat(1, kp.shape[1], 1) - kp_t = kp_rotated + t - - # add expression deviation - exp = exp.view(exp.shape[0], -1, 3) - kp_transformed = kp_t + exp - - return {'value': kp_transformed} - - def forward(self, source_image, target_audio): - pose_source = self.he_estimator_video(source_image) - pose_generated = self.he_estimator_audio(target_audio) - kp_canonical = self.kp_extractor(source_image) - kp_source = self.keypoint_transformation(kp_canonical, pose_source) - kp_transformed_generated = self.keypoint_transformation(kp_canonical, pose_generated) - generated = self.generator(source_image, kp_source=kp_source, kp_driving=kp_transformed_generated) - return generated \ No newline at end of file diff --git a/src/facerender/sync_batchnorm/__init__.py b/src/facerender/sync_batchnorm/__init__.py deleted file mode 100644 index bc8709d..0000000 --- a/src/facerender/sync_batchnorm/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -# -*- coding: utf-8 -*- -# File : __init__.py -# Author : Jiayuan Mao -# Email : maojiayuan@gmail.com -# Date : 27/01/2018 -# -# This file is part of Synchronized-BatchNorm-PyTorch. -# https://github.com/vacancy/Synchronized-BatchNorm-PyTorch -# Distributed under MIT License. - -from .batchnorm import SynchronizedBatchNorm1d, SynchronizedBatchNorm2d, SynchronizedBatchNorm3d -from .replicate import DataParallelWithCallback, patch_replication_callback diff --git a/src/facerender/sync_batchnorm/batchnorm.py b/src/facerender/sync_batchnorm/batchnorm.py deleted file mode 100644 index 5f4e763..0000000 --- a/src/facerender/sync_batchnorm/batchnorm.py +++ /dev/null @@ -1,315 +0,0 @@ -# -*- coding: utf-8 -*- -# File : batchnorm.py -# Author : Jiayuan Mao -# Email : maojiayuan@gmail.com -# Date : 27/01/2018 -# -# This file is part of Synchronized-BatchNorm-PyTorch. -# https://github.com/vacancy/Synchronized-BatchNorm-PyTorch -# Distributed under MIT License. - -import collections - -import torch -import torch.nn.functional as F - -from torch.nn.modules.batchnorm import _BatchNorm -from torch.nn.parallel._functions import ReduceAddCoalesced, Broadcast - -from .comm import SyncMaster - -__all__ = ['SynchronizedBatchNorm1d', 'SynchronizedBatchNorm2d', 'SynchronizedBatchNorm3d'] - - -def _sum_ft(tensor): - """sum over the first and last dimention""" - return tensor.sum(dim=0).sum(dim=-1) - - -def _unsqueeze_ft(tensor): - """add new dementions at the front and the tail""" - return tensor.unsqueeze(0).unsqueeze(-1) - - -_ChildMessage = collections.namedtuple('_ChildMessage', ['sum', 'ssum', 'sum_size']) -_MasterMessage = collections.namedtuple('_MasterMessage', ['sum', 'inv_std']) - - -class _SynchronizedBatchNorm(_BatchNorm): - def __init__(self, num_features, eps=1e-5, momentum=0.1, affine=True): - super(_SynchronizedBatchNorm, self).__init__(num_features, eps=eps, momentum=momentum, affine=affine) - - self._sync_master = SyncMaster(self._data_parallel_master) - - self._is_parallel = False - self._parallel_id = None - self._slave_pipe = None - - def forward(self, input): - # If it is not parallel computation or is in evaluation mode, use PyTorch's implementation. - if not (self._is_parallel and self.training): - return F.batch_norm( - input, self.running_mean, self.running_var, self.weight, self.bias, - self.training, self.momentum, self.eps) - - # Resize the input to (B, C, -1). - input_shape = input.size() - input = input.view(input.size(0), self.num_features, -1) - - # Compute the sum and square-sum. - sum_size = input.size(0) * input.size(2) - input_sum = _sum_ft(input) - input_ssum = _sum_ft(input ** 2) - - # Reduce-and-broadcast the statistics. - if self._parallel_id == 0: - mean, inv_std = self._sync_master.run_master(_ChildMessage(input_sum, input_ssum, sum_size)) - else: - mean, inv_std = self._slave_pipe.run_slave(_ChildMessage(input_sum, input_ssum, sum_size)) - - # Compute the output. - if self.affine: - # MJY:: Fuse the multiplication for speed. - output = (input - _unsqueeze_ft(mean)) * _unsqueeze_ft(inv_std * self.weight) + _unsqueeze_ft(self.bias) - else: - output = (input - _unsqueeze_ft(mean)) * _unsqueeze_ft(inv_std) - - # Reshape it. - return output.view(input_shape) - - def __data_parallel_replicate__(self, ctx, copy_id): - self._is_parallel = True - self._parallel_id = copy_id - - # parallel_id == 0 means master device. - if self._parallel_id == 0: - ctx.sync_master = self._sync_master - else: - self._slave_pipe = ctx.sync_master.register_slave(copy_id) - - def _data_parallel_master(self, intermediates): - """Reduce the sum and square-sum, compute the statistics, and broadcast it.""" - - # Always using same "device order" makes the ReduceAdd operation faster. - # Thanks to:: Tete Xiao (http://tetexiao.com/) - intermediates = sorted(intermediates, key=lambda i: i[1].sum.get_device()) - - to_reduce = [i[1][:2] for i in intermediates] - to_reduce = [j for i in to_reduce for j in i] # flatten - target_gpus = [i[1].sum.get_device() for i in intermediates] - - sum_size = sum([i[1].sum_size for i in intermediates]) - sum_, ssum = ReduceAddCoalesced.apply(target_gpus[0], 2, *to_reduce) - mean, inv_std = self._compute_mean_std(sum_, ssum, sum_size) - - broadcasted = Broadcast.apply(target_gpus, mean, inv_std) - - outputs = [] - for i, rec in enumerate(intermediates): - outputs.append((rec[0], _MasterMessage(*broadcasted[i*2:i*2+2]))) - - return outputs - - def _compute_mean_std(self, sum_, ssum, size): - """Compute the mean and standard-deviation with sum and square-sum. This method - also maintains the moving average on the master device.""" - assert size > 1, 'BatchNorm computes unbiased standard-deviation, which requires size > 1.' - mean = sum_ / size - sumvar = ssum - sum_ * mean - unbias_var = sumvar / (size - 1) - bias_var = sumvar / size - - self.running_mean = (1 - self.momentum) * self.running_mean + self.momentum * mean.data - self.running_var = (1 - self.momentum) * self.running_var + self.momentum * unbias_var.data - - return mean, bias_var.clamp(self.eps) ** -0.5 - - -class SynchronizedBatchNorm1d(_SynchronizedBatchNorm): - r"""Applies Synchronized Batch Normalization over a 2d or 3d input that is seen as a - mini-batch. - - .. math:: - - y = \frac{x - mean[x]}{ \sqrt{Var[x] + \epsilon}} * gamma + beta - - This module differs from the built-in PyTorch BatchNorm1d as the mean and - standard-deviation are reduced across all devices during training. - - For example, when one uses `nn.DataParallel` to wrap the network during - training, PyTorch's implementation normalize the tensor on each device using - the statistics only on that device, which accelerated the computation and - is also easy to implement, but the statistics might be inaccurate. - Instead, in this synchronized version, the statistics will be computed - over all training samples distributed on multiple devices. - - Note that, for one-GPU or CPU-only case, this module behaves exactly same - as the built-in PyTorch implementation. - - The mean and standard-deviation are calculated per-dimension over - the mini-batches and gamma and beta are learnable parameter vectors - of size C (where C is the input size). - - During training, this layer keeps a running estimate of its computed mean - and variance. The running sum is kept with a default momentum of 0.1. - - During evaluation, this running mean/variance is used for normalization. - - Because the BatchNorm is done over the `C` dimension, computing statistics - on `(N, L)` slices, it's common terminology to call this Temporal BatchNorm - - Args: - num_features: num_features from an expected input of size - `batch_size x num_features [x width]` - eps: a value added to the denominator for numerical stability. - Default: 1e-5 - momentum: the value used for the running_mean and running_var - computation. Default: 0.1 - affine: a boolean value that when set to ``True``, gives the layer learnable - affine parameters. Default: ``True`` - - Shape: - - Input: :math:`(N, C)` or :math:`(N, C, L)` - - Output: :math:`(N, C)` or :math:`(N, C, L)` (same shape as input) - - Examples: - >>> # With Learnable Parameters - >>> m = SynchronizedBatchNorm1d(100) - >>> # Without Learnable Parameters - >>> m = SynchronizedBatchNorm1d(100, affine=False) - >>> input = torch.autograd.Variable(torch.randn(20, 100)) - >>> output = m(input) - """ - - def _check_input_dim(self, input): - if input.dim() != 2 and input.dim() != 3: - raise ValueError('expected 2D or 3D input (got {}D input)' - .format(input.dim())) - super(SynchronizedBatchNorm1d, self)._check_input_dim(input) - - -class SynchronizedBatchNorm2d(_SynchronizedBatchNorm): - r"""Applies Batch Normalization over a 4d input that is seen as a mini-batch - of 3d inputs - - .. math:: - - y = \frac{x - mean[x]}{ \sqrt{Var[x] + \epsilon}} * gamma + beta - - This module differs from the built-in PyTorch BatchNorm2d as the mean and - standard-deviation are reduced across all devices during training. - - For example, when one uses `nn.DataParallel` to wrap the network during - training, PyTorch's implementation normalize the tensor on each device using - the statistics only on that device, which accelerated the computation and - is also easy to implement, but the statistics might be inaccurate. - Instead, in this synchronized version, the statistics will be computed - over all training samples distributed on multiple devices. - - Note that, for one-GPU or CPU-only case, this module behaves exactly same - as the built-in PyTorch implementation. - - The mean and standard-deviation are calculated per-dimension over - the mini-batches and gamma and beta are learnable parameter vectors - of size C (where C is the input size). - - During training, this layer keeps a running estimate of its computed mean - and variance. The running sum is kept with a default momentum of 0.1. - - During evaluation, this running mean/variance is used for normalization. - - Because the BatchNorm is done over the `C` dimension, computing statistics - on `(N, H, W)` slices, it's common terminology to call this Spatial BatchNorm - - Args: - num_features: num_features from an expected input of - size batch_size x num_features x height x width - eps: a value added to the denominator for numerical stability. - Default: 1e-5 - momentum: the value used for the running_mean and running_var - computation. Default: 0.1 - affine: a boolean value that when set to ``True``, gives the layer learnable - affine parameters. Default: ``True`` - - Shape: - - Input: :math:`(N, C, H, W)` - - Output: :math:`(N, C, H, W)` (same shape as input) - - Examples: - >>> # With Learnable Parameters - >>> m = SynchronizedBatchNorm2d(100) - >>> # Without Learnable Parameters - >>> m = SynchronizedBatchNorm2d(100, affine=False) - >>> input = torch.autograd.Variable(torch.randn(20, 100, 35, 45)) - >>> output = m(input) - """ - - def _check_input_dim(self, input): - if input.dim() != 4: - raise ValueError('expected 4D input (got {}D input)' - .format(input.dim())) - super(SynchronizedBatchNorm2d, self)._check_input_dim(input) - - -class SynchronizedBatchNorm3d(_SynchronizedBatchNorm): - r"""Applies Batch Normalization over a 5d input that is seen as a mini-batch - of 4d inputs - - .. math:: - - y = \frac{x - mean[x]}{ \sqrt{Var[x] + \epsilon}} * gamma + beta - - This module differs from the built-in PyTorch BatchNorm3d as the mean and - standard-deviation are reduced across all devices during training. - - For example, when one uses `nn.DataParallel` to wrap the network during - training, PyTorch's implementation normalize the tensor on each device using - the statistics only on that device, which accelerated the computation and - is also easy to implement, but the statistics might be inaccurate. - Instead, in this synchronized version, the statistics will be computed - over all training samples distributed on multiple devices. - - Note that, for one-GPU or CPU-only case, this module behaves exactly same - as the built-in PyTorch implementation. - - The mean and standard-deviation are calculated per-dimension over - the mini-batches and gamma and beta are learnable parameter vectors - of size C (where C is the input size). - - During training, this layer keeps a running estimate of its computed mean - and variance. The running sum is kept with a default momentum of 0.1. - - During evaluation, this running mean/variance is used for normalization. - - Because the BatchNorm is done over the `C` dimension, computing statistics - on `(N, D, H, W)` slices, it's common terminology to call this Volumetric BatchNorm - or Spatio-temporal BatchNorm - - Args: - num_features: num_features from an expected input of - size batch_size x num_features x depth x height x width - eps: a value added to the denominator for numerical stability. - Default: 1e-5 - momentum: the value used for the running_mean and running_var - computation. Default: 0.1 - affine: a boolean value that when set to ``True``, gives the layer learnable - affine parameters. Default: ``True`` - - Shape: - - Input: :math:`(N, C, D, H, W)` - - Output: :math:`(N, C, D, H, W)` (same shape as input) - - Examples: - >>> # With Learnable Parameters - >>> m = SynchronizedBatchNorm3d(100) - >>> # Without Learnable Parameters - >>> m = SynchronizedBatchNorm3d(100, affine=False) - >>> input = torch.autograd.Variable(torch.randn(20, 100, 35, 45, 10)) - >>> output = m(input) - """ - - def _check_input_dim(self, input): - if input.dim() != 5: - raise ValueError('expected 5D input (got {}D input)' - .format(input.dim())) - super(SynchronizedBatchNorm3d, self)._check_input_dim(input) diff --git a/src/facerender/sync_batchnorm/comm.py b/src/facerender/sync_batchnorm/comm.py deleted file mode 100644 index 922f8c4..0000000 --- a/src/facerender/sync_batchnorm/comm.py +++ /dev/null @@ -1,137 +0,0 @@ -# -*- coding: utf-8 -*- -# File : comm.py -# Author : Jiayuan Mao -# Email : maojiayuan@gmail.com -# Date : 27/01/2018 -# -# This file is part of Synchronized-BatchNorm-PyTorch. -# https://github.com/vacancy/Synchronized-BatchNorm-PyTorch -# Distributed under MIT License. - -import queue -import collections -import threading - -__all__ = ['FutureResult', 'SlavePipe', 'SyncMaster'] - - -class FutureResult(object): - """A thread-safe future implementation. Used only as one-to-one pipe.""" - - def __init__(self): - self._result = None - self._lock = threading.Lock() - self._cond = threading.Condition(self._lock) - - def put(self, result): - with self._lock: - assert self._result is None, 'Previous result has\'t been fetched.' - self._result = result - self._cond.notify() - - def get(self): - with self._lock: - if self._result is None: - self._cond.wait() - - res = self._result - self._result = None - return res - - -_MasterRegistry = collections.namedtuple('MasterRegistry', ['result']) -_SlavePipeBase = collections.namedtuple('_SlavePipeBase', ['identifier', 'queue', 'result']) - - -class SlavePipe(_SlavePipeBase): - """Pipe for master-slave communication.""" - - def run_slave(self, msg): - self.queue.put((self.identifier, msg)) - ret = self.result.get() - self.queue.put(True) - return ret - - -class SyncMaster(object): - """An abstract `SyncMaster` object. - - - During the replication, as the data parallel will trigger an callback of each module, all slave devices should - call `register(id)` and obtain an `SlavePipe` to communicate with the master. - - During the forward pass, master device invokes `run_master`, all messages from slave devices will be collected, - and passed to a registered callback. - - After receiving the messages, the master device should gather the information and determine to message passed - back to each slave devices. - """ - - def __init__(self, master_callback): - """ - - Args: - master_callback: a callback to be invoked after having collected messages from slave devices. - """ - self._master_callback = master_callback - self._queue = queue.Queue() - self._registry = collections.OrderedDict() - self._activated = False - - def __getstate__(self): - return {'master_callback': self._master_callback} - - def __setstate__(self, state): - self.__init__(state['master_callback']) - - def register_slave(self, identifier): - """ - Register an slave device. - - Args: - identifier: an identifier, usually is the device id. - - Returns: a `SlavePipe` object which can be used to communicate with the master device. - - """ - if self._activated: - assert self._queue.empty(), 'Queue is not clean before next initialization.' - self._activated = False - self._registry.clear() - future = FutureResult() - self._registry[identifier] = _MasterRegistry(future) - return SlavePipe(identifier, self._queue, future) - - def run_master(self, master_msg): - """ - Main entry for the master device in each forward pass. - The messages were first collected from each devices (including the master device), and then - an callback will be invoked to compute the message to be sent back to each devices - (including the master device). - - Args: - master_msg: the message that the master want to send to itself. This will be placed as the first - message when calling `master_callback`. For detailed usage, see `_SynchronizedBatchNorm` for an example. - - Returns: the message to be sent back to the master device. - - """ - self._activated = True - - intermediates = [(0, master_msg)] - for i in range(self.nr_slaves): - intermediates.append(self._queue.get()) - - results = self._master_callback(intermediates) - assert results[0][0] == 0, 'The first result should belongs to the master.' - - for i, res in results: - if i == 0: - continue - self._registry[i].result.put(res) - - for i in range(self.nr_slaves): - assert self._queue.get() is True - - return results[0][1] - - @property - def nr_slaves(self): - return len(self._registry) diff --git a/src/facerender/sync_batchnorm/replicate.py b/src/facerender/sync_batchnorm/replicate.py deleted file mode 100644 index b71c7b8..0000000 --- a/src/facerender/sync_batchnorm/replicate.py +++ /dev/null @@ -1,94 +0,0 @@ -# -*- coding: utf-8 -*- -# File : replicate.py -# Author : Jiayuan Mao -# Email : maojiayuan@gmail.com -# Date : 27/01/2018 -# -# This file is part of Synchronized-BatchNorm-PyTorch. -# https://github.com/vacancy/Synchronized-BatchNorm-PyTorch -# Distributed under MIT License. - -import functools - -from torch.nn.parallel.data_parallel import DataParallel - -__all__ = [ - 'CallbackContext', - 'execute_replication_callbacks', - 'DataParallelWithCallback', - 'patch_replication_callback' -] - - -class CallbackContext(object): - pass - - -def execute_replication_callbacks(modules): - """ - Execute an replication callback `__data_parallel_replicate__` on each module created by original replication. - - The callback will be invoked with arguments `__data_parallel_replicate__(ctx, copy_id)` - - Note that, as all modules are isomorphism, we assign each sub-module with a context - (shared among multiple copies of this module on different devices). - Through this context, different copies can share some information. - - We guarantee that the callback on the master copy (the first copy) will be called ahead of calling the callback - of any slave copies. - """ - master_copy = modules[0] - nr_modules = len(list(master_copy.modules())) - ctxs = [CallbackContext() for _ in range(nr_modules)] - - for i, module in enumerate(modules): - for j, m in enumerate(module.modules()): - if hasattr(m, '__data_parallel_replicate__'): - m.__data_parallel_replicate__(ctxs[j], i) - - -class DataParallelWithCallback(DataParallel): - """ - Data Parallel with a replication callback. - - An replication callback `__data_parallel_replicate__` of each module will be invoked after being created by - original `replicate` function. - The callback will be invoked with arguments `__data_parallel_replicate__(ctx, copy_id)` - - Examples: - > sync_bn = SynchronizedBatchNorm1d(10, eps=1e-5, affine=False) - > sync_bn = DataParallelWithCallback(sync_bn, device_ids=[0, 1]) - # sync_bn.__data_parallel_replicate__ will be invoked. - """ - - def replicate(self, module, device_ids): - modules = super(DataParallelWithCallback, self).replicate(module, device_ids) - execute_replication_callbacks(modules) - return modules - - -def patch_replication_callback(data_parallel): - """ - Monkey-patch an existing `DataParallel` object. Add the replication callback. - Useful when you have customized `DataParallel` implementation. - - Examples: - > sync_bn = SynchronizedBatchNorm1d(10, eps=1e-5, affine=False) - > sync_bn = DataParallel(sync_bn, device_ids=[0, 1]) - > patch_replication_callback(sync_bn) - # this is equivalent to - > sync_bn = SynchronizedBatchNorm1d(10, eps=1e-5, affine=False) - > sync_bn = DataParallelWithCallback(sync_bn, device_ids=[0, 1]) - """ - - assert isinstance(data_parallel, DataParallel) - - old_replicate = data_parallel.replicate - - @functools.wraps(old_replicate) - def new_replicate(module, device_ids): - modules = old_replicate(module, device_ids) - execute_replication_callbacks(modules) - return modules - - data_parallel.replicate = new_replicate diff --git a/src/facerender/sync_batchnorm/unittest.py b/src/facerender/sync_batchnorm/unittest.py deleted file mode 100644 index 0675c02..0000000 --- a/src/facerender/sync_batchnorm/unittest.py +++ /dev/null @@ -1,29 +0,0 @@ -# -*- coding: utf-8 -*- -# File : unittest.py -# Author : Jiayuan Mao -# Email : maojiayuan@gmail.com -# Date : 27/01/2018 -# -# This file is part of Synchronized-BatchNorm-PyTorch. -# https://github.com/vacancy/Synchronized-BatchNorm-PyTorch -# Distributed under MIT License. - -import unittest - -import numpy as np -from torch.autograd import Variable - - -def as_numpy(v): - if isinstance(v, Variable): - v = v.data - return v.cpu().numpy() - - -class TorchTestCase(unittest.TestCase): - def assertTensorClose(self, a, b, atol=1e-3, rtol=1e-3): - npa, npb = as_numpy(a), as_numpy(b) - self.assertTrue( - np.allclose(npa, npb, atol=atol), - 'Tensor close check failed\n{}\n{}\nadiff={}, rdiff={}'.format(a, b, np.abs(npa - npb).max(), np.abs((npa - npb) / np.fmax(npa, 1e-5)).max()) - ) diff --git a/src/generate_batch.py b/src/generate_batch.py deleted file mode 100644 index b095660..0000000 --- a/src/generate_batch.py +++ /dev/null @@ -1,120 +0,0 @@ -import os - -from tqdm import tqdm -import torch -import numpy as np -import random -import scipy.io as scio -import custom_nodes.ComfyUI_Lam.src.utils.audio as audio - -def crop_pad_audio(wav, audio_length): - if len(wav) > audio_length: - wav = wav[:audio_length] - elif len(wav) < audio_length: - wav = np.pad(wav, [0, audio_length - len(wav)], mode='constant', constant_values=0) - return wav - -def parse_audio_length(audio_length, sr, fps): - bit_per_frames = sr / fps - - num_frames = int(audio_length / bit_per_frames) - audio_length = int(num_frames * bit_per_frames) - - return audio_length, num_frames - -def generate_blink_seq(num_frames): - ratio = np.zeros((num_frames,1)) - frame_id = 0 - while frame_id in range(num_frames): - start = 80 - if frame_id+start+9<=num_frames - 1: - ratio[frame_id+start:frame_id+start+9, 0] = [0.5,0.6,0.7,0.9,1, 0.9, 0.7,0.6,0.5] - frame_id = frame_id+start+9 - else: - break - return ratio - -def generate_blink_seq_randomly(num_frames): - ratio = np.zeros((num_frames,1)) - if num_frames<=20: - return ratio - frame_id = 0 - while frame_id in range(num_frames): - start = random.choice(range(min(10,num_frames), min(int(num_frames/2), 70))) - if frame_id+start+5<=num_frames - 1: - ratio[frame_id+start:frame_id+start+5, 0] = [0.5, 0.9, 1.0, 0.9, 0.5] - frame_id = frame_id+start+5 - else: - break - return ratio - -def get_data(first_coeff_path, audio_path, device, ref_eyeblink_coeff_path, still=False, idlemode=False, length_of_audio=False, use_blink=True): - - syncnet_mel_step_size = 16 - fps = 25 - - pic_name = os.path.splitext(os.path.split(first_coeff_path)[-1])[0] - audio_name = os.path.splitext(os.path.split(audio_path)[-1])[0] - - - if idlemode: - num_frames = int(length_of_audio * 25) - indiv_mels = np.zeros((num_frames, 80, 16)) - else: - wav = audio.load_wav(audio_path, 16000) - wav_length, num_frames = parse_audio_length(len(wav), 16000, 25) - wav = crop_pad_audio(wav, wav_length) - orig_mel = audio.melspectrogram(wav).T - spec = orig_mel.copy() # nframes 80 - indiv_mels = [] - - for i in tqdm(range(num_frames), 'mel:'): - start_frame_num = i-2 - start_idx = int(80. * (start_frame_num / float(fps))) - end_idx = start_idx + syncnet_mel_step_size - seq = list(range(start_idx, end_idx)) - seq = [ min(max(item, 0), orig_mel.shape[0]-1) for item in seq ] - m = spec[seq, :] - indiv_mels.append(m.T) - indiv_mels = np.asarray(indiv_mels) # T 80 16 - - ratio = generate_blink_seq_randomly(num_frames) # T - source_semantics_path = first_coeff_path - source_semantics_dict = scio.loadmat(source_semantics_path) - ref_coeff = source_semantics_dict['coeff_3dmm'][:1,:70] #1 70 - ref_coeff = np.repeat(ref_coeff, num_frames, axis=0) - - if ref_eyeblink_coeff_path is not None: - ratio[:num_frames] = 0 - refeyeblink_coeff_dict = scio.loadmat(ref_eyeblink_coeff_path) - refeyeblink_coeff = refeyeblink_coeff_dict['coeff_3dmm'][:,:64] - refeyeblink_num_frames = refeyeblink_coeff.shape[0] - if refeyeblink_num_frames audio_length: - wav = wav[:audio_length] - elif len(wav) < audio_length: - wav = np.pad(wav, [0, audio_length - len(wav)], mode='constant', constant_values=0) - return wav - -def parse_audio_length(audio_length, sr, fps): - bit_per_frames = sr / fps - - num_frames = int(audio_length / bit_per_frames) - audio_length = int(num_frames * bit_per_frames) - - return audio_length, num_frames - -def generate_blink_seq(num_frames): - ratio = np.zeros((num_frames,1)) - frame_id = 0 - while frame_id in range(num_frames): - start = 80 - if frame_id+start+9<=num_frames - 1: - ratio[frame_id+start:frame_id+start+9, 0] = [0.5,0.6,0.7,0.9,1, 0.9, 0.7,0.6,0.5] - frame_id = frame_id+start+9 - else: - break - return ratio - -def generate_blink_seq_randomly(num_frames): - ratio = np.zeros((num_frames,1)) - if num_frames<=20: - return ratio - frame_id = 0 - while frame_id in range(num_frames): - start = random.choice(range(min(10,num_frames), min(int(num_frames/2), 70))) - if frame_id+start+5<=num_frames - 1: - ratio[frame_id+start:frame_id+start+5, 0] = [0.5, 0.9, 1.0, 0.9, 0.5] - frame_id = frame_id+start+5 - else: - break - return ratio - -def get_data(first_coeff_path, audio_path, device): - - syncnet_mel_step_size = 16 - fps = 25 - - pic_name = os.path.splitext(os.path.split(first_coeff_path)[-1])[0] - audio_name = os.path.splitext(os.path.split(audio_path)[-1])[0] - - wav = audio.load_wav(audio_path, 16000) - wav_length, num_frames = parse_audio_length(len(wav), 16000, 25) - wav = crop_pad_audio(wav, wav_length) - orig_mel = audio.melspectrogram(wav).T - spec = orig_mel.copy() # nframes 80 - indiv_mels = [] - - for i in tqdm(range(num_frames), 'mel:'): - start_frame_num = i-2 - start_idx = int(80. * (start_frame_num / float(fps))) - end_idx = start_idx + syncnet_mel_step_size - seq = list(range(start_idx, end_idx)) - seq = [ min(max(item, 0), orig_mel.shape[0]-1) for item in seq ] - m = spec[seq, :] - indiv_mels.append(m.T) - indiv_mels = np.asarray(indiv_mels) # T 80 16 - - ratio = generate_blink_seq_randomly(num_frames) # T - source_semantics_path = first_coeff_path - source_semantics_dict = scio.loadmat(source_semantics_path) - # ref_coeff = source_semantics_dict['coeff_3dmm'][:num_frames,:70] #1 70 - # # ref_coeff = np.repeat(ref_coeff, num_frames, axis=0) - coeff_3dmm = source_semantics_dict['coeff_3dmm'] - if coeff_3dmm.shape[0] >= num_frames: - ref_coeff = source_semantics_dict['coeff_3dmm'][:num_frames, :70] - else: - ref_coeff_ori = source_semantics_dict['coeff_3dmm'][:, :70] - ref_coeff_last = source_semantics_dict['coeff_3dmm'][-1, :70].reshape(1,-1) - ref_coeff_add = np.repeat(ref_coeff_last, num_frames - coeff_3dmm.shape[0], axis=0) - ref_coeff = np.concatenate((ref_coeff_ori, ref_coeff_add)) - indiv_mels = torch.FloatTensor(indiv_mels).unsqueeze(1).unsqueeze(0) # bs T 1 80 16 - ratio = torch.FloatTensor(ratio).unsqueeze(0).fill_(0.) # bs T - ref_coeff = torch.FloatTensor(ref_coeff).unsqueeze(0) # bs 1 70 - - indiv_mels = indiv_mels.to(device) - ratio = ratio.to(device) - ref_coeff = ref_coeff.to(device) - - return {'indiv_mels': indiv_mels, - 'ref': ref_coeff, - 'num_frames': num_frames, - 'ratio_gt': ratio, - 'audio_name': audio_name, 'pic_name': pic_name} - diff --git a/src/generate_facerender_batch.py b/src/generate_facerender_batch.py deleted file mode 100644 index a62b6ed..0000000 --- a/src/generate_facerender_batch.py +++ /dev/null @@ -1,136 +0,0 @@ -import os -import numpy as np -from PIL import Image -from skimage import io, img_as_float32, transform -import torch -import scipy.io as scio - -def get_facerender_data(coeff_path, pic_path, first_coeff_path, audio_path, - batch_size, input_yaw_list=None, input_pitch_list=None, input_roll_list=None, - expression_scale=1.0, still_mode = False, preprocess='crop', size = 256): - - semantic_radius = 13 - video_name = os.path.splitext(os.path.split(coeff_path)[-1])[0] - txt_path = os.path.splitext(coeff_path)[0] - - data={} - - img1 = Image.open(pic_path) - source_image = np.array(img1) - source_image = img_as_float32(source_image) - source_image = transform.resize(source_image, (size, size, 3)) - source_image = source_image.transpose((2, 0, 1)) - source_image_ts = torch.FloatTensor(source_image).unsqueeze(0) - source_image_ts = source_image_ts.repeat(batch_size, 1, 1, 1) - data['source_image'] = source_image_ts - - source_semantics_dict = scio.loadmat(first_coeff_path) - generated_dict = scio.loadmat(coeff_path) - - if 'full' not in preprocess.lower(): - source_semantics = source_semantics_dict['coeff_3dmm'][:1,:70] #1 70 - generated_3dmm = generated_dict['coeff_3dmm'][:,:70] - - else: - source_semantics = source_semantics_dict['coeff_3dmm'][:1,:73] #1 70 - generated_3dmm = generated_dict['coeff_3dmm'][:,:70] - - source_semantics_new = transform_semantic_1(source_semantics, semantic_radius) - source_semantics_ts = torch.FloatTensor(source_semantics_new).unsqueeze(0) - source_semantics_ts = source_semantics_ts.repeat(batch_size, 1, 1) - data['source_semantics'] = source_semantics_ts - - # target - generated_3dmm[:, :64] = generated_3dmm[:, :64] * expression_scale - - if 'full' in preprocess.lower(): - generated_3dmm = np.concatenate([generated_3dmm, np.repeat(source_semantics[:,70:], generated_3dmm.shape[0], axis=0)], axis=1) - - if still_mode: - generated_3dmm[:, 64:] = np.repeat(source_semantics[:, 64:], generated_3dmm.shape[0], axis=0) - - with open(txt_path+'.txt', 'w') as f: - for coeff in generated_3dmm: - for i in coeff: - f.write(str(i)[:7] + ' '+'\t') - f.write('\n') - - target_semantics_list = [] - frame_num = generated_3dmm.shape[0] - data['frame_num'] = frame_num - for frame_idx in range(frame_num): - target_semantics = transform_semantic_target(generated_3dmm, frame_idx, semantic_radius) - target_semantics_list.append(target_semantics) - - remainder = frame_num%batch_size - if remainder!=0: - for _ in range(batch_size-remainder): - target_semantics_list.append(target_semantics) - - target_semantics_np = np.array(target_semantics_list) #frame_num 70 semantic_radius*2+1 - target_semantics_np = target_semantics_np.reshape(batch_size, -1, target_semantics_np.shape[-2], target_semantics_np.shape[-1]) - data['target_semantics_list'] = torch.FloatTensor(target_semantics_np) - data['video_name'] = video_name - data['audio_path'] = audio_path - - if input_yaw_list is not None: - yaw_c_seq = gen_camera_pose(input_yaw_list, frame_num, batch_size) - data['yaw_c_seq'] = torch.FloatTensor(yaw_c_seq) - if input_pitch_list is not None: - pitch_c_seq = gen_camera_pose(input_pitch_list, frame_num, batch_size) - data['pitch_c_seq'] = torch.FloatTensor(pitch_c_seq) - if input_roll_list is not None: - roll_c_seq = gen_camera_pose(input_roll_list, frame_num, batch_size) - data['roll_c_seq'] = torch.FloatTensor(roll_c_seq) - - return data - -def transform_semantic_1(semantic, semantic_radius): - semantic_list = [semantic for i in range(0, semantic_radius*2+1)] - coeff_3dmm = np.concatenate(semantic_list, 0) - return coeff_3dmm.transpose(1,0) - -def transform_semantic_target(coeff_3dmm, frame_index, semantic_radius): - num_frames = coeff_3dmm.shape[0] - seq = list(range(frame_index- semantic_radius, frame_index + semantic_radius+1)) - index = [ min(max(item, 0), num_frames-1) for item in seq ] - coeff_3dmm_g = coeff_3dmm[index, :] - return coeff_3dmm_g.transpose(1,0) - -def gen_camera_pose(camera_degree_list, frame_num, batch_size): - - new_degree_list = [] - if len(camera_degree_list) == 1: - for _ in range(frame_num): - new_degree_list.append(camera_degree_list[0]) - remainder = frame_num%batch_size - if remainder!=0: - for _ in range(batch_size-remainder): - new_degree_list.append(new_degree_list[-1]) - new_degree_np = np.array(new_degree_list).reshape(batch_size, -1) - return new_degree_np - - degree_sum = 0. - for i, degree in enumerate(camera_degree_list[1:]): - degree_sum += abs(degree-camera_degree_list[i]) - - degree_per_frame = degree_sum/(frame_num-1) - for i, degree in enumerate(camera_degree_list[1:]): - degree_last = camera_degree_list[i] - degree_step = degree_per_frame * abs(degree-degree_last)/(degree-degree_last) - new_degree_list = new_degree_list + list(np.arange(degree_last, degree, degree_step)) - if len(new_degree_list) > frame_num: - new_degree_list = new_degree_list[:frame_num] - elif len(new_degree_list) < frame_num: - for _ in range(frame_num-len(new_degree_list)): - new_degree_list.append(new_degree_list[-1]) - print(len(new_degree_list)) - print(frame_num) - - remainder = frame_num%batch_size - if remainder!=0: - for _ in range(batch_size-remainder): - new_degree_list.append(new_degree_list[-1]) - new_degree_np = np.array(new_degree_list).reshape(batch_size, -1) - return new_degree_np - diff --git a/src/generate_facerender_batch_two.py b/src/generate_facerender_batch_two.py deleted file mode 100644 index e127f70..0000000 --- a/src/generate_facerender_batch_two.py +++ /dev/null @@ -1,124 +0,0 @@ -import os -import numpy as np -from PIL import Image -from skimage import img_as_float32, transform -import torch -import scipy.io as scio -from glob import glob - - -def get_facerender_data(coeff_path, pic_path, first_coeff_path, audio_path, batch_size, device): - semantic_radius = 13 - video_name = os.path.splitext(os.path.split(coeff_path)[-1])[0] - txt_path = os.path.splitext(coeff_path)[0] - - data = {} - images_list = sorted(glob(os.path.join(pic_path, '*.png'))) - source_image_ts_list = [] - for single in images_list: - img1 = Image.open(single) - source_image = np.array(img1) - source_image = img_as_float32(source_image) - source_image = transform.resize(source_image, (256, 256, 3)) - source_image = source_image.transpose((2, 0, 1)) - source_image_ts = torch.FloatTensor(source_image).unsqueeze(0) - source_image_ts = source_image_ts.repeat(batch_size, 1, 1, 1) - source_image_ts = source_image_ts.to(device) - source_image_ts_list.append(source_image_ts) - data['source_image'] = source_image_ts_list - - source_semantics_dict = scio.loadmat(first_coeff_path) - - source_semantics = source_semantics_dict['coeff_3dmm'][:1, :73] # 1 70 - - source_semantics_new = transform_semantic_1(source_semantics, semantic_radius) - source_semantics_ts = torch.FloatTensor(source_semantics_new).unsqueeze(0) - source_semantics_ts = source_semantics_ts.repeat(batch_size, 1, 1) - data['source_semantics'] = source_semantics_ts - - # target - generated_dict = scio.loadmat(coeff_path) - generated_3dmm = generated_dict['coeff_3dmm'] - generated_3dmm[:, :64] = generated_3dmm[:, :64] * 1.0 - - generated_3dmm = np.concatenate( - [generated_3dmm, np.repeat(source_semantics[:, 70:], generated_3dmm.shape[0], axis=0)], axis=1) - - generated_3dmm[:, 64:] = np.repeat(source_semantics[:, 64:], generated_3dmm.shape[0], axis=0) - - with open(txt_path + '.txt', 'w') as f: - for coeff in generated_3dmm: - for i in coeff: - f.write(str(i)[:7] + ' ' + '\t') - f.write('\n') - - target_semantics_list = [] - frame_num = generated_3dmm.shape[0] - data['frame_num'] = frame_num - for frame_idx in range(frame_num): - target_semantics = transform_semantic_target(generated_3dmm, frame_idx, semantic_radius) - target_semantics_list.append(target_semantics) - - remainder = frame_num % batch_size - if remainder != 0: - for _ in range(batch_size - remainder): - target_semantics_list.append(target_semantics) - - target_semantics_np = np.array(target_semantics_list) # frame_num 70 semantic_radius*2+1 - target_semantics_np = target_semantics_np.reshape(batch_size, -1, target_semantics_np.shape[-2], - target_semantics_np.shape[-1]) - data['target_semantics_list'] = torch.FloatTensor(target_semantics_np) - data['video_name'] = video_name - data['audio_path'] = audio_path - return data - - -def transform_semantic_1(semantic, semantic_radius): - semantic_list = [semantic for i in range(0, semantic_radius * 2 + 1)] - coeff_3dmm = np.concatenate(semantic_list, 0) - return coeff_3dmm.transpose(1, 0) - - -def transform_semantic_target(coeff_3dmm, frame_index, semantic_radius): - num_frames = coeff_3dmm.shape[0] - seq = list(range(frame_index - semantic_radius, frame_index + semantic_radius + 1)) - index = [min(max(item, 0), num_frames - 1) for item in seq] - coeff_3dmm_g = coeff_3dmm[index, :] - return coeff_3dmm_g.transpose(1, 0) - - -def gen_camera_pose(camera_degree_list, frame_num, batch_size): - new_degree_list = [] - if len(camera_degree_list) == 1: - for _ in range(frame_num): - new_degree_list.append(camera_degree_list[0]) - remainder = frame_num % batch_size - if remainder != 0: - for _ in range(batch_size - remainder): - new_degree_list.append(new_degree_list[-1]) - new_degree_np = np.array(new_degree_list).reshape(batch_size, -1) - return new_degree_np - - degree_sum = 0. - for i, degree in enumerate(camera_degree_list[1:]): - degree_sum += abs(degree - camera_degree_list[i]) - - degree_per_frame = degree_sum / (frame_num - 1) - for i, degree in enumerate(camera_degree_list[1:]): - degree_last = camera_degree_list[i] - degree_step = degree_per_frame * abs(degree - degree_last) / (degree - degree_last) - new_degree_list = new_degree_list + list(np.arange(degree_last, degree, degree_step)) - if len(new_degree_list) > frame_num: - new_degree_list = new_degree_list[:frame_num] - elif len(new_degree_list) < frame_num: - for _ in range(frame_num - len(new_degree_list)): - new_degree_list.append(new_degree_list[-1]) - print(len(new_degree_list)) - print(frame_num) - - remainder = frame_num % batch_size - if remainder != 0: - for _ in range(batch_size - remainder): - new_degree_list.append(new_degree_list[-1]) - new_degree_np = np.array(new_degree_list).reshape(batch_size, -1) - return new_degree_np diff --git a/src/gradio_demo.py b/src/gradio_demo.py deleted file mode 100644 index a54992d..0000000 --- a/src/gradio_demo.py +++ /dev/null @@ -1,242 +0,0 @@ -import folder_paths -import torch, uuid -import os, sys, shutil -from custom_nodes.ComfyUI_Lam.src.utils.preprocess import CropAndExtract -from custom_nodes.ComfyUI_Lam.src.test_audio2coeff import Audio2Coeff -from custom_nodes.ComfyUI_Lam.src.facerender.animate import AnimateFromCoeff -from custom_nodes.ComfyUI_Lam.src.generate_batch import get_data -from custom_nodes.ComfyUI_Lam.src.generate_facerender_batch import get_facerender_data - -from custom_nodes.ComfyUI_Lam.src.utils.preprocess_two import CropAndExtract as CropAndExtractt -from custom_nodes.ComfyUI_Lam.src.test_audio2coeff_two import Audio2Coeff as Audio2Coefft -from custom_nodes.ComfyUI_Lam.src.facerender.animate_two import AnimateFromCoeff as AnimateFromCoefft -from custom_nodes.ComfyUI_Lam.src.generate_batch_two import get_data as get_datat -from custom_nodes.ComfyUI_Lam.src.generate_facerender_batch_two import get_facerender_data as get_facerender_datat -from custom_nodes.ComfyUI_Lam.third_part.GPEN.gpen_face_enhancer import FaceEnhancement -import warnings - -from custom_nodes.ComfyUI_Lam.src.utils.init_path import init_path - -from pydub import AudioSegment -import numpy as np -from PIL import Image - - -def mp3_to_wav(mp3_filename,wav_filename,frame_rate): - mp3_file = AudioSegment.from_file(file=mp3_filename) - mp3_file.set_frame_rate(frame_rate).export(wav_filename,format="wav") - - -class SadTalker(): - - def __init__(self, checkpoint_path='checkpoints', config_path='src/config', lazy_load=False): - - if torch.cuda.is_available() : - device = "cuda" - else: - device = "cpu" - - self.device = device - - os.environ['TORCH_HOME']= checkpoint_path - - self.checkpoint_path = checkpoint_path - self.config_path = os.path.join(folder_paths.base_path,config_path) - - def run_video_2_video(self,pic_path, audio_path,enhancer='lip',batch_size=1, - use_DAIN=False,remove_duplicates=False,dian_output='dian_output',time_step=0.5,result_dir='./results/'): - path_of_lm_croper = os.path.join(self.checkpoint_path, 'shape_predictor_68_face_landmarks.dat') - path_of_net_recon_model = os.path.join(self.checkpoint_path, 'epoch_20.pth') - dir_of_BFM_fitting = os.path.join(self.checkpoint_path, 'BFM_Fitting') - wav2lip_checkpoint = os.path.join(self.checkpoint_path, 'wav2lip.pth') - - audio2pose_checkpoint = os.path.join(self.checkpoint_path, 'auido2pose_00140-model.pth') - audio2pose_yaml_path = os.path.join(self.config_path, 'auido2pose.yaml') - - audio2exp_checkpoint = os.path.join(self.checkpoint_path, 'auido2exp_00300-model.pth') - audio2exp_yaml_path = os.path.join(self.config_path, 'auido2exp.yaml') - - free_view_checkpoint = os.path.join(self.checkpoint_path, 'facevid2vid_00189-model.pth.tar') - - mapping_checkpoint = os.path.join(self.checkpoint_path, 'mapping_00109-model.pth.tar') - facerender_yaml_path = os.path.join(self.config_path, 'facerender_still.yaml') - - print("==========",result_dir) - # init model - print(path_of_net_recon_model) - preprocess_model = CropAndExtractt(path_of_lm_croper, path_of_net_recon_model, dir_of_BFM_fitting, self.device) - - print(audio2pose_checkpoint) - print(audio2exp_checkpoint) - audio_to_coeff = Audio2Coefft(audio2pose_checkpoint, audio2pose_yaml_path, audio2exp_checkpoint, audio2exp_yaml_path, - wav2lip_checkpoint, self.device) - - print(free_view_checkpoint) - print(mapping_checkpoint) - animate_from_coeff = AnimateFromCoefft(free_view_checkpoint, mapping_checkpoint, facerender_yaml_path, self.device) - - - enhancer_model = FaceEnhancement(base_dir=self.checkpoint_path, size=512, model='GPEN-BFR-512', use_sr=False, - sr_model='rrdb_realesrnet_psnr', channel_multiplier=2, narrow=1,device=self.device) - - time_tag = str(uuid.uuid4()) - save_dir = os.path.join(result_dir, time_tag) - os.makedirs(save_dir, exist_ok=True) - # crop image and extract 3dmm from image - first_frame_dir = os.path.join(save_dir, 'first_frame_dir') - os.makedirs(first_frame_dir, exist_ok=True) - print('3DMM Extraction for source image') - first_coeff_path, crop_pic_path, crop_info = preprocess_model.generate(pic_path, first_frame_dir) - if first_coeff_path is None: - print("Can't get the coeffs of the input") - return - # audio2ceoff - batch = get_datat(first_coeff_path, audio_path, self.device) - coeff_path = audio_to_coeff.generate(batch, save_dir) - # coeff2video - data = get_facerender_datat(coeff_path, crop_pic_path, first_coeff_path, audio_path, batch_size, self.device) - enhancer='none' - tmp_path, new_audio_path, return_path = animate_from_coeff.generate(data, save_dir, pic_path, crop_info, - 'none', enhancer_model, enhancer) - torch.cuda.empty_cache() - if use_DAIN: - import paddle - from custom_nodes.ComfyUI_Lam.src.dain_model import dain_predictor - #paddle.device.set_device("gpu") # 把get—device的结果直接复制进去 - paddle.enable_static() - predictor_dian = dain_predictor.DAINPredictor(dian_output, weight_path=os.path.join(self.checkpoint_path,'DAIN_weight'), - time_step=time_step, - remove_duplicates=remove_duplicates) - frames_path, temp_video_path = predictor_dian.run(tmp_path) - paddle.disable_static() - save_path = return_path[:-4] + '_dain.mp4' - command = r'ffmpeg -y -i "%s" -i "%s" -vcodec copy "%s"' % (temp_video_path, new_audio_path, save_path) - os.system(command) - os.remove(tmp_path) - return return_path - - def run_image_2_video(self, source_image, driven_audio, preprocess='crop', - still_mode=False, use_enhancer=False, batch_size=1, size=256, - pose_style = 0, exp_scale=1.0, - use_ref_video = False, - ref_video = None, - ref_info = None, - use_idle_mode = False, - length_of_audio = 0, use_blink=True, - result_dir='./results/'): - - self.sadtalker_paths = init_path(self.checkpoint_path, self.config_path, size, False, preprocess) - print(self.sadtalker_paths) - - self.audio_to_coeff = Audio2Coeff(self.sadtalker_paths, self.device) - self.preprocess_model = CropAndExtract(self.sadtalker_paths, self.device) - self.animate_from_coeff = AnimateFromCoeff(self.sadtalker_paths, self.device) - - time_tag = str(uuid.uuid4()) - save_dir = os.path.join(result_dir, time_tag) - os.makedirs(save_dir, exist_ok=True) - - input_dir = os.path.join(save_dir, 'input') - os.makedirs(input_dir, exist_ok=True) - - # print(source_image) - file = "face_image.png" - pic_path = os.path.join(input_dir, file) - # shutil.move(source_image, input_dir) - - i = 255. * source_image[0].cpu().numpy() - img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8)) - img.save(os.path.join(input_dir, file),compress_level=4) - - if driven_audio is not None and os.path.isfile(driven_audio): - audio_path = os.path.join(input_dir, os.path.basename(driven_audio)) - - #### mp3 to wav - if '.mp3' in audio_path: - mp3_to_wav(driven_audio, audio_path.replace('.mp3', '.wav'), 16000) - audio_path = audio_path.replace('.mp3', '.wav') - else: - shutil.move(driven_audio, input_dir) - - elif use_idle_mode: - audio_path = os.path.join(input_dir, 'idlemode_'+str(length_of_audio)+'.wav') ## generate audio from this new audio_path - from pydub import AudioSegment - one_sec_segment = AudioSegment.silent(duration=1000*length_of_audio) #duration in milliseconds - one_sec_segment.export(audio_path, format="wav") - else: - print(use_ref_video, ref_info) - assert use_ref_video == True and ref_info == 'all' - - if use_ref_video and ref_info == 'all': # full ref mode - ref_video_videoname = os.path.basename(ref_video) - audio_path = os.path.join(save_dir, ref_video_videoname+'.wav') - print('new audiopath:',audio_path) - # if ref_video contains audio, set the audio from ref_video. - cmd = r"ffmpeg -y -hide_banner -loglevel error -i %s %s"%(ref_video, audio_path) - os.system(cmd) - - os.makedirs(save_dir, exist_ok=True) - - #crop image and extract 3dmm from image - first_frame_dir = os.path.join(save_dir, 'first_frame_dir') - os.makedirs(first_frame_dir, exist_ok=True) - first_coeff_path, crop_pic_path, crop_info = self.preprocess_model.generate(pic_path, first_frame_dir, preprocess, True, size) - - if first_coeff_path is None: - raise AttributeError("No face is detected") - - if use_ref_video: - print('using ref video for genreation') - ref_video_videoname = os.path.splitext(os.path.split(ref_video)[-1])[0] - ref_video_frame_dir = os.path.join(save_dir, ref_video_videoname) - os.makedirs(ref_video_frame_dir, exist_ok=True) - print('3DMM Extraction for the reference video providing pose') - ref_video_coeff_path, _, _ = self.preprocess_model.generate(ref_video, ref_video_frame_dir, preprocess, source_image_flag=False) - else: - ref_video_coeff_path = None - - if use_ref_video: - if ref_info == 'pose': - ref_pose_coeff_path = ref_video_coeff_path - ref_eyeblink_coeff_path = None - elif ref_info == 'blink': - ref_pose_coeff_path = None - ref_eyeblink_coeff_path = ref_video_coeff_path - elif ref_info == 'pose+blink': - ref_pose_coeff_path = ref_video_coeff_path - ref_eyeblink_coeff_path = ref_video_coeff_path - elif ref_info == 'all': - ref_pose_coeff_path = None - ref_eyeblink_coeff_path = None - else: - raise('error in refinfo') - else: - ref_pose_coeff_path = None - ref_eyeblink_coeff_path = None - - #audio2ceoff - if use_ref_video and ref_info == 'all': - coeff_path = ref_video_coeff_path # self.audio_to_coeff.generate(batch, save_dir, pose_style, ref_pose_coeff_path) - else: - batch = get_data(first_coeff_path, audio_path, self.device, ref_eyeblink_coeff_path=ref_eyeblink_coeff_path, still=still_mode, idlemode=use_idle_mode, length_of_audio=length_of_audio, use_blink=use_blink) # longer audio? - coeff_path = self.audio_to_coeff.generate(batch, save_dir, pose_style, ref_pose_coeff_path) - - #coeff2video - data = get_facerender_data(coeff_path, crop_pic_path, first_coeff_path, audio_path, batch_size, still_mode=still_mode, preprocess=preprocess, size=size, expression_scale = exp_scale) - return_path = self.animate_from_coeff.generate(data, save_dir, pic_path, crop_info, enhancer='gfpgan' if use_enhancer else None, preprocess=preprocess, img_size=size) - video_name = data['video_name'] - print(f'The generated video is named {video_name} in {save_dir}') - - del self.preprocess_model - del self.audio_to_coeff - del self.animate_from_coeff - - if torch.cuda.is_available(): - torch.cuda.empty_cache() - torch.cuda.synchronize() - - import gc; gc.collect() - - return return_path - - \ No newline at end of file diff --git a/src/inference_utils.py b/src/inference_utils.py deleted file mode 100644 index 92a554a..0000000 --- a/src/inference_utils.py +++ /dev/null @@ -1,46 +0,0 @@ -import numpy as np -import cv2 - - -def Laplacian_Pyramid_Blending_with_mask(A, B, m, num_levels=6): - # generate Gaussian pyramid for A,B and mask - GA = A.copy() - GB = B.copy() - GM = m.copy() - gpA = [GA] - gpB = [GB] - gpM = [GM] - for i in range(num_levels): - GA = cv2.pyrDown(GA) - GB = cv2.pyrDown(GB) - GM = cv2.pyrDown(GM) - gpA.append(np.float32(GA)) - gpB.append(np.float32(GB)) - gpM.append(np.float32(GM)) - - # generate Laplacian Pyramids for A,B and masks - lpA = [gpA[num_levels - 1]] # the bottom of the Lap-pyr holds the last (smallest) Gauss level - lpB = [gpB[num_levels - 1]] - gpMr = [gpM[num_levels - 1]] - for i in range(num_levels - 1, 0, -1): - # Laplacian: subtarct upscaled version of lower level from current level - # to get the high frequencies - LA = np.subtract(gpA[i - 1], cv2.pyrUp(gpA[i])) - LB = np.subtract(gpB[i - 1], cv2.pyrUp(gpB[i])) - lpA.append(LA) - lpB.append(LB) - gpMr.append(gpM[i - 1]) # also reverse the masks - - # Now blend images according to mask in each level - LS = [] - for la, lb, gm in zip(lpA, lpB, gpMr): - gm = gm[:, :, np.newaxis] - ls = la * gm + lb * (1.0 - gm) - LS.append(ls) - - # now reconstruct - ls_ = LS[0] - for i in range(1, num_levels): - ls_ = cv2.pyrUp(ls_) - ls_ = cv2.add(ls_, LS[i]) - return ls_ diff --git a/src/test_audio2coeff.py b/src/test_audio2coeff.py deleted file mode 100644 index fabe48a..0000000 --- a/src/test_audio2coeff.py +++ /dev/null @@ -1,123 +0,0 @@ -import os -import torch -import numpy as np -from scipy.io import savemat, loadmat -from yacs.config import CfgNode as CN -from scipy.signal import savgol_filter - -import safetensors -import safetensors.torch - -from custom_nodes.ComfyUI_Lam.src.audio2pose_models.audio2pose import Audio2Pose -from custom_nodes.ComfyUI_Lam.src.audio2exp_models.networks import SimpleWrapperV2 -from custom_nodes.ComfyUI_Lam.src.audio2exp_models.audio2exp import Audio2Exp -from custom_nodes.ComfyUI_Lam.src.utils.safetensor_helper import load_x_from_safetensor - -def load_cpk(checkpoint_path, model=None, optimizer=None, device="cpu"): - checkpoint = torch.load(checkpoint_path, map_location=torch.device(device)) - if model is not None: - model.load_state_dict(checkpoint['model']) - if optimizer is not None: - optimizer.load_state_dict(checkpoint['optimizer']) - - return checkpoint['epoch'] - -class Audio2Coeff(): - - def __init__(self, sadtalker_path, device): - #load config - fcfg_pose = open(sadtalker_path['audio2pose_yaml_path']) - cfg_pose = CN.load_cfg(fcfg_pose) - cfg_pose.freeze() - fcfg_exp = open(sadtalker_path['audio2exp_yaml_path']) - cfg_exp = CN.load_cfg(fcfg_exp) - cfg_exp.freeze() - - # load audio2pose_model - self.audio2pose_model = Audio2Pose(cfg_pose, None, device=device) - self.audio2pose_model = self.audio2pose_model.to(device) - self.audio2pose_model.eval() - for param in self.audio2pose_model.parameters(): - param.requires_grad = False - - try: - if sadtalker_path['use_safetensor']: - checkpoints = safetensors.torch.load_file(sadtalker_path['checkpoint']) - self.audio2pose_model.load_state_dict(load_x_from_safetensor(checkpoints, 'audio2pose')) - else: - load_cpk(sadtalker_path['audio2pose_checkpoint'], model=self.audio2pose_model, device=device) - except: - raise Exception("Failed in loading audio2pose_checkpoint") - - # load audio2exp_model - netG = SimpleWrapperV2() - netG = netG.to(device) - for param in netG.parameters(): - netG.requires_grad = False - netG.eval() - try: - if sadtalker_path['use_safetensor']: - checkpoints = safetensors.torch.load_file(sadtalker_path['checkpoint']) - netG.load_state_dict(load_x_from_safetensor(checkpoints, 'audio2exp')) - else: - load_cpk(sadtalker_path['audio2exp_checkpoint'], model=netG, device=device) - except: - raise Exception("Failed in loading audio2exp_checkpoint") - self.audio2exp_model = Audio2Exp(netG, cfg_exp, device=device, prepare_training_loss=False) - self.audio2exp_model = self.audio2exp_model.to(device) - for param in self.audio2exp_model.parameters(): - param.requires_grad = False - self.audio2exp_model.eval() - - self.device = device - - def generate(self, batch, coeff_save_dir, pose_style, ref_pose_coeff_path=None): - - with torch.no_grad(): - #test - results_dict_exp= self.audio2exp_model.test(batch) - exp_pred = results_dict_exp['exp_coeff_pred'] #bs T 64 - - #for class_id in range(1): - #class_id = 0#(i+10)%45 - #class_id = random.randint(0,46) #46 styles can be selected - batch['class'] = torch.LongTensor([pose_style]).to(self.device) - results_dict_pose = self.audio2pose_model.test(batch) - pose_pred = results_dict_pose['pose_pred'] #bs T 6 - - pose_len = pose_pred.shape[1] - if pose_len<13: - pose_len = int((pose_len-1)/2)*2+1 - pose_pred = torch.Tensor(savgol_filter(np.array(pose_pred.cpu()), pose_len, 2, axis=1)).to(self.device) - else: - pose_pred = torch.Tensor(savgol_filter(np.array(pose_pred.cpu()), 13, 2, axis=1)).to(self.device) - - coeffs_pred = torch.cat((exp_pred, pose_pred), dim=-1) #bs T 70 - - coeffs_pred_numpy = coeffs_pred[0].clone().detach().cpu().numpy() - - if ref_pose_coeff_path is not None: - coeffs_pred_numpy = self.using_refpose(coeffs_pred_numpy, ref_pose_coeff_path) - - savemat(os.path.join(coeff_save_dir, '%s##%s.mat'%(batch['pic_name'], batch['audio_name'])), - {'coeff_3dmm': coeffs_pred_numpy}) - - return os.path.join(coeff_save_dir, '%s##%s.mat'%(batch['pic_name'], batch['audio_name'])) - - def using_refpose(self, coeffs_pred_numpy, ref_pose_coeff_path): - num_frames = coeffs_pred_numpy.shape[0] - refpose_coeff_dict = loadmat(ref_pose_coeff_path) - refpose_coeff = refpose_coeff_dict['coeff_3dmm'][:,64:70] - refpose_num_frames = refpose_coeff.shape[0] - if refpose_num_frames= 0 - if hp.symmetric_mels: - return (2 * hp.max_abs_value) * ((S - hp.min_level_db) / (-hp.min_level_db)) - hp.max_abs_value - else: - return hp.max_abs_value * ((S - hp.min_level_db) / (-hp.min_level_db)) - -def _denormalize(D): - if hp.allow_clipping_in_normalization: - if hp.symmetric_mels: - return (((np.clip(D, -hp.max_abs_value, - hp.max_abs_value) + hp.max_abs_value) * -hp.min_level_db / (2 * hp.max_abs_value)) - + hp.min_level_db) - else: - return ((np.clip(D, 0, hp.max_abs_value) * -hp.min_level_db / hp.max_abs_value) + hp.min_level_db) - - if hp.symmetric_mels: - return (((D + hp.max_abs_value) * -hp.min_level_db / (2 * hp.max_abs_value)) + hp.min_level_db) - else: - return ((D * -hp.min_level_db / hp.max_abs_value) + hp.min_level_db) diff --git a/src/utils/croper.py b/src/utils/croper.py deleted file mode 100644 index b435ac5..0000000 --- a/src/utils/croper.py +++ /dev/null @@ -1,144 +0,0 @@ -import os -import cv2 -import time -import glob -import argparse -import scipy -import numpy as np -from PIL import Image -import torch -from tqdm import tqdm -from itertools import cycle - -from custom_nodes.ComfyUI_Lam.src.face3d.extract_kp_videos_safe import KeypointExtractor -from facexlib.alignment import landmark_98_to_68 - -import numpy as np -from PIL import Image - -class Preprocesser: - def __init__(self, device='cuda'): - self.predictor = KeypointExtractor(device) - - def get_landmark(self, img_np): - """get landmark with dlib - :return: np.array shape=(68, 2) - """ - with torch.no_grad(): - dets = self.predictor.det_net.detect_faces(img_np, 0.97) - - if len(dets) == 0: - return None - det = dets[0] - - img = img_np[int(det[1]):int(det[3]), int(det[0]):int(det[2]), :] - lm = landmark_98_to_68(self.predictor.detector.get_landmarks(img)) # [0] - - #### keypoints to the original location - lm[:,0] += int(det[0]) - lm[:,1] += int(det[1]) - - return lm - - def align_face(self, img, lm, output_size=1024): - """ - :param filepath: str - :return: PIL Image - """ - lm_chin = lm[0: 17] # left-right - lm_eyebrow_left = lm[17: 22] # left-right - lm_eyebrow_right = lm[22: 27] # left-right - lm_nose = lm[27: 31] # top-down - lm_nostrils = lm[31: 36] # top-down - lm_eye_left = lm[36: 42] # left-clockwise - lm_eye_right = lm[42: 48] # left-clockwise - lm_mouth_outer = lm[48: 60] # left-clockwise - lm_mouth_inner = lm[60: 68] # left-clockwise - - # Calculate auxiliary vectors. - eye_left = np.mean(lm_eye_left, axis=0) - eye_right = np.mean(lm_eye_right, axis=0) - eye_avg = (eye_left + eye_right) * 0.5 - eye_to_eye = eye_right - eye_left - mouth_left = lm_mouth_outer[0] - mouth_right = lm_mouth_outer[6] - mouth_avg = (mouth_left + mouth_right) * 0.5 - eye_to_mouth = mouth_avg - eye_avg - - # Choose oriented crop rectangle. - x = eye_to_eye - np.flipud(eye_to_mouth) * [-1, 1] # Addition of binocular difference and double mouth difference - x /= np.hypot(*x) # hypot函数计算直角三角形的斜边长,用斜边长对三角形两条直边做归一化 - x *= max(np.hypot(*eye_to_eye) * 2.0, np.hypot(*eye_to_mouth) * 1.8) # 双眼差和眼嘴差,选较大的作为基准尺度 - y = np.flipud(x) * [-1, 1] - c = eye_avg + eye_to_mouth * 0.1 - quad = np.stack([c - x - y, c - x + y, c + x + y, c + x - y]) # 定义四边形,以面部基准位置为中心上下左右平移得到四个顶点 - qsize = np.hypot(*x) * 2 # 定义四边形的大小(边长),为基准尺度的2倍 - - # Shrink. - # 如果计算出的四边形太大了,就按比例缩小它 - shrink = int(np.floor(qsize / output_size * 0.5)) - if shrink > 1: - rsize = (int(np.rint(float(img.size[0]) / shrink)), int(np.rint(float(img.size[1]) / shrink))) - img = img.resize(rsize, Image.ANTIALIAS) - quad /= shrink - qsize /= shrink - else: - rsize = (int(np.rint(float(img.size[0]))), int(np.rint(float(img.size[1])))) - - # Crop. - border = max(int(np.rint(qsize * 0.1)), 3) - crop = (int(np.floor(min(quad[:, 0]))), int(np.floor(min(quad[:, 1]))), int(np.ceil(max(quad[:, 0]))), - int(np.ceil(max(quad[:, 1])))) - crop = (max(crop[0] - border, 0), max(crop[1] - border, 0), min(crop[2] + border, img.size[0]), - min(crop[3] + border, img.size[1])) - if crop[2] - crop[0] < img.size[0] or crop[3] - crop[1] < img.size[1]: - # img = img.crop(crop) - quad -= crop[0:2] - - # Pad. - pad = (int(np.floor(min(quad[:, 0]))), int(np.floor(min(quad[:, 1]))), int(np.ceil(max(quad[:, 0]))), - int(np.ceil(max(quad[:, 1])))) - pad = (max(-pad[0] + border, 0), max(-pad[1] + border, 0), max(pad[2] - img.size[0] + border, 0), - max(pad[3] - img.size[1] + border, 0)) - # if enable_padding and max(pad) > border - 4: - # pad = np.maximum(pad, int(np.rint(qsize * 0.3))) - # img = np.pad(np.float32(img), ((pad[1], pad[3]), (pad[0], pad[2]), (0, 0)), 'reflect') - # h, w, _ = img.shape - # y, x, _ = np.ogrid[:h, :w, :1] - # mask = np.maximum(1.0 - np.minimum(np.float32(x) / pad[0], np.float32(w - 1 - x) / pad[2]), - # 1.0 - np.minimum(np.float32(y) / pad[1], np.float32(h - 1 - y) / pad[3])) - # blur = qsize * 0.02 - # img += (scipy.ndimage.gaussian_filter(img, [blur, blur, 0]) - img) * np.clip(mask * 3.0 + 1.0, 0.0, 1.0) - # img += (np.median(img, axis=(0, 1)) - img) * np.clip(mask, 0.0, 1.0) - # img = Image.fromarray(np.uint8(np.clip(np.rint(img), 0, 255)), 'RGB') - # quad += pad[:2] - - # Transform. - quad = (quad + 0.5).flatten() - lx = max(min(quad[0], quad[2]), 0) - ly = max(min(quad[1], quad[7]), 0) - rx = min(max(quad[4], quad[6]), img.size[0]) - ry = min(max(quad[3], quad[5]), img.size[0]) - - # Save aligned image. - return rsize, crop, [lx, ly, rx, ry] - - def crop(self, img_np_list, still=False, xsize=512): # first frame for all video - img_np = img_np_list[0] - lm = self.get_landmark(img_np) - - if lm is None: - raise 'can not detect the landmark from source image' - rsize, crop, quad = self.align_face(img=Image.fromarray(img_np), lm=lm, output_size=xsize) - clx, cly, crx, cry = crop - lx, ly, rx, ry = quad - lx, ly, rx, ry = int(lx), int(ly), int(rx), int(ry) - for _i in range(len(img_np_list)): - _inp = img_np_list[_i] - _inp = cv2.resize(_inp, (rsize[0], rsize[1])) - _inp = _inp[cly:cry, clx:crx] - if not still: - _inp = _inp[ly:ry, lx:rx] - img_np_list[_i] = _inp - return img_np_list, crop, quad - diff --git a/src/utils/croper_two.py b/src/utils/croper_two.py deleted file mode 100644 index 1c9b684..0000000 --- a/src/utils/croper_two.py +++ /dev/null @@ -1,296 +0,0 @@ -import os -import cv2 -import time -import glob -import argparse -import scipy -import numpy as np -from PIL import Image -from tqdm import tqdm -from itertools import cycle - -from torch.multiprocessing import Pool, Process, set_start_method - - -""" -brief: face alignment with FFHQ method (https://github.com/NVlabs/ffhq-dataset) -author: lzhbrian (https://lzhbrian.me) -date: 2020.1.5 -note: code is heavily borrowed from - https://github.com/NVlabs/ffhq-dataset - http://dlib.net/face_landmark_detection.py.html -requirements: - apt install cmake - conda install Pillow numpy scipy - pip install dlib - # download face landmark model from: - # http://dlib.net/files/shape_predictor_68_face_landmarks.dat.bz2 -""" - -import numpy as np -from PIL import Image -import dlib - - -class Croper: - def __init__(self, path_of_lm): - # download model from: http://dlib.net/files/shape_predictor_68_face_landmarks.dat.bz2 - self.predictor = dlib.shape_predictor(path_of_lm) - - def get_landmark(self, img_np): - """get landmark with dlib - :return: np.array shape=(68, 2) - """ - detector = dlib.get_frontal_face_detector() - dets = detector(img_np, 1) - # print("Number of faces detected: {}".format(len(dets))) - # for k, d in enumerate(dets): - if len(dets) == 0: - return None - d = dets[0] - # Get the landmarks/parts for the face in box d. - shape = self.predictor(img_np, d) - # print("Part 0: {}, Part 1: {} ...".format(shape.part(0), shape.part(1))) - t = list(shape.parts()) - a = [] - for tt in t: - a.append([tt.x, tt.y]) - lm = np.array(a) - # lm is a shape=(68,2) np.array - return lm - - def align_face(self, img, lm, output_size=1024): - """ - :param filepath: str - :return: PIL Image - """ - lm_chin = lm[0: 17] # left-right - lm_eyebrow_left = lm[17: 22] # left-right - lm_eyebrow_right = lm[22: 27] # left-right - lm_nose = lm[27: 31] # top-down - lm_nostrils = lm[31: 36] # top-down - lm_eye_left = lm[36: 42] # left-clockwise - lm_eye_right = lm[42: 48] # left-clockwise - lm_mouth_outer = lm[48: 60] # left-clockwise - lm_mouth_inner = lm[60: 68] # left-clockwise - - # Calculate auxiliary vectors. - eye_left = np.mean(lm_eye_left, axis=0) - eye_right = np.mean(lm_eye_right, axis=0) - eye_avg = (eye_left + eye_right) * 0.5 - eye_to_eye = eye_right - eye_left - mouth_left = lm_mouth_outer[0] - mouth_right = lm_mouth_outer[6] - mouth_avg = (mouth_left + mouth_right) * 0.5 - eye_to_mouth = mouth_avg - eye_avg - - # Choose oriented crop rectangle. - x = eye_to_eye - np.flipud(eye_to_mouth) * [-1, 1] # 双眼差与双嘴差相加 - x /= np.hypot(*x) # hypot函数计算直角三角形的斜边长,用斜边长对三角形两条直边做归一化 - x *= max(np.hypot(*eye_to_eye) * 2.0, np.hypot(*eye_to_mouth) * 1.8) # 双眼差和眼嘴差,选较大的作为基准尺度 - y = np.flipud(x) * [-1, 1] - c = eye_avg + eye_to_mouth * 0.1 - quad = np.stack([c - x - y, c - x + y, c + x + y, c + x - y]) # 定义四边形,以面部基准位置为中心上下左右平移得到四个顶点 - qsize = np.hypot(*x) * 2 # 定义四边形的大小(边长),为基准尺度的2倍 - - # Shrink. - # 如果计算出的四边形太大了,就按比例缩小它 - shrink = int(np.floor(qsize / output_size * 0.5)) - if shrink > 1: - rsize = (int(np.rint(float(img.size[0]) / shrink)), int(np.rint(float(img.size[1]) / shrink))) - img = img.resize(rsize, Image.ANTIALIAS) - quad /= shrink - qsize /= shrink - - # Crop. - border = max(int(np.rint(qsize * 0.1)), 3) - crop = (int(np.floor(min(quad[:, 0]))), int(np.floor(min(quad[:, 1]))), int(np.ceil(max(quad[:, 0]))), - int(np.ceil(max(quad[:, 1])))) - crop = (max(crop[0] - border, 0), max(crop[1] - border, 0), min(crop[2] + border, img.size[0]), - min(crop[3] + border, img.size[1])) - if crop[2] - crop[0] < img.size[0] or crop[3] - crop[1] < img.size[1]: - # img = img.crop(crop) - quad -= crop[0:2] - - # Pad. - pad = (int(np.floor(min(quad[:, 0]))), int(np.floor(min(quad[:, 1]))), int(np.ceil(max(quad[:, 0]))), - int(np.ceil(max(quad[:, 1])))) - pad = (max(-pad[0] + border, 0), max(-pad[1] + border, 0), max(pad[2] - img.size[0] + border, 0), - max(pad[3] - img.size[1] + border, 0)) - # if enable_padding and max(pad) > border - 4: - # pad = np.maximum(pad, int(np.rint(qsize * 0.3))) - # img = np.pad(np.float32(img), ((pad[1], pad[3]), (pad[0], pad[2]), (0, 0)), 'reflect') - # h, w, _ = img.shape - # y, x, _ = np.ogrid[:h, :w, :1] - # mask = np.maximum(1.0 - np.minimum(np.float32(x) / pad[0], np.float32(w - 1 - x) / pad[2]), - # 1.0 - np.minimum(np.float32(y) / pad[1], np.float32(h - 1 - y) / pad[3])) - # blur = qsize * 0.02 - # img += (scipy.ndimage.gaussian_filter(img, [blur, blur, 0]) - img) * np.clip(mask * 3.0 + 1.0, 0.0, 1.0) - # img += (np.median(img, axis=(0, 1)) - img) * np.clip(mask, 0.0, 1.0) - # img = Image.fromarray(np.uint8(np.clip(np.rint(img), 0, 255)), 'RGB') - # quad += pad[:2] - - # Transform. - quad = (quad + 0.5).flatten() - lx = max(min(quad[0], quad[2]), 0) - ly = max(min(quad[1], quad[7]), 0) - rx = min(max(quad[4], quad[6]), img.size[0]) - ry = min(max(quad[3], quad[5]), img.size[0]) - # img = img.transform((transform_size, transform_size), Image.QUAD, (quad + 0.5).flatten(), - # Image.BILINEAR) - # if output_size < transform_size: - # img = img.resize((output_size, output_size), Image.ANTIALIAS) - - # Save aligned image. - return crop, [lx, ly, rx, ry] - - # def crop(self, img_np_list): - # for _i in range(len(img_np_list)): - # img_np = img_np_list[_i] - # lm = self.get_landmark(img_np) - # if lm is None: - # return None - # crop, quad = self.align_face(img=Image.fromarray(img_np), lm=lm, output_size=512) - # clx, cly, crx, cry = crop - # lx, ly, rx, ry = quad - # lx, ly, rx, ry = int(lx), int(ly), int(rx), int(ry) - - # _inp = img_np_list[_i] - # _inp = _inp[cly:cry, clx:crx] - # _inp = _inp[ly:ry, lx:rx] - # img_np_list[_i] = _inp - # return img_np_list - - def crop(self, img_np_list, still=False, xsize=512): # first frame for all video - img_np = img_np_list[0] - lm = self.get_landmark(img_np) - if lm is None: - return None - crop, quad = self.align_face(img=Image.fromarray(img_np), lm=lm, output_size=xsize) - clx, cly, crx, cry = crop - lx, ly, rx, ry = quad - lx, ly, rx, ry = int(lx), int(ly), int(rx), int(ry) - for _i in range(len(img_np_list)): - _inp = img_np_list[_i] - _inp = _inp[cly:cry, clx:crx] - # cv2.imwrite('test1.jpg', _inp) - if not still: - _inp = _inp[ly:ry, lx:rx] - # cv2.imwrite('test2.jpg', _inp) - img_np_list[_i] = _inp - return img_np_list, crop, quad - - -def read_video(filename, uplimit=100): - frames = [] - cap = cv2.VideoCapture(filename) - cnt = 0 - while cap.isOpened(): - ret, frame = cap.read() - if ret: - frame = cv2.resize(frame, (512, 512)) - frames.append(frame) - else: - break - cnt += 1 - if cnt >= uplimit: - break - cap.release() - assert len(frames) > 0, f'{filename}: video with no frames!' - return frames - - -def create_video(video_name, frames, fps=25, video_format='.mp4', resize_ratio=1): - # video_name = os.path.dirname(image_folder) + video_format - # img_list = glob.glob1(image_folder, 'frame*') - # img_list.sort() - # frame = cv2.imread(os.path.join(image_folder, img_list[0])) - # frame = cv2.resize(frame, (0, 0), fx=resize_ratio, fy=resize_ratio) - # height, width, layers = frames[0].shape - height, width, layers = 512, 512, 3 - if video_format == '.mp4': - fourcc = cv2.VideoWriter_fourcc(*'mp4v') - elif video_format == '.avi': - fourcc = cv2.VideoWriter_fourcc(*'XVID') - video = cv2.VideoWriter(video_name, fourcc, fps, (width, height)) - for _frame in frames: - _frame = cv2.resize(_frame, (height, width), interpolation=cv2.INTER_LINEAR) - video.write(_frame) - -def create_images(video_name, frames): - height, width, layers = 512, 512, 3 - images_dir = video_name.split('.')[0] - os.makedirs(images_dir, exist_ok=True) - for i, _frame in enumerate(frames): - _frame = cv2.resize(_frame, (height, width), interpolation=cv2.INTER_LINEAR) - _frame_path = os.path.join(images_dir, str(i)+'.jpg') - cv2.imwrite(_frame_path, _frame) - -def run(data): - filename, opt, device = data - os.environ['CUDA_VISIBLE_DEVICES'] = device - croper = Croper() - - frames = read_video(filename, uplimit=opt.uplimit) - name = filename.split('/')[-1] # .split('.')[0] - name = os.path.join(opt.output_dir, name) - - frames = croper.crop(frames) - if frames is None: - print(f'{name}: detect no face. should removed') - return - # create_video(name, frames) - create_images(name, frames) - - -def get_data_path(video_dir): - eg_video_files = ['/apdcephfs/share_1290939/quincheng/datasets/HDTF/backup_fps25/WDA_KatieHill_000.mp4'] - # filenames = list() - # VIDEO_EXTENSIONS_LOWERCASE = {'mp4'} - # VIDEO_EXTENSIONS = VIDEO_EXTENSIONS_LOWERCASE.union({f.upper() for f in VIDEO_EXTENSIONS_LOWERCASE}) - # extensions = VIDEO_EXTENSIONS - # for ext in extensions: - # filenames = sorted(glob.glob(f'{opt.input_dir}/**/*.{ext}')) - # print('Total number of videos:', len(filenames)) - return eg_video_files - - -def get_wra_data_path(video_dir): - if opt.option == 'video': - videos_path = sorted(glob.glob(f'{video_dir}/*.mp4')) - elif opt.option == 'image': - videos_path = sorted(glob.glob(f'{video_dir}/*/')) - else: - raise NotImplementedError - print('Example videos: ', videos_path[:2]) - return videos_path - - -if __name__ == '__main__': - set_start_method('spawn') - parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) - parser.add_argument('--input_dir', type=str, help='the folder of the input files') - parser.add_argument('--output_dir', type=str, help='the folder of the output files') - parser.add_argument('--device_ids', type=str, default='0,1') - parser.add_argument('--workers', type=int, default=8) - parser.add_argument('--uplimit', type=int, default=500) - parser.add_argument('--option', type=str, default='video') - - root = '/apdcephfs/share_1290939/quincheng/datasets/HDTF' - cmd = f'--input_dir {root}/backup_fps25_first20s_sync/ ' \ - f'--output_dir {root}/crop512_stylegan_firstframe_sync/ ' \ - '--device_ids 0 ' \ - '--workers 8 ' \ - '--option video ' \ - '--uplimit 500 ' - opt = parser.parse_args(cmd.split()) - # filenames = get_data_path(opt.input_dir) - filenames = get_wra_data_path(opt.input_dir) - os.makedirs(opt.output_dir, exist_ok=True) - print(f'Video numbers: {len(filenames)}') - pool = Pool(opt.workers) - args_list = cycle([opt]) - device_ids = opt.device_ids.split(",") - device_ids = cycle(device_ids) - for data in tqdm(pool.imap_unordered(run, zip(filenames, args_list, device_ids))): - None \ No newline at end of file diff --git a/src/utils/face_enhancer.py b/src/utils/face_enhancer.py deleted file mode 100644 index 432a75e..0000000 --- a/src/utils/face_enhancer.py +++ /dev/null @@ -1,124 +0,0 @@ -import os -import torch -import folder_paths - -from gfpgan import GFPGANer - -from tqdm import tqdm - -from custom_nodes.ComfyUI_Lam.src.utils.videoio import load_video_to_cv2 - -import cv2 - - -class GeneratorWithLen(object): - """ From https://stackoverflow.com/a/7460929 """ - - def __init__(self, gen, length): - self.gen = gen - self.length = length - - def __len__(self): - return self.length - - def __iter__(self): - return self.gen - -def enhancer_list(images, method='gfpgan', bg_upsampler='realesrgan'): - gen = enhancer_generator_no_len(images, method=method, bg_upsampler=bg_upsampler) - return list(gen) - -def enhancer_generator_with_len(images, method='gfpgan', bg_upsampler='realesrgan'): - """ Provide a generator with a __len__ method so that it can passed to functions that - call len()""" - - if os.path.isfile(images): # handle video to images - # TODO: Create a generator version of load_video_to_cv2 - images = load_video_to_cv2(images) - - gen = enhancer_generator_no_len(images, method=method, bg_upsampler=bg_upsampler) - gen_with_len = GeneratorWithLen(gen, len(images)) - return gen_with_len - -def enhancer_generator_no_len(images, method='gfpgan', bg_upsampler='realesrgan'): - """ Provide a generator function so that all of the enhanced images don't need - to be stored in memory at the same time. This can save tons of RAM compared to - the enhancer function. """ - - print('face enhancer....') - if not isinstance(images, list) and os.path.isfile(images): # handle video to images - images = load_video_to_cv2(images) - - # ------------------------ set up GFPGAN restorer ------------------------ - if method == 'gfpgan': - arch = 'clean' - channel_multiplier = 2 - model_name = 'GFPGANv1.4' - url = 'https://github.com/TencentARC/GFPGAN/releases/download/v1.3.0/GFPGANv1.4.pth' - elif method == 'RestoreFormer': - arch = 'RestoreFormer' - channel_multiplier = 2 - model_name = 'RestoreFormer' - url = 'https://github.com/TencentARC/GFPGAN/releases/download/v1.3.4/RestoreFormer.pth' - elif method == 'codeformer': # TODO: - arch = 'CodeFormer' - channel_multiplier = 2 - model_name = 'CodeFormer' - url = 'https://github.com/sczhou/CodeFormer/releases/download/v0.1.0/codeformer.pth' - else: - raise ValueError(f'Wrong model version {method}.') - - - # ------------------------ set up background upsampler ------------------------ - if bg_upsampler == 'realesrgan': - if not torch.cuda.is_available(): # CPU - import warnings - warnings.warn('The unoptimized RealESRGAN is slow on CPU. We do not use it. ' - 'If you really want to use it, please modify the corresponding codes.') - bg_upsampler = None - else: - from basicsr.archs.rrdbnet_arch import RRDBNet - from realesrgan import RealESRGANer - model = RRDBNet(num_in_ch=3, num_out_ch=3, num_feat=64, num_block=23, num_grow_ch=32, scale=2) - bg_upsampler = RealESRGANer( - scale=2, - model_path='https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.1/RealESRGAN_x2plus.pth', - model=model, - tile=400, - tile_pad=10, - pre_pad=0, - half=True) # need to set False in CPU mode - else: - bg_upsampler = None - - # determine model paths - model_path = os.path.join('gfpgan/weights', model_name + '.pth') - - if not os.path.isfile(model_path): - model_path = os.path.join('checkpoints', model_name + '.pth') - - if not os.path.isfile(model_path): - # download pre-trained models from url - model_path = url - - restorer = GFPGANer( - model_path=model_path, - upscale=2, - arch=arch, - channel_multiplier=channel_multiplier, - bg_upsampler=bg_upsampler) - - # ------------------------ restore ------------------------ - for idx in tqdm(range(len(images)), 'Face Enhancer:'): - - img = cv2.cvtColor(images[idx], cv2.COLOR_RGB2BGR) - - # restore faces and background if necessary - cropped_faces, restored_faces, r_img = restorer.enhance( - img, - has_aligned=False, - only_center_face=False, - paste_back=True) - - r_img = cv2.cvtColor(r_img, cv2.COLOR_BGR2RGB) - yield r_img diff --git a/src/utils/hparams.py b/src/utils/hparams.py deleted file mode 100644 index 743c5c7..0000000 --- a/src/utils/hparams.py +++ /dev/null @@ -1,160 +0,0 @@ -from glob import glob -import os - -class HParams: - def __init__(self, **kwargs): - self.data = {} - - for key, value in kwargs.items(): - self.data[key] = value - - def __getattr__(self, key): - if key not in self.data: - raise AttributeError("'HParams' object has no attribute %s" % key) - return self.data[key] - - def set_hparam(self, key, value): - self.data[key] = value - - -# Default hyperparameters -hparams = HParams( - num_mels=80, # Number of mel-spectrogram channels and local conditioning dimensionality - # network - rescale=True, # Whether to rescale audio prior to preprocessing - rescaling_max=0.9, # Rescaling value - - # Use LWS (https://github.com/Jonathan-LeRoux/lws) for STFT and phase reconstruction - # It"s preferred to set True to use with https://github.com/r9y9/wavenet_vocoder - # Does not work if n_ffit is not multiple of hop_size!! - use_lws=False, - - n_fft=800, # Extra window size is filled with 0 paddings to match this parameter - hop_size=200, # For 16000Hz, 200 = 12.5 ms (0.0125 * sample_rate) - win_size=800, # For 16000Hz, 800 = 50 ms (If None, win_size = n_fft) (0.05 * sample_rate) - sample_rate=16000, # 16000Hz (corresponding to librispeech) (sox --i ) - - frame_shift_ms=None, # Can replace hop_size parameter. (Recommended: 12.5) - - # Mel and Linear spectrograms normalization/scaling and clipping - signal_normalization=True, - # Whether to normalize mel spectrograms to some predefined range (following below parameters) - allow_clipping_in_normalization=True, # Only relevant if mel_normalization = True - symmetric_mels=True, - # Whether to scale the data to be symmetric around 0. (Also multiplies the output range by 2, - # faster and cleaner convergence) - max_abs_value=4., - # max absolute value of data. If symmetric, data will be [-max, max] else [0, max] (Must not - # be too big to avoid gradient explosion, - # not too small for fast convergence) - # Contribution by @begeekmyfriend - # Spectrogram Pre-Emphasis (Lfilter: Reduce spectrogram noise and helps model certitude - # levels. Also allows for better G&L phase reconstruction) - preemphasize=True, # whether to apply filter - preemphasis=0.97, # filter coefficient. - - # Limits - min_level_db=-100, - ref_level_db=20, - fmin=55, - # Set this to 55 if your speaker is male! if female, 95 should help taking off noise. (To - # test depending on dataset. Pitch info: male~[65, 260], female~[100, 525]) - fmax=7600, # To be increased/reduced depending on data. - - ###################### Our training parameters ################################# - img_size=96, - fps=25, - - batch_size=16, - initial_learning_rate=1e-4, - nepochs=300000, ### ctrl + c, stop whenever eval loss is consistently greater than train loss for ~10 epochs - num_workers=20, - checkpoint_interval=3000, - eval_interval=3000, - writer_interval=300, - save_optimizer_state=True, - - syncnet_wt=0.0, # is initially zero, will be set automatically to 0.03 later. Leads to faster convergence. - syncnet_batch_size=64, - syncnet_lr=1e-4, - syncnet_eval_interval=1000, - syncnet_checkpoint_interval=10000, - - disc_wt=0.07, - disc_initial_learning_rate=1e-4, -) - - - -# Default hyperparameters -hparamsdebug = HParams( - num_mels=80, # Number of mel-spectrogram channels and local conditioning dimensionality - # network - rescale=True, # Whether to rescale audio prior to preprocessing - rescaling_max=0.9, # Rescaling value - - # Use LWS (https://github.com/Jonathan-LeRoux/lws) for STFT and phase reconstruction - # It"s preferred to set True to use with https://github.com/r9y9/wavenet_vocoder - # Does not work if n_ffit is not multiple of hop_size!! - use_lws=False, - - n_fft=800, # Extra window size is filled with 0 paddings to match this parameter - hop_size=200, # For 16000Hz, 200 = 12.5 ms (0.0125 * sample_rate) - win_size=800, # For 16000Hz, 800 = 50 ms (If None, win_size = n_fft) (0.05 * sample_rate) - sample_rate=16000, # 16000Hz (corresponding to librispeech) (sox --i ) - - frame_shift_ms=None, # Can replace hop_size parameter. (Recommended: 12.5) - - # Mel and Linear spectrograms normalization/scaling and clipping - signal_normalization=True, - # Whether to normalize mel spectrograms to some predefined range (following below parameters) - allow_clipping_in_normalization=True, # Only relevant if mel_normalization = True - symmetric_mels=True, - # Whether to scale the data to be symmetric around 0. (Also multiplies the output range by 2, - # faster and cleaner convergence) - max_abs_value=4., - # max absolute value of data. If symmetric, data will be [-max, max] else [0, max] (Must not - # be too big to avoid gradient explosion, - # not too small for fast convergence) - # Contribution by @begeekmyfriend - # Spectrogram Pre-Emphasis (Lfilter: Reduce spectrogram noise and helps model certitude - # levels. Also allows for better G&L phase reconstruction) - preemphasize=True, # whether to apply filter - preemphasis=0.97, # filter coefficient. - - # Limits - min_level_db=-100, - ref_level_db=20, - fmin=55, - # Set this to 55 if your speaker is male! if female, 95 should help taking off noise. (To - # test depending on dataset. Pitch info: male~[65, 260], female~[100, 525]) - fmax=7600, # To be increased/reduced depending on data. - - ###################### Our training parameters ################################# - img_size=96, - fps=25, - - batch_size=2, - initial_learning_rate=1e-3, - nepochs=100000, ### ctrl + c, stop whenever eval loss is consistently greater than train loss for ~10 epochs - num_workers=0, - checkpoint_interval=10000, - eval_interval=10, - writer_interval=5, - save_optimizer_state=True, - - syncnet_wt=0.0, # is initially zero, will be set automatically to 0.03 later. Leads to faster convergence. - syncnet_batch_size=64, - syncnet_lr=1e-4, - syncnet_eval_interval=10000, - syncnet_checkpoint_interval=10000, - - disc_wt=0.07, - disc_initial_learning_rate=1e-4, -) - - -def hparams_debug_string(): - values = hparams.values() - hp = [" %s: %s" % (name, values[name]) for name in sorted(values) if name != "sentences"] - return "Hyperparameters:\n" + "\n".join(hp) diff --git a/src/utils/init_path.py b/src/utils/init_path.py deleted file mode 100644 index 5f38d11..0000000 --- a/src/utils/init_path.py +++ /dev/null @@ -1,47 +0,0 @@ -import os -import glob - -def init_path(checkpoint_dir, config_dir, size=512, old_version=False, preprocess='crop'): - - if old_version: - #### load all the checkpoint of `pth` - sadtalker_paths = { - 'wav2lip_checkpoint' : os.path.join(checkpoint_dir, 'wav2lip.pth'), - 'audio2pose_checkpoint' : os.path.join(checkpoint_dir, 'auido2pose_00140-model.pth'), - 'audio2exp_checkpoint' : os.path.join(checkpoint_dir, 'auido2exp_00300-model.pth'), - 'free_view_checkpoint' : os.path.join(checkpoint_dir, 'facevid2vid_00189-model.pth.tar'), - 'path_of_net_recon_model' : os.path.join(checkpoint_dir, 'epoch_20.pth') - } - - use_safetensor = False - elif len(glob.glob(os.path.join(checkpoint_dir, '*.safetensors'))): - print('using safetensor as default') - sadtalker_paths = { - "checkpoint":os.path.join(checkpoint_dir, 'SadTalker_V0.0.2_'+str(size)+'.safetensors'), - } - use_safetensor = True - else: - print("WARNING: The new version of the model will be updated by safetensor, you may need to download it mannully. We run the old version of the checkpoint this time!") - use_safetensor = False - - sadtalker_paths = { - 'wav2lip_checkpoint' : os.path.join(checkpoint_dir, 'wav2lip.pth'), - 'audio2pose_checkpoint' : os.path.join(checkpoint_dir, 'auido2pose_00140-model.pth'), - 'audio2exp_checkpoint' : os.path.join(checkpoint_dir, 'auido2exp_00300-model.pth'), - 'free_view_checkpoint' : os.path.join(checkpoint_dir, 'facevid2vid_00189-model.pth.tar'), - 'path_of_net_recon_model' : os.path.join(checkpoint_dir, 'epoch_20.pth') - } - - sadtalker_paths['dir_of_BFM_fitting'] = os.path.join(config_dir) # , 'BFM_Fitting' - sadtalker_paths['audio2pose_yaml_path'] = os.path.join(config_dir, 'auido2pose.yaml') - sadtalker_paths['audio2exp_yaml_path'] = os.path.join(config_dir, 'auido2exp.yaml') - sadtalker_paths['use_safetensor'] = use_safetensor # os.path.join(config_dir, 'auido2exp.yaml') - - if 'full' in preprocess: - sadtalker_paths['mappingnet_checkpoint'] = os.path.join(checkpoint_dir, 'mapping_00109-model.pth.tar') - sadtalker_paths['facerender_yaml'] = os.path.join(config_dir, 'facerender_still.yaml') - else: - sadtalker_paths['mappingnet_checkpoint'] = os.path.join(checkpoint_dir, 'mapping_00229-model.pth.tar') - sadtalker_paths['facerender_yaml'] = os.path.join(config_dir, 'facerender.yaml') - - return sadtalker_paths \ No newline at end of file diff --git a/src/utils/model2safetensor.py b/src/utils/model2safetensor.py deleted file mode 100644 index fed7f63..0000000 --- a/src/utils/model2safetensor.py +++ /dev/null @@ -1,141 +0,0 @@ -import torch -import yaml -import os - -import safetensors -from safetensors.torch import save_file -from yacs.config import CfgNode as CN -import sys - -sys.path.append('/apdcephfs/private_shadowcun/SadTalker') - -from custom_nodes.ComfyUI_Lam.src.face3d.models import networks - -from custom_nodes.ComfyUI_Lam.src.facerender.modules.keypoint_detector import HEEstimator, KPDetector -from custom_nodes.ComfyUI_Lam.src.facerender.modules.mapping import MappingNet -from custom_nodes.ComfyUI_Lam.src.facerender.modules.generator import OcclusionAwareGenerator, OcclusionAwareSPADEGenerator - -from custom_nodes.ComfyUI_Lam.src.audio2pose_models.audio2pose import Audio2Pose -from custom_nodes.ComfyUI_Lam.src.audio2exp_models.networks import SimpleWrapperV2 -from custom_nodes.ComfyUI_Lam.src.test_audio2coeff import load_cpk - -size = 256 -############ face vid2vid -config_path = os.path.join('src', 'config', 'facerender.yaml') -current_root_path = '.' - -path_of_net_recon_model = os.path.join(current_root_path, 'checkpoints', 'epoch_20.pth') -net_recon = networks.define_net_recon(net_recon='resnet50', use_last_fc=False, init_path='') -checkpoint = torch.load(path_of_net_recon_model, map_location='cpu') -net_recon.load_state_dict(checkpoint['net_recon']) - -with open(config_path) as f: - config = yaml.safe_load(f) - -generator = OcclusionAwareSPADEGenerator(**config['model_params']['generator_params'], - **config['model_params']['common_params']) -kp_extractor = KPDetector(**config['model_params']['kp_detector_params'], - **config['model_params']['common_params']) -he_estimator = HEEstimator(**config['model_params']['he_estimator_params'], - **config['model_params']['common_params']) -mapping = MappingNet(**config['model_params']['mapping_params']) - -def load_cpk_facevid2vid(checkpoint_path, generator=None, discriminator=None, - kp_detector=None, he_estimator=None, optimizer_generator=None, - optimizer_discriminator=None, optimizer_kp_detector=None, - optimizer_he_estimator=None, device="cpu"): - - checkpoint = torch.load(checkpoint_path, map_location=torch.device(device)) - if generator is not None: - generator.load_state_dict(checkpoint['generator']) - if kp_detector is not None: - kp_detector.load_state_dict(checkpoint['kp_detector']) - if he_estimator is not None: - he_estimator.load_state_dict(checkpoint['he_estimator']) - if discriminator is not None: - try: - discriminator.load_state_dict(checkpoint['discriminator']) - except: - print ('No discriminator in the state-dict. Dicriminator will be randomly initialized') - if optimizer_generator is not None: - optimizer_generator.load_state_dict(checkpoint['optimizer_generator']) - if optimizer_discriminator is not None: - try: - optimizer_discriminator.load_state_dict(checkpoint['optimizer_discriminator']) - except RuntimeError as e: - print ('No discriminator optimizer in the state-dict. Optimizer will be not initialized') - if optimizer_kp_detector is not None: - optimizer_kp_detector.load_state_dict(checkpoint['optimizer_kp_detector']) - if optimizer_he_estimator is not None: - optimizer_he_estimator.load_state_dict(checkpoint['optimizer_he_estimator']) - - return checkpoint['epoch'] - - -def load_cpk_facevid2vid_safetensor(checkpoint_path, generator=None, - kp_detector=None, he_estimator=None, - device="cpu"): - - checkpoint = safetensors.torch.load_file(checkpoint_path) - - if generator is not None: - x_generator = {} - for k,v in checkpoint.items(): - if 'generator' in k: - x_generator[k.replace('generator.', '')] = v - generator.load_state_dict(x_generator) - if kp_detector is not None: - x_generator = {} - for k,v in checkpoint.items(): - if 'kp_extractor' in k: - x_generator[k.replace('kp_extractor.', '')] = v - kp_detector.load_state_dict(x_generator) - if he_estimator is not None: - x_generator = {} - for k,v in checkpoint.items(): - if 'he_estimator' in k: - x_generator[k.replace('he_estimator.', '')] = v - he_estimator.load_state_dict(x_generator) - - return None - -free_view_checkpoint = '/apdcephfs/private_shadowcun/SadTalker/checkpoints/facevid2vid_'+str(size)+'-model.pth.tar' -load_cpk_facevid2vid(free_view_checkpoint, kp_detector=kp_extractor, generator=generator, he_estimator=he_estimator) - -wav2lip_checkpoint = os.path.join(current_root_path, 'checkpoints', 'wav2lip.pth') - -audio2pose_checkpoint = os.path.join(current_root_path, 'checkpoints', 'auido2pose_00140-model.pth') -audio2pose_yaml_path = os.path.join(current_root_path, 'src', 'config', 'auido2pose.yaml') - -audio2exp_checkpoint = os.path.join(current_root_path, 'checkpoints', 'auido2exp_00300-model.pth') -audio2exp_yaml_path = os.path.join(current_root_path, 'src', 'config', 'auido2exp.yaml') - -fcfg_pose = open(audio2pose_yaml_path) -cfg_pose = CN.load_cfg(fcfg_pose) -cfg_pose.freeze() -audio2pose_model = Audio2Pose(cfg_pose, wav2lip_checkpoint) -audio2pose_model.eval() -load_cpk(audio2pose_checkpoint, model=audio2pose_model, device='cpu') - -# load audio2exp_model -netG = SimpleWrapperV2() -netG.eval() -load_cpk(audio2exp_checkpoint, model=netG, device='cpu') - -class SadTalker(torch.nn.Module): - def __init__(self, kp_extractor, generator, netG, audio2pose, face_3drecon): - super(SadTalker, self).__init__() - self.kp_extractor = kp_extractor - self.generator = generator - self.audio2exp = netG - self.audio2pose = audio2pose - self.face_3drecon = face_3drecon - - -model = SadTalker(kp_extractor, generator, netG, audio2pose_model, net_recon) - -# here, we want to convert it to safetensor -save_file(model.state_dict(), "checkpoints/SadTalker_V0.0.2_"+str(size)+".safetensors") - -### test -load_cpk_facevid2vid_safetensor('checkpoints/SadTalker_V0.0.2_'+str(size)+'.safetensors', kp_detector=kp_extractor, generator=generator, he_estimator=None) \ No newline at end of file diff --git a/src/utils/paste_pic.py b/src/utils/paste_pic.py deleted file mode 100644 index b972398..0000000 --- a/src/utils/paste_pic.py +++ /dev/null @@ -1,69 +0,0 @@ -import cv2, os -import numpy as np -from tqdm import tqdm -import uuid - -from custom_nodes.ComfyUI_Lam.src.utils.videoio import save_video_with_watermark - -def paste_pic(video_path, pic_path, crop_info, new_audio_path, full_video_path, extended_crop=False): - - if not os.path.isfile(pic_path): - raise ValueError('pic_path must be a valid path to video/image file') - elif pic_path.split('.')[-1] in ['jpg', 'png', 'jpeg']: - # loader for first frame - full_img = cv2.imread(pic_path) - else: - # loader for videos - video_stream = cv2.VideoCapture(pic_path) - fps = video_stream.get(cv2.CAP_PROP_FPS) - full_frames = [] - while 1: - still_reading, frame = video_stream.read() - if not still_reading: - video_stream.release() - break - break - full_img = frame - frame_h = full_img.shape[0] - frame_w = full_img.shape[1] - - video_stream = cv2.VideoCapture(video_path) - fps = video_stream.get(cv2.CAP_PROP_FPS) - crop_frames = [] - while 1: - still_reading, frame = video_stream.read() - if not still_reading: - video_stream.release() - break - crop_frames.append(frame) - - if len(crop_info) != 3: - print("you didn't crop the image") - return - else: - r_w, r_h = crop_info[0] - clx, cly, crx, cry = crop_info[1] - lx, ly, rx, ry = crop_info[2] - lx, ly, rx, ry = int(lx), int(ly), int(rx), int(ry) - # oy1, oy2, ox1, ox2 = cly+ly, cly+ry, clx+lx, clx+rx - # oy1, oy2, ox1, ox2 = cly+ly, cly+ry, clx+lx, clx+rx - - if extended_crop: - oy1, oy2, ox1, ox2 = cly, cry, clx, crx - else: - oy1, oy2, ox1, ox2 = cly+ly, cly+ry, clx+lx, clx+rx - - tmp_path = str(uuid.uuid4())+'.mp4' - out_tmp = cv2.VideoWriter(tmp_path, cv2.VideoWriter_fourcc(*'MP4V'), fps, (frame_w, frame_h)) - for crop_frame in tqdm(crop_frames, 'seamlessClone:'): - p = cv2.resize(crop_frame.astype(np.uint8), (ox2-ox1, oy2 - oy1)) - - mask = 255*np.ones(p.shape, p.dtype) - location = ((ox1+ox2) // 2, (oy1+oy2) // 2) - gen_img = cv2.seamlessClone(p, full_img, mask, location, cv2.NORMAL_CLONE) - out_tmp.write(gen_img) - - out_tmp.release() - - save_video_with_watermark(tmp_path, new_audio_path, full_video_path, watermark=False) - os.remove(tmp_path) diff --git a/src/utils/paste_pic_two.py b/src/utils/paste_pic_two.py deleted file mode 100644 index 8c3ccc1..0000000 --- a/src/utils/paste_pic_two.py +++ /dev/null @@ -1,70 +0,0 @@ -import cv2, os -import numpy as np -from tqdm import tqdm -import uuid -from custom_nodes.ComfyUI_Lam.src.inference_utils import Laplacian_Pyramid_Blending_with_mask - - -def paste_pic(video_path, pic_path, crop_info, new_audio_path, full_video_path, restorer, enhancer, enhancer_region="none"): - video_stream_input = cv2.VideoCapture(pic_path) - full_img_list = [] - while 1: - input_reading, full_img = video_stream_input.read() - if not input_reading: - video_stream_input.release() - break - full_img_list.append(full_img) - - frame_h = full_img_list[0].shape[0] - frame_w = full_img_list[0].shape[1] - - video_stream = cv2.VideoCapture(video_path) - fps = video_stream.get(cv2.CAP_PROP_FPS) - crop_frames = [] - while 1: - still_reading, frame = video_stream.read() - if not still_reading: - video_stream.release() - break - crop_frames.append(frame) - - if len(crop_info) != 3: - print("you didn't crop the image") - return - else: - clx, cly, crx, cry = crop_info[1] - paths=video_path.split('\\') - tmp_path = os.path.join(video_path[:(-1*len(paths[-1])-1)],str(uuid.uuid4()) + '.mp4') - out_tmp = cv2.VideoWriter(tmp_path, cv2.VideoWriter_fourcc(*'MP4V'), fps, (frame_w, frame_h)) - - for index, crop_frame in enumerate(tqdm(crop_frames, 'faceClone:')): - p = cv2.resize(crop_frame.astype(np.uint8), (crx - clx, cry - cly)) - - ff = full_img_list[index].copy() - ff[cly:cry, clx:crx] = p - if enhancer_region == 'none': - pp = ff - else: - cropped_faces, restored_faces, restored_img = restorer.enhance( - ff, has_aligned=False, only_center_face=True, paste_back=True) - if enhancer_region == 'lip': - mm = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 0, 0, 0, 0, 0, 0] - else: - mm = [0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 255, 255, 255, 0, 0, 0, 0, 0, 0] - mouse_mask = np.zeros_like(restored_img) - tmp_mask = enhancer.faceparser.process(restored_img[cly:cry, clx:crx], mm)[0] - mouse_mask[cly:cry, clx:crx] = cv2.resize(tmp_mask, (crx - clx, cry - cly))[:, :, np.newaxis] / 255. - - height, width = ff.shape[:2] - restored_img, ff, full_mask = [cv2.resize(x, (512, 512)) for x in - (restored_img, ff, np.float32(mouse_mask))] - img = Laplacian_Pyramid_Blending_with_mask(restored_img, ff, full_mask[:, :, 0], 10) - pp = np.uint8(cv2.resize(np.clip(img, 0, 255), (width, height))) - pp, orig_faces, enhanced_faces = enhancer.process(pp, full_img_list[index], bbox=[cly, cry, clx, crx], - face_enhance=False, possion_blending=True) - out_tmp.write(pp) - out_tmp.release() - cmd = r'ffmpeg -y -i "%s" -i "%s" -vcodec copy "%s"' % (tmp_path, new_audio_path, full_video_path) - os.system(cmd) - # os.remove(tmp_path) - return tmp_path, new_audio_path diff --git a/src/utils/preprocess.py b/src/utils/preprocess.py deleted file mode 100644 index ad852ce..0000000 --- a/src/utils/preprocess.py +++ /dev/null @@ -1,170 +0,0 @@ -import numpy as np -import cv2, os, sys, torch -from tqdm import tqdm -from PIL import Image - -# 3dmm extraction -import safetensors -import safetensors.torch -from custom_nodes.ComfyUI_Lam.src.face3d.util.preprocess import align_img -from custom_nodes.ComfyUI_Lam.src.face3d.util.load_mats import load_lm3d -from custom_nodes.ComfyUI_Lam.src.face3d.models import networks - -from scipy.io import loadmat, savemat -from custom_nodes.ComfyUI_Lam.src.utils.croper import Preprocesser - - -import warnings - -from custom_nodes.ComfyUI_Lam.src.utils.safetensor_helper import load_x_from_safetensor -warnings.filterwarnings("ignore") - -def split_coeff(coeffs): - """ - Return: - coeffs_dict -- a dict of torch.tensors - - Parameters: - coeffs -- torch.tensor, size (B, 256) - """ - id_coeffs = coeffs[:, :80] - exp_coeffs = coeffs[:, 80: 144] - tex_coeffs = coeffs[:, 144: 224] - angles = coeffs[:, 224: 227] - gammas = coeffs[:, 227: 254] - translations = coeffs[:, 254:] - return { - 'id': id_coeffs, - 'exp': exp_coeffs, - 'tex': tex_coeffs, - 'angle': angles, - 'gamma': gammas, - 'trans': translations - } - - -class CropAndExtract(): - def __init__(self, sadtalker_path, device): - - self.propress = Preprocesser(device) - self.net_recon = networks.define_net_recon(net_recon='resnet50', use_last_fc=False, init_path='').to(device) - - if sadtalker_path['use_safetensor']: - checkpoint = safetensors.torch.load_file(sadtalker_path['checkpoint']) - self.net_recon.load_state_dict(load_x_from_safetensor(checkpoint, 'face_3drecon')) - else: - checkpoint = torch.load(sadtalker_path['path_of_net_recon_model'], map_location=torch.device(device)) - self.net_recon.load_state_dict(checkpoint['net_recon']) - - self.net_recon.eval() - self.lm3d_std = load_lm3d(sadtalker_path['dir_of_BFM_fitting']) - self.device = device - - def generate(self, input_path, save_dir, crop_or_resize='crop', source_image_flag=False, pic_size=256): - - pic_name = os.path.splitext(os.path.split(input_path)[-1])[0] - - landmarks_path = os.path.join(save_dir, pic_name+'_landmarks.txt') - coeff_path = os.path.join(save_dir, pic_name+'.mat') - png_path = os.path.join(save_dir, pic_name+'.png') - - #load input - if not os.path.isfile(input_path): - raise ValueError('input_path must be a valid path to video/image file') - elif input_path.split('.')[-1] in ['jpg', 'png', 'jpeg']: - # loader for first frame - full_frames = [cv2.imread(input_path)] - fps = 25 - else: - # loader for videos - video_stream = cv2.VideoCapture(input_path) - fps = video_stream.get(cv2.CAP_PROP_FPS) - full_frames = [] - while 1: - still_reading, frame = video_stream.read() - if not still_reading: - video_stream.release() - break - full_frames.append(frame) - if source_image_flag: - break - - x_full_frames= [cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) for frame in full_frames] - - #### crop images as the - if 'crop' in crop_or_resize.lower(): # default crop - x_full_frames, crop, quad = self.propress.crop(x_full_frames, still=True if 'ext' in crop_or_resize.lower() else False, xsize=512) - clx, cly, crx, cry = crop - lx, ly, rx, ry = quad - lx, ly, rx, ry = int(lx), int(ly), int(rx), int(ry) - oy1, oy2, ox1, ox2 = cly+ly, cly+ry, clx+lx, clx+rx - crop_info = ((ox2 - ox1, oy2 - oy1), crop, quad) - elif 'full' in crop_or_resize.lower(): - x_full_frames, crop, quad = self.propress.crop(x_full_frames, still=True if 'ext' in crop_or_resize.lower() else False, xsize=512) - clx, cly, crx, cry = crop - lx, ly, rx, ry = quad - lx, ly, rx, ry = int(lx), int(ly), int(rx), int(ry) - oy1, oy2, ox1, ox2 = cly+ly, cly+ry, clx+lx, clx+rx - crop_info = ((ox2 - ox1, oy2 - oy1), crop, quad) - else: # resize mode - oy1, oy2, ox1, ox2 = 0, x_full_frames[0].shape[0], 0, x_full_frames[0].shape[1] - crop_info = ((ox2 - ox1, oy2 - oy1), None, None) - - frames_pil = [Image.fromarray(cv2.resize(frame,(pic_size, pic_size))) for frame in x_full_frames] - if len(frames_pil) == 0: - print('No face is detected in the input file') - return None, None - - # save crop info - for frame in frames_pil: - cv2.imwrite(png_path, cv2.cvtColor(np.array(frame), cv2.COLOR_RGB2BGR)) - - # 2. get the landmark according to the detected face. - if not os.path.isfile(landmarks_path): - lm = self.propress.predictor.extract_keypoint(frames_pil, landmarks_path) - else: - print(' Using saved landmarks.') - lm = np.loadtxt(landmarks_path).astype(np.float32) - lm = lm.reshape([len(x_full_frames), -1, 2]) - - if not os.path.isfile(coeff_path): - # load 3dmm paramter generator from Deep3DFaceRecon_pytorch - video_coeffs, full_coeffs = [], [] - for idx in tqdm(range(len(frames_pil)), desc='3DMM Extraction In Video:'): - frame = frames_pil[idx] - W,H = frame.size - lm1 = lm[idx].reshape([-1, 2]) - - if np.mean(lm1) == -1: - lm1 = (self.lm3d_std[:, :2]+1)/2. - lm1 = np.concatenate( - [lm1[:, :1]*W, lm1[:, 1:2]*H], 1 - ) - else: - lm1[:, -1] = H - 1 - lm1[:, -1] - - trans_params, im1, lm1, _ = align_img(frame, lm1, self.lm3d_std) - - trans_params = np.array([float(item) for item in np.hsplit(trans_params, 5)]).astype(np.float32) - im_t = torch.tensor(np.array(im1)/255., dtype=torch.float32).permute(2, 0, 1).to(self.device).unsqueeze(0) - - with torch.no_grad(): - full_coeff = self.net_recon(im_t) - coeffs = split_coeff(full_coeff) - - pred_coeff = {key:coeffs[key].cpu().numpy() for key in coeffs} - - pred_coeff = np.concatenate([ - pred_coeff['exp'], - pred_coeff['angle'], - pred_coeff['trans'], - trans_params[2:][None], - ], 1) - video_coeffs.append(pred_coeff) - full_coeffs.append(full_coeff.cpu().numpy()) - - semantic_npy = np.array(video_coeffs)[:,0] - - savemat(coeff_path, {'coeff_3dmm': semantic_npy, 'full_3dmm': np.array(full_coeffs)[0]}) - - return coeff_path, png_path, crop_info diff --git a/src/utils/preprocess_two.py b/src/utils/preprocess_two.py deleted file mode 100644 index 1bdb880..0000000 --- a/src/utils/preprocess_two.py +++ /dev/null @@ -1,149 +0,0 @@ -import numpy as np -import cv2, os, torch -from tqdm import tqdm -from PIL import Image -from custom_nodes.ComfyUI_Lam.src.face3d.util.preprocess import align_img -from custom_nodes.ComfyUI_Lam.src.face3d.util.load_mats import load_lm3d -from custom_nodes.ComfyUI_Lam.src.face3d.models import networks -from custom_nodes.ComfyUI_Lam.src.face3d.extract_kp_videos import KeypointExtractor -from scipy.io import savemat -from custom_nodes.ComfyUI_Lam.src.utils.croper_two import Croper -import warnings - -warnings.filterwarnings("ignore") - - -def split_coeff(coeffs): - """ - Return: - coeffs_dict -- a dict of torch.tensors - - Parameters: - coeffs -- torch.tensor, size (B, 256) - """ - id_coeffs = coeffs[:, :80] - exp_coeffs = coeffs[:, 80: 144] - tex_coeffs = coeffs[:, 144: 224] - angles = coeffs[:, 224: 227] - gammas = coeffs[:, 227: 254] - translations = coeffs[:, 254:] - return { - 'id': id_coeffs, - 'exp': exp_coeffs, - 'tex': tex_coeffs, - 'angle': angles, - 'gamma': gammas, - 'trans': translations - } - - -class CropAndExtract(): - def __init__(self, path_of_lm_croper, path_of_net_recon_model, dir_of_BFM_fitting, device): - - self.croper = Croper(path_of_lm_croper) - self.kp_extractor = KeypointExtractor(device) - self.net_recon = networks.define_net_recon(net_recon='resnet50', use_last_fc=False, init_path='').to(device) - checkpoint = torch.load(path_of_net_recon_model, map_location=torch.device(device)) - self.net_recon.load_state_dict(checkpoint['net_recon']) - self.net_recon.eval() - self.lm3d_std = load_lm3d(dir_of_BFM_fitting) - self.device = device - - def generate(self, input_path, save_dir): - - pic_size = 256 - pic_name = os.path.splitext(os.path.split(input_path)[-1])[0] - - landmarks_path = os.path.join(save_dir, pic_name + '_landmarks.txt') - coeff_path = os.path.join(save_dir, pic_name + '.mat') - - # load input - if not os.path.isfile(input_path): - raise ValueError('input_path must be a valid path to video/image file') - elif input_path.split('.')[-1] in ['jpg', 'png', 'jpeg']: - # loader for first frame - full_frames = [cv2.imread(input_path)] - else: - # loader for videos - video_stream = cv2.VideoCapture(input_path) - full_frames = [] - while 1: - still_reading, frame = video_stream.read() - if not still_reading: - video_stream.release() - break - full_frames.append(frame) - - x_full_frames = [cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) for frame in full_frames] - - x_full_frames, crop, quad = self.croper.crop(x_full_frames, still=True, xsize=pic_size) - clx, cly, crx, cry = crop - lx, ly, rx, ry = quad - lx, ly, rx, ry = int(lx), int(ly), int(rx), int(ry) - oy1, oy2, ox1, ox2 = cly + ly, cly + ry, clx + lx, clx + rx - crop_info = ((ox2 - ox1, oy2 - oy1), crop, quad) - - frames_pil = [Image.fromarray(cv2.resize(frame, (pic_size, pic_size))) for frame in x_full_frames] - if len(frames_pil) == 0: - print('No face is detected in the input file') - return None, None - - # save crop info - index = 0 - save_img = os.path.join(save_dir, 'img') - os.makedirs(save_img, exist_ok=True) - for frame in frames_pil: - png_path = os.path.join(save_img, pic_name + '_{}'.format(str(index).zfill(6)) + '.png') - cv2.imwrite(png_path, cv2.cvtColor(np.array(frame), cv2.COLOR_RGB2BGR)) - index += 1 - - # 2. get the landmark according to the detected face. - if not os.path.isfile(landmarks_path): - lm = self.kp_extractor.extract_keypoint(frames_pil, landmarks_path) - else: - print(' Using saved landmarks.') - lm = np.loadtxt(landmarks_path).astype(np.float32) - lm = lm.reshape([len(x_full_frames), -1, 2]) - - if not os.path.isfile(coeff_path): - # load 3dmm paramter generator from Deep3DFaceRecon_pytorch - video_coeffs, full_coeffs = [], [] - for idx in tqdm(range(len(frames_pil)), desc='3DMM Extraction In Video:'): - frame = frames_pil[idx] - W, H = frame.size - lm1 = lm[idx].reshape([-1, 2]) - - if np.mean(lm1) == -1: - lm1 = (self.lm3d_std[:, :2] + 1) / 2. - lm1 = np.concatenate( - [lm1[:, :1] * W, lm1[:, 1:2] * H], 1 - ) - else: - lm1[:, -1] = H - 1 - lm1[:, -1] - - trans_params, im1, lm1, _ = align_img(frame, lm1, self.lm3d_std) - - trans_params = np.array([float(item) for item in np.hsplit(trans_params, 5)]).astype(np.float32) - im_t = torch.tensor(np.array(im1) / 255., dtype=torch.float32).permute(2, 0, 1).to( - self.device).unsqueeze(0) - - with torch.no_grad(): - full_coeff = self.net_recon(im_t) - coeffs = split_coeff(full_coeff) - - pred_coeff = {key: coeffs[key].cpu().numpy() for key in coeffs} - - pred_coeff = np.concatenate([ - pred_coeff['exp'], - pred_coeff['angle'], - pred_coeff['trans'], - trans_params[2:][None], - ], 1) - video_coeffs.append(pred_coeff) - full_coeffs.append(full_coeff.cpu().numpy()) - - semantic_npy = np.array(video_coeffs)[:, 0] - - savemat(coeff_path, {'coeff_3dmm': semantic_npy, 'full_3dmm': np.array(full_coeffs)[0]}) - - return coeff_path, save_img, crop_info diff --git a/src/utils/safetensor_helper.py b/src/utils/safetensor_helper.py deleted file mode 100644 index 3cdbdd2..0000000 --- a/src/utils/safetensor_helper.py +++ /dev/null @@ -1,8 +0,0 @@ - - -def load_x_from_safetensor(checkpoint, key): - x_generator = {} - for k,v in checkpoint.items(): - if key in k: - x_generator[k.replace(key+'.', '')] = v - return x_generator \ No newline at end of file diff --git a/src/utils/text2speech.py b/src/utils/text2speech.py deleted file mode 100644 index 00d165b..0000000 --- a/src/utils/text2speech.py +++ /dev/null @@ -1,20 +0,0 @@ -import os -import tempfile -from TTS.api import TTS - - -class TTSTalker(): - def __init__(self) -> None: - model_name = TTS.list_models()[0] - self.tts = TTS(model_name) - - def test(self, text, language='en'): - - tempf = tempfile.NamedTemporaryFile( - delete = False, - suffix = ('.'+'wav'), - ) - - self.tts.tts_to_file(text, speaker=self.tts.speakers[0], language=language, file_path=tempf.name) - - return tempf.name \ No newline at end of file diff --git a/src/utils/videoio.py b/src/utils/videoio.py deleted file mode 100644 index ea791a1..0000000 --- a/src/utils/videoio.py +++ /dev/null @@ -1,33 +0,0 @@ -import shutil -import uuid -import folder_paths -import os - -import cv2 - -def load_video_to_cv2(input_path): - video_stream = cv2.VideoCapture(input_path) - fps = video_stream.get(cv2.CAP_PROP_FPS) - full_frames = [] - while 1: - still_reading, frame = video_stream.read() - if not still_reading: - video_stream.release() - break - full_frames.append(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)) - return full_frames - -def save_video_with_watermark(video, audio, save_path, watermark=False): - temp_file = str(uuid.uuid4())+'.mp4' - cmd = r'ffmpeg -y -hide_banner -loglevel error -i "%s" -i "%s" -vcodec copy "%s"' % (video, audio, temp_file) - os.system(cmd) - - if watermark is False: - shutil.move(temp_file, save_path) - else: - dir_path = folder_paths.get_folder_paths('custom_nodes')[0] - watarmark_path = '' #dir_path+"/ComfyUI_Lam/docs/sadtalker_logo.png" - - cmd = r'ffmpeg -y -hide_banner -loglevel error -i "%s" -i "%s" -filter_complex "[1]scale=100:-1[wm];[0][wm]overlay=(main_w-overlay_w)-10:10" "%s"' % (temp_file, watarmark_path, save_path) - os.system(cmd) - os.remove(temp_file) \ No newline at end of file diff --git a/src/utils/videoio_two.py b/src/utils/videoio_two.py deleted file mode 100644 index 9bcfdc5..0000000 --- a/src/utils/videoio_two.py +++ /dev/null @@ -1,42 +0,0 @@ -import shutil -import uuid - -import os - -import cv2 - -def load_video_to_cv2(input_path): - video_stream = cv2.VideoCapture(input_path) - fps = video_stream.get(cv2.CAP_PROP_FPS) - full_frames = [] - while 1: - still_reading, frame = video_stream.read() - if not still_reading: - video_stream.release() - break - full_frames.append(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)) - return full_frames - -def save_video_with_watermark(video, audio, save_path, watermark=False): - temp_file = str(uuid.uuid4())+'.mp4' - cmd = r'ffmpeg -y -i "%s" -i "%s" -vcodec copy "%s"' % (video, audio, temp_file) - os.system(cmd) - - if watermark is False: - shutil.move(temp_file, save_path) - else: - # watermark - try: - ##### check if stable-diffusion-webui - import webui - from modules import paths - watarmark_path = paths.script_path+"/extensions/SadTalker/docs/sadtalker_logo.png" - - except: - # get the root path of sadtalker. - dir_path = os.path.dirname(os.path.realpath(__file__)) - watarmark_path = dir_path+"/../../docs/sadtalker_logo.png" - - cmd = r'ffmpeg -y -hide_banner -i "%s" -i "%s" -filter_complex "[1]scale=100:-1[wm];[0][wm]overlay=(main_w-overlay_w)-10:10" "%s"' % (temp_file, watarmark_path, save_path) - os.system(cmd) - os.remove(temp_file) \ No newline at end of file diff --git a/styles/sdxl_styles_diva.json b/styles/sdxl_styles_diva.json new file mode 100644 index 0000000..ea552f4 --- /dev/null +++ b/styles/sdxl_styles_diva.json @@ -0,0 +1,397 @@ +[ + { + "name": "cinematic-diva", + "prompt": "UHD, 8K, ultra detailed, a cinematic photograph of {prompt}, beautiful lighting, great composition", + "negative_prompt": "ugly, deformed, noisy, blurry, NSFW" + }, + { + "name": "Abstract Expressionism", + "prompt": "Abstract Expressionism Art, {prompt}, High contrast, minimalistic, colorful, stark, dramatic, expressionism", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" + }, + { + "name": "Academia", + "prompt": "Academia, {prompt}, preppy Ivy League style, stark, dramatic, chic boarding school, academia", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, grunge, sloppy, unkempt" + }, + { + "name": "Action Figure", + "prompt": "Action Figure, {prompt}, plastic collectable action figure, collectable toy action figure", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast" + }, + { + "name": "Adorable 3D Character", + "prompt": "Adorable 3D Character, {prompt}, 3D render, adorable character, 3D art", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, grunge, sloppy, unkempt, photograph, photo, realistic" + }, + { + "name": "Adorable Kawaii", + "prompt": "Adorable Kawaii, {prompt}, pretty, cute, adorable, kawaii", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, gothic, dark, moody, monochromatic" + }, + { + "name": "Art Deco", + "prompt": "Art Deco, {prompt}, sleek, geometric forms, art deco style", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast" + }, + { + "name": "Art Nouveau", + "prompt": "Art Nouveau, beautiful art, {prompt}, sleek, organic forms, long, sinuous, art nouveau style", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, industrial, mechanical" + }, + { + "name": "Astral Aura", + "prompt": "Astral Aura, {prompt}, astral, colorful aura, vibrant energy", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast" + }, + { + "name": "Avant-garde", + "prompt": "Avant-garde, {prompt}, unusual, experimental, avant-garde art", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast" + }, + { + "name": "Baroque", + "prompt": "Baroque, {prompt}, dramatic, exuberant, grandeur, baroque art", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast" + }, + { + "name": "Bauhaus-Style Poster", + "prompt": "Bauhaus-Style Poster, {prompt}, simple geometric shapes, clean lines, primary colors, Bauhaus-Style Poster", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast" + }, + { + "name": "Blueprint Schematic Drawing", + "prompt": "Blueprint Schematic Drawing, {prompt}, technical drawing, blueprint, schematic", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast" + }, + { + "name": "Caricature", + "prompt": "Caricature, {prompt}, exaggerated, comical, caricature", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realistic" + }, + { + "name": "Cel Shaded Art", + "prompt": "Cel Shaded Art, {prompt}, 2D, flat color, toon shading, cel shaded style", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast" + }, + { + "name": "Character Design Sheet", + "prompt": "Character Design Sheet, {prompt}, character reference sheet, character turn around", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast" + }, + { + "name": "Classicism Art", + "prompt": "Classicism Art, {prompt}, inspired by Roman and Greek culture, clarity, harmonious, classicism art", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast" + }, + { + "name": "Color Field Painting", + "prompt": "Color Field Painting, {prompt}, abstract, simple, geometic, color field painting style", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast" + }, + { + "name": "Colored Pencil Art", + "prompt": "Colored Pencil Art, {prompt}, colored pencil strokes, light color, visible paper texture, colored pencil art", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast" + }, + { + "name": "Conceptual Art", + "prompt": "Conceptual Art, {prompt}, concept art", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast" + }, + { + "name": "Constructivism", + "prompt": "Constructivism Art, {prompt}, minimalistic, geometric forms, constructivism art", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast" + }, + { + "name": "Cubism", + "prompt": "Cubism Art, {prompt}, flat geometric forms, cubism art", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast" + }, + { + "name": "Dadaism", + "prompt": "Dadaism Art, {prompt}, satirical, nonsensical, dadaism art", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast" + }, + { + "name": "Dark Fantasy", + "prompt": "Dark Fantasy Art, {prompt}, dark, moody, dark fantasy style", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, bright, sunny" + }, + { + "name": "Dark Moody Atmosphere", + "prompt": "Dark Moody Atmosphere, {prompt}, dramatic, mysterious, dark moody atmosphere", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, vibrant, colorful, bright" + }, + { + "name": "DMT Art Style", + "prompt": "DMT Art Style, {prompt}, bright colors, surreal visuals, swirling patterns, DMT art style", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast" + }, + { + "name": "Doodle Art", + "prompt": "Doodle Art Style, {prompt}, drawing, freeform, swirling patterns, doodle art style", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast" + }, + { + "name": "Double Exposure", + "prompt": "Double Exposure Style, {prompt}, double image ghost effect, image combination, double exposure style", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast" + }, + { + "name": "Dripping Paint Splatter Art", + "prompt": "Dripping Paint Splatter Art, {prompt}, dramatic, paint drips, splatters, dripping paint", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast" + }, + { + "name": "Expressionism", + "prompt": "Expressionism Art Style, {prompt}, movement, contrast, emotional, exaggerated forms, expressionism art style", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast" + }, + { + "name": "Faded Polaroid Photo", + "prompt": "Faded Polaroid Photo, {prompt}, analog, old faded photo, old polaroid", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, vibrant, colorful" + }, + { + "name": "Fauvism", + "prompt": "Fauvism Art, {prompt}, painterly, bold colors, textured brushwork, fauvism art", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast" + }, + { + "name": "Flat 2D Art", + "prompt": "Flat 2D Art, {prompt}, simple flat color, 2-dimensional, Flat 2D Art Style", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, 3D, photo, realistic" + }, + { + "name": "Fortnite Art Style", + "prompt": "Fortnite Art Style, {prompt}, 3D cartoon, colorful, Fortnite Art Style", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, photo, realistic" + }, + { + "name": "Futurism", + "prompt": "Futurism Art Style, {prompt}, dynamic, dramatic, Futurism Art Style", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast" + }, + { + "name": "Glitchcore", + "prompt": "Glitchcore Art Style, {prompt}, dynamic, dramatic, distorted, vibrant colors, glitchcore art style", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast" + }, + { + "name": "Glo-fi", + "prompt": "Glo-fi Art Style, {prompt}, dynamic, dramatic, vibrant colors, glo-fi art style", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast" + }, + { + "name": "Googie Art Style", + "prompt": "Googie Art Style, {prompt}, dynamic, dramatic, 1950's futurism, bold boomerang angles, Googie art style", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast" + }, + { + "name": "Graffiti Art", + "prompt": "Graffiti Art Style, {prompt}, dynamic, dramatic, vibrant colors, graffiti art style", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast" + }, + { + "name": "Harlem Renaissance Art", + "prompt": "Harlem Renaissance Art Style, {prompt}, dynamic, dramatic, 1920s African American culture, Harlem Renaissance art style", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast" + }, + { + "name": "High Fashion", + "prompt": "High Fashion, {prompt}, dynamic, dramatic, haute couture, elegant, ornate clothing, High Fashion", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast" + }, + { + "name": "Idyllic", + "prompt": "Idyllic, {prompt}, peaceful, happy, pleasant, happy, harmonious, picturesque, charming", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast" + }, + { + "name": "Impressionism", + "prompt": "Impressionism, {prompt}, painterly, small brushstrokes, visible brushstrokes, impressionistic style", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast" + }, + { + "name": "Infographic Drawing", + "prompt": "Infographic Drawing, {prompt}, diagram, infographic", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast" + }, + { + "name": "Ink Dripping Drawing", + "prompt": "Ink Dripping Drawing, {prompt}, ink drawing, dripping ink", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, colorful, vibrant" + }, + { + "name": "Japanese Ink Drawing", + "prompt": "Japanese Ink Drawing, {prompt}, ink drawing, inkwash, Japanese Ink Drawing", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, colorful, vibrant" + }, + { + "name": "Knolling Photography", + "prompt": "Knolling Photography, {prompt}, flat lay photography, object arrangment, knolling photography", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast" + }, + { + "name": "Light Cheery Atmosphere", + "prompt": "Light Cheery Atmosphere, {prompt}, happy, joyful, cheerful, carefree, gleeful, lighthearted, pleasant atmosphere", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, monochromatic, dark, moody" + }, + { + "name": "Logo Design", + "prompt": "Logo Design, {prompt}, dynamic graphic art, vector art, minimalist, professional logo design", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast" + }, + { + "name": "Luxurious Elegance", + "prompt": "Luxurious Elegance, {prompt}, extravagant, ornate, designer, opulent, picturesque, lavish", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast" + }, + { + "name": "Macro Photography", + "prompt": "Macro Photography, {prompt}, close-up, macro 100mm, macro photography", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast" + }, + { + "name": "Mandola Art", + "prompt": "Mandola art style, {prompt}, complex, circular design, mandola", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast" + }, + { + "name": "Marker Drawing", + "prompt": "Marker Drawing, {prompt}, bold marker lines, visibile paper texture, marker drawing", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, photograph, realistic" + }, + { + "name": "Medievalism", + "prompt": "Medievalism, {prompt}, inspired by The Middle Ages, medieval art, elaborate patterns and decoration, Medievalism", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast" + }, + { + "name": "Minimalism", + "prompt": "Minimalism, {prompt}, abstract, simple geometic shapes, hard edges, sleek contours, Minimalism", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast" + }, + { + "name": "Neo-Baroque", + "prompt": "Neo-Baroque, {prompt}, ornate and elaborate, dynaimc, Neo-Baroque", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast" + }, + { + "name": "Neo-Byzantine", + "prompt": "Neo-Byzantine, {prompt}, grand decorative religious style, Orthodox Christian inspired, Neo-Byzantine", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast" + }, + { + "name": "Neo-Futurism", + "prompt": "Neo-Futurism, {prompt}, high-tech, curves, spirals, flowing lines, idealistic future, Neo-Futurism", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast" + }, + { + "name": "Neo-Impressionism", + "prompt": "Neo-Impressionism, {prompt}, tiny dabs of color, Pointillism, painterly, Neo-Impressionism", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, photograph, realistic" + }, + { + "name": "Neo-Rococo", + "prompt": "Neo-Rococo, {prompt}, curved forms, naturalistic ornamentation, elaborate, decorative, gaudy, Neo-Rococo", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast" + }, + { + "name": "Neoclassicism", + "prompt": "Neoclassicism, {prompt}, ancient Rome and Greece inspired, idealic, sober colors, Neoclassicism", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast" + }, + { + "name": "Op Art", + "prompt": "Op Art, {prompt}, optical illusion, abstract, geometric pattern, impression of movement, Op Art", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast" + }, + { + "name": "Ornate and Intricate", + "prompt": "Ornate and Intricate, {prompt}, decorative, highly detailed, elaborate, ornate, intricate", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast" + }, + { + "name": "Pencil Sketch Drawing", + "prompt": "Pencil Sketch Drawing, {prompt}, black and white drawing, graphite drawing", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast" + }, + { + "name": "Pop Art 2", + "prompt": "Pop Art, {prompt}, vivid colors, flat color, 2D, strong lines, Pop Art", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, photo, realistic" + }, + { + "name": "Rococo", + "prompt": "Rococo, {prompt}, flamboyant, pastel colors, curved lines, elaborate detail, Rococo", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast" + }, + { + "name": "Silhouette Art", + "prompt": "Silhouette Art, {prompt}, high contrast, well defined, Silhouette Art", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast" + }, + { + "name": "Simple Vector Art", + "prompt": "Simple Vector Art, {prompt}, 2D flat, simple shapes, minimalistic, professional graphic, flat color, high contrast, Simple Vector Art", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, 3D, photo, realistic" + }, + { + "name": "Sketchup", + "prompt": "Sketchup, {prompt}, CAD, professional design, Sketchup", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, photo, photograph" + }, + { + "name": "Steampunk 2", + "prompt": "Steampunk, {prompt}, retrofuturistic science fantasy, steam-powered tech, vintage industry, gears, neo-victorian, steampunk", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast" + }, + { + "name": "Surrealism", + "prompt": "Surrealism, {prompt}, expressive, dramatic, organic lines and forms, dreamlike and mysterious, Surrealism", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realistic" + }, + { + "name": "Suprematism", + "prompt": "Suprematism, {prompt}, abstract, limited color palette, geometric forms, Suprematism", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realistic" + }, + { + "name": "Terragen", + "prompt": "Terragen, {prompt}, beautiful massive landscape, epic scenery, Terragen", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast" + }, + { + "name": "Tranquil Relaxing Atmosphere", + "prompt": "Tranquil Relaxing Atmosphere, {prompt}, calming style, soothing colors, peaceful, idealic, Tranquil Relaxing Atmosphere", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, oversaturated" + }, + { + "name": "Sticker Designs", + "prompt": "Vector Art Stickers, {prompt}, professional vector design, sticker designs, Sticker Sheet", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast" + }, + { + "name": "Vibrant Rim Light", + "prompt": "Vibrant Rim Light, {prompt}, bright rim light, high contrast, bold edge light", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast" + }, + { + "name": "Volumetric Lighting", + "prompt": "Volumetric Lighting, {prompt}, light depth, dramatic atmospheric lighting, Volumetric Lighting", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast" + }, + { + "name": "Watercolor 2", + "prompt": "Watercolor style painting, {prompt}, visible paper texture, colorwash, watercolor", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, photo, realistic" + }, + { + "name": "Whimsical and Playful", + "prompt": "Whimsical and Playful, {prompt}, imaginative, fantastical, bight colors, stylized, happy, Whimsical and Playful", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, drab, boring, moody" + } +] diff --git a/styles/sdxl_styles_fooocus.json b/styles/sdxl_styles_fooocus.json new file mode 100644 index 0000000..81d6442 --- /dev/null +++ b/styles/sdxl_styles_fooocus.json @@ -0,0 +1,30 @@ +[ + { + "name": "Fooocus Enhance", + "negative_prompt": "(worst quality, low quality, normal quality, lowres, low details, oversaturated, undersaturated, overexposed, underexposed, grayscale, bw, bad photo, bad photography, bad art:1.4), (watermark, signature, text font, username, error, logo, words, letters, digits, autograph, trademark, name:1.2), (blur, blurry, grainy), morbid, ugly, asymmetrical, mutated malformed, mutilated, poorly lit, bad shadow, draft, cropped, out of frame, cut off, censored, jpeg artifacts, out of focus, glitch, duplicate, (airbrushed, cartoon, anime, semi-realistic, cgi, render, blender, digital art, manga, amateur:1.3), (3D ,3D Game, 3D Game Scene, 3D Character:1.1), (bad hands, bad anatomy, bad body, bad face, bad teeth, bad arms, bad legs, deformities:1.3)" + }, + { + "name": "Fooocus Sharp", + "prompt": "cinematic still {prompt} . emotional, harmonious, vignette, 4k epic detailed, shot on kodak, 35mm photo, sharp focus, high budget, cinemascope, moody, epic, gorgeous, film grain, grainy", + "negative_prompt": "anime, cartoon, graphic, (blur, blurry, bokeh), text, painting, crayon, graphite, abstract, glitch, deformed, mutated, ugly, disfigured" + }, + { + "name": "Fooocus Masterpiece", + "prompt": "(masterpiece), (best quality), (ultra-detailed), {prompt}, illustration, disheveled hair, detailed eyes, perfect composition, moist skin, intricate details, earrings, by wlop", + "negative_prompt": "longbody, lowres, bad anatomy, bad hands, missing fingers, pubic hair,extra digit, fewer digits, cropped, worst quality, low quality" + }, + { + "name": "Fooocus Photograph", + "prompt": "photograph {prompt}, 50mm . cinematic 4k epic detailed 4k epic detailed photograph shot on kodak detailed cinematic hbo dark moody, 35mm photo, grainy, vignette, vintage, Kodachrome, Lomography, stained, highly detailed, found footage", + "negative_prompt": "Brad Pitt, bokeh, depth of field, blurry, cropped, regular face, saturated, contrast, deformed iris, deformed pupils, semi-realistic, cgi, 3d, render, sketch, cartoon, drawing, anime, text, cropped, out of frame, worst quality, low quality, jpeg artifacts, ugly, duplicate, morbid, mutilated, extra fingers, mutated hands, poorly drawn hands, poorly drawn face, mutation, deformed, dehydrated, bad anatomy, bad proportions, extra limbs, cloned face, disfigured, gross proportions, malformed limbs, missing arms, missing legs, extra arms, extra legs, fused fingers, too many fingers, long neck" + }, + { + "name": "Fooocus Negative", + "negative_prompt": "deformed, bad anatomy, disfigured, poorly drawn face, mutated, extra limb, ugly, poorly drawn hands, missing limb, floating limbs, disconnected limbs, disconnected head, malformed hands, long neck, mutated hands and fingers, bad hands, missing fingers, cropped, worst quality, low quality, mutation, poorly drawn, huge calf, bad hands, fused hand, missing hand, disappearing arms, disappearing thigh, disappearing calf, disappearing legs, missing fingers, fused fingers, abnormal eye proportion, Abnormal hands, abnormal legs, abnormal feet, abnormal fingers, drawing, painting, crayon, sketch, graphite, impressionist, noisy, blurry, soft, deformed, ugly, anime, cartoon, graphic, text, painting, crayon, graphite, abstract, glitch" + }, + { + "name": "Fooocus Cinematic", + "prompt": "cinematic still {prompt} . emotional, harmonious, vignette, highly detailed, high budget, bokeh, cinemascope, moody, epic, gorgeous, film grain, grainy", + "negative_prompt": "anime, cartoon, graphic, text, painting, crayon, graphite, abstract, glitch, deformed, mutated, ugly, disfigured" + } +] diff --git a/styles/sdxl_styles_mre.json b/styles/sdxl_styles_mre.json new file mode 100644 index 0000000..c18c310 --- /dev/null +++ b/styles/sdxl_styles_mre.json @@ -0,0 +1,112 @@ +[ + { + "name": "mre-cinematic-dynamic", + "prompt": "epic cinematic shot of dynamic {prompt} in motion. main subject of high budget action movie. raw photo, motion blur. best quality, high resolution", + "negative_prompt": "static, still, motionless, sluggish. drawing, painting, illustration, rendered. low budget. low quality, low resolution" + }, + { + "name": "mre-spontaneous-picture", + "prompt": "spontaneous picture of {prompt}, taken by talented amateur. best quality, high resolution. magical moment, natural look. simple but good looking", + "negative_prompt": "overthinked. low quality, low resolution" + }, + { + "name": "mre-artistic-vision", + "prompt": "powerful artistic vision of {prompt}. breathtaking masterpiece made by great artist. best quality, high resolution", + "negative_prompt": "insignificant, flawed, made by bad artist. low quality, low resolution" + }, + { + "name": "mre-dark-dream", + "prompt": "dark and unsettling dream showing {prompt}. best quality, high resolution. created by genius but depressed mad artist. grim beauty", + "negative_prompt": "naive, cheerful. comfortable, casual, boring, cliche. low quality, low resolution" + }, + { + "name": "mre-gloomy-art", + "prompt": "astonishing gloomy art made mainly of shadows and lighting, forming {prompt}. masterful usage of lighting, shadows and chiaroscuro. made by black-hearted artist, drawing from darkness. best quality, high resolution", + "negative_prompt": "low quality, low resolution" + }, + { + "name": "mre-bad-dream", + "prompt": "picture from really bad dream about terrifying {prompt}, true horror. bone-chilling vision. mad world that shouldn't exist. best quality, high resolution", + "negative_prompt": "nice dream, pleasant experience. low quality, low resolution" + }, + { + "name": "mre-underground", + "prompt": "uncanny caliginous vision of {prompt}, created by remarkable underground artist. best quality, high resolution. raw and brutal art, careless but impressive style. inspired by darkness and chaos", + "negative_prompt": "photography, mainstream, civilized. low quality, low resolution" + }, + { + "name": "mre-surreal-painting", + "prompt": "surreal painting representing strange vision of {prompt}. harmonious madness, synergy with chance. unique artstyle, mindbending art, magical surrealism. best quality, high resolution", + "negative_prompt": "photography, illustration, drawing. realistic, possible. logical, sane. low quality, low resolution" + }, + { + "name": "mre-dynamic-illustration", + "prompt": "insanely dynamic illustration of {prompt}. best quality, high resolution. crazy artstyle, careless brushstrokes, emotional and fun", + "negative_prompt": "photography, realistic. static, still, slow, boring. low quality, low resolution" + }, + { + "name": "mre-undead-art", + "prompt": "long forgotten art created by undead artist illustrating {prompt}, tribute to the death and decay. miserable art of the damned. wretched and decaying world. best quality, high resolution", + "negative_prompt": "alive, playful, living. low quality, low resolution" + }, + { + "name": "mre-elemental-art", + "prompt": "art illustrating insane amounts of raging elemental energy turning into {prompt}, avatar of elements. magical surrealism, wizardry. best quality, high resolution", + "negative_prompt": "photography, realistic, real. low quality, low resolution" + }, + { + "name": "mre-space-art", + "prompt": "winner of inter-galactic art contest illustrating {prompt}, symbol of the interstellar singularity. best quality, high resolution. artstyle previously unseen in the whole galaxy", + "negative_prompt": "created by human race, low quality, low resolution" + }, + { + "name": "mre-ancient-illustration", + "prompt": "sublime ancient illustration of {prompt}, predating human civilization. crude and simple, but also surprisingly beautiful artwork, made by genius primeval artist. best quality, high resolution", + "negative_prompt": "low quality, low resolution" + }, + { + "name": "mre-brave-art", + "prompt": "brave, shocking, and brutally true art showing {prompt}. inspired by courage and unlimited creativity. truth found in chaos. best quality, high resolution", + "negative_prompt": "low quality, low resolution" + }, + { + "name": "mre-heroic-fantasy", + "prompt": "heroic fantasy painting of {prompt}, in the dangerous fantasy world. airbrush over oil on canvas. best quality, high resolution", + "negative_prompt": "low quality, low resolution" + }, + { + "name": "mre-dark-cyberpunk", + "prompt": "dark cyberpunk illustration of brutal {prompt} in a world without hope, ruled by ruthless criminal corporations. best quality, high resolution", + "negative_prompt": "low quality, low resolution" + }, + { + "name": "mre-lyrical-geometry", + "prompt": "geometric and lyrical abstraction painting presenting {prompt}. oil on metal. best quality, high resolution", + "negative_prompt": "photography, realistic, drawing, rendered. low quality, low resolution" + }, + { + "name": "mre-sumi-e-symbolic", + "prompt": "big long brushstrokes of deep black sumi-e turning into symbolic painting of {prompt}. master level raw art. best quality, high resolution", + "negative_prompt": "photography, rendered. low quality, low resolution" + }, + { + "name": "mre-sumi-e-detailed", + "prompt": "highly detailed black sumi-e painting of {prompt}. in-depth study of perfection, created by a master. best quality, high resolution", + "negative_prompt": "low quality, low resolution" + }, + { + "name": "mre-manga", + "prompt": "manga artwork presenting {prompt}. created by japanese manga artist. highly emotional. best quality, high resolution", + "negative_prompt": "low quality, low resolution" + }, + { + "name": "mre-anime", + "prompt": "anime artwork illustrating {prompt}. created by japanese anime studio. highly emotional. best quality, high resolution", + "negative_prompt": "low quality, low resolution" + }, + { + "name": "mre-comic", + "prompt": "breathtaking illustration from adult comic book presenting {prompt}. fabulous artwork. best quality, high resolution", + "negative_prompt": "deformed, ugly, low quality, low resolution" + } +] diff --git a/styles/sdxl_styles_sai.json b/styles/sdxl_styles_sai.json new file mode 100644 index 0000000..a705b24 --- /dev/null +++ b/styles/sdxl_styles_sai.json @@ -0,0 +1,87 @@ +[ + { + "name": "sai-3d-model", + "prompt": "professional 3d model {prompt} . octane render, highly detailed, volumetric, dramatic lighting", + "negative_prompt": "ugly, deformed, noisy, low poly, blurry, painting" + }, + { + "name": "sai-analog film", + "prompt": "analog film photo {prompt} . faded film, desaturated, 35mm photo, grainy, vignette, vintage, Kodachrome, Lomography, stained, highly detailed, found footage", + "negative_prompt": "painting, drawing, illustration, glitch, deformed, mutated, cross-eyed, ugly, disfigured" + }, + { + "name": "sai-anime", + "prompt": "anime artwork {prompt} . anime style, key visual, vibrant, studio anime, highly detailed", + "negative_prompt": "photo, deformed, black and white, realism, disfigured, low contrast" + }, + { + "name": "sai-cinematic", + "prompt": "cinematic film still {prompt} . shallow depth of field, vignette, highly detailed, high budget, bokeh, cinemascope, moody, epic, gorgeous, film grain, grainy", + "negative_prompt": "anime, cartoon, graphic, text, painting, crayon, graphite, abstract, glitch, deformed, mutated, ugly, disfigured" + }, + { + "name": "sai-comic book", + "prompt": "comic {prompt} . graphic illustration, comic art, graphic novel art, vibrant, highly detailed", + "negative_prompt": "photograph, deformed, glitch, noisy, realistic, stock photo" + }, + { + "name": "sai-craft clay", + "prompt": "play-doh style {prompt} . sculpture, clay art, centered composition, Claymation", + "negative_prompt": "sloppy, messy, grainy, highly detailed, ultra textured, photo" + }, + { + "name": "sai-digital art", + "prompt": "concept art {prompt} . digital artwork, illustrative, painterly, matte painting, highly detailed", + "negative_prompt": "photo, photorealistic, realism, ugly" + }, + { + "name": "sai-enhance", + "prompt": "breathtaking {prompt} . award-winning, professional, highly detailed", + "negative_prompt": "ugly, deformed, noisy, blurry, distorted, grainy" + }, + { + "name": "sai-fantasy art", + "prompt": "ethereal fantasy concept art of {prompt} . magnificent, celestial, ethereal, painterly, epic, majestic, magical, fantasy art, cover art, dreamy", + "negative_prompt": "photographic, realistic, realism, 35mm film, dslr, cropped, frame, text, deformed, glitch, noise, noisy, off-center, deformed, cross-eyed, closed eyes, bad anatomy, ugly, disfigured, sloppy, duplicate, mutated, black and white" + }, + { + "name": "sai-isometric", + "prompt": "isometric style {prompt} . vibrant, beautiful, crisp, detailed, ultra detailed, intricate", + "negative_prompt": "deformed, mutated, ugly, disfigured, blur, blurry, noise, noisy, realistic, photographic" + }, + { + "name": "sai-line art", + "prompt": "line art drawing {prompt} . professional, sleek, modern, minimalist, graphic, line art, vector graphics", + "negative_prompt": "anime, photorealistic, 35mm film, deformed, glitch, blurry, noisy, off-center, deformed, cross-eyed, closed eyes, bad anatomy, ugly, disfigured, mutated, realism, realistic, impressionism, expressionism, oil, acrylic" + }, + { + "name": "sai-lowpoly", + "prompt": "low-poly style {prompt} . low-poly game art, polygon mesh, jagged, blocky, wireframe edges, centered composition", + "negative_prompt": "noisy, sloppy, messy, grainy, highly detailed, ultra textured, photo" + }, + { + "name": "sai-neonpunk", + "prompt": "neonpunk style {prompt} . cyberpunk, vaporwave, neon, vibes, vibrant, stunningly beautiful, crisp, detailed, sleek, ultramodern, magenta highlights, dark purple shadows, high contrast, cinematic, ultra detailed, intricate, professional", + "negative_prompt": "painting, drawing, illustration, glitch, deformed, mutated, cross-eyed, ugly, disfigured" + }, + { + "name": "sai-origami", + "prompt": "origami style {prompt} . paper art, pleated paper, folded, origami art, pleats, cut and fold, centered composition", + "negative_prompt": "noisy, sloppy, messy, grainy, highly detailed, ultra textured, photo" + }, + { + "name": "sai-photographic", + "prompt": "cinematic photo {prompt} . 35mm photograph, film, bokeh, professional, 4k, highly detailed", + "negative_prompt": "drawing, painting, crayon, sketch, graphite, impressionist, noisy, blurry, soft, deformed, ugly" + }, + { + "name": "sai-pixel art", + "prompt": "pixel-art {prompt} . low-res, blocky, pixel art style, 8-bit graphics", + "negative_prompt": "sloppy, messy, blurry, noisy, highly detailed, ultra textured, photo, realistic" + }, + { + "name": "sai-texture", + "prompt": "texture {prompt} top down close-up", + "negative_prompt": "ugly, deformed, noisy, blurry" + } +] \ No newline at end of file diff --git a/styles/sdxl_styles_twri.json b/styles/sdxl_styles_twri.json new file mode 100644 index 0000000..687a19d --- /dev/null +++ b/styles/sdxl_styles_twri.json @@ -0,0 +1,447 @@ +[ + { + "name": "ads-advertising", + "prompt": "advertising poster style {prompt} . Professional, modern, product-focused, commercial, eye-catching, highly detailed", + "negative_prompt": "noisy, blurry, amateurish, sloppy, unattractive" + }, + { + "name": "ads-automotive", + "prompt": "automotive advertisement style {prompt} . sleek, dynamic, professional, commercial, vehicle-focused, high-resolution, highly detailed", + "negative_prompt": "noisy, blurry, unattractive, sloppy, unprofessional" + }, + { + "name": "ads-corporate", + "prompt": "corporate branding style {prompt} . professional, clean, modern, sleek, minimalist, business-oriented, highly detailed", + "negative_prompt": "noisy, blurry, grungy, sloppy, cluttered, disorganized" + }, + { + "name": "ads-fashion editorial", + "prompt": "fashion editorial style {prompt} . high fashion, trendy, stylish, editorial, magazine style, professional, highly detailed", + "negative_prompt": "outdated, blurry, noisy, unattractive, sloppy" + }, + { + "name": "ads-food photography", + "prompt": "food photography style {prompt} . appetizing, professional, culinary, high-resolution, commercial, highly detailed", + "negative_prompt": "unappetizing, sloppy, unprofessional, noisy, blurry" + }, + { + "name": "ads-gourmet food photography", + "prompt": "gourmet food photo of {prompt} . soft natural lighting, macro details, vibrant colors, fresh ingredients, glistening textures, bokeh background, styled plating, wooden tabletop, garnished, tantalizing, editorial quality", + "negative_prompt": "cartoon, anime, sketch, grayscale, dull, overexposed, cluttered, messy plate, deformed" + }, + { + "name": "ads-luxury", + "prompt": "luxury product style {prompt} . elegant, sophisticated, high-end, luxurious, professional, highly detailed", + "negative_prompt": "cheap, noisy, blurry, unattractive, amateurish" + }, + { + "name": "ads-real estate", + "prompt": "real estate photography style {prompt} . professional, inviting, well-lit, high-resolution, property-focused, commercial, highly detailed", + "negative_prompt": "dark, blurry, unappealing, noisy, unprofessional" + }, + { + "name": "ads-retail", + "prompt": "retail packaging style {prompt} . vibrant, enticing, commercial, product-focused, eye-catching, professional, highly detailed", + "negative_prompt": "noisy, blurry, amateurish, sloppy, unattractive" + }, + { + "name": "artstyle-abstract", + "prompt": "abstract style {prompt} . non-representational, colors and shapes, expression of feelings, imaginative, highly detailed", + "negative_prompt": "realistic, photographic, figurative, concrete" + }, + { + "name": "artstyle-abstract expressionism", + "prompt": "abstract expressionist painting {prompt} . energetic brushwork, bold colors, abstract forms, expressive, emotional", + "negative_prompt": "realistic, photorealistic, low contrast, plain, simple, monochrome" + }, + { + "name": "artstyle-art deco", + "prompt": "art deco style {prompt} . geometric shapes, bold colors, luxurious, elegant, decorative, symmetrical, ornate, detailed", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic, modernist, minimalist" + }, + { + "name": "artstyle-art nouveau", + "prompt": "art nouveau style {prompt} . elegant, decorative, curvilinear forms, nature-inspired, ornate, detailed", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic, modernist, minimalist" + }, + { + "name": "artstyle-constructivist", + "prompt": "constructivist style {prompt} . geometric shapes, bold colors, dynamic composition, propaganda art style", + "negative_prompt": "realistic, photorealistic, low contrast, plain, simple, abstract expressionism" + }, + { + "name": "artstyle-cubist", + "prompt": "cubist artwork {prompt} . geometric shapes, abstract, innovative, revolutionary", + "negative_prompt": "anime, photorealistic, 35mm film, deformed, glitch, low contrast, noisy" + }, + { + "name": "artstyle-expressionist", + "prompt": "expressionist {prompt} . raw, emotional, dynamic, distortion for emotional effect, vibrant, use of unusual colors, detailed", + "negative_prompt": "realism, symmetry, quiet, calm, photo" + }, + { + "name": "artstyle-graffiti", + "prompt": "graffiti style {prompt} . street art, vibrant, urban, detailed, tag, mural", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" + }, + { + "name": "artstyle-hyperrealism", + "prompt": "hyperrealistic art {prompt} . extremely high-resolution details, photographic, realism pushed to extreme, fine texture, incredibly lifelike", + "negative_prompt": "simplified, abstract, unrealistic, impressionistic, low resolution" + }, + { + "name": "artstyle-impressionist", + "prompt": "impressionist painting {prompt} . loose brushwork, vibrant color, light and shadow play, captures feeling over form", + "negative_prompt": "anime, photorealistic, 35mm film, deformed, glitch, low contrast, noisy" + }, + { + "name": "artstyle-pointillism", + "prompt": "pointillism style {prompt} . composed entirely of small, distinct dots of color, vibrant, highly detailed", + "negative_prompt": "line drawing, smooth shading, large color fields, simplistic" + }, + { + "name": "artstyle-pop art", + "prompt": "pop Art style {prompt} . bright colors, bold outlines, popular culture themes, ironic or kitsch", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic, minimalist" + }, + { + "name": "artstyle-psychedelic", + "prompt": "psychedelic style {prompt} . vibrant colors, swirling patterns, abstract forms, surreal, trippy", + "negative_prompt": "monochrome, black and white, low contrast, realistic, photorealistic, plain, simple" + }, + { + "name": "artstyle-renaissance", + "prompt": "renaissance style {prompt} . realistic, perspective, light and shadow, religious or mythological themes, highly detailed", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, modernist, minimalist, abstract" + }, + { + "name": "artstyle-steampunk", + "prompt": "steampunk style {prompt} . antique, mechanical, brass and copper tones, gears, intricate, detailed", + "negative_prompt": "deformed, glitch, noisy, low contrast, anime, photorealistic" + }, + { + "name": "artstyle-surrealist", + "prompt": "surrealist art {prompt} . dreamlike, mysterious, provocative, symbolic, intricate, detailed", + "negative_prompt": "anime, photorealistic, realistic, deformed, glitch, noisy, low contrast" + }, + { + "name": "artstyle-typography", + "prompt": "typographic art {prompt} . stylized, intricate, detailed, artistic, text-based", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" + }, + { + "name": "artstyle-watercolor", + "prompt": "watercolor painting {prompt} . vibrant, beautiful, painterly, detailed, textural, artistic", + "negative_prompt": "anime, photorealistic, 35mm film, deformed, glitch, low contrast, noisy" + }, + { + "name": "futuristic-biomechanical", + "prompt": "biomechanical style {prompt} . blend of organic and mechanical elements, futuristic, cybernetic, detailed, intricate", + "negative_prompt": "natural, rustic, primitive, organic, simplistic" + }, + { + "name": "futuristic-biomechanical cyberpunk", + "prompt": "biomechanical cyberpunk {prompt} . cybernetics, human-machine fusion, dystopian, organic meets artificial, dark, intricate, highly detailed", + "negative_prompt": "natural, colorful, deformed, sketch, low contrast, watercolor" + }, + { + "name": "futuristic-cybernetic", + "prompt": "cybernetic style {prompt} . futuristic, technological, cybernetic enhancements, robotics, artificial intelligence themes", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic, historical, medieval" + }, + { + "name": "futuristic-cybernetic robot", + "prompt": "cybernetic robot {prompt} . android, AI, machine, metal, wires, tech, futuristic, highly detailed", + "negative_prompt": "organic, natural, human, sketch, watercolor, low contrast" + }, + { + "name": "futuristic-cyberpunk cityscape", + "prompt": "cyberpunk cityscape {prompt} . neon lights, dark alleys, skyscrapers, futuristic, vibrant colors, high contrast, highly detailed", + "negative_prompt": "natural, rural, deformed, low contrast, black and white, sketch, watercolor" + }, + { + "name": "futuristic-futuristic", + "prompt": "futuristic style {prompt} . sleek, modern, ultramodern, high tech, detailed", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic, vintage, antique" + }, + { + "name": "futuristic-retro cyberpunk", + "prompt": "retro cyberpunk {prompt} . 80's inspired, synthwave, neon, vibrant, detailed, retro futurism", + "negative_prompt": "modern, desaturated, black and white, realism, low contrast" + }, + { + "name": "futuristic-retro futurism", + "prompt": "retro-futuristic {prompt} . vintage sci-fi, 50s and 60s style, atomic age, vibrant, highly detailed", + "negative_prompt": "contemporary, realistic, rustic, primitive" + }, + { + "name": "futuristic-sci-fi", + "prompt": "sci-fi style {prompt} . futuristic, technological, alien worlds, space themes, advanced civilizations", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic, historical, medieval" + }, + { + "name": "futuristic-vaporwave", + "prompt": "vaporwave style {prompt} . retro aesthetic, cyberpunk, vibrant, neon colors, vintage 80s and 90s style, highly detailed", + "negative_prompt": "monochrome, muted colors, realism, rustic, minimalist, dark" + }, + { + "name": "game-bubble bobble", + "prompt": "Bubble Bobble style {prompt} . 8-bit, cute, pixelated, fantasy, vibrant, reminiscent of Bubble Bobble game", + "negative_prompt": "realistic, modern, photorealistic, violent, horror" + }, + { + "name": "game-cyberpunk game", + "prompt": "cyberpunk game style {prompt} . neon, dystopian, futuristic, digital, vibrant, detailed, high contrast, reminiscent of cyberpunk genre video games", + "negative_prompt": "historical, natural, rustic, low detailed" + }, + { + "name": "game-fighting game", + "prompt": "fighting game style {prompt} . dynamic, vibrant, action-packed, detailed character design, reminiscent of fighting video games", + "negative_prompt": "peaceful, calm, minimalist, photorealistic" + }, + { + "name": "game-gta", + "prompt": "GTA-style artwork {prompt} . satirical, exaggerated, pop art style, vibrant colors, iconic characters, action-packed", + "negative_prompt": "realistic, black and white, low contrast, impressionist, cubist, noisy, blurry, deformed" + }, + { + "name": "game-mario", + "prompt": "Super Mario style {prompt} . vibrant, cute, cartoony, fantasy, playful, reminiscent of Super Mario series", + "negative_prompt": "realistic, modern, horror, dystopian, violent" + }, + { + "name": "game-minecraft", + "prompt": "Minecraft style {prompt} . blocky, pixelated, vibrant colors, recognizable characters and objects, game assets", + "negative_prompt": "smooth, realistic, detailed, photorealistic, noise, blurry, deformed" + }, + { + "name": "game-pokemon", + "prompt": "Pokémon style {prompt} . vibrant, cute, anime, fantasy, reminiscent of Pokémon series", + "negative_prompt": "realistic, modern, horror, dystopian, violent" + }, + { + "name": "game-retro arcade", + "prompt": "retro arcade style {prompt} . 8-bit, pixelated, vibrant, classic video game, old school gaming, reminiscent of 80s and 90s arcade games", + "negative_prompt": "modern, ultra-high resolution, photorealistic, 3D" + }, + { + "name": "game-retro game", + "prompt": "retro game art {prompt} . 16-bit, vibrant colors, pixelated, nostalgic, charming, fun", + "negative_prompt": "realistic, photorealistic, 35mm film, deformed, glitch, low contrast, noisy" + }, + { + "name": "game-rpg fantasy game", + "prompt": "role-playing game (RPG) style fantasy {prompt} . detailed, vibrant, immersive, reminiscent of high fantasy RPG games", + "negative_prompt": "sci-fi, modern, urban, futuristic, low detailed" + }, + { + "name": "game-strategy game", + "prompt": "strategy game style {prompt} . overhead view, detailed map, units, reminiscent of real-time strategy video games", + "negative_prompt": "first-person view, modern, photorealistic" + }, + { + "name": "game-streetfighter", + "prompt": "Street Fighter style {prompt} . vibrant, dynamic, arcade, 2D fighting game, highly detailed, reminiscent of Street Fighter series", + "negative_prompt": "3D, realistic, modern, photorealistic, turn-based strategy" + }, + { + "name": "game-zelda", + "prompt": "Legend of Zelda style {prompt} . vibrant, fantasy, detailed, epic, heroic, reminiscent of The Legend of Zelda series", + "negative_prompt": "sci-fi, modern, realistic, horror" + }, + { + "name": "misc-architectural", + "prompt": "architectural style {prompt} . clean lines, geometric shapes, minimalist, modern, architectural drawing, highly detailed", + "negative_prompt": "curved lines, ornate, baroque, abstract, grunge" + }, + { + "name": "misc-disco", + "prompt": "disco-themed {prompt} . vibrant, groovy, retro 70s style, shiny disco balls, neon lights, dance floor, highly detailed", + "negative_prompt": "minimalist, rustic, monochrome, contemporary, simplistic" + }, + { + "name": "misc-dreamscape", + "prompt": "dreamscape {prompt} . surreal, ethereal, dreamy, mysterious, fantasy, highly detailed", + "negative_prompt": "realistic, concrete, ordinary, mundane" + }, + { + "name": "misc-dystopian", + "prompt": "dystopian style {prompt} . bleak, post-apocalyptic, somber, dramatic, highly detailed", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, cheerful, optimistic, vibrant, colorful" + }, + { + "name": "misc-fairy tale", + "prompt": "fairy tale {prompt} . magical, fantastical, enchanting, storybook style, highly detailed", + "negative_prompt": "realistic, modern, ordinary, mundane" + }, + { + "name": "misc-gothic", + "prompt": "gothic style {prompt} . dark, mysterious, haunting, dramatic, ornate, detailed", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic, cheerful, optimistic" + }, + { + "name": "misc-grunge", + "prompt": "grunge style {prompt} . textured, distressed, vintage, edgy, punk rock vibe, dirty, noisy", + "negative_prompt": "smooth, clean, minimalist, sleek, modern, photorealistic" + }, + { + "name": "misc-horror", + "prompt": "horror-themed {prompt} . eerie, unsettling, dark, spooky, suspenseful, grim, highly detailed", + "negative_prompt": "cheerful, bright, vibrant, light-hearted, cute" + }, + { + "name": "misc-kawaii", + "prompt": "kawaii style {prompt} . cute, adorable, brightly colored, cheerful, anime influence, highly detailed", + "negative_prompt": "dark, scary, realistic, monochrome, abstract" + }, + { + "name": "misc-lovecraftian", + "prompt": "lovecraftian horror {prompt} . eldritch, cosmic horror, unknown, mysterious, surreal, highly detailed", + "negative_prompt": "light-hearted, mundane, familiar, simplistic, realistic" + }, + { + "name": "misc-macabre", + "prompt": "macabre style {prompt} . dark, gothic, grim, haunting, highly detailed", + "negative_prompt": "bright, cheerful, light-hearted, cartoonish, cute" + }, + { + "name": "misc-manga", + "prompt": "manga style {prompt} . vibrant, high-energy, detailed, iconic, Japanese comic style", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic, Western comic style" + }, + { + "name": "misc-metropolis", + "prompt": "metropolis-themed {prompt} . urban, cityscape, skyscrapers, modern, futuristic, highly detailed", + "negative_prompt": "rural, natural, rustic, historical, simple" + }, + { + "name": "misc-minimalist", + "prompt": "minimalist style {prompt} . simple, clean, uncluttered, modern, elegant", + "negative_prompt": "ornate, complicated, highly detailed, cluttered, disordered, messy, noisy" + }, + { + "name": "misc-monochrome", + "prompt": "monochrome {prompt} . black and white, contrast, tone, texture, detailed", + "negative_prompt": "colorful, vibrant, noisy, blurry, deformed" + }, + { + "name": "misc-nautical", + "prompt": "nautical-themed {prompt} . sea, ocean, ships, maritime, beach, marine life, highly detailed", + "negative_prompt": "landlocked, desert, mountains, urban, rustic" + }, + { + "name": "misc-space", + "prompt": "space-themed {prompt} . cosmic, celestial, stars, galaxies, nebulas, planets, science fiction, highly detailed", + "negative_prompt": "earthly, mundane, ground-based, realism" + }, + { + "name": "misc-stained glass", + "prompt": "stained glass style {prompt} . vibrant, beautiful, translucent, intricate, detailed", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" + }, + { + "name": "misc-techwear fashion", + "prompt": "techwear fashion {prompt} . futuristic, cyberpunk, urban, tactical, sleek, dark, highly detailed", + "negative_prompt": "vintage, rural, colorful, low contrast, realism, sketch, watercolor" + }, + { + "name": "misc-tribal", + "prompt": "tribal style {prompt} . indigenous, ethnic, traditional patterns, bold, natural colors, highly detailed", + "negative_prompt": "modern, futuristic, minimalist, pastel" + }, + { + "name": "misc-zentangle", + "prompt": "zentangle {prompt} . intricate, abstract, monochrome, patterns, meditative, highly detailed", + "negative_prompt": "colorful, representative, simplistic, large fields of color" + }, + { + "name": "papercraft-collage", + "prompt": "collage style {prompt} . mixed media, layered, textural, detailed, artistic", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" + }, + { + "name": "papercraft-flat papercut", + "prompt": "flat papercut style {prompt} . silhouette, clean cuts, paper, sharp edges, minimalist, color block", + "negative_prompt": "3D, high detail, noise, grainy, blurry, painting, drawing, photo, disfigured" + }, + { + "name": "papercraft-kirigami", + "prompt": "kirigami representation of {prompt} . 3D, paper folding, paper cutting, Japanese, intricate, symmetrical, precision, clean lines", + "negative_prompt": "painting, drawing, 2D, noisy, blurry, deformed" + }, + { + "name": "papercraft-paper mache", + "prompt": "paper mache representation of {prompt} . 3D, sculptural, textured, handmade, vibrant, fun", + "negative_prompt": "2D, flat, photo, sketch, digital art, deformed, noisy, blurry" + }, + { + "name": "papercraft-paper quilling", + "prompt": "paper quilling art of {prompt} . intricate, delicate, curling, rolling, shaping, coiling, loops, 3D, dimensional, ornamental", + "negative_prompt": "photo, painting, drawing, 2D, flat, deformed, noisy, blurry" + }, + { + "name": "papercraft-papercut collage", + "prompt": "papercut collage of {prompt} . mixed media, textured paper, overlapping, asymmetrical, abstract, vibrant", + "negative_prompt": "photo, 3D, realistic, drawing, painting, high detail, disfigured" + }, + { + "name": "papercraft-papercut shadow box", + "prompt": "3D papercut shadow box of {prompt} . layered, dimensional, depth, silhouette, shadow, papercut, handmade, high contrast", + "negative_prompt": "painting, drawing, photo, 2D, flat, high detail, blurry, noisy, disfigured" + }, + { + "name": "papercraft-stacked papercut", + "prompt": "stacked papercut art of {prompt} . 3D, layered, dimensional, depth, precision cut, stacked layers, papercut, high contrast", + "negative_prompt": "2D, flat, noisy, blurry, painting, drawing, photo, deformed" + }, + { + "name": "papercraft-thick layered papercut", + "prompt": "thick layered papercut art of {prompt} . deep 3D, volumetric, dimensional, depth, thick paper, high stack, heavy texture, tangible layers", + "negative_prompt": "2D, flat, thin paper, low stack, smooth texture, painting, drawing, photo, deformed" + }, + { + "name": "photo-alien", + "prompt": "alien-themed {prompt} . extraterrestrial, cosmic, otherworldly, mysterious, sci-fi, highly detailed", + "negative_prompt": "earthly, mundane, common, realistic, simple" + }, + { + "name": "photo-film noir", + "prompt": "film noir style {prompt} . monochrome, high contrast, dramatic shadows, 1940s style, mysterious, cinematic", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic, vibrant, colorful" + }, + { + "name": "photo-glamour", + "prompt": "glamorous photo {prompt} . high fashion, luxurious, extravagant, stylish, sensual, opulent, elegance, stunning beauty, professional, high contrast, detailed", + "negative_prompt": "ugly, deformed, noisy, blurry, distorted, grainy, sketch, low contrast, dull, plain, modest" + }, + { + "name": "photo-hdr", + "prompt": "HDR photo of {prompt} . High dynamic range, vivid, rich details, clear shadows and highlights, realistic, intense, enhanced contrast, highly detailed", + "negative_prompt": "flat, low contrast, oversaturated, underexposed, overexposed, blurred, noisy" + }, + { + "name": "photo-iphone photographic", + "prompt": "iphone photo {prompt} . large depth of field, deep depth of field, highly detailed", + "negative_prompt": "drawing, painting, crayon, sketch, graphite, impressionist, noisy, blurry, soft, deformed, ugly, shallow depth of field, bokeh" + }, + { + "name": "photo-long exposure", + "prompt": "long exposure photo of {prompt} . Blurred motion, streaks of light, surreal, dreamy, ghosting effect, highly detailed", + "negative_prompt": "static, noisy, deformed, shaky, abrupt, flat, low contrast" + }, + { + "name": "photo-neon noir", + "prompt": "neon noir {prompt} . cyberpunk, dark, rainy streets, neon signs, high contrast, low light, vibrant, highly detailed", + "negative_prompt": "bright, sunny, daytime, low contrast, black and white, sketch, watercolor" + }, + { + "name": "photo-silhouette", + "prompt": "silhouette style {prompt} . high contrast, minimalistic, black and white, stark, dramatic", + "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, color, realism, photorealistic" + }, + { + "name": "photo-tilt-shift", + "prompt": "tilt-shift photo of {prompt} . selective focus, miniature effect, blurred background, highly detailed, vibrant, perspective control", + "negative_prompt": "blurry, noisy, deformed, flat, low contrast, unrealistic, oversaturated, underexposed" + } +] \ No newline at end of file diff --git a/styles/zh_CN.json b/styles/zh_CN.json new file mode 100644 index 0000000..67dada3 --- /dev/null +++ b/styles/zh_CN.json @@ -0,0 +1,443 @@ +{ + "Preview": "预览", + "Gallery": "相册", + "Generate": "生成", + "Skip": "跳过", + "Stop": "停止", + "Input Image": "图像输入", + "Advanced": "高级设置", + "Upscale or Variation": "放大或变化", + "Image Prompt": "参考图", + "Inpaint or Outpaint": "内部重绘或外部扩图", + "Drag above image to here": "将图像拖到这里", + "Upscale or Variation:": "放大或变化:", + "Disabled": "禁用", + "Vary (Subtle)": "变化(微妙)", + "Vary (Strong)": "变化(强烈)", + "Upscale (1.5x)": "放大(1.5倍)", + "Upscale (2x)": "放大(2倍)", + "Upscale (Fast 2x)": "快速放大(2倍)", + "\ud83d\udcd4 Document": "\ud83d\udcd4 说明文档", + "Image": "图像", + "Stop At": "停止于", + "Weight": "权重", + "Type": "类型", + "PyraCanny": "轮廓复制", + "CPDS": "深度结构复制", + "* \"Image Prompt\" is powered by Fooocus Image Mixture Engine (v1.0.1).": "* \"图像输入\"由 Fooocus 图像混合引擎提供支持(v1.0.1)。", + "The scaler multiplied to positive ADM (use 1.0 to disable).": "正向 ADM 的缩放倍数(使用 1.0 禁用)。", + "The scaler multiplied to negative ADM (use 1.0 to disable).": "反向 ADM 的缩放倍数(使用 1.0 禁用)。", + "When to end the guidance from positive/negative ADM.": "何时结束来自正向/反向 ADM 的指导。", + "Similar to the Control Mode in A1111 (use 0.0 to disable).": "类似于SD WebUI中的控制模式(使用 0.0 禁用)。", + "Outpaint Direction": "外部扩图", + "Outpaint": "外部扩图", + "Left": "向左扩图", + "Right": "向右扩图", + "Top": "向上扩图", + "Bottom": "向下扩图", + "* Powered by Fooocus Inpaint Engine": "* 由Fooocus重绘引擎提供支持", + "Setting": "设置", + "Style": "样式", + "Performance": "性能", + "Speed": "速度", + "Quality": "质量", + "Aspect Ratios": "宽高比", + "width \u00d7 height": "宽 \u00d7 高", + "Image Number": "出图数量", + "Negative Prompt": "反向提示词", + "Describing what you do not want to see.": "描述你不想看到的内容。", + "Random": "随机种子", + "Seed": "种子", + "\ud83d\udcda History Log": "\ud83d\udcda 图片生成历史记录", + "Image Style": "图像风格", + "Fooocus V2": "Fooocus V2提示词智能扩图", + "Default (Slightly Cinematic)": "默认(轻微的电影感)", + "Fooocus Masterpiece": "Fooocus-杰作", + "Fooocus Photograph": "Fooocus-照片", + "Fooocus Negative": "Fooocus-反向提示词", + "SAI 3D Model": "SAI-3D模型", + "SAI Analog Film": "SAI-模拟电影", + "SAI Anime": "SAI-动漫", + "SAI Cinematic": "SAI-电影片段", + "SAI Comic Book": "SAI-漫画", + "SAI Craft Clay": "SAI-工艺粘土", + "SAI Digital Art": "SAI-数字艺术", + "SAI Enhance": "SAI-增强", + "SAI Fantasy Art": "SAI-奇幻艺术", + "SAI Isometric": "SAI-等距风格", + "SAI Line Art": "SAI-线条艺术", + "SAI Lowpoly": "SAI-低多边形", + "SAI Neonpunk": "SAI-霓虹朋克", + "SAI Origami": "SAI-折纸", + "SAI Photographic": "SAI-摄影", + "SAI Pixel Art": "SAI-像素艺术", + "SAI Texture": "SAI-纹理", + "MRE Cinematic Dynamic": "MRE-史诗电影", + "MRE Spontaneous Picture": "MRE-自然的抓拍照片", + "MRE Artistic Vision": "MRE-艺术视觉", + "MRE Dark Dream": "MRE-黑暗梦境", + "MRE Gloomy Art": "MRE-阴郁艺术", + "MRE Bad Dream": "MRE-噩梦", + "MRE Underground": "MRE-阴森地下", + "MRE Surreal Painting": "MRE-超现实主义绘画", + "MRE Dynamic Illustration": "MRE-动态插画", + "MRE Undead Art": "MRE-遗忘艺术家作品", + "MRE Elemental Art": "MRE-元素艺术", + "MRE Space Art": "MRE-空间艺术", + "MRE Ancient Illustration": "MRE-古代插图", + "MRE Brave Art": "MRE-勇敢艺术", + "MRE Heroic Fantasy": "MRE-英雄幻想", + "MRE Dark Cyberpunk": "MRE-黑暗赛博朋克", + "MRE Lyrical Geometry": "MRE-抒情几何抽象画", + "MRE Sumi E Symbolic": "MRE-墨绘长笔画", + "MRE Sumi E Detailed": "MRE-精细墨绘画", + "MRE Manga": "MRE-日本漫画", + "MRE Anime": "MRE-日本动画片", + "MRE Comic": "MRE-成人漫画书插画", + "Ads Advertising": "广告-广告", + "Ads Automotive": "广告-汽车", + "Ads Corporate": "广告-企业品牌", + "Ads Fashion Editorial": "广告-时尚编辑", + "Ads Food Photography": "广告-食品摄影", + "Ads Gourmet Food Photography": "广告-顶级美食摄影", + "Ads Luxury": "广告-奢侈品", + "Ads Real Estate": "广告-房地产", + "Ads Retail": "广告-零售", + "Artstyle Abstract": "艺术风格-抽象", + "Artstyle Abstract Expressionism": "艺术风格-抽象表现主义", + "Artstyle Art Deco": "艺术风格-装饰艺术", + "Artstyle Art Nouveau": "艺术风格-新艺术", + "Artstyle Constructivist": "艺术风格-构造主义", + "Artstyle Cubist": "艺术风格-立体主义", + "Artstyle Expressionist": "艺术风格-表现主义", + "Artstyle Graffiti": "艺术风格-涂鸦", + "Artstyle Hyperrealism": "艺术风格-超写实主义", + "Artstyle Impressionist": "艺术风格-印象派", + "Artstyle Pointillism": "艺术风格-点彩派", + "Artstyle Pop Art": "艺术风格-波普艺术", + "Artstyle Psychedelic": "艺术风格-迷幻", + "Artstyle Renaissance": "艺术风格-文艺复兴", + "Artstyle Steampunk": "艺术风格-蒸汽朋克", + "Artstyle Surrealist": "艺术风格-超现实主义", + "Artstyle Typography": "艺术风格-字体设计", + "Artstyle Watercolor": "艺术风格-水彩", + "Futuristic Biomechanical": "未来主义-生物机械", + "Futuristic Biomechanical Cyberpunk": "未来主义-生物机械-赛博朋克", + "Futuristic Cybernetic": "未来主义-人机融合", + "Futuristic Cybernetic Robot": "未来主义-人机融合-机器人", + "Futuristic Cyberpunk Cityscape": "未来主义-赛博朋克城市", + "Futuristic Futuristic": "未来主义-未来主义", + "Futuristic Retro Cyberpunk": "未来主义-复古赛博朋克", + "Futuristic Retro Futurism": "未来主义-复古未来主义", + "Futuristic Sci Fi": "未来主义-科幻", + "Futuristic Vaporwave": "未来主义-蒸汽波", + "Game Bubble Bobble": "游戏-泡泡龙", + "Game Cyberpunk Game": "游戏-赛博朋克游戏", + "Game Fighting Game": "游戏-格斗游戏", + "Game Gta": "游戏-侠盗猎车手", + "Game Mario": "游戏-马里奥", + "Game Minecraft": "游戏-我的世界", + "Game Pokemon": "游戏-宝可梦", + "Game Retro Arcade": "游戏-复古街机", + "Game Retro Game": "游戏-复古游戏", + "Game Rpg Fantasy Game": "游戏-角色扮演幻想游戏", + "Game Strategy Game": "游戏-策略游戏", + "Game Streetfighter": "游戏-街头霸王", + "Game Zelda": "游戏-塞尔达传说", + "Misc Architectural": "其他-建筑", + "Misc Disco": "其他-迪斯科", + "Misc Dreamscape": "其他-梦境", + "Misc Dystopian": "其他-反乌托邦", + "Misc Fairy Tale": "其他-童话故事", + "Misc Gothic": "其他-哥特风", + "Misc Grunge": "其他-垮掉的", + "Misc Horror": "其他-恐怖", + "Misc Kawaii": "其他-可爱", + "Misc Lovecraftian": "其他-洛夫克拉夫特", + "Misc Macabre": "其他-恐怖", + "Misc Manga": "其他-漫画", + "Misc Metropolis": "其他-大都市", + "Misc Minimalist": "其他-极简主义", + "Misc Monochrome": "其他-单色", + "Misc Nautical": "其他-航海", + "Misc Space": "其他-太空", + "Misc Stained Glass": "其他-彩色玻璃", + "Misc Techwear Fashion": "其他-科技时尚", + "Misc Tribal": "其他-部落", + "Misc Zentangle": "其他-禅绕画", + "Papercraft Collage": "手工艺-拼贴", + "Papercraft Flat Papercut": "手工艺-平面剪纸", + "Papercraft Kirigami": "手工艺-切纸", + "Papercraft Paper Mache": "手工艺-纸浆塑造", + "Papercraft Paper Quilling": "手工艺-纸艺卷轴", + "Papercraft Papercut Collage": "手工艺-剪纸拼贴", + "Papercraft Papercut Shadow Box": "手工艺-剪纸影箱", + "Papercraft Stacked Papercut": "手工艺-层叠剪纸", + "Papercraft Thick Layered Papercut": "手工艺-厚层剪纸", + "Photo Alien": "摄影-外星人", + "Photo Film Noir": "摄影-黑色电影", + "Photo Glamour": "摄影-魅力", + "Photo Hdr": "摄影-高动态范围", + "Photo Iphone Photographic": "摄影-苹果手机摄影", + "Photo Long Exposure": "摄影-长曝光", + "Photo Neon Noir": "摄影-霓虹黑色", + "Photo Silhouette": "摄影-轮廓", + "Photo Tilt Shift": "摄影-移轴", + "Cinematic Diva": "电影女主角", + "Abstract Expressionism": "抽象表现主义", + "Academia": "学术", + "Action Figure": "动作人偶", + "Adorable 3D Character": "可爱的3D角色", + "Adorable Kawaii": "可爱的卡哇伊", + "Art Deco": "装饰艺术", + "Art Nouveau": "新艺术,美丽艺术", + "Astral Aura": "星体光环", + "Avant Garde": "前卫", + "Baroque": "巴洛克", + "Bauhaus Style Poster": "包豪斯风格海报", + "Blueprint Schematic Drawing": "蓝图示意图", + "Caricature": "漫画", + "Cel Shaded Art": "卡通渲染", + "Character Design Sheet": "角色设计表", + "Classicism Art": "古典主义艺术", + "Color Field Painting": "色彩领域绘画", + "Colored Pencil Art": "彩色铅笔艺术", + "Conceptual Art": "概念艺术", + "Constructivism": "建构主义", + "Cubism": "立体主义", + "Dadaism": "达达主义", + "Dark Fantasy": "黑暗奇幻", + "Dark Moody Atmosphere": "黑暗忧郁气氛", + "Dmt Art Style": "迷幻艺术风格", + "Doodle Art": "涂鸦艺术", + "Double Exposure": "双重曝光", + "Dripping Paint Splatter Art": "滴漆飞溅艺术", + "Expressionism": "表现主义", + "Faded Polaroid Photo": "褪色的宝丽来照片", + "Fauvism": "野兽派", + "Flat 2d Art": "平面 2D 艺术", + "Fortnite Art Style": "堡垒之夜艺术风格", + "Futurism": "未来派", + "Glitchcore": "故障核心", + "Glo Fi": "光明高保真", + "Googie Art Style": "古吉艺术风格", + "Graffiti Art": "涂鸦艺术", + "Harlem Renaissance Art": "哈莱姆文艺复兴艺术", + "High Fashion": "高级时装", + "Idyllic": "田园诗般", + "Impressionism": "印象派", + "Infographic Drawing": "信息图表绘图", + "Ink Dripping Drawing": "滴墨绘画", + "Japanese Ink Drawing": "日式水墨画", + "Knolling Photography": "规律摆放摄影", + "Light Cheery Atmosphere": "轻松愉快的气氛", + "Logo Design": "标志设计", + "Luxurious Elegance": "奢华优雅", + "Macro Photography": "微距摄影", + "Mandola Art": "曼陀罗艺术", + "Marker Drawing": "马克笔绘图", + "Medievalism": "中世纪主义", + "Minimalism": "极简主义", + "Neo Baroque": "新巴洛克", + "Neo Byzantine": "新拜占庭", + "Neo Futurism": "新未来派", + "Neo Impressionism": "新印象派", + "Neo Rococo": "新洛可可", + "Neoclassicism": "新古典主义", + "Op Art": "欧普艺术", + "Ornate And Intricate": "华丽而复杂", + "Pencil Sketch Drawing": "铅笔素描", + "Pop Art 2": "流行艺术2", + "Rococo": "洛可可", + "Silhouette Art": "剪影艺术", + "Simple Vector Art": "简单矢量艺术", + "Sketchup": "草图", + "Steampunk 2": "赛博朋克2", + "Surrealism": "超现实主义", + "Suprematism": "至上主义", + "Terragen": "地表风景", + "Tranquil Relaxing Atmosphere": "宁静轻松的氛围", + "Sticker Designs": "贴纸设计", + "Vibrant Rim Light": "生动的边缘光", + "Volumetric Lighting": "体积照明", + "Watercolor 2": "水彩2", + "Whimsical And Playful": "异想天开、俏皮", + "Model": "模型", + "Base Model (SDXL only)": "基础模型(只支持SDXL)", + "sd_xl_base_1.0_0.9vae.safetensors": "基础模型 sd_xl_base_1.0_0.9vae", + "bluePencilXL_v009.safetensors": "动漫模型 bluePencilXL_v009", + "bluePencilXL_v050.safetensors": "动漫模型 bluePencilXL_v050", + "DreamShaper_8_pruned.safetensors": "2.5D模型 DreamShaper_8_pruned", + "realisticStockPhoto_v10.safetensors": "现实模型 realisticStockPhoto_v10", + "realisticVisionV51_v51VAE.safetensors": "现实模型 realisticVisionV51_v51VAE", + "sd_xl_refiner_1.0_0.9vae.safetensors": "精炼模型 sd_xl_refiner_1.0_0.9vae", + "Refiner (SDXL or SD 1.5)": "精炼模型 (支持SDXL 或 SD 1.5)", + "None": "无", + "LoRAs": "LoRAs模型", + "SDXL LoRA 1": "SDXL LoRA 1", + "sd_xl_offset_example-lora_1.0.safetensors": "sd_xl_offset_example-lora_1.0 官方示例lora", + "3d_render_style_xl.safetensors": "3d_render_style_xl 3D渲染风格模型", + "Bloodstained-XL-V1.safetensors": "Bloodstained-XL-V1 血色污染模型", + "SDXL_FILM_PHOTOGRAPHY_STYLE_BetaV0.4.safetensors": "SDXL_FILM_PHOTOGRAPHY_STYLE_BetaV0.4 电影剧照模型", + "SDXL LoRA 2": "SDXL LoRA 2", + "SDXL LoRA 3": "SDXL LoRA 3", + "SDXL LoRA 4": "SDXL LoRA 4", + "SDXL LoRA 5": "SDXL LoRA 5", + "Refresh": "刷新", + "\ud83d\udd04 Refresh All Files": "\ud83d\udd04 刷新全部文件", + "Image Sharpness": "图像清晰度", + "Higher value means image and texture are sharper.": "值越大,图像和纹理越清晰。", + "Guidance Scale": "CFG提示词引导系数", + "Higher value means style is cleaner, vivider, and more artistic.": "提示词作用的强度,值越大,风格越干净、生动、更具艺术感。", + "Developer Debug Mode": "开发者调试模式", + "Debug Tools": "调试工具", + "Positive ADM Guidance Scaler": "正向ADM引导系数", + "The scaler multiplied to positive ADM (use 1.0 to disable). ": "正向ADM引导的倍率 (使用1.0以禁用)。 ", + "Negative ADM Guidance Scaler": "负向ADM引导系数", + "The scaler multiplied to negative ADM (use 1.0 to disable). ": "负向ADM引导的倍率(使用1.0以禁用)。 ", + "ADM Guidance End At Step": "ADM引导结束步数", + "When to end the guidance from positive/negative ADM. ": "正向/负向ADM结束引导的时间。 ", + "Refiner swap method": "Refiner精炼模型交换方式", + "joint": "joint联合", + "separate": "separate分离", + "vae": "vae变分自编码器", + "CFG Mimicking from TSNR": "从TSNR模拟CFG", + "Enabling Fooocus's implementation of CFG mimicking for TSNR (effective when real CFG > mimicked CFG).": "启用TSNR模拟CFG,修复因CFG过高而引起的画面污染(当真实的CFG大于模拟的CFG时生效)。", + "Sampler": "采样器", + "dpmpp_2m_sde_gpu": "dpmpp_2m_sde_gpu", + "Only effective in non-inpaint mode.": "仅在非重绘模式下有效。", + "euler": "euler", + "euler_ancestral": "euler_ancestral", + "heun": "heun", + "dpm_2": "dpm_2", + "dpm_2_ancestral": "dpm_2_ancestral", + "lms": "lms", + "dpm_fast": "dpm_fast", + "dpm_adaptive": "dpm_adaptive", + "dpmpp_2s_ancestral": "dpmpp_2s_ancestral", + "dpmpp_sde": "dpmpp_sde", + "dpmpp_sde_gpu": "dpmpp_sde_gpu", + "dpmpp_2m": "dpmpp_2m", + "dpmpp_2m_sde": "dpmpp_2m_sde", + "dpmpp_3m_sde": "dpmpp_3m_sde", + "dpmpp_3m_sde_gpu": "dpmpp_3m_sde_gpu", + "ddpm": "ddpm", + "ddim": "ddim", + "uni_pc": "uni_pc", + "uni_pc_bh2": "uni_pc_bh2", + "Scheduler": "调度器", + "karras": "karras", + "Scheduler of Sampler.": "采样器的调度器。", + "normal": "normal", + "exponential": "exponential", + "sgm_uniform": "sgm_uniform", + "simple": "simple", + "ddim_uniform": "ddim_uniform", + "Forced Overwrite of Sampling Step": "强制覆盖采样步数", + "Set as -1 to disable. For developer debugging.": "设为-1以禁用。用于开发者调试。", + "Forced Overwrite of Refiner Switch Step": "强制重写精炼器开关步数", + "Forced Overwrite of Generating Width": "强制覆盖生成宽度", + "Set as -1 to disable. For developer debugging. Results will be worse for non-standard numbers that SDXL is not trained on.": "设为-1以禁用。用于开发者调试。对于SDXL没有训练过的非标准数字,结果会差。", + "Forced Overwrite of Generating Height": "强制覆盖生成高度", + "Forced Overwrite of Denoising Strength of \"Vary\"": "强制覆盖 \"变化\"的去噪强度", + "Set as negative number to disable. For developer debugging.": "设为负数以禁用。用于开发者调试。", + "Forced Overwrite of Denoising Strength of \"Upscale\"": "强制覆盖 \"放大\"去噪强度", + "Inpaint Engine": "Inpaint 重绘引擎", + "v1": "v1.0稳定版", + "Version of Fooocus inpaint model": "inpaint 重绘模型的版本选择", + "v2.5": "v2.5测试版", + "Control Debug": "控制调试", + "Debug Preprocessors": "调试预处理器", + "Mixing Image Prompt and Vary/Upscale": "混合图像提示词和变化/放大", + "Mixing Image Prompt and Inpaint": "混合图像提示词和重绘", + "Softness of ControlNet": "ControlNet柔和度", + "Similar to the Control Mode in A1111 (use 0.0 to disable). ": "类似于SD WebUI(A1111)中的控制模式(使用0.0来禁用)。 ", + "Canny": "Canny 边缘检测算法", + "Canny Low Threshold": "Canny 最低阈值", + "Canny High Threshold": "Canny 最高阈值", + "FreeU": "FreeU 提示词精准性优化", + "Enabled": "启用", + "B1": "B1", + "B2": "B2", + "S1": "S1", + "S2": "S2", + "Extreme Speed": "极速LCM", + "\uD83D\uDD0E Type here to search styles ...": "\uD83D\uDD0E 在此输入关键词搜索样式……", + "Type prompt here.": "在这里输入提示词,用英文逗号分隔。(支持选中后用Ctrl+上下方向键调整提示词权重,支持wildcards文件夹内通配符批量出图:__通配符文件名__提示词)", + "Outpaint Expansion Direction:": "外部扩图方向:", + "* Powered by Fooocus Inpaint Engine (beta)": "* 由Fooocus修复引擎驱动(测试版)", + "Fooocus Enhance": "Fooocus-优化增强", + "Fooocus Cinematic": "Fooocus-电影感", + "Fooocus Sharp": "Fooocus-锐化", + "Disable Preview": "禁用预览", + "Disable preview during generation.": "在图像生成过程中禁用预览。", + "bluePencilXL_v080.safetensors": "动漫模型 bluePencilXL_v080", + "4Guofeng4XL_v10RealBeta.safetensors": "国风真人模型 4Guofeng4XL_v10RealBeta", + "4Guofeng4XL_v1125D.safetensors": "国风动漫模型 4Guofeng4XL_v1125D", + "SDXLRonghua_v30.safetensors": "国风容华 SDXLRonghua_v30", + "juggernautXL_version6Rundiffusion.safetensors": "默认现实模型juggernautXL_version6Rundiffusion", + "Alt + wheel": "Alt + 滚轮", + "Ctrl + wheel": "Ctrl + 滚轮", + "- Zoom canvas": "= 缩放画布", + "- Adjust brush size": "= 调整笔刷尺寸", + "- Reset zoom": "= 画布复位", + "- Fullscreen mode": "= 全屏模式", + "- Move canvas": "= 移动画布", + "- Overlap": "= 图层重叠", + "Refiner Switch At": "精炼器切换节点", + "Use 0.4 for SD1.5 realistic models; or 0.667 for SD1.5 anime models; or 0.8 for XL-refiners; or any value for switching two SDXL models.": "SD1.5的现实模型用0.4;SD1.5的动漫模型用0.667;XL专用精炼模型用0.8;两个SDXL模型之间可以在任意节点切换。", + "Initializing ...": "正在初始化……", + "Downloading inpainter ...": "正在下载局部重绘工具……", + "Downloading upscale models ...": "正在下载放大模型……", + "Downloading control models ...": "正在下载Control模型……", + "Loading models ...": "正在装载模型……", + "Loading control models ...": "正在装载Control模型……", + "Processing prompts ...": "正在处理提示词……", + "Image processing ...": "正在处理图像……", + "Preparing Fooocus text #1 ...": "正在准备Fooocus文本 #1……", + "Encoding positive #1 ...": "编码正向提示词 #1……", + "Encoding negative #1 ...": "编码反向提示词 #1……", + "VAE encoding ...": "正在编码VAE……", + "VAE Inpaint encoding ...": "正在编码局部重绘VAE……", + "VAE Inpaint SD15 encoding ...": "正在编码SD1.5局部重绘VAE……", + "Moving model to GPU ...": "正在将模型移动到GPU……", + "Generate Image Grid for Each Batch": "为每个批次生成图像网格", + "(Experimental) This may cause performance problems on some computers and certain internet conditions.": "(试验性)在某些计算机和特定的网络条件下,这可能会导致性能问题。", + "Use brush": "使用画笔", + "Undo": "撤回一步", + "Clear": "清除", + "Remove Image": "关闭图片", + "Edit": "编辑", + "LoRAs (SDXL or SD 1.5)": "Lora微调模型 (支持SDXL 或 SD 1.5)", + "ImagePrompt": "图像提示词", + "FaceSwap": "换脸", + "v2.6": "v2.6测试版", + "See the results from preprocessors.": "查看预处理器结果。", + "Skip Preprocessors": "跳过预处理器", + "Do not preprocess images. (Inputs are already canny/depth/cropped-face/etc.)": "不要对图像进行预处理。(输入的图像已经是边缘检测/深度/换脸等。)", + "Waiting for task to start ...": "等待任务开始……", + "Error": "错误", + "Connection errored out.": "可能是命令行被关闭造成连接出错。", + "All connection attempts failed": "可能是端口被占用或者IP冲突造成网络连接失败。", + "CPUAllocator": "可能是未设置虚拟内存。", + "Method": "模式", + "Inpaint or Outpaint (default)": "内部重绘或外部扩图(默认)", + "Improve Detail (face, hand, eyes, etc.)": "改善细节(面部、手部、眼睛等)", + "Modify Content (add objects, change background, etc.)": "修改内容(添加物体,更改背景等)", + "Control": "控制", + "Inpaint": "重绘", + "Debug Inpaint Preprocessing": "调试重绘预处理", + "Disable initial latent in inpaint": "禁用重绘中的初始潜在变量", + "Inpaint Denoising Strength": "重绘去噪强度", + "Same as the denoising strength in A1111 inpaint. Only used in inpaint, not used in outpaint. (Outpaint always use 1.0)": "与SD WebUI(A1111)重绘中的去噪强度相同。仅用于内部,不用于外部扩图。(外部扩图始终使用1.0)", + "Inpaint Respective Field": "重绘遮罩区域", + "The area to inpaint. Value 0 is same as \"Only Masked\" in A1111. Value 1 is same as \"Whole Image\" in A1111. Only used in inpaint, not used in outpaint. (Outpaint always use 1.0)": "重绘遮罩区域。值0与SD WebUI(A1111)中的“仅遮罩”相同。值1与SD WebUI(A1111)中的“整个图像”相同。中间值相当于PS中的羽化范围。只用于内部重绘,在外部扩图中不使用。(外部扩图总是使用1.0)", + "Additional Prompt Quick List": "附加提示快速列表", + "highly detailed face": "高细节的脸", + "detailed girl face": "细节的女性脸", + "detailed man face": "细节的男性脸", + "detailed hand": "细节的手", + "beautiful eyes": "细节的眼", + "Inpaint Additional Prompt": "重绘附加提示词", + "Describe what you want to inpaint.": "描述你想要重绘的内容。" +} \ No newline at end of file diff --git a/third_part/GFPGAN/LICENSE b/third_part/GFPGAN/LICENSE deleted file mode 100644 index 24384c0..0000000 --- a/third_part/GFPGAN/LICENSE +++ /dev/null @@ -1,351 +0,0 @@ -Tencent is pleased to support the open source community by making GFPGAN available. - -Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. - -GFPGAN is licensed under the Apache License Version 2.0 except for the third-party components listed below. - - -Terms of the Apache License Version 2.0: ---------------------------------------------- -Apache License - -Version 2.0, January 2004 - -http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION -1. Definitions. - -“License” shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. - -“Licensor” shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. - -“Legal Entity” shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, “control” means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. - -“You” (or “Your”) shall mean an individual or Legal Entity exercising permissions granted by this License. - -“Source” form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. - -“Object” form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. - -“Work” shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). - -“Derivative Works” shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. - -“Contribution” shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, “submitted” means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as “Not a Contribution.” - -“Contributor” shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: - -You must give any other recipients of the Work or Derivative Works a copy of this License; and - -You must cause any modified files to carry prominent notices stating that You changed the files; and - -You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and - -If the Work includes a “NOTICE” text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. - -You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - - - -Other dependencies and licenses: - - -Open Source Software licensed under the Apache 2.0 license and Other Licenses of the Third-Party Components therein: ---------------------------------------------- -1. basicsr -Copyright 2018-2020 BasicSR Authors - - -This BasicSR project is released under the Apache 2.0 license. - -A copy of Apache 2.0 is included in this file. - -StyleGAN2 -The codes are modified from the repository stylegan2-pytorch. Many thanks to the author - Kim Seonghyeon 😊 for translating from the official TensorFlow codes to PyTorch ones. Here is the license of stylegan2-pytorch. -The official repository is https://github.com/NVlabs/stylegan2, and here is the NVIDIA license. -DFDNet -The codes are largely modified from the repository DFDNet. Their license is Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. - -Terms of the Nvidia License: ---------------------------------------------- - -1. Definitions - -"Licensor" means any person or entity that distributes its Work. - -"Software" means the original work of authorship made available under -this License. - -"Work" means the Software and any additions to or derivative works of -the Software that are made available under this License. - -"Nvidia Processors" means any central processing unit (CPU), graphics -processing unit (GPU), field-programmable gate array (FPGA), -application-specific integrated circuit (ASIC) or any combination -thereof designed, made, sold, or provided by Nvidia or its affiliates. - -The terms "reproduce," "reproduction," "derivative works," and -"distribution" have the meaning as provided under U.S. copyright law; -provided, however, that for the purposes of this License, derivative -works shall not include works that remain separable from, or merely -link (or bind by name) to the interfaces of, the Work. - -Works, including the Software, are "made available" under this License -by including in or with the Work either (a) a copyright notice -referencing the applicability of this License to the Work, or (b) a -copy of this License. - -2. License Grants - - 2.1 Copyright Grant. Subject to the terms and conditions of this - License, each Licensor grants to you a perpetual, worldwide, - non-exclusive, royalty-free, copyright license to reproduce, - prepare derivative works of, publicly display, publicly perform, - sublicense and distribute its Work and any resulting derivative - works in any form. - -3. Limitations - - 3.1 Redistribution. You may reproduce or distribute the Work only - if (a) you do so under this License, (b) you include a complete - copy of this License with your distribution, and (c) you retain - without modification any copyright, patent, trademark, or - attribution notices that are present in the Work. - - 3.2 Derivative Works. You may specify that additional or different - terms apply to the use, reproduction, and distribution of your - derivative works of the Work ("Your Terms") only if (a) Your Terms - provide that the use limitation in Section 3.3 applies to your - derivative works, and (b) you identify the specific derivative - works that are subject to Your Terms. Notwithstanding Your Terms, - this License (including the redistribution requirements in Section - 3.1) will continue to apply to the Work itself. - - 3.3 Use Limitation. The Work and any derivative works thereof only - may be used or intended for use non-commercially. The Work or - derivative works thereof may be used or intended for use by Nvidia - or its affiliates commercially or non-commercially. As used herein, - "non-commercially" means for research or evaluation purposes only. - - 3.4 Patent Claims. If you bring or threaten to bring a patent claim - against any Licensor (including any claim, cross-claim or - counterclaim in a lawsuit) to enforce any patents that you allege - are infringed by any Work, then your rights under this License from - such Licensor (including the grants in Sections 2.1 and 2.2) will - terminate immediately. - - 3.5 Trademarks. This License does not grant any rights to use any - Licensor's or its affiliates' names, logos, or trademarks, except - as necessary to reproduce the notices described in this License. - - 3.6 Termination. If you violate any term of this License, then your - rights under this License (including the grants in Sections 2.1 and - 2.2) will terminate immediately. - -4. Disclaimer of Warranty. - -THE WORK IS PROVIDED "AS IS" WITHOUT WARRANTIES OR CONDITIONS OF ANY -KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WARRANTIES OR CONDITIONS OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE OR -NON-INFRINGEMENT. YOU BEAR THE RISK OF UNDERTAKING ANY ACTIVITIES UNDER -THIS LICENSE. - -5. Limitation of Liability. - -EXCEPT AS PROHIBITED BY APPLICABLE LAW, IN NO EVENT AND UNDER NO LEGAL -THEORY, WHETHER IN TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE -SHALL ANY LICENSOR BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY DIRECT, -INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF -OR RELATED TO THIS LICENSE, THE USE OR INABILITY TO USE THE WORK -(INCLUDING BUT NOT LIMITED TO LOSS OF GOODWILL, BUSINESS INTERRUPTION, -LOST PROFITS OR DATA, COMPUTER FAILURE OR MALFUNCTION, OR ANY OTHER -COMMERCIAL DAMAGES OR LOSSES), EVEN IF THE LICENSOR HAS BEEN ADVISED OF -THE POSSIBILITY OF SUCH DAMAGES. - -MIT License - -Copyright (c) 2019 Kim Seonghyeon - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - - -Open Source Software licensed under the BSD 3-Clause license: ---------------------------------------------- -1. torchvision -Copyright (c) Soumith Chintala 2016, -All rights reserved. - -2. torch -Copyright (c) 2016- Facebook, Inc (Adam Paszke) -Copyright (c) 2014- Facebook, Inc (Soumith Chintala) -Copyright (c) 2011-2014 Idiap Research Institute (Ronan Collobert) -Copyright (c) 2012-2014 Deepmind Technologies (Koray Kavukcuoglu) -Copyright (c) 2011-2012 NEC Laboratories America (Koray Kavukcuoglu) -Copyright (c) 2011-2013 NYU (Clement Farabet) -Copyright (c) 2006-2010 NEC Laboratories America (Ronan Collobert, Leon Bottou, Iain Melvin, Jason Weston) -Copyright (c) 2006 Idiap Research Institute (Samy Bengio) -Copyright (c) 2001-2004 Idiap Research Institute (Ronan Collobert, Samy Bengio, Johnny Mariethoz) - - -Terms of the BSD 3-Clause License: ---------------------------------------------- -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - - -Open Source Software licensed under the BSD 3-Clause License and Other Licenses of the Third-Party Components therein: ---------------------------------------------- -1. numpy -Copyright (c) 2005-2020, NumPy Developers. -All rights reserved. - -A copy of BSD 3-Clause License is included in this file. - -The NumPy repository and source distributions bundle several libraries that are -compatibly licensed. We list these here. - -Name: Numpydoc -Files: doc/sphinxext/numpydoc/* -License: BSD-2-Clause - For details, see doc/sphinxext/LICENSE.txt - -Name: scipy-sphinx-theme -Files: doc/scipy-sphinx-theme/* -License: BSD-3-Clause AND PSF-2.0 AND Apache-2.0 - For details, see doc/scipy-sphinx-theme/LICENSE.txt - -Name: lapack-lite -Files: numpy/linalg/lapack_lite/* -License: BSD-3-Clause - For details, see numpy/linalg/lapack_lite/LICENSE.txt - -Name: tempita -Files: tools/npy_tempita/* -License: MIT - For details, see tools/npy_tempita/license.txt - -Name: dragon4 -Files: numpy/core/src/multiarray/dragon4.c -License: MIT - For license text, see numpy/core/src/multiarray/dragon4.c - - - -Open Source Software licensed under the MIT license: ---------------------------------------------- -1. facexlib -Copyright (c) 2020 Xintao Wang - -2. opencv-python -Copyright (c) Olli-Pekka Heinisuo -Please note that only files in cv2 package are used. - - -Terms of the MIT License: ---------------------------------------------- -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - - -Open Source Software licensed under the MIT license and Other Licenses of the Third-Party Components therein: ---------------------------------------------- -1. tqdm -Copyright (c) 2013 noamraph - -`tqdm` is a product of collaborative work. -Unless otherwise stated, all authors (see commit logs) retain copyright -for their respective work, and release the work under the MIT licence -(text below). - -Exceptions or notable authors are listed below -in reverse chronological order: - -* files: * - MPLv2.0 2015-2020 (c) Casper da Costa-Luis - [casperdcl](https://github.com/casperdcl). -* files: tqdm/_tqdm.py - MIT 2016 (c) [PR #96] on behalf of Google Inc. -* files: tqdm/_tqdm.py setup.py README.rst MANIFEST.in .gitignore - MIT 2013 (c) Noam Yorav-Raphael, original author. - -[PR #96]: https://github.com/tqdm/tqdm/pull/96 - - -Mozilla Public Licence (MPL) v. 2.0 - Exhibit A ------------------------------------------------ - -This Source Code Form is subject to the terms of the -Mozilla Public License, v. 2.0. -If a copy of the MPL was not distributed with this file, -You can obtain one at https://mozilla.org/MPL/2.0/. - - -MIT License (MIT) ------------------ - -Copyright (c) 2013 noamraph - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/third_part/GFPGAN/gfpgan/__init__.py b/third_part/GFPGAN/gfpgan/__init__.py deleted file mode 100644 index a1d4dcf..0000000 --- a/third_part/GFPGAN/gfpgan/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -# flake8: noqa -import sys -sys.path.append('third_part/GFPGAN') - -from .archs import * -from .data import * -from .models import * -from .utils import * - -# from .version import * diff --git a/third_part/GFPGAN/gfpgan/archs/__init__.py b/third_part/GFPGAN/gfpgan/archs/__init__.py deleted file mode 100644 index 5ec5367..0000000 --- a/third_part/GFPGAN/gfpgan/archs/__init__.py +++ /dev/null @@ -1,51 +0,0 @@ -import importlib -import os -from os import path as osp - -def scandir(dir_path, suffix=None, recursive=False, full_path=False): - """Scan a directory to find the interested files. - - Args: - dir_path (str): Path of the directory. - suffix (str | tuple(str), optional): File suffix that we are - interested in. Default: None. - recursive (bool, optional): If set to True, recursively scan the - directory. Default: False. - full_path (bool, optional): If set to True, include the dir_path. - Default: False. - - Returns: - A generator for all the interested files with relative paths. - """ - - if (suffix is not None) and not isinstance(suffix, (str, tuple)): - raise TypeError('"suffix" must be a string or tuple of strings') - - root = dir_path - - def _scandir(dir_path, suffix, recursive): - for entry in os.scandir(dir_path): - if not entry.name.startswith('.') and entry.is_file(): - if full_path: - return_path = entry.path - else: - return_path = osp.relpath(entry.path, root) - - if suffix is None: - yield return_path - elif return_path.endswith(suffix): - yield return_path - else: - if recursive: - yield from _scandir(entry.path, suffix=suffix, recursive=recursive) - else: - continue - - return _scandir(dir_path, suffix=suffix, recursive=recursive) - -# automatically scan and import arch modules for registry -# scan all the files that end with '_arch.py' under the archs folder -arch_folder = osp.dirname(osp.abspath(__file__)) -arch_filenames = [osp.splitext(osp.basename(v))[0] for v in scandir(arch_folder) if v.endswith('_arch.py')] -# import all the arch modules -_arch_modules = [importlib.import_module(f'custom_nodes.ComfyUI_Lam.third_part.GFPGAN.gfpgan.archs.{file_name}') for file_name in arch_filenames] diff --git a/third_part/GFPGAN/gfpgan/archs/arcface_arch.py b/third_part/GFPGAN/gfpgan/archs/arcface_arch.py deleted file mode 100644 index e6d3bd9..0000000 --- a/third_part/GFPGAN/gfpgan/archs/arcface_arch.py +++ /dev/null @@ -1,245 +0,0 @@ -import torch.nn as nn -from basicsr.utils.registry import ARCH_REGISTRY - - -def conv3x3(inplanes, outplanes, stride=1): - """A simple wrapper for 3x3 convolution with padding. - - Args: - inplanes (int): Channel number of inputs. - outplanes (int): Channel number of outputs. - stride (int): Stride in convolution. Default: 1. - """ - return nn.Conv2d(inplanes, outplanes, kernel_size=3, stride=stride, padding=1, bias=False) - - -class BasicBlock(nn.Module): - """Basic residual block used in the ResNetArcFace architecture. - - Args: - inplanes (int): Channel number of inputs. - planes (int): Channel number of outputs. - stride (int): Stride in convolution. Default: 1. - downsample (nn.Module): The downsample module. Default: None. - """ - expansion = 1 # output channel expansion ratio - - def __init__(self, inplanes, planes, stride=1, downsample=None): - super(BasicBlock, self).__init__() - self.conv1 = conv3x3(inplanes, planes, stride) - self.bn1 = nn.BatchNorm2d(planes) - self.relu = nn.ReLU(inplace=True) - self.conv2 = conv3x3(planes, planes) - self.bn2 = nn.BatchNorm2d(planes) - self.downsample = downsample - self.stride = stride - - def forward(self, x): - residual = x - - out = self.conv1(x) - out = self.bn1(out) - out = self.relu(out) - - out = self.conv2(out) - out = self.bn2(out) - - if self.downsample is not None: - residual = self.downsample(x) - - out += residual - out = self.relu(out) - - return out - - -class IRBlock(nn.Module): - """Improved residual block (IR Block) used in the ResNetArcFace architecture. - - Args: - inplanes (int): Channel number of inputs. - planes (int): Channel number of outputs. - stride (int): Stride in convolution. Default: 1. - downsample (nn.Module): The downsample module. Default: None. - use_se (bool): Whether use the SEBlock (squeeze and excitation block). Default: True. - """ - expansion = 1 # output channel expansion ratio - - def __init__(self, inplanes, planes, stride=1, downsample=None, use_se=True): - super(IRBlock, self).__init__() - self.bn0 = nn.BatchNorm2d(inplanes) - self.conv1 = conv3x3(inplanes, inplanes) - self.bn1 = nn.BatchNorm2d(inplanes) - self.prelu = nn.PReLU() - self.conv2 = conv3x3(inplanes, planes, stride) - self.bn2 = nn.BatchNorm2d(planes) - self.downsample = downsample - self.stride = stride - self.use_se = use_se - if self.use_se: - self.se = SEBlock(planes) - - def forward(self, x): - residual = x - out = self.bn0(x) - out = self.conv1(out) - out = self.bn1(out) - out = self.prelu(out) - - out = self.conv2(out) - out = self.bn2(out) - if self.use_se: - out = self.se(out) - - if self.downsample is not None: - residual = self.downsample(x) - - out += residual - out = self.prelu(out) - - return out - - -class Bottleneck(nn.Module): - """Bottleneck block used in the ResNetArcFace architecture. - - Args: - inplanes (int): Channel number of inputs. - planes (int): Channel number of outputs. - stride (int): Stride in convolution. Default: 1. - downsample (nn.Module): The downsample module. Default: None. - """ - expansion = 4 # output channel expansion ratio - - def __init__(self, inplanes, planes, stride=1, downsample=None): - super(Bottleneck, self).__init__() - self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, bias=False) - self.bn1 = nn.BatchNorm2d(planes) - self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride, padding=1, bias=False) - self.bn2 = nn.BatchNorm2d(planes) - self.conv3 = nn.Conv2d(planes, planes * self.expansion, kernel_size=1, bias=False) - self.bn3 = nn.BatchNorm2d(planes * self.expansion) - self.relu = nn.ReLU(inplace=True) - self.downsample = downsample - self.stride = stride - - def forward(self, x): - residual = x - - out = self.conv1(x) - out = self.bn1(out) - out = self.relu(out) - - out = self.conv2(out) - out = self.bn2(out) - out = self.relu(out) - - out = self.conv3(out) - out = self.bn3(out) - - if self.downsample is not None: - residual = self.downsample(x) - - out += residual - out = self.relu(out) - - return out - - -class SEBlock(nn.Module): - """The squeeze-and-excitation block (SEBlock) used in the IRBlock. - - Args: - channel (int): Channel number of inputs. - reduction (int): Channel reduction ration. Default: 16. - """ - - def __init__(self, channel, reduction=16): - super(SEBlock, self).__init__() - self.avg_pool = nn.AdaptiveAvgPool2d(1) # pool to 1x1 without spatial information - self.fc = nn.Sequential( - nn.Linear(channel, channel // reduction), nn.PReLU(), nn.Linear(channel // reduction, channel), - nn.Sigmoid()) - - def forward(self, x): - b, c, _, _ = x.size() - y = self.avg_pool(x).view(b, c) - y = self.fc(y).view(b, c, 1, 1) - return x * y - - -@ARCH_REGISTRY.register() -class ResNetArcFace(nn.Module): - """ArcFace with ResNet architectures. - - Ref: ArcFace: Additive Angular Margin Loss for Deep Face Recognition. - - Args: - block (str): Block used in the ArcFace architecture. - layers (tuple(int)): Block numbers in each layer. - use_se (bool): Whether use the SEBlock (squeeze and excitation block). Default: True. - """ - - def __init__(self, block, layers, use_se=True): - if block == 'IRBlock': - block = IRBlock - self.inplanes = 64 - self.use_se = use_se - super(ResNetArcFace, self).__init__() - - self.conv1 = nn.Conv2d(1, 64, kernel_size=3, padding=1, bias=False) - self.bn1 = nn.BatchNorm2d(64) - self.prelu = nn.PReLU() - self.maxpool = nn.MaxPool2d(kernel_size=2, stride=2) - self.layer1 = self._make_layer(block, 64, layers[0]) - self.layer2 = self._make_layer(block, 128, layers[1], stride=2) - self.layer3 = self._make_layer(block, 256, layers[2], stride=2) - self.layer4 = self._make_layer(block, 512, layers[3], stride=2) - self.bn4 = nn.BatchNorm2d(512) - self.dropout = nn.Dropout() - self.fc5 = nn.Linear(512 * 8 * 8, 512) - self.bn5 = nn.BatchNorm1d(512) - - # initialization - for m in self.modules(): - if isinstance(m, nn.Conv2d): - nn.init.xavier_normal_(m.weight) - elif isinstance(m, nn.BatchNorm2d) or isinstance(m, nn.BatchNorm1d): - nn.init.constant_(m.weight, 1) - nn.init.constant_(m.bias, 0) - elif isinstance(m, nn.Linear): - nn.init.xavier_normal_(m.weight) - nn.init.constant_(m.bias, 0) - - def _make_layer(self, block, planes, num_blocks, stride=1): - downsample = None - if stride != 1 or self.inplanes != planes * block.expansion: - downsample = nn.Sequential( - nn.Conv2d(self.inplanes, planes * block.expansion, kernel_size=1, stride=stride, bias=False), - nn.BatchNorm2d(planes * block.expansion), - ) - layers = [] - layers.append(block(self.inplanes, planes, stride, downsample, use_se=self.use_se)) - self.inplanes = planes - for _ in range(1, num_blocks): - layers.append(block(self.inplanes, planes, use_se=self.use_se)) - - return nn.Sequential(*layers) - - def forward(self, x): - x = self.conv1(x) - x = self.bn1(x) - x = self.prelu(x) - x = self.maxpool(x) - - x = self.layer1(x) - x = self.layer2(x) - x = self.layer3(x) - x = self.layer4(x) - x = self.bn4(x) - x = self.dropout(x) - x = x.view(x.size(0), -1) - x = self.fc5(x) - x = self.bn5(x) - - return x diff --git a/third_part/GFPGAN/gfpgan/archs/gfpgan_bilinear_arch.py b/third_part/GFPGAN/gfpgan/archs/gfpgan_bilinear_arch.py deleted file mode 100644 index 52e0de8..0000000 --- a/third_part/GFPGAN/gfpgan/archs/gfpgan_bilinear_arch.py +++ /dev/null @@ -1,312 +0,0 @@ -import math -import random -import torch -from basicsr.utils.registry import ARCH_REGISTRY -from torch import nn - -from .gfpganv1_arch import ResUpBlock -from .stylegan2_bilinear_arch import (ConvLayer, EqualConv2d, EqualLinear, ResBlock, ScaledLeakyReLU, - StyleGAN2GeneratorBilinear) - - -class StyleGAN2GeneratorBilinearSFT(StyleGAN2GeneratorBilinear): - """StyleGAN2 Generator with SFT modulation (Spatial Feature Transform). - - It is the bilinear version. It does not use the complicated UpFirDnSmooth function that is not friendly for - deployment. It can be easily converted to the clean version: StyleGAN2GeneratorCSFT. - - Args: - out_size (int): The spatial size of outputs. - num_style_feat (int): Channel number of style features. Default: 512. - num_mlp (int): Layer number of MLP style layers. Default: 8. - channel_multiplier (int): Channel multiplier for large networks of StyleGAN2. Default: 2. - lr_mlp (float): Learning rate multiplier for mlp layers. Default: 0.01. - narrow (float): The narrow ratio for channels. Default: 1. - sft_half (bool): Whether to apply SFT on half of the input channels. Default: False. - """ - - def __init__(self, - out_size, - num_style_feat=512, - num_mlp=8, - channel_multiplier=2, - lr_mlp=0.01, - narrow=1, - sft_half=False): - super(StyleGAN2GeneratorBilinearSFT, self).__init__( - out_size, - num_style_feat=num_style_feat, - num_mlp=num_mlp, - channel_multiplier=channel_multiplier, - lr_mlp=lr_mlp, - narrow=narrow) - self.sft_half = sft_half - - def forward(self, - styles, - conditions, - input_is_latent=False, - noise=None, - randomize_noise=True, - truncation=1, - truncation_latent=None, - inject_index=None, - return_latents=False): - """Forward function for StyleGAN2GeneratorBilinearSFT. - - Args: - styles (list[Tensor]): Sample codes of styles. - conditions (list[Tensor]): SFT conditions to generators. - input_is_latent (bool): Whether input is latent style. Default: False. - noise (Tensor | None): Input noise or None. Default: None. - randomize_noise (bool): Randomize noise, used when 'noise' is False. Default: True. - truncation (float): The truncation ratio. Default: 1. - truncation_latent (Tensor | None): The truncation latent tensor. Default: None. - inject_index (int | None): The injection index for mixing noise. Default: None. - return_latents (bool): Whether to return style latents. Default: False. - """ - # style codes -> latents with Style MLP layer - if not input_is_latent: - styles = [self.style_mlp(s) for s in styles] - # noises - if noise is None: - if randomize_noise: - noise = [None] * self.num_layers # for each style conv layer - else: # use the stored noise - noise = [getattr(self.noises, f'noise{i}') for i in range(self.num_layers)] - # style truncation - if truncation < 1: - style_truncation = [] - for style in styles: - style_truncation.append(truncation_latent + truncation * (style - truncation_latent)) - styles = style_truncation - # get style latents with injection - if len(styles) == 1: - inject_index = self.num_latent - - if styles[0].ndim < 3: - # repeat latent code for all the layers - latent = styles[0].unsqueeze(1).repeat(1, inject_index, 1) - else: # used for encoder with different latent code for each layer - latent = styles[0] - elif len(styles) == 2: # mixing noises - if inject_index is None: - inject_index = random.randint(1, self.num_latent - 1) - latent1 = styles[0].unsqueeze(1).repeat(1, inject_index, 1) - latent2 = styles[1].unsqueeze(1).repeat(1, self.num_latent - inject_index, 1) - latent = torch.cat([latent1, latent2], 1) - - # main generation - out = self.constant_input(latent.shape[0]) - out = self.style_conv1(out, latent[:, 0], noise=noise[0]) - skip = self.to_rgb1(out, latent[:, 1]) - - i = 1 - for conv1, conv2, noise1, noise2, to_rgb in zip(self.style_convs[::2], self.style_convs[1::2], noise[1::2], - noise[2::2], self.to_rgbs): - out = conv1(out, latent[:, i], noise=noise1) - - # the conditions may have fewer levels - if i < len(conditions): - # SFT part to combine the conditions - if self.sft_half: # only apply SFT to half of the channels - out_same, out_sft = torch.split(out, int(out.size(1) // 2), dim=1) - out_sft = out_sft * conditions[i - 1] + conditions[i] - out = torch.cat([out_same, out_sft], dim=1) - else: # apply SFT to all the channels - out = out * conditions[i - 1] + conditions[i] - - out = conv2(out, latent[:, i + 1], noise=noise2) - skip = to_rgb(out, latent[:, i + 2], skip) # feature back to the rgb space - i += 2 - - image = skip - - if return_latents: - return image, latent - else: - return image, None - - -@ARCH_REGISTRY.register() -class GFPGANBilinear(nn.Module): - """The GFPGAN architecture: Unet + StyleGAN2 decoder with SFT. - - It is the bilinear version and it does not use the complicated UpFirDnSmooth function that is not friendly for - deployment. It can be easily converted to the clean version: GFPGANv1Clean. - - - Ref: GFP-GAN: Towards Real-World Blind Face Restoration with Generative Facial Prior. - - Args: - out_size (int): The spatial size of outputs. - num_style_feat (int): Channel number of style features. Default: 512. - channel_multiplier (int): Channel multiplier for large networks of StyleGAN2. Default: 2. - decoder_load_path (str): The path to the pre-trained decoder model (usually, the StyleGAN2). Default: None. - fix_decoder (bool): Whether to fix the decoder. Default: True. - - num_mlp (int): Layer number of MLP style layers. Default: 8. - lr_mlp (float): Learning rate multiplier for mlp layers. Default: 0.01. - input_is_latent (bool): Whether input is latent style. Default: False. - different_w (bool): Whether to use different latent w for different layers. Default: False. - narrow (float): The narrow ratio for channels. Default: 1. - sft_half (bool): Whether to apply SFT on half of the input channels. Default: False. - """ - - def __init__( - self, - out_size, - num_style_feat=512, - channel_multiplier=1, - decoder_load_path=None, - fix_decoder=True, - # for stylegan decoder - num_mlp=8, - lr_mlp=0.01, - input_is_latent=False, - different_w=False, - narrow=1, - sft_half=False): - - super(GFPGANBilinear, self).__init__() - self.input_is_latent = input_is_latent - self.different_w = different_w - self.num_style_feat = num_style_feat - - unet_narrow = narrow * 0.5 # by default, use a half of input channels - channels = { - '4': int(512 * unet_narrow), - '8': int(512 * unet_narrow), - '16': int(512 * unet_narrow), - '32': int(512 * unet_narrow), - '64': int(256 * channel_multiplier * unet_narrow), - '128': int(128 * channel_multiplier * unet_narrow), - '256': int(64 * channel_multiplier * unet_narrow), - '512': int(32 * channel_multiplier * unet_narrow), - '1024': int(16 * channel_multiplier * unet_narrow) - } - - self.log_size = int(math.log(out_size, 2)) - first_out_size = 2**(int(math.log(out_size, 2))) - - self.conv_body_first = ConvLayer(3, channels[f'{first_out_size}'], 1, bias=True, activate=True) - - # downsample - in_channels = channels[f'{first_out_size}'] - self.conv_body_down = nn.ModuleList() - for i in range(self.log_size, 2, -1): - out_channels = channels[f'{2**(i - 1)}'] - self.conv_body_down.append(ResBlock(in_channels, out_channels)) - in_channels = out_channels - - self.final_conv = ConvLayer(in_channels, channels['4'], 3, bias=True, activate=True) - - # upsample - in_channels = channels['4'] - self.conv_body_up = nn.ModuleList() - for i in range(3, self.log_size + 1): - out_channels = channels[f'{2**i}'] - self.conv_body_up.append(ResUpBlock(in_channels, out_channels)) - in_channels = out_channels - - # to RGB - self.toRGB = nn.ModuleList() - for i in range(3, self.log_size + 1): - self.toRGB.append(EqualConv2d(channels[f'{2**i}'], 3, 1, stride=1, padding=0, bias=True, bias_init_val=0)) - - if different_w: - linear_out_channel = (int(math.log(out_size, 2)) * 2 - 2) * num_style_feat - else: - linear_out_channel = num_style_feat - - self.final_linear = EqualLinear( - channels['4'] * 4 * 4, linear_out_channel, bias=True, bias_init_val=0, lr_mul=1, activation=None) - - # the decoder: stylegan2 generator with SFT modulations - self.stylegan_decoder = StyleGAN2GeneratorBilinearSFT( - out_size=out_size, - num_style_feat=num_style_feat, - num_mlp=num_mlp, - channel_multiplier=channel_multiplier, - lr_mlp=lr_mlp, - narrow=narrow, - sft_half=sft_half) - - # load pre-trained stylegan2 model if necessary - if decoder_load_path: - self.stylegan_decoder.load_state_dict( - torch.load(decoder_load_path, map_location=lambda storage, loc: storage)['params_ema']) - # fix decoder without updating params - if fix_decoder: - for _, param in self.stylegan_decoder.named_parameters(): - param.requires_grad = False - - # for SFT modulations (scale and shift) - self.condition_scale = nn.ModuleList() - self.condition_shift = nn.ModuleList() - for i in range(3, self.log_size + 1): - out_channels = channels[f'{2**i}'] - if sft_half: - sft_out_channels = out_channels - else: - sft_out_channels = out_channels * 2 - self.condition_scale.append( - nn.Sequential( - EqualConv2d(out_channels, out_channels, 3, stride=1, padding=1, bias=True, bias_init_val=0), - ScaledLeakyReLU(0.2), - EqualConv2d(out_channels, sft_out_channels, 3, stride=1, padding=1, bias=True, bias_init_val=1))) - self.condition_shift.append( - nn.Sequential( - EqualConv2d(out_channels, out_channels, 3, stride=1, padding=1, bias=True, bias_init_val=0), - ScaledLeakyReLU(0.2), - EqualConv2d(out_channels, sft_out_channels, 3, stride=1, padding=1, bias=True, bias_init_val=0))) - - def forward(self, x, return_latents=False, return_rgb=True, randomize_noise=True): - """Forward function for GFPGANBilinear. - - Args: - x (Tensor): Input images. - return_latents (bool): Whether to return style latents. Default: False. - return_rgb (bool): Whether return intermediate rgb images. Default: True. - randomize_noise (bool): Randomize noise, used when 'noise' is False. Default: True. - """ - conditions = [] - unet_skips = [] - out_rgbs = [] - - # encoder - feat = self.conv_body_first(x) - for i in range(self.log_size - 2): - feat = self.conv_body_down[i](feat) - unet_skips.insert(0, feat) - - feat = self.final_conv(feat) - - # style code - style_code = self.final_linear(feat.view(feat.size(0), -1)) - if self.different_w: - style_code = style_code.view(style_code.size(0), -1, self.num_style_feat) - - # decode - for i in range(self.log_size - 2): - # add unet skip - feat = feat + unet_skips[i] - # ResUpLayer - feat = self.conv_body_up[i](feat) - # generate scale and shift for SFT layers - scale = self.condition_scale[i](feat) - conditions.append(scale.clone()) - shift = self.condition_shift[i](feat) - conditions.append(shift.clone()) - # generate rgb images - if return_rgb: - out_rgbs.append(self.toRGB[i](feat)) - - # decoder - image, _ = self.stylegan_decoder([style_code], - conditions, - return_latents=return_latents, - input_is_latent=self.input_is_latent, - randomize_noise=randomize_noise) - - return image, out_rgbs diff --git a/third_part/GFPGAN/gfpgan/archs/gfpganv1_arch.py b/third_part/GFPGAN/gfpgan/archs/gfpganv1_arch.py deleted file mode 100644 index e092b4f..0000000 --- a/third_part/GFPGAN/gfpgan/archs/gfpganv1_arch.py +++ /dev/null @@ -1,439 +0,0 @@ -import math -import random -import torch -from basicsr.archs.stylegan2_arch import (ConvLayer, EqualConv2d, EqualLinear, ResBlock, ScaledLeakyReLU, - StyleGAN2Generator) -from basicsr.ops.fused_act import FusedLeakyReLU -from basicsr.utils.registry import ARCH_REGISTRY -from torch import nn -from torch.nn import functional as F - - -class StyleGAN2GeneratorSFT(StyleGAN2Generator): - """StyleGAN2 Generator with SFT modulation (Spatial Feature Transform). - - Args: - out_size (int): The spatial size of outputs. - num_style_feat (int): Channel number of style features. Default: 512. - num_mlp (int): Layer number of MLP style layers. Default: 8. - channel_multiplier (int): Channel multiplier for large networks of StyleGAN2. Default: 2. - resample_kernel (list[int]): A list indicating the 1D resample kernel magnitude. A cross production will be - applied to extent 1D resample kernel to 2D resample kernel. Default: (1, 3, 3, 1). - lr_mlp (float): Learning rate multiplier for mlp layers. Default: 0.01. - narrow (float): The narrow ratio for channels. Default: 1. - sft_half (bool): Whether to apply SFT on half of the input channels. Default: False. - """ - - def __init__(self, - out_size, - num_style_feat=512, - num_mlp=8, - channel_multiplier=2, - resample_kernel=(1, 3, 3, 1), - lr_mlp=0.01, - narrow=1, - sft_half=False): - super(StyleGAN2GeneratorSFT, self).__init__( - out_size, - num_style_feat=num_style_feat, - num_mlp=num_mlp, - channel_multiplier=channel_multiplier, - resample_kernel=resample_kernel, - lr_mlp=lr_mlp, - narrow=narrow) - self.sft_half = sft_half - - def forward(self, - styles, - conditions, - input_is_latent=False, - noise=None, - randomize_noise=True, - truncation=1, - truncation_latent=None, - inject_index=None, - return_latents=False): - """Forward function for StyleGAN2GeneratorSFT. - - Args: - styles (list[Tensor]): Sample codes of styles. - conditions (list[Tensor]): SFT conditions to generators. - input_is_latent (bool): Whether input is latent style. Default: False. - noise (Tensor | None): Input noise or None. Default: None. - randomize_noise (bool): Randomize noise, used when 'noise' is False. Default: True. - truncation (float): The truncation ratio. Default: 1. - truncation_latent (Tensor | None): The truncation latent tensor. Default: None. - inject_index (int | None): The injection index for mixing noise. Default: None. - return_latents (bool): Whether to return style latents. Default: False. - """ - # style codes -> latents with Style MLP layer - if not input_is_latent: - styles = [self.style_mlp(s) for s in styles] - # noises - if noise is None: - if randomize_noise: - noise = [None] * self.num_layers # for each style conv layer - else: # use the stored noise - noise = [getattr(self.noises, f'noise{i}') for i in range(self.num_layers)] - # style truncation - if truncation < 1: - style_truncation = [] - for style in styles: - style_truncation.append(truncation_latent + truncation * (style - truncation_latent)) - styles = style_truncation - # get style latents with injection - if len(styles) == 1: - inject_index = self.num_latent - - if styles[0].ndim < 3: - # repeat latent code for all the layers - latent = styles[0].unsqueeze(1).repeat(1, inject_index, 1) - else: # used for encoder with different latent code for each layer - latent = styles[0] - elif len(styles) == 2: # mixing noises - if inject_index is None: - inject_index = random.randint(1, self.num_latent - 1) - latent1 = styles[0].unsqueeze(1).repeat(1, inject_index, 1) - latent2 = styles[1].unsqueeze(1).repeat(1, self.num_latent - inject_index, 1) - latent = torch.cat([latent1, latent2], 1) - - # main generation - out = self.constant_input(latent.shape[0]) - out = self.style_conv1(out, latent[:, 0], noise=noise[0]) - skip = self.to_rgb1(out, latent[:, 1]) - - i = 1 - for conv1, conv2, noise1, noise2, to_rgb in zip(self.style_convs[::2], self.style_convs[1::2], noise[1::2], - noise[2::2], self.to_rgbs): - out = conv1(out, latent[:, i], noise=noise1) - - # the conditions may have fewer levels - if i < len(conditions): - # SFT part to combine the conditions - if self.sft_half: # only apply SFT to half of the channels - out_same, out_sft = torch.split(out, int(out.size(1) // 2), dim=1) - out_sft = out_sft * conditions[i - 1] + conditions[i] - out = torch.cat([out_same, out_sft], dim=1) - else: # apply SFT to all the channels - out = out * conditions[i - 1] + conditions[i] - - out = conv2(out, latent[:, i + 1], noise=noise2) - skip = to_rgb(out, latent[:, i + 2], skip) # feature back to the rgb space - i += 2 - - image = skip - - if return_latents: - return image, latent - else: - return image, None - - -class ConvUpLayer(nn.Module): - """Convolutional upsampling layer. It uses bilinear upsampler + Conv. - - Args: - in_channels (int): Channel number of the input. - out_channels (int): Channel number of the output. - kernel_size (int): Size of the convolving kernel. - stride (int): Stride of the convolution. Default: 1 - padding (int): Zero-padding added to both sides of the input. Default: 0. - bias (bool): If ``True``, adds a learnable bias to the output. Default: ``True``. - bias_init_val (float): Bias initialized value. Default: 0. - activate (bool): Whether use activateion. Default: True. - """ - - def __init__(self, - in_channels, - out_channels, - kernel_size, - stride=1, - padding=0, - bias=True, - bias_init_val=0, - activate=True): - super(ConvUpLayer, self).__init__() - self.in_channels = in_channels - self.out_channels = out_channels - self.kernel_size = kernel_size - self.stride = stride - self.padding = padding - # self.scale is used to scale the convolution weights, which is related to the common initializations. - self.scale = 1 / math.sqrt(in_channels * kernel_size**2) - - self.weight = nn.Parameter(torch.randn(out_channels, in_channels, kernel_size, kernel_size)) - - if bias and not activate: - self.bias = nn.Parameter(torch.zeros(out_channels).fill_(bias_init_val)) - else: - self.register_parameter('bias', None) - - # activation - if activate: - if bias: - self.activation = FusedLeakyReLU(out_channels) - else: - self.activation = ScaledLeakyReLU(0.2) - else: - self.activation = None - - def forward(self, x): - # bilinear upsample - out = F.interpolate(x, scale_factor=2, mode='bilinear', align_corners=False) - # conv - out = F.conv2d( - out, - self.weight * self.scale, - bias=self.bias, - stride=self.stride, - padding=self.padding, - ) - # activation - if self.activation is not None: - out = self.activation(out) - return out - - -class ResUpBlock(nn.Module): - """Residual block with upsampling. - - Args: - in_channels (int): Channel number of the input. - out_channels (int): Channel number of the output. - """ - - def __init__(self, in_channels, out_channels): - super(ResUpBlock, self).__init__() - - self.conv1 = ConvLayer(in_channels, in_channels, 3, bias=True, activate=True) - self.conv2 = ConvUpLayer(in_channels, out_channels, 3, stride=1, padding=1, bias=True, activate=True) - self.skip = ConvUpLayer(in_channels, out_channels, 1, bias=False, activate=False) - - def forward(self, x): - out = self.conv1(x) - out = self.conv2(out) - skip = self.skip(x) - out = (out + skip) / math.sqrt(2) - return out - - -@ARCH_REGISTRY.register() -class GFPGANv1(nn.Module): - """The GFPGAN architecture: Unet + StyleGAN2 decoder with SFT. - - Ref: GFP-GAN: Towards Real-World Blind Face Restoration with Generative Facial Prior. - - Args: - out_size (int): The spatial size of outputs. - num_style_feat (int): Channel number of style features. Default: 512. - channel_multiplier (int): Channel multiplier for large networks of StyleGAN2. Default: 2. - resample_kernel (list[int]): A list indicating the 1D resample kernel magnitude. A cross production will be - applied to extent 1D resample kernel to 2D resample kernel. Default: (1, 3, 3, 1). - decoder_load_path (str): The path to the pre-trained decoder model (usually, the StyleGAN2). Default: None. - fix_decoder (bool): Whether to fix the decoder. Default: True. - - num_mlp (int): Layer number of MLP style layers. Default: 8. - lr_mlp (float): Learning rate multiplier for mlp layers. Default: 0.01. - input_is_latent (bool): Whether input is latent style. Default: False. - different_w (bool): Whether to use different latent w for different layers. Default: False. - narrow (float): The narrow ratio for channels. Default: 1. - sft_half (bool): Whether to apply SFT on half of the input channels. Default: False. - """ - - def __init__( - self, - out_size, - num_style_feat=512, - channel_multiplier=1, - resample_kernel=(1, 3, 3, 1), - decoder_load_path=None, - fix_decoder=True, - # for stylegan decoder - num_mlp=8, - lr_mlp=0.01, - input_is_latent=False, - different_w=False, - narrow=1, - sft_half=False): - - super(GFPGANv1, self).__init__() - self.input_is_latent = input_is_latent - self.different_w = different_w - self.num_style_feat = num_style_feat - - unet_narrow = narrow * 0.5 # by default, use a half of input channels - channels = { - '4': int(512 * unet_narrow), - '8': int(512 * unet_narrow), - '16': int(512 * unet_narrow), - '32': int(512 * unet_narrow), - '64': int(256 * channel_multiplier * unet_narrow), - '128': int(128 * channel_multiplier * unet_narrow), - '256': int(64 * channel_multiplier * unet_narrow), - '512': int(32 * channel_multiplier * unet_narrow), - '1024': int(16 * channel_multiplier * unet_narrow) - } - - self.log_size = int(math.log(out_size, 2)) - first_out_size = 2**(int(math.log(out_size, 2))) - - self.conv_body_first = ConvLayer(3, channels[f'{first_out_size}'], 1, bias=True, activate=True) - - # downsample - in_channels = channels[f'{first_out_size}'] - self.conv_body_down = nn.ModuleList() - for i in range(self.log_size, 2, -1): - out_channels = channels[f'{2**(i - 1)}'] - self.conv_body_down.append(ResBlock(in_channels, out_channels, resample_kernel)) - in_channels = out_channels - - self.final_conv = ConvLayer(in_channels, channels['4'], 3, bias=True, activate=True) - - # upsample - in_channels = channels['4'] - self.conv_body_up = nn.ModuleList() - for i in range(3, self.log_size + 1): - out_channels = channels[f'{2**i}'] - self.conv_body_up.append(ResUpBlock(in_channels, out_channels)) - in_channels = out_channels - - # to RGB - self.toRGB = nn.ModuleList() - for i in range(3, self.log_size + 1): - self.toRGB.append(EqualConv2d(channels[f'{2**i}'], 3, 1, stride=1, padding=0, bias=True, bias_init_val=0)) - - if different_w: - linear_out_channel = (int(math.log(out_size, 2)) * 2 - 2) * num_style_feat - else: - linear_out_channel = num_style_feat - - self.final_linear = EqualLinear( - channels['4'] * 4 * 4, linear_out_channel, bias=True, bias_init_val=0, lr_mul=1, activation=None) - - # the decoder: stylegan2 generator with SFT modulations - self.stylegan_decoder = StyleGAN2GeneratorSFT( - out_size=out_size, - num_style_feat=num_style_feat, - num_mlp=num_mlp, - channel_multiplier=channel_multiplier, - resample_kernel=resample_kernel, - lr_mlp=lr_mlp, - narrow=narrow, - sft_half=sft_half) - - # load pre-trained stylegan2 model if necessary - if decoder_load_path: - self.stylegan_decoder.load_state_dict( - torch.load(decoder_load_path, map_location=lambda storage, loc: storage)['params_ema']) - # fix decoder without updating params - if fix_decoder: - for _, param in self.stylegan_decoder.named_parameters(): - param.requires_grad = False - - # for SFT modulations (scale and shift) - self.condition_scale = nn.ModuleList() - self.condition_shift = nn.ModuleList() - for i in range(3, self.log_size + 1): - out_channels = channels[f'{2**i}'] - if sft_half: - sft_out_channels = out_channels - else: - sft_out_channels = out_channels * 2 - self.condition_scale.append( - nn.Sequential( - EqualConv2d(out_channels, out_channels, 3, stride=1, padding=1, bias=True, bias_init_val=0), - ScaledLeakyReLU(0.2), - EqualConv2d(out_channels, sft_out_channels, 3, stride=1, padding=1, bias=True, bias_init_val=1))) - self.condition_shift.append( - nn.Sequential( - EqualConv2d(out_channels, out_channels, 3, stride=1, padding=1, bias=True, bias_init_val=0), - ScaledLeakyReLU(0.2), - EqualConv2d(out_channels, sft_out_channels, 3, stride=1, padding=1, bias=True, bias_init_val=0))) - - def forward(self, x, return_latents=False, return_rgb=True, randomize_noise=True): - """Forward function for GFPGANv1. - - Args: - x (Tensor): Input images. - return_latents (bool): Whether to return style latents. Default: False. - return_rgb (bool): Whether return intermediate rgb images. Default: True. - randomize_noise (bool): Randomize noise, used when 'noise' is False. Default: True. - """ - conditions = [] - unet_skips = [] - out_rgbs = [] - - # encoder - feat = self.conv_body_first(x) - for i in range(self.log_size - 2): - feat = self.conv_body_down[i](feat) - unet_skips.insert(0, feat) - - feat = self.final_conv(feat) - - # style code - style_code = self.final_linear(feat.view(feat.size(0), -1)) - if self.different_w: - style_code = style_code.view(style_code.size(0), -1, self.num_style_feat) - - # decode - for i in range(self.log_size - 2): - # add unet skip - feat = feat + unet_skips[i] - # ResUpLayer - feat = self.conv_body_up[i](feat) - # generate scale and shift for SFT layers - scale = self.condition_scale[i](feat) - conditions.append(scale.clone()) - shift = self.condition_shift[i](feat) - conditions.append(shift.clone()) - # generate rgb images - if return_rgb: - out_rgbs.append(self.toRGB[i](feat)) - - # decoder - image, _ = self.stylegan_decoder([style_code], - conditions, - return_latents=return_latents, - input_is_latent=self.input_is_latent, - randomize_noise=randomize_noise) - - return image, out_rgbs - - -@ARCH_REGISTRY.register() -class FacialComponentDiscriminator(nn.Module): - """Facial component (eyes, mouth, noise) discriminator used in GFPGAN. - """ - - def __init__(self): - super(FacialComponentDiscriminator, self).__init__() - # It now uses a VGG-style architectrue with fixed model size - self.conv1 = ConvLayer(3, 64, 3, downsample=False, resample_kernel=(1, 3, 3, 1), bias=True, activate=True) - self.conv2 = ConvLayer(64, 128, 3, downsample=True, resample_kernel=(1, 3, 3, 1), bias=True, activate=True) - self.conv3 = ConvLayer(128, 128, 3, downsample=False, resample_kernel=(1, 3, 3, 1), bias=True, activate=True) - self.conv4 = ConvLayer(128, 256, 3, downsample=True, resample_kernel=(1, 3, 3, 1), bias=True, activate=True) - self.conv5 = ConvLayer(256, 256, 3, downsample=False, resample_kernel=(1, 3, 3, 1), bias=True, activate=True) - self.final_conv = ConvLayer(256, 1, 3, bias=True, activate=False) - - def forward(self, x, return_feats=False): - """Forward function for FacialComponentDiscriminator. - - Args: - x (Tensor): Input images. - return_feats (bool): Whether to return intermediate features. Default: False. - """ - feat = self.conv1(x) - feat = self.conv3(self.conv2(feat)) - rlt_feats = [] - if return_feats: - rlt_feats.append(feat.clone()) - feat = self.conv5(self.conv4(feat)) - if return_feats: - rlt_feats.append(feat.clone()) - out = self.final_conv(feat) - - if return_feats: - return out, rlt_feats - else: - return out, None diff --git a/third_part/GFPGAN/gfpgan/archs/gfpganv1_clean_arch.py b/third_part/GFPGAN/gfpgan/archs/gfpganv1_clean_arch.py deleted file mode 100644 index eb2e15d..0000000 --- a/third_part/GFPGAN/gfpgan/archs/gfpganv1_clean_arch.py +++ /dev/null @@ -1,324 +0,0 @@ -import math -import random -import torch -from basicsr.utils.registry import ARCH_REGISTRY -from torch import nn -from torch.nn import functional as F - -from .stylegan2_clean_arch import StyleGAN2GeneratorClean - - -class StyleGAN2GeneratorCSFT(StyleGAN2GeneratorClean): - """StyleGAN2 Generator with SFT modulation (Spatial Feature Transform). - - It is the clean version without custom compiled CUDA extensions used in StyleGAN2. - - Args: - out_size (int): The spatial size of outputs. - num_style_feat (int): Channel number of style features. Default: 512. - num_mlp (int): Layer number of MLP style layers. Default: 8. - channel_multiplier (int): Channel multiplier for large networks of StyleGAN2. Default: 2. - narrow (float): The narrow ratio for channels. Default: 1. - sft_half (bool): Whether to apply SFT on half of the input channels. Default: False. - """ - - def __init__(self, out_size, num_style_feat=512, num_mlp=8, channel_multiplier=2, narrow=1, sft_half=False): - super(StyleGAN2GeneratorCSFT, self).__init__( - out_size, - num_style_feat=num_style_feat, - num_mlp=num_mlp, - channel_multiplier=channel_multiplier, - narrow=narrow) - self.sft_half = sft_half - - def forward(self, - styles, - conditions, - input_is_latent=False, - noise=None, - randomize_noise=True, - truncation=1, - truncation_latent=None, - inject_index=None, - return_latents=False): - """Forward function for StyleGAN2GeneratorCSFT. - - Args: - styles (list[Tensor]): Sample codes of styles. - conditions (list[Tensor]): SFT conditions to generators. - input_is_latent (bool): Whether input is latent style. Default: False. - noise (Tensor | None): Input noise or None. Default: None. - randomize_noise (bool): Randomize noise, used when 'noise' is False. Default: True. - truncation (float): The truncation ratio. Default: 1. - truncation_latent (Tensor | None): The truncation latent tensor. Default: None. - inject_index (int | None): The injection index for mixing noise. Default: None. - return_latents (bool): Whether to return style latents. Default: False. - """ - # style codes -> latents with Style MLP layer - if not input_is_latent: - styles = [self.style_mlp(s) for s in styles] - # noises - if noise is None: - if randomize_noise: - noise = [None] * self.num_layers # for each style conv layer - else: # use the stored noise - noise = [getattr(self.noises, f'noise{i}') for i in range(self.num_layers)] - # style truncation - if truncation < 1: - style_truncation = [] - for style in styles: - style_truncation.append(truncation_latent + truncation * (style - truncation_latent)) - styles = style_truncation - # get style latents with injection - if len(styles) == 1: - inject_index = self.num_latent - - if styles[0].ndim < 3: - # repeat latent code for all the layers - latent = styles[0].unsqueeze(1).repeat(1, inject_index, 1) - else: # used for encoder with different latent code for each layer - latent = styles[0] - elif len(styles) == 2: # mixing noises - if inject_index is None: - inject_index = random.randint(1, self.num_latent - 1) - latent1 = styles[0].unsqueeze(1).repeat(1, inject_index, 1) - latent2 = styles[1].unsqueeze(1).repeat(1, self.num_latent - inject_index, 1) - latent = torch.cat([latent1, latent2], 1) - - # main generation - out = self.constant_input(latent.shape[0]) - out = self.style_conv1(out, latent[:, 0], noise=noise[0]) - skip = self.to_rgb1(out, latent[:, 1]) - - i = 1 - for conv1, conv2, noise1, noise2, to_rgb in zip(self.style_convs[::2], self.style_convs[1::2], noise[1::2], - noise[2::2], self.to_rgbs): - out = conv1(out, latent[:, i], noise=noise1) - - # the conditions may have fewer levels - if i < len(conditions): - # SFT part to combine the conditions - if self.sft_half: # only apply SFT to half of the channels - out_same, out_sft = torch.split(out, int(out.size(1) // 2), dim=1) - out_sft = out_sft * conditions[i - 1] + conditions[i] - out = torch.cat([out_same, out_sft], dim=1) - else: # apply SFT to all the channels - out = out * conditions[i - 1] + conditions[i] - - out = conv2(out, latent[:, i + 1], noise=noise2) - skip = to_rgb(out, latent[:, i + 2], skip) # feature back to the rgb space - i += 2 - - image = skip - - if return_latents: - return image, latent - else: - return image, None - - -class ResBlock(nn.Module): - """Residual block with bilinear upsampling/downsampling. - - Args: - in_channels (int): Channel number of the input. - out_channels (int): Channel number of the output. - mode (str): Upsampling/downsampling mode. Options: down | up. Default: down. - """ - - def __init__(self, in_channels, out_channels, mode='down'): - super(ResBlock, self).__init__() - - self.conv1 = nn.Conv2d(in_channels, in_channels, 3, 1, 1) - self.conv2 = nn.Conv2d(in_channels, out_channels, 3, 1, 1) - self.skip = nn.Conv2d(in_channels, out_channels, 1, bias=False) - if mode == 'down': - self.scale_factor = 0.5 - elif mode == 'up': - self.scale_factor = 2 - - def forward(self, x): - out = F.leaky_relu_(self.conv1(x), negative_slope=0.2) - # upsample/downsample - out = F.interpolate(out, scale_factor=self.scale_factor, mode='bilinear', align_corners=False) - out = F.leaky_relu_(self.conv2(out), negative_slope=0.2) - # skip - x = F.interpolate(x, scale_factor=self.scale_factor, mode='bilinear', align_corners=False) - skip = self.skip(x) - out = out + skip - return out - - -@ARCH_REGISTRY.register() -class GFPGANv1Clean(nn.Module): - """The GFPGAN architecture: Unet + StyleGAN2 decoder with SFT. - - It is the clean version without custom compiled CUDA extensions used in StyleGAN2. - - Ref: GFP-GAN: Towards Real-World Blind Face Restoration with Generative Facial Prior. - - Args: - out_size (int): The spatial size of outputs. - num_style_feat (int): Channel number of style features. Default: 512. - channel_multiplier (int): Channel multiplier for large networks of StyleGAN2. Default: 2. - decoder_load_path (str): The path to the pre-trained decoder model (usually, the StyleGAN2). Default: None. - fix_decoder (bool): Whether to fix the decoder. Default: True. - - num_mlp (int): Layer number of MLP style layers. Default: 8. - input_is_latent (bool): Whether input is latent style. Default: False. - different_w (bool): Whether to use different latent w for different layers. Default: False. - narrow (float): The narrow ratio for channels. Default: 1. - sft_half (bool): Whether to apply SFT on half of the input channels. Default: False. - """ - - def __init__( - self, - out_size, - num_style_feat=512, - channel_multiplier=1, - decoder_load_path=None, - fix_decoder=True, - # for stylegan decoder - num_mlp=8, - input_is_latent=False, - different_w=False, - narrow=1, - sft_half=False): - - super(GFPGANv1Clean, self).__init__() - self.input_is_latent = input_is_latent - self.different_w = different_w - self.num_style_feat = num_style_feat - - unet_narrow = narrow * 0.5 # by default, use a half of input channels - channels = { - '4': int(512 * unet_narrow), - '8': int(512 * unet_narrow), - '16': int(512 * unet_narrow), - '32': int(512 * unet_narrow), - '64': int(256 * channel_multiplier * unet_narrow), - '128': int(128 * channel_multiplier * unet_narrow), - '256': int(64 * channel_multiplier * unet_narrow), - '512': int(32 * channel_multiplier * unet_narrow), - '1024': int(16 * channel_multiplier * unet_narrow) - } - - self.log_size = int(math.log(out_size, 2)) - first_out_size = 2**(int(math.log(out_size, 2))) - - self.conv_body_first = nn.Conv2d(3, channels[f'{first_out_size}'], 1) - - # downsample - in_channels = channels[f'{first_out_size}'] - self.conv_body_down = nn.ModuleList() - for i in range(self.log_size, 2, -1): - out_channels = channels[f'{2**(i - 1)}'] - self.conv_body_down.append(ResBlock(in_channels, out_channels, mode='down')) - in_channels = out_channels - - self.final_conv = nn.Conv2d(in_channels, channels['4'], 3, 1, 1) - - # upsample - in_channels = channels['4'] - self.conv_body_up = nn.ModuleList() - for i in range(3, self.log_size + 1): - out_channels = channels[f'{2**i}'] - self.conv_body_up.append(ResBlock(in_channels, out_channels, mode='up')) - in_channels = out_channels - - # to RGB - self.toRGB = nn.ModuleList() - for i in range(3, self.log_size + 1): - self.toRGB.append(nn.Conv2d(channels[f'{2**i}'], 3, 1)) - - if different_w: - linear_out_channel = (int(math.log(out_size, 2)) * 2 - 2) * num_style_feat - else: - linear_out_channel = num_style_feat - - self.final_linear = nn.Linear(channels['4'] * 4 * 4, linear_out_channel) - - # the decoder: stylegan2 generator with SFT modulations - self.stylegan_decoder = StyleGAN2GeneratorCSFT( - out_size=out_size, - num_style_feat=num_style_feat, - num_mlp=num_mlp, - channel_multiplier=channel_multiplier, - narrow=narrow, - sft_half=sft_half) - - # load pre-trained stylegan2 model if necessary - if decoder_load_path: - self.stylegan_decoder.load_state_dict( - torch.load(decoder_load_path, map_location=lambda storage, loc: storage)['params_ema']) - # fix decoder without updating params - if fix_decoder: - for _, param in self.stylegan_decoder.named_parameters(): - param.requires_grad = False - - # for SFT modulations (scale and shift) - self.condition_scale = nn.ModuleList() - self.condition_shift = nn.ModuleList() - for i in range(3, self.log_size + 1): - out_channels = channels[f'{2**i}'] - if sft_half: - sft_out_channels = out_channels - else: - sft_out_channels = out_channels * 2 - self.condition_scale.append( - nn.Sequential( - nn.Conv2d(out_channels, out_channels, 3, 1, 1), nn.LeakyReLU(0.2, True), - nn.Conv2d(out_channels, sft_out_channels, 3, 1, 1))) - self.condition_shift.append( - nn.Sequential( - nn.Conv2d(out_channels, out_channels, 3, 1, 1), nn.LeakyReLU(0.2, True), - nn.Conv2d(out_channels, sft_out_channels, 3, 1, 1))) - - def forward(self, x, return_latents=False, return_rgb=True, randomize_noise=True): - """Forward function for GFPGANv1Clean. - - Args: - x (Tensor): Input images. - return_latents (bool): Whether to return style latents. Default: False. - return_rgb (bool): Whether return intermediate rgb images. Default: True. - randomize_noise (bool): Randomize noise, used when 'noise' is False. Default: True. - """ - conditions = [] - unet_skips = [] - out_rgbs = [] - - # encoder - feat = F.leaky_relu_(self.conv_body_first(x), negative_slope=0.2) - for i in range(self.log_size - 2): - feat = self.conv_body_down[i](feat) - unet_skips.insert(0, feat) - feat = F.leaky_relu_(self.final_conv(feat), negative_slope=0.2) - - # style code - style_code = self.final_linear(feat.view(feat.size(0), -1)) - if self.different_w: - style_code = style_code.view(style_code.size(0), -1, self.num_style_feat) - - # decode - for i in range(self.log_size - 2): - # add unet skip - feat = feat + unet_skips[i] - # ResUpLayer - feat = self.conv_body_up[i](feat) - # generate scale and shift for SFT layers - scale = self.condition_scale[i](feat) - conditions.append(scale.clone()) - shift = self.condition_shift[i](feat) - conditions.append(shift.clone()) - # generate rgb images - if return_rgb: - out_rgbs.append(self.toRGB[i](feat)) - - # decoder - image, _ = self.stylegan_decoder([style_code], - conditions, - return_latents=return_latents, - input_is_latent=self.input_is_latent, - randomize_noise=randomize_noise) - - return image, out_rgbs diff --git a/third_part/GFPGAN/gfpgan/archs/stylegan2_bilinear_arch.py b/third_part/GFPGAN/gfpgan/archs/stylegan2_bilinear_arch.py deleted file mode 100644 index 1342ee3..0000000 --- a/third_part/GFPGAN/gfpgan/archs/stylegan2_bilinear_arch.py +++ /dev/null @@ -1,613 +0,0 @@ -import math -import random -import torch -from basicsr.ops.fused_act import FusedLeakyReLU, fused_leaky_relu -from basicsr.utils.registry import ARCH_REGISTRY -from torch import nn -from torch.nn import functional as F - - -class NormStyleCode(nn.Module): - - def forward(self, x): - """Normalize the style codes. - - Args: - x (Tensor): Style codes with shape (b, c). - - Returns: - Tensor: Normalized tensor. - """ - return x * torch.rsqrt(torch.mean(x**2, dim=1, keepdim=True) + 1e-8) - - -class EqualLinear(nn.Module): - """Equalized Linear as StyleGAN2. - - Args: - in_channels (int): Size of each sample. - out_channels (int): Size of each output sample. - bias (bool): If set to ``False``, the layer will not learn an additive - bias. Default: ``True``. - bias_init_val (float): Bias initialized value. Default: 0. - lr_mul (float): Learning rate multiplier. Default: 1. - activation (None | str): The activation after ``linear`` operation. - Supported: 'fused_lrelu', None. Default: None. - """ - - def __init__(self, in_channels, out_channels, bias=True, bias_init_val=0, lr_mul=1, activation=None): - super(EqualLinear, self).__init__() - self.in_channels = in_channels - self.out_channels = out_channels - self.lr_mul = lr_mul - self.activation = activation - if self.activation not in ['fused_lrelu', None]: - raise ValueError(f'Wrong activation value in EqualLinear: {activation}' - "Supported ones are: ['fused_lrelu', None].") - self.scale = (1 / math.sqrt(in_channels)) * lr_mul - - self.weight = nn.Parameter(torch.randn(out_channels, in_channels).div_(lr_mul)) - if bias: - self.bias = nn.Parameter(torch.zeros(out_channels).fill_(bias_init_val)) - else: - self.register_parameter('bias', None) - - def forward(self, x): - if self.bias is None: - bias = None - else: - bias = self.bias * self.lr_mul - if self.activation == 'fused_lrelu': - out = F.linear(x, self.weight * self.scale) - out = fused_leaky_relu(out, bias) - else: - out = F.linear(x, self.weight * self.scale, bias=bias) - return out - - def __repr__(self): - return (f'{self.__class__.__name__}(in_channels={self.in_channels}, ' - f'out_channels={self.out_channels}, bias={self.bias is not None})') - - -class ModulatedConv2d(nn.Module): - """Modulated Conv2d used in StyleGAN2. - - There is no bias in ModulatedConv2d. - - Args: - in_channels (int): Channel number of the input. - out_channels (int): Channel number of the output. - kernel_size (int): Size of the convolving kernel. - num_style_feat (int): Channel number of style features. - demodulate (bool): Whether to demodulate in the conv layer. - Default: True. - sample_mode (str | None): Indicating 'upsample', 'downsample' or None. - Default: None. - eps (float): A value added to the denominator for numerical stability. - Default: 1e-8. - """ - - def __init__(self, - in_channels, - out_channels, - kernel_size, - num_style_feat, - demodulate=True, - sample_mode=None, - eps=1e-8, - interpolation_mode='bilinear'): - super(ModulatedConv2d, self).__init__() - self.in_channels = in_channels - self.out_channels = out_channels - self.kernel_size = kernel_size - self.demodulate = demodulate - self.sample_mode = sample_mode - self.eps = eps - self.interpolation_mode = interpolation_mode - if self.interpolation_mode == 'nearest': - self.align_corners = None - else: - self.align_corners = False - - self.scale = 1 / math.sqrt(in_channels * kernel_size**2) - # modulation inside each modulated conv - self.modulation = EqualLinear( - num_style_feat, in_channels, bias=True, bias_init_val=1, lr_mul=1, activation=None) - - self.weight = nn.Parameter(torch.randn(1, out_channels, in_channels, kernel_size, kernel_size)) - self.padding = kernel_size // 2 - - def forward(self, x, style): - """Forward function. - - Args: - x (Tensor): Tensor with shape (b, c, h, w). - style (Tensor): Tensor with shape (b, num_style_feat). - - Returns: - Tensor: Modulated tensor after convolution. - """ - b, c, h, w = x.shape # c = c_in - # weight modulation - style = self.modulation(style).view(b, 1, c, 1, 1) - # self.weight: (1, c_out, c_in, k, k); style: (b, 1, c, 1, 1) - weight = self.scale * self.weight * style # (b, c_out, c_in, k, k) - - if self.demodulate: - demod = torch.rsqrt(weight.pow(2).sum([2, 3, 4]) + self.eps) - weight = weight * demod.view(b, self.out_channels, 1, 1, 1) - - weight = weight.view(b * self.out_channels, c, self.kernel_size, self.kernel_size) - - if self.sample_mode == 'upsample': - x = F.interpolate(x, scale_factor=2, mode=self.interpolation_mode, align_corners=self.align_corners) - elif self.sample_mode == 'downsample': - x = F.interpolate(x, scale_factor=0.5, mode=self.interpolation_mode, align_corners=self.align_corners) - - b, c, h, w = x.shape - x = x.view(1, b * c, h, w) - # weight: (b*c_out, c_in, k, k), groups=b - out = F.conv2d(x, weight, padding=self.padding, groups=b) - out = out.view(b, self.out_channels, *out.shape[2:4]) - - return out - - def __repr__(self): - return (f'{self.__class__.__name__}(in_channels={self.in_channels}, ' - f'out_channels={self.out_channels}, ' - f'kernel_size={self.kernel_size}, ' - f'demodulate={self.demodulate}, sample_mode={self.sample_mode})') - - -class StyleConv(nn.Module): - """Style conv. - - Args: - in_channels (int): Channel number of the input. - out_channels (int): Channel number of the output. - kernel_size (int): Size of the convolving kernel. - num_style_feat (int): Channel number of style features. - demodulate (bool): Whether demodulate in the conv layer. Default: True. - sample_mode (str | None): Indicating 'upsample', 'downsample' or None. - Default: None. - """ - - def __init__(self, - in_channels, - out_channels, - kernel_size, - num_style_feat, - demodulate=True, - sample_mode=None, - interpolation_mode='bilinear'): - super(StyleConv, self).__init__() - self.modulated_conv = ModulatedConv2d( - in_channels, - out_channels, - kernel_size, - num_style_feat, - demodulate=demodulate, - sample_mode=sample_mode, - interpolation_mode=interpolation_mode) - self.weight = nn.Parameter(torch.zeros(1)) # for noise injection - self.activate = FusedLeakyReLU(out_channels) - - def forward(self, x, style, noise=None): - # modulate - out = self.modulated_conv(x, style) - # noise injection - if noise is None: - b, _, h, w = out.shape - noise = out.new_empty(b, 1, h, w).normal_() - out = out + self.weight * noise - # activation (with bias) - out = self.activate(out) - return out - - -class ToRGB(nn.Module): - """To RGB from features. - - Args: - in_channels (int): Channel number of input. - num_style_feat (int): Channel number of style features. - upsample (bool): Whether to upsample. Default: True. - """ - - def __init__(self, in_channels, num_style_feat, upsample=True, interpolation_mode='bilinear'): - super(ToRGB, self).__init__() - self.upsample = upsample - self.interpolation_mode = interpolation_mode - if self.interpolation_mode == 'nearest': - self.align_corners = None - else: - self.align_corners = False - self.modulated_conv = ModulatedConv2d( - in_channels, - 3, - kernel_size=1, - num_style_feat=num_style_feat, - demodulate=False, - sample_mode=None, - interpolation_mode=interpolation_mode) - self.bias = nn.Parameter(torch.zeros(1, 3, 1, 1)) - - def forward(self, x, style, skip=None): - """Forward function. - - Args: - x (Tensor): Feature tensor with shape (b, c, h, w). - style (Tensor): Tensor with shape (b, num_style_feat). - skip (Tensor): Base/skip tensor. Default: None. - - Returns: - Tensor: RGB images. - """ - out = self.modulated_conv(x, style) - out = out + self.bias - if skip is not None: - if self.upsample: - skip = F.interpolate( - skip, scale_factor=2, mode=self.interpolation_mode, align_corners=self.align_corners) - out = out + skip - return out - - -class ConstantInput(nn.Module): - """Constant input. - - Args: - num_channel (int): Channel number of constant input. - size (int): Spatial size of constant input. - """ - - def __init__(self, num_channel, size): - super(ConstantInput, self).__init__() - self.weight = nn.Parameter(torch.randn(1, num_channel, size, size)) - - def forward(self, batch): - out = self.weight.repeat(batch, 1, 1, 1) - return out - - -@ARCH_REGISTRY.register() -class StyleGAN2GeneratorBilinear(nn.Module): - """StyleGAN2 Generator. - - Args: - out_size (int): The spatial size of outputs. - num_style_feat (int): Channel number of style features. Default: 512. - num_mlp (int): Layer number of MLP style layers. Default: 8. - channel_multiplier (int): Channel multiplier for large networks of - StyleGAN2. Default: 2. - lr_mlp (float): Learning rate multiplier for mlp layers. Default: 0.01. - narrow (float): Narrow ratio for channels. Default: 1.0. - """ - - def __init__(self, - out_size, - num_style_feat=512, - num_mlp=8, - channel_multiplier=2, - lr_mlp=0.01, - narrow=1, - interpolation_mode='bilinear'): - super(StyleGAN2GeneratorBilinear, self).__init__() - # Style MLP layers - self.num_style_feat = num_style_feat - style_mlp_layers = [NormStyleCode()] - for i in range(num_mlp): - style_mlp_layers.append( - EqualLinear( - num_style_feat, num_style_feat, bias=True, bias_init_val=0, lr_mul=lr_mlp, - activation='fused_lrelu')) - self.style_mlp = nn.Sequential(*style_mlp_layers) - - channels = { - '4': int(512 * narrow), - '8': int(512 * narrow), - '16': int(512 * narrow), - '32': int(512 * narrow), - '64': int(256 * channel_multiplier * narrow), - '128': int(128 * channel_multiplier * narrow), - '256': int(64 * channel_multiplier * narrow), - '512': int(32 * channel_multiplier * narrow), - '1024': int(16 * channel_multiplier * narrow) - } - self.channels = channels - - self.constant_input = ConstantInput(channels['4'], size=4) - self.style_conv1 = StyleConv( - channels['4'], - channels['4'], - kernel_size=3, - num_style_feat=num_style_feat, - demodulate=True, - sample_mode=None, - interpolation_mode=interpolation_mode) - self.to_rgb1 = ToRGB(channels['4'], num_style_feat, upsample=False, interpolation_mode=interpolation_mode) - - self.log_size = int(math.log(out_size, 2)) - self.num_layers = (self.log_size - 2) * 2 + 1 - self.num_latent = self.log_size * 2 - 2 - - self.style_convs = nn.ModuleList() - self.to_rgbs = nn.ModuleList() - self.noises = nn.Module() - - in_channels = channels['4'] - # noise - for layer_idx in range(self.num_layers): - resolution = 2**((layer_idx + 5) // 2) - shape = [1, 1, resolution, resolution] - self.noises.register_buffer(f'noise{layer_idx}', torch.randn(*shape)) - # style convs and to_rgbs - for i in range(3, self.log_size + 1): - out_channels = channels[f'{2**i}'] - self.style_convs.append( - StyleConv( - in_channels, - out_channels, - kernel_size=3, - num_style_feat=num_style_feat, - demodulate=True, - sample_mode='upsample', - interpolation_mode=interpolation_mode)) - self.style_convs.append( - StyleConv( - out_channels, - out_channels, - kernel_size=3, - num_style_feat=num_style_feat, - demodulate=True, - sample_mode=None, - interpolation_mode=interpolation_mode)) - self.to_rgbs.append( - ToRGB(out_channels, num_style_feat, upsample=True, interpolation_mode=interpolation_mode)) - in_channels = out_channels - - def make_noise(self): - """Make noise for noise injection.""" - device = self.constant_input.weight.device - noises = [torch.randn(1, 1, 4, 4, device=device)] - - for i in range(3, self.log_size + 1): - for _ in range(2): - noises.append(torch.randn(1, 1, 2**i, 2**i, device=device)) - - return noises - - def get_latent(self, x): - return self.style_mlp(x) - - def mean_latent(self, num_latent): - latent_in = torch.randn(num_latent, self.num_style_feat, device=self.constant_input.weight.device) - latent = self.style_mlp(latent_in).mean(0, keepdim=True) - return latent - - def forward(self, - styles, - input_is_latent=False, - noise=None, - randomize_noise=True, - truncation=1, - truncation_latent=None, - inject_index=None, - return_latents=False): - """Forward function for StyleGAN2Generator. - - Args: - styles (list[Tensor]): Sample codes of styles. - input_is_latent (bool): Whether input is latent style. - Default: False. - noise (Tensor | None): Input noise or None. Default: None. - randomize_noise (bool): Randomize noise, used when 'noise' is - False. Default: True. - truncation (float): TODO. Default: 1. - truncation_latent (Tensor | None): TODO. Default: None. - inject_index (int | None): The injection index for mixing noise. - Default: None. - return_latents (bool): Whether to return style latents. - Default: False. - """ - # style codes -> latents with Style MLP layer - if not input_is_latent: - styles = [self.style_mlp(s) for s in styles] - # noises - if noise is None: - if randomize_noise: - noise = [None] * self.num_layers # for each style conv layer - else: # use the stored noise - noise = [getattr(self.noises, f'noise{i}') for i in range(self.num_layers)] - # style truncation - if truncation < 1: - style_truncation = [] - for style in styles: - style_truncation.append(truncation_latent + truncation * (style - truncation_latent)) - styles = style_truncation - # get style latent with injection - if len(styles) == 1: - inject_index = self.num_latent - - if styles[0].ndim < 3: - # repeat latent code for all the layers - latent = styles[0].unsqueeze(1).repeat(1, inject_index, 1) - else: # used for encoder with different latent code for each layer - latent = styles[0] - elif len(styles) == 2: # mixing noises - if inject_index is None: - inject_index = random.randint(1, self.num_latent - 1) - latent1 = styles[0].unsqueeze(1).repeat(1, inject_index, 1) - latent2 = styles[1].unsqueeze(1).repeat(1, self.num_latent - inject_index, 1) - latent = torch.cat([latent1, latent2], 1) - - # main generation - out = self.constant_input(latent.shape[0]) - out = self.style_conv1(out, latent[:, 0], noise=noise[0]) - skip = self.to_rgb1(out, latent[:, 1]) - - i = 1 - for conv1, conv2, noise1, noise2, to_rgb in zip(self.style_convs[::2], self.style_convs[1::2], noise[1::2], - noise[2::2], self.to_rgbs): - out = conv1(out, latent[:, i], noise=noise1) - out = conv2(out, latent[:, i + 1], noise=noise2) - skip = to_rgb(out, latent[:, i + 2], skip) - i += 2 - - image = skip - - if return_latents: - return image, latent - else: - return image, None - - -class ScaledLeakyReLU(nn.Module): - """Scaled LeakyReLU. - - Args: - negative_slope (float): Negative slope. Default: 0.2. - """ - - def __init__(self, negative_slope=0.2): - super(ScaledLeakyReLU, self).__init__() - self.negative_slope = negative_slope - - def forward(self, x): - out = F.leaky_relu(x, negative_slope=self.negative_slope) - return out * math.sqrt(2) - - -class EqualConv2d(nn.Module): - """Equalized Linear as StyleGAN2. - - Args: - in_channels (int): Channel number of the input. - out_channels (int): Channel number of the output. - kernel_size (int): Size of the convolving kernel. - stride (int): Stride of the convolution. Default: 1 - padding (int): Zero-padding added to both sides of the input. - Default: 0. - bias (bool): If ``True``, adds a learnable bias to the output. - Default: ``True``. - bias_init_val (float): Bias initialized value. Default: 0. - """ - - def __init__(self, in_channels, out_channels, kernel_size, stride=1, padding=0, bias=True, bias_init_val=0): - super(EqualConv2d, self).__init__() - self.in_channels = in_channels - self.out_channels = out_channels - self.kernel_size = kernel_size - self.stride = stride - self.padding = padding - self.scale = 1 / math.sqrt(in_channels * kernel_size**2) - - self.weight = nn.Parameter(torch.randn(out_channels, in_channels, kernel_size, kernel_size)) - if bias: - self.bias = nn.Parameter(torch.zeros(out_channels).fill_(bias_init_val)) - else: - self.register_parameter('bias', None) - - def forward(self, x): - out = F.conv2d( - x, - self.weight * self.scale, - bias=self.bias, - stride=self.stride, - padding=self.padding, - ) - - return out - - def __repr__(self): - return (f'{self.__class__.__name__}(in_channels={self.in_channels}, ' - f'out_channels={self.out_channels}, ' - f'kernel_size={self.kernel_size},' - f' stride={self.stride}, padding={self.padding}, ' - f'bias={self.bias is not None})') - - -class ConvLayer(nn.Sequential): - """Conv Layer used in StyleGAN2 Discriminator. - - Args: - in_channels (int): Channel number of the input. - out_channels (int): Channel number of the output. - kernel_size (int): Kernel size. - downsample (bool): Whether downsample by a factor of 2. - Default: False. - bias (bool): Whether with bias. Default: True. - activate (bool): Whether use activateion. Default: True. - """ - - def __init__(self, - in_channels, - out_channels, - kernel_size, - downsample=False, - bias=True, - activate=True, - interpolation_mode='bilinear'): - layers = [] - self.interpolation_mode = interpolation_mode - # downsample - if downsample: - if self.interpolation_mode == 'nearest': - self.align_corners = None - else: - self.align_corners = False - - layers.append( - torch.nn.Upsample(scale_factor=0.5, mode=interpolation_mode, align_corners=self.align_corners)) - stride = 1 - self.padding = kernel_size // 2 - # conv - layers.append( - EqualConv2d( - in_channels, out_channels, kernel_size, stride=stride, padding=self.padding, bias=bias - and not activate)) - # activation - if activate: - if bias: - layers.append(FusedLeakyReLU(out_channels)) - else: - layers.append(ScaledLeakyReLU(0.2)) - - super(ConvLayer, self).__init__(*layers) - - -class ResBlock(nn.Module): - """Residual block used in StyleGAN2 Discriminator. - - Args: - in_channels (int): Channel number of the input. - out_channels (int): Channel number of the output. - """ - - def __init__(self, in_channels, out_channels, interpolation_mode='bilinear'): - super(ResBlock, self).__init__() - - self.conv1 = ConvLayer(in_channels, in_channels, 3, bias=True, activate=True) - self.conv2 = ConvLayer( - in_channels, - out_channels, - 3, - downsample=True, - interpolation_mode=interpolation_mode, - bias=True, - activate=True) - self.skip = ConvLayer( - in_channels, - out_channels, - 1, - downsample=True, - interpolation_mode=interpolation_mode, - bias=False, - activate=False) - - def forward(self, x): - out = self.conv1(x) - out = self.conv2(out) - skip = self.skip(x) - out = (out + skip) / math.sqrt(2) - return out diff --git a/third_part/GFPGAN/gfpgan/archs/stylegan2_clean_arch.py b/third_part/GFPGAN/gfpgan/archs/stylegan2_clean_arch.py deleted file mode 100644 index 9e2ee94..0000000 --- a/third_part/GFPGAN/gfpgan/archs/stylegan2_clean_arch.py +++ /dev/null @@ -1,368 +0,0 @@ -import math -import random -import torch -from basicsr.archs.arch_util import default_init_weights -from basicsr.utils.registry import ARCH_REGISTRY -from torch import nn -from torch.nn import functional as F - - -class NormStyleCode(nn.Module): - - def forward(self, x): - """Normalize the style codes. - - Args: - x (Tensor): Style codes with shape (b, c). - - Returns: - Tensor: Normalized tensor. - """ - return x * torch.rsqrt(torch.mean(x**2, dim=1, keepdim=True) + 1e-8) - - -class ModulatedConv2d(nn.Module): - """Modulated Conv2d used in StyleGAN2. - - There is no bias in ModulatedConv2d. - - Args: - in_channels (int): Channel number of the input. - out_channels (int): Channel number of the output. - kernel_size (int): Size of the convolving kernel. - num_style_feat (int): Channel number of style features. - demodulate (bool): Whether to demodulate in the conv layer. Default: True. - sample_mode (str | None): Indicating 'upsample', 'downsample' or None. Default: None. - eps (float): A value added to the denominator for numerical stability. Default: 1e-8. - """ - - def __init__(self, - in_channels, - out_channels, - kernel_size, - num_style_feat, - demodulate=True, - sample_mode=None, - eps=1e-8): - super(ModulatedConv2d, self).__init__() - self.in_channels = in_channels - self.out_channels = out_channels - self.kernel_size = kernel_size - self.demodulate = demodulate - self.sample_mode = sample_mode - self.eps = eps - - # modulation inside each modulated conv - self.modulation = nn.Linear(num_style_feat, in_channels, bias=True) - # initialization - default_init_weights(self.modulation, scale=1, bias_fill=1, a=0, mode='fan_in', nonlinearity='linear') - - self.weight = nn.Parameter( - torch.randn(1, out_channels, in_channels, kernel_size, kernel_size) / - math.sqrt(in_channels * kernel_size**2)) - self.padding = kernel_size // 2 - - def forward(self, x, style): - """Forward function. - - Args: - x (Tensor): Tensor with shape (b, c, h, w). - style (Tensor): Tensor with shape (b, num_style_feat). - - Returns: - Tensor: Modulated tensor after convolution. - """ - b, c, h, w = x.shape # c = c_in - # weight modulation - style = self.modulation(style).view(b, 1, c, 1, 1) - # self.weight: (1, c_out, c_in, k, k); style: (b, 1, c, 1, 1) - weight = self.weight * style # (b, c_out, c_in, k, k) - - if self.demodulate: - demod = torch.rsqrt(weight.pow(2).sum([2, 3, 4]) + self.eps) - weight = weight * demod.view(b, self.out_channels, 1, 1, 1) - - weight = weight.view(b * self.out_channels, c, self.kernel_size, self.kernel_size) - - # upsample or downsample if necessary - if self.sample_mode == 'upsample': - x = F.interpolate(x, scale_factor=2, mode='bilinear', align_corners=False) - elif self.sample_mode == 'downsample': - x = F.interpolate(x, scale_factor=0.5, mode='bilinear', align_corners=False) - - b, c, h, w = x.shape - x = x.view(1, b * c, h, w) - # weight: (b*c_out, c_in, k, k), groups=b - out = F.conv2d(x, weight, padding=self.padding, groups=b) - out = out.view(b, self.out_channels, *out.shape[2:4]) - - return out - - def __repr__(self): - return (f'{self.__class__.__name__}(in_channels={self.in_channels}, out_channels={self.out_channels}, ' - f'kernel_size={self.kernel_size}, demodulate={self.demodulate}, sample_mode={self.sample_mode})') - - -class StyleConv(nn.Module): - """Style conv used in StyleGAN2. - - Args: - in_channels (int): Channel number of the input. - out_channels (int): Channel number of the output. - kernel_size (int): Size of the convolving kernel. - num_style_feat (int): Channel number of style features. - demodulate (bool): Whether demodulate in the conv layer. Default: True. - sample_mode (str | None): Indicating 'upsample', 'downsample' or None. Default: None. - """ - - def __init__(self, in_channels, out_channels, kernel_size, num_style_feat, demodulate=True, sample_mode=None): - super(StyleConv, self).__init__() - self.modulated_conv = ModulatedConv2d( - in_channels, out_channels, kernel_size, num_style_feat, demodulate=demodulate, sample_mode=sample_mode) - self.weight = nn.Parameter(torch.zeros(1)) # for noise injection - self.bias = nn.Parameter(torch.zeros(1, out_channels, 1, 1)) - self.activate = nn.LeakyReLU(negative_slope=0.2, inplace=True) - - def forward(self, x, style, noise=None): - # modulate - out = self.modulated_conv(x, style) * 2**0.5 # for conversion - # noise injection - if noise is None: - b, _, h, w = out.shape - noise = out.new_empty(b, 1, h, w).normal_() - out = out + self.weight * noise - # add bias - out = out + self.bias - # activation - out = self.activate(out) - return out - - -class ToRGB(nn.Module): - """To RGB (image space) from features. - - Args: - in_channels (int): Channel number of input. - num_style_feat (int): Channel number of style features. - upsample (bool): Whether to upsample. Default: True. - """ - - def __init__(self, in_channels, num_style_feat, upsample=True): - super(ToRGB, self).__init__() - self.upsample = upsample - self.modulated_conv = ModulatedConv2d( - in_channels, 3, kernel_size=1, num_style_feat=num_style_feat, demodulate=False, sample_mode=None) - self.bias = nn.Parameter(torch.zeros(1, 3, 1, 1)) - - def forward(self, x, style, skip=None): - """Forward function. - - Args: - x (Tensor): Feature tensor with shape (b, c, h, w). - style (Tensor): Tensor with shape (b, num_style_feat). - skip (Tensor): Base/skip tensor. Default: None. - - Returns: - Tensor: RGB images. - """ - out = self.modulated_conv(x, style) - out = out + self.bias - if skip is not None: - if self.upsample: - skip = F.interpolate(skip, scale_factor=2, mode='bilinear', align_corners=False) - out = out + skip - return out - - -class ConstantInput(nn.Module): - """Constant input. - - Args: - num_channel (int): Channel number of constant input. - size (int): Spatial size of constant input. - """ - - def __init__(self, num_channel, size): - super(ConstantInput, self).__init__() - self.weight = nn.Parameter(torch.randn(1, num_channel, size, size)) - - def forward(self, batch): - out = self.weight.repeat(batch, 1, 1, 1) - return out - - -@ARCH_REGISTRY.register() -class StyleGAN2GeneratorClean(nn.Module): - """Clean version of StyleGAN2 Generator. - - Args: - out_size (int): The spatial size of outputs. - num_style_feat (int): Channel number of style features. Default: 512. - num_mlp (int): Layer number of MLP style layers. Default: 8. - channel_multiplier (int): Channel multiplier for large networks of StyleGAN2. Default: 2. - narrow (float): Narrow ratio for channels. Default: 1.0. - """ - - def __init__(self, out_size, num_style_feat=512, num_mlp=8, channel_multiplier=2, narrow=1): - super(StyleGAN2GeneratorClean, self).__init__() - # Style MLP layers - self.num_style_feat = num_style_feat - style_mlp_layers = [NormStyleCode()] - for i in range(num_mlp): - style_mlp_layers.extend( - [nn.Linear(num_style_feat, num_style_feat, bias=True), - nn.LeakyReLU(negative_slope=0.2, inplace=True)]) - self.style_mlp = nn.Sequential(*style_mlp_layers) - # initialization - default_init_weights(self.style_mlp, scale=1, bias_fill=0, a=0.2, mode='fan_in', nonlinearity='leaky_relu') - - # channel list - channels = { - '4': int(512 * narrow), - '8': int(512 * narrow), - '16': int(512 * narrow), - '32': int(512 * narrow), - '64': int(256 * channel_multiplier * narrow), - '128': int(128 * channel_multiplier * narrow), - '256': int(64 * channel_multiplier * narrow), - '512': int(32 * channel_multiplier * narrow), - '1024': int(16 * channel_multiplier * narrow) - } - self.channels = channels - - self.constant_input = ConstantInput(channels['4'], size=4) - self.style_conv1 = StyleConv( - channels['4'], - channels['4'], - kernel_size=3, - num_style_feat=num_style_feat, - demodulate=True, - sample_mode=None) - self.to_rgb1 = ToRGB(channels['4'], num_style_feat, upsample=False) - - self.log_size = int(math.log(out_size, 2)) - self.num_layers = (self.log_size - 2) * 2 + 1 - self.num_latent = self.log_size * 2 - 2 - - self.style_convs = nn.ModuleList() - self.to_rgbs = nn.ModuleList() - self.noises = nn.Module() - - in_channels = channels['4'] - # noise - for layer_idx in range(self.num_layers): - resolution = 2**((layer_idx + 5) // 2) - shape = [1, 1, resolution, resolution] - self.noises.register_buffer(f'noise{layer_idx}', torch.randn(*shape)) - # style convs and to_rgbs - for i in range(3, self.log_size + 1): - out_channels = channels[f'{2**i}'] - self.style_convs.append( - StyleConv( - in_channels, - out_channels, - kernel_size=3, - num_style_feat=num_style_feat, - demodulate=True, - sample_mode='upsample')) - self.style_convs.append( - StyleConv( - out_channels, - out_channels, - kernel_size=3, - num_style_feat=num_style_feat, - demodulate=True, - sample_mode=None)) - self.to_rgbs.append(ToRGB(out_channels, num_style_feat, upsample=True)) - in_channels = out_channels - - def make_noise(self): - """Make noise for noise injection.""" - device = self.constant_input.weight.device - noises = [torch.randn(1, 1, 4, 4, device=device)] - - for i in range(3, self.log_size + 1): - for _ in range(2): - noises.append(torch.randn(1, 1, 2**i, 2**i, device=device)) - - return noises - - def get_latent(self, x): - return self.style_mlp(x) - - def mean_latent(self, num_latent): - latent_in = torch.randn(num_latent, self.num_style_feat, device=self.constant_input.weight.device) - latent = self.style_mlp(latent_in).mean(0, keepdim=True) - return latent - - def forward(self, - styles, - input_is_latent=False, - noise=None, - randomize_noise=True, - truncation=1, - truncation_latent=None, - inject_index=None, - return_latents=False): - """Forward function for StyleGAN2GeneratorClean. - - Args: - styles (list[Tensor]): Sample codes of styles. - input_is_latent (bool): Whether input is latent style. Default: False. - noise (Tensor | None): Input noise or None. Default: None. - randomize_noise (bool): Randomize noise, used when 'noise' is False. Default: True. - truncation (float): The truncation ratio. Default: 1. - truncation_latent (Tensor | None): The truncation latent tensor. Default: None. - inject_index (int | None): The injection index for mixing noise. Default: None. - return_latents (bool): Whether to return style latents. Default: False. - """ - # style codes -> latents with Style MLP layer - if not input_is_latent: - styles = [self.style_mlp(s) for s in styles] - # noises - if noise is None: - if randomize_noise: - noise = [None] * self.num_layers # for each style conv layer - else: # use the stored noise - noise = [getattr(self.noises, f'noise{i}') for i in range(self.num_layers)] - # style truncation - if truncation < 1: - style_truncation = [] - for style in styles: - style_truncation.append(truncation_latent + truncation * (style - truncation_latent)) - styles = style_truncation - # get style latents with injection - if len(styles) == 1: - inject_index = self.num_latent - - if styles[0].ndim < 3: - # repeat latent code for all the layers - latent = styles[0].unsqueeze(1).repeat(1, inject_index, 1) - else: # used for encoder with different latent code for each layer - latent = styles[0] - elif len(styles) == 2: # mixing noises - if inject_index is None: - inject_index = random.randint(1, self.num_latent - 1) - latent1 = styles[0].unsqueeze(1).repeat(1, inject_index, 1) - latent2 = styles[1].unsqueeze(1).repeat(1, self.num_latent - inject_index, 1) - latent = torch.cat([latent1, latent2], 1) - - # main generation - out = self.constant_input(latent.shape[0]) - out = self.style_conv1(out, latent[:, 0], noise=noise[0]) - skip = self.to_rgb1(out, latent[:, 1]) - - i = 1 - for conv1, conv2, noise1, noise2, to_rgb in zip(self.style_convs[::2], self.style_convs[1::2], noise[1::2], - noise[2::2], self.to_rgbs): - out = conv1(out, latent[:, i], noise=noise1) - out = conv2(out, latent[:, i + 1], noise=noise2) - skip = to_rgb(out, latent[:, i + 2], skip) # feature back to the rgb space - i += 2 - - image = skip - - if return_latents: - return image, latent - else: - return image, None diff --git a/third_part/GFPGAN/gfpgan/data/__init__.py b/third_part/GFPGAN/gfpgan/data/__init__.py deleted file mode 100644 index 69fd9f9..0000000 --- a/third_part/GFPGAN/gfpgan/data/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -import importlib -from basicsr.utils import scandir -from os import path as osp - -# automatically scan and import dataset modules for registry -# scan all the files that end with '_dataset.py' under the data folder -data_folder = osp.dirname(osp.abspath(__file__)) -dataset_filenames = [osp.splitext(osp.basename(v))[0] for v in scandir(data_folder) if v.endswith('_dataset.py')] -# import all the dataset modules -_dataset_modules = [importlib.import_module(f'gfpgan.data.{file_name}') for file_name in dataset_filenames] diff --git a/third_part/GFPGAN/gfpgan/data/ffhq_degradation_dataset.py b/third_part/GFPGAN/gfpgan/data/ffhq_degradation_dataset.py deleted file mode 100644 index 64e5755..0000000 --- a/third_part/GFPGAN/gfpgan/data/ffhq_degradation_dataset.py +++ /dev/null @@ -1,230 +0,0 @@ -import cv2 -import math -import numpy as np -import os.path as osp -import torch -import torch.utils.data as data -from basicsr.data import degradations as degradations -from basicsr.data.data_util import paths_from_folder -from basicsr.data.transforms import augment -from basicsr.utils import FileClient, get_root_logger, imfrombytes, img2tensor -from basicsr.utils.registry import DATASET_REGISTRY -from torchvision.transforms.functional import (adjust_brightness, adjust_contrast, adjust_hue, adjust_saturation, - normalize) - - -@DATASET_REGISTRY.register() -class FFHQDegradationDataset(data.Dataset): - """FFHQ dataset for GFPGAN. - - It reads high resolution images, and then generate low-quality (LQ) images on-the-fly. - - Args: - opt (dict): Config for train datasets. It contains the following keys: - dataroot_gt (str): Data root path for gt. - io_backend (dict): IO backend type and other kwarg. - mean (list | tuple): Image mean. - std (list | tuple): Image std. - use_hflip (bool): Whether to horizontally flip. - Please see more options in the codes. - """ - - def __init__(self, opt): - super(FFHQDegradationDataset, self).__init__() - self.opt = opt - # file client (io backend) - self.file_client = None - self.io_backend_opt = opt['io_backend'] - - self.gt_folder = opt['dataroot_gt'] - self.mean = opt['mean'] - self.std = opt['std'] - self.out_size = opt['out_size'] - - self.crop_components = opt.get('crop_components', False) # facial components - self.eye_enlarge_ratio = opt.get('eye_enlarge_ratio', 1) # whether enlarge eye regions - - if self.crop_components: - # load component list from a pre-process pth files - self.components_list = torch.load(opt.get('component_path')) - - # file client (lmdb io backend) - if self.io_backend_opt['type'] == 'lmdb': - self.io_backend_opt['db_paths'] = self.gt_folder - if not self.gt_folder.endswith('.lmdb'): - raise ValueError(f"'dataroot_gt' should end with '.lmdb', but received {self.gt_folder}") - with open(osp.join(self.gt_folder, 'meta_info.txt')) as fin: - self.paths = [line.split('.')[0] for line in fin] - else: - # disk backend: scan file list from a folder - self.paths = paths_from_folder(self.gt_folder) - - # degradation configurations - self.blur_kernel_size = opt['blur_kernel_size'] - self.kernel_list = opt['kernel_list'] - self.kernel_prob = opt['kernel_prob'] - self.blur_sigma = opt['blur_sigma'] - self.downsample_range = opt['downsample_range'] - self.noise_range = opt['noise_range'] - self.jpeg_range = opt['jpeg_range'] - - # color jitter - self.color_jitter_prob = opt.get('color_jitter_prob') - self.color_jitter_pt_prob = opt.get('color_jitter_pt_prob') - self.color_jitter_shift = opt.get('color_jitter_shift', 20) - # to gray - self.gray_prob = opt.get('gray_prob') - - logger = get_root_logger() - logger.info(f'Blur: blur_kernel_size {self.blur_kernel_size}, sigma: [{", ".join(map(str, self.blur_sigma))}]') - logger.info(f'Downsample: downsample_range [{", ".join(map(str, self.downsample_range))}]') - logger.info(f'Noise: [{", ".join(map(str, self.noise_range))}]') - logger.info(f'JPEG compression: [{", ".join(map(str, self.jpeg_range))}]') - - if self.color_jitter_prob is not None: - logger.info(f'Use random color jitter. Prob: {self.color_jitter_prob}, shift: {self.color_jitter_shift}') - if self.gray_prob is not None: - logger.info(f'Use random gray. Prob: {self.gray_prob}') - self.color_jitter_shift /= 255. - - @staticmethod - def color_jitter(img, shift): - """jitter color: randomly jitter the RGB values, in numpy formats""" - jitter_val = np.random.uniform(-shift, shift, 3).astype(np.float32) - img = img + jitter_val - img = np.clip(img, 0, 1) - return img - - @staticmethod - def color_jitter_pt(img, brightness, contrast, saturation, hue): - """jitter color: randomly jitter the brightness, contrast, saturation, and hue, in torch Tensor formats""" - fn_idx = torch.randperm(4) - for fn_id in fn_idx: - if fn_id == 0 and brightness is not None: - brightness_factor = torch.tensor(1.0).uniform_(brightness[0], brightness[1]).item() - img = adjust_brightness(img, brightness_factor) - - if fn_id == 1 and contrast is not None: - contrast_factor = torch.tensor(1.0).uniform_(contrast[0], contrast[1]).item() - img = adjust_contrast(img, contrast_factor) - - if fn_id == 2 and saturation is not None: - saturation_factor = torch.tensor(1.0).uniform_(saturation[0], saturation[1]).item() - img = adjust_saturation(img, saturation_factor) - - if fn_id == 3 and hue is not None: - hue_factor = torch.tensor(1.0).uniform_(hue[0], hue[1]).item() - img = adjust_hue(img, hue_factor) - return img - - def get_component_coordinates(self, index, status): - """Get facial component (left_eye, right_eye, mouth) coordinates from a pre-loaded pth file""" - components_bbox = self.components_list[f'{index:08d}'] - if status[0]: # hflip - # exchange right and left eye - tmp = components_bbox['left_eye'] - components_bbox['left_eye'] = components_bbox['right_eye'] - components_bbox['right_eye'] = tmp - # modify the width coordinate - components_bbox['left_eye'][0] = self.out_size - components_bbox['left_eye'][0] - components_bbox['right_eye'][0] = self.out_size - components_bbox['right_eye'][0] - components_bbox['mouth'][0] = self.out_size - components_bbox['mouth'][0] - - # get coordinates - locations = [] - for part in ['left_eye', 'right_eye', 'mouth']: - mean = components_bbox[part][0:2] - half_len = components_bbox[part][2] - if 'eye' in part: - half_len *= self.eye_enlarge_ratio - loc = np.hstack((mean - half_len + 1, mean + half_len)) - loc = torch.from_numpy(loc).float() - locations.append(loc) - return locations - - def __getitem__(self, index): - if self.file_client is None: - self.file_client = FileClient(self.io_backend_opt.pop('type'), **self.io_backend_opt) - - # load gt image - # Shape: (h, w, c); channel order: BGR; image range: [0, 1], float32. - gt_path = self.paths[index] - img_bytes = self.file_client.get(gt_path) - img_gt = imfrombytes(img_bytes, float32=True) - - # random horizontal flip - img_gt, status = augment(img_gt, hflip=self.opt['use_hflip'], rotation=False, return_status=True) - h, w, _ = img_gt.shape - - # get facial component coordinates - if self.crop_components: - locations = self.get_component_coordinates(index, status) - loc_left_eye, loc_right_eye, loc_mouth = locations - - # ------------------------ generate lq image ------------------------ # - # blur - kernel = degradations.random_mixed_kernels( - self.kernel_list, - self.kernel_prob, - self.blur_kernel_size, - self.blur_sigma, - self.blur_sigma, [-math.pi, math.pi], - noise_range=None) - img_lq = cv2.filter2D(img_gt, -1, kernel) - # downsample - scale = np.random.uniform(self.downsample_range[0], self.downsample_range[1]) - img_lq = cv2.resize(img_lq, (int(w // scale), int(h // scale)), interpolation=cv2.INTER_LINEAR) - # noise - if self.noise_range is not None: - img_lq = degradations.random_add_gaussian_noise(img_lq, self.noise_range) - # jpeg compression - if self.jpeg_range is not None: - img_lq = degradations.random_add_jpg_compression(img_lq, self.jpeg_range) - - # resize to original size - img_lq = cv2.resize(img_lq, (w, h), interpolation=cv2.INTER_LINEAR) - - # random color jitter (only for lq) - if self.color_jitter_prob is not None and (np.random.uniform() < self.color_jitter_prob): - img_lq = self.color_jitter(img_lq, self.color_jitter_shift) - # random to gray (only for lq) - if self.gray_prob and np.random.uniform() < self.gray_prob: - img_lq = cv2.cvtColor(img_lq, cv2.COLOR_BGR2GRAY) - img_lq = np.tile(img_lq[:, :, None], [1, 1, 3]) - if self.opt.get('gt_gray'): # whether convert GT to gray images - img_gt = cv2.cvtColor(img_gt, cv2.COLOR_BGR2GRAY) - img_gt = np.tile(img_gt[:, :, None], [1, 1, 3]) # repeat the color channels - - # BGR to RGB, HWC to CHW, numpy to tensor - img_gt, img_lq = img2tensor([img_gt, img_lq], bgr2rgb=True, float32=True) - - # random color jitter (pytorch version) (only for lq) - if self.color_jitter_pt_prob is not None and (np.random.uniform() < self.color_jitter_pt_prob): - brightness = self.opt.get('brightness', (0.5, 1.5)) - contrast = self.opt.get('contrast', (0.5, 1.5)) - saturation = self.opt.get('saturation', (0, 1.5)) - hue = self.opt.get('hue', (-0.1, 0.1)) - img_lq = self.color_jitter_pt(img_lq, brightness, contrast, saturation, hue) - - # round and clip - img_lq = torch.clamp((img_lq * 255.0).round(), 0, 255) / 255. - - # normalize - normalize(img_gt, self.mean, self.std, inplace=True) - normalize(img_lq, self.mean, self.std, inplace=True) - - if self.crop_components: - return_dict = { - 'lq': img_lq, - 'gt': img_gt, - 'gt_path': gt_path, - 'loc_left_eye': loc_left_eye, - 'loc_right_eye': loc_right_eye, - 'loc_mouth': loc_mouth - } - return return_dict - else: - return {'lq': img_lq, 'gt': img_gt, 'gt_path': gt_path} - - def __len__(self): - return len(self.paths) diff --git a/third_part/GFPGAN/gfpgan/models/__init__.py b/third_part/GFPGAN/gfpgan/models/__init__.py deleted file mode 100644 index 6afad57..0000000 --- a/third_part/GFPGAN/gfpgan/models/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -import importlib -from basicsr.utils import scandir -from os import path as osp - -# automatically scan and import model modules for registry -# scan all the files that end with '_model.py' under the model folder -model_folder = osp.dirname(osp.abspath(__file__)) -model_filenames = [osp.splitext(osp.basename(v))[0] for v in scandir(model_folder) if v.endswith('_model.py')] -# import all the model modules -_model_modules = [importlib.import_module(f'gfpgan.models.{file_name}') for file_name in model_filenames] diff --git a/third_part/GFPGAN/gfpgan/models/gfpgan_model.py b/third_part/GFPGAN/gfpgan/models/gfpgan_model.py deleted file mode 100644 index 5e47b28..0000000 --- a/third_part/GFPGAN/gfpgan/models/gfpgan_model.py +++ /dev/null @@ -1,580 +0,0 @@ -import math -import os.path as osp -import torch -from basicsr.archs import build_network -from basicsr.losses import build_loss -# from basicsr.losses.losses import r1_penalty -from basicsr.losses import r1_penalty -from basicsr.metrics import calculate_metric -from basicsr.models.base_model import BaseModel -from basicsr.utils import get_root_logger, imwrite, tensor2img -from basicsr.utils.registry import MODEL_REGISTRY -from collections import OrderedDict -from torch.nn import functional as F -from torchvision.ops import roi_align -from tqdm import tqdm - - -@MODEL_REGISTRY.register() -class GFPGANModel(BaseModel): - """The GFPGAN model for Towards real-world blind face restoratin with generative facial prior""" - - def __init__(self, opt): - super(GFPGANModel, self).__init__(opt) - self.idx = 0 # it is used for saving data for check - - # define network - self.net_g = build_network(opt['network_g']) - self.net_g = self.model_to_device(self.net_g) - self.print_network(self.net_g) - - # load pretrained model - load_path = self.opt['path'].get('pretrain_network_g', None) - if load_path is not None: - param_key = self.opt['path'].get('param_key_g', 'params') - self.load_network(self.net_g, load_path, self.opt['path'].get('strict_load_g', True), param_key) - - self.log_size = int(math.log(self.opt['network_g']['out_size'], 2)) - - if self.is_train: - self.init_training_settings() - - def init_training_settings(self): - train_opt = self.opt['train'] - - # ----------- define net_d ----------- # - self.net_d = build_network(self.opt['network_d']) - self.net_d = self.model_to_device(self.net_d) - self.print_network(self.net_d) - # load pretrained model - load_path = self.opt['path'].get('pretrain_network_d', None) - if load_path is not None: - self.load_network(self.net_d, load_path, self.opt['path'].get('strict_load_d', True)) - - # ----------- define net_g with Exponential Moving Average (EMA) ----------- # - # net_g_ema only used for testing on one GPU and saving. There is no need to wrap with DistributedDataParallel - self.net_g_ema = build_network(self.opt['network_g']).to(self.device) - # load pretrained model - load_path = self.opt['path'].get('pretrain_network_g', None) - if load_path is not None: - self.load_network(self.net_g_ema, load_path, self.opt['path'].get('strict_load_g', True), 'params_ema') - else: - self.model_ema(0) # copy net_g weight - - self.net_g.train() - self.net_d.train() - self.net_g_ema.eval() - - # ----------- facial component networks ----------- # - if ('network_d_left_eye' in self.opt and 'network_d_right_eye' in self.opt and 'network_d_mouth' in self.opt): - self.use_facial_disc = True - else: - self.use_facial_disc = False - - if self.use_facial_disc: - # left eye - self.net_d_left_eye = build_network(self.opt['network_d_left_eye']) - self.net_d_left_eye = self.model_to_device(self.net_d_left_eye) - self.print_network(self.net_d_left_eye) - load_path = self.opt['path'].get('pretrain_network_d_left_eye') - if load_path is not None: - self.load_network(self.net_d_left_eye, load_path, True, 'params') - # right eye - self.net_d_right_eye = build_network(self.opt['network_d_right_eye']) - self.net_d_right_eye = self.model_to_device(self.net_d_right_eye) - self.print_network(self.net_d_right_eye) - load_path = self.opt['path'].get('pretrain_network_d_right_eye') - if load_path is not None: - self.load_network(self.net_d_right_eye, load_path, True, 'params') - # mouth - self.net_d_mouth = build_network(self.opt['network_d_mouth']) - self.net_d_mouth = self.model_to_device(self.net_d_mouth) - self.print_network(self.net_d_mouth) - load_path = self.opt['path'].get('pretrain_network_d_mouth') - if load_path is not None: - self.load_network(self.net_d_mouth, load_path, True, 'params') - - self.net_d_left_eye.train() - self.net_d_right_eye.train() - self.net_d_mouth.train() - - # ----------- define facial component gan loss ----------- # - self.cri_component = build_loss(train_opt['gan_component_opt']).to(self.device) - - # ----------- define losses ----------- # - # pixel loss - if train_opt.get('pixel_opt'): - self.cri_pix = build_loss(train_opt['pixel_opt']).to(self.device) - else: - self.cri_pix = None - - # perceptual loss - if train_opt.get('perceptual_opt'): - self.cri_perceptual = build_loss(train_opt['perceptual_opt']).to(self.device) - else: - self.cri_perceptual = None - - # L1 loss is used in pyramid loss, component style loss and identity loss - self.cri_l1 = build_loss(train_opt['L1_opt']).to(self.device) - - # gan loss (wgan) - self.cri_gan = build_loss(train_opt['gan_opt']).to(self.device) - - # ----------- define identity loss ----------- # - if 'network_identity' in self.opt: - self.use_identity = True - else: - self.use_identity = False - - if self.use_identity: - # define identity network - self.network_identity = build_network(self.opt['network_identity']) - self.network_identity = self.model_to_device(self.network_identity) - self.print_network(self.network_identity) - load_path = self.opt['path'].get('pretrain_network_identity') - if load_path is not None: - self.load_network(self.network_identity, load_path, True, None) - self.network_identity.eval() - for param in self.network_identity.parameters(): - param.requires_grad = False - - # regularization weights - self.r1_reg_weight = train_opt['r1_reg_weight'] # for discriminator - self.net_d_iters = train_opt.get('net_d_iters', 1) - self.net_d_init_iters = train_opt.get('net_d_init_iters', 0) - self.net_d_reg_every = train_opt['net_d_reg_every'] - - # set up optimizers and schedulers - self.setup_optimizers() - self.setup_schedulers() - - def setup_optimizers(self): - train_opt = self.opt['train'] - - # ----------- optimizer g ----------- # - net_g_reg_ratio = 1 - normal_params = [] - for _, param in self.net_g.named_parameters(): - normal_params.append(param) - optim_params_g = [{ # add normal params first - 'params': normal_params, - 'lr': train_opt['optim_g']['lr'] - }] - optim_type = train_opt['optim_g'].pop('type') - lr = train_opt['optim_g']['lr'] * net_g_reg_ratio - betas = (0**net_g_reg_ratio, 0.99**net_g_reg_ratio) - self.optimizer_g = self.get_optimizer(optim_type, optim_params_g, lr, betas=betas) - self.optimizers.append(self.optimizer_g) - - # ----------- optimizer d ----------- # - net_d_reg_ratio = self.net_d_reg_every / (self.net_d_reg_every + 1) - normal_params = [] - for _, param in self.net_d.named_parameters(): - normal_params.append(param) - optim_params_d = [{ # add normal params first - 'params': normal_params, - 'lr': train_opt['optim_d']['lr'] - }] - optim_type = train_opt['optim_d'].pop('type') - lr = train_opt['optim_d']['lr'] * net_d_reg_ratio - betas = (0**net_d_reg_ratio, 0.99**net_d_reg_ratio) - self.optimizer_d = self.get_optimizer(optim_type, optim_params_d, lr, betas=betas) - self.optimizers.append(self.optimizer_d) - - # ----------- optimizers for facial component networks ----------- # - if self.use_facial_disc: - # setup optimizers for facial component discriminators - optim_type = train_opt['optim_component'].pop('type') - lr = train_opt['optim_component']['lr'] - # left eye - self.optimizer_d_left_eye = self.get_optimizer( - optim_type, self.net_d_left_eye.parameters(), lr, betas=(0.9, 0.99)) - self.optimizers.append(self.optimizer_d_left_eye) - # right eye - self.optimizer_d_right_eye = self.get_optimizer( - optim_type, self.net_d_right_eye.parameters(), lr, betas=(0.9, 0.99)) - self.optimizers.append(self.optimizer_d_right_eye) - # mouth - self.optimizer_d_mouth = self.get_optimizer( - optim_type, self.net_d_mouth.parameters(), lr, betas=(0.9, 0.99)) - self.optimizers.append(self.optimizer_d_mouth) - - def feed_data(self, data): - self.lq = data['lq'].to(self.device) - if 'gt' in data: - self.gt = data['gt'].to(self.device) - - if 'loc_left_eye' in data: - # get facial component locations, shape (batch, 4) - self.loc_left_eyes = data['loc_left_eye'] - self.loc_right_eyes = data['loc_right_eye'] - self.loc_mouths = data['loc_mouth'] - - # uncomment to check data - # import torchvision - # if self.opt['rank'] == 0: - # import os - # os.makedirs('tmp/gt', exist_ok=True) - # os.makedirs('tmp/lq', exist_ok=True) - # print(self.idx) - # torchvision.utils.save_image( - # self.gt, f'tmp/gt/gt_{self.idx}.png', nrow=4, padding=2, normalize=True, range=(-1, 1)) - # torchvision.utils.save_image( - # self.lq, f'tmp/lq/lq{self.idx}.png', nrow=4, padding=2, normalize=True, range=(-1, 1)) - # self.idx = self.idx + 1 - - def construct_img_pyramid(self): - """Construct image pyramid for intermediate restoration loss""" - pyramid_gt = [self.gt] - down_img = self.gt - for _ in range(0, self.log_size - 3): - down_img = F.interpolate(down_img, scale_factor=0.5, mode='bilinear', align_corners=False) - pyramid_gt.insert(0, down_img) - return pyramid_gt - - def get_roi_regions(self, eye_out_size=80, mouth_out_size=120): - face_ratio = int(self.opt['network_g']['out_size'] / 512) - eye_out_size *= face_ratio - mouth_out_size *= face_ratio - - rois_eyes = [] - rois_mouths = [] - for b in range(self.loc_left_eyes.size(0)): # loop for batch size - # left eye and right eye - img_inds = self.loc_left_eyes.new_full((2, 1), b) - bbox = torch.stack([self.loc_left_eyes[b, :], self.loc_right_eyes[b, :]], dim=0) # shape: (2, 4) - rois = torch.cat([img_inds, bbox], dim=-1) # shape: (2, 5) - rois_eyes.append(rois) - # mouse - img_inds = self.loc_left_eyes.new_full((1, 1), b) - rois = torch.cat([img_inds, self.loc_mouths[b:b + 1, :]], dim=-1) # shape: (1, 5) - rois_mouths.append(rois) - - rois_eyes = torch.cat(rois_eyes, 0).to(self.device) - rois_mouths = torch.cat(rois_mouths, 0).to(self.device) - - # real images - all_eyes = roi_align(self.gt, boxes=rois_eyes, output_size=eye_out_size) * face_ratio - self.left_eyes_gt = all_eyes[0::2, :, :, :] - self.right_eyes_gt = all_eyes[1::2, :, :, :] - self.mouths_gt = roi_align(self.gt, boxes=rois_mouths, output_size=mouth_out_size) * face_ratio - # output - all_eyes = roi_align(self.output, boxes=rois_eyes, output_size=eye_out_size) * face_ratio - self.left_eyes = all_eyes[0::2, :, :, :] - self.right_eyes = all_eyes[1::2, :, :, :] - self.mouths = roi_align(self.output, boxes=rois_mouths, output_size=mouth_out_size) * face_ratio - - def _gram_mat(self, x): - """Calculate Gram matrix. - - Args: - x (torch.Tensor): Tensor with shape of (n, c, h, w). - - Returns: - torch.Tensor: Gram matrix. - """ - n, c, h, w = x.size() - features = x.view(n, c, w * h) - features_t = features.transpose(1, 2) - gram = features.bmm(features_t) / (c * h * w) - return gram - - def gray_resize_for_identity(self, out, size=128): - out_gray = (0.2989 * out[:, 0, :, :] + 0.5870 * out[:, 1, :, :] + 0.1140 * out[:, 2, :, :]) - out_gray = out_gray.unsqueeze(1) - out_gray = F.interpolate(out_gray, (size, size), mode='bilinear', align_corners=False) - return out_gray - - def optimize_parameters(self, current_iter): - # optimize net_g - for p in self.net_d.parameters(): - p.requires_grad = False - self.optimizer_g.zero_grad() - - # do not update facial component net_d - if self.use_facial_disc: - for p in self.net_d_left_eye.parameters(): - p.requires_grad = False - for p in self.net_d_right_eye.parameters(): - p.requires_grad = False - for p in self.net_d_mouth.parameters(): - p.requires_grad = False - - # image pyramid loss weight - pyramid_loss_weight = self.opt['train'].get('pyramid_loss_weight', 0) - if pyramid_loss_weight > 0 and current_iter > self.opt['train'].get('remove_pyramid_loss', float('inf')): - pyramid_loss_weight = 1e-12 # very small weight to avoid unused param error - if pyramid_loss_weight > 0: - self.output, out_rgbs = self.net_g(self.lq, return_rgb=True) - pyramid_gt = self.construct_img_pyramid() - else: - self.output, out_rgbs = self.net_g(self.lq, return_rgb=False) - - # get roi-align regions - if self.use_facial_disc: - self.get_roi_regions(eye_out_size=80, mouth_out_size=120) - - l_g_total = 0 - loss_dict = OrderedDict() - if (current_iter % self.net_d_iters == 0 and current_iter > self.net_d_init_iters): - # pixel loss - if self.cri_pix: - l_g_pix = self.cri_pix(self.output, self.gt) - l_g_total += l_g_pix - loss_dict['l_g_pix'] = l_g_pix - - # image pyramid loss - if pyramid_loss_weight > 0: - for i in range(0, self.log_size - 2): - l_pyramid = self.cri_l1(out_rgbs[i], pyramid_gt[i]) * pyramid_loss_weight - l_g_total += l_pyramid - loss_dict[f'l_p_{2**(i+3)}'] = l_pyramid - - # perceptual loss - if self.cri_perceptual: - l_g_percep, l_g_style = self.cri_perceptual(self.output, self.gt) - if l_g_percep is not None: - l_g_total += l_g_percep - loss_dict['l_g_percep'] = l_g_percep - if l_g_style is not None: - l_g_total += l_g_style - loss_dict['l_g_style'] = l_g_style - - # gan loss - fake_g_pred = self.net_d(self.output) - l_g_gan = self.cri_gan(fake_g_pred, True, is_disc=False) - l_g_total += l_g_gan - loss_dict['l_g_gan'] = l_g_gan - - # facial component loss - if self.use_facial_disc: - # left eye - fake_left_eye, fake_left_eye_feats = self.net_d_left_eye(self.left_eyes, return_feats=True) - l_g_gan = self.cri_component(fake_left_eye, True, is_disc=False) - l_g_total += l_g_gan - loss_dict['l_g_gan_left_eye'] = l_g_gan - # right eye - fake_right_eye, fake_right_eye_feats = self.net_d_right_eye(self.right_eyes, return_feats=True) - l_g_gan = self.cri_component(fake_right_eye, True, is_disc=False) - l_g_total += l_g_gan - loss_dict['l_g_gan_right_eye'] = l_g_gan - # mouth - fake_mouth, fake_mouth_feats = self.net_d_mouth(self.mouths, return_feats=True) - l_g_gan = self.cri_component(fake_mouth, True, is_disc=False) - l_g_total += l_g_gan - loss_dict['l_g_gan_mouth'] = l_g_gan - - if self.opt['train'].get('comp_style_weight', 0) > 0: - # get gt feat - _, real_left_eye_feats = self.net_d_left_eye(self.left_eyes_gt, return_feats=True) - _, real_right_eye_feats = self.net_d_right_eye(self.right_eyes_gt, return_feats=True) - _, real_mouth_feats = self.net_d_mouth(self.mouths_gt, return_feats=True) - - def _comp_style(feat, feat_gt, criterion): - return criterion(self._gram_mat(feat[0]), self._gram_mat( - feat_gt[0].detach())) * 0.5 + criterion( - self._gram_mat(feat[1]), self._gram_mat(feat_gt[1].detach())) - - # facial component style loss - comp_style_loss = 0 - comp_style_loss += _comp_style(fake_left_eye_feats, real_left_eye_feats, self.cri_l1) - comp_style_loss += _comp_style(fake_right_eye_feats, real_right_eye_feats, self.cri_l1) - comp_style_loss += _comp_style(fake_mouth_feats, real_mouth_feats, self.cri_l1) - comp_style_loss = comp_style_loss * self.opt['train']['comp_style_weight'] - l_g_total += comp_style_loss - loss_dict['l_g_comp_style_loss'] = comp_style_loss - - # identity loss - if self.use_identity: - identity_weight = self.opt['train']['identity_weight'] - # get gray images and resize - out_gray = self.gray_resize_for_identity(self.output) - gt_gray = self.gray_resize_for_identity(self.gt) - - identity_gt = self.network_identity(gt_gray).detach() - identity_out = self.network_identity(out_gray) - l_identity = self.cri_l1(identity_out, identity_gt) * identity_weight - l_g_total += l_identity - loss_dict['l_identity'] = l_identity - - l_g_total.backward() - self.optimizer_g.step() - - # EMA - self.model_ema(decay=0.5**(32 / (10 * 1000))) - - # ----------- optimize net_d ----------- # - for p in self.net_d.parameters(): - p.requires_grad = True - self.optimizer_d.zero_grad() - if self.use_facial_disc: - for p in self.net_d_left_eye.parameters(): - p.requires_grad = True - for p in self.net_d_right_eye.parameters(): - p.requires_grad = True - for p in self.net_d_mouth.parameters(): - p.requires_grad = True - self.optimizer_d_left_eye.zero_grad() - self.optimizer_d_right_eye.zero_grad() - self.optimizer_d_mouth.zero_grad() - - fake_d_pred = self.net_d(self.output.detach()) - real_d_pred = self.net_d(self.gt) - l_d = self.cri_gan(real_d_pred, True, is_disc=True) + self.cri_gan(fake_d_pred, False, is_disc=True) - loss_dict['l_d'] = l_d - # In WGAN, real_score should be positive and fake_score should be negative - loss_dict['real_score'] = real_d_pred.detach().mean() - loss_dict['fake_score'] = fake_d_pred.detach().mean() - l_d.backward() - - # regularization loss - if current_iter % self.net_d_reg_every == 0: - self.gt.requires_grad = True - real_pred = self.net_d(self.gt) - l_d_r1 = r1_penalty(real_pred, self.gt) - l_d_r1 = (self.r1_reg_weight / 2 * l_d_r1 * self.net_d_reg_every + 0 * real_pred[0]) - loss_dict['l_d_r1'] = l_d_r1.detach().mean() - l_d_r1.backward() - - self.optimizer_d.step() - - # optimize facial component discriminators - if self.use_facial_disc: - # left eye - fake_d_pred, _ = self.net_d_left_eye(self.left_eyes.detach()) - real_d_pred, _ = self.net_d_left_eye(self.left_eyes_gt) - l_d_left_eye = self.cri_component( - real_d_pred, True, is_disc=True) + self.cri_gan( - fake_d_pred, False, is_disc=True) - loss_dict['l_d_left_eye'] = l_d_left_eye - l_d_left_eye.backward() - # right eye - fake_d_pred, _ = self.net_d_right_eye(self.right_eyes.detach()) - real_d_pred, _ = self.net_d_right_eye(self.right_eyes_gt) - l_d_right_eye = self.cri_component( - real_d_pred, True, is_disc=True) + self.cri_gan( - fake_d_pred, False, is_disc=True) - loss_dict['l_d_right_eye'] = l_d_right_eye - l_d_right_eye.backward() - # mouth - fake_d_pred, _ = self.net_d_mouth(self.mouths.detach()) - real_d_pred, _ = self.net_d_mouth(self.mouths_gt) - l_d_mouth = self.cri_component( - real_d_pred, True, is_disc=True) + self.cri_gan( - fake_d_pred, False, is_disc=True) - loss_dict['l_d_mouth'] = l_d_mouth - l_d_mouth.backward() - - self.optimizer_d_left_eye.step() - self.optimizer_d_right_eye.step() - self.optimizer_d_mouth.step() - - self.log_dict = self.reduce_loss_dict(loss_dict) - - def test(self): - with torch.no_grad(): - if hasattr(self, 'net_g_ema'): - self.net_g_ema.eval() - self.output, _ = self.net_g_ema(self.lq) - else: - logger = get_root_logger() - logger.warning('Do not have self.net_g_ema, use self.net_g.') - self.net_g.eval() - self.output, _ = self.net_g(self.lq) - self.net_g.train() - - def dist_validation(self, dataloader, current_iter, tb_logger, save_img): - if self.opt['rank'] == 0: - self.nondist_validation(dataloader, current_iter, tb_logger, save_img) - - def nondist_validation(self, dataloader, current_iter, tb_logger, save_img): - dataset_name = dataloader.dataset.opt['name'] - with_metrics = self.opt['val'].get('metrics') is not None - use_pbar = self.opt['val'].get('pbar', False) - - if with_metrics: - if not hasattr(self, 'metric_results'): # only execute in the first run - self.metric_results = {metric: 0 for metric in self.opt['val']['metrics'].keys()} - # initialize the best metric results for each dataset_name (supporting multiple validation datasets) - self._initialize_best_metric_results(dataset_name) - # zero self.metric_results - self.metric_results = {metric: 0 for metric in self.metric_results} - - metric_data = dict() - if use_pbar: - pbar = tqdm(total=len(dataloader), unit='image') - - for idx, val_data in enumerate(dataloader): - img_name = osp.splitext(osp.basename(val_data['lq_path'][0]))[0] - self.feed_data(val_data) - self.test() - - sr_img = tensor2img(self.output.detach().cpu(), min_max=(-1, 1)) - metric_data['img'] = sr_img - if hasattr(self, 'gt'): - gt_img = tensor2img(self.gt.detach().cpu(), min_max=(-1, 1)) - metric_data['img2'] = gt_img - del self.gt - - # tentative for out of GPU memory - del self.lq - del self.output - torch.cuda.empty_cache() - - if save_img: - if self.opt['is_train']: - save_img_path = osp.join(self.opt['path']['visualization'], img_name, - f'{img_name}_{current_iter}.png') - else: - if self.opt['val']['suffix']: - save_img_path = osp.join(self.opt['path']['visualization'], dataset_name, - f'{img_name}_{self.opt["val"]["suffix"]}.png') - else: - save_img_path = osp.join(self.opt['path']['visualization'], dataset_name, - f'{img_name}_{self.opt["name"]}.png') - imwrite(sr_img, save_img_path) - - if with_metrics: - # calculate metrics - for name, opt_ in self.opt['val']['metrics'].items(): - self.metric_results[name] += calculate_metric(metric_data, opt_) - if use_pbar: - pbar.update(1) - pbar.set_description(f'Test {img_name}') - if use_pbar: - pbar.close() - - if with_metrics: - for metric in self.metric_results.keys(): - self.metric_results[metric] /= (idx + 1) - # update the best metric result - self._update_best_metric_result(dataset_name, metric, self.metric_results[metric], current_iter) - - self._log_validation_metric_values(current_iter, dataset_name, tb_logger) - - def _log_validation_metric_values(self, current_iter, dataset_name, tb_logger): - log_str = f'Validation {dataset_name}\n' - for metric, value in self.metric_results.items(): - log_str += f'\t # {metric}: {value:.4f}' - if hasattr(self, 'best_metric_results'): - log_str += (f'\tBest: {self.best_metric_results[dataset_name][metric]["val"]:.4f} @ ' - f'{self.best_metric_results[dataset_name][metric]["iter"]} iter') - log_str += '\n' - - logger = get_root_logger() - logger.info(log_str) - if tb_logger: - for metric, value in self.metric_results.items(): - tb_logger.add_scalar(f'metrics/{dataset_name}/{metric}', value, current_iter) - - def save(self, epoch, current_iter): - # save net_g and net_d - self.save_network([self.net_g, self.net_g_ema], 'net_g', current_iter, param_key=['params', 'params_ema']) - self.save_network(self.net_d, 'net_d', current_iter) - # save component discriminators - if self.use_facial_disc: - self.save_network(self.net_d_left_eye, 'net_d_left_eye', current_iter) - self.save_network(self.net_d_right_eye, 'net_d_right_eye', current_iter) - self.save_network(self.net_d_mouth, 'net_d_mouth', current_iter) - # save training state - self.save_training_state(epoch, current_iter) diff --git a/third_part/GFPGAN/gfpgan/train.py b/third_part/GFPGAN/gfpgan/train.py deleted file mode 100644 index fe5f1f9..0000000 --- a/third_part/GFPGAN/gfpgan/train.py +++ /dev/null @@ -1,11 +0,0 @@ -# flake8: noqa -import os.path as osp -from basicsr.train import train_pipeline - -import gfpgan.archs -import gfpgan.data -import gfpgan.models - -if __name__ == '__main__': - root_path = osp.abspath(osp.join(__file__, osp.pardir, osp.pardir)) - train_pipeline(root_path) diff --git a/third_part/GFPGAN/gfpgan/utils.py b/third_part/GFPGAN/gfpgan/utils.py deleted file mode 100644 index edd14d8..0000000 --- a/third_part/GFPGAN/gfpgan/utils.py +++ /dev/null @@ -1,144 +0,0 @@ -import cv2 -import os -import torch -from basicsr.utils import img2tensor, tensor2img -from basicsr.utils.download_util import load_file_from_url -from facexlib.utils.face_restoration_helper import FaceRestoreHelper -from torchvision.transforms.functional import normalize - -from gfpgan.archs.gfpgan_bilinear_arch import GFPGANBilinear -from gfpgan.archs.gfpganv1_arch import GFPGANv1 -from gfpgan.archs.gfpganv1_clean_arch import GFPGANv1Clean - -ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - - -class GFPGANer(): - """Helper for restoration with GFPGAN. - - It will detect and crop faces, and then resize the faces to 512x512. - GFPGAN is used to restored the resized faces. - The background is upsampled with the bg_upsampler. - Finally, the faces will be pasted back to the upsample background image. - - Args: - model_path (str): The path to the GFPGAN model. It can be urls (will first download it automatically). - upscale (float): The upscale of the final output. Default: 2. - arch (str): The GFPGAN architecture. Option: clean | original. Default: clean. - channel_multiplier (int): Channel multiplier for large networks of StyleGAN2. Default: 2. - bg_upsampler (nn.Module): The upsampler for the background. Default: None. - """ - - def __init__(self, model_path, upscale=2, arch='clean', channel_multiplier=2, bg_upsampler=None): - self.upscale = upscale - self.bg_upsampler = bg_upsampler - - # initialize model - self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') - # initialize the GFP-GAN - if arch == 'clean': - self.gfpgan = GFPGANv1Clean( - out_size=512, - num_style_feat=512, - channel_multiplier=channel_multiplier, - decoder_load_path=None, - fix_decoder=False, - num_mlp=8, - input_is_latent=True, - different_w=True, - narrow=1, - sft_half=True) - elif arch == 'bilinear': - self.gfpgan = GFPGANBilinear( - out_size=512, - num_style_feat=512, - channel_multiplier=channel_multiplier, - decoder_load_path=None, - fix_decoder=False, - num_mlp=8, - input_is_latent=True, - different_w=True, - narrow=1, - sft_half=True) - elif arch == 'original': - self.gfpgan = GFPGANv1( - out_size=512, - num_style_feat=512, - channel_multiplier=channel_multiplier, - decoder_load_path=None, - fix_decoder=True, - num_mlp=8, - input_is_latent=True, - different_w=True, - narrow=1, - sft_half=True) - # initialize face helper - self.face_helper = FaceRestoreHelper( - upscale, - face_size=512, - crop_ratio=(1, 1), - det_model='retinaface_resnet50', - save_ext='png', - device=self.device, - model_rootpath='gfpgan/weights') - - if model_path.startswith('https://'): - model_path = load_file_from_url( - url=model_path, model_dir=os.path.join(ROOT_DIR, 'gfpgan/weights'), progress=True, file_name=None) - loadnet = torch.load(model_path) - if 'params_ema' in loadnet: - keyname = 'params_ema' - else: - keyname = 'params' - self.gfpgan.load_state_dict(loadnet[keyname], strict=True) - self.gfpgan.eval() - self.gfpgan = self.gfpgan.to(self.device) - - @torch.no_grad() - def enhance(self, img, has_aligned=False, only_center_face=False, paste_back=True): - self.face_helper.clean_all() - - if has_aligned: # the inputs are already aligned - img = cv2.resize(img, (512, 512)) - self.face_helper.cropped_faces = [img] - else: - self.face_helper.read_image(img) - # get face landmarks for each face - self.face_helper.get_face_landmarks_5(only_center_face=only_center_face, eye_dist_threshold=5) - # eye_dist_threshold=5: skip faces whose eye distance is smaller than 5 pixels - # TODO: even with eye_dist_threshold, it will still introduce wrong detections and restorations. - # align and warp each face - self.face_helper.align_warp_face() - - # face restoration - for cropped_face in self.face_helper.cropped_faces: - # prepare data - cropped_face_t = img2tensor(cropped_face / 255., bgr2rgb=True, float32=True) - normalize(cropped_face_t, (0.5, 0.5, 0.5), (0.5, 0.5, 0.5), inplace=True) - cropped_face_t = cropped_face_t.unsqueeze(0).to(self.device) - - try: - output = self.gfpgan(cropped_face_t, return_rgb=False)[0] - # convert to image - restored_face = tensor2img(output.squeeze(0), rgb2bgr=True, min_max=(-1, 1)) - except RuntimeError as error: - print(f'\tFailed inference for GFPGAN: {error}.') - restored_face = cropped_face - - restored_face = restored_face.astype('uint8') - self.face_helper.add_restored_face(restored_face) - - if not has_aligned and paste_back: - # upsample the background - if self.bg_upsampler is not None: - # Now only support RealESRGAN for upsampling background - bg_img = self.bg_upsampler.enhance(img, outscale=self.upscale)[0] - else: - bg_img = None - - self.face_helper.get_inverse_affine(None) - # paste each restored face to the input image - restored_img = self.face_helper.paste_faces_to_input_image(upsample_img=bg_img) - return self.face_helper.cropped_faces, self.face_helper.restored_faces, restored_img - else: - return self.face_helper.cropped_faces, self.face_helper.restored_faces, None diff --git a/third_part/GFPGAN/gfpgan/version.py b/third_part/GFPGAN/gfpgan/version.py deleted file mode 100644 index 565af38..0000000 --- a/third_part/GFPGAN/gfpgan/version.py +++ /dev/null @@ -1,5 +0,0 @@ -# GENERATED VERSION FILE -# TIME: Wed Apr 20 14:43:06 2022 -__version__ = '1.3.2' -__gitsha__ = '924ce47' -version_info = (1, 3, 2) diff --git a/third_part/GFPGAN/gfpgan/weights/README.md b/third_part/GFPGAN/gfpgan/weights/README.md deleted file mode 100644 index 4d7b7e6..0000000 --- a/third_part/GFPGAN/gfpgan/weights/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Weights - -Put the downloaded weights to this folder. diff --git a/third_part/GFPGAN/options/train_gfpgan_v1.yml b/third_part/GFPGAN/options/train_gfpgan_v1.yml deleted file mode 100644 index aa5212a..0000000 --- a/third_part/GFPGAN/options/train_gfpgan_v1.yml +++ /dev/null @@ -1,216 +0,0 @@ -# general settings -name: train_GFPGANv1_512 -model_type: GFPGANModel -num_gpu: auto # officially, we use 4 GPUs -manual_seed: 0 - -# dataset and data loader settings -datasets: - train: - name: FFHQ - type: FFHQDegradationDataset - # dataroot_gt: datasets/ffhq/ffhq_512.lmdb - dataroot_gt: datasets/ffhq/ffhq_512 - io_backend: - # type: lmdb - type: disk - - use_hflip: true - mean: [0.5, 0.5, 0.5] - std: [0.5, 0.5, 0.5] - out_size: 512 - - blur_kernel_size: 41 - kernel_list: ['iso', 'aniso'] - kernel_prob: [0.5, 0.5] - blur_sigma: [0.1, 10] - downsample_range: [0.8, 8] - noise_range: [0, 20] - jpeg_range: [60, 100] - - # color jitter and gray - color_jitter_prob: 0.3 - color_jitter_shift: 20 - color_jitter_pt_prob: 0.3 - gray_prob: 0.01 - - # If you do not want colorization, please set - # color_jitter_prob: ~ - # color_jitter_pt_prob: ~ - # gray_prob: 0.01 - # gt_gray: True - - crop_components: true - component_path: experiments/pretrained_models/FFHQ_eye_mouth_landmarks_512.pth - eye_enlarge_ratio: 1.4 - - # data loader - use_shuffle: true - num_worker_per_gpu: 6 - batch_size_per_gpu: 3 - dataset_enlarge_ratio: 1 - prefetch_mode: ~ - - val: - # Please modify accordingly to use your own validation - # Or comment the val block if do not need validation during training - name: validation - type: PairedImageDataset - dataroot_lq: datasets/faces/validation/input - dataroot_gt: datasets/faces/validation/reference - io_backend: - type: disk - mean: [0.5, 0.5, 0.5] - std: [0.5, 0.5, 0.5] - scale: 1 - -# network structures -network_g: - type: GFPGANv1 - out_size: 512 - num_style_feat: 512 - channel_multiplier: 1 - resample_kernel: [1, 3, 3, 1] - decoder_load_path: experiments/pretrained_models/StyleGAN2_512_Cmul1_FFHQ_B12G4_scratch_800k.pth - fix_decoder: true - num_mlp: 8 - lr_mlp: 0.01 - input_is_latent: true - different_w: true - narrow: 1 - sft_half: true - -network_d: - type: StyleGAN2Discriminator - out_size: 512 - channel_multiplier: 1 - resample_kernel: [1, 3, 3, 1] - -network_d_left_eye: - type: FacialComponentDiscriminator - -network_d_right_eye: - type: FacialComponentDiscriminator - -network_d_mouth: - type: FacialComponentDiscriminator - -network_identity: - type: ResNetArcFace - block: IRBlock - layers: [2, 2, 2, 2] - use_se: False - -# path -path: - pretrain_network_g: ~ - param_key_g: params_ema - strict_load_g: ~ - pretrain_network_d: ~ - pretrain_network_d_left_eye: ~ - pretrain_network_d_right_eye: ~ - pretrain_network_d_mouth: ~ - pretrain_network_identity: experiments/pretrained_models/arcface_resnet18.pth - # resume - resume_state: ~ - ignore_resume_networks: ['network_identity'] - -# training settings -train: - optim_g: - type: Adam - lr: !!float 2e-3 - optim_d: - type: Adam - lr: !!float 2e-3 - optim_component: - type: Adam - lr: !!float 2e-3 - - scheduler: - type: MultiStepLR - milestones: [600000, 700000] - gamma: 0.5 - - total_iter: 800000 - warmup_iter: -1 # no warm up - - # losses - # pixel loss - pixel_opt: - type: L1Loss - loss_weight: !!float 1e-1 - reduction: mean - # L1 loss used in pyramid loss, component style loss and identity loss - L1_opt: - type: L1Loss - loss_weight: 1 - reduction: mean - - # image pyramid loss - pyramid_loss_weight: 1 - remove_pyramid_loss: 50000 - # perceptual loss (content and style losses) - perceptual_opt: - type: PerceptualLoss - layer_weights: - # before relu - 'conv1_2': 0.1 - 'conv2_2': 0.1 - 'conv3_4': 1 - 'conv4_4': 1 - 'conv5_4': 1 - vgg_type: vgg19 - use_input_norm: true - perceptual_weight: !!float 1 - style_weight: 50 - range_norm: true - criterion: l1 - # gan loss - gan_opt: - type: GANLoss - gan_type: wgan_softplus - loss_weight: !!float 1e-1 - # r1 regularization for discriminator - r1_reg_weight: 10 - # facial component loss - gan_component_opt: - type: GANLoss - gan_type: vanilla - real_label_val: 1.0 - fake_label_val: 0.0 - loss_weight: !!float 1 - comp_style_weight: 200 - # identity loss - identity_weight: 10 - - net_d_iters: 1 - net_d_init_iters: 0 - net_d_reg_every: 16 - -# validation settings -val: - val_freq: !!float 5e3 - save_img: true - - metrics: - psnr: # metric name - type: calculate_psnr - crop_border: 0 - test_y_channel: false - -# logging settings -logger: - print_freq: 100 - save_checkpoint_freq: !!float 5e3 - use_tb_logger: true - wandb: - project: ~ - resume_id: ~ - -# dist training settings -dist_params: - backend: nccl - port: 29500 - -find_unused_parameters: true diff --git a/third_part/GFPGAN/options/train_gfpgan_v1_simple.yml b/third_part/GFPGAN/options/train_gfpgan_v1_simple.yml deleted file mode 100644 index 3807575..0000000 --- a/third_part/GFPGAN/options/train_gfpgan_v1_simple.yml +++ /dev/null @@ -1,182 +0,0 @@ -# general settings -name: train_GFPGANv1_512_simple -model_type: GFPGANModel -num_gpu: auto # officially, we use 4 GPUs -manual_seed: 0 - -# dataset and data loader settings -datasets: - train: - name: FFHQ - type: FFHQDegradationDataset - # dataroot_gt: datasets/ffhq/ffhq_512.lmdb - dataroot_gt: datasets/ffhq/ffhq_512 - io_backend: - # type: lmdb - type: disk - - use_hflip: true - mean: [0.5, 0.5, 0.5] - std: [0.5, 0.5, 0.5] - out_size: 512 - - blur_kernel_size: 41 - kernel_list: ['iso', 'aniso'] - kernel_prob: [0.5, 0.5] - blur_sigma: [0.1, 10] - downsample_range: [0.8, 8] - noise_range: [0, 20] - jpeg_range: [60, 100] - - # color jitter and gray - color_jitter_prob: 0.3 - color_jitter_shift: 20 - color_jitter_pt_prob: 0.3 - gray_prob: 0.01 - - # If you do not want colorization, please set - # color_jitter_prob: ~ - # color_jitter_pt_prob: ~ - # gray_prob: 0.01 - # gt_gray: True - - # data loader - use_shuffle: true - num_worker_per_gpu: 6 - batch_size_per_gpu: 3 - dataset_enlarge_ratio: 1 - prefetch_mode: ~ - - val: - # Please modify accordingly to use your own validation - # Or comment the val block if do not need validation during training - name: validation - type: PairedImageDataset - dataroot_lq: datasets/faces/validation/input - dataroot_gt: datasets/faces/validation/reference - io_backend: - type: disk - mean: [0.5, 0.5, 0.5] - std: [0.5, 0.5, 0.5] - scale: 1 - -# network structures -network_g: - type: GFPGANv1 - out_size: 512 - num_style_feat: 512 - channel_multiplier: 1 - resample_kernel: [1, 3, 3, 1] - decoder_load_path: experiments/pretrained_models/StyleGAN2_512_Cmul1_FFHQ_B12G4_scratch_800k.pth - fix_decoder: true - num_mlp: 8 - lr_mlp: 0.01 - input_is_latent: true - different_w: true - narrow: 1 - sft_half: true - -network_d: - type: StyleGAN2Discriminator - out_size: 512 - channel_multiplier: 1 - resample_kernel: [1, 3, 3, 1] - - -# path -path: - pretrain_network_g: ~ - param_key_g: params_ema - strict_load_g: ~ - pretrain_network_d: ~ - resume_state: ~ - -# training settings -train: - optim_g: - type: Adam - lr: !!float 2e-3 - optim_d: - type: Adam - lr: !!float 2e-3 - optim_component: - type: Adam - lr: !!float 2e-3 - - scheduler: - type: MultiStepLR - milestones: [600000, 700000] - gamma: 0.5 - - total_iter: 800000 - warmup_iter: -1 # no warm up - - # losses - # pixel loss - pixel_opt: - type: L1Loss - loss_weight: !!float 1e-1 - reduction: mean - # L1 loss used in pyramid loss, component style loss and identity loss - L1_opt: - type: L1Loss - loss_weight: 1 - reduction: mean - - # image pyramid loss - pyramid_loss_weight: 1 - remove_pyramid_loss: 50000 - # perceptual loss (content and style losses) - perceptual_opt: - type: PerceptualLoss - layer_weights: - # before relu - 'conv1_2': 0.1 - 'conv2_2': 0.1 - 'conv3_4': 1 - 'conv4_4': 1 - 'conv5_4': 1 - vgg_type: vgg19 - use_input_norm: true - perceptual_weight: !!float 1 - style_weight: 50 - range_norm: true - criterion: l1 - # gan loss - gan_opt: - type: GANLoss - gan_type: wgan_softplus - loss_weight: !!float 1e-1 - # r1 regularization for discriminator - r1_reg_weight: 10 - - net_d_iters: 1 - net_d_init_iters: 0 - net_d_reg_every: 16 - -# validation settings -val: - val_freq: !!float 5e3 - save_img: true - - metrics: - psnr: # metric name - type: calculate_psnr - crop_border: 0 - test_y_channel: false - -# logging settings -logger: - print_freq: 100 - save_checkpoint_freq: !!float 5e3 - use_tb_logger: true - wandb: - project: ~ - resume_id: ~ - -# dist training settings -dist_params: - backend: nccl - port: 29500 - -find_unused_parameters: true diff --git a/third_part/GPEN/align_faces.py b/third_part/GPEN/align_faces.py deleted file mode 100644 index 98ed541..0000000 --- a/third_part/GPEN/align_faces.py +++ /dev/null @@ -1,271 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Mon Apr 24 15:43:29 2017 -@author: zhaoy -""" -""" -@Modified by yangxy (yangtao9009@gmail.com) -""" -import cv2 -import numpy as np -from skimage import transform as trans - -# reference facial points, a list of coordinates (x,y) -REFERENCE_FACIAL_POINTS = [ - [30.29459953, 51.69630051], - [65.53179932, 51.50139999], - [48.02519989, 71.73660278], - [33.54930115, 92.3655014], - [62.72990036, 92.20410156] -] - -DEFAULT_CROP_SIZE = (96, 112) - - -def _umeyama(src, dst, estimate_scale=True, scale=1.0): - """Estimate N-D similarity transformation with or without scaling. - Parameters - ---------- - src : (M, N) array - Source coordinates. - dst : (M, N) array - Destination coordinates. - estimate_scale : bool - Whether to estimate scaling factor. - Returns - ------- - T : (N + 1, N + 1) - The homogeneous similarity transformation matrix. The matrix contains - NaN values only if the problem is not well-conditioned. - References - ---------- - .. [1] "Least-squares estimation of transformation parameters between two - point patterns", Shinji Umeyama, PAMI 1991, :DOI:`10.1109/34.88573` - """ - - num = src.shape[0] - dim = src.shape[1] - - # Compute mean of src and dst. - src_mean = src.mean(axis=0) - dst_mean = dst.mean(axis=0) - - # Subtract mean from src and dst. - src_demean = src - src_mean - dst_demean = dst - dst_mean - - # Eq. (38). - A = dst_demean.T @ src_demean / num - - # Eq. (39). - d = np.ones((dim,), dtype=np.double) - if np.linalg.det(A) < 0: - d[dim - 1] = -1 - - T = np.eye(dim + 1, dtype=np.double) - - U, S, V = np.linalg.svd(A) - - # Eq. (40) and (43). - rank = np.linalg.matrix_rank(A) - if rank == 0: - return np.nan * T - elif rank == dim - 1: - if np.linalg.det(U) * np.linalg.det(V) > 0: - T[:dim, :dim] = U @ V - else: - s = d[dim - 1] - d[dim - 1] = -1 - T[:dim, :dim] = U @ np.diag(d) @ V - d[dim - 1] = s - else: - T[:dim, :dim] = U @ np.diag(d) @ V - - if estimate_scale: - # Eq. (41) and (42). - scale = 1.0 / src_demean.var(axis=0).sum() * (S @ d) - else: - scale = scale - - T[:dim, dim] = dst_mean - scale * (T[:dim, :dim] @ src_mean.T) - T[:dim, :dim] *= scale - - return T, scale - - -class FaceWarpException(Exception): - def __str__(self): - return 'In File {}:{}'.format( - __file__, super.__str__(self)) - - -def get_reference_facial_points(output_size=None, - inner_padding_factor=0.0, - outer_padding=(0, 0), - default_square=False): - tmp_5pts = np.array(REFERENCE_FACIAL_POINTS) - tmp_crop_size = np.array(DEFAULT_CROP_SIZE) - - # 0) make the inner region a square - if default_square: - size_diff = max(tmp_crop_size) - tmp_crop_size - tmp_5pts += size_diff / 2 - tmp_crop_size += size_diff - - if (output_size and - output_size[0] == tmp_crop_size[0] and - output_size[1] == tmp_crop_size[1]): - print('output_size == DEFAULT_CROP_SIZE {}: return default reference points'.format(tmp_crop_size)) - return tmp_5pts - - if (inner_padding_factor == 0 and - outer_padding == (0, 0)): - if output_size is None: - print('No paddings to do: return default reference points') - return tmp_5pts - else: - raise FaceWarpException( - 'No paddings to do, output_size must be None or {}'.format(tmp_crop_size)) - - # check output size - if not (0 <= inner_padding_factor <= 1.0): - raise FaceWarpException('Not (0 <= inner_padding_factor <= 1.0)') - - if ((inner_padding_factor > 0 or outer_padding[0] > 0 or outer_padding[1] > 0) - and output_size is None): - output_size = tmp_crop_size * \ - (1 + inner_padding_factor * 2).astype(np.int32) - output_size += np.array(outer_padding) - print(' deduced from paddings, output_size = ', output_size) - - if not (outer_padding[0] < output_size[0] - and outer_padding[1] < output_size[1]): - raise FaceWarpException('Not (outer_padding[0] < output_size[0]' - 'and outer_padding[1] < output_size[1])') - - # 1) pad the inner region according inner_padding_factor - # print('---> STEP1: pad the inner region according inner_padding_factor') - if inner_padding_factor > 0: - size_diff = tmp_crop_size * inner_padding_factor * 2 - tmp_5pts += size_diff / 2 - tmp_crop_size += np.round(size_diff).astype(np.int32) - - # print(' crop_size = ', tmp_crop_size) - # print(' reference_5pts = ', tmp_5pts) - - # 2) resize the padded inner region - # print('---> STEP2: resize the padded inner region') - size_bf_outer_pad = np.array(output_size) - np.array(outer_padding) * 2 - # print(' crop_size = ', tmp_crop_size) - # print(' size_bf_outer_pad = ', size_bf_outer_pad) - - if size_bf_outer_pad[0] * tmp_crop_size[1] != size_bf_outer_pad[1] * tmp_crop_size[0]: - raise FaceWarpException('Must have (output_size - outer_padding)' - '= some_scale * (crop_size * (1.0 + inner_padding_factor)') - - scale_factor = size_bf_outer_pad[0].astype(np.float32) / tmp_crop_size[0] - # print(' resize scale_factor = ', scale_factor) - tmp_5pts = tmp_5pts * scale_factor - # size_diff = tmp_crop_size * (scale_factor - min(scale_factor)) - # tmp_5pts = tmp_5pts + size_diff / 2 - tmp_crop_size = size_bf_outer_pad - # print(' crop_size = ', tmp_crop_size) - # print(' reference_5pts = ', tmp_5pts) - - # 3) add outer_padding to make output_size - reference_5point = tmp_5pts + np.array(outer_padding) - tmp_crop_size = output_size - # print('---> STEP3: add outer_padding to make output_size') - # print(' crop_size = ', tmp_crop_size) - # print(' reference_5pts = ', tmp_5pts) - # - # print('===> end get_reference_facial_points\n') - - return reference_5point - - -def get_affine_transform_matrix(src_pts, dst_pts): - tfm = np.float32([[1, 0, 0], [0, 1, 0]]) - n_pts = src_pts.shape[0] - ones = np.ones((n_pts, 1), src_pts.dtype) - src_pts_ = np.hstack([src_pts, ones]) - dst_pts_ = np.hstack([dst_pts, ones]) - - A, res, rank, s = np.linalg.lstsq(src_pts_, dst_pts_) - - if rank == 3: - tfm = np.float32([ - [A[0, 0], A[1, 0], A[2, 0]], - [A[0, 1], A[1, 1], A[2, 1]] - ]) - elif rank == 2: - tfm = np.float32([ - [A[0, 0], A[1, 0], 0], - [A[0, 1], A[1, 1], 0] - ]) - - return tfm - - -def warp_and_crop_face(src_img, - facial_pts, - reference_pts=None, - crop_size=(96, 112), - align_type='smilarity'): #smilarity cv2_affine affine - if reference_pts is None: - if crop_size[0] == 96 and crop_size[1] == 112: - reference_pts = REFERENCE_FACIAL_POINTS - else: - default_square = False - inner_padding_factor = 0 - outer_padding = (0, 0) - output_size = crop_size - - reference_pts = get_reference_facial_points(output_size, - inner_padding_factor, - outer_padding, - default_square) - - ref_pts = np.float32(reference_pts) - ref_pts_shp = ref_pts.shape - if max(ref_pts_shp) < 3: # or min(ref_pts_shp) != 2: - raise FaceWarpException( - 'reference_pts.shape must be (K,2) or (2,K) and K>2') - - if ref_pts_shp[0] == 2 or ref_pts_shp[0] == 3: - ref_pts = ref_pts.T - - src_pts = np.float32(facial_pts) - src_pts_shp = src_pts.shape - if max(src_pts_shp) < 3: # or min(src_pts_shp) != 2: - raise FaceWarpException( - 'facial_pts.shape must be (K,2) or (2,K) and K>2') - - if src_pts_shp[0] == 2 or src_pts_shp[0] == 3: - src_pts = src_pts.T - - if src_pts.shape != ref_pts.shape: - raise FaceWarpException( - 'facial_pts and reference_pts must have the same shape') - - if align_type is 'cv2_affine': - tfm = cv2.getAffineTransform(src_pts[0:3], ref_pts[0:3]) - tfm_inv = cv2.getAffineTransform(ref_pts[0:3], src_pts[0:3]) - elif align_type is 'cv2_rigid': - tfm, _ = cv2.estimateAffinePartial2D(src_pts[0:3], ref_pts[0:3]) - tfm_inv, _ = cv2.estimateAffinePartial2D(ref_pts[0:3], src_pts[0:3]) - elif align_type is 'affine': - tfm = get_affine_transform_matrix(src_pts, ref_pts) - tfm_inv = get_affine_transform_matrix(ref_pts, src_pts) - else: - params, scale = _umeyama(src_pts, ref_pts) - tfm = params[:2, :] - - params, _ = _umeyama(ref_pts, src_pts, False, scale=1.0/scale) - tfm_inv = params[:2, :] - - # M = cv2.getPerspectiveTransform(ref_pts[0:4], src_pts[0:4]) - face_img = cv2.warpAffine(src_img, tfm, (crop_size[0], crop_size[1]), flags=3) - # face_img = cv2.warpPerspective(src_img, M, (crop_size[0], crop_size[1]), flags=cv2.INTER_LINEAR ) - - return face_img, tfm_inv diff --git a/third_part/GPEN/face_detect/.DS_Store b/third_part/GPEN/face_detect/.DS_Store deleted file mode 100644 index 2112b95d33b5353f276676a84ecba1bf76202daf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12292 zcmeHNTWl3Y82iTG8@IdV?4BM< zt*ME`7cpuyModf$Z-_i;lowyT#7n%yV3Zgh^uNaV-nZ%d`Fr^El63XOxGl`U_(Fn zV3AZwc|dt!Dtn-UCdcpJO3Jlx2F!8yuM2@247O!lKLb$lN@l=yg0Qjid#spMSyf%5 zF*e)i*th>cude3<`-ttDp)+Ww%+L;c^?5;`U+BpC-r<}*=-7p>tncRgJ<`-=O*fmT z&UTCIrkx9R&>h<6LwW6< zrbl+AZqB`Do?bVfvEz=v)`_(*u&V( zMT?irHDbnnMOC?prq^rT`HXWe?}S5QjO8l~cG9AmAM`Cgilx7!F`=nnVHhX4rY&bv zX=$2hUejQ(&VW{-n^DKaI>tI5cRWiEXbODdO&geT)HO4I z+)$rvlrMEF_Ox3NEv=I9(}WOA zy8i~E`|s35_g`#J``*+)y#HvN$f;Tx-WQ##r1F6Bzzp^Pe?G)1bD50fsFdQ-K?SD} zT+bh^3ltYkg7v|I+kLt-tj!G%b0maDlJJHA(MJOoE z0pp@OU{Xp|Di0_R-01;|_b284{~P!CcijKh97*`^U9fO@eZ#s9Tei29h;bPe9?QuE z5tmF`Fz_P77F>87t>@&~h%IHyCJPOY5`nkRYn+AaHL`a+wo)VFtqbOD#8$=DXcQgj z6++9_#;B*M>9b_#^{h!Fwy#%7?MAklQ9PhmN^LXSrV;JetEF})OEMm@=pgH>VkxHS zI2oCrl*+#WSK$VH1fLOw{}yh;Pw)%;iGbCp3;G_zrML`N;~IPb*J1)U;btQ8&A1Iy zxEtGXFOm5)cH%MY!X6^?SwZIe@Eqna#5|GuQGALh{Y88xBK0rf%lJB8k(;hW;XB4} zLcBKF4jfG9JlFTmlAb^GtHC8!JVbAnau8$+L9{#J(;_qet{9nWwOZ}m+Q-rqf~G?= z%m=Mz?gXzLHp9M5blvIuZt2?09HSt!S6|TPd!gxgc1{>mJV{+ncG2JM7h3(iX9ePS z(o&&mOk=(JlHuV^%^MT(=8YqX_;6EGV`Cz|W$VKuBTQSos%b~Z>HeV$!%vJn`Knwv zMhFJ;WW7{gE?=Yyzg74>#l!CjwyZ*p=v`C!3nhgb|1oNeLxeK4sDeDbL2(E(rY%;C zS)xJ;g%ndCDaLPDg%b)V?oMeM2UOP*o!J!dFi}ix<*x{1uC3WI63uka_n|h zD5+5L?x198*Og)9gkOeCOQa}%KrtiU8WZkw?5EVg&;NH%{{5fmqzdH$<$>wx0jx}= zQ?0bN;>kq$GWl71obqYPJn?>`QVJ6)qT_g>6G!pX7JXhO+LF|r+l@*o%&0ES?%GEH T>I{y%A7G+0IEDW+3jhBGkj<<9 diff --git a/third_part/GPEN/face_detect/data/FDDB/img_list.txt b/third_part/GPEN/face_detect/data/FDDB/img_list.txt deleted file mode 100644 index 5cf3d31..0000000 --- a/third_part/GPEN/face_detect/data/FDDB/img_list.txt +++ /dev/null @@ -1,2845 +0,0 @@ -2002/08/11/big/img_591 -2002/08/26/big/img_265 -2002/07/19/big/img_423 -2002/08/24/big/img_490 -2002/08/31/big/img_17676 -2002/07/31/big/img_228 -2002/07/24/big/img_402 -2002/08/04/big/img_769 -2002/07/19/big/img_581 -2002/08/13/big/img_723 -2002/08/12/big/img_821 -2003/01/17/big/img_610 -2002/08/13/big/img_1116 -2002/08/28/big/img_19238 -2002/08/21/big/img_660 -2002/08/14/big/img_607 -2002/08/05/big/img_3708 -2002/08/19/big/img_511 -2002/08/07/big/img_1316 -2002/07/25/big/img_1047 -2002/07/23/big/img_474 -2002/07/27/big/img_970 -2002/09/02/big/img_15752 -2002/09/01/big/img_16378 -2002/09/01/big/img_16189 -2002/08/26/big/img_276 -2002/07/24/big/img_518 -2002/08/14/big/img_1027 -2002/08/24/big/img_733 -2002/08/15/big/img_249 -2003/01/15/big/img_1371 -2002/08/07/big/img_1348 -2003/01/01/big/img_331 -2002/08/23/big/img_536 -2002/07/30/big/img_224 -2002/08/10/big/img_763 -2002/08/21/big/img_293 -2002/08/15/big/img_1211 -2002/08/15/big/img_1194 -2003/01/15/big/img_390 -2002/08/06/big/img_2893 -2002/08/17/big/img_691 -2002/08/07/big/img_1695 -2002/08/16/big/img_829 -2002/07/25/big/img_201 -2002/08/23/big/img_36 -2003/01/15/big/img_763 -2003/01/15/big/img_637 -2002/08/22/big/img_592 -2002/07/25/big/img_817 -2003/01/15/big/img_1219 -2002/08/05/big/img_3508 -2002/08/15/big/img_1108 -2002/07/19/big/img_488 -2003/01/16/big/img_704 -2003/01/13/big/img_1087 -2002/08/10/big/img_670 -2002/07/24/big/img_104 -2002/08/27/big/img_19823 -2002/09/01/big/img_16229 -2003/01/13/big/img_846 -2002/08/04/big/img_412 -2002/07/22/big/img_554 -2002/08/12/big/img_331 -2002/08/02/big/img_533 -2002/08/12/big/img_259 -2002/08/18/big/img_328 -2003/01/14/big/img_630 -2002/08/05/big/img_3541 -2002/08/06/big/img_2390 -2002/08/20/big/img_150 -2002/08/02/big/img_1231 -2002/08/16/big/img_710 -2002/08/19/big/img_591 -2002/07/22/big/img_725 -2002/07/24/big/img_820 -2003/01/13/big/img_568 -2002/08/22/big/img_853 -2002/08/09/big/img_648 -2002/08/23/big/img_528 -2003/01/14/big/img_888 -2002/08/30/big/img_18201 -2002/08/13/big/img_965 -2003/01/14/big/img_660 -2002/07/19/big/img_517 -2003/01/14/big/img_406 -2002/08/30/big/img_18433 -2002/08/07/big/img_1630 -2002/08/06/big/img_2717 -2002/08/21/big/img_470 -2002/07/23/big/img_633 -2002/08/20/big/img_915 -2002/08/16/big/img_893 -2002/07/29/big/img_644 -2002/08/15/big/img_529 -2002/08/16/big/img_668 -2002/08/07/big/img_1871 -2002/07/25/big/img_192 -2002/07/31/big/img_961 -2002/08/19/big/img_738 -2002/07/31/big/img_382 -2002/08/19/big/img_298 -2003/01/17/big/img_608 -2002/08/21/big/img_514 -2002/07/23/big/img_183 -2003/01/17/big/img_536 -2002/07/24/big/img_478 -2002/08/06/big/img_2997 -2002/09/02/big/img_15380 -2002/08/07/big/img_1153 -2002/07/31/big/img_967 -2002/07/31/big/img_711 -2002/08/26/big/img_664 -2003/01/01/big/img_326 -2002/08/24/big/img_775 -2002/08/08/big/img_961 -2002/08/16/big/img_77 -2002/08/12/big/img_296 -2002/07/22/big/img_905 -2003/01/13/big/img_284 -2002/08/13/big/img_887 -2002/08/24/big/img_849 -2002/07/30/big/img_345 -2002/08/18/big/img_419 -2002/08/01/big/img_1347 -2002/08/05/big/img_3670 -2002/07/21/big/img_479 -2002/08/08/big/img_913 -2002/09/02/big/img_15828 -2002/08/30/big/img_18194 -2002/08/08/big/img_471 -2002/08/22/big/img_734 -2002/08/09/big/img_586 -2002/08/09/big/img_454 -2002/07/29/big/img_47 -2002/07/19/big/img_381 -2002/07/29/big/img_733 -2002/08/20/big/img_327 -2002/07/21/big/img_96 -2002/08/06/big/img_2680 -2002/07/25/big/img_919 -2002/07/21/big/img_158 -2002/07/22/big/img_801 -2002/07/22/big/img_567 -2002/07/24/big/img_804 -2002/07/24/big/img_690 -2003/01/15/big/img_576 -2002/08/14/big/img_335 -2003/01/13/big/img_390 -2002/08/11/big/img_258 -2002/07/23/big/img_917 -2002/08/15/big/img_525 -2003/01/15/big/img_505 -2002/07/30/big/img_886 -2003/01/16/big/img_640 -2003/01/14/big/img_642 -2003/01/17/big/img_844 -2002/08/04/big/img_571 -2002/08/29/big/img_18702 -2003/01/15/big/img_240 -2002/07/29/big/img_553 -2002/08/10/big/img_354 -2002/08/18/big/img_17 -2003/01/15/big/img_782 -2002/07/27/big/img_382 -2002/08/14/big/img_970 -2003/01/16/big/img_70 -2003/01/16/big/img_625 -2002/08/18/big/img_341 -2002/08/26/big/img_188 -2002/08/09/big/img_405 -2002/08/02/big/img_37 -2002/08/13/big/img_748 -2002/07/22/big/img_399 -2002/07/25/big/img_844 -2002/08/12/big/img_340 -2003/01/13/big/img_815 -2002/08/26/big/img_5 -2002/08/10/big/img_158 -2002/08/18/big/img_95 -2002/07/29/big/img_1297 -2003/01/13/big/img_508 -2002/09/01/big/img_16680 -2003/01/16/big/img_338 -2002/08/13/big/img_517 -2002/07/22/big/img_626 -2002/08/06/big/img_3024 -2002/07/26/big/img_499 -2003/01/13/big/img_387 -2002/08/31/big/img_18025 -2002/08/13/big/img_520 -2003/01/16/big/img_576 -2002/07/26/big/img_121 -2002/08/25/big/img_703 -2002/08/26/big/img_615 -2002/08/17/big/img_434 -2002/08/02/big/img_677 -2002/08/18/big/img_276 -2002/08/05/big/img_3672 -2002/07/26/big/img_700 -2002/07/31/big/img_277 -2003/01/14/big/img_220 -2002/08/23/big/img_232 -2002/08/31/big/img_17422 -2002/07/22/big/img_508 -2002/08/13/big/img_681 -2003/01/15/big/img_638 -2002/08/30/big/img_18408 -2003/01/14/big/img_533 -2003/01/17/big/img_12 -2002/08/28/big/img_19388 -2002/08/08/big/img_133 -2002/07/26/big/img_885 -2002/08/19/big/img_387 -2002/08/27/big/img_19976 -2002/08/26/big/img_118 -2002/08/28/big/img_19146 -2002/08/05/big/img_3259 -2002/08/15/big/img_536 -2002/07/22/big/img_279 -2002/07/22/big/img_9 -2002/08/13/big/img_301 -2002/08/15/big/img_974 -2002/08/06/big/img_2355 -2002/08/01/big/img_1526 -2002/08/03/big/img_417 -2002/08/04/big/img_407 -2002/08/15/big/img_1029 -2002/07/29/big/img_700 -2002/08/01/big/img_1463 -2002/08/31/big/img_17365 -2002/07/28/big/img_223 -2002/07/19/big/img_827 -2002/07/27/big/img_531 -2002/07/19/big/img_845 -2002/08/20/big/img_382 -2002/07/31/big/img_268 -2002/08/27/big/img_19705 -2002/08/02/big/img_830 -2002/08/23/big/img_250 -2002/07/20/big/img_777 -2002/08/21/big/img_879 -2002/08/26/big/img_20146 -2002/08/23/big/img_789 -2002/08/06/big/img_2683 -2002/08/25/big/img_576 -2002/08/09/big/img_498 -2002/08/08/big/img_384 -2002/08/26/big/img_592 -2002/07/29/big/img_1470 -2002/08/21/big/img_452 -2002/08/30/big/img_18395 -2002/08/15/big/img_215 -2002/07/21/big/img_643 -2002/07/22/big/img_209 -2003/01/17/big/img_346 -2002/08/25/big/img_658 -2002/08/21/big/img_221 -2002/08/14/big/img_60 -2003/01/17/big/img_885 -2003/01/16/big/img_482 -2002/08/19/big/img_593 -2002/08/08/big/img_233 -2002/07/30/big/img_458 -2002/07/23/big/img_384 -2003/01/15/big/img_670 -2003/01/15/big/img_267 -2002/08/26/big/img_540 -2002/07/29/big/img_552 -2002/07/30/big/img_997 -2003/01/17/big/img_377 -2002/08/21/big/img_265 -2002/08/09/big/img_561 -2002/07/31/big/img_945 -2002/09/02/big/img_15252 -2002/08/11/big/img_276 -2002/07/22/big/img_491 -2002/07/26/big/img_517 -2002/08/14/big/img_726 -2002/08/08/big/img_46 -2002/08/28/big/img_19458 -2002/08/06/big/img_2935 -2002/07/29/big/img_1392 -2002/08/13/big/img_776 -2002/08/24/big/img_616 -2002/08/14/big/img_1065 -2002/07/29/big/img_889 -2002/08/18/big/img_188 -2002/08/07/big/img_1453 -2002/08/02/big/img_760 -2002/07/28/big/img_416 -2002/08/07/big/img_1393 -2002/08/26/big/img_292 -2002/08/26/big/img_301 -2003/01/13/big/img_195 -2002/07/26/big/img_532 -2002/08/20/big/img_550 -2002/08/05/big/img_3658 -2002/08/26/big/img_738 -2002/09/02/big/img_15750 -2003/01/17/big/img_451 -2002/07/23/big/img_339 -2002/08/16/big/img_637 -2002/08/14/big/img_748 -2002/08/06/big/img_2739 -2002/07/25/big/img_482 -2002/08/19/big/img_191 -2002/08/26/big/img_537 -2003/01/15/big/img_716 -2003/01/15/big/img_767 -2002/08/02/big/img_452 -2002/08/08/big/img_1011 -2002/08/10/big/img_144 -2003/01/14/big/img_122 -2002/07/24/big/img_586 -2002/07/24/big/img_762 -2002/08/20/big/img_369 -2002/07/30/big/img_146 -2002/08/23/big/img_396 -2003/01/15/big/img_200 -2002/08/15/big/img_1183 -2003/01/14/big/img_698 -2002/08/09/big/img_792 -2002/08/06/big/img_2347 -2002/07/31/big/img_911 -2002/08/26/big/img_722 -2002/08/23/big/img_621 -2002/08/05/big/img_3790 -2003/01/13/big/img_633 -2002/08/09/big/img_224 -2002/07/24/big/img_454 -2002/07/21/big/img_202 -2002/08/02/big/img_630 -2002/08/30/big/img_18315 -2002/07/19/big/img_491 -2002/09/01/big/img_16456 -2002/08/09/big/img_242 -2002/07/25/big/img_595 -2002/07/22/big/img_522 -2002/08/01/big/img_1593 -2002/07/29/big/img_336 -2002/08/15/big/img_448 -2002/08/28/big/img_19281 -2002/07/29/big/img_342 -2002/08/12/big/img_78 -2003/01/14/big/img_525 -2002/07/28/big/img_147 -2002/08/11/big/img_353 -2002/08/22/big/img_513 -2002/08/04/big/img_721 -2002/08/17/big/img_247 -2003/01/14/big/img_891 -2002/08/20/big/img_853 -2002/07/19/big/img_414 -2002/08/01/big/img_1530 -2003/01/14/big/img_924 -2002/08/22/big/img_468 -2002/08/18/big/img_354 -2002/08/30/big/img_18193 -2002/08/23/big/img_492 -2002/08/15/big/img_871 -2002/08/12/big/img_494 -2002/08/06/big/img_2470 -2002/07/23/big/img_923 -2002/08/26/big/img_155 -2002/08/08/big/img_669 -2002/07/23/big/img_404 -2002/08/28/big/img_19421 -2002/08/29/big/img_18993 -2002/08/25/big/img_416 -2003/01/17/big/img_434 -2002/07/29/big/img_1370 -2002/07/28/big/img_483 -2002/08/11/big/img_50 -2002/08/10/big/img_404 -2002/09/02/big/img_15057 -2003/01/14/big/img_911 -2002/09/01/big/img_16697 -2003/01/16/big/img_665 -2002/09/01/big/img_16708 -2002/08/22/big/img_612 -2002/08/28/big/img_19471 -2002/08/02/big/img_198 -2003/01/16/big/img_527 -2002/08/22/big/img_209 -2002/08/30/big/img_18205 -2003/01/14/big/img_114 -2003/01/14/big/img_1028 -2003/01/16/big/img_894 -2003/01/14/big/img_837 -2002/07/30/big/img_9 -2002/08/06/big/img_2821 -2002/08/04/big/img_85 -2003/01/13/big/img_884 -2002/07/22/big/img_570 -2002/08/07/big/img_1773 -2002/07/26/big/img_208 -2003/01/17/big/img_946 -2002/07/19/big/img_930 -2003/01/01/big/img_698 -2003/01/17/big/img_612 -2002/07/19/big/img_372 -2002/07/30/big/img_721 -2003/01/14/big/img_649 -2002/08/19/big/img_4 -2002/07/25/big/img_1024 -2003/01/15/big/img_601 -2002/08/30/big/img_18470 -2002/07/22/big/img_29 -2002/08/07/big/img_1686 -2002/07/20/big/img_294 -2002/08/14/big/img_800 -2002/08/19/big/img_353 -2002/08/19/big/img_350 -2002/08/05/big/img_3392 -2002/08/09/big/img_622 -2003/01/15/big/img_236 -2002/08/11/big/img_643 -2002/08/05/big/img_3458 -2002/08/12/big/img_413 -2002/08/22/big/img_415 -2002/08/13/big/img_635 -2002/08/07/big/img_1198 -2002/08/04/big/img_873 -2002/08/12/big/img_407 -2003/01/15/big/img_346 -2002/08/02/big/img_275 -2002/08/17/big/img_997 -2002/08/21/big/img_958 -2002/08/20/big/img_579 -2002/07/29/big/img_142 -2003/01/14/big/img_1115 -2002/08/16/big/img_365 -2002/07/29/big/img_1414 -2002/08/17/big/img_489 -2002/08/13/big/img_1010 -2002/07/31/big/img_276 -2002/07/25/big/img_1000 -2002/08/23/big/img_524 -2002/08/28/big/img_19147 -2003/01/13/big/img_433 -2002/08/20/big/img_205 -2003/01/01/big/img_458 -2002/07/29/big/img_1449 -2003/01/16/big/img_696 -2002/08/28/big/img_19296 -2002/08/29/big/img_18688 -2002/08/21/big/img_767 -2002/08/20/big/img_532 -2002/08/26/big/img_187 -2002/07/26/big/img_183 -2002/07/27/big/img_890 -2003/01/13/big/img_576 -2002/07/30/big/img_15 -2002/07/31/big/img_889 -2002/08/31/big/img_17759 -2003/01/14/big/img_1114 -2002/07/19/big/img_445 -2002/08/03/big/img_593 -2002/07/24/big/img_750 -2002/07/30/big/img_133 -2002/08/25/big/img_671 -2002/07/20/big/img_351 -2002/08/31/big/img_17276 -2002/08/05/big/img_3231 -2002/09/02/big/img_15882 -2002/08/14/big/img_115 -2002/08/02/big/img_1148 -2002/07/25/big/img_936 -2002/07/31/big/img_639 -2002/08/04/big/img_427 -2002/08/22/big/img_843 -2003/01/17/big/img_17 -2003/01/13/big/img_690 -2002/08/13/big/img_472 -2002/08/09/big/img_425 -2002/08/05/big/img_3450 -2003/01/17/big/img_439 -2002/08/13/big/img_539 -2002/07/28/big/img_35 -2002/08/16/big/img_241 -2002/08/06/big/img_2898 -2003/01/16/big/img_429 -2002/08/05/big/img_3817 -2002/08/27/big/img_19919 -2002/07/19/big/img_422 -2002/08/15/big/img_560 -2002/07/23/big/img_750 -2002/07/30/big/img_353 -2002/08/05/big/img_43 -2002/08/23/big/img_305 -2002/08/01/big/img_2137 -2002/08/30/big/img_18097 -2002/08/01/big/img_1389 -2002/08/02/big/img_308 -2003/01/14/big/img_652 -2002/08/01/big/img_1798 -2003/01/14/big/img_732 -2003/01/16/big/img_294 -2002/08/26/big/img_213 -2002/07/24/big/img_842 -2003/01/13/big/img_630 -2003/01/13/big/img_634 -2002/08/06/big/img_2285 -2002/08/01/big/img_2162 -2002/08/30/big/img_18134 -2002/08/02/big/img_1045 -2002/08/01/big/img_2143 -2002/07/25/big/img_135 -2002/07/20/big/img_645 -2002/08/05/big/img_3666 -2002/08/14/big/img_523 -2002/08/04/big/img_425 -2003/01/14/big/img_137 -2003/01/01/big/img_176 -2002/08/15/big/img_505 -2002/08/24/big/img_386 -2002/08/05/big/img_3187 -2002/08/15/big/img_419 -2003/01/13/big/img_520 -2002/08/04/big/img_444 -2002/08/26/big/img_483 -2002/08/05/big/img_3449 -2002/08/30/big/img_18409 -2002/08/28/big/img_19455 -2002/08/27/big/img_20090 -2002/07/23/big/img_625 -2002/08/24/big/img_205 -2002/08/08/big/img_938 -2003/01/13/big/img_527 -2002/08/07/big/img_1712 -2002/07/24/big/img_801 -2002/08/09/big/img_579 -2003/01/14/big/img_41 -2003/01/15/big/img_1130 -2002/07/21/big/img_672 -2002/08/07/big/img_1590 -2003/01/01/big/img_532 -2002/08/02/big/img_529 -2002/08/05/big/img_3591 -2002/08/23/big/img_5 -2003/01/14/big/img_882 -2002/08/28/big/img_19234 -2002/07/24/big/img_398 -2003/01/14/big/img_592 -2002/08/22/big/img_548 -2002/08/12/big/img_761 -2003/01/16/big/img_497 -2002/08/18/big/img_133 -2002/08/08/big/img_874 -2002/07/19/big/img_247 -2002/08/15/big/img_170 -2002/08/27/big/img_19679 -2002/08/20/big/img_246 -2002/08/24/big/img_358 -2002/07/29/big/img_599 -2002/08/01/big/img_1555 -2002/07/30/big/img_491 -2002/07/30/big/img_371 -2003/01/16/big/img_682 -2002/07/25/big/img_619 -2003/01/15/big/img_587 -2002/08/02/big/img_1212 -2002/08/01/big/img_2152 -2002/07/25/big/img_668 -2003/01/16/big/img_574 -2002/08/28/big/img_19464 -2002/08/11/big/img_536 -2002/07/24/big/img_201 -2002/08/05/big/img_3488 -2002/07/25/big/img_887 -2002/07/22/big/img_789 -2002/07/30/big/img_432 -2002/08/16/big/img_166 -2002/09/01/big/img_16333 -2002/07/26/big/img_1010 -2002/07/21/big/img_793 -2002/07/22/big/img_720 -2002/07/31/big/img_337 -2002/07/27/big/img_185 -2002/08/23/big/img_440 -2002/07/31/big/img_801 -2002/07/25/big/img_478 -2003/01/14/big/img_171 -2002/08/07/big/img_1054 -2002/09/02/big/img_15659 -2002/07/29/big/img_1348 -2002/08/09/big/img_337 -2002/08/26/big/img_684 -2002/07/31/big/img_537 -2002/08/15/big/img_808 -2003/01/13/big/img_740 -2002/08/07/big/img_1667 -2002/08/03/big/img_404 -2002/08/06/big/img_2520 -2002/07/19/big/img_230 -2002/07/19/big/img_356 -2003/01/16/big/img_627 -2002/08/04/big/img_474 -2002/07/29/big/img_833 -2002/07/25/big/img_176 -2002/08/01/big/img_1684 -2002/08/21/big/img_643 -2002/08/27/big/img_19673 -2002/08/02/big/img_838 -2002/08/06/big/img_2378 -2003/01/15/big/img_48 -2002/07/30/big/img_470 -2002/08/15/big/img_963 -2002/08/24/big/img_444 -2002/08/16/big/img_662 -2002/08/15/big/img_1209 -2002/07/24/big/img_25 -2002/08/06/big/img_2740 -2002/07/29/big/img_996 -2002/08/31/big/img_18074 -2002/08/04/big/img_343 -2003/01/17/big/img_509 -2003/01/13/big/img_726 -2002/08/07/big/img_1466 -2002/07/26/big/img_307 -2002/08/10/big/img_598 -2002/08/13/big/img_890 -2002/08/14/big/img_997 -2002/07/19/big/img_392 -2002/08/02/big/img_475 -2002/08/29/big/img_19038 -2002/07/29/big/img_538 -2002/07/29/big/img_502 -2002/08/02/big/img_364 -2002/08/31/big/img_17353 -2002/08/08/big/img_539 -2002/08/01/big/img_1449 -2002/07/22/big/img_363 -2002/08/02/big/img_90 -2002/09/01/big/img_16867 -2002/08/05/big/img_3371 -2002/07/30/big/img_342 -2002/08/07/big/img_1363 -2002/08/22/big/img_790 -2003/01/15/big/img_404 -2002/08/05/big/img_3447 -2002/09/01/big/img_16167 -2003/01/13/big/img_840 -2002/08/22/big/img_1001 -2002/08/09/big/img_431 -2002/07/27/big/img_618 -2002/07/31/big/img_741 -2002/07/30/big/img_964 -2002/07/25/big/img_86 -2002/07/29/big/img_275 -2002/08/21/big/img_921 -2002/07/26/big/img_892 -2002/08/21/big/img_663 -2003/01/13/big/img_567 -2003/01/14/big/img_719 -2002/07/28/big/img_251 -2003/01/15/big/img_1123 -2002/07/29/big/img_260 -2002/08/24/big/img_337 -2002/08/01/big/img_1914 -2002/08/13/big/img_373 -2003/01/15/big/img_589 -2002/08/13/big/img_906 -2002/07/26/big/img_270 -2002/08/26/big/img_313 -2002/08/25/big/img_694 -2003/01/01/big/img_327 -2002/07/23/big/img_261 -2002/08/26/big/img_642 -2002/07/29/big/img_918 -2002/07/23/big/img_455 -2002/07/24/big/img_612 -2002/07/23/big/img_534 -2002/07/19/big/img_534 -2002/07/19/big/img_726 -2002/08/01/big/img_2146 -2002/08/02/big/img_543 -2003/01/16/big/img_777 -2002/07/30/big/img_484 -2002/08/13/big/img_1161 -2002/07/21/big/img_390 -2002/08/06/big/img_2288 -2002/08/21/big/img_677 -2002/08/13/big/img_747 -2002/08/15/big/img_1248 -2002/07/31/big/img_416 -2002/09/02/big/img_15259 -2002/08/16/big/img_781 -2002/08/24/big/img_754 -2002/07/24/big/img_803 -2002/08/20/big/img_609 -2002/08/28/big/img_19571 -2002/09/01/big/img_16140 -2002/08/26/big/img_769 -2002/07/20/big/img_588 -2002/08/02/big/img_898 -2002/07/21/big/img_466 -2002/08/14/big/img_1046 -2002/07/25/big/img_212 -2002/08/26/big/img_353 -2002/08/19/big/img_810 -2002/08/31/big/img_17824 -2002/08/12/big/img_631 -2002/07/19/big/img_828 -2002/07/24/big/img_130 -2002/08/25/big/img_580 -2002/07/31/big/img_699 -2002/07/23/big/img_808 -2002/07/31/big/img_377 -2003/01/16/big/img_570 -2002/09/01/big/img_16254 -2002/07/21/big/img_471 -2002/08/01/big/img_1548 -2002/08/18/big/img_252 -2002/08/19/big/img_576 -2002/08/20/big/img_464 -2002/07/27/big/img_735 -2002/08/21/big/img_589 -2003/01/15/big/img_1192 -2002/08/09/big/img_302 -2002/07/31/big/img_594 -2002/08/23/big/img_19 -2002/08/29/big/img_18819 -2002/08/19/big/img_293 -2002/07/30/big/img_331 -2002/08/23/big/img_607 -2002/07/30/big/img_363 -2002/08/16/big/img_766 -2003/01/13/big/img_481 -2002/08/06/big/img_2515 -2002/09/02/big/img_15913 -2002/09/02/big/img_15827 -2002/09/02/big/img_15053 -2002/08/07/big/img_1576 -2002/07/23/big/img_268 -2002/08/21/big/img_152 -2003/01/15/big/img_578 -2002/07/21/big/img_589 -2002/07/20/big/img_548 -2002/08/27/big/img_19693 -2002/08/31/big/img_17252 -2002/07/31/big/img_138 -2002/07/23/big/img_372 -2002/08/16/big/img_695 -2002/07/27/big/img_287 -2002/08/15/big/img_315 -2002/08/10/big/img_361 -2002/07/29/big/img_899 -2002/08/13/big/img_771 -2002/08/21/big/img_92 -2003/01/15/big/img_425 -2003/01/16/big/img_450 -2002/09/01/big/img_16942 -2002/08/02/big/img_51 -2002/09/02/big/img_15379 -2002/08/24/big/img_147 -2002/08/30/big/img_18122 -2002/07/26/big/img_950 -2002/08/07/big/img_1400 -2002/08/17/big/img_468 -2002/08/15/big/img_470 -2002/07/30/big/img_318 -2002/07/22/big/img_644 -2002/08/27/big/img_19732 -2002/07/23/big/img_601 -2002/08/26/big/img_398 -2002/08/21/big/img_428 -2002/08/06/big/img_2119 -2002/08/29/big/img_19103 -2003/01/14/big/img_933 -2002/08/11/big/img_674 -2002/08/28/big/img_19420 -2002/08/03/big/img_418 -2002/08/17/big/img_312 -2002/07/25/big/img_1044 -2003/01/17/big/img_671 -2002/08/30/big/img_18297 -2002/07/25/big/img_755 -2002/07/23/big/img_471 -2002/08/21/big/img_39 -2002/07/26/big/img_699 -2003/01/14/big/img_33 -2002/07/31/big/img_411 -2002/08/16/big/img_645 -2003/01/17/big/img_116 -2002/09/02/big/img_15903 -2002/08/20/big/img_120 -2002/08/22/big/img_176 -2002/07/29/big/img_1316 -2002/08/27/big/img_19914 -2002/07/22/big/img_719 -2002/08/28/big/img_19239 -2003/01/13/big/img_385 -2002/08/08/big/img_525 -2002/07/19/big/img_782 -2002/08/13/big/img_843 -2002/07/30/big/img_107 -2002/08/11/big/img_752 -2002/07/29/big/img_383 -2002/08/26/big/img_249 -2002/08/29/big/img_18860 -2002/07/30/big/img_70 -2002/07/26/big/img_194 -2002/08/15/big/img_530 -2002/08/08/big/img_816 -2002/07/31/big/img_286 -2003/01/13/big/img_294 -2002/07/31/big/img_251 -2002/07/24/big/img_13 -2002/08/31/big/img_17938 -2002/07/22/big/img_642 -2003/01/14/big/img_728 -2002/08/18/big/img_47 -2002/08/22/big/img_306 -2002/08/20/big/img_348 -2002/08/15/big/img_764 -2002/08/08/big/img_163 -2002/07/23/big/img_531 -2002/07/23/big/img_467 -2003/01/16/big/img_743 -2003/01/13/big/img_535 -2002/08/02/big/img_523 -2002/08/22/big/img_120 -2002/08/11/big/img_496 -2002/08/29/big/img_19075 -2002/08/08/big/img_465 -2002/08/09/big/img_790 -2002/08/19/big/img_588 -2002/08/23/big/img_407 -2003/01/17/big/img_435 -2002/08/24/big/img_398 -2002/08/27/big/img_19899 -2003/01/15/big/img_335 -2002/08/13/big/img_493 -2002/09/02/big/img_15460 -2002/07/31/big/img_470 -2002/08/05/big/img_3550 -2002/07/28/big/img_123 -2002/08/01/big/img_1498 -2002/08/04/big/img_504 -2003/01/17/big/img_427 -2002/08/27/big/img_19708 -2002/07/27/big/img_861 -2002/07/25/big/img_685 -2002/07/31/big/img_207 -2003/01/14/big/img_745 -2002/08/31/big/img_17756 -2002/08/24/big/img_288 -2002/08/18/big/img_181 -2002/08/10/big/img_520 -2002/08/25/big/img_705 -2002/08/23/big/img_226 -2002/08/04/big/img_727 -2002/07/24/big/img_625 -2002/08/28/big/img_19157 -2002/08/23/big/img_586 -2002/07/31/big/img_232 -2003/01/13/big/img_240 -2003/01/14/big/img_321 -2003/01/15/big/img_533 -2002/07/23/big/img_480 -2002/07/24/big/img_371 -2002/08/21/big/img_702 -2002/08/31/big/img_17075 -2002/09/02/big/img_15278 -2002/07/29/big/img_246 -2003/01/15/big/img_829 -2003/01/15/big/img_1213 -2003/01/16/big/img_441 -2002/08/14/big/img_921 -2002/07/23/big/img_425 -2002/08/15/big/img_296 -2002/07/19/big/img_135 -2002/07/26/big/img_402 -2003/01/17/big/img_88 -2002/08/20/big/img_872 -2002/08/13/big/img_1110 -2003/01/16/big/img_1040 -2002/07/23/big/img_9 -2002/08/13/big/img_700 -2002/08/16/big/img_371 -2002/08/27/big/img_19966 -2003/01/17/big/img_391 -2002/08/18/big/img_426 -2002/08/01/big/img_1618 -2002/07/21/big/img_754 -2003/01/14/big/img_1101 -2003/01/16/big/img_1022 -2002/07/22/big/img_275 -2002/08/24/big/img_86 -2002/08/17/big/img_582 -2003/01/15/big/img_765 -2003/01/17/big/img_449 -2002/07/28/big/img_265 -2003/01/13/big/img_552 -2002/07/28/big/img_115 -2003/01/16/big/img_56 -2002/08/02/big/img_1232 -2003/01/17/big/img_925 -2002/07/22/big/img_445 -2002/07/25/big/img_957 -2002/07/20/big/img_589 -2002/08/31/big/img_17107 -2002/07/29/big/img_483 -2002/08/14/big/img_1063 -2002/08/07/big/img_1545 -2002/08/14/big/img_680 -2002/09/01/big/img_16694 -2002/08/14/big/img_257 -2002/08/11/big/img_726 -2002/07/26/big/img_681 -2002/07/25/big/img_481 -2003/01/14/big/img_737 -2002/08/28/big/img_19480 -2003/01/16/big/img_362 -2002/08/27/big/img_19865 -2003/01/01/big/img_547 -2002/09/02/big/img_15074 -2002/08/01/big/img_1453 -2002/08/22/big/img_594 -2002/08/28/big/img_19263 -2002/08/13/big/img_478 -2002/07/29/big/img_1358 -2003/01/14/big/img_1022 -2002/08/16/big/img_450 -2002/08/02/big/img_159 -2002/07/26/big/img_781 -2003/01/13/big/img_601 -2002/08/20/big/img_407 -2002/08/15/big/img_468 -2002/08/31/big/img_17902 -2002/08/16/big/img_81 -2002/07/25/big/img_987 -2002/07/25/big/img_500 -2002/08/02/big/img_31 -2002/08/18/big/img_538 -2002/08/08/big/img_54 -2002/07/23/big/img_686 -2002/07/24/big/img_836 -2003/01/17/big/img_734 -2002/08/16/big/img_1055 -2003/01/16/big/img_521 -2002/07/25/big/img_612 -2002/08/22/big/img_778 -2002/08/03/big/img_251 -2002/08/12/big/img_436 -2002/08/23/big/img_705 -2002/07/28/big/img_243 -2002/07/25/big/img_1029 -2002/08/20/big/img_287 -2002/08/29/big/img_18739 -2002/08/05/big/img_3272 -2002/07/27/big/img_214 -2003/01/14/big/img_5 -2002/08/01/big/img_1380 -2002/08/29/big/img_19097 -2002/07/30/big/img_486 -2002/08/29/big/img_18707 -2002/08/10/big/img_559 -2002/08/15/big/img_365 -2002/08/09/big/img_525 -2002/08/10/big/img_689 -2002/07/25/big/img_502 -2002/08/03/big/img_667 -2002/08/10/big/img_855 -2002/08/10/big/img_706 -2002/08/18/big/img_603 -2003/01/16/big/img_1055 -2002/08/31/big/img_17890 -2002/08/15/big/img_761 -2003/01/15/big/img_489 -2002/08/26/big/img_351 -2002/08/01/big/img_1772 -2002/08/31/big/img_17729 -2002/07/25/big/img_609 -2003/01/13/big/img_539 -2002/07/27/big/img_686 -2002/07/31/big/img_311 -2002/08/22/big/img_799 -2003/01/16/big/img_936 -2002/08/31/big/img_17813 -2002/08/04/big/img_862 -2002/08/09/big/img_332 -2002/07/20/big/img_148 -2002/08/12/big/img_426 -2002/07/24/big/img_69 -2002/07/27/big/img_685 -2002/08/02/big/img_480 -2002/08/26/big/img_154 -2002/07/24/big/img_598 -2002/08/01/big/img_1881 -2002/08/20/big/img_667 -2003/01/14/big/img_495 -2002/07/21/big/img_744 -2002/07/30/big/img_150 -2002/07/23/big/img_924 -2002/08/08/big/img_272 -2002/07/23/big/img_310 -2002/07/25/big/img_1011 -2002/09/02/big/img_15725 -2002/07/19/big/img_814 -2002/08/20/big/img_936 -2002/07/25/big/img_85 -2002/08/24/big/img_662 -2002/08/09/big/img_495 -2003/01/15/big/img_196 -2002/08/16/big/img_707 -2002/08/28/big/img_19370 -2002/08/06/big/img_2366 -2002/08/06/big/img_3012 -2002/08/01/big/img_1452 -2002/07/31/big/img_742 -2002/07/27/big/img_914 -2003/01/13/big/img_290 -2002/07/31/big/img_288 -2002/08/02/big/img_171 -2002/08/22/big/img_191 -2002/07/27/big/img_1066 -2002/08/12/big/img_383 -2003/01/17/big/img_1018 -2002/08/01/big/img_1785 -2002/08/11/big/img_390 -2002/08/27/big/img_20037 -2002/08/12/big/img_38 -2003/01/15/big/img_103 -2002/08/26/big/img_31 -2002/08/18/big/img_660 -2002/07/22/big/img_694 -2002/08/15/big/img_24 -2002/07/27/big/img_1077 -2002/08/01/big/img_1943 -2002/07/22/big/img_292 -2002/09/01/big/img_16857 -2002/07/22/big/img_892 -2003/01/14/big/img_46 -2002/08/09/big/img_469 -2002/08/09/big/img_414 -2003/01/16/big/img_40 -2002/08/28/big/img_19231 -2002/07/27/big/img_978 -2002/07/23/big/img_475 -2002/07/25/big/img_92 -2002/08/09/big/img_799 -2002/07/25/big/img_491 -2002/08/03/big/img_654 -2003/01/15/big/img_687 -2002/08/11/big/img_478 -2002/08/07/big/img_1664 -2002/08/20/big/img_362 -2002/08/01/big/img_1298 -2003/01/13/big/img_500 -2002/08/06/big/img_2896 -2002/08/30/big/img_18529 -2002/08/16/big/img_1020 -2002/07/29/big/img_892 -2002/08/29/big/img_18726 -2002/07/21/big/img_453 -2002/08/17/big/img_437 -2002/07/19/big/img_665 -2002/07/22/big/img_440 -2002/07/19/big/img_582 -2002/07/21/big/img_233 -2003/01/01/big/img_82 -2002/07/25/big/img_341 -2002/07/29/big/img_864 -2002/08/02/big/img_276 -2002/08/29/big/img_18654 -2002/07/27/big/img_1024 -2002/08/19/big/img_373 -2003/01/15/big/img_241 -2002/07/25/big/img_84 -2002/08/13/big/img_834 -2002/08/10/big/img_511 -2002/08/01/big/img_1627 -2002/08/08/big/img_607 -2002/08/06/big/img_2083 -2002/08/01/big/img_1486 -2002/08/08/big/img_700 -2002/08/01/big/img_1954 -2002/08/21/big/img_54 -2002/07/30/big/img_847 -2002/08/28/big/img_19169 -2002/07/21/big/img_549 -2002/08/03/big/img_693 -2002/07/31/big/img_1002 -2003/01/14/big/img_1035 -2003/01/16/big/img_622 -2002/07/30/big/img_1201 -2002/08/10/big/img_444 -2002/07/31/big/img_374 -2002/08/21/big/img_301 -2002/08/13/big/img_1095 -2003/01/13/big/img_288 -2002/07/25/big/img_232 -2003/01/13/big/img_967 -2002/08/26/big/img_360 -2002/08/05/big/img_67 -2002/08/29/big/img_18969 -2002/07/28/big/img_16 -2002/08/16/big/img_515 -2002/07/20/big/img_708 -2002/08/18/big/img_178 -2003/01/15/big/img_509 -2002/07/25/big/img_430 -2002/08/21/big/img_738 -2002/08/16/big/img_886 -2002/09/02/big/img_15605 -2002/09/01/big/img_16242 -2002/08/24/big/img_711 -2002/07/25/big/img_90 -2002/08/09/big/img_491 -2002/07/30/big/img_534 -2003/01/13/big/img_474 -2002/08/25/big/img_510 -2002/08/15/big/img_555 -2002/08/02/big/img_775 -2002/07/23/big/img_975 -2002/08/19/big/img_229 -2003/01/17/big/img_860 -2003/01/02/big/img_10 -2002/07/23/big/img_542 -2002/08/06/big/img_2535 -2002/07/22/big/img_37 -2002/08/06/big/img_2342 -2002/08/25/big/img_515 -2002/08/25/big/img_336 -2002/08/18/big/img_837 -2002/08/21/big/img_616 -2003/01/17/big/img_24 -2002/07/26/big/img_936 -2002/08/14/big/img_896 -2002/07/29/big/img_465 -2002/07/31/big/img_543 -2002/08/01/big/img_1411 -2002/08/02/big/img_423 -2002/08/21/big/img_44 -2002/07/31/big/img_11 -2003/01/15/big/img_628 -2003/01/15/big/img_605 -2002/07/30/big/img_571 -2002/07/23/big/img_428 -2002/08/15/big/img_942 -2002/07/26/big/img_531 -2003/01/16/big/img_59 -2002/08/02/big/img_410 -2002/07/31/big/img_230 -2002/08/19/big/img_806 -2003/01/14/big/img_462 -2002/08/16/big/img_370 -2002/08/13/big/img_380 -2002/08/16/big/img_932 -2002/07/19/big/img_393 -2002/08/20/big/img_764 -2002/08/15/big/img_616 -2002/07/26/big/img_267 -2002/07/27/big/img_1069 -2002/08/14/big/img_1041 -2003/01/13/big/img_594 -2002/09/01/big/img_16845 -2002/08/09/big/img_229 -2003/01/16/big/img_639 -2002/08/19/big/img_398 -2002/08/18/big/img_978 -2002/08/24/big/img_296 -2002/07/29/big/img_415 -2002/07/30/big/img_923 -2002/08/18/big/img_575 -2002/08/22/big/img_182 -2002/07/25/big/img_806 -2002/07/22/big/img_49 -2002/07/29/big/img_989 -2003/01/17/big/img_789 -2003/01/15/big/img_503 -2002/09/01/big/img_16062 -2003/01/17/big/img_794 -2002/08/15/big/img_564 -2003/01/15/big/img_222 -2002/08/01/big/img_1656 -2003/01/13/big/img_432 -2002/07/19/big/img_426 -2002/08/17/big/img_244 -2002/08/13/big/img_805 -2002/09/02/big/img_15067 -2002/08/11/big/img_58 -2002/08/22/big/img_636 -2002/07/22/big/img_416 -2002/08/13/big/img_836 -2002/08/26/big/img_363 -2002/07/30/big/img_917 -2003/01/14/big/img_206 -2002/08/12/big/img_311 -2002/08/31/big/img_17623 -2002/07/29/big/img_661 -2003/01/13/big/img_417 -2002/08/02/big/img_463 -2002/08/02/big/img_669 -2002/08/26/big/img_670 -2002/08/02/big/img_375 -2002/07/19/big/img_209 -2002/08/08/big/img_115 -2002/08/21/big/img_399 -2002/08/20/big/img_911 -2002/08/07/big/img_1212 -2002/08/20/big/img_578 -2002/08/22/big/img_554 -2002/08/21/big/img_484 -2002/07/25/big/img_450 -2002/08/03/big/img_542 -2002/08/15/big/img_561 -2002/07/23/big/img_360 -2002/08/30/big/img_18137 -2002/07/25/big/img_250 -2002/08/03/big/img_647 -2002/08/20/big/img_375 -2002/08/14/big/img_387 -2002/09/01/big/img_16990 -2002/08/28/big/img_19341 -2003/01/15/big/img_239 -2002/08/20/big/img_528 -2002/08/12/big/img_130 -2002/09/02/big/img_15108 -2003/01/15/big/img_372 -2002/08/16/big/img_678 -2002/08/04/big/img_623 -2002/07/23/big/img_477 -2002/08/28/big/img_19590 -2003/01/17/big/img_978 -2002/09/01/big/img_16692 -2002/07/20/big/img_109 -2002/08/06/big/img_2660 -2003/01/14/big/img_464 -2002/08/09/big/img_618 -2002/07/22/big/img_722 -2002/08/25/big/img_419 -2002/08/03/big/img_314 -2002/08/25/big/img_40 -2002/07/27/big/img_430 -2002/08/10/big/img_569 -2002/08/23/big/img_398 -2002/07/23/big/img_893 -2002/08/16/big/img_261 -2002/08/06/big/img_2668 -2002/07/22/big/img_835 -2002/09/02/big/img_15093 -2003/01/16/big/img_65 -2002/08/21/big/img_448 -2003/01/14/big/img_351 -2003/01/17/big/img_133 -2002/07/28/big/img_493 -2003/01/15/big/img_640 -2002/09/01/big/img_16880 -2002/08/15/big/img_350 -2002/08/20/big/img_624 -2002/08/25/big/img_604 -2002/08/06/big/img_2200 -2002/08/23/big/img_290 -2002/08/13/big/img_1152 -2003/01/14/big/img_251 -2002/08/02/big/img_538 -2002/08/22/big/img_613 -2003/01/13/big/img_351 -2002/08/18/big/img_368 -2002/07/23/big/img_392 -2002/07/25/big/img_198 -2002/07/25/big/img_418 -2002/08/26/big/img_614 -2002/07/23/big/img_405 -2003/01/14/big/img_445 -2002/07/25/big/img_326 -2002/08/10/big/img_734 -2003/01/14/big/img_530 -2002/08/08/big/img_561 -2002/08/29/big/img_18990 -2002/08/10/big/img_576 -2002/07/29/big/img_1494 -2002/07/19/big/img_198 -2002/08/10/big/img_562 -2002/07/22/big/img_901 -2003/01/14/big/img_37 -2002/09/02/big/img_15629 -2003/01/14/big/img_58 -2002/08/01/big/img_1364 -2002/07/27/big/img_636 -2003/01/13/big/img_241 -2002/09/01/big/img_16988 -2003/01/13/big/img_560 -2002/08/09/big/img_533 -2002/07/31/big/img_249 -2003/01/17/big/img_1007 -2002/07/21/big/img_64 -2003/01/13/big/img_537 -2003/01/15/big/img_606 -2002/08/18/big/img_651 -2002/08/24/big/img_405 -2002/07/26/big/img_837 -2002/08/09/big/img_562 -2002/08/01/big/img_1983 -2002/08/03/big/img_514 -2002/07/29/big/img_314 -2002/08/12/big/img_493 -2003/01/14/big/img_121 -2003/01/14/big/img_479 -2002/08/04/big/img_410 -2002/07/22/big/img_607 -2003/01/17/big/img_417 -2002/07/20/big/img_547 -2002/08/13/big/img_396 -2002/08/31/big/img_17538 -2002/08/13/big/img_187 -2002/08/12/big/img_328 -2003/01/14/big/img_569 -2002/07/27/big/img_1081 -2002/08/14/big/img_504 -2002/08/23/big/img_785 -2002/07/26/big/img_339 -2002/08/07/big/img_1156 -2002/08/07/big/img_1456 -2002/08/23/big/img_378 -2002/08/27/big/img_19719 -2002/07/31/big/img_39 -2002/07/31/big/img_883 -2003/01/14/big/img_676 -2002/07/29/big/img_214 -2002/07/26/big/img_669 -2002/07/25/big/img_202 -2002/08/08/big/img_259 -2003/01/17/big/img_943 -2003/01/15/big/img_512 -2002/08/05/big/img_3295 -2002/08/27/big/img_19685 -2002/08/08/big/img_277 -2002/08/30/big/img_18154 -2002/07/22/big/img_663 -2002/08/29/big/img_18914 -2002/07/31/big/img_908 -2002/08/27/big/img_19926 -2003/01/13/big/img_791 -2003/01/15/big/img_827 -2002/08/18/big/img_878 -2002/08/14/big/img_670 -2002/07/20/big/img_182 -2002/08/15/big/img_291 -2002/08/06/big/img_2600 -2002/07/23/big/img_587 -2002/08/14/big/img_577 -2003/01/15/big/img_585 -2002/07/30/big/img_310 -2002/08/03/big/img_658 -2002/08/10/big/img_157 -2002/08/19/big/img_811 -2002/07/29/big/img_1318 -2002/08/04/big/img_104 -2002/07/30/big/img_332 -2002/07/24/big/img_789 -2002/07/29/big/img_516 -2002/07/23/big/img_843 -2002/08/01/big/img_1528 -2002/08/13/big/img_798 -2002/08/07/big/img_1729 -2002/08/28/big/img_19448 -2003/01/16/big/img_95 -2002/08/12/big/img_473 -2002/07/27/big/img_269 -2003/01/16/big/img_621 -2002/07/29/big/img_772 -2002/07/24/big/img_171 -2002/07/19/big/img_429 -2002/08/07/big/img_1933 -2002/08/27/big/img_19629 -2002/08/05/big/img_3688 -2002/08/07/big/img_1691 -2002/07/23/big/img_600 -2002/07/29/big/img_666 -2002/08/25/big/img_566 -2002/08/06/big/img_2659 -2002/08/29/big/img_18929 -2002/08/16/big/img_407 -2002/08/18/big/img_774 -2002/08/19/big/img_249 -2002/08/06/big/img_2427 -2002/08/29/big/img_18899 -2002/08/01/big/img_1818 -2002/07/31/big/img_108 -2002/07/29/big/img_500 -2002/08/11/big/img_115 -2002/07/19/big/img_521 -2002/08/02/big/img_1163 -2002/07/22/big/img_62 -2002/08/13/big/img_466 -2002/08/21/big/img_956 -2002/08/23/big/img_602 -2002/08/20/big/img_858 -2002/07/25/big/img_690 -2002/07/19/big/img_130 -2002/08/04/big/img_874 -2002/07/26/big/img_489 -2002/07/22/big/img_548 -2002/08/10/big/img_191 -2002/07/25/big/img_1051 -2002/08/18/big/img_473 -2002/08/12/big/img_755 -2002/08/18/big/img_413 -2002/08/08/big/img_1044 -2002/08/17/big/img_680 -2002/08/26/big/img_235 -2002/08/20/big/img_330 -2002/08/22/big/img_344 -2002/08/09/big/img_593 -2002/07/31/big/img_1006 -2002/08/14/big/img_337 -2002/08/16/big/img_728 -2002/07/24/big/img_834 -2002/08/04/big/img_552 -2002/09/02/big/img_15213 -2002/07/25/big/img_725 -2002/08/30/big/img_18290 -2003/01/01/big/img_475 -2002/07/27/big/img_1083 -2002/08/29/big/img_18955 -2002/08/31/big/img_17232 -2002/08/08/big/img_480 -2002/08/01/big/img_1311 -2002/07/30/big/img_745 -2002/08/03/big/img_649 -2002/08/12/big/img_193 -2002/07/29/big/img_228 -2002/07/25/big/img_836 -2002/08/20/big/img_400 -2002/07/30/big/img_507 -2002/09/02/big/img_15072 -2002/07/26/big/img_658 -2002/07/28/big/img_503 -2002/08/05/big/img_3814 -2002/08/24/big/img_745 -2003/01/13/big/img_817 -2002/08/08/big/img_579 -2002/07/22/big/img_251 -2003/01/13/big/img_689 -2002/07/25/big/img_407 -2002/08/13/big/img_1050 -2002/08/14/big/img_733 -2002/07/24/big/img_82 -2003/01/17/big/img_288 -2003/01/15/big/img_475 -2002/08/14/big/img_620 -2002/08/21/big/img_167 -2002/07/19/big/img_300 -2002/07/26/big/img_219 -2002/08/01/big/img_1468 -2002/07/23/big/img_260 -2002/08/09/big/img_555 -2002/07/19/big/img_160 -2002/08/02/big/img_1060 -2003/01/14/big/img_149 -2002/08/15/big/img_346 -2002/08/24/big/img_597 -2002/08/22/big/img_502 -2002/08/30/big/img_18228 -2002/07/21/big/img_766 -2003/01/15/big/img_841 -2002/07/24/big/img_516 -2002/08/02/big/img_265 -2002/08/15/big/img_1243 -2003/01/15/big/img_223 -2002/08/04/big/img_236 -2002/07/22/big/img_309 -2002/07/20/big/img_656 -2002/07/31/big/img_412 -2002/09/01/big/img_16462 -2003/01/16/big/img_431 -2002/07/22/big/img_793 -2002/08/15/big/img_877 -2002/07/26/big/img_282 -2002/07/25/big/img_529 -2002/08/24/big/img_613 -2003/01/17/big/img_700 -2002/08/06/big/img_2526 -2002/08/24/big/img_394 -2002/08/21/big/img_521 -2002/08/25/big/img_560 -2002/07/29/big/img_966 -2002/07/25/big/img_448 -2003/01/13/big/img_782 -2002/08/21/big/img_296 -2002/09/01/big/img_16755 -2002/08/05/big/img_3552 -2002/09/02/big/img_15823 -2003/01/14/big/img_193 -2002/07/21/big/img_159 -2002/08/02/big/img_564 -2002/08/16/big/img_300 -2002/07/19/big/img_269 -2002/08/13/big/img_676 -2002/07/28/big/img_57 -2002/08/05/big/img_3318 -2002/07/31/big/img_218 -2002/08/21/big/img_898 -2002/07/29/big/img_109 -2002/07/19/big/img_854 -2002/08/23/big/img_311 -2002/08/14/big/img_318 -2002/07/25/big/img_523 -2002/07/21/big/img_678 -2003/01/17/big/img_690 -2002/08/28/big/img_19503 -2002/08/18/big/img_251 -2002/08/22/big/img_672 -2002/08/20/big/img_663 -2002/08/02/big/img_148 -2002/09/02/big/img_15580 -2002/07/25/big/img_778 -2002/08/14/big/img_565 -2002/08/12/big/img_374 -2002/08/13/big/img_1018 -2002/08/20/big/img_474 -2002/08/25/big/img_33 -2002/08/02/big/img_1190 -2002/08/08/big/img_864 -2002/08/14/big/img_1071 -2002/08/30/big/img_18103 -2002/08/18/big/img_533 -2003/01/16/big/img_650 -2002/07/25/big/img_108 -2002/07/26/big/img_81 -2002/07/27/big/img_543 -2002/07/29/big/img_521 -2003/01/13/big/img_434 -2002/08/26/big/img_674 -2002/08/06/big/img_2932 -2002/08/07/big/img_1262 -2003/01/15/big/img_201 -2003/01/16/big/img_673 -2002/09/02/big/img_15988 -2002/07/29/big/img_1306 -2003/01/14/big/img_1072 -2002/08/30/big/img_18232 -2002/08/05/big/img_3711 -2002/07/23/big/img_775 -2002/08/01/big/img_16 -2003/01/16/big/img_630 -2002/08/22/big/img_695 -2002/08/14/big/img_51 -2002/08/14/big/img_782 -2002/08/24/big/img_742 -2003/01/14/big/img_512 -2003/01/15/big/img_1183 -2003/01/15/big/img_714 -2002/08/01/big/img_2078 -2002/07/31/big/img_682 -2002/09/02/big/img_15687 -2002/07/26/big/img_518 -2002/08/27/big/img_19676 -2002/09/02/big/img_15969 -2002/08/02/big/img_931 -2002/08/25/big/img_508 -2002/08/29/big/img_18616 -2002/07/22/big/img_839 -2002/07/28/big/img_313 -2003/01/14/big/img_155 -2002/08/02/big/img_1105 -2002/08/09/big/img_53 -2002/08/16/big/img_469 -2002/08/15/big/img_502 -2002/08/20/big/img_575 -2002/07/25/big/img_138 -2003/01/16/big/img_579 -2002/07/19/big/img_352 -2003/01/14/big/img_762 -2003/01/01/big/img_588 -2002/08/02/big/img_981 -2002/08/21/big/img_447 -2002/09/01/big/img_16151 -2003/01/14/big/img_769 -2002/08/23/big/img_461 -2002/08/17/big/img_240 -2002/09/02/big/img_15220 -2002/07/19/big/img_408 -2002/09/02/big/img_15496 -2002/07/29/big/img_758 -2002/08/28/big/img_19392 -2002/08/06/big/img_2723 -2002/08/31/big/img_17752 -2002/08/23/big/img_469 -2002/08/13/big/img_515 -2002/09/02/big/img_15551 -2002/08/03/big/img_462 -2002/07/24/big/img_613 -2002/07/22/big/img_61 -2002/08/08/big/img_171 -2002/08/21/big/img_177 -2003/01/14/big/img_105 -2002/08/02/big/img_1017 -2002/08/22/big/img_106 -2002/07/27/big/img_542 -2002/07/21/big/img_665 -2002/07/23/big/img_595 -2002/08/04/big/img_657 -2002/08/29/big/img_19002 -2003/01/15/big/img_550 -2002/08/14/big/img_662 -2002/07/20/big/img_425 -2002/08/30/big/img_18528 -2002/07/26/big/img_611 -2002/07/22/big/img_849 -2002/08/07/big/img_1655 -2002/08/21/big/img_638 -2003/01/17/big/img_732 -2003/01/01/big/img_496 -2002/08/18/big/img_713 -2002/08/08/big/img_109 -2002/07/27/big/img_1008 -2002/07/20/big/img_559 -2002/08/16/big/img_699 -2002/08/31/big/img_17702 -2002/07/31/big/img_1013 -2002/08/01/big/img_2027 -2002/08/02/big/img_1001 -2002/08/03/big/img_210 -2002/08/01/big/img_2087 -2003/01/14/big/img_199 -2002/07/29/big/img_48 -2002/07/19/big/img_727 -2002/08/09/big/img_249 -2002/08/04/big/img_632 -2002/08/22/big/img_620 -2003/01/01/big/img_457 -2002/08/05/big/img_3223 -2002/07/27/big/img_240 -2002/07/25/big/img_797 -2002/08/13/big/img_430 -2002/07/25/big/img_615 -2002/08/12/big/img_28 -2002/07/30/big/img_220 -2002/07/24/big/img_89 -2002/08/21/big/img_357 -2002/08/09/big/img_590 -2003/01/13/big/img_525 -2002/08/17/big/img_818 -2003/01/02/big/img_7 -2002/07/26/big/img_636 -2003/01/13/big/img_1122 -2002/07/23/big/img_810 -2002/08/20/big/img_888 -2002/07/27/big/img_3 -2002/08/15/big/img_451 -2002/09/02/big/img_15787 -2002/07/31/big/img_281 -2002/08/05/big/img_3274 -2002/08/07/big/img_1254 -2002/07/31/big/img_27 -2002/08/01/big/img_1366 -2002/07/30/big/img_182 -2002/08/27/big/img_19690 -2002/07/29/big/img_68 -2002/08/23/big/img_754 -2002/07/30/big/img_540 -2002/08/27/big/img_20063 -2002/08/14/big/img_471 -2002/08/02/big/img_615 -2002/07/30/big/img_186 -2002/08/25/big/img_150 -2002/07/27/big/img_626 -2002/07/20/big/img_225 -2003/01/15/big/img_1252 -2002/07/19/big/img_367 -2003/01/15/big/img_582 -2002/08/09/big/img_572 -2002/08/08/big/img_428 -2003/01/15/big/img_639 -2002/08/28/big/img_19245 -2002/07/24/big/img_321 -2002/08/02/big/img_662 -2002/08/08/big/img_1033 -2003/01/17/big/img_867 -2002/07/22/big/img_652 -2003/01/14/big/img_224 -2002/08/18/big/img_49 -2002/07/26/big/img_46 -2002/08/31/big/img_18021 -2002/07/25/big/img_151 -2002/08/23/big/img_540 -2002/08/25/big/img_693 -2002/07/23/big/img_340 -2002/07/28/big/img_117 -2002/09/02/big/img_15768 -2002/08/26/big/img_562 -2002/07/24/big/img_480 -2003/01/15/big/img_341 -2002/08/10/big/img_783 -2002/08/20/big/img_132 -2003/01/14/big/img_370 -2002/07/20/big/img_720 -2002/08/03/big/img_144 -2002/08/20/big/img_538 -2002/08/01/big/img_1745 -2002/08/11/big/img_683 -2002/08/03/big/img_328 -2002/08/10/big/img_793 -2002/08/14/big/img_689 -2002/08/02/big/img_162 -2003/01/17/big/img_411 -2002/07/31/big/img_361 -2002/08/15/big/img_289 -2002/08/08/big/img_254 -2002/08/15/big/img_996 -2002/08/20/big/img_785 -2002/07/24/big/img_511 -2002/08/06/big/img_2614 -2002/08/29/big/img_18733 -2002/08/17/big/img_78 -2002/07/30/big/img_378 -2002/08/31/big/img_17947 -2002/08/26/big/img_88 -2002/07/30/big/img_558 -2002/08/02/big/img_67 -2003/01/14/big/img_325 -2002/07/29/big/img_1357 -2002/07/19/big/img_391 -2002/07/30/big/img_307 -2003/01/13/big/img_219 -2002/07/24/big/img_807 -2002/08/23/big/img_543 -2002/08/29/big/img_18620 -2002/07/22/big/img_769 -2002/08/26/big/img_503 -2002/07/30/big/img_78 -2002/08/14/big/img_1036 -2002/08/09/big/img_58 -2002/07/24/big/img_616 -2002/08/02/big/img_464 -2002/07/26/big/img_576 -2002/07/22/big/img_273 -2003/01/16/big/img_470 -2002/07/29/big/img_329 -2002/07/30/big/img_1086 -2002/07/31/big/img_353 -2002/09/02/big/img_15275 -2003/01/17/big/img_555 -2002/08/26/big/img_212 -2002/08/01/big/img_1692 -2003/01/15/big/img_600 -2002/07/29/big/img_825 -2002/08/08/big/img_68 -2002/08/10/big/img_719 -2002/07/31/big/img_636 -2002/07/29/big/img_325 -2002/07/21/big/img_515 -2002/07/22/big/img_705 -2003/01/13/big/img_818 -2002/08/09/big/img_486 -2002/08/22/big/img_141 -2002/07/22/big/img_303 -2002/08/09/big/img_393 -2002/07/29/big/img_963 -2002/08/02/big/img_1215 -2002/08/19/big/img_674 -2002/08/12/big/img_690 -2002/08/21/big/img_637 -2002/08/21/big/img_841 -2002/08/24/big/img_71 -2002/07/25/big/img_596 -2002/07/24/big/img_864 -2002/08/18/big/img_293 -2003/01/14/big/img_657 -2002/08/15/big/img_411 -2002/08/16/big/img_348 -2002/08/05/big/img_3157 -2002/07/20/big/img_663 -2003/01/13/big/img_654 -2003/01/16/big/img_433 -2002/08/30/big/img_18200 -2002/08/12/big/img_226 -2003/01/16/big/img_491 -2002/08/08/big/img_666 -2002/07/19/big/img_576 -2003/01/15/big/img_776 -2003/01/16/big/img_899 -2002/07/19/big/img_397 -2002/08/14/big/img_44 -2003/01/15/big/img_762 -2002/08/02/big/img_982 -2002/09/02/big/img_15234 -2002/08/17/big/img_556 -2002/08/21/big/img_410 -2002/08/21/big/img_386 -2002/07/19/big/img_690 -2002/08/05/big/img_3052 -2002/08/14/big/img_219 -2002/08/16/big/img_273 -2003/01/15/big/img_752 -2002/08/08/big/img_184 -2002/07/31/big/img_743 -2002/08/23/big/img_338 -2003/01/14/big/img_1055 -2002/08/05/big/img_3405 -2003/01/15/big/img_17 -2002/08/03/big/img_141 -2002/08/14/big/img_549 -2002/07/27/big/img_1034 -2002/07/31/big/img_932 -2002/08/30/big/img_18487 -2002/09/02/big/img_15814 -2002/08/01/big/img_2086 -2002/09/01/big/img_16535 -2002/07/22/big/img_500 -2003/01/13/big/img_400 -2002/08/25/big/img_607 -2002/08/30/big/img_18384 -2003/01/14/big/img_951 -2002/08/13/big/img_1150 -2002/08/08/big/img_1022 -2002/08/10/big/img_428 -2002/08/28/big/img_19242 -2002/08/05/big/img_3098 -2002/07/23/big/img_400 -2002/08/26/big/img_365 -2002/07/20/big/img_318 -2002/08/13/big/img_740 -2003/01/16/big/img_37 -2002/08/26/big/img_274 -2002/08/02/big/img_205 -2002/08/21/big/img_695 -2002/08/06/big/img_2289 -2002/08/20/big/img_794 -2002/08/18/big/img_438 -2002/08/07/big/img_1380 -2002/08/02/big/img_737 -2002/08/07/big/img_1651 -2002/08/15/big/img_1238 -2002/08/01/big/img_1681 -2002/08/06/big/img_3017 -2002/07/23/big/img_706 -2002/07/31/big/img_392 -2002/08/09/big/img_539 -2002/07/29/big/img_835 -2002/08/26/big/img_723 -2002/08/28/big/img_19235 -2003/01/16/big/img_353 -2002/08/10/big/img_150 -2002/08/29/big/img_19025 -2002/08/21/big/img_310 -2002/08/10/big/img_823 -2002/07/26/big/img_981 -2002/08/11/big/img_288 -2002/08/19/big/img_534 -2002/08/21/big/img_300 -2002/07/31/big/img_49 -2002/07/30/big/img_469 -2002/08/28/big/img_19197 -2002/08/25/big/img_205 -2002/08/10/big/img_390 -2002/08/23/big/img_291 -2002/08/26/big/img_230 -2002/08/18/big/img_76 -2002/07/23/big/img_409 -2002/08/14/big/img_1053 -2003/01/14/big/img_291 -2002/08/10/big/img_503 -2002/08/27/big/img_19928 -2002/08/03/big/img_563 -2002/08/17/big/img_250 -2002/08/06/big/img_2381 -2002/08/17/big/img_948 -2002/08/06/big/img_2710 -2002/07/22/big/img_696 -2002/07/31/big/img_670 -2002/08/12/big/img_594 -2002/07/29/big/img_624 -2003/01/17/big/img_934 -2002/08/03/big/img_584 -2002/08/22/big/img_1003 -2002/08/05/big/img_3396 -2003/01/13/big/img_570 -2002/08/02/big/img_219 -2002/09/02/big/img_15774 -2002/08/16/big/img_818 -2002/08/23/big/img_402 -2003/01/14/big/img_552 -2002/07/29/big/img_71 -2002/08/05/big/img_3592 -2002/08/16/big/img_80 -2002/07/27/big/img_672 -2003/01/13/big/img_470 -2003/01/16/big/img_702 -2002/09/01/big/img_16130 -2002/08/08/big/img_240 -2002/09/01/big/img_16338 -2002/07/26/big/img_312 -2003/01/14/big/img_538 -2002/07/20/big/img_695 -2002/08/30/big/img_18098 -2002/08/25/big/img_259 -2002/08/16/big/img_1042 -2002/08/09/big/img_837 -2002/08/31/big/img_17760 -2002/07/31/big/img_14 -2002/08/09/big/img_361 -2003/01/16/big/img_107 -2002/08/14/big/img_124 -2002/07/19/big/img_463 -2003/01/15/big/img_275 -2002/07/25/big/img_1151 -2002/07/29/big/img_1501 -2002/08/27/big/img_19889 -2002/08/29/big/img_18603 -2003/01/17/big/img_601 -2002/08/25/big/img_355 -2002/08/08/big/img_297 -2002/08/20/big/img_290 -2002/07/31/big/img_195 -2003/01/01/big/img_336 -2002/08/18/big/img_369 -2002/07/25/big/img_621 -2002/08/11/big/img_508 -2003/01/14/big/img_458 -2003/01/15/big/img_795 -2002/08/12/big/img_498 -2002/08/01/big/img_1734 -2002/08/02/big/img_246 -2002/08/16/big/img_565 -2002/08/11/big/img_475 -2002/08/22/big/img_408 -2002/07/28/big/img_78 -2002/07/21/big/img_81 -2003/01/14/big/img_697 -2002/08/14/big/img_661 -2002/08/15/big/img_507 -2002/08/19/big/img_55 -2002/07/22/big/img_152 -2003/01/14/big/img_470 -2002/08/03/big/img_379 -2002/08/22/big/img_506 -2003/01/16/big/img_966 -2002/08/18/big/img_698 -2002/08/24/big/img_528 -2002/08/23/big/img_10 -2002/08/01/big/img_1655 -2002/08/22/big/img_953 -2002/07/19/big/img_630 -2002/07/22/big/img_889 -2002/08/16/big/img_351 -2003/01/16/big/img_83 -2002/07/19/big/img_805 -2002/08/14/big/img_704 -2002/07/19/big/img_389 -2002/08/31/big/img_17765 -2002/07/29/big/img_606 -2003/01/17/big/img_939 -2002/09/02/big/img_15081 -2002/08/21/big/img_181 -2002/07/29/big/img_1321 -2002/07/21/big/img_497 -2002/07/20/big/img_539 -2002/08/24/big/img_119 -2002/08/01/big/img_1281 -2002/07/26/big/img_207 -2002/07/26/big/img_432 -2002/07/27/big/img_1006 -2002/08/05/big/img_3087 -2002/08/14/big/img_252 -2002/08/14/big/img_798 -2002/07/24/big/img_538 -2002/09/02/big/img_15507 -2002/08/08/big/img_901 -2003/01/14/big/img_557 -2002/08/07/big/img_1819 -2002/08/04/big/img_470 -2002/08/01/big/img_1504 -2002/08/16/big/img_1070 -2002/08/16/big/img_372 -2002/08/23/big/img_416 -2002/08/30/big/img_18208 -2002/08/01/big/img_2043 -2002/07/22/big/img_385 -2002/08/22/big/img_466 -2002/08/21/big/img_869 -2002/08/28/big/img_19429 -2002/08/02/big/img_770 -2002/07/23/big/img_433 -2003/01/14/big/img_13 -2002/07/27/big/img_953 -2002/09/02/big/img_15728 -2002/08/01/big/img_1361 -2002/08/29/big/img_18897 -2002/08/26/big/img_534 -2002/08/11/big/img_121 -2002/08/26/big/img_20130 -2002/07/31/big/img_363 -2002/08/13/big/img_978 -2002/07/25/big/img_835 -2002/08/02/big/img_906 -2003/01/14/big/img_548 -2002/07/30/big/img_80 -2002/07/26/big/img_982 -2003/01/16/big/img_99 -2002/08/19/big/img_362 -2002/08/24/big/img_376 -2002/08/07/big/img_1264 -2002/07/27/big/img_938 -2003/01/17/big/img_535 -2002/07/26/big/img_457 -2002/08/08/big/img_848 -2003/01/15/big/img_859 -2003/01/15/big/img_622 -2002/07/30/big/img_403 -2002/07/29/big/img_217 -2002/07/26/big/img_891 -2002/07/24/big/img_70 -2002/08/25/big/img_619 -2002/08/05/big/img_3375 -2002/08/01/big/img_2160 -2002/08/06/big/img_2227 -2003/01/14/big/img_117 -2002/08/14/big/img_227 -2002/08/13/big/img_565 -2002/08/19/big/img_625 -2002/08/03/big/img_812 -2002/07/24/big/img_41 -2002/08/16/big/img_235 -2002/07/29/big/img_759 -2002/07/21/big/img_433 -2002/07/29/big/img_190 -2003/01/16/big/img_435 -2003/01/13/big/img_708 -2002/07/30/big/img_57 -2002/08/22/big/img_162 -2003/01/01/big/img_558 -2003/01/15/big/img_604 -2002/08/16/big/img_935 -2002/08/20/big/img_394 -2002/07/28/big/img_465 -2002/09/02/big/img_15534 -2002/08/16/big/img_87 -2002/07/22/big/img_469 -2002/08/12/big/img_245 -2003/01/13/big/img_236 -2002/08/06/big/img_2736 -2002/08/03/big/img_348 -2003/01/14/big/img_218 -2002/07/26/big/img_232 -2003/01/15/big/img_244 -2002/07/25/big/img_1121 -2002/08/01/big/img_1484 -2002/07/26/big/img_541 -2002/08/07/big/img_1244 -2002/07/31/big/img_3 -2002/08/30/big/img_18437 -2002/08/29/big/img_19094 -2002/08/01/big/img_1355 -2002/08/19/big/img_338 -2002/07/19/big/img_255 -2002/07/21/big/img_76 -2002/08/25/big/img_199 -2002/08/12/big/img_740 -2002/07/30/big/img_852 -2002/08/15/big/img_599 -2002/08/23/big/img_254 -2002/08/19/big/img_125 -2002/07/24/big/img_2 -2002/08/04/big/img_145 -2002/08/05/big/img_3137 -2002/07/28/big/img_463 -2003/01/14/big/img_801 -2002/07/23/big/img_366 -2002/08/26/big/img_600 -2002/08/26/big/img_649 -2002/09/02/big/img_15849 -2002/07/26/big/img_248 -2003/01/13/big/img_200 -2002/08/07/big/img_1794 -2002/08/31/big/img_17270 -2002/08/23/big/img_608 -2003/01/13/big/img_837 -2002/08/23/big/img_581 -2002/08/20/big/img_754 -2002/08/18/big/img_183 -2002/08/20/big/img_328 -2002/07/22/big/img_494 -2002/07/29/big/img_399 -2002/08/28/big/img_19284 -2002/08/08/big/img_566 -2002/07/25/big/img_376 -2002/07/23/big/img_138 -2002/07/25/big/img_435 -2002/08/17/big/img_685 -2002/07/19/big/img_90 -2002/07/20/big/img_716 -2002/08/31/big/img_17458 -2002/08/26/big/img_461 -2002/07/25/big/img_355 -2002/08/06/big/img_2152 -2002/07/27/big/img_932 -2002/07/23/big/img_232 -2002/08/08/big/img_1020 -2002/07/31/big/img_366 -2002/08/06/big/img_2667 -2002/08/21/big/img_465 -2002/08/15/big/img_305 -2002/08/02/big/img_247 -2002/07/28/big/img_46 -2002/08/27/big/img_19922 -2002/08/23/big/img_643 -2003/01/13/big/img_624 -2002/08/23/big/img_625 -2002/08/05/big/img_3787 -2003/01/13/big/img_627 -2002/09/01/big/img_16381 -2002/08/05/big/img_3668 -2002/07/21/big/img_535 -2002/08/27/big/img_19680 -2002/07/22/big/img_413 -2002/07/29/big/img_481 -2003/01/15/big/img_496 -2002/07/23/big/img_701 -2002/08/29/big/img_18670 -2002/07/28/big/img_319 -2003/01/14/big/img_517 -2002/07/26/big/img_256 -2003/01/16/big/img_593 -2002/07/30/big/img_956 -2002/07/30/big/img_667 -2002/07/25/big/img_100 -2002/08/11/big/img_570 -2002/07/26/big/img_745 -2002/08/04/big/img_834 -2002/08/25/big/img_521 -2002/08/01/big/img_2148 -2002/09/02/big/img_15183 -2002/08/22/big/img_514 -2002/08/23/big/img_477 -2002/07/23/big/img_336 -2002/07/26/big/img_481 -2002/08/20/big/img_409 -2002/07/23/big/img_918 -2002/08/09/big/img_474 -2002/08/02/big/img_929 -2002/08/31/big/img_17932 -2002/08/19/big/img_161 -2002/08/09/big/img_667 -2002/07/31/big/img_805 -2002/09/02/big/img_15678 -2002/08/31/big/img_17509 -2002/08/29/big/img_18998 -2002/07/23/big/img_301 -2002/08/07/big/img_1612 -2002/08/06/big/img_2472 -2002/07/23/big/img_466 -2002/08/27/big/img_19634 -2003/01/16/big/img_16 -2002/08/14/big/img_193 -2002/08/21/big/img_340 -2002/08/27/big/img_19799 -2002/08/01/big/img_1345 -2002/08/07/big/img_1448 -2002/08/11/big/img_324 -2003/01/16/big/img_754 -2002/08/13/big/img_418 -2003/01/16/big/img_544 -2002/08/19/big/img_135 -2002/08/10/big/img_455 -2002/08/10/big/img_693 -2002/08/31/big/img_17967 -2002/08/28/big/img_19229 -2002/08/04/big/img_811 -2002/09/01/big/img_16225 -2003/01/16/big/img_428 -2002/09/02/big/img_15295 -2002/07/26/big/img_108 -2002/07/21/big/img_477 -2002/08/07/big/img_1354 -2002/08/23/big/img_246 -2002/08/16/big/img_652 -2002/07/27/big/img_553 -2002/07/31/big/img_346 -2002/08/04/big/img_537 -2002/08/08/big/img_498 -2002/08/29/big/img_18956 -2003/01/13/big/img_922 -2002/08/31/big/img_17425 -2002/07/26/big/img_438 -2002/08/19/big/img_185 -2003/01/16/big/img_33 -2002/08/10/big/img_252 -2002/07/29/big/img_598 -2002/08/27/big/img_19820 -2002/08/06/big/img_2664 -2002/08/20/big/img_705 -2003/01/14/big/img_816 -2002/08/03/big/img_552 -2002/07/25/big/img_561 -2002/07/25/big/img_934 -2002/08/01/big/img_1893 -2003/01/14/big/img_746 -2003/01/16/big/img_519 -2002/08/03/big/img_681 -2002/07/24/big/img_808 -2002/08/14/big/img_803 -2002/08/25/big/img_155 -2002/07/30/big/img_1107 -2002/08/29/big/img_18882 -2003/01/15/big/img_598 -2002/08/19/big/img_122 -2002/07/30/big/img_428 -2002/07/24/big/img_684 -2002/08/22/big/img_192 -2002/08/22/big/img_543 -2002/08/07/big/img_1318 -2002/08/18/big/img_25 -2002/07/26/big/img_583 -2002/07/20/big/img_464 -2002/08/19/big/img_664 -2002/08/24/big/img_861 -2002/09/01/big/img_16136 -2002/08/22/big/img_400 -2002/08/12/big/img_445 -2003/01/14/big/img_174 -2002/08/27/big/img_19677 -2002/08/31/big/img_17214 -2002/08/30/big/img_18175 -2003/01/17/big/img_402 -2002/08/06/big/img_2396 -2002/08/18/big/img_448 -2002/08/21/big/img_165 -2002/08/31/big/img_17609 -2003/01/01/big/img_151 -2002/08/26/big/img_372 -2002/09/02/big/img_15994 -2002/07/26/big/img_660 -2002/09/02/big/img_15197 -2002/07/29/big/img_258 -2002/08/30/big/img_18525 -2003/01/13/big/img_368 -2002/07/29/big/img_1538 -2002/07/21/big/img_787 -2002/08/18/big/img_152 -2002/08/06/big/img_2379 -2003/01/17/big/img_864 -2002/08/27/big/img_19998 -2002/08/01/big/img_1634 -2002/07/25/big/img_414 -2002/08/22/big/img_627 -2002/08/07/big/img_1669 -2002/08/16/big/img_1052 -2002/08/31/big/img_17796 -2002/08/18/big/img_199 -2002/09/02/big/img_15147 -2002/08/09/big/img_460 -2002/08/14/big/img_581 -2002/08/30/big/img_18286 -2002/07/26/big/img_337 -2002/08/18/big/img_589 -2003/01/14/big/img_866 -2002/07/20/big/img_624 -2002/08/01/big/img_1801 -2002/07/24/big/img_683 -2002/08/09/big/img_725 -2003/01/14/big/img_34 -2002/07/30/big/img_144 -2002/07/30/big/img_706 -2002/08/08/big/img_394 -2002/08/19/big/img_619 -2002/08/06/big/img_2703 -2002/08/29/big/img_19034 -2002/07/24/big/img_67 -2002/08/27/big/img_19841 -2002/08/19/big/img_427 -2003/01/14/big/img_333 -2002/09/01/big/img_16406 -2002/07/19/big/img_882 -2002/08/17/big/img_238 -2003/01/14/big/img_739 -2002/07/22/big/img_151 -2002/08/21/big/img_743 -2002/07/25/big/img_1048 -2002/07/30/big/img_395 -2003/01/13/big/img_584 -2002/08/13/big/img_742 -2002/08/13/big/img_1168 -2003/01/14/big/img_147 -2002/07/26/big/img_803 -2002/08/05/big/img_3298 -2002/08/07/big/img_1451 -2002/08/16/big/img_424 -2002/07/29/big/img_1069 -2002/09/01/big/img_16735 -2002/07/21/big/img_637 -2003/01/14/big/img_585 -2002/08/02/big/img_358 -2003/01/13/big/img_358 -2002/08/14/big/img_198 -2002/08/17/big/img_935 -2002/08/04/big/img_42 -2002/08/30/big/img_18245 -2002/07/25/big/img_158 -2002/08/22/big/img_744 -2002/08/06/big/img_2291 -2002/08/05/big/img_3044 -2002/07/30/big/img_272 -2002/08/23/big/img_641 -2002/07/24/big/img_797 -2002/07/30/big/img_392 -2003/01/14/big/img_447 -2002/07/31/big/img_898 -2002/08/06/big/img_2812 -2002/08/13/big/img_564 -2002/07/22/big/img_43 -2002/07/26/big/img_634 -2002/07/19/big/img_843 -2002/08/26/big/img_58 -2002/07/21/big/img_375 -2002/08/25/big/img_729 -2002/07/19/big/img_561 -2003/01/15/big/img_884 -2002/07/25/big/img_891 -2002/08/09/big/img_558 -2002/08/26/big/img_587 -2002/08/13/big/img_1146 -2002/09/02/big/img_15153 -2002/07/26/big/img_316 -2002/08/01/big/img_1940 -2002/08/26/big/img_90 -2003/01/13/big/img_347 -2002/07/25/big/img_520 -2002/08/29/big/img_18718 -2002/08/28/big/img_19219 -2002/08/13/big/img_375 -2002/07/20/big/img_719 -2002/08/31/big/img_17431 -2002/07/28/big/img_192 -2002/08/26/big/img_259 -2002/08/18/big/img_484 -2002/07/29/big/img_580 -2002/07/26/big/img_84 -2002/08/02/big/img_302 -2002/08/31/big/img_17007 -2003/01/15/big/img_543 -2002/09/01/big/img_16488 -2002/08/22/big/img_798 -2002/07/30/big/img_383 -2002/08/04/big/img_668 -2002/08/13/big/img_156 -2002/08/07/big/img_1353 -2002/07/25/big/img_281 -2003/01/14/big/img_587 -2003/01/15/big/img_524 -2002/08/19/big/img_726 -2002/08/21/big/img_709 -2002/08/26/big/img_465 -2002/07/31/big/img_658 -2002/08/28/big/img_19148 -2002/07/23/big/img_423 -2002/08/16/big/img_758 -2002/08/22/big/img_523 -2002/08/16/big/img_591 -2002/08/23/big/img_845 -2002/07/26/big/img_678 -2002/08/09/big/img_806 -2002/08/06/big/img_2369 -2002/07/29/big/img_457 -2002/07/19/big/img_278 -2002/08/30/big/img_18107 -2002/07/26/big/img_444 -2002/08/20/big/img_278 -2002/08/26/big/img_92 -2002/08/26/big/img_257 -2002/07/25/big/img_266 -2002/08/05/big/img_3829 -2002/07/26/big/img_757 -2002/07/29/big/img_1536 -2002/08/09/big/img_472 -2003/01/17/big/img_480 -2002/08/28/big/img_19355 -2002/07/26/big/img_97 -2002/08/06/big/img_2503 -2002/07/19/big/img_254 -2002/08/01/big/img_1470 -2002/08/21/big/img_42 -2002/08/20/big/img_217 -2002/08/06/big/img_2459 -2002/07/19/big/img_552 -2002/08/13/big/img_717 -2002/08/12/big/img_586 -2002/08/20/big/img_411 -2003/01/13/big/img_768 -2002/08/07/big/img_1747 -2002/08/15/big/img_385 -2002/08/01/big/img_1648 -2002/08/15/big/img_311 -2002/08/21/big/img_95 -2002/08/09/big/img_108 -2002/08/21/big/img_398 -2002/08/17/big/img_340 -2002/08/14/big/img_474 -2002/08/13/big/img_294 -2002/08/24/big/img_840 -2002/08/09/big/img_808 -2002/08/23/big/img_491 -2002/07/28/big/img_33 -2003/01/13/big/img_664 -2002/08/02/big/img_261 -2002/08/09/big/img_591 -2002/07/26/big/img_309 -2003/01/14/big/img_372 -2002/08/19/big/img_581 -2002/08/19/big/img_168 -2002/08/26/big/img_422 -2002/07/24/big/img_106 -2002/08/01/big/img_1936 -2002/08/05/big/img_3764 -2002/08/21/big/img_266 -2002/08/31/big/img_17968 -2002/08/01/big/img_1941 -2002/08/15/big/img_550 -2002/08/14/big/img_13 -2002/07/30/big/img_171 -2003/01/13/big/img_490 -2002/07/25/big/img_427 -2002/07/19/big/img_770 -2002/08/12/big/img_759 -2003/01/15/big/img_1360 -2002/08/05/big/img_3692 -2003/01/16/big/img_30 -2002/07/25/big/img_1026 -2002/07/22/big/img_288 -2002/08/29/big/img_18801 -2002/07/24/big/img_793 -2002/08/13/big/img_178 -2002/08/06/big/img_2322 -2003/01/14/big/img_560 -2002/08/18/big/img_408 -2003/01/16/big/img_915 -2003/01/16/big/img_679 -2002/08/07/big/img_1552 -2002/08/29/big/img_19050 -2002/08/01/big/img_2172 -2002/07/31/big/img_30 -2002/07/30/big/img_1019 -2002/07/30/big/img_587 -2003/01/13/big/img_773 -2002/07/30/big/img_410 -2002/07/28/big/img_65 -2002/08/05/big/img_3138 -2002/07/23/big/img_541 -2002/08/22/big/img_963 -2002/07/27/big/img_657 -2002/07/30/big/img_1051 -2003/01/16/big/img_150 -2002/07/31/big/img_519 -2002/08/01/big/img_1961 -2002/08/05/big/img_3752 -2002/07/23/big/img_631 -2003/01/14/big/img_237 -2002/07/28/big/img_21 -2002/07/22/big/img_813 -2002/08/05/big/img_3563 -2003/01/17/big/img_620 -2002/07/19/big/img_523 -2002/07/30/big/img_904 -2002/08/29/big/img_18642 -2002/08/11/big/img_492 -2002/08/01/big/img_2130 -2002/07/25/big/img_618 -2002/08/17/big/img_305 -2003/01/16/big/img_520 -2002/07/26/big/img_495 -2002/08/17/big/img_164 -2002/08/03/big/img_440 -2002/07/24/big/img_441 -2002/08/06/big/img_2146 -2002/08/11/big/img_558 -2002/08/02/big/img_545 -2002/08/31/big/img_18090 -2003/01/01/big/img_136 -2002/07/25/big/img_1099 -2003/01/13/big/img_728 -2003/01/16/big/img_197 -2002/07/26/big/img_651 -2002/08/11/big/img_676 -2003/01/15/big/img_10 -2002/08/21/big/img_250 -2002/08/14/big/img_325 -2002/08/04/big/img_390 -2002/07/24/big/img_554 -2003/01/16/big/img_333 -2002/07/31/big/img_922 -2002/09/02/big/img_15586 -2003/01/16/big/img_184 -2002/07/22/big/img_766 -2002/07/21/big/img_608 -2002/08/07/big/img_1578 -2002/08/17/big/img_961 -2002/07/27/big/img_324 -2002/08/05/big/img_3765 -2002/08/23/big/img_462 -2003/01/16/big/img_382 -2002/08/27/big/img_19838 -2002/08/01/big/img_1505 -2002/08/21/big/img_662 -2002/08/14/big/img_605 -2002/08/19/big/img_816 -2002/07/29/big/img_136 -2002/08/20/big/img_719 -2002/08/06/big/img_2826 -2002/08/10/big/img_630 -2003/01/17/big/img_973 -2002/08/14/big/img_116 -2002/08/02/big/img_666 -2002/08/21/big/img_710 -2002/08/05/big/img_55 -2002/07/31/big/img_229 -2002/08/01/big/img_1549 -2002/07/23/big/img_432 -2002/07/21/big/img_430 -2002/08/21/big/img_549 -2002/08/08/big/img_985 -2002/07/20/big/img_610 -2002/07/23/big/img_978 -2002/08/23/big/img_219 -2002/07/25/big/img_175 -2003/01/15/big/img_230 -2002/08/23/big/img_385 -2002/07/31/big/img_879 -2002/08/12/big/img_495 -2002/08/22/big/img_499 -2002/08/30/big/img_18322 -2002/08/15/big/img_795 -2002/08/13/big/img_835 -2003/01/17/big/img_930 -2002/07/30/big/img_873 -2002/08/11/big/img_257 -2002/07/31/big/img_593 -2002/08/21/big/img_916 -2003/01/13/big/img_814 -2002/07/25/big/img_722 -2002/08/16/big/img_379 -2002/07/31/big/img_497 -2002/07/22/big/img_602 -2002/08/21/big/img_642 -2002/08/21/big/img_614 -2002/08/23/big/img_482 -2002/07/29/big/img_603 -2002/08/13/big/img_705 -2002/07/23/big/img_833 -2003/01/14/big/img_511 -2002/07/24/big/img_376 -2002/08/17/big/img_1030 -2002/08/05/big/img_3576 -2002/08/16/big/img_540 -2002/07/22/big/img_630 -2002/08/10/big/img_180 -2002/08/14/big/img_905 -2002/08/29/big/img_18777 -2002/08/22/big/img_693 -2003/01/16/big/img_933 -2002/08/20/big/img_555 -2002/08/15/big/img_549 -2003/01/14/big/img_830 -2003/01/16/big/img_64 -2002/08/27/big/img_19670 -2002/08/22/big/img_729 -2002/07/27/big/img_981 -2002/08/09/big/img_458 -2003/01/17/big/img_884 -2002/07/25/big/img_639 -2002/08/31/big/img_18008 -2002/08/22/big/img_249 -2002/08/17/big/img_971 -2002/08/04/big/img_308 -2002/07/28/big/img_362 -2002/08/12/big/img_142 -2002/08/26/big/img_61 -2002/08/14/big/img_422 -2002/07/19/big/img_607 -2003/01/15/big/img_717 -2002/08/01/big/img_1475 -2002/08/29/big/img_19061 -2003/01/01/big/img_346 -2002/07/20/big/img_315 -2003/01/15/big/img_756 -2002/08/15/big/img_879 -2002/08/08/big/img_615 -2003/01/13/big/img_431 -2002/08/05/big/img_3233 -2002/08/24/big/img_526 -2003/01/13/big/img_717 -2002/09/01/big/img_16408 -2002/07/22/big/img_217 -2002/07/31/big/img_960 -2002/08/21/big/img_610 -2002/08/05/big/img_3753 -2002/08/03/big/img_151 -2002/08/21/big/img_267 -2002/08/01/big/img_2175 -2002/08/04/big/img_556 -2002/08/21/big/img_527 -2002/09/02/big/img_15800 -2002/07/27/big/img_156 -2002/07/20/big/img_590 -2002/08/15/big/img_700 -2002/08/08/big/img_444 -2002/07/25/big/img_94 -2002/07/24/big/img_778 -2002/08/14/big/img_694 -2002/07/20/big/img_666 -2002/08/02/big/img_200 -2002/08/02/big/img_578 -2003/01/17/big/img_332 -2002/09/01/big/img_16352 -2002/08/27/big/img_19668 -2002/07/23/big/img_823 -2002/08/13/big/img_431 -2003/01/16/big/img_463 -2002/08/27/big/img_19711 -2002/08/23/big/img_154 -2002/07/31/big/img_360 -2002/08/23/big/img_555 -2002/08/10/big/img_561 -2003/01/14/big/img_550 -2002/08/07/big/img_1370 -2002/07/30/big/img_1184 -2002/08/01/big/img_1445 -2002/08/23/big/img_22 -2002/07/30/big/img_606 -2003/01/17/big/img_271 -2002/08/31/big/img_17316 -2002/08/16/big/img_973 -2002/07/26/big/img_77 -2002/07/20/big/img_788 -2002/08/06/big/img_2426 -2002/08/07/big/img_1498 -2002/08/16/big/img_358 -2002/08/06/big/img_2851 -2002/08/12/big/img_359 -2002/08/01/big/img_1521 -2002/08/02/big/img_709 -2002/08/20/big/img_935 -2002/08/12/big/img_188 -2002/08/24/big/img_411 -2002/08/22/big/img_680 -2002/08/06/big/img_2480 -2002/07/20/big/img_627 -2002/07/30/big/img_214 -2002/07/25/big/img_354 -2002/08/02/big/img_636 -2003/01/15/big/img_661 -2002/08/07/big/img_1327 -2002/08/01/big/img_2108 -2002/08/31/big/img_17919 -2002/08/29/big/img_18768 -2002/08/05/big/img_3840 -2002/07/26/big/img_242 -2003/01/14/big/img_451 -2002/08/20/big/img_923 -2002/08/27/big/img_19908 -2002/08/16/big/img_282 -2002/08/19/big/img_440 -2003/01/01/big/img_230 -2002/08/08/big/img_212 -2002/07/20/big/img_443 -2002/08/25/big/img_635 -2003/01/13/big/img_1169 -2002/07/26/big/img_998 -2002/08/15/big/img_995 -2002/08/06/big/img_3002 -2002/07/29/big/img_460 -2003/01/14/big/img_925 -2002/07/23/big/img_539 -2002/08/16/big/img_694 -2003/01/13/big/img_459 -2002/07/23/big/img_249 -2002/08/20/big/img_539 -2002/08/04/big/img_186 -2002/08/26/big/img_264 -2002/07/22/big/img_704 -2002/08/25/big/img_277 -2002/08/22/big/img_988 -2002/07/29/big/img_504 -2002/08/05/big/img_3600 -2002/08/30/big/img_18380 -2003/01/14/big/img_937 -2002/08/21/big/img_254 -2002/08/10/big/img_130 -2002/08/20/big/img_339 -2003/01/14/big/img_428 -2002/08/20/big/img_889 -2002/08/31/big/img_17637 -2002/07/26/big/img_644 -2002/09/01/big/img_16776 -2002/08/06/big/img_2239 -2002/08/06/big/img_2646 -2003/01/13/big/img_491 -2002/08/10/big/img_579 -2002/08/21/big/img_713 -2002/08/22/big/img_482 -2002/07/22/big/img_167 -2002/07/24/big/img_539 -2002/08/14/big/img_721 -2002/07/25/big/img_389 -2002/09/01/big/img_16591 -2002/08/13/big/img_543 -2003/01/14/big/img_432 -2002/08/09/big/img_287 -2002/07/26/big/img_126 -2002/08/23/big/img_412 -2002/08/15/big/img_1034 -2002/08/28/big/img_19485 -2002/07/31/big/img_236 -2002/07/30/big/img_523 -2002/07/19/big/img_141 -2003/01/17/big/img_957 -2002/08/04/big/img_81 -2002/07/25/big/img_206 -2002/08/15/big/img_716 -2002/08/13/big/img_403 -2002/08/15/big/img_685 -2002/07/26/big/img_884 -2002/07/19/big/img_499 -2002/07/23/big/img_772 -2002/07/27/big/img_752 -2003/01/14/big/img_493 -2002/08/25/big/img_664 -2002/07/31/big/img_334 -2002/08/26/big/img_678 -2002/09/01/big/img_16541 -2003/01/14/big/img_347 -2002/07/23/big/img_187 -2002/07/30/big/img_1163 -2002/08/05/big/img_35 -2002/08/22/big/img_944 -2002/08/07/big/img_1239 -2002/07/29/big/img_1215 -2002/08/03/big/img_312 -2002/08/05/big/img_3523 -2002/07/29/big/img_218 -2002/08/13/big/img_672 -2002/08/16/big/img_205 -2002/08/17/big/img_594 -2002/07/29/big/img_1411 -2002/07/30/big/img_942 -2003/01/16/big/img_312 -2002/08/08/big/img_312 -2002/07/25/big/img_15 -2002/08/09/big/img_839 -2002/08/01/big/img_2069 -2002/08/31/big/img_17512 -2002/08/01/big/img_3 -2002/07/31/big/img_320 -2003/01/15/big/img_1265 -2002/08/14/big/img_563 -2002/07/31/big/img_167 -2002/08/20/big/img_374 -2002/08/13/big/img_406 -2002/08/08/big/img_625 -2002/08/02/big/img_314 -2002/08/27/big/img_19964 -2002/09/01/big/img_16670 -2002/07/31/big/img_599 -2002/08/29/big/img_18906 -2002/07/24/big/img_373 -2002/07/26/big/img_513 -2002/09/02/big/img_15497 -2002/08/19/big/img_117 -2003/01/01/big/img_158 -2002/08/24/big/img_178 -2003/01/13/big/img_935 -2002/08/13/big/img_609 -2002/08/30/big/img_18341 -2002/08/25/big/img_674 -2003/01/13/big/img_209 -2002/08/13/big/img_258 -2002/08/05/big/img_3543 -2002/08/07/big/img_1970 -2002/08/06/big/img_3004 -2003/01/17/big/img_487 -2002/08/24/big/img_873 -2002/08/29/big/img_18730 -2002/08/09/big/img_375 -2003/01/16/big/img_751 -2002/08/02/big/img_603 -2002/08/19/big/img_325 -2002/09/01/big/img_16420 -2002/08/05/big/img_3633 -2002/08/21/big/img_516 -2002/07/19/big/img_501 -2002/07/26/big/img_688 -2002/07/24/big/img_256 -2002/07/25/big/img_438 -2002/07/31/big/img_1017 -2002/08/22/big/img_512 -2002/07/21/big/img_543 -2002/08/08/big/img_223 -2002/08/19/big/img_189 -2002/08/12/big/img_630 -2002/07/30/big/img_958 -2002/07/28/big/img_208 -2002/08/31/big/img_17691 -2002/07/22/big/img_542 -2002/07/19/big/img_741 -2002/07/19/big/img_158 -2002/08/15/big/img_399 -2002/08/01/big/img_2159 -2002/08/14/big/img_455 -2002/08/17/big/img_1011 -2002/08/26/big/img_744 -2002/08/12/big/img_624 -2003/01/17/big/img_821 -2002/08/16/big/img_980 -2002/07/28/big/img_281 -2002/07/25/big/img_171 -2002/08/03/big/img_116 -2002/07/22/big/img_467 -2002/07/31/big/img_750 -2002/07/26/big/img_435 -2002/07/19/big/img_822 -2002/08/13/big/img_626 -2002/08/11/big/img_344 -2002/08/02/big/img_473 -2002/09/01/big/img_16817 -2002/08/01/big/img_1275 -2002/08/28/big/img_19270 -2002/07/23/big/img_607 -2002/08/09/big/img_316 -2002/07/29/big/img_626 -2002/07/24/big/img_824 -2002/07/22/big/img_342 -2002/08/08/big/img_794 -2002/08/07/big/img_1209 -2002/07/19/big/img_18 -2002/08/25/big/img_634 -2002/07/24/big/img_730 -2003/01/17/big/img_356 -2002/07/23/big/img_305 -2002/07/30/big/img_453 -2003/01/13/big/img_972 -2002/08/06/big/img_2610 -2002/08/29/big/img_18920 -2002/07/31/big/img_123 -2002/07/26/big/img_979 -2002/08/24/big/img_635 -2002/08/05/big/img_3704 -2002/08/07/big/img_1358 -2002/07/22/big/img_306 -2002/08/13/big/img_619 -2002/08/02/big/img_366 diff --git a/third_part/GPEN/face_detect/data/__init__.py b/third_part/GPEN/face_detect/data/__init__.py deleted file mode 100644 index ea50eba..0000000 --- a/third_part/GPEN/face_detect/data/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .wider_face import WiderFaceDetection, detection_collate -from .data_augment import * -from .config import * diff --git a/third_part/GPEN/face_detect/data/config.py b/third_part/GPEN/face_detect/data/config.py deleted file mode 100644 index e57cdc5..0000000 --- a/third_part/GPEN/face_detect/data/config.py +++ /dev/null @@ -1,42 +0,0 @@ -# config.py - -cfg_mnet = { - 'name': 'mobilenet0.25', - 'min_sizes': [[16, 32], [64, 128], [256, 512]], - 'steps': [8, 16, 32], - 'variance': [0.1, 0.2], - 'clip': False, - 'loc_weight': 2.0, - 'gpu_train': True, - 'batch_size': 32, - 'ngpu': 1, - 'epoch': 250, - 'decay1': 190, - 'decay2': 220, - 'image_size': 640, - 'pretrain': False, - 'return_layers': {'stage1': 1, 'stage2': 2, 'stage3': 3}, - 'in_channel': 32, - 'out_channel': 64 -} - -cfg_re50 = { - 'name': 'Resnet50', - 'min_sizes': [[16, 32], [64, 128], [256, 512]], - 'steps': [8, 16, 32], - 'variance': [0.1, 0.2], - 'clip': False, - 'loc_weight': 2.0, - 'gpu_train': True, - 'batch_size': 24, - 'ngpu': 4, - 'epoch': 100, - 'decay1': 70, - 'decay2': 90, - 'image_size': 840, - 'pretrain': False, - 'return_layers': {'layer2': 1, 'layer3': 2, 'layer4': 3}, - 'in_channel': 256, - 'out_channel': 256 -} - diff --git a/third_part/GPEN/face_detect/data/data_augment.py b/third_part/GPEN/face_detect/data/data_augment.py deleted file mode 100644 index baca38f..0000000 --- a/third_part/GPEN/face_detect/data/data_augment.py +++ /dev/null @@ -1,237 +0,0 @@ -import cv2 -import numpy as np -import random -from custom_nodes.ComfyUI_Lam.third_part.GPEN.face_detect.utils.box_utils import matrix_iof - - -def _crop(image, boxes, labels, landm, img_dim): - height, width, _ = image.shape - pad_image_flag = True - - for _ in range(250): - """ - if random.uniform(0, 1) <= 0.2: - scale = 1.0 - else: - scale = random.uniform(0.3, 1.0) - """ - PRE_SCALES = [0.3, 0.45, 0.6, 0.8, 1.0] - scale = random.choice(PRE_SCALES) - short_side = min(width, height) - w = int(scale * short_side) - h = w - - if width == w: - l = 0 - else: - l = random.randrange(width - w) - if height == h: - t = 0 - else: - t = random.randrange(height - h) - roi = np.array((l, t, l + w, t + h)) - - value = matrix_iof(boxes, roi[np.newaxis]) - flag = (value >= 1) - if not flag.any(): - continue - - centers = (boxes[:, :2] + boxes[:, 2:]) / 2 - mask_a = np.logical_and(roi[:2] < centers, centers < roi[2:]).all(axis=1) - boxes_t = boxes[mask_a].copy() - labels_t = labels[mask_a].copy() - landms_t = landm[mask_a].copy() - landms_t = landms_t.reshape([-1, 5, 2]) - - if boxes_t.shape[0] == 0: - continue - - image_t = image[roi[1]:roi[3], roi[0]:roi[2]] - - boxes_t[:, :2] = np.maximum(boxes_t[:, :2], roi[:2]) - boxes_t[:, :2] -= roi[:2] - boxes_t[:, 2:] = np.minimum(boxes_t[:, 2:], roi[2:]) - boxes_t[:, 2:] -= roi[:2] - - # landm - landms_t[:, :, :2] = landms_t[:, :, :2] - roi[:2] - landms_t[:, :, :2] = np.maximum(landms_t[:, :, :2], np.array([0, 0])) - landms_t[:, :, :2] = np.minimum(landms_t[:, :, :2], roi[2:] - roi[:2]) - landms_t = landms_t.reshape([-1, 10]) - - - # make sure that the cropped image contains at least one face > 16 pixel at training image scale - b_w_t = (boxes_t[:, 2] - boxes_t[:, 0] + 1) / w * img_dim - b_h_t = (boxes_t[:, 3] - boxes_t[:, 1] + 1) / h * img_dim - mask_b = np.minimum(b_w_t, b_h_t) > 0.0 - boxes_t = boxes_t[mask_b] - labels_t = labels_t[mask_b] - landms_t = landms_t[mask_b] - - if boxes_t.shape[0] == 0: - continue - - pad_image_flag = False - - return image_t, boxes_t, labels_t, landms_t, pad_image_flag - return image, boxes, labels, landm, pad_image_flag - - -def _distort(image): - - def _convert(image, alpha=1, beta=0): - tmp = image.astype(float) * alpha + beta - tmp[tmp < 0] = 0 - tmp[tmp > 255] = 255 - image[:] = tmp - - image = image.copy() - - if random.randrange(2): - - #brightness distortion - if random.randrange(2): - _convert(image, beta=random.uniform(-32, 32)) - - #contrast distortion - if random.randrange(2): - _convert(image, alpha=random.uniform(0.5, 1.5)) - - image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV) - - #saturation distortion - if random.randrange(2): - _convert(image[:, :, 1], alpha=random.uniform(0.5, 1.5)) - - #hue distortion - if random.randrange(2): - tmp = image[:, :, 0].astype(int) + random.randint(-18, 18) - tmp %= 180 - image[:, :, 0] = tmp - - image = cv2.cvtColor(image, cv2.COLOR_HSV2BGR) - - else: - - #brightness distortion - if random.randrange(2): - _convert(image, beta=random.uniform(-32, 32)) - - image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV) - - #saturation distortion - if random.randrange(2): - _convert(image[:, :, 1], alpha=random.uniform(0.5, 1.5)) - - #hue distortion - if random.randrange(2): - tmp = image[:, :, 0].astype(int) + random.randint(-18, 18) - tmp %= 180 - image[:, :, 0] = tmp - - image = cv2.cvtColor(image, cv2.COLOR_HSV2BGR) - - #contrast distortion - if random.randrange(2): - _convert(image, alpha=random.uniform(0.5, 1.5)) - - return image - - -def _expand(image, boxes, fill, p): - if random.randrange(2): - return image, boxes - - height, width, depth = image.shape - - scale = random.uniform(1, p) - w = int(scale * width) - h = int(scale * height) - - left = random.randint(0, w - width) - top = random.randint(0, h - height) - - boxes_t = boxes.copy() - boxes_t[:, :2] += (left, top) - boxes_t[:, 2:] += (left, top) - expand_image = np.empty( - (h, w, depth), - dtype=image.dtype) - expand_image[:, :] = fill - expand_image[top:top + height, left:left + width] = image - image = expand_image - - return image, boxes_t - - -def _mirror(image, boxes, landms): - _, width, _ = image.shape - if random.randrange(2): - image = image[:, ::-1] - boxes = boxes.copy() - boxes[:, 0::2] = width - boxes[:, 2::-2] - - # landm - landms = landms.copy() - landms = landms.reshape([-1, 5, 2]) - landms[:, :, 0] = width - landms[:, :, 0] - tmp = landms[:, 1, :].copy() - landms[:, 1, :] = landms[:, 0, :] - landms[:, 0, :] = tmp - tmp1 = landms[:, 4, :].copy() - landms[:, 4, :] = landms[:, 3, :] - landms[:, 3, :] = tmp1 - landms = landms.reshape([-1, 10]) - - return image, boxes, landms - - -def _pad_to_square(image, rgb_mean, pad_image_flag): - if not pad_image_flag: - return image - height, width, _ = image.shape - long_side = max(width, height) - image_t = np.empty((long_side, long_side, 3), dtype=image.dtype) - image_t[:, :] = rgb_mean - image_t[0:0 + height, 0:0 + width] = image - return image_t - - -def _resize_subtract_mean(image, insize, rgb_mean): - interp_methods = [cv2.INTER_LINEAR, cv2.INTER_CUBIC, cv2.INTER_AREA, cv2.INTER_NEAREST, cv2.INTER_LANCZOS4] - interp_method = interp_methods[random.randrange(5)] - image = cv2.resize(image, (insize, insize), interpolation=interp_method) - image = image.astype(np.float32) - image -= rgb_mean - return image.transpose(2, 0, 1) - - -class preproc(object): - - def __init__(self, img_dim, rgb_means): - self.img_dim = img_dim - self.rgb_means = rgb_means - - def __call__(self, image, targets): - assert targets.shape[0] > 0, "this image does not have gt" - - boxes = targets[:, :4].copy() - labels = targets[:, -1].copy() - landm = targets[:, 4:-1].copy() - - image_t, boxes_t, labels_t, landm_t, pad_image_flag = _crop(image, boxes, labels, landm, self.img_dim) - image_t = _distort(image_t) - image_t = _pad_to_square(image_t,self.rgb_means, pad_image_flag) - image_t, boxes_t, landm_t = _mirror(image_t, boxes_t, landm_t) - height, width, _ = image_t.shape - image_t = _resize_subtract_mean(image_t, self.img_dim, self.rgb_means) - boxes_t[:, 0::2] /= width - boxes_t[:, 1::2] /= height - - landm_t[:, 0::2] /= width - landm_t[:, 1::2] /= height - - labels_t = np.expand_dims(labels_t, 1) - targets_t = np.hstack((boxes_t, landm_t, labels_t)) - - return image_t, targets_t diff --git a/third_part/GPEN/face_detect/data/wider_face.py b/third_part/GPEN/face_detect/data/wider_face.py deleted file mode 100644 index 22f56ef..0000000 --- a/third_part/GPEN/face_detect/data/wider_face.py +++ /dev/null @@ -1,101 +0,0 @@ -import os -import os.path -import sys -import torch -import torch.utils.data as data -import cv2 -import numpy as np - -class WiderFaceDetection(data.Dataset): - def __init__(self, txt_path, preproc=None): - self.preproc = preproc - self.imgs_path = [] - self.words = [] - f = open(txt_path,'r') - lines = f.readlines() - isFirst = True - labels = [] - for line in lines: - line = line.rstrip() - if line.startswith('#'): - if isFirst is True: - isFirst = False - else: - labels_copy = labels.copy() - self.words.append(labels_copy) - labels.clear() - path = line[2:] - path = txt_path.replace('label.txt','images/') + path - self.imgs_path.append(path) - else: - line = line.split(' ') - label = [float(x) for x in line] - labels.append(label) - - self.words.append(labels) - - def __len__(self): - return len(self.imgs_path) - - def __getitem__(self, index): - img = cv2.imread(self.imgs_path[index]) - height, width, _ = img.shape - - labels = self.words[index] - annotations = np.zeros((0, 15)) - if len(labels) == 0: - return annotations - for idx, label in enumerate(labels): - annotation = np.zeros((1, 15)) - # bbox - annotation[0, 0] = label[0] # x1 - annotation[0, 1] = label[1] # y1 - annotation[0, 2] = label[0] + label[2] # x2 - annotation[0, 3] = label[1] + label[3] # y2 - - # landmarks - annotation[0, 4] = label[4] # l0_x - annotation[0, 5] = label[5] # l0_y - annotation[0, 6] = label[7] # l1_x - annotation[0, 7] = label[8] # l1_y - annotation[0, 8] = label[10] # l2_x - annotation[0, 9] = label[11] # l2_y - annotation[0, 10] = label[13] # l3_x - annotation[0, 11] = label[14] # l3_y - annotation[0, 12] = label[16] # l4_x - annotation[0, 13] = label[17] # l4_y - if (annotation[0, 4]<0): - annotation[0, 14] = -1 - else: - annotation[0, 14] = 1 - - annotations = np.append(annotations, annotation, axis=0) - target = np.array(annotations) - if self.preproc is not None: - img, target = self.preproc(img, target) - - return torch.from_numpy(img), target - -def detection_collate(batch): - """Custom collate fn for dealing with batches of images that have a different - number of associated object annotations (bounding boxes). - - Arguments: - batch: (tuple) A tuple of tensor images and lists of annotations - - Return: - A tuple containing: - 1) (tensor) batch of images stacked on their 0 dim - 2) (list of tensors) annotations for a given image are stacked on 0 dim - """ - targets = [] - imgs = [] - for _, sample in enumerate(batch): - for _, tup in enumerate(sample): - if torch.is_tensor(tup): - imgs.append(tup) - elif isinstance(tup, type(np.empty(0))): - annos = torch.from_numpy(tup).float() - targets.append(annos) - - return (torch.stack(imgs, 0), targets) diff --git a/third_part/GPEN/face_detect/facemodels/__init__.py b/third_part/GPEN/face_detect/facemodels/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/third_part/GPEN/face_detect/facemodels/net.py b/third_part/GPEN/face_detect/facemodels/net.py deleted file mode 100644 index beb6040..0000000 --- a/third_part/GPEN/face_detect/facemodels/net.py +++ /dev/null @@ -1,137 +0,0 @@ -import time -import torch -import torch.nn as nn -import torchvision.models._utils as _utils -import torchvision.models as models -import torch.nn.functional as F -from torch.autograd import Variable - -def conv_bn(inp, oup, stride = 1, leaky = 0): - return nn.Sequential( - nn.Conv2d(inp, oup, 3, stride, 1, bias=False), - nn.BatchNorm2d(oup), - nn.LeakyReLU(negative_slope=leaky, inplace=True) - ) - -def conv_bn_no_relu(inp, oup, stride): - return nn.Sequential( - nn.Conv2d(inp, oup, 3, stride, 1, bias=False), - nn.BatchNorm2d(oup), - ) - -def conv_bn1X1(inp, oup, stride, leaky=0): - return nn.Sequential( - nn.Conv2d(inp, oup, 1, stride, padding=0, bias=False), - nn.BatchNorm2d(oup), - nn.LeakyReLU(negative_slope=leaky, inplace=True) - ) - -def conv_dw(inp, oup, stride, leaky=0.1): - return nn.Sequential( - nn.Conv2d(inp, inp, 3, stride, 1, groups=inp, bias=False), - nn.BatchNorm2d(inp), - nn.LeakyReLU(negative_slope= leaky,inplace=True), - - nn.Conv2d(inp, oup, 1, 1, 0, bias=False), - nn.BatchNorm2d(oup), - nn.LeakyReLU(negative_slope= leaky,inplace=True), - ) - -class SSH(nn.Module): - def __init__(self, in_channel, out_channel): - super(SSH, self).__init__() - assert out_channel % 4 == 0 - leaky = 0 - if (out_channel <= 64): - leaky = 0.1 - self.conv3X3 = conv_bn_no_relu(in_channel, out_channel//2, stride=1) - - self.conv5X5_1 = conv_bn(in_channel, out_channel//4, stride=1, leaky = leaky) - self.conv5X5_2 = conv_bn_no_relu(out_channel//4, out_channel//4, stride=1) - - self.conv7X7_2 = conv_bn(out_channel//4, out_channel//4, stride=1, leaky = leaky) - self.conv7x7_3 = conv_bn_no_relu(out_channel//4, out_channel//4, stride=1) - - def forward(self, input): - conv3X3 = self.conv3X3(input) - - conv5X5_1 = self.conv5X5_1(input) - conv5X5 = self.conv5X5_2(conv5X5_1) - - conv7X7_2 = self.conv7X7_2(conv5X5_1) - conv7X7 = self.conv7x7_3(conv7X7_2) - - out = torch.cat([conv3X3, conv5X5, conv7X7], dim=1) - out = F.relu(out) - return out - -class FPN(nn.Module): - def __init__(self,in_channels_list,out_channels): - super(FPN,self).__init__() - leaky = 0 - if (out_channels <= 64): - leaky = 0.1 - self.output1 = conv_bn1X1(in_channels_list[0], out_channels, stride = 1, leaky = leaky) - self.output2 = conv_bn1X1(in_channels_list[1], out_channels, stride = 1, leaky = leaky) - self.output3 = conv_bn1X1(in_channels_list[2], out_channels, stride = 1, leaky = leaky) - - self.merge1 = conv_bn(out_channels, out_channels, leaky = leaky) - self.merge2 = conv_bn(out_channels, out_channels, leaky = leaky) - - def forward(self, input): - # names = list(input.keys()) - input = list(input.values()) - - output1 = self.output1(input[0]) - output2 = self.output2(input[1]) - output3 = self.output3(input[2]) - - up3 = F.interpolate(output3, size=[output2.size(2), output2.size(3)], mode="nearest") - output2 = output2 + up3 - output2 = self.merge2(output2) - - up2 = F.interpolate(output2, size=[output1.size(2), output1.size(3)], mode="nearest") - output1 = output1 + up2 - output1 = self.merge1(output1) - - out = [output1, output2, output3] - return out - - - -class MobileNetV1(nn.Module): - def __init__(self): - super(MobileNetV1, self).__init__() - self.stage1 = nn.Sequential( - conv_bn(3, 8, 2, leaky = 0.1), # 3 - conv_dw(8, 16, 1), # 7 - conv_dw(16, 32, 2), # 11 - conv_dw(32, 32, 1), # 19 - conv_dw(32, 64, 2), # 27 - conv_dw(64, 64, 1), # 43 - ) - self.stage2 = nn.Sequential( - conv_dw(64, 128, 2), # 43 + 16 = 59 - conv_dw(128, 128, 1), # 59 + 32 = 91 - conv_dw(128, 128, 1), # 91 + 32 = 123 - conv_dw(128, 128, 1), # 123 + 32 = 155 - conv_dw(128, 128, 1), # 155 + 32 = 187 - conv_dw(128, 128, 1), # 187 + 32 = 219 - ) - self.stage3 = nn.Sequential( - conv_dw(128, 256, 2), # 219 +3 2 = 241 - conv_dw(256, 256, 1), # 241 + 64 = 301 - ) - self.avg = nn.AdaptiveAvgPool2d((1,1)) - self.fc = nn.Linear(256, 1000) - - def forward(self, x): - x = self.stage1(x) - x = self.stage2(x) - x = self.stage3(x) - x = self.avg(x) - # x = self.model(x) - x = x.view(-1, 256) - x = self.fc(x) - return x - diff --git a/third_part/GPEN/face_detect/facemodels/retinaface.py b/third_part/GPEN/face_detect/facemodels/retinaface.py deleted file mode 100644 index fedb066..0000000 --- a/third_part/GPEN/face_detect/facemodels/retinaface.py +++ /dev/null @@ -1,127 +0,0 @@ -import torch -import torch.nn as nn -import torchvision.models.detection.backbone_utils as backbone_utils -import torchvision.models._utils as _utils -import torch.nn.functional as F -from collections import OrderedDict - -from custom_nodes.ComfyUI_Lam.third_part.GPEN.face_detect.facemodels.net import MobileNetV1 as MobileNetV1 -from custom_nodes.ComfyUI_Lam.third_part.GPEN.face_detect.facemodels.net import FPN as FPN -from custom_nodes.ComfyUI_Lam.third_part.GPEN.face_detect.facemodels.net import SSH as SSH - - - -class ClassHead(nn.Module): - def __init__(self,inchannels=512,num_anchors=3): - super(ClassHead,self).__init__() - self.num_anchors = num_anchors - self.conv1x1 = nn.Conv2d(inchannels,self.num_anchors*2,kernel_size=(1,1),stride=1,padding=0) - - def forward(self,x): - out = self.conv1x1(x) - out = out.permute(0,2,3,1).contiguous() - - return out.view(out.shape[0], -1, 2) - -class BboxHead(nn.Module): - def __init__(self,inchannels=512,num_anchors=3): - super(BboxHead,self).__init__() - self.conv1x1 = nn.Conv2d(inchannels,num_anchors*4,kernel_size=(1,1),stride=1,padding=0) - - def forward(self,x): - out = self.conv1x1(x) - out = out.permute(0,2,3,1).contiguous() - - return out.view(out.shape[0], -1, 4) - -class LandmarkHead(nn.Module): - def __init__(self,inchannels=512,num_anchors=3): - super(LandmarkHead,self).__init__() - self.conv1x1 = nn.Conv2d(inchannels,num_anchors*10,kernel_size=(1,1),stride=1,padding=0) - - def forward(self,x): - out = self.conv1x1(x) - out = out.permute(0,2,3,1).contiguous() - - return out.view(out.shape[0], -1, 10) - -class RetinaFace(nn.Module): - def __init__(self, cfg = None, phase = 'train'): - """ - :param cfg: Network related settings. - :param phase: train or test. - """ - super(RetinaFace,self).__init__() - self.phase = phase - backbone = None - if cfg['name'] == 'mobilenet0.25': - backbone = MobileNetV1() - if cfg['pretrain']: - checkpoint = torch.load("./weights/mobilenetV1X0.25_pretrain.tar", map_location=torch.device('cpu')) - from collections import OrderedDict - new_state_dict = OrderedDict() - for k, v in checkpoint['state_dict'].items(): - name = k[7:] # remove module. - new_state_dict[name] = v - # load params - backbone.load_state_dict(new_state_dict) - elif cfg['name'] == 'Resnet50': - import torchvision.models as models - backbone = models.resnet50(pretrained=cfg['pretrain']) - - self.body = _utils.IntermediateLayerGetter(backbone, cfg['return_layers']) - in_channels_stage2 = cfg['in_channel'] - in_channels_list = [ - in_channels_stage2 * 2, - in_channels_stage2 * 4, - in_channels_stage2 * 8, - ] - out_channels = cfg['out_channel'] - self.fpn = FPN(in_channels_list,out_channels) - self.ssh1 = SSH(out_channels, out_channels) - self.ssh2 = SSH(out_channels, out_channels) - self.ssh3 = SSH(out_channels, out_channels) - - self.ClassHead = self._make_class_head(fpn_num=3, inchannels=cfg['out_channel']) - self.BboxHead = self._make_bbox_head(fpn_num=3, inchannels=cfg['out_channel']) - self.LandmarkHead = self._make_landmark_head(fpn_num=3, inchannels=cfg['out_channel']) - - def _make_class_head(self,fpn_num=3,inchannels=64,anchor_num=2): - classhead = nn.ModuleList() - for i in range(fpn_num): - classhead.append(ClassHead(inchannels,anchor_num)) - return classhead - - def _make_bbox_head(self,fpn_num=3,inchannels=64,anchor_num=2): - bboxhead = nn.ModuleList() - for i in range(fpn_num): - bboxhead.append(BboxHead(inchannels,anchor_num)) - return bboxhead - - def _make_landmark_head(self,fpn_num=3,inchannels=64,anchor_num=2): - landmarkhead = nn.ModuleList() - for i in range(fpn_num): - landmarkhead.append(LandmarkHead(inchannels,anchor_num)) - return landmarkhead - - def forward(self,inputs): - out = self.body(inputs) - - # FPN - fpn = self.fpn(out) - - # SSH - feature1 = self.ssh1(fpn[0]) - feature2 = self.ssh2(fpn[1]) - feature3 = self.ssh3(fpn[2]) - features = [feature1, feature2, feature3] - - bbox_regressions = torch.cat([self.BboxHead[i](feature) for i, feature in enumerate(features)], dim=1) - classifications = torch.cat([self.ClassHead[i](feature) for i, feature in enumerate(features)],dim=1) - ldm_regressions = torch.cat([self.LandmarkHead[i](feature) for i, feature in enumerate(features)], dim=1) - - if self.phase == 'train': - output = (bbox_regressions, classifications, ldm_regressions) - else: - output = (bbox_regressions, F.softmax(classifications, dim=-1), ldm_regressions) - return output \ No newline at end of file diff --git a/third_part/GPEN/face_detect/layers/__init__.py b/third_part/GPEN/face_detect/layers/__init__.py deleted file mode 100644 index 53a3f4b..0000000 --- a/third_part/GPEN/face_detect/layers/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .functions import * -from .modules import * diff --git a/third_part/GPEN/face_detect/layers/functions/prior_box.py b/third_part/GPEN/face_detect/layers/functions/prior_box.py deleted file mode 100644 index 80c7f85..0000000 --- a/third_part/GPEN/face_detect/layers/functions/prior_box.py +++ /dev/null @@ -1,34 +0,0 @@ -import torch -from itertools import product as product -import numpy as np -from math import ceil - - -class PriorBox(object): - def __init__(self, cfg, image_size=None, phase='train'): - super(PriorBox, self).__init__() - self.min_sizes = cfg['min_sizes'] - self.steps = cfg['steps'] - self.clip = cfg['clip'] - self.image_size = image_size - self.feature_maps = [[ceil(self.image_size[0]/step), ceil(self.image_size[1]/step)] for step in self.steps] - self.name = "s" - - def forward(self): - anchors = [] - for k, f in enumerate(self.feature_maps): - min_sizes = self.min_sizes[k] - for i, j in product(range(f[0]), range(f[1])): - for min_size in min_sizes: - s_kx = min_size / self.image_size[1] - s_ky = min_size / self.image_size[0] - dense_cx = [x * self.steps[k] / self.image_size[1] for x in [j + 0.5]] - dense_cy = [y * self.steps[k] / self.image_size[0] for y in [i + 0.5]] - for cy, cx in product(dense_cy, dense_cx): - anchors += [cx, cy, s_kx, s_ky] - - # back to torch land - output = torch.Tensor(anchors).view(-1, 4) - if self.clip: - output.clamp_(max=1, min=0) - return output diff --git a/third_part/GPEN/face_detect/layers/modules/__init__.py b/third_part/GPEN/face_detect/layers/modules/__init__.py deleted file mode 100644 index cf24bdd..0000000 --- a/third_part/GPEN/face_detect/layers/modules/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .multibox_loss import MultiBoxLoss - -__all__ = ['MultiBoxLoss'] diff --git a/third_part/GPEN/face_detect/layers/modules/multibox_loss.py b/third_part/GPEN/face_detect/layers/modules/multibox_loss.py deleted file mode 100644 index 1feb73b..0000000 --- a/third_part/GPEN/face_detect/layers/modules/multibox_loss.py +++ /dev/null @@ -1,125 +0,0 @@ -import torch -import torch.nn as nn -import torch.nn.functional as F -from torch.autograd import Variable -from custom_nodes.ComfyUI_Lam.third_part.GPEN.face_detect.utils.box_utils import match, log_sum_exp -from custom_nodes.ComfyUI_Lam.third_part.GPEN.face_detect.data import cfg_mnet -GPU = cfg_mnet['gpu_train'] - -class MultiBoxLoss(nn.Module): - """SSD Weighted Loss Function - Compute Targets: - 1) Produce Confidence Target Indices by matching ground truth boxes - with (default) 'priorboxes' that have jaccard index > threshold parameter - (default threshold: 0.5). - 2) Produce localization target by 'encoding' variance into offsets of ground - truth boxes and their matched 'priorboxes'. - 3) Hard negative mining to filter the excessive number of negative examples - that comes with using a large number of default bounding boxes. - (default negative:positive ratio 3:1) - Objective Loss: - L(x,c,l,g) = (Lconf(x, c) + αLloc(x,l,g)) / N - Where, Lconf is the CrossEntropy Loss and Lloc is the SmoothL1 Loss - weighted by α which is set to 1 by cross val. - Args: - c: class confidences, - l: predicted boxes, - g: ground truth boxes - N: number of matched default boxes - See: https://arxiv.org/pdf/1512.02325.pdf for more details. - """ - - def __init__(self, num_classes, overlap_thresh, prior_for_matching, bkg_label, neg_mining, neg_pos, neg_overlap, encode_target): - super(MultiBoxLoss, self).__init__() - self.num_classes = num_classes - self.threshold = overlap_thresh - self.background_label = bkg_label - self.encode_target = encode_target - self.use_prior_for_matching = prior_for_matching - self.do_neg_mining = neg_mining - self.negpos_ratio = neg_pos - self.neg_overlap = neg_overlap - self.variance = [0.1, 0.2] - - def forward(self, predictions, priors, targets): - """Multibox Loss - Args: - predictions (tuple): A tuple containing loc preds, conf preds, - and prior boxes from SSD net. - conf shape: torch.size(batch_size,num_priors,num_classes) - loc shape: torch.size(batch_size,num_priors,4) - priors shape: torch.size(num_priors,4) - - ground_truth (tensor): Ground truth boxes and labels for a batch, - shape: [batch_size,num_objs,5] (last idx is the label). - """ - - loc_data, conf_data, landm_data = predictions - priors = priors - num = loc_data.size(0) - num_priors = (priors.size(0)) - - # match priors (default boxes) and ground truth boxes - loc_t = torch.Tensor(num, num_priors, 4) - landm_t = torch.Tensor(num, num_priors, 10) - conf_t = torch.LongTensor(num, num_priors) - for idx in range(num): - truths = targets[idx][:, :4].data - labels = targets[idx][:, -1].data - landms = targets[idx][:, 4:14].data - defaults = priors.data - match(self.threshold, truths, defaults, self.variance, labels, landms, loc_t, conf_t, landm_t, idx) - if GPU: - loc_t = loc_t.cuda() - conf_t = conf_t.cuda() - landm_t = landm_t.cuda() - - zeros = torch.tensor(0).cuda() - # landm Loss (Smooth L1) - # Shape: [batch,num_priors,10] - pos1 = conf_t > zeros - num_pos_landm = pos1.long().sum(1, keepdim=True) - N1 = max(num_pos_landm.data.sum().float(), 1) - pos_idx1 = pos1.unsqueeze(pos1.dim()).expand_as(landm_data) - landm_p = landm_data[pos_idx1].view(-1, 10) - landm_t = landm_t[pos_idx1].view(-1, 10) - loss_landm = F.smooth_l1_loss(landm_p, landm_t, reduction='sum') - - - pos = conf_t != zeros - conf_t[pos] = 1 - - # Localization Loss (Smooth L1) - # Shape: [batch,num_priors,4] - pos_idx = pos.unsqueeze(pos.dim()).expand_as(loc_data) - loc_p = loc_data[pos_idx].view(-1, 4) - loc_t = loc_t[pos_idx].view(-1, 4) - loss_l = F.smooth_l1_loss(loc_p, loc_t, reduction='sum') - - # Compute max conf across batch for hard negative mining - batch_conf = conf_data.view(-1, self.num_classes) - loss_c = log_sum_exp(batch_conf) - batch_conf.gather(1, conf_t.view(-1, 1)) - - # Hard Negative Mining - loss_c[pos.view(-1, 1)] = 0 # filter out pos boxes for now - loss_c = loss_c.view(num, -1) - _, loss_idx = loss_c.sort(1, descending=True) - _, idx_rank = loss_idx.sort(1) - num_pos = pos.long().sum(1, keepdim=True) - num_neg = torch.clamp(self.negpos_ratio*num_pos, max=pos.size(1)-1) - neg = idx_rank < num_neg.expand_as(idx_rank) - - # Confidence Loss Including Positive and Negative Examples - pos_idx = pos.unsqueeze(2).expand_as(conf_data) - neg_idx = neg.unsqueeze(2).expand_as(conf_data) - conf_p = conf_data[(pos_idx+neg_idx).gt(0)].view(-1,self.num_classes) - targets_weighted = conf_t[(pos+neg).gt(0)] - loss_c = F.cross_entropy(conf_p, targets_weighted, reduction='sum') - - # Sum of losses: L(x,c,l,g) = (Lconf(x, c) + αLloc(x,l,g)) / N - N = max(num_pos.data.sum().float(), 1) - loss_l /= N - loss_c /= N - loss_landm /= N1 - - return loss_l, loss_c, loss_landm diff --git a/third_part/GPEN/face_detect/retinaface_detection.py b/third_part/GPEN/face_detect/retinaface_detection.py deleted file mode 100644 index 3b540c9..0000000 --- a/third_part/GPEN/face_detect/retinaface_detection.py +++ /dev/null @@ -1,193 +0,0 @@ -''' -@paper: GAN Prior Embedded Network for Blind Face Restoration in the Wild (CVPR2021) -@author: yangxy (yangtao9009@gmail.com) -''' -import os -import torch -import torch.backends.cudnn as cudnn -import numpy as np -from custom_nodes.ComfyUI_Lam.third_part.GPEN.face_detect.data import cfg_re50 -from custom_nodes.ComfyUI_Lam.third_part.GPEN.face_detect.layers.functions.prior_box import PriorBox -from custom_nodes.ComfyUI_Lam.third_part.GPEN.face_detect.utils.nms.py_cpu_nms import py_cpu_nms -import cv2 -from custom_nodes.ComfyUI_Lam.third_part.GPEN.face_detect.facemodels.retinaface import RetinaFace -from custom_nodes.ComfyUI_Lam.third_part.GPEN.face_detect.utils.box_utils import decode, decode_landm -import time -import torch.nn.functional as F - - -class RetinaFaceDetection(object): - def __init__(self, base_dir, device='cuda', network='RetinaFace-R50'): - torch.set_grad_enabled(False) - cudnn.benchmark = True - self.pretrained_path = os.path.join(base_dir, network+'.pth') - self.device = device #torch.cuda.current_device() - self.cfg = cfg_re50 - self.net = RetinaFace(cfg=self.cfg, phase='test') - self.load_model() - self.net = self.net.to(device) - - self.mean = torch.tensor([[[[104]], [[117]], [[123]]]]).to(device) - - def check_keys(self, pretrained_state_dict): - ckpt_keys = set(pretrained_state_dict.keys()) - model_keys = set(self.net.state_dict().keys()) - used_pretrained_keys = model_keys & ckpt_keys - unused_pretrained_keys = ckpt_keys - model_keys - missing_keys = model_keys - ckpt_keys - assert len(used_pretrained_keys) > 0, 'load NONE from pretrained checkpoint' - return True - - def remove_prefix(self, state_dict, prefix): - ''' Old style model is stored with all names of parameters sharing common prefix 'module.' ''' - f = lambda x: x.split(prefix, 1)[-1] if x.startswith(prefix) else x - return {f(key): value for key, value in state_dict.items()} - - def load_model(self, load_to_cpu=False): - #if load_to_cpu: - # pretrained_dict = torch.load(self.pretrained_path, map_location=lambda storage, loc: storage) - #else: - # pretrained_dict = torch.load(self.pretrained_path, map_location=lambda storage, loc: storage.cuda()) - pretrained_dict = torch.load(self.pretrained_path, map_location=torch.device('cpu')) - if "state_dict" in pretrained_dict.keys(): - pretrained_dict = self.remove_prefix(pretrained_dict['state_dict'], 'module.') - else: - pretrained_dict = self.remove_prefix(pretrained_dict, 'module.') - self.check_keys(pretrained_dict) - self.net.load_state_dict(pretrained_dict, strict=False) - self.net.eval() - - def detect(self, img_raw, resize=1, confidence_threshold=0.9, nms_threshold=0.4, top_k=5000, keep_top_k=750, save_image=False): - img = np.float32(img_raw) - - im_height, im_width = img.shape[:2] - ss = 1.0 - # tricky - if max(im_height, im_width) > 1500: - ss = 1000.0/max(im_height, im_width) - img = cv2.resize(img, (0,0), fx=ss, fy=ss) - im_height, im_width = img.shape[:2] - - scale = torch.Tensor([img.shape[1], img.shape[0], img.shape[1], img.shape[0]]) - img -= (104, 117, 123) - img = img.transpose(2, 0, 1) - img = torch.from_numpy(img).unsqueeze(0) - img = img.to(self.device) - scale = scale.to(self.device) - - with torch.no_grad(): - loc, conf, landms = self.net(img) # forward pass - - priorbox = PriorBox(self.cfg, image_size=(im_height, im_width)) - priors = priorbox.forward() - priors = priors.to(self.device) - prior_data = priors.data - boxes = decode(loc.data.squeeze(0), prior_data, self.cfg['variance']) - boxes = boxes * scale / resize - boxes = boxes.cpu().numpy() - scores = conf.squeeze(0).data.cpu().numpy()[:, 1] - landms = decode_landm(landms.data.squeeze(0), prior_data, self.cfg['variance']) - scale1 = torch.Tensor([img.shape[3], img.shape[2], img.shape[3], img.shape[2], - img.shape[3], img.shape[2], img.shape[3], img.shape[2], - img.shape[3], img.shape[2]]) - scale1 = scale1.to(self.device) - landms = landms * scale1 / resize - landms = landms.cpu().numpy() - - # ignore low scores - inds = np.where(scores > confidence_threshold)[0] - boxes = boxes[inds] - landms = landms[inds] - scores = scores[inds] - - # keep top-K before NMS - order = scores.argsort()[::-1][:top_k] - boxes = boxes[order] - landms = landms[order] - scores = scores[order] - - # do NMS - dets = np.hstack((boxes, scores[:, np.newaxis])).astype(np.float32, copy=False) - keep = py_cpu_nms(dets, nms_threshold) - # keep = nms(dets, nms_threshold,force_cpu=args.cpu) - dets = dets[keep, :] - landms = landms[keep] - - # keep top-K faster NMS - dets = dets[:keep_top_k, :] - landms = landms[:keep_top_k, :] - - # sort faces(delete) - ''' - fscores = [det[4] for det in dets] - sorted_idx = sorted(range(len(fscores)), key=lambda k:fscores[k], reverse=False) # sort index - tmp = [landms[idx] for idx in sorted_idx] - landms = np.asarray(tmp) - ''' - - landms = landms.reshape((-1, 5, 2)) - landms = landms.transpose((0, 2, 1)) - landms = landms.reshape(-1, 10, ) - return dets/ss, landms/ss - - def detect_tensor(self, img, resize=1, confidence_threshold=0.9, nms_threshold=0.4, top_k=5000, keep_top_k=750, save_image=False): - im_height, im_width = img.shape[-2:] - ss = 1000/max(im_height, im_width) - img = F.interpolate(img, scale_factor=ss) - im_height, im_width = img.shape[-2:] - scale = torch.Tensor([im_width, im_height, im_width, im_height]).to(self.device) - img -= self.mean - - loc, conf, landms = self.net(img) # forward pass - - priorbox = PriorBox(self.cfg, image_size=(im_height, im_width)) - priors = priorbox.forward() - priors = priors.to(self.device) - prior_data = priors.data - boxes = decode(loc.data.squeeze(0), prior_data, self.cfg['variance']) - boxes = boxes * scale / resize - boxes = boxes.cpu().numpy() - scores = conf.squeeze(0).data.cpu().numpy()[:, 1] - landms = decode_landm(landms.data.squeeze(0), prior_data, self.cfg['variance']) - scale1 = torch.Tensor([img.shape[3], img.shape[2], img.shape[3], img.shape[2], - img.shape[3], img.shape[2], img.shape[3], img.shape[2], - img.shape[3], img.shape[2]]) - scale1 = scale1.to(self.device) - landms = landms * scale1 / resize - landms = landms.cpu().numpy() - - # ignore low scores - inds = np.where(scores > confidence_threshold)[0] - boxes = boxes[inds] - landms = landms[inds] - scores = scores[inds] - - # keep top-K before NMS - order = scores.argsort()[::-1][:top_k] - boxes = boxes[order] - landms = landms[order] - scores = scores[order] - - # do NMS - dets = np.hstack((boxes, scores[:, np.newaxis])).astype(np.float32, copy=False) - keep = py_cpu_nms(dets, nms_threshold) - # keep = nms(dets, nms_threshold,force_cpu=args.cpu) - dets = dets[keep, :] - landms = landms[keep] - - # keep top-K faster NMS - dets = dets[:keep_top_k, :] - landms = landms[:keep_top_k, :] - - # sort faces(delete) - ''' - fscores = [det[4] for det in dets] - sorted_idx = sorted(range(len(fscores)), key=lambda k:fscores[k], reverse=False) # sort index - tmp = [landms[idx] for idx in sorted_idx] - landms = np.asarray(tmp) - ''' - - landms = landms.reshape((-1, 5, 2)) - landms = landms.transpose((0, 2, 1)) - landms = landms.reshape(-1, 10, ) - return dets/ss, landms/ss diff --git a/third_part/GPEN/face_detect/utils/__init__.py b/third_part/GPEN/face_detect/utils/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/third_part/GPEN/face_detect/utils/box_utils.py b/third_part/GPEN/face_detect/utils/box_utils.py deleted file mode 100644 index c1d12bc..0000000 --- a/third_part/GPEN/face_detect/utils/box_utils.py +++ /dev/null @@ -1,330 +0,0 @@ -import torch -import numpy as np - - -def point_form(boxes): - """ Convert prior_boxes to (xmin, ymin, xmax, ymax) - representation for comparison to point form ground truth data. - Args: - boxes: (tensor) center-size default boxes from priorbox layers. - Return: - boxes: (tensor) Converted xmin, ymin, xmax, ymax form of boxes. - """ - return torch.cat((boxes[:, :2] - boxes[:, 2:]/2, # xmin, ymin - boxes[:, :2] + boxes[:, 2:]/2), 1) # xmax, ymax - - -def center_size(boxes): - """ Convert prior_boxes to (cx, cy, w, h) - representation for comparison to center-size form ground truth data. - Args: - boxes: (tensor) point_form boxes - Return: - boxes: (tensor) Converted xmin, ymin, xmax, ymax form of boxes. - """ - return torch.cat((boxes[:, 2:] + boxes[:, :2])/2, # cx, cy - boxes[:, 2:] - boxes[:, :2], 1) # w, h - - -def intersect(box_a, box_b): - """ We resize both tensors to [A,B,2] without new malloc: - [A,2] -> [A,1,2] -> [A,B,2] - [B,2] -> [1,B,2] -> [A,B,2] - Then we compute the area of intersect between box_a and box_b. - Args: - box_a: (tensor) bounding boxes, Shape: [A,4]. - box_b: (tensor) bounding boxes, Shape: [B,4]. - Return: - (tensor) intersection area, Shape: [A,B]. - """ - A = box_a.size(0) - B = box_b.size(0) - max_xy = torch.min(box_a[:, 2:].unsqueeze(1).expand(A, B, 2), - box_b[:, 2:].unsqueeze(0).expand(A, B, 2)) - min_xy = torch.max(box_a[:, :2].unsqueeze(1).expand(A, B, 2), - box_b[:, :2].unsqueeze(0).expand(A, B, 2)) - inter = torch.clamp((max_xy - min_xy), min=0) - return inter[:, :, 0] * inter[:, :, 1] - - -def jaccard(box_a, box_b): - """Compute the jaccard overlap of two sets of boxes. The jaccard overlap - is simply the intersection over union of two boxes. Here we operate on - ground truth boxes and default boxes. - E.g.: - A ∩ B / A ∪ B = A ∩ B / (area(A) + area(B) - A ∩ B) - Args: - box_a: (tensor) Ground truth bounding boxes, Shape: [num_objects,4] - box_b: (tensor) Prior boxes from priorbox layers, Shape: [num_priors,4] - Return: - jaccard overlap: (tensor) Shape: [box_a.size(0), box_b.size(0)] - """ - inter = intersect(box_a, box_b) - area_a = ((box_a[:, 2]-box_a[:, 0]) * - (box_a[:, 3]-box_a[:, 1])).unsqueeze(1).expand_as(inter) # [A,B] - area_b = ((box_b[:, 2]-box_b[:, 0]) * - (box_b[:, 3]-box_b[:, 1])).unsqueeze(0).expand_as(inter) # [A,B] - union = area_a + area_b - inter - return inter / union # [A,B] - - -def matrix_iou(a, b): - """ - return iou of a and b, numpy version for data augenmentation - """ - lt = np.maximum(a[:, np.newaxis, :2], b[:, :2]) - rb = np.minimum(a[:, np.newaxis, 2:], b[:, 2:]) - - area_i = np.prod(rb - lt, axis=2) * (lt < rb).all(axis=2) - area_a = np.prod(a[:, 2:] - a[:, :2], axis=1) - area_b = np.prod(b[:, 2:] - b[:, :2], axis=1) - return area_i / (area_a[:, np.newaxis] + area_b - area_i) - - -def matrix_iof(a, b): - """ - return iof of a and b, numpy version for data augenmentation - """ - lt = np.maximum(a[:, np.newaxis, :2], b[:, :2]) - rb = np.minimum(a[:, np.newaxis, 2:], b[:, 2:]) - - area_i = np.prod(rb - lt, axis=2) * (lt < rb).all(axis=2) - area_a = np.prod(a[:, 2:] - a[:, :2], axis=1) - return area_i / np.maximum(area_a[:, np.newaxis], 1) - - -def match(threshold, truths, priors, variances, labels, landms, loc_t, conf_t, landm_t, idx): - """Match each prior box with the ground truth box of the highest jaccard - overlap, encode the bounding boxes, then return the matched indices - corresponding to both confidence and location preds. - Args: - threshold: (float) The overlap threshold used when mathing boxes. - truths: (tensor) Ground truth boxes, Shape: [num_obj, 4]. - priors: (tensor) Prior boxes from priorbox layers, Shape: [n_priors,4]. - variances: (tensor) Variances corresponding to each prior coord, - Shape: [num_priors, 4]. - labels: (tensor) All the class labels for the image, Shape: [num_obj]. - landms: (tensor) Ground truth landms, Shape [num_obj, 10]. - loc_t: (tensor) Tensor to be filled w/ endcoded location targets. - conf_t: (tensor) Tensor to be filled w/ matched indices for conf preds. - landm_t: (tensor) Tensor to be filled w/ endcoded landm targets. - idx: (int) current batch index - Return: - The matched indices corresponding to 1)location 2)confidence 3)landm preds. - """ - # jaccard index - overlaps = jaccard( - truths, - point_form(priors) - ) - # (Bipartite Matching) - # [1,num_objects] best prior for each ground truth - best_prior_overlap, best_prior_idx = overlaps.max(1, keepdim=True) - - # ignore hard gt - valid_gt_idx = best_prior_overlap[:, 0] >= 0.2 - best_prior_idx_filter = best_prior_idx[valid_gt_idx, :] - if best_prior_idx_filter.shape[0] <= 0: - loc_t[idx] = 0 - conf_t[idx] = 0 - return - - # [1,num_priors] best ground truth for each prior - best_truth_overlap, best_truth_idx = overlaps.max(0, keepdim=True) - best_truth_idx.squeeze_(0) - best_truth_overlap.squeeze_(0) - best_prior_idx.squeeze_(1) - best_prior_idx_filter.squeeze_(1) - best_prior_overlap.squeeze_(1) - best_truth_overlap.index_fill_(0, best_prior_idx_filter, 2) # ensure best prior - # TODO refactor: index best_prior_idx with long tensor - # ensure every gt matches with its prior of max overlap - for j in range(best_prior_idx.size(0)): # 判别此anchor是预测哪一个boxes - best_truth_idx[best_prior_idx[j]] = j - matches = truths[best_truth_idx] # Shape: [num_priors,4] 此处为每一个anchor对应的bbox取出来 - conf = labels[best_truth_idx] # Shape: [num_priors] 此处为每一个anchor对应的label取出来 - conf[best_truth_overlap < threshold] = 0 # label as background overlap<0.35的全部作为负样本 - loc = encode(matches, priors, variances) - - matches_landm = landms[best_truth_idx] - landm = encode_landm(matches_landm, priors, variances) - loc_t[idx] = loc # [num_priors,4] encoded offsets to learn - conf_t[idx] = conf # [num_priors] top class label for each prior - landm_t[idx] = landm - - -def encode(matched, priors, variances): - """Encode the variances from the priorbox layers into the ground truth boxes - we have matched (based on jaccard overlap) with the prior boxes. - Args: - matched: (tensor) Coords of ground truth for each prior in point-form - Shape: [num_priors, 4]. - priors: (tensor) Prior boxes in center-offset form - Shape: [num_priors,4]. - variances: (list[float]) Variances of priorboxes - Return: - encoded boxes (tensor), Shape: [num_priors, 4] - """ - - # dist b/t match center and prior's center - g_cxcy = (matched[:, :2] + matched[:, 2:])/2 - priors[:, :2] - # encode variance - g_cxcy /= (variances[0] * priors[:, 2:]) - # match wh / prior wh - g_wh = (matched[:, 2:] - matched[:, :2]) / priors[:, 2:] - g_wh = torch.log(g_wh) / variances[1] - # return target for smooth_l1_loss - return torch.cat([g_cxcy, g_wh], 1) # [num_priors,4] - -def encode_landm(matched, priors, variances): - """Encode the variances from the priorbox layers into the ground truth boxes - we have matched (based on jaccard overlap) with the prior boxes. - Args: - matched: (tensor) Coords of ground truth for each prior in point-form - Shape: [num_priors, 10]. - priors: (tensor) Prior boxes in center-offset form - Shape: [num_priors,4]. - variances: (list[float]) Variances of priorboxes - Return: - encoded landm (tensor), Shape: [num_priors, 10] - """ - - # dist b/t match center and prior's center - matched = torch.reshape(matched, (matched.size(0), 5, 2)) - priors_cx = priors[:, 0].unsqueeze(1).expand(matched.size(0), 5).unsqueeze(2) - priors_cy = priors[:, 1].unsqueeze(1).expand(matched.size(0), 5).unsqueeze(2) - priors_w = priors[:, 2].unsqueeze(1).expand(matched.size(0), 5).unsqueeze(2) - priors_h = priors[:, 3].unsqueeze(1).expand(matched.size(0), 5).unsqueeze(2) - priors = torch.cat([priors_cx, priors_cy, priors_w, priors_h], dim=2) - g_cxcy = matched[:, :, :2] - priors[:, :, :2] - # encode variance - g_cxcy /= (variances[0] * priors[:, :, 2:]) - # g_cxcy /= priors[:, :, 2:] - g_cxcy = g_cxcy.reshape(g_cxcy.size(0), -1) - # return target for smooth_l1_loss - return g_cxcy - - -# Adapted from https://github.com/Hakuyume/chainer-ssd -def decode(loc, priors, variances): - """Decode locations from predictions using priors to undo - the encoding we did for offset regression at train time. - Args: - loc (tensor): location predictions for loc layers, - Shape: [num_priors,4] - priors (tensor): Prior boxes in center-offset form. - Shape: [num_priors,4]. - variances: (list[float]) Variances of priorboxes - Return: - decoded bounding box predictions - """ - - boxes = torch.cat(( - priors[:, :2] + loc[:, :2] * variances[0] * priors[:, 2:], - priors[:, 2:] * torch.exp(loc[:, 2:] * variances[1])), 1) - boxes[:, :2] -= boxes[:, 2:] / 2 - boxes[:, 2:] += boxes[:, :2] - return boxes - -def decode_landm(pre, priors, variances): - """Decode landm from predictions using priors to undo - the encoding we did for offset regression at train time. - Args: - pre (tensor): landm predictions for loc layers, - Shape: [num_priors,10] - priors (tensor): Prior boxes in center-offset form. - Shape: [num_priors,4]. - variances: (list[float]) Variances of priorboxes - Return: - decoded landm predictions - """ - landms = torch.cat((priors[:, :2] + pre[:, :2] * variances[0] * priors[:, 2:], - priors[:, :2] + pre[:, 2:4] * variances[0] * priors[:, 2:], - priors[:, :2] + pre[:, 4:6] * variances[0] * priors[:, 2:], - priors[:, :2] + pre[:, 6:8] * variances[0] * priors[:, 2:], - priors[:, :2] + pre[:, 8:10] * variances[0] * priors[:, 2:], - ), dim=1) - return landms - - -def log_sum_exp(x): - """Utility function for computing log_sum_exp while determining - This will be used to determine unaveraged confidence loss across - all examples in a batch. - Args: - x (Variable(tensor)): conf_preds from conf layers - """ - x_max = x.data.max() - return torch.log(torch.sum(torch.exp(x-x_max), 1, keepdim=True)) + x_max - - -# Original author: Francisco Massa: -# https://github.com/fmassa/object-detection.torch -# Ported to PyTorch by Max deGroot (02/01/2017) -def nms(boxes, scores, overlap=0.5, top_k=200): - """Apply non-maximum suppression at test time to avoid detecting too many - overlapping bounding boxes for a given object. - Args: - boxes: (tensor) The location preds for the img, Shape: [num_priors,4]. - scores: (tensor) The class predscores for the img, Shape:[num_priors]. - overlap: (float) The overlap thresh for suppressing unnecessary boxes. - top_k: (int) The Maximum number of box preds to consider. - Return: - The indices of the kept boxes with respect to num_priors. - """ - - keep = torch.Tensor(scores.size(0)).fill_(0).long() - if boxes.numel() == 0: - return keep - x1 = boxes[:, 0] - y1 = boxes[:, 1] - x2 = boxes[:, 2] - y2 = boxes[:, 3] - area = torch.mul(x2 - x1, y2 - y1) - v, idx = scores.sort(0) # sort in ascending order - # I = I[v >= 0.01] - idx = idx[-top_k:] # indices of the top-k largest vals - xx1 = boxes.new() - yy1 = boxes.new() - xx2 = boxes.new() - yy2 = boxes.new() - w = boxes.new() - h = boxes.new() - - # keep = torch.Tensor() - count = 0 - while idx.numel() > 0: - i = idx[-1] # index of current largest val - # keep.append(i) - keep[count] = i - count += 1 - if idx.size(0) == 1: - break - idx = idx[:-1] # remove kept element from view - # load bboxes of next highest vals - torch.index_select(x1, 0, idx, out=xx1) - torch.index_select(y1, 0, idx, out=yy1) - torch.index_select(x2, 0, idx, out=xx2) - torch.index_select(y2, 0, idx, out=yy2) - # store element-wise max with next highest score - xx1 = torch.clamp(xx1, min=x1[i]) - yy1 = torch.clamp(yy1, min=y1[i]) - xx2 = torch.clamp(xx2, max=x2[i]) - yy2 = torch.clamp(yy2, max=y2[i]) - w.resize_as_(xx2) - h.resize_as_(yy2) - w = xx2 - xx1 - h = yy2 - yy1 - # check sizes of xx1 and xx2.. after each iteration - w = torch.clamp(w, min=0.0) - h = torch.clamp(h, min=0.0) - inter = w*h - # IoU = i / (area(a) + area(b) - i) - rem_areas = torch.index_select(area, 0, idx) # load remaining areas) - union = (rem_areas - inter) + area[i] - IoU = inter/union # store result in iou - # keep only elements with an IoU <= overlap - idx = idx[IoU.le(overlap)] - return keep, count - - diff --git a/third_part/GPEN/face_detect/utils/nms/__init__.py b/third_part/GPEN/face_detect/utils/nms/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/third_part/GPEN/face_detect/utils/nms/py_cpu_nms.py b/third_part/GPEN/face_detect/utils/nms/py_cpu_nms.py deleted file mode 100644 index 54e7b25..0000000 --- a/third_part/GPEN/face_detect/utils/nms/py_cpu_nms.py +++ /dev/null @@ -1,38 +0,0 @@ -# -------------------------------------------------------- -# Fast R-CNN -# Copyright (c) 2015 Microsoft -# Licensed under The MIT License [see LICENSE for details] -# Written by Ross Girshick -# -------------------------------------------------------- - -import numpy as np - -def py_cpu_nms(dets, thresh): - """Pure Python NMS baseline.""" - x1 = dets[:, 0] - y1 = dets[:, 1] - x2 = dets[:, 2] - y2 = dets[:, 3] - scores = dets[:, 4] - - areas = (x2 - x1 + 1) * (y2 - y1 + 1) - order = scores.argsort()[::-1] - - keep = [] - while order.size > 0: - i = order[0] - keep.append(i) - xx1 = np.maximum(x1[i], x1[order[1:]]) - yy1 = np.maximum(y1[i], y1[order[1:]]) - xx2 = np.minimum(x2[i], x2[order[1:]]) - yy2 = np.minimum(y2[i], y2[order[1:]]) - - w = np.maximum(0.0, xx2 - xx1 + 1) - h = np.maximum(0.0, yy2 - yy1 + 1) - inter = w * h - ovr = inter / (areas[i] + areas[order[1:]] - inter) - - inds = np.where(ovr <= thresh)[0] - order = order[inds + 1] - - return keep diff --git a/third_part/GPEN/face_detect/utils/timer.py b/third_part/GPEN/face_detect/utils/timer.py deleted file mode 100644 index e4b3b80..0000000 --- a/third_part/GPEN/face_detect/utils/timer.py +++ /dev/null @@ -1,40 +0,0 @@ -# -------------------------------------------------------- -# Fast R-CNN -# Copyright (c) 2015 Microsoft -# Licensed under The MIT License [see LICENSE for details] -# Written by Ross Girshick -# -------------------------------------------------------- - -import time - - -class Timer(object): - """A simple timer.""" - def __init__(self): - self.total_time = 0. - self.calls = 0 - self.start_time = 0. - self.diff = 0. - self.average_time = 0. - - def tic(self): - # using time.time instead of time.clock because time time.clock - # does not normalize for multithreading - self.start_time = time.time() - - def toc(self, average=True): - self.diff = time.time() - self.start_time - self.total_time += self.diff - self.calls += 1 - self.average_time = self.total_time / self.calls - if average: - return self.average_time - else: - return self.diff - - def clear(self): - self.total_time = 0. - self.calls = 0 - self.start_time = 0. - self.diff = 0. - self.average_time = 0. diff --git a/third_part/GPEN/face_model/face_gan.py b/third_part/GPEN/face_model/face_gan.py deleted file mode 100644 index 39e837d..0000000 --- a/third_part/GPEN/face_model/face_gan.py +++ /dev/null @@ -1,55 +0,0 @@ -''' -@paper: GAN Prior Embedded Network for Blind Face Restoration in the Wild (CVPR2021) -@author: yangxy (yangtao9009@gmail.com) -''' -import torch -import os -import cv2 -import glob -import numpy as np -from torch import nn -import torch.nn.functional as F -from torchvision import transforms, utils -from custom_nodes.ComfyUI_Lam.third_part.GPEN.face_model.gpen_model import FullGenerator - -class FaceGAN(object): - def __init__(self, base_dir='./', size=512, model=None, channel_multiplier=2, narrow=1, is_norm=True, device='cuda'): - self.mfile = os.path.join(base_dir, model+'.pth') - self.n_mlp = 8 - self.device = device - self.is_norm = is_norm - self.resolution = size - self.load_model(channel_multiplier, narrow) - - def load_model(self, channel_multiplier=2, narrow=1): - self.model = FullGenerator(self.resolution, 512, self.n_mlp, channel_multiplier, narrow=narrow, device=self.device) - pretrained_dict = torch.load(self.mfile, map_location=torch.device('cpu')) - self.model.load_state_dict(pretrained_dict) - self.model.to(self.device) - self.model.eval() - - def process(self, img): - img = cv2.resize(img, (self.resolution, self.resolution)) - img_t = self.img2tensor(img) - - with torch.no_grad(): - out, __ = self.model(img_t) - - out = self.tensor2img(out) - - return out - - def img2tensor(self, img): - img_t = torch.from_numpy(img).to(self.device)/255. - if self.is_norm: - img_t = (img_t - 0.5) / 0.5 - img_t = img_t.permute(2, 0, 1).unsqueeze(0).flip(1) # BGR->RGB - return img_t - - def tensor2img(self, img_t, pmax=255.0, imtype=np.uint8): - if self.is_norm: - img_t = img_t * 0.5 + 0.5 - img_t = img_t.squeeze(0).permute(1, 2, 0).flip(2) # RGB->BGR - img_np = np.clip(img_t.float().cpu().numpy(), 0, 1) * pmax - - return img_np.astype(imtype) diff --git a/third_part/GPEN/face_model/gpen_model.py b/third_part/GPEN/face_model/gpen_model.py deleted file mode 100644 index 3feda42..0000000 --- a/third_part/GPEN/face_model/gpen_model.py +++ /dev/null @@ -1,746 +0,0 @@ -''' -@paper: GAN Prior Embedded Network for Blind Face Restoration in the Wild (CVPR2021) -@author: yangxy (yangtao9009@gmail.com) -''' -import math -import random -import functools -import operator -import itertools - -import torch -from torch import nn -from torch.nn import functional as F -from torch.autograd import Function - -from custom_nodes.ComfyUI_Lam.third_part.GPEN.face_model.op import FusedLeakyReLU, fused_leaky_relu, upfirdn2d - -class PixelNorm(nn.Module): - def __init__(self): - super().__init__() - - def forward(self, input): - return input * torch.rsqrt(torch.mean(input ** 2, dim=1, keepdim=True) + 1e-8) - - -def make_kernel(k): - k = torch.tensor(k, dtype=torch.float32) - - if k.ndim == 1: - k = k[None, :] * k[:, None] - - k /= k.sum() - - return k - - -class Upsample(nn.Module): - def __init__(self, kernel, factor=2, device='cpu'): - super().__init__() - - self.factor = factor - kernel = make_kernel(kernel) * (factor ** 2) - self.register_buffer('kernel', kernel) - - p = kernel.shape[0] - factor - - pad0 = (p + 1) // 2 + factor - 1 - pad1 = p // 2 - - self.pad = (pad0, pad1) - self.device = device - - def forward(self, input): - out = upfirdn2d(input, self.kernel, up=self.factor, down=1, pad=self.pad, device=self.device) - - return out - - -class Downsample(nn.Module): - def __init__(self, kernel, factor=2, device='cpu'): - super().__init__() - - self.factor = factor - kernel = make_kernel(kernel) - self.register_buffer('kernel', kernel) - - p = kernel.shape[0] - factor - - pad0 = (p + 1) // 2 - pad1 = p // 2 - - self.pad = (pad0, pad1) - self.device = device - - def forward(self, input): - out = upfirdn2d(input, self.kernel, up=1, down=self.factor, pad=self.pad, device=self.device) - - return out - - -class Blur(nn.Module): - def __init__(self, kernel, pad, upsample_factor=1, device='cpu'): - super().__init__() - - kernel = make_kernel(kernel) - - if upsample_factor > 1: - kernel = kernel * (upsample_factor ** 2) - - self.register_buffer('kernel', kernel) - - self.pad = pad - self.device = device - - def forward(self, input): - out = upfirdn2d(input, self.kernel, pad=self.pad, device=self.device) - - return out - - -class EqualConv2d(nn.Module): - def __init__( - self, in_channel, out_channel, kernel_size, stride=1, padding=0, bias=True - ): - super().__init__() - - self.weight = nn.Parameter( - torch.randn(out_channel, in_channel, kernel_size, kernel_size) - ) - self.scale = 1 / math.sqrt(in_channel * kernel_size ** 2) - - self.stride = stride - self.padding = padding - - if bias: - self.bias = nn.Parameter(torch.zeros(out_channel)) - - else: - self.bias = None - - def forward(self, input): - out = F.conv2d( - input, - self.weight * self.scale, - bias=self.bias, - stride=self.stride, - padding=self.padding, - ) - - return out - - def __repr__(self): - return ( - f'{self.__class__.__name__}({self.weight.shape[1]}, {self.weight.shape[0]},' - f' {self.weight.shape[2]}, stride={self.stride}, padding={self.padding})' - ) - - -class EqualLinear(nn.Module): - def __init__( - self, in_dim, out_dim, bias=True, bias_init=0, lr_mul=1, activation=None, device='cpu' - ): - super().__init__() - - self.weight = nn.Parameter(torch.randn(out_dim, in_dim).div_(lr_mul)) - - if bias: - self.bias = nn.Parameter(torch.zeros(out_dim).fill_(bias_init)) - - else: - self.bias = None - - self.activation = activation - self.device = device - - self.scale = (1 / math.sqrt(in_dim)) * lr_mul - self.lr_mul = lr_mul - - def forward(self, input): - if self.activation: - out = F.linear(input, self.weight * self.scale) - out = fused_leaky_relu(out, self.bias * self.lr_mul, device=self.device) - - else: - out = F.linear(input, self.weight * self.scale, bias=self.bias * self.lr_mul) - - return out - - def __repr__(self): - return ( - f'{self.__class__.__name__}({self.weight.shape[1]}, {self.weight.shape[0]})' - ) - - -class ScaledLeakyReLU(nn.Module): - def __init__(self, negative_slope=0.2): - super().__init__() - - self.negative_slope = negative_slope - - def forward(self, input): - out = F.leaky_relu(input, negative_slope=self.negative_slope) - - return out * math.sqrt(2) - - -class ModulatedConv2d(nn.Module): - def __init__( - self, - in_channel, - out_channel, - kernel_size, - style_dim, - demodulate=True, - upsample=False, - downsample=False, - blur_kernel=[1, 3, 3, 1], - device='cpu' - ): - super().__init__() - - self.eps = 1e-8 - self.kernel_size = kernel_size - self.in_channel = in_channel - self.out_channel = out_channel - self.upsample = upsample - self.downsample = downsample - - if upsample: - factor = 2 - p = (len(blur_kernel) - factor) - (kernel_size - 1) - pad0 = (p + 1) // 2 + factor - 1 - pad1 = p // 2 + 1 - - self.blur = Blur(blur_kernel, pad=(pad0, pad1), upsample_factor=factor, device=device) - - if downsample: - factor = 2 - p = (len(blur_kernel) - factor) + (kernel_size - 1) - pad0 = (p + 1) // 2 - pad1 = p // 2 - - self.blur = Blur(blur_kernel, pad=(pad0, pad1), device=device) - - fan_in = in_channel * kernel_size ** 2 - self.scale = 1 / math.sqrt(fan_in) - self.padding = kernel_size // 2 - - self.weight = nn.Parameter( - torch.randn(1, out_channel, in_channel, kernel_size, kernel_size) - ) - - self.modulation = EqualLinear(style_dim, in_channel, bias_init=1) - - self.demodulate = demodulate - - def __repr__(self): - return ( - f'{self.__class__.__name__}({self.in_channel}, {self.out_channel}, {self.kernel_size}, ' - f'upsample={self.upsample}, downsample={self.downsample})' - ) - - def forward(self, input, style): - batch, in_channel, height, width = input.shape - - style = self.modulation(style).view(batch, 1, in_channel, 1, 1) - weight = self.scale * self.weight * style - - if self.demodulate: - demod = torch.rsqrt(weight.pow(2).sum([2, 3, 4]) + 1e-8) - weight = weight * demod.view(batch, self.out_channel, 1, 1, 1) - - weight = weight.view( - batch * self.out_channel, in_channel, self.kernel_size, self.kernel_size - ) - - if self.upsample: - input = input.view(1, batch * in_channel, height, width) - weight = weight.view( - batch, self.out_channel, in_channel, self.kernel_size, self.kernel_size - ) - weight = weight.transpose(1, 2).reshape( - batch * in_channel, self.out_channel, self.kernel_size, self.kernel_size - ) - out = F.conv_transpose2d(input, weight, padding=0, stride=2, groups=batch) - _, _, height, width = out.shape - out = out.view(batch, self.out_channel, height, width) - out = self.blur(out) - - elif self.downsample: - input = self.blur(input) - _, _, height, width = input.shape - input = input.view(1, batch * in_channel, height, width) - out = F.conv2d(input, weight, padding=0, stride=2, groups=batch) - _, _, height, width = out.shape - out = out.view(batch, self.out_channel, height, width) - - else: - input = input.view(1, batch * in_channel, height, width) - out = F.conv2d(input, weight, padding=self.padding, groups=batch) - _, _, height, width = out.shape - out = out.view(batch, self.out_channel, height, width) - - return out - - -class NoiseInjection(nn.Module): - def __init__(self, isconcat=True): - super().__init__() - - self.isconcat = isconcat - self.weight = nn.Parameter(torch.zeros(1)) - - def forward(self, image, noise=None): - if noise is None: - batch, _, height, width = image.shape - noise = image.new_empty(batch, 1, height, width).normal_() - - if self.isconcat: - return torch.cat((image, self.weight * noise), dim=1) - else: - return image + self.weight * noise - - -class ConstantInput(nn.Module): - def __init__(self, channel, size=4): - super().__init__() - - self.input = nn.Parameter(torch.randn(1, channel, size, size)) - - def forward(self, input): - batch = input.shape[0] - out = self.input.repeat(batch, 1, 1, 1) - - return out - - -class StyledConv(nn.Module): - def __init__( - self, - in_channel, - out_channel, - kernel_size, - style_dim, - upsample=False, - blur_kernel=[1, 3, 3, 1], - demodulate=True, - isconcat=True, - device='cpu' - ): - super().__init__() - - self.conv = ModulatedConv2d( - in_channel, - out_channel, - kernel_size, - style_dim, - upsample=upsample, - blur_kernel=blur_kernel, - demodulate=demodulate, - device=device - ) - - self.noise = NoiseInjection(isconcat) - #self.bias = nn.Parameter(torch.zeros(1, out_channel, 1, 1)) - #self.activate = ScaledLeakyReLU(0.2) - feat_multiplier = 2 if isconcat else 1 - self.activate = FusedLeakyReLU(out_channel*feat_multiplier, device=device) - - def forward(self, input, style, noise=None): - out = self.conv(input, style) - out = self.noise(out, noise=noise) - # out = out + self.bias - out = self.activate(out) - - return out - - -class ToRGB(nn.Module): - def __init__(self, in_channel, style_dim, upsample=True, blur_kernel=[1, 3, 3, 1], device='cpu'): - super().__init__() - - if upsample: - self.upsample = Upsample(blur_kernel, device=device) - - self.conv = ModulatedConv2d(in_channel, 3, 1, style_dim, demodulate=False, device=device) - self.bias = nn.Parameter(torch.zeros(1, 3, 1, 1)) - - def forward(self, input, style, skip=None): - out = self.conv(input, style) - out = out + self.bias - - if skip is not None: - skip = self.upsample(skip) - - out = out + skip - - return out - -class Generator(nn.Module): - def __init__( - self, - size, - style_dim, - n_mlp, - channel_multiplier=2, - blur_kernel=[1, 3, 3, 1], - lr_mlp=0.01, - isconcat=True, - narrow=1, - device='cpu' - ): - super().__init__() - - self.size = size - self.n_mlp = n_mlp - self.style_dim = style_dim - self.feat_multiplier = 2 if isconcat else 1 - - layers = [PixelNorm()] - - for i in range(n_mlp): - layers.append( - EqualLinear( - style_dim, style_dim, lr_mul=lr_mlp, activation='fused_lrelu', device=device - ) - ) - - self.style = nn.Sequential(*layers) - - self.channels = { - 4: int(512 * narrow), - 8: int(512 * narrow), - 16: int(512 * narrow), - 32: int(512 * narrow), - 64: int(256 * channel_multiplier * narrow), - 128: int(128 * channel_multiplier * narrow), - 256: int(64 * channel_multiplier * narrow), - 512: int(32 * channel_multiplier * narrow), - 1024: int(16 * channel_multiplier * narrow) - } - - self.input = ConstantInput(self.channels[4]) - self.conv1 = StyledConv( - self.channels[4], self.channels[4], 3, style_dim, blur_kernel=blur_kernel, isconcat=isconcat, device=device - ) - self.to_rgb1 = ToRGB(self.channels[4]*self.feat_multiplier, style_dim, upsample=False, device=device) - - self.log_size = int(math.log(size, 2)) - - self.convs = nn.ModuleList() - self.upsamples = nn.ModuleList() - self.to_rgbs = nn.ModuleList() - - in_channel = self.channels[4] - - for i in range(3, self.log_size + 1): - out_channel = self.channels[2 ** i] - - self.convs.append( - StyledConv( - in_channel*self.feat_multiplier, - out_channel, - 3, - style_dim, - upsample=True, - blur_kernel=blur_kernel, - isconcat=isconcat, - device=device - ) - ) - - self.convs.append( - StyledConv( - out_channel*self.feat_multiplier, out_channel, 3, style_dim, blur_kernel=blur_kernel, isconcat=isconcat, device=device - ) - ) - - self.to_rgbs.append(ToRGB(out_channel*self.feat_multiplier, style_dim, device=device)) - - in_channel = out_channel - - self.n_latent = self.log_size * 2 - 2 - - def make_noise(self): - device = self.input.input.device - - noises = [torch.randn(1, 1, 2 ** 2, 2 ** 2, device=device)] - - for i in range(3, self.log_size + 1): - for _ in range(2): - noises.append(torch.randn(1, 1, 2 ** i, 2 ** i, device=device)) - - return noises - - def mean_latent(self, n_latent): - latent_in = torch.randn( - n_latent, self.style_dim, device=self.input.input.device - ) - latent = self.style(latent_in).mean(0, keepdim=True) - - return latent - - def get_latent(self, input): - return self.style(input) - - def forward( - self, - styles, - return_latents=False, - inject_index=None, - truncation=1, - truncation_latent=None, - input_is_latent=False, - noise=None, - ): - if not input_is_latent: - styles = [self.style(s) for s in styles] - - if noise is None: - ''' - noise = [None] * (2 * (self.log_size - 2) + 1) - ''' - noise = [] - batch = styles[0].shape[0] - for i in range(self.n_mlp + 1): - size = 2 ** (i+2) - noise.append(torch.randn(batch, self.channels[size], size, size, device=styles[0].device)) - - if truncation < 1: - style_t = [] - - for style in styles: - style_t.append( - truncation_latent + truncation * (style - truncation_latent) - ) - - styles = style_t - - if len(styles) < 2: - inject_index = self.n_latent - - latent = styles[0].unsqueeze(1).repeat(1, inject_index, 1) - - else: - if inject_index is None: - inject_index = random.randint(1, self.n_latent - 1) - - latent = styles[0].unsqueeze(1).repeat(1, inject_index, 1) - latent2 = styles[1].unsqueeze(1).repeat(1, self.n_latent - inject_index, 1) - - latent = torch.cat([latent, latent2], 1) - - out = self.input(latent) - out = self.conv1(out, latent[:, 0], noise=noise[0]) - - skip = self.to_rgb1(out, latent[:, 1]) - - i = 1 - for conv1, conv2, noise1, noise2, to_rgb in zip( - self.convs[::2], self.convs[1::2], noise[1::2], noise[2::2], self.to_rgbs - ): - out = conv1(out, latent[:, i], noise=noise1) - out = conv2(out, latent[:, i + 1], noise=noise2) - skip = to_rgb(out, latent[:, i + 2], skip) - - i += 2 - - image = skip - - if return_latents: - return image, latent - - else: - return image, None - -class ConvLayer(nn.Sequential): - def __init__( - self, - in_channel, - out_channel, - kernel_size, - downsample=False, - blur_kernel=[1, 3, 3, 1], - bias=True, - activate=True, - device='cpu' - ): - layers = [] - - if downsample: - factor = 2 - p = (len(blur_kernel) - factor) + (kernel_size - 1) - pad0 = (p + 1) // 2 - pad1 = p // 2 - - layers.append(Blur(blur_kernel, pad=(pad0, pad1), device=device)) - - stride = 2 - self.padding = 0 - - else: - stride = 1 - self.padding = kernel_size // 2 - - layers.append( - EqualConv2d( - in_channel, - out_channel, - kernel_size, - padding=self.padding, - stride=stride, - bias=bias and not activate, - ) - ) - - if activate: - if bias: - layers.append(FusedLeakyReLU(out_channel, device=device)) - - else: - layers.append(ScaledLeakyReLU(0.2)) - - super().__init__(*layers) - - -class ResBlock(nn.Module): - def __init__(self, in_channel, out_channel, blur_kernel=[1, 3, 3, 1], device='cpu'): - super().__init__() - - self.conv1 = ConvLayer(in_channel, in_channel, 3, device=device) - self.conv2 = ConvLayer(in_channel, out_channel, 3, downsample=True, device=device) - - self.skip = ConvLayer( - in_channel, out_channel, 1, downsample=True, activate=False, bias=False - ) - - def forward(self, input): - out = self.conv1(input) - out = self.conv2(out) - - skip = self.skip(input) - out = (out + skip) / math.sqrt(2) - - return out - -class FullGenerator(nn.Module): - def __init__( - self, - size, - style_dim, - n_mlp, - channel_multiplier=2, - blur_kernel=[1, 3, 3, 1], - lr_mlp=0.01, - isconcat=True, - narrow=1, - device='cpu' - ): - super().__init__() - channels = { - 4: int(512 * narrow), - 8: int(512 * narrow), - 16: int(512 * narrow), - 32: int(512 * narrow), - 64: int(256 * channel_multiplier * narrow), - 128: int(128 * channel_multiplier * narrow), - 256: int(64 * channel_multiplier * narrow), - 512: int(32 * channel_multiplier * narrow), - 1024: int(16 * channel_multiplier * narrow) - } - - self.log_size = int(math.log(size, 2)) - self.generator = Generator(size, style_dim, n_mlp, channel_multiplier=channel_multiplier, blur_kernel=blur_kernel, lr_mlp=lr_mlp, isconcat=isconcat, narrow=narrow, device=device) - - conv = [ConvLayer(3, channels[size], 1, device=device)] - self.ecd0 = nn.Sequential(*conv) - in_channel = channels[size] - - self.names = ['ecd%d'%i for i in range(self.log_size-1)] - for i in range(self.log_size, 2, -1): - out_channel = channels[2 ** (i - 1)] - #conv = [ResBlock(in_channel, out_channel, blur_kernel)] - conv = [ConvLayer(in_channel, out_channel, 3, downsample=True, device=device)] - setattr(self, self.names[self.log_size-i+1], nn.Sequential(*conv)) - in_channel = out_channel - self.final_linear = nn.Sequential(EqualLinear(channels[4] * 4 * 4, style_dim, activation='fused_lrelu', device=device)) - - def forward(self, - inputs, - return_latents=False, - inject_index=None, - truncation=1, - truncation_latent=None, - input_is_latent=False, - ): - noise = [] - for i in range(self.log_size-1): - ecd = getattr(self, self.names[i]) - inputs = ecd(inputs) - noise.append(inputs) - - inputs = inputs.view(inputs.shape[0], -1) - outs = self.final_linear(inputs) - noise = list(itertools.chain.from_iterable(itertools.repeat(x, 2) for x in noise))[::-1] - outs = self.generator([outs], return_latents, inject_index, truncation, truncation_latent, input_is_latent, noise=noise[1:]) - return outs - -class Discriminator(nn.Module): - def __init__(self, size, channel_multiplier=2, blur_kernel=[1, 3, 3, 1], narrow=1, device='cpu'): - super().__init__() - - channels = { - 4: int(512 * narrow), - 8: int(512 * narrow), - 16: int(512 * narrow), - 32: int(512 * narrow), - 64: int(256 * channel_multiplier * narrow), - 128: int(128 * channel_multiplier * narrow), - 256: int(64 * channel_multiplier * narrow), - 512: int(32 * channel_multiplier * narrow), - 1024: int(16 * channel_multiplier * narrow) - } - - convs = [ConvLayer(3, channels[size], 1, device=device)] - - log_size = int(math.log(size, 2)) - - in_channel = channels[size] - - for i in range(log_size, 2, -1): - out_channel = channels[2 ** (i - 1)] - - convs.append(ResBlock(in_channel, out_channel, blur_kernel, device=device)) - - in_channel = out_channel - - self.convs = nn.Sequential(*convs) - - self.stddev_group = 4 - self.stddev_feat = 1 - - self.final_conv = ConvLayer(in_channel + 1, channels[4], 3, device=device) - self.final_linear = nn.Sequential( - EqualLinear(channels[4] * 4 * 4, channels[4], activation='fused_lrelu', device=device), - EqualLinear(channels[4], 1), - ) - - def forward(self, input): - out = self.convs(input) - - batch, channel, height, width = out.shape - group = min(batch, self.stddev_group) - stddev = out.view( - group, -1, self.stddev_feat, channel // self.stddev_feat, height, width - ) - stddev = torch.sqrt(stddev.var(0, unbiased=False) + 1e-8) - stddev = stddev.mean([2, 3, 4], keepdims=True).squeeze(2) - stddev = stddev.repeat(group, 1, height, width) - out = torch.cat([out, stddev], 1) - - out = self.final_conv(out) - - out = out.view(batch, -1) - out = self.final_linear(out) - return out diff --git a/third_part/GPEN/face_model/op/__init__.py b/third_part/GPEN/face_model/op/__init__.py deleted file mode 100644 index d0918d9..0000000 --- a/third_part/GPEN/face_model/op/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .fused_act import FusedLeakyReLU, fused_leaky_relu -from .upfirdn2d import upfirdn2d diff --git a/third_part/GPEN/face_model/op/fused_act.py b/third_part/GPEN/face_model/op/fused_act.py deleted file mode 100644 index 59db126..0000000 --- a/third_part/GPEN/face_model/op/fused_act.py +++ /dev/null @@ -1,96 +0,0 @@ -import os -import platform - -import torch -from torch import nn -import torch.nn.functional as F -from torch.autograd import Function -from torch.utils.cpp_extension import load, _import_module_from_library - -# if running GPEN without cuda, please comment line 11-19 -if platform.system() == 'Linux' and torch.cuda.is_available(): - module_path = os.path.dirname(__file__) - fused = load( - 'fused', - sources=[ - os.path.join(module_path, 'fused_bias_act.cpp'), - os.path.join(module_path, 'fused_bias_act_kernel.cu'), - ], - ) - - -#fused = _import_module_from_library('fused', '/tmp/torch_extensions/fused', True) - - -class FusedLeakyReLUFunctionBackward(Function): - @staticmethod - def forward(ctx, grad_output, out, negative_slope, scale): - ctx.save_for_backward(out) - ctx.negative_slope = negative_slope - ctx.scale = scale - - empty = grad_output.new_empty(0) - - grad_input = fused.fused_bias_act( - grad_output, empty, out, 3, 1, negative_slope, scale - ) - - dim = [0] - - if grad_input.ndim > 2: - dim += list(range(2, grad_input.ndim)) - - grad_bias = grad_input.sum(dim).detach() - - return grad_input, grad_bias - - @staticmethod - def backward(ctx, gradgrad_input, gradgrad_bias): - out, = ctx.saved_tensors - gradgrad_out = fused.fused_bias_act( - gradgrad_input, gradgrad_bias, out, 3, 1, ctx.negative_slope, ctx.scale - ) - - return gradgrad_out, None, None, None - - -class FusedLeakyReLUFunction(Function): - @staticmethod - def forward(ctx, input, bias, negative_slope, scale): - empty = input.new_empty(0) - out = fused.fused_bias_act(input, bias, empty, 3, 0, negative_slope, scale) - ctx.save_for_backward(out) - ctx.negative_slope = negative_slope - ctx.scale = scale - - return out - - @staticmethod - def backward(ctx, grad_output): - out, = ctx.saved_tensors - - grad_input, grad_bias = FusedLeakyReLUFunctionBackward.apply( - grad_output, out, ctx.negative_slope, ctx.scale - ) - - return grad_input, grad_bias, None, None - - -class FusedLeakyReLU(nn.Module): - def __init__(self, channel, negative_slope=0.2, scale=2 ** 0.5, device='cpu'): - super().__init__() - - self.bias = nn.Parameter(torch.zeros(channel)) - self.negative_slope = negative_slope - self.scale = scale - self.device = device - - def forward(self, input): - return fused_leaky_relu(input, self.bias, self.negative_slope, self.scale, self.device) - - -def fused_leaky_relu(input, bias, negative_slope=0.2, scale=2 ** 0.5, device='cpu'): - if platform.system() == 'Linux' and torch.cuda.is_available() and device != 'cpu': - return FusedLeakyReLUFunction.apply(input, bias, negative_slope, scale) - else: - return scale * F.leaky_relu(input + bias.view((1, -1)+(1,)*(len(input.shape)-2)), negative_slope=negative_slope) diff --git a/third_part/GPEN/face_model/op/fused_bias_act.cpp b/third_part/GPEN/face_model/op/fused_bias_act.cpp deleted file mode 100644 index 02be898..0000000 --- a/third_part/GPEN/face_model/op/fused_bias_act.cpp +++ /dev/null @@ -1,21 +0,0 @@ -#include - - -torch::Tensor fused_bias_act_op(const torch::Tensor& input, const torch::Tensor& bias, const torch::Tensor& refer, - int act, int grad, float alpha, float scale); - -#define CHECK_CUDA(x) TORCH_CHECK(x.type().is_cuda(), #x " must be a CUDA tensor") -#define CHECK_CONTIGUOUS(x) TORCH_CHECK(x.is_contiguous(), #x " must be contiguous") -#define CHECK_INPUT(x) CHECK_CUDA(x); CHECK_CONTIGUOUS(x) - -torch::Tensor fused_bias_act(const torch::Tensor& input, const torch::Tensor& bias, const torch::Tensor& refer, - int act, int grad, float alpha, float scale) { - CHECK_CUDA(input); - CHECK_CUDA(bias); - - return fused_bias_act_op(input, bias, refer, act, grad, alpha, scale); -} - -PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) { - m.def("fused_bias_act", &fused_bias_act, "fused bias act (CUDA)"); -} \ No newline at end of file diff --git a/third_part/GPEN/face_model/op/fused_bias_act_kernel.cu b/third_part/GPEN/face_model/op/fused_bias_act_kernel.cu deleted file mode 100644 index c9fa56f..0000000 --- a/third_part/GPEN/face_model/op/fused_bias_act_kernel.cu +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright (c) 2019, NVIDIA Corporation. All rights reserved. -// -// This work is made available under the Nvidia Source Code License-NC. -// To view a copy of this license, visit -// https://nvlabs.github.io/stylegan2/license.html - -#include - -#include -#include -#include -#include - -#include -#include - - -template -static __global__ void fused_bias_act_kernel(scalar_t* out, const scalar_t* p_x, const scalar_t* p_b, const scalar_t* p_ref, - int act, int grad, scalar_t alpha, scalar_t scale, int loop_x, int size_x, int step_b, int size_b, int use_bias, int use_ref) { - int xi = blockIdx.x * loop_x * blockDim.x + threadIdx.x; - - scalar_t zero = 0.0; - - for (int loop_idx = 0; loop_idx < loop_x && xi < size_x; loop_idx++, xi += blockDim.x) { - scalar_t x = p_x[xi]; - - if (use_bias) { - x += p_b[(xi / step_b) % size_b]; - } - - scalar_t ref = use_ref ? p_ref[xi] : zero; - - scalar_t y; - - switch (act * 10 + grad) { - default: - case 10: y = x; break; - case 11: y = x; break; - case 12: y = 0.0; break; - - case 30: y = (x > 0.0) ? x : x * alpha; break; - case 31: y = (ref > 0.0) ? x : x * alpha; break; - case 32: y = 0.0; break; - } - - out[xi] = y * scale; - } -} - - -torch::Tensor fused_bias_act_op(const torch::Tensor& input, const torch::Tensor& bias, const torch::Tensor& refer, - int act, int grad, float alpha, float scale) { - int curDevice = -1; - cudaGetDevice(&curDevice); - cudaStream_t stream = at::cuda::getCurrentCUDAStream(curDevice); - - auto x = input.contiguous(); - auto b = bias.contiguous(); - auto ref = refer.contiguous(); - - int use_bias = b.numel() ? 1 : 0; - int use_ref = ref.numel() ? 1 : 0; - - int size_x = x.numel(); - int size_b = b.numel(); - int step_b = 1; - - for (int i = 1 + 1; i < x.dim(); i++) { - step_b *= x.size(i); - } - - int loop_x = 4; - int block_size = 4 * 32; - int grid_size = (size_x - 1) / (loop_x * block_size) + 1; - - auto y = torch::empty_like(x); - - AT_DISPATCH_FLOATING_TYPES_AND_HALF(x.scalar_type(), "fused_bias_act_kernel", [&] { - fused_bias_act_kernel<<>>( - y.data_ptr(), - x.data_ptr(), - b.data_ptr(), - ref.data_ptr(), - act, - grad, - alpha, - scale, - loop_x, - size_x, - step_b, - size_b, - use_bias, - use_ref - ); - }); - - return y; -} \ No newline at end of file diff --git a/third_part/GPEN/face_model/op/upfirdn2d.cpp b/third_part/GPEN/face_model/op/upfirdn2d.cpp deleted file mode 100644 index d2e633d..0000000 --- a/third_part/GPEN/face_model/op/upfirdn2d.cpp +++ /dev/null @@ -1,23 +0,0 @@ -#include - - -torch::Tensor upfirdn2d_op(const torch::Tensor& input, const torch::Tensor& kernel, - int up_x, int up_y, int down_x, int down_y, - int pad_x0, int pad_x1, int pad_y0, int pad_y1); - -#define CHECK_CUDA(x) TORCH_CHECK(x.type().is_cuda(), #x " must be a CUDA tensor") -#define CHECK_CONTIGUOUS(x) TORCH_CHECK(x.is_contiguous(), #x " must be contiguous") -#define CHECK_INPUT(x) CHECK_CUDA(x); CHECK_CONTIGUOUS(x) - -torch::Tensor upfirdn2d(const torch::Tensor& input, const torch::Tensor& kernel, - int up_x, int up_y, int down_x, int down_y, - int pad_x0, int pad_x1, int pad_y0, int pad_y1) { - CHECK_CUDA(input); - CHECK_CUDA(kernel); - - return upfirdn2d_op(input, kernel, up_x, up_y, down_x, down_y, pad_x0, pad_x1, pad_y0, pad_y1); -} - -PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) { - m.def("upfirdn2d", &upfirdn2d, "upfirdn2d (CUDA)"); -} \ No newline at end of file diff --git a/third_part/GPEN/face_model/op/upfirdn2d.py b/third_part/GPEN/face_model/op/upfirdn2d.py deleted file mode 100644 index 2e38447..0000000 --- a/third_part/GPEN/face_model/op/upfirdn2d.py +++ /dev/null @@ -1,194 +0,0 @@ -import os -import platform - -import torch -import torch.nn.functional as F -from torch.autograd import Function -from torch.utils.cpp_extension import load, _import_module_from_library - -# if running GPEN without cuda, please comment line 10-18 -if platform.system() == 'Linux' and torch.cuda.is_available(): - module_path = os.path.dirname(__file__) - upfirdn2d_op = load( - 'upfirdn2d', - sources=[ - os.path.join(module_path, 'upfirdn2d.cpp'), - os.path.join(module_path, 'upfirdn2d_kernel.cu'), - ], - ) - - -#upfirdn2d_op = _import_module_from_library('upfirdn2d', '/tmp/torch_extensions/upfirdn2d', True) - -class UpFirDn2dBackward(Function): - @staticmethod - def forward( - ctx, grad_output, kernel, grad_kernel, up, down, pad, g_pad, in_size, out_size - ): - - up_x, up_y = up - down_x, down_y = down - g_pad_x0, g_pad_x1, g_pad_y0, g_pad_y1 = g_pad - - grad_output = grad_output.reshape(-1, out_size[0], out_size[1], 1) - - grad_input = upfirdn2d_op.upfirdn2d( - grad_output, - grad_kernel, - down_x, - down_y, - up_x, - up_y, - g_pad_x0, - g_pad_x1, - g_pad_y0, - g_pad_y1, - ) - grad_input = grad_input.view(in_size[0], in_size[1], in_size[2], in_size[3]) - - ctx.save_for_backward(kernel) - - pad_x0, pad_x1, pad_y0, pad_y1 = pad - - ctx.up_x = up_x - ctx.up_y = up_y - ctx.down_x = down_x - ctx.down_y = down_y - ctx.pad_x0 = pad_x0 - ctx.pad_x1 = pad_x1 - ctx.pad_y0 = pad_y0 - ctx.pad_y1 = pad_y1 - ctx.in_size = in_size - ctx.out_size = out_size - - return grad_input - - @staticmethod - def backward(ctx, gradgrad_input): - kernel, = ctx.saved_tensors - - gradgrad_input = gradgrad_input.reshape(-1, ctx.in_size[2], ctx.in_size[3], 1) - - gradgrad_out = upfirdn2d_op.upfirdn2d( - gradgrad_input, - kernel, - ctx.up_x, - ctx.up_y, - ctx.down_x, - ctx.down_y, - ctx.pad_x0, - ctx.pad_x1, - ctx.pad_y0, - ctx.pad_y1, - ) - # gradgrad_out = gradgrad_out.view(ctx.in_size[0], ctx.out_size[0], ctx.out_size[1], ctx.in_size[3]) - gradgrad_out = gradgrad_out.view( - ctx.in_size[0], ctx.in_size[1], ctx.out_size[0], ctx.out_size[1] - ) - - return gradgrad_out, None, None, None, None, None, None, None, None - - -class UpFirDn2d(Function): - @staticmethod - def forward(ctx, input, kernel, up, down, pad): - up_x, up_y = up - down_x, down_y = down - pad_x0, pad_x1, pad_y0, pad_y1 = pad - - kernel_h, kernel_w = kernel.shape - batch, channel, in_h, in_w = input.shape - ctx.in_size = input.shape - - input = input.reshape(-1, in_h, in_w, 1) - - ctx.save_for_backward(kernel, torch.flip(kernel, [0, 1])) - - out_h = (in_h * up_y + pad_y0 + pad_y1 - kernel_h) // down_y + 1 - out_w = (in_w * up_x + pad_x0 + pad_x1 - kernel_w) // down_x + 1 - ctx.out_size = (out_h, out_w) - - ctx.up = (up_x, up_y) - ctx.down = (down_x, down_y) - ctx.pad = (pad_x0, pad_x1, pad_y0, pad_y1) - - g_pad_x0 = kernel_w - pad_x0 - 1 - g_pad_y0 = kernel_h - pad_y0 - 1 - g_pad_x1 = in_w * up_x - out_w * down_x + pad_x0 - up_x + 1 - g_pad_y1 = in_h * up_y - out_h * down_y + pad_y0 - up_y + 1 - - ctx.g_pad = (g_pad_x0, g_pad_x1, g_pad_y0, g_pad_y1) - - out = upfirdn2d_op.upfirdn2d( - input, kernel, up_x, up_y, down_x, down_y, pad_x0, pad_x1, pad_y0, pad_y1 - ) - # out = out.view(major, out_h, out_w, minor) - out = out.view(-1, channel, out_h, out_w) - - return out - - @staticmethod - def backward(ctx, grad_output): - kernel, grad_kernel = ctx.saved_tensors - - grad_input = UpFirDn2dBackward.apply( - grad_output, - kernel, - grad_kernel, - ctx.up, - ctx.down, - ctx.pad, - ctx.g_pad, - ctx.in_size, - ctx.out_size, - ) - - return grad_input, None, None, None, None - - -def upfirdn2d(input, kernel, up=1, down=1, pad=(0, 0), device='cpu'): - if platform.system() == 'Linux' and torch.cuda.is_available() and device != 'cpu': - out = UpFirDn2d.apply( - input, kernel, (up, up), (down, down), (pad[0], pad[1], pad[0], pad[1]) - ) - else: - out = upfirdn2d_native(input, kernel, up, up, down, down, pad[0], pad[1], pad[0], pad[1]) - - return out - - -def upfirdn2d_native( - input, kernel, up_x, up_y, down_x, down_y, pad_x0, pad_x1, pad_y0, pad_y1 -): - input = input.permute(0, 2, 3, 1) - _, in_h, in_w, minor = input.shape - kernel_h, kernel_w = kernel.shape - out = input.view(-1, in_h, 1, in_w, 1, minor) - out = F.pad(out, [0, 0, 0, up_x - 1, 0, 0, 0, up_y - 1]) - out = out.view(-1, in_h * up_y, in_w * up_x, minor) - - out = F.pad( - out, [0, 0, max(pad_x0, 0), max(pad_x1, 0), max(pad_y0, 0), max(pad_y1, 0)] - ) - out = out[ - :, - max(-pad_y0, 0) : out.shape[1] - max(-pad_y1, 0), - max(-pad_x0, 0) : out.shape[2] - max(-pad_x1, 0), - :, - ] - - out = out.permute(0, 3, 1, 2) - out = out.reshape( - [-1, 1, in_h * up_y + pad_y0 + pad_y1, in_w * up_x + pad_x0 + pad_x1] - ) - w = torch.flip(kernel, [0, 1]).view(1, 1, kernel_h, kernel_w) - out = F.conv2d(out, w) - out = out.reshape( - -1, - minor, - in_h * up_y + pad_y0 + pad_y1 - kernel_h + 1, - in_w * up_x + pad_x0 + pad_x1 - kernel_w + 1, - ) - # out = out.permute(0, 2, 3, 1) - return out[:, :, ::down_y, ::down_x] - diff --git a/third_part/GPEN/face_model/op/upfirdn2d_kernel.cu b/third_part/GPEN/face_model/op/upfirdn2d_kernel.cu deleted file mode 100644 index 2a710aa..0000000 --- a/third_part/GPEN/face_model/op/upfirdn2d_kernel.cu +++ /dev/null @@ -1,272 +0,0 @@ -// Copyright (c) 2019, NVIDIA Corporation. All rights reserved. -// -// This work is made available under the Nvidia Source Code License-NC. -// To view a copy of this license, visit -// https://nvlabs.github.io/stylegan2/license.html - -#include - -#include -#include -#include -#include - -#include -#include - - -static __host__ __device__ __forceinline__ int floor_div(int a, int b) { - int c = a / b; - - if (c * b > a) { - c--; - } - - return c; -} - - -struct UpFirDn2DKernelParams { - int up_x; - int up_y; - int down_x; - int down_y; - int pad_x0; - int pad_x1; - int pad_y0; - int pad_y1; - - int major_dim; - int in_h; - int in_w; - int minor_dim; - int kernel_h; - int kernel_w; - int out_h; - int out_w; - int loop_major; - int loop_x; -}; - - -template -__global__ void upfirdn2d_kernel(scalar_t* out, const scalar_t* input, const scalar_t* kernel, const UpFirDn2DKernelParams p) { - const int tile_in_h = ((tile_out_h - 1) * down_y + kernel_h - 1) / up_y + 1; - const int tile_in_w = ((tile_out_w - 1) * down_x + kernel_w - 1) / up_x + 1; - - __shared__ volatile float sk[kernel_h][kernel_w]; - __shared__ volatile float sx[tile_in_h][tile_in_w]; - - int minor_idx = blockIdx.x; - int tile_out_y = minor_idx / p.minor_dim; - minor_idx -= tile_out_y * p.minor_dim; - tile_out_y *= tile_out_h; - int tile_out_x_base = blockIdx.y * p.loop_x * tile_out_w; - int major_idx_base = blockIdx.z * p.loop_major; - - if (tile_out_x_base >= p.out_w | tile_out_y >= p.out_h | major_idx_base >= p.major_dim) { - return; - } - - for (int tap_idx = threadIdx.x; tap_idx < kernel_h * kernel_w; tap_idx += blockDim.x) { - int ky = tap_idx / kernel_w; - int kx = tap_idx - ky * kernel_w; - scalar_t v = 0.0; - - if (kx < p.kernel_w & ky < p.kernel_h) { - v = kernel[(p.kernel_h - 1 - ky) * p.kernel_w + (p.kernel_w - 1 - kx)]; - } - - sk[ky][kx] = v; - } - - for (int loop_major = 0, major_idx = major_idx_base; loop_major < p.loop_major & major_idx < p.major_dim; loop_major++, major_idx++) { - for (int loop_x = 0, tile_out_x = tile_out_x_base; loop_x < p.loop_x & tile_out_x < p.out_w; loop_x++, tile_out_x += tile_out_w) { - int tile_mid_x = tile_out_x * down_x + up_x - 1 - p.pad_x0; - int tile_mid_y = tile_out_y * down_y + up_y - 1 - p.pad_y0; - int tile_in_x = floor_div(tile_mid_x, up_x); - int tile_in_y = floor_div(tile_mid_y, up_y); - - __syncthreads(); - - for (int in_idx = threadIdx.x; in_idx < tile_in_h * tile_in_w; in_idx += blockDim.x) { - int rel_in_y = in_idx / tile_in_w; - int rel_in_x = in_idx - rel_in_y * tile_in_w; - int in_x = rel_in_x + tile_in_x; - int in_y = rel_in_y + tile_in_y; - - scalar_t v = 0.0; - - if (in_x >= 0 & in_y >= 0 & in_x < p.in_w & in_y < p.in_h) { - v = input[((major_idx * p.in_h + in_y) * p.in_w + in_x) * p.minor_dim + minor_idx]; - } - - sx[rel_in_y][rel_in_x] = v; - } - - __syncthreads(); - for (int out_idx = threadIdx.x; out_idx < tile_out_h * tile_out_w; out_idx += blockDim.x) { - int rel_out_y = out_idx / tile_out_w; - int rel_out_x = out_idx - rel_out_y * tile_out_w; - int out_x = rel_out_x + tile_out_x; - int out_y = rel_out_y + tile_out_y; - - int mid_x = tile_mid_x + rel_out_x * down_x; - int mid_y = tile_mid_y + rel_out_y * down_y; - int in_x = floor_div(mid_x, up_x); - int in_y = floor_div(mid_y, up_y); - int rel_in_x = in_x - tile_in_x; - int rel_in_y = in_y - tile_in_y; - int kernel_x = (in_x + 1) * up_x - mid_x - 1; - int kernel_y = (in_y + 1) * up_y - mid_y - 1; - - scalar_t v = 0.0; - - #pragma unroll - for (int y = 0; y < kernel_h / up_y; y++) - #pragma unroll - for (int x = 0; x < kernel_w / up_x; x++) - v += sx[rel_in_y + y][rel_in_x + x] * sk[kernel_y + y * up_y][kernel_x + x * up_x]; - - if (out_x < p.out_w & out_y < p.out_h) { - out[((major_idx * p.out_h + out_y) * p.out_w + out_x) * p.minor_dim + minor_idx] = v; - } - } - } - } -} - - -torch::Tensor upfirdn2d_op(const torch::Tensor& input, const torch::Tensor& kernel, - int up_x, int up_y, int down_x, int down_y, - int pad_x0, int pad_x1, int pad_y0, int pad_y1) { - int curDevice = -1; - cudaGetDevice(&curDevice); - cudaStream_t stream = at::cuda::getCurrentCUDAStream(curDevice); - - UpFirDn2DKernelParams p; - - auto x = input.contiguous(); - auto k = kernel.contiguous(); - - p.major_dim = x.size(0); - p.in_h = x.size(1); - p.in_w = x.size(2); - p.minor_dim = x.size(3); - p.kernel_h = k.size(0); - p.kernel_w = k.size(1); - p.up_x = up_x; - p.up_y = up_y; - p.down_x = down_x; - p.down_y = down_y; - p.pad_x0 = pad_x0; - p.pad_x1 = pad_x1; - p.pad_y0 = pad_y0; - p.pad_y1 = pad_y1; - - p.out_h = (p.in_h * p.up_y + p.pad_y0 + p.pad_y1 - p.kernel_h + p.down_y) / p.down_y; - p.out_w = (p.in_w * p.up_x + p.pad_x0 + p.pad_x1 - p.kernel_w + p.down_x) / p.down_x; - - auto out = at::empty({p.major_dim, p.out_h, p.out_w, p.minor_dim}, x.options()); - - int mode = -1; - - int tile_out_h; - int tile_out_w; - - if (p.up_x == 1 && p.up_y == 1 && p.down_x == 1 && p.down_y == 1 && p.kernel_h <= 4 && p.kernel_w <= 4) { - mode = 1; - tile_out_h = 16; - tile_out_w = 64; - } - - if (p.up_x == 1 && p.up_y == 1 && p.down_x == 1 && p.down_y == 1 && p.kernel_h <= 3 && p.kernel_w <= 3) { - mode = 2; - tile_out_h = 16; - tile_out_w = 64; - } - - if (p.up_x == 2 && p.up_y == 2 && p.down_x == 1 && p.down_y == 1 && p.kernel_h <= 4 && p.kernel_w <= 4) { - mode = 3; - tile_out_h = 16; - tile_out_w = 64; - } - - if (p.up_x == 2 && p.up_y == 2 && p.down_x == 1 && p.down_y == 1 && p.kernel_h <= 2 && p.kernel_w <= 2) { - mode = 4; - tile_out_h = 16; - tile_out_w = 64; - } - - if (p.up_x == 1 && p.up_y == 1 && p.down_x == 2 && p.down_y == 2 && p.kernel_h <= 4 && p.kernel_w <= 4) { - mode = 5; - tile_out_h = 8; - tile_out_w = 32; - } - - if (p.up_x == 1 && p.up_y == 1 && p.down_x == 2 && p.down_y == 2 && p.kernel_h <= 2 && p.kernel_w <= 2) { - mode = 6; - tile_out_h = 8; - tile_out_w = 32; - } - - dim3 block_size; - dim3 grid_size; - - if (tile_out_h > 0 && tile_out_w) { - p.loop_major = (p.major_dim - 1) / 16384 + 1; - p.loop_x = 1; - block_size = dim3(32 * 8, 1, 1); - grid_size = dim3(((p.out_h - 1) / tile_out_h + 1) * p.minor_dim, - (p.out_w - 1) / (p.loop_x * tile_out_w) + 1, - (p.major_dim - 1) / p.loop_major + 1); - } - - AT_DISPATCH_FLOATING_TYPES_AND_HALF(x.scalar_type(), "upfirdn2d_cuda", [&] { - switch (mode) { - case 1: - upfirdn2d_kernel<<>>( - out.data_ptr(), x.data_ptr(), k.data_ptr(), p - ); - - break; - - case 2: - upfirdn2d_kernel<<>>( - out.data_ptr(), x.data_ptr(), k.data_ptr(), p - ); - - break; - - case 3: - upfirdn2d_kernel<<>>( - out.data_ptr(), x.data_ptr(), k.data_ptr(), p - ); - - break; - - case 4: - upfirdn2d_kernel<<>>( - out.data_ptr(), x.data_ptr(), k.data_ptr(), p - ); - - break; - - case 5: - upfirdn2d_kernel<<>>( - out.data_ptr(), x.data_ptr(), k.data_ptr(), p - ); - - break; - - case 6: - upfirdn2d_kernel<<>>( - out.data_ptr(), x.data_ptr(), k.data_ptr(), p - ); - - break; - } - }); - - return out; -} \ No newline at end of file diff --git a/third_part/GPEN/face_morpher/README.rst b/third_part/GPEN/face_morpher/README.rst deleted file mode 100644 index a864e85..0000000 --- a/third_part/GPEN/face_morpher/README.rst +++ /dev/null @@ -1,260 +0,0 @@ -Face Morpher -============ - -| Warp, average and morph human faces! -| Scripts will automatically detect frontal faces and skip images if - none is detected. - -Built with Python, `dlib`_, Numpy, Scipy, dlib. - -| Supported on Python 2.7, Python 3.6+ -| Tested on macOS Mojave and 64bit Linux (dockerized). - -Requirements --------------- -- ``pip install -r requirements.txt`` -- Download `http://dlib.net/files/shape_predictor_68_face_landmarks.dat.bz2` and extract file. -- Export environment variable ``DLIB_DATA_DIR`` to the folder where ``shape_predictor_68_face_landmarks.dat`` is located. Default ``data``. E.g ``export DLIB_DATA_DIR=/Downloads/data`` - -Either: - -- `Use as local command-line utility`_ -- `Use as pip library`_ -- `Try out in a docker container`_ - -.. _`Use as local command-line utility`: - -Use as local command-line utility ---------------------------------- -:: - - $ git clone https://github.com/alyssaq/face_morpher - -Morphing Faces --------------- - -Morph from a source to destination image: - -:: - - python facemorpher/morpher.py --src= --dest= --plot - -Morph through a series of images in a folder: - -:: - - python facemorpher/morpher.py --images= --out_video=out.avi - -All options listed in ``morpher.py`` (pasted below): - -:: - - Morph from source to destination face or - Morph through all images in a folder - - Usage: - morpher.py (--src= --dest= | --images=) - [--width=] [--height=] - [--num=] [--fps=] - [--out_frames=] [--out_video=] - [--plot] [--background=(black|transparent|average)] - - Options: - -h, --help Show this screen. - --src= Filepath to source image (.jpg, .jpeg, .png) - --dest= Filepath to destination image (.jpg, .jpeg, .png) - --images= Folderpath to images - --width= Custom width of the images/video [default: 500] - --height= Custom height of the images/video [default: 600] - --num= Number of morph frames [default: 20] - --fps= Number frames per second for the video [default: 10] - --out_frames= Folder path to save all image frames - --out_video= Filename to save a video - --plot Flag to plot images to result.png [default: False] - --background= Background of images to be one of (black|transparent|average) [default: black] - --version Show version. - -Averaging Faces ---------------- - -Average faces from all images in a folder: - -:: - - python facemorpher/averager.py --images= --out=average.png - -All options listed in ``averager.py`` (pasted below): - -:: - - Face averager - - Usage: - averager.py --images= [--blur] [--plot] - [--background=(black|transparent|average)] - [--width=] [--height=] - [--out=] [--destimg=] - - Options: - -h, --help Show this screen. - --images= Folder to images (.jpg, .jpeg, .png) - --blur Flag to blur edges of image [default: False] - --width= Custom width of the images/video [default: 500] - --height= Custom height of the images/video [default: 600] - --out= Filename to save the average face [default: result.png] - --destimg= Destination face image to overlay average face - --plot Flag to display the average face [default: False] - --background= Background of image to be one of (black|transparent|average) [default: black] - --version Show version. - -Steps (facemorpher folder) --------------------------- - -1. Locator -^^^^^^^^^^ - -- Locates face points -- For a different locator, return an array of (x, y) control face - points - -2. Aligner -^^^^^^^^^^ - -- Align faces by resizing, centering and cropping to given size - -3. Warper -^^^^^^^^^ - -- Given 2 images and its face points, warp one image to the other -- Triangulates face points -- Affine transforms each triangle with bilinear interpolation - -4a. Morpher -^^^^^^^^^^^ - -- Morph between 2 or more images - -4b. Averager -^^^^^^^^^^^^ - -- Average faces from 2 or more images - -Blender -^^^^^^^ - -Optional blending of warped image: - -- Weighted average -- Alpha feathering -- Poisson blend - -Examples - `Being John Malkovich`_ ----------------------------------- - -Create a morphing video between the 2 images: -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -| ``> python facemorpher/morpher.py --src=alyssa.jpg --dest=john_malkovich.jpg`` -| ``--out_video=out.avi`` - -(out.avi played and recorded as gif) - -.. figure:: https://raw.github.com/alyssaq/face_morpher/master/examples/being_john_malvokich.gif - :alt: gif - -Save the frames to a folder: -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -| ``> python facemorpher/morpher.py --src=alyssa.jpg --dest=john_malkovich.jpg`` -| ``--out_frames=out_folder --num=30`` - -Plot the frames: -^^^^^^^^^^^^^^^^ - -| ``> python facemorpher/morpher.py --src=alyssa.jpg --dest=john_malkovich.jpg`` -| ``--num=12 --plot`` - -.. figure:: https://raw.github.com/alyssaq/face_morpher/master/examples/plot.png - :alt: plot - -Average all face images in a folder: -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -85 images used - -| ``> python facemorpher/averager.py --images=images --blur --background=transparent`` -| ``--width=220 --height=250`` - -.. figure:: https://raw.github.com/alyssaq/face_morpher/master/examples/average_faces.png - :alt: average\_faces - -.. _`Use as pip library`: - -Use as pip library ---------------------------------- -:: - - $ pip install facemorpher - -Examples -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Additional options are exactly the same as the command line - -:: - - import facemorpher - - # Get a list of image paths in a folder - imgpaths = facemorpher.list_imgpaths('imagefolder') - - # To morph, supply an array of face images: - facemorpher.morpher(imgpaths, plot=True) - - # To average, supply an array of face images: - facemorpher.averager(['image1.png', 'image2.png'], plot=True) - - -Once pip installed, 2 binaries are also available as a command line utility: - -:: - - $ facemorpher --src= --dest= --plot - $ faceaverager --images= --plot - -Try out in a docker container ---------------------------------- -Mount local folder to `/images` in docker container, run it and enter a bash session. ---rm removes the container when you close it. -:: - - $ docker run -v /Users/alyssa/Desktop/images:/images --name py3 --rm -it jjanzic/docker-python3-opencv bash - -Once you're in the container, install ``facemorpher`` and try the examples listed above -:: - - root@0dad0912ebbe:/# pip install facemorpher - root@0dad0912ebbe:/# facemorpher --src= --dest= --plot - -Documentation -------------- - -http://alyssaq.github.io/face_morpher - -Build & publish Docs -^^^^^^^^^^^^^^^^^^^^ - -:: - - ./scripts/publish_ghpages.sh - -License -------- -`MIT`_ - -.. _Being John Malkovich: http://www.rottentomatoes.com/m/being_john_malkovich -.. _Mac installation steps: https://gist.github.com/alyssaq/f60393545173379e0f3f#file-4-opencv3-with-python3-md -.. _MIT: http://alyssaq.github.io/mit-license -.. _OpenCV: http://opencv.org -.. _Homebrew: https://brew.sh -.. _source: https://github.com/opencv/opencv -.. _dlib: http://dlib.net diff --git a/third_part/GPEN/face_morpher/facemorpher/__init__.py b/third_part/GPEN/face_morpher/facemorpher/__init__.py deleted file mode 100644 index bb9074b..0000000 --- a/third_part/GPEN/face_morpher/facemorpher/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -""" -Face Morpher module init code -""" -from .morpher import morpher, list_imgpaths -from .averager import averager - -__all__ = ['list_imgpaths', - 'morpher', - 'averager'] diff --git a/third_part/GPEN/face_morpher/facemorpher/aligner.py b/third_part/GPEN/face_morpher/facemorpher/aligner.py deleted file mode 100644 index 04a4072..0000000 --- a/third_part/GPEN/face_morpher/facemorpher/aligner.py +++ /dev/null @@ -1,102 +0,0 @@ -""" -Align face and image sizes -""" -import cv2 -import numpy as np - -def positive_cap(num): - """ Cap a number to ensure positivity - - :param num: positive or negative number - :returns: (overflow, capped_number) - """ - if num < 0: - return 0, abs(num) - else: - return num, 0 - -def roi_coordinates(rect, size, scale): - """ Align the rectangle into the center and return the top-left coordinates - within the new size. If rect is smaller, we add borders. - - :param rect: (x, y, w, h) bounding rectangle of the face - :param size: (width, height) are the desired dimensions - :param scale: scaling factor of the rectangle to be resized - :returns: 4 numbers. Top-left coordinates of the aligned ROI. - (x, y, border_x, border_y). All values are > 0. - """ - rectx, recty, rectw, recth = rect - new_height, new_width = size - mid_x = int((rectx + rectw/2) * scale) - mid_y = int((recty + recth/2) * scale) - roi_x = mid_x - int(new_width/2) - roi_y = mid_y - int(new_height/2) - - roi_x, border_x = positive_cap(roi_x) - roi_y, border_y = positive_cap(roi_y) - return roi_x, roi_y, border_x, border_y - -def scaling_factor(rect, size): - """ Calculate the scaling factor for the current image to be - resized to the new dimensions - - :param rect: (x, y, w, h) bounding rectangle of the face - :param size: (width, height) are the desired dimensions - :returns: floating point scaling factor - """ - new_height, new_width = size - rect_h, rect_w = rect[2:] - height_ratio = rect_h / new_height - width_ratio = rect_w / new_width - scale = 1 - if height_ratio > width_ratio: - new_recth = 0.8 * new_height - scale = new_recth / rect_h - else: - new_rectw = 0.8 * new_width - scale = new_rectw / rect_w - return scale - -def resize_image(img, scale): - """ Resize image with the provided scaling factor - - :param img: image to be resized - :param scale: scaling factor for resizing the image - """ - cur_height, cur_width = img.shape[:2] - new_scaled_height = int(scale * cur_height) - new_scaled_width = int(scale * cur_width) - - return cv2.resize(img, (new_scaled_width, new_scaled_height)) - -def resize_align(img, points, size): - """ Resize image and associated points, align face to the center - and crop to the desired size - - :param img: image to be resized - :param points: *m* x 2 array of points - :param size: (height, width) tuple of new desired size - """ - new_height, new_width = size - - # Resize image based on bounding rectangle - rect = cv2.boundingRect(np.array([points], np.int32)) - scale = scaling_factor(rect, size) - img = resize_image(img, scale) - - # Align bounding rect to center - cur_height, cur_width = img.shape[:2] - roi_x, roi_y, border_x, border_y = roi_coordinates(rect, size, scale) - roi_h = np.min([new_height-border_y, cur_height-roi_y]) - roi_w = np.min([new_width-border_x, cur_width-roi_x]) - - # Crop to supplied size - crop = np.zeros((new_height, new_width, 3), img.dtype) - crop[border_y:border_y+roi_h, border_x:border_x+roi_w] = ( - img[roi_y:roi_y+roi_h, roi_x:roi_x+roi_w]) - - # Scale and align face points to the crop - points[:, 0] = (points[:, 0] * scale) + (border_x - roi_x) - points[:, 1] = (points[:, 1] * scale) + (border_y - roi_y) - - return (crop, points) diff --git a/third_part/GPEN/face_morpher/facemorpher/averager.py b/third_part/GPEN/face_morpher/facemorpher/averager.py deleted file mode 100644 index e205565..0000000 --- a/third_part/GPEN/face_morpher/facemorpher/averager.py +++ /dev/null @@ -1,123 +0,0 @@ -""" -:: - - Face averager - - Usage: - averager.py --images= [--blur] [--plot] - [--background=(black|transparent|average)] - [--width=] [--height=] - [--out=] [--destimg=] - - Options: - -h, --help Show this screen. - --images= Folder to images (.jpg, .jpeg, .png) - --blur Flag to blur edges of image [default: False] - --width= Custom width of the images/video [default: 500] - --height= Custom height of the images/video [default: 600] - --out= Filename to save the average face [default: result.png] - --destimg= Destination face image to overlay average face - --plot Flag to display the average face [default: False] - --background= Background of image to be one of (black|transparent|average) [default: black] - --version Show version. -""" - -from docopt import docopt -import os -import cv2 -import numpy as np -import matplotlib.pyplot as plt -import matplotlib.image as mpimg - -from custom_nodes.ComfyUI_Lam.third_part.GPEN.face_morpher.facemorpher import locator -from custom_nodes.ComfyUI_Lam.third_part.GPEN.face_morpher.facemorpher import aligner -from custom_nodes.ComfyUI_Lam.third_part.GPEN.face_morpher.facemorpher import warper -from custom_nodes.ComfyUI_Lam.third_part.GPEN.face_morpher.facemorpher import blender -from custom_nodes.ComfyUI_Lam.third_part.GPEN.face_morpher.facemorpher import plotter - -def list_imgpaths(imgfolder): - for fname in os.listdir(imgfolder): - if (fname.lower().endswith('.jpg') or - fname.lower().endswith('.png') or - fname.lower().endswith('.jpeg')): - yield os.path.join(imgfolder, fname) - -def sharpen(img): - blured = cv2.GaussianBlur(img, (0, 0), 2.5) - return cv2.addWeighted(img, 1.4, blured, -0.4, 0) - -def load_image_points(path, size): - img = cv2.imread(path) - points = locator.face_points(img) - - if len(points) == 0: - print('No face in %s' % path) - return None, None - else: - return aligner.resize_align(img, points, size) - -def averager(imgpaths, dest_filename=None, width=500, height=600, background='black', - blur_edges=False, out_filename='result.png', plot=False): - - size = (height, width) - - images = [] - point_set = [] - for path in imgpaths: - img, points = load_image_points(path, size) - if img is not None: - images.append(img) - point_set.append(points) - - if len(images) == 0: - raise FileNotFoundError('Could not find any valid images.' + - ' Supported formats are .jpg, .png, .jpeg') - - if dest_filename is not None: - dest_img, dest_points = load_image_points(dest_filename, size) - if dest_img is None or dest_points is None: - raise Exception('No face or detected face points in dest img: ' + dest_filename) - else: - dest_img = np.zeros(images[0].shape, np.uint8) - dest_points = locator.average_points(point_set) - - num_images = len(images) - result_images = np.zeros(images[0].shape, np.float32) - for i in range(num_images): - result_images += warper.warp_image(images[i], point_set[i], - dest_points, size, np.float32) - - result_image = np.uint8(result_images / num_images) - face_indexes = np.nonzero(result_image) - dest_img[face_indexes] = result_image[face_indexes] - - mask = blender.mask_from_points(size, dest_points) - if blur_edges: - blur_radius = 10 - mask = cv2.blur(mask, (blur_radius, blur_radius)) - - if background in ('transparent', 'average'): - dest_img = np.dstack((dest_img, mask)) - - if background == 'average': - average_background = locator.average_points(images) - dest_img = blender.overlay_image(dest_img, mask, average_background) - - print('Averaged {} images'.format(num_images)) - plt = plotter.Plotter(plot, num_images=1, out_filename=out_filename) - plt.save(dest_img) - plt.plot_one(dest_img) - plt.show() - -def main(): - args = docopt(__doc__, version='Face Averager 1.0') - try: - averager(list_imgpaths(args['--images']), args['--destimg'], - int(args['--width']), int(args['--height']), - args['--background'], args['--blur'], args['--out'], args['--plot']) - except Exception as e: - print(e) - - -if __name__ == "__main__": - main() diff --git a/third_part/GPEN/face_morpher/facemorpher/blender.py b/third_part/GPEN/face_morpher/facemorpher/blender.py deleted file mode 100644 index d05c77e..0000000 --- a/third_part/GPEN/face_morpher/facemorpher/blender.py +++ /dev/null @@ -1,133 +0,0 @@ -import cv2 -import numpy as np -import scipy.sparse - -def mask_from_points(size, points): - """ Create a mask of supplied size from supplied points - :param size: tuple of output mask size - :param points: array of [x, y] points - :returns: mask of values 0 and 255 where - 255 indicates the convex hull containing the points - """ - radius = 10 # kernel size - kernel = np.ones((radius, radius), np.uint8) - - mask = np.zeros(size, np.uint8) - cv2.fillConvexPoly(mask, cv2.convexHull(points), 255) - mask = cv2.erode(mask, kernel) - - return mask - -def overlay_image(foreground_image, mask, background_image): - """ Overlay foreground image onto the background given a mask - :param foreground_image: foreground image points - :param mask: [0-255] values in mask - :param background_image: background image points - :returns: image with foreground where mask > 0 overlaid on background image - """ - foreground_pixels = mask > 0 - background_image[..., :3][foreground_pixels] = foreground_image[..., :3][foreground_pixels] - return background_image - -def apply_mask(img, mask): - """ Apply mask to supplied image - :param img: max 3 channel image - :param mask: [0-255] values in mask - :returns: new image with mask applied - """ - masked_img = np.copy(img) - num_channels = 3 - for c in range(num_channels): - masked_img[..., c] = img[..., c] * (mask / 255) - - return masked_img - -def weighted_average(img1, img2, percent=0.5): - if percent <= 0: - return img2 - elif percent >= 1: - return img1 - else: - return cv2.addWeighted(img1, percent, img2, 1-percent, 0) - -def alpha_feathering(src_img, dest_img, img_mask, blur_radius=15): - mask = cv2.blur(img_mask, (blur_radius, blur_radius)) - mask = mask / 255.0 - - result_img = np.empty(src_img.shape, np.uint8) - for i in range(3): - result_img[..., i] = src_img[..., i] * mask + dest_img[..., i] * (1-mask) - - return result_img - -def poisson_blend(img_source, dest_img, img_mask, offset=(0, 0)): - # http://opencv.jp/opencv2-x-samples/poisson-blending - img_target = np.copy(dest_img) - import pyamg - # compute regions to be blended - region_source = ( - max(-offset[0], 0), - max(-offset[1], 0), - min(img_target.shape[0] - offset[0], img_source.shape[0]), - min(img_target.shape[1] - offset[1], img_source.shape[1])) - region_target = ( - max(offset[0], 0), - max(offset[1], 0), - min(img_target.shape[0], img_source.shape[0] + offset[0]), - min(img_target.shape[1], img_source.shape[1] + offset[1])) - region_size = (region_source[2] - region_source[0], - region_source[3] - region_source[1]) - - # clip and normalize mask image - img_mask = img_mask[region_source[0]:region_source[2], - region_source[1]:region_source[3]] - - # create coefficient matrix - coff_mat = scipy.sparse.identity(np.prod(region_size), format='lil') - for y in range(region_size[0]): - for x in range(region_size[1]): - if img_mask[y, x]: - index = x + y * region_size[1] - coff_mat[index, index] = 4 - if index + 1 < np.prod(region_size): - coff_mat[index, index + 1] = -1 - if index - 1 >= 0: - coff_mat[index, index - 1] = -1 - if index + region_size[1] < np.prod(region_size): - coff_mat[index, index + region_size[1]] = -1 - if index - region_size[1] >= 0: - coff_mat[index, index - region_size[1]] = -1 - coff_mat = coff_mat.tocsr() - - # create poisson matrix for b - poisson_mat = pyamg.gallery.poisson(img_mask.shape) - # for each layer (ex. RGB) - for num_layer in range(img_target.shape[2]): - # get subimages - t = img_target[region_target[0]:region_target[2], - region_target[1]:region_target[3], num_layer] - s = img_source[region_source[0]:region_source[2], - region_source[1]:region_source[3], num_layer] - t = t.flatten() - s = s.flatten() - - # create b - b = poisson_mat * s - for y in range(region_size[0]): - for x in range(region_size[1]): - if not img_mask[y, x]: - index = x + y * region_size[1] - b[index] = t[index] - - # solve Ax = b - x = pyamg.solve(coff_mat, b, verb=False, tol=1e-10) - - # assign x to target image - x = np.reshape(x, region_size) - x[x > 255] = 255 - x[x < 0] = 0 - x = np.array(x, img_target.dtype) - img_target[region_target[0]:region_target[2], - region_target[1]:region_target[3], num_layer] = x - - return img_target diff --git a/third_part/GPEN/face_morpher/facemorpher/locator.py b/third_part/GPEN/face_morpher/facemorpher/locator.py deleted file mode 100644 index adea529..0000000 --- a/third_part/GPEN/face_morpher/facemorpher/locator.py +++ /dev/null @@ -1,112 +0,0 @@ -""" -Locate face points -""" - -import cv2 -import numpy as np -import os.path as path -import dlib -import os - - -DATA_DIR = os.environ.get( - 'DLIB_DATA_DIR', - path.join(path.dirname(path.dirname(path.realpath(__file__))), 'data') -) -dlib_detector = dlib.get_frontal_face_detector() -dlib_predictor = dlib.shape_predictor(path.join(DATA_DIR, 'shape_predictor_68_face_landmarks.dat')) - -def boundary_points(points, width_percent=0.1, height_percent=0.1): - """ Produce additional boundary points - :param points: *m* x 2 array of x,y points - :param width_percent: [-1, 1] percentage of width to taper inwards. Negative for opposite direction - :param height_percent: [-1, 1] percentage of height to taper downwards. Negative for opposite direction - :returns: 2 additional points at the top corners - """ - x, y, w, h = cv2.boundingRect(np.array([points], np.int32)) - spacerw = int(w * width_percent) - spacerh = int(h * height_percent) - return [[x+spacerw, y+spacerh], - [x+w-spacerw, y+spacerh]] - - -def face_points(img, add_boundary_points=True): - return face_points_dlib(img, add_boundary_points) - -def face_points_dlib(img, add_boundary_points=True): - """ Locates 68 face points using dlib (http://dlib.net) - Requires shape_predictor_68_face_landmarks.dat to be in face_morpher/data - Download at: http://dlib.net/files/shape_predictor_68_face_landmarks.dat.bz2 - :param img: an image array - :param add_boundary_points: bool to add additional boundary points - :returns: Array of x,y face points. Empty array if no face found - """ - try: - points = [] - rgbimg = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) - rects = dlib_detector(rgbimg, 1) - - if rects and len(rects) > 0: - # We only take the first found face - shapes = dlib_predictor(rgbimg, rects[0]) - points = np.array([(shapes.part(i).x, shapes.part(i).y) for i in range(68)], np.int32) - - if add_boundary_points: - # Add more points inwards and upwards as dlib only detects up to eyebrows - points = np.vstack([ - points, - boundary_points(points, 0.1, -0.03), - boundary_points(points, 0.13, -0.05), - boundary_points(points, 0.15, -0.08), - boundary_points(points, 0.33, -0.12)]) - - return points - except Exception as e: - print(e) - return [] - -def face_points_stasm(img, add_boundary_points=True): - import stasm - """ Locates 77 face points using stasm (http://www.milbo.users.sonic.net/stasm) - - :param img: an image array - :param add_boundary_points: bool to add 2 additional points - :returns: Array of x,y face points. Empty array if no face found - """ - try: - points = stasm.search_single(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)) - except Exception as e: - print('Failed finding face points: ', e) - return [] - - points = points.astype(np.int32) - if len(points) == 0: - return points - - if add_boundary_points: - return np.vstack([points, boundary_points(points)]) - - return points - -def average_points(point_set): - """ Averages a set of face points from images - - :param point_set: *n* x *m* x 2 array of face points. \\ - *n* = number of images. *m* = number of face points per image - """ - return np.mean(point_set, 0).astype(np.int32) - -def weighted_average_points(start_points, end_points, percent=0.5): - """ Weighted average of two sets of supplied points - - :param start_points: *m* x 2 array of start face points. - :param end_points: *m* x 2 array of end face points. - :param percent: [0, 1] percentage weight on start_points - :returns: *m* x 2 array of weighted average points - """ - if percent <= 0: - return end_points - elif percent >= 1: - return start_points - else: - return np.asarray(start_points*percent + end_points*(1-percent), np.int32) diff --git a/third_part/GPEN/face_morpher/facemorpher/morpher.py b/third_part/GPEN/face_morpher/facemorpher/morpher.py deleted file mode 100644 index fd4a58e..0000000 --- a/third_part/GPEN/face_morpher/facemorpher/morpher.py +++ /dev/null @@ -1,153 +0,0 @@ -""" -:: - - Morph from source to destination face or - Morph through all images in a folder - - Usage: - morpher.py (--src= --dest= | --images=) - [--width=] [--height=] - [--num=] [--fps=] - [--out_frames=] [--out_video=] - [--plot] [--background=(black|transparent|average)] - - Options: - -h, --help Show this screen. - --src= Filepath to source image (.jpg, .jpeg, .png) - --dest= Filepath to destination image (.jpg, .jpeg, .png) - --images= Folderpath to images - --width= Custom width of the images/video [default: 500] - --height= Custom height of the images/video [default: 600] - --num= Number of morph frames [default: 20] - --fps= Number frames per second for the video [default: 10] - --out_frames= Folder path to save all image frames - --out_video= Filename to save a video - --plot Flag to plot images to result.png [default: False] - --background= Background of images to be one of (black|transparent|average) [default: black] - --version Show version. -""" -from docopt import docopt -import os -import numpy as np -import cv2 - -from custom_nodes.ComfyUI_Lam.third_part.GPEN.face_morpher.facemorpher import locator -from custom_nodes.ComfyUI_Lam.third_part.GPEN.face_morpher.facemorpher import aligner -from custom_nodes.ComfyUI_Lam.third_part.GPEN.face_morpher.facemorpher import warper -from custom_nodes.ComfyUI_Lam.third_part.GPEN.face_morpher.facemorpher import blender -from custom_nodes.ComfyUI_Lam.third_part.GPEN.face_morpher.facemorpher import plotter -from custom_nodes.ComfyUI_Lam.third_part.GPEN.face_morpher.facemorpher import videoer - -def verify_args(args): - if args['--images'] is None: - valid = os.path.isfile(args['--src']) & os.path.isfile(args['--dest']) - if not valid: - print('--src=%s or --dest=%s file does not exist. Double check the supplied paths' % ( - args['--src'], args['--dest'])) - exit(1) - else: - valid = os.path.isdir(args['--images']) - if not valid: - print('--images=%s is not a valid directory' % args['--images']) - exit(1) - -def load_image_points(path, size): - img = cv2.imread(path) - points = locator.face_points(img) - - if len(points) == 0: - print('No face in %s' % path) - return None, None - else: - return aligner.resize_align(img, points, size) - -def load_valid_image_points(imgpaths, size): - for path in imgpaths: - img, points = load_image_points(path, size) - if img is not None: - print(path) - yield (img, points) - -def list_imgpaths(images_folder=None, src_image=None, dest_image=None): - if images_folder is None: - yield src_image - yield dest_image - else: - for fname in os.listdir(images_folder): - if (fname.lower().endswith('.jpg') or - fname.lower().endswith('.png') or - fname.lower().endswith('.jpeg')): - yield os.path.join(images_folder, fname) - -def morph(src_img, src_points, dest_img, dest_points, - video, width=500, height=600, num_frames=20, fps=10, - out_frames=None, out_video=None, plot=False, background='black'): - """ - Create a morph sequence from source to destination image - - :param src_img: ndarray source image - :param src_points: source image array of x,y face points - :param dest_img: ndarray destination image - :param dest_points: destination image array of x,y face points - :param video: facemorpher.videoer.Video object - """ - size = (height, width) - stall_frames = np.clip(int(fps*0.15), 1, fps) # Show first & last longer - plt = plotter.Plotter(plot, num_images=num_frames, out_folder=out_frames) - num_frames -= (stall_frames * 2) # No need to process src and dest image - - plt.plot_one(src_img) - video.write(src_img, 1) - - # Produce morph frames! - for percent in np.linspace(1, 0, num=num_frames): - points = locator.weighted_average_points(src_points, dest_points, percent) - src_face = warper.warp_image(src_img, src_points, points, size) - end_face = warper.warp_image(dest_img, dest_points, points, size) - average_face = blender.weighted_average(src_face, end_face, percent) - - if background in ('transparent', 'average'): - mask = blender.mask_from_points(average_face.shape[:2], points) - average_face = np.dstack((average_face, mask)) - - if background == 'average': - average_background = blender.weighted_average(src_img, dest_img, percent) - average_face = blender.overlay_image(average_face, mask, average_background) - - plt.plot_one(average_face) - plt.save(average_face) - video.write(average_face) - - plt.plot_one(dest_img) - video.write(dest_img, stall_frames) - plt.show() - -def morpher(imgpaths, width=500, height=600, num_frames=20, fps=10, - out_frames=None, out_video=None, plot=False, background='black'): - """ - Create a morph sequence from multiple images in imgpaths - - :param imgpaths: array or generator of image paths - """ - video = videoer.Video(out_video, fps, width, height) - images_points_gen = load_valid_image_points(imgpaths, (height, width)) - src_img, src_points = next(images_points_gen) - for dest_img, dest_points in images_points_gen: - morph(src_img, src_points, dest_img, dest_points, video, - width, height, num_frames, fps, out_frames, out_video, plot, background) - src_img, src_points = dest_img, dest_points - video.end() - -def main(): - args = docopt(__doc__, version='Face Morpher 1.0') - verify_args(args) - - morpher(list_imgpaths(args['--images'], args['--src'], args['--dest']), - int(args['--width']), int(args['--height']), - int(args['--num']), int(args['--fps']), - args['--out_frames'], args['--out_video'], - args['--plot'], args['--background']) - - -if __name__ == "__main__": - main() diff --git a/third_part/GPEN/face_morpher/facemorpher/plotter.py b/third_part/GPEN/face_morpher/facemorpher/plotter.py deleted file mode 100644 index cfd7fa4..0000000 --- a/third_part/GPEN/face_morpher/facemorpher/plotter.py +++ /dev/null @@ -1,90 +0,0 @@ -""" -Plot and save images -""" - -import matplotlib.pyplot as plt -import matplotlib.image as mpimg -import os.path -import numpy as np -import cv2 - -def bgr2rgb(img): - # OpenCV's BGR to RGB - rgb = np.copy(img) - rgb[..., 0], rgb[..., 2] = img[..., 2], img[..., 0] - return rgb - -def check_do_plot(func): - def inner(self, *args, **kwargs): - if self.do_plot: - func(self, *args, **kwargs) - - return inner - -def check_do_save(func): - def inner(self, *args, **kwargs): - if self.do_save: - func(self, *args, **kwargs) - - return inner - -class Plotter(object): - def __init__(self, plot=True, rows=0, cols=0, num_images=0, out_folder=None, out_filename=None): - self.save_counter = 1 - self.plot_counter = 1 - self.do_plot = plot - self.do_save = out_filename is not None - self.out_filename = out_filename - self.set_filepath(out_folder) - - if (rows + cols) == 0 and num_images > 0: - # Auto-calculate the number of rows and cols for the figure - self.rows = np.ceil(np.sqrt(num_images / 2.0)) - self.cols = np.ceil(num_images / self.rows) - else: - self.rows = rows - self.cols = cols - - def set_filepath(self, folder): - if folder is None: - self.filepath = None - return - - if not os.path.exists(folder): - os.makedirs(folder) - self.filepath = os.path.join(folder, 'frame{0:03d}.png') - self.do_save = True - - @check_do_save - def save(self, img, filename=None): - if self.filepath: - filename = self.filepath.format(self.save_counter) - self.save_counter += 1 - elif filename is None: - filename = self.out_filename - - mpimg.imsave(filename, bgr2rgb(img)) - print(filename + ' saved') - - @check_do_plot - def plot_one(self, img): - p = plt.subplot(self.rows, self.cols, self.plot_counter) - p.axes.get_xaxis().set_visible(False) - p.axes.get_yaxis().set_visible(False) - plt.imshow(bgr2rgb(img)) - self.plot_counter += 1 - - @check_do_plot - def show(self): - plt.gcf().subplots_adjust(hspace=0.05, wspace=0, - left=0, bottom=0, right=1, top=0.98) - plt.axis('off') - #plt.show() - plt.savefig('result.png') - - @check_do_plot - def plot_mesh(self, points, tri, color='k'): - """ plot triangles """ - for tri_indices in tri.simplices: - t_ext = [tri_indices[0], tri_indices[1], tri_indices[2], tri_indices[0]] - plt.plot(points[t_ext, 0], points[t_ext, 1], color) diff --git a/third_part/GPEN/face_morpher/facemorpher/videoer.py b/third_part/GPEN/face_morpher/facemorpher/videoer.py deleted file mode 100644 index 6aabff4..0000000 --- a/third_part/GPEN/face_morpher/facemorpher/videoer.py +++ /dev/null @@ -1,36 +0,0 @@ -""" -Create a video with image frames -""" - -import cv2 -import numpy as np - - -def check_write_video(func): - def inner(self, *args, **kwargs): - if self.video: - return func(self, *args, **kwargs) - else: - pass - return inner - - -class Video(object): - def __init__(self, filename, fps, w, h): - self.filename = filename - - if filename is None: - self.video = None - else: - fourcc = cv2.VideoWriter_fourcc(*'MJPG') - self.video = cv2.VideoWriter(filename, fourcc, fps, (w, h), True) - - @check_write_video - def write(self, img, num_times=1): - for i in range(num_times): - self.video.write(img[..., :3]) - - @check_write_video - def end(self): - print(self.filename + ' saved') - self.video.release() diff --git a/third_part/GPEN/face_morpher/facemorpher/warper.py b/third_part/GPEN/face_morpher/facemorpher/warper.py deleted file mode 100644 index 50b1a31..0000000 --- a/third_part/GPEN/face_morpher/facemorpher/warper.py +++ /dev/null @@ -1,139 +0,0 @@ -import numpy as np -import scipy.spatial as spatial - -def bilinear_interpolate(img, coords): - """ Interpolates over every image channel - http://en.wikipedia.org/wiki/Bilinear_interpolation - - :param img: max 3 channel image - :param coords: 2 x _m_ array. 1st row = xcoords, 2nd row = ycoords - :returns: array of interpolated pixels with same shape as coords - """ - int_coords = np.int32(coords) - x0, y0 = int_coords - dx, dy = coords - int_coords - - # 4 Neighour pixels - q11 = img[y0, x0] - q21 = img[y0, x0+1] - q12 = img[y0+1, x0] - q22 = img[y0+1, x0+1] - - btm = q21.T * dx + q11.T * (1 - dx) - top = q22.T * dx + q12.T * (1 - dx) - inter_pixel = top * dy + btm * (1 - dy) - - return inter_pixel.T - -def grid_coordinates(points): - """ x,y grid coordinates within the ROI of supplied points - - :param points: points to generate grid coordinates - :returns: array of (x, y) coordinates - """ - xmin = np.min(points[:, 0]) - xmax = np.max(points[:, 0]) + 1 - ymin = np.min(points[:, 1]) - ymax = np.max(points[:, 1]) + 1 - return np.asarray([(x, y) for y in range(ymin, ymax) - for x in range(xmin, xmax)], np.uint32) - -def process_warp(src_img, result_img, tri_affines, dst_points, delaunay): - """ - Warp each triangle from the src_image only within the - ROI of the destination image (points in dst_points). - """ - roi_coords = grid_coordinates(dst_points) - # indices to vertices. -1 if pixel is not in any triangle - roi_tri_indices = delaunay.find_simplex(roi_coords) - - for simplex_index in range(len(delaunay.simplices)): - coords = roi_coords[roi_tri_indices == simplex_index] - num_coords = len(coords) - out_coords = np.dot(tri_affines[simplex_index], - np.vstack((coords.T, np.ones(num_coords)))) - x, y = coords.T - result_img[y, x] = bilinear_interpolate(src_img, out_coords) - - return None - -def triangular_affine_matrices(vertices, src_points, dest_points): - """ - Calculate the affine transformation matrix for each - triangle (x,y) vertex from dest_points to src_points - - :param vertices: array of triplet indices to corners of triangle - :param src_points: array of [x, y] points to landmarks for source image - :param dest_points: array of [x, y] points to landmarks for destination image - :returns: 2 x 3 affine matrix transformation for a triangle - """ - ones = [1, 1, 1] - for tri_indices in vertices: - src_tri = np.vstack((src_points[tri_indices, :].T, ones)) - dst_tri = np.vstack((dest_points[tri_indices, :].T, ones)) - mat = np.dot(src_tri, np.linalg.inv(dst_tri))[:2, :] - yield mat - -def warp_image(src_img, src_points, dest_points, dest_shape, dtype=np.uint8): - # Resultant image will not have an alpha channel - num_chans = 3 - src_img = src_img[:, :, :3] - - rows, cols = dest_shape[:2] - result_img = np.zeros((rows, cols, num_chans), dtype) - - delaunay = spatial.Delaunay(dest_points) - tri_affines = np.asarray(list(triangular_affine_matrices( - delaunay.simplices, src_points, dest_points))) - - process_warp(src_img, result_img, tri_affines, dest_points, delaunay) - - return result_img - -def test_local(): - from functools import partial - import cv2 - import scipy.misc - import locator - import aligner - from matplotlib import pyplot as plt - - # Load source image - face_points_func = partial(locator.face_points, '../data') - base_path = '../females/Screenshot 2015-03-04 17.11.12.png' - src_path = '../females/BlDmB5QCYAAY8iw.jpg' - src_img = cv2.imread(src_path) - - # Define control points for warps - src_points = face_points_func(src_path) - base_img = cv2.imread(base_path) - base_points = face_points_func(base_path) - - size = (600, 500) - src_img, src_points = aligner.resize_align(src_img, src_points, size) - base_img, base_points = aligner.resize_align(base_img, base_points, size) - result_points = locator.weighted_average_points(src_points, base_points, 0.2) - - # Perform transform - dst_img1 = warp_image(src_img, src_points, result_points, size) - dst_img2 = warp_image(base_img, base_points, result_points, size) - - import blender - ave = blender.weighted_average(dst_img1, dst_img2, 0.6) - mask = blender.mask_from_points(size, result_points) - blended_img = blender.poisson_blend(dst_img1, dst_img2, mask) - - plt.subplot(2, 2, 1) - plt.imshow(ave) - plt.subplot(2, 2, 2) - plt.imshow(dst_img1) - plt.subplot(2, 2, 3) - plt.imshow(dst_img2) - plt.subplot(2, 2, 4) - - plt.imshow(blended_img) - plt.show() - - -if __name__ == "__main__": - test_local() diff --git a/third_part/GPEN/face_morpher/requirements.txt b/third_part/GPEN/face_morpher/requirements.txt deleted file mode 100644 index c0ce5bf..0000000 --- a/third_part/GPEN/face_morpher/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -numpy -scipy -matplotlib -docopt -dlib diff --git a/third_part/GPEN/face_morpher/scripts/make_docs.sh b/third_part/GPEN/face_morpher/scripts/make_docs.sh deleted file mode 100644 index 3b8b22d..0000000 --- a/third_part/GPEN/face_morpher/scripts/make_docs.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash - -rm -rf docs -# reStructuredText in python files to rst. Documentation in docs folder -sphinx-apidoc -A "Alyssa Quek" -f -F -o docs facemorpher/ - -cd docs - -# Append module path to end of conf file -echo "" >> conf.py -echo "import os" >> conf.py -echo "import sys" >> conf.py -echo "sys.path.insert(0, os.path.abspath('../'))" >> conf.py -echo "sys.path.insert(0, os.path.abspath('../facemorpher'))" >> conf.py - -# Make sphinx documentation -make html -cd .. diff --git a/third_part/GPEN/face_morpher/scripts/publish_ghpages.sh b/third_part/GPEN/face_morpher/scripts/publish_ghpages.sh deleted file mode 100644 index 3d3d956..0000000 --- a/third_part/GPEN/face_morpher/scripts/publish_ghpages.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/bash - -# delete previous gh-pages -git branch -D gh-pages -git push origin :gh-pages - -git checkout -b gh-pages -git rebase master -git reset HEAD - -# make docs -./scripts/make_docs.sh - -# Add docs -mv docs/_build/html/*.html . -git add *.html -mv docs/_build/html/*.js . -git add *.js -mv docs/_build/html/_static/ _static -git add _static - -touch .nojekyll -git add .nojekyll - -# Publish to gh-pages -git commit -m "docs" -git push origin gh-pages - -git checkout master diff --git a/third_part/GPEN/face_morpher/setup.cfg b/third_part/GPEN/face_morpher/setup.cfg deleted file mode 100644 index 437cb18..0000000 --- a/third_part/GPEN/face_morpher/setup.cfg +++ /dev/null @@ -1,7 +0,0 @@ -[pep8] -ignore = E111,E114,E226,E302,E41,E121,E701 -max-line-length = 100 - -[flake8] -ignore = E111,E114,E226,E302,E41,E121,E701 -max-line-length = 100 \ No newline at end of file diff --git a/third_part/GPEN/face_morpher/setup.py b/third_part/GPEN/face_morpher/setup.py deleted file mode 100644 index 09fba56..0000000 --- a/third_part/GPEN/face_morpher/setup.py +++ /dev/null @@ -1,30 +0,0 @@ -from setuptools import setup, find_packages - -# To test locally: python setup.py sdist bdist_wheel -# To upload to pypi: twine upload dist/* - -setup( - name='facemorpher', - version='5.2.dev0', - author='Alyssa Quek', - author_email='alyssaquek@gmail.com', - description=('Warp, morph and average human faces!'), - keywords='face morphing, averaging, warping', - url='https://github.com/alyssaq/face_morpher', - license='MIT', - packages=find_packages(), - install_requires=[ - 'docopt', - 'numpy', - 'scipy', - 'matplotlib', - 'dlib' - ], - entry_points={'console_scripts': [ - 'facemorpher=facemorpher.morpher:main', - 'faceaverager=facemorpher.averager:main' - ] - }, - data_files=[('readme', ['README.rst'])], - long_description=open('README.rst').read(), -) diff --git a/third_part/GPEN/face_parse/blocks.py b/third_part/GPEN/face_parse/blocks.py deleted file mode 100644 index fc7facb..0000000 --- a/third_part/GPEN/face_parse/blocks.py +++ /dev/null @@ -1,127 +0,0 @@ -# -*- coding: utf-8 -*- -import torch -import torch.nn as nn -from torch.nn.parameter import Parameter -from torch.nn import functional as F -import numpy as np - -class NormLayer(nn.Module): - """Normalization Layers. - ------------ - # Arguments - - channels: input channels, for batch norm and instance norm. - - input_size: input shape without batch size, for layer norm. - """ - def __init__(self, channels, normalize_shape=None, norm_type='bn', ref_channels=None): - super(NormLayer, self).__init__() - norm_type = norm_type.lower() - self.norm_type = norm_type - if norm_type == 'bn': - self.norm = nn.BatchNorm2d(channels, affine=True) - elif norm_type == 'in': - self.norm = nn.InstanceNorm2d(channels, affine=False) - elif norm_type == 'gn': - self.norm = nn.GroupNorm(32, channels, affine=True) - elif norm_type == 'pixel': - self.norm = lambda x: F.normalize(x, p=2, dim=1) - elif norm_type == 'layer': - self.norm = nn.LayerNorm(normalize_shape) - elif norm_type == 'none': - self.norm = lambda x: x*1.0 - else: - assert 1==0, 'Norm type {} not support.'.format(norm_type) - - def forward(self, x, ref=None): - if self.norm_type == 'spade': - return self.norm(x, ref) - else: - return self.norm(x) - - -class ReluLayer(nn.Module): - """Relu Layer. - ------------ - # Arguments - - relu type: type of relu layer, candidates are - - ReLU - - LeakyReLU: default relu slope 0.2 - - PRelu - - SELU - - none: direct pass - """ - def __init__(self, channels, relu_type='relu'): - super(ReluLayer, self).__init__() - relu_type = relu_type.lower() - if relu_type == 'relu': - self.func = nn.ReLU(True) - elif relu_type == 'leakyrelu': - self.func = nn.LeakyReLU(0.2, inplace=True) - elif relu_type == 'prelu': - self.func = nn.PReLU(channels) - elif relu_type == 'selu': - self.func = nn.SELU(True) - elif relu_type == 'none': - self.func = lambda x: x*1.0 - else: - assert 1==0, 'Relu type {} not support.'.format(relu_type) - - def forward(self, x): - return self.func(x) - - -class ConvLayer(nn.Module): - def __init__(self, in_channels, out_channels, kernel_size=3, scale='none', norm_type='none', relu_type='none', use_pad=True, bias=True): - super(ConvLayer, self).__init__() - self.use_pad = use_pad - self.norm_type = norm_type - if norm_type in ['bn']: - bias = False - - stride = 2 if scale == 'down' else 1 - - self.scale_func = lambda x: x - if scale == 'up': - self.scale_func = lambda x: nn.functional.interpolate(x, scale_factor=2, mode='nearest') - - self.reflection_pad = nn.ReflectionPad2d(int(np.ceil((kernel_size - 1.)/2))) - self.conv2d = nn.Conv2d(in_channels, out_channels, kernel_size, stride, bias=bias) - - self.relu = ReluLayer(out_channels, relu_type) - self.norm = NormLayer(out_channels, norm_type=norm_type) - - def forward(self, x): - out = self.scale_func(x) - if self.use_pad: - out = self.reflection_pad(out) - out = self.conv2d(out) - out = self.norm(out) - out = self.relu(out) - return out - - -class ResidualBlock(nn.Module): - """ - Residual block recommended in: http://torch.ch/blog/2016/02/04/resnets.html - """ - def __init__(self, c_in, c_out, relu_type='prelu', norm_type='bn', scale='none'): - super(ResidualBlock, self).__init__() - - if scale == 'none' and c_in == c_out: - self.shortcut_func = lambda x: x - else: - self.shortcut_func = ConvLayer(c_in, c_out, 3, scale) - - scale_config_dict = {'down': ['none', 'down'], 'up': ['up', 'none'], 'none': ['none', 'none']} - scale_conf = scale_config_dict[scale] - - self.conv1 = ConvLayer(c_in, c_out, 3, scale_conf[0], norm_type=norm_type, relu_type=relu_type) - self.conv2 = ConvLayer(c_out, c_out, 3, scale_conf[1], norm_type=norm_type, relu_type='none') - - def forward(self, x): - identity = self.shortcut_func(x) - - res = self.conv1(x) - res = self.conv2(res) - return identity + res - - diff --git a/third_part/GPEN/face_parse/face_parsing.py b/third_part/GPEN/face_parse/face_parsing.py deleted file mode 100644 index da08c0e..0000000 --- a/third_part/GPEN/face_parse/face_parsing.py +++ /dev/null @@ -1,148 +0,0 @@ -''' -@paper: GAN Prior Embedded Network for Blind Face Restoration in the Wild (CVPR2021) -@author: yangxy (yangtao9009@gmail.com) -''' -import os -import cv2 -import torch -import numpy as np -from custom_nodes.ComfyUI_Lam.third_part.GPEN.face_parse.parse_model import ParseNet -import torch.nn.functional as F - -from custom_nodes.ComfyUI_Lam.third_part.GPEN.face_parse.model import BiSeNet -import torchvision.transforms as transforms - -class FaceParse(object): - def __init__(self, base_dir='./', model='ParseNet-latest', device='cuda', mask_map = [0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0]): - self.mfile = os.path.join(base_dir, model+'.pth') - self.size = 512 - self.device = device - - ''' - 0: 'background' 1: 'skin' 2: 'nose' - 3: 'eye_g' 4: 'l_eye' 5: 'r_eye' - 6: 'l_brow' 7: 'r_brow' 8: 'l_ear' - 9: 'r_ear' 10: 'mouth' 11: 'u_lip' - 12: 'l_lip' 13: 'hair' 14: 'hat' - 15: 'ear_r' 16: 'neck_l' 17: 'neck' - 18: 'cloth' - ''' - # self.MASK_COLORMAP = [[0, 0, 0], [204, 0, 0], [76, 153, 0], [204, 204, 0], [51, 51, 255], [204, 0, 204], [0, 255, 255], [255, 204, 204], [102, 51, 0], [255, 0, 0], [102, 204, 0], [255, 255, 0], [0, 0, 153], [0, 0, 204], [255, 51, 153], [0, 204, 204], [0, 51, 0], [255, 153, 51], [0, 204, 0]] - #self.#MASK_COLORMAP = [[0, 0, 0], [204, 0, 0], [76, 153, 0], [204, 204, 0], [51, 51, 255], [204, 0, 204], [0, 255, 255], [255, 204, 204], [102, 51, 0], [255, 0, 0], [102, 204, 0], [255, 255, 0], [0, 0, 153], [0, 0, 204], [255, 51, 153], [0, 204, 204], [0, 51, 0], [255, 153, 51], [0, 204, 0]] = [[0, 0, 0], [204, 0, 0], [76, 153, 0], [204, 204, 0], [51, 51, 255], [204, 0, 204], [0, 255, 255], [255, 204, 204], [102, 51, 0], [255, 0, 0], [102, 204, 0], [255, 255, 0], [0, 0, 153], [0, 0, 204], [255, 51, 153], [0, 204, 204], [0, 51, 0], [0, 0, 0], [0, 0, 0]] - # self.MASK_COLORMAP = [0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 255, 0, 0, 0] - self.MASK_COLORMAP = mask_map - - self.load_model() - - def load_model(self): - self.faceparse = ParseNet(self.size, self.size, 32, 64, 19, norm_type='bn', relu_type='LeakyReLU', ch_range=[32, 256]) - self.faceparse.load_state_dict(torch.load(self.mfile, map_location=torch.device('cpu'))) - self.faceparse.to(self.device) - self.faceparse.eval() - - def process(self, im, masks=[0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0]): - im = cv2.resize(im, (self.size, self.size)) - imt = self.img2tensor(im) - with torch.no_grad(): - pred_mask, sr_img_tensor = self.faceparse(imt) # (1, 19, 512, 512) - mask = self.tenor2mask(pred_mask, masks) - - return mask - - def process_tensor(self, imt): - imt = F.interpolate(imt.flip(1)*2-1, (self.size, self.size)) - pred_mask, sr_img_tensor = self.faceparse(imt) - - mask = pred_mask.argmax(dim=1) - for idx, color in enumerate(self.MASK_COLORMAP): - mask = torch.where(mask==idx, color, mask) - #mask = mask.repeat(3, 1, 1).unsqueeze(0) #.cpu().float().numpy() - mask = mask.unsqueeze(0) - - return mask - - def img2tensor(self, img): - img = img[..., ::-1] # BGR to RGB - img = img / 255. * 2 - 1 - img_tensor = torch.from_numpy(img.transpose(2, 0, 1)).unsqueeze(0).to(self.device) - return img_tensor.float() - - def tenor2mask(self, tensor, masks): - if len(tensor.shape) < 4: - tensor = tensor.unsqueeze(0) - if tensor.shape[1] > 1: - tensor = tensor.argmax(dim=1) - - tensor = tensor.squeeze(1).data.cpu().numpy() # (1, 512, 512) - color_maps = [] - for t in tensor: - #tmp_img = np.zeros(tensor.shape[1:] + (3,)) - tmp_img = np.zeros(tensor.shape[1:]) - for idx, color in enumerate(masks): - tmp_img[t == idx] = color - color_maps.append(tmp_img.astype(np.uint8)) - return color_maps - - - -class FaceParse_v2(object): - def __init__(self, device='cuda', mask_map = [0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0]): - self.mfile = '/apdcephfs/private_quincheng/Expression/face-parsing.PyTorch/res/cp/79999_iter.pth' - self.size = 512 - self.device = device - - ''' - 0: 'background' 1: 'skin' 2: 'nose' - 3: 'eye_g' 4: 'l_eye' 5: 'r_eye' - 6: 'l_brow' 7: 'r_brow' 8: 'l_ear' - 9: 'r_ear' 10: 'mouth' 11: 'u_lip' - 12: 'l_lip' 13: 'hair' 14: 'hat' - 15: 'ear_r' 16: 'neck_l' 17: 'neck' - 18: 'cloth' - ''' - # self.MASK_COLORMAP = [[0, 0, 0], [204, 0, 0], [76, 153, 0], [204, 204, 0], [51, 51, 255], [204, 0, 204], [0, 255, 255], [255, 204, 204], [102, 51, 0], [255, 0, 0], [102, 204, 0], [255, 255, 0], [0, 0, 153], [0, 0, 204], [255, 51, 153], [0, 204, 204], [0, 51, 0], [255, 153, 51], [0, 204, 0]] - #self.#MASK_COLORMAP = [[0, 0, 0], [204, 0, 0], [76, 153, 0], [204, 204, 0], [51, 51, 255], [204, 0, 204], [0, 255, 255], [255, 204, 204], [102, 51, 0], [255, 0, 0], [102, 204, 0], [255, 255, 0], [0, 0, 153], [0, 0, 204], [255, 51, 153], [0, 204, 204], [0, 51, 0], [255, 153, 51], [0, 204, 0]] = [[0, 0, 0], [204, 0, 0], [76, 153, 0], [204, 204, 0], [51, 51, 255], [204, 0, 204], [0, 255, 255], [255, 204, 204], [102, 51, 0], [255, 0, 0], [102, 204, 0], [255, 255, 0], [0, 0, 153], [0, 0, 204], [255, 51, 153], [0, 204, 204], [0, 51, 0], [0, 0, 0], [0, 0, 0]] - # self.MASK_COLORMAP = [0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 255, 0, 0, 0] - self.MASK_COLORMAP = mask_map - self.load_model() - self.to_tensor = transforms.Compose([ - transforms.ToTensor(), - transforms.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225)), - ]) - - def load_model(self): - self.faceparse = BiSeNet(n_classes=19) - self.faceparse.load_state_dict(torch.load(self.mfile)) - self.faceparse.to(self.device) - self.faceparse.eval() - - def process(self, im, masks=[0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0]): - im = cv2.resize(im[...,::-1], (self.size, self.size)) - im = self.to_tensor(im) - imt = torch.unsqueeze(im, 0).to(self.device) - with torch.no_grad(): - pred_mask = self.faceparse(imt)[0] - mask = self.tenor2mask(pred_mask, masks) - return mask - - # def img2tensor(self, img): - # img = img[..., ::-1] # BGR to RGB - # img = img / 255. * 2 - 1 - # img_tensor = torch.from_numpy(img.transpose(2, 0, 1)).unsqueeze(0).to(self.device) - # return img_tensor.float() - - def tenor2mask(self, tensor, masks): - if len(tensor.shape) < 4: - tensor = tensor.unsqueeze(0) - if tensor.shape[1] > 1: - tensor = tensor.argmax(dim=1) - - tensor = tensor.squeeze(1).data.cpu().numpy() - color_maps = [] - for t in tensor: - #tmp_img = np.zeros(tensor.shape[1:] + (3,)) - tmp_img = np.zeros(tensor.shape[1:]) - for idx, color in enumerate(masks): - tmp_img[t == idx] = color - color_maps.append(tmp_img.astype(np.uint8)) - return color_maps \ No newline at end of file diff --git a/third_part/GPEN/face_parse/mask.png b/third_part/GPEN/face_parse/mask.png deleted file mode 100644 index 729f36dfb23b90381fe819fe24326153ba46828e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9303 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 R;SYw!G5vz*XO_q9|8M>P=->bV diff --git a/third_part/GPEN/face_parse/model.py b/third_part/GPEN/face_parse/model.py deleted file mode 100644 index 5119e75..0000000 --- a/third_part/GPEN/face_parse/model.py +++ /dev/null @@ -1,283 +0,0 @@ -#!/usr/bin/python -# -*- encoding: utf-8 -*- - - -import torch -import torch.nn as nn -import torch.nn.functional as F -import torchvision - -from .resnet import Resnet18 -# from modules.bn import InPlaceABNSync as BatchNorm2d - - -class ConvBNReLU(nn.Module): - def __init__(self, in_chan, out_chan, ks=3, stride=1, padding=1, *args, **kwargs): - super(ConvBNReLU, self).__init__() - self.conv = nn.Conv2d(in_chan, - out_chan, - kernel_size = ks, - stride = stride, - padding = padding, - bias = False) - self.bn = nn.BatchNorm2d(out_chan) - self.init_weight() - - def forward(self, x): - x = self.conv(x) - x = F.relu(self.bn(x)) - return x - - def init_weight(self): - for ly in self.children(): - if isinstance(ly, nn.Conv2d): - nn.init.kaiming_normal_(ly.weight, a=1) - if not ly.bias is None: nn.init.constant_(ly.bias, 0) - -class BiSeNetOutput(nn.Module): - def __init__(self, in_chan, mid_chan, n_classes, *args, **kwargs): - super(BiSeNetOutput, self).__init__() - self.conv = ConvBNReLU(in_chan, mid_chan, ks=3, stride=1, padding=1) - self.conv_out = nn.Conv2d(mid_chan, n_classes, kernel_size=1, bias=False) - self.init_weight() - - def forward(self, x): - x = self.conv(x) - x = self.conv_out(x) - return x - - def init_weight(self): - for ly in self.children(): - if isinstance(ly, nn.Conv2d): - nn.init.kaiming_normal_(ly.weight, a=1) - if not ly.bias is None: nn.init.constant_(ly.bias, 0) - - def get_params(self): - wd_params, nowd_params = [], [] - for name, module in self.named_modules(): - if isinstance(module, nn.Linear) or isinstance(module, nn.Conv2d): - wd_params.append(module.weight) - if not module.bias is None: - nowd_params.append(module.bias) - elif isinstance(module, nn.BatchNorm2d): - nowd_params += list(module.parameters()) - return wd_params, nowd_params - - -class AttentionRefinementModule(nn.Module): - def __init__(self, in_chan, out_chan, *args, **kwargs): - super(AttentionRefinementModule, self).__init__() - self.conv = ConvBNReLU(in_chan, out_chan, ks=3, stride=1, padding=1) - self.conv_atten = nn.Conv2d(out_chan, out_chan, kernel_size= 1, bias=False) - self.bn_atten = nn.BatchNorm2d(out_chan) - self.sigmoid_atten = nn.Sigmoid() - self.init_weight() - - def forward(self, x): - feat = self.conv(x) - atten = F.avg_pool2d(feat, feat.size()[2:]) - atten = self.conv_atten(atten) - atten = self.bn_atten(atten) - atten = self.sigmoid_atten(atten) - out = torch.mul(feat, atten) - return out - - def init_weight(self): - for ly in self.children(): - if isinstance(ly, nn.Conv2d): - nn.init.kaiming_normal_(ly.weight, a=1) - if not ly.bias is None: nn.init.constant_(ly.bias, 0) - - -class ContextPath(nn.Module): - def __init__(self, *args, **kwargs): - super(ContextPath, self).__init__() - self.resnet = Resnet18() - self.arm16 = AttentionRefinementModule(256, 128) - self.arm32 = AttentionRefinementModule(512, 128) - self.conv_head32 = ConvBNReLU(128, 128, ks=3, stride=1, padding=1) - self.conv_head16 = ConvBNReLU(128, 128, ks=3, stride=1, padding=1) - self.conv_avg = ConvBNReLU(512, 128, ks=1, stride=1, padding=0) - - self.init_weight() - - def forward(self, x): - H0, W0 = x.size()[2:] - feat8, feat16, feat32 = self.resnet(x) - H8, W8 = feat8.size()[2:] - H16, W16 = feat16.size()[2:] - H32, W32 = feat32.size()[2:] - - avg = F.avg_pool2d(feat32, feat32.size()[2:]) - avg = self.conv_avg(avg) - avg_up = F.interpolate(avg, (H32, W32), mode='nearest') - - feat32_arm = self.arm32(feat32) - feat32_sum = feat32_arm + avg_up - feat32_up = F.interpolate(feat32_sum, (H16, W16), mode='nearest') - feat32_up = self.conv_head32(feat32_up) - - feat16_arm = self.arm16(feat16) - feat16_sum = feat16_arm + feat32_up - feat16_up = F.interpolate(feat16_sum, (H8, W8), mode='nearest') - feat16_up = self.conv_head16(feat16_up) - - return feat8, feat16_up, feat32_up # x8, x8, x16 - - def init_weight(self): - for ly in self.children(): - if isinstance(ly, nn.Conv2d): - nn.init.kaiming_normal_(ly.weight, a=1) - if not ly.bias is None: nn.init.constant_(ly.bias, 0) - - def get_params(self): - wd_params, nowd_params = [], [] - for name, module in self.named_modules(): - if isinstance(module, (nn.Linear, nn.Conv2d)): - wd_params.append(module.weight) - if not module.bias is None: - nowd_params.append(module.bias) - elif isinstance(module, nn.BatchNorm2d): - nowd_params += list(module.parameters()) - return wd_params, nowd_params - - -### This is not used, since I replace this with the resnet feature with the same size -class SpatialPath(nn.Module): - def __init__(self, *args, **kwargs): - super(SpatialPath, self).__init__() - self.conv1 = ConvBNReLU(3, 64, ks=7, stride=2, padding=3) - self.conv2 = ConvBNReLU(64, 64, ks=3, stride=2, padding=1) - self.conv3 = ConvBNReLU(64, 64, ks=3, stride=2, padding=1) - self.conv_out = ConvBNReLU(64, 128, ks=1, stride=1, padding=0) - self.init_weight() - - def forward(self, x): - feat = self.conv1(x) - feat = self.conv2(feat) - feat = self.conv3(feat) - feat = self.conv_out(feat) - return feat - - def init_weight(self): - for ly in self.children(): - if isinstance(ly, nn.Conv2d): - nn.init.kaiming_normal_(ly.weight, a=1) - if not ly.bias is None: nn.init.constant_(ly.bias, 0) - - def get_params(self): - wd_params, nowd_params = [], [] - for name, module in self.named_modules(): - if isinstance(module, nn.Linear) or isinstance(module, nn.Conv2d): - wd_params.append(module.weight) - if not module.bias is None: - nowd_params.append(module.bias) - elif isinstance(module, nn.BatchNorm2d): - nowd_params += list(module.parameters()) - return wd_params, nowd_params - - -class FeatureFusionModule(nn.Module): - def __init__(self, in_chan, out_chan, *args, **kwargs): - super(FeatureFusionModule, self).__init__() - self.convblk = ConvBNReLU(in_chan, out_chan, ks=1, stride=1, padding=0) - self.conv1 = nn.Conv2d(out_chan, - out_chan//4, - kernel_size = 1, - stride = 1, - padding = 0, - bias = False) - self.conv2 = nn.Conv2d(out_chan//4, - out_chan, - kernel_size = 1, - stride = 1, - padding = 0, - bias = False) - self.relu = nn.ReLU(inplace=True) - self.sigmoid = nn.Sigmoid() - self.init_weight() - - def forward(self, fsp, fcp): - fcat = torch.cat([fsp, fcp], dim=1) - feat = self.convblk(fcat) - atten = F.avg_pool2d(feat, feat.size()[2:]) - atten = self.conv1(atten) - atten = self.relu(atten) - atten = self.conv2(atten) - atten = self.sigmoid(atten) - feat_atten = torch.mul(feat, atten) - feat_out = feat_atten + feat - return feat_out - - def init_weight(self): - for ly in self.children(): - if isinstance(ly, nn.Conv2d): - nn.init.kaiming_normal_(ly.weight, a=1) - if not ly.bias is None: nn.init.constant_(ly.bias, 0) - - def get_params(self): - wd_params, nowd_params = [], [] - for name, module in self.named_modules(): - if isinstance(module, nn.Linear) or isinstance(module, nn.Conv2d): - wd_params.append(module.weight) - if not module.bias is None: - nowd_params.append(module.bias) - elif isinstance(module, nn.BatchNorm2d): - nowd_params += list(module.parameters()) - return wd_params, nowd_params - - -class BiSeNet(nn.Module): - def __init__(self, n_classes, *args, **kwargs): - super(BiSeNet, self).__init__() - self.cp = ContextPath() - ## here self.sp is deleted - self.ffm = FeatureFusionModule(256, 256) - self.conv_out = BiSeNetOutput(256, 256, n_classes) - self.conv_out16 = BiSeNetOutput(128, 64, n_classes) - self.conv_out32 = BiSeNetOutput(128, 64, n_classes) - self.init_weight() - - def forward(self, x): - H, W = x.size()[2:] - feat_res8, feat_cp8, feat_cp16 = self.cp(x) # here return res3b1 feature - feat_sp = feat_res8 # use res3b1 feature to replace spatial path feature - feat_fuse = self.ffm(feat_sp, feat_cp8) - - feat_out = self.conv_out(feat_fuse) - feat_out16 = self.conv_out16(feat_cp8) - feat_out32 = self.conv_out32(feat_cp16) - - feat_out = F.interpolate(feat_out, (H, W), mode='bilinear', align_corners=True) - feat_out16 = F.interpolate(feat_out16, (H, W), mode='bilinear', align_corners=True) - feat_out32 = F.interpolate(feat_out32, (H, W), mode='bilinear', align_corners=True) - return feat_out, feat_out16, feat_out32 - - def init_weight(self): - for ly in self.children(): - if isinstance(ly, nn.Conv2d): - nn.init.kaiming_normal_(ly.weight, a=1) - if not ly.bias is None: nn.init.constant_(ly.bias, 0) - - def get_params(self): - wd_params, nowd_params, lr_mul_wd_params, lr_mul_nowd_params = [], [], [], [] - for name, child in self.named_children(): - child_wd_params, child_nowd_params = child.get_params() - if isinstance(child, FeatureFusionModule) or isinstance(child, BiSeNetOutput): - lr_mul_wd_params += child_wd_params - lr_mul_nowd_params += child_nowd_params - else: - wd_params += child_wd_params - nowd_params += child_nowd_params - return wd_params, nowd_params, lr_mul_wd_params, lr_mul_nowd_params - - -if __name__ == "__main__": - net = BiSeNet(19) - net.cuda() - net.eval() - in_ten = torch.randn(16, 3, 640, 480).cuda() - out, out16, out32 = net(in_ten) - print(out.shape) - - net.get_params() diff --git a/third_part/GPEN/face_parse/parse_model.py b/third_part/GPEN/face_parse/parse_model.py deleted file mode 100644 index 9a27545..0000000 --- a/third_part/GPEN/face_parse/parse_model.py +++ /dev/null @@ -1,77 +0,0 @@ -''' -@Created by chaofengc (chaofenghust@gmail.com) - -@Modified by yangxy (yangtao9009@gmail.com) -''' - -from custom_nodes.ComfyUI_Lam.third_part.GPEN.face_parse.blocks import * -import torch -from torch import nn -import numpy as np - -def define_P(in_size=512, out_size=512, min_feat_size=32, relu_type='LeakyReLU', isTrain=False, weight_path=None): - net = ParseNet(in_size, out_size, min_feat_size, 64, 19, norm_type='bn', relu_type=relu_type, ch_range=[32, 256]) - if not isTrain: - net.eval() - if weight_path is not None: - net.load_state_dict(torch.load(weight_path)) - return net - - -class ParseNet(nn.Module): - def __init__(self, - in_size=128, - out_size=128, - min_feat_size=32, - base_ch=64, - parsing_ch=19, - res_depth=10, - relu_type='prelu', - norm_type='bn', - ch_range=[32, 512], - ): - super().__init__() - self.res_depth = res_depth - act_args = {'norm_type': norm_type, 'relu_type': relu_type} - min_ch, max_ch = ch_range - - ch_clip = lambda x: max(min_ch, min(x, max_ch)) - min_feat_size = min(in_size, min_feat_size) - - down_steps = int(np.log2(in_size//min_feat_size)) - up_steps = int(np.log2(out_size//min_feat_size)) - - # =============== define encoder-body-decoder ==================== - self.encoder = [] - self.encoder.append(ConvLayer(3, base_ch, 3, 1)) - head_ch = base_ch - for i in range(down_steps): - cin, cout = ch_clip(head_ch), ch_clip(head_ch * 2) - self.encoder.append(ResidualBlock(cin, cout, scale='down', **act_args)) - head_ch = head_ch * 2 - - self.body = [] - for i in range(res_depth): - self.body.append(ResidualBlock(ch_clip(head_ch), ch_clip(head_ch), **act_args)) - - self.decoder = [] - for i in range(up_steps): - cin, cout = ch_clip(head_ch), ch_clip(head_ch // 2) - self.decoder.append(ResidualBlock(cin, cout, scale='up', **act_args)) - head_ch = head_ch // 2 - - self.encoder = nn.Sequential(*self.encoder) - self.body = nn.Sequential(*self.body) - self.decoder = nn.Sequential(*self.decoder) - self.out_img_conv = ConvLayer(ch_clip(head_ch), 3) - self.out_mask_conv = ConvLayer(ch_clip(head_ch), parsing_ch) - - def forward(self, x): - feat = self.encoder(x) - x = feat + self.body(feat) - x = self.decoder(x) - out_img = self.out_img_conv(x) - out_mask = self.out_mask_conv(x) - return out_mask, out_img - - diff --git a/third_part/GPEN/face_parse/resnet.py b/third_part/GPEN/face_parse/resnet.py deleted file mode 100644 index aa2bf95..0000000 --- a/third_part/GPEN/face_parse/resnet.py +++ /dev/null @@ -1,109 +0,0 @@ -#!/usr/bin/python -# -*- encoding: utf-8 -*- - -import torch -import torch.nn as nn -import torch.nn.functional as F -import torch.utils.model_zoo as modelzoo - -# from modules.bn import InPlaceABNSync as BatchNorm2d - -resnet18_url = 'https://download.pytorch.org/models/resnet18-5c106cde.pth' - - -def conv3x3(in_planes, out_planes, stride=1): - """3x3 convolution with padding""" - return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride, - padding=1, bias=False) - - -class BasicBlock(nn.Module): - def __init__(self, in_chan, out_chan, stride=1): - super(BasicBlock, self).__init__() - self.conv1 = conv3x3(in_chan, out_chan, stride) - self.bn1 = nn.BatchNorm2d(out_chan) - self.conv2 = conv3x3(out_chan, out_chan) - self.bn2 = nn.BatchNorm2d(out_chan) - self.relu = nn.ReLU(inplace=True) - self.downsample = None - if in_chan != out_chan or stride != 1: - self.downsample = nn.Sequential( - nn.Conv2d(in_chan, out_chan, - kernel_size=1, stride=stride, bias=False), - nn.BatchNorm2d(out_chan), - ) - - def forward(self, x): - residual = self.conv1(x) - residual = F.relu(self.bn1(residual)) - residual = self.conv2(residual) - residual = self.bn2(residual) - - shortcut = x - if self.downsample is not None: - shortcut = self.downsample(x) - - out = shortcut + residual - out = self.relu(out) - return out - - -def create_layer_basic(in_chan, out_chan, bnum, stride=1): - layers = [BasicBlock(in_chan, out_chan, stride=stride)] - for i in range(bnum-1): - layers.append(BasicBlock(out_chan, out_chan, stride=1)) - return nn.Sequential(*layers) - - -class Resnet18(nn.Module): - def __init__(self): - super(Resnet18, self).__init__() - self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, - bias=False) - self.bn1 = nn.BatchNorm2d(64) - self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1) - self.layer1 = create_layer_basic(64, 64, bnum=2, stride=1) - self.layer2 = create_layer_basic(64, 128, bnum=2, stride=2) - self.layer3 = create_layer_basic(128, 256, bnum=2, stride=2) - self.layer4 = create_layer_basic(256, 512, bnum=2, stride=2) - self.init_weight() - - def forward(self, x): - x = self.conv1(x) - x = F.relu(self.bn1(x)) - x = self.maxpool(x) - - x = self.layer1(x) - feat8 = self.layer2(x) # 1/8 - feat16 = self.layer3(feat8) # 1/16 - feat32 = self.layer4(feat16) # 1/32 - return feat8, feat16, feat32 - - def init_weight(self): - state_dict = modelzoo.load_url(resnet18_url) - self_state_dict = self.state_dict() - for k, v in state_dict.items(): - if 'fc' in k: continue - self_state_dict.update({k: v}) - self.load_state_dict(self_state_dict) - - def get_params(self): - wd_params, nowd_params = [], [] - for name, module in self.named_modules(): - if isinstance(module, (nn.Linear, nn.Conv2d)): - wd_params.append(module.weight) - if not module.bias is None: - nowd_params.append(module.bias) - elif isinstance(module, nn.BatchNorm2d): - nowd_params += list(module.parameters()) - return wd_params, nowd_params - - -if __name__ == "__main__": - net = Resnet18() - x = torch.randn(16, 3, 224, 224) - out = net(x) - print(out[0].size()) - print(out[1].size()) - print(out[2].size()) - net.get_params() diff --git a/third_part/GPEN/face_parse/test.png b/third_part/GPEN/face_parse/test.png deleted file mode 100644 index f2bea14ce322ad51da09a640c0fcd3617ef3105a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 178505 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≫d($E5$;qK2jo-60rDR03RA-C$6h}<+TBEk zXKNTPsYPPuFd)dy!je??9EkpS4g{9yFo6LFt;rk>e(uQaw-#LOhwP!V9P~? zi`Y$>Lbaeuc_phwsQ`xie_8?4(#ys$HcD!a(g2&jCKsGjT7=ycK??)iVQ_cp)vzP7 z&9=-7=|idUR>4J86~y$C1z@cM@{;3p{DGg2_vdrI&u{{LUZ3;)__+>uz{S-~W0k;& z3nS+;x1Raxvu((y7z_5XtCUc>5TqboMM58{w-GQvp@(u`SHRN8uh=!yOX>urF4?c1 z7ZFVAj)6oQ#0g1DJ1m4s=;npGgK7=gasPNf6u?HuhZcT(p~u(E(*1vUbrNS-F*9nR@<0sV# z*NMxW0}ApR(xVEs(2hy?DkKs;oJfw`gk4p7H}>K;sRxEjuJilh6#}uV^r} zz(BFV68YfyLgW7B;N?@a!Yn@RHb{Gzp_QNMt0eK!g;=5;cKw1W-u`W%8KEO6yGQMmsSwKnEj*TB!8VLO4TVnpUPcg14&Wk#${U1r>Gv&Mgp;^eM^jn{o!Uj49$0(|4;;hu#^UHlv}&y#ApQ%-A!^ z@0I?ZX~|_bb*1v*M7RQwoFf5)WCpBhWaGPf8aR^r*>k{y&0f?gL3(-$qZ`yz7UY1O zhrFPt%b!2~et&-6pU=l>w%@jeVe8sUnfuN7DMTxRZ;`9HOidTFLL&f>D#O&4h{Y8B zx)H3C*<`6&-69QbIj##Eli_{?$Zg~AuHtkZsRxuG zMgwZ@Aqoi(8el38heSM9A)>L=X|B0l!vY(GpsNw8hEs-p$q{ROi@4)+{+xeK|9srf z&*$Tx6S1bN7n-D{O>90Uf^@1E?6;jOjc?EcvG;1jLCJ z7DCcc-Az2dV47CjTvXMDg#*`zg*Muj$1hEe{mgNC7LK8>Iew?WQi;_yBMazXUc1V> zUJBL>U9vBZB>nzo(*q^L+oN>*l6^JsXed)^$1vAplqU&H7h%&xDd4zP>st7mKE0K$ z(Vs(lNB_>-NmVfZuFB}-n_&E#^MX6=DYnv4zE@j05f(+leoH;6UXM7hLs zc^;&kLZj*1FH)TW$%d6&p1Md{d}$(H>C!6Ux%af%GNLz#U9y1x%sfbE#32x6l}u!?vSG; zh?S^X=%H+eMt`CCw0}RG?I8_hEVQvUWzeq+yUmuOyX+TG#eBCVFwlWn2gV(xiY~M? z>Z}WGr3Tt4Fs91t;dIRc`jw{7Q!h7qKI!}YF6TTpS0T!H#7zz8?RX0ItZzSxbds>& z$-$QsyIu9U7Ei|>)Y?Fr&;k|Jqn~HEe5`{4mHb!!U>nK(r5}nqma4h52<1}Bc1=wX zt_`mBt{O|*m>OcHQ{=&`TJdm+DY6jh{i>u$dOnoOcXAaKVbWj}GBPFzmBF!Nh3H{~ zPwn6DM@K*QKs8X{s9;sl+DB>8H!Ap(+^=Cg(YBc(b-Cfm{h61k5f zxnGJkDqgGC8f{JpOtm%zK4TIE4-|#bUdqCx+loEJeAK*O&g*iBNyhA5V;S$X*Jhsw z?MU1+N32n{IVB~{>zelf0RR9=L_t)!%i2yhSUi+$Tn5P| z8)-3elcE09yE!$n37^z1W?b1kpd9~QwufLx5@a+xA>+c-t zZE*E5dH6lcJT+r{E&-7;#L7?~_@zbsc*56qxT~yLj{b>pIbO8ixBNZ^0?fwB(7Gl0 z(|+LDa|T#|n-$PW{oe}MbYS=fBS@+J9^xv2`~eQ$!=*_k7<1+LAV2Dat7%G$j7&Wf z;$@x{zS!_J6D@TlW@%+jvX0fx5D{*3kAl#4fvJd$mMI!YM@xJ_66WLr<<&=$dvKg0 zXQ@Rif_|$Iuo2qGR?eZQvLnF;-BbaU(v<8!+tCFY0JK0$zg=WiImvV`Q4Ke_iZULd z^Uj2ttEAGoBIsygnJS`cV)0Y}D(X1l{I(>33RN^h6}ewe<~kePme`VYypH31{rvoV z&Y$<^{om*FI*<3cmapaRZtBZcR61q@0fk%M^(!7#V<64JHDBII~byZW{l{s=jdmRs-Mc6x*ve@4Y zO>o0uh}2p@Iu8DZ{&%JHtdFj`@B68Qj_*&RCYYK+FDDN2R4R2V1qFa7p*$5L!9F`n zsJf%$>TKG+_b;zPf35LAuYKT-5#1y7yKJqcD;93cs6a>B6-Xh_7EYBq2#n5PPr<)I}NA~@iF;C#VPoag8B_Rq)tgY)$B#Q70lzFgo~mXqgT z%T9^Zq%#w6y!&I=0yWs`&4d_^?U1l$998xj?qmxUt0F~^+~Q732aKg9pXlb;W}u4E zO;x8)1+k6N9GeAZqdwDff5|`D+5)%t_cvSd9xZ?`uVz2!W z(Ue-QXC&!<5=(?LW?J0Zt`Tk2*?kbylw*FSGS5c%zYCr4S2j}r^uz`3 z8r<3-m%flbh2j&{5$Yvm@1!JZ?qtIHq@t^}pHqB73{_3TABWr-Mj857z|_DzfY{}w z`U?&5G|yIKyrzlxVsY@1m%r@ zh=B2ET1J`RA7+d?kE;dJo2&QaUokf@e?Ip0GF8#ohF}Uv*}1`CP#v`cQ5=e>3^|b| zsHK7F5H*d}SQ)I0vc>*9!TQ}|w`X_gvCLuc!~OL0_2=_C->=X6b-rKk_uF5GpX)r& z^R&~{pz1+Fayf2kbjBcSLeXh4&&DQVN-nuE)p{WTFH3Q!I}Pa-px_qWC@%pWIBdW6 zI<&^$v2VKGueJe(^v5ZqU%=)s=*ntV$u3XgYkERZ61_iozy5h1zy|gCmVNM#ucs5E z4|aXru{X&<%%Q2$e19uRzwLoMt3?Ew571U7bVp~LCUIY;BHPBujOl$VN`6d43GiV6 zw-)kDsvl|pe8sy;{4wQx-nc+@nhZT9jKg~-%9Oz(Iq03DUBsjZyA=Bb8#{0!wi6LG z?%pd#EDC~jI$Ea%t1%hPb{}!m`6keqI0R!TISY_olRBf3iMoO4SGcYN`s4lX?$@zP z4z*_)OymKYV35SirJOmU&E}^La2f)DaO1}W*YXdE+=hyxmYvCVkVcq5>Z)9tJw*Z} zbU^!B6lNk}u{lH)hMW-yQ;p??Y9ihA9~9;cys9^~Z5g{T%LtX&4q9UF2Te}R#R(%U z5&lSsy2#2xo5L2?;jfqfynf!V&(F{M{om*H^Lf8N$Ln>R{_%62=D9#bsIyQs5emq@ zO1Bb7!JYox69{4#3m_huN{#vDUHdSnIXc zTI=9WkZWWDRq#Ohv59|dR{~*aNa%3BDWU4W$sJ*xP?nf+FthJlY3cD!O)Rr$kx+@xEEb z(3e5Hki8m+K8e3@V`OlfcyLM7J|%zUstQx~y4eY+vCLYD_GXnRKc@nKs(P@4Tt`#Q z=n1l5|FLhJnS}-bVhvbPG0(Q?&gs;ZN_I#ZrQM4?(o6tx(WP+4%2NPTV=*M;igDo? zjG2JyrZ9yq91G|1^Ll;${P_8axxxN^!9VBcJpJRJ+*|_gv0-qm3Rl7nl1J6tv2GvS zQP|2WlePfLvrk%7)l({gnGw~<&%P>vZXwTmU3xgMw>$<Ouef$0D9a?p<|@RwJsF=csvx%JZ#$n2eY<1cqgC{K(pCEO~#Tc(|O z(!x@=+1hr~6PG6wfr`6neUOpTZ(R6iO#WT5T)&e2le2azkkm0Y2*+|-_bhSLr2x31 z7Z8ju(h;O8oXKNCbdIfBOk&=csfnKvwT?V;Qxl5`R&HYAxvOB7EZ1tTUfXF`G-<+( zR%s?nrPLBEr=Cw>%&sVpQkgKZmXPEe+u=~_cAWMF`9OjLMl3d26%B%`GblBL!-adG zjlBS966j!L((FT|K)DneJy)z5|I;Bj1jF2U$U^}b%+(AqhHhvRmgej6uzQhvC{qV; zc+6gDiXenawsKfX7OG<_0YD)7|V?_BxKAKIbGADrI*F1EN4;hzKt**zBRlO8aivI0YPIWlCUD5^gKOy;SnC(3)`~b(e zow=W-5AUw86lLiVvt2P2(vx!HpzC%~YWB*&C~|}i_3NW!_(Tq*HBDI1Sr!fo7@t6}A&tl;Bn65s-bn#Jzx z$ve$0aun5Eotc(lA*4zz+q!(KJl^^>w1x0@nj-8Q4wACR%V;Sjn-)>|O*na0q}68t zr%2lYz2IarMu96A2HxEUDxw4NhpQiH3{)tfc1Ccdy3Mgp$}F-8O%0ZbClu7w079e1 zVh>)@f67xX4&rL&FmuRCGtgiXupHq53(&TGP$2q!`pdhRadDK`RJkOmu#Z!`bQ_Oh zc>#i0=2LAgb2mTMI@as;IzR89pV!a(=kxRVobS*3^*KJrXPwK=)68M&!A7`_lI8TZ zna2^#fS<2JqWt9%cxUu=Kw;sCYPdE-zfUi3tvCT{Z9IDjCznrz5ULikCMTS zJ++}mh#c;N%sNS+r_HNeE+8<72&E}B*G8ur2Lq_XBG?r`@BgLD`rN%h5$9c%^RHA1 zYcBQMap3ZK6o^Ju9I>KdNfJQjLvkX;T**oY6jeC2oDY%3obggg{%?pdq?oEY5QT)g zM-g4kvh^)nwRZZ>=x(!+Y`y9J&Vr79l+BFZeSD+6Q!?xmoy5*v35H6)k5+)QhM(~8KueA!GYOy!Ny^+{UAebQsLF8nxx0zM z7R1fX>{te0ueHu&yVuEmJT}5sj2Dr|d>zX0k z{M_J{z1b7ph>zq&go(<`Vk^@T7Bf8x?h*D*CDu(Mo6pBp18F6fGWNS+#cb`_={ioD z)S$i`mrt?)jwDFGE8A^j|JP(#6-^h=6Z-#8uLrU^#8E6);cm6%tRor|5MAf)sINi zjF2g>c_$gSq})CzfLijsmVQS!jd4Ix%*tnwAY&DY=JIo<%|kh#AxbX6Qe7C1CFRcS zI*E-_$Qe5kWfO^klP$Y>c=j*LOm#qIHqkz{aoG&bCMpVE)K@ikFjKaj45tsdzq@lc z`z1y8SGyj!uL@-lO@; zzr08(h8$l`c(5q;HYgpZI*^FU?gsx_U-84Yg4H=g`)650<(JE|EC)!ZL!j>7K%7C# zNlE4phb;;HvRvqm=x=MSW36RJCjhN>@G7CI7;6tuh$Q+)@(uH{Nj5cxtORYz9cj

NB(|SsYP~IO)@`YY#f}7?I~J&k=}HaZnwjgG&|#q#9moZF zIG40#u^9_-1?v8Ux<@0LL!I!ZuG=T|PB!$@VBo8VM=us*=595zMVE!s_LwW@|ZQnHXf5G{J{Q zY8EWk)C^^i2;!6ar?7JfT*mKMW}>@5RbpP6tEw(!P(f9dm9eOr!C+W0U+eJY$KmI3 zzFt2+ug}lx^Z9v)74Z4IKI`MBuN7XjpcFCIoQ|9c1Vq;1=`X3dg9ao+QQ5?0KN4#= z__g?8RB7XV*#9cML!G}f1T~bEij2l)ISY%5s1L2mciJjs42AJ6FDPsI;_&ZhUK~v0 zaTg2V$y)jQE2Ihqmfy6AULG~n=Jw11300WANVCOozrkZIh$D&l3?en1paxUoXm!`1 z8oR!|qJKh>6#N+Z{}%0K@@M=I|IMVf`{=gexXg-|Pa(WO9h4Lwnyn+1VF3`FatHST zZ>pJq6S{=hn|xkOSMD!QXrCxVt*~;-8ntuLO;Nsz7|B4I6o@5Xs;Q&IBZo+8CJb?~ zntzPuvfvY++SnK`=F4hs7v36fmsXYJl|2a!^{7C!0QFeM(mibe_fP?;Aw2d$cA*mR z3-=Bw8T0pG$#diZEW5DR;_aUh0I zr-&>MawT=_Tpv5tqQIJqVC?!I8^nr)YRJEAKNfkrG~GiNQm2tEP&7RPL}B=M{!HCrU`i6L zE?0Noj)2|TH;h6zdmlr!ehEL=Bjvd0KtN0C-Y|UlEHX!3eWec;0LJP6{=$57RRXx2 zbG!AX(1zD{fIoG0boNy>@Yk+8ZV!RT9tX*9P&hx;ExVYVIvOueEcL7`pfvc{#K{W( zme(iff1P5TyWKc;Y2uKhENnnX5mfHwEfASATZA`+Q0hm7=^ud-hjT z36us=r6&ND^)> zVyeV`v2p>$^zr4ke61Dc$nxcm<&lcC)>;C{IueK__FC1QCeY5KE`q@E{GkhpDf z5VR??h1JwW#S~m~$)PcK+@Pj#K-Y@-z03TxwT`u3$Ln*vUdPYt`0seVj^kLzvC=Pk zo(ff0byEa&5O{%?W~XorVyyf~%qEu~E|rjHkn?%TM4r6H~OHq@W z|1~f^-TgEIIuA6#GzUFppxbIjZ|Uv{jwxv0L@Qs9?b>-l;!wsa8|)cHnE8u)*2++4 zoK}>YE!Hj4r5VY{U$#h5)$Aw@cdWD{OcffLik8G(%Im`Mz$TY=OkC(!p3N>BLQ+jg z`lxkoXte^0xzIn76@<&1E7O57VTe0i5F6bF@KX<*bP1iM8IbMor)LaCs?)OTT+(U8 z2m%?OvY?~wNhl&HQ&S1!{W@1-=UU6xVdq-wIF1E};aDaxn1#wyQC$$ITCOc2s65(2 zh=F)D=;yerqSiQ8%cL(O)WwY973Z0XsV_a;;LF$g9LLY=^?tp6UdQWoypEh1^T}$~ zD|7!hH#ay}WrTZ&&`usG(it`wvx8Irsd7DB)!gr%GS%w@-*_tpR0h7_^3>6EU8&R=O_Ey8=!CstLT$u3F8Rd*xZ(YG$kk0#Vn4fWgH0o|Z1qvc zMK|c@u(%cEUP{7+kSb^&o&_$s#{9d#sWPGEW;ZjS8#iq~gh&tDvCPAw8d%Gg>Eh%s z)6BV774oYAh8;1C`Kick!*or%)4iVzGj>r&1%E~0fC91ii^EF+P>WD82x4&qFG%80 z%t{GeS;Mz)UB89Cm>F?P*s1iaQry}d zk$*;Cu&b3`-|BpJ@Flkwqym|m%A=(O&`duaYk~g!O;sRa8f*%Hp#easO=?Y{3I$ZL zHxr;ze!|^W&>z3dSL%UdEgZgl`Ej~0gc3LogDosYx@!cc8c9LcyT_^ZSm|a+W?1Hjy_S8Jy^iA`{eKPr3v!_s$foRXziMG`_B(v1mp zZ489KPi!HI?|eI%=%vT z{8ExR%_vo37U^rD6NGDA@J{SdO>9YtCqR}|iP9}G)Icr>kx_wFiT`m0b&A` z{(oO?cKTXo%MY`2`C8}l<6Q2J<;%|FI8HbYhr6t0IK*7tL{*nbgknsAwKOMpc;jPR z=OzCYpQdzjD1oKMP~)+yKp;+T>k`haGy`iQj0OB3$FUAOGBHk1S2NX5|2uD7U`LJ z5E?U=+pzrO32cw*3^q!A>LEJ{PE3v7dQbq*F#eCP2Cd7{>z~K>1w;nYG>_-kT+>f$O@(G2aWQqWPIOU={m-4d=;=~Ix-QBB$oCS{PGeI> zb%mRqr_~DY%gjz&roPRx4g=gO@Q&eNi%F+diDk5CX zRIR*W(D2`sKmRI1w2)M0YqCA0Mjt3Sjm`O6%9m3fpa=<^r=RY*)0IcOpj>m_hf+(2 z$rj<`S~vnN(g5=Z6Z?avE;BV>v22c?ZWjA#9lqQScRv=~m#?+l-9j`Dz_9?>$_d0+ z;Sh=$-REVVgetLZOc*ZLLks7irdmpYr@6+S&;lrO76PK+%gk-1$NxB9$9jbq=zYA` z=UDbxYn@>#n5((DInJET6Y2mc_s?uL5!EtLxH@fGIJJHi$Em8B7^7OL>)P6bil+I| zISB)do*;(mX8?}UNC;yJAd(DDCD&W+z*eX=zpP0HQz7uz1x9ZwVCaRXwSc~&e`}V% zUGo2+Ky-V{PpXe+JeEMe7Eq;~1@U>;ASz=K(5b=*70DZ`*DJ#-CBudElCnj%%DJ00 z380sNV+3F<_}QHdp>8MS3^y|K4mlSosU~uf6mV&Eh(Kt?s7P;SSPpH3BM50)2>PZZ ztE~ViJqnA(TGSO9`#P!n^3#2V1~4~GFNwPVAL5C+2}Bp*Jv@S%T7gcDR55`;x!N3s zL&Jti9&1r>bqPNP>6L{3YnqiIG?d7Q6o~{`e&Yb@yK!PDi8#l>1q?j#zZ5`me&2qW zE9zkZoMpA4^pi5cpQ4b;(NuxjH5bd^xq!04;fDnekVB|*mSi7`pOBZBTg)9;4mXR; zT`Y-(<|^B$;_FDLfQrI9IVK=DdjfpF6K-B6aYr!~n42vF%zW9h^*Yvjt=IATIbQGA z@qQhj_wnCx9PjhAbE$vA?h$iEt?x-5BCR>K$aW8C6p7lBLUWjMoLXfFNNh>wIS<}q zt7H}7x8oL#%EGeD5bbw(suuh;GcG(0sq44mbt-zJ~ zJ0R+-3+MrE_Zt80buosvTRvg=REb5hS&!cf#-pR=eS;kQqs8e+td_%k#>Ej)P0YTR zmUrHuIO-r78>^zryz@WH7T~B8>W-oD1Gov=898O@MIprt`)HwOeX(A^}_^ zGZyK2E;0;8lu!DVBGwdHc`>r7{J>qq!j8}eEeMvxgkTq_GHvoSjj5gHWcHDEugp#f zS+!V#ln~+?xp`rl7mHUDI5hLkgYTge#!1Ae`wQtgwut0H#mXe&v2*j($f(3{7M>v%@e0I5ZL_U`aAp4yP0Epal|RB1mgC0hdxg26l~!D}fWKkaOHA?2UARI!D55 zt@m-fUdPYz`tLZ7(YbJ*vTxe{(n3XMKfQK(4WT5iw76x#1b9Ci^WHv+0 z&=1`vhGJW)+>=d}K@ll;1a6HD_J}_-K5;>HHkxAFnqJDvLXWx$aTD@J5THt^ zG=ZrAhS^pojeU;+pbO3C{tNn#8jYh@(8m4H?WJGNdpJh@(qH}hfc+c#Hqs@O=uftv z^3?o>@=aT^^H+3gfOIjc#_In@_&=EcV^)-_R;`qgcO<0&@`tn(f&202bbdg&b3`iq z+w~2%h@?-$9WJMz#d#$a18`Pydn*9-grHawLd4_*RJd)RBFLx!u^7!tM9RmT>GE~@ zsi(S$o~l4id0r|Wn-6F6o!sD3R9vx?x!DGGK;p2t!whh5pF`>#5p~J6@}p;M_I?3x z*T20zZ5?!SUdjXFC)-kbibYPekeUeQdE0WvW%uQx5$R+bBHa3kz+Qr^Ip5pTs}f2^ z+(gdY#a9lIQxI!000OKGVM5_cT-RZKikZS;W{7p&mdr2sbM9#B8t?qB>rzUIU0*XE)cf5ji0b4`wTq z{u7}(+dKl0XHG`ViC}rn8Gm%vM;n2h!Gol_S{49vSHiPF1rT}rP%*VrMNhTZk}#Ig zQZ;i^4R=KR=Dbu<)nzy>rhj>8%y8H*H4%rZnU}s)RVddWlY9b)mgfL14Xxp(m2Mve z;NcjOw1+})v6C*c|Dw#@(sg@Dw5LOFa^F1DQ|(fHB=f$G6iBr`gTn6KXMvY6U4){T zMvaP&6t29V#hUppqQIGY;uOtI?5#RJwTjt-nF*E~Z25|%eN+ty=b3WKLCQ^}@)*5B zGS`xeUKw-g!*NL*GD5H}m@6dKhQ=~;ra~XbS}U`F7!}CexRn_b>S5biF!b1UOx8glEGeWQ+h% zHIRF#`y`a{o`pwOMC0zN<@ZlkK-Z3zqplo01bhnuD!Ee2=3o(N3t&%Dx|~$y$I~|( zbNPo?*R1Zfd3fYI){h51ozj|=XN(hX*XX5F@{C;%vUnXJsQW49zLErD_0Cv z$?IE&0Z29^O6j>jq`_UNuv7)AuHl(>n86*V`EvLnj*O5;N}wSasF_Rnu+?D71zS6Z?%%r2;0e`QAYOWaw#JaoW{X7` zi;s?}QW@B|kBD|P4caOle`;>q!>x}1fO)Qt1~^>ZTwGN}TujtY5tS2SuoJP7`^@fg zTPJ*(xT~$@2E}ryEf`dx2h>fMWLAGV@!e|I359kvrk$i(vvFhh87cISjRI71PC)D* zkjo%Oy!Umi<5;iE2F>FZIT9bz|5y|v)@o7}xR${l%x#R=p#V}cGuMZiYfCE!Z!I$w z6ElR*$`KN*;^kGb>ZIOVLz+_*c21m_C?pqX(ef=9`X~7JL<|a83LtAW)JZvaq(EeQ zf@se`;u>Bh&5dVe0qmoK_u55xaFbcV2T{LYy8%14PuDXh`IkQM*Vnk^ys)yPSYtXA>JkwV&upLECEX}JF1pxGW9KTXJ;6epB*t(xeX{Rr6J4d;-STz~haJ*HOnu}b^DWfOc7H;sBJqbx47a)k8 z)3y3FrhbT*^wU6%ePL1xR9qyc=Bn8eiRrp#Yn`Xq5_9p$56Vnk6Nj3KH|B_FF881i z8ChE!K12(VMU_?T8h-b)cSX&4YzLDg4zqWp%Bi_}Obkvxl8D4gf|9 zRAX}bTA=_|uES-yL~l?AuQ*Fpgx(z2WMYki1s^0(p0Zj!HzKwi5q0V4XAr(^O!X}^ z$S8CSC!QQN9zHB>nJLe5tr()5Ng_fi0#rmzk0%EIh0H(0eBs~|qCcpCs`vc@mFpM( ztOBlQwA(0m`jKw9@3$bDr+vw|QqzroRQ-;@tS#f!DN>Q9HGj%Qq}s?4mj|C}PFkRp zQb_{OY!ZI)tZpY_@x710Ld}FNLMnqCZ1Y%zHUdwg^&3<@@0GI)JaO06{d8A9-Ph^o z=_}R&a!s!$a&=*eC8cA}Lor_};;|B1LJb)7jCME8k_HHdugZ)2( zY9znGgV>T8G9hj%H<*r&jV#QgXT|4i<~)-6g3fy9uW|=PL8DiER^U1P>>p#q18uK-gBCSHYrP_On2Kp2eD=-@ROE%8mgO-?C-C0bg z^9vhJ^3#f*|+NjmdrrOjc;AVE?cCCCu^rJA{ye@MiL%zrMKL+EH7P+cc` zCB{#w0BQneaXLNMB|DHyR|z_TM0$e+1sSXjq4zw-?N}v@H&M}E3RRW4ZX%T_VgiVE zh7nxd#ogRLaCHy=PH{^nA0)lu5@w4+Vm~Qy z)!4!;GKh56RcP8sVQT9^jL^P_ikgUNlp{1ix)#z$9Uf{|RAfYHUQ}oyg^1~Z1Ph8^ zT9oZ!VBe3@G9Lw@IAaN?%vLuS5rdwI_<#zq!c-6!xWPQ8Zp3;`k*y0iv*m8fM6k@U z7GN%!oSe)2SR(IA=Zf5jV*NmW@+j`PUL%|&Y^SBPcki!;I)Av=Z<5GvtV=B6jY zVo0`?b9P+>%(Ln7J#eNhG>$h@WAH6^<2Y7M#S}G(Y&P`;af1y%VBFf;uh$aDj5CDR zvy`NO29O(}$rPQ`v2V z)B)N0;n}H$fO_wjRtchvd722F2yHHAbu_`LFoFy)-_yB)WDF9J%1KaU8O1k6bBA9@ zlBx);r5n36r-|ec(0+QUa+O?FY*c&|Ar8-%wDn)0l^`3A`naEaV5*Ad62J+Ksj4ip zz*SXDOmg{Q)yRsBxviSm1`o3D9-Eg$19@##4sK#XARVfp7V9Zz+8Fv2;_ThXZC|Rc zw$$8snVJ~X#WkbvtebG;mabQkrI|mADimnEP<*w4qXd@Acc_b+LsVUj8*ox1!*eqq zdjElib*e8{Gq*FN_O_07#`<#WSUCMW);ir{eF-x)Ad+%(ul7WGae=5AEr3NSopiD7 zs`!}cO0oBrsV)gGkjBo~@%y#bYuP)4v}$x2HCxK=sH{&xTj~(8Tw6rQF>+&VTS?J! z8P&Dck^T`wW(+`Li&J$S?yea3x$b&W#Q2~yVI$3T`dr5hl-7)7nR21Ql(8^XRk=o8 z84d)Ov0S7wnUz2v$g(soI?gc)Q%VxUuB*JrQE)RmuU{Yy`u<`^eEoL3yPl?xZ*M2; z1i1m9?G&HxX#wAgyK9;4cxnJ904aOCeVk>gDJ=ji_dxdz*-my8ZG)WmWy+P`govB{ zEJ@f&3iCf%J`a>1hoG%Q7;1uRyB5C)>JWx~MoqOGNf3=i0UQ>Im6~8Iy|r(oiIiYw z-cxy6Mb&hfs>#ZAz({MTlY}D|8MA2Rlj zR(S0I*AelO_ay2)lt4ZxRx*nawiRm!;ISB*=ez||IYV914XJ7OgnHZTgj_o( z?=|GRwb5SY^n|O!1fhs5_D*yWb9Idv3V8(7A!d$c>gJ{^TtH^9Okg#5qzD}Xftjo( zt)O4eU z`2QQ07M*_J)DK542iyNO9#w7iU^i^{xZeI|By1<(;S>}*9XHstb$=|)z#@iIP%~Mg zP(q5}tL1{A)n%HSL{grL(P-@0DrskQVTk?O1Z!eJcoK@4hxnsR;c_xe^%(#rv4{d^ zQ)Vh-@?S~2wx$+?rbl_Uso;bnwm6|}^`PsO%hs~P*0h1L?q%?ShDLBJXKqWhf9RWh zr0c1XaFr>Lq9z_AEfc{Za7l~KSaPIIqMJ0t+twl!hiL?)ToCx%+KU0n&VfN8xhZ-$ zt*47O`zywOHe|&zMVZo_aIYgEL@G*ZyNkR^sZz!UQpZM!KXO7fHieNBh^-wnBoH1C zSf&No={T_509nh-Vatvv5iG6Yi2FFioN?<`xKyR-{Gd{Ua#=Vff za!}CDO`ou;gaT+rE&J;c!*8ctUs|@{IP&m*p0GV8rMw2~FJ-uVOW_!pj{WAF%B9d$ zr_E8{HDf<+wb~0c>9zKs{w-l7Bm$Sdpp{w`XJT1F6X8kdNakX2isbI20>L!fT9BZl zt+x_yBGIxuLUF!J`}d=&o-BOkw)1k z$rs}3aENAcr2#CFg(~S)5C4DmQ$#1Dz4D6;(*=BB^l&8-(ta0-5W+(iXJ>PS3WF(3 z#g>U=JjG&B3UwhdC~vK1I6G6$(zBNNNTpxn=`9VkxHzJyc4#^Fos)A2dqt-FB`Q*! z1b}dtU6n|DOnw81=qVzi4#1WyK$ff$fip}Xx5(iFAZ}PQ{#gSs>60P_PZ6nZT*o@rD>Q)UijnX+b5=vO z*_M?>D5c5~NM!!xNzETrG!{vTIf+5Db{glumN)>eMhzJ%X(AXrP{d3FI~AhY)Y-~j&ZT`^ zifoJlv|clWtziwY_uB$k3DB=)$67~B2V1sIU9mrmM&zAiRu#-eEh^Q+YUIZRWFCjT zUdPW*EI@Z0ufx`{Y(WgF=jVhVk`!o|RyE2{%`PZ04U?N%BDUHL&oN9Cmhz{`F*HjJ z-ELXdJHxuUMi^tbv{w$}OzGNmqeAJ*&W?dt6rpMVaR11t$UO-B!0G46{PK-HUbZPA zktx|NeHT*Q=YM@3HaMHGHqY7gz^9^G+ysc^0tq!!s0b324>rYD3VEn{ZP2+!Bo_Wr+WHhSfI_kvlP%av(q^w_gBz0_e zTz<-B+zO1x)}a5|**M5j(Peej(JHy1LJVth)u8zF7|{!z_!{91&zwh4A*7=vsadc$ zuijQ=4rU(OcEr&#{u=%R|05J3kfm&Mz=5@6Up6v4EdTTrOZo@k0*mBBGj~o~n{-Cl zIIV6@j8xI%tAI> zfO?en13%4bu$DH{@jE)lY7`|i$SPWfm#F3=a1sbST+221sMIBFxEevZZ`6?nG*?co zOxd)fZMFH`5mY242f(}-C3PBG>S^k&;21%9q=!uo z9fOjKbt~$|aCIAlo8HZ*m|7xI`6e~410OXqF_c>x5a4sJNx6JfwGODM>0vzqn*Kcj z6_nJXCb?=)0x}TP8C4dCs6j~=fM}pUTFz#VQSf#Jkbf*= zFo7IKLrj$$bFNjYL2Xd75+oVAy8U+LPoT-c3Q_gUL;Ru#r3h$Xf@%X5sX$+-I)IOx z<#EQ$(q;M?dRGlxeIcL78R>LOmSL)*x%A5GIF8rrIDUR!|M%z5@p>IArX4RegX@V5 zU9aVwsB;b#Kpv2O8n&JwL?byuggnCB1T=!mA<}JHQQ|mEhbGQxqueavsQqKf`wvF0 zE!{&!F*zD@zB~B(HnKvO+FzR#WY~&IV3rS+JO*C3OmTvphW{XbI_&=;ExQ* zjgewu5kOUQCJj?6Bl$jE9~SI3oT98E*i^~^d7)rs2`E)c9F1g-(lT9jW-@81ffYc* zT^=jjxNT7z<8Wn`VIE67hl5gu@G1z=zmcS=xB!6VK7+ZeX}DF&uh~&9p~F)tQ7ywN z(=U|mf_4J&QX*9k9ApPXGq{==g3*WfU(A*W#8wJmTEq#(W_)o(@ay4Ilp(PsUk;dz zWm)>wYB?Vfl}PG|jEP8>Sed%c6@hxzysc&Hqi#7D1kk0K-YcFf9x1tdnVt!ouh;8! zynbFkub)3Zub)4EUdPYtSauv{OVr%)IbC!C>h8zK53`T1Gk6t9^`%y7D5IOp3v%#B zs%U_Pi!7&|M+t53V=7(_toGig3ba%J)g*STV;z>8tJ26DdT(WCN&GMV0=S5Y#WaVJ z84`bX7f_1cPOVE=ZiqOj(}u5iKpqWseY-t+UVM2v?d)+zep6omG6w_aE~NibivR%* zQzdk2c=XBsIPpn@lREhX-KwEIPRVPo@IMrTY(rToRf)%z`R<_uU>aH9>LMX9(Xm>I z$Q6E)p#T{1<3Lmvo0dzbGB9F56G784gjm{$>3a3A^7aDWD_WO~bHo5Fos^{iTcYSP0EM_i%PmUB@08|OPq{|lYB4mx_rlp z<5n8rDCcldEAZx}0YoEnS2N$_=!~sp>FH~GwuQOI?9Q1`<0Qr`^KoMZZR8+}flz8? znH^@~-O5#~ZcB)P_8hG7e;5I@*RR@u)L(huR0DYf1sfq&O zb%o|iUNL)5bIE;GNsoc$W_#rpW%mcUKB)l8N{cF>3LGoG7i+F`6gOfpV5I7=eW2cpZfPKR^HXfB#1{@Or&|j^l_b zM#9iKq08ZZzRvfj{3|&|H%6>23ew?#ZH%*7N^&M8Z6(4%AtTZvq0igLHsZR~r+Hzr zMq?o*;T)o|EN~)!ZcQ;3|MiLkt17NpK-2pDJ7n^Tr-Q#|9_1mOF8%YAm%M zg^Pqu&o^|141KXL@nMxN2tYXrO#L z{d4|c1akFQdY-YEb4F*XW6I}Kf_c1Nub-bke}4Y_{Q2|e&!7MM^ZN6DKR>UZ*XuZb zB7hY#nJ9k3fBbWLY}i!wOo8cos4$bG?Q7z<<_aq|NMH=E8cE%Q`L5h)@vV*TA~3>E zj@fEKL7-N*?FO#^Lt=ig{oF9zdk2u*1EdDP-_`<}=I{=91|{`x_td8|?rNjR z;BP7=5qA=E`H~ z<1FG&Djz&DS9*zc&md{h2iQmYOe~MtvmI_;t)fvQOt33}4cO@fDu*)@O^lubqD9af z?a{#M3h}Pnx)%SD(HnhAws$(P(s}@AX+k=!JO?UynN$PMZYjpp&uQ_6#!@@v{+IyJ zaG6RZyg_9DBC#Tl=S(4w&9L0`bTzoUh8<3dqErCEMY{UL9d=ot-_R?>Wcq6~kgMVW z?XOGvZ#Gf`8Q!jpbV{U%+OiD%#tkH0|HExvrD#>!s!~`Zux9L}oCJ`o4VDftqMtDg z&=Z4WBMn>UTE}v@o5#Kawe+)^>Eg?DWytsE=jZkFe}8^{e*XO5pFgiZKd+zH&vC4E zM53se1r;)fnw*}y5tO$q>SRzKEfm>(6eqfXDEU)I7yIByiqTZdzblKM=UAN{qb}{D zU1NxHpo<99V^u&E5#(+l8FA(w;kmP4{YKiL(f9`3TtImIa0A~RTd%Nkh2IO#_IKli zxUlH(^~~R15K&C6QiQ**zL+(SMh2n%(Wt3APVg#P&|Hz7D6Q@}pj$NJOsW1a#gNOD z(fJcav7D1d3olk61JTGdqsg>v+9>;`RD@y?$OtzI(maVaJj19=kEb1{6T8Ld{yI z80V6Hy$D-6L`5YmJt>03El#b6cFSV6AIHMf{sawvRZ4?m7$WMVIqGN3dGH#nG*&%n z-?pif`+1(N03u-|%7a@B;0yTOVEA`Xe)#tH%h7fHJKhVCaAx{w9~?K81oUYiWa>rP zb$T(hzczyO*{sEYZz-TrzE=WMb?6xReSW+jAecAiL!{R?$fGV06-OkXXjTf^7i5mI z0LWP@viP3n18a~ZFwWBzxY@%ml zzn)m2hV40oRD@Yg(?J`e7jgRNMa~2URKiLYt(9@h@6FgR-9cdtz}+pDDU`?tR?Y2& zSL%SZUdQY8I(}XO{y#sj*H6s-U2DBEQXsK|LuN=SNadpeazza@J3Y3_T$%}&mVw(O z-mL&E7+Y!x5utJ*|5p<-`&+wl==bmd!OM+#0K^)PyLk(8iXu6o14zyvdxm@M@)z5X zrkv?R41k=cWv_Wx75Dnu`uiVVJ0qAfsbxBhFCK6`wC_AT1BP1X)#z<40473;NO=?P zWyGwIUfzZ7$Tn$|uW>sNU-Ja5gM~!Y`XmB`D~*LtxUF8=(c}RTL2iGZFq8h=PHZQv zu?n%&n@B}WQvq}i>;QZ~gTLtC$p#Z|7y~FRkP5)+n>EfU=g)wNfDxrgQMgq^V6j}H zLc~o)#L^*U)qA&Y;6P!q6HdCD(nELE87jNfY~5N~v7!LpD8?SAffBxK6G3cNXc++( zxT)rD@0`x3!TclKUAma2N1oLAuf!n{WLBTlC{Inx4D#atGY&jshn<$CR=YWJj3Wm_ zD`-{e3AGmzFjY^Btl-wl*_6?!sv>FhL{th225}i!iTLzfQg=XqRL&2x)6C5lWNcWY zTI&eqVxq6(6`B7r>-TlMUdQWay_#l~ki-6dYB0Q@}NJ@<^Q zFfdnmj1Kp|#{gu4%Kw1!^}3>KFfQ2#VvWH4!McC!3-p(>^xL)n!|SS;E;nNG@%Tmy zk%rPN3d+8!)24zL2cvmZMQV>F5w)aIBC%ykzW2m&;>X~X$@F6#1#1NWG`V01P?T2E zrUq)HcW%Pa0(~HWrm(?kNEA#+*H*iEsxiX7#Z~75`ZK7ZY4|1rvLSUg@!rs5wbk33 zF3(m9;Y_hz=MR-ibZFHOmbU=m4iUg=HDJIQ1&*!Jvddf{F&zg?`=@&h<&=*gMUbWBt62pV#r{=k@b* z{JdT-YJpfvA?g(v1W&H+8oC}Tq9)69nI2}y9ENaw^)!nLt5EFo52|H`xn8(kP5=(< zV7eP8qj{L8k+;=oO&g1Co>{OVmViqJec^2ZAU6?&c&?Y_$OH$yxPWkb-VV>2RO&)r70dHT5$3=NT^j2(AY+bY=O}q`Dbt`?P$%?Dr5Y9q&2Fv+C`=-1 z)8X6!ULBm0N}%3v;~^jg0?lnrpG8P>{ZfUfTdvd7Sa_3`8wnVSphd70PPOuO3zd=E zTiD;KG>Vh!0(H)Xslrub1!}Q$X2iHuYUBj{Nc=(WS%AR)6;Zm_$1J-{Irb%bN7?vV zk&<$|(3Mls7voGk?fH1c{yQSyVn?YZ?DP>zWEvZJgbG1(hG_|Jj|VL(tKFDMu1on= z-UQmHS2{O}Gg#qly5u_^u$K903*r$8fJn}Ph;8N9aU8GL>vbgDAIIx><=^-pA@h)b z7g5dKiEze}d=Oz?SELh(#P+2*n|f8MZ3o0c6Gxk1$dDA!Pl2ojx}xC{+ll*g&7RoB zElTBZshwPErcWsi-Vd4bpV|Hl1Op<@)KHN$LnZacuNHt7@?VGC^XXT_-c9(27p{Qt zaPGIuS-9Ih6Ab@S+P$!a=~Kb(_v45Bvz;`l(0+L6hW#6=2q7&i4HDCid*J8O<|dTY z|2h1G&|RlVOyi{~N(B(>|JScn+M5|)cq3E*<>4>F-|3!2Bc>*6Iy;Sf2$FL? zhh-y-dy7b}e8h!{b<4()L75cg$?R^bTyv_qT&-=+<(e9(_uJYuKsH~EGb;E@LsE@t z0HTO>4|4M{jqt6&;o&68C5|{tE!KvNnPBPJ?RsUG#GfGw6alH$?NprcS~DJOmg_(e z`=jrKTz_Buj4p!(X``O$7`+QI9_WV77Nj?5Ys5Z5^15^*K z`Hq@<9obL-Y%5M2wqckLG#Yf8#4XslgKFTUuZDj6dJp>dHHds04b}GJH9p0+&Zoz7 zypX080A`1vP-edVhUZUkoM_e;=I7BWTMISeYsO6osQV9*>jB+pkfUi=Ep?#cE2B>a z#$U0W&_cpowJ7);rFdu?Jl%3A1$s2fXDE&Hb?34JM6ISD*Av-197e)1){~p*{OJNx zB?v)vl)lkIw%i9sc#T2e5hK0H`WN|KwZ;iG>83rqiI|JB-{b@wnZQ@*4W9iL{UArkx1ABJ%8qh_U>YcHJ&5s}N63wK>eb!+bbL~b z{o5l!#YkSD`z?H&Ws((8_v6o=U&vlTGzJN{H&&9FQG*7Ua z_n6lzhfr~_dTt8Z7^XENR?0KM&uq7e1=VYu~=NP4cp#XNdnnWi3 zPAN+CLJ_{D^n++O8K-nh`J<~XL$+BVaK0Obu9CNgkz=Y$bAzy>7sB+=1m|gMiJxj| zH%3jhyhg{eT>N>h*O41h)Eb&GZ^H|NPXQuYdd4d`H#{Qxsos0!PN^vcN_sm8DkpIa z&_zl!r{l`V(ad1&dAgtHRR@G>U=qL6L4>OS&G13v{a*4pYc@;u>;lp{Upknv4ch0| z(@=bFxG^1dKfQ>$l=ZNc`5C+)?^FVL_3G?T`gZ-D)NHRCuJ@;#n!7+Y-zcdO`2#qx z24^n&X`nwzVFe6%Gk%Og8~taTj8k})mEIL9->{&WjyRY-5vXlnsc%Rg2Y+n0Pg4L$ zqNozuIl*i0EIa1LO14VbU$e7NM8AqZ@ny71YgDCz*}E|wigBOri2U9-7$6aX9NV|? z8p2?aye4MhkP9D>yQgkQjn+N^Y1dO}fZ3pJj5W(|$X^@%pA)!Rs}1WOA`0cAWmA$$ zK9xc+nrlOW5~&d23-k!pK+Qeh0Xv5YBFg`^@P5^#73jNFTQVH`SBI*JIGx6an26!&+6v9RJHM)_cJY)my zeChoB!2k0M6C|+z{AAYtiBTc|oXP6~cgbmO^%Xo%km_bluPL+Rq?9=m{8lj zjP2VmuV)nfrx%zIG43VO$k$(OKB9lTdEVsrGj0x}zUZ48xY;v$xyL~Nc}$B%k(X&^ zuW0Ocqz(G2&CbAi_pJstb3I@}=mM&ds_FOd4DHDiQapI1%dMlV1^sH{_xL8}zVNtI z6{7~F)>5CrBKOB84#OmUsHQkY%cUg*RrbVV>+-D)HYB}8m`v>L^J6}s!SjYTc|A0dso#F46*PSP`t zQnT1UfD^iM{rs_1N$PObTsx88l{2B(&(lBWc{<3-ApuQkF<>o?X$WB-=S*D6f2q~X zerk;_;QDNlqH`Ad`+0u+JU^+1GHnx~004Ivx~7=l({fUVJw-QnpR6zD4B6&M7=Mo9 zFypL^5$pAiup5 zXYY7F_;~-!#$cTrtP@G|`P!_R&`D-ogQSdr8a%;limyw#l2kIIv@y8JUlje zJsEGffF?@jt}tVz$r6$t4pp^BPL){CoqC1>Od;ed>7{5Za*~3^*t?DsLf|u79%?F{ zGqQwQZPNffxy{ZIsIY007{W;(N zeLkO0=1|7OfTe1-6mDxRS2Z=)(#u9f5O$|xbbG@$6~KqSpQ8T`^qj5X?Fn0lt{;k4 zV`>Q9oxoL}awxVXJ^>krcHeE+KfQFrjsNL22H@uA8IKE}&4rsavG_*)n`Yc5i~d#? zKH;KmZs&+>t^N4g8az$jRs{36=HMb{AKZ$^5;bGGTES?XrC}@VK%4x7nfKavgNTTg zk9bvTq<}sph&s`JyP-WYrWPO*JdAG(s!-(s;YfM zSMH6iy@yALBL^yZObI92Z>aiSdt{br`2ps#pr}zJrn<$!@EfGogW6Yr!0R)bq z`=8T40srsM`~CU6-|x>EAy*MqGgqS;I?pmZv?&}X;xp;X>GJVX06;o$PEY6$%&!nH z=W01mCX)!CPbq*B*9O!PfuJa-yiqCyqXR-HsW@QV&3;lOJ1H{Xi~ok84c?#`0b$}E|yu>=wFy7eTg(CBuR`V zzeYkwyHZ&E9(hHNw->vqC+rp1(aA+clna+q9Sx0@Lx1+}GzTE4Y|a@pN(jn1FI$uUhhH0+UYG#U4PzggCbH=y@*vd%($6?1(PKU_BV4|)n5#tRvP|TV7 zd_M2<9k2i1pU>y4kZZ4x_ld-}Nwh))C|#ZP8+&xX0Xd7kJ6xFSnXEt7@9~lbC-`#& zP$om;4!G_PRCXY(MpQdr?B1?pB!@t~p2+{W4Q@y4GXn4-z zt9GP~tG9Fd0T(i)a(Vi_MF);=axOA{NdZa^B3@XNqIiP_|EB-6_efc@=+V2 z*i(CMIM+m!&+Wr`R$WSYy!WK$1m=Epw7t|q@hIwBfi&qp5^1yH*3`v4ON}cnL%k#Z zgV1v@J$^M@J_V?4!Ihen3=XIGv;r8O+n5?RckMurcb+|MQ=(1g*zPufT?4<`lLcB* zs)kYdcH!KWnY01Je!2`WW(Y7=pR(4DviVj!&-A(B6GPiD7EKqURcK>zw*WI=rl(sl z$DAn^DL^qJY^_(!`&u13$i@(XPipM#13E(md_M2bd7kGdg0i8d#oR>I&+28BF>+`p zH&7#{`-z~TT`~qBQj7CMR_&)qEx5xcVt`ktGHTptXfFjBdkL&Zt z`05`eqn8#WW&E$lmL$yv4F?(ef;6PvHNxP5D&dcI7DYn})XFcQ-K=vS+LZG8M@Rka z#trc`0i^QGRnjFs+NanreIdmB@xF~-P3l=W1H3gjS_(lg*lPf~ z4VJ2L=2&Karu%1QYjyD{=zw@6W1i>dJj3r39-#l;pZA$T;2`pDfSfsWHjI^_sEekg zJxa0kRsbpW+?E9t_H2$zLcmmF+R*+lHm zIk{-V>Qo9C?Wo!&fpCD(^Iq1T7rjR6P>sq+Lg8K9r+k>&SxL%`HmYmstH5 zLQR_rR;qF64%_k;${b5+=!;lGq&hW$&& zNlEeS@p2SR($MOEr=`^|Q)Ixv?>L=XBCzqht+~btTaFc18;Z|VEjMDw?N?%jqLs^n z+7V8m*!8_;Y#H^NN^M94_?g82zyIFv|Ni^@_x|tw{_peoct(H&xCP3v!RD`yULCY) z6yI5m5f6wXyeGUHzXv@n;n|pIHdq3`3H=n~jZr>)f4O#30Kj(J|EE{#d%Zn%1Nkps z_6HI+>C50 zIr)X&I`u;P<2$8yrtH+x8HFWd&}yBKm-FvPJQ{XE;9&a%_BJ5SNgJhF8g#T>C8nm! zr$l9NeiES(11N?e607*4`i%5{ykU1?BoUBq^&)S|>qj4GIRmTAp_T{BBFSnbY~9e- zEHJ?j)uIx+1f*bS*)9A>q#B)~{h|Yix?rx%T1o#>=r3Jd^m~ed*d+k_Q36{H(IwG74Gy zb_X7|be@hK46P8P{1rp_Oa;=#Vfa_?#n7$cv^?CG#@*~V-CH(vLrP)Bd+EE$={LhM zo`OpB=H(eTB~A>3ng>$Cy6tDrjU)4OP-9KiA;kU>e;#nyEg4lcnazk%O_grMCU`NJ zfyx>$M^lcT8tH@nw<6dUWF`DJR93Mz{;xV@Yx=78PA0vI_e0vW<-5cK9a7G$PUiin z6hc0M4D~&GIA>qAMojh;#n7#Kf&Wt}FT+E+a=r9=@+>WeKXR`{iRMtX zb&eKCq2GTx+O}uUpHZTwH`{&hs1D*ceKwMU02OBO=)~mm-LyN%audp4vVp3C{)uh) zmOW1kU?0`IYHwco{t+Uzvtt4uhHqh#rtuq5yu-Q%C_e<8yqDc6e4Br(^#&1Z`M>CN>xp~ zV;>Jgpg=#Ne~Riw?`_5T{!iHLG11+A$RedSHT@;7Mt(J9_)zvkj* zt_;6rziXGIe$y7RQUMhf5srmm0t%SmbTI$(eg5}8|9gM_d!O$&-XEN&z{SZ>9Pm?e zFTJittIlFNk2P)umLJB3L%Ytcdtkp>gnd*nwJBBQL%Mi7kWMetp7EzLmV?%(!tcHI z*Vk|7T+w~Q>Y1idl6b8~fBK@5uD+rgvES;}Cty1;4<$NZg9b#OF3y+V=muoR6cJC@ zD%5RN!$OP`RL39PVIkiX1++e+ehbwFM^5S*wXGgCxMk>rM1)G;+)$MJJ$E zbE**dQ$@t@UI>=~VX)2mYG#lH@aI>WY4?pToDo)+j)#v?R4z7tsvGn{c7zmL(K2j1 z7)Zn?ly2Ua8t06#D5EWi<|gkS;yyZ|x>-hVOX2=50o2@iRShOErZ56FY{60%gB&-! zHu0)E=QZFVGTN-y=&;K3tDV7=@`B-1)e1FdO`Tj4s|9O7Ao(~b_J}T2Qmmu0$08lbEfIu=|fZ5J1WK#|M<%A#pce3-27ZO+`st$ z0YW9N1$ed&w#xoizU{H8Cnp`d>E%}4zqLYK*4!rRl!9Gn%zSJuuj$AoQ5{dy29qpl zb9JUotL@c{ge(BIO-S1=tj!4Wv{BnuF9CQkwEHJ7`m*a*+?xpgeDK)mGl9 zn+?#tpdmU4{1qgL-V;i*0Jq6!zr3o{6SfEBCz#2m6P~qO4~<_nQL-}1e~r|KqJ1m! zY`RSciwNVj*TZ8B^(ef&Q4T2Gj|O+7Oz;89=P5~J5&4FBDVEqw(E2xz#yYhPUrYlX zQ|NZIw|4iuNRyq0tJK(;1>0My&VncZi# zUw&ggsJaWrC2^<+JdYKdAHBF)+1k2+J;+nC<=c_bd|hW1tNTW!y92xD`M@zUTJ*a} zDjky^Sdltsx)!?kNY$#zA>;`&V~WqIxJ~_%3z4ro562HvBWnr#Rsrx*R@yBQHP5-h zEAoG0CNTF!>tO;a%91p9KhO8){eFKwpU?Z_XYQZH#rZ`fC-Hh|%J%xSynwAZot3ls zMJm4zr6mULkKCucWuSV^wYZ=rt#IN(O~JT~<4v?lf+jstkyZd(>f44`J7jM9L;Hpo zsGb^YK5o}!4{%(HMz@b0C(dBgW1`Xa@ov&wpQe{nwqa7hQT-cVXwx+%l<(th)_jfy z691w;sTFU6&%9#vIZ5eI4)rWS&8{we7~MZ+MD(pX zidv==*yhzOB7?SXeU^`D1EYnk;GbPvz}C*6QHlY(8zRgGZ1oZA^O3n;T6MH;9ZFtT z)@?rxE%Uh>)p+^%jS2mZUMH_tz^Q^$1LT4}#@P<0#t8tgV;#9>gsQ3`LI98x64{p; z>+76;e$MmrIo}`uoc=!jgha@pvh*piDgp+4rbKKiuXJe@wzz)?Lv;XUxbL8|XE5%3 zg|T|#K0>9B=`6#%!FJpHMsZXXw(qVlTCNujFrLn@AE5vHYvA9U6!37yleT!&AkW8t zt=#P_I;b0Kw!4?tB;miHwRFP0L=FA-p40JLh`%Z8jroH^6#AF`p*@gADyPqM6!4^S z8cY_;C%4{-Bki)SGH+Qd0sG*eeouHO;1|1x#6Tq8b3QBwTkDMO7LI=QNH?&~J}Q}y z%cly_?S#nn+~x@I*S@`c-&PXJA^L-TQLeqR@h+;s%RSg$Acxl$Ss;B< z&Wof72m0p}u-E{A8{}xW=E%x{#CD12d7eT4-{&XS-Y?B}6T?|kdL^eK=(ZeR<-u-r zQmIUkuCd11hN#9yXYW+GsrZXslx{~I&Rfg&DnV>@#q$1jQ}{2hDeCz|hC=>&;ntQ< zAP$j>-Sd3?cOCG(xD)0H9=M#PmrHefkamY(6hwgtKPD8S59WAC?whA0_5MXg_lRxK zi(XW{RU28xD&bH#mZ|yOG7hwlPHL7_gW@Bq|Wm@WOeDC*%aHNi&xxR*|uy=MOFX+!M+pE*x z4*U(IR;i=otAX6Naq?6+X-jT1A4(_O6^l$EyWbp=ZMubYhIh{emDmG8&WUO@FkYNH z3$iCv$&|}5o8}UD$6>YmOYMPK)sUX^k!~G#Ki^#b^K+h`6Mm++) zdSDcPfq4D(I7ZWapR8bmKHh9RMjIi9 z`Jr7p9v@c0ApZ3<6(J?wOGwI~r3&aMDdkY`-;l1&{&QR)VuUsDi!)qMLgL%>Q`3qr zprt8O%I$)k_gBTTi&a3nH@9(0JsdaW*tv|q3I2c-(149U%qOPV@WSl9Mru{n>h{gO zHKnL3N2H!Q;wlmIAuZ4oED8>c+OC3d@{^oA|;sGL39lXg3%F|Ph zpbP_`Rz)u3u)Cz7OjQ~Ftpw1{kK&}|6e^|Cah&J*@$>WX_u2a|Qh2GtV%zvx-KZ3! z(Lj${8V@>!NH#nkf$+;KHAhcaA6lg$MoUrD@VhF9DZ^6$=4q4D$uZvl{nh8p`6T_I ziwt0Vr~S>w?sufWemh%dNGpwveGuO+;rkn~5BK}*R#sx3%vFiUeo1|wyGq+Zb#IEo zxT!ViIIXEhW%}uUzcG{_ABtS)QDk6BluuhVNP)uuk!TFM@i(B@a`X3+cVp279O$ln z+?)M*xKvRmOWOzD?wZ(5o8DQvlXo9-By-JDWlFBHvK$-ocwz%+*)+kU?4MF;s zB-}rr&l!t;o}crRTY|woo1~bFR{h49W#?(gNn2vOwm`G;rWF@Efyc33CMAGX13?9a z2J}9=UF!CiFL(U|k>4$V8}R=1^-Yg_y#c;_eNhD8%5cTVuTQ*vEYK@mHuQ8)Xs-)& zyLb+T2G{bd7aCmP8)Jx+Gf2CB?M~jHk5ADqSyigF(tM+YeJQWc^ae@Ynsl$Hwd9Hd=#i0-t=%}<`TgCqiI#e~1-3sj{kA1Bvt;PE zrMhvSy)=3-&pqVfE_=T}zV_RWA@uci)kWXX{RZ~9yt@w4zddT+H*d~9xx{k%DXIT= z+@CNzT+u@8XXk78Q3_4@wTJh!(Y0{3z15={y46Q>+ACE~9-EJ^W916cQN#@s8@#d_ z6LE$>JS*UCF-!_yl}%93WpHy)i&Sd02Oco#TWCe6nMeuAjhyd7-k%?gy{Xs+^20o2LG=`ZNF<1>!8)1h*SiQOWnq zvn8badzA<96Nfv4VjIMBTJxxzM%}7$RHV)C2L(OAv06-Pj$WUAbLTvddL0$Yt7c$f zJbjJ~cIur#>8{+K#+%Ci)vglm7j2g}IEzre%ZV}rJOj;r{6}q?mArpug3^u1rzupq zBV{azw2a%w=hB~a(Qph-0~E|HIzMM7f;#-H#7`l4d8%tF(7j*3vQkd#XDbO+!qc6* z*J@0K${033s-Np4<~LiRTGf05#1Q|Z>+tLA3A1>ss^Hg4^;VMc@iGFgMMeilpV0q* zeQhP3(6(s?xuXVu2KsOi=Fh-two0o2G#u_;P!ZXv|C5TpRXc%=xP@C^yZgy6kC)ZK|Ofuv!aMu~xQaHx4lxF%4Gtg4bFn-Ly>=g1vL; z!pqs?=uQ5Z^w~$MGBjorxTah;nD-Wk4ev9;3vPE_lK@8EXX%n?u@narsqFyPvbAg- zu{5ZtMZXTWIz^9b3>Ef&KAz5h#NG^WhL-e4`~$d0&x%~4ejBoJ%DAaPY{sCxt-wjs1f06=sSnw`^%tIk+Nwn^}90EahL7` z9RQ#+1l7DL?he#!Pt{UrGI-3T-712HTF9$Cy0zG6hx-ILW5d(ISP)C3jgqhii~>w| zzl=}SL8GA>>Qz#|jbLdH!`xe2t4ZfLA5q)Atp~z(8_yFrtx+jTjF+o?E^Zy1i@#MkNW^k=3i@8DTpZ_lzRyqOf1bH7Zw|(4T_n)@9lLg%8l5~p zQVn!-r<|xvKSa0Z=soS)4r$`4dVTQL;fy|Q1)C>s<(6py`~~y3*G0mIe*Wc(F_Qm& z`~My7<#N&1No?Y$^38!WN+9fl5`Hx7)4#q61GPc_X3GrG9t#{|Rut9!t&p{EOQdG5p}NB*HpLtJYm8Bz@c|KFUr!5T zpb)wFhuogw-rk;D7@Cp7&zU@w<=>r-+GDhxB{EQO(5%E+bi}X7Ua6evyu09O!88w! z;?O_WR!sDF*Sc>q6+zz*+k-|g#t{l)<4~7!_?50BsK{(ngK={$h%U4jX$YUG{Q>;7 z$j{jPTy~#NsgIPc5UzSK@xAxNTr0MP%oLzf)girYeAa%vu?x(HOTcjo7|K`ca88J> zM_=|M64}~twA>X<2k`0sP02`f_8yTeQJPDbZ16vxUvk>fC#_2yAD{|Z&-4m8yoWCJkqAE%#S@W zHNVxbH6OAzAs);G)xy6E9$f0I(z(am4ePEmM#A@{iS|*TaBP53zo__nQrMgpmP6bH z+=Wn}VuDQgE}n@T`a!H>LCEf!>KDn%t9F027AT;WcCdC0M0)>;WVN%oAV}|7X~o`> zuf~ctD5sdv^R@-$HdR!xHj$kqLmQ%pf2+g~7nL@8x$B3z!A9hFjiuY=v%#umbsHl) z*bN;}I)UV;J_;v-rzg(DUy%018TZIVtIFA!bJvt2^uOL?CaenMKX_qqX429y^zSS z1+>G-5+K?>qVez2ZHX3vrrRi$T_RIvX6k;Uzm5xPRI;U%y)k9C=zkO|lFI22BFYc1 z#<;3<^^Jmzf%_!)Y{;Plo`+`9$MNkRs!4-yM7T-|NP79(osTW-3sl}eIl-jie140n zb~{4E)J%f$=fZD$J+##fhhJI(?tXrl1N#1aKIg~%bb-U8(*OPV!DDJ{^E$b=qJv;3MHRVk+i%woi`(f+Z`-sKiChQj>=cR$B?cdr#9 z;>D_fOQ5#Kg^0RHhP8$yQ@t1JYn6`y0lI^fYNEQ3u)8sjIJXJ{5`-4PdL}zwb`foB z3gfKy8%lxtj6!I^R~D27*V<*6YN4M`o2K>H{ymGW(t`!NBoSf14DW*eVkCWWT4znL zQP3#`peS3Asr)j{fxd+I*_?<#?53#Q$*i-fiY)E|zsy!G{=@z7K+w)xfsc-DU}6=Z znErKU3_>7-cW1pD{gnd3e#HURU+=iMHCx`hq1 zmrG1lMKG%2b)3{wL$f{SmDDA@?-m=k{8V)2P*tRpX8gmt7Nq(&1vv5=oHX=2|rU&gn6W*dqEP9QI764IhVTvbu}eav)8 zg@H&KOJ4T--~`Ut3;xXczbE&9>1s`&C$;I_}w_JCQ%H^?J4_wmK z=jEkgE+imNMxRmmFpHx9qyYYc{{N;Q9x{}Fd*DCA``^#K^0N86MCI{**a5!|PBsS7 z(ZPk1n4san~Qh=KzrwObyb+!U*7(zVEuh3*ZeI`jv+Ly!Zgr+0Z2SWgd z`&0@#dHQ5D)D7r^HIRnpt8BnB0tP><%arI3(TD*s5d$Xbp$kg6$>^LS{iO*?XZKiD zB}6u?PN1jTrZ$adH+MtDWg}R3N6x%$Hdj5npjf-$?86Las7M8Inh5YIRhYaErKEY| zl7hA;s#JAN-F19BZ6oP!*!0HyUcwkF#l1^=^7=7>xB8rXXlMa16G3Cr~)iZ7|v2jzCYx_SeKqlCH_W zrq1Ume1i8uNA`#R_R!%Df{Nn!NBAC3*bAW71CMw68=P0cC)K=v``6d6wP(JgK74mS zxkvF<#SZK>>XhW&ia^^|a=UX9<4TUURsh7%%GRIuXY=n{C36Hu#wH}47^2`H0FV^<~HM0hm8GJ zRJAi?@>9OVjQFqZXCeG|-&PP6s2osD?VlZ>bB4CaWsYpsAOi&5aNurc+qw+|*o*cS zk+xQ-%Ce{>T6wW1I1P2sDk;Ax$K3D);L_15mE6)XH?}V7G5spQDj+YAopv8t4kG~4 zK#wpB;pHGI+|SF$!KM_&z%{$sjEny)vDG>sY~$U^h;YQ2>p+|pjY~6Mv+K&1VFR;8 zsDV&`ef1$Wu6Cc++2b6{11O2vPS{$UoqO-(I%jt1b}#C5+Hm_>y9?;6D){mRvT%A) zYCB3@&FI@5ZqNFD!jmTWH`S1ku9wnnZrhga(V{gN$Abv@16%5SoiHEWK-n|k;SM*r zh*1H!$O3@?)B&_WZl>fz4Fl~%id%nF!Zyu-N-W4_?gj&?p2Xg%yhQ;Hhh~pmYBFJ- zH}T&AKPv&8qg;fmucX2O0WbwUe^AG=6^Me6NEfOuAsXgSyHlp4Wjf#U2+Bj-YOXYI zyKh<`dNlIVp$hnYZk?95V^CW~D_xg`p+YJPriF_L(LBm9ihLWpub;G;4|bY>^F_y8$Up+mK@wN(0wW>|SBb+m`FQ zv5*@}_~y zslBpwZG#OBfH4%yEzc)3G#2~Mm8XI!sk*9$*y%O~fHc2BvA8&vDSY{-Ojv}^~kwtT`vA{r$>O@`UvsB#HS7SOZ?aJG4~d3g~Hq_eBK%dT#o za$%H>!kQK$Z&Gj7Y~7=tmHDdLN&J97L%L{WXGqoJye zN9PUcU<42?tpEyCH_7;3oc z&bNb9Y`V4?Q)&i?T3BYK%gPxyY)D}>M5?gVUt8@lG((nOiGmko-^T?Tnpd@=4>d9& zZ`eH*`G5#&G9VQ~YlJKUWDI;6+@C)ctkN2oIZW8DbP=^a9kh%7|FQiq*_c*kWZX+! z#33jA<9@==a7$$`Xgz^x<2PKmjqw^BxA8xPqDE{#a@+lY_UF7KrgM1Uhb|KtW=Wf70T)`k|RQA|VvwaGU&~Be}CWH4?Mm6OyRiIrxgP zd2JQ20S2W|E+x;#A3!(VL+FtXfphC@7jc)<5j(4%K0X2t@w7@t>F6Gyw$ZJCm2T8# zOvxy;Bxg8l3p#CTg8FSl(ylrCnk|~al9D4T&;ez*3JY6#w%4W4vUX5W7ucAdg|c2o zOK&fwxJ{w%`98a592Zn?jZQ{?-!QQQ#AURAKFCXjQ&@_rUu@j%K#_8p8lY@Hs!hUw z3x|h6$|kWg;A#z^*!^*tTCxzfD(}{V&N&0@^z)3||5?zMf1%H+HOOeZF}b$h40c-I z4w2X-X8yi{b)USy2nd~KTJST^q1+CQ)8Q={U(U;pOF_Q9h6yo?{8h93{dGV2>GG*S z`?HKje!66q6mr+l`}y~{b6-L2I!`F(&ogwt!3>LQXSmGXlyc2p4g7RD0NaB(<8| zc}TW0nvwK5b96Vr)j%9WoU1e-7VBu*K1|5w_?KvfiuTMm?O%Oj-U6gPJTyFltud-( zj^_7zh zdENiE&nPv9ZlC)74d$!6b(}M?Lb5c3HFP=pkL-9j@O*#Ch50x85wh&7gQxqIEUCMB z#9x$NFcS%lzAS$%4hl3ZaRzcUgbhheQZxho{d1mYZ9&$mUpQRZyU{v{_|ZgHH^tW3 z(>|!~+-&J<_wXaO?|#XdAKh}lSqlKM86rK4ItR|2Q}ov?psxiP*FAa6Zsj9X{_Spm zdF|4f;J1$_f@VD$^1nS_AXR=XAYKh17tN1eE)5c(NhZ+b*005gKZ!10u ztimmr@~X|JJSVeaAKGEhn$HCMtgu!zIFP&i7wq6_ z$9_w26ROv~gopq`(Sn}3lYQB8rr8z^|8YRsNF|5kicRU{`5?LrKk=eL6SZdc?L%2x zqW6~)CaY0EjfjDp?A^ljb^^uds9eq4L6#=|o2s`Z7yf^)1gG2`luAq_gGw>n((jY5 zpHI^NPsk;oE2-j2^gw&Fdk?9O${a8MN&ro}@ixr4y>>}@I1iT_ZM`~W@`4G>?1Rn= z8YCM!OChO<{1OD7Li~HK&A9MUiChZ05tseOo0j-R368G@&~KySQj$sfcy^EYOA#@S zjSIEybP@7SX`B2(Hz%4)5hD-(PsITs;;yFZE@pr`%pw^+)P{<mGh#vK2FP_C2CV;tcSY3(u$Ag_Kx z!e2X0GM;(qsa*i}a7YyLg6q{k3H(CSfdAU7l6R^8ha)MDhwWiQCmNb}!B}Z8;J<_dGSk!HYV8N9-oX4wxo%K3A9n`^v>eL=R z7+d=lxo?r+a+>uuDZkA3-Fo}`Zq!zxqfY@CM_K5DPyw{+!d!1ck&o?_N0DC1kDs~z z|5&B8M2qFrwX6~+PnhtzH1c#%`Tw=N?M$|4N;E-7Sq(&?N z5sT_13xopJYlRjgNO{r1d<^!Anpc*$9@CiTCVuX*s(ScX&(E2o%-v5wGV4Ef4J=Rv zG687t_9~iIvkoY)bK^hR-ynwL{pD3~vAckTC1N+L_&_60#~8FN>yT}tO1Mb7tA}Y; zI+U|VcsUfjC}OrM&9}aQZF>(VSl)g@)xZr;S!md6da}+TYyCpSN33J{;XxO0a!-1l)ff`XvF{;{?FY1 z@%%*mms(zx{?;5zbRyx_RGlp+F^nb)tu`O9HY9#DxXd@(>*?q`ru)$Xaw?TZ_U-za zN^^nKzbpmu1sX4huBl0vi?8puy0APO>Z4_SFY|*E*c7DR-hV6Arg#Z7o!)kN7|rkE zlO1zv7FV_h2FonXo9NF!?y#H;>~M2|2+ZIP5tss+D}@rN2?ynzmXUuDBB@jW$%vA- zDqTdW5+GtoZq=Hgt4u?nX>>9RqggOA~ z0Fh$!@!t~UR7GmPZ{^zw`>`2RACdaXDi4U?cayY4Kyp3PZe$MUL^ih1hVql>63U2^ zs1&toR)Gst;T5yBjM9@;P!UkO7XiupQbn-fTP1#{KJzs}X+ILx&^7@F=z*f#Q+}^H zpiF~8(1Q6=0eK&)f}uIP<58jdO=|NAIwO&B>XUNtJ0&hiC>$&kwDzX}z``1J3rmnsh<^nj}o6)c-ug?Mf><-3vb3l&g)B#**7b;rm#lcuf85mEUUuP|OJB_8{>g zPuyOuw8vvm&>CecoL5#n{nP`mT1I?G7YgtRZ`G0_lKY#v%ZjQygT*avUIM-gXUm#0 zTUrYIOEP=-DRFe_hfT15c~kF0|Ig&+0`n%?*x#UPR27S{7(x8`G>kVnQikO)hF~c> zs_*FY?p~`AInaUEG04M5K7dH+{Ev#K&}2{=E106ovL{IQeS^KhnEb%&%`nxdk_6Gy6OE53CQnT#uh9*WMwEN>fSw0;B5wd zWk==3m~3v|QjC7l0$S{!e18Hz=b%T(a^WQ?H1+go$tiqL?nn*59AYpP@i5toL)_r1 zB}Dq@OpG9*RaA_Zj|R-aISm2X{`|R;CkpwNEEbjDIqm$BVe7Cx!>k$Pi5SSUL=!A6Z=^-{OJCT9tBehPqVIUb>v-On} zioGYO7aUB1gEuVCB=`@>zll2e0=(%R8q(yL#ds zG6$w6R;P|1vy5OZ0&J=h%YAgyt-fe30v-QaE2tomg>4{9JTv}fCe`$d8w174qnE;6 zgKNnB&ZI`@((?Z3gYW@VBy5a?n5Jt!6Wk@^?&TWlmYl$BurA@OcNv+CN?LbNk!aq8 zs$i=qO}D6aIZ0x)f^8kp4nH5m_%rA#zEB&fNR`I>cxQ8uS+Q(ZOmh*HO;tACViVv; z>;kaKmbzU9ywo#q&WYI~rX`G&KxZe`;dR(78SMdtdE%jaRm+Q}e&4K;}*LtIX$n_@jv=$Xh z@~238SNVk-jF}etDDLNtnJc}(aDgE;0CN?1NQoMnxJN8c_Eez#XaX%2%^|?mTG5T1ZmonVErNcZ(HsHtWCACY`hl$ zbN!duvD-^+?QV^SYc>Vtl#F>I6Al_qT;_&b z+Td2r)DK?ZD&!GO+i6a(DNR}_TCFyeK{=1h!5%5owQ|P>%B6p6z*Vm9AR8({SFV#o*rA2UZSbky? z_&qd$BX)`G4sfqtk!94p{Ap+tG1!ZfEq6o9XM1Qx*9CrrhJQ1 zNa-ec$(?XdKcrR%alvucb1otc+I3{!2AWI5=;KE(ts(Fq5eUTCPvSpvSPX56%==JQe)#6!8<#l88`)3+I6TU zE%R2An`Vc4{wad>l5x#VTtT~qQ0mX9AV4C#CEMVVzJV-ikwOv+=ERXf408>GjPcDG zpM*J@F;JipvZ!JzvcvG>SpEUsj~Kn{&4>7G?S#tt;x(fHdPK_kziq19_Sm+E=oW!M zUVuyAM2aeAo~u+M59qNU`@Xjiz}UwAmy*!J5)0)MsOhMh)BomJPKI5#l>=G2x#sI# zIRo!%G#87;%`BdP*CXqrL2>d{16T#izclP`$3)||CCJ+=eDt1OoUdGdC)@kJg0*() z{RqffMRwl|Tgi)3XUBIwSfz8?xC0^n2Xv_+0|fywH@N7Wh@q}p?3lI4e7RnRncoU= zyh&?*qEuB3%0#{cQghX;G-_tA2rb~qz<-Mj>qe)5XChu8a7&>95IKo-@=_@-JHlN; z&@KL&45C7Lc$CI4M8f3mM!V^Q0n`r+4#4lD z%cO>PKuTNcs~W6_$3u-*HR&W(y=~j{@z_2d+hfzmwmmWq48q_Xagty1DKq!O>5ZTLMOP^ zahCcxRr@^zt_}NFJm7r%cMt#VsfGKk2>tg$y?*lHvy`@z7^Gg45ljm4)bNr6 z8d9GT5w2Vy9acY%KAp&3gmPj6^HQ;dIx#hX^sJ7qK~r=zQ0Rhc;kr0SX;Izrqc1}q z3p=GG(?|s{_J9v3&_iVtY$6glK~ier#uFm}sAUd_*Z$o1*DH2|-^~uPH}W(O55kyj zMQtOD^&)yY#fe3PoX?*@|NYT7d;`SGP5I^gx`m4`ml`7h2OcL!#--%tm43SV^TWrx z|8nE`wxt2n-gPO?NiU~|-}bxr8nB)qapE-Gm*YFmQiKp*K3|9;hG$HekW)m|%|r!> z{}C7zHW<~-yNnQ!b4m(v&fr`&)l+?7+_jRHLvLYU?kiEx!KMZWsPBdPnOM;l`;WsV z+`k%Ym=i$A2OMdni7ud%Ajm}F5HS-`RnQUQCsRX#4PVHqZ*UbigT?}CUEp4Z{X0R8 z>l-@GwIFWY!@vr{r#xIkp5zpwZ~L7jA%#Jl4tV(f6VnX|?76Zda>D{?^%01|EY%=* zFN%t3;6;sBJtT#|3HgK4&tJw(QxRZqqx!sYyuKo9z{Cu98bE1tpp~EVZc=HPvT3pt z`hm7hA6p#y*i^T44wd~{Do8&^6rNGaw(rOOsz|k95qT$S70{@Fx62n(&8pE}=2Ckt zy7GN(oqa3oXyjSS|I2aa4`)SS6f2tJXt$R&*A{6nTlpoS^ z3`3}|X)=jP6gy&>w-BGCo%tfNe_SAL5T)qGSiWtD@@W;*Jd_E=TO0W*J4#hC7U1 zES`pScXv?(6mBzLDEl+}#(>JsNBr*<&-op>bx5u=%|Ak^CiOg`J_moSut+JRIys5J zU{JtvOZbrV#UUb=Y`|PZ)!ZaDj!W=|gprIYXbm7YHC_m@r1>Jtfw_%7!>rQv+BPec$)JGL?7^tB85TU9i>UUxO<+)y?k2o;P{6DVRFVqJMU8dnbyT z(0Im>Y*1;Hr}Oh54_nc58Q@Vd;fo!3Oo_1%xq0X5_zRbsNZK=@AoZraZ&Z=B-!BZf zs&myHN;O_5<*8*SKXHs|^SFSrbIZ<+JJ*|4Ki^O)GiOkU)FNsSfosGw2qbj@SOkG~ zo0vyugd`*{u$n%}A(&{u69iMLG*?k%JSiyqv$LjC{Gy0xOv3CiL;9E|2wWG>d;Qg^RwY zWS`UdtH6Ff?!mIJ$lb{+;J>+TxlxL9qpK@7)xNC@sek&<`qDTee3@RbQj0WBq3#0w^a8x!6=_qs0cp`>7|u@g)9t1Bv$%xSU! z+6cC_4{VjxMWQ$pL`01Uaz~CE3pad=ifj`dRG-lq#y*rrV|;+vBk%{6Dl)!6^i&AWnxn4$rXVv0R@ z>yKhjvxDAN_w8`|xa$Uo<$5Roa-UdU8j4pk<=Ogj5>#O$BuukPs@8dGB=HMBhheFlc&SWq)z|jWZQCBP3)m*2icOcT zK)Ab*J7C<9$ZS7se;v>L*q_`9z~L^IcB+uGnt`2h-#b8aVdcxKF|mSbp_8|qI(`)2 z;K~PQzpkXz9eBYnzxmdPn?!pto_mIY%B5QM_i+KMzdK5$SjG9-JQ@Wyt6D(Cs10@c$J5$DZ;zYr<5?FGsil_WiX#j|>0`HMJX++rx8wo2!+~E?x17 zQ$>P;VZ2F6AkVvxiFwZsSG4j~?mQokmAlvJrlHp4s#KbG$&naXcMb!^iC*sTkd>6n zne*e{VeKIKw~bxF6ekY$bBe(N{xjq-%G)o<76VC)DuHvy~&2l|cSZgxaZcKU~BkCSwt?sUXi5vpe!%fV@j*$Jcdpjqm zx+8kZ)!f5`3Svl$1s1Zjj~pP! zUnF)2i1IWa2=BEGA3#P4wdFrG0VhXIr4!WUn!|g~*&AuE^pa#ot(XpO#lG+rAqL#D zO36~Vi(8(_3i1qOXjUZIY+fz^Ru|cH3&YPx`T%s>!e^el7saZXp+y9l9tUCz;Qh5f zpRecZwZHcLb;N!EIlwUuz39FcYGU};suKrux?sjzq@1F`l^5R}Ll(WrI4o&Q?VC#6 zy~fwC{P~yt02Z+R4%dHx#q#F+w~u^x;|~i#$!R})#7ECJ4LHs^Urp(jc*@4->kzQS zB(D}K`4S?5o0#TC0A?z16TotHmnt0Yra)~T@5{n3=eIZ)R`3(!EJiW%o)ME}KtY*# zQ?^2WIpu&y%_QcCfk%M~z#@d*TQp6Nu!z{;tyPic1P)KkI}TCN!&EK#>JGuN!*Mj% ziER{I+)PwmT_V?BYC#u)B|)ITe=+=k?JOFQ8pa%S?&*TyXo*E$!gvv1p-GBf*%`DT zIbtzj8vP@3Dw-eX6p~3JkPJZ6ffDCjvwb4$z}hCID-IxI1d1l(ngGM%U5ZgqNVUH? z1d7vN*~oZlZDzL0L{)<_`goO@H)}mBqAHunrux|ALuC`)LiTr{Og8{B?g(z?hxuV{ ze(YwiBhdf(d_AB0z8`xo1McN2zO;OH*u`LfBS2maE6d1(y*o!kO0!r|)vu4=l__`k zEK0$9E^~g%-x$DckvH8iq?@`~)~nY#$m!9WvPx%~@@Y{Je^de2oAR`uZalqlEdZ2# zs;2I}F;mF{0S8VPt?5jPEH*L;#0T^Q*@vIEJOs|!CD1}3rc8-ELF(!TNMwyx$#plT zs>XI2YilGqK?aqCe@&AUI12sIa#}GY??@OUA7BH@090QwzNgY0Av+>CBE~VGUy%8; z1Li1$O<@v{NQZG4#MNBP^iVog>=3Dx{Olz0Oe^Q^ZlWr#))w*s-WZIlLqAaqNs)Ol zyl5}0MVbiRBe_@RAZoH=s=jhNL^T^)=4_pfXhILqR|L-jVj=)EvRZp;08|CaHIQ6f zc)w&NdRB}V%Sy2z`7M9jGZh-nP+8}bY%eD*Re`d7ou}MdnV2mOfvOlHPz<6X+s6EV zV*`jN*H($DGSk7}hxu;De(cZv`FuUU_SZ9`fVfo!+d=+AMNfd*q}oR)CwunPjx+iX zeBs6$Hv;*+FZd^i)N7Mi?t>?#$Hq_3E<*joPek#Kd&n5>2H-c%IGw)lgUb!~m;55T z)&0IU>E`7pUNcN~r~68bs29w7`JiAYtvVfxMdbjzHNNu-u&z7VNB8KU!)&yt55P=C zGAKwqZ0a8JzZfcThp|~A%K$s2PBz5^0%yEwn28xo@}I!vh&~bc=CRwNh+1q2qaM3!O5lH5iyHI1 zKG!v^&C*6&2kp32(j{re3KyuT!y;*YP|{iNBnc6eqnnaqqCQegf*0WPCF0=;py?-A zsHS8+8Y*e=3)^&)``!A#J*y2z#3d@7hElTaD zJVcr5b*oj;4#X~*&WwkYnIednnMpjef4=tpxxb#T*J~eJ0i?u`6?V+serM9fS<~sq zV~6^Eml7wdS1;G^c*6Ov`@_~p%37G5&iFj_=$pRb0`lgrAKvpfZa0u)w&22S z`~1o=Xmb9`tCrcg4xC@QNv;pf+kClgR3``hZ9V1%&ZmJiG_@e4egiV`TOg{W6c(9e z;;``M=AHpLmlAG+cMuaG4vz$x4ph(t_YW4Io0=5|$Xap=@_=J zrdo7Vu$7EIwF*Rya3*FF%{7})OEHk-I# z8V&0!Y+ta@F@%DYa#h3?>VjCkrJ9>> z(Lf3MP-*7jU{ttJfQj*j5aN3(v}8i*gw*c+HSfERYb?T$a$hh~sSQ~d84O|ZL2h2{ zfT}`NRkq9;9xMUOj~jGOX$kI;;m7UpSpEBY9k18^e7;_<{q;Kb-HzSe26ZL(81Z?< z4Q0wtMa&IvluTu`qfui5#LeO7pJy8R4$2%Ssh>pSet-Mv^n|@1n}Co4Bt5bKRQA0TZW zGeZmlH#M;c3UX1zQZaGMJ!7O`C$}lf*Xcr^VfblGIyJv6It7J&l?Ga21drrIhiERT ztVKyRKNvLQDP}2z7Oq;fk}u0h(%>fQX3EVw+{|s;w#~NQ7@#(TR04l;1Ksirq`ah* z>#Ajq{noZE;vg$gJ_O39Ni@;DVMj9;HMoZ2?=lLQR2a{n<3X~M+4ab-%dk#&F-hgp zk;}%W>4(to!X?%gd!TiLa{>>|E`}4&ta7V89%PP{MSrw}A zsthJg6HwWq!R|@%CS4UBh`s`h)Srfk)8UI%dAa#7$5pt>?{g2Rr5?>_`ue|N01X{~ zzxU=AUoQNR1)SdI=L-e8YoX(@AyK_KBi%75f+${>gs|G7c*|(H`0rD)X zX1p-Nf%|~RxWhvgP`D!uL1_*ORoLNa%a1^R&T^)HAUnt@URpdeErF7_1mfhda!W38{`)t?2u1ul;yEkLP~uJ54{Gkx6KA6w+k1 zQ5sVw&X7Y99Xf%G_vf4*YsdQfbM)=qD4g)}+hd|+t8-^QQk5LEER@eI4kb6F4NY(a)et1 zC}F1>eUf{qO*tq%kgt+z(xnjt2nD3zc4!~&4jdLcR3H0M>1xCL|9ZY&uV_(#S$dJp9QE9r!zQmAlW8igwXh-lLteb+tfFCipe2AEPW40f+ojvliPQHA*ZEee8@t*pPQ~PU2z@R+=KyHec8q&NKDc@mCNT&j9I@fgY9{{?6A^>G$RV!FLLY zTB2iflOm!5^Z=Kw1Yty_E3mjjx2gfB+CHOa(0QFtwzqggrIbKK>H~n89qwlK*vz+M z+qUi4G_Ay0Oq`<%Shb-V*_;VRLY~cy%PgRZ8(zLROh?s}JAyzfV8aC>Ss}ltjnmG)RM_| z8&sFVVX)&c#}Snyl*XtPzz+w?2gP8}}j*ePpX&I>M&noAP;rKT& zI7LKU%N$(&n_}fX`h#=o2G-{*1rT}6DxB^w5ut6E^WmH0+m2Xf)=yZ657Wu?!z`Ci zQvWdB^XIJd+EPa86%wO0aR&IuV=kzTH;T(}AC7hc4{O@}7|P|#xr>AvF4FVH+yTi3 zoY@2{Ck+tUc49S>heI+gJxRl!Xp+n{+B|xr2Sl1P<(IaM@Uu^kE5!m)OqbIl|IejK zDF|lIgy@@{p)O%JcXf%)k;AVMyf+;U@lJDyxrt@MA9a|yN~{!fz+FtS#Y!@?3~)g# zO4iM6(BKOdGmWG>0bXhAcXgEUj@KN0h9o9~5;(^Bm)2|wDTH$=Ry+x|BzmAsJgZUE z(f>op%4-tmZB^p85VBxD_(S;Qe3wLR@}v;RN5zCtAJ?bppMN4=h!F*nTwvATgaS-* zq3HR{NM(oF;TCfKe(d}H`r2Qw*Yml*_Wjr`m;bhtr_Fti5};NL^kxFlk!Q#rBRejA zk8QpAW|zYO9PGE0yeZNyU;fKuJSi6^@S9@4gX$AQc+VexRPj^aTO)J5YXzK?#0k}^?G?ypG(`*M)|3mq#Q*IejT%)!D zV#sZKn2c~YJL2<*jo%{y#Mg6wy<#I6_ru+tD)i_H21HP5Ze%I>^CUN%Wl_Mo*+0$rPKw6Dd=`Af46Q05&vOneDx7d2IBAdbyX zCZoftla`-Vqd_0V<{U`lyJZUGK;=%3KX4xtuF0mB)(IdolN=Mg(lpaHI%f?_3k!ii zQ%vU*^bXQH%Ye_i#Q(`5y6_VBV$4}(FEPStF9WhDC37MBk>O;fc&hXm2o)_3{)ofz z;p3DBy6mxFm2}U0MS-0rz$!*n#amhB74{<%1j-3qrcb0m0CL2dAU#u11ksO-E6aVe zY7_6JMm1$1x>DE^5jtF*j@SrJTsz|h$FW{PbDeWLio*VVf4yE`&)3)2>nj}qu{(TF zYEgs3SjFh^IrUfhvJa89wS#88tofITeeNvb+#NkR=gb5rVnF*pzu^&=OQQyD*^s71 zzYhZ)B@0e3`Vr3mkRKF%zde$KZg28=ZDqe5tJ61*n)&HF#rPhL?5aFBY^EuTd1~!m zTYlhTjS7*94`x*do|iViG`4I^nI*I|#+0nXx@+nh*)P=qHA|;)<`Jv*v*U!*uOxtl zRg%@r1U2c;$iKw=adH`_A!}rWBoNW30>)vc6(@ap%B%T&`&d(c4uc5UOL|tw20Fk$ zP-2^CQJg@bEVk|Y+~(^9a|D%)R$@`qEOaE`>2+r+h0}z0O!W(=Hf=d)VsEP zI$1&G?-Y_wFaehg|4%LeRRtiuyd&;Uav1w~+8%*FnfvXvzrJ2yU(c`S^ZD9e`>|Vu zfToHyXsWBXc-a>&d2Qnvj(l*2dHD{u$m8?YOWf5S~-0rlqW@A2q8 z1K@1FY3nz~Pw!jb^M*^x{6?gn-Rd-Qv~hCOz$a9Tti#2)3?bhg=yhL_k{D|Nj|0A- zcb^+)WVxe$UZtoxIZw5%o~j1}7+YRME@`A4{vt5;+-o~Rzp`gFrvqn*6POmUzuD2O zUTM5;9cT2Z9IV-Y(85+ z9WeTYXsx={yTC%z(Tr#UxYD2)KvkVaC^URk^|IYaYoCb2x8$@5ykr3@ea@lH$D2kd zYhWzhDiZJcB#alBgtdr33vCv=7$@uAaFO^yvq^ zFp58Bni=<}Gq>H3-7@&+>-F_~eLY{#*Zz9hZnea(k~2&ct+|b4oMfuim(5~l*E=WH zAbC0W+~w$qANEks_nZ6Acru|d22SqYG7rA+7RpaMf}P(^q?+yO8S>4OlL|&*lfTt( zUv9fANcztxTV`iOiL4p#DXnMJMsTTl%Y$BV3SP5>g-ZwRQtA&vbUMDA?E>f`tZ6nX zPGP7eknVst150M(1qeQ7u363thll*_V@h5-Gds=;d%E5flv5&2J5L#Z;=U4|XhChm zY4(f|W*Xc>R2B%0HkseQrhVWjO)i=++EIF=o2Wxn*#n}->jsAaWrYwPYs`}Z$psma zR3)==gm5YN7lmdO3isE{qf1qAsTbxnN>FRAOUoB~e8lZy()|z_irGb@WU~8#_h-@~ z_eb|p%L4pZWfsZ{#T!Nxy|jdzdhr1N#2^X^Dg%?h*CV zwjbMH$NqdiKc8P;pMO5TzMjvo*Zw-}I4t*A0_wA71E%~x<~#$a7nGUi7-2^U&^`ci zs5gLC05XQ8c{nj+VWXo!ITbZfqtEuklNrfkttRcHx zVE^gq1@DKfBJ0)3oN9!sOP4b&*HNNO2f`%<#D8pPje{d>FOcE`b#K^YoR*rHuc$ZO6krb@fCl>pp7B!W5B;fnw4@05UZB< zRUjV#qMlMX2DB_FA|0g}ppO#Rb1@eRc&pc10h2Ta73R}KpoHlbwP|n})tuu#I=#V3 z=JTl##3gMTQ1GAzcYfwnX)tSSRw-Y0D@Q~X%ua_hVl+(GVHpBdJ_zBWUUVX$qW9T2 zl7J^tO_6Evf`R_a7mUkZyr&|6xt@U!6&4NZ_Ux_Z0K(0TqVK9NG5a*VP%ue~4DdgW zW8Zz>ZQu9jYybLs{rUX*&+k9~`SbPp`Fy_i{jlRmTdpUz7F;-W%tUY+42l;d9WOvy zn4R3Cx$IPhsw2i=v;wtQsNu`s;h`Bfy_NHOM)ReE(odXTYRKvZk1Ht8a!=poa?dP( z5p?gH*W6@zzW8Vl4GBtEb*vS_`DU*PK_<<<989kp%Q=TBygEz4=v9`$uU;WY3AuwA zfLy?{s2)ujT_B!rzMg8%$dS?2?8ecwiqxmXRpN>xc9K!Tb4r&k=cb^W&|+o$3=qmj z@P{E$iMyt{&z=-^ibmWAV_*XOVn16NPr{tCb&$Ta8NI%qH2^7zBgN)`*dbqIATavA zMf#p}Rn$WQ6HfEB6i7xeX}uR*otg=1r2G{%pmjP%*qZSP+o>K&kI^9ZHXbJtd$cyyU=evLCWVpeJ_2g6_uXo?!N1(il9EGu! zG_IY+5Q)%d$;eO7g}-*}7LxxncKm%lzdpade*gLN`}d#UzyExGeLi3NaqQ-HBzdB| zcQGc}Jt+thacW8prdqt#r*bW&?k?2TJ#!J~XE%&FI4_m|L41I!pPrpPTgyD_d{fCf z{V&JV0Q~yRFQ2m&r!zgsi(%$+BAFee3g8YuM$t0=Z_hc%pYG#k(-yI{k{ zB%akMLR$W0j?0leo7Ut{K~5}yIzt4tR{f|GmLF!Pp(*iu}9KlnY{#2-O5o=mI|ruN@O zLU~Gg>Y|>B9V2vJMJ3|tT?zD&TLEz8gMq5U@-xNr1yuQ)SYOwZ9ih%b22K)kiib2a zP;C}cHk;B@D(f_B0Fl_p80nE_;l&RNkjHtc?nY>h5;8v=?q&`(0aCAXDwNd3a_$zp zix*8m8q? z72D0l9EaKAM{M_U*z2(8>v(>>zCOSH{Qmm$=j->M=hxTqdf9P^yRtQvOo)TpWZNxD z2rdY?HXA;kV}VQSs~LqHGJ7nqo>+xnfwg_cp1@iiPIwN`tmK@Lu!ng%E4VTq7)BdnZb{8UOvf;`G%^o^{lE!1EMc?z=NEVA=C{KC0b*xb7Npy?~{Qv&*=YRkA&wqY@{`q`+!rFae#J#f5X+ z;(n9g%G0KV^&Jo3)t@kx8x*^*nV)NOsX%(tWch(YsoE_Iv% z|3y_sEfyA<%%RRCP~~T$(u!3?x5)#j4Y5>D`%L5Xq%c0fCKfNtTbm1dGXOdzX_a%f zi|nq|0B5W|Bn&>0&{uN#?xvb~J84IckUx~C6d^{-2>B->w|PbqvFY&zQtYl?9K}_? z3YqJ<3!8r|Osj1ZI$8hB3ay*>Xj2a^|qZ&TKs zP`2m<1yRck09geQzs9!BK?RrvOuq0JvigO{!W!!fY*Ke@s;s8U2`om>L@A!upO|I4 zx$X9{eSaOVJp}*HKfgc!^XK>P|NQ6opFe-TzFyClnH@(3uT`gK=Sc=Atxc(Fbp&lc zxcs1i=PaceOm}7cS{V%cu{HYch2lm9hy%j>+~+M}1E-gC-{N{KqJYR)2zGtg*PnNV zoxig}@A$?2^MAXw=!!o_|1IH>r2H}5e7wF7Bpd@U#e^Xzq}uK!y<}3w)nVjbR9{lD zH#rQ#0_IGW1pXZCl;x#&b{#^bcNcTy+x1uP%M=j_n-g${rSJYKY#!E`uci4kK=VX zcm7TF3ORct?9|~Z)qPmHXPvki+2n&^CxzCWEvapQx7Sa0S|2O!`M8Uqb0vc=YnF0} z^TGO4))(I#cVVFHgqtc~ZXFLvziqP9g_W$;bCGkMacRZvYsp+4LxD};r&j=1HZUX> zXRQHLNKQVF5@%+JkR<5_ITw>JZvZVW+`xU}=YqCga-pZ86!oRh$urx0Gl=fqh7>@$ z;|mBvcsI{=MVyK#$%g3Py_sKpoM?Uqa*}Q|md+uPr3zedsbJ8&s%FmqO+`0wbMKJ! zi7kbnM=kw3k=kNdh{NK+%fByIP z|Ni;<^ZE7X>ucX%`|gL?VZkz}JS9qd#ETJo;r8s>b<(b*Jk-B2u=i|d6z}&8;L?_NkbGe{>uti*Cvjptaia2XUwEDo z?>I;6dX#fVpIkaKjX`mepkJA(I9aI967&VhQmo z2$_Ei!G9auw6xIdH25q%a0B`Skf)4KU19A1yL=yC$yhjB_;hO$#SP?ij9O8@;gejp zfRVL;!`aa|C`<&XYe~{yb}Fa+Vq$(mq{;QhcE86~G!1T7?NvbCgW=^jub{DU!oJ z!DZHT_v8;RY3bNn_j!20~DV&4Gs zYM{83LH>EZa(~=0x4wjhD$C!?*)tg#p=}n>bXp?FLZd+oQZ%02V7xt>>xOa(c)8Df z;^(M!^U}MfP1xt3QVGVF^Hb7)(=Nuj=Ux?yba9~u$SkC*>7&DvqMrk zc`2aR!GPXm-P- zn367Zt&nZ5nUzJe{oZ%eM3lRF9cIU2=6>wQe)wUB9oUcK zx$n>C>+|dN`_Jd^KcBxppMU=R`Fy@UKcCP2c)ecF!;bwp01HxW?o)Of7oqW|7x`qB z+A>w2%xFF#kbKKC+&L_B?B`{u##s2^1IzIaTYWuK*dwR9$mM&Vocy-W`{@CHV1a=% zCu(i&#L0(B%hNt4?rd10>Z#Iaa$6t#h6kLUqUpkGu$+)Mi^|C;=gw$4K(~@$nn9H! z#Tz&&%b1t?rDvFZQZ?r|&_U7BpB*DTzaYf2G>c00#?9 zq_;7<2?9aNQz$%zwRd%9MBMZ*j<Wil+Equ{4dz(S|`0sj&P~Q@Buh3TE=@E_GO{b`Kej8%cW~s@6haYV$|hYt?0m< zpgu(6%&Lg%oLgNtr|6; zUj*pr^&XXE6zM4qKex&#RN{YkWcBL6d}RGvb(mjHip8K>p%uQz zan&A?*1$^koM=dmpyaD;+8w241;{Ks$aD4?gHZ-=;!ro~aOmh+>{pDJjnSMAVksd8 z!|`x>s2bv&1zuVYzj{5;0!HHhVYVOpZpUjs_Sf;+U(eU^e7!!uUY}pjKY#xG=kxjd z&*$fJe?DJdne5Zde1}JpKco`uYps&zos?_nidf&8lNTg)4srJ!yDfKFa-ny=#|c>r zN_IUQlRrxu1;OPUZMtjdE7QXQ?pG^f_B_*m`#d!&mE$A%KYTKP(}3RM{tu25zW&={ zeD*X?oDh1>w)XFwZf@J$QmaeXcp``ogC1%~h%HW-g4%Cm;`<~M3YB|4jyzS_}Q^IsRLvZCG!`%Xz{-xROsH5(EzY# z0u%}v7k)*g2OTODh!GfDh9QjZWq<)lHK}>F#9lLnI}+!>JBkb3%)d8Na?&!Zu}sAY zkXi3SlY*vE-pa4%SK58R>>}o#_#eBv@B6+V&)4($dVPKFU;FFN*Yor1`RDWN&*$^& z>-qKCU$1>Xj@^#kJj_0$(nULqvjZU#QF0l&%))}Bjd9U}QVb^wEeCQL7E1o2WW4Bz zd2?fxHy!buG!yE3^7;Uv$;!_|xw3h@8*A_8JXlvaLx-D&Pere6?sVc(hk9gTd$;wu zsooprvA*>7{mr(=J%Z)uxrmdiw@IeST3VUpNSH%QjSL==sk=B&Xa<4zn?{{VK#=*X z+d6<}m^1)T^6mKMIrJ6H1cPrb4g-=0q&V#h52zl>PYlHtM4TZl~vHg00eV4!;!MT?RM&#&j#bN_nopI^_< zXJq-`zXJLX+s(~dUiR~6pic=ONT-Yg;?{kZqW+xqo!z?4qt*9KzxCj9VRqxBb(+m{ zGz2*Lhp7>;n+JbSwk0Y0%=fJ?mdlsB!R_P0L1l;eD18q8i4ZQfqMPF?6x`J1=IUK@ zuCJUkU!{tCG z*=Xt@%VOJ`nxe0$2ey3>-Mth_hiX8UFfB)gT0j@LqQKHBNyV^9XU+ihKnuTxF#`$V z)e$^9+E$S=^|low<{+Y3K+})Ao1wFdn46g&9==~Y%q%wd+K>J9dcF2NcXiyKul=>} z&m)q+nK^bxXxhdx$uT>s=3V`!_8P5RYsUFoFgQB4#D4darH5+PiaA3LVm~DLaEgpk zsCnbd@1*|0EV#6f8CEgv9#CLm9CT*H!BlNOif?zwAWXewqUh49$Ky zbB}0p;_VFs;P8{_EobRLnthIy-{R);`tv+(hwoz%B)l4evO6a!F9h17FPHMi8Un&U z3B4*OH4#=}fI2hvfG_}sM>kV^zFx2A{(A2F^R-8q=YH&mnVF3`O26x7G7D{&OI9N% z`WMii`kj53MatUhb2kqRI=P|hcFx+<>}@2aOjoQk;b_x4|qwhAYd(W74)z#FN5zQiwQsvUnpws*(;8VCnD2gi|t9T1v3N>O(-rjajvQ=6<4{g4aCWW}W=$F0zCC>Qe<%I-wLusp|{E5g8xQkc9PypUBeuxMd0o2v}5Qn%K1V-z6 zZp#5Ng@}1n0hF_BCrts>^CYUY6F?tH=5UFDJxf54;8FSzqZUyA=r{p9Xsf1f}<+O#ml z?6#EFXb~bF)}vwMXcG`h(mTDnX9YWbT5aQ_pngT4I@u3q0;0Z$&{<8A27=K{7l=)r zbg5!WaRtXAtr44(&zU*QW`olg>%KV!9>E5>`3ow=YNHVDb@#yrTxc| z-e0F)0}-g0r?bO^`@=X?izy2PP;m|OQBDc%nl=W6oe60+Yg#DwT(H9PAo_a=T0>I6 zklst058G}HSd%0tb#sikX3cnuL$1`&gDFv4GBW{Vg-%S*I=&65sOMai!c7AaMF{CYH09CaNQjzGh#ghhoh8!=2^R{wwc)Ius#iq6%ZaX)G z5jKMOj@T7>bNJ4(`M1oxqjGenp#pfOL0S zz?_Ol^3Je_q_5ml9EXdUY^tuN4)Mt9k3cEaWCYG0H;X$0&bohB0ns68;VKrKBQIoR z!JP_PMXBi_*Oii_~#HWq++%P5UxByW_`d(GH1!qH2W4u z+Et=k9Nw-TGSE1av7hedu?8%F-_2}~a3Bj$z!BTN@0r@io&A{-rNhl<2aX2FXknck zXFQo=H;#9}Lo8YU)4 zJ@M$9&l2GO*T*-&T~+xzxc}{jYAT28!b$zxmM=`3r=-@=uveLfnedNk3koWoN1(wZ z$TNfw38TMH>HpO1Npn(sDXshUz2lWv0!b4xm7C-SmX{Lz1NP0$4Cc9Itr-qCKRnL> z5K$9RSNOKM!`0Qz?V)Nas%om6GZ&l=(YgA9>oUvWBLdM_T^9RxGpsd2L|sHQ);h#~ zm)vI{Tcr*-RMZ`6;tFwyig<49;w&f6imK-NIRNF5K?`GEg=gnRuQ`mGAuY<>H=j*Cst!yuUGFk_qt%{Ow4$xx2UEGC^uUXO@F ztZ5I;*E?Nk_DNv7(I8YB0RNa-UWS=Fa@*7VobKS!(6uiH))rhBbIJkg6K$01V$v|y zF0&k3j0hb@V1&av7S$NX5&KMP&v*1*-@8SBwApCkgN`2Ie2cQ4H95;}?4#rO& z@ypepaDpEder@7k8|}+j0GI^Q=Aif}+AipHN+@NAL-qCr^WUA2f&ME2FY-g{==oSr z<<&b-Um5h0&CJRb!V%XqV2@h5FAN~!_u!n1-1c`fQ`nou1b!P+$i4` zMl4k^VWAZrG)BZ28Mu&88$6N5oU`Jq(@asM!$Bzzdn*NbwJBo}^RSS~ zO6L?QWswCa*32a@^iwxQl{Q*M7QnLYf)OH?)sG|%WA#^Rm4WdGa{)!te+GtTq)-S_ zS+sY#mv*&GQ_QH1ROs!Rd5VUZil)dX>nwXKi$442PN%Un-0D@b@~rFJfLOfw>X7)jwYb%EbuOVJ*-7rWF|mF44}Y3>Lo?lgXTJVlWIV;BD{1lFC*ci9d`H; z`0wV2@8P=R{(BAtu$j&TQN;usex z5yz&%U<53ACR$y{t738akk==cVnO+HLgGS6W0Ej(v1icy zLJr^&1eE3ZXN zMP1a2Cy=w04xu6@npPmGmvXOoz7xaeDreeDt9k{5F4E#e{W4dagrQO$_}SEw&-+UE z>l_?7G_Y!Vgwd#95zeiYmf2wzSY(1`1|^6{`a!01gYR9)pNEx6@yH0!i26Ki5Ae4% z{!?#YR|0U&47XL4?BI+8NM&daPbrPX0|s|p`<|_yeK+Oz8?e71L_T}sBnzB(40b>Y&fMS;e6OMw^0cZPBC0roBiIoPAP`p% zVNYwP9YI7wOBK@WkhI=vRQ7X0_nAA_g&fF1^D@N##pq2a^cv5$dSqB;4(bB-Qe8ag zea4v#VU4)zwI#q1FHQ$rQ(!FAcFx4sB2j9Vv?GZ|oE_LO(JRnZBf z;R&c5vYsYhRfP;Q%jmJQ6oqlE&s^YtRTs|Szu?X_@Nx{tct09aDC9tTl6!cafn`C&f_h^K3ai;QE5&vdVH4y}G*^uxkXaafEgNHqRr($)Fhf=M ziU&H(Ej56{G7d=Lu*}lwsD-*NW-1X0bfi%yjOw%U0`-HqDWl*S0}9 z2p76q12f4qq;_B+>KALKw`G9~;X-HDq6R&y%zxv(q%oh6;>D;I5@{ZQR#=ym)D5K7 z2VOb>c^BMQXE2ijl@Ithj@|5t96_MhDa=KftZ!7ay`)o)U~6Zu1R@y{rJ`l@2h4t0 zyF_G+(G>}nFOB{uNj;`-ipUFwE{t-Lh|G8Tcu!><i|6~zH^;v&`7iIeJ3IRm;|uTo=^@J>fTWF+3b1^cI{p2-9Jigd&~Fm?D*H5b zGI~EtqXUMaX&8~A5f*XE{gfI&)b?osyb5kI2aX-Y>;Og|;Dh>2bu{}UOeE_gx2QC7Lss+xcCqWdJDT5+q zoQakTz_O5V0u%lD78x7|%>YCInug{GRXbv}TV~=bMnKNt-cz1nmh!|{U0X85WC=wv zq>SNgoRY$sYzt8QZW<(76$GAVRT)woO_4m~bkFl&uLi*U64Rkw0OocWt&FJ*gbv`8 zpWu=09W{oNA4oND7zKM+F*v|vt=}98 z@9gh|^Ygb~9yph=OtL;6r`u*!`B#%vgl3S$8p-GNlce!JC)^5f~J`Vyr33G!&+VxNvkcg4-AYzb1aL zfm&SVg`5MWBzbL_EQBx?>lqTN!~rhDM$hGvPsw8#y+cvgv6L(_yp>s{lzLIOEYo-jzp1(QG zt`_%`BzR*`Z&_)+#{sUtFMAK~@72*cw8Bbx^8vERpI}oFP^nirKwdTLV%GfC)r27t zG!k4j09iT`ATs>l@cN4Q|F6;iVmn`czU${2(Vrey7P^QxN!+kTd*!z}=|#Z29jGsB z>0@Ylc5PKPZP*;iXvIhT6{?Fop`bvd#Tbm93O-_)zPA_am}NX(x_v`PJ65fRiz92H z6@_m`V{R&Fmu?xN8x*55B7(WIa7oz`h4&j{4<2elPNo>AGPpBWkfx>F&2tTyiH5UX zJeHaPW&c5VB4UvlLbW0Em3{#5vFv#WI{-)9?Yzz>Fyt-1f&xcx_={-rsU?IqX?L)a z({j~9ro?Jl=%j9<>3>`rmK>j$Sq6;(5e1%Anqv+l<$drHlSk#$DAUiL2B1s?VO%F1 z(@j*H@hBCWMx%k;Texwz$5uf=n}&ZklR>0tlDt5Qg=Lk?yh8;7XuJGW zDrmr=0VZ+J6LFIhJ4{7g%p%25t{aQWCSq%ps!^upUiIV(RkZTmc`ut^x1b(Cnu(%4 zu@IT#*$Pnz@iH7Wr3g&xPl43HSr6vq3(KE|h;fdbBg!siqpe4vv5IxiXS3l^1(9V% zSYc;0g!G*MTJN=<6QxofvBgU!eTyY0ma(7Kj$pt$=Vw+T-@RlpVP$#M>)zQFSf!TR zi9rT9K|Oy*FD!G@LCM@Tl&hp`0)7T(l#J98*xhj%fXtyoxha)C=A&}hJE6-ybZ%MwF@0S>hNr+mS&p3}%4VzkxMOcFb$ zC9vwIx^3Izv@?uZg>uxd_DIu)tj7 zYF|lCGr7+ABzI}Sz4Zgh^~T{D7;p~WlaFe#6>)`wKJa7)r8j}!y-YMt*D%GnF_P8F zX-h&^Zb<9_25n28B{hIFAvcLkL5KZ(LsowJ)xcj)vb5vWelW*jmGyH^0}wsGTs)kR zPJ87d9%nQ9JI6=r^#c z)pD6De*~Z{s#=!xQ;cURu%tnM{1yHEt{>LYmTo(LKk=SngOlaH|05glQQD~}_C3yb z48~ZJ%G*Q6C)VLz?#P0@{!ZmLCqPLaiTtC`b5Te{ny;tivCPIR%#YH%7ot!u%6lB0 zAyRlIsfdDwR3w5>Q%)ySgJ?cBuz!s@tXy)ot50-L{9uTChzkX^%iE ze@~nQ-#Iy4i3MYc?(aYh7crR89NCX!)q41y$!T&NrB?&qQ%e$fLT_;E=Q|kz$ zn|-kSZB9`lHYKC$ylFTtfL>xX216ij_H?}EBBR!S0csgtEqvr&x0v<5XaI8VWqe<+ zH;;_RLYqDLkA7Y}vHII%wEL#Cdt3!(uN8>Q`_Ih!n=^y|@L$#tbhukiQ+_7xO{i98@FXXF9G0E z`PZel$Ciky++^D}eQb~I@z71T?U8x_aa2_>mRiYVR`mA;`VrrTsk)uWW1MTY7sO5L z=-|mQyJ~tSv)L{-dE%;sF zc;x~-fAX&^_b-4{Jog>gO1oZq)b2`~U4x4TKt#;JcTnEQ%X02R??<|bDQ}z2MI{HEIm7UAA>dLkT)-3bheark%qtYr$J?tm zNxmtqR)Z3`3x3&8UX?q2dI`UY-C6&pQGqDJvLOuJ1bTT+I5w)q2obos8hj+*B1Uc! z>3~8TVDNn%!>-OvJF>dN0FW?T=LiFSBGGY~W86%iw)JR!Of`qYh&E#E1rT+e?%bjd zil8!qH#a9s1`vyq)9bypI~=N;h(uzdvAzxUpPWoBHCJfdFNq)*jXTr$v?7(n0q0|r zeo5y&8=W((`_R`aELEBqIs%uHij(|;z>#c#_Wy&aKMVeo0c3h{ROV0v(oEX3R6s3c z3=ywUG?F5h#Spz>()A>2I`{&lMS3M?&9K`f7N?Za23jfyd4`h83R2e{s=LRXmO#i- zRRQIdA^Nnmv$kkC8})j(4&cPB8c67CE0~|2eRsdS{c(S$8g;{1-|^ZX9A)kF za&wsr9m2T)CP#&kLZw28LqxeHa{w*dS7&ui+F>>3xY+xBTzH zgczzK=@r*)lTd?_2}o|MQvWGQGbA9-GeTVsv)jskb|(c6F)d6Leljxn5I)B%Nk~&k zr69sbq@GXtAo*!~!5H~-?0atg$Zfy4#+*Y?b!;*qVas_L+N^3;tXUkZS}ban4t;#V z-ItQPZe_yto>a~FUHUUet$h<^x|@Yu3O|j^cp3UfKYqn789aU#b!Gs~>Lzvs&3{`* zyy=)99RK>EfB&L$U)ARY{m1DaR|YGwB(?xk=k*Szq<6Tz!a5j+6aB%EKOu)NO*30x zPzQ{0U%hs-`PFdg5=u^_Wr)wJz|u-hy9Gp`xvQg7Az(AD#71|x9WF>+#?ez`(46t3E9m%`j2>V@1%&8C|GZ@H7d((f1s(>Gkgs# zkR!GAhWHaB_|824Z;q9@-45TBpw9g3q5+gyuUc{{xx4qTb-$yiyL0~q7_!BT;Fii1 z9HyBui_x%JmTj5(rwGg;5cRZKiw5}T?QCEMbVNu{V6+QO(@=o{Vq$jKA%|{ihuLOk zre?0$%D^q=Skgr* zDLp5sudM+@m#suR#OVUSKE(~qcu~5W`&Sw;OX6u&${Y@c2KOSEk^a*>f`4AG{rP&m z!UPl>PB25X)o#-qs;*uk^cYD2xSMee{NO)xX~XCMuZE7U;)@ubT>a>jJmPN$S<@B; z@C=4^cuvwQ_Phtcdo&t<%k;FEI&TWBdYq116A&fN_K}{uGT_6P7O?|P?~sK7jZrH( z_1gHqisU!)-Kiisf2~Knv0#`9_{tn_Cl|cF`*ir|FHCC86^=p_5rTgo!#ga_Wn*S8 z%IyV2yv#)GFZO!aYuF|!5F7)s8p{ZUQ?4$^;F&O_8^Ut%C0NOE=;q=o>U0nl zCy2igh1j72x5EI{!(H7D_iZGX3!|okc5XtWXjsWd@*SjSA$qz1y(F@l^jX|s0A;V{ zOg_;D&nS8mgWzEkNS>O~1N|~~Z{H(4u+xO$O@B3?iapmu@|D+f-XW*1bvDCX5RN{Vj z)Y`CY8>e@}W?3}rp5-ye+Lz;%E3P{oQ#F;+@H%%Am$pzglbxFT49`9vU6nFs&asen z^u|;3YTf=e7tnx3$>us@3U#3WAf7=mm$<7TmI5n9zKh+zf%N6t&rZI(@fOm5g7!Z; zf^@^OW9BB#Pc!qwearYa*RfJeeSm4Qgb*+381Qqa0vOJ_@iUp%SUgY^MYsoY3tR~^ zL$QKZXB{pIY_9s)wmn&Z9*1k@$I14Fbt^<*zyuI%hd2xmfjP`@xM&Deid(aCj_@h=uCT1JdgXuB14uE(d!F{{4AMEN#9I$2=q|cw zeV3g7sI)(?V}HH&*WR}O0{?sgBOnI6yqTkeNjNL&jqruf<8j7yx-X|ETFw_vU+_>R z*P|9=c*G=-zKE6W)2b6d`ZCB8Bu0g7y~b=xrBmLV%`Y*w8^bdLfJ=7zE*abfJxK>Q zF!XLzzAx~*qwq}b_}RZIgq{B8>iBu*CV5lu8bRrql*6BMAT?hq0V-un=#U_km_y_b z>k7C{X*WMeBX4LSK>R!$lhGE9=1SHf54X91pqWBH93txO3b?EIhKIRrkH@wj+qP{F zH{TBT$o34w0FqSK%?~xe%tc{{TySL~5p$TUrw2QjfIzlzJBx0i1Z)hN5s|HMordoG zQ!q%bppAh-#)cujqD&*Js8X<(h+S^VOt} zDo||$UGedD2~0{}VpcE-_#^^#hXuj#=aef_kX~SVYPS^o-45oEG6d_~_Zj?i>{PF4 zD}_rW?F4cY$gEz@JE{3mc7+mIuj1oC>u!64=q;MBr@{bHNb)inF;_k+qREmJ2u@n-3~RCO<)@7agu!j;knCK9s)5H zvBM8_J>0Y)AhDr>J-muw-xBJ}ACS5GM^1k9-%18uIeaR+bL#{J{lok*2?-?Z@%h_kFYDkbN`VWj{=J>}qo0Fc&*^1S53E5xG^J#zBV*HWA&n z{c-I3@hdjoIsD_}c$nFTKm38sWeda3wr$Q4&JIyw;_Bpl!Aq4;k{sY9-j>ww6kXCV zGR5ucm*}UKIud2q$+CP;=_Q$d4|v7IMdaseFX6xE29P-~T_?`G8on$Ir2z5l0Re{Jh}riSWmc|YfE7q&6DLN#pY0ipJX8c@*R%V#z8 z16OG%jEQ4lc&KwBOns@>$a$d4CeqAd(tW1HiuI>To&bu`r56N>2X4A3B5rZh{h@m3 z=3<+esTrNN4mltd2IvkmgXwXYsHxduuI7i?X0e>u)J**drwAe}$VW_Uwl%o{${{eE z1A@#VQNClcwhF1w+zV_Qxj?eRMb6<|D{p3cDpT;^AIGuVejNKl_t$nDw(q(f`=PoW z$Fa+%wu`79yFla+fgEN~HwPpVt8&fU;cj+B#Ow~y+zvvXoKnFzyyy!Qa$+>}YHLn>_Mn>y%as)Q69TS_Kj32L0mrrs3n zo$(~O%C!HJuO7#-=eCbW3;*c=hHDzoSkV(M6bzO&l0%!G=`&m|4O;plifj4$eDme$ z;sNoNFg->mR4*6UdCkl?Uvgqtk(OB2Iadp@N-Xjc>$9X=(CS9LWfb$;B?>4THpSVc zC$5rj^T82ueqJ&5rmFw;VBahQySDYeDD~#}o+Z3Hd^}k8RIZto&ZE|zVcLSsZBYSQ zCgu-mIjFgFi9Ex!+}C?07%2JDI9w2zCIX>wEcBZS;9+(UxNrXO$H(J1GE76wHn+n} z4_CEFu68toa)-pEKMqqhRXaA*$oQy-(%ZYs0a156AcolEPet6#4X#1-R^nY@TZ;B5 zrKL3jLFU*=Oss2tLpL>gN`Q(Yj1L!s>*2DA9)}#jA=nP=kOQJ3`ymP(5QXo6xCu6F z2H#xGU10`R!D#0SXhbDIi**GmdhBKo!{H9Vz?Bn&4n>Upk;AM2lMjs$;UEwZHmH4- z!_wVW_C)AzdYSNxo(T%MPRU7eyeC1rd|2k>CCm|Z%-vqhcH3Xa{@P!!eSf9#hdKZa ziC%<1NPd}X6$Le}Iah->dn5B`Ikc`$zA~Zq??>~161Jp!h1sBeG6cE=noF$L!{5P4 zUE388M4bb-)R+9=4fLI`Y`JR*1I@-ZSC>t_~CJv?OLU<~0FFbn9Q3T*BIDgSUJA9th`k#Ed*j+lfX zve^kV`!X)1Q89jnHb8+o9R?|z7!V%X?XhjPJ&w(Nvtu(Hhw0{<9k%&lEqY}%i zwzLJu4&TRNTSSG%zVTsmicP~zUDIQp;S{MgsXL{f4zbQY2SQ;IhbtrmK%>?j1%=(5 z95dVKtp`LWrTGh-hKSS@>as=d9u>H{nr{j}9O5RMim9lIDOAN(bc4EV4mYU6RMeot zRSyn@8*+bhj($k6{t}2_gKVOQ>Y;L|ZK_CMWd8~knc|LwJZS-xQYz4E3K!y@6i)%K zulFPhwE~nDmf6yCo51SS0b)Z8?jRcZJ?Tc-Ym4cz_REq7WfJm&f9b-z@=`S}`{=O2 z-ZXOf&nAoT`n>CzH^;;nZ~XVr1`ug&L&C34np}eYdt%cB+3$e(d|ZI|{vP%gZxw;x zYXI|sYY}f>_E-FWQ_=6L?u$(QUyAo9o^$HkzxMF;aja+hJi~n#yA7v<#RiI=)4}X8 zQ_J{Iz?!8`J~}3mbG^XQ}m+V^ngtuy}pVio_SnvhWIFft%2-9X(U* zG*ysvums#k3e#Ld=b{@lmQ+C14VsAxf^DUoD-ML62+s5xVc!9l1VHKFuFy}AAe`w# zW0SLB`L%@%EVLDTU$7$;Li}M*zq@_KO^=(Y(QxM z%9doQ#Pr9RRNaLqbZ!L%{^uls8nC^?%y81_7j$h)Hqxu)6%M(wyQJ9 zT}0*^AiS&llmL5K2hZf^Pqu`-zT>6_mug?0r5FmX&u+iR`{Vt%oV$f}n&*3N-^n#o z`uR&8zB%5prJPAMB`STgrQP4G77Dlvt)$^EK(7yuNYGq)6)?e>aOOaSxl@bU8aE!0 zSec48jFd4QB4UAWqGkrD1BwrQ_+}rEhabnWn?Ee}JTrTUx%-wiZ!1M$b|#WGljAsa zPoQ>3H~=E7^6=fw9+_(TaDSL>+qS_C*x-h312({1RUk^YfD2SLwj55kfT|%7Gv=W{ z#SqKRLWmFGPqYYB6pE6}I1?6Z+NE*C{+RCOIN%3hfI-HJJviVBw*Y?wGV`-;n@0lZ z11^UhsemZN9S4GSWS$7sP4%H$d_Hu0Y}=;WL-nD#a(Ek-x}=B`u6PeQA#MS`@g+}t z$-q9zW!#bWralw)Dj#a@h`GI7`<-3%+XQ|y?ZROq1 z8O!-I2H=-B+=|nGMarr`xjp{Y$Nufl(eqbv{9XHfy>?rJrL`9hd~?2fsQ%SYbBLRn zT3bTp*usEr2Sn8l5jospE^6W;t^zlUy{b|-E~IH0E^A8(cEpGIAnk?t5fUqdm4&%C z%aO{&_5#n8#VI-)`~g2u^E@;c96`t;hXO}5iUN^lhr64ZKz7};SIpdeGxzN{_Q&x! zHrtQKzJKf=+hf~5J~CO{_J}pJx^1#;+qQ{tKllm>itITm05>4xpW<#AG=p?_m-)X2 zl4w$%&44%Z?sN!e`pU9paO09PVPwb)$A+2#qP9?10Op&7FB&bVlBYO$H(K> z$H&LVuaC#asfoLdPea1H(x$Op1jS zWh4o4Qh>Rlow-gJ-*8kIEe$>UVf%5s_WkvIeZ8Kq*K3a$TW%`B`jiqd1f7vGXLvQP zVo={b6T=+EN{N~H@-7fiFCQ`R@1vhcTH>hHe4gJvw{u`E$~#%aw8jbzV@aCenui~@2q z=EPAgZ-9wrq>!4~X0eO9nTM6aDjy=iaN_yCYnzv@Ohu0o3etTo#Wl(wIy|JwQ#7WW zIMmH`(}$@&9yr_%$A`PSSokLdsZiJ(JHVB2Cx0o_y;T06A!8{d;`L3x}KOR(88tpq@60NII&i#c=^ zyl7^2c&%7HcyK4fSSgm*DHtYA8Y z{URmJjtlHEFXE)lX0ZYFLHF-*9LHJxxZeS{lSc~|EjE0sE3p|)w#x-yeCEp z3Es)ky=OpHe&LBrSVhm6DKfn|ZXxS}3KtgOa}k-KwFQ&|`>KtcUUB_j;Bq-G6) zQOa*6a1SnU*ruB}{P6$~K{~KdODytMC}j88Vk_n5s2a^n8RuYbuwAg>Ft^S8*hRN( z-{rA=Y_Heju|FTWJsy!J;qll;;^;?Yix5o2pV>D zACl!DQ;R@EF5BR0CO0NCPT_VxcFQ$mLnW~A{z!NY1mdES`%!LCH&Hl5W&7B+U%!6+ z`k#+~{^y_n`RAXHUmw4IJwAT@`uOM9_OU%457|^Vm2Imn1d2~LUM~3K7OWsHiie4P z0-q-b=r*q;E~GcCI@=U4Hs?EYSr={dJl0OLB&eT>tk?+!V)Z?Z%d z+NZ140_UdiZ{*Ak){38a;c~HR;p`n;TmQpq@XKF#$zNBH39ypWPrKqOgI5_| z6U|_1W?~8bhaGCFy&_7)1TJDGvEtKp-9R7%qXDGECLJg5MQ<(fA>YklBuM%k4weiVSYs9A`WwGaEGX4I}~zA#=IAw8Qxcjxx^x2!(k8+ zJKPku`L@fx+2f(Q9gl}98NjyDI=(S)erzp34p4NSCLxV2=b2PUOH+`Fd=in<#F;#x zmANqJq>ZB)K(d1fVi^|h2;VPhICe1)Pl04rUBz4ls^Njy^zrfW`1R}KpI`s{^UpuO ze*N?D&&S8F$K&I%Jw9R+<=R%JnR5+ZjwnxDNVvb2?B2Kfn9D+$=)-?rexkPlO>-*K`r` zh z=J=*$Id|T_K!3UWCD$uID#>q&;9oI;>sw2ZiBY6vgjxV~9CB>Oaj0&3h^W~nVj?DH zu}5vh2u3(b28L4Ej`_>AE@usLsYOqFVtGE~8B*#7(KD|mWRoBj<{BPNY`ksrk4Kt) z<$$|m6NrlMs%qlq>IV#mJB9Pi&co20+2!u$CI*9x`R=Z2Zl)S}LpR&>&}~yabj!^8 zS)?M&K(y+IEi3lHq7i8E2`CLkAOtWfswQ2z$a*!Z7v^yxpTqs=z~0VZ;LL?dPYoL? z^gD}->ITTR#gKhG9>0El{QBqDKmYU3KmYU3KfnHY{PXeg@z_4}@z^3~kUvBuGGYj{ z)`9`l;7X8>F`gqTShvcV3_yf3>M(|9PmJJ!mNv+75ZIsT{<*rXm_THAIQAnKxb4UO z-1pZrbN}xL?PMkYBbjBSe;{Ral_oI{ac9D0n?%n-b&Hy9>2QjC`L1D@d4~We#xPGV zfVW76#Qwnn>X8Zf9IJ~*V11W2a^2z6sJ&EW2D0Tzmi#ZGfPQ$SkZ~5JMg?mZ+#f*f zO2?-w?;gI5NLt2jefV6|>G!|pj85FIPlOjYl>jpwhaPI%VFt6!OvF^okPJX1Txk*R zXt9I2scVU(=7`k5Ttk9M%#Hn2049;Sy9N)~;JIm2h8d-OveXL)JTMDHPSM!m-E`Zw zhpBJdW@-<`VaNbY7mHP7kyRRK1_17nB2(RvvP2ZW38O1Z4$T7O=DCW z)m22q1QCftL4eFSiiT5G+KX}KZZIv~=Qt3>%*}u!I@{ewCVOs~Mti#` z;JJ@HRORFG@$vZh_3@Fu-(Me(k6({RDm`H31WLx-#*UIMVHp`KDxRitNl5`2cVVM7 zq}8C7KGk!FETgrD0^9N$z?t{6o!}+&(!xzMPbE=_hn*}#>!Z{J+RGTf|h>+r4+py|vhCOm13DI}O60wS^ZSO~h*1td8^iGx8xgPWF|N}VqF zdIqdBv7I0m9KhAZlE+0SyNbAqxXb1aRaHg0e}Db@`1SGYpO24Uk6*t&wqM)!*r1Yo zr%8Z6k{PG~9rz!?0jr4XTpXO-oTmI0?S{;HqUv1CBntv3>N|kk)y^!k_8SR113mY{ zcC&*^y<*MR>xj_*S11Q&6*HRlwWWsS!zJ(6K>@iHv>vg7`UyK#FuB4Fw5EMT z1ZGgxSRz2sAK?w%h^iwE#6|e(K(6i$i;Eg;3x5MFLK4eT6n)_#Jt|=8;;t$l%bGUX zO!c9gshVzZ#|Cp8Fm-crgStZ$NMz4s_<_MX8A4!oTtl08Xuio^X&_wapN(_pTB^i$ z^2Ge@q9QOcIs)=VmP;N2{5Ahatq*I7tkxa3-_$`lzpQd1kWF9$XrPZ#af__qqOm9F zn2-X%R zHlx^8{H;+@MuqARjS~!-nGRR3jSF*^EiqDQ?Y9{nWx)&f=U(O?KQjKa5c42spr`F@{~R~#DvWBag-ZS@%q z(|Xr3PFUL0?OeEn&)b}Jrh%Z5Zot~m0NyaWzjMiVhm6lz5J7ZA82^RU&MRYhZcRP& z9ZMN^?BaXIx)9Mj=)dJc0TIIl6lY3FMog4lZHq}0ORfZ=2OI+R04X~Hp;-y#>{ka-#sV7A&jfkq6iNzX@t+$R3@$pEz|KKcPaofZw6v@@+i%9ur%U=s>@I*q7pp4X(nh z;G7EL`taYXu~g++)cMTd339pRTOKg$a^fui&ySz*yj$F#=bb#qD^xCai7_`ZKg=?r zrrBXGV#xs1U}_Q@$f!te3?s2P01h>Cg@-?&<)M75EcoyyY#=Q}!l_IjMHx2y6hV=q z3n-o=Vi7erwcSj8Q`m+_Las&Xt2B9sOE&g4kw^FtDujRImcyDxAwjK(FWrE~o1j_; zg1cnC$e!;~Ml4C27CC~$6>@|sGh_A24GiIb;^RC0%iV)y37mkG5VnBD%?}uD;%1I? z8+%#WB^aAPHhrjUk6$1EeEj9k^tbgB?+x48I+_<-vLnl9k#HHc`~!u0U_-DhwaCH?6CyQBJK0*^?c>t zj}`iFBl%mtP3VFu^p|v?$`XzmEYuO)?59`&0J!)`L_t(40cKNEH1rys3iq9;JN1B{ zBgxMo>TUtV>8+}`Y%B(zkb+05Tr z-G8NjyyN1FJbWtuUFYK4G$B{jW&U($DzY-vn44JaBBf?qY%KM#!$j?{W&>$NF{8aA zRtK9A1H*4CU2?o!GQdZMSOuqH!)Mjfq~zgGjvdIr3CZyIYD_sF4T8wnso@IHhi$&O z!&NpM;(K|@+tyj0GrlhnL8s3^a=yDCwLrH#kjb;! z)47(BlQ0`4su5?@{)lTf7To-|qzXriO9)Rt>+=?%L*^RNH%kT>7!HH8bec)iL8=k}fk*)QjBkC>7H6`qvoZsITt3IE7#1Px-U z2D3;XEbfs(C%mq)Bn<8lSFZs3SQ+2|djI#7A}X@Gq~cMn(*MQGMI}OZ!muJ*G_(?+ z0}Ki(RX~FSnCs>n5UJZN+bI#vYQPo-_-zZrk#3puMl-j;60u2PpZ{g-`&j)1@z`EC zB+1OOWQ3G%D+8v z*(=*4Q$TEwhde6k9{@yY-pa_&3ZWa)%n-^;kR&lAypaZ~{bO_oc{u3)?O67}hg2BT zR__V=jBSo6(6nnFejMcth|F)V=f1yUV;y%U2#V1XT1r#p^hFGohJOLM6vC{kA>L~^ zf+VUZAsU%M>92u6J@i``=k~&`OJ`hN zZaY`>%&s0Xel7fQt;a3W$>_}}<>t25=4PeJF@mMl|CY}zb^JRV-^gp1FQ@OfgZ&c< zr~x0-FsVE?OUyL^E@sCeP?JrNQoo9M7=pwM2B?N52x@8)sq*Pq7Gk_Cy{OkFqKws0M6%;ZIiF-ZC^!vv(;BU0LAjAJA~ zIUhg`AgU7yM%$e}BFcfu282kMW*dld7i0+Dh ziZD9qK`L!VE*Ue+Ke;HnDwZ$Goh~EY(Z}Osdu&@o{j2(xrk*lDdCO^o2NY)knO{(v zSO(fg7?~80D-$7cCqJk&N-+?WaUu(L3yrfLc}+d{SI*C+p8!X zDmY?Y&NqyApQ>>E{ucSlyK@U-SoZ_C6#m!4-~W7vwHGE=U?k_qzK8VNiprZytFX6x zyZzJRg2yTNKrz#&Jh1U54fH!~46$%SAhrV$$&Ye_v~cXH`eTJ4GZ z#E8-+!T6OyNCXi8ZX)8EVWplyKq;%bxKcW3qiV;%AZ4oTGLaM=mDDVP4X`xeLp8Ur z6_qUo;0(~;RHbzSrgmERzl2#c^&4*QU~Y$JKfCXW6Kg>6`!_~|W z@h$d2shA4nR*}pe9Wgs_6>lW~px!(*=ZrtAstxX!1mk460ZEl-g2*4bGG~)lbI<;z zvQN{tjtsnHE;4M1H}P~@(-kFOcRJ5t1cA33L0FJmhiUi*Hi9?p>uyZQz{R+3}cc47~>q*PU zBrOX$XHVg#?&~xj^}1fX0Fazcd-QL!fD(;bI+Om>)#z-YgQ4Hn>rTubH%|vS?VzVh zKI*wsG2gv6M`__xi}c{Pb&E@P>|qkYpn~;SJLyf2REe^bv+#pcai?~c^>p^a?2p-G zKNodVmAF;};29q3?tYj*!g;0<`!D=1=1dEraxf`Ggn>dVTmhbe`N0L+@DHYeC0U{!qqF*fq9)m%k ziiq2vL7@e5#R#SRgeY;+BecBM8MA5<3sy}aCOt_9m}W22+Aos%B19xK9|{N(tBiAL zsZVbuVF<_=f9V{bnt?)Fx`+`bbGl>0D6QPE#oT;zTHn)4NghS#Aj2a`L)lBAfza49 zvj%0lIm8OJDb)=44+u8)21NacWZvb2L{G$&!382I^@q~2L1WwC7SEk$Bj{6u!KPU>2py{*#eymzCk8;jaUmAdHv!UXhVRK0d8fByu>e{|Egj|^$A&%C+$ zo=2TTfNxs<%_S%LPc~qVu==~I8T|nwX0awfTr*3kh`YpwS0m06AQ?dA4hj|kHoj<6GP$G2f|Fc?SGAGd~;_;{UcXa~>4SRB>Q@kzerIY2N8XKC!BCKTf2M$T* zO+tJQiyGNywH+M0Y?5RUeR5@LPr>cS!iLIa#RL$?aYQ^&ECS1<|F73^B=~zT)JV$@ zie1LPQUp9xM@I}o#o zXUkj?4!DX%K7WJD;fl0+FB3fRZyJOsW_BM9s!DAjTkU|w-sKi7!6bP^@8prMEc?G? z1Y$;ZOUc^;D8iWJ3CRfsSr$FEpzVAxhB9SCg{;eg$Q7dJ*DKH8OiNVEY+N9Q` z6r<$e@V3>w7cr2z6sM6U4xz|HDlMVEB^65}5R8m1{@*k2gYCK5@4j=xm*?yCd_A9$ z`jdJ8D}^??>SJO|sPf|5wHTU?nrx@2B&Uk5q5UmI3na@Igcqc#Da^3$kXZSs4x>e} z;%z6!t%!j2;{A~VM^C=XMINj7z~_lGfD(tK8Wzu#h+3*b2}U6KdY32T4e{-V4B$s# z{pRK?82%NV;~t+6{0;ZN>$>~92L(c^1G^_7aHltQ6mzQ8EoGc|K%r^xn= zrz;|YoHBnn05q2ahruD_N&(;|rV!-%v0+~!C6-8$Q(GXOu|2GFbT(aN4hYPB%XDx! zB1xJ%EggD^n~Y zW7?zPDg**Yru|P=Qcw}M<$3T6uO})LV@kGX?w~J|nIB*sq^1L=FRC!ONHDP=U7p%Kph+zbNsK%%890 zcpZBr|4Hx{Ewnt}u%4;X%C&#<{(H6|^V{Dz%lXdpBffs|^GNhz zmpX~z5LffvjGwtNe8grp=$pj4FLS^wmKaDT3614eX&M1EgQb%Is2z9HDHT`69T566 zMF6+OD@c%~S;8$Pt`d4Ddc_q}LKsY&$NTQ#K@idhW zfnn2Owe@q44=SL~bxwxmOVUqDN`d*=i8aM?yuCO<1VTDe#Lt*^w+6}VAIhgS>6Ecf zA`(l^XqG43Q2<8bV`)dAxElyf8~P%!G~){00{Pk2bP?{K*jUsIvivq>+wx(!ISdZE ztelBIEu%a4$n)^pU(eUq^Ywb3I6!%Sd!7^z%XScO3$~;rR(4rrB9EWBBa&sTr~(%2 z(~axbQKH?PiwQ+J=oBH=c{vWh|Mhd=sE(dBSh~J2Y}|RlvNvz`bK%QN*5A(aQeWi% z!H!_^gYWQn7v7xtzg6SyS;5Sln#_opm6C*klVM*Oql6 zKmzDaa$iof%LO&l$qaH$T$aue7nJS6d-%MDtyO^dsDZLH>A}4|GiE($D&<(NYlCj0 zTck-s{8JJO`M{*pHvRC9j(kMJWqPkw+Q38&V0u3|oC!l&&VWi6Fp7zcAQhZg^~-?& zSlY#Jw7XKQ#20(3T`gAa78KmBHaf4!5vz z3Ks>pV3`rN4vA%%)R49Lb;;rGH3$p(PI=yo9@lg0DuFWIv?4o4ux^lION%{X2qIP5 z&3B%`YP?`KoCKzS9R>6a54_<-mz&J-{SBwbpB=Kfo}&v53vOycW5@T|yQr&0kk4 zEKE9!yTjZ?7U6w87q3MhQK8ROt|_<4B(f+kLtPTTB-5cvt>kGjkbFhmH+Og6+dwb(3JGh0tQ>G>IR~4Yj^%B?H+&hgVbSmWexqxW zrR7|qWkBUxIq#Ew^{*PhJ7|3ia7(j)$W_LDx4?QT=31v~SH|gKkK<&hNk48IrJSR} zgzF10^2y(iQ*RU|WDa-0Ju*MrVNg4cLykuVOc)~KqHMk;LRGS!2o!>A_zNVK#wv1G zVPt3!)AI=ZL4};<46MWvQr)p|yEqXta~h+A|A+-$rTsvd{({^jcOWaBB7p>=hnY72 zNomw01S%0U!aCu|mCCB_*wlRsmyOKlX>nmoe5EPMR|%Q!=y(`MWG)5eWI|zT30}`P z!-<7_HdLV;CgR&7TW|;C6#q9=iaCw67zpci1ZNGQZK0)NR_)a2?gO0{nMtWQ!@CJg z{?77cu=7$w;qbYKT`c9wc>iN}+mHS5V^8DHGqS#=dO+WZnUQxDbveB{JwpasrFkwI zK^_o$JOd({M!npD4GO@`RN-Em9RX8{1jn^Roaz^C<7}R;U_dADU;FJeG~-eswWC-~ z58(LKpuF5et1Z^`Dm_XIRsS80WHkMr5*{npI(;8izbo7C&im!2e|g*zKQ47xYIWDP zutbmqM%PQ1I9MyFd6$^DK;gM9-_U`tH;mVvzXW7h=7wV)6+P6{6z-e3E6idiUbhGV z0wjRHs9YUK5QK&2D^qpGKH=dZ(Cz|tKnvm4E-XoD^+P$g)tHU+**BE5>U$fsTiIoh zW|o#&dty}_WbR$Hjq=^yT{Bl%^4Y~L`z_jWt3q7OMv$y(*3^z{`buK!!_9prPL5f1AZ-AAD)nb{K+~q zIh1HH008vtnnJiy5{&u-02#^PK(j9T^(WeoZTNaIOVwA16t{TBlM@n3+QfoXE0mJ! z4Kx}v4t~1*UnA+8qj)U9Y@jDJlbZ+b3Yr&Y?=B7gz8-ng>ANqqd8t5k_j?}Y<9`Ty zb9I^VhB4b@jPL-rrEb+-xAgEmV<;&kZZMia&2Si3)vIq?#DvDGVAr(#hhT;Xeeg&e z3b9ztDPrc_-Z83?TcV@Z9A#)@Z?8mHoG_8@f>X6I#sfg=A++T($LQ$Il+;+Kae!Ad(4#miZ zKNQ{J#NhspQ>DqP5#E$bfSsy9?)v!J_t)$7e7!RE-!t;F6GxL(W?-zqV(nrLM{|Q@ z1?jtPSGI^ESAoR1JDl>4K_r%tIXncM$o*+TK_>U-ExL?!O+}@AajhpC($3%=HRo3v zzYyApu*+g=0xaPexIDryUbHw43Z(0Ksf#lfW9`#R6`9!Ub|Kge=ALf5Q%^3Z7A&08 z?u?s5WX?*jZ1T1We>)ZkljgLi;gTHJxa9Te*Z0U0GJScWpWAZ2Q^q|$&Uhf4+2OL= z@i5&@AAX4I7D1nYaV2n&kT3}xqr&L5j2OxgeJoZr1w|_angFR_MuReu&gwQ2NmrPe zj#Dt?h3b&Rr7CUG5C=8=t7x1hBdK)PHe;od$83p2N_!ZM!J^(gpl}%{(P^P2VbOD5 z5BZc2`iZ?S23L2WOktdPIigM}LyEexuDLX~lAcoh@A+GW&o?iIXgLY8-M})JkBOh( z3(~rMw5=^~F2Zy&>EQ#&JhhPmC>;RD{yO&O>-Bs-zar)H5$k>l{fzsJm$iSys9Dro zR~pO~670Die9-_zpiqaYOC;7vMIhxWky?X7Uw1B{kGF=$*x)Fi0X!oqx>scKfx)eO zv3?AP(h0jRG*kn&4&NMtW-$Y=zky_2#_{cOQTP`hzzxU1Tev;f=mxS|P^sF!PG2v=f)OiU#jr*iSl2g7!?am0LxMZkCe2 z!PG3$U4j3=2hfl|n<`k=;_{!iSuTv&AhEHUB$1vPQCx$G-#Kd%fD^}{ST4FF1$sI?M_mDDiLH_|-&F>HifTHT%>}U(5g7)pl%}KI%AOT)Yg4GhEq9uv z?(B$QqH<1g5E_`*Il)#;t6>90N*Rq6bH>#yq0bD=UK%paSo~2l?^-W6 z)WkFv=c>7hNF7UBZaA@-QP*l+4g>X>+R24o;+Wkdpi#2rf$a2U5l)j_ zXJV4t{-q2*ZS^6Xm%31deWI2s-;+}neFe)XSs8(x1td8rM3E72L!1E_+5rD?n3wFI zE5Htq)xXE{wSPUIU(e^)^Ywb|ZT&Cj_z>-qTY9N!#WGiGZ!x*@0L0!9!t~stp#xI| z7I#+>h^NRCFH+%>fzw26Vr&`_xGKzp9cRXLn=zZ>qpDF9$7mt7A$d$>p10kS=FNpw zzQ|^^J7Rrw{VkWzYc=xQ!T<)k=1;$Vy*b#$K2EI9egFIkE=Og~opr_a6Q7XFr4$>l&r@^m;b?rKwPTuAMYh9C%p^93jJ;38`tOO3p$e;H z`wXHH1D%P|LyUm}7K^S_1W<1FVltRwF075}ro1eRu;E?V5*HghqQ~87A1d;jlt&hj z!_%Kx1BoIdZV#aFspbZY^=*+3&ti|vis6uQU5rvfKztaowp)7XMRp$A-{2E^Kxm7C|l)S2+wsF(qODva~Ks!~MJp_;3= zt7*8%Y+No6Z>=;zhQ-RM+$KYxr@;BQ5}?-w=`;P@2!-tyNfDUDp1@~rGVp8EXtWVc zP@^Iz7=QQu{*armBbwq=wJ}p}(Z4Uqe6Vlw(+xRSPi?I!mIK&j&|7fRJ9n*{1a!UM zxO~gt=Ge`|j?JK^n~9igs>XEyh^6f&8hH&oO__pR#-e7RH2~bf9hueBRK+cA4o-p4 z30%^n%Lexz0|46ogXDA7E5!%0$0&*=@p&7&<4>pBfzZ2A9mPq}Z_SEC#t4H-=0?yY za7vr`q-EhlUhpxuma)1}^3YtSad$jSF46%wk)1+>0!cEV6wOt7Z-ZK)wNmmrx1Ef{ z`ID1ZuC*Clmea7wGU_$Ypx7+iYc_WJWfZa*(f?rN&tbIt@2{`t*XQ%=^Xsc}{vWwP zKxbVeKc>AbMTeyt2~AT#OA}BA{&x;jEpAYRLkkvknC5O?c=jxlWQ zwuX7uV&FFkTxz*|6UNP2fh*LlXD*EX!~^orpdzT3&ehP;D`%!G!Z)yQ7Y_;BaTkC%FnJu_C*ep!8z-OMW`PO$2wY4vtDl|Ld zQnT?+Z!!w2Yh->qOpg87V}-B6EVo$^5$R-z?hwx{z@yjHG!4IT$we}R5*eoAl>Rl` zV&O~HwPPDL8}0If)O=oPP`U5!s?oc+XhhQ!Bn3r{30DG-y*~0em&^<7OuuaEdLVZD z3h8hIOv4OgLQa;HU$z=)aj5tY&H2SH$Q`6(Ku@zm>OO#?CqQ#`&U+zjI5S$t?{xX* zzuF8lO+d|6n$tFdvM$}7C{7o@_$Jna&iEV#k7U&gz30{q77p0DRC{J!ZVpl+BfrcH+BVklfIOz~Kkk%qibXe9rj@}!O~lQPOE z)g2d6#bHq3NX8H@fk+h>3iKFM5y`ly9^QiWAw7%^ACsK3Vays%;gfvPlZg#3s;h?v zZIZ}J%&t)Yja8HGH_Z4=k1Z9RpB_OA4vj@&;<;b^A-mdk*rDMM zP=kp?Mj(k~X~ixz0Kq-?iiA*v4@j`sJ8$Gj$ zRiy!pjzf2xEM0$h%(3#C1LZ7q&ji?+0mBb-sO6$Jsa;g9EtGsz@ncjEy z_&F>Sw!zI0+JD@b_3b$JeGjY8=hxTs>+2c2z8op|TkD1tX@GLH#Os6qPkI<~_elJ# zkuIqgN~eh{&0R^C!3&xx8eD77`k+-op79@UG5I~i!y`<))3}G_)J@Y3V(#vTLmL3VNFy4~rDA=SVJ!)) z=IAX!VwhWA`UCXQ+JqXUK_QYZz-p#gZacNp? zma|ZZM^;z(`d{y%=}d1L2~Fwu*IxPzIoW9Ni4_$YGSpUx(OPnEaVMXBEiN1+Fq#3tT@?;HS91v?Uqh88d9 z)U>}L(f=~Rn^8CzfVz@kJ(3fXs#gt`=+83d_nUP9CkVT; z0lE7*PWt6smnX|p>unc@ep~NB zBK@y4qkZKH2Rk*vDs^%gwk5dkFQMie0 zp{fg6KoKE)B?IqM^p$WmSk7Zms%{(6%GkRPgy1@fZaj?)2LcjV%n%;n%zx()27+=m zK+1KFhV703AH>zcJH*Z*a*(i=ff|^dR-fbp(x^W)f&MRw@AN$N_(Ft)L>av-eXe_u z^H|DMAOQtZB?ZujH^K>g9&1Ak9tkf3_8H>|#`rro)nvAxjP=}YuUrqG&(F`#&(G)Q z*Yo*$y$;(W?$f>Gr!lPuNL@)`<b`!2;UoKg?yVReFKIXP@JP1(Hy0vxyieE3x$v5EN5C6ssJ*sR2p`&p5wKvkTpkO zIc`uCcxM8VyYGHm?%Sthj)VkKR^hgpwal~h=*bZ>Cel*;RKc&WUuMmjfn6C|z5kx& zRo53EU3fsrS(rN3tn9g7Yf#BUT|6b;bG3#Uc?LOd;lx zZbvxPnN8Y}`6a|76=F#4AsjJg(82KZ(5DAbNke}6_)d~5$@{LiTl1a%=5!Xt3rwJh zg|;G&SOcf3neWA1L_Ico4NI;f^V}0j@}fzvL9(ZX!00#-W@ zC%o3{QPeBB<$22H&XZ8l7$@Oe@-K3c_}f3Dc&WQOJ$N- z3nt4ozDLEPMMgKCx>-qV7em!6NzUO`JW#0z{Q|g3a-ghc}$t4D4k*0Ed z+MdJBnITM=JqBSkHvC7?wGnC{^+iP^2KNSA-droEgQhs?6fJ-i*Q+3akI)n51kn68v z?%e^L8!ML-&WbK(W%)O+>fZ6wJm?26WAUce*VdliKA88sXD9&EeRFg8_?#z~f8PIM zHT~h8Q9WD$1at^DmrrmrF@dUChQpa!NX@2_SyOTg4CC@Ki6vnwo)I6`|kc+=f%=bSzpcv`HO_(LV9 zG5*xG!8wPw(8m=anNiNjxKB8@r%R!u(!U+YvBz@Xu={^S!p}Vxtauv!gHBMH)n?-? zS%Of4%5hHsClg()W!x>T^sHgpIN?w#K_Lu?Kr%Sgq*uHT_B2)mo@sJoNR1D>GJA3m zs-PP&%ew)Rc|I#KWnK5dt4JzE0(`nbW)jF!*{=NQkh|~P4`8kSw|F`Fr|N%so80|4 zv+$El#WS)#anAs1_|{vm^Wpk?SV>n-Om0;<-U$B z`2~}+L|(X{;#9aTB0nX+gBzH+BQ}dP2}4Rm{Bu8HPx%jSuq_j6Ap-2VUxE`f0S(ol zy{2W8WDR9ZfSMaOn(1ZK;PoO<7b$jAx0pe~1r9((jQ_i+MzXJj5ArApXD(2h0c1|O zj3B2x-I!m3TB3X{r^pE^trmfdg&cq;A%LPx0$WM@(*ZCC)y*OTvyUP*Rsw29`1WnggIJN1aS+%1b>iJQ*k93AJo&b?vG#Di-;NDo~S(tOEZJO6Zs zDm2Caw8^-cyPBCCrg~)Vx6GC13V5Xa3>&Xlq|{X8F4(b~mH?)4n;^p+G{Px8a_r|? zE79O>wTs>Yh&j|{sb9nepajAAUdVH6p)8tiDb{%gEo+UQ4Ue#!9ike`8`8|nZ61XT zAkiQJI^(}3-XSr~+Q-X@bsDl|HpUsM2gAHW)GCV-$(S6z%Pp{nOt0YSo$D*n{P21= z;7cLSVQ4`nogF?poM=r9E)BrU{jeirek#+a9nAOee7>GvUteF(=f1yU7x+l{$xf`! zFNPO=)i$94|5+VxNrUE|Y5%ER?5RUh;sE8P?yBN|f>6?`yq>?SK9J8X2 z=#isJR(m|y{oIQw$*4=_K)+=nGZbOgM5%E-0V~C&2{z}l*h{N0*`?KBX)u|qjpY*6(?4@pM-*{mz-&PVIlK#a*6JB6`<(dGO&CEpXzyXCEA|p_(N>qd8{*bQXuAN3QVkV(pnmdd67HO z=gPRW{e<+E;GEfi8ET&1w+N(F)`d3<$Ad)R-TZEL|4T$4Gpky2`pJ8{BpLF+L`rE# zdfz!%sf;h_OUXeQ6Th(6>YTR?KE9een6}3}GPxalWc=Tcy7@J8^9EME$T=i!EvFg7~c5OjJb3GY4D=qe8AHn zZjPl_pK{3ZtwB*;+% zti%d@p*@KiAB{jQ&vNf*nfb7W_evl&&w#0Qifz-vBvR8!)DE@$Gm%bTK`k%?@*u;|Ep&hZ_s^%oC_TEW&k3J$eAU?7w@93J{HG3 z6?eum{ca7vyHiY=V>AYDi1oAOAbV+wSnlwN>ApUgBoRD{R9Cp` zx?HM{@k+kFW)=TWbRL4^m_h^kCliTFqd!FIxw50qTLDTr)2dS|c~D4mmKU0-t|$8^ z7Yi>p(EdgVVvgat%$2E(V_v?!<_^yo_^1f{FcUF|L5}Dm4ZbKA0Szh+xJR-dg@z|j z9pWNx+)M?C&9zL7dkY&7TMdYL50da;0CetY746}+z$Z}y z=#k`52Ok(z;r*wi9(6#%)6D>o#RQadpBDx&lZd2fDwu|WvQdwy=3rzCd|DzTmjE;$ z7!>Gi*5yOklYEYZBS~$XR1H#bSNJ&5VK!%);a#2czR$ZCr{R$snjt8_I^4gOe8sk3 z-0AoA-1q0}I9`@J{YC*HT_{1bAS*9Aik~=hOz@a6DYA@PnSZoGXxSZW+p7eM>Em*A zb$E&xQ7y3U0;(AUS4{S zB1sLa5rzDg94_AMcuaab74oL|3oA-w81q(of(@3j!pM~`pl9Iv>hVmzuty`g`jm*I zm{UtAKRX8I7lUp4>8$bD2FMz%o&Nq!ld;zsXG4+^h{UYQoVuQ=a3mIe!9@kG0;zQB z5+U*~Fol|UW{ilPgoPWhh^dRaILxt?QZ6XPHws5Ls$)vpO|r-UU^Ygrd0GNi61Laq zpaymF`$Fc~O{A4&0(zzTf-~*A5St74iP}OU3Ec^t-wqKlbrRL_T5B}kJt?)V3yI-E zF=rY3B4e*x^By*07?vibj5jYcEp6NjO*sdq1ff8BU+x(i8F4bvnR~KP_^;5P!*a{_ zeLrHam*<`-pN~`*(0$`5=9)a%A(rJ^I~#^WxB$5MwC2XYOq!HirU^<9YtWv4*~v*Q zcY1*a^+xc6#QHawxG0muF-5$W1+7PVppnA3XFyEF;e;+VHYW$+W0iU*Dl7)-zMcGP zv~rSBkL3i;vNYEWoPD&ucOfsbHirrx%dA?7aef(Zt`U4g@huAt!w}D}EaG(GO@aM` zYQ1-h$=R|}6x^kKXVE5V!4tRF9y(z>TfazU{By2oo1|ue%LE>GPZGf8dz$AVJy|F| zsksLM3$|2Db5*cqGvR7*!3J?M1BIG8U@C}>S2T8-lvq=%aC4PVJHV)aUq^Fvpa>1l zb1sN5C*9I)&)dDmcqsohkR`sabkg?8)uK1dZ=6Qj6z|?@gbN^a0hELlSlkrm0Hv`J zUN8As{Dq3e{8u+gJyHe`bc334$drUCiU3%fBn?izeH#@ok+Ne0O(<;|FE_lzwh=ud zL0q1)Kag4e-7NQdN%7z8IF3Cw{Ce&CzQ6X}@+P--9OYzdu3ioXhg&4|UZnrLhVq<9 zvXABiX$8{BXHgWu9+)%7yFX(dN-Tz_Zc%hA zZoBj}ICpo?QmX+)s>JbVJI6Em3N z5TD@SODk*D>#~ef{}3VLo%5Yt((_|E#yu1JS(Ya(fOVW2^PEn!Gql`P<p9BXb+=(oNjS_BhL#(v3`_M#ohoHZBlDtP{x12hvNh_FPlW@>%D3 z8wM{Au%_>Efc~mK=hPD7KFQ3J1=G`@V>~Fv@#W3($hW0wo-B($>qZ&R2*xwtQAmGp zJ95m`GU`mbV*^1tb!$pjn3j+SkZr~v1Elw9L2SZf8UGV&z>diG9|oXA|JN%S10x3t zt&*ojs_yENtS}8h@jYupQ@i98t*oE5S&niIaOQAp#=_|{88t<+LV-}?OjKiAJe9~P zSUH-k$U7{lH4J0aQCt|AbUeM0ICpqfFG1qQq|)Z`(F8}w&7NsPFlc!~#oJ?||MIx&5 zb37muW0T=_8m9>|M=Isq=#Sy^<&Px2oaHEp$J!0W z9ftp1!cP-rWYSx=9N)~K%Gju3D)6OTP1Af#69BPoJfI>&-1p|!H%A%Yq^C455o{X4 z8h0H&gD&2bzx2ikBDmhukU7{F#yWu0afLgc9~`ETv?L+^*CxRxe;JRBIa@$1>(A2* zmu3z2AOI8DGxCQo8?J#8_@#3!)onS6b9g3;8yx%EPo-!Cv*F7YE_LlaoC`_fsbO0G zh18!FNdp(Rcxxq~kyT(SYHn%(VBAa#kjx7a+g@1UlmsgA9sdNiE{RyR$~luC4gs!-m9a)bBSz8J1P~ve*Lj#%hnmK*A>Q*d zeSc0VXA^?amvigj_nM>Ua54CmxgxzPs-Z0UdN*XDwye z+w$(791CQl<_$21(#O1A-Y9ae(y2xhbK4}evd)3%_0)az<{dx$$+7Upb45?*=M7cM znyEQ^th0C4#0Img1jBN36Q~8ko47{2zi4NU2=}fl9H9$G_Rur~TZYjaOnOELIqMe8 zF|Dz2AqGWTIFT|H89Z|fg&Zny_|^bWw$An?F9^N}4g4wsY3=|L8n2=y@!|uVL%fc8 zuh7s`f~PLAC$VSm#=U!$QHk?YCcV!JkrULI3rVAH zV^}Ktb8h{JoXPc zBj-A$O|Hx=y!J(MENf18kI;n>hROmtA0m7z`pw$ne)<1*_O}bNqddS53ItBVPc3*_WSV4C<3rsa*>Q8t?8ROj}Z}h2* zzdu`l0R4nC1hQMCJu$WX!Ay~b+m;A59AbL5=3YIE31Pp>?oy{s}1?tacBaL_6D@_TJG1^_60|7sZNj@gzF9Y#S^g# z&h_5`#ldGuDRs|kqFE>m!&-rewNU3P5G!NFs;>*RHwoQ8GAvjTdV>lv-F_CBA0Dv& zfujm+}Jk85CI%o82}W7y3O$Dkbth*HM=E)BVW*)MFUkcG1Xw+2ayXFqNm40#$XGN zq|g+aQu8;PV_LPflf>?Y)C0L!tc@8M8D(=w>HreQErFKv+@ zWv4JH`>s70FT@Yk>bc20BSS!8iJW-Pw@J}5OL&a3n{pJBjR!e-FRB6#U!=N(1#F~u z9QWHNUrUmp)yBRjiz~+ZifPK*70+SzW4io;_HW;LF&EBVinMY(s?%1(vM;RIfnYja>dZ)Z|QhGxs8);Qc{6yUH7mJnH8`HBAeG0>n7>Hp)|3Hf|ttxw1W#H;uH z2@U)G#{+<#l(IkzU<0d%xgvWREzoADBo;FJ>rAAtq2C=F8;xq&^Cl_qxz+M{ppe1n z%ONc?-o6hEPfs)1x#9lH5$6pLu>E5Ym`UHVElgA|8)Zm6?EnZ!KYR7?pV`Xy-<^QH zZ>8Pv7%4w=dEGt0|G0~{oDYeD`X@aQ_txbfd+&R@KZ_XpcZkC1deqI6exa_aj$!GS z);5v35*IcW5Va3XMS}O`y~eAvbWcv&g_`U^S@{+Lof;0=N8UGf;^8q(4*dh9hDUD*V?91dCkI z%h4=)hJ^g&iq7Mt+<&QvSY_FjP%I)t)>@M#$ad5B+NCWZ9YfXLzm$K7gIxe(*C+tp zJv__axNY~Q5O#2uZb$w14gihawE#I=3p5}?6a*|pdjWbQxCZ~4RH6ObD_3H3 z1V0zLYBsP|)<6RZVVd{=)6uUD<<)7dg67+_^;{4Rzv%vtBhPaWqV46NEkn|O`+6Ly z3XOv%6}v1u?BIWpET!?}*+{o+^3m54cm*(gtk^#xb-j?p7gJJYKNtZmUTT^++P|)t z>i-vwXa4ouk@|7<6iq5|omBTW4Fh1hZDT&sB)VkEXJ*z=E+ThqT!HBD|5!a3xI5$# zHFsbGQJWp@ULm@t8b|az;sjl&GlV|)CT3?FcMA|{<@%MQiS|X6izCV1SR+lud9wi4 zGVi^PRxzabMND=E@tJ26(@u2a57!`2gFnw3`2+1IvKP96!D1Nrr)fj5JgNyZZxRxe zR{mgen~+GGOND+nNYAJ5Eon2b!Zq_Z75%gQpcQbe+K|=dN@r*3xJW}6y>YO3CEN3} zd%MUR1Q|Ue0vvxQ*M6Fh69^wMnEs|Z6i5mWm3G@uk>L1Q%>`JQHT?y#RwQ=q8oyWF z7|d?=AaJaenXzigJRsM~y-|BqtVAMrQ#sq6UW%i)PYA08vo;2_U31ir83p=w1I@de z{N>2oNgNk3gTR6NFJ~$-cTuS_{mc538|s5nr>2xP$>0w8?^rt4LrFz?Wf6vYcySVm zBgIhsq5Xa%O%FxA~$mITP0f+5gB6972vi({|eC&Z8use zy2GoOr41L%w${IQ&HtrZv!$_9u*uA1WR3i;4hqF;@0g7HY!p{!(rTE!#<~Ml##n{E zd*#|ub0v3&t!B^hBG}V6SuUPRnA;5%O5W3qZ zE%Si^S1AuL5}HD3`Vo)M=b?A?JwH~RCtkiJ4H&Z5Kg=gg9$Ypaq(i|!i*v6W9S;pV zNstAQDGj*o0>bh*7VK8^5xv{Wg#dbur#FZ9AYpbo!t ztJ3#q{<*K+i@}xZZ(*Bc=QL3t6W$q=Y$c_8-)gr|X)mD0zp{6%POY&8#9BR> zzlPrwB4pi~s3raNpu_RZuuvyd4@?Yz!PM#Vv>$W9=pJ95| zOU~z~ivS$<4(#vq_BwD-aV+_uHq^`Pg~>p(fhv)1#W8eqguIa!4zZ27 zNKNwvl4Am!ziw=<0NU^`Dxg6?R<2|_0Tm3~gSx)0kOxn@%ItYP^Vw=U6*E;@8*d_b z#JJr;L*gCZA@HxNqPS3rv%VEm;k+Iza__w&b4Bb}y=e79WTAY``MUS!o=~5V5Y?{> zvFBZDWjRw3)DmDqy0pcfID=k3!*!yF6S# z(i_Qu1Ih=un-OsQng2nkcW9qi$HHM4z$ifG?I~+v2rzv|O@%R*s9H@YVS0`Pf;}O> z>t$7SRP#ZkCpm2LLL+@?r|tQ1$oI7x_*wt49vC(k+nH~mz2}C<3Q7~uNah_h}{u|*dj{()J8sQn0zn257t~e^SYyU+0 zO!&sp5OfLL+tV+rVfP$hng=uyE?s9LyiI%n98F4nBX`Xw=8MXwvLiPbpE?zWM(5S* zZJ;p!W34Hn^p21y6(*?gCT!9D9eW-1-|qt>ZG{U%5BEM8(K94(y>iW3KnT>Li&@Jp zt{oMaQ7h?gcAoac5+?2`{`r;pAAm3*k)AmQ$6YErV1;A)efr=ba(S6$3hadh!>)lfcz9Mpj4x5(T<2K zko&Sx<+zAhfnazj5|InN!ZU^4P!%I1W(_v*P1Q*EKk2;R{2+ij?fF{EeyS*-(+$)* zJ(=W$e)Ou4eb1rNlQzyn)H7*l)8SEVrfvPwM02V?g@1t*=tGKbI--0j3FHt(Cb*+7 z07#!lt!&WPkMZnd(lM6cCv)IdMzTFtti3XjvEyjRcJdVVZwnv=W*ZR5U^$rD;T2%IubAvU;8aqK)c$+X{MpqNjtI5zzV> z#i(ICePMO?B|OI`T`6!-NXo9s#_f1M{XFt`ydj^mr1Hy2E*AH@jp0p`F#fj`IBpE=8 z;Q=p^nRvtGvzT2bZ3zx5v=Pxe2CMfB&)R4&BY$QM;0J<*$MgbYj8Ab;Xu{!#*C%Z3XO&zGo1AFDlUVL#24{KAt#T7>j=Td5{ zi^NgaoFr}1WhD1J^3Sg~8nL3;l|(NE(Iknk_w#uvG{H~WXx*%qJqi6;2>yE7XH5q| zbLqH#d<*+Ku={|HH-yzAryg?D3yxMNQ{x^NB=Aa8(e4ciBBHSJBUObd#ys zh1giJP}9mrZOu}bIj3yz%}ft#bc0sC&RC9uKSLBiona~|0xhlG)5bY1;U?tt2x+Pr znNvzxh=*KBgsl_QqM0?y8@`z9c07Z;sH^a+)EDAWKBthvClxZuisD0ge?(Ut)_BQq z&bm=uxt0EQP^ehUxn`tb89AmAbYW8QU1?9;CmaBOWWg#WD1{M6P05Vrgv_S2rm7Vt zpqvmHmhZkbb4P{$XU9CS_CJVAdF?2bZEb=(l1}mrYLnpPeq*7l-XGE z>HQhk=kwu8Qk(3QNN~I~FK#J7W=T1fr-s(FqWgZvdR(m`G+S2m10#XJg~$aXR)xK+ z6?$x-e$mQ;EpxpxQ$#Sj$Bo`RrKAi|5OJrC&1Qi)Nip95wRAKadAAWwUlgAx0OLQ*v`^f zu9l;goevG~3c|RSvoJejqKBDlPnop=WKGybhP1sg2Bb)gZss4Auf(ak2Yk@CRmD%# zA~O7x0_h$if_zM37;VO^f_WRr88$?$yXKf~!EirQXSxw3%zk18HVJJw2L$RVKh1b> zDwHR+&0@QQHWm5}d3+S%=|%#&dM$lIbWW}sj2A*HV1L%ydw)uyCz^DUpRU|=TWT5;W+a}2A1#fn+VGB<0?_H}!`(QF~6t20n-nomAaJYIa#Hb*A7Z+CYZ~jAm~zd&9%& z{nKxK_Z18Mr1I!Sfr!Qp1M~F=T_BJT;7=;pZ-l#g14YpZ)NUd)-+BkQV|gg*ehxm_0NjE)>^xC z-^{3`qI*{duHy9J_QF&%>tX z3IRu1sx3|A<9;%K6NGk-R?A8=g)PSEBS2^|zeSGblT7}7BpeYOt#NvPh7Szj^;VrT`^F3pQm_K^ zAvMj5{HjmB`5tp1XZpUp`*hV99r&>sqG_uvr#$byySs76E)#)ZN`!Pung7%hb;b(! z==@xf2wbw9lNHf^f0`LqGr`oX*`goK&@OSf^efX`@}h^p(gcKW&W^`i4^96fy6P(ry;HvSvx{pX-Iqn}2185EFG z$ZqX^0!*DN9~w~` z5A62SBX{XZnc1r|riTcO%m}&*UbAhO-dJGx7pThtfxD^yL?wEBbn$Qj4GV~F^as=x zVpJU7iTi)d&gjvUYt{0hE>Lhp>eFE*%Lz4Xk$hWv<=U}XT&7)%BI<;Tt+l6TQ&v*A zOouaj0@`uM?n9lF>Uz||A_mv1cR{ErBBcr^)pWflYZh`#Yjvlo&Td3sdYj=t0?N2o z5S%CvHBM6gf`OrrE)M7cxP@Q#p4mW}16QdpL`76=sEL6@_EdmcXgaIid!{66sJv_^ zmaCT_(yBw0kuw0m(P;=ODWgMCjg#@mY_j)@k6$IWKT6cGaul6gw}86Sjem@f4iC)n zb`ZTAFAc)oNt7=I83MPo9)-M-rpXTOl{@M3M5;sp=((zW-s)2T<8+K>%QSjZ>;d{F+K_Evc21|)8XNhnBstWVB)M!xg*1@GYu(mXv|7krz3)#H=8FMn z>D8}$J~_uXb6@T0+2V<<1)$aGJTw7$PwI;Z6d$4WX4@M2izUkR{#|9!}w=uml5bf{l zb%AVVpF!ZTmkD*HKW`d|X6 z2OT<*Qi#E;UX%FM0X6o8a3?}iNqo{5^_Zavyp=R(#Aj|8p2%_j!L@R$ia5WX=iV5T z!{K6fsv8miSEKybA>6NKNOTZ%b>rNbA28Cw+yI#fj(ag11*HI5A(MLrXU~a z@!k)HE`QOoo4sF4 zEAKTDOr^eCe4;xU`ME*sfSlp(qn+$?0H=ghwx8+~-I&%Y-GGk+!`5PoVgL(2tuhN` zd+FFor007v{4-`fAdH+(?kIF11xGs!F!>E4%5>jEtbzW~9o>?8RRs@1i1q-jE@r2> z1GOLqAoqWV_m}pcoGDy;Qr04I8?`RGAK8-@X2ANMA1kHgsM_b~%ah=FQ4;CIXD}VW zPq!)(qesT@Emf|mA8AciU1EzTUT5cvlR8-N5ujV0+(9(c%I~s!^e-Hmm@<==CJ#Ak z`0R=G{y0J?lpP|Mc1xU9en0*n$l$@y^qB>yR&}3WwRL-xJnh`80)*Jg!AMLn6uBZR zGc$0d%3Eq$={N`=uAS>shCYzg#yfiX!#2)D;tS*jYy!u z@zhKK=@>^15~p$OO4;R{+n$<7i)hq|NwkB7z~%m&-H=D7Vx(AK^7s|^Z$Whg9^{aq z63xa)Y=?bm*Yx*|5paw5_qkrJf49m@Qo!`vmaI)ZPpfxFL||q8O#2B4fE%^f3Tj{( zS`mRMA|xKG5n~iv@@HoK3d4_oNbeN9DP%|@I2qd-_G~o)UjSAQO#HA1?2#00Axlm( zlTvN+%P-Nmk3dKMWY3nV=X>+uP8lvbAbvch_W=p1D9QPH{_YPN(<`XFTbmHjxBWEt zybLTH+7#yex9I`iOZn0; zo)isMj@y4ovZ5ia3m71f2tA?SxhftP!@7;yD`f;Eb|R3`8_yxJuf)!Ep;r5+7TmmF zVwto1YRT_s#Q;Qz!CEU?xd9Ge?Y(eqAaQk1pw~j;wZN65kon2Lhv@o=h>Z$ILevC= z)FQf3uOtVggv%r-7y=;x5kd1#m@;@67LbV$b7fR|ozH*4D&uc3Q6@3vyty<}twRVE zS{`G zna5LW6QxhrO)*Vd0Gr}kCE5`Sy;o#AMO)SC#o}1}P@gQ!$acl5ng~sUK2jUtL;*ju z@}-RYFj4D-`aEKgN_A55nfPh@oE}bx(bM`1UhB8JiF`DdNV2l#xPBC&DOi2mA<|FV z4pf$3ZhJr2pwHXmaU)9%XV$nsRRz*4lm6dsdqMt}OSdEcV1@L5r6KODHGg&@Hngf^ zZQ@mcKF*4qX6t3E8h;XK8&;m$9jJK%Eq9r@sk7H!gON1{Y~>~XP9)Z+-*83Ziz|T- zE(9_bhZ!i-t9O~1q4IPC^JtfXkLLZb4mi->I!$*=yW#zx=)*&st-Fy4EHizUFQ$n% zs;e*TuBj2f*+wylmOv_;w}2nH_ugpd9{v8aWy|RP8XIVuxafD`6fsgvldTl5m9+pe zg>0%o4w}S{+UmA?uKYH;{xt87j?x)s4xz%S`!#sT|8pCl7m^QlEw5LI0)$0`>`r^a z(5*FcMA?%bGv$c<2}s$i=1$CjP2ST#VX`G=Au?jGSXg^+5UdkSdgRIA@xXeJ zo6q-}J7cUAeaqu{)rS*H%mo08djhk}UaB#%jCWrSB|NWnk3WQ!;h)SzPWH}9I9cl; zkWK+5wxlsqybJue$0qM$ z!@G;=NbHnLk=e+)Ybhm#6^+y9Ne2X*F@>N+{O!B8@E(PM?p}RUdpjnSWS`~8i*!6< zYe8cmHkY^wM66g>YE21LMAD{g8>*g`=b-mV0DZm2dKYTH0mRA*2(6s%$Y^82i0KW` zl<0|6ID~43l}|tTdHRH&^GYUuXo3;LP1sM;Jz4P7{f{s6rkl=$L-V_3EN{2M%jF?G z2ante7NS(#26_ROsNL2YWwZAHZpRr4uRNJhNxMXWm!~QoHTah8CQspapABgovv{Q!oQ28 zesbLKgZ6tw^+8zWQ)4pa%8=|%33yUae>A+5M=;~K;SP8vxjFH$gILot!=VDB4T4XTF`E-5z)D|JP65m(l`(fP#ja>$`LBTpN4WT=d$et17~| z=+G0mrSF52N>|CujGei%EZJ4N%-1O9Mr2}Pe~=lUPom}mU>qA)GVxkCnCV}YuMA*E zpq0_un!8+2T2Fnbzda!V?iH?)bXJIz0a_4KU`lXE#x(7v2fu@@8SYDA zH#OL$^JP5E&~^P815UyHn_7SFJu3Y*o3)>Q{(hEwd5uAcn8id7$#dd2>xyLr*LDlW zGr4-Bo)=zk-Tx?(V*q%q=qdR^TK|uw^}JW-DMD{5#pVWlHSHi4R;_bA@-gO`w4E^l zA`L-BOh*A(8AO@3pBtnc~Eyqo@YoJbuiIQVmLEjEJYcM6;!F`J>9f$-Gm#^B~SWvOHb;A=H1| zdA#Bj{1?qo9B^bcfbr7ZUg7m-z&V$-E>J>@?gSEVE%?S!_f~E6r{82JYOmf{T7;e9 zfjjr=JvNvqSEOAw&7;PzhW!`@wff~5$l{pDjb4EwN_68&&Vz5VUBDG$ln7i=y&h{{ z)k*7P2O_XDq9YiQSfB1Cs!qV{6UY~LNIXnoI;*Yx-tE1ecABRl?^wMkS}T$snTN|) zPuz8Dxpw*w5r3GF(CA*fZpYN#jP&p9D~bBK_rCTe9|Ns*cZHv7=XX_7!flF9q6U7+K?B_S#3LI)Pk(S#aJ0KFQ-z^` zSud8aDsVq&Et$?B4iJ=0@w+nFQ=aK{)mB&QO6d|VM9r~T#aR*Hco?(XV=j(&2OF7w z{EmTo_7!jOyz>0@Ct}T@G+2ME%wbvfCi7~Wd|6HTyG9xC3+$`+8ya9-M^S1eq0Yx-(mCN3IonZ^Q>z*6uQu(r0}Hga8m#{)G=BGOuwR zK%mudJ0SMFvxZbMs*U7J#6`2==>R8_mm|#I1r+0FH~2dtiMx_|K3?4Si;_U1J6g% z|LBK*9PvCoOZ_1Fw?`giVj1$9!Y$u7uF3N!9q**VpCn?*&R$nGHzv(Mue;L@f|Xkk z2(U|?MTWIpe?O?v{kvwJaua#%iam-RmqP*>v7+}6NbC=;h`JM;T%tA!( zI8fuh0qni2&Cj0&=Ken3`<=8{dR$QT&0t}INAjO-DB zDn}-eEA!%gX}vVqNF%N$Mc#LkL_{jYq*c}0R)a6qL2JUnTR7tsKut7O#NI1bMo)j~ zMy0Cn40!9;e;kfM^RyS3y#Q$6TA^pHqyun@7?Vrow;o^#Bch`35Nk#5?l>`(j2224 z)JMHr4}Fl<^D%btBH?`8yTSEt>}DPUdVd}fe$={`B@ z+3~^N`e#iHtK4rm{aaI%XXYQJ7VaN_5P*9UQcpMhOi5m8E+6N?lwD)dZ9M|Zi1J$Q z{mT4QShDC^{RhO*to44VJaU#nwhfy-Hc&&qteEOy?JJJV=$QT*4*=EPpU;O*fWbom z_Dj#_Q;l(Ay%%#}N-twqhxMDz+aAo6r^ z67(h@gL`+h+oE1q7Eh5tuvJ*DcIPKg%qB>V)N0XIg1%xof!w=4OWVKln(Y*|u7dVf z6r0pL#*s0C7=vI^z+2!#G+zwavYNEz&1Yp&XIff{=3?ZUgalbReQPsKtf2X+Ra;m2 z{p0PQF#su|jKsBpwScT~ovl}OS!u9R)^mPCViW;!l$x;R-cUqcPYlZ1HAwBop0Q`e z*zFEutDsPBk{b~#E0C#I4DDrxvcjUtGUJ%T<1myAR(>2;?4gtzXaYhB4&71Lf5e)} zlBhDw7laAMn7R`0zVpC`n+a_J7Sn55Up@u6v_9(f zY4~S2IEw!{Nq1{1FRv5muf6ly)c$V-;BRB@#09Xi#LaC@%>tz}mu%R(F1$(I5HG|W!9&qGap&k-b!8Aw))c>|Cyhu>gRqHRl4k_TGzAf_g`U19y%Cfev78 zN}pAw+*E#IpBCrYLksl#A4|AhsGVIv56}2|a*P6ogp#cXvkvV5E70z+ zl`#y7icAOEzM)V{95QEJ-mZ{>-if3u%qMI1EHK#tEd6{?Xp6g}T|Wq%lfR4~`Zcmy zTKvLq*|qjmoBm6!)c_DY2TM_1Mpcx?0QI(fDw4Cclk-8f1%&m4t`B>+^{yeClaR39G1r^)_Oi%Q`nPmwBcq`FO!?@AM3(-BU z>+NYj(6B}pSl1Ho3wvW#aEA70p;JzP%au)cPYKM3LV_EKq?+H|KJDp|^6$ET`}(Q=iwkfu z@KY2}|6M}ohK_>%gd{|Y^xSo$yKuT2T+K68Go8;pZ!DM0()r8dmA4=K_36LI zd*i*G{lzx0y7{YyhqJmvIh!7#RI1AdQT*%Gc~t)VG-Ay@U2C}2NZ?fxwK8|^k6=IA z{GMWWxPI6hTO@VGl^>OWOZVu~EV z69O`S2KEjD81IiCX-X(+616CDO0r-XA{Lm<|SO=G(aUjB&OvginTg9LVkfKGo|WqwL?f2E#r`g0JH2(M(>nX8b)nc%2M|GcMV#Bg9DwH zWV%xt8`VkUa zh{%izMyln4BJl;Au~phlS3#+08=ntyBR{!zu21$tH03?$A&h1C(df}p;VW0&)`m=N zWRkho%BV?kHMStC2}f#CH96EvkMOZ z18zgeN|YU^74oqJ=yF=ZfFh}kjJ1&~D+Cl#T1~W*GFHT1tDnt1v%|0|zAoCpmF`GC zkBi}nPcLl*vO-IZeKyUU+WHSYT&Fs2DGQAJYrjXUI-i>PJ+0GXwPcRsydh@jr;-@D z!X8eiu(Yf_W4^x@&eK*(%D@A+Em|C@56iw(hw!PRl2+%*dllRoQ_!DzbVjLf3G9XF zsQ~NR`=YDi)35+|{~|Bt3gqrFffBn`T|iZdfs|Fe#SN{wMYAqv*tNh;p@Ll-C zC$3W2k!aA^=v8$yGgpCsW`5Snl~^k?)?QV6hqu0=sn&$4UCe5gtrZK26|3VeR%UWH z+r4A0-ozw&q&ppv+-(~sH-m{L7qa-M`=b%3_F+T^Ywlj;Q`@uTxPVYm|1~%;*Js(E zUhb_I;G?EZ1dsBTtj<3I>XmK+zzG{1N9 zILpS4$^Mygp9HyJH46cVwHEe;2wV%%exTYhc5-G0zn~>=U*m_TCy=qE|F~x8oX7Xu;il$+{(f|0K6q|8mOtFk{CKso!UOSdnH!-y zX-I`}*1u)+?W@%P$IZ{eKA!!v>dfG>v{F9XU49en*n7vm_Wrb6w(naieT1e4Z6FrI z$f4>@uf`Jo1f{Q6LuUws*CCSsMLBDc$WPFnuvTGbX6C2YKSudHK6_>6C->T)&nI$! zaz(6OL#tKwhHvy7vBvM(n{Y+#k(QAaCLR&FSF~+_h;^u>1@|=`x5g_W8(=DRoOiu{Sty&Ag7r5@TP{@e$-#vjGM^rr(aA9%{2O012vY z)^;OcvRB#J-FtTI6>+T<7dJDzp;ZnCHJutZXvWzIeFhqxuhRU?wNcL39mrTKHhPbE zjP#Dp)}98cc7^ZvLa-OX{7>@UVU(q3L{ z2tVYSJ~=>a_Xk9p8emV|Zs1!dw-Z}xIXIZ@EtBEJVV={T=2-f0*%a!T|2$9PJWoBT z^unrx`E5sM=9T-}dDWz_>JkPHfYInmWDUG_a_w@f^NosP&i0Xyy{)bmK!sBte9WbX z6S;HOfbW$xC`|{@rw|YOr=piWxsji>elmBgPp(=hW@Xgo8dBU_H!MS*JC*NTy$8p_ zs#S?fG$*r3FZ4j zZ>k8T(!C~HMd+#y1wsYq$eN-(ZOzUZlA@6;L4LEC4epidpWa(DbO*;bL~x@Z&@JBC z(B?1AxBx5KG#+9)j{&uq?E}yTU&slzN|T4cD${dTd!hvcfh!IRD>8et754xE3X5aXMc~m7FEl=pmu$hrxL86j2Z?AkVKM3iei(8iOT3mdgO7%cjRVX>oShQQCiJdb;V z_nWGgC)VkpBs$BO-}A1xJ@S_P|8hL($Jrw2Mp@+uN`w{#s((ODn<0`3K&q)XGNj#G z4iiX)e|F45iA8mk%*+}~3uH3fL~YgxxzT};rBE&av1++G0pKUEy+3<@a(}M;d~)x# zR_@hD#9B3xD;5+Jq_fzo&~F!#ueEmXGrM-`cNr;h-)snK z!VZS!6xqABzuCPj?>5E%G8JER;^qRis8#|kUz|_7iqk%^5U~=eC`;;-pqR2xH@h!p(=B%84m8+j5sj*EFijX|2*g~@l@JHXQPxDvkV`EKT~QFPWxgi{*fttG^3)(yhVE@_D(jbzx8@@)|BT1iQpu#`>@6c8cjhiH z4FA#HQ98+@Nl!MjmqoX3BgCp+$@?R-d|0vN@(eEp^*@7+VbwzSXD4{H1Noznv%Ywwm0e2*2V@o=mWa#I0Q( za6NzXkLwtye!ixZNdERf?E8gVb;of-9GC3RaZ{YXoq&BLCaEU6-N?3Im#-zerxa(s zJh>_UOTpCBNXW3^Wq*s&t8*W&qX zKYRC*sA~>r843I$3|*!4HmK6KU|y9!OSEQk9>BFv{4bgH1^klce*IVIAnuM{Nx7Cq zUaKstNMa1}MuYv;q&adD!KO-d2c&Kivc9_&8(A9mss3w{vJQ#$2r8XbB|3+fu;IV1 z5>~BmBtmaw$B0=(`m@^faW*fx%bi$zU%BJjSM1*YTAOaJmbtM{tclyTG^OZFKLHPP zsRjLKK_QW@nuPx9XKjg7@mTfK0=arEfjYn{ib&wDzEx;*yn*xjxSc6 zpIegek2$~CZ;dcVO?@F4(syrk(W=K55XtMhzT z3c_(uHB}xLsaiaW{H%ZfIy1VMzqDr3zn$2LQva`wE3tR%pNLvX1uL5aGSvH|*r7ss zXnk^QAo@D3xY`1&7Gk6! z-L+j;yMV5CO>kki1kqS_2&feU;O?x0Z;1KQPc&p@)pNIk$q$u;Muq6MzI7)lrSt5J z7xdz59Tb{LV%Qa*dI+aLUuu6)?Xb=j1T4>jplY zPz%i1?PIO!{-ZmNCm~sF4~8PGPL>Xce0a74KMr!Qi9UGnhPr>M!LL6!Q$2VKnj;_q z_6O~ZqSva<>uvGpgY~6rLlPM92eJWE9)|fGN|F(r|7mlZl1g7pd;kdH*15HS<-$!4(sfp{PSp z;2poLjCI>l@Yh4+bd0KIOQmyHcthNX`p#)DuehDs#dMk{wc2ZsVm+EsdXF|Q3IN)0 zPlTIEQV<||CZBTl`jaaC6vVzQb&{=}_2kTC6lg@xk_D@XRz5#A6 z9?fal!KYkJCLmYCz(%w%1-p97^xhe`cyIO0wbsg9E7w*RpOL>NR>^B@x=wXa_s1mk91{r~EGae9|2_=V5F#%mAwTw1;ssM4h>V;Mg6qOnU51$(Q zMqsUlz!J3y$E#9Haz{RbWZzz-j`(J{qG}}XNAOZ?dTeg1@y(0bdjn2e-Iu{ zgXZ9mM_v8T2UYEOOJbzHt==pbLwST!N))?i6`Udt#PkEPZ76(Hfg_1tA*|rOr`xZj z{SyEKNh5b}EQvJLv}>inW$#4R@}Ya@it_yAUa{j-;Q!IKffz^rfIT&5J2);F%guzf zZj(_5pSmi{0B{#j*UkY8Uh%?3jQ}_QwJ(BLIVf9^I0fWDq1pOG08LUK&3U7P|9&23 zow1XQV=_i{qDq_kIv`RaoKoTWZzHp!2?l?2*M?u&>VL%VB^A_GAqdEv$vLd&|b=eU5>61y!XU7IN3~ zna8I$*OCz2QX!0JZ$UX8G6ET~df^~0$GcjCjHu1R3^-ixlkWN;))urUSfcaa1Fw>! zz>kYZJI(GO9uyo_h={1`MRhTM4ivr}UaH4Wf5E)7&>dgv^Ny0?_uo^!znV2li$Vk} z_->{bQ}WbokW_p_o=x;e-sedBoazw*nKZ_Eb22KNJ(*EJ>^jv;Q=xN9C6z!DtP>Hv zoKMdQkTz3x0NUjljUhUxd}Q{r(Ty&fX=DvOa>%$A!(SQLiM7{W>)P?TVpkNyzS5fZk^VvT#6Q#vq!=d$;`1u1#hAfnaKALEIy7&eb*1VW!&R3uZg^VIr=~r)_Wa}~ZDjH1#Xszie z65jf1bO%s)XLlXLpQA&Y_lJq0l_qg^O$Ls&a@PVTD|)Yu9kKfTYu7ygT5HkXpA(~= zla=r~4Ny08MCmSl247`c0GVfF9Ic{OzJabrY94?VxZU~paici@#m%rT0A>CK)9+8e z+~HYr-3U`rj`vJ7T8HaAnQb*8FrxbzA~31wg#U-?m^bH?`2Oqt{UdkSiM{jMd37L4 zyKy`7UC4;&5w`A-MgWnR2`7DCsSELYib0J~E$R-W{G{KZHOvD4CV_DeCS8@tUK%lW z#>$9XpHcYLtH;eFT&(N|y*$EnjUFsdLb~^Oe)MtIItXc!iRlI{4TGtVrr=y8I((@W zuNCX_`Ov=ktOXJPqT)JDWiDvTUc!il9T1%ivg-$%t4kz}<&dj6CqHCjBe-2n6GMUg zl`Tu(y}x^ZuYJ`9mW{>%*u?7P@Xw5@2Q?S4FfPwIC=8%yNk_+Z=Dzm+M8sN~&d4@G zhhRd%HX!?y-qSTmMS%3m2FpMAnciFPQL~4_0u1ZPze0@Cp6KaTrF4kU!b0Yb z#R!ZsNmfmPL@;EFvT11#@)*R=WEU$7%ZgL-afe(c&>SkeK@G^8tj?8^{ao88kCNZD2~o z)YyL?l|g{_8I zq8GJDW52|)L5YrJpU!R%W~_d~Q1fPfId)SQOO{ew)XyA4-r4ovb2IK0JvJIBYlpxA zsz1S4_OgDplv~*#aqVkmtZPN=tF{pBSy*MvgqApEGj-k6U~kq&j3I7_&NhnxoSktx zzi6WHI~>#bchrHS?jdNKUh=fbGTvc0j5i!AO7Bazrhs-(ZvMTZkyj zy!}5xl_od{ra3uONgF7ljrZN(O?N)(I}y=aU1HlnOvKt7OC!M$t_Y8H89>AmB@l$9 z`w`hqyYGe?WjR4L5lrF8E#W>@lM&$BFCl+4R>!XVd_L>*`T4~9e11Ob^Z%9q2cOmM z{m#JhckrW;r%#g^w}zjpI8>LLWEIg2^j8}srA2Nb1~X+%sk*M~d+qDm^>J}zn`l~U zYUk7aZLnMgdZ}qNM@$b%%Wpei$N@Vu_7y9xE4Ys&c_ea`ww95(JH>QGxKx>rvTrTI zM56r`!~2SxY@j^{ferklK`u;f22;vzFHx|!WC=UG>R~=Ov%JNk?vW3d;FHcdEl%HnFu#qLBjv2xcl*>;CTD#m78waNN@x=u`@DbXQ}Un z)k`!I2xH5`JnD^*`oeiz_JoqgT`e=c)!<1M6w}x zlC0-QMC{soz>uCe3GxJybo-?tfZ+|@2@&5xq0So)_X7HYl57)WQT;98S>=RS>+@Nk z&*$^=SwBCY&u5kQ=c9Q{t2eXhth6q#uZIV443+b9)KK_DfY$n3p$DBNC6<|7r*ETH zXU&bCJ)X$TeTJ^QzK#3WwRg{E=piANcX2tolbcS0#;LT#0UT^}Fn?I+XFdlqi_GyzmJtIOH^qug;6ld2t*d;-~Xu80KT?M7X#If1B`DP;>gwIX2fa2~Nfp9rj21@-Im^Yi)n`FwspKP%QJ;`@O#3>$|V-)dZm7q*nMB z*6v*eV&ay9P7c#ynCIhS%jx+ifW*dLxpu9i3}Dp;Loq@YAE2jPtq^izx`Be(0O~i~ zn~3~#v^q0Uk6Iju_ov(8VUPLv^b^Sc1L@QO1XgeG7vQ2m=rQ5)V%1hh)fr0xDbJl) zzmc*b=IrED1FD5m`H=5OV*r!gqm=pbaR;OP$G6)r7O{VV(H{V>ii+pe?kgf?Oc^~P zCTscXiSU0Q>sf#&GX}{$LP_QHxbp#HuM|xiX5U}?;3~`GDYJe;mE)+VLjUBLWX%ak zwV`Q|GTG~7>1#7y_CN=EKkfwpxhO0FaK*x^jD&R2%&fInvL4CoEyFwrn~teCREY7c zviygehX81~T_3aB5Qh`9#F$^NENWSZUE|-sP5J&$K z6nXrwKK4}u02N-0+^rJ_J7p9~G5C!Ng+!53=H2fh=IMXSSZo1UHHV_S|Lq95F!Y>P zBxCZ?B?W9agNlz79Fg~nSC(QZj^jv2&S+$J;Jo$v9MNqK6H_|ICFQl;!t{&vHl|gf zH4{I8jUCeJ7jh$3=H6I{TztW)1u~OdWhl=)bP?4uK#c)VR_<{32b%xgg!VPZAt|aC zFYyn~JJ67MB>#%kVO08p@s_FEn>_zdy73pKCu+pq@UMYyJr!N(BiSS`#8g1q>;7># ziN)i}Q4)by_rK%)-s5eEtCKV@N0dvW7oVRIPHLv*0xIZgM}MP#G;G&TM6kke4L)w3 z*LZX zqnuI^z!e+UMJSJMBvbcMBvtj9GuC7d{K|M|SQG$amK3C;2mOVM@%HNhBWY=7*!$Y* z6*Xw4*aCKN_?MBaT;?@3%$V~cH-smLff|&w@(Hc1s)I9u$()_(zoJ4hp#)R0hM9S* zy5sNplOfl^!8zS}-ghP&$CZ)($3c(kAEkMOS%&zj1$5-H!HSAk(n@6Pox8wCwhtL` z7u=ss2YZ_z5@GiWfc-f!l$r85v}YC3N&j^!5g&W9^P4Zohw< zU%>$MNVXQFS&?|4k$_MStX4KycYv7AtHHBV6Sv0znFr8io`bk!*3R5S1|-l&5dVXF z64D5u$LB-L@6b-JQ@J9d<5w5fS|2jS3Jqeyxz$9Zu)piZ#z=*uRuF)ZSyNFqCPgnz z!+lRnZXF=eEBPdYdXl02*R`+T>-t{b-|zRkzpeh~Y%{m3Ej3yiAi_k*Dqt{cb2K9a z5cZ#y#OuK%y|jl+tlWDeBK8JwUDgTWBJZF`yPn~Sd$aF?yj|{|A0-amCl1Tp^}c33 zV~C(**Ye$_enQ$3n?tge{qj)Kb=o_|2PX{MupA6X?aO!vyyx@hf)g^GAdHKN$p%-|mP@mEa|bfGe^5^ZqWY=l{q^cmWROkP#&UyS*gYt`?=xw+O%II5Pq`z6sVBj;jso zz3z}aL1K-V>0SsfSb-+mpnfx0(%*_00if3m7{F3S(o2$L)Gk5q&I!#DF`?5ZM0Okw4)>^cwlh)t zEN{vsXzL`pNWUMRfrDZO-8|148|A=Y53RIAQl5Uc)QDJJ)`dSpuq@N*m2*>@idjxY zYRuoXwmC^Gq3vYbFnkwvxZ2;V2WbKUmtYGy`$Iq2b#P|FZMh*--<{APRymtaN{%nu zC=8uk_lH|Cb|>wrRIoX1(5f+QiJQGWy_7}i#HHJ#Vm`m$eSP=$Tdx0E?x)QJsnJxV z0|5)+YN?o3RAEMH!`g2!2?!fTAX@e+j72*;61Dda5E)khh=sV~YN80Pb=~9Jnzwvy z9VCGgf=qSdFvSYPpDb02`5ALyLkKzL7p08WLSWDj0V#^Ap+IiRK@`wO0NmIvV@2(A zU3F-1KK)*~%cGMEQN>hK1|cOrz<>q`lR_$~JoM0(#|*o5-=c#m*?i4v%< zc2xUf_Cu&maSswejS1TVB∨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] = tmp_mask[np.where(mask>0)] - full_img[np.where(mask>0)] = tmp_img[np.where(mask>0)] - - mask_sharp = cv2.GaussianBlur(mask_sharp, (0,0), sigmaX=1, sigmaY=1, borderType = cv2.BORDER_DEFAULT) - - full_mask = full_mask[:, :, np.newaxis] - mask_sharp = mask_sharp[:, :, np.newaxis] - - if self.use_sr and img_sr is not None: - img = cv2.convertScaleAbs(img_sr*(1-full_mask) + full_img*full_mask) - - elif possion_blending is True: - if bbox is not None: - y1, y2, x1, x2 = bbox - mask_bbox = np.zeros_like(mask_sharp) - mask_bbox[y1:y2 - 5, x1:x2] = 1 - full_img, ori_img, full_mask = [cv2.resize(x,(512,512)) for x in (full_img, ori_img, np.float32(mask_sharp * mask_bbox))] - else: - full_img, ori_img, full_mask = [cv2.resize(x,(512,512)) for x in (full_img, ori_img, full_mask)] - - img = Laplacian_Pyramid_Blending_with_mask(full_img, ori_img, full_mask, 6) - img = np.clip(img, 0 ,255) - img = np.uint8(cv2.resize(img, (width, height))) - - else: - img = cv2.convertScaleAbs(ori_img*(1-full_mask) + full_img*full_mask) - img = cv2.convertScaleAbs(ori_img*(1-mask_sharp) + img*mask_sharp) - - return img, orig_faces, enhanced_faces \ No newline at end of file