java opencv边缘提取_运用HOG通过Java实现面部识别比较

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

java opencv边缘提取_运用HOG通过Java实现面部识别比较_第1张图片

来源:xaecong

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

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

a7b1bf9eb7bdb07929fee77f0e92027f.png

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除了具有最佳性能外,还是世界上最流行的编程语言。

a7b1bf9eb7bdb07929fee77f0e92027f.png

HOG

java opencv边缘提取_运用HOG通过Java实现面部识别比较_第2张图片

来源:Pexels

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

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

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

a7b1bf9eb7bdb07929fee77f0e92027f.png

方法

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

java opencv边缘提取_运用HOG通过Java实现面部识别比较_第3张图片

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

java opencv边缘提取_运用HOG通过Java实现面部识别比较_第4张图片

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

java opencv边缘提取_运用HOG通过Java实现面部识别比较_第5张图片

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

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

java opencv边缘提取_运用HOG通过Java实现面部识别比较_第6张图片

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

a7b1bf9eb7bdb07929fee77f0e92027f.png

使用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 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 ++的部分,该代码比较两个图像并说明它们是否来自同一个人。查看相同人像的执行情况:

java opencv边缘提取_运用HOG通过Java实现面部识别比较_第7张图片

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

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

java opencv边缘提取_运用HOG通过Java实现面部识别比较_第8张图片

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

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

a7b1bf9eb7bdb07929fee77f0e92027f.png

如何编译和运行项目

朋友,你需要耐心……非常耐心!我使用的是三星笔记本电脑,第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 ++的通信变得更加容易。

java opencv边缘提取_运用HOG通过Java实现面部识别比较_第9张图片

来源:Pexels

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

a7b1bf9eb7bdb07929fee77f0e92027f.png

总结

java opencv边缘提取_运用HOG通过Java实现面部识别比较_第10张图片

来源:Pexels

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

6ec839715e170f6d2568fd53b02eb8ef.png

留言点赞关注

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

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

你可能感兴趣的:(java,opencv边缘提取)