赞
踩
组件知识笔记:
yolov5组件笔记_jacke121的专栏-CSDN博客_yolov5深度可分离卷积
参考:使用YOLOv5训练自己的数据集_laovife的博客-CSDN博客_使用yolov5训练自己的数据集
Model | size (pixels) | mAPval 0.5:0.95 | mAPtest 0.5:0.95 | mAPval 0.5 | Speed V100 (ms) | params (M) | FLOPS 640 (B) | |
---|---|---|---|---|---|---|---|---|
YOLOv5s | 640 | 36.7 | 36.7 | 55.4 | 2.0 | 7.3 | 17.0 | |
YOLOv5m | 640 | 44.5 | 44.5 | 63.1 | 2.7 | 21.4 | 51.3 | |
YOLOv5l | 640 | 48.2 | 48.2 | 66.9 | 3.8 | 47.0 | 115.4 | |
YOLOv5x | 640 | 50.4 | 50.4 | 68.8 | 6.1 | 87.7 | 218.8 | |
YOLOv5s6 | 1280 | 43.3 | 43.3 | 61.9 | 4.3 | 12.7 | 17.4 | |
YOLOv5m6 | 1280 | 50.5 | 50.5 | 68.7 | 8.4 | 35.9 | 52.4 | |
YOLOv5l6 | 1280 | 53.4 | 53.4 | 71.1 | 12.3 | 77.2 | 117.7 | |
YOLOv5x6 | 1280 | 54.4 | 54.4 | 72.0 | 22.4 | 141.8 | 222.9 |
开源地址:https://github.com/ultralytics/YOLOv5
保存路径修改:
opt.save_dir = increment_path(Path(opt.project) / opt.name, exist_ok=opt.exist_ok | opt.evolve) # increment run
yolov5保存模型:
- if save:
- with open(results_file, 'r') as f: # create checkpoint
- ckpt = {'epoch': epoch,
- 'best_fitness': best_fitness,
- 'training_results': f.read(),
- 'model': ema.ema.module if hasattr(ema, 'module') else ema.ema,
- 'optimizer': None if final_epoch else optimizer.state_dict()}
-
- # Save last, best and delete
-
- if best_fitness == fi:
- best=wdir+f'_{best_fitness[0]:.4f}_{epoch}.pth'
- torch.save(ckpt, best)
加载模型:
类的方式加载:
- config_file='models/yolov5s.yaml'
- with open(config_file) as f:
- myaml = yaml.load(f, Loader=yaml.FullLoader) # model dict
- model = YOLOv5_s(num_cls=1, anchors=myaml['anchors'], strides=myaml['strides']).to(device)
- # Load model
- model.eval()
-
- model.load_state_dict(torch.load(weights, map_location=device)['model'].state_dict())
不需要声明类的自动加载,但是类的代码项目中需要有:
- def attempt_load(weights, map_location=None):
- # Loads an ensemble of models weights=[a,b,c] or a single model weights=[a] or weights=a
- model = Ensemble()
- for w in weights if isinstance(weights, list) else [weights]:
- attempt_download(w)
- # model.append(torch.load(w, map_location=map_location)['model'].float().fuse().eval()) # load FP32 model
- model.append(torch.load(w, map_location=map_location)['model'].float().eval()) # load FP32 model
-
- if len(model) == 1:
- return model[-1] # return model
- else:
- print('Ensemble created with %s\n' % weights)
- for k in ['names', 'stride']:
- setattr(model, k, getattr(model[-1], k))
- return model # return ensemble
x:
return (mp, mr, map50, map, *(loss.cpu() / len(dataloader)).tolist()), maps, t
fit计算:相当于map50*0.1+0.9*mAP@0.5:0.95
- def fitness(x):
- # Returns fitness (for use with results.txt or evolve.txt)
- w = [0.0, 0.0, 0.1, 0.9] # weights for [P, R, mAP@0.5, mAP@0.5:0.95]
- return (x[:, :4] * w).sum(1)
关于多分类:好像有个标签单独标注是否是物体,再有one-hot分类是哪类物体
有一个文件.shapes,里面放的每个文件的宽高
数据集准备
图片大小:自己准备的图片大小不需要统一尺寸,预处理中会自动数据增强,统一尺寸进行训练。
coco数据集格式:xc yc w h,不是百分比
- {
- "segmentation": [[510.66,423.01,511.72,420.03,510.45......]],
- "area": 702.1057499999998,
- "iscrowd": 0,
- "image_id": 289343,
- "bbox": [473.07,395.93,38.65,28.67],
- "category_id": 18,
- "id": 1768
- },
yolov5使用的是yolo格式的标注文件,内容长这样,第一个数是标签的序号,后面四个是坐标。
坐标格式:class_index xc yc w h,都是百分比
标签处理代码:
标签长度为5的时候,
直接返回l和shape,segments 是空list []
- if os.path.isfile(lb_file):
- nf += 1 # label found
- with open(lb_file, 'r') as f:
- l = [x.split() for x in f.read().strip().splitlines()]
- if any([len(x) > 8 for x in l]): # is segment
- classes = np.array([x[0] for x in l], dtype=np.float32)
- segments = [np.array(x[1:], dtype=np.float32).reshape(-1, 2) for x in l] # (cls, xy1...)
- l = np.concatenate((classes.reshape(-1, 1), segments2boxes(segments)), 1) # (cls, xywh)
- l = np.array(l, dtype=np.float32)
- if len(l):
- assert l.shape[1] == 5, 'labels require 5 columns each'
- assert (l >= 0).all(), 'negative labels'
- assert (l[:, 1:] <= 1).all(), 'non-normalized or out of bounds coordinate labels'
- assert np.unique(l, axis=0).shape[0] == l.shape[0], 'duplicate labels'
- else:
- ne += 1 # label empty
- l = np.zeros((0, 5), dtype=np.float32)
x[im_file] = [l, shape, segments]
load_image:
最大边放大到640,同比例缩放
坐标框处理:
- if x.size > 0:
- # Normalized xywh to pixel xyxy format
- labels = x.copy()
- labels[:, 1] = ratio[0] * w * (x[:, 1] - x[:, 3] / 2) + pad[0] # pad width
- labels[:, 2] = ratio[1] * h * (x[:, 2] - x[:, 4] / 2) + pad[1] # pad height
- labels[:, 3] = ratio[0] * w * (x[:, 1] + x[:, 3] / 2) + pad[0]
- labels[:, 4] = ratio[1] * h * (x[:, 2] + x[:, 4] / 2) + pad[1]
在下面两个函数中都有处理,送入算法转xc yc w h形式。
getitem
load_mosaic
标注软件依然是labelimg,在使用前将VOC格式转换为YOLO即可
如果有之前标注好的xml文件,可以通过脚本直接转成yolo所需的txt格式: link.
不过在转换完成后记得添加labels文件,标注文件根据序号从labels里面对应标签。
到此数据集准备完毕,在data/coco128.yaml文件里,修改为自己的参数,到这一步就可以尝试train。
coco128.yaml:
- train: ./data/coco128/images/train2017/
- val: ./data/coco128/images/train2017/
-
- # number of classes
- nc: 80
-
- # class names
- ...
三.参数调整
yolov5提供了几种权重供选择,其中5l的性价比最高,适合CV爱好者日常研究;5x效果最好,如果硬件配置低,还可以选用只有27M的5s
在train.py修改你选用的权重,并前往权重文件中将nc改为和你样本库匹配的值。
根据要求修改epoch和batchsize,就可以开始初步的训练了。
ap计算:
def test(data,...)中
-
- seen = 0
- names = model.names if hasattr(model, 'names') else model.module.names
- coco91class = coco80_to_coco91_class()
- s = ('%20s' + '%12s' * 6) % ('Class', 'Images', 'Targets', 'P', 'R', 'mAP@.5', 'mAP@.5:.95')
- p, r, f1, mp, mr, map50, map, t0, t1 = 0., 0., 0., 0., 0., 0., 0., 0., 0.
- loss = torch.zeros(3, device=device)
- jdict, stats, ap, ap_class = [], [], [], []
- for batch_i, (img, targets, paths, shapes) in enumerate(tqdm(dataloader, desc=s)):
- img = img.to(device, non_blocking=True)
- img = img.half() if half else img.float() # uint8 to fp16/32
- img /= 255.0 # 0 - 255 to 0.0 - 1.0
- targets = targets.to(device)
- nb, _, height, width = img.shape # batch size, channels, height, width
- whwh = torch.Tensor([width, height, width, height]).to(device)
-
- # Disable gradients
- with torch.no_grad():
- # Run model
- t = torch_utils.time_synchronized()
- inf_out, train_out = model(img, augment=augment) # inference and training outputs
- t0 += torch_utils.time_synchronized() - t
-
- # Compute loss
- if training: # if model has loss hyperparameters
- loss += compute_loss([x.float() for x in train_out], targets, model)[1][:3] # GIoU, obj, cls
-
- # Run NMS
- t = torch_utils.time_synchronized()
- output = non_max_suppression(inf_out, conf_thres=conf_thres, iou_thres=iou_thres, merge=merge)
- t1 += torch_utils.time_synchronized() - t
-
- # Statistics per image
- for si, pred in enumerate(output):
- labels = targets[targets[:, 0] == si, 1:]
- nl = len(labels)
- tcls = labels[:, 0].tolist() if nl else [] # target class
- seen += 1
-
- if pred is None:
- if nl:
- stats.append((torch.zeros(0, niou, dtype=torch.bool), torch.Tensor(), torch.Tensor(), tcls))
- continue
-
- # Append to text file
- if save_txt:
- gn = torch.tensor(shapes[si][0])[[1, 0, 1, 0]] # normalization gain whwh
- txt_path = str(out / Path(paths[si]).stem)
- pred[:, :4] = scale_coords(img[si].shape[1:], pred[:, :4], shapes[si][0], shapes[si][1]) # to original
- for *xyxy, conf, cls in pred:
- xywh = (xyxy2xywh(torch.tensor(xyxy).view(1, 4)) / gn).view(-1).tolist() # normalized xywh
- with open(txt_path + '.txt', 'a') as f:
- f.write(('%g ' * 5 + '\n') % (cls, *xywh)) # label format
-
- # Clip boxes to image bounds
- clip_coords(pred, (height, width))
-
- # Append to pycocotools JSON dictionary
- if save_json:
- # [{"image_id": 42, "category_id": 18, "bbox": [258.15, 41.29, 348.26, 243.78], "score": 0.236}, ...
- image_id = Path(paths[si]).stem
- box = pred[:, :4].clone() # xyxy
- scale_coords(img[si].shape[1:], box, shapes[si][0], shapes[si][1]) # to original shape
- box = xyxy2xywh(box) # xywh
- box[:, :2] -= box[:, 2:] / 2 # xy center to top-left corner
- for p, b in zip(pred.tolist(), box.tolist()):
- jdict.append({'image_id': int(image_id) if image_id.isnumeric() else image_id,
- 'category_id': coco91class[int(p[5])],
- 'bbox': [round(x, 3) for x in b],
- 'score': round(p[4], 5)})
-
- # Assign all predictions as incorrect
- correct = torch.zeros(pred.shape[0], niou, dtype=torch.bool, device=device)
- if nl:
- detected = [] # target indices
- tcls_tensor = labels[:, 0]
-
- # target boxes
- tbox = xywh2xyxy(labels[:, 1:5]) * whwh
-
- # Per target class
- for cls in torch.unique(tcls_tensor):
- ti = (cls == tcls_tensor).nonzero().view(-1) # prediction indices
- pi = (cls == pred[:, 5]).nonzero().view(-1) # target indices
-
- # Search for detections
- if pi.shape[0]:
- # Prediction to target ious
- ious, i = box_iou(pred[pi, :4], tbox[ti]).max(1) # best ious, indices
-
- # Append detections
- for j in (ious > iouv[0]).nonzero():
- d = ti[i[j]] # detected target
- if d not in detected:
- detected.append(d)
- correct[pi[j]] = ious[j] > iouv # iou_thres is 1xn
- if len(detected) == nl: # all targets already located in image
- break
-
- # Append statistics (correct, conf, pcls, tcls)
- stats.append((correct.cpu(), pred[:, 4].cpu(), pred[:, 5].cpu(), tcls))
-
- # Plot images
- if batch_i < 1:
- f = Path(save_dir) / ('test_batch%g_gt.jpg' % batch_i) # filename
- plot_images(img, targets, paths, str(f), names) # ground truth
- f = Path(save_dir) / ('test_batch%g_pred.jpg' % batch_i)
- plot_images(img, output_to_target(output, width, height), paths, str(f), names) # predictions
-
- # Compute statistics
- stats = [np.concatenate(x, 0) for x in zip(*stats)] # to numpy
- if len(stats) and stats[0].any():
- p, r, ap, f1, ap_class = ap_per_class(*stats)
- p, r, ap50, ap = p[:, 0], r[:, 0], ap[:, 0], ap.mean(1) # [P, R, AP@0.5, AP@0.5:0.95]
- mp, mr, map50, map = p.mean(), r.mean(), ap50.mean(), ap.mean()
- nt = np.bincount(stats[3].astype(np.int64), minlength=nc) # number of targets per class
- else:
- nt = torch.zeros(1)
-
- # Print results
- pf = '%20s' + '%12.3g' * 6 # print format
- print(pf % ('all', seen, nt.sum(), mp, mr, map50, map))
-
- # Print results per class
- if verbose and nc > 1 and len(stats):
- for i, c in enumerate(ap_class):
- print(pf % (names[c], seen, nt[c], p[i], r[i], ap50[i], ap[i]))
-
- # Print speeds
- t = tuple(x / seen * 1E3 for x in (t0, t1, t0 + t1)) + (imgsz, imgsz, batch_size) # tuple
- if not training:
- print('Speed: %.1f/%.1f/%.1f ms inference/NMS/total per %gx%g image at batch-size %g' % t)
-
- # Save JSON
- if save_json and len(jdict):
- f = 'detections_val2017_%s_results.json' % \
- (weights.split(os.sep)[-1].replace('.pt', '') if isinstance(weights, str) else '') # filename
- print('\nCOCO mAP with pycocotools... saving %s...' % f)
- with open(f, 'w') as file:
- json.dump(jdict, file)
-
- try: # https://github.com/cocodataset/cocoapi/blob/master/PythonAPI/pycocoEvalDemo.ipynb
- from pycocotools.coco import COCO
- from pycocotools.cocoeval import COCOeval
-
- imgIds = [int(Path(x).stem) for x in dataloader.dataset.img_files]
- cocoGt = COCO(glob.glob('../coco/annotations/instances_val*.json')[0]) # initialize COCO ground truth api
- cocoDt = cocoGt.loadRes(f) # initialize COCO pred api
- cocoEval = COCOeval(cocoGt, cocoDt, 'bbox')
- cocoEval.params.imgIds = imgIds # image IDs to evaluate
- cocoEval.evaluate()
- cocoEval.accumulate()
- cocoEval.summarize()
- map, map50 = cocoEval.stats[:2] # update results (mAP@0.5:0.95, mAP@0.5)
- except Exception as e:
- print('ERROR: pycocotools unable to run: %s' % e)
-
- # Return results
- model.float() # for training
- maps = np.zeros(nc) + map
- for i, c in enumerate(ap_class):
- maps[c] = ap[i]
- return (mp, mr, map50, map, *(loss.cpu() / len(dataloader)).tolist()), maps, t
计算 p r
ap50就来自:compute_ap
- def ap_per_class(tp, conf, pred_cls, target_cls):
- """ Compute the average precision, given the recall and precision curves.
- Source: https://github.com/rafaelpadilla/Object-Detection-Metrics.
- # Arguments
- tp: True positives (nparray, nx1 or nx10).
- conf: Objectness value from 0-1 (nparray).
- pred_cls: Predicted object classes (nparray).
- target_cls: True object classes (nparray).
- # Returns
- The average precision as computed in py-faster-rcnn.
- """
-
- # Sort by objectness
- i = np.argsort(-conf)
- tp, conf, pred_cls = tp[i], conf[i], pred_cls[i]
-
- # Find unique classes
- unique_classes = np.unique(target_cls)
-
- # Create Precision-Recall curve and compute AP for each class
- pr_score = 0.1 # score to evaluate P and R https://github.com/ultralytics/yolov3/issues/898
- s = [unique_classes.shape[0], tp.shape[1]] # number class, number iou thresholds (i.e. 10 for mAP0.5...0.95)
- ap, p, r = np.zeros(s), np.zeros(s), np.zeros(s)
- for ci, c in enumerate(unique_classes):
- i = pred_cls == c
- n_gt = (target_cls == c).sum() # Number of ground truth objects
- n_p = i.sum() # Number of predicted objects
-
- if n_p == 0 or n_gt == 0:
- continue
- else:
- # Accumulate FPs and TPs
- fpc = (1 - tp[i]).cumsum(0)
- tpc = tp[i].cumsum(0)
-
- # Recall
- recall = tpc / (n_gt + 1e-16) # recall curve
- r[ci] = np.interp(-pr_score, -conf[i], recall[:, 0]) # r at pr_score, negative x, xp because xp decreases
-
- # Precision
- precision = tpc / (tpc + fpc) # precision curve
- p[ci] = np.interp(-pr_score, -conf[i], precision[:, 0]) # p at pr_score
-
- # AP from recall-precision curve
- for j in range(tp.shape[1]):
- ap[ci, j] = compute_ap(recall[:, j], precision[:, j])
-
- # Plot
- # fig, ax = plt.subplots(1, 1, figsize=(5, 5))
- # ax.plot(recall, precision)
- # ax.set_xlabel('Recall')
- # ax.set_ylabel('Precision')
- # ax.set_xlim(0, 1.01)
- # ax.set_ylim(0, 1.01)
- # fig.tight_layout()
- # fig.savefig('PR_curve.png', dpi=300)
-
- # Compute F1 score (harmonic mean of precision and recall)
- f1 = 2 * p * r / (p + r + 1e-16)
-
- return p, r, ap, f1, unique_classes.astype('int32')
常见报错:
BrokenPipeError: [Errno 32] Broken pipe
- File "G:/project/detect/v5/yolov5_annotations/train.py", line 890, in <module>
- main(opt)
- File "G:/project/detect/v5/yolov5_annotations/train.py", line 755, in main
- train(opt.hyp, opt, device)
- File "G:/project/detect/v5/yolov5_annotations/train.py", line 320, in train
- prefix=colorstr('val: '))[0]
- File "G:\project\detect\v5\yolov5_annotations\utils\datasets.py", line 136, in create_dataloader
- collate_fn=LoadImagesAndLabels.collate_fn4 if quad else LoadImagesAndLabels.collate_fn)
- File "G:\project\detect\v5\yolov5_annotations\utils\datasets.py", line 149, in __init__
- self.iterator = super().__iter__()
- File "D:\Users\Administrator\miniconda3\lib\site-packages\torch\utils\data\dataloader.py", line 352, in __iter__
- return self._get_iterator()
- File "D:\Users\Administrator\miniconda3\lib\site-packages\torch\utils\data\dataloader.py", line 294, in _get_iterator
- return _MultiProcessingDataLoaderIter(self)
- File "D:\Users\Administrator\miniconda3\lib\site-packages\torch\utils\data\dataloader.py", line 801, in __init__
- w.start()
- File "D:\Users\Administrator\miniconda3\lib\multiprocessing\process.py", line 112, in start
- self._popen = self._Popen(self)
- File "D:\Users\Administrator\miniconda3\lib\multiprocessing\context.py", line 223, in _Popen
- return _default_context.get_context().Process._Popen(process_obj)
- File "D:\Users\Administrator\miniconda3\lib\multiprocessing\context.py", line 322, in _Popen
- return Popen(process_obj)
- File "D:\Users\Administrator\miniconda3\lib\multiprocessing\popen_spawn_win32.py", line 89, in __init__
- reduction.dump(process_obj, to_child)
- File "D:\Users\Administrator\miniconda3\lib\multiprocessing\reduction.py", line 60, in dump
- ForkingPickler(file, protocol).dump(obj)
- BrokenPipeError: [Errno 32] Broken pipe
解决方法:
workers 默认为8,改小一点,不能超过电脑cpu核数。
parser.add_argument('--workers', type=int, default=1, help='maximum number of dataloader workers')
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。