我们使用OpenCV2.4.13自带的样本生成工具和支持向量机来生成自己的手势级联分类器。
大致的过程参见 Opencv目标检测之级联分类器训练与测试
按照这个过程执行也有一些问题,在这里记录一下。
1.准备正样本
这里我用Kinect拍摄了三十多张手部图片,将他们在PS里截取手部并统一改为30*28的大小(这个办法并不好,训练集可能很大,效率太低,应在拍摄时就注意手的位置,然后使用一些工具批量修改大小),刚开始使用的就是原图640*480,也不是全图只有手部,处理起来电脑“僵劲不能动”,于是修改大小。
这里还涉及到统一命名的问题,Kinect拍摄截图后名称不统一(问题不大)可能中间还有空格(问题很大),于是将图片放到一个文件夹pos中,全选+F2,修改第一个图片的名字,后面的会自动改成相同的名字加上序号,但是仍然有空格。这里用到控制台在pos文件加下进行dir /b>pos.dat这样就能将所有文件名一行行放到pos/pos.dat文件中,我们将所有图片文件名(需要将pos.dat这一行删除)拷贝到excel中,第一列全部写上ren,第二列为文件名(注意在两边加上双引号,使用"替换工具"),第三列为没有空格的文件名(使用“替换”工具)。然后将三列拷贝到一个文本文件中,再用notepad替换将制表符换为空格,将该文件命名为a.bat,双击执行,所有文件名就替换好了,这时再生成(或用之前替换过的)pos.dat文件,让然可以用notepad或记事本自带的替换在每行后写上" 1 0 0 30 28",1表示每个文件中只有一个目标,0,0是目标位置,30,28是目标大小(也有文章说是左上角和右下角的坐标)。这样我们就生成了可使用的pos.dat文件。
(刚刚说我只有30多张图片,是的,我只是复制成了4倍,所以才有那么多名字要替换,否则名称中全是空格;不一定要很多张,手势等可能需要的稍微多一点因为有变化,固定的物体一张也可以,而且我用复制的方式得到这么多张其实并不一定有用)
用opencv_createsamples.exe生成.vec格式的正样本文件:
"D:\OpenCV\opencv\build\x64\vc12\bin\opencv_createsamples.exe" -info pos/pos.dat -vec pos.vec -num 156 -w 30 -h 28
pause
2.准备负样本
任意不带有检测目标的图片(我这里出现了手,其实不太好),任意的名字均可,同样生成一个neg.dat文件,这个文件中只有文件名就可以了
注意:正样本和pos.dat可以放在一个单独的文件夹pos中,负样本刚开始放在单独的文件夹neg中来产生neg.dat,但是此时请将他们与net.dat放到和pos.vec同一级的位置。
3.使用opencv_traincascade.exe训练级联分类器
第一个为opencv_traincascade.exe的带路径文件名,-data后写当前文件夹(即现在neg图片的路径),-vec后写pos.vec文件名,-bg后写neg.dat文件名(注意这里不能带路径否则不成功),-numPos为每一级训练的样本数量,据说应该在总样本的90%左右,-numNeg为负样本数量,据说可以比真实负样本数量大,-numStage后写训练的级数,-mem后写分配的内存,越大处理地越快,-featureType后写类型,这里用LBP,-w后写目标宽度,-h后写目标高度。(Pause可以让你看到打印信息,而不是一脸懵比不知道发生了什么,对了type命令相当于Linux中的cat可以读取文本文件内容,不记得在哪里用过了)
"D:\OpenCV\opencv\build\x64\vc12\bin\opencv_traincascade.exe" -data "F:\AirBand\training" -vec pos.vec -bg neg.dat -numPos 150 -numNeg 46 -numStage 10 -mem 1024 -featureType LBP -w 30 -h 28
Pause
成功的样子是这样的,请注意负样本不能放在二级目录中:http://bbs.youkuaiyun.com/topics/391005340
这里代码使用了opencv学习笔记—opencv+openni2 深度图像与彩色图像的读取与显示中Opencv读取Kinect数据的方法和之前的检测人脸、眼睛的Opencv示例
#include "iostream"
#include "OpenNI.h"
#include "opencv\cv.h"
#include "opencv\highgui.h"
using namespace std;
using namespace openni;
using namespace cv;
/** Function Headers */
void detectAndDisplay(Mat frame);
/** Global variables */
String hand_cascade_name = "cascade.xml";
CascadeClassifier hand_cascade;
string window_name = "Capture - Face detection";
RNG rng(12345);
int main(int argc, char** argv)
{
//-- 1. 加载分类器
if (!hand_cascade.load(hand_cascade_name)){ printf("--(!)Error loading\n"); return -1; };
//opencv
//Mat imgDepth(640, 480, IPL_DEPTH_16U, 1);
char key = 0;
//1.initialize OpenNI
Status rc = OpenNI::initialize();
if (rc != STATUS_OK)
{
cout << "Initialize failed" << OpenNI::getExtendedError();
cin.get();
cin.get();
return -1;
}
//2.open a device
Device device;
rc = device.open(ANY_DEVICE);
VideoStream depth, image;
if (device.getSensorInfo(SENSOR_DEPTH) != NULL)
{
rc = depth.create(device, SENSOR_DEPTH);
if (rc == STATUS_OK)
{
rc = depth.start();
if (rc != STATUS_OK)
{
cout << "couldn't create depth stream" << OpenNI::getExtendedError();
cin.get();
cin.get();
return -1;
}
}
}
if (device.getSensorInfo(SENSOR_COLOR) != NULL)
{
rc = image.create(device, SENSOR_COLOR);
if (rc == STATUS_OK)
{
rc = image.start();
}
}
VideoStream* streams[] = { &depth, &image };
//videoStream.setVideoMode(videoMode);
VideoFrameRef frame;
if (device.hasSensor(SENSOR_COLOR) && device.hasSensor(SENSOR_DEPTH))
cout << "the color is OK";
while (key != 27)
{
int readyStream = -1;
//for the readFrame, you should waitForAnyStream to get the new frame;
rc = OpenNI::waitForAnyStream(streams, 2, &readyStream);
switch (readyStream)
{
case 0:
//depth
depth.readFrame(&frame);
break;
case 1:
//color
image.readFrame(&frame);
break;
default:
cout << "unexpected stream" << endl;
}
switch (frame.getVideoMode().getPixelFormat())
{
case PIXEL_FORMAT_DEPTH_1_MM:
case PIXEL_FORMAT_DEPTH_100_UM:
{
DepthPixel *pDepth = (DepthPixel*)frame.getData();
Mat imgDEP(frame.getHeight(), frame.getWidth(), CV_16UC1, (void*)frame.getData());
// cout << depth.getMaxPixelValue() << endl;
imgDEP.convertTo(imgDEP, CV_8U, 255.0 / 4096.0);
namedWindow("depth", 1);
imshow("depth", imgDEP);
break;
}
case PIXEL_FORMAT_RGB888:
{
Mat imgRGB(frame.getHeight(), frame.getWidth(), CV_8UC3, (void*)frame.getData()); //const 使用
cvtColor(imgRGB, imgRGB, CV_BGR2RGB);
//-- 3. 逐帧执行分类器
std::vector<Rect> faces;
Mat frame_gray;
cvtColor(imgRGB, frame_gray, COLOR_BGR2GRAY);
equalizeHist(frame_gray, frame_gray);
//-- 检测手部
hand_cascade.detectMultiScale(frame_gray, faces, 1.1, 2, 0, Size(80, 80));
for (size_t i = 0; i < faces.size(); i++)
{
Mat faceROI = frame_gray(faces[i]);
//-- 画出手部
Point center(faces[i].x + faces[i].width / 2, faces[i].y + faces[i].height / 2);
ellipse(imgRGB, center, Size(faces[i].width / 2, faces[i].height / 2), 0, 0, 360, Scalar(255, 0, 0), 2, 8, 0);
}
namedWindow("test", 1);
imshow("test", imgRGB);
//-- 显示结果
//imshow(window_name, imgRGB);
break;
}
default:
cout << "unknown format" << endl;
}
key = cvWaitKey(20);
}
image.stop();
image.destroy();
depth.stop();
depth.destroy();
device.close();
OpenNI::shutdown();
cin.get();
cin.get();
return 0;
}
结果:
结果很粗糙,从我上述的训练过程就可以看出,这里只是一个示例。
这个过程中还涉及了VS2013对部分opencv的lib中文件找不到,在设置中将VC++ C/C++ Link中include和lib路径都配一下大概就可以,这里一个人总结的很好:http://bbs.youkuaiyun.com/topics/390789326
VC6:
工程、设置、C/C++、分类:Preprocessor、附加包含路径:填写附加头文件所在目录 逗号间隔多项
工程、设置、Link、分类:Input、附加库路径:填写附加依赖库所在目录 分号间隔多项
工程、设置、Link、分类:Input、对象/库模块:填写附加依赖库的名字.lib 空格间隔多项
VS20xx:
项目、属性、C/C++、附加包含目录:填写附加头文件所在目录 分号间隔多项
项目、属性、链接器、常规、附加库目录:填写附加依赖库所在目录 分号间隔多项
项目、属性、链接器、输入、附加依赖项:填写附加依赖库的名字.lib 空格或分号间隔多项
Window7打开笔记本的网络摄像头可能出现问题,见https://stackoverflow.com/questions/4749498/cant-access-webcam-with-opencv。
还有可能在调试时出现找不到pdb文件,使用Ctrl+F5它就不会来烦你了,毕竟我们不求甚解,具体为什么就不深究了。