一、眨眼检测方法 (Wink Detection)
目标:给定一组图片或者视频,检测眨眼和沉默表情
1.1 面部检测:
使用Haar级联分类器(haarcascade_frontalface_alt.xml)检测人脸,对图像进行灰度转换和直方图均衡化预处理,提高检测效果
1.2 眼睛区域定位:
在检测到的面部区域内,定义上半部分为眼睛区域
使用比例参数(eye_region_height_ratio)动态计算眼睛区域高度
1.3 眼睛检测:
在眼睛区域内使用Haar级联分类器(haarcascade_eye.xml)检测眼睛
添加了眼睛宽高比过滤,只保留合理的眼睛检测结果(0.5 < 宽高比 < 2.0)
1.4 眨眼判断逻辑:
- 如果在眼睛区域只检测到一只眼睛,则判定为眨眼
- 如果没有检测到面部但直接检测到一只眼睛,也判定为眨眼
- 连续上下帧之间闭上两只眼睛也是眨眼的一种常见表现
可视化反馈:
- 用紫色椭圆标记检测到的面部
- 用青色椭圆标记检测到的眼睛
- 当检测到眨眼时,用绿色椭圆标记面部
二、沉默表情检测方法 (Silence Detection)
2.1 面部检测:
同样使用Haar级联分类器检测人脸,进行相同的预处理步骤
2.2 嘴部区域定位:
在检测到的面部区域内,定义下半部分为嘴部区域,使用多个比例参数(mouth_region_width_ratio, mouth_region_height_ratio, mouth_region_y_offset_ratio)精确计算嘴部区域位置和大小
2.3 嘴部检测:
在嘴部区域内使用专用的嘴部分类器(mouth.xml)检测嘴部
调整了检测参数(scaleFactor=1.2, minNeighbors=3)优化嘴部检测
2.4 沉默判断逻辑:
如果在嘴部区域没有检测到任何嘴部,则判定为沉默表情
这与常见的"嘴唇紧闭"或"不张嘴"的沉默表情特征相符
2.5 可视化反馈:
- 用紫色椭圆标记检测到的面部
- 用青色椭圆标记检测到的嘴部
- 当检测到沉默表情时,用绿色椭圆标记面部
mouse检测器地址
#include "opencv2/highgui.hpp"
#include "opencv2/objdetect/objdetect.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>
#include <stdio.h>
#include <memory>
#include <string>
#include "dirent.h"
#include <map>
using namespace std;
using namespace cv;
// 配置参数结构体
struct AppConfig {
string opencv_root = "C:/Users/meetd/Downloads/opencv/";
string faces_cascade_name = "haarcascade_frontalface_alt.xml";
string eyes_cascade_name = "haarcascade_eye.xml";
string mouth_cascade_name = "mouth.xml";
// 检测参数
float face_scale_factor = 1.1f;
int face_min_neighbors = 3;
Size face_min_size = Size(30, 30);
float eye_scale_factor = 1.1f;
int eye_min_neighbors = 4;
Size eye_min_size = Size(20, 20);
float mouth_scale_factor = 1.2f;
int mouth_min_neighbors = 3;
Size mouth_min_size = Size(30, 30);
// 区域定义
float mouth_region_height_ratio = 0.3f;
float mouth_region_width_ratio = 0.6f;
float mouth_region_y_offset_ratio = 0.65f;
float eye_region_height_ratio = 0.5f;
};
// 资源管理类
class DirWrapper {
public:
DirWrapper(const string& path) : dir(opendir(path.c_str())) {
if (!dir) {
throw runtime_error("Can't open folder " + path);
}
}
~DirWrapper() {
if (dir) closedir(dir);
}
dirent* read() {
return readdir(dir); }
private:
DIR* dir;
};
// 检测模式枚举
enum class DetectionMode {
WINK,
SILENCE,
WINK2,//双眼闭
UNKNOWN
};
// 基础检测器类
class FaceFeatureDetector {
protected:
CascadeClassifier face_cascade;
CascadeClassifier feature_cascade;
AppConfig config;
public:
FaceFeatureDetector(const AppConfig& cfg) : config(cfg) {
}
virtual ~FaceFeatureDetector() = default;
bool init() {
string cascades_path = config.opencv_root + "build/etc/haarcascades/";
if (!face_cascade.load(cascades_path + config.faces_cascade_name)) {
cerr << "Error loading face cascade: " << config.faces_cascade_name << endl;
return false;
}
return true;
}
virtual int detect(Mat& frame) = 0;
int runOnFolder(const string& folder) {
try {
DirWrapper dir(folder);
string windowName;
int detections = 0;
while (auto entry = dir.read()) {
string dname = folder + "/" + entry->d_name;
Mat img = imread(dname, IMREAD_COLOR);
if (!img.empty()) {
int d = detect(img);
cerr << d << " detections in " << entry->d_name << endl;
detections += d;
if (!windowName.empty()) destroyWindow(windowName);
windowName = entry->d_name;
namedWindow(windowName, WINDOW_AUTOSIZE);
imshow(windowName, img);
if (waitKey(0) == 27) break; // ESC to exit
}
}
return detections;
}
catch (const exception& e) {
cerr << "Error processing folder: " << e.what() << endl;
return -1;
}
}
void runOnVideo() {
VideoCapture cap(0);
if (!cap.isOpened()) {
throw runtime_error("Can't open default video camera");
}
namedWindow("Live Detection", WINDOW_AUTOSIZE);
Mat frame;
while (cap.read(frame)) {
detect(frame);
imshow("Live Detection", frame);
if (waitKey(30) >= 0) break;
}
}
protected:
void drawEllipse(Mat& frame, const Rect& rect, const Scalar& color) {
Point center(rect.x + rect.width / 2, rect.y + rect.height / 2);
ellipse(frame, center, Size(rect.width / 2, rect.height / 2),
0, 0, 360, color, 2, LINE_8, 0);
}
};
// 眨眼检测器
class WinkDetector : public FaceFeatureDetector {
private:
std::map<