(1)OpenCV人脸检测C++程序流程:
OpenCV的人脸检测程序采用了Viola & Jones人脸检测方法,主要是调用训练好的瀑布级联分类器cascade来进行模式匹配。
cvHaarDetectObjects先将图像灰度化,根据传入参数判断是否进行canny边缘处理(默认不使用),再进行匹配。匹配后收集找出的匹配块,过滤噪声,计算相邻个数如果超过了规定值(传入的min_neighbors)就当成输出结果,否则删去。
匹配循环:将匹配分类器放大scale(传入值)倍,同时原图缩小scale倍,进行匹配,直到匹配分类器的大小大于原图,则返回匹配结果。匹配的时候调用cvRunHaarClassifierCascade来进行匹配,将所有结果存入CvSeq* pcvSeqFaces(可动态增长元素序列),将结果传给cvHaarDetectObjects。
cvRunHaarClassifierCascade函数整体是根据传入的图像和cascade来进行匹配。并且可以根据传入的cascade类型不同(树型、stump(不完整的树)或其他的),进行不同的匹配方式。函数cvRunHaarClassifierCascade 用于对单幅图片的检测。在函数调用前首先利用cvSetImagesForHaarClassifierCascade设定积分图和合适的比例系数 (>= 窗口尺寸)。当分析的矩形框全部通过级联分类器每一层的时返回正值(这是一个候选目标),否则返回0或负值。
Haar分类器的训练是独立于人脸检测过程的。分类器的训练分为两个阶段:
A.创建样本,用OpenCV自带的creatsamples.exe完成。
B.训练分类器,生成xml文件,由OpenCV自带的haartraining.exe完成。
同时,OpenCV中采用的训练算法adaboost是gentle adaboost,为最适合人脸检测的方案。
基于 OpenCV 的人脸检测主要完成 3 部分功能 , 即加载分类器、 加载待检测图象以及检测并标示。本算法使用 OpenCV 中提供的 “ haarcascade_frontalface_alt. xml ” 文件存储的目标检测分类 , 用 cvLoad 函数载入后 , 进行强制类型转换。OpenCV 中提供的用于检测图像中目标的函数是 cvHaarDetectObjects , 该函数使用指针对某目标物体 ( 如人脸) 训练的级联分类器在图象中找到包含目标物体的矩形区域 , 并将这些区域作为一序列的圆形框返回,实现代码如下 :
#include <opencv2/opencv.hpp>
#include <cstdio>
#include <cstdlib>
#include <Windows.h>
int main(){
// 加载Haar特征检测分类器
// haarcascade_frontalface_alt.xml系OpenCV自带的分类器,下面是文件路径
const char *pstrCascadeFileName ="D:\\Program Files\\opencv\\opencv\\sources\\data\\haarcascades\\haarcascade_frontalface_alt.xml";
// 创建瀑布级联分类器cascade来进行模式匹配
CvHaarClassifierCascade *pHaarCascade =NULL;
pHaarCascade =(CvHaarClassifierCascade*)cvLoad(pstrCascadeFileName);
// 载入图像
// 创建一个IpIImage 图像数据结构进行处理
const char *pstrImageName ="101.jpg";
IplImage *pSrcImage = cvLoadImage(pstrImageName,CV_LOAD_IMAGE_UNCHANGED);
// 输出图像
IplImage *pGrayImage =cvCreateImage(cvGetSize(pSrcImage), IPL_DEPTH_8U, 1);
cvCvtColor(pSrcImage, pGrayImage,CV_BGR2GRAY);
// 人脸识别与标记
if (pHaarCascade != NULL)
{
CvScalar FaceCirclecolors[] =
{ //标记人脸框的颜色
{{0, 0, 255}}, {{0, 128,255}}, {{0, 255, 255}}, {{0, 255, 0}}, {{255, 128, 0}}, {{255, 255, 0}}, {{255, 0, 0}}, {{255, 0, 255}}
};
//创建一个新的内存存储区 , 参数为0 则采用默认设置
CvMemStorage *pcvMStorage =cvCreateMemStorage(0);
cvClearMemStorage(pcvMStorage);
// 处理的开始和结束时间
DWORD dwTimeBegin, dwTimeEnd;
dwTimeBegin = GetTickCount();
// 人脸识别核心函数(opencv自带)cvHaarDetectObjects
//返回一个包含检测结果(图片中的位置)信息的结构体数据 pcvSeqFaces
//具体算法实现见opencv源码D:\ProgramFiles\opencv\opencv\sources\modules\objdetect\src\haar.cpp
CvSeq *pcvSeqFaces = cvHaarDetectObjects(pGrayImage,pHaarCascade, pcvMStorage);
dwTimeEnd = GetTickCount();
printf("人脸个数: %d 识别用时: %d ms\n", pcvSeqFaces->total,dwTimeEnd - dwTimeBegin);
// 开始标记
for(int i = 0; i<pcvSeqFaces->total; i++)
{ // 逐次获取位置信息
CvRect* r =(CvRect*)cvGetSeqElem(pcvSeqFaces, i);
CvPoint center;
int radius;
center.x = cvRound((r->x +r->width * 0.5));
center.y = cvRound((r->y +r->height * 0.5));
radius = cvRound((r->width +r->height) * 0.25);
cvCircle(pSrcImage, center, radius,FaceCirclecolors[i % 8], 2);
}
cvReleaseMemStorage(&pcvMStorage); //释放内存存储区
}
const char *pstrWindowsTitle = "人脸识别";
//创建处理结果展示窗口
cvNamedWindow(pstrWindowsTitle,CV_WINDOW_AUTOSIZE);
cvShowImage(pstrWindowsTitle,pSrcImage);
cvWaitKey(0);
cvDestroyWindow(pstrWindowsTitle); //释放窗口资源
cvReleaseImage(&pSrcImage); //释放图片资源
cvReleaseImage(&pGrayImage);
return 0;
}
(2)OpenCV 移植到Android
首先,需确定搭建配置完成 Android 开发环境所需的 JDK、EclipseIDE、Android SDK 和 ADT。其次,确定安装 CDT 插件,以供 Eclipse 能够开发 C++。
Android 需要使用JNI 编写本地代码,并使用 Android NDK进行交叉编译。Android NDK 有多种版本,本实验使用的计算机是 32 位的 Windows 操作系统,所以选择版本为 android-ndk-r10-windows-x86。
OpenCV从 2.2 版本后支持在 Android 下开发,本系统采用的是 OpenCV-2.4.10 版本。在该版本解压缩后的文件夹下,有4 个子文件夹,它们分别是 sdk、samples、doc 和apk。Sdk 是OpenCV所需的类库;samples 是 OpenCV 应用的例子;doc 是OpenCV 类库的使用说明、api文档和一些图像等;apk 是一些对应于各内核版本的 OpenCV_2.4. 10_Manager_2.9 应用安装包。该应用是用来管理 OpenCV 类库,若手机中无此应用,或未将该应用移植到自己编写的 Android 应用程序中,将无法加载 OpenCV
类库,从而导致程序无法运行下去。
在 Eclipse 上需导入进 OpenCV-2.4. 10 里的 sdk 文件夹下所有内容作为一个项目,在所需开发的项目的 Properties Android 里添加库,该库即为导入进来的 OpenCV 的sdk 项目。
在 Android 平台上使用 OpenCV 进行人脸检测,需要使用NDK 工具编译使用 JNI 编写的本地代码,并将编译后生成的 .so动态库加载到 Android 应用程序中。因此实现过程分为两部分:(1) 在Android 应用程序中编写相关的 Java 代码;(2) 是使用JNI编写本地代码并调用 OpenCV 中的相关函数,然后通过 NDK 编译生成可供 Java 代码调用的动态库,最后通过 Android SDK 开发成应用程序。实现过程如图 4 所示。
图 4实现过程图
主要代码(主要方法说明,具体见源码--即OpenCV-2.4.10-android-sdk(自行下载)提供的示例工程):
public class FdActivity extends Activityimplements CvCameraViewListener2 {
//动态检测回调方法,进行加载库、IO、检测准备等操作
private BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this) {
@Override
public void onManagerConnected(int status) {
switch (status) {
case LoaderCallbackInterface.SUCCESS:
{
//加载本地动态检测库,mOpenCvCameraView对象初始化后调用
System.loadLibrary("detection_based_tracker");
try {
//加载cascade分类器资源,涉及到IO
InputStream is =getResources().openRawResource(R.raw.lbpcascade_frontalface);
File cascadeDir =getDir("cascade", Context.MODE_PRIVATE);
mCascadeFile =new File(cascadeDir, "lbpcascade_frontalface.xml");
FileOutputStream os =new FileOutputStream(mCascadeFile);
byte[] buffer = newbyte[4096];
int bytesRead;
while ((bytesRead =is.read(buffer)) != -1) {
os.write(buffer, 0,bytesRead);
}
is.close();
os.close();
mJavaDetector = newCascadeClassifier(mCascadeFile.getAbsolutePath());
if(mJavaDetector.empty()) {
Log.e(TAG,"Failed to load cascade classifier");
mJavaDetector =null;
} else
mNativeDetector = newDetectionBasedTracker(mCascadeFile.getAbsolutePath(), 0);
cascadeDir.delete();
} catch (IOException e) {
e.printStackTrace();
Log.e(TAG, "Failedto load cascade. Exception thrown: " + e);
}
mOpenCvCameraView.enableView(); //检测准备完毕
} break;
default:
{
super.onManagerConnected(status);
} break;
}
}
};
//构造方法
public FdActivity() {…}
//重写方法,android资源初始化
@Override
public void onCreate(Bundle savedInstanceState) {…}
//开启摄像头检测人脸相关配置方法
public voidonCameraViewStarted(int width, int height) {…}
//停用摄像头检测人脸相关配置方法
public void onCameraViewStopped(){…}
//实现动态从摄像头取帧读取,转换为灰度图进行分析,检测出人脸之后圈出显示
public Mat onCameraFrame(CvCameraViewFrameinputFrame) {
mRgba = inputFrame.rgba();//转化为RGB格式
mGray = inputFrame.gray();//转化为灰度图
if (mAbsoluteFaceSize == 0) {
int height = mGray.rows();
if (Math.round(height *mRelativeFaceSize) > 0) {
mAbsoluteFaceSize =Math.round(height * mRelativeFaceSize);
}
mNativeDetector.setMinFaceSize(mAbsoluteFaceSize);//设置检测人脸大小
}
MatOfRect faces = new MatOfRect();
if (mDetectorType == JAVA_DETECTOR) {
if (mJavaDetector != null)
//多人脸检测
mJavaDetector.detectMultiScale(mGray, faces, 1.1, 2, 2, newSize(mAbsoluteFaceSize, mAbsoluteFaceSize), new Size());
}
else if (mDetectorType ==NATIVE_DETECTOR) {
if (mNativeDetector != null)
//单个人脸检测
mNativeDetector.detect(mGray,faces);
}
Rect[] facesArray = faces.toArray();
for (int i = 0; i <facesArray.length; i++)
Core.rectangle(mRgba,facesArray[i].tl(), facesArray[i].br(), FACE_RECT_COLOR,3);
return mRgba;
}
private void setMinFaceSize(float faceSize) {…} //设置检测人脸大小方法
private void setDetectorType(int type) {…} //设置检测器类型(是否检测多个人脸)方法
}
待修正,请见谅