Skip to content

Commit be4853e

Browse files
committed
add multi-gpu script
1 parent 67b7218 commit be4853e

3 files changed

Lines changed: 157 additions & 0 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ All rights are reserved to the copyright owners (TM & © Universal (2019)). This
2626
## News
2727
- [ ] **Incoming**: Support vot-challenge toolkit intergration.
2828
- [ ] **Incoming**: Release demo script to support inference on video (with mask prompt).
29+
- [x] **2025/02/18**: Release multi-GPU inference script.
2930
- [x] **2025/01/27**: Release [inference script](https://github.com/yangchris11/samurai/blob/master/sam2/tools/README.md#samurai-vos-inference) on VOS task (SA-V)!
3031
- [x] **2024/11/21**: Release [demo script](https://github.com/yangchris11/samurai?tab=readme-ov-file#demo-on-custom-video) to support inference on video (bounding box prompt).
3132
- [x] **2024/11/20** Release [inference script](https://github.com/yangchris11/samurai?tab=readme-ov-file#main-inference) on VOT task (LaSOT, LaSOT-ext, GOT-10k, UAV123, TrackingNet, OTB100)!

scripts/inference.sh

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
set -x
2+
3+
gpu_list="${CUDA_VISIBLE_DEVICES:-0}"
4+
IFS=',' read -ra GPULIST <<< "$gpu_list"
5+
CHUNKS=8
6+
7+
for IDX in $(seq 0 $((CHUNKS-1))); do
8+
CUDA_VISIBLE_DEVICES=$IDX python ./scripts/main_inference_chunk.py \
9+
--chunk_idx $IDX \
10+
--num_chunks $CHUNKS &
11+
done
12+
13+
wait

scripts/main_inference_chunk.py

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import argparse
2+
import gc
3+
import os
4+
import os.path as osp
5+
import pdb
6+
7+
import cv2
8+
import numpy as np
9+
import torch
10+
from loguru import logger
11+
from tqdm import tqdm
12+
13+
from sam2.build_sam import build_sam2_video_predictor
14+
15+
16+
def load_test_video_list(testing_list_path):
17+
with open(testing_list_path, 'r') as f:
18+
test_videos = [line.strip() for line in f.readlines()]
19+
return test_videos
20+
21+
def load_gt(gt_path):
22+
"""
23+
Load the ground truth from the given path
24+
"""
25+
with open(gt_path, 'r') as f:
26+
gt = f.readlines()
27+
# bbox in first frame are prompts
28+
prompts = {}
29+
fid = 0
30+
for line in gt:
31+
x, y, w, h = map(int, line.split(','))
32+
prompts[fid] = ((x, y, x+w, y+h), 0)
33+
fid += 1
34+
35+
return prompts
36+
37+
def get_ckpt_and_cfg(tracker_name, model_name):
38+
"""
39+
Get the checkpoint and config file for the given tracker and model
40+
"""
41+
assert tracker_name in ["sam2.1", "samurai"], "Invalid tracker name"
42+
assert model_name in ["tiny", "small", "base_plus", "large"], "Invalid model name"
43+
model_ckpt = f"sam2/checkpoints/sam2.1_hiera_{model_name}.pt"
44+
45+
if model_name == "base_plus":
46+
model_cfg = f"configs/{tracker_name}/sam2.1_hiera_b+.yaml"
47+
else:
48+
model_cfg = f"configs/{tracker_name}/sam2.1_hiera_{model_name[0]}.yaml"
49+
50+
return model_ckpt, model_cfg
51+
52+
def split_list(video_list, num_chunks):
53+
"""
54+
Split a list into num_chunks chunks
55+
"""
56+
chunk_size = len(video_list) // num_chunks
57+
return [video_list[i:i+chunk_size] for i in range(0, len(video_list), chunk_size)]
58+
59+
def inference_chunk(dataset_path, tracker_name, model_name, chunk_videos, result_folder):
60+
exp_name = "test"
61+
62+
model_ckpt, model_cfg = get_ckpt_and_cfg(tracker_name, model_name)
63+
64+
for vid, video in enumerate(chunk_videos):
65+
66+
cat_name = video.split('-')[0]
67+
cid_name = video.split('-')[1]
68+
video_basename = video.strip()
69+
frame_folder = osp.join(dataset_path, cat_name, video.strip(), "img")
70+
num_frames = len(os.listdir(osp.join(dataset_path, cat_name, video.strip(), "img")))
71+
height, width = cv2.imread(osp.join(frame_folder, "00000001.jpg")).shape[:2]
72+
73+
logger.info(f"Running video [{vid+1}/{len(chunk_videos)}]: {video} with {num_frames} frames ({height}x{width})")
74+
75+
predictor = build_sam2_video_predictor(model_cfg, model_ckpt, device="cuda:0")
76+
77+
predictions = []
78+
79+
# Start processing frames
80+
with torch.inference_mode(), torch.autocast("cuda", dtype=torch.float16):
81+
state = predictor.init_state(frame_folder, offload_video_to_cpu=True, offload_state_to_cpu=True)
82+
83+
prompts = load_gt(osp.join(dataset_path, cat_name, video.strip(), "groundtruth.txt"))
84+
85+
bbox, track_label = prompts[0]
86+
frame_idx, object_ids, masks = predictor.add_new_points_or_box(state, box=bbox, frame_idx=0, obj_id=0)
87+
88+
for frame_idx, object_ids, masks in predictor.propagate_in_video(state):
89+
mask_to_vis = {}
90+
bbox_to_vis = {}
91+
92+
assert len(masks) == 1 and len(object_ids) == 1, "Only one object is supported right now"
93+
for obj_id, mask in zip(object_ids, masks):
94+
mask = mask[0].cpu().numpy()
95+
mask = mask > 0.0
96+
non_zero_indices = np.argwhere(mask)
97+
if len(non_zero_indices) == 0:
98+
bbox = [0, 0, 0, 0]
99+
else:
100+
y_min, x_min = non_zero_indices.min(axis=0).tolist()
101+
y_max, x_max = non_zero_indices.max(axis=0).tolist()
102+
bbox = [x_min, y_min, x_max-x_min, y_max-y_min]
103+
bbox_to_vis[obj_id] = bbox
104+
mask_to_vis[obj_id] = mask
105+
106+
predictions.append(bbox_to_vis)
107+
108+
os.makedirs(result_folder, exist_ok=True)
109+
with open(osp.join(result_folder, f'{video_basename}.txt'), 'w') as f:
110+
for pred in predictions:
111+
x, y, w, h = pred[0]
112+
f.write(f"{x},{y},{w},{h}\n")
113+
114+
del predictor
115+
del state
116+
gc.collect()
117+
torch.clear_autocast_cache()
118+
torch.cuda.empty_cache()
119+
120+
def main():
121+
parser = argparse.ArgumentParser()
122+
parser.add_argument("--dataset_path", type=str, default="data/LaSOT-ext")
123+
parser.add_argument("--tracker_name", type=str, default="samurai")
124+
parser.add_argument("--model_name", type=str, default="large")
125+
parser.add_argument("--chunk_idx", type=int, default=0)
126+
parser.add_argument("--num_chunks", type=int, default=1)
127+
parser.add_argument("--exp_name", type=str, default="test")
128+
parser.add_argument("--root_result_folder", type=str, default="results")
129+
args = parser.parse_args()
130+
131+
test_videos = load_test_video_list("data/LaSOT-ext/testing_set.txt")
132+
chunk_video_list = split_list(test_videos, args.num_chunks)
133+
134+
chunk_videos = chunk_video_list[args.chunk_idx]
135+
136+
logger.info(f"Chunk ID: {args.chunk_idx}, Number of videos: {len(chunk_videos)} (from {chunk_videos[0]} to {chunk_videos[-1]})")
137+
138+
exp_result_folder = osp.join(args.root_result_folder, args.tracker_name, f"{args.exp_name}_{args.model_name}")
139+
140+
inference_chunk(args.dataset_path, args.tracker_name, args.model_name, chunk_videos, exp_result_folder)
141+
142+
if __name__ == "__main__":
143+
main()

0 commit comments

Comments
 (0)