最近开始学习目标检测faster rcnn,首先看了很多博客讲解原理,然后从github上下载tensorflow版本的代码,代码太长看了好几天没明白,后来看到了chenyuntc的 simple-faster-rcnn-pytorch,还有作者写这份代码的心得,让我感觉很佩服,自认为目前阶段不能手写如此复杂的代码。作者是从tf版本的改为pytorch版的,我在学习的过程中也查阅了很多其他人写的讲解代码的博客,得到了很大的帮助,所以也打算把自己一些粗浅的理解记录下来,一是记录下自己的菜鸟学习之路,方便自己过后查阅,二来可以回馈网络。目前编程能力有限,且是第一次写博客,中间可能会有一些错误。

目录

第一步 跑通代码
第二步 数据预处理
第三步 模型准备
第四步 训练代码

第一步 跑通代码

作者的README.MD部分讲解的很清楚了,一步一步安装PyTorch,cupy,然后运行pip install -r requirements.txt安装各种包。我用的是自己的台式机,1070GPU,python3.6,windows环境。安装PyTorch>=0.4时上官网根据自己的环境去找对应的pip指令安装时可能下载速度巨慢,我是直接下载了torch-0.4.1-cp36-cp36m-win_amd64.whl,运行pip install torch-0.4.1-cp36-cp36m-win_amd64.whl安装的,这里我将文件放到云盘上有需要自取https://pan.baidu.com/s/1pqSpeTFH3ooh7q1M3vrtfQ,提取码:hda0。cupy安装时出错了,提示我需要安装VS,注意最好安装vs2015以上版本。接下来是build cython code nms_gpu_post,这里我一直报错,后来发现是可选择运行的,我就直接跳过了,不过作者说最好运行这一步。然后是下载预训练模型,数据集,解压数据集操作。需要将util/config.py中voc_data_dir改为自己的路径,通过执行python misc/convert_caffe_pretrain.py,下载caffe_pretrain预训练模型,我是直接将caffe_pretrain改为True了,如果设置false则直接会下载pytorch版本的预训练模型。然后在终端下运行python -m visdom.server,弹出一个网址打开。visdom是类似tensorflow中的tensorboard的工具,可以可视化训练过程中的各种曲线或者图,然后运行python train. py train --env=‘fasterrcnn-caffe’ --plot-every=100 --caffe-pretrain,就开始了训练过程,可以看到网页中出现了5个loss曲线图,还有一个标签图和预测图。类似下面这样的

swin transformer 目标检测 代码 目标检测代码看不懂_深度学习

第二步 数据预处理

1.data/dataset.py文件(主代码中调用的其他重要函数会在后面按顺序讲解)

swin transformer 目标检测 代码 目标检测代码看不懂_数据集_02

#去正则化,img维度为[[B,G,R],H,W],因为caffe预训练模型输入为BGR 0-255图片,pytorch预训练模型采用RGB 0-1图片
def inverse_normalize(img):
    if opt.caffe_pretrain:   #如果采用caffe预训练模型,则返回 img[::-1, :, :],如果不采用,则返回(img * 0.225 + 0.45).clip(min=0, max=1) * 255 
        img = img + (np.array([122.7717, 115.9465, 102.9801]).reshape(3, 1, 1))   #[122.7717, 115.9465, 102.9801]reshape为[3,1,1]与img维度相同就可以相加了,caffe_normalize之前有减均值预处理,现在还原回去。
        return img[::-1, :, :]     #将BGR转换为RGB图片(python [::-1]为逆序输出)
    return (img * 0.225 + 0.45).clip(min=0, max=1) * 255    #pytorch_normalze中标准化为减均值除以标准差,现在乘以标准差加上均值还原回去,转换为0-255

#采用pytorch预训练模型对图片预处理,函数输入的img为0-1
def pytorch_normalze(img):
    normalize = tvtsf.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])  #transforms.Normalize使用如下公式进行归一化channel=(channel-mean)/std,转换为[-1,1]
    img = normalize(t.from_numpy(img))     #(ndarray) → Tensor
    return img.numpy()
    
 #采用caffe预训练模型时对输入图像进行标准化,函数输入的img为0-1
 def caffe_normalize(img):
    img = img[[2, 1, 0], :, :]  # RGB-BGR
    img = img * 255
    mean = np.array([122.7717, 115.9465, 102.9801]).reshape(3, 1, 1) #转换为与img维度相同
    img = (img - mean).astype(np.float32, copy=True)   #减均值操作
    return img
    
 #函数输入的img为0-255
def preprocess(img, min_size=600, max_size=1000): #按照论文长边不超1000,短边不超600。按此比例缩放
    C, H, W = img.shape
    scale1 = min_size / min(H, W)
    scale2 = max_size / max(H, W)
    scale = min(scale1, scale2)    #选小的比例,这样长和宽都能放缩到规定的尺寸
    img = img / 255  #转换为0-1
    img = sktsf.resize(img, (C, H * scale, W * scale), mode='reflect',anti_aliasing=False) #resize到(H * scale, W * scale)大小,anti_aliasing为是否采用高斯滤波
#调用pytorch_normalze或者caffe_normalze对图像进行正则化
    if opt.caffe_pretrain:
        normalize = caffe_normalize
    else:
        normalize = pytorch_normalze
    return normalize(img)
 
class Transform(object):
    def __init__(self, min_size=600, max_size=1000):
        self.min_size = min_size
        self.max_size = max_size
    def __call__(self, in_data):
        img, bbox, label = in_data
        _, H, W = img.shape
        img = preprocess(img, self.min_size, self.max_size)  #图像等比例缩放
        _, o_H, o_W = img.shape 
        scale = o_H / H   #得出缩放比因子
        bbox = util.resize_bbox(bbox, (H, W), (o_H, o_W))  #bbox按照与原图等比例缩放

        # horizontally flip
        img, params = util.random_flip(
            img, x_random=True, return_param=True) #将图片进行随机水平翻转,没有进行垂直翻转
        bbox = util.flip_bbox(
            bbox, (o_H, o_W), x_flip=params['x_flip']) #同样地将bbox进行与对应图片同样的水平翻转
        return img, bbox, label, scale 
        
class Dataset:      #训练集样本的生成
    def __init__(self, opt):
        self.opt = opt
        self.db = VOCBboxDataset(opt.voc_data_dir)   #实例化类
        self.tsf = Transform(opt.min_size, opt.max_size) ##实例化类
    def __getitem__(self, idx):   #__ xxx__运行Dataset类时自动运行
        ori_img, bbox, label, difficult = self.db.get_example(idx) #调用VOCBboxDataset中的get_example()从数据集存储路径中将img, bbox, label, difficult 一个个的获取出来
        img, bbox, label, scale = self.tsf((ori_img, bbox, label)) #调用前面的Transform函数将图片,label进行最小值最大值放缩归一化,重新调整bboxes的大小,然后随机反转,最后将数据集返回
        return img.copy(), bbox.copy(), label.copy(), scale
    def __len__(self):
        return len(self.db)  

class TestDataset:   #测试集样本的生成
    def __init__(self, opt, split='test', use_difficult=True):
        self.opt = opt
        self.db = VOCBboxDataset(opt.voc_data_dir, split=split, use_difficult=use_difficult)  #此处设置了use_difficult,

    def __getitem__(self, idx):
        ori_img, bbox, label, difficult = self.db.get_example(idx)
        img = preprocess(ori_img)
        return img, ori_img.shape[1:], bbox, label, difficult

    def __len__(self):
        return len(self.db)

下面是data/util.py文件

def resize_bbox(bbox, in_size, out_size):
    bbox = bbox.copy()
    y_scale = float(out_size[0]) / in_size[0]
    x_scale = float(out_size[1]) / in_size[1]  #获得与原图同样的缩放比
    bbox[:, 0] = y_scale * bbox[:, 0]
    bbox[:, 2] = y_scale * bbox[:, 2]
    bbox[:, 1] = x_scale * bbox[:, 1]
    bbox[:, 3] = x_scale * bbox[:, 3]  #按与原图同等比例缩放bbox
    return bbox
    
 def random_flip(img, y_random=False, x_random=False,
                return_param=False, copy=False):
    y_flip, x_flip = False, False
    if y_random:   #False
        y_flip = random.choice([True, False])
    if x_random:   #True
        x_flip = random.choice([True, False])  #随机选择图片是否进行水平翻转

    if y_flip:
        img = img[:, ::-1, :]
    if x_flip:
        img = img[:, :, ::-1]   #python [::-1]为逆序输出,这里指水平翻转
    if copy:
        img = img.copy()
    if return_param:   #True
        return img, {'y_flip': y_flip, 'x_flip': x_flip}  #返回img和x_flip(为了让bbox有同样的水平翻转操作)
    else:
        return img

 def flip_bbox(bbox, size, y_flip=False, x_flip=False):  
    H, W = size  #缩放后图片的size
    bbox = bbox.copy()
    if y_flip:              #没有进行垂直翻转
        y_max = H - bbox[:, 0]
        y_min = H - bbox[:, 2]
        bbox[:, 0] = y_min
        bbox[:, 2] = y_max
    if x_flip:
        x_max = W - bbox[:, 1]  
        x_min = W - bbox[:, 3] #计算水平翻转后左下角和右上角的坐标
        bbox[:, 1] = x_min
        bbox[:, 3] = x_max
    return bbox

下面是data/voc_dataset.py文件

class VOCBboxDataset:
    def __init__(self, data_dir, split='trainval',
                 use_difficult=False, return_difficult=False,
                 ):
        id_list_file = os.path.join(
            data_dir, 'ImageSets/Main/{0}.txt'.format(split))  # id_list_file为split.txt,split为'trainval'或者'test'
        self.ids = [id_.strip() for id_ in open(id_list_file)] #id_为每个样本文件名
        self.data_dir = data_dir #写到/VOC2007/的路径
        self.use_difficult = use_difficult 
        self.return_difficult = return_difficult 
        self.label_names = VOC_BBOX_LABEL_NAMES   #20类

    def __len__(self):
        return len(self.ids)  #trainval.txt有5011个,test.txt有210个
    def get_example(self, i):
        id_ = self.ids[i]
        anno = ET.parse(
            os.path.join(self.data_dir, 'Annotations', id_ + '.xml'))  #读入 xml标签文件
        bbox = list()
        label = list()
        difficult = list()
        #解析xml文件
        for obj in anno.findall('object'):
            if not self.use_difficult and int(obj.find('difficult').text) == 1:  #标为difficult的目标在测试评估中一般会被忽略
                continue   #xml文件中包含object name和difficult(0或者1,0代表容易检测)
            difficult.append(int(obj.find('difficult').text)) 
            bndbox_anno = obj.find('bndbox')  #bndbox(xmin,ymin,xmax,ymax),表示框左下角和右上角坐标
            bbox.append([
                int(bndbox_anno.find(tag).text) - 1  
                for tag in ('ymin', 'xmin', 'ymax', 'xmax')]) #让坐标基于(0,0)
            name = obj.find('name').text.lower().strip()  #框中object name
            label.append(VOC_BBOX_LABEL_NAMES.index(name))  
        bbox = np.stack(bbox).astype(np.float32)   #所有object的bbox坐标存在列表里
        label = np.stack(label).astype(np.int32)     #所有object的label存在列表里
        # When `use_difficult==False`, all elements in `difficult` are False.
        difficult = np.array(difficult, dtype=np.bool).astype(np.uint8)  # PyTorch 不支持 np.bool,所以这里转换为uint8
        img_file = os.path.join(self.data_dir, 'JPEGImages', id_ + '.jpg') #根据图片编号在/JPEGImages/取图片
        img = read_image(img_file, color=True)    #如果color=True,则转换为RGB图
        return img, bbox, label, difficult
    __getitem__ = get_example  #一般如果想使用索引访问元素时,就可以在类中定义这个方法(__getitem__(self, key) )

以上部分就是全部的数据预处理内容,这部分不太难,还是挺好理解的。下面一部分是模型准备部分。