_______ __________
/ .__. \ ____ ____ ___.__.\______ \
| | | |/ _ \ / _ \ || | | | _/
| |__| ( <_> ) <_> )___ | | | \
\______/\____/ \____// ____| |______ /
\/ \/
This project is based on Visual Studio 2022 IDE
Using Opencv 4.7.0 ; Qt 5.14.2 ; Qt VS Tool 2.10.1 ; Python 3.10
- 直方图均衡化 : $ s_k = \displaystyle \sum_{j=0}^{k}\frac{n_j}{n}$ 累计频率 哪些灰度数量多,映射曲线越陡峭,均衡后越均匀
- 拉普拉斯锐化 : $ g(x,y) = f(x,y)+[8\cdot f(x,y)-\displaystyle \sum_{D8}f(i,j)] $ 当邻域中心像素灰度低于它所在的领域内其它像素的平均灰度时,此中心像素的灰度应被进一步降低,当邻域中心像素灰度高于它所在的邻域内其它像素的平均灰度时,此中心像素的灰度应被进一步提高
- 频域滤波 : DFT: $ F(u,v) = \frac{1}{MN}\displaystyle \sum_{x=0}^{M-1} \sum_{y=0}^{N-1}f(x,y)\exp{-j2\pi (\frac{ux}{M}+\frac{vy}{N}) }$
Python部分
- 人脸特征点检测 :
- 调用Dlib的HOG人脸检测(将图像分割成一些 16×16 像素的小方块,计算指向性最强的梯度方向)
- 用训练好的81个人脸关键点位置模型去寻找人脸中的81个特征点
UI部分
1、用Qt Designer设计好初步界面
2、连接Button、SpinBox等信号与相应的槽函数,信号产生调用对应槽函数,在槽函数中进行相关功能函数的调用
Codes of Digital-Image-Processing homework
-
直方图均衡化
代码展开
// 直方图均衡化的定义 void HistEqual(Mat& gray, Mat& result) { // 哈希表统计0~255像素值的个数 mappixelCounter; for (int i = 0; i < gray.rows; i++) { for (int j = 0; j < gray.cols; j++) { int value = gray.at(i, j); pixelCounter[value]++; } } //统计0~255像素值的频率,并计算累计频率 map pixel_fre; int pixel_sum = gray.cols * gray.rows; double cumul_fre = 0; for (int i = 0; i < 256; i++) { // 累计频率 哪些灰度数量多,映射曲线越陡峭,均衡后越均匀 cumul_fre += double(pixelCounter[i]) / pixel_sum; pixel_fre[i] = cumul_fre; } //根据累计频率进行转换 for (int i = 0; i < gray.rows; i++) { for (int j = 0; j < gray.cols; j++) { int value = gray.at(i, j); double fre = pixel_fre[value]; // 原始灰度值乘以累计频率 result.at(i, j) = fre * value; } } }
-
拉普拉斯锐化
代码展开
// 默认0填充 void Laplacian(Mat& gray, Mat& result, int padding) { //result.convertTo(result, CV_64F); Mat gray_buf(gray.rows + 2, gray.cols + 2, gray.depth()); // 0填充 if (padding == 0) { cv::copyMakeBorder(gray, gray_buf, 1, 1, 1, 1, cv::BORDER_CONSTANT); } // 镜像填充 else if (padding == 1) { cv::copyMakeBorder(gray, gray_buf, 1, 1, 1, 1, cv::BORDER_REFLECT); } for (int i = 0; i < gray.rows; i++) { for (int j = 0; j < gray.cols; j++) { // cv::saturate_cast()保证范围为0~255 // 直接访问 result.at(i, j) = cv::saturate_cast(gray.at(i, j) + 8 * gray_buf.at(i + 1, j + 1) - gray_buf.at(i, j) - gray_buf.at(i, j + 1) - gray_buf.at(i, j + 2) \ - gray_buf.at(i + 1, j) - gray_buf.at(i + 1, j + 2) - gray_buf.at(i + 2, j) - gray_buf.at(i + 2, j + 1) - gray_buf.at(i + 2, j + 2)); } } }
-
理想低通滤波器
代码展开
// 理想低通滤波器 void ILPF(Mat& gray, Mat& result, int fc) { // 扩展图像矩阵,为2,3,5的倍数时运算速度快 int m = cv:: getOptimalDFTSize(gray.rows); int n = cv::getOptimalDFTSize(gray.cols); Mat padded; // 零填充 cv::copyMakeBorder(gray, padded, 0, m - gray.rows, 0, n - gray.cols, cv::BORDER_CONSTANT); padded.convertTo(padded, CV_32FC1); int row = padded.rows; int col = padded.cols; if (fc > MIN(row, col)) throw "截止频率超出图像范围"; Mat filter = Mat::zeros(padded.size(), CV_32FC1); for (int i = 0; i < row; i++) { for (int j = 0; j < col; j++) { double d = sqrt(pow((i - row / 2.0), 2) + pow((j - col / 2.0), 2)); if (d <= fc) { filter.at(i, j) = 1; } } } // imshow("滤波器", filter); // 实部和虚部 Mat plane[] = {padded, Mat::zeros(padded.size(), CV_32FC1)}; Mat complexIm; merge(plane, 2, complexIm); //合并通道 (把两个矩阵合并为一个2通道的Mat类容器) dft(complexIm, complexIm); //进行傅立叶变换,结果保存在自身 split(complexIm, plane); //分离通道 fftshift(plane[0], plane[1]); Mat Real, Imag, BLUR; Real = plane[0].mul(filter); Imag = plane[1].mul(filter); // fftshift(Real, Imag); //效果一样 周期性 Mat plane1[] = { Real, Imag }; merge(plane1, 2, BLUR);//实部与虚部合并 idft(BLUR, BLUR); split(BLUR, plane); //分离通道,主要获取通道 magnitude(plane[0], plane[1], result); //求幅值(模) normalize(result, result, 0, 1.0, NORM_MINMAX); //归一化便于显示 } // 低频移动到中心 void fftshift(Mat plane0, Mat plane1) { // -2 : 1111_……_1110 plane0 = plane0(Rect(0, 0, plane0.cols & -2, plane0.rows & -2)); int cx = plane0.cols / 2; int cy = plane0.rows / 2; Mat part1_r(plane0, Rect(0, 0, cx, cy)); Mat part2_r(plane0, Rect(cx, 0, cx, cy)); Mat part3_r(plane0, Rect(0, cy, cx, cy)); Mat part4_r(plane0, Rect(cx, cy, cx, cy)); Mat temp; part1_r.copyTo(temp); //左上与右下交换位置(实部) part4_r.copyTo(part1_r); temp.copyTo(part4_r); part2_r.copyTo(temp); //右上与左下交换位置(实部) part3_r.copyTo(part2_r); temp.copyTo(part3_r); Mat part1_i(plane1, Rect(0, 0, cx, cy)); //元素坐标(cx,cy) Mat part2_i(plane1, Rect(cx, 0, cx, cy)); Mat part3_i(plane1, Rect(0, cy, cx, cy)); Mat part4_i(plane1, Rect(cx, cy, cx, cy)); part1_i.copyTo(temp); //左上与右下交换位置(虚部) part4_i.copyTo(part1_i); temp.copyTo(part4_i); part2_i.copyTo(temp); //右上与左下交换位置(虚部) part3_i.copyTo(part2_i); temp.copyTo(part3_i); }
-
巴特沃斯低通滤波器
代码展开
// 巴特沃斯的滤波器 Mat filter = Mat::zeros(padded.size(), CV_32FC1); for (int i = 0; i < row; i++) { //float* data = filter.ptr(i); for (int j = 0; j < col; j++) { float d = sqrt(pow((i - row / 2.0), 2) + pow((j - col / 2.0), 2)); filter.at(i, j) = 1 / (1 + pow(float(d / fc), 2 * level)); } }
-
高斯低通滤波器
代码展开
// 高斯滤波器 Mat filter = Mat::zeros(padded.size(), CV_32FC1); for (int i = 0; i < row; i++) { for (int j = 0; j < col; j++) { float d = sqrt(pow((i - row / 2.0), 2) + pow((j - col / 2.0), 2)); filter.at(i, j) = exp(-pow(d, 2) / (2 * pow(fc, 2))); } }
-
人脸检测(Python)
代码展开
# HOG人脸检测器寻找人脸 def find_face(filename): face_detector = dlib.get_frontal_face_detector() # 创建HOG人脸检测器 image = cv2.imread(filename) detected_faces = face_detector(image, 1) # 存储人脸矩形框的坐标信息 location = [] for i, face_rect in enumerate(detected_faces): location.append(face_rect.left()) location.append(face_rect.top()) location.append(face_rect.right()) location.append(face_rect.bottom()) return location
-
人脸81个特征点位置(Python)
代码展开
def find_face_landmarks(filename): # 人脸81个关键点模型位置 predictor_model = r"E:\Face_Landmarks\shape_predictor_81_face_landmarks.dat" face_detector = dlib.get_frontal_face_detector() # 创建HOG人脸检测器 face_pose_predictor = dlib.shape_predictor(predictor_model) # 创建人脸特征点检测器 image = cv2.imread(filename) detected_faces = face_detector(image, 1) mark = [] for i, face_rect in enumerate(detected_faces): pose_landmarks = face_pose_predictor(image, face_rect) # 获取面部的姿势 for j in range(81): # 以tuple形式存放到mark列表中,方便C++调用 mark.append((pose_landmarks.part(j).x, pose_landmarks.part(j).y)) return mark
-
UI部分
代码展开
// 接收并响应拖拽事件 // 过滤非图片文件 void DIP_GUI::dragEnterEvent(QDragEnterEvent* event) { QStringList FileTypes; FileTypes.append("jpg"); FileTypes.append("png"); FileTypes.append("bmp"); if (event->mimeData()->hasUrls() && event->mimeData()->urls().count() == 1) { // 对象是否可以返回URL列表,并且只有一个 QFileInfo file(event->mimeData()->urls().at(0).toLocalFile()); // 在FileTypes查找文件后缀是否符合 if (FileTypes.contains(file.suffix().toLower())) event->acceptProposedAction(); } } // 响应拖拽事件 void DIP_GUI::dropEvent(QDropEvent* event) { // 接收文件 QString url = event->mimeData()->urls().first().toLocalFile(); if (url.isEmpty()) return; // 具体将拿到的数据进行处理 QImage img; img.load(url); // 设置图片适应QLabel大小 img.scaled(ui.originImg->size(), Qt::KeepAspectRatio); ui.originImg->setScaledContents(true); ui.originImg->setPixmap(QPixmap::fromImage(img)); // 保存图片路径 filepath = url.toStdString(); } void DIP_GUI::setupView() { this->setAcceptDrops(true); //可以接收图片 } // 连接信号与槽函数 connect(ui.histbtn, &QPushButton::clicked, this, &DIP_GUI::hist); connect(ui.laplacebtn, &QPushButton::clicked, this, &DIP_GUI::laplace); connect(ui.ilpfbtn, &QPushButton::clicked, this, &DIP_GUI::ideal); connect(ui.blpfbtn, &QPushButton::clicked, this, &DIP_GUI::butter); connect(ui.glpfbtn, &QPushButton::clicked, this, &DIP_GUI::gauss); connect(ui.face, &QPushButton::clicked, this, &DIP_GUI::fac); // Lamda表达式 connect(ui.fc_i, &QSpinBox::editingFinished, this, [&]() {fc = ui.fc_i->value(); }); connect(ui.fc_b, &QSpinBox::editingFinished, this, [&]() {fc = ui.fc_b->value(); }); connect(ui.fc_g, &QSpinBox::editingFinished, this, [&]() {fc = ui.fc_g->value(); });
- 人脸部分具体处理
- 人脸眼部、鼻部、唇部、发际线进行可选性处理
- 对视频中的人脸进行滤波
_ooOoo_
o8888888o
88" . "88
(| -_- |)
O\ = /O
____/`---'\____
. ' \\| |// `.
/ \\||| : |||// \
/ _||||| -:- |||||- \
| | \\\ - /// | |
| \_| ''\---/'' | |
\ .-\__ `-` ___/-. /
___`. .' /--.--\ `. . __
."" '< `.___\_<|>_/___.' >'"".
| | : `- \`.;`\ _ /`;.`/ - ` : | |
\ \ `-. \_ __\ /__ _/ .-` / /
======`-.____`-.___\_____/___.-`____.-'======
`=---='
.............................................
佛祖镇楼 BUG辟易
佛曰:
V我50
v我50
V我50