Image Classification with Tensorflow and C++
How to build, export and load the model

Often, neural networks models are built and trained in a more friendly language, such as Python. However, to deploy and use such model, a more efficient language can be preffered. C++ is considered one of the fastest programming languages out there, making it a strong candidate for such task.

The repository with the source code of this repository can be found here.

Requirements

  • Python 3 with tensorflow 2 installed, as well as OpenCV
  • Cppflow, a library that wraps the tensorflow C API for use in C++

Folder Structure

This is very important, since C++ configuration files can get quite messy, and the libraries and headers can go missing.

Download the cppflow library, and create a folder inside it, and create all the project files inside of it:

cppflow/
    include/
    src/
    my-folder/
        ... all files here ...

Simple Python Model

Let's define a simple python model and save it in the SavedModel format

import tensorflow as tf
tf.__version__ # Make sure Tensorflow 2

IMAGE_SIZE = 150
FOLDER='model'

# Define model
inputs = tf.keras.Input(shape=(IMAGE_SIZE,IMAGE_SIZE,3), dtype=tf.float32, name='input_layer')
x = tf.keras.layers.Flatten()(inputs)
x = tf.keras.layers.Dense(4, activation=tf.nn.relu)(x)
outputs = tf.keras.layers.Dense(5, activation=tf.nn.softmax, name='output_layer')(x)
model = tf.keras.Model(inputs=inputs, outputs=outputs)

# Train it...

# Save model
tf.saved_model.save(model, FOLDER)

Load the Model Using Cppflow

Make sure the tensorflow C API is installed properly.

Create a main.cpp file with this minimal code to load the Tensorflow model and run the prediction on an RGB image.

First, we load the model and declare the input and output tensor names

// Initialize neural network
std::cout<<"Current tensorflow version: "<< TF_Version() << std::endl;
Model m("model");

// Input and output Tensors
Tensor input(m, "serving_default_input_layer");
Tensor prediction(m, "StatefulPartitionedCall");

serving_default_input_layer is the input tensor name, and StatefulPartitionedCall is the output tensor name. To discover these names, you can use the SavedModel CLI tool.

Next, read the image and pass its data to a std::vector

// Read image and convert to float
Mat image = cv::imread("intel-dataset/8.jpg");
image.convertTo(image, CV_32F, 1.0/255.0);

// Image dimensions
int rows = image.rows;
int cols = image.cols;
int channels = image.channels();
int total = image.total();

// Assign to vector for 3 channel image
// Souce: https://stackoverflow.com/a/56600115/2076973
Mat flat = image.reshape(1, image.total() * channels);

std::vector<float> img_data(IMG_SIZE*IMG_SIZE*3);
img_data = image.isContinuous()? flat : flat.clone();

Finally, pass that vector to the input tensor, and call model.run to predict the output

// Feed data to input tensor
input.set_data(img_data, {1, rows, cols, channels});

// Run and show predictions
m.run(input, prediction);

// Get tensor with predictions
std::vector<float> predictions = prediction.Tensor::get_data<float>();
for(int i=0; i<predictions.size(); i++)
    std::cout<< std::to_string(predictions[i]) << std::endl;

And we see the expected result

0.118159
0.134306
0.429265
0.195421
0.122850

In this case, the model wasn't trained, so the output doesn't make any real prediction. However, we do get 5 float values, that sum up to 1, as is expected from our last layer, with 5 neurons and a softmax activation.