场景文字识别
目标检测任务的目标是给定一张图像或是视频帧,让计算机找出其中所有目标的位置,并给出每个目标的具体类别。对于人类来说,目标检测是一个非常简单的任务。然而,计算机能够“看到”的仅有一些值为0 ~ 255的矩阵,很难解图像或是视频帧中出现了人或是物体这样的高层语义概念,也就更加难以定位目标出现在图像中哪个区域。与此同时,由于目标会出现在图像或是视频帧中的任何位置,目标的形态千变万化,图像或是视频帧的背景千差万别,诸多因素都使得目标检测对计算机来说是一个具有挑战性的问题。
【目标检测】
SSD目标检测
|1. 概述
SSD全称:Single Shot MultiBox Detector,是目标检测领域较新且效果较好的检测算法之一[1],有着检测速度快且检测精度高的特点。PaddlePaddle已集成SSD算法,本示例旨在介绍如何使用PaddlePaddle中的SSD模型进行目标检测。下文首先简要介绍SSD原理,然后介绍示例包含文件及如何使用,接着介绍如何在PASCAL VOC数据集上训练、评估及检测,最后简要介绍如何在自有数据集上使用SSD。
|2. SSD原理
SSD使用一个卷积神经网络实现“端到端”的检测:输入为原始图像,输出为检测结果,无需借助外部工具或流程进行特征提取、候选框生成等。论文中SSD使用VGG16[2]作为基础网络进行图像特征提取。但SSD对原始VGG16网络做了一些改变:
下图为模型(输入图像尺寸:300x300)的总体结构:
图1. SSD 网络结构
图中每个矩形盒子代表一个卷积层,最后两个矩形框分别表示汇总各卷积层输出结果和后处理阶段。在预测阶段,网络会输出一组候选矩形框,每个矩形包含:位置和类别得分。图中倒数第二个矩形框即表示网络的检测结果的汇总处理。由于候选矩形框数量较多且很多矩形框重叠严重,这时需要经过后处理来筛选出质量较高的少数矩形框,主要方法有非极大值抑制(Non-maximum Suppression)。
从SSD的网络结构可以看出,候选矩形框在多个特征图(feature map)上生成,不同的feature map具有的感受野不同,这样可以在不同尺度扫描图像,相对于其他检测方法可以生成更丰富的候选框,从而提高检测精度;另一方面SSD对VGG16的扩展部分以较小的代价实现对候选框的位置和类别得分的计算,整个过程只需要一个卷积神经网络完成,所以速度较快。
|3.示例总览
本示例共包含如下文件:
表1. 示例文件
训练阶段需要对数据做预处理,包括裁剪、采样等,这部分操作在image_util.py和data_provider.py中完成。
需要注意:config/vgg_config.py是参数配置文件,含有训练参数、神经网络参数等。配置文件中的参数针对PASCAL VOC数据集而配置,当训练自有数据时,需要进行针对性的修改。
data/prepare_voc_data.py脚本用来生成文件列表,包括切分训练集和测试集,使用时需要事先下载并解压数据,默认采用VOC2007和VOC2012。
|4. PASCAL VOC数据集
[1]数据准备
请首先下载数据集:VOC2007[3]和VOC2012[4]。VOC2007包含训练集和测试集,VOC2012只包含训练集,将下载好的数据解压,目录结构为data/VOCdevkit/VOC2007和data/VOCdevkit/VOC2012。
进入data目录,运行python prepare_voc_data.py即可生成trainval.txt和test.txt。核心函数为:
def prepare_filelist(devkit_dir, years, output_dir):
trainval_list = []
test_list = []
for year in years:
trainval, test = walk_dir(devkit_dir, year)
trainval_list.extend(trainval)
test_list.extend(test)
random.shuffle(trainval_list)
with open(osp.join(output_dir, 'trainval.txt'), 'w') as ftrainval:
for item in trainval_list:
ftrainval.write(item[0] + ' ' + item[1] + '\n')
with open(osp.join(output_dir, 'test.txt'), 'w') as ftest:
for item in test_list:
ftest.write(item[0] + ' ' + item[1] + '\n')
该函数首先对每一年(year)数据进行处理,然后将训练图像的文件路径列表进行随机乱序,最后保存训练文件列表和测试文件列表。默认prepare_voc_data.py和VOCdevkit在相同目录下,且生成的文件列表也在该目录次数。
需注意trainval.txt既包含VOC2007的训练数据,也包含VOC2012的训练数据,test.txt只包含VOC2007的测试数据。
下面是trainval.txt前几行输入示例:
```text
VOCdevkit/VOC2007/JPEGImages/000005.jpg VOCdevkit/VOC2007/Annotations/000005.xml
VOCdevkit/VOC2007/JPEGImages/000007.jpg VOCdevkit/VOC2007/Annotations/000007.xml
VOCdevkit/VOC2007/JPEGImages/000009.jpg VOCdevkit/VOC2007/Annotations/000009.xml
```
文件共两个字段,第一个字段为图像文件的相对路径,第二个字段为对应标注文件的相对路径。
[2]预训练模型准备
下载预训练的VGG-16模型,我们提供了一个转换好的模型,下载模型http://paddlepaddle.bj.bcebos.com/model_zoo/detection/ssd_model/vgg_model.tar.gz,并将其放置路径为vgg/vgg_model.tar.gz。
[3]模型训练
直接执行python train.py即可进行训练。需要注意本示例仅支持CUDA GPU环境,无法在CPU上训练,主要因为使用CPU训练速度很慢,实践中一般使用GPU来处理图像任务,这里实现采用硬编码方式使用cuDNN,不提供CPU版本。train.py的一些关键执行逻辑:
paddle.init(use_gpu=True, trainer_count=4)
data_args = data_provider.Settings(
data_dir='./data',
label_file='label_list',
resize_h=cfg.IMG_HEIGHT,
resize_w=cfg.IMG_WIDTH,
mean_value=[104,117,124])
train(train_file_list='./data/trainval.txt',
dev_file_list='./data/test.txt',
data_args=data_args,
init_model_path='./vgg/vgg_model.tar.gz')
主要包括:
下面给出SDD300x300在VOC数据集(train包括07+12,test为07)上的mAP曲线,迭代140轮mAP可达到71.52%。
图2. SSD300x300 mAP收敛曲线
[4]模型评估
执行python eval.py即可对模型进行评估,eval.py的关键执行逻辑如下:
paddle.init(use_gpu=True, trainer_count=4) # use 4 gpus
data_args = data_provider.Settings(
data_dir='./data',
label_file='label_list',
resize_h=cfg.IMG_HEIGHT,
resize_w=cfg.IMG_WIDTH,
mean_value=[104, 117, 124])
eval(
eval_file_list='./data/test.txt',
batch_size=4,
data_args=data_args,
model_path='models/pass-00000.tar.gz')
调用paddle.init指定使用4卡GPU评估;data_provider.Settings参见训练阶段的配置;调用eval执行评估,其中eval_file_list指定评估数据列表,batch_size指定评估时batch size的大小,model_path指定模型:位置。评估结束会输出loss信息和mAP信息。
[5]图像检测
执行python infer.py即可使用训练好的模型对图片实施检测,infer.py关键逻辑如下:
infer(
eval_file_list='./data/infer.txt',
save_path='infer.res',
data_args=data_args,
batch_size=4,
model_path='models/pass-00000.tar.gz',
threshold=0.3)
其中eval_file_list指定图像路径列表;save_path指定预测结果保存路径;data_args如上;batch_size为每多少样本预测一次;model_path指模型的位置;threshold为置信度阈值,只有得分大于或等于该值的才会输出。下面给出infer.res的一些输出样例:
VOCdevkit/VOC2007/JPEGImages/006936.jpg 12 0.997844 131.255611777 162.271582842 396.475315094 334.0
VOCdevkit/VOC2007/JPEGImages/006936.jpg 14 0.998557 229.160234332 49.5991278887 314.098775387 312.913876176
VOCdevkit/VOC2007/JPEGImages/006936.jpg 14 0.372522 187.543615699 133.727034628 345.647156239 327.448492289
...
共包含4个字段,以tab分割,第一个字段是检测图像路径,第二字段为检测矩形框内类别,第三个字段是置信度,第四个字段是4个坐标值(以空格分割)。
示例还提供了一个可视化脚本,直接运行python visual.py即可,须指定输出检测结果路径及输出目录,默认可视化后图像保存在./visual_res,下面是用训练好的模型infer部分图像并可视化的效果:
图3. SSD300x300 检测可视化示例
|5. 自有数据集
在自有数据上训练PaddlePaddle SSD需要完成两个关键准备,首先需要适配网络可以接受的输入格式,这里提供一个推荐的结构,以train.txt为例
image00000_file_path image00000_annotation_file_path
image00001_file_path image00001_annotation_file_path
image00002_file_path image00002_annotation_file_path
...
文件共两列,以空白符分割,第一列为图像文件的路径,第二列为对应标注数据的文件路径。对图像文件的读取比较直接,略微复杂的是对标注数据的解析,本示例中标注数据使用xml文件存储,所以需要在data_provider.py中对xml解析,核心逻辑如下:
bbox_labels = []
root = xml.etree.ElementTree.parse(label_path).getroot()
for object in root.findall('object'):
bbox_sample = []
# start from 1
bbox_sample.append(float(settings.label_list.index(
object.find('name').text)))
bbox = object.find('bndbox')
difficult = float(object.find('difficult').text)
bbox_sample.append(float(bbox.find('xmin').text)/img_width)
bbox_sample.append(float(bbox.find('ymin').text)/img_height)
bbox_sample.append(float(bbox.find('xmax').text)/img_width)
bbox_sample.append(float(bbox.find('ymax').text)/img_height)
bbox_sample.append(difficult)
bbox_labels.append(bbox_sample)
这里一条标注数据包括:label、xmin、ymin、xmax、ymax和is_difficult,is_difficult表示该object是否为难例,实际中如果不需要,只需把该字段置零即可。自有数据也需要提供对应的解析逻辑,假设标注数据(比如image00000_annotation_file_path)存储格式如下:
label1 xmin1 ymin1 xmax1 ymax1
label2 xmin2 ymin2 xmax2 ymax2
...
每行对应一个物体,共5个字段,第一个为label(注背景为0,需从1编号),剩余4个为坐标,对应的解析逻辑可更改为如下:
bbox_labels = []
with open(label_path) as flabel:
for line in flabel:
bbox_sample = []
bbox = [float(i) for i in line.strip().split()]
label = bbox[0]
bbox_sample.append(label)
bbox_sample.append(bbox[1]/float(img_width))
bbox_sample.append(bbox[2]/float(img_height)) bbox_sample.append(bbox[3]/float(img_width)) bbox_sample.append(bbox[4]/float(img_height))
bbox_sample.append(0.0)
bbox_labels.append(bbox_sample)
同时需要注意根据图像大小及检测物体的大小等更改网络结构相关的超参数,请仿照config/vgg_config.py创建自己的配置文件,参数设置经验请参考论文[1]。
【参考文献】