In a
previous post, I demonstrated the basics of generating C/C++ code from a MATLAB function and how to use that code to create a dynamic library (DLL) that could be used in an external application. Today, I'd like to build upon that post and apply it to a more complex example. Let's say we have developed some code in MATLAB® using features from the
Computer Vision Toolbox to perform some image analysis, and would like to harness that power in an external C++ application. In this document, we'll demonstrate this workflow by creating a DLL from some MATLAB routines and calling them from an external C++ code.
MATLAB Image Processing Code
In this example, our code is performing feature detection on circuit boards to find points of interest (corners) which will be used by the external application for some process. In MATLAB, we need to load the image, detect some features and extract them. The extracted features will be used in our application, so we'll need access to the resulting array points which indicate the feature location.
The raw MATLAB code to perform this is:
% Image file name on the client side this will be a parameter
% Quality factor -- on the client side this will be a parameter
% Read the grayscale image
% Find corner candidates in the image
corners = detectHarrisFeatures(I, 'MinQuality', qFac);
% Extract the valid corers
[~,valid_corners] = extractFeatures(I,corners);
To validate the results on the MATLAB side, we'll overlay the valid corners we extracted onto the image. (Our client code will use these corners for other purposes.)
J = insertMarker(I, valid_corners, '+');
In order to make our MATLAB code suitable for generation and production, we'll put the above code into a function. This function will have
argument checking and an interface to pass back the corner points and a validity flag to the client routine. Here's the routine suitable for code generation:
function [X, valid] = cornerFinder(imFileName, qFac)
% Use Harris corner detector on the input file with a quality factor given
% by qFac.
% Copyright 2022 The MathWorks(R), Inc.
% Perform basic argument type checking
arguments(Input)
imFileName (1,:) char
qFac single
end
% Allocate types for outputs (single, uint32, boolean)
X=zeros(0,2, 'like', qFac);
valid = false;
% Ensure quality factor is in range.
if qFac < 0 || qFac > 1
fprintf("Error: Quality factor must be between 0 and 1.\n");
return;
end
% Test to see that the given file exists and is readable
fID = fopen(imFileName, "r");
if fID == -1
fprintf("Error: File %s not found.\n", imFileName);
return;
end
% Close file test
fclose(fID);
% Get the image from the disk
I = imread(imFileName);
% Compute and extract the corner features
corners = detectHarrisFeatures(I, 'MinQuality', qFac);
[~,valid_corners] = extractFeatures(I,corners);
% Store the corner positions in the output array for the client and
% record the number of points. Set the valid flag to true.
X = valid_corners.Location;
valid = true;
end
Generating Code and Creating a Dynamic Library for Our Function
To generate code for our function and compile it into a DLL for the Windows environment, we create a
coder.config object in the workspace. The computer vision calls require the code to be in
RowMajor mode, so we set that in our code configuration. Then we define dummy arguments for the two inputs:
- qFac: a single precision quality factor
- imFile: a variable-sized character array for the filename (here we use coder.typeof to specify this)
Finally, we generate code with the
codegen command. This will create a C code representation of the MATLAB routine which we will later compile into a dynamic library.
cfg = coder.config('dll');
% Define input argument types for coder
imFile = coder.typeof('a', [1,2048], [0, 1]);
% Call the coder to generate the dynamic library
codegen -config cfg cornerFinder.m -args {imFile, qFac} -nargout 2 -report
Code generation successful: View report
Looking into cornerFinder.h from the report shows the client interface for the generated library. It has the prototype for the cornerFinder C function. Note that the function has three input arguments, the name of the image file, it's dimensions and the quality factor. There are two outputs, the array of points and the valid flag. The important part of the interface code is here:
We'll use this info later to construct our client code.
Packaging the Library and Its Dependencies
Now that we've generated our library code, we need to do two things. First we need to provide instructions on how to build our library on the client side. To do this we'll use the
codebuild functionality to generate CMake artifacts. Second, we'll collect the code and all the dependent libraries and package them for distribution. We will use the
packNGo functionality from MATLAB. This will generate a zip file with all we need on the client side.
codebuild('./codegen/dll/cornerFinder', 'BuildMethod', 'CMake', 'BuildVariant', 'SHARED_LIBRARY');
packNGo('./codegen/dll/cornerFinder', 'packType', 'hierarchical', 'fileName', 'myCornerFinderExample');
This sequence of commands generates what we'll need to pass to our client. The codebuild command creates a CMakeLists.txt file which we will eventually use to create a DLL from our code. The packNGo command generates a file called myCornerFinderExample.zip. Inside this file are two other zip files, mlrFiles.zip and sDirFiles.zip. The first of these contains all the MATLAB runtime files and libraries needed for the computer vision functionality, and the second contains the generated code. On my machine, I now have this in my Current Folder:
Using Our Archive to Build a DLL with CMake
Once we have our archive, we can hand it to our friend who will compile it. They begin by unzipping the archive and its subarchives. This produces a directory tree that looks like this:
Once they have this, they can create the DLL from the command line in Windows using the
Ninja tool from the
Visual Studio 2019 tools:
This will create cornerFinder_win64.dll and cornerFinder_win64.lib. These will be in the .\build\codegen\dll\cornerFinder directory.
Create a Simple Client Application
Now that our friend has built our dynamic library, they can call it from their C++ program via the cornerFinder routine shown in the header above. At the heart of this code, which is called myClientCode.cpp, is the call to the cornerFinder function. The lines of interest are here:
The first two lines create an array for storage of the corners and run the detector. The last line frees the point array.
Now our friend creates a CMakeLists.txt file for their client code. It looks like this:
The client application is built similarly to the DLL in earlier steps. At the command line in the ClientCode directory our friend executes these commands:
This will generate myClient.exe. Once the generated and dependent DLLs are added to the PATH environment variable, the client program can be executed to produce:
(Here, we've removed all but the first and last rows of output data for brevity.)
Accessing This Example
Now It's Your Turn
Tell us how you've been using the packNGo and codebuild tools.
© 2023 The MathWorks, Inc.
Comments
To leave a comment, please click here to sign in to your MathWorks Account or create a new one.