1 准备工作
下载工程
- 工程下载:https://github.com/CoinCheung/BiSeNet
- 预训练模型下载:
- 工程下载后解压,并在其中创建文件夹【MODEL】用于存放预训练模型
本人的开发环境:
ubuntu 18.04、cuda10.2、cudnn7、python3.7、pytorch 1.8.1
工程运行过程中,会报错找不到库,pip安装对应的库即可
2 运行demo
- 使用 【bisenetv2_city】测试图片:
python tools/demo.py --config configs/bisenetv2_city.py --weight-path ./MODEL/model_final_v2_city.pth --img-path ./example.png
会保存结果为【res.jpg】
- 使用【bisenetv2_coco】测试视频:
python tools/demo_video.py --config configs/bisenetv2_coco.py --weight-path ./MODEL/model_final_v2_coco.pth --input ./video.mp4 --output res.mp4
会保存结果为【res.mp4】,展示的结果是从视频中截取的效果,所以彩色图和预测图片效果不对应,间隔了少量的帧数
3 训练cityscapes数据集
3.1 下载数据集并解压
官网链接:https://www.cityscapes-dataset.com/,下载数据需要注册,且账号有一定的要求。登录后进行数据下载:
然后剪切置合适的路径,一般来说,建议将【数据集文件】【工程文件】放置同级路径,不要将 数据集文件 从属于 工程文件。方便多个工程都能很好的使用数据集。
然后进行解压,(命令运行解压速度很快)运行unzip leftImg8bit_trainvaltest.zip unzip gtFine_trainvaltest.zip
遵守该工程调用数据路径,我们需要在工程路径下的【./datasets/cityscapes】下创建个软连接。进入该路径运行
ln -s ../../../cityscapes/leftImg8bit leftImg8bit ln -s ../../../cityscapes/gtFine gtFine
3.2 训练BiSeNetv2-cityscapes
源码提供的pytorch的分布式训练,而我们常有的是单机单卡、或单机多卡。
export CUDA_VISIBLE_DEVICES=0,1 python -m torch.distributed.launch --nproc_per_node=2 tools/train_amp.py --config configs/bisenetv2_city.py
export CUDA_VISIBLE_DEVICES=0 python -m torch.distributed.launch --nproc_per_node=1 tools/train_amp.py --config configs/bisenetv2_city.py
上面的命令和官网提供的命令是一样的效果
4 训练自己的数据集
4.1 数据标注
使用 【labelme】进行标注,语义标注的使用记录在 labelme标注软件的使用 || 语义分割数据标注、批量转换、多类别转换颜色错位问题
4.2 数据路径布局
标注完数据,编写脚本处理数据路径。结果如下图所示
三个路径下的图片为:
这里需要注意:
中间的图片其实是单通道的,使用 【label = np.array(Image.open(file))
】能够正确读取像素值。
使用【label = cv2.imread(file, 0)
】读取的像素值,并不是我们标注的标签。
但工程中使用的是opencv,为了尽可能少的修改源码,这里需要将【label_pil】里的数据使用Image.open
进行读取,然后使用cv2.imwrite
进行保存。
【label】文件夹下存放的图片效果如下图所示
4.3 生成train.txt、val.txt
【./tools/gen_coco_annos.py】的功能,就是生成 COCO 数据集的train.txt等,我们需要针对我们的数据集来生成 自己数据集的 train.txt
创建文件【./tools/gen_SW_annos.py】,内容如下:import os import random def gen_SW(): data_path = '../Sweeper/data_v1/data' # 需要修改为自己的路径 label_path = '../Sweeper/data_v1/label' # 需要修改为自己的路径 save_path = "datasets/sweeper/" # os.makedirs(save_path) if not os.path.exists(save_path) else None ftrain = open(os.path.join(save_path,"train.txt"),"w") fval = open(os.path.join(save_path,"val.txt"),"w") ftest = open(os.path.join(save_path,"test.txt"),"w") files = os.listdir(data_path) random.shuffle(files) count = -1 for file in files: count += 1 im_root = os.path.join(data_path, file) lb_root = os.path.join(label_path, file) ftrain.writelines(im_root+","+lb_root+"\n") if count<8000 else None # 修改自己的数据量的划分 fval.writelines(im_root+","+lb_root+"\n") if 8000<count<8600 else None # 修改自己的数据量的划分 ftest.writelines(im_root+","+lb_root+"\n") if 8600<count else None # 修改自己的数据量的划分 ftrain.close() fval.close() ftest.close() gen_SW()
4.4 源码修改
【./configs/bisenetv2_city.py】
## bisenetv2 # cfg = dict( # model_type='bisenetv2', # n_cats=19, # num_aux_heads=4, # lr_start=5e-3, # weight_decay=5e-4, # warmup_iters=1000, # max_iter=150000, # dataset='CityScapes', # im_root='./datasets/cityscapes', # train_im_anns='./datasets/cityscapes/train.txt', # val_im_anns='./datasets/cityscapes/val.txt', # scales=[0.25, 2.], # cropsize=[512, 1024], # eval_crop=[1024, 1024], # eval_scales=[0.5, 0.75, 1.0, 1.25, 1.5, 1.75], # ims_per_gpu=8, # eval_ims_per_gpu=2, # use_fp16=True, # use_sync_bn=True, # respth='./res', # ) cfg = dict( model_type='bisenetv2', n_cats=19, # 修改 num_aux_heads=4, lr_start=5e-3, weight_decay=5e-4, warmup_iters=1000, max_iter=150000, dataset='CityScapes', im_root='./', train_im_anns='./datasets/sweeper/train.txt', # 修改 val_im_anns='./datasets/sweeper/val.txt', # 修改 scales=[0.25, 2.], cropsize=[384, 640], # 修改 eval_crop=[384, 640], # 修改 eval_scales=[0.5, 0.75, 1.0, 1.25, 1.5, 1.75], ims_per_gpu=8, eval_ims_per_gpu=2, use_fp16=True, use_sync_bn=True, respth='./res', )
【./lib/cityscapes_cv2.py】
1 修改自己数据集合的类别数量,无需加1。
2 注释代码上图第二个红框。
一般的我们的标签都是从0开始按顺序标注,不需要标签序号的重映射,注释掉即可。如果需要重新映射标签,仿照变量labels_info
改写即可。
3 统计自己数据集的均值与方差,然后用结果替换代码中的 mean、stdimport cv2 import numpy as np file_name = "./datasets/sweeper/train.txt" with open(file_name,"r") as f: files = f.readlines() files = [file[:-1].split(",") for file in files] # print(files) MEAN = [] STD = [] for file in files: img = cv2.imread(file[0])[:,:,::-1]/255.0 MEAN.append(np.mean(img, axis=(0,1))) STD.append(np.std(img, axis=(0,1))) MEAN = np.array(MEAN) STD = np.array(STD) print(MEAN.shape) print(STD.shape) MEAN = np.mean(MEAN,axis=0) STD = np.mean(STD,axis=0) print(MEAN) print(STD)
4.5 训练与评估
因为上面有偷懒,直接在 cityscapes 的相关脚本上进行修改,所以训练cityscapes 运行命令与前面一致
- 单机多卡
export CUDA_VISIBLE_DEVICES=0,1
python -m torch.distributed.launch --nproc_per_node=2 tools/train_amp.py --config configs/bisenetv2_city.py
- 单机单卡
export CUDA_VISIBLE_DEVICES=0
python -m torch.distributed.launch --nproc_per_node=1 tools/train_amp.py --config configs/bisenetv2_city.py
- 模型评估
python tools/evaluate.py --config configs/bisenetv2_city.py --weight-path ./res/model_tmp.pth