教程:如何使用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學習與發展的乾貨


分享到:


相關文章: