一、经过前人学者大量的皮肤统计信息可以知道,如果将皮肤信息映射到YCrCb空间,则在CrCb二维空间中这些皮肤像素点近似成一个椭圆分布。
如果我们得到了一个皮肤CrCb的椭圆,我们只需判断它是否在椭圆内(包括边界),如果是,则可以判断其为皮肤,否则就是非皮肤像素点。
void ellipse(Mat& img, Point center, Size axes, double angle, double startAngle, double endAngle, const Scalar&color, int thickness=1, int lineType=8, int shift=0)
该函数是用来在指定图片上绘制椭圆弧线的。
参数image为需要绘制椭圆的图像;
参数center是该椭圆的中心点坐标;
参数axes是该椭圆的长半轴和短半轴;
参数angle是该椭圆和水平方向上的旋转夹角;
参数startAngle表示绘制椭圆弧线相对该椭圆自己的水平轴的起始角度;
参数endAngel表示绘制椭圆弧线相对该椭圆自己的水平轴的终止角度;
findContours函数,这个函数的原型为:
void findContours(InputOutputArray image, OutputArrayOfArrays contours, OutputArray hierar-
chy, int mode, int method, Point offset=Point())
参数说明
输入图像image必须为一个2值单通道图像
contours参数为检测的轮廓数组,每一个轮廓用一个point类型的vector表示
hiararchy参数和轮廓个数相同,每个轮廓contours[ i ]对应4个hierarchy元素hierarchy[ i ][ 0 ] ~hierarchy[ i ][ 3 ],分别表示后一个轮廓、前一个轮廓、父轮廓、内嵌轮廓的索引编号,如果没有对应项,该值设置为负数。
mode表示轮廓的检索模式
CV_RETR_EXTERNAL表示只检测外轮廓
CV_RETR_LIST检测的轮廓不建立等级关系
CV_RETR_CCOMP建立两个等级的轮廓,上面的一层为外边界,里面的一层为内孔的边界信息。如果内孔内还有一个连通物体,这个物体的边界也在顶层。
CV_RETR_TREE建立一个等级树结构的轮廓。具体参考contours.c这个demo
method为轮廓的近似办法
CV_CHAIN_APPROX_NONE存储所有的轮廓点,相邻的两个点的像素位置差不超过1,即max(abs(x1-x2),abs(y2-y1))==1
CV_CHAIN_APPROX_SIMPLE压缩水平方向,垂直方向,对角线方向的元素,只保留该方向的终点坐标,例如一个矩形轮廓只需4个点来保存轮廓信息
CV_CHAIN_APPROX_TC89_L1,CV_CHAIN_APPROX_TC89_KCOS使用teh-Chinl chain 近似算法
offset表示代表轮廓点的偏移量,可以设置为任意值。对ROI图像中找出的轮廓,并要在整个图像中进行分析时,这个参数还是很有用的。
findContours后会对输入的2值图像改变,所以如果不想改变该2值图像,需创建新mat来存放,findContours后的轮廓信息contours可能过于复杂不平滑,可以用approxPolyDP函数对该多边形曲线做适当近似
contourArea函数可以得到当前轮廓包含区域的大小,方便轮廓的筛选
findContours经常与drawContours配合使用,用来将轮廓绘制出来。其中第一个参数image表示目标图像,第二个参数contours表示输入的轮廓组,每一组轮廓由点vector构成,第三个参数contourIdx指明画第几个轮廓,如果该参数为负值,则画全部轮廓,第四个参数color为轮廓的颜色,第五个参数thickness为轮廓的线宽,如果为负值或CV_FILLED表示填充轮廓内部,第六个参数lineType为线型,第七个参数为轮廓结构信息,第八个参数为maxLevel
得到了复杂轮廓往往不适合特征的检测,这里再介绍一个点集凸包络的提取函数convexHull,输入参数就可以是contours组中的一个轮廓,返回外凸包络的点集
还可以得到轮廓的外包络矩形,使用函数boundingRect,如果想得到旋转的外包络矩形,使用函数minAreaRect,返回值为RotatedRect;也可以得到轮廓的外包络圆,对应的函数为minEnclosingCircle;想得到轮廓的外包络椭圆,对应的函数为fitEllipse,返回值也是RotatedRect,可以用ellipse函数画出对应的椭圆
如果想根据多边形的轮廓信息得到多边形的多阶矩,可以使用类moments,这个类可以得到多边形和光栅形状的3阶以内的所有矩,类内有变量m00,m10,m01,m20,m11,m02,m30,m21,m12,m03,比如多边形的质心为 x = m10 / m00,y = m01 / m00。
如果想获得一点与多边形封闭轮廓的信息,可以调用pointPolygonTest函数,这个函数返回值为该点距离轮廓最近边界的距离,为正值为在轮廓内部,负值为在轮廓外部,0表示在边界上。
二、结果
三、代码
<span style="font-size:14px;"></pre><pre name="code" class="cpp">#include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include <opencv2/face.hpp> #include <opencv2/imgproc/imgproc.hpp> #include <iostream> #include <vector> using namespace std; using namespace cv; Mat input_image; Mat output_mask; Mat output_image; Mat mask; int main(int argc,char *argv[]) { if (2 != argc) { cout << "Please enter the image list!" <<endl; return -1; } vector<string> file_names; FILE *file_list = fopen(argv[1],"r"); char buf[255]; memset(&buf,0,sizeof(buf)); while(fgets(buf,255,file_list)) { if(buf[strlen(buf)-1] == '\n') buf[strlen(buf)-1] = '\0'; file_names.push_back(string(buf)); } fclose(file_list); int count = file_names.size(); Mat skinCrCbHist = Mat::zeros(Size(256, 256), CV_8UC1); ellipse(skinCrCbHist, Point(113, 155.6), Size(25,12), -20, 0.0, 360.0, Scalar(255, 255, 255), -1); Mat element = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1) ); for(int i=0; i<count; i++) { string img_nm = file_names[i]; string img_mask = "mask" + img_nm; int pos = img_nm.rfind('.'); string img_fmt = img_nm.substr(pos+1); if("jpg" != img_fmt) { cout << "Unknown format: " << img_fmt << endl; continue; } input_image=imread(img_nm,1); if(input_image.empty()) return 0; Mat ycrcb_image; output_mask = Mat::zeros(input_image.size(), CV_8UC1); cvtColor(input_image, ycrcb_image, CV_BGR2YCrCb); for(int i = 0; i < input_image.rows; i++) { uchar* p = (uchar*)output_mask.ptr<uchar>(i); Vec3b* ycrcb = (Vec3b*)ycrcb_image.ptr<Vec3b>(i); for(int j = 0; j < input_image.cols; j++) { if(skinCrCbHist.at<uchar>(ycrcb[j][1], ycrcb[j][2]) > 0) p[j] = 255; } } morphologyEx(output_mask,output_mask,MORPH_CLOSE,element); vector< vector<Point> > contours; vector< vector<Point> > filterContours; vector< Vec4i > hierarchy; contours.clear(); hierarchy.clear(); filterContours.clear(); findContours(output_mask, contours, hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE); for (size_t i = 0; i < contours.size(); i++) { if (fabs(contourArea(Mat(contours[i]))) > 2000&&fabs(arcLength(Mat(contours[i]),true))>500) filterContours.push_back(contours[i]); } output_mask.setTo(0); drawContours(output_mask, filterContours, -1, Scalar(255,0,0), CV_FILLED); input_image.copyTo(output_image, output_mask); imwrite(img_nm,output_image); imwrite(img_mask,output_mask); // namedWindow("input image",0); // // namedWindow("output mask",0); // namedWindow("output image",0); // imshow("input image", input_image); // imshow("output image", output_image); output_image.setTo(0); // waitKey(0); } return 0; } </span>
说明: