#基于VR环境下的手指识别键盘输入# 利用OpenCV for Unity初步完成的手指识别效果(二)

在前一篇文章中,我们完成了对手指肌肤颜色的检测。检测到肌肤颜色之后,接下来就是要确定手掌的范围。

在这个过程中,将要使用到的核心函数是Opencv中的convexityDefects。


如上图所示,黑色的轮廓线为convexity hull, 而convexity hull与手掌之间的部分为convexity defects. 每个convexity defect区域有四个特征量:起始点(startPoint),结束点(endPoint),距离convexity hull最远点(farPoint),最远点到convexity hull的距离(depth)。

void convexityDefects(InputArray contour, InputArray convexhull, OutputArrayconvexityDefects)
参数:
coutour: 输入参数,检测到的轮廓,可以调用findContours函数得到;
convexhull: 输入参数,检测到的凸包,可以调用convexHull函数得到。注意,convexHull函数可以得到vector<vector<Point>>和vector<vector<int>>两种类型结果,这里的convexhull应该为vector<vector<int>>类型,否则通不过ASSERT检查;
convexityDefects:输出参数,检测到的最终结果,应为vector<vector<Vec4i>>类型,Vec4i存储了起始点(startPoint),结束点(endPoint),距离convexity hull最远点(farPoint)以及最远点到convexity hull的距离(depth)

在这张例图中,蓝色的是convexity defects的起始点和结束点,红色的是最远点。通过观察可以发现,我们需要定位的是蓝色的点。

所以在编写代码时,需要进行以下几步的处理:

(1)进行高斯滤波,对图像的Mat进行处理,方便后面的提取轮廓。

			//高斯滤波,将rgbaMat用高斯滤波处理后,重新输出到rgbaMat中去
            Imgproc.GaussianBlur (rgbaMat, rgbaMat, new OpenCVForUnity.Size (5, 5), 1, 1);

(2)提取图像中选定颜色的轮廓,将其存在一个点阵中。这里就用到了我们在上一篇文章中提到过的HSV处理。对其进行了膨胀和轮廓提取。

            List<MatOfPoint> contours = detector.GetContours ();
	    detector.Process (rgbaMat);

(3)寻找最小的包围盒,提取屏幕中占比最大和次大的rect。之所以要寻找两个rect,是因为想要实现双手的识别。但是在实际操作的时候发现,双手的识别的情况容易因为手部的移动显得很不稳定(两只手占比差不多的情况下,最大和次大经常会跳变。)所以暂时应用的是单手。

            //寻找最小包围矩形
            RotatedRect rect = Imgproc.minAreaRect (new MatOfPoint2f (contours [0].toArray ()));
            
            double boundWidth = rect.size.width;
            double boundHeight = rect.size.height;
            int boundPos1 = 0;
//			int boundPos2 = 0;
            
			//取最大的rect和次大的rect(双手)
            for (int i = 0; i < contours.Count; i++) {
                rect = Imgproc.minAreaRect (new MatOfPoint2f (contours [i].toArray ()));
                if (rect.size.width * rect.size.height > boundWidth * boundHeight) {
                    boundWidth = rect.size.width;
                    boundHeight = rect.size.height;
                    boundPos1 = i;
                }
            }

//			for (int i = 0; i < contours.Count; i++) {
//				if (i != boundPos1) {
//					rect = Imgproc.minAreaRect (new MatOfPoint2f (contours [i].toArray ()));
//					if (rect.size.width * rect.size.height > boundWidth * boundHeight) {
//						boundWidth = rect.size.width;
//						boundHeight = rect.size.height;
//						boundPos2 = i;
//					}
//				}
//			}

(4)获得凸包,并且利用凸包和之前得到的轮廓,计算起始点和结束点的位置。

            MatOfInt hull1 = new MatOfInt ();
//			MatOfInt hull2 = new MatOfInt ();
            MatOfInt4 convexDefect1 = new MatOfInt4 ();
//			MatOfInt4 convexDefect2 = new MatOfInt4 ();
            Imgproc.convexHull (new MatOfPoint (contour1.toArray ()), hull1);
//			Imgproc.convexHull (new MatOfPoint (contour2.toArray ()), hull2);
            
//            if (hull2.toArray ().Length < 3)
//                return;

			if (hull1.toArray ().Length < 3)
				return;
            
            Imgproc.convexityDefects (new MatOfPoint (contour1.toArray ()), hull1, convexDefect1);
//			Imgproc.convexityDefects (new MatOfPoint (contour2.toArray ()), hull2, convexDefect2);
            List<MatOfPoint> hullPoints1 = new List<MatOfPoint> ();
            List<Point> listPo1 = new List<Point> ();
            for (int j = 0; j < hull1.toList().Count; j++) {
                listPo1.Add (contour1.toList () [hull1.toList () [j]]);
            }

			MatOfPoint e1 = new MatOfPoint ();
			e1.fromList (listPo1);
			hullPoints1.Add (e1);
//
//			List<MatOfPoint> hullPoints2 = new List<MatOfPoint> ();
//			List<Point> listPo2 = new List<Point> ();
//			for (int j = 0; j < hull2.toList().Count; j++) {
//				listPo2.Add (contour2.toList () [hull2.toList () [j]]);
//			}
//			MatOfPoint e2 = new MatOfPoint ();
//			e2.fromList (listPo2);
//			hullPoints2.Add (e2);

            //List<Point> listPoDefect = new List<Point> ();
			listPoDefect1 = new List<Point>();
			int count1 = 0;
            if (convexDefect1.rows () > 0) {
                List<int> convexDefectList1 = convexDefect1.toList ();
                List<Point> contourList1 = contour1.toList ();
                for (int j = 0; j < convexDefectList1.Count; j = j + 4) {
					//最远点
                    Point farPoint = contourList1 [convexDefectList1 [j + 2]];
					//最远点到convexity hull的距离(depth)
                    int depth = convexDefectList1 [j + 3];
					if (depth > threasholdSlider.value&&farPoint.y<a1) {
                        listPoDefect1.Add (contourList1 [convexDefectList1 [j]]);
						count1++;
                    }
                    //                              Debug.Log ("convexDefectList [" + j + "] " + convexDefectList [j + 3]);
                }
            }

所有的点的信息就被存储在名为listPoDefect1的点的list中。在VR项目中实际应用的时候,只需要利用cs代码获取对应脚本中的参数,并且进行赋值就可以了。


通过这张图可以看到,手指被五个红点较好地定位了。(而且是在背景比较复杂的情况下,如果是纯色桌面背景,相信效果会更好。)

猜你喜欢

转载自blog.csdn.net/zxycs1996/article/details/80669060
今日推荐