【opencv】示例-facedetect.cpp使用OpenCV进行面部和眼睛检测,展示了使用级联分类器进行物体检测的基本流程...

博客围绕OpenCV在人工智能和计算机视觉领域展开。OpenCV是重要工具,在这两个信息技术领域有广泛应用,能推动相关技术发展,为解决复杂问题提供支持。

e4cf887c586da5ecfe62b4befd47391d.png

adb2831709159f55fe4ce87f2d5a8f4d.png

d62b4fcca03868cb4d80c8484615c1a3.png

// 包含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 );  
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值