Guy on Simulink

Simulink & Model-Based Design

Linking to a Dynamic Library from MATLAB Coder for a Windows Computer Vision Application

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
imFile = 'circuit.jpg';
% Quality factor -- on the client side this will be a parameter
qFac = 0.1;
% Read the grayscale image
I = imread(imFile);
% 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, '+');
figure(100);
clf
imshow(J);
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');
cfg.RowMajor = true;
cfg.GenCodeOnly=true;
% Define input argument types for coder
qFac = single(0.1);
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:
extern void cornerFinder(const char imFileName_data[],
const int imFileName_size[2], float qFac,
emxArray_real32_T *X, boolean_T *valid);
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:
ZipFileContents.png

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:
C:\work> dir /B c:\work\myCornerFinderExample
mlrFiles
mlrFiles.zip
sDirFiles
sDirFiles.zip
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:
C:\work> cmake -G Ninja -B ./build -DCMAKE_BUILD_TYPE=Release -DMATLAB_ROOT=./myCornerFinderExample/mlrFiles/R2022b -S ./myCornerFinderExample/sDirFiles/codegen/dll/cornerFinder
C:\work> cmake --build build
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:
/* Call the entry-point 'cornerFinder'. */
emxInitArray_real32_T(&X, 2);
cornerFinder(imFileName_data, imFileName_size, qFac, X, &valid);
...
// Free the image array
emxDestroyArray_real32_T(X);
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:
cmake_minimum_required(VERSION 3.12)
project(MyClient)
set(CMAKE_CXX_STANDARD 14)
add_executable(myClient myClientCode.cpp)
target_include_directories(myClient PRIVATE
../myCornerFinderExample/sDirFiles/codegen/dll/cornerFinder
../myCornerFinderExample/mlrFiles/R2022b/extern/include )
target_link_directories(myClient PRIVATE ../build/codegen/dll/cornerFinder
../myCornerFinderExample/mlrFiles/R2022b/extern/lib/win64/microsoft/
../myCornerFinderExample/mlrFiles/R2022b/toolbox/vision/builtins/src/ocvcg/opencv/win64/lib
)
target_link_libraries(myClient PRIVATE cornerFinder_win64.lib)
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:
C:\work\ClientCode> cmake -G Ninja -B build -DCMAKE_BUILD_TYPE=Release
C:\work\ClientCode> cmake --build build
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:
C:\work\ClientCode> set PATH=C:\work\build\codegen\dll\cornerFinder;%PATH%
C:\work\ClientCode> set PATH=C:\work\myCornerFinderExample\mlrFiles\R2022b\bin\win64\;%PATH%
C:\work\ClientCode> .\build\myClient.exe
Found some corners!
Number of points: 31 by 2
31.19 135.873
...
247.013 188.08
(Here, we've removed all but the first and last rows of output data for brevity.)

Accessing This Example

The files for this example can be access at the MathWorks File Exchange.

Now It's Your Turn

Tell us how you've been using the packNGo and codebuild tools.
© 2023 The MathWorks, Inc.
|
  • print

Comments

To leave a comment, please click here to sign in to your MathWorks Account or create a new one.