先贴代码
IplImage *imgYCrCb = cvCreateImage(imageSize, img->depth, img->nChannels);
cvCvtColor(img,imgYCrCb,CV_BGR2YCrCb);
cvSplit(imgYCrCb, imgY, imgCr, imgCb, 0);
int y, cr, cb, l, x1, y1, value;
unsigned char *pY, *pCr, *pCb, *pMask;
pY = (unsigned char *)imgY->imageData;
pCr = (unsigned char *)imgCr->imageData;
pCb = (unsigned char *)imgCb->imageData;
pMask = (unsigned char *)mask->imageData;
cvSetZero(mask);
l = img->height * img->width;
for (int i = 0; i < l; i++){
y = *pY;
cr = *pCr;
cb = *pCb;
cb -= 109;
cr -= 152
;
x1 = (819*cr-614*cb)/32 + 51;
y1 = (819*cr+614*cb)/32 + 77;
x1 = x1*41/1024;
y1 = y1*73/1024;
value = x1*x1+y1*y1;
if(y<100) (*pMask)=(value<700) ? 255:0;
else (*pMask)=(value<850)? 255:0;
pY++;
pCr++;
pCb++;
pMask++;
}
cvReleaseImage(&imgY);
cvReleaseImage(&imgCr);
cvReleaseImage(&imgCb);
cvReleaseImage(&imgYCrCb);
}
主要原理就是通过在Cb Cr空间上找到一个可以拟合常规肤色分布的椭圆形,然后把在椭圆形区域内的像素点标记为肤色
图1.1 椭圆模板示例
以上插图来源于《一种基于KL变换的椭圆模型肤色检测方法 》,具体参数参考的那篇文献时间久远找不到了
以下是代码运行后的效果图
图1.2 运行效果1
图1.3 运行效果2
从上面两图可以看出,在光线条件比较理想的情况下,肤色检测的效果还是不错的(1.2就比1.3效果好),但是对于一些似肤色区域(比如图1.3后面的木质门),还是会被误检,但这是肤色检测无法解决的问题。
关于效果图里面一些类似噪点的部分,可以通过膨胀腐蚀模糊再二值化的方法取得比较圆润的肤色图(就是可以做mask的)
当然有时候效果也不是特别好,这个要靠自己调参数的。
总体而言,与OpenCV2.0的adapativeskindetector.cpp相比的话,效果要好(其实我改进的代码就是参照里面CvAdaptiveSkinDetector类里的process函数的),当然也有可能是因为我的肤色检测是根据我所处环境的光照条件和摄像头特性调节的缘故。
最后,小小地对代码作一个说明。
其实代码很简单,就是把Y Cb Cr三个通道分开,然后用指针分别对这三个通道的每一个像素进行处理。
需要作修改的就是if(y<100) (*pMask)=(value<700) ? 255:0; else (*pMask)=(value<850)? 255:0; 这条做阈值判断的命令
由于光照和摄像头性能的不同,这里的阈值需要根据自己的摄像头调节出最合适的效果才可以
另外的话,对于质量不是很好的WebCam 建议在输入图像上加一个小点的高斯模糊以去除噪点
以上算法还曾经作为我的大作业在Matlab和Xilinx FPGA上实现,具体可以参考我的答辩PPT:Skin Segmentation on FPGA