您的位置 首页 java

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

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

来源:xaecong

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

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

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

Java /C++ vs. Python

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

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

·

·

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

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

根据TIOBE榜单(),Java除了具有最佳性能外,还是世界上最流行的编程语言。

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

HOG

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

来源:Pexels

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

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

教程:如何使用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训练的模型。它返回一个矩形的向量,该矩形含有从图像中找到的面部。

 dlib::frontal_face_detectordetector = dlib::get_frontal_face_detector();
…
for (auto face : detector(dlibImage))
  

2- 提取并准备面部:

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

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

3- 提取特征向量:

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

输出ResNet模型:

 
template <template <int,template<typename>class,int,typename> classblock, intN, template<typename>classBN, typenameSUBNET>
usingresidual = add_prev1<block<N,BN,1,tag1<SUBNET>>>;

template <template <int,template<typename>class,int,typename> classblock, intN, template<typename>classBN, typenameSUBNET>
usingresidual_down = add_prev2<avg_pool<2,2,2,2,skip1<tag2<block<N,BN,2,tag1<SUBNET>>>>>>;

template <int N, template <typename> classBN, intstride, typenameSUBNET>
usingblock = BN<con<N,3,3,1,1,relu<BN<con<N,3,3,stride,stride,SUBNET>>>>>;

template <int N, typename SUBNET> using ares =relu<residual<block,N,affine,SUBNET>>;
template <int N, typename SUBNET> using ares_down = relu<residual_down<block,N,affine,SUBNET>>;

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

using anet_type = loss_metric<fc_no_bias<128,avg_pool_everything<
alevel0<
alevel1<
alevel2<
alevel3<
alevel4<
max_pool<3,3,2,2,relu<affine<con<32,7,7,2,2,
input_rgb_image_sized<150>
>>>>>>>>>>>>;  

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

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

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

 std::vector<matrix<float,0,1>> face_descriptors1 = net(faces1);  

4- 比较向量

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

 std::vector<sample_pair> edges;
for (size_t i = 0; i <face_descriptors.size(); ++i)
{
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]) < 0.6)
edges.push_back(sample_pair(i,j));
}  

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

示例代码

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

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

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

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

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

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

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

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

如何编译和运行项目

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

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

1- 克隆仓库

 2- git clone   

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

2- Java应用程序

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

 mvn cleanpackage  

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

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

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

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

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

 Javah -jni-classpath C:ProjectNamesrc com.abc.YourClassName  

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

 <plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<compiler arg s>
<arg>-h</arg>
<arg>target/headers</arg>
</compilerArgs>
<source>11</source>
<target>11</target>
</configuration>
</plugin>  

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

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

 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()));  

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

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

来源:Pexels

3- App中的C++部分

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

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

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

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

 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::array2d<rgb_pixel>dlibImage;
dlib::array2d<rgb_pixel>dlibImage2;
dlib::assign_image(dlibImage,dlib::cv_image<bgr_pixel>(*pInputImage));
dlib::assign_image(dlibImage2,dlib::cv_image<bgr_pixel>(*pInputImage2));  

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

·

·

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

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

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

return thereIsAmatch;  

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

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

 cd hog/cplusplus
mkdirbuild
cd build
cmake ..
cmake--build . --config Release  

完成编译后,构建文件夹中将有一个文件“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++在应用程序中实现面部识别

来源:Pexels

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

留言点赞关注

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

如转载,请后台留言,遵守转载规范

文章来源:智云一二三科技

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

文章地址:https://www.zhihuclub.com/187608.shtml

关于作者: 智云科技

热门文章

网站地图