1. 程式人生 > >c++調取inceptionv3網路實現影象分類

c++調取inceptionv3網路實現影象分類

這個例子是在看tensorflow裡面的官網提供的例子裡面看到的,總體來說比較簡單,首先是模型下載,最好是用wget的方式下載,我用curl下載失敗:

wget https://storage.googleapis.com/download.tensorflow.org/models/inception_v3_2016_08_28_frozen.pb.tar.gz

然後解壓,解壓之後是google訓練好的inceptionv3網路模型和標籤資料txt檔案,這網路原理相對簡單,加入了卷積核flat的思想,

cmake_minimum_required(VERSION 3.10)
project(cppexcise)

set(CMAKE_CXX_STANDARD 11)
link_directories(/Users/xxx/Documents/tensorflow/bazel-bin/tensorflow)
include_directories(
        /Users/xxx/Documents/tensorflow
        /Users/xxx/Documents/tensorflow/bazel-genfiles
        /Users/xxx/Documents/tensorflow/bazel-bin/tensorflow
        /Users/xxx/Downloads/eigen3)

add_executable(cppexcise main.cpp )
target_link_libraries(cppexcise  tensorflow_cc tensorflow_framework)

再看下main.cpp檔案,寫這些example的人一定是高手,c++寫的程式碼可讀性這麼強,各種異常考慮的非常清楚:

#include <fstream>
#include <utility>
#include <vector>

#include "tensorflow/cc/ops/const_op.h"
#include "tensorflow/cc/ops/image_ops.h"
#include "tensorflow/cc/ops/standard_ops.h"
#include "tensorflow/core/framework/graph.pb.h"
#include "tensorflow/core/framework/tensor.h"
#include "tensorflow/core/graph/default_device.h"
#include "tensorflow/core/graph/graph_def_builder.h"
#include "tensorflow/core/lib/core/errors.h"
#include "tensorflow/core/lib/core/stringpiece.h"
#include "tensorflow/core/lib/core/threadpool.h"
#include "tensorflow/core/lib/io/path.h"
#include "tensorflow/core/lib/strings/str_util.h"
#include "tensorflow/core/lib/strings/stringprintf.h"
#include "tensorflow/core/platform/env.h"
#include "tensorflow/core/platform/init_main.h"
#include "tensorflow/core/platform/logging.h"
#include "tensorflow/core/platform/types.h"
#include "tensorflow/core/public/session.h"
#include "tensorflow/core/util/command_line_flags.h"

// These are all common classes it's handy to reference with no namespace.
using tensorflow::Flag;
using tensorflow::Tensor;
using tensorflow::Status;
using tensorflow::string;
using tensorflow::int32;

// Takes a file name, and loads a list of labels from it, one per line, and
// returns a vector of the strings. It pads with empty strings so the length
// of the result is a multiple of 16, because our model expects that.
Status ReadLabelsFile(const string& file_name, std::vector<string>* result,
                      size_t* found_label_count) {
    std::ifstream file(file_name);
    if (!file) {
        return tensorflow::errors::NotFound("Labels file ", file_name,
                                            " not found.");
    }
    result->clear();
    string line;
    while (std::getline(file, line)) {
        result->push_back(line);
    }
    *found_label_count = result->size();
    const int padding = 16;
    while (result->size() % padding) {
        result->emplace_back();
    }
    return Status::OK();
}

static Status ReadEntireFile(tensorflow::Env* env, const string& filename,
                             Tensor* output) {
    tensorflow::uint64 file_size = 0;
    TF_RETURN_IF_ERROR(env->GetFileSize(filename, &file_size));

    string contents;
    contents.resize(file_size);

    std::unique_ptr<tensorflow::RandomAccessFile> file;
    TF_RETURN_IF_ERROR(env->NewRandomAccessFile(filename, &file));

    tensorflow::StringPiece data;
    TF_RETURN_IF_ERROR(file->Read(0, file_size, &data, &(contents)[0]));
    if (data.size() != file_size) {
        return tensorflow::errors::DataLoss("Truncated read of '", filename,
                                            "' expected ", file_size, " got ",
                                            data.size());
    }
    output->scalar<string>()() = data.ToString();
    return Status::OK();
}

// Given an image file name, read in the data, try to decode it as an image,
// resize it to the requested size, and then scale the values as desired.
Status ReadTensorFromImageFile(const string& file_name, const int input_height,
                               const int input_width, const float input_mean,
                               const float input_std,
                               std::vector<Tensor>* out_tensors) {
    auto root = tensorflow::Scope::NewRootScope();
    using namespace ::tensorflow::ops;  // NOLINT(build/namespaces)

    string input_name = "file_reader";
    string output_name = "normalized";

    // read file_name into a tensor named input
    Tensor input(tensorflow::DT_STRING, tensorflow::TensorShape());
    TF_RETURN_IF_ERROR(
            ReadEntireFile(tensorflow::Env::Default(), file_name, &input));

    // use a placeholder to read input data
    auto file_reader =
            Placeholder(root.WithOpName("input"), tensorflow::DataType::DT_STRING);

    std::vector<std::pair<string, tensorflow::Tensor>> inputs = {
            {"input", input},
    };

    // Now try to figure out what kind of file it is and decode it.
    const int wanted_channels = 3;
    tensorflow::Output image_reader;
    if (tensorflow::str_util::EndsWith(file_name, ".png")) {
        image_reader = DecodePng(root.WithOpName("png_reader"), file_reader,
                                 DecodePng::Channels(wanted_channels));
    } else if (tensorflow::str_util::EndsWith(file_name, ".gif")) {
        // gif decoder returns 4-D tensor, remove the first dim
        image_reader =
                Squeeze(root.WithOpName("squeeze_first_dim"),
                        DecodeGif(root.WithOpName("gif_reader"), file_reader));
    } else if (tensorflow::str_util::EndsWith(file_name, ".bmp")) {
        image_reader = DecodeBmp(root.WithOpName("bmp_reader"), file_reader);
    } else {
        // Assume if it's neither a PNG nor a GIF then it must be a JPEG.
        image_reader = DecodeJpeg(root.WithOpName("jpeg_reader"), file_reader,
                                  DecodeJpeg::Channels(wanted_channels));
    }
    // Now cast the image data to float so we can do normal math on it.
    auto float_caster =
            Cast(root.WithOpName("float_caster"), image_reader, tensorflow::DT_FLOAT);
    // The convention for image ops in TensorFlow is that all images are expected
    // to be in batches, so that they're four-dimensional arrays with indices of
    // [batch, height, width, channel]. Because we only have a single image, we
    // have to add a batch dimension of 1 to the start with ExpandDims().
    auto dims_expander = ExpandDims(root, float_caster, 0);
    // Bilinearly resize the image to fit the required dimensions.
    auto resized = ResizeBilinear(
            root, dims_expander,
            Const(root.WithOpName("size"), {input_height, input_width}));
    // Subtract the mean and divide by the scale.
    Div(root.WithOpName(output_name), Sub(root, resized, {input_mean}),
        {input_std});

    // This runs the GraphDef network definition that we've just constructed, and
    // returns the results in the output tensor.
    tensorflow::GraphDef graph;
    TF_RETURN_IF_ERROR(root.ToGraphDef(&graph));

    std::unique_ptr<tensorflow::Session> session(
            tensorflow::NewSession(tensorflow::SessionOptions()));
    TF_RETURN_IF_ERROR(session->Create(graph));
    TF_RETURN_IF_ERROR(session->Run({inputs}, {output_name}, {}, out_tensors));
    return Status::OK();
}

// Reads a model graph definition from disk, and creates a session object you
// can use to run it.
Status LoadGraph(const string& graph_file_name,
                 std::unique_ptr<tensorflow::Session>* session) {
    tensorflow::GraphDef graph_def;
    Status load_graph_status =
            ReadBinaryProto(tensorflow::Env::Default(), graph_file_name, &graph_def);
    if (!load_graph_status.ok()) {
        return tensorflow::errors::NotFound("Failed to load compute graph at '",
                                            graph_file_name, "'");
    }
    session->reset(tensorflow::NewSession(tensorflow::SessionOptions()));
    Status session_create_status = (*session)->Create(graph_def);
    if (!session_create_status.ok()) {
        return session_create_status;
    }
    return Status::OK();
}

// Analyzes the output of the Inception graph to retrieve the highest scores and
// their positions in the tensor, which correspond to categories.
Status GetTopLabels(const std::vector<Tensor>& outputs, int how_many_labels,
                    Tensor* indices, Tensor* scores) {
    auto root = tensorflow::Scope::NewRootScope();
    using namespace ::tensorflow::ops;  // NOLINT(build/namespaces)

    string output_name = "top_k";
    TopK(root.WithOpName(output_name), outputs[0], how_many_labels);
    // This runs the GraphDef network definition that we've just constructed, and
    // returns the results in the output tensors.
    tensorflow::GraphDef graph;
    TF_RETURN_IF_ERROR(root.ToGraphDef(&graph));

    std::unique_ptr<tensorflow::Session> session(
            tensorflow::NewSession(tensorflow::SessionOptions()));
    TF_RETURN_IF_ERROR(session->Create(graph));
    // The TopK node returns two outputs, the scores and their original indices,
    // so we have to append :0 and :1 to specify them both.
    std::vector<Tensor> out_tensors;
    TF_RETURN_IF_ERROR(session->Run({}, {output_name + ":0", output_name + ":1"},
                                    {}, &out_tensors));
    *scores = out_tensors[0];
    *indices = out_tensors[1];
    return Status::OK();
}

// Given the output of a model run, and the name of a file containing the labels
// this prints out the top five highest-scoring values.
Status PrintTopLabels(const std::vector<Tensor>& outputs,
                      const string& labels_file_name) {
    std::vector<string> labels;
    size_t label_count;
    Status read_labels_status =
            ReadLabelsFile(labels_file_name, &labels, &label_count);
    if (!read_labels_status.ok()) {
        //LOG(ERROR) << read_labels_status;
        return read_labels_status;
    }
    const int how_many_labels = std::min(5, static_cast<int>(label_count));
    Tensor indices;
    Tensor scores;
    TF_RETURN_IF_ERROR(GetTopLabels(outputs, how_many_labels, &indices, &scores));
    tensorflow::TTypes<float>::Flat scores_flat = scores.flat<float>();
    tensorflow::TTypes<int32>::Flat indices_flat = indices.flat<int32>();
    for (int pos = 0; pos < how_many_labels; ++pos) {
        const int label_index = indices_flat(pos);
        const float score = scores_flat(pos);
        LOG(INFO) << labels[label_index] << " (" << label_index << "): " << score;
    }
    return Status::OK();
}

// This is a testing function that returns whether the top label index is the
// one that's expected.
Status CheckTopLabel(const std::vector<Tensor>& outputs, int expected,
                     bool* is_expected) {
    *is_expected = false;
    Tensor indices;
    Tensor scores;
    const int how_many_labels = 1;
    TF_RETURN_IF_ERROR(GetTopLabels(outputs, how_many_labels, &indices, &scores));
    tensorflow::TTypes<int32>::Flat indices_flat = indices.flat<int32>();
    if (indices_flat(0) != expected) {
        LOG(ERROR) << "Expected label #" << expected << " but got #"
                   << indices_flat(0);
        *is_expected = false;
    } else {
        *is_expected = true;
    }
    return Status::OK();
}

int main(int argc, char* argv[]) {
    // These are the command-line flags the program can understand.
    // They define where the graph and input data is located, and what kind of
    // input the model expects. If you train your own model, or use something
    // other than inception_v3, then you'll need to update these.
    string image = "/Users/xxx/Downloads/inception_v3_model/grace_hopper.jpg";
    string graph =
            "/Users/xxx/Downloads/inception_v3_model/inception_v3_2016_08_28_frozen.pb";
    string labels =
            "/Users/xxx/Downloads/inception_v3_model/imagenet_slim_labels.txt";
    int32 input_width = 299;
    int32 input_height = 299;
    float input_mean = 0;
    float input_std = 255;
    string input_layer = "input";
    string output_layer = "InceptionV3/Predictions/Reshape_1";
    bool self_test = false;
    string root_dir = "";
    std::vector<Flag> flag_list = {
            Flag("image", &image, "image to be processed"),
            Flag("graph", &graph, "graph to be executed"),
            Flag("labels", &labels, "name of file containing labels"),
            Flag("input_width", &input_width, "resize image to this width in pixels"),
            Flag("input_height", &input_height,
                 "resize image to this height in pixels"),
            Flag("input_mean", &input_mean, "scale pixel values to this mean"),
            Flag("input_std", &input_std, "scale pixel values to this std deviation"),
            Flag("input_layer", &input_layer, "name of input layer"),
            Flag("output_layer", &output_layer, "name of output layer"),
            Flag("self_test", &self_test, "run a self test"),
            Flag("root_dir", &root_dir,
                 "interpret image and graph file names relative to this directory"),
    };
    string usage = tensorflow::Flags::Usage(argv[0], flag_list);
    const bool parse_result = tensorflow::Flags::Parse(&argc, argv, flag_list);
    if (!parse_result) {
        LOG(ERROR) << usage;
        return -1;
    }

    // We need to call this to set up global state for TensorFlow.
    tensorflow::port::InitMain(argv[0], &argc, &argv);
    if (argc > 1) {
        LOG(ERROR) << "Unknown argument " << argv[1] << "\n" << usage;
        return -1;
    }

    // First we load and initialize the model.
    std::unique_ptr<tensorflow::Session> session;
    string graph_path = tensorflow::io::JoinPath(root_dir, graph);
    Status load_graph_status = LoadGraph(graph_path, &session);
    if (!load_graph_status.ok()) {
        //LOG(ERROR) << load_graph_status;
        return -1;
    }

    // Get the image from disk as a float array of numbers, resized and normalized
    // to the specifications the main graph expects.
    std::vector<Tensor> resized_tensors;
    string image_path = tensorflow::io::JoinPath(root_dir, image);
    Status read_tensor_status =
            ReadTensorFromImageFile(image_path, input_height, input_width, input_mean,
                                    input_std, &resized_tensors);
    if (!read_tensor_status.ok()) {
       // LOG(ERROR) << read_tensor_status;
        return -1;
    }
    const Tensor& resized_tensor = resized_tensors[0];

    // Actually run the image through the model.
    std::vector<Tensor> outputs;
    Status run_status = session->Run({{input_layer, resized_tensor}},
                                     {output_layer}, {}, &outputs);
    if (!run_status.ok()) {
        LOG(ERROR) << "Running model failed: " << run_status;
        return -1;
    }

    // This is for automated testing to make sure we get the expected result with
    // the default settings. We know that label 653 (military uniform) should be
    // the top label for the Admiral Hopper image.
    if (self_test) {
        bool expected_matches;
        Status check_status = CheckTopLabel(outputs, 653, &expected_matches);
        if (!check_status.ok()) {
            LOG(ERROR) << "Running check failed: " << check_status;
            return -1;
        }
        if (!expected_matches) {
            LOG(ERROR) << "Self-test failed!";
            return -1;
        }
    }

    // Do something interesting with the results we've generated.
    Status print_status = PrintTopLabels(outputs, labels);
    if (!print_status.ok()) {
        LOG(ERROR) << "Running print failed: " << print_status;
        return -1;
    }

    return 0;
}

military uniform (653): 0.834306
mortarboard (668): 0.0218694
academic gown (401): 0.010358
pickelhaube (716): 0.00800816
bulletproof vest (466): 0.00535089

在上面的目錄還提供python載入模型py,更改下模型、標籤路徑也可以執行,不過是基於python3.5的,注意下版本可執行