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

通过网络摄像头进行鼠标控制

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.82/5 (61投票s)

2012年11月24日

CPOL

4分钟阅读

viewsIcon

214918

downloadIcon

37025

用手势控制您的鼠标

引言

此应用程序使用 emguCV,一个用于 openCV 的 .NET 包装器,来执行图像处理。通过图像处理,我们尝试识别手势并使用这些手势控制鼠标。 在此应用程序中,光标的移动由手的移动控制,点击事件通过手势触发。

背景

我一直想拥有一个方便的应用程序来控制鼠标。因此,当 Sajid Hussain 先生,我们在工业电子工程研究所 IIEE 的老师,要求我们使用 C# 开发一个应用程序时,我们(我、Sajjad Idrees 和 Saad Hafeez)决定做这个。 之前已经有很多类似的应用程序,但它们通常使用 kinect 或其他东西,很少有应用程序使用简单的网络摄像头作为输入。 虽然该应用程序并不完美,需要大量工作才能完善,但它确实有效。

Using the Code

该代码使用了一个著名的图像处理库,即 openCV,以 emguCV 的形式存在,它是它的 .NET 包装器。 代码非常简单易懂。

首先,应用程序尝试捕获视频输入设备。如果成功,它会调用函数 ProcessFramAndUpdateGUI();,其中完成所有处理。

private void Form1_Load(object sender, EventArgs e)
{
	//trying to capture a vedio input device
    try
    {
        CapWebCam = new Capture();
    }
    catch ()
    {                
    }

    Application.Idle += ProcessFramAndUpdateGUI;
}  

处理代码分为三个主要部分

  1. 图像过滤以获得最大的肤色轮廓
  2. 查找凸包缺陷以计算手指的数量,用于鼠标点击事件
  3. 查找手掌中心并进行必要的噪声过滤以移动光标位置

过滤以获得肤色轮廓

首先,应用 YCbCr 过滤器,使用所需的阈值来获得图像的肤色部分。

int Finger_num = 0;
Double Result1 = 0;
Double Result2 = 0;
		
imgOrignal = CapWebCam.QueryFrame();	//querying image

if (imgOrignal == null) return;		// pass only if there is some image
            
//Applying YCrCb filter
Image<Ycc, Byte> currentYCrCbFrame = imgOrignal.Convert<Ycc, byte>();
Image<Gray, byte> skin = new Image<Gray, byte>(imgOrignal.Width, imgOrignal.Height);
            
skin = currentYCrCbFrame.InRange(new Ycc(0, 131, 80), new Ycc(255, 185, 135));
            
StructuringElementEx rect_12 = 
  new StructuringElementEx(10, 10, 5, 5, Emgu.CV.CvEnum.CV_ELEMENT_SHAPE.CV_SHAPE_RECT);

现在使用 cv.erode()cv.dilate() 函数对过滤后的图像进行腐蚀和膨胀。

StructuringElementEx rect_12 = 
   new StructuringElementEx(10, 10, 5, 5, Emgu.CV.CvEnum.CV_ELEMENT_SHAPE.CV_SHAPE_RECT);
 
//Eroding the source image using the specified structuring element
CvInvoke.cvErode(skin, skin, rect_12, 1);
            
StructuringElementEx rect_6 = 
  new StructuringElementEx(6, 6, 3, 3, Emgu.CV.CvEnum.CV_ELEMENT_SHAPE.CV_SHAPE_RECT);
 
//dilating the source image using the specified structuring element
CvInvoke.cvDilate(skin, skin, rect_6, 2);

最后,使用高斯滤波器对图像进行平滑处理,并从生成的图像中提取最大的轮廓。

//smoothing the filterd , eroded and dilated image.
skin = skin.SmoothGaussian(9);                    
          
Contour<Point> contours = skin.FindContours(); //extracting all contours.
Contour<Point> biggestContour = null;
            
//extracting the biggest contour.
while (contours != null)
{
    Result1 = contours.Area;
    if (Result1 >

使用凸包缺陷计数手指

现在为了确定手指的数量,我使用了一种非常流行的方法,称为凸包缺陷。 此方法确定我们轮廓中的所有缺陷,从而告诉我们图像中手指的数量。 代码如下

//applying convexty defect allgoritm to find the count of fingers
if (biggestContour != null)
{
    Finger_num = 0;
    
    biggestContour = biggestContour.ApproxPoly((0.00025));
    imgOrignal.Draw(biggestContour, new Bgr(Color.LimeGreen), 2);
    
    Hull = biggestContour.GetConvexHull(ORIENTATION.CV_CLOCKWISE);
    defects = biggestContour.GetConvexityDefacts(storage, ORIENTATION.CV_CLOCKWISE);
    imgOrignal.DrawPolyline(Hull.ToArray(), true, new Bgr(0, 0, 256), 2);
    
    box = biggestContour.GetMinAreaRect();
    
    defectArray = defects.ToArray();
    
    for (int i = 0; i < defects.Total; i++)
    {
        PointF startPoint = new PointF((float)defectArray[i].StartPoint.X,
                                    (float)defectArray[i].StartPoint.Y);

        PointF depthPoint = new PointF((float)defectArray[i].DepthPoint.X,
                                        (float)defectArray[i].DepthPoint.Y);

        PointF endPoint = new PointF((float)defectArray[i].EndPoint.X,
                                        (float)defectArray[i].EndPoint.Y);

                    
        CircleF startCircle = new CircleF(startPoint, 5f);
        CircleF depthCircle = new CircleF(depthPoint, 5f);
        CircleF endCircle = new CircleF(endPoint, 5f);
        
        
        if (    (startCircle.Center.Y < box.center.Y || depthCircle.Center.Y < box.center.Y) && 
                (startCircle.Center.Y < depthCircle.Center.Y) && 
                (Math.Sqrt(Math.Pow(startCircle.Center.X - depthCircle.Center.X, 2) + 
                           Math.Pow(startCircle.Center.Y - depthCircle.Center.Y, 2)) > 
                           box.size.Height / 6.5)   )
        {
            Finger_num++;
        }

知道了手指的数量,我就将鼠标点击事件与之关联。 为了让用户点击鼠标左键,他/她张开手掌,一旦手指计数大于四,就点击鼠标左键。

查找轮廓的中心

最后,我们尝试使用轮廓的矩来找到轮廓的中心。 一旦我们能够获得中心的坐标,我们注意到轮廓的中心波动太大,因为手不断闪烁。 对于这个问题,我们将中心的坐标除以 10,以删除它们的单位部分,因为坐标的十分位没有波动。 这是代码

MCvMoments moment = new MCvMoments();                    // a new MCvMoments object

moment = biggestContour.GetMoments();                    // Moments of biggestContour

CvInvoke.cvMoments(biggestContour, ref moment, 0);
        
double m_00 = CvInvoke.cvGetSpatialMoment(ref moment, 0, 0);
double m_10 = CvInvoke.cvGetSpatialMoment(ref moment, 1, 0);
double m_01 = CvInvoke.cvGetSpatialMoment(ref moment, 0, 1);

int current_X = Convert.ToInt32(m_10 / m_00) / 10;       // X location of centre of contour              
int current_Y = Convert.ToInt32(m_01 / m_00) / 10;       // Y location of center of contour

我们还面临另一个问题,当手掌张开和闭合时,中心会发生偏移。 对于这个问题,我们设置了条件,只有在手掌闭合时,即手指计数为零时,才会移动鼠标光标位置,否则鼠标将保持在原来的位置。 下面的代码还显示了鼠标点击事件

// move cursor to center of contour only if Finger count is 1 or 0
// i.e. palm is closed

if (Finger_num == 0 || Finger_num == 1)
{
    Cursor.Position = new Point(current_X * 20, current_Y * 20);   
}

// Leave the cursor where it was and Do mouse click, if finger count >= 4

if (Finger_num >= 4)
{
    DoMouseClick();                     // function clicks mouse left button
}  

DoMouseClick() 的函数定义如下,但是 system32 函数 mouse_event() 现在已经过时了,但我们仍然使用了它。

// function for mouse clicks

[DllImport("user32.dll")]
static extern void mouse_event(uint dwFlags, uint dx, uint dy, uint dwData,
   int dwExtraInfo);

private const int MOUSEEVENTF_LEFTDOWN = 0x02;          // mouse left button pressed 
private const int MOUSEEVENTF_LEFTUP = 0x04;            // mouse left button unpressed
private const int MOUSEEVENTF_RIGHTDOWN = 0x08;         // mouse right button pressed
private const int MOUSEEVENTF_RIGHTUP = 0x10;           // mouse right button unpressed

//this function will click the mouse using the parameters assigned to it
public void DoMouseClick()
{
    //Call the imported function with the cursor's current position
    uint X = Convert.ToUInt32(Cursor.Position.X);
    uint Y =Convert.ToUInt32(Cursor.Position.Y);
    mouse_event(MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_LEFTUP, X, Y, 0, 0);
}     

以上就是有关使用代码的全部内容,希望对您有所帮助。 还有很多事情要做才能完善此应用程序。 我们希望观众的建议可以帮助我们改进它。

注意:我们没有在附加的文件中包含 emguCV DLL,必须安装 emguCV 并复制所有 DLL 才能运行该项目。

在您的 PC 上运行此应用程序

在您的 PC 上运行此应用程序需要三个步骤

  1. 此处下载并安装 emguCV 库。
  2. 将所有文件从 "C:\Emgu\emgucv-windows-x86 2.4.0.1717\bin\x86\" 复制到您的 system32 文件夹中。
  3. 从以下链接下载并安装应用程序的安装程序

关注点

由于 Windows 8 现在已经在市场上推出,并且它为 PC 提供了更多的平板电脑触摸体验,因此我们正在考虑改进此应用程序,以便能够使用手势来为笔记本电脑提供触摸效果。

© . All rights reserved.