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

news/2024/11/8 20:58:37/

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

bb2428a59e8845dda31b31c11ae6a78c.png来源:xaecong

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

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

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

ee7e1dcea649dc07a65b22f16b8baf38.png

HOG

32c9523b950e1f23d718720e3a7beb30.png来源:Pexels

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

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

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

ee7e1dcea649dc07a65b22f16b8baf38.png

方法

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

f8dc358da864ee135f23f967e286abd9.png

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

83461a2d77c39c279d09d6aef998a3d6.png

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

0150c7c00305047fcbb66aaa437d96d6.png

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

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

c6638c70e6d7ad9cc37daa3e98cc2ffe.png

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

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

b6ce895e412987cdd56efdcdbcd26c78.png

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

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

b19eb1014540a1a69a0d217328f5ceb4.png

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

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

ee7e1dcea649dc07a65b22f16b8baf38.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.gitdlib代码已包含在内。它含有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:\ProjectName\src 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 ++的通信变得更加容易。

9e4887a5a9e7116ad4f055fb9a562fbb.png来源: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文件夹(如果更改了它),然后打开一个终端:

cdhog/cplusplusmkdirbuildcdbuildcmake..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

ee7e1dcea649dc07a65b22f16b8baf38.png

总结

aa54c771600ac3c2fa6dc2653f5fd308.png

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

b38daaef0f27cb54608d5c351bf2fe4e.png

留言点赞关注

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

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


http://www.ppmy.cn/news/721790.html

相关文章

广州4.6及考试

广东话46级考试题 [%title%] [%count%]票 [[%percent%]&#xff05;] &#xff08;看看你能回答多少题&#xff09; 一、选择题&#xff1a;&#xff08;40%&#xff09; 01.“缩骨”的意思是 &#xff08; &#xff09; A.形容人很瘦 B.形容人很穷 C.形容人很吝啬 D.形容…

【C4D周练作业061-070】用C4D做了个锤子~

第61份作业 作者&#xff1a;渣渣J 打卡作业第二张 上次的锤子小伙伴们说要放在深色的场景里面更突出&#xff0c;但是锤子的糙感不知道怎么调试&#xff0c;金属感确实有点假&#xff0c;应该不能直接加贴图&#xff0c;可乐瓶子的水珠材质不会调 LXT&#xff1a;铁锤感觉没有…

c语言编程框架_如何学习新的编程语言或框架

c语言编程框架 每天都有新的编程语言诞生。 Dart&#xff0c;Go&#xff0c;Kotlin&#xff0c;Elixir等&#xff01; 打破学习曲线可能很困难&#xff0c;但是有一些技巧可以帮助您度过难关。 您需要采取一些步骤来加速学习过程&#xff0c;并提高您正在学习的编程语言或框架…

美媒体评出太空十大怪现象

据美国宇航局太空网 6 日报道&#xff0c;人类探索太空决不仅仅是为了移民太空&#xff0c;也是为了更好的理解我们 人类自己&#xff0c;因为直到今天&#xff0c;无论从宇宙的起源还是人类的起源&#xff0c;仍有许多未解之谜。太空也有许多奇怪的现象&#xff0c;而以下十种…

意味深长的两张照片

是走&#xff1f;还是留&#xff1f; 无论篮球还是足球&#xff0c;都有球员去或留的问题&#xff0c;除了商业&#xff0c;另一个就是球员的个人梦想追求。 骑士和魔术还在这厢鏖战&#xff0c;骑士老板那边早已谈起了交易&#xff0c;不过这次交易不是一两个球员&#xff0c;…

[转] 粤语八级题,你会做岩几多题??

呢篇野真系好鬼搞笑!!笑到我肚痛... 一、选择题&#xff1a;&#xff08;40分&#xff09; 01.“缩骨”的意思是 &#xff08; &#xff09; A.形容人很瘦 B.形容人很穷 C.形容人很吝啬 D.形容人很小心、谨慎 02.“鸡咁脚”的意思是 &#xff08; &#xff09; A.形容脚像鸡一样…

C/C++笔试题

微软亚洲技术中心的面试题&#xff01;&#xff01;&#xff01; 1&#xff0e;进程和线程的差别。 线程是指进程内的一个执行单元,也是进程内的可调度实体. 与进程的区别: (1)调度&#xff1a;线程作为调度和分配的基本单位&#xff0c;进程作为拥有资源的基本单位 (2)并发性&…

照片尺寸

1寸 3.9X2.6 2寸 5.4X3.6 5寸&#xff1a;4X5 英寸 6寸&#xff1a;4X6 英寸 7寸&#xff1a;5X7 英寸 8寸&#xff1a;6X8 英寸 1寸是3.75X2.5cm 2寸是3.5*5.3 1寸 2.5*3.5cm 413*295 大头照 3.3*2.2 390*260 2寸 3.5*5.3cm 626*413 小2寸&#xff08;护照&#xff09; 4.8*3.…