opencv学习笔记(二):基于肤色的人手检测

opencv学习笔记(二):基于肤色的人手检测




 原文:http://blog.csdn.net/wzmsltw/article/details/50849810



先写了人手的检测程序,下一步基于检测程序再用camshift算法做人手的跟踪。

目前完成的程序在我的笔记本上运行大概是一帧80-100ms,直接用检测算法来做跟踪算法其实也马马虎虎可以用了。

 

开发环境如下:

系统:Windows 10

IDEVisual Studio 2013

语言:C++

算法库:OpenCV

 

程序思路如下

 

1)获取视频帧

2)将视频帧转换到YCrCb颜色空间,并分割通道

3)基于CrCb两个通道做肤色区域的分割,得到肤色区域二值图像

4)将二值图像分别做膨胀和腐蚀处理,得到前景和背景的标记(marker)图像,应用分水岭算法,得到大块肤色区域的边缘轮廓

5)对4)中得到的边缘轮廓用8向种子算法处理,对不同的肤色区域做了标记,并返回了不同肤色区域的边界范围,这些肤色区域作为人手区域的候选区域

6)将5)中得到的候选区域与准备好的人手模板(Cr通道)进行模板匹配,匹配前先将候选区域缩放到与模板相同的大小;使用的方法是平方差匹配法,得到每个候选区域的匹配值(越小越接近)

7)对5)中得到的候选区域中肤色像素的比例进行统计

8)根据6)与7)中得到的结果对候选区域进行筛选,认为匹配值 <0.02(0为最匹配,1为最不匹配),且肤色区域比例<0.65的区域为人手区域(因为人脸区域一般肤色占比比较高)

9)在输出帧中对确定的人手区域画长方形框做标记

 

讨论:

1)之所以使用肤色区域比例的筛选方法,是因为基于肤色的情况下,人脸和人手非常容易混淆,经过尝试发现增进模板的数量效果并不好,因此采用了这种方法,但这种方法带来的问题就是人手必须张开(从而降低肤色在候选框中的比例)才能稳定被找到。

2)其实不做模板匹配只统计肤色比例应该也是可以的,但是稳定性会比较差。

3)分水岭算法的介绍见以下链接:

http://www.xuebuyuan.com/1014698.html

 

 

效果图如下:








[cpp]  view plain  copy
  1. #include "stdafx.h"  
  2. #include <iostream>  
  3. #include <vector>  
  4. #include <string>  
  5. #include <list>  
  6. #include <map>  
  7. #include <stack>  
  8. #include <opencv2/core/core.hpp>  
  9. #include <opencv2/features2d/features2d.hpp>  
  10. #include <opencv2/highgui/highgui.hpp>  
  11. #include <opencv2/imgproc/imgproc.hpp>  
  12. #include <opencv2/calib3d/calib3d.hpp>  
  13.   
  14.   
  15. using namespace std;  
  16. using namespace cv;  
  17.   
  18.   
  19. //8邻接种子算法,并返回每块区域的边缘框  
  20. void Seed_Filling(const cv::Mat& binImg, cv::Mat& labelImg, int& labelNum, int (&ymin)[20], int(&ymax)[20], int(&xmin)[20], int(&xmax)[20])   //种子填充法    
  21. {  
  22.     if (binImg.empty() ||  
  23.         binImg.type() != CV_8UC1)  
  24.     {  
  25.         return;  
  26.     }  
  27.   
  28.     labelImg.release();  
  29.     binImg.convertTo(labelImg, CV_32SC1);  
  30.     int label = 1;  
  31.     int rows = binImg.rows - 1;  
  32.     int cols = binImg.cols - 1;  
  33.     for (int i = 1; i < rows - 1; i++)  
  34.     {  
  35.         int* data = labelImg.ptr<int>(i);  
  36.         for (int j = 1; j < cols - 1; j++)  
  37.         {  
  38.             if (data[j] == 1)  
  39.             {  
  40.                 std::stack<std::pair<intint>> neighborPixels;  
  41.                 neighborPixels.push(std::pair<intint>(j, i));     // 像素位置: <j,i>    
  42.                 ++label;  // 没有重复的团,开始新的标签   
  43.                 ymin[label] = i;  
  44.                 ymax[label] = i;  
  45.                 xmin[label] = j;  
  46.                 xmax[label] = j;  
  47.                 while (!neighborPixels.empty())  
  48.                 {  
  49.                     std::pair<intint> curPixel = neighborPixels.top(); //如果与上一行中一个团有重合区域,则将上一行的那个团的标号赋给它    
  50.                     int curX = curPixel.first;  
  51.                     int curY = curPixel.second;  
  52.                     labelImg.at<int>(curY,curX) = label;  
  53.   
  54.                     neighborPixels.pop();  
  55.   
  56.                     if ((curX>0)&&(curY>0)&&(curX<(cols-1))&&(curY<(rows-1)))  
  57.                     {   
  58.                         if (labelImg.at<int>(curY - 1,curX) == 1)                     //上  
  59.                         {  
  60.                             neighborPixels.push(std::pair<intint>(curX, curY - 1));  
  61.                             //ymin[label] = curY - 1;  
  62.                         }  
  63.                         if (labelImg.at<int>( curY + 1,curX) == 1)                        //下  
  64.                         {  
  65.                             neighborPixels.push(std::pair<intint>(curX, curY + 1));  
  66.                             if ((curY+1)>ymax[label])  
  67.                                 ymax[label] = curY + 1;  
  68.                         }  
  69.                         if (labelImg.at<int>(curY,curX - 1) == 1)                     //左  
  70.                         {  
  71.                             neighborPixels.push(std::pair<intint>(curX - 1, curY));  
  72.                             if ((curX - 1)<xmin[label])  
  73.                                 xmin[label] = curX - 1;  
  74.                         }  
  75.                         if (labelImg.at<int>(curY,curX + 1) == 1)                     //右  
  76.                         {  
  77.                             neighborPixels.push(std::pair<intint>(curX + 1, curY));  
  78.                             if ((curX + 1)>xmax[label])  
  79.                                 xmax[label] = curX + 1;  
  80.                         }  
  81.                         if (labelImg.at<int>(curY - 1,curX-1) == 1)                   //左上  
  82.                         {    
  83.                             neighborPixels.push(std::pair<intint>(curX - 1, curY - 1));  
  84.                             //ymin[label] = curY - 1;  
  85.                             if ((curX - 1)<xmin[label])  
  86.                                 xmin[label] = curX - 1;  
  87.                         }  
  88.                         if (labelImg.at<int>(curY + 1,curX+1) == 1)                   //右下  
  89.                         {    
  90.                             neighborPixels.push(std::pair<intint>(curX+1, curY + 1));  
  91.                             if ((curY + 1)>ymax[label])  
  92.                                 ymax[label] = curY + 1;  
  93.                             if ((curX + 1)>xmax[label])  
  94.                                 xmax[label] = curX + 1;  
  95.   
  96.                         }  
  97.                         if (labelImg.at<int>( curY + 1,curX - 1) == 1)                    //左下  
  98.                         {   
  99.                             neighborPixels.push(std::pair<intint>(curX - 1, curY+1));  
  100.                             if ((curY + 1)>ymax[label])  
  101.                                 ymax[label] = curY + 1;  
  102.                             if ((curX - 1)<xmin[label])  
  103.                                 xmin[label] = curX - 1;  
  104.                         }  
  105.                         if (labelImg.at<int>( curY - 1,curX + 1) == 1)                    //右上  
  106.                         {  
  107.                             neighborPixels.push(std::pair<intint>(curX + 1, curY-1));  
  108.                             //ymin[label] = curY - 1;  
  109.                             if ((curX + 1)>xmax[label])  
  110.                                 xmax[label] = curX + 1;  
  111.   
  112.                         }  
  113.                     }  
  114.                 }  
  115.             }  
  116.         }  
  117.     }  
  118.     labelNum = label-1;  
  119.   
  120. }  
  121.   
  122. class WatershedSegmenter {  
  123. private:  
  124.     cv::Mat markers;  
  125. public:  
  126.     void setMarkers(const cv::Mat& markerImage) {  
  127.   
  128.         // Convert to image of ints  
  129.         markerImage.convertTo(markers, CV_32S);  
  130.     }  
  131.     cv::Mat process(const cv::Mat &image) {  
  132.   
  133.         // Apply watershed  
  134.         cv::watershed(image, markers);  
  135.         return markers;  
  136.     }  
  137.     // Return result in the form of an image  
  138.     cv::Mat getSegmentation() {  
  139.   
  140.         cv::Mat tmp;  
  141.         // all segment with label higher than 255  
  142.         // will be assigned value 255  
  143.         markers.convertTo(tmp, CV_8U);  
  144.         return tmp;  
  145.     }  
  146.   
  147.     // Return watershed in the form of an image  
  148.     cv::Mat getWatersheds() {  
  149.         cv::Mat tmp;  
  150.         markers.convertTo(tmp, CV_8U,255, 255);  
  151.         return tmp;  
  152.     }  
  153. };  
  154.   
  155.   
  156.   
  157.   
  158.   
  159. int main()  
  160. {  
  161. //设置视频读入,括号里面的数字是摄像头的选择,一般自带的是0  
  162. cv::VideoCapture cap(0);  
  163. if (!cap.isOpened())  
  164. {  
  165.     return -1;  
  166. }  
  167. Mat frame;  
  168. Mat binImage,tmp;  
  169. Mat Y, Cr, Cb;  
  170. vector<Mat> channels;  
  171. //模板图片,是Cr颜色通道的人手图像截图  
  172. Mat tmpl = imread("bwz.jpg",CV_8UC1);  
  173.   
  174.   
  175. bool stop = false;  
  176. while (!stop)  
  177. {  
  178.     //读入视频帧,转换颜色空间,并分割通道  
  179.     cap >> frame;  
  180.     cvtColor(frame, binImage, CV_BGR2GRAY);  
  181.     frame.copyTo(tmp);  
  182.     cvtColor(tmp, tmp, CV_BGR2YCrCb);  
  183.     split(tmp, channels);  
  184.     Cr = channels.at(1);  
  185.     Cb = channels.at(2);  
  186.   
  187.     //肤色检测,输出二值图像  
  188.     for (int j = 1; j < Cr.rows - 1; j++)  
  189.     {  
  190.         uchar* currentCr = Cr.ptr< uchar>(j);  
  191.         uchar* currentCb = Cb.ptr< uchar>(j);  
  192.         uchar* current = binImage.ptr< uchar>(j);  
  193.         for (int i = 1; i < Cb.cols - 1; i++)  
  194.         {  
  195.             if ((currentCr[i] > 140) && (currentCr[i] < 170)  &&(currentCb[i] > 77) && (currentCb[i] < 123))  
  196.                 current[i] = 255;  
  197.             else  
  198.                 current[i] = 0;  
  199.         }  
  200.     }  
  201.   
  202.     //形态学处理  
  203.     //dilate(binImage, binImage, Mat());  
  204.     dilate(binImage, binImage, Mat());  
  205.   
  206.     //分水岭算法  
  207.     cv::Mat fg;  
  208.     cv::erode(binImage, fg, cv::Mat(), cv::Point(-1, -1), 6);  
  209.     // Identify image pixels without objects  
  210.     cv::Mat bg;  
  211.     cv::dilate(binImage, bg, cv::Mat(), cv::Point(-1, -1), 6);  
  212.     cv::threshold(bg, bg, 1, 128, cv::THRESH_BINARY_INV);  
  213.     // Show markers image  
  214.     cv::Mat markers(binImage.size(), CV_8U, cv::Scalar(0));  
  215.     markers = fg + bg;  
  216.     // Create watershed segmentation object  
  217.     WatershedSegmenter segmenter;  
  218.     segmenter.setMarkers(markers);  
  219.     segmenter.process(frame);  
  220.     Mat waterShed;  
  221.     waterShed = segmenter.getWatersheds();  
  222.     //imshow("watershed", waterShed);  
  223.     //获得区域边框  
  224.     threshold(waterShed, waterShed, 1, 1, THRESH_BINARY_INV);  
  225.       
  226.     //8向种子算法,给边框做标记  
  227.     Mat labelImg;  
  228.     int label, ymin[20], ymax[20], xmin[20], xmax[20];  
  229.     Seed_Filling(waterShed, labelImg, label, ymin, ymax, xmin, xmax);  
  230.   
  231.     //根据标记,对每块候选区就行缩放,并与模板比较  
  232.     Size dsize = Size(tmpl.cols, tmpl.rows);  
  233.     float simi[20];  
  234.     for (int i = 0; i < label; i++)  
  235.     {  
  236.         simi[i] = 1;  
  237.         if (((xmax[2 + i] - xmin[2 + i])>50) && ((ymax[2 + i] - ymin[2 + i]) > 50))  
  238.         {  
  239.             //rectangle(frame, Point(xmin[2 + i], ymin[2 + i]), Point(xmax[2 + i], ymax[2 + i]), Scalar::all(255), 2, 8, 0);  
  240.             Mat rROI = Mat(dsize, CV_8UC1);  
  241.             if (rROI.cols==0)
    continue;
  242.             resize(Cr(Rect(xmin[2 + i], ymin[2 + i], xmax[2 + i] - xmin[2 + i], ymax[2 + i] - ymin[2 + i])), rROI, dsize);  
  243.             Mat result;  
  244.             matchTemplate(rROI, tmpl, result, CV_TM_SQDIFF_NORMED);  
  245.             simi[i] = result.ptr<float>(0)[0];  
  246.             //cout << simi[i] << endl;  
  247.         }  
  248.     }  
  249.       
  250.     //统计一下区域中的肤色区域比例  
  251.     float fuseratio[20];  
  252.     for (int k = 0; k < label; k++)  
  253.     {  
  254.         fuseratio[k] = 1;  
  255.         if (((xmax[2 + k] - xmin[2 + k])>50) && ((ymax[2 + k] - ymin[2 + k]) > 50))  
  256.         {  
  257.             int fusepoint=0;  
  258.             for (int j = ymin[2+k]; j < ymax[2+k]; j++)  
  259.             {  
  260.                 uchar* current = binImage.ptr< uchar>(j);  
  261.                 for (int i = xmin[2+k]; i < xmax[2+k]; i++)  
  262.                 {  
  263.                     if (current[i] == 255)  
  264.                         fusepoint += 1;  
  265.                 }  
  266.             }  
  267.             fuseratio[k] = float(fusepoint) / ((xmax[2 + k] - xmin[2 + k])*(ymax[2 + k] - ymin[2 + k]));  
  268.             //cout << fuseratio[k] << endl;  
  269.         }  
  270.     }  
  271.   
  272.     //给符合阈值条件的位置画框  
  273.     for (int i = 0; i < label; i++)  
  274.     {  
  275.         if ((simi[i]<0.02)&&(fuseratio[i]<0.65))  
  276.             rectangle(frame, Point(xmin[2 + i], ymin[2 + i]), Point(xmax[2 + i], ymax[2 + i]), Scalar::all(255), 2, 8, 0);  
  277.     }  
  278.       
  279.     imshow("frame", frame);  
  280.     //processor.writeNextFrame(frame);  
  281.     imshow("test", binImage);  
  282.   
  283.     if (waitKey(1) >= 0)  
  284.         stop = true;  
  285. }  
  286. cout << "ss" << endl;  
  287. //cv::waitKey();  
  288. return 0;  
  289. }  


其中的模板图片为



目前还存在诸多不如意之处,还待继续改进。若有表达不清或者有错误之处,烦请看到此文的朋友提出或指正~

  • 3
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值