OpenBR 入门教程

欢迎来到OpenBR!在此我们设计了一系列用于使你快速了解OpenBR是什么,怎么工作及其命令行接口的 教程 。这些教程并没有刻意按照某种顺序排列,你可以随意跳跃阅读, 只要你开心 。如果需要帮助,请 联系我们

快速入门

该教程将使用一些有趣的例子来帮你熟悉OpenBR背后的思想、对象与动机。 注意部分教程是需要网络摄像头的

OpenBR是建立于 QtOpenCV 以及 Eigen 之上的一个C++库。可以在命令行下通过br程序使用它,或者从 C++C 接口进行调用。使用br是最简单快速的入门方法,本教程的举例也都用br进行实现。

首先,确认下OpenBR是否在你的系统上 成功 安装了,没有的话参照 安装部分 去部署吧。

打开终端或者命令行,并输入:

$ br -gui -algorithm "Show(false)" -enroll 0.webcam

如果一切都还好的话,你的摄像头会打开并捕捉视频信号。那么恭喜你正在使用OpenBR啦!

让我们来讨论下上面命令行发生了啥。-gui、-algorithm 和 -enroll 是OpenBR标记里的几个示例,作为br的输入指令。标志通过 - 符号指定,参数跟随其后,并以空格分开。标志通常需要特定数量的参数跟随着。 这儿的文档 描述了所有可能的标志及其赋值。让我们追个过一下这些参数和值:

  • -gui告诉OpenBR去打开一个GUI图形界面。注意使用该标志时,它必须第一个传给br。

  • -algorithm是OpenBR最重要的标志之一,其需要一个参数,通常为一个字符串来指定算法。该字符串决定了图像和元数据将通过什么管道来传递。其由各种 变换 组成,这在教程后头将详细描述。

  • -enroll从一个 画廊 或者 格式 并送入算法管道,然后再将其序列化成另一种画廊或者格式。需要一个参数(该例中为0.webcam)和一个可选输出参数OpenBR支持包括jpg,png,csv和xml的多种格式。.webcam格式告知OpenBR从计算机的摄像头获取帧。

让我们尝试下稍微复杂点的例子。毕竟OpenBR能做的远不止打开一个摄像头啊!再次打开终端并喂入:

$ br -gui -algorithm "Cvt(Gray)+Show(false)" -enroll 0.webcam

此处我们将通常的BGR(OpenCV中可选RGB)图像通过Cvt(Gray)转换为灰度图像。 Cvt 简短并用于转换,是一个OpenBR 变换 的例子,Show也是一种变换。事实上,OpenBR中每一个算法字符串都只是一系列变换联合起来组成的管道,设置+号都是一个管子的缩写,属于另一种OpenBR 变换

通常变换需要参数。我们制定Gray作为Cvt的运行时参数以告知变换需要将图像转换到哪个颜色空间。我们也可以写Cvt(HSV)或者Cvt(Luv)。参数可以使用键值对或者不明确指定键(Cvt(Gray)等同于Cvt(colorSpace=Gray))。注意如果你只想提供值,参数需要按顺序指定。(跟命名参数一样的)。尝试改变上面的算法字符串为含Show(true)来看看如何修改参数以影响命令的输出(提示:hit a key to cycle through the images)。

让我们使这个例子稍微令人兴奋点,这触及到了OpenBR的生物识别方法。人脸检测通常是 人脸识别 算法的第一步。让我们回到终端去体验下OpenBR中的人脸检测:

$ br -gui -algorithm "Cvt(Gray)+Cascade(FrontalFace)+Draw(lineThickness=3)+Show(false)" -enroll 0.webcam

你的摄像头应该会再次打开,但是,这一次一个框框将会框住你的脸!我们增加了两个新的变换到字符串里: 级联分类器绘制 。让我们一步步看下每个变换是怎么工作的吧:

1.Cvt(Gray):从BGR转换图像到灰度,灰度图是级联器正常工作所需的。

    2. 正脸级联分类器 : 这是OpenCV 级联分类 框架的一个封装。其使用正脸模型来检测正脸。

    3. 绘制(线宽=3) : 获取级联器检测得到的矩形并绘制于摄像头帧上。lineThickness决定了绘制矩形时的线宽。

    4. Show(false) : 在GUI窗口中显示该图像。false意味着图像应该被接着显示出来而不需要等待一个键的按下。

每一个变换完成一个任务并传递输出到下一个变换。你个以将你想要的变换随意多地组合到一起,但是需要注意确定的变换对其输入是有特定要求的。

你可能好奇什么对象可以通过算法途径传递。在OpenBR有两个对象处理数据:

  • 文件 (系统)通常用于存储与磁盘文件的路径与元数据(在键值对的表单中)。以上的例子,我存储的矩形检测到级联作为元数据,然后用于可视化绘制。

  • 模板 是图片和文件的容器。在OpenBReak的图片是OpenCV Mats,与不同模板的成员。模板可以包含一个或者多个图片。

假如你想深入学习命令行或者所有的插件与键数据结构,请参考文档链接。下一个指导将会探讨算法以及深度应用。

OpenBR的算法

OpenBR的其中一个优势是简单易用性,用一个一致和简单方式表达生物特征识别算法。

在OpenBR中,一个算法字符串定义一个技术来注册图像和(可选)比较他们的方法。常见做法是存储一个优化标识,而不是存储整个原图像用于做对比,或者手上任务的图片模板。我们注意到,为了清楚起见,OpenBR对象模板的命名出自这个概念,模板是一个相对通用的生物概念。生成这个优化标识的过程,称之为模板注册,或者模板生成。鉴于两个模板,模板对比是计算两者的相似度,更高的值更可能地匹配。操作上,宗旨是生成一个小型、精确与快速的模板用来对比。

正如前面提到的,在OpenBR里一个算法通过一个算法字符串进行定义。算法由字符串定义具有许多的优势,最重要的是:

  1. 由于强行解耦了算法开发的每一步,其保证了较好的软件开发体验,还具备了算法修改以及重用独立步骤的优点。

  2. 其解放了大量非常相似的头文件的创建于维护,否则将需要在算法的每一步去做这些事(注意在openbr/plugins文件中缺少的头)。

  3. 其运行不需要再编译就实现了算法调参。

  4. 这是完全明确的, 不光是OpenBR解释器,任何人熟悉该工程的都能从这个描述上确切理解你的算法实现了什么。

OpenBR 提供了一套用于设置插件属性值的语法并创建了简约的算法字符串。相关的符号是:

符号 含义
插件名(属性1=值1 ,...属性N=值N) 插件通过其名字(不需要摘要)以及属性值列表进行描述。插件中未指定属性的将被设置其缺省值。
: 将模板注册和模板比较分隔开。 注册在冒号左边,比较在右边。定义一个算法并带有模板比较这一步是可选的。
+ Pipe 的简写。将变换和工程输入按串行联结在一起。左边变换的输入作为右边变换的输入。
/ Fork 的简写。并行将变换与工程输入联结在一起。所有变换接受相同的输入, 输出则联系在一起。
{} Cache 的简写。将一个插件的输出缓存到内存里用于后端的快速查找。
<> LoadStore 的简写。放在尖括号里的变换所用参数在训练后被存于磁盘上并且在预测之前被从磁盘加载。
() 操作顺序。用圆括号指定操作优先级。

让我们看一些码库中实现这些的重要部分:

在openbr/core/core.cpp的AlgorithmCore::init()中你可以看到在冒号处用于分割算法描述的代码。函数中紧接着后边有模板注册和模板比较对象的实例化。这些实例化调用定义于公共 C++插件API , 并且也能被最终用户的代码调用。

下面我们讨论一些在openbr/openbr_plugin.cpp中关于Transform::make的源码。注意,用于其他插件类型的make函数本质上是相似的并且不会被覆盖。

当转换模板注册为 变换 时的首要步骤之一就是替换操作符,比如 '+'的完全体就是:

{ // Check for use of '+' as shorthand for Pipe(...)
     QStringList words = parse(str, '+');
     if (words.size() > 1)
         return make("Pipe([" + words.join(",") + "])", parent);}

在操作符扩展后, 模板加入描述建立了一个树,并且 变换 从这个描述中在树的根节点处开始递归地建立:

Transform *transform = Factory<Transform>::make("." + str);

以scripts/helloWorld.sh中的算法为例:

Open+Cvt(Gray)+Cascade(FrontalFace)+ASEFEyes+Affine(128,128,0.33,0.45)+CvtFloat+PCA(0.95):Dist(L2)

通过现有OpenBR算法语法的只是我们来扩展该语句。首先算法在冒号处被分为enrollment和比较部分。所以enrollment部分为:

Open+Cvt(Gray)+Cascade(FrontalFace)+ASEFEyes+Affine(128,128,0.33,0.45)+CvtFloat+PCA(0.95)

而比较部分:

Dist(L2)

在冒号左边,通过+号联合的 变换 转为Pipe的孩子。因此enrollment算法建立为:

Pipe(transforms=[Open,Cvt(Gray),Cascade(FrontalFace),ASEFEyes,Affine(128,128,0.33,0.45,CvtFloat,PCA(0.95)])

该算法所涉及到的低级细节可以从 project 函数中找到。简单概为:

1. 从磁盘读取图像
2. 灰度转换
3. 人脸检测
4. 脸部中的眼睛检测
5. 使用眼睛位置信息对人脸进行旋转和尺度的归一化
6. 图像转为浮点格式
7. 在一个人脸图像训练出来的PCA子空间中嵌入该图像

若你对人脸识别熟悉的话,你将很可能认出这是特征脸 1 算法。

最后要说的是, 特征脸使用的是欧式距离 (或者说二范数) 来比较模板。由于OpenBR在模板比较时期望的是相似度而不是不相似度, DistDistance 将默认返回 -log(distance+1) 以至于更小的距离 (欧式尺度)意味着更高的相似度。注意 NegativeLogPlusOne(负log加1) 距离也是存在的所以你可以通过上述函数转换任何距离的输出。

训练算法

OpenBR使你很容易实现在自定义数据集上创建并训练你的算法。让我们从算法字符串开始 Algorithms 教程所述特征脸算法之旅吧。 回顾下这个算法:

$ Open+Cvt(Gray)+Cascade(FrontalFace)+ASEFEyes+Affine(128,128,0.33,0.45)+CvtFloat+PCA(0.95)

假如我们想在所搜集的数据上训练该算法。 首先,研究下OpenBR中训练的一些规则。记得每一个算法都是由变换组成的,但不是所有的变换都需要训练。在我们的例子中,Cvt(Gray)并不需要被训练,同样的Open,ASEFEyes,Affine(128,128,0.33,0.45)和CvtFloat也不需要。这些是 UntrainableTransforms(不可训练变换) (变换的子类)。Cascade(FrontalFace)是一种特殊情况。其实一种变换并且因此能够被训练。但是,我们已将其传递一个参数,意寓着应该使用预训练好的模型(正脸)。因此,PCA(0.95)是该算法训练中唯一的可训练变换。进一步说明下,该变换正进行主成分分析并保留了能够代表95%变换的维度。

当然,提供数据训练算法是必须的。 回去想想完整的训练命令。个中一个例子可能是:

$ br -algorithm "Open+Cvt(Gray)+Cascade(FrontalFace)+ASEFEyes+Affine(128,128,0.33,0.45)+CvtFloat+PCA(0.95)" -train training_data.csv EigenFaces

注意算法中使用的-train标志。 -train 至少需要一个参数, 一个训练 Gallery 。注意特定的变换需要带标签的训练数据。 -train 可以不仅需要一个 Gallery ,更多的也能接受:

$ br -algorithm "Open+Cvt(Gray)+Cascade(FrontalFace)+ASEFEyes+Affine(128,128,0.33,0.45)+CvtFloat+PCA(0.95)" -train training_data1.csv training_data2.csv EigenFaces

-train 有个可选的参数: 训练模型的名字 (比如上面的EigenFaces)。 可选的模型文件是一个序列化压缩的二进制文件,其存储了算法训练中学习到的参数。模型文件仅当你的算法字符串使用了一个 LoadStoreTransform (教程后边会加深讨论)时被考虑。  否则,没有一个在算法训练中学习到的参数会被储存!

正如上面简单的讨论,每一个变换都是可训练或者不可训练的(在我们的例子中仅有PCA(0.95)是可训练的)。在训练的时候训练数据按顺序在 不可训练变换 中传递,测试时也是如此。当数据到达训练变换时,训练方法在预处理变换了之后被调用,该方法在新的训练变换中被调用并且数据持续在算法中传递下去。

算法在训练完成后序列化到一个模型文件里(若你指定了一个文件)。算法字符串首先会被序列化以助于算法的再现,接下来是每一个变换所需的参数会被 存储 。注意下仅仅可训练变换需要实现存储,毕竟 不可训练变换 是能够通过算法字符串的描述单独再现的。

然后我们可以使用训练好的算法 -enroll 图像,通过替换字符串为带有模型文件的算法字符串:

$ br -algorithm EigenFaces -enroll enroll_data.csv enroll_data.gal

当我们想要训练和测试算法不一样时,可以使用 LoadStoreTransform(加载存储变换) 来序列化算法字符串中的指定部分。再次以EigenFaces 为例,我们可以指定CvtFloat和PCA(0.95)需要序列化到模型,而其它算法步骤则可以在测试的时候来指定。于是命令就是这样的:

$ br -algorithm "Open+Cvt(Gray)+Cascade(FrontalFace)+ASEFEyes+Affine(128,128,0.33,0.45)+<CvtFloat+PCA(0.95),EigenFaces>" -train training_data.csv

请回忆下,教程中提到的<>就是 LoadStoreTransform 的速记符。同时注意下 LoadStoreTransform 有两个参数:算法字符串与一个可选的模型名字。如果名字没有给定,将会随机生成一个。使用该模型将会是这样的:

$ br -algorithm "Open+Cvt(Gray)+Cascade(FrontalFace)+ASEFEyes+Affine(128,128,0.2,0.55)+<EigenFaces>"

举个例子,由于我们还没有序列化Affine的参数部分, 所以现在它可以在测试时改变!不过需要注意的是,如果这样做了,改变了Affine的参数的话,将会严重削弱算法性能。最后还要提醒的是,当一个 LoadStoreTransform 出现在用于训练的算法字符串中时, OpenBR不会覆写已经存在的指定模型文件。然而,它会加载老的模型文件并将相关的变换视为不可训练(因为变换已经被训练过了呀!)。 这很有用,当你网孤立一个特定的算法步骤时(比如探索(合适的)参数)但并不像对算法中的每一部分每一次迭代都进行再训练。

既然介绍完了一般算法的训练,接下来将会带来OpenBR所支持的著名案例,包括了 FaceRecognition(人脸识别) ,  Age Estimation(年龄估计) , 以及 Gender Estimation(性别估计)

面部识别

此教程有一个关于如何在OpenBR中执行面部识别的 例子 。OpenBR是基于4SF 2 算法来实现面部识别的。请阅读相应的论文来了解此算法的详细内容。

首先,从命令行运行面部识别。打开终端并输入

$ br -algorithm FaceRecognition \
    -compare ../data/MEDS/img/S354-01-t10_01.jpg ../data/MEDS/img/S354-02-t10_01.jpg \
     -compare ../data/MEDS/img/S354-01-t10_01.jpg ../data/MEDS/img/S386-04-t10_01.jpg

够简单了吧?你应该能够看到终端内显示出类似以下的内容

$ Set algorithm to FaceRecognition
$ Loading /usr/local/share/openbr/models/algorithms/FaceRecognition
$ Loading /usr/local/share/openbr/models/transforms//FaceRecognitionExtraction$ Loading /usr/local/share/openbr/models/transforms//FaceRecognitionEmbedding$ Loading /usr/local/share/openbr/models/transforms//FaceRecognitionQuantization$ Comparing ../data/MEDS/img/S354-01-t10_01.jpg and ../data/MEDS/img/S354-02-t10_01.jpg
$ Enrolling ../data/MEDS/img/S354-01-t10_01.jpg to S354-01-t10_01r7Rv4W.mem
$ 100.00%  ELAPSED=00:00:00  REMAINING=00:00:00  COUNT=1$ 100.00%  ELAPSED=00:00:00  REMAINING=00:00:00  COUNT=1$ 1.8812$ Comparing ../data/MEDS/img/S354-01-t10_01.jpg and ../data/MEDS/img/S386-04-t10_01.jpg
$ Enrolling ../data/MEDS/img/S354-01-t10_01.jpg to S354-01-t10_01r7Rv4W.mem
$ 100.00%  ELAPSED=00:00:00  REMAINING=00:00:00  COUNT=1$ 100.00%  ELAPSED=00:00:00  REMAINING=00:00:00  COUNT=1$ 0.571219

所以,究竟什么时面部识别呢?它是实现算法的一个简化的缩写。所有的算法缩写都位于openbr/plugins/core/algorithms.cpp 。

它也可能是:

  • 评估人脸识别性能 (注意此操作需要安装 R ):

    $ br -algorithm FaceRecognition -path ../data/MEDS/img/ \
    -enroll ../data/MEDS/sigset/MEDS_frontal_target.xml target.gal \
    -enroll ../data/MEDS/sigset/MEDS_frontal_query.xml query.gal \
    -compare target.gal query.gal scores.mtx \
    -makeMask ../data/MEDS/sigset/MEDS_frontal_target.xml ../data/MEDS/sigset/MEDS_frontal_query.xml MEDS.mask \
    -eval scores.mtx MEDS.mask Algorithm_Dataset/FaceRecognition_MEDS.csv \
    -plot Algorithm_Dataset/FaceRecognition_MEDS.csv MEDS
  • 执行一个 1:N 的面部识别的查找:

    $ br -algorithm FaceRecognition -enrollAll -enroll ../data/MEDS/img 'meds.gal'$ br -algorithm FaceRecognition -compare meds.gal ../data/MEDS/img/S001-01-t10_01.jpg match_scores.csv
  • 测试一个新的面部识别算法 (基于不同的数据集):

    $ br -algorithm 'Open+Cvt(Gray)+Cascade(FrontalFace)+ASEFEyes+Affine(128,128,0.33,0.45)+(Grid(10,10)+SIFTDescriptor(12)+ByRow)/(Blur(1.1)+Gamma(0.2)+DoG(1,2)+ContrastEq(0.1,10)+LBP(1,2)+RectRegions(8,8,6,6)+Hist(59))+PCA(0.95)+Normalize(L2)+Dup(12)+RndSubspace(0.05,1)+LDA(0.98)+Cat+PCA(0.95)+Normalize(L1)+Quantize:NegativeLogPlusOne(ByteL1)' -train ../data/ATT/img FaceRecognitionATT

全部的命令行API记录在 此处 .

年龄估算

年龄估算与 面部识别 非常的相似。在一些细节上也有重叠。

实现年龄估算你需要从命令行运行:

$ br -algorithm AgeEstimation \
    -enroll ../data/MEDS/img/S354-01-t10_01.jpg ../data/MEDS/img/S001-01-t10_01.jpg metadata.csv

结果会被存储在metadata.csv,其关键字为 'Age'. 记住在 Face Recognition 教程中 AgeEstimation只是所有算法描述中的一个缩写。

运行年龄估算的程序代码在 app/examples/age_estimation.cpp

性别判断

和年龄估算相同,性别判断与 面部识别 在原理上很相似,因此不会作详细的阐述。

在命令行实现年龄判断需要执行以下命令:

$ br -algorithm GenderEstimation \
    -enroll ../data/MEDS/img/S354-01-t10_01.jpg ../data/MEDS/img/S001-01-t10_01.jpg metadata.csv

结果会存储在以'性别'为关键字的 metadata.csv 文件中。记住在 面部识别 教程中的GenderEstimation只是完整的算法描述的缩写。

运行性别判断的程序代码在 app/examples/gender_estimation.cpp

OpenBR做为库

OpenBR公开了一个可以嵌入到个人应用程序的 C++ API 。让我们按步骤通过实例代码app/example/face_recognition.cpp学习作为库来使用OpenBR。

我们主函数从以下开始:

br::Context::initialize(argc, argv)

对所有的基于OpenBR的应用程序来说,这都是第一步,它初始化了全局变量。

QSharedPointer<br::Transform> transform = br::Transform::fromAlgorithm("FaceRecognition");QSharedPointer<br::Distance> distance = br::Distance::fromAlgorithm("FaceRecognition");

这里,我们把算法分拆为 注册 ( Transform :: fromAlgorithm ) 和 对照 ( Distance :: fromAlgorithm )

br::Template queryA("../data/MEDS/img/S354-01-t10_01.jpg");br::Template queryB("../data/MEDS/img/S382-08-t10_01.jpg");br::Template target("../data/MEDS/img/S354-02-t10_01.jpg");

这几行创建注册 模版 。 在这里,模版只简单的存储指定的图片的文件路径到到硬盘。在这个例子中,queryA 描述了相同的人作为target(通常称为 真实匹配 ) 而queryB描绘了来自target(通常称为 冒名匹配 )中不同的人。

queryA >> *transform;
queryB >> *transform;
target >> *transform;

>>在 转换 中对于注册 模版 来说,这是一个很方便的操作。因此,在这一步,我们的 模版 存储了通过FaceRecognition算法注册的图片。

float comparisonA = distance->compare(target, queryA);float comparisonB = distance->compare(target, queryB);

这之后对比我们查询的 模版 与目标 模版 。其结果是,指出相似性的浮点值。

printf("Genuine match score: %.3f\n", comparisonA);printf("Impostor match score: %.3f\n", comparisonB);

在输出结果后,你能够看到comparisonA(在queryA和target之间) 比comparisonB有一个较高的相似值,这正是我们期待的!

br::Context::finalize();

这最后一行在所有的OpenBR 程序中被叫做finalize。此函数用于执行OpenBR的清理。

就这样!你现在可以把面部识别嵌入到你自己的应用程序中了。

评价体系

OpenBR实现了一个完整的兼容 NIST 的评价控制用于评估人脸识别,人脸检测以及面部标记。其目标在于提供一个一致的环境用于学术以及开源社区中算法评估。为了完成这个,OpenBR 定义了接下来的生物识别评价环境标准部分。

  • Signature set -一个签名集 (也叫 sigset  ) 是一份在 MBGC File Overview 中第九页指定的XML文件列表而且在 xmlGallery 中实现了。 Sigsets识别为.xml扩展名。

  • Similarity matrix - 一个相似矩阵 (也叫 simmat ) 是一份 MBGC File Overview 中第12页指定的二进制评分矩阵且在 mtxOutput 中实现了。 Simmats 识别为.mtx扩展名。详情戳 br_eval

  • Mask matrix - 一个掩模矩阵 (也叫 mask  )是一个 MBGC File Overview 中14页指定的二进制矩阵其决定了关于simmat中是否需要匹配的部分。掩模识别为.mask扩展名。详情戳 br_make_maskbr_combine_masks

该评价体系也可以通过命令行使用。相关资料戳 -eval , -evalDetection , -evalLandmarking , -evalClassification , -evalClustering , 或 -evalRegression

我来评几句
登录后评论

已发表评论数()

相关站点

热门文章