Main Content

Code Generation for Face Tracking with PackNGo

This example shows how to generate code from Face Detection and Tracking Using the KLT Algorithm example with packNGo function. The packNGo (MATLAB Coder) function packages all relevant files in a compressed zip file so you can relocate, unpack, and rebuild your project in another development environment without MATLAB present. This example also shows how to create a makefile for the packNGo content, rebuild the source files and finally run the standalone executable outside MATLAB environment.

This example requires a MATLAB® Coder™ license.

This example is a function with the main body at the top and helper routines in the form of Nested Functions below.

function FaceTrackingKLTpackNGoExample()

Set Up Your C++ Compiler

To run this example, you must have access to a C++ compiler and you must configure it using 'mex -setup c++' command. For more information, see Choose a C++ Compiler. If you deploy the application on MATLAB host, use a C++ compiler that is compatible with the compiler used to build OpenCV libraries. For more information, see Portable C Code Generation for Functions That Use OpenCV Library.

Break Out the Computational Part of the Algorithm into a Separate MATLAB Function

MATLAB Coder requires MATLAB code to be in the form of a function in order to generate C code. The code for the main algorithm of this example resides in a function called FaceTrackingKLTpackNGo_kernel.m. This file is derived from Face Detection and Tracking Using the KLT Algorithm. To learn how to modify the MATLAB code to make it compatible for code generation, you can look at example Introduction to Code Generation with Feature Matching and Registration.

fileName = "FaceTrackingKLTpackNGo_kernel.m";
visiondemo_dir = pwd;
currentDir = pwd; % Store the current directory
fileName = fullfile(visiondemo_dir, fileName);

Configure Code Generation Arguments for packNGo

Create a code generation configuration object for EXE output with packNGo function call in post code generation stage.

codegenArgs = createCodegenArgs(visiondemo_dir);

Setup Code Generation Environment

Change output directory name.

codegenOutDir = fullfile(visiondemo_dir, "codegen");
mkdir(codegenOutDir);

Add path to the existing directory to have access to necessary files.

currentPath = addpath(visiondemo_dir);
pathCleanup = onCleanup(@()path(currentPath));
cd(codegenOutDir);
dirChange = onCleanup(@()cd(currentDir));

Create the Packaged Zip-file

Invoke codegen command with packNGo function call.

fprintf("-> Generating Code (it may take a few minutes) ....\n");
codegen(codegenArgs{:}, fileName);
-> Generating Code (it may take a few minutes) ....
Code generation successful.

Note that, instead of using codegen command, you can open a dialog and launch a code generation project using codegen (MATLAB Coder). Use the post code generation command with packNGo function to create a zip file.

Build Standalone Executable

Unzip the zip file into a new folder. Note that the zip file contains source files, header files, libraries, MAT-file containing the build information object, data files. unzipPackageContents and other helper functions are included in the appendix.

zipFileLocation  = codegenOutDir;
fprintf("-> Unzipping files ....\n");
unzipFolderLocation       = unzipPackageContents(zipFileLocation);
-> Unzipping files ....

Create platform dependent makefile from a template makefile.

fprintf("-> Creating makefile ....\n");
[~, fname, ~] = fileparts(fileName);
makefileName = createMakeFile(visiondemo_dir, unzipFolderLocation, fname);
-> Creating makefile ....

Create the commands required to build the project and to run it.

fprintf("-> Creating 'Build Command' and 'Run command' ....\n");
[buildCommand, runCommand] = createBuildAndRunCommands(zipFileLocation,...
    unzipFolderLocation,makefileName,fname);
-> Creating 'Build Command' and 'Run command' ....

Build the project using build command.

fprintf("-> Building executable....\n");
buildExecutable(unzipFolderLocation, buildCommand);
-> Building executable....

Run the Executable and Deploy

Run the executable and verify that it works.

cd(unzipFolderLocation);
system(runCommand);

The application can be deployed in another machine by copying the executable and the library files.

isPublishing = ~isempty(snapnow("get"));
if ~isPublishing % skip printing out directory to html page
  fprintf("Executable and library files are located in the following folder:\n%s\n", unzipFolderLocation);
  fprintf("To re-execute run the following commands:\n");
  fprintf("1. cd(''%s'')\n", unzipFolderLocation);
  fprintf("2. system(''%s'')\n", runCommand);
end

Appendix - Helper Functions

    % Configure coder to create executable. Use packNGo at post code
    % generation stage.
    function codegenArgs = createCodegenArgs(folderForMainC)
        % Create arguments required for code generation.

        % For standalone executable a main C function is required. The main.c
        % created for this example is compatible with the content of the file
        % visionFaceTrackingKLTpackNGo_kernel.m
        mainCFile = fullfile(folderForMainC,"main.c");

        % Handle path with space
        if contains(mainCFile, ' ')
            mainCFile = ['"' mainCFile '"'];
        end

        cfg                               = coder.config("exe");
        cfg.PostCodeGenCommand            = "packNGo(buildInfo,'packType','hierarchical');";
        cfg.CustomSource                  = mainCFile;
        cfg.CustomInclude                 = folderForMainC;
        cfg.EnableOpenMP                  = false;

        codegenArgs = {"-config", cfg};

    end

    % Create a folder and unzip the packNGo content into it.
    function unzipFolderLocation   = unzipPackageContents(zipFileLocation)
        % Unzip the packaged zip file.

        unzipFolderLocationName = "unzipPackNGo";
        mkdir(unzipFolderLocationName);

        % Get the name of the zip file generated by packNGo.
        zipFile = dir("*.zip");

        assert(numel(zipFile)==1);

        unzip(zipFile.name,unzipFolderLocationName);

        % Unzip internal zip files created in hierarchical packNGo.
        zipFileInternal = dir(fullfile(unzipFolderLocationName,"*.zip"));
        assert(numel(zipFileInternal)==3);

        for i=1:numel(zipFileInternal)
            unzip(fullfile(unzipFolderLocationName,zipFileInternal(i).name), ...
                unzipFolderLocationName);
        end

        unzipFolderLocation = fullfile(zipFileLocation,unzipFolderLocationName);
    end

    % Create platform dependent makefile from template makefile. Use
    % buildInfo to get info about toolchain.
    function makefileName = createMakeFile(visiondemo_dir, unzipFolderLocation, fname)
        % Create Makefile from buildInfo.

        binfo = load(fullfile(pwd, "codegen", "exe", fname, "buildInfo.mat"));

        lastDir    = cd(unzipFolderLocation);
        dirCleanup = onCleanup(@()cd(lastDir));

        % Get the root directory that contains toolbox/vision sub-directories
        matlabDirName = getRootDirName(unzipFolderLocation);

        % Get defines
        horzcat_with_space = @(cellval)sprintf('%s',cellval{:});
        defs   = horzcat_with_space(getDefines(binfo.buildInfo));

        % Get source file list
        if ispc
            [~, cFiles] = system(['dir /s/b ' '*.c']);
            [~, cppFiles] = system(['dir /s/b ' '*.cpp']);

        else
            [~, cFiles] = system(['find ./ ' '-name ' '''*.c''']);
            [~, cppFiles] = system(['find ./ ' '-name ' '''*.cpp''']);

        end

        cIndx = strfind(cFiles, ".c");
        cppIndx = strfind(cppFiles, ".cpp");
        srcFilesC = [];
        srcFilesCPP = [];

        for i = 1:length(cIndx)
            if i == 1
                startIdx = 1;
                endIdx = cIndx(i);
            else
                startIdx = cIndx(i-1)+1;
                endIdx = cIndx(i);
            end

            [~, b, ~] = fileparts(cFiles(startIdx:endIdx));
            srcFilesC = [srcFilesC ' ' b '.c']; %#ok<AGROW>
        end

        for i = 1:length(cppIndx)
            if i == 1
                startIdx = 1;
                endIdx = cppIndx(i);
            else
                startIdx = cppIndx(i-1)+1;
                endIdx = cppIndx(i);
            end

            [~, b, ~] = fileparts(cppFiles(startIdx:endIdx));
            srcFilesCPP = [srcFilesCPP ' ' b '.cpp']; %#ok<AGROW>
        end

        srcFiles = [srcFilesC ' ' srcFilesCPP];

        % Get platform dependent names
        if isunix % both mac and linux
            tmf = "TemplateMakefilePackNGo_unix";
            if ismac
                archDir = 'maci64';
                dllExt  = 'dylib';
            else
                archDir = 'glnxa64';
                dllExt  = 'so';
            end
        else
            tmf = "TemplateMakefilePackNGo_win";
            archDir = 'win64';
            dllExt  = 'dll';
        end

        % Now that we have defines, lets create a platform dependent makefile
        % from template.
        fid = fopen(fullfile(visiondemo_dir,tmf));

        filecontent = char(fread(fid)');
        fclose(fid);

        newfilecontent = regexprep(filecontent,...
                {'PASTE_ARCH','PASTE_EXT','PASTE_DEFINES','PASTE_SRCFILES', 'PASTE_MATLAB'},...
                { archDir,     dllExt,      defs,           srcFiles,         matlabDirName});

        makefileName = "Makefile";
        mk_name = fullfile(unzipFolderLocation,makefileName);

        if isunix
            if( ismac )
                [status,sysHeaderPath] = system( "xcode-select -print-path" );
                assert(status==0, ["Could not obtain a path to the system ..." + ...
                    "header files using 'xcode-select -print-path' "]);

                [status,sdkPaths] = system( "xcrun -sdk macosx --show-sdk-path" );
                assert(status==0, "Could not find MacOSX sdk" );

               % There might be multiple SDK's
                sdkPathCell = strsplit(sdkPaths,'\n');
                for idx = 1:numel(sdkPathCell)
                   if ~isempty(sdkPathCell{idx})
                       % Pick the first one that's not empty.
                       sdkPath = sdkPathCell{idx};
                       fprintf("Choosing SDK in %s\n",sdkPath);
                       break;
                   end
                end
                assert(~isempty(sdkPath), ...
                  sprintf("There is no sdk available in %s. Please check system environment.\n",sysHeaderPath));

                ccCMD = [ 'xcrun clang -isysroot ' deblank( sdkPath ) ];
                cppCMD = [ 'xcrun clang++ -isysroot ' deblank( sdkPath ) ];
            else
                ccCMD  = "gcc";
                cppCMD = "g++";
            end

            newfilecontent = regexprep(newfilecontent,"PASTE_CC",ccCMD);
            newfilecontent = regexprep(newfilecontent,"PASTE_CPP",cppCMD);
        end

        fid = fopen(mk_name,"w+");
        fprintf(fid,"%s",newfilecontent);
        fclose(fid);

    end

    % Create platform specific commands needed to build the executable and
    % to run it.
    function [buildCommand, runCommand] = createBuildAndRunCommands( ...
        packageLocation,unzipFolderLocation,makefileName,fileName)
        % Create the build and run command.

        if ismac
            buildCommand = "xcrun make -f " + makefileName;
            runCommand   = "./" + fileName + " """ + fileName + """";
        elseif isunix
            buildCommand = "make -f " + makefileName;
            runCommand   = "./" + fileName + " """ + fileName + """";
        else
            % On PC we use the generated BAT files (there should be 2) to help
            % build the generated code.  These files are copied to the
            % unzipFolderLocation where we can use them to build.
            batFilename       = fileName + "_rtw.bat";
            batFilelocation   = fullfile(packageLocation,"codegen", ...
                                         filesep,"exe",filesep,fileName);
            batFileDestination = unzipFolderLocation;

            % For MSVC, also copy 'setup_msvc.bat'
            fid = fopen(fullfile(batFilelocation, batFilename));
            batFileContent = fread(fid, "*char");
            fclose(fid);
            if ~isempty(regexp(convertCharsToStrings(batFileContent), "setup_msvc.bat", "once"))
                setup_msvc_batFile = fullfile(batFilelocation, "setup_msvc.bat");
                copyfile(setup_msvc_batFile, batFileDestination);
            end

            % Copy it to packNGo output directory.
            copyfile(fullfile(batFilelocation,batFilename),batFileDestination);

            % The Makefile we created is named 'Makefile', whereas the Batch
            % file refers to <filename>_rtw.mk. Hence we rename the file.
            newMakefileName = fileName + "_rtw.mk";
            oldMakefilename = makefileName;
            copyfile(fullfile(batFileDestination,oldMakefilename),...
                fullfile(batFileDestination,newMakefileName));

            buildCommand = batFilename;
            runCommand = fileName + ".exe" + " """ + fileName + """";
        end

    end

    % Build the executable with the build command.
    function buildExecutable(unzipFolderLocation, buildCommand)
        % Call system command to build the executable.

        lastDir    = cd(unzipFolderLocation);
        dirCleanup = onCleanup(@()cd(lastDir));

        [hadError, sysResults] = system(buildCommand);

        if hadError
            error (sysResults);
        end

    end

    % Get the root directory that contains toolbox/vision sub-directories
    function matlabDirName = getRootDirName(unzipFolderName)
        dirLists = dir(unzipFolderName);
        dirLists = dirLists(~ismember({dirLists.name},{'.','..'}));

        matlabDirName='';
        for ij=1:length(dirLists)
            thisDirName = dirLists(ij).name;
            if (isfolder(thisDirName))
                % subdirectory will have toolbox/vision
                [subDir1, hasSubDir1]  = hasSubdirectory(thisDirName, 'toolbox');
                if hasSubDir1
                    [~, hasSubDir2]  = hasSubdirectory(subDir1, 'vision');
                    if hasSubDir2
                        matlabDirName = thisDirName;
                        break;
                    end
                end
            end
        end
    end

    % Find the directory that contains the specified sub-directory
    function [subDir, hasSubDir]  = hasSubdirectory(dirName, subDirName)
        dirLists = dir(dirName);
        dirLists = dirLists(~ismember({dirLists.name},{'.','..'}));

        subDir = '';
        hasSubDir = false;

        for ij=1:length(dirLists)
            thisDirName = dirLists(ij).name;
            thisDir = fullfile(dirName,thisDirName);

            if (isfolder(thisDir) && strcmp(thisDirName, subDirName))
                hasSubDir = true;
                subDir = thisDir;
                break;
            end
        end
    end
end