教程:如何使用Java和C++在应用程序中实现面部识别

全文共8470字,预计学习时长

25分钟


教程:如何使用Java和C++在应用程序中实现面部识别


HOG:梯度方向直方图(histogram of orientedgradients)是一种图片描述符格式,它能够汇总图像(例如人脸)的主要特征,从而与相似图像进行比较。

本文以及教程源于两年前,我决定更新源代码使其现代化并再次发布。

教程:如何使用Java和C++在应用程序中实现面部识别

Java/C++ vs. Python

本演示将在C++程序中使用dlib库来比较两个面部图像的HOG矩阵,并返回它们之间的相似度。因为JNI(Java本机接口集成)是在进程内完成的,并且具有高性能,所以本演示还会使用Java来“封装”C++函数。

我已经看到了几种基于Python的图像处理解决方案,特别是关于面部比较甚至面部识别的方案。这些解决方案使用Python作为主要的编程语言,从dlib或OpenCV库中调用函数。实际上,所有这些解决方案都基于Github上提供的一些Python库,例如:

· https://github.com/ageitgey/face_recognition;

· https://github.com/chanddu/Face-Recognition;

尽管它们具有便于开发的优点,但这些库可能会损害图像处理解决方案的性能,尤其是在主机没有GPU的情况下。正如我在上一篇文章中提到的,众所周知,主要的Python解释器(例如CPython和PyPy)含有GIL(全局解释器锁)。此外,与Java应用程序相比,Python应用程序的性能可能是另一个问题。

因此,在C ++中实现识别功能并封装在Java代码中,将其作为RESTful服务公开更合理。毕竟,在C ++中这样做不会为解决方案增加价值,而只会增加复杂性。

根据TIOBE榜单(https://www.tiobe.com/tiobe-index/),Java除了具有最佳性能外,还是世界上最流行的编程语言。

教程:如何使用Java和C++在应用程序中实现面部识别

HOG

教程:如何使用Java和C++在应用程序中实现面部识别

回到这一技术,我们将看到如何从图像中提取HOG描述符并在不同图像描述符之间进行比较,这是面部比较应用程序的基础。

简单来说,提取出一个描述像素强度(梯度)变化方向和幅度的矩阵,并使用此数据生成直方图。虽然有几种方法可以从图像中提取HOG,但是原始文章使用的是下文的方法:

http://lear.inrialpes.fr/people/triggs/pubs/Dalal-cvpr05.pdf

教程:如何使用Java和C++在应用程序中实现面部识别

方法

第一步是将原始图像转换为灰度图,然后过滤线条以删除背景和不感兴趣的其他特征。可以使用OpenCV或dlib等函数库,甚至使用Gimp来完成此操作:

教程:如何使用Java和C++在应用程序中实现面部识别


在这张照片中,左上角是原始图像,其在之后被转换为单色图像,最后是带有边缘过滤器(可以是Sobel或其他突出显示线条的过滤器)的图像。为了获得更好的效果,建议仅切割和加工脸部,因为其余部分无关紧要且可能会干扰比较。

教程:如何使用Java和C++在应用程序中实现面部识别


对于每个提取的梯度计算强度和幅度的变化方向。

教程:如何使用Java和C++在应用程序中实现面部识别


然后,计算直方图,其中类别为倾斜角度(0.20、40、60、80、100、120、140、160),值(票数)为幅度(强度变化)。

绘制该图(这一步没什么意义,但展示效果更好),可以得到此版本的图像:

教程:如何使用Java和C++在应用程序中实现面部识别


由此可以得到HOG特性可能的最佳表示形式。

教程:如何使用Java和C++在应用程序中实现面部识别

使用dlib

使用dlib,必须查看哪些对象和函数可以帮助分析图像并提取其HOG矩阵。

1- 检测人脸:

Dlib库含有frontal_face_detector,这是一个使用iBUG 300-W数据集进行HOG和SVG训练的模型。它返回一个矩形的向量,该矩形含有从图像中找到的面部。


<code>dlib::frontal_face_detectordetector = dlib::get_frontal_face_detector();

for (auto face : detector(dlibImage))
/<code>


2- 提取并准备面部:

必须获取生成的矩形,从原始图像中提取出面部并适当地旋转和缩放。为此,使用之前训练的具有68个面部特征或“面部标志”模型:


<code>...
dlib::frontal_face_detectordetector = dlib::get_frontal_face_detector();
dlib::shape_predictorsp;
dlib::deserialize(path+ "/shape_predictor_5_face_landmarks.dat") >> sp;
...
matrix face_chip;
dlib::extract_image_chip(dlibImage,dlib::get_face_chip_details(shape,150,0.25), face_chip);
/<code>


3- 提取特征向量:

有一个非常有趣的示例,它使用代码中实现的神经网络和预先训练的ResNet v1模型(“ dlib_face_recognition_resnet_model_v1.dat”)从图像中提取HOG向量。可以在以下位置访问使用该技术的原始源代码:http://dlib.net/dnn_face_recognition_ex.cpp.html

输出ResNet模型:

<code>
template <template>class,int,typename> classblock, intN, template<typename>classBN, typenameSUBNET>
usingresidual = add_prev1<block>>>;

template <template>class,int,typename> classblock, intN, template<typename>classBN, typenameSUBNET>
usingresidual_down = add_prev2>>>>>;

template  classBN, intstride, typenameSUBNET>
usingblock = BN>>>>;

template  using ares =relu<residual>>;
template  using ares_down = relu<residual>>;

template <typename> using alevel0 = ares_down<256,SUBNET>;
template <typename> using alevel1 = ares<256,ares<256,ares_down<256,SUBNET>>>;
template <typename> using alevel2 = ares<128,ares<128,ares_down<128,SUBNET>>>;
template <typename> using alevel3 = ares<64,ares<64,ares<64,ares_down<64,SUBNET>>>>;
template <typename> using alevel4 = ares<32,ares<32,ares<32,SUBNET>>>;

using anet_type = loss_metricalevel0<
alevel1<
alevel2<
alevel3<
alevel4<
max_pool<3,3,2,2,relu<affine>input_rgb_image_sized<150>
>>>>>>>>>>>>;/<affine>
/<typename>/<typename>/<typename>/<typename>/<typename>/<residual>
/<residual>
/<typename>/<template>/<block>/<typename>/<template>/<code>

现在,加载预训练的ResNet模型:


<code>anet_typenet;
dlib::deserialize(path+ "/dlib_face_recognition_resnet_model_v1.dat") >> net;/<code>


最后,从面部图像中提取特征矩阵:


<code>std::vector<matrix>> face_descriptors1 = net(faces1);/<matrix>/<code>


4- 比较向量

如果要比较人脸来判断它们来自同一个人,则可以通过矩阵向量计算欧几里得距离。如果小于0.6,则图像可能来自同一个人:


<code>std::vector<sample> edges;
for (size_t i = 0; i <face>{
for (size_t j = i; j < face_descriptors.size(); ++j)
{
// Facesare connected in the graph if they are close enough. Here we check if
// thedistance between two face descriptors is less than 0.6, which is the
//decision threshold the network was trained to use. Although you can
//certainly use any other threshold you find useful.
if(length(face_descriptors[i]-face_descriptors[j]) edges.push_back(sample_pair(i,j));
}/<face>/<sample>/<code>


可以预先计算并存储认识的人的图像矩阵,然后在需要识别脸部时搜索数据库。实际上,我使用家庭安全摄像头开发并实现了这样的系统。它运行良好,精度合理。

示例代码

本文附带一个示例代码,其中包含Java和C ++的部分,该代码比较两个图像并说明它们是否来自同一个人。查看相同人像的执行情况:

教程:如何使用Java和C++在应用程序中实现面部识别


这是两张我的照片,相隔至少7年,其中一张我留着山羊胡子和胡须,这并不妨碍人们认出我。C ++函数的返回为“true”,也就是说,它正确地判断了两个图像来自同一个人。

现在来看一个使用不同图像的示例:

教程:如何使用Java和C++在应用程序中实现面部识别


我使用了来自维基百科(https://nn.wikipedia.org/wiki/Thomas_Edison)的Thomas Edison的图像,结果是负面的。我用其他几张图像进行了测试,获得了相同的结果。

可以只使用OpenCV库,它实现同样的功能,但是我发现dlib示例代码更准确。

教程:如何使用Java和C++在应用程序中实现面部识别


如何编译和运行项目

朋友,你需要耐心……非常耐心!我使用的是三星笔记本电脑,第8代I7,具有12 GB RAM和Nvidia芯片组,尽管我没有在项目中使用编译的dlib或OpenCV。如果要开发“生产级”解决方案,请不要浪费任何时间:使用指令集AVX和GPU进行编译!

甚至不要浪费时间尝试在另一个操作系统上进行编译!原始版本是在MacOS上完成的,但我修改了所有内容,使其可以在Ubuntu(18.xx)上运行。问题变少了!我试图在MS Windows上运行,但是,它需要花费更多的工作来调整,性能也不是很好。

1- 克隆仓库


<code>2- git clone https://github.com/cleuton/hogcomparator.git/<code>


dlib代码已包含在内。它含有Java应用程序代码和实现了调用的原生方法的C ++函数。

2- Java应用程序

编译刚刚运行的应用程序:


<code>mvn cleanpackage/<code>


或者,将Maven项目导入到Eclipse工作区中。此应用程序使用JNI调用原生方法:


<code>static {
nu.pattern.OpenCV.loadShared();
System.loadLibrary("hogcomparator");
}

// Nativemethod implemented by a C++ library:
privatenativebooleancompareFaces(long addressPhoto1, longaddressPhoto2);/<code>


为了使Java调用“compareFaces”方法,需要创建一个名为“hogcomparator”的共享库(或DLL,如果要坚持使用MS Windows)。该库应以JNI(Java本机接口)制定的方式实现原生方法“compareFaces”。为此,需要创建一个包含方法声明的C ++标头。在仓库中,这些都已经完成,但是如果你需要创建另一个应用程序,最好看看我是如何做的。

为了创建标头,之前使用的是javah程序:


<code>Javah -jni-classpath C:\\ProjectName\\src com.abc.YourClassName/<code>


自从Java 10开始javah已不存在!现在,使用javac编译器-h选项。但是,因为我在使用Maven,只需在pom.xml中正确配置构建插件即可:


<code><plugin>
<artifactid>maven-compiler-plugin/<artifactid>
<version>3.7.0/<version>
<configuration>
<compilerargs>
-h
target/headers
/<compilerargs>

<source>11/<source>
<target>11/<target>
/<configuration>
/<plugin>/<code>


编译程序时(使用mvn clean程序包或通过eclipse),在目标/标头文件夹中找到文件:“com_obomprogramador_hog_HogComparator.h”。该文件需要复制到plasta hog / cplusplus,并将导入cpp源。

使用OpenCV读取图像并将它们传递给原生方法。使用OpenCV中的Mat类和imread函数来读取图像:


<code>Mat photo1= imread(args[0]);
Mat photo2= imread(args[1]);
HogComparatorhg = new HogComparator();
System.out.println("Images are from the same person? "
+hg.compareFaces(photo1.getNativeObjAddr(),photo2.getNativeObjAddr()));/<code>


原生方法接收内存中Mat结构的地址,可以使用getNativeObjAddr()方法实现。这使与C ++的通信变得更加容易。

教程:如何使用Java和C++在应用程序中实现面部识别


3- App中的C++部分

实际上,可以直接使用Java进行所有操作而无需C ++,也可以使用OpenCV本身来计算HOG矩阵。但是出于性能和实用性的考虑,某些操作使用C++会更好。

我创建了一个“Java绑定”,即一个小的C ++代码,可对其进行编译以生成共享库。为了与Java部分进行通信,需要导入在上一步中生成的标头:


<code>#include<jni.h>
#include<iostream>
#include <cstdlib>
#include "com_obomprogramador_hog_HogComparator.h"/<cstdlib>/<iostream>/<jni.h>/<code>


C ++代码接收Mat结构的地址,将其转换为dlib使用的类型array2d:


<code>JNIEXPORT jboolean JNICALL Java_com_obomprogramador_hog_HogComparator_compareFaces
(JNIEnv *env, jobject obj, jlong addFoto1, jlong addFoto2) {
constchar* pPath = getenv ("HOGCOMPARATOR_PATH");
std::stringpath(pPath);
cv::Mat*pInputImage = (cv::Mat*)addFoto1;
cv::Mat*pInputImage2 = (cv::Mat*)addFoto2;
dlib::array2ddlibImage;

dlib::array2ddlibImage2;
dlib::assign_image(dlibImage,dlib::cv_image(*pInputImage));
dlib::assign_image(dlibImage2,dlib::cv_image(*pInputImage2));
/<code>


一个重要的细节是,需要加载两个模型文件,它们从以下地址获取:

· http://dlib.net/files/shape_predictor_5_face_landmarks.dat.bz2

· http://dlib.net/files/dlib_face_recognition_resnet_model_v1.dat.bz2

只需解压缩,然后创建一个名为HOGCOMPARATOR_PATH的环境变量,指向两个文件解压缩的路径。

其余部分在谈到dlib时已经进行了解释:检测人脸,提取人脸,计算矩阵然后比较距离:


<code>bool thereIsAmatch = false;
for (size_t i = 0; i <face>{
for (size_t j = i; j < face_descriptors2.size(); ++j)
{
if (length(face_descriptors1[i]-face_descriptors2[j])thereIsAmatch= true;
}
}

return thereIsAmatch;/<face>/<code>


编译C++部分有些痛苦……正如我所说,dlib已经内置在CmakeLists.txt中,但是你需要在工作站上安装OpenCV。我正在使用的是Ubuntu 18和OpenCV 3.4.2-1。如果要使用较新版本的OpenCV,需要知道没有对应的Java库。我使用Maven存储库中的org.openpnp项目来促进Java代码与OpenCV的集成。

一旦安装了OpenCV,就可以编译C++部分。为此,将生成的头文件复制到cplusplus文件夹(如果更改了它),然后打开一个终端:


<code>cd hog/cplusplus
mkdirbuild
cd build
cmake ..
cmake--build . --config Release/<code>


完成编译后,构建文件夹中将有一个文件“libhogcomparator.so”。这是实现原生方法的库。

要在Eclipse中运行项目,请打开RUN菜单,然后点击RUN CONFIGURATIONS。创建运行“Java应用程序”的配置,选择主类(HogComparator)并添加两个参数,它们是要比较的图像的路径。还要为JVM添加一个参数-Djava.library.path,指向cplusplus / build文件夹。最后,创建指向两个模板文件解压缩路径的环境变量。

命令行参数,例如:

/home/cleuton/Documentos/projetos/hog/etc/cleuton.jpg/home/cleuton/Documentos/projetos/hog/etc/thomas_edison.jpg

“libhogcomparator”的位置参数:

-Djava.library.path = / home / cleuton /Documents / projects / hog / cplusplus / build

环境变量:HOGCOMPARATOR_PATH = / home / cleuton /Documents / projects / hog / cplusplus / build

教程:如何使用Java和C++在应用程序中实现面部识别

总结

教程:如何使用Java和C++在应用程序中实现面部识别


这个简短的教程展示了如何使用Java和C++以出色的性能在应用程序中实现面部识别。现在,你可以将Java部分变成RESTful服务并将其放置在移动应用程序中,从而提供面部识别作为身份验证的一种方式。

教程:如何使用Java和C++在应用程序中实现面部识别

我们一起分享AI学习与发展的干货


分享到:


相關文章: