计算机视觉图像处理深度学习机器学习【免费下载链接】opencv_contribRepository for OpenCVs extra modules项目地址https://gitcode.com/gh_mirrors/op/opencv_contrib点击查看免费下载导读本教程基于 OpenCV 的扩展模块 modules/gapi/tutorials/face_beautification/face_beautification.markdown 及其配套示例源码 face_beautification.cpp完整讲解如何在 G-APIGraph API中构建一条深度学习 传统图像处理混合的视频流处理管线用 SSD 人脸检测器定位人脸、用关键点检测器提取 35 个面部特征点再由传统滤波器生成三张互不重叠的掩码最终组合出皮肤平滑、五官保持清晰的美化结果。读完本文你将掌握cv::gapi::infer()在管线内触发网络推断的两种用法、自定义 G-API 算子kernel的编写方式、compileStreaming()流式编译与try_pull()结果拉取的完整实战流程并理解如何将推理后端OpenVINO™ IE与 Fluid 后端组合以提升吞吐。人脸美化示例效果左侧为原始输入中间为算法中间结果绿色框为检测到的人脸框、黄色线为面部元素与下颌轮廓右侧为美化输出。一、算法总体设计DNN 定位 传统滤波组合1.1 核心思想人脸美化算法的总体思路是让面部皮肤更平滑同时保留眼睛、嘴巴等五官的对比度与细节。它先用 DNN 推断定位出面部的各个区域再对不同区域施加不同的滤波器最后用基础图像算术把各部分组合为最终结果。这是一种深度学习与传统计算机视觉结合的典型混合管线——DNN 只负责理解图像哪里是脸、哪里是眼睛像素级的处理仍然交给成熟、可控的传统算子。整个管线可以抽象为下图所示的有向图源自原教程的流程描述输入帧分别流向三条支路——两条滤波支路unsharp mask 锐化、bilateral 双边滤波磨皮和一条 DNN 推断支路人脸检测 → 逐个人脸的关键点检测 → 掩码生成最后通过三次乘法与一次加法汇合输出。简要描述算法的数据流输入图像 \(I\) 分别送入 unsharp mask 滤波器与双边滤波器得到 \(U\)锐化结果与 \(L\)平滑结果输入图像 \(I\) 送入基于 SSD 的人脸检测器SSD 输出一个 \([1 \times 1 \times 200 \times 7]\) 的 blob被解析并转换为一个人脸矩形数组每个人脸矩形再送入关键点landmarks检测器基于每个人脸的关键点生成三张图像掩码背景掩码 \(b\)指示原图中哪些区域保持原样面部特征掩码 \(p\)标识需要保留锐化的区域如眼睛、嘴巴、眉毛、鼻子面部皮肤掩码 \(s\)标识需要模糊平滑的区域最终结果 \(O\) 按公式 \(O b \cdot I p \cdot U s \cdot L\) 计算得出。由于三张掩码互不重叠且相加覆盖全图最终只需把三张已掩码图像直接相加即可得到输出无需额外合成逻辑。掩码之间无交集是这套设计的关键约束。1.2 仅靠 35 个关键点生成掩码为什么并不简单每个面部分辨出的特征点只有 35 个其中 18 个是面部元素——眼睛、眉毛、鼻子、嘴巴17 个是下颌轮廓。要用这么有限的一组离散点生成覆盖整张脸的连续闭合轮廓无法直接使用原始点集而必须做几何插值/拟合。原教程特别指出把关键点转换成闭合轮廓这一步是整个后处理中最不 trivial 的部分也是本教程重点讲解的内容见下文自定义操作章节。在实际代码中这一步由custom::GGetContours算子完成见 face_beautification.cpp它把 18 个面部元素关键点和 17 个下颌点加工成眼睛、鼻子、嘴巴、额头等闭合轮廓。二、运行环境与前置条件2.1 硬件与操作系统示例对运行环境的要求如下PC 运行 GNU/Linux 或 Microsoft WindowsmacOS 官方声明支持但未经测试安装有 OpenVINO™ Toolkit 的 OpenCV 4.2 或更高版本opencv_contrib仓库当前版本的 G-API 模块要求版本不低于 4.2见 gapi.markdown 中的兼容性标注建议同时安装 Intel® TBB 以获得更好的并行性能非必需仅是加分项需要 G-API 模块在编译时启用了 Inference EngineIE后端——示例源码的兜底分支会提示 This tutorial code requires G-API module with Inference Engine backend to run见 face_beautification.cpp。2.2 需要的模型拓扑示例使用 OpenVINO™ Open Model Zoo 中的两个拓扑拓扑名称作用输出说明face-detection-adas-0001人脸检测SSD输出 \([1, 1, N, 7]\) blobN 为检测框数量最多 200facial-landmarks-35-adas-0002面部关键点检测每张脸输出 35 个关键点前 18 个为面部元素后 17 个为下颌轮廓两个模型均以.xml网络结构与.bin权重文件形式提供。示例运行时会从命令行接收两个.xml路径并自动推导出对应的.bin路径——源码中的getWeightsPath()函数见 face_beautification.cpp利用.xml与.bin文件名的最后三个字符差异完成替换face.xml→face.bin。2.3 编译与运行示例示例源码位于 modules/gapi/samples/tutorial_code/face_beautification/face_beautification.cpp随 G-API 模块一起编译编译 G-API 时需要开启 OpenVINO 支持。运行时通过命令行参数指定模型与输入完整参数表如下源自源码中的config::kParserOptions见 face_beautification.cpp参数缩写默认值含义--help-h—打印帮助信息--facepath-f必填人脸检测模型.xml文件路径--facedevice—GPU人脸检测推断设备--landmpath-l必填关键点检测模型.xml文件路径--landmdevice—CPU关键点检测推断设备--input-i摄像头输入视频文件路径缺省则从摄像头索引 0读取--boxes-bfalse是否在输入窗口绘制人脸框--landmarks-mfalse是否在输入窗口绘制关键点/轮廓--streaming-strue是否启用流式pipelined模式--performance-pfalse性能模式禁用输出显示仅统计帧率一个典型的调用示例以视频文件为输入、启用流式模式并叠加绘制人脸框与轮廓./face_beautification \ -f/path/to/face-detection-adas-0001.xml \ -l/path/to/facial-landmarks-35-adas-0002.xml \ -i/path/to/video.mp4 \ -b -m程序结束时会在控制台输出处理的总帧数、总耗时与平均 FPS源码使用Avg计时辅助类统计见 face_beautification.cpp。三、构建 G-API 管线从网络声明到图描述3.1 用 G_API_NET 声明深度学习拓扑示例使用两个 DNN 检测器每个网络均一个输入、一个输出。在 G-API 中网络通过宏G_API_NET()声明见 face_beautification.cppG_API_NET(FaceDetector, cv::GMat(cv::GMat), face_detector); G_API_NET(LandmDetector, cv::GMat(cv::GMat), landm_detector);宏的三个参数分别是网络类型的名字FaceDetector/LandmDetector、网络的签名输入cv::GMat、输出cv::GMat以及网络中使用的唯一字符串标识。声明之后这两个类型就可以作为cv::gapi::infer()的模板参数使用。关于网络声明的更完整讲解可参考同模块的 Face Analytics pipeline 教程 interactive_face_detection.markdown。3.2 描述处理图两种 infer 用法图的主体在cv::GComputation的 lambda 构造函数中声明见 face_beautification.cpp。该图混合了三类节点G-API 标准算子、用户自定义算子命名空间custom::以及 DNN 推断。通用函数cv::gapi::infer()用于在管线内部触发推断需要推断的网络作为模板参数传入。示例同时使用了它的两个版本帧级frame-oriented推断——用于在整帧上检测人脸cv::GMat gimgIn; // 输入图像 cv::GMat faceOut cv::gapi::infercustom::FaceDetector(gimgIn); // 人脸检测ROI 列表级ROI-list oriented推断——用于在每个人脸矩形上分别运行关键点检测输出为每张脸一份关键点的数组cv::GArraycv::GMat landmOut cv::gapi::infercustom::LandmDetector(garRects, gimgIn);注意这里infer的输入不仅有人脸矩形数组garRects还有原始图像gimgIn——因为关键点检测需要从原图裁剪 ROIG-API 会按 ROI 列表自动完成裁剪、逐 ROI 推断与结果组装。3.3 图的完整拓扑与输出在两次推断之后管线依次连接后处理与掩码生成算子最终返回cv::GComputation(cv::GIn(gimgIn), cv::GOut(...))声明gimgIn为唯一输入、五个输出美化图、原始图副本、人脸轮廓、面部元素轮廓、人脸框数组。把图中的自定义算子串联起来完整的数据流为inferFaceDetector → GFacePostProc解析人脸框 → inferLandmDetector逐 ROI 关键点推断 → GLandmPostProc坐标反归一化 → GGetContours关键点→闭合轮廓 → GFillPolyGContours gaussianBlur生成锐化/模糊掩码 → GBilatFilter双边滤波 unsharpMask锐化 → mask3C 分别掩码 → 相加 → 输出图的中间输出还包括garFaceConts、garElsConts、garRects供--landmarks/--boxes选项在输入窗口中叠加绘制轮廓与人脸框。3.4 用 G-API 表达 Unsharp MaskUnsharp mask \(U\) 对图像 \(I\) 的定义为\[U I - s \cdot L(M(I)),\]其中 \(M()\) 是中值滤波\(L()\) 是 Laplacian拉普拉斯算子\(s\) 是锐化强度系数。G-API 没有直接提供这一复合函数但可以很自然地用既有算子组合表达见 face_beautification.cppinline cv::GMat custom::unsharpMask(const cv::GMat src, const int sigma, const float strength) { cv::GMat blurred cv::gapi::medianBlur(src, sigma); // M(I) cv::GMat laplacian custom::GLaplacian::on(blurred, CV_8U); // L(M(I)) return (src - (laplacian * strength)); // I - s*L(M(I)) }这段代码是一个以 G-API 类型定义的普通 C 函数而不是一个独立的算子。用户可以编写这样的辅助函数来简化图构建当它被调用时只是把相关节点拼装进当前管线——这是 G-API 图构建中非常实用的一种组织方式。示例中的mask3C()对三通道图像按单通道掩码逐通道mask后merge3合并也是同样的模式见 face_beautification.cpp。四、自定义操作从推断结果到可用掩码人脸美化图大量使用自定义算子。下面按数据流顺序逐个讲解最关键的 kernelG-API 中定义算子与实现 kernel 的通用机制可参考 G-API Kernel API 的官方说明本仓库中 gapi.hpp 等头文件也提供了相关声明。4.1 自定义算子的声明示例用G_TYPED_KERNEL声明单输出算子、用G_TYPED_KERNEL_M声明多输出算子。例如关键点后处理GGetContours的声明见 face_beautification.cppusing TplFaces_FaceElements std::tuplecv::GArrayContour, cv::GArrayContour; G_TYPED_KERNEL_M(GGetContours, TplFaces_FaceElements (cv::GArrayLandmarks, cv::GArrayContour), custom.faceb12n.getContours)其中Contour即std::vectorcv::Point点的集合Landmarks即std::vectorcv::Point关键点数组。每个 kernel 通过字符串 ID如custom.faceb12n.getContours唯一标识并须实现outMeta()以声明输出数据的元信息尺寸、类型等。kernel 本体则用GAPI_OCV_KERNEL实现——其run()内部直接调用 OpenCV 经典 API。4.2 人脸检测器后处理解析 [1,1,N,7] blob人脸检测器输出一个形状为 \([1, 1, N, 7]\) 的 blob其中 N 为检测到的框数。每个框由 7 个浮点元素描述[image_id, label, confidence, x_min, y_min, x_max, y_max]。后处理 kernel 负责把这一 blob 解析成std::vectorcv::Rect见 face_beautification.cppGAPI_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 cv::Rect borders({0, 0}, inFrame.size()); outFaces.clear(); const int numOfDetections inDetectResult.size[2]; const float *data inDetectResult.ptrfloat(); for (int i 0; i numOfDetections; i) { const float faceId data[i * kObjectSize 0]; if (faceId 0.f) 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 * inFrame.size().width), toIntRounded(top * inFrame.size().height)); cv::Point br(toIntRounded(right * inFrame.size().width), toIntRounded(bottom * inFrame.size().height)); outFaces.push_back(cv::Rect(tl, br) borders); // 与帧边界求交 } } } };这段实现里有三个值得注意的工程细节置信度阈值过滤config::kConfThresh 0.7f只有置信度高于该值的检测框才会被保留用于避免检测器误报见 face_beautification.cpp坐标归一化还原SSD 输出的是 01 的归一化坐标需分别乘以图像宽、高得到真实像素坐标越界处理检测器偶尔会返回超出图像范围的矩形。这里先用cv::Rect borders({0,0}, inFrame.size())构造帧边界再通过operator()求交集确保送入关键点检测的 ROI 一定落在帧内——否则后续推断会出错。4.3 关键点后处理坐标反归一化facial-landmarks-35-adas-0002为每张脸输出 35 个关键点前 18 个是面部元素眼睛、眉毛、鼻子、嘴巴后 17 个是下颌轮廓。这些点是相对于输入 ROI 归一化的浮点坐标而非原图坐标因此GCPULandmPostProc首先要做反归一化见 face_beautification.cpp把每个关键点的坐标乘上对应 ROI 的宽高再加上 ROI 的左上角坐标还原为原图中的像素坐标cv::Point pt cv::Point(toIntRounded(data[j] * vctRects[i].width), toIntRounded(data[j1] * vctRects[i].height)) vctRects[i].tl();对 18 个面部元素关键点与 17 个下颌点分别执行上述换算后输出两组数组vctPtsFaceElems每张脸的面部元素关键点与vctCntJaw每张脸的下颌轮廓点。4.4 关键点→闭合轮廓的几何插值检测到的关键点本身不足以直接生成掩码——掩码需要由闭合轮廓围成的区域因此要应用几何插值。这一步由GCPUGetContours完成见 face_beautification.cpp它输出两类轮廓面部元素轮廓数组要锐化的区域每张脸 4 个——左眼、右眼、鼻子、嘴巴整脸轮廓数组要平滑的区域每张脸 1 个额头椭圆 下颌点拼接。1眼睛轮廓基于两个眼角点的半椭圆眼睛下轮廓由getEyeEllipse()用半椭圆近似见 face_beautification.cppinline Contour custom::getEyeEllipse(const cv::Point ptLeft, const cv::Point ptRight) { Contour cntEyeBottom; const cv::Point ptEyeCenter((ptRight ptLeft) / 2); // 椭圆中心两眼角中点 const int angle getLineInclinationAngleDegrees(ptLeft, ptRight); // 椭圆倾角 const int axisX toIntRounded(cv::norm(ptRight - ptLeft) / 2.0); // X 半轴眼宽一半 const int axisY axisX / 3; // Y 半轴按平均眼高约为眼长 1/3假设 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; }只凭两个点函数通过cv::ellipse2Poly()就能近似出眼区轮廓所需参数全部由几何关系推导椭圆中心与 X 半轴由两个眼角点直接算出中心为中点、半轴为眼宽一半Y 半轴按经验假设平均眼高约为眼长的 1/3计算起始/结束角度0° 与 180°即只取椭圆的下半部分参考cv::ellipse()文档的角度约定角度增量决定轮廓点的疏密示例取config::kAngDelta 1见 face_beautification.cpp轴倾角即眼睛在图像中的倾斜角度。倾角计算中atan2()的选择custom::getLineInclinationAngleDegrees()见 face_beautification.cpp是值得强调的细节相比atan()atan2(y, x)会根据x、y的符号返回负角度从而在脸部上下颠倒只要点按正确顺序传入时也能得到正确的倾角避免椭圆方向错误。在GCPUGetContours::run()中左眼的下半椭圆加上左眉三个点关键点索引 12、13、14按顺时针插入即构成完整的左眼轮廓右眼同理椭圆用关键点 2、3眉点用 15、16、17见 face_beautification.cpp。2鼻子与嘴巴轮廓鼻子直接按顺时针顺序取 4 个关键点索引 4、7、5、6作为闭合轮廓嘴巴由getPatchedEllipse()用两个半椭圆拼接——上唇用上、下、左、右 4 个端点索引 811中的上半部分下唇用下半部分共享中心与 X 半轴见 face_beautification.cpp。3额头轮廓基于下颌三点的半椭圆getForeheadEllipse()只利用下颌点来近似额头见 face_beautification.cpp由于检测到的关键点中没有额头点必须基于下颌的三个点——最左、最右、最低——来构造半椭圆inline Contour custom::getForeheadEllipse(const cv::Point ptJawLeft, const cv::Point ptJawRight, const cv::Point ptJawLower) { Contour cntForehead; const cv::Point ptFaceCenter((ptJawLeft ptJawRight) / 2); // 椭圆中心 const int angFace getLineInclinationAngleDegrees(ptJawLeft, ptJawRight); // 倾角 const double jawWidth cv::norm(ptJawLeft - ptJawRight); const int axisX toIntRounded(jawWidth / 2.0); // 额宽颌宽 const double jawHeight cv::norm(ptFaceCenter - ptJawLower); const int axisY toIntRounded(jawHeight * 2 / 3.0); // 额高≈颌高 2/3 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; }其几何假设有两个额宽 颌宽由最左、最右下颌点之间的距离直接得到 X 半轴额高 ≈ 颌高的 2/3Y 方向没有直接的数据点于是以额高约为颌高三分之二的经验比例推算其中颌高由人脸中心左右下颌点中点与最低下颌点之间的距离求得。取椭圆上半部分180°360°作为额头轮廓后代码再把下颌点逆序crbegin()/crend()反向迭代器插入——因为椭圆是按顺时针生成的而下颌点集是逆时针排列的需要统一方向才能构成闭合的整脸轮廓见 face_beautification.cpp。4.5 绘制三张互不重叠的掩码拿到全部轮廓之后即可绘制掩码。核心代码如下见 face_beautification.cppcv::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); 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); // 取反得背景各掩码的计算步骤与原教程一一对应锐化掩码mskSharpG先填充需要锐化的轮廓眼睛、眉毛、鼻子、嘴巴再用高斯滤波模糊边缘得到柔和的锐化区域掩码双边掩码mskBlurFinal先填充所有整脸轮廓并模糊然后减去与锐化掩码相交的区域——用cv::gapi::mask(mskBlurG, mskSharpG)把锐化区域从模糊掩码中挖掉从而保证两张掩码互不重叠背景掩码mskNoFaces把前两张掩码相加用cv::gapi::threshold()把所有非零像素置为 255再经cv::gapi::bitwise_not取反得到没有人脸区域的背景掩码。填充轮廓的 kernel 是GCPUFillPolyGContours——它先把输出清为全黑cv::Scalar(0)再用cv::fillPoly()以白色填充轮廓见 face_beautification.cpp。相关配置常量见 face_beautification.cpp包括高斯核kGKernelSize(5, 5)、kGSigma 0.0双边滤波kBSize 9、kBSigmaCol 30.0、kBSigmaSp 30.0unsharp 的kUnshSigma 3、kUnshStrength 0.7f轮廓角度增量kAngDelta 1。掩码与图像的合成同样在图中完成mask3C()把美化候选图双边滤波结果、锐化图unsharp 结果与原图分别按三张掩码逐通道mask最后三者相加即得最终输出gimgBeautif见 face_beautification.cpp。五、配置与运行管线图表达完成之后需要编译才能真正在真实数据上运行。编译阶段是 G-API 框架真正确定用哪些 kernel、哪些网络的阶段全部通过**编译参数compile arguments**配置。5.1 DNN 参数绑定 IE 后端与网络示例用 OpenVINO™ Toolkit 的 Inference Engine 后端做推断配置方式如下见 face_beautification.cppauto faceParams cv::gapi::ie::Paramscustom::FaceDetector { faceXmlPath, // 网络结构 .xml faceBinPath, // 权重 .bin faceDevice // 推断设备GPU / CPU 等 }; auto landmParams cv::gapi::ie::Paramscustom::LandmDetector { landmXmlPath, landmBinPath, landmDevice };每个cv::gapi::ie::Params对象与模板参数指定的网络一一对应——模板类型必须使用开头在G_API_NET()中定义的那个网络类型custom::FaceDetector、custom::LandmDetector。然后网络参数被包装进cv::gapi::NetworkPackageauto networks cv::gapi::networks(faceParams, landmParams);设备参数来自命令行人脸检测默认GPU关键点检测默认CPU。关于管线配置的更多细节可参考 Face Analytics Pipeline 教程的配置章节interactive_face_detection.markdown。5.2 Kernel 包自定义 kernel Fluid 后端示例使用大量自定义 kernel同时用 Fluid 后端为 G-API 标准算子优化内存占用。最终 kernel 包按如下方式组合见 face_beautification.cppauto customKernels cv::gapi::kernelscustom::GCPUBilateralFilter, custom::GCPULaplacian, custom::GCPUFillPolyGContours, custom::GCPUPolyLines, custom::GCPURectangle, custom::GCPUFacePostProc, custom::GCPULandmPostProc, custom::GCPUGetContours(); auto kernels cv::gapi::combine(cv::gapi::core::fluid::kernels(), customKernels);其中cv::gapi::core::fluid::kernels()提供 Fluid 后端的标准算子实现用于逐像素/局部窗口类算子内存友好cv::gapi::combine()把两套 kernel 合并。此外--landmarks与--boxes选项依赖的两个绘制 kernelGCPUPolyLines、GCPURectangle也在包内。5.3 编译流式管线Streaming 模式G-API 在流式Streaming模式下会为视频流优化执行方式——这是相对逐帧调用apply()的串行模式的性能优势所在。编译代码如下见 face_beautification.cppcv::GStreamingCompiled stream pipeline.compileStreaming(cv::compile_args(kernels, networks));随后为流指定输入源命令行给了--input则打开视频文件否则打开摄像头索引 0见 face_beautification.cppif (parser.has(input)) { stream.setSource(cv::gapi::wip::make_srccv::gapi::wip::GCaptureSource( parser.getcv::String(input))); } else { stream.setSource(cv::gapi::wip::make_srccv::gapi::wip::GCaptureSource(0)); }5.4 运行流式管线并拉取结果运行流式管线只需要三件事指定输入源、调用cv::GStreamingCompiled::start()、然后持续拉取处理结果见 face_beautification.cppauto out_vector cv::gout(imgBeautif, imgShow, vctFaceConts, vctElsConts, vctRects); stream.start(); avg.start(); while (stream.running()) { if (!stream.try_pull(std::move(out_vector))) { // 暂无数据时让 UI 刷新并处理按键 if (cv::waitKey(1) 0) break; else continue; } frames; // 按需绘制关键点轮廓与人脸框 if (flgLandmarks true) { cv::polylines(imgShow, vctFaceConts, true, config::kClrYellow); cv::polylines(imgShow, vctElsConts, true, config::kClrYellow); } if (flgBoxes true) for (auto rect : vctRects) cv::rectangle(imgShow, rect, config::kClrGreen); cv::imshow(config::kWinInput, imgShow); cv::imshow(config::kWinFaceBeautification, imgBeautif); }这里使用了非阻塞的try_pull()当管线数据尚未就绪时立即返回让主线程有机会刷新 GUI 与响应按键cv::waitKey(1)从而实现边处理边显示的实时交互。拉取到的输出经cv::gout(...)绑定到 5 个输出变量与cv::GComputation构造时声明的cv::GOut(...)一一对应。性能模式下--performance则改用阻塞式stream.pull()并跳过所有显示只统计 FPS。5.5 串行模式逐帧 apply 对照作为对照示例还保留了串行非流水线模式--streamingfalse。该模式用cv::GCompiledcv::VideoCapture逐帧处理见 face_beautification.cpp首帧到来时用pipeline.compile(cv::descr_of(img), ...)按实际图像格式完成图编译编译发生在运行时、首帧之后才启动计时器此后每帧直接调用pipeline.apply(...)执行。两种模式共用同一份图描述与编译参数区别仅在于执行调度方式——这正好为文档结论中流式模式相对串行模式获得约 2.7 倍加速的性能对比提供了可复现的实验设置。六、结果与结论本教程的目标有二一是展示 OpenCV 4.2 引入的 G-API 新特性图式编程、流式编译、DNN 推断入图二是让读者建立对示例人脸美化算法本身的基本理解。从算法效果看输出图像中面部皮肤被明显平滑双边滤波而眼睛、眉毛、鼻子、嘴巴等区域保持锐利unsharp mask整体观感自然见开头的示例效果图。从性能看在测试机Intel® Core™ i7-8700上G-API 优化的流式视频管线相对串行非流水线版本获得了约2.7 倍的加速——对这一体量的复杂图而言合理的流水线调度能带来接近 3 倍的吞吐提升。这也正是把图描述与执行调度解耦的 G-API 的价值所在同一张图仅在编译阶段切换到 Streaming 模式就能自动获得跨帧/跨节点的重叠执行。可继续深入的方向ROI 整形与推断精度关键点检测器要求方形 ROI而人脸检测器通常给出非方矩形。若放任后端自行压缩矩形某些情况下会损失推断精度改为外接方形 ROI又可能越界。原教程在注释中提出过一种改进思路先为每个矩形求外接方形、统计越界幅度再用纯色边框按该幅度 padding 原图从而安全地给关键点检测器喂方形 ROI。参数调优与自适应高斯模糊、unsharp mask、双边滤波等的参数如kGKernelSize、kBSigmaCol、kUnshStrength目前是固定常量可针对不同分辨率/场景研究更优取值或按输入尺寸自动缩放。本文对应的完整示例源码位于 modules/gapi/samples/tutorial_code/face_beautification/face_beautification.cpp原始教程文档位于 face_beautification.markdown同系列的 G-API 教程总览见 gapi.markdownFace Analytics pipeline含网络声明、图构建、管线配置与运行的系统讲解见 interactive_face_detection.markdown。赞分享计算机视觉图像处理深度学习机器学习【免费下载链接】opencv_contribRepository for OpenCVs extra modules项目地址https://gitcode.com/gh_mirrors/op/opencv_contrib点击查看免费下载相关推荐7天入门Accord.NET计算机视觉从人脸检测到全景拼接的完整指南7天入门Accord.NET计算机视觉从人脸检测到全景拼接的完整指南 Accord.NET是一个功能强大的.NET机器学习框架提供了丰富的计算机视觉、统计分机器学习计算机视觉图像处理强化学习终极指南简单快速定制你的macOS光标主题终极指南简单快速定制你的macOS光标主题 Mousecape是一款专为macOS设计的免费开源光标管理器通过调用CoreGraphics API实现非侵入桌面应用Bokeh OpenCV 实时人脸检测示例全解析基于 Haar Cascade 的视频流可视化Bokeh OpenCV 实时人脸检测示例全解析基于 Haar Cascade 的视频流可视化 导读 本文围绕 Bokeh 仓库中的 examples/s数据可视化图表库上一篇数据之星轻量级的Web实时应用新秀 - DataStar下一篇终极风扇控制指南如何用FanControl让电脑风扇更智能、更安静创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
