NKU计算机视觉期末大作业
目录
车辆检测的整体框架是结合hog-svm分类器和haar-cascade分类器对车辆进行检测,之后采用非极大值抑制,得出最终的检测框。
方向梯度直方图(Histogram of Oriented Gradient, HOG)特征是一种在计算机视觉和图像处理中用来进行物体检测的特征描述子。它通过计算和统计图像局部区域的梯度方向直方图来构成特征。HOG特征提取方法就是将一个image(要检测的目标或者扫描窗口):
1)灰度化(将图像看做一个x,y,z(灰度)的三维图像);
2)采用Gamma校正法对输入图像进行颜色空间的标准化(归一化);目的是调节图像的对比度,降低图像局部的阴影和光照变化所造成的影响,同时可以抑制噪音的干扰;
3)计算图像每个像素的梯度(包括大小和方向);主要是为了捕获轮廓信息,同时进一步弱化光照的干扰。
4)将图像划分成小cells(例如6*6像素/cell);
5)统计每个cell的梯度直方图(不同梯度的个数),即可形成每个cell的descriptor;
6)将每几个cell组成一个block(例如3*3个cell/block),一个block内所有cell的特征descriptor串联起来便得到该block的HOG特征descriptor。
7)将图像image内的所有block的HOG特征descriptor串联起来就可以得到该image(你要检测的目标)的HOG特征descriptor了。这个就是最终的可供分类使用的特征向量了。
为了样本的多样性,我采用的是部分数据集一的负样本和数据集二部分正样本和全部负样本作为训练数据。最终正样本与负样本的比例为1:3,一共16000张图片。
正样本大致如下:
因为汽车大致呈现正方形,故对每张图片resize到64x64大小,然后提取hog特征。在这里我选择的相关系数为:
根据如下公式可以算出整个hog特征维度为8100
config.cpp
可以修改这些全局变量。
在提取特征之前我们要先将训练集与测试集写到两个txt中方便读取,考虑到c++文件读写以及科学计算方面不是很方便,在这里我采用python对数据集进行的划分处理,利用numpy,cv2,sklearn.model_selection
可以较为方便的完成,具体方法在/python_func/BuildImgList.py文件中。
接下来可以提取hog特征,这部分代码在GetFeature.cpp
中,引入头文件opencv2/xfeature2d.hpp
,我们可以调用提取hog特征的方法:
Mat GetHOGfeature(string imgname){
Mat img = imread(imgname);
resize(img, img, Size(Imgheight, Imgwidth));
Ptr hog = new HOGDescriptor(Size(Window_y, Window_x),
Size(block_y, block_x),
Size(block_stride_y, block_stride_x),
Size(cell_y, cell_x), 9);
assert(hog->getDescriptorSize() == dimension);
vector<float> descriptor;
hog->compute(img, descriptor, Size(Window_stride_y, Window_stride_x), Size(0, 0));
assert(descriptor.size() == dimension);
Mat s(descriptor);
transpose(s, s);
return s;
}
我们对所有图片提取特征,接下来的步骤便是送进支持向量机中进行训练,这一部分的代码在train.cpp
中。在对数据集进行处理时,我把正样本的label标注为1,把负样本的label标注为-1。由于这是一个二分类问题,因此在选择SVM的核类型时,选择线性核即可。为了求出最优的参数,在这里采用opencv的machine learning模块的trainAuto函数(可以自动调节超参数)而非train函数。在调用之前必须要对一些超参数赋予初始值,如下为训练方法:
void HOGSVMtrainAuto(string trainlist){
Mat Data4Train(0, dimension, CV_32FC1), labels(0, 1, CV_32SC1);
GetAllImgHOGfeature(Data4Train, labels, trainlist, ImgTrainPath);
struct timeval pre, after;
gettimeofday(&pre, NULL);
Ptr<ml::SVM> model = ml::SVM::create();
model->setKernel(ml::SVM::KernelTypes::LINEAR);
model->setType(ml::SVM::C_SVC);
model->setP(1e-2);
model->setC(1);
model->setGamma(1e-2);
model->setTermCriteria(cvTermCriteria(CV_TERMCRIT_ITER, 10000, 0.000001));
if(debug){
cout << "height: "<<Data4Train.rows << ", width: " << Data4Train.cols << endl;
cout << "trainingdata depth: " << Data4Train.depth() << endl;
cout << "label depth: " << labels.depth() << endl;
cout << "trainingdata type " << Data4Train.type() << endl;
cout << "label type " << labels.type() << endl;
}
assert(Data4Train.type() == CV_32FC1);
assert(labels.type() == CV_32SC1);
Ptr<ml::TrainData> data = ml::TrainData::create(Data4Train, ml::ROW_SAMPLE, labels);
cout << "start training ..." << endl;
model->trainAuto(data, 10);
cout << "finish training ..." << endl;
gettimeofday(&after, NULL);
cout << "training time: " << after.tv_sec - pre.tv_sec << "s"<< endl;
model->save("../model/svm_hog_classifier.xml");
cout << "model saving fininshed ..." << endl;
}
训练大概花费20~30分钟,训练完成后会生成xml文件,即训练好的模型,在测试集上测试,准确率可以达到98%,仅从测试集上看,效果还是不错的。
Haar-like特征点,是一种简单的特征描述,其理论相当容易理解,就是按照下图的模式来计算白色窗口的像素总和和黑色窗口像素总和的差,如下图:
利用提取到的haar特征可以训练弱分类器,通过若干个弱分类器可以组建一个强分类器,类似于一种投票的手段,只不过不同的分类器具有不同的权重,整个训练过程可以看做是一个不断调整权重大小的过程,如下:
接下来便是级联,如下图,最终的分类器是由多个强分类器级联而成。当且仅当通过了所有分类器的判定后才能输出结果。
仅仅参考图像的hog特征可能会存在漏检测。由此,我将人脸识别中常见的级联器检测方法迁移到车辆检测中,参考博文一、博文二和opencv官方文档。以数据集一的全部正样本和负样本为数据源,正负样本比大概为1:3。首先制作两个标准格式的txt文件,一个是正样本txt,另一个时负样本txt。正样本txt格式大致如下(路径 图片中目标个数 xmin ymin xmax ymax):
../../../data/ProData3/17622107.jpg 1 0 0 38 38
../../../data/ProData3/112237754.BMP 1 0 0 128 96
../../../data/ProData3/12130486.BMP 1 0 0 128 96
...
负样本txt只需要图片路径即可,如下:
../../../data/ProData4/174157305.jpg
../../../data/ProData4/18635891.jpg
../../../data/ProData4/1356388.jpg
...
为了方便我用python制作了该txt文件,具体方法在/adaboost/Gen_Imglist.py中。
opencv提供了opencv_createsamples.exe建立训练所需要的参数列表,在命令行中调用该exe,输入如下命令:
opencv_createsamples -vec pos.vec -info pos_info.dat -bg neg_info.dat -num 2000 -w 24 -b 24
根据上图的解释可知:-vec为最终生成的文件,-num为要产生的正样本的数量,-w为输出的样本高度,-h为输出的样本宽度。
接下来利用opencv提供的opencv_traincascade.exe进行训练,在命令行中调用该exe,输入如下命令:
opencv_traincascade -data ../model/adaboost -vec pos.vec -bg neg_info.dat -numPos 2000 -numNeg 7000
根据上图的解释可知:在训练过程中所有中间模型都会放在model/adaboost这个文件夹里,这里采用2000个正样本和7000个负样本。
级联器的训练很慢,大概训练了一天左右,在模型文件夹中存放着每一级的弱分类器和最终的分类器。
结合训练好的hog-svm分类器和haar-cascade分类器。便可以检测出物体。大致pipline如下:
部分检测代码如下:
void FinalDetect(string filename, string model_cascade, string model_hog, int dataset = 1, bool IsLine = false) {
setUseOptimized(true);
setNumThreads(8);
HOGDescriptor my_hog(Size(Window_y, Window_x), Size(block_y, block_x), Size(block_stride_y, block_stride_x),
Size(cell_y, cell_x), 9);
CascadeClassifier car_classifier;
car_classifier.load(model_cascade);
//get support vector from model
Ptr model = ml::StatModel::load(model_hog);
Mat sv = model->getSupportVectors();
vector<float> hog_detector;
const int sv_total = sv.cols;
Mat alpha, svidx;
double rho = model->getDecisionFunction(0, alpha, svidx);
Mat alpha2;
alpha.convertTo(alpha2, CV_32FC1);
Mat result(1, sv_total, CV_32FC1);
result = alpha2 * sv;
for (int i = 0; i < sv_total; ++i)
hog_detector.push_back(-1 * result.at<float>(0, i));
hog_detector.push_back((float) rho);
//load vector to hog detector
my_hog.setSVMDetector(hog_detector);
vector detections;
vector<double> foundWeights;
vector<int> rejLevel;
vector dets;
vector keep;
VideoCapture cap;
cap.open(filename);
while (true) {
Mat img;
cap >> img;
if (!img.data)
break;
resize(img, img, Size(448, 448));
cout << img.size() << endl;
if (IsLine)
LineDetect2(img, dataset);
detections.clear();
foundWeights.clear();
rejLevel.clear();
dets.clear();
keep.clear();
my_hog.detectMultiScale(img, detections, foundWeights, 0, Size(8, 8), Size(), 1.1, 2., true);
cout << "hog detect object: " << detections.size() << endl;
for (size_t i = 0; i < detections.size(); i++) {
if (foundWeights[i] > 1.3) {
bbox_info tmp_bbox(detections[i].x, detections[i].y, detections[i].br().x, detections[i].br().y,
foundWeights[i]);
dets.push_back(tmp_bbox);
}
}
car_classifier.detectMultiScale(img, detections, rejLevel, foundWeights, 1.1, 3, 0, Size(), Size(), true);
cout << "cascade detect object: " << detections.size() << endl;
for (int i = 0; i < detections.size(); i++) {
if (rejLevel[i] < 20 || foundWeights[i] < 1.)
continue;
bbox_info tmp(detections[i].x, detections[i].y, detections[i].br().x, detections[i].br().y,
foundWeights[i]);
dets.push_back(tmp);
}
keep = nms(dets);
for (size_t i = 0; i < keep.size(); i++) {
Point p1(keep[i].xmin, keep[i].ymin), p2(keep[i].xmax, keep[i].ymax);
Scalar color(0, 255, 0);
rectangle(img, p1, p2, color, 2);
}
imshow("detect", img);
waitKey(0);
}
}
部分结果如下:
参考博文3,博文4,博文5,对于车道检测,主要采用如下的pipline:
Point2f origin[] = {Point2f(204, 286), Point2f(71, 448), Point2f(394, 448), Point2f(243, 286)};
Point2f dst[] = {Point2f(112, 0), Point2f(112, 448), Point2f(336, 448), Point2f(336, 0)};
trans = getPerspectiveTransform(origin, dst);
warpPerspective(img_o ,img, trans, img.size());
void mag_threshold(const Mat img, Mat &out, int sobel_kernel, int min_thres, int max_thres) {
cvtColor(img, out, CV_BGR2GRAY);
Sobel(out, out, CV_8UC1, 1, 0, sobel_kernel);
normalize(out, out, 0, 255, NORM_MINMAX);
threshold(out, out, min_thres, 0, THRESH_TOZERO);
threshold(out, out, max_thres, 255, THRESH_BINARY);
}
void yellow_white_threshold(Mat origin, Mat &out1) {
int y_lower[] = {10, 0, 100};
int y_upper[] = {40, 255, 255};
int w_lower[] = {0, 200, 0};
int w_upper[] = {180, 255, 255};
Mat HLS, y_mask, w_mask, mask;
cvtColor(origin, HLS, CV_BGR2HLS);
vector<int> yellow_lower(y_lower, y_lower + 3);
vector<int> yellow_upper(y_upper, y_upper + 3);
vector<int> white_lower(w_lower, w_lower + 3);
vector<int> white_upper(w_upper, w_upper + 3);
inRange(HLS, yellow_lower, yellow_upper, y_mask);
inRange(HLS, white_lower, white_upper, w_mask);
bitwise_or(y_mask, w_mask, mask);
bitwise_and(origin, origin, out1, mask);
cvtColor(out1, out1, CV_HLS2BGR);
cvtColor(out1, out1, CV_BGR2GRAY);
threshold(out1, out1, 130, 255, THRESH_BINARY);
}
vector lines;
vector leftlines;
vector rightlines;
HoughLinesP(out1, lines, 1, CV_PI / 180, 50, 30, 10);
cout << lines.size() << endl;
for (size_t i = 0; i < lines.size(); i++) {
//abandon horizontal line.
if (lines[i][1] == lines[i][3])
continue;
//get left lines
if (lines[i][0] <= 224 && lines[i][2] <=224){
float k = 1.5;
//if not verticle line
if (lines[i][0] != lines[i][2])
k = fabs(float(lines[i][3]-lines[i][1])/float(lines[i][2]-lines[i][0]));
if (k>=1.5) {
leftlines.push_back(Point2f(lines[i][0], lines[i][1]));
leftlines.push_back(Point2f(lines[i][2], lines[i][3]));
}
}
Vec4f line_left, line_right;
fitLine(leftlines, line_left, DIST_L1, 0, 0.01, 0.01);
fitLine(rightlines, line_right, DIST_L1, 0, 0.01, 0.01);
源代码详见:我的github