废话少说,先上图:
一直想训练一个目标检测的级联分类器,花了一天的时间阅读其他优秀博客,然后自己实践了一下,里面也遇到一些坑,希望能给阅读本文章的读者带来帮助。
opencv 已经提供了训练好的人脸和眼睛的目标检测的xml文件 ,可以做到检测视频图像中是否有人脸 ,但无法做到 像dlib提供的人脸的具体特征点的定位
但是 自定义目标检测 才是我们真正感兴趣的。
实现流程:
1、正负样本的获取
1.1 正样本的抠图(代码在最后)
1.2 负样本(背景)
2、训练
3、检测(代码在最后)
正样本的抠图 :没用像objectmaker 或者 LabelImg之类的 目标截取软件 而是在vs中 自己写了一个 如下图所示:
时间比较紧,具体过程就不一步一步阐述了,需要注意的坑 有 ,生成负样本描述文件的 文件名 最好是文件的绝对路径,这样就可以忽略 在不同的文件夹 命令行执行 opencv_traincascade.exe 所产生的错误 如:大家所常遇到的 pos count:consumed Train dataset for temp stage can not be filled
执行训练的命令如下所示:
训练过程很短, 不超过一分钟, 样本 数量信息:正样本300个 ,负样本824个
1 抠图代码 opencv 实现
//
#include "stdafx.h"
#include "stdafx.h"
#include<opencv2\opencv.hpp>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>
#include <stdio.h>
using namespace cv;
using namespace std;
char filename[200];
String flodername = "C:/opencv/sources/apps/annotation/vs/annotation/postivephoto/";
int countss = 102;
cv::Mat org, dst, img, tmp ,frame;
void on_mouse(int event, int x, int y, int flags, void *ustc)//event鼠标事件代号,x,y鼠标坐标,flags拖拽和键盘操作的代号
{
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 = 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, 255, 0, 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);
sprintf_s(filename, "postive%d.bmp", ++countss);
String full_path = flodername + "/" + filename;
resize(dst, dst, Size(24, 24));
imwrite( full_path, dst);//图片保存到本工程目录中
cout << "postive" << countss << ".bmp has been stored" << endl;
}
}
int main()
{
#if 1
String flodername = "C:/opencv/sources/apps/annotation/vs/annotation/negtivephoto/";
//sprintf_s(flodername, "%s", flod.c_str());
cout << flodername << endl;
// 打开摄像头
VideoCapture capture(0);
if (false == capture.isOpened())
{
cout << "camera open failed!" << endl;
return -1;
}
while (true)
{
// 获取图片帧
capture >> org;
if (true == org.empty())
{
cout << "get no frame" << endl;
break;
}
// 显示原始图片
imshow("frame", org);
//复制采集到的frame到resultRGB
char key = (char)waitKey(10);
if (27 == key)
{
break;
}
// char key = (char)waitKey(10);
if (key == 32)//按空格键进行拍照
{
namedWindow("org");
imshow("org", org);
org.copyTo(tmp);
org.copyTo(img);
setMouseCallback("org", on_mouse, 0);
while (1)
{
key = (char)waitKey(10);
if (27 == key)
{
destroyWindow("org");
destroyWindow("img");
break;
}
}
}
}
#endif
return 0;
}
2 、检测目标代码 opencv 实现
// suqianfeng.cpp: 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <string>
#include <Windows.h>
#include<opencv2\opencv.hpp>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>
#include <stdio.h>
#include "opencv2/objdetect/objdetect.hpp"
#include "opencv2/imgproc/imgproc.hpp"
//#define unsigned char uchar
//#define unsigned int uint
#define MAXVALUE (255)
#define KERNEL_SIZE 5
using namespace std;
using namespace cv;
#include <iostream>
#include <stdio.h>
using namespace std;
using namespace cv;
/** Function Headers */
void detectAndDisplay(Mat frame);
/** Global variables */
//-- Note, either copy these two files from opencv/data/haarscascades to your current folder, or change these locations
String obj_cascade_name = "cascade.xml";
CascadeClassifier obj_cascade;
string window_name = "Capture - Face detection";
RNG rng(12345);
/**
* @function main
*/
int main(void)
{
VideoCapture capture;
Mat frame;
//-- 1. Load the cascades
if (!obj_cascade.load(obj_cascade_name)) { printf("--(!)Error loading\n");
while (1)
{
;
}
return -1;
};
//-- 2. Read the video stream
capture.open(0);
if (capture.isOpened())
{
for (;;)
{
capture >> frame;
//-- 3. Apply the classifier to the frame
if (!frame.empty())
{
detectAndDisplay(frame);
}
else
{
printf(" --(!) No captured frame -- Break!"); break;
}
int c = waitKey(10);
if ((char)c == 'c') { break; }
}
}
return 0;
}
/**
* @function detectAndDisplay
*/
void detectAndDisplay(Mat frame)
{
vector<Rect> rect;
std::vector<Rect> obj;
Mat frame_gray;
cvtColor(frame, frame_gray, COLOR_BGR2GRAY);
//-- Detect faces
obj_cascade.detectMultiScale(frame_gray, obj, 1.1, 2, 0 , Size(20, 30), Size(640, 480));
for (size_t i = 0; i < obj.size(); i++)
{
if (obj[i].width > 20 && obj[i].height > 20)
{
rect.push_back(Rect(obj[i].x, obj[i].y, obj[i].width, obj[i].height));
rectangle(frame, rect[i], Scalar(0, 0, 255), 2);
}
//Point center(obj[i].x + obj[i].width / 2, obj[i].y + obj[i].height / 2);
//ellipse(frame, center, Size(obj[i].width / 2, obj[i].height / 2), 0, 0, 360, Scalar(255, 0, 0), 3, 8, 0);
}
rect.clear();
//-- Show what you got
imshow(window_name, frame);
}