File size: 4,691 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
#!/usr/bin/env python3
"""Build NNCF calibration tensors for the inswapper export, from real face photos.

Calibrating a face model with random noise gives useless activation ranges (the
INT8 recogniser embeddings drift by ~0.2 cosine).  This script runs the *reference*
ONNX pipeline on real images, and stores the exact tensors each network sees:

    calib/inswapper_target.npy   (N, 3, 128, 128) float32  RGB in [0, 1]
    calib/inswapper_source.npy   (N, 512)         float32  projected source embedding
    calib/arcface_input.npy      (N, 3, 112, 112) float32  RGB, (x-127.5)/127.5
    calib/retinaface_input.npy   (N, 3, 640, 640) uint8-ish RGB, (x-127.5)/128

Usage:
    python make_calib_data.py --images faces/*.jpg --out-dir calib --samples 24
"""
import argparse
import glob
import os
import sys

import cv2
import numpy as np
import onnxruntime as ort

HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, os.path.dirname(HERE))
import inswapper_ov as iov  # noqa: E402


class ORTSession:
    def __init__(self, path):
        self.sess = ort.InferenceSession(path, providers=['CPUExecutionProvider'])
        self.names = [i.name for i in self.sess.get_inputs()]

    def __call__(self, inputs):
        return self.sess.run(None, {n: inputs[i] for i, n in enumerate(self.names)})


def augment(img, rng, scale_range=(0.75, 1.35), max_rot=8):
    """Random resize + rotation so that a handful of photos give usable statistics."""
    h, w = img.shape[:2]
    scale = rng.uniform(*scale_range)
    ang = rng.uniform(-max_rot, max_rot)
    matrix = cv2.getRotationMatrix2D((w / 2, h / 2), ang, scale)
    cos, sin = abs(matrix[0, 0]), abs(matrix[0, 1])
    nw, nh = int(h * cos + w * sin), int(h * sin + w * cos)
    matrix[0, 2] += nw / 2 - w / 2
    matrix[1, 2] += nh / 2 - h / 2
    return cv2.warpAffine(img, matrix, (nw, nh), borderMode=cv2.BORDER_REFLECT_101)


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('--images', nargs='+', required=True)
    parser.add_argument('--onnx-dir', default='onnx')
    parser.add_argument('--out-dir', default='calib')
    parser.add_argument('--samples', type=int, default=24)
    args = parser.parse_args()

    paths = []
    for pattern in args.images:
        paths.extend(sorted(glob.glob(pattern)))
    if not paths:
        raise SystemExit('no images given')

    detector = iov.RetinaFace(ORTSession(os.path.join(args.onnx_dir, 'retinaface_10g.onnx')))
    recognizer = iov.ArcFace(ORTSession(os.path.join(args.onnx_dir, 'arcface_w600k_r50.onnx')))
    import onnx
    emap = onnx.numpy_helper.to_array(onnx.load(os.path.join(args.onnx_dir, 'inswapper_128.onnx'))
                                      .graph.initializer[-1]).astype(np.float32)
    swapper = iov.INSwapper(ORTSession(os.path.join(args.onnx_dir, 'inswapper_128.onnx')), emap)

    rng = np.random.default_rng(0)
    swap_target, swap_source, arc_in, det_in = [], [], [], []
    for path in paths:
        base = cv2.imread(path)
        for i in range(max(1, args.samples // len(paths))):
            img = augment(base, rng) if i else base
            _, kpss = detector.detect(img)
            if len(kpss) == 0:
                continue
            kps = kpss[0]
            aimg, _ = iov.norm_crop(img, kps, 128)
            arc = cv2.dnn.blobFromImage(aimg, 1 / 127.5, (112, 112), (127.5, 127.5, 127.5), swapRB=True)
            emb = recognizer.compiled([arc])[0][0].flatten()
            emb = emb / np.linalg.norm(emb)
            latent = np.dot(emb.reshape(1, -1), emap)
            latent = (latent / np.linalg.norm(latent)).astype(np.float32)
            swap_target.append(cv2.dnn.blobFromImage(aimg, 1 / 255.0, (128, 128), (0, 0, 0), swapRB=True))
            swap_source.append(latent)
            arc_in.append(arc)
            h, w = img.shape[:2]
            det_img = np.zeros((640, 640, 3), np.uint8)
            s = min(640 / w, 640 / h)
            r = cv2.resize(img, (int(w * s), int(h * s)))
            det_img[:r.shape[0], :r.shape[1]] = r
            det_in.append(cv2.dnn.blobFromImage(det_img, 1 / 128.0, (640, 640),
                                                (127.5, 127.5, 127.5), swapRB=True))

    os.makedirs(args.out_dir, exist_ok=True)
    for name, data in (('inswapper_target', swap_target), ('inswapper_source', swap_source),
                       ('arcface_input', arc_in), ('retinaface_input', det_in)):
        arr = np.asarray(data, dtype=np.float32)
        path = os.path.join(args.out_dir, f'{name}.npy')
        np.save(path, arr)
        print(f'{path}: {arr.shape} {arr.dtype} min={arr.min():.3f} max={arr.max():.3f}')


if __name__ == '__main__':
    main()