traincascade 是opencv训练自己的分类器的工具,具体使用的时候有很多出错点,具体见 http://blog.csdn.net/bomingzi/article/details/77123678
这说一下使用步骤
第一: 准备正负样本,负样本建议自己采集,如果是别人提供的非常大的负样本,训练起来非常费时间、
这里说一下正负样本,正样本是包含你所要检测的图片的样本,但不要只包含你检测的图片,而且你会发现裁剪起来非常不好裁剪,这里我有一个方便裁剪的程序
#include "stdafx.h"
#include <opencv2\core\core.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>
#include <opencv.hpp>
#include <sstream>
#include <direct.h>
using namespace std;
using namespace cv;
Mat org, dst, tmp,img;
int num = 0;
int pic_x = 200;
int pic_y = 200;
void on_mouse(int event, int x, int y, int flags, void *ustc)
{
static Point pre_pt = (-1, -1);//初始坐标
static Point cur_pt = (-1, -1);//实时坐标
char temp[16];
if (event == CV_EVENT_LBUTTONDOWN)//左键按下,读取初始坐标,并在图像上该点处划圆
{
org.copyTo(img);//将原始图片复制到img中
sprintf_s(temp, "(%d,%d)", x, y);
pre_pt = Point(x, y);
putText(img, temp, pre_pt, FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0, 0, 0, 255), 1, 8);//在窗口上显示坐标
circle(img, pre_pt, 2, Scalar(255, 0, 0, 0), CV_FILLED, CV_AA, 0);//划圆
imshow("img", img);
}
else if (event == CV_EVENT_MOUSEMOVE && !(flags & CV_EVENT_FLAG_LBUTTON))//左键没有按下的情况下鼠标移动的处理函数
{
img.copyTo(tmp);//将img复制到临时图像tmp上,用于显示实时坐标
sprintf_s(temp, "(%d,%d)", x, y);
cur_pt = Point(x, y);
putText(tmp, temp, cur_pt, FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0, 0, 0, 255));//只是实时显示鼠标移动的坐标
imshow("img", tmp);
}
else if (event == CV_EVENT_MOUSEMOVE && (flags & CV_EVENT_FLAG_LBUTTON))//左键按下时,鼠标移动,则在图像上划矩形
{
img.copyTo(tmp);
sprintf_s(temp, "(%d,%d)", x, y);
cur_pt = Point(x, y);
putText(tmp, temp, cur_pt, FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0, 0, 0, 255));
rectangle(tmp, pre_pt, cur_pt, Scalar(0, 255, 0, 0), 1, 8, 0);//在临时图像上实时显示鼠标拖动时形成的矩形
imshow("img", tmp);
}
else if (event == CV_EVENT_LBUTTONUP)//左键松开,将在图像上划矩形
{
org.copyTo(img);
sprintf_s(temp, "(%d,%d)", x, y);
cur_pt.x = pre_pt.x + pic_x;
cur_pt.y = pre_pt.y + pic_y;
//cur_pt = Point(x,y); //如果要自定义绘制截图大小,这句话就可以,因为截取样本需要固定大小,所以这里不用
putText(img, temp, cur_pt, FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0, 0, 0, 255));
circle(img, pre_pt, 2, Scalar(255, 0, 0, 0), CV_FILLED, CV_AA, 0);
rectangle(img, pre_pt, cur_pt, Scalar(0, 0, 255, 0), 1, 8, 0);//根据初始点和结束点,将矩形画到img上
imshow("img", img);
img.copyTo(tmp);
//截取矩形包围的图像,并保存到dst中
int width = abs(pre_pt.x - cur_pt.x);
int height = abs(pre_pt.y - cur_pt.y);
if (width == 0 || height == 0)
{
printf("width == 0 || height == 0");
return;
}
dst = org(Rect(min(cur_pt.x, pre_pt.x), min(cur_pt.y, pre_pt.y), width, height));
namedWindow("dst");
imshow("dst", dst);
if (waitKey(0) == 13)
{
stringstream ss;
string str;
ss << "G:\\template\\sample\\" << num << ".jpg";
ss >> str;
imwrite(str, dst);
num++;
cout << "has saved " << str << endl;
destroyWindow("dst");
destroyWindow("img");
}
else
{
cout << "dont save" << endl;
}
}
}
int main()
{
VideoCapture cap(0);
char key;
while (1)
{
cap >> org;
key=waitKey(30);
imshow("ddd", org);
if (key == 'a' )
{
org.copyTo(img);
org.copyTo(tmp);
namedWindow("img");
setMouseCallback("img", on_mouse, 0);//调用回调函数
imshow("img", img);
}
if (key==27)
{
destroyWindow("ddd");
break;
}
}
return 0;
}
大致就是按下a开始选框框,然后按enter保存,路径是
G:\\template\\sample\\" << num << ".jpg";
这个大家根据自己的盘符保存。保存完之后,图片是这个格式的
int pic_x = 200;
int pic_y = 200;
附一下我的python代码吧,另外感叹下python真的很方便我当时没学python的时候为了写个归一化写了两个cpp。
import os
from PIL import Image
import sys
def use(f,w,h):
fs = os.listdir(f)
for f1 in fs:
tmp_path = os.path.join(f, f1)
if not os.path.isdir(tmp_path):
print('文件: %s' % tmp_path)
img = Image.open(tmp_path)
out = img.resize((w, h)) # 改变大小
out.save(tmp_path)
else:
print('文件夹:%s' % tmp_path)
use(tmp_path.w,h)
if __name__ == "__main__":
path = 'D:/picture/background'
use(path,20,20)
然后是正样本统一命名,方便写标签,当然也可以不命名,反正有强大的python
再来一段python写txt的代码吧
import os
def traverse(f,w,h):
with open("D:/pos.txt", "w") as f_t:
fs = os.listdir(f)
for f1 in fs:
tmp_path = os.path.join(f, f1)
#print(f1)
if not os.path.isdir(tmp_path):
print('文件: %s' % tmp_path)
f_t.write(f1+' '+str(1)+' '+str(0)+' '+str(0)+' '+str(w)+' '+str(h)+'\n')
else:
print('文件夹:%s' % tmp_path)
traverse(tmp_path,w,h)
if __name__ == "__main__":
path = 'D:/picture/background'
traverse(path,20,20)
这样正样本的txt和图片都准备好了,把他们放到一个文件夹下
类似于这样
负样本采集完之后不需要处理,但是不要像素太大,否则训练时间太长。 负样本写txt,这个直接用windows命令就可以
然后开始创建正样本的vec,我把他写成一个txt,每次使用的时候从命令窗口里面输入。在正样本文件夹下输入
opencv_createsamples.exe -info pos.txt -vec pos.vec -num 68 -w 40 -h 40
这里的num就是你的图片的数量,和后面的不一样。
会创建一个pos.vec文件,这个文件是traincascade用的,把它拿出来,方便traincascade用
这里要说一下neg,txt 因为我的neg.txt在neg图片文件夹的外部,所以我的neg.txt是这样写的
如果你的neg文件夹在里面直接写名字就好
然后是traincascade
opencv_traincascade.exe -data DATA -vec pos.vec -bg neg.txt -numPos 60 -numNeg 200 -numStages 11 -w 40 -h 40 -minHitRate 0.99 -featureType HAAR -mode ALL
这里解释下参数的意思,
-data 就是你的分类器放的位置,一定要是空文件夹
-vev 就是你刚才创建的vec的位置
-bg neg.txt 是负样本标签的txt
-numPos 取值小于图片数量,具体见之前的博客
-numNeg 负样本图片的数量
-numStages 这个有特殊的要求,具体见之前的博客
-w -h 这个是你正样本标签里面写的w和h,不能超过40,不然会报错
-minHitRate 这个也有要求,越接近于1越好。
-featureType 可以选择LBP HAAR HOG特征,下文会有赘述
-mode 只有HAAR特征有这个参数,这个mode资源够的话尽量选ALL,如果你是32位的系统,选ALL可能会内存不够。
参数就是这样,训练完之后会有这个文件
下面就开始来用这个文件,用的方法
#include "stdafx.h"
#include "prepare.h"
using namespace std;
using namespace sl;
Camera zed;
cv::Mat image_ocv;
cv::Mat image_gray;
void on_mouse(int event, int x, int y, int flags, void *ustc)
{
if (event == CV_EVENT_LBUTTONDOWN)//左键按下,读取初始坐标,并在图像上该点处划圆
{
cv::Vec4f data;
sl::Mat depth, point_cloud;
zed.retrieveMeasure(depth, MEASURE_DEPTH);
zed.retrieveMeasure(point_cloud, MEASURE_XYZRGBA);
sl::float4 point_cloud_value;
point_cloud.getValue(x, y, &point_cloud_value);
float distance = sqrt(point_cloud_value.x*point_cloud_value.x + point_cloud_value.y*point_cloud_value.y + point_cloud_value.z*point_cloud_value.z);
printf("Distance to Camera at (%d, %d): %f m\n", x, y, distance);
//cv::cvtColor(image_ocv, image_gray, CV_RGB2GRAY);
//int gray_data = image_gray.at<unsigned char>(x, y);
//cv::imshow("gray", image_gray);
//cv::waitKey(1000);
//cv::destroyWindow("gray");
//cout << "depth: " << image_gray.depth() << endl;
//cout << "the gray is " << gray_data << endl;
//data = image_ocv.at<cv::Vec4f>(x, y);
//int gray = data[0] * 0.299 + data[1] * 0.587 + data[2] * 0.114;
//cout << "the gray is " << gray << endl;
//printf("%d %d %d", data[0], data[1], data[2]);
/*
sl::Mat depth_zed(zed.getResolution(), MAT_TYPE_32F_C1);
cv::Mat depth_ocv = slMat2cvMat(depth_zed);
zed.retrieveMeasure(depth_zed, MEASURE_DEPTH);
std::cout << depth_ocv.at<float>(x, y) << std::endl;
*/
}
}
float get_deepth(int x, int y)
{
cv::Vec4f data;
sl::Mat depth, point_cloud;
zed.retrieveMeasure(depth, MEASURE_DEPTH);
zed.retrieveMeasure(point_cloud, MEASURE_XYZRGBA);
sl::float4 point_cloud_value;
point_cloud.getValue(x, y, &point_cloud_value);
float distance = sqrt(point_cloud_value.x*point_cloud_value.x + point_cloud_value.y*point_cloud_value.y + point_cloud_value.z*point_cloud_value.z);
printf("Distance to Camera at (%d, %d): %f m\n", x, y, distance);
return distance;
}
int main()
{
/*
cv::Mat src;
src = cv::imread("G:\\pic\\timg.jpg");
fitting_rect(src);
cv::waitKey(0);
*/
std::string szName = "NameOfMappingObject";
HANDLE hMapFile = CreateFileMapping(
INVALID_HANDLE_VALUE, //物理文件句柄
NULL, //默认安全级别
PAGE_READWRITE, //可读可写
0, //高位文件大小
BUF_SIZE, //地位文件大小
stringToLPCWSTR(szName) //共享内存名称
);
char *pBuf = (char *)MapViewOfFile(
hMapFile, //共享内存的句柄
FILE_MAP_ALL_ACCESS,//可读写许可
0,
0,
BUF_SIZE
);
string xmlPath = "G:\\traincascade\\DATA\\cascade.xml";
cv::CascadeClassifier cas;
cas.load(xmlPath);
if (cas.empty())
{
std::cerr << "load detector failed!!!" << std::endl;
return -1;
}
InitParameters init_params;
init_params.depth_mode = DEPTH_MODE_PERFORMANCE;
init_params.camera_resolution = RESOLUTION_HD2K;
init_params.coordinate_units = UNIT_MILLIMETER;
ERROR_CODE err = zed.open(init_params);
if (err != SUCCESS)
{
exit(-1);
cout << "can not open the camera" << endl;
}
RuntimeParameters runtime_parameters;
runtime_parameters.sensing_mode = SENSING_MODE_STANDARD; // Use STANDARD sensing mode
sl::Mat image(zed.getResolution(), MAT_TYPE_8U_C3);
cv::Mat image_left(image.getHeight(), image.getWidth(), CV_8UC3);
char c = 0;
while (1)
{
if (zed.grab(runtime_parameters) == SUCCESS)
{
zed.retrieveImage(image, VIEW_LEFT);
image_left = slMat2cvMat(image);
cv::imshow("aaa", image_left);
//cv::setMouseCallback("aaa", on_mouse, 0);
if (c == 49)
{
std::vector<cv::Rect> rect;
cv::Mat my_data;
my_data = image_left.clone();
//cv::cvtColor(my_data, my_data, CV_RGBA2BGR);
//CV_HAAR_SCALE_IMAGE
cas.detectMultiScale(my_data, rect, 1.1, 3, CV_HAAR_DO_CANNY_PRUNING, cv::Size(70, 70), cv::Size(110, 110));
cv::Rect last_rect = fitting_rect(image_left);
for (int i = 0; i < rect.size(); i++)
{
int distance_x;
int distance_y;
distance_x = abs(last_rect.x - rect[i].x);
distance_y = abs(last_rect.y - rect[i].y);
if (distance_x < 30)
{
cv::rectangle(my_data, rect[i], cv::Scalar(255, 65, 124), 2, 14);
cout << " x : " << rect[i].x << " y: " << rect[i].y << endl;
float rect_deepth = get_deepth(rect[i].x, rect[i].y);
cout << "start send" << endl;
char szInfo[BUF_SIZE];
sprintf_s(szInfo, "x y z : %d: %d: %f", rect[i].x, rect[i].y, rect_deepth);
strncpy(pBuf, szInfo, BUF_SIZE - 1);
pBuf[BUF_SIZE - 1] = '\0';
}
//cv::rectangle(my_data, rect[i], cv::Scalar(255, 65, 124), 2, 14);
}
cv::imshow("data", my_data);
cv::waitKey(4000);
cv::destroyWindow("data");
//cas.detectMultiScale()
}
if (c == 50)
{
cv::Rect last_rect=fitting_rect(image_left);
cv::waitKey(3000);
}
c = cv::waitKey(5);
if (c==27)
{
cv::destroyAllWindows();
break;
}
}
}
zed.close();
UnmapViewOfFile(pBuf);
CloseHandle(hMapFile);
cout << "alll ha benn end" << endl;
return 0;
}
这个是zed相机的识别程序,可以参考着改成普通相机的。
然后来说一下关于LBP,HOG,HAAR的区别
HOG方向梯度直方图,最著名的HOG+svm的行人检测,其实行人的轮廓特征是比较明显的,但是数目灯杆等会造成干扰,
毕竟边缘都是沿着一个方向的,所以还有了专门为了排除干扰的难负样本,HOG的核心是计算横坐标和纵坐标的方向梯度,
所以他的信息集中在轮廓的趋势,以及变换的频率(个人感觉),应用于一类物体的检测是比较好的,比如封口瓶?
LBP 局部二值模式,LBP的特点就是抽象化图像的特征,除去检测过程中很多不想要的细节,比如光照等,其提取特征的方法
有点类似于卷积,但是和卷积不同不会过分强调某一特征(卷积有很多卷积核),而且拥有旋转不变性。
HAAR特征,haar特征分为3类,边缘特征,线性特征,中心特征。(这些特征就很多了)HAAR用的比较多,因为他既可以关注到细节有能看到整体。
同时计算量也明显突出。对于一些稍微复杂的标识,HAAR有非常好的效果。我做过一个香烟的检测,HAAR的检测效果是比较好的。