前言
2023.11.27记录
没想到大家还都挺关注这篇工程的使用。我是在2022年初测试该工程,当时我测的时候,要训练自己的数据集还是做了些改动的,如本篇博客记载,最后成功训练并推理。当时CPU、GPU都跑通,在cityscapes数据集上训练,验证集上得到mIOU为 0.77。
同时也测试了分割排名的绝大部分易部署的工程,综合原因评定,没有使用该方案。
后面就搞其它的了。中间评论区也有同学问问题,去年断断续续重新测过两次(改代码的时候一定要细心),都是可以跑通的。今年年中服务器清理,现在我这边工程环境已经被清除,所以太过于详细的内容我这也没办法给到解释。
论文解读路径:【论文阅读–实时语义分割】DDRNet:Deep Dual-resolution Networks for Real-time and Accurate Semantic Segmentation
先上本篇博客的结果展示:
- demo测试效果
- cityscapes 数据集训练结果
- 自己数据集训练结果
1 工程和数据集的下载
使用电脑环境 :Ubuntu18+anconde
环境配置:略
1.1 工程下载
作者提供的测试工程:https://github.com/ydhongHIT/DDRNet,(仅使用其预训练模型)
其他开发者复现的完整工程:https://github.com/chenjun2hao/DDRNet.pytorch(下载下来当时我直接重命名为 DDRNet_record)
下载第二个工程重命名了DDRNet_record,并下载第一个工程中的预训练模型,将其放置路径【DDRNet_record/pretrained_models】
1.2 下载数据集并解压
官网链接:https://www.cityscapes-dataset.com/,下载数据需要注册,且账号有一定的要求。登录后进行数据下载:
1.3 文件重布局
为了工程好管理,所以文件布局如下。(本人习惯工程和数据集分开放置,多个工程获取数据更规范)
└──DataSet_2D
├── cityscapes
├── 其他数据集
└── list (工程中的【./data/list】)
└──Code_2D
├── DDRNet_record
└── 其他工程
2 使用开源模型测试cityscapes数据
这里说下,作者把该工程完成了并开源出来很优秀了,美中不足的是使用起来或多或少有些地方略微不便,比如GPUS的设置,容易引起报错。
预测时需要改部分代码和设置(2022.04.01记录,没准后面作者就优化了)。
2.1 工程的修改
修改1:
【ddrnet_23_slim.yaml】中,设置DATASET的TEST_SET、 TEST的 MODEL_FILE。
注意这里的TEST_SET必须设置为 test.lst,该文件可以重命名为 test1.lst/test_***.lst,总之lst 的文件名中必须出现test,因为代码中会凭这个标志返回相应的数据。否则会报错:
修改2:
在【./tools/demo.py】中,修改如下内容。放弃使用提供的多gpu的使用,直接避免掉gpu的设置带来的报错。
修改3:
在【lib/core/function.py】中的
def test()
,在预测时会被调用。但运行时会报错。本人看了看这个函数,调用了一些其他函数,想着希望改动全局代码最小的情况下 工程能够正常预测,只需将该函数用下面的函数替换
(由于发布博客格式的问题,复制到自己代码中,4个空格可能变成3个… 自己修改下咯)def test(config, test_dataset, testloader, model, sv_dir='', sv_pred=True): ## 作者数据读取时,使用的opencv,所以我这里保存时也用opencv了 import cv2 ## 我这里设置自己的颜色,当然代码生成颜色都可以。 COLOR = [ (0, 0, 0), (174, 199, 232), (152, 223, 138), (31, 119, 180), (255, 187, 120), (188, 189, 34), (140, 86, 75), (255, 152, 150), (214, 39, 40), (197, 176, 213), (148, 103, 189), (196, 156, 148), (23, 190, 207), (178, 76, 76), (247, 182, 210), (66, 188, 102), (219, 219, 141), (140, 57, 197), (202, 185, 52), (51, 176, 203), (200, 54, 131),] ## 保存图片,用于结果对比时,了解哪个颜色代表第几类别。当然图片的名字保存为自己的类别名是最ok了。 ## 这里简单起见使用数字表示 color_path = "label_color/" os.makedirs(color_path) if not os.path.exists(color_path) else None for i in range(len(COLOR)): img = np.zeros((500,500,3),dtype=np.uint8) img =img+list(COLOR[i] ) # print(img) cv2.imwrite(os.path.join(color_path, str(i)+".png"), img) model.eval() with torch.no_grad(): for _, batch in enumerate(tqdm(testloader)): image, size, name = batch size = size[0] pred = test_dataset.multi_scale_inference( config, model, image, scales=config.TEST.SCALE_LIST, flip=config.TEST.FLIP_TEST) if pred.size()[-2] != size[0] or pred.size()[-1] != size[1]: pred = F.interpolate( pred, size[-2:], mode='bilinear', align_corners=config.MODEL.ALIGN_CORNERS ) if sv_pred: image = image.squeeze(0) image = image.numpy().transpose((1,2,0)) image *= test_dataset.std image += test_dataset.mean image *= 255.0 image = image.astype(np.uint8) _, pred = torch.max(pred, dim=1) pred = pred.squeeze(0).cpu().numpy() ## ================================================ ## 将神经网络的输出pred,转换成color图片显示,便于观察 img_lbl = np.zeros((pred.shape[0],pred.shape[1],3),dtype=np.uint8) pred_uni = np.unique(pred) for s in range(len(pred_uni)): mask = pred==s img_lbl[mask] = list(COLOR[s]) ## ================================================ ## 将掩码添加到原图上,便于观察。 # image = image[:,:,[2,1,0]] # image1 = Image.fromarray(image).convert('RGBA') # image2 = Image.fromarray(img_lbl).convert('RGBA') # img_merge = Image.blend(image1, image2, 0.5) # img_merge = np.array(img_merge)[:,:,:3] ## 将掩码添加到原图上,便于观察。 image = image[:,:,[2,1,0]] img_merge = image*0.5 + img_lbl*0.5 ## ================================================ ## 原图,标签图,叠加图 拼接保存。 image = np.concatenate([image, img_lbl, img_merge], axis=1) sv_path = os.path.join(sv_dir, 'test_results') if not os.path.exists(sv_path): os.mkdir(sv_path) cv2.imwrite(os.path.join(sv_path, name[0]+".png"), image)
2.2 进行推理:
修改结束后,打开终端激活环境,运行命令:
export CUDA_VISIBLE_DEVICES=2
# 设置为自己使用的gpu的id
python tools/demo.py --cfg experiments/cityscapes/ddrnet23_slim.yaml
然后在路径【./output/cityscapes/ddrnet23_slim/test_result/test_results】下得到了预测结果:
3 训练 cityscape dataset
3.1 文件的修改
【./experiments/cityscapes/ddrnet23_slim.py】中,修改
DATASET.ROOT
为自己具体的路径。注意,前面测试时,将 TEST_SET 设置成了 test.lst,训练时要修改回 val.txt。否侧会报错
3.2 工程的训练
需要注意的是,在使用 DDP式的分布式训练时,该文件内的
GPUS: ( )
并不能很好的设定网络具体使用的GPU的id,但使用使用GPU的个数,和这里括号内的个数要一致。具体使用哪几个训练,比如想用id为2,3 的显卡训练,命令如下。
export CUDA_VISIBLE_DEVICES=2,3
python -m torch.distributed.launch --nproc_per_node=2 tools/train.py --cfg experiments/cityscapes/ddrnet23_slim.yaml
训练过程如下图:
3.3 工程的结果
4 训练自己的数据集
4.1 准备好自己的数据集
语义分割的标注参考 2D图片语义分割标注
当数据标签都准备好了,仿照cityscapes数据集进行布局:
我这里将自己的数据集命名为【Seg_ChenZCH】。
在路径【DataSet_2D/Seg_ChenZCH】下存放这文件夹【data、label】,里面存放这神经网络所需的数据和标签。
在路径【DataSet_2D/list/Seg_ChenZCH】下存放着文件【train.lst、val.lst、test.lst】,里面存放着所需数据的相对路径。比如 train.lst,内容保存如下。注意相对路径是相对于【ddrnet23_slim.yaml】中的DATASET下的ROOT路径的。
我所有的博客都将不重要但又可能有点敏感的信息全部涂掉了,小心驶得好久船么
4.2 工程的修改
修改1
复制文件【./lib/datasets/cityscapes.py】并重命名为【Seg_ChenZCH.py】
需要修改一下内容:
- 处理数据类的名字:
class Seg_ChenZCH()
修改为class Seg_ChenZCH()
- mean、std、self.label_mapping、self.class_weights的内容根据自己实际情况修改
- 由于我这边保存的train.lst中的相对路径,这里也需要调整下图片的读取路径
需要修改内容 修改如下:
这里的self.label_mapping的设置,不小心容易设置错误。使用如下代码,更直观不易出错的设置,且要进行不同的类别训练,要修改label_mapping设置会很方便。
假设类别有11类,原始的标签如下,此时我们想进行转换,部分类别不想进行训练。{'bg': 0, 'sofa:': 1, 'bed': 2, 'desk': 3, 'chair': 4, 'cupboard': 5, 'cabinet': 6, 'refrigerator': 7, 'toilet': 8, 'washing_machine': 9, 'TV': 10, 'curtain': 11}
解决方法:需要创建一个字典,key是自己原本标签类别的name(按照原本的顺序),value是我们想要转换后的label的值:
class_dict_11 = {"bg":0, "sofa:":1, "bed":2, "desk":3, "chair":4, "cupboard":0, "cabinet":0, "refrigerator":5, "toilet":6, "washing_machine":7, "TV":8, "curtain":0, } class_list = list(class_dict_11.keys()) label_ori = {class_list[i]:i for i in range(len(class_list))} print(" =======这里得到的是 name:原始的idx") print(label_ori) print() label_mapping = {i:class_dict_11[class_list[i]] for i in range(len(class_list))} print(" =======这里得到的是 原始的idx:转换后的idx") print(label_mapping)
修改2
在文件【./lib/datasets/init.py】导入刚才编写的数据读取脚本
修改3
在文件【./lib/models/ddrnet_23_slim.py】中,作者对网络的分类设置了固定的19类。需要将这个
19
调整成cfg.DATASET.NUM_CLASSES
,自动的从yaml文件中获取类别信息
修改4
复制文件【./experiments/cityscapes/ddrnet23_slim_Seg_CHenZCH.yaml】并重命名为【ddrnet23_slim_Seg_CHenZCH.yaml】,其中必须修改的参数如下图。其他的参数根据自己训练情况进行调整。
4.3 工程的训练
修改完以上内容,运行命令:
export CUDA_VISIBLE_DEVICES=2,3
python -m torch.distributed.launch --nproc_per_node=2 tools/train.py --cfg experiments/cityscapes/ddrnet23_slim_Seg_CHenZCH.yaml
4.4自己数据集训练结果