Delete files script/ with huggingface_hub
Browse files- script/batch_extract_smpl_cs.sh +0 -72
- script/hamer_batch.sh +0 -305
- script/mgpu_mworker_extract_smpl_cs.sh +0 -438
- script/mgpu_mworker_hamer_batch.sh +0 -444
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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|