Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

C++ Face Recognition demo #3685

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
2 changes: 0 additions & 2 deletions demos/face_recognition_demo/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,3 @@ add_demo(NAME face_recognition_demo
HEADERS ${HEADERS}
INCLUDE_DIRECTORIES "${CMAKE_CURRENT_SOURCE_DIR}/include"
DEPENDENCIES monitors utils)

target_compile_features(face_recognition_demo PRIVATE cxx_std_17)
24 changes: 13 additions & 11 deletions demos/face_recognition_demo/cpp/include/reid_gallery.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,25 +32,27 @@ class EmbeddingsGallery {
static constexpr char unknownLabel[] = "Unknown";
static constexpr int unknownId = -1;
static constexpr float unknownDistance = 1.0;
EmbeddingsGallery(const std::string& ids_list, double threshold,
bool crop_gallery, const DetectorConfig& detector_config,
AsyncModel& landmarks_det,
AsyncModel& image_reid,
bool use_greedy_matcher=false);
EmbeddingsGallery(const std::string& fgPath,
double threshold,
bool crop,
const DetectorConfig& detectorConfig,
AsyncModel& landmarksDet,
AsyncModel& imageReid,
bool useGreedyMatcher = false);
size_t size() const;
std::vector<std::pair<int, float>> getIDsByEmbeddings(const std::vector<cv::Mat>& embeddings) const;
std::string getLabelByID(int id) const;
bool labelExists(const std::string& label) const;
std::string tryToSave(cv::Mat new_face);
void addFace(cv::Mat new_face, cv::Mat embedding, std::string label);
std::string tryToSave(cv::Mat newFace);
void addFace(cv::Mat newFace, cv::Mat embedding, std::string label);

private:
RegistrationStatus registerIdentity(const std::string& identity_label,
RegistrationStatus registerIdentity(const std::string& identityLabel,
const cv::Mat& image,
const bool crop_gallery,
const bool crop,
FaceDetector& detector,
AsyncModel& landmarks_det,
AsyncModel& image_reid,
AsyncModel& landmarksDet,
AsyncModel& imageReid,
cv::Mat& embedding);
std::vector<int> idxToId;
double reidThreshold;
Expand Down
82 changes: 42 additions & 40 deletions demos/face_recognition_demo/cpp/src/align_transform.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,65 +6,67 @@
#include <opencv2/imgproc.hpp>
#include <vector>

static const float h = 112.;
static const float w = 96.;
// reference landmarks points in the unit square [0,1]x[0,1]
static const float ref_landmarks_normalized[] = {
30.2946f / w, 51.6963f / h, 65.5318f / w, 51.5014f / h, 48.0252f / w,
71.7366f / h, 33.5493f / w, 92.3655f / h, 62.7299f / w, 92.2041f / h
};
namespace {
static const float h = 112.;
static const float w = 96.;
// reference landmarks points in the unit square [0,1]x[0,1]
static const float REF_LANDMARKS_NORMED[] = {
30.2946f / w, 51.6963f / h, 65.5318f / w, 51.5014f / h, 48.0252f / w,
71.7366f / h, 33.5493f / w, 92.3655f / h, 62.7299f / w, 92.2041f / h
};
}

cv::Mat getTransform(cv::Mat* src, cv::Mat* dst) {
cv::Mat col_mean_src;
reduce(*src, col_mean_src, 0, cv::REDUCE_AVG);
cv::Mat colMeanSrc;
reduce(*src, colMeanSrc, 0, cv::REDUCE_AVG);
for (int i = 0; i < src->rows; i++) {
src->row(i) -= col_mean_src;
src->row(i) -= colMeanSrc;
}

cv::Mat col_mean_dst;
reduce(*dst, col_mean_dst, 0, cv::REDUCE_AVG);
cv::Mat colMeanDst;
reduce(*dst, colMeanDst, 0, cv::REDUCE_AVG);
for (int i = 0; i < dst->rows; i++) {
dst->row(i) -= col_mean_dst;
dst->row(i) -= colMeanDst;
}

cv::Scalar mean, dev_src, dev_dst;
cv::meanStdDev(*src, mean, dev_src);
dev_src(0) =
std::max(static_cast<double>(std::numeric_limits<float>::epsilon()), dev_src(0));
*src /= dev_src(0);
cv::meanStdDev(*dst, mean, dev_dst);
dev_dst(0) =
std::max(static_cast<double>(std::numeric_limits<float>::epsilon()), dev_dst(0));
*dst /= dev_dst(0);
cv::Scalar mean, devSrc, devDst;
cv::meanStdDev(*src, mean, devSrc);
devSrc(0) =
std::max(static_cast<double>(std::numeric_limits<float>::epsilon()), devSrc(0));
*src /= devSrc(0);
cv::meanStdDev(*dst, mean, devDst);
devDst(0) =
std::max(static_cast<double>(std::numeric_limits<float>::epsilon()), devDst(0));
*dst /= devDst(0);

cv::Mat w, u, vt;
cv::SVD::compute((*src).t() * (*dst), w, u, vt);
cv::Mat r = (u * vt).t();
cv::Mat m(2, 3, CV_32F);
m.colRange(0, 2) = r * (dev_dst(0) / dev_src(0));
m.col(2) = (col_mean_dst.t() - m.colRange(0, 2) * col_mean_src.t());
m.colRange(0, 2) = r * (devDst(0) / devSrc(0));
m.col(2) = (colMeanDst.t() - m.colRange(0, 2) * colMeanSrc.t());
return m;
}

void alignFaces(std::vector<cv::Mat>& face_images, std::vector<cv::Mat>& landmarks_vec) {
if (landmarks_vec.size() == 0) {
void alignFaces(std::vector<cv::Mat>& faceImages, std::vector<cv::Mat>& landmarksVec) {
if (landmarksVec.size() == 0) {
return;
}
CV_Assert(face_images.size() == landmarks_vec.size());
cv::Mat ref_landmarks = cv::Mat(5, 2, CV_32F);
CV_Assert(faceImages.size() == landmarksVec.size());
cv::Mat refLandmarks = cv::Mat(5, 2, CV_32F);

for (size_t j = 0; j < face_images.size(); j++) {
for (int i = 0; i < ref_landmarks.rows; i++) {
ref_landmarks.at<float>(i, 0) =
ref_landmarks_normalized[2 * i] * face_images.at(j).cols;
ref_landmarks.at<float>(i, 1) =
ref_landmarks_normalized[2 * i + 1] * face_images.at(j).rows;
landmarks_vec.at(j) = landmarks_vec.at(j).reshape(1, 5);
landmarks_vec.at(j).at<float>(i, 0) *= face_images.at(j).cols;
landmarks_vec.at(j).at<float>(i, 1) *= face_images.at(j).rows;
for (size_t j = 0; j < faceImages.size(); j++) {
for (int i = 0; i < refLandmarks.rows; i++) {
refLandmarks.at<float>(i, 0) =
REF_LANDMARKS_NORMED[2 * i] * faceImages.at(j).cols;
refLandmarks.at<float>(i, 1) =
REF_LANDMARKS_NORMED[2 * i + 1] * faceImages.at(j).rows;
landmarksVec.at(j) = landmarksVec.at(j).reshape(1, 5);
landmarksVec.at(j).at<float>(i, 0) *= faceImages.at(j).cols;
landmarksVec.at(j).at<float>(i, 1) *= faceImages.at(j).rows;
}
cv::Mat m = getTransform(&ref_landmarks, &landmarks_vec.at(j));
cv::warpAffine(face_images.at(j), face_images.at(j), m,
face_images.at(j).size(), cv::WARP_INVERSE_MAP);
cv::Mat m = getTransform(&refLandmarks, &landmarksVec.at(j));
cv::warpAffine(faceImages.at(j), faceImages.at(j), m,
faceImages.at(j).size(), cv::WARP_INVERSE_MAP);
}
}
26 changes: 13 additions & 13 deletions demos/face_recognition_demo/cpp/src/async_queue.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@
#include "utils/ocv_common.hpp"
#include <vector>

AsyncInferQueue::AsyncInferQueue(ov::CompiledModel& compiled_model, size_t size) {
AsyncInferQueue::AsyncInferQueue(ov::CompiledModel& compiledModel, size_t size) {
requests.resize(size);
for (size_t request_id = 0; request_id < size; ++request_id) {
requests[request_id] = compiled_model.create_infer_request();
idsOfFreeRequests.push(request_id);
for (size_t requestId = 0; requestId < size; ++requestId) {
requests[requestId] = compiledModel.create_infer_request();
idsOfFreeRequests.push(requestId);
}

for (const auto& output: compiled_model.outputs()) {
for (const auto& output: compiledModel.outputs()) {
outputNames.push_back(output.get_any_name());
}

Expand All @@ -23,7 +23,7 @@ AsyncInferQueue::AsyncInferQueue(ov::CompiledModel& compiled_model, size_t size)

void AsyncInferQueue::setCallback() {
for (size_t requestId = 0; requestId < requests.size(); ++requestId) {
requests[requestId].set_callback([this, requestId /* ... */](std::exception_ptr exception_ptr) {
requests[requestId].set_callback([this, requestId /* ... */](std::exception_ptr exceptionPtr) {
{
// acquire the mutex to access m_idle_handles
std::lock_guard<std::mutex> lock(mutex);
Expand All @@ -38,8 +38,8 @@ void AsyncInferQueue::setCallback() {
// Notify locks in getIdleRequestId()
cv.notify_one();
try {
if (exception_ptr) {
std::rethrow_exception(exception_ptr);
if (exceptionPtr) {
std::rethrow_exception(exceptionPtr);
}
} catch (const std::exception& e) {
throw ov::Exception(e.what());
Expand Down Expand Up @@ -71,29 +71,29 @@ void AsyncInferQueue::waitAll() {
}
}

void AsyncInferQueue::submitData(std::unordered_map<std::string, cv::Mat> inputs, size_t input_id) {
void AsyncInferQueue::submitData(std::unordered_map<std::string, cv::Mat> inputs, size_t inputId) {
size_t id = getIdleRequestId();

{
std::lock_guard<std::mutex> lock(mutex);
idsOfFreeRequests.pop();
}
requests[id].set_callback([this, id, input_id /* ... */](std::exception_ptr exception_ptr) {
requests[id].set_callback([this, id, inputId /* ... */](std::exception_ptr exceptionPtr) {
{
// acquire the mutex to access m_idle_handles
std::lock_guard<std::mutex> lock(mutex);
for (const auto& outName : outputNames) {
auto tensor = requests[id].get_tensor(outName);
results[input_id][outName] = tensor;
results[inputId][outName] = tensor;
}
// Add idle handle to queue
idsOfFreeRequests.push(id);
}
// Notify locks in getIdleRequestId()
cv.notify_one();
try {
if (exception_ptr) {
std::rethrow_exception(exception_ptr);
if (exceptionPtr) {
std::rethrow_exception(exceptionPtr);
}
} catch (const std::exception& e) {
throw ov::Exception(e.what());
Expand Down
89 changes: 50 additions & 39 deletions demos/face_recognition_demo/cpp/src/reid_gallery.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,19 @@

#include "reid_gallery.hpp"

#include <filesystem>
#include <opencv2/opencv.hpp>
#include <string>
#include <vector>

#ifdef _WIN32
# include "w_dirent.hpp"
#else
# include <dirent.h> // for closedir, dirent, opendir, readdir, DIR
#endif

#include "utils/kuhn_munkres.hpp"

namespace fs = std::filesystem;
// namespace fs = std::filesystem;
Wovchena marked this conversation as resolved.
Show resolved Hide resolved
namespace {
float computeReidDistance(const cv::Mat& descr1, const cv::Mat& descr2) {
float xy = static_cast<float>(descr1.dot(descr2));
Expand All @@ -25,38 +30,44 @@ namespace {

} // namespace

EmbeddingsGallery::EmbeddingsGallery(const std::string& face_gallery_path,
EmbeddingsGallery::EmbeddingsGallery(const std::string& fgPath,
double threshold,
bool crop_gallery,
const DetectorConfig& detector_config,
AsyncModel& landmarks_det,
AsyncModel& image_reid,
bool crop,
const DetectorConfig& detectorConfig,
AsyncModel& landmarksDet,
AsyncModel& imageReid,
bool useGreedyMatcher) :
reidThreshold(threshold), useGreedyMatcher(useGreedyMatcher), faceGalleryPath(face_gallery_path) {
reidThreshold(threshold), useGreedyMatcher(useGreedyMatcher), faceGalleryPath(fgPath) {
if (faceGalleryPath.empty()) {
return;
}

FaceDetector detector(detector_config);
FaceDetector detector(detectorConfig);

int id = 0;
for (const auto & entry : fs::directory_iterator(faceGalleryPath)) {
if (entry.is_regular_file() &&
std::find(file_extensions.begin(), file_extensions.end(), entry.path().extension()) != file_extensions.end()) {
std::string label = entry.path().stem();
DIR* dir = opendir(fgPath.c_str());
if (!dir) {
throw std::runtime_error("Can't find the directory " + fgPath);
}
while (struct dirent* ent = readdir(dir)) {
if (strcmp(ent->d_name, ".") && strcmp(ent->d_name, "..")) {
std::string name = ent->d_name;
cv::Mat image = cv::imread(fgPath + '/' + name);
if (image.empty()) {
throw std::runtime_error("Image is empty");
}
std::vector<cv::Mat> embeddings;
cv::Mat image = cv::imread(entry.path());
assert(!image.empty());
cv::Mat emb;
RegistrationStatus status = registerIdentity(label, image, crop_gallery, detector, landmarks_det, image_reid, emb);
RegistrationStatus status = registerIdentity(name, image, crop, detector, landmarksDet, imageReid, emb);
if (status == RegistrationStatus::SUCCESS) {
embeddings.push_back(emb);
idxToId.push_back(id);
identities.emplace_back(embeddings, label, id);
identities.emplace_back(embeddings, name, id);
++id;
}
}
}
closedir(dir);
slog::info << identities.size() << " persons to recognize were added from the gallery" << slog::endl;
}

Expand Down Expand Up @@ -111,24 +122,24 @@ bool EmbeddingsGallery::labelExists(const std::string& label) const {
[label](const GalleryObject& o) {return o.label == label;});
}

std::string EmbeddingsGallery::tryToSave(cv::Mat new_face){
std::string EmbeddingsGallery::tryToSave(cv::Mat newFace){
std::string winname = "Unknown face";
size_t height = int(400 * new_face.rows / new_face.cols);
size_t height = int(400 * newFace.rows / newFace.cols);
cv::Mat resized;
cv::resize(new_face, resized, cv::Size(400, height), 0.0, 0.0, cv::INTER_AREA);
cv::resize(newFace, resized, cv::Size(400, height), 0.0, 0.0, cv::INTER_AREA);
size_t font = cv::FONT_HERSHEY_PLAIN;
size_t fontScale = 1;
cv::Scalar fontColor(255, 255, 255);
size_t lineType = 1;
cv::copyMakeBorder(resized, new_face, 5, 5, 5, 5, cv::BORDER_CONSTANT, cv::Scalar(255, 255, 255));
cv::putText(new_face, "Please, enter person's name and", cv::Point2d(30, 80), font, fontScale, fontColor, lineType);
cv::putText(new_face, "press \"Enter\" to accept and continue.", cv::Point2d(30, 110), font, fontScale, fontColor, lineType);
cv::putText(new_face, "Press \"Escape\" to discard.", cv::Point2d(30, 140), font, fontScale, fontColor, lineType);
cv::putText(new_face, "Name: ", cv::Point2d(30, 170), font, fontScale, fontColor, lineType);
cv::copyMakeBorder(resized, newFace, 5, 5, 5, 5, cv::BORDER_CONSTANT, cv::Scalar(255, 255, 255));
cv::putText(newFace, "Please, enter person's name and", cv::Point2d(30, 80), font, fontScale, fontColor, lineType);
cv::putText(newFace, "press \"Enter\" to accept and continue.", cv::Point2d(30, 110), font, fontScale, fontColor, lineType);
cv::putText(newFace, "Press \"Escape\" to discard.", cv::Point2d(30, 140), font, fontScale, fontColor, lineType);
cv::putText(newFace, "Name: ", cv::Point2d(30, 170), font, fontScale, fontColor, lineType);
std::string name;
bool save = false;
while (true) {
cv::Mat cc = new_face.clone();
cv::Mat cc = newFace.clone();
cv::putText(cc, name, cv::Point2d(30, 200), font, fontScale, fontColor, lineType);
cv::imshow(winname, cc);
int key = (cv::waitKey(0) & 0xFF);
Expand Down Expand Up @@ -164,42 +175,42 @@ std::string EmbeddingsGallery::tryToSave(cv::Mat new_face){
continue;
}
}

cv::destroyWindow(winname);
return (save ? name : "");
}

void EmbeddingsGallery::addFace(const cv::Mat new_face, const cv::Mat embedding, std::string label) {
void EmbeddingsGallery::addFace(const cv::Mat newFace, const cv::Mat embedding, std::string label) {
identities.emplace_back(std::vector<cv::Mat>{embedding}, label, idxToId.size());
idxToId.push_back(idxToId.size());
label += ".jpg";
fs::path filename = fs::path(faceGalleryPath) / label;

cv::imwrite(filename, new_face);
cv::imwrite(faceGalleryPath + "/" + label, newFace);
}

RegistrationStatus EmbeddingsGallery::registerIdentity(const std::string& identity_label,
RegistrationStatus EmbeddingsGallery::registerIdentity(const std::string& identityLabel,
const cv::Mat& image,
bool crop_gallery,
bool crop,
FaceDetector& detector,
AsyncModel& landmarks_det,
AsyncModel& image_reid,
AsyncModel& landmarksDet,
AsyncModel& imageReid,
cv::Mat& embedding) {
cv::Mat target = image;
if (crop_gallery) {
if (crop) {
detector.submitData(image);
std::vector<FaceBox> faces = detector.getResults();
if (faces.size() == 0) {
return RegistrationStatus::FAILURE_NOT_DETECTED;
}
CV_Assert(faces.size() == 1);
if (faces.size() != 1) {
throw std::runtime_error("More than 1 face on the image provided for face gallery");
}
cv::Mat faceRoi = image(faces[0].face);
target = faceRoi;
}

cv::Mat landmarks;
std::vector<cv::Mat> images = { target };
std::vector<cv::Mat> landmarksVec = landmarks_det.infer(images);
std::vector<cv::Mat> landmarksVec = landmarksDet.infer(images);
alignFaces(images, landmarksVec);
embedding = image_reid.infer(images)[0];
embedding = imageReid.infer(images)[0];
return RegistrationStatus::SUCCESS;
}