0、项目开发需求
(1)开发app部署到安卓手机
(2)支持VOC数据集上所有的目标检测
1、开发环境搭建
windows10+pytorch+pyCharm+Anaconda
python 3.8
torch 1.9.0+cu111
torchvision 0.10.0+cu111
显卡:NVIDIA GeForce RTX 3070
2、数据集准备
(1)下载VOC数据集(链接)
(2)将VOC数据集的格式调整为yolov5的(链接)
yolov5是通过读取.yaml文件进行数据集加载的,.yaml文件内容如下所示
其中原VOC数据集与yolov5数据集文件夹结构对比如图所示:
在需要训练模型的.yaml里修改类别个数:
3、模型训练
(1)需要修改初始参数
"""
weights: 权重文件/需要提前下载,后面模型训练在此基础上进行,修改为自己的路径
cfg: 模型配置文件 包括nc、depth_multiple、width_multiple、anchors、backbone、head等,修改为自己的路径
data: 数据集配置文件 包括path、train、val、test、nc、names、download等,修改为自己的路径
hyp: 初始超参文件,修改为自己的路径
epochs: 训练轮次
batch-size: 训练批次大小(根据自己硬件选择建议(6-32))
img-size: 输入网络的图片分辨率大小
resume: 断点续训, 从上次打断的训练结果处接着训练 默认False
nosave: 不保存模型 默认False(保存) True: only test final epoch
notest: 是否只测试最后一轮 默认False True: 只测试最后一轮 False: 每轮训练完都测试mAP
workers: dataloader中的最大work数(线程个数),根据自己硬件选择
device: 训练的设备
single-cls: 数据集是否只有一个类别 默认False
rect: 训练集是否采用矩形训练 默认False
noautoanchor: 不自动调整anchor 默认False(自动调整anchor)
evolve: 是否进行超参进化 默认False
multi-scale: 是否使用多尺度训练 默认False
label-smoothing: 标签平滑增强 默认0.0不增强 要增强一般就设为0.1
adam: 是否使用adam优化器 默认False(使用SGD)
sync-bn: 是否使用跨卡同步bn操作,再DDP中使用 默认False
linear-lr: 是否使用linear lr 线性学习率 默认False 使用cosine lr
cache-image: 是否提前缓存图片到内存cache,以加速训练 默认False
image-weights: 是否使用图片采用策略(selection img to training by class weights) 默认False 不使用
bucket: 谷歌云盘bucket 一般用不到
project: 训练结果保存的根目录 默认是runs/train
name: 训练结果保存的目录 默认是exp 最终: runs/train/exp
exist-ok: 如果文件存在就ok不存在就新建或increment name 默认False(默认文件都是不存在的)
quad: dataloader取数据时, 是否使用collate_fn4代替collate_fn 默认False
save_period: Log model after every "save_period" epoch 默认-1 不需要log model 信息
artifact_alias: which version of dataset artifact to be stripped 默认lastest 貌似没用到这个参数?
local_rank: rank为进程编号 -1且gpu=1时不进行分布式 -1且多块gpu使用DataParallel模式math
entity: wandb entity 默认None
upload_dataset: 是否上传dataset到wandb tabel(将数据集作为交互式 dsviz表 在浏览器中查看、查询、筛选和分析数据集) 默认False
bbox_interval: 设置界框图像记录间隔 Set bounding-box image logging interval for W&B 默认-1 opt.epochs // 10
"""
parser = argparse.ArgumentParser()
# --------------------------------------------------- 常用参数 ---------------------------------------------
parser.add_argument('--weights', type=str, default='weights/yolov5s.pt', help='initial weights path')
parser.add_argument('--cfg', type=str, default='models/yolov5s.yaml', help='model.yaml path')
parser.add_argument('--data', type=str, default='data/VOC.yaml', help='dataset.yaml path')
parser.add_argument('--hyp', type=str, default='data/hyps/hyp.finetune.yaml', help='hyperparameters path')
parser.add_argument('--epochs', type=int, default=200)
parser.add_argument('--batch-size', type=int, default=8, 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('--resume', nargs='?', const=True, default=False, help='resume most recent training')
parser.add_argument('--nosave', action='store_true', help='True only save final checkpoint')
parser.add_argument('--notest', action='store_true', help='True only test final epoch')
parser.add_argument('--workers', type=int, default=1, help='maximum number of dataloader workers')
parser.add_argument('--device', default='cuda:0', help='cuda device, i.e. 0 or 0,1,2,3 or cpu')
parser.add_argument('--single-cls', action='store_true', help='train multi-class data as single-class')
# --------------------------------------------------- 数据增强参数 ---------------------------------------------
parser.add_argument('--rect', action='store_true', help='rectangular training')
parser.add_argument('--noautoanchor', action='store_true', help='disable autoanchor check')
parser.add_argument('--evolve', default=False, action='store_true', help='evolve hyperparameters')
parser.add_argument('--multi-scale', default=True, action='store_true', help='vary img-size +/- 50%%')
parser.add_argument('--label-smoothing', type=float, default=0.0, help='Label smoothing epsilon')
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('--linear-lr', default=False, action='store_true', help='linear LR')
parser.add_argument('--cache-images', action='store_true', help='cache images for faster training')
parser.add_argument('--image-weights', default=True, action='store_true', help='use weighted image selection for training')
# --------------------------------------------------- 其他参数 ---------------------------------------------
parser.add_argument('--bucket', type=str, default='', help='gsutil bucket')
parser.add_argument('--project', default='runs/train', help='save to project/name')
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('--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('--local_rank', type=int, default=-1, help='DDP parameter, wins do not modify')
# --------------------------------------------------- 三个W&B(wandb)参数 ---------------------------------------------
parser.add_argument('--entity', default=None, help='W&B entity')
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.parse_known_args()
# 作用就是当仅获取到基本设置时,如果运行命令中传入了之后才会获取到的其他配置,不会报错;而是将多出来的部分保存起来,留到后面使用
opt = parser.parse_known_args()[0] if known else parser.parse_args()
return opt
修改完以上参数就可以开始训练了。
(2)训练数据记录
1)训练参数
batch size:8
works:1
epoch:200
关于batch size和works有个疑问,第一次训练的时候batch size可以设置到12和works 为2 后面就不可以了,不知道为啥!!!!
2)训练耗时:接近23个小时。。。。。
3)CPU以及GPU占用率
4)result
"""
opt参数解析
weights: 模型的权重地址 默认 weights/best.pt
source: 测试数据文件(图片或视频)的保存路径 默认data/images
imgsz: 网络输入图片的大小 默认640
conf-thres: object置信度阈值 默认0.25
iou-thres: 做nms的iou阈值 默认0.45
max-det: 每张图片最大的目标个数 默认1000
device: 设置代码执行的设备 cuda device, i.e. 0 or 0,1,2,3 or cpu
view-img: 是否展示预测之后的图片或视频 默认False
save-txt: 是否将预测的框坐标以txt文件格式保存 默认True 会在runs/detect/expn/labels下生成每张图片预测的txt文件
save-conf: 是否保存预测每个目标的置信度到预测tx文件中 默认True
save-crop: 是否需要将预测到的目标从原图中扣出来 剪切好 并保存 会在runs/detect/expn下生成crops文件,将剪切的图片保存在里面 默认False
nosave: 是否不要保存预测后的图片 默认False 就是默认要保存预测后的图片
classes: 在nms中是否是只保留某些特定的类 默认是None 就是所有类只要满足条件都可以保留
agnostic-nms: 进行nms是否也除去不同类别之间的框 默认False
augment: 预测是否也要采用数据增强 TTA
update: 是否将optimizer从ckpt中删除 更新模型 默认False
project: 当前测试结果放在哪个主文件夹下 默认runs/detect
name: 当前测试结果放在run/detect下的文件名 默认是exp
exist-ok: 是否存在当前文件 默认False 一般是 no exist-ok 连用 所以一般都要重新创建文件夹
line-thickness: 画框的框框的线宽 默认是 3
hide-labels: 画出的框框是否需要隐藏label信息 默认False
hide-conf: 画出的框框是否需要隐藏conf信息 默认False
half: 是否使用半精度 Float16 推理 可以缩短推理时间 但是默认是False
"""
parser = argparse.ArgumentParser()
parser.add_argument('--weights', nargs='+', type=str, default='runs/train/exp19/weights/best.pt', help='model.pt path(s)')
parser.add_argument('--source', type=str, default='data/images/112.mp4', help='file/dir/URL/glob, 0 for webcam')
parser.add_argument('--imgsz', '--img', '--img-size', type=int, default=640, help='inference size (pixels)')
parser.add_argument('--conf-thres', type=float, default=0.25, help='confidence threshold')
parser.add_argument('--iou-thres', type=float, default=0.45, help='NMS IoU threshold')
parser.add_argument('--max-det', type=int, default=1000, help='maximum detections per image')
parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu')
parser.add_argument('--view-img', action='store_true', help='show results')
parser.add_argument('--save-txt', action='store_true', help='save results to *.txt')
parser.add_argument('--save-conf', action='store_true', help='save confidences in --save-txt labels')
parser.add_argument('--save-crop', action='store_true', help='save cropped prediction boxes')
parser.add_argument('--nosave', action='store_true', help='do not save images/videos')
parser.add_argument('--classes', nargs='+', type=int, help='filter by class: --class 0, or --class 0 2 3')
parser.add_argument('--agnostic-nms', action='store_true', help='class-agnostic NMS')
parser.add_argument('--augment', action='store_true', help='augmented inference')
parser.add_argument('--update', action='store_true', help='update all models')
parser.add_argument('--project', default='runs/detect', help='save results to project/name')
parser.add_argument('--name', default='exp', help='save results to project/name')
parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment')
parser.add_argument('--line-thickness', default=3, type=int, help='bounding box thickness (pixels)')
parser.add_argument('--hide-labels', default=False, action='store_true', help='hide labels')
parser.add_argument('--hide-conf', default=False, action='store_true', help='hide confidences')
parser.add_argument('--half', action='store_true', help='use FP16 half-precision inference')
parser.add_argument('--prune-model', default=False, action='store_true', help='model prune')
parser.add_argument('--fuse', default=False, action='store_true', help='fuse conv and bn')
opt = parser.parse_args()
输入一张图,输入一段视屏:
修改图片路径或者视屏路径即可。代码里面的 cv2.waitKey(1) 需要修改,图片为 cv2.waitKey(0) ,视屏为 cv2.waitKey(1)
# 是否需要显示我们预测后的结果 img0(此时已将pred结果可视化到了img0中)
if view_img:
cv2.imshow(str(p), im0)
cv2.waitKey(1) # 1 millisecond
4、模型转换
1).将.pt 转换为onnx
在模型pt转化为onnx的时候需要先进行修改models中的cocommon.py,修改Focus 去除slice数组操作,如果不改后续onnx2ncnn转换的时候会报错。
#原代码中训练用的
def forward(self, x):
return self.conv(torch.cat([x[..., ::2, ::2], x[..., 1::2, ::2], x[..., ::2, 1::2], x[..., 1::2, 1::2]], 1))
#模型转换用的代码
def forward(self, x):
#修改Focus去除slice数组操作,如果不改后续onnx2ncnn转换的时候会报错
return self.conv(torch.cat([x, x,x,x], 1))
修改export.py参数
img-size: 输入模型的图片size=(height, width) 默认=[640, 640] 可以减小一下尺寸免得手机卡爆了
batch-size: batch大小 默认=1
device: 模型运行设备 cuda device, i.e. 0 or 0,1,2,3 or cpu 默认=cpu
include: 要将pt文件转为什么格式 可以为单个原始也可以为list 默认=['torchscript', 'onnx', 'coreml']
half: 是否使用半精度FP16export转换 默认=False
inplace: 是否set YOLOv5 Detect() inplace=True 默认=False
train: 是否开启model.train() mode 默认=True coreml转换必须为True
optimize: TorchScript转化参数 是否进行移动端优化 默认=False
dynamic: ONNX转换参数 dynamic_axes ONNX转换是否要进行批处理变量 默认=False
simplify: ONNX转换参数 是否简化onnx模型 默认=False
opset-version: ONNX转换参数 设置版本 默认=10
"""
parser = argparse.ArgumentParser()
parser.add_argument('--weights', type=str, default='../runs/train/exp19/weights/best.pt', help='weights path')
parser.add_argument('--img-size', nargs='+', type=int, default=[416, 416], help='image (height, width)')
parser.add_argument('--batch-size', type=int, default=1, help='batch size')
parser.add_argument('--device', default='cpu', help='cuda device, i.e. 0 or 0,1,2,3 or cpu')
parser.add_argument('--include', nargs='+', default=['torchscript', 'onnx', 'coreml'], help='include formats')
parser.add_argument('--half', action='store_true', help='FP16 half-precision export')
parser.add_argument('--inplace', action='store_true', help='set YOLOv5 Detect() inplace=True')
parser.add_argument('--train', default="True", action='store_true', help='model.train() mode')
parser.add_argument('--optimize', action='store_true', help='TorchScript: optimize for mobile')
parser.add_argument('--dynamic', action='store_true', help='ONNX: dynamic axes')
parser.add_argument('--simplify', action='store_true', help='ONNX: simplify model')
parser.add_argument('--opset-version', type=int, default=10, help='ONNX: opset version')
opt = parser.parse_args()
return opt
模型简化可以用以下命令:
python -m onnxsim yolov5s.onnx yolov5ssim.onnx,自己把“yolov5s.onnx”修改成自己的名字就好了,如果没有安装onnxsim,先安装pip install onnx-simplifier再运行上面的简化指令python -m onnxsim yolov5s.onnx yolov5ssim.onnx
或者在export.py中设置参数,则转换为onnx 以后模型已经被简化了。
2).将onnx转换为ncnn
a.简单粗暴的:https://convertmodel.com/
直接选择输入输出模型就可以使用,简单粗暴,方便快捷。
b.使用cmake 进行编译,过程较复杂难道较大,切容易出错,后面进行补充。
c、在ncnn(https://github.com/Tencent/ncnn/releases)库下载对应的Windows和VS版本的文件,比如说我这里安装的VS2019,我下载的文件就是:https://github.com/Tencent/ncnn/releases/download/20220216/ncnn-20220216-windows-vs2019.zip
版本问题的话没有太多要求,下好后解压,在X64/bin文件下有对应的exe文件,把模型文件拷贝到当前目录,在文件夹里面按住shift点右键打开Powershell窗口,输入***./onnx2ncnn yolov5s.onnx yolov5s.param yolov5s.bin当前目录就会出现 yolov5s.param yolov5s.bin两个文件.
然后接着重复打开Powershell,./ncnnoptimize yolov5s.param yolov5s.bin yolov5s_out.param yolov5s_out.bin 65536*** 压缩文件大小。
4、修改转换后的模型.param文件
打开上一步生成的 .param文件,直接拉到最后,将图中的3个数字改为-1,这一步是为了防止Android移植后,检测结果正确显示用,如果不改的话,会出现n多个框,密密麻麻的覆盖你的原本图片。
修改前:
修改后:
log.csdnimg.cn/c23f82279b344211b023cbd8e313db30.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA6aqR552A5ouW5ouJ5py65Y675peF6KGM,size_18,color_FFFFFF,t_70,g_se,x_16)
自此模型准备完毕下一步在app开发里面加载。
5、Androidapp开发
官网提供了现成的模块:ncnn-android-yolov5 https://github.com/nihui/ncnn-android-yolov5
1)下载ncnn-20220216-android-vulkan(链接:https://github.com/Tencent/ncnn/releases)
2)修改代码
找到3个Permute查看对应的Output的name,修改完成后,继续修改class_names,换成自己的标签,最后连上设备就可以识别图像了。
6、运行检测