Protobuf基本用法

本文介绍了protobuf的基本用法,特别是在Caffe中的应用。主要内容包括protobuf的功能、优点,以及如何使用protobuf进行数据结构的定义、序列化与反序列化。通过示例展示了如何创建.proto文件,以及如何在C++代码中读写二进制和文本文件。最后,文章提到了将.caffemodel文件转换为可读文本文件的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文主要讲解protobuf的基本用法(caffe使用protobuf所用到的语法)

protobuf简介


protobuf功能是把某种数据结构的信息以某种格式保存起来。它主要用于文件存储以及传输协议格式等场合。protobuf的优点主要有

  • 性能好/效率高
  • 向前兼容和向前兼容
  • 支持多种语言



protobuf简单用法


假如我们有一个问题是关于:存储一个人的名字(name)以及唯一表示符(id)和邮箱(email)以及它的电话号码(number)和此电话号码所在的类型(PhoneType)。并且需要将其保存在二进制文件中或者txt文件中,如果需要还需要将其从二进制文件中或者txt文件中读取,我们如何使用protobuf去实现它呢?

  • 首先我们需要创建一个caffe.proto文件,文件中的内容为:
#caffe.proto
syntax = "proto2";
package caffe;

message Person {
    required string name = 1;
    required int32 id = 2;
    optional string email = 3;

    enum PhoneType {
        MOBILE = 0;
        HOME = 1;
        WORK = 2;
    }

    message PhoneNumber {
        required string number = 1;
        optional PhoneType type = 2 [default = HOME];
    }

    repeated PhoneNumber phone = 4;
}

syntax声明使用的语法规则,proto2相当于python3和python2的区别,所以为了正确解析,我们需要声明正确的proto版本。
message表示消息,相当于c++中的一个类Person。required表示必须字段,而optional表示可选字段,repeated表示可重复字段(每一个人都有多个电话号码),这些关键字紧接着的关键字(string int32 etc.)为我们声明的变量的类型。



  • 第二部需要创建一个主函数(main.cpp),主要功能是演示序列化一个Person类,并且写入文件中,main.cpp文件的主要内容为:
#include <iostream>
#include <string>
#include "caffe.pb.h"
#include <google/protobuf/io/zero_copy_stream_impl.h>
#include <glog/logging.h>
#include <google/protobuf/io/coded_stream.h>
#include <google/protobuf/io/zero_copy_stream_impl.h>
#include <google/protobuf/text_format.h>
#include <fcntl.h>
#include <unistd.h>
#include <fstream>

const int kProtoReadBytesLimit = INT_MAX;

using google::protobuf::io::FileInputStream;
using google::protobuf::io::FileOutputStream;
using google::protobuf::io::ZeroCopyInputStream;
using google::protobuf::io::CodedInputStream;
using google::protobuf::io::ZeroCopyOutputStream;
using google::protobuf::io::CodedOutputStream;
using google::protobuf::Message;


using namespace std;

//从txt文件中读取proto消息函数 成功返回true
bool ReadProtoFromTextFile(const char* filename, Message* proto) {
    //以只读方式(O_RDONLY)打开filename文件
    int fd = open(filename, O_RDONLY);
    //使用glog库中的CHECK_NE函数检查是否读取文件成功
    CHECK_NE(fd, -1) << "File not found: " << filename;
    //使用FileInputStream流读取文件
    FileInputStream* input = new FileInputStream(fd);
    //使用Text格式解析input输入,并将读取到的数据赋值到proto上
    bool success = google::protobuf::TextFormat::Parse(input, proto);
    //释放input对象
    delete input;
    //关闭stream流
    close(fd);
    //返回是否成功
    return success;
}

//将proto消息写入txt文件中
void WriteProtoToTextFile(const Message& proto, const char* filename) {
    //0644表示文件的权限
    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);
}

//从二进制文件中读取proto消息函数 成功返回true
bool ReadProtoFromBinaryFile(const char* filename, Message* 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);
    //proto文件读取二进制默认最大大小为64M,所以我们需要设置通过SetTotalBytesLimit函数设置一下最大值,其中kProtoReadBytesLimit设置上限,而第二个参数536870912设置读取文件超过这个值会产生警报
    coded_input->SetTotalBytesLimit(kProtoReadBytesLimit, 536870912);
    bool success = proto->ParseFromCodedStream(coded_input);
    delete coded_input;
    delete raw_input;
    close(fd);
    return success;
}

//向二进制文件中写入proto消息函数
void WriteProtoToBinaryFile(const Message& proto, const char* filename) {
    fstream output(filename, ios::out | ios::trunc | ios::binary);
    CHECK(proto.SerializeToOstream(&output));
}


int main(int argc, char* argv[]) {
    GOOGLE_PROTOBUF_VERIFY_VERSION;

    //调用caffe.pb.h中的Person类,并设置id以及name,通过调用add_phone()来添加number
    caffe::Person person;
    person.set_id(10421);
    person.set_name("hopo");
    person.set_email("2381892713@qq.com");

    //添加person中的phone 默认类型为HOME
    caffe::Person::PhoneNumber* phonenumber = person.add_phone();
    phonenumber->set_number("17601001773");

    phonenumber = person.add_phone();
    phonenumber->set_number("13261009856");
    phonenumber->set_type(caffe::Person::PhoneType::Person_PhoneType_WORK);

    phonenumber = person.add_phone();
    phonenumber->set_number("15660195460");
    phonenumber->set_type(caffe::Person::PhoneType::Person_PhoneType_MOBILE);


    WriteProtoToTextFile(person, "../re.txt");

    string serializedStr;
    //序列化person到serializedStr中
    person.SerializeToString(&serializedStr);
    cout<<"serialization result: "<<serializedStr<<endl;
    cout<<endl<<"debugString: "<<person.DebugString();




    //通过serializedStr反序列化
    //从serializedStr中读取person
    caffe::Person deserializedPerson;
    if(!deserializedPerson.ParseFromString(serializedStr)) {
        cerr << "Failed to parse student." <<endl;
        return -1;
    }


    cout<<"person id: "<<deserializedPerson.id()<<endl;
    cout<<"person name: "<<deserializedPerson.name()<<endl;
    cout<<"person email: "<<deserializedPerson.email()<<endl;
    for (int i = 0; i < deserializedPerson.phone_size(); i++){
        const caffe::Person_PhoneNumber phone_number = deserializedPerson.phone(i);
        switch (phone_number.type()) {
            case caffe::Person::HOME:
                cout<<"Home phone #: ";
                break;
            case caffe::Person::WORK:
                cout<<"Work #: ";
                break;
            case caffe::Person::MOBILE:
                cout<<"Mobile #: ";
                break;
        }
        cout<<phone_number.number()<<endl;
    }


    google::protobuf::ShutdownProtobufLibrary();

}

在main.cpp文件中,我们通过ReadProtoFromTextFile(),WriteProtoToTextFile(),ReadProtoFromBinaryFile(), WriteProtoToBinaryFile()四个函数分别实现的是从txt中读取, 写入txt文件,从二进制文件中读取,写入二进制文件中。



  • cmake文件通过caffe.protof文件生成caffe.pb.h和caffe.pb.cc文件,CMakeLists.txt文件内容如下:
cmake_minimum_required(VERSION 3.12)
project(protobuf)

set(CMAKE_CXX_STANDARD 14)

#寻找Protobuf库文件
find_package(Protobuf REQUIRED)


include_directories(${PROTOBUF_INCLUDE_DIRS})
include_directories(${CMAKE_CURRENT_BINARY_DIR})

#通过student.proto文件分别
protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS caffe.proto)

add_executable(protobuf main.cpp ${PROTO_SRCS} ${PROTO_HDRS})

target_link_libraries(protobuf glog ${PROTOBUF_LIBRARIES})

其中的protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS caffe.proto)将通过caffe.protof文件生成caffe.pb.h和caffe.pb.cc,生成文件的路径为运行CMakeLists.txt文件所在文件夹。由于我使用的为CLion,最终生成的文件路径在cmake-build-debug文件夹下,所以我需要包含CMAKE_CURRENT_BINARY_DIR所指向的路径,通过include_directories(${CMAKE_CURRENT_BINARY_DIR})包含。同时PROTO_SRCS和PROTO_HDRS变量分别表示生成的头文件以及源文件,也就是caffe.pb.cc和caffe.pb.h文件




总结


学习protobuf主要原因是学习caffe源码,在caffe中,主要通过protobuf读取模型以及写入模型,所以仅仅需要知道protobuf的基本用法。而上述函数中的ReadProtoFromTextFile()函数是直接从caffe源码中copy过来的,如果你想将caffe中的caffemodel文件转换成可读文件,可以通过ReadProtoFromBinaryFile()从.caffemodel读取文件,然后通过WriteProtoToTextFile()函数写入到txt文件中,最终打开生成txt文件我们就可以看到可视化的网络模型。可以发现caffemode仅仅比deploy.txt网络多出来网络中的参数。
下面是实现读取caffemodel文件,然后写入txt文件中的具体过程:
我们重新创建一个main.cpp文件,文件内容如下:

#include <caffe/caffe.hpp>
#include <google/protobuf/io/coded_stream.h>
#include <google/protobuf/io/zero_copy_stream_impl.h>
#include <google/protobuf/text_format.h>
#include <algorithm>
#include <iosfwd>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include <iostream>
#include "caffe/common.hpp"
#include "caffe/proto/caffe.pb.h"
#include "caffe/util/io.hpp"

using namespace caffe;
using namespace std;
using google::protobuf::io::FileInputStream;
using google::protobuf::io::FileOutputStream;
using google::protobuf::io::ZeroCopyInputStream;
using google::protobuf::io::CodedInputStream;
using google::protobuf::io::ZeroCopyOutputStream;
using google::protobuf::io::CodedOutputStream;
using google::protobuf::Message;

int main()
{
    //NetParameter为caffe目录下caffe.pb.h文件中的
    NetParameter proto;
    ReadProtoFromBinaryFile("/home/cvlab/files/caffe-master/data/mnist/lenet_iter_10000.caffemodel", &proto);
    WriteProtoToTextFile(proto, "/home/cvlab/files/caffe-master/data/mnist/test.txt");
    return 0;
}

需要注意的是:传入函数中的参数为&proto,但是proto是一个抽象类,所以我们需要通过声明一个NetParameter来传入到函数中,此参数在caffe/proto/caffe.pb.h文件中,然后在CMakeLists.txt中需要声明包含caffe目录caffe.pb.h所在的目录。最终重新编译运行,即可获得最终的结果。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值