源码分析和使用指南,博文的增查改删

  1. 概念好模型 xxx.php
  2. 概念好数据变化的条条框框 database/factories/XxxlFactory.php
  3. 写入生成数据的代码,控制好转变的多少数目,对转移后的多寡做出修改 database/seeds/XxxTableSeeder.php
  4. 注册

对数码做增查改删(CRUD)是交互式网络采纳的骨干职能。对于用户,大家只提供了前三项操作;而对博文,四项操作我们的拔取都要提供。

摘要: 什么是时间系列数据   什么是时刻连串(提姆e
Series,以下简称时序)数据?从概念上的话,就是一串按时间维度索引的数据。用描述性的语言来解释什么是时序数据,总而言之,就是这类数据描述了某个被测量的主心骨在一个时间限制内的每个时刻点上的测量值。

本项目地址:caffe/ssd

(对于模型中隐藏 $hidden 的字段,需要使用 makeVisible()方法来暂时平息 hidden, 放置写入数据库时出错)

创制博文的模子

如本体系其次部分所说的,博文应该有如下数据:

  • slug:为后端为每篇博文自动生成的独一字符串,用于数据库里的查询。倘若你对这些定义不熟知,请阅读这一维基百科页面
  • title:标题
  • body:我们的博客平台采取马克(Mark)down编辑器,所以body里积存的是本篇博文的马克down文本
  • description:对本篇博文的一小段介绍
  • favoritesCount:拿到的点赞数
  • tagList:标签列表
  • author:作者,为其公共音讯对象

俺们先来为地点的数额新建Mongoose格局,并将其登记为可用的模型。新建models/Article.js文件,写入:

const mongoose = require('mongoose');
const uniqueValidator = require('mongoose-unique-validator');
const slug = require('slug');

const ArticleSchema = new mongoose.Schema({
  slug: {type: String, lowercase: true, unique: true},
  title: String,
  description: String,
  body: String,
  favoritesCount: {type: Number, default: 0},
  tagList: [{type: String}],
  author: {type: mongoose.Schema.Types.ObjectId, ref: 'User'}
}, {timestamps: true});

ArticleSchema.plugin(uniqueValidator, {message: 'is already taken'});

mongoose.model('Article', ArticleSchema);

如同用户情势里平等,我们这里运用了mongoose-unique-validator来验证slug是唯一的。

地点的代码已经导入了slug包。接下来,大家要定义一个新章程,使用这些包来生成博文slug。为了保险slug的唯一性,大家会在原标题生成的字符串后再添加多少个随机字符。

ArticleSchema.plugin(uniqueValidator, {message: 'is already taken'});

// +++
ArticleSchema.methods.slugify = function() {
  this.slug = slug(this.title) + '-' + (Math.random() * Math.pow(36, 6) | 0).toString(36);
};
// +++

mongoose.model('Article', ArticleSchema);

如何时候调用这些点子吗?每当一篇新博文创制后依然是一篇已有博文的标题暴发变更后,往数据库保存的时候,咱们需要调用那么些艺术生成新的slug。

我们可以依靠Mongoose中间件来在上述的场合自动调用slugify方法。

models/Article.js进入以下代码:

// +++
ArticleSchema.pre('validate', function (next) {
  if (!this.slug || this.isModified('title'))
    this.slugify();
  return next();
});
// +++

mongoose.model('Article', ArticleSchema);

slug的浮动应该暴发在Mongoose检验数据从前源码分析和使用指南,博文的增查改删。,否则没有slug字段,检验必然败北。这也是下面代码中pre'validate'公布的情致。

其余注意,这里回调函数中的this本着的是当前的博文对象;因此,定义Mongoose中间件时也不可以用箭头函数。

说到底,咱们还索要加上一个办法,重回博文的JSON对象。不要忘了进入Mongoose自动创造的createAtupdateAt这两条数据。

// +++
ArticleSchema.methods.toJSONFor = function (user) {
  return {
    slug: this.slug,
    title: this.title,
    description: this.description,
    body: this.body,
    createdAt: this.createdAt,
    updatedAt: this.updatedAt,
    tagList: this.tagList,
    favoritesCount: this.favoritesCount,
    author: this.author.toProfileJSONFor(user)
  };
};
// +++

ArticleSchema.pre('validate', ...);

值得提议的是,author一行,大家用到了事先为用户模型定义的toProfileJSONFor(user)方法。

末尾,大家需要在后端应用中运行方面的脚本,否则之后的中间件不可以选拔Article模型。

app.js文件里出席一行代码:

require('./models/User');
// +++
require('./models/Article');
// +++
require('./config/passport');

到此博文模型就创制完成了。我们接下去要做的就是增长增查改删博文的路由及相应的中间件。

咋样是光阴系列数据

SSD: Single Shot MultiBox Detector

By Wei
Liu,
Dragomir
Anguelov,
Dumitru
Erhan,
Christian
Szegedy,
Scott
Reed,
Cheng-Yang
Fu,
Alexander C. Berg.

为博文操作新建路由对象

跟此前的步子一样,我们第一要为博文的兼具操作(其跟URL都将是/api/articles)新建一个Router对象。

新建文件/routes/api/articles.js,写入:

const router = require('express').Router();
const passport = require('passport');
const mongoose = require('mongoose');
const Article = mongoose.model('Article');
const User = mongoose.model('User');
const auth = require('../auth');

module.exports = router;

一样地,这么些路由索要注册到API的主路由上。打开routes/api/index.js,加入:

router.use('/profiles', require('./profiles'));
// +++
router.use('/articles', require('./articles'));
// +++

路由对象建好了,也注册到主路由上了,接下去就该兑现具体的中间件了。

  什么是岁月体系(提姆(Tim)e
Series,以下简称时序)数据?从概念上来说,就是一串按时间维度索引的数目。用描述性的语言来诠释咋样是时序数据,一句话来说,就是这类数据描述了某个被测量的着重点在一个刻钟范围内的各类时间点上的测量值。
  对时序数据举办建模的话,会蕴藏多少个举足轻重片段,分别是:主体,时间点和测量值。套用这套模型,你会发现你在经常工作生活中,无时无刻不在接触着这类数据。

简介

SSD是运用单个网络开展物体检测任务的集合框架.
你可以使用本代码训练/评估物体检测任务. 更多细节请见 arXiv
paper
以及
slide.

澳门葡京备用网址 1

MultiBox

System VOC2007 test mAP FPS (Titan X) Number of Boxes Input resolution
Faster R-CNN (VGG16) 73.2 7 ~6000 ~1000 x 600
YOLO (customized) 63.4 45 98 448 x 448
SSD300* (VGG16) 77.2 46 8732 300 x 300
SSD512* (VGG16) 79.8 19 24564 512 x 512

澳门葡京备用网址 2

测试对照

澳门葡京备用网址 ,Note: SSD300\ and SSD512* are the latest models. Current code should
reproduce these results.*

新建博文

新建博文的端点为POST /api/articles,而且只有登录的用户才能实施,所以需要身份验证。

routes/api/articles.js出席如下代码:

// +++
const loadCurrentUser = require('./user').loadCurrentUser;

const respondArticle = (req, res, next) => {
  return res.json({article: res.locals.article.otJSONFor(res.locals.res)});
}

router.post('/', auth.required, loadCurrentUser, (req, res, next) => {
  const article = new Article(req.body.article);
  article.author = res.locals.user;
  return article.save().then(() => {
    res.locals.article = article;
    return next();
  }).catch(next);
}, respondArticle);
// +++

module.exports = router;

倘诺您是一个投保人,某只股票的股价就是一类时序数据,其记录着各类时刻点该股票的股价。
假若您是一个运维人士,监控数据是一类时序数据,例如对于机器的CPU的督察数据,就是记录着各样日子点机器上CPU的实际上消耗值。
  这么些世界是由数量整合的,在这么些世界上设有的每个物体,每时每刻都在发出着数量。而对那么些数量的掘进和动用,在这些时代,正在默默的改变人们的生存方式。例如通过可穿戴设备对民用健康的军事管制,就是经过配备源源采撷你的私房健康数据,例如心跳、体温等等,收集完数据后套用模型总结来评估你的健康度。
  假使您的视野和想象空间丰裕大,你会意识你可知挖掘并应用的数量充斥在你所生存的环境中。这一个可以发生多少的目的,会包括你的手机、汽车、空调、冰柜等等。当前可比炎热的物联网的主旨思想,其实就是构建一个足以让具备物体生产数据并开挖其市值的网络。而由此这多少个网络收集的数据,就是鹤立鸡群的时序数据。
  时序数据用于描述一个物体在历史的年华维度上的情况变化音讯,而对于时序数据的剖析,就是尝试通晓并把控其生成的规律的经过。随着物联网、大数量和人造智能技术的进化,时序数据也呈一个暴发式的滋长。而为了更好的补助这类数据的储存和剖析,在市面上衍生出了各式各类的新生的数据库产品。这类数据库产品的发明都是为了缓解传统关系型数据库在时序数据存储和分析上的缺乏和症结,那类产品被统一归类为时序数据库。

目录

  1. 安装
  2. 预备
  3. 训练/评估
  4. 模型
  5. 全新的数据集

从URL中得到博文slug

博文的查、改、删操作,都急需从数据库中通过其slug读取该博文的多寡。如同上一讲获取用户名相同,我们可以在routes/api/articles.js中用router.param为路由定义一个到手博文slug的参数中间件,如下:

// +++
router.param('slug', (req, res, next, slug) => {
  Article.findOne({slug})
    //.populate('author')
    .then(article => {
      if (!article)
        return res.status(404).json({errors: {slug: `no such slug: ${slug}`}});
      res.locals.article = article;
      return next();
    })
    .catch(next);
});
// +++

router.post('/', auth.required, loadCurrentUser, ...

每当该路由遭逢的URL中有和:slug相应的有的时,Express就会调用上述中间件,把截取到的数据传给第三个参数slug,然后从数据库中读取相应的博文,存给res.locals.article依然重返404。

  从DB-Engines的数据库序列流行度趋势榜上能够见见,时序数据库(提姆e
Series DB)的流行度在目前的两年内,一贯都是保障一个很高的提升趋势。
  接下去我会写几篇小说,分别来分析:
  1. 时序数据的基本概念,包括模型、特性和主导的询问和处理操作。
  2. 多少个流行开源时序数据库的最底层实现分析
  3. 阿里云表格存储(TableStore)的时序数据存储和计量解决方案

安装

  1. 下载代码。假如把Caffe克隆到目录$CAFFE_ROOT

  git clone https://github.com/weiliu89/caffe.git
  cd caffe
  git checkout ssd
  1. Build 代码. 按照 Caffe
    instruction
    安装
    必要的packages,然后build。

# 根据Caffe安装的方式修改Makefile.config。
cp Makefile.config.example Makefile.config
make -j8
# 确保include $CAFFE_ROOT/python到PYTHONPATH环境变量内.
make py
make test -j8
# 运行测试,可选   
make runtest -j8

查看博文

端点为GET /api/articles/:slug,身份验证可有可无。

routes/api/articles.js中写入:

// +++
router.get('/:slug', auth.optional, loadCurrentUser, (req, res, next) => {
  res.locals.article.populate('author')
    .execPopulate()
    .then(() => next())
    .catch(next);
}, respondArticle);
// +++

Mongoose提供的populate()艺术,允许一个文档(这里是个Article)读取关联的另一个文档(这里属于User)。

日子序列数据的风味

预备

  1. 下载 fully convolutional reduced (atrous)
    VGGNet.
    假如文件被下载到了$CAFFE_ROOT/models/VGGNet/目录

  2. 下载VOC2007和VOC2012数据集.
    对Pascal VOC数据集的简介:

    澳门葡京备用网址 3

    pascal_voc2012_detection_table.jpg

假设下载到了`$HOME/data/`目录

  # 下载数据.
  cd $HOME/data
  wget http://host.robots.ox.ac.uk/pascal/VOC/voc2012/VOCtrainval_11-May-2012.tar
  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
  # 解压数据.
  tar -xvf VOCtrainval_11-May-2012.tar
  tar -xvf VOCtrainval_06-Nov-2007.tar
  tar -xvf VOCtest_06-Nov-2007.tar

VOC 二零零七年的数额分为VOCtrainval和VOCtest多少个tar包,VOC
二零一二年的多少只有VOCtrainval一个tar包,如下

澳门葡京备用网址 4

VOC0712的三个tar包

解压后,2007和2012两年的数量在VOCdevkit目录的VOC2007VOC2012五个子目录中。每个子目录下,分别包含了多少个文件夹,分别是Annotations
ImageSets JPEGImages SegmentationClass 以及
SegmentationObject。对于SSD的Object任务,需要使用Annotations中的xml标注文件,ImagesSets/Main/目录中的trainval.txttest.txt,以及JPEGImages目录下的图像。

澳门葡京备用网址 5

VOC2007解压

澳门葡京备用网址 6

VOC2012解压后

  1. 创建LMDB文件.

  cd $CAFFE_ROOT
  # Create the trainval.txt, test.txt, and test_name_size.txt in data/VOC0712/
  ./data/VOC0712/create_list.sh
  # 如有必要,可以按需修改create_data.sh文件.
  # 编码trainval和test原始图像,生成lmdb文件:
  #   - $HOME/data/VOCdevkit/VOC0712/lmdb/VOC0712_trainval_lmdb
  #   - $HOME/data/VOCdevkit/VOC0712/lmdb/VOC0712_test_lmdb
  # and make soft links at examples/VOC0712/
  ./data/VOC0712/create_data.sh

变更的trainval.txt格式如图,文件内容是图像的路子和标注文件的不二法门,中间用空格分隔开:

澳门葡京备用网址 7

trainval.txt

生成的test_name_size.txt是测试图像的id heightwidth

澳门葡京备用网址 8

test_name_size.txt

最终,生成trainval和test六个lmdb数据库,分别用来锻炼和测试SSD模型。

澳门葡京备用网址 9

voc0712 trainval and test lmdbs

trainval LMDB

澳门葡京备用网址 10

trainval lmdb

同时,在.../caffe/examples/VOC0712/途径下封存了下面几个lmdb数据库的链接,截图如下:

澳门葡京备用网址 11

voc0712_lmdb_link

更改博文

端点是PUT /api/articles/:slug,身份验证是必备的,请求体的数据会覆盖相应字段。另外,大家还需确保当前报到的用户必须是该博文的撰稿人,否则重回403。

routes/api/articles.js中写入:

// +++
const checkAuthor = (req, res, next) => {
  if (!res.locals.user.equals(res.locals.article.author))
    return res.status(403).json({errors: {user: 'not the author'}});
  return next();
};

router.put('/:slug', auth.required, loadCurrentUser, checkAuthor,
  (req, res, next) => {
    ['title', 'description', 'body'].forEach(propName => {
      if (!req.body.article.hasOwnProperty(propName)) return;
      res.locals.article[propName] = req.body.article[propName];
    });
    res.locals.article.save()
      .then(() => next())
      .catch(next);
  }, respondArticle);
// +++

module.exports = router;

  对于时序数据的特性的解析,会从数量的写入、查询和储存这七个维度来讲演,通过对其特征的辨析,来演绎对时序数据库的基本要求。

训练/评估

  1. 操练你协调的模型并评估.

# 创建模型定义文件并保存模型训练快照到如下路径:
#   - $CAFFE_ROOT/models/VGGNet/VOC0712/SSD_300x300/
# and job file, log file, and the python script in:
#   - $CAFFE_ROOT/jobs/VGGNet/VOC0712/SSD_300x300/
# 保存当前评估结果到:
#   - $HOME/data/VOCdevkit/results/VOC2007/SSD_300x300/
# 120K次迭代之后,应该可以达到77.*的mAP.
python examples/ssd/ssd_pascal.py

倘使不乐意自己磨练模型,可以在here下载预训练好的模型.注意是用PASCAL
VOC数据集磨练的。

透过分析ssd_pascal.py的源码,可以领略训练ssd模型需要多少个文件输入,分别是
train_data = "examples/VOC0712/VOC0712_trainval_lmdb"
test_data = "examples/VOC0712/VOC0712_test_lmdb"
name_size_file = "data/VOC0712/test_name_size.txt"
pretrain_model = "models/VGGNet/VGG_ILSVRC_16_layers_fc_reduced.caffemodel"
label_map_file = "data/VOC0712/labelmap_voc.prototxt"
train_net_file = "models/VGGNet/VOC0712/SSD_300x300/train.prototxt"
test_net_file = "models/VGGNet/VOC0712/SSD_300x300/test.prototxt"
deploy_net_file = "models/VGGNet/VOC0712/SSD_300x300/deploy.prototxt"
solver_file = "models/VGGNet/VOC0712/SSD_300x300/solver.prototxt"

其中,train_datatest_data是往日创设的LMDB数据库文件,用于磨炼和测试模型。name_size_file是事先创设的测试图像集的图像id和size文件,用于模型的测试。pretrain_model是base
network部分(VGG_16的卷积层)的预操练参数。label_map_file保存的是实体的name和label的投射文件,用于磨炼和测试。这多少个公文是事先都准备好的.

末端的多个文本,train_net_file test_net_file
deploy_net_filesolver_file是在ssd_pascal.py剧本中按照模型定义和教练方针参数自动生成的。例如,train_net_file,也就是train.prototxt,生成语句是shutil.copy(train_net_file, job_dir),具体的代码片段如下:

# Create train net.
net = caffe.NetSpec()
net.data, net.label = CreateAnnotatedDataLayer(train_data, batch_size=batch_size_per_device,
        train=True, output_label=True, label_map_file=label_map_file,
        transform_param=train_transform_param, batch_sampler=batch_sampler)

VGGNetBody(net, from_layer='data', fully_conv=True, reduced=True, dilated=True,
    dropout=False)

AddExtraLayers(net, use_batchnorm, lr_mult=lr_mult)

mbox_layers = CreateMultiBoxHead(net, data_layer='data', from_layers=mbox_source_layers,
        use_batchnorm=use_batchnorm, min_sizes=min_sizes, max_sizes=max_sizes,
        aspect_ratios=aspect_ratios, steps=steps, normalizations=normalizations,
        num_classes=num_classes, share_location=share_location, flip=flip, clip=clip,
        prior_variance=prior_variance, kernel_size=3, pad=1, lr_mult=lr_mult)

# Create the MultiBoxLossLayer.
name = "mbox_loss"
mbox_layers.append(net.label)
net[name] = L.MultiBoxLoss(*mbox_layers, multibox_loss_param=multibox_loss_param,
        loss_param=loss_param, include=dict(phase=caffe_pb2.Phase.Value('TRAIN')),
        propagate_down=[True, True, False, False])

with open(train_net_file, 'w') as f:
    print('name: "{}_train"'.format(model_name), file=f)
    print(net.to_proto(), file=f)
shutil.copy(train_net_file, job_dir)
  1. 应用新型模型快照评估模型.

  # 如果你需要对训练的模型进行评估,执行脚本:
  python examples/ssd/score_ssd_pascal.py
  1. 接纳webcam视频头测试模型. 注意: 按 <kbd>esc</kbd> 停止.

  # 连接webcam摄像头和预训练的模型进行演示,运行:
  python examples/ssd/ssd_pascal_webcam.py

Here
呈现了一个在
MSCOCO
数据集上锻练的模型SSD500的以身作则视频.

  1. 查看
    examples/ssd_detect.ipynb
    或者
    examples/ssd/ssd_detect.cpp
    咋样利用ssd模型检测物体. 查看
    examples/ssd/plot_detections.py
    怎样绘制 ssd_detect.cpp的检测结果.

  2. 假如利用其他数据集练习, 请参考data/OTHERDATASET 了然更多细节.
    近来帮助COCO 和 ILSVRC2016多少集. 指出接纳
    examples/ssd.ipynb
    检查新的数据集是否顺应要求.

除去博文

端点为DELETE /api/articles/:slug,需要身份验证,不需要请求体。同上,大家需要检查当前用户是否该博文的作者。假诺除去成功,我们回来状态码为204且无响应体的响应。

routes/api/articles.js中写入:

// +++
router.delete('/:slug', auth.required, loadCurrentUser, checkAuthor,
  (req, res, next) => {
    res.locals.article.remove()
      .then(() => res.sendStatus(204))
      .catch(next);
  });
// +++

module.exports = router;

到此,博文增查改删就满门贯彻了。

数据写入的性状

模型

在不同数量集上磨炼了模型以供下载. 为了复现论文Table
6中的结果,
每个模型文件夹内都富含一个.caffemodel 文件, 几个.prototxt 文件,
以及python脚本文件.

  1. PASCAL VOC 模型:

    • 07+12:
      SSD300*,
      SSD512*
    • 07++12:
      SSD300*,
      SSD512*
    • COCO\[1\]:
      SSD300*,
      SSD512*
    • 07+12+COCO:
      SSD300*,
      SSD512*
    • 07++12+COCO:
      SSD300*,
      SSD512*
  2. COCO 模型:

    • trainval35k:
      SSD300*,
      SSD512*
  3. ILSVRC 模型:

    • trainval1:
      SSD300*,
      SSD500

写入平稳、持续、高并发高吞吐:时序数据的写入是比较稳定的,这一点与使用数据不同,应用数据一般与利用的访问量成正比,而采纳的访问量平常存在波峰波谷。时序数据的暴发平常是以一个定位的日子频率发生,不会受任何因素的制约,其数据变动的速度是相对相比稳定的。时序数据是由每个个体独立生成,所以当私家数量过多时,平时写入的出现和吞吐量都是相比较高的,特别是在物联网场景下。写入并发和吞吐量,可以大概的经过个人数量和数据生成频率来总结,例假诺您有1000个民用以10秒的功能暴发多少,则你平分每秒暴发的出现和写入量就是100。
写多读少:时序数据上95%-99%的操作都是写操作,是超人的写多读少的数据。这与其数量特性相关,例如监控数据,你的督察项可能很多,可是你实在去读的或者相比少,平常只会关心多少个特定的机要目标或者在特定的情景下才会去读数据。
实时写入如今变化的多少,无更新:时序数据的写入是实时的,且每一次写入都是近日生成的数据,那与其数额变动的特征有关,因为其数据变化是随着时光推进的,而新生成的数目会实时的开展写入。数据写入无更新,在岁月这么些维度上,随着岁月的递进,每便数据都是新数据,不会存在旧数据的翻新,但是不消除人为的对数码做订正。

崭新的数据集

在头里的训练/评估的首先部分,我们介绍了怎样准备数据集:

  • dbname_trainval_lmdb
  • dbname_test_lmdb
  • test_name_size.txt
  • labelmap_dbname.prototxt
  • VGG_ILSVRC_16_layers_fc_reduced.caffemodel

崭新的数量代表不同的教练/测试图像,不同的object name
label映射关系,不同的网络模型定义参数。首先,咱们需要依照新的图像数据集生成模型的输入部分,也就是下边的六个公文。

  1. VGG_ILSVRC_16_layers_fc_reduced.caffemodel是预训练好的VGG_16的卷积层的参数,直接下载应用即可,这里不再介绍如何重新磨练VGG_16分类模型。

  2. labelmap_dbname.prototxt是标注文件中object的name和label的映射文件,一般品种不会太多,直接编写此文件即可。例如,一个可能的炫耀文件:

    item {
      name: "none_of_the_above"
      label: 0
      display_name: "background"
    }
    item {
      name: "Car"
      label: 1
      display_name: "car"
    }
    item {
      name: "Bus"
      label: 2
      display_name: "bus"
    }
    item {
      name: "Van"
      label: 3
      display_name: "van"
    }
    ...
    
  3. test_name_size.txt文本保留了独具测试图像的id height
    width信息,由create_list.sh本子完成创立。通过分析create_list.sh本子可了然,该脚本共创造了五个txt文件,分别是trainval.txt
    test.txtdbname_name_size.txt

    • trainval.txttest.txt中,每一行保存了图像文件的途径和图像标注文件的路径,中间以空格分开。片段如下:

    VOC2012/JPEGImages/2010_003429.jpg VOC2012/Annotations/2010_003429.xml
    VOC2007/JPEGImages/008716.jpg VOC2007/Annotations/008716.xml
    VOC2012/JPEGImages/2009_004804.jpg VOC2012/Annotations/2009_004804.xml
    VOC2007/JPEGImages/005293.jpg VOC2007/Annotations/005293.xml
    

    留神,trainval中的顺序是乱糟糟的,test中的顺序不必打乱。

    • test_name_size.txt文件是由.../caffe/get_image_size先后生成的,其源码位于.../caffe/tools/get_image_size.cpp中。这段程序的效益是按照test.txt中提供的测试图像的途径音讯和数目集根目录音讯(两段路径拼合得到图像的相对路径),自动测算每张图像的heightwidthget_image_size.cpp中的主旨代码段为:

    // Storing to outfile
    boost::filesystem::path root_folder(argv[1]);
    std::ofstream outfile(argv[3]);
    if (!outfile.good()) {
      LOG(FATAL) << "Failed to open file: " << argv[3];
    }
    int height, width;
    int count = 0;
    for (int line_id = 0; line_id < lines.size(); ++line_id) {
      boost::filesystem::path img_file = root_folder / lines[line_id].first;
      GetImageSize(img_file.string(), &height, &width);
      std::string img_name = img_file.stem().string();
      if (map_name_id.size() == 0) {
        outfile << img_name << " " << height << " " << width << std::endl;
      } else {
        CHECK(map_name_id.find(img_name) != map_name_id.end());
        int img_id = map_name_id.find(img_name)->second;
        outfile << img_id << " " << height << " " << width << std::endl;
      }
    
      if (++count % 1000 == 0) {
        LOG(INFO) << "Processed " << count << " files.";
      }
    }
    // write the last batch
    if (count % 1000 != 0) {
      LOG(INFO) << "Processed " << count << " files.";
    }
    outfile.flush();
    outfile.close();
    

    保存到test_name_size.txt中的内容片段如下:

    000001 500 353
    000002 500 335
    000003 375 500
    000004 406 500
    000006 375 500
    000008 375 500
    000010 480 354
    

    现在,trainval.txt
    test.txttest_name_size.txt的内容已经很清晰了,可以拔取现成的代码程序,适当修改图像数据集名称和路线就可以创设这七个文件。当然,也可以遵照自己的编程喜好,重新编写脚本生成符合地点格式的txt文件即可。

  4. dbname_trainval_lmdb
    转变该数据库文件的程序为create_data.sh,其基本代码是执行python脚本.../caffe/scripts/create_annoset.py,该脚本需要前边准备的
    labelmap_dbname.prototxttrainval.txt
    作为输入,以及多少个可安排项。
    .../caffe/scripts/create_annoset.py本子的中心代码是执行.../caffe/build/tools/convert_annoset程序。labelmap_dbname.prototxt

    trainval.txt就是为convert_annoset先后准备的,其源码在.../caffe/tools/convert_annoset.cpp中。创造并写入数据库的骨干代码片段如下:

// 创建一个新的数据库
scoped_ptr<db::DB> db(db::GetDB(FLAGS_backend));
db->Open(argv[3], db::NEW);
scoped_ptr<db::Transaction> txn(db->NewTransaction());

// 把数据存储到数据库
std::string root_folder(argv[1]);
AnnotatedDatum anno_datum;
Datum* datum = anno_datum.mutable_datum();
int count = 0;
int data_size = 0;
bool data_size_initialized = false;

for (int line_id = 0; line_id < lines.size(); ++line_id) {
  bool status = true;
  std::string enc = encode_type;
  if (encoded && !enc.size()) {
    // Guess the encoding type from the file name
    string fn = lines[line_id].first;
    size_t p = fn.rfind('.');
    if ( p == fn.npos )
      LOG(WARNING) << "Failed to guess the encoding of '" << fn << "'";
    enc = fn.substr(p);
    std::transform(enc.begin(), enc.end(), enc.begin(), ::tolower);
  }
  filename = root_folder + lines[line_id].first;
  if (anno_type == "classification") {
    label = boost::get<int>(lines[line_id].second);
    status = ReadImageToDatum(filename, label, resize_height, resize_width,
        min_dim, max_dim, is_color, enc, datum);
  } else if (anno_type == "detection") {
    labelname = root_folder + boost::get<std::string>(lines[line_id].second);
    status = ReadRichImageToAnnotatedDatum(filename, labelname, resize_height,
        resize_width, min_dim, max_dim, is_color, enc, type, label_type,
        name_to_label, &anno_datum);
    anno_datum.set_type(AnnotatedDatum_AnnotationType_BBOX);
  }
  if (status == false) {
    LOG(WARNING) << "Failed to read " << lines[line_id].first;
    continue;
  }
  if (check_size) {
    if (!data_size_initialized) {
      data_size = datum->channels() * datum->height() * datum->width();
      data_size_initialized = true;
    } else {
      const std::string& data = datum->data();
      CHECK_EQ(data.size(), data_size) << "Incorrect data field size "
          << data.size();
    }
  }
  // 序列化
  string key_str = caffe::format_int(line_id, 8) + "_" + lines[line_id].first;

  // 把数据Put到数据库
  string out;
  CHECK(anno_datum.SerializeToString(&out));
  txn->Put(key_str, out);

  if (++count % 1000 == 0) {
    // Commit db
    txn->Commit();
    txn.reset(db->NewTransaction());
    LOG(INFO) << "Processed " << count << " files.";
  }// end if
}//end for
// 写入最后一个batch的数据
if (count % 1000 != 0) {
  txn->Commit();
  LOG(INFO) << "Processed " << count << " files.";
}

这段代码中最首要的一行是对ReadRichImageToAnnotatedDatum()方法的调用,将图像文件和标注音讯一起写入到了anno_datum变量中,再系列化,提交到数据库缓存区,缓存到早晚数额的笔录后一回性写入数据库。

ReadRichImageToAnnotatedDatum()主意由Caffe提供,是caffe/src/util/io.cpp中定义的一个措施,该模式及其其调用的ReadImageToDatum方法和GetImageSize艺术源码如下:

bool ReadImageToDatum(const string& filename, const int label,
    const int height, const int width, const int min_dim, const int max_dim,
    const bool is_color, const std::string & encoding, Datum* datum) {
  cv::Mat cv_img = ReadImageToCVMat(filename, height, width, min_dim, max_dim,
                                    is_color);
  if (cv_img.data) {
    if (encoding.size()) {
      if ( (cv_img.channels() == 3) == is_color && !height && !width &&
          !min_dim && !max_dim && matchExt(filename, encoding) ) {
        datum->set_channels(cv_img.channels());
        datum->set_height(cv_img.rows);
        datum->set_width(cv_img.cols);
        return ReadFileToDatum(filename, label, datum);
      }
      EncodeCVMatToDatum(cv_img, encoding, datum);
      datum->set_label(label);
      return true;
    }
    CVMatToDatum(cv_img, datum);
    datum->set_label(label);
    return true;
  } else {
    return false;
  }
}

void GetImageSize(const string& filename, int* height, int* width) {
  cv::Mat cv_img = cv::imread(filename);
  if (!cv_img.data) {
    LOG(ERROR) << "Could not open or find file " << filename;
    return;
  }
  *height = cv_img.rows;
  *width = cv_img.cols;
}

bool ReadRichImageToAnnotatedDatum(const string& filename,
    const string& labelfile, const int height, const int width,
    const int min_dim, const int max_dim, const bool is_color,
    const string& encoding, const AnnotatedDatum_AnnotationType type,
    const string& labeltype, const std::map<string, int>& name_to_label,
    AnnotatedDatum* anno_datum) {
  // Read image to datum.
  bool status = ReadImageToDatum(filename, -1, height, width,
                                 min_dim, max_dim, is_color, encoding,
                                 anno_datum->mutable_datum());
  if (status == false) {
    return status;
  }
  anno_datum->clear_annotation_group();
  if (!boost::filesystem::exists(labelfile)) {
    return true;
  }
  switch (type) {
    case AnnotatedDatum_AnnotationType_BBOX:
      int ori_height, ori_width;
      GetImageSize(filename, &ori_height, &ori_width);
      if (labeltype == "xml") {
        return ReadXMLToAnnotatedDatum(labelfile, ori_height, ori_width,
                                       name_to_label, anno_datum);
      } else if (labeltype == "json") {
        return ReadJSONToAnnotatedDatum(labelfile, ori_height, ori_width,
                                        name_to_label, anno_datum);
      } else if (labeltype == "txt") {
        return ReadTxtToAnnotatedDatum(labelfile, ori_height, ori_width,
                                       anno_datum);
      } else {
        LOG(FATAL) << "Unknown label file type.";
        return false;
      }
      break;
    default:
      LOG(FATAL) << "Unknown annotation type.";
      return false;
  }
}

可以见到在上头的法子中延续调用了io.cpp中的六个章程ReadFileToDatumReadXMLToAnnotatedDatum,分别把图像和图像的标注XML写入到了anno_datum中。其中,图像保存到了anno_datummutable_datum中,XML标注信息被保留到了anno_datumanno_group->anno->bbox中,anno_group还保存了label等信息。

  1. dbname_test_lmdb
    4.dbname_trainval_lmdb
  2. 使用examples/ssd.ipynb核实上边生成的文书的没错

\[1\]We use
examples/convert_model.ipynb
to extract a VOC model from a pretrained COCO model.

相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*
*
Website