全文共8470字,预计学习时长25分钟
HOG:梯度方向直方图(histogram of orientedgradients)是一种图片描述符格式,它能够汇总图像(例如人脸)的主要特征,从而与相似图像进行比较。
本文以及教程源于两年前,我决定更新源代码使其现代化并再次发布。
本演示将在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除了具有最佳性能外,还是世界上最流行的编程语言。
回到这一技术,我们将看到如何从图像中提取HOG描述符并在不同图像描述符之间进行比较,这是面部比较应用程序的基础。
简单来说,提取出一个描述像素强度(梯度)变化方向和幅度的矩阵,并使用此数据生成直方图。虽然有几种方法可以从图像中提取HOG,但是原始文章使用的是下文的方法:
http://lear.inrialpes.fr/people/triggs/pubs/Dalal-cvpr05.pdf
第一步是将原始图像转换为灰度图,然后过滤线条以删除背景和不感兴趣的其他特征。可以使用OpenCV或dlib等函数库,甚至使用Gimp来完成此操作:
在这张照片中,左上角是原始图像,其在之后被转换为单色图像,最后是带有边缘过滤器(可以是Sobel或其他突出显示线条的过滤器)的图像。为了获得更好的效果,建议仅切割和加工脸部,因为其余部分无关紧要且可能会干扰比较。
对于每个提取的梯度计算强度和幅度的变化方向。
然后,计算直方图,其中类别为倾斜角度(0.20、40、60、80、100、120、140、160),值(票数)为幅度(强度变化)。
绘制该图(这一步没什么意义,但展示效果更好),可以得到此版本的图像:
由此可以得到HOG特性可能的最佳表示形式。
使用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 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向量。可以在以下位置访问使用该技术的原始源代码:http://dlib.net/dnn_face_recognition_ex.cpp.html
输出ResNet模型:
template class,int,typename> classblock, intN, templateclassBN, typenameSUBNET>usingresidual = add_prev1>>;template class,int,typename> classblock, intN, templateclassBN, typenameSUBNET>usingresidual_down = add_prev2>>>>>;template classBN, intstride, typenameSUBNET>usingblock = BN>>>>;template using ares =relu>;template using ares_down = relu>;template using alevel0 = ares_down<256,SUBNET>;template using alevel1 = ares<256,ares<256,ares_down<256,SUBNET>>>;template using alevel2 = ares<128,ares<128,ares_down<128,SUBNET>>>;template using alevel3 = ares<64,ares<64,ares<64,ares_down<64,SUBNET>>>>;template using alevel4 = ares<32,ares<32,ares<32,SUBNET>>>;using anet_type = loss_metric>>>>>>>>>>>;
现在,加载预训练的ResNet模型:
anet_typenet;dlib::deserialize(path+ "/dlib_face_recognition_resnet_model_v1.dat") >> net;
最后,从面部图像中提取特征矩阵:
std::vector> face_descriptors1 = net(faces1);
4- 比较向量
如果要比较人脸来判断它们来自同一个人,则可以通过矩阵向量计算欧几里得距离。如果小于0.6,则图像可能来自同一个人:
std::vector edges;for (size_t i = 0; i
可以预先计算并存储认识的人的图像矩阵,然后在需要识别脸部时搜索数据库。实际上,我使用家庭安全摄像头开发并实现了这样的系统。它运行良好,精度合理。
本文附带一个示例代码,其中包含Java和C ++的部分,该代码比较两个图像并说明它们是否来自同一个人。查看相同人像的执行情况:
这是两张我的照片,相隔至少7年,其中一张我留着山羊胡子和胡须,这并不妨碍人们认出我。C ++函数的返回为“true”,也就是说,它正确地判断了两个图像来自同一个人。
现在来看一个使用不同图像的示例:
我使用了来自维基百科(https://nn.wikipedia.org/wiki/Thomas_Edison)的Thomas Edison的图像,结果是负面的。我用其他几张图像进行了测试,获得了相同的结果。
可以只使用OpenCV库,它实现同样的功能,但是我发现dlib示例代码更准确。
朋友,你需要耐心……非常耐心!我使用的是三星笔记本电脑,第8代I7,具有12 GB RAM和Nvidia芯片组,尽管我没有在项目中使用编译的dlib或OpenCV。如果要开发“生产级”解决方案,请不要浪费任何时间:使用指令集AVX和GPU进行编译!
甚至不要浪费时间尝试在另一个操作系统上进行编译!原始版本是在MacOS上完成的,但我修改了所有内容,使其可以在Ubuntu(18.xx)上运行。问题变少了!我试图在MS Windows上运行,但是,它需要花费更多的工作来调整,性能也不是很好。
1- 克隆仓库
2- git clone https://github.com/cleuton/hogcomparator.git
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中正确配置构建插件即可:
maven-compiler-plugin3.7.0-htarget/headers1111
编译程序时(使用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 ++的通信变得更加容易。
3- App中的C++部分
实际上,可以直接使用Java进行所有操作而无需C ++,也可以使用OpenCV本身来计算HOG矩阵。但是出于性能和实用性的考虑,某些操作使用C++会更好。
我创建了一个“Java绑定”,即一个小的C ++代码,可对其进行编译以生成共享库。为了与Java部分进行通信,需要导入在上一步中生成的标头:
#include#include#include #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::array2ddlibImage;dlib::array2ddlibImage2;dlib::assign_image(dlibImage,dlib::cv_image(*pInputImage));dlib::assign_image(dlibImage2,dlib::cv_image(*pInputImage2));
一个重要的细节是,需要加载两个模型文件,它们从以下地址获取:
· 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时已经进行了解释:检测人脸,提取人脸,计算矩阵然后比较距离:
bool thereIsAmatch = false;for (size_t i = 0; i
编译C++部分有些痛苦……正如我所说,dlib已经内置在CmakeLists.txt中,但是你需要在工作站上安装OpenCV。我正在使用的是Ubuntu 18和OpenCV 3.4.2-1。如果要使用较新版本的OpenCV,需要知道没有对应的Java库。我使用Maven存储库中的org.openpnp项目来促进Java代码与OpenCV的集成。
一旦安装了OpenCV,就可以编译C++部分。为此,将生成的头文件复制到cplusplus文件夹(如果更改了它),然后打开一个终端:
cd hog/cplusplusmkdirbuildcd buildcmake ..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部分变成RESTful服务并将其放置在移动应用程序中,从而提供面部识别作为身份验证的一种方式。
留言点赞关注
我们一起分享AI学习与发展的干货
如转载,请后台留言,遵守转载规范