1.导入需要的包和基本配置
importargparse#解析命令行参数模块 importjson#字典列表和JSON字符串之间的相互解析模块 importos#与操作系统进行交互的模块包含文件路径操作和解析 importsys#sys系统模块包含了与Python解释器和它的环境有关的函数 frompathlibimportPath#Path将str转换为Path对象使字符串路径易于操作的模块 importnumpyasnp#NumPy(NumericalPython)是Python的一种开源的数值计算扩展 importoneflowasflow#OneFlow深度学习框架 fromtqdmimporttqdm#进度条模块 frommodels.commonimportDetectMultiBackend#下面都是one-yolov5定义的模块,在本系列的其它文章都有涉及 fromutils.callbacksimportCallbacks fromutils.dataloadersimportcreate_dataloader fromutils.generalimport( LOGGER, check_dataset, check_img_size, check_requirements, check_yaml, coco80_to_coco91_class, colorstr, increment_path, non_max_suppression, print_args, scale_coords, xywh2xyxy, xyxy2xywh, ) fromutils.metricsimportConfusionMatrix,ap_per_class,box_iou fromutils.oneflow_utilsimportselect_device,time_sync fromutils.plotsimportoutput_to_target,plot_images,plot_val_study
2.opt参数详解
参数 | 解析 | |
---|---|---|
data | dataset.yaml path | 数据集配置文件地址 包含数据集的路径、类别个数、类名、下载地址等信息 |
weights | model weights path(s) | 模型的权重文件地址 weights/yolov5s |
batch-size | batch size | 计算样本的批次大小 默认32 |
imgsz | inference size (pixels) | 输入网络的图片分辨率 默认640 |
conf-thres | confidence threshold | object置信度阈值 默认0.001 |
iou-thres | NMS IoU threshold | 进行NMS时IOU的阈值 默认0.6 |
task | train, val, test, speed or study | 设置测试的类型 有train, val, test, speed or study几种 默认val |
device | cuda device, i.e. 0 or 0,1,2,3 or cpu | 测试的设备 |
workers | max dataloader workers (per RANK in DDP mode) | 加载数据使用的 dataloader workers |
single-cls | treat as single-class dataset | 数据集是否只用一个类别 默认False |
augment | augmented inference | 测试是否使用TTA Test Time Augment 默认False |
verbose | report mAP by class | 是否打印出每个类别的mAP 默认False |
save-hybrid | save label+prediction hybrid results to *.txt | 保存label+prediction 杂交结果到对应.txt 默认False |
save-conf | save confidences in --save-txt labels | |
save-json | save a COCO-JSON results file | 是否按照coco的json格式保存结果 默认False |
project | save to project/name | 测试保存的源文件 默认runs/val |
name | save to project/name | 测试保存的文件地址名 默认exp 保存在runs/val/exp下 |
exist-ok | existing project/name ok, do not increment | 是否保存在当前文件,不新增 默认False |
half | use FP16 half-precision inference | 是否使用半精度推理 默认False |
dnn | use OpenCV DNN for ONNX inference | 是否使用 OpenCV DNN 对 ONNX 模型推理 |
3.main函数
根据解析的opt参数,调用run函数
defmain(opt): #检测requirements文件中需要的包是否安装好了 check_requirements(requirements=ROOT/"requirements.txt",exclude=("tensorboard","thop")) ifopt.taskin("train","val","test"):#runnormally ifopt.conf_thres>0.001:#更多请见https://github.com/ultralytics/yolov5/issues/1466 LOGGER.info(f"WARNING:confidencethreshold{opt.conf_thres}>0.001producesinvalidresults") run(**vars(opt)) else: weights=opt.weightsifisinstance(opt.weights,list)else[opt.weights] opt.half=True#FP16forfastestresults ifopt.task=="speed":#speedbenchmarks #pythonval.py--taskspeed--datacoco.yaml #--batch1--weightsyolov5n/yolov5s/... opt.conf_thres,opt.iou_thres,opt.save_json=0.25,0.45,False foropt.weightsinweights: run(**vars(opt),plots=False) elifopt.task=="study":#speedvsmAPbenchmarks #pythonval.py--taskstudy--datacoco.yaml #--iou0.7--weightsyolov5n/yolov5s/... foropt.weightsinweights: f=f"study_{Path(opt.data).stem}_{Path(opt.weights).stem}.txt" x,y=( list(range(256,1536+128,128)), [], )#xaxis(imagesizes),yaxis #"study":模型在各个尺度下的指标并可视化, #上面list(range(256,1536+128,128)),代表img-size的各个尺度,具体代码如下: foropt.imgszinx:#img-size LOGGER.info(f" Running{f}--imgsz{opt.imgsz}...") r,_,t=run(**vars(opt),plots=False) y.append(r+t)#resultsandtimes np.savetxt(f,y,fmt="%10.4g")#save os.system("zip-rstudy.zipstudy_*.txt") #可视化各个指标 plot_val_study(x=x)#plot
3. run函数
3.1 载入参数
#不参与反向传播 @flow.no_grad() defrun( data,#数据集配置文件地址包含数据集的路径、类别个数、类名、下载地址等信息train.py时传入data_dict weights=None,#模型的权重文件地址运行train.py=None运行test.py=默认weights/yolov5s batch_size=32,#前向传播的批次大小运行test.py传入默认32运行train.py则传入batch_size//WORLD_SIZE*2 imgsz=640,#输入网络的图片分辨率运行test.py传入默认640运行train.py则传入imgsz_test conf_thres=0.001,#object置信度阈值默认0.001 iou_thres=0.6,#进行NMS时IOU的阈值默认0.6 task="val",#设置测试的类型有train,val,test,speedorstudy几种默认val device="",#执行val.py所在的设备cudadevice,i.e.0or0,1,2,3orcpu workers=8,#dataloader中的最大worker数(线程个数) single_cls=False,#数据集是否只有一个类别默认False augment=False,#测试时增强,详细请看我们的教程:https://start.oneflow.org/oneflow-yolo-doc/tutorials/03_chapter/TTA.html verbose=False,#是否打印出每个类别的mAP运行test.py传入默认Fasle运行train.py则传入nc< 50 and final_epoch save_txt=False, # 是否以txt文件的形式保存模型预测框的坐标 默认True save_hybrid=False, # 是否save label+prediction hybrid results to *.txt 默认False save_conf=False, # 是否保存预测每个目标的置信度到预测txt文件中 默认True save_json=False, # 是否按照coco的json格式保存预测框,并且使用cocoapi做评估(需要同样coco的json格式的标签), #运行test.py传入默认Fasle 运行train.py则传入is_coco and final_epoch(一般也是False) project=ROOT / "runs/val", # 验证结果保存的根目录 默认是 runs/val name="exp", # 验证结果保存的目录 默认是exp 最终: runs/val/exp exist_ok=False, # 如果文件存在就increment name,不存在就新建 默认False(默认文件都是不存在的) half=True, # 使用 FP16 的半精度推理 dnn=False, # 在 ONNX 推理时使用 OpenCV DNN 后段端 model=None, # 如果执行val.py就为None 如果执行train.py就会传入( model=attempt_load(f, device).half() ) dataloader=None, # 数据加载器 如果执行val.py就为None 如果执行train.py就会传入testloader save_dir=Path(""), # 文件保存路径 如果执行val.py就为‘’ , 如果执行train.py就会传入save_dir(runs/train/expn) plots=True, # 是否可视化 运行val.py传入,默认True callbacks=Callbacks(), compute_loss=None, # 损失函数 运行val.py传入默认None 运行train.py则传入compute_loss(train) ):
3.2 Initialize/load model and set device(初始化/加载模型以及设置设备)
iftraining:#通过train.py调用的run函数 device,of,engine=( next(model.parameters()).device, True, False, )#getmodeldevice,OneFlowmodel half&=device.type!="cpu"#halfprecisiononlysupportedonCUDA model.half()ifhalfelsemodel.float() else:#直接通过val.py调用run函数 device=select_device(device,batch_size=batch_size) #Directories生成save_dir文件路径run/val/expn save_dir=increment_path(Path(project)/name,exist_ok=exist_ok)#incrementrun (save_dir/"labels"ifsave_txtelsesave_dir).mkdir(parents=True,exist_ok=True)#makedir #加载模型只在运行val.py才需要自己加载model model=DetectMultiBackend(weights,device=device,dnn=dnn,data=data,fp16=half) stride,of,engine=model.stride,model.of,model.engine #检测输入图片的分辨率imgsz是否能被stride整除 imgsz=check_img_size(imgsz,s=stride)#checkimagesize half=model.fp16#FP16supportedonlimitedbackendswithCUDA ifengine: batch_size=model.batch_size else: device=model.device ifnotof: batch_size=1#export.pymodelsdefaulttobatch-size1 LOGGER.info(f"Forcing--batch-size1inference(1,3,{imgsz},{imgsz})fornon-OneFlowmodels") #Data data=check_dataset(data)#check
3.3 Configure
#配置 model.eval()#启动模型验证模式 cuda=device.type!="cpu" is_coco=isinstance(data.get("val"),str)anddata["val"].endswith(f"coco{os.sep}val2017.txt")#通过COCO数据集的文件夹组织结构判断当前数据集是否为COCO数据集 nc=1ifsingle_clselseint(data["nc"])#numberofclasses #设置iou阈值从0.5-0.95取10个(0.05间隔)iouvectorformAP@0.5:0.95 #iouv:[0.50000,0.55000,0.60000,0.65000,0.70000,0.75000,0.80000,0.85000,0.90000,0.95000] iouv=flow.linspace(0.5,0.95,10,device=device)#iouvectorformAP@0.5:0.95 niou=iouv.numel()#示例mAP@0.5:0.95iou阈值个数=10个,计算mAP的详细教程可以在https://start.oneflow.org/oneflow-yolo-doc/tutorials/05_chapter/map_analysis.html这里查看
3.4 Dataloader
通过 train.py 调用 run 函数会传入一个 Dataloader,而通过 val.py 需要加载测试数据集
#Dataloader #如果不是训练(执行val.py脚本调用run函数)就调用create_dataloader生成dataloader #如果是训练(执行train.py调用run函数)就不需要生成dataloader可以直接从参数中传过来testloader ifnottraining:#加载val数据集 ifofandnotsingle_cls:#check--weightsaretrainedon--data ncm=model.model.nc assertncm==nc,( f"{weights}({ncm}classes)trainedondifferent--datathanwhatyoupassed({nc}"f"classes).Passcorrectcombinationof"f"--weightsand--datathataretrainedtogether." ) model.warmup(imgsz=(1ifofelsebatch_size,3,imgsz,imgsz))#warmup pad=0.0iftaskin("speed","benchmark")else0.5 rect=Falseiftask=="benchmark"elseof#squareinferenceforbenchmarks task=taskiftaskin("train","val","test")else"val"#pathtotrain/val/testimages #创建dataloader这里的rect默认为True矩形推理用于测试集在不影响mAP的情况下可以大大提升推理速度 dataloader=create_dataloader( data[task], imgsz, batch_size, stride, single_cls, pad=pad, rect=rect, workers=workers, prefix=colorstr(f"{task}:"), )[0]
3.5 初始化
#初始化验证的图片的数量 seen=0 #初始化混淆矩阵 confusion_matrix=ConfusionMatrix(nc=nc) #获取数据集所有目标类别的类名 names=dict(enumerate(model.namesifhasattr(model,"names")elsemodel.module.names)) #coco80_to_coco91_class:converts80-index(val2014)to91-index(paper) #https://tech.amikelive.com/node-718/what-object-categories-labels-are-in-coco-dataset/ class_map=coco80_to_coco91_class()ifis_cocoelselist(range(1000)) #设置进度条模块显示信息 s=("%20s"+"%11s"*6)%( "Class", "Images", "Labels", "P", "R", "mAP@.5", "mAP@.5:.95", ) #初始化时间dt[t0(预处理的时间),t1(推理的时间),t2(后处理的时间)]和p,r,f1,mp,mr,map50,map指标 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, ) #初始化验证集的损失 loss=flow.zeros(3,device=device) #初始化json文件中的字典,统计信息,ap,ap_class jdict,stats,ap,ap_class=[],[],[],[] callbacks.run("on_val_start") #初始化tqdm进度条模块 pbar=tqdm(dataloader,desc=s,bar_format="{l_bar}{bar:10}{r_bar}{bar:-10b}")示例输出
val:data=data/coco.yaml,weights=['yolov5x'],batch_size=32,imgsz=640,conf_thres=0.001,iou_thres=0.6,task=val, device=,workers=8,single_cls=False,augment=False,verbose=False,save_txt=False,save_hybrid=False, save_conf=False,save_json=True,project=runs/val,name=exp,exist_ok=False,half=True,dnn=False YOLOv5v1.0-8-g94ec5c4Python-3.8.13oneflow-0.8.1.dev20221018+cu112 Fusinglayers... Modelsummary:322layers,86705005parameters,571965gradients val:Scanning'/data/dataset/fengwen/coco/val2017.cache'imagesandlabels...4952found,48missing,0empty,0corrupt:100%|████████ ClassImagesLabelsPRmAP@.5mAP@.5:.95:100%|██████████|157/157[01:55<00:00, 1.36it/ all 5000 36335 0.743 0.627 0.685 0.503 Speed: 0.1ms pre-process, 7.5ms inference, 2.1ms NMS per image at shape (32, 3, 640, 640) # <--- baseline speed Evaluating pycocotools mAP... saving runs/val/exp3/yolov5x_predictions.json... ... Average Precision (AP) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.505 # <--- baseline mAP Average Precision (AP) @[ IoU=0.50 | area= all | maxDets=100 ] = 0.689 Average Precision (AP) @[ IoU=0.75 | area= all | maxDets=100 ] = 0.545 Average Precision (AP) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.339 Average Precision (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.557 Average Precision (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.650 Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 1 ] = 0.382 Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 10 ] = 0.628 Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.677 # <--- baseline mAR Average Recall (AR) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.523 Average Recall (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.730 Average Recall (AR) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.826
3.6 开始验证
forbatch_i,(im,targets,paths,shapes)inenumerate(pbar): """https://github.com/Oneflow-Inc/one-yolov5/blob/bf8c66e011fcf5b8885068074ffc6b56c113a20c/utils/dataloaders.py#L735 im:flow.from_numpy(img); targets:labels_out paths:self.im_files[index] shapes:shapes """
3.6.1 验证开始前的预处理
callbacks.run("on_val_batch_start") t1=time_sync() ifcuda: im=im.to(device) targets=targets.to(device) im=im.half()ifhalfelseim.float()#uint8tofp16/32 im/=255#0-255to0.0-1.0 nb,_,height,width=im.shape#batchsize,channels,height,width t2=time_sync() dt[0]+=t2-t1
3.6.2 推理
#Inference out,train_out=model(im)iftrainingelsemodel(im,augment=augment,val=True)#输出为:推理结果、损失值 dt[1]+=time_sync()-t2
3.6.3 计算损失
#Loss """ 分类损失(cls_loss):该损失用于判断模型是否能够准确地识别出图像中的对象,并将其分类到正确的类别中。 置信度损失(obj_loss):该损失用于衡量模型预测的框(即包含对象的矩形)与真实框之间的差异。 边界框损失(box_loss):该损失用于衡量模型预测的边界框与真实边界框之间的差异,这有助于确保模型能够准确地定位对象。 """ ifcompute_loss: loss+=compute_loss([x.float()forxintrain_out],targets)[1]#box,obj,cls
3.6.4 Run NMS
#NMS #将真实框target的xywh(因为target是在labelimg中做了归一化的)映射到真实的图像(test)尺寸 targets[:,2:]*=flow.tensor((width,height,width,height),device=device)#topixels #在NMS之前将数据集标签targets添加到模型预测中,这允许在数据集中自动标记(forautolabelling)其它对象(在pred中混入gt)并且mAP反映了新的混合标签 #targets:[num_target,img_index+class_index+xywh]=[31,6] #lb:{list:bs}第一张图片的target[17,5]第二张[1,5]第三张[7,5]第四张[6,5] lb=[targets[targets[:,0]==i,1:]foriinrange(nb)]ifsave_hybridelse[]#forautolabelling t3=time_sync() """non_max_suppression(非最大值抑制) Non-MaximumSuppression(NMS)oninferenceresultstorejectoverlappingboundingboxes 该算法的原理: 先假设有6个矩形框,根据分类器的类别分类概率大小排序,假设从小到大属于车辆(被检测的目标)的概率分别为:A、B、C、D、E、F (1)从最大概率矩形框F开始,分别判断A~E与F的重叠度IOU是否大于某个指定的阀值; (2)假设B、D与F的重叠度大于指定的阀值,则丢弃B、D,并标记第一个矩形框F,是我们要保留的 (3)从剩下的矩形框A、C、E中,选择最大概率,假设为E,然后判断A、C与E的重叠度是否大于指定的阀值, 假如大于就丢弃A、C,并标记E,是我们保留下来的第二个矩形框 一直重复上述过程,找到所有被保留的矩形框 Returns: listofdetections,on(n,6)tensorperimage[xyxy,conf,cls] """ out=non_max_suppression(out,conf_thres,iou_thres,labels=lb,multi_label=True,agnostic=single_cls) #获取NMS时间 dt[2]+=time_sync()-t3
3.6.5 统计每张图片的真实框、预测框信息
#为每张图片做统计,写入预测信息到txt文件,生成json文件字典,统计tp等 #out:list{bs}[300,6][42,6][300,6][300,6][:,image_index+class+xywh] forsi,predinenumerate(out): #获取第si张图片的gt标签信息包括class,x,y,w,htarget[:,0]为标签属于哪张图片的编号 labels=targets[targets[:,0]==si,1:]#[:,class+xywh] nl,npr=labels.shape[0],pred.shape[0]#numberoflabels,predictions path,shape=Path(paths[si]),shapes[si][0] correct=flow.zeros(npr,niou,dtype=flow.bool,device=device)#init seen+=1#统计测试图片数量+1 ifnpr==0:#如果预测为空,则添加空的信息到stats里 ifnl: stats.append((correct,*flow.zeros((2,0),device=device),labels[:,0])) ifplots: confusion_matrix.process_batch(detections=None,labels=labels[:,0]) continue #Predictions ifsingle_cls: pred[:,5]=0 predn=pred.clone() #将预测坐标映射到原图img中 scale_coords(im[si].shape[1:],predn[:,:4],shape,shapes[si][1])#native-spacepred #Evaluate ifnl: tbox=xywh2xyxy(labels[:,1:5])#targetboxes scale_coords(im[si].shape[1:],tbox,shape,shapes[si][1])#native-spacelabels labelsn=flow.cat((labels[:,0:1],tbox),1)#native-spacelabels correct=process_batch(predn,labelsn,iouv) ifplots: confusion_matrix.process_batch(predn,labelsn) stats.append((correct,pred[:,4],pred[:,5],labels[:,0]))#(correct,conf,pcls,tcls) #Save/log #保存预测信息到txt文件runsvalexp7labelsimage_name.txt ifsave_txt: save_one_txt( predn, save_conf, shape, file=save_dir/"labels"/f"{path.stem}.txt", ) ifsave_json: save_one_json(predn,jdict,path,class_map)#appendtoCOCO-JSONdictionary callbacks.run("on_val_image_end",pred,predn,path,names,im[si])
3.6.6 画出前三个batch图片的 gt 和 pred 框
gt : 真实框,Ground truth box, 是人工标注的位置,存放在标注文件中
pred : 预测框,Prediction box, 是由目标检测模型计算输出的框
#Plotimages ifplotsandbatch_i< 3: plot_images(im, targets, paths, save_dir / f"val_batch{batch_i}_labels.jpg", names) # labels plot_images( im, output_to_target(out), paths, save_dir / f"val_batch{batch_i}_pred.jpg", names, ) # pred callbacks.run("on_val_batch_end")
3.7 计算指标
指标名字在代码中体现
#Computemetrics stats=[flow.cat(x,0).cpu().numpy()forxinzip(*stats)]#tonumpy iflen(stats)andstats[0].any(): tp,fp,p,r,f1,ap,ap_class=ap_per_class(*stats,plot=plots,save_dir=save_dir,names=names) ap50,ap=ap[:,0],ap.mean(1)#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(int),minlength=nc)#numberoftargetsperclass
3.8 打印日志
#Printresultsperclass if(verboseor(nc< 50 and not training)) and nc >1andlen(stats): fori,cinenumerate(ap_class): LOGGER.info(pf%(names[c],seen,nt[c],p[i],r[i],ap50[i],ap[i])) #Printspeeds t=tuple(x/seen*1e3forxindt)#speedsperimage ifnottraining: shape=(batch_size,3,imgsz,imgsz) LOGGER.info(f"Speed:%.1fmspre-process,%.1fmsinference,%.1fmsNMSperimageatshape{shape}"%t)
3.9 保存验证结果
#Plots ifplots: confusion_matrix.plot(save_dir=save_dir,names=list(names.values())) callbacks.run("on_val_end") #SaveJSON ifsave_jsonandlen(jdict): w=Path(weights[0]ifisinstance(weights,list)elseweights).stemifweightsisnotNoneelse""#weights anno_json=str(Path(data.get("path","../coco"))/"annotations/instances_val2017.json")#annotationsjson pred_json=str(save_dir/f"{w}_predictions.json")#predictionsjson LOGGER.info(f" EvaluatingpycocotoolsmAP...saving{pred_json}...") withopen(pred_json,"w")asf: json.dump(jdict,f) #try-catch,会有哪些error """ pycocotools介绍: https://github.com/cocodataset/cocoapi/blob/master/PythonAPI/pycocoEvalDemo.ipynb 尝试: 使用pycocotools工具计算loss COCOAPI-http://cocodataset.org/ 失败error: 直接打印抛出的异常 1.可能没有安装pycocotools,但是网络有问题,无法实现自动下载。 2.pycocotools包版本有问题 """ try:#https://github.com/cocodataset/cocoapi/blob/master/PythonAPI/pycocoEvalDemo.ipynb check_requirements(["pycocotools"]) frompycocotools.cocoimportCOCO frompycocotools.cocoevalimportCOCOeval anno=COCO(anno_json)#initannotationsapi pred=anno.loadRes(pred_json)#initpredictionsapi eval=COCOeval(anno,pred,"bbox") ifis_coco: eval.params.imgIds=[int(Path(x).stem)forxindataloader.dataset.im_files]#imageIDstoevaluate eval.evaluate() eval.accumulate() eval.summarize() map,map50=eval.stats[:2]#updateresults(mAP@0.5:0.95,mAP@0.5) exceptExceptionase: LOGGER.info(f"pycocotoolsunabletorun:{e}")
3.10 返回结果
#Returnresults model.float()#fortraining ifnottraining: s=f" {len(list(save_dir.glob('labels/*.txt')))}labelssavedto{save_dir/'labels'}"ifsave_txtelse"" LOGGER.info(f"Resultssavedto{colorstr('bold',save_dir)}{s}") maps=np.zeros(nc)+map fori,cinenumerate(ap_class): maps[c]=ap[i] return(mp,mr,map50,map,*(loss.cpu()/len(dataloader)).tolist()),maps,t
审核编辑:刘清
-
NMS
+关注
关注
0文章
9浏览量
6021 -
python
+关注
关注
56文章
4782浏览量
84449 -
JSON
+关注
关注
0文章
117浏览量
6940 -
解释器
+关注
关注
0文章
103浏览量
6494
原文标题:《YOLOv5全面解析教程》十六,val.py 源码解读
文章出处:【微信号:GiantPandaCV,微信公众号:GiantPandaCV】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
相关推荐
评论