天天看點

Caffe3——ImageNet資料集建立lmdb類型的資料

ImageNet資料集和cifar,mnist資料集最大的不同,就是資料量特别大;單張圖檔尺寸大,訓練樣本個數多;面對如此大的資料集,在轉換成lmdb檔案時;使用了很多新的類型對象。

1,動态擴容的數組“vector”,動态地添加新元素

2,pair類型資料對,用于存儲成對的對象,例如存儲檔案名和對應标簽

3,利用opencv中的圖像處理函數,來讀取和處理大尺寸圖像

一:程式開始

由于要向imageNet資料集中設定resize和是否亂序等參數,是以本文使用gflags指令行解析工具;在Create.sh檔案中,調用convert_imageset.bin語句為:

<pre name="code" class="cpp">GLOG_logtostderr=1$TOOLS/convert_imageset \
    --resize_height=$RESIZE_HEIGHT \
    --resize_width=$RESIZE_WIDTH \
    --shuffle \
    $TRAIN_DATA_ROOT \  圖像資料集存放的根目錄
$DATA/train.txt \       圖像的ID和對應的分類标簽數字
$EXAMPLE/ilsvrc12_train_lmdb  lmdb檔案儲存的路徑
           

由于train.txt檔案太大,電腦打不開,故打開val.txt一窺之;val.txt中的某個資料為:

65ILSVRC2012_val_00000002.JPEG ,65應該是對應的标簽,後面的是圖像的編号id。

二:資料轉換流程圖

Caffe3——ImageNet資料集建立lmdb類型的資料
Caffe3——ImageNet資料集建立lmdb類型的資料

三:convert_imageset.cpp函數分析

1引入必要的頭檔案和命名空間

#include<algorithm>//輸出數組的内容、對數組進行升幂排序、反轉數組内容、複制數組内容等操作,
#include <fstream>  // NOLINT(readability/streams)
#include <string>
#include<utility>//utility頭檔案定義了一個pair類型,pair類型用于存儲一對資料
#include<vector>//會自動擴充容量的數組
#include "boost/scoped_ptr.hpp"//智能指針頭檔案
#include "gflags/gflags.h"
#include "glog/logging.h"
#include"caffe/proto/caffe.pb.h"
#include "caffe/util/db.hpp" //引入包裝好的lmdb操作函數
#include "caffe/util/io.hpp" //引入opencv中的圖像操作函數
#include "caffe/util/rng.hpp"
           

頭檔案和convert_cifar_data.cpp的差別:

1,引入gflags指令行解析工具;

2,引入utility頭檔案,裡面提供了數組洗牌等操作

using namespace caffe;  // NOLINT(build/namespaces)
using std::pair;
using boost::scoped_ptr;
           

命名空間差別:

1,引入全部caffe命名空間

2,引入pair對命名空間

2 gflags宏定義參數

//通過gflags宏定義一些程式的參數變量

DEFINE_bool(gray, false,"When thisoption is on, treat images as grayscale ones");//是否為灰階圖檔
DEFINE_bool(shuffle, false,"Randomlyshuffle the order of images and their labels");//定義洗牌變量,是否随機打亂資料集的順序
DEFINE_string(backend, "lmdb","The backend {lmdb, leveldb} for storing the result");//預設轉換的資料類型
DEFINE_int32(resize_width, 0, "Width images areresized to");//定義resize的尺寸,預設為0,不轉換尺寸
DEFINE_int32(resize_height, 0, "Height imagesare resized to");
DEFINE_bool(check_size, false,"When this optionis on, check that all the datum have the samesize");
DEFINE_bool(encoded, false,"When this option ison, the encoded image will be save in datum");//用于轉換資料格式的
DEFINE_string(encode_type, "","Optional:What type should we encode the image as ('png','jpg',...).");//要轉換的資料格式
           

3 main()函數

沒有想cifar和mnist的main函數,通過調用convert_data()函數來轉換資料,而是直接在main函數内完成了所有資料轉換代碼。

3.1 通過gflags宏定義接收指令行中傳入的參數

const boolis_color = !FLAGS_gray;  //通過gflags把宏定義變量的值,指派給常值變量
  const boolcheck_size = FLAGS_check_size; //檢查圖像的size
  const boolencoded = FLAGS_encoded;//是否編譯(轉換)圖像格式
  const stringencode_type = FLAGS_encode_type;//要編譯的圖像格式
           

3.2讀取源資料

3.2.1建立讀取對象變量

std::ifstream infile(argv[2]);//建立指向train.txt檔案的檔案讀入流

std::vector<std::pair<std::string, int> > lines;//定義向量變量,向量中每個元素為一個pair對,pair對有兩個成員變量,一個為string類型,一個為int類型;其中string類型用于存儲檔案名,int類型,感覺用于存數對應類别的id

如val.txt中前幾個字元為“ILSVRC2012_val_00000001.JPEG65ILSVRC2012_val_00000002.JPEG”;感覺這個string= ILSVRC2012_val_00000001.JPEG   int=65

std::stringfilename;

int label;

3.2.2 讀取資料

  //下面一條while語句是把train.txt檔案中存放的所有檔案名和标簽,都存放到vextor類型變量lines中;lines中存放圖檔的名字和對應的标簽,不存儲真正的圖檔資料

while (infile>> filename >> label) {
lines.push_back(std::make_pair(filename, label));
           

//make_pair是pair模闆中定義的給pair對象指派的函數,push_back()函數是vector對象的一個成員函數,用來在末端添加新元素}

3.3判斷是否進行洗牌操作

if(FLAGS_shuffle) {
    // randomlyshuffle data
    LOG(INFO)<< "Shuffling data";
           
<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">//洗牌函數,使用随機生成器g對元素[first,last)容器内部元素進行随機排列</span>
           

 shuffle(lines.begin(), lines.end());//vector.begin() - 回傳一個Iterator疊代器,它指向 vector 第一個元素。}

3.4以智能指針的方式建立db::DB類型的對象 db

scoped_ptr<db::DB>db(db::GetDB(FLAGS_backend));
//智能指針的建立方式類似泛型的格式,上面通過db.cpp内定義的命名的子命名空間中db的“成員函數”GetDB函數來初始化db對象
db->Open(argv[3], db::NEW);//argv[3]的檔案夾下建立并打開lmdb的操作環境
scoped_ptr<db::Transaction>txn(db->NewTransaction());//建立lmdb檔案的操作句柄
           

3.5 源資料中提取圖像資料

3.5.1 通過ReadImageToDatum函數把圖像資料讀取到datum中

//到源資料位置讀取每張圖檔的資料。(../imagenet/xxx.jpeg,65,256,256,true,jpeg,&datum)

status= ReadImageToDatum(root_folder + lines[line_id].first,lines[line_id].second, resize_height,resize_width, is_color,enc, &datum); //把圖像資料讀取到datum中
           

3.5.2  ReadImageToDatum函數說明

ReadImageToDatum函數為io.cpp檔案中定義的函數;io.cpp主要實作了3部分功能:

1,從text檔案或者二進制檔案中讀寫proto檔案;

2,利用opencv的Mat矩陣,把圖像資料讀到Mat矩陣中;

3,把Mat矩陣中的值放入到datum中

3.5.3 檢查資料尺寸

if (check_size) {//檢查圖檔尺寸
      if (!data_size_initialized) {//若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();
      }
           

3.6   序列化鍵和值并放入臨時資料庫

// sequential
intlength = snprintf(key_cstr, kMaxKeyLength, "%08d_%s", line_id,lines[line_id].first.c_str());//若line_id=1234,lines[line_id].first=“abc.jpeg” 則 key_str=00001234_abc.jpeg,length=00001234_abc.jpeg字元串的長度
    // Put in db
 string out;
    CHECK(datum.SerializeToString(&out));//datum資料,序列化到字元串中
    txn->Put(string(key_cstr, length), out);//把鍵值對放入到資料庫
           

3.7 批量送出到lmdb檔案

if (++count % 1000 == 0) {
      // Commit db
      txn->Commit();//儲存到lmdb類型的檔案
      txn.reset(db->NewTransaction());//重新初始化操作句柄
      LOG(ERROR) << "Processed" << count << " files.";
    }
           

四,相關檔案

4.1 Convert_imageset.cpp檔案

// This program converts a set of images to a lmdb/leveldb by storing them
// as Datum proto buffers.
// Usage:
//   convert_imageset [FLAGS] ROOTFOLDER/ LISTFILE DB_NAME
//
// where ROOTFOLDER is the root folder that holds all the images, and LISTFILE
// should be a list of files as well as their labels, in the format as
//   subfolder1/file1.JPEG 7
//   ....

#include <algorithm>//輸出數組的内容、對數組進行升幂排序、反轉數組内容、複制數組内容等操作,
#include <fstream>  // NOLINT(readability/streams)
#include <string>
#include <utility>//utility頭檔案定義了一個pair類型
#include <vector>//會自動擴充容量的數組

#include "boost/scoped_ptr.hpp"
#include "gflags/gflags.h"
#include "glog/logging.h"

#include "caffe/proto/caffe.pb.h"
#include "caffe/util/db.hpp"
#include "caffe/util/io.hpp"
#include "caffe/util/rng.hpp"

using namespace caffe;  // NOLINT(build/namespaces)
using std::pair;
using boost::scoped_ptr;


//通過gflags宏定義一些程式的參數變量
DEFINE_bool(gray, false,
    "When this option is on, treat images as grayscale ones");
DEFINE_bool(shuffle, false,
    "Randomly shuffle the order of images and their labels");//洗牌,随機打亂資料集的順序
DEFINE_string(backend, "lmdb",
        "The backend {lmdb, leveldb} for storing the result");
DEFINE_int32(resize_width, 0, "Width images are resized to");
DEFINE_int32(resize_height, 0, "Height images are resized to");
DEFINE_bool(check_size, false,
    "When this option is on, check that all the datum have the same size");
DEFINE_bool(encoded, false,
    "When this option is on, the encoded image will be save in datum");//用于轉換資料格式的
DEFINE_string(encode_type, "",
    "Optional: What type should we encode the image as ('png','jpg',...).");//要轉換的資料格式

int main(int argc, char** argv) {
  ::google::InitGoogleLogging(argv[0]);

#ifndef GFLAGS_GFLAGS_H_
  namespace gflags = google;
#endif

  gflags::SetUsageMessage("Convert a set of images to the leveldb/lmdb\n"
        "format used as input for Caffe.\n"
        "Usage:\n"
        "    convert_imageset [FLAGS] ROOTFOLDER/ LISTFILE DB_NAME\n"
        "The ImageNet dataset for the training demo is at\n"
        "    http://www.image-net.org/download-images\n");
  gflags::ParseCommandLineFlags(&argc, &argv, true);

  if (argc < 4) {
    gflags::ShowUsageWithFlagsRestrict(argv[0], "tools/convert_imageset");
    return 1;
  }
  //arg[1] 訓練集存放的位址,arg[2] train.txt(估計是訓練集中所有圖檔的檔案名稱),arg[3] 要儲存的檔案名稱xxlmdb
  const bool is_color = !FLAGS_gray;  //通過gflags把宏定義變量的值,指派給常值變量
  const bool check_size = FLAGS_check_size; //檢查圖像的size
  const bool encoded = FLAGS_encoded;//是否編譯(轉換)圖像格式
  const string encode_type = FLAGS_encode_type;//要編譯的圖像格式

  std::ifstream infile(argv[2]);//定義指向train.txt資料檔案的檔案讀入流
  std::vector<std::pair<std::string, int> > lines;//定義向量變量,向量中每個元素為一個pair對,pair對有兩個成員變量,一個為string類型,一個為int類型
  std::string filename;
  int label;
  //下面一條while語句是把train.txt檔案中存數的資料和标簽,都存放到vextor類型變量中lines中;lines中存放圖檔的名字和對應的标簽,不存儲真正的圖檔資料
  while (infile >> filename >> label) {
    lines.push_back(std::make_pair(filename, label));//make_pair是pair模闆中定義的給pair對象指派的函數,push_back()函數是vector對象的一個成員函數,用來在末端添加新元素
  }
  if (FLAGS_shuffle) {
    // randomly shuffle data
    LOG(INFO) << "Shuffling data";
	//洗牌函數,使用随機生成器g對元素[first, last)容器内部元素進行随機排列
    shuffle(lines.begin(), lines.end());//vector.begin() - 回傳一個Iterator疊代器,它指向 vector 第一個元素。
  }
  LOG(INFO) << "A total of " << lines.size() << " images.";

  if (encode_type.size() && !encoded)
    LOG(INFO) << "encode_type specified, assuming encoded=true.";

  int resize_height = std::max<int>(0, FLAGS_resize_height);
  int resize_width = std::max<int>(0, FLAGS_resize_width);

  // Create new DB
  scoped_ptr<db::DB> db(db::GetDB(FLAGS_backend));
  db->Open(argv[3], db::NEW);//argv[3]的檔案夾下打開建立lmdb的操作環境
  scoped_ptr<db::Transaction> txn(db->NewTransaction());//建立lmdb檔案的操作句柄

  // Storing to db
  std::string root_folder(argv[1]);//把源資料檔案的位址複制給root_folder
  Datum datum;//聲明資料“轉換”對象
  int count = 0;
  const int kMaxKeyLength = 256;
  char key_cstr[kMaxKeyLength];
  int data_size = 0;
  bool data_size_initialized = false;

  for (int line_id = 0; line_id < lines.size(); ++line_id) {
    bool status;
    std::string enc = encode_type; //enc為空串,則enc.size()=false;否則為true
    if (encoded && !enc.size()) {
      // Guess the encoding type from the file name
      string fn = lines[line_id].first;//把圖像的檔案名指派給fn(filename)
      size_t p = fn.rfind('.');//rfind函數的傳回值是一個整形的索引值,直線要查找的字元在字元串中的位置;若沒有找到,傳回string::npos
      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);//将enc字元串轉換成小寫
    }
	//到源資料位置,以此讀取每張圖檔的資料。(../imagenet/xxx.jpeg,65,256,256,true,jpeg,&datum)
    status = ReadImageToDatum(root_folder + lines[line_id].first,
        lines[line_id].second, resize_height, resize_width, is_color,enc, &datum);  //把圖像資料讀取到datum中
    if (status == false) continue;//status=false,說明此張圖檔讀取錯誤;“跳過”繼續下一張
    if (check_size) {//檢查圖檔尺寸
      if (!data_size_initialized) {//若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();
      }
    }
    // sequential
    int length = snprintf(key_cstr, kMaxKeyLength, "%08d_%s", line_id, 
        lines[line_id].first.c_str());//若line_id=1234,lines[line_id].first=“abc.jpeg” 則 key_str=00001234_abc.jpeg,length=00001234_abc.jpeg字元串的長度

    // Put in db
    string out;
    CHECK(datum.SerializeToString(&out));//datum資料,序列化到字元串中
    txn->Put(string(key_cstr, length), out);//把鍵值對放入到資料庫

    if (++count % 1000 == 0) {
      // Commit db
      txn->Commit();//儲存到lmdb類型的檔案
      txn.reset(db->NewTransaction());//重新初始化操作句柄
      LOG(ERROR) << "Processed " << count << " files.";
    }
  }
  // write the last batch
  if (count % 1000 != 0) {
    txn->Commit();
    LOG(ERROR) << "Processed " << count << " files.";
  }
  return 0;
}
           

4.2 io.cpp檔案

#include <fcntl.h>
#include <google/protobuf/io/coded_stream.h>
#include <google/protobuf/io/zero_copy_stream_impl.h>
#include <google/protobuf/text_format.h>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/highgui/highgui_c.h>
#include <opencv2/imgproc/imgproc.hpp>
#include <stdint.h>

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

#include "caffe/common.hpp"
#include "caffe/proto/caffe.pb.h"
#include "caffe/util/io.hpp"

const int kProtoReadBytesLimit = INT_MAX;  // Max size of 2 GB minus 1 byte.

namespace caffe {

using google::protobuf::io::FileInputStream;//檔案輸入流
using google::protobuf::io::FileOutputStream;//檔案輸出流
using google::protobuf::io::ZeroCopyInputStream;//These interfaces are different from classic I/O streams in that they try to minimize the amount of data copying that needs to be done
using google::protobuf::io::CodedInputStream;
using google::protobuf::io::ZeroCopyOutputStream;
using google::protobuf::io::CodedOutputStream;
using google::protobuf::Message;

bool ReadProtoFromTextFile(const char* filename, Message* proto) {//從文本檔案中讀入proto檔案
  int fd = open(filename, O_RDONLY);
  CHECK_NE(fd, -1) << "File not found: " << filename;
  FileInputStream* input = new FileInputStream(fd);
  bool success = google::protobuf::TextFormat::Parse(input, proto);
  delete input;
  close(fd);
  return success;
}

void WriteProtoToTextFile(const Message& proto, const char* filename) {//想文本檔案中寫入proto檔案
  int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0644);
  FileOutputStream* output = new FileOutputStream(fd);
  CHECK(google::protobuf::TextFormat::Print(proto, output));
  delete output;
  close(fd);
}

bool ReadProtoFromBinaryFile(const char* filename, Message* proto) {//從二進制檔案讀入proto
  int fd = open(filename, O_RDONLY);
  CHECK_NE(fd, -1) << "File not found: " << filename;
  ZeroCopyInputStream* raw_input = new FileInputStream(fd);
  CodedInputStream* coded_input = new CodedInputStream(raw_input);
  coded_input->SetTotalBytesLimit(kProtoReadBytesLimit, 536870912);

  bool success = proto->ParseFromCodedStream(coded_input);

  delete coded_input;
  delete raw_input;
  close(fd);
  return success;
}

void WriteProtoToBinaryFile(const Message& proto, const char* filename) {//把proto寫入二進制檔案中
  fstream output(filename, ios::out | ios::trunc | ios::binary);
  CHECK(proto.SerializeToOstream(&output));
}


//基本上講 Mat 是一個類,由兩個資料部分組成:矩陣頭(包含矩陣尺寸,存儲方法,存儲位址等資訊)和
//一個指向存儲所有像素值的矩陣的指針(根據所選存儲方法的不同矩陣可以是不同的維數)。
//矩陣頭的尺寸是常數值,但矩陣本身的尺寸會依圖像的不同而不同,通常比矩陣頭的尺寸大數個數量級。是以,當在程式中傳遞圖像并建立拷貝時,
//大的開銷是由矩陣造成的,而不是資訊頭。OpenCV是一個圖像處理庫,囊括了大量的圖像處理函數,為了解決問題通常要使用庫中的多個函數,
//是以在函數中傳遞圖像是家常便飯。同時不要忘了我們正在讨論的是計算量很大的圖像處理算法,是以,除非萬不得已,我們不應該拷貝 大 的圖像,因為這會降低程式速度。
cv::Mat ReadImageToCVMat(const string& filename,
    const int height, const int width, const bool is_color) {//讀取圖檔到CVMat中,cv::Mat ,Mat資料結構式opencv2.0以後的特定的資料類型
  cv::Mat cv_img;
  int cv_read_flag = (is_color ? CV_LOAD_IMAGE_COLOR :
    CV_LOAD_IMAGE_GRAYSCALE);
  cv::Mat cv_img_origin = cv::imread(filename, cv_read_flag);//讀取圖檔内容
  if (!cv_img_origin.data) {
    LOG(ERROR) << "Could not open or find file " << filename;
    return cv_img_origin;
  }
  if (height > 0 && width > 0) {
    cv::resize(cv_img_origin, cv_img, cv::Size(width, height));
  } else {
    cv_img = cv_img_origin;
  }
  return cv_img;
}

cv::Mat ReadImageToCVMat(const string& filename,//讀取圖檔到CVMat中,重載1
    const int height, const int width) {
  return ReadImageToCVMat(filename, height, width, true);
}

cv::Mat ReadImageToCVMat(const string& filename,//讀取圖檔到CVMat中,重載2
    const bool is_color) {
  return ReadImageToCVMat(filename, 0, 0, is_color);
}

cv::Mat ReadImageToCVMat(const string& filename) {//讀取圖檔到CVMat中,重載3
  return ReadImageToCVMat(filename, 0, 0, true);
}
// Do the file extension and encoding match?
static bool matchExt(const std::string & fn, //比對拓展名稱?
                     std::string en) {
  size_t p = fn.rfind('.');//查找"."字元,若找到則傳回“.”在字元串中的位置,找不到則傳回npos
  std::string ext = p != fn.npos ? fn.substr(p) : fn;//如果字元串fn中存在".“,則截取字元串p
  std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);//把ext變成小寫
  std::transform(en.begin(), en.end(), en.begin(), ::tolower);
  if ( ext == en )
    return true;
  if ( en == "jpg" && ext == "jpeg" )
    return true;
  return false;
}

bool ReadImageToDatum(const string& filename, const int label,//把圖檔讀到 Datum中
    const int height, const int width, const bool is_color,
    const std::string & encoding, Datum* datum) {
  cv::Mat cv_img = ReadImageToCVMat(filename, height, width, is_color);//先把資料讀到cv::Mat類型矩陣中
  if (cv_img.data) {//Mat矩陣中資料指針Mat.data是uchar類型指針,矩陣中的元素應該是uchar類型;該語句是判斷cv_img中是否有資料
    if (encoding.size()) {//是否需要編碼
      if ( (cv_img.channels() == 3) == is_color && !height && !width &&
          matchExt(filename, encoding) )
        return ReadFileToDatum(filename, label, datum);

      std::vector<uchar> buf;
      cv::imencode("."+encoding, cv_img, buf);//感覺這行代碼的作用是把cv_img中的值指派給buf
      datum->set_data(std::string(reinterpret_cast<char*>(&buf[0]),
                      buf.size()));
      datum->set_label(label);
      datum->set_encoded(true);//感覺是一種編碼函數
      return true;
    }
    CVMatToDatum(cv_img, datum);
    datum->set_label(label);
    return true;
  } else {
    return false;
  }
}

bool ReadFileToDatum(const string& filename, const int label,
    Datum* datum) {
  std::streampos size;

  fstream file(filename.c_str(), ios::in|ios::binary|ios::ate);
  if (file.is_open()) {
    size = file.tellg();
    std::string buffer(size, ' ');
    file.seekg(0, ios::beg);
    file.read(&buffer[0], size);
    file.close();
    datum->set_data(buffer);
    datum->set_label(label);
    datum->set_encoded(true);
    return true;
  } else {
    return false;
  }
}

cv::Mat DecodeDatumToCVMatNative(const Datum& datum) {
  cv::Mat cv_img;
  CHECK(datum.encoded()) << "Datum not encoded";
  const string& data = datum.data();
  std::vector<char> vec_data(data.c_str(), data.c_str() + data.size());
  cv_img = cv::imdecode(vec_data, -1);
  if (!cv_img.data) {
    LOG(ERROR) << "Could not decode datum ";
  }
  return cv_img;
}
cv::Mat DecodeDatumToCVMat(const Datum& datum, bool is_color) {
  cv::Mat cv_img;
  CHECK(datum.encoded()) << "Datum not encoded";
  const string& data = datum.data();
  std::vector<char> vec_data(data.c_str(), data.c_str() + data.size());
  int cv_read_flag = (is_color ? CV_LOAD_IMAGE_COLOR :
    CV_LOAD_IMAGE_GRAYSCALE);
  cv_img = cv::imdecode(vec_data, cv_read_flag);
  if (!cv_img.data) {
    LOG(ERROR) << "Could not decode datum ";
  }
  return cv_img;
}

// If Datum is encoded will decoded using DecodeDatumToCVMat and CVMatToDatum
// If Datum is not encoded will do nothing
bool DecodeDatumNative(Datum* datum) {
  if (datum->encoded()) {
    cv::Mat cv_img = DecodeDatumToCVMatNative((*datum));
    CVMatToDatum(cv_img, datum);
    return true;
  } else {
    return false;
  }
}
bool DecodeDatum(Datum* datum, bool is_color) {
  if (datum->encoded()) {
    cv::Mat cv_img = DecodeDatumToCVMat((*datum), is_color);
    CVMatToDatum(cv_img, datum);
    return true;
  } else {
    return false;
  }
}

void CVMatToDatum(const cv::Mat& cv_img, Datum* datum) {
  CHECK(cv_img.depth() == CV_8U) << "Image data type must be unsigned byte";
  datum->set_channels(cv_img.channels());
  datum->set_height(cv_img.rows);
  datum->set_width(cv_img.cols);
  datum->clear_data();
  datum->clear_float_data();
  datum->set_encoded(false);
  int datum_channels = datum->channels();
  int datum_height = datum->height();
  int datum_width = datum->width();
  int datum_size = datum_channels * datum_height * datum_width;
  std::string buffer(datum_size, ' ');
  for (int h = 0; h < datum_height; ++h) {
    const uchar* ptr = cv_img.ptr<uchar>(h);
    int img_index = 0;
    for (int w = 0; w < datum_width; ++w) {
      for (int c = 0; c < datum_channels; ++c) {
        int datum_index = (c * datum_height + h) * datum_width + w;
        buffer[datum_index] = static_cast<char>(ptr[img_index++]);
      }
    }
  }
  datum->set_data(buffer);
}
。。。。。
           

五, 以上代碼注釋為個人了解,如有遺漏,錯誤還望大家多多交流,指正,以便共同學習,進步!!