VerokeAI/Object_tracking_boxmot
0
1# Mikel Broström 🔥 Yolo Tracking 🧾 AGPL-3.0 license2 3import argparse4import subprocess5from pathlib import Path6import numpy as np7from tqdm import tqdm8import configparser9import shutil10import json11import queue12import select13import re14import os15import torch16from functools import partial17import threading18import sys19import copy20import concurrent.futures21 22from boxmot import TRACKERS23from boxmot.tracker_zoo import create_tracker24from boxmot.utils import ROOT, WEIGHTS, TRACKER_CONFIGS, logger as LOGGER, EXAMPLES, DATA25from boxmot.utils.checks import RequirementsChecker26from boxmot.utils.torch_utils import select_device27from boxmot.utils.misc import increment_path28from boxmot.postprocessing.gsi import gsi29 30from ultralytics import YOLO31from ultralytics.data.loaders import LoadImagesAndVideos32 33from tracking.detectors import (get_yolo_inferer, default_imgsz,34 is_ultralytics_model, is_yolox_model)35from tracking.utils import convert_to_mot_format, write_mot_results, download_mot_eval_tools, download_mot_dataset, unzip_mot_dataset, eval_setup, split_dataset36from boxmot.appearance.reid.auto_backend import ReidAutoBackend37 38checker = RequirementsChecker()39checker.check_packages(('ultralytics @ git+https://github.com/mikel-brostrom/ultralytics.git', )) # install40 41 42def cleanup_mot17(data_dir, keep_detection='FRCNN'):43 """44 Cleans up the MOT17 dataset to resemble the MOT16 format by keeping only one detection folder per sequence.45 Skips sequences that have already been cleaned.46 47 Args:48 - data_dir (str): Path to the MOT17 train directory.49 - keep_detection (str): Detection type to keep (options: 'DPM', 'FRCNN', 'SDP'). Default is 'DPM'.50 """51 52 # Get all folders in the train directory53 all_dirs = [d for d in os.listdir(data_dir) if os.path.isdir(os.path.join(data_dir, d))]54 55 # Identify unique sequences by removing detection suffixes56 unique_sequences = set(seq.split('-')[0] + '-' + seq.split('-')[1] for seq in all_dirs)57 58 for seq in unique_sequences:59 # Directory path to the cleaned sequence60 cleaned_seq_dir = os.path.join(data_dir, seq)61 62 # Skip if the sequence is already cleaned63 if os.path.exists(cleaned_seq_dir):64 print(f"Sequence {seq} is already cleaned. Skipping.")65 continue66 67 # Directories for each detection method68 seq_dirs = [os.path.join(data_dir, d)69 for d in all_dirs if d.startswith(seq)]70 71 # Directory path for the detection folder to keep72 keep_dir = os.path.join(data_dir, f"{seq}-{keep_detection}")73 74 if os.path.exists(keep_dir):75 # Move the directory to a new name (removing the detection suffix)76 shutil.move(keep_dir, cleaned_seq_dir)77 print(f"Moved {keep_dir} to {cleaned_seq_dir}")78 79 # Remove other detection directories80 for seq_dir in seq_dirs:81 if os.path.exists(seq_dir) and seq_dir != keep_dir:82 shutil.rmtree(seq_dir)83 print(f"Removed {seq_dir}")84 else:85 print(f"Directory for {seq} with {keep_detection} detection does not exist. Skipping.")86 87 print("MOT17 Cleanup completed!")88 89 90def prompt_overwrite(path_type: str, path: str, ci: bool = True) -> bool:91 """92 Prompts the user to confirm overwriting an existing file.93 94 Args:95 path_type (str): Type of the path (e.g., 'Detections and Embeddings', 'MOT Result').96 path (str): The path to check.97 ci (bool): If True, automatically reuse existing file without prompting (for CI environments).98 99 Returns:100 bool: True if user confirms to overwrite, False otherwise.101 """102 if ci:103 LOGGER.debug(f"{path_type} {path} already exists. Use existing due to no UI mode.")104 return False105 106 def input_with_timeout(prompt, timeout=3.0):107 print(prompt, end='', flush=True)108 109 result = []110 input_received = threading.Event()111 112 def get_input():113 user_input = sys.stdin.readline().strip().lower()114 result.append(user_input)115 input_received.set()116 117 input_thread = threading.Thread(target=get_input)118 input_thread.daemon = True # Ensure thread does not prevent program exit119 input_thread.start()120 input_thread.join(timeout)121 122 if input_received.is_set():123 return result[0] in ['y', 'yes']124 else:125 print("\nNo response, not proceeding with overwrite...")126 return False127 128 return input_with_timeout(f"{path_type} {path} already exists. Overwrite? [y/N]: ")129 130 131def generate_dets_embs(args: argparse.Namespace, y: Path, source: Path) -> None:132 """133 Generates detections and embeddings for the specified 134 arguments, YOLO model and source.135 136 Args:137 args (Namespace): Parsed command line arguments.138 y (Path): Path to the YOLO model file.139 source (Path): Path to the source directory.140 """141 WEIGHTS.mkdir(parents=True, exist_ok=True)142 143 if args.imgsz is None:144 args.imgsz = default_imgsz(y)145 146 yolo = YOLO(147 y if is_ultralytics_model(y)148 else 'yolov8n.pt',149 )150 151 results = yolo(152 source=source,153 conf=args.conf,154 iou=args.iou,155 agnostic_nms=args.agnostic_nms,156 stream=True,157 device=args.device,158 verbose=False,159 exist_ok=args.exist_ok,160 project=args.project,161 name=args.name,162 classes=args.classes,163 imgsz=args.imgsz,164 vid_stride=args.vid_stride,165 )166 167 if not is_ultralytics_model(y):168 m = get_yolo_inferer(y)169 yolo_model = m(model=y, device=yolo.predictor.device,170 args=yolo.predictor.args)171 yolo.predictor.model = yolo_model172 173 # If current model is YOLOX, change the preprocess and postprocess174 if is_yolox_model(y):175 # add callback to save image paths for further processing176 yolo.add_callback("on_predict_batch_start",177 lambda p: yolo_model.update_im_paths(p))178 yolo.predictor.preprocess = (179 lambda im: yolo_model.preprocess(im=im))180 yolo.predictor.postprocess = (181 lambda preds, im, im0s:182 yolo_model.postprocess(preds=preds, im=im, im0s=im0s))183 184 reids = []185 for r in args.reid_model:186 reid_model = ReidAutoBackend(weights=args.reid_model,187 device=yolo.predictor.device,188 half=args.half).model189 reids.append(reid_model)190 embs_path = args.project / 'dets_n_embs' / y.stem / 'embs' / r.stem / (source.parent.name + '.txt')191 embs_path.parent.mkdir(parents=True, exist_ok=True)192 embs_path.touch(exist_ok=True)193 194 if os.path.getsize(embs_path) > 0:195 open(embs_path, 'w').close()196 197 yolo.predictor.custom_args = args198 199 dets_path = args.project / 'dets_n_embs' / y.stem / 'dets' / (source.parent.name + '.txt')200 dets_path.parent.mkdir(parents=True, exist_ok=True)201 dets_path.touch(exist_ok=True)202 203 if os.path.getsize(dets_path) > 0:204 open(dets_path, 'w').close()205 206 with open(str(dets_path), 'ab+') as f:207 np.savetxt(f, [], fmt='%f', header=str(source))208 209 for frame_idx, r in enumerate(tqdm(results, desc="Frames")):210 nr_dets = len(r.boxes)211 frame_idx = torch.full((1, 1), frame_idx + 1).repeat(nr_dets, 1)212 img = r.orig_img213 214 dets = np.concatenate(215 [216 frame_idx,217 r.boxes.xyxy.to('cpu'),218 r.boxes.conf.unsqueeze(1).to('cpu'),219 r.boxes.cls.unsqueeze(1).to('cpu'),220 ], axis=1221 )222 223 # Filter dets with incorrect boxes: (x2 < x1 or y2 < y1)224 boxes = r.boxes.xyxy.to('cpu').numpy().round().astype(int)225 boxes_filter = ((np.maximum(0, boxes[:, 0]) < np.minimum(boxes[:, 2], img.shape[1])) &226 (np.maximum(0, boxes[:, 1]) < np.minimum(boxes[:, 3], img.shape[0])))227 dets = dets[boxes_filter]228 229 with open(str(dets_path), 'ab+') as f:230 np.savetxt(f, dets, fmt='%f')231 232 for reid, reid_model_name in zip(reids, args.reid_model):233 embs = reid.get_features(dets[:, 1:5], img)234 embs_path = args.project / "dets_n_embs" / y.stem / 'embs' / reid_model_name.stem / (source.parent.name + '.txt')235 with open(str(embs_path), 'ab+') as f:236 np.savetxt(f, embs, fmt='%f')237 238 239def generate_mot_results(args: argparse.Namespace, config_dict: dict = None) -> dict[str, np.ndarray]:240 """241 Generates MOT results for the specified arguments and configuration.242 243 Args:244 args (Namespace): Parsed command line arguments.245 config_dict (dict, optional): Additional configuration dictionary.246 247 Returns:248 dict[str, np.ndarray]: {seq_name: array} with frame ids used for MOT249 """250 args.device = select_device(args.device)251 tracker = create_tracker(252 args.tracking_method,253 TRACKER_CONFIGS / (args.tracking_method + '.yaml'),254 args.reid_model[0].with_suffix('.pt'),255 args.device,256 False,257 False,258 config_dict259 )260 261 with open(args.dets_file_path, 'r') as file:262 source = Path(file.readline().strip().replace("# ", ""))263 264 dets = np.loadtxt(args.dets_file_path, skiprows=1)265 embs = np.loadtxt(args.embs_file_path)266 267 dets_n_embs = np.concatenate([dets, embs], axis=1)268 269 dataset = LoadImagesAndVideos(source)270 271 txt_path = args.exp_folder_path / (source.parent.name + '.txt')272 all_mot_results = []273 274 # Change FPS275 if args.fps:276 277 # Extract original FPS278 conf_path = source.parent / 'seqinfo.ini'279 conf = configparser.ConfigParser()280 conf.read(conf_path)281 282 orig_fps = int(conf.get("Sequence", "frameRate"))283 284 if orig_fps < args.fps:285 LOGGER.warning(f"Original FPS ({orig_fps}) is lower than "286 f"requested FPS ({args.fps}) for sequence "287 f"{source.parent.name}. Using original FPS.")288 target_fps = orig_fps289 else:290 target_fps = args.fps291 292 293 step = orig_fps/target_fps294 else:295 step = 1296 297 # Create list with frame numbers according to needed step298 frame_nums = np.arange(1, len(dataset) + 1, step).astype(int).tolist()299 300 seq_frame_nums = {source.parent.name: frame_nums.copy()}301 302 for frame_num, d in enumerate(tqdm(dataset, desc=source.parent.name), 1):303 # Filter using list with needed numbers304 if len(frame_nums) > 0:305 if frame_num < frame_nums[0]:306 continue307 else:308 frame_nums.pop(0)309 310 im = d[1][0]311 frame_dets_n_embs = dets_n_embs[dets_n_embs[:, 0] == frame_num]312 313 dets = frame_dets_n_embs[:, 1:7]314 embs = frame_dets_n_embs[:, 7:]315 tracks = tracker.update(dets, im, embs)316 317 if tracks.size > 0:318 mot_results = convert_to_mot_format(tracks, frame_num)319 all_mot_results.append(mot_results)320 321 if all_mot_results:322 all_mot_results = np.vstack(all_mot_results)323 else:324 all_mot_results = np.empty((0, 0))325 326 write_mot_results(txt_path, all_mot_results)327 328 return seq_frame_nums329 330 331def parse_mot_results(results: str) -> dict:332 """333 Extracts the COMBINED HOTA, MOTA, IDF1 from the results generated by the run_mot_challenge.py script.334 335 Args:336 results (str): MOT results as a string.337 338 Returns:339 dict: A dictionary containing HOTA, MOTA, and IDF1 scores.340 """341 combined_results = results.split('COMBINED')[2:-1]342 combined_results = [float(re.findall(r"[-+]?(?:\d*\.*\d+)", f)[0])343 for f in combined_results]344 345 results_dict = {}346 for key, value in zip(["HOTA", "MOTA", "IDF1"], combined_results):347 results_dict[key] = value348 349 return results_dict350 351 352def trackeval(args: argparse.Namespace, seq_paths: list, save_dir: Path, MOT_results_folder: Path, gt_folder: Path, metrics: list = ["HOTA", "CLEAR", "Identity"]) -> str:353 """354 Executes a Python script to evaluate MOT challenge tracking results using specified metrics.355 356 Args:357 seq_paths (list): List of sequence paths.358 save_dir (Path): Directory to save evaluation results.359 MOT_results_folder (Path): Folder containing MOT results.360 gt_folder (Path): Folder containing ground truth data.361 metrics (list, optional): List of metrics to use for evaluation. Defaults to ["HOTA", "CLEAR", "Identity"].362 363 Returns:364 str: Standard output from the evaluation script.365 """366 367 d = [seq_path.parent.name for seq_path in seq_paths]368 369 args = [370 sys.executable, EXAMPLES / 'val_utils' / 'scripts' / 'run_mot_challenge.py',371 "--GT_FOLDER", str(gt_folder),372 "--BENCHMARK", "",373 "--TRACKERS_FOLDER", args.exp_folder_path,374 "--TRACKERS_TO_EVAL", "",375 "--SPLIT_TO_EVAL", "train",376 "--METRICS", *metrics,377 "--USE_PARALLEL", "True",378 "--TRACKER_SUB_FOLDER", "",379 "--NUM_PARALLEL_CORES", str(4),380 "--SKIP_SPLIT_FOL", "True",381 "--GT_LOC_FORMAT", "{gt_folder}/{seq}/gt/gt_temp.txt",382 "--SEQ_INFO", *d383 ]384 385 p = subprocess.Popen(386 args=args,387 stdout=subprocess.PIPE,388 stderr=subprocess.PIPE,389 text=True390 )391 392 stdout, stderr = p.communicate()393 394 if stderr:395 print("Standard Error:\n", stderr)396 return stdout397 398 399def run_generate_dets_embs(opt: argparse.Namespace) -> None:400 """401 Runs the generate_dets_embs function for all YOLO models and source directories.402 403 Args:404 opt (Namespace): Parsed command line arguments.405 """406 mot_folder_paths = sorted([item for item in Path(opt.source).iterdir()])407 for y in opt.yolo_model:408 for i, mot_folder_path in enumerate(mot_folder_paths):409 dets_path = Path(opt.project) / 'dets_n_embs' / y.stem / 'dets' / (mot_folder_path.name + '.txt')410 embs_path = Path(opt.project) / 'dets_n_embs' / y.stem / 'embs' / (opt.reid_model[0].stem) / (mot_folder_path.name + '.txt')411 if dets_path.exists() and embs_path.exists():412 if prompt_overwrite('Detections and Embeddings', dets_path, opt.ci):413 LOGGER.debug(f'Overwriting detections and embeddings for {mot_folder_path}...')414 else:415 LOGGER.debug(f'Skipping generation for {mot_folder_path} as they already exist.')416 continue417 LOGGER.debug(f'Generating detections and embeddings for data under {mot_folder_path} [{i + 1}/{len(mot_folder_paths)} seqs]')418 generate_dets_embs(opt, y, source=mot_folder_path / 'img1')419 420 421def process_single_mot(opt: argparse.Namespace, d: Path, e: Path, evolve_config: dict):422 # Create a deep copy of opt so each task works independently423 new_opt = copy.deepcopy(opt)424 new_opt.dets_file_path = d425 new_opt.embs_file_path = e426 frames_dict = generate_mot_results(new_opt, evolve_config)427 return frames_dict428 429def run_generate_mot_results(opt: argparse.Namespace, evolve_config: dict = None) -> None:430 """431 Runs the generate_mot_results function for all YOLO models and detection/embedding files432 in parallel.433 """434 435 for y in opt.yolo_model:436 exp_folder_path = opt.project / 'mot' / (f"{y.stem}_{opt.reid_model[0].stem}_{opt.tracking_method}")437 exp_folder_path = increment_path(path=exp_folder_path, sep="_", exist_ok=False)438 opt.exp_folder_path = exp_folder_path439 440 mot_folder_names = [item.stem for item in Path(opt.source).iterdir()]441 442 dets_folder = opt.project / "dets_n_embs" / y.stem / 'dets'443 embs_folder = opt.project / "dets_n_embs" / y.stem / 'embs' / opt.reid_model[0].stem444 445 dets_file_paths = sorted([446 item for item in dets_folder.glob('*.txt')447 if not item.name.startswith('.') and item.stem in mot_folder_names448 ])449 embs_file_paths = sorted([450 item for item in embs_folder.glob('*.txt')451 if not item.name.startswith('.') and item.stem in mot_folder_names452 ])453 454 LOGGER.info(f"\nStarting tracking on:\n\t{opt.source}\nwith preloaded dets\n\t({dets_folder.relative_to(ROOT)})\nand embs\n\t({embs_folder.relative_to(ROOT)})\nusing\n\t{opt.tracking_method}")455 456 tasks = []457 # Create a thread pool to run each file pair in parallel458 with concurrent.futures.ThreadPoolExecutor() as executor:459 for d, e in zip(dets_file_paths, embs_file_paths):460 mot_result_path = exp_folder_path / (d.stem + '.txt')461 if mot_result_path.exists():462 if prompt_overwrite('MOT Result', mot_result_path, opt.ci):463 LOGGER.info(f'Overwriting MOT result for {d.stem}...')464 else:465 LOGGER.info(f'Skipping MOT result generation for {d.stem} as it already exists.')466 continue467 # Submit the task to process this file pair in parallel468 tasks.append(executor.submit(process_single_mot, opt, d, e, evolve_config))469 470 # Dict with {seq_name: [frame_nums]}471 seqs_frame_nums = {}472 # Wait for all tasks to complete and log any exceptions473 for future in concurrent.futures.as_completed(tasks):474 try:475 seqs_frame_nums.update(future.result())476 except Exception as exc:477 LOGGER.error(f'Error processing file pair: {exc}')478 479 # Postprocess data with gsi if requested480 if opt.gsi:481 gsi(mot_results_folder=opt.exp_folder_path)482 483 with open(opt.exp_folder_path / 'seqs_frame_nums.json', 'w') as f:484 json.dump(seqs_frame_nums, f)485 486 487def run_trackeval(opt: argparse.Namespace) -> dict:488 """489 Runs the trackeval function to evaluate tracking results.490 491 Args:492 opt (Namespace): Parsed command line arguments.493 """494 seq_paths, save_dir, MOT_results_folder, gt_folder = eval_setup(opt, opt.val_tools_path)495 trackeval_results = trackeval(opt, seq_paths, save_dir, MOT_results_folder, gt_folder)496 hota_mota_idf1 = parse_mot_results(trackeval_results)497 if opt.verbose:498 LOGGER.info(trackeval_results)499 with open(opt.tracking_method + "_output.json", "w") as outfile:500 outfile.write(json.dumps(hota_mota_idf1))501 LOGGER.info(json.dumps(hota_mota_idf1))502 return hota_mota_idf1503 504 505def run_all(opt: argparse.Namespace) -> None:506 """507 Runs all stages of the pipeline: generate_dets_embs, generate_mot_results, and trackeval.508 509 Args:510 opt (Namespace): Parsed command line arguments.511 """512 run_generate_dets_embs(opt)513 run_generate_mot_results(opt)514 run_trackeval(opt)515 516 517def parse_opt() -> argparse.Namespace:518 parser = argparse.ArgumentParser()519 520 # Global arguments521 parser.add_argument('--yolo-model', nargs='+', type=Path, default=[WEIGHTS / 'yolov8n.pt'], help='yolo model path')522 parser.add_argument('--reid-model', nargs='+', type=Path, default=[WEIGHTS / 'osnet_x0_25_msmt17.pt'], help='reid model path')523 parser.add_argument('--source', type=str, help='file/dir/URL/glob, 0 for webcam')524 parser.add_argument('--imgsz', '--img', '--img-size', nargs='+', type=int, default=None, help='inference size h,w')525 parser.add_argument('--fps', type=int, default=None, help='video frame-rate')526 parser.add_argument('--conf', type=float, default=0.01, help='min confidence threshold')527 parser.add_argument('--iou', type=float, default=0.7, help='intersection over union (IoU) threshold for NMS')528 parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu')529 parser.add_argument('--classes', nargs='+', type=int, default=0, help='filter by class: --classes 0, or --classes 0 2 3')530 parser.add_argument('--project', default=ROOT / 'runs', type=Path, help='save results to project/name')531 parser.add_argument('--name', default='', help='save results to project/name')532 parser.add_argument('--exist-ok', action='store_true', default=True, help='existing project/name ok, do not increment')533 parser.add_argument('--half', action='store_true', help='use FP16 half-precision inference')534 parser.add_argument('--vid-stride', type=int, default=1, help='video frame-rate stride')535 parser.add_argument('--ci', action='store_true', help='Automatically reuse existing due to no UI in CI')536 parser.add_argument('--tracking-method', type=str, default='deepocsort', help='deepocsort, botsort, strongsort, ocsort, bytetrack, imprassoc, boosttrack')537 parser.add_argument('--dets-file-path', type=Path, help='path to detections file')538 parser.add_argument('--embs-file-path', type=Path, help='path to embeddings file')539 parser.add_argument('--exp-folder-path', type=Path, help='path to experiment folder')540 parser.add_argument('--verbose', action='store_true', help='print results')541 parser.add_argument('--agnostic-nms', default=False, action='store_true', help='class-agnostic NMS')542 parser.add_argument('--gsi', action='store_true', help='apply Gaussian smooth interpolation postprocessing')543 parser.add_argument('--n-trials', type=int, default=4, help='nr of trials for evolution')544 parser.add_argument('--objectives', type=str, nargs='+', default=["HOTA", "MOTA", "IDF1"], help='set of objective metrics: HOTA,MOTA,IDF1')545 parser.add_argument('--val-tools-path', type=Path, default=EXAMPLES / 'val_utils', help='path to store trackeval repo in')546 parser.add_argument('--split-dataset', action='store_true', help='Use the second half of the dataset')547 548 subparsers = parser.add_subparsers(dest='command')549 550 # Subparser for generate_dets_embs551 generate_dets_embs_parser = subparsers.add_parser('generate_dets_embs', help='Generate detections and embeddings')552 generate_dets_embs_parser.add_argument('--source', type=str, required=True, help='file/dir/URL/glob, 0 for webcam')553 generate_dets_embs_parser.add_argument('--yolo-model', nargs='+', type=Path, default=WEIGHTS / 'yolov8n.pt', help='yolo model path')554 generate_dets_embs_parser.add_argument('--reid-model', nargs='+', type=Path, default=WEIGHTS / 'osnet_x0_25_msmt17.pt', help='reid model path')555 generate_dets_embs_parser.add_argument('--imgsz', '--img', '--img-size', nargs='+', type=int, default=[640], help='inference size h,w')556 generate_dets_embs_parser.add_argument('--classes', nargs='+', type=int, default=0, help='filter by class: --classes 0, or --classes 0 2 3')557 558 # Subparser for generate_mot_results559 generate_mot_results_parser = subparsers.add_parser('generate_mot_results', help='Generate MOT results')560 generate_mot_results_parser.add_argument('--yolo-model', nargs='+', type=Path, default=WEIGHTS / 'yolov8n.pt', help='yolo model path')561 generate_mot_results_parser.add_argument('--reid-model', nargs='+', type=Path, default=WEIGHTS / 'osnet_x0_25_msmt17.pt', help='reid model path')562 generate_mot_results_parser.add_argument('--tracking-method', type=str, default='deepocsort', help='deepocsort, botsort, strongsort, ocsort, bytetrack, imprassoc, boosttrack')563 generate_mot_results_parser.add_argument('--imgsz', '--img', '--img-size', nargs='+', type=int, default=[640], help='inference size h,w')564 565 # Subparser for trackeval566 trackeval_parser = subparsers.add_parser('trackeval', help='Evaluate tracking results')567 trackeval_parser.add_argument('--source', type=str, required=True, help='file/dir/URL/glob, 0 for webcam')568 trackeval_parser.add_argument('--exp-folder-path', type=Path, required=True, help='path to experiment folder')569 570 opt = parser.parse_args()571 source_path = Path(opt.source)572 opt.benchmark, opt.split = source_path.parent.name, source_path.name573 574 return opt575 576 577if __name__ == "__main__":578 opt = parse_opt()579 580 # download MOT benchmark581 download_mot_eval_tools(opt.val_tools_path)582 583 if not Path(opt.source).exists():584 zip_path = download_mot_dataset(opt.val_tools_path, opt.benchmark)585 unzip_mot_dataset(zip_path, opt.val_tools_path, opt.benchmark)586 587 if opt.benchmark == 'MOT17':588 cleanup_mot17(opt.source)589 590 if opt.split_dataset:591 opt.source, opt.benchmark = split_dataset(opt.source)592 593 if opt.command == 'generate_dets_embs':594 run_generate_dets_embs(opt)595 elif opt.command == 'generate_mot_results':596 run_generate_mot_results(opt)597 elif opt.command == 'trackeval':598 run_trackeval(opt)599 else:600 run_all(opt)601 