截止于这篇博文发出的时间,Opencv最新版本为3.2.0,该版本提供了Opencv4Android SDK,这个SDK提供了人脸检测的Java接口,但却没提供人脸识别的接口。所以, 既然这样,就一并把人脸检测和人脸识别的两个过程放到JNI层进行实现。另外,下载下来的Android Pack是没有人脸识别的库支持的,人脸识别的源码在GitHub的分支上,需要自己动手编译,编译过程可以参阅《Opencv4Android人脸识别之opencv_contrib编译》。
1、环境配置
- Opencv
- Android Studio
- Android SDK和Android NDK开发环境
2、设计方法
- 人脸库的创建
在windows平台上,通过人脸检测将每张人脸图片中的人脸检测出来,按照指定的大小将其人脸区域进行缩放,保存。 - 识别器训练
通过第一步创建的人脸库进行人脸识别器的训练,opencv提供了三种识别器,分别是:LBPHFaceRecognizer、EigenFaceRecognizer、FisherFaceRecognize。对人脸库训练完成之后,将其保存为文件(.xml或者yaml)。在人脸识别时, 识别器可以加载改文件,然后再进行人脸识别。 - 创建对应的人脸识别器
创建人脸识别器,并加载第二步中训练的识别器文件。 - 初始化参数
设置人脸大小调整比例,因为EigenFaceRecognizer和FisherFaceRecognizer这两个识别器要求识别的对象尺寸大小要与对应的训练库中的人脸图片大小一致。 - 人脸检测
首先加载人脸检测分类器文件,然后利用Opencv提供的java接口,对Android平台的摄像头进行操作,捕获到每帧画面都以Mat图像容器的形式返回,在JNI层对这个Mat进行人脸检测操作。在人脸检测之前,首先对Mat图像容器进行缩小处理,这样能提升一定的处理速度。 - 人脸识别
对人脸检测的结果进行人脸识别处理,识别结果通过JNI回调Java方法传回,然后显示识别结果。
3、流程设计
4、设计流程详细说明
CSV文件生成
一个CSV文件对应一个人脸库,它描述的是人脸库中每张人脸图片的路径、标签和名字。同一个人的所有的人脸图片对应一个标签。在本设计中,人脸库存储在sdcard中,路径固定,如下图所示,分别为人脸库Aberdeen_105、Aberdeen_140、Aberdeen_250_250、Aberdeen_70:
在一个人脸库中,一个人名对应一个文件夹,文件夹用人名命名,每个人的人脸图片都存储该人名对应的文件夹下。
通过文件遍历生成每个人脸库对应的CSV文件,每个人脸库图片描述为:path;label;name,其中label是int类型。
文件内容如下:
识别器训练
根据上面生成的人脸库对应的CSV文件,选择对应的人脸库进行训练。在JNI层实现训练逻辑的设计,根据传入的CSV文件路径读取文件,读取每张人脸图的描述信息。下面的简单示例展示了读取CSV文件、识别器训练和训练文件的保存。
std::vector<cv::Mat> images;
std::vector<int> labels;
read_csv(csvPath,images,labels,jseparator);
faceRecognizer->train(images,labels);
cv::FileStorage modelFs(stdTrainingFile, FileStorage::WRITE);
faceRecognizer->save(modelFs);
目前,Opencv提供了三种识别器,在识别器训练时,train()函数需要传入该人脸库中所有人脸图(Mat图像容器)和一 一对应的labels。训练完毕,保存文件为.xml或者yaml格式都可以。人脸库中的人脸图片数量决定着生成的识别器训练文件的大小,训练图片应该囊括尽量多的人脸姿态,这样能提升一定的识别率。同时,针对不同的识别器,需要注意几个要点:
LBPHFaceRecognizer
- 要求样本是灰度图。
- 支持model更新。(当有新的人脸加入人脸库时,无需重新对 整个人脸库进行训练,使用update进行更新)
EigenFaceRecognizer
要求样本是灰度图。
训练样本和识别图大小都一致。
不支持model更新。
FisherFaceRecognizer
要求样本是灰度图。
训练样本和识别图片大小一致。
不支持model更新。
训练库中至少包含两个人的。
人脸检测
Opencv的Java SDK提供了方便的接口进行画面捕获,只需在布局中加入Opencv封装的JavaCameraView就可以了:
<org.opencv.android.JavaCameraView
android:id="@+id/java_camera_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
通过回调返回每帧画面:
@Override
public Mat onCameraFrame(CameraBridgeViewBase.CvCameraViewFrame inputFrame) {
Mat mRgba = inputFrame.rgba();
Mat mGray = inputFrame.gray();
}
Opencv提供的几个人脸检测分类器都是使用灰度图,所以进行人脸检测时,要对图片进行灰度化处理,这里能直接获取到灰度图片。一般来说,在JNI层要对传入的检测图片进行通道判断处理,如果不是一个通道,需要对图片灰度化。保证在人脸检测时使用的是灰度图片。另外,在检测之前,一般还需要适当地缩小检测图片,这样能提升一定的检测速度。
自定义的本地检测方法jniDetectMultiScale:
private native void jniDetectMultiScale(long image_nativeObj, long objects_mat_nativeObj,double scaleFactor, int minNeighbors, int flags, double minSize_width,double minSize_height, double maxSize_width, double maxSize_height,float scale);
其中最后一个参数scale代表缩放的大小,缩小为原来的1/scale。上面方法参照的是Opencv的detectMultiScale方法:
void cv::CascadeClassifier::detectMultiScale(InputArray image,
std::vector< Rect > & objects,
double scaleFactor = 1.1,
int minNeighbors = 3,
int flags = 0,
Size minSize = Size(),
Size maxSize = Size()
)
Detects objects of different sizes in the input image. The detected objects are returned as a list of rectangles.
Parameters:
image Matrix of the type CV_8U containing an image where objects are detected.
objects Vector of rectangles where each rectangle contains the detected object, the rectangles may be partially outside the original image.
scaleFactor Parameter specifying how much the image size is reduced at each image scale.
minNeighbors Parameter specifying how many neighbors each candidate rectangle should have to retain it.
flags Parameter with the same meaning for an old cascade as in the function cvHaarDetectObjects. It is not used for a new cascade.
minSize Minimum possible object size. Objects smaller than that are ignored.
maxSize Maximum possible object size. Objects larger than that are ignored. If maxSize == minSize model is evaluated on single scale.
检测到的人脸大小与后面的人脸识别有一定的关系,如果minSize设置过小,虽然可以检测到画面中个别比较小的人脸,但是在人脸识别中,由于人脸过小,该人脸的特征不够明显,可能会导致识别失败。但是具体是如何设置minSize,可能需要根据设备的配置和实际环境。在本设计中,通过获取捕获画面的高度height,然后,以0.1*height作为minSize。大概的逻辑如下:
MatOfRect faces = new MatOfRect(); //create matofrect for faces
mFaceDetect.detectMultiScale(mGray, faces, 1.2, 2, 2, new Size(mAbsoluteFaceSize, mAbsoluteFaceSize), new Size(), 1.4f); //invoke native method to detect faces
List<Rect> facesList = faces.toList(); //get result of detection
在JNI层完成人脸检测之后,直接客户以通过faces这个变量来获取检测结果,最后可以利用Imgproc类提供的rectangle静态方法将检测到的人脸框出来。人脸检测的结果如下图所示:
在本设计中使用到的人脸检测分类器是Opencv提供的haar人脸分类器,是针对正脸的检测分类器,所以,会检测不出侧脸。
人脸识别
通过创建一个对应的人脸识别器来加载之前训练好的识别器文件。目前,Opencv是提供三种识别器,利用load()函数,传入文件路径,进行加载。
识别器的创建
LBPHFaceRecognizer
Ptr<LBPHFaceRecognizer> cv::face::createLBPHFaceRecognizer(
int radius = 1,
int neighbors = 8,
int grid_x = 8,
int grid_y = 8,
double threshold = DBL_MAX
)
Parameters
radius The radius used for building the Circular Local Binary Pattern. The greater the radius, the
neighbors The number of sample points to build a Circular Local Binary Pattern from. An appropriate value is to use 8 sample points. Keep in mind: the more sample points you include, the higher the computational cost.
grid_x The number of cells in the horizontal direction, 8 is a common value used in publications. The more cells, the finer the grid, the higher the dimensionality of the resulting feature vector.
grid_y The number of cells in the vertical direction, 8 is a common value used in publications. The more cells, the finer the grid, the higher the dimensionality of the resulting feature vector.
threshold The threshold applied in the prediction. If the distance to the nearest neighbor is larger than the threshold, this method returns -1.
Notes:
• The Circular Local Binary Patterns (used in training and prediction) expect the data given as grayscale images, use cvtColor to convert between the color spaces.
• This model supports updating.
Model internal data:
• radius see createLBPHFaceRecognizer.
• neighbors see createLBPHFaceRecognizer.
• grid_x see createLBPHFaceRecognizer.
• grid_y see createLBPHFaceRecognizer.
• threshold see createLBPHFaceRecognizer.
• histograms Local Binary Patterns Histograms calculated from the given training data (empty if none was given).
• labels Labels corresponding to the calculated Local Binary
LBPHFaceRecognizer(Local Binary Patterns Histograms)局部二进制模式直方图,它的基本思想是以每个像素为中心,判断该像素与周围像素灰度值大小关系,大于该像素的为1小于该像素的为0,最后进行二进制编码,从而获得整个图像的LBP编码图像,在将LBP图像分为grid_x*grid_y区域,获取每个区域的LBP编码直方图,继而得到整个图像的LBP编码直方图,通过比较不同人脸图像LBP编码直方图达到人脸识别目的。其优点是不会受到光照、缩放、旋转和平移的影响。
所以如果增大了半径radius、neighbors、grid_x和grid_y,都会增大最后的识别器文件。最后,在识别过程也会导致速率下降。
EigenFaceRecognizer
Ptr<BasicFaceRecognizer> cv::face::createEigenFaceRecognizer(int num_components = 0,double threshold =DBL_MAX)
Parameters
num_components The number of components (read: Eigenfaces) kept for this Principal Component Analysis. As a hint: There’s no rule how many components (read: Eigenfaces) should be kept for good reconstruction capabilities. It is based on your input data, so experiment with the number. Keeping 80 components should almost always be sufficient.
threshold The threshold applied in the prediction.
Notes:
• Training and prediction must be done on grayscale images, use cvtColor to convert between the color spaces.
•THE EIGENFACES METHOD MAKES THE ASSUMPTION, THAT THE TRAINING AND TEST IMAGES ARE OF EQUAL SIZE. (caps-lock, because I got so many mails asking for this). You have to make sure your input data has the correct shape, else a meaningful exception is thrown. Use resize to resize the images.
•This model does not support updating.
Model internal data:
• num_components see createEigenFaceRecognizer.
• threshold see createEigenFaceRecognizer.
• eigenvalues The eigenvalues for this Principal Component Analysis (ordered descending).
• eigenvectors The eigenvectors for this Principal Component Analysis (ordered by their eigenvalue).
• mean The sample mean calculated from the training data.
• projections The projections of the training data.
• labels The threshold applied in the prediction. If the distance to the nearest neighbor is larger than the threshold, this method returns -1.
EigenFace(特征脸),它的思想就是把人脸从像素空间变换到另一个空间,在另一个空间中做相似性计算。它选择的空间变换方法是PCA(主成分分析)。它利用PCA得到人脸分布的主要成分,具体实现是对训练集中所有人脸图像的协方差矩阵进行本征值分解,得到对应的本征向量,这些本征向量就是“特征脸”。每个“特征脸”相当于捕捉或者描述人脸之间的一种变化或者特性。这就意味着每个人脸都可以表示为这些特征脸的线性组合。
所以,根据以上的介绍,num_components是计算生成的特征脸个数,如果这个参数越大,将会导致识别器文件越大,识别速率降低。
FisherFaceRecognizer
Ptr<BasicFaceRecognizer> cv::face::createFisherFaceRecognizer(int num_components = 0,double threshold = DBL_MAX)
Parameters:
num_components The number of components (read: Fisherfaces) kept for this Linear Discriminant Analysis with the Fisherfaces criterion. It’s useful to keep all components, that means the number of your classes c (read: subjects, persons you want to recognize). If you leave this at the default (0) or set it to a value less-equal 0 or greater (c-1), it will be set to the correct number (c-1) automatically.
threshold The threshold applied in the prediction. If the distance to the nearest neighbor is larger than the threshold, this method returns -1.
Notes:
• Training and prediction must be done on grayscale images, use cvtColor to convert between the color spaces.
• THE FISHERFACES METHOD MAKES THE ASSUMPTION, THAT THE TRAINING AND TEST IMAGES ARE OF EQUAL SIZE. (caps-lock, because I got so many mails asking for this). You have to make sure your input data has the correct shape, else a meaningful exception is thrown. Use resize to resize the images.
• This model does not support updating.
Model internal data:
• num_components see createFisherFaceRecognizer.
• threshold see createFisherFaceRecognizer.
• eigenvalues The eigenvalues for this Linear Discriminant Analysis (ordered descending).
• eigenvectors The eigenvectors for this Linear Discriminant Analysis (ordered by their eigenvalue).
• mean The sample mean calculated from the training data.
• projections The projections of the training data.
• labels The labels corresponding to the projections.
FisherFace是Ronald Fisher发明,基于LDA(Linear Discriminant Analiys,线性判别分析),LDA与PCA与相似之处,都是对原有数据进行整体降维映射到低维空间。FisherFace的思想与EigenFace相似,最后也是得到每个人脸的众多特征脸。由于个人对其算法没有研究,这里不做过多的介绍。
同样,参数num_conponents的增大会导致识别器训练文件增大,导致识别速率下降。
Note:LDA和PCA两种方法对光照都是比较敏感的,如果以关照均匀的图像作为训练集进行训练,那么训练出来的识别器在判别非均匀对象时,表现会很差。
在以上各个识别器创建中,都有一个Threshold(阈值)的参数传入,这个阈值的意义是在识别一个人脸中,当与人脸库中最相近的那个人脸的距离超出这个阈值时,识别返回负1.这个距离越小,也表示识别结果越接近。
预测识别
加载完识别器训练文件之后,运用predict函数进行人脸识别,opencv分别提供了三个predict函数:
int cv::face::FaceRecognizer::predict ( InputArray
src ) const
void cv::face::FaceRecognizer::predict ( InputArray
src,
int & label,
double & confidence
) const
Predicts a label and associated confidence (e.g. distance) for a given input image.
Parameters
src Sample image to get a prediction from.
label The predicted label for the given image.
confidence Associated confidence (e.g. distance) for the predicted label.
virtual void cv::face::FaceRecognizer::predict ( InputArray
src,
Ptr< PredictCollector > collector
) const
• if implemented - send all result of prediction to collector that can be used for somehow custom result handling
Parameters
src Sample image to get a prediction from.
collector User-defined collector object that accepts all results
To implement this method u just have to do same internal cycle as in predict(InputArray src, CV_OUT int &label, CV_OUT double &confidence) but not try to get “best@ result, just resend it to caller side with given collector.
第一个函数只需传入人脸图片,返回值是识别结果label。第二个函数没有返回值,传入参数为人脸图片、label变量的引用和confidence变量的引用,识别结果可以直接访问label和confidence。第三个函数没有返回值,传入参数为人脸图片和只想PredictCollector对象的指针,最后可以通过这个对象获取到label和confidence。
除了第一个函数,后面两个函数可以说与confidence都有关联,正如上面说明的识别器创建时提到的Threshold阈值的设定,这个confidence正是反映了识别对象与人脸库中最相近的那个人脸距离,最后通过与阈值比较后返回label。 confidence在不同的识别器中的表现不一样,同时,它的表现还与对应的训练库中人脸的尺寸和识别环境有关,比如光照条件等。那么,阈值Threashold的设定同时受到这些因素的影响。到底哪个阈值才能真正地区别识别在训练集和不在训练集的人呢?我觉得这需要更深入的研究和更加灵活的调整,在本设计中,测试环境相对比较单一,对于阈值的调整也比较限制。以下是通过采集相同光照条件下,不同人脸图尺寸时,confidence返回值大小。
下面对FisherFaceRecognizer和LBHFaceRecognizer两个识别器的confidence返回值进行统计绘图(注意,下面数据是我的在设计中的测试数据)
下面几个图表是FisherFaceRecognizer识别器在训练库中人脸尺寸分别为70_70、105_105和140_140,识别结果正确的confidence返回值的数据:
从以上三张图表可以看出在识别同一个人脸是时,不同的Size会有不同弄的confidence返回值。Size为70_70基本聚集在500–700之间,size为105_105基本聚集在800–1000之间,size为140_140基本聚集在1200–1400之间。
下面三张图表是LBPHFaceRecognizer识别器在训练库中人脸图片尺寸分别是70_70、105_105和140_140,识别结果正确的confidence返回值数据:
从以上三个图表能看到,LBPHFaceRecognizer识别器的识别结果confidence返回值在三个尺寸中的表现相差不大,而且随着尺寸的增大有递减的表现。尺寸为70_70的confidence基本聚集在120-135之间,尺寸为105_105基本聚集在100-120之间,尺寸为140_140基本聚集在再100-120之间,规律性没那么强,当然,这种无规律的结果或者说这些confidence的聚集区域可能是我该次测试出现的偶然性。
在我的手机上,在训练集不大的情况下(就几个人的训练集),LBPHReceRecognizer的表现优于其他两个识别器,同时,在前面已经介绍过了,如果测试环境和训练集中图片的采集环境变化较大的话,识别效果会非常差,而LBPH对测试环境不是很苛刻。
识别结果展示:
5、遗留问题
T-API没有硬件加速效果
Opencv3.0之后,提供了T-API,T-API的出现让开发者更加容易对代码进行硬件加速,并且能有更好的兼容性,意味着如果搭载平台不支持硬件加速,使用T-API编写的代码依然能正确运行。当然,我手机搭载了QUALCOMM Adreno GPU,OpenCL 1.1,同样能支持GPU加速。但是在该项目中,同样使用T-API进行编码,但是并没有加速效果。同样地,在Windows平台下,依然如此。
对比度和亮度增强没有得到应用
对捕获到的图片做对比度和亮度增强,能一定程度上提高检测和识别精度,但是,在测试中,对比度和亮度的增强带来了更加的卡顿。所以在本设计中没有得到应用。
没有过多的深入研究
Opencv在人脸检测和人脸识别都分别提供了三种方式,LBPHFace、EigenFace和FIsherFace,由于没有相关的研究,只能按照相关的说明进行项目需求开发,在很多问题上的,没有可靠的解决方法。
训练集的限制
目前使用的人脸训练集都是来自网上的数据库,没有自己采集的人脸训练集,同时,训练集的大小也相对很小,所以目前人脸识别器训练文件也相对比较小。如果训练集增大之后,训练文件也随之增大,人脸识别的速率也将会下降。因为EigenFaceRecognizer和FisherFaceRecognizer对训练集图片的采集环境和测试环境都比较挑剔。在本项目中,使用了LBPHFaceRecognizer进行项目开展,但是由于A920c的性能限制,LBPH的训练集不能太大,所以,如果增大训练集需要更加好的性能平台来进行识别测试。
代码资源
本设计中的全部代码都上传到iCSDN中,供大家免费下载:CSDN