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
[](https://github.com/sensity-ai/dot/stargazers)
[](https://github.com/sensity-ai/dot/blob/main/LICENSE)
[](https://www.python.org/downloads/release/python-3812/)
[](https://github.com/sensity-ai/dot/actions/workflows/build_dot.yaml)
[](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://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": [
"
"
]
},
{
"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