1. 程式人生 > >基於VC++的人臉美化的實現實踐篇(含程式碼)

基於VC++的人臉美化的實現實踐篇(含程式碼)

作者:張皓霖 上海電力學院

課程老師:秦倫明

上篇我將人臉美化的過程列出來了,這篇我是用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,減少了濾波的工作量(因為有現成庫),剩下的難點在於融合演算法和美白演算法,尤其是美白演算法裡涉及到多次計算,因此選用了二維陣列來處理,確實在二維陣列的學習上吃了不少報錯的苦頭。

  • 心得體會

本以為使用了Opencv能把任務變得非常簡單,但是因為不熟悉,還是參考了大量的資料(包括CSDN部落格、各種論壇、各種影象處理程式碼),肝了整整一整天!

由於我沒有找到融合和美白的一些參考程式碼,決定自己根據PPT的效果和公式進行編寫程式,在這個過程中,Opencv對我來說又失去意義了,從底層還是選擇了用二維陣列來儲存影象資料來依次處理。

這個過程對我來說很艱難,在檢查報錯和無效果的過程中,我接受了一次又一次打擊,最終在一次一次失敗、一次一次查資料學習、一次一次嘗試中最終把成品完成了,我自我覺得這次作業完成的非常用心,也學習了非常多的知識,MFC的基本應用(傳遞值、函式、操作框等)、VC++影象處理的指標的應用、Opencv讀取顯示儲存等庫函式的實用,非常有意義的一次課程設計!

PS:大家如果自己寫的話,尤其是使用二維陣列多的時候,可能會出現記憶體訪問失敗這種很難受的報錯(別問我為什麼難受,新手面對這個花費了2個小時學習動態陣列定義、初始化、釋放和應用),所以大家可以參考我下面的資料幫助你們在過程中少一點錯誤,多一點希望!

  • 參考資料(以下是我在學習過程中參考的博主或網上的資料,謝謝各位的分享)

我剛開始是根據這個博主的MFC教程一步步學習VC6.0+MFC處理7個影象處理實驗來入門的,如果不入門,可能對影象處理的概念不清晰,導致使用陣列的時候也被搞得暈頭轉向!唯一可惜的就是用VC6.0做一些比較難的比較麻煩,所以可以拿來入門,建議程式設計使用VS2008以上版本。