Opencv之人臉膚色檢測總結
1.RGB顏色空間膚色檢測
opencv程式碼非常簡單:
效果圖:void SkinRGB(IplImage* src,IplImage* dst) { //RGB顏色空間 //均勻照明:R>95,G>40,B>20,R-B>15,R-G>15 //側向照明:R>200,G>210,B>170,R-B<=15,R>B,G>B int height=src->height,width=src->width,channel=src->nChannels,step=src->widthStep; int b=0,g=1,r=2; cvZero(dst); unsigned char* p_src=(unsigned char*)src->imageData; unsigned char* p_dst=(unsigned char*)dst->imageData; for(int j=0;j<height;j++) { for(int i=0;i<width;i++) { if((p_src[j*step+i*channel+r]>95&&p_src[j*step+i*channel+g]>40&&p_src[j*step+i*channel+b]>20&& (p_src[j*step+i*channel+r]-p_src[j*step+i*channel+b])>15&&(p_src[j*step+i*channel+r]-p_src[j*step+i*channel+g])>15)|| (p_src[j*step+i*channel+r]>200&&p_src[j*step+i*channel+g]>210&&p_src[j*step+i*channel+b]>170&& (p_src[j*step+i*channel+r]-p_src[j*step+i*channel+b])<=15&&p_src[j*step+i*channel+r]>p_src[j*step+i*channel+b]&& p_src[j*step+i*channel+g]>p_src[j*step+i*channel+b])) p_dst[j*width+i]=255; } } }
2.二次多項式模型
其中
程式碼也比較簡單:
void cvSkinRG(IplImage* src,IplImage* dst) { //二項式混合模型 //來源:Adaptive skin color modeling using the skin locus int b=0,g=1,r=2; double Aup=-1.8423,Bup=1.5294,Cup=0.0422,Adown=-0.7279,Bdown=0.6066,Cdown=0.1766; double r_ratio=0.0,g_ratio=0.0,sum=0.0,Rup=0.0,Rdown=0.0,wr=0.0; int height=src->height,width=src->width,channel=src->nChannels,step=src->widthStep; if(dst==NULL) dst=cvCreateImage(cvGetSize(src),IPL_DEPTH_8U,1); cvZero(dst); unsigned char* p_src=(unsigned char*)src->imageData; unsigned char* p_dst=(unsigned char*)dst->imageData; for(int j=0;j<height;j++) { for(int i=0;i<width;i++) { sum=p_src[j*step+i*channel+r]+p_src[j*step+i*channel+g]+p_src[j*step+i*channel+b]; r_ratio=p_src[j*step+i*channel+r]/sum; g_ratio=p_src[j*step+i*channel+g]/sum; Rup=Aup*r_ratio*r_ratio+Bup*r_ratio+Cup; Rdown=Adown*r_ratio*r_ratio+Bdown*r_ratio+Cdown; wr=(r_ratio-0.33)*(r_ratio-0.33)+(g_ratio-0.33)*(g_ratio-0.33); if(g_ratio<Rup && g_ratio>Rdown && wr>0.004) p_dst[j*width+i]=255; } } }
效果圖:
3.反向投影法膚色檢測
這個方法來源《學習opencv》裡面的反向投影,原理如下:
使用 模型直方圖 (代表手掌的面板色調) 來檢測測試影象中的面板區域。以下是檢測的步驟
-
對測試影象中的每個畫素 ( ),獲取色調資料並找到該色調( )在直方圖中的bin的位置。
-
查詢 模型直方圖 中對應的bin - - 並讀取該bin的數值。
-
將此數值儲存在新的影象中(BackProjection)。 你也可以先歸一化 模型直方圖 ,這樣測試影象的輸出就可以在螢幕顯示了。
-
通過對測試影象中的每個畫素採用以上步驟.
int main() { /**********************************\ * back Project * \**********************************/ //一維直方圖 //獲取直方圖模型 IplImage* src=cvLoadImage("base.jpg",1);//下載base影象,base影象為目標顏色,即包含人臉顏色的影象 IplImage* hsv_src=cvCreateImage(cvGetSize(src),8,3);//建立一幅與src同樣大小3通道的hsv影象 cvCvtColor(src,hsv_src,CV_RGB2HSV);//將src從rgb顏色空間轉換到hsv顏色空間 IplImage* h_plane=cvCreateImage(cvGetSize(src),8,1);//建立一幅與src同樣大小單通道的h影象 cvSplit(hsv_src,h_plane,0,0,0);//取hsv影象中h分量 // 計算base的h分量直方圖 //------------------------------------------------------------- int dims=1; int size[]={180}; float h_range[]={0,180}; float* ranges[]={h_range}; IplImage* hsv_test=NULL; IplImage* h_test_plane=NULL; IplImage* back_proj=NULL; IplImage* img_dst=NULL; CvHistogram* hist=cvCreateHist(dims,size,CV_HIST_ARRAY,ranges,1); cvCalcHist(&h_plane,hist,0,0);
//---------------------------------------------------------------
IplImage* img_src=NULL;
IplImage* test=NULL;
CvCapture* capture=cvCreateCameraCapture(0);
cvNamedWindow("Input Video",1);
cvNamedWindow("Skin Detect",1);
while(1)
{
img_src=cvQueryFrame(capture);
if(!img_src) break;
cvShowImage("Input Video",img_src);
if(test==NULL)
{
test=cvCreateImage(cvGetSize(img_src),img_src->depth,img_src->nChannels);
}
cvZero(test);
cvCopy(img_src,test);
if(hsv_test==NULL)
hsv_test=cvCreateImage(cvGetSize(test),8,3);
cvZero(hsv_test);
cvCvtColor(test,hsv_test,CV_RGB2HSV);//轉換到hsv空間
if(h_test_plane==NULL)
h_test_plane=cvCreateImage(cvGetSize(test),8,1);
cvZero(h_test_plane);
cvSplit(hsv_test,h_test_plane,0,0,0);//取h分量
if(back_proj==NULL)
back_proj=cvCreateImage(cvGetSize(test),8,1);//用於存取膚色檢測結果
cvZero(back_proj);
cvCalcBackProject(&h_test_plane,back_proj,hist);//反向投影計算投影
cvThreshold(back_proj,back_proj,250,255,CV_THRESH_BINARY);
cvShowImage("Skin Detect",<span style="font-family: sans-serif;">back_proj</span>);
char c=cvWaitKey(33);
if(c==27) break;
}
cvWaitKey(0);
}
效果圖:
4.YCrCb顏色空間Cr分量+Ostu法
這個方法暫時沒有找到論文來源,參考別人的部落格,原理很簡單:
a.將RGB影象轉換到YCrCb顏色空間,提取Cr分量影象
b.對Cr做自適應二值化處理(Ostu法)
程式碼:
void cvSkinOtsu(IplImage* src, IplImage* dst)
{
//Cr自適應閾值法
//
IplImage* img_ycrcb=cvCreateImage(cvGetSize(src),IPL_DEPTH_8U,3);
IplImage* img_cr=cvCreateImage(cvGetSize(src),IPL_DEPTH_8U,1);
cvCvtColor(src,img_ycrcb,CV_BGR2YCrCb);
cvSplit(img_ycrcb,0,img_cr,0,0);
cvThresholdOtsu(img_cr,img_cr);
cvCopy(img_cr,dst);
cvReleaseImage(&img_ycrcb);
cvReleaseImage(&img_cr);
}
Ostu法:
void cvThresholdOtsu(IplImage* src, IplImage* dst)
{
int height=src->height,width=src->width,threshold=0;
double histogram[256]={0};
double average=0.0,max_variance=0.0,w=0.0,u=0.0;
IplImage* temp=cvCreateImage(cvGetSize(src),src->depth,1);
if(src->nChannels!=1)
cvCvtColor(src,temp,CV_BGR2GRAY);
else
cvCopy(src,temp);
unsigned char* p_temp=(unsigned char*)temp->imageData;
//計算灰度直方圖
//
for(int j=0;j<height;j++)
{
for(int i=0;i<width;i++)
{
histogram[p_temp[j*width+i]]++;
}
}
for(int i=0;i<256;i++)
histogram[i]=histogram[i]/(double)(height*width);
//計算平局值
for(int i=0;i<256;i++)
average+=i*histogram[i];
for(int i=0;i<256;i++)
{
w+=histogram[i];
u+=i*histogram[i];
double t=average*w-u;
double variance=t*t/(w*(1-w));
if(variance>max_variance)
{
max_variance=variance;
threshold=i;
}
}
cvThreshold(temp,dst,threshold,255,CV_THRESH_BINARY);
cvReleaseImage(&temp);
}
效果圖:
對於膚色檢測後,應該加上一些去噪處理,比如形態學的開運算;對於檢測特定目標(人臉),在形態學處理後計算出影象輪廓,有選擇性地選擇一些輪廓,比如像素面積大於一定閾值來進行限制。
5.opencv自帶膚色檢測類AdaptiveSkinDetector
CvAdaptiveSkinDetector類中的2個比較重要的函式:
CvAdaptiveSkinDetector(int samplingDivider = 1, int morphingMethod = MORPHING_METHOD_NONE);
該函式為類的建構函式,其中引數1表示的是樣本取樣的間隔,預設情況下為1,即表示不進行降取樣;引數2為圖形學操作方式,即對用面板檢測後的影象進行圖形學操作。其取值有3種可能,如果為MORPHING_METHOD_ERODE,則表示只進行一次腐蝕操作;如果為MORPHING_METHOD_ERODE_ERODE,則表示連續進行2次腐蝕操作;如果為MORPHING_METHOD_ERODE_DILATE,則表示先進行一次腐蝕操作,後進行一次膨脹操作。
virtual void process(IplImage *inputBGRImage, IplImage *outputHueMask);
該函式為面板檢測的核心函式,引數1為需要進行面板檢測的輸入影象;引數2為輸出面板的掩膜影象,如果其值為1,代表該畫素為面板,否則當其為0時,代表為非面板。另外需要注意的是,這個函式只有opencv的c版本的,因為CvAdaptiveSkinDetector這個類放在opencv原始碼裡的contrib目錄裡,即表示比較新的但不成熟的演算法
程式碼:
#include <opencv\cv.h>
#include <opencv\highgui.h>
#include <contrib\contrib.hpp>
#include <core\core.hpp>
#include <imgproc\imgproc.hpp>
#include<time.h>
int main()
{
CvCapture* capture=cvCreateCameraCapture(0);
cvNamedWindow("Input Video",1);
cvNamedWindow("Output Video",1);
IplImage* img_src=NULL;
IplImage* input_img=NULL;
IplImage* output_mask=NULL;
IplImage* output_img=NULL;
clock_t start,finish;
double duration;
CvAdaptiveSkinDetector skin_detector(1,CvAdaptiveSkinDetector::MORPHING_METHOD_ERODE_DILATE); //定義膚色檢測運算元
while(1)
{
img_src=cvQueryFrame(capture);
if(!img_src) break;
cvShowImage("Input Video",img_src);
if(input_img==NULL)
{
input_img=cvCreateImage(cvGetSize(img_src),img_src->depth,img_src->nChannels);
}
cvCopy(img_src,input_img);
output_img=cvCreateImage(cvGetSize(img_src),img_src->depth,img_src->nChannels);
cvZero(output_img);
if(output_mask==NULL)
{
output_mask=cvCreateImage(cvGetSize(img_src),img_src->depth,1);
}
//膚色檢測
//
start=clock();
skin_detector.process(input_img,output_mask);
finish=clock();
duration=(double)(finish-start)/CLOCKS_PER_SEC;
printf("elapsed time :%.0f 毫秒\n",duration*1000);
cvCopy(img_src,output_img,output_mask);
cvShowImage("Output Video",output_img);
char c=cvWaitKey(33);
if(c==27)break;
}
cvReleaseCapture(&capture);
cvDestroyWindow("Video");
}
效果圖:
6.橢圓模型膚色檢測
經過前人學者大量的面板統計資訊可以知道,如果將面板資訊對映到YCrCb空間,則在CrCb二維空間中這些面板畫素點近似成一個橢圓分佈。因此如果我們得到了一個CrCb的橢圓,下次來一個座標(Cr, Cb)我們只需判斷它是否在橢圓內(包括邊界),如果是,則可以判斷其為面板,否則就是非面板畫素點。
膚色在CrCb座標系下,類聚影象如下:
該方法與反向投影法比較類似。
程式碼如下:
#include <opencv\cv.h>
#include <opencv\highgui.h>
#include <time.h>
int main()
{
//膚色橢圓
IplImage* skin_ellipse=cvCreateImage(cvSize(256,256),IPL_DEPTH_8U,1);
cvZero(skin_ellipse);
cvEllipse(skin_ellipse, cvPoint(113, 155.6), cvSize(23.4, 15.2), 43.0, 0.0, 360.0, cvScalar(255, 255, 255), -1);
CvCapture* capture=cvCreateCameraCapture(0);
IplImage* img_src=NULL;
IplImage* img_ycrcb=NULL;
IplImage* output_mask=NULL;
IplImage* img_dst=NULL;
CvMemStorage* storage=cvCreateMemStorage(0);
CvSeq* first_contour;
bool create_bool=false;
cvNamedWindow("Input Video",1);
cvNamedWindow("Skin Detect",1);
clock_t start,finish;
double duration;
while(1)
{
img_src=cvQueryFrame(capture);
if(!img_src) break;
cvShowImage("Input Video",img_src);
if(!create_bool)
{
img_ycrcb=cvCreateImage(cvGetSize(img_src),IPL_DEPTH_8U,3);
img_dst=cvCreateImage(cvGetSize(img_src),img_src->depth,img_src->nChannels);
output_mask=cvCreateImage(cvGetSize(img_src),IPL_DEPTH_8U,1);
create_bool=true;
}
cvZero(img_dst);
cvZero(output_mask);
start=clock();
cvCvtColor(img_src,img_ycrcb,CV_BGR2YCrCb);
unsigned char* p_ellipse=(unsigned char*)skin_ellipse->imageData;
for(int i=0;i<img_ycrcb->height;i++)
{
unsigned char* p_mask=(unsigned char*)(output_mask->imageData+i*output_mask->widthStep);
unsigned char* p_ycrcb=(unsigned char*)(img_ycrcb->imageData+i*img_ycrcb->widthStep);
for(int j=0;j<img_ycrcb->width;j++)
{
if(p_ellipse[p_ycrcb[j*3+1]*skin_ellipse->widthStep+p_ycrcb[j*3+2]] > 0)
p_mask[j] = 255;
}
}
finish=clock();
duration=(double)(finish-start)/CLOCKS_PER_SEC;
printf("elapsed time :%.0f 毫秒\n",duration*1000);
cvCopy(img_src,img_dst,output_mask);
cvShowImage("Skin Detect",img_dst);
char c=cvWaitKey(33);
if(c==27) break;
}
cvReleaseImage(&img_src);
cvReleaseCapture(&capture);
cvReleaseImage(&img_ycrcb);
cvReleaseImage(&output_mask);
cvDestroyWindow("Skin Detect");
cvDestroyWindow("Input Video");
cvWaitKey(0);
}
效果圖: