faster-rcnn.pytorch-1.0的jwyang当前最火版本代码复现与讲解

一、faster-rcnn.pytorch-1.0

1、下载代码与权重文件

选择的是当前最火的框架,直接从jwyang大神的github上下载即可,注意要选择pytorch1.0版本进行下载。
数据集准备:Pascal_VOC数据集
下载训练、验证测试数据集training, validation, test data 和 VOCdevkit

wget http://host.robots.ox.ac.uk/pascal/VOC/voc2007/VOCtrainval_06-Nov-2007.tar
wget http://host.robots.ox.ac.uk/pascal/VOC/voc2007/VOCtest_06-Nov-2007.tar
wget http://host.robots.ox.ac.uk/pascal/VOC/voc2007/VOCdevkit_08-Jun-2007.tar

将三个文件解压到同一个文件夹内VOCdevkit

tar xvf VOCtrainval_06-Nov-2007.tar
tar xvf VOCtest_06-Nov-2007.tar
tar xvf VOCdevkit_08-Jun-2007.tar

VOC数据集的文件夹tree结构:其中Annotations内放xml标注文件,JPEGImages内放图片,ImageSets/Main/内的四个txt文件分别是测试集、训练集、训练验证集、验证集。
最终在VOCdevkit目录下有VOC2007/Annotations等文件夹,数据集格式如图所示

VOCdevkit
└── VOC2007
    ├── Annotations
    ├── ImageSets
    │   └── Main
    │       ├── test.txt
    │       ├── train.txt
    │       ├── trainval.txt
    │       └── val.txt
    └── JPEGImages

特征提取训练权重准备:作者给出了两种特征提取网络的Pretrained Models,下载完成后把他们放到data/pretrained_model目录下(见作者github)

cp /data/faster-rcnn.pytorch.zip ~/
unzip faster-rcnn.pytorch.zip

将下载的压缩包拷贝到云服务器上后进行解压。

2、先安装所有环境

要进入到faster-rcnn.pytorch目录下,后执行命令

pip install -r requirements.txt

3、相关库函数版本以及安装

sudo pip uninstall torch
sudo pip install torch==1.0.0

sudo pip uninstall torchvision
sudo pip install torchvision==0.2.1

sudo pip uninstall scipy
sudo pip install scipy==1.0.0

可以跑通的版本号

cython==0.29.21
cffi==1.11.5
opencv-python==4.3.0.36
scipy==1.0.0
msgpack==1.0.0
easydict==1.9
matplotlib==3.3.0
pyyaml==5.3.1
tensorboardX==2.1

4、创建软连接

cd faster-rcnn.pytorch/data 
# 进入到我们创建的data文件夹
ln -s /home/mist/faster-rcnn.pytorch/data/VOCdevkit VOCdevkit2007
# ln -s VOCdevkit的绝对路径 VOCdevkit2007
# ln是链接(link)命令,ln -s 表示创建软连接。具体用法是:ln -s 源文件 目标文件。

5、编译CUDA依赖环境

cd lib
sudo python setup.py build develop

6、改bug

运行出现一个错误

ImportError: cannot import name '_mask'

解决——安装CoCO API

cd data
git clone https://github.com/pdollar/coco.git 
cd coco/PythonAPI
make
cd ../../..

7、训练

CUDA_VISIBLE_DEVICES=$GPU_ID python trainval_net.py \
                   --dataset pascal_voc --net vgg16 \
                   --bs $BATCH_SIZE --nw $WORKER_NUMBER \
                   --lr $LEARNING_RATE --lr_decay_step $DECAY_STEP \
                   --cuda

可用的示例

CUDA_VISIBLE_DEVICES=0 python trainval_net.py \
                   --dataset pascal_voc --net vgg16 \
                   --epochs 1 --bs 1 --nw 4 \
                   --lr  1e-3 --lr_decay_step 5 \
                   --cuda

参数选择图
在这里插入图片描述
从断点开始继续训练方法:
例如我训练好的模型名为faster_rcnn_1_9_9547.pth,它就对应了 checksession 为1 ,checkepoch为 9,checkpoint为 9547
例如我要继续训练这个模型到20次epoch,就可以写

CUDA_VISIBLE_DEVICES=0 python trainval_net.py --dataset pascal_voc --net res101 --bs 1 --nw 4  --cuda --r true --checksession 1 --checkepoch 8 --checkpoint 9547 --epochs 20

训练过程:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

8、测试

python test_net.py --dataset pascal_voc --net vgg16 \
                   --checksession $SESSION --checkepoch $EPOCH --checkpoint $CHECKPOINT \
                   --cuda
python test_net.py --dataset pascal_voc --net vgg16 \
                   --checksession 1 --checkepoch 1 --checkpoint 10021 \
                   --cuda

测试过程:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

9、demo

  • 在跑demo文件过程中,也需要把类别修改成适应你的新数据集的形式。
//个例实验
python demo.py --net vgg16  --checksession 1  --checkepoch 10 --checkpoint 359 --cuda --load_dir models

二、生成数据集

1、修改xml文件参数

import os
import os.path
import xml.dom.minidom


# path="../xml/"
path_xml = '/home/Annotations/'
path_img='/home/JPEGImage/'
files = os.listdir(path_xml)  # 得到文件夹下所有文件名称
modify_folder = "Annotations"
modify_database = "labproject"

for xmlFile in files:  # 遍历文件夹
    if not os.path.isdir(xmlFile):  # 判断是否是文件夹,不是文件夹才打开
        print(xmlFile)
        xmlFile

    filepath = os.path.join(path_xml, xmlFile)
    # 最核心的部分,路径拼接,输入的是具体路径
    dom = xml.dom.minidom.parse(os.path.join(path_xml, xmlFile))
    root = dom.documentElement

    # 修改path
    oldpath = root.getElementsByTagName('path_xm')
    # print(oldpath[0].firstChild.data)

    newpath = path_img + xmlFile[0:6] + ".jpg"
    oldpath[0].firstChild.data = newpath

    # 修改folder
    oldfolder = root.getElementsByTagName('folder')
    oldfolder[0].firstChild.data = modify_folder

    #修改database
    olddatabase = root.getElementsByTagName('database')
    olddatabase[0].firstChild.data = modify_database

    #修改 filename
    oldfilename = root.getElementsByTagName('filename')
    oldfilename[0].firstChild.data = xmlFile[0:6] + ".jpg"

    with open(os.path.join(path, xmlFile), 'w') as fh:
        dom.writexml(fh)
    pass

2、划分数据集

接下来为制作ImageSets文件夹下Main和Layout文件夹中的4个文件(test.txt、train.txt、trainval.txt、val.txt)
  首先说明一下这四个文件到底是干什么用的:
    test.txt:测试集
    train.txt:训练集
    val.txt:验证集
    trainval.txt:训练和验证集
  需要保证的是train和val两者没有交集,也就是训练数据和验证数据不能有重复,在选取训练数据的时候,也应该是随机产生的。
  在原始VOC2007数据集中,trainval大约占整个数据集的50%,test大约为整个数据集的50%;train大约是trainval的50%,val大约为trainval的50%。所以我们可参考以下代码来生成这4个txt文件,运行完之后会在Main文件夹下生成对应的4个文件(test.txt、train.txt、trainval.txt、val.txt),随后将这4个文件复制到Layout文件夹中即可。

import os
import random
 
trainval_percent = 0.5
train_percent = 0.5
xmlfilepath = '/home/VOC2007/Annotations'
txtsavepath = '/home/VOC2007/ImageSets/Main'
total_xml = os.listdir(xmlfilepath)
 
num=len(total_xml)
list=range(num)
tv=int(num*trainval_percent)
tr=int(tv*train_percent)
trainval= random.sample(list,tv)
train=random.sample(trainval,tr)
 
ftrainval = open(txtsavepath+'/trainval.txt', 'w')
ftest = open(txtsavepath+'/test.txt', 'w')
ftrain = open(txtsavepath+'/train.txt', 'w')
fval = open(txtsavepath+'/val.txt', 'w')
 
for i  in list:
    name=total_xml[i][:-4]+'\n'
    if i in trainval:
        ftrainval.write(name)
        if i in train:
            ftrain.write(name)
        else:
            fval.write(name)
    else:
        ftest.write(name)
 
ftrainval.close()
ftrain.close()
fval.close()
ftest .close()

3、PNG 转 JPG

from PIL import Image
import cv2 as cv
import os

def PNG_JPG(PngPath):
    img = cv.imread(PngPath, 0)
    w, h = img.shape[::-1]
    infile = PngPath
    outfile = os.path.splitext(infile)[0] + ".jpg"
    img = Image.open(infile)
    # img = img.resize((int(w / 2), int(h / 2)), Image.ANTIALIAS)
    try:
        if len(img.split()) == 4:
            # prevent IOError: cannot write mode RGBA as BMP
            r, g, b, a = img.split()
            img = Image.merge("RGB", (r, g, b))
            img.convert('RGB').save(outfile, quality=70)
            os.remove(PngPath)
        else:
            img.convert('RGB').save(outfile, quality=70)
            os.remove(PngPath)
        return outfile
    except Exception as e:
        print("PNG转换JPG 错误", e)


################转换多张图片#####################

path_root = os.getcwd()
Path='/data1/training/image_2/' #图片的绝对路径
img_dir = os.listdir(Path)
for img in img_dir:
    if img.endswith('.png'):
        PngPath= Path + img
        PNG_JPG(PngPath)
img_dir = os.listdir(Path)
for img in img_dir:
    print(img)

4、类别调整

数据集中的类别有以下几种:’Car’, ’Van’, ’Truck’, ’Pedestrian’, ’Person (sit- ting)’, ’Cyclist’, ’Tram’ 和’Misc’(e.g., Trailers, Segways),除此之外,标签中还有一种‘DontCare’(毫无用处)。由于每个人的任务不同,就要做出调整,至少也得把‘DontCare’去掉。
  而且为了适配faster rcnn也需要将类别开头的大写字母换成小写。
  PS:代码中并没写写文件夹的生成,所以需要提前新建好相关的文件夹。
  代码中设计了一个flag,目的是这样的:经常我们不需要所有的类别,但是删除其他类别的过程中会导致有些txt文件里的内容全删了,那么这个txt就需要删除,flag就是用来判断这个txt文件是否需要删除。默认flag是0,表示要删,像我这个只保留‘car’的类别,就在处理‘car’的时候转置了flag。

#!/usr/bin/env python  
# -*- coding: UTF-8 -*-  

# modify_annotations_txt.py  
import glob  
import string  

txt_list = glob.glob('./label_2/*.txt') # 存储Labels文件夹所有txt文件路径  
def show_category(txt_list):  
    category_list= []  
    for item in txt_list:  
        try:  
            with open(item) as tdf:  
                for each_line in tdf:  
                    labeldata = each_line.strip().split(' ') # 去掉前后多余的字符并把其分开  
                    category_list.append(labeldata[0]) # 只要第一个字段,即类别  
        except IOError as ioerr:  
            print('File error:'+str(ioerr))  
    print(set(category_list)) # 输出集合  

def merge(line):  
    each_line=''  
    for i in range(len(line)):  
        if i!= (len(line)-1):  
            each_line=each_line+line[i]+' '  
        else:  
            each_line=each_line+line[i] # 最后一条字段后面不加空格  
    each_line=each_line+'\n'  
    return (each_line)  

print('before modify categories are:\n')  
show_category(txt_list)  

for item in txt_list:  
    new_txt=[]  
    try:  
        with open(item, 'r') as r_tdf:  
            for each_line in r_tdf:  
                labeldata = each_line.strip().split(' ')

                '''if labeldata[0] in ['Truck','Van','Tram','Car']: # 合并汽车类  
                    labeldata[0] = labeldata[0].replace(labeldata[0],'car')  
                if labeldata[0] in ['Person_sitting','Cyclist','Pedestrian']: # 合并行人类  
                    labeldata[0] = labeldata[0].replace(labeldata[0],'pedestrian')'''
                #print type(labeldata[4])
                if labeldata[4] == '0.00':
                    labeldata[4] = labeldata[4].replace(labeldata[4],'1.00')
                if labeldata[5] == '0.00':
                    labeldata[5] = labeldata[5].replace(labeldata[5],'1.00')
                if labeldata[0] == 'Truck':  
                    labeldata[0] = labeldata[0].replace(labeldata[0],'truck')
                if labeldata[0] == 'Van':  
                    labeldata[0] = labeldata[0].replace(labeldata[0],'van') 
                if labeldata[0] == 'Tram':  
                    labeldata[0] = labeldata[0].replace(labeldata[0],'tram')
                if labeldata[0] == 'Car':  
                    labeldata[0] = labeldata[0].replace(labeldata[0],'car')
                #if labeldata[0] == 'Cyclist':  
                    #labeldata[0] = labeldata[0].replace(labeldata[0],'cyclist')
                if labeldata[0] in ['Person_sitting','Pedestrian']: # 合并行人类  
                    labeldata[0] = labeldata[0].replace(labeldata[0],'pedestrian')
                if labeldata[0] == 'Cyclist':  
                    continue
                if labeldata[0] == 'DontCare': # 忽略Dontcare类  
                    continue  
                if labeldata[0] == 'Misc': # 忽略Misc类  
                    continue  
                new_txt.append(merge(labeldata)) # 重新写入新的txt文件  
        with open(item,'w+') as w_tdf: # w+是打开原文件将内容删除,另写新内容进去  
            for temp in new_txt:  
                w_tdf.write(temp)  
    except IOError as ioerr:  
        print('File error:'+str(ioerr))  

print('\nafter modify categories are:\n')
newtxt_list = glob.glob('./label_2/*.txt')
show_category(newtxt_list)

5、TXT 转 XML

首先也是需要新建好代码中提到的文件夹,然后就是要修改类别参数(和上一步是对应的,有几类就写几类),最后就是填好图像文件所在的路径。

#!/usr/bin/env python  
# -*- coding: UTF-8 -*-  
# txt_to_xml.py  
# 根据一个给定的XML Schema,使用DOM树的形式从空白文件生成一个XML  
from xml.dom.minidom import Document  
import cv2  
import os  

def generate_xml(name,split_lines,img_size,class_ind):  
    doc = Document()  # 创建DOM文档对象  

    annotation = doc.createElement('annotation')  
    doc.appendChild(annotation)  

    title = doc.createElement('folder')  
    title_text = doc.createTextNode('VOC2007')#这里修改了文件夹名  
    title.appendChild(title_text)  
    annotation.appendChild(title)  

    img_name=name+'.jpg'#要用jpg格式    
    

    title = doc.createElement('filename')  
    title_text = doc.createTextNode(img_name)  
    title.appendChild(title_text)  
    annotation.appendChild(title)  

    source = doc.createElement('source')  
    annotation.appendChild(source)  

    title = doc.createElement('database')  
    title_text = doc.createTextNode('The VOC2007 Database')#修改为VOC  
    title.appendChild(title_text)  
    source.appendChild(title)  

    title = doc.createElement('annotation')  
    title_text = doc.createTextNode('PASCAL VOC2007')#修改为VOC  
    title.appendChild(title_text)  
    source.appendChild(title)  

    size = doc.createElement('size')  
    annotation.appendChild(size)  

    title = doc.createElement('width')  
    title_text = doc.createTextNode(str(img_size[1]))  
    title.appendChild(title_text)  
    size.appendChild(title)  

    title = doc.createElement('height')  
    title_text = doc.createTextNode(str(img_size[0]))  
    title.appendChild(title_text)  
    size.appendChild(title)  

    title = doc.createElement('depth')  
    title_text = doc.createTextNode(str(img_size[2]))  
    title.appendChild(title_text)  
    size.appendChild(title)  

    for split_line in split_lines:  
        line=split_line.strip().split()  
        if line[0] in class_ind:  
            object = doc.createElement('object')  
            annotation.appendChild(object)  

            title = doc.createElement('name')  
            title_text = doc.createTextNode(line[0])  
            title.appendChild(title_text)  
            object.appendChild(title)  

            title = doc.createElement('difficult')  
            title_text = doc.createTextNode('0')  
            title.appendChild(title_text)  
            object.appendChild(title)  

            bndbox = doc.createElement('bndbox')  
            object.appendChild(bndbox)  
            title = doc.createElement('xmin')  
            title_text = doc.createTextNode(str(int(float(line[4]))))  
            title.appendChild(title_text)  
            bndbox.appendChild(title)  
            title = doc.createElement('ymin')  
            title_text = doc.createTextNode(str(int(float(line[5]))))  
            title.appendChild(title_text)  
            bndbox.appendChild(title)  
            title = doc.createElement('xmax')  
            title_text = doc.createTextNode(str(int(float(line[6]))))  
            title.appendChild(title_text)  
            bndbox.appendChild(title)  
            title = doc.createElement('ymax')  
            title_text = doc.createTextNode(str(int(float(line[7]))))  
            title.appendChild(title_text)  
            bndbox.appendChild(title)  

    # 将DOM对象doc写入文件  
    f = open('/data1/faster-rcnn.pytorch/data/VOCdevkit/VOC2007/Annotations/'+name+'.xml','w')   # Annotations/
    #f = open('Annotations/'+name+'.xml','w')
    f.write(doc.toprettyxml(indent = ''))  
    f.close()  

if __name__ == '__main__':  
    class_ind=('van', 'tram', 'car', 'pedestrian', 'truck')#修改为了5类  
    cur_dir=os.getcwd()  
    print(cur_dir) #wjg
    labels_dir=os.path.join(cur_dir,'label_2') 
    print(labels_dir)  #wjg
    for parent, dirnames, filenames in os.walk(labels_dir): # 分别得到根目录,子目录和根目录下文件     
        for file_name in filenames:  
            full_path=os.path.join(parent, file_name) # 获取文件全路径  
            #print full_path  
            print(full_path) # wjg
            f=open(full_path)  
            split_lines = f.readlines()  
            name= file_name[:-4] # 后四位是扩展名.txt,只取前面的文件名  
            #print name  
            img_name=name+'.jpg'  
            img_path=os.path.join('/data1/faster-rcnn.pytorch/data/VOCdevkit/VOC2007/JPEGImages',img_name) # 路径需要自行修改              
            #print img_path  
            # wjg
            print("img_path",img_path)
            img_size=cv2.imread(img_path).shape  
            generate_xml(name,split_lines,img_size,class_ind)  
print('all txts has converted into xmls')  

6、生成检索txt文件

在faster rcnn中文件夹ImageSets/Main和ImageSets/Layout中是用来存放检索训练测试数据的txt文件。因此,我们要根据新的label_2中的txt文件来划分。除了要提前建好文件夹之外,要修改的地方就是类别来,也是要和之前的匹配。
在这里插入图片描述

# create_train_test_txt.py  
# encoding:utf-8  
import pdb  
import glob  
import os  
import random  
import math  

def get_sample_value(txt_name, category_name):  
    label_path = './label_2/'  
    txt_path = label_path + txt_name+'.txt'  
    try:  
        with open(txt_path) as r_tdf:  
            if category_name in r_tdf.read():  
                return ' 1'  
            else:  
                return '-1'  
    except IOError as ioerr:  
        print('File error:'+str(ioerr))  

txt_list_path = glob.glob('./label_2/*.txt')  
txt_list = []  

for item in txt_list_path:  
    temp1,temp2 = os.path.splitext(os.path.basename(item))  
    txt_list.append(temp1)  
txt_list.sort()  
print(txt_list, end = '\n\n')  

# 有博客建议train:val:test=8:1:1,先尝试用一下  
num_trainval = random.sample(txt_list, math.floor(len(txt_list)*9/10.0)) # 可修改百分比  
num_trainval.sort()  
print(num_trainval, end = '\n\n')  

num_train = random.sample(num_trainval,math.floor(len(num_trainval)*8/9.0)) # 可修改百分比  
num_train.sort()  
print(num_train, end = '\n\n')  

num_val = list(set(num_trainval).difference(set(num_train)))  
num_val.sort()  
print(num_val, end = '\n\n')  

num_test = list(set(txt_list).difference(set(num_trainval)))  
num_test.sort()  
print(num_test, end = '\n\n')  

# pdb.set_trace()  # wjg

Main_path = '/data1/faster-rcnn.pytorch/data/VOCdevkit/VOC2007/ImageSets/Main/'  
train_test_name = ['trainval','train','val','test']  
category_name = ['van', 'tram', 'car', 'pedestrian', 'truck']#修改类别  

# 循环写trainvl train val test  
for item_train_test_name in train_test_name:  
    list_name = 'num_'  
    list_name += item_train_test_name  
    train_test_txt_name = Main_path + item_train_test_name + '.txt'   
    try:  
        # 写单个文件  
        with open(train_test_txt_name, 'w') as w_tdf:  
            # 一行一行写  
            for item in eval(list_name):  
                w_tdf.write(item+'\n')  
        # 循环写Car Pedestrian Cyclist  
        for item_category_name in category_name:  
            category_txt_name = Main_path + item_category_name + '_' + item_train_test_name + '.txt'  
            with open(category_txt_name, 'w') as w_tdf:  
                # 一行一行写  
                for item in eval(list_name):  
                    w_tdf.write(item+' '+ get_sample_value(item, item_category_name)+'\n')  
    except IOError as ioerr:  
        print('File error:'+str(ioerr)) 

三、训练

1、修改参数

  • 首先,在faster-rcnn.pytorch/lib/datasets目录下的pascal_voc.py里更改自己的类别,'background’切记不可删掉,把后面的原来的20个label换成自己的,不用更改类别数目,也没有地方可以更改。
  • 图片格式默认为jpg,可以修改为自己数据库中的图片格式。
    在这里插入图片描述

2、删除缓存

于我们之前用vgg16训练过原始的数据库,因此需要将以下目录中的缓存删除。若本来就没有生成以上目录,或者没有更换数据集,则无需删除。如果不删除这些这些缓存,会导致在更换数据集的时候各种规格不一致。

faster-rcnn.pytorch/output/vgg16/voc_2007_trainval/default
faster-rcnn.pytorch/data/cache
faster-rcnn.pytorch/data/VOCdevkit2007/annotations_cache

3、修bug

(1)在运行指令的时候可能会出现一些问题assert(boxes[:,2]>=boxes[:,0]).all()只要是你标注的框靠近边缘,都会出这个错误。
修改lib/datasets/imdb.py,append_flipped_images()函数
  在一行代码为 boxes[:, 2] = widths[i] - oldx1 - 1下加入代码:

for b in range(len(boxes)):
	if boxes[b][2]< boxes[b][0]:
		boxes[b][0] = 0

(2)训练过程中loss全都是nan,解决办法是修改faster-rcnn.pytorch/lib/datasets/pascal_voc.py把-1全部去掉,因为这个脚本对VOC数据集的处理是全部-1了,但是自己的数据集不用减,就使得计算溢出产生了nan
在这里插入图片描述

四、修改

1、数据路径

在lib/datasets/pascal_voc.py中将路径置为绝对路径。
修改self._data_path中的内容,同时注释掉用来检查数据是否存在的assert语句。
在这里插入图片描述
将self._devkit_path全部修改为self._data_path保证当前路径是存在的。
在这里插入图片描述

2、去掉预训练模型

在这里插入图片描述

3、bbox解析的问题

减不减1的问题 在这里插入图片描述

4、numpy版本问题

将float强制转换为int型
当前版本的不支持floaf索引
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5、一些文件的作用

datasets.factory工厂文件,定义了voc和coco的目录结构,其中包含了get_imdb(name)函数,就可以获取输入的目录结构。
在这里插入图片描述
combined_roidb完成数据的处理,其中执行了get_imdb(name)函数
train_net完成网络的前向运算以及训练
可以在lib/faster_rcnn/config.py中修改其特有的一些参数

  • 13
    点赞
  • 86
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值