// This file is part of OpenCV project. // It is subject to the license terms in the LICENSE file found in the top-level // directory of this distribution and at http://opencv.org/license.html. // // Copyright (C) 2018-2019 Intel Corporation #include #include #include #include #include #include #include #include "opencv2/gapi/streaming/cap.hpp" #include #include #include namespace config { constexpr char kWinFaceBeautification[] = "FaceBeautificator"; constexpr char kWinInput[] = "Input"; const cv::Scalar kClrWhite (255, 255, 255); const cv::Scalar kClrGreen ( 0, 255, 0); const cv::Scalar kClrYellow( 0, 255, 255); constexpr float kConfThresh = 0.7f; const cv::Size kGKernelSize(5, 5); constexpr double kGSigma = 0.0; constexpr int kBSize = 9; constexpr double kBSigmaCol = 30.0; constexpr double kBSigmaSp = 30.0; constexpr int kUnshSigma = 3; constexpr float kUnshStrength = 0.7f; constexpr int kAngDelta = 1; constexpr bool kClosedLine = true; const size_t kNumPointsInHalfEllipse = 180 / config::kAngDelta + 1; } // namespace config namespace { using VectorROI = std::vector; using GArrayROI = cv::GArray; using Contour = std::vector; using Landmarks = std::vector; // Wrapper function template inline int toIntRounded(const Tp x) { return static_cast(std::lround(x)); } template inline double toDouble(const Tp x) { return static_cast(x); } std::string getWeightsPath(const std::string &mdlXMLPath) // mdlXMLPath = // "The/Full/Path.xml" { size_t size = mdlXMLPath.size(); CV_Assert(mdlXMLPath.substr(size - 4, size) // The last 4 symbols == ".xml"); // must be ".xml" std::string mdlBinPath(mdlXMLPath); return mdlBinPath.replace(size - 3, 3, "bin"); // return // "The/Full/Path.bin" } } // anonymous namespace namespace custom { using TplPtsFaceElements_Jaw = std::tuple, cv::GArray>; using TplFaces_FaceElements = std::tuple, cv::GArray>; // Wrapper-functions inline int getLineInclinationAngleDegrees(const cv::Point &ptLeft, const cv::Point &ptRight); inline Contour getForeheadEllipse(const cv::Point &ptJawLeft, const cv::Point &ptJawRight, const cv::Point &ptJawMiddle, const size_t capacity); inline Contour getEyeEllipse(const cv::Point &ptLeft, const cv::Point &ptRight, const size_t capacity); inline Contour getPatchedEllipse(const cv::Point &ptLeft, const cv::Point &ptRight, const cv::Point &ptUp, const cv::Point &ptDown); // Networks G_API_NET(FaceDetector, , "face_detector"); G_API_NET(LandmDetector, , "landm_detector"); // Function kernels G_TYPED_KERNEL(GBilatFilter, , "custom.faceb12n.bilateralFilter") { static cv::GMatDesc outMeta(cv::GMatDesc in, int,double,double) { return in; } }; G_TYPED_KERNEL(GLaplacian, , "custom.faceb12n.Laplacian") { static cv::GMatDesc outMeta(cv::GMatDesc in, int) { return in; } }; G_TYPED_KERNEL(GFillPolyGContours, )>, "custom.faceb12n.fillPolyGContours") { static cv::GMatDesc outMeta(cv::GMatDesc in, cv::GArrayDesc) { return in.withType(CV_8U, 1); } }; G_TYPED_KERNEL(GPolyLines, ,bool,cv::Scalar)>, "custom.faceb12n.polyLines") { static cv::GMatDesc outMeta(cv::GMatDesc in, cv::GArrayDesc,bool,cv::Scalar) { return in; } }; G_TYPED_KERNEL(GRectangle, , "custom.faceb12n.rectangle") { static cv::GMatDesc outMeta(cv::GMatDesc in, cv::GArrayDesc,cv::Scalar) { return in; } }; G_TYPED_KERNEL(GFacePostProc, , "custom.faceb12n.faceDetectPostProc") { static cv::GArrayDesc outMeta(const cv::GMatDesc&,const cv::GMatDesc&,float) { return cv::empty_array_desc(); } }; G_TYPED_KERNEL_M(GLandmPostProc, ,GArrayROI)>, "custom.faceb12n.landmDetectPostProc") { static std::tuple outMeta( const cv::GArrayDesc&,const cv::GArrayDesc&) { return std::make_tuple(cv::empty_array_desc(), cv::empty_array_desc()); } }; G_TYPED_KERNEL_M(GGetContours, , cv::GArray)>, "custom.faceb12n.getContours") { static std::tuple outMeta( const cv::GArrayDesc&,const cv::GArrayDesc&) { return std::make_tuple(cv::empty_array_desc(), cv::empty_array_desc()); } }; // OCV_Kernels // This kernel applies Bilateral filter to an input src with default // "cv::bilateralFilter" border argument GAPI_OCV_KERNEL(GCPUBilateralFilter, custom::GBilatFilter) { static void run(const cv::Mat &src, const int diameter, const double sigmaColor, const double sigmaSpace, cv::Mat &out) { cv::bilateralFilter(src, out, diameter, sigmaColor, sigmaSpace); } }; // This kernel applies Laplace operator to an input src with default // "cv::Laplacian" arguments GAPI_OCV_KERNEL(GCPULaplacian, custom::GLaplacian) { static void run(const cv::Mat &src, const int ddepth, cv::Mat &out) { cv::Laplacian(src, out, ddepth); } }; // This kernel draws given white filled contours "cnts" on a clear Mat "out" // (defined by a Scalar(0)) with standard "cv::fillPoly" arguments. // It should be used to create a mask. // The input Mat seems unused inside the function "run", but it is used deeper // in the kernel to define an output size. GAPI_OCV_KERNEL(GCPUFillPolyGContours, custom::GFillPolyGContours) { static void run(const cv::Mat &, const std::vector &cnts, cv::Mat &out) { out = cv::Scalar(0); cv::fillPoly(out, cnts, config::kClrWhite); } }; // This kernel draws given contours on an input src with default "cv::polylines" // arguments GAPI_OCV_KERNEL(GCPUPolyLines, custom::GPolyLines) { static void run(const cv::Mat &src, const std::vector &cnts, const bool isClosed, const cv::Scalar &color, cv::Mat &out) { src.copyTo(out); cv::polylines(out, cnts, isClosed, color); } }; // This kernel draws given rectangles on an input src with default // "cv::rectangle" arguments GAPI_OCV_KERNEL(GCPURectangle, custom::GRectangle) { static void run(const cv::Mat &src, const VectorROI &vctFaceBoxes, const cv::Scalar &color, cv::Mat &out) { src.copyTo(out); for (const cv::Rect &box : vctFaceBoxes) { cv::rectangle(out, box, color); } } }; // A face detector outputs a blob with the shape: [1, 1, N, 7], where N is // the number of detected bounding boxes. Structure of an output for every // detected face is the following: // [image_id, label, conf, x_min, y_min, x_max, y_max]; all the seven elements // are floating point. For more details please visit: // https://github.com/opencv/open_model_zoo/blob/master/intel_models/face-detection-adas-0001 // This kernel is the face detection output blob parsing that returns a vector // of detected faces' rects: GAPI_OCV_KERNEL(GCPUFacePostProc, GFacePostProc) { static void run(const cv::Mat &inDetectResult, const cv::Mat &inFrame, const float faceConfThreshold, VectorROI &outFaces) { const int kObjectSize = 7; const int imgCols = inFrame.size().width; const int imgRows = inFrame.size().height; const cv::Rect borders({0, 0}, inFrame.size()); outFaces.clear(); const int numOfDetections = inDetectResult.size[2]; const float *data = inDetectResult.ptr(); for (int i = 0; i < numOfDetections; i++) { const float faceId = data[i * kObjectSize + 0]; if (faceId < 0.f) // indicates the end of detections { break; } const float faceConfidence = data[i * kObjectSize + 2]; if (faceConfidence > faceConfThreshold) { const float left = data[i * kObjectSize + 3]; const float top = data[i * kObjectSize + 4]; const float right = data[i * kObjectSize + 5]; const float bottom = data[i * kObjectSize + 6]; cv::Point tl(toIntRounded(left * imgCols), toIntRounded(top * imgRows)); cv::Point br(toIntRounded(right * imgCols), toIntRounded(bottom * imgRows)); outFaces.push_back(cv::Rect(tl, br) & borders); } } } }; // This kernel is the facial landmarks detection output Mat parsing for every // detected face; returns a tuple containing a vector of vectors of // face elements' Points and a vector of vectors of jaw's Points: GAPI_OCV_KERNEL(GCPULandmPostProc, GLandmPostProc) { static void run(const std::vector &vctDetectResults, const VectorROI &vctRects, std::vector &vctPtsFaceElems, std::vector &vctCntJaw) { // There are 35 landmarks given by the default detector for each face // in a frame; the first 18 of them are face elements (eyes, eyebrows, // a nose, a mouth) and the last 17 - a jaw contour. The detector gives // floating point values for landmarks' normed coordinates relatively // to an input ROI (not the original frame). // For more details please visit: // https://github.com/opencv/open_model_zoo/blob/master/intel_models/facial-landmarks-35-adas-0002 static constexpr int kNumFaceElems = 18; static constexpr int kNumTotal = 35; const size_t numFaces = vctRects.size(); CV_Assert(vctPtsFaceElems.size() == 0ul); CV_Assert(vctCntJaw.size() == 0ul); vctPtsFaceElems.reserve(numFaces); vctCntJaw.reserve(numFaces); Landmarks ptsFaceElems; Contour cntJaw; ptsFaceElems.reserve(kNumFaceElems); cntJaw.reserve(kNumTotal - kNumFaceElems); for (size_t i = 0; i < numFaces; i++) { const float *data = vctDetectResults[i].ptr(); // The face elements points: ptsFaceElems.clear(); for (int j = 0; j < kNumFaceElems * 2; j += 2) { cv::Point pt = cv::Point(toIntRounded(data[j] * vctRects[i].width), toIntRounded(data[j+1] * vctRects[i].height)) + vctRects[i].tl(); ptsFaceElems.push_back(pt); } vctPtsFaceElems.push_back(ptsFaceElems); // The jaw contour points: cntJaw.clear(); for(int j = kNumFaceElems * 2; j < kNumTotal * 2; j += 2) { cv::Point pt = cv::Point(toIntRounded(data[j] * vctRects[i].width), toIntRounded(data[j+1] * vctRects[i].height)) + vctRects[i].tl(); cntJaw.push_back(pt); } vctCntJaw.push_back(cntJaw); } } }; // This kernel is the facial landmarks detection post-processing for every face // detected before; output is a tuple of vectors of detected face contours and // facial elements contours: GAPI_OCV_KERNEL(GCPUGetContours, GGetContours) { static void run(const std::vector &vctPtsFaceElems, const std::vector &vctCntJaw, std::vector &vctElemsContours, std::vector &vctFaceContours) { size_t numFaces = vctCntJaw.size(); CV_Assert(numFaces == vctPtsFaceElems.size()); CV_Assert(vctElemsContours.size() == 0ul); CV_Assert(vctFaceContours.size() == 0ul); // vctFaceElemsContours will store all the face elements' contours found // on an input image, namely 4 elements (two eyes, nose, mouth) // for every detected face vctElemsContours.reserve(numFaces * 4); // vctFaceElemsContours will store all the faces' contours found on // an input image vctFaceContours.reserve(numFaces); Contour cntFace, cntLeftEye, cntRightEye, cntNose, cntMouth; cntNose.reserve(4); for (size_t i = 0ul; i < numFaces; i++) { // The face elements contours // A left eye: // Approximating the lower eye contour by half-ellipse // (using eye points) and storing in cntLeftEye: cntLeftEye = getEyeEllipse(vctPtsFaceElems[i][1], vctPtsFaceElems[i][0], config::kNumPointsInHalfEllipse + 3); // Pushing the left eyebrow clock-wise: cntLeftEye.insert(cntLeftEye.cend(), {vctPtsFaceElems[i][12], vctPtsFaceElems[i][13], vctPtsFaceElems[i][14]}); // A right eye: // Approximating the lower eye contour by half-ellipse // (using eye points) and storing in vctRightEye: cntRightEye = getEyeEllipse(vctPtsFaceElems[i][2], vctPtsFaceElems[i][3], config::kNumPointsInHalfEllipse + 3); // Pushing the right eyebrow clock-wise: cntRightEye.insert(cntRightEye.cend(), {vctPtsFaceElems[i][15], vctPtsFaceElems[i][16], vctPtsFaceElems[i][17]}); // A nose: // Storing the nose points clock-wise cntNose.clear(); cntNose.insert(cntNose.cend(), {vctPtsFaceElems[i][4], vctPtsFaceElems[i][7], vctPtsFaceElems[i][5], vctPtsFaceElems[i][6]}); // A mouth: // Approximating the mouth contour by two half-ellipses // (using mouth points) and storing in vctMouth: cntMouth = getPatchedEllipse(vctPtsFaceElems[i][8], vctPtsFaceElems[i][9], vctPtsFaceElems[i][10], vctPtsFaceElems[i][11]); // Storing all the elements in a vector: vctElemsContours.insert(vctElemsContours.cend(), {cntLeftEye, cntRightEye, cntNose, cntMouth}); // The face contour: // Approximating the forehead contour by half-ellipse // (using jaw points) and storing in vctFace: cntFace = getForeheadEllipse(vctCntJaw[i][0], vctCntJaw[i][16], vctCntJaw[i][8], config::kNumPointsInHalfEllipse + vctCntJaw[i].size()); // The ellipse is drawn clock-wise, but jaw contour points goes // vice versa, so it's necessary to push cntJaw from the end // to the begin using a reverse iterator: std::copy(vctCntJaw[i].crbegin(), vctCntJaw[i].crend(), std::back_inserter(cntFace)); // Storing the face contour in another vector: vctFaceContours.push_back(cntFace); } } }; // GAPI subgraph functions inline cv::GMat unsharpMask(const cv::GMat &src, const int sigma, const float strength); inline cv::GMat mask3C(const cv::GMat &src, const cv::GMat &mask); } // namespace custom // Functions implementation: // Returns an angle (in degrees) between a line given by two Points and // the horison. Note that the result depends on the arguments order: inline int custom::getLineInclinationAngleDegrees(const cv::Point &ptLeft, const cv::Point &ptRight) { const cv::Point residual = ptRight - ptLeft; if (residual.y == 0 && residual.x == 0) return 0; else return toIntRounded(atan2(toDouble(residual.y), toDouble(residual.x)) * 180.0 / M_PI); } // Approximates a forehead by half-ellipse using jaw points and some geometry // and then returns points of the contour; "capacity" is used to reserve enough // memory as there will be other points inserted. inline Contour custom::getForeheadEllipse(const cv::Point &ptJawLeft, const cv::Point &ptJawRight, const cv::Point &ptJawLower, const size_t capacity = 0) { Contour cntForehead; cntForehead.reserve(std::max(capacity, config::kNumPointsInHalfEllipse)); // The point amid the top two points of a jaw: const cv::Point ptFaceCenter((ptJawLeft + ptJawRight) / 2); // This will be the center of the ellipse. // The angle between the jaw and the vertical: const int angFace = getLineInclinationAngleDegrees(ptJawLeft, ptJawRight); // This will be the inclination of the ellipse // Counting the half-axis of the ellipse: const double jawWidth = cv::norm(ptJawLeft - ptJawRight); // A forehead width equals the jaw width, and we need a half-axis: const int axisX = toIntRounded(jawWidth / 2.0); const double jawHeight = cv::norm(ptFaceCenter - ptJawLower); // According to research, in average a forehead is approximately 2/3 of // a jaw: const int axisY = toIntRounded(jawHeight * 2 / 3.0); // We need the upper part of an ellipse: static constexpr int kAngForeheadStart = 180; static constexpr int kAngForeheadEnd = 360; cv::ellipse2Poly(ptFaceCenter, cv::Size(axisX, axisY), angFace, kAngForeheadStart, kAngForeheadEnd, config::kAngDelta, cntForehead); return cntForehead; } // Approximates the lower eye contour by half-ellipse using eye points and some // geometry and then returns points of the contour; "capacity" is used // to reserve enough memory as there will be other points inserted. inline Contour custom::getEyeEllipse(const cv::Point &ptLeft, const cv::Point &ptRight, const size_t capacity = 0) { Contour cntEyeBottom; cntEyeBottom.reserve(std::max(capacity, config::kNumPointsInHalfEllipse)); const cv::Point ptEyeCenter((ptRight + ptLeft) / 2); const int angle = getLineInclinationAngleDegrees(ptLeft, ptRight); const int axisX = toIntRounded(cv::norm(ptRight - ptLeft) / 2.0); // According to research, in average a Y axis of an eye is approximately // 1/3 of an X one. const int axisY = axisX / 3; // We need the lower part of an ellipse: static constexpr int kAngEyeStart = 0; static constexpr int kAngEyeEnd = 180; cv::ellipse2Poly(ptEyeCenter, cv::Size(axisX, axisY), angle, kAngEyeStart, kAngEyeEnd, config::kAngDelta, cntEyeBottom); return cntEyeBottom; } //This function approximates an object (a mouth) by two half-ellipses using // 4 points of the axes' ends and then returns points of the contour: inline Contour custom::getPatchedEllipse(const cv::Point &ptLeft, const cv::Point &ptRight, const cv::Point &ptUp, const cv::Point &ptDown) { // Shared characteristics for both half-ellipses: const cv::Point ptMouthCenter((ptLeft + ptRight) / 2); const int angMouth = getLineInclinationAngleDegrees(ptLeft, ptRight); const int axisX = toIntRounded(cv::norm(ptRight - ptLeft) / 2.0); // The top half-ellipse: Contour cntMouthTop; const int axisYTop = toIntRounded(cv::norm(ptMouthCenter - ptUp)); // We need the upper part of an ellipse: static constexpr int angTopStart = 180; static constexpr int angTopEnd = 360; cv::ellipse2Poly(ptMouthCenter, cv::Size(axisX, axisYTop), angMouth, angTopStart, angTopEnd, config::kAngDelta, cntMouthTop); // The bottom half-ellipse: Contour cntMouth; const int axisYBot = toIntRounded(cv::norm(ptMouthCenter - ptDown)); // We need the lower part of an ellipse: static constexpr int angBotStart = 0; static constexpr int angBotEnd = 180; cv::ellipse2Poly(ptMouthCenter, cv::Size(axisX, axisYBot), angMouth, angBotStart, angBotEnd, config::kAngDelta, cntMouth); // Pushing the upper part to vctOut cntMouth.reserve(cntMouth.size() + cntMouthTop.size()); std::copy(cntMouthTop.cbegin(), cntMouthTop.cend(), std::back_inserter(cntMouth)); return cntMouth; } inline cv::GMat custom::unsharpMask(const cv::GMat &src, const int sigma, const float strength) { cv::GMat blurred = cv::gapi::medianBlur(src, sigma); cv::GMat laplacian = custom::GLaplacian::on(blurred, CV_8U); return (src - (laplacian * strength)); } inline cv::GMat custom::mask3C(const cv::GMat &src, const cv::GMat &mask) { std::tuple tplIn = cv::gapi::split3(src); cv::GMat masked0 = cv::gapi::mask(std::get<0>(tplIn), mask); cv::GMat masked1 = cv::gapi::mask(std::get<1>(tplIn), mask); cv::GMat masked2 = cv::gapi::mask(std::get<2>(tplIn), mask); return cv::gapi::merge3(masked0, masked1, masked2); } int main(int argc, char** argv) { cv::CommandLineParser parser(argc, argv, "{ help h || print the help message. }" "{ facepath f || a path to a Face detection model file (.xml).}" "{ facedevice |GPU| the face detection computation device.}" "{ landmpath l || a path to a Landmarks detection model file (.xml).}" "{ landmdevice |CPU| the landmarks detection computation device.}" "{ input i || a path to an input. Skip to capture from a camera.}" "{ boxes b |false| set true to draw face Boxes in the \"Input\" window.}" "{ landmarks m |false| set true to draw landMarks in the \"Input\" window.}" ); parser.about("Use this script to run the face beautification" " algorithm on G-API."); if (argc == 1 || parser.has("help")) { parser.printMessage(); return 0; } cv::namedWindow(config::kWinFaceBeautification, cv::WINDOW_NORMAL); cv::namedWindow(config::kWinInput, cv::WINDOW_NORMAL); // Parsing input arguments const std::string faceXmlPath = parser.get("facepath"); const std::string faceBinPath = getWeightsPath(faceXmlPath); const std::string faceDevice = parser.get("facedevice"); const std::string landmXmlPath = parser.get("landmpath"); const std::string landmBinPath = getWeightsPath(landmXmlPath); const std::string landmDevice = parser.get("landmdevice"); // The flags for drawing/not drawing face boxes or/and landmarks in the // \"Input\" window: const bool flgBoxes = parser.get("boxes"); const bool flgLandmarks = parser.get("landmarks"); // To provide this opportunity, it is necessary to check the flags when // compiling a graph // Declaring a graph // Streaming-API version of a pipeline expression with a lambda-based // constructor is used to keep all temporary objects in a dedicated scope. cv::GComputation pipeline([=]() { cv::GMat gimgIn; // Infering cv::GMat faceOut = cv::gapi::infer(gimgIn); GArrayROI garRects = custom::GFacePostProc::on(faceOut, gimgIn, config::kConfThresh); cv::GArray garElems; cv::GArray garJaws; cv::GArray landmOut = cv::gapi::infer( garRects, gimgIn); std::tie(garElems, garJaws) = custom::GLandmPostProc::on(landmOut, garRects); cv::GArray garElsConts; cv::GArray garFaceConts; std::tie(garElsConts, garFaceConts) = custom::GGetContours::on(garElems, garJaws); // Masks drawing // All masks are created as CV_8UC1 cv::GMat mskSharp = custom::GFillPolyGContours::on(gimgIn, garElsConts); cv::GMat mskSharpG = cv::gapi::gaussianBlur(mskSharp, config::kGKernelSize, config::kGSigma); cv::GMat mskBlur = custom::GFillPolyGContours::on(gimgIn, garFaceConts); cv::GMat mskBlurG = cv::gapi::gaussianBlur(mskBlur, config::kGKernelSize, config::kGSigma); // The first argument in mask() is Blur as we want to subtract from // BlurG the next step: cv::GMat mskBlurFinal = mskBlurG - cv::gapi::mask(mskBlurG, mskSharpG); cv::GMat mskFacesGaussed = mskBlurFinal + mskSharpG; cv::GMat mskFacesWhite = cv::gapi::threshold(mskFacesGaussed, 0, 255, cv::THRESH_BINARY); cv::GMat mskNoFaces = cv::gapi::bitwise_not(mskFacesWhite); cv::GMat gimgBilat = custom::GBilatFilter::on(gimgIn, config::kBSize, config::kBSigmaCol, config::kBSigmaSp); cv::GMat gimgSharp = custom::unsharpMask(gimgIn, config::kUnshSigma, config::kUnshStrength); // Applying the masks // Custom function mask3C() should be used instead of just gapi::mask() // as mask() provides CV_8UC1 source only (and we have CV_8U3C) cv::GMat gimgBilatMasked = custom::mask3C(gimgBilat, mskBlurFinal); cv::GMat gimgSharpMasked = custom::mask3C(gimgSharp, mskSharpG); cv::GMat gimgInMasked = custom::mask3C(gimgIn, mskNoFaces); cv::GMat gimgBeautif = gimgBilatMasked + gimgSharpMasked + gimgInMasked; // Drawing face boxes and landmarks if necessary: cv::GMat gimgTemp; if (flgLandmarks == true) { cv::GMat gimgTemp2 = custom::GPolyLines::on(gimgIn, garFaceConts, config::kClosedLine, config::kClrYellow); gimgTemp = custom::GPolyLines::on(gimgTemp2, garElsConts, config::kClosedLine, config::kClrYellow); } else { gimgTemp = gimgIn; } cv::GMat gimgShow; if (flgBoxes == true) { gimgShow = custom::GRectangle::on(gimgTemp, garRects, config::kClrGreen); } else { // This action is necessary because an output node must be a result of // some operations applied to an input node, so it handles the case // when it should be nothing to draw gimgShow = cv::gapi::copy(gimgTemp); } return cv::GComputation(cv::GIn(gimgIn), cv::GOut(gimgBeautif, gimgShow)); }); // Declaring IE params for networks auto faceParams = cv::gapi::ie::Params { faceXmlPath, faceBinPath, faceDevice }; auto landmParams = cv::gapi::ie::Params { landmXmlPath, landmBinPath, landmDevice }; auto networks = cv::gapi::networks(faceParams, landmParams); // Declaring custom and fluid kernels have been used: auto customKernels = cv::gapi::kernels(); auto kernels = cv::gapi::combine(cv::gapi::core::fluid::kernels(), customKernels); // Now we are ready to compile the pipeline to a stream with specified // kernels, networks and image format expected to process auto stream = pipeline.compileStreaming(cv::GMatDesc{CV_8U,3, cv::Size(1280,720)}, cv::compile_args(kernels, networks)); // Setting the source for the stream: if (parser.has("input")) { stream.setSource(cv::gapi::wip::make_src (parser.get("input"))); } else { stream.setSource(cv::gapi::wip::make_src (0)); } // Declaring output variables cv::Mat imgShow; cv::Mat imgBeautif; // Streaming: stream.start(); while (stream.running()) { auto out_vector = cv::gout(imgBeautif, imgShow); if (!stream.try_pull(std::move(out_vector))) { // Use a try_pull() to obtain data. // If there's no data, let UI refresh (and handle keypress) if (cv::waitKey(1) >= 0) break; else continue; } cv::imshow(config::kWinInput, imgShow); cv::imshow(config::kWinFaceBeautification, imgBeautif); } return 0; }