# Ultralytics YOLO 🚀, AGPL-3.0 license """ Run prediction on images, videos, directories, globs, YouTube, webcam, streams, etc. Usage - sources: $ yolo mode=predict model=yolov8n.pt source=0 # webcam img.jpg # image vid.mp4 # video screen # screenshot path/ # directory list.txt # list of images list.streams # list of streams 'path/*.jpg' # glob 'https://youtu.be/Zgi9g1ksQHc' # YouTube 'rtsp://example.com/media.mp4' # RTSP, RTMP, HTTP stream Usage - formats: $ yolo mode=predict model=yolov8n.pt # PyTorch yolov8n.torchscript # TorchScript yolov8n.onnx # ONNX Runtime or OpenCV DNN with dnn=True yolov8n_openvino_model # OpenVINO yolov8n.engine # TensorRT yolov8n.mlmodel # CoreML (macOS-only) yolov8n_saved_model # TensorFlow SavedModel yolov8n.pb # TensorFlow GraphDef yolov8n.tflite # TensorFlow Lite yolov8n_edgetpu.tflite # TensorFlow Edge TPU yolov8n_paddle_model # PaddlePaddle """ import math import os import platform import time from math import sqrt from pathlib import Path from random import random import cv2 import numpy as np import torch from PIL import Image from ultralytics.nn.autobackend import AutoBackend from ultralytics.yolo.cfg import get_cfg from ultralytics.yolo.data import load_inference_source from ultralytics.yolo.data.augment import LetterBox, classify_transforms from ultralytics.yolo.utils import DEFAULT_CFG, LOGGER, SETTINGS, callbacks, colorstr, ops from ultralytics.yolo.utils.checks import check_imgsz, check_imshow from ultralytics.yolo.utils.files import increment_path from ultralytics.yolo.utils.torch_utils import select_device, smart_inference_mode from ultralytics.yolo.engine.ids import check_tings STREAM_WARNING = """ WARNING ⚠️ stream/video/webcam/dir predict source will accumulate results in RAM unless `stream=True` is passed, causing potential out-of-memory errors for large sources or long-running streams/videos. Usage: results = model(source=..., stream=True) # generator of Results objects for r in results: boxes = r.boxes # Boxes object for bbox outputs masks = r.masks # Masks object for segment masks outputs probs = r.probs # Class probabilities for classification outputs """ class BasePredictor: """ BasePredictor A base class for creating predictors. Attributes: args (SimpleNamespace): Configuration for the predictor. save_dir (Path): Directory to save results. done_setup (bool): Whether the predictor has finished setup. model (nn.Module): Model used for prediction. data (dict): Data configuration. device (torch.device): Device used for prediction. dataset (Dataset): Dataset used for prediction. vid_path (str): Path to video file. vid_writer (cv2.VideoWriter): Video writer for saving video output. annotator (Annotator): Annotator used for prediction. data_path (str): Path to data. """ def __init__(self, cfg=DEFAULT_CFG, overrides=None, _callbacks=None): """ Initializes the BasePredictor class. Args: cfg (str, optional): Path to a configuration file. Defaults to DEFAULT_CFG. overrides (dict, optional): Configuration overrides. Defaults to None. """ # self.real_MovBox = None self.args = get_cfg(cfg, overrides) project = self.args.project or Path(SETTINGS['runs_dir']) / self.args.task name = self.args.name or f'{self.args.mode}' self.save_dir = increment_path(Path(project) / name, exist_ok=self.args.exist_ok) if self.args.conf is None: self.args.conf = 0.25 # default conf=0.25 self.done_warmup = False if self.args.show: self.args.show = check_imshow(warn=True) # Usable if setup is done self.model = None self.data = self.args.data # data_dict self.imgsz = None self.device = None self.dataset = None self.vid_path, self.vid_writer = None, None self.plotted_img = None self.data_path = None self.source_type = None self.batch = None self.callbacks = _callbacks or callbacks.get_default_callbacks() callbacks.add_integration_callbacks(self) def preprocess(self, im): """Prepares input image before inference. Args: im (torch.Tensor | List(np.ndarray)): (N, 3, h, w) for tensor, [(h, w, 3) x N] for list. """ if not isinstance(im, torch.Tensor): im = np.stack(self.pre_transform(im)) #size(640,XX) im = im[..., ::-1].transpose((0, 3, 1, 2)) # BGR to RGB, BHWC to BCHW, (n, 3, h, w) im = np.ascontiguousarray(im) # contiguous im = torch.from_numpy(im) # NOTE: assuming im with (b, 3, h, w) if it's a tensor img = im.to(self.device) img = img.half() if self.model.fp16 else img.float() # uint8 to fp16/32 img /= 255 # 0 - 255 to 0.0 - 1.0 return img def pre_transform(self, im): """Pre-tranform input image before inference. Args: im (List(np.ndarray)): (N, 3, h, w) for tensor, [(h, w, 3) x N] for list. Return: A list of transformed imgs. """ same_shapes = all(x.shape == im[0].shape for x in im) auto = same_shapes and self.model.pt return [LetterBox(self.imgsz, auto=auto, stride=self.model.stride)(image=x) for x in im] def write_results(self, idx, results, batch): """Write inference results to a file or directory.""" p, im, _ = batch log_string = '' if len(im.shape) == 3: im = im[None] # expand for batch dim self.seen += 1 if self.source_type.webcam or self.source_type.from_img: # batch_size >= 1 log_string += f'{idx}: ' frame = self.dataset.count else: frame = getattr(self.dataset, 'frame', 0) self.data_path = p self.txt_path = str(self.save_dir / 'labels' / p.stem) + ('' if self.dataset.mode == 'image' else f'_{frame}') log_string += '%gx%g ' % im.shape[2:] # print string result = results[idx] # print('result', result) log_string += result.verbose() if self.args.save or self.args.show: # Add bbox to image plot_args = dict(line_width=self.args.line_width, boxes=self.args.boxes, conf=self.args.show_conf, labels=self.args.show_labels) if not self.args.retina_masks: plot_args['im_gpu'] = im[idx] self.plotted_img = result.plot(**plot_args) # Write if self.args.save_txt: result.save_txt(f'{self.txt_path}.txt', save_conf=self.args.save_conf) if self.args.save_crop: result.save_crop(save_dir=self.save_dir / 'crops', file_name=self.data_path.stem) return log_string def postprocess(self, preds, img, orig_img): """Post-processes predictions for an image and returns them.""" return preds def boxesMov_output(self, path, img_MovBoxes): return img_MovBoxes def __call__(self, source=None, model=None, stream=False): """Performs inference on an image or stream.""" self.stream = stream if stream: return self.stream_inference(source, model) else: # print('*****************',list(self.stream_inference(source, model))) return list(self.stream_inference(source, model)) # merge list of Result into one def predict_cli(self, source=None, model=None): """Method used for CLI prediction. It uses always generator as outputs as not required by CLI mode.""" gen = self.stream_inference(source, model) for _ in gen: # running CLI inference without accumulating any outputs (do not modify) pass def setup_source(self, source): """Sets up source and inference mode.""" self.imgsz = check_imgsz(self.args.imgsz, stride=self.model.stride, min_dim=2) # check image size self.transforms = getattr(self.model.model, 'transforms', classify_transforms( self.imgsz[0])) if self.args.task == 'classify' else None self.dataset = load_inference_source(source=source, imgsz=self.imgsz, vid_stride=self.args.vid_stride) self.dataset0 = load_inference_source(source=source, imgsz=self.imgsz, vid_stride=self.args.vid_stride) self.source_type = self.dataset.source_type if not getattr(self, 'stream', True) and (self.dataset.mode == 'stream' or # streams len(self.dataset) > 1000 or # images any(getattr(self.dataset, 'video_flag', [False]))): # videos LOGGER.warning(STREAM_WARNING) self.vid_path, self.vid_writer = [None] * self.dataset.bs, [None] * self.dataset.bs @smart_inference_mode() def stream_inference(self, source=None, model=None): """Streams real-time inference on camera feed and saves results to file.""" if self.args.verbose: LOGGER.info('') # Setup model if not self.model: self.setup_model(model) # Setup source every time predict is called self.setup_source(source if source is not None else self.args.source) # Check if save_dir/ label file exists if self.args.save or self.args.save_txt: (self.save_dir / 'labels' if self.args.save_txt else self.save_dir).mkdir(parents=True, exist_ok=True) # Warmup model if not self.done_warmup: self.model.warmup(imgsz=(1 if self.model.pt or self.model.triton else self.dataset.bs, 3, *self.imgsz)) self.done_warmup = True self.seen, self.windows, self.batch, self.batch0, profilers = 0, [], None, None, (ops.Profile(), ops.Profile(), ops.Profile()) self.run_callbacks('on_predict_start') allMovBoxes = [] boxesAll = [] id = 0 img_MovBoxes = [] idx = [] id0 = 0 fgbg = cv2.createBackgroundSubtractorMOG2(history=500, varThreshold=16, detectShadows=True) kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) for batch0 in self.dataset0: path, im0, vid_cap, s = batch0 id0 += 1 # video_len = int(str(s).split(')')[0].split('/')[-1]) # print('video_len', id0, video_len) img0 = np.array(im0).squeeze() frame = cv2.resize(img0, (512, 640), interpolation=cv2.INTER_CUBIC) frame = cv2.medianBlur(frame, ksize=3) # 计算前景掩码 fgmask = fgbg.apply(frame) draw1 = cv2.threshold(fgmask, 230, 255, cv2.THRESH_BINARY)[1] # 二值化 draw1 = cv2.erode(draw1, kernel, iterations=1) draw1 = cv2.dilate(draw1, kernel, iterations=1) dics = {} contours_m, hierarchy_m = cv2.findContours(draw1.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) for contour in contours_m: dics[len(contour)] = contour if len(dics.keys()) > 0: cc = sorted(dics.keys()) iouArea = cv2.contourArea(dics[cc[-1]]) print("***********id0", id0, iouArea) if iouArea>3000: idx.append(id0) # if len(idx) != 0 and abs(id0 - idx[-1]) > 30: # break # print('*******id0', id0, idx) if len(idx) != 0: idx.sort() print('idx', idx) for batch in self.dataset: t0 = time.time() id += 1 if idx[0] <= id <= idx[-1]: self.run_callbacks('on_predict_batch_start') self.batch = batch path, im0s, vid_cap, s = batch visualize = increment_path(self.save_dir / Path(path[0]).stem, mkdir=True) if self.args.visualize and (not self.source_type.tensor) else False # Preprocess with profilers[0]: im = self.preprocess(im0s) # Inference with profilers[1]: preds = self.model(im, augment=self.args.augment, visualize=visualize) # Postprocess with profilers[2]: self.results = self.postprocess(preds, im, im0s) self.run_callbacks('on_predict_postprocess_end') # Visualize, save, write results n = len(im0s) for i in range(n): tt0 = time.time() im1 = im0s[i].copy() ### create class MovingBox boxesMov = MovingBox(id, self.results[i].boxes.xywh, self.results[i].boxes.xyxy, self.results[i].boxes.conf, path) if id == idx[0]: boxesAll= boxesMov._initiate_box() else: ### allMovBoxes->是运动框的坐标 boxesAll, allMovBoxes = boxesMov.update(boxesAll, im1, self.save_dir) if len(allMovBoxes) != 0: mov_id = 0 for box in allMovBoxes: box_xyxy = xywh2xyxy(box[0]) cropMov = im0s[i][int(box_xyxy[1]):int(box_xyxy[3]), int(box_xyxy[0]):int(box_xyxy[2]), ::-1] # crop_save = os.path.abspath(im0s[i]) # Image.fromarray(cropMov).show() ### img_MovBoxes->运动商品图片 img_MovBoxes.append([id, cropMov]) tt1 = time.time() - tt0 # print('运动框判断所需时间{}s'.format(tt1)) crop_save = self.save_dir / 'movboxes/' ori_img = self.save_dir / 'oriImage/' if not os.path.exists(crop_save): # crop_save.parent.mkdir(parents=True, exist_ok=True) crop_save.mkdir(parents=True, exist_ok=True) if not os.path.exists(ori_img): ori_img.mkdir() f = str(crop_save) + '\\frame' + str(id) + '_' + str(mov_id) + '_' + '.jpg' g = str(ori_img) + '\\frame' + str(id) + '_' + '.jpg' # Image.fromarray(cropMov[::-1]).save(f, quality=95, subsampling=0) Image.fromarray(cropMov).save(f, quality=95, subsampling=0) # Image.fromarray(im1[:,:,::-1]).save(g, quality=95, subsampling=0) mov_id += 1 self.results[i].speed = { 'preprocess': profilers[0].dt * 1E3 / n, 'inference': profilers[1].dt * 1E3 / n, 'postprocess': profilers[2].dt * 1E3 / n} if self.source_type.tensor: # skip write, show and plot operations if input is raw tensor continue p, im0 = path[i], im0s[i].copy() p = Path(p) if self.args.verbose or self.args.save or self.args.save_txt or self.args.show: s += self.write_results(i, self.results, (p, im, im0)) if self.args.show and self.plotted_img is not None: self.show(p) if self.args.save and self.plotted_img is not None: self.save_preds(vid_cap, i, str(self.save_dir / p.name)) elif id < idx[0]: # self.results = [] continue else: break else: for batch in self.dataset: t0 = time.time() id += 1 self.run_callbacks('on_predict_batch_start') self.batch = batch path, im0s, vid_cap, s = batch visualize = increment_path(self.save_dir / Path(path[0]).stem, mkdir=True) if self.args.visualize and ( not self.source_type.tensor) else False # Preprocess with profilers[0]: im = self.preprocess(im0s) # Inference with profilers[1]: preds = self.model(im, augment=self.args.augment, visualize=visualize) # Postprocess with profilers[2]: self.results = self.postprocess(preds, im, im0s) self.run_callbacks('on_predict_postprocess_end') # Visualize, save, write results n = len(im0s) for i in range(n): tt0 = time.time() im1 = im0s[i].copy() ### create class MovingBox boxesMov = MovingBox(id, self.results[i].boxes.xywh, self.results[i].boxes.xyxy, self.results[i].boxes.conf, path) if id == 1: boxesAll = boxesMov._initiate_box() else: ### allMovBoxes->是运动框的坐标 boxesAll, allMovBoxes = boxesMov.update(boxesAll, im1, self.save_dir) if len(allMovBoxes) != 0: mov_id = 0 for box in allMovBoxes: box_xyxy = xywh2xyxy(box[0]) cropMov = im0s[i][int(box_xyxy[1]):int(box_xyxy[3]), int(box_xyxy[0]):int(box_xyxy[2]), ::-1] # crop_save = os.path.abspath(im0s[i]) # Image.fromarray(cropMov).show() ### img_MovBoxes->运动商品图片 img_MovBoxes.append([id, cropMov]) tt1 = time.time() - tt0 # print('运动框判断所需时间{}s'.format(tt1)) crop_save = self.save_dir / 'movboxes/' ori_img = self.save_dir / 'oriImage/' if not os.path.exists(crop_save): # crop_save.parent.mkdir(parents=True, exist_ok=True) crop_save.mkdir(parents=True, exist_ok=True) if not os.path.exists(ori_img): ori_img.mkdir() f = str(crop_save) + '\\frame' + str(id) + '_' + str(mov_id) + '_' + '.jpg' g = str(ori_img) + '\\frame' + str(id) + '_' + '.jpg' # Image.fromarray(cropMov[::-1]).save(f, quality=95, subsampling=0) Image.fromarray(cropMov).save(f, quality=95, subsampling=0) # Image.fromarray(im1[:,:,::-1]).save(g, quality=95, subsampling=0) mov_id += 1 self.results[i].speed = { 'preprocess': profilers[0].dt * 1E3 / n, 'inference': profilers[1].dt * 1E3 / n, 'postprocess': profilers[2].dt * 1E3 / n} if self.source_type.tensor: # skip write, show and plot operations if input is raw tensor continue p, im0 = path[i], im0s[i].copy() p = Path(p) if self.args.verbose or self.args.save or self.args.save_txt or self.args.show: s += self.write_results(i, self.results, (p, im, im0)) if self.args.show and self.plotted_img is not None: self.show(p) if self.args.save and self.plotted_img is not None: self.save_preds(vid_cap, i, str(self.save_dir / p.name)) self.run_callbacks('on_predict_batch_end') yield from self.results # Print time (inference-only) if self.args.verbose: LOGGER.info(f'{s}{profilers[1].dt * 1E3:.1f}ms') t1 = time.time() - t0 # print('推理所需时间:{}s'.format(t1)) t2 = time.time() self.real_MovBox = self.boxesMov_output(path, img_MovBoxes) for i in range(len(self.results)): self.results[i].movBoxes = self.real_MovBox # print("self.results11111111111", self.results[i]) t3 = time.time() - t2 print('保留最后十帧所有运动框所需时间:{}s'.format(t3)) # Release assets if isinstance(self.vid_writer[-1], cv2.VideoWriter): self.vid_writer[-1].release() # release final video writer # Print results if self.args.verbose and self.seen: t = tuple(x.t / self.seen * 1E3 for x in profilers) # speeds per image LOGGER.info(f'Speed: %.1fms preprocess, %.1fms inference, %.1fms postprocess per image at shape ' f'{(1, 3, *self.imgsz)}' % t) if self.args.save or self.args.save_txt or self.args.save_crop: nl = len(list(self.save_dir.glob('labels/*.txt'))) # number of labels s = f"\n{nl} label{'s' * (nl > 1)} saved to {self.save_dir / 'labels'}" if self.args.save_txt else '' LOGGER.info(f"Results saved to {colorstr('bold', self.save_dir)}{s}") self.run_callbacks('on_predict_end') def setup_model(self, model, verbose=True): """Initialize YOLO model with given parameters and set it to evaluation mode.""" device = select_device(self.args.device, verbose=verbose) model = model or self.args.model self.args.half &= device.type != 'cpu' # half precision only supported on CUDA self.model = AutoBackend(model, device=device, dnn=self.args.dnn, data=self.args.data, fp16=self.args.half, fuse=True, verbose=verbose) self.device = device self.model.eval() def show(self, p): """Display an image in a window using OpenCV imshow().""" im0 = self.plotted_img if platform.system() == 'Linux' and p not in self.windows: self.windows.append(p) cv2.namedWindow(str(p), cv2.WINDOW_NORMAL | cv2.WINDOW_KEEPRATIO) # allow window resize (Linux) cv2.resizeWindow(str(p), im0.shape[1], im0.shape[0]) cv2.imshow(str(p), im0) cv2.waitKey(500 if self.batch[3].startswith('image') else 1) # 1 millisecond def save_preds(self, vid_cap, idx, save_path): """Save video predictions as mp4 at specified path.""" im0 = self.plotted_img # Save imgs if self.dataset.mode == 'image': cv2.imwrite(save_path, im0) else: # 'video' or 'stream' if self.vid_path[idx] != save_path: # new video self.vid_path[idx] = save_path if isinstance(self.vid_writer[idx], cv2.VideoWriter): self.vid_writer[idx].release() # release previous video writer if vid_cap: # video fps = int(vid_cap.get(cv2.CAP_PROP_FPS)) # integer required, floats produce error in MP4 codec w = int(vid_cap.get(cv2.CAP_PROP_FRAME_WIDTH)) h = int(vid_cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) else: # stream fps, w, h = 30, im0.shape[1], im0.shape[0] save_path = str(Path(save_path).with_suffix('.mp4')) # force *.mp4 suffix on results videos self.vid_writer[idx] = cv2.VideoWriter(save_path, cv2.VideoWriter_fourcc(*'mp4v'), fps, (w, h)) self.vid_writer[idx].write(im0) def run_callbacks(self, event: str): """Runs all registered callbacks for a specific event.""" for callback in self.callbacks.get(event, []): callback(self) def add_callback(self, event: str, func): """ Add callback """ self.callbacks[event].append(func) class BoxState: Static = 0 Moving = 1 class allBoxes: def __init__(self, fame_id, tr_id, xywh): self.fame_id = fame_id self.state = BoxState.Static self.track_id = tr_id self.xywh = xywh def is_static(self): return self.state == BoxState.Static def is_moving(self): return self.state == BoxState.Moving def xywh2xyxy(x): # Convert nx4 boxes from [x, y, w, h] to [x1, y1, x2, y2] where xy1=top-left, xy2=bottom-right y = x.clone() if isinstance(x, torch.Tensor) else np.copy(x) y[0] = x[0] - x[2] / 2 # top left x y[1] = x[1] - x[3] / 2 # top left y y[2] = x[0] + x[2] / 2 # bottom right x y[3] = x[1] + x[3] / 2 # bottom right y return y def plot_one_box(x, img, trid, line_thickness=3,color=None): # Plots one bounding box on image img tl = line_thickness or round(0.002 * (img.shape[0] + img.shape[1]) / 2) + 1 # line/font thickness # print('x',x) c1, c2 = (int(x[0]), int(x[1])), (int(x[2]), int(x[3])) cv2.rectangle(img, c1, c2, color, thickness=tl, lineType=cv2.LINE_AA) cv2.putText(img, str(trid), (c1[0], c1[1] - 2), 0, tl / 3, [0, 0, 255], thickness=6, lineType=cv2.LINE_AA) def compute_iou(box1, box2): xx1 = max(box1[0], box2[0]) yy1 = max(box1[1], box2[1]) xx2 = min(box1[2], box2[2]) yy2 = min(box1[3], box2[3]) w = max(0., xx2 - xx1) h = max(0., yy2 - yy1) wh = w * h o = wh / ((box1[2] - box1[0]) * (box1[3] - box1[1]) + (box2[2] - box2[0]) * (box2[3] - box2[1]) - wh) return o # def sort_(elem): # return elem[0] class MovingBox(object): def __init__(self, frame_id, xywh, xyxy, conf, path): # self.track_id = 0 self.frame_id = frame_id # self.state = 1 # 0 static, 1 moving self.xywh = xywh self.conf = conf self.xyxy = xyxy self.min_thr = 0.8 self.max_thr = 1.2 self.iou_thr = 0.9 self.path = path def _initiate_box(self): boxes = [] state = 0 for i in range(len(self.xywh)): boxes.append([self.xywh[i], state, self.xyxy[i]]) # print('moving_box', boxes) return boxes def update(self, boxes, img, save_dir): index = [] allBoxes = [] state = 1 print(">>>>>>>>>>>>id", self.frame_id) # if len(self.xywh) != 0: ##detect box 不为空 if len(boxes) == 0: for i in range(len(self.xywh)): allBoxes.append([self.xywh[i], state, self.xyxy[i]]) else: for i in range(len(self.xywh)): ## self.xywh为当前帧的检测框 flag0 = 0 # 0 长宽比未匹配上, 1 匹配上 for j in range(len(boxes)): ## boxes为前一帧的检测框 rw = self.xywh[i][2] / boxes[j][0][2] rh = self.xywh[i][3] / boxes[j][0][3] area0 = boxes[j][0][2] * boxes[j][0][3] area1 = self.xywh[i][2] * self.xywh[i][3] area_r = area1 / area0 # print(f'detect_{i} track_{j}') # print("rw,rh,area_r", rw, rh, area_r) ##可能短边比长边,可能长边比短边 if (rw < self.max_thr and rw > self.min_thr) and (rh < self.max_thr and rh > self.min_thr) \ and (area_r < self.max_thr and area_r > self.min_thr): flag0 = 1 iou = compute_iou(self.xyxy[i], boxes[j][2]) print('iou',iou) if iou > self.iou_thr: index.append(i) break print("=====last_box", boxes) print('index', index) for i in range(len(self.xywh)): if i in index: state = 0 allBoxes.append([self.xywh[i], state, self.xyxy[i]]) else: state = 1 allBoxes.append([self.xywh[i], state, self.xyxy[i]]) # track_id += 1 moving_boxes = [box for box in allBoxes if box[1]==1] if len(self.xywh) != 0: for i, x in enumerate(allBoxes): xyxy = xywh2xyxy(x[0]) if x[1] == 1: # flag = str(i) + '_moving_' flag = 'moving_' box_color = (0,255,0) else: # flag = str(i) + '_static_' flag = 'static_' box_color = (0,0,255) plot_one_box(xyxy, img, f'{flag}{self.conf[i].cpu().numpy():.2f}', color=box_color) path2 = save_dir / 'detect_result/' if not os.path.exists(path2): path2.mkdir(parents=True, exist_ok=True) cv2.imwrite(os.sep.join([str(path2), str(self.path).split('.mp4')[0].split('\\')[-1]+ "_" + str(self.frame_id) + '.jpg']), img) else: path2 = save_dir / 'detect_result/' if not os.path.exists(path2): path2.mkdir(parents=True, exist_ok=True) cv2.imwrite(os.sep.join([str(path2), str(self.path).split('.mp4')[0].split('\\')[-1] + "_" + str(self.frame_id) + '.jpg']), img) return allBoxes, moving_boxes