Register
Login
Resources
Docs Blog Datasets Glossary Case Studies Tutorials & Webinars
Product
Data Engine LLMs Platform Enterprise
Pricing Explore
Connect to our Discord channel

val.py 30 KB

You have to be logged in to leave a comment. Sign In
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
  1. # Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
  2. """
  3. Validate a trained YOLOv5 detection model on a detection dataset.
  4. Usage:
  5. $ python val.py --weights yolov5s.pt --data coco128.yaml --img 640
  6. Usage - formats:
  7. $ python val.py --weights yolov5s.pt # PyTorch
  8. yolov5s.torchscript # TorchScript
  9. yolov5s.onnx # ONNX Runtime or OpenCV DNN with --dnn
  10. yolov5s_openvino_model # OpenVINO
  11. yolov5s.engine # TensorRT
  12. yolov5s.mlpackage # 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. yolov5s_paddle_model # PaddlePaddle
  18. """
  19. import argparse
  20. import json
  21. import os
  22. import subprocess
  23. import sys
  24. from pathlib import Path
  25. import numpy as np
  26. import torch
  27. from tqdm import tqdm
  28. FILE = Path(__file__).resolve()
  29. ROOT = FILE.parents[0] # YOLOv5 root directory
  30. if str(ROOT) not in sys.path:
  31. sys.path.append(str(ROOT)) # add ROOT to PATH
  32. ROOT = Path(os.path.relpath(ROOT, Path.cwd())) # relative
  33. from models.common import DetectMultiBackend
  34. from utils.callbacks import Callbacks
  35. from utils.dataloaders import create_dataloader
  36. from utils.general import (
  37. LOGGER,
  38. TQDM_BAR_FORMAT,
  39. Profile,
  40. check_dataset,
  41. check_img_size,
  42. check_requirements,
  43. check_yaml,
  44. coco80_to_coco91_class,
  45. colorstr,
  46. increment_path,
  47. non_max_suppression,
  48. print_args,
  49. scale_boxes,
  50. xywh2xyxy,
  51. xyxy2xywh,
  52. )
  53. from utils.metrics import ConfusionMatrix, ap_per_class, box_iou
  54. from utils.plots import output_to_target, plot_images, plot_val_study
  55. from utils.torch_utils import select_device, smart_inference_mode
  56. def save_one_txt(predn, save_conf, shape, file):
  57. """
  58. Saves one detection result to a txt file in normalized xywh format, optionally including confidence.
  59. Args:
  60. predn (torch.Tensor): Predicted bounding boxes and associated confidence scores and classes in xyxy format, tensor
  61. of shape (N, 6) where N is the number of detections.
  62. save_conf (bool): If True, saves the confidence scores along with the bounding box coordinates.
  63. shape (tuple): Shape of the original image as (height, width).
  64. file (str | Path): File path where the result will be saved.
  65. Returns:
  66. None
  67. Notes:
  68. The xyxy bounding box format represents the coordinates (xmin, ymin, xmax, ymax).
  69. The xywh format represents the coordinates (center_x, center_y, width, height) and is normalized by the width and
  70. height of the image.
  71. Example:
  72. ```python
  73. predn = torch.tensor([[10, 20, 30, 40, 0.9, 1]]) # example prediction
  74. save_one_txt(predn, save_conf=True, shape=(640, 480), file="output.txt")
  75. ```
  76. """
  77. gn = torch.tensor(shape)[[1, 0, 1, 0]] # normalization gain whwh
  78. for *xyxy, conf, cls in predn.tolist():
  79. xywh = (xyxy2xywh(torch.tensor(xyxy).view(1, 4)) / gn).view(-1).tolist() # normalized xywh
  80. line = (cls, *xywh, conf) if save_conf else (cls, *xywh) # label format
  81. with open(file, "a") as f:
  82. f.write(("%g " * len(line)).rstrip() % line + "\n")
  83. def save_one_json(predn, jdict, path, class_map):
  84. """
  85. Saves a single JSON detection result, including image ID, category ID, bounding box, and confidence score.
  86. Args:
  87. predn (torch.Tensor): Predicted detections in xyxy format with shape (n, 6) where n is the number of detections.
  88. The tensor should contain [x_min, y_min, x_max, y_max, confidence, class_id] for each detection.
  89. jdict (list[dict]): List to collect JSON formatted detection results.
  90. path (pathlib.Path): Path object of the image file, used to extract image_id.
  91. class_map (dict[int, int]): Mapping from model class indices to dataset-specific category IDs.
  92. Returns:
  93. None: Appends detection results as dictionaries to `jdict` list in-place.
  94. Example:
  95. ```python
  96. predn = torch.tensor([[100, 50, 200, 150, 0.9, 0], [50, 30, 100, 80, 0.8, 1]])
  97. jdict = []
  98. path = Path("42.jpg")
  99. class_map = {0: 18, 1: 19}
  100. save_one_json(predn, jdict, path, class_map)
  101. ```
  102. This will append to `jdict`:
  103. ```
  104. [
  105. {'image_id': 42, 'category_id': 18, 'bbox': [125.0, 75.0, 100.0, 100.0], 'score': 0.9},
  106. {'image_id': 42, 'category_id': 19, 'bbox': [75.0, 55.0, 50.0, 50.0], 'score': 0.8}
  107. ]
  108. ```
  109. Notes:
  110. The `bbox` values are formatted as [x, y, width, height], where x and y represent the top-left corner of the box.
  111. """
  112. image_id = int(path.stem) if path.stem.isnumeric() else path.stem
  113. box = xyxy2xywh(predn[:, :4]) # xywh
  114. box[:, :2] -= box[:, 2:] / 2 # xy center to top-left corner
  115. for p, b in zip(predn.tolist(), box.tolist()):
  116. jdict.append(
  117. {
  118. "image_id": image_id,
  119. "category_id": class_map[int(p[5])],
  120. "bbox": [round(x, 3) for x in b],
  121. "score": round(p[4], 5),
  122. }
  123. )
  124. def process_batch(detections, labels, iouv):
  125. """
  126. Return a correct prediction matrix given detections and labels at various IoU thresholds.
  127. Args:
  128. detections (np.ndarray): Array of shape (N, 6) where each row corresponds to a detection with format
  129. [x1, y1, x2, y2, conf, class].
  130. labels (np.ndarray): Array of shape (M, 5) where each row corresponds to a ground truth label with format
  131. [class, x1, y1, x2, y2].
  132. iouv (np.ndarray): Array of IoU thresholds to evaluate at.
  133. Returns:
  134. correct (np.ndarray): A binary array of shape (N, len(iouv)) indicating whether each detection is a true positive
  135. for each IoU threshold. There are 10 IoU levels used in the evaluation.
  136. Example:
  137. ```python
  138. detections = np.array([[50, 50, 200, 200, 0.9, 1], [30, 30, 150, 150, 0.7, 0]])
  139. labels = np.array([[1, 50, 50, 200, 200]])
  140. iouv = np.linspace(0.5, 0.95, 10)
  141. correct = process_batch(detections, labels, iouv)
  142. ```
  143. Notes:
  144. - This function is used as part of the evaluation pipeline for object detection models.
  145. - IoU (Intersection over Union) is a common evaluation metric for object detection performance.
  146. """
  147. correct = np.zeros((detections.shape[0], iouv.shape[0])).astype(bool)
  148. iou = box_iou(labels[:, 1:], detections[:, :4])
  149. correct_class = labels[:, 0:1] == detections[:, 5]
  150. for i in range(len(iouv)):
  151. x = torch.where((iou >= iouv[i]) & correct_class) # IoU > threshold and classes match
  152. if x[0].shape[0]:
  153. matches = torch.cat((torch.stack(x, 1), iou[x[0], x[1]][:, None]), 1).cpu().numpy() # [label, detect, iou]
  154. if x[0].shape[0] > 1:
  155. matches = matches[matches[:, 2].argsort()[::-1]]
  156. matches = matches[np.unique(matches[:, 1], return_index=True)[1]]
  157. # matches = matches[matches[:, 2].argsort()[::-1]]
  158. matches = matches[np.unique(matches[:, 0], return_index=True)[1]]
  159. correct[matches[:, 1].astype(int), i] = True
  160. return torch.tensor(correct, dtype=torch.bool, device=iouv.device)
  161. @smart_inference_mode()
  162. def run(
  163. data,
  164. weights=None, # model.pt path(s)
  165. batch_size=32, # batch size
  166. imgsz=640, # inference size (pixels)
  167. conf_thres=0.001, # confidence threshold
  168. iou_thres=0.6, # NMS IoU threshold
  169. max_det=300, # maximum detections per image
  170. task="val", # train, val, test, speed or study
  171. device="", # cuda device, i.e. 0 or 0,1,2,3 or cpu
  172. workers=8, # max dataloader workers (per RANK in DDP mode)
  173. single_cls=False, # treat as single-class dataset
  174. augment=False, # augmented inference
  175. verbose=False, # verbose output
  176. save_txt=False, # save results to *.txt
  177. save_hybrid=False, # save label+prediction hybrid results to *.txt
  178. save_conf=False, # save confidences in --save-txt labels
  179. save_json=False, # save a COCO-JSON results file
  180. project=ROOT / "runs/val", # save to project/name
  181. name="exp", # save to project/name
  182. exist_ok=False, # existing project/name ok, do not increment
  183. half=True, # use FP16 half-precision inference
  184. dnn=False, # use OpenCV DNN for ONNX inference
  185. model=None,
  186. dataloader=None,
  187. save_dir=Path(""),
  188. plots=True,
  189. callbacks=Callbacks(),
  190. compute_loss=None,
  191. ):
  192. """
  193. Evaluates a YOLOv5 model on a dataset and logs performance metrics.
  194. Args:
  195. data (str | dict): Path to a dataset YAML file or a dataset dictionary.
  196. weights (str | list[str], optional): Path to the model weights file(s). Supports various formats including PyTorch,
  197. TorchScript, ONNX, OpenVINO, TensorRT, CoreML, TensorFlow SavedModel, TensorFlow GraphDef, TensorFlow Lite,
  198. TensorFlow Edge TPU, and PaddlePaddle.
  199. batch_size (int, optional): Batch size for inference. Default is 32.
  200. imgsz (int, optional): Input image size (pixels). Default is 640.
  201. conf_thres (float, optional): Confidence threshold for object detection. Default is 0.001.
  202. iou_thres (float, optional): IoU threshold for Non-Maximum Suppression (NMS). Default is 0.6.
  203. max_det (int, optional): Maximum number of detections per image. Default is 300.
  204. task (str, optional): Task type - 'train', 'val', 'test', 'speed', or 'study'. Default is 'val'.
  205. device (str, optional): Device to use for computation, e.g., '0' or '0,1,2,3' for CUDA or 'cpu' for CPU. Default is ''.
  206. workers (int, optional): Number of dataloader workers. Default is 8.
  207. single_cls (bool, optional): Treat dataset as a single class. Default is False.
  208. augment (bool, optional): Enable augmented inference. Default is False.
  209. verbose (bool, optional): Enable verbose output. Default is False.
  210. save_txt (bool, optional): Save results to *.txt files. Default is False.
  211. save_hybrid (bool, optional): Save label and prediction hybrid results to *.txt files. Default is False.
  212. save_conf (bool, optional): Save confidences in --save-txt labels. Default is False.
  213. save_json (bool, optional): Save a COCO-JSON results file. Default is False.
  214. project (str | Path, optional): Directory to save results. Default is ROOT/'runs/val'.
  215. name (str, optional): Name of the run. Default is 'exp'.
  216. exist_ok (bool, optional): Overwrite existing project/name without incrementing. Default is False.
  217. half (bool, optional): Use FP16 half-precision inference. Default is True.
  218. dnn (bool, optional): Use OpenCV DNN for ONNX inference. Default is False.
  219. model (torch.nn.Module, optional): Model object for training. Default is None.
  220. dataloader (torch.utils.data.DataLoader, optional): Dataloader object. Default is None.
  221. save_dir (Path, optional): Directory to save results. Default is Path('').
  222. plots (bool, optional): Plot validation images and metrics. Default is True.
  223. callbacks (utils.callbacks.Callbacks, optional): Callbacks for logging and monitoring. Default is Callbacks().
  224. compute_loss (function, optional): Loss function for training. Default is None.
  225. Returns:
  226. dict: Contains performance metrics including precision, recall, mAP50, and mAP50-95.
  227. """
  228. # Initialize/load model and set device
  229. training = model is not None
  230. if training: # called by train.py
  231. device, pt, jit, engine = next(model.parameters()).device, True, False, False # get model device, PyTorch model
  232. half &= device.type != "cpu" # half precision only supported on CUDA
  233. model.half() if half else model.float()
  234. else: # called directly
  235. device = select_device(device, batch_size=batch_size)
  236. # Directories
  237. save_dir = increment_path(Path(project) / name, exist_ok=exist_ok) # increment run
  238. (save_dir / "labels" if save_txt else save_dir).mkdir(parents=True, exist_ok=True) # make dir
  239. # Load model
  240. model = DetectMultiBackend(weights, device=device, dnn=dnn, data=data, fp16=half)
  241. stride, pt, jit, engine = model.stride, model.pt, model.jit, model.engine
  242. imgsz = check_img_size(imgsz, s=stride) # check image size
  243. half = model.fp16 # FP16 supported on limited backends with CUDA
  244. if engine:
  245. batch_size = model.batch_size
  246. else:
  247. device = model.device
  248. if not (pt or jit):
  249. batch_size = 1 # export.py models default to batch-size 1
  250. LOGGER.info(f"Forcing --batch-size 1 square inference (1,3,{imgsz},{imgsz}) for non-PyTorch models")
  251. # Data
  252. data = check_dataset(data) # check
  253. # Configure
  254. model.eval()
  255. cuda = device.type != "cpu"
  256. is_coco = isinstance(data.get("val"), str) and data["val"].endswith(f"coco{os.sep}val2017.txt") # COCO dataset
  257. nc = 1 if single_cls else int(data["nc"]) # number of classes
  258. iouv = torch.linspace(0.5, 0.95, 10, device=device) # iou vector for mAP@0.5:0.95
  259. niou = iouv.numel()
  260. # Dataloader
  261. if not training:
  262. if pt and not single_cls: # check --weights are trained on --data
  263. ncm = model.model.nc
  264. assert ncm == nc, (
  265. f"{weights} ({ncm} classes) trained on different --data than what you passed ({nc} "
  266. f"classes). Pass correct combination of --weights and --data that are trained together."
  267. )
  268. model.warmup(imgsz=(1 if pt else batch_size, 3, imgsz, imgsz)) # warmup
  269. pad, rect = (0.0, False) if task == "speed" else (0.5, pt) # square inference for benchmarks
  270. task = task if task in ("train", "val", "test") else "val" # path to train/val/test images
  271. dataloader = create_dataloader(
  272. data[task],
  273. imgsz,
  274. batch_size,
  275. stride,
  276. single_cls,
  277. pad=pad,
  278. rect=rect,
  279. workers=workers,
  280. prefix=colorstr(f"{task}: "),
  281. )[0]
  282. seen = 0
  283. confusion_matrix = ConfusionMatrix(nc=nc)
  284. names = model.names if hasattr(model, "names") else model.module.names # get class names
  285. if isinstance(names, (list, tuple)): # old format
  286. names = dict(enumerate(names))
  287. class_map = coco80_to_coco91_class() if is_coco else list(range(1000))
  288. s = ("%22s" + "%11s" * 6) % ("Class", "Images", "Instances", "P", "R", "mAP50", "mAP50-95")
  289. tp, fp, p, r, f1, mp, mr, map50, ap50, map = 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0
  290. dt = Profile(device=device), Profile(device=device), Profile(device=device) # profiling times
  291. loss = torch.zeros(3, device=device)
  292. jdict, stats, ap, ap_class = [], [], [], []
  293. callbacks.run("on_val_start")
  294. pbar = tqdm(dataloader, desc=s, bar_format=TQDM_BAR_FORMAT) # progress bar
  295. for batch_i, (im, targets, paths, shapes) in enumerate(pbar):
  296. callbacks.run("on_val_batch_start")
  297. with dt[0]:
  298. if cuda:
  299. im = im.to(device, non_blocking=True)
  300. targets = targets.to(device)
  301. im = im.half() if half else im.float() # uint8 to fp16/32
  302. im /= 255 # 0 - 255 to 0.0 - 1.0
  303. nb, _, height, width = im.shape # batch size, channels, height, width
  304. # Inference
  305. with dt[1]:
  306. preds, train_out = model(im) if compute_loss else (model(im, augment=augment), None)
  307. # Loss
  308. if compute_loss:
  309. loss += compute_loss(train_out, targets)[1] # box, obj, cls
  310. # NMS
  311. targets[:, 2:] *= torch.tensor((width, height, width, height), device=device) # to pixels
  312. lb = [targets[targets[:, 0] == i, 1:] for i in range(nb)] if save_hybrid else [] # for autolabelling
  313. with dt[2]:
  314. preds = non_max_suppression(
  315. preds, conf_thres, iou_thres, labels=lb, multi_label=True, agnostic=single_cls, max_det=max_det
  316. )
  317. # Metrics
  318. for si, pred in enumerate(preds):
  319. labels = targets[targets[:, 0] == si, 1:]
  320. nl, npr = labels.shape[0], pred.shape[0] # number of labels, predictions
  321. path, shape = Path(paths[si]), shapes[si][0]
  322. correct = torch.zeros(npr, niou, dtype=torch.bool, device=device) # init
  323. seen += 1
  324. if npr == 0:
  325. if nl:
  326. stats.append((correct, *torch.zeros((2, 0), device=device), labels[:, 0]))
  327. if plots:
  328. confusion_matrix.process_batch(detections=None, labels=labels[:, 0])
  329. continue
  330. # Predictions
  331. if single_cls:
  332. pred[:, 5] = 0
  333. predn = pred.clone()
  334. scale_boxes(im[si].shape[1:], predn[:, :4], shape, shapes[si][1]) # native-space pred
  335. # Evaluate
  336. if nl:
  337. tbox = xywh2xyxy(labels[:, 1:5]) # target boxes
  338. scale_boxes(im[si].shape[1:], tbox, shape, shapes[si][1]) # native-space labels
  339. labelsn = torch.cat((labels[:, 0:1], tbox), 1) # native-space labels
  340. correct = process_batch(predn, labelsn, iouv)
  341. if plots:
  342. confusion_matrix.process_batch(predn, labelsn)
  343. stats.append((correct, pred[:, 4], pred[:, 5], labels[:, 0])) # (correct, conf, pcls, tcls)
  344. # Save/log
  345. if save_txt:
  346. (save_dir / "labels").mkdir(parents=True, exist_ok=True)
  347. save_one_txt(predn, save_conf, shape, file=save_dir / "labels" / f"{path.stem}.txt")
  348. if save_json:
  349. save_one_json(predn, jdict, path, class_map) # append to COCO-JSON dictionary
  350. callbacks.run("on_val_image_end", pred, predn, path, names, im[si])
  351. # Plot images
  352. if plots and batch_i < 3:
  353. plot_images(im, targets, paths, save_dir / f"val_batch{batch_i}_labels.jpg", names) # labels
  354. plot_images(im, output_to_target(preds), paths, save_dir / f"val_batch{batch_i}_pred.jpg", names) # pred
  355. callbacks.run("on_val_batch_end", batch_i, im, targets, paths, shapes, preds)
  356. # Compute metrics
  357. stats = [torch.cat(x, 0).cpu().numpy() for x in zip(*stats)] # to numpy
  358. if len(stats) and stats[0].any():
  359. tp, fp, p, r, f1, ap, ap_class = ap_per_class(*stats, plot=plots, save_dir=save_dir, names=names)
  360. ap50, ap = ap[:, 0], ap.mean(1) # AP@0.5, AP@0.5:0.95
  361. mp, mr, map50, map = p.mean(), r.mean(), ap50.mean(), ap.mean()
  362. nt = np.bincount(stats[3].astype(int), minlength=nc) # number of targets per class
  363. # Print results
  364. pf = "%22s" + "%11i" * 2 + "%11.3g" * 4 # print format
  365. LOGGER.info(pf % ("all", seen, nt.sum(), mp, mr, map50, map))
  366. if nt.sum() == 0:
  367. LOGGER.warning(f"WARNING ⚠️ no labels found in {task} set, can not compute metrics without labels")
  368. # Print results per class
  369. if (verbose or (nc < 50 and not training)) and nc > 1 and len(stats):
  370. for i, c in enumerate(ap_class):
  371. LOGGER.info(pf % (names[c], seen, nt[c], p[i], r[i], ap50[i], ap[i]))
  372. # Print speeds
  373. t = tuple(x.t / seen * 1e3 for x in dt) # speeds per image
  374. if not training:
  375. shape = (batch_size, 3, imgsz, imgsz)
  376. LOGGER.info(f"Speed: %.1fms pre-process, %.1fms inference, %.1fms NMS per image at shape {shape}" % t)
  377. # Plots
  378. if plots:
  379. confusion_matrix.plot(save_dir=save_dir, names=list(names.values()))
  380. callbacks.run("on_val_end", nt, tp, fp, p, r, f1, ap, ap50, ap_class, confusion_matrix)
  381. # Save JSON
  382. if save_json and len(jdict):
  383. w = Path(weights[0] if isinstance(weights, list) else weights).stem if weights is not None else "" # weights
  384. anno_json = str(Path("../datasets/coco/annotations/instances_val2017.json")) # annotations
  385. if not os.path.exists(anno_json):
  386. anno_json = os.path.join(data["path"], "annotations", "instances_val2017.json")
  387. pred_json = str(save_dir / f"{w}_predictions.json") # predictions
  388. LOGGER.info(f"\nEvaluating pycocotools mAP... saving {pred_json}...")
  389. with open(pred_json, "w") as f:
  390. json.dump(jdict, f)
  391. try: # https://github.com/cocodataset/cocoapi/blob/master/PythonAPI/pycocoEvalDemo.ipynb
  392. check_requirements("pycocotools>=2.0.6")
  393. from pycocotools.coco import COCO
  394. from pycocotools.cocoeval import COCOeval
  395. anno = COCO(anno_json) # init annotations api
  396. pred = anno.loadRes(pred_json) # init predictions api
  397. eval = COCOeval(anno, pred, "bbox")
  398. if is_coco:
  399. eval.params.imgIds = [int(Path(x).stem) for x in dataloader.dataset.im_files] # image IDs to evaluate
  400. eval.evaluate()
  401. eval.accumulate()
  402. eval.summarize()
  403. map, map50 = eval.stats[:2] # update results (mAP@0.5:0.95, mAP@0.5)
  404. except Exception as e:
  405. LOGGER.info(f"pycocotools unable to run: {e}")
  406. # Return results
  407. model.float() # for training
  408. if not training:
  409. s = f"\n{len(list(save_dir.glob('labels/*.txt')))} labels saved to {save_dir / 'labels'}" if save_txt else ""
  410. LOGGER.info(f"Results saved to {colorstr('bold', save_dir)}{s}")
  411. maps = np.zeros(nc) + map
  412. for i, c in enumerate(ap_class):
  413. maps[c] = ap[i]
  414. return (mp, mr, map50, map, *(loss.cpu() / len(dataloader)).tolist()), maps, t
  415. def parse_opt():
  416. """
  417. Parse command-line options for configuring YOLOv5 model inference.
  418. Args:
  419. data (str, optional): Path to the dataset YAML file. Default is 'data/coco128.yaml'.
  420. weights (list[str], optional): List of paths to model weight files. Default is 'yolov5s.pt'.
  421. batch_size (int, optional): Batch size for inference. Default is 32.
  422. imgsz (int, optional): Inference image size in pixels. Default is 640.
  423. conf_thres (float, optional): Confidence threshold for predictions. Default is 0.001.
  424. iou_thres (float, optional): IoU threshold for Non-Max Suppression (NMS). Default is 0.6.
  425. max_det (int, optional): Maximum number of detections per image. Default is 300.
  426. task (str, optional): Task type - options are 'train', 'val', 'test', 'speed', or 'study'. Default is 'val'.
  427. device (str, optional): Device to run the model on. e.g., '0' or '0,1,2,3' or 'cpu'. Default is empty to let the system choose automatically.
  428. workers (int, optional): Maximum number of dataloader workers per rank in DDP mode. Default is 8.
  429. single_cls (bool, optional): If set, treats the dataset as a single-class dataset. Default is False.
  430. augment (bool, optional): If set, performs augmented inference. Default is False.
  431. verbose (bool, optional): If set, reports mAP by class. Default is False.
  432. save_txt (bool, optional): If set, saves results to *.txt files. Default is False.
  433. save_hybrid (bool, optional): If set, saves label+prediction hybrid results to *.txt files. Default is False.
  434. save_conf (bool, optional): If set, saves confidences in --save-txt labels. Default is False.
  435. save_json (bool, optional): If set, saves results to a COCO-JSON file. Default is False.
  436. project (str, optional): Project directory to save results to. Default is 'runs/val'.
  437. name (str, optional): Name of the directory to save results to. Default is 'exp'.
  438. exist_ok (bool, optional): If set, existing directory will not be incremented. Default is False.
  439. half (bool, optional): If set, uses FP16 half-precision inference. Default is False.
  440. dnn (bool, optional): If set, uses OpenCV DNN for ONNX inference. Default is False.
  441. Returns:
  442. argparse.Namespace: Parsed command-line options.
  443. Notes:
  444. - The '--data' parameter is checked to ensure it ends with 'coco.yaml' if '--save-json' is set.
  445. - The '--save-txt' option is set to True if '--save-hybrid' is enabled.
  446. - Args are printed using `print_args` to facilitate debugging.
  447. Example:
  448. To validate a trained YOLOv5 model on a COCO dataset:
  449. ```python
  450. $ python val.py --weights yolov5s.pt --data coco128.yaml --img 640
  451. ```
  452. Different model formats could be used instead of `yolov5s.pt`:
  453. ```python
  454. $ python val.py --weights yolov5s.pt yolov5s.torchscript yolov5s.onnx yolov5s_openvino_model yolov5s.engine
  455. ```
  456. Additional options include saving results in different formats, selecting devices, and more.
  457. """
  458. parser = argparse.ArgumentParser()
  459. parser.add_argument("--data", type=str, default=ROOT / "data/coco128.yaml", help="dataset.yaml path")
  460. parser.add_argument("--weights", nargs="+", type=str, default=ROOT / "yolov5s.pt", help="model path(s)")
  461. parser.add_argument("--batch-size", type=int, default=32, help="batch size")
  462. parser.add_argument("--imgsz", "--img", "--img-size", type=int, default=640, help="inference size (pixels)")
  463. parser.add_argument("--conf-thres", type=float, default=0.001, help="confidence threshold")
  464. parser.add_argument("--iou-thres", type=float, default=0.6, help="NMS IoU threshold")
  465. parser.add_argument("--max-det", type=int, default=300, help="maximum detections per image")
  466. parser.add_argument("--task", default="val", help="train, val, test, speed or study")
  467. parser.add_argument("--device", default="", help="cuda device, i.e. 0 or 0,1,2,3 or cpu")
  468. parser.add_argument("--workers", type=int, default=8, help="max dataloader workers (per RANK in DDP mode)")
  469. parser.add_argument("--single-cls", action="store_true", help="treat as single-class dataset")
  470. parser.add_argument("--augment", action="store_true", help="augmented inference")
  471. parser.add_argument("--verbose", action="store_true", help="report mAP by class")
  472. parser.add_argument("--save-txt", action="store_true", help="save results to *.txt")
  473. parser.add_argument("--save-hybrid", action="store_true", help="save label+prediction hybrid results to *.txt")
  474. parser.add_argument("--save-conf", action="store_true", help="save confidences in --save-txt labels")
  475. parser.add_argument("--save-json", action="store_true", help="save a COCO-JSON results file")
  476. parser.add_argument("--project", default=ROOT / "runs/val", help="save to project/name")
  477. parser.add_argument("--name", default="exp", help="save to project/name")
  478. parser.add_argument("--exist-ok", action="store_true", help="existing project/name ok, do not increment")
  479. parser.add_argument("--half", action="store_true", help="use FP16 half-precision inference")
  480. parser.add_argument("--dnn", action="store_true", help="use OpenCV DNN for ONNX inference")
  481. opt = parser.parse_args()
  482. opt.data = check_yaml(opt.data) # check YAML
  483. opt.save_json |= opt.data.endswith("coco.yaml")
  484. opt.save_txt |= opt.save_hybrid
  485. print_args(vars(opt))
  486. return opt
  487. def main(opt):
  488. """
  489. Executes YOLOv5 tasks like training, validation, testing, speed, and study benchmarks based on provided options.
  490. Args:
  491. opt (argparse.Namespace): Parsed command-line options.
  492. This includes values for parameters like 'data', 'weights', 'batch_size', 'imgsz', 'conf_thres',
  493. 'iou_thres', 'max_det', 'task', 'device', 'workers', 'single_cls', 'augment', 'verbose', 'save_txt',
  494. 'save_hybrid', 'save_conf', 'save_json', 'project', 'name', 'exist_ok', 'half', and 'dnn', essential
  495. for configuring the YOLOv5 tasks.
  496. Returns:
  497. None
  498. Examples:
  499. To validate a trained YOLOv5 model on the COCO dataset with a specific weights file, use:
  500. ```python
  501. $ python val.py --weights yolov5s.pt --data coco128.yaml --img 640
  502. ```
  503. """
  504. check_requirements(ROOT / "requirements.txt", exclude=("tensorboard", "thop"))
  505. if opt.task in ("train", "val", "test"): # run normally
  506. if opt.conf_thres > 0.001: # https://github.com/ultralytics/yolov5/issues/1466
  507. LOGGER.info(f"WARNING ⚠️ confidence threshold {opt.conf_thres} > 0.001 produces invalid results")
  508. if opt.save_hybrid:
  509. LOGGER.info("WARNING ⚠️ --save-hybrid will return high mAP from hybrid labels, not from predictions alone")
  510. run(**vars(opt))
  511. else:
  512. weights = opt.weights if isinstance(opt.weights, list) else [opt.weights]
  513. opt.half = torch.cuda.is_available() and opt.device != "cpu" # FP16 for fastest results
  514. if opt.task == "speed": # speed benchmarks
  515. # python val.py --task speed --data coco.yaml --batch 1 --weights yolov5n.pt yolov5s.pt...
  516. opt.conf_thres, opt.iou_thres, opt.save_json = 0.25, 0.45, False
  517. for opt.weights in weights:
  518. run(**vars(opt), plots=False)
  519. elif opt.task == "study": # speed vs mAP benchmarks
  520. # python val.py --task study --data coco.yaml --iou 0.7 --weights yolov5n.pt yolov5s.pt...
  521. for opt.weights in weights:
  522. f = f"study_{Path(opt.data).stem}_{Path(opt.weights).stem}.txt" # filename to save to
  523. x, y = list(range(256, 1536 + 128, 128)), [] # x axis (image sizes), y axis
  524. for opt.imgsz in x: # img-size
  525. LOGGER.info(f"\nRunning {f} --imgsz {opt.imgsz}...")
  526. r, _, t = run(**vars(opt), plots=False)
  527. y.append(r + t) # results and times
  528. np.savetxt(f, y, fmt="%10.4g") # save
  529. subprocess.run(["zip", "-r", "study.zip", "study_*.txt"])
  530. plot_val_study(x=x) # plot
  531. else:
  532. raise NotImplementedError(f'--task {opt.task} not in ("train", "val", "test", "speed", "study")')
  533. if __name__ == "__main__":
  534. opt = parse_opt()
  535. main(opt)
Tip!

Press p or to see the previous file or, n or to see the next file

Comments

Loading...