cs686's picture
Deploy GEM-X ZeroGPU motion capture
49d36c0 verified
Raw
History Blame Contribute Delete
14.2 kB
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
import os
import os.path as osp
import platform
import random
import subprocess
import cv2 as cv
import numpy as np
import seaborn as sns
from PIL import ImageColor
FFMPEG_PATH = "/usr/bin/ffmpeg" if osp.exists("/usr/bin/ffmpeg") else "ffmpeg"
font_files = {
"Windows": "C:/Windows/Fonts/arial.ttf",
"Linux": "/usr/share/fonts/truetype/lato/Lato-Regular.ttf",
"Darwin": "/System/Library/Fonts/Supplemental/Arial.ttf",
}
def get_video_width_height(video_file):
vcap = cv.VideoCapture(video_file)
img_w = int(vcap.get(3))
img_h = int(vcap.get(4))
return img_w, img_h
def get_video_num_fr(video_file):
vcap = cv.VideoCapture(video_file)
num_fr = int(vcap.get(cv.CAP_PROP_FRAME_COUNT))
return num_fr
def get_video_fps(video_file):
vcap = cv.VideoCapture(video_file)
fps = vcap.get(cv.CAP_PROP_FPS)
return fps
def rescale_video(video_path, out_path, width=-1, height=-1, verbose=True):
os.makedirs(osp.dirname(out_path), exist_ok=True)
cmd = [
FFMPEG_PATH,
"-y",
"-i",
video_path,
"-vf",
f"scale={width}:{height}",
out_path,
]
if not verbose:
cmd += ["-hide_banner", "-loglevel", "error"]
subprocess.run(cmd)
def crop_video(
video_path, out_path, width="iw", height="ih", x=0, y=0, crop_str=None, verbose=True
):
os.makedirs(osp.dirname(out_path), exist_ok=True)
if crop_str is None:
crop_str = f"{width}:{height}:{x}:{y}"
cmd = [FFMPEG_PATH, "-y", "-i", video_path, "-vf", f"crop={crop_str}", out_path]
if not verbose:
cmd += ["-hide_banner", "-loglevel", "error"]
subprocess.run(cmd)
def clip_video(video_path, out_path, start, end, verbose=True):
os.makedirs(osp.dirname(out_path), exist_ok=True)
cmd = [
FFMPEG_PATH,
"-y",
"-ss",
f"{start}",
"-i",
video_path,
"-t",
f"{end - start}",
out_path,
]
if not verbose:
cmd += ["-hide_banner", "-loglevel", "error"]
subprocess.run(cmd)
def text_video(
video_path,
out_path,
text,
x=10,
y=30,
verbose=True,
text_color="white",
text_size=60,
):
os.makedirs(osp.dirname(out_path), exist_ok=True)
font_file = font_files[platform.system()]
draw_str = f"drawtext=fontsize={text_size}:fontfile={font_file}:fontcolor={text_color}:text='{text}':x={x}:y={y}"
cmd = [FFMPEG_PATH, "-y", "-i", video_path, "-vf", draw_str, out_path]
if not verbose:
cmd += ["-hide_banner", "-loglevel", "error"]
subprocess.run(cmd)
def make_gif(
video_path,
out_path,
t_start="00:00:00",
t_end=None,
width=600,
height=-1,
verbose=True,
):
os.makedirs(osp.dirname(out_path), exist_ok=True)
cmd = (
[FFMPEG_PATH, "-y", "-i", video_path, "-ss", t_start]
+ (["-t", t_end] if t_end is not None else [])
+ [
"-vf",
f"fps=30,scale={width}:{height}:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse",
"-loop",
"0",
out_path,
]
)
if not verbose:
cmd += ["-hide_banner", "-loglevel", "error"]
subprocess.run(cmd)
def images_to_video(img_dir, out_path, img_fmt="%06d.jpg", fps=30, crf=25, verbose=True):
os.makedirs(osp.dirname(out_path), exist_ok=True)
cmd = [
FFMPEG_PATH,
"-y",
"-r",
f"{fps}",
"-f",
"image2",
"-start_number",
"0",
"-i",
f"{img_dir}/{img_fmt}",
"-vcodec",
"libx264",
"-vf",
"pad=ceil(iw/2)*2:ceil(ih/2)*2",
"-crf",
f"{crf}",
"-pix_fmt",
"yuv420p",
out_path,
]
if not verbose:
cmd += ["-hide_banner", "-loglevel", "error"]
p = subprocess.run(cmd)
if p.returncode != 0:
raise Exception("Something went wrong during images_to_video!")
def video_to_images(video_path, out_path, img_fmt="%06d.jpg", fps=30, verbose=True):
os.makedirs(out_path, exist_ok=True)
cmd = [FFMPEG_PATH, "-i", video_path, "-r", f"{fps}", f"{out_path}/{img_fmt}"]
if not verbose:
cmd += ["-hide_banner", "-loglevel", "error"]
p = subprocess.run(cmd)
if p.returncode != 0:
raise Exception("Something went wrong during video_to_images!")
def hstack_videos(
video1_path,
video2_path,
out_path,
crf=25,
verbose=True,
text1=None,
text2=None,
text_color="white",
text_size=60,
):
if not (text1 is None or text2 is None):
write_text = True
tmp_file = f"{osp.splitext(out_path)[0]}_tmp.mp4"
else:
write_text = False
os.makedirs(osp.dirname(out_path), exist_ok=True)
cmd = [
FFMPEG_PATH,
"-y",
"-i",
video1_path,
"-i",
video2_path,
"-filter_complex",
"hstack,format=yuv420p",
"-vcodec",
"libx264",
"-crf",
f"{crf}",
tmp_file if write_text else out_path,
]
if not verbose:
cmd += ["-hide_banner", "-loglevel", "error"]
subprocess.run(cmd)
if write_text:
font_file = font_files[platform.system()]
draw_str = (
f"drawtext=fontsize={text_size}:fontfile={font_file}:fontcolor={text_color}:text='{text1}':x=(w-text_w)/4:y=20,"
f"drawtext=fontsize={text_size}:fontfile={font_file}:fontcolor={text_color}:text='{text2}':x=3*(w-text_w)/4:y=20"
)
cmd = [
FFMPEG_PATH,
"-i",
tmp_file,
"-y",
"-vf",
draw_str,
"-c:a",
"copy",
out_path,
]
if not verbose:
cmd += ["-hide_banner", "-loglevel", "error"]
subprocess.run(cmd)
os.remove(tmp_file)
def vstack_videos(
video1_path,
video2_path,
out_path,
crf=25,
verbose=True,
text1=None,
text2=None,
text_color="white",
text_size=60,
):
if not (text1 is None or text2 is None):
write_text = True
tmp_file = f"{osp.splitext(out_path)[0]}_tmp.mp4"
else:
write_text = False
os.makedirs(osp.dirname(out_path), exist_ok=True)
cmd = [
FFMPEG_PATH,
"-y",
"-i",
video1_path,
"-i",
video2_path,
"-filter_complex",
"vstack,format=yuv420p",
"-vcodec",
"libx264",
"-crf",
f"{crf}",
tmp_file if write_text else out_path,
]
if not verbose:
cmd += ["-hide_banner", "-loglevel", "error"]
subprocess.run(cmd)
if write_text:
font_file = font_files[platform.system()]
draw_str = (
f"drawtext=fontsize={text_size}:fontfile={font_file}:fontcolor={text_color}:text='{text1}':x=10:y=20,"
f"drawtext=fontsize={text_size}:fontfile={font_file}:fontcolor={text_color}:text='{text2}':x=10:y=h/2+20"
)
cmd = [
FFMPEG_PATH,
"-i",
tmp_file,
"-y",
"-vf",
draw_str,
"-c:a",
"copy",
out_path,
]
if not verbose:
cmd += ["-hide_banner", "-loglevel", "error"]
subprocess.run(cmd)
os.remove(tmp_file)
def vstack_video_arr(
video_arr,
out_path,
crf=25,
verbose=True,
text_arr=None,
text_color="white",
text_size=60,
):
assert len(video_arr) > 1
tmp_file1 = f"{osp.splitext(out_path)[0]}_tmp1.mp4"
tmp_file2 = f"{osp.splitext(out_path)[0]}_tmp2.mp4"
height = np.array([get_video_width_height(x)[1] for x in video_arr])
start_h = np.concatenate([np.array([0]), np.cumsum(height)[:-1]])
os.makedirs(osp.dirname(out_path), exist_ok=True)
for i in range(1, len(video_arr)):
prev_video = video_arr[0] if i == 1 else tmp_file1
cmd = [
FFMPEG_PATH,
"-y",
"-i",
prev_video,
"-i",
video_arr[i],
"-filter_complex",
"vstack,format=yuv420p",
"-vcodec",
"libx264",
"-crf",
f"{crf}",
tmp_file2,
]
if not verbose:
cmd += ["-hide_banner", "-loglevel", "error"]
subprocess.run(cmd)
tmp_file1, tmp_file2 = tmp_file2, tmp_file1
if text_arr is not None:
font_file = font_files[platform.system()]
draw_str = ",".join(
[
f"drawtext=fontsize={text_size}:fontfile={font_file}:fontcolor={text_color}:text='{x}':x=10:y={h}+20"
for h, x in zip(start_h, text_arr)
]
)
cmd = [
FFMPEG_PATH,
"-i",
tmp_file1,
"-y",
"-vf",
draw_str,
"-c:a",
"copy",
out_path,
]
if not verbose:
cmd += ["-hide_banner", "-loglevel", "error"]
subprocess.run(cmd)
else:
os.rename(tmp_file1, out_path)
if os.path.exists(tmp_file1):
os.remove(tmp_file1)
if os.path.exists(tmp_file2):
os.remove(tmp_file2)
def hstack_video_arr(
video_arr,
out_path,
crf=25,
verbose=True,
text_arr=None,
text_color="white",
text_size=60,
):
assert len(video_arr) > 1
tmp_file1 = f"{osp.splitext(out_path)[0]}_tmp1.mp4"
tmp_file2 = f"{osp.splitext(out_path)[0]}_tmp2.mp4"
width = np.array([get_video_width_height(x)[0] for x in video_arr])
start_w = np.concatenate([np.array([0]), np.cumsum(width)[:-1]])
os.makedirs(osp.dirname(out_path), exist_ok=True)
for i in range(1, len(video_arr)):
prev_video = video_arr[0] if i == 1 else tmp_file1
cmd = [
FFMPEG_PATH,
"-y",
"-i",
prev_video,
"-i",
video_arr[i],
"-filter_complex",
"hstack,format=yuv420p",
"-vcodec",
"libx264",
"-crf",
f"{crf}",
tmp_file2,
]
if not verbose:
cmd += ["-hide_banner", "-loglevel", "error"]
subprocess.run(cmd)
tmp_file1, tmp_file2 = tmp_file2, tmp_file1
if text_arr is not None:
font_file = font_files[platform.system()]
draw_str = ",".join(
[
f"drawtext=fontsize={text_size}:fontfile={font_file}:fontcolor={text_color}:text='{x}':x={w}+10:y=20"
for w, x in zip(start_w, text_arr)
]
)
cmd = [
FFMPEG_PATH,
"-i",
tmp_file1,
"-y",
"-vf",
draw_str,
"-c:a",
"copy",
out_path,
]
if not verbose:
cmd += ["-hide_banner", "-loglevel", "error"]
subprocess.run(cmd)
else:
os.rename(tmp_file1, out_path)
if os.path.exists(tmp_file1):
os.remove(tmp_file1)
if os.path.exists(tmp_file2):
os.remove(tmp_file2)
def make_checker_board_texture(
color1="black", color2="white", width=10, height=10, n_tile=15, to_bgr=False
):
c1 = np.asarray(ImageColor.getcolor(color1, "RGB")).astype(np.uint8)
c2 = np.asarray(ImageColor.getcolor(color2, "RGB")).astype(np.uint8)
if to_bgr:
c1 = c1[[2, 1, 0]]
c2 = c2[[2, 1, 0]]
hw = width // 2
hh = height // 2
c1_block = np.tile(c1, (hh, hw, 1))
c2_block = np.tile(c2, (hh, hw, 1))
tex = np.block([[[c1_block], [c2_block]], [[c2_block], [c1_block]]])
tex = np.tile(tex, (n_tile, n_tile, 1))
return tex
def resize_bbox(bbox, scale):
x1, y1, x2, y2 = bbox[..., 0], bbox[..., 1], bbox[..., 2], bbox[..., 3]
h, w = y2 - y1, x2 - x1
cx, cy = x1 + 0.5 * w, y1 + 0.5 * h
h_new, w_new = h * scale, w * scale
x1_new, x2_new = cx - 0.5 * w_new, cx + 0.5 * w_new
y1_new, y2_new = cy - 0.5 * h_new, cy + 0.5 * h_new
bbox_new = np.stack([x1_new, y1_new, x2_new, y2_new], axis=-1)
return bbox_new
def nparray_to_vtk_matrix(array):
"""Convert a numpy.ndarray to a vtk.vtkMatrix4x4"""
import vtk
matrix = vtk.vtkMatrix4x4()
for i in range(array.shape[0]):
for j in range(array.shape[1]):
matrix.SetElement(i, j, array[i, j])
return matrix
def vtk_matrix_to_nparray(matrix):
"""Convert a numpy.ndarray to a vtk.vtkMatrix4x4"""
array = np.zeros([4, 4])
for i in range(array.shape[0]):
for j in range(array.shape[1]):
array[i, j] = matrix.GetElement(i, j)
return array
def random_color(seed):
"""Random a color according to the input seed."""
random.seed(seed)
colors = sns.color_palette()
color = random.choice(colors)
return color
def draw_tracks(img, bbox, idx, score, thickness=2, font_scale=0.4, text_height=10, text_width=15):
x1, y1, x2, y2 = bbox.astype(np.int32)
# bbox
bbox_color = random_color(idx)
bbox_color = [int(255 * _c) for _c in bbox_color][::-1]
cv.rectangle(img, (x1, y1), (x2, y2), bbox_color, thickness=thickness)
# id
text = str(idx)
width = len(text) * text_width
img[y1 : y1 + text_height, x1 : x1 + width, :] = bbox_color
cv.putText(
img,
str(idx),
(x1, y1 + text_height - 2),
cv.FONT_HERSHEY_COMPLEX,
font_scale,
color=(0, 0, 0),
)
# score
text = f"{score:.02f}"
width = len(text) * text_width
img[y1 - text_height : y1, x1 : x1 + width, :] = bbox_color
cv.putText(img, text, (x1, y1 - 2), cv.FONT_HERSHEY_COMPLEX, font_scale, color=(0, 0, 0))
return img
def draw_keypoints(img, keypoints, confidence, size=4, color=(255, 0, 255)):
for kp, conf in zip(keypoints, confidence):
if conf > 0.2:
cv.circle(img, np.round(kp).astype(int).tolist(), size, color=color, thickness=-1)
return img