使用TensorFlow训练综合FPN和Faster-RCNN的目标检测模型

Faster-RCNN已经推出几年了,尽管其后有相当多另辟蹊径的方法,比如SSD和YOLO,等等,但目前其仍是目标检测领域精度最优的模型之一。而最近推出的FPN模型,更是融合神经网络中不同层之间的特征,对目标检测的大小物体兼顾,从而达到了最好的检测结果。比如,COCO目标检测的Leaderboard前几名目前无不是基于FPN进行的各种改进。 我想着将FPN与Faster-RCNN结合使用,GitHub上目前一共有3个类似的Repositories: FPN_TensorflowFastFPNFPN。 我将它们分别clone了下来,并试着调试使用,最后发现最好用的是FPN_Tensorflow,而其他两个,在调试和使用中,要么碰到各种问题,提issue未获解决;要么虽然跑通了,但无法测试模型的效果,也没有评估模型mAP的程序。 下面,我就将我在调试和使用这个FPN_Tensorflow Repository的过程中总结的经验简单记录一下,以备以后使用需要。需要特别说明的是,虽然作者说“not suitable for multi-target detection tasks”,但经我实验,对于multi-target的检测任务,其效果也是可以的。 此外,我将自己修改和订制过的Repository上传到了我的GitHub上,可以点此访问参考

1. 从GitHub上将项目clone下来

2. 根据数据集的名字修改配置文件

修改libs/configs/cfgs.py文件中的DATASET_NAME,将其修改为自己的数据集的名字,如:

DATASET_NAME = ‘animals’

3. 将数据集放到项目的data文件夹下

比如,我们放置的数据集名为animals,然后将数据放到animals文件夹下

3.1 原始图片放到data/animals/JPEGImages文件夹下

3.2 原始标注信息放到data/animals/annotations文件夹下

3.3 在data/animals文件夹下生成一个classes.txt的文件,将所有的类别(除background)逐行放到该文件里

在我的Repository里,运行项目根目录下的gen_classes.py即可在data/animals文件夹下生成一个classes.txt文件。

3.4 将标注信息转化为PASCAL VOC格式的xml标注文件

在我的Repository里,运行根目录下的convert_txt2xml.py即可将我自己的TXT格式的标注信息转化为xml格式的文件,并放置在data/animals/Annotations文件夹里。你可以参考我的程序,根据自己的原始标注信息,将其转化为PASCAL VOC格式的xml文件。

3.5 运行data/io/convert_data_to_tfrecord.py将准备好的数据转化为TensorFlow的tfrecords格式的文件

这些转化后的文件,位于data/tfrecords文件夹下,名为{dataset}_train.tfrecord和{dataset}_test.tfrecord。 转化好之后,可以在项目根目录下运行“./scripts/test.sh 0 output/res101_trained_weights/v1_animals/animals_model.ckpt 20”来测试验证生成的数据是否正确。运行后,会在tools/test_result文件夹下生成一些样例图片,查看检查即可。需要注意的是,这里需要指定一个模型,如果没有模型,可以略过此步,直接进行下面的训练,训练出模型之后,再回头通过这一步验证数据集的正确性。

4. 修改其他参数

准备好数据之后,还需要修改一些其他参数,如下:

4.1 修改libs/configs/cfgs.py中的参数

我们在上面2中已经修改了这个配置文件中DATASET_NAME这个参数了,下面还可以修改这个文件中的其他参数,如:

NET_NAME = ‘resnet_v1_101’
DATASET_NAME = ‘animals’
VERSION = ‘v1_{}’.format(DATASET_NAME)
ANCHOR_SCALES = [0.5, 1., 2.]
ANCHOR_RATIOS = [0.5, 1, 2] # height to width
SCALE_FACTORS = [10., 5., 1., 0.5]

此外,如CLASS_NUM也需要根据数据集进行针对性地修改。这里的CLASS_NUM不包含background,可以直接将其修改为前面3.3中生成的classes.txt中的class的数量即可。

4.2 修改configs/config_*.py中的参数

这里修改的配置文件位于configs文件夹下,具体修改哪个文件,根据4.1中的NET_NAME来确定,如上,我们选择的网络为resnet_v1_101,所以我们需要修改的配置文件是configs/config_res101.py,可以修改的参数主要有:

pretrained_model_path # to use a pretrained model
batch_size

4.3 在程序中添加数据集的名字

具体来说,是在文件data/io/read_tfrecord.py中的next_batch函数的第一行添加数据集的名字,如下:

if dataset_name not in [‘cooler’, ‘animals’, ‘pascal’, ‘coco’, ‘layer’, ‘shelf’]:
raise ValueError(‘dataSet name must be in cooler, animals, pascal, coco, layer or shelf’)

4.4 在程序中添加label映射NAME_LABEL_MAP

具体来说,是在libs/label_name_dict/label_dict.py文件中添加,如下:

elif cfgs.DATASET_NAME == ‘animals’:
NAME_LABEL_MAP = {}
NAME_LABEL_MAP[‘back_ground’] = 0
with open(‘data/{}/classes.txt’.format(cfgs.DATASET_NAME)) as f:
lines = [line.strip() for line in f.readlines()]
for i, line in enumerate(lines, 1):
NAME_LABEL_MAP[line] = i
elif cfgs.DATASET_NAME == ‘layer’:
NAME_LABEL_MAP = {
‘back_ground’: 0,
“layer”: 1
}

如果类别较少,如layer这种Dataset,可以直接手动添加;如果类别较多,推荐采用上面的animals这种通过文件的添加方式进行添加。

5. 运行scripts/train.sh训练网络

在项目根目录运行如下所示的脚本进行训练:

cd $ FPN_Tensorflow
# ./scripts/train.sh GPU DATASET
./scripts/train.sh 0 animals

如上,运行train.sh脚本时,后面跟两个参数,第一个指定使用第几块GPU,第二个指定使用的数据集。

6.评测训练得到的网络的效果

在项目根目录下运行以下所示脚本进行评测:

cd $ FPN_Tensorflow
# ./scripts/test.sh GPU MODEL_PATH IMG_NUM
./scripts/test.sh 0 output/res101_trained_weights/v1_animals/animals_model.ckpt 20
# ./scripts/eval.sh GPU MODEL_PATH IMG_NUM
./scripts/eval.sh 0 output/res101_trained_weights/v1_animals/animals_model.ckpt 20
# ./scripts/demo.sh GPU MODEL_PATH
./scripts/demo.sh 0 output/res101_trained_weights/v1_animals/animals_model.ckpt
# ./scripts/inference.sh GPU MODEL_PATH
./scripts/inference.sh 0 output/res101_trained_weights/v1_animals/animals_model.ckpt

如上,需要注意的是这些脚本的几个参数: GPU指定使用的哪块GPU; MODEL_PATH指定评测哪个模型,为模型的路径; IMG_NUM指定测试或验证多少张图片。

7. 可能遇到的一些错误

7.1 LossTensor is inf or nan : Tensor had NaN values

错误如下:

InvalidArgumentError (see above for traceback): LossTensor is inf or nan : Tensor had NaN values
[[Node: train_op/CheckNumerics = CheckNumerics[T=DT_FLOAT, message=”LossTensor is inf or nan”, _device=”/job:localhost/replica:0/task:0/device:GPU:0”](control_dependency)]]
[[Node: gradients/rpn_net/concat_grad/Squeeze_3/_1493 = _Recv[client_terminated=false, recv_device=”/job:localhost/replica:0/task:0/device:CPU:0”, send_device=”/job:localhost/replica:0/task:0/device:GPU:0”, send_device_incarnation=1, tensor_name=”edge_8085_gradients/rpn_net/concat_grad/Squeeze_3”, tensor_type=DT_INT64, _device=”/job:localhost/replica:0/task:0/device:CPU:0”]()]]

可能是因为数据自身的原因,比如标注的坐标超出了图片的范围,如标注坐标小于0,或者大于图像的高或者宽,等等。

7.2 signed integer is less than minimum

错误如下:

UnknownError (see above for traceback): exceptions.OverflowError: signed integer is less than minimum
[[Node: fast_rcnn_loss/PyFunc_1 = PyFunc[Tin=[DT_FLOAT, DT_FLOAT, DT_INT32], Tout=[DT_UINT8], token=”pyfunc_7”, _device=”/job:localhost/replica:0/task:0/device:CPU:0”](rpn_losses/Squeeze/_1579, fast_rcnn_loss/mul_1/_1759, fast_rcnn_loss/strided_slice_1/_1761)]]
[[Node: draw_proposals/Reshape_2/tensor/_1825 = _Recv[client_terminated=false, recv_device=”/job:localhost/replica:0/task:0/device:GPU:0”, send_device=”/job:localhost/replica:0/task:0/device:CPU:0”, send_device_incarnation=1, tensor_name=”edge_3802_draw_proposals/Reshape_2/tensor”, tensor_type=DT_UINT8, _device=”/job:localhost/replica:0/task:0/device:GPU:0”]()]]

原因可能与7.1类似。