/*############################################################################### # # Copyright 2020-2021 NVIDIA Corporation # # Permission is hereby granted, free of charge, to any person obtaining a copy of # this software and associated documentation files (the "Software"), to deal in # the Software without restriction, including without limitation the rights to # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of # the Software, and to permit persons to whom the Software is furnished to do so, # subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # ###############################################################################*/ #include "FaceEngine.h" #include "RenderingUtils.h" #include bool CheckResult(NvCV_Status nvErr, unsigned line) { if (NVCV_SUCCESS == nvErr) return true; std::cout << "ERROR: " << NvCV_GetErrorStringFromCode(nvErr) << ", line " << line << std::endl; return false; } FaceEngine::Err FaceEngine::fitFaceModel(cv::Mat& frame) { FaceEngine::Err err = FaceEngine::Err::errNone; NvCV_Status nvErr; if (!frame.empty()) { NvCVImage fxSrcChunkyCPU; (void)NVWrapperForCVMat(&frame, &fxSrcChunkyCPU); NvCV_Status cvErr = NvCVImage_Transfer(&fxSrcChunkyCPU, &inputImageBuffer, 1.0f, stream, NULL); if (NVCV_SUCCESS != cvErr) { return errRun; } } nvErr = NvAR_Run(faceFitHandle); BAIL_IF_NVERR(nvErr, err, FaceEngine::Err::errRun); if (getAverageLandmarksConfidence() < confidenceThreshold) return FaceEngine::Err::errRun; bail: return err; } NvAR_FaceMesh* FaceEngine::getFaceMesh() { return face_mesh; } NvAR_RenderingParams* FaceEngine::getRenderingParams() { return rendering_params; } float* FaceEngine::getShapeEigenvalues() { return shapeEigenvalues.data(); } float* FaceEngine::getExpressionCoefficients() { return expressionCoefficients.data(); } int FaceEngine::getNumShapeEigenvalues() { unsigned n = 0; (void)NvAR_GetU32(faceFitHandle, NvAR_Parameter_Config(ShapeEigenValueCount), &n); return n; } int FaceEngine::getNumExpressionCoefficients() { unsigned n = 0; (void)NvAR_GetU32(faceFitHandle, NvAR_Parameter_Config(ExpressionCount), &n); return n; } FaceEngine::Err FaceEngine::createFeatures(const char* modelPath, unsigned int _batchSize, unsigned int mode) { FaceEngine::Err err = FaceEngine::Err::errNone; NvCV_Status cuErr = NvAR_CudaStreamCreate(&stream); if (NVCV_SUCCESS != cuErr) { printf("Cannot create a cuda stream: %s\n", NvCV_GetErrorStringFromCode(cuErr)); return errInitialization; } if (appMode == faceDetection) { err = createFaceDetectionFeature(modelPath, stream); if (err != Err::errNone) { printf("ERROR: An error has occurred while initializing Face Detection\n"); } } else if (appMode == landmarkDetection) { err = createLandmarkDetectionFeature(modelPath, _batchSize, stream, mode); if (err != Err::errNone) { printf("ERROR: An error has occurred while initializing Landmark Detection\n"); } } else if (appMode == faceMeshGeneration) { err = createFaceFittingFeature(modelPath, stream); if (err != Err::errNone) { printf("ERROR: An error has occurred while initializing Face Fitting\n"); } } return err; } FaceEngine::Err FaceEngine::createFaceDetectionFeature(const char* modelPath, CUstream str) { FaceEngine::Err err = FaceEngine::Err::errNone; NvCV_Status nvErr; nvErr = NvAR_Create(NvAR_Feature_FaceDetection, &faceDetectHandle); BAIL_IF_NVERR(nvErr, err, FaceEngine::Err::errEffect); if (bUseOTAU && (!modelPath || !modelPath[0])) { nvErr = NvAR_SetString(faceDetectHandle, NvAR_Parameter_Config(ModelDir), this->fdOTAModelPath); BAIL_IF_NVERR(nvErr, err, FaceEngine::Err::errParameter); } else { nvErr = NvAR_SetString(faceDetectHandle, NvAR_Parameter_Config(ModelDir), modelPath); BAIL_IF_NVERR(nvErr, err, FaceEngine::Err::errParameter); } nvErr = NvAR_SetCudaStream(faceDetectHandle, NvAR_Parameter_Config(CUDAStream), str); BAIL_IF_NVERR(nvErr, err, FaceEngine::Err::errParameter); nvErr = NvAR_SetU32(faceDetectHandle, NvAR_Parameter_Config(Temporal), (bStabilizeFace ? -1 : 0)); BAIL_IF_NVERR(nvErr, err, FaceEngine::Err::errParameter); nvErr = NvAR_Load(faceDetectHandle); BAIL_IF_NVERR(nvErr, err, FaceEngine::Err::errInitialization); bail: return err; } FaceEngine::Err FaceEngine::createLandmarkDetectionFeature(const char* modelPath, unsigned int _batchSize, CUstream str, unsigned int mode) { FaceEngine::Err err = FaceEngine::Err::errNone; NvCV_Status nvErr; batchSize = _batchSize; nvErr = NvAR_Create(NvAR_Feature_LandmarkDetection, &landmarkDetectHandle); BAIL_IF_NVERR(nvErr, err, FaceEngine::Err::errEffect); if (bUseOTAU && (!modelPath || !modelPath[0])) { nvErr = NvAR_SetString(landmarkDetectHandle, NvAR_Parameter_Config(ModelDir), this->ldOTAModelPath); BAIL_IF_NVERR(nvErr, err, FaceEngine::Err::errParameter); } else { nvErr = NvAR_SetString(landmarkDetectHandle, NvAR_Parameter_Config(ModelDir), modelPath); BAIL_IF_NVERR(nvErr, err, FaceEngine::Err::errParameter); } nvErr = NvAR_SetCudaStream(landmarkDetectHandle, NvAR_Parameter_Config(CUDAStream), str); BAIL_IF_NVERR(nvErr, err, FaceEngine::Err::errParameter); nvErr = NvAR_SetU32(landmarkDetectHandle, NvAR_Parameter_Config(BatchSize), batchSize); BAIL_IF_NVERR(nvErr, err, FaceEngine::Err::errParameter); nvErr = NvAR_SetU32(landmarkDetectHandle, NvAR_Parameter_Config(Temporal), (bStabilizeFace ? -1 : 0)); BAIL_IF_NVERR(nvErr, err, FaceEngine::Err::errParameter); nvErr = NvAR_SetU32(landmarkDetectHandle, NvAR_Parameter_Config(Landmarks_Size), numLandmarks); BAIL_IF_NVERR(nvErr, err, FaceEngine::Err::errParameter); nvErr = NvAR_SetU32(landmarkDetectHandle, NvAR_Parameter_Config(LandmarksConfidence_Size), numLandmarks); BAIL_IF_NVERR(nvErr, err, FaceEngine::Err::errParameter); nvErr = NvAR_SetU32(landmarkDetectHandle, NvAR_Parameter_Config(Mode), mode); BAIL_IF_NVERR(nvErr, err, FaceEngine::Err::errParameter); nvErr = NvAR_Load(landmarkDetectHandle); BAIL_IF_NVERR(nvErr, err, FaceEngine::Err::errInitialization); bail: return err; } FaceEngine::Err FaceEngine::createFaceFittingFeature(const char* modelPath, CUstream str) { FaceEngine::Err err = FaceEngine::Err::errNone; NvCV_Status nvErr; nvErr = NvAR_Create(NvAR_Feature_Face3DReconstruction, &faceFitHandle); BAIL_IF_NVERR(nvErr, err, FaceEngine::Err::errEffect); nvErr = NvAR_SetString(faceFitHandle, NvAR_Parameter_Config(ModelDir), modelPath); BAIL_IF_NVERR(nvErr, err, FaceEngine::Err::errParameter); nvErr = NvAR_SetCudaStream(faceFitHandle, NvAR_Parameter_Config(CUDAStream), str); BAIL_IF_NVERR(nvErr, err, FaceEngine::Err::errParameter); nvErr = NvAR_SetU32(faceFitHandle, NvAR_Parameter_Config(Landmarks_Size), numLandmarks); // TODO: Check if nonzero?? BAIL_IF_NVERR(nvErr, err, FaceEngine::Err::errParameter); if (!face_model.empty()) { nvErr = NvAR_SetString(faceFitHandle, NvAR_Parameter_Config(ModelName), face_model.c_str()); BAIL_IF_NVERR(nvErr, err, FaceEngine::Err::errParameter); } nvErr = NvAR_Load(faceFitHandle); BAIL_IF_NVERR(nvErr, err, FaceEngine::Err::errInitialization); bail: return err; } FaceEngine::Err FaceEngine::initFeatureIOParams() { FaceEngine::Err err = FaceEngine::Err::errNone; NvCV_Status cvErr = NvCVImage_Alloc(&inputImageBuffer, input_image_width, input_image_height, NVCV_BGR, NVCV_U8, NVCV_CHUNKY, NVCV_GPU, 1); BAIL_IF_NVERR(cvErr, err, FaceEngine::Err::errInitialization); if (appMode == faceDetection) { err = initFaceDetectionIOParams(&inputImageBuffer); if (err != Err::errNone) { printf("ERROR: An error has occured while setting input, output parmeters for Face Detection\n"); } } else if (appMode == landmarkDetection) { err = initLandmarkDetectionIOParams(&inputImageBuffer); if (err != Err::errNone) { printf("ERROR: An error has occured while setting input, output parmeters for Landmark Detection\n"); } } else if (appMode == faceMeshGeneration) { err = initFaceFittingIOParams(&inputImageBuffer); if (err != Err::errNone) { printf("ERROR: An error has occured while setting input, output parmeters for Face Fitting\n"); } } return err; bail: return err; } FaceEngine::Err FaceEngine::initFaceDetectionIOParams(NvCVImage* inBuf) { NvCV_Status nvErr = NVCV_SUCCESS; FaceEngine::Err err = FaceEngine::Err::errNone; nvErr = NvAR_SetObject(faceDetectHandle, NvAR_Parameter_Input(Image), inBuf, sizeof(NvCVImage)); BAIL_IF_NVERR(nvErr, err, FaceEngine::Err::errParameter); output_bbox_data.assign(25, {0.f, 0.f, 0.f, 0.f}); output_bbox_conf_data.assign(25, 0.f); output_bboxes.boxes = output_bbox_data.data(); output_bboxes.max_boxes = (uint8_t)output_bbox_data.size(); output_bboxes.num_boxes = 0; nvErr = NvAR_SetObject(faceDetectHandle, NvAR_Parameter_Output(BoundingBoxes), &output_bboxes, sizeof(NvAR_BBoxes)); BAIL_IF_NVERR(nvErr, err, FaceEngine::Err::errParameter); nvErr = NvAR_SetF32Array(faceDetectHandle, NvAR_Parameter_Output(BoundingBoxesConfidence), output_bbox_conf_data.data(), output_bboxes.max_boxes); BAIL_IF_NVERR(nvErr, err, FaceEngine::Err::errParameter); bail: return err; } FaceEngine::Err FaceEngine::initLandmarkDetectionIOParams(NvCVImage* inBuf) { NvCV_Status nvErr = NVCV_SUCCESS; FaceEngine::Err err = FaceEngine::Err::errNone; uint output_bbox_size; nvErr = NvAR_SetObject(landmarkDetectHandle, NvAR_Parameter_Input(Image), inBuf, sizeof(NvCVImage)); BAIL_IF_NVERR(nvErr, err, FaceEngine::Err::errParameter); unsigned int OUTPUT_SIZE_KPTS, OUTPUT_SIZE_KPTS_CONF; nvErr = NvAR_GetU32(landmarkDetectHandle, NvAR_Parameter_Config(Landmarks_Size), &OUTPUT_SIZE_KPTS); BAIL_IF_NVERR(nvErr, err, FaceEngine::Err::errParameter); nvErr = NvAR_GetU32(landmarkDetectHandle, NvAR_Parameter_Config(LandmarksConfidence_Size), &OUTPUT_SIZE_KPTS_CONF); BAIL_IF_NVERR(nvErr, err, FaceEngine::Err::errParameter); facial_landmarks.assign(batchSize * OUTPUT_SIZE_KPTS, {0.f, 0.f}); facial_pose.assign(batchSize, {0.f, 0.f, 0.f, 0.f}); facial_landmarks_confidence.assign(batchSize * OUTPUT_SIZE_KPTS_CONF, 0.f); nvErr = NvAR_SetObject(landmarkDetectHandle, NvAR_Parameter_Output(Landmarks), facial_landmarks.data(), sizeof(NvAR_Point2f)); BAIL_IF_NVERR(nvErr, err, FaceEngine::Err::errParameter); nvErr = NvAR_SetObject(landmarkDetectHandle, NvAR_Parameter_Output(Pose), facial_pose.data(), sizeof(NvAR_Quaternion)); BAIL_IF_NVERR(nvErr, err, FaceEngine::Err::errParameter); nvErr = NvAR_SetF32Array(landmarkDetectHandle, NvAR_Parameter_Output(LandmarksConfidence), facial_landmarks_confidence.data(), batchSize * OUTPUT_SIZE_KPTS); BAIL_IF_NVERR(nvErr, err, FaceEngine::Err::errParameter); output_bbox_size = batchSize; if (!bStabilizeFace) output_bbox_size = 25; output_bbox_data.assign(output_bbox_size, {0.f, 0.f, 0.f, 0.f}); output_bboxes.boxes = output_bbox_data.data(); output_bboxes.max_boxes = (uint8_t)output_bbox_size; output_bboxes.num_boxes = (uint8_t)output_bbox_size; nvErr = NvAR_SetObject(landmarkDetectHandle, NvAR_Parameter_Output(BoundingBoxes), &output_bboxes, sizeof(NvAR_BBoxes)); BAIL_IF_NVERR(nvErr, err, FaceEngine::Err::errParameter); bail: return err; } FaceEngine::Err FaceEngine::initFaceFittingIOParams(NvCVImage* inBuf) { NvCV_Status nvErr = NVCV_SUCCESS; FaceEngine::Err err = FaceEngine::Err::errNone; face_mesh = new NvAR_FaceMesh(); unsigned int num_vertices, num_triangles; nvErr = NvAR_GetU32(faceFitHandle, NvAR_Parameter_Config(VertexCount), &num_vertices); BAIL_IF_NVERR(nvErr, err, FaceEngine::Err::errParameter); nvErr = NvAR_GetU32(faceFitHandle, NvAR_Parameter_Config(TriangleCount), &num_triangles); BAIL_IF_NVERR(nvErr, err, FaceEngine::Err::errParameter); face_mesh->num_vertices = num_vertices; face_mesh->num_triangles = num_triangles; m_vertices.assign(num_vertices, {0.f, 0.f, 0.f}); m_triangles.assign(num_triangles, { 0, 0, 0 }); face_mesh->vertices = m_vertices.data(); face_mesh->tvi = m_triangles.data(); rendering_params = new NvAR_RenderingParams(); nvErr = NvAR_SetObject(faceFitHandle, NvAR_Parameter_Input(Image), inBuf, sizeof(NvCVImage)); BAIL_IF_NVERR(nvErr, err, FaceEngine::Err::errParameter); nvErr = NvAR_SetS32(faceFitHandle, NvAR_Parameter_Input(Width), input_image_width); BAIL_IF_NVERR(nvErr, err, FaceEngine::Err::errParameter); nvErr = NvAR_SetS32(faceFitHandle, NvAR_Parameter_Input(Height), input_image_height); BAIL_IF_NVERR(nvErr, err, FaceEngine::Err::errParameter); unsigned int OUTPUT_SIZE_KPTS; nvErr = NvAR_GetU32(faceFitHandle, NvAR_Parameter_Config(Landmarks_Size), &OUTPUT_SIZE_KPTS); BAIL_IF_NVERR(nvErr, err, FaceEngine::Err::errParameter); facial_landmarks.assign(batchSize * OUTPUT_SIZE_KPTS, {0.f, 0.f}); nvErr = NvAR_SetObject(faceFitHandle, NvAR_Parameter_Output(Landmarks), facial_landmarks.data(), sizeof(NvAR_Point2f)); BAIL_IF_NVERR(nvErr, err, FaceEngine::Err::errParameter); facial_landmarks_confidence.assign(batchSize * OUTPUT_SIZE_KPTS, 0.f); nvErr = NvAR_SetF32Array(faceFitHandle, NvAR_Parameter_Output(LandmarksConfidence), facial_landmarks_confidence.data(), batchSize * OUTPUT_SIZE_KPTS); BAIL_IF_NVERR(nvErr, err, FaceEngine::Err::errParameter); facial_pose.assign(batchSize, {0.f, 0.f, 0.f, 0.f}); nvErr = NvAR_SetObject(faceFitHandle, NvAR_Parameter_Output(Pose), facial_pose.data(), sizeof(NvAR_Quaternion)); BAIL_IF_NVERR(nvErr, err, FaceEngine::Err::errParameter); output_bbox_data.assign(batchSize, {0.f, 0.f, 0.f, 0.f}); output_bboxes.boxes = output_bbox_data.data(); output_bboxes.max_boxes = (uint8_t)batchSize; output_bboxes.num_boxes = (uint8_t)batchSize; nvErr = NvAR_SetObject(faceFitHandle, NvAR_Parameter_Output(BoundingBoxes), &output_bboxes, sizeof(NvAR_BBoxes)); nvErr = NvAR_SetObject(faceFitHandle, NvAR_Parameter_Output(FaceMesh), face_mesh, sizeof(NvAR_FaceMesh)); BAIL_IF_NVERR(nvErr, err, FaceEngine::Err::errParameter); nvErr = NvAR_SetObject(faceFitHandle, NvAR_Parameter_Output(RenderingParams), rendering_params, sizeof(NvAR_RenderingParams)); BAIL_IF_NVERR(nvErr, err, FaceEngine::Err::errParameter); unsigned n; nvErr = NvAR_GetU32(faceFitHandle, NvAR_Parameter_Config(ShapeEigenValueCount), &n); BAIL_IF_NVERR(nvErr, err, FaceEngine::Err::errParameter); shapeEigenvalues.resize(n); nvErr = NvAR_SetF32Array(faceFitHandle, NvAR_Parameter_Output(ShapeEigenValues), shapeEigenvalues.data(), n); BAIL_IF_NVERR(nvErr, err, FaceEngine::Err::errParameter); nvErr = NvAR_GetU32(faceFitHandle, NvAR_Parameter_Config(ExpressionCount), &n); BAIL_IF_NVERR(nvErr, err, FaceEngine::Err::errParameter); expressionCoefficients.resize(n); nvErr = NvAR_SetF32Array(faceFitHandle, NvAR_Parameter_Output(ExpressionCoefficients), expressionCoefficients.data(), n); BAIL_IF_NVERR(nvErr, err, FaceEngine::Err::errParameter); bail: return err; } void FaceEngine::destroyFeatures() { if (stream) { NvAR_CudaStreamDestroy(stream); stream = 0; } releaseFeatureIOParams(); destroyFaceDetectionFeature(); destroyLandmarkDetectionFeature(); destroyFaceFittingFeature(); } void FaceEngine::destroyFaceDetectionFeature() { if (faceDetectHandle) { (void)NvAR_Destroy(faceDetectHandle); faceDetectHandle = nullptr; } } void FaceEngine::destroyLandmarkDetectionFeature() { if (landmarkDetectHandle) { (void)NvAR_Destroy(landmarkDetectHandle); landmarkDetectHandle = nullptr; } } void FaceEngine::destroyFaceFittingFeature() { if (faceFitHandle) { (void)NvAR_Destroy(faceFitHandle); faceFitHandle = nullptr; } } void FaceEngine::releaseFeatureIOParams() { releaseFaceDetectionIOParams(); releaseLandmarkDetectionIOParams(); releaseFaceFittingIOParams(); } void FaceEngine::releaseFaceDetectionIOParams() { NvCVImage_Dealloc(&inputImageBuffer); if (!output_bbox_data.empty()) output_bbox_data.clear(); if (!output_bbox_conf_data.empty()) output_bbox_conf_data.clear(); } void FaceEngine::releaseLandmarkDetectionIOParams() { NvCVImage_Dealloc(&inputImageBuffer); if (!output_bbox_data.empty()) output_bbox_data.clear(); if (!facial_landmarks.empty()) facial_landmarks.clear(); if (!facial_pose.empty()) facial_pose.clear(); if (!facial_landmarks_confidence.empty()) facial_landmarks_confidence.clear(); } void FaceEngine::releaseFaceFittingIOParams() { if (!output_bbox_data.empty()) output_bbox_data.clear(); if (!facial_landmarks.empty()) facial_landmarks.clear(); if (!facial_landmarks_confidence.empty()) facial_landmarks_confidence.clear(); if (!facial_pose.empty()) facial_pose.clear(); NvCVImage_Dealloc(&inputImageBuffer); if (rendering_params) { delete rendering_params; rendering_params = nullptr; } if (face_mesh) { delete face_mesh; face_mesh = nullptr; } } NvCV_Status FaceEngine::findFaceBoxes(unsigned &num_boxes) { NvCV_Status nvErr = NvAR_Run(faceDetectHandle); num_boxes = (unsigned)output_bboxes.num_boxes; return nvErr; } NvAR_Rect* FaceEngine::getLargestBox() { NvAR_Rect *box, *bigBox, *lastBox; float maxArea, area; for (lastBox = (box = &output_bboxes.boxes[0]) + output_bboxes.num_boxes, bigBox = nullptr, maxArea = 0; box != lastBox; ++box) { if (maxArea < (area = box->width * box->height)) { maxArea = area; bigBox = box; } } return bigBox; } NvAR_BBoxes* FaceEngine::getBoundingBoxes() { return &output_bboxes; } void FaceEngine::enlargeAndSquarifyImageBox(float enlarge, NvAR_Rect& box, int FLAG_variant) { NvAR_Vector2f size = {box.width * .5f, box.height * .5f}; NvAR_Point2f center = {box.x + size.x, box.y + size.y}; float t; size.x *= (1.f + enlarge); size.y *= (1.f + enlarge); if (!(FLAG_variant & 1)) /* Default: enforce square bounding box */ { if (size.x < size.y) /* Make square */ size.x = size.y; else size.y = size.x; } if (center.x < size.x) /* Shift box into image left-right */ center.x = size.x; else if (center.x > (t = input_image_width - size.x)) center.x = t; if (center.y < size.y) /* Shift box into image up-down */ center.y = size.y; else if (center.y > (t = input_image_height - size.y)) center.y = t; // TODO: Above we assume that the box is smaller than the image. box.width = roundf(size.x * 2.f); /* Integral box */ box.height = roundf(size.y * 2.f); box.x = roundf(center.x - box.width * .5f); box.y = roundf(center.y - box.height * .5f); } /** * Perturb a bounding box. * @param[in,out] ran the random number generator. * @param[in] minMag inset magnitude of noise. * @param[in] maxMag outset magnitude of noise. * @param[in] cleanBox the clean input box. * @param[out] noisyBox the perturbed output box. */ // TOD0: Fix the 8 bounding boxes instead if random jiggling void FaceEngine::jiggleBox(std::mt19937& ran, float minMag, float maxMag, const NvAR_Rect& cleanBox, NvAR_Rect& jitteredBox) { if (0.f == minMag && 0.f == maxMag) { jitteredBox = cleanBox; return; } std::uniform_real_distribution uRand(minMag, maxMag); #if 1 // jitter both sides jitteredBox.x = cleanBox.x - uRand(ran); jitteredBox.y = cleanBox.y - uRand(ran); jitteredBox.width = cleanBox.width + cleanBox.x + uRand(ran) - jitteredBox.x; jitteredBox.height = cleanBox.height + cleanBox.y + uRand(ran) - jitteredBox.y; #elif 0 // jitter only the right side noisyBox.x = cleanBox.x; noisyBox.y = cleanBox.y; noisyBox.width = cleanBox.width + cleanBox.x + uRand(ran) - noisyBox.x; noisyBox.height = cleanBox.height + cleanBox.y + uRand(ran) - noisyBox.y; #elif 1 // jitter only the location noisyBox.x = cleanBox.x - uRand(ran); noisyBox.y = cleanBox.y - uRand(ran); noisyBox.width = cleanBox.width; noisyBox.height = cleanBox.height; #endif // None of these makes a significant difference } #ifdef UNUSED /** Intersect a rectangle with an image. * @param[in] srcRect the source rectangle. * @param[in] src the source image. * @param[out] clipRect the source rectangle clipped to the source image. * @return true if the rectangle was clipped, * false if the rectangle did not need to be clipped. */ static bool IntersectRectWithImage(const cv::Rect& srcRect, const cv::Mat& src, cv::Rect& clipRect) { bool result = 0; // unclipped cv::Point2i rect[2] = {{srcRect.x, srcRect.y}, {srcRect.x + srcRect.width, srcRect.y + srcRect.height}}; if (rect[0].x < 0) { result = 1; rect[0].x = 0; } // clipped if (rect[0].y < 0) { result = 1; rect[0].y = 0; } // clipped if (rect[1].x > src.cols) { result = 1; rect[1].x = src.cols; } // clipped if (rect[1].y > src.rows) { result = 1; rect[1].y = src.rows; } // clipped clipRect.x = rect[0].x; clipRect.y = rect[0].y; clipRect.width = rect[1].x - rect[0].x; clipRect.height = rect[1].y - rect[0].y; return result; } #endif // UNUSED void FaceEngine::DrawPose(const cv::Mat& src, const NvAR_Quaternion* pose) const { const float vector_scale = 50.0f; const int thickness = 2; float rot_mat[3][3]; set_rotation_from_quaternion(pose, rot_mat[0]); const auto avg_landmark_pos = GetAverageLandmarkPositionInGlSpace(); cv::Point p0 = {static_cast(avg_landmark_pos[0]), static_cast(avg_landmark_pos[1])}; cv::Point px = p0 + cv::Point(std::lround(rot_mat[0][0] * vector_scale), std::lround(rot_mat[0][1] * vector_scale)); cv::Point py = p0 + cv::Point(std::lround(rot_mat[1][0] * vector_scale), std::lround(rot_mat[1][1] * vector_scale)); cv::Point pz = p0 + cv::Point(std::lround(rot_mat[2][0] * vector_scale), std::lround(rot_mat[2][1] * vector_scale)); // Convert from OpenGL to OpenCV space p0.y = src.rows - 1 - p0.y; px.y = src.rows - 1 - px.y; py.y = src.rows - 1 - py.y; pz.y = src.rows - 1 - pz.y; cv::line(src, p0, px, CV_RGB(255, 0, 0), thickness); cv::line(src, p0, py, CV_RGB(0, 255, 0), thickness); cv::line(src, p0, pz, CV_RGB(0, 0, 255), thickness); } FaceEngine::Err FaceEngine::findLandmarks() { NvCV_Status nvErr; nvErr = NvAR_Run(landmarkDetectHandle); if (NVCV_SUCCESS != nvErr) { return FaceEngine::Err::errRun; } float avg_confidence = getAverageLandmarksConfidence(); if (avg_confidence < confidenceThreshold) { return FaceEngine::Err::errNoFaceDetected; } else { average_poses(getPose(), batchSize); NvAR_Point2f *pt, *endPt; int i = 0; for (endPt = (pt = getLandmarks()) + numLandmarks; pt != endPt; ++pt, i += 2) { for (int j = 1; j < batchSize; j++) { pt->x += pt[j * numLandmarks].x; pt->y += pt[j * numLandmarks].y; } // average batch of inferences to generate final result landmark points pt->x /= batchSize; pt->y /= batchSize; } } return FaceEngine::Err::errNone; } NvAR_Point2f* FaceEngine::getLandmarks() { return facial_landmarks.data(); } float* FaceEngine::getLandmarksConfidence() { return facial_landmarks_confidence.data(); } std::array FaceEngine::GetAverageLandmarkPositionInGlSpace() const { std::array res = { 0.0f, 0.0f }; for (const auto& landmark : facial_landmarks) { res[0] += landmark.x; res[1] += landmark.y; } res[0] /= facial_landmarks.size(); res[1] /= facial_landmarks.size(); res[1] = input_image_height - res[1]; //Convert y coordinate from CV to GL space return res; } float FaceEngine::getAverageLandmarksConfidence() { float average_confidence = 0.0f; float* keypoints_landmarks_confidence = getLandmarksConfidence(); for (int i = 0; i < batchSize; i++) { for (int j = 0; j < numLandmarks; j++) { average_confidence += keypoints_landmarks_confidence[i * numLandmarks + j]; } } average_confidence /= batchSize * numLandmarks; return average_confidence; } NvAR_Quaternion* FaceEngine::getPose() { return facial_pose.data(); } FaceEngine::Err FaceEngine::findLargestFaceBox(NvAR_Rect& faceBox, int variant) { NvAR_Rect* pFaceBox; unsigned num_boxes; NvCV_Status nv_err = findFaceBoxes(num_boxes); if (NVCV_SUCCESS != nv_err) { return FaceEngine::Err::errRun; } if (num_boxes >= 1) { pFaceBox = getLargestBox(); if (nullptr == pFaceBox) { faceBox.x = faceBox.y = faceBox.width = faceBox.height = 0.0f; } else { faceBox = *pFaceBox; } enlargeAndSquarifyImageBox(.2f, faceBox, variant); } else return FaceEngine::Err::errNoFaceDetected; return FaceEngine::Err::errNone; } FaceEngine::Err FaceEngine::acquireFaceBox(cv::Mat& src, NvAR_Rect& faceBox, int variant) { NvCVImage fxSrcChunkyCPU; (void)NVWrapperForCVMat(&src, &fxSrcChunkyCPU); NvCV_Status cvErr = NvCVImage_Transfer(&fxSrcChunkyCPU, &inputImageBuffer, 1.0f, stream, &tmpImage); if (NVCV_SUCCESS != cvErr) { return FaceEngine::Err::errRun; } FaceEngine::Err nv_err = findLargestFaceBox(faceBox, variant); return nv_err; } FaceEngine::Err FaceEngine::acquireFaceBoxAndLandmarks(cv::Mat& src, NvAR_Point2f* refMarks, NvAR_Rect& faceBox, int /*variant*/) { NvCVImage fxSrcChunkyCPU; (void)NVWrapperForCVMat(&src, &fxSrcChunkyCPU); NvCV_Status cvErr = NvCVImage_Transfer(&fxSrcChunkyCPU, &inputImageBuffer, 1.0f, stream, &tmpImage); if (NVCV_SUCCESS != cvErr) { return FaceEngine::Err::errRun; } FaceEngine::Err nv_err = findLandmarks(); if(nv_err == FaceEngine::Err::errNone){ faceBox = output_bboxes.boxes[0]; memcpy(refMarks, getLandmarks(), sizeof(NvAR_Point2f) * numLandmarks); } return nv_err; } void FaceEngine::setFaceStabilization(bool _bStabilizeFace) { bStabilizeFace = _bStabilizeFace; } FaceEngine::Err FaceEngine::setNumLandmarks(int n) { FaceEngine::Err err = errNone; #ifdef _WIN32 // Keep Updated Thresholds for Windows Only for (auto const& info : LANDMARKS_INFO) #else for (auto const& info : LANDMARKS_INFO_UNIX) #endif { if (n == info.numPoints) { numLandmarks = info.numPoints; confidenceThreshold = info.confidence_threshold; return err; } } err = errGeneral; return err; } void FaceEngine::setAppMode(FaceEngine::mode _mode) { appMode = _mode; }