Facial Features Detection
A program to detect facial feature points using Haarcascade classifiers for face, eyes, nose and mouth.
The sample demonstrates facial feature points detection using Haarcascade classifiers. The program detects a face and eyes, nose and mouth inside the face. The code has been tested on the Japanese Female Facial Expression (JAFFE) database and found to give reasonably accurate results.
The classifiers for face and eyes can be downloaded from:
The classifiers for nose and mouth can be downloaded from:
Sources:
function facial_features_demo(im) % load input image of a face if nargin < 1 im = fullfile(tempdir(), 'face.jpg'); if exist(im, 'file') ~= 2 if true url = 'https://upload.wikimedia.org/wikipedia/commons/c/c9/Julia_Roberts_Cannes_2016_3.jpg'; elseif true url = 'https://upload.wikimedia.org/wikipedia/commons/3/33/Nicolas_Cage_2011_CC.jpg'; else url = 'https://upload.wikimedia.org/wikipedia/commons/8/8d/George_Clooney_2016.jpg'; end urlwrite(url, im); end img = cv.imread(im, 'Color',true); elseif ischar(im) img = cv.imread(im, 'Color',true); else img = im; end % haarcascade classifier files for face, eye, nose, and mouth detection % (only the face is required, others are optional) xml_face = fullfile(mexopencv.root(),'test','haarcascade_frontalface_alt.xml'); xml_eye = fullfile(mexopencv.root(),'test','haarcascade_eye_tree_eyeglasses.xml'); xml_nose = fullfile(mexopencv.root(),'test','haarcascade_mcs_nose.xml'); xml_mouth = fullfile(mexopencv.root(),'test','haarcascade_mcs_mouth.xml'); % Detect faces and facial features out = detectFacialFeatures(img, xml_face, xml_eye, xml_nose, xml_mouth); imshow(out), title('Facial Features') end function out = detectFacialFeatures(img, xml_face, xml_eye, xml_nose, xml_mouth) % input image as grayscale out = img; if size(img,3) == 3 img = cv.cvtColor(img, 'RGB2GRAY'); end % detect faces opts = {'ScaleFactor',1.15, 'MinNeighbors',3, ... 'ScaleImage',true, 'MinSize',[30 30]}; faces = cascadeDetect(img, xml_face, opts{:}); % for each face, detect facial features opts = {'ScaleFactor',1.20, 'MinNeighbors',5, ... 'ScaleImage',true, 'MinSize',[30 30]}; for i=1:size(faces,1) % draw face face = faces(i,:); out = cv.rectangle(out, face, 'Color',[0 0 255], 'Thickness',2); % eyes, nose and mouth will be detected inside the face (ROI) roi = cv.Rect.crop(img, face); % minimum object center height, used to filter detections % (mouth is expected to lie below nose, which in turn is below eyes) ymin = 0; % detect and draw eyes if ~isempty(xml_eye) eyes = cascadeDetect(roi, xml_eye, opts{:}); [out, ymin] = drawDetections(out, face, eyes, ymin, [0 255 0]); end % detect and draw nose if ~isempty(xml_nose) nose = cascadeDetect(roi, xml_nose, opts{:}); [out, ymin] = drawDetections(out, face, nose, ymin, [0 255 255]); end % detect and draw mouth if ~isempty(xml_mouth) mouth = cascadeDetect(roi, xml_mouth, opts{:}); [out, ymin] = drawDetections(out, face, mouth, ymin, [255 0 255]); end end end function rects = cascadeDetect(img, xml_file, varargin) % if missing, attempt to download XML file from two possible locations if exist(xml_file, 'file') ~= 2 [~, f, ext] = fileparts(xml_file); url = 'https://cdn.rawgit.com/opencv/opencv_contrib/3.2.0/modules/face/data/cascades/'; [~,status] = urlwrite([url f ext], xml_file); if status == 0 url = 'https://cdn.rawgit.com/opencv/opencv/3.2.0/data/haarcascades/'; [~,status] = urlwrite([url f ext], xml_file); end assert(status == 1, 'Failed to download'); end % detect and return Nx4 array of rectangles cascade = cv.CascadeClassifier(xml_file); rects = cascade.detect(img, varargin{:}); rects = cat(1, rects{:}); end function [out, ymin] = drawDetections(out, face, rects, ymin, clr) % filter detections ycenter = rects(:,2) + rects(:,4)/2; if ymin > 0 idx = ycenter > ymin; else idx = true(size(rects,1),1); end ymin = max(ycenter(idx,:)); % draw rectangle enclosing detection r = bsxfun(@plus, rects(idx,:), [face(1:2) 0 0]); out = cv.rectangle(out, r, 'Color',clr, 'Thickness',2); % draw center of detection c = bsxfun(@plus, rects(idx,1:2) + rects(idx,3:4)/2, face(1:2)); out = cv.circle(out, c, 3, 'Color',clr, 'Thickness','Filled'); end
Warning: Image is too big to fit on screen; displaying at 67%