Repository: sensity-ai/dot Branch: main Commit: 64cb9db61047 Files: 127 Total size: 490.8 KB Directory structure: gitextract_wvjb3qsw/ ├── .flake8 ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── ask_a_question.md │ │ ├── bug_report.md │ │ ├── documentation.md │ │ └── feature_request.md │ ├── PULL_REQUEST_TEMPLATE.md │ └── workflows/ │ ├── build_dot.yaml │ └── code_check.yaml ├── .gitignore ├── .pre-commit-config.yaml ├── .yamllint ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── README.md ├── configs/ │ ├── faceswap_cv2.yaml │ ├── fomm.yaml │ ├── simswap.yaml │ └── simswaphq.yaml ├── docker-compose.yml ├── docs/ │ ├── create_executable.md │ ├── profiling.md │ └── run_without_camera.md ├── envs/ │ ├── environment-apple-m2.yaml │ ├── environment-cpu.yaml │ └── environment-gpu.yaml ├── notebooks/ │ └── colab_demo.ipynb ├── pyproject.toml ├── requirements-apple-m2.txt ├── requirements-dev.txt ├── requirements.txt ├── scripts/ │ ├── image_swap.py │ ├── metadata_swap.py │ ├── profile_simswap.py │ └── video_swap.py ├── setup.cfg ├── src/ │ └── dot/ │ ├── __init__.py │ ├── __main__.py │ ├── commons/ │ │ ├── __init__.py │ │ ├── cam/ │ │ │ ├── __init__.py │ │ │ ├── cam.py │ │ │ └── camera_selector.py │ │ ├── camera_utils.py │ │ ├── model_option.py │ │ ├── pose/ │ │ │ └── head_pose.py │ │ ├── utils.py │ │ └── video/ │ │ ├── __init__.py │ │ ├── video_utils.py │ │ └── videocaptureasync.py │ ├── dot.py │ ├── faceswap_cv2/ │ │ ├── __init__.py │ │ ├── generic.py │ │ ├── option.py │ │ └── swap.py │ ├── fomm/ │ │ ├── __init__.py │ │ ├── config/ │ │ │ └── vox-adv-256.yaml │ │ ├── face_alignment.py │ │ ├── modules/ │ │ │ ├── __init__.py │ │ │ ├── dense_motion.py │ │ │ ├── generator_optim.py │ │ │ ├── keypoint_detector.py │ │ │ └── util.py │ │ ├── option.py │ │ ├── predictor_local.py │ │ └── sync_batchnorm/ │ │ ├── __init__.py │ │ ├── batchnorm.py │ │ └── comm.py │ ├── gpen/ │ │ ├── __init__.py │ │ ├── __init_paths.py │ │ ├── align_faces.py │ │ ├── face_enhancement.py │ │ ├── face_model/ │ │ │ ├── __init__.py │ │ │ ├── face_gan.py │ │ │ ├── model.py │ │ │ └── op/ │ │ │ ├── __init__.py │ │ │ ├── fused_act.py │ │ │ ├── fused_act_v2.py │ │ │ ├── fused_bias_act.cpp │ │ │ ├── fused_bias_act_kernel.cu │ │ │ ├── upfirdn2d.cpp │ │ │ ├── upfirdn2d.py │ │ │ ├── upfirdn2d_kernel.cu │ │ │ └── upfirdn2d_v2.py │ │ └── retinaface/ │ │ ├── __init__.py │ │ ├── data/ │ │ │ ├── FDDB/ │ │ │ │ └── img_list.txt │ │ │ ├── __init__.py │ │ │ ├── config.py │ │ │ ├── data_augment.py │ │ │ └── wider_face.py │ │ ├── facemodels/ │ │ │ ├── __init__.py │ │ │ ├── net.py │ │ │ └── retinaface.py │ │ ├── layers/ │ │ │ ├── __init__.py │ │ │ ├── functions/ │ │ │ │ └── prior_box.py │ │ │ └── modules/ │ │ │ ├── __init__.py │ │ │ └── multibox_loss.py │ │ ├── retinaface_detection.py │ │ └── utils/ │ │ ├── __init__.py │ │ ├── box_utils.py │ │ ├── nms/ │ │ │ ├── __init__.py │ │ │ └── py_cpu_nms.py │ │ └── timer.py │ ├── simswap/ │ │ ├── __init__.py │ │ ├── configs/ │ │ │ ├── config.yaml │ │ │ └── config_512.yaml │ │ ├── fs_model.py │ │ ├── mediapipe/ │ │ │ ├── __init__.py │ │ │ ├── face_mesh.py │ │ │ └── utils/ │ │ │ ├── face_align_ffhqandnewarc.py │ │ │ └── mediapipe_landmarks.py │ │ ├── models/ │ │ │ ├── __init__.py │ │ │ ├── arcface_models.py │ │ │ ├── base_model.py │ │ │ ├── fs_networks.py │ │ │ ├── fs_networks_512.py │ │ │ └── models.py │ │ ├── option.py │ │ ├── parsing_model/ │ │ │ ├── __init__.py │ │ │ ├── model.py │ │ │ └── resnet.py │ │ └── util/ │ │ ├── __init__.py │ │ ├── norm.py │ │ ├── reverse2original.py │ │ └── util.py │ └── ui/ │ └── ui.py └── tests/ └── pipeline_test.py ================================================ FILE CONTENTS ================================================ ================================================ FILE: .flake8 ================================================ [flake8] max-line-length = 120 extend-ignore = E203 per-file-ignores = __init__.py:F401 ================================================ FILE: .github/ISSUE_TEMPLATE/ask_a_question.md ================================================ --- name: Ask a Question about: Ask a question about using dot labels: question --- ## :question: Ask a Question: ### Description: ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug Report about: Report bugs to improve dot labels: bug --- ## :bug: Bug Report ### Description: #### Actual Behavior: #### Expected Behavior: ================================================ FILE: .github/ISSUE_TEMPLATE/documentation.md ================================================ --- name: Documentation about: Report an issue related to dot documentation labels: documentation --- ## :memo: Documentation ### Description: ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature Request about: Submit a feature request for dot labels: feature --- ## :sparkles: Feature Request ### Description: ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ ## Description Fixes #(issue-number) ### Changelog: #### Added: - ... #### Updated: - ... #### Fixed: - ... #### Removed: - ... ================================================ FILE: .github/workflows/build_dot.yaml ================================================ name: build-dot on: push: branches: - main paths-ignore: - "**.md" pull_request: types: [opened, synchronize, reopened, ready_for_review] paths-ignore: - "**.md" jobs: build-and-test: if: github.event.pull_request.draft == false runs-on: ubuntu-latest steps: - name: Code Checkout uses: actions/checkout@v3 - name: Set up Python uses: actions/setup-python@v4 with: python-version: 3.8 cache: 'pip' cache-dependency-path: 'requirements*.txt' - name: Install dependencies run: | sudo apt-get update && sudo apt-get install -y ffmpeg libsndfile1-dev pip install -r requirements.txt pip install -e . - name: Unit Tests run: | pip install -c requirements-dev.txt --force-reinstall pytest pytest-cov pytest --cov=src --cov-report=term-missing:skip-covered --cov-fail-under=10 ================================================ FILE: .github/workflows/code_check.yaml ================================================ name: code-check on: push: branches: - main pull_request: types: [opened, synchronize, reopened, ready_for_review] jobs: code-check: if: github.event.pull_request.draft == false runs-on: ubuntu-latest steps: - name: Code Checkout uses: actions/checkout@v2 - name: Set up Python 3.8 uses: actions/setup-python@v2 with: python-version: 3.8 cache: 'pip' cache-dependency-path: 'requirements*.txt' - uses: actions/cache@v3 with: path: ~/.cache/pre-commit key: ${{ runner.os }}-pre-commit-${{ hashFiles('.pre-commit-config.yaml') }} - name: Code Check run: | pip install pre-commit pre-commit run --all --show-diff-on-failure ================================================ FILE: .gitignore ================================================ # repo ignores data/results/* saved_models/* *.patch # Created by https://www.toptal.com/developers/gitignore/api/python,macos,windows,linux # Edit at https://www.toptal.com/developers/gitignore?templates=python,macos,windows,linux ### Linux ### *~ # temporary files which can be created if a process still has a handle open of a deleted file .fuse_hidden* # KDE directory preferences .directory # Linux trash folder which might appear on any partition or disk .Trash-* # .nfs files are created when an open file is removed but is still being accessed .nfs* ### macOS ### # General .DS_Store .AppleDouble .LSOverride # Icon must end with two \r Icon # Thumbnails ._* # Files that might appear in the root of a volume .DocumentRevisions-V100 .fseventsd .Spotlight-V100 .TemporaryItems .Trashes .VolumeIcon.icns .com.apple.timemachine.donotpresent # Directories potentially created on remote AFP share .AppleDB .AppleDesktop Network Trash Folder Temporary Items .apdisk ### macOS Patch ### # iCloud generated files *.icloud ### Python ### # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ share/python-wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover *.py,cover .hypothesis/ .pytest_cache/ cover/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 db.sqlite3-journal # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder .pybuilder/ target/ # Jupyter Notebook .ipynb_checkpoints # IPython profile_default/ ipython_config.py # pyenv # For a library or package, you might want to ignore these files since the code is # intended to run in multiple environments; otherwise, check them in: # .python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. # However, in case of collaboration, if having platform-specific dependencies or dependencies # having no cross-platform support, pipenv may install dependencies that don't work, or not # install all needed dependencies. #Pipfile.lock # poetry # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. # This is especially recommended for binary packages to ensure reproducibility, and is more # commonly ignored for libraries. # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control #poetry.lock # pdm # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. #pdm.lock # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it # in version control. # https://pdm.fming.dev/#use-with-ide .pdm.toml # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm __pypackages__/ # Celery stuff celerybeat-schedule celerybeat.pid # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ .dmypy.json dmypy.json # Pyre type checker .pyre/ # pytype static type analyzer .pytype/ # Cython debug symbols cython_debug/ # PyCharm # JetBrains specific template is maintained in a separate JetBrains.gitignore that can # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ ### Windows ### # Windows thumbnail cache files Thumbs.db Thumbs.db:encryptable ehthumbs.db ehthumbs_vista.db # Dump file *.stackdump # Folder config file [Dd]esktop.ini # Recycle Bin used on file shares $RECYCLE.BIN/ # Windows Installer files *.cab *.msi *.msix *.msm *.msp # Windows shortcuts *.lnk # End of https://www.toptal.com/developers/gitignore/api/python,macos,windows,linux ================================================ FILE: .pre-commit-config.yaml ================================================ default_language_version: python: python3.8 repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.3.0 hooks: - id: check-json - id: check-toml - id: check-yaml args: [--allow-multiple-documents] - id: end-of-file-fixer - id: mixed-line-ending - id: trailing-whitespace args: [--markdown-linebreak-ext=md] exclude: "setup.cfg" - repo: https://github.com/psf/black rev: 22.6.0 hooks: - id: black - repo: https://github.com/PyCQA/flake8 rev: 6.0.0 hooks: - id: flake8 args: [--max-line-length=150, --extend-ignore=E203] - repo: https://github.com/PyCQA/isort rev: 5.12.0 hooks: - id: isort args: ["--profile", "black"] - repo: https://github.com/pre-commit/mirrors-mypy rev: v0.961 hooks: - id: mypy files: ^dot/ args: [--ignore-missing, --no-strict-optional] additional_dependencies: [types-pyyaml, types-requests] ================================================ FILE: .yamllint ================================================ --- yaml-files: - '*.yaml' - '*.yml' - .yamllint rules: braces: enable brackets: enable colons: enable commas: enable comments: level: warning comments-indentation: level: warning document-end: disable document-start: disable empty-lines: enable empty-values: disable hyphens: enable indentation: enable key-duplicates: enable key-ordering: disable line-length: disable new-line-at-end-of-file: enable new-lines: enable octal-values: disable quoted-strings: disable trailing-spaces: enable truthy: level: warning ================================================ FILE: CHANGELOG.md ================================================ # Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] * Fix fomm model download by @Ghassen-Chaabouni in https://github.com/sensity-ai/dot/pull/160 * Add video and image swap to the GUI by @Ghassen-Chaabouni in https://github.com/sensity-ai/dot/pull/116 ## [1.3.0] - 2024-02-19 ## What's Changed * Trace error in CLI and UI by @Ghassen-Chaabouni in https://github.com/sensity-ai/dot/pull/137 * Update Windows executable by @Ghassen-Chaabouni in https://github.com/sensity-ai/dot/pull/133 * Update colab notebook by @Ghassen-Chaabouni in https://github.com/sensity-ai/dot/pull/128 * Add a Docker container for dot by @Ghassen-Chaabouni in https://github.com/sensity-ai/dot/pull/95 * Fix of cusolver error on GPU by @Ghassen-Chaabouni in https://github.com/sensity-ai/dot/pull/110 * Update the GUI, PyTorch and the documentation by @Ghassen-Chaabouni in https://github.com/sensity-ai/dot/pull/107 **Full Changelog**: https://github.com/sensity-ai/dot/compare/1.2.0...1.3.0 ## [1.2.0] - 2023-07-20 ## What's Changed * Create a dot executable for windows by @Ghassen-Chaabouni in https://github.com/sensity-ai/dot/pull/92 * Add a graphical interface for dot by @Ghassen-Chaabouni in https://github.com/sensity-ai/dot/pull/85 * Update README and CONTRIBUTING by @giorgiop in https://github.com/sensity-ai/dot/pull/40 * Fix config paths in additional scripts under `scripts/` folder by @Ghassen-Chaabouni in https://github.com/sensity-ai/dot/pull/43 * Update README and add instructions for running dot with an Android emulator by @Ghassen-Chaabouni in https://github.com/sensity-ai/dot/pull/45 **Full Changelog**: https://github.com/sensity-ai/dot/compare/1.1.0...1.2.0 ## [1.1.0] - 2022-07-27 ## What's Changed * Update readme by @giorgiop in https://github.com/sensity-ai/dot/pull/6 * Add more press on README.md by @giorgiop in https://github.com/sensity-ai/dot/pull/7 * [ImgBot] Optimize images by @imgbot in https://github.com/sensity-ai/dot/pull/8 * Update README to Download Models from Github Release Binaries by @ajndkr in https://github.com/sensity-ai/dot/pull/19 * Update README + Add Github Templates by @ajndkr in https://github.com/sensity-ai/dot/pull/16 * Verify camera ID when running dot in camera mode by @ajndkr in https://github.com/sensity-ai/dot/pull/18 * Add Feature to Use Config Files by @ajndkr in https://github.com/sensity-ai/dot/pull/17 * ⬆️ Bump numpy from 1.21.1 to 1.22.0 by @dependabot in https://github.com/sensity-ai/dot/pull/25 * Update python version to 3.8 by @vassilispapadop in https://github.com/sensity-ai/dot/pull/28 * Requirements changes now trigger CI by @giorgiop in https://github.com/sensity-ai/dot/pull/27 * Fix python3.8 pip cache location in CI by @ajndkr in https://github.com/sensity-ai/dot/pull/29 * Fix `--save_folder` CLI Option by @vassilispapadop and @Ghassen-Chaabouni in https://github.com/sensity-ai/dot/pull/26 * Add contributors list by @ajndkr in https://github.com/sensity-ai/dot/pull/31 * Add Google Colab demo notebook by @ajndkr in https://github.com/sensity-ai/dot/pull/33 * Speed up SimSwap's `reverse2original` by @ajndkr and @Ghassen-Chaabouni in https://github.com/sensity-ai/dot/pull/20 * Add `bumpversion` for semantic versioning by @ajndkr in https://github.com/sensity-ai/dot/pull/34 * Update README with speed metrics by @giorgiop in https://github.com/sensity-ai/dot/pull/37 ## New Contributors * @giorgiop made their first contribution in https://github.com/sensity-ai/dot/pull/6 * @ghassen1302 made their first contribution in https://github.com/sensity-ai/dot/pull/6 * @imgbot made their first contribution in https://github.com/sensity-ai/dot/pull/8 * @ajndkr made their first contribution in https://github.com/sensity-ai/dot/pull/19 * @dependabot made their first contribution in https://github.com/sensity-ai/dot/pull/25 * @vassilispapadop made their first contribution in https://github.com/sensity-ai/dot/pull/28 **Full Changelog**: https://github.com/sensity-ai/dot/compare/1.0.0...1.1.0 ## [1.0.0] - 2022-06-04 * dot is open sourced **Full Changelog**: https://github.com/sensity-ai/dot/commits/1.0.0 [Unreleased]: https://github.com/sensity-ai/dot/compare/1.2.0...HEAD [1.2.0]: https://github.com/sensity-ai/dot/compare/1.1.0...1.2.0 [1.1.0]: https://github.com/sensity-ai/dot/compare/1.0.0...1.1.0 [1.0.0]: https://github.com/sensity-ai/dot/releases/tag/1.0.0 ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing When contributing to this repository, please refer to the following. ## Suggested Guidelines 1. When opening a pull request (PR), the title should be clear and concise in describing the changes. The PR description can include a more descriptive log of the changes. 2. If the pull request (PR) is linked to a specific issue, the PR should be linked to the issue. You can use the [Closing Keywords](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue) in the PR description to automatically link the issue. Merging a PR will close the linked issue. 3. This repository follows the [Google Python Style Guide](https://google.github.io/styleguide/pyguide.html) for code formatting. 4. If you are working on improving the speed of *dot*, please read first our guide on [code profiling](docs/profiling.md). ## Setup Dev-Tools 1. Install Dev Requirements ```bash pip install -r requirements-dev.txt ``` 2. Install Pre-Commit Hooks ```bash pre-commit install ``` ## CI/CD Run Unit Tests (with coverage): ```bash pytest --cov=src --cov-report=term-missing:skip-covered --cov-fail-under=10 ``` Lock Base and Dev Requirements (pre-requisite: `pip install pip-tools==6.8.0`): ```bash pip-compile setup.cfg pip-compile --extra=dev --output-file=requirements-dev.txt --strip-extras setup.cfg ``` ## Semantic Versioning This repository follows the [Semantic Versioning](https://semver.org/) standard. Bump a major release: ```bash bumpversion major ``` Bump a minor release: ```bash bumpversion minor ``` Bump a patch release: ```bash bumpversion patch ``` ================================================ FILE: Dockerfile ================================================ FROM nvidia/cuda:11.8.0-cudnn8-devel-ubuntu22.04 # copy repo codebase COPY . ./dot # set working directory WORKDIR ./dot ARG DEBIAN_FRONTEND=noninteractive # Install system dependencies RUN apt-get update && apt-get install -y --no-install-recommends \ # Needed by opencv libglib2.0-0 libsm6 libgl1 \ libxext6 libxrender1 ffmpeg \ build-essential cmake wget unzip zip \ git libprotobuf-dev protobuf-compiler \ && apt-get clean && rm -rf /var/lib/apt/lists/* # Install Miniconda RUN wget \ https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh \ && mkdir /root/.conda \ && bash Miniconda3-latest-Linux-x86_64.sh -b \ && rm -f Miniconda3-latest-Linux-x86_64.sh # Add Miniconda to the PATH environment variable ENV PATH="/root/miniconda3/bin:${PATH}" RUN conda --version # Install requirements RUN conda config --add channels conda-forge RUN conda install python==3.8 RUN conda install pip==21.3 RUN pip install onnxruntime-gpu==1.9.0 RUN pip install -r requirements.txt # Install pytorch RUN pip install --no-cache-dir torch==2.0.1+cu118 torchvision==0.15.2+cu118 torchaudio==2.0.2 --index-url https://download.pytorch.org/whl/cu118 # Install dot RUN pip install -e . # Download and extract the checkpoints RUN pip install gdown RUN gdown 1Qaf9hE62XSvgmxR43dfiwEPWWS_dXSCE RUN unzip -o dot_model_checkpoints.zip RUN rm -rf *.z* ENTRYPOINT /bin/bash ================================================ FILE: LICENSE ================================================ Copyright (c) 2022, Sensity B.V. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * 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. * 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. ================================================ FILE: README.md ================================================

the Deepfake Offensive Toolkit

[![stars](https://img.shields.io/github/stars/sensity-ai/dot)](https://github.com/sensity-ai/dot/stargazers) [![license](https://img.shields.io/badge/License-BSD_3--Clause-blue.svg)](https://github.com/sensity-ai/dot/blob/main/LICENSE) [![Python 3.8](https://img.shields.io/badge/python-3.8-blue.svg)](https://www.python.org/downloads/release/python-3812/) [![build-dot](https://github.com/sensity-ai/dot/actions/workflows/build_dot.yaml/badge.svg)](https://github.com/sensity-ai/dot/actions/workflows/build_dot.yaml) [![code-check](https://github.com/sensity-ai/dot/actions/workflows/code_check.yaml/badge.svg)](https://github.com/sensity-ai/dot/actions/workflows/code_check.yaml)
*dot* (aka Deepfake Offensive Toolkit) makes real-time, controllable deepfakes ready for virtual cameras injection. *dot* is created for performing penetration testing against e.g. identity verification and video conferencing systems, for the use by security analysts, Red Team members, and biometrics researchers. If you want to learn more about *dot* is used for penetration tests with deepfakes in the industry, read these articles by [The Verge](https://www.theverge.com/2022/5/18/23092964/deepfake-attack-facial-recognition-liveness-test-banks-sensity-report) and [Biometric Update](https://www.biometricupdate.com/202205/sensity-alleges-biometric-onboarding-providers-downplaying-deepfake-threat). dot *is developed for research and demonstration purposes. As an end user, you have the responsibility to obey all applicable laws when using this program. Authors and contributing developers assume no liability and are not responsible for any misuse or damage caused by the use of this program.*

## How it works In a nutshell, *dot* works like this ```mermaid flowchart LR; A(your webcam feed) --> B(suite of realtime deepfakes); B(suite of realtime deepfakes) --> C(virtual camera injection); ``` All deepfakes supported by *dot* do not require additional training. They can be used in real-time on the fly on a photo that becomes the target of face impersonation. Supported methods: - face swap (via [SimSwap](https://github.com/neuralchen/SimSwap)), at resolutions `224` and `512` - with the option of face superresolution (via [GPen](https://github.com/yangxy/GPEN)) at resolutions `256` and `512` - lower quality face swap (via OpenCV) - [FOMM](https://github.com/AliaksandrSiarohin/first-order-model), First Order Motion Model for image animation ## Running dot ### Graphical interface #### GUI Installation Download and run the dot executable for your OS: - Windows (Tested on Windows 10 and 11): - Download `dot.zip` from [here](https://drive.google.com/file/d/1_duaEs2SAUGfAvr5oC4V3XR-ZzBtWQXo/view), unzip it and then run `dot.exe` - Ubuntu: - ToDo - Mac (Tested on Apple M2 Sonoma 14.0): - Download `dot-m2.zip` from [here](https://drive.google.com/file/d/1KTRzQrl_AVpiFIxUxW_k2F5EsosJJ_1Y/view?usp=sharing) and unzip it - Open terminal and run `xattr -cr dot-executable.app` to remove any extended attributes - In case of camera reading error: - Right click and choose `Show Package Contents` - Execute `dot-executable` from `Contents/MacOS` folder #### GUI Usage Usage example: 1. Specify the source image in the field `source`. 2. Specify the camera id number in the field `target`. In most cases, `0` is the correct camera id. 3. Specify the config file in the field `config_file`. Select a default configuration from the dropdown list or use a custom file. 4. (Optional) Check the field `use_gpu` to use the GPU. 5. Click on the `RUN` button to start the deepfake. For more information about each field, click on the menu `Help/Usage`. Watch the following demo video for better understanding of the interface

### Command Line #### CLI Installation ##### Install Pre-requisites - Linux ```bash sudo apt install ffmpeg cmake ``` - MacOS ```bash brew install ffmpeg cmake ``` - Windows 1. Download and install Visual Studio Community from [here](https://visualstudio.microsoft.com/vs/community/) 2. Install Desktop development with C++ from the Visual studio installer ##### Create Conda Environment > The instructions assumes that you have Miniconda installed on your machine. If you don't, you can refer to this [link](https://docs.conda.io/projects/conda/en/latest/user-guide/install/index.html) for installation instructions. ###### With GPU Support ```bash conda env create -f envs/environment-gpu.yaml conda activate dot ``` Install the `torch` and `torchvision` dependencies based on the CUDA version installed on your machine: - Install CUDA 11.8 from [link](https://developer.nvidia.com/cuda-11-8-0-download-archive) - Install `cudatoolkit` from `conda`: `conda install cudatoolkit=` (replace `` with the version on your machine) - Install `torch` and `torchvision` dependencies: `pip install torch==2.0.1+ torchvision==0.15.2+ torchaudio==2.0.2 --index-url https://download.pytorch.org/whl/cu118`, where `` is the CUDA tag defined by Pytorch. For example, `pip install torch==2.0.1+cu118 torchvision==0.15.2+cu118 torchaudio==2.0.2 --index-url https://download.pytorch.org/whl/cu118` for CUDA 11.8. Note: `torch1.9.0+cu111` can also be used. To check that `torch` and `torchvision` are installed correctly, run the following command: `python -c "import torch; print(torch.cuda.is_available())"`. If the output is `True`, the dependencies are installed with CUDA support. ###### With MPS Support(Apple Silicon) ```bash conda env create -f envs/environment-apple-m2.yaml conda activate dot ``` To check that `torch` and `torchvision` are installed correctly, run the following command: `python -c "import torch; print(torch.backends.mps.is_available())"`. If the output is `True`, the dependencies are installed with Metal programming framework support. ###### With CPU Support (slow, not recommended) ```bash conda env create -f envs/environment-cpu.yaml conda activate dot ``` ##### Install dot ```bash pip install -e . ``` ##### Download Models - Download dot model checkpoints from [here](https://drive.google.com/file/d/1Y_11R66DL4N1WY8cNlXVNR3RkHnGDGWX/view) - Unzip the downloaded file in the root of this project #### CLI Usage Run `dot --help` to get a full list of available options. 1. Simswap ```bash dot -c ./configs/simswap.yaml --target 0 --source "./data" --use_gpu ``` 2. SimSwapHQ ```bash dot -c ./configs/simswaphq.yaml --target 0 --source "./data" --use_gpu ``` 3. FOMM ```bash dot -c ./configs/fomm.yaml --target 0 --source "./data" --use_gpu ``` 4. FaceSwap CV2 ```bash dot -c ./configs/faceswap_cv2.yaml --target 0 --source "./data" --use_gpu ``` **Note**: To enable face superresolution, use the flag `--gpen_type gpen_256` or `--gpen_type gpen_512`. To use *dot* on CPU (not recommended), do not pass the `--use_gpu` flag. #### Controlling dot with CLI > **Disclaimer**: We use the `SimSwap` technique for the following demonstration Running *dot* via any of the above methods generates real-time Deepfake on the input video feed using source images from the `data/` folder.

When running *dot* a list of available control options appear on the terminal window as shown above. You can toggle through and select different source images by pressing the associated control key. Watch the following demo video for better understanding of the control options:

## Docker ### Setting up docker - Build the container ``` docker-compose up --build -d ``` - Access the container ``` docker-compose exec dot "/bin/bash" ``` ### Connect docker to the webcam #### Ubuntu 1. Build the container ``` docker build -t dot -f Dockerfile . ``` 2. Run the container ``` xhost + docker run -ti --gpus all \ -e NVIDIA_DRIVER_CAPABILITIES=compute,utility \ -e NVIDIA_VISIBLE_DEVICES=all \ -e PYTHONUNBUFFERED=1 \ -e DISPLAY \ -v .:/dot \ -v /tmp/.X11-unix:/tmp/.X11-unix:rw \ --runtime nvidia \ --entrypoint /bin/bash \ -p 8080:8080 \ --device=/dev/video0:/dev/video0 \ dot ``` #### Windows 1. Follow the instructions [here](https://medium.com/@jijupax/connect-the-webcam-to-docker-on-mac-or-windows-51d894c44468) under Windows to set up the webcam with docker. 2. Build the container ``` docker build -t dot -f Dockerfile . ``` 3. Run the container ``` docker run -ti --gpus all \ -e NVIDIA_DRIVER_CAPABILITIES=compute,utility \ -e NVIDIA_VISIBLE_DEVICES=all \ -e PYTHONUNBUFFERED=1 \ -e DISPLAY=192.168.99.1:0 \ -v .:/dot \ --runtime nvidia \ --entrypoint /bin/bash \ -p 8080:8080 \ --device=/dev/video0:/dev/video0 \ -v /tmp/.X11-unix:/tmp/.X11-unix \ dot ``` #### macOS 1. Follow the instructions [here](https://github.com/gzupark/boot2docker-webcam-mac/blob/master/README.md) to set up the webcam with docker. 2. Build the container ``` docker build -t dot -f Dockerfile . ``` 3. Run the container ``` docker run -ti --gpus all \ -e NVIDIA_DRIVER_CAPABILITIES=compute,utility \ -e NVIDIA_VISIBLE_DEVICES=all \ -e PYTHONUNBUFFERED=1 \ -e DISPLAY=$IP:0 \ -v .:/dot \ -v /tmp/.X11-unix:/tmp/.X11-unix \ --runtime nvidia \ --entrypoint /bin/bash \ -p 8080:8080 \ --device=/dev/video0:/dev/video0 \ dot ``` ## Virtual Camera Injection Instructions vary depending on your operating system. ### Windows - Install [OBS Studio](https://obsproject.com/). - Run OBS Studio. - In the Sources section, press on Add button ("+" sign), select Windows Capture and press OK. In the appeared window, choose "[python.exe]: fomm" in Window drop-down menu and press OK. Then select Edit -> Transform -> Fit to screen. - In OBS Studio, go to Tools -> VirtualCam. Check AutoStart, set Buffered Frames to 0 and press Start. - Now `OBS-Camera` camera should be available in Zoom (or other videoconferencing software). ### Ubuntu ```bash sudo apt update sudo apt install v4l-utils v4l2loopback-dkms v4l2loopback-utils sudo modprobe v4l2loopback devices=1 card_label="OBS Cam" exclusive_caps=1 v4l2-ctl --list-devices sudo add-apt-repository ppa:obsproject/obs-studio sudo apt install obs-studio ``` Open `OBS Studio` and check if `tools --> v4l2sink` exists. If it doesn't follow these instructions: ```bash mkdir -p ~/.config/obs-studio/plugins/v4l2sink/bin/64bit/ ln -s /usr/lib/obs-plugins/v4l2sink.so ~/.config/obs-studio/plugins/v4l2sink/bin/64bit/ ``` Use the virtual camera with `OBS Studio`: - Open `OBS Studio` - Go to `tools --> v4l2sink` - Select `/dev/video2` and `YUV420` - Click on `start` - Join a meeting and select `OBS Cam` ### MacOS - Download and install OBS Studio for MacOS from [here](https://obsproject.com/) - Open OBS and follow the first-time setup (you might be required to enable certain permissions in *System Preferences*) - Run *dot* with `--use_cam` flag to enable camera feed - Click the "+" button in the sources section → select "Windows Capture", create a new source and enter "OK" → select window with "python" included in the name and enter OK - Click "Start Virtual Camera" button in the controls section - Select "OBS Cam" as default camera in the video settings of the application target of the injection ## Run dot with an Android emulator If you are performing a test against a mobile app, virtual cameras are much harder to inject. An alternative is to use mobile emulators and still resort to virtual camera injection. - Run `dot`. Check [running dot](https://github.com/sensity-ai/dot#running-dot) for more information. - Run `OBS Studio` and set up the virtual camera. Check [virtual-camera-injection](https://github.com/sensity-ai/dot#virtual-camera-injection) for more information. - Download and Install [Genymotion](https://www.genymotion.com/download/). - Open Genymotion and set up the Android emulator. - Set up dot with the Android emulator: - Open the Android emulator. - Click on `camera` and select `OBS-Camera` as front and back cameras. A preview of the dot window should appear. In case there is no preview, restart `OBS` and the emulator and try again. If that didn't work, use a different virtual camera software like `e2eSoft VCam` or `ManyCam`. - `dot` deepfake output should be now the emulator's phone camera. ## Speed ### With GPU Tested on a AMD Ryzen 5 2600 Six-Core Processor with one NVIDIA GeForce RTX 2070 ```example Simswap: FPS 13.0 Simswap + gpen 256: FPS 7.0 SimswapHQ: FPS 11.0 FOMM: FPS 31.0 ``` ### With Apple Silicon Tested on Macbook Air M2 2022 16GB ```example Simswap: FPS 3.2 Simswap + gpen 256: FPS 1.8 SimswapHQ: FPS 2.7 FOMM: FPS 2.0 ``` ## License *This is not a commercial Sensity product, and it is distributed freely with no warranties* The software is distributed under [BSD 3-Clause](LICENSE). *dot* utilizes several open source libraries. If you use *dot*, make sure you agree with their licenses too. In particular, this codebase is built on top of the following research projects: - - - - ## Contributing If you have ideas for improving *dot*, feel free to open relevant Issues and PRs. Please read [CONTRIBUTING.md](./CONTRIBUTING.md) before contributing to the repository. ## Maintainers - [@ghassen1302](https://github.com/ghassen1302) - [@vassilispapadop](https://github.com/vassilispapadop) - [@giorgiop](https://github.com/giorgiop) - [@AjinkyaIndulkar](https://github.com/AjinkyaIndulkar) - [@kjod](https://github.com/kjod) ## Contributors [![](https://img.shields.io/github/contributors-anon/sensity-ai/dot)](https://github.com/sensity-ai/dot/graphs/contributors) ## Run `dot` on pre-recorded image and video files - [Run *dot* on image and video files instead of camera feed](docs/run_without_camera.md) ## FAQ - **`dot` is very slow and I can't run it in real time** Make sure that you are running it on a GPU card by using the `--use_gpu` flag. CPU is not recommended. If you still find it too slow it may be because you running it on an old GPU model, with less than 8GB of RAM. - **Does `dot` only work with a webcam feed or also with a pre-recorded video?** You can use `dot` on a pre-recorded video file by [these scripts](docs/run_without_camera.md) or try it directly on [Colab](https://colab.research.google.com/github/sensity-ai/dot/blob/main/notebooks/colab_demo.ipynb). ================================================ FILE: configs/faceswap_cv2.yaml ================================================ --- swap_type: faceswap_cv2 model_path: saved_models/faceswap_cv/shape_predictor_68_face_landmarks.dat ================================================ FILE: configs/fomm.yaml ================================================ --- swap_type: fomm model_path: saved_models/fomm/vox-adv-cpk.pth.tar head_pose: true ================================================ FILE: configs/simswap.yaml ================================================ --- swap_type: simswap parsing_model_path: saved_models/simswap/parsing_model/checkpoint/79999_iter.pth arcface_model_path: saved_models/simswap/arcface_model/arcface_checkpoint.tar checkpoints_dir: saved_models/simswap/checkpoints ================================================ FILE: configs/simswaphq.yaml ================================================ --- swap_type: simswap parsing_model_path: saved_models/simswap/parsing_model/checkpoint/79999_iter.pth arcface_model_path: saved_models/simswap/arcface_model/arcface_checkpoint.tar checkpoints_dir: saved_models/simswap/checkpoints crop_size: 512 ================================================ FILE: docker-compose.yml ================================================ services: dot: build: context: . dockerfile: Dockerfile # Set environment variables, if needed environment: - PYTHONUNBUFFERED=1 - NVIDIA_DRIVER_CAPABILITIES=compute,utility - NVIDIA_VISIBLE_DEVICES=all # Preserve files across container restarts volumes: - .:/dot # Use NVIDIA runtime to enable GPU support in the container runtime: nvidia entrypoint: /bin/bash ports: - "8080:8080" container_name: dot stdin_open: true tty: true ================================================ FILE: docs/create_executable.md ================================================ # Create executable Create an executable of dot for different OS. ## Windows Follow these steps to generate the executable for Windows. 1. Run these commands ``` cd path/to/dot conda activate dot ``` 2. Get the path of the `site-packages` by running this command ``` python -c "import site; print(''.join(site.getsitepackages()))" ``` 3. Replace `path/to/site-packages` with the path of the `site-packages` and run this command ``` pyinstaller --noconfirm --onedir --name "dot" --add-data "src/dot/fomm/config;dot/fomm/config" --add-data "src/dot/simswap/models;dot/simswap/models" --add-data "path/to/site-packages;." --add-data "configs;configs/" --add-data "data;data/" --add-data "saved_models;saved_models/" src/dot/ui/ui.py ``` The executable files can be found under the folder `dist`. ## Ubuntu ToDo ## Mac Follow these steps to generate the executable for Mac. 1. Run these commands ``` cd path/to/dot conda activate dot ``` 2. Get the path of the `site-packages` by running this command ``` python -c "import site; print(''.join(site.getsitepackages()))" ``` 3. Replace `path/to/site-packages` with the path of the `site-packages` and run this comman ``` pyinstaller --noconfirm --onedir --name "dot" --add-data "src/dot/fomm/config:dot/fomm/config" --add-data "src/dot/simswap/models:dot/simswap/models" --add-data "path/to/site-packages:." --add-data "configs:configs/" --add-data "data:data/" --add-data "saved_models:saved_models/" src/dot/ui/ui.py ``` The executable files can be found under the folder `dist`. ================================================ FILE: docs/profiling.md ================================================ # Profiling Profiling should be carried out whenever significant changes are made to the pipeline. Profiling results are saved as `.txt` and `.prof` files. ## Scripts ### Profile SimSwap - `profile_simswap.py` This script profiles SimSwap pipeline on a single image pair. #### Basic Usage ```bash python profile_simswap.py ``` ## Visualisation Tools Apart from analysing the `.txt` profiling data, we visualise and explore the `.prof` profiling data with: * [snakeviz](#snakviz): * [gprof2dot](#gprof2dot): * [flameprof](#flameprof): ### SnakeViz #### Conda Installation ```bash conda install -c conda-forge snakeviz ``` #### Basic Usage ```bash snakeviz .prof --server ``` ### GProf2Dot #### Conda Installation ```bash conda install graphviz conda install -c conda-forge gprof2dot ``` #### Basic Usage ```bash python -m gprof2dot -f pstats .prof | dot -Tpng -o .png ``` ### FlameProf #### Pip Installation ```bash pip install flameprof ``` #### Basic Usage ```bash python -m flameprof .prof > .svg ``` ================================================ FILE: docs/run_without_camera.md ================================================ # Run dot on image and video files instead of camera feed ## Using Images ```bash dot -c ./configs/simswap.yaml --target data/ --source "data/" --save_folder test_local/ --use_image --use_gpu ``` ```bash dot -c ./configs/faceswap_cv2.yaml --target data/ --source "data/" --save_folder test_local/ --use_image --use_gpu ``` ## Using Videos ``` dot -c ./configs/simswap.yaml --target "/path/to/driving/video" --source "data/image.png" --save_folder test_local/ --use_gpu --use_video ``` ``` dot -c ./configs/fomm.yaml --target "/path/to/driving/video" --source "data/image.png" --save_folder test_local/ --use_gpu --use_video ``` ## Faceswap images from directory (Simswap) You can pass a `--source` folder with images and some `--target` images. Faceswapped images will be generated at `--save_folder` including a metadata json file. ```bash python scripts/image_swap.py --config --source --target --save_folder --limit 100 ``` ## Faceswap images from metadata (SimSwap) ```bash python scripts/metadata_swap.py --config --local_root_path --metadata --set --save_folder --limit 100 ``` ## Faceswap on video files (SimSwap) ```bash python scripts/video_swap.py -c -s -t -o -d 5 -l 5 ``` `-d 5` is optional to trim video in seconds `-l 5` is optional limit total swaps ================================================ FILE: envs/environment-apple-m2.yaml ================================================ --- name: dot channels: - conda-forge - defaults dependencies: - python=3.8 - pip=21.3 - pip: - -r ../requirements-apple-m2.txt ================================================ FILE: envs/environment-cpu.yaml ================================================ --- name: dot channels: - conda-forge - defaults dependencies: - python=3.8 - pip=21.3 - pip: - -r ../requirements.txt ================================================ FILE: envs/environment-gpu.yaml ================================================ --- name: dot channels: - conda-forge - defaults dependencies: - python=3.8 - pip=21.3 - pip: - onnxruntime-gpu==1.18.0 - -r ../requirements.txt ================================================ FILE: notebooks/colab_demo.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "view-in-github" }, "source": [ "\"Open" ] }, { "cell_type": "markdown", "metadata": { "id": "rOTJFaF9WIqg" }, "source": [ "# Deepfake Offensive Toolkit\n", "\n", "> **Disclaimer**: This notebook is primarily used for demo purposes on Google Colab.\n", "\n", "**Note**: We recommend running this notebook on Google Colab with GPU enabled.\n", "\n", "To enable GPU, do the following:\n", "\n", "`Click \"Runtime\" tab > select \"Change runtime type\" option > set \"Hardware accelerator\" to \"GPU\"`\n", "\n", "### Install Notebook Pre-requisites:\n", "\n", "We install the following pre-requisities:\n", "- `ffmpeg`\n", "- `conda` (via [condacolab](https://github.com/conda-incubator/condacolab))\n", "\n", "Note: The notebook session will restart after installing the pre-requisites.\n", "\n", "**RUN THE BELOW CELL ONLY ONCE.**\n", "\n", "**ONCE THE NOTEBOOK SESSION RESTARTS, SKIP THIS CELL MOVE TO \"STEP 1\" SECTION OF THIS NOTEBOOK**" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "GnL7GZXGWIqo" }, "outputs": [], "source": [ "# install linux pre-requisites\n", "!sudo apt install ffmpeg\n", "\n", "# install miniconda3\n", "!pip install -q condacolab\n", "import condacolab\n", "condacolab.install_miniconda()\n" ] }, { "cell_type": "markdown", "metadata": { "id": "9oI_egyVWIqq" }, "source": [ "## Step 1 - Clone Repository" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "LvZL-BD0WIqq" }, "outputs": [], "source": [ "import os\n", "os.chdir('/content')\n", "CODE_DIR = 'dot'\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "gTnnBM5xWIqr" }, "outputs": [], "source": [ "!git clone https://github.com/sensity-ai/dot.git $CODE_DIR\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "Hgx6JdrrWIqr" }, "outputs": [], "source": [ "os.chdir(f'./{CODE_DIR}')\n" ] }, { "cell_type": "markdown", "metadata": { "id": "Nb3q4HbSWIqs" }, "source": [ "## Step 2 - Setup Conda Environment\n", "\n", "**ONCE THE INSTALLATION IS COMPLETE, RESTART THE NOTEBOOK AND MOVE TO \"STEP 2\" SECTION OF THIS NOTEBOOK**" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "VkLiUqtbWIqt" }, "outputs": [], "source": [ "# update base conda environment: install python=3.8 + cudatoolkit=11.8\n", "!conda install python=3.8 cudatoolkit=11.8\n", "\n", "# install pip requirements\n", "!pip install llvmlite==0.38.1 onnxruntime-gpu==1.9.0\n", "!pip install torch==2.0.1+cu118 torchvision==0.15.2+cu118 torchaudio==2.0.2 --index-url https://download.pytorch.org/whl/cu118\n", "!pip install -r requirements.txt\n", "\n", "# install dot\n", "!pip install -e .\n" ] }, { "cell_type": "markdown", "metadata": { "id": "cuCaEkOiWIqy" }, "source": [ "## Step 2 - Download Pretrained models" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "RVQqmGmsWIqy" }, "outputs": [], "source": [ "%cd /content/dot\n", "\n", "# download binaries\n", "!gdown 1Qaf9hE62XSvgmxR43dfiwEPWWS_dXSCE\n", "\n", "# unzip binaries\n", "!unzip dot_model_checkpoints.zip\n", "\n", "# clean-up\n", "!rm -rf *.z*\n" ] }, { "cell_type": "markdown", "metadata": { "id": "IEYtimAjWIqz" }, "source": [ "## Step 3: Run dot on image and video files instead of camera feed\n", "\n", "### Using SimSwap on Images\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "cA0H6ynvWIq0" }, "outputs": [], "source": [ "!dot \\\n", "-c ./configs/simswap.yaml \\\n", "--target \"data/\" \\\n", "--source \"data/\" \\\n", "--save_folder \"image_simswap_output/\" \\\n", "--use_image \\\n", "--use_gpu\n" ] }, { "cell_type": "markdown", "metadata": { "id": "MKbRDeSAWIq0" }, "source": [ "### Using SimSwap on Videos" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "rJqqmy2vD8uf" }, "outputs": [], "source": [ "!dot \\\n", "-c ./configs/simswap.yaml \\\n", "--source \"data/\" \\\n", "--target \"data/\" \\\n", "--save_folder \"video_simswap_output/\" \\\n", "--limit 1 \\\n", "--use_video \\\n", "--use_gpu" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "oBJOJ2NWWIq1" }, "outputs": [], "source": [ "!python scripts/video_swap.py \\\n", "-s \"data/\" \\\n", "-t \"data/\" \\\n", "-o \"video_simswap_output/\" \\\n", "-d 5 \\\n", "-l 1\n" ] } ], "metadata": { "accelerator": "GPU", "colab": { "include_colab_link": true, "provenance": [] }, "gpuClass": "standard", "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3" } }, "nbformat": 4, "nbformat_minor": 0 } ================================================ FILE: pyproject.toml ================================================ [build-system] requires = [ "setuptools>=42", "wheel", ] build-backend = "setuptools.build_meta" [tool.pytest.ini_options] filterwarnings = ["ignore:.*"] ================================================ FILE: requirements-apple-m2.txt ================================================ # # This file is autogenerated by pip-compile with python 3.8 # To update, run: # # pip-compile setup.cfg # absl-py==1.1.0 # via mediapipe attrs==21.4.0 # via mediapipe certifi==2023.7.22 # via requests chardet==4.0.0 # via requests click==8.0.2 # via dot (setup.cfg) cycler==0.11.0 # via matplotlib dlib==19.19.0 # via dot (setup.cfg) face-alignment==1.3.3 # via dot (setup.cfg) flatbuffers==2.0 # via onnxruntime fonttools==4.43.0 # via matplotlib idna==2.10 # via requests imageio==2.19.3 # via scikit-image kiwisolver==1.4.3 # via matplotlib kornia==0.6.5 # via dot (setup.cfg) llvmlite==0.38.1 # via numba matplotlib==3.5.2 # via mediapipe mediapipe-silicon # via dot (setup.cfg) mediapipe==0.10.3 networkx==2.8.4 # via scikit-image numba==0.55.2 # via face-alignment numpy==1.22.0 # via # dot (setup.cfg) # face-alignment # imageio # matplotlib # mediapipe # numba # onnxruntime # opencv-contrib-python # opencv-python # pywavelets # scikit-image # scipy # tifffile # torchvision onnxruntime==1.15.1 # via dot (setup.cfg) opencv-contrib-python==4.5.5.62 # via # dot (setup.cfg) # mediapipe opencv-python==4.5.5.62 # via # dot (setup.cfg) # face-alignment packaging==21.3 # via # kornia # matplotlib # scikit-image pillow==10.0.1 # via # dot (setup.cfg) # imageio # matplotlib # scikit-image # torchvision protobuf==3.20.2 # via # dot (setup.cfg) # mediapipe # onnxruntime pyparsing==3.0.9 # via # matplotlib # packaging python-dateutil==2.8.2 # via matplotlib pywavelets==1.3.0 # via scikit-image pyyaml==5.4.1 # via dot (setup.cfg) requests==2.31.0 # via dot (setup.cfg) scikit-image==0.19.1 # via # dot (setup.cfg) # face-alignment scipy==1.10.1 # via # dot (setup.cfg) # face-alignment # scikit-image six==1.16.0 # via # mediapipe # python-dateutil tifffile==2022.5.4 # via scikit-image torch==2.0.1 # via # dot (setup.cfg) # face-alignment # kornia # torchvision torchvision==0.15.2 # via dot (setup.cfg) tqdm==4.64.0 # via face-alignment typing-extensions==4.3.0 # via torch urllib3==1.26.18 # via requests wheel==0.38.1 # via mediapipe # The following packages are considered to be unsafe in a requirements file: # setuptools ================================================ FILE: requirements-dev.txt ================================================ # # This file is autogenerated by pip-compile with Python 3.8 # by the following command: # # pip-compile --extra=dev --output-file=requirements-dev.txt --strip-extras setup.cfg # absl-py==1.1.0 # via mediapipe altgraph==0.17.3 # via pyinstaller asttokens==2.0.5 # via stack-data atomicwrites==1.4.1 # via pytest attrs==21.4.0 # via # mediapipe # pytest backcall==0.2.0 # via ipython black==22.3.0 # via dot (setup.cfg) bump2version==1.0.1 # via bumpversion bumpversion==0.6.0 # via dot (setup.cfg) certifi==2023.7.22 # via requests cffi==1.15.1 # via sounddevice cfgv==3.3.1 # via pre-commit charset-normalizer==3.2.0 # via requests click==8.0.2 # via # black # dot (setup.cfg) colorama==0.4.6 # via # click # ipython # pytest # tqdm coloredlogs==15.0.1 # via onnxruntime-gpu coverage==6.4.2 # via # coverage # pytest-cov customtkinter==5.2.0 # via dot (setup.cfg) cycler==0.11.0 # via matplotlib darkdetect==0.8.0 # via customtkinter decorator==5.1.1 # via # ipdb # ipython distlib==0.3.4 # via virtualenv dlib==19.19.0 # via dot (setup.cfg) executing==0.8.3 # via stack-data face-alignment==1.4.1 # via dot (setup.cfg) filelock==3.7.1 # via # torch # virtualenv flake8==3.9.2 # via dot (setup.cfg) flatbuffers==2.0 # via # mediapipe # onnxruntime-gpu fonttools==4.43.0 # via matplotlib humanfriendly==10.0 # via coloredlogs identify==2.5.1 # via pre-commit idna==2.10 # via requests imageio==2.19.3 # via scikit-image iniconfig==1.1.1 # via pytest ipdb==0.13.9 # via dot (setup.cfg) ipython==8.10.0 # via # dot (setup.cfg) # ipdb isort==5.12.0 # via dot (setup.cfg) jedi==0.18.1 # via ipython jinja2==3.1.3 # via torch kiwisolver==1.4.3 # via matplotlib kornia==0.6.5 # via dot (setup.cfg) llvmlite==0.38.1 # via numba markupsafe==2.1.3 # via jinja2 matplotlib==3.5.2 # via mediapipe matplotlib-inline==0.1.3 # via ipython mccabe==0.6.1 # via flake8 mediapipe==0.10.3 # via dot (setup.cfg) mpmath==1.3.0 # via sympy mypy-extensions==0.4.3 # via black networkx==2.8.4 # via # scikit-image # torch nodeenv==1.7.0 # via pre-commit numba==0.55.2 # via face-alignment numpy==1.22.0 # via # dot (setup.cfg) # face-alignment # imageio # matplotlib # mediapipe # numba # onnxruntime-gpu # opencv-contrib-python # opencv-python # pywavelets # scikit-image # scipy # tifffile # torchvision onnxruntime-gpu==1.18.0 # via dot (setup.cfg) opencv-contrib-python==4.5.5.62 # via # dot (setup.cfg) # mediapipe opencv-python==4.5.5.62 # via # dot (setup.cfg) # face-alignment packaging==21.3 # via # kornia # matplotlib # onnxruntime-gpu # pytest # scikit-image parso==0.8.3 # via jedi pathspec==0.9.0 # via black pefile==2023.2.7 # via pyinstaller pickleshare==0.7.5 # via ipython pillow==10.0.1 # via # dot (setup.cfg) # imageio # matplotlib # scikit-image # torchvision platformdirs==2.5.2 # via # black # virtualenv pluggy==1.0.0 # via pytest pre-commit==2.19.0 # via dot (setup.cfg) prompt-toolkit==3.0.30 # via ipython protobuf==3.20.2 # via # dot (setup.cfg) # mediapipe # onnxruntime-gpu pure-eval==0.2.2 # via stack-data py==1.11.0 # via pytest pycodestyle==2.7.0 # via flake8 pycparser==2.21 # via cffi pyflakes==2.3.1 # via flake8 pygments==2.15.0 # via ipython pyinstaller==5.13.1 # via dot (setup.cfg) pyinstaller-hooks-contrib==2023.5 # via pyinstaller pyparsing==3.0.9 # via # matplotlib # packaging pyreadline3==3.4.1 # via humanfriendly pytest==7.1.2 # via # dot (setup.cfg) # pytest-cov pytest-cov==3.0.0 # via dot (setup.cfg) python-dateutil==2.8.2 # via matplotlib pywavelets==1.3.0 # via scikit-image pywin32-ctypes==0.2.2 # via pyinstaller pyyaml==5.4.1 # via # dot (setup.cfg) # pre-commit requests==2.31.0 # via # dot (setup.cfg) # torchvision scikit-image==0.19.1 # via # dot (setup.cfg) # face-alignment scipy==1.10.0 # via # dot (setup.cfg) # face-alignment # scikit-image six==1.16.0 # via # asttokens # python-dateutil # virtualenv sounddevice==0.4.6 # via mediapipe stack-data==0.3.0 # via ipython sympy==1.12 # via # onnxruntime-gpu # torch tifffile==2022.5.4 # via scikit-image toml==0.10.2 # via # ipdb # pre-commit tomli==2.0.1 # via # black # coverage # pytest torch==2.0.1 # via # dot (setup.cfg) # face-alignment # kornia # torchvision torchvision==0.15.2 # via dot (setup.cfg) tqdm==4.64.0 # via face-alignment traitlets==5.3.0 # via # ipython # matplotlib-inline types-pyyaml==6.0.10 # via dot (setup.cfg) typing-extensions==4.3.0 # via # black # torch urllib3==1.26.18 # via requests virtualenv==20.15.1 # via pre-commit wcwidth==0.2.5 # via prompt-toolkit # The following packages are considered to be unsafe in a requirements file: # setuptools ================================================ FILE: requirements.txt ================================================ # # This file is autogenerated by pip-compile with Python 3.8 # by the following command: # # pip-compile setup.cfg # absl-py==1.1.0 # via mediapipe attrs==21.4.0 # via mediapipe certifi==2023.7.22 # via requests cffi==1.15.1 # via sounddevice charset-normalizer==3.2.0 # via requests click==8.0.2 # via dot (setup.cfg) colorama==0.4.6 # via # click # pytest # tqdm coloredlogs==15.0.1 # via onnxruntime-gpu customtkinter==5.2.0 # via dot (setup.cfg) cycler==0.11.0 # via matplotlib darkdetect==0.8.0 # via customtkinter dlib==19.19.0 # via dot (setup.cfg) exceptiongroup==1.1.2 # via pytest face-alignment==1.4.1 # via dot (setup.cfg) filelock==3.12.2 # via torch flatbuffers==2.0 # via # mediapipe # onnxruntime-gpu fonttools==4.43.0 # via matplotlib humanfriendly==10.0 # via coloredlogs idna==2.10 # via requests imageio==2.19.3 # via scikit-image iniconfig==2.0.0 # via pytest jinja2==3.1.3 # via torch kiwisolver==1.4.3 # via matplotlib kornia==0.6.5 # via dot (setup.cfg) llvmlite==0.38.1 # via numba markupsafe==2.1.3 # via jinja2 matplotlib==3.5.2 # via mediapipe mediapipe==0.10.3 # via dot (setup.cfg) mpmath==1.3.0 # via sympy networkx==2.8.4 # via # scikit-image # torch numba==0.55.2 # via face-alignment numpy==1.22.0 # via # dot (setup.cfg) # face-alignment # imageio # matplotlib # mediapipe # numba # onnxruntime-gpu # opencv-contrib-python # opencv-python # pywavelets # scikit-image # scipy # tifffile # torchvision onnxruntime-gpu==1.18.0 # via dot (setup.cfg) opencv-contrib-python==4.5.5.62 # via # dot (setup.cfg) # mediapipe opencv-python==4.5.5.62 # via # dot (setup.cfg) # face-alignment packaging==21.3 # via # kornia # matplotlib # onnxruntime-gpu # pytest # scikit-image pillow==10.0.1 # via # dot (setup.cfg) # imageio # matplotlib # scikit-image # torchvision pluggy==1.2.0 # via pytest protobuf==3.20.2 # via # dot (setup.cfg) # mediapipe # onnxruntime-gpu pycparser==2.21 # via cffi pyparsing==3.0.9 # via # matplotlib # packaging pyreadline3==3.4.1 # via humanfriendly pytest==7.4.0 # via dot (setup.cfg) python-dateutil==2.8.2 # via matplotlib pywavelets==1.3.0 # via scikit-image pyyaml==5.4.1 # via dot (setup.cfg) requests==2.31.0 # via # dot (setup.cfg) # torchvision scikit-image==0.19.1 # via # dot (setup.cfg) # face-alignment scipy==1.10.0 # via # dot (setup.cfg) # face-alignment # scikit-image six==1.16.0 # via python-dateutil sounddevice==0.4.6 # via mediapipe sympy==1.12 # via # onnxruntime-gpu # torch tifffile==2022.5.4 # via scikit-image tomli==2.0.1 # via pytest torch==2.0.1 # via # dot (setup.cfg) # face-alignment # kornia # torchvision torchvision==0.15.2 # via dot (setup.cfg) tqdm==4.64.0 # via face-alignment typing-extensions==4.3.0 # via torch urllib3==1.26.18 # via requests # The following packages are considered to be unsafe in a requirements file: # setuptools ================================================ FILE: scripts/image_swap.py ================================================ #!/usr/bin/env python3 """ Copyright (c) 2022, Sensity B.V. All rights reserved. licensed under the BSD 3-Clause "New" or "Revised" License. """ import glob import json import os import click import yaml import dot """ Usage: python image_swap.py -c -s -t -o -l 5(Optional limit total swaps) """ @click.command() @click.option("-c", "--config", default="./src/dot/simswap/configs/config.yaml") @click.option("-s", "--source", required=True) @click.option("-t", "--target", required=True) @click.option("-o", "--save_folder", required=False) @click.option("-l", "--limit", type=int, required=False) def main( config: str, source: str, target: str, save_folder: str, limit: int = False ) -> None: """Performs face-swap given a `source/target` image(s). Saves JSON file of (un)successful swaps. Args: config (str): Path to DOT configuration yaml file. source (str): Path to source images folder or certain image file. target (str): Path to target images folder or certain image file. save_folder (str): Output folder to store face-swaps and metadata file. limit (int, optional): Number of desired face-swaps. If not specified, all possible combinations of source/target pairs will be processed. Defaults to False. """ print(f"Loading config: {config}") with open(config) as f: config = yaml.safe_load(f) _dot = dot.DOT(use_cam=False, use_video=False, save_folder=save_folder) analysis_config = config["analysis"]["simswap"] option = _dot.simswap( use_gpu=analysis_config.get("use_gpu", False), use_mask=analysis_config.get("opt_use_mask", False), gpen_type=analysis_config.get("gpen", None), gpen_path=analysis_config.get("gpen_path", None), crop_size=analysis_config.get("opt_crop_size", 224), ) swappedMD, rejectedMD = _dot.generate( option, source=source, target=target, limit=limit, **analysis_config ) # save metadata file if swappedMD: with open(os.path.join(save_folder, "metadata.json"), "a") as fp: json.dump(swappedMD, fp, indent=4) # save rejected face-swaps if rejectedMD: with open(os.path.join(save_folder, "rejected.json"), "a") as fp: json.dump(rejectedMD, fp, indent=4) def find_images_from_path(path): if os.path.isfile(path): return [path] try: return int(path) except ValueError: # supported extensions ext = ["png", "jpg", "jpeg"] files = [] [files.extend(glob.glob(path + "**/*." + e, recursive=True)) for e in ext] return files if __name__ == "__main__": main() ================================================ FILE: scripts/metadata_swap.py ================================================ #!/usr/bin/env python3 """ Copyright (c) 2022, Sensity B.V. All rights reserved. licensed under the BSD 3-Clause "New" or "Revised" License. """ import json import os import click import numpy as np import pandas as pd import yaml import dot """ Usage: python metadata_swap.py \ --config \ --local_root_path \ --metadata \ --set \ --save_folder \ --limit 100 """ # common face identity features face_identity_features = { 1: "ArchedEyebrows", 2: "Attractive", 3: "BagsUnderEyes", 6: "BigLips", 7: "BigNose", 12: "BushyEyebrows", 16: "Goatee", 18: "HeavyMakeup", 19: "HighCheekbones", 22: "Mustache", 23: "NarrowEyes", 24: "NoBeard", 27: "PointyNose", } @click.command() @click.option("-c", "--config", default="./src/dot/simswap/configs/config.yaml") @click.option("--local_root_path", required=True) @click.option("--metadata", required=True) @click.option("--set", required=True) @click.option("-o", "--save_folder", required=False) @click.option("--limit", type=int, required=False) def main( config: str, local_root_path: str, metadata: str, set: str, save_folder: str, limit: bool = None, ) -> None: """Script is tailored to dictionary format as shown below. `key` is the relative path to image, `value` is a list of total 44 attributes. [0:40] `Face attributes`: 50'ClockShadow, ArchedEyebrows, Attractive, BagsUnderEyes, Bald,Bangs,BigLips, BigNose, BlackHair, BlondHair, Blurry, BrownHair, BushyEyebrows, Chubby, DoubleChin ,Eyeglasses,Goatee, GrayHair, HeavyMakeup, HighCheekbones, Male, MouthSlightlyOpen, Mustache, NarrowEyes, NoBeard, OvalFace, PaleSkin, PointyNose, RecedingHairline, RosyCheeks, Sideburns, Smiling, StraightHair, WavyHair, WearingEarrings, WearingHat, WearingLipstick, WearingNecklace, WearingNecktie, Young. [41] `Spoof type`: Live, Photo, Poster, A4, Face Mask, Upper Body Mask, Region Mask, PC, Pa, Phone, 3D Mask. [42] `Illumination`: Live, Normal, Strong, Back, Dark. [43] `Live/Spoof(binary)`: Live, Spoof. { "rel_path/img1.png": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,2,2,1], "rel_path/img2.png": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,1,2,1] .... } It constructs a pd.DataFrame from `metadata` and filters rows where examples are under-aged(young==0). Face-swaps are performed randomly based on gender. The `result-swap` image shares common attributes with the `source` image which are defined in `face_identity_features` dict. Spoof-type of swapped image is defined at index 40 of attributes list and set to 11. Args: config (str): Path to DOT configuration yaml file. local_root_path (str): Root path of dataset. metadata (str): JSON metadata file path of dataset. set (str): Defines train/test dataset. save_folder (str): Output folder to store face-swaps and metadata file. limit (int, optional): Number of desired face-swaps. If not specified, will be set equal to DataFrame size. Defaults to False. """ if limit and limit < 4: print("Error: limit should be >= 4") return output_data_folder = os.path.join(save_folder + f"Data/{set}/swap/") df = pd.read_json(metadata, orient="index") mapping = { df.columns[20]: "gender", df.columns[26]: "pale_skin", df.columns[39]: "young", } df = df.rename(columns=mapping) df.head() # keep only live images df = df.loc[df.index.str.contains("live")] # keep only adult images df = df.loc[df["young"] == 0] if not limit: limit = df.shape[0] print(f"Limit is set to: {limit}") filters = ["gender==1", "gender==0"] swaps = [] for filter in filters: # get n random rows based on condition ==1(male) filtered = df.query(filter).sample(n=round(limit / len(filters)), replace=True) # shuffle again, keep only indices and convert to list filtered = filtered.sample(frac=1).index.tolist() # append local_root_path filtered = [os.path.join(local_root_path, p) for p in filtered] # split into two lists roughly equal size mid_index = round(len(filtered) / 2) src = filtered[0:mid_index] tar = filtered[mid_index:] swaps.append((src, tar)) print(f"Loading config: {config}") with open(config) as f: config = yaml.safe_load(f) analysis_config = config["analysis"]["simswap"] _dot = dot.DOT(use_video=False, save_folder=output_data_folder) _dot.use_cam = False option = _dot.build_option( swap_type="simswap", use_gpu=analysis_config.get("use_gpu", False), use_mask=analysis_config.get("opt_use_mask", False), gpen_type=analysis_config.get("gpen", None), gpen_path=analysis_config.get("gpen_path", None), crop_size=analysis_config.get("opt_crop_size", 224), ) total_succeed = {} total_failed = {} for swap in swaps: source_list = swap[0] target_list = swap[1] # perform faceswap for source, target in zip(source_list, target_list): success, rejections = _dot.generate( option, source=source, target=target, duration=None, **analysis_config, ) total_succeed = {**total_succeed, **success} total_failed = {**total_failed, **rejections} # save succeed face-swaps file if total_succeed: # append attribute list for source/target images for key, value in total_succeed.items(): src_attr = ( df.loc[df.index == value["source"]["path"].replace(local_root_path, "")] .iloc[0, 0:] .tolist() ) tar_attr = ( df.loc[df.index == value["target"]["path"].replace(local_root_path, "")] .iloc[0, 0:] .tolist() ) total_succeed[key]["source"]["attr"] = src_attr total_succeed[key]["target"]["attr"] = tar_attr with open(os.path.join(save_folder, "swaps_succeed.json"), "w") as fp: json.dump(total_succeed, fp) # save failed face-swaps file if total_failed: with open(os.path.join(save_folder, "swaps_failed.json"), "w") as fp: json.dump(total_failed, fp) # format metadata to appropriate format formatted = format_swaps(total_succeed) # save file if formatted: with open(os.path.join(save_folder, f"{set}_label_swap.json"), "w") as fp: json.dump(formatted, fp) def format_swaps(succeeds): formatted = {} for key, value in succeeds.items(): # attributes of source image src_attr = np.asarray(value["source"]["attr"]) # attributes of target image tar_attr = np.asarray(value["target"]["attr"]) # attributes of swapped image. copy from target image swap_attr = tar_attr # transfer facial attributes from source image for idx in face_identity_features.keys(): swap_attr[idx] = src_attr[idx] # swap-spoof-type-11, FaceSwap swap_attr[40] = 11 # store in dict formatted[key] = swap_attr.tolist() return formatted if __name__ == "__main__": main() ================================================ FILE: scripts/profile_simswap.py ================================================ #!/usr/bin/env python3 """ Copyright (c) 2022, Sensity B.V. All rights reserved. licensed under the BSD 3-Clause "New" or "Revised" License. """ import cProfile import glob import os import pstats import click import yaml import dot # define globals CONFIG = "./src/dot/simswap/configs/config.yaml" SOURCE = "data/obama.jpg" TARGET = "data/mona.jpg" SAVE_FOLDER = "./profile_output/" LIMIT = 1 @click.command() @click.option("-c", "--config", default=CONFIG) @click.option("--source", default=SOURCE) @click.option("--target", default=TARGET) @click.option("--save_folder", default=SAVE_FOLDER) @click.option("--limit", type=int, default=LIMIT) def main( config=CONFIG, source=SOURCE, target=TARGET, save_folder=SAVE_FOLDER, limit=LIMIT ): profiler = cProfile.Profile() with open(config) as f: config = yaml.safe_load(f) analysis_config = config["analysis"]["simswap"] _dot = dot.DOT(use_cam=False, use_video=False, save_folder=save_folder) option = _dot.simswap( use_gpu=config["analysis"]["simswap"]["use_gpu"], gpen_type=config["analysis"]["simswap"]["gpen"], gpen_path=config["analysis"]["simswap"]["gpen_path"], use_mask=config["analysis"]["simswap"]["opt_use_mask"], crop_size=config["analysis"]["simswap"]["opt_crop_size"], ) option.create_model(**analysis_config) profiler.enable() swappedMD, rejectedMD = _dot.generate( option, source=source, target=target, limit=limit, profiler=True, **analysis_config ) profiler.disable() stats = pstats.Stats(profiler) stats.dump_stats("SimSwap_profiler.prof") def find_images_from_path(path): if os.path.isfile(path): return [path] try: return int(path) except ValueError: # supported extensions ext = ["png", "jpg", "jpeg"] files = [] [files.extend(glob.glob(path + "**/*." + e, recursive=True)) for e in ext] return files if __name__ == "__main__": main() ================================================ FILE: scripts/video_swap.py ================================================ #!/usr/bin/env python3 """ Copyright (c) 2022, Sensity B.V. All rights reserved. licensed under the BSD 3-Clause "New" or "Revised" License. """ import click import yaml import dot """ Usage: python video_swap.py -c -s -t -o -d 5(Optional trim video) -l 5(Optional limit total swaps) """ @click.command() @click.option("-c", "--config", default="./src/dot/simswap/configs/config.yaml") @click.option("-s", "--source_image_path", required=True) @click.option("-t", "--target_video_path", required=True) @click.option("-o", "--output", required=True) @click.option("-d", "--duration_per_video", required=False) @click.option("-l", "--limit", type=int, required=False) def main( config: str, source_image_path: str, target_video_path: str, output: str, duration_per_video: int, limit: int = None, ): """Given `source` and `target` folders, performs face-swap on each video with randomly chosen image found `source` path. Supported image formats: `["jpg", "png", "jpeg"]` Supported video formats: `["avi", "mp4", "mov", "MOV"]` Args: config (str): Path to configuration file. source_image_path (str): Path to source images target_video_path (str): Path to target videos output (str): Output folder path. duration_per_video (int): Trim duration of target video in seconds. limit (int, optional): Limit number of video-swaps. Defaults to None. """ print(f"Loading config: {config}") with open(config) as f: config = yaml.safe_load(f) _dot = dot.DOT(use_cam=False, use_video=True, save_folder=output) analysis_config = config["analysis"]["simswap"] option = _dot.simswap( use_gpu=analysis_config.get("use_gpu", False), use_mask=analysis_config.get("opt_use_mask", False), gpen_type=analysis_config.get("gpen", None), gpen_path=analysis_config.get("gpen_path", None), crop_size=analysis_config.get("opt_crop_size", 224), ) _dot.generate( option=option, source=source_image_path, target=target_video_path, duration=duration_per_video, limit=limit, **analysis_config, ) if __name__ == "__main__": main() ================================================ FILE: setup.cfg ================================================ [bumpversion] current_version = 1.4.0 commit = True tag = False parse = (?P\d+)\.(?P\d+)\.(?P\d+)? serialize = {major}.{minor}.{patch} [bumpversion:file:src/dot/__init__.py] search = __version__ = "{current_version}" replace = __version__ = "{new_version}" [metadata] name = dot version = attr: dot.__version__ author = attr: dot.__author__ description = attr: dot.__doc__ long_description = file: README.md log_description_content_type = text/markdown url = attr: dot.__url__ license = BSD 3-Clause License classifiers = Programming Language :: Python :: 3.8 [options] package_dir = = src packages = find: python_requires = >=3.8,<3.9 install_requires = click dlib face_alignment==1.4.1 kornia mediapipe numpy onnxruntime-gpu==1.18.0 opencv-contrib-python opencv_python Pillow protobuf PyYAML requests scikit_image scipy torch==2.0.1 torchvision==0.15.2 customtkinter pytest [options.extras_require] dev = black bumpversion flake8 ipdb ipython isort==5.12.0 pre-commit pyinstaller pytest pytest-cov types-PyYAML [options.packages.find] where = src [options.entry_points] console_scripts = dot = dot.__main__:main dot-ui = dot.ui.ui:main ================================================ FILE: src/dot/__init__.py ================================================ #!/usr/bin/env python3 """ Copyright (c) 2022, Sensity B.V. All rights reserved. licensed under the BSD 3-Clause "New" or "Revised" License. """ from .dot import DOT __version__ = "1.4.0" __author__ = "Sensity" __url__ = "https://github.com/sensity-ai/dot/tree/main/dot" __docs__ = "Deepfake offensive toolkit" __all__ = ["DOT"] ================================================ FILE: src/dot/__main__.py ================================================ #!/usr/bin/env python3 """ Copyright (c) 2022, Sensity B.V. All rights reserved. licensed under the BSD 3-Clause "New" or "Revised" License. """ import traceback from typing import Union import click import yaml from .dot import DOT def run( swap_type: str, source: str, target: Union[int, str], model_path: str = None, parsing_model_path: str = None, arcface_model_path: str = None, checkpoints_dir: str = None, gpen_type: str = None, gpen_path: str = "saved_models/gpen", crop_size: int = 224, head_pose: bool = False, save_folder: str = None, show_fps: bool = False, use_gpu: bool = False, use_video: bool = False, use_image: bool = False, limit: int = None, ): """Builds a DOT object and runs it. Args: swap_type (str): The type of swap to run. source (str): The source image or video. target (Union[int, str]): The target image or video. model_path (str, optional): The path to the model's weights. Defaults to None. parsing_model_path (str, optional): The path to the parsing model. Defaults to None. arcface_model_path (str, optional): The path to the arcface model. Defaults to None. checkpoints_dir (str, optional): The path to the checkpoints directory. Defaults to None. gpen_type (str, optional): The type of gpen model to use. Defaults to None. gpen_path (str, optional): The path to the gpen models. Defaults to "saved_models/gpen". crop_size (int, optional): The size to crop the images to. Defaults to 224. save_folder (str, optional): The path to the save folder. Defaults to None. show_fps (bool, optional): Pass flag to show fps value. Defaults to False. use_gpu (bool, optional): Pass flag to use GPU else use CPU. Defaults to False. use_video (bool, optional): Pass flag to use video-swap pipeline. Defaults to False. use_image (bool, optional): Pass flag to use image-swap pipeline. Defaults to False. limit (int, optional): The number of frames to process. Defaults to None. """ try: # initialize dot _dot = DOT(use_video=use_video, use_image=use_image, save_folder=save_folder) # build dot option = _dot.build_option( swap_type=swap_type, use_gpu=use_gpu, gpen_type=gpen_type, gpen_path=gpen_path, crop_size=crop_size, ) # run dot _dot.generate( option=option, source=source, target=target, show_fps=show_fps, model_path=model_path, limit=limit, parsing_model_path=parsing_model_path, arcface_model_path=arcface_model_path, checkpoints_dir=checkpoints_dir, opt_crop_size=crop_size, head_pose=head_pose, ) except: # noqa print(traceback.format_exc()) @click.command() @click.option( "--swap_type", "swap_type", type=click.Choice(["fomm", "faceswap_cv2", "simswap"], case_sensitive=False), ) @click.option( "--source", "source", required=True, help="Images to swap with target", ) @click.option( "--target", "target", required=True, help="Cam ID or target media", ) @click.option( "--model_path", "model_path", default=None, help="Path to 68-point facial landmark detector for FaceSwap-cv2 or to the model's weights for the FOM", ) @click.option( "--parsing_model_path", "parsing_model_path", default=None, help="Path to the parsing model", ) @click.option( "--arcface_model_path", "arcface_model_path", default=None, help="Path to arcface model", ) @click.option( "--checkpoints_dir", "checkpoints_dir", default=None, help="models are saved here", ) @click.option( "--gpen_type", "gpen_type", default=None, type=click.Choice(["gpen_256", "gpen_512"]), ) @click.option( "--gpen_path", "gpen_path", default="saved_models/gpen", help="Path to gpen models.", ) @click.option("--crop_size", "crop_size", type=int, default=224) @click.option("--save_folder", "save_folder", type=str, default=None) @click.option( "--show_fps", "show_fps", type=bool, default=False, is_flag=True, help="Pass flag to show fps value.", ) @click.option( "--use_gpu", "use_gpu", type=bool, default=False, is_flag=True, help="Pass flag to use GPU else use CPU.", ) @click.option( "--use_video", "use_video", type=bool, default=False, is_flag=True, help="Pass flag to use video-swap pipeline.", ) @click.option( "--use_image", "use_image", type=bool, default=False, is_flag=True, help="Pass flag to use image-swap pipeline.", ) @click.option("--limit", "limit", type=int, default=None) @click.option( "-c", "--config", "config_file", help="Configuration file. Overrides duplicate options passed.", required=False, default=None, ) def main( swap_type: str, source: str, target: Union[int, str], model_path: str = None, parsing_model_path: str = None, arcface_model_path: str = None, checkpoints_dir: str = None, gpen_type: str = None, gpen_path: str = "saved_models/gpen", crop_size: int = 224, save_folder: str = None, show_fps: bool = False, use_gpu: bool = False, use_video: bool = False, use_image: bool = False, limit: int = None, config_file: str = None, ): """CLI entrypoint for dot.""" # load config, if provided config = {} if config_file is not None: with open(config_file) as f: config = yaml.safe_load(f) # run dot run( swap_type=config.get("swap_type", swap_type), source=source, target=target, model_path=config.get("model_path", model_path), parsing_model_path=config.get("parsing_model_path", parsing_model_path), arcface_model_path=config.get("arcface_model_path", arcface_model_path), checkpoints_dir=config.get("checkpoints_dir", checkpoints_dir), gpen_type=config.get("gpen_type", gpen_type), gpen_path=config.get("gpen_path", gpen_path), crop_size=config.get("crop_size", crop_size), head_pose=config.get("head_pose", False), save_folder=save_folder, show_fps=show_fps, use_gpu=use_gpu, use_video=use_video, use_image=use_image, limit=limit, ) if __name__ == "__main__": main() ================================================ FILE: src/dot/commons/__init__.py ================================================ #!/usr/bin/env python3 """ Copyright (c) 2022, Sensity B.V. All rights reserved. licensed under the BSD 3-Clause "New" or "Revised" License. """ from .model_option import ModelOption __all__ = ["ModelOption"] ================================================ FILE: src/dot/commons/cam/__init__.py ================================================ #!/usr/bin/env python3 ================================================ FILE: src/dot/commons/cam/cam.py ================================================ #!/usr/bin/env python3 import glob import os import cv2 import numpy as np import requests import yaml from ..utils import info, resize from .camera_selector import query_cameras def is_new_frame_better(log, source, driving, predictor): global avatar_kp global display_string if avatar_kp is None: display_string = "No face detected in avatar." return False if predictor.get_start_frame() is None: display_string = "No frame to compare to." return True _ = resize(driving, (128, 128))[..., :3] new_kp = predictor.get_frame_kp(driving) if new_kp is not None: new_norm = (np.abs(avatar_kp - new_kp) ** 2).sum() old_norm = (np.abs(avatar_kp - predictor.get_start_frame_kp()) ** 2).sum() out_string = "{0} : {1}".format(int(new_norm * 100), int(old_norm * 100)) display_string = out_string log(out_string) return new_norm < old_norm else: display_string = "No face found!" return False def load_stylegan_avatar(IMG_SIZE=256): url = "https://thispersondoesnotexist.com/image" r = requests.get(url, headers={"User-Agent": "My User Agent 1.0"}).content image = np.frombuffer(r, np.uint8) image = cv2.imdecode(image, cv2.IMREAD_COLOR) image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) image = resize(image, (IMG_SIZE, IMG_SIZE)) return image def load_images(log, opt_avatars, IMG_SIZE=256): avatars = [] filenames = [] images_list = sorted(glob.glob(f"{opt_avatars}/*")) for i, f in enumerate(images_list): if f.endswith(".jpg") or f.endswith(".jpeg") or f.endswith(".png"): img = cv2.imread(f) if img is None: log("Failed to open image: {}".format(f)) continue if img.ndim == 2: img = np.tile(img[..., None], [1, 1, 3]) img = img[..., :3][..., ::-1] img = resize(img, (IMG_SIZE, IMG_SIZE)) avatars.append(img) filenames.append(f) return avatars, filenames def draw_rect(img, rw=0.6, rh=0.8, color=(255, 0, 0), thickness=2): h, w = img.shape[:2] _l = w * (1 - rw) // 2 r = w - _l u = h * (1 - rh) // 2 d = h - u img = cv2.rectangle(img, (int(_l), int(u)), (int(r), int(d)), color, thickness) def kp_to_pixels(arr): """Convert normalized landmark locations to screen pixels""" return ((arr + 1) * 127).astype(np.int32) def draw_face_landmarks(LANDMARK_SLICE_ARRAY, img, face_kp, color=(20, 80, 255)): if face_kp is not None: img = cv2.polylines( img, np.split(kp_to_pixels(face_kp), LANDMARK_SLICE_ARRAY), False, color ) def print_help(avatar_names): info("\n\n=== Control keys ===") info("1-9: Change avatar") for i, fname in enumerate(avatar_names): key = i + 1 name = fname.split("/")[-1] info(f"{key}: {name}") info("W: Zoom camera in") info("S: Zoom camera out") info("A: Previous avatar in folder") info("D: Next avatar in folder") info("Q: Get random avatar") info("X: Calibrate face pose") info("I: Show FPS") info("ESC: Quit") info("\nFull key list: https://github.com/alievk/avatarify#controls") info("\n\n") def draw_fps( frame, fps, timing, x0=10, y0=20, ystep=30, fontsz=0.5, color=(255, 255, 255), IMG_SIZE=256, ): frame = frame.copy() black = (0, 0, 0) black_thick = 2 cv2.putText( frame, f"FPS: {fps:.1f}", (x0, y0 + ystep * 0), 0, fontsz * IMG_SIZE / 256, (0, 0, 0), black_thick, ) cv2.putText( frame, f"FPS: {fps:.1f}", (x0, y0 + ystep * 0), 0, fontsz * IMG_SIZE / 256, color, 1, ) cv2.putText( frame, f"Model time (ms): {timing['predict']:.1f}", (x0, y0 + ystep * 1), 0, fontsz * IMG_SIZE / 256, black, black_thick, ) cv2.putText( frame, f"Model time (ms): {timing['predict']:.1f}", (x0, y0 + ystep * 1), 0, fontsz * IMG_SIZE / 256, color, 1, ) cv2.putText( frame, f"Preproc time (ms): {timing['preproc']:.1f}", (x0, y0 + ystep * 2), 0, fontsz * IMG_SIZE / 256, black, black_thick, ) cv2.putText( frame, f"Preproc time (ms): {timing['preproc']:.1f}", (x0, y0 + ystep * 2), 0, fontsz * IMG_SIZE / 256, color, 1, ) cv2.putText( frame, f"Postproc time (ms): {timing['postproc']:.1f}", (x0, y0 + ystep * 3), 0, fontsz * IMG_SIZE / 256, black, black_thick, ) cv2.putText( frame, f"Postproc time (ms): {timing['postproc']:.1f}", (x0, y0 + ystep * 3), 0, fontsz * IMG_SIZE / 256, color, 1, ) return frame def draw_landmark_text(frame, thk=2, fontsz=0.5, color=(0, 0, 255), IMG_SIZE=256): frame = frame.copy() cv2.putText(frame, "ALIGN FACES", (60, 20), 0, fontsz * IMG_SIZE / 255, color, thk) cv2.putText( frame, "THEN PRESS X", (60, 245), 0, fontsz * IMG_SIZE / 255, color, thk ) return frame def draw_calib_text(frame, thk=2, fontsz=0.5, color=(0, 0, 255), IMG_SIZE=256): frame = frame.copy() cv2.putText( frame, "FIT FACE IN RECTANGLE", (40, 20), 0, fontsz * IMG_SIZE / 255, color, thk ) cv2.putText(frame, "W - ZOOM IN", (60, 40), 0, fontsz * IMG_SIZE / 255, color, thk) cv2.putText(frame, "S - ZOOM OUT", (60, 60), 0, fontsz * IMG_SIZE / 255, color, thk) cv2.putText( frame, "THEN PRESS X", (60, 245), 0, fontsz * IMG_SIZE / 255, color, thk ) return frame def select_camera(log, config): cam_config = config["cam_config"] cam_id = None if os.path.isfile(cam_config): with open(cam_config, "r") as f: cam_config = yaml.load(f, Loader=yaml.FullLoader) cam_id = cam_config["cam_id"] else: cam_frames = query_cameras(config["query_n_cams"]) if cam_frames: if len(cam_frames) == 1: cam_id = list(cam_frames)[0] else: cam_id = select_camera(cam_frames, window="CLICK ON YOUR CAMERA") log(f"Selected camera {cam_id}") with open(cam_config, "w") as f: yaml.dump({"cam_id": cam_id}, f) else: log("No cameras are available") return cam_id ================================================ FILE: src/dot/commons/cam/camera_selector.py ================================================ #!/usr/bin/env python3 import cv2 import numpy as np import yaml from ..utils import log g_selected_cam = None def query_cameras(n_cams): cam_frames = {} cap = None for camid in range(n_cams): log(f"Trying camera with id {camid}") cap = cv2.VideoCapture(camid) if not cap.isOpened(): log(f"Camera with id {camid} is not available") continue ret, frame = cap.read() if not ret or frame is None: log(f"Could not read from camera with id {camid}") cap.release() continue for i in range(10): ret, frame = cap.read() cam_frames[camid] = frame.copy() cap.release() return cam_frames def make_grid(images, cell_size=(320, 240), cols=2): w0, h0 = cell_size _rows = len(images) // cols + int(len(images) % cols) _cols = min(len(images), cols) grid = np.zeros((h0 * _rows, w0 * _cols, 3), dtype=np.uint8) for i, (camid, img) in enumerate(images.items()): img = cv2.resize(img, (w0, h0)) # add rect img = cv2.rectangle(img, (1, 1), (w0 - 1, h0 - 1), (0, 0, 255), 2) # add id img = cv2.putText(img, f"Camera {camid}", (10, 30), 0, 1, (0, 255, 0), 2) c = i % cols r = i // cols grid[r * h0 : (r + 1) * h0, c * w0 : (c + 1) * w0] = img[..., :3] return grid def mouse_callback(event, x, y, flags, userdata): global g_selected_cam if event == 1: cell_size, grid_cols, cam_frames = userdata c = x // cell_size[0] r = y // cell_size[1] camid = r * grid_cols + c if camid < len(cam_frames): g_selected_cam = camid def select_camera(cam_frames, window="Camera selector"): cell_size = 320, 240 grid_cols = 2 grid = make_grid(cam_frames, cols=grid_cols) # to fit the text if only one cam available if grid.shape[1] == 320: cell_size = 640, 480 grid = cv2.resize(grid, cell_size) cv2.putText( grid, "Click on the web camera to use", (10, grid.shape[0] - 30), 0, 0.7, (200, 200, 200), 2, ) cv2.namedWindow(window) cv2.setMouseCallback(window, mouse_callback, (cell_size, grid_cols, cam_frames)) cv2.imshow(window, grid) while True: key = cv2.waitKey(10) if g_selected_cam is not None: break if key == 27: break cv2.destroyAllWindows() if g_selected_cam is not None: return list(cam_frames)[g_selected_cam] else: return list(cam_frames)[0] if __name__ == "__main__": with open("config.yaml", "r") as f: config = yaml.load(f, Loader=yaml.FullLoader) cam_frames = query_cameras(config["query_n_cams"]) if cam_frames: selected_cam = select_camera(cam_frames) print(f"Selected camera {selected_cam}") else: log("No cameras are available") ================================================ FILE: src/dot/commons/camera_utils.py ================================================ #!/usr/bin/env python3 """ Copyright (c) 2022, Sensity B.V. All rights reserved. licensed under the BSD 3-Clause "New" or "Revised" License. """ from typing import Any, Callable, Dict, List, Union import cv2 import numpy as np from .cam.cam import draw_fps from .utils import TicToc, find_images_from_path from .video.videocaptureasync import VideoCaptureAsync def fetch_camera(target: int) -> VideoCaptureAsync: """Fetches a VideoCaptureAsync object. Args: target (int): Camera ID descriptor. Raises: ValueError: If camera ID descriptor is not valid. Returns: VideoCaptureAsync: VideoCaptureAsync object. """ try: return VideoCaptureAsync(target) except RuntimeError: raise ValueError(f"Camera {target} does not exist.") def camera_pipeline( cap: VideoCaptureAsync, source: str, target: int, change_option: Callable[[np.ndarray], None], process_image: Callable[[np.ndarray], np.ndarray], post_process_image: Callable[[np.ndarray], np.ndarray], crop_size: int = 224, show_fps: bool = False, **kwargs: Dict, ) -> None: """Open a webcam stream `target` and performs face-swap based on `source` image by frame. Args: cap (VideoCaptureAsync): VideoCaptureAsync object. source (str): Path to source image folder. target (int): Camera ID descriptor. change_option (Callable[[np.ndarray], None]): Set `source` arg as faceswap source image. process_image (Callable[[np.ndarray], np.ndarray]): Performs actual face swap. post_process_image (Callable[[np.ndarray], np.ndarray]): Applies face restoration GPEN to result image. crop_size (int, optional): Face crop size. Defaults to 224. show_fps (bool, optional): Display FPS. Defaults to False. """ source = find_images_from_path(source) print("=== Control keys ===") print("1-9: Change avatar") for i, fname in enumerate(source): print(str(i + 1) + ": " + fname) # Todo describe controls available pic_a = source[0] img_a_whole = cv2.imread(pic_a) change_option(img_a_whole) img_a_align_crop = process_image(img_a_whole) img_a_align_crop = post_process_image(img_a_align_crop) cap.start() ret, frame = cap.read() cv2.namedWindow("cam", cv2.WINDOW_GUI_NORMAL) cv2.moveWindow("cam", 500, 250) frame_index = -1 fps_hist: List = [] fps: Union[Any, float] = 0 show_self = False while True: frame_index += 1 ret, frame = cap.read() frame = cv2.flip(frame, 1) if ret: tt = TicToc() timing = {"preproc": 0, "predict": 0, "postproc": 0} tt.tic() key = cv2.waitKey(1) if 48 < key < 58: show_self = False source_image_i = min(key - 49, len(source) - 1) pic_a = source[source_image_i] img_a_whole = cv2.imread(pic_a) change_option(img_a_whole, **kwargs) elif key == ord("y"): show_self = True elif key == ord("q"): break elif key == ord("i"): show_fps = not show_fps if not show_self: result_frame = process_image(frame, crop_size=crop_size, **kwargs) # type: ignore timing["postproc"] = tt.toc() result_frame = post_process_image(result_frame, **kwargs) if show_fps: result_frame = draw_fps(np.array(result_frame), fps, timing) fps_hist.append(tt.toc(total=True)) if len(fps_hist) == 10: fps = 10 / (sum(fps_hist) / 1000) fps_hist = [] cv2.imshow("cam", result_frame) else: cv2.imshow("cam", frame) else: break cap.stop() cv2.destroyAllWindows() ================================================ FILE: src/dot/commons/model_option.py ================================================ #!/usr/bin/env python3 """ Copyright (c) 2022, Sensity B.V. All rights reserved. licensed under the BSD 3-Clause "New" or "Revised" License. """ import os from abc import ABC, abstractmethod from typing import Dict, List, Optional, Tuple, Union import cv2 import torch from ..gpen.face_enhancement import FaceEnhancement from .camera_utils import camera_pipeline, fetch_camera from .utils import find_images_from_path, generate_random_file_idx, rand_idx_tuple from .video.video_utils import video_pipeline class ModelOption(ABC): def __init__( self, gpen_type=None, gpen_path="saved_models/gpen", use_gpu=True, crop_size=256, ): self.gpen_type = gpen_type self.use_gpu = use_gpu self.crop_size = crop_size if gpen_type: if gpen_type == "gpen_512": model = { "name": "GPEN-BFR-512", "size": 512, "channel_multiplier": 2, "narrow": 1, } else: model = { "name": "GPEN-BFR-256", "size": 256, "channel_multiplier": 1, "narrow": 0.5, } self.face_enhancer = FaceEnhancement( size=model["size"], model=model["name"], channel_multiplier=model["channel_multiplier"], narrow=model["narrow"], use_gpu=self.use_gpu, base_dir=gpen_path, ) def generate_from_image( self, source: Union[str, List], target: Union[str, List], save_folder: str, limit: Optional[int] = None, swap_case_idx: Optional[Tuple] = (0, 0), **kwargs, ) -> Optional[List[Dict]]: """_summary_ Args: source (Union[str, List]): A list with source images filepaths, or single image filepath. target (Union[str, List]): A list with target images filepaths, or single image filepath. save_folder (str): Output path. limit (Optional[int], optional): Total number of face-swaps. If None, is set to `len(souce)` * `len(target)`. Defaults to None. swap_case_idx (Optional[Tuple], optional): Used as keyword among multiple swaps. Defaults to (0, 0). Returns: List[Dict]: Array of successful and rejected metadata dictionaries """ if not save_folder: print("Need to define output folder... Skipping") return None # source/target can be single file if not isinstance(source, list): source = find_images_from_path(source) target = find_images_from_path(target) if not limit: # allow all possible swaps limit = len(source) * len(target) swappedDict = {} rejectedDict = {} count = 0 rejected_count = 0 seen_swaps = [] source_len = len(source) target_len = len(target) with torch.no_grad(): profiler = kwargs.get("profiler", False) if not profiler: self.create_model(**kwargs) while count < limit: rand_swap = rand_idx_tuple(source_len, target_len) while rand_swap in seen_swaps: rand_swap = rand_idx_tuple(source_len, target_len) src_idx = rand_swap[0] tar_idx = rand_swap[1] src_img = source[src_idx] tar_img = target[tar_idx] # check if files exits if not os.path.exists(src_img) or not os.path.exists(tar_img): print("source/image file does not exist", src_img, tar_img) continue # read source image source_image = cv2.imread(src_img) frame = cv2.imread(tar_img) try: self.change_option(source_image) frame = self.process_image(frame, use_cam=False, ignore_error=False) # check if frame == target_image, if it does, image rejected frame = self.post_process_image(frame) # flush image to disk file_idx = generate_random_file_idx(6) file_name = os.path.join(save_folder, f"{file_idx:0>6}.jpg") while os.path.exists(file_name): print(f"Swap id: {file_idx} already exists, generating again.") file_idx = generate_random_file_idx(6) file_name = os.path.join(save_folder, f"{file_idx:0>6}.jpg") cv2.imwrite(file_name, frame) # keep track metadata key = f"{swap_case_idx[1]}{file_idx:0>6}.jpg" swappedDict[key] = { "target": {"path": tar_img, "size": frame.shape}, "source": {"path": src_img, "size": source_image.shape}, } print( f"{count}: Performed face swap {src_img, tar_img} saved to {file_name}" ) # keep track of previous swaps seen_swaps.append(rand_swap) count += 1 except Exception as e: rejectedDict[rejected_count] = { "target": {"path": tar_img, "size": frame.shape}, "source": {"path": src_img, "size": source_image.shape}, } rejected_count += 1 print(f"Cannot perform face swap {src_img, tar_img}") print(e) return [swappedDict, rejectedDict] def generate_from_camera( self, source: str, target: int, opt_crop_size: int = 224, show_fps: bool = False, **kwargs: Dict, ) -> None: """Invokes `camera_pipeline` main-loop. Args: source (str): Source image filepath. target (int): Camera descriptor/ID. opt_crop_size (int, optional): Crop size. Defaults to 224. show_fps (bool, optional): Show FPS. Defaults to False. """ with torch.no_grad(): cap = fetch_camera(target) self.create_model(opt_crop_size=opt_crop_size, **kwargs) camera_pipeline( cap, source, target, self.change_option, self.process_image, self.post_process_image, crop_size=opt_crop_size, show_fps=show_fps, ) def generate_from_video( self, source: str, target: str, save_folder: str, duration: int, limit: int = None, **kwargs: Dict, ) -> None: """Invokes `video_pipeline` main-loop. Args: source (str): Source image filepath. target (str): Target video filepath. save_folder (str): Output folder. duration (int): Trim target video in seconds. limit (int, optional): Limit number of video-swaps. Defaults to None. """ with torch.no_grad(): self.create_model(**kwargs) video_pipeline( source, target, save_folder, duration, self.change_option, self.process_image, self.post_process_image, self.crop_size, limit, **kwargs, ) def post_process_image(self, image, **kwargs): if self.gpen_type: image, orig_faces, enhanced_faces = self.face_enhancer.process( img=image, use_gpu=self.use_gpu ) return image @abstractmethod def change_option(self, image, **kwargs): pass @abstractmethod def process_image(self, image, **kwargs): pass @abstractmethod def create_model(self, source, target, limit=None, swap_case_idx=0, **kwargs): pass ================================================ FILE: src/dot/commons/pose/head_pose.py ================================================ #!/usr/bin/env python3 import cv2 import mediapipe as mp import numpy as np mp_face_mesh = mp.solutions.face_mesh face_mesh = mp_face_mesh.FaceMesh( min_detection_confidence=0.5, min_tracking_confidence=0.5 ) mp_drawing = mp.solutions.drawing_utils # https://github.com/google/mediapipe/issues/1615 HEAD_POSE_LANDMARKS = [ 33, 263, 1, 61, 291, 199, ] def pose_estimation( image: np.array, roll: int = 3, pitch: int = 3, yaw: int = 3 ) -> int: """ Adjusted from: https://github.com/niconielsen32/ComputerVision/blob/master/headPoseEstimation.py Given an image and desired `roll`, `pitch` and `yaw` angles, the method checks whether estimated head-pose meets requirements. Args: image: Image to estimate head pose. roll: Rotation margin in X axis. pitch: Rotation margin in Y axis. yaw: Rotation margin in Z axis. Returns: int: Success(0) or Fail(-1). """ results = face_mesh.process(image) img_h, img_w, img_c = image.shape face_3d = [] face_2d = [] if results.multi_face_landmarks: for face_landmarks in results.multi_face_landmarks: for idx, lm in enumerate(face_landmarks.landmark): if idx in HEAD_POSE_LANDMARKS: x, y = int(lm.x * img_w), int(lm.y * img_h) # get 2d coordinates face_2d.append([x, y]) # get 3d coordinates face_3d.append([x, y, lm.z]) # convert to numpy face_2d = np.array(face_2d, dtype=np.float64) face_3d = np.array(face_3d, dtype=np.float64) # camera matrix focal_length = 1 * img_w cam_matrix = np.array( [[focal_length, 9, img_h / 2], [0, focal_length, img_w / 2], [0, 0, 1]] ) # distortion dist_matrix = np.zeros((4, 1), dtype=np.float64) # solve pnp success, rot_vec, trans_vec = cv2.solvePnP( face_3d, face_2d, cam_matrix, dist_matrix ) # rotational matrix rmat, jac = cv2.Rodrigues(rot_vec) # get angles angles, mtxR, mtxQ, Qx, Qy, Qz = cv2.RQDecomp3x3(rmat) # get rotation angles x = angles[0] * 360 y = angles[1] * 360 z = angles[2] * 360 # head rotation in X axis if x < -roll or x > roll: return -1 # head rotation in Y axis if y < -pitch or y > pitch: return -1 # head rotation in Z axis if z < -yaw or z > yaw: return -1 return 0 return -1 ================================================ FILE: src/dot/commons/utils.py ================================================ #!/usr/bin/env python3 import glob import os import random import sys import time from collections import defaultdict from typing import Dict, List, Tuple import cv2 import numpy as np SEED = 42 np.random.seed(SEED) def log(*args, **kwargs): time_str = f"{time.time():.6f}" print(f"[{time_str}]", *args, **kwargs) def info(*args, file=sys.stdout, **kwargs): print(*args, file=file, **kwargs) def find_images_from_path(path): """ @arguments: path (str/int) : Could be either path(str) or a CamID(int) """ if os.path.isfile(path): return [path] try: return int(path) except ValueError: # supported extensions ext = ["png", "jpg", "jpeg"] files = [] [files.extend(glob.glob(path + "**/*." + e, recursive=True)) for e in ext] return files def find_files_from_path(path: str, ext: List, filter: str = None): """ @arguments: path (str) Parent directory of files ext (list) List of desired file extensions """ if os.path.isdir(path): files = [] [ files.extend(glob.glob(path + "**/*." + e, recursive=True)) for e in ext # type: ignore ] np.random.shuffle(files) # filter if filter is not None: files = [file for file in files if filter in file] print("Filtered files: ", len(files)) return files return [path] def expand_bbox( bbox, image_width, image_height, scale=None ) -> Tuple[int, int, int, int]: if scale is None: raise ValueError("scale parameter is none") x1, y1, x2, y2 = bbox center_x, center_y = (x1 + x2) // 2, (y1 + y2) // 2 size_bb = round(max(x2 - x1, y2 - y1) * scale) # Check for out of bounds, x-y top left corner x1 = max(int(center_x - size_bb // 2), 0) y1 = max(int(center_y - size_bb // 2), 0) # Check for too big bb size for given x, y size_bb = min(image_width - x1, size_bb) size_bb = min(image_height - y1, size_bb) return (x1, y1, x1 + size_bb, y1 + size_bb) def rand_idx_tuple(source_len, target_len): """ pick a random tuple for source/target """ return (random.randrange(source_len), random.randrange(target_len)) def generate_random_file_idx(length): return int("".join([str(random.randint(0, 10)) for _ in range(length)])) class Tee(object): def __init__(self, filename, mode="w", terminal=sys.stderr): self.file = open(filename, mode, buffering=1) self.terminal = terminal def __del__(self): self.file.close() def write(self, *args, **kwargs): log(*args, file=self.file, **kwargs) log(*args, file=self.terminal, **kwargs) def __call__(self, *args, **kwargs): return self.write(*args, **kwargs) def flush(self): self.file.flush() class Logger: def __init__(self, filename, verbose=True): self.tee = Tee(filename) self.verbose = verbose def __call__(self, *args, important=False, **kwargs): if not self.verbose and not important: return self.tee(*args, **kwargs) class Once: _id: Dict = {} def __init__(self, what, who=log, per=1e12): """Do who(what) once per seconds. what: args for who who: callable per: frequency in seconds. """ assert callable(who) now = time.time() if what not in Once._id or now - Once._id[what] > per: who(what) Once._id[what] = now class TicToc: def __init__(self): self.t = None self.t_init = time.time() def tic(self): self.t = time.time() def toc(self, total=False): if total: return (time.time() - self.t_init) * 1000 assert self.t, "You forgot to call tic()" return (time.time() - self.t) * 1000 def tocp(self, str): t = self.toc() log(f"{str} took {t:.4f}ms") return t class AccumDict: def __init__(self, num_f=3): self.d = defaultdict(list) self.num_f = num_f def add(self, k, v): self.d[k] += [v] def __dict__(self): return self.d def __getitem__(self, key): return self.d[key] def __str__(self): s = "" for k in self.d: if not self.d[k]: continue cur = self.d[k][-1] avg = np.mean(self.d[k]) format_str = "{:.%df}" % self.num_f cur_str = format_str.format(cur) avg_str = format_str.format(avg) s += f"{k} {cur_str} ({avg_str})\t\t" return s def __repr__(self): return self.__str__() def clamp(value, min_value, max_value): return max(min(value, max_value), min_value) def crop(img, p=0.7, offset_x=0, offset_y=0): h, w = img.shape[:2] x = int(min(w, h) * p) _l = (w - x) // 2 r = w - _l u = (h - x) // 2 d = h - u offset_x = clamp(offset_x, -_l, w - r) offset_y = clamp(offset_y, -u, h - d) _l += offset_x r += offset_x u += offset_y d += offset_y return img[u:d, _l:r], (offset_x, offset_y) def pad_img(img, target_size, default_pad=0): sh, sw = img.shape[:2] w, h = target_size pad_w, pad_h = default_pad, default_pad if w / h > 1: pad_w += int(sw * (w / h) - sw) // 2 else: pad_h += int(sh * (h / w) - sh) // 2 out = np.pad(img, [[pad_h, pad_h], [pad_w, pad_w], [0, 0]], "constant") return out def resize(img, size, version="cv"): return cv2.resize(img, size) def determine_path(): """ Find the script path """ try: root = __file__ if os.path.islink(root): root = os.path.realpath(root) return os.path.dirname(os.path.abspath(root)) except Exception as e: print(e) print("I'm sorry, but something is wrong.") print("There is no __file__ variable. Please contact the author.") sys.exit() ================================================ FILE: src/dot/commons/video/__init__.py ================================================ #!/usr/bin/env python3 ================================================ FILE: src/dot/commons/video/video_utils.py ================================================ #!/usr/bin/env python3 """ Copyright (c) 2022, Sensity B.V. All rights reserved. licensed under the BSD 3-Clause "New" or "Revised" License. """ import os import random from typing import Callable, Dict, Union import cv2 import mediapipe as mp import numpy as np from mediapipe.python.solutions.drawing_utils import _normalized_to_pixel_coordinates from ..pose.head_pose import pose_estimation from ..utils import expand_bbox, find_files_from_path mp_face = mp.solutions.face_detection.FaceDetection( model_selection=0, # model selection min_detection_confidence=0.5, # confidence threshold ) def _crop_and_pose( image: np.array, estimate_pose: bool = False ) -> Union[np.array, int]: """Crops face of `image` and estimates head pose. Args: image (np.array): Image to be cropped and estimate pose. estimate_pose (Boolean, optional): Enables pose estimation. Defaults to False. Returns: Union[np.array,int]: Cropped image or -1. """ image_rows, image_cols, _ = image.shape results = mp_face.process(image) if results.detections is None: return -1 detection = results.detections[0] location = detection.location_data relative_bounding_box = location.relative_bounding_box rect_start_point = _normalized_to_pixel_coordinates( relative_bounding_box.xmin, relative_bounding_box.ymin, image_cols, image_rows ) rect_end_point = _normalized_to_pixel_coordinates( min(relative_bounding_box.xmin + relative_bounding_box.width, 1.0), min(relative_bounding_box.ymin + relative_bounding_box.height, 1.0), image_cols, image_rows, ) xleft, ytop = rect_start_point xright, ybot = rect_end_point xleft, ytop, xright, ybot = expand_bbox( (xleft, ytop, xright, ybot), image_rows, image_cols, 2.0 ) try: crop_image = image[ytop:ybot, xleft:xright] if estimate_pose: if pose_estimation(image=crop_image, roll=3, pitch=3, yaw=3) != 0: return -1 return cv2.flip(crop_image, 1) except Exception as e: print(e) return -1 def video_pipeline( source: str, target: str, save_folder: str, duration: int, change_option: Callable[[np.ndarray], None], process_image: Callable[[np.ndarray], np.ndarray], post_process_image: Callable[[np.ndarray], np.ndarray], crop_size: int = 224, limit: int = None, **kwargs: Dict, ) -> None: """Process input video file `target` by frame and performs face-swap based on first image found in `source` path folder. Uses cv2.VideoWriter to flush the resulted video on disk. Trimming video is done as: trimmed = fps * duration. Args: source (str): Path to source image folder. target (str): Path to target video folder. save_folder (str): Output folder path. duration (int): Crop target video in seconds. change_option (Callable[[np.ndarray], None]): Set `source` arg as faceswap source image. process_image (Callable[[np.ndarray], np.ndarray]): Performs actual face swap. post_process_image (Callable[[np.ndarray], np.ndarray]): Applies face restoration GPEN to result image. head_pose (bool): Estimates head pose before swap. Used by Avatarify. crop_size (int, optional): Face crop size. Defaults to 224. limit (int, optional): Limit number of video-swaps. Defaults to None. """ head_pose = kwargs.get("head_pose", False) source_imgs = find_files_from_path(source, ["jpg", "png", "jpeg"], filter=None) target_videos = find_files_from_path(target, ["avi", "mp4", "mov", "MOV"]) if not source_imgs or not target_videos: print("Could not find any source/target files") return # unique combinations of source/target swaps_combination = [(im, vi) for im in source_imgs for vi in target_videos] # randomize list random.shuffle(swaps_combination) if limit: swaps_combination = swaps_combination[:limit] print("Total source images: ", len(source_imgs)) print("Total target videos: ", len(target_videos)) print("Total number of face-swaps: ", len(swaps_combination)) # iterate on each source-target pair for (source, target) in swaps_combination: img_a_whole = cv2.imread(source) img_a_whole = _crop_and_pose(img_a_whole, estimate_pose=head_pose) if isinstance(img_a_whole, int): print( f"Image {source} failed on face detection or pose estimation requirements haven't met." ) continue change_option(img_a_whole) img_a_align_crop = process_image(img_a_whole) img_a_align_crop = post_process_image(img_a_align_crop) # video handle cap = cv2.VideoCapture(target) fps = int(cap.get(cv2.CAP_PROP_FPS)) frame_width = int(cap.get(3)) frame_height = int(cap.get(4)) total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) # trim original video length if duration and (fps * int(duration)) < total_frames: total_frames = fps * int(duration) # result video is saved in `save_folder` with name combining source/target files. source_base_name = os.path.basename(source) target_base_name = os.path.basename(target) output_file = f"{os.path.splitext(source_base_name)[0]}_{os.path.splitext(target_base_name)[0]}.mp4" output_file = os.path.join(save_folder, output_file) fourcc = cv2.VideoWriter_fourcc("X", "V", "I", "D") video_writer = cv2.VideoWriter( output_file, fourcc, fps, (frame_width, frame_height), True ) print( f"Source: {source} \nTarget: {target} \nOutput: {output_file} \nFPS: {fps} \nTotal frames: {total_frames}" ) # process each frame individually for _ in range(total_frames): ret, frame = cap.read() if ret is True: frame = cv2.flip(frame, 1) result_frame = process_image(frame, use_cam=False, crop_size=crop_size, **kwargs) # type: ignore result_frame = post_process_image(result_frame, **kwargs) video_writer.write(result_frame) else: break cap.release() video_writer.release() ================================================ FILE: src/dot/commons/video/videocaptureasync.py ================================================ #!/usr/bin/env python3 # https://github.com/gilbertfrancois/video-capture-async import threading import time import cv2 WARMUP_TIMEOUT = 10.0 class VideoCaptureAsync: def __init__(self, src=0, width=640, height=480): self.src = src self.cap = cv2.VideoCapture(self.src) if not self.cap.isOpened(): raise RuntimeError("Cannot open camera") self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, width) self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, height) self.grabbed, self.frame = self.cap.read() self.started = False self.read_lock = threading.Lock() def set(self, var1, var2): self.cap.set(var1, var2) def isOpened(self): return self.cap.isOpened() def start(self): if self.started: print("[!] Asynchronous video capturing has already been started.") return None self.started = True self.thread = threading.Thread(target=self.update, args=(), daemon=True) self.thread.start() # (warmup) wait for the first successfully grabbed frame warmup_start_time = time.time() while not self.grabbed: warmup_elapsed_time = time.time() - warmup_start_time if warmup_elapsed_time > WARMUP_TIMEOUT: raise RuntimeError( f"Failed to succesfully grab frame from " f"the camera (timeout={WARMUP_TIMEOUT}s). " f"Try to restart." ) time.sleep(0.5) return self def update(self): while self.started: grabbed, frame = self.cap.read() if not grabbed or frame is None or frame.size == 0: continue with self.read_lock: self.grabbed = grabbed self.frame = frame def read(self): while True: with self.read_lock: frame = self.frame.copy() grabbed = self.grabbed break return grabbed, frame def stop(self): self.started = False self.thread.join() def __exit__(self, exec_type, exc_value, traceback): self.cap.release() ================================================ FILE: src/dot/dot.py ================================================ #!/usr/bin/env python3 """ Copyright (c) 2022, Sensity B.V. All rights reserved. licensed under the BSD 3-Clause "New" or "Revised" License. """ from pathlib import Path from typing import List, Optional, Union from .commons import ModelOption from .faceswap_cv2 import FaceswapCVOption from .fomm import FOMMOption from .simswap import SimswapOption AVAILABLE_SWAP_TYPES = ["simswap", "fomm", "faceswap_cv2"] class DOT: """Main DOT Interface. Supported Engines: - `simswap` - `fomm` - `faceswap_cv2` Attributes: use_cam (bool): Use camera descriptor and pipeline. use_video (bool): Use video-swap pipeline. use_image (bool): Use image-swap pipeline. save_folder (str): Output folder to store face-swaps and metadata file when `use_cam` is False. """ def __init__( self, use_video: bool = False, use_image: bool = False, save_folder: str = None, *args, **kwargs, ): """Constructor method. Args: use_video (bool, optional): if True, use video-swap pipeline. Defaults to False. use_image (bool, optional): if True, use image-swap pipeline. Defaults to False. save_folder (str, optional): Output folder to store face-swaps and metadata file when `use_cam` is False. Defaults to None. """ # init self.use_video = use_video self.save_folder = save_folder self.use_image = use_image # additional attributes self.use_cam = (not use_video) and (not use_image) # create output folder if self.save_folder and not Path(self.save_folder).exists(): Path(self.save_folder).mkdir(parents=True, exist_ok=True) def build_option( self, swap_type: str, use_gpu: bool, gpen_type: str, gpen_path: str, crop_size: int, **kwargs, ) -> ModelOption: """Build DOT option based on swap type. Args: swap_type (str): Swap type engine. use_gpu (bool): If True, use GPU. gpen_type (str): GPEN type. gpen_path (str): path to GPEN model checkpoint. crop_size (int): crop size. Returns: ModelOption: DOT option. """ if swap_type not in AVAILABLE_SWAP_TYPES: raise ValueError(f"Invalid swap type: {swap_type}") option: ModelOption = None if swap_type == "simswap": option = self.simswap( use_gpu=use_gpu, gpen_type=gpen_type, gpen_path=gpen_path, crop_size=crop_size, ) elif swap_type == "fomm": option = self.fomm( use_gpu=use_gpu, gpen_type=gpen_type, gpen_path=gpen_path, **kwargs ) elif swap_type == "faceswap_cv2": option = self.faceswap_cv2( use_gpu=use_gpu, gpen_type=gpen_type, gpen_path=gpen_path ) return option def generate( self, option: ModelOption, source: str, target: Union[int, str], show_fps: bool = False, duration: int = None, **kwargs, ) -> Optional[List]: """Differentiates among different swap options. Available swap options: - `camera` - `image` - `video` Args: option (ModelOption): Swap engine class. source (str): File path of source image. target (Union[int, str]): Either `int` which indicates camera descriptor or target image file. show_fps (bool, optional): Displays FPS during camera pipeline. Defaults to False. duration (int, optional): Used to trim source video in seconds. Defaults to None. Returns: Optional[List]: None when using camera, otherwise metadata of successful and rejected face-swaps. """ if self.use_cam: option.generate_from_camera( source, int(target), show_fps=show_fps, **kwargs ) return None if isinstance(target, str): if self.use_video: option.generate_from_video( source, target, self.save_folder, duration, **kwargs ) return None elif self.use_image: [swappedDict, rejectedDict] = option.generate_from_image( source, target, self.save_folder, **kwargs ) return [swappedDict, rejectedDict] else: return None else: return None def simswap( self, use_gpu: bool, gpen_type: str, gpen_path: str, crop_size: int = 224, use_mask: bool = True, ) -> SimswapOption: """Build Simswap Option. Args: use_gpu (bool): If True, use GPU. gpen_type (str): GPEN type. gpen_path (str): path to GPEN model checkpoint. crop_size (int, optional): crop size. Defaults to 224. use_mask (bool, optional): If True, use mask. Defaults to True. Returns: SimswapOption: Simswap Option. """ return SimswapOption( use_gpu=use_gpu, gpen_type=gpen_type, gpen_path=gpen_path, crop_size=crop_size, use_mask=use_mask, ) def faceswap_cv2( self, use_gpu: bool, gpen_type: str, gpen_path: str, crop_size: int = 256 ) -> FaceswapCVOption: """Build FaceswapCV Option. Args: use_gpu (bool): If True, use GPU. gpen_type (str): GPEN type. gpen_path (str): path to GPEN model checkpoint. crop_size (int, optional): crop size. Defaults to 256. Returns: FaceswapCVOption: FaceswapCV Option. """ return FaceswapCVOption( use_gpu=use_gpu, gpen_type=gpen_type, gpen_path=gpen_path, crop_size=crop_size, ) def fomm( self, use_gpu: bool, gpen_type: str, gpen_path: str, crop_size: int = 256, **kwargs, ) -> FOMMOption: """Build FOMM Option. Args: use_gpu (bool): If True, use GPU. gpen_type (str): GPEN type. gpen_path (str): path to GPEN model checkpoint. crop_size (int, optional): crop size. Defaults to 256. Returns: FOMMOption: FOMM Option. """ return FOMMOption( use_gpu=use_gpu, gpen_type=gpen_type, gpen_path=gpen_path, crop_size=crop_size, offline=self.use_video, ) ================================================ FILE: src/dot/faceswap_cv2/__init__.py ================================================ #!/usr/bin/env python3 from .option import FaceswapCVOption __all__ = ["FaceswapCVOption"] ================================================ FILE: src/dot/faceswap_cv2/generic.py ================================================ #!/usr/bin/env python3 import cv2 import numpy as np import scipy.spatial as spatial def bilinear_interpolate(img, coords): """ Interpolates over every image channel. https://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, dst_points): """ Calculate the affine transformation matrix for each triangle (x,y) vertex from dst_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 dst_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((dst_points[tri_indices, :].T, ones)) mat = np.dot(src_tri, np.linalg.inv(dst_tri))[:2, :] yield mat def warp_image_3d(src_img, src_points, dst_points, dst_shape, dtype=np.uint8): rows, cols = dst_shape[:2] result_img = np.zeros((rows, cols, 3), dtype=dtype) delaunay = spatial.Delaunay(dst_points) tri_affines = np.asarray( list(triangular_affine_matrices(delaunay.simplices, src_points, dst_points)) ) process_warp(src_img, result_img, tri_affines, dst_points, delaunay) return result_img def transformation_from_points(points1, points2): points1 = points1.astype(np.float64) points2 = points2.astype(np.float64) c1 = np.mean(points1, axis=0) c2 = np.mean(points2, axis=0) points1 -= c1 points2 -= c2 s1 = np.std(points1) s2 = np.std(points2) points1 /= s1 points2 /= s2 U, S, Vt = np.linalg.svd(np.dot(points1.T, points2)) R = (np.dot(U, Vt)).T return np.vstack( [ np.hstack([s2 / s1 * R, (c2.T - np.dot(s2 / s1 * R, c1.T))[:, np.newaxis]]), np.array([[0.0, 0.0, 1.0]]), ] ) def warp_image_2d(im, M, dshape): output_im = np.zeros(dshape, dtype=im.dtype) cv2.warpAffine( im, M[:2], (dshape[1], dshape[0]), dst=output_im, borderMode=cv2.BORDER_TRANSPARENT, flags=cv2.WARP_INVERSE_MAP, ) return output_im def mask_from_points(size, points, erode_flag=1): radius = 10 # kernel size kernel = np.ones((radius, radius), np.uint8) mask = np.zeros(size, np.uint8) cv2.fillConvexPoly(mask, cv2.convexHull(points), 255) if erode_flag: mask = cv2.erode(mask, kernel, iterations=1) return mask def correct_colours(im1, im2, landmarks1): COLOUR_CORRECT_BLUR_FRAC = 0.75 LEFT_EYE_POINTS = list(range(42, 48)) RIGHT_EYE_POINTS = list(range(36, 42)) blur_amount = COLOUR_CORRECT_BLUR_FRAC * np.linalg.norm( np.mean(landmarks1[LEFT_EYE_POINTS], axis=0) - np.mean(landmarks1[RIGHT_EYE_POINTS], axis=0) ) blur_amount = int(blur_amount) if blur_amount % 2 == 0: blur_amount += 1 im1_blur = cv2.GaussianBlur(im1, (blur_amount, blur_amount), 0) im2_blur = cv2.GaussianBlur(im2, (blur_amount, blur_amount), 0) # Avoid divide-by-zero errors. im2_blur = im2_blur.astype(int) im2_blur += 128 * (im2_blur <= 1) result = ( im2.astype(np.float64) * im1_blur.astype(np.float64) / im2_blur.astype(np.float64) ) result = np.clip(result, 0, 255).astype(np.uint8) return result 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 = cv2.bitwise_and(img, img, mask=mask) return masked_img ================================================ FILE: src/dot/faceswap_cv2/option.py ================================================ #!/usr/bin/env python3 import cv2 import dlib import numpy as np from ..commons import ModelOption from ..commons.utils import crop, resize from ..faceswap_cv2.swap import Swap class FaceswapCVOption(ModelOption): def __init__( self, use_gpu=True, use_mask=False, crop_size=224, gpen_type=None, gpen_path=None, ): super(FaceswapCVOption, self).__init__( gpen_type=gpen_type, use_gpu=use_gpu, crop_size=crop_size, gpen_path=gpen_path, ) self.frame_proportion = 0.9 self.frame_offset_x = 0 self.frame_offset_y = 0 def create_model(self, model_path, **kwargs) -> None: # type: ignore self.model = Swap( predictor_path=model_path, end=68, warp_2d=False, correct_color=True ) self.detector = dlib.get_frontal_face_detector() def change_option(self, image, **kwargs): self.source_image = image self.src_landmarks, self.src_shape, self.src_face = self.model._process_face( image ) def process_image( self, image, use_cam=True, ignore_error=True, **kwargs ) -> np.array: frame = image[..., ::-1] if use_cam: frame, (self.frame_offset_x, self.frame_offset_y) = crop( frame, p=self.frame_proportion, offset_x=self.frame_offset_x, offset_y=self.frame_offset_y, ) frame = resize(frame, (self.crop_size, self.crop_size))[..., :3] frame = cv2.flip(frame, 1) faces = self.detector(frame[..., ::-1]) if len(faces) > 0: try: swapped_img = self.model.apply_face_swap( source_image=self.source_image, target_image=frame, save_path=None, src_landmarks=self.src_landmarks, src_shape=self.src_shape, src_face=self.src_face, ) swapped_img = np.array(swapped_img)[..., ::-1].copy() except Exception as e: if ignore_error: print(e) swapped_img = frame[..., ::-1].copy() else: raise e else: swapped_img = frame[..., ::-1].copy() return swapped_img ================================================ FILE: src/dot/faceswap_cv2/swap.py ================================================ #!/usr/bin/env python3 from typing import Any, Dict import cv2 import dlib import numpy as np from PIL import Image from .generic import ( apply_mask, correct_colours, mask_from_points, transformation_from_points, warp_image_2d, warp_image_3d, ) # define globals CACHED_PREDICTOR_PATH = "saved_models/faceswap_cv/shape_predictor_68_face_landmarks.dat" class Swap: def __init__( self, predictor_path: str = None, warp_2d: bool = True, correct_color: bool = True, end: int = 48, ): """ Face Swap. @description: perform face swapping using Poisson blending @arguments: predictor_path: (str) path to 68-point facial landmark detector warp_2d: (bool) if True, perform 2d warping for swapping correct_color: (bool) if True, color correct swap output image end: (int) last facial landmark point for face swap """ if not predictor_path: predictor_path = CACHED_PREDICTOR_PATH # init self.predictor_path = predictor_path self.warp_2d = warp_2d self.correct_color = correct_color self.end = end # Load dlib models self.detector = dlib.get_frontal_face_detector() self.predictor = dlib.shape_predictor(self.predictor_path) def apply_face_swap(self, source_image, target_image, save_path=None, **kwargs): """ apply face swapping from source to target image @arguments: source_image: (PIL or str) source PIL image or path to source image target_image: (PIL or str) target PIL image or path to target image save_path: (str) path to save face swap output image (optional) **kwargs: Extra argument for specifying the source and target landmarks, shape and face @returns: faceswap_output_image: (PIL) face swap output image """ # load image if path given, else convert to cv2 format if isinstance(source_image, str): source_image_cv2 = cv2.imread(source_image) else: source_image_cv2 = cv2.cvtColor(np.array(source_image), cv2.COLOR_RGB2BGR) if isinstance(target_image, str): target_image_cv2 = cv2.imread(target_image) else: target_image_cv2 = cv2.cvtColor(np.array(target_image), cv2.COLOR_RGB2BGR) # process source image try: src_landmarks = kwargs["src_landmarks"] src_shape = kwargs["src_shape"] src_face = kwargs["src_face"] except Exception as e: print(e) src_landmarks, src_shape, src_face = self._process_face(source_image_cv2) # process target image trg_landmarks, trg_shape, trg_face = self._process_face(target_image_cv2) # get target face dimensions h, w = trg_face.shape[:2] # 3d warp warped_src_face = warp_image_3d( src_face, src_landmarks[: self.end], trg_landmarks[: self.end], (h, w) ) # Mask for blending mask = mask_from_points((h, w), trg_landmarks) mask_src = np.mean(warped_src_face, axis=2) > 0 mask = np.asarray(mask * mask_src, dtype=np.uint8) # Correct color if self.correct_color: warped_src_face = apply_mask(warped_src_face, mask) dst_face_masked = apply_mask(trg_face, mask) warped_src_face = correct_colours( dst_face_masked, warped_src_face, trg_landmarks ) # 2d warp if self.warp_2d: unwarped_src_face = warp_image_3d( warped_src_face, trg_landmarks[: self.end], src_landmarks[: self.end], src_face.shape[:2], ) warped_src_face = warp_image_2d( unwarped_src_face, transformation_from_points(trg_landmarks, src_landmarks), (h, w, 3), ) mask = mask_from_points((h, w), trg_landmarks) mask_src = np.mean(warped_src_face, axis=2) > 0 mask = np.asarray(mask * mask_src, dtype=np.uint8) # perform base blending operation faceswap_output_cv2 = self._perform_base_blending( mask, trg_face, warped_src_face ) x, y, w, h = trg_shape target_faceswap_img = target_image_cv2.copy() target_faceswap_img[y : y + h, x : x + w] = faceswap_output_cv2 faceswap_output_image = Image.fromarray( cv2.cvtColor(target_faceswap_img, cv2.COLOR_BGR2RGB) ) if save_path: faceswap_output_image.save(save_path, compress_level=0) return faceswap_output_image def _face_and_landmark_detection(self, image): """perform face detection and get facial landmarks""" # get face bounding box faces = self.detector(image) idx = np.argmax( [ (face.right() - face.left()) * (face.bottom() - face.top()) for face in faces ] ) bbox = faces[idx] # predict landmarks landmarks_dlib = self.predictor(image=image, box=bbox) face_landmarks = np.array([[p.x, p.y] for p in landmarks_dlib.parts()]) return face_landmarks def _process_face(self, image, r=10): """process detected face and landmarks""" # get landmarks landmarks = self._face_and_landmark_detection(image) # get image dimensions im_w, im_h = image.shape[:2] # get face edges left, top = np.min(landmarks, 0) right, bottom = np.max(landmarks, 0) # scale landmarks and face edges x, y = max(0, left - r), max(0, top - r) w, h = min(right + r, im_h) - x, min(bottom + r, im_w) - y return ( landmarks - np.asarray([[x, y]]), (x, y, w, h), image[y : y + h, x : x + w], ) @staticmethod def _perform_base_blending(mask, trg_face, warped_src_face): """perform Poisson blending using mask""" # Shrink the mask kernel = np.ones((10, 10), np.uint8) mask = cv2.erode(mask, kernel, iterations=1) # Poisson Blending r = cv2.boundingRect(mask) center = (r[0] + int(r[2] / 2), r[1] + int(r[3] / 2)) output_cv2 = cv2.seamlessClone( warped_src_face, trg_face, mask, center, cv2.NORMAL_CLONE ) return output_cv2 @classmethod def from_config(cls, config: Dict[str, Any]) -> "Swap": """ Instantiates a Swap from a configuration. Args: config: A configuration for a Swap. Returns: A Swap instance. """ # get config swap_config = config.get("swap") # return instance return cls( predictor_path=swap_config.get("predictor_path", CACHED_PREDICTOR_PATH), warp_2d=swap_config.get("warp_2d", True), correct_color=swap_config.get("correct_color", True), end=swap_config.get("end", 48), ) ================================================ FILE: src/dot/fomm/__init__.py ================================================ #!/usr/bin/env python3 from .option import FOMMOption __all__ = ["FOMMOption"] ================================================ FILE: src/dot/fomm/config/vox-adv-256.yaml ================================================ --- dataset_params: root_dir: data/vox-png frame_shape: [256, 256, 3] id_sampling: true pairs_list: data/vox256.csv augmentation_params: flip_param: horizontal_flip: true time_flip: true jitter_param: brightness: 0.1 contrast: 0.1 saturation: 0.1 hue: 0.1 model_params: common_params: num_kp: 10 num_channels: 3 estimate_jacobian: true kp_detector_params: temperature: 0.1 block_expansion: 32 max_features: 1024 scale_factor: 0.25 num_blocks: 5 generator_params: block_expansion: 64 max_features: 512 num_down_blocks: 2 num_bottleneck_blocks: 6 estimate_occlusion_map: true dense_motion_params: block_expansion: 64 max_features: 1024 num_blocks: 5 scale_factor: 0.25 discriminator_params: scales: [1] block_expansion: 32 max_features: 512 num_blocks: 4 use_kp: true train_params: num_epochs: 150 num_repeats: 75 epoch_milestones: [] lr_generator: 2.0e-4 lr_discriminator: 2.0e-4 lr_kp_detector: 2.0e-4 batch_size: 36 scales: [1, 0.5, 0.25, 0.125] checkpoint_freq: 50 transform_params: sigma_affine: 0.05 sigma_tps: 0.005 points_tps: 5 loss_weights: generator_gan: 1 discriminator_gan: 1 feature_matching: [10, 10, 10, 10] perceptual: [10, 10, 10, 10, 10] equivariance_value: 10 equivariance_jacobian: 10 reconstruction_params: num_videos: 1000 format: .mp4 animate_params: num_pairs: 50 format: .mp4 normalization_params: adapt_movement_scale: false use_relative_movement: true use_relative_jacobian: true visualizer_params: kp_size: 5 draw_border: true colormap: gist_rainbow ================================================ FILE: src/dot/fomm/face_alignment.py ================================================ import warnings from enum import IntEnum import numpy as np import torch from face_alignment.folder_data import FolderData from face_alignment.utils import crop, draw_gaussian, flip, get_image, get_preds_fromhm from packaging import version from tqdm import tqdm class LandmarksType(IntEnum): """Enum class defining the type of landmarks to detect. ``TWO_D`` - the detected points ``(x,y)`` are detected in a 2D space and follow the visible contour of the face ``TWO_HALF_D`` - this points represent the projection of the 3D points into 3D ``THREE_D`` - detect the points ``(x,y,z)``` in a 3D space """ TWO_D = 1 TWO_HALF_D = 2 THREE_D = 3 class NetworkSize(IntEnum): # TINY = 1 # SMALL = 2 # MEDIUM = 3 LARGE = 4 default_model_urls = { "2DFAN-4": "saved_models/face_alignment/2DFAN4-cd938726ad.zip", "3DFAN-4": "saved_models/face_alignment/3DFAN4-4a694010b9.zip", "depth": "saved_models/face_alignment/depth-6c4283c0e0.zip", } models_urls = { "1.6": { "2DFAN-4": "saved_models/face_alignment/2DFAN4_1.6-c827573f02.zip", "3DFAN-4": "saved_models/face_alignment/3DFAN4_1.6-ec5cf40a1d.zip", "depth": "saved_models/face_alignment/depth_1.6-2aa3f18772.zip", }, "1.5": { "2DFAN-4": "saved_models/face_alignment/2DFAN4_1.5-a60332318a.zip", "3DFAN-4": "saved_models/face_alignment/3DFAN4_1.5-176570af4d.zip", "depth": "saved_models/face_alignment/depth_1.5-bc10f98e39.zip", }, } class FaceAlignment: def __init__( self, landmarks_type, network_size=NetworkSize.LARGE, device="cuda", dtype=torch.float32, flip_input=False, face_detector="sfd", face_detector_kwargs=None, verbose=False, ): self.device = device self.flip_input = flip_input self.landmarks_type = landmarks_type self.verbose = verbose self.dtype = dtype if version.parse(torch.__version__) < version.parse("1.5.0"): raise ImportError( "Unsupported pytorch version detected. Minimum supported version of pytorch: 1.5.0\ Either upgrade (recommended) your pytorch setup, or downgrade to face-alignment 1.2.0" ) network_size = int(network_size) pytorch_version = torch.__version__ if "dev" in pytorch_version: pytorch_version = pytorch_version.rsplit(".", 2)[0] else: pytorch_version = pytorch_version.rsplit(".", 1)[0] if "cuda" in device: torch.backends.cudnn.benchmark = True # Get the face detector face_detector_module = __import__( "face_alignment.detection." + face_detector, globals(), locals(), [face_detector], 0, ) face_detector_kwargs = face_detector_kwargs or {} self.face_detector = face_detector_module.FaceDetector( device=device, verbose=verbose, **face_detector_kwargs ) # Initialise the face alignemnt networks if landmarks_type == LandmarksType.TWO_D: network_name = "2DFAN-" + str(network_size) else: network_name = "3DFAN-" + str(network_size) self.face_alignment_net = torch.jit.load( models_urls.get(pytorch_version, default_model_urls)[network_name] ) self.face_alignment_net.to(device, dtype=dtype) self.face_alignment_net.eval() # Initialiase the depth prediciton network if landmarks_type == LandmarksType.THREE_D: self.depth_prediciton_net = torch.jit.load( models_urls.get(pytorch_version, default_model_urls)["depth"] ) self.depth_prediciton_net.to(device, dtype=dtype) self.depth_prediciton_net.eval() def get_landmarks( self, image_or_path, detected_faces=None, return_bboxes=False, return_landmark_score=False, ): """Deprecated, please use get_landmarks_from_image Arguments: image_or_path {string or numpy.array or torch.tensor} -- The input image or path to it Keyword Arguments: detected_faces {list of numpy.array} -- list of bounding boxes, one for each face found in the image (default: {None}) return_bboxes {boolean} -- If True, return the face bounding boxes in addition to the keypoints. return_landmark_score {boolean} -- If True, return the keypoint scores along with the keypoints. """ return self.get_landmarks_from_image( image_or_path, detected_faces, return_bboxes, return_landmark_score ) @torch.no_grad() def get_landmarks_from_image( self, image_or_path, detected_faces=None, return_bboxes=False, return_landmark_score=False, ): """Predict the landmarks for each face present in the image. This function predicts a set of 68 2D or 3D images, one for each image present. If detect_faces is None the method will also run a face detector. Arguments: image_or_path {string or numpy.array or torch.tensor} -- The input image or path to it. Keyword Arguments: detected_faces {list of numpy.array} -- list of bounding boxes, one for each face found in the image (default: {None}) return_bboxes {boolean} -- If True, return the face bounding boxes in addition to the keypoints. return_landmark_score {boolean} -- If True, return the keypoint scores along with the keypoints. Return: result: 1. if both return_bboxes and return_landmark_score are False, result will be: landmark 2. Otherwise, result will be one of the following, depending on the actual value of return_* arguments. (landmark, landmark_score, detected_face) (landmark, None, detected_face) (landmark, landmark_score, None ) """ image = get_image(image_or_path) # noqa if detected_faces is None: detected_faces = self.face_detector.detect_from_image(image.copy()) if len(detected_faces) == 0: warnings.warn("No faces were detected.") if return_bboxes or return_landmark_score: return None, None, None else: return None landmarks = [] landmarks_scores = [] for i, d in enumerate(detected_faces): center = torch.tensor( [d[2] - (d[2] - d[0]) / 2.0, d[3] - (d[3] - d[1]) / 2.0] ) center[1] = center[1] - (d[3] - d[1]) * 0.12 scale = (d[2] - d[0] + d[3] - d[1]) / self.face_detector.reference_scale inp = crop(image, center, scale) # noqa inp = torch.from_numpy(inp.transpose((2, 0, 1))).float() inp = inp.to(self.device, dtype=self.dtype) inp.div_(255.0).unsqueeze_(0) out = self.face_alignment_net(inp).detach() if self.flip_input: out += flip( self.face_alignment_net(flip(inp)).detach(), is_label=True ) # noqa out = out.to(device="cpu", dtype=torch.float32).numpy() pts, pts_img, scores = get_preds_fromhm(out, center.numpy(), scale) # noqa pts, pts_img = torch.from_numpy(pts), torch.from_numpy(pts_img) pts, pts_img = pts.view(68, 2) * 4, pts_img.view(68, 2) scores = scores.squeeze(0) if self.landmarks_type == LandmarksType.THREE_D: heatmaps = np.zeros((68, 256, 256), dtype=np.float32) for i in range(68): if pts[i, 0] > 0 and pts[i, 1] > 0: heatmaps[i] = draw_gaussian(heatmaps[i], pts[i], 2) # noqa heatmaps = torch.from_numpy(heatmaps).unsqueeze_(0) heatmaps = heatmaps.to(self.device, dtype=self.dtype) depth_pred = ( self.depth_prediciton_net(torch.cat((inp, heatmaps), 1)) .data.cpu() .view(68, 1) .to(dtype=torch.float32) ) pts_img = torch.cat( (pts_img, depth_pred * (1.0 / (256.0 / (200.0 * scale)))), 1 ) landmarks.append(pts_img.numpy()) landmarks_scores.append(scores) if not return_bboxes: detected_faces = None if not return_landmark_score: landmarks_scores = None if return_bboxes or return_landmark_score: return landmarks, landmarks_scores, detected_faces else: return landmarks @torch.no_grad() def get_landmarks_from_batch( self, image_batch, detected_faces=None, return_bboxes=False, return_landmark_score=False, ): """Predict the landmarks for each face present in the image. This function predicts a set of 68 2D or 3D images, one for each image in a batch in parallel. If detect_faces is None the method will also run a face detector. Arguments: image_batch {torch.tensor} -- The input images batch Keyword Arguments: detected_faces {list of numpy.array} -- list of bounding boxes, one for each face found in the image (default: {None}) return_bboxes {boolean} -- If True, return the face bounding boxes in addition to the keypoints. return_landmark_score {boolean} -- If True, return the keypoint scores along with the keypoints. Return: result: 1. if both return_bboxes and return_landmark_score are False, result will be: landmarks 2. Otherwise, result will be one of the following, depending on the actual value of return_* arguments. (landmark, landmark_score, detected_face) (landmark, None, detected_face) (landmark, landmark_score, None ) """ if detected_faces is None: detected_faces = self.face_detector.detect_from_batch(image_batch) if len(detected_faces) == 0: warnings.warn("No faces were detected.") if return_bboxes or return_landmark_score: return None, None, None else: return None landmarks = [] landmarks_scores_list = [] # A batch for each frame for i, faces in enumerate(detected_faces): res = self.get_landmarks_from_image( image_batch[i].cpu().numpy().transpose(1, 2, 0), detected_faces=faces, return_landmark_score=return_landmark_score, ) if return_landmark_score: landmark_set, landmarks_scores, _ = res landmarks_scores_list.append(landmarks_scores) else: landmark_set = res # Bacward compatibility if landmark_set is not None: landmark_set = np.concatenate(landmark_set, axis=0) else: landmark_set = [] landmarks.append(landmark_set) if not return_bboxes: detected_faces = None if not return_landmark_score: landmarks_scores_list = None if return_bboxes or return_landmark_score: return landmarks, landmarks_scores_list, detected_faces else: return landmarks def get_landmarks_from_directory( self, path, extensions=[".jpg", ".png"], recursive=True, show_progress_bar=True, return_bboxes=False, return_landmark_score=False, ): """Scan a directory for images with a given extension type(s) and predict the landmarks for each face present in the images found. Arguments: path {str} -- path to the target directory containing the images Keyword Arguments: extensions {list of str} -- list containing the image extensions considered (default: ['.jpg', '.png']) recursive {boolean} -- If True, scans for images recursively (default: True) show_progress_bar {boolean} -- If True displays a progress bar (default: True) return_bboxes {boolean} -- If True, return the face bounding boxes in addition to the keypoints. return_landmark_score {boolean} -- If True, return the keypoint scores along with the keypoints. """ dataset = FolderData( path, self.face_detector.tensor_or_path_to_ndarray, extensions, recursive, self.verbose, ) dataloader = torch.utils.data.DataLoader( dataset, batch_size=1, shuffle=False, num_workers=2, prefetch_factor=4 ) predictions = {} for (image_path, image) in tqdm(dataloader, disable=not show_progress_bar): image_path, image = image_path[0], image[0] bounding_boxes = self.face_detector.detect_from_image(image) if return_bboxes or return_landmark_score: preds, bbox, score = self.get_landmarks_from_image( image, bounding_boxes, return_bboxes=return_bboxes, return_landmark_score=return_landmark_score, ) predictions[image_path] = (preds, bbox, score) else: preds = self.get_landmarks_from_image(image, bounding_boxes) predictions[image_path] = preds return predictions ================================================ FILE: src/dot/fomm/modules/__init__.py ================================================ #!/usr/bin/env python3 ================================================ FILE: src/dot/fomm/modules/dense_motion.py ================================================ #!/usr/bin/env python3 import torch import torch.nn.functional as F from torch import nn from .util import AntiAliasInterpolation2d, Hourglass, kp2gaussian, make_coordinate_grid 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, num_channels, estimate_occlusion_map=False, scale_factor=1, kp_variance=0.01, ): super(DenseMotionNetwork, self).__init__() self.hourglass = Hourglass( block_expansion=block_expansion, in_features=(num_kp + 1) * (num_channels + 1), max_features=max_features, num_blocks=num_blocks, ) self.mask = nn.Conv2d( self.hourglass.out_filters, num_kp + 1, kernel_size=(7, 7), padding=(3, 3) ) if estimate_occlusion_map: self.occlusion = nn.Conv2d( self.hourglass.out_filters, 1, kernel_size=(7, 7), padding=(3, 3) ) else: self.occlusion = None self.num_kp = num_kp self.scale_factor = scale_factor self.kp_variance = kp_variance if self.scale_factor != 1: self.down = AntiAliasInterpolation2d(num_channels, self.scale_factor) def create_heatmap_representations(self, source_image, kp_driving, kp_source): """ Eq 6. in the paper H_k(z) """ spatial_size = source_image.shape[2:] gaussian_driving = kp2gaussian( kp_driving, spatial_size=spatial_size, kp_variance=self.kp_variance ) gaussian_source = kp2gaussian( kp_source, spatial_size=spatial_size, kp_variance=self.kp_variance ) heatmap = gaussian_driving - gaussian_source # adding background feature zeros = torch.zeros(heatmap.shape[0], 1, spatial_size[0], spatial_size[1]).type( heatmap.type() ) heatmap = torch.cat([zeros, heatmap], dim=1) heatmap = heatmap.unsqueeze(2) return heatmap def create_sparse_motions(self, source_image, kp_driving, kp_source): """ Eq 4. in the paper T_{s<-d}(z) """ bs, _, h, w = source_image.shape identity_grid = make_coordinate_grid((h, w), type=kp_source["value"].type()) identity_grid = identity_grid.view(1, 1, h, w, 2) coordinate_grid = identity_grid - kp_driving["value"].view( bs, self.num_kp, 1, 1, 2 ) if "jacobian" in kp_driving: jacobian = torch.matmul( kp_source["jacobian"], torch.inverse(kp_driving["jacobian"]) ) jacobian = jacobian.unsqueeze(-3).unsqueeze(-3) jacobian = jacobian.repeat(1, 1, 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, 2 ) # adding background feature identity_grid = identity_grid.repeat(bs, 1, 1, 1, 1) sparse_motions = torch.cat([identity_grid, driving_to_source], dim=1) return sparse_motions def create_deformed_source_image(self, source_image, sparse_motions): """ Eq 7. in the paper hat{T}_{s<-d}(z) """ bs, _, h, w = source_image.shape source_repeat = ( source_image.unsqueeze(1) .unsqueeze(1) .repeat(1, self.num_kp + 1, 1, 1, 1, 1) ) source_repeat = source_repeat.view(bs * (self.num_kp + 1), -1, h, w) sparse_motions = sparse_motions.view((bs * (self.num_kp + 1), h, w, -1)) sparse_deformed = F.grid_sample(source_repeat, sparse_motions) sparse_deformed = sparse_deformed.view((bs, self.num_kp + 1, -1, h, w)) return sparse_deformed def forward(self, source_image, kp_driving, kp_source): if self.scale_factor != 1: source_image = self.down(source_image) bs, _, h, w = source_image.shape out_dict = dict() heatmap_representation = self.create_heatmap_representations( source_image, kp_driving, kp_source ) sparse_motion = self.create_sparse_motions(source_image, kp_driving, kp_source) deformed_source = self.create_deformed_source_image(source_image, sparse_motion) out_dict["sparse_deformed"] = deformed_source input = torch.cat([heatmap_representation, deformed_source], dim=2) input = input.view(bs, -1, h, w) prediction = self.hourglass(input) mask = self.mask(prediction) mask = F.softmax(mask, dim=1) out_dict["mask"] = mask mask = mask.unsqueeze(2) sparse_motion = sparse_motion.permute(0, 1, 4, 2, 3) deformation = (sparse_motion * mask).sum(dim=1) deformation = deformation.permute(0, 2, 3, 1) out_dict["deformation"] = deformation # Sec. 3.2 in the paper if self.occlusion: occlusion_map = torch.sigmoid(self.occlusion(prediction)) out_dict["occlusion_map"] = occlusion_map return out_dict ================================================ FILE: src/dot/fomm/modules/generator_optim.py ================================================ #!/usr/bin/env python3 import torch import torch.nn.functional as F from torch import nn from .dense_motion import DenseMotionNetwork from .util import DownBlock2d, ResBlock2d, SameBlock2d, UpBlock2d class OcclusionAwareGenerator(nn.Module): """ Generator that given source image and keypoints try to transform image according to movement trajectories induced by keypoints. Generator follows Johnson architecture. """ def __init__( self, num_channels, num_kp, block_expansion, max_features, num_down_blocks, num_bottleneck_blocks, 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, num_channels=num_channels, estimate_occlusion_map=estimate_occlusion_map, **dense_motion_params ) else: self.dense_motion_network = None self.first = SameBlock2d( num_channels, 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) up_blocks = [] for i in range(num_down_blocks): in_features = min( max_features, block_expansion * (2 ** (num_down_blocks - i)) ) out_features = min( max_features, 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.bottleneck = torch.nn.Sequential() in_features = min(max_features, block_expansion * (2**num_down_blocks)) for i in range(num_bottleneck_blocks): self.bottleneck.add_module( "r" + str(i), ResBlock2d(in_features, kernel_size=(3, 3), padding=(1, 1)), ) self.final = nn.Conv2d( block_expansion, num_channels, kernel_size=(7, 7), padding=(3, 3) ) self.estimate_occlusion_map = estimate_occlusion_map self.num_channels = num_channels self.enc_features = None def deform_input(self, inp, deformation): _, h_old, w_old, _ = deformation.shape _, _, h, w = inp.shape if h_old != h or w_old != w: deformation = deformation.permute(0, 3, 1, 2) deformation = F.interpolate(deformation, size=(h, w), mode="bilinear") deformation = deformation.permute(0, 2, 3, 1) return F.grid_sample(inp, deformation) def encode_source(self, source_image): # Encoding (downsampling) part out = self.first(source_image) for i in range(len(self.down_blocks)): out = self.down_blocks[i](out) self.enc_features = out def forward(self, source_image, kp_driving, kp_source, optim_ret=True): assert self.enc_features is not None, "Call encode_source()" out = self.enc_features # Transforming feature representation # according to deformation and occlusion output_dict = {} if self.dense_motion_network is not None: dense_motion = self.dense_motion_network( source_image=source_image, kp_driving=kp_driving, kp_source=kp_source ) output_dict["mask"] = dense_motion["mask"] output_dict["sparse_deformed"] = dense_motion["sparse_deformed"] 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(out, deformation) 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 if not optim_ret: output_dict["deformed"] = self.deform_input(source_image, deformation) # Decoding part out = self.bottleneck(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 ================================================ FILE: src/dot/fomm/modules/keypoint_detector.py ================================================ #!/usr/bin/env python3 import torch import torch.nn.functional as F from torch import nn from .util import AntiAliasInterpolation2d, Hourglass, make_coordinate_grid class KPDetector(nn.Module): """ Detecting a keypoints. Return keypoint position and jacobian near each keypoint. """ def __init__( self, block_expansion, num_kp, num_channels, max_features, num_blocks, temperature, estimate_jacobian=False, scale_factor=1, single_jacobian_map=False, pad=0, ): super(KPDetector, self).__init__() self.predictor = Hourglass( block_expansion, in_features=num_channels, max_features=max_features, num_blocks=num_blocks, ) self.kp = nn.Conv2d( in_channels=self.predictor.out_filters, out_channels=num_kp, kernel_size=(7, 7), padding=pad, ) if estimate_jacobian: self.num_jacobian_maps = 1 if single_jacobian_map else num_kp self.jacobian = nn.Conv2d( in_channels=self.predictor.out_filters, out_channels=4 * self.num_jacobian_maps, kernel_size=(7, 7), padding=pad, ) self.jacobian.weight.data.zero_() self.jacobian.bias.data.copy_( torch.tensor([1, 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(num_channels, self.scale_factor) def gaussian2kp(self, heatmap): """ Extract the mean and 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)) 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, 4, final_shape[2], final_shape[3], ) heatmap = heatmap.unsqueeze(2) jacobian = heatmap * jacobian_map jacobian = jacobian.view(final_shape[0], final_shape[1], 4, -1) jacobian = jacobian.sum(dim=-1) jacobian = jacobian.view(jacobian.shape[0], jacobian.shape[1], 2, 2) out["jacobian"] = jacobian return out ================================================ FILE: src/dot/fomm/modules/util.py ================================================ #!/usr/bin/env python3 import torch import torch.nn.functional as F from torch import nn from ..sync_batchnorm.batchnorm import SynchronizedBatchNorm2d as BatchNorm2d 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) coordinate_grid = coordinate_grid.repeat(*repeats) # Preprocess kp shape shape = mean.shape[:number_of_leading_dimensions] + (1, 1, 2) 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(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 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 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 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 SameBlock2d(nn.Module): """ Simple block, preserve spatial resolution. """ def __init__(self, in_features, out_features, groups=1, kernel_size=3, padding=1): 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) def forward(self, x): out = self.conv(x) out = self.norm(out) out = F.relu(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( 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, ) ) 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( UpBlock2d(in_filters, out_filters, kernel_size=3, padding=1) ) self.up_blocks = nn.ModuleList(up_blocks) self.out_filters = block_expansion + in_features def forward(self, x): out = x.pop() for up_block in self.up_blocks: out = up_block(out) skip = x.pop() out = torch.cat([out, skip], dim=1) 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 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], indexing="xy", ) 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 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 = F.interpolate(out, scale_factor=(self.scale, self.scale)) return out ================================================ FILE: src/dot/fomm/option.py ================================================ #!/usr/bin/env python3 import os import sys import cv2 import numpy as np from ..commons import ModelOption from ..commons.cam.cam import ( draw_calib_text, draw_face_landmarks, draw_landmark_text, draw_rect, is_new_frame_better, ) from ..commons.utils import crop, log, pad_img, resize from .predictor_local import PredictorLocal def determine_path(): """ Find the script path """ try: root = __file__ if os.path.islink(root): root = os.path.realpath(root) return os.path.dirname(os.path.abspath(root)) except Exception as e: print(e) print("I'm sorry, but something is wrong.") print("There is no __file__ variable. Please contact the author.") sys.exit() class FOMMOption(ModelOption): def __init__( self, use_gpu: bool = True, use_mask: bool = False, crop_size: int = 256, gpen_type: str = None, gpen_path: str = None, offline: bool = False, ): super(FOMMOption, self).__init__( gpen_type=gpen_type, use_gpu=use_gpu, crop_size=crop_size, gpen_path=gpen_path, ) # use FOMM offline, video or image file self.offline = offline self.frame_proportion = 0.9 self.frame_offset_x = 0 self.frame_offset_y = 0 self.overlay_alpha = 0.0 self.preview_flip = False self.output_flip = False self.find_keyframe = False self.is_calibrated = True if self.offline else False self.show_landmarks = False self.passthrough = False self.green_overlay = False self.opt_relative = True self.opt_adapt_scale = True self.opt_enc_downscale = 1 self.opt_no_pad = True self.opt_in_port = 5557 self.opt_out_port = 5558 self.opt_hide_rect = False self.opt_in_addr = None self.opt_out_addr = None self.LANDMARK_SLICE_ARRAY = np.array([17, 22, 27, 31, 36, 42, 48, 60]) self.display_string = "" def create_model(self, model_path, **kwargs) -> None: # type: ignore opt_config = determine_path() + "/config/vox-adv-256.yaml" opt_checkpoint = model_path predictor_args = { "config_path": opt_config, "checkpoint_path": opt_checkpoint, "relative": self.opt_relative, "adapt_movement_scale": self.opt_adapt_scale, "enc_downscale": self.opt_enc_downscale, } self.predictor = PredictorLocal(**predictor_args) def change_option(self, image, **kwargs): if image.ndim == 2: image = np.tile(image[..., None], [1, 1, 3]) image = image[..., :3][..., ::-1] image = resize(image, (self.crop_size, self.crop_size)) print("Image shape ", image.shape) self.source_kp = self.predictor.get_frame_kp(image) self.kp_source = None self.predictor.set_source_image(image) self.source_image = image def handle_keyboard_input(self): key = cv2.waitKey(1) if key == ord("w"): self.frame_proportion -= 0.05 self.frame_proportion = max(self.frame_proportion, 0.1) elif key == ord("s"): self.frame_proportion += 0.05 self.frame_proportion = min(self.frame_proportion, 1.0) elif key == ord("H"): self.frame_offset_x -= 1 elif key == ord("h"): self.frame_offset_x -= 5 elif key == ord("K"): self.frame_offset_x += 1 elif key == ord("k"): self.frame_offset_x += 5 elif key == ord("J"): self.frame_offset_y -= 1 elif key == ord("j"): self.frame_offset_y -= 5 elif key == ord("U"): self.frame_offset_y += 1 elif key == ord("u"): self.frame_offset_y += 5 elif key == ord("Z"): self.frame_offset_x = 0 self.frame_offset_y = 0 self.frame_proportion = 0.9 elif key == ord("x"): self.predictor.reset_frames() if not self.is_calibrated: cv2.namedWindow("FOMM", cv2.WINDOW_GUI_NORMAL) cv2.moveWindow("FOMM", 600, 250) self.is_calibrated = True self.show_landmarks = False elif key == ord("z"): self.overlay_alpha = max(self.overlay_alpha - 0.1, 0.0) elif key == ord("c"): self.overlay_alpha = min(self.overlay_alpha + 0.1, 1.0) elif key == ord("r"): self.preview_flip = not self.preview_flip elif key == ord("t"): self.output_flip = not self.output_flip elif key == ord("f"): self.find_keyframe = not self.find_keyframe elif key == ord("o"): self.show_landmarks = not self.show_landmarks elif key == 48: self.passthrough = not self.passthrough elif key != -1: log(key) def process_image(self, image, use_gpu=True, **kwargs) -> np.array: if not self.offline: self.handle_keyboard_input() stream_img_size = image.shape[1], image.shape[0] frame = image[..., ::-1] frame, (frame_offset_x, frame_offset_y) = crop( frame, p=self.frame_proportion, offset_x=self.frame_offset_x, offset_y=self.frame_offset_y, ) frame = resize(frame, (self.crop_size, self.crop_size))[..., :3] if self.find_keyframe: if is_new_frame_better(log, self.source_image, frame, self.predictor): log("Taking new frame!") self.green_overlay = True self.predictor.reset_frames() if self.passthrough: out = frame elif self.is_calibrated: out = self.predictor.predict(frame) if out is None: log("predict returned None") else: out = None if self.overlay_alpha > 0: preview_frame = cv2.addWeighted( self.source_image, self.overlay_alpha, frame, 1.0 - self.overlay_alpha, 0.0, ) else: preview_frame = frame.copy() if self.show_landmarks: # Dim the background to make it easier to see the landmarks preview_frame = cv2.convertScaleAbs(preview_frame, alpha=0.5, beta=0.0) draw_face_landmarks( self.LANDMARK_SLICE_ARRAY, preview_frame, self.source_kp, (200, 20, 10) ) frame_kp = self.predictor.get_frame_kp(frame) draw_face_landmarks(self.LANDMARK_SLICE_ARRAY, preview_frame, frame_kp) preview_frame = cv2.flip(preview_frame, 1) if self.green_overlay: green_alpha = 0.8 overlay = preview_frame.copy() overlay[:] = (0, 255, 0) preview_frame = cv2.addWeighted( preview_frame, green_alpha, overlay, 1.0 - green_alpha, 0.0 ) if self.find_keyframe: preview_frame = cv2.putText( preview_frame, self.display_string, (10, 220), 0, 0.5 * self.crop_size / 256, (255, 255, 255), 1, ) if not self.is_calibrated: preview_frame = draw_calib_text(preview_frame) elif self.show_landmarks: preview_frame = draw_landmark_text(preview_frame) if not self.opt_hide_rect: draw_rect(preview_frame) if not self.offline: cv2.imshow("FOMM", preview_frame[..., ::-1]) if out is not None: if not self.opt_no_pad: out = pad_img(out, stream_img_size) if self.output_flip: out = cv2.flip(out, 1) return out[..., ::-1] else: return preview_frame[..., ::-1] ================================================ FILE: src/dot/fomm/predictor_local.py ================================================ #!/usr/bin/env python3 import numpy as np import torch import yaml from scipy.spatial import ConvexHull from . import face_alignment from .modules.generator_optim import OcclusionAwareGenerator from .modules.keypoint_detector import KPDetector 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 to_tensor(a): return torch.tensor(a[np.newaxis].astype(np.float32)).permute(0, 3, 1, 2) / 255 class PredictorLocal: def __init__( self, config_path, checkpoint_path, relative=False, adapt_movement_scale=False, device=None, enc_downscale=1, ): self.device = device or ("cuda" if torch.cuda.is_available() else "cpu") self.relative = relative self.adapt_movement_scale = adapt_movement_scale self.start_frame = None self.start_frame_kp = None self.kp_driving_initial = None self.config_path = config_path self.checkpoint_path = checkpoint_path self.generator, self.kp_detector = self.load_checkpoints() self.fa = face_alignment.FaceAlignment( face_alignment.LandmarksType.TWO_D, flip_input=True, device=self.device, face_detector_kwargs={ "path_to_detector": "saved_models/face_alignment/s3fd-619a316812.pth" }, ) self.source = None self.kp_source = None self.enc_downscale = enc_downscale def load_checkpoints(self): with open(self.config_path) as f: config = yaml.load(f, Loader=yaml.FullLoader) generator = OcclusionAwareGenerator( **config["model_params"]["generator_params"], **config["model_params"]["common_params"] ) generator.to(self.device) kp_detector = KPDetector( **config["model_params"]["kp_detector_params"], **config["model_params"]["common_params"] ) kp_detector.to(self.device) checkpoint = torch.load(self.checkpoint_path, map_location=self.device) generator.load_state_dict(checkpoint["generator"]) kp_detector.load_state_dict(checkpoint["kp_detector"]) generator.eval() kp_detector.eval() return generator, kp_detector def reset_frames(self): self.kp_driving_initial = None def set_source_image(self, source_image): self.source = to_tensor(source_image).to(self.device) self.kp_source = self.kp_detector(self.source) if self.enc_downscale > 1: h = int(self.source.shape[2] / self.enc_downscale) w = int(self.source.shape[3] / self.enc_downscale) source_enc = torch.nn.functional.interpolate( self.source, size=(h, w), mode="bilinear" ) else: source_enc = self.source self.generator.encode_source(source_enc) def predict(self, driving_frame): assert self.kp_source is not None, "call set_source_image()" with torch.no_grad(): driving = to_tensor(driving_frame).to(self.device) if self.kp_driving_initial is None: self.kp_driving_initial = self.kp_detector(driving) self.start_frame = driving_frame.copy() self.start_frame_kp = self.get_frame_kp(driving_frame) kp_driving = self.kp_detector(driving) kp_norm = normalize_kp( kp_source=self.kp_source, kp_driving=kp_driving, kp_driving_initial=self.kp_driving_initial, use_relative_movement=self.relative, use_relative_jacobian=self.relative, adapt_movement_scale=self.adapt_movement_scale, ) out = self.generator( self.source, kp_source=self.kp_source, kp_driving=kp_norm ) out = np.transpose(out["prediction"].data.cpu().numpy(), [0, 2, 3, 1])[0] out = (np.clip(out, 0, 1) * 255).astype(np.uint8) return out def get_frame_kp(self, image): kp_landmarks = self.fa.get_landmarks(image) if kp_landmarks: kp_image = kp_landmarks[0] kp_image = self.normalize_alignment_kp(kp_image) return kp_image else: return None @staticmethod def normalize_alignment_kp(kp): kp = kp - kp.mean(axis=0, keepdims=True) area = ConvexHull(kp[:, :2]).volume area = np.sqrt(area) kp[:, :2] = kp[:, :2] / area return kp def get_start_frame(self): return self.start_frame def get_start_frame_kp(self): return self.start_frame_kp ================================================ FILE: src/dot/fomm/sync_batchnorm/__init__.py ================================================ #!/usr/bin/env python3 # -*- 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. ================================================ FILE: src/dot/fomm/sync_batchnorm/batchnorm.py ================================================ #!/usr/bin/env python3 # -*- 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.nn.functional as F from torch.nn.modules.batchnorm import _BatchNorm from torch.nn.parallel._functions import Broadcast, ReduceAddCoalesced from .comm import SyncMaster __all__ = ["SynchronizedBatchNorm2d"] 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 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) ================================================ FILE: src/dot/fomm/sync_batchnorm/comm.py ================================================ #!/usr/bin/env python3 # -*- 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 collections import queue 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) ================================================ FILE: src/dot/gpen/__init__.py ================================================ #!/usr/bin/env python3 ================================================ FILE: src/dot/gpen/__init_paths.py ================================================ #!/usr/bin/env python3 """ @paper: GAN Prior Embedded Network for Blind Face Restoration in the Wild (CVPR2021) @author: yangxy (yangtao9009@gmail.com) """ import os.path as osp import sys def add_path(path): if path not in sys.path: sys.path.insert(0, path) this_dir = osp.dirname(__file__) path = osp.join(this_dir, "retinaface") add_path(path) path = osp.join(this_dir, "face_model") add_path(path) ================================================ FILE: src/dot/gpen/align_faces.py ================================================ #!/usr/bin/env python3 # -*- 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 # 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 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) # 2) resize the padded inner region size_bf_outer_pad = np.array(output_size) - np.array(outer_padding) * 2 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] tmp_5pts = tmp_5pts * scale_factor tmp_crop_size = size_bf_outer_pad # 3) add outer_padding to make output_size reference_5point = tmp_5pts + np.array(outer_padding) tmp_crop_size = output_size 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: 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: 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 == "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 == "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, :] face_img = cv2.warpAffine(src_img, tfm, (crop_size[0], crop_size[1]), flags=3) return face_img, tfm_inv ================================================ FILE: src/dot/gpen/face_enhancement.py ================================================ #!/usr/bin/env python3 """ @paper: GAN Prior Embedded Network for Blind Face Restoration in the Wild (CVPR2021) @author: yangxy (yangtao9009@gmail.com) """ import glob import os import cv2 import numpy as np from .align_faces import get_reference_facial_points, warp_and_crop_face from .face_model.face_gan import FaceGAN from .retinaface.retinaface_detection import RetinaFaceDetection class FaceEnhancement(object): def __init__( self, base_dir="./", size=512, model=None, channel_multiplier=2, narrow=1, use_gpu=True, ): self.facedetector = RetinaFaceDetection(base_dir, use_gpu=use_gpu) self.facegan = FaceGAN( base_dir, size, model, channel_multiplier, narrow, use_gpu=use_gpu ) self.size = size self.threshold = 0.9 # the mask for pasting restored faces back self.mask = np.zeros((512, 512), np.float32) cv2.rectangle(self.mask, (26, 26), (486, 486), (1, 1, 1), -1, cv2.LINE_AA) self.mask = cv2.GaussianBlur(self.mask, (101, 101), 11) self.mask = cv2.GaussianBlur(self.mask, (101, 101), 11) self.kernel = np.array( ([0.0625, 0.125, 0.0625], [0.125, 0.25, 0.125], [0.0625, 0.125, 0.0625]), dtype="float32", ) # get the reference 5 landmarks position in the crop settings default_square = True inner_padding_factor = 0.25 outer_padding = (0, 0) self.reference_5pts = get_reference_facial_points( (self.size, self.size), inner_padding_factor, outer_padding, default_square ) def process(self, img, use_gpu=True): facebs, landms = self.facedetector.detect(img, use_gpu=use_gpu) orig_faces, enhanced_faces = [], [] height, width = img.shape[:2] full_mask = np.zeros((height, width), dtype=np.float32) full_img = np.zeros(img.shape, dtype=np.uint8) for i, (faceb, facial5points) in enumerate(zip(facebs, landms)): if faceb[4] < self.threshold: continue fh, fw = (faceb[3] - faceb[1]), (faceb[2] - faceb[0]) facial5points = np.reshape(facial5points, (2, 5)) of, tfm_inv = warp_and_crop_face( img, facial5points, reference_pts=self.reference_5pts, crop_size=(self.size, self.size), ) # enhance the face ef = self.facegan.process(of, use_gpu=use_gpu) orig_faces.append(of) enhanced_faces.append(ef) tmp_mask = self.mask tmp_mask = cv2.resize(tmp_mask, ef.shape[:2]) tmp_mask = cv2.warpAffine(tmp_mask, tfm_inv, (width, height), flags=3) if min(fh, fw) < 100: # gaussian filter for small faces ef = cv2.filter2D(ef, -1, self.kernel) tmp_img = cv2.warpAffine(ef, tfm_inv, (width, height), flags=3) mask = tmp_mask - full_mask full_mask[np.where(mask > 0)] = tmp_mask[np.where(mask > 0)] full_img[np.where(mask > 0)] = tmp_img[np.where(mask > 0)] full_mask = full_mask[:, :, np.newaxis] img = cv2.convertScaleAbs(img * (1 - full_mask) + full_img * full_mask) return img, orig_faces, enhanced_faces if __name__ == "__main__": # model = {'name':'GPEN-BFR-512', 'size':512, 'channel_multiplier':2, 'narrow':1} model = { "name": "GPEN-BFR-256", "size": 256, "channel_multiplier": 1, "narrow": 0.5, } indir = "examples/imgs" outdir = "examples/outs-BFR" os.makedirs(outdir, exist_ok=True) faceenhancer = FaceEnhancement( size=model["size"], model=model["name"], channel_multiplier=model["channel_multiplier"], narrow=model["narrow"], ) files = sorted(glob.glob(os.path.join(indir, "*.*g"))) for n, file in enumerate(files[:]): filename = os.path.basename(file) im = cv2.imread(file, cv2.IMREAD_COLOR) # BGR if not isinstance(im, np.ndarray): print(filename, "error") continue im = cv2.resize(im, (0, 0), fx=2, fy=2) img, orig_faces, enhanced_faces = faceenhancer.process(im) cv2.imwrite( os.path.join(outdir, ".".join(filename.split(".")[:-1]) + "_COMP.jpg"), np.hstack((im, img)), ) cv2.imwrite( os.path.join(outdir, ".".join(filename.split(".")[:-1]) + "_GPEN.jpg"), img ) for m, (ef, of) in enumerate(zip(enhanced_faces, orig_faces)): of = cv2.resize(of, ef.shape[:2]) cv2.imwrite( os.path.join( outdir, ".".join(filename.split(".")[:-1]) + "_face%02d" % m + ".jpg", ), np.hstack((of, ef)), ) if n % 10 == 0: print(n, filename) ================================================ FILE: src/dot/gpen/face_model/__init__.py ================================================ #!/usr/bin/env python3 ================================================ FILE: src/dot/gpen/face_model/face_gan.py ================================================ #!/usr/bin/env python3 """ @paper: GAN Prior Embedded Network for Blind Face Restoration in the Wild (CVPR2021) @author: yangxy (yangtao9009@gmail.com) """ import os import cv2 import numpy as np import torch from .model import FullGenerator class FaceGAN(object): def __init__( self, base_dir="./", size=512, model=None, channel_multiplier=2, narrow=1, is_norm=True, use_gpu=True, ): self.mfile = os.path.join(base_dir, "weights", model + ".pth") self.n_mlp = 8 self.is_norm = is_norm self.resolution = size self.device = ( ("mps" if torch.backends.mps.is_available() else "cuda") if use_gpu else "cpu" ) self.load_model( channel_multiplier=channel_multiplier, narrow=narrow, use_gpu=use_gpu ) def load_model(self, channel_multiplier=2, narrow=1, use_gpu=True): if use_gpu: self.model = FullGenerator( self.resolution, 512, self.n_mlp, channel_multiplier, narrow=narrow ).to(self.device) pretrained_dict = torch.load(self.mfile, map_location=self.device) else: self.model = FullGenerator( self.resolution, 512, self.n_mlp, channel_multiplier, narrow=narrow ).cpu() pretrained_dict = torch.load(self.mfile, map_location=torch.device("cpu")) self.model.load_state_dict(pretrained_dict) self.model.eval() def process(self, img, use_gpu=True): img = cv2.resize(img, (self.resolution, self.resolution)) img_t = self.img2tensor(img, use_gpu) with torch.no_grad(): out, __ = self.model(img_t) out = self.tensor2img(out) return out def img2tensor(self, img, use_gpu=True): if use_gpu: img_t = torch.from_numpy(img).to(self.device) / 255.0 else: img_t = torch.from_numpy(img).cpu() / 255.0 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) ================================================ FILE: src/dot/gpen/face_model/model.py ================================================ #!/usr/bin/env python3 """ @paper: GAN Prior Embedded Network for Blind Face Restoration in the Wild (CVPR2021) @author: yangxy (yangtao9009@gmail.com) """ import itertools import math import random import torch from torch import nn from torch.nn import functional as F from .op.fused_act_v2 import FusedLeakyReLU_v2 as FusedLeakyReLU from .op.fused_act_v2 import fused_leaky_relu_v2 as fused_leaky_relu from .op.upfirdn2d_v2 import upfirdn2d_v2 as 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): 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) def forward(self, input): out = upfirdn2d(input, self.kernel, up=self.factor, down=1, pad=self.pad) return out class Downsample(nn.Module): def __init__(self, kernel, factor=2): 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) def forward(self, input): out = upfirdn2d(input, self.kernel, up=1, down=self.factor, pad=self.pad) return out class Blur(nn.Module): def __init__(self, kernel, pad, upsample_factor=1): super().__init__() kernel = make_kernel(kernel) if upsample_factor > 1: kernel = kernel * (upsample_factor**2) self.register_buffer("kernel", kernel) self.pad = pad def forward(self, input): out = upfirdn2d(input, self.kernel, pad=self.pad) 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 ): 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.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) 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], ): 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) 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)) 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, ): super().__init__() self.conv = ModulatedConv2d( in_channel, out_channel, kernel_size, style_dim, upsample=upsample, blur_kernel=blur_kernel, demodulate=demodulate, ) self.noise = NoiseInjection(isconcat) feat_multiplier = 2 if isconcat else 1 self.activate = FusedLeakyReLU(out_channel * feat_multiplier) def forward(self, input, style, noise=None): out = self.conv(input, style) out = self.noise(out, noise=noise) 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]): super().__init__() if upsample: self.upsample = Upsample(blur_kernel) self.conv = ModulatedConv2d(in_channel, 3, 1, style_dim, demodulate=False) 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, ): 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" ) ) 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, ) self.to_rgb1 = ToRGB( self.channels[4] * self.feat_multiplier, style_dim, upsample=False ) 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, ) ) self.convs.append( StyledConv( out_channel * self.feat_multiplier, out_channel, 3, style_dim, blur_kernel=blur_kernel, isconcat=isconcat, ) ) self.to_rgbs.append(ToRGB(out_channel * self.feat_multiplier, style_dim)) 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, ): 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))) 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)) 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]): super().__init__() self.conv1 = ConvLayer(in_channel, in_channel, 3) self.conv2 = ConvLayer(in_channel, out_channel, 3, downsample=True) 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, ): 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, ) conv = [ConvLayer(3, channels[size], 1)] 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 = [ConvLayer(in_channel, out_channel, 3, downsample=True)] 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") ) 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): 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)] 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)) 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) self.final_linear = nn.Sequential( EqualLinear(channels[4] * 4 * 4, channels[4], activation="fused_lrelu"), 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 ================================================ FILE: src/dot/gpen/face_model/op/__init__.py ================================================ #!/usr/bin/env python3 # from .fused_act import FusedLeakyReLU, fused_leaky_relu # from .upfirdn2d import upfirdn2d ================================================ FILE: src/dot/gpen/face_model/op/fused_act.py ================================================ #!/usr/bin/env python3 # This file is no longer used import os import torch from torch import nn from torch.autograd import Function from torch.utils.cpp_extension import load module_path = os.path.dirname(__file__) try: try: fused = load( "fused", sources=[ os.path.join(module_path, "fused_bias_act.cpp"), os.path.join(module_path, "fused_bias_act_kernel.cu"), ], ) except Exception as e: print(e) fused = load( "fused", sources=[ os.path.join(module_path, "fused_bias_act.cpp"), os.path.join(module_path, "fused_bias_act_kernel.cu"), ], ) except Exception as e: print(e) fused = load( "fused", sources=[ os.path.join(module_path, "fused_bias_act.cpp"), os.path.join(module_path, "fused_bias_act_kernel.cu"), ], ) 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): super().__init__() self.bias = nn.Parameter(torch.zeros(channel)) self.negative_slope = negative_slope self.scale = scale def forward(self, input): return fused_leaky_relu(input, self.bias, self.negative_slope, self.scale) def fused_leaky_relu(input, bias, negative_slope=0.2, scale=2**0.5): return FusedLeakyReLUFunction.apply(input, bias, negative_slope, scale) ================================================ FILE: src/dot/gpen/face_model/op/fused_act_v2.py ================================================ #!/usr/bin/env python3 import os import torch from torch import nn from torch.nn import functional as F module_path = os.path.dirname(__file__) class FusedLeakyReLU_v2(nn.Module): def __init__(self, channel, negative_slope=0.2, scale=2**0.5): super().__init__() self.bias = nn.Parameter(torch.zeros(channel)) self.negative_slope = negative_slope self.scale = scale def forward(self, input): return fused_leaky_relu_v2(input, self.bias, self.negative_slope, self.scale) def fused_leaky_relu_v2(input, bias, negative_slope=0.2, scale=2**0.5): rest_dim = [1] * (input.ndim - bias.ndim - 1) if input.ndim == 3: return ( F.leaky_relu( input + bias.view(1, *rest_dim, bias.shape[0]), negative_slope=negative_slope, ) * scale ) else: return ( F.leaky_relu( input + bias.view(1, bias.shape[0], *rest_dim), negative_slope=negative_slope, ) * scale ) ================================================ FILE: src/dot/gpen/face_model/op/fused_bias_act.cpp ================================================ // This file is no longer used #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)"); } ================================================ FILE: src/dot/gpen/face_model/op/fused_bias_act_kernel.cu ================================================ // This file is no longer used // 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; } ================================================ FILE: src/dot/gpen/face_model/op/upfirdn2d.cpp ================================================ // This file is no longer used #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)"); } ================================================ FILE: src/dot/gpen/face_model/op/upfirdn2d.py ================================================ #!/usr/bin/env python3 # This file is no longer used import os import torch import torch.nn.functional as F from torch.autograd import Function from torch.utils.cpp_extension import load 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"), ], ) 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.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(-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)): out = UpFirDn2d.apply( 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 ): _, 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, :] ================================================ FILE: src/dot/gpen/face_model/op/upfirdn2d_kernel.cu ================================================ // This file is no longer used // 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; } ================================================ FILE: src/dot/gpen/face_model/op/upfirdn2d_v2.py ================================================ #!/usr/bin/env python3 import os import torch from torch.nn import functional as F module_path = os.path.dirname(__file__) def upfirdn2d_v2(input, kernel, up=1, down=1, pad=(0, 0)): 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 ): _, channel, in_h, in_w = input.shape input = input.reshape(-1, in_h, in_w, 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) out = out[:, ::down_y, ::down_x, :] 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 return out.view(-1, channel, out_h, out_w) ================================================ FILE: src/dot/gpen/retinaface/__init__.py ================================================ #!/usr/bin/env python3 ================================================ FILE: src/dot/gpen/retinaface/data/FDDB/img_list.txt ================================================ 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 ================================================ FILE: src/dot/gpen/retinaface/data/__init__.py ================================================ #!/usr/bin/env python3 from dot.gpen.retinaface.data.config import cfg_mnet, cfg_re50 from dot.gpen.retinaface.data.data_augment import ( _crop, _distort, _expand, _mirror, _pad_to_square, _resize_subtract_mean, preproc, ) from dot.gpen.retinaface.data.wider_face import WiderFaceDetection, detection_collate ================================================ FILE: src/dot/gpen/retinaface/data/config.py ================================================ #!/usr/bin/env python3 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, } ================================================ FILE: src/dot/gpen/retinaface/data/data_augment.py ================================================ #!/usr/bin/env python3 import random import cv2 import numpy as np from ..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 length = random.randrange(width - w) if width == w: length = 0 if height == h: t = 0 else: t = random.randrange(height - h) roi = np.array((length, t, length + 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 ================================================ FILE: src/dot/gpen/retinaface/data/wider_face.py ================================================ #!/usr/bin/env python3 import cv2 import numpy as np import torch import torch.utils.data as data 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) ================================================ FILE: src/dot/gpen/retinaface/facemodels/__init__.py ================================================ #!/usr/bin/env python3 ================================================ FILE: src/dot/gpen/retinaface/facemodels/net.py ================================================ #!/usr/bin/env python3 import torch import torch.nn as nn import torch.nn.functional as F 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): 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 = x.view(-1, 256) x = self.fc(x) return x ================================================ FILE: src/dot/gpen/retinaface/facemodels/retinaface.py ================================================ #!/usr/bin/env python3 import torch import torch.nn as nn import torch.nn.functional as F import torchvision.models._utils as _utils from .net import FPN as FPN from .net import SSH as SSH from .net import MobileNetV1 as MobileNetV1 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 ================================================ FILE: src/dot/gpen/retinaface/layers/__init__.py ================================================ #!/usr/bin/env python3 ================================================ FILE: src/dot/gpen/retinaface/layers/functions/prior_box.py ================================================ #!/usr/bin/env python3 from itertools import product as product from math import ceil import torch 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 ================================================ FILE: src/dot/gpen/retinaface/layers/modules/__init__.py ================================================ #!/usr/bin/env python3 from .multibox_loss import MultiBoxLoss __all__ = ["MultiBoxLoss"] ================================================ FILE: src/dot/gpen/retinaface/layers/modules/multibox_loss.py ================================================ #!/usr/bin/env python3 import torch import torch.nn as nn import torch.nn.functional as F from ...data import cfg_mnet from ...utils.box_utils import log_sum_exp, match 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, ) device = "cpu" if GPU: device = "mps" if torch.backends.mps.is_available() else "cuda" loc_t = loc_t.to(device) conf_t = conf_t.to(device) landm_t = landm_t.to(device) zeros = torch.tensor(0).to(device) # 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 ================================================ FILE: src/dot/gpen/retinaface/retinaface_detection.py ================================================ #!/usr/bin/env python3 """ @paper: GAN Prior Embedded Network for Blind Face Restoration in the Wild (CVPR2021) @author: yangxy (yangtao9009@gmail.com) """ import os import numpy as np import torch import torch.backends.cudnn as cudnn from .data import cfg_re50 from .facemodels.retinaface import RetinaFace from .layers.functions.prior_box import PriorBox from .utils.box_utils import decode, decode_landm from .utils.nms.py_cpu_nms import py_cpu_nms class RetinaFaceDetection(object): def __init__(self, base_dir, network="RetinaFace-R50", use_gpu=True): torch.set_grad_enabled(False) cudnn.benchmark = True self.pretrained_path = os.path.join(base_dir, "weights", network + ".pth") if use_gpu: self.device = "mps" if torch.backends.mps.is_available() else "cuda" else: self.device = "cpu" self.cfg = cfg_re50 self.net = RetinaFace(cfg=self.cfg, phase="test") if use_gpu: self.load_model() self.net = self.net.to(self.device) else: self.load_model(load_to_cpu=True) self.net = self.net.cpu() 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 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.'""" return { (lambda x: x.split(prefix, 1)[-1] if x.startswith(prefix) else x)( 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.to("mps")#.cuda() # ) pretrained_dict = torch.load(self.pretrained_path, map_location=self.device) 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, use_gpu=True, ): img = np.float32(img_raw) 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) if use_gpu: img = img.to(self.device) scale = scale.to(self.device) else: img = img.cpu() scale = scale.cpu() loc, conf, landms = self.net(img) # forward pass priorbox = PriorBox(self.cfg, image_size=(im_height, im_width)) priors = priorbox.forward() if use_gpu: priors = priors.to(self.device) else: priors = priors.cpu() 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], ] ) if use_gpu: scale1 = scale1.to(self.device) else: scale1 = scale1.cpu() 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) 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, landms ================================================ FILE: src/dot/gpen/retinaface/utils/__init__.py ================================================ #!/usr/bin/env python3 ================================================ FILE: src/dot/gpen/retinaface/utils/box_utils.py ================================================ #!/usr/bin/env python3 import numpy as np import torch 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, boxes[:, 2:] - boxes[:, :2], 1 # cx, cy ) # 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)): best_truth_idx[best_prior_idx[j]] = j matches = truths[best_truth_idx] # Shape: [num_priors,4] conf = labels[best_truth_idx] # Shape: [num_priors] conf[best_truth_overlap < threshold] = 0 # label as background 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 = 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 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() count = 0 while idx.numel() > 0: i = idx[-1] # index of current largest val 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 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 ================================================ FILE: src/dot/gpen/retinaface/utils/nms/__init__.py ================================================ #!/usr/bin/env python3 ================================================ FILE: src/dot/gpen/retinaface/utils/nms/py_cpu_nms.py ================================================ #!/usr/bin/env python3 # -------------------------------------------------------- # 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 ================================================ FILE: src/dot/gpen/retinaface/utils/timer.py ================================================ #!/usr/bin/env python3 # -------------------------------------------------------- # 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.0 self.calls = 0 self.start_time = 0.0 self.diff = 0.0 self.average_time = 0.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.0 self.calls = 0 self.start_time = 0.0 self.diff = 0.0 self.average_time = 0.0 ================================================ FILE: src/dot/simswap/__init__.py ================================================ #!/usr/bin/env python3 from .option import SimswapOption __all__ = ["SimswapOption"] ================================================ FILE: src/dot/simswap/configs/config.yaml ================================================ --- analysis: simswap: parsing_model_path: saved_models/simswap/parsing_model/checkpoint/79999_iter.pth checkpoints_dir: saved_models/simswap/checkpoints arcface_model_path: saved_models/simswap/arcface_model/arcface_checkpoint.tar detection_threshold: 0.6 det_size: [640, 640] use_gpu: true show_fps: true opt_verbose: false opt_crop_size: 224 opt_gpu_ids: 0 opt_fp16: false opt_use_mask: true opt_name: people opt_resize_or_crop: scale_width opt_load_pretrain: '' opt_which_epoch: latest opt_continue_train: store_true gpen: gpen_256 gpen_path: saved_models/gpen ================================================ FILE: src/dot/simswap/configs/config_512.yaml ================================================ --- analysis: simswap: parsing_model_path: saved_models/simswap/parsing_model/checkpoint/79999_iter.pth checkpoints_dir: saved_models/simswap/checkpoints arcface_model_path: saved_models/simswap/arcface_model/arcface_checkpoint.tar detection_threshold: 0.6 det_size: [640, 640] use_gpu: true show_fps: true opt_verbose: false opt_crop_size: 512 opt_gpu_ids: 0 opt_fp16: false opt_use_mask: true opt_name: people opt_resize_or_crop: scale_width opt_load_pretrain: '' opt_which_epoch: '550000' opt_continue_train: store_true gpen: gpen_256 gpen_path: saved_models/gpen ================================================ FILE: src/dot/simswap/fs_model.py ================================================ #!/usr/bin/env python3 import os import sys import torch from .models.base_model import BaseModel def determine_path(): """ Find the script path """ try: root = __file__ if os.path.islink(root): root = os.path.realpath(root) return os.path.dirname(os.path.abspath(root)) except Exception as e: print(e) print("I'm sorry, but something is wrong.") print("There is no __file__ variable. Please contact the author.") sys.exit() sys.path.insert(0, determine_path()) # TODO: Move this class inside models class fsModel(BaseModel): def name(self): return "fsModel" def initialize( self, opt_gpu_ids, opt_checkpoints_dir, opt_name, opt_verbose, opt_crop_size, opt_resize_or_crop, opt_load_pretrain, opt_which_epoch, opt_continue_train, arcface_model_path, use_gpu=True, ): BaseModel.initialize( self, opt_gpu_ids, opt_checkpoints_dir, opt_name, opt_verbose ) torch.backends.cudnn.benchmark = True if use_gpu: device = torch.device( "mps" if torch.backends.mps.is_available() else "cuda" ) else: device = torch.device("cpu") if opt_crop_size == 224: from .models.fs_networks import Generator_Adain_Upsample elif opt_crop_size == 512: from .models.fs_networks_512 import Generator_Adain_Upsample # Generator network self.netG = Generator_Adain_Upsample( input_nc=3, output_nc=3, latent_size=512, n_blocks=9, deep=False ) self.netG.to(device) # Id network if use_gpu: netArc_checkpoint = torch.load(arcface_model_path) else: netArc_checkpoint = torch.load( arcface_model_path, map_location=torch.device("cpu") ) self.netArc = netArc_checkpoint self.netArc = self.netArc.to(device) self.netArc.eval() pretrained_path = "" self.load_network(self.netG, "G", opt_which_epoch, pretrained_path) return def forward(self, img_id, img_att, latent_id, latent_att, for_G=False): img_fake = self.netG.forward(img_att, latent_id) return img_fake def create_model( opt_verbose, opt_crop_size, opt_fp16, opt_gpu_ids, opt_checkpoints_dir, opt_name, opt_resize_or_crop, opt_load_pretrain, opt_which_epoch, opt_continue_train, arcface_model_path, use_gpu=True, ): model = fsModel() model.initialize( opt_gpu_ids, opt_checkpoints_dir, opt_name, opt_verbose, opt_crop_size, opt_resize_or_crop, opt_load_pretrain, opt_which_epoch, opt_continue_train, arcface_model_path, use_gpu=use_gpu, ) if opt_verbose: print("model [%s] was created" % (model.name())) return model ================================================ FILE: src/dot/simswap/mediapipe/__init__.py ================================================ #!/usr/bin/env python3 ================================================ FILE: src/dot/simswap/mediapipe/face_mesh.py ================================================ #!/usr/bin/env python3 from typing import List, Optional, Tuple import cv2 import mediapipe as mp import numpy as np from mediapipe.framework.formats.landmark_pb2 import NormalizedLandmark from .utils import face_align_ffhqandnewarc as face_align from .utils import mediapipe_landmarks mp_face_mesh = mp.solutions.face_mesh class FaceMesh: """Wrapper class of Mediapipe's FaceMesh module. Extracts facial landmarks and performs face alignment. Args: static_image_mode (bool, optional): Indicates whether to treat input images as separated images(not video-stream). Defaults to True. max_num_faces (int, optional): Maximum allowed faces to examine in single image. Defaults to 1. refine_landmarks (bool, optional): Used to reduce jitter across multiple input images. Ignored if `static_image_mode = True`. Defaults to True. min_detection_confidence (float, optional): Threshold for a detection to considered successfull. Defaults to 0.5. mode (str, optional): Either ['None' | 'ffhq']. Instructs `estimate_norm` function for face alignment mode. Defaults to "None". """ def __init__( self, static_image_mode: bool = True, max_num_faces: int = 1, refine_landmarks: bool = True, min_detection_confidence: float = 0.5, mode: str = "None", ): self.MediaPipeIds = mediapipe_landmarks.MediaPipeLandmarks self.static_image_mode = static_image_mode self.max_num_faces = max_num_faces self.refine_landmarks = refine_landmarks self.min_detection_confidence = min_detection_confidence self.mode = mode def _get_centroid(self, landmarks: List[NormalizedLandmark]) -> Tuple[float, float]: """Given a set of normalized landmarks/points finds centroid point Args: landmarks (List[NormalizedLandmark]): List of relative points that form a polygon Returns: Tuple[float, float]: x,y coordinates of polygon centroid """ x_li = [landmark.x for landmark in landmarks] y_li = [landmark.y for landmark in landmarks] _len = len(landmarks) return sum(x_li) / _len, sum(y_li) / _len def get_face_landmarks(self, image: np.ndarray) -> Optional[np.array]: """Calls FaceMesh module from Mediapipe and retrieves related landmarks. The order of landmarks is important for face alignment landmarks: [ [ Left Eye, Right Eye, Nose Tip, Left Mouth Tip, Right Mouth Tip ] ] Extracted landmarks are normalized points based on width/height of the image @Eyes, Mediapipe returns a list of landmarks that forms a polygon `_get_centroid` method returns middle point @Mouth, Mediapipe returns a list of landmarks that forms a polygon. Only edge points are needed, `min/max` on x-axis Args: image (np.ndarray): [description] Returns: Optional[np.array]: [description] """ # keypoints for all detected faces detection_kpss = [] with mp_face_mesh.FaceMesh( static_image_mode=self.static_image_mode, max_num_faces=self.max_num_faces, refine_landmarks=self.refine_landmarks, min_detection_confidence=self.min_detection_confidence, ) as face_mesh: # convert BGR image to RGB before processing. detection = face_mesh.process(cv2.cvtColor(image, cv2.COLOR_BGR2RGB)) if not detection.multi_face_landmarks: return None # get width/height to de-normalize relative points height, width, _ = image.shape for face_landmarks in detection.multi_face_landmarks: landmark_arr = np.empty((5, 2)) # left eye: gets landmarks(polygon) and calculates center point left_eye = [ face_landmarks.landmark[pt] for pt in self.MediaPipeIds.LEFT_EYE_OUTER ] centroid_left_eye = self._get_centroid(left_eye) landmark_arr[0] = np.array( (centroid_left_eye[0] * width, centroid_left_eye[1] * height) ) # right eye: gets landmarks(polygon) and calculates center point right_eye = [ face_landmarks.landmark[pt] for pt in self.MediaPipeIds.RIGHT_EYE_OUTER ] centroid_right_eye = self._get_centroid(right_eye) landmark_arr[1] = np.array( (centroid_right_eye[0] * width, centroid_right_eye[1] * height) ) # nose tip nose_landmark = face_landmarks.landmark[self.MediaPipeIds.NOSE_TIP] landmark_arr[2] = np.array( (nose_landmark.x * width, nose_landmark.y * height) ) # mouth region: finds the most left and most right point of outer lips region lips_outer_landmarks = [ face_landmarks.landmark[pt] for pt in self.MediaPipeIds.LIPS_OUTER ] mouth_most_left_point = min(lips_outer_landmarks, key=lambda x: x.x) mouth_most_right_point = max(lips_outer_landmarks, key=lambda x: x.x) landmark_arr[3] = np.array( ( mouth_most_left_point.x * width, mouth_most_left_point.y * height, ) ) landmark_arr[4] = np.array( ( mouth_most_right_point.x * width, mouth_most_right_point.y * height, ) ) detection_kpss.append(landmark_arr) return np.array(detection_kpss) def get( self, image: np.ndarray, crop_size: Tuple[int, int] ) -> Optional[Tuple[List, List]]: """Driver method of face alignment Args: image (np.ndarray): raw cv2 image crop_size (Tuple[int, int]): face alignment crop size Returns: Optional[Tuple[List, List]]: List of face aligned images for each detected person """ # gets facial landmarks using Face_Mesh model from MediaPipe landmarks = self.get_face_landmarks(image) if landmarks is None: print("ERROR: No face detected!") return None align_img_list = [] M_list = [] for i in range(landmarks.shape[0]): kps = landmarks[i] M, _ = face_align.estimate_norm(kps, crop_size, self.mode) align_img = cv2.warpAffine( image, M, (crop_size, crop_size), borderValue=0.0 ) align_img_list.append(align_img) M_list.append(M) return align_img_list, M_list ================================================ FILE: src/dot/simswap/mediapipe/utils/face_align_ffhqandnewarc.py ================================================ #!/usr/bin/env python3 import cv2 import numpy as np from skimage import transform as trans src1 = np.array( [ [51.642, 50.115], [57.617, 49.990], [35.740, 69.007], [51.157, 89.050], [57.025, 89.702], ], dtype=np.float32, ) # <--left src2 = np.array( [ [45.031, 50.118], [65.568, 50.872], [39.677, 68.111], [45.177, 86.190], [64.246, 86.758], ], dtype=np.float32, ) # ---frontal src3 = np.array( [ [39.730, 51.138], [72.270, 51.138], [56.000, 68.493], [42.463, 87.010], [69.537, 87.010], ], dtype=np.float32, ) # -->right src4 = np.array( [ [46.845, 50.872], [67.382, 50.118], [72.737, 68.111], [48.167, 86.758], [67.236, 86.190], ], dtype=np.float32, ) # -->right profile src5 = np.array( [ [54.796, 49.990], [60.771, 50.115], [76.673, 69.007], [55.388, 89.702], [61.257, 89.050], ], dtype=np.float32, ) src = np.array([src1, src2, src3, src4, src5]) src_map = src ffhq_src = np.array( [ [192.98138, 239.94708], [318.90277, 240.1936], [256.63416, 314.01935], [201.26117, 371.41043], [313.08905, 371.15118], ] ) ffhq_src = np.expand_dims(ffhq_src, axis=0) # lmk is prediction; src is template def estimate_norm(lmk, image_size=112, mode="ffhq"): assert lmk.shape == (5, 2) tform = trans.SimilarityTransform() lmk_tran = np.insert(lmk, 2, values=np.ones(5), axis=1) min_M = [] min_index = [] min_error = float("inf") if mode == "ffhq": src = ffhq_src * image_size / 512 else: src = src_map * image_size / 112 for i in np.arange(src.shape[0]): tform.estimate(lmk, src[i]) M = tform.params[0:2, :] results = np.dot(M, lmk_tran.T) results = results.T error = np.sum(np.sqrt(np.sum((results - src[i]) ** 2, axis=1))) if error < min_error: min_error = error min_M = M min_index = i return min_M, min_index def norm_crop(img, landmark, image_size=112, mode="ffhq"): if mode == "Both": M_None, _ = estimate_norm(landmark, image_size, mode="newarc") M_ffhq, _ = estimate_norm(landmark, image_size, mode="ffhq") warped_None = cv2.warpAffine( img, M_None, (image_size, image_size), borderValue=0.0 ) warped_ffhq = cv2.warpAffine( img, M_ffhq, (image_size, image_size), borderValue=0.0 ) return warped_ffhq, warped_None else: M, pose_index = estimate_norm(landmark, image_size, mode) warped = cv2.warpAffine(img, M, (image_size, image_size), borderValue=0.0) return warped def square_crop(im, S): if im.shape[0] > im.shape[1]: height = S width = int(float(im.shape[1]) / im.shape[0] * S) scale = float(S) / im.shape[0] else: width = S height = int(float(im.shape[0]) / im.shape[1] * S) scale = float(S) / im.shape[1] resized_im = cv2.resize(im, (width, height)) det_im = np.zeros((S, S, 3), dtype=np.uint8) det_im[: resized_im.shape[0], : resized_im.shape[1], :] = resized_im return det_im, scale def transform(data, center, output_size, scale, rotation): scale_ratio = scale rot = float(rotation) * np.pi / 180.0 t1 = trans.SimilarityTransform(scale=scale_ratio) cx = center[0] * scale_ratio cy = center[1] * scale_ratio t2 = trans.SimilarityTransform(translation=(-1 * cx, -1 * cy)) t3 = trans.SimilarityTransform(rotation=rot) t4 = trans.SimilarityTransform(translation=(output_size / 2, output_size / 2)) t = t1 + t2 + t3 + t4 M = t.params[0:2] cropped = cv2.warpAffine(data, M, (output_size, output_size), borderValue=0.0) return cropped, M def trans_points2d(pts, M): new_pts = np.zeros(shape=pts.shape, dtype=np.float32) for i in range(pts.shape[0]): pt = pts[i] new_pt = np.array([pt[0], pt[1], 1.0], dtype=np.float32) new_pt = np.dot(M, new_pt) new_pts[i] = new_pt[0:2] return new_pts def trans_points3d(pts, M): scale = np.sqrt(M[0][0] * M[0][0] + M[0][1] * M[0][1]) new_pts = np.zeros(shape=pts.shape, dtype=np.float32) for i in range(pts.shape[0]): pt = pts[i] new_pt = np.array([pt[0], pt[1], 1.0], dtype=np.float32) new_pt = np.dot(M, new_pt) new_pts[i][0:2] = new_pt[0:2] new_pts[i][2] = pts[i][2] * scale return new_pts def trans_points(pts, M): if pts.shape[1] == 2: return trans_points2d(pts, M) else: return trans_points3d(pts, M) ================================================ FILE: src/dot/simswap/mediapipe/utils/mediapipe_landmarks.py ================================================ #!/usr/bin/env python3 class MediaPipeLandmarks: """Defines facial landmark indexes for Google's MediaPipe""" LIPS_OUTER = [ 61, 185, 40, 39, 37, 0, 267, 269, 270, 409, 291, 375, 321, 405, 314, 17, 84, 181, 91, 146, 61, ] LIPS_INNER = [ 78, 191, 80, 81, 82, 13, 312, 311, 310, 415, 308, 324, 318, 402, 317, 14, 87, 178, 88, 95, 78, ] RIGHT_EYE_OUTER = [ 463, 414, 286, 258, 257, 259, 260, 467, 359, 255, 339, 254, 253, 252, 256, 341, ] LEFT_EYE_OUTER = [ 130, 247, 30, 29, 27, 28, 56, 190, 243, 112, 26, 22, 23, 24, 110, 25, ] NOSE_TIP = 4 ================================================ FILE: src/dot/simswap/models/__init__.py ================================================ #!/usr/bin/env python3 ================================================ FILE: src/dot/simswap/models/arcface_models.py ================================================ import math import torch import torch.nn.functional as F from torch import nn from torch.nn import Parameter from dot.simswap.parsing_model.resnet import conv3x3 class SEBlock(nn.Module): def __init__(self, channel, reduction=16): super(SEBlock, self).__init__() self.avg_pool = nn.AdaptiveAvgPool2d(1) 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 class IRBlock(nn.Module): expansion = 1 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 ResNet(nn.Module): def __init__(self, block, layers, use_se=True): self.inplanes = 64 self.use_se = use_se super(ResNet, self).__init__() self.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=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.bn2 = nn.BatchNorm2d(512) self.dropout = nn.Dropout() self.fc = nn.Linear(512 * 7 * 7, 512) self.bn3 = nn.BatchNorm1d(512) 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, 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 i in range(1, 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.bn2(x) x = self.dropout(x) # feature = x x = x.view(x.size(0), -1) x = self.fc(x) x = self.bn3(x) return x class ArcMarginModel(nn.Module): def __init__(self, args): super(ArcMarginModel, self).__init__() num_classes = 93431 self.weight = Parameter(torch.FloatTensor(num_classes, args.emb_size)) nn.init.xavier_uniform_(self.weight) self.easy_margin = args.easy_margin self.m = args.margin_m self.s = args.margin_s self.cos_m = math.cos(self.m) self.sin_m = math.sin(self.m) self.th = math.cos(math.pi - self.m) self.mm = math.sin(math.pi - self.m) * self.m def forward(self, input, label): x = F.normalize(input) W = F.normalize(self.weight) cosine = F.linear(x, W) sine = torch.sqrt(1.0 - torch.pow(cosine, 2)) phi = cosine * self.cos_m - sine * self.sin_m # cos(theta + m) if self.easy_margin: phi = torch.where(cosine > 0, phi, cosine) else: phi = torch.where(cosine > self.th, phi, cosine - self.mm) device = torch.device("cuda" if torch.cuda.is_available() else "cpu") one_hot = torch.zeros(cosine.size(), device=device) one_hot.scatter_(1, label.view(-1, 1).long(), 1) output = (one_hot * phi) + ((1.0 - one_hot) * cosine) output *= self.s return output ================================================ FILE: src/dot/simswap/models/base_model.py ================================================ #!/usr/bin/env python3 import os import sys import torch class BaseModel(torch.nn.Module): def name(self): return "BaseModel" def initialize(self, opt_gpu_ids, opt_checkpoints_dir, opt_name, opt_verbose): self.gpu_ids = opt_gpu_ids self.Tensor = torch.cuda.FloatTensor if self.gpu_ids else torch.Tensor self.save_dir = os.path.join(opt_checkpoints_dir, opt_name) self.opt_verbose = opt_verbose def set_input(self, input): self.input = input def forward(self): pass # used in test time, no backprop def test(self): pass def get_image_paths(self): pass def optimize_parameters(self): pass def get_current_visuals(self): return self.input def get_current_errors(self): return {} def save(self, label): pass # helper saving function that can be used by subclasses def save_network(self, network, network_label, epoch_label, gpu_ids): save_filename = "%s_net_%s.pth" % (epoch_label, network_label) save_path = os.path.join(self.save_dir, save_filename) torch.save(network.cpu().state_dict(), save_path) if len(gpu_ids) and torch.cuda.is_available(): network.cuda() # helper loading function that can be used by subclasses def load_network(self, network, network_label, epoch_label, save_dir=""): save_filename = "%s_net_%s.pth" % (epoch_label, network_label) if not save_dir: save_dir = self.save_dir save_path = os.path.join(save_dir, save_filename) if not os.path.isfile(save_path): print("%s not exists yet!" % save_path) if network_label == "G": raise ("Generator must exist!") else: try: network.load_state_dict(torch.load(save_path), strict=False) except Exception as e: print(e) pretrained_dict = torch.load(save_path) model_dict = network.state_dict() try: pretrained_dict = { k: v for k, v in pretrained_dict.items() if k in model_dict } network.load_state_dict(pretrained_dict) if self.opt_verbose: print( "Pretrained network %s has excessive layers;" "Only loading layers that are" "used" % network_label ) except Exception as e: print(e) print( "Pretrained network %s has fewer layers; The" "following are not initialized:" % network_label ) for k, v in pretrained_dict.items(): if v.size() == model_dict[k].size(): model_dict[k] = v if sys.version_info >= (3, 0): not_initialized = set() else: from sets import Set not_initialized = Set() for k, v in model_dict.items(): if (k not in pretrained_dict) or ( v.size() != pretrained_dict[k].size() ): not_initialized.add(k.split(".")[0]) print(sorted(not_initialized)) network.load_state_dict(model_dict) def update_learning_rate(self): pass ================================================ FILE: src/dot/simswap/models/fs_networks.py ================================================ #!/usr/bin/env python3 """ Copyright (C) 2019 NVIDIA Corporation. All rights reserved. Licensed under the CC BY-NC-SA 4.0 license (https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode). """ import torch import torch.nn as nn class InstanceNorm(nn.Module): def __init__(self, epsilon=1e-8): """ @notice: avoid in-place ops. https://discuss.pytorch.org/t/encounter-the-runtimeerror-one-of-the-variables-needed-for-gradient-computation-has-been-modified-by-an-inplace-operation/836/3 """ super(InstanceNorm, self).__init__() self.epsilon = epsilon def forward(self, x): x = x - torch.mean(x, (2, 3), True) tmp = torch.mul(x, x) # or x ** 2 tmp = torch.rsqrt(torch.mean(tmp, (2, 3), True) + self.epsilon) return x * tmp class ApplyStyle(nn.Module): """ @ref: https://github.com/lernapparat/lernapparat/blob/master/style_gan/pytorch_style_gan.ipynb """ def __init__(self, latent_size, channels): super(ApplyStyle, self).__init__() self.linear = nn.Linear(latent_size, channels * 2) def forward(self, x, latent): style = self.linear(latent) # style => [batch_size, n_channels*2] shape = [-1, 2, x.size(1), 1, 1] style = style.view(shape) # [batch_size, 2, n_channels, ...] x = x * (style[:, 0] * 1 + 1.0) + style[:, 1] * 1 return x class ResnetBlock_Adain(nn.Module): def __init__(self, dim, latent_size, padding_type, activation=nn.ReLU(True)): super(ResnetBlock_Adain, self).__init__() p = 0 conv1 = [] if padding_type == "reflect": conv1 += [nn.ReflectionPad2d(1)] elif padding_type == "replicate": conv1 += [nn.ReplicationPad2d(1)] elif padding_type == "zero": p = 1 else: raise NotImplementedError("padding [%s] is not implemented" % padding_type) conv1 += [nn.Conv2d(dim, dim, kernel_size=3, padding=p), InstanceNorm()] self.conv1 = nn.Sequential(*conv1) self.style1 = ApplyStyle(latent_size, dim) self.act1 = activation p = 0 conv2 = [] if padding_type == "reflect": conv2 += [nn.ReflectionPad2d(1)] elif padding_type == "replicate": conv2 += [nn.ReplicationPad2d(1)] elif padding_type == "zero": p = 1 else: raise NotImplementedError("padding [%s] is not implemented" % padding_type) conv2 += [nn.Conv2d(dim, dim, kernel_size=3, padding=p), InstanceNorm()] self.conv2 = nn.Sequential(*conv2) self.style2 = ApplyStyle(latent_size, dim) def forward(self, x, dlatents_in_slice): y = self.conv1(x) y = self.style1(y, dlatents_in_slice) y = self.act1(y) y = self.conv2(y) y = self.style2(y, dlatents_in_slice) out = x + y return out class Generator_Adain_Upsample(nn.Module): def __init__( self, input_nc, output_nc, latent_size, n_blocks=6, deep=False, norm_layer=nn.BatchNorm2d, padding_type="reflect", ): assert n_blocks >= 0 super(Generator_Adain_Upsample, self).__init__() activation = nn.ReLU(True) self.deep = deep self.first_layer = nn.Sequential( nn.ReflectionPad2d(3), nn.Conv2d(input_nc, 64, kernel_size=7, padding=0), norm_layer(64), activation, ) # downsample self.down1 = nn.Sequential( nn.Conv2d(64, 128, kernel_size=3, stride=2, padding=1), norm_layer(128), activation, ) self.down2 = nn.Sequential( nn.Conv2d(128, 256, kernel_size=3, stride=2, padding=1), norm_layer(256), activation, ) self.down3 = nn.Sequential( nn.Conv2d(256, 512, kernel_size=3, stride=2, padding=1), norm_layer(512), activation, ) if self.deep: self.down4 = nn.Sequential( nn.Conv2d(512, 512, kernel_size=3, stride=2, padding=1), norm_layer(512), activation, ) # resnet blocks BN = [] for i in range(n_blocks): BN += [ ResnetBlock_Adain( 512, latent_size=latent_size, padding_type=padding_type, activation=activation, ) ] self.BottleNeck = nn.Sequential(*BN) if self.deep: self.up4 = nn.Sequential( nn.Upsample(scale_factor=2, mode="bilinear"), nn.Conv2d(512, 512, kernel_size=3, stride=1, padding=1), nn.BatchNorm2d(512), activation, ) self.up3 = nn.Sequential( nn.Upsample(scale_factor=2, mode="bilinear"), nn.Conv2d(512, 256, kernel_size=3, stride=1, padding=1), nn.BatchNorm2d(256), activation, ) self.up2 = nn.Sequential( nn.Upsample(scale_factor=2, mode="bilinear"), nn.Conv2d(256, 128, kernel_size=3, stride=1, padding=1), nn.BatchNorm2d(128), activation, ) self.up1 = nn.Sequential( nn.Upsample(scale_factor=2, mode="bilinear"), nn.Conv2d(128, 64, kernel_size=3, stride=1, padding=1), nn.BatchNorm2d(64), activation, ) self.last_layer = nn.Sequential( nn.ReflectionPad2d(3), nn.Conv2d(64, output_nc, kernel_size=7, padding=0), nn.Tanh(), ) def forward(self, input, dlatents): x = input # 3*224*224 skip1 = self.first_layer(x) skip2 = self.down1(skip1) skip3 = self.down2(skip2) if self.deep: skip4 = self.down3(skip3) x = self.down4(skip4) else: x = self.down3(skip3) for i in range(len(self.BottleNeck)): x = self.BottleNeck[i](x, dlatents) if self.deep: x = self.up4(x) x = self.up3(x) x = self.up2(x) x = self.up1(x) x = self.last_layer(x) x = (x + 1) / 2 return x ================================================ FILE: src/dot/simswap/models/fs_networks_512.py ================================================ #!/usr/bin/env python3 """ Author: Naiyuan liu Github: https://github.com/NNNNAI Date: 2021-11-23 16:55:48 LastEditors: Naiyuan liu LastEditTime: 2021-11-24 16:58:06 Description: Copyright (C) 2019 NVIDIA Corporation. All rights reserved. Licensed under the CC BY-NC-SA 4.0 license (https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode). """ import torch import torch.nn as nn class InstanceNorm(nn.Module): def __init__(self, epsilon=1e-8): """ @notice: avoid in-place ops. https://discuss.pytorch.org/t/encounter-the-runtimeerror-one-of-the-variables-needed-for-gradient-computation-has-been-modified-by-an-inplace-operation/836/3 """ super(InstanceNorm, self).__init__() self.epsilon = epsilon def forward(self, x): x = x - torch.mean(x, (2, 3), True) tmp = torch.mul(x, x) # or x ** 2 tmp = torch.rsqrt(torch.mean(tmp, (2, 3), True) + self.epsilon) return x * tmp class ApplyStyle(nn.Module): """ @ref: https://github.com/lernapparat/lernapparat/blob/master/style_gan/pytorch_style_gan.ipynb """ def __init__(self, latent_size, channels): super(ApplyStyle, self).__init__() self.linear = nn.Linear(latent_size, channels * 2) def forward(self, x, latent): style = self.linear(latent) # style => [batch_size, n_channels*2] shape = [-1, 2, x.size(1), 1, 1] style = style.view(shape) # [batch_size, 2, n_channels, ...] x = x * (style[:, 0] * 1 + 1.0) + style[:, 1] * 1 return x class ResnetBlock_Adain(nn.Module): def __init__(self, dim, latent_size, padding_type, activation=nn.ReLU(True)): super(ResnetBlock_Adain, self).__init__() p = 0 conv1 = [] if padding_type == "reflect": conv1 += [nn.ReflectionPad2d(1)] elif padding_type == "replicate": conv1 += [nn.ReplicationPad2d(1)] elif padding_type == "zero": p = 1 else: raise NotImplementedError("padding [%s] is not implemented" % padding_type) conv1 += [nn.Conv2d(dim, dim, kernel_size=3, padding=p), InstanceNorm()] self.conv1 = nn.Sequential(*conv1) self.style1 = ApplyStyle(latent_size, dim) self.act1 = activation p = 0 conv2 = [] if padding_type == "reflect": conv2 += [nn.ReflectionPad2d(1)] elif padding_type == "replicate": conv2 += [nn.ReplicationPad2d(1)] elif padding_type == "zero": p = 1 else: raise NotImplementedError("padding [%s] is not implemented" % padding_type) conv2 += [nn.Conv2d(dim, dim, kernel_size=3, padding=p), InstanceNorm()] self.conv2 = nn.Sequential(*conv2) self.style2 = ApplyStyle(latent_size, dim) def forward(self, x, dlatents_in_slice): y = self.conv1(x) y = self.style1(y, dlatents_in_slice) y = self.act1(y) y = self.conv2(y) y = self.style2(y, dlatents_in_slice) out = x + y return out class Generator_Adain_Upsample(nn.Module): def __init__( self, input_nc, output_nc, latent_size, n_blocks=6, deep=False, norm_layer=nn.BatchNorm2d, padding_type="reflect", ): assert n_blocks >= 0 super(Generator_Adain_Upsample, self).__init__() activation = nn.ReLU(True) self.deep = deep self.first_layer = nn.Sequential( nn.ReflectionPad2d(3), nn.Conv2d(input_nc, 32, kernel_size=7, padding=0), norm_layer(32), activation, ) # downsample self.down0 = nn.Sequential( nn.Conv2d(32, 64, kernel_size=3, stride=2, padding=1), norm_layer(64), activation, ) self.down1 = nn.Sequential( nn.Conv2d(64, 128, kernel_size=3, stride=2, padding=1), norm_layer(128), activation, ) self.down2 = nn.Sequential( nn.Conv2d(128, 256, kernel_size=3, stride=2, padding=1), norm_layer(256), activation, ) self.down3 = nn.Sequential( nn.Conv2d(256, 512, kernel_size=3, stride=2, padding=1), norm_layer(512), activation, ) if self.deep: self.down4 = nn.Sequential( nn.Conv2d(512, 512, kernel_size=3, stride=2, padding=1), norm_layer(512), activation, ) # resnet blocks BN = [] for i in range(n_blocks): BN += [ ResnetBlock_Adain( 512, latent_size=latent_size, padding_type=padding_type, activation=activation, ) ] self.BottleNeck = nn.Sequential(*BN) if self.deep: self.up4 = nn.Sequential( nn.Upsample(scale_factor=2, mode="bilinear"), nn.Conv2d(512, 512, kernel_size=3, stride=1, padding=1), nn.BatchNorm2d(512), activation, ) self.up3 = nn.Sequential( nn.Upsample(scale_factor=2, mode="bilinear"), nn.Conv2d(512, 256, kernel_size=3, stride=1, padding=1), nn.BatchNorm2d(256), activation, ) self.up2 = nn.Sequential( nn.Upsample(scale_factor=2, mode="bilinear"), nn.Conv2d(256, 128, kernel_size=3, stride=1, padding=1), nn.BatchNorm2d(128), activation, ) self.up1 = nn.Sequential( nn.Upsample(scale_factor=2, mode="bilinear"), nn.Conv2d(128, 64, kernel_size=3, stride=1, padding=1), nn.BatchNorm2d(64), activation, ) self.up0 = nn.Sequential( nn.Upsample(scale_factor=2, mode="bilinear"), nn.Conv2d(64, 32, kernel_size=3, stride=1, padding=1), nn.BatchNorm2d(32), activation, ) self.last_layer = nn.Sequential( nn.ReflectionPad2d(3), nn.Conv2d(32, output_nc, kernel_size=7, padding=0), nn.Tanh(), ) def forward(self, input, dlatents): x = input # 3*224*224 skip0 = self.first_layer(x) skip1 = self.down0(skip0) skip2 = self.down1(skip1) skip3 = self.down2(skip2) if self.deep: skip4 = self.down3(skip3) x = self.down4(skip4) else: x = self.down3(skip3) for i in range(len(self.BottleNeck)): x = self.BottleNeck[i](x, dlatents) if self.deep: x = self.up4(x) x = self.up3(x) x = self.up2(x) x = self.up1(x) x = self.up0(x) x = self.last_layer(x) x = (x + 1) / 2 return x ================================================ FILE: src/dot/simswap/models/models.py ================================================ #!/usr/bin/env python3 import math import torch import torch.nn.functional as F from torch import nn from torch.nn import Parameter from dot.simswap.parsing_model.resnet import conv3x3 class SEBlock(nn.Module): def __init__(self, channel, reduction=16): super(SEBlock, self).__init__() self.avg_pool = nn.AdaptiveAvgPool2d(1) 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 # Todo Can this be removed? class IRBlock(nn.Module): expansion = 1 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 ResNet(nn.Module): def __init__(self, block, layers, use_se=True): self.inplanes = 64 self.use_se = use_se super(ResNet, self).__init__() self.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=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.bn2 = nn.BatchNorm2d(512) self.dropout = nn.Dropout() self.fc = nn.Linear(512 * 7 * 7, 512) self.bn3 = nn.BatchNorm1d(512) 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, 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 i in range(1, 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.bn2(x) x = self.dropout(x) x = x.view(x.size(0), -1) x = self.fc(x) x = self.bn3(x) return x class ArcMarginModel(nn.Module): def __init__(self, args): super(ArcMarginModel, self).__init__() num_classes = 93431 self.weight = Parameter(torch.FloatTensor(num_classes, args.emb_size)) nn.init.xavier_uniform_(self.weight) self.easy_margin = args.easy_margin self.m = args.margin_m self.s = args.margin_s self.cos_m = math.cos(self.m) self.sin_m = math.sin(self.m) self.th = math.cos(math.pi - self.m) self.mm = math.sin(math.pi - self.m) * self.m def forward(self, input, label): x = F.normalize(input) W = F.normalize(self.weight) cosine = F.linear(x, W) sine = torch.sqrt(1.0 - torch.pow(cosine, 2)) phi = cosine * self.cos_m - sine * self.sin_m # cos(theta + m) if self.easy_margin: phi = torch.where(cosine > 0, phi, cosine) else: phi = torch.where(cosine > self.th, phi, cosine - self.mm) device = torch.device("cuda" if torch.cuda.is_available() else "cpu") one_hot = torch.zeros(cosine.size(), device=device) one_hot.scatter_(1, label.view(-1, 1).long(), 1) output = (one_hot * phi) + ((1.0 - one_hot) * cosine) output *= self.s return output ================================================ FILE: src/dot/simswap/option.py ================================================ #!/usr/bin/env python3 import cv2 import numpy as np import torch import torch.nn.functional as F from PIL import Image from torchvision import transforms from dot.commons import ModelOption from dot.simswap.fs_model import create_model from dot.simswap.mediapipe.face_mesh import FaceMesh from dot.simswap.parsing_model.model import BiSeNet from dot.simswap.util.norm import SpecificNorm from dot.simswap.util.reverse2original import reverse2wholeimage from dot.simswap.util.util import _totensor class SimswapOption(ModelOption): """Extends `ModelOption` and initializes models.""" def __init__( self, use_gpu=True, use_mask=False, crop_size=224, gpen_type=None, gpen_path=None, ): super(SimswapOption, self).__init__( gpen_type=gpen_type, use_gpu=use_gpu, crop_size=crop_size, gpen_path=gpen_path, ) self.use_mask = use_mask def create_model( # type: ignore self, detection_threshold=0.6, det_size=(640, 640), opt_verbose=False, opt_crop_size=224, opt_gpu_ids=[0], opt_fp16=False, checkpoints_dir="./checkpoints", opt_name="people", opt_resize_or_crop="scale_width", opt_load_pretrain="", opt_which_epoch="latest", opt_continue_train="store_true", parsing_model_path="./parsing_model/checkpoint/79999_iter.pth", arcface_model_path="./arcface_model/arcface_checkpoint.tar", **kwargs ) -> None: # preprocess_f self.transformer_Arcface = transforms.Compose( [ transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), ] ) if opt_crop_size == 512: opt_which_epoch = 550000 opt_name = "512" self.mode = "ffhq" else: self.mode = "None" self.detect_model = FaceMesh( static_image_mode=True, max_num_faces=2, refine_landmarks=True, min_detection_confidence=0.5, mode=self.mode, ) # Tod check if we need this self.spNorm = SpecificNorm(use_gpu=self.use_gpu) if self.use_mask: n_classes = 19 self.net = BiSeNet(n_classes=n_classes) if self.use_gpu: device = "mps" if torch.backends.mps.is_available() else "cuda" self.net.to(device) self.net.load_state_dict( torch.load(parsing_model_path, map_location=device) ) else: self.net.cpu() self.net.load_state_dict( torch.load(parsing_model_path, map_location=torch.device("cpu")) ) self.net.eval() else: self.net = None torch.nn.Module.dump_patches = False # Model self.model = create_model( opt_verbose, opt_crop_size, opt_fp16, opt_gpu_ids, checkpoints_dir, opt_name, opt_resize_or_crop, opt_load_pretrain, opt_which_epoch, opt_continue_train, arcface_model_path, use_gpu=self.use_gpu, ) self.model.eval() def change_option(self, image: np.array, **kwargs) -> None: """Sets the source image in source/target pair face-swap. Args: image (np.array): Source image. """ img_a_align_crop, _ = self.detect_model.get(image, self.crop_size) img_a_align_crop_pil = Image.fromarray( cv2.cvtColor(img_a_align_crop[0], cv2.COLOR_BGR2RGB) ) img_a = self.transformer_Arcface(img_a_align_crop_pil) img_id = img_a.view(-1, img_a.shape[0], img_a.shape[1], img_a.shape[2]) # convert numpy to tensor if self.use_gpu: img_id = ( img_id.to("mps") if torch.backends.mps.is_available() else img_id.to("cuda") ) else: img_id = img_id.cpu() # create latent id img_id_downsample = F.interpolate(img_id, size=(112, 112)) source_image = self.model.netArc(img_id_downsample) source_image = source_image.detach().to("cpu") source_image = source_image / np.linalg.norm( source_image, axis=1, keepdims=True ) source_image = ( source_image.to("mps" if torch.backends.mps.is_available() else "cuda") if self.use_gpu else source_image.to("cpu") ) self.source_image = source_image def process_image(self, image: np.array, **kwargs) -> np.array: """Main process of simswap method. There are 3 main steps: * face detection and alignment of target image. * swap with `self.source_image`. * face segmentation and reverse to whole image. Args: image (np.array): Target frame where face from `self.source_image` will be swapped with. Returns: np.array: Resulted face-swap image """ detect_results = self.detect_model.get(image, self.crop_size) if detect_results is not None: frame_align_crop_list = detect_results[0] frame_mat_list = detect_results[1] swap_result_list = [] frame_align_crop_tenor_list = [] for frame_align_crop in frame_align_crop_list: if self.use_gpu: frame_align_crop_tenor = _totensor( cv2.cvtColor(frame_align_crop, cv2.COLOR_BGR2RGB) )[None, ...].to( "mps" if torch.backends.mps.is_available() else "cuda" ) else: frame_align_crop_tenor = _totensor( cv2.cvtColor(frame_align_crop, cv2.COLOR_BGR2RGB) )[None, ...].cpu() swap_result = self.model( None, frame_align_crop_tenor, self.source_image, None, True )[0] swap_result_list.append(swap_result) frame_align_crop_tenor_list.append(frame_align_crop_tenor) result_frame = reverse2wholeimage( frame_align_crop_tenor_list, swap_result_list, frame_mat_list, self.crop_size, image, pasring_model=self.net, use_mask=self.use_mask, norm=self.spNorm, use_gpu=self.use_gpu, use_cam=kwargs.get("use_cam", True), ) return result_frame else: return image ================================================ FILE: src/dot/simswap/parsing_model/__init__.py ================================================ #!/usr/bin/env python3 ================================================ FILE: src/dot/simswap/parsing_model/model.py ================================================ #!/usr/bin/python # -*- encoding: utf-8 -*- import torch import torch.nn as nn import torch.nn.functional as F from .resnet import Resnet18 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 ly.bias is not 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 ly.bias is not 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 module.bias is not 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 ly.bias is not 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 ly.bias is not 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 module.bias is not 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 ly.bias is not 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 module.bias is not 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:] # here return res3b1 feature feat_res8, feat_cp8, feat_cp16 = self.cp(x) # use res3b1 feature to replace spatial path feature feat_sp = feat_res8 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 ly.bias is not 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() ================================================ FILE: src/dot/simswap/parsing_model/resnet.py ================================================ #!/usr/bin/python # -*- encoding: utf-8 -*- import torch import torch.nn as nn import torch.nn.functional as F resnet18_url = "saved_models/simswap/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 = torch.load(resnet18_url, map_location=None, weights_only=False) 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 module.bias is not 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() ================================================ FILE: src/dot/simswap/util/__init__.py ================================================ #!/usr/bin/env python3 ================================================ FILE: src/dot/simswap/util/norm.py ================================================ #!/usr/bin/env python3 import numpy as np import torch import torch.nn as nn class SpecificNorm(nn.Module): def __init__(self, epsilon=1e-8, use_gpu=True): """ @notice: avoid in-place ops. https://discuss.pytorch.org/t/encounter-the-runtimeerror-one-of-the-variables-needed-for-gradient-computation-has-been-modified-by-an-inplace-operation/836/3 """ super(SpecificNorm, self).__init__() self.mean = np.array([0.485, 0.456, 0.406]) if use_gpu: self.mean = ( torch.from_numpy(self.mean) .float() .to("mps" if torch.backends.mps.is_available() else "cuda") ) else: self.mean = torch.from_numpy(self.mean).float().cpu() self.mean = self.mean.view([1, 3, 1, 1]) self.std = np.array([0.229, 0.224, 0.225]) if use_gpu: self.std = ( torch.from_numpy(self.std) .float() .to("mps" if torch.backends.mps.is_available() else "cuda") ) else: self.std = torch.from_numpy(self.std).float().cpu() self.std = self.std.view([1, 3, 1, 1]) def forward(self, x, use_gpu=True): mean = self.mean.expand([1, 3, x.shape[2], x.shape[3]]) std = self.std.expand([1, 3, x.shape[2], x.shape[3]]) if use_gpu: x = (x - mean) / std else: x = (x - mean.detach().to("cpu")) / std.detach().to("cpu") return x ================================================ FILE: src/dot/simswap/util/reverse2original.py ================================================ #!/usr/bin/env python3 import cv2 import kornia as K import numpy as np import torch import torch.nn as nn from kornia.geometry import transform as ko_transform from torch.nn import functional as F def isin(ar1, ar2): return (ar1[..., None] == ar2).any(-1) def encode_segmentation_rgb(segmentation, device, no_neck=True): parse = segmentation face_part_ids = ( [1, 2, 3, 4, 5, 6, 10, 12, 13] if no_neck else [1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 13, 14] ) mouth_id = [11] face_map = ( isin( parse, torch.tensor(face_part_ids).to(device), ) * 255.0 ).to(device) mouth_map = ( isin( parse, torch.tensor(mouth_id).to(device), ) * 255.0 ).to(device) mask_stack = torch.stack((face_map, mouth_map), axis=2) mask_out = torch.zeros([2, parse.shape[0], parse.shape[1]]).to(device) mask_out[0, :, :] = mask_stack[:, :, 0] mask_out[1, :, :] = mask_stack[:, :, 1] return mask_out class SoftErosion(nn.Module): def __init__(self, kernel_size=15, threshold=0.6, iterations=1): super(SoftErosion, self).__init__() r = kernel_size // 2 self.padding = r self.iterations = iterations self.threshold = threshold # Create kernel y_indices, x_indices = torch.meshgrid( torch.arange(0.0, kernel_size), torch.arange(0.0, kernel_size), indexing="xy", ) dist = torch.sqrt((x_indices - r) ** 2 + (y_indices - r) ** 2) kernel = dist.max() - dist kernel /= kernel.sum() kernel = kernel.view(1, 1, *kernel.shape) self.register_buffer("weight", kernel) def forward(self, x): x = x.float() for i in range(self.iterations - 1): x = torch.min( x, F.conv2d( x, weight=self.weight, groups=x.shape[1], padding=self.padding ), ) x = F.conv2d(x, weight=self.weight, groups=x.shape[1], padding=self.padding) mask = x >= self.threshold x[mask] = 1.0 x[~mask] /= x[~mask].max() return x, mask def postprocess(swapped_face, target, target_mask, smooth_mask, device): target_mask /= 255.0 face_mask_tensor = target_mask[0] + target_mask[1] soft_face_mask_tensor, _ = smooth_mask(face_mask_tensor.unsqueeze_(0).unsqueeze_(0)) soft_face_mask_tensor.squeeze_() soft_face_mask_tensor = soft_face_mask_tensor[None, :, :] result = swapped_face * soft_face_mask_tensor + target * (1 - soft_face_mask_tensor) return result def reverse2wholeimage( b_align_crop_tenor_list, swaped_imgs, mats, crop_size, oriimg, pasring_model=None, norm=None, use_mask=True, use_gpu=True, use_cam=True, ): device = torch.device( ("mps" if torch.backends.mps.is_available() else "cuda") if use_gpu else "cpu" ) if use_mask: smooth_mask = SoftErosion(kernel_size=17, threshold=0.9, iterations=7).to( device ) img = K.utils.image_to_tensor(oriimg).float().to(device) img /= 255.0 kernel_use_cam = torch.ones(5, 5).to(device) kernel_use_image = np.ones((40, 40), np.uint8) orisize = (oriimg.shape[0], oriimg.shape[1]) mat_rev_initial = np.ones([3, 3]) mat_rev_initial[2, :] = np.array([0.0, 0.0, 1.0]) for swaped_img, mat, source_img in zip(swaped_imgs, mats, b_align_crop_tenor_list): img_white = torch.full((1, 3, crop_size, crop_size), 1.0, dtype=torch.float).to( device ) # invert the Affine transformation matrix mat_rev_initial[0:2, :] = mat mat_rev = np.linalg.inv(mat_rev_initial.astype(np.float32)) mat_rev = mat_rev[:2, :] mat_rev = torch.tensor(mat_rev[None, ...]).to(device) if use_mask: source_img_norm = norm(source_img, use_gpu=use_gpu) source_img_512 = F.interpolate(source_img_norm, size=(512, 512)) out = pasring_model(source_img_512)[0] parsing = out.squeeze(0).argmax(0) tgt_mask = encode_segmentation_rgb(parsing, device) # If the mask is large if tgt_mask.sum() >= 5000: target_mask = ko_transform.resize(tgt_mask, (crop_size, crop_size)) target_image_parsing = postprocess( swaped_img, source_img[0], target_mask, smooth_mask, device=device, ) target_image_parsing = target_image_parsing[None, ...] swaped_img = swaped_img[None, ...] target_image = ko_transform.warp_affine( target_image_parsing, mat_rev, orisize ) else: swaped_img = swaped_img[None, ...] target_image = ko_transform.warp_affine( swaped_img, mat_rev, orisize, ) else: swaped_img = swaped_img[None, ...] target_image = ko_transform.warp_affine( swaped_img, mat_rev, orisize, ) img_white = ko_transform.warp_affine(img_white, mat_rev, orisize) img_white[img_white > 0.0784] = 1.0 if use_cam: img_white = K.morphology.erosion(img_white, kernel_use_cam) else: img_white = K.utils.tensor_to_image(img_white) * 255 img_white = cv2.erode(img_white, kernel_use_image, iterations=1) img_white = cv2.GaussianBlur(img_white, (41, 41), 0) img_white = K.utils.image_to_tensor(img_white).to(device) img_white /= 255.0 target_image = K.color.rgb_to_bgr(target_image) img = img_white * target_image + (1 - img_white) * img final_img = K.utils.tensor_to_image(img) final_img = (final_img * 255).astype(np.uint8) return final_img ================================================ FILE: src/dot/simswap/util/util.py ================================================ #!/usr/bin/env python3 from __future__ import print_function import os import cv2 import numpy as np import torch import torch.nn.functional as F from PIL import Image from ..parsing_model.model import BiSeNet # Converts a Tensor into a Numpy array # |imtype|: the desired type of the converted numpy array def tensor2im(image_tensor, imtype=np.uint8, normalize=True): if isinstance(image_tensor, list): image_numpy = [] for i in range(len(image_tensor)): image_numpy.append(tensor2im(image_tensor[i], imtype, normalize)) return image_numpy image_numpy = image_tensor.cpu().float().numpy() if normalize: image_numpy = (np.transpose(image_numpy, (1, 2, 0)) + 1) / 2.0 * 255.0 else: image_numpy = np.transpose(image_numpy, (1, 2, 0)) * 255.0 image_numpy = np.clip(image_numpy, 0, 255) if image_numpy.shape[2] == 1 or image_numpy.shape[2] > 3: image_numpy = image_numpy[:, :, 0] return image_numpy.astype(imtype) # Converts a one-hot tensor into a colorful label map def tensor2label(label_tensor, n_label, imtype=np.uint8): if n_label == 0: return tensor2im(label_tensor, imtype) label_tensor = label_tensor.cpu().float() if label_tensor.size()[0] > 1: label_tensor = label_tensor.max(0, keepdim=True)[1] label_tensor = Colorize(n_label)(label_tensor) label_numpy = np.transpose(label_tensor.numpy(), (1, 2, 0)) return label_numpy.astype(imtype) def save_image(image_numpy, image_path): image_pil = Image.fromarray(image_numpy) image_pil.save(image_path) def mkdirs(paths): if isinstance(paths, list) and not isinstance(paths, str): for path in paths: mkdir(path) else: mkdir(paths) def mkdir(path): if not os.path.exists(path): os.makedirs(path) ############################################################################### # Code from # https://github.com/ycszen/pytorch-seg/blob/master/transform.py # Modified so it complies with the Citscape label map colors ############################################################################### def uint82bin(n, count=8): """returns the binary of integer n, count refers to amount of bits""" return "".join([str((n >> y) & 1) for y in range(count - 1, -1, -1)]) def labelcolormap(N): if N == 35: # cityscape cmap = np.array( [ (0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0), (111, 74, 0), (81, 0, 81), (128, 64, 128), (244, 35, 232), (250, 170, 160), (230, 150, 140), (70, 70, 70), (102, 102, 156), (190, 153, 153), (180, 165, 180), (150, 100, 100), (150, 120, 90), (153, 153, 153), (153, 153, 153), (250, 170, 30), (220, 220, 0), (107, 142, 35), (152, 251, 152), (70, 130, 180), (220, 20, 60), (255, 0, 0), (0, 0, 142), (0, 0, 70), (0, 60, 100), (0, 0, 90), (0, 0, 110), (0, 80, 100), (0, 0, 230), (119, 11, 32), (0, 0, 142), ], dtype=np.uint8, ) else: cmap = np.zeros((N, 3), dtype=np.uint8) for i in range(N): r, g, b = 0, 0, 0 id = i for j in range(7): str_id = uint82bin(id) r = r ^ (np.uint8(str_id[-1]) << (7 - j)) g = g ^ (np.uint8(str_id[-2]) << (7 - j)) b = b ^ (np.uint8(str_id[-3]) << (7 - j)) id = id >> 3 cmap[i, 0] = r cmap[i, 1] = g cmap[i, 2] = b return cmap def _totensor(array): tensor = torch.from_numpy(array) img = tensor.transpose(0, 1).transpose(0, 2).contiguous() return img.float().div(255) def load_parsing_model(path, use_mask, use_gpu): if use_mask: n_classes = 19 net = BiSeNet(n_classes=n_classes) if use_gpu: net.to("mps" if torch.backends.mps.is_available() else "cuda") net.load_state_dict(torch.load(path)) else: net.cpu() net.load_state_dict(torch.load(path, map_location=torch.device("cpu"))) net.eval() return net else: return None def crop_align( detect_model, img_a_whole, crop_size, use_gpu, transformer_Arcface, swap_model ): face_detection = detect_model.get(img_a_whole, crop_size) if face_detection is None: return None img_a_align_crop, _ = face_detection img_a_align_crop_pil = Image.fromarray( cv2.cvtColor(img_a_align_crop[0], cv2.COLOR_BGR2RGB) ) img_a = transformer_Arcface(img_a_align_crop_pil) img_id = img_a.view(-1, img_a.shape[0], img_a.shape[1], img_a.shape[2]) # convert numpy to tensor img_id = ( img_id.to("mps") if torch.backends.mps.is_available() else "cuda" if use_gpu else img_id.cpu() ) # create latent id img_id_downsample = F.interpolate(img_id, size=(112, 112)) id_vector = swap_model.netArc(img_id_downsample) id_vector = id_vector.detach().to("cpu") id_vector = id_vector / np.linalg.norm(id_vector, axis=1, keepdims=True) id_vector = id_vector.to("cuda") if use_gpu else id_vector.to("cpu") return id_vector class Colorize(object): def __init__(self, n=35): self.cmap = labelcolormap(n) self.cmap = torch.from_numpy(self.cmap[:n]) def __call__(self, gray_image): size = gray_image.size() color_image = torch.ByteTensor(3, size[1], size[2]).fill_(0) for label in range(0, len(self.cmap)): mask = (label == gray_image[0]).cpu() color_image[0][mask] = self.cmap[label][0] color_image[1][mask] = self.cmap[label][1] color_image[2][mask] = self.cmap[label][2] return color_image ================================================ FILE: src/dot/ui/ui.py ================================================ #!/usr/bin/env python3 """ Copyright (c) 2022, Sensity B.V. All rights reserved. licensed under the BSD 3-Clause "New" or "Revised" License. """ import os import sys import tkinter import traceback from pathlib import Path import click import customtkinter import yaml from dot.__main__ import run customtkinter.set_appearance_mode("Dark") customtkinter.set_default_color_theme("blue") class ToolTip(object): def __init__(self, widget): self.widget = widget self.tipwindow = None self.id = None self.x = self.y = 0 def showtip(self, text): "Display text in tooltip window" self.text = text if self.tipwindow or not self.text: return x, y, cx, cy = self.widget.bbox("insert") x = x + self.widget.winfo_rootx() + 57 y = y + cy + self.widget.winfo_rooty() + 27 self.tipwindow = tw = tkinter.Toplevel(self.widget) tw.wm_overrideredirect(1) tw.wm_geometry("+%d+%d" % (x, y)) label = tkinter.Label( tw, text=self.text, justify=tkinter.LEFT, background="#ffffff", relief=tkinter.SOLID, borderwidth=1, font=("arial", "10", "normal"), ) label.pack(ipadx=8, ipady=5) def hidetip(self): tw = self.tipwindow self.tipwindow = None if tw: tw.destroy() class ToplevelUsageWindow(customtkinter.CTkToplevel): """ The class of the usage window """ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.title("Usage") self.geometry(f"{700}x{470}") self.resizable(False, False) self.attributes("-topmost", True) self.textbox = customtkinter.CTkTextbox( master=self, width=700, height=550, corner_radius=0 ) self.textbox.grid(row=0, column=0, sticky="nsew") self.textbox.insert( "0.0", """ source (str): The source image or video.\n target (Union[int, str]): The target image, video or camera id.\n config_file (str): Path to the configuration file for the deepfake.\n swap_type (str): The type of swap to run.\n gpen_type (str, optional): The type of gpen model to use. Defaults to None.\n gpen_path (str, optional): The path to the gpen models. Defaults to "saved_models/gpen".\n show_fps (bool, optional): Pass flag to show fps value. Defaults to False.\n use_gpu (bool, optional): Pass flag to use GPU else use CPU. Defaults to False.\n head_pose (bool): Estimates head pose before swap. Used by fomm.\n model_path (str, optional): The path to the model's weights. Defaults to None.\n parsing_model_path (str, optional): The path to the parsing model. Defaults to None.\n arcface_model_path (str, optional): The path to the arcface model. Defaults to None.\n checkpoints_dir (str, optional): The path to the checkpoints directory. Defaults to None.\n crop_size (int, optional): The size to crop the images to. Defaults to 224.\n """, ) self.textbox.configure(state=tkinter.DISABLED) class ToplevelAboutWindow(customtkinter.CTkToplevel): """ The class of the about window """ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.title("About DOT") self.geometry(f"{700}x{300}") self.resizable(False, False) self.attributes("-topmost", True) self.textbox = customtkinter.CTkTextbox( master=self, width=700, height=300, corner_radius=0 ) self.textbox.grid(row=0, column=0, sticky="nsew") self.textbox.insert( "0.0", """ DOT (aka Deepfake Offensive Toolkit) makes real-time, controllable deepfakes ready for virtual \n cameras injection. DOT is created for performing penetration testing against e.g. identity \n verification and video conferencing systems, for the use by security analysts, \n Red Team members, and biometrics researchers. DOT is developed for research and demonstration purposes. \n As an end user, you have the responsibility to obey all applicable laws when using this program. \n Authors and contributing developers assume no liability and are not responsible for any misuse \n or damage caused by the use of this program. """, ) self.textbox.configure(state=tkinter.DISABLED) class TabView: """ A class to handle the layout and functionality for each tab. """ def __init__(self, tab_view, target_tip_text, use_image=False, use_video=False): self.tab_view = tab_view self.target_tip_text = target_tip_text self.use_image = use_image self.use_video = use_video self.save_folder = None self.resources_path = "" # MacOS bundle has different resource directory structure if sys.platform == "darwin": if getattr(sys, "frozen", False): self.resources_path = os.path.join( str(Path(sys.executable).resolve().parents[0]).replace("MacOS", ""), "Resources", ) self.setup_ui() def setup_ui(self): # create entry text for source, target and config self.source_target_config_frame = customtkinter.CTkFrame(self.tab_view) self.source_target_config_frame.grid( row=0, column=0, padx=(20, 20), pady=(20, 0), sticky="nsew" ) self.source_label = customtkinter.CTkLabel( master=self.source_target_config_frame, text="source" ) self.source = customtkinter.CTkEntry( master=self.source_target_config_frame, placeholder_text="source", width=85, ) self.source_button = customtkinter.CTkButton( master=self.source_target_config_frame, fg_color="gray", text_color="white", text="Open", command=lambda: self.upload_file_action(self.source), width=10, ) self.target = customtkinter.CTkEntry( master=self.source_target_config_frame, placeholder_text="target", width=85 ) self.target_label = customtkinter.CTkLabel( master=self.source_target_config_frame, text="target" ) if (self.use_image) or (self.use_video): self.target_button = customtkinter.CTkButton( master=self.source_target_config_frame, fg_color="gray", text_color="white", text="Open", command=lambda: self.upload_file_action(self.target), width=10, ) self.save_folder = customtkinter.CTkEntry( master=self.source_target_config_frame, placeholder_text="save_folder", width=85, ) self.save_folder_label = customtkinter.CTkLabel( master=self.source_target_config_frame, text="save_folder" ) self.save_folder_button = customtkinter.CTkButton( master=self.source_target_config_frame, fg_color="gray", text_color="white", text="Open", command=lambda: self.upload_folder_action(self.save_folder), width=10, ) self.config_file_var = customtkinter.StringVar( value="Select" ) # set initial value self.config_file_combobox = customtkinter.CTkOptionMenu( master=self.source_target_config_frame, values=["fomm", "faceswap_cv2", "simswap", "simswaphq"], command=self.optionmenu_callback, variable=self.config_file_var, width=85, button_color="#3C3C3C", fg_color="#343638", dynamic_resizing=False, ) self.config_file = customtkinter.CTkEntry( master=self.source_target_config_frame, placeholder_text="config", width=85 ) self.config_file_label = customtkinter.CTkLabel( master=self.source_target_config_frame, text="config_file" ) self.config_file_button = customtkinter.CTkButton( master=self.source_target_config_frame, fg_color="gray", text_color="white", text="Open", command=lambda: self.upload_action_config_file( self.config_file_combobox, self.config_file_var ), width=10, ) self.source_label.grid(row=1, column=0, pady=(32, 10), padx=30, sticky="w") self.source.grid(row=1, column=0, pady=(32, 10), padx=(80, 20), sticky="w") self.source_button.grid( row=1, column=0, pady=(32, 10), padx=(175, 20), sticky="w", ) self.CreateToolTip( self.source, text="The path of the source directory that contains a set of images\n" "or the path of one image intended for utilization in the deepfake generation process", ) self.target.grid(row=2, column=0, pady=10, padx=(80, 20), sticky="w") if (self.use_image) or (self.use_video): self.target_button.grid( row=2, column=0, pady=(10, 10), padx=(175, 20), sticky="w", ) self.target_label.grid(row=2, column=0, pady=10, padx=(35, 20), sticky="w") if (not self.use_image) and (not self.use_video): self.target.insert(0, 0) self.CreateToolTip(self.target, text=self.target_tip_text) if (self.use_image) or (self.use_video): self.save_folder.grid(row=3, column=0, pady=10, padx=(80, 20), sticky="w") self.save_folder_button.grid( row=3, column=0, pady=(10, 10), padx=(175, 20), sticky="w", ) self.save_folder_label.grid(row=3, column=0, pady=10, padx=5, sticky="w") self.CreateToolTip(self.save_folder, text="The path to the save folder") self.config_file_combobox.grid( row=4, column=0, pady=10, padx=(80, 20), sticky="w" ) self.config_file_label.grid(row=4, column=0, pady=10, padx=10, sticky="w") self.config_file_button.grid( row=4, column=0, pady=10, padx=(175, 20), sticky="w", ) self.CreateToolTip( self.config_file_combobox, text="Configuration file for the deepfake" ) # create entry text for dot options self.option_entry_frame = customtkinter.CTkFrame(self.tab_view) self.option_entry_frame.grid( row=1, column=0, columnspan=4, padx=(20, 20), pady=(20, 0), sticky="nsew" ) self.advanced_options = customtkinter.CTkLabel( master=self.option_entry_frame, text="Advanced" ) self.model_path_label = customtkinter.CTkLabel( master=self.option_entry_frame, text="model_path" ) self.model_path = customtkinter.CTkEntry( master=self.option_entry_frame, placeholder_text="model_path", width=85 ) self.parsing_model_path_label = customtkinter.CTkLabel( master=self.option_entry_frame, text="parsing_model" ) self.parsing_model_path = customtkinter.CTkEntry( master=self.option_entry_frame, placeholder_text="parsing_model_path", width=85, ) self.arcface_model_path_label = customtkinter.CTkLabel( master=self.option_entry_frame, text="arcface_model" ) self.arcface_model_path = customtkinter.CTkEntry( master=self.option_entry_frame, placeholder_text="arcface_model_path", width=85, ) self.checkpoints_dir_label = customtkinter.CTkLabel( master=self.option_entry_frame, text="checkpoints_dir" ) self.checkpoints_dir = customtkinter.CTkEntry( master=self.option_entry_frame, placeholder_text="checkpoints_dir", width=85 ) self.gpen_path_label = customtkinter.CTkLabel( master=self.option_entry_frame, text="gpen_path" ) self.gpen_path = customtkinter.CTkEntry( master=self.option_entry_frame, placeholder_text="gpen_path", width=85 ) self.crop_size_label = customtkinter.CTkLabel( master=self.option_entry_frame, text="crop_size" ) self.crop_size = customtkinter.CTkEntry( master=self.option_entry_frame, placeholder_text="crop_size" ) self.model_path_button = customtkinter.CTkButton( master=self.option_entry_frame, fg_color="gray", text_color="white", text="Open", command=lambda: self.upload_file_action(self.model_path), width=10, ) self.parsing_model_path_button = customtkinter.CTkButton( master=self.option_entry_frame, fg_color="gray", text_color="white", text="Open", command=lambda: self.upload_file_action(self.parsing_model_path), width=10, ) self.arcface_model_path_button = customtkinter.CTkButton( master=self.option_entry_frame, fg_color="gray", text_color="white", text="Open", command=lambda: self.upload_file_action(self.arcface_model_path), width=10, ) self.checkpoints_dir_button = customtkinter.CTkButton( master=self.option_entry_frame, fg_color="gray", text_color="white", text="Open", command=lambda: self.upload_file_action(self.checkpoints_dir), width=10, ) self.gpen_path_button = customtkinter.CTkButton( master=self.option_entry_frame, fg_color="gray", text_color="white", text="Open", command=lambda: self.upload_file_action(self.gpen_path), width=10, ) self.advanced_options.grid(row=0, column=0, pady=10, padx=(20, 20), sticky="w") self.model_path_label.grid(row=1, column=2, pady=10, padx=(40, 20), sticky="w") self.model_path.grid(row=1, column=2, pady=10, padx=(115, 20), sticky="w") self.model_path_button.grid( row=1, column=2, pady=10, padx=(210, 20), sticky="w", ) self.parsing_model_path_label.grid( row=2, column=2, pady=10, padx=(23, 20), sticky="w" ) self.parsing_model_path.grid( row=2, column=2, pady=10, padx=(115, 20), sticky="w" ) self.parsing_model_path_button.grid( row=2, column=2, pady=10, padx=(210, 20), sticky="w", ) self.arcface_model_path_label.grid( row=3, column=2, pady=10, padx=(21, 20), sticky="w" ) self.arcface_model_path.grid( row=3, column=2, pady=10, padx=(115, 20), sticky="w" ) self.arcface_model_path_button.grid( row=3, column=2, pady=10, padx=(210, 20), sticky="w", ) self.checkpoints_dir_label.grid( row=1, column=3, pady=10, padx=(16, 20), sticky="w" ) self.checkpoints_dir.grid(row=1, column=3, pady=10, padx=(115, 20), sticky="w") self.checkpoints_dir_button.grid( row=1, column=3, pady=10, padx=(210, 20), sticky="w", ) self.gpen_path_label.grid(row=2, column=3, pady=10, padx=(48, 20), sticky="w") self.gpen_path.grid(row=2, column=3, pady=10, padx=(115, 20), sticky="w") self.gpen_path_button.grid( row=2, column=3, pady=10, padx=(210, 20), sticky="w", ) self.crop_size_label.grid(row=3, column=3, pady=10, padx=(50, 20), sticky="w") self.crop_size.grid(row=3, column=3, pady=10, padx=(115, 20), sticky="w") self.CreateToolTip(self.crop_size, text="The size of the image crop") self.CreateToolTip(self.gpen_path, text="The path to the gpen models") self.CreateToolTip( self.checkpoints_dir, text="The path to the checkpoints directory. Used by SimSwap", ) self.CreateToolTip( self.arcface_model_path, text="The path to the arcface model. Used by SimSwap", ) self.CreateToolTip( self.parsing_model_path, text="The path to the parsing model. Used by SimSwap", ) self.CreateToolTip( self.model_path, text="The path to the model's weights. Used by fomm" ) # create radiobutton frame for swap_type self.swap_type_frame = customtkinter.CTkFrame(self.tab_view) self.swap_type_frame.grid( row=0, column=1, padx=(20, 20), pady=(20, 0), sticky="nsew" ) self.swap_type_radio_var = tkinter.StringVar(value=None) self.swap_type_label_radio_group = customtkinter.CTkLabel( master=self.swap_type_frame, text="swap_type" ) self.swap_type_label_radio_group.grid( row=0, column=2, columnspan=1, padx=10, pady=10, sticky="" ) self.fomm_radio_button = customtkinter.CTkRadioButton( master=self.swap_type_frame, variable=self.swap_type_radio_var, value="fomm", text="fomm", ) self.fomm_radio_button.grid(row=1, column=2, pady=10, padx=20, sticky="w") self.CreateToolTip(self.fomm_radio_button, text="Use the deepfake from fomm") self.faceswap_cv2_radio_button = customtkinter.CTkRadioButton( master=self.swap_type_frame, variable=self.swap_type_radio_var, value="faceswap_cv2", text="faceswap_cv2", ) self.faceswap_cv2_radio_button.grid( row=2, column=2, pady=10, padx=20, sticky="w" ) self.CreateToolTip( self.faceswap_cv2_radio_button, text="Use the deepfake from faceswap cv2" ) self.simswap_radio_button = customtkinter.CTkRadioButton( master=self.swap_type_frame, variable=self.swap_type_radio_var, value="simswap", text="simswap", ) self.simswap_radio_button.grid(row=3, column=2, pady=10, padx=20, sticky="w") self.CreateToolTip( self.simswap_radio_button, text="Use the deepfake from SimSwap" ) # create radiobutton frame for gpen_type self.gpen_type_frame = customtkinter.CTkFrame(self.tab_view) self.gpen_type_frame.grid( row=0, column=2, padx=(20, 20), pady=(20, 0), sticky="nsew" ) self.gpen_type_radio_var = tkinter.StringVar(value="") self.gpen_type_label_radio_group = customtkinter.CTkLabel( master=self.gpen_type_frame, text="gpen_type" ) self.gpen_type_label_radio_group.grid( row=0, column=2, columnspan=1, padx=10, pady=10, sticky="" ) self.gpen_type_radio_button_1 = customtkinter.CTkRadioButton( master=self.gpen_type_frame, variable=self.gpen_type_radio_var, value="gpen_256", text="gpen_256", ) self.gpen_type_radio_button_1.grid( row=1, column=2, pady=10, padx=20, sticky="w" ) self.CreateToolTip( self.gpen_type_radio_button_1, text="Apply face restoration with GPEN 256" ) self.gpen_type_radio_button_2 = customtkinter.CTkRadioButton( master=self.gpen_type_frame, variable=self.gpen_type_radio_var, value="gpen_512", text="gpen_512", ) self.gpen_type_radio_button_2.grid( row=2, column=2, pady=10, padx=20, sticky="w" ) self.CreateToolTip( self.gpen_type_radio_button_2, text="Apply face restoration with GPEN 512" ) # create checkbox and switch frame self.checkbox_slider_frame = customtkinter.CTkFrame(self.tab_view) self.checkbox_slider_frame.grid( row=0, column=3, padx=(20, 20), pady=(20, 0), sticky="nsew" ) self.show_fps_checkbox_var = tkinter.IntVar() self.show_fps_checkbox = customtkinter.CTkCheckBox( master=self.checkbox_slider_frame, text="show_fps", variable=self.show_fps_checkbox_var, ) self.use_gpu_checkbox_var = tkinter.IntVar() self.use_gpu_checkbox = customtkinter.CTkCheckBox( master=self.checkbox_slider_frame, text="use_gpu", variable=self.use_gpu_checkbox_var, ) self.head_pose_checkbox_var = tkinter.IntVar() self.head_pose_checkbox = customtkinter.CTkCheckBox( master=self.checkbox_slider_frame, text="head_pose", variable=self.head_pose_checkbox_var, ) self.show_fps_checkbox.grid(row=1, column=3, pady=(39, 0), padx=20, sticky="w") self.use_gpu_checkbox.grid(row=2, column=3, pady=(20, 0), padx=20, sticky="w") self.head_pose_checkbox.grid(row=5, column=3, pady=(20, 0), padx=20, sticky="w") self.CreateToolTip(self.show_fps_checkbox, text="Show the fps value") self.CreateToolTip( self.use_gpu_checkbox, text="If checked, the deepfake will use the GPU.\n" "If it's not checked, the deepfake will use the CPU", ) self.CreateToolTip( self.head_pose_checkbox, text="Estimate head pose before swap. Used by fomm" ) # create run button self.error_label = customtkinter.CTkLabel( master=self.tab_view, text_color="red", text="" ) self.error_label.grid( row=4, column=0, columnspan=4, padx=(20, 20), pady=(0, 20), sticky="nsew" ) self.run_button = customtkinter.CTkButton( master=self.tab_view, fg_color="white", border_width=2, text_color="black", text="RUN", height=40, command=lambda: self.start_button_event(self.error_label), ) self.run_button.grid( row=2, column=1, columnspan=2, padx=(50, 150), pady=(20, 0), sticky="nsew" ) self.CreateToolTip(self.run_button, text="Start running the deepfake") self.run_label = customtkinter.CTkLabel( master=self.tab_view, text="The initial execution of dot may require a few minutes to complete.", text_color="gray", ) self.run_label.grid( row=3, column=0, columnspan=3, padx=(180, 0), pady=(0, 20), sticky="nsew" ) def modify_entry(self, entry_element: customtkinter.CTkEntry, text: str): """ Modify the value of the CTkEntry Args: entry_element (customtkinter.CTkOptionMenu): The CTkEntry element. text (str): The new text that will be inserted into the CTkEntry """ entry_element.delete(0, tkinter.END) entry_element.insert(0, text) def upload_action_config_file( self, element: customtkinter.CTkOptionMenu, config_file_var: customtkinter.StringVar, ): """ Set the configurations for the swap_type using the upload button Args: element (customtkinter.CTkOptionMenu): The OptionMenu element. config_file_var (customtkinter.StringVar): OptionMenu variable. """ entry_list = [ "source", "target", "model_path", "parsing_model_path", "arcface_model_path", "checkpoints_dir", "gpen_path", "crop_size", ] radio_list = ["swap_type"] filename = tkinter.filedialog.askopenfilename() config = {} if len(filename) > 0: with open(filename) as f: config = yaml.safe_load(f) if config["swap_type"] == "simswap": if config.get("swap_type", "0") == "512": config_file_var = "simswaphq" else: config_file_var = "simswap" else: config_file_var = config["swap_type"] element.set(config_file_var) for key, value in config.items(): if key in entry_list: self.modify_entry(getattr(self, key), value) elif key in radio_list: self.swap_type_radio_var = tkinter.StringVar(value=value) radio_button = getattr(self, f"{value}_radio_button") radio_button.invoke() for entry in entry_list: if (entry not in ["source", "target"]) and (entry not in config): self.modify_entry(getattr(self, entry), "") def CreateToolTip(self, widget, text): toolTip = ToolTip(widget) def enter(event): toolTip.showtip(text) def leave(event): toolTip.hidetip() widget.bind("", enter) widget.bind("", leave) def start_button_event(self, error_label): """ Start running the deepfake """ try: error_label.configure(text="") # load config, if provided config = {} if len(self.config_file.get()) > 0: with open(self.config_file.get()) as f: config = yaml.safe_load(f) # run dot run( swap_type=config.get( "swap_type", self.swap_type_radio_var.get() or None ), source=config.get("source", self.source.get() or None), target=config.get("target", self.target.get() or None), model_path=config.get("model_path", self.model_path.get() or None), parsing_model_path=config.get( "parsing_model_path", self.parsing_model_path.get() or None ), arcface_model_path=config.get( "arcface_model_path", self.arcface_model_path.get() or None ), checkpoints_dir=config.get( "checkpoints_dir", self.checkpoints_dir.get() or None ), gpen_type=config.get("gpen_type", self.gpen_type_radio_var.get()), gpen_path=config.get( "gpen_path", self.gpen_path.get() or "saved_models/gpen" ), crop_size=config.get( "crop_size", ( int(self.crop_size.get()) if len(self.crop_size.get()) > 0 else None ) or 224, ), head_pose=config.get("head_pose", int(self.head_pose_checkbox.get())), save_folder=self.save_folder.get() if self.save_folder is not None else None, show_fps=config.get("show_fps", int(self.show_fps_checkbox.get())), use_gpu=config.get("use_gpu", int(self.use_gpu_checkbox.get())), use_video=self.use_video, use_image=self.use_image, limit=None, ) except Exception as e: print(e) print(traceback.format_exc()) error_label.configure(text=e) def upload_folder_action(self, entry_element: customtkinter.CTkOptionMenu): """ Action for the upload folder buttons to update the value of a CTkEntry Args: entry_element (customtkinter.CTkOptionMenu): The CTkEntry element. """ foldername = tkinter.filedialog.askdirectory() self.modify_entry(entry_element, foldername) def upload_file_action(self, entry_element: customtkinter.CTkOptionMenu): """ Action for the upload file buttons to update the value of a CTkEntry Args: entry_element (customtkinter.CTkOptionMenu): The CTkEntry element. """ filename = tkinter.filedialog.askopenfilename() self.modify_entry(entry_element, filename) def optionmenu_callback(self, choice: str): """ Set the configurations for the swap_type using the optionmenu Args: choice (str): The type of swap to run. """ entry_list = ["source", "target", "crop_size"] radio_list = ["swap_type", "gpen_type"] model_list = [ "model_path", "parsing_model_path", "arcface_model_path", "checkpoints_dir", "gpen_path", ] config_file = os.path.join(self.resources_path, f"configs/{choice}.yaml") if os.path.isfile(config_file): config = {} with open(config_file) as f: config = yaml.safe_load(f) for key in config.keys(): if key in entry_list: self.modify_entry(eval(f"self.{key}"), config[key]) elif key in radio_list: if key == "swap_type": self.swap_type_radio_var = tkinter.StringVar(value=config[key]) elif key == "gpen_type": self.gpen_type_radio_var = tkinter.StringVar(value=config[key]) eval(f"self.{config[key]}_radio_button").invoke() elif key in model_list: self.modify_entry( eval(f"self.{key}"), os.path.join(self.resources_path, config[key]), ) for entry in entry_list: if entry not in ["source", "target"]: if entry not in config.keys(): self.modify_entry(eval(f"self.{entry}"), "") class App(customtkinter.CTk): """ The main class of the ui interface """ def __init__(self): super().__init__() # configure window self.title("Deepfake Offensive Toolkit") self.geometry(f"{835}x{600}") self.resizable(False, False) self.grid_columnconfigure((0, 1), weight=1) self.grid_rowconfigure((0, 1, 2, 3), weight=1) # create menubar menubar = tkinter.Menu(self) filemenu = tkinter.Menu(menubar, tearoff=0) filemenu.add_command(label="Exit", command=self.quit) menubar.add_cascade(label="File", menu=filemenu) helpmenu = tkinter.Menu(menubar, tearoff=0) helpmenu.add_command(label="Usage", command=self.usage_window) helpmenu.add_separator() helpmenu.add_command(label="About DOT", command=self.about_window) menubar.add_cascade(label="Help", menu=helpmenu) self.config(menu=menubar) self.toplevel_usage_window = None self.toplevel_about_window = None tabview = customtkinter.CTkTabview(self) tabview.pack(padx=0, pady=0) live_tab = tabview.add("Live") image_tab = tabview.add("Image") video_tab = tabview.add("Video") self.live_tab_view = TabView( live_tab, target_tip_text="The camera id. Usually 0 is the correct id" ) self.image_tab_view = TabView( image_tab, target_tip_text="target images folder or certain image file", use_image=True, ) self.video_tab_view = TabView( video_tab, target_tip_text="target videos folder or certain video file", use_video=True, ) def usage_window(self): """ Open the usage window """ if ( self.toplevel_usage_window is None or not self.toplevel_usage_window.winfo_exists() ): self.toplevel_usage_window = ToplevelUsageWindow( self ) # create window if its None or destroyed self.toplevel_usage_window.focus() def about_window(self): """ Open the about window """ if ( self.toplevel_about_window is None or not self.toplevel_about_window.winfo_exists() ): self.toplevel_about_window = ToplevelAboutWindow( self ) # create window if its None or destroyed self.toplevel_about_window.focus() @click.command() def main(): """Run the dot UI.""" app = App() app.mainloop() if __name__ == "__main__": main() ================================================ FILE: tests/pipeline_test.py ================================================ #!/usr/bin/env python3 """ Copyright (c) 2022, Sensity B.V. All rights reserved. licensed under the BSD 3-Clause "New" or "Revised" License. """ import unittest from unittest import mock from dot import DOT def fake_generate(self, option, source, target, show_fps=False, **kwargs): return [[None], [None]] @mock.patch.object(DOT, "generate", fake_generate) class TestDotOptions(unittest.TestCase): def setUp(self): self._dot = DOT(use_image=True, save_folder="./tests") self.faceswap_cv2_option = self._dot.faceswap_cv2(False, False, None) self.fomm_option = self._dot.fomm(False, False, None) self.simswap_option = self._dot.simswap(False, False, None) def test_option_creation(self): success, rejected = self._dot.generate( self.faceswap_cv2_option, "./tests", "./tests", show_fps=False, model_path=None, limit=5, ) assert len(success) == 1 assert len(rejected) == 1 success, rejected = self._dot.generate( self.fomm_option, "./tests", "./tests", show_fps=False, model_path=None, limit=5, ) assert len(success) == 1 assert len(rejected) == 1 success, rejected = self._dot.generate( self.simswap_option, "./tests", "./tests", show_fps=False, parsing_model_path=None, arcface_model_path=None, checkpoints_dir=None, limit=5, ) assert len(success) == 1 assert len(rejected) == 1