/*############################################################################### # # Copyright 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 #include #include #include #include #include #include #include "nvAR.h" #include "nvAR_defs.h" #include "nvCVOpenCV.h" #include "opencv2/opencv.hpp" #include "MeshRenderer.h" #ifdef _WIN32 #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif // WIN32_LEAN_AND_MEAN #include #define strcasecmp _stricmp #else #include #endif // _WIN32 #ifdef _ENABLE_UI #include "ExpressionAppUI.h" #endif // _ENABLE_UI #if CV_MAJOR_VERSION >= 4 #define CV_CAP_PROP_FPS cv::CAP_PROP_FPS #define CV_CAP_PROP_FRAME_COUNT cv::CAP_PROP_FRAME_COUNT #define CV_CAP_PROP_FRAME_HEIGHT cv::CAP_PROP_FRAME_HEIGHT #define CV_CAP_PROP_FRAME_WIDTH cv::CAP_PROP_FRAME_WIDTH #define CV_CAP_PROP_POS_FRAMES cv::CAP_PROP_POS_FRAMES #define CV_INTER_AREA cv::INTER_AREA #define CV_INTER_LINEAR cv::INTER_LINEAR #endif // CV_MAJOR_VERSION #ifndef M_PI #define M_PI 3.1415926535897932385 #endif /* M_PI */ #ifndef M_2PI #define M_2PI 6.2831853071795864769 #endif /* M_2PI */ #ifndef M_PI_2 #define M_PI_2 1.5707963267948966192 #endif /* M_PI_2 */ #define D_RADIANS_PER_DEGREE (M_PI / 180.) #define F_PI ((float)M_PI) #define F_PI_2 ((float)M_PI_2) #define F_2PI ((float)M_2PI) #define F_RADIANS_PER_DEGREE (float)(M_PI / 180.) #define CTL(x) ((x) & 0x1F) #define HELP_REQUESTED 411 #define BAIL_IF_ERR(err) do { if ((int)(err) != 0) { goto bail; } } while(0) #define BAIL_IF_NULL(x, err, code) do { if ((void*)(x) == NULL) { err = code; goto bail; } } while(0) #define BAIL_IF_CUERR(cu, err) do { if (cudaSuccess != (cu)) { err = NvFromCuErr(cu); } } while(0) #define BAIL(err, code) do { err = code; goto bail; } while(0) #define DEFAULT_CODEC "avc1" #define DEFAULT_FACE_MODEL "face_model2.nvf" #define DEFAULT_RENDER_MODEL "face_model2.nvf" #define NUM_CAMERA_INTRINSIC_PARAMS 3 /******************************************************************************** * Command-line arguments ********************************************************************************/ bool FLAG_debug = false, FLAG_loop = false, FLAG_show = false, FLAG_showUI = false, FLAG_verbose = false; std::string FLAG_camRes, FLAG_codec = DEFAULT_CODEC, FLAG_fitModel = DEFAULT_FACE_MODEL, FLAG_inFile, FLAG_modelDir, FLAG_outDir, FLAG_outFile, FLAG_renderModel = DEFAULT_RENDER_MODEL; int FLAG_filter = NVAR_TEMPORAL_FILTER_FACE_BOX | NVAR_TEMPORAL_FILTER_FACIAL_LANDMARKS | NVAR_TEMPORAL_FILTER_FACE_ROTATIONAL_POSE | NVAR_TEMPORAL_FILTER_FACIAL_EXPRESSIONS | NVAR_TEMPORAL_FILTER_FACIAL_GAZE, //| NVAR_TEMPORAL_FILTER_ENHANCE_EXPRESSIONS, FLAG_viewMode = 0xF, // VIEW_MESH | VIEW_IMAGE | VIEW_PLOT | VIEW_LM FLAG_exprMode = 2, // 1=mesh, 2=MLP FLAG_poseMode = 0, FLAG_cheekPuff = 0, FLAG_gaze = 0; double FLAG_fov = 0.0; // Orthographic by default /******************************************************************************** * Usage ********************************************************************************/ static void Usage() { printf( "ExpressionApp [ ...]\n" "where is\n" " --cam_res=[WWWx]HHH specify resolution as height or width x height\n" " --codec= FOURCC code for the desired codec (default avc1)\n" " --debug[=(true|false)] report debugging info (default false)\n" " --expr_mode= SDK feature used for generation expressions: 1=Face3DReconstruction, 2=FaceExpressions (default 2)\n" " --pose_mode= Pose mode used for the FaceExpressions feature only: 0:3DOF, 1:6DOF (default 0)\n" " --cheekpuff[=(true|false)] (Experimental) Enable cheek puff blendshapes (default=false)\n" " --face_model= specify the face model to be used for fitting (default " DEFAULT_FACE_MODEL ")\n" " --filter= 1: face box, 2: landmarks, 4: pose, 16: expressions, 32: gaze,\n" " 256: eye and mouth closure\n" " (default 55: face box, landmarks, pose, expressions, gaze; no closure)\n" " --gaze= specify gaze estimation mode 0=implicit, 1=explicit (default 0)\n" " --fov= field of view, in degrees; 0 implies orthographic (default 0)\n" " --help print this message\n" " --in= specify the input file (default webcam 0)\n" " --loop[=(true|false)] play the same video repeatedly\n" " --model_dir= specify the directory containing the TRT models\n" " --model_path= specify the directory containing the TRT models\n" " --out= specify the output file\n" " --render_model= specify the face model to be used for rendering (default " DEFAULT_RENDER_MODEL ")\n" " --show[=(true|false)] show the results (default false, unless --out is empty)\n" " --show_ui[=(true|false)] show the expression calibration UI (default false)\n" " --temporal= apply temporal filter: see --filter\n" " --view_mode= 1: mesh, 2: image, 4: plot, 8: landmarks (default 15: all)\n" " --verbose[=(true|false)] report interesting info (default off)\n" "Keyboard commands:\n" " escape - quit\n" " q or Q - quit\n" " m - toggle mesh display\n" " n - calibrate expression weights\n" " i - toggle image display\n" " p - toggle plot display\n" " l - toggle landmark display\n" " f - toggle frame rate display\n" " L or ctrl-L - toggle landmark filtering\n" " N or ctrl-N - un-calibrate expression weights\n" " P or ctrl-P - toggle pose filtering\n" " E or ctrl-E - toggle expression filtering\n" " G or ctrl-G - toggle gaze filtering\n" " C or ctrl-C - toggle closure enhancement\n" " 1 - expressions from mesh fitting\n" " 2 - expressions from DNN\n" ); } static bool GetFlagArgVal(const char *flag, const char *arg, const char **val) { if (*arg != '-') return false; while (*++arg == '-') continue; const char *s = strchr(arg, '='); if (s == nullptr) { if (strcmp(flag, arg)) return false; *val = nullptr; return true; } unsigned n = (unsigned)(s - arg); if ((strlen(flag) != n) || (strncmp(flag, arg, n) != 0)) return false; *val = s + 1; return true; } static bool GetFlagArgVal(const char *flag, const char *arg, std::string *val) { const char *valStr; if (!GetFlagArgVal(flag, arg, &valStr)) return false; val->assign(valStr ? valStr : ""); return true; } static bool GetFlagArgVal(const char *flag, const char *arg, bool *val) { const char *valStr; bool success = GetFlagArgVal(flag, arg, &valStr); if (success) *val = (!valStr || !strcasecmp(valStr, "true") || !strcasecmp(valStr, "on") || !strcasecmp(valStr, "yes") || !strcasecmp(valStr, "1")); return success; } bool GetFlagArgVal(const char *flag, const char *arg, long *val) { const char *valStr; bool success = GetFlagArgVal(flag, arg, &valStr); if (success) *val = strtol(valStr, nullptr, 0); return success; } static bool GetFlagArgVal(const char *flag, const char *arg, int *val) { long longVal; bool success = GetFlagArgVal(flag, arg, &longVal); if (success) *val = (int)longVal; return success; } bool GetFlagArgVal(const char* flag, const char* arg, double* val) { const char* valStr; bool success = GetFlagArgVal(flag, arg, &valStr); if (success) *val = valStr ? strtod(valStr, nullptr) : 1.0; return success; } /******************************************************************************** * StringToFourcc ********************************************************************************/ static int StringToFourcc(const std::string &str) { union chint { int i; char c[4]; }; chint x = {0}; for (int n = (str.size() < 4) ? (int)str.size() : 4; n--;) x.c[n] = str[n]; return x.i; } /******************************************************************************** * ParseMyArgs ********************************************************************************/ static int ParseMyArgs(int argc, char **argv) { // query NVAR_MODEL_DIR environment variable first before checking the command line arguments const char* modelPath = getenv("NVAR_MODEL_DIR"); if (modelPath) { FLAG_modelDir = modelPath; } int errs = 0; for (--argc, ++argv; argc--; ++argv) { bool help; const char *arg = *argv; if (arg[0] != '-') { continue; } else if ((arg[1] == '-') && ( GetFlagArgVal("cam_res", arg, &FLAG_camRes) || GetFlagArgVal("codec", arg, &FLAG_codec) || GetFlagArgVal("debug", arg, &FLAG_debug) || GetFlagArgVal("expr_mode", arg, &FLAG_exprMode) || GetFlagArgVal("pose_mode", arg, &FLAG_poseMode) || GetFlagArgVal("cheekpuff", arg, &FLAG_cheekPuff) || GetFlagArgVal("face_model", arg, &FLAG_fitModel) || GetFlagArgVal("filter", arg, &FLAG_filter) || GetFlagArgVal("gaze", arg, &FLAG_gaze) || GetFlagArgVal("fov", arg, &FLAG_fov) || GetFlagArgVal("in", arg, &FLAG_inFile) || GetFlagArgVal("in_file", arg, &FLAG_inFile) || GetFlagArgVal("loop", arg, &FLAG_loop) || GetFlagArgVal("model_dir", arg, &FLAG_modelDir) || GetFlagArgVal("model_path", arg, &FLAG_modelDir) || GetFlagArgVal("out", arg, &FLAG_outFile) || GetFlagArgVal("out_file", arg, &FLAG_outFile) || GetFlagArgVal("render_model", arg, &FLAG_renderModel) || GetFlagArgVal("show", arg, &FLAG_show) || GetFlagArgVal("show_ui", arg, &FLAG_showUI) || GetFlagArgVal("temporal", arg, &FLAG_filter) || GetFlagArgVal("verbose", arg, &FLAG_verbose) || GetFlagArgVal("view_mode", arg, &FLAG_viewMode) )) { continue; } else if (GetFlagArgVal("help", arg, &help)) { Usage(); errs = HELP_REQUESTED; } else if (arg[1] != '-') { for (++arg; *arg; ++arg) { if (*arg == 'v') { FLAG_verbose = true; } else { // printf("Unknown flag: \"-%c\"\n", *arg); } } continue; } else { // printf("Unknown flag: \"%s\"\n", arg); } } return errs; } enum { myErrNone = 0, myErrShader = -1, myErrProgram = -2, myErrTexture = -3, }; static bool FileExists(const char* fileName) { #ifdef _MSC_VER DWORD attributes = GetFileAttributesA(fileName); return attributes != INVALID_FILE_ATTRIBUTES; #else // !MSC_VER struct stat statBuf; return 0 == stat(fileName, &statBuf); #endif // MSC_VER } inline bool FileExists(const std::string& str) { return FileExists(str.c_str()); } static bool SetDirIfFileExists(const std::string& testDir, const std::string& file, std::string& resultDir) { std::string dirFile = (testDir + '/' + file); bool exists = FileExists(dirFile); if (exists) resultDir = testDir; return exists; } static bool SetPathIfFileExists(const std::string& testDir, const std::string& file, std::string& path) { std::string dirFile = testDir.empty() ? file : (testDir + '/' + file); bool exists = FileExists(dirFile); if (exists) path = dirFile; return exists; } class MyTimer { public: MyTimer() { dt = dt.zero(); } /**< Clear the duration to 0. */ void start() { t0 = std::chrono::high_resolution_clock::now(); } /**< Start the timer. */ void pause() { dt = std::chrono::high_resolution_clock::now() - t0; } /**< Pause the timer. */ void resume() { t0 = std::chrono::high_resolution_clock::now() - dt; } /**< Resume the timer. */ void stop() { pause(); } /**< Stop the timer. */ double elapsedTimeFloat() const { return std::chrono::duration(dt).count(); } /**< Report the elapsed time as a float. */ private: std::chrono::high_resolution_clock::time_point t0; std::chrono::high_resolution_clock::duration dt; }; inline NvCV_Status NvFromAppErr(int appErr) { return (NvCV_Status)appErr; } class App { public: App() {} ~App() { stop(); } NvCV_Status run(); NvCV_Status stop(); NvCV_Status setInputVideo(const std::string& file); // open immediately NvCV_Status setInputCamera(int index, const std::string& resStr); // open immediately NvCV_Status setOutputVideo(const std::string& file); NvCV_Status set(int codec, double fps, unsigned width, unsigned height); // deferred open NvCV_Status init(); NvCV_Status resizeDst(); NvCV_Status openOutputVideo(int codec, double fps, unsigned width, unsigned height); NvCV_Status initFaceFit(); NvCV_Status initMLPExpressions(); NvCV_Status calibrateExpressionWeights(); NvCV_Status unCalibrateExpressionWeights(); NvCV_Status normalizeExpressionsWeights(); NvCV_Status updateCamera(); NvCV_Status toggleFaceBoxFiltering(); NvCV_Status toggleLandmarkFiltering(); NvCV_Status togglePoseFiltering(); NvCV_Status toggleExpressionFiltering(); NvCV_Status toggleGazeFiltering(); NvCV_Status toggleClosureEnhancement(); NvCV_Status togglePoseMode(); NvCV_Status overlayLandmarks(const float landmarks[126 * 2], unsigned screenHeight, NvCVImage *im); void getFPS(); void drawFPS(cv::Mat& img); void barPlotExprs(); const char *getErrorStringFromCode(NvCV_Status err); struct Pose { NvAR_Quaternion rotation; NvAR_Vector3f translation; float* data() { return &rotation.x; } const float* data() const { return &rotation.x; } }; CUstream _stream = 0; cv::Mat _ocvSrcImg, _ocvDstImg; // _ocvSrcImg is allocated, _ocvDstImg is just a wrapper cv::VideoCapture _vidIn{}; cv::VideoWriter _vidOut{}; double _frameRate; int _miniX, _miniY, _renderX, _renderY, _plotX, _plotY; NvAR_FaceMesh _arMesh { nullptr, 0, nullptr, 0 }; NvAR_FeatureHandle _featureHan{}; NvAR_RenderingParams _renderParams; NvCVImage _srcImg, _compImg, _srcGpu, _renderImg; // wrapper, alloced, alloced, alloced Pose _pose; float _cameraIntrinsicParams[NUM_CAMERA_INTRINSIC_PARAMS]; std::string _inFile, _outFile; std::vector _outputBboxData; NvAR_BBoxes _outputBboxes; std::vector _expressions, _expressionZeroPoint, _expressionScale, _expressionExponent, _eigenvalues, _landmarkConfidence; std::vector _landmarks; std::vector _vertices; std::vector< NvAR_Vector3u16> _triangles; unsigned _videoWidth, _videoHeight, _miniWidth, _miniHeight, _renderWidth, _renderHeight, _plotWidth, _plotHeight, _compWidth, _compHeight, _eigenCount, _exprCount, _landmarkCount, _viewMode, _exprMode, _filtering; unsigned _poseMode = 0; bool _enableCheekPuff; MeshRendererBroker _broker; MeshRenderer *_renderer = nullptr; static const char _windowTitle[], *_exprAbbr[][4], *_sfmExprAbbr[][4]; MyTimer _timer; bool _showFPS; bool _performCalibration; bool _cameraNeedsUpdate; double _frameTime; float _globalExpressionParam; #ifdef _ENABLE_UI ExpressionAppUI ui_obj_; #endif // _ENABLE_UI enum { EXPR_MODE_MESH = 1, EXPR_MODE_MLP = 2, }; enum { VIEW_MESH = (1 << 0), VIEW_IMAGE = (1 << 1), VIEW_PLOT = (1 << 2), VIEW_LM = (1 << 3) }; enum { APP_ERR_GENERAL = 1, APP_ERR_OPEN, APP_ERR_READ, APP_ERR_WRITE, APP_ERR_INIT, APP_ERR_RUN, APP_ERR_EFFECT, APP_ERR_PARAM, APP_ERR_UNIMPLEMENTED, APP_ERR_MISSING, APP_ERR_VIDEO, APP_ERR_IMAGE_SIZE, APP_ERR_NOT_FOUND, APP_ERR_FACE_MODEL, APP_ERR_GLFW_INIT, APP_ERR_GL_INIT, APP_ERR_RENDER_INIT, APP_ERR_GL_RESOURCE, APP_ERR_GL_GENERAL, APP_ERR_FACE_FIT, APP_ERR_NO_FACE, APP_ERR_CANCEL, APP_ERR_CAMERA, APP_ERR_ARG_PARSE, APP_ERR_EOF }; }; const char App::_windowTitle[] = "Expression App"; const char *App::_exprAbbr[][4] = { { "BROW", "DOWN", "LEFT", NULL }, // 0 browDown_L { "BROW", "DOWN", "RIGHT",NULL }, // 1 browDown_R { "BROW", "INNR", "UP", "LEFT" }, // 2 browInnerUp_L { "BROW", "INNR", "UP", "RIGHT" }, // 3 browInnerUp_R { "BROW", "OUTR", "UP", "LEFT" }, // 4 browOuterUp_L { "BROW", "OUTR", "UP", "RIGHT" }, // 5 browOuterUp_R { "CHEE", "PUFF", "LEFT", NULL }, // 6 cheekPuff_L { "CHEE", "PUFF", "RIGHT",NULL }, // 7 cheekPuff_R { "CHEE", "SQNT", "LEFT", NULL }, // 8 cheekSquint_L { "CHEE", "SQNT", "RIGHT",NULL }, // 9 cheekSquint_R { "EYE", "BLNK", "LEFT", NULL }, // 10 eyeBlink_L { "EYE", "BLNK", "RIGHT",NULL }, // 11 eyeBlink_R { "EYE", "LOOK", "DOWN", "LEFT" }, // 12 eyeLookDown_L { "EYE", "LOOK", "DOWN", "RIGHT" }, // 13 eyeLookDown_R { "EYE", "LOOK", "IN", "LEFT" }, // 14 eyeLookIn_L { "EYE", "LOOK", "IN", "RIGHT" }, // 15 eyeLookIn_R { "EYE", "LOOK", "OUT", "LEFT" }, // 16 eyeLookOut_L { "EYE", "LOOK", "OUT", "RIGHT" }, // 17 eyeLookOut_R { "EYE", "LOOK", "UP", "LEFT" }, // 18 eyeLookUp_L { "EYE", "LOOK", "UP", "RIGHT" }, // 19 eyeLookUp_R { "EYE", "SQNT", "LEFT", NULL }, // 20 eyeSquint_L { "EYE", "SQNT", "RIGHT",NULL }, // 21 eyeSquint_R { "EYE", "WIDE", "LEFT", NULL }, // 22 eyeWide_L { "EYE", "WIDE", "RIGHT",NULL }, // 23 eyeWide_R { "JAW", "FWD", NULL, NULL }, // 24 jawForward { "JAW", "LEFT", NULL, NULL }, // 25 jawLeft { "JAW", "OPEN", NULL, NULL }, // 26 jawOpen { "JAW", "RIGHT",NULL ,NULL }, // 27 jawRight { "MOUT", "CLOS", NULL, NULL }, // 28 mouthClose { "MOUT", "DMPL", "LEFT", NULL }, // 29 mouthDimple_L { "MOUT", "DMPL", "RIGHT",NULL }, // 30 mouthDimple_R { "MOUT", "FRWN", "LEFT", NULL }, // 31 mouthFrown_L { "MOUT", "FRWN", "RIGHT",NULL }, // 32 mouthFrown_R { "MOUT", "FUNL", NULL, NULL }, // 33 mouthFunnel { "MOUT", "LEFT", NULL, NULL }, // 34 mouthLeft { "MOUT", "LOWR", "DOWN", "LEFT" }, // 35 mouthLowerDown_L { "MOUT", "LOWR", "DOWN", "RIGHT" }, // 36 mouthLowerDown_R { "MOUT", "PRES", "LEFT", NULL }, // 37 mouthPress_L { "MOUT", "PRES", "RIGHT",NULL }, // 38 mouthPress_R { "MOUT", "PUKR", NULL, NULL }, // 39 mouthPucker { "MOUT", "RIGHT",NULL, NULL }, // 40 mouthRight { "MOUT", "ROLL", "LOWR", NULL }, // 41 mouthRollLower { "MOUT", "ROLL", "UPPR", NULL }, // 41 mouthRollUpper { "MOUT", "SHRG", "LOWR", NULL }, // 43 mouthShrugLower { "MOUT", "SHRG", "UPPR", NULL }, // 44 mouthShrugUpper { "MOUT", "SMIL", "LEFT", NULL }, // 45 mouthSmile_L { "MOUT", "SMIL", "RIGHT",NULL }, // 46 mouthSmile_R { "MOUT", "STRH", "LEFT", NULL }, // 47 mouthStretch_L { "MOUT", "STRH", "RIGHT",NULL }, // 48 mouthStretch_R { "MOUT", "UPPR", "UP", "LEFT" }, // 49 mouthUpperUp_L { "MOUT", "UPPR", "UP", "RIGHT" }, // 50 mouthUpperUp_R { "NOSE", "SNER", "LEFT", NULL }, // 51 noseSneer_L { "NOSE", "SNER", "RIGHT",NULL }, // 52 noseSneer_R }; const char *App::_sfmExprAbbr[][4] = { { "ANGER", NULL, NULL, NULL }, { "DISGUST", NULL, NULL, NULL }, { "FEAR", NULL, NULL, NULL }, { "HAPPY", NULL, NULL, NULL }, { "SAD", NULL, NULL, NULL }, { "SURPRISE", NULL, NULL, NULL }, }; NvCV_Status App::setInputVideo(const std::string& file) { // opencv2/vidioio.hpp if (!_vidIn.open(file)) return (NvCV_Status)APP_ERR_OPEN; _frameRate = _vidIn.get(CV_CAP_PROP_FPS); _videoWidth = (unsigned)_vidIn.get(CV_CAP_PROP_FRAME_WIDTH); _videoHeight = (unsigned)_vidIn.get(CV_CAP_PROP_FRAME_HEIGHT); if (FLAG_verbose) printf("Video capture resolution set to %dx%d @ %4.1f fps\n", _videoWidth, _videoHeight, _frameRate); _inFile = file; return NVCV_SUCCESS; } NvCV_Status App::setInputCamera(int index, const std::string& resStr) { if (!_vidIn.open(index)) return NvFromAppErr(APP_ERR_OPEN); if (!resStr.empty()) { int n, width, height; n = sscanf(resStr.c_str(), "%d%*[xX]%d", &width, &height); switch (n) { case 2: break; // We have read both width and height case 1: height = width; width = (int)(height * (4. / 3.) + .5); break; default: height = 0; width = 0; break; } if (width) _vidIn.set(CV_CAP_PROP_FRAME_WIDTH, width); if (height) _vidIn.set(CV_CAP_PROP_FRAME_HEIGHT, height); _inFile = "webcam"; _inFile += std::to_string(index); } _videoWidth = (unsigned)_vidIn.get(CV_CAP_PROP_FRAME_WIDTH); _videoHeight = (unsigned)_vidIn.get(CV_CAP_PROP_FRAME_HEIGHT); _frameRate = _vidIn.get(CV_CAP_PROP_FPS); // Rounding the frame rate is required because OpenCV does not support all frame rates when writing video static const int fps_precision = 1000; _frameRate = static_cast((_frameRate + 0.5) * fps_precision) / static_cast(fps_precision); if (FLAG_verbose) printf("Camera capture resolution set to %dx%d @ %4.1f fps\n", _videoWidth, _videoHeight, _frameRate); return NVCV_SUCCESS; } NvCV_Status App::setOutputVideo(const std::string& file) { _outFile = file; return NVCV_SUCCESS; } NvCV_Status App::openOutputVideo(int codec, double fps, unsigned width, unsigned height) { cv::Size sz; sz.width = width; sz.height = height; if (_outFile.empty()) { if (_inFile.size() <= 4) return NvFromAppErr(APP_ERR_OPEN); _outFile.assign(_inFile, 0, _inFile.size() - 4); _outFile += "_out.mp4"; } _vidOut.open(_outFile, codec, fps, sz, true); return _vidOut.isOpened() ? NVCV_SUCCESS : NvFromAppErr(APP_ERR_OPEN); } NvCV_Status App::stop() { #if _ENABLE_UI if(FLAG_showUI) ui_obj_.cleanup(); #endif // _ENABLE_UI if (_vidOut.isOpened()) _vidOut.release(); if (_vidIn.isOpened()) _vidIn.release(); if (_featureHan) NvAR_Destroy(_featureHan); if (_renderer) _renderer->destroy(); _renderer = nullptr; _featureHan = nullptr; _inFile.clear(); _outFile.clear(); return NVCV_SUCCESS; } const char *App::getErrorStringFromCode(NvCV_Status err) { int intErr = (int)err; if (intErr > 0) { struct LUTEntry { int code; const char *string; }; static const struct LUTEntry lut[] { { APP_ERR_GENERAL, "General application error" }, { APP_ERR_READ, "Read error" }, { APP_ERR_WRITE, "Write error" }, { APP_ERR_INIT, "Initialization error" }, { APP_ERR_RUN, "Run error" }, { APP_ERR_EFFECT, "Error creating an effect" }, { APP_ERR_PARAM, "Parameter error" }, { APP_ERR_UNIMPLEMENTED, "Unimplemented" }, { APP_ERR_MISSING, "Something is missing" }, { APP_ERR_VIDEO, "Video error" }, { APP_ERR_IMAGE_SIZE, "Image size error" }, { APP_ERR_NOT_FOUND, "Not found" }, { APP_ERR_FACE_MODEL, "Face model error" }, { APP_ERR_GLFW_INIT, "Error initializing GLFW" }, { APP_ERR_GL_INIT, "Error initializing OpenGL" }, { APP_ERR_RENDER_INIT, "Error initializing the renderer" }, { APP_ERR_GL_RESOURCE, "OpenGL resource error" }, { APP_ERR_GL_GENERAL, "General OpenGL error" }, { APP_ERR_FACE_FIT, "Face fit error" }, { APP_ERR_NO_FACE, "No face was found" }, { APP_ERR_CANCEL, "The operation has been canceled" }, { APP_ERR_CAMERA, "Camera error" }, { APP_ERR_ARG_PARSE, "Argument parsing error" }, }; for (const LUTEntry *p = lut; p != &lut[sizeof(lut) / sizeof(lut[0])]; ++p) if (intErr == p->code) return p->string; } return NvCV_GetErrorStringFromCode(err); } char *g_nvARSDKPath = NULL; NvCV_Status ResizeNvCVImage(const NvCVImage *src, NvCVImage *dst) { int interpolation = ((double)dst->width * dst->height / (src->width * src->height) < 1.) ? CV_INTER_AREA : CV_INTER_LINEAR; cv::Mat ocvSrc, ocvDst; CVWrapperForNvCVImage(src, &ocvSrc); CVWrapperForNvCVImage(dst, &ocvDst); cv::resize(ocvSrc, ocvDst, ocvDst.size(), 0, 0, interpolation); return NVCV_SUCCESS; } NvCV_Status App::initFaceFit() { unsigned modelLandmarks = 126, n; NvCV_Status err; // Initialize AR effect if (_featureHan) { if (EXPR_MODE_MESH == _exprMode) return NVCV_SUCCESS; NvAR_Destroy(_featureHan); _featureHan = nullptr; } BAIL_IF_ERR(err = NvAR_Create(NvAR_Feature_Face3DReconstruction, &_featureHan)); if (!FLAG_modelDir.empty()) BAIL_IF_ERR(err = NvAR_SetString(_featureHan, NvAR_Parameter_Config(ModelDir), FLAG_modelDir.c_str())); if (!FLAG_fitModel.empty()) { BAIL_IF_ERR(err = NvAR_SetString(_featureHan, NvAR_Parameter_Config(ModelName), FLAG_fitModel.c_str())); if (FLAG_fitModel[FLAG_fitModel.size() - 5] == '0') // face model 0 has 68 landmarks, the others have 126 modelLandmarks = 68; } BAIL_IF_ERR(err = NvAR_SetU32(_featureHan, NvAR_Parameter_Config(Landmarks_Size), modelLandmarks)); BAIL_IF_ERR(err = NvAR_SetCudaStream(_featureHan, NvAR_Parameter_Config(CUDAStream), _stream)); BAIL_IF_ERR(err = NvAR_SetU32(_featureHan, NvAR_Parameter_Config(Temporal), _filtering)); BAIL_IF_ERR(err = NvAR_SetU32(_featureHan, NvAR_Parameter_Config(GazeMode), FLAG_gaze)); BAIL_IF_ERR(err = NvAR_Load(_featureHan)); BAIL_IF_ERR(err = NvAR_GetU32(_featureHan, NvAR_Parameter_Config(ShapeEigenValueCount), &_eigenCount)); _eigenvalues.resize(_eigenCount); _outputBboxData.assign(25, { 0.f, 0.f, 0.f, 0.f }); _outputBboxes.boxes = _outputBboxData.data(); _outputBboxes.max_boxes = (uint8_t)_outputBboxData.size(); _outputBboxes.num_boxes = 0; BAIL_IF_ERR(err = NvAR_SetObject(_featureHan, NvAR_Parameter_Output(BoundingBoxes), &_outputBboxes, sizeof(NvAR_BBoxes))); BAIL_IF_ERR(err = NvAR_GetU32(_featureHan, NvAR_Parameter_Config(Landmarks_Size), &_landmarkCount)); _landmarks.resize(_landmarkCount); BAIL_IF_ERR(err = NvAR_SetObject(_featureHan, NvAR_Parameter_Output(Landmarks), _landmarks.data(), sizeof(NvAR_Point2f))); _landmarkConfidence.resize(_landmarkCount); BAIL_IF_ERR(err = NvAR_SetF32Array(_featureHan, NvAR_Parameter_Output(LandmarksConfidence), _landmarkConfidence.data(), _landmarkCount)); BAIL_IF_ERR(err = NvAR_GetU32(_featureHan, NvAR_Parameter_Config(ExpressionCount), &_exprCount)); _expressions.resize(_exprCount, 0.0f); _expressionZeroPoint.resize(_exprCount, 0.0f); _expressionScale.resize(_exprCount, 1.0f); _expressionExponent.resize(_exprCount, 1.0f); BAIL_IF_ERR(err = NvAR_SetF32Array(_featureHan, NvAR_Parameter_Output(ExpressionCoefficients), _expressions.data(), _exprCount)); BAIL_IF_ERR(err = NvAR_SetF32Array(_featureHan, NvAR_Parameter_Output(ShapeEigenValues), _eigenvalues.data(), _eigenCount)); BAIL_IF_ERR(err = NvAR_SetObject(_featureHan, NvAR_Parameter_Input(Image), &_srcGpu, sizeof(NvCVImage))); BAIL_IF_ERR(err = NvAR_SetObject(_featureHan, NvAR_Parameter_Output(Pose), &_pose, sizeof(_pose.rotation))); // The following are not used, but apparently required BAIL_IF_ERR(err = NvAR_GetU32(_featureHan, NvAR_Parameter_Config(VertexCount), &n)); _vertices.resize(_arMesh.num_vertices = n); _arMesh.vertices = &_vertices[0]; BAIL_IF_ERR(err = NvAR_GetU32(_featureHan, NvAR_Parameter_Config(TriangleCount), &n)); _triangles.resize(_arMesh.num_triangles = n); _arMesh.tvi = &_triangles[0]; BAIL_IF_ERR(err = NvAR_SetObject(_featureHan, NvAR_Parameter_Output(FaceMesh), &_arMesh, sizeof(_arMesh))); BAIL_IF_ERR(err = NvAR_SetObject(_featureHan, NvAR_Parameter_Output(RenderingParams), &_renderParams, sizeof(_renderParams))); _exprMode = EXPR_MODE_MESH; bail: return err; } NvCV_Status App::initMLPExpressions() { const unsigned landmarkCount = 126; NvCV_Status err; // Initialize AR effect if (_featureHan) { if (EXPR_MODE_MLP == _exprMode) return NVCV_SUCCESS; NvAR_Destroy(_featureHan); _featureHan = nullptr; } BAIL_IF_ERR(err = NvAR_Create(NvAR_Feature_FaceExpressions, &_featureHan)); if (!FLAG_modelDir.empty()) BAIL_IF_ERR(err = NvAR_SetString(_featureHan, NvAR_Parameter_Config(ModelDir), FLAG_modelDir.c_str())); BAIL_IF_ERR(err = NvAR_SetCudaStream(_featureHan, NvAR_Parameter_Config(CUDAStream), _stream)); BAIL_IF_ERR(err = NvAR_SetU32(_featureHan, NvAR_Parameter_Config(Temporal), _filtering)); BAIL_IF_ERR(err = NvAR_SetU32(_featureHan, NvAR_Parameter_Config(PoseMode), _poseMode)); BAIL_IF_ERR(err = NvAR_SetU32(_featureHan, NvAR_Parameter_Config(EnableCheekPuff), _enableCheekPuff)); BAIL_IF_ERR(err = NvAR_Load(_featureHan)); _outputBboxData.assign(25, { 0.f, 0.f, 0.f, 0.f }); _outputBboxes.boxes = _outputBboxData.data(); _outputBboxes.max_boxes = (uint8_t)_outputBboxData.size(); _outputBboxes.num_boxes = 0; BAIL_IF_ERR(err = NvAR_SetObject(_featureHan, NvAR_Parameter_Output(BoundingBoxes), &_outputBboxes, sizeof(NvAR_BBoxes))); _landmarks.resize(landmarkCount); BAIL_IF_ERR(err = NvAR_SetObject(_featureHan, NvAR_Parameter_Output(Landmarks), _landmarks.data(), sizeof(NvAR_Point2f))); _landmarkConfidence.resize(landmarkCount); BAIL_IF_ERR(err = NvAR_SetF32Array(_featureHan, NvAR_Parameter_Output(LandmarksConfidence), _landmarkConfidence.data(), landmarkCount)); BAIL_IF_ERR(err = NvAR_GetU32(_featureHan, NvAR_Parameter_Config(ExpressionCount), &_exprCount)); _expressions.resize(_exprCount); _expressionZeroPoint.resize(_exprCount, 0.0f); _expressionScale.resize(_exprCount, 1.0f); _expressionExponent.resize(_exprCount, 1.0f); BAIL_IF_ERR(err = NvAR_SetF32Array(_featureHan, NvAR_Parameter_Output(ExpressionCoefficients), _expressions.data(), _exprCount)); BAIL_IF_ERR(err = NvAR_SetObject(_featureHan, NvAR_Parameter_Input(Image), &_srcGpu, sizeof(NvCVImage))); // Heuristic for focal length if it is not known _cameraIntrinsicParams[0] = static_cast(_srcGpu.height); // focal length _cameraIntrinsicParams[1] = static_cast(_srcGpu.width) / 2.0f; // cx _cameraIntrinsicParams[2] = static_cast(_srcGpu.height) / 2.0f; // cy BAIL_IF_ERR(err = NvAR_SetF32Array(_featureHan, NvAR_Parameter_Input(CameraIntrinsicParams), _cameraIntrinsicParams, NUM_CAMERA_INTRINSIC_PARAMS)); BAIL_IF_ERR(err = NvAR_SetObject(_featureHan, NvAR_Parameter_Output(Pose), &_pose.rotation, sizeof(NvAR_Quaternion))); BAIL_IF_ERR(err = NvAR_SetObject(_featureHan, NvAR_Parameter_Output(PoseTranslation), &_pose.translation, sizeof(NvAR_Vector3f))); _exprMode = EXPR_MODE_MLP; bail: return err; } NvCV_Status App::calibrateExpressionWeights() { assert(_expressions.size() == _exprCount); assert(_expressionScale.size() == _exprCount); assert(_expressionZeroPoint.size() == _exprCount); _expressionZeroPoint = _expressions; for (size_t i = 0; i < _exprCount; i++) { _expressionScale[i] = 1.0f / (1.0f - _expressionZeroPoint[i]); } _performCalibration = false; return NVCV_SUCCESS; } NvCV_Status App::unCalibrateExpressionWeights() { std::fill(_expressionZeroPoint.begin(), _expressionZeroPoint.end(), 0.0f); std::fill(_expressionScale.begin(), _expressionScale.end(), 1.0f); std::fill(_expressionExponent.begin(), _expressionExponent.end(), 1.0f); return NVCV_SUCCESS; } NvCV_Status App::normalizeExpressionsWeights() { assert(_expressions.size() == _exprCount); assert(_expressionScale.size() == _exprCount); assert(_expressionZeroPoint.size() == _exprCount); for (size_t i = 0; i < _exprCount; i++) { float tempExpr = _expressions[i]; _expressions[i] = 1.0f - (std::pow( 1.0f - (std::max(_expressions[i] - _expressionZeroPoint[i], 0.0f) * _expressionScale[i]), _expressionExponent[i])); _expressions[i] = _globalExpressionParam * _expressions[i] + (1.0f - _globalExpressionParam) * tempExpr; } return NVCV_SUCCESS; } NvCV_Status App::updateCamera() { NvCV_Status err; if (_poseMode == 1) { const NvAR_Vector3f cam_loc = {0, 0, 0}; const NvAR_Vector3f cam_dir = {0, 0, -1}; const NvAR_Vector3f cam_up = {0, 1, 0}; const float focal_length = _cameraIntrinsicParams[0]; const float v_fov = focal_length == 0.0f ? 0.0f : 2.0f * atan(_videoHeight / (2 * focal_length)); err = _renderer->setCamera(&cam_loc.vec[0], &cam_dir.vec[0], &cam_up.vec[0], v_fov, 0.01f, 1000.0f); } else { err = _renderer->setCamera(nullptr, nullptr, nullptr, 0.0f); } _cameraNeedsUpdate = false; return err; } NvCV_Status App::init() { NvCV_Status err; std::string path; std::vector rendererList; _renderHeight = 480; _renderWidth = 480; _miniHeight = _renderHeight; _miniWidth = (unsigned)((float)_videoWidth * _renderHeight / (_videoHeight * 2) + 0.5f) * 2; _compWidth = _miniWidth + _renderWidth; _plotWidth = _compWidth; _plotHeight = 72; _compHeight = _renderHeight + _plotHeight; _miniX = 0; _miniY = 0; _renderX = _miniWidth; _renderY = 0; _plotX = 0; _plotY = _renderHeight; _viewMode = FLAG_viewMode; _showFPS = false; _performCalibration = false; _frameTime = 0.f; _exprMode = 0; _poseMode = FLAG_poseMode; _enableCheekPuff = FLAG_cheekPuff; _featureHan = nullptr; _filtering = FLAG_filter; _globalExpressionParam = 1.0f; _cameraIntrinsicParams[0] = 0.0f; _cameraIntrinsicParams[1] = 0.0f; _cameraIntrinsicParams[2] = 0.0f; _cameraNeedsUpdate = true; err = _broker.getMeshRendererList(rendererList); if (NVCV_SUCCESS != err) { printf("Cannot engage renderer broker: %s\n", NvCV_GetErrorStringFromCode(err)); return err; } if (FLAG_verbose) { printf("Renderer list:\n"); for (const std::string& str : rendererList) printf(" %s\n", str.c_str()); } if (rendererList.size() < 1) { printf("No renderers available to the broker\n"); return NVCV_ERR_FEATURENOTFOUND; } err = _broker.create(rendererList[0].c_str(), &_renderer); if (NVCV_SUCCESS != err) { printf("Cannot create the %s renderer\n", rendererList[0].c_str()); return NVCV_ERR_FEATURENOTFOUND; } if (!SetPathIfFileExists("", FLAG_renderModel, path) && !SetPathIfFileExists(FLAG_modelDir, FLAG_renderModel, path)) { err = NVCV_ERR_FILE; printf("Cannot find %s: %s\n", FLAG_renderModel.c_str(), NvCV_GetErrorStringFromCode(err)); return err; } err = _renderer->read(path.c_str()); if (NVCV_SUCCESS != err) { printf("{\"%s\",\"%s\"}: %s\n", FLAG_modelDir.c_str(), FLAG_renderModel.c_str(), NvCV_GetErrorStringFromCode(err)); return err; } err = _renderer->init(_renderWidth, _renderHeight, _windowTitle); if (NVCV_SUCCESS != err) { printf("renderer init: %s\n", NvCV_GetErrorStringFromCode(err)); return err; } BAIL_IF_ERR(err = NvCVImage_Alloc(&_srcGpu, _videoWidth, _videoHeight, NVCV_BGR, NVCV_U8, NVCV_CHUNKY, NVCV_GPU, 1)); BAIL_IF_ERR(err = NvCVImage_Alloc(&_srcImg, _videoWidth, _videoHeight, NVCV_BGR, NVCV_U8, NVCV_CHUNKY, NVCV_CPU_PINNED, 0)); BAIL_IF_ERR(err = NvCVImage_Alloc(&_compImg, _compWidth, _compHeight, NVCV_BGR, NVCV_U8, NVCV_CHUNKY, NVCV_CPU, 0)); BAIL_IF_ERR(err = NvCVImage_Alloc(&_renderImg, _renderWidth, _renderHeight, NVCV_RGBA, NVCV_U8, NVCV_CHUNKY, NVCV_CPU, 0)); CVWrapperForNvCVImage(&_compImg, &_ocvDstImg); CVWrapperForNvCVImage(&_srcImg, &_ocvSrcImg); resizeDst(); if (!FLAG_outFile.empty() || !FLAG_show) { err = App::openOutputVideo(StringToFourcc(FLAG_codec), _frameRate, _compWidth, _compHeight); if (NVCV_SUCCESS != err) { printf("ERROR: \"%s\": %s\n", _outFile.c_str(), getErrorStringFromCode(err)); goto bail; } } // Initialize AR effect switch (FLAG_exprMode) { default: printf("Unknown expression mode %u; using 1=mesh instead\n", FLAG_exprMode); /* fall through */ case EXPR_MODE_MESH: BAIL_IF_ERR(err = initFaceFit()); break; case EXPR_MODE_MLP: BAIL_IF_ERR(err = initMLPExpressions()); break; } if (FLAG_show) cv::namedWindow(_windowTitle, 1); #if _ENABLE_UI if(FLAG_showUI) ui_obj_.init(_exprCount, _filtering, FLAG_exprMode, _viewMode, _showFPS); #endif // _ENABLE_UI bail: return err; } NvCV_Status App::overlayLandmarks(const float landmarks[126 * 2], unsigned screenHeight, NvCVImage* im) { cv::Mat frame; CVWrapperForNvCVImage(im, &frame); float scale = (float)sqrtf((float)frame.rows / screenHeight); if (scale < 1.f) scale = 1.f; int size = int(5 * scale), thickness = int(scale); const float* p = landmarks; for (unsigned i = 126; i--; p += 2) cv::drawMarker(frame, { (int)lround(p[0]), (int)lround(p[1]) }, CV_RGB(0, 255, 0), cv::MARKER_CROSS, size, thickness, 8); return NVCV_SUCCESS; } NvCV_Status App::run() { NvCV_Status err = NVCV_SUCCESS; NvCVImage tmpImg, view; for (unsigned frameCount = 0;; ++frameCount) { if (!_vidIn.read(_ocvSrcImg) || _ocvSrcImg.empty()) { if (!frameCount) return NvFromAppErr(APP_ERR_VIDEO); // No frames in video if (!FLAG_loop) return NvFromAppErr(APP_ERR_EOF); // Video has completed _vidIn.set(CV_CAP_PROP_POS_FRAMES, 0); // Rewind, because loop mode has been selected --frameCount; // Account for the wasted frame continue; // Read the first frame again } #ifdef _ENABLE_UI unsigned int exprMode = 0; bool uncalibrate = false; bool calibrate = false; unsigned int filter = 0; unsigned int viewMode = 0; bool killApp = false; if (FLAG_showUI) { ui_obj_.stateQuerybyCore(viewMode, exprMode, filter, calibrate, uncalibrate, _showFPS, _globalExpressionParam, _expressionZeroPoint, _expressionScale, _expressionExponent, killApp); if (killApp == true) { return NvFromAppErr(APP_ERR_CANCEL); } _performCalibration = calibrate; if (viewMode != _viewMode) { _viewMode = viewMode; resizeDst(); } if (uncalibrate) { unCalibrateExpressionWeights(); } if ((filter ^ _filtering) & NVAR_TEMPORAL_FILTER_FACE_BOX) { toggleFaceBoxFiltering(); } if ((filter ^ _filtering) & NVAR_TEMPORAL_FILTER_FACIAL_LANDMARKS) { toggleLandmarkFiltering(); } if ((filter ^ _filtering) & NVAR_TEMPORAL_FILTER_FACE_ROTATIONAL_POSE) { togglePoseFiltering(); } if ((filter ^ _filtering) & NVAR_TEMPORAL_FILTER_FACIAL_EXPRESSIONS) { toggleExpressionFiltering(); } if ((filter ^ _filtering) & NVAR_TEMPORAL_FILTER_FACIAL_GAZE) { toggleGazeFiltering(); } if ((filter ^ _filtering) & NVAR_TEMPORAL_FILTER_ENHANCE_EXPRESSIONS) { toggleClosureEnhancement(); } _filtering = filter; if (_exprMode != exprMode) { if (exprMode == EXPR_MODE_MESH) { initFaceFit(); } else if (exprMode == EXPR_MODE_MLP) { initMLPExpressions(); } else { // add more modes if needed } } } #endif // _ENABLE_UI BAIL_IF_ERR(err = NvCVImage_Transfer(&_srcImg, &_srcGpu, 1.f, _stream, nullptr)); BAIL_IF_ERR(err = NvAR_Run(_featureHan)); unsigned isFaceDetected = (_outputBboxes.num_boxes > 0) ? 0xFF : 0; if (_cameraNeedsUpdate) { err = updateCamera(); if (NVCV_SUCCESS != err) { printf("renderer setCamera: %s\n", NvCV_GetErrorStringFromCode(err)); return err; } } if (_performCalibration) { calibrateExpressionWeights(); } normalizeExpressionsWeights(); if (_viewMode & VIEW_LM & isFaceDetected) { BAIL_IF_ERR(err = overlayLandmarks(&_landmarks.data()->x, _renderHeight, &_srcImg)); } if (_viewMode & VIEW_IMAGE) { NvCVImage_InitView(&view, &_compImg, _miniX, _miniY, _miniWidth, _miniHeight); err = ResizeNvCVImage(&_srcImg, &view); } if (_viewMode & VIEW_PLOT) { if (!isFaceDetected) std::fill(_expressions.begin(), _expressions.end(), 0); barPlotExprs(); } if (_viewMode & VIEW_MESH) { if (isFaceDetected) { float* head_translation = _poseMode == 1 ? &_pose.translation.vec[0] : nullptr; BAIL_IF_ERR(err = _renderer->render(_expressions.data(), &_pose.rotation.x, head_translation, &_renderImg)); // GL _renderImg is upside down NvCVImage_InitView(&view, &_compImg, _renderX, _renderY, _renderWidth, _renderHeight); NvCVImage_FlipY(&view, &view); // Since OpenGL renderImg is upside-down, we copy it to a flipped dst NvCVImage_Transfer(&_renderImg, &view, 1.0f, _stream, nullptr); // VFlip RGBA --> BGR } else { cv::Mat compImgCVMat; CVWrapperForNvCVImage(&_compImg, &compImgCVMat); cv::rectangle(compImgCVMat, cv::Rect(_renderX, _renderY, _renderWidth, _renderHeight), cv::Scalar(0, 0, 0), -1); } } if (_vidOut.isOpened()) _vidOut.write(_ocvDstImg); drawFPS(_ocvDstImg); if (FLAG_show && _ocvDstImg.cols && _ocvDstImg.rows) { cv::imshow(_windowTitle, _ocvDstImg); } int key = cv::waitKey(1); if (key >= 0 && FLAG_debug) printf("Key press '%c' (%02x)\n", ((0x20 <= key && key <= 0x7f) ? key : '#'), key); #ifdef _ENABLE_UI if (FLAG_showUI){ ui_obj_.stateSetbyCore(_expressions, _expressionZeroPoint, _expressionScale, _expressionExponent, (uncalibrate || calibrate), key); } #endif // _ENABLE_UI if (!FLAG_showUI) { switch (key) { case 27 /*ESC*/: case 'q': case 'Q': return NvFromAppErr(APP_ERR_CANCEL); // Quit case 'i': _viewMode ^= VIEW_IMAGE; resizeDst(); break; case 'l': _viewMode ^= VIEW_LM; resizeDst(); break; case 'm': _viewMode ^= VIEW_MESH; resizeDst(); break; case 'n': _performCalibration = true; break; case 'p': _viewMode ^= VIEW_PLOT; resizeDst(); break; case 'f': _showFPS = !_showFPS; break; case '1': initFaceFit(); break; case '2': initMLPExpressions(); break; case 'L': case CTL('L'): toggleLandmarkFiltering(); break; case 'N': case CTL('N'): unCalibrateExpressionWeights(); break; case 'P': case CTL('P'): togglePoseFiltering(); break; case 'E': case CTL('E'): toggleExpressionFiltering(); break; case 'G': case CTL('G'): toggleGazeFiltering(); break; case 'C': case CTL('C'): toggleClosureEnhancement(); break; case 'M': case CTL('M'): togglePoseMode(); break; default: if (key < 0) continue; // No key break; // Non-mapped key } } } bail: return err; } NvCV_Status App::resizeDst() { NvCV_Status err = NVCV_SUCCESS; unsigned width = 0, height = 0; if (_viewMode & VIEW_IMAGE) { width += _miniWidth; if (height < _miniHeight) height = _miniHeight; _miniX = 0; _miniY = 0; } if (_viewMode & VIEW_MESH) { width += _renderWidth; if (height < _renderHeight) height = _renderHeight; _renderY = 0; _renderX = (_viewMode & VIEW_IMAGE) ? _miniWidth : 0; } if (_viewMode & VIEW_PLOT) { _plotX = 0; _plotY = height; _plotWidth = width; height += _plotHeight; } BAIL_IF_ERR(err = NvCVImage_Realloc(&_compImg, width, height, _compImg.pixelFormat, _compImg.componentType, NVCV_CHUNKY, NVCV_CPU, 0)); memset(_compImg.deletePtr, 0, _compImg.bufferBytes); CVWrapperForNvCVImage(&_compImg, &_ocvDstImg); cv::resizeWindow(_windowTitle, width, height); bail: return err; } void App::barPlotExprs() { int barWidth = (int)(_plotWidth / _expressions.size()), barHeight = (int)_plotHeight; cv::Scalar fgColor, bgColor, txColor; cv::Rect r; cv::Point pt; std::string str; const char *(*exprAbbr)[4] = (_expressions.size() > 6) ? _exprAbbr : _sfmExprAbbr; bgColor = { 0, 0, 0, 255 }; r = { _plotX, _plotY, (int)_plotWidth, (int)_plotHeight }; cv::rectangle(_ocvDstImg, r, bgColor, cv::FILLED, cv::LINE_4, 0); bgColor = { 32, 32, 32, 255 }; fgColor = { 0, 255, 0, 255 }; txColor = CV_RGB(255, 255, 0); //{ 255, 255, 0, 255 }; r.width = barWidth - 1; for (unsigned i = unsigned(_expressions.size()); i--;) { r.x = _plotX + i * barWidth; r.y = _plotY; r.height = _plotHeight; cv::rectangle(_ocvDstImg, r, bgColor, cv::FILLED, cv::LINE_4, 0); r.height = (int)(_expressions[i] * barHeight + 0.5f); r.y = _plotY + _plotHeight - r.height; cv::rectangle(_ocvDstImg, r, fgColor, cv::FILLED, cv::LINE_4, 0); cv::putText(_ocvDstImg, std::to_string(i), cv::Point(r.x + 1, _plotY + 10), cv::FONT_HERSHEY_SIMPLEX, 0.25, txColor, 1, cv::LINE_8, false); for (unsigned j = 0; j < 4; ++j) if (exprAbbr[i][j]) cv::putText(_ocvDstImg, exprAbbr[i][j], cv::Point(r.x + 1, _plotY + 20 + 10 * j), cv::FONT_HERSHEY_SIMPLEX, 0.25, txColor, 1, cv::LINE_8, false); } } void App::getFPS() { const float timeConstant = 16.f; _timer.stop(); float t = (float)_timer.elapsedTimeFloat(); if (t < 100.f) { if (_frameTime) _frameTime += (t - _frameTime) * (1.f / timeConstant); // 1 pole IIR filter else _frameTime = t; } else { // Ludicrous time interval; reset _frameTime = 0.f; // WAKE UP } _timer.start(); } void App::drawFPS(cv::Mat& img) { getFPS(); if (_frameTime && _showFPS) { char buf[32]; snprintf(buf, sizeof(buf), "%.1f", 1. / _frameTime); cv::putText(img, buf, cv::Point(img.cols - 80, 30), cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(255, 255, 255), 1); } } NvCV_Status App::toggleFaceBoxFiltering() { _filtering ^= NVAR_TEMPORAL_FILTER_FACE_BOX; NvCV_Status err = NvAR_SetU32(_featureHan, NvAR_Parameter_Config(Temporal), _filtering); if (NVCV_SUCCESS == err) err = NvAR_Load(_featureHan); if (FLAG_verbose) printf("FaceBox Filtering %s\n", ((_filtering & NVAR_TEMPORAL_FILTER_FACE_BOX) ? "ON" : "OFF")); return err; } NvCV_Status App::toggleLandmarkFiltering() { _filtering ^= NVAR_TEMPORAL_FILTER_FACIAL_LANDMARKS; NvCV_Status err = NvAR_SetU32(_featureHan, NvAR_Parameter_Config(Temporal), _filtering); if (NVCV_SUCCESS == err) err = NvAR_Load(_featureHan); if (FLAG_verbose) printf("Landmark Filtering %s\n", ((_filtering & NVAR_TEMPORAL_FILTER_FACIAL_LANDMARKS) ? "ON" : "OFF")); return err; } NvCV_Status App::togglePoseFiltering() { _filtering ^= NVAR_TEMPORAL_FILTER_FACE_ROTATIONAL_POSE; NvCV_Status err = NvAR_SetU32(_featureHan, NvAR_Parameter_Config(Temporal), _filtering); if (NVCV_SUCCESS == err) err = NvAR_Load(_featureHan); if (FLAG_verbose) printf("Pose Filtering %s\n", ((_filtering & NVAR_TEMPORAL_FILTER_FACE_ROTATIONAL_POSE) ? "ON" : "OFF")); return err; } NvCV_Status App::toggleExpressionFiltering() { _filtering ^= NVAR_TEMPORAL_FILTER_FACIAL_EXPRESSIONS; NvCV_Status err = NvAR_SetU32(_featureHan, NvAR_Parameter_Config(Temporal), _filtering); if (NVCV_SUCCESS == err) err = NvAR_Load(_featureHan); if (FLAG_verbose) printf("Expression Filtering %s\n", ((_filtering & NVAR_TEMPORAL_FILTER_FACIAL_EXPRESSIONS) ? "ON" : "OFF")); return err; } NvCV_Status App::toggleGazeFiltering() { _filtering ^= NVAR_TEMPORAL_FILTER_FACIAL_GAZE; NvCV_Status err = NvAR_SetU32(_featureHan, NvAR_Parameter_Config(Temporal), _filtering); if (NVCV_SUCCESS == err) err = NvAR_Load(_featureHan); if (FLAG_verbose) printf("Gaze Filtering %s\n", ((_filtering & NVAR_TEMPORAL_FILTER_FACIAL_GAZE) ? "ON" : "OFF")); return err; } NvCV_Status App::toggleClosureEnhancement() { _filtering ^= NVAR_TEMPORAL_FILTER_ENHANCE_EXPRESSIONS; NvCV_Status err = NvAR_SetU32(_featureHan, NvAR_Parameter_Config(Temporal), _filtering); if (NVCV_SUCCESS == err) err = NvAR_Load(_featureHan); if (FLAG_verbose) printf("Closure Enhancement %s\n", ((_filtering & NVAR_TEMPORAL_FILTER_ENHANCE_EXPRESSIONS) ? "ON" : "OFF")); return err; } NvCV_Status App::togglePoseMode() { _poseMode = !_poseMode; NvCV_Status err = NvAR_SetU32(_featureHan, NvAR_Parameter_Config(PoseMode), _poseMode); if (NVCV_SUCCESS == err) err = NvAR_Load(_featureHan); _cameraNeedsUpdate = true; return err; } /******************************************************************************** * main ********************************************************************************/ int main(int argc, char **argv) { NvCV_Status err = NVCV_SUCCESS; App app; int nErrs; if (0 != (nErrs = ParseMyArgs(argc, argv))) { if (HELP_REQUESTED == nErrs) // If it was a call for help ... BAIL(err, NVCV_SUCCESS); // ... just exit quietly printf("ERROR: argument syntax\n"); BAIL(err, NVCV_ERR_PARSE); } if (FLAG_fitModel.empty()) FLAG_fitModel = DEFAULT_FACE_MODEL; if (FLAG_renderModel.empty()) FLAG_renderModel = DEFAULT_RENDER_MODEL; if (FLAG_modelDir.empty()) { do { if (SetDirIfFileExists("../data", FLAG_renderModel, FLAG_modelDir)) break; if (SetDirIfFileExists("../../data", FLAG_renderModel, FLAG_modelDir)) break; if (SetDirIfFileExists("../../../data", FLAG_renderModel, FLAG_modelDir)) break; if (SetDirIfFileExists("data", FLAG_renderModel, FLAG_modelDir)) break; printf("Please set --model_dir=/path/to/models\n"); BAIL(err, NVCV_ERR_MISSINGINPUT); } while (0); } if (!FLAG_inFile.empty()) { // Input from a video file err = app.setInputVideo(FLAG_inFile); if (NVCV_SUCCESS != err) { printf("ERROR: \"%s\": %s\n", FLAG_inFile.c_str(), app.getErrorStringFromCode(err)); goto bail; } } else { // Input from a webcam, #0 err = app.setInputCamera(0, FLAG_camRes); if (NVCV_SUCCESS != err) { printf("ERROR: cam0: %s\n", app.getErrorStringFromCode(err)); goto bail; } } if (!FLAG_outFile.empty()) { // Output to a file err = app.setOutputVideo(FLAG_outFile); if (NVCV_SUCCESS != err) { printf("ERROR: \"%s\": %s\n", FLAG_outFile.c_str(), app.getErrorStringFromCode(err)); goto bail; } } else if (!FLAG_show) { // Without choosing to show or write to an output file, it would be wasting energy printf("WARNING: Setting --show since neither --show nor --out were supplied\n"); FLAG_show = true; } if (FLAG_verbose) printf("Enabled filters = %x\n", FLAG_filter); if (FLAG_verbose) printf("Enabled cnn gaze = %x\n", FLAG_gaze); err = app.init(); BAIL_IF_ERR(err); err = app.run(); switch ((int)err) { case App::APP_ERR_CANCEL: // The user stopped case App::APP_ERR_EOF: // The end of the file was reached. err = NVCV_SUCCESS; break; default: break; } BAIL_IF_ERR(err); bail: if (err) printf("ERROR: %s\n", app.getErrorStringFromCode(err)); return (int)err; }