tasmulaev commited on
Commit
6fe5cf8
·
verified ·
1 Parent(s): c0dcaa2

Upload folder using huggingface_hub

Browse files
README.md ADDED
@@ -0,0 +1,142 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ pipeline_tag: keypoint-detection
4
+ library_name: mmpose
5
+ tags:
6
+ - pose-estimation
7
+ - hand-keypoints
8
+ - rtmpose
9
+ - simcc
10
+ - onnx
11
+ - sign-language
12
+ - russian-sign-language
13
+ - self-distillation
14
+ ---
15
+
16
+ # RTMPose-m Hand — blur-robust 2D hand keypoints for sign language video
17
+
18
+ RTMPose-m (21 hand keypoints, SimCC, 256×256) fine-tuned via **self-distillation on degraded video** — pseudo-labels produced by the model itself on clean frames, training inputs artificially degraded — to keep tracking hands through low resolution and motion blur, the main failure modes of off-the-shelf hand pose models on real-world sign language footage.
19
+
20
+ <p align="center">
21
+ <img src="assets/output_pytorch.jpg" alt="21-keypoint hand skeleton correctly placed on a heavily motion-blurred hand" width="420"/>
22
+ <br/>
23
+ <em>Model output on a heavily motion-blurred frame: the skeleton stays on the fingers. PyTorch and ONNX Runtime outputs are byte-identical (<code>assets/output_pytorch.jpg</code> vs <code>assets/output_onnxruntime.jpg</code>).</em>
24
+ </p>
25
+
26
+ Compared to the base [RTMPose-m Hand5](https://github.com/open-mmlab/mmpose/tree/main/projects/rtmpose) checkpoint, this model:
27
+
28
+ - **retains hands under high confidence thresholds**: at thr 0.3 it keeps 98.0% of hand detections vs 93.1% for the base model, so you can raise the threshold to cut false positives without losing recall;
29
+ - **detects hands the base model misses** on hard frames (motion blur during fast signing, crossed/interlocked hands, hands pressed against the body): at thr 0.3 it fires on 2,672 frames (52% of a test video) where the base model returns nothing;
30
+ - **produces temporally smoother keypoints**: ~39% less frame-to-frame jitter at thr 0.3, which directly reduces ragged keypoint sequences fed into downstream sign language models (Uni-Sign, streaming/wait-k translation pipelines);
31
+ - **does not regress on clean frames** — on sharp, unoccluded frames the two models are visually indistinguishable.
32
+
33
+ Same architecture, same input size, same 21-keypoint COCO hand skeleton as the original — a **drop-in replacement** for the `rtmpose-m_simcc-hand5` checkpoint in any mmpose / rtmlib / mmdeploy pipeline.
34
+
35
+ ## Files
36
+
37
+ | File | Description |
38
+ |---|---|
39
+ | `rtmpose-m_hand_distill-256x256-a996d9ec.pth` | PyTorch weights (EMA, epoch 100), mmpose format, 55 MB |
40
+ | `rtmpose-m_hand_distill.py` | mmpose/mmengine training and inference config |
41
+ | `degrade_video.py` | Video degradation script used to build the "dirty" half of the training set (opencv + numpy only) |
42
+ | `onnx/rtmpose-m-distill-256x256.onnx` | ONNX export (opset 11, dynamic batch, FP32), outputs `simcc_x`/`simcc_y` |
43
+ | `onnx/deploy.json`, `onnx/pipeline.json` | mmdeploy SDK configs for the ONNX model |
44
+ | `assets/` | PyTorch vs ONNX Runtime output parity check (byte-identical) |
45
+
46
+ ## How it was trained
47
+
48
+ Self-distillation on degraded video — the model is its own teacher:
49
+
50
+ 1. **Pseudo-labels.** The base RTMPose-m Hand5 checkpoint with a hand-crop pipeline was run offline over the original clean FullHD frames of the [Slovo](https://github.com/hukenovs/slovo) Russian Sign Language video dataset, producing hand crops with 21-keypoint pseudo-labels.
51
+ 2. **Input degradation.** 50% of the source videos were then degraded (the "dirty" half) with the included `degrade_video.py`, targeting the dominant real-world failure mode — low source resolution: the full frame is downscaled so its short side lands around 300 px (randomized per clip), then resized back to the original size (`INTER_AREA` down, bilinear up), so teacher coordinates taken from the clean frames stay valid. The degradation toolkit also includes optical-flow-based motion blur (Farneback flow, accumulated along the flow field), gamma/lighting shift, Gaussian noise and JPEG compression, organized into severity profiles 1–5. Degradation is applied to the **full frame before hand cropping** (so crops don't retain more detail than a real low-res source would have), and per-clip seeding (`crc32(filename) + seed`) makes it fully reproducible. The student therefore learns to predict sharp-frame keypoints from corrupted inputs.
52
+ 3. **Fine-tuning.** The student — the same RTMPose-m Hand5 checkpoint that produced the labels — was fine-tuned for 100 epochs on the resulting **handset_mix** set: 300,238 training crops, 33,347 validation crops (16,697 clean / 16,650 dirty).
53
+
54
+ Training setup: AdamW (lr 4e-4, wd 0.05), batch 1024, cosine schedule, AMP, EMA (ExpMomentumEMA, momentum 2e-4), flip/rotate/scale augmentation, seed 21. Single NVIDIA RTX PRO 6000 Blackwell GPU, PyTorch 2.7.0 / CUDA 12.8 / MMEngine 0.10.7, ~10 h wall-clock. Full details in `rtmpose-m_hand_distill.py`.
55
+
56
+ Held-out validation against pseudo-labels (mixed clean + dirty, 33,347 crops): the released checkpoint is the **EMA weights at epoch 100** — **PCK@0.2 (bbox-normalized) 0.9893, EPE 5.96 px**. Best raw validation score during training was PCK 0.9896 / EPE 5.87 at epoch 42; the validation curve is flat from roughly epoch 20 onward.
57
+
58
+ ## Evaluation vs the base model
59
+
60
+ Side-by-side comparison on a sign language test video (~5,100 frames), hand retention relative to detections at thr 0.1:
61
+
62
+ | Confidence threshold | 0.1 | 0.15 | 0.2 | 0.3 |
63
+ |---|---|---|---|---|
64
+ | Hand retention, **base** | 100% | 98.8% | 97.3% | 93.1% |
65
+ | Hand retention, **this model** | 100% | **99.8%** | **99.4%** | **98.0%** |
66
+ | Frames where this model detects a hand and base does not | 431 (8%) | 875 (17%) | 1,338 (26%) | 2,672 (52%) |
67
+
68
+ Frame-to-frame keypoint jitter at thr 0.3 is ~39% lower than the base model. The frames recovered by this model are dominated by motion blur during fast signing, crossed/interlocked hands, and hands pressed against the torso; visual inspection confirms the recovered skeletons lie on the fingers rather than being spurious detections.
69
+
70
+ **Recommended operating point:** thr 0.2–0.3 (the base model effectively requires thr ≤ 0.15 to avoid dropping hands).
71
+
72
+ ## Usage
73
+
74
+ ### mmpose
75
+
76
+ ```python
77
+ from mmpose.apis import init_model, inference_topdown
78
+
79
+ model = init_model(
80
+ 'rtmpose-m_hand_distill.py',
81
+ 'rtmpose-m_hand_distill-256x256-a996d9ec.pth',
82
+ device='cuda:0',
83
+ )
84
+ results = inference_topdown(model, 'hand_crop.jpg')
85
+ keypoints = results[0].pred_instances.keypoints # (1, 21, 2)
86
+ scores = results[0].pred_instances.keypoint_scores # (1, 21)
87
+ ```
88
+
89
+ ### ONNX Runtime (no mmpose dependency)
90
+
91
+ ```python
92
+ import cv2
93
+ import numpy as np
94
+ import onnxruntime as ort
95
+
96
+ sess = ort.InferenceSession('onnx/rtmpose-m-distill-256x256.onnx')
97
+
98
+ img = cv2.imread('hand_crop.jpg') # BGR hand crop
99
+ inp = cv2.resize(img, (256, 256))[:, :, ::-1].astype(np.float32) # to RGB
100
+ inp = (inp - [123.675, 116.28, 103.53]) / [58.395, 57.12, 57.375]
101
+ inp = inp.transpose(2, 0, 1)[None]
102
+
103
+ simcc_x, simcc_y = sess.run(None, {'input': inp.astype(np.float32)})
104
+ # SimCC decode: argmax over each axis, divide by split ratio (2.0)
105
+ x = simcc_x[0].argmax(axis=1) / 2.0 # (21,) in 256x256 crop coords
106
+ y = simcc_y[0].argmax(axis=1) / 2.0
107
+ conf = np.minimum(simcc_x[0].max(axis=1), simcc_y[0].max(axis=1))
108
+ ```
109
+
110
+ The ONNX file is also compatible with [rtmlib](https://github.com/Tau-J/rtmlib) and the [mmdeploy](https://github.com/open-mmlab/mmdeploy) SDK (use `onnx/` as the SDK model directory).
111
+
112
+ ## Limitations
113
+
114
+ - **Pseudo-label supervision.** Training targets are the base model's own predictions, not human annotations; systematic biases of RTMPose-m Hand5 are inherited rather than corrected. Validation PCK/EPE above are measured against pseudo-labels, not ground truth.
115
+ - **Comparative evaluation.** The improvement numbers compare this model against its own teacher on sign language video; the model has not been benchmarked on GT hand datasets (FreiHAND, COCO-WholeBody Hand).
116
+ - **Domain.** Tuned on Russian Sign Language studio-style recordings (frontal upper-body view, 194 signers). Behavior on in-the-wild hands (egocentric, object interaction, outdoor) is untested.
117
+ - Top-down model: expects a hand crop; you still need a hand/person detector upstream.
118
+
119
+ ## Training data attribution
120
+
121
+ Pseudo-labels and training crops are derived from the [Slovo Russian Sign Language dataset](https://github.com/hukenovs/slovo) (SaluteDevices), distributed under a variant of CC BY-SA 4.0. The dataset itself is **not** included in this repository — only model weights.
122
+
123
+ ## Citations
124
+
125
+ ```bibtex
126
+ @misc{jiang2023rtmpose,
127
+ title={RTMPose: Real-Time Multi-Person Pose Estimation based on MMPose},
128
+ author={Jiang, Tao and Lu, Peng and Zhang, Li and Ma, Ningsheng and Han, Rui and Lyu, Chengqi and Li, Yining and Chen, Kai},
129
+ year={2023},
130
+ eprint={2303.07399},
131
+ archivePrefix={arXiv}
132
+ }
133
+
134
+ @inproceedings{kapitanov2023slovo,
135
+ title={Slovo: Russian Sign Language Dataset},
136
+ author={Kapitanov, Alexander and Kvanchiani, Karina and Nagaev, Alexander and Petrova, Elizaveta},
137
+ booktitle={International Conference on Computer Vision Systems},
138
+ pages={63--73},
139
+ year={2023},
140
+ organization={Springer}
141
+ }
142
+ ```
assets/output_onnxruntime.jpg ADDED
assets/output_pytorch.jpg ADDED
degrade_video.py ADDED
@@ -0,0 +1,340 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import argparse
2
+ import glob
3
+ import os
4
+ import zlib
5
+ from concurrent.futures import ProcessPoolExecutor, as_completed
6
+ import cv2
7
+ import numpy as np
8
+ from tqdm import tqdm
9
+
10
+ def _log(msg):
11
+ try:
12
+ tqdm.write(msg)
13
+ except Exception:
14
+ print(msg)
15
+
16
+ def _worker_init():
17
+ try:
18
+ cv2.setNumThreads(1)
19
+ except Exception:
20
+ pass
21
+
22
+ VIDEO_EXT = (".mp4", ".avi", ".mov", ".mkv", ".webm", ".m4v")
23
+ MIN_SHORT_SIDE = 32
24
+
25
+ SEVERITY = {
26
+ 1: dict(res=(0.80, 0.95), jpeg=(60, 80), gamma=(1.00, 1.25), noise=(2, 5), blur=(0.3, 0.6)),
27
+ 2: dict(res=(0.65, 0.85), jpeg=(45, 65), gamma=(1.10, 1.40), noise=(4, 9), blur=(0.5, 0.9)),
28
+ 3: dict(res=(0.50, 0.70), jpeg=(30, 50), gamma=(1.20, 1.60), noise=(7, 13), blur=(0.7, 1.1)),
29
+ 4: dict(res=(0.38, 0.55), jpeg=(20, 35), gamma=(1.40, 1.90), noise=(10, 18), blur=(0.9, 1.4)),
30
+ 5: dict(res=(0.28, 0.42), jpeg=(12, 22), gamma=(1.60, 2.20), noise=(14, 24), blur=(1.1, 1.8)),
31
+ }
32
+
33
+ def make_profile(level, rng):
34
+ p = SEVERITY[level]
35
+ return {
36
+ "res": float(rng.uniform(*p["res"])),
37
+ "jpeg": int(rng.uniform(*p["jpeg"])),
38
+ "gamma": float(rng.uniform(*p["gamma"])),
39
+ "noise": float(rng.uniform(*p["noise"])),
40
+ "blur": float(rng.uniform(*p["blur"])),
41
+ }
42
+
43
+ def compute_flow(prev_gray, cur_gray, flow_max_side=480):
44
+ h, w = cur_gray.shape
45
+ s = min(1.0, flow_max_side / float(max(h, w)))
46
+ if s < 1.0:
47
+ pg = cv2.resize(prev_gray, (int(w * s), int(h * s)), interpolation=cv2.INTER_AREA)
48
+ cg = cv2.resize(cur_gray, (int(w * s), int(h * s)), interpolation=cv2.INTER_AREA)
49
+ else:
50
+ pg, cg = prev_gray, cur_gray
51
+ flow = cv2.calcOpticalFlowFarneback(pg, cg, None, 0.5, 3, 21, 3, 7, 1.5, 0)
52
+ if s < 1.0:
53
+ flow = cv2.resize(flow, (w, h), interpolation=cv2.INTER_LINEAR) / s
54
+ return cv2.GaussianBlur(flow, (0, 0), 2.0)
55
+
56
+ def motion_blur_flow(frame, flow, strength, n_steps=7):
57
+ if flow is None or strength <= 0:
58
+ return frame
59
+ h, w = frame.shape[:2]
60
+ xx, yy = np.meshgrid(np.arange(w, dtype=np.float32), np.arange(h, dtype=np.float32))
61
+ acc = np.zeros_like(frame, dtype=np.float32)
62
+ for t in np.linspace(-0.5, 0.5, n_steps, dtype=np.float32):
63
+ mapx = xx + (t * strength) * flow[..., 0]
64
+ mapy = yy + (t * strength) * flow[..., 1]
65
+ acc += cv2.remap(frame, mapx, mapy, cv2.INTER_LINEAR, borderMode=cv2.BORDER_REFLECT)
66
+ return np.clip(acc / n_steps, 0, 255).astype(np.uint8)
67
+
68
+ def downscale(frame, res_scale, keep_native=False):
69
+ h, w = frame.shape[:2]
70
+ if res_scale >= 0.999:
71
+ return frame
72
+ nh, nw = max(2, round(h * res_scale)), max(2, round(w * res_scale))
73
+ small = cv2.resize(frame, (nw, nh), interpolation=cv2.INTER_AREA)
74
+ if keep_native:
75
+ return small
76
+ return cv2.resize(small, (w, h), interpolation=cv2.INTER_LINEAR)
77
+
78
+ def degrade_resolution(frame, target_short_side, rng=None, jitter=0):
79
+ h, w = frame.shape[:2]
80
+ tgt = int(target_short_side)
81
+ if jitter and rng is not None:
82
+ lo = max(MIN_SHORT_SIDE, tgt - jitter)
83
+ hi = tgt + jitter + 1
84
+ tgt = int(rng.integers(lo, max(lo + 1, hi)))
85
+ tgt = max(MIN_SHORT_SIDE, tgt)
86
+ short = min(h, w)
87
+ res = min(1.0, tgt / float(short))
88
+ return downscale(frame, res, keep_native=False)
89
+
90
+ def linear_motion_blur(img, ksize, angle_deg):
91
+ ksize = max(3, int(ksize)) | 1
92
+ k = np.zeros((ksize, ksize), np.float32)
93
+ k[ksize // 2, :] = 1.0
94
+ c = (ksize - 1) * 0.5
95
+ M = cv2.getRotationMatrix2D((c, c), angle_deg, 1.0)
96
+ k = cv2.warpAffine(k, M, (ksize, ksize))
97
+ ssum = float(k.sum())
98
+ if ssum > 1e-6:
99
+ k /= ssum
100
+ return cv2.filter2D(img, -1, k)
101
+
102
+ def random_motion_blur(img, rng, max_ksize):
103
+ if max_ksize is None or max_ksize < 3 or rng is None:
104
+ return img
105
+ ksize = int(rng.integers(1, int(max_ksize) + 1))
106
+ if ksize < 3:
107
+ return img
108
+ return linear_motion_blur(img, ksize, float(rng.uniform(0.0, 180.0)))
109
+
110
+ def light_noise(frame, gamma, noise_sigma, rng):
111
+ if abs(gamma - 1.0) < 1e-3 and noise_sigma <= 0:
112
+ return frame
113
+ f = frame.astype(np.float32)
114
+ if abs(gamma - 1.0) > 1e-3:
115
+ f = np.power(f / 255.0, gamma) * 255.0
116
+ if noise_sigma > 0:
117
+ f = f + rng.normal(0.0, noise_sigma, f.shape).astype(np.float32)
118
+ return np.clip(f, 0, 255).astype(np.uint8)
119
+
120
+ def jpeg_compress(frame, quality):
121
+ if quality >= 100:
122
+ return frame
123
+ ok, enc = cv2.imencode(".jpg", frame, [cv2.IMWRITE_JPEG_QUALITY, int(quality)])
124
+ return cv2.imdecode(enc, cv2.IMREAD_COLOR) if ok else frame
125
+
126
+ def degrade_frame(frame, flow, prof, flags):
127
+ out = frame
128
+ if flags["blur"]:
129
+ out = motion_blur_flow(out, flow, prof["blur"])
130
+ if flags["noise"]:
131
+ out = light_noise(out, prof["gamma"], prof["noise"], flags["rng"])
132
+ if flags["res"]:
133
+ out = downscale(out, prof["res"], keep_native=flags["keep_native"])
134
+ if flags["jpeg"]:
135
+ out = jpeg_compress(out, prof["jpeg"])
136
+ return out
137
+
138
+ def process_video(src, dst, args, flags, quiet=False):
139
+ cap = cv2.VideoCapture(src)
140
+ if not cap.isOpened():
141
+ _log(f"[err]: {src}")
142
+ return False
143
+ fps = cap.get(cv2.CAP_PROP_FPS)
144
+ fps = fps if fps and fps > 1 else 30.0
145
+ w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
146
+ h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
147
+ n_total = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
148
+
149
+ seed = (args.seed + zlib.crc32(os.path.basename(src).encode("utf-8"))) % (2**31)
150
+ rng = np.random.default_rng(seed)
151
+ flags["rng"] = rng
152
+
153
+ lvl = int(rng.integers(2, 6)) if args.severity == "random" else int(args.severity)
154
+ prof = make_profile(lvl, rng)
155
+
156
+ if args.target_short_side:
157
+ tgt = args.target_short_side
158
+ if args.target_jitter:
159
+ lo = max(MIN_SHORT_SIDE, args.target_short_side - args.target_jitter)
160
+ hi = args.target_short_side + args.target_jitter + 1
161
+ tgt = int(rng.integers(lo, max(lo + 1, hi)))
162
+ tgt = max(MIN_SHORT_SIDE, tgt)
163
+ short = min(h, w)
164
+ prof["res"] = min(1.0, tgt / float(short))
165
+ flags["res"] = True
166
+ flags["blur"] = flags["blur"] and not args.res_only
167
+ flags["noise"] = flags["noise"] and not args.res_only
168
+ flags["jpeg"] = (args.jpeg is not None) and flags["jpeg"]
169
+ if args.jpeg is not None:
170
+ prof["jpeg"] = args.jpeg
171
+ eff = int(round(short * prof["res"]))
172
+ mode_str = f"target_short={tgt} (eff~{eff}px) res_only={args.res_only} jpeg={args.jpeg}"
173
+ else:
174
+ if args.jpeg is not None:
175
+ prof["jpeg"] = args.jpeg
176
+ mode_str = (f"lvl={lvl} res={prof['res']:.2f} jpeg={prof['jpeg']} "
177
+ f"gamma={prof['gamma']:.2f} noise={prof['noise']:.1f} blur={prof['blur']:.2f}")
178
+
179
+ if flags["keep_native"]:
180
+ nh, nw = max(2, round(h * prof["res"])), max(2, round(w * prof["res"]))
181
+ out_w, out_h = nw, nh
182
+ else:
183
+ out_w, out_h = (w * 2 if args.preview else w), h
184
+
185
+ tmp_dst = dst + ".part.mp4"
186
+ writer = cv2.VideoWriter(tmp_dst, cv2.VideoWriter_fourcc(*"mp4v"), fps, (out_w, out_h))
187
+ if not writer.isOpened():
188
+ _log(f"[err]: {dst}")
189
+ cap.release()
190
+ return False
191
+ if not quiet:
192
+ _log(f"[run] {os.path.basename(src)} {mode_str}")
193
+
194
+ bar = tqdm(total=(n_total if n_total > 0 else None),
195
+ desc=os.path.basename(src)[:24], unit="f", leave=False, disable=quiet)
196
+ prev_gray, idx = None, 0
197
+ while True:
198
+ ok, frame = cap.read()
199
+ if not ok:
200
+ break
201
+ idx += 1
202
+ if bar is not None:
203
+ bar.update(1)
204
+ flow = None
205
+ if flags["blur"]:
206
+ cur_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
207
+ if prev_gray is not None:
208
+ flow = compute_flow(prev_gray, cur_gray)
209
+ prev_gray = cur_gray
210
+ dirty = degrade_frame(frame, flow, prof, flags)
211
+ if args.preview and not flags["keep_native"]:
212
+ dirty = np.hstack([frame, dirty])
213
+ writer.write(dirty)
214
+
215
+ if bar is not None:
216
+ bar.close()
217
+ cap.release()
218
+ writer.release()
219
+ if idx == 0:
220
+ _log(f"[err]: {src}")
221
+ try:
222
+ os.remove(tmp_dst)
223
+ except OSError:
224
+ pass
225
+ return False
226
+ try:
227
+ os.replace(tmp_dst, dst)
228
+ except OSError as e:
229
+ _log(f"[err] {tmp_dst} -> {dst}: {e}")
230
+ return False
231
+ if not quiet:
232
+ _log(f"[ok] {os.path.basename(dst)} ({idx}, {out_w}x{out_h})")
233
+ return True
234
+
235
+
236
+ def main():
237
+ ap = argparse.ArgumentParser(description="Video degradation for self-distillation.")
238
+ ap.add_argument("--source", required=True, help="video file or directory")
239
+ ap.add_argument("--out", required=True, help="output file or directory")
240
+ ap.add_argument("--severity", default="3", help="1..5 or 'random' (profile mode)")
241
+ ap.add_argument("--target-short-side", type=int, default=0,
242
+ help="mode B: absolute short side, px (e.g. 300)")
243
+ ap.add_argument("--target-jitter", type=int, default=0,
244
+ help="random +/- to the short side per clip (e.g. 50)")
245
+ ap.add_argument("--res-only", action="store_true",
246
+ help="mode B: resolution only, no noise/blur")
247
+ ap.add_argument("--jpeg", type=int, default=None,
248
+ help="JPEG quality 1..100 (if set, applied on top)")
249
+ ap.add_argument("--keep-native", action="store_true",
250
+ help="output at low resolution, do not upscale back")
251
+ ap.add_argument("--seed", type=int, default=0)
252
+ ap.add_argument("--workers", type=int, default=max(1, (os.cpu_count() or 2) // 2),
253
+ help="parallel processes per video (default = half the cores)")
254
+ ap.add_argument("--overwrite", action="store_true",
255
+ help="overwrite finished files (default skips them — resume)")
256
+ ap.add_argument("--preview", action="store_true")
257
+ ap.add_argument("--no-blur", action="store_true")
258
+ ap.add_argument("--no-res", action="store_true")
259
+ ap.add_argument("--no-noise", action="store_true")
260
+ ap.add_argument("--no-jpeg", action="store_true")
261
+ args = ap.parse_args()
262
+
263
+ if args.severity != "random" and not args.target_short_side:
264
+ if int(args.severity) not in SEVERITY:
265
+ raise SystemExit("[err] severity 1..5 or 'random'")
266
+ if args.preview and args.keep_native:
267
+ print("[warn] --preview is incompatible with --keep-native, preview disabled")
268
+ args.preview = False
269
+
270
+ base_flags = {
271
+ "blur": not args.no_blur,
272
+ "res": not args.no_res,
273
+ "noise": not args.no_noise,
274
+ "jpeg": not args.no_jpeg,
275
+ "keep_native": args.keep_native,
276
+ }
277
+
278
+ if os.path.isdir(args.source):
279
+ os.makedirs(args.out, exist_ok=True)
280
+ files = sorted(f for f in glob.glob(os.path.join(args.source, "*"))
281
+ if f.lower().endswith(VIDEO_EXT))
282
+ if not files:
283
+ raise SystemExit(f"[err] no videos in {args.source}")
284
+
285
+ def dst_of(f):
286
+ return os.path.join(args.out, os.path.splitext(os.path.basename(f))[0] + "_dirty.mp4")
287
+
288
+ todo, skipped = [], 0
289
+ for f in files:
290
+ d = dst_of(f)
291
+ if not args.overwrite and os.path.exists(d): # resume
292
+ skipped += 1
293
+ else:
294
+ todo.append((f, d))
295
+ print(f"[info] found {len(files)} videos, to process {len(todo)}, "
296
+ f"skipped (already done) {skipped}")
297
+ if not todo:
298
+ return
299
+
300
+ workers = max(1, args.workers)
301
+ if workers == 1:
302
+ for f, d in tqdm(todo, desc="videos", unit="vid"):
303
+ process_video(f, d, args, dict(base_flags), quiet=False)
304
+ else:
305
+ print(f"[info] parallel processes: {workers}")
306
+ # in parallel mode each video has no per-frame bar (quiet),
307
+ # instead we show overall progress across files
308
+ with ProcessPoolExecutor(max_workers=workers, initializer=_worker_init) as ex:
309
+ futs = {ex.submit(process_video, f, d, args, dict(base_flags), True): f
310
+ for f, d in todo}
311
+ ok = fail = 0
312
+ bar = tqdm(total=len(futs), desc="videos", unit="vid")
313
+ for fut in as_completed(futs):
314
+ try:
315
+ ok += 1 if fut.result() else 0
316
+ fail += 0 if fut.result() else 1
317
+ except Exception as e:
318
+ fail += 1
319
+ _log(f"[err] {os.path.basename(futs[fut])}: {type(e).__name__}: {e}")
320
+ if bar is not None:
321
+ bar.update(1)
322
+ bar.set_postfix(ok=ok, fail=fail)
323
+ if bar is not None:
324
+ bar.close()
325
+ print(f"[done] succeeded {ok}, failed {fail}")
326
+ else:
327
+ out = args.out
328
+ if os.path.isdir(out) or out.endswith(os.sep):
329
+ os.makedirs(out, exist_ok=True)
330
+ out = os.path.join(out, os.path.splitext(os.path.basename(args.source))[0] + "_dirty.mp4")
331
+ elif os.path.dirname(out):
332
+ os.makedirs(os.path.dirname(out), exist_ok=True)
333
+ if not args.overwrite and os.path.exists(out):
334
+ print(f"[info] already exists, skipping: {out} (--overwrite to recompute)")
335
+ return
336
+ process_video(args.source, out, args, dict(base_flags), quiet=False)
337
+
338
+
339
+ if __name__ == "__main__":
340
+ main()
onnx/deploy.json ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "version": "1.3.1",
3
+ "task": "PoseDetector",
4
+ "models": [
5
+ {
6
+ "name": "topdownposeestimator",
7
+ "net": "rtmpose-m-distill-256x256.onnx",
8
+ "weights": "",
9
+ "backend": "onnxruntime",
10
+ "precision": "FP32",
11
+ "batch_size": 1,
12
+ "dynamic_shape": true
13
+ }
14
+ ],
15
+ "customs": []
16
+ }
onnx/pipeline.json ADDED
@@ -0,0 +1,125 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "pipeline": {
3
+ "input": [
4
+ "img"
5
+ ],
6
+ "output": [
7
+ "post_output"
8
+ ],
9
+ "tasks": [
10
+ {
11
+ "type": "Task",
12
+ "module": "Transform",
13
+ "name": "Preprocess",
14
+ "input": [
15
+ "img"
16
+ ],
17
+ "output": [
18
+ "prep_output"
19
+ ],
20
+ "transforms": [
21
+ {
22
+ "type": "LoadImageFromFile"
23
+ },
24
+ {
25
+ "type": "TopDownGetBboxCenterScale",
26
+ "padding": 1.25,
27
+ "image_size": [
28
+ 192,
29
+ 256
30
+ ]
31
+ },
32
+ {
33
+ "type": "TopDownAffine",
34
+ "image_size": [
35
+ 192,
36
+ 256
37
+ ]
38
+ },
39
+ {
40
+ "type": "Normalize",
41
+ "mean": [
42
+ 123.675,
43
+ 116.28,
44
+ 103.53
45
+ ],
46
+ "std": [
47
+ 58.395,
48
+ 57.12,
49
+ 57.375
50
+ ],
51
+ "to_rgb": true
52
+ },
53
+ {
54
+ "type": "ImageToTensor",
55
+ "keys": [
56
+ "img"
57
+ ]
58
+ },
59
+ {
60
+ "type": "Collect",
61
+ "keys": [
62
+ "img"
63
+ ],
64
+ "meta_keys": [
65
+ "img_shape",
66
+ "pad_shape",
67
+ "ori_shape",
68
+ "img_norm_cfg",
69
+ "scale_factor",
70
+ "bbox_score",
71
+ "center",
72
+ "scale"
73
+ ]
74
+ }
75
+ ]
76
+ },
77
+ {
78
+ "name": "topdownposeestimator",
79
+ "type": "Task",
80
+ "module": "Net",
81
+ "is_batched": true,
82
+ "input": [
83
+ "prep_output"
84
+ ],
85
+ "output": [
86
+ "infer_output"
87
+ ],
88
+ "input_map": {
89
+ "img": "input"
90
+ },
91
+ "output_map": {}
92
+ },
93
+ {
94
+ "type": "Task",
95
+ "module": "mmpose",
96
+ "name": "postprocess",
97
+ "component": "SimCCLabelDecode",
98
+ "params": {
99
+ "flip_test": false,
100
+ "type": "SimCCLabel",
101
+ "input_size": [
102
+ 256,
103
+ 256
104
+ ],
105
+ "sigma": [
106
+ 5.66,
107
+ 5.66
108
+ ],
109
+ "simcc_split_ratio": 2.0,
110
+ "normalize": false,
111
+ "use_dark": false,
112
+ "_scope_": "mmpose",
113
+ "export_postprocess": false
114
+ },
115
+ "output": [
116
+ "post_output"
117
+ ],
118
+ "input": [
119
+ "prep_output",
120
+ "infer_output"
121
+ ]
122
+ }
123
+ ]
124
+ }
125
+ }
onnx/rtmpose-m-distill-256x256.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6d50664e566fffee41a090c98f75e893b50846a753b802dbf5e2072a8dfd7784
3
+ size 55118513
rtmpose-m_hand_distill-256x256-a996d9ec.pth ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a996d9ec1807be4bfa7444a6a99cf833bfcdf3096035536fccc2093742833db1
3
+ size 55292443
rtmpose-m_hand_distill.py ADDED
@@ -0,0 +1,677 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ auto_scale_lr = dict(base_batch_size=256)
2
+ backend_args = dict(backend='local')
3
+ base_lr = 0.0004
4
+ codec = dict(
5
+ _scope_='mmpose',
6
+ input_size=(
7
+ 256,
8
+ 256,
9
+ ),
10
+ normalize=False,
11
+ sigma=(
12
+ 5.66,
13
+ 5.66,
14
+ ),
15
+ simcc_split_ratio=2.0,
16
+ type='SimCCLabel',
17
+ use_dark=False)
18
+ custom_hooks = [
19
+ dict(
20
+ _scope_='mmpose',
21
+ ema_type='ExpMomentumEMA',
22
+ momentum=0.0002,
23
+ priority=49,
24
+ type='EMAHook',
25
+ update_buffers=True),
26
+ dict(
27
+ _scope_='mmpose',
28
+ switch_epoch=200,
29
+ switch_pipeline=[
30
+ dict(backend_args=dict(backend='local'), type='LoadImage'),
31
+ dict(type='GetBBoxCenterScale'),
32
+ dict(
33
+ rotate_factor=180,
34
+ scale_factor=[
35
+ 0.75,
36
+ 1.25,
37
+ ],
38
+ shift_factor=0.0,
39
+ type='RandomBBoxTransform'),
40
+ dict(direction='horizontal', type='RandomFlip'),
41
+ dict(input_size=(
42
+ 256,
43
+ 256,
44
+ ), type='TopdownAffine'),
45
+ dict(type='mmdet.YOLOXHSVRandomAug'),
46
+ dict(
47
+ transforms=[
48
+ dict(p=0.2, type='Blur'),
49
+ dict(p=0.2, type='MedianBlur'),
50
+ dict(
51
+ max_height=0.4,
52
+ max_holes=1,
53
+ max_width=0.4,
54
+ min_height=0.2,
55
+ min_holes=1,
56
+ min_width=0.2,
57
+ p=0.5,
58
+ type='CoarseDropout'),
59
+ ],
60
+ type='Albumentation'),
61
+ dict(
62
+ encoder=dict(
63
+ input_size=(
64
+ 256,
65
+ 256,
66
+ ),
67
+ normalize=False,
68
+ sigma=(
69
+ 5.66,
70
+ 5.66,
71
+ ),
72
+ simcc_split_ratio=2.0,
73
+ type='SimCCLabel',
74
+ use_dark=False),
75
+ type='GenerateTarget'),
76
+ dict(type='PackPoseInputs'),
77
+ ],
78
+ type='mmdet.PipelineSwitchHook'),
79
+ ]
80
+ data_mode = 'topdown'
81
+ data_root = 'data/handset_mix/'
82
+ dataset_coco = dict(
83
+ _scope_='mmpose',
84
+ ann_file='coco/annotations/coco_wholebody_train_v1.0.json',
85
+ data_mode='topdown',
86
+ data_prefix=dict(img='detection/coco/train2017/'),
87
+ data_root='data/',
88
+ pipeline=[],
89
+ type='CocoWholeBodyHandDataset')
90
+ dataset_freihand = dict(
91
+ _scope_='mmpose',
92
+ ann_file='freihand/annotations/freihand_train.json',
93
+ data_mode='topdown',
94
+ data_prefix=dict(img='pose/FreiHand/'),
95
+ data_root='data/',
96
+ pipeline=[],
97
+ type='FreiHandDataset')
98
+ dataset_halpehand = dict(
99
+ _scope_='mmpose',
100
+ ann_file='halpe/annotations/halpe_train_v1.json',
101
+ data_mode='topdown',
102
+ data_prefix=dict(img='pose/Halpe/hico_20160224_det/images/train2015/'),
103
+ data_root='data/',
104
+ pipeline=[],
105
+ type='HalpeHandDataset')
106
+ dataset_onehand10k = dict(
107
+ _scope_='mmpose',
108
+ ann_file='onehand10k/annotations/onehand10k_train.json',
109
+ data_mode='topdown',
110
+ data_prefix=dict(img='pose/OneHand10K/'),
111
+ data_root='data/',
112
+ pipeline=[],
113
+ type='OneHand10KDataset')
114
+ dataset_rhd = dict(
115
+ _scope_='mmpose',
116
+ ann_file='rhd/annotations/rhd_train.json',
117
+ data_mode='topdown',
118
+ data_prefix=dict(img='pose/RHD/'),
119
+ data_root='data/',
120
+ pipeline=[
121
+ dict(
122
+ mapping=[
123
+ (
124
+ 0,
125
+ 0,
126
+ ),
127
+ (
128
+ 1,
129
+ 4,
130
+ ),
131
+ (
132
+ 2,
133
+ 3,
134
+ ),
135
+ (
136
+ 3,
137
+ 2,
138
+ ),
139
+ (
140
+ 4,
141
+ 1,
142
+ ),
143
+ (
144
+ 5,
145
+ 8,
146
+ ),
147
+ (
148
+ 6,
149
+ 7,
150
+ ),
151
+ (
152
+ 7,
153
+ 6,
154
+ ),
155
+ (
156
+ 8,
157
+ 5,
158
+ ),
159
+ (
160
+ 9,
161
+ 12,
162
+ ),
163
+ (
164
+ 10,
165
+ 11,
166
+ ),
167
+ (
168
+ 11,
169
+ 10,
170
+ ),
171
+ (
172
+ 12,
173
+ 9,
174
+ ),
175
+ (
176
+ 13,
177
+ 16,
178
+ ),
179
+ (
180
+ 14,
181
+ 15,
182
+ ),
183
+ (
184
+ 15,
185
+ 14,
186
+ ),
187
+ (
188
+ 16,
189
+ 13,
190
+ ),
191
+ (
192
+ 17,
193
+ 20,
194
+ ),
195
+ (
196
+ 18,
197
+ 19,
198
+ ),
199
+ (
200
+ 19,
201
+ 18,
202
+ ),
203
+ (
204
+ 20,
205
+ 17,
206
+ ),
207
+ ],
208
+ num_keypoints=21,
209
+ type='KeypointConverter'),
210
+ ],
211
+ type='Rhd2DDataset')
212
+ dataset_type = 'OneHand10KDataset'
213
+ default_hooks = dict(
214
+ badcase=dict(
215
+ _scope_='mmpose',
216
+ badcase_thr=5,
217
+ enable=False,
218
+ metric_type='loss',
219
+ out_dir='badcase',
220
+ type='BadCaseAnalysisHook'),
221
+ checkpoint=dict(
222
+ _scope_='mmpose',
223
+ interval=2,
224
+ max_keep_ckpts=2,
225
+ rule='greater',
226
+ save_best='PCK',
227
+ type='CheckpointHook'),
228
+ logger=dict(_scope_='mmpose', interval=50, type='LoggerHook'),
229
+ param_scheduler=dict(_scope_='mmpose', type='ParamSchedulerHook'),
230
+ sampler_seed=dict(_scope_='mmpose', type='DistSamplerSeedHook'),
231
+ timer=dict(_scope_='mmpose', type='IterTimerHook'),
232
+ visualization=dict(
233
+ _scope_='mmpose', enable=False, type='PoseVisualizationHook'))
234
+ default_scope = 'mmpose'
235
+ env_cfg = dict(
236
+ cudnn_benchmark=True,
237
+ dist_cfg=dict(backend='nccl'),
238
+ mp_cfg=dict(mp_start_method='fork', opencv_num_threads=0))
239
+ launcher = 'none'
240
+ load_from = 'https://download.openmmlab.com/mmpose/v1/projects/rtmpose/rtmpose-m_simcc-hand5_pt-aic-coco_210e-256x256-74fb594_20230320.pth'
241
+ log_level = 'INFO'
242
+ log_processor = dict(
243
+ _scope_='mmpose',
244
+ by_epoch=True,
245
+ num_digits=6,
246
+ type='LogProcessor',
247
+ window_size=50)
248
+ max_epochs = 100
249
+ model = dict(
250
+ _scope_='mmpose',
251
+ backbone=dict(
252
+ _scope_='mmdet',
253
+ act_cfg=dict(type='SiLU'),
254
+ arch='P5',
255
+ channel_attention=True,
256
+ deepen_factor=0.67,
257
+ expand_ratio=0.5,
258
+ init_cfg=dict(
259
+ checkpoint=
260
+ 'https://download.openmmlab.com/mmpose/v1/projects/rtmpose/cspnext-m_udp-aic-coco_210e-256x192-f2f7d6f6_20230130.pth',
261
+ prefix='backbone.',
262
+ type='Pretrained'),
263
+ norm_cfg=dict(type='SyncBN'),
264
+ out_indices=(4, ),
265
+ type='CSPNeXt',
266
+ widen_factor=0.75),
267
+ data_preprocessor=dict(
268
+ bgr_to_rgb=True,
269
+ mean=[
270
+ 123.675,
271
+ 116.28,
272
+ 103.53,
273
+ ],
274
+ std=[
275
+ 58.395,
276
+ 57.12,
277
+ 57.375,
278
+ ],
279
+ type='PoseDataPreprocessor'),
280
+ head=dict(
281
+ decoder=dict(
282
+ input_size=(
283
+ 256,
284
+ 256,
285
+ ),
286
+ normalize=False,
287
+ sigma=(
288
+ 5.66,
289
+ 5.66,
290
+ ),
291
+ simcc_split_ratio=2.0,
292
+ type='SimCCLabel',
293
+ use_dark=False),
294
+ final_layer_kernel_size=7,
295
+ gau_cfg=dict(
296
+ act_fn='SiLU',
297
+ drop_path=0.0,
298
+ dropout_rate=0.0,
299
+ expansion_factor=2,
300
+ hidden_dims=256,
301
+ pos_enc=False,
302
+ s=128,
303
+ use_rel_bias=False),
304
+ in_channels=768,
305
+ in_featuremap_size=(
306
+ 8,
307
+ 8,
308
+ ),
309
+ input_size=(
310
+ 256,
311
+ 256,
312
+ ),
313
+ loss=dict(
314
+ beta=10.0,
315
+ label_softmax=True,
316
+ type='KLDiscretLoss',
317
+ use_target_weight=True),
318
+ out_channels=21,
319
+ simcc_split_ratio=2.0,
320
+ type='RTMCCHead'),
321
+ test_cfg=dict(flip_test=True),
322
+ type='TopdownPoseEstimator')
323
+ optim_wrapper = dict(
324
+ _scope_='mmpose',
325
+ optimizer=dict(lr=0.0004, type='AdamW', weight_decay=0.05),
326
+ paramwise_cfg=dict(
327
+ bias_decay_mult=0, bypass_duplicate=True, norm_decay_mult=0),
328
+ type='OptimWrapper')
329
+ param_scheduler = [
330
+ dict(
331
+ begin=0, by_epoch=False, end=200, start_factor=0.001, type='LinearLR'),
332
+ dict(
333
+ begin=50,
334
+ by_epoch=True,
335
+ convert_to_iter_based=True,
336
+ end=100,
337
+ eta_min=2e-05,
338
+ type='CosineAnnealingLR'),
339
+ ]
340
+ randomness = dict(seed=21)
341
+ resume = False
342
+ stage2_num_epochs = 10
343
+ test_cfg = dict()
344
+ test_dataloader = dict(
345
+ batch_size=32,
346
+ dataset=dict(
347
+ ann_file='ann_val_clean.json',
348
+ data_prefix=dict(img=''),
349
+ data_root='data/handset_mix/',
350
+ pipeline=[
351
+ dict(backend_args=dict(backend='local'), type='LoadImage'),
352
+ dict(type='GetBBoxCenterScale'),
353
+ dict(input_size=(
354
+ 256,
355
+ 256,
356
+ ), type='TopdownAffine'),
357
+ dict(type='PackPoseInputs'),
358
+ ],
359
+ test_mode=True,
360
+ type='OneHand10KDataset'),
361
+ drop_last=False,
362
+ num_workers=10,
363
+ persistent_workers=True,
364
+ sampler=dict(
365
+ _scope_='mmpose', round_up=False, shuffle=False,
366
+ type='DefaultSampler'))
367
+ test_evaluator = [
368
+ dict(norm_item='bbox', thr=0.2, type='PCKAccuracy'),
369
+ dict(type='EPE'),
370
+ ]
371
+ train_cfg = dict(by_epoch=True, max_epochs=100, val_interval=2)
372
+ train_dataloader = dict(
373
+ batch_size=1024,
374
+ dataset=dict(
375
+ ann_file='ann_train.json',
376
+ data_prefix=dict(img=''),
377
+ data_root='data/handset_mix/',
378
+ pipeline=[
379
+ dict(backend_args=dict(backend='local'), type='LoadImage'),
380
+ dict(type='GetBBoxCenterScale'),
381
+ dict(direction='horizontal', type='RandomFlip'),
382
+ dict(
383
+ rotate_factor=180,
384
+ scale_factor=[
385
+ 0.5,
386
+ 1.5,
387
+ ],
388
+ type='RandomBBoxTransform'),
389
+ dict(input_size=(
390
+ 256,
391
+ 256,
392
+ ), type='TopdownAffine'),
393
+ dict(
394
+ encoder=dict(
395
+ input_size=(
396
+ 256,
397
+ 256,
398
+ ),
399
+ normalize=False,
400
+ sigma=(
401
+ 5.66,
402
+ 5.66,
403
+ ),
404
+ simcc_split_ratio=2.0,
405
+ type='SimCCLabel',
406
+ use_dark=False),
407
+ type='GenerateTarget'),
408
+ dict(type='PackPoseInputs'),
409
+ ],
410
+ type='OneHand10KDataset'),
411
+ num_workers=24,
412
+ persistent_workers=True,
413
+ sampler=dict(_scope_='mmpose', shuffle=True, type='DefaultSampler'))
414
+ train_pipeline = [
415
+ dict(backend_args=dict(backend='local'), type='LoadImage'),
416
+ dict(type='GetBBoxCenterScale'),
417
+ dict(direction='horizontal', type='RandomFlip'),
418
+ dict(
419
+ rotate_factor=180,
420
+ scale_factor=[
421
+ 0.5,
422
+ 1.5,
423
+ ],
424
+ type='RandomBBoxTransform'),
425
+ dict(input_size=(
426
+ 256,
427
+ 256,
428
+ ), type='TopdownAffine'),
429
+ dict(
430
+ encoder=dict(
431
+ input_size=(
432
+ 256,
433
+ 256,
434
+ ),
435
+ normalize=False,
436
+ sigma=(
437
+ 5.66,
438
+ 5.66,
439
+ ),
440
+ simcc_split_ratio=2.0,
441
+ type='SimCCLabel',
442
+ use_dark=False),
443
+ type='GenerateTarget'),
444
+ dict(type='PackPoseInputs'),
445
+ ]
446
+ train_pipeline_stage2 = [
447
+ dict(
448
+ _scope_='mmpose', backend_args=dict(backend='local'),
449
+ type='LoadImage'),
450
+ dict(_scope_='mmpose', type='GetBBoxCenterScale'),
451
+ dict(
452
+ _scope_='mmpose',
453
+ rotate_factor=180,
454
+ scale_factor=[
455
+ 0.75,
456
+ 1.25,
457
+ ],
458
+ shift_factor=0.0,
459
+ type='RandomBBoxTransform'),
460
+ dict(_scope_='mmpose', direction='horizontal', type='RandomFlip'),
461
+ dict(_scope_='mmpose', input_size=(
462
+ 256,
463
+ 256,
464
+ ), type='TopdownAffine'),
465
+ dict(_scope_='mmpose', type='mmdet.YOLOXHSVRandomAug'),
466
+ dict(
467
+ _scope_='mmpose',
468
+ transforms=[
469
+ dict(p=0.2, type='Blur'),
470
+ dict(p=0.2, type='MedianBlur'),
471
+ dict(
472
+ max_height=0.4,
473
+ max_holes=1,
474
+ max_width=0.4,
475
+ min_height=0.2,
476
+ min_holes=1,
477
+ min_width=0.2,
478
+ p=0.5,
479
+ type='CoarseDropout'),
480
+ ],
481
+ type='Albumentation'),
482
+ dict(
483
+ _scope_='mmpose',
484
+ encoder=dict(
485
+ input_size=(
486
+ 256,
487
+ 256,
488
+ ),
489
+ normalize=False,
490
+ sigma=(
491
+ 5.66,
492
+ 5.66,
493
+ ),
494
+ simcc_split_ratio=2.0,
495
+ type='SimCCLabel',
496
+ use_dark=False),
497
+ type='GenerateTarget'),
498
+ dict(_scope_='mmpose', type='PackPoseInputs'),
499
+ ]
500
+ val_cfg = dict()
501
+ val_coco = dict(
502
+ _scope_='mmpose',
503
+ ann_file='coco/annotations/coco_wholebody_val_v1.0.json',
504
+ data_mode='topdown',
505
+ data_prefix=dict(img='detection/coco/val2017/'),
506
+ data_root='data/',
507
+ pipeline=[],
508
+ type='CocoWholeBodyHandDataset')
509
+ val_dataloader = dict(
510
+ batch_size=32,
511
+ dataset=dict(
512
+ ann_file='ann_val.json',
513
+ data_prefix=dict(img=''),
514
+ data_root='data/handset_mix/',
515
+ pipeline=[
516
+ dict(backend_args=dict(backend='local'), type='LoadImage'),
517
+ dict(type='GetBBoxCenterScale'),
518
+ dict(input_size=(
519
+ 256,
520
+ 256,
521
+ ), type='TopdownAffine'),
522
+ dict(type='PackPoseInputs'),
523
+ ],
524
+ test_mode=True,
525
+ type='OneHand10KDataset'),
526
+ drop_last=False,
527
+ num_workers=10,
528
+ persistent_workers=True,
529
+ sampler=dict(
530
+ _scope_='mmpose', round_up=False, shuffle=False,
531
+ type='DefaultSampler'))
532
+ val_evaluator = [
533
+ dict(norm_item='bbox', thr=0.2, type='PCKAccuracy'),
534
+ dict(type='EPE'),
535
+ ]
536
+ val_freihand = dict(
537
+ _scope_='mmpose',
538
+ ann_file='freihand/annotations/freihand_test.json',
539
+ data_mode='topdown',
540
+ data_prefix=dict(img='pose/FreiHand/'),
541
+ data_root='data/',
542
+ pipeline=[],
543
+ type='FreiHandDataset')
544
+ val_halpehand = dict(
545
+ _scope_='mmpose',
546
+ ann_file='halpe/annotations/halpe_val_v1.json',
547
+ data_mode='topdown',
548
+ data_prefix=dict(img='detection/coco/val2017/'),
549
+ data_root='data/',
550
+ pipeline=[],
551
+ type='HalpeHandDataset')
552
+ val_onehand10k = dict(
553
+ _scope_='mmpose',
554
+ ann_file='onehand10k/annotations/onehand10k_test.json',
555
+ data_mode='topdown',
556
+ data_prefix=dict(img='pose/OneHand10K/'),
557
+ data_root='data/',
558
+ pipeline=[],
559
+ type='OneHand10KDataset')
560
+ val_pipeline = [
561
+ dict(backend_args=dict(backend='local'), type='LoadImage'),
562
+ dict(type='GetBBoxCenterScale'),
563
+ dict(input_size=(
564
+ 256,
565
+ 256,
566
+ ), type='TopdownAffine'),
567
+ dict(type='PackPoseInputs'),
568
+ ]
569
+ val_rhd = dict(
570
+ _scope_='mmpose',
571
+ ann_file='rhd/annotations/rhd_test.json',
572
+ data_mode='topdown',
573
+ data_prefix=dict(img='pose/RHD/'),
574
+ data_root='data/',
575
+ pipeline=[
576
+ dict(
577
+ mapping=[
578
+ (
579
+ 0,
580
+ 0,
581
+ ),
582
+ (
583
+ 1,
584
+ 4,
585
+ ),
586
+ (
587
+ 2,
588
+ 3,
589
+ ),
590
+ (
591
+ 3,
592
+ 2,
593
+ ),
594
+ (
595
+ 4,
596
+ 1,
597
+ ),
598
+ (
599
+ 5,
600
+ 8,
601
+ ),
602
+ (
603
+ 6,
604
+ 7,
605
+ ),
606
+ (
607
+ 7,
608
+ 6,
609
+ ),
610
+ (
611
+ 8,
612
+ 5,
613
+ ),
614
+ (
615
+ 9,
616
+ 12,
617
+ ),
618
+ (
619
+ 10,
620
+ 11,
621
+ ),
622
+ (
623
+ 11,
624
+ 10,
625
+ ),
626
+ (
627
+ 12,
628
+ 9,
629
+ ),
630
+ (
631
+ 13,
632
+ 16,
633
+ ),
634
+ (
635
+ 14,
636
+ 15,
637
+ ),
638
+ (
639
+ 15,
640
+ 14,
641
+ ),
642
+ (
643
+ 16,
644
+ 13,
645
+ ),
646
+ (
647
+ 17,
648
+ 20,
649
+ ),
650
+ (
651
+ 18,
652
+ 19,
653
+ ),
654
+ (
655
+ 19,
656
+ 18,
657
+ ),
658
+ (
659
+ 20,
660
+ 17,
661
+ ),
662
+ ],
663
+ num_keypoints=21,
664
+ type='KeypointConverter'),
665
+ ],
666
+ type='Rhd2DDataset')
667
+ vis_backends = [
668
+ dict(_scope_='mmpose', type='LocalVisBackend'),
669
+ ]
670
+ visualizer = dict(
671
+ _scope_='mmpose',
672
+ name='visualizer',
673
+ type='PoseLocalVisualizer',
674
+ vis_backends=[
675
+ dict(type='LocalVisBackend'),
676
+ ])
677
+ work_dir = './work_dirs/rtmpose-m_hand_distill'