SSD(Single Shot MultiBox Detector)源碼分析和使用指南

本項(xiàng)目地址:caffe/ssd

SSD: Single Shot MultiBox Detector

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

簡(jiǎn)介

SSD是使用單個(gè)網(wǎng)絡(luò)進(jìn)行物體檢測(cè)任務(wù)的統(tǒng)一框架. 你可以使用本代碼訓(xùn)練/評(píng)估物體檢測(cè)任務(wù). 更多細(xì)節(jié)請(qǐng)見(jiàn) arXiv paper 以及 slide.

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
測(cè)試對(duì)比

Note: SSD300* and SSD512* are the latest models. Current code should reproduce these results.

目錄

  1. 安裝
  2. 預(yù)備
  3. 訓(xùn)練/評(píng)估
  4. 模型
  5. 全新的數(shù)據(jù)集

安裝

  1. 下載代碼衩婚。假設(shè)把Caffe克隆到目錄$CAFFE_ROOT
  git clone https://github.com/weiliu89/caffe.git
  cd caffe
  git checkout ssd
  1. Build 代碼. 按照 Caffe instruction 安裝
    必要的packages,然后build吧趣。
# 根據(jù)Caffe安裝的方式修改Makefile.config。
cp Makefile.config.example Makefile.config
make -j8
# 確保include $CAFFE_ROOT/python到PYTHONPATH環(huán)境變量?jī)?nèi).
make py
make test -j8
# 運(yùn)行測(cè)試早处,可選   
make runtest -j8

預(yù)備

  1. 下載 fully convolutional reduced (atrous) VGGNet. 假設(shè)文件被下載到了$CAFFE_ROOT/models/VGGNet/目錄

  2. 下載VOC2007和VOC2012數(shù)據(jù)集.
    對(duì)Pascal VOC數(shù)據(jù)集的簡(jiǎn)介:

    pascal_voc2012_detection_table.jpg

    假設(shè)下載到了$HOME/data/目錄

  # 下載數(shù)據(jù).
  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
  # 解壓數(shù)據(jù).
  tar -xvf VOCtrainval_11-May-2012.tar
  tar -xvf VOCtrainval_06-Nov-2007.tar
  tar -xvf VOCtest_06-Nov-2007.tar

VOC 2007年的數(shù)據(jù)分為VOCtrainval和VOCtest兩個(gè)tar包县踢,VOC 2012年的數(shù)據(jù)只有VOCtrainval一個(gè)tar包衔峰,如下

VOC0712的三個(gè)tar包

解壓后,2007和2012兩年的數(shù)據(jù)在VOCdevkit目錄的VOC2007VOC2012兩個(gè)子目錄中验庙。每個(gè)子目錄下,分別包含了五個(gè)文件夾社牲,分別是Annotations ImageSets JPEGImages SegmentationClass 以及 SegmentationObject粪薛。對(duì)于SSD的Object任務(wù),需要使用Annotations中的xml標(biāo)注文件搏恤,ImagesSets/Main/目錄中的trainval.txttest.txt违寿,以及JPEGImages目錄下的圖像湃交。

VOC2007解壓

VOC2012解壓后

  1. 創(chuàng)建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格式如圖搞莺,文件內(nèi)容是圖像的路徑和標(biāo)注文件的路徑,中間用空格分隔開(kāi):

trainval.txt

生成的test_name_size.txt是測(cè)試圖像的id heightwidth

test_name_size.txt

最終掂咒,生成trainval和test兩個(gè)lmdb數(shù)據(jù)庫(kù)才沧,分別用來(lái)訓(xùn)練和測(cè)試SSD模型。

voc0712 trainval and test lmdbs

trainval LMDB

trainval lmdb

同時(shí)绍刮,在.../caffe/examples/VOC0712/路徑下保存了上面兩個(gè)lmdb數(shù)據(jù)庫(kù)的鏈接温圆,截圖如下:

voc0712_lmdb_link

訓(xùn)練/評(píng)估

  1. 訓(xùn)練你自己的模型并評(píng)估.
# 創(chuàng)建模型定義文件并保存模型訓(xùn)練快照到如下路徑:
#   - $CAFFE_ROOT/models/VGGNet/VOC0712/SSD_300x300/
# and job file, log file, and the python script in:
#   - $CAFFE_ROOT/jobs/VGGNet/VOC0712/SSD_300x300/
# 保存當(dāng)前評(píng)估結(jié)果到:
#   - $HOME/data/VOCdevkit/results/VOC2007/SSD_300x300/
# 120K次迭代之后,應(yīng)該可以達(dá)到77.*的mAP.
python examples/ssd/ssd_pascal.py

如果不樂(lè)意自己訓(xùn)練模型孩革,可以在here下載預(yù)訓(xùn)練好的模型.注意是用PASCAL VOC數(shù)據(jù)集訓(xùn)練的岁歉。

通過(guò)分析ssd_pascal.py的源碼,可以知道訓(xùn)練ssd模型需要幾個(gè)文件輸入膝蜈,分別是
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是之前創(chuàng)建的LMDB數(shù)據(jù)庫(kù)文件,用于訓(xùn)練和測(cè)試模型彬檀。name_size_file是之前創(chuàng)建的測(cè)試圖像集的圖像id和size文件帆啃,用于模型的測(cè)試。pretrain_model是base network部分(VGG_16的卷積層)的預(yù)訓(xùn)練參數(shù)窍帝。label_map_file保存的是物體的name和label的映射文件努潘,用于訓(xùn)練和測(cè)試。這五個(gè)文件是之前都準(zhǔn)備好的.

后面的四個(gè)文件坤学,train_net_file test_net_file deploy_net_filesolver_file是在ssd_pascal.py腳本中根據(jù)模型定義和訓(xùn)練策略參數(shù)自動(dòng)生成的疯坤。例如,train_net_file深浮,也就是train.prototxt宝穗,生成語(yǔ)句是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. 使用最新模型快照評(píng)估模型.
  # 如果你需要對(duì)訓(xùn)練的模型進(jìn)行評(píng)估旋膳,執(zhí)行腳本:
  python examples/ssd/score_ssd_pascal.py
  1. 使用webcam攝像頭測(cè)試模型. 注意: 按 <kbd>esc</kbd> 停止.
  # 連接webcam攝像頭和預(yù)訓(xùn)練的模型進(jìn)行演示收夸,運(yùn)行:
  python examples/ssd/ssd_pascal_webcam.py

Here 展示了一個(gè)在 MSCOCO 數(shù)據(jù)集上訓(xùn)練的模型SSD500的演示視頻.

  1. 查看 examples/ssd_detect.ipynb 或者 examples/ssd/ssd_detect.cpp 如何使用ssd模型檢測(cè)物體. 查看 examples/ssd/plot_detections.py 如何繪制 ssd_detect.cpp的檢測(cè)結(jié)果.

  2. 如果使用其他數(shù)據(jù)集訓(xùn)練, 請(qǐng)參考data/OTHERDATASET 了解更多細(xì)節(jié). 目前支持COCO 和 ILSVRC2016數(shù)據(jù)集. 建議使用 examples/ssd.ipynb 檢查新的數(shù)據(jù)集是否符合要求.

模型

在不同數(shù)據(jù)集上訓(xùn)練了模型以供下載. 為了復(fù)現(xiàn)論文Table 6中的結(jié)果, 每個(gè)模型文件夾內(nèi)都包含一個(gè).caffemodel 文件, 幾個(gè).prototxt 文件, 以及python腳本文件.

  1. PASCAL VOC 模型:

  2. COCO 模型:

  3. ILSVRC 模型:

全新的數(shù)據(jù)集

在之前的訓(xùn)練/評(píng)估的第一部分,我們介紹了如何準(zhǔn)備數(shù)據(jù)集:

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

全新的數(shù)據(jù)意味著不同的訓(xùn)練/測(cè)試圖像布卡,不同的object name label映射關(guān)系雨让,不同的網(wǎng)絡(luò)模型定義參數(shù)。首先忿等,我們需要根據(jù)新的圖像數(shù)據(jù)集生成模型的輸入部分栖忠,也就是上面的五個(gè)文件。

  1. VGG_ILSVRC_16_layers_fc_reduced.caffemodel是預(yù)訓(xùn)練好的VGG_16的卷積層的參數(shù),直接下載使用即可庵寞,這里不再介紹如何重新訓(xùn)練VGG_16分類(lèi)模型狸相。

  2. labelmap_dbname.prototxt是標(biāo)注文件中object的name和label的映射文件,一般類(lèi)別不會(huì)太多捐川,直接編寫(xiě)此文件即可脓鹃。例如,一個(gè)可能的映射文件:

    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文件保存了所有測(cè)試圖像的id height width信息属拾,由create_list.sh腳本完成創(chuàng)建将谊。通過(guò)分析create_list.sh腳本可知道,該腳本共創(chuàng)建了三個(gè)txt文件渐白,分別是trainval.txt test.txtdbname_name_size.txt尊浓。

    • trainval.txttest.txt中,每一行保存了圖像文件的路徑和圖像標(biāo)注文件的路徑纯衍,中間以空格分開(kāi)栋齿。片段如下:
    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中歌亲。這段程序的作用是根據(jù)test.txt中提供的測(cè)試圖像的路徑信息和數(shù)據(jù)集根目錄信息(兩段路徑拼合得到圖像的絕對(duì)路徑)菇用,自動(dòng)計(jì)算每張圖像的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中的內(nèi)容片段如下:

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

    現(xiàn)在陷揪,trainval.txt test.txttest_name_size.txt的內(nèi)容已經(jīng)很清晰了惋鸥,可以利用現(xiàn)成的代碼程序,適當(dāng)修改圖像數(shù)據(jù)集名稱(chēng)和路徑就可以創(chuàng)建這三個(gè)文件悍缠。當(dāng)然卦绣,也可以根據(jù)自己的編程喜好,重新編寫(xiě)腳本生成符合上面格式的txt文件即可飞蚓。

  4. dbname_trainval_lmdb
    生成該數(shù)據(jù)庫(kù)文件的程序?yàn)?code>create_data.sh滤港,其核心代碼是執(zhí)行python腳本.../caffe/scripts/create_annoset.py,該腳本需要之前準(zhǔn)備的 labelmap_dbname.prototxttrainval.txt 作為輸入趴拧,以及幾個(gè)可配置項(xiàng)溅漾。
    .../caffe/scripts/create_annoset.py腳本的核心代碼是執(zhí)行.../caffe/build/tools/convert_annoset程序。labelmap_dbname.prototxttrainval.txt就是為convert_annoset程序準(zhǔn)備的著榴,其源碼在.../caffe/tools/convert_annoset.cpp中樟凄。創(chuàng)建并寫(xiě)入數(shù)據(jù)庫(kù)的核心代碼片段如下:

// 創(chuàng)建一個(gè)新的數(shù)據(jù)庫(kù)
scoped_ptr<db::DB> db(db::GetDB(FLAGS_backend));
db->Open(argv[3], db::NEW);
scoped_ptr<db::Transaction> txn(db->NewTransaction());

// 把數(shù)據(jù)存儲(chǔ)到數(shù)據(jù)庫(kù)
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;

  // 把數(shù)據(jù)Put到數(shù)據(jù)庫(kù)
  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
// 寫(xiě)入最后一個(gè)batch的數(shù)據(jù)
if (count % 1000 != 0) {
  txn->Commit();
  LOG(INFO) << "Processed " << count << " files.";
}

這段代碼中最重要的一行是對(duì)ReadRichImageToAnnotatedDatum()方法的調(diào)用,將圖像文件和標(biāo)注信息一起寫(xiě)入到了anno_datum變量中兄渺,再序列化,提交到數(shù)據(jù)庫(kù)緩存區(qū),緩存到一定數(shù)量的記錄后一次性寫(xiě)入數(shù)據(jù)庫(kù)挂谍。

ReadRichImageToAnnotatedDatum()方法由Caffe提供叔壤,是caffe/src/util/io.cpp中定義的一個(gè)方法,該方法及其其調(diào)用的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;
  }
}

可以看到在上面的方法中繼續(xù)調(diào)用了io.cpp中的兩個(gè)方法ReadFileToDatumReadXMLToAnnotatedDatum口叙,分別把圖像和圖像的標(biāo)注XML寫(xiě)入到了anno_datum中炼绘。其中,圖像保存到了anno_datummutable_datum中妄田,XML標(biāo)注信息被保存到了anno_datumanno_group->anno->bbox中俺亮,anno_group還保存了label等信息。

  1. dbname_test_lmdb
    4.dbname_trainval_lmdb
  2. 使用examples/ssd.ipynb核實(shí)上面生成的文件的正確性

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末疟呐,一起剝皮案震驚了整個(gè)濱河市脚曾,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌启具,老刑警劉巖本讥,帶你破解...
    沈念sama閱讀 211,376評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異鲁冯,居然都是意外死亡拷沸,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門(mén)薯演,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)撞芍,“玉大人,你說(shuō)我怎么就攤上這事跨扮⌒蛭蓿” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,966評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵好港,是天一觀的道長(zhǎng)愉镰。 經(jīng)常有香客問(wèn)我,道長(zhǎng)钧汹,這世上最難降的妖魔是什么丈探? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,432評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮拔莱,結(jié)果婚禮上碗降,老公的妹妹穿的比我還像新娘。我一直安慰自己塘秦,他們只是感情好讼渊,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,519評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著尊剔,像睡著了一般爪幻。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,792評(píng)論 1 290
  • 那天挨稿,我揣著相機(jī)與錄音仇轻,去河邊找鬼。 笑死奶甘,一個(gè)胖子當(dāng)著我的面吹牛篷店,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播臭家,決...
    沈念sama閱讀 38,933評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼疲陕,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了钉赁?” 一聲冷哼從身側(cè)響起蹄殃,我...
    開(kāi)封第一講書(shū)人閱讀 37,701評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎橄霉,沒(méi)想到半個(gè)月后窃爷,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,143評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡姓蜂,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,488評(píng)論 2 327
  • 正文 我和宋清朗相戀三年按厘,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片钱慢。...
    茶點(diǎn)故事閱讀 38,626評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡逮京,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出束莫,到底是詐尸還是另有隱情懒棉,我是刑警寧澤,帶...
    沈念sama閱讀 34,292評(píng)論 4 329
  • 正文 年R本政府宣布览绿,位于F島的核電站策严,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏饿敲。R本人自食惡果不足惜妻导,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,896評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望怀各。 院中可真熱鬧倔韭,春花似錦、人聲如沸瓢对。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,742評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)硕蛹。三九已至醇疼,卻和暖如春硕并,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背僵腺。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工鲤孵, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人辰如。 一個(gè)月前我還...
    沈念sama閱讀 46,324評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像贵试,于是被迫代替她去往敵國(guó)和親琉兜。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,494評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容