65.9K
CodeProject 正在变化。 阅读更多。
Home

初学者指南,了解使用 OpenCV 中的凸缺陷进行手指计数

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.86/5 (19投票s)

2014年6月18日

CPOL

5分钟阅读

viewsIcon

68938

downloadIcon

3828

引言

大家好,这是我的第一篇文章,在这篇文章中,我将向大家展示如何使用 OpenCV 中的凸包缺陷函数来计数手指。我希望这篇文章对那些想学习或想学习 OpenCV 的初学者非常有帮助。我会尽量让这篇文章对初学者来说更简单,并且我很乐意帮助你解决任何问题。OpenCV 旨在实现计算效率,并高度关注实时应用。该库采用优化的 C/C++ 编写,可以利用多核处理。在此 了解更多关于 OpenCV 的信息。

OpenCV 是 开源计算机视觉 的缩写,由英特尔开发

背景

具有 C++ 和 Visual Studio 的使用经验以及对 OpenCV 的基本了解将会很有帮助。

使用代码

在开始编码之前,我们必须在 Visual Studio 中配置 OpenCV,下面的视频展示了配置 OpenCV 的步骤。

配置 OpenCV

配置完成后,请包含以下头文件,以确保您已正确配置 Visual Studio 的 OpenCV。并且不要忘记使用 `cv` 命名空间。

#include <opencv\cv.h>
#include <opencv\highgui.h>
#include <opencv2\imgproc\imgproc.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\video\background_segm.hpp>
#include <opencv2\opencv.hpp>

using namespace cv;

首先,我们将创建一个滑块,稍后用于更改 HSV 颜色值,以匹配用户的肤色。

const string trackbarWindowName = "Trackbars"; 

以下变量初始化用于保存色相 (Hue)、饱和度 (Saturation) 和明度 (Value) 的最小值和最大值。

int H_MIN = 0; // minimum Hue
int H_MAX = 180; // maximum Hue
int S_MIN = 0; // minimum Saturation
int S_MAX = 255; // maximum Saturation
int V_MIN = 0; // minimum Value
int V_MAX = 255; //maximum Value

滑块的回调函数。每当滑块位置改变时,都会调用此函数。

void on_trackbar( int, void* )
{

// Doing nothing here since we are going to handle the changes in some other place

}

以下是在 OpenCV 中创建滑块的方法。

void createTrackbars(){

    namedWindow(trackbarWindowName,0);
    //create memory to store trackbar name on window
    char TrackbarName[50];
    sprintf( TrackbarName, "H_MIN", H_MIN);
    sprintf( TrackbarName, "H_MAX", H_MAX);
    sprintf( TrackbarName, "S_MIN", S_MIN);
    sprintf( TrackbarName, "S_MAX", S_MAX);
    sprintf( TrackbarName, "V_MIN", V_MIN);
    sprintf( TrackbarName, "V_MAX", V_MAX);
 
    createTrackbar( "H_MIN", trackbarWindowName, &H_MIN, H_MAX, on_trackbar );
    createTrackbar( "H_MAX", trackbarWindowName, &H_MAX, H_MAX, on_trackbar );
    createTrackbar( "S_MIN", trackbarWindowName, &S_MIN, S_MAX, on_trackbar );
    createTrackbar( "S_MAX", trackbarWindowName, &S_MAX, S_MAX, on_trackbar );
    createTrackbar( "V_MIN", trackbarWindowName, &V_MIN, V_MAX, on_trackbar );
    createTrackbar( "V_MAX", trackbarWindowName, &V_MAX, V_MAX, on_trackbar );

}

 

                         滑块预览

在我们的主函数中,我们将创建两个三通道的空图像,`frame` 将保存我们使用网络摄像头捕获的原始视频,而 `frame2` 将用于显示对 `frame` 图像进行一些更改后的输出。使用 `Mat` 类的优点是您不必像 C 结构 `IplImage` 那样手动分配和释放内存。

Mat frame(Size(640, 420),CV_8UC3);
Mat frame2(Size(640, 420),CV_8UC3);

 

int _tmain(int argc, _TCHAR* argv[])
{
    VideoCapture cap(0);  // open the default camera
    Mat frame(Size(640, 420),CV_8UC3);
    Mat frame2(Size(640, 420),CV_8UC3);
    createTrackbars(); // create trackbars
    if(!cap.isOpened())   
        return -1;

    while(true){
        cap>>frame; // new frame from camera
        imshow("Original Video", frame);
          if( cvWaitKey( 15 )==27 )  break; 
    }
    return 0;
}

如果您的代码没有错误,您将看到一个名为“原始视频”的窗口,其中显示着我们的视频。如果您想使用默认摄像头以外的其他摄像头,只需将 `cap(0)` 设置为 `cap(1)`。它将打开连接到计算机的外部摄像头。

现在,在继续处理捕获的视频之前,我们将进行一些降噪。这里我使用了高斯模糊来减少图像噪声,目标帧将与源帧相同。`kSize` 表示高斯核大小,它必须是正数且为奇数。您可以通过此 链接 找到有关 `GaussianBlur` 函数及其参数的更多详细信息。

Size kSize;
kSize.height = 3;
kSize.width = 3;
double sigma = 0.3*(3/2 - 1) + 0.8;
GaussianBlur(frame,frame,kSize,sigma,0.0,4);

下面我们将捕获的帧转换为 HSV 颜色格式。

Mat hsv(Size(640, 420),CV_8UC3);
cvtColor(frame,hsv,CV_RGB2HSV);

`inRange` 函数接受四个参数,它们是:

void inRange(InputArray src, InputArray lowerb, InputArray upperb, OutputArray dst)

src - 输入数组(HSV 图像矩阵)

lowerb - 下边界数组或标量。

upperb - 上边界数组或标量。

dst - 二值图像输出,尺寸与输入数组相同。白色像素表示在范围内的像素,黑色像素表示在范围外的像素。

Mat bw(Size(640, 420),CV_8UC1);
inRange(hsv,Scalar(H_MIN,S_MIN,V_MIN),Scalar(H_MAX,S_MAX,V_MAX),bw);  

膨胀和腐蚀是两种形态学操作。膨胀会增加图像中对象的边界像素,而腐蚀会去除对象边界上的像素。您可以在此 阅读更多关于形态学操作的信息。

Mat Erode(Size(640, 420),CV_8UC1);
cv::erode(bw, Erode, cv::Mat(), cv::Point(-1,-1));

Mat Dialate(Size(640, 420),CV_8UC1);
cv::dilate(Erode, Dialate, cv::Mat(), cv::Point(-1,-1),2);

Image after erosion            Image after Dialation

 

 

 

 

 

                       腐蚀图像                                                                     膨胀图像

好的。现在我们将找到输入视频的轮廓。我将膨胀图像用作 `findContours` 函数的输入图像,因为它需要一个二值图像。如 OpenCV 文档中所述,您可以使用 `compare()`、`inRange()`、`threshold()`、`adaptiveThreshold()`、`Canny()` 等函数从灰度或彩色图像创建二值图像。

vector<Vec4i> hierarchy;
vector<vector<Point> > contours_hull;

findContours(Dialate.clone(), contours_hull, hierarchy, CV_RETR_TREE , CV_CLOCKWISE, Point(0, 0) ); 

在下面的代码段中,我们使用轮廓和凸包来获取凸包点和缺陷点。之后,我们仅为最大的轮廓绘制轮廓和凸包。在这里,我们还为最大的轮廓绘制了边界框和旋转矩形。我添加了一些注释来解释绘图部分,这样您就可以理解这些代码的用途。

for( int i = 0; i < contours_hull.size(); i++ )
     { 
         convexHull( Mat(contours_hull[i]), hull[i], false );
         convexHull( Mat(contours_hull[i]), hullsI[i], false );
         convexityDefects(Mat(contours_hull[i]),hullsI[i], defects[i]);

            if(IndexOfBiggestContour == i)
               {
                  minRect[i] = minAreaRect( Mat(contours_hull[i]) );
                    
                 //draw contour of biggest object
                  drawContours( frame2, contours_hull,IndexOfBiggestContour, CV_RGB(255,255,255), 2, 8, hierarchy,0, Point() );
                //draw hull of biggesr object
                  drawContours( frame2, hull, IndexOfBiggestContour, CV_RGB(255,0,0), 2, 8, hierarchy, 0, Point() );

                  
                  approxPolyDP( Mat(contours_hull[i]), contours_poly[i], 3, true );
                  boundRect[i] = boundingRect( Mat(contours_poly[i]) );
                 
                  //draw rectangle for the biggest contour
                  rectangle( frame2, boundRect[i].tl(), boundRect[i].br(), CV_RGB(0,0,0), 2, 8, 0 );

                  Point2f rect_points[4];
                  minRect[i].points( rect_points );

                  for( int j = 0; j < 4; j++ )
                      {
                        //draw rotated rectangle for the biggest contour
                        line( frame2, rect_points[j], rect_points[(j+1)%4], CV_RGB(255,255,0), 2, 8 );
                      }

               }
     }

绘制凸包和最大的轮廓

hull image

为最大的轮廓绘制边界框

 为最大的轮廓绘制旋转矩形

rotated rectangle

 下面的代码段用于绘制最大轮廓的缺陷点。您可以使用深度值来避免不必要的缺陷点。深度值是指最远点与凸包之间的距离。在我的例子中,我使用深度值移除了几个缺陷点。在您的案例中可能无法正常工作。因此,您可以添加其他条件来避免不必要的点。

                size_t count = contours_hull[i].size();
                std::cout<<"Count : "<<count<<std::endl;
                if( count < 300 )
                    continue;

                vector<Vec4i>::iterator d =defects[i].begin();

                while( d!=defects[i].end() ) {
                    Vec4i& v=(*d);
                    if(IndexOfBiggestContour == i){

                        int startidx=v[0]; 
                        Point ptStart( contours_hull[i][startidx] ); // point of the contour where the defect begins
                        int endidx=v[1]; 
                        Point ptEnd( contours_hull[i][endidx] ); // point of the contour where the defect ends
                        int faridx=v[2]; 
                        Point ptFar( contours_hull[i][faridx] );// the farthest from the convex hull point within the defect
                        float depth = v[3] / 256; // distance between the farthest point and the convex hull

                        if(depth > 20 && depth < 80)
                        {
                        line( frame2, ptStart, ptFar, CV_RGB(0,255,0), 2 );
                        line( frame2, ptEnd, ptFar, CV_RGB(0,255,0), 2 );
                        circle( frame2, ptStart,   4, Scalar(100,0,255), 2 );
                        }
                    }
                    d++;
                }

绘制缺陷的起点和终点

defects points

程序的最终输出

final output

 

结论

OpenCV 开发的目的是为了实现实时计算机视觉。它主要侧重于实时图像处理。通过学习 OpenCV,您可以开发人脸识别系统、手势识别、人机交互、移动机器人、运动跟踪、增强现实等。所以希望您能看到学习它的重要性,我希望在我的下一篇文章中继续介绍 OpenCV 的更多内容。

 编程愉快!

© . All rights reserved.