文章目录

  • 前言
  • detect.py
    • 1.输入参数
    • 2.设置文件保存路径
    • 3.加载训练好的模型权重
    • 4.数据处理
    • 5.进行推理
    • 6.yolov5里的nms
  • 总结
  • yolov5 系列

前言

    推理阶段是整个检测模型完成后,要对模型进行测试的部分。很重要的一部分,只有了解了这个部分,才能在比赛或者项目提交中很好的输出自己模型的检测结果。同时,推理输出对模型部署在不同的环境下也是十分重要的。
源码:https://github.com/ultralytics/yolov5
版本yolov5 v6.1

detect.py

1.输入参数

@torch.no_grad()  # 装饰器,推理部分不进行反向传播计算
def run(weights=ROOT / 'yolov5s.pt',  # 加载的训练模型权重路径source=ROOT / 'data/images',  # 输入图像或视频文件路径data=ROOT / 'data/coco128.yaml',  # dataset.yaml path,自己生成的数据集yaml文件路径imgsz=(640, 640),  # inference size (height, width)conf_thres=0.25,  # confidence thresholdiou_thres=0.45,  # NMS IOU thresholdmax_det=1000,  # maximum detections per image,默认一张图最多输出1000个目标device='',  # cuda device, i.e. 0 or 0,1,2,3 or cpu,选择推理输出的设备view_img=False,  # show results,推理后是否展示结果图片save_txt=False,  # save results to *.txt,保存成.txt文件save_conf=False,  # save confidences in --save-txt labels,是否将置信度保存到txt文件save_crop=False,  # save cropped prediction boxes,是否将预测到的目标从原图剪切下来nosave=False,  # do not save images/videos,是否不保存推理输出的结果classes=None,  # filter by class: --class 0, or --class 0 2 3,是否保留指定的类agnostic_nms=False,  # class-agnostic NMS,进行nms时是否将其他类当作同一个类别处理augment=False,  # augmented inference,是否进行测试推理时的数据增强,TTA之类的visualize=False,  # visualize features,是否可视化输出update=False,  # update all models,project=ROOT / 'runs/detect',  # save results to project/name ,结果输出保存主文件目录name='exp',  # save results to project/name,结果输出保存文件名称exist_ok=False,  # existing project/name ok, do not incrementline_thickness=3,  # bounding box thickness (pixels)hide_labels=False,  # hide labelshide_conf=False,  # hide confidenceshalf=False,  # use FP16 half-precision inference,是否采用半精度浮点型进行推理运算dnn=False,  # use OpenCV DNN for ONNX inference
):

2.设置文件保存路径

 source = str(source)   # 图像输入路径save_img = not nosave and not source.endswith('.txt')  # save inference imagesis_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('.txt') or (is_url and not is_file)  #视频流文件if is_url and is_file:source = check_file(source)  # download# Directoriessave_dir = increment_path(Path(project) / name, exist_ok=exist_ok)  # increment run,默认路径runs/detect/exp(save_dir / 'labels' if save_txt else save_dir).mkdir(parents=True, exist_ok=True)  # make dir

3.加载训练好的模型权重

# Load modeldevice = select_device(device)   # 选择设备# 不同的权重格式选择不同的加载方式 ,如.pt,.onnx,.trtmodel = DetectMultiBackend(weights, device=device, dnn=dnn, data=data, fp16=half)stride, names, pt = model.stride, model.names, model.pt  imgsz = check_img_size(imgsz, s=stride)  # check image size,检查选择输入模型的尺寸是否符合32的倍数

4.数据处理

整个数据处理的步骤;

  • 1.先对输入图像的原图进行最短边的放缩;
  • 2.为了匹配yolov5的下采样操作还要使得输入模型的宽高符合最后一次下采样stride(三层一般32,四层64)的倍数,还要对其进行padding。
    +3. 同时,因为opencv读取图像是BGR格式,要将其转化为RGB格式。
  • 4.最后,还要对输入的像素值进行归一化。
# Dataloaderif webcam:view_img = check_imshow()cudnn.benchmark = True  # set True to speed up constant image size inferencedataset = LoadStreams(source, img_size=imgsz, stride=stride, auto=pt)bs = len(dataset)  # batch_sizeelse:# 这里只是简单的加载输入图像数据的路径dataset = LoadImages(source, img_size=imgsz, stride=stride, auto=pt)bs = 1  # batch_sizevid_path, vid_writer = [None] * bs, [None] * bs# Run inference# 先进行预热,用全为0的先进行一次推理,过一遍代码model.warmup(imgsz=(1 if pt else bs, 3, *imgsz))  # warmup# 这里迭代的时候要进入,utils/dataloaders.py里的__next__里进行letterbox数据宽高放缩处理for path, im, im0s, vid_cap, s in dataset:t1 = time_sync()  # 计算时间im = torch.from_numpy(im).to(device)  # 转成tensor并将数据转到cuda上im = im.half() if model.fp16 else im.float()  # uint8 to fp16/32,选择使用的精度,半浮点型速度要更快im /= 255  # 0 - 255 to 0.0 - 1.0  这是像素值归一化,跟坐标归一化无关if len(im.shape) == 3:im = im[None]  # expand for batch dim,拓展一个batch维度t2 = time_sync()dt[0] += t2 - t1  # 计算数据预处理时间
  • letterbox进行resize和pad,opencv读取的bgr转成rgb
# Padded resize
img = letterbox(img0, self.img_size, stride=self.stride, auto=self.auto)[0]
---------------------------------------------------------------------------
---------------------------------------------------------------------------
def letterbox(im, new_shape=(640, 640), color=(114, 114, 114), auto=True, scaleFill=False, scaleup=True, stride=32):# Resize and pad image while meeting stride-multiple constraintsshape = im.shape[:2]  # 输入原图高宽[height, width]if isinstance(new_shape, int):new_shape = (new_shape, new_shape)# Scale ratio (new / old),求自己要求输入模型图像的高宽和原图高宽的最小比值r = min(new_shape[0] / shape[0], new_shape[1] / shape[1]) if not scaleup:  # only scale down, do not scale up (for better val mAP)r = min(r, 1.0)  # 这表示如果比值大于1不进行比例缩放# Compute paddingratio = r, r  # width, height ratios# 对原图宽高进行最短边比列缩放,保持宽高比不变,使图像尽量不失真new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r)) # 为了符合32的倍数要求(满足最小预测输出的特征层),要对缩放的尺寸进行padding  dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1]  # wh paddingif auto:  # minimum rectangledw, dh = np.mod(dw, stride), np.mod(dh, stride)  # wh padding np.mod取模运算elif scaleFill:  # stretchdw, dh = 0.0, 0.0new_unpad = (new_shape[1], new_shape[0])ratio = new_shape[1] / shape[1], new_shape[0] / shape[0]  # width, height ratiosdw /= 2  # divide padding into 2 sidesdh /= 2 # padding是对图像两边进行的if shape[::-1] != new_unpad:  # 和原图宽高不等,opencv进行resizeim = cv2.resize(im, new_unpad, interpolation=cv2.INTER_LINEAR)top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1)) # 四舍五入后取整left, right = int(round(dw - 0.1)), int(round(dw + 0.1))# padding的部分是无效区域,用color=(114, 114, 114)常数值填充,前面讲mmdetection里的mask记录放缩图像无效区域一个意思im = cv2.copyMakeBorder(im, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color)  # add border# 返回缩放的宽高,比例和padding大小,方便后面对归一化的预测输出进行还原,推理只要imreturn im, ratio, (dw, dh)
---------------------------------------------------------------------------------
# opncv读取的图片数据是HWC和BGR格式的,为了方便要进行处理
# Convert
img = img.transpose((2, 0, 1))[::-1]  # HWC to CHW, BGR to RGB
img = np.ascontiguousarray(img)
return path, img, img0, self.cap, s

5.进行推理

推理步骤:

  • 1.将输入图像经过模型得到预测输出;
  • 2.对预测输出进行NMS处理;
  • 3.对NMS处理后的Boxes进行相应的后处理。
     # Inferencevisualize = increment_path(save_dir / Path(path).stem, mkdir=True) if visualize else False# [b,num_pre_boxes,(5+cls_scores)]这里是输出归一化的boxes:x,y,h,w,5表示[x,y,h,w,conf]pred = model(im, augment=augment, visualize=visualize)  t3 = time_sync()dt[1] += t3 - t2  # 模型预测输出时间# 多类别的nms,部署的话要可以用opencv的,也可以用c++可以自己写# NMS,经过下面的NMS后才是[num_P,6],[x1,y1,x2,y2,conf,cls]pred = non_max_suppression(pred, conf_thres, iou_thres, classes, agnostic_nms, max_det=max_det)# [x1,y1,x2,y2,conf,cls]dt[2] += time_sync() - t3 # NMS的时间# Process predictionsfor i, det in enumerate(pred):  # per imageseen += 1if webcam:  # batch_size >= 1p, im0, frame = path[i], im0s[i].copy(), dataset.counts += f'{i}: 'else:p, im0, frame = path, im0s.copy(), getattr(dataset, 'frame', 0)p = Path(p)  # to Pathsave_path = str(save_dir / p.name)  # im.jpgtxt_path = str(save_dir / 'labels' / p.stem) + ('' if dataset.mode == 'image' else f'_{frame}')  # im.txts += '%gx%g ' % im.shape[2:]  # print stringgn = torch.tensor(im0.shape)[[1, 0, 1, 0]]  # normalization gain whwh,  获取原图大小imc = im0.copy() if save_crop else im0  # for save_cropannotator = Annotator(im0, line_width=line_thickness, example=str(names))if len(det):# Rescale boxes from img_size to im0 size,im:640,640,im0:原图大小det[:, :4] = scale_coords(im.shape[2:], det[:, :4], im0.shape).round()  #还原原来图像大小# Print resultsfor c in det[:, -1].unique():n = (det[:, -1] == c).sum()  # detections per classs += f"{n} {names[int(c)]}{'s' * (n > 1)}, "  # add to string# Write resultsfor *xyxy, conf, cls in reversed(det):if save_txt:  # Write to file# xyxy2xywh:字面意思,将左上右下角的坐标值,转换为中心宽高值,再除以比例,归一化保存到txt文件上# 比赛或者项目里一般不会要求提交归一化的结果,所以这里自己稍微进行相应的处理即可xywh = (xyxy2xywh(torch.tensor(xyxy).view(1, 4)) / gn).view(-1).tolist()  # normalized xywh# txt文件里每行记录(cls, *xywh),如果要记录置信度,将save_conf设置为trueline = (cls, *xywh, conf) if save_conf else (cls, *xywh)  # label format# 打开txt文件,将推理的结果写入with open(f'{txt_path}.txt', 'a') as f:f.write(('%g ' * len(line)).rstrip() % line + '\n')# 后面的代码都是保存和展示结果文件的了,这里就不解析了if save_img or save_crop or view_img:  # Add bbox to imagec = int(cls)  # integer classlabel = None if hide_labels else (names[c] if hide_conf else f'{names[c]} {conf:.2f}')annotator.box_label(xyxy, label, color=colors(c, True))if save_crop:save_one_box(xyxy, imc, file=save_dir / 'crops' / names[c] / f'{p.stem}.jpg', BGR=True)

6.yolov5里的nms

在前面的博客,讲了单类别的nms,但是如果是多类别的nms可以怎么做呢?
已知的方法:

  • 第一种方法就是,对循环遍历每类,然后每类进行nms;
  • 第二种方法,直接对所有的box进行分数降序排列,之后在循环里进行判断,只要类别相同的才进行iou计算,然后再比较阈值。这要用一个标记数组来记录哪些是要筛选的,之后筛选掉。
  • 第三种方法,使用一个偏移量,将不同的类加上一个偏移量,将每个类的所有box单独变换到不同的坐标域,直接按原来的进行nms即可。

yolov5就是采用的第三种方法,这里这要讲下主要的代码,完整代码在utils/general.py里的non_max_suppression里

# 主要代码max_wh = 7680  # 最大宽高
----------------------------------------------------------
# 这里的代码主要解释x的维度代表什么,下面有用到# 找出的conf_thres(类别分数*置信度)最大的对应的一个类和索引,j是类别label的索引(其实就是类别标签):[0,1,1,2]这种conf, j = x[:, 5:].max(1, keepdim=True)    # 在维度1上重新合并[经过初步分数阈值筛选的预测数量,4+1+1=6],取出大于conf_thres阈值的预测值[k,6]x = torch.cat((box, conf, j.float()), 1)[conf.view(-1) > conf_thres]
---------------------------------------------------------
# 进行多类别的nms# Batched NMS,如果agnostic为true,表示所有的作为一类进行nms,默认false,每类乘上一个最大wh长度的偏移值c = x[:, 5:6] * (0 if agnostic else max_wh)  # classes# 之后在boxes上加上偏移量作为iou计算的boxesboxes, scores = x[:, :4] + c, x[:, 4]  # boxes (offset by class), scoresi = torchvision.ops.nms(boxes, scores, iou_thres)  # NMS

总结

    推理输出部分的理解是十分重要的,在比赛和项目中有提交结果格式的要求,都要在这修改相应的代码。同时,也可以在推理过程中使用一些trick去提升性能。同时,为了实时性,算法的开发部署主要就是推理阶段的部署。为了加速推理速度,提升检测性能,可以很好的将训练好的torch模型转换成onnx或者engine,使用c++进行部署。

yolov5 系列

yolov5使用自己的数据集相关代码
yolov5网络结构代码解读
yolov5的正负样本的定义和匹配

yolov5的推理输出detect.py部分相关推荐

  1. Yolov5代码详解——detect.py

    首先执行扩展包的导入: import argparse import os import platform import sys from pathlib import Path ​ import t ...

  2. 将yolov5的detect.py改写成可以供其他程序调用的方式,并实现低时延(<0.5s)直播推理

    将yolov5的推理代码改成可供其它程序调用的方式,并实现低时延(<0.5s)直播推理 yolov5的代码具有高度的模块化,对于初学者十分友好,但是如果咱们要做二次开发,想直接调用其中一些函数, ...

  3. yolov5 推理,运行detect.py出错

    在训练完成准备运行推理的时候,出项这个报错,孩子实在找不出原因了 Namespace(agnostic_nms=False, augment=False, classes=None, conf_thr ...

  4. yolov5——detect.py代码【注释、详解、使用教程】

    yolov5--detect.py代码[注释.详解.使用教程] yolov5--detect.py代码[注释.详解.使用教程] 1. 函数parse_opt() 2. 函数main() 3. 函数ru ...

  5. YOLOV5检测代码detect.py注释与解析

    YOLOv5代码注释版更新啦,注释的是最近的2021.07.14的版本,且注释更全 github: https://github.com/Laughing-q/yolov5_annotations Y ...

  6. YOLOv5的Tricks | 【Trick13】YOLOv5的detect.py脚本的解析与简化

    如有错误,恳请指出. 在之前介绍了一堆yolov5的训练技巧,train.py脚本也介绍得差不多了.之后还有detect和val两个脚本文件,还想把它们总结完. 在之前测试yolov5训练好的模型时, ...

  7. Python —— 解析Yolov5 - detect.py

    Yolov5自带detect.py加入cv2简单操作      说明:im0为mat的原图     detect.py参数解析      1.运行detect.py的两种方式:           ( ...

  8. YoLoV5学习(4)--detect.py程序(预测图片、视频、网络流)逐段讲解~

    本章博客主要分析YoloV5代码中的detect程序代码,按照程序运行步骤顺序主要分为3大部分. 1.包与库的导入 1.1 导入安装好的python库.torch库等等 其中:argparse模块.o ...

  9. yolov5的detect.py代码详解

    目标检测系列之yolov5的detect.py代码详解 前言 哈喽呀!今天又是小白挑战读代码啊!所写的是目标检测系列之yolov5的detect.py代码详解.yolov5代码对应的是官网v6.1版本 ...

最新文章

  1. oracle 11所选安装,在red hat enterprise linux 5.4上安装oracle11g
  2. WebService简单验证:SoapHeader
  3. delphi 改变闪动光标
  4. linux重定向文件容加时间,linux – 如何在Bash中将stdout重定向到文件时添加时间戳?...
  5. 怎么连接屏幕_触控一体机怎么实现无线投屏功能
  6. UWP Composition API - GroupListView(一)
  7. MYSQL的binary解决mysql数据大小写敏感问题
  8. 【LeetCode笔记】96. 不同的二叉搜索树(Java、动态规划)
  9. 给定(x,y)填充数据,前端怎么实现?
  10. html5 苹果手机上传word文件_DocumentsbyReaddle文件管理器,让你的苹果手机 更顺畅...
  11. 什么是边缘计算网关?(边缘计算网关产品的特点?)
  12. 中国拖车洒水器市场趋势报告、技术动态创新及市场预测
  13. 跨境电商的三个增长点:产品曝光 品类轮转 入自建站
  14. 「业内分析」拉卡拉新商业模式的转化,拥抱下半场
  15. 使用parent.layer.open()打开页面如何调用子页面的方法
  16. C语言实现raw格式图像的读入和存取
  17. Python 第三方模块 数据分析 Pandas模块 字符串处理
  18. javaspringboot面试题,java面试问职业规划
  19. Android 13:一文看懂两大重磅升级
  20. 关于三点演讲与口才训练方法

热门文章

  1. Python网络请求库Requests,妈妈再也不会担心我的网络请求了(一)
  2. dell电脑重装系统后开机出现NO boot Device Found进不了系统
  3. 浅尝不辄止系列之试试腾讯云的 TUIRoom(上)
  4. CoreText 轻松设置字体大小,间距,行间距,段间距,算高度
  5. 一个快速使用 Golang 开发和构建生成 NodeJS Addon 扩展的开发工具
  6. 交通银行签约易观千帆,全面升级数智能力
  7. 制作人物双重曝光海报
  8. 消防车Firetruck
  9. Java程序运行机制及IDEA安装
  10. django部署 nginx 配置简单的负载均衡