YOLOv5代码详解 (第二部分)
2. test.py2.1 设置超参数2.2 设置任务(验证,测试,学习)2.3 测试函数2.3.1 初始化模型2.3.2 判断设备类型并仅使用一张GPU进行测试2.3.3 获取配置文件路径和文件参数2.3.4 数据获取2.3.5 计算map数据2.3.5 打印结果(图片,速度),保存结果至json,并返回结果
2. test.py
该部分主要用于运行train.py时,计算每个epoch的mAP。 PS,与train.py相似的部分就不再阐述。
2.1 设置超参数
权重,数据,batch size,图像尺寸,使用哪张显卡,数据增强,计算mAP。
if __name__
== '__main__':
parser
= argparse
.ArgumentParser
(prog
='test.py')
parser
.add_argument
('--weights', type=str, default
='weights/best.pt', help='model.pt path')
parser
.add_argument
('--data', type=str, default
='data/coco.yaml', help='*.data path')
parser
.add_argument
('--batch-size', type=int, default
=16, help='size of each image batch')
parser
.add_argument
('--img-size', type=int, default
=608, help='inference size (pixels)')
parser
.add_argument
('--conf-thres', type=float, default
=0.001, help='object confidence threshold')
parser
.add_argument
('--iou-thres', type=float, default
=0.65, help='IOU threshold for NMS')
parser
.add_argument
('--save-json', action
='store_true', help='save a cocoapi-compatible JSON results file')
parser
.add_argument
('--task', default
='', help="'val', 'test', 'study'")
parser
.add_argument
('--device', default
='1', help='cuda device, i.e. 0 or 0,1,2,3 or cpu')
parser
.add_argument
('--single-cls', action
='store_true', help='treat as single-class dataset')
parser
.add_argument
('--augment', action
='store_true', help='augmented inference')
parser
.add_argument
('--verbose', action
='store_true', help='report mAP by class')
opt
= parser
.parse_args
()
opt
.img_size
= check_img_size
(opt
.img_size
)
opt
.save_json
= opt
.save_json
or opt
.data
.endswith
('coco.yaml')
opt
.data
= check_file
(opt
.data
)
print(opt
)
2.2 设置任务(验证,测试,学习)
if opt
.task
in ['val','test']:
test
(opt
.data
,
opt
.weights
,
opt
.batch_size
,
opt
.img_size
,
opt
.conf_thres
,
opt
.iou_thres
,
opt
.save_json
,
opt
.single_cls
,
opt
.augment
,
opt
.verbose
)
elif opt
.task
== 'study':
for weights
in ['yolov5s.pt', 'yolov5m.pt', 'yolov5l.pt', 'yolov5x.pt']:
f
= 'study_%s_%s.txt' % (Path
(opt
.data
).stem
, Path
(weights
).stem
)
x
= list(range(288, 896, 64))
y
= []
for i
in x
:
print('\nRunning %s point %s...' % (f
, i
))
r
, _
, t
= test
(opt
.data
, weights
, opt
.batch_size
, i
, opt
.conf_thres
, opt
.iou_thres
, opt
.save_json
)
y
.append
(r
+ t
)
np
.savetxt
(f
, y
, fmt
='.4g')
os
.system
('zip -r study.zip study_*.txt')
“study”应该是可以修改预训练权重的操作。
2.3 测试函数
2.3.1 初始化模型
判断模型是否存在,若不存在则训练为假,移除之前的测试结果,下载模型。
if model
is None:
training
= False
device
= torch_utils
.select_device
(opt
.device
, batch_size
=batch_size
)
half
= device
.type != 'cpu'
for f
in glob
.glob
('test_batch*.jpg'):
os
.remove
(f
)
google_utils
.attempt_download
(weights
)
model
= torch
.load
(weights
, map_location
=device
)['model'].float()
torch_utils
.model_info
(model
)
model
.fuse
()
model
.to
(device
)
if half
:
model
.half
()
else:
training
= True
device
= next(model
.parameters
()).device
half
= False
if half
:
model
.half
()
2.3.2 判断设备类型并仅使用一张GPU进行测试
判断设备类型并仅仅单GPU支持一半的精度。
half
= device
.type != 'cpu' and torch
.cuda
.device_count
() == 1
if half
:
model
.half
()
2.3.3 获取配置文件路径和文件参数
获取配置文件yaml中的nc参数。
model
.eval()
with open(data
) as f
:
data
= yaml
.load
(f
, Loader
=yaml
.FullLoader
)
nc
= 1 if single_cls
else int(data
['nc'])
iouv
= torch
.linspace
(0.5, 0.95, 10).to
(device
)
niou
= iouv
.numel
()
2.3.4 数据获取
测试数据的获取使用NMS初始化图片大小获取测试或验证图片路径用梯度下降运行模型、计算损失和运行NMS计算每张测试图片的数据并保存到json文件中画出某几张batch图片(包括GT和预测)
if dataloader
is None:
merge
= opt
.merge
img
= torch
.zeros
((1, 3, imgsz
, imgsz
), device
=device
)
_
= model
(img
.half
() if half
else img
) if device
.type != 'cpu' else None
path
= data
['test'] if opt
.task
== 'test' else data
['val']
dataloader
= create_dataloader
(path
, imgsz
, batch_size
, int(max(model
.stride
)), opt
,
hyp
=None, augment
=False, cache
=False, pad
=0.5, rect
=True)[0]
seen
= 0
names
= model
.names
if hasattr(model
, 'names') else model
.module
.names
coco91class
= coco80_to_coco91_class
()
s
= (' s' + 's' * 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
)
img
= img
.half
() if half
else img
.float()
img
/= 255.0
targets
= targets
.to
(device
)
nb
, _
, height
, width
= img
.shape
whwh
= torch
.Tensor
([width
, height
, width
, height
]).to
(device
)
with torch
.no_grad
():
t
= torch_utils
.time_synchronized
()
inf_out
, train_out
= model
(img
, augment
=augment
)
t0
+= torch_utils
.time_synchronized
() - t
if training
:
loss
+= compute_loss
([x
.float() for x
in train_out
], targets
, model
)[1][:3]
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
for si
, pred
in enumerate(output
):
labels
= targets
[targets
[:, 0] == si
, 1:]
nl
= len(labels
)
tcls
= labels
[:, 0].tolist
() if nl
else []
seen
+= 1
if pred
is None:
if nl
:
stats
.append
((torch
.zeros
(0, niou
, dtype
=torch
.bool), torch
.Tensor
(), torch
.Tensor
(), tcls
))
continue
clip_coords
(pred
, (height
, width
))
if save_json
:
image_id
= int(Path
(paths
[si
]).stem
.split
('_')[-1])
box
= pred
[:, :4].clone
()
scale_coords
(img
[si
].shape
[1:], box
, shapes
[si
][0], shapes
[si
][1])
box
= xyxy2xywh
(box
)
box
[:, :2] -= box
[:, 2:] / 2
for p
, b
in zip(pred
.tolist
(), box
.tolist
()):
jdict
.append
({'image_id': image_id
,
'category_id': coco91class
[int(p
[5])],
'bbox': [round(x
, 3) for x
in b
],
'score': round(p
[4], 5)})
correct
= torch
.zeros
(pred
.shape
[0], niou
, dtype
=torch
.bool, device
=device
)
if nl
:
detected
= []
tcls_tensor
= labels
[:, 0]
tbox
= xywh2xyxy
(labels
[:, 1:5]) * whwh
for cls
in torch
.unique
(tcls_tensor
):
ti
= (cls
== tcls_tensor
).nonzero
().view
(-1)
pi
= (cls
== pred
[:, 5]).nonzero
().view
(-1)
if pi
.shape
[0]:
ious
, i
= box_iou
(pred
[pi
, :4], tbox
[ti
]).max(1)
for j
in (ious
> iouv
[0]).nonzero
():
d
= ti
[i
[j
]]
if d
not in detected
:
detected
.append
(d
)
correct
[pi
[j
]] = ious
[j
] > iouv
if len(detected
) == nl
:
break
stats
.append
((correct
.cpu
(), pred
[:, 4].cpu
(), pred
[:, 5].cpu
(), tcls
))
if batch_i
< 1:
f
= 'test_batch%g_gt.jpg' % batch_i
plot_images
(img
, targets
, paths
, f
, names
)
f
= 'test_batch%g_pred.jpg' % batch_i
plot_images
(img
, output_to_target
(output
, width
, height
), paths
, f
, names
)
2.3.5 计算map数据
stats
= [np
.concatenate
(x
, 0) for x
in zip(*stats
)]
if len(stats
):
p
, r
, ap
, f1
, ap_class
= ap_per_class
(*stats
)
p
, r
, ap50
, ap
= p
[:, 0], r
[:, 0], ap
[:, 0], ap
.mean
(1)
mp
, mr
, map50
, map = p
.mean
(), r
.mean
(), ap50
.mean
(), ap
.mean
()
nt
= np
.bincount
(stats
[3].astype
(np
.int64
), minlength
=nc
)
else:
nt
= torch
.zeros
(1)
2.3.5 打印结果(图片,速度),保存结果至json,并返回结果
pf
= ' s' + '.3g' * 6
print(pf
% ('all', seen
, nt
.sum(), mp
, mr
, map50
, map))
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
]))
t
= tuple(x
/ seen
* 1E3 for x
in (t0
, t1
, t0
+ t1
)) + (imgsz
, imgsz
, batch_size
)
if not training
:
print('Speed: %.1f/%.1f/%.1f ms inference/NMS/total per %gx%g image at batch-size %g' % t
)
if save_json
and map50
and len(jdict
):
imgIds
= [int(Path
(x
).stem
.split
('_')[-1]) for x
in dataloader
.dataset
.img_files
]
f
= 'detections_val2017_%s_results.json' % \
(weights
.split
(os
.sep
)[-1].replace
('.pt', '') if weights
else '')
print('\nCOCO mAP with pycocotools... saving %s...' % f
)
with open(f
, 'w') as file:
json
.dump
(jdict
, file)
try:
from pycocotools
.coco
import COCO
from pycocotools
.cocoeval
import COCOeval
cocoGt
= COCO
(glob
.glob
('../coco/annotations/instances_val*.json')[0])
cocoDt
= cocoGt
.loadRes
(f
)
cocoEval
= COCOeval
(cocoGt
, cocoDt
, 'bbox')
cocoEval
.params
.imgIds
= imgIds
cocoEval
.evaluate
()
cocoEval
.accumulate
()
cocoEval
.summarize
()
map, map50
= cocoEval
.stats
[:2]
except:
print('WARNING: pycocotools must be installed with numpy==1.17 to run correctly. '
'See https://github.com/cocodataset/cocoapi/issues/356')
model
.float()
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