BingoG commited on
Commit
2be35e3
·
verified ·
1 Parent(s): 30b1566

Delete files script/ with huggingface_hub

Browse files
script/batch_extract_smpl_cs.sh DELETED
@@ -1,72 +0,0 @@
1
- conda activate uni3c
2
- #!/usr/bin/env bash
3
- set -euo pipefail
4
- IFS=$'\n\t'
5
-
6
- # ========= 路径配置 =========
7
- SRC_DIR="/mnt/data_nas/guanfb/Pose/tiktok/rgb" # 待处理视频目录
8
- GVHMR_DIR="../Uni3C/third_party/GVHMR_realisdance"
9
- BATCH_OUT_BASE="../Uni3C/outputs/batch" # 每个视频的输出根
10
- FINAL_DIR="../SMPL_result" # 汇总目录
11
- # PYTHON_BIN="/home/pai/envs/uni3c/bin/python" # 或写绝对路径:/home/pai/envs/uni3c/bin/python
12
-
13
- mkdir -p "$BATCH_OUT_BASE" "$FINAL_DIR"
14
-
15
- # ========= 自检 =========
16
- if [[ ! -f "$GVHMR_DIR/extract_pose.py" ]]; then
17
- echo "❌ 找不到 $GVHMR_DIR/extract_pose.py(请检查 Uni3C 路径)"
18
- exit 2
19
- fi
20
- echo "▶ 批处理来源目录: $SRC_DIR"
21
- echo "▶ GVHMR_realisdance: $GVHMR_DIR"
22
- echo "▶ 中间输出根 : $BATCH_OUT_BASE"
23
- echo "▶ 最终结果目录 : $FINAL_DIR"
24
- echo
25
-
26
- SUCCESS=0
27
- FAIL=0
28
-
29
- # 用 NUL 分隔读取(find -print0 | sort -z),避免空格/换行/特殊字符问题
30
- while IFS= read -r -d '' V; do
31
- BASENAME="${V##*/}" # foo.mp4
32
- STEM="${BASENAME%.*}" # foo
33
- OUT_ROOT="$BATCH_OUT_BASE/$STEM"
34
-
35
- echo "==============================="
36
- echo "▶ 处理视频: $BASENAME"
37
- echo " 输出根 : $OUT_ROOT"
38
- echo "==============================="
39
-
40
- # 1) 在 GVHMR 目录执行(保持相对路径与资源一致)
41
- pushd "$GVHMR_DIR" >/dev/null
42
- if python extract_pose.py \
43
- --video "$V" \
44
- --output_root "$OUT_ROOT"; then
45
- popd >/dev/null
46
- else
47
- popd >/dev/null
48
- echo "❌ 提取失败:$BASENAME"
49
- FAIL=$((FAIL + 1))
50
- echo
51
- continue
52
- fi
53
-
54
- # 2) 找到并复制 cs_map.mp4 -> 最终目录,命名为 原名_smpl.mp4
55
- CS_MP4="$(find "$OUT_ROOT" -type f -name 'cs_map.mp4' -print -quit 2>/dev/null || true)"
56
- if [[ -n "${CS_MP4:-}" && -f "$CS_MP4" ]]; then
57
- TARGET="$FINAL_DIR/${STEM}_smpl.mp4"
58
- cp -f "$CS_MP4" "$TARGET"
59
- echo "✅ 提取成功:$TARGET"
60
- SUCCESS=$((SUCCESS + 1))
61
- else
62
- echo "❌ 未找到 cs_map.mp4(视频:$BASENAME)。请检查 $OUT_ROOT"
63
- FAIL=$((FAIL + 1))
64
- fi
65
-
66
- echo
67
- done < <(find "$SRC_DIR" -maxdepth 1 -type f -iname '*.mp4' -print0 | sort -z)
68
-
69
- echo "==============================="
70
- echo "🎯 完成:成功 $SUCCESS 个,失败 $FAIL 个"
71
- echo "📂 汇总目录:$FINAL_DIR"
72
- echo "==============================="
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
script/hamer_batch.sh DELETED
@@ -1,305 +0,0 @@
1
- #!/usr/bin/env bash
2
- conda activate hamer
3
- set -euo pipefail
4
- IFS=$'\n\t'
5
-
6
- ########################################
7
- # 通用:日志 & 目录校验
8
- ########################################
9
- log() { echo "[$(date +'%F %T')] $*"; }
10
- die() { echo "[$(date +'%F %T')] ❌ $*" >&2; exit 1; }
11
-
12
- # dir 存在且可写(会自动 mkdir -p)
13
- ensure_dir_writable() {
14
- local d="$1"
15
- mkdir -p "$d" || die "创建目录失败:$d"
16
- if ! test -w "$d"; then
17
- die "目录不可写:$d(可能是只读挂载或权限不足)"
18
- fi
19
- local tf="$d/.write_test_$$"
20
- if ! ( : > "$tf" ) 2>/dev/null; then
21
- die "无法在 $d 写入文件(磁盘满 / quota / 权限?)"
22
- fi
23
- rm -f "$tf" || true
24
- }
25
-
26
- ########################################
27
- # 路径基准:以脚本所在目录为根处理相对路径
28
- ########################################
29
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
30
-
31
- ########################################
32
- # 先确保依赖
33
- ########################################
34
- command -v ffmpeg >/dev/null 2>&1 || die "未找到 ffmpeg,请先安装:sudo apt-get install -y ffmpeg 或 conda install -c conda-forge ffmpeg"
35
- command -v ffprobe >/dev/null 2>&1 || die "未找到 ffprobe,请先安装:sudo apt-get install -y ffmpeg 或 conda install -c conda-forge ffmpeg"
36
-
37
- ########################################
38
- # 配置
39
- ########################################
40
- # 1) HaMeR 仓库与缓存
41
- export HAMER_DIR="${SCRIPT_DIR}/../hamer"
42
- ensure_dir_writable "$HAMER_DIR/_DATA/cache"
43
- export FVCORE_CACHE="$HAMER_DIR/_DATA/cache"
44
- export IOPATH_CACHE_DIR="$HAMER_DIR/_DATA/cache"
45
-
46
- # 2) 输入/输出根目录
47
- SRC_DIR="/mnt/data_nas/guanfb/Pose/tiktok/rgb"
48
- OUT_IMG_ROOT="${SCRIPT_DIR}/../hamer_img"
49
- OUT_RES_ROOT="${SCRIPT_DIR}/../hamer_result"
50
-
51
- ensure_dir_writable "$OUT_IMG_ROOT"
52
- ensure_dir_writable "$OUT_RES_ROOT"
53
-
54
- ########################################
55
- # 3) 环境(离屏渲染)—— 检测/安装 EGL,不行就回退 OSMesa
56
- ########################################
57
- unset DISPLAY
58
- export LD_PRELOAD=${LD_PRELOAD:-/usr/lib/x86_64-linux-gnu/libstdc++.so.6}
59
-
60
- _have_cmd() { command -v "$1" >/dev/null 2>&1; }
61
- _have_sudo() { _have_cmd sudo && sudo -n true 2>/dev/null; }
62
- _has_libegl() { ldconfig -p 2>/dev/null | grep -qi 'libEGL\.so'; }
63
- _apt_ensure() {
64
- local pkgs=("$@")
65
- _have_cmd apt-get || return 1
66
- _have_sudo || return 2
67
- sudo apt-get update -y
68
- sudo apt-get install -y "${pkgs[@]}"
69
- }
70
-
71
- choose_gl_backend() {
72
- if _has_libegl; then
73
- echo "egl"; return 0
74
- fi
75
- if _apt_ensure libegl1 libglvnd0 libgles2 libopengl0 libgl1-mesa-glx libgl1 libglx-mesa0 2>/dev/null; then
76
- hash -r || true
77
- _have_cmd ldconfig && { _have_sudo && sudo ldconfig || ldconfig || true; }
78
- _has_libegl && { echo "egl"; return 0; }
79
- fi
80
- if _apt_ensure libosmesa6 libosmesa6-dev mesa-utils libglu1-mesa 2>/dev/null; then
81
- echo "osmesa"; return 0
82
- fi
83
- echo "osmesa"
84
- }
85
-
86
- GL_BACKEND="$(choose_gl_backend)"
87
- if [[ "$GL_BACKEND" == "egl" ]]; then
88
- export PYOPENGL_PLATFORM=egl
89
- log "[GL] 使用 EGL 后端(PYOPENGL_PLATFORM=egl)"
90
- else
91
- export PYOPENGL_PLATFORM=osmesa
92
- export LIBGL_ALWAYS_SOFTWARE=1
93
- export MESA_GL_VERSION_OVERRIDE=3.3
94
- export MESA_GLSL_VERSION_OVERRIDE=330
95
- log "[GL] 未检测/安装到 EGL,回退到 OSMesa(PYOPENGL_PLATFORM=osmesa)"
96
- fi
97
-
98
- # 4) HaMeR demo 运行参数
99
- BATCH_SIZE=48
100
- SIDE_VIEW=1
101
- FULL_FRAME=0 # ⚠️ 为提高手部像素密度,这里改为 0;如需全画幅可改回 1
102
- SAVE_MESH=1
103
-
104
- ########################################
105
- # 单视频处理
106
- ########################################
107
- process_one() {
108
- local vid="$1"
109
- local base="$(basename "$vid")"
110
- local stem="${base%.*}"
111
- log "开始处理:$base"
112
-
113
- local ROOT="$OUT_IMG_ROOT/$stem"
114
- local FRAMES="$ROOT/frames"
115
- local HAMER_OUT="$ROOT/hamer_vis"
116
- local FILLED="$ROOT/hamer_vis_all_filled"
117
- local ALIGNED_COLOR="$ROOT/hamer_vis_color_aligned"
118
- local OUT_MP4="$OUT_RES_ROOT/hamer_color_aligned_${stem}.mp4"
119
-
120
- ensure_dir_writable "$ROOT"
121
- ensure_dir_writable "$FRAMES"
122
- ensure_dir_writable "$HAMER_OUT"
123
- ensure_dir_writable "$FILLED"
124
- ensure_dir_writable "$ALIGNED_COLOR"
125
- ensure_dir_writable "$OUT_RES_ROOT"
126
-
127
- pushd "$ROOT" >/dev/null
128
- trap 'popd >/dev/null || true' RETURN
129
-
130
- # 1) 抽帧 —— 无损 PNG
131
- local FPS_RAW
132
- FPS_RAW=$(ffprobe -v error -select_streams v:0 -show_entries stream=r_frame_rate \
133
- -of default=nokey=1:noprint_wrappers=1 "$vid")
134
- ffmpeg -y -i "$vid" -vsync 0 "$FRAMES/%06d.png"
135
- local TOTAL
136
- TOTAL=$(ls -1 "$FRAMES"/*.png | wc -l)
137
- log "[$stem] 源帧数: $TOTAL (FPS raw=$FPS_RAW)"
138
-
139
- # 2) 跑 HaMeR
140
- [[ -d "$HAMER_DIR" ]] || die "HaMeR 目录不存在:$HAMER_DIR"
141
- pushd "$HAMER_DIR" >/dev/null
142
- python demo.py \
143
- --img_folder "$FRAMES" \
144
- --out_folder "$HAMER_OUT" \
145
- --batch_size "$BATCH_SIZE" \
146
- $( ((SIDE_VIEW)) && echo --side_view ) \
147
- $( ((SAVE_MESH)) && echo --save_mesh ) \
148
- $( ((FULL_FRAME)) && echo --full_frame )
149
- popd >/dev/null
150
-
151
- # 3) 只用 *_all.{jpg|png} 并补齐缺帧(无则回退原始帧 PNG)
152
- local EXT=""
153
- if ls "$HAMER_OUT"/*_all.jpg 1>/dev/null 2>&1; then
154
- EXT="jpg"
155
- elif ls "$HAMER_OUT"/*_all.png 1>/dev/null 2>&1; then
156
- EXT="png"
157
- else
158
- EXT=""
159
- fi
160
-
161
- python - "$FRAMES" "$HAMER_OUT" "$FILLED" "$EXT" <<'PY'
162
- import os, sys, glob, shutil
163
- FRAMES, HAMER_OUT, FILLED, EXT = sys.argv[1:]
164
- imgs = sorted(glob.glob(os.path.join(FRAMES, '*.png')))
165
- N = len(imgs)
166
- if not EXT:
167
- EXT = 'png'
168
- os.makedirs(FILLED, exist_ok=True)
169
- for i in range(1, N+1):
170
- stem = f"{i:06d}"
171
- src_all = os.path.join(HAMER_OUT, f"{stem}_all.{EXT}")
172
- dst_all = os.path.join(FILLED, f"{stem}_all.{EXT}")
173
- if os.path.exists(src_all):
174
- shutil.copy2(src_all, dst_all)
175
- else:
176
- shutil.copy2(os.path.join(FRAMES, f"{stem}.png"), dst_all)
177
- print("[Info] 补齐到目录:", FILLED)
178
- PY
179
-
180
- # 4) 黑底 + 彩色(像素对齐,不改位置/尺度)
181
- if ls "$FILLED"/*_all.jpg 1>/dev/null 2>&1; then
182
- EXT="jpg"
183
- elif ls "$FILLED"/*_all.png 1>/dev/null 2>&1; then
184
- EXT="png"
185
- else
186
- log "❌ $FILLED 里没有 *_all.{jpg,png}"; return 2
187
- fi
188
-
189
- python - "$FRAMES" "$FILLED" "$ALIGNED_COLOR" "$EXT" <<'PY'
190
- import os, sys, glob
191
- import numpy as np
192
- from PIL import Image, ImageFilter
193
-
194
- FRAMES, FILLED, OUT, EXT = sys.argv[1:]
195
- os.makedirs(OUT, exist_ok=True)
196
-
197
- frames = sorted(glob.glob(os.path.join(FRAMES, '*.png')))
198
- N = len(frames)
199
- THR = 16 # ↑ 略提阈值,掩模更贴边;可调 14~22
200
-
201
- def hsv_to_rgb_arr(h):
202
- h6 = (h * 6.0).astype(np.float32)
203
- i = np.floor(h6).astype(np.int32) % 6
204
- f = h6 - np.floor(h6)
205
- v = np.ones_like(h, dtype=np.float32)
206
- s = np.ones_like(h, dtype=np.float32)
207
- p = v*(1-s); q = v*(1-f*s); t = v*(1-(1-f)*s)
208
-
209
- r = np.zeros_like(h, dtype=np.float32)
210
- g = np.zeros_like(h, dtype=np.float32)
211
- b = np.zeros_like(h, dtype=np.float32)
212
-
213
- m0 = (i==0); r[m0]=v[m0]; g[m0]=t[m0]; b[m0]=p[m0]
214
- m1 = (i==1); r[m1]=q[m1]; g[m1]=v[m1]; b[m1]=p[m1]
215
- m2 = (i==2); r[m2]=p[m2]; g[m2]=v[m2]; b[m2]=t[m2]
216
- m3 = (i==3); r[m3]=p[m3]; g[m3]=q[m3]; b[m3]=v[m3]
217
- m4 = (i==4); r[m4]=t[m4]; g[m4]=p[m4]; b[m4]=v[m4]
218
- m5 = (i==5); r[m5]=v[m5]; g[m5]=p[m5]; b[m5]=q[m5]
219
- return np.stack([r,g,b], axis=2)
220
-
221
- for idx in range(1, N+1):
222
- stem = f"{idx:06d}"
223
- src_frame = os.path.join(FRAMES, f"{stem}.png")
224
- src_all = os.path.join(FILLED, f"{stem}_all.{EXT}")
225
- dst_png = os.path.join(OUT, f"{stem}_all.png")
226
-
227
- im0 = Image.open(src_frame).convert("RGB")
228
- W, H = im0.size
229
-
230
- if not os.path.exists(src_all):
231
- Image.new("RGB", (W,H), (0,0,0)).save(dst_png)
232
- continue
233
-
234
- im = Image.open(src_all).convert("RGB")
235
- if im.size != (W,H):
236
- im = im.resize((W,H), Image.BILINEAR)
237
-
238
- a = np.asarray(im , np.int16)
239
- b = np.asarray(im0, np.int16)
240
- diff = np.mean(np.abs(a - b), axis=2).astype(np.uint8)
241
-
242
- # 掩模:差分二值化 + 轻微腐蚀(代替膨胀,细化边缘)
243
- mask = (diff > THR).astype(np.uint8)*255
244
- mask_img = Image.fromarray(mask).filter(ImageFilter.MinFilter(3)) # ← 原来是 MaxFilter(3)
245
- mask_np = np.asarray(mask_img, np.uint8)
246
- if mask_np.max() == 0:
247
- Image.new("RGB", (W,H), (0,0,0)).save(dst_png)
248
- continue
249
-
250
- # 纵向渐变(蓝→红)
251
- ys = np.linspace(0, 1, H, dtype=np.float32)[:, None]
252
- hs = np.repeat(ys, W, axis=1)
253
- h = 0.66 * (1.0 - hs)
254
- color = hsv_to_rgb_arr(h)
255
-
256
- # 明暗调制(用合成图亮度)
257
- gray = a.mean(axis=2).astype(np.float32) / 255.0
258
- shade = 0.6 + 0.4 * gray
259
- color = color * shade[..., None]
260
-
261
- color_u8 = (np.clip(color, 0, 1) * 255).astype(np.uint8)
262
- color_u8[mask_np==0] = 0
263
-
264
- Image.fromarray(color_u8, mode="RGB").save(dst_png)
265
-
266
- print("[Info] 彩色黑底输出:", OUT)
267
- PY
268
-
269
- # 5) 合成 mp4(保持原视频 FPS)
270
- local FPS
271
- FPS=$(python - <<PY
272
- fr="$FPS_RAW"
273
- try:
274
- n,d = fr.split("/")
275
- print(float(n)/float(d))
276
- except Exception:
277
- print(30.0)
278
- PY
279
- )
280
- ffmpeg -y -framerate "$FPS" \
281
- -start_number 1 \
282
- -i "$ALIGNED_COLOR/%06d_all.png" \
283
- -c:v libx264 -pix_fmt yuv420p "$OUT_MP4"
284
-
285
- log "完成:$OUT_MP4"
286
- }
287
-
288
- ########################################
289
- # 扫描并处理所有 mp4(串行)
290
- ########################################
291
- ensure_dir_writable "$OUT_IMG_ROOT"
292
- ensure_dir_writable "$OUT_RES_ROOT"
293
-
294
- log "扫描目录:$SRC_DIR"
295
- shopt -s nullglob
296
- mapfile -d '' VIDEOS < <(find "$SRC_DIR" -maxdepth 1 -type f -name '*.mp4' -print0)
297
-
298
- if (( ${#VIDEOS[@]} == 0 )); then
299
- log "未发现 mp4 文件,退出。"
300
- exit 0
301
- fi
302
-
303
- for v in "${VIDEOS[@]}"; do
304
- process_one "$v"
305
- done
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
script/mgpu_mworker_extract_smpl_cs.sh DELETED
@@ -1,438 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
- IFS=$'\n\t'
4
-
5
- ########################################
6
- # 环境与 Python 选择(静默、固定解释器)
7
- ########################################
8
- unset BASH_ENV
9
- PYTHON_BIN=""
10
-
11
- if command -v conda >/dev/null 2>&1; then
12
- if __conda_setup="$('conda' 'shell.bash' 'hook' 2>/dev/null)"; then
13
- eval "$__conda_setup"
14
- unset __conda_setup
15
- else
16
- for csh in "$HOME/miniconda3/etc/profile.d/conda.sh" \
17
- "$HOME/anaconda3/etc/profile.d/conda.sh" \
18
- "/opt/conda/etc/profile.d/conda.sh"; do
19
- [[ -f "$csh" ]] && . "$csh" && break
20
- done
21
- fi
22
- PYTHON_BIN="$(conda run -n hamer python -c 'import sys;print(sys.executable)' 2>/dev/null || true)"
23
- fi
24
- [[ -n "${PYTHON_BIN:-}" ]] || PYTHON_BIN="$(command -v python || command -v python3)"
25
-
26
- ########################################
27
- # 日志 & 工具(与单线程版风格一致)
28
- ########################################
29
- log(){ echo "[$(date +'%F %T')] $*"; }
30
- die(){ echo "[$(date +'%F %T')] ❌ $*" >&2; exit 1; }
31
-
32
- ensure_dir_writable() {
33
- local d="$1"
34
- mkdir -p "$d" || die "创建目录失败:$d"
35
- if ! test -w "$d"; then die "目录不可写:$d(可能是只读挂载或权限不足)"; fi
36
- local tf="$d/.write_test_$$"
37
- : > "$tf" 2>/dev/null || die "无法在 $d 写入文件(磁盘满 / quota / 权限?)"
38
- rm -f "$tf" || true
39
- }
40
-
41
- ########################################
42
- # 路径基准 & 依赖
43
- ########################################
44
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
45
- SCRIPT_PATH="$(readlink -f "${BASH_SOURCE[0]}")"
46
-
47
- command -v ffmpeg >/dev/null 2>&1 || die "未找到 ffmpeg,请先安装:sudo apt-get install -y ffmpeg 或 conda install -c conda-forge ffmpeg"
48
- command -v ffprobe >/dev/null 2>&1 || die "未找到 ffprobe,请先安装:sudo apt-get install -y ffmpeg 或 conda install -c conda-forge ffmpeg"
49
-
50
- ########################################
51
- # 配置(保持与单线程一致)
52
- ########################################
53
- export HAMER_DIR="${SCRIPT_DIR}/../hamer"
54
- ensure_dir_writable "$HAMER_DIR/_DATA/cache"
55
- export FVCORE_CACHE="$HAMER_DIR/_DATA/cache"
56
- export IOPATH_CACHE_DIR="$HAMER_DIR/_DATA/cache"
57
-
58
- SRC_DIR="/mnt/data_nas/guanfb/Pose/tiktok/rgb"
59
- OUT_IMG_ROOT="${SCRIPT_DIR}/../hamer_img"
60
- OUT_RES_ROOT="${SCRIPT_DIR}/../hamer_result"
61
- LOG_DIR="${SCRIPT_DIR}/../hamer_log"
62
-
63
- ensure_dir_writable "$OUT_IMG_ROOT"
64
- ensure_dir_writable "$OUT_RES_ROOT"
65
- ensure_dir_writable "$LOG_DIR"
66
-
67
- ########################################
68
- # 离屏渲染(与单线程一致)
69
- ########################################
70
- unset DISPLAY
71
- export LD_PRELOAD=${LD_PRELOAD:-/usr/lib/x86_64-linux-gnu/libstdc++.so.6}
72
-
73
- _have_cmd() { command -v "$1" >/dev/null 2>&1; }
74
- _have_sudo() { _have_cmd sudo && sudo -n true 2>/dev/null; }
75
- _has_libegl() { ldconfig -p 2>/dev/null | grep -qi 'libEGL\.so'; }
76
- _apt_ensure() {
77
- local pkgs=("$@")
78
- _have_cmd apt-get || return 1
79
- _have_sudo || return 2
80
- sudo apt-get update -y
81
- sudo apt-get install -y "${pkgs[@]}"
82
- }
83
- choose_gl_backend() {
84
- if _has_libegl; then echo "egl"; return 0; fi
85
- _apt_ensure libegl1 libglvnd0 libgles2 libopengl0 libgl1-mesa-glx libgl1 libglx-mesa0 2>/dev/null || true
86
- if _has_libegl; then echo "egl"; return 0; fi
87
- _apt_ensure libosmesa6 libosmesa6-dev mesa-utils libglu1-mesa 2>/dev/null || true
88
- echo "osmesa"
89
- }
90
- GL_BACKEND="$(choose_gl_backend)"
91
- if [[ "$GL_BACKEND" == "egl" ]]; then
92
- export PYOPENGL_PLATFORM=egl
93
- else
94
- export PYOPENGL_PLATFORM=osmesa
95
- export LIBGL_ALWAYS_SOFTWARE=1
96
- export MESA_GL_VERSION_OVERRIDE=3.3
97
- export MESA_GLSL_VERSION_OVERRIDE=330
98
- fi
99
-
100
- ########################################
101
- # HaMeR 运行参数(与单线程一致)
102
- ########################################
103
- BATCH_SIZE=48
104
- SIDE_VIEW=1
105
- FULL_FRAME=0 # 提高手部像素密度;需要全幅改为 1
106
- SAVE_MESH=1
107
-
108
- ########################################
109
- # 并行与 GPU 调度(GPU 分配信息将写入每视频日志)
110
- ########################################
111
- GPUS="${GPUS:-}" # 形如 "0,1,2,3";为空自动探测
112
- if [[ -z "$GPUS" ]] && command -v nvidia-smi >/dev/null 2>&1; then
113
- NGPU="$(nvidia-smi --query-gpu=index --format=csv,noheader | wc -l | awk '{print $1}')"
114
- (( NGPU > 0 )) && GPUS="$(seq -s, 0 $((NGPU-1)))" || true
115
- fi
116
-
117
- MAX_JOBS_PER_GPU="${MAX_JOBS_PER_GPU:-1}"
118
- SKIP_IF_DONE="${SKIP_IF_DONE:-1}"
119
-
120
- if [[ -n "$GPUS" ]]; then
121
- _GPU_COUNT="$(awk -F, '{print NF}' <<<"$GPUS")"
122
- DEFAULT_JOBS=$((_GPU_COUNT * MAX_JOBS_PER_GPU))
123
- (( DEFAULT_JOBS > 0 )) || DEFAULT_JOBS=1
124
- else
125
- DEFAULT_JOBS=4 # 无GPU时的CPU并发(若模型支持)
126
- fi
127
- JOBS="${JOBS:-$DEFAULT_JOBS}"
128
-
129
- GPU_LOCK_DIR="${SCRIPT_DIR}/.gpu_locks"
130
- ensure_dir_writable "$GPU_LOCK_DIR"
131
-
132
- init_gpu_tokens() {
133
- rm -f "${GPU_LOCK_DIR}/gpu"*".lock" 2>/dev/null || true
134
- if [[ -z "$GPUS" ]]; then
135
- return 0
136
- fi
137
- IFS=',' read -r -a GPU_ARR <<<"$GPUS"
138
- for gid in "${GPU_ARR[@]}"; do
139
- for ((s=1; s<=MAX_JOBS_PER_GPU; s++)); do : > "${GPU_LOCK_DIR}/gpu${gid}.${s}.lock"; done
140
- done
141
- }
142
- init_gpu_tokens
143
-
144
- # 子进程组统一清理(强力中止)
145
- declare -a CHILD_PGIDS=()
146
- ADD_PGID(){ CHILD_PGIDS+=("$1"); }
147
- run_pgroup(){
148
- if command -v setsid >/dev/null 2>&1; then
149
- setsid bash -c "$*" &
150
- local pid=$!
151
- ADD_PGID "$pid"
152
- wait "$pid"; return $?
153
- else
154
- bash -c "$*"; return $?
155
- fi
156
- }
157
- CLEANED=0
158
- cleanup_all(){
159
- (( CLEANED )) && return 0
160
- CLEANED=1
161
- for pg in "${CHILD_PGIDS[@]:-}"; do [[ -n "$pg" ]] && kill -TERM -- "-${pg}" 2>/dev/null || true; done
162
- sleep 0.5
163
- for pg in "${CHILD_PGIDS[@]:-}"; do [[ -n "$pg" ]] && kill -KILL -- "-${pg}" 2>/dev/null || true; done
164
- }
165
- on_master_signal(){ cleanup_all; kill -TERM -- "-$$" 2>/dev/null || true; }
166
- on_worker_signal(){ cleanup_all; exit 130; }
167
- trap on_master_signal INT TERM
168
-
169
- # GPU token(分配/释放信息写入**每视频日志**)
170
- GPU_LOCK_FD=-1
171
- GPU_LOCK_FILE=""
172
- GPU_ID=""
173
- acquire_gpu(){
174
- if [[ -z "$GPUS" ]]; then
175
- unset CUDA_VISIBLE_DEVICES
176
- GPU_ID="cpu"
177
- # 分配信息会在每视频日志中打印(见 process_one 内部)
178
- return 0
179
- fi
180
- IFS=',' read -r -a GPU_ARR <<<"$GPUS"
181
- while true; do
182
- for gid in "${GPU_ARR[@]}"; do
183
- for ((s=1; s<=MAX_JOBS_PER_GPU; s++)); do
184
- local f="${GPU_LOCK_DIR}/gpu${gid}.${s}.lock"
185
- exec {fd}> "$f" || continue
186
- if flock -n "$fd"; then
187
- GPU_LOCK_FD="$fd"; GPU_LOCK_FILE="$f"; GPU_ID="$gid"
188
- export CUDA_VISIBLE_DEVICES="$gid"
189
- return 0
190
- else
191
- exec {fd}>&-
192
- fi
193
- done
194
- done
195
- sleep 0.2
196
- done
197
- }
198
- release_gpu(){
199
- if [[ "$GPU_ID" == "cpu" || -z "$GPUS" ]]; then return 0; fi
200
- if [[ "$GPU_LOCK_FD" -ge 0 ]]; then
201
- flock -u "$GPU_LOCK_FD" || true
202
- eval "exec ${GPU_LOCK_FD}>&-" || true
203
- GPU_LOCK_FD=-1; GPU_LOCK_FILE=""; GPU_ID=""
204
- fi
205
- }
206
-
207
- ########################################
208
- # —— 核心:单视频处理(日志写 hamer_log,内容与单线程一致 + GPU分配信息)——
209
- ########################################
210
- process_one() {
211
- trap on_worker_signal INT TERM
212
-
213
- local vid="$1"
214
- local base="$(basename "$vid")"
215
- local stem="${base%.*}"
216
-
217
- local ROOT="$OUT_IMG_ROOT/$stem"
218
- local FRAMES="$ROOT/frames"
219
- local HAMER_OUT="$ROOT/hamer_vis"
220
- local FILLED="$ROOT/hamer_vis_all_filled"
221
- local ALIGNED_COLOR="$ROOT/hamer_vis_color_aligned"
222
- local OUT_MP4="$OUT_RES_ROOT/hamer_color_aligned_${stem}.mp4"
223
- local LOGF="$LOG_DIR/${stem}.log"
224
-
225
- # 跳过:已有最终结果且非空 —— 与单线程保持一致(跳过时不产生日志)
226
- if (( SKIP_IF_DONE )) && [[ -s "$OUT_MP4" ]]; then
227
- return 0
228
- fi
229
-
230
- # 从此处起,每视频日志
231
- exec >>"$LOGF" 2>&1
232
-
233
- log "开始处理:$base"
234
-
235
- ensure_dir_writable "$ROOT"
236
- ensure_dir_writable "$FRAMES"
237
- ensure_dir_writable "$HAMER_OUT"
238
- ensure_dir_writable "$FILLED"
239
- ensure_dir_writable "$ALIGNED_COLOR"
240
- ensure_dir_writable "$OUT_RES_ROOT"
241
-
242
- pushd "$ROOT" >/dev/null
243
- trap 'popd >/dev/null || true' RETURN
244
-
245
- # 1) 抽帧 —— 无损 PNG
246
- local FPS_RAW
247
- FPS_RAW=$(ffprobe -v error -select_streams v:0 -show_entries stream=r_frame_rate \
248
- -of default=nokey=1:noprint_wrappers=1 "$vid")
249
- run_pgroup "ffmpeg -y -i \"$vid\" -vsync 0 \"$FRAMES/%06d.png\""
250
-
251
- local TOTAL
252
- TOTAL=$(ls -1 "$FRAMES"/*.png 2>/dev/null | wc -l)
253
- log "[$stem] 源帧数: $TOTAL (FPS raw=$FPS_RAW)"
254
-
255
- # 2) HaMeR 推理(打印 GPU 分配/释放信息到**该视频日志**)
256
- [[ -d "$HAMER_DIR" ]] || { die "HaMeR 目录不存在:$HAMER_DIR"; }
257
- acquire_gpu
258
- if [[ "$GPU_ID" == "cpu" ]]; then
259
- log "[GPU] 分配:CPU(无可用 GPU)"
260
- else
261
- log "[GPU] 分配:GPU ${GPU_ID}(token=${GPU_LOCK_FILE})"
262
- fi
263
-
264
- pushd "$HAMER_DIR" >/dev/null
265
- run_pgroup "\"$PYTHON_BIN\" demo.py \
266
- --img_folder \"$FRAMES\" \
267
- --out_folder \"$HAMER_OUT\" \
268
- --batch_size \"$BATCH_SIZE\" \
269
- $([[ $SIDE_VIEW -eq 1 ]] && echo --side_view) \
270
- $([[ $SAVE_MESH -eq 1 ]] && echo --save_mesh) \
271
- $([[ $FULL_FRAME -eq 1 ]] && echo --full_frame)"
272
- local rc=$?
273
- popd >/dev/null
274
-
275
- if [[ "$GPU_ID" != "cpu" ]]; then
276
- log "[GPU] 释放:GPU ${GPU_ID}(token=${GPU_LOCK_FILE})"
277
- fi
278
- release_gpu
279
- (( rc == 0 )) || { return 1; }
280
-
281
- # 3) 补齐缺帧
282
- local EXT=""
283
- if ls "$HAMER_OUT"/*_all.jpg >/dev/null 2>&1; then EXT="jpg"
284
- elif ls "$HAMER_OUT"/*_all.png >/dev/null 2>&1; then EXT="png"
285
- else EXT="png"; fi
286
-
287
- "$PYTHON_BIN" - "$FRAMES" "$HAMER_OUT" "$FILLED" "$EXT" <<'PY'
288
- import os, sys, glob, shutil
289
- FRAMES, HAMER_OUT, FILLED, EXT = sys.argv[1:]
290
- imgs = sorted(glob.glob(os.path.join(FRAMES, '*.png')))
291
- N = len(imgs)
292
- if not EXT:
293
- EXT = 'png'
294
- os.makedirs(FILLED, exist_ok=True)
295
- for i in range(1, N+1):
296
- stem = f"{i:06d}"
297
- src_all = os.path.join(HAMER_OUT, f"{stem}_all.{EXT}")
298
- dst_all = os.path.join(FILLED, f"{stem}_all.{EXT}")
299
- if os.path.exists(src_all):
300
- shutil.copy2(src_all, dst_all)
301
- else:
302
- shutil.copy2(os.path.join(FRAMES, f"{stem}.png"), dst_all)
303
- print("[Info] 补齐到目录:", FILLED)
304
- PY
305
-
306
- # 4) 黑底 + 彩色(保持单线程同实现)
307
- if ls "$FILLED"/*_all.jpg >/dev/null 2>&1; then EXT="jpg"
308
- elif ls "$FILLED"/*_all.png >/dev/null 2>&1; then EXT="png"
309
- else return 2; fi
310
-
311
- "$PYTHON_BIN" - "$FRAMES" "$FILLED" "$ALIGNED_COLOR" "$EXT" <<'PY'
312
- import os, sys, glob
313
- import numpy as np
314
- from PIL import Image, ImageFilter
315
-
316
- FRAMES, FILLED, OUT, EXT = sys.argv[1:]
317
- os.makedirs(OUT, exist_ok=True)
318
-
319
- frames = sorted(glob.glob(os.path.join(FRAMES, '*.png')))
320
- N = len(frames)
321
- THR = 16
322
-
323
- def hsv_to_rgb_arr(h):
324
- h6 = (h * 6.0).astype(np.float32)
325
- i = np.floor(h6).astype(np.int32) % 6
326
- f = h6 - np.floor(h6)
327
- v = np.ones_like(h, dtype=np.float32)
328
- s = np.ones_like(h, dtype=np.float32)
329
- p = v*(1-s); q = v*(1-f*s); t = v*(1-(1-f)*s)
330
- r = np.zeros_like(h, dtype=np.float32)
331
- g = np.zeros_like(h, dtype=np.float32)
332
- b = np.zeros_like(h, dtype=np.float32)
333
- m0 = (i==0); r[m0]=v[m0]; g[m0]=t[m0]; b[m0]=p[m0]
334
- m1 = (i==1); r[m1]=q[m1]; g[m1]=v[m1]; b[m1]=p[m1]
335
- m2 = (i==2); r[m2]=p[m2]; g[m2]=v[m2]; b[m2]=t[m2]
336
- m3 = (i==3); r[m3]=p[m3]; g[m3]=q[m3]; b[m3]=v[m3]
337
- m4 = (i==4); r[m4]=t[m4]; g[m4]=p[m4]; b[m4]=v[m4]
338
- m5 = (i==5); r[m5]=v[m5]; g[m5]=p[m5]; b[m5]=q[m5]
339
- return np.stack([r,g,b], axis=2)
340
-
341
- for idx in range(1, N+1):
342
- stem = f"{idx:06d}"
343
- src_frame = os.path.join(FRAMES, f"{stem}.png")
344
- src_all = os.path.join(FILLED, f"{stem}_all.{EXT}")
345
- dst_png = os.path.join(OUT, f"{stem}_all.png")
346
-
347
- im0 = Image.open(src_frame).convert("RGB")
348
- W, H = im0.size
349
-
350
- if not os.path.exists(src_all):
351
- Image.new("RGB", (W,H), (0,0,0)).save(dst_png)
352
- continue
353
-
354
- im = Image.open(src_all).convert("RGB")
355
- if im.size != (W,H):
356
- im = im.resize((W,H), Image.BILINEAR)
357
-
358
- a = np.asarray(im , np.int16)
359
- b = np.asarray(im0, np.int16)
360
- diff = np.mean(np.abs(a - b), axis=2).astype(np.uint8)
361
-
362
- mask = (diff > THR).astype(np.uint8)*255
363
- mask_img = Image.fromarray(mask).filter(ImageFilter.MinFilter(3))
364
- mask_np = np.asarray(mask_img, np.uint8)
365
- if mask_np.max() == 0:
366
- Image.new("RGB", (W,H), (0,0,0)).save(dst_png)
367
- continue
368
-
369
- ys = np.linspace(0, 1, H, dtype=np.float32)[:, None]
370
- hs = np.repeat(ys, W, axis=1)
371
- h = 0.66 * (1.0 - hs)
372
- color = hsv_to_rgb_arr(h)
373
-
374
- gray = a.mean(axis=2).astype(np.float32) / 255.0
375
- shade = 0.6 + 0.4 * gray
376
- color = color * shade[..., None]
377
-
378
- color_u8 = (np.clip(color, 0, 1) * 255).astype(np.uint8)
379
- color_u8[mask_np==0] = 0
380
-
381
- Image.fromarray(color_u8, mode="RGB").save(dst_png)
382
-
383
- print("[Info] 彩色黑底输出:", OUT)
384
- PY
385
-
386
- # 5) 合成 mp4(保持原视频 FPS)
387
- local FPS
388
- FPS=$("$PYTHON_BIN" - <<PY
389
- fr = "${FPS_RAW}"
390
- try:
391
- if "/" in fr:
392
- n, d = fr.split("/")
393
- print(float(n)/float(d))
394
- else:
395
- print(float(fr))
396
- except Exception:
397
- print(30.0)
398
- PY
399
- )
400
- run_pgroup "ffmpeg -y -framerate \"$FPS\" -start_number 1 -i \"$ALIGNED_COLOR/%06d_all.png\" -c:v libx264 -pix_fmt yuv420p \"$OUT_MP4\""
401
-
402
- log "完成:$OUT_MP4"
403
- }
404
-
405
- ########################################
406
- # 单文件模式(供并行 worker 调用)
407
- ########################################
408
- if [[ "${1:-}" == "--one" ]]; then
409
- shift
410
- trap on_worker_signal INT TERM
411
- trap 'cleanup_all' EXIT
412
- process_one "$@"
413
- exit $?
414
- fi
415
-
416
- ########################################
417
- # 主控:扫描输入并并行处理
418
- ########################################
419
- log "扫描目录:$SRC_DIR"
420
- shopt -s nullglob
421
- mapfile -d '' VIDEOS < <(find "$SRC_DIR" -maxdepth 1 -type f -iname '*.mp4' -print0 | sort -z)
422
-
423
- if (( ${#VIDEOS[@]} == 0 )); then
424
- log "未发现 mp4 文件,退出。"
425
- exit 0
426
- fi
427
-
428
- # 并行执行(优先 parallel,无则 xargs)
429
- if command -v parallel >/dev/null 2>&1; then
430
- printf '%s\0' "${VIDEOS[@]}" \
431
- | parallel -0 -P "$JOBS" --no-run-if-empty --halt now,fail=1 \
432
- bash "$SCRIPT_PATH" --one {}
433
- else
434
- printf '%s\0' "${VIDEOS[@]}" \
435
- | xargs -0 -n1 -P "$JOBS" -I{} bash "$SCRIPT_PATH" --one "{}"
436
- fi
437
-
438
- cleanup_all
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
script/mgpu_mworker_hamer_batch.sh DELETED
@@ -1,444 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
- IFS=$'\n\t'
4
- unset BASH_ENV
5
-
6
- ########################################
7
- # 选 Python(优先 conda:hamer)
8
- ########################################
9
- PYTHON_BIN=""
10
- if command -v conda >/dev/null 2>&1; then
11
- if __conda_setup="$('conda' 'shell.bash' 'hook' 2>/dev/null)"; then
12
- eval "$__conda_setup"
13
- unset __conda_setup
14
- else
15
- for csh in "$HOME/miniconda3/etc/profile.d/conda.sh" \
16
- "$HOME/anaconda3/etc/profile.d/conda.sh" \
17
- "/opt/conda/etc/profile.d/conda.sh"; do
18
- [[ -f "$csh" ]] && . "$csh" && break
19
- done
20
- fi
21
- PYTHON_BIN="$(conda run -n hamer python -c 'import sys;print(sys.executable)' 2>/dev/null || true)"
22
- fi
23
- [[ -n "${PYTHON_BIN:-}" ]] || PYTHON_BIN="$(command -v python || command -v python3)"
24
-
25
- ########################################
26
- # 日志 & 工具
27
- ########################################
28
- log(){ echo "[$(date +'%F %T')] $*"; }
29
- die(){ echo "[$(date +'%F %T')] ❌ $*" >&2; exit 1; }
30
-
31
- ensure_dir_writable() {
32
- local d="$1"
33
- mkdir -p "$d" || die "创建目录失败:$d"
34
- test -w "$d" || die "目录不可写:$d"
35
- local tf="$d/.write_test_$$"; : > "$tf" 2>/dev/null || die "无法在 $d 写入"
36
- rm -f "$tf" || true
37
- }
38
-
39
- ########################################
40
- # 路径 & 依赖
41
- ########################################
42
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
43
- SCRIPT_PATH="$(readlink -f "${BASH_SOURCE[0]}")"
44
-
45
- command -v ffmpeg >/dev/null 2>&1 || die "未找到 ffmpeg,请先安装:sudo apt-get install -y ffmpeg 或 conda install -c conda-forge ffmpeg"
46
- command -v ffprobe >/dev/null 2>&1 || die "未找到 ffprobe,请先安装:sudo apt-get install -y ffmpeg 或 conda install -c conda-forge ffmpeg"
47
-
48
- export HAMER_DIR="${SCRIPT_DIR}/../hamer"
49
- ensure_dir_writable "$HAMER_DIR/_DATA/cache"
50
- export FVCORE_CACHE="$HAMER_DIR/_DATA/cache"
51
- export IOPATH_CACHE_DIR="$HAMER_DIR/_DATA/cache"
52
-
53
- SRC_DIR="/mnt/data_nas/guanfb/Pose/tiktok/rgb"
54
- OUT_IMG_ROOT="${SCRIPT_DIR}/../hamer_img"
55
- OUT_RES_ROOT="${SCRIPT_DIR}/../hamer_result"
56
- LOG_DIR="${SCRIPT_DIR}/hamer_log"
57
- STATUS_DIR="${SCRIPT_DIR}/.status_hamer"
58
-
59
- ensure_dir_writable "$OUT_IMG_ROOT"
60
- ensure_dir_writable "$OUT_RES_ROOT"
61
- ensure_dir_writable "$LOG_DIR"
62
- ensure_dir_writable "$STATUS_DIR"
63
-
64
- ########################################
65
- # 离屏渲染(与单线程一致)
66
- ########################################
67
- unset DISPLAY
68
- export LD_PRELOAD=${LD_PRELOAD:-/usr/lib/x86_64-linux-gnu/libstdc++.so.6}
69
- _has_libegl(){ ldconfig -p 2>/dev/null | grep -qi 'libEGL\.so'; }
70
- if _has_libegl; then
71
- export PYOPENGL_PLATFORM=egl
72
- else
73
- export PYOPENGL_PLATFORM=osmesa
74
- export LIBGL_ALWAYS_SOFTWARE=1
75
- export MESA_GL_VERSION_OVERRIDE=3.3
76
- export MESA_GLSL_VERSION_OVERRIDE=330
77
- fi
78
-
79
- ########################################
80
- # HaMeR 运行参数(保持一致)
81
- ########################################
82
- BATCH_SIZE=48
83
- SIDE_VIEW=1
84
- FULL_FRAME=1 # 手部像素更高;要全幅改 1
85
- SAVE_MESH=1
86
-
87
- ########################################
88
- # 并发/GPU 自动计算(按显存)
89
- ########################################
90
- GPUS="${GPUS:-}" # 为空自动探测
91
- JOB_MEM_GB="${JOB_MEM_GB:-13}" # 单任务显存估计
92
- RESERVE_GB="${RESERVE_GB:-4}" # 固定预留
93
- RESERVE_RATIO="${RESERVE_RATIO:-0.15}" # 比例预留
94
- SKIP_IF_DONE="${SKIP_IF_DONE:-1}"
95
-
96
- if [[ -z "$GPUS" ]] && command -v nvidia-smi >/dev/null 2>&1; then
97
- NGPU="$(nvidia-smi --query-gpu=index --format=csv,noheader | wc -l | awk '{print $1}')"
98
- (( NGPU > 0 )) && GPUS="$(seq -s, 0 $((NGPU-1)))" || true
99
- fi
100
-
101
- gpu_total_mib(){ nvidia-smi --query-gpu=memory.total --format=csv,noheader,nounits -i "$1" 2>/dev/null | head -n1; }
102
- gpu_free_mib(){ nvidia-smi --query-gpu=memory.free --format=csv,noheader,nounits -i "$1" 2>/dev/null | head -n1; }
103
-
104
- calc_slots(){
105
- local gid="$1"
106
- local total=$(gpu_total_mib "$gid"); local free=$(gpu_free_mib "$gid")
107
- [[ -z "$total" || -z "$free" ]] && { echo 0; return; }
108
- local job=$(( JOB_MEM_GB * 1024 ))
109
- local reserve_by_ratio=$(awk -v t="$total" -v r="$RESERVE_RATIO" 'BEGIN{printf("%d", t*r)}')
110
- local reserve_by_abs=$(( RESERVE_GB * 1024 ))
111
- local reserve=$(( reserve_by_ratio > reserve_by_abs ? reserve_by_ratio : reserve_by_abs ))
112
- local avail_free=$(( free - reserve )); (( avail_free < 0 )) && avail_free=0
113
- local avail_total=$(( total - reserve )); (( avail_total < 0 )) && avail_total=0
114
- local a=$(( avail_free / job )); local b=$(( avail_total / job ))
115
- local slots=$(( a < b ? a : b )); (( slots < 0 )) && slots=0
116
- echo "$slots"
117
- }
118
-
119
- GPU_LOCK_DIR="${SCRIPT_DIR}/.gpu_locks"
120
- ensure_dir_writable "$GPU_LOCK_DIR"
121
- declare -A GPU_SLOTS=()
122
-
123
- init_gpu_tokens(){
124
- rm -f "${GPU_LOCK_DIR}/gpu"*".lock" 2>/dev/null || true
125
- local total_slots=0 mapping=""
126
- if [[ -n "$GPUS" ]]; then
127
- IFS=',' read -r -a GPU_ARR <<<"$GPUS"
128
- for gid in "${GPU_ARR[@]}"; do
129
- local s; s="$(calc_slots "$gid")"
130
- GPU_SLOTS["$gid"]="$s"
131
- if (( s > 0 )); then
132
- for ((i=1;i<=s;i++)); do : > "${GPU_LOCK_DIR}/gpu${gid}.${i}.lock"; done
133
- total_slots=$(( total_slots + s ))
134
- mapping+=" GPU${gid}:${s}"
135
- else
136
- mapping+=" GPU${gid}:0"
137
- fi
138
- done
139
- fi
140
- if [[ -z "${JOBS:-}" ]]; then
141
- JOBS=$(( total_slots > 0 ? total_slots : 1 ))
142
- fi
143
- log "[GPU] 自动并发(单任务≈${JOB_MEM_GB}GB,预留=max(${RESERVE_GB}GB, ${RESERVE_RATIO}×总显存)):${mapping} → 总并发=${JOBS}"
144
- }
145
- init_gpu_tokens
146
-
147
- ########################################
148
- # 进程组清理(强力中止)
149
- ########################################
150
- declare -a CHILD_PGIDS=()
151
- ADD_PGID(){ CHILD_PGIDS+=("$1"); }
152
- run_pgroup(){
153
- if command -v setsid >/dev/null 2>&1; then
154
- setsid bash -c "$*" &
155
- local pid=$!; ADD_PGID "$pid"; wait "$pid"; return $?
156
- else
157
- bash -c "$*"; return $?
158
- fi
159
- }
160
- CLEANED=0
161
- cleanup_all(){
162
- (( CLEANED )) && return 0
163
- CLEANED=1
164
- for pg in "${CHILD_PGIDS[@]:-}"; do [[ -n "$pg" ]] && kill -TERM -- "-${pg}" 2>/dev/null || true; done
165
- sleep 0.5
166
- for pg in "${CHILD_PGIDS[@]:-}"; do [[ -n "$pg" ]] && kill -KILL -- "-${pg}" 2>/dev/null || true; done
167
- }
168
- on_master_signal(){ cleanup_all; kill -TERM -- "-$$" 2>/dev/null || true; }
169
- on_worker_signal(){ cleanup_all; exit 130; }
170
- trap on_master_signal INT TERM
171
-
172
- ########################################
173
- # GPU token
174
- ########################################
175
- GPU_LOCK_FD=-1
176
- GPU_LOCK_FILE=""
177
- GPU_ID=""
178
- acquire_gpu(){
179
- if [[ -z "$GPUS" ]]; then GPU_ID="cpu"; unset CUDA_VISIBLE_DEVICES; return 0; fi
180
- IFS=',' read -r -a GPU_ARR <<<"$GPUS"
181
- while :; do
182
- for gid in "${GPU_ARR[@]}"; do
183
- local slots="${GPU_SLOTS[$gid]:-0}"; (( slots<=0 )) && continue
184
- for ((s=1;s<=slots;s++)); do
185
- local f="${GPU_LOCK_DIR}/gpu${gid}.${s}.lock"
186
- exec {fd}> "$f" || continue
187
- if flock -n "$fd"; then
188
- GPU_LOCK_FD="$fd"; GPU_LOCK_FILE="$f"; GPU_ID="$gid"; export CUDA_VISIBLE_DEVICES="$gid"; return 0
189
- else
190
- exec {fd}>&-
191
- fi
192
- done
193
- done
194
- sleep 0.2
195
- done
196
- }
197
- release_gpu(){
198
- if [[ "$GPU_ID" == "cpu" || -z "$GPUS" ]]; then return 0; fi
199
- if [[ "$GPU_LOCK_FD" -ge 0 ]]; then
200
- flock -u "$GPU_LOCK_FD" || true; eval "exec ${GPU_LOCK_FD}>&-" || true
201
- GPU_LOCK_FD=-1; GPU_LOCK_FILE=""; GPU_ID=""
202
- fi
203
- }
204
-
205
- ########################################
206
- # —— 核心:单视频处理(日志实时,产物一致)——
207
- ########################################
208
- process_one() {
209
- trap on_worker_signal INT TERM
210
-
211
- local vid="$1"
212
- local base="$(basename "$vid")"
213
- local stem="${base%.*}"
214
-
215
- local ROOT="$OUT_IMG_ROOT/$stem"
216
- local FRAMES="$ROOT/frames"
217
- local HAMER_OUT="$ROOT/hamer_vis"
218
- local FILLED="$ROOT/hamer_vis_all_filled"
219
- local ALIGNED_COLOR="$ROOT/hamer_vis_color_aligned"
220
- local OUT_MP4="$OUT_RES_ROOT/hamer_color_aligned_${stem}.mp4"
221
- local LOGF="$LOG_DIR/${stem}.log"
222
-
223
- # 跳过已完成(不写视频日志,仅做状态标记)
224
- if (( SKIP_IF_DONE )) && [[ -s "$OUT_MP4" ]]; then
225
- : > "${STATUS_DIR}/${stem}.skip"
226
- return 0
227
- fi
228
-
229
- # ↓↓↓ 从这里开始写入该视频日志(行缓冲实时刷入)
230
- {
231
- log "开始处理:$base"
232
-
233
- ensure_dir_writable "$ROOT"
234
- ensure_dir_writable "$FRAMES"
235
- ensure_dir_writable "$HAMER_OUT"
236
- ensure_dir_writable "$FILLED"
237
- ensure_dir_writable "$ALIGNED_COLOR"
238
- ensure_dir_writable "$OUT_RES_ROOT"
239
-
240
- pushd "$ROOT" >/dev/null
241
- trap 'popd >/dev/null || true' RETURN
242
-
243
- # 1) 抽帧 —— 无损 PNG(行缓冲)
244
- local FPS_RAW
245
- FPS_RAW=$(ffprobe -v error -select_streams v:0 -show_entries stream=r_frame_rate \
246
- -of default=nokey=1:noprint_wrappers=1 "$vid")
247
- stdbuf -oL -eL ffmpeg -y -i "$vid" -vsync 0 "$FRAMES/%06d.png"
248
- local TOTAL
249
- TOTAL=$(ls -1 "$FRAMES"/*.png 2>/dev/null | wc -l)
250
- log "[$stem] 源帧数: $TOTAL (FPS raw=$FPS_RAW)"
251
-
252
- # 2) HaMeR 推理(无缓冲 Python;记录 GPU 分配/释放)
253
- [[ -d "$HAMER_DIR" ]] || { : > "${STATUS_DIR}/${stem}.fail"; echo "HaMeR 目录不存在:$HAMER_DIR"; exit 1; }
254
- acquire_gpu
255
- if [[ "$GPU_ID" == "cpu" ]]; then
256
- log "[GPU] 分配:CPU(无可用 GPU)"
257
- echo "gpu=cpu" > "${STATUS_DIR}/${stem}.running"
258
- else
259
- log "[GPU] 分配:GPU ${GPU_ID}(token=${GPU_LOCK_FILE})"
260
- echo "gpu=${GPU_ID}" > "${STATUS_DIR}/${stem}.running"
261
- fi
262
-
263
- pushd "$HAMER_DIR" >/dev/null
264
- stdbuf -oL -eL "$PYTHON_BIN" -u demo.py \
265
- --img_folder "$FRAMES" \
266
- --out_folder "$HAMER_OUT" \
267
- --batch_size "$BATCH_SIZE" \
268
- $([[ $SIDE_VIEW -eq 1 ]] && echo --side_view) \
269
- $([[ $SAVE_MESH -eq 1 ]] && echo --save_mesh) \
270
- $([[ $FULL_FRAME -eq 1 ]] && echo --full_frame)
271
- local rc=$?
272
- popd >/dev/null
273
-
274
- if [[ "$GPU_ID" != "cpu" ]]; then
275
- log "[GPU] 释放:GPU ${GPU_ID}(token=${GPU_LOCK_FILE})"
276
- fi
277
- release_gpu
278
- (( rc == 0 )) || { : > "${STATUS_DIR}/${stem}.fail"; rm -f "${STATUS_DIR}/${stem}.running"; exit 1; }
279
-
280
- # 3) 补齐缺帧(无缓冲)
281
- local EXT=""
282
- if ls "$HAMER_OUT"/*_all.jpg >/dev/null 2>&1; then EXT="jpg"
283
- elif ls "$HAMER_OUT"/*_all.png >/dev/null 2>&1; then EXT="png"
284
- else EXT="png"; fi
285
-
286
- "$PYTHON_BIN" -u - "$FRAMES" "$HAMER_OUT" "$FILLED" "$EXT" <<'PY'
287
- import os, sys, glob, shutil
288
- FRAMES, HAMER_OUT, FILLED, EXT = sys.argv[1:]
289
- imgs = sorted(glob.glob(os.path.join(FRAMES, '*.png')))
290
- N = len(imgs)
291
- if not EXT: EXT = 'png'
292
- os.makedirs(FILLED, exist_ok=True)
293
- for i in range(1, N+1):
294
- s=f"{i:06d}"
295
- src_all=os.path.join(HAMER_OUT, f"{s}_all.{EXT}")
296
- dst_all=os.path.join(FILLED, f"{s}_all.{EXT}")
297
- if os.path.exists(src_all): shutil.copy2(src_all, dst_all)
298
- else: shutil.copy2(os.path.join(FRAMES, f"{s}.png"), dst_all)
299
- print("[Info] 补齐到目录:", FILLED, flush=True)
300
- PY
301
-
302
- # 4) 黑底 + 彩色(无缓冲)
303
- if ls "$FILLED"/*_all.jpg >/dev/null 2>&1; then EXT="jpg"
304
- elif ls "$FILLED"/*_all.png >/dev/null 2>&1; then EXT="png"
305
- else { : > "${STATUS_DIR}/${stem}.fail"; rm -f "${STATUS_DIR}/${stem}.running"; echo "缺少 *_all.{jpg,png}"; exit 2; }; fi
306
-
307
- "$PYTHON_BIN" -u - "$FRAMES" "$FILLED" "$ALIGNED_COLOR" "$EXT" <<'PY'
308
- import os, sys, glob
309
- import numpy as np
310
- from PIL import Image, ImageFilter
311
- FRAMES, FILLED, OUT, EXT = sys.argv[1:]
312
- os.makedirs(OUT, exist_ok=True)
313
- frames = sorted(glob.glob(os.path.join(FRAMES, '*.png')))
314
- N = len(frames)
315
- THR = 16
316
- def hsv_to_rgb_arr(h):
317
- h6 = (h * 6.0).astype(np.float32); i = np.floor(h6).astype(np.int32) % 6; f = h6 - np.floor(h6)
318
- v = np.ones_like(h, dtype=np.float32); s = np.ones_like(h, dtype=np.float32)
319
- p=v*(1-s); q=v*(1-f*s); t=v*(1-(1-f)*s)
320
- r=np.zeros_like(h, np.float32); g=np.zeros_like(h, np.float32); b=np.zeros_like(h, np.float32)
321
- m0=(i==0); r[m0]=v[m0]; g[m0]=t[m0]; b[m0]=p[m0]
322
- m1=(i==1); r[m1]=q[m1]; g[m1]=v[m1]; b[m1]=p[m1]
323
- m2=(i==2); r[m2]=p[m2]; g[m2]=v[m2]; b[m2]=t[m2]
324
- m3=(i==3); r[m3]=p[m3]; g[m3]=q[m3]; b[m3]=v[m3]
325
- m4=(i==4); r[m4]=t[m4]; g[m4]=p[m4]; b[m4]=v[m4]
326
- m5=(i==5); r[m5]=v[m5]; g[m5]=p[m5]; b[m5]=q[m5]
327
- return np.stack([r,g,b], axis=2)
328
- for idx in range(1, N+1):
329
- s=f"{idx:06d}"; src0=os.path.join(FRAMES, f"{s}.png"); src=os.path.join(FILLED, f"{s}_all.{EXT}"); dst=os.path.join(OUT, f"{s}_all.png")
330
- im0=Image.open(src0).convert("RGB"); W,H=im0.size
331
- if not os.path.exists(src): Image.new("RGB",(W,H),(0,0,0)).save(dst); continue
332
- im=Image.open(src).convert("RGB")
333
- if im.size!=(W,H): im=im.resize((W,H), Image.BILINEAR)
334
- a=np.asarray(im, np.int16); b=np.asarray(im0, np.int16)
335
- diff=np.mean(np.abs(a-b), axis=2).astype(np.uint8)
336
- mask=(diff>THR).astype(np.uint8)*255
337
- mask_img=Image.fromarray(mask).filter(ImageFilter.MinFilter(3))
338
- mask_np=np.asarray(mask_img, np.uint8)
339
- if mask_np.max()==0: Image.new("RGB",(W,H),(0,0,0)).save(dst); continue
340
- ys=np.linspace(0,1,H,dtype=np.float32)[:,None]; hs=np.repeat(ys,W,axis=1)
341
- h=0.66*(1.0-hs); color=hsv_to_rgb_arr(h)
342
- gray=a.mean(axis=2).astype(np.float32)/255.0; shade=0.6+0.4*gray
343
- color=(color*shade[...,None]); color_u8=(np.clip(color,0,1)*255).astype(np.uint8)
344
- color_u8[mask_np==0]=0; Image.fromarray(color_u8,"RGB").save(dst)
345
- print("[Info] 彩色黑底输出:", OUT, flush=True)
346
- PY
347
-
348
- # 5) 合成 mp4(用 -u 解析 FPS,ffmpeg 行缓冲)
349
- local FPS
350
- FPS=$("$PYTHON_BIN" -u - <<PY
351
- fr = """${FPS_RAW}"""
352
- try:
353
- if "/" in fr:
354
- n, d = fr.split("/")
355
- print(float(n)/float(d))
356
- else:
357
- print(float(fr))
358
- except Exception:
359
- print(30.0)
360
- PY
361
- )
362
- stdbuf -oL -eL ffmpeg -y -framerate "$FPS" -start_number 1 -i "$ALIGNED_COLOR/%06d_all.png" -c:v libx264 -pix_fmt yuv420p "$OUT_MP4"
363
-
364
- log "完成:$OUT_MP4"
365
- } >>"$LOGF" 2>&1
366
-
367
- : > "${STATUS_DIR}/${stem}.done"
368
- rm -f "${STATUS_DIR}/${stem}.running" "${STATUS_DIR}/${stem}.fail" "${STATUS_DIR}/${stem}.skip" 2>/dev/null || true
369
- return 0
370
- }
371
-
372
- ########################################
373
- # 单文件模式(给并行 worker 调用)
374
- ########################################
375
- if [[ "${1:-}" == "--one" ]]; then
376
- shift
377
- trap on_worker_signal INT TERM
378
- trap 'cleanup_all' EXIT
379
- process_one "$@"
380
- exit $?
381
- fi
382
-
383
- ########################################
384
- # 主控:扫描 & 并行 + 终端实时进度
385
- ########################################
386
- log "扫描目录:$SRC_DIR"
387
- shopt -s nullglob
388
- mapfile -d '' VIDEOS < <(find "$SRC_DIR" -maxdepth 1 -type f -iname '*.mp4' -print0 | sort -z)
389
- TOTAL=${#VIDEOS[@]}
390
- if (( TOTAL == 0 )); then log "未发现 mp4 文件,退出。"; exit 0; fi
391
-
392
- # 进度刷新(每 1.5s)
393
- progress_loop() {
394
- tput civis 2>/dev/null || true
395
- while :; do
396
- local done fail skip run gpumap c
397
- done=$(ls "$STATUS_DIR"/*.done 2>/dev/null | wc -l | awk '{print $1}')
398
- fail=$(ls "$STATUS_DIR"/*.fail 2>/dev/null | wc -l | awk '{print $1}')
399
- skip=$(ls "$STATUS_DIR"/*.skip 2>/dev/null | wc -l | awk '{print $1}')
400
- run=$(ls "$STATUS_DIR"/*.running 2>/dev/null | wc -l | awk '{print $1}')
401
- gpumap=""
402
- if [[ -n "$GPUS" ]]; then
403
- IFS=',' read -r -a GPU_ARR <<<"$GPUS"
404
- for gid in "${GPU_ARR[@]}"; do
405
- c=$(grep -h "^gpu=${gid}$" -s "$STATUS_DIR"/*.running 2>/dev/null | wc -l | awk '{print $1}')
406
- gpumap+=" GPU${gid}:${c}"
407
- done
408
- else
409
- gpumap+=" CPU:${run}"
410
- fi
411
- printf "\r[%s] Progress: %d/%d | Done:%d | Skip:%d | Run:%d | Fail:%d%s" \
412
- "$(date +'%H:%M:%S')" "$((done+skip+fail))" "$TOTAL" "$done" "$skip" "$run" "$fail" "$gpumap"
413
- if (( done+skip+fail >= TOTAL )) && (( run == 0 )); then
414
- printf "\n"; break
415
- fi
416
- sleep 1.5
417
- done
418
- tput cnorm 2>/dev/null || true
419
- }
420
- progress_loop & PROG_PID=$!
421
-
422
- # 并行启动(parallel 优先,否则 xargs)
423
- if command -v parallel >/dev/null 2>&1; then
424
- printf '%s\0' "${VIDEOS[@]}" \
425
- | parallel -0 -P "${JOBS}" --no-run-if-empty --halt now,fail=1 \
426
- bash "$SCRIPT_PATH" --one {}
427
- else
428
- printf '%s\0' "${VIDEOS[@]}" \
429
- | xargs -0 -n1 -P "${JOBS}" -I{} bash "$SCRIPT_PATH" --one "{}"
430
- fi
431
-
432
- wait "$PROG_PID" 2>/dev/null || true
433
-
434
- # 汇总
435
- DONE=$(ls "$STATUS_DIR"/*.done 2>/dev/null | wc -l | awk '{print $1}')
436
- FAIL=$(ls "$STATUS_DIR"/*.fail 2>/dev/null | wc -l | awk '{print $1}')
437
- SKIP=$(ls "$STATUS_DIR"/*.skip 2>/dev/null | wc -l | awk '{print $1}')
438
- echo "==============================="
439
- echo "🎯 完成:成功 $DONE ;失败 $FAIL ;跳过 $SKIP"
440
- echo "📂 结果目录:$OUT_RES_ROOT"
441
- echo "🗒 日志目录:$LOG_DIR"
442
- echo "==============================="
443
-
444
- cleanup_all