总所周知,机器学习前要训练很多数据,一直感觉训练数据是个很神圣的东西,到底怎么训练呢?头脑一直有这么个疑问,但一直没时间去体验。因此最近在学adaboost算法,就要学会怎样训练出一个.xml文件了。方法是相同的,用过一次,以后的训练过程就差不多了。
只是打算进行简单的人脸数据训练,而是在网上下载了yale大学的人脸数据库,由耶鲁大学计算视觉与控制中心创建,包含15位志愿者的165张图片,包含光照,表情和姿态。下载网址为:http://download.csdn.net/detail/davebobo/9737712,图片如下所示:
(右下角是我的系统透明主题的青花瓷照片,别惊讶怎么出来的哈…)
Yale大学人脸数据都是bmp格式的,80*80像素大小。由于这次的haartraining只是为了学会其过程,再加上这个算法训练的时间非常长,图片不到1000就需要的时间都是以天为单位。所以为了减少训练时间,我们一方面得减少图片的数量,这里我打算用133张正样本图,另外22个张用来做测试,另一方面打算减小图片的尺寸,将80*80的图片统一到尺寸24*24像素大小。
(这里插2个小知识:1. 理解图片尺寸的概念。比如说图片的尺寸为80*80代表的是图片的大小为80*80,并不是指分辨率,而是说的是80*80像素,即宽度为80个像素,长度也为80个像素。因为一个像素宽在一个显示器件上已经确定了。因此当一副图片被放大时,它的尺寸像素就可能改变,比如变成了100*100像素,因为放大的时候它的长和宽变了。但是其分辨率在放大时并没有变化。另外像素和长度cm的对应关系也得看图形的分辨率,比如通常所说的说1cm=28像素是指在72像素/平方英寸的时候。所以一般所说的数码相机的像素过高的话其实对显示器显示图片清晰度没有太大帮助,像素高只是可以打印出来更大更清晰的照片。
2. 在window下如果一副图片,比如说是bmp格式的。我只需把后缀名改为其他的比如jpg。然后右键查看其属性时竟然是jpg格式,并且也能打开,难道真的就是jpg格式的吗?肯定不是,比较bmp和改后的jpg就会发现两者的大小是一样的。最后用matlab命令的imfinfo来查看图片的信息,也还是bmp格式的。说明上面单独改后缀名是一种误导,以后做图像处理的时候一定要小心。)
接下来所要进行的操作就是把这165张图片的尺寸缩小到24*24像素了,当然这里有一个笨方法,就是用图片查看软件(比如说ACDsee)一张一张转换。不过要是图片成千上万那就麻烦了。也不懂有什么批处理的工具没有,有的话大家也可以提出来共享。
所以打算自己写一个小程序来完成这些批处理工作,图片的缩放就用OpenCV中现有的函数了。写这个小程序的主要目的是练习对文件名的操作,因为以前一直对这些东西没概念,今天练习了下,收获还是有的。
刚刚在上图可以看出,人脸数据库的命名比如1_s1.bmp表示的是第一个人的第一张人脸图。最后调整后的名字改为1_s1n.bmp。
首先在工程文件目录下,建立yale和yale_small_size文件夹,并把165张人脸数据图片拷贝到yale文件夹中。最后通过程序生成的小尺寸图全部放在yale_small_size文件夹中。如下所示:
其程序代码如下:
#include "opencv2/highgui/highgui.hpp";
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>
#include <stdio.h>
using namespace cv;
using namespace std;
#define DST_IMG_WIDTH 24 //需要调整图片后的尺寸宽度
#define SRC_IMG_HEIGH 24 //需要调整图片后的尺寸高度
int main(int argc, char* argv[])
{
Mat src_img;
int i, j;
string src_img_name = "yale/", dst_img_name = "yale_small_size/";//源文件和目的文件的文件夹名字
char chari[5], charj[5];//因为人脸数据不是很多,所以下标5足够用
for (i = 1; i <= 15; i++)//15个人的人脸数据
{
for (j = 1; j <= 11; j++)//每个人的人脸有11种不同的表情
{
itoa(i, chari, 10);//将变量转换成字符型,此处的chari是字符数组首地址,但是如果定义为char *chari="";则会出现错误,why?
itoa(j, charj, 10);
src_img_name += chari;//原图命名格式为,比如第5个人的第6张图,5_s6.bmp
src_img_name += "_s";
src_img_name += charj;
src_img_name += ".bmp";
src_img = imread(src_img_name, 1);
Mat dst_img_rsize(DST_IMG_WIDTH, SRC_IMG_HEIGH, src_img.type());
resize(src_img, dst_img_rsize, dst_img_rsize.size(), 0, 0, INTER_LINEAR);
dst_img_name += chari;//转换后图的命名格式为:例上,5_s6n.bmp
dst_img_name += "_s";
dst_img_name += charj;
dst_img_name += "n.bmp";
imwrite(dst_img_name, dst_img_rsize);
src_img_name = "yale/", dst_img_name = "yale_small_size/";//每次循环后要重新清0字符数组内的内容,目的文件夹一定要事先建立,否则无效果
}
}
return 0;
}
最后生成的归一化后的图片截图如下所示(yale_small_size文件夹内):
好吧,很简单的工作已经完成了,没什么含量。以后有时间打算写个界面出来,可以批处理的调整图片到指定的像素大小,为以后搞视觉的图片前期处理带来便利。
1.准备正负样本:
在上一讲http://www.cnblogs.com/tornadomeet/archive/2012/03/27/2420088.html 中,我们已经收集到了训练所用的正样本。下面就开始收集负样本了,负样本要求是:不能包含人脸,且图片大小也不需要归一化到正样本尺寸,只需比正样本尺寸大或者相等即可。建议负样本用灰度图,加快训练速度,且负样本一定不能重复,要增大负样本的差异性。
这里我采用的负样本是用的是weizmann团队http://www.wisdom.weizmann.ac.il/~vision/Seg_Evaluation_DB/dl.html 网站上的图像分割数据库,里面有灰色图和彩色图,下载网址:http://download.csdn.net/detail/davebobo/9737714这里当然选取灰度图了。
总共用了200幅图片,大小大约在300*200像素,截图如下所示:
可以看出这些200多张图片基本都没有人脸,所以说应该是可以的。
正负样本的图片准备好了,下面就开始制作正负样本的描述文件了。
首先建立好文件夹,把图片拷贝好,如下所示:
Pos_image中放入的是正样本,neg_image放入的是负样本,test_image放入的是测试样本。并将后面要用到的2个工具.exe文件也拷贝过来(在opencv的安装目录C:\Program Files\opencv2.3.1\build\common\x86下)。
2.生成正负样本描述文件:
建立正样本的描述文件:
打开cmd窗口,进入上图所在pos_img文件夹内,可以看到此文件夹图片显示如下:
使用命令dir /b >pos_image.txt。如图所示
且用editplus打开该文件,删除最后一行,最后将名字归一化如下所示:
其中的pos_image/是相对路径名,后面紧接着的是文件名,1代表一个文件,0 0 24 24表示这个文件的2个顶点位置坐标。保存退出即ok!
负样本的描述文件类似,只是不需要考虑其大小位置。
也是进入neg_imgae后在cmd内使用命令dir /b >neg_image.txt,如图所示:
同样删除最后一行文字,且将文件相对路径加入如下所示:
至此,训练数据准备完备了。
3.创建vec文件:
在创建vec文件时,需要把pos_image.txt和neg_image.txt两个样本描述文件剪切到上一目录,如图所示:
然后利用opencv_createsamples.exe应用程序在该目录下使用如下cmd命令:
其中的-vec是指定后面输出vec文件的文件名,-info指定正样本描述文件,-bg指定负样本描述文件,-w和-h分别指正样本的宽和高,-num表示正样本的个数。执行完该命令后就会在当前目录下生产一个pos.vec文件了。
4.使用opencv_haartraining.exe文件进行训练
首先在当前目录下新建一个xml文件夹用于存放生成的.xml文件。
在当前目录使用cmd命令:
Opcnv_haartraining.exe –data xml –vec pos.vec –bg neg_image.txt –nsplits 1 –sym –w 24 –h 24 –mode all –mem 1280
截图如下:
其中-data为输出xml中间文件的位置,-sym表示训练的目标为垂直对称,-nsplits 1表示使用简单的stump classfier分类。-mem 1280 表示允许使用计算机的1280M内存,-mode all 表示使用haar特征集的种类既有垂直的,又有45度角旋转的。
因为数据量不是很多,不到半个钟头就训练好了。在当前目录下生产了一个xml.xml文件,将其重名名为face_test.xml。
5.实验结果:
利用上面训练出来的face_test.xml文件来检测下人脸,首先来一张比较正面的人脸图,用奥巴马的,检测结果如下:
为了看看是否不是特别正的,且有背景干扰的结果,用了lena的图,检测结果如下:
上面说明其效果还是不错的。其测试源码和前面的博客http://www.cnblogs.com/tornadomeet/archive/2012/03/22/2411318.html的代码类似,删减了人眼检测的代码而已,源码如下:
#include "opencv2/objdetect/objdetect.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/ml/ml.hpp"
#include <iostream>
#include <stdio.h>
using namespace std;
using namespace cv;
void detectAndDraw(Mat& img,
CascadeClassifier& cascade,
double scale);
String cascadeName = "face_test.xml";//人脸的训练数据
int main(int argc, const char** argv)
{
Mat image;
CascadeClassifier cascade, nestedCascade;//创建级联分类器对象
double scale = 1.8;
// image = imread("obama_gray.bmp",1);
image = imread("lena_gray.jpg", 1);
namedWindow("result", 1);//opencv2.0以后用namedWindow函数会自动销毁窗口
if (!cascade.load(cascadeName))//从指定的文件目录中加载级联分类器
{
cerr << "ERROR: Could not load classifier cascade" << endl;
return 0;
}
if (!image.empty())//读取图片数据不能为空
{
detectAndDraw(image, cascade, scale);
waitKey(0);
}
return 0;
}
void detectAndDraw(Mat& img,CascadeClassifier& cascade,double scale)
{
int i = 0;
double t = 0;
vector<Rect> faces;
const static Scalar colors[] = { CV_RGB(0, 0, 255),
CV_RGB(0, 128, 255),
CV_RGB(0, 255, 255),
CV_RGB(0, 255, 0),
CV_RGB(255, 128, 0),
CV_RGB(255, 255, 0),
CV_RGB(255, 0, 0),
CV_RGB(255, 0, 255) };//用不同的颜色表示不同的人脸
Mat gray, smallImg(cvRound(img.rows / scale), cvRound(img.cols / scale), CV_8UC1);//将图片缩小,加快检测速度
cvtColor(img, gray, CV_BGR2GRAY);//因为用的是类haar特征,所以都是基于灰度图像的,这里要转换成灰度图像
resize(gray, smallImg, smallImg.size(), 0, 0, INTER_LINEAR);//将尺寸缩小到1/scale,用线性插值
equalizeHist(smallImg, smallImg);//直方图均衡
t = (double)cvGetTickCount();//用来计算算法执行时间
//检测人脸
//detectMultiScale函数中smallImg表示的是要检测的输入图像为smallImg,faces表示检测到的人脸目标序列,1.1表示
//每次图像尺寸减小的比例为1.1,2表示每一个候选矩形需要记录2个邻居,CV_HAAR_SCALE_IMAGE表示使用haar特征,Size(30, 30)
//为目标的最小最大尺寸
cascade.detectMultiScale(smallImg, faces,
1.1, 2, 0
//|CV_HAAR_FIND_BIGGEST_OBJECT
//|CV_HAAR_DO_ROUGH_SEARCH
| CV_HAAR_SCALE_IMAGE,
Size(30, 30));
t = (double)cvGetTickCount() - t;//相减为算法执行的时间
printf("detection time = %g ms\n", t / ((double)cvGetTickFrequency()*1000.));
for (vector<Rect>::const_iterator r = faces.begin(); r != faces.end(); r++, i++)
{
Mat smallImgROI;
vector<Rect> nestedObjects;
Point center;
Scalar color = colors[i % 8];
int radius;
center.x = cvRound((r->x + r->width*0.5)*scale);//还原成原来的大小
center.y = cvRound((r->y + r->height*0.5)*scale);
radius = cvRound((r->width + r->height)*0.25*scale);
circle(img, center, radius, color, 3, 8, 0);
smallImgROI = smallImg(*r);
}
cv::imshow("result", img);
}
作者:tornadomeet 出处:http://www.cnblogs.com/tornadomeet 欢迎转载或分享,但请务必声明文章出处。 (新浪微博:tornadomeet,欢迎交流!)
如果在训练过程中遇到问题请参考:
http://blog.csdn.net/u014365862/article/details/53000619