目录
1.Yolov7介绍
官方版的YOLOv7相同体量下比YOLOv5精度更高,速度快120%(FPS),比 YOLOX 快180%(FPS),比 Dual-Swin-T 快1200%(FPS),比 ConvNext 快550%(FPS),比 SWIN-L快500%(FPS)。在5FPS到160FPS的范围内,无论是速度或是精度,YOLOv7都超过了目前已知的检测器,并且在GPU V100上进行测试, 精度为56.8% AP的模型可达到30 FPS(batch=1)以上的检测速率,与此同时,这是目前唯一一款在如此高精度下仍能超过30FPS的检测器。
1.1.polygon detection介绍(不规则四边形的目标检测)
使模型输出的是x1,y1,x2,y2,x3,y3,x4,y4,box_score,class_score,也就是去掉box分支,这时可以减小模型体积,减少计算量。修改的地方在yolov5的检测头和loss函数,图解如下:
(1).build_targets函数和检测头Detect
(2). 4个角点逆时针排序
(3). polygon box loss
2.不规则多边形数据集格式转换
在ultralytics/yolov5的检测头里添加一个学习四个角点的分支,这时,模型输出的每个候选框里包含的信息是 x,y,w,h, box_score, x1,y1,x2,y2,x3,y3,x4,y4, class_score这种形式,其中x1,y1,x2,y2,x3,y3,x4,y4表示4个角点的坐标值x和y。这时候的模型就能满足项目的需求,但是注意到水平矩形框的位置信息x,y,w,h在项目里并没有起作用,因此水平矩形框的位置信息是一个冗余的信息。
标注数据集格式如下:
<annotation>
<folder>JPEGImages</folder>
<filename>BB1_0_0_0_1.jpg</filename>
<source>
<database>Unknown</database>
</source>
<size>
<width>640</width>
<height>640</height>
<depth>3</depth>
</size>
<segmented>0</segmented>
<object>
<name>BB1</name>
<pose>Unspecified</pose>
<truncated>0</truncated>
<difficult>0</difficult>
<gt_poly>
<x1>127.0</x1>
<y1>276.0</y1>
<x2>127.0</x2>
<y2>373.0</y2>
<x3>532.0</x3>
<y3>373.0</y3>
<x4>532.0</x4>
<y4>276.0</y4>
</gt_poly>
</object>
</annotation>
格式转换代码:
python Polygon_xml_txt.py --xml_dir Annotations --output_dir labels --classes BB1,BB3
#python Polygon_xml_txt.py --xml_dir Annotations --output_dir labels --classes BB1,BB3
import os
import glob
import argparse
import xml.etree.ElementTree as ET
def parse_args():
parser = argparse.ArgumentParser()
parser.add_argument('--xml_dir', type=str, help='Path to the directory containing XML files')
parser.add_argument('--output_dir', type=str, help='Path to the output directory')
parser.add_argument('--classes', type=str, help='Comma-separated list of classes')
args = parser.parse_args()
return args
def convert(xml_dir, output_dir, classes):
xml_files = glob.glob(os.path.join(xml_dir, '*.xml'))
for xml_file in xml_files:
basename = os.path.basename(xml_file)
filename = os.path.splitext(basename)[0]
outfile = os.path.join(output_dir, f'{filename}.txt')
with open(outfile, 'w') as f:
tree = ET.parse(xml_file)
root = tree.getroot()
size = root.find('size')
width = int(size.find('width').text)
height = int(size.find('height').text)
for obj in root.iter('object'):
cls = obj.find('name').text
if cls not in classes:
continue
cls_id = classes.index(cls)
xml_box = obj.find('gt_poly')
x1 = float(obj.find('gt_poly/x1').text)
y1 = float(obj.find('gt_poly/y1').text)
x2 = float(obj.find('gt_poly/x2').text)
y2 = float(obj.find('gt_poly/y2').text)
x3 = float(obj.find('gt_poly/x3').text)
y3 = float(obj.find('gt_poly/y3').text)
x4 = float(obj.find('gt_poly/x4').text)
y4 = float(obj.find('gt_poly/y4').text)
yolo_x1 = round(float(x1 / width), 6)
yolo_y1 = round(float(y1 / height), 6)
yolo_x2 = round(float(x2 / width), 6)
yolo_y2 = round(float(y2 / height), 6)
yolo_x3 = round(float(x3 / width), 6)
yolo_y3 = round(float(y3 / height), 6)
yolo_x4 = round(float(x4 / width), 6)
yolo_y4 = round(float(y4 / height), 6)
f.write(f'{cls_id} {yolo_x1:.6f} {yolo_y1:.6f} {yolo_x2:.6f} {yolo_y2:.6f} {yolo_x3:.6f} {yolo_y3:.6f} {yolo_x4:.6f} {yolo_y4:.6f} \n')
if __name__ == '__main__':
args = parse_args()
classes = args.classes.split(',')
convert(args.xml_dir, args.output_dir, classes)
转化后的格式如下:
0 0.198437 0.431250 0.198437 0.582812 0.831250 0.582812 0.831250 0.431250
源码获取:GitHub - AlbinZhu/yolov7-polygon-detection: yolov7-polygon-detection
3.如何训练polygon-yolov7
3.1 data/polygon_test.yaml修改
train: ../test/trian/ #
val: ../test/val/
# number of classes
nc: 2
# class names
names: ['BB1', 'BB3']
3.2 yolov7-tiny-polygon.yaml
# parameters
nc: 2 # number of classes
depth_multiple: 1.0 # model depth multiple
width_multiple: 1.0 # layer channel multiple
# anchors
anchors:
# - [10,13, 16,30, 33,23] # P3/8
# - [30,61, 62,45, 59,119] # P4/16
# - [116,90, 156,198, 373,326] # P5/32
- [31, 30, 28, 49, 50, 31] # P3/8
- [46, 45, 58, 58, 74, 74] # P4/16
- [94, 94, 115, 115, 151, 151] # P5/32
# yolov7-tiny backbone
backbone:
# [from, number, module, args] c2, k=1, s=1, p=None, g=1, act=True
[[-1, 1, Conv, [32, 3, 2, None, 1, nn.LeakyReLU(0.1)]], # 0-P1/2
[-1, 1, Conv, [64, 3, 2, None, 1, nn.LeakyReLU(0.1)]], # 1-P2/4
[-1, 1, Conv, [32, 1, 1, None, 1, nn.LeakyReLU(0.1)]],
[-2, 1, Conv, [32, 1, 1, None, 1, nn.LeakyReLU(0.1)]],
[-1, 1, Conv, [32, 3, 1, None, 1, nn.LeakyReLU(0.1)]],
[-1, 1, Conv, [32, 3, 1, None, 1, nn.LeakyReLU(0.1)]],
[[-1, -2, -3, -4], 1, Concat, [1]],
[-1, 1, Conv, [64, 1, 1, None, 1, nn.LeakyReLU(0.1)]], # 7
[-1, 1, MP, []], # 8-P3/8
[-1, 1, Conv, [64, 1, 1, None, 1, nn.LeakyReLU(0.1)]],
[-2, 1, Conv, [64, 1, 1, None, 1, nn.LeakyReLU(0.1)]],
[-1, 1, Conv, [64, 3, 1, None, 1, nn.LeakyReLU(0.1)]],
[-1, 1, Conv, [64, 3, 1, None, 1, nn.LeakyReLU(0.1)]],
[[-1, -2, -3, -4], 1, Concat, [1]],
[-1, 1, Conv, [128, 1, 1, None, 1, nn.LeakyReLU(0.1)]], # 14
[-1, 1, MP, []], # 15-P4/16
[-1, 1, Conv, [128, 1, 1, None, 1, nn.LeakyReLU(0.1)]],
[-2, 1, Conv, [128, 1, 1, None, 1, nn.LeakyReLU(0.1)]],
[-1, 1, Conv, [128, 3, 1, None, 1, nn.LeakyReLU(0.1)]],
[-1, 1, Conv, [128, 3, 1, None, 1, nn.LeakyReLU(0.1)]],
[[-1, -2, -3, -4], 1, Concat, [1]],
[-1, 1, Conv, [256, 1, 1, None, 1, nn.LeakyReLU(0.1)]], # 21
[-1, 1, MP, []], # 22-P5/32
[-1, 1, Conv, [256, 1, 1, None, 1, nn.LeakyReLU(0.1)]],
[-2, 1, Conv, [256, 1, 1, None, 1, nn.LeakyReLU(0.1)]],
[-1, 1, Conv, [256, 3, 1, None, 1, nn.LeakyReLU(0.1)]],
[-1, 1, Conv, [256, 3, 1, None, 1, nn.LeakyReLU(0.1)]],
[[-1, -2, -3, -4], 1, Concat, [1]],
[-1, 1, Conv, [512, 1, 1, None, 1, nn.LeakyReLU(0.1)]], # 28
]
# yolov7-tiny head
head:
[[-1, 1, Conv, [256, 1, 1, None, 1, nn.LeakyReLU(0.1)]],
[-2, 1, Conv, [256, 1, 1, None, 1, nn.LeakyReLU(0.1)]],
[-1, 1, SP, [5]],
[-2, 1, SP, [9]],
[-3, 1, SP, [13]],
[[-1, -2, -3, -4], 1, Concat, [1]],
[-1, 1, Conv, [256, 1, 1, None, 1, nn.LeakyReLU(0.1)]],
[[-1, -7], 1, Concat, [1]],
[-1, 1, Conv, [256, 1, 1, None, 1, nn.LeakyReLU(0.1)]], # 37
[-1, 1, Conv, [128, 1, 1, None, 1, nn.LeakyReLU(0.1)]],
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
[21, 1, Conv, [128, 1, 1, None, 1, nn.LeakyReLU(0.1)]], # route backbone P4
[[-1, -2], 1, Concat, [1]],
[-1, 1, Conv, [64, 1, 1, None, 1, nn.LeakyReLU(0.1)]],
[-2, 1, Conv, [64, 1, 1, None, 1, nn.LeakyReLU(0.1)]],
[-1, 1, Conv, [64, 3, 1, None, 1, nn.LeakyReLU(0.1)]],
[-1, 1, Conv, [64, 3, 1, None, 1, nn.LeakyReLU(0.1)]],
[[-1, -2, -3, -4], 1, Concat, [1]],
[-1, 1, Conv, [128, 1, 1, None, 1, nn.LeakyReLU(0.1)]], # 47
[-1, 1, Conv, [64, 1, 1, None, 1, nn.LeakyReLU(0.1)]],
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
[14, 1, Conv, [64, 1, 1, None, 1, nn.LeakyReLU(0.1)]], # route backbone P3
[[-1, -2], 1, Concat, [1]],
[-1, 1, Conv, [32, 1, 1, None, 1, nn.LeakyReLU(0.1)]],
[-2, 1, Conv, [32, 1, 1, None, 1, nn.LeakyReLU(0.1)]],
[-1, 1, Conv, [32, 3, 1, None, 1, nn.LeakyReLU(0.1)]],
[-1, 1, Conv, [32, 3, 1, None, 1, nn.LeakyReLU(0.1)]],
[[-1, -2, -3, -4], 1, Concat, [1]],
[-1, 1, Conv, [64, 1, 1, None, 1, nn.LeakyReLU(0.1)]], # 57
[-1, 1, Conv, [128, 3, 2, None, 1, nn.LeakyReLU(0.1)]],
[[-1, 47], 1, Concat, [1]],
[-1, 1, Conv, [64, 1, 1, None, 1, nn.LeakyReLU(0.1)]],
[-2, 1, Conv, [64, 1, 1, None, 1, nn.LeakyReLU(0.1)]],
[-1, 1, Conv, [64, 3, 1, None, 1, nn.LeakyReLU(0.1)]],
[-1, 1, Conv, [64, 3, 1, None, 1, nn.LeakyReLU(0.1)]],
[[-1, -2, -3, -4], 1, Concat, [1]],
[-1, 1, Conv, [128, 1, 1, None, 1, nn.LeakyReLU(0.1)]], # 65
[-1, 1, Conv, [256, 3, 2, None, 1, nn.LeakyReLU(0.1)]],
[[-1, 37], 1, Concat, [1]],
[-1, 1, Conv, [128, 1, 1, None, 1, nn.LeakyReLU(0.1)]],
[-2, 1, Conv, [128, 1, 1, None, 1, nn.LeakyReLU(0.1)]],
[-1, 1, Conv, [128, 3, 1, None, 1, nn.LeakyReLU(0.1)]],
[-1, 1, Conv, [128, 3, 1, None, 1, nn.LeakyReLU(0.1)]],
[[-1, -2, -3, -4], 1, Concat, [1]],
[-1, 1, Conv, [256, 1, 1, None, 1, nn.LeakyReLU(0.1)]], # 73
[57, 1, Conv, [128, 3, 1, None, 1, nn.LeakyReLU(0.1)]],
[65, 1, Conv, [256, 3, 1, None, 1, nn.LeakyReLU(0.1)]],
[73, 1, Conv, [512, 3, 1, None, 1, nn.LeakyReLU(0.1)]],
[[74,75,76], 1, Polygon_Detect, [nc, anchors]], # Detect(P3, P4, P5)
]
3.3 train_polygon.py修改
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('--weights', type=str, default='weights/yolov7-tiny.pt', help='initial weights path')
parser.add_argument('--cfg', type=str, default='cfg/training/yolov7-tiny-polygon.yaml', help='model.yaml path')
parser.add_argument('--data', type=str, default='data/polygon_test.yaml', help='data.yaml path')
parser.add_argument('--hyp', type=str, default='data/hyp.ucas.yaml', help='hyperparameters path')
parser.add_argument('--epochs', type=int, default=300)
parser.add_argument('--batch-size', type=int, default=16, help='total batch size for all GPUs')
parser.add_argument('--img-size', nargs='+', type=int, default=[640, 640], help='[train, test] image sizes')
parser.add_argument('--rect', action='store_true', help='rectangular training')
parser.add_argument('--resume', nargs='?', const=True, default=False, help='resume most recent training')
parser.add_argument('--nosave', action='store_true', help='only save final checkpoint')
parser.add_argument('--notest', action='store_true', help='only test final epoch')
parser.add_argument('--noautoanchor', action='store_true', help='disable autoanchor check')
parser.add_argument('--evolve', action='store_true', help='evolve hyperparameters')
parser.add_argument('--bucket', type=str, default='', help='gsutil bucket')
parser.add_argument('--cache-images', action='store_true', help='cache images for faster training')
parser.add_argument('--image-weights', action='store_true', help='use weighted image selection for training')
parser.add_argument('--device', default='1', help='cuda device, i.e. 0 or 0,1,2,3 or cpu')
parser.add_argument('--multi-scale', action='store_true', help='vary img-size +/- 50%%')
parser.add_argument('--single-cls', action='store_true', help='train multi-class data as single-class')
parser.add_argument('--adam', action='store_true', help='use torch.optim.Adam() optimizer')
parser.add_argument('--sync-bn', action='store_true', help='use SyncBatchNorm, only available in DDP mode')
parser.add_argument('--local_rank', type=int, default=-1, help='DDP parameter, do not modify')
parser.add_argument('--workers', type=int, default=0, help='maximum number of dataloader workers')
parser.add_argument('--project', default='runs/train', help='save to project/name')
parser.add_argument('--entity', default=None, help='W&B entity')
parser.add_argument('--name', default='exp', help='save to project/name')
parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment')
parser.add_argument('--quad', action='store_true', help='quad dataloader')
parser.add_argument('--linear-lr', action='store_true', help='linear LR')
parser.add_argument('--label-smoothing', type=float, default=0.0, help='Label smoothing epsilon')
parser.add_argument('--upload_dataset', action='store_true', help='Upload dataset as W&B artifact table')
parser.add_argument('--bbox_interval', type=int, default=-1, help='Set bounding-box image logging interval for W&B')
parser.add_argument('--save_period', type=int, default=-1, help='Log model after every "save_period" epoch')
parser.add_argument('--artifact_alias', type=str, default="latest", help='version of dataset artifact to be used')
parser.add_argument('--freeze', nargs='+', type=int, default=[0], help='Freeze layers: backbone of yolov7=50, first3=0 1 2')
parser.add_argument('--polygon', action='store_true', help='enable polygon anchor boxes')
opt = parser.parse_args()
3.4 开启训练
python train_polygon.py --polygon
4.性能评价
map为0.984
测试图片结果可视化: