File size: 10,182 Bytes
c6ec17d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
---
license: other
library_name: openvino
tags:
  - inswapper
  - insightface
  - face-swap
  - openvino
  - nncf
  - int4
  - quantization
  - deepfake
pipeline_tag: image-to-image
---

# inswapper (InsightFace face swap) → OpenVINO 16 / 8 / 4 bit

把 [haofanwang/inswapper](https://github.com/haofanwang/inswapper) / [InsightFace `inswapper_128`](https://github.com/deepinsight/insightface)
的整條換臉流程轉成 **OpenVINO IR**,並用 NNCF 量化成 **16-bit / 8-bit / 4-bit**。
推論時**不需要 PyTorch / onnxruntime**,只要 `openvino + opencv + numpy`。

> ⚠️ **授權與使用範圍(請先讀)**
> `inswapper_128`、ArcFace、RetinaFace 都是 **InsightFace** 訓練並發佈的模型,
> 依其官方授權**僅限非商業研究用途**(non-commercial research use only)。
> 本 repo 只做格式轉換與量化測試,**請勿用於冒充他人、散布謠言或任何未經當事人同意的用途**;
> 商用請先取得 InsightFace 官方授權。

## 這條流程用到三個模型

| 網路 | 用途 | 原始 ONNX |
|---|---|---|
| `retinaface_10g` | 人臉偵測 + 5 點關鍵點 | 16.9 MB |
| `arcface_w600k_r50` | 人臉辨識,輸出 512 維 embedding | 174.4 MB |
| `inswapper_128` | **換臉本體**(target 128×128 + source 512 維 embedding → 換好的臉) | 554.3 MB |

三者都已轉成 OpenVIR,各有 **fp16 / int8 / int4** 三種版本(`int4-mixed` 只有 swapper 版本)。

## 模型清單(`models/`)

| 檔案 | 精度 | 大小 (xml+bin) |
|---|---|---|
| `inswapper_fp16.*` | 16-bit FP16 | 276.9 MB |
| `inswapper_int8.*` | 8-bit W8A8(NNCF PTQ + SmoothQuant) | 139.1 MB |
| `inswapper_int4.*` | 4-bit `u4` 權重(NNCF LWC) | **69.7 MB** |
| `inswapper_int4_mixed.*` | 4-bit/8-bit 混合(75% 權重為 4-bit) | 91.7 MB |
| `retinaface_fp16.* / _int8.* / _int4.*` | 偵測 | 8.7 / 4.6 / 2.5 MB |
| `arcface_fp16.* / _int8.* / _int4.*` | 辨識 | 87.5 / 44.2 / 22.3 MB |
| `inswapper_emap.npy` | 512×512 的 embedding 投影矩陣(原模型最後一個 initializer) | 1.0 MB |

## 快速開始(單一 py 檔)

```bash
pip install openvino opencv-python numpy huggingface_hub
git clone https://huggingface.co/HelloSun/inswapper
cd inswapper
python inswapper_ov.py -s input/source_face_nasa_astronaut.jpg \
                       -t input/target_photo_nasa_astronaut.jpg \
                       -o result.jpg -m int8
```

模型會在第一次執行時自動從本 repo 下載並快取到 `~/.cache/inswapper_openvino`
(可用 `INSWAPPER_OV_CACHE` 改路徑;也可下載後用 `--model-dir models` 指定本機資料夾)。

```python
# 最小可運作範例
import cv2
from inswapper_ov import FaceSwapperOpenVINO

swapper = FaceSwapperOpenVINO(precision='int8')       # fp16 / int8 / int4 / int4-mixed
out = swapper.swap('source.jpg', 'target.jpg', out='result.jpg')
```

### 命令列選項

| 選項 | 說明 |
|---|---|
| `-s, --source` | 提供臉孔的來源圖 |
| `-t, --target` | 要被換臉的目標圖 |
| `-o, --output` | 輸出路徑(預設 `result.jpg`) |
| `-m, --model` | `fp16`(16-bit) / `int8`(8-bit) / `int4`(4-bit) / `int4-mixed` |
| `-d, --device` | `CPU` / `GPU` / `NPU` / `AUTO` |
| `--all-faces` | 目標圖中**每一張臉**都換 |
| `--face-index N` | 只換第 N 張臉(依置信度排序,預設 0 = 最大張) |
| `--det-size W H` | 偵測輸入尺寸,預設 `640 640` |
| `--model-dir` | 使用本機 `models/` 資料夾,不連網 |
| `--num-threads` | CPU 執行緒數 |

### Python API

```python
from inswapper_ov import FaceSwapperOpenVINO

sw = FaceSwapperOpenVINO(precision='int4-mixed', device='CPU', det_size=(640, 640))

bboxes, kpss = sw.detect(img_bgr)          # 偵測
emb, kps    = sw.embedding(img_bgr)        # 512 維 embedding(ArcFace)
out         = sw.swap('src.jpg', 'tgt.jpg', all_faces=True)
```

## 實測結果(16 / 8 / 4 bit)

素材為 NASA **公有領域**太空人肖像照片(見 `samples/README.md`),
以 **ONNX Runtime FP32**(完全相同的預處理與貼回程式碼)為參考基準:

![face montage](samples/20_face_montage.jpg)

| 精度 | swapper 大小 | 臉部 PSNR ↑ | 臉部 SSIM ↑ | 全圖 PSNR ↑ | embedding cos ↑ | 單次換臉 ↑ |
|---|---|---|---|---|---|---|
| ONNX Runtime FP32(參考) | 554.3 MB | – | – | – | 1.000 | 8.08 s |
| **fp16 (16-bit)** | 276.9 MB | **56.29 dB** | **0.9994** | 71.02 dB | 1.000 | 0.47 s |
| **int8 (8-bit)** | 139.1 MB | 19.28 dB | 0.556 | 37.09 dB | 0.979 | **0.09 s** |
| **int4-mixed (4-bit 主體)** | 91.7 MB | 17.28 dB | 0.672 | **37.70 dB** | 0.947 | 0.40 s |
| **int4 (純 4-bit)** | **69.7 MB** | 14.79 dB | 0.532 | 32.29 dB | 0.947 | 0.43 s |

(測試機:Xeon Platinum 8559C 16 vCPU、OpenVINO 2026.4.1、1280×1024 照片、單張臉。)

### 怎麼選

1. **fp16(16-bit)**:與原模型幾乎一模一樣(56 dB),又快 17×,**大多數情況請用這個**。
2. **int8(8-bit)**:**CPU 上最快(0.09 s)**,模型只有原版 1/4;臉部會有細微雜訊,
   但辨識度完全沒問題。注意:**校準資料一定要用真實人臉**(見下方匯出說明),
   用隨機雜訊校準會讓 ArcFace embedding 偏移到 cosine 0.78(會換到錯的人)。
3. **int4-mixed(4-bit 主體)**:視覺品質比純 4-bit 好很多,幾乎達到 int8 的水準,
   模型 91.7 MB(FP32 的 1/6)。**要在 4-bit 路線上兼顧品質就用這個**。
4. **int4(純 4-bit)**:最小(69.7 MB),但 swapper 是類 GAN 網路,
   4-bit 誤差會讓膚色嚴重偏色(見 montage 右下),**不建議實際使用**。

> INT4 在 **CPU 上不會比 INT8 快**(0.43 s vs 0.09 s):CPU plugin 會在 runtime 把 `u4` 權重
> 解壓回 FP32/FP16 再計算。INT4 的收益是**模型體積**,不是速度。

## Intel GPU / NPU

```bash
python inswapper_ov.py -s src.jpg -t tgt.jpg -o out.jpg -m int8 -d GPU
```

這三個 IR 都是標準 OpenVINO IR,不綁 plugin,`CPU / GPU / NPU / AUTO / HETERO` 都能跑。
GPU 上推薦 `int8`(plugin 對 INT8 卷積最佳化)與 `fp16`;
`int4` 在 GPU 上也不會有 4-bit 卷積加速,權重同樣是 runtime 解壓。
要產生針對 GPU plugin 調校的 INT8 模型:

```bash
python export_openvino_inswapper.py --onnx-dir onnx --target-device gpu \
    --models inswapper --precisions int8 --calib-dir calib
```

> 本 repo 的數據是在**沒有 GPU 的機器**上量測的(`available_devices = ['CPU']`),
> GPU 的說明依 OpenVINO plugin 能力整理,非本機實測。

## 從原始 ONNX 重新轉換/量化

```bash
# 1) 先取得三個 ONNX(見 README 的來源)
# 2) 用真實人臉照片產生 NNCF 校準張量(強烈建議,否則 INT8 embedding 會偏移)
python make_calib_data.py --images "input/*.jpg" --samples 24 --out-dir calib

# 3) 轉換 + 量化(fp16 / int8 / int4 一次產出)
python export_openvino_inswapper.py --onnx-dir onnx --out-dir models --calib-dir calib

# 只做純 4-bit 或混合精度 4/8-bit
python export_openvino_inswapper.py --onnx-dir onnx --out-dir models \
    --models inswapper --precisions int4 --int4-ratio 0.75
```

轉換管線裡處理了三個「不處理就會出包」的點:

1. **NNCF 的 `compress_weights(mode='int4_*')` 預設只壓 MatMul**,卷積會靜默退回 INT8
   → `enable_int4_for_convolutions()` 擴充 `_get_ratio_defining_params`,讓 Conv 也用 4-bit。
   swapper 裡的 `Gemm` 會自動被壓,`Conv` 則需要這個 patch。
2. **NNCF 的 CPU hardware config 只允許 8/16-bit 權重** → `enable_int4_weights_in_cpu_hw_config()`
   把 `q4_w` 加進 Convolution/MatMul 的 qspace,才會產生真正的 `u4` 常數。
   (`--target-device gpu` 時 NNCF 本身就允許 4-bit,不需 patch。)
3. **`openvino.convert_model()` 預設把權重壓成 FP16**(舊版 Model Converter 的行為)
   → `convert_onnx_fp32()` 以 `compress_to_fp16=False` 轉換,量化才從真正的 FP32 出發。

另外,`emap`(embedding 投影矩陣)在推論時需要,但它是 ONNX 最後一個 initializer,
已匯出成 `models/inswapper_emap.npy`,執行期就不必安裝 `onnx` 了。

## 重新產生 `samples/`

```bash
python make_sample.py --onnx-dir onnx --models-dir models --out-dir samples
```

## 疑難排解

| 症狀 | 原因 / 解決 |
|---|---|
| 換錯人 / 換到奇怪的人 | ArcFace 精度太低或校準資料是隨機雜訊;用 `--model-dir` 指定本機 IR,並用 `int8` 以上 |
| 臉部出現彩色斑塊 | 用了純 `int4`,改用 `int4-mixed` 或 `int8` / `fp16` |
| `No face detected` | 把 `--det-size` 調大(例如 `1024 1024`),或換張臉比較大的圖 |
| 想對影片換臉 | 逐影格呼叫 `sw.swap(...)`,參考下方「影片」範例 |
| OpenVINO 對 FP32 graph 預設 bf16 | `inswapper_ov.py` 已固定 `INFERENCE_PRECISION_HINT=f32`;自己呼叫 `ov.Core().compile_model` 時記得傳入 |

### 影片換臉(逐影格)

```python
import cv2
from inswapper_ov import FaceSwapperOpenVINO
sw = FaceSwapperOpenVINO(precision='int8')
cap, out = cv2.VideoCapture('in.mp4'), cv2.VideoWriter('out.mp4', cv2.VideoWriter_fourcc(*'mp4v'), 25, (1920, 1080))
while True:
    ok, frame = cap.read()
    if not ok: break
    out.write(sw.swap('src.jpg', frame, verbose=False))
out.release()
```

## 來源與授權

| 項目 | 來源 | 授權 |
|---|---|---|
| 換臉程式 | [haofanwang/inswapper](https://github.com/haofanwang/inswapper) | Apache-2.0 |
| `inswapper_128` | [deepinsight/insightface](https://github.com/deepinsight/insightface) | **非商業研究用途** |
| `arcface_w600k_r50`、`retinaface_10g` | InsightFace(buffalo_l 系列) | **非商業研究用途** |
| 量化 | [OpenVINO NNCF](https://github.com/openvinotoolkit/nncf) | Apache-2.0 |
| 測試照片 | NASA Johnson Space Center(公有領域) | Public Domain |
| `inswapper_ov.py` / 匯出腳本 / 本說明 | 本專案 | Apache-2.0 |

本 repo 的 `inswapper_ov.py` 內含從 InsightFace 移植的 `estimate_norm` / RetinaFace 後處理 /
換臉貼回(`paste_back`)邏輯,以確保 OpenVINO 版本與原版逐像素一致。