基于FM810深度摄像头的人脸检测项目过程

基于FM810深度摄像头的人脸检测项目过程

之前从来没有接触过OpenCV和VS,所以从安装和配置浆砌。

1.VS2017的安装

我是下载的VS 2017 enterprise版本,从网上下载的离线安装包17个G左右,我当时是在百度云上下载的,网址忘记了。但是大家可以再官网下载下载好之后安装的时候记得选对应版本的windows SDK,详细的安装过程可以看这个  我当时的选的版本是win8.1和win10,因为编译的时候要用到,当时我的就是提示没有找到对应的window SDK,所以重新在Win10的程序和应用(控制面板,卸载程序窗口)中,选中右键选项选择修改,或者在启动程序中的VS installer启动,然后点击更新,在右侧添加对应的SDK。安装就讲这么多吧。

2.OpenCV的下载和安装

官网中下载OpenCV3.4.1或者其他版本,解压到你要安装的路径即可。然后配置环境变量。具体可以查看链接 

3.新建工程,配置路径,除了OpenCV的包含路径,库路径和库名称正确配置之外,记得使用 FM810  相机自带的tycam.lib库,路径也要在属性--C++目录--库路径中将路径目录和库名称添加到链接器--输入--库。

4.为了方便,不用每次都配置路劲,可以配置属性管理器,每次新建工程之后,直接添加即可。或者使用这种方法,在属性配置管理器的Microsoft.Cpp.Win32.user,打开属性管理器方法:视图(窗口)--属性配置管理器(或者其他窗口中查找)。

  5.从摄像头中获取每一帧图像。

(1)工程目录结构


(2)添加头文件

    从官方给的SDK中找到sample中复制common文件夹到工程目录,并添加到属性配置的包含路径中,然后在工程的解决方案管理窗口的头文件,添加common中的头文件。

(3)添加源码

    官方给的SDK中给了几个例程,我选取的是SimpleView_FetchFrame ,复制源码到工程中,并添加到源文件里。另外还有一个头文件( TY_API.h)在..\camport2-master\include路径中也要添加进去。

(4)安装FM810驱动

    尝试编译一下,发现没有安装摄像头的驱动。于是找到官方网站中的文档,按照上面给的文档安装win10的驱动。win10安装驱动时,注意在系统设置--更新和安全--恢复--高级启动中选择立即重新启动,接着按照官方文档中的步骤操作即可。

(5)调试获取每一帧图像

    调试过程中遇到挺多的问题,经过百度和自己摸索终于不报错。运行效果如图:


RGB和云点图我就不在这里贴了,一开始我发现运行的时候非常卡,明显速度跟不上,我以为是我电脑太垃圾了,导致程序运行速度太慢了。后来发现我使用的是debug模式,换成 release模式之后就很顺畅了。

下面是源码:

#include "common/common.hpp"
#include <iostream>
#include <opencv2/opencv.hpp>


static char buffer[1024 * 1024 * 20];
static int  n;
static int count = 0;
static volatile bool exit_main;
static volatile bool save_frame;
static char depthImage_name[30];
static char colorImage_name[30];
static int numberOfstudent1;
static int numberOfstudent2=1;
static int numberOfstudent3;
static char originalDepthImage_name[40];




using namespace cv;
using namespace std;


struct CallbackData {
int             index;
TY_DEV_HANDLE   hDevice;   
DepthRender*    render;


TY_CAMERA_DISTORTION color_dist;
TY_CAMERA_INTRINSIC color_intri;
};




void handleFrame(TY_FRAME_DATA* frame, void* userdata)
{
CallbackData* pData = (CallbackData*)userdata;
LOGD("=== Get frame %d", ++pData->index);


cv::Mat depth, irl, irr, color, point3D;
cv::Mat resizedColor;
parseFrame(*frame, &depth, &irl, &irr, &color, &point3D);
if (!depth.empty()) {
cv::Mat colorDepth = pData->render->Compute(depth);
cv::imshow("ColorDepth", colorDepth);

}
if (!irl.empty()) { cv::imshow("LeftIR", irl); }
if (!irr.empty()) { cv::imshow("RightIR", irr); }
if (!color.empty()) {

cv::imshow("color", resizedColor);

}



}


int key = cv::waitKey(1);
switch (key) {
case -1:
break;
case 'q': case 1048576 + 'q':
exit_main = true;
break;
default:
LOGD("Pressed key %d", key);
}


LOGD("=== Callback: Re-enqueue buffer(%p, %d)", frame->userBuffer, frame->bufferSize);
ASSERT_OK(TYEnqueueBuffer(pData->hDevice, frame->userBuffer, frame->bufferSize));
}


int main(int argc, char* argv[])
{
const char* IP = NULL;
const char* ID = NULL;
TY_DEV_HANDLE hDevice;
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "-id") == 0) {
ID = argv[++i];
}
else if (strcmp(argv[i], "-ip") == 0) {
IP = argv[++i];
}
else if (strcmp(argv[i], "-h") == 0) {
LOGI("Usage: SimpleView_Callback [-h] [-ip <IP>]");
return 0;
}
}


LOGD("=== Init lib");
ASSERT_OK(TYInitLib());
TY_VERSION_INFO* pVer = (TY_VERSION_INFO*)buffer;
ASSERT_OK(TYLibVersion(pVer));
LOGD("     - lib version: %d.%d.%d", pVer->major, pVer->minor, pVer->patch);


if (IP) {
LOGD("=== Open device %s", IP);
ASSERT_OK(TYOpenDeviceWithIP(IP, &hDevice));
}
else {
if (ID == NULL) {
LOGD("=== Get device info");
ASSERT_OK(TYGetDeviceNumber(&n));
LOGD("     - device number %d", n);


TY_DEVICE_BASE_INFO* pBaseInfo = (TY_DEVICE_BASE_INFO*)buffer;
ASSERT_OK(TYGetDeviceList(pBaseInfo, 100, &n));


if (n == 0) {
LOGD("=== No device got");
return -1;
}
ID = pBaseInfo[0].id;
}


LOGD("=== Open device: %s", ID);
ASSERT_OK(TYOpenDevice(ID, &hDevice));
}


int32_t allComps;
ASSERT_OK(TYGetComponentIDs(hDevice, &allComps));
if (!(allComps & TY_COMPONENT_RGB_CAM)) {
LOGE("=== Has no RGB camera, cant do registration");
return -1;
}


LOGD("=== Configure components");
int32_t componentIDs = TY_COMPONENT_POINT3D_CAM | TY_COMPONENT_RGB_CAM;
ASSERT_OK(TYEnableComponents(hDevice, componentIDs));


LOGD("=== Prepare image buffer");
int32_t frameSize;


//frameSize = 1280 * 960 * (3 + 2 + 2);
ASSERT_OK(TYGetFrameBufferSize(hDevice, &frameSize));
LOGD("     - Get size of framebuffer, %d", frameSize);
LOGD("     - Allocate & enqueue buffers");
char* frameBuffer[2];
frameBuffer[0] = new char[frameSize];
frameBuffer[1] = new char[frameSize];
LOGD("     - Enqueue buffer (%p, %d)", frameBuffer[0], frameSize);
ASSERT_OK(TYEnqueueBuffer(hDevice, frameBuffer[0], frameSize));
LOGD("     - Enqueue buffer (%p, %d)", frameBuffer[1], frameSize);
ASSERT_OK(TYEnqueueBuffer(hDevice, frameBuffer[1], frameSize));


LOGD("=== Register callback");
LOGD("Note: Callback may block internal data receiving,");
LOGD("      so that user should not do long time work in callback.");
LOGD("      To avoid copying data, we pop the framebuffer from buffer queue and");
LOGD("      give it back to user, user should call TYEnqueueBuffer to re-enqueue it.");
DepthRender render;
CallbackData cb_data;
cb_data.index = 0;
cb_data.hDevice = hDevice;
cb_data.render = &render;
// ASSERT_OK( TYRegisterCallback(hDevice, frameCallback, &cb_data) );


LOGD("=== Disable trigger mode");
ASSERT_OK(TYSetBool(hDevice, TY_COMPONENT_DEVICE, TY_BOOL_TRIGGER_MODE, false));


LOGD("=== Start capture");
ASSERT_OK(TYStartCapture(hDevice));


LOGD("=== Read color rectify matrix");
{
TY_CAMERA_DISTORTION color_dist;
TY_CAMERA_INTRINSIC color_intri;
TY_STATUS ret = TYGetStruct(hDevice, TY_COMPONENT_RGB_CAM, TY_STRUCT_CAM_DISTORTION, &color_dist, sizeof(color_dist));
ret |= TYGetStruct(hDevice, TY_COMPONENT_RGB_CAM, TY_STRUCT_CAM_INTRINSIC, &color_intri, sizeof(color_intri));
if (ret == TY_STATUS_OK)
{
cb_data.color_intri = color_intri;
cb_data.color_dist = color_dist;
}
else
{ //reading data from device failed .set some default values....
memset(cb_data.color_dist.data, 0, 12 * sizeof(float));
memset(cb_data.color_intri.data, 0, 9 * sizeof(float));
cb_data.color_intri.data[0] = 1000.f;
cb_data.color_intri.data[4] = 1000.f;
cb_data.color_intri.data[2] = 600.f;
cb_data.color_intri.data[5] = 450.f;
}
}


LOGD("=== Wait for callback");
exit_main = false;


while (!exit_main) {
TY_FRAME_DATA frame;
int err = TYFetchFrame(hDevice, &frame, -1);
if (err != TY_STATUS_OK) {
LOGE("Fetch frame error %d: %s", err, TYErrorString(err));
break;
}
else {
handleFrame(&frame, &cb_data);
}

}


ASSERT_OK(TYStopCapture(hDevice));
ASSERT_OK(TYCloseDevice(hDevice));
ASSERT_OK(TYDeinitLib());
delete frameBuffer[0];
delete frameBuffer[1];


LOGD("=== Main done!");
return 0;
}

6.利用OpenCV3.4.1进行人脸检测

下面给出OpenCV实现人脸检测的一般步骤:

1).加载人脸检测器

2).开启摄像头

3).对图片进行灰度处理(其实可以不处理,上图中原图的标题栏就是未进行灰度处理进行的检测,这里的灰度是为下节人脸识别打基础)

4).对图片进行直方图均衡化(其实可以不处理,上图中原图的标题栏就是未进行灰度处理进行的检测和灰度图是为进行均衡化识别,这里的均衡化是为下节人脸识别打基础)

5)保存RGB图片和深度信息

    考虑到我们要采集摄像头的RGB图像和深度信息,所以我这里是讲图像存放在文件夹中,文件夹目录如下:


然后从文件夹中读取图片进行人脸检测,关键代码如下:

计算原始深度值并保存图片:

cv::Mat newDepth;
cv::Mat depthColor;
if (!point3D.empty() && !color.empty()) {
ASSERT_OK(TYRegisterWorldToColor(pData->hDevice, (TY_VECT_3F*)point3D.data, 0
, point3D.cols * point3D.rows, (uint16_t*)buffer, sizeof(buffer)
));
newDepth = cv::Mat(color.rows, color.cols, CV_16U, (uint16_t*)buffer);
cv::Mat resized_color;
cv::Mat temp;
//you may want to use median filter to fill holes in projected depth image or do something else here
cv::medianBlur(newDepth, temp, 5);
newDepth = temp;
//resize to the same size for display
cv::resize(newDepth, newDepth, depth.size(), 0, 0, 0);
cv::resize(color, resized_color, depth.size());
depthColor = pData->render->Compute(newDepth);

// cv::imshow("originalDepth", newDepth);

                //保存图片

sprintf(originalDepthImage_name, "%s%d%s", "originalDepth\\", ++numberOfstudent3, ".png");

imwrite(originalDepthImage_name, newDepth);

sprintf(depthImage_name, "%s%d%s", "depthimage\\", ++numberOfstudent1, ".jpg");
imwrite(depthImage_name, depthColor);

其他图片的保存类似。


6).基于OpenCV的人脸检测

代码参考(原博客:https://blog.youkuaiyun.com/qq_31028891/article/details/51974125)

    但是对于我这里的应用场景,不需要打开摄像头,因为已经从FM810摄像头中获取每一帧图像,只需要获取讲获取的图像放入到 face_cascade.detectMultiScale 函数中,这个函数中的参数含义可以参考此博客(https://blog.youkuaiyun.com/itismelzp/article/details/50379359),这里贴出关键部分

cvHaarDetectObjects是opencv1中的函数,opencv2中人脸检测使用的是 detectMultiScale函数。它可以检测出图片中所有的人脸,并将人脸用vector保存各个人脸的坐标、大小(用矩形表示),函数由分类器对象调用

[cpp]  view plain  copy
  1. void detectMultiScale(  
  2.     const Mat& image,  
  3.     CV_OUT vector<Rect>& objects,  
  4.     double scaleFactor = 1.1,  
  5.     int minNeighbors = 3,   
  6.     int flags = 0,  
  7.     Size minSize = Size(),  
  8.     Size maxSize = Size()  
  9. );  

函数介绍:

参数1:image--待检测图片,一般为灰度图像加快检测速度;

参数2:objects--被检测物体的矩形框向量组;
参数3:scaleFactor--表示在前后两次相继的扫描中,搜索窗口的比例系数。默认为1.1即每次搜索窗口依次扩大10%;
参数4:minNeighbors--表示构成检测目标的相邻矩形的最小个数(默认为3个)。
        如果组成检测目标的小矩形的个数和小于 min_neighbors - 1 都会被排除。
        如果min_neighbors 为 0, 则函数不做任何操作就返回所有的被检候选矩形框,
        这种设定值一般用在用户自定义对检测结果的组合程序上;
参数5:flags--要么使用默认值,要么使用CV_HAAR_DO_CANNY_PRUNING,如果设置为

        CV_HAAR_DO_CANNY_PRUNING,那么函数将会使用Canny边缘检测来排除边缘过多或过少的区域,

        因此这些区域通常不会是人脸所在区域;

参数6、7:minSize和maxSize用来限制得到的目标区域的范围。

需要注意的地方是,代码前面声明加载完整检测的XML路径或者是放入工程文件夹中,我的源码如下:

String facefile = "E:/soft/opencv/sources/data/haarcascades_cuda/haarcascade_frontalface_alt.xml";
String eyefile = "E:/soft/opencv/sources/data/haarcascades_cuda/haarcascade_eye.xml";

如果你是从路径中加载图片,注意使用转义字符\,我的读取路径图片源码吐如下

        Mat img = imread("F:\\vs2017\\project\\FM810getframe\\FM810getframe\\colorimage\\1.jpg", CV_LOAD_IMAGE_UNCHANGED);

facedectect(img);//检测函数

        或者使用:

        sprintf(colorImage_name, "%s%d%s", "colorimage\\", numberOfstudent2, ".jpg");

Mat color = imread(colorImage_name);//RGB

        facedectect(color );

其中facedetect(img)是人脸检测函数关键代码如下:

vector<Rect> eyes;
Mat gray,rect;
cvtColor(frame, gray, COLOR_BGR2GRAY);
equalizeHist(gray, gray);
face_cascader.detectMultiScale(gray, faces, 1.3, 3, 0, Size(30, 30));
for (size_t t = 0; t < faces.size(); t++) {

//绘制人脸的矩形框
rectangle(frame, faces[static_cast<int>(t)], Scalar(0, 0, 255), 2, 8, 0);


}
//输出实时图像
imshow("camera-demo", frame);
return 0;

}

在检测人脸这一步时,当加入face_cascader.detectMultiScale(gray, faces, 1.3, 3, 0, Size(30, 30))遇到了一个内存读取错误问题,卡在这里很久。这里先列出几个使用detectMultiScale常见的问题

(1)报错cv2.error: C:\builds\master_PackSlaveAddon-win64-vc12-static\opencv\modules\objdetect\src\cascadedetect.cpp:1639: error: (-215) !empty() in function cv::CascadeClassifier::detectMultiScale

原因是XML没有加载完整的路径。face_cascade.load('D:/opencv/opencv/sources/data/haarcascades/haarcascade_frontalface_default.xml')

(2)OpenCV人脸检测使用detectMultiScale可能会报错_CrtIsValidHeapPointer的解决方法(原博客

存在问题: 
在使用OpenCV的人脸检测时,用到了detectMultiScale函数,使用方法为:face_cascade.detectMultiScale( frame_gray, faces, 1.1, 2, 0|CV_HAAR_SCALE_IMAGE, cv::Size(30, 30) ); 

其中faces为vector,在faces这个vector释放时,会出现_CrtIsValidHeapPointer的bug。这应该是由于detectMultiScale函数封装在OpenCV的dll中,在检测到人脸后会对faces这个vector进行操作,而在该变量使用结束释放时,再次对其进行操作时,就会报错。

(3)写入位置时内存读取位置错误


可能原因是原地址:http://tieba.baidu.com/p/5163054239

但是我的并不是这个原因我使用的OpenCV版本是3.4.1,经过参考这位博主的博客终于解决了问题。

一般运行复杂的工程时,由于openCV官方提供的编译库一般只是标准版本,可能会出现某些兼容性的问题,比如官方提供的编译好的版本与opengl就不兼容,这时就只能自己构建项目后编译。当然,一般工程使用的话,用官方提供的库即可。通过使用Cmake+VS对openCV库进行重新编译,以生成新的.dll文件以及.lib文件替换原有库中相应文件。 

将dll和lib文件复制到OpenCV文件夹的库文件夹和bin文件夹中就解决了。也不是很清楚是不是兼容性问题。注意复制的时候也要复制到对应的VS版本中,VS15对应的是VS2017,低版本的以此类推,属性路径中也同样要注意。

Cmake+VS对openCV库进行重新编译,可以参考这篇博客:编译OpenCV3.4.1 +contrib+vs2013+x86版本

我这里简单记录一下编译后的lib名称:

debug(数量:44)

opencv_aruco341d.lib
opencv_bgsegm341d.lib
opencv_bioinspired341d.lib
opencv_calib3d341d.lib
opencv_ccalib341d.lib
opencv_core341d.lib
opencv_datasets341d.lib
opencv_dnn341d.lib
opencv_dnn_objdetect341d.lib
opencv_dpm341d.lib
opencv_face341d.lib
opencv_features2d341d.lib
opencv_flann341d.lib
opencv_fuzzy341d.lib
opencv_hfs341d.lib
opencv_highgui341d.lib
opencv_imgcodecs341d.lib
opencv_imgproc341d.lib
opencv_img_hash341d.lib
opencv_line_descriptor341d.lib
opencv_ml341d.lib
opencv_objdetect341d.lib
opencv_optflow341d.lib
opencv_phase_unwrapping341d.lib
opencv_photo341d.lib
opencv_plot341d.lib
opencv_reg341d.lib
opencv_rgbd341d.lib
opencv_saliency341d.lib
opencv_shape341d.lib
opencv_stereo341d.lib
opencv_stitching341d.lib
opencv_structured_light341d.lib
opencv_superres341d.lib
opencv_surface_matching341d.lib
opencv_text341d.lib
opencv_tracking341d.lib
opencv_video341d.lib
opencv_videoio341d.lib
opencv_videostab341d.lib
opencv_xfeatures2d341d.lib
opencv_ximgproc341d.lib
opencv_xobjdetect341d.lib
opencv_xphoto341d.lib

release(数量:44)

opencv_aruco341.lib
opencv_bgsegm341.lib
opencv_bioinspired341.lib
opencv_calib3d341.lib
opencv_ccalib341.lib
opencv_core341.lib
opencv_datasets341.lib
opencv_dnn341.lib
opencv_dnn_objdetect341.lib
opencv_dpm341.lib
opencv_face341.lib
opencv_features2d341.lib
opencv_flann341.lib
opencv_fuzzy341.lib
opencv_hfs341.lib
opencv_highgui341.lib
opencv_imgcodecs341.lib
opencv_imgproc341.lib
opencv_img_hash341.lib
opencv_line_descriptor341.lib
opencv_ml341.lib
opencv_objdetect341.lib
opencv_optflow341.lib
opencv_phase_unwrapping341.lib
opencv_photo341.lib
opencv_plot341.lib
opencv_reg341.lib
opencv_rgbd341.lib
opencv_saliency341.lib
opencv_shape341.lib
opencv_stereo341.lib
opencv_stitching341.lib
opencv_structured_light341.lib
opencv_superres341.lib
opencv_surface_matching341.lib
opencv_text341.lib
opencv_tracking341.lib
opencv_video341.lib
opencv_videoio341.lib
opencv_videostab341.lib
opencv_xfeatures2d341.lib
opencv_ximgproc341.lib
opencv_xobjdetect341.lib
opencv_xphoto341.lib


7.保存检测到的人脸的RGB图像,并将对应的深度人脸图像保存下来


face_cascader.detectMultiScale(gray, faces, 1.3, 3, 0, Size(30, 30));函数中的faces已经获得了人脸所在的区域,只要将对应的坐标图像保存下来即可,同理对应的深度图像的人脸部分也是如此。为了后续人脸活体检测模型中人脸训练方便,将人脸图像稍微往外扩一些。

保存人脸的关键代码如下:

      for (size_t t = 0; t < faces.size(); t++) {

        Mat faceROI = frame(faces[static_cast<int>(t)]);

sprintf(save_path, "%s%d%s", "colorcrop\\", numberOfstudent3, ".jpg");

imwrite(save_path , faceROI);

        rectangle(frame, faces[static_cast<int>(t)], Scalar(0, 0, 255), 2, 8, 0);

    }

效果如图(手动马赛克,哈哈哈):



发现自带的检测不是很准,可能是由于参数设置问题。也可以试下seeteaface、Dlib或者mtcnn进行检测

暂时写到这里,下次再更新,开会去了。





评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值