赞
踩
上一节我们分析了人脸检测的原理,以及简化了示例代码。在检测过程中,我们主要使用了opencv的级联分类器CascadeClassifier,而它提供的detectMultiScale方法能够检测出一张图片中的一个或多个人脸,并用矩形标识出人脸位置。结果如下图所示
在检测过程当中,我们并没有训练模型,而是使用opencv提供给我们训练好的模型。事实上,opencv提供给我们很多模型。我们可以根据这些模型做相应的检测。
我们可以根据这些模型,套用上次代码做相应不同的检测,比如根据名字,上图中红色框框起的模型应该是用来检测眼睛部位的。但是我们不能直接套用上次代码,原因是双眼检测是在人脸检测的基础上进行的,也就是说,检测某人双眼位置之前,我们需要先检测出该人的人脸部位,继而在人脸范围内寻找双眼部位,这样比较合理。
代码应该如下所示;
// face1.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。 // #define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <opencv2/opencv.hpp> using namespace std; using namespace cv; void detectAndDraw(Mat& img, CascadeClassifier& cascade, CascadeClassifier& nestedCascade, double scale, bool tryflip); string cascadeName; string nestedCascadeName; int main(int argc, const char** argv) { string inputName; bool tryflip; double scale; CommandLineParser parser(argc, argv, "{help h||}" "{cascade|data/haarcascades/haarcascade_frontalface_alt2.xml|}" //双眼检测模型 "{nested-cascade|data/haarcascades/haarcascade_eye.xml|}" "{scale|1|}{try-flip||}{@filename|D:/试验/test/smile.jpg|}" ); if (parser.has("help")) { return 0; } //解析参数得到人脸检测模型路径 cascadeName = parser.get<string>("cascade"); //解析参数得到眼检测模型路径 nestedCascadeName = parser.get<string>("nested-cascade"); //开始解析其他参数 scale = parser.get<double>("scale"); if (scale < 1) scale = 1; tryflip = parser.has("try-flip"); //解析输入文件名(就是我们想要检测的图像路径) inputName = parser.get<string>("@filename"); if (!parser.check()) { parser.printErrors(); return 0; } //构造分类器用于检测脸部 cv::CascadeClassifier cascade(cascadeName); //根据参数inputName加载被检测图像 Mat image = imread(inputName); Mat gray; //转灰度图便于检测 cvtColor(image, gray, cv::COLOR_BGRA2GRAY); vector<Rect> faces; //检测人脸 cascade.detectMultiScale(gray,faces); //构造“内层”的分类器,该分类器用于检测双眼 cv::CascadeClassifier nestedCascade(nestedCascadeName); for (int i = 0; i < faces.size(); i++) { //得到本人人脸检测范围,我们需要在这个范围内进行内层检测(双眼定位) Rect r = faces[i]; //绘制人脸矩形 cv::rectangle(image,r,Scalar(255, 0, 0)); //定义一个矩形数组,用于标识检测到的双眼 vector<Rect> faces2; //检测双眼矩形 nestedCascade.detectMultiScale(image(r), faces2); for (int j = 0; j < faces2.size(); j++) { Mat image2 = image(r); cv::rectangle(image2, faces2[j], Scalar(0, 255, 0)); } } //显示标记好的图像 imshow("检测人脸", image); waitKey(); return 0; }
结果如下图所示
让我们看看还有什么模型
上面红框标记的好像是检测笑脸。笑脸跟双眼一样也是在脸部检测的基础上,再来看微笑有无出现,所以可以套用上述代码。事实上我们可以利用这个模型,来检测出人脸微笑的程度。但是这个检测是个动态的概念,就是说,对人脸笑颜动态展开,会比较敏感。
接下来我们有一个视频,人再逐渐展开微笑时,视频两边红色条块随着男人微笑程度而逐渐明亮
效果视频
代码如下
#define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <opencv2/opencv.hpp> using namespace std; using namespace cv; void detectAndDraw(Mat& img, CascadeClassifier& cascade, CascadeClassifier& nestedCascade); string cascadeName; string nestedCascadeName; VideoWriter writer("output.mp4", CV_FOURCC('m', 'p', '4', 'v'), 10, Size(700, 350)); int main(int argc, const char** argv) { VideoCapture capture; Mat frame, image; string inputName; bool tryflip; CascadeClassifier cascade, nestedCascade; double scale; cv::CommandLineParser parser(argc, argv, "{help h||}{scale|1|}" "{cascade|data/haarcascades/haarcascade_frontalface_alt.xml|}" "{smile-cascade|data/haarcascades/haarcascade_smile.xml|}" "{try-flip||}{@input|D:/试验/test/smile.mp4|}"); //以下参数解析 if (parser.has("help")) { return 0; } cascadeName = samples::findFile(parser.get<string>("cascade")); nestedCascadeName = samples::findFile(parser.get<string>("smile-cascade")); inputName = parser.get<string>("@input"); if (!parser.check()) { return 1; } if (!cascade.load(cascadeName)) { cerr << "错误:不能加载人脸检测模型" << endl; return -1; } if (!nestedCascade.load(nestedCascadeName)) { cerr << "错误:不能加载内层检测模型" << endl; return -1; } if (inputName.empty() ) { cerr << "错误:被检测图像路径为空" << endl; return -1; } //跟据参数“inputName”打开相应视频文件,并读取一帧图像 capture.open(inputName); capture.read(frame); //循环读取视频每帧图像,并进行检测处理 while (!frame.empty()) { //检测处理以及微笑程度显示 detectAndDraw(frame, cascade, nestedCascade); capture.read(frame); } writer.release(); return 0; } //检测处理以及微笑程度显示 void detectAndDraw(Mat& img, CascadeClassifier& cascade, CascadeClassifier& nestedCascade) { //定义矩形数组存放人脸检测结果 vector<Rect> faces; Mat gray; //将原图转换为灰度图方便进一步处理 cvtColor(img, gray, COLOR_BGR2GRAY); //检测人脸 cascade.detectMultiScale(img, faces, 1.1, 2, 0|CASCADE_SCALE_IMAGE); //对每个人脸检测结果进行处理(进一步检测微笑程度) Mat imgOut; for (size_t i = 0; i < faces.size(); i++) { //得到本人人脸检测范围,我们需要在这个范围内进行内层检测(微笑密度) Rect r = faces[i]; Mat imgROI = img(r); //用于存储内层检测结果 vector<Rect> nestedObjects; //绘制人脸矩形 rectangle(img,r,Scalar(255,0,0)); //在矩形范围内检测微笑密度 nestedCascade.detectMultiScale(imgROI, nestedObjects, 1.1, 0, 0 | CASCADE_SCALE_IMAGE); // 微笑密度和内层检测的结果大小有关(成正比) const int smile_neighbors = (int)nestedObjects.size(); //结合前帧图像数据,经过换算得到真正的微笑密度 //注意:以下两个变量为静态变量,循环调用此函数,它们的值不会重新初始化。 static int max_neighbors = -1; static int min_neighbors = -1; if (min_neighbors == -1) min_neighbors = smile_neighbors; max_neighbors = MAX(max_neighbors, smile_neighbors); float intensityZeroOne = ((float)smile_neighbors - min_neighbors) / (max_neighbors - min_neighbors + 1); //根据微笑密度换算“红色”深度 Scalar color = Scalar(0,0,(float)255 * intensityZeroOne); //绘制左右两边矩形,显示微笑程度 rectangle(img, Point(0, 0), Point(img.cols / 10, img.rows), color, -1); rectangle(img, Point(img.cols*9/10, 0), Point(img.cols , img.rows), color, -1); } resize(img, imgOut,Size(700, 350)); writer.write(imgOut); }
事实你还可以依此原理玩出其他花样,比如用微笑触发烟花,植入手机,用微笑触发拍照,植入电子锁,用微笑打开日记,等等
最后值得一提的是,实际上opencv只是实现了经典的算法,但很多时候并不精确,还需要我们根据工作需要改进。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。