dlib is a famous c++ machine learning library and Matlab is a numeric computing platform that provide many algorithms and tools to do researches. There are lot of ML algorithms in dlib that Matlab doesn't have. But still we can use the dlib algorithms in Matlab by compiling the dlib functions as Mex files. In this post I am discussing about the official approach to make a dlib wrapper to matlab.
- +dlib/
- -- private/ # C++ source files
- -- build/ # Makefiles that generated by cmake
- -- .gitignore # List of ignored file names and folder names.
- -- *.m # Matlab classes linked to Mex files
- -- *.mexa64 # Built mex files
- demo.m # Matlab file to test the wrapper
You can find an example dlib wrapper in
dlib/matlab
folder of the dlib Github repository. Copy the following files from this
folder to the +dlib/private
folder of your workspace.
CMakeLists.txt
cmake_mex_wrapper
mex_wrapper.cpp
call_matlab.h
Change the CMakeLists.txt
to find dlib from the include path instead
from an absolute path.
# +dlib/private/CMakeLists.txt
# Remove the following line
add_subdirectory(.. dlib_build)
# And add the following line
find_package(dlib REQUIRED)
And remove following lines from the CMakeLists.txt
to start our
project as a new one.
# +dlib/private/CMakeLists.txt
add_mex_function(example_mex_function dlib::dlib)
add_mex_function(example_mex_callback dlib::dlib)
add_mex_function(example_mex_struct dlib::dlib)
add_mex_function(example_mex_class dlib::dlib)
By default the built Mex file is saving to the source directory. But our
source directory is a separate folder named private
. So we need to save the
Mex file to the parent directory of the private
folder. Change
CMakeLists.txt
as following to change it.
# +dlib/private/CMakeLists.txt
// ...
# You can tell cmake where to put the mex files when you run 'make install' by
# setting this variable. The path is relative to this CMakeLists.txt file.
get_filename_component(MEX_INSTALL_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../" ABSOLUTE)
set(install_target_output_folder ${MEX_INSTALL_PATH})
If you are using linux, change the value of MATLAB_EXECUTABLE
in the
cmake_mex_wrapper
file.
# +dlib/private/cmake_mex_wrapper
find_program(MATLAB_EXECUTABLE matlab PATHS
"/usr/local/MATLAB/*/bin"
)
We are going to build the dlib example project as a standalone project.
But it made to compile as a component in dlib project. So there are some
relative includes in the mex_wrapper.cpp
file. Change all of these
relative includes as the following.
# +dlib/private/mex_wrapper.cpp
// ...
// Change following lines
#include "../matrix.h"
#include "../array2d.h"
#include "../array.h"
#include "../image_transforms.h"
#include "../is_kind.h"
#include "../string.h"
#include "../any.h" // for sig_traits
#include "../hash.h"
// As in the following
#include <dlib/matrix.h>
#include <dlib/array2d.h>
#include <dlib/array.h>
#include <dlib/image_transforms.h>
#include <dlib/is_kind.h>
#include <dlib/string.h>
#include <dlib/any.h> // for sig_traits
#include <dlib/hash.h>
And add the dlib
namespace to the mex_wrapper.cpp
file.
# +dlib/private/mex_wrapper.cpp
// All includes goes here
//..
using namespace dlib;
As the start create a C++ file to implement the wrapper to your dlib function. In this example I am implementing a wrapper to the dlib CNN face detector. Because it is more accurate than the Matlab built-in face detector. I am creating a C++ class file and copying the contents of the dlib/matlab/examplemexclass.cpp file to my new class file. And I am removing the inner contents and comments from the file.
# +dlib/private/CNNFaceDetectorMex.cpp
#include <iostream>
using namespace std;
class CNNFaceDetector
{
public:
CNNFaceDetector()
{
}
void test(){
cout << "Yeah! It worked";
}
};
#define MEX_CLASS_NAME CNNFaceDetector
#define MEX_CLASS_METHODS test
#include "mex_wrapper.cpp"
I am including it to the CMakeLists.txt
to compile as a Mex file.
# +dlib/private/CMakeLists.txt
# ...
add_mex_function(CNNFaceDetectorMex dlib::dlib)
Create an another CMakeLists.txt
file in the root folder to tell cmake
to compile the +dlib/private
folder.
# CMakeLists.txt
add_subdirectory(+dlib/private)
Now compile the project using following commands.
$ mkdir build
$ cd build
$ cmake .. -DCMAKE_INSTALL_PREFIX=../
$ make
$ make install
You can find the compiled Mex file in the +dlib
folder.
The Mex files are not classes. These files are acting like functions. But we can convert Mex files to classes by wrapping the Mex file in a Matlab class.
Run the Mex file as a function in the Matlab command line to generate the contents of the Matlab class file.
>> dlib.CNNFaceDetectorMex()
This command is generating the contents that should be in the Matlab class file. Copy this contents to your matlab class file.
# +dlib/CNNFaceDetector.m
classdef CNNFaceDetector < handle
properties (Access = private)
cpp_ptr
end
methods
function this = CNNFaceDetector()
this.cpp_ptr = CNNFaceDetectorMex('construct');
end
function copied_obj = clone(this)
%Returns a new independent object that is a copy of this.
copied_obj = CNNFaceDetector();
copied_obj.cpp_ptr = CNNFaceDetectorMex(this.cpp_ptr,'clone');
end
function varargout = test(this, varargin)
[varargout{1:nargout}] = CNNFaceDetectorMex(this.cpp_ptr, 1, varargin{:});
end
end
methods(Access=private)
function delete(this)
CNNFaceDetectorMex(this.cpp_ptr);
end
end
end
Now create an instance of this class in the demo.m
file and try to run
it.
# demo.m
detector = dlib.CNNFaceDetector();
detector.test()
I got the following error by running the script.
>> demo
Warning: The following error was caught while executing 'dlib.CNNFaceDetector' class destructor:
Undefined function 'CNNFaceDetectorMex' for input arguments of type 'double'.
Error in dlib.CNNFaceDetector/delete (line 25)
CNNFaceDetectorMex(this.cpp_ptr);
Error in demo (line 1)
detector = dlib.CNNFaceDetector();
> In demo (line 1)
'CNNFaceDetectorMex' is not found in the current folder or on the MATLAB path, but exists in:
/home/ramesh/Researches/EyeTouch/facetrack/+dlib
Change the MATLAB current folder or add its folder to the MATLAB path.
Error in dlib.CNNFaceDetector (line 8)
this.cpp_ptr = CNNFaceDetectorMex('construct');
Error in demo (line 1)
detector = dlib.CNNFaceDetector();
This error means the CNNFaceDetectorMex
function is not in the current
namespace. But it is available in the dlib
namespace. So I changed the
all CNNFaceDetectorMex
words to dlib.CNNFaceDetectorMex
in the
CNNFaceDetector.m
file.
# +dlib/CNNFaceDetector.m
classdef CNNFaceDetector < handle
properties (Access = private)
cpp_ptr
end
methods
function this = CNNFaceDetector()
this.cpp_ptr = dlib.CNNFaceDetectorMex('construct');
end
function copied_obj = clone(this)
%Returns a new independent object that is a copy of this.
copied_obj = CNNFaceDetector();
copied_obj.cpp_ptr = dlib.CNNFaceDetectorMex(this.cpp_ptr,'clone');
end
function varargout = test(this, varargin)
[varargout{1:nargout}] = dlib.CNNFaceDetectorMex(this.cpp_ptr, 1, varargin{:});
end
end
methods(Access=private)
function delete(this)
dlib.CNNFaceDetectorMex(this.cpp_ptr);
end
end
end
Finally I could successfully run the demo.m
.
>> demo
Yeah! It worked>>
Do not forget to repeat this step in each time you changed the Mex class methods.
As in this
example I downloaded the trained
dataset
and extracted the .dat
file to data
folder.
After I created a network in the CNNFaceDetectorMex.cpp
file as in the
above example.
# +dlib/private/CNNFaceDetectorMex.cpp
// ...
#include <dlib/dnn.h>
// ...
using namespace dlib;
template <long num_filters, typename SUBNET>
using con5d = con<num_filters, 5, 5, 2, 2, SUBNET>;
template <long num_filters, typename SUBNET>
using con5 = con<num_filters, 5, 5, 1, 1, SUBNET>;
template <typename SUBNET>
using downsampler = relu<affine<
con5d<32, relu<affine<con5d<32, relu<affine<con5d<16, SUBNET>>>>>>>>>;
template <typename SUBNET> using rcon5 = relu<affine<con5<45, SUBNET>>>;
using net_type = loss_mmod<
con<1, 9, 9, 1, 1,
rcon5<rcon5<
rcon5<downsampler<input_rgb_image_pyramid<pyramid_down<6>>>>>>>>;
class CNNFaceDetector
{
// ...
private:
net_type _net;
}
Next implemented a new method to train the detector and initialize it.
deleted the old method train
# +dlib/private/CNNFaceDetector.cpp
// ...
#include <dlib/image_processing/full_object_detection.h>
// ...
class CNNFaceDetector
{
public:
// ...
void train(string fd_file_name) { deserialize(fd_file_name) >> _net; }
}
// ...
#define MEX_CLASS_METHODS train
I compiled these changes again and repeated the Mex class creating step.
Then I edited the demo.m
as following.
# demo.m
detector = dlib.CNNFaceDetector();
detector.train('./data/mmod_human_face_detector.dat');
It executed without any error.
Next I implemented a method to do the facial detection. This method is taking a RGB picture as an input and Returning a list of bounding boxes of detected faces.
# +dlib/private/CNNFaceDetectorMex.cpp
// ...
#include <dlib/matrix/matrix.h>
#include <dlib/pixel.h>
// ...
class CNNFaceDetector
{
public:
// ...
void detect(const matrix<rgb_pixel> &img,
std::vector<matrix_colmajor> &bboxes) {
std::vector<mmod_rect> rects = _net(img);
if (rects.size() > 0) {
matrix_colmajor bbox = matrix_colmajor();
bbox.set_size(1, 4);
for (int i = 0; i < rects.size(); i++) {
mmod_rect rect = rects[i];
long t = rect.rect.top();
long l = rect.rect.left();
bbox(0, 0) = l;
bbox(0, 1) = t;
bbox(0, 2) = rect.rect.right() - l;
bbox(0, 3) = rect.rect.bottom() - t;
bboxes.push_back(bbox);
}
}
}
// ...
}
// ...
#define MEX_CLASS_METHODS train, detect
After compiling it I repeated the Mex class creating step again.
And I downloaded an image to the input
folder and changed the demo.m
file as following.
# demo.m
% ...
I = imread("./input/students.jpg");
faces = detector.detect(I);
After running the above script I got the following error.
>> demo
Error using CNNFaceDetectorMex
mex_function has some bug in it related to processing input argument 1
Error in dlib.CNNFaceDetector/detect (line 22)
[varargout{1:nargout}] = dlib.CNNFaceDetectorMex(this.cpp_ptr, 2, varargin{:});
Error in demo (line 5)
faces = detector.detect(I);
This error is causing because there is no method to cast a Matlab image
to matrix<rgb_pixel>
. I implemented a new method in the
mex_wrapper.cpp
to cast a Matlab image to dlib matrix.
# +dlib/private/mex_wrapper.cpp
// After line 640
template <
long num_rows,
long num_cols,
typename mem_manager,
typename layout
>
void assign_image (
const long arg_idx,
matrix<dlib::rgb_pixel,num_rows,num_cols, mem_manager,layout>& img,
const dlib::uint8* data,
long nr,
long nc
)
{
img.set_size(nr, nc);
for (long c = 0; c < img.nc(); ++c)
for (long r = 0; r < img.nr(); ++r)
img(r,c).red = *data++;
for (long c = 0; c < img.nc(); ++c)
for (long r = 0; r < img.nr(); ++r)
img(r,c).green = *data++;
for (long c = 0; c < img.nc(); ++c)
for (long r = 0; r < img.nr(); ++r)
img(r,c).blue = *data++;
}
After compiling it, the matlab script was executed without any errors.
Again I changed the matlab script to see a graphical output of the result.
# demo.m
% ...
for i = 1:length(faces)
bbox = cell2mat(faces(i));
I = insertShape(I, 'Rectangle', bbox, 'LineWidth',5);
end
figure; imshow(I); title('Detected faces');
It gives me the following output.