Caffe学习系列——Faster-RCNN训练自己的数据集

Caffe学习系列——6使用Faster-RCNN进行目标检测

Contents

  1. 1. 配置与运行Demo
    1. 1.1. 1.配置、编译与安装环境
    2. 1.2. 2.运行demo
    3. 1.3. 3.训练PASCAL VOC 2007的数据集
    4. 1.4. 4.遇到的问题
  2. 2. 使用Faster-RCNN训练自己的数据集
    1. 2.1. 1.工程目录简介
    2. 2.2. 2.创建数据集
    3. 2.3. 3.训练自己的数据
    4. 2.4. 4.测试结果

由于要实现服装的目标检测,所以一直在研究Faster-RCNN 。讲到目标检测,不得不提到rbg大神的深度神经网络检测算法系列RCNN、Fast-RCNN、Faster-RCNN,其还在github上开源了自己的代码,造福广大码农。这是rbg大神的主页 https://people.eecs.berkeley.edu/~rbg/index.html ,以及本篇博文会用到的Faster-RCNN的github源码地址 https://github.com/rbgirshick/py-faster-rcnn,欢迎前去膜拜学习。对于RCNN、Fast-RCNN、Faster-RCNN的介绍就不在这里赘述了,可以看看我的上一篇博文 。该github源码是Faster-RCNN python版本的实现,并使用caffe。除了python版本,rbg大神还提供了matlab代码。这两种实现方式有些许的不同,主要体现在以下几点:

  1. 由于使用了python layer,python版本的在测试时间上会比matlab版本的慢10%左右,但是准确率差不多甚至略高一点。
  2. 由于在实现方式上有一点点不同,所以使用MATLAB代码训练的模型与Python版本的不兼容
  3. 目前已经实现的方式有两种:Alternative training和Approximate join training。推荐使用第二种,因为第二种使用的显存更小,而且训练会更快。

本篇博文主要分为两部分,第一部分讲如何配置py-faster-rcnn并训练PASCALVOC2007,运行demo。第二部分讲如何对代码和数据集进行修改实现自己数据的训练与检测。

配置与运行Demo

这个过程主要是跟着github上的说明:https://github.com/rbgirshick/py-faster-rcnn/blob/master/README.md 走了一遍用以熟悉用Faster-RCNN进行目标检测的过程。

1.配置、编译与安装环境

  1. 在进行这一步之前,需已经在自己的机器上配置好caffe环境以及各种依赖项的安装,在配置之前,需确保已经安装以下几个python包:cython、easydict和python-opencv。安装命令如下:

    1
    2
    3
    pip install cython 
    pip install easydict
    apt-get install python-opencv
  2. 从github上clone项目,注意!一定要在clone时加入–recursive参数,不然会很麻烦,也不要直接下载,在机器上装个git来clone,这样会省去很多时间。

    git clone –recursive https://github.com/rbgirshick/py-faster-rcnn.git

  3. Cython模块编译

    cd $FRCN_ROOT /lib
    make

  4. caffe和pycaffe的编译
    在编译之前,需要复制$FRCN_ROOT/caffe-fast-rcnn 的Makefile.config.example,然后重命名为Makefile.config。

    需要注意的是里面有几个配置需要添加
    打开USE_CUDNN=1,这个选项默认情况下是关闭的,需要打开让CUDA支持DNN
    打开WITH_PYTHON_LAYER=1,默认关闭,需打开,因为FasterRCNN需要支持Python接口。
    执行以下命令进行编译

    cd $FRCN_ROOT/caffe-fast-rcnn
    make -j8 && make pycaffe

2.运行demo

  1. 下载训练好的模型,下载后这个faster_rcnn_models文件夹在$FRCN_ROOT/data下面,可以从data/README.md中查看关于这个的详细介绍。这些模型是在VOC 2007 上训练的。

    cd $FRCN_ROOT
    ./data/scripts/fetch_faster_rcnn_models.sh
    这里有个小建议,就是下载模型时,直接去脚本文件中复制URL使用迅雷下载更快。

  2. 运行demo

    cd $FRCN_ROOT
    ./tools/demo.py
    这个demo展示了使用在PASCAL VOC 2007上训练的VGG16网络来进行目标检测。运行结果如下图所示:

3.训练PASCAL VOC 2007的数据集

  1. 下载训练、验证以及测试集和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

  2. 解压

    tar xvf VOCtrainval_06-Nov-2007.tar
    tar xvf VOCtest_06-Nov-2007.tar
    tar xvf VOCdevkit_08-Jun-2007.ta
    解压后的文件结构如下

    1
    2
    3
    4
    $VOCdevkit/   # 开发工具包
    $VOCdevkit/VOCcode/ # VOC实用代码
    $VOCdevkit/VOC2007# 图片集, 注释, 等等
    # 一些其他的目录

将VOCdevkit改名为VOCdevkit2007,然后放到data文件夹下,亦可以使用软连接的方式,

cd $FRCN_ROOT/data
ln -s $VOCdevkit VOCdevkit2007   
  1. 下载预训练的ImageNet模型

    cd $FRCN_ROOT
    ./data/scripts/fetch_imagenet_models.sh
    VGG16来自Caffe Mode Zoo,ZF是由MSRA训练的结构

  2. 训练数据
    (1) 使用交替优化(alternating optimization)算法来训练和测试Faster R-CNN
    1
    2
    3
    4
    5
    6
    7
    8
    cd $FRCN_ROOT
    ./experiments/scripts/faster_rcnn_alt_opt.sh [GPU_ID] [NET] [--set ...]
    # GPU_ID是你想要训练的GPUID
    # 你可以选择如下的网络之一进行训练:ZF, VGG_CNN_M_1024, VGG16
    # --set ... 运行你自定义fast_rcnn.config参数,例如.
    # --set EXP_DIR seed_rng1701 RNG_SEED 1701
    #例如命令
    ./experiments/scripts/faster_rcnn_alt_opt.sh 0 ZF pascal_voc

输出的结果在 $FRCN_ROOT/output下。训练过程截图:

(2) 使用近似联合训练( approximate joint training)

cd $FRCN_ROOT
./experiments/scripts/faster_rcnn_end2end.sh [GPU_ID] [NET] [--set ...]

这个方法是联合RPN模型和Fast R-CNN网络训练。而不是交替训练。用此种方法比交替优化快1.5倍,但是准确率相近。所以推荐使用这种方法
训练Fast R-CNN网络的结果保存在这个目录下:

output///

测试保存在这个目录下:

output////

4.遇到的问题

  1. TypeError: ‘numpy.float64’ object cannot be interpreted as an index

    这个错误是$FRCN_ROOT/lib/roi_data_layer下的minibatch.py中的npr.choice引起的,所以需要改成ruxia所示
    1
    2
    3
    4
    if fg_inds.size > 0:
    for i in range(0,len(fg_inds)):
    fg_inds[i] = int(fg_inds[i])
    fg_inds = npr.choice(fg_inds, size=int(fg_rois_per_this_image), replace=False)

注意有两个npr.choice,所以两个地方都按照如上来改。

  1. labels[fg_rois_per_this_image:] = 0
    TypeError: slice indices must be integers or None or have an index method

    这个错误是由numpy的版本引起的,只要将fg_rois_per_this_image强制转换为int型就可以了
    labels[int(fg_rois_per_this_image):] = 0

使用Faster-RCNN训练自己的数据集

1.工程目录简介

由于需要训练自己的数据集,所以需要对这个工程各个目录的作用有所了解

  1. caffe-fast-rcnn:caffe框架目录
  2. data:用来存放pretrained模型以及读取文件的cache缓存,还有一些下载模型的脚本
  3. experiments:存放配置文件以及运行的log文件,另外这个目录下有scripts,里面存放end2end和alt_opt两种训练方式的脚本
  4. lib:用来存放一些python接口文件,如其下的datasets主要负责数据库读取,config负责一些训练的配置选项
  5. models:里面存放了三个模型文件,小型网络ZF,中型网络VGG_CNN_M_1024以及大型网络VGG16,根据你的硬件条件来选择使用哪种网络,ZF和VGG_CNN_M_1024需要至少3G内存,VGG16需要更多的内存,但不会超过11G。
  6. output:这里存放的是训练完成后的输出目录,这是运行了训练后才会出现的目录
  7. tools:里面存放的是训练和测试的Python文件

2.创建数据集

我个人觉得训练模型最头疼的是数据集的准备,本文我以服装的识别为例来说明如何用自己的数据集进行目标检测。在准备数据集的时候,假如你是第一次用自己的数据集进行训练,那么最好是参照上一章节跑的demo中的VOC2007数据集的格式来准备,这样,关于后续训练过程会涉及到的配置更改会较为简单,比较容易成功。在本次的训练过程中,我使用的是由香港中文大学Large-scale Fashion (DeepFashion) Database提供的服装标记相关的数据集,在此特别感谢一下他们愿意将数据集公开。想要获取他们的数据集只需要发邮件申请即可。基于上述已标记的数据集,可将数据集整理得到如下所示的格式:
0000001.jpg Blouse 72 79 232 273
0000002.jpg Shorts 67 59 155 161
如果一张图片有多个目标,则格式如下:(比如两个目标)
000002.jpg dog 44 28 132 121
000002.jpg car 50 27 140 110

  1. 将上述的txt转成xml,可参考如下matlab代码:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    %%
    %该代码可以做voc2007数据集中的xml文件,
    %txt文件每行格式为:0000001.jpg Tee 44 28 132 121
    %即每行由图片名、目标类型、包围框坐标组成,空格隔开
    %包围框坐标为左上角和右下角
    %@author:bealin
    %%
    clc;
    clear;
    %注意修改下面四个变量
    imgpath='/home/linbiyuan/py-faster-rcnn/data/VOCdevkit2007/VOC2007/JPEGImages/';%图像存放文件夹
    txtpath='/home/linbiyuan/py-faster-rcnn/data/VOCdevkit2007/VOC2007/alldata.txt';%txt文件
    xmlpath_new='/home/linbiyuan/py-faster-rcnn/data/VOCdevkit2007/VOC2007/Annotations/';%修改后的xml保存文件夹
    foldername='VOC2007';%xml的folder字段名

    fidin=fopen(txtpath,'r');

    while ~feof(fidin)
    tline=fgetl(fidin);
    str = regexp(tline, ' ','split');
    filepath=[imgpath,str{1}];
    img=imread(filepath);
    [h,w,d]=size(img);
    rectangle('Position',[str2double(str{3}),str2double(str{4}),str2double(str{5})-str2double(str{3}),str2double(str{6})-str2double(str{4})],'LineWidth',4,'EdgeColor','r');
    Createnode=com.mathworks.xml.XMLUtils.createDocument('annotation');
    Root=Createnode.getDocumentElement;%根节点
    node=Createnode.createElement('folder');
    node.appendChild(Createnode.createTextNode(sprintf('%s',foldername)));
    Root.appendChild(node);
    node=Createnode.createElement('filename');
    node.appendChild(Createnode.createTextNode(sprintf('%s',str{1})));
    Root.appendChild(node);
    source_node=Createnode.createElement('source');
    Root.appendChild(source_node);
    node=Createnode.createElement('database');
    node.appendChild(Createnode.createTextNode(sprintf('My Database')));
    source_node.appendChild(node);
    node=Createnode.createElement('annotation');
    node.appendChild(Createnode.createTextNode(sprintf('VOC2007')));
    source_node.appendChild(node);
    node=Createnode.createElement('image');
    node.appendChild(Createnode.createTextNode(sprintf('flickr')));
    source_node.appendChild(node);
    node=Createnode.createElement('flickrid');
    node.appendChild(Createnode.createTextNode(sprintf('NULL')));
    source_node.appendChild(node);
    owner_node=Createnode.createElement('owner');
    Root.appendChild(owner_node);
    node=Createnode.createElement('flickrid');
    node.appendChild(Createnode.createTextNode(sprintf('NULL')));
    owner_node.appendChild(node);

    node=Createnode.createElement('name');
    node.appendChild(Createnode.createTextNode(sprintf('lby')));
    owner_node.appendChild(node);
    size_node=Createnode.createElement('size');
    Root.appendChild(size_node);

    node=Createnode.createElement('width');
    node.appendChild(Createnode.createTextNode(sprintf('%s',num2str(w))));
    size_node.appendChild(node);

    node=Createnode.createElement('height');
    node.appendChild(Createnode.createTextNode(sprintf('%s',num2str(h))));
    size_node.appendChild(node);

    node=Createnode.createElement('depth');
    node.appendChild(Createnode.createTextNode(sprintf('%s',num2str(d))));
    size_node.appendChild(node);

    node=Createnode.createElement('segmented');
    node.appendChild(Createnode.createTextNode(sprintf('%s','0')));
    Root.appendChild(node);
    object_node=Createnode.createElement('object');
    Root.appendChild(object_node);
    node=Createnode.createElement('name');
    node.appendChild(Createnode.createTextNode(sprintf('%s',str{2})));
    object_node.appendChild(node);

    node=Createnode.createElement('pose');
    node.appendChild(Createnode.createTextNode(sprintf('%s','Unspecified')));
    object_node.appendChild(node);

    node=Createnode.createElement('truncated');
    node.appendChild(Createnode.createTextNode(sprintf('%s','0')));
    object_node.appendChild(node);

    node=Createnode.createElement('difficult');
    node.appendChild(Createnode.createTextNode(sprintf('%s','0')));
    object_node.appendChild(node);

    bndbox_node=Createnode.createElement('bndbox');
    object_node.appendChild(bndbox_node);

    node=Createnode.createElement('xmin');
    node.appendChild(Createnode.createTextNode(sprintf('%s',num2str(str{3}))));
    bndbox_node.appendChild(node);

    node=Createnode.createElement('ymin');
    node.appendChild(Createnode.createTextNode(sprintf('%s',num2str(str{4}))));
    bndbox_node.appendChild(node);

    node=Createnode.createElement('xmax');
    node.appendChild(Createnode.createTextNode(sprintf('%s',num2str(str{5}))));
    bndbox_node.appendChild(node);

    node=Createnode.createElement('ymax');
    node.appendChild(Createnode.createTextNode(sprintf('%s',num2str(str{6}))));
    bndbox_node.appendChild(node);
    %保存xml%
    lastname=str{1};
    tempname=strrep(lastname,'.jpg','.xml');
    xmlwrite(tempname,Createnode);
    fprintf('%s\n',tempname);

    end
    fclose(fidin);

由于我的数据集是单标签的,所以这份代码是针对单标签来生成xml。假如你的数据集是多标签的,txt的格式可前所示,而相应的matlab代码做出一点修改即可。生成的xml格式如下

这份代码运行生成的xml文件是在当前目录下的,所以还需要将所有的xml放到VOC2007下的Annotations中,可参考如下linux命令:
for xml in *.xml;do mv $xml Annotations/;done

  1. 将所有的训练图片放到JPEGImages文件夹中,生成ImageSet\Main里的四个txt文件,分别是:trainval.txt(训练和验证集总和)、train.txt(训练集)、val.txt(验证集)、test.txt(测试集),trainval集占整个数据集的70%,train集占trainval集的70%,val集占trainval集的30%,test集占整个数据集的30%。可参考以下代码进行数据集的划分:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    %%
    %该代码根据已生成的xml,制作VOC2007数据集中的trainval.txt;train.txt;test.txt和val.txt
    %trainval占总数据集的70%,test占总数据集的30%;train占trainval的70%,val占trainval的30%;
    %上面所占百分比可根据自己的数据集修改
    %注意修改下面两个路径
    xmlfilepath='/home/linbiyuan/py-faster-rcnn/data/VOCdevkit2007/VOC2007/Annotations';
    txtsavepath='/home/linbiyuan/py-faster-rcnn/data/VOCdevkit2007/VOC2007/ImageSets/Main/';

    xmlfile=dir(xmlfilepath);
    numOfxml=length(xmlfile)-2;%减去.和.. 总的数据集大小

    trainval=sort(randperm(numOfxml,floor(numOfxml*0.7)));%trainval为数据集的50%
    test=sort(setdiff(1:numOfxml,trainval));%test为剩余50%

    trainvalsize=length(trainval);%trainval的大小
    train=sort(trainval(randperm(trainvalsize,floor(trainvalsize*0.7))));
    val=sort(setdiff(trainval,train));

    ftrainval=fopen([txtsavepath 'trainval.txt'],'w');
    ftest=fopen([txtsavepath 'test.txt'],'w');
    ftrain=fopen([txtsavepath 'train.txt'],'w');
    fval=fopen([txtsavepath 'val.txt'],'w');

    for i=1:numOfxml
    if ismember(i,trainval)
    fprintf(ftrainval,'%s\n',xmlfile(i+2).name(1:end-4));
    if ismember(i,train)
    fprintf(ftrain,'%s\n',xmlfile(i+2).name(1:end-4));
    else
    fprintf(fval,'%s\n',xmlfile(i+2).name(1:end-4));
    end
    else
    fprintf(ftest,'%s\n',xmlfile(i+2).name(1:end-4));
    end
    end
    fclose(ftrainval);
    fclose(ftrain);
    fclose(fval);
    fclose(ftest);

至此,数据集的构建就完成啦,你可以新建一个文件夹,将上述三个文件夹放到里面去,也可将上述三个文件夹分贝替换VOC2007数据集中的Annotations、ImageSets和JPEGImages,这样可免去一些训练的修改。本文选择的是替换~

3.训练自己的数据

1. 修改prototxt配置文件
这些配置文件都在models下的pascal_voc下。里面有三种网络结构:ZF、VGG16、VGG_CNN_M_1024,本文选择的是VGG_CNN_M_1024 。每个网络结构中都有三个文件夹,分别是faster_rcnn_end2end、faster_rcnn_alt_opt、faster_rcnn。使用近似联合训练,比交替优化快1.5倍,但是准确率相近,所以推荐使用这种方法。更改faster_rcnn_end2end文件夹下的train.prototxt和test.prototxt,train中需要更改的地方有三处,
第一处是input-data层,将原先的21改成:你的实际类别数+1(背景),我目标检测一共有46类,所以加上背景这一类,一共47类。

第二处是cls_score层

第三处是bbox_pred,这里需将原来的84改成(你的类别数+1)4,即(46+1)4=188

test.prototxt中没有input-data层,所以只需按照train中的修改cls_score层以及bbox_pred层即可
2. 修改lib/datasets/pascal_voc.py,将类别改成自己的类别

这里有一个注意点就是,这里的类别以及你之前的类别名称最好是全部小写,假如是大写的话,则会报keyError的错误,这时只需要在pascal_voc。py中第218行的lower去掉即可

datasets目录下主要有三个文件,分别是
(1) factory.py:这是一个工厂类,用类生成imdb类并且返回数据库供网络训练和测试使用;
(2) imdb.py:是数据库读写类的基类,封装了许多db的操作;
(3) pascl_voc.pyRoss用这个类来操作
3. 修改py-faster-rcnn/lib/datasets/imdb.py
在使用自己的数据进行训练时,假如你的数据集中的图片没有统一整理过就会报 assert(boxes[:,2] >= boxes[:,0]).all() 这个错误,故需在imdb.py中加入如下几行

4. 开始训练

cd py-faster-rcnn
./experiments/scripts/faster_rcnn_end2end.sh 0 VGG_CNN_M_1024 pascal_voc

由于训练过程太长,可以将训练过程产生的输出定向输入到log文件中,这样可方便查看。只需在上述命令中加入定向输入的命令即可,如下:

./experiments/scripts/faster_rcnn_end2end.sh 0 VGG_CNN_M_1024 pascal_voc > /home/lby/log/clothdirector.log 2>&1

!!!训练前需要将cache中的pki文件以及VOCdevkit2007中annotations_cache的缓存删掉。

训练过程中会遇到的问题

  1. roidb[i][‘image’] = imdb.image_path_at(i)
    IndexError: list index out of range

    解决方法:删除data/cache里面的pki文件
    注意:不管在训练过程中遇到什么问题,修正过后,重新训练之前都需要将cache中的pki文件删除之后再重新运行,
  2. R = [obj for obj in recs[imagename] if obj[‘name’] == classname]
    KeyError: ‘0000001’

    这是在测试时出现错误,删掉VOCdevkit2007中annotations_cache的缓存

4.测试结果

训练完成之后,将output中的最终模型拷贝到data/faster_rcnn_models,修改tools下的demo.py,我是使用VGG_CNN_M_1024这个中型网络,不是默认的ZF,所以要改的地方挺多
1. 修改class

1
2
3
4
5
6
7
8
9
10
11
12
CLASSES = ('__background__',
'Blouse', 'Sweatpants', 'Cardigan', 'Button-Down',
'Cutoffs', 'Chinos', 'Top', 'Anorak', 'Kimono',
'Tank', 'Robe', 'Parka', 'Jodhpurs',
'Halter', 'Shorts', 'Caftan','Turtleneck',
'Leggings', 'Joggers', 'Hoodie', 'Culottes',
'Sweater', 'Flannel', 'Jeggings', 'Blazer',
'Onesie', 'Coat', 'Henley', 'Jacket',
'Trunks', 'Gauchos', 'Sweatshorts', 'Romper',
'Jersey', 'Bomber', 'Sarong', 'Dress','Jeans',
'Tee', 'Coverup', 'Capris', 'Kaftan','Peacoat',
'Poncho', 'Skirt', 'Jumpsuit')

2. 增加你自己训练的模型

1
2
3
4
5
NETS = {'vgg16': ('VGG16',
'VGG16_faster_rcnn_final.caffemodel'),
'zf': ('ZF',
'ZF_faster_rcnn_final.caffemodel'),
'myvgg1024':('VGG_CNN_M_1024','vgg_cnn_m_1024_faster_rcnn_iter_70000.caffemodel')}

3. 修改prototxt,如果你用的是ZF,就不用改了

1
2
prototxt = os.path.join(cfg.MODELS_DIR, NETS[args.demo_net][0],
'faster_rcnn_end2end', 'test.prototxt')

4. 开始检测
执行 ./tools/demo.py –net myvgg1024
假如不想那么麻烦输入参数,可以在demo的parse_args()里修改默认参数
parser.add_argument(‘–net’, dest=’demo_net’, help=’Network to use [myvgg1024]’,
choices=NETS.keys(), default=’myvgg1024’)
这样只需要输入 ./tools/demo.py 就可以了
检测结果:

遇到的问题

  1. Cannot copy param 0 weights from layer“”:已放弃(核心已转储)

    没有修改prototxt,详情请见第3步
  2. Makefile:2: recipe for target ‘all’ failed

    Traceback (most recent call last):
    File “setup.py”, line 59, in
    CUDA = locate_cuda()
    File “setup.py”, line 56, in locate_cuda
    raise EnvironmentError(‘The CUDA %s path could not be located in %s’ % (k, v))
    EnvironmentError: The CUDA lib64 path could not be located in /usr/lib64
    Makefile:2: recipe for target ‘all’ failed
    解决方法:打开 setup.py,把lib64改为lib

    cudaconfig = {‘home’:home, ‘nvcc’:nvcc,

    'include': pjoin(home, 'include'),  
     'lib64': pjoin(home, 'lib')} 
    
  3. make error:command ‘/usr/local/bin/nvcc’ failed with exit status 1

    添加 export PATH=/usr/local/cuda/bin:”$PATH” 到你的 ~/.bashrc

Updated:2017-03-01
上一篇
机器学习系列—1.机器学习介绍
下一篇
Caffe学习系列——5使用R-CNN进行目标检测
Please enable JavaScript to view the comments powered by duoshuo.

你可能感兴趣的:(deep,learning)