赞
踩
需要将yolo-seg和ros2结合起来,所以要将yolov5-seg中的predict.py预测部分封装成类,并且只保留主体功能部分,把不需要的部分都删除。
- 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传入一个空的张量来触发预处理过程,这个过程中模型会对计算流程进行预处理和缓存,以便在后面实际推理时更快的相应请求。
总的来说,这段是选择设备、初始化模型,并进行模型推理准备工作。
- 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))
总的来说,这段是将原始图像进行预处理,以符合模型的输入要求。
- 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:非最大抑制的候选框数量
最后得到经过非最大抑制处理后的预测框。
总的来说,这段是通过模型进行推理,并使用非最大抑制算法对预测框进行过滤。
- 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)
-
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。