Skip to content

Reference for ultralytics/models/yolo/detect/val.py

Note

This file is available at https://github.com/ultralytics/ultralytics/blob/main/ultralytics/models/yolo/detect/val.py. If you spot a problem please help fix it by contributing a Pull Request 🛠️. Thank you 🙏!


ultralytics.models.yolo.detect.val.DetectionValidator

DetectionValidator(dataloader=None, save_dir=None, pbar=None, args=None, _callbacks=None)

Bases: BaseValidator

A class extending the BaseValidator class for validation based on a detection model.

Example
from ultralytics.models.yolo.detect import DetectionValidator

args = dict(model='yolov8n.pt', data='coco8.yaml')
validator = DetectionValidator(args=args)
validator()
Source code in ultralytics/models/yolo/detect/val.py
def __init__(self, dataloader=None, save_dir=None, pbar=None, args=None, _callbacks=None):
    """Initialize detection model with necessary variables and settings."""
    super().__init__(dataloader, save_dir, pbar, args, _callbacks)
    self.nt_per_class = None
    self.nt_per_image = None
    self.is_coco = False
    self.is_lvis = False
    self.class_map = None
    self.args.task = "detect"
    self.metrics = DetMetrics(save_dir=self.save_dir, on_plot=self.on_plot)
    self.iouv = torch.linspace(0.5, 0.95, 10)  # IoU vector for mAP@0.5:0.95
    self.niou = self.iouv.numel()
    self.lb = []  # for autolabelling
    if self.args.save_hybrid:
        LOGGER.warning(
            "WARNING ⚠️ 'save_hybrid=True' will append ground truth to predictions for autolabelling.\n"
            "WARNING ⚠️ 'save_hybrid=True' will cause incorrect mAP.\n"
        )

build_dataset

build_dataset(img_path, mode='val', batch=None)

Build YOLO Dataset.

Parameters:

Name Type Description Default
img_path str

Path to the folder containing images.

required
mode str

train mode or val mode, users are able to customize different augmentations for each mode.

'val'
batch int

Size of batches, this is for rect. Defaults to None.

None
Source code in ultralytics/models/yolo/detect/val.py
def build_dataset(self, img_path, mode="val", batch=None):
    """
    Build YOLO Dataset.

    Args:
        img_path (str): Path to the folder containing images.
        mode (str): `train` mode or `val` mode, users are able to customize different augmentations for each mode.
        batch (int, optional): Size of batches, this is for `rect`. Defaults to None.
    """
    return build_yolo_dataset(self.args, img_path, batch, self.data, mode=mode, stride=self.stride)

eval_json

eval_json(stats)

Evaluates YOLO output in JSON format and returns performance statistics.

Source code in ultralytics/models/yolo/detect/val.py
def eval_json(self, stats):
    """Evaluates YOLO output in JSON format and returns performance statistics."""
    if self.args.save_json and (self.is_coco or self.is_lvis) and len(self.jdict):
        pred_json = self.save_dir / "predictions.json"  # predictions
        anno_json = (
            self.data["path"]
            / "annotations"
            / ("instances_val2017.json" if self.is_coco else f"lvis_v1_{self.args.split}.json")
        )  # annotations
        pkg = "pycocotools" if self.is_coco else "lvis"
        LOGGER.info(f"\nEvaluating {pkg} mAP using {pred_json} and {anno_json}...")
        try:  # https://github.com/cocodataset/cocoapi/blob/master/PythonAPI/pycocoEvalDemo.ipynb
            for x in pred_json, anno_json:
                assert x.is_file(), f"{x} file not found"
            check_requirements("pycocotools>=2.0.6" if self.is_coco else "lvis>=0.5.3")
            if self.is_coco:
                from pycocotools.coco import COCO  # noqa
                from pycocotools.cocoeval import COCOeval  # noqa

                anno = COCO(str(anno_json))  # init annotations api
                pred = anno.loadRes(str(pred_json))  # init predictions api (must pass string, not Path)
                val = COCOeval(anno, pred, "bbox")
            else:
                from lvis import LVIS, LVISEval

                anno = LVIS(str(anno_json))  # init annotations api
                pred = anno._load_json(str(pred_json))  # init predictions api (must pass string, not Path)
                val = LVISEval(anno, pred, "bbox")
            val.params.imgIds = [int(Path(x).stem) for x in self.dataloader.dataset.im_files]  # images to eval
            val.evaluate()
            val.accumulate()
            val.summarize()
            if self.is_lvis:
                val.print_results()  # explicitly call print_results
            # update mAP50-95 and mAP50
            stats[self.metrics.keys[-1]], stats[self.metrics.keys[-2]] = (
                val.stats[:2] if self.is_coco else [val.results["AP50"], val.results["AP"]]
            )
        except Exception as e:
            LOGGER.warning(f"{pkg} unable to run: {e}")
    return stats

finalize_metrics

finalize_metrics(*args, **kwargs)

Set final values for metrics speed and confusion matrix.

Source code in ultralytics/models/yolo/detect/val.py
def finalize_metrics(self, *args, **kwargs):
    """Set final values for metrics speed and confusion matrix."""
    self.metrics.speed = self.speed
    self.metrics.confusion_matrix = self.confusion_matrix

get_dataloader

get_dataloader(dataset_path, batch_size)

Construct and return dataloader.

Source code in ultralytics/models/yolo/detect/val.py
def get_dataloader(self, dataset_path, batch_size):
    """Construct and return dataloader."""
    dataset = self.build_dataset(dataset_path, batch=batch_size, mode="val")
    return build_dataloader(dataset, batch_size, self.args.workers, shuffle=False, rank=-1)  # return dataloader

get_desc

get_desc()

Return a formatted string summarizing class metrics of YOLO model.

Source code in ultralytics/models/yolo/detect/val.py
def get_desc(self):
    """Return a formatted string summarizing class metrics of YOLO model."""
    return ("%22s" + "%11s" * 6) % ("Class", "Images", "Instances", "Box(P", "R", "mAP50", "mAP50-95)")

get_stats

get_stats()

Returns metrics statistics and results dictionary.

Source code in ultralytics/models/yolo/detect/val.py
def get_stats(self):
    """Returns metrics statistics and results dictionary."""
    stats = {k: torch.cat(v, 0).cpu().numpy() for k, v in self.stats.items()}  # to numpy
    self.nt_per_class = np.bincount(stats["target_cls"].astype(int), minlength=self.nc)
    self.nt_per_image = np.bincount(stats["target_img"].astype(int), minlength=self.nc)
    stats.pop("target_img", None)
    if len(stats) and stats["tp"].any():
        self.metrics.process(**stats)
    return self.metrics.results_dict

init_metrics

init_metrics(model)

Initialize evaluation metrics for YOLO.

Source code in ultralytics/models/yolo/detect/val.py
def init_metrics(self, model):
    """Initialize evaluation metrics for YOLO."""
    val = self.data.get(self.args.split, "")  # validation path
    self.is_coco = (
        isinstance(val, str)
        and "coco" in val
        and (val.endswith(f"{os.sep}val2017.txt") or val.endswith(f"{os.sep}test-dev2017.txt"))
    )  # is COCO
    self.is_lvis = isinstance(val, str) and "lvis" in val and not self.is_coco  # is LVIS
    self.class_map = converter.coco80_to_coco91_class() if self.is_coco else list(range(len(model.names)))
    self.args.save_json |= (self.is_coco or self.is_lvis) and not self.training  # run on final val if training COCO
    self.names = model.names
    self.nc = len(model.names)
    self.metrics.names = self.names
    self.metrics.plot = self.args.plots
    self.confusion_matrix = ConfusionMatrix(nc=self.nc, conf=self.args.conf)
    self.seen = 0
    self.jdict = []
    self.stats = dict(tp=[], conf=[], pred_cls=[], target_cls=[], target_img=[])

plot_predictions

plot_predictions(batch, preds, ni)

Plots predicted bounding boxes on input images and saves the result.

Source code in ultralytics/models/yolo/detect/val.py
def plot_predictions(self, batch, preds, ni):
    """Plots predicted bounding boxes on input images and saves the result."""
    plot_images(
        batch["img"],
        *output_to_target(preds, max_det=self.args.max_det),
        paths=batch["im_file"],
        fname=self.save_dir / f"val_batch{ni}_pred.jpg",
        names=self.names,
        on_plot=self.on_plot,
    )  # pred

plot_val_samples

plot_val_samples(batch, ni)

Plot validation image samples.

Source code in ultralytics/models/yolo/detect/val.py
def plot_val_samples(self, batch, ni):
    """Plot validation image samples."""
    plot_images(
        batch["img"],
        batch["batch_idx"],
        batch["cls"].squeeze(-1),
        batch["bboxes"],
        paths=batch["im_file"],
        fname=self.save_dir / f"val_batch{ni}_labels.jpg",
        names=self.names,
        on_plot=self.on_plot,
    )

postprocess

postprocess(preds)

Apply Non-maximum suppression to prediction outputs.

Source code in ultralytics/models/yolo/detect/val.py
def postprocess(self, preds):
    """Apply Non-maximum suppression to prediction outputs."""
    return ops.non_max_suppression(
        preds,
        self.args.conf,
        self.args.iou,
        labels=self.lb,
        multi_label=True,
        agnostic=self.args.single_cls or self.args.agnostic_nms,
        max_det=self.args.max_det,
    )

pred_to_json

pred_to_json(predn, filename)

Serialize YOLO predictions to COCO json format.

Source code in ultralytics/models/yolo/detect/val.py
def pred_to_json(self, predn, filename):
    """Serialize YOLO predictions to COCO json format."""
    stem = Path(filename).stem
    image_id = int(stem) if stem.isnumeric() else stem
    box = ops.xyxy2xywh(predn[:, :4])  # xywh
    box[:, :2] -= box[:, 2:] / 2  # xy center to top-left corner
    for p, b in zip(predn.tolist(), box.tolist()):
        self.jdict.append(
            {
                "image_id": image_id,
                "category_id": self.class_map[int(p[5])]
                + (1 if self.is_lvis else 0),  # index starts from 1 if it's lvis
                "bbox": [round(x, 3) for x in b],
                "score": round(p[4], 5),
            }
        )

preprocess

preprocess(batch)

Preprocesses batch of images for YOLO training.

Source code in ultralytics/models/yolo/detect/val.py
def preprocess(self, batch):
    """Preprocesses batch of images for YOLO training."""
    batch["img"] = batch["img"].to(self.device, non_blocking=True)
    batch["img"] = (batch["img"].half() if self.args.half else batch["img"].float()) / 255
    for k in ["batch_idx", "cls", "bboxes"]:
        batch[k] = batch[k].to(self.device)

    if self.args.save_hybrid:
        height, width = batch["img"].shape[2:]
        nb = len(batch["img"])
        bboxes = batch["bboxes"] * torch.tensor((width, height, width, height), device=self.device)
        self.lb = [
            torch.cat([batch["cls"][batch["batch_idx"] == i], bboxes[batch["batch_idx"] == i]], dim=-1)
            for i in range(nb)
        ]

    return batch

print_results

print_results()

Prints training/validation set metrics per class.

Source code in ultralytics/models/yolo/detect/val.py
def print_results(self):
    """Prints training/validation set metrics per class."""
    pf = "%22s" + "%11i" * 2 + "%11.3g" * len(self.metrics.keys)  # print format
    LOGGER.info(pf % ("all", self.seen, self.nt_per_class.sum(), *self.metrics.mean_results()))
    if self.nt_per_class.sum() == 0:
        LOGGER.warning(f"WARNING ⚠️ no labels found in {self.args.task} set, can not compute metrics without labels")

    # Print results per class
    if self.args.verbose and not self.training and self.nc > 1 and len(self.stats):
        for i, c in enumerate(self.metrics.ap_class_index):
            LOGGER.info(
                pf % (self.names[c], self.nt_per_image[c], self.nt_per_class[c], *self.metrics.class_result(i))
            )

    if self.args.plots:
        for normalize in True, False:
            self.confusion_matrix.plot(
                save_dir=self.save_dir, names=self.names.values(), normalize=normalize, on_plot=self.on_plot
            )

save_one_txt

save_one_txt(predn, save_conf, shape, file)

Save YOLO detections to a txt file in normalized coordinates in a specific format.

Source code in ultralytics/models/yolo/detect/val.py
def save_one_txt(self, predn, save_conf, shape, file):
    """Save YOLO detections to a txt file in normalized coordinates in a specific format."""
    from ultralytics.engine.results import Results

    Results(
        np.zeros((shape[0], shape[1]), dtype=np.uint8),
        path=None,
        names=self.names,
        boxes=predn[:, :6],
    ).save_txt(file, save_conf=save_conf)

update_metrics

update_metrics(preds, batch)

Metrics.

Source code in ultralytics/models/yolo/detect/val.py
def update_metrics(self, preds, batch):
    """Metrics."""
    for si, pred in enumerate(preds):
        self.seen += 1
        npr = len(pred)
        stat = dict(
            conf=torch.zeros(0, device=self.device),
            pred_cls=torch.zeros(0, device=self.device),
            tp=torch.zeros(npr, self.niou, dtype=torch.bool, device=self.device),
        )
        pbatch = self._prepare_batch(si, batch)
        cls, bbox = pbatch.pop("cls"), pbatch.pop("bbox")
        nl = len(cls)
        stat["target_cls"] = cls
        stat["target_img"] = cls.unique()
        if npr == 0:
            if nl:
                for k in self.stats.keys():
                    self.stats[k].append(stat[k])
                if self.args.plots:
                    self.confusion_matrix.process_batch(detections=None, gt_bboxes=bbox, gt_cls=cls)
            continue

        # Predictions
        if self.args.single_cls:
            pred[:, 5] = 0
        predn = self._prepare_pred(pred, pbatch)
        stat["conf"] = predn[:, 4]
        stat["pred_cls"] = predn[:, 5]

        # Evaluate
        if nl:
            stat["tp"] = self._process_batch(predn, bbox, cls)
            if self.args.plots:
                self.confusion_matrix.process_batch(predn, bbox, cls)
        for k in self.stats.keys():
            self.stats[k].append(stat[k])

        # Save
        if self.args.save_json:
            self.pred_to_json(predn, batch["im_file"][si])
        if self.args.save_txt:
            self.save_one_txt(
                predn,
                self.args.save_conf,
                pbatch["ori_shape"],
                self.save_dir / "labels" / f'{Path(batch["im_file"][si]).stem}.txt',
            )





Created 2023-11-12, Updated 2024-07-21
Authors: glenn-jocher (6), Burhan-Q (1)