minhho commited on
Commit
69532ce
·
1 Parent(s): 75d3319

Deploy FaceSwapLite application with full documentation

Browse files
.gitattributes CHANGED
@@ -33,3 +33,7 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ demo.gif filter=lfs diff=lfs merge=lfs -text
37
+ *.jpg filter=lfs diff=lfs merge=lfs -text
38
+ *.jpeg filter=lfs diff=lfs merge=lfs -text
39
+ *.png filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ build/
8
+ develop-eggs/
9
+ dist/
10
+ downloads/
11
+ eggs/
12
+ .eggs/
13
+ lib/
14
+ lib64/
15
+ parts/
16
+ sdist/
17
+ var/
18
+ wheels/
19
+ *.egg-info/
20
+ .installed.cfg
21
+ *.egg
22
+ MANIFEST
23
+
24
+ # Virtual environments
25
+ venv/
26
+ env/
27
+ ENV/
28
+ env.bak/
29
+ venv.bak/
30
+
31
+ # IDE
32
+ .vscode/
33
+ .idea/
34
+ *.swp
35
+ *.swo
36
+ *~
37
+ .DS_Store
38
+
39
+ # Model files (large files)
40
+ *.onnx
41
+ *.pth
42
+ *.pt
43
+ *.bin
44
+
45
+ # InsightFace models cache
46
+ .insightface/
47
+
48
+ # Output files
49
+ out/*
50
+ !out/.gitkeep
51
+ *.mp4
52
+ *.avi
53
+ *.mov
54
+ *.mkv
55
+
56
+ # Test files
57
+ test_*.py
58
+ testsrc.mp4
59
+ *.jpg
60
+ *.png
61
+ *.jpeg
62
+ !demo.gif
63
+ !image.jpg
64
+
65
+ # Logs
66
+ *.log
67
+
68
+ # System
69
+ .DS_Store
70
+ Thumbs.db
71
+
72
+ # Temporary files
73
+ tmp/
74
+ temp/
75
+ *.tmp
76
+ image.jpg
DEPLOYMENT.md ADDED
@@ -0,0 +1,228 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Deployment Guide for Hugging Face Spaces
2
+
3
+ This guide will help you deploy FaceSwapLite to Hugging Face Spaces.
4
+
5
+ ## Prerequisites
6
+
7
+ 1. A Hugging Face account (create one at https://huggingface.co/join)
8
+ 2. Git installed on your system
9
+ 3. Hugging Face CLI installed: `pip install huggingface_hub`
10
+ 4. An access token with write permissions from https://huggingface.co/settings/tokens
11
+
12
+ ## Method 1: Deploy via Git (Recommended)
13
+
14
+ ### Step 1: Clone Your Space Repository
15
+
16
+ ```bash
17
+ git clone https://huggingface.co/spaces/minhho/FaceSwapLite-1.0
18
+ cd FaceSwapLite-1.0
19
+ ```
20
+
21
+ When prompted for a password, use your Hugging Face access token.
22
+
23
+ ### Step 2: Copy Project Files
24
+
25
+ Copy all the necessary files from your local FaceSwapLite directory to the cloned space directory:
26
+
27
+ ```bash
28
+ # Copy from your local project to the space directory
29
+ cp /path/to/local/FaceSwapLite/app.py .
30
+ cp /path/to/local/FaceSwapLite/refacer.py .
31
+ cp /path/to/local/FaceSwapLite/script.py .
32
+ cp /path/to/local/FaceSwapLite/requirements.txt .
33
+ cp /path/to/local/FaceSwapLite/README.md .
34
+ cp -r /path/to/local/FaceSwapLite/recognition .
35
+ cp /path/to/local/FaceSwapLite/inswapper_128.onnx .
36
+ ```
37
+
38
+ ### Step 3: Create the Output Directory
39
+
40
+ ```bash
41
+ mkdir -p out
42
+ touch out/.gitkeep
43
+ ```
44
+
45
+ ### Step 4: Add and Commit Files
46
+
47
+ ```bash
48
+ git add .
49
+ git commit -m "Initial deployment of FaceSwapLite"
50
+ ```
51
+
52
+ ### Step 5: Push to Hugging Face
53
+
54
+ ```bash
55
+ git push
56
+ ```
57
+
58
+ When prompted for a password, use your Hugging Face access token.
59
+
60
+ ### Step 6: Wait for Build
61
+
62
+ - Go to https://huggingface.co/spaces/minhho/FaceSwapLite-1.0
63
+ - Wait for the space to build (this may take 5-10 minutes)
64
+ - Check the build logs for any errors
65
+
66
+ ## Method 2: Deploy via Hugging Face CLI
67
+
68
+ ### Step 1: Login to Hugging Face
69
+
70
+ ```bash
71
+ huggingface-cli login
72
+ ```
73
+
74
+ Enter your access token when prompted.
75
+
76
+ ### Step 2: Upload Files
77
+
78
+ ```bash
79
+ cd /path/to/your/FaceSwapLite
80
+ huggingface-cli upload minhho/FaceSwapLite-1.0 . . --repo-type=space
81
+ ```
82
+
83
+ ## Method 3: Manual Upload via Web Interface
84
+
85
+ 1. Go to https://huggingface.co/spaces/minhho/FaceSwapLite-1.0
86
+ 2. Click on "Files" tab
87
+ 3. Click "Add file" → "Create a new file" or "Upload files"
88
+ 4. Upload the following files one by one:
89
+ - `app.py`
90
+ - `refacer.py`
91
+ - `script.py`
92
+ - `requirements.txt`
93
+ - `README.md`
94
+ - `inswapper_128.onnx`
95
+ - All files in `recognition/` folder
96
+
97
+ ## Important Notes
98
+
99
+ ### Model File Size
100
+
101
+ The `inswapper_128.onnx` file is quite large (~500MB). You have several options:
102
+
103
+ #### Option 1: Git LFS (Recommended)
104
+ ```bash
105
+ # Install Git LFS if not already installed
106
+ git lfs install
107
+
108
+ # Track the model file
109
+ git lfs track "*.onnx"
110
+ git add .gitattributes
111
+ git add inswapper_128.onnx
112
+ git commit -m "Add model file with LFS"
113
+ git push
114
+ ```
115
+
116
+ #### Option 2: External Storage
117
+ Store the model file externally (e.g., Google Drive, Dropbox) and download it during app initialization. Add this to `refacer.py`:
118
+
119
+ ```python
120
+ import urllib.request
121
+ import os
122
+
123
+ def download_model():
124
+ model_path = 'inswapper_128.onnx'
125
+ if not os.path.exists(model_path):
126
+ print("Downloading model...")
127
+ url = "YOUR_MODEL_URL_HERE"
128
+ urllib.request.urlretrieve(url, model_path)
129
+ print("Model downloaded!")
130
+ ```
131
+
132
+ #### Option 3: Hugging Face Hub
133
+ Upload the model to Hugging Face Model Hub and download it:
134
+
135
+ ```python
136
+ from huggingface_hub import hf_hub_download
137
+
138
+ model_path = hf_hub_download(
139
+ repo_id="YOUR_REPO/model",
140
+ filename="inswapper_128.onnx"
141
+ )
142
+ ```
143
+
144
+ ### System Dependencies
145
+
146
+ If you need FFmpeg or other system packages, create a `packages.txt` file:
147
+
148
+ ```txt
149
+ ffmpeg
150
+ ```
151
+
152
+ ### Environment Variables
153
+
154
+ You can set environment variables in the Space settings:
155
+ - Go to Space Settings → Variables and secrets
156
+ - Add variables like `MAX_NUM_FACES`, `FORCE_CPU`, etc.
157
+
158
+ ### Hardware Requirements
159
+
160
+ For better performance:
161
+ 1. Go to Space Settings
162
+ 2. Under "Hardware", upgrade to a GPU-enabled instance (requires payment)
163
+ 3. Recommended: T4 small or better for real-time processing
164
+
165
+ ### Free Tier Limitations
166
+
167
+ On the free tier:
168
+ - CPU-only processing (slower)
169
+ - 16GB RAM limit
170
+ - 50GB storage limit
171
+ - May go to sleep after inactivity
172
+
173
+ ## Troubleshooting
174
+
175
+ ### Build Fails
176
+
177
+ Check the build logs at your Space URL. Common issues:
178
+ - Missing dependencies in `requirements.txt`
179
+ - Large files not tracked by Git LFS
180
+ - Incompatible package versions
181
+
182
+ ### App Doesn't Start
183
+
184
+ - Check that `app.py` is in the root directory
185
+ - Verify `sdk: gradio` is in README.md frontmatter
186
+ - Check Python version compatibility
187
+
188
+ ### Model Not Found
189
+
190
+ - Ensure `inswapper_128.onnx` is uploaded
191
+ - Check file permissions and paths
192
+ - Verify Git LFS is tracking large files
193
+
194
+ ### Performance Issues
195
+
196
+ - Upgrade to GPU hardware
197
+ - Reduce video resolution
198
+ - Process shorter clips
199
+ - Optimize batch processing settings
200
+
201
+ ## Post-Deployment
202
+
203
+ After successful deployment:
204
+
205
+ 1. **Test the Space**: Try uploading a video and swapping faces
206
+ 2. **Monitor Logs**: Check for any runtime errors
207
+ 3. **Update Documentation**: Add examples and usage tips
208
+ 4. **Share**: Share your Space URL with others!
209
+
210
+ ## Updating Your Space
211
+
212
+ To update your deployed space:
213
+
214
+ ```bash
215
+ cd FaceSwapLite-1.0
216
+ # Make your changes
217
+ git add .
218
+ git commit -m "Update: description of changes"
219
+ git push
220
+ ```
221
+
222
+ The space will automatically rebuild with your changes.
223
+
224
+ ## Need Help?
225
+
226
+ - Check Hugging Face Spaces documentation: https://huggingface.co/docs/hub/spaces
227
+ - Visit the Hugging Face forums: https://discuss.huggingface.co/
228
+ - Open an issue on your Space repository
DEPLOYMENT_READY.md ADDED
@@ -0,0 +1,178 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ✅ FaceSwapLite - Ready for Deployment
2
+
3
+ ## Summary of Changes
4
+
5
+ Your FaceSwapLite project has been updated and is now ready for deployment to Hugging Face Spaces!
6
+
7
+ ### 📝 What Was Updated
8
+
9
+ #### 1. **README.md** ✅
10
+ - Added comprehensive project description
11
+ - Included features, installation, and usage instructions
12
+ - Added technical details and architecture information
13
+ - Included disclaimer and acknowledgments
14
+ - Updated emoji from 🏆 to 🎃 for consistency
15
+
16
+ #### 2. **app.py** ✅
17
+ - Removed `argparse` (not needed for Hugging Face Spaces)
18
+ - Removed `ngrok` integration (not compatible with HF Spaces)
19
+ - Simplified configuration using environment variables
20
+ - Fixed for Hugging Face Spaces deployment
21
+ - Clean, production-ready code
22
+
23
+ #### 3. **requirements.txt** ✅
24
+ - Updated Gradio from 3.33.1 to 4.40.0 (matches README.md)
25
+ - Fixed package name: `ffmpeg_python` → `ffmpeg-python`
26
+ - Removed `opencv_python` (keeping only `opencv-python-headless` for server deployment)
27
+ - Removed `ngrok` (not needed for HF Spaces)
28
+ - All packages are now compatible with Hugging Face Spaces
29
+
30
+ #### 4. **New Files Created** ✅
31
+ - `.gitignore` - Excludes unnecessary files from version control
32
+ - `packages.txt` - System dependencies (FFmpeg) for Hugging Face
33
+ - `LICENSE` - MIT License with disclaimer
34
+ - `DEPLOYMENT.md` - Comprehensive deployment guide
35
+ - `QUICKSTART.md` - Quick deployment instructions
36
+ - `deploy.sh` - Automated deployment script
37
+
38
+ ### 📦 Current Project Structure
39
+
40
+ ```
41
+ FaceSwapLite/
42
+ ├── app.py # ✅ Updated - Gradio web interface (HF compatible)
43
+ ├── refacer.py # ✅ Core face swapping logic
44
+ ├── script.py # ✅ Command-line interface
45
+ ├── requirements.txt # ✅ Updated - Python dependencies
46
+ ├── packages.txt # ✨ New - System dependencies (FFmpeg)
47
+ ├── README.md # ✅ Updated - Comprehensive documentation
48
+ ├── LICENSE # ✨ New - MIT License
49
+ ├── DEPLOYMENT.md # ✨ New - Detailed deployment guide
50
+ ├── QUICKSTART.md # ✨ New - Quick start guide
51
+ ├── deploy.sh # ✨ New - Automated deployment script
52
+ ├── .gitignore # ✨ New - Git ignore rules
53
+ ├── .gitattributes # ✅ Already exists - Git LFS config
54
+ ├── inswapper_128.onnx # ✅ Model file (529MB, tracked by LFS)
55
+ ├── demo.gif # ✅ Demo file
56
+ ├── image.jpg # ✅ Sample image
57
+ ├── recognition/ # ✅ Face detection and recognition
58
+ │ ├── arcface_onnx.py
59
+ │ ├── face_align.py
60
+ │ ├── main.py
61
+ │ └── scrfd.py
62
+ └── out/ # ✅ Output directory
63
+ └── .gitkeep
64
+ ```
65
+
66
+ ### 🎯 Ready for Deployment
67
+
68
+ Your project is now ready to deploy! Here's what's been verified:
69
+
70
+ ✅ **No Python errors** in app.py or refacer.py
71
+ ✅ **Model file exists** (inswapper_128.onnx - 529MB)
72
+ ✅ **Git LFS configured** (.gitattributes already has *.onnx)
73
+ ✅ **Dependencies updated** for Hugging Face compatibility
74
+ ✅ **System packages** specified (FFmpeg in packages.txt)
75
+ ✅ **Documentation** complete and comprehensive
76
+ ✅ **Deployment scripts** ready to use
77
+
78
+ ### 🚀 Deploy Now!
79
+
80
+ You have **three options** to deploy:
81
+
82
+ #### Option 1: Automated Script (Easiest)
83
+ ```bash
84
+ cd /Users/hoangminh.ho/Works/research/FaceSwapLite
85
+ ./deploy.sh
86
+ ```
87
+
88
+ #### Option 2: Manual Git Push
89
+ ```bash
90
+ # See QUICKSTART.md for detailed steps
91
+ git clone https://huggingface.co/spaces/minhho/FaceSwapLite-1.0
92
+ # Copy files and push
93
+ ```
94
+
95
+ #### Option 3: Hugging Face CLI
96
+ ```bash
97
+ pip install huggingface_hub
98
+ huggingface-cli login
99
+ huggingface-cli upload minhho/FaceSwapLite-1.0 . . --repo-type=space
100
+ ```
101
+
102
+ ### 📋 Pre-Deployment Checklist
103
+
104
+ Before deploying, make sure you have:
105
+
106
+ - [ ] Hugging Face account created
107
+ - [ ] Access token with write permissions from https://huggingface.co/settings/tokens
108
+ - [ ] Git and Git LFS installed
109
+ - [ ] Git LFS initialized (`git lfs install`)
110
+
111
+ ### 🔍 What Happens After Deployment
112
+
113
+ 1. **Files Upload**: All files will be uploaded to your Space
114
+ 2. **Build Process**: Hugging Face will build your Space (5-10 minutes)
115
+ 3. **Dependencies Install**: Install packages from requirements.txt and packages.txt
116
+ 4. **Model Download**: InsightFace models auto-download on first run
117
+ 5. **App Launch**: Your app will be live at https://huggingface.co/spaces/minhho/FaceSwapLite-1.0
118
+
119
+ ### ⚙️ Configuration Options
120
+
121
+ You can set environment variables in Space Settings:
122
+
123
+ - `MAX_NUM_FACES` - Maximum number of faces (default: 5)
124
+ - `FORCE_CPU` - Force CPU mode (default: False)
125
+
126
+ ### 💡 Performance Tips
127
+
128
+ **Free Tier (CPU):**
129
+ - Processing will be slower
130
+ - Good for testing and demos
131
+ - No additional cost
132
+
133
+ **Paid Tier (GPU):**
134
+ - Much faster processing
135
+ - Recommended for production use
136
+ - Upgrade in Space Settings → Hardware
137
+ - Suggested: T4 small (~$0.60/hour)
138
+
139
+ ### 🐛 Common Issues & Solutions
140
+
141
+ **Issue: "No application file"**
142
+ ✅ Solution: Make sure app.py is in the root directory
143
+
144
+ **Issue: Build fails**
145
+ ✅ Solution: Check logs, verify requirements.txt and packages.txt
146
+
147
+ **Issue: Model file not found**
148
+ ✅ Solution: Ensure Git LFS is tracking *.onnx files
149
+
150
+ **Issue: Out of memory**
151
+ ✅ Solution: Upgrade to GPU instance or reduce video resolution
152
+
153
+ ### 📚 Documentation
154
+
155
+ - **README.md** - Main documentation
156
+ - **QUICKSTART.md** - Fast deployment guide
157
+ - **DEPLOYMENT.md** - Detailed deployment instructions
158
+ - **This file** - Summary and checklist
159
+
160
+ ### 🎉 Next Steps
161
+
162
+ 1. **Deploy** using one of the three methods above
163
+ 2. **Monitor** the build logs at your Space URL
164
+ 3. **Test** with a sample video once deployed
165
+ 4. **Share** your Space with others!
166
+
167
+ ### 📧 Support
168
+
169
+ If you encounter issues:
170
+ - Check DEPLOYMENT.md for troubleshooting
171
+ - Review Hugging Face Spaces docs: https://huggingface.co/docs/hub/spaces
172
+ - Open an issue on your Space repository
173
+
174
+ ---
175
+
176
+ **Your FaceSwapLite is production-ready! 🚀**
177
+
178
+ Deploy now and start swapping faces in the cloud!
LICENSE ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2024 FaceSwapLite
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ ---
24
+
25
+ DISCLAIMER: This software is provided for educational and research purposes only.
26
+ Users are responsible for ensuring their use complies with all applicable laws
27
+ and regulations. The authors and contributors are not responsible for any misuse
28
+ of this software. Always obtain proper consent before processing anyone's images
29
+ or videos.
QUICKSTART.md ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 🚀 Quick Start - Deploy to Hugging Face
2
+
3
+ This is a quick guide to deploy FaceSwapLite to your Hugging Face Space.
4
+
5
+ ## Prerequisites
6
+
7
+ ✅ Hugging Face account
8
+ ✅ Git installed
9
+ ✅ Git LFS installed (for the 529MB model file)
10
+ ✅ Hugging Face access token with write permissions
11
+
12
+ ## Option 1: Automated Deployment (Recommended)
13
+
14
+ ### Step 1: Prepare Git LFS
15
+
16
+ ```bash
17
+ # Install Git LFS (if not already installed)
18
+ # macOS
19
+ brew install git-lfs
20
+
21
+ # Linux
22
+ sudo apt-get install git-lfs
23
+
24
+ # Initialize Git LFS
25
+ git lfs install
26
+ ```
27
+
28
+ ### Step 2: Run Deployment Script
29
+
30
+ ```bash
31
+ # Make sure you're in the FaceSwapLite directory
32
+ cd /Users/hoangminh.ho/Works/research/FaceSwapLite
33
+
34
+ # Run the deployment script
35
+ ./deploy.sh
36
+ ```
37
+
38
+ The script will:
39
+ 1. Clone your Hugging Face Space
40
+ 2. Copy all necessary files
41
+ 3. Set up Git LFS for the model file
42
+ 4. Commit and push to Hugging Face
43
+
44
+ When prompted, enter your Hugging Face access token.
45
+
46
+ ## Option 2: Manual Deployment
47
+
48
+ ### Step 1: Clone Your Space
49
+
50
+ ```bash
51
+ git clone https://huggingface.co/spaces/minhho/FaceSwapLite-1.0
52
+ cd FaceSwapLite-1.0
53
+ ```
54
+
55
+ ### Step 2: Set Up Git LFS
56
+
57
+ ```bash
58
+ git lfs install
59
+ git lfs track "*.onnx"
60
+ git add .gitattributes
61
+ ```
62
+
63
+ ### Step 3: Copy Files
64
+
65
+ Copy the following files from your local project:
66
+
67
+ ```bash
68
+ # From your FaceSwapLite directory
69
+ cp /Users/hoangminh.ho/Works/research/FaceSwapLite/app.py .
70
+ cp /Users/hoangminh.ho/Works/research/FaceSwapLite/refacer.py .
71
+ cp /Users/hoangminh.ho/Works/research/FaceSwapLite/script.py .
72
+ cp /Users/hoangminh.ho/Works/research/FaceSwapLite/requirements.txt .
73
+ cp /Users/hoangminh.ho/Works/research/FaceSwapLite/README.md .
74
+ cp /Users/hoangminh.ho/Works/research/FaceSwapLite/LICENSE .
75
+ cp /Users/hoangminh.ho/Works/research/FaceSwapLite/packages.txt .
76
+ cp -r /Users/hoangminh.ho/Works/research/FaceSwapLite/recognition .
77
+ cp /Users/hoangminh.ho/Works/research/FaceSwapLite/inswapper_128.onnx .
78
+
79
+ # Create output directory
80
+ mkdir -p out
81
+ touch out/.gitkeep
82
+ ```
83
+
84
+ ### Step 4: Commit and Push
85
+
86
+ ```bash
87
+ git add .
88
+ git commit -m "Deploy FaceSwapLite application"
89
+ git push
90
+ ```
91
+
92
+ Enter your Hugging Face access token when prompted for password.
93
+
94
+ ## Verify Deployment
95
+
96
+ 1. Visit https://huggingface.co/spaces/minhho/FaceSwapLite-1.0
97
+ 2. Wait 5-10 minutes for the build to complete
98
+ 3. Check the "Logs" tab for any errors
99
+ 4. Test the application with a sample video
100
+
101
+ ## Troubleshooting
102
+
103
+ ### Large File Upload Issues
104
+
105
+ If you have issues uploading the model file:
106
+
107
+ ```bash
108
+ # Make sure Git LFS is tracking it
109
+ git lfs track "*.onnx"
110
+ git add .gitattributes
111
+ git add inswapper_128.onnx
112
+ git commit -m "Add model file with LFS"
113
+ git push
114
+ ```
115
+
116
+ ### Build Failures
117
+
118
+ Check the logs at your Space URL. Common issues:
119
+ - Missing `packages.txt` (for FFmpeg)
120
+ - Incorrect `requirements.txt`
121
+ - Model file not uploaded
122
+
123
+ ### Permission Denied
124
+
125
+ Make sure you're using your Hugging Face **access token** (not password) when pushing.
126
+
127
+ Get your token at: https://huggingface.co/settings/tokens
128
+
129
+ ## Next Steps
130
+
131
+ After successful deployment:
132
+
133
+ 1. ✅ Test with a sample video
134
+ 2. ✅ Monitor the logs for errors
135
+ 3. ✅ Share your Space with others!
136
+ 4. ✅ Consider upgrading to GPU for better performance
137
+
138
+ ## Getting Your Access Token
139
+
140
+ 1. Go to https://huggingface.co/settings/tokens
141
+ 2. Click "New token"
142
+ 3. Give it a name (e.g., "FaceSwapLite")
143
+ 4. Select "Write" permissions
144
+ 5. Click "Generate"
145
+ 6. Copy the token (you won't see it again!)
146
+
147
+ ## Using Hugging Face CLI (Alternative)
148
+
149
+ ```bash
150
+ # Install Hugging Face CLI
151
+ pip install huggingface_hub
152
+
153
+ # Login
154
+ huggingface-cli login
155
+
156
+ # Upload all files
157
+ huggingface-cli upload minhho/FaceSwapLite-1.0 . . --repo-type=space
158
+ ```
159
+
160
+ ---
161
+
162
+ For detailed deployment instructions, see `DEPLOYMENT.md`
README.md CHANGED
@@ -1,12 +1,153 @@
1
  ---
2
- title: FaceSwapLite 1.0
3
- emoji: 🚀
4
- colorFrom: gray
5
- colorTo: blue
6
  sdk: gradio
7
- sdk_version: 5.48.0
8
  app_file: app.py
9
  pinned: false
 
10
  ---
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: FaceSwapLite
3
+ emoji:
4
+ colorFrom: purple
5
+ colorTo: red
6
  sdk: gradio
7
+ sdk_version: 4.40.0
8
  app_file: app.py
9
  pinned: false
10
+ license: mit
11
  ---
12
 
13
+ # 🎃 FaceSwapLite - AI Face Swapping Application
14
+
15
+ A lightweight and efficient face swapping application powered by InsightFace and ONNX Runtime. Swap faces in videos with high-quality results using AI technology.
16
+
17
+ ## 🌟 Features
18
+
19
+ - **Multi-Face Support**: Swap multiple faces in a single video
20
+ - **High-Quality Results**: Uses InsightFace's state-of-the-art face recognition and swapping models
21
+ - **Flexible Processing**: Support for CPU, CUDA, CoreML, and TensorRT execution
22
+ - **Adjustable Transparency**: Control the blending threshold for each face swap
23
+ - **Audio Preservation**: Automatically preserves audio from the original video
24
+ - **User-Friendly Interface**: Simple Gradio web interface for easy interaction
25
+
26
+ ## 🚀 Quick Start
27
+
28
+ ### Online Usage
29
+
30
+ Visit the [Hugging Face Space](https://huggingface.co/spaces/minhho/FaceSwapLite-1.0) to use the application directly in your browser.
31
+
32
+ ### Local Installation
33
+
34
+ 1. **Clone the repository**
35
+ ```bash
36
+ git clone https://huggingface.co/spaces/minhho/FaceSwapLite-1.0
37
+ cd FaceSwapLite-1.0
38
+ ```
39
+
40
+ 2. **Install dependencies**
41
+ ```bash
42
+ pip install -r requirements.txt
43
+ ```
44
+
45
+ 3. **Download the face swapping model**
46
+ - Download `inswapper_128.onnx` from [InsightFace Model Zoo](https://github.com/deepinsight/insightface/releases)
47
+ - Place it in the root directory
48
+
49
+ 4. **Run the application**
50
+ ```bash
51
+ python app.py
52
+ ```
53
+
54
+ ## 📖 Usage
55
+
56
+ ### Web Interface
57
+
58
+ 1. Upload a target video (MP4 format)
59
+ 2. For each face you want to swap:
60
+ - Upload the target face from the video (face to replace)
61
+ - Upload the source face (replacement face)
62
+ - Adjust the transparency slider (0.0-1.0, default: 0.2)
63
+ 3. Click "⏳ Start processing"
64
+ 4. Download the result video
65
+
66
+ ### Command Line
67
+
68
+ ```bash
69
+ python script.py --video path/to/video.mp4 --face path/to/target_face.jpg,path/to/source_face.jpg,0.2
70
+ ```
71
+
72
+ **Arguments:**
73
+ - `--video`: Path to the target video
74
+ - `--face`: Face swap configuration (can be used multiple times)
75
+ - Format: `target_face,source_face,threshold`
76
+ - `--force_cpu`: Force CPU execution (optional)
77
+ - `--colab_performance`: Optimize for Google Colab (optional)
78
+
79
+ ## ⚙️ Configuration
80
+
81
+ ### Execution Modes
82
+
83
+ The application automatically detects and uses the best available execution provider:
84
+
85
+ - **CPU Mode**: Default fallback, works on all systems
86
+ - **CUDA Mode**: NVIDIA GPU acceleration (recommended for best performance)
87
+ - **CoreML Mode**: Apple Silicon optimization
88
+ - **TensorRT Mode**: Advanced NVIDIA GPU optimization
89
+
90
+ ### Performance Tips
91
+
92
+ - For faster processing, use a CUDA-compatible GPU
93
+ - Reduce video resolution for quicker processing
94
+ - Process shorter video segments for testing
95
+ - On Colab, use the `--colab_performance` flag
96
+
97
+ ## 🛠️ Technical Details
98
+
99
+ ### Architecture
100
+
101
+ - **Face Detection**: SCRFD (Sample and Computation Redistributed Face Detection)
102
+ - **Face Recognition**: ArcFace with ResNet-50 backbone
103
+ - **Face Swapping**: INSwapper model (128x128)
104
+ - **Backend**: ONNX Runtime for optimized inference
105
+
106
+ ### Requirements
107
+
108
+ - Python 3.8+
109
+ - FFmpeg (for video processing)
110
+ - 8GB+ RAM recommended
111
+ - GPU with 4GB+ VRAM (optional, for faster processing)
112
+
113
+ ## 📁 Project Structure
114
+
115
+ ```
116
+ FaceSwapLite/
117
+ ├── app.py # Gradio web interface
118
+ ├── refacer.py # Core face swapping logic
119
+ ├── script.py # Command-line interface
120
+ ├── requirements.txt # Python dependencies
121
+ ├── inswapper_128.onnx # Face swapping model
122
+ ├── recognition/ # Face detection and recognition modules
123
+ │ ├── scrfd.py # Face detector
124
+ │ ├── arcface_onnx.py # Face recognition
125
+ │ └── ...
126
+ └── out/ # Output directory for processed videos
127
+ ```
128
+
129
+ ## 🤝 Contributing
130
+
131
+ Contributions are welcome! Please feel free to submit issues or pull requests.
132
+
133
+ ## 📝 License
134
+
135
+ This project is licensed under the MIT License - see the LICENSE file for details.
136
+
137
+ ## 🙏 Acknowledgments
138
+
139
+ - [InsightFace](https://github.com/deepinsight/insightface) for the face analysis models
140
+ - [ONNX Runtime](https://onnxruntime.ai/) for efficient model inference
141
+ - [Gradio](https://gradio.app/) for the web interface
142
+
143
+ ## ⚠️ Disclaimer
144
+
145
+ This software is provided for educational and research purposes only. Please use responsibly and ethically. Do not use this tool for creating misleading or harmful content. Always obtain proper consent before swapping someone's face.
146
+
147
+ ## 📧 Contact
148
+
149
+ For questions or support, please open an issue on the GitHub repository.
150
+
151
+ ---
152
+
153
+ **Note**: Make sure you have the required model files (`inswapper_128.onnx`) before running the application. The InsightFace models will be automatically downloaded on first run.
app.py ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ import gradio as gr
3
+ from refacer import Refacer
4
+ import os
5
+
6
+ # Configuration
7
+ MAX_NUM_FACES = int(os.environ.get("MAX_NUM_FACES", "5"))
8
+ FORCE_CPU = os.environ.get("FORCE_CPU", "False").lower() == "true"
9
+
10
+ # Initialize the face swapper
11
+ print("Initializing FaceSwapLite...")
12
+ refacer = Refacer(force_cpu=FORCE_CPU, colab_performance=False)
13
+ print("Initialization complete!")
14
+
15
+ num_faces = MAX_NUM_FACES
16
+
17
+
18
+ def run(*vars):
19
+ video_path=vars[0]
20
+ origins=vars[1:(num_faces+1)]
21
+ destinations=vars[(num_faces+1):(num_faces*2)+1]
22
+ thresholds=vars[(num_faces*2)+1:]
23
+
24
+ faces = []
25
+ for k in range(0,num_faces):
26
+ if origins[k] is not None and destinations[k] is not None:
27
+ faces.append({
28
+ 'origin':origins[k],
29
+ 'destination':destinations[k],
30
+ 'threshold':thresholds[k]
31
+ })
32
+
33
+ return refacer.reface(video_path,faces)
34
+
35
+ origin = []
36
+ destination = []
37
+ thresholds = []
38
+
39
+ with gr.Blocks() as demo:
40
+ with gr.Row():
41
+ gr.Markdown("# 🎃 FaceSwap Lite 🎃")
42
+ with gr.Row():
43
+ video=gr.Video(label=u"🖥️ Target Video,foramt mp4",format="mp4")
44
+ video2=gr.Video(label=u"🎞️ Target Video",interactive=False,format="mp4")
45
+
46
+ for i in range(0,num_faces):
47
+ with gr.Tab(u"Face #" + f"{i+1}"):
48
+ with gr.Row():
49
+ origin.append(gr.Image(label=u"🎯 Target Face from Video"))
50
+ destination.append(gr.Image(label=u"📸 Source Face"))
51
+ with gr.Row():
52
+ thresholds.append(gr.Slider(label=u"Transparency",minimum=0.0,maximum=1.0,value=0.2))
53
+ with gr.Row():
54
+ button=gr.Button(u"⏳ Start processing", variant="primary")
55
+
56
+ button.click(fn=run,inputs=[video]+origin+destination+thresholds,outputs=[video2])
57
+
58
+ # Launch the Gradio app
59
+ demo.queue().launch()
demo.gif ADDED

Git LFS Details

  • SHA256: 52b95c2e607c6edd0a9180596759c20ec00f92fa00798922004a612482dda2f2
  • Pointer size: 132 Bytes
  • Size of remote file: 2.87 MB
deploy.sh ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+
3
+ # FaceSwapLite - Hugging Face Deployment Script
4
+ # This script helps deploy FaceSwapLite to Hugging Face Spaces
5
+
6
+ set -e
7
+
8
+ echo "🎃 FaceSwapLite - Hugging Face Deployment Script 🎃"
9
+ echo "=================================================="
10
+ echo ""
11
+
12
+ # Configuration
13
+ SPACE_REPO="https://huggingface.co/spaces/minhho/FaceSwapLite-1.0"
14
+ SPACE_DIR="FaceSwapLite-1.0-hf"
15
+
16
+ # Check if git is installed
17
+ if ! command -v git &> /dev/null; then
18
+ echo "❌ Error: Git is not installed. Please install git first."
19
+ exit 1
20
+ fi
21
+
22
+ # Check if git-lfs is installed
23
+ if ! command -v git-lfs &> /dev/null; then
24
+ echo "⚠️ Warning: Git LFS is not installed."
25
+ echo " The model file (529MB) requires Git LFS."
26
+ echo " Install it with: brew install git-lfs (macOS) or visit https://git-lfs.github.com/"
27
+ read -p " Continue anyway? (y/n) " -n 1 -r
28
+ echo
29
+ if [[ ! $REPLY =~ ^[Yy]$ ]]; then
30
+ exit 1
31
+ fi
32
+ else
33
+ echo "✅ Git LFS is installed"
34
+ git lfs install
35
+ fi
36
+
37
+ # Step 1: Clone or update the space repository
38
+ echo ""
39
+ echo "Step 1: Cloning Hugging Face Space..."
40
+ if [ -d "$SPACE_DIR" ]; then
41
+ echo "Directory $SPACE_DIR already exists."
42
+ read -p "Do you want to remove it and clone fresh? (y/n) " -n 1 -r
43
+ echo
44
+ if [[ $REPLY =~ ^[Yy]$ ]]; then
45
+ rm -rf "$SPACE_DIR"
46
+ git clone "$SPACE_REPO" "$SPACE_DIR"
47
+ else
48
+ cd "$SPACE_DIR"
49
+ git pull
50
+ cd ..
51
+ fi
52
+ else
53
+ git clone "$SPACE_REPO" "$SPACE_DIR"
54
+ fi
55
+
56
+ # Step 2: Copy files
57
+ echo ""
58
+ echo "Step 2: Copying project files..."
59
+ cp -v app.py "$SPACE_DIR/"
60
+ cp -v refacer.py "$SPACE_DIR/"
61
+ cp -v script.py "$SPACE_DIR/"
62
+ cp -v requirements.txt "$SPACE_DIR/"
63
+ cp -v README.md "$SPACE_DIR/"
64
+ cp -v LICENSE "$SPACE_DIR/"
65
+ cp -v packages.txt "$SPACE_DIR/"
66
+
67
+ # Copy recognition folder
68
+ echo "Copying recognition module..."
69
+ cp -rv recognition "$SPACE_DIR/"
70
+
71
+ # Copy model file if it exists
72
+ if [ -f "inswapper_128.onnx" ]; then
73
+ echo "Copying model file (this may take a moment - 529MB)..."
74
+ cp -v inswapper_128.onnx "$SPACE_DIR/"
75
+ else
76
+ echo "⚠️ Warning: inswapper_128.onnx not found!"
77
+ echo " Please manually copy the model file or download it from:"
78
+ echo " https://github.com/deepinsight/insightface/releases/download/v0.7/inswapper_128.onnx"
79
+ fi
80
+
81
+ # Create output directory
82
+ mkdir -p "$SPACE_DIR/out"
83
+ touch "$SPACE_DIR/out/.gitkeep"
84
+
85
+ # Step 3: Git operations
86
+ echo ""
87
+ echo "Step 3: Preparing git commit..."
88
+ cd "$SPACE_DIR"
89
+
90
+ # Track large files with LFS if git-lfs is available
91
+ if command -v git-lfs &> /dev/null; then
92
+ echo "Setting up Git LFS..."
93
+ git lfs track "*.onnx"
94
+ git add .gitattributes
95
+ fi
96
+
97
+ # Add all files
98
+ git add .
99
+
100
+ # Check if there are changes
101
+ if git diff --staged --quiet; then
102
+ echo "ℹ️ No changes to commit."
103
+ else
104
+ echo ""
105
+ echo "Files to be committed:"
106
+ git diff --staged --name-status
107
+ echo ""
108
+
109
+ read -p "Enter commit message (or press Enter for default): " COMMIT_MSG
110
+ if [ -z "$COMMIT_MSG" ]; then
111
+ COMMIT_MSG="Update FaceSwapLite application"
112
+ fi
113
+
114
+ git commit -m "$COMMIT_MSG"
115
+
116
+ # Step 4: Push to Hugging Face
117
+ echo ""
118
+ echo "Step 4: Pushing to Hugging Face..."
119
+ echo "⚠️ You will be prompted for your Hugging Face access token."
120
+ echo " Get it from: https://huggingface.co/settings/tokens"
121
+ echo ""
122
+
123
+ git push
124
+
125
+ echo ""
126
+ echo "✅ Deployment complete!"
127
+ echo ""
128
+ echo "🎉 Your Space should be live at:"
129
+ echo " $SPACE_REPO"
130
+ echo ""
131
+ echo "⏳ The space will take 5-10 minutes to build."
132
+ echo " Check the build logs at the URL above."
133
+ fi
134
+
135
+ cd ..
136
+
137
+ echo ""
138
+ echo "=================================================="
139
+ echo "Deployment script finished!"
140
+ echo "=================================================="
docker/0 ADDED
@@ -0,0 +1 @@
 
 
1
+ v
docker/Dockerfile.nvidia ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM nvidia/cuda:11.8.0-cudnn8-runtime-ubuntu22.04
2
+
3
+ # Always use UTC on a server
4
+ RUN ln -snf /usr/share/zoneinfo/UTC /etc/localtime && echo UTC > /etc/timezone
5
+
6
+ RUN DEBIAN_FRONTEND=noninteractive apt update && apt install -y python3 python3-pip python3-tk git ffmpeg nvidia-cuda-toolkit nvidia-container-runtime libnvidia-decode-525-server wget unzip
7
+ RUN wget https://github.com/deepinsight/insightface/releases/download/v0.7/buffalo_l.zip -O /tmp/buffalo_l.zip && \
8
+ mkdir -p /root/.insightface/models/buffalo_l && \
9
+ cd /root/.insightface/models/buffalo_l && \
10
+ unzip /tmp/buffalo_l.zip && \
11
+ rm -f /tmp/buffalo_l.zip
12
+
13
+ RUN pip install nvidia-tensorrt
14
+ RUN git clone https://github.com/xaviviro/refacer && cd refacer && pip install -r requirements-GPU.txt
15
+
16
+ WORKDIR /refacer
17
+
18
+ # Test following commands in container to make sure GPU stuff works
19
+ # nvidia-smi
20
+ # python3 -c "import tensorflow as tf; print(tf.config.list_physical_devices('GPU'))"
docker/run.sh ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ # Run this script from within the refacer/docker folder.
3
+ # You'll need inswrapper_128.onnx from either:
4
+ # https://drive.google.com/file/d/1eu60OrRtn4WhKrzM4mQv4F3rIuyUXqfl/view?usp=drive_link
5
+ # or https://drive.google.com/file/d/1jbDUGrADco9A1MutWjO6d_1dwizh9w9P/view?usp=sharing
6
+ # or https://mega.nz/file/9l8mGDJA#FnPxHwpdhDovDo6OvbQjhHd2nDAk8_iVEgo3mpHLG6U
7
+ # or https://1drv.ms/u/s!AsHA3Xbnj6uAgxhb_tmQ7egHACOR?e=CPoThO
8
+ # or https://civitai.com/models/80324?modelVersionId=85159
9
+
10
+ docker stop -t 0 refacer
11
+ docker build -t refacer -f Dockerfile.nvidia . && \
12
+ docker run --rm --name refacer -v $(pwd)/..:/refacer -p 7860:7860 --gpus all refacer python3 app.py --server_name 0.0.0.0 &
13
+ sleep 2 && google-chrome --new-window "http://127.0.0.1:7860" &
out.gitkeep ADDED
File without changes
out/.gitkeep ADDED
File without changes
packages.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ ffmpeg
recognition/0 ADDED
@@ -0,0 +1 @@
 
 
1
+ t
recognition/arcface_onnx.py ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ # @Organization : insightface.ai
3
+ # @Author : Jia Guo
4
+ # @Time : 2021-05-04
5
+ # @Function :
6
+
7
+ import numpy as np
8
+ import cv2
9
+ import onnx
10
+ import onnxruntime
11
+ import face_align
12
+
13
+ __all__ = [
14
+ 'ArcFaceONNX',
15
+ ]
16
+
17
+
18
+ class ArcFaceONNX:
19
+ def __init__(self, model_file=None, session=None):
20
+ assert model_file is not None
21
+ self.model_file = model_file
22
+ self.session = session
23
+ self.taskname = 'recognition'
24
+ find_sub = False
25
+ find_mul = False
26
+ model = onnx.load(self.model_file)
27
+ graph = model.graph
28
+ for nid, node in enumerate(graph.node[:8]):
29
+ #print(nid, node.name)
30
+ if node.name.startswith('Sub') or node.name.startswith('_minus'):
31
+ find_sub = True
32
+ if node.name.startswith('Mul') or node.name.startswith('_mul'):
33
+ find_mul = True
34
+ if find_sub and find_mul:
35
+ #mxnet arcface model
36
+ input_mean = 0.0
37
+ input_std = 1.0
38
+ else:
39
+ input_mean = 127.5
40
+ input_std = 127.5
41
+ self.input_mean = input_mean
42
+ self.input_std = input_std
43
+ #print('input mean and std:', self.input_mean, self.input_std)
44
+ if self.session is None:
45
+ self.session = onnxruntime.InferenceSession(self.model_file, providers=['CoreMLExecutionProvider','CUDAExecutionProvider'])
46
+ input_cfg = self.session.get_inputs()[0]
47
+ input_shape = input_cfg.shape
48
+ input_name = input_cfg.name
49
+ self.input_size = tuple(input_shape[2:4][::-1])
50
+ self.input_shape = input_shape
51
+ outputs = self.session.get_outputs()
52
+ output_names = []
53
+ for out in outputs:
54
+ output_names.append(out.name)
55
+ self.input_name = input_name
56
+ self.output_names = output_names
57
+ assert len(self.output_names)==1
58
+ self.output_shape = outputs[0].shape
59
+
60
+ def prepare(self, ctx_id, **kwargs):
61
+ if ctx_id<0:
62
+ self.session.set_providers(['CPUExecutionProvider'])
63
+
64
+ def get(self, img, kps):
65
+ aimg = face_align.norm_crop(img, landmark=kps, image_size=self.input_size[0])
66
+ embedding = self.get_feat(aimg).flatten()
67
+ return embedding
68
+
69
+ def compute_sim(self, feat1, feat2):
70
+ from numpy.linalg import norm
71
+ feat1 = feat1.ravel()
72
+ feat2 = feat2.ravel()
73
+ sim = np.dot(feat1, feat2) / (norm(feat1) * norm(feat2))
74
+ return sim
75
+
76
+ def get_feat(self, imgs):
77
+ if not isinstance(imgs, list):
78
+ imgs = [imgs]
79
+ input_size = self.input_size
80
+
81
+ blob = cv2.dnn.blobFromImages(imgs, 1.0 / self.input_std, input_size,
82
+ (self.input_mean, self.input_mean, self.input_mean), swapRB=True)
83
+ net_out = self.session.run(self.output_names, {self.input_name: blob})[0]
84
+ return net_out
85
+
86
+ def forward(self, batch_data):
87
+ blob = (batch_data - self.input_mean) / self.input_std
88
+ net_out = self.session.run(self.output_names, {self.input_name: blob})[0]
89
+ return net_out
90
+
91
+
recognition/face_align.py ADDED
@@ -0,0 +1,141 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ import numpy as np
3
+ from skimage import transform as trans
4
+
5
+ src1 = np.array([[51.642, 50.115], [57.617, 49.990], [35.740, 69.007],
6
+ [51.157, 89.050], [57.025, 89.702]],
7
+ dtype=np.float32)
8
+ #<--left
9
+ src2 = np.array([[45.031, 50.118], [65.568, 50.872], [39.677, 68.111],
10
+ [45.177, 86.190], [64.246, 86.758]],
11
+ dtype=np.float32)
12
+
13
+ #---frontal
14
+ src3 = np.array([[39.730, 51.138], [72.270, 51.138], [56.000, 68.493],
15
+ [42.463, 87.010], [69.537, 87.010]],
16
+ dtype=np.float32)
17
+
18
+ #-->right
19
+ src4 = np.array([[46.845, 50.872], [67.382, 50.118], [72.737, 68.111],
20
+ [48.167, 86.758], [67.236, 86.190]],
21
+ dtype=np.float32)
22
+
23
+ #-->right profile
24
+ src5 = np.array([[54.796, 49.990], [60.771, 50.115], [76.673, 69.007],
25
+ [55.388, 89.702], [61.257, 89.050]],
26
+ dtype=np.float32)
27
+
28
+ src = np.array([src1, src2, src3, src4, src5])
29
+ src_map = {112: src, 224: src * 2}
30
+
31
+ arcface_src = np.array(
32
+ [[38.2946, 51.6963], [73.5318, 51.5014], [56.0252, 71.7366],
33
+ [41.5493, 92.3655], [70.7299, 92.2041]],
34
+ dtype=np.float32)
35
+
36
+ arcface_src = np.expand_dims(arcface_src, axis=0)
37
+
38
+ # In[66]:
39
+
40
+
41
+ # lmk is prediction; src is template
42
+ def estimate_norm(lmk, image_size=112, mode='arcface'):
43
+ assert lmk.shape == (5, 2)
44
+ tform = trans.SimilarityTransform()
45
+ lmk_tran = np.insert(lmk, 2, values=np.ones(5), axis=1)
46
+ min_M = []
47
+ min_index = []
48
+ min_error = float('inf')
49
+ if mode == 'arcface':
50
+ if image_size == 112:
51
+ src = arcface_src
52
+ else:
53
+ src = float(image_size) / 112 * arcface_src
54
+ else:
55
+ src = src_map[image_size]
56
+ for i in np.arange(src.shape[0]):
57
+ tform.estimate(lmk, src[i])
58
+ M = tform.params[0:2, :]
59
+ results = np.dot(M, lmk_tran.T)
60
+ results = results.T
61
+ error = np.sum(np.sqrt(np.sum((results - src[i])**2, axis=1)))
62
+ # print(error)
63
+ if error < min_error:
64
+ min_error = error
65
+ min_M = M
66
+ min_index = i
67
+ return min_M, min_index
68
+
69
+
70
+ def norm_crop(img, landmark, image_size=112, mode='arcface'):
71
+ M, pose_index = estimate_norm(landmark, image_size, mode)
72
+ warped = cv2.warpAffine(img, M, (image_size, image_size), borderValue=0.0)
73
+ return warped
74
+
75
+ def square_crop(im, S):
76
+ if im.shape[0] > im.shape[1]:
77
+ height = S
78
+ width = int(float(im.shape[1]) / im.shape[0] * S)
79
+ scale = float(S) / im.shape[0]
80
+ else:
81
+ width = S
82
+ height = int(float(im.shape[0]) / im.shape[1] * S)
83
+ scale = float(S) / im.shape[1]
84
+ resized_im = cv2.resize(im, (width, height))
85
+ det_im = np.zeros((S, S, 3), dtype=np.uint8)
86
+ det_im[:resized_im.shape[0], :resized_im.shape[1], :] = resized_im
87
+ return det_im, scale
88
+
89
+
90
+ def transform(data, center, output_size, scale, rotation):
91
+ scale_ratio = scale
92
+ rot = float(rotation) * np.pi / 180.0
93
+ #translation = (output_size/2-center[0]*scale_ratio, output_size/2-center[1]*scale_ratio)
94
+ t1 = trans.SimilarityTransform(scale=scale_ratio)
95
+ cx = center[0] * scale_ratio
96
+ cy = center[1] * scale_ratio
97
+ t2 = trans.SimilarityTransform(translation=(-1 * cx, -1 * cy))
98
+ t3 = trans.SimilarityTransform(rotation=rot)
99
+ t4 = trans.SimilarityTransform(translation=(output_size / 2,
100
+ output_size / 2))
101
+ t = t1 + t2 + t3 + t4
102
+ M = t.params[0:2]
103
+ cropped = cv2.warpAffine(data,
104
+ M, (output_size, output_size),
105
+ borderValue=0.0)
106
+ return cropped, M
107
+
108
+
109
+ def trans_points2d(pts, M):
110
+ new_pts = np.zeros(shape=pts.shape, dtype=np.float32)
111
+ for i in range(pts.shape[0]):
112
+ pt = pts[i]
113
+ new_pt = np.array([pt[0], pt[1], 1.], dtype=np.float32)
114
+ new_pt = np.dot(M, new_pt)
115
+ #print('new_pt', new_pt.shape, new_pt)
116
+ new_pts[i] = new_pt[0:2]
117
+
118
+ return new_pts
119
+
120
+
121
+ def trans_points3d(pts, M):
122
+ scale = np.sqrt(M[0][0] * M[0][0] + M[0][1] * M[0][1])
123
+ #print(scale)
124
+ new_pts = np.zeros(shape=pts.shape, dtype=np.float32)
125
+ for i in range(pts.shape[0]):
126
+ pt = pts[i]
127
+ new_pt = np.array([pt[0], pt[1], 1.], dtype=np.float32)
128
+ new_pt = np.dot(M, new_pt)
129
+ #print('new_pt', new_pt.shape, new_pt)
130
+ new_pts[i][0:2] = new_pt[0:2]
131
+ new_pts[i][2] = pts[i][2] * scale
132
+
133
+ return new_pts
134
+
135
+
136
+ def trans_points(pts, M):
137
+ if pts.shape[1] == 2:
138
+ return trans_points2d(pts, M)
139
+ else:
140
+ return trans_points3d(pts, M)
141
+
recognition/main.py ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python
2
+
3
+ import os
4
+ import os.path as osp
5
+ import argparse
6
+ import cv2
7
+ import numpy as np
8
+ import onnxruntime
9
+ from scrfd import SCRFD
10
+ from arcface_onnx import ArcFaceONNX
11
+
12
+ onnxruntime.set_default_logger_severity(5)
13
+
14
+ assets_dir = osp.expanduser('~/.insightface/models/buffalo_l')
15
+
16
+ detector = SCRFD(os.path.join(assets_dir, 'det_10g.onnx'))
17
+ detector.prepare(0)
18
+ model_path = os.path.join(assets_dir, 'w600k_r50.onnx')
19
+ rec = ArcFaceONNX(model_path)
20
+ rec.prepare(0)
21
+
22
+ def parse_args() -> argparse.Namespace:
23
+ parser = argparse.ArgumentParser()
24
+ parser.add_argument('img1', type=str)
25
+ parser.add_argument('img2', type=str)
26
+ return parser.parse_args()
27
+
28
+
29
+ def func(args):
30
+ image1 = cv2.imread(args.img1)
31
+ image2 = cv2.imread(args.img2)
32
+ bboxes1, kpss1 = detector.autodetect(image1, max_num=1)
33
+ if bboxes1.shape[0]==0:
34
+ return -1.0, "Face not found in Image-1"
35
+ bboxes2, kpss2 = detector.autodetect(image2, max_num=1)
36
+ if bboxes2.shape[0]==0:
37
+ return -1.0, "Face not found in Image-2"
38
+ kps1 = kpss1[0]
39
+ kps2 = kpss2[0]
40
+ feat1 = rec.get(image1, kps1)
41
+ feat2 = rec.get(image2, kps2)
42
+ sim = rec.compute_sim(feat1, feat2)
43
+ if sim<0.2:
44
+ conclu = 'They are NOT the same person'
45
+ elif sim>=0.2 and sim<0.28:
46
+ conclu = 'They are LIKELY TO be the same person'
47
+ else:
48
+ conclu = 'They ARE the same person'
49
+ return sim, conclu
50
+
51
+
52
+
53
+ if __name__ == '__main__':
54
+ args = parse_args()
55
+ output = func(args)
56
+ print('sim: %.4f, message: %s'%(output[0], output[1]))
57
+
recognition/scrfd.py ADDED
@@ -0,0 +1,329 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ from __future__ import division
3
+ import datetime
4
+ import numpy as np
5
+ #import onnx
6
+ import onnxruntime
7
+ import os
8
+ import os.path as osp
9
+ import cv2
10
+ import sys
11
+
12
+ def softmax(z):
13
+ assert len(z.shape) == 2
14
+ s = np.max(z, axis=1)
15
+ s = s[:, np.newaxis] # necessary step to do broadcasting
16
+ e_x = np.exp(z - s)
17
+ div = np.sum(e_x, axis=1)
18
+ div = div[:, np.newaxis] # dito
19
+ return e_x / div
20
+
21
+ def distance2bbox(points, distance, max_shape=None):
22
+ """Decode distance prediction to bounding box.
23
+
24
+ Args:
25
+ points (Tensor): Shape (n, 2), [x, y].
26
+ distance (Tensor): Distance from the given point to 4
27
+ boundaries (left, top, right, bottom).
28
+ max_shape (tuple): Shape of the image.
29
+
30
+ Returns:
31
+ Tensor: Decoded bboxes.
32
+ """
33
+ x1 = points[:, 0] - distance[:, 0]
34
+ y1 = points[:, 1] - distance[:, 1]
35
+ x2 = points[:, 0] + distance[:, 2]
36
+ y2 = points[:, 1] + distance[:, 3]
37
+ if max_shape is not None:
38
+ x1 = x1.clamp(min=0, max=max_shape[1])
39
+ y1 = y1.clamp(min=0, max=max_shape[0])
40
+ x2 = x2.clamp(min=0, max=max_shape[1])
41
+ y2 = y2.clamp(min=0, max=max_shape[0])
42
+ return np.stack([x1, y1, x2, y2], axis=-1)
43
+
44
+ def distance2kps(points, distance, max_shape=None):
45
+ """Decode distance prediction to bounding box.
46
+
47
+ Args:
48
+ points (Tensor): Shape (n, 2), [x, y].
49
+ distance (Tensor): Distance from the given point to 4
50
+ boundaries (left, top, right, bottom).
51
+ max_shape (tuple): Shape of the image.
52
+
53
+ Returns:
54
+ Tensor: Decoded bboxes.
55
+ """
56
+ preds = []
57
+ for i in range(0, distance.shape[1], 2):
58
+ px = points[:, i%2] + distance[:, i]
59
+ py = points[:, i%2+1] + distance[:, i+1]
60
+ if max_shape is not None:
61
+ px = px.clamp(min=0, max=max_shape[1])
62
+ py = py.clamp(min=0, max=max_shape[0])
63
+ preds.append(px)
64
+ preds.append(py)
65
+ return np.stack(preds, axis=-1)
66
+
67
+ class SCRFD:
68
+ def __init__(self, model_file=None, session=None):
69
+ import onnxruntime
70
+ self.model_file = model_file
71
+ self.session = session
72
+ self.taskname = 'detection'
73
+ self.batched = False
74
+ if self.session is None:
75
+ assert self.model_file is not None
76
+ assert osp.exists(self.model_file)
77
+ self.session = onnxruntime.InferenceSession(self.model_file, providers=['CoreMLExecutionProvider','CUDAExecutionProvider'])
78
+ self.center_cache = {}
79
+ self.nms_thresh = 0.4
80
+ self.det_thresh = 0.5
81
+ self._init_vars()
82
+
83
+ def _init_vars(self):
84
+ input_cfg = self.session.get_inputs()[0]
85
+ input_shape = input_cfg.shape
86
+ #print(input_shape)
87
+ if isinstance(input_shape[2], str):
88
+ self.input_size = None
89
+ else:
90
+ self.input_size = tuple(input_shape[2:4][::-1])
91
+ #print('image_size:', self.image_size)
92
+ input_name = input_cfg.name
93
+ self.input_shape = input_shape
94
+ outputs = self.session.get_outputs()
95
+ if len(outputs[0].shape) == 3:
96
+ self.batched = True
97
+ output_names = []
98
+ for o in outputs:
99
+ output_names.append(o.name)
100
+ self.input_name = input_name
101
+ self.output_names = output_names
102
+ self.input_mean = 127.5
103
+ self.input_std = 128.0
104
+ #print(self.output_names)
105
+ #assert len(outputs)==10 or len(outputs)==15
106
+ self.use_kps = False
107
+ self._anchor_ratio = 1.0
108
+ self._num_anchors = 1
109
+ if len(outputs)==6:
110
+ self.fmc = 3
111
+ self._feat_stride_fpn = [8, 16, 32]
112
+ self._num_anchors = 2
113
+ elif len(outputs)==9:
114
+ self.fmc = 3
115
+ self._feat_stride_fpn = [8, 16, 32]
116
+ self._num_anchors = 2
117
+ self.use_kps = True
118
+ elif len(outputs)==10:
119
+ self.fmc = 5
120
+ self._feat_stride_fpn = [8, 16, 32, 64, 128]
121
+ self._num_anchors = 1
122
+ elif len(outputs)==15:
123
+ self.fmc = 5
124
+ self._feat_stride_fpn = [8, 16, 32, 64, 128]
125
+ self._num_anchors = 1
126
+ self.use_kps = True
127
+
128
+ def prepare(self, ctx_id, **kwargs):
129
+ if ctx_id<0:
130
+ self.session.set_providers(['CPUExecutionProvider'])
131
+ nms_thresh = kwargs.get('nms_thresh', None)
132
+ if nms_thresh is not None:
133
+ self.nms_thresh = nms_thresh
134
+ det_thresh = kwargs.get('det_thresh', None)
135
+ if det_thresh is not None:
136
+ self.det_thresh = det_thresh
137
+ input_size = kwargs.get('input_size', None)
138
+ if input_size is not None:
139
+ if self.input_size is not None:
140
+ print('warning: det_size is already set in scrfd model, ignore')
141
+ else:
142
+ self.input_size = input_size
143
+
144
+ def forward(self, img, threshold):
145
+ scores_list = []
146
+ bboxes_list = []
147
+ kpss_list = []
148
+ input_size = tuple(img.shape[0:2][::-1])
149
+ blob = cv2.dnn.blobFromImage(img, 1.0/self.input_std, input_size, (self.input_mean, self.input_mean, self.input_mean), swapRB=True)
150
+ net_outs = self.session.run(self.output_names, {self.input_name : blob})
151
+
152
+ input_height = blob.shape[2]
153
+ input_width = blob.shape[3]
154
+ fmc = self.fmc
155
+ for idx, stride in enumerate(self._feat_stride_fpn):
156
+ # If model support batch dim, take first output
157
+ if self.batched:
158
+ scores = net_outs[idx][0]
159
+ bbox_preds = net_outs[idx + fmc][0]
160
+ bbox_preds = bbox_preds * stride
161
+ if self.use_kps:
162
+ kps_preds = net_outs[idx + fmc * 2][0] * stride
163
+ # If model doesn't support batching take output as is
164
+ else:
165
+ scores = net_outs[idx]
166
+ bbox_preds = net_outs[idx + fmc]
167
+ bbox_preds = bbox_preds * stride
168
+ if self.use_kps:
169
+ kps_preds = net_outs[idx + fmc * 2] * stride
170
+
171
+ height = input_height // stride
172
+ width = input_width // stride
173
+ K = height * width
174
+ key = (height, width, stride)
175
+ if key in self.center_cache:
176
+ anchor_centers = self.center_cache[key]
177
+ else:
178
+ #solution-1, c style:
179
+ #anchor_centers = np.zeros( (height, width, 2), dtype=np.float32 )
180
+ #for i in range(height):
181
+ # anchor_centers[i, :, 1] = i
182
+ #for i in range(width):
183
+ # anchor_centers[:, i, 0] = i
184
+
185
+ #solution-2:
186
+ #ax = np.arange(width, dtype=np.float32)
187
+ #ay = np.arange(height, dtype=np.float32)
188
+ #xv, yv = np.meshgrid(np.arange(width), np.arange(height))
189
+ #anchor_centers = np.stack([xv, yv], axis=-1).astype(np.float32)
190
+
191
+ #solution-3:
192
+ anchor_centers = np.stack(np.mgrid[:height, :width][::-1], axis=-1).astype(np.float32)
193
+ #print(anchor_centers.shape)
194
+
195
+ anchor_centers = (anchor_centers * stride).reshape( (-1, 2) )
196
+ if self._num_anchors>1:
197
+ anchor_centers = np.stack([anchor_centers]*self._num_anchors, axis=1).reshape( (-1,2) )
198
+ if len(self.center_cache)<100:
199
+ self.center_cache[key] = anchor_centers
200
+
201
+ pos_inds = np.where(scores>=threshold)[0]
202
+ bboxes = distance2bbox(anchor_centers, bbox_preds)
203
+ pos_scores = scores[pos_inds]
204
+ pos_bboxes = bboxes[pos_inds]
205
+ scores_list.append(pos_scores)
206
+ bboxes_list.append(pos_bboxes)
207
+ if self.use_kps:
208
+ kpss = distance2kps(anchor_centers, kps_preds)
209
+ #kpss = kps_preds
210
+ kpss = kpss.reshape( (kpss.shape[0], -1, 2) )
211
+ pos_kpss = kpss[pos_inds]
212
+ kpss_list.append(pos_kpss)
213
+ return scores_list, bboxes_list, kpss_list
214
+
215
+ def detect(self, img, input_size = None, thresh=None, max_num=0, metric='default'):
216
+ assert input_size is not None or self.input_size is not None
217
+ input_size = self.input_size if input_size is None else input_size
218
+
219
+ im_ratio = float(img.shape[0]) / img.shape[1]
220
+ model_ratio = float(input_size[1]) / input_size[0]
221
+ if im_ratio>model_ratio:
222
+ new_height = input_size[1]
223
+ new_width = int(new_height / im_ratio)
224
+ else:
225
+ new_width = input_size[0]
226
+ new_height = int(new_width * im_ratio)
227
+ det_scale = float(new_height) / img.shape[0]
228
+ resized_img = cv2.resize(img, (new_width, new_height))
229
+ det_img = np.zeros( (input_size[1], input_size[0], 3), dtype=np.uint8 )
230
+ det_img[:new_height, :new_width, :] = resized_img
231
+ det_thresh = thresh if thresh is not None else self.det_thresh
232
+
233
+ scores_list, bboxes_list, kpss_list = self.forward(det_img, det_thresh)
234
+
235
+ scores = np.vstack(scores_list)
236
+ scores_ravel = scores.ravel()
237
+ order = scores_ravel.argsort()[::-1]
238
+ bboxes = np.vstack(bboxes_list) / det_scale
239
+ if self.use_kps:
240
+ kpss = np.vstack(kpss_list) / det_scale
241
+ pre_det = np.hstack((bboxes, scores)).astype(np.float32, copy=False)
242
+ pre_det = pre_det[order, :]
243
+ keep = self.nms(pre_det)
244
+ det = pre_det[keep, :]
245
+ if self.use_kps:
246
+ kpss = kpss[order,:,:]
247
+ kpss = kpss[keep,:,:]
248
+ else:
249
+ kpss = None
250
+ if max_num > 0 and det.shape[0] > max_num:
251
+ area = (det[:, 2] - det[:, 0]) * (det[:, 3] -
252
+ det[:, 1])
253
+ img_center = img.shape[0] // 2, img.shape[1] // 2
254
+ offsets = np.vstack([
255
+ (det[:, 0] + det[:, 2]) / 2 - img_center[1],
256
+ (det[:, 1] + det[:, 3]) / 2 - img_center[0]
257
+ ])
258
+ offset_dist_squared = np.sum(np.power(offsets, 2.0), 0)
259
+ if metric=='max':
260
+ values = area
261
+ else:
262
+ values = area - offset_dist_squared * 2.0 # some extra weight on the centering
263
+ bindex = np.argsort(
264
+ values)[::-1] # some extra weight on the centering
265
+ bindex = bindex[0:max_num]
266
+ det = det[bindex, :]
267
+ if kpss is not None:
268
+ kpss = kpss[bindex, :]
269
+ return det, kpss
270
+
271
+ def autodetect(self, img, max_num=0, metric='max'):
272
+ bboxes, kpss = self.detect(img, input_size=(640, 640), thresh=0.5)
273
+ bboxes2, kpss2 = self.detect(img, input_size=(128, 128), thresh=0.5)
274
+ bboxes_all = np.concatenate([bboxes, bboxes2], axis=0)
275
+ kpss_all = np.concatenate([kpss, kpss2], axis=0)
276
+ keep = self.nms(bboxes_all)
277
+ det = bboxes_all[keep,:]
278
+ kpss = kpss_all[keep,:]
279
+ if max_num > 0 and det.shape[0] > max_num:
280
+ area = (det[:, 2] - det[:, 0]) * (det[:, 3] -
281
+ det[:, 1])
282
+ img_center = img.shape[0] // 2, img.shape[1] // 2
283
+ offsets = np.vstack([
284
+ (det[:, 0] + det[:, 2]) / 2 - img_center[1],
285
+ (det[:, 1] + det[:, 3]) / 2 - img_center[0]
286
+ ])
287
+ offset_dist_squared = np.sum(np.power(offsets, 2.0), 0)
288
+ if metric=='max':
289
+ values = area
290
+ else:
291
+ values = area - offset_dist_squared * 2.0 # some extra weight on the centering
292
+ bindex = np.argsort(
293
+ values)[::-1] # some extra weight on the centering
294
+ bindex = bindex[0:max_num]
295
+ det = det[bindex, :]
296
+ if kpss is not None:
297
+ kpss = kpss[bindex, :]
298
+ return det, kpss
299
+
300
+ def nms(self, dets):
301
+ thresh = self.nms_thresh
302
+ x1 = dets[:, 0]
303
+ y1 = dets[:, 1]
304
+ x2 = dets[:, 2]
305
+ y2 = dets[:, 3]
306
+ scores = dets[:, 4]
307
+
308
+ areas = (x2 - x1 + 1) * (y2 - y1 + 1)
309
+ order = scores.argsort()[::-1]
310
+
311
+ keep = []
312
+ while order.size > 0:
313
+ i = order[0]
314
+ keep.append(i)
315
+ xx1 = np.maximum(x1[i], x1[order[1:]])
316
+ yy1 = np.maximum(y1[i], y1[order[1:]])
317
+ xx2 = np.minimum(x2[i], x2[order[1:]])
318
+ yy2 = np.minimum(y2[i], y2[order[1:]])
319
+
320
+ w = np.maximum(0.0, xx2 - xx1 + 1)
321
+ h = np.maximum(0.0, yy2 - yy1 + 1)
322
+ inter = w * h
323
+ ovr = inter / (areas[i] + areas[order[1:]] - inter)
324
+
325
+ inds = np.where(ovr <= thresh)[0]
326
+ order = order[inds + 1]
327
+
328
+ return keep
329
+
refacer.py ADDED
@@ -0,0 +1,262 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ import onnxruntime as rt
3
+ import sys
4
+ from insightface.app import FaceAnalysis
5
+ sys.path.insert(1, './recognition')
6
+ from scrfd import SCRFD
7
+ from arcface_onnx import ArcFaceONNX
8
+ import os.path as osp
9
+ import os
10
+ from pathlib import Path
11
+ from tqdm import tqdm
12
+ import ffmpeg
13
+ import random
14
+ import multiprocessing as mp
15
+ from concurrent.futures import ThreadPoolExecutor
16
+ from insightface.model_zoo.inswapper import INSwapper
17
+ import psutil
18
+ from enum import Enum
19
+ from insightface.app.common import Face
20
+ from insightface.utils.storage import ensure_available
21
+ import re
22
+ import subprocess
23
+
24
+ class RefacerMode(Enum):
25
+ CPU, CUDA, COREML, TENSORRT = range(1, 5)
26
+
27
+ class Refacer:
28
+ def __init__(self,force_cpu=False,colab_performance=False):
29
+ self.first_face = False
30
+ self.force_cpu = force_cpu
31
+ self.colab_performance = colab_performance
32
+ self.__check_encoders()
33
+ self.__check_providers()
34
+ self.total_mem = psutil.virtual_memory().total
35
+ self.__init_apps()
36
+
37
+ def __check_providers(self):
38
+ if self.force_cpu :
39
+ self.providers = ['CPUExecutionProvider']
40
+ else:
41
+ self.providers = rt.get_available_providers()
42
+ rt.set_default_logger_severity(4)
43
+ self.sess_options = rt.SessionOptions()
44
+ self.sess_options.execution_mode = rt.ExecutionMode.ORT_SEQUENTIAL
45
+ self.sess_options.graph_optimization_level = rt.GraphOptimizationLevel.ORT_ENABLE_ALL
46
+
47
+ if len(self.providers) == 1 and 'CPUExecutionProvider' in self.providers:
48
+ self.mode = RefacerMode.CPU
49
+ self.use_num_cpus = mp.cpu_count()-1
50
+ self.sess_options.intra_op_num_threads = int(self.use_num_cpus/3)
51
+ print(f"CPU mode with providers {self.providers}")
52
+ elif self.colab_performance:
53
+ self.mode = RefacerMode.TENSORRT
54
+ self.use_num_cpus = mp.cpu_count()-1
55
+ self.sess_options.intra_op_num_threads = int(self.use_num_cpus/3)
56
+ print(f"TENSORRT mode with providers {self.providers}")
57
+ elif 'CoreMLExecutionProvider' in self.providers:
58
+ self.mode = RefacerMode.COREML
59
+ self.use_num_cpus = mp.cpu_count()-1
60
+ self.sess_options.intra_op_num_threads = int(self.use_num_cpus/3)
61
+ print(f"CoreML mode with providers {self.providers}")
62
+ elif 'CUDAExecutionProvider' in self.providers:
63
+ self.mode = RefacerMode.CUDA
64
+ self.use_num_cpus = 2
65
+ self.sess_options.intra_op_num_threads = 1
66
+ if 'TensorrtExecutionProvider' in self.providers:
67
+ self.providers.remove('TensorrtExecutionProvider')
68
+ print(f"CUDA mode with providers {self.providers}")
69
+ """
70
+ elif 'TensorrtExecutionProvider' in self.providers:
71
+ self.mode = RefacerMode.TENSORRT
72
+ #self.use_num_cpus = 1
73
+ #self.sess_options.intra_op_num_threads = 1
74
+ self.use_num_cpus = mp.cpu_count()-1
75
+ self.sess_options.intra_op_num_threads = int(self.use_num_cpus/3)
76
+ print(f"TENSORRT mode with providers {self.providers}")
77
+ """
78
+
79
+
80
+ def __init_apps(self):
81
+ assets_dir = ensure_available('models', 'buffalo_l', root='~/.insightface')
82
+
83
+ model_path = os.path.join(assets_dir, 'det_10g.onnx')
84
+ sess_face = rt.InferenceSession(model_path, self.sess_options, providers=self.providers)
85
+ self.face_detector = SCRFD(model_path,sess_face)
86
+ self.face_detector.prepare(0,input_size=(640, 640))
87
+
88
+ model_path = os.path.join(assets_dir , 'w600k_r50.onnx')
89
+ sess_rec = rt.InferenceSession(model_path, self.sess_options, providers=self.providers)
90
+ self.rec_app = ArcFaceONNX(model_path,sess_rec)
91
+ self.rec_app.prepare(0)
92
+
93
+ model_path = 'inswapper_128.onnx'
94
+ sess_swap = rt.InferenceSession(model_path, self.sess_options, providers=self.providers)
95
+ self.face_swapper = INSwapper(model_path,sess_swap)
96
+
97
+ def prepare_faces(self, faces):
98
+ self.replacement_faces=[]
99
+ for face in faces:
100
+ #image1 = cv2.imread(face.origin)
101
+ if "origin" in face:
102
+ face_threshold = face['threshold']
103
+ bboxes1, kpss1 = self.face_detector.autodetect(face['origin'], max_num=1)
104
+ if len(kpss1)<1:
105
+ raise Exception('No face detected on "Face to replace" image')
106
+ feat_original = self.rec_app.get(face['origin'], kpss1[0])
107
+ else:
108
+ face_threshold = 0
109
+ self.first_face = True
110
+ feat_original = None
111
+ print('No origin image: First face change')
112
+ #image2 = cv2.imread(face.destination)
113
+ _faces = self.__get_faces(face['destination'],max_num=1)
114
+ if len(_faces)<1:
115
+ raise Exception('No face detected on "Destination face" image')
116
+ self.replacement_faces.append((feat_original,_faces[0],face_threshold))
117
+
118
+ def __convert_video(self,video_path,output_video_path):
119
+ if self.video_has_audio:
120
+ print("Merging audio with the refaced video...")
121
+ new_path = output_video_path + str(random.randint(0,999)) + "_c.mp4"
122
+ #stream = ffmpeg.input(output_video_path)
123
+ in1 = ffmpeg.input(output_video_path)
124
+ in2 = ffmpeg.input(video_path)
125
+ out = ffmpeg.output(in1.video, in2.audio, new_path,video_bitrate=self.ffmpeg_video_bitrate,vcodec=self.ffmpeg_video_encoder)
126
+ out.run(overwrite_output=True,quiet=True)
127
+ else:
128
+ new_path = output_video_path
129
+ print("The video doesn't have audio, so post-processing is not necessary")
130
+
131
+ print(f"The process has finished.\nThe refaced video can be found at {os.path.abspath(new_path)}")
132
+ return new_path
133
+
134
+ def __get_faces(self,frame,max_num=0):
135
+
136
+ bboxes, kpss = self.face_detector.detect(frame,max_num=max_num,metric='default')
137
+
138
+ if bboxes.shape[0] == 0:
139
+ return []
140
+ ret = []
141
+ for i in range(bboxes.shape[0]):
142
+ bbox = bboxes[i, 0:4]
143
+ det_score = bboxes[i, 4]
144
+ kps = None
145
+ if kpss is not None:
146
+ kps = kpss[i]
147
+ face = Face(bbox=bbox, kps=kps, det_score=det_score)
148
+ face.embedding = self.rec_app.get(frame, kps)
149
+ ret.append(face)
150
+ return ret
151
+
152
+ def process_first_face(self,frame):
153
+ faces = self.__get_faces(frame,max_num=1)
154
+ if len(faces) != 0:
155
+ frame = self.face_swapper.get(frame, faces[0], self.replacement_faces[0][1], paste_back=True)
156
+ return frame
157
+
158
+ def process_faces(self,frame):
159
+ faces = self.__get_faces(frame,max_num=0)
160
+ for rep_face in self.replacement_faces:
161
+ for i in range(len(faces) - 1, -1, -1):
162
+ sim = self.rec_app.compute_sim(rep_face[0], faces[i].embedding)
163
+ if sim>=rep_face[2]:
164
+ frame = self.face_swapper.get(frame, faces[i], rep_face[1], paste_back=True)
165
+ del faces[i]
166
+ break
167
+ return frame
168
+
169
+ def __check_video_has_audio(self,video_path):
170
+ self.video_has_audio = False
171
+ probe = ffmpeg.probe(video_path)
172
+ audio_stream = next((stream for stream in probe['streams'] if stream['codec_type'] == 'audio'), None)
173
+ if audio_stream is not None:
174
+ self.video_has_audio = True
175
+
176
+ def reface_group(self, faces, frames, output):
177
+ with ThreadPoolExecutor(max_workers = self.use_num_cpus) as executor:
178
+ if self.first_face:
179
+ results = list(tqdm(executor.map(self.process_first_face, frames), total=len(frames),desc="Processing frames"))
180
+ else:
181
+ results = list(tqdm(executor.map(self.process_faces, frames), total=len(frames),desc="Processing frames"))
182
+ for result in results:
183
+ output.write(result)
184
+
185
+ def reface(self, video_path, faces):
186
+ self.__check_video_has_audio(video_path)
187
+ output_video_path = os.path.join('out',Path(video_path).name)
188
+ self.prepare_faces(faces)
189
+
190
+ cap = cv2.VideoCapture(video_path)
191
+ total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
192
+ print(f"Total frames: {total_frames}")
193
+
194
+ fps = cap.get(cv2.CAP_PROP_FPS)
195
+ frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
196
+ frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
197
+
198
+ fourcc = cv2.VideoWriter_fourcc(*'mp4v')
199
+ output = cv2.VideoWriter(output_video_path, fourcc, fps, (frame_width, frame_height))
200
+
201
+ frames=[]
202
+ self.k = 1
203
+ with tqdm(total=total_frames,desc="Extracting frames") as pbar:
204
+ while cap.isOpened():
205
+ flag, frame = cap.read()
206
+ if flag and len(frame)>0:
207
+ frames.append(frame.copy())
208
+ pbar.update()
209
+ else:
210
+ break
211
+ if (len(frames) > 1000):
212
+ self.reface_group(faces,frames,output)
213
+ frames=[]
214
+
215
+ cap.release()
216
+ pbar.close()
217
+
218
+ self.reface_group(faces,frames,output)
219
+ frames=[]
220
+ output.release()
221
+
222
+ return self.__convert_video(video_path,output_video_path)
223
+
224
+ def __try_ffmpeg_encoder(self, vcodec):
225
+ print(f"Trying FFMPEG {vcodec} encoder")
226
+ command = ['ffmpeg', '-y', '-f','lavfi','-i','testsrc=duration=1:size=1280x720:rate=30','-vcodec',vcodec,'testsrc.mp4']
227
+ try:
228
+ subprocess.run(command, check=True, capture_output=True).stderr
229
+ except subprocess.CalledProcessError as e:
230
+ print(f"FFMPEG {vcodec} encoder doesn't work -> Disabled.")
231
+ return False
232
+ print(f"FFMPEG {vcodec} encoder works")
233
+ return True
234
+
235
+ def __check_encoders(self):
236
+ self.ffmpeg_video_encoder='libx264'
237
+ self.ffmpeg_video_bitrate='0'
238
+
239
+ pattern = r"encoders: ([a-zA-Z0-9_]+(?: [a-zA-Z0-9_]+)*)"
240
+ command = ['ffmpeg', '-codecs', '--list-encoders']
241
+ commandout = subprocess.run(command, check=True, capture_output=True).stdout
242
+ result = commandout.decode('utf-8').split('\n')
243
+ for r in result:
244
+ if "264" in r:
245
+ encoders = re.search(pattern, r).group(1).split(' ')
246
+ for v_c in Refacer.VIDEO_CODECS:
247
+ for v_k in encoders:
248
+ if v_c == v_k:
249
+ if self.__try_ffmpeg_encoder(v_k):
250
+ self.ffmpeg_video_encoder=v_k
251
+ self.ffmpeg_video_bitrate=Refacer.VIDEO_CODECS[v_k]
252
+ print(f"Video codec for FFMPEG: {self.ffmpeg_video_encoder}")
253
+ return
254
+
255
+ VIDEO_CODECS = {
256
+ 'h264_videotoolbox':'0', #osx HW acceleration
257
+ 'h264_nvenc':'0', #NVIDIA HW acceleration
258
+ #'h264_qsv', #Intel HW acceleration
259
+ #'h264_vaapi', #Intel HW acceleration
260
+ #'h264_omx', #HW acceleration
261
+ 'libx264':'0' #No HW acceleration
262
+ }
requirements-COREML.txt ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ffmpeg_python==0.2.0
2
+ gradio==3.33.1
3
+ insightface==0.7.3
4
+ numpy==1.24.3
5
+ onnx==1.14.0
6
+ onnxruntime-silicon
7
+ opencv_python==4.7.0.72
8
+ opencv_python_headless==4.7.0.72
9
+ scikit-image==0.20.0
10
+ tqdm
11
+ psutil
12
+ ngrok
requirements-GPU.txt ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ffmpeg_python==0.2.0
2
+ gradio==3.33.1
3
+ insightface==0.7.3
4
+ numpy==1.24.3
5
+ onnx==1.14.0
6
+ onnxruntime_gpu==1.15.0
7
+ opencv_python==4.7.0.72
8
+ opencv_python_headless==4.7.0.72
9
+ scikit-image==0.20.0
10
+ tqdm
11
+ psutil
12
+ ngrok
requirements.txt ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ ffmpeg-python==0.2.0
2
+ gradio==4.40.0
3
+ insightface==0.7.3
4
+ numpy==1.24.3
5
+ onnx==1.14.0
6
+ onnxruntime==1.15.0
7
+ opencv-python-headless==4.7.0.72
8
+ scikit-image==0.20.0
9
+ tqdm
10
+ psutil
script.py ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from refacer import Refacer
2
+ from os.path import exists
3
+ import argparse
4
+ import cv2
5
+
6
+ parser = argparse.ArgumentParser(description='Refacer')
7
+ parser.add_argument("--force_cpu", help="Force CPU mode", default=False, action="store_true")
8
+ parser.add_argument("--colab_performance", help="Use in colab for better performance", default=False,action="store_true")
9
+ parser.add_argument("--face", help="Face to replace (ex: <src>,<dst>,<thresh=0.2>)", nargs='+', action="append", required=True)
10
+ parser.add_argument("--video", help="Video to parse", required=True)
11
+ args = parser.parse_args()
12
+
13
+ refacer = Refacer(force_cpu=args.force_cpu,colab_performance=args.colab_performance)
14
+
15
+ def run(video_path,faces):
16
+ video_path_exists = exists(video_path)
17
+ if video_path_exists == False:
18
+ print ("Can't find " + video_path)
19
+ return
20
+
21
+ faces_out = []
22
+ for face in faces:
23
+ face_str = face[0].split(",")
24
+ origin = exists(face_str[0])
25
+ if origin == False:
26
+ print ("Can't find " + face_str[0])
27
+ return
28
+ destination = exists(face_str[1])
29
+ if destination == False:
30
+ print ("Can't find " + face_str[1])
31
+ return
32
+
33
+ faces_out.append({
34
+ 'origin':cv2.imread(face_str[0]),
35
+ 'destination':cv2.imread(face_str[1]),
36
+ 'threshold':float(face_str[2])
37
+ })
38
+
39
+ return refacer.reface(video_path,faces_out)
40
+
41
+ run(args.video, args.face)