


// 包含OpenCV库中的对象检测、图形用户界面、图像处理、视频捕获等相关的头文件
#include "opencv2/objdetect.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
#include "opencv2/videoio.hpp"
#include <iostream> // 包含输入输出流的头文件
// 使用标准名字空间std和OpenCV名字空间cv
using namespace std;
using namespace cv;
// 函数声明:帮助信息函数,打印程序如何使用的说明
static void help(const char** argv);
// 函数声明:检测和绘制函数,用于检测图像中的对象(比如脸部和眼睛)并绘制检测结果
void detectAndDraw( Mat& img, CascadeClassifier& cascade,
CascadeClassifier& nestedCascade,
double scale, bool tryflip );
// 全局变量声明:主级联分类器和次级联分类器的名字,用于对象检测
string cascadeName;
string nestedCascadeName;
// 主函数入口
int main( int argc, const char** argv )
{
VideoCapture capture; // 定义一个VideoCapture对象用于视频捕获
Mat frame, image; // 定义Mat对象用于存放视频帧和图片
string inputName; // 定义一个输入名字的字符串
bool tryflip; // 定义一个bool变量表示是否尝试翻转检测
CascadeClassifier cascade, nestedCascade; // 定义两个级联分类器对象
double scale; // 定义一个缩放比例
// 定义一个命令行解析器,用于处理命令行输入参数
cv::CommandLineParser parser(argc, argv,
"{help h||}" // 帮助信息
"{cascade|data/haarcascades/haarcascade_frontalface_alt.xml|}" // 主分类器xml文件路径
"{nested-cascade|data/haarcascades/haarcascade_eye_tree_eyeglasses.xml|}" // 嵌套分类器xml文件路径
"{scale|1|}{try-flip||}{@filename||}" // 缩放比例,是否翻转,文件名参数
);
// 如果有帮助标识,则打印帮助信息并退出
if (parser.has("help"))
{
help(argv);
return 0;
}
// 获取主分类器文件路径
cascadeName = parser.get<string>("cascade");
// 获取嵌套分类器文件路径
nestedCascadeName = parser.get<string>("nested-cascade");
// 获取缩放比例
scale = parser.get<double>("scale");
if (scale < 1) // 如果缩放比例小于1,则强制设为1
scale = 1;
// 获取是否翻转标识
tryflip = parser.has("try-flip");
// 获取文件名参数
inputName = parser.get<string>("@filename");
// 检查参数解析是否有误
if (!parser.check())
{
parser.printErrors();
return 0;
}
// 加载嵌套分类器文件
if (!nestedCascade.load(samples::findFileOrKeep(nestedCascadeName)))
cerr << "WARNING: Could not load classifier cascade for nested objects" << endl;
// 加载主分类器文件
if (!cascade.load(samples::findFile(cascadeName)))
{
cerr << "ERROR: Could not load classifier cascade" << endl;
help(argv);
return -1;
}
// 根据inputName来判断加载视频流还是图像
if( inputName.empty() || (isdigit(inputName[0]) && inputName.size() == 1) )
{
int camera = inputName.empty() ? 0 : inputName[0] - '0';
// 尝试打开摄像头视频流
if(!capture.open(camera))
{
cout << "Capture from camera #" << camera << " didn't work" << endl;
return 1;
}
}
else if (!inputName.empty())
{
// 尝试读取图片
image = imread(samples::findFileOrKeep(inputName), IMREAD_COLOR);
if (image.empty())
{
// 尝试打开视频文件
if (!capture.open(samples::findFileOrKeep(inputName)))
{
cout << "Could not read " << inputName << endl;
return 1;
}
}
}
else
{
// 默认尝试读取lena.jpg图片
image = imread(samples::findFile("lena.jpg"), IMREAD_COLOR);
if (image.empty())
{
cout << "Couldn't read lena.jpg" << endl;
return 1;
}
}
// 如果视频流打开成功
if( capture.isOpened() )
{
cout << "Video capturing has been started ..." << endl;
// 无限循环直到视频流结束或用户键入退出
for(;;)
{
capture >> frame; // 从视频流中获取新的帧
if( frame.empty() ) // 如果帧为空(视频结束),则退出循环
break;
Mat frame1 = frame.clone(); // 复制当前帧
// 调用函数进行检测并绘制结果
detectAndDraw( frame1, cascade, nestedCascade, scale, tryflip );
// 每10毫秒检查一次,是否有退出按键被按下
char c = (char)waitKey(10);
if( c == 27 || c == 'q' || c == 'Q' ) // 如果按下ESC/Q,则退出循环
break;
}
}
else
{
// 如果是读取的图片文件
cout << "Detecting face(s) in " << inputName << endl;
if( !image.empty() )
{
// 直接进行检测并绘制结果
detectAndDraw( image, cascade, nestedCascade, scale, tryflip );
waitKey(0); // 等待用户操作,无限等待
}
else if( !inputName.empty() )
{
// 如果带有文件名参数,这里假设它是包含图像文件名列表的文本文件,每行一个文件名
FILE* f = fopen( inputName.c_str(), "rt" ); // 以文本模式打开文件
if( f )
{
char buf[1000+1]; // 定义缓冲区以读取行
// 循环读取文件中的每一行,直到文件结束
while( fgets( buf, 1000, f ) )
{
int len = (int)strlen(buf); // 获取行长度
while( len > 0 && isspace(buf[len-1]) ) // 去除行尾空白符
len--;
buf[len] = '\0'; // 设置字符串结束符
cout << "file " << buf << endl; // 输出文件名
image = imread( buf, IMREAD_COLOR ); // 读取图片
if( !image.empty() ) // 如果图片读取成功
{
// 进行检测并绘制结果
detectAndDraw( image, cascade, nestedCascade, scale, tryflip );
char c = (char)waitKey(0); // 等待用户操作
if( c == 27 || c == 'q' || c == 'Q' ) // 如果按下ESC/Q,则退出读取循环
break;
}
else
{
cerr << "Aw snap, couldn't read image " << buf << endl; // 读取失败信息
}
}
fclose(f); // 关闭文件
}
}
}
return 0; // 正常退出程序
}
// 帮助信息函数的实现,用于打印如何使用该程序的信息
static void help(const char** argv)
{
cout << "\nThis program demonstrates the use of cv::CascadeClassifier class to detect objects (Face + eyes). You can use Haar or LBP features.\n"
"This classifier can recognize many kinds of rigid objects, once the appropriate classifier is trained.\n"
"It's most known use is for faces.\n"
"Usage:\n"
<< argv[0]
<< " [--cascade=<cascade_path> this is the primary trained classifier such as frontal face]\n"
" [--nested-cascade[=nested_cascade_path this an optional secondary classifier such as eyes]]\n"
" [--scale=<image scale greater or equal to 1, try 1.3 for example>]\n"
" [--try-flip]\n"
" [filename|camera_index]\n\n"
"example:\n"
<< argv[0]
<< " --cascade=\"data/haarcascades/haarcascade_frontalface_alt.xml\" --nested-cascade=\"data/haarcascades/haarcascade_eye_tree_eyeglasses.xml\" --scale=1.3\n\n"
"During execution:\n\tHit any key to quit.\n"
"\tUsing OpenCV version " << CV_VERSION << "\n" << endl;
}
// 检测和绘制函数的实现,用于检测图像中的对象(比如脸部和眼睛)并绘制检测结果
// 定义detectAndDraw函数,用于检测并绘制识别结果
void detectAndDraw( Mat& img, CascadeClassifier& cascade,
CascadeClassifier& nestedCascade,
double scale, bool tryflip )
{
double t = 0; // 定义一个变量用来存储时间
vector<Rect> faces, faces2; // 定义两个向量来存储识别到的人脸矩形
// 定义一个静态Scalar数组,用于绘制时不同的颜色,Scalar代表一个颜色值
const static Scalar colors[] =
{
Scalar(255,0,0), // 红色
Scalar(255,128,0), // 橙色
Scalar(255,255,0), // 黄色
Scalar(0,255,0), // 绿色
Scalar(0,128,255), // 蓝绿色
Scalar(0,255,255), // 青色
Scalar(0,0,255), // 蓝色
Scalar(255,0,255) // 紫红色
};
Mat gray, smallImg; // 定义灰度图和缩小版图像的Mat对象
// 将输入图像转换成灰度图,这是面部检测的前提步骤
cvtColor( img, gray, COLOR_BGR2GRAY );
// 由scale计算图像缩放比例
double fx = 1 / scale;
// 缩放灰度图,降低图像尺寸以加快检测速度,但不能低于原图的scale倍
resize( gray, smallImg, Size(), fx, fx, INTER_LINEAR_EXACT );
// 直方图均衡化,提高图像的对比度,使其更容易识别
equalizeHist( smallImg, smallImg );
// 开始计时
t = (double)getTickCount();
// 使用级联分类器进行多尺度检测,存储检测到的脸部到faces向量中
cascade.detectMultiScale( smallImg, faces,
1.1, 2, 0
//|CASCADE_FIND_BIGGEST_OBJECT // 可选项,寻找最大的对象
//|CASCADE_DO_ROUGH_SEARCH // 可选项,进行初略搜索
|CASCADE_SCALE_IMAGE, // 表示缩放图片而不是缩放检测窗口
Size(30, 30) ); // 设置最小的对象检测尺寸
// 如果tryflip标志位为真,则进行水平翻转图像并再次检测,能够识别镜像中的脸部
if( tryflip )
{
flip(smallImg, smallImg, 1); // 对图像进行水平翻转
// 再次使用级联分类器检测翻转后的图像中的面部
cascade.detectMultiScale( smallImg, faces2,
1.1, 2, 0
//|CASCADE_FIND_BIGGEST_OBJECT
//|CASCADE_DO_ROUGH_SEARCH
|CASCADE_SCALE_IMAGE,
Size(30, 30) );
// 遍历翻转图像检测到的脸部
for( vector<Rect>::const_iterator r = faces2.begin(); r != faces2.end(); ++r )
{
// 计算该脸部在未翻转前图像中的位置,并添加到faces向量中
faces.push_back(Rect(smallImg.cols - r->x - r->width, r->y, r->width, r->height));
}
}
t = (double)getTickCount() - t; // 计算检测时间
printf( "detection time = %g ms\n", t*1000/getTickFrequency()); // 打印检测耗时
// 遍历检测到的脸部区域
// 循环遍历检测到的所有脸部
for ( size_t i = 0; i < faces.size(); i++ )
{
Rect r = faces[i]; // 从faces向量中获取当前脸部的矩形
Mat smallImgROI; // 声明一个Mat对象来保存感兴趣的区域,即脸部区域
vector<Rect> nestedObjects; // 用于存储嵌套对象(例如眼睛)的向量
Point center; // 声明一个点对象,用于存放绘制图形的中心点
Scalar color = colors[i%8]; // 获取一个颜色,用于绘制图形
int radius; // 声明一个半径变量,用于圆形的绘制
// 计算当前脸部矩形的宽高比
double aspect_ratio = (double)r.width/r.height;
// 如果宽高比在0.75到1.3之间则认为是正脸,绘制圆形
if( 0.75 < aspect_ratio && aspect_ratio < 1.3 )
{
center.x = cvRound((r.x + r.width*0.5)*scale); // 计算圆心X坐标
center.y = cvRound((r.y + r.height*0.5)*scale); // 计算圆心Y坐标
radius = cvRound((r.width + r.height)*0.25*scale); // 计算半径
circle( img, center, radius, color, 3, 8, 0 ); // 在img图像上绘制圆形
}
// 如果不是正脸,绘制矩形
else
rectangle( img, Point(cvRound(r.x*scale), cvRound(r.y*scale)),
Point(cvRound((r.x + r.width-1)*scale), cvRound((r.y + r.height-1)*scale)),
color, 3, 8, 0);
// 如果没有加载嵌套分类器,则继续下一个循环
if( nestedCascade.empty() )
continue;
// 按照脸部矩形设置smallImg的感兴趣区域为当前脸部
smallImgROI = smallImg( r );
// 在感兴趣区域内使用嵌套分类器检测嵌套对象(例如眼睛)
nestedCascade.detectMultiScale( smallImgROI, nestedObjects,
1.1, 2, 0
//|CASCADE_FIND_BIGGEST_OBJECT
//|CASCADE_DO_ROUGH_SEARCH
//|CASCADE_DO_CANNY_PRUNING
|CASCADE_SCALE_IMAGE,
Size(30, 30) );
// 遍历检测到的嵌套对象
for ( size_t j = 0; j < nestedObjects.size(); j++ )
{
Rect nr = nestedObjects[j]; // 获取当前嵌套对象的矩形
center.x = cvRound((r.x + nr.x + nr.width*0.5)*scale); // 计算圆心X坐标
center.y = cvRound((r.y + nr.y + nr.height*0.5)*scale); // 计算圆心Y坐标
radius = cvRound((nr.width + nr.height)*0.25*scale); // 计算半径
circle( img, center, radius, color, 3, 8, 0 ); // 在img图像上绘制圆形
}
}
// 显示带有检测结果的图像
imshow( "result", img );
}