FaceLog4 is an advanced analytic that tracks unique faces in a video stream, classifies and optionally identifies them against a supplied watchlist. This plugin offers multithread support and is able to process high resolution video streams on a multicores CPU (typically Intel i7 with 4 cores or more).
It is possible to restrict video analytics area using a region-of-interest, see parameters table below. It can be used to select a specific area in images and/or to speed-up processing.
A detection represents a single detection of a face in an image of a frame. If there are more than one face in the image then there will be multiple detection events released. Detection events are released per frame, there is no delay (other than the processing of the frame).
A track is a collection of detections, in successive frames, that belong to the same subject. Track events are released as soon as the track has ended, i.e. when a track has not been added to in the next frame.
A sighting is a collection of tracks of the same subject. The tracks do not need to be in successive frames, there can be a break (lag) between them.
For example, often a track of a person walking through a video can be lost; For example, if they turn their head, or become obscured by someone else walking in front of them. In FaceLog4 we attempt to join similar tracks back together using both spatial information and using face-recognition. This collection of tracks is called a sighting.
After a configurable amount of time (measured in frames), when a track has not been added to a sighting, the sighting is released as an event. So a sighting will always be released a few seconds after the subject has left the scene.
It should be noted, the most reliable identity is from the sighting as this is aggregated over the tracks.
The face recognition algorithm in Papillon are based on a powerful new technique called Deep Learning. They are vastly superior to what was available a few years ago. However, they still have their limitations.
You will get best performance when the faces in the video are within +/-30 degrees from frontal, the faces are diffusely illuminated (with no strong shadows) and there is a minimum of 50 pixels between the eyes. The algorithms will still function outside these parameters, however the performance will be degraded. Ideally, face-recognition should be used as an aid to a human operator performing their task, helping them reduce the amount of data they have to search or ordering the priority of data they have to review.
A face-meta classifier is applied to the faces in each track and sighting. This provides extra information about the faces in the track and sighting. Currently this is limited to Gender information.
PFaceLog4Parameters is a class which can be used to help configure FaceLog4.
We aim to have a sensible set of default parameters that work effectively in many scenarios, however given the range of different use-cases for this analytic this is not always possible.
The code below shows a small snippet in how to create a FaceLog4 analytic, attach a watchlist and run it on frames in a video:
This analytic provides three public types of events; "Face", "Track" and "Sighting". The type of event can be checked through PEvent::GetType().
The description will contain information about the subject in the track that can be stored in a watchlist, and later searched.
The description will also contain meta-data about the face in track, e.g. its gender.
#include "PFaceLog4Parameters.h"
#include <algorithm>
#include <map>
#include <queue>
#include <sstream>
#include <unordered_map>
USING_NAMESPACE_PAPILLON
"Data",
"Samples");
P_LOG_INFO <<
"Creating face detector:" << detectorName;
throw std::runtime_error("Failed to create face localiser");
}
}
parameters.
Set(
"enableLocaliser",
true);
P_LOG_ERROR <<
"Failed to create face detector:" << res;
throw std::runtime_error("Failed to create face detector");
}
return detector;
}
void CreateTemplateDescriber(
PDescriber& describer,
int batchSize,
int gpuId,
bool sharedMode,
bool doThumbnails) {
P_LOG_INFO <<
"Creating face recognition describer...";
P_LOG_INFO <<
"describerBatchSize: " << batchSize;
PString(
";batchSize=%1").Arg(batchSize));
if(doThumbnails) {
dnnParameters.
Set(
"descriptorThumbnail",
PBool(
true));
}
if(sharedMode) {
dnnParameters.
Set(
"sharedDnn",
true);
}
}
PResult HandleSighting(
const PEvent& sighting,
const PWatchlist& watchlist,
bool writeVideo,
bool display =
false) {
const PString videoFile =
PString(
"sighting_%0.avi?encode_with=opencv&fourcc=XVID&fps=10").
Arg(shortSightingId);
if(writeVideo) {
P_LOG_ERROR <<
"Failed to open " << videoFile <<
" for writing";
writeVideo = false;
}
}
sighting.
GetPayload().Get(papillon::C_PROPERTY_THUMBNAIL, thumbnail);
if(display)
sighting.
GetPayload().Get(
"DetectionList", detectionList);
sighting.
GetPayload().Get(
"SubjectId", subjectId);
double subjectConfidence = -1.0;
sighting.
GetPayload().Get(
"SubjectIdConfidence", subjectConfidence);
sighting.
GetPayload().Get(
"Description", description);
int32 startFrame, endFrame;
sighting.
GetPayload().Get(
"StartFrame", startFrame);
if(metaClassifications.Size() == 1) {
const PString& classification = metaClassifications.Get(0);
int32 classIndex{-1};
float classConfidence{0.f};
if(faceMetaData.
GetClassIndex(classification, classIndex, classConfidence).
Ok()) {
if(classIndex == 0)
else if(classIndex == 1)
}
}
for(int32 j = 0; j < detectionList.
Size(); ++j) {
int32 centreWidth = image.
GetWidth() / 2;
if(subjectConfidence > 0.0)
if(display)
if(writeVideo)
}
P_LOG_INFO <<
PString(
"Sighting: %0 Id: %1 Frames: (%4,%5) Name: '%2' Confidence: %3")
.Arg(subjectConfidence)
}
for(int32 i = 0, ni = events.
Size(); i < ni; ++i) {
HandleSighting(event, watchlist, writeVideo, display);
if(event.
GetPayload().Get(
"Description", description).Ok()) {
P_LOG_TRACE <<
event.GetType() <<
" (" << i <<
"): " << faceMetaData;
}
}
}
class CTrack {
public:
CTrack(
int startFrame,
const PPoint2Df& point)
: m_startFrame{startFrame}
, m_lastFrame{startFrame} {
m_track.push_back(point);
static int colourIdx = 0;
}
void AddPoint(
int curFrame,
PPoint2Df point) {
for(int i = m_lastFrame; i < curFrame; i++) {
m_track.push_back(point);
}
m_lastFrame = curFrame;
}
std::vector<PPoint2Df> m_track;
int m_startFrame;
int m_lastFrame;
};
class CShowEvents {
public:
explicit CShowEvents(bool skipPlotting, bool showPoints, bool drawTracks)
: m_skipImage(false)
, m_framesTotal(0)
, m_framesSkipped(0)
, m_totalFaces(0)
, m_skipPlotting(skipPlotting)
, m_showPoints(showPoints)
, m_drawTracks(drawTracks) {}
void SetWatchList(
const PWatchlist& watchlist) { m_watchlist = watchlist; }
void StartTimer() {
m_framesTotal = m_framesSkipped = m_totalFaces = 0;
}
void QueueEvents(
const PList& events) {
for(int32 i = 0, ni = events.
Size(); i < ni; i++) {
m_eventQueue.push(event);
}
}
bool ShowEvents() {
bool imageReady = false;
while(!m_eventQueue.empty()) {
PEvent event = m_eventQueue.front();
m_eventQueue.pop();
const PString& eventType =
event.GetType();
if(eventType == "Face") {
m_totalFaces++;
if(!m_skipPlotting) {
const PProperties& eventProperties =
event.GetPayload();
if(!eventProperties.Get("Detection", detection)) {
continue;
}
if(!eventProperties.Get("SubjectId", subjectId))
double subjectIdConfidence = 0;
if(!eventProperties.Get("SubjectIdConfidence", subjectIdConfidence))
P_LOG_TRACE <<
"Subject:" << subjectId <<
" confidence:" << subjectIdConfidence;
if(!eventProperties.Get("Description", description))
bool subjectKnown = false;
if(m_watchlist.GetDescription(subjectId, subjectDescription).Ok())
subjectKnown = true;
const PString classificationForChoosingColours{
"mask"};
{
int32 classIndex{-1};
float classConfidence{0.f};
if(faceMetaData.
GetClassIndex(classificationForChoosingColours, classIndex, classConfidence)) {
detectionColour = (classIndex == 1)
}
}
if(!m_workImage.DrawRectangle(detRect, detectionColour, 1)) {
}
{
for(int i = 0, n = metaClassifications.Size(); i < n; i++) {
const PString& classification = metaClassifications.Get(i);
int32 classIndex{-1};
float classConfidence{0.f};
if(faceMetaData.
GetClassIndex(classification, classIndex, classConfidence).
Ok()) {
if(!m_workImage.DrawLabel(classLabel, at, detectionColour,
PColour3i::White())) {
}
at = at.Translated(0, -21);
}
}
}
{
if(subjectKnown) {
PString(
"%0: %1").
Arg(subjectDescription.
GetName()).Arg(subjectIdConfidence, 0,
'g', 2);
} else {
message = name;
}
} else {
message = "Unknown";
}
if(!m_workImage.DrawLabel(message, at,
PColour3i(tagColour), fontColour)) {
}
}
if(!eventProperties.Get("SightingId", sightingId)) {
continue;
}
auto track = m_tracks.find(sightingId);
if(track == m_tracks.end()) {
} else {
}
}
} else if(eventType == "FrameStart") {
if(!m_skipPlotting) {
const PProperties& eventProperties =
event.GetPayload();
if(!eventProperties.Get("Frame", frame)) {
continue;
}
static int lastFrameNo = 0;
}
if(m_roi.IsValid()) {
}
}
m_skipImage = false;
} else if(eventType == "FrameEnd") {
const PProperties& eventProperties =
event.GetPayload();
if(!eventProperties.Get("Frame", frame)) {
continue;
}
if(m_drawTracks) {
for(auto track : m_tracks) {
int startIdx = std::max(0, startFrame - track.second.m_startFrame);
if(startIdx >= track.second.m_track.size()) {
continue;
}
PPoint2Di prevPoint = track.second.m_track[startIdx].ToPPoint2Di();
for(int i = startIdx + 1; i < track.second.m_track.size(); i++) {
PPoint2Di curPoint = track.second.m_track[i].ToPPoint2Di();
m_workImage.DrawLine(prevPoint.
GetX(), prevPoint.
GetY(), curPoint.
GetX(), curPoint.
GetY(),
track.second.m_clolour, 2);
prevPoint = curPoint;
}
}
}
auto it = m_tracks.cbegin();
while(it != m_tracks.cend()) {
if(it->second.m_lastFrame < frameThreshold) {
it = m_tracks.erase(it);
} else {
++it;
}
}
++m_framesTotal;
if(!m_skipImage) {
m_readyImage = m_workImage;
imageReady = true;
break;
}
} else if(eventType == "FrameSkipped") {
++m_framesSkipped;
m_skipImage = true;
}
}
return imageReady;
}
const PImage& GetImage()
const {
return m_readyImage; }
const PString GetFpsString()
const {
return PString(
"fps %0 (%1) faces per sec %3 faces per frame %4")
.
Arg(
double(m_framesTotal - m_framesSkipped) / m_timer.ElapsedSec(), 4,
'f', 2)
.Arg(double(m_framesTotal) / m_timer.ElapsedSec(), 4, 'f', 1)
.Arg(double(m_totalFaces) / m_timer.ElapsedSec(), 6, 'f', 1)
.Arg(double(m_totalFaces) / double(m_framesTotal - m_framesSkipped), 4, 'f', 1);
}
private:
bool m_skipImage;
int64 m_framesTotal;
int64 m_framesSkipped;
int64 m_totalFaces;
bool m_skipPlotting;
bool m_showPoints;
bool m_drawTracks{false};
std::map<PGuid, CTrack> m_tracks;
std::queue<PEvent> m_eventQueue;
};
int CountProcessedFrames(
const PList& events) {
int result = 0;
for(int32 i = 0, ni = events.
Size(); i < ni; ++i) {
const PString& eventType =
event.GetType();
if(eventType == "FrameEnd")
++result;
else if(eventType == "FrameSkipped")
--result;
}
return result;
}
if(watchlist.
Size() < 1) {
}
{
std::unordered_map<PGuid, int> counter;
for(
int wlIndex = 0; wlIndex < std::min(wlItems.
Size(), 20); wlIndex++) {
if(!wlItems.
Get(wlIndex, description)) {
P_LOG_ERROR <<
"Failed to read description from the list";
continue;
}
for(
int i = 0; i < descriptors.
Size(); ++i) {
if(descriptors.
Get(i, descriptor).
Ok()) {
}
}
}
}
int popularity{0};
for(const auto& p : counter) {
P_LOG_INFO <<
"describeId: " << p.first <<
" popularity: " << p.second;
if(popularity < p.second) {
popularity = p.second;
oldDescribeId = p.first;
}
}
if(popularity < 1) {
}
}
P_LOG_INFO <<
"Old describe id:" << oldDescribeId;
#if 0
P_LOG_INFO <<
"Number of descriptions converted:" << res;
if(res < 1) {
}
#else
for(
int wlIndex = 0; wlIndex < wlItems.
Size(); wlIndex++) {
if(!wlItems.
Get(wlIndex, description)) {
P_LOG_ERROR <<
"Failed to read description from the list";
continue;
}
PGuid manualDetectorId =
PGuid(
"{cccccccc-f8f9-0000-0000-000000000000}");
float confidence = 1.0;
proceduralFeatureMap.
Add(boundingBoxDetection);
proceduralFeatureMap.
Add(leftEye);
proceduralFeatureMap.
Add(rightEye);
proceduralFeatureMap.
Add(mouthCentre);
for(
int i = 0; i < descriptorsFR08.
Size(); ++i) {
if(descriptorsFR08.
Get(i, descriptorFR08).LogIfError().
Failed())
continue;
P_LOG_ERROR <<
"FaceLog Thread: import watchlist: failed to convert descriptor " continue;
}
PFrame frameForThumbnail(descriptorImage);
confidence);
describer.
Describe(detection, descriptionFR09);
P_LOG_ERROR <<
"FaceLog Thread: import watchlist: failed to convert descriptor " continue;
}
continue;
<< " has been converted from model FR08 to FR09";
}
}
#endif
}
PResult RunDemo(
int argc,
char** argv) {
opt.AddStandardOptions();
PString inputVideoFile = opt.String (
"inputVideo,iv",
PPath::Join(SAMPLE_DIR,
"busy_office.avi"),
"Input video file");
PString outputVideoFile = opt.String (
"outputVideo,ov",
"",
"Output video file");
int32 maxFrames = opt.Int (",f", 0, "Maximum number of frames to process");
int32 skip = opt.Int ("skip,skip", 0, "Skip this number of frames at the beginning");
bool createWatchlist = opt.Bool ("createWatchlist,cw", false, "Create a watchlist from the watchlistConfigFile");
PString watchlistConfigFile = opt.String (
"watchlistConfig,wlc",
PPath::Join(SAMPLE_DIR,
"watchlist.config"),
"Use a config file to build a watchlist");
PString watchlistFile = opt.String (
"watchlist,wl",
"",
"Load watchlist from file");
PString faceLogName = opt.String (
"facelog,fl",
"FaceLog4",
"FaceLog plugin name (FaceLog4 or FaceLog6)");
PString faceLogParsString = opt.String (
"facelogPars,flp",
"",
"FaceLog plugin parameters (ex par1=v1;par2=v2)");
double watchlistThreshold = opt.Double ("watchlistThreshold,wt", 0.75, "The threshold to use for identification");
bool display = !opt.Option ("noDisplay,nd", "Display video as we go");
bool displayPoints = opt.Option ("displayPoints", "Display face points");
bool displayTracks = opt.Option ("displayTracks", "Display tracks");
bool displayEvents = !opt.Option ("noDisplayEvents,nde", "Display events as we go");
bool writeSightings = opt.Bool ("saveSightings,ss", false, "Write labeled video for each sighting");
int32 delay = opt.Int ("delay,delay", 0, "Delay in ms between frames");
PString roi = opt.String (
"roi,roi",
"",
"Region of interest (ex. 100,150,600,400)");
int fdGpuId = opt.Int ("fdGpuId,fdGpuId", -1, "Face detector GpuId");
int dGpuId = opt.Int ("dGpuId,dGpuId", -1, "Describer GpuId");
bool doThumbnails = opt.Bool ("thumbnails,th", false, "Create thumbnails in describer");
PString detectorName = opt.String (
"detector,d",
"FaceDetector2",
"Face detector name");
PString detectorParsString = opt.String (
"detectorPars,fdp",
"",
"Detector parameters (ex par1=v1;par2=v2)");
PString describerParsString = opt.String (
"describerPars,dp",
"",
"Describer parameters (ex par1=v1;par2=v2)");
PString localiserName = opt.String (
"localiser,loc",
"FaceDetector2",
"Face localiser name");
PString metaDescriberPars = opt.String (
"mdPars,mdPars",
"",
"Meta describer parameters (if empty create gender describer)");
PString metaDescriberPars2 = opt.String (
"mdPars2,mdPars2",
"",
"Meta describer parameters for second describer");
if(opt.Has("h") || opt.Has("help")) {
}
P_LOG_DEBUG <<
"output video ------'" << outputVideoFile <<
"'-------------";
if(!outputVideoFile.IsEmpty()) {
if(!outputVideoFile.Contains("?")) {
outputVideoFile += "?encode_with=opencv&fourcc=XVID&fps=25";
}
P_LOG_INFO <<
"Will write result video to: " << outputVideoFile;
P_LOG_ERROR <<
"Failed to open " << outputVideoFile <<
" for writing";
}
}
PFaceLog4Parameters faceLogParameters = PFaceLog4Parameters::BuildDefaultParameters();
PList keys = newProps.GetKeyList();
for(int32 i = 0; i < keys.
Size(); ++i) {
if(keys.
Get(i, key).
Ok()) {
}
}
}
PDetector detector = CreateFaceDetector(detectorName, localiserName, detectorParsString);
faceLogParameters.Set("FaceDetector", detector);
CreateTemplateDescriber(faceDescriber, describerParsString, 4, dGpuId, true, doThumbnails);
faceLogParameters.Set("FaceRecognitionDescriber", faceDescriber);
{
}
metaDescribers.
Add(metaDescriber);
if(!metaDescriberPars2.
IsEmpty()) {
P_LOG_INFO <<
"Creating meta data describer 2...";
metaDescribers.
Add(metaDescriber);
}
faceLogParameters.
Set(
"MetaDescribers", metaDescribers);
}
if(createWatchlist) {
P_LOG_INFO <<
"Creating watchlist using configuration file " << watchlistConfigFile;
}
else if(!watchlistFile.
IsEmpty()) {
P_LOG_INFO <<
"Loaded watchlist " << watchlistFile <<
" with " << watchlist.
Size() <<
" subjects";
P_LOG_INFO <<
"Watchlist is not compatible with describer. Will try to convert it.";
if(!ConvertWatchlist(watchlist, detector, faceDescriber).LogIfError()) {
}
}
} else {
P_LOG_INFO <<
"Not using a watchlist, anonymous sightings will be reported.";
}
watchlistOptions.
SetThreshold(static_cast<float>(watchlistThreshold));
faceLogParameters.SetWatchlist(watchlist);
faceLogParameters.SetWatchlistOptions(watchlistOptions);
{
PList keys = faceLogParameters.GetKeyList();
for(int32 i = 0; i < keys.
Size(); ++i) {
if(keys.
Get(i, key).
Ok()) {
if(key == "Watchlist") {
PList descriptions = faceLogParameters.GetWatchlist().GetAllDescriptions();
for(int32 j = 0; j < descriptions.
Size(); ++j) {
if(descriptions.
Get(j, d).LogIfError().
Ok()) {
}
}
} else {
P_LOG_DEBUG << key <<
" => " << faceLogParameters.GetAsString(key);
}
}
}
}
std::stringstream ss(std::string(roi.
c_str()));
std::vector<int> v;
int i;
while(ss >> i) {
v.push_back(i);
if(ss.peek() == ',')
ss.ignore();
}
if(v.size() != 4) {
}
}
int frameNumber = 0;
CShowEvents eventShow(!display && !ovs.
IsOpened(), displayPoints, displayTracks);
eventShow.SetWatchList(watchlist);
faceLog.
Set(
"roi", roiRect);
eventShow.SetRoi(roiRect);
}
while(true) {
break;
if(display) {
}
break;
frameNumber++;
if(frameNumber < skip)
continue;
eventShow.QueueEvents(events);
if(eventShow.ShowEvents()) {
const PImage& img = eventShow.GetImage();
break;
}
}
if(display) {
img.
Display(
"Face Log").LogIfError();
}
}
ProcessEvents(events, watchlist, writeSightings, displayEvents).LogIfError();
if(frameNumber == maxFrames) {
P_LOG_INFO <<
"Maximum number of frames reached. Stop processing...";
break;
}
if(delay > 0) {
}
}
faceLog.
Finish(events).LogIfError();
eventShow.QueueEvents(events);
while(eventShow.ShowEvents()) {
const PImage& img = eventShow.GetImage();
}
if(display) {
img.
Display(
"Face Log").LogIfError();
}
}
ProcessEvents(events, watchlist, writeSightings, display).LogIfError();
}
int main(int argc, char** argv) {
try {
RunDemo(argc, argv).LogIfError();
} catch(const std::exception& exc) {
return -1;
} catch(...) {
return -1;
}
return 0;
}