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






4.82/5 (61投票s)
用手势控制您的鼠标
引言
此应用程序使用 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;
}
处理代码分为三个主要部分
- 图像过滤以获得最大的肤色轮廓
- 查找凸包缺陷以计算手指的数量,用于鼠标点击事件
- 查找手掌中心并进行必要的噪声过滤以移动光标位置
过滤以获得肤色轮廓
首先,应用 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 上运行此应用程序需要三个步骤
- 从此处下载并安装
emguCV
库。 - 将所有文件从 "C:\Emgu\emgucv-windows-x86 2.4.0.1717\bin\x86\" 复制到您的 system32 文件夹中。
- 从以下链接下载并安装应用程序的安装程序
关注点
由于 Windows 8 现在已经在市场上推出,并且它为 PC 提供了更多的平板电脑触摸体验,因此我们正在考虑改进此应用程序,以便能够使用手势来为笔记本电脑提供触摸效果。