Face landmark detection in a video (LBF)

This demos lets you detect landmarks of detected faces in a video. It first detects faces in a current video frame and then finds their facial landmarks.

Sources:

Contents

Options

% [INPUT] path to input video
if true
    vid = fullfile(mexopencv.root(),'test','dudek.webm');
else
    vid = 0;
end

% [INPUT] path to the trained model to load
modelFile = fullfile(mexopencv.root(),'test','lbfmodel.yaml');
if exist(modelFile, 'file') ~= 2
    % download model from GitHub
    disp('Downloading model (~ 54MB)...')
    url = 'https://github.com/kurnianggoro/GSOC2017/raw/master/data/lbfmodel.yaml';
    urlwrite(url, modelFile);
end

% [INPUT] path to the cascade xml file for the face detector
xmlFace = fullfile(mexopencv.root(),'test','lbpcascade_frontalface.xml');
download_classifier_xml(xmlFace);

% name of user-defined face detector function
faceDetectFcn = 'myFaceDetector';
assert(exist([faceDetectFcn '.m'], 'file') == 2, 'missing face detect function');

Init

create instance of the face landmark detection class, and set the face detector function, then load the pre-trained model

if true
    obj = cv.Facemark('LBF');
    obj.setFaceDetector(faceDetectFcn);
else
    obj = cv.Facemark('LBF', 'CascadeFace',xmlFace);
end
obj.loadModel(modelFile);

Video

open video, and prepare figure

cap = cv.VideoCapture(vid);
assert(cap.isOpened(), 'Failed to load video');
img = cap.read();
assert(~isempty(img), 'Failed to read frame');
hImg = imshow(img);

Detect

main loop

counter = 0;
tID = tic();
while ishghandle(hImg)
    % read frame
    img = cap.read();
    if isempty(img), break; end

    % scale frame
    scale = 400 / size(img,2);
    imgS = cv.resize(img, fix(scale * [size(img,2) size(img,1)]));

    % detect faces
    rects = obj.getFaces(imgS);
    rects = cellfun(@(r) fix(r/scale), rects, 'Uniform',false);

    % detect and display face landmarks
    if ~isempty(rects)
        img = cv.rectangle(img, rects, 'Color',[0 255 0]);
        landmarks = obj.fit(img, rects);
        for i=1:numel(landmarks)
            img = cv.Facemark.drawFacemarks(img, landmarks{i}, 'Color',[0 0 255]);
        end
    end

    % show FPS
    counter = counter + 1;
    fps = counter/toc(tID);
    txt = sprintf('faces: %d, fps: %03.2f', numel(rects), fps);
    img = cv.putText(img, txt, [20 40], ...
        'FontFace','HersheyPlain', 'FontScale',2, ...
        'Thickness',2, 'Color',[255 255 255]);

    % show frame + results
    set(hImg, 'CData',img)
    drawnow
end
cap.release();

Helper function

function download_classifier_xml(fname)
    if exist(fname, 'file') ~= 2
        % attempt to download trained Haar/LBP/HOG classifier from Github
        url = 'https://cdn.rawgit.com/opencv/opencv/3.4.0/data/';
        [~, f, ext] = fileparts(fname);
        if strncmpi(f, 'haarcascade_', length('haarcascade_'))
            url = [url, 'haarcascades/'];
        elseif strncmpi(f, 'lbpcascade_', length('lbpcascade_'))
            url = [url, 'lbpcascades/'];
        elseif strncmpi(f, 'hogcascade_', length('hogcascade_'))
            url = [url, 'hogcascades/'];
        else
            error('File not found');
        end
        urlwrite([url f ext], fname);
    end
end

% The facemark API provides the functionality to the user to use their own
% face detector. The code below implements a sample face detector. This
% function must be saved in its own M-function to be used by the facemark API.
function faces = myFaceDetector(img)
    persistent obj
    if isempty(obj)
        obj = cv.CascadeClassifier();
        obj.load(xmlFace);
    end

    if size(img,3) > 1
        gray = cv.cvtColor(img, 'RGB2GRAY');
    else
        gray = img;
    end
    gray = cv.equalizeHist(gray);
    faces = obj.detect(gray, 'ScaleFactor',1.4, 'MinNeighbors',2, ...
        'ScaleImage',true, 'MinSize',[30 30]);
end