久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

Caffe: MNIST 數(shù)據(jù)集格式轉(zhuǎn)換,、用 python 讀寫(xiě) LMDB 數(shù)據(jù)庫(kù)

 學(xué)海無(wú)涯GL 2017-02-22

Preface

這兩天概覽了一下卜居(趙永科)的《深度學(xué)習(xí) 21天實(shí)戰(zhàn)caffe》,進(jìn)入深度學(xué)習(xí)挺長(zhǎng)時(shí)間的了,。文章也看了不少,,Caffe、Theano,、Torch 也都用過(guò),。其實(shí)個(gè)人認(rèn)為,這本書(shū)對(duì)于已經(jīng)深入這個(gè)領(lǐng)域已定時(shí)間的人來(lái)說(shuō),,幫助不大,。本書(shū)講述的只是“術(shù)“,有點(diǎn)像深度學(xué)習(xí)的說(shuō)明書(shū),,講的很淺,。

但是翻了一翻,還是有點(diǎn)收獲的,,這個(gè) MNIST 手寫(xiě)數(shù)字識(shí)別是深度學(xué)習(xí)入門很經(jīng)典的例子,。基本上所有的深度學(xué)習(xí)框架,,在讓初學(xué)者入門使用的時(shí)候都有這個(gè)例子,。

我一直對(duì) Caffe 中使用的 LMDB、LEVELDB 數(shù)據(jù)組織比較疑惑,,很多時(shí)候不明白該怎么樣組織圖像數(shù)據(jù),、以及其對(duì)應(yīng)的標(biāo)簽。之前都是按照別人的代碼生成的,,自己其實(shí)懵懵的,。所以,我想通過(guò) MNIST 輸入數(shù)據(jù)生成過(guò)程,,熟悉一下 LMDB,、LEVELDB 的基本使用方法,。

熟悉了 C++ 版本的轉(zhuǎn) lmdb 方式后,我會(huì)解析一下 Python 版本的 lmdb 轉(zhuǎn)換過(guò)程,。

最后 Reference 部分,,列出了我這里面參考的文章。


MNIST 及其轉(zhuǎn) LMDB 數(shù)據(jù)庫(kù)源碼 create_mnist_data

MINIST(Mixed National Institute of Stanfords and Technology)是一個(gè)大型的手寫(xiě)數(shù)字數(shù)據(jù)庫(kù),,廣泛用于機(jī)器學(xué)習(xí)領(lǐng)域的訓(xùn)練和測(cè)試,,由紐約大學(xué) Yann LeCun 教授整理。MNIST 包括 60000 個(gè)訓(xùn)練集和 10000 個(gè)測(cè)試集,,每張圖都已經(jīng)進(jìn)行尺寸歸一化,,數(shù)字居中處理,固定尺寸為 28×28,。如下圖所示:

這里寫(xiě)圖片描述


MNIST 數(shù)據(jù)格式描述

MNIST 具體的文件格式描述如下面的表所示:

MNIST 原始數(shù)據(jù)文件

MNIST 原始數(shù)據(jù)文件

訓(xùn)練集圖片文件格式描述(train-images-idx3-ubyte)

這里寫(xiě)圖片描述

訓(xùn)練集標(biāo)簽文件格式描述(train-labels-idx1-ubyte)

這里寫(xiě)圖片描述

測(cè)試集圖片文件格式描述(t10k-images-idx3-ubyte)

這里寫(xiě)圖片描述

測(cè)試集標(biāo)簽文件格式描述(t10k-labels-idx1-ubyte)

這里寫(xiě)圖片描述

注意:圖片文件中像素按行組織,像素值 0 表示背景(白色),,像素值 255 表示前景(黑色),。

轉(zhuǎn)換格式、create_mnist_data.cpp 源碼解析

先說(shuō)一下 Caffe 為什么采用 LMDB,、LEVELDB,,而不是直接讀取原始數(shù)據(jù)?

原因是,,一方面,,數(shù)據(jù)類型多種多樣,有二進(jìn)制文件,、文本文件,、編碼后的圖像文件(如 JPEG、PNG,、網(wǎng)絡(luò)爬取的數(shù)據(jù)等),,不可能用一套代碼實(shí)現(xiàn)所有類型的輸入數(shù)據(jù)讀取,轉(zhuǎn)換為統(tǒng)一格式可以簡(jiǎn)化數(shù)據(jù)讀取層的實(shí)現(xiàn),;

另一方面,,使用 LMDB、LEVELDB 可以提高磁盤 IO 利用率,。



下載到的原始數(shù)據(jù)為二進(jìn)制文件,,需要轉(zhuǎn)換為 LEVELDB 或 LMDB 才能被 Caffe 識(shí)別。
我們 Git 得到的 Caffe 中,,在 examples/mnist/ 下有一個(gè)腳本文件:create_mnist.sh ,,這個(gè)就可以將原始的二進(jìn)制數(shù)據(jù),生成 LMDB 格式數(shù)據(jù),。
運(yùn)行后,,會(huì)生成 examples/mnist/mnist_train_lmdb/examples/mnist/mnist_test_lmdb/ 這兩個(gè)目錄。每個(gè)目錄下都有兩個(gè)文件:data.mdblock.mdb

看一下腳本文件:create_mnist.sh 里面是什么:

#!/usr/bin/env sh
# This script converts the mnist data into lmdb/leveldb format,
# depending on the value assigned to $BACKEND.

EXAMPLE=examples/mnist
DATA=data/mnist
BUILD=build/examples/mnist

BACKEND="lmdb"

echo "Creating ${BACKEND}..."

rm -rf $EXAMPLE/mnist_train_${BACKEND}
rm -rf $EXAMPLE/mnist_test_${BACKEND}

$BUILD/convert_mnist_data.bin $DATA/train-images-idx3-ubyte   $DATA/train-labels-idx1-ubyte $EXAMPLE/mnist_train_${BACKEND} --backend=${BACKEND}
$BUILD/convert_mnist_data.bin $DATA/t10k-images-idx3-ubyte   $DATA/t10k-labels-idx1-ubyte $EXAMPLE/mnist_test_${BACKEND} --backend=${BACKEND}

echo "Done."
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21


create_mnist_data.cpp 源碼解析

可以看到,,上面腳本最核心的部分,,就是調(diào)用 convert_mnist_data.bin 這個(gè)可執(zhí)行程序,對(duì)應(yīng)的源文件為 examples/mnist/convert_mnist_data.cpp,,對(duì)這個(gè)源代碼的解讀如下,,深入這段代碼可以更清楚的了解 LMDB 是如何生成的。

// 這段代碼將 MNIST 數(shù)據(jù)集轉(zhuǎn)換為(默認(rèn)的)lmdb 或者 leveldb(--backend=leveldb) 格式,,用于在使用 caffe 的時(shí)候讀取數(shù)據(jù)
// 使用方法:
//    convert_mnist_data [FLAGS] input_image_file input_label_file output_db_file

// gflags: 命令行參數(shù)解析頭文件
#include <gflags/gflags.h> 
// glog: 記錄程序日志頭文件
#include <glog/logging.h>
// 解析 *.prototxt 文件
#include <google/protobuf/text_format.h>

#include <leveldb/db.h>
#include <leveldb/write_batch.h>
#include <lmdb.h>
#include <stdint.h>
#include <sys/stat.h>

#include <fstream>  // NOLINT(readability/streams)
#include <string>

// 解析caffe中proto類型文件的頭文件
#include "caffe/proto/caffe.pb.h"

using namespace caffe; // NOLINT(build/namespace)
using std::string;

// GFLAGS 工具定義命令行選項(xiàng) backend, 默認(rèn)值為 lmdb, 即: --backend=lmdb
DEFINE_string(backend, "lmdb", "The backend for storing the result");

// 大小端轉(zhuǎn)換, MNIST 原始數(shù)據(jù)文件中 32 位整型值為大端存儲(chǔ), C/C++ 變量為小端存儲(chǔ),,因此需要加入轉(zhuǎn)換機(jī)制
uint32_t swap_endian(uint32_t val) {
    val = ((val << 8) & 0xFF00FF00) | ((val >> 8) & 0xFF00FF);
    return (val << 16) | (val >> 16);
}

// 轉(zhuǎn)換數(shù)據(jù)集函數(shù)
void convert_dataset(const char* image_filename, const char* label_filename,
        const char* db_path, const string& db_backend) {

    // 用 C++ 輸入文件流以二進(jìn)制方式打開(kāi)
    // 定義, 打開(kāi)圖像文件 對(duì)象: image_file(讀入的文件名, 讀入方式), 此處以二進(jìn)制的方式
    std::ifstream image_file(image_filename, std::ios::in | std::ios::binary);
    // 定義, 打開(kāi)標(biāo)簽文件 對(duì)象: label_file(讀入的文件名, 讀入方式), 此處以二進(jìn)制的方式
    std::ifstream label_file(label_filename, std::ios::in | std::ios::binary);

    // CHECK: 用于檢測(cè)文件能否正常打開(kāi)函數(shù)
    CHECK(image_file) << "Unable to open file " << image_filename;
    CHECK(label_file) << "Unable to open file " << label_filename;

    // 讀取魔數(shù)與基本信息
    // uint32_t 用 typedef 來(lái)自定義的一種數(shù)據(jù)類型, unsigned int32, 每個(gè)int32整數(shù)占用4個(gè)字節(jié), 這樣做是為了程序的可擴(kuò)展性
    uint32_t magic; // 魔數(shù)
    uint32_t num_items; // 文件包含條目總數(shù) 
    uint32_t num_labels; // 標(biāo)簽值
    uint32_t rows; // 行數(shù)
    uint32_t cols; // 列數(shù)

    // 讀取魔數(shù): magic
    // image_file.read( 讀取內(nèi)容的指針, 讀取的字節(jié)數(shù) ) , magic 是一個(gè) int32 類型的整數(shù),每個(gè)占 4 個(gè)字節(jié),,所以這里指定為 4
    // reinterpret_cast 為 C++ 中定義的強(qiáng)制轉(zhuǎn)換符, 這里把 &magic, 即 magic 的地址(一個(gè) 16 進(jìn)制的數(shù)), 轉(zhuǎn)變成 char 類型的指針
    image_file.read(reinterpret_cast<char*>(&magic), 4);
    // 大端到小端的轉(zhuǎn)換
    magic = swap_endian(magic);
    // 校驗(yàn)圖像文件中魔數(shù)是否為 2051, 不是則報(bào)錯(cuò)
    CHECK_EQ(magic, 2051) << "Incorrect image file magic.";
    // 同理, 校驗(yàn)標(biāo)簽文件中的魔數(shù)是否為 2049, 不是則報(bào)錯(cuò)
    label_file.read(reinterpret_cast<char*>(&magic), 4);
    magic = swap_endian(magic);
    CHECK_EQ(magic, 2049) << "Incorrect label file magic.";

    // 讀取圖片的數(shù)量: num_items
    image_file.read(reinterpret_cast<char*>(&num_items), 4);
    num_items = swap_endian(num_items); // 大端到小端轉(zhuǎn)換
    // 讀取圖片標(biāo)簽的數(shù)量
    label_file.read(reinterpret_cast<char*>(&num_labels), 4);
    num_labels = swap_endian(num_labels); // 大端到小端轉(zhuǎn)換
    // 圖片數(shù)量應(yīng)等于其標(biāo)簽數(shù)量, 檢查兩者是否相等
    CHECK_EQ(num_items, num_labels);
    // 讀取圖片的行大小
    image_file.read(reinterpret_cast<char*>(&rows), 4);
    rows = swap_endian(rows); // 大端到小端轉(zhuǎn)換
    // 讀取圖片的列大小
    image_file.read(reinterpret_cast<char*>(&cols), 4);
    cols = swap_endian(cols); // 大端到小端轉(zhuǎn)換

    // lmdb 相關(guān)句柄
    MDB_env *mdb_env;
    MDB_dbi mdb_dbi;
    MDB_val mdb_key, mdb_data;
    MDB_txn *mdb_txn;
    // leveldb 相關(guān)句柄
    leveldb::DB* db;
    leveldb::Options options;
    options.error_if_exists = true;
    options.create_if_missing = true;
    options.write_buffer_size = 268435456;
    level::WriteBatch* batch = NULL;

    // 打開(kāi) db
    if (db_backend == "leveldb") { // leveldb
        LOG(INFO) << "Opening leveldb " << db_path;
        leveldb::Status status = leveldb::DB::Open(
          options, db_path, &db);
        CHECK(status.ok()) << "Failed to open leveldb " << db_path << ". Is it already existing?";
        batch = new leveldb::WriteBatch();
    }else if (db_backend == "lmdb") { // lmdb
        LOG(INFO) << "Opening lmdb " << db_path;
        CHECK_EQ(mkdir(db_path, 0744), 0) << "mkdir " << db_path << "failed";
        CHECK_EQ(mdb_env_create(&mdb_env), MDB_SUCCESS) << "mdb_env_create failed";
        CHECK_EQ(mdb_env_set_mapsize(mdb_env, 1099511627776), MDB_SUCCESS) << "mdb_env_set_mapsize failed"; // 1TB
        CHECK_EQ(mdb_env_open(mdb_env, db_path, 0, 0664), MDB_SUCCESS) << "mdb_env_open_failed";
        CHECK_EQ(mdb_txn_begin(mdb_env, NULL, 0, &mdb_txn), MDB_SUCCESS) << "mdb_txn_begin failed";
        CHECK_EQ(mdb_open(mdb_txn, NULL, 0, &mdb_dbi), MDB_SUCCESS) << "mdb_open failed. Does the lmdb already exist?";
    } else {
        LOG(FATAL) << "Unknown db backend " << db_backend;
    }

    // 將讀取數(shù)據(jù)保存至 db
    char label;
    char* pixels = new char[rows * cols];
    int count = 0;
    const int kMaxKeyLength = 10;
    char key_cstr[kMaxKeyLength];
    string value;

    // 設(shè)置datum數(shù)據(jù)對(duì)象的結(jié)構(gòu),,其結(jié)構(gòu)和源圖像結(jié)構(gòu)相同
    Datum datum;
    datum.set_channels(1);
    datum.set_height(rows);
    datum.set_width(cols);

    // 輸出 Log, 輸出圖片總數(shù)
    LOG(INFO) << "A total of " << num_items << " items.";
    // 輸出 Log, 輸出圖片的行、列大小
    LOG(INFO) << "Rows: " << rows << " Cols: " << cols;

    // 讀取圖片數(shù)據(jù)以及 label 存入 protobuf 定義好的數(shù)據(jù)結(jié)構(gòu)中,
    // 序列化成字符串儲(chǔ)存到數(shù)據(jù)庫(kù)中,
    // 這里為了減少單次操作帶來(lái)的帶寬成本(驗(yàn)證數(shù)據(jù)包完整等), 
    // 每 1000 次執(zhí)行一次操作
    for (int item_id = 0; item_id < num_items; ++item_id) {
        // 從數(shù)據(jù)中讀取 rows * cols 個(gè)字節(jié), 圖像中一個(gè)像素值(應(yīng)該是 int8 類型)用一個(gè)字節(jié)表示即可
        image_file.read(pixels, rows * cols);
        // 讀取標(biāo)簽
        label_file.read(&label, 1);
        // set_data 函數(shù), 把源圖像值放入 datum 對(duì)象
        datum.set_data(pixels, rows*cols);
        // set_label 函數(shù), 把標(biāo)簽值放入 datum
        datum.set_label(label);

        // snprintf(str1, size_t, "format", str), 把 str 按照 format 的格式以字符串的形式寫(xiě)入 str1, size_t 表示寫(xiě)入的字符個(gè)數(shù)    
        // 這里是把 item_id 轉(zhuǎn)換成 8 位長(zhǎng)度的十進(jìn)制整數(shù),,然后在變成字符串復(fù)制給 key_str, 如:item_id=1500(int), 則 key_cstr = 00015000(string, \0為字符串結(jié)束標(biāo)志)
        snprintf(key_cstr, kMaxKeyLength, "%08d", item_id);
        datum.SerializeToString(&value);
        // 感覺(jué)是將 datum 中的值序列化成字符串,,保存在變量 value 內(nèi),通過(guò)指針來(lái)給 value 賦值
        string keystr(key_cstr);

        // 放到數(shù)據(jù)庫(kù)中
        if (db_backend == "leveldb") { // leveldb
            // 通過(guò) batch 中的子方法 Put, 把數(shù)據(jù)寫(xiě)入 datum 中(此時(shí)在內(nèi)存中)
            batch->Put(keystr, value);
        } else if (db_backend == "lmdb") { // lmdb
            // mv 應(yīng)該是 move value, 應(yīng)該是和 write() 和 read() 函數(shù)文件讀寫(xiě)的方式一樣, 以固定的子節(jié)長(zhǎng)度按照地址進(jìn)行讀寫(xiě)操作
            // 獲取 value 的子節(jié)長(zhǎng)度, 類似 sizeof() 函數(shù)
            mdb_data.mv_size = value.size()
            // 把 value 的首個(gè)字符地址轉(zhuǎn)換成空類型的指針
            mdb_data.mv_data = reinterpret_cast<void*>(&value[0]);
            mdb_key.mv_size = keystr.size();
            mdb_key.mv_data = reinterpret_cast<void*>(&keystr[0]);
            // 通過(guò) mdb_put 函數(shù)把 mdb_key 和 mdb_data 所指向的數(shù)據(jù),  寫(xiě)入到 mdb_dbi
            CHECK_EQ(mdb_put(mdb_txn, mdb_dbi, &mdb_key, &mdb_data, 0), MDB_SUCCESS) << "mdb_put failed";
        } else {
            LOG(FATAL) << "Unknown db backend " << db_back_end;
        }

        // 把 db 數(shù)據(jù)寫(xiě)入硬盤
        // 選擇 1000 個(gè)樣本放入一個(gè) batch 中,,通過(guò) batch 以批量的方式把數(shù)據(jù)寫(xiě)入硬盤
        // 寫(xiě)入硬盤通過(guò) db.write() 函數(shù)來(lái)實(shí)現(xiàn)
        if (++count % 1000 == 0) {
            // 批量提交更改
            if(db_backend == "leveldb") { // leveldb
                // 把batch寫(xiě)入到 db 中,,然后刪除 batch 并重新創(chuàng)建
                db->Write(leveldb::WriteOptions(), batch);
                delete batch;
                batch = new leveldb::WriteBatch();
            } else if (db_backend == "lmdb") { // lmdb
                // 通過(guò) mdb_txn_commit 函數(shù)把 mdb_txn 數(shù)據(jù)寫(xiě)入到硬盤
                CHECK_EQ(mdb_txn_commit(mdb_txn), MDB_SUCCESS) << "mdb_txn_commit failed";
                // 重新設(shè)置 mdb_txn 的寫(xiě)入位置, 追加(繼續(xù))寫(xiě)入
                CHECK_EQ(mdb_txn_begin(mdb_env, NULL, 0, &mdb_txn), MDB_SUCCESS) << "mdb_txn_begin failed";
            } else {
                LOG(FATAL) << "Unknown db backend " << db_backend;
            }
        } // if (++count % 1000 == 0) 

    } // for (int item_id = 0; item_id < num_items; ++item_id)

    // 寫(xiě)最后一個(gè) batch 
    if (count % 1000 != 0) {
        if (db_backend == "leveldb") { // leveldb
            db->Write(leveldb::WriteOptions(), batch);
            delete batch;
            delete db; // 刪除臨時(shí)變量,清理內(nèi)存占用
        } else if (db_backend == "lmdb") { // lmdb
            CHECK_EQ(mdb_txn_commit(mdb_txn), MDB_SUCCESS) << "mdb_txn_commit failed";
            // 關(guān)閉 mdb 數(shù)據(jù)對(duì)象變量
            mdb_close(mdb_env, mdb_dbi);
            // 關(guān)閉 mdb 操作環(huán)境變量
            mdb_env_close(mdb_env);
        } else {
            LOG(FATAL) << "Unknown db backend " << db_backend;
        }
        LOG(ERROE) << "Processed " << count << " files.";
    }

    delete[] pixels;
} // void convert_dataset(const char* image_filename, const char* label_filename, const char* db_path, const string& db_backend)

int main(int argc, char** argv) {
#ifndef GFLAGS_GFLAGS_H
    namespace gflags = google;
#endif

    gflags::SetUsageMessage("This script converts the MNIST dataset to \n"
        "the lmdb/leveldb format used by Caffe to load data. \n"
        "Usage:\n"
        "    convert_mnist_data [FLAGS] input_image_file input_label_file "
        "output_db_file\n"
        "The MNIST dataset could be downloaded at\n"
        "    http://yann./exdb/mnist/\n"
        "You should gunzip them after downloading,"
        "or directly use the data/mnist/get_mnist.sh\n");
    gflags::ParseCommandLineFlags(&argc, &argv, true);

    // FLAGS_backend 在前面通過(guò) DEFINE_string 定義,,是字符串類型
    const string& db_backend = FLAGS_backend;

    if (argc != 4) {
        gflags::ShowUsageWithFlagsRestrict(argv[0], "examples/mnist/convert_mnist_data");
    } else {
        google::InitGoogleLogging(argv[0]);
        convert_dataset(argv[1], argv[2], argv[3], db_backend);
    }

    return 0;
}
  • 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
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 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
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247


LMDB 句柄

變量 說(shuō)明
MDB_dbi mdb_dbi 環(huán)境中一個(gè)數(shù)據(jù)庫(kù)的句柄
MDB_env *mdb_env 整個(gè)數(shù)據(jù)環(huán)境的句柄
MDB_val mdb_key, mdb_data 存放要輸入進(jìn)數(shù)據(jù)庫(kù)的數(shù)據(jù)值
MDB_txn *mdb_txn 數(shù)據(jù)庫(kù)事物操作的句柄


LMDB 流程圖

這里寫(xiě)圖片描述

小端存儲(chǔ),、大端存儲(chǔ)(Little-Endian、Big-Endian)

上面的源碼中,,有一個(gè)函數(shù)是進(jìn)行大端存儲(chǔ)到小端存儲(chǔ)的轉(zhuǎn)換的,。這部分沒(méi)有計(jì)算機(jī)匯編的基礎(chǔ),一開(kāi)始一頭霧水……參考的一篇博客:http://www.cnblogs.com/passingcloudss/archive/2011/05/03/2035273.html

不同的CPU有不同的字節(jié)序類型,,這些字節(jié)序是指整數(shù)在內(nèi)存中保存的順序,。最常見(jiàn)的有兩種:
1. Little-endian:將低序字節(jié)存儲(chǔ)在起始地址(低位編址)
2. Big-endian:將高序字節(jié)存儲(chǔ)在起始地址(高位編址)

LE(little-endian):
最符合人的思維的字節(jié)序,地址低位存儲(chǔ)值的低位 ,,地址高位存儲(chǔ)值的高位 ,。
這種存儲(chǔ)最符合人的思維的字節(jié)序,因?yàn)閺娜说牡谝挥^感來(lái)說(shuō),,低位值小,,就應(yīng)該放在內(nèi)存地址小的地方,也即內(nèi)存地址低位,。反之,,高位值就應(yīng)該放在內(nèi)存地址大的地方,也即內(nèi)存地址高位

BE(big-endian):
最直觀的字節(jié)序,,地址低位存儲(chǔ)值的高位,,地址高位存儲(chǔ)值的低位
為什么說(shuō)直觀,,不要考慮對(duì)應(yīng)關(guān)系,只需要把內(nèi)存地址從左到右按照由低到高的順序?qū)懗?,把值按照通常的高位到低位的順序?qū)懗?。兩者?duì)照,一個(gè)字節(jié)一個(gè)字節(jié)的填充進(jìn)去 ,。

注:×86 系列的 CPU 都是 Little-Endian 的字節(jié)序,。

例子1:在內(nèi)存中雙字 0x01020304(DWORD) 的存儲(chǔ)方式:
內(nèi)存地址為:4000 4001 4002 4003
小端存儲(chǔ): 04 03 02 01
大端存儲(chǔ): 01 02 03 04
注:每個(gè)地址存 1 個(gè)字節(jié),每個(gè)字有 4 字節(jié),。2 位 16 進(jìn)制數(shù)是 1 個(gè)字節(jié)(0xFF = 11111111),。

例子2:如果我們將 0x1234abcd 寫(xiě)入到以 0x0000 開(kāi)始的內(nèi)存中,則結(jié)果為:

big-endian little-endian
0x0000 0x12 0xcd
0x0001 0x23 0xab
0x0002 0xab 0x34
0x0003 0xcd 0x12


Python 讀寫(xiě) LMDB 格式圖像數(shù)據(jù)

我想這部分才是很多人關(guān)心的,,因?yàn)槲覀兪褂?caffe,,將圖像數(shù)據(jù)轉(zhuǎn)換為 caffe 可以識(shí)別的數(shù)據(jù)格式是第一步。同時(shí)大多數(shù)都是通過(guò) python 接口來(lái)轉(zhuǎn)換數(shù)據(jù)格式的,。

LMDB 數(shù)據(jù)庫(kù)

Caffe 使用 LMDB 的情況大約有兩類:

  • 第一類是 DataLayer 層中 使用的 訓(xùn)練集,、驗(yàn)證集、測(cè)試集,;
  • 第二類 就是 ./caffe/build/tools/extract_feature.bin 這種特征提取工具提取特征后,輸出的特征文件,。

LMDB 的全稱是 Lighting Memory-Mapped Database(閃電般的內(nèi)存映射數(shù)據(jù)庫(kù)) ,。它文件結(jié)構(gòu)簡(jiǎn)單,一個(gè)文件夾,,里面一個(gè)數(shù)據(jù)文件,,一個(gè)鎖文件。數(shù)據(jù)隨意復(fù)制,,隨意傳輸,。它的訪問(wèn)簡(jiǎn)單,不需要運(yùn)行單獨(dú)的數(shù)據(jù)管理進(jìn)程,。只要在訪問(wèn)的代碼里引用 LMDB 庫(kù),,訪問(wèn)時(shí)給文件路徑即可。

Caffe 中使用的數(shù)據(jù)較為很簡(jiǎn)單,,就是大量的矩陣/向量平鋪開(kāi)來(lái),。數(shù)據(jù)之間沒(méi)有什么關(guān)聯(lián),數(shù)據(jù)內(nèi)沒(méi)有復(fù)雜的對(duì)象結(jié)構(gòu),,就是向量和矩陣,。既然數(shù)據(jù)并不復(fù)雜,Caffe 就選擇了 LMDB 這個(gè)簡(jiǎn)單的數(shù)據(jù)庫(kù)來(lái)存放數(shù)據(jù),。

上面提到了,,Caffe 使用 LMDB 數(shù)據(jù)庫(kù)有兩點(diǎn)原因:

一方面是因?yàn)閿?shù)據(jù)源的格式多樣性,,有文本文件、二進(jìn)制文件圖像文件等等,,不可能用一個(gè)代碼完成上述所有的數(shù)據(jù)格式,。因此,通過(guò) LMDB 數(shù)據(jù)庫(kù),,轉(zhuǎn)化成統(tǒng)一的數(shù)據(jù)格式可以簡(jiǎn)化數(shù)據(jù)讀取層的實(shí)現(xiàn),。

第二個(gè)方面就是使用 LMDB 數(shù)據(jù)庫(kù)可以大大的節(jié)約磁盤 IO 的時(shí)間開(kāi)銷。因?yàn)樽x取大量小文件的時(shí)間開(kāi)銷是相當(dāng)大的,,尤其是在機(jī)械硬盤上,。
數(shù)據(jù)庫(kù)單文件還能減少數(shù)據(jù)集復(fù)制、傳輸過(guò)程的開(kāi)銷,。因?yàn)槲覀兌加羞^(guò)體會(huì),,一個(gè)具有幾萬(wàn)個(gè)、幾十萬(wàn)個(gè)文件的數(shù)據(jù)集,,不管是直接復(fù)制,,還是打開(kāi)再解包,過(guò)程都巨慢無(wú)比,。LMDB 只有一個(gè)文件,,你的介質(zhì)有多快,就能復(fù)制多快,,不會(huì)因?yàn)槲募喽牧钊诵乃椤?

Caffe 中 Datum 數(shù)據(jù)結(jié)構(gòu)

Caffe 并不是把向量和矩陣直接放進(jìn)數(shù)據(jù)庫(kù)的,,而是將數(shù)據(jù)通過(guò) caffe.proto 里定義的一個(gè) datum 類來(lái)封裝的。數(shù)據(jù)庫(kù)里存放的是一個(gè)個(gè) datum 序列化成的字符串,。Datum 的定義如下

message Datum {
  optional int32 channels = 1;
  optional int32 height = 2;
  optional int32 width = 3;
  // the actual image data, in bytes
  optional bytes data = 4;
  optional int32 label = 5;
  // Optionally, the datum could also hold float data.
  repeated float float_data = 6;
  // If true data contains an encoded image that need to be decoded
  optional bool encoded = 7 [default = false];
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

一個(gè) Datum 有三個(gè)維度,,channnelsheight,、width,,可以看作是少了 num 維度的 Blob
存放數(shù)據(jù)的地方有兩個(gè):bytes data,、float_data,,分別存放整數(shù)型和浮點(diǎn)型數(shù)據(jù)。圖像數(shù)據(jù)一般是整形,,放在 bytes data 中,,特征向量一般是浮點(diǎn)型,存放在 float_data 中,。
label 里存放的是類別標(biāo)簽,,是整數(shù)型。
encoded 標(biāo)識(shí)數(shù)據(jù)是否需要被解碼,,因?yàn)槔锩婵赡艽娣诺氖?JPEG 或者 PNG 之類經(jīng)過(guò)編碼的數(shù)據(jù),。

Datum 這個(gè)數(shù)據(jù)結(jié)構(gòu)將數(shù)據(jù)和標(biāo)簽封裝在一起,,兼容整形和浮點(diǎn)型數(shù)據(jù)。經(jīng)過(guò) protobuf 編譯后,,可以在 Python 和 C++ 中都提供高效的訪問(wèn),。
同時(shí) protobuf 還為它提供了序列化、反序列化的功能,。存放進(jìn) LMDB 的就是 Datum 序列化生成的字符串,。

Caffe 中將圖像寫(xiě)入 LMDB 數(shù)據(jù)庫(kù)

我上面解析的 create_mnist_data.cpp 代碼對(duì)于這部分是很有用的,特別是 LMDB 流程圖中的 lmdb 數(shù)據(jù)操作函數(shù),,如打開(kāi)一個(gè) lmdb 數(shù)據(jù)庫(kù),,寫(xiě)入數(shù)據(jù)等操作,python 中的使用類似,,但比 C++ 的要簡(jiǎn)潔許多 ,。

下面通過(guò)代碼來(lái)說(shuō)明吧,這段代碼是一個(gè)大牛寫(xiě)的教程:《A Practical Introduction to Deep Learning with Caffe and Python》,,寫(xiě)的很清晰,。

import os
import glob
import random
import numpy as np

import cv2

import caffe
from caffe.proto import caffe_pb2
import lmdb

#Size of images
IMAGE_WIDTH = 227
IMAGE_HEIGHT = 227

# train_lmdb、validation_lmdb 路徑
train_lmdb = '/home/chenxp/Documents/vehicleID/val/train_lmdb'
validation_lmdb = '/home/chenxp/Documents/vehicleID/val/validation_lmdb'

# 如果存在了這個(gè)文件夾, 先刪除
os.system('rm -rf  ' + train_lmdb)
os.system('rm -rf  ' + validation_lmdb)

# 讀取圖像
train_data = [img for img in glob.glob("/home/chenxp/Documents/vehicleID/val/query/*jpg")]
test_data = [img for img in glob.glob("/home/chenxp/Documents/vehicleID/val/query/*jpg")]

# Shuffle train_data
# 打亂數(shù)據(jù)的順序
random.shuffle(train_data)

# 圖像的變換, 直方圖均衡化, 以及裁剪到 IMAGE_WIDTH x IMAGE_HEIGHT 的大小
def transform_img(img, img_width=IMAGE_WIDTH, img_height=IMAGE_HEIGHT):
    #Histogram Equalization
    img[:, :, 0] = cv2.equalizeHist(img[:, :, 0])
    img[:, :, 1] = cv2.equalizeHist(img[:, :, 1])
    img[:, :, 2] = cv2.equalizeHist(img[:, :, 2])

    #Image Resizing, 三次插值
    img = cv2.resize(img, (img_width, img_height), interpolation = cv2.INTER_CUBIC)
    return img

def make_datum(img, label):
    #image is numpy.ndarray format. BGR instead of RGB
    return caffe_pb2.Datum(
        channels=3,
        width=IMAGE_WIDTH,
        height=IMAGE_HEIGHT,
        label=label,
        data=np.rollaxis(img, 2).tobytes()) # or .tostring() if numpy < 1.9

# 打開(kāi) lmdb 環(huán)境, 生成一個(gè)數(shù)據(jù)文件,,定義最大空間, 1e12 = 1000000000000.0
in_db = lmdb.open(train_lmdb, map_size=int(1e12)) 
with in_db.begin(write=True) as in_txn: # 創(chuàng)建操作數(shù)據(jù)庫(kù)句柄
    for in_idx, img_path in enumerate(train_data):
        if in_idx %  6 == 0: # 只處理 5/6 的數(shù)據(jù)作為訓(xùn)練集
            continue         # 留下 1/6 的數(shù)據(jù)用作驗(yàn)證集
        # 讀取圖像. 做直方圖均衡化,、裁剪操作
        img = cv2.imread(img_path, cv2.IMREAD_COLOR)
        img = transform_img(img, img_width=IMAGE_WIDTH, img_height=IMAGE_HEIGHT)

        if 'cat' in img_path: # 組織 label, 這里是如果文件名稱中有 'cat', 標(biāo)簽就是 0
            label = 0         # 如果圖像名稱中沒(méi)有 'cat', 有的是 'dog', 標(biāo)簽則為 1
        else:                 # 這里方, label 需要自己去組織
            label = 1         # 每次情況可能不一樣, 靈活點(diǎn)

        datum = make_datum(img, label)
        # '{:0>5d}'.format(in_idx):
        #      lmdb的每一個(gè)數(shù)據(jù)都是由鍵值對(duì)構(gòu)成的,
        #      因此生成一個(gè)用遞增順序排列的定長(zhǎng)唯一的key
        in_txn.put('{:0>5d}'.format(in_idx), datum.SerializeToString()) #調(diào)用句柄,寫(xiě)入內(nèi)存
        print '{:0>5d}'.format(in_idx) + ':' + img_path

# 結(jié)束后記住釋放資源,,否則下次用的時(shí)候打不開(kāi),。。,。
in_db.close() 

# 創(chuàng)建驗(yàn)證集 lmdb 格式文件
print '\nCreating validation_lmdb'
in_db = lmdb.open(validation_lmdb, map_size=int(1e12))
with in_db.begin(write=True) as in_txn:
    for in_idx, img_path in enumerate(train_data):
        if in_idx % 6 != 0:
            continue
        img = cv2.imread(img_path, cv2.IMREAD_COLOR)
        img = transform_img(img, img_width=IMAGE_WIDTH, img_height=IMAGE_HEIGHT)
        if 'cat' in img_path:
            label = 0
        else:
            label = 1
        datum = make_datum(img, label)
        in_txn.put('{:0>5d}'.format(in_idx), datum.SerializeToString())
        print '{:0>5d}'.format(in_idx) + ':' + img_path
in_db.close()
print '\nFinished processing all images'
  • 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
  • 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

再展示一段生成 lmdb 的代碼,來(lái)源自:http:///2015/04/28/creating-lmdb-in-python/
這段代碼并沒(méi)有用真實(shí)的圖像數(shù)據(jù)來(lái)生成,,二是用 numpy 中的 np.zeros() 生成了圖像格式的數(shù)據(jù):

import numpy as np
import lmdb
import caffe

N = 1000

# Let's pretend this is interesting data
X = np.zeros((N, 3, 32, 32), dtype=np.uint8)
y = np.zeros(N, dtype=np.int64)

# We need to prepare the database for the size. We'll set it 10 times
# greater than what we theoretically need. There is little drawback to
# setting this too big. If you still run into problem after raising
# this, you might want to try saving fewer entries in a single
# transaction.
map_size = X.nbytes * 10

env = lmdb.open('mylmdb', map_size=map_size)

with env.begin(write=True) as txn:
    # txn is a Transaction object
    for i in range(N):
        datum = caffe.proto.caffe_pb2.Datum()
        datum.channels = X.shape[1]
        datum.height = X.shape[2]
        datum.width = X.shape[3]
        datum.data = X[i].tobytes()  # or .tostring() if numpy < 1.9
        datum.label = int(y[i])
        str_id = '{:08}'.format(i)

        # The encode is only essential in Python 3
        txn.put(str_id.encode('ascii'), datum.SerializeToString())
  • 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
  • 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

運(yùn)行上一段代碼,,會(huì)生成下面兩個(gè)文件:

這里寫(xiě)圖片描述


Caffe 從 LMDB 數(shù)據(jù)庫(kù)中讀取數(shù)據(jù)

下面就是從生成好的 lmdb 中讀取數(shù)據(jù)了:

import numpy as np
import caffe
import lmdb
import cv2

# 打開(kāi) lmdb 數(shù)據(jù)庫(kù), 指定好位置
env = lmdd.open('mylmdb', readonly=True)
with env.begin() as txn:
    raw_datum = txn.get(b'00000000')

datum = caffe.proto.caffe_pb2.Datum()
datum.ParseFromString(raw_datum)

flat_x = np.fromstring(datum.data, dtype=np.uint8)
x = flat_x.reshape(datum.channels, datum.height, datum.width)
y = datum.label

print(datum.channels)
print 'label = ' + str(y) # y 為整型, 需要轉(zhuǎn)成字符串

# C x H x W 轉(zhuǎn)換到 H x W x C, 才能在 cv2 中顯示
img = cv2.transpose(img, (1, 2, 0)) # 或者: img = x.transpose(1, 2, 0)
cv2.imshow("Image", img)
cv2.waitKey(0)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

輸出為:

這里寫(xiě)圖片描述

下圖是輸出的圖像……別笑……那是因?yàn)樯厦娲a用 np.zeros() 生成的太小了:
這里寫(xiě)圖片描述

可以迭代讀取 <key, value>

with env.open() as txn:
    cursor = txn.cursor()
    for key, value in cursor:
        print(key, value)
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

下面代碼用迭代循環(huán) txn.cursor() 讀取:

import caffe
from caffe.proto import caffe_pb2

import lmdb
import cv2
import numpy as np

lmdb_env = lmdb.open('mylmdb', readonly=True) # 打開(kāi)數(shù)據(jù)文件
lmdb_txn = lmdb_env.begin() # 生成處理句柄
lmdb_cursor = lmdb_txn.cursor() # 生成迭代器指針
datum = caffe_pb2.Datum() # caffe 定義的數(shù)據(jù)類型

for key, value in lmdb_cursor: # 循環(huán)獲取數(shù)據(jù)
    datum.ParseFromString(value) # 從 value 中讀取 datum 數(shù)據(jù)

    label = datum.label
    data = caffe.io.datum_to_array(datum)
    print data.shape
    print datum.channels
    image = data.transpose(1, 2, 0)
    cv2.imshow('cv2.png', image)
    cv2.waitKey(0)

cv2.destroyAllWindows()
lmdb_env.close()
  • 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
  • 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


Reference

  1. 《深度學(xué)習(xí) 21天實(shí)戰(zhàn) Caffe》, 卜居
  2. Caffe1——Mnist數(shù)據(jù)集創(chuàng)建lmdb或leveldb類型的數(shù)據(jù)
  3. caffe源碼閱讀(1): 數(shù)據(jù)加載
  4. 愚見(jiàn)caffe中的LeNet
  5. 小端格式和大端格式(Little-Endian&Big-Endian)
  6. Creating an LMDB database in Python
  7. A Practical Introduction to Deep Learning with Caffe and Python
  8. 中科院自動(dòng)化所博士@beanfrog:Write/Read lmdb file for caffe with python
  9. 利用caffe與lmdb讀寫(xiě)圖像數(shù)據(jù)
  10. Caffe中LMDB的使用
  11. Caffe: Reading LMDB from Python

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,,所有內(nèi)容均由用戶發(fā)布,,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式,、誘導(dǎo)購(gòu)買等信息,,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,,請(qǐng)點(diǎn)擊一鍵舉報(bào),。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多