作者:张皓霖 上海电力学院
课程老师:秦伦明
上篇我将人脸美化的过程列出来了,这篇我是用VS2012(VC++)+MFC+OpenCv 将这些功能实现。
- 实验目的
利用VC++实现人脸美化软件,要求:
1、具有人脸美化界面;
2、具有磨皮功能,参数可调;
3、具有美白功能,参数可调;
- 实验内容
基于VS2012+OpenCv+MFC制作人脸美化软件
- 实验原理
磨皮:滤波(均值滤波、高斯滤波、双边滤波)
美白:使用阈值白平衡法
融合:使用高反差保留进行融合
- 实验步骤
1、创建MFC工程,搭建外壳(可视化界面)
2、编写图片打开、自动调整长宽显示和图片保存部分
3、编写磨皮部分,参数可调
4、编写融合部分,参数可调
5、编写美白部分,参数可调
- 关键代码
1、在头文件中XXXDlg.h添加
#include <iostream>
#include<opencv2/core/core.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<string>
using namespace std;
using namespace cv;
2、在OnInitDialog初始化函数中添加
namedWindow("view",WINDOW_AUTOSIZE);
HWND hWnd = (HWND)cvGetWindowHandle("view");
HWND hParent = ::GetParent(hWnd);
::SetParent(hWnd,GetDlgItem(IDC_PIC_STATIC)->m_hWnd);
::ShowWindow(hParent,SW_HIDE);
3、打开指定路径图片并自动调节尺寸显示
void CzhanghaolinDlg::OnBnClickedOpenButton()
{
// TODO: 在此添加控件通知处理程序代码
//CString picPath; //定义图片路径变量
CFileDialog dlg(TRUE, NULL, NULL, OFN_HIDEREADONLY |
OFN_OVERWRITEPROMPT|OFN_ALLOWMULTISELECT, NULL, this); //选择文件对话框
if(dlg.DoModal() == IDOK)
{
picPath= dlg.GetPathName(); //获取图片路径
}
//CString to string 使用这个方法记得字符集选用“使用多字节字符”,不然会报错
string picpath=picPath.GetBuffer(0);
srcImage=imread(picpath);
Mat imagedst;
//以下操作获取图形控件尺寸并以此改变图片尺寸
CRect rect;
GetDlgItem(IDC_PIC_STATIC)->GetClientRect(&rect);
Rect dst(rect.left,rect.top,rect.right,rect.bottom);
resize(srcImage,imagedst,cv::Size(rect.Width(),rect.Height()));
imshow("view",imagedst);
}
4、磨皮算法
void CzhanghaolinDlg::OnBnClickedOpenButton2()
{
/* Mat srcImage = imread("C:\\Users\\zhanghaolin\\Desktop\\我发誓这是最后一遍测试\\zhanghaolin\\ceshi.bmp");
if (!srcImage.data){
cout << "falied to read" << endl;
system("pause");
return;
}*/
Mat imagedst1;
Mat imagedst2;
srcImage.copyTo(src);
blur(src,imagedst1, Size(3, 3));//均值滤波
GaussianBlur(imagedst1, imagedst2, Size(3, 3), 0);//高斯滤波
UpdateData(TRUE); //mValue的值在此时更新
int d,sc,ss;
d=zhijing;
sc=sColor;
ss=sSPACE;
//C++: void bilateralFilter(InputArray src, OutputArray dst, int d, double sigmaColor, double sigmaSpace, int borderType=BORDER_DEFAULT )
bilateralFilter(imagedst2, dst1, d, sc, ss);//双边滤波
dst1.copyTo(dst1src);
dst1.copyTo(src);
CRect rect;
GetDlgItem(IDC_PIC_STATIC)->GetClientRect(&rect);
Rect dst(rect.left,rect.top,rect.right,rect.bottom);
resize(dst1,dst1,cv::Size(rect.Width(),rect.Height()));
imshow("view", dst1);
}
5、融合算法
void CzhanghaolinDlg::OnBnClickedOpenButton3()
{
// TODO: 在此添加控件通知处理程序代码
int width=srcImage.cols;
int heigh=srcImage.rows;
//srcImage.copyTo(src);
//dst1src是第一步做完的双边滤波后的图像
float tmp;
Mat dstH(src.size(),CV_8UC3);//RGB3通道就用CV_8UC3 高反差结果 H=F-I+128
for (int y=0;y<heigh;y++)
{
uchar* srcP=src.ptr<uchar>(y);
uchar* lvboP=dst1src.ptr<uchar>(y);
uchar* dstHP=dstH.ptr<uchar>(y);
for (int x=0;x<width;x++)
{
float r0 = abs((float)lvboP[3*x]-(float)srcP[3*x]);
tmp = abs( r0 + 128 );
tmp=tmp>255?255:tmp;
tmp=tmp<0?0:tmp;
dstHP[3*x]=(uchar)(tmp);
float r1 = abs((float)lvboP[3*x+1]-(float)srcP[3*x+1]);
tmp = abs( r1+ 128 );
tmp=tmp>255?255:tmp;
tmp=tmp<0?0:tmp;
dstHP[3*x+1]=(uchar)(tmp);
float r2 = abs((float)lvboP[3*x+2]-(float)srcP[3*x+2]);
tmp = abs( r2 + 128 );
tmp=tmp>255?255:tmp;
tmp=tmp<0?0:tmp;
dstHP[3*x+2]=(uchar)(tmp);
}
}
Mat dstY(dstH.size(),CV_8UC3);
UpdateData(TRUE); //mValue的值在此时更新
int ksize;
ksize=banjing;
GaussianBlur(dstH, dstY, Size(ksize,ksize),0,0,0); //高斯滤波得到Y
//void GaussianBlur(InputArray src, OutputArray dst, Size ksize, double sigmaX, double sigmaY=0, int borderType=BORDER_DEFAULT);
srcImage.copyTo(src);
Mat dstZ(src.size(),CV_8UC3);//Z = X * Op + (X + 2 * Y - 256)* Op= X + (2*Y-256) *Op OP不透明度 X原图 Y是高斯滤波后图像
float OP;//不透明度
OP=(float)(OPvalue*0.01);
for (int y=0;y<heigh;y++) //图层混合
{
uchar* XP=src.ptr<uchar>(y);
uchar* dstYP=dstY.ptr<uchar>(y);
uchar* dstZP=dstZ.ptr<uchar>(y);
for (int x=0;x<width;x++)
{
float r3 = ((float)dstYP[3*x]+(float)dstYP[3*x]-256)*OP;
tmp = r3+(float)XP[3*x];
tmp=tmp>255?255:tmp;
tmp=tmp<0?0:tmp;
dstZP[3*x]=(uchar)(tmp);
float r4 = ((float)dstYP[3*x+1]+(float)dstYP[3*x+1]-256)*OP;
tmp = r4+(float)XP[3*x+1];
tmp=tmp>255?255:tmp;
tmp=tmp<0?0:tmp;
dstZP[3*x+1]=(uchar)(tmp);
float r5 = ((float)dstYP[3*x+2]+(float)dstYP[3*x+2]-256)*OP;
tmp = r5+(float)XP[3*x+2];
tmp=tmp>255?255:tmp;
tmp=tmp<0?0:tmp;
dstZP[3*x+2]=(uchar)(tmp);
}
}
dstZ.copyTo(dst2);
dstZ.copyTo(src);
CRect rect;
GetDlgItem(IDC_PIC_STATIC)->GetClientRect(&rect);
Rect dst(rect.left,rect.top,rect.right,rect.bottom);
resize(dst2,dst2,cv::Size(rect.Width(),rect.Height()));
imshow("view", dst2);
}
6、美白算法
void CzhanghaolinDlg::OnBnClickedOpenButton4()
{
// TODO: 在此添加控件通知处理程序代码
int width=srcImage.cols;
int heigh=srcImage.rows;
src.copyTo(dst3);
//Mat dst3(src.size(),CV_8UC3); //融合后的RGB三通道
/*vector<Mat> imageRGB;
Mat Y,Cr,Cb;
Mat RL;
//RGB三通道分离
split(src, imageRGB);*/
/*转换颜色空间并分割颜色通道*/
/*cvtColor(src, src, CV_BGR2YCrCb);
split(src,imageRGB);
Y = imageRGB.at(0);
Cr = imageRGB.at(1);
Cb = imageRGB.at(2);
RL.create(heigh, width, CV_8UC3);*/
//float Y[1024][1024];
//float Cr[1024][1024];
//float Cb[1024][1024];
//float RL[1024][1024];
Y = (float **)malloc(heigh * sizeof(float*));
Cr = (float **)malloc(heigh * sizeof(float*));
Cb = (float **)malloc(heigh * sizeof(float*));
float Mr,Mb,number;
float sumR=0;
float sumB=0;
for (int y=0;y<heigh;y++)
{
Y[y] = (float *)malloc(width * sizeof(float));
Cr[y] = (float *)malloc(width * sizeof(float));
Cb[y] = (float *)malloc(width * sizeof(float));
}
//把图像从RGB转换到YCrCb空间 分别计算Cr,Cb的平均值Mr,Mb******************
for (int y=0;y<heigh;y++)
{
uchar* dst3P=dst3.ptr<uchar>(y);
for (int x=0;x<width;x++)
{
Y[y][x] = (0.299f * (float)dst3P[3*x]) + (0.587f * (float)dst3P[3*x+1]) + (0.114f * (float)dst3P[3*x+2])+0.0f;
Cr[y][x] =(0.500f * (float)dst3P[3*x])- (0.419f * (float)dst3P[3*x+1]) -( 0.081f * (float)dst3P[3*x+2])+128.0f;
Cb[y][x] = ((-0.169f) * (float)dst3P[3*x])+( (- 0.331f) * (float)dst3P[3*x+1])+ (0.500f * (float)dst3P[3*x+2])+128.0f;
sumR=sumR+Cr[y][x];
sumB=sumB+Cb[y][x];
}
}
number=(float)heigh*(float)width;
Mr=sumR/number;
Mb=sumB/number;
//分别计算Cr,Cb的方差Dr,Db******************
float Dr=0;
float Db=0;
for (int y=0;y<heigh;y++) //求方差
{ uchar* dst3P=dst3.ptr<uchar>(y);
for (int x=0;x<width;x++)
{
//Cr[y][x] =(0.500f * (float)dst3P[3*x])- (0.419f * (float)dst3P[3*x+1]) -( 0.081f * (float)dst3P[3*x+2]);
// Cb[y][x] = ((-0.169f) * (float)dst3P[3*x])+( (- 0.331f) * (float)dst3P[3*x+1])+ (0.500f * (float)dst3P[3*x+2]);
Dr=Dr+((Cr[y][x]-Mr)*(Cr[y][x]-Mr));
Db=Dr+((Cb[y][x]-Mb)*(Cb[y][x]-Mb));
}
}
Dr=Dr/number;
Db=Db/number;
//判别参考点******************
float sumR2=0; float sumG2=0; float sumB2=0;
R2 = (float **)malloc(heigh * sizeof(float*));
G2 = (float **)malloc(heigh * sizeof(float*));
B2 = (float **)malloc(heigh * sizeof(float*));
RL = (float **)malloc(heigh * sizeof(float*));
for (int y=0;y<heigh;y++)
{
R2[y] = (float *)malloc(width * sizeof(float));
G2[y] = (float *)malloc(width * sizeof(float));
B2[y] = (float *)malloc(width * sizeof(float));
RL[y] = (float *)malloc(width * sizeof(float));
}
float panbie1;
float panbie2;
float panbie3;
float panbie4;
for (int y=0;y<heigh;y++)
{
for (int x=0;x<width;x++)
{
panbie1=Cr[y][x]-(Mb+Db);
panbie2=1.50f * Db;
panbie3=Cr[y][x]-(1.50f *Mr+Dr);
panbie4=1.50f * Dr;
if(panbie1<panbie2 && panbie3<panbie4)
{
RL[y][x]=Y[y][x];
}
else {RL[y][x]=0.0f;}
}
}
//提取白色点前10%的最小值LUmin*****************
float* YY = (float *)malloc((int)number* sizeof(float));
int q=0;
for(int y=0;y<heigh;y++)
{
for(int x=0;x<width;x++)
{
YY[q]=RL[y][x];
q++;
}
}
float t;int j;int i;
for(i=1;i<((int)number);i++)
{
t=YY[i];
j=i-1;
while(YY[j]>t && j>=0)
{
YY[j+1]=YY[j];
j--;
}
if(j!=(i-1))
YY[j+1]=t;
}
/*
float t=0;
for(int i=0;i<((int)number)-1;i++)
{
for(int j=i+1;j<(int)number;j++)
{
if(YY[i]<YY[j]) //由大到小
{
t=YY[i];
YY[i]=YY[j];
YY[j]=t;
}
}
}*/
float LUmin=0;
int percent10;
percent10=(int)(number*0.9);//前10%中的最小值
LUmin=YY[percent10];
//调整RL************************
for (int y=0;y<heigh;y++)
{
for (int x=0;x<width;x++)
{
if(RL[y][x]<LUmin)
{
RL[y][x]=0.0f;
}
else {RL[y][x]=1.0f;}
}
}
//分别把 R,G,B与RL点乘,得到 R2,G2,B2.分别计算其均值得到 Rav,Gav,Bav*****************
for (int y=0;y<heigh;y++) //判别参考点
{ uchar* dst3P=dst3.ptr<uchar>(y);
for (int x=0;x<width;x++)
{
R2[y][x]=((float)dst3P[3*x])*(float)RL[y][x];
G2[y][x]=((float)dst3P[3*x+1])*(float)RL[y][x];
B2[y][x]=((float)dst3P[3*x+2])*(float)RL[y][x];
sumR2=sumR2+R2[y][x];
sumG2=sumG2+G2[y][x];
sumB2=sumB2+B2[y][x];
}
}
float Rav,Gav,Bav;
Rav=sumR2/number;
Gav=sumG2/number;
Bav=sumB2/number;
//计算出图片中亮度最大值,其中Y为亮度值矩阵
free(Cr);
free(Cb);
free(R2);
free(G2);
free(B2);
free(RL);
//float Y2[1024][1024];
//计算出图片中亮度最大值
Y2 = (float **)malloc(heigh * sizeof(float*));
for (int y=0;y<heigh;y++)
{
Y2[y] = (float *)malloc(width * sizeof(float));
}
float Ymax=0;
for (int y=0;y<heigh;y++) // 找Y的最大值
{ uchar* dst3P=dst3.ptr<uchar>(y);
for (int x=0;x<width;x++)
{ //Y[y][x] =( 0.299f * (float)dst3P[3*x]) + (0.587f * (float)dst3P[3*x+1] )+ (0.114f * (float)dst3P[3*x+2]);
Y2[y][x]=Y[y][x];
if(Y2[y][x]>Ymax)
{Ymax=Y2[y][x];}
}
}
float Yzengyi,zengyi;
zengyi=(float)liangdu*0.01f;
zengyi=1.00f-zengyi;
Yzengyi=10.0f * zengyi;
Ymax=Ymax/Yzengyi;
free(Y2);
//得到调整增益
free(Y);
float Rgain,Ggain,Bgain;
Rgain=Ymax/Rav;
Ggain=Ymax/Gav;
Bgain=Ymax/Bav;
//调整原图像
//float Rgain,Ggain,Bgain;
//Rgain=2.0f;
//Ggain=2.0f;
//Bgain=2.0f;
float tmp=0;
Mat dst4(src.size(),CV_8UC3);
for(int y=0;y<heigh;y++)
{
uchar* dst4P=dst3.ptr<uchar>(y);
for(int x=0;x<width;x++)
{
float r6 = (float)dst4P[3*x]*Rgain;
tmp = r6;
tmp=tmp>255?255:tmp;
tmp=tmp<0?0:tmp;
dst4P[3*x]=(uchar)(tmp);
float r7 = (float)dst4P[3*x+1]*Ggain;
tmp = r7;
tmp=tmp>255?255:tmp;
tmp=tmp<0?0:tmp;
dst4P[3*x+1]=(uchar)(tmp);
float r8 = (float)dst4P[3*x+2]*Bgain;
tmp = r8;
tmp=tmp>255?255:tmp;
tmp=tmp<0?0:tmp;
dst4P[3*x+2]=(uchar)(tmp);
}
}
dst3.copyTo(dst4);
dst4.copyTo(dstmeibai);
dst4.copyTo(src);
CRect rect;
GetDlgItem(IDC_PIC_STATIC)->GetClientRect(&rect);
Rect dst(rect.left,rect.top,rect.right,rect.bottom);
resize(dstmeibai,dstmeibai,cv::Size(rect.Width(),rect.Height()));
imshow("view", dstmeibai);
}
- 实验难点
总结起来,因为使用了Opencv2.4.10,减少了滤波的工作量(因为有现成库),剩下的难点在于融合算法和美白算法,尤其是美白算法里涉及到多次计算,因此选用了二维数组来处理,确实在二维数组的学习上吃了不少报错的苦头。
程序下载链接:https://download.csdn.net/download/qq_37286676/10917266
- 心得体会
本以为使用了Opencv能把任务变得非常简单,但是因为不熟悉,还是参考了大量的资料(包括CSDN博客、各种论坛、各种图像处理代码),肝了整整一整天!
由于我没有找到融合和美白的一些参考代码,决定自己根据PPT的效果和公式进行编写程序,在这个过程中,Opencv对我来说又失去意义了,从底层还是选择了用二维数组来保存图像数据来依次处理。
这个过程对我来说很艰难,在检查报错和无效果的过程中,我接受了一次又一次打击,最终在一次一次失败、一次一次查资料学习、一次一次尝试中最终把成品完成了,我自我觉得这次作业完成的非常用心,也学习了非常多的知识,MFC的基本应用(传递值、函数、操作框等)、VC++图像处理的指针的应用、Opencv读取显示保存等库函数的实用,非常有意义的一次课程设计!
PS:大家如果自己写的话,尤其是使用二维数组多的时候,可能会出现内存访问失败这种很难受的报错(别问我为什么难受,新手面对这个花费了2个小时学习动态数组定义、初始化、释放和应用),所以大家可以参考我下面的资料帮助你们在过程中少一点错误,多一点希望!
- 参考资料(以下是我在学习过程中参考的博主或网上的资料,谢谢各位的分享)
https://blog.csdn.net/xingchenbingbuyu/article/details/51348394(OpenCV实践之路——opencv与MFC强强联合打开图片)
https://blog.csdn.net/lz0499/article/details/78234631(一种动态阈值白平衡算法实现)
http://blog.sina.com.cn/s/blog_61e10f020101htdk.html(rgb转YCrCb)
https://blog.csdn.net/wzmsltw/article/details/50658744(opencv学习笔记(一):基于YCrCb颜色空间的肤色检测)
https://blog.csdn.net/dieju8330/article/details/82744895(Opencv2.4学习::滤波(4)双边滤波)
https://www.cnblogs.com/dzw2017/p/6632855.html(opencv2.4.10+VS2012配置)
http://www.45fan.com/dnjc/83329.html(如何处理VS2013中error C4996:'fopen'问题?)
https://blog.csdn.net/xiajun07061225/article/details/6633938(【数字图像处理】C++读取、旋转和保存bmp图像文件编程实现)
https://blog.csdn.net/sxlsxl119/article/details/51258998(OpenCV3.0+MFC+VS2010打开图像)
https://blog.csdn.net/u011361880/article/details/77505380(C++动态创建二维数组,memset()初始化二维数组)
https://blog.csdn.net/lc331257229/article/details/45129713(二维数组的创建、初始化、释放)
最后给大家推荐一个博主的博客 : https://blog.csdn.net/Eastmount/column/info/eastmount-mfc
我刚开始是根据这个博主的MFC教程一步步学习VC6.0+MFC处理7个图像处理实验来入门的,如果不入门,可能对图像处理的概念不清晰,导致使用数组的时候也被搞得晕头转向!唯一可惜的就是用VC6.0做一些比较难的比较麻烦,所以可以拿来入门,建议编程使用VS2008以上版本。