val.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  1. # YOLOv5 🚀 by Ultralytics, GPL-3.0 license
  2. """
  3. Validate a trained YOLOv5 model accuracy on a custom dataset
  4. Usage:
  5. $ python path/to/val.py --weights yolov5s.pt --data coco128.yaml --img 640
  6. Usage - formats:
  7. $ python path/to/val.py --weights yolov5s.pt # PyTorch
  8. yolov5s.torchscript # TorchScript
  9. yolov5s.onnx # ONNX Runtime or OpenCV DNN with --dnn
  10. yolov5s.xml # OpenVINO
  11. yolov5s.engine # TensorRT
  12. yolov5s.mlmodel # CoreML (macOS-only)
  13. yolov5s_saved_model # TensorFlow SavedModel
  14. yolov5s.pb # TensorFlow GraphDef
  15. yolov5s.tflite # TensorFlow Lite
  16. yolov5s_edgetpu.tflite # TensorFlow Edge TPU
  17. """
  18. import argparse
  19. import json
  20. import os
  21. import sys
  22. from pathlib import Path
  23. from threading import Thread
  24. import numpy as np
  25. import torch
  26. from tqdm.auto import tqdm
  27. FILE = Path(__file__).resolve()
  28. ROOT = FILE.parents[0] # YOLOv5 root directory
  29. if str(ROOT) not in sys.path:
  30. sys.path.append(str(ROOT)) # add ROOT to PATH
  31. ROOT = Path(os.path.relpath(ROOT, Path.cwd())) # relative
  32. from models.common import DetectMultiBackend
  33. from utils.callbacks import Callbacks
  34. from utils.datasets import create_dataloader
  35. from utils.general import (LOGGER, check_dataset, check_img_size, check_requirements, check_yaml,
  36. coco80_to_coco91_class, colorstr, increment_path, non_max_suppression, print_args,
  37. scale_coords, xywh2xyxy, xyxy2xywh)
  38. from utils.metrics import ConfusionMatrix, ap_per_class, box_iou
  39. from utils.plots import output_to_target, plot_images, plot_val_study
  40. from utils.torch_utils import select_device, time_sync
  41. def save_one_txt(predn, save_conf, shape, file):
  42. # Save one txt result
  43. gn = torch.tensor(shape)[[1, 0, 1, 0]] # normalization gain whwh
  44. for *xyxy, conf, cls in predn.tolist():
  45. xywh = (xyxy2xywh(torch.tensor(xyxy).view(1, 4)) / gn).view(-1).tolist() # normalized xywh
  46. line = (cls, *xywh, conf) if save_conf else (cls, *xywh) # label format
  47. with open(file, 'a') as f:
  48. f.write(('%g ' * len(line)).rstrip() % line + '\n')
  49. def save_one_json(predn, jdict, path, class_map):
  50. # Save one JSON result {"image_id": 42, "category_id": 18, "bbox": [258.15, 41.29, 348.26, 243.78], "score": 0.236}
  51. image_id = int(path.stem) if path.stem.isnumeric() else path.stem
  52. box = xyxy2xywh(predn[:, :4]) # xywh
  53. box[:, :2] -= box[:, 2:] / 2 # xy center to top-left corner
  54. for p, b in zip(predn.tolist(), box.tolist()):
  55. jdict.append({
  56. 'image_id': image_id,
  57. 'category_id': class_map[int(p[5])],
  58. 'bbox': [round(x, 3) for x in b],
  59. 'score': round(p[4], 5)})
  60. def process_batch(detections, labels, iouv):
  61. """
  62. Return correct predictions matrix. Both sets of boxes are in (x1, y1, x2, y2) format.
  63. Arguments:
  64. detections (Array[N, 6]), x1, y1, x2, y2, conf, class
  65. labels (Array[M, 5]), class, x1, y1, x2, y2
  66. Returns:
  67. correct (Array[N, 10]), for 10 IoU levels
  68. """
  69. correct = torch.zeros(detections.shape[0], iouv.shape[0], dtype=torch.bool, device=iouv.device)
  70. iou = box_iou(labels[:, 1:], detections[:, :4])
  71. x = torch.where((iou >= iouv[0]) & (labels[:, 0:1] == detections[:, 5])) # IoU above threshold and classes match
  72. if x[0].shape[0]:
  73. matches = torch.cat((torch.stack(x, 1), iou[x[0], x[1]][:, None]), 1).cpu().numpy() # [label, detection, iou]
  74. if x[0].shape[0] > 1:
  75. matches = matches[matches[:, 2].argsort()[::-1]]
  76. matches = matches[np.unique(matches[:, 1], return_index=True)[1]]
  77. # matches = matches[matches[:, 2].argsort()[::-1]]
  78. matches = matches[np.unique(matches[:, 0], return_index=True)[1]]
  79. matches = torch.from_numpy(matches).to(iouv.device)
  80. correct[matches[:, 1].long()] = matches[:, 2:3] >= iouv
  81. return correct
  82. @torch.no_grad()
  83. def run(
  84. data, # 数据集配置文件地址 包含数据集的路径、类别个数、类名、下载地址等信息
  85. weights=None, # model.pt path(s) 模型的权重文件地址
  86. batch_size=32, # batch size 前向传播的批次大小
  87. imgsz=640, # inference size (pixels) 输入网络的图片分辨率
  88. conf_thres=0.001, # confidence threshold object置信度阈值
  89. iou_thres=0.6, # NMS IoU threshold 进行NMS时IOU的阈值
  90. task='val', # train, val, test, speed or study 设置测试的类型 有train, val, test, speed or study几种
  91. device='', # cuda device, i.e. 0 or 0,1,2,3 or cpu 测试的设备
  92. workers=8, # max dataloader workers (per RANK in DDP mode)
  93. single_cls=False, # treat as single-class dataset 数据集是否只用一个类别
  94. augment=False, # augmented inference 测试是否使用TTA Test Time Augment
  95. verbose=False, # verbose output 是否打印出每个类别的mAP
  96. save_txt=False, # save results to *.txt 是否以txt文件的形式保存模型预测框的坐标
  97. save_hybrid=False, # save label+prediction hybrid results to *.txt 是否保存
  98. save_conf=False, # save confidences in --save-txt labels 是否保存预测每个目标的置信度到预测tx文件中
  99. save_json=False, # save a COCO-JSON results file 是否按照coco的json格式保存预测框,并且使用cocoapi做评估
  100. project=ROOT / 'runs/val', # save to project/name 测试保存的源文件
  101. name='exp', # save to project/name 测试保存的文件地址
  102. exist_ok=False, # existing project/name ok, do not increment 是否存在当前文件
  103. half=True, # use FP16 half-precision inference 是否使用半精度推理
  104. dnn=False, # use OpenCV DNN for ONNX inference 是否使用Opencv DNN 进行 ONNX 推理
  105. model=None, # 模型
  106. dataloader=None, # 数据加载器
  107. save_dir=Path(''), # 文件保存路径
  108. plots=True,# 是否可视化
  109. callbacks=Callbacks(),
  110. compute_loss=None, # 损失函数
  111. ):
  112. # Initialize/load model and set device
  113. training = model is not None
  114. if training: # called by train.py
  115. # 判断是否是训练时调用run函数(执行train.py脚本), 如果是就使用训练时的设备 一般都是train
  116. device, pt, jit, engine = next(model.parameters()).device, True, False, False # get model device, PyTorch model
  117. half &= device.type != 'cpu' # half precision only supported on CUDA
  118. model.half() if half else model.float()
  119. else: # called directly
  120. # 如果不是train.py调用run函数(执行val.py脚本)就调用select_device选择可用的设备
  121. # 并生成save_dir + 加载模型model + 检查输入图片的尺寸 + 加载data配置信息
  122. device = select_device(device, batch_size=batch_size)
  123. # Directories 生成save_dir文件路径
  124. save_dir = increment_path(Path(project) / name, exist_ok=exist_ok) # increment run 生成增量文件夹 runs/val/exp8
  125. (save_dir / 'labels' if save_txt else save_dir).mkdir(parents=True, exist_ok=True) # make dir
  126. # Load model 加载模型
  127. model = DetectMultiBackend(weights, device=device, dnn=dnn, data=data, fp16=half) #检测编译框架,根据不同的编译框架读取不同类型的权重文件 pytorch、tensorflow、tensorrt等
  128. stride, pt, jit, engine = model.stride, model.pt, model.jit, model.engine
  129. imgsz = check_img_size(imgsz, s=stride) # check image size 检查输入图片的尺寸是否能被 stride(32) 整除,如果不能则调整图片大小后返回
  130. half = model.fp16 # FP16 supported on limited backends with CUDA
  131. if engine:
  132. batch_size = model.batch_size
  133. else:
  134. device = model.device
  135. if not (pt or jit):
  136. batch_size = 1 # export.py models default to batch-size 1
  137. LOGGER.info(f'Forcing --batch-size 1 square inference (1,3,{imgsz},{imgsz}) for non-PyTorch models')
  138. # Data
  139. data = check_dataset(data) # check 下载或者解压数据集
  140. # Configure
  141. model.eval() #模型验证模式
  142. cuda = device.type != 'cpu'
  143. is_coco = isinstance(data.get('val'), str) and data['val'].endswith(f'coco{os.sep}val2017.txt') # COCO dataset
  144. nc = 1 if single_cls else int(data['nc']) # number of classes
  145. # 计算mAP相关参数
  146. # 设置iou阈值 从0.5-0.95取10个(0.05间隔) iou vector for mAP@0.5:0.95
  147. # iouv: [0.50000, 0.55000, 0.60000, 0.65000, 0.70000, 0.75000, 0.80000, 0.85000, 0.90000, 0.95000]
  148. iouv = torch.linspace(0.5, 0.95, 10, device=device) # iou vector for mAP@0.5:0.95 计算mAP相关参数分组,从0.5-0.95取10个
  149. niou = iouv.numel() # 统计mAP@0.5:0.95的分组数
  150. # 如果不是训练就调用create_dataloader生成dataloader
  151. # 如果是训练就不需要生成dataloader 可以直接从参数中传过来testloader
  152. # Dataloader
  153. if not training:
  154. if pt and not single_cls: # check --weights are trained on --data 检查权重和多标签预测是否为True
  155. ncm = model.model.nc
  156. assert ncm == nc, f'{weights[0]} ({ncm} classes) trained on different --data than what you passed ({nc} ' \
  157. f'classes). Pass correct combination of --weights and --data that are trained together.'
  158. model.warmup(imgsz=(1 if pt else batch_size, 3, imgsz, imgsz)) # warmup
  159. pad = 0.0 if task in ('speed', 'benchmark') else 0.5
  160. rect = False if task == 'benchmark' else pt # square inference for benchmarks
  161. task = task if task in ('train', 'val', 'test') else 'val' # path to train/val/test images
  162. # 创建dataloader 这里的rect默认为True 矩形推理用于测试集 在不影响mAP的情况下可以大大提升推理速度。
  163. dataloader = create_dataloader(data[task],
  164. imgsz,
  165. batch_size,
  166. stride,
  167. single_cls,
  168. pad=pad,
  169. rect=rect,
  170. workers=workers,
  171. prefix=colorstr(f'{task}: '))[0]
  172. seen = 0 # 初始化测试的图片数量
  173. confusion_matrix = ConfusionMatrix(nc=nc) # 初始化混淆矩阵
  174. names = {k: v for k, v in enumerate(model.names if hasattr(model, 'names') else model.module.names)} # 获取数据集所有的类别名称
  175. class_map = coco80_to_coco91_class() if is_coco else list(range(1000))# 获取coco数据集的类别索引,如果没有则range1000
  176. s = ('%20s' + '%11s' * 6) % ('Class', 'Images', 'Labels', 'P', 'R', 'mAP@.5', 'mAP@.5:.95') #进度
  177. dt, p, r, f1, mp, mr, map50, map = [0.0, 0.0, 0.0], 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 # 初始化p, r, f1, mp, mr, map50, map指标和时间t0, t1, t2
  178. loss = torch.zeros(3, device=device)# 初始化测试集的损失
  179. jdict, stats, ap, ap_class = [], [], [], [] # 初始化json文件中的字典、统计信息、ap等
  180. callbacks.run('on_val_start')
  181. pbar = tqdm(dataloader, desc=s, bar_format='{l_bar}{bar:10}{r_bar}{bar:-10b}') # progress bar 进度
  182. # 验证
  183. for batch_i, (im, targets, paths, shapes) in enumerate(pbar):
  184. callbacks.run('on_val_batch_start')
  185. t1 = time_sync()
  186. if cuda:
  187. im = im.to(device, non_blocking=True)
  188. targets = targets.to(device)
  189. im = im.half() if half else im.float() # uint8 to fp16/32 是否使用半精度
  190. im /= 255 # 0 - 255 to 0.0 - 1.0
  191. nb, _, height, width = im.shape # batch size, channels, height, width
  192. t2 = time_sync()
  193. dt[0] += t2 - t1
  194. # Inference 向前推理
  195. """
  196. out: 推理结果 1个[bs, anchor_num*grid_w*grid_h, xywh+c+20class] = [1, (3*80*80)+(3*40*40)+(3*20*20), 25]
  197. train_out: 训练结果 3个[bs, anchor_num, grid_w, grid_h, xywh+c+20classes] = [1, 3, 80, 80, 25] [1, 3, 40, 40, 25] [1, 3, 20, 20, 25]
  198. """
  199. out, train_out = model(im) if training else model(im, augment=augment, val=True) # inference, loss outputs
  200. dt[1] += time_sync() - t2 # 累计前向推理时间
  201. # Loss 计算验证集损失
  202. if compute_loss:
  203. loss += compute_loss([x.float() for x in train_out], targets)[1] # box, obj, cls
  204. # NMS 将真实框target的xywh(因为target是在labelimg中做了一个归一化)映射到img(test)尺寸
  205. targets[:, 2:] *= torch.tensor((width, height, width, height), device=device) # to pixels
  206. # save_hybrid: adding the dataset labels to the model predictions before NMS
  207. # 是在NMS之前将数据集标签targets添加到模型预测中
  208. # 这允许在数据集中自动标记(for autolabelling)其他对象(在pred中混入gt) 并且mAP反映了新的混合标签
  209. # targets: [num_target, img_index+class_index+xywh] = [31, 6]
  210. # lb: {list: bs} 第一张图片的target[17, 5] 第二张[1, 5] 第三张[7, 5] 第四张[6, 5]
  211. lb = [targets[targets[:, 0] == i, 1:] for i in range(nb)] if save_hybrid else [] # for autolabelling
  212. t3 = time_sync()
  213. out = non_max_suppression(out, conf_thres, iou_thres, labels=lb, multi_label=True, agnostic=single_cls)
  214. dt[2] += time_sync() - t3
  215. # Metrics 统计每张图片的真实框、预测框信息
  216. # 为每张图片做统计,写入预测信息到txt文件,生成json文件字典,统计tp等
  217. for si, pred in enumerate(out):
  218. # 统计每张图片的真实框、预测框信息;
  219. # 获取第si张图片的gt标签信息 包括class, x, y, w, h target[:, 0]为标签属于哪张图片的编号
  220. labels = targets[targets[:, 0] == si, 1:]# [:, class+xywh]
  221. nl, npr = labels.shape[0], pred.shape[0] # number of labels, predictions
  222. path, shape = Path(paths[si]), shapes[si][0]
  223. correct = torch.zeros(npr, niou, dtype=torch.bool, device=device) # init
  224. seen += 1 # 统计测试图片数量 +1
  225. if npr == 0: # 如果预测为空,则添加空的信息到stats里
  226. if nl:
  227. stats.append((correct, *torch.zeros((3, 0), device=device)))
  228. continue
  229. # Predictions
  230. if single_cls:
  231. pred[:, 5] = 0
  232. predn = pred.clone()
  233. # 将预测坐标映射到原图img中
  234. scale_coords(im[si].shape[1:], predn[:, :4], shape, shapes[si][1]) # native-space pred
  235. # Evaluate 计算混淆矩阵 重点
  236. if nl:
  237. tbox = xywh2xyxy(labels[:, 1:5]) # target boxes 获取xyxy格式的框
  238. scale_coords(im[si].shape[1:], tbox, shape, shapes[si][1]) # native-space labels 将预测框映射到原图img
  239. labelsn = torch.cat((labels[:, 0:1], tbox), 1) # native-space labels
  240. correct = process_batch(predn, labelsn, iouv)
  241. if plots:
  242. confusion_matrix.process_batch(predn, labelsn) # 计算混淆矩阵
  243. stats.append((correct, pred[:, 4], pred[:, 5], labels[:, 0])) # (correct, conf, pcls, tcls)
  244. # Save/log
  245. if save_txt: # 保存预测信息到txt文件
  246. save_one_txt(predn, save_conf, shape, file=save_dir / 'labels' / (path.stem + '.txt'))
  247. if save_json: # 保存预测信息到json文件
  248. save_one_json(predn, jdict, path, class_map) # append to COCO-JSON dictionary
  249. callbacks.run('on_val_image_end', pred, predn, path, names, im[si])
  250. # Plot images 将测试数据集中的预测结果和真实结果分别画在对应的图像中
  251. if plots and batch_i < 3:
  252. f = save_dir / f'val_batch{batch_i}_labels.jpg' # labels
  253. Thread(target=plot_images, args=(im, targets, paths, f, names), daemon=True).start()
  254. f = save_dir / f'val_batch{batch_i}_pred.jpg' # predictions
  255. Thread(target=plot_images, args=(im, output_to_target(out), paths, f, names), daemon=True).start()
  256. callbacks.run('on_val_batch_end')
  257. # Compute metrics 计算mAP 重点
  258. # 统计stats中所有图片的统计结果 将stats列表的信息拼接到一起
  259. # stats(concat后): list{4} correct, conf, pcls, tcls 统计出的整个数据集的GT
  260. # correct [img_sum, 10] 整个数据集所有图片中所有预测框在每一个iou条件下是否是TP [1905, 10]
  261. # conf [img_sum] 整个数据集所有图片中所有预测框的conf [1905]
  262. # pcls [img_sum] 整个数据集所有图片中所有预测框的类别 [1905]
  263. # tcls [gt_sum] 整个数据集所有图片所有gt框的class [929]
  264. stats = [torch.cat(x, 0).cpu().numpy() for x in zip(*stats)] # to numpy
  265. if len(stats) and stats[0].any():
  266. # 根据上面的统计预测结果计算p, r, ap, f1, ap_class(ap_per_class函数是计算每个类的mAP等指标的)等指标
  267. # p: [nc] 最大平均f1时每个类别的precision
  268. # r: [nc] 最大平均f1时每个类别的recall
  269. # ap: [71, 10] 数据集每个类别在10个iou阈值下的mAP
  270. # f1 [nc] 最大平均f1时每个类别的f1
  271. # ap_class: [nc] 返回数据集中所有的类别index
  272. tp, fp, p, r, f1, ap, ap_class = ap_per_class(*stats, plot=plots, save_dir=save_dir, names=names)
  273. # ap50: [nc] 所有类别的mAP@0.5 ap: [nc] 所有类别的mAP@0.5:0.95
  274. ap50, ap = ap[:, 0], ap.mean(1) # AP@0.5, AP@0.5:0.95
  275. # mp: [1] 所有类别的平均precision(最大f1时)
  276. # mr: [1] 所有类别的平均recall(最大f1时)
  277. # map50: [1] 所有类别的平均mAP@0.5
  278. # map: [1] 所有类别的平均mAP@0.5:0.95
  279. mp, mr, map50, map = p.mean(), r.mean(), ap50.mean(), ap.mean()
  280. # nt: [nc] 统计出整个数据集的gt框中数据集各个类别的个数
  281. nt = np.bincount(stats[3].astype(np.int64), minlength=nc) # number of targets per class
  282. else:
  283. nt = torch.zeros(1)
  284. # Print results
  285. pf = '%20s' + '%11i' * 2 + '%11.3g' * 4 # print format
  286. LOGGER.info(pf % ('all', seen, nt.sum(), mp, mr, map50, map))
  287. # Print results per class
  288. if (verbose or (nc < 50 and not training)) and nc > 1 and len(stats):
  289. for i, c in enumerate(ap_class):
  290. LOGGER.info(pf % (names[c], seen, nt[c], p[i], r[i], ap50[i], ap[i]))
  291. # Print speeds
  292. t = tuple(x / seen * 1E3 for x in dt) # speeds per image
  293. if not training:
  294. shape = (batch_size, 3, imgsz, imgsz)
  295. LOGGER.info(f'Speed: %.1fms pre-process, %.1fms inference, %.1fms NMS per image at shape {shape}' % t)
  296. # Plots 画出混淆矩阵
  297. if plots:
  298. confusion_matrix.plot(save_dir=save_dir, names=list(names.values()))
  299. callbacks.run('on_val_end')
  300. # Save JSON
  301. if save_json and len(jdict):
  302. w = Path(weights[0] if isinstance(weights, list) else weights).stem if weights is not None else '' # weights
  303. anno_json = str(Path(data.get('path', '../coco')) / 'annotations/instances_val2017.json') # annotations json
  304. pred_json = str(save_dir / f"{w}_predictions.json") # predictions json
  305. LOGGER.info(f'\nEvaluating pycocotools mAP... saving {pred_json}...')
  306. with open(pred_json, 'w') as f:
  307. json.dump(jdict, f)
  308. try: # https://github.com/cocodataset/cocoapi/blob/master/PythonAPI/pycocoEvalDemo.ipynb
  309. check_requirements(['pycocotools'])
  310. from pycocotools.coco import COCO
  311. from pycocotools.cocoeval import COCOeval
  312. anno = COCO(anno_json) # init annotations api
  313. pred = anno.loadRes(pred_json) # init predictions api
  314. eval = COCOeval(anno, pred, 'bbox')
  315. if is_coco:
  316. eval.params.imgIds = [int(Path(x).stem) for x in dataloader.dataset.im_files] # image IDs to evaluate
  317. eval.evaluate()
  318. eval.accumulate()
  319. eval.summarize()
  320. map, map50 = eval.stats[:2] # update results (mAP@0.5:0.95, mAP@0.5)
  321. except Exception as e:
  322. LOGGER.info(f'pycocotools unable to run: {e}')
  323. # Return results
  324. model.float() # for training
  325. if not training:
  326. s = f"\n{len(list(save_dir.glob('labels/*.txt')))} labels saved to {save_dir / 'labels'}" if save_txt else ''
  327. LOGGER.info(f"Results saved to {colorstr('bold', save_dir)}{s}")
  328. maps = np.zeros(nc) + map
  329. for i, c in enumerate(ap_class):
  330. maps[c] = ap[i]
  331. return (mp, mr, map50, map, *(loss.cpu() / len(dataloader)).tolist()), maps, t
  332. def parse_opt():
  333. parser = argparse.ArgumentParser()
  334. parser.add_argument('--data', type=str, default=ROOT / 'data/coco128.yaml', help='dataset.yaml path') # 数据集配置文件地址 包含数据集的路径、类别个数、类名、下载地址等信息
  335. parser.add_argument('--weights', nargs='+', type=str, default=ROOT / 'yolov5s.pt', help='model.pt path(s)') #模型的权重文件地址 weights/yolov5s.pt
  336. parser.add_argument('--batch-size', type=int, default=32, help='batch size') # 前向传播的批次大小 默认32
  337. parser.add_argument('--imgsz', '--img', '--img-size', type=int, default=640, help='inference size (pixels)') # 输入网络的图片分辨率 默认640
  338. parser.add_argument('--conf-thres', type=float, default=0.001, help='confidence threshold') # object置信度阈值 默认0.001
  339. parser.add_argument('--iou-thres', type=float, default=0.6, help='NMS IoU threshold') # 进行NMS时IOU的阈值 默认0.6
  340. parser.add_argument('--task', default='val', help='train, val, test, speed or study') # 设置测试的类型 有train, val, test, speed or study几种 默认val
  341. parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu') # 测试的设备
  342. parser.add_argument('--workers', type=int, default=8, help='max dataloader workers (per RANK in DDP mode)') # 最大数据加载进程数
  343. parser.add_argument('--single-cls', action='store_true', help='treat as single-class dataset') # 数据集是否只用一个类别 默认False
  344. parser.add_argument('--augment', action='store_true', help='augmented inference') # 测试是否使用TTA Test Time Augment 默认False
  345. parser.add_argument('--verbose', action='store_true', help='report mAP by class') # 是否打印出每个类别的mAP 默认False
  346. parser.add_argument('--save-txt', action='store_true', help='save results to *.txt') # 保存结果为txt文件
  347. parser.add_argument('--save-hybrid', action='store_true', help='save label+prediction hybrid results to *.txt') # 保存标签+预测混合结果到*.txt
  348. parser.add_argument('--save-conf', action='store_true', help='save confidences in --save-txt labels') # 保存置信度到txt文件中
  349. parser.add_argument('--save-json', action='store_true', help='save a COCO-JSON results file')# 是否按照coco的json格式保存预测框,并且使用cocoapi做评估(需要同样coco的json格式的标签) 默认False
  350. parser.add_argument('--project', default=ROOT / 'runs/val', help='save to project/name') # 测试保存的源文件 默认runs/test
  351. parser.add_argument('--name', default='exp', help='save to project/name') # 测试保存的文件地址 默认exp 保存在runs/test/exp下
  352. parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment') # 是否存在当前文件 默认False 一般是 no exist-ok 连用 所以一般都要重新创建文件夹
  353. parser.add_argument('--half', action='store_true', help='use FP16 half-precision inference') # 是否使用半精度推理 默认False
  354. parser.add_argument('--dnn', action='store_true', help='use OpenCV DNN for ONNX inference') # 使用OpenCV DNN进行ONNX推断
  355. opt = parser.parse_args()
  356. opt.data = check_yaml(opt.data) # check YAML
  357. opt.save_json |= opt.data.endswith('coco.yaml') # |或 左右两个变量有一个为True 左边变量就为True
  358. opt.save_txt |= opt.save_hybrid
  359. print_args(vars(opt))
  360. return opt
  361. def main(opt):
  362. # 检测requirements文件中需要的包是否安装好了
  363. check_requirements(requirements=ROOT / 'requirements.txt', exclude=('tensorboard', 'thop'))
  364. if opt.task in ('train', 'val', 'test'): # run normally 如果task in ['train', 'val', 'test']就正常测试
  365. if opt.conf_thres > 0.001: # https://github.com/ultralytics/yolov5/issues/1466
  366. LOGGER.info(f'WARNING: confidence threshold {opt.conf_thres} >> 0.001 will produce invalid mAP values.')
  367. run(**vars(opt))
  368. else:
  369. weights = opt.weights if isinstance(opt.weights, list) else [opt.weights]
  370. opt.half = True # FP16 for fastest results
  371. if opt.task == 'speed': # speed benchmarks 如果task == 'speed' 就测试yolov5系列和yolov3-spp各个模型的速度评估
  372. # python val.py --task speed --data coco.yaml --batch 1 --weights yolov5n.pt yolov5s.pt...
  373. opt.conf_thres, opt.iou_thres, opt.save_json = 0.25, 0.45, False
  374. for opt.weights in weights:
  375. run(**vars(opt), plots=False) # 主要分支
  376. elif opt.task == 'study': # speed vs mAP benchmarks 就评估yolov5系列和yolov3-spp各个模型在各个尺度下的指标并可视化
  377. # python val.py --task study --data coco.yaml --iou 0.7 --weights yolov5n.pt yolov5s.pt...
  378. for opt.weights in weights:
  379. f = f'study_{Path(opt.data).stem}_{Path(opt.weights).stem}.txt' # filename to save to
  380. x, y = list(range(256, 1536 + 128, 128)), [] # x axis (image sizes), y axis
  381. for opt.imgsz in x: # img-size
  382. LOGGER.info(f'\nRunning {f} --imgsz {opt.imgsz}...')
  383. r, _, t = run(**vars(opt), plots=False)
  384. y.append(r + t) # results and times
  385. np.savetxt(f, y, fmt='%10.4g') # save
  386. os.system('zip -r study.zip study_*.txt')
  387. plot_val_study(x=x) # plot
  388. if __name__ == "__main__":
  389. opt = parse_opt()
  390. main(opt)