Files
ANSCORE/modules/ANSFR/ANSFRCommon.cpp

1166 lines
47 KiB
C++
Raw Normal View History

2026-03-28 16:54:11 +11:00
#include "ANSFRCommon.h"
#include "dirent.h"
#include <ctime>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/dnn/dnn.hpp>
#include <opencv2/highgui.hpp>
#include <utils/ocv_common.hpp>
2026-04-06 17:02:42 +10:00
#include <json.hpp>
2026-03-28 16:54:11 +11:00
namespace ANSCENTER {
constexpr size_t ndetections = 200;
template <typename T> T GetData(const boost::property_tree::ptree& pt, const std::string& key)
{
T ret;
if (boost::optional<T> data = pt.get_optional<T>(key))
{
ret = data.get();
}
return ret;
}
Face::Face(size_t id, cv::Rect& location) :
_location(location), _intensity_mean(0.f), _id(id), _age(-1),
_maleScore(0), _femaleScore(0), _realFaceConfidence(0),
_isAgeGenderEnabled(false), _isEmotionsEnabled(false),
_isHeadPoseEnabled(false), _isLandmarksEnabled(false), _isAntispoofingEnabled(false)
{
HeadPoseResults headPose;
headPose.angle_p = 0.f;
headPose.angle_r = 0.f;
headPose.angle_y = 0.f;
_headPose = headPose;
}
void Face::updateAge(float value) {
_age = (_age == -1) ? value : 0.95f * _age + 0.05f * value;
}
void Face::updateGender(float value) {
if (value < 0)
return;
if (value > 0.5) {
_maleScore += value - 0.5f;
}
else {
_femaleScore += 0.5f - value;
}
}
void Face::updateEmotions(std::map<std::string, float> values) {
for (auto& kv : values) {
if (_emotions.find(kv.first) == _emotions.end()) {
_emotions[kv.first] = kv.second;
}
else {
_emotions[kv.first] = 0.9f * _emotions[kv.first] + 0.1f * kv.second;
}
}
}
void Face::updateHeadPose(HeadPoseResults values) {
_headPose = values;
}
void Face::updateLandmarks(std::vector<float> values) {
_landmarks = std::move(values);
}
void Face::updateRealFaceConfidence(float value) {
_realFaceConfidence = value;
}
void Face::updateFaceLiveness(int value) {
_faceLiveness = value;
}
int Face::getAge() {
return static_cast<int>(std::floor(_age + 0.5f));
}
bool Face::isMale() {
return _maleScore > _femaleScore;
}
bool Face::isReal() {
return _realFaceConfidence > 0.4;
}
float Face::getAntispoofingScore() {
return _realFaceConfidence;
}
int Face::getFaceLiveness() {
return _faceLiveness;
}
std::map<std::string, float> Face::getEmotions() {
return _emotions;
}
std::pair<std::string, float> Face::getMainEmotion() {
auto x = std::max_element(_emotions.begin(), _emotions.end(),
[](const std::pair<std::string, float>& p1, const std::pair<std::string, float>& p2) {
return p1.second < p2.second; });
return std::make_pair(x->first, x->second);
}
HeadPoseResults Face::getHeadPose() {
return _headPose;
}
const std::vector<float>& Face::getLandmarks() {
return _landmarks;
}
size_t Face::getId() {
return _id;
}
void Face::ageGenderEnable(bool value) {
_isAgeGenderEnabled = value;
}
void Face::emotionsEnable(bool value) {
_isEmotionsEnabled = value;
}
void Face::headPoseEnable(bool value) {
_isHeadPoseEnabled = value;
}
void Face::landmarksEnable(bool value) {
_isLandmarksEnabled = value;
}
void Face::antispoofingEnable(bool value) {
_isAntispoofingEnabled = value;
}
void Face::faceLivenessEnable(bool value) {
_isFaceLivenessEnabled = value;
}
bool Face::isAgeGenderEnabled() {
return _isAgeGenderEnabled;
}
bool Face::isEmotionsEnabled() {
return _isEmotionsEnabled;
}
bool Face::isHeadPoseEnabled() {
return _isHeadPoseEnabled;
}
bool Face::isLandmarksEnabled() {
return _isLandmarksEnabled;
}
bool Face::isAntispoofingEnabled() {
return _isAntispoofingEnabled;
}
bool Face::isFaceLivenessEnabled() {
return _isFaceLivenessEnabled;
}
float calcIoU(cv::Rect& src, cv::Rect& dst) {
cv::Rect i = src & dst;
cv::Rect u = src | dst;
return static_cast<float>(i.area()) / static_cast<float>(u.area());
}
float calcMean(const cv::Mat& src) {
cv::Mat tmp;
cv::cvtColor(src, tmp, cv::COLOR_BGR2GRAY);
cv::Scalar mean = cv::mean(tmp);
return static_cast<float>(mean[0]);
}
Face matchFace(cv::Rect rect, std::list<Face>& faces) {
Face face(0,rect);
float maxIoU = 0.55f;
for (auto&& f : faces) {
float iou = calcIoU(rect, f._location);
if (iou > maxIoU) {
face = f;
maxIoU = iou;
}
}
return face;
}
BaseDetection::BaseDetection(const std::string& pathToModel, bool doRawOutputMessages)
: pathToModel(pathToModel), doRawOutputMessages(doRawOutputMessages) {
}
bool BaseDetection::enabled() const {
return bool(request);
}
AntispoofingClassifier::AntispoofingClassifier(const std::string& pathToModel, bool doRawOutputMessages)
: BaseDetection(pathToModel, doRawOutputMessages)
{
_modelFilePath = pathToModel;
}
float AntispoofingClassifier::runInfer(const cv::Mat& frame) {
std::lock_guard<std::recursive_mutex> lock(_mutex);
try {
cv::Mat face = frame.clone();
auto inSlice = ov::Tensor{ inTensor, {0, 0, 0, 0}, {1, inShape[1], inShape[2], inShape[3]} };
resize2tensor(face, inSlice);
request.set_input_tensor(ov::Tensor{ inTensor, {0, 0, 0, 0}, {1, inShape[1], inShape[2], inShape[3]} });
request.infer();
face.release();
float r = request.get_output_tensor().data<const float>()[0] * 100;
return r;
}
catch (const std::exception& error) {
std::cerr << "Error: " << error.what() << std::endl;
return -1;
}
}
std::shared_ptr<ov::Model> AntispoofingClassifier::read(const ov::Core& core) {
slog::info << "Reading model: " << _modelFilePath << slog::endl;
std::shared_ptr<ov::Model> model = core.read_model(_modelFilePath);
ov::preprocess::PrePostProcessor ppp(model);
ppp.input().tensor().
set_element_type(ov::element::u8).
set_layout("NHWC");
ppp.input().preprocess().convert_layout("NCHW");
ppp.output().tensor().set_element_type(ov::element::f32);
model = ppp.build();
inShape = model->input().get_shape();
inShape[0] = ndetections;
//ov::set_batch(model, { 1, int64_t(ndetections) });
ov::set_batch(model, { 1 }); // Force single batch for all inputs
return model;
}
AgeGenderDetection::AgeGenderDetection(const std::string& pathToModel,
bool doRawOutputMessages)
: BaseDetection(pathToModel, doRawOutputMessages)
{
_modelFilePath = pathToModel;
}
AgeGenderResults AgeGenderDetection::runInfer(const cv::Mat& frame) {
std::lock_guard<std::recursive_mutex> lock(_mutex);
try {
cv::Mat face = frame.clone();
auto inSlice = ov::Tensor{ inTensor, {0, 0, 0, 0}, {1, inShape[1], inShape[2], inShape[3]} };
resize2tensor(face, inSlice);
request.set_input_tensor(ov::Tensor{ inTensor, {0, 0, 0, 0}, {1, inShape[1], inShape[2], inShape[3]} });
request.infer();
face.release();
AgeGenderResults r = { request.get_tensor(outputAge).data<const float>()[0] * 100,
request.get_tensor(outputGender).data<const float>()[0 * 2 + 1] };
return r;
}
catch (const std::exception& error) {
AgeGenderResults r = { -1, -1 };
return r;
}
}
std::shared_ptr<ov::Model> AgeGenderDetection::read(const ov::Core& core) {
slog::info << "Reading model: " << _modelFilePath << slog::endl;
std::shared_ptr<ov::Model> model = core.read_model(_modelFilePath);
outputAge = "age_conv3";
outputGender = "prob";
ov::preprocess::PrePostProcessor ppp(model);
ppp.input().tensor().
set_element_type(ov::element::u8).
set_layout("NHWC");
ppp.input().preprocess().
convert_element_type(ov::element::f32).
convert_layout("NCHW");
ppp.output(outputAge).tensor().set_element_type(ov::element::f32);
ppp.output(outputGender).tensor().set_element_type(ov::element::f32);
model = ppp.build();
inShape = model->input().get_shape();
inShape[0] = ndetections;
//ov::set_batch(model, { 1, int64_t(ndetections) });
ov::set_batch(model, { 1 }); // Force single batch for all inputs
return model;
}
HeadPoseDetection::HeadPoseDetection(const std::string& pathToModel,
bool doRawOutputMessages)
: BaseDetection(pathToModel, doRawOutputMessages),
outputAngleR("angle_r_fc"), outputAngleP("angle_p_fc"), outputAngleY("angle_y_fc")
{
_modelFilePath = pathToModel;
}
HeadPoseResults HeadPoseDetection::runInfer(const cv::Mat& frame) {
std::lock_guard<std::recursive_mutex> lock(_mutex);
try {
cv::Mat face = frame.clone();
auto inSlice = ov::Tensor{ inTensor, {0, 0, 0, 0}, {1, inShape[1], inShape[2], inShape[3]} };
resize2tensor(face, inSlice);
request.set_input_tensor(ov::Tensor{ inTensor, {0, 0, 0, 0}, {1, inShape[1], inShape[2], inShape[3]} });
request.infer();
face.release();
HeadPoseResults r = { request.get_tensor(outputAngleR).data<const float>()[0],
request.get_tensor(outputAngleP).data<const float>()[0],
request.get_tensor(outputAngleY).data<const float>()[0] };
return r;
}
catch (const std::exception& error) {
HeadPoseResults r = { -1, -1, -1 };
return r;
}
}
std::shared_ptr<ov::Model> HeadPoseDetection::read(const ov::Core& core) {
std::shared_ptr<ov::Model> model = core.read_model(_modelFilePath);
ov::preprocess::PrePostProcessor ppp(model);
ppp.input().tensor().
set_element_type(ov::element::u8).
set_layout("NHWC");
ppp.input().preprocess().convert_layout("NCHW");
ppp.output(outputAngleR).tensor().set_element_type(ov::element::f32);
ppp.output(outputAngleP).tensor().set_element_type(ov::element::f32);
ppp.output(outputAngleY).tensor().set_element_type(ov::element::f32);
model = ppp.build();
inShape = model->input().get_shape();
inShape[0] = ndetections;
//ov::set_batch(model, { 1, int64_t(ndetections) });
ov::set_batch(model, { 1 }); // Force single batch for all inputs
return model;
}
EmotionsDetection::EmotionsDetection(const std::string& pathToModel,
bool doRawOutputMessages)
: BaseDetection(pathToModel, doRawOutputMessages)
{
_modelFilePath = pathToModel;
}
std::map<std::string, float> EmotionsDetection::runInfer(const cv::Mat& frame) {
std::lock_guard<std::recursive_mutex> lock(_mutex);
try {
cv::Mat face = frame.clone();
auto inSlice = ov::Tensor{ inTensor, {0, 0, 0, 0}, {1, inShape[1], inShape[2], inShape[3]} };
resize2tensor(face, inSlice);
request.set_input_tensor(ov::Tensor{ inTensor, {0, 0, 0, 0}, {1, inShape[1], inShape[2], inShape[3]} });
request.infer();
face.release();
auto tensor = request.get_output_tensor();
auto emotionsVecSize = emotionsVec.size();
size_t numOfChannels = tensor.get_shape().at(1);
if (numOfChannels == emotionsVecSize) {
const float* emotionsValues = tensor.data<const float>();
auto outputIdxPos = emotionsValues + 0 * emotionsVecSize;
std::map<std::string, float> emotions;
for (size_t i = 0; i < emotionsVecSize; i++) {
emotions[emotionsVec[i]] = outputIdxPos[i];
}
return emotions;
}
else return { };
}
catch (const std::exception& error) {
std::cerr << "Error: " << error.what() << std::endl;
return { };
}
}
std::shared_ptr<ov::Model> EmotionsDetection::read(const ov::Core& core) {
slog::info << "Reading model: " << _modelFilePath << slog::endl;
std::shared_ptr<ov::Model> model = core.read_model(_modelFilePath);
ov::preprocess::PrePostProcessor ppp(model);
ppp.input().tensor().
set_element_type(ov::element::u8).
set_layout("NHWC");
ppp.input().preprocess().convert_layout("NCHW");
ppp.output().tensor().set_element_type(ov::element::f32);
model = ppp.build();
inShape = model->input().get_shape();
inShape[0] = ndetections;
//ov::set_batch(model, { 1, int64_t(ndetections) });
ov::set_batch(model, { 1 }); // Force single batch for all inputs
return model;
}
FacialLandmarksDetection::FacialLandmarksDetection(const std::string& pathToModel,
bool doRawOutputMessages)
: BaseDetection(pathToModel, doRawOutputMessages)
{
_modelFilePath = pathToModel;
}
std::vector<float> FacialLandmarksDetection::runInfer(const cv::Mat& frame) {
std::lock_guard<std::recursive_mutex> lock(_mutex);
std::vector<float> normedLandmarks;
try {
cv::Mat face = frame.clone();
auto inSlice = ov::Tensor{ inTensor, {0, 0, 0, 0}, {1, inShape[1], inShape[2], inShape[3]} };
resize2tensor(face, inSlice);
request.set_input_tensor(ov::Tensor{ inTensor, {0, 0, 0, 0}, {1, inShape[1], inShape[2], inShape[3]} });
request.infer();
face.release();
auto tensor = request.get_output_tensor();
size_t n_lm = tensor.get_shape().at(1);
const float* normed_coordinates = tensor.data<const float>();
auto begin = 0;
auto end = begin + n_lm / 2;
for (auto i_lm = begin; i_lm < end; ++i_lm) {
float normed_x = normed_coordinates[2 * i_lm];
float normed_y = normed_coordinates[2 * i_lm + 1];
normedLandmarks.push_back(normed_x);
normedLandmarks.push_back(normed_y);
}
return normedLandmarks;
}
catch (const std::exception& error) {
std::cerr << "Error: " << error.what() << std::endl;
return normedLandmarks;
}
}
std::shared_ptr<ov::Model> FacialLandmarksDetection::read(const ov::Core& core) {
std::shared_ptr<ov::Model> model = core.read_model(_modelFilePath);
ov::Shape outShape = model->output().get_shape();
if (outShape.size() != 2 && outShape.back() != 70) {
return model;
}
ov::preprocess::PrePostProcessor ppp(model);
ppp.input().tensor().
set_element_type(ov::element::u8).
set_layout("NHWC");
ppp.input().preprocess().convert_layout("NCHW");
ppp.output().tensor().set_element_type(ov::element::f32);
model = ppp.build();
inShape = model->input().get_shape();
inShape[0] = ndetections;
//ov::set_batch(model, { 1, int64_t(ndetections) });
ov::set_batch(model, { 1 }); // Force single batch for all inputs
return model;
}
//void ANSFRHelper::ANSFRHelper::CheckCudaStatus(cudaError_t status) {
// try {
// if (status != cudaSuccess) {
// std::cerr << "CUDA API failed with status " << status << ": " << cudaGetErrorString(status) << std::endl;
// }
// }
// catch (std::exception& e) {
// std::cerr << "ANSFRHelper::CheckCublasStatus" << e.what();
// }
//}
//void ANSFRHelper::ANSFRHelper::CheckCublasStatus(cublasStatus_t status) {
// try {
// if (status != CUBLAS_STATUS_SUCCESS) {
// std::cerr << "cuBLAS API failed with status " << status << std::endl;
// }
// }
// catch (std::exception& e) {
// std::cerr << "ANSFRHelper::CheckCublasStatus" << e.what();
// }
//}
void ANSFRHelper::GetCroppedFaces(const cv::Mat &input, std::vector<Object>& outputBbox, int resize_w, int resize_h, std::vector<struct CroppedFace>& croppedFaces) {
croppedFaces.clear();
cv::Mat frame = input.clone();
if (outputBbox.size() > 0) {
for (std::vector<Object>::iterator it = outputBbox.begin(); it != outputBbox.end(); it++) {
int x1, y1, x2, y2;
x1 = (*it).box.x;
y1 = (*it).box.y;
x2 = (*it).box.x + (*it).box.width;
y2 = (*it).box.y + (*it).box.height;
cv::Rect facePos(cv::Point(x1, y1), cv::Point(x2, y2));
cv::Mat tempCrop = frame(facePos);
struct CroppedFace currFace;
cv::resize(tempCrop, currFace.faceMat, cv::Size(resize_h, resize_w), 0, 0, cv::INTER_CUBIC);
currFace.face = currFace.faceMat.clone();
currFace.x1 = x1;
currFace.y1 = y1;
currFace.x2 = x2;
currFace.y2 = y2;
croppedFaces.push_back(currFace);
}
}
else {
// this will be the same image
int x1, y1, x2, y2;
x1 = 0;
y1 = 0;
x2 = frame.cols;
y2 = frame.rows;
cv::Rect facePos(cv::Point(x1, y1), cv::Point(x2, y2));
cv::Mat tempCrop = frame(facePos);
struct CroppedFace currFace;
cv::resize(tempCrop, currFace.faceMat, cv::Size(resize_h, resize_w), 0, 0, cv::INTER_CUBIC);
currFace.face = currFace.faceMat.clone();
currFace.x1 = x1;
currFace.y1 = y1;
currFace.x2 = x2;
currFace.y2 = y2;
croppedFaces.push_back(currFace);
}
frame.release();
}
bool ANSFRHelper::LoadConfigFile(std::string configFile, FRConfig& config) {
try {
if (FileExist(configFile)) {
boost::property_tree::ptree pt;
std::vector <std::string> labels;
boost::property_tree::read_json(configFile, pt);
config._databasePath = GetData<std::string>(pt, "database_path");
config._recEngineFile = GetData<std::string>(pt, "rec_engine");
config._detEngineFile = GetData<std::string>(pt, "det_engine");
return true;
}
else return false;
}
catch (std::exception& e) {
std::cerr << "ANSFRHelper::LoadConfigFile" << e.what();
return false;
}
}
void ANSFRHelper::GetFilePaths(std::string rootPath, std::vector<struct Paths>& paths) {
/*
imagesPath--|
|--class0--|
| |--f0.jpg
| |--f1.jpg
|
|--class1--|
|--f0.jpg
|--f1.jpg
...
*/
DIR* dir;
struct dirent* entry;
std::string postfix = ".jpg";
if ((dir = opendir(rootPath.c_str())) != NULL) {
while ((entry = readdir(dir)) != NULL) {
std::string class_path = rootPath + "/" + entry->d_name;
DIR* class_dir = opendir(class_path.c_str());
struct dirent* file_entry;
while ((file_entry = readdir(class_dir)) != NULL) {
std::string name(file_entry->d_name);
if (name.length() >= postfix.length() && 0 == name.compare(name.length() - postfix.length(), postfix.length(), postfix))
if (file_entry->d_type != DT_DIR) {
struct Paths tempPaths;
tempPaths.className = std::string(entry->d_name);
tempPaths.absPath = class_path + "/" + name;
paths.push_back(tempPaths);
}
}
}
closedir(dir);
}
}
unsigned char* ANSCENTER::ANSFRHelper::CVMatToBytes(cv::Mat image, unsigned int& bufferLengh)
{
int size = int(image.total() * image.elemSize());
unsigned char* bytes = new unsigned char[size]; // you will have to delete[] that later
std::memcpy(bytes, image.data, size * sizeof(unsigned char));
bufferLengh = size * sizeof(unsigned char);
return bytes;
}
std::string ANSFRHelper::StringCurrentDateTime()
{
// Get the current time
std::time_t t = std::time(nullptr);
std::tm now = {}; // Initialize to all zeros
localtime_s(&now, &t);
// Convert the time to a string
std::stringstream ss;
ss << std::put_time(&now, "%Y%m%d%H%M%S");
std::string currentDateTime = ss.str();
return currentDateTime;
}
std::string ANSFRHelper::FaceObjectsToJsonString(const std::vector<FaceResultObject>& faces) {
boost::property_tree::ptree root;
boost::property_tree::ptree detectedObjects;
for (int i = 0; i < faces.size(); i++) {
boost::property_tree::ptree detectedNode;
detectedNode.put("user_id", faces[i].userId);
detectedNode.put("user_name", faces[i].userName);
detectedNode.put("similarity", faces[i].similarity);
detectedNode.put("is_unknown", faces[i].isUnknown);
detectedNode.put("prob", faces[i].confidence);
detectedNode.put("x", faces[i].box.x);
detectedNode.put("y", faces[i].box.y);
detectedNode.put("width", faces[i].box.width);
detectedNode.put("height", faces[i].box.height);
detectedNode.put("extra_info", faces[i].extraInformation);
// we might add masks into this using comma seperated string
detectedObjects.push_back(std::make_pair("", detectedNode));
}
root.add_child("results", detectedObjects);
std::ostringstream stream;
boost::property_tree::write_json(stream, root,false);
std::string faceResult = stream.str();
return faceResult;
}
std::string ANSFRHelper::FaceAttributeToJsonString(Face face) {
boost::property_tree::ptree root;
boost::property_tree::ptree detectedObjects;
boost::property_tree::ptree detectedNode;
if(face.isAntispoofingEnabled()) {
if (face.isReal()) {
detectedNode.put("Antispoofing", "Real");
}
else {
detectedNode.put("Antispoofing", "Fake");
}
}
if (face.isFaceLivenessEnabled()) {
if (face.getFaceLiveness() == 1) {
detectedNode.put("FaceLiveness", "Real");
}
else {
detectedNode.put("FaceLiveness", "Fake");
}
}
if (face.isAgeGenderEnabled()) {
if (face.isMale()) {
detectedNode.put("Gender", "Male");
}
else {
detectedNode.put("Gender", "Female");
}
int age = face.getAge();
if (age < 12)
detectedNode.put("Age", "Kids");
else if (age < 20)
detectedNode.put("Age", "Young Adults");
else if (age < 60)
detectedNode.put("Age", "Adults");
else
detectedNode.put("Age", "Elders");
}
if (face.isEmotionsEnabled()) {
const auto& [emotionName, emotionConfidence] = face.getMainEmotion();
std::ostringstream emotionStr;
std::string adjustedEmotionName = emotionName;
if (emotionName == "Sad" || emotionName == "sad")
adjustedEmotionName = "Attentive";
else if (emotionName == "Happy" || emotionName == "happy")
adjustedEmotionName = "Happy";
else if (emotionName == "Neutral" || emotionName == "neutral")
adjustedEmotionName = "Neutral";
else if (emotionName == "Angry" || emotionName == "angry")
adjustedEmotionName = "Attentive";
else if (emotionName == "Surprised" || emotionName == "surprised")
adjustedEmotionName = "Attentive";
else if (emotionName == "Disgusted" || emotionName == "disgusted")
adjustedEmotionName = "Attentive";
else if (emotionName == "Fearful" || emotionName == "fearful")
adjustedEmotionName = "Attentive";
else if (emotionName == "Anger" || emotionName == "anger")
adjustedEmotionName = "Attentive";
else
adjustedEmotionName = "Neutral";
emotionStr << adjustedEmotionName << " (" << static_cast<int>(emotionConfidence * 100) << "%)";
detectedNode.put("Emotion", emotionStr.str());
}
if (face.isHeadPoseEnabled()) {
const auto& headPose = face.getHeadPose();
std::ostringstream headPoseStr;
headPoseStr << "(" << static_cast<int>(headPose.angle_r) << ", "
<< static_cast<int>(headPose.angle_p) << ", "
<< static_cast<int>(headPose.angle_y) << ")";
detectedNode.put("Pose(r,p,y)", headPoseStr.str());
}
// we might add masks into this using comma seperated string
detectedObjects.push_back(std::make_pair("", detectedNode));
root.add_child("attributes", detectedObjects);
std::ostringstream stream;
boost::property_tree::write_json(stream, root,false);
std::string faceResult = stream.str();
return faceResult;
}
2026-04-06 17:02:42 +10:00
// Encode non-ASCII UTF-8 characters as double-escaped Unicode (\uXXXX) for JSON transport.
// Surrogate pairs are used for codepoints above U+FFFF.
static std::string DoubleEscapeUnicode(const std::string& utf8Str) {
bool hasNonAscii = false;
for (unsigned char c : utf8Str) {
if (c >= 0x80) { hasNonAscii = true; break; }
}
if (!hasNonAscii) return utf8Str;
std::string result;
result.reserve(utf8Str.size() * 2);
size_t i = 0;
while (i < utf8Str.size()) {
unsigned char c = static_cast<unsigned char>(utf8Str[i]);
if (c < 0x80) { result += utf8Str[i++]; continue; }
uint32_t cp = 0;
if ((c & 0xE0) == 0xC0 && i + 1 < utf8Str.size()) {
cp = ((c & 0x1F) << 6) | (static_cast<unsigned char>(utf8Str[i + 1]) & 0x3F); i += 2;
} else if ((c & 0xF0) == 0xE0 && i + 2 < utf8Str.size()) {
cp = ((c & 0x0F) << 12) | ((static_cast<unsigned char>(utf8Str[i + 1]) & 0x3F) << 6) | (static_cast<unsigned char>(utf8Str[i + 2]) & 0x3F); i += 3;
} else if ((c & 0xF8) == 0xF0 && i + 3 < utf8Str.size()) {
cp = ((c & 0x07) << 18) | ((static_cast<unsigned char>(utf8Str[i + 1]) & 0x3F) << 12) | ((static_cast<unsigned char>(utf8Str[i + 2]) & 0x3F) << 6) | (static_cast<unsigned char>(utf8Str[i + 3]) & 0x3F); i += 4;
} else { i++; continue; }
if (cp <= 0xFFFF) { char buf[8]; snprintf(buf, sizeof(buf), "\\u%04x", cp); result += buf; }
else { cp -= 0x10000; char buf[16]; snprintf(buf, sizeof(buf), "\\u%04x\\u%04x", 0xD800 + (uint16_t)(cp >> 10), 0xDC00 + (uint16_t)(cp & 0x3FF)); result += buf; }
}
return result;
}
// Using nlohmann/json (consistent with FaceObjectsToJsonString)
2026-03-28 16:54:11 +11:00
std::string ANSFRHelper::UserRecordToJsonString(const UserRecord userRecord) {
2026-04-06 17:02:42 +10:00
try {
nlohmann::json faceIdArray = nlohmann::json::array();
for (const auto& faceId : userRecord.FaceIds) {
faceIdArray.push_back(faceId);
}
nlohmann::json root = {
{"user_id", std::to_string(userRecord.UserId)},
{"user_code", userRecord.UserCode},
{"user_username", DoubleEscapeUnicode(userRecord.UserName)},
{"face_ids", faceIdArray}
};
return root.dump();
}
catch (const std::exception&) {
return R"({})";
2026-03-28 16:54:11 +11:00
}
}
2026-04-06 17:02:42 +10:00
// Using nlohmann/json (consistent with FaceObjectsToJsonString)
2026-03-28 16:54:11 +11:00
std::string ANSFRHelper::UserRecordsToJsonString(const std::vector<UserRecord> userRecords) {
2026-04-06 17:02:42 +10:00
try {
nlohmann::json root;
auto& records = root["user_records"] = nlohmann::json::array();
for (const auto& userRecord : userRecords) {
nlohmann::json faceIdArray = nlohmann::json::array();
for (const auto& faceId : userRecord.FaceIds) {
faceIdArray.push_back(faceId);
}
records.push_back({
{"user_id", std::to_string(userRecord.UserId)},
{"user_code", userRecord.UserCode},
{"user_username", DoubleEscapeUnicode(userRecord.UserName)},
{"face_ids", faceIdArray}
});
2026-03-28 16:54:11 +11:00
}
2026-04-06 17:02:42 +10:00
return root.dump();
}
catch (const std::exception&) {
return R"({"user_records":[]})";
2026-03-28 16:54:11 +11:00
}
}
std::string ANSFRHelper::FaceRecordToJsonString(const FaceRecord faceRecord) {
boost::property_tree::ptree root;
root.put("face_id", faceRecord.FaceId);
root.put("user_id", faceRecord.UserId);
root.put("image_path", faceRecord.ImagePath);
std::ostringstream stream;
boost::property_tree::write_json(stream, root,false);
std::string userRecordResult = stream.str();
return userRecordResult;
}
std::string ANSFRHelper::FaceRecordsToJsonString(const std::vector<FaceRecord> faceRecords) {
boost::property_tree::ptree root;
boost::property_tree::ptree faceNodes;
for (int i = 0; i < faceRecords.size(); i++) {
FaceRecord faceRecord = faceRecords[i];
boost::property_tree::ptree faceNode;
faceNode.put("face_id", faceRecord.FaceId);
faceNode.put("user_id", faceRecord.UserId);
faceNode.put("image_path", faceRecord.ImagePath);
faceNodes.push_back(std::make_pair("", faceNode));
}
root.add_child("face_records", faceNodes);
std::ostringstream stream;
boost::property_tree::write_json(stream, root,false);
std::string userRecordResult = stream.str();
return userRecordResult;
}
cv::Mat ANSFRHelper::PreprocessImg(cv::Mat& img, int input_w, int input_h) {
try {
int w, h, x, y;
float r_w = input_w / (img.cols * 1.0);
float r_h = input_h / (img.rows * 1.0);
if (r_h > r_w) {
w = input_w;
h = r_w * img.rows;
x = 0;
y = (input_h - h) / 2;
}
else {
w = r_h * img.cols;
h = input_h;
x = (input_w - w) / 2;
y = 0;
}
cv::Mat re(h, w, CV_8UC3);
cv::resize(img, re, re.size(), 0, 0, cv::INTER_LINEAR);
cv::Mat out(input_h, input_w, CV_8UC3, cv::Scalar(128, 128, 128));
re.copyTo(out(cv::Rect(x, y, re.cols, re.rows)));
return out;
}
catch (std::exception& e) {
cv::Mat result;
std::cerr << "ANSFRHelper::PreprocessImg" << e.what();
return result;
}
}
int ANSFRHelper::ReadFilesInDir(const char* p_dir_name, std::vector<std::string>& file_names) {
try {
DIR* p_dir = opendir(p_dir_name);
if (p_dir == nullptr) {
return -1;
}
struct dirent* p_file = nullptr;
while ((p_file = readdir(p_dir)) != nullptr) {
if (strcmp(p_file->d_name, ".") != 0 &&
strcmp(p_file->d_name, "..") != 0) {
std::string cur_file_name(p_file->d_name);
file_names.push_back(cur_file_name);
}
}
closedir(p_dir);
return 0;
}
catch (std::exception& e) {
std::cerr << "ANSFRHelper::ReadFilesInDir" << e.what();
return -1;
}
}
cv::Rect ANSFRHelper::GetRectAdaptLandmark(cv::Mat& img, int input_w, int input_h, float bbox[4], float lmk[10]) {
int l, r, t, b;
float r_w = input_w / (img.cols * 1.0);
float r_h = input_h / (img.rows * 1.0);
if (r_h > r_w) {
l = bbox[0] / r_w;
r = bbox[2] / r_w;
t = (bbox[1] - (input_h - r_w * img.rows) / 2) / r_w;
b = (bbox[3] - (input_h - r_w * img.rows) / 2) / r_w;
for (int i = 0; i < 10; i += 2) {
lmk[i] /= r_w;
lmk[i + 1] = (lmk[i + 1] - (input_h - r_w * img.rows) / 2) / r_w;
}
}
else {
l = (bbox[0] - (input_w - r_h * img.cols) / 2) / r_h;
r = (bbox[2] - (input_w - r_h * img.cols) / 2) / r_h;
t = bbox[1] / r_h;
b = bbox[3] / r_h;
for (int i = 0; i < 10; i += 2) {
lmk[i] = (lmk[i] - (input_w - r_h * img.cols) / 2) / r_h;
lmk[i + 1] /= r_h;
}
}
return cv::Rect(l, t, r - l, b - t);
}
float ANSFRHelper::IOU(float lbox[4], float rbox[4]) {
float interBox[] = {
MAX(lbox[0], rbox[0]), //left
MIN(lbox[2], rbox[2]), //right
MAX(lbox[1], rbox[1]), //top
MIN(lbox[3], rbox[3]), //bottom
};
if (interBox[2] > interBox[3] || interBox[0] > interBox[1])
return 0.0f;
float interBoxS = (interBox[1] - interBox[0]) * (interBox[3] - interBox[2]);
return interBoxS / ((lbox[2] - lbox[0]) * (lbox[3] - lbox[1]) + (rbox[2] - rbox[0]) * (rbox[3] - rbox[1]) - interBoxS + 0.000001f);
}
bool ANSFRHelper::CMP(const Detection& a, const Detection& b) {
return a.class_confidence > b.class_confidence;
}
void ANSFRHelper::NMS(std::vector<Detection>& res, float* output, float nms_thresh) {
std::vector<Detection> dets;
for (int i = 0; i < output[0]; i++) {
if (output[15 * i + 1 + 4] <= 0.1) continue;
Detection det;
memcpy(&det, &output[15 * i + 1], sizeof(Detection));
dets.push_back(det);
}
std::sort(dets.begin(), dets.end(), ANSFRHelper::CMP);
for (size_t m = 0; m < dets.size(); ++m) {
auto& item = dets[m];
res.push_back(item);
//std::cout << item.class_confidence << " bbox " << item.bbox[0] << ", " << item.bbox[1] << ", " << item.bbox[2] << ", " << item.bbox[3] << std::endl;
for (size_t n = m + 1; n < dets.size(); ++n) {
if (ANSFRHelper::IOU(item.bbox, dets[n].bbox) > nms_thresh) {
dets.erase(dets.begin() + n);
--n;
}
}
}
}
std::map<std::string, Weights> ANSFRHelper::LoadWeights(const std::string file) {
std::cout << "Loading weights: " << file << std::endl;
std::map<std::string, Weights> weightMap;
// Open weights file
std::ifstream input(file);
assert(input.is_open() && "Unable to load weight file.");
// Read number of weight blobs
int32_t count;
input >> count;
assert(count > 0 && "Invalid weight map file.");
while (count--)
{
Weights wt{ DataType::kFLOAT, nullptr, 0 };
uint32_t size;
// Read name and type of blob
std::string name;
input >> name >> std::dec >> size;
wt.type = DataType::kFLOAT;
// Load blob
uint32_t* val = reinterpret_cast<uint32_t*>(malloc(sizeof(val) * size));
for (uint32_t x = 0, y = size; x < y; ++x)
{
input >> std::hex >> val[x];
}
wt.values = val;
wt.count = size;
weightMap[name] = wt;
}
return weightMap;
}
Weights ANSFRHelper::GetWeights(std::map<std::string, Weights>& weightMap, std::string key) {
if (weightMap.count(key) != 1) {
std::cerr << key << " not existed in weight map, fatal error!!!" << std::endl;
exit(-1);
}
return weightMap[key];
}
IScaleLayer* ANSFRHelper::AddBatchNorm2D(INetworkDefinition* network, std::map<std::string, Weights>& weightMap, ITensor& input, std::string lname, float eps) {
float* gamma = (float*)weightMap[lname + ".weight"].values;
float* beta = (float*)weightMap[lname + ".bias"].values;
float* mean = (float*)weightMap[lname + ".running_mean"].values;
float* var = (float*)weightMap[lname + ".running_var"].values;
int len = weightMap[lname + ".running_var"].count;
float* scval = reinterpret_cast<float*>(malloc(sizeof(float) * len));
for (int i = 0; i < len; i++) {
scval[i] = gamma[i] / sqrt(var[i] + eps);
}
Weights scale{ DataType::kFLOAT, scval, len };
float* shval = reinterpret_cast<float*>(malloc(sizeof(float) * len));
for (int i = 0; i < len; i++) {
shval[i] = beta[i] - mean[i] * gamma[i] / sqrt(var[i] + eps);
}
Weights shift{ DataType::kFLOAT, shval, len };
float* pval = reinterpret_cast<float*>(malloc(sizeof(float) * len));
for (int i = 0; i < len; i++) {
pval[i] = 1.0;
}
Weights power{ DataType::kFLOAT, pval, len };
weightMap[lname + ".scale"] = scale;
weightMap[lname + ".shift"] = shift;
weightMap[lname + ".power"] = power;
IScaleLayer* scale_1 = network->addScale(input, ScaleMode::kCHANNEL, shift, scale, power);
assert(scale_1);
return scale_1;
}
/// <summary>
/// MatMul
/// </summary>
//MatMul::MatMul() {
// try {
// ANSFRHelper::ANSFRHelper::CheckCudaStatus(cudaMalloc(&workspace, workspaceSize));
// ANSFRHelper::CheckCublasStatus(cublasLtCreate(&ltHandle));
// ANSFRHelper::CheckCudaStatus(cudaStreamCreate(&stream));
// }
// catch (std::exception& e) {
// std::cerr << "MatMul::MatMul" << e.what();
// }
//}
//void MatMul::Init(float* knownEmbeds, int numRow, int numCol) {
// try {
// m = static_cast<const int>(numRow);
// k = static_cast<const int>(numCol);
// lda = static_cast<const int>(numCol);
// ldb = static_cast<const int>(numCol);
// ldc = static_cast<const int>(numRow);
// // alloc and copy known embeddings to GPU
// ANSFRHelper::CheckCudaStatus(cudaMalloc(reinterpret_cast<void**>(&dA), m * k * sizeof(float)));
// ANSFRHelper::CheckCudaStatus(cudaMemcpyAsync(dA, knownEmbeds, m * k * sizeof(float), cudaMemcpyHostToDevice, stream));
// // create operation desciriptor; see cublasLtMatmulDescAttributes_t for details about defaults;
// // here we just need to set the transforms for A and B
// ANSFRHelper::CheckCublasStatus(cublasLtMatmulDescCreate(&operationDesc, computeType, cudaDataType));
// ANSFRHelper::CheckCublasStatus(cublasLtMatmulDescSetAttribute(operationDesc, CUBLASLT_MATMUL_DESC_TRANSA, &transa, sizeof(transa)));
// ANSFRHelper::CheckCublasStatus(cublasLtMatmulDescSetAttribute(operationDesc, CUBLASLT_MATMUL_DESC_TRANSB, &transb, sizeof(transb)));
// // create matrix descriptors, we are good with the details here so no need to set any extra attributes
// ANSFRHelper::CheckCublasStatus(cublasLtMatrixLayoutCreate(&Adesc, cudaDataType, transa == CUBLAS_OP_N ? m : k, transa == CUBLAS_OP_N ? k : m, lda));
// // create preference handle; here we could use extra attributes to disable tensor ops or to make sure algo selected
// // will work with badly aligned A, B, C; here for simplicity we just assume A,B,C are always well aligned (e.g.
// // directly come from cudaMalloc)
// ANSFRHelper::CheckCublasStatus(cublasLtMatmulPreferenceCreate(&preference));
// ANSFRHelper::CheckCublasStatus(cublasLtMatmulPreferenceSetAttribute(preference, CUBLASLT_MATMUL_PREF_MAX_WORKSPACE_BYTES, &workspaceSize, sizeof(workspaceSize)));
// }
// catch (std::exception& e) {
// std::cerr << "MatMul::Init" << e.what();
// }
//}
//void MatMul::Calculate(float* embeds, int embedCount, float* outputs) {
// try {
// n = embedCount;
// // Allocate arrays on GPU
// ANSFRHelper::CheckCudaStatus(cudaMalloc(reinterpret_cast<void**>(&dB), k * n * sizeof(float)));
// ANSFRHelper::CheckCudaStatus(cudaMalloc(reinterpret_cast<void**>(&dC), m * n * sizeof(float)));
// ANSFRHelper::CheckCudaStatus(cudaMemcpyAsync(dB, embeds, k * n * sizeof(float), cudaMemcpyHostToDevice, stream));
// // create matrix descriptors, we are good with the details here so no need to set any extra attributes
// cublasLtMatrixLayout_t Bdesc = NULL, Cdesc = NULL;
// ANSFRHelper::CheckCublasStatus(cublasLtMatrixLayoutCreate(&Bdesc, cudaDataType, transb == CUBLAS_OP_N ? k : n, transb == CUBLAS_OP_N ? n : k, ldb));
// ANSFRHelper::CheckCublasStatus(cublasLtMatrixLayoutCreate(&Cdesc, cudaDataType, m, n, ldc));
// // we just need the best available heuristic to try and run matmul. There is no guarantee this will work, e.g. if A
// // is badly aligned, you can request more (e.g. 32) algos and try to run them one by one until something works
// int returnedResults = 0;
// cublasLtMatmulHeuristicResult_t heuristicResult = {};
// ANSFRHelper::CheckCublasStatus(cublasLtMatmulAlgoGetHeuristic(ltHandle, operationDesc, Adesc, Bdesc, Cdesc, Cdesc, preference, 1, &heuristicResult, &returnedResults));
// if (returnedResults == 0) {
// ANSFRHelper::CheckCublasStatus(CUBLAS_STATUS_NOT_SUPPORTED);
// }
// // Do the actual multiplication
// ANSFRHelper::CheckCublasStatus(cublasLtMatmul(ltHandle, operationDesc, &alpha, dA, Adesc, dB, Bdesc, &beta, dC, Cdesc, dC, Cdesc, &heuristicResult.algo, workspace,
// workspaceSize, stream));
// // Cleanup: descriptors are no longer needed as all GPU work was already enqueued
// if (Cdesc)
// ANSFRHelper::CheckCublasStatus(cublasLtMatrixLayoutDestroy(Cdesc));
// if (Bdesc)
// ANSFRHelper::CheckCublasStatus(cublasLtMatrixLayoutDestroy(Bdesc));
// // Copy the result on host memory
// ANSFRHelper::CheckCudaStatus(cudaMemcpyAsync(outputs, dC, m * n * sizeof(float), cudaMemcpyDeviceToHost, stream));
// // CUDA stream sync
// ANSFRHelper::CheckCudaStatus(cudaStreamSynchronize(stream));
// // Free GPU memory
// ANSFRHelper::CheckCudaStatus(cudaFree(dB));
// ANSFRHelper::CheckCudaStatus(cudaFree(dC));
// }
// catch (std::exception& e) {
// std::cerr <<"MatMul::Calculate"<< e.what();
// }
//}
//MatMul::~MatMul() {
// try {
// if (preference)
// ANSFRHelper::CheckCublasStatus(cublasLtMatmulPreferenceDestroy(preference));
// if (Adesc)
// ANSFRHelper::CheckCublasStatus(cublasLtMatrixLayoutDestroy(Adesc));
// if (operationDesc)
// ANSFRHelper::CheckCublasStatus(cublasLtMatmulDescDestroy(operationDesc));
// ANSFRHelper::CheckCublasStatus(cublasLtDestroy(ltHandle));
// ANSFRHelper::CheckCudaStatus(cudaFree(dA));
// ANSFRHelper::CheckCudaStatus(cudaFree(workspace));
// ANSFRHelper::CheckCudaStatus(cudaStreamDestroy(stream));
// }
// catch (std::exception& e) {
// std::cout << "MatMul::~MatMul" << e.what();
// }
//}
//Int8EntropyCalibrator2::Int8EntropyCalibrator2(int batchsize, int input_w, int input_h, const char* img_dir, const char* calib_table_name, const char* input_blob_name, bool read_cache)
// : batchsize_(batchsize)
// , input_w_(input_w)
// , input_h_(input_h)
// , img_idx_(0)
// , img_dir_(img_dir)
// , calib_table_name_(calib_table_name)
// , input_blob_name_(input_blob_name)
// , read_cache_(read_cache)
//{
// input_count_ = 3 * input_w * input_h * batchsize;
// CUDACHECK(cudaMalloc(&device_input_, input_count_ * sizeof(float)));
// ANSFRHelper::ReadFilesInDir(img_dir, img_files_);
//}
//Int8EntropyCalibrator2::~Int8EntropyCalibrator2()
//{
// CUDACHECK(cudaFree(device_input_));
//}
//int Int8EntropyCalibrator2::getBatchSize() const TRT_NOEXCEPT
//{
// return batchsize_;
//}
//bool Int8EntropyCalibrator2::getBatch(void* bindings[], const char* names[], int nbBindings) TRT_NOEXCEPT
//{
// if (img_idx_ + batchsize_ > (int)img_files_.size()) {
// return false;
// }
// std::vector<cv::Mat> input_imgs_;
// for (int i = img_idx_; i < img_idx_ + batchsize_; i++) {
// std::cout << img_files_[i] << " " << i << std::endl;
// cv::Mat temp = cv::imread(img_dir_ + img_files_[i]);
// if (temp.empty()) {
// std::cerr << "Fatal error: image cannot open!" << std::endl;
// return false;
// }
// cv::Mat pr_img = ANSFRHelper::PreprocessImg(temp, input_w_, input_h_);
// input_imgs_.push_back(pr_img);
// }
// img_idx_ += batchsize_;
// cv::Mat blob = cv::dnn::blobFromImages(input_imgs_, 1.0, cv::Size(input_w_, input_h_), cv::Scalar(104, 117, 123), false, false);
// CUDACHECK(cudaMemcpy(device_input_, blob.ptr<float>(0), input_count_ * sizeof(float), cudaMemcpyHostToDevice));
// assert(!strcmp(names[0], input_blob_name_));
// bindings[0] = device_input_;
// return true;
//}
//const void* Int8EntropyCalibrator2::readCalibrationCache(size_t& length) TRT_NOEXCEPT
//{
// std::cout << "reading calib cache: " << calib_table_name_ << std::endl;
// calib_cache_.clear();
// std::ifstream input(calib_table_name_, std::ios::binary);
// input >> std::noskipws;
// if (read_cache_ && input.good())
// {
// std::copy(std::istream_iterator<char>(input), std::istream_iterator<char>(), std::back_inserter(calib_cache_));
// }
// length = calib_cache_.size();
// return length ? calib_cache_.data() : nullptr;
//}
//void Int8EntropyCalibrator2::writeCalibrationCache(const void* cache, size_t length) TRT_NOEXCEPT
//{
// std::cout << "writing calib cache: " << calib_table_name_ << " size: " << length << std::endl;
// std::ofstream output(calib_table_name_, std::ios::binary);
// output.write(reinterpret_cast<const char*>(cache), length);
//}
}