深度学习准确率高识别效果好,但是对软硬件环境要求高,使用及部署有一定难度,应用受限。相比之下,机器学习效果差一个量级,但是计算量小,计算迅速,部署相对容易,尤其是在一些计算能力有限的嵌入式应用场合,因此这里打算学习一下基于OpenCV实现的机器学习,套路上打算先找一些例程学习怎么使用,再学习怎么训练自己的算法。
先前在github上下载到了几个车辆识别的机器学习例程,但是发现大多是使用python实现的,不便应用于嵌入式项目中,这里我们尝试转为Visual Studio C/C++编码状态。
图1 github上下载的基于OpenCV用python实现的车辆识别代码及效果
下载的源码随例程附带了训练好的算法模型xml文件、测试视频和.cpp样例源码:
图2 例程xml文件及附属的.cpp状态源码
一、Python转C/C++尝试与遇到的问题
我们在VS2015下新建功能,加入该cpp文件,发现报错较多无法编译执行:
图3 例程cpp文件编译报错
检查发现,是因为我安装的是OpenCV4.5.5,版本较高,而从例程源码看,原作者使用的OpenCV版本偏低,因此许多功能函数已经变更,无法再使用。这里面主要是CvHaarClassifierCascade加载xml文件做目标检测识别的相关函数找不到新的定义所在,在网上搜索“OpenCV4.5 CvHaarClassifierCascade”、“OpenCV4.5 cvLoad”、“OpenCV4.5 机器学习”均找不到关于在OpenCV4.5.5状态下怎么加载xml进行机器学习目标检测识别的例程。
二、思路与解决
突然想到,我的OpenCV是在官网上下载了源码,然后在本地自行编译形成的OpenCV库,源码中包含了使用了例程Sample,其中一定有关于机器学习ml模块的使用样例,可以参考OpenCV源码自带的样例找到OpenCV4.5.5下机器学习的应用方法。
最后在.\opencv-4.5.5\samples\cpp下找到一个人脸识别的例程:
图4 OpenCV4.5.5自带的人脸识别cpp例程
把它拷贝出来加载到程序中,编译没有报错,运行也正常,它是一个启动电脑摄像头拍摄电脑前人员并进行人脸识别的例程:
图5 OpenCV4.5.5自带的人脸识别cpp例程运行效果(人员做了部分隐藏处理)
通过对比两个程序,参考OpenCV4.5.5自带的人脸识别例程方法,对github上下载的软件进行重新改编:
图6 github例程xml加载方法与OpenCV4.5.5自带的人脸识别cpp例程对比
对比发现,github下载的例程采用的是CvHaarClassifierCascade,用网上最常见的cvLoad(xml_file_path, 0, 0, 0)函数加载xml文件,而OpenCV4.5.5则采用了CascadeClassifier的.load(xml_file_path)函数形式加载。在具体识别上,github例程采用了cvHaarDetectObjects(...)函数,而OpenCV4.5.5例程则采用了.detectMultiScale()函数,并且还对目标做了翻转测试:
图7 github例程目标检测函数与OpenCV4.5.5自带的人脸识别cpp例程对比
结合github例程读取本地视频进行测验的方式,我们参考OpenCV4.5.5人脸识别cpp例程改编如下:
#include <opencv2/opencv.hpp>
#include <opencv2\objdetect\objdetect.hpp>
#include <iostream>
#include <string>
#include <fstream>
const int KEY_SPACE = 32;
const int KEY_ESC = 27;
//定义路径
const char *carCascade = "cars.xml";
void detectAndDraw(cv::Mat& img, cv::CascadeClassifier& cascade, double scale, bool tryflip)
{
double t = 0;
std::vector<cv::Rect> faces, faces2;
const static cv::Scalar colors[] =
{
cv::Scalar(255,0,0),
cv::Scalar(255,128,0),
cv::Scalar(255,255,0),
cv::Scalar(0,255,0),
cv::Scalar(0,128,255),
cv::Scalar(0,255,255),
cv::Scalar(0,0,255),
cv::Scalar(255,0,255)
};
cv::Mat gray, smallImg;
cvtColor(img, gray, cv::COLOR_BGR2GRAY);
double fx = 1 / scale;
resize(gray, smallImg, cv::Size(), fx, fx, cv::INTER_LINEAR_EXACT);
equalizeHist(smallImg, smallImg);
t = (double)cv::getTickCount();
cascade.detectMultiScale(smallImg, faces,
1.1, 2, 0
//|CASCADE_FIND_BIGGEST_OBJECT
//|CASCADE_DO_ROUGH_SEARCH
| cv::CASCADE_SCALE_IMAGE,
cv::Size(30, 30));
if (tryflip)
{
flip(smallImg, smallImg, 1);
cascade.detectMultiScale(smallImg, faces2,
1.1, 2, 0
//|CASCADE_FIND_BIGGEST_OBJECT
//|CASCADE_DO_ROUGH_SEARCH
| cv::CASCADE_SCALE_IMAGE,
cv::Size(30, 30));
for (std::vector<cv::Rect>::const_iterator r = faces2.begin(); r != faces2.end(); ++r)
{
faces.push_back(cv::Rect(smallImg.cols - r->x - r->width, r->y, r->width, r->height));
}
}
t = (double)cv::getTickCount() - t;
printf("detection time = %g ms\n", t * 1000 / cv::getTickFrequency());
for (size_t i = 0; i < faces.size(); i++)
{
cv::Rect r = faces[i];
cv::Mat smallImgROI;
std::vector<cv::Rect> nestedObjects;
cv::Point center;
cv::Scalar color = colors[i % 8];
int radius;
double aspect_ratio = (double)r.width / r.height;
if (0.75 < aspect_ratio && aspect_ratio < 1.3) {
rectangle(img, cv::Point(cvRound(r.x*scale), cvRound(r.y*scale)),
cv::Point(cvRound((r.x + r.width - 1)*scale), cvRound((r.y + r.height - 1)*scale)),
color, 3, 8, 0);
}
else {
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);
}
}
cv::imshow("cars", img);
}
int main(int argc, char** argv)
{
std::cout << "Using OpenCV " << CV_MAJOR_VERSION << "." << CV_MINOR_VERSION << "." << CV_SUBMINOR_VERSION << std::endl;
cv::Mat frame;
cv::Mat frame1;
cv::Size size;
int input_resize_percent = 100;
cv::CascadeClassifier casca;
cv::VideoCapture vdocap;
if (!casca.load(carCascade)) {
std::cout << "CascadeClassifier Load " << carCascade << " Failed" << std::endl;
}
else {
std::cout << "CascadeClassifier Load " << carCascade << " Success" << std::endl;
}
if (argc == 4) {
input_resize_percent = atoi(argv[3]);
std::cout << "Resizing to: " << input_resize_percent << "%" << std::endl;
}
vdocap.open("video1.avi");
cv::namedWindow("cars", 1);
vdocap.read(frame1);
size = cv::Size((int)((frame1.cols*input_resize_percent) / 100), (int)((frame1.rows*input_resize_percent) / 100));
frame = cv::Mat(size, frame1.depth(), frame1.channels());
int key = 0;
do {
vdocap.read(frame1);
if (frame1.empty())
break;
cv::resize(frame1, frame, size);
//detect(frame);
detectAndDraw(frame, casca, 1, true);
//cv::imshow("video", frame1);
key = cv::waitKey(40);
if (key == KEY_SPACE)
key = cv::waitKey(0);
if (key == KEY_ESC)
break;
} while (1);
cv::destroyAllWindows();
frame.release();
frame1.release();
vdocap.release();
return 0;
}
改编后的源码编译无误:
图8 根据OpenCV4.5.5自带的人脸识别cpp例程改编github例程
运行效果与python状态相近:
图9 改编后运行效果图
至此,改编结束,下一步学习怎么训练自己的模型