# YOLOv5 by Ultralytics, GPL-3.0 license
"""
Run YOLOv5 detection inference on images, videos, directories, globs, YouTube, webcam, streams, etc.
Usage - sources:
$ python detect.py --weights yolov5s.pt --source 0 # webcam
img.jpg # image
vid.mp4 # video
screen # screenshot
path/ # directory
'path/*.jpg' # glob
'https://youtu.be/Zgi9g1ksQHc' # YouTube
'rtsp://example.com/media.mp4' # RTSP, RTMP, HTTP stream
Usage - formats:
$ python detect.py --weights yolov5s.pt # PyTorch
yolov5s.torchscript # TorchScript
yolov5s.onnx # ONNX Runtime or OpenCV DNN with --dnn
yolov5s_openvino_model # OpenVINO
yolov5s.engine # TensorRT
yolov5s.mlmodel # CoreML (macOS-only)
yolov5s_saved_model # TensorFlow SavedModel
yolov5s.pb # TensorFlow GraphDef
yolov5s.tflite # TensorFlow Lite
yolov5s_edgetpu.tflite # TensorFlow Edge TPU
yolov5s_paddle_model # PaddlePaddle
"""
###一、导包和基本配置
import platform
"""==================1.导入安装好的python库等======================="""
import argparse#解析命令行参数
import os ##与操作系统进行交互的文件库,包含文件路径操作与解析
import sys#sys模块包含了与python解释器和他的环境有关的模块
from pathlib import Path##Path能够更加方便得对字符串路径进行处理
import cv2#sys模块包含了与python解释器和它的环境有关的函数
import torch #pytorch深度学习库
import torch.backends.cudnn as cudnn #让内置的cudnn的auto-tuner自动寻找最合适当前配置的高效算法,来达到优化运行效率的问题
"""==================2.获得当前文件的绝对路径======================="""
"""这段代码会获取当前文件的绝对路径,并使用Path库将其转换为Path对象
这一部分的主要作用有两个:
将当前项目添加到系统路径上,以使得项目中的模块可以调用
将当前项目的相对路径保存在ROOT中,便于寻找项目中的文件."""
FILE=Path(__file__).resolve()##__file__指的是当前文件(即detect.py),FILE最终保存着当前文件的绝对路径 比如D://yolov5/detect.py
ROOT = FILE.parents[0]##YOLOv5 root directory ROOT保存着当前项目的父目录,比如D://yolov5
if str(ROOT) not in sys.path:##sys.path即当前python环境可以运行的路径,假如当前项目不在该路径中,就无法运行其中的模块,所以就需要加载路径
sys.path.append((str(ROOT)))#add ROOT to PATH 把ROOT添加到运行路径中
#print(Path.cwd()) C:\Users\86131\PycharmProjects\pythonProject\hand_self_yolov5
ROOT=Path(os.path.relpath(ROOT,Path.cwd()))##relative ROOT设置为相对路径
"""==================3.加载自定义模块======================="""
"""这些都是用户自定义的库,由于上一步已经把路径加载上了,所以现在可以导入,这个顺序不可以调换。
具体来说,代码从如下几个文件中导入了部分函数和类:
models1.common.py: 这个文件定义了一些通用的函数和类,比如图像的处理、非极大值抑制等等.
utils.dataloaders.py: 这个文件定义了两个类,Loadlmages和LoadStreams,它们可以加载图像或视频帧,
并对它们进行一些预理,以便进行物体检测或识别。
utils.general.py:这个文件定义了一些常用的工具函数,比如检查文件是否存在、检查图像大小是否符合要求、打印命令行参数等等。
utils.plots.py: 这个文件定义了Annotator类,可以在图像上绘制矩形框和标注信息。
utils.torch utils.py: 这个文件定义了一些与PyTorch有关的工具函数,比如选择设备、同步时间等等.
通过导入这些模块,可以更方便地进行目标检测的相关任务,并且减少了代码的复杂度和几余。
"""
from models1.common import DetectMultiBackend
from models1.utils1.dataloaders import IMG_FORMATS, VID_FORMATS, LoadImages, LoadScreenshots, LoadStreams
from models1.utils1.general import (LOGGER, Profile, check_file, check_img_size, check_imshow, check_requirements, colorstr, cv2,
increment_path, non_max_suppression, print_args, scale_boxes, strip_optimizer, xyxy2xywh)
from models1.utils1.plots import Annotator, colors, save_one_box
from models1.utils1.torch_utils import select_device, smart_inference_mode
####################Parse_opt()用来设置输入参数的子函数
"""
argparse是python用于解析命令行参数和选项的标准模块,用于代替已经过时的optparse模块。
argparse模块的作用适用于解析命令行参数。我们很多时候用到解析命令行参数的程序,
目的是在终端窗口能够输入训练的参数和选项(ubuntu是终端窗口,windows是命令行窗口)。
常常可以将其简化为四个步骤:
1)import argparse 首先导入模块;
2)parser = argparse.ArgumentParser() 然后创建一个解析对象;
3)parser.add_argument() 然后向该对象中添加你要关注的命令行参数和选项,每一个add_argument方法对应一个一要关注的参数和选项;
4)parser.parse_args() 最后调用parse_srgs()方法进行解析;解析成功后可使用。
"""
def parse_opt():
parse=argparse.ArgumentParser()
parse.add_argument()
parse.add_argument('--weights',nargs='+',type=str,default=ROOT/'yolov5s.pt',help='model path(s)')
"""正则表达式的规则-参数个数:
nargs='*' 表示参数可设置零个或多个
nargs=' '+' 表示参数可设置一个或多个
nargs='?' 表示参数可设置零个或一个"""
#--weights:训练的权重路径,可以使用自己训练的权重,也可以使用官网提供的权重。默认官网权重yolov5s.pt
##--source:测试数据,可以是图片/视频路径。也可以是‘0’(电脑自带摄像头),也可以是rtfs等视频流,默认data/images
parse.add_argument('--sorece',type=str,default=ROOT /'data/images',help='file/dir/URL/glob,0 for webcam')
parse.add_argument('--imgsz','--img','--img-size',nargs='+',type=int,default=[640],help='inference size h,w')
parse.add_argument('--conf-thres',type=float,default=0.5,help='confidence threshold')
###--conf-thres:置信度阈值,默认为0.50
parse.add_argument('--iou-thres',type=float,default=0.45,help='NMS IoU threshold')
##--iou-thres 非极大值抑制时的IoU阈值,默认为0.45
parse.add_argument('--max-det',type=int,default=1000,help='maximum detections per image')
##--max-der:保留的最大的检测框数量,每张图片中检测目标的个数最多为1000类
parse.add_argument('--device',default='',help='cuda device,i,e 0or 0,1,2,3 or cpu')
##--device:使用的设备,可以是cuda设备的ID(例如0、0,1,2,3)或者是“cpu”,默认是‘0’
parse.add_argument('--view-img',action='store_true',help='show results')
#--view-img:是否展示预测之后的图片或视频?默认为False
parse.add_argument('--save-txt',action='store_true',help='save results to *.txt')
##--save-txt:是否将预测的框坐标以txt文件形式保存,默认为False,使用--save-txt在路径runs/detect/exp*/labels/*.txt下生成每张图片预测的txt文件
parse.add_argument('--save-conf',action='store_true',help='save confidences in --save-txt labels')
##--save-conf:是否保存检测结果的置信度到txt文件,默认为False
parse.add_argument('--sava-crop',action='store_true',help='save cropped prediction boxes')
##--save-conf:是否保存裁剪预测框图片,默认为False,使用--save-crop在runs/detect/exp*/crop/剪切类别文件夹/路径下会保存每个接下来的目标
parse.add_argument('--nosave',action='store_true',help='do not save images/videos')
##--nosave:不保存图片、视频,要保存图片,不设置-nosave 在runs/datect/exp*/会出现预测的结果
parse.add_argument('--classes',nargs='+',type=int,help='filter by class:--classes 0,or --classes 0 2 3')
##--classes:仅检测指定类别,默认为None
parse.add_argument('--agnostic-nms',action='store_true',help='class-agnostic NMS')
##agnostic-nms:是否使用类别不敏感的非极大值抑制(及不考虑类别信息),默认为False
parse.add_argument('--augment',action='store_true',help='augmented inferences')
##--augment:是否使用数据增强进行推理,默认为False
parse.add_argument('--visualize',action='store_true',help='visualize feature')
##--visualize:是否可视化特征图,默认为False
parse.add_argument('--update',action='store_true',help='update all models1')
##--update如果为True,则对所有模型进行strip_optimizer操作,去除pt文件中的优化器等信息,默认为False
parse.add_argument('--project',default=ROOT /'runs/detect',help='save results to project/name')
##--project:结果保存的项目目录路径,默认为‘ROOT/runs/detect’
parse.add_argument('--name',default='exp',help='save results to project/name')
##--name:结果保存的子目录名称,默认为‘exp'
parse.add_argument('--exist-ok',action='store_true',help='existing project/name ok,do not increment')
##--exist-ok:是否覆盖已有结果,默认为False
parse.add_argument('--line-thickness',default=3,type=int,help='bounding box thickness (pixels)')
##--line-thickness:画bounding box时的线条宽度,默认为3
parse.add_argument('--hide-labels',default=False,action='store_true',help='hide labels')
##--hide-labels:是否隐藏标签信息,默认为False
parse.add_argument('--hide-conf',default=False,action='store_true',help='hide confidence')
parse.add_argument('--half',action='store_true',help='use FP16 half-precision inference')
##--hide-conf:是否隐藏置信度信息,默认为False
##--half:是否使用FP16半精度进行推理,默认为False
parse.add_argument('--dnn',action='store_true',help='use Opencv DNN for ONNX inference')
##--dnn:是否使用OpenCV DNN进行ONNX推理,默认为False
opt=parse.parse_args()#扩充维度
opt.imgsz*=2 if len(opt.imgsz)==1 else 1 ##expand--->目的是方便,只写一维,但图片是2维 list=[640]*2-->[640, 640]
"""
k=[30]
k2=k*2
print(k2) [30, 30]
print(k) [30]
"""
"""
detect: weights=yolov5s.pt, source=data\images, data=data\coco128.yaml, imgsz=[640, 640], conf_thres=0.25,
iou_thres=0.45, max_det=1000, device=, view_img=False, save_txt=False, save_conf=False, save_crop=False,
nosave=False, classes=None, agnostic_nms=False, augment=False, visualize=False, update=False, project=runs\detect,
name=exp, exist_ok=False, line_thickness=3, hide_labels=False, hide_conf=False, half=False, dnn=False, vid_stride=1
"""
print_args(vars(opt))## 将其所有的参数信息进行打印 #打印所有参数信息
"""
detect: weights=yolov5s.pt, source=data\images, data=data\coco128.yaml, imgsz=[640, 640], conf_thres=0.25,
iou_thres=0.45, max_det=1000, device=, view_img=False, save_txt=False, save_conf=False, save_crop=False,
nosave=False, classes=None, agnostic_nms=False, augment=False, visualize=False, update=False, project=runs\detect,
name=exp, exist_ok=False, line_thickness=3, hide_labels=False, hide_conf=False, half=False, dnn=False, vid_stride=1
"""
"""
vars() 是Python内置的一个函数,它可以返回对象object的__dict__属性。其中,__dict__属性(变量)是一个字典类型,表示对象的属性名称(detect)和对应的属性值。
在print_args(vars(opt))这行代码中,vars(opt)会返回opt对象的属性字典,然后print_args()函数会以字典的形式打印出所有属性的名称(detect)和值。
这通常用于调试和查看对象的属性信息。
"""
"""
opt=Namespace(agnostic_nms=False, augment=False, classes=None, conf_thres=0.25, data=WindowsPath('data/coco128.yaml'),
device='', dnn=False, exist_ok=False, half=False, hide_conf=False, hide_labels=False, imgsz=[640], iou_thres=0.45,
line_thickness=3, max_det=1000, name='exp', nosave=False, project=WindowsPath('runs/detect'), save_conf=False,
save_crop=False, save_txt=False, source=WindowsPath('data/images'),
update=False, vid_stride=1, view_img=False, visualize=False, weights=WindowsPath('yolov5s.pt'))
"""
return opt
@torch.no_grad()#给方法加上额外的功能和限制 该标注使得方法中所有计算得出的tensor的requires_grad都自动设置为False,也就是说不进行梯度的计算(当然也就没办法反向传播了),节约显存和算力
def run(
weights=ROOT/'yolov5s.pt',
source=ROOT/'data/images',
data=ROOT / 'data/coco128.yaml', # dataset.yaml path #data文件路径
imgsz=(640,640),#预测时的放缩后图片大小(因为YOLO算法需要预先放缩图片),两个值分别是height, width。默认640*640
conf_thres=0.25,##confidence threshold 置信度阈值,高于此值的bounding box才会被保留。默认0.25,用在nms中
iou_thres=0.45,##NMS IOU threshold IOU阈值,高于此值的bounding_box才会被保留。默认0.45,用在nms中
max_det=1000,##maximum detections per image 一张图片上检测的最大目标数量,用在nms中
device='',#cuda device,i.e. 0 or 0,1,2,3 or cpu 所使用的GPU编号,如果使用CPU就写cpualse
view_img=False,#show results 是否展示预测之后的图片或视频,默认False
save_txt=False,##save results to *,txt 是否将预测的框坐标以txt文件形式保存,默认False,
save_conf=False,##save confidences in --save-txt labels 是否将结果中的置信度保存在txt文件中,默认False
save_crop=False,## save cropped prediction boxes 是否保存裁剪后的顶测框默认为Fase, 使用--save-crop 在runs/detect/exp*/crop/剪切类别文件夹
nosave=False,##do not save images/videos 不保存图片、视频,要保存图片,不设置--nosave 在runs/detect/exp*/会出现预测的结果
classes=None,#filter by class: --class ,or --class 0 2 3 过滤指定类的预测结果
agnostic_nms=False,## class-agnostic NMS 进行NMS去除不同类别之间的框,默认False
augment=False,##augmented inference TTA测试时增强/多尺度预测,可以提分
visualize=False,##visualize features 是否可视化网络层输出特征
update=False,##update all models 如果为True,则对所有模型进行strip_optimizer操作,去除pt文件中的优化器等信息,默认为False
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 increment True: 推理结果覆盖之前的结果 False: 推理结果新建文件夹保存,文件夹名递增
line_thickness=3,#bounding box thickness (pixels) 绘制Bounding box的线宽度
hide_labels=False,#hide labels 若为True: 隐藏标签
hide_conf=False,#hide confidences 若为True: 隐藏置信度
half=False,#use FP16 half-precision inference 是否使用半精度推理(节约显存)
dnn=False,##use OpenCV DNN for ONNX inference 是否使用OpenCV DNN预测
vid_stride=1,
):
########################2.初始化配置##############
"""
这段代码主要用于处理输入来源。定义了一些布尔值区分输入是图片、视频、网络流还是摄像头.首先将source转换为字符串类型,然后判断是否需要保存输出结果。
如果nosave和source的后缀不是.txt,则会保存输出结果.接着根据source的类型,确定输入数据的类型:
。如果source的后缀是图像或视频格式之一,那么将is fle设置为True;
。如果source以rtsp://、rtmp://、http://、https://开头,那么将is url设置为True;。如果source是数字或以.txt结尾或是一个URL,那么将webcam设置为True;
。如果source既是文件又是URL,那么会调用check file函数下载文件。
"""
##输入路径变为字符串
source=str(source)
##是否保存图片或txt文件,如果nosave(传入的参数)为false且source的结尾不是txt则保存图片
save_img=not nosave and not source.endswith('.txt')##save inference images
##判断source()是不是视频/图像路径
##Path()提取文件名。suffix:最后一个组件的文件扩展名。若source是"D://YOLOv5/data/1.jpg",则Path(source).suffix是".jpg",Path(source).suffix[1:]是"jpg
##而IMG_FORMATS 和VID_FORMATS两个变量保存的是所有的视频和图片的格式后缀
is_file=Path(source).suffix[1:] in (IMG_FORMATS+VID_FORMATS)##如果source的后缀是图像或视频格式之一,那么将is_file设置为True
##判断source是否是链接
###.lower()转化为小写 .upper()转化为大写 .title()首字母转化为大写,其余为小写 .startswith('http://")返回True or False
is_url=source.lower().startswith(('rtsp://', 'rtmp://', 'http://', 'https://'))#如果source以rtsp://、rtmp://、http://、https://开头,那么将is_url设置为True;
##判断source是否是摄像头
##.isnumeric()是否由数字组成,返回True or False
webcam=source.isnumeric() or source.endswith('.txt') or (is_url and not is_file)
screenshot = source.lower().startswith('screen')
if is_url and is_file:
##返回文件。如果source是一个指向图片/视频的链接,则下载输入数据
source=check_file(source)##下载
########################3.保存结果##############
"""
这段代码主要是用于创建保存输出结果的目录。创建一个新的文件夹exp (在runs文件夹下)来保存运行的结果。
首先代码中的 project指 run 函数中的 project,对应的是 runs/detect 的目录,name 对应 run 函数中的“name=exp”,
然后进行拼接操作。使用increment_path函数来确保目录不存在,如果存在,则在名称后面添加递增的数字。
然后判断save_txt是否为true,save_txt 在run函数以及parse_opt()函数中都有相应操作,如果传入save_txt,新建“labels”文件夹存储结果.
这个过程中,如果目录已经存在,而exist_ok为False,那么会抛出一个异常,指示目录已存在。如果exist_ok为True,
则不会抛出异常,而是直接使用已经存在的目录。
"""
##Directories
##save_dir是保存运行结果的文件夹名,是通过递增的方式来命名的。第一次运行时的路径是“runs\detect\exp",第二次运行时的路径是“runs\detect\exp1"
save_dir=increment_path(Path(project)/name,exist_ok=exist_ok)
##根据前面生成的路径创建文件夹
(save_dir/'labels' if save_txt else save_dir).mkdir(parents=True,exist_ok=True)##make dir 创建文件夹,如果save_txt为True,则创建labels文件夹,否则创建save_dir文件夹
########################4.加载模型##############
"""
这段代码主要是用于选择设备、初始化模型和检查图像大小。
首先调用select_device函数选择设备,如果device为空,则使用默认设备。
然后使用DetectMultiBackend类来初始化模型,其中
。weights 指模型的权重路径
。device 指设备
。dnn 指是否使用OpenCV DNN
。data 指数据集配置文件的路径
。fp16 指是否使用半精度浮点数进行推理
接着从模型中获取stride、names和pt等参数,其中
。stride 指下采样率
。names 指模型预测的类别名称
。pt 是Pytorch模型对象
最后调用check_img_size函数检查图像大小是否符合要求,如果不符合则进行调整。
"""
#load model 加载模型
##获取设备cpu/CUDA
device=select_device(device)
##DetectMultiBackend定义在models.common模块中,是我们要加载的网络,其中weights参数就是输入时指定的权重文件(比如yolo5s.pt)
model=DetectMultiBackend(weights,device=device,dnn=dnn,data=data, fp16=half)##加载模型,DetectMultiBackend()函数用于加载模型,weights为模型路径,device为设备,dnn为是否使用opencv dnn,data为数据集,fp16为是否使用fp16推理
stride,names,pt,jit,onnx=model.stride,model.names,model.pt,model.jit,model.onnx
##确保输入图片的尺寸imgsz能够整除stride=32 ;如果不能,则调整为能被整除并返回
imgsz=check_img_size(imgsz,s=stride)##check image size
#Half
##如果不是cpu,使用半进度(图片半精度/模型半精度)
########################5.加载数据##############
"""
这段代码是根据输入的 source 参数来判断是否是通过 webcam 摄像头捕捉视频流。
。如果是,则使用 LoadStreams 加载视频流
。否则,使用 Loadlmages 加载图像
如果是 webcam 模式,则设置cudnn.benchmark = True 以加速常量图像大小的推理,bs 表示 batch_size (批量大小),这里是1或视频流中的帧数。
vid_path 和 vid_writer 分别是视频路径和视频编写器,初始化为长度为 batch_size 的空列表
"""
##通过不同的输入源来设置不同的数据加载方式
bs=1##batch_size,初始化batch_size==1
if webcam:##使用摄像头作为输入
view_img=check_imshow(warn=True)##是否显示图片,如果view_img为True,则显示图片 检测cv2.imshow()方法是否可以执行,不能执行抛出异常
cudnn.benchmark=True##以加速常量图像大小的推理,可以加速预测
dataset=LoadStreams(source,img_size=imgsz,stride=stride,auto=pt , vid_stride=vid_stride)##创建LoadStream()对象,加载输入数据流。source为输入源,img_size为图像大小,stride为模型的stride,auto为是否自动选择设备,vid_stride为视频帧率
"""
source: 输入数据源;image_size 图片识别前被放缩到的大小;
stride: 识别时的步长 stride 指下采样率
auto的作用可以看utils.augmentations.letterbox方法,它决定了是否需要将图片填充为正方形,如果auto=True则不需要
"""
bs=len(dataset) ##bs为:batch_size为数据集的长度
elif screenshot:##如果source是截图,则创建LoadScreenshot()对象
dataset=LoadScreenshots(source,img_size=imgsz,stride=stride,auto=pt)##创建LoadScreenshots()对象,source为输入源,img_size为图像大小,stride为模型的stride,auto为是否自动选择设备
"""需要加auto参数,并设置为False,主要是为了把参数传到以下函数里,不然自动padding的时候图像大小可能会变化,导致报错"""
else:
dataset=LoadImages(source,img_size=imgsz,stride=stride,auto=pt,vid_stride=vid_stride)##创建LoadImages()对象,直接加载图片,source为输入源,img_size为图像大小,stride为模型的stride,auto为是否自动选择设备,vid_stride为视频帧率
vid_path,vid_writer=[None]*bs,[None]*bs#初始化vid_path和vid_writer,vid_path为视频路径,vid_writer为视频写入对象
"""
stride=32
pt=True
jit=False
onnx=False
name={0: 'person', 1: 'bicycle', 2: 'car', 3: 'motorcycle', 4: 'airplane', 5: 'bus', 6: 'train', 7: 'truck', 8: 'boat', 9: 'traffic light', 10: 'fire hydrant',
1: 'stop sign', 12: 'parking meter', 13: 'bench', 14: 'bird', 15: 'cat', 16: 'dog', 17: 'horse', 18: 'sheep', 19: 'cow', 20: 'elephant', 21: 'bear', 22: 'zebra',
23: 'giraffe', 24: 'backpack', 25: 'umbrella', 26: 'handbag', 27: 'tie', 28: 'suitcase', 29: 'frisbee', 30: 'skis', 31: 'snowboard', 32: 'sports ball', 33: 'kite',
34: 'baseball bat', 35: 'baseball glove', 36: 'skateboard', 37: 'surfboard', 38: 'tennis racket', 39: 'bottle', 40: 'wine glass', 41: 'cup', 42: 'fork',
43: 'knife', 44: 'spoon', 45: 'bowl', 46: 'banana', 47: 'apple', 48: 'sandwich', 49: 'orange', 50: 'broccoli', 51: 'carrot', 52: 'hot dog', 53: 'pizza',
54: 'donut', 55: 'cake', 56: 'chair', 57: 'couch', 58: 'potted plant', 59: 'bed', 60: 'dining table', 61: 'toilet', 62: 'tv', 63: 'laptop', 64: 'mouse',
65: 'remote', 66: 'keyboard', 67: 'cell phone', 68: 'microwave', 69: 'oven',
70: 'toaster', 71: 'sink', 72: 'refrigerator', 73: 'book', 74: 'clock', 75: 'vase', 76: 'scissors', 77: 'teddy bear', 78: 'hair drier', 79: 'toothbrush'}
"""
"""
stride: 推理时所用到的步长,默认为32, 大步长适合于大目标,小步长适合于小目标
names: 保存推理结果名的列表,比如默认模型的值是["person","bicycle','car",...]
pt: 加载的是否是pytorch模型《也就是pt格式的文件)
jit: 当某段代码即将第一次被执行时进行编译,因而叫“即时编译”
onnx: 利用Pytorch我们可以将model.pt转化为model.onnx格式的权重,在这里onnx充当一个后缀名称,model.onnx就代表ONNX格式的权重文件,
这个权重文件不仅包含了权重值,也包含了神经网络的网络流动信息以及每一层网络的输入输出信息和一些其他的辅助信息。
"""
####################################推理部分##########################
"""
这段代码进行了模型的热身 (warmup) 操作,即对模型进行一些预处理以加速后续的推理过程.
首先定义了一些变量,包括seen、windows和dt,分别表示已处理的图片数量、窗口列表和时间消耗列表。遍历 dataset ,整理图片信息,进行预测,
根据 run 函数里面的置信度以及10U参数,进行信息过流:对检测框进行后续处理,画框选择,坐标映射(640*6404标映射为原图坐标),是否保存绘画结果.
接着对数据集中的每张图片进行预处理
首先将图片转换为Tensor格式,并根据需要将其转换为FP16或FP32格式
然后将像素值从0-255转换为0.0-1.0归一化,并为批处理增加一维
最后记录时间消耗并更新dt列表
"""
##1.热身部分
# if pt and device.type!='cpu':
##使用空白图片(零矩阵)预先用GPU跑一遍预测流程,可以加速预测
##bchw
# model(torch.zeros(1,3,*imgsz).to(device).type_as(next(model.model.parameters())))##warmup # warmup,预热,用于提前加载模型,加快推理速度,imgsz为图像大小,如果pt为True或者model.triton为True,则bs=1,否则bs为数据集的长度。3为通道数,*imgsz为图像大小,即(1,3,640,640)
model.warmup(imgsz=(1 if pt or model.triton else bs,3,*imgsz))# warmup,预热,用于提前加载模型,加快推理速度,imgsz为图像大小,如果pt为True或者model.triton为True,则bs=1,否则bs为数据集的长度。3为通道数,*imgsz为图像大小,即(1,3,640,640)
#*imgsz是对imgsz进行拆包
seen,windows,dt=0,[],(Profile(),Profile(),Profile())##初始化seen,windows,dt,seen为已检测的图片数量,windows为空列表,dt为时间统计对象
for path,im,im0s,vid_cap,s in dataset:
"""
在dataset中,每次迭代的返回值是self.sources,img,img0,None,
path: 文件路径(即source)
im: resize后的图片《经过了放缩操作)
im0s: 原始图片
vid_cap=none
s:图片的基本信息,比如路径,大小
"""
with dt[0]:#开始计时,读取图片
im=torch.from_numpy(im).to(model.device)##将图片放到指定设备(如GPU)上识别。#torch.size=[3,640,480] 将图片转换为tensor,并放到模型的设备上,pytorch模型的输入必须是tensor
im=im.half() if model.fp16 else im.float()##unit8 to fp16/32 把输入从整形转化为半精度/全精度浮点数 如果模型使用fp16推理,则将图片转换为fp16,否则转换为fp32
im/=255 ##将图片归一化,将图片像素值从0-255转换为0-1
if len(im.shape)==3:#如果图片的维度为3,则添加batch维度
im=im[None]#在前面添加batch维度,即将图片的维度从3维转换为4维,即(3,640,640)转换为(1,3,640,640),pytorch模型的输入必须是4维的
with dt[1]:#开始计时,推理时间
visualize=increment_path(save_dir/Path(path).stem,mkdir=True) if visualize else False #如果visualize为True,则创建visualize文件夹,否则为False
"""
第一行代码,创建了一个名为“visualize”的变量,如果需要可视化,则将其设置为保存可视化结果的路径,否则将其设置为False。
使用increment_path函数创建路径,如果文件名已存在,则将数字附加到文件名后面以避免覆盖已有文件。
可视化文件路径。如果为True则保留推理过程中的特征图,保存在runs文件夹中
"""
pred=model(im,augment=augment,visualize=visualize)#模型预测出来的所有检测框,[3,640,480]---->torch.size=[1,18900,85](网络将(1,3,80,60,85)、(1,3,40,30,85)、(1,3,20,15,85)合成统一的输出) 85:80表示类别,5表示4个坐标和1个置信度
"""
推理,model()函数用于推理,im为输入图片,augment为是否使用数据增强,visualize为是否可视化,输出pred为一个列表,
形状为(n,6),n代表预测框的数量,6代表预测框的 坐标和置信度,类别
"""
##NMS,非极大值抑制,有用于去除重复的预测框
with dt[2]:#开始计时,NMS时间
pred=non_max_suppression(pred,conf_thres,iou_thres,classes,agnostic_nms,max_det=max_det)
"""
形状为(n,6),n代表预测框的数量,6代表预测框的 坐标和置信度,类别
5*6-->tensor([[3.98111e+02, 2.34299e+02, 4.80189e+02, 5.20503e+02, 8.96175e-01, 0.00000e+00],
[1.30764e+02, 2.41863e+02, 2.05144e+02, 5.14008e+02, 8.70149e-01, 0.00000e+00],
[2.91886e+01, 2.31116e+02, 1.47005e+02, 5.40706e+02, 8.51393e-01, 0.00000e+00],
[7.53642e+00, 1.32370e+02, 4.79800e+02, 4.67284e+02, 8.49282e-01, 5.00000e+00],
[2.55947e-02, 3.27359e+02, 4.02252e+01, 5.18738e+02, 5.35295e-01, 0.00000e+00]], device='cuda:0')
"""
"""
这段代码是执行非最大值抑制 (NMS)的步骤,用于筛选预测结果.
non_max_suppression函数的输入参数包括预测结果pred、置信度阈值conf_thres、IOU(交共比)阈值iou_thres、类别classes、
是否进行类别无关的NMSagnostic_nms,以及最大测数max_det,该函数的输出是经过NMS筛选后的预测结果。
#NMS,non_max_suppression()函数用于NMS,pred为输入的预测框,conf_thres为置信度阈值,iou_thres为iou阈值,classes为类别,
agnostic_nms为是否使用类别无关的NMS,max_det为最大检测框数量,
pred: 网络的输出结果
conf_thres: 置信度阈值
iou thres: iou阈值
classes: 是否只保留特定的类别 默认为None
agnostic_nms: 进行nms是否也去除不同类别之间的框
max det: 检测框结果的最大数量 默认1000
"""
##处理预测结果
##把所有的检测框画在原图中
for i,det in enumerate(pred):##per image每次迭代处理一张图片 # per image,遍历每张图片,enumerate()函数将pred转换为索引和值的形式,i为索引,det为对应的元素,即每个物体的预测框
"""
i;每个batch的信息
det:表示5个检测框的信息
"""
seen+=1#seen是一个计数的功能 #检测的图片数量加1
if webcam:##batch_size>=1 网络摄像头 # batch_size >= 1,如果是摄像头,则获取视频帧率
##如果输入源是webcam,则batch_size>=1,取出dataset中的一张图片
p,im0,frame=path[i],im0s[i].copy(),dataset.count #path[i]为路径列表,ims[i].copy()为将输入图像的副本存储在im0变量中,dataset.count为当前输入图像的帧数
s+=f'{i}:'#s后面拼接一个字符串i #在打印输出中添加当前处理的图像索引号i,方便调试和查看结果。在此处,如果是摄像头模式,i表示当前批次中第i张图像;否则,i始终为0,因为处理的只有一张图像。
else:
p,im0,frame=path,im0s.copy(),getattr(dataset,'frame',0)#如果不是摄像头,frame为0 im0的为原始图片形状:(1080,810,3) im为裁剪之后的大小:(1,3,640,480)
"""
大部分我们一般都是从LoadImages流读取本地文件中的照片或者视频 所以batch_size=1
p: 当前图片/视频的绝对路径 如 F:\yolo_v5\yolov5-U\data\images\bus.jpg
s: 输出信息 初始为""
im0: 原始图片 letterbox + pad 之前的图片
frame: 视频流,此次取的是第几张图片
这段代码使用了一个循环来遍历检测结果列表中的每个物体,并对每个物体进行处理。
循环中的变量“i”是一个索引变量,表示当前正在处理第几个物体,而变量"det"则表示当前物体的检测结果。循环体中的第一行代码"seen += 1”用于增加一个计数器,记录已处理的物体数量
接下来,根据是否使用网络摄像头来判断处理单张图像还是批量图像
如果使用的是网络摄像头,则代码会遍历每个图像并复制一份备份到变量"im0"中,同时将当前图像的路径和计数器记录到变量"p"和"frame"中。最后,将当前处理的物体索引和相关信息记录到字符串变量"s"中
如果没有使用网络摄像头,则会直接使用"im0s"变量中的图像,将图像路径和计数器记录到变量""和"rame"中。同时,还会检查数据集中是否有"frame"属性,如果有,则将其值记录到变量”frame"中。
"""
"""
首先将图像路径转换为"Path"对象
接下来,使用“save_dir"变量中的路径和图像文件名来构建保存检测结果图像的完整路径,并将其保存在变量"save_path"中。
根据数据集的模式("image"或"video")来构建保存检测结果标签的文件路径,并将其保存在变量"txt_path"中。
在处理图像路径和文件路径之后,将图像的尺寸信息添加到宁符串变量“s"中,以便于打印。接着,计算归一化增益”n",并将其保存在变量中,以便后续使用。
然后,根据是否需要保存截取图像的标志“save_crop"来选择是否要对原始图像进行复制,以备保存截取图像时使用。最后,创建了一个”Annotator”对象,以便于在图像上绘制检测结果
"""
p=Path(p)##to Path 将路径转换为Path对象
#图片和视频的保存路径save_path
save_path=str(save_dir/p.name)##im.jpg ,保存图片的路径,save_dir为保存图片的文件夹,p.name为图片名称 'runs\\detect\\exp12\\bus.jpg'
##设置保存框坐标的txt文件路径,每张图片对应一个框坐标信息
txt_path=str(save_dir/'labels' /p.stem)+('' if dataset.mode =='image' else f'_{frame}')##im.txt 保存预测框的路径,save_dir为保存图片的文件夹,p.stem为图片名称,dataset.mode为数据集的模式,如果是image,则为图片,否则为视频
#'runs\\detect\\exp12\\labels\\bus'
##设置输出图片信息。图片shape(w,h)
s+='%gx%g' % im.shape[2:]#print string,打印输出,im.shape[2:]为图片的宽和高 'image 1/2 E:\\AI\\yolov5-7.0\\data\\images\\bus.jpg: 640x480 '
#得到原图的宽和高
gn=torch.tensor(im0.shape)[[1,0,1,0]]# normalization gain whwh,归一化因子,用于将预测框的坐标从归一化坐标转换为原始坐标 tensor([ 810, 1080, 810, 1080])
##保存截图。如果save_crop的值为true,则将检测到的bounding_box单独保存成一张图片。
imc=im0.copy() if save_crop else im0 #for save_crop # for save_crop,如果save_crop为True,则将im0复制一份,否则为im0 (1080,810,3)
##得到一个绘图的类,类中预先存储了原图、线条宽度、类名
annotator=Annotator(im0,line_width=line_thickness,example=str(names)) #创建Annotator对象,用于在图片上绘制预测框和标签,im0为输入图片,line_width为线宽,example为标签
if len(det):#判断有没有框,如果预测框的数量大于0
"""
将预测信息映射到原图
将标注的bounding_box大小调整为和原图一致(因为训练时原图经过了放缩》此时坐标格式为xyxy
"""
# Rescale boxes from img_size to im0 size
##im.shape[2:]-->640x480 det-->(5,6)预测框 im0.shape___原始图片-->(1080,810,3)
##det[:,:4]前4列为4个坐标值
det[:,:4]=scale_boxes(im.shape[2:],det[:,:4],im0.shape).round()###scale_coords:坐标映射功能。将预测框的坐标从归一化坐标转换为原始坐标,im.shape[2:]为图片的宽和高,det[:, :4]为预测框的坐标,im0.shape为图片的宽和高
##打印检测到的类别数量
"""
这段代码会判断有没有框。如果检测结果列表中存在物体,则代码会执行一些操作.
首先,将检测结果中的物体坐标从缩放后的图像大小还原回原始图像的大小。这里使用了一个名为”scale_coords"的函数来进行缩放,该函数的作用是将物体坐标从缩放前的大小变换到缩放后的大小。
接着,遍历每个物体,将其类别和数量添加到字符串变量”s"中。具体来说,计算当前类别下检测到的物体数量"n",然后根据数量和类别名字构建一段字符串,并将其添加到变量"s"中。
代码中的"names"变量包含了数据集中所有类别的名称。
最后,返回字符串变量"s”,并结束当前代码块
"""
for c in det[:,-1].unique(): #遍历每个类别,unique()用于获取检测结果中不同类别是数量
n=(det[:,-1]==c).sum()# #n为每个类别的预测框的数量
s+=f"{n}{names[int(c)]}{'s'*(n>1)},"##'image 1/2 E:\\AI\\yolov5-7.0\\data\\images\\bus.jpg: 640x480 4 persons, 1 bus, ' #s为每个类别的预测框的数量和类别
##保存预测结果:txt/图片画框/crop-image
"""
这段代码是打印目标检测结果的一些操作:
如果存在目标检测结果,则代码会执行下一步操作,这里是将检测结果写入文件或在图像上添加框并保存。
如果需要将检测结果写入文件,则将检测结果中的物体坐标转换为相对于原始图像的归一化坐标,并将其写入到以图像文件名命名的”.txt"文件中,
在写入文件时,代码将包含类别、位置和可选置信度等信息。文件的保存路径是变量"txt path"。
如果需要保存检测结果图像或者在图像上绘制框,每个物体添加一个边界框,并将其标记在图像上。具体来说,将边界框选择一个颜色,并在边界框周围添加标签 (可选)。
如果需要将边界框截取出来保存,则调用名为"save one box"的函数,将边界框从图像中截取出来,并将其保存到特定的文件夹中。
这些操作都是基于一些设置变量(如"sae xt"、"save im"等)来控制的,这些变量决定了检测结果是否应该写入文件或图像.最后,如果需要在窗口中查看检测结果,则代码会在图像上绘制边界框并显示图像。
"""
"""
其中det是YOLOv5识别出来的结果,例如tensor([[121.00000, 7.00000, 480.00000, 305.00000, 0.67680, 0.00000],
[278.00000, 166.00000, 318.00000, 305.00000, 0.66222, 27.00000]])就是识别出了两个物体。
xyxy是物体检测框的坐标,对于上面的例子的第一个物体,xyxy = [121.00000, 7.00000, 480.00000, 305.00000]对应坐标(121, 7)和
(480, 305),两个点可以确定一个矩形也就是检测框。conf是该物体的置信度,第一个物体置信度为0.67680。cls则是该物体对应的类别,
这里0对应的是“人”,因为我们只识别人的情感,所以cls不是0就可以跳过该过程。
"""
"""
这段代码是打印目标检测结果的一些操作.如果存在目标检测结果,则代码会执行下一步操作,这里是将检测结果写入文件或在图像上添加框并保存.
如果需要将检测结果写入文件,则将检测结果中的物体坐标转换为相对于原始图像的归一化坐标,并将其写入到以图像文件名命名的".txt"文件中。
在写入文件时,代码将包合类别、位置和可选置信度等信息。文件的保存路径是变量"txt path"。
如果需要保存检测结果图像或者在图像上绘制框,每个物体添加一个边界框,并将其标记在图像上。具体来说,将边界框选择一个颜色,并在边界框周围添加标签 (可选)。
如果需要将边界框截取出来保存,则调用名为"save one box"的函数,将边界框从图像中截取出来,并将其保存到特定的文件夹中。
这些操作都是基于一些设置变量(如"save t"、"save img"等)来控制的,这些变量决定了检测结果是否应该写入文件或图像最后,如果需要在窗口中查看检测结果,则代码会在图像上绘制边界框并显示图像。
"""
for *xyxy,conf,cls in reversed(det):
##将每个图片的预测信息分别存入save_dir/labels下的xxx.txt中 每行:class_id+score+xywh
##将xyxy(左上角+右下角)格式转为xywh(中心点+宽长)格式,并归一化,转化为列表再保存
if save_txt: # Write to file,如果save_txt为True,则将预测框的坐标和类别写入txt文件中
xywh = (xyxy2xywh(torch.tensor(xyxy).view(1, 4)) / gn).view(
-1).tolist() # normalized xywh,将预测框的坐标从原始坐标转换为归一化坐标
line = (cls, *xywh, conf) if save_conf else (
cls, *xywh) # label format,如果save_conf为True,则将置信度也写入txt文件中
with open(f'{txt_path}.txt', 'a') as f: # 打开txt文件,'a'表示追加
f.write(('%g ' * len(line)).rstrip() % line + '\n') # 写入txt文件
# 在原图上画框+将预测到的目标剪切出来保存成图片,保存在save_dir/crops下,在原图像画图或者保存结果
if save_img or save_crop or view_img:##Add bbox to image
c=int(cls)##integer class ##获取类别标号
label =None if hide_labels else (names[c] if hide_conf else f'{names[c]}{conf:.2f}')##类别名 #如果hide_labels为True,则不显示标签,否则显示标签,如果hide_conf为True,则不显示置信度,否则显示置信度
annotator.box_label(xyxy,label,color=colors(c,True))#绘制边框 #绘制预测框和标签
### 在原图上画框+将预测到的目标剪切出来保存成图片,保存在save_dir/crops下,单独保存
if save_crop:#如果save_crop为True,则保存裁剪的图片
save_one_box(xyxy,imc,file=save_dir/'crops'/names[c] /f'{p.stem}.jpg',BGR=True)
"""这段代码是实现在输出窗口实时查看检测结果
如果需要在窗口中实时查看检测结果,则会使用OpenCV库中的函数将图像显示在窗口中,并等待1毫秒以便继续下一帧的检测,
代码会检查是否已经为当前图像创建了窗口 (if p not in windows),并在必要时创建窗口,并使用图像名称来命名该窗口。
窗口的名称是由变量"p”指定的图像路径名。
如果检测到图像尚未在窗口中打开,则代码会创建一个新窗口并将图像显示在窗口中
如果图像已经在窗口中打开,则代码会直接更新窗口中的图像"""
#Stream results,在图上绘制预测框和标签展示
im0=annotator.result() ##获取绘制预测框和标签图片
if view_img:#如果view_img为True,则展示图片
if platform.system()=="Linux" and p not in windows:#如果系统为Linux,且p不在windows中
windows.append(p)#将p添加到windows中
cv2.namedWindow(str(p),cv2.WINDOW_NORMAL | cv2.WINDOW_KEEPRATIO)# allow window resize (Linux),允许窗口调整大小,WINDOW_NORMAL表示用户可以调整窗口大小,WINDOW_KEEPRATIO表示窗口大小不变
cv2.resizeWindow(str(p), im0.shape[1], im0.shape[0]) # 调整窗口大小,使其与图片大小一致
cv2.imshow(str(p), im0) # 显示图片
cv2.waitKey(1) # 1 millisecond #等待1毫秒
"""这段代码是设置保存图片和视频
首先“save_img"判断是否是图片,如果是则保存路径和图片;如果是视频或流,需要重新创建视频文件。保存视频文件中
如果是视频“vid_cap”,则使用python-opencv读取视频,计算视频速率FPS以及视频顿宽度和高度.
如果是流,则保存路径后缀加上mp4"""
# Save results (image with detections) 设置保存图片和视频
if save_img: # 如果save_img为True,则保存绘制完的图片
if dataset.mode == 'image': # 如果数据集模式为image
cv2.imwrite(save_path, im0) # 如果是图片,保存图片
else: # 'video' or 'stream',如果数据集模式为video(视频)或stream(流)
if vid_path[i] != save_path: # new video,如果vid_path[i]不等于save_path
vid_path[i] = save_path # 将save_path赋值给vid_path[i]
##以下是保存视频文件
if isinstance(vid_writer[i], cv2.VideoWriter): # 如果vid_writer[i]是cv2.VideoWriter类型
vid_writer[i].release() # release previous video writer
if vid_cap: # video
fps = vid_cap.get(cv2.CAP_PROP_FPS) # 获取视频的帧率 FPS
w = int(vid_cap.get(cv2.CAP_PROP_FRAME_WIDTH)) # 获取视频的宽度
h = int(vid_cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) # 获取视频的高度
else: # stream
fps, w, h = 30, im0.shape[1], im0.shape[0]
save_path = str(Path(save_path).with_suffix('.mp4')) # force *.mp4 suffix on results videos
vid_writer[i] = cv2.VideoWriter(save_path, cv2.VideoWriter_fourcc(*'mp4v'), fps, (w, h))
vid_writer[i].write(im0)
# Print time (inference-only),打印时间
print(dt[1])# 不是图片,是一个对象
print(dt[1].dt)##0.08513545989990234---->#推理时间
LOGGER.info(f"{s}{'' if len(det) else '(no detections), '}{dt[1].dt * 1E3:.1f}ms")
"""
dt=(, , )
image 1/2 E:\AI\yolov5-7.0\data\images\bus.jpg: 640x480 4 persons, 1 bus, 58.3ms
image 2/2 E:\AI\yolov5-7.0\data\images\zidane.jpg: 384x640 2 persons, 2 ties, 78.3ms
"""
# Print results,打印结果
t = tuple(x.t / seen * 1E3 for x in dt) # speeds per image,每张图片的速度
LOGGER.info(
f'Speed: %.1fms pre-process, %.1fms inference, %.1fms NMS per image at shape {(1, 3, *imgsz)}' % t) # 打印速度
#Speed: 3737.0ms pre-process, 68.3ms inference, 5.1ms NMS per image at shape (1, 3, 640, 640)
if save_txt or save_img:
s = f"\n{len(list(save_dir.glob('labels/*.txt')))} labels saved to {save_dir / 'labels'}" if save_txt else '' # 如果save_txt为True,则打印保存的标签数量
LOGGER.info(f"Results saved to {colorstr('bold', save_dir)}{s}") # 打印保存的路径
#Results saved to runs\detect\exp13
if update:
strip_optimizer(weights[0]) # update model (to fix SourceChangeWarning)
"""我们来对推理过程这一部分代码做一个总结:
这一段代码是一个目标检测算法中的推理过程,通过对一张或多张图片中的物体进行检测,输出检测结果,并将检测结果保存到文件或显示在窗口中。以下是每个步骤的详细说明:
1.对于每个输入图片,将其路径、原始图像和当前顿数(如果存在)分别赋值给p、im0和frame变量;
2.如果webcam为True,则将输出信息字符串s初始化为空,否则将其初始化为该数据集的“frame”属性;
3.将p转换为Path类型,并生成保存检测结果的路径save_path和文本文件路径txt_path;
4.将im0大小与目标检测的输入大小匹配,将检测结果det中的边界框坐标从img_size缩放到im0大小,然后将结果打印在输出字符串s中;
5.如果save_txt为True,则将结果写入文本文件中;
6.如果save_img、save_crop或view_img中任意一个为True,则将检测结果添加到图像中,并在窗口中显示结果
7.如果save_img为True,则保存结果图像;
8.如果是视频数据集,则将结果写入视频文件中
9.最后,打印每个图片的检测时间。."""
"""
mkdir这个函数是在pathlib.Path.mkdir这里,通过import pathlib导入;
pathlib的mkdir接收两个参数:
mkdir(parents=True, exist_ok=True)
parents=True表示,如果父目录不存在,则新建父目录.
exist_ok=True表示,如果文件夹已存在,那么就不做任何处理,也不抛出异常。
这个过程中,如果目录已经存在,而exist_ok为False,那么会抛出一个异常,指示目录已存在。如果exist_ok为True,
则不会抛出异常,而是直接使用已经存在的目录。
"""
# ####二、执行main函数
"""这是程序的主函数。它调用了check_requirements()函数和run()函数,并将命令行参数opt转换为字典,作为参数传递给run()函数"""
def main(opt):
##检查环境/打印参数,主要是requirement.txt的包是否安装,用彩色显示设置的参数
check_requirements(exclude=('tensorboard','thop'))###检查程序所需的依赖项是否已安装
#执行run()函数
run(**vars(opt))##将opt变量的属性和属性值作为关键字参数传递给run()函数 **vars(opt)--》将=转为: 解包
"""
{'weights': WindowsPath('yolov5s.pt'), 'source': WindowsPath('data/images'), 'data': WindowsPath('data/coco128.yaml'),
'imgsz': [640, 640], 'conf_thres': 0.25, 'iou_thres': 0.45, 'max_det': 1000, 'device': '', 'view_img': False,
'save_txt': False, 'save_conf': False, 'save_crop': False, 'nosave': False, 'classes': None, 'agnostic_nms': False,
'augment': False, 'visualize': False, 'update': False, 'project': WindowsPath('runs/detect'), 'name': 'exp',
'exist_ok': False, 'line_thickness': 3, 'hide_labels': False, 'hide_conf': False, 'half': False, 'dnn': False, 'vid_stride': 1}
"""
##命令使用:python detect.py --weights runs/train/exp_yolov5s/weights/best.pt --source data/images/fishman.jpg #webcam
if __name__ == '__main__':
opt=parse_opt()##解析参数 解析命令行传进的参数。该代码分为三部分:第一部分定义了一些可以传导的参数类型,第二部分对于imgsize部分进行了额外的判断(640*640)
#第三部分打印所有参数信息,opt变量存储所有参数信息,并返回。
main(opt)#执行主函数 执行命令行参数。该段代码分为两部分,第一部分首先完成对于requirements.txt的检查,检查这些依赖包有没有安装;第二部分,将opt变量参数传入,执行run函数。