最近在整理手头一个人脸识别项目趁热把过程沉淀下来。这个系列打算用 Qt C OpenCV 做一套完整的桌面端人脸识别工具从环境搭建、人脸检测、界面交互一路做到特征提取和身份匹配。作为系列第一篇我只聊“地基”部分怎么把开发环境搭到能稳定复现怎么把第一版人脸检测跑起来以及如何用 Qt 把摄像头画面接到界面上。如果你和我一样之前主要写 Python 脚本或者刚接触 C 桌面开发那这篇文章应该能给你省下不少折腾时间。它面向的读者是那些已经知道 OpenCV 能做图像处理、Qt 能做界面但还不太清楚“三者怎么在一个工程里协作”的人。这一篇不会碰高深算法但会尽量把关键技术决策讲清楚——为什么这么选、不这么做会踩什么坑。1. 项目整体设计与第一阶段的拆解思路1.1 这个系列准备做到什么程度很多教程喜欢一上来就摆一个完整的人脸识别系统看着很爽但跟着做的时候容易卡在某个环节整个项目就废了。所以我给这个系列定了一个比较克制的目标最终交付一个桌面程序支持打开摄像头、检测并框出人脸、采集人脸样本、保存特征、加载样本库然后对摄像头中出现的人脸进行身份匹配。听上去功能不少但落到工程里必须拆成几个独立阶段。第一个阶段就是本篇文章要完成的内容搭好工程环境、写好一个人脸检测模块、把摄像头画面实时显示到 Qt 界面上。注意这里的核心动词是“检测”不是“识别”。检测只负责从画面中找到“哪里有人脸”识别才负责判断“这张脸是谁”。两者很容易混但工程上必须分开做原因我下面细说。这个阶段完成后你已经拥有一个可以预览摄像头画面、实时框出人脸、显示检测到的人脸数和帧率的桌面程序。后续的采集样本、训练特征、比对身份都可以在这个基础上叠加不需要返工。1.2 为什么先从人脸检测开始我见过不少急着做识别的朋友一上来就去找各种识别模型结果在实际摄像头里检测不出来、框不住人脸识别自然也没法用。识别质量的上限取决于检测质量。如果检测框忽大忽小、时有时无后面提取特征、比对身份都会不稳定。所以第一步先把检测做稳是性价比最高的选择。从工程角度讲人脸检测模块能倒逼你解决几个基础问题图像从摄像头到算法模块的传递、帧率控制、图像坐标和界面坐标的换算、以及界面线程和视频流线程的关系。这些问题不解决就算你有一个再牛的识别模型也只能躺在代码库里睡大觉。先做检测相当于先把整条数据通路打通。还有一点很实际OpenCV 自带的人脸检测模型比如 Haar 级联分类器不需要额外下载庞大的神经网络权重一个 xml 文件就能跑。这对第一阶段的工程验证特别友好。等后面需要提升精度再切换到 DNN 版的人脸检测器时只需替换检测类的内部实现界面层完全不用动。1.3 技术选型Qt、C 与 OpenCV 的分工这套技术组合乍一看有点像“三个和尚”其实分工很清楚。C 是骨架负责定义数据结构和模块接口比如检测器类、图像转换函数、主窗口类。选择 C 而不是 Python主要是想把这个程序做成真正能分发的桌面工具启动快、内存可控、部署路径清晰。Qt 是门面负责所有用户交互比如窗口布局、按钮点击、摄像头开关、状态显示。它的事件循环机制能很好地驱动摄像头定时取帧比写死循环优雅得多。OpenCV 是手脚负责图像采集、图像预处理、人脸检测、图像绘制等底层操作。它内部做了大量优化直接用即可。有人可能会问OpenCV 也有自带的高层 GUI 接口为什么还要 Qt答案很简单OpenCV 的 GUI 能力偏基础只能弹个窗口显示图做不了复杂业务界面。真正的产品需要按钮、状态栏、配置面板、日志输出等交互这些是 Qt 的强项。反过来如果只用 Qt 不用 OpenCV人脸检测算法得自己从零写工作量完全是另一个量级。两者组合是互补关系。好整体思路清楚了下面进入让人又爱又恨的环境搭建环节。2. 开发环境准备搭出一套可复现的工程2.1 版本组合与编译器选型环境搭建这个环节90% 的“诡异问题”都出在版本不匹配上。我自己踩过最痛的一次是 OpenCV 预编译库用的编译器和当前 Qt 用的编译器不一致结果链接阶段报几百个 unresolved symbol查了三小时才发现是工具链不匹配。如果你在 Windows 上开发我建议直接采用下面这套组合我自己测下来比较稳组件推荐版本说明操作系统Windows 10/11 64位64位环境可以避免很多内存访问问题Qt5.15.2 或 6.4以上两个版本均可建议和 OpenCV 同时更换编译器MSVC2019/2022 64bit尽量使用 MSVC避开 MinGW 的兼容问题OpenCV4.8.0 或 4.9.0下载官方预编译 Windows 版CMake3.20以上Qt 6 要求更高用最新稳定版即可IDEQt Creator 或 VS Code如果 Qt 写代码Qt Creator 更省心这里有三个容易踩坑的点提前说清楚第一OpenCV 官方预编译包分成vc16、vc15等不同目录分别对应不同的 Visual Studio 版本。Qt 如果用的是 MSVC2019那你要找的是vc16版本如果是 MSVC2022需要确认对应版本号。选错之后编译可能没问题但链接基本必挂。第二MinGW 编译器下使用 OpenCV 预编译库非常坑。OpenCV 官方很久不提供 MinGW 版自己编译又费时间。所以除非你有特殊原因否则请在 Windows 上用 MSVC。这里不展开 MinGW 的解决路径坚持用 MSVC 会轻松很多。第三Debug 和 Release 库不能混用。OpenCV 安装目录里通常会有opencv_world480.libRelease和opencv_world480d.libDebug两个库对应关系不允许搞混。你程序编的是 Debug 版就链接带d的库编 Release 版就链接不带d的库。配合 Qt 时也要保持一致的构建类型。2.2 OpenCV 库的获取与配置Windows 下最省事的方式是去官方 GitHub Release 页面下载opencv-4.x.x-windows.exe自解压文件。解压后你会得到一个opencv文件夹里面有两个关键目录build和sources。build里是已经编译好的 dll 和 libsources里是头文件和官方示例我们只需要build。解压完成后建议把整个opencv文件夹放到一个固定位置比如D:\libs\opencv。然后配置两个环境变量一个是OpenCV_DIR指到D:\libs\opencv\build方便 CMake 查找另一个是在Path中加入D:\libs\opencv\build\x64\vc16\bin保证程序运行时能找到opencv_world480.dll。D:\libs\opencv\build\x64\vc16\bin里有大量 dll其中opencv_world480.dll是核心库。运行时如果系统找不到这个 dll你的程序会在启动瞬间报错“找不到 opencv_world480.dll”所以这一步不能省。Linux 环境下就简单很多Ubuntu 系可以执行sudo apt update sudo apt install libopencv-dev安装后头文件在/usr/include/opencv4库在/usr/lib/x86_64-linux-gnu。CMake 一般能自动找到稍后在 CMakeLists 里指定即可。2.3 项目目录结构与 CMake 配置工程结构我习惯这样组织每一层职责清晰FaceRecognition/ ├── CMakeLists.txt ├── src/ │ ├── main.cpp │ ├── MainWindow.h │ ├── MainWindow.cpp │ ├── FaceDetector.h │ └── FaceDetector.cpp第一版不引入过多抽象但也要避免把代码都塞进一个 main.cpp。我的原则是界面相关的代码归界面算法相关的代码归算法这样后续替换检测模型时不会牵动界面代码。下面是第一版就能跑的CMakeLists.txt已经兼容 Qt5 和 Qt6cmake_minimum_required(VERSION 3.16) project(FaceRecognition) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # 查找 OpenCV find_package(OpenCV REQUIRED) # 查找 Qt find_package(Qt5 COMPONENTS Widgets REQUIRED) # 如果用 Qt6把上面这行替换为 # find_package(Qt6 COMPONENTS Widgets REQUIRED) set(SOURCES src/main.cpp src/MainWindow.cpp src/FaceDetector.cpp ) set(HEADERS src/MainWindow.h src/FaceDetector.h ) add_executable(${PROJECT_NAME} ${SOURCES} ${HEADERS}) target_include_directories(${PROJECT_NAME} PRIVATE src) target_link_libraries(${PROJECT_NAME} ${OpenCV_LIBS} Qt5::Widgets )这里有几个关键点。find_package(OpenCV REQUIRED)之后CMake 会提供OpenCV_INCLUDE_DIRS和OpenCV_LIBS变量直接拿来用即可。Qt5 的 CMake 目标写法是Qt5::Widgets早期那种${Qt5Widgets_LIBRARIES}的写法也是可以的但目标式写法对依赖传递和编译选项的处理更完善。在实际配置时很可能出现 CMake “找不到 OpenCV”的情况。这时候你需要手动指定OpenCV_DIR。在 Qt Creator 的 CMake 配置参数里加一行-DOpenCV_DIRD:/libs/opencv/build如果是 Linux则通常会自动定位到/usr/lib/x86_64-linux-gnu/cmake/opencv4无需额外配置。还有一个小细节如果你启用了 precompiled headers 或者项目中有windows.h要小心宏冲突。OpenCV 的头文件会在某些 Windows 版本下和 Qt 发生min/max宏冲突。解决办法很简单在相关代码之前定义#ifndef NOMINMAX #define NOMINMAX #endif这个宏可以避免windows.h把min和max定义成宏干扰 Qt 和 OpenCV 的模板代码。别小看这个东西我在实际项目里被坑过不止一次。环境就绪下面开始写第一个真正有用的模块。3. 核心细节解析人脸检测模块实现3.1 Haar 级联与 DNN 检测的取舍OpenCV 中实现人脸检测有很多方案最传统的是 Haar 级联分类器。它基于大量正负样本训练出一组弱分类器级联成强分类器检测速度快、模型体积小在正面人脸场景下表现稳定。缺点是姿态变化大、光线差或者脸部侧转严重时很容易漏检或误检。DNN 方式则使用深度学习模型常见的有 OpenCV 自带的 SSD 人脸检测器res10_300x300_ssd_iter_140000或者 YuNet 等。精度明显高于 Haar但需要下载模型文件体积在 100MB 左右对硬件也有一定要求。两种方案对比如下维度Haar 级联DNN (SSD/YuNet)模型体积约 1MB几MB到几十MB检测精度一般较高CPU 推理速度很快中等姿态鲁棒性较差较好工程接入复杂度低中第一阶段我选了 Haar。原因很实在不需要额外下载大模型OpenCV 安装包自带适合先把工程跑通。后一阶段如果要做识别我会再把检测器替换成 DNN。好在封装成独立的FaceDetector类之后替换成本很低这又一次印证了模块化的价值。3.2 用 C 封装一个可替换的检测器FaceDetector的头文件我这样设计#ifndef FACEDETECTOR_H #define FACEDETECTOR_H #include opencv2/opencv.hpp #include vector class FaceDetector { public: FaceDetector(); ~FaceDetector(); // 加载级联分类器模型 bool load(const std::string cascadePath); // 检测输入帧中的所有人脸返回矩形列表 std::vectorcv::Rect detect(const cv::Mat frame); private: cv::CascadeClassifier cascade_; }; #endif // FACEDETECTOR_Hdetect方法接收一整帧图像返回所有人脸的矩形列表调用者只关心结果。实现代码很简单#include FaceDetector.h FaceDetector::FaceDetector() {} FaceDetector::~FaceDetector() {} bool FaceDetector::load(const std::string cascadePath) { return cascade_.load(cascadePath); } std::vectorcv::Rect FaceDetector::detect(const cv::Mat frame) { std::vectorcv::Rect faces; if (frame.empty()) { return faces; } cv::Mat gray; cv::cvtColor(frame, gray, cv::COLOR_BGR2GRAY); cv::equalizeHist(gray, gray); cascade_.detectMultiScale( gray, faces, 1.1, // scaleFactor 5, // minNeighbors 0 | cv::CASCADE_SCALE_IMAGE, cv::Size(30, 30), cv::Size(500, 500) ); return faces; }有人可能疑惑为什么检测前要先转灰度图再直方图均衡化Haar 特征是在灰度图像上计算的颜色信息对检测没有直接帮助所以先把三通道的 BGR 图转成单通道灰度图计算量直接降到原来的三分之一。equalizeHist的作用是让灰度分布更均匀在光线偏亮或偏暗时提高对比度减少漏检概率。这一步算是人脸检测的标准预处理。参数方面scaleFactor 1.1表示每层金字塔缩小到原来的 90%值越小检测越精细但越慢minNeighbors 5表示一个候选区域至少被周围 5 个窗口确认才算人脸值越大误检越少但也可能漏检。第一版可以先用这两个值后面再根据实际画面调。3.3 Mat 到 QImage 的高效转换摄像头取到的是 OpenCV 的cv::MatQt 界面显示用的是QImage两者需要转换。很多人第一次显示图像时发现颜色不对——人脸发蓝、背景发绿原因就是通道顺序没换。OpenCV 默认通道顺序是 BGR而 QImage 默认是 RGB。直接拷贝会导致红蓝互换。正确的转换如下QImage cvMatToQImage(const cv::Mat mat) { if (mat.type() CV_8UC3) { cv::Mat rgb; cv::cvtColor(mat, rgb, cv::COLOR_BGR2RGB); QImage image( rgb.data, rgb.cols, rgb.rows, static_castint(rgb.step), QImage::Format_RGB888 ); // 必须深拷贝否则原 Mat 被释放后 image 是野指针 return image.copy(); } return QImage(); }关键点有三处第一是做cvtColor把 BGR 转成 RGB否则颜色错乱。第二是构造 QImage 时bytesPerLine参数不能省略。Mat 的每一行可能因为内存对齐而比cols * channels多几个字节直接用默认参数容易出现图像歪斜或底边错位。用rgb.step作为bytesPerLine最保险。第三是返回值处的.copy()。QImage 默认只保存指向数据的指针不管理内存。如果 cv::Mat 销毁图像数据跟着没了界面上就会变成一堆随机色块。.copy()会创建深拷贝确保数据显示稳定。在实时视频流里每一帧都做一次转换和拷贝性能开销不算小。但第一版先确保正确后面再优化。一个可用的优化技巧是提前分配好 QImage 的缓冲区用cv::Mat直接包裹QImage的数据指针省去拷贝。这个对新手有点复杂先不展开等后续帧率不够时再考虑。3.4 实时视频流的驱动方式摄像头图像是持续产生的界面必须不断刷新。最常见的错误做法是写一个while (true)循环不停cap.read()然后把图像传给 UI。这在命令行程序里没问题到了 Qt 的 GUI 线程里会直接把事件循环卡死窗口拖不动、按钮点不了。正确方式是借助 QTimer 驱动// 在 MainWindow 中 QTimer* timer_ new QTimer(this); timer_-setInterval(33); // 约30fps connect(timer_, QTimer::timeout, this, MainWindow::updateFrame);每 33 毫秒触发一次updateFrame在这个函数里读取摄像头当期帧、检测人脸、转换 QImage、更新界面。这样一来Qt 的事件循环仍然畅通界面可以正常响应鼠标事件。帧率也不是越高越好。OpenCV 的VideoCapture在 Windows 上默认可能以摄像头最高帧率输出比如 60fps但检测算法不一定跟得上。QTimer 的 interval 设置成 33 毫秒相当于强制 UI 层 30fps既流畅又给检测留出余量。如果检测速度较慢可以把 interval 放宽到 50 毫秒。实时视频流还有一个容易犯的错误在摄像头没有打开时就去cap.read()。每次读取前必须检查cap.isOpened()读取后检查返回的frame.empty()。我在后面问题排查部分会补充细节。4. 实操过程Qt 界面搭建与摄像头接入4.1 主窗口布局与控件设计第一版的界面不需要花哨三个区域就够用上方是图像显示区左侧或底部是控制按钮区最下方一行状态栏。我还是喜欢用代码直接构建界面不用 .ui 文件因为是动态项目代码写的界面更容易看到构造顺序。主窗口类头文件#ifndef MAINWINDOW_H #define MAINWINDOW_H #include QMainWindow #include QLabel #include QPushButton #include QTimer #include opencv2/opencv.hpp class FaceDetector; class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget* parent nullptr); ~MainWindow(); private slots: void onOpenCamera(); void onCloseCamera(); void updateFrame(); private: void setupUI(); QImage cvMatToQImage(const cv::Mat mat); QLabel* imageLabel_; QPushButton* openButton_; QPushButton* closeButton_; QLabel* statusLabel_; QTimer* timer_; cv::VideoCapture capture_; FaceDetector* detector_; }; #endif // MAINWINDOW_H构造函数里做三件事setupUI()创建界面detector_-load(...)加载 Haar 模型初始化 timer。setupUI的核心代码大概是void MainWindow::setupUI() { auto* central new QWidget(this); auto* layout new QVBoxLayout(central); imageLabel_ new QLabel(摄像头画面将显示在这里, central); imageLabel_-setAlignment(Qt::AlignCenter); imageLabel_-setMinimumSize(640, 480); imageLabel_-setStyleSheet(background-color: #1e1e1e; color: #aaaaaa;); layout-addWidget(imageLabel_); auto* btnLayout new QHBoxLayout(); openButton_ new QPushButton(打开摄像头, central); closeButton_ new QPushButton(关闭摄像头, central); closeButton_-setEnabled(false); btnLayout-addWidget(openButton_); btnLayout-addWidget(closeButton_); layout-addLayout(btnLayout); statusLabel_ new QLabel(就绪, central); layout-addWidget(statusLabel_); setCentralWidget(central); resize(800, 600); setWindowTitle(人脸识别项目(一) - 实时检测); }按钮初始状态要让“关闭摄像头”不可用防止用户还没打开就点关闭。状态栏用于显示实时信息比如当前分辨率、检测到的人脸数和 FPS。4.2 摄像头打开与帧率控制打开摄像头比较简单void MainWindow::onOpenCamera() { if (!capture_.open(0)) { statusLabel_-setText(错误无法打开摄像头); return; } capture_.set(cv::CAP_PROP_FRAME_WIDTH, 640); capture_.set(cv::CAP_PROP_FRAME_HEIGHT, 480); timer_-start(); openButton_-setEnabled(false); closeButton_-setEnabled(true); statusLabel_-setText(摄像头已打开); }这里通过open(0)打开默认摄像头。如果电脑有多个摄像头可能要试试 1、2 等索引。设置分辨率为 640x480是因为这个分辨率下脸比较清晰检测也快。如果你的摄像头支持 1280x720也可以后面调大但需要注意 UI 显示区域也要相应变化。关闭摄像头时一定要先停掉 timer 再释放 capturevoid MainWindow::onCloseCamera() { timer_-stop(); if (capture_.isOpened()) { capture_.release(); } imageLabel_-setText(摄像头已关闭); openButton_-setEnabled(true); closeButton_-setEnabled(false); statusLabel_-setText(已停止采集); }顺序很重要。如果先release()再stop()存在一种边界情况定时器回调可能正卡在读取摄像头帧上此时调用 release 会引发异常或崩溃。先停定时器再从根源上保证回调不会再触发最后释放资源才是最安全的。4.3 在帧上绘制检测结果updateFrame是整个程序的核心它的流程可以概括为四步读帧、检测、绘制、显示。void MainWindow::updateFrame() { if (!capture_.isOpened()) { return; } cv::Mat frame; capture_ frame; if (frame.empty()) { return; } // 人脸检测 std::vectorcv::Rect faces detector_-detect(frame); // 绘制矩形和文字 for (const auto face : faces) { cv::rectangle(frame, face, cv::Scalar(0, 255, 0), 2); cv::putText(frame, Face, cv::Point(face.x, face.y - 5), cv::FONT_HERSHEY_SIMPLEX, 0.6, cv::Scalar(0, 255, 0), 2); } // 转为 QImage 并显示 QImage image cvMatToQImage(frame); imageLabel_-setPixmap(QPixmap::fromImage(image).scaled( imageLabel_-size(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); // 更新状态 statusLabel_-setText(QString(人脸数: %1 | 帧尺寸: %2x%3) .arg(faces.size()) .arg(frame.cols) .arg(frame.rows)); }绘制方面cv::rectangle的第三个参数是颜色注意这里是 BGR 顺序。Scalar(0, 255, 0)表示 B0、G255、R0也就是纯绿色。如果你习惯 RGB 直觉想画红色写成Scalar(0,0,255)容易画成蓝色这是又一个容易翻车的地方。QPixmap::fromImage(image)之后立即scaled是为了让图像自适应窗口大小。注意这里的性能损耗不高因为 Qt 在缩放时做了优化但每帧都缩放在低配电脑上可能有点压力。第一版可接受后续如果想优化可以只在窗口大小变化时缩放一次或者直接让摄像头输出分辨率匹配显示区。4.4 首版联调与效果验证代码写完后在 Qt Creator 里点击运行。第一次成功运行看到的画面大概是摄像头灯亮起界面上出现实时画面如果有人脸正对镜头会被绿框标出来状态栏显示人脸数量和分辨率。这里我要强调一个“你以为成功了其实还没成功”的细节千万不要只看一秒钟。请把脸稍微侧一点、靠近一点、距离远一点逐渐移动观察绿框是否跟丢、是否会误判多个框。如果侧脸老是丢框这是 Haar 的正常表现不要因此怀疑代码写错了。相反如果正脸都框不准那才需要检查参数。我自己第一版联调时在正脸、离镜头 50cm 左右、光线充足的情况下基本稳定能检测到 1-2 个人脸把脸侧到 45 度以上时框就会出现跳动。这个结果在意料之中。了解检测器的能力边界比追求“什么角度都能框住”更实际。5. 问题排查与参数调优实录5.1 编译和链接阶段的坑这一阶段遇到最多的问题集中在编译和链接。我把几个高频问题整理成表文末附上避坑细节现象可能原因解决思路找不到 OpenCV 头文件CMake 未找到 OpenCV手动指定OpenCV_DIR大量 LNK2019 链接错误库版本/位数不匹配检查 Debug/Release、x86/x64、vc 版本提示找不到 dll系统 Path 未配置将opencv_world*.dll所在目录加入 Path与windows.h冲突min/max 宏污染定义NOMINMAXQt 和 OpenCV 的库重复定义同时链接了 opencv_world 和 opencv_core 等使用OpenCV_LIBS自动处理不要手动添加多个库链接错误里最坑的是“明明包含路径都对但就是告警一堆 unresolved external symbol”。99% 是因为你使用了opencv_world480.libRelease但当前 CMake 是 Debug 构建或者相反。检查方法很简单看 CMake 的构建目录如果是build-Desktop-Debug那就必须链接opencv_world480d.lib。如果你是自行编译的 OpenCV这类问题会少一些因为编译时可以一开始就选好工具链。但编译 OpenCV 需要半小时以上第一次建议还是用官方预编译包。5.2 运行时常见问题速查程序能跑之后仍然有一批“运行期疑难杂症”。我把自己遇到过的问题列成速查表现象可能原因解决思路摄像头打不开索引错误或已被占用换索引关闭其他占用摄像头的程序画面颜色偏蓝/偏绿没有从 BGR 转 RGB在 Mat 转 QImage 时执行 cvtColor画面撕裂或歪斜QImage 的 bytesPerLine 参数错误使用rgb.step作为bytesPerLine窗口卡死无响应在 GUI 线程里写了死循环改用 QTimer 驱动取帧界面闪烁严重每帧都直接 setPixmap 全量刷新可开启双缓冲Qt 默认支持或减小显示区域人脸检测框疯狂抖动参数过于敏感增大 minNeighbors适当调大 minSize帧率偏低分辨率太高或检测参数太精细降分辨率调大 scaleFactor5.3 检测参数调优的一点体会参数调优这件事网上说法很多但最终要以你自己的摄像头画面为准。我分享几个实际调整经验。scaleFactor这个参数我之前一直用 1.1在光线不好时检测会变慢。后来调成 1.2速度提升明显漏检也没增加多少。如果你电脑性能较弱可以从 1.2 起步试试。minNeighbors从默认的 3 提升到 5桌面环境下的误检会少很多。假设画面里你把打印机、海报上的照片误判成脸就可以继续调到 6 或 7。还有一个很多人不注意的点minSize。如果你的摄像头距离人脸区域比较远输出图像中的人脸可能只有十几个像素宽。这时 minSize 设 30x30会直接把小脸全部漏掉。我自己做走廊摄像头测试时把minSize调低到 20x20检测率明显提高但误检也上来了。这属于典型精度和速度的取舍要看业务场景。关于 Haar 级联模型提醒一句haarcascade_frontalface_default.xml对戴眼镜、有刘海的正脸影响不大但对侧脸和低头族很不友好。如果你的应用场景主要是正面考勤、闸机这种人脸相对正面的情况可以用它如果场景复杂第二阶段换 DNN 是必须的。人脸检测这一层跑稳之后项目的“地基”就算打完了。后续要做的人脸采集、特征提取、身份比对都会站在这个基础上。等我把第二阶段的功能够顺手了再来更新系列下一篇。