DETR(detection transformer)简介

DETR是Facebook AI的研究者提出的Transformer的视觉版本,是CNN和transformer的融合,实现了端到端的预测,主要用于目标检测和全景分割。
DETR的Github地址:https://github.com/facebookresearch/detr
DETR的论文地址:https://arxiv.org/pdf/2005.12872.pdf

DETR训练自己数据集

数据准备

DETR需要coco数据集才可以进行训练,需要将数据标签和图片保存为如下格式:
其中,annotations是如下json文件,

test、train和val2017存储的只有图片。
那么要如何得到coco数据集格式的文件呢,接下来我提供两种方法:

coco数据集获取

1、labelme打好json文件后转换为coco格式数据集
2、roboflow标注后直接生成coco格式数据集(需要连外网,需要的联系我可以免费给你提供好用的外网扩展程序)。roboflow网址:https://app.roboflow.com/
然后介绍如何用labelme转换数据集,首先在cmd python环境或者在pycharm终端输入pip install labelme,下载好后输入labelme进入打标签页面,打好标签后生成json文件,再运行如下脚本:

import argparseimport jsonimport matplotlib.pyplot as pltimport skimage.io as iofrom labelme import utilsimport numpy as npimport globimport PIL.Imageclass MyEncoder(json.JSONEncoder):def default(self, obj):if isinstance(obj, np.integer):return int(obj)elif isinstance(obj, np.floating):return float(obj)elif isinstance(obj, np.ndarray):return obj.tolist()else:return super(MyEncoder, self).default(obj)class labelme2coco(object):def __init__(self, labelme_json=[], save_json_path='./tran.json'):self.labelme_json = labelme_jsonself.save_json_path = save_json_pathself.images = []self.categories = []self.annotations = []# self.data_coco = {}self.label = []self.annID = 1self.height = 0self.width = 0self.save_json()def data_transfer(self):for num, json_file in enumerate(self.labelme_json):with open(json_file, 'r') as fp:data = json.load(fp)# 加载json文件self.images.append(self.image(data, num))for shapes in data['shapes']:label = shapes['label']if label not in self.label:self.categories.append(self.categorie(label))self.label.append(label)points = shapes['points']# 这里的point是用rectangle标注得到的,只有两个点,需要转成四个点points.append([points[0][0], points[1][1]])points.append([points[1][0], points[0][1]])self.annotations.append(self.annotation(points, label, num))self.annID += 1def image(self, data, num):image = {}img = utils.img_b64_to_arr(data['imageData'])# 解析原图片数据# img=io.imread(data['imagePath']) # 通过图片路径打开图片# img = cv2.imread(data['imagePath'], 0)height, width = img.shape[:2]img = Noneimage['height'] = heightimage['width'] = widthimage['id'] = num + 1image['file_name'] = data['imagePath'].split('/')[-1]self.height = heightself.width = widthreturn imagedef categorie(self, label):categorie = {}categorie['supercategory'] = 'Cancer'categorie['id'] = len(self.label) + 1# 0 默认为背景categorie['name'] = labelreturn categoriedef annotation(self, points, label, num):annotation = {}annotation['segmentation'] = [list(np.asarray(points).flatten())]annotation['iscrowd'] = 0annotation['image_id'] = num + 1# annotation['bbox'] = str(self.getbbox(points)) # 使用list保存json文件时报错(不知道为什么)# list(map(int,a[1:-1].split(','))) a=annotation['bbox'] 使用该方式转成listannotation['bbox'] = list(map(float, self.getbbox(points)))annotation['area'] = annotation['bbox'][2] * annotation['bbox'][3]# annotation['category_id'] = self.getcatid(label)annotation['category_id'] = self.getcatid(label)# 注意,源代码默认为1annotation['id'] = self.annIDreturn annotationdef getcatid(self, label):for categorie in self.categories:if label == categorie['name']:return categorie['id']return 1def getbbox(self, points):# img = np.zeros([self.height,self.width],np.uint8)# cv2.polylines(img, [np.asarray(points)], True, 1, lineType=cv2.LINE_AA)# 画边界线# cv2.fillPoly(img, [np.asarray(points)], 1)# 画多边形 内部像素值为1polygons = pointsmask = self.polygons_to_mask([self.height, self.width], polygons)return self.mask2box(mask)def mask2box(self, mask):'''从mask反算出其边框mask:[h,w]0、1组成的图片1对应对象,只需计算1对应的行列号(左上角行列号,右下角行列号,就可以算出其边框)'''# np.where(mask==1)index = np.argwhere(mask == 1)rows = index[:, 0]clos = index[:, 1]# 解析左上角行列号left_top_r = np.min(rows)# yleft_top_c = np.min(clos)# x# 解析右下角行列号right_bottom_r = np.max(rows)right_bottom_c = np.max(clos)# return [(left_top_r,left_top_c),(right_bottom_r,right_bottom_c)]# return [(left_top_c, left_top_r), (right_bottom_c, right_bottom_r)]# return [left_top_c, left_top_r, right_bottom_c, right_bottom_r]# [x1,y1,x2,y2]return [left_top_c, left_top_r, right_bottom_c - left_top_c,right_bottom_r - left_top_r]# [x1,y1,w,h] 对应COCO的bbox格式def polygons_to_mask(self, img_shape, polygons):mask = np.zeros(img_shape, dtype=np.uint8)mask = PIL.Image.fromarray(mask)xy = list(map(tuple, polygons))PIL.ImageDraw.Draw(mask).polygon(xy=xy, outline=1, fill=1)mask = np.array(mask, dtype=bool)return maskdef data2coco(self):data_coco = {}data_coco['images'] = self.imagesdata_coco['categories'] = self.categoriesdata_coco['annotations'] = self.annotationsreturn data_cocodef save_json(self):self.data_transfer()self.data_coco = self.data2coco()# 保存json文件json.dump(self.data_coco, open(self.save_json_path, 'w'), indent=4, cls=MyEncoder)# indent=4 更加美观显示labelme_json = glob.glob(r'./*.json')# labelme_json=['./1.json']labelme2coco(labelme_json, '.\\instances_val2017.json')

这个脚本是我之前在别人CSDN找的,比较好用。

预训练文件下载

有了数据集后,为了加快学习速度,可以去官网下载预训练模型,官网提供的有resnet_50和resnet_101两个预训练版本,下载后得到pth文件。下载如下:

修改detr-main文件的一些配置

因为detr是针对的是91(数字可能错了,不是记得了)个目标进行预测,所以我们在进行预测的时候,需要把目标预测数目改为自己的需要检测目标的数目。首先需要修改上一步下载好的pth文件,运行如下脚本:

import torchmodel1= torch.load('detr-r101-2c7b67e5.pth')num_class = 2 #我只需要检测一个物体,所以是2(检测个数+background)model1["model"]["class_embed.weight"].resize_(num_class+1, 256)model1["model"]["class_embed.bias"].resize_(num_class+1)torch.save(model1, "detr-r50_test_%d.pth"%num_class)

然后还需要修改detr.py文件夹下的num_classes,

训练模型

训练模型这块,可以直接执行命令行,或者在main.py里面修改好参数后运行,
官方提供的命令行如下:

python -m torch.distributed.launch --nproc_per_node=8 --use_env main.py --coco_path /path/to/coco 

结束语

我觉得在训练那块还是改main.py文件比较好,需要改的地方挺多,我觉得需要修改的主要有–epoch(轮次)、–num_workers(主要看你电脑性能怎么样,好点可以调高些)、–output_dir(输出的模型权重,pth文件)、–dataset_file(数据存放位置)、–coco_path(coco数据集的位置)和–resume(预训练权重文件位置)。
还一点就是官方只提供了训练脚本,但是没预测脚本,其实预测脚本也挺简单的,就是加载模型,加载权重参数,然后传入图片预处理什么的,代码挺多的,放上来内容太多了,我写了两个预测脚本,需要的可以联系我,或者不会运行的可以问我,其实也挺简单的,多玩几次就会了~~
最后展示下效果吧,预测的还是挺准的