New YOLOv8 Results() class for prediction outputs (#314)
Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Glenn Jocher <glenn.jocher@ultralytics.com> Co-authored-by: Laughing-q <1185102784@qq.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Laughing <61612323+Laughing-q@users.noreply.github.com> Co-authored-by: Viet Nhat Thai <60825385+vietnhatthai@users.noreply.github.com> Co-authored-by: Paula Derrenger <107626595+pderrenger@users.noreply.github.com>
This commit is contained in:
parent
0cb87f7dd3
commit
c6985da9de
32 changed files with 813 additions and 259 deletions
|
|
@ -27,13 +27,14 @@ Usage - formats:
|
|||
"""
|
||||
import platform
|
||||
from collections import defaultdict
|
||||
from itertools import chain
|
||||
from pathlib import Path
|
||||
|
||||
import cv2
|
||||
|
||||
from ultralytics.nn.autobackend import AutoBackend
|
||||
from ultralytics.yolo.configs import get_config
|
||||
from ultralytics.yolo.data.dataloaders.stream_loaders import LoadImages, LoadScreenshots, LoadStreams
|
||||
from ultralytics.yolo.data.dataloaders.stream_loaders import LoadImages, LoadPilAndNumpy, LoadScreenshots, LoadStreams
|
||||
from ultralytics.yolo.data.utils import IMG_FORMATS, VID_FORMATS
|
||||
from ultralytics.yolo.utils import DEFAULT_CONFIG, LOGGER, SETTINGS, callbacks, colorstr, ops
|
||||
from ultralytics.yolo.utils.checks import check_file, check_imgsz, check_imshow
|
||||
|
|
@ -89,7 +90,6 @@ class BasePredictor:
|
|||
self.vid_path, self.vid_writer = None, None
|
||||
self.annotator = None
|
||||
self.data_path = None
|
||||
self.output = {}
|
||||
self.callbacks = defaultdict(list, {k: [v] for k, v in callbacks.default_callbacks.items()}) # add callbacks
|
||||
callbacks.add_integration_callbacks(self)
|
||||
|
||||
|
|
@ -99,29 +99,18 @@ class BasePredictor:
|
|||
def get_annotator(self, img):
|
||||
raise NotImplementedError("get_annotator function needs to be implemented")
|
||||
|
||||
def write_results(self, pred, batch, print_string):
|
||||
def write_results(self, results, batch, print_string):
|
||||
raise NotImplementedError("print_results function needs to be implemented")
|
||||
|
||||
def postprocess(self, preds, img, orig_img):
|
||||
return preds
|
||||
|
||||
def setup(self, source=None, model=None, return_outputs=False):
|
||||
def setup(self, source=None, model=None):
|
||||
# source
|
||||
source = str(source if source is not None else self.args.source)
|
||||
is_file = Path(source).suffix[1:] in (IMG_FORMATS + VID_FORMATS)
|
||||
is_url = source.lower().startswith(('rtsp://', 'rtmp://', 'http://', 'https://'))
|
||||
webcam = source.isnumeric() or source.endswith('.streams') or (is_url and not is_file)
|
||||
screenshot = source.lower().startswith('screen')
|
||||
if is_url and is_file:
|
||||
source = check_file(source) # download
|
||||
|
||||
source, webcam, screenshot, from_img = self.check_source(source)
|
||||
# model
|
||||
device = select_device(self.args.device)
|
||||
model = model or self.args.model
|
||||
self.args.half &= device.type != 'cpu' # half precision only supported on CUDA
|
||||
model = AutoBackend(model, device=device, dnn=self.args.dnn, fp16=self.args.half)
|
||||
stride, pt = model.stride, model.pt
|
||||
imgsz = check_imgsz(self.args.imgsz, stride=stride) # check image size
|
||||
stride, pt = self.setup_model(model)
|
||||
imgsz = check_imgsz(self.args.imgsz, stride=stride, min_dim=2) # check image size
|
||||
|
||||
# Dataloader
|
||||
bs = 1 # batch_size
|
||||
|
|
@ -131,7 +120,7 @@ class BasePredictor:
|
|||
imgsz=imgsz,
|
||||
stride=stride,
|
||||
auto=pt,
|
||||
transforms=getattr(model.model, 'transforms', None),
|
||||
transforms=getattr(self.model.model, 'transforms', None),
|
||||
vid_stride=self.args.vid_stride)
|
||||
bs = len(self.dataset)
|
||||
elif screenshot:
|
||||
|
|
@ -139,32 +128,47 @@ class BasePredictor:
|
|||
imgsz=imgsz,
|
||||
stride=stride,
|
||||
auto=pt,
|
||||
transforms=getattr(model.model, 'transforms', None))
|
||||
transforms=getattr(self.model.model, 'transforms', None))
|
||||
elif from_img:
|
||||
self.dataset = LoadPilAndNumpy(source,
|
||||
imgsz=imgsz,
|
||||
stride=stride,
|
||||
auto=pt,
|
||||
transforms=getattr(self.model.model, 'transforms', None))
|
||||
else:
|
||||
self.dataset = LoadImages(source,
|
||||
imgsz=imgsz,
|
||||
stride=stride,
|
||||
auto=pt,
|
||||
transforms=getattr(model.model, 'transforms', None),
|
||||
transforms=getattr(self.model.model, 'transforms', None),
|
||||
vid_stride=self.args.vid_stride)
|
||||
self.vid_path, self.vid_writer = [None] * bs, [None] * bs
|
||||
model.warmup(imgsz=(1 if pt or model.triton else bs, 3, *imgsz)) # warmup
|
||||
self.model.warmup(imgsz=(1 if pt or self.model.triton else bs, 3, *imgsz)) # warmup
|
||||
|
||||
self.model = model
|
||||
self.webcam = webcam
|
||||
self.screenshot = screenshot
|
||||
self.from_img = from_img
|
||||
self.imgsz = imgsz
|
||||
self.done_setup = True
|
||||
self.device = device
|
||||
self.return_outputs = return_outputs
|
||||
|
||||
return model
|
||||
|
||||
@smart_inference_mode()
|
||||
def __call__(self, source=None, model=None, return_outputs=False):
|
||||
def __call__(self, source=None, model=None, verbose=False, stream=False):
|
||||
if stream:
|
||||
return self.stream_inference(source, model, verbose)
|
||||
else:
|
||||
return list(chain(*list(self.stream_inference(source, model, verbose)))) # merge list of Result into one
|
||||
|
||||
def predict_cli(self):
|
||||
# Method used for cli prediction. It uses always generator as outputs as not required by cli mode
|
||||
gen = self.stream_inference(verbose=True)
|
||||
for _ in gen: # running CLI inference without accumulating any outputs (do not modify)
|
||||
pass
|
||||
|
||||
def stream_inference(self, source=None, model=None, verbose=False):
|
||||
self.run_callbacks("on_predict_start")
|
||||
model = self.model if self.done_setup else self.setup(source, model, return_outputs)
|
||||
model.eval()
|
||||
if not self.done_setup:
|
||||
self.setup(source, model)
|
||||
self.seen, self.windows, self.dt = 0, [], (ops.Profile(), ops.Profile(), ops.Profile())
|
||||
for batch in self.dataset:
|
||||
self.run_callbacks("on_predict_batch_start")
|
||||
|
|
@ -177,17 +181,17 @@ class BasePredictor:
|
|||
|
||||
# Inference
|
||||
with self.dt[1]:
|
||||
preds = model(im, augment=self.args.augment, visualize=visualize)
|
||||
preds = self.model(im, augment=self.args.augment, visualize=visualize)
|
||||
|
||||
# postprocess
|
||||
with self.dt[2]:
|
||||
preds = self.postprocess(preds, im, im0s)
|
||||
|
||||
results = self.postprocess(preds, im, im0s)
|
||||
for i in range(len(im)):
|
||||
if self.webcam:
|
||||
path, im0s = path[i], im0s[i]
|
||||
p = Path(path)
|
||||
s += self.write_results(i, preds, (p, im, im0s))
|
||||
p, im0 = (path[i], im0s[i]) if self.webcam or self.from_img else (path, im0s)
|
||||
p = Path(p)
|
||||
|
||||
if verbose or self.args.save or self.args.save_txt:
|
||||
s += self.write_results(i, results, (p, im, im0))
|
||||
|
||||
if self.args.show:
|
||||
self.show(p)
|
||||
|
|
@ -195,30 +199,50 @@ class BasePredictor:
|
|||
if self.args.save:
|
||||
self.save_preds(vid_cap, i, str(self.save_dir / p.name))
|
||||
|
||||
if self.return_outputs:
|
||||
yield self.output
|
||||
self.output.clear()
|
||||
yield results
|
||||
|
||||
# Print time (inference-only)
|
||||
LOGGER.info(f"{s}{'' if len(preds) else '(no detections), '}{self.dt[1].dt * 1E3:.1f}ms")
|
||||
if verbose:
|
||||
LOGGER.info(f"{s}{'' if len(preds) else '(no detections), '}{self.dt[1].dt * 1E3:.1f}ms")
|
||||
|
||||
self.run_callbacks("on_predict_batch_end")
|
||||
|
||||
# Print results
|
||||
t = tuple(x.t / self.seen * 1E3 for x in self.dt) # speeds per image
|
||||
LOGGER.info(
|
||||
f'Speed: %.1fms pre-process, %.1fms inference, %.1fms postprocess per image at shape {(1, 3, *self.imgsz)}'
|
||||
% t)
|
||||
if verbose:
|
||||
t = tuple(x.t / self.seen * 1E3 for x in self.dt) # speeds per image
|
||||
LOGGER.info(f'Speed: %.1fms pre-process, %.1fms inference, %.1fms postprocess per image at shape '
|
||||
f'{(1, 3, *self.imgsz)}' % t)
|
||||
if self.args.save_txt or self.args.save:
|
||||
s = f"\n{len(list(self.save_dir.glob('labels/*.txt')))} labels saved to {self.save_dir / 'labels'}" if self.args.save_txt else ''
|
||||
s = f"\n{len(list(self.save_dir.glob('labels/*.txt')))} labels 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 predict_cli(self, source=None, model=None, return_outputs=False):
|
||||
# as __call__ is a generator now so have to treat it like a generator
|
||||
for _ in (self.__call__(source, model, return_outputs)):
|
||||
pass
|
||||
def setup_model(self, model):
|
||||
device = select_device(self.args.device)
|
||||
model = model or self.args.model
|
||||
self.args.half &= device.type != 'cpu' # half precision only supported on CUDA
|
||||
model = AutoBackend(model, device=device, dnn=self.args.dnn, fp16=self.args.half)
|
||||
self.model = model
|
||||
self.device = device
|
||||
self.model.eval()
|
||||
return model.stride, model.pt
|
||||
|
||||
def check_source(self, source):
|
||||
source = source if source is not None else self.args.source
|
||||
webcam, screenshot, from_img = False, False, False
|
||||
if isinstance(source, (str, int, Path)): # int for local usb carame
|
||||
source = str(source)
|
||||
is_file = Path(source).suffix[1:] in (IMG_FORMATS + VID_FORMATS)
|
||||
is_url = source.lower().startswith(('rtsp://', 'rtmp://', 'http://', 'https://'))
|
||||
webcam = source.isnumeric() or source.endswith('.streams') or (is_url and not is_file)
|
||||
screenshot = source.lower().startswith('screen')
|
||||
if is_url and is_file:
|
||||
source = check_file(source) # download
|
||||
else:
|
||||
from_img = True
|
||||
return source, webcam, screenshot, from_img
|
||||
|
||||
def show(self, p):
|
||||
im0 = self.annotator.result()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue