Command line tool to perform Identification (1:N matching)
/*
* Copyright (C) 2015-2018 Digital Barriers plc. All rights reserved.
* Contact: http://www.digitalbarriers.com/
*
* This file is part of the Papillon SDK.
*
* You can't use, modify or distribute any part of this file without
* the explicit written agreements of Digital Barriers.
*/
// ****************************************************************************
// Description:
//
// This command line tool is dedicated to Face Recognition.
//
// Its role is to perform identification (1 to N matching) from subjects
// held in a watchlist.
// ****************************************************************************
#include <PapillonCore.h> // Papillon SDK
USING_NAMESPACE_PAPILLON
const int RET_SUCCESS = 0;
const int RET_FAILURE = 1;
POption commandLineOptions;
int minDetectionSize = 80;
bool isDisplay = false;
PWatchlist watchlist;
PWatchlistOptions watchlistOptions;
PString describerModelName = "DescriberDnn";
const PDetector& GetFaceDetector() // lazy instanciation
{
static PDetector faceDetector;
if (faceDetector.GetPluginName().IsEmpty())
{
PDetector::Create("FaceDetector2", PProperties(), faceDetector).OrDie();
faceDetector.SetMinDetectionSize(minDetectionSize);
faceDetector.SetMaxDetectionSize(4000);
faceDetector.EnableLocaliser(true);
faceDetector.SetMaxDetections(1000);
}
return faceDetector;
}
const PDescriber& GetFaceDescriber() // lazy instanciation
{
static PDescriber faceDescriber;
if (faceDescriber.GetPluginName().IsEmpty())
PDescriber::Create(describerModelName, PProperties(), faceDescriber).OrDie();
return faceDescriber;
}
const PComparer& GetInstanceComparer() // lazy instanciation
{
static PComparer faceComparer;
if (faceComparer.GetName().IsEmpty())
return faceComparer;
}
PResult IdentifyFromImage(const PImage& image, const PString& imageName)
{
if (watchlist.IsEmpty())
return PResult::ErrorBadArgument("unexpeced empty watchlist; cannot perform identification");
if (image.IsEmpty())
return PResult::ErrorBadArgument("unexpeced empty image; cannot perform identification");
P_LOG_INFO << "*** Analyse " << imageName << " (" << image.GetWidth() << "x" << image.GetHeight() <<"-" << image.GetPixelFormatToString() << ") for identification ***";
P_LOG_INFO << "Start detecting faces...";
PDetectionList detectionList;
ReturnIfFailed(GetFaceDetector().Detect(PFrame(image), detectionList));
P_LOG_INFO << "Face detector: " << detectionList.Size() << " face(s) found";
PImage imageOutput = image.Clone();
for (int i=0, n=detectionList.Size(); i<n; ++i)
{
PDetection detection = detectionList.Get(i);
imageOutput.DrawRectangle(bbox, PColour3i::Red(), 3);
P_LOG_INFO << "* Compute description for face " << i;
PDescription unknownDescription;
if (GetFaceDescriber().Describe(detectionList.Get(i), "unknown", PGuid::CreateUniqueId(), unknownDescription).LogIfError().Failed())
continue;
P_LOG_INFO << " Perform identification for face " << i;
PIdentifyResults identifyResults;
if (watchlist.Search(unknownDescription, GetInstanceComparer(), watchlistOptions, identifyResults).Failed())
{
P_LOG_INFO << " - No subjects identified at given threshold level.";
PUtils::DrawLabel(imageOutput, detection, PString("Unknown"), PUtils::E_TOP_CENTRE, PUtils::E_RED);
continue;
}
if (identifyResults.GetNumberOfMatches() > 0)
{
PString name = identifyResults.Get(0).GetName();
double score = identifyResults.Get(0).GetScore();
PString message = PString("%1 (%2)").Arg(name).Arg(score, 3, 'f', 2);
PUtils::DrawLabel(imageOutput, detection, message, PUtils::E_TOP_CENTRE, PUtils::E_GREEN);
}
P_LOG_INFO << " Identification: " << watchlistOptions.GetTopN() << " best matches (with threshold set to " << watchlistOptions.GetThreshold() << ") are: ";
for (int j=0; j<identifyResults.Size(); ++j)
{
P_LOG_INFO << PString(" - %1: ").Arg(j, 3) << identifyResults.Get(j).ToString();
}
}
if (isDisplay)
imageOutput.Display("Identification");
if (!ovs.IsEmpty() && ovs.IsOpened())
ovs.PutFrame(imageOutput);
if (!ovs2.IsEmpty() && ovs2.IsOpened())
ovs2.PutFrame(imageOutput);
std::cout << std::endl;
return PResult::C_OK;
}
PResult IdentifyFromImages(const PString& path)
{
PStringList listImageFilenames = PPath::ListEntries(path, true, true);
for (int i=0, n=listImageFilenames.Size(); i<n; ++i)
{
PImage image;
if (image.Load(listImageFilenames.Get(i)).Ok())
IdentifyFromImage(image, listImageFilenames.Get(i).Quote()).LogIfError();
}
return PResult::C_OK;
}
PResult IdentifyFromVideoStream(const PString& videoURL, int maxFrames)
{
if (maxFrames < 0)
maxFrames = PMath::IntMax();
int countFrames = 0;
while (countFrames < maxFrames)
{
PFrame frame;
ReturnIfFailed(ivs.GetFrame(frame));
IdentifyFromImage(frame.GetImage(), PString("frame %1 of %2").Arg(countFrames).Arg(videoURL.Quote())).LogIfError();
++countFrames;
}
return PResult::C_OK;
}
void PrintHelp()
{
static bool helpHasBeenDisplayed = false;
if (!helpHasBeenDisplayed)
{
P_LOG_INFO << "Description: face recognition command line tool to perform identification (1 to N matching)";
P_LOG_INFO << commandLineOptions.ToStringHelp();
helpHasBeenDisplayed = true;
}
}
int main(int argc, char **argv)
{
P_LOG_INFO << "PapillonIdentification - v" << PAPILLON_VERSION.ToStringMajorMinor() << " - (C) Digital Barriers";
P_LOG_INFO << "====================================================";
commandLineOptions = POption(argc, argv);
commandLineOptions.AddStandardOptions();
PString watchlistFilename = commandLineOptions.String("watchlist", "watchlist.bin", "Watchlist file name");
PString input = commandLineOptions.String("input", PString::Empty(), "Image(s) or input video stream with subject(s) to identify");
PString output = commandLineOptions.String("output", PString::Empty(), "URL of the output video stream (can be screen, video file, TVI stream)");
PString output2 = commandLineOptions.String("output2", PString::Empty(), "URL of the second output video stream (can be screen, video file, TVI stream)");
minDetectionSize = commandLineOptions.Int ("minSize", 80, "Minimum detection size in pixels");
int maxFrames = commandLineOptions.Int ("maxFrames", -1, "Max frames to analyse (for a video stream); use -1 for full content");
int topN = commandLineOptions.Int ("topN", 3, "Display N better matches for each detection");
bool normalise = commandLineOptions.Bool ("normalise", false, "Apply score normalisation to find best matches");
float threshold = commandLineOptions.Float ("threshold", 0.6f, "Minimum threshold used for face recognition (must be in the range 0 .. 1)");
commandLineOptions.Option("display", "Display result on image or video if any");
commandLineOptions.Check().OrDie();
if (commandLineOptions.Has("h") || commandLineOptions.Has("help") || argc==1)
{
PrintHelp();
return RET_SUCCESS;
}
if (watchlistFilename.IsEmpty())
{
PrintHelp();
P_LOG_FATAL << "Watchlist is missing; cannot perform identification";
return RET_FAILURE;
}
if (input.IsEmpty())
{
PrintHelp();
P_LOG_FATAL << "Input (video stream or image(s) with subject to identify is missing, abort...";
return RET_FAILURE;
}
papillon::PapillonSDK::Initialise().OrDie("Failed to initialise Papillon SDK");
isDisplay = commandLineOptions.Has("display");
P_LOG_INFO << "Face Describer = " << describerModelName;
P_LOG_INFO << "Identification threshold = " << threshold;
P_LOG_INFO << "Top N identification = " << topN;
P_LOG_INFO << "Normalisation = " << (normalise ? "ENABLED" : "DISABLED");
P_LOG_INFO << "*** Loading watchlist ***";
PWatchlist::ReadFromFile(watchlistFilename, watchlist).OrDie();
watchlistOptions.SetTopN(topN);
watchlistOptions.SetThreshold(threshold);
watchlistOptions.SetNormalise(normalise);
P_LOG_INFO << "Watchlist is " << PFile::GetAbsoluteFilePath(watchlistFilename).Quote();
P_LOG_INFO << watchlist.Size() << " subjects found";
if (!output.IsEmpty())
if (!output2.IsEmpty())
POutputVideoStream::Open(output2, ovs2).OrDie();
PResult ret;
PImage image;
if (PPath::IsDirectory(input))
ret = IdentifyFromImages(input);
else if (image.Load(input).Ok())
{
// !! known bug when trying to display a single image under Linux !!
isDisplay = false;
ret = IdentifyFromImage(image, input.Quote());
}
else
ret = IdentifyFromVideoStream(input, maxFrames);
ret.LogIfError();
if (isDisplay)
{
std::cout << std::endl << "Press RETURN to continue..." << std::endl;
getchar();
}
return RET_SUCCESS;
}