基于反向投影的肤色学习以及手势模型建立

来源:互联网 发布:程序员 1000万 编辑:程序博客网 时间:2024/04/25 09:06

主要想做的内容:

  1. 在室内条件下,用一些手和脸来建立H-S直方图(肤色的直方图)
  2. 利用函数calcbackproject()来找到肤色区域,分别代表了不同手势
  3. 对所找到的手势进行直方图建立模型,从而可作为后续输入手势的手势识别器。

操作过程:

首先是肤色直方图的建,我使用的是如左下图所示的皮肤图像,可以计算出其二维直方图,作为肤色的描述子,如右图所示。

皮肤图像 这里写图片描述
下一步是通过calcbackproject()寻找不同手势,这一步比较简单,如下图所示为反投影出的一些手势

这里写图片描述
手势1

这里写图片描述
手势2

这里写图片描述
手势3

下一步需要建立不同手势的直方图,但从以上结果可以看到,投影出的结果不是很理想,除了有噪声外,轮廓内部有很多黑洞(我们希望是实心的)。以下处理过程只使用一幅图片为例子。

我的处理思路是:

  1. 形态学去噪
    首先将投影出的图像转换为二值化图像,更易于处理,使用开操作+闭操作的方法去噪
  2. 轮廓提取
    轮廓提取有两个目的,一是通过检测轮廓长度可以去除较小的轮廓(同样是噪声),二是可以提取出手型轮廓的坐标信息。
  3. 漫水填充
    计算轮廓坐标的质心,可以认为在手型的内部。可以将该点作为种子点,做漫水填充,这一步是为了解决轮廓内很多黑洞的问题。

下图为形态学去噪的结果,除了黑洞外,还是有一些小噪声。
这里写图片描述
形态学去噪

再对形态学去噪图像的轮廓提取图像,由于要求轮廓长度大于图像周长的四分之一,右上角的小块噪声已经不见了,如下图。
这里写图片描述
轮廓图像


通过计算轮廓的零阶矩和一阶矩就可以计算出轮廓的质心坐标,该坐标作为下一步漫水填充的种子点。

为漫水填充的算法的使用建立一个新的模板,填充过后为1的点,我们就可以认为是手势的区域,该mask可以传入直方图计算函数,仅仅计算手势的直方图分布。

在试验过程中可以发现opencv中漫水填充算法中的lodiff和updiff并不好选,需要多次尝试才可以找到比较满意的结果,由于手的边缘有一些阴影,因此如果lodiff取的较大的话常常会得到更多的填充区域

如下图,为填充过后的模板区域。
这里写图片描述
漫水填充结果

可以跟最初反投影计算结果比较,此时的模板更加符合一个剪刀手势了,也就是我们真正感兴趣的区域。通过下一步函数,就可以建立该手势的直方图。
Hist = cvCalcHist(img,hist_size,range,1,flood_fill_mask);

因此,通过不同的手势图像学习,我们就可以获得不同手势的直方图了,即完成手势模型的建立。


后续实验:

既然已经建立了手势的模型,我便希望可以完成手势识别,采用了基于块的反向投影函数calcbackprojectpatch(),实验图像如左下所示,实验结果右下所示:
这里写图片描述

可以看出在复杂的背景条件下,同时检测出了手势以及脸型。因此可以说明前面所建立的剪刀手直方图是不完善的,即可以检测出皮肤,但对皮肤形状不能进行分类,因此,如果将更多的信息考虑在内,可能会收获更好地效果(例如轮廓HU矩)。


总结:

直方图是一种基于统计学的操作,不受平移或旋转的影响,因此它用于目标信息的学习和检测效果还是不错的。但该方法会受到光线变化、阴影等影响,一种解决办法是学习不同光照下的直方图,进行多次匹配,但从实验过程中可知,直方图匹配的过程是比较慢的,因此实时性较差。

#include<cv.h>#include<highgui.h>using namespace cv;int main(){    Mat  skin_learning = imread("skin.jpg");    Mat  gesture = imread("gesture.jpg");    Mat  hsv_skin;    Mat  hsv_gesture;    cvtColor(skin_learning, hsv_skin, CV_BGR2HSV);    cvtColor(gesture, hsv_gesture, CV_BGR2HSV);    int hbins = 20, sbins = 22;    int histSize[] = {hbins, sbins};    // hue varies from 0 to 179, see cvtColor    float hranges[] = { 0, 180 };    // saturation varies from 0 (black-gray-white) to    // 255 (pure spectrum color)    float sranges[] = { 0, 256 };    const float* ranges[] = { hranges, sranges };    MatND hist;    // we compute the histogram from the 0-th and 1-st channels    int channels[] = {0, 1};    calcHist( &hsv_skin, 1, channels, Mat(), // do not use mask             hist, 2, histSize, ranges,             true, // the histogram is uniform             false );    //反投影    MatND back_pro;    calcBackProject(&hsv_gesture,1,channels,hist,back_pro,ranges,1.0);    //二值化    threshold(back_pro,back_pro,30,255,CV_THRESH_BINARY);    //去噪    Mat k = getStructuringElement(MORPH_RECT,Size(3,3), Point(-1,-1));    morphologyEx(back_pro,back_pro,MORPH_OPEN,k);    morphologyEx(back_pro,back_pro,MORPH_CLOSE,k);    ////寻找轮廓(目标为寻找质心),此处不直接使用掩模图像,也是为了多目标分离    //flood fill寻找区域    //利用mask建立手势直方图    MatND gesture_hist;    calcHist( &hsv_gesture, 1, channels,back_pro , //  use mask             gesture_hist, 2, histSize, ranges,             true, // the histogram is uniform             false );    //读入图像测试    Mat test = imread("test.jpg");    Mat hsv;    cvtColor(test,hsv, CV_BGR2HSV);    MatND back_pro2;    calcBackProject(&hsv,1,channels,gesture_hist,back_pro2,ranges,1.0);    namedWindow("test");    imshow("test",test);    namedWindow("backproject");    imshow("backproject",back_pro2);    cvWaitKey(0);    return 0;}
0 0
原创粉丝点击