☰
使用 OpenCV Contrib face 模块训练自定义人脸关键点检测器(FacemarkKazemi 实战指南)
2026/9/25 2:30:44 网站建设 项目流程
  • 计算机视觉
  • 图像处理
  • 机器学习

【免费下载链接】opencv_contrib

项目地址:https://gitcode.com/gh_mirrors/ope/opencv_contrib
点击查看免费下载

本指南围绕 opencv_contrib 的 face 模块中face_landmark_trainer教程展开,系统讲解如何基于 HELEN / ibug 格式的标注数据,使用FacemarkKazemi算法训练属于自己的 68 点人脸关键点检测模型。读完本文,你将掌握训练数据的组织格式、全部命令行参数与 8 个核心训练超参数的含义及源码级默认值,并能独立跑通sample_train_landmark_detector与sample_train_landmark_detector2两个官方训练样例,理解整个级联回归训练流程的底层实现。

概述:训练器能做什么

modules/face模块提供的训练应用用于训练自定义的人脸关键点检测器(face landmark detector)。你只需提供两类输入:

  • 存放训练图片的目录;
  • 与图片一一对应的关键点标注文件。

算法会自动完成人脸检测、尺度归一、均值形状计算、级联回归器训练,最终产出一个可直接用于Facemark::loadModel+Facemark::fit推理流程的模型文件。由于该检测器最初是在 HELEN 数据集 上训练的,其训练数据格式与 HELEN 数据集保持一致,也兼容 ibug 人脸标注点数据集的标准格式(见后文)。

训练数据格式准备

HELEN 风格标注(.txt单文件格式)

数据集由若干.txt文件构成,每个文件的第一行是图片文件名,后续每一行是一个关键点的x,y坐标。格式如下:

/directory/images/abc.jpg 123.45,345.65 321.67,543.89 .... , .... .... , ....

对应到源码,这一格式由 facemark_train.hpp 中声明的loadTrainingData(std::vector<String> filename, ...)重载解析:第一个字段为图片路径,后续逐行读取形如x,y的坐标对。

ibug 风格标注(.pts文件格式)

若你使用的是 ibug 人脸标注点数据集(68 点标准),其单个.pts标注文件格式为:

version: 1 n_points: 68 { 115.167660 220.807529 116.164839 245.721357 120.208690 270.389841 ... }

其中n_points声明关键点总数,每个点以空格分隔的x y坐标表示。这类格式由loadFacePoints与两文件列表式loadTrainingData重载支持(见 facemark_train.hpp),并且要求同时提供两个列表文件:

# images.txt 内容示例 ../trainset/image_0001.png ../trainset/image_0002.png # annotations.txt 内容示例 ../trainset/image_0001.pts ../trainset/image_0002.pts

两个列表逐行对应,指明每张图片及其标注文件的位置。对应的加载调用为:

std::vector<String> images; std::vector<std::vector<Point2f> > facePoints; loadTrainingData(imagesList, annotations, images, facePoints, 0.0);

其中最后一个参数offset(默认0.0)用于对加载到的点坐标做整体平移修正。

命令行参数说明

运行官方样例sample_train_landmark_detector的命令(源码见 sample_train_landmark_detector.cpp):

./sample_train_landmark_detector -annotations=/home/sukhad/Downloads/code/trainset/ -config=config.xml -face_cascade=lbpcascadefrontalface.xml -model=trained_model.dat -width=460 -height=460

各参数含义如下表:

参数简写必填说明
annotationsa是存放标注.txt文件的目录路径(HELEN 风格),例如/data/annotations.txt
configc是训练配置文件(XML)路径,内含全部训练超参数,例如/data/config.xml
modelm是训练完成后模型保存路径,例如/data/model.dat
widthw否训练前将所有图片缩放到的目标宽度,用于加速处理大图,默认460
heighth否训练前将所有图片缩放到的目标高度,默认460
face_cascadef是训练期用于人脸检测的 Haar/LBP 级联分类器 XML 路径,例如lbpcascadefrontalface.xml

要点说明:

  • 源码中默认的width、height均为460,与文档描述一致;
  • 官方示例提示尽量使用绝对路径以避免路径解析问题;
  • 若缺少任一必填参数,程序会打印参数帮助信息并返回错误码(见 sample_train_landmark_detector.cpp)。

训练配置文件与 8 个核心超参数

训练配置文件是一个标准的 OpenCVFileStorageXML 文件,官方示例 sample_config_file.xml 内容如下:

<?xml version="1.0"?> <opencv_storage> <cascade_depth>15</cascade_depth> <tree_depth>4</tree_depth> <num_trees_per_cascade_level>500</num_trees_per_cascade_level> <learning_rate>1.0000000149011612e-01</learning_rate> <oversampling_amount>20</oversampling_amount> <num_test_coordinates>400</num_test_coordinates> <lambda>1.0000000149011612e-01</lambda> <num_test_splits>20</num_test_splits> </opencv_storage>

文档中给出的 8 个参数含义如下:

  1. cascade_depth(级联深度):训练所用回归器级联(cascade of regressors)的级数;
  2. tree_depth(树深度):梯度提升过程中作为弱学习器的决策树深度;
  3. num_trees_per_cascade_level(每级级联的树数量):每一级级联所需训练的树的总数;
  4. learning_rate(学习率):梯度提升的学习率,用于通过收缩(shrinkage)防止过拟合;
  5. oversampling_amount(过采样量):对训练样本的过采样倍数;
  6. num_test_coordinates(测试坐标数):为决定分裂而生成的候选测试坐标数量;
  7. lambda(λ):用于计算像素选择概率的值,帮助在分裂时选取更靠近当前形状的像素;
  8. num_test_splits(测试分裂数):在选出最优分裂之前生成的候选分裂数量。

这些参数的理论出处是论文One Millisecond Face Alignment with an Ensemble of Regression Trees(Vahid Kazemi & Josephine Sullivan),即 FacemarkKazemi 算法所依据的研究论文。

源码级默认值与解析逻辑

参数默认值定义在 face_alignment.cpp 的FacemarkKazemi::Params::Params()中:

参数源码默认值示例配置文件值
cascade_depth1515
tree_depth54
num_trees_per_cascade_level500500
learning_rate0.10.1
oversampling_amount2020
num_test_coordinates500400
lambda0.10.1
num_test_splits2020

配置文件的实际读取在 trainFacemark.cpp 的setTrainingParameters中完成:通过FileStorage以READ模式打开 XML,逐个键读取上述 8 个字段并写入params结构;若配置文件无法打开,会抛出Error while opening configuration file.Aborting..异常。因此训练前必须保证该文件存在且字段键名与上表完全一致。

训练代码逐段解析

文档在 "Understanding code" 章节给出完整的训练主流程,下面结合源码与官方样例逐段说明。

第一步:自定义人脸检测器

训练过程需要先找到每张图片中的人脸区域。FacemarkTrain基类允许用户注入自己的人脸检测函数(facemark_train.hpp 中的setFaceDetector),检测器函数签名类型为FN_FaceDetector:

typedef bool(*FN_FaceDetector)(InputArray, OutputArray, void* userData);

文档中的示例检测器基于 Haar 级联:

CascadeClassifier face_cascade; bool myDetector( InputArray image, OutputArray ROIs ); bool myDetector( InputArray image, OutputArray ROIs ){ Mat gray; std::vector<Rect> faces; if(image.channels()>1){ cvtColor(image.getMat(),gray,COLOR_BGR2GRAY); } else{ gray = image.getMat().clone(); } equalizeHist( gray, gray ); face_cascade.detectMultiScale( gray, faces, 1.1, 3,0, Size(30, 30) ); Mat(faces).copyTo(ROIs); return true; }

该函数将彩色图转灰度、做直方图均衡化后调用detectMultiScale,把检出的人脸矩形框写入ROIs。官方样例实现略有差异:级联参数为scaleFactor=1.4、minNeighbors=2、CASCADE_SCALE_IMAGE标志与Size(30,30)最小尺寸(见 sample_train_landmark_detector.cpp),并且通过void* userData把CascadeClassifier指针传入检测函数。

第二步:扫描标注文件

vector<String> filenames; glob(directory,filenames);

glob遍历指定目录,把其中所有标注.txt文件名存入filenames向量,供后续加载使用(官方样例实际调用glob(directory + "*.txt", filenames))。

第三步:创建 Facemark 实例并注入检测器

Mat img = imread(image); face_cascade.load(cascade_name); FacemarkKazemi::Params params; params.configfile = configfile_name; Ptr<Facemark> facemark = FacemarkKazemi::create(params); facemark->setFaceDetector(myDetector);

流程要点:

  • face_cascade.load(cascade_name)加载命令行指定的级联模型;
  • FacemarkKazemi::Params中的configfile字段指向训练配置文件(官方样例与源码training()均会据此调用setTrainingParameters);
  • FacemarkKazemi::create(params)创建算法实例(工厂实现见 face_alignment.cpp);
  • setFaceDetector将第一步定义的检测器注入 facemark,训练期间用它定位人脸。注意FacemarkTrain::setFaceDetector的完整签名还接收userData指针:setFaceDetector(FN_FaceDetector detector, void* userData = 0)。

第四步:加载图片与标注

HELEN 风格数据加载:

vector<String> imagenames; vector< vector<Point2f> > trainlandmarks,Trainlandmarks; vector<Mat> trainimages; loadTrainingData(filenames,trainlandmarks,imagenames); for(unsigned long i=0;i<300;i++){ string imgname = imagenames[i].substr(0, imagenames[i].size()-1); string img = directory + string(imgname) + ".jpg"; Mat src = imread(img); if(src.empty()){ cerr<<string("Image "+img+" not found\n.")<<endl; continue; } trainimages.push_back(src); Trainlandmarks.push_back(trainlandmarks[i]); }

loadTrainingData解析每个.txt标注文件,把图片名与逐点坐标分别填入imagenames与trainlandmarks;随后循环读取对应图片,跳过加载失败的文件。注意substr(0, size()-1)是为了去掉文件名末尾的换行符。

ibug 风格(.pts)数据加载则直接使用两列表重载:

std::vector<String> images; std::vector<std::vector<Point2f> > facePoints; loadTrainingData(imagesList, annotations, images, facePoints, 0.0);

此重载读取图片列表与标注列表两个文件,得到图片路径与对应的 68 点标注。对应完整样例见 sample_train_landmark_detector2.cpp。

第五步:执行训练

facemark->training(Trainimages,Trainlandmarks,configfile_name,scale,modelfile_name);

training的重载签名(实现于 trainFacemark.cpp):

  • images:训练图片;
  • landmarks:与图片一一对应的标注点;
  • filename:训练配置 XML 路径;
  • scale:所有图片与标注的目标缩放尺寸(由width/height命令行参数决定);
  • modelFilename:训练完成后的模型保存路径。

训练开始成功后,控制台会输出类似下面的训练进度(对应文档配图 train1.png):

训练流程的源码级原理

结合 trainFacemark.cpp 与 regtree.cpp,FacemarkKazemiImpl::training的内部执行顺序为:

  1. setTrainingParameters(filename):读取并校验配置文件中的 8 个超参数;
  2. scaleData(landmarks, images, scale):按scale目标尺寸等比缩放所有图片与标注点(scalex = scale.width / img.cols,scaley = scale.height / img.rows,缩放后用INTER_LINEAR_EXACT重采样),这是文档强调"大图处理慢"的工程化解法;
  3. calcMeanShape(landmarks, images, rectangles):对每张图用人脸检测器求人脸框,通过仿射变换convertToUnit将各形状统一到单位参考系,逐点累加平均,得到均值形状(mean shape),作为训练与推理的初始形状;
  4. getTestCoordinates():在每个级联层级内、以均值形状的包围范围为界,随机生成num_test_coordinates个候选像素坐标;
  5. createTrainingSamples:按oversampling_amount倍扩充训练样本——半数样本以均值形状为当前形状,半数以随机其他样本的真实形状为当前形状,并通过getDiffShape并行计算残差actual_shape - current_shape;
  6. 逐级联训练森林:对每一级(cascade_depth级)调用gradientBoosting,每级训练num_trees_per_cascade_level棵回归树。gradientBoosting中通过parallel_for_的splitSamples并行执行分裂搜索(regtree.cpp):随机取两测试坐标feat.index1/index2(范围受num_test_coordinates约束),生成num_test_splits个候选分裂并择优;树节点数以2^tree_depth计算,叶节点按learning_rate收缩更新残差(regtree.cpp);
  7. saveModel(modelFilename):以二进制流写出模型,内容包括cascade_depth、各级pixel_coordinates、mean_shape、num_trees以及逐棵树的 split/leaf 节点(见 trainFacemark.cpp)。

从源码结构可以推断,training()的每一步(缩放、均值形状、样本过采样、级联森林、模型序列化)都直接受配置文件中对应超参数驱动,这也是为什么config文件是必填参数。

训练效果评估与误差曲线

训练完成后,模型的精度与训练样本数量正相关。文档给出的两组典型关系曲线如下:

训练集上的误差率随训练图片数量的变化:

测试集上的误差率随训练图片数量的变化:

从曲线趋势可以直观看出:样本越少时误差越高且波动明显,随着训练图片数量增加,训练集与测试集误差均收敛到较低水平;这也解释了为何官方样例默认取前 300 张图片训练——需要在数据规模与训练耗时之间取得平衡。

训练完成后如何复用模型

训练产物(如trained_model.dat)可在其他应用中直接加载推理,典型流程来自 facemark.hpp 中定义的通用 Facemark 管线:

Ptr<Facemark> facemark = FacemarkKazemi::create(); facemark->loadModel("trained_model.dat"); // 加载训练好的模型 std::vector<Rect> faces; std::vector<std::vector<Point2f> > landmarks; facemark->fit(image, faces, landmarks); // 拟合关键点

其中faces可以由训练期使用的同一人脸检测器提供。检测与视频流应用可进一步参考同目录下的 face_landmark_detection.markdown 与 face_landmark_video.markdown 教程。

小结

本指南完整覆盖了FacemarkKazemi自定义训练的全部要素:HELEN 与 ibug 两种标注格式、6 个命令行参数、8 个训练超参数及其源码默认值、自定义人脸检测器的注入方式,以及从数据加载、缩放、均值形状到级联回归森林的底层训练流程。按此流程准备好标注数据与配置文件后,即可用官方两个样例(sample_train_landmark_detector.cpp、sample_train_landmark_detector2.cpp)训练出适配自有数据分布的人脸关键点模型,再通过loadModel+fit投入实际应用。

  • 计算机视觉
  • 图像处理
  • 机器学习

【免费下载链接】opencv_contrib

项目地址:https://gitcode.com/gh_mirrors/ope/opencv_contrib
点击查看免费下载

相关推荐

上一篇:如何解决SpERT实体识别误报问题:neg_entity_count参数调优指南
下一篇:FSLineChart:iOS平台的优雅折线图库

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询