YOLOv5-seg中推理部分predict.py代码功能介绍与删减封装

 一、任务介绍

        需要将yolo-seg和ros2结合起来,所以要将yolov5-seg中的predict.py预测部分封装成类,并且只保留主体功能部分,把不需要的部分都删除。

二、关键代码介绍

        1.推理准备

device = select_device(self.device)
model = DetectMultiBackend(self.weights, device=self.device, dnn=self.dnn, data=self.data, fp16=self.half)
stride, names, pt = model.stride, model.names, model.pt
imgsz = check_img_size(self.imgsz, s=stride)
bs = 1  # batch_size
model.warmup(imgsz=(1 if pt else bs, 3, *imgsz))  # warmup

         select_device:选择使用的设备,有CUDA用CUDA,没有就用cpu.

        DetectMultibackend:pytorch模型类,用于在不同的推理后端上进行目标检测,对于不同的推理后端有不同的加载和初始化逻辑,还有对前向推理的方法。这里的用法是调用其构造函数创建对象。

        check_img_size用于检查图像大小是否为步幅s的倍数,如果不是则调整成s的倍数。

        warmup函数为模型推理前进行的预热操作,它会调用forward函数,根据imgsz传入一个空的张量来触发预处理过程,这个过程中模型会对计算流程进行预处理和缓存,以便在后面实际推理时更快的相应请求。

        总的来说,这段是选择设备、初始化模型,并进行模型推理准备工作。

        2.图像预处理

img = letterbox(image_raw, self.imgsz, stride=stride)[0]
img = img.transpose((2, 0, 1))[::-1]
im = np.ascontiguousarray(img)
im = torch.from_numpy(im).to(model.device)
im = im.half() if model.fp16 else im.float()
im /= 255  # 0 - 255 to 0.0 - 1.0
if len(im.shape) == 3:
    im = im[None]  # expand for batch dim

         letterbox函数将原始图像调整到指定尺寸imgsz,并且保持长宽比不变,函数的返回是处理后的图片、缩放比例、填充的宽度和高度。

        transpose函数是把图像维度顺序从(高度H,宽度W,通道数C)转换为(通道数C,高度H,宽度W)这样是为了符合pytorch对输入数据的要求,后面的[::-1]是将图像的通道顺序从RGB转为BGR,也是为了符合机器学习框架的需求。

        ascontiguousarray函数是为了保证图像数组是连续的(某些图像格式在存储时可能采取非连续的方式来组织数据,如BMP格式等),返回一个连续数组的拷贝,防止因为图像数组不连续导致的后续计算函数报错或者效率低下。

        后面则是:

                转换为pytorch张量移动到指定设备(cpu或CUDA);

                数据类型转换成全精度浮点数;

                像素值归一化(将像素值归一化到0.0-1.0之间);

                维度调整(如果张量的维度是3(例如形状为(H,W,C)的图像),为了适应对输入张量的要求,会在第0维额外加一个批次维度,变成(1,H,W,C))

        总的来说,这段是将原始图像进行预处理,以符合模型的输入要求。

         3.模型推理

pred, proto = model(im, augment=self.augment, visualize=self.visualize)[:2]
pred = non_max_suppression(pred, self.conf_thres, self.iou_thres, self.classes, self.agnostic_nms, max_det=self.max_det, nm=32)

        model通过调用前面加载的model对输入图像进行推理。其中:

                augment:推理时是否使用数据增强(对数据进行随机裁剪、翻转、旋转、缩放等来生成更多样化的样本)

                visualize:是否返回可视化结果。

        最后返回值是预测框(pred)和原型向量(proto)的元组。其中原型向量(proto)可以理解为用来代表训练集中不同类别的样本的特征向量,每个原型向量代表一个类别。原型向量可以用于计算损失函数,优化模型参数等。

        non_max_suppression函数将预测框应用非最大抑制算法(NMS),根据置信度阈值(conf_thres)和重叠IOU阈值(iou_thres)对预测框进行过滤,其中:

                classes:指定推理类别

                agnostic_nms:是否使用类别不可知的非最大抑制。

                max_det:每个图像中保留的最大检测框数量

                nm:非最大抑制的候选框数量

        最后得到经过非最大抑制处理后的预测框。

        总的来说,这段是通过模型进行推理,并使用非最大抑制算法对预测框进行过滤。

         4.绘制结果

im0 = image_raw
annotator = Annotator(im0, line_width=self.line_thickness, example=str(names))

        创建一个annotator注释器对象,传入原始图像im0,线条宽度line_width,类别名称str(names)

if self.retina_masks:
    det[:, :4] = scale_boxes(im.shape[2:], det[:, :4], im0.shape).round()  # rescale boxes to im0 size
    masks = process_mask_native(proto[i], det[:, 6:], det[:, :4], im0.shape[:2])  # HWC
else:
    masks = process_mask(proto[i], det[:, 6:], det[:, :4], im.shape[2:], upsample=True)  # HWC
    det[:, :4] = scale_boxes(im.shape[2:], det[:, :4], im0.shape).round()  # rescale boxes to im0 size

         retina_masks为真:先把图像调整成原图像尺寸,再对掩码进行处理;为假则先对掩码进行处理,再调整到原图像尺寸。

        scale_boxes函数作用是把边界框按照给定图片形状和缩放比例进行缩放和填充。

        process_mask_native和process_mask函数都是对掩码进行裁剪、缩放、上采样等操作,区别在于裁剪顺序、裁剪方式不同,前者先上采样再裁剪、直接使用原始bboxes裁剪;而后者是先裁剪再上采样、根据输入图像大小和原始掩码大小的比例来调整边界框的坐标,裁剪时使用调整后的边界框。

        这两种方式最大的区别在于后面segments的结果不同,retina_masks为真时得到的是基于原始图像计算得到的,而为假时则是基于当前图像。

segments = [
            scale_segments(im0.shape if self.retina_masks else im.shape[2:], x, im0.shape, normalize=True)
            for x in reversed(masks2segments(masks))]

        masks2segments函数把掩码转为对应的分割区域 

        scale_segments函数根据retina_masks的值把分割区域缩放至指定尺寸(原图尺寸或当前图像尺寸),后进行归一化存入segments列表。

for j, (*xyxy, conf, cls) in enumerate(reversed(det[:, :6])):
    seg = segments[j].reshape(-1)  # (n,2) to (n*2)
    line = (cls, *seg)   # label format
    c = int(cls)  # integer class
    label = f'{names[c]} {conf:.2f}'
    annotator.box_label(xyxy, label, color=colors(c, True))

        seg为掩码数据的一维数组形式,以便处理。box_label函数将边界框和类别、置信度绘制到原图。 

im0 = annotator.result()

        获取绘制了边界框和标签后的图像.因为前面annotator对象内部维护了一个内存中的缓冲图像,用于绘制标注,所以最后需要获取一下。

        至此,关键代码基本介绍完毕。

三、完整代码 

import argparse
import os
import platform
import sys
from pathlib import Path
import numpy as np
import torch

from ultralytics.utils.plotting import Annotator, colors, save_one_box

from models.common import DetectMultiBackend
from utils.dataloaders import IMG_FORMATS, VID_FORMATS, LoadImages, LoadScreenshots, LoadStreams
from utils.general import (LOGGER, Profile, check_file, check_img_size, check_imshow, check_requirements, colorstr, cv2,
                           increment_path, non_max_suppression, print_args, scale_boxes, scale_segments,
                           strip_optimizer)
from utils.segment.general import masks2segments, process_mask, process_mask_native
from utils.torch_utils import select_device, time_sync
from utils.augmentations import letterbox


class yolov5_test():
    def __init__(self, 
                    weights=None,    
                    data=None,
                    imgsz=(640, 640),
                    conf_thres=0.25,
                    iou_thres=0.4,
                    max_det=1000,
                    device='cpu',
                    classes=None,
                    agnostic_nms=False,
                    augment=False,
                    visualize=False,
                    line_thickness=3,
                    half=False,
                    dnn=False,
                    vid_stride=1,
                    retina_masks=False):
        self.weights = weights
        self.data = data
        self.imgsz = imgsz
        self.conf_thres = conf_thres
        self.iou_thres = iou_thres
        self.max_det = max_det
        self.device = device
        self.classes = classes
        self.agnostic_nms = agnostic_nms
        self.augment = augment
        self.visualize = visualize
        self.line_thickness = line_thickness
        self.half = half
        self.dnn = dnn
        self.vid_stride = vid_stride
        self.retina_masks = retina_masks 

    def image_callback(self, image_raw):

        save_path = "/home/nvidia/test.jpg"    # 测试用,使用修改成自己的路径
        # Load model
        device = select_device(self.device)
        model = DetectMultiBackend(self.weights, device=self.device, dnn=self.dnn, data=self.data, fp16=self.half)
        stride, names, pt = model.stride, model.names, model.pt
        imgsz = check_img_size(self.imgsz, s=stride)  # check image size

        model.warmup(imgsz=(1 if pt else bs, 3, *imgsz))  # warmup
        dt = [0.0, 0.0, 0.0]

        bs = 1  # batch_size
        img = letterbox(image_raw, self.imgsz, stride=stride)[0]
        img = img.transpose((2, 0, 1))[::-1]
        im = np.ascontiguousarray(img)
        t1 = time_sync()

        im = torch.from_numpy(im).to(model.device)
        # im = im.half() if model.fp16 else im.float()  # uint8 to fp16/32
        im = im.half() if model.fp16 else im.float()
        im /= 255  # 0 - 255 to 0.0 - 1.0
        if len(im.shape) == 3:
            im = im[None]  # expand for batch dim

        t2 = time_sync()
        dt[0] += t2 - t1
        # visualize = increment_path(save_dir / Path(path).stem, mkdir=True) if visualize else False
        pred, proto = model(im, augment=self.augment, visualize=self.visualize)[:2]
        t3 = time_sync()
        dt[1] += t3 - t2
        pred = non_max_suppression(pred, self.conf_thres, self.iou_thres, self.classes, self.agnostic_nms, max_det=self.max_det, nm=32)
        dt[2] += time_sync() - t3

        for i, det in enumerate(pred):
            im0 = image_raw
            annotator = Annotator(im0, line_width=self.line_thickness, example=str(names))
            if len(det):
                if self.retina_masks:
                    det[:, :4] = scale_boxes(im.shape[2:], det[:, :4], im0.shape).round()  # rescale boxes to im0 size
                    masks = process_mask_native(proto[i], det[:, 6:], det[:, :4], im0.shape[:2])  # HWC
                else:
                    masks = process_mask(proto[i], det[:, 6:], det[:, :4], im.shape[2:], upsample=True)  # HWC
                    det[:, :4] = scale_boxes(im.shape[2:], det[:, :4], im0.shape).round()  # rescale boxes to im0 size 
                segments = [
                            scale_segments(im0.shape if self.retina_masks else im.shape[2:], x, im0.shape, normalize=True)
                            for x in reversed(masks2segments(masks))]       
                annotator.masks(
                        masks,
                        colors=[colors(x, True) for x in det[:, 5]],
                        im_gpu=torch.as_tensor(im0, dtype=torch.float16).to(device).permute(2, 0, 1).flip(0).contiguous() /
                        255 if self.retina_masks else im[i])
                
                for j, (*xyxy, conf, cls) in enumerate(reversed(det[:, :6])):
                    seg = segments[j].reshape(-1)  # (n,2) to (n*2)
                    line = (cls, *seg)   # label format
                    c = int(cls)  # integer class
                    label = f'{names[c]} {conf:.2f}'
                    annotator.box_label(xyxy, label, color=colors(c, True))
        
        
        im0 = annotator.result()

        # Save results (image with detections)
        cv2.imwrite(save_path, im0)    
       

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值