竞赛 题目:基于机器视觉opencv的手势检测 手势识别 算法 - 深度学习 卷积神经网络 opencv python
文章目录
- 1 简介
- 2 传统机器视觉的手势检测
- 2.1 轮廓检测法
- 2.2 算法结果
- 2.3 整体代码实现
- 2.3.1 算法流程
- 3 深度学习方法做手势识别
- 3.1 经典的卷积神经网络
- 3.2 YOLO系列
- 3.3 SSD
- 3.4 实现步骤
- 3.4.1 数据集
- 3.4.2 图像预处理
- 3.4.3 构建卷积神经网络结构
- 3.4.4 实验训练过程及结果
- 3.5 关键代码
- 4 最后
1 简介
🔥 优质竞赛项目系列,今天要分享的是
基于机器视觉opencv的手势检测 手势识别 算法
该项目较为新颖,适合作为竞赛课题方向,学长非常推荐!
🧿 更多资料, 项目分享:
https://gitee.com/dancheng-senior/postgraduate
2 传统机器视觉的手势检测
普通机器视觉手势检测的基本流程如下:
其中轮廓的提取,多边形拟合曲线的求法,凸包集和凹陷集的求法都是采用opencv中自带的函数。手势数字的识别是利用凸包点以及凹陷点和手部中心点的几何关系,简单的做了下逻辑判别了(可以肯定的是这种方法很烂),具体的做法是先在手部定位出2个中心点坐标,这2个中心点坐标之间的距离阈值由程序设定,其中一个中心点就是利用OpenNI跟踪得到的手部位置。有了这2个中心点的坐标,在程序中就可以分别计算出在这2个中心点坐标上的凸凹点的个数。当然了,这样做的前提是用人在做手势表示数字的同时应该是将手指的方向朝上(因为没有像机器学习那样通过样本来训练,所以使用时条件要苛刻很多)。利用上面求出的4种点的个数(另外程序中还设置了2个辅助计算点的个数,具体见代码部分)和简单的逻辑判断就可以识别出数字0~5了。其它的数字可以依照具体的逻辑去设计(还可以设计出多位数字的识别),只是数字越多设计起来越复杂,因为要考虑到它们之间的干扰性,且这种不通用的设计方法也没有太多的实际意义。
2.1 轮廓检测法
使用 void convexityDefects(InputArray contour, InputArray convexhull,
OutputArray convexityDefects) 方法
该函数的作用是对输入的轮廓contour,凸包集合来检测其轮廓的凸型缺陷,一个凸型缺陷结构体包括4个元素,缺陷起点坐标,缺陷终点坐标,缺陷中离凸包线距离最远的点的坐标,以及此时最远的距离。参数3即其输出的凸型缺陷结构体向量。
其凸型缺陷的示意图如下所示:
第1个参数虽然写的是contour,字面意思是轮廓,但是本人实验过很多次,发现如果该参数为目标通过轮廓检测得到的原始轮廓的话,则程序运行到onvexityDefects()函数时会报内存错误。因此本程序中采用的不是物体原始的轮廓,而是经过多项式曲线拟合后的轮廓,即多项式曲线,这样程序就会顺利地运行得很好。另外由于在手势识别过程中可能某一帧检测出来的轮廓非常小(由于某种原因),以致于少到只有1个点,这时候如果程序运行到onvexityDefects()函数时就会报如下的错误:
int Mat::checkVector(int _elemChannels, int _depth, bool _requireContinuous) const{return (depth() == _depth || _depth <= 0) &&(isContinuous() || !_requireContinuous) &&((dims == 2 && (((rows == 1 || cols == 1) && channels() == _elemChannels) || (cols == _elemChannels))) ||(dims == 3 && channels() == 1 && size.p[2] == _elemChannels && (size.p[0] == 1 || size.p[1] == 1) &&(isContinuous() || step.p[1] == step.p[2]*size.p[2])))? (int)(total()*channels()/_elemChannels) : -1;}
该函数源码大概意思就是说对应的Mat矩阵如果其深度,连续性,通道数,行列式满足一定条件的话就返回Mat元素的个数和其通道数的乘积,否则返回-1;而本文是要求其返回值大于3,有得知此处输入多边形曲线(即参数1)的通道数为2,所以还需要求其元素的个数大于1.5,即大于2才满足ptnum
3。简单的说就是用convexityDefects()函数来对多边形曲线进行凹陷检测时,必须要求参数1曲线本身至少有2个点(也不知道这样分析对不对)。因此本人在本次程序convexityDefects()函数前加入了if(Mat(approx_poly_curve).checkVector(2,
CV_32S) > 3)来判断,只有满足该if条件,才会进行后面的凹陷检测。这样程序就不会再出现类似的bug了。
第2个参数一般是由opencv中的函数convexHull()获得的,一般情况下该参数里面存的是凸包集合中的点在多项式曲线点中的位置索引,且该参数以vector的形式存在,因此参数convexhull中其元素的类型为unsigned
int。在本次凹陷点检测函数convexityDefects()里面根据文档,要求该参数为Mat型。因此在使用convexityDefects()的参数2时,一般将vector直接转换Mat型。
参数3是一个含有4个元素的结构体的集合,如果在c++的版本中,该参数可以直接用vector来代替,Vec4i中的4个元素分别表示凹陷曲线段的起始坐标索引,终点坐标索引,离凸包集曲线最远点的坐标索引以及此时的最远距离值,这4个值都是整数。在c版本的opencv中一般不是保存的索引,而是坐标值。
2.2 算法结果
数字“0”的识别结果:
数字“1”的识别结果
数字“2”的识别结果
数字“3”的识别结果:
数字“4”的识别结果:
数字“5”的识别结果:
2.3 整体代码实现
2.3.1 算法流程
学长实现过程和上面的系统流程图类似,大概过程如下:
-
1. 求出手部的掩膜
-
2. 求出掩膜的轮廓
-
3. 求出轮廓的多变形拟合曲线
-
4. 求出多边形拟合曲线的凸包集,找出凸点
-
5. 求出多变形拟合曲线的凹陷集,找出凹点
-
6. 利用上面的凸凹点和手部中心点的几何关系来做简单的数字手势识别
(这里用的是C语言写的,这个代码是学长早期写的,同学们需要的话,学长出一个python版本的)
#include #include "opencv2/highgui/highgui.hpp"#include "opencv2/imgproc/imgproc.hpp"#include #include "copenni.cpp"#include #define DEPTH_SCALE_FACTOR 255./4096.#define ROI_HAND_WIDTH 140#define ROI_HAND_HEIGHT 140#define MEDIAN_BLUR_K 5#define XRES 640#define YRES 480#define DEPTH_SEGMENT_THRESH 5#define MAX_HANDS_COLOR 10#define MAX_HANDS_NUMBER 10#define HAND_LIKELY_AREA 2000#define DELTA_POINT_DISTENCE 25 //手部中心点1和中心点2距离的阈值#define SEGMENT_POINT1_DISTANCE 27 //凸点与手部中心点1远近距离的阈值#define SEGMENT_POINT2_DISTANCE 30 //凸点与手部中心点2远近距离的阈值using namespace cv;using namespace xn;using namespace std;int main (int argc, char **argv){unsigned int convex_number_above_point1 = 0;unsigned int concave_number_above_point1 = 0;unsigned int convex_number_above_point2 = 0;unsigned int concave_number_above_point2 = 0;unsigned int convex_assist_above_point1 = 0;unsigned int convex_assist_above_point2 = 0;unsigned int point_y1 = 0;unsigned int point_y2 = 0;int number_result = -1;bool recognition_flag = false; //开始手部数字识别的标志vector<Scalar> color_array;//采用默认的10种颜色{color_array.push_back(Scalar(255, 0, 0));color_array.push_back(Scalar(0, 255, 0));color_array.push_back(Scalar(0, 0, 255));color_array.push_back(Scalar(255, 0, 255));color_array.push_back(Scalar(255, 255, 0));color_array.push_back(Scalar(0, 255, 255));color_array.push_back(Scalar(128, 255, 0));color_array.push_back(Scalar(0, 128, 255));color_array.push_back(Scalar(255, 0, 128));color_array.push_back(Scalar(255, 128, 255));}vector<unsigned int> hand_depth(MAX_HANDS_NUMBER, 0);vector<Rect> hands_roi(MAX_HANDS_NUMBER, Rect(XRES/2, YRES/2, ROI_HAND_WIDTH, ROI_HAND_HEIGHT));namedWindow("color image", CV_WINDOW_AUTOSIZE);namedWindow("depth image", CV_WINDOW_AUTOSIZE);namedWindow("hand_segment", CV_WINDOW_AUTOSIZE); //显示分割出来的手的区域namedWindow("handrecognition", CV_WINDOW_AUTOSIZE); //显示0~5数字识别的图像COpenNI openni;if(!openni.Initial())return 1;if(!openni.Start())return 1;while(1) {if(!openni.UpdateData()) {return 1;}/*获取并显示色彩图像*/Mat color_image_src(openni.image_metadata_.YRes(), openni.image_metadata_.XRes(),CV_8UC3, (char *)openni.image_metadata_.Data());Mat color_image;cvtColor(color_image_src, color_image, CV_RGB2BGR);Mat hand_segment_mask(color_image.size(), CV_8UC1, Scalar::all(0));for(auto itUser = openni.hand_points_.cbegin(); itUser != openni.hand_points_.cend(); ++itUser) {point_y1 = itUser->second.Y;point_y2 = itUser->second.Y + DELTA_POINT_DISTENCE;circle(color_image, Point(itUser->second.X, itUser->second.Y),5, color_array.at(itUser->first % color_array.size()), 3, 8);/*设置不同手部的深度*/hand_depth.at(itUser->first % MAX_HANDS_COLOR) = (unsigned int)(itUser->second.Z* DEPTH_SCALE_FACTOR);//itUser->first会导致程序出现bug/*设置不同手部的不同感兴趣区域*/hands_roi.at(itUser->first % MAX_HANDS_NUMBER) = Rect(itUser->second.X - ROI_HAND_WIDTH/2, itUser->second.Y - ROI_HAND_HEIGHT/2,ROI_HAND_WIDTH, ROI_HAND_HEIGHT);hands_roi.at(itUser->first % MAX_HANDS_NUMBER).x = itUser->second.X - ROI_HAND_WIDTH/2;hands_roi.at(itUser->first % MAX_HANDS_NUMBER).y = itUser->second.Y - ROI_HAND_HEIGHT/2;hands_roi.at(itUser->first % MAX_HANDS_NUMBER).width = ROI_HAND_WIDTH;hands_roi.at(itUser->first % MAX_HANDS_NUMBER).height = ROI_HAND_HEIGHT;if(hands_roi.at(itUser->first % MAX_HANDS_NUMBER).x <= 0)hands_roi.at(itUser->first % MAX_HANDS_NUMBER).x = 0;if(hands_roi.at(itUser->first % MAX_HANDS_NUMBER).x > XRES)hands_roi.at(itUser->first % MAX_HANDS_NUMBER).x = XRES;if(hands_roi.at(itUser->first % MAX_HANDS_NUMBER).y <= 0)hands_roi.at(itUser->first % MAX_HANDS_NUMBER).y = 0;if(hands_roi.at(itUser->first % MAX_HANDS_NUMBER).y > YRES)hands_roi.at(itUser->first % MAX_HANDS_NUMBER).y = YRES;}imshow("color image", color_image);/*获取并显示深度图像*/Mat depth_image_src(openni.depth_metadata_.YRes(), openni.depth_metadata_.XRes(),CV_16UC1, (char *)openni.depth_metadata_.Data());//因为kinect获取到的深度图像实际上是无符号的16位数据Mat depth_image;depth_image_src.convertTo(depth_image, CV_8U, DEPTH_SCALE_FACTOR);imshow("depth image", depth_image);//取出手的mask部分//不管原图像时多少通道的,mask矩阵声明为单通道就okfor(auto itUser = openni.hand_points_.cbegin(); itUser != openni.hand_points_.cend(); ++itUser) {for(int i = hands_roi.at(itUser->first % MAX_HANDS_NUMBER).x; i < std::min(hands_roi.at(itUser->first % MAX_HANDS_NUMBER).x+hands_roi.at(itUser->first % MAX_HANDS_NUMBER).width, XRES); i++)for(int j = hands_roi.at(itUser->first % MAX_HANDS_NUMBER).y; j < std::min(hands_roi.at(itUser->first % MAX_HANDS_NUMBER).y+hands_roi.at(itUser->first % MAX_HANDS_NUMBER).height, YRES); j++) {hand_segment_mask.at<unsigned char>(j, i) = ((hand_depth.at(itUser->first % MAX_HANDS_NUMBER)-DEPTH_SEGMENT_THRESH) < depth_image.at<unsigned char>(j, i))& ((hand_depth.at(itUser->first % MAX_HANDS_NUMBER)+DEPTH_SEGMENT_THRESH) > depth_image.at<unsigned char>(j,i));}}medianBlur(hand_segment_mask, hand_segment_mask, MEDIAN_BLUR_K);Mat hand_segment(color_image.size(), CV_8UC3);color_image.copyTo(hand_segment, hand_segment_mask);/*对mask图像进行轮廓提取,并在手势识别图像中画出来*/std::vector< std::vector<Point> > contours;findContours(hand_segment_mask, contours, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE);//找出mask图像的轮廓Mat hand_recognition_image = Mat::zeros(color_image.rows, color_image.cols, CV_8UC3);for(int i = 0; i < contours.size(); i++) { //只有在检测到轮廓时才会去求它的多边形,凸包集,凹陷集recognition_flag = true;/*找出轮廓图像多边形拟合曲线*/Mat contour_mat = Mat(contours[i]);if(contourArea(contour_mat) > HAND_LIKELY_AREA) { //比较有可能像手的区域std::vector<Point> approx_poly_curve;approxPolyDP(contour_mat, approx_poly_curve, 10, true);//找出轮廓的多边形拟合曲线std::vector< std::vector<Point> > approx_poly_curve_debug;approx_poly_curve_debug.push_back(approx_poly_curve);drawContours(hand_recognition_image, contours, i, Scalar(255, 0, 0), 1, 8); //画出轮廓// drawContours(hand_recognition_image, approx_poly_curve_debug, 0, Scalar(256, 128, 128), 1, 8); //画出多边形拟合曲线/*对求出的多边形拟合曲线求出其凸包集*/vector<int> hull;convexHull(Mat(approx_poly_curve), hull, true);for(int i = 0; i < hull.size(); i++) {circle(hand_recognition_image, approx_poly_curve[hull[i]], 2, Scalar(0, 255, 0), 2, 8);/*统计在中心点1以上凸点的个数*/if(approx_poly_curve[hull[i]].y <= point_y1) {/*统计凸点与中心点1的y轴距离*/long dis_point1 = abs(long(point_y1 - approx_poly_curve[hull[i]].y));int dis1 = point_y1 - approx_poly_curve[hull[i]].y;if(dis_point1 > SEGMENT_POINT1_DISTANCE && dis1 >= 0) {convex_assist_above_point1++;}convex_number_above_point1++;}/*统计在中心点2以上凸点的个数*/if(approx_poly_curve[hull[i]].y <= point_y2) {/*统计凸点与中心点1的y轴距离*/long dis_point2 = abs(long(point_y2 - approx_poly_curve[hull[i]].y));int dis2 = point_y2 - approx_poly_curve[hull[i]].y;if(dis_point2 > SEGMENT_POINT2_DISTANCE && dis2 >= 0) {convex_assist_above_point2++;}convex_number_above_point2++;}}// /*对求出的多边形拟合曲线求出凹陷集*/std::vector<Vec4i> convexity_defects;if(Mat(approx_poly_curve).checkVector(2, CV_32S) > 3)convexityDefects(approx_poly_curve, Mat(hull), convexity_defects);for(int i = 0; i < convexity_defects.size(); i++) {circle(hand_recognition_image, approx_poly_curve[convexity_defects[i][2]] , 2, Scalar(0, 0, 255), 2, 8);/*统计在中心点1以上凹陷点的个数*/if(approx_poly_curve[convexity_defects[i][2]].y <= point_y1)concave_number_above_point1++;/*统计在中心点2以上凹陷点的个数*/if(approx_poly_curve[convexity_defects[i][2]].y <= point_y2)concave_number_above_point2++;}}}/**画出手势的中心点**/for(auto itUser = openni.hand_points_.cbegin(); itUser != openni.hand_points_.cend(); ++itUser) {circle(hand_recognition_image, Point(itUser->second.X, itUser->second.Y), 3, Scalar(0, 255, 255), 3, 8);circle(hand_recognition_image, Point(itUser->second.X, itUser->second.Y + 25), 3, Scalar(255, 0, 255), 3, 8);}/*手势数字0~5的识别*///"0"的识别if((convex_assist_above_point1 ==0 && convex_number_above_point2 >= 2 && convex_number_above_point2 <= 3 &&concave_number_above_point2 <= 1 && concave_number_above_point1 <= 1) || (concave_number_above_point1 ==0|| concave_number_above_point2 == 0) && recognition_flag == true)number_result = 0;//"1"的识别if(convex_assist_above_point1 ==1 && convex_number_above_point1 >=1 && convex_number_above_point1 <=2 &&convex_number_above_point2 >=2 && convex_assist_above_point2 == 1)number_result = 1;//"2"的识别if(convex_number_above_point1 == 2 && concave_number_above_point1 == 1 && convex_assist_above_point2 == 2/*convex_assist_above_point1 <=1*/ && concave_number_above_point2 == 1)number_result = 2;//"3"的识别if(convex_number_above_point1 == 3 && concave_number_above_point1 <= 3 &&concave_number_above_point1 >=1 && convex_number_above_point2 >= 3 && convex_number_above_point2 <= 4 &&convex_assist_above_point2 == 3)number_result = 3;//"4"的识别if(convex_number_above_point1 == 4 && concave_number_above_point1 <=3 && concave_number_above_point1 >=2 &&convex_number_above_point2 == 4)number_result = 4;//"5"的识别if(convex_number_above_point1 >=4 && convex_number_above_point2 == 5 && concave_number_above_point2 >= 3 &&convex_number_above_point2 >= 4)number_result = 5;if(number_result !=0 && number_result != 1 && number_result != 2 && number_result != 3 && number_result != 4 && number_result != 5)number_result == -1;/*在手势识别图上显示匹配的数字*/std::stringstream number_str;number_str << number_result;putText(hand_recognition_image, "Match: ", Point(0, 60), 4, 1, Scalar(0, 255, 0), 2, 0 );if(number_result == -1)putText(hand_recognition_image, " ", Point(120, 60), 4, 2, Scalar(255, 0 ,0), 2, 0);elseputText(hand_recognition_image, number_str.str(), Point(150, 60), 4, 2, Scalar(255, 0 ,0), 2, 0);imshow("handrecognition", hand_recognition_image);imshow("hand_segment", hand_segment);/*一个循环中对有些变量进行初始化操作*/convex_number_above_point1 = 0;convex_number_above_point2 = 0;concave_number_above_point1 = 0;concave_number_above_point2 = 0;convex_assist_above_point1 = 0;convex_assist_above_point2 = 0;number_result = -1;recognition_flag = false;number_str.clear();waitKey(20);}}#include "copenni.h"#include #include #include using namespace xn;using namespace std;COpenNI::COpenNI(){}COpenNI::~COpenNI(){}bool COpenNI::Initial(){status_ = context_.Init();if(CheckError("Context initial failed!")) {return false;}context_.SetGlobalMirror(true);//设置镜像xmode_.nXRes = 640;xmode_.nYRes = 480;xmode_.nFPS = 30;//产生颜色nodestatus_ = image_generator_.Create(context_);if(CheckError("Create image generator error!")) {return false;}//设置颜色图片输出模式status_ = image_generator_.SetMapOutputMode(xmode_);if(CheckError("SetMapOutputMdoe error!")) {return false;}//产生深度nodestatus_ = depth_generator_.Create(context_);if(CheckError("Create depth generator error!")) {return false;}//设置深度图片输出模式status_ = depth_generator_.SetMapOutputMode(xmode_);if(CheckError("SetMapOutputMdoe error!")) {return false;}//产生手势nodestatus_ = gesture_generator_.Create(context_);if(CheckError("Create gesture generator error!")) {return false;}/*添加手势识别的种类*/gesture_generator_.AddGesture("Wave", NULL);gesture_generator_.AddGesture("click", NULL);gesture_generator_.AddGesture("RaiseHand", NULL);gesture_generator_.AddGesture("MovingHand", NULL);//产生手部的nodestatus_ = hand_generator_.Create(context_);if(CheckError("Create hand generaotr error!")) {return false;}//产生人体nodestatus_ = user_generator_.Create(context_);if(CheckError("Create gesturen generator error!")) {return false;}//视角校正status_ = depth_generator_.GetAlternativeViewPointCap().SetViewPoint(image_generator_);if(CheckError("Can't set the alternative view point on depth generator!")) {return false;}//设置与手势有关的回调函数XnCallbackHandle gesture_cb;gesture_generator_.RegisterGestureCallbacks(CBGestureRecognized, CBGestureProgress, this, gesture_cb);//设置于手部有关的回调函数XnCallbackHandle hands_cb;hand_generator_.RegisterHandCallbacks(HandCreate, HandUpdate, HandDestroy, this, hands_cb);//设置有人进入视野的回调函数XnCallbackHandle new_user_handle;user_generator_.RegisterUserCallbacks(CBNewUser, NULL, NULL, new_user_handle);user_generator_.GetSkeletonCap().SetSkeletonProfile(XN_SKEL_PROFILE_ALL);//设定使用所有关节(共15个)//设置骨骼校正完成的回调函数XnCallbackHandle calibration_complete;user_generator_.GetSkeletonCap().RegisterToCalibrationComplete(CBCalibrationComplete, this, calibration_complete);return true;}bool COpenNI::Start(){status_ = context_.StartGeneratingAll();if(CheckError("Start generating error!")) {return false;}return true;}bool COpenNI::UpdateData(){status_ = context_.WaitNoneUpdateAll();if(CheckError("Update date error!")) {return false;}//获取数据image_generator_.GetMetaData(image_metadata_);depth_generator_.GetMetaData(depth_metadata_);return true;}ImageGenerator &COpenNI::getImageGenerator(){return image_generator_;}DepthGenerator &COpenNI::getDepthGenerator(){return depth_generator_;}UserGenerator &COpenNI::getUserGenerator(){return user_generator_;}GestureGenerator &COpenNI::getGestureGenerator(){return gesture_generator_;}HandsGenerator &COpenNI::getHandGenerator(){return hand_generator_;}bool COpenNI::CheckError(const char *error){if(status_ != XN_STATUS_OK) {cerr << error << ": " << xnGetStatusString( status_ ) << endl;return true;}return false;}void COpenNI::CBNewUser(UserGenerator &generator, XnUserID user, void *p_cookie){//得到skeleton的capability,并调用RequestCalibration函数设置对新检测到的人进行骨骼校正generator.GetSkeletonCap().RequestCalibration(user, true);}void COpenNI::CBCalibrationComplete(SkeletonCapability &skeleton, XnUserID user, XnCalibrationStatus calibration_error, void *p_cookie){if(calibration_error == XN_CALIBRATION_STATUS_OK) {skeleton.StartTracking(user);//骨骼校正完成后就开始进行人体跟踪了}else {UserGenerator *p_user = (UserGenerator*)p_cookie;skeleton.RequestCalibration(user, true);//骨骼校正失败时重新设置对人体骨骼继续进行校正}}void COpenNI::CBGestureRecognized(GestureGenerator &generator, const XnChar *strGesture, const XnPoint3D *pIDPosition, const XnPoint3D *pEndPosition, void *pCookie){COpenNI *openni = (COpenNI*)pCookie;openni->hand_generator_.StartTracking(*pEndPosition);}void COpenNI::CBGestureProgress(GestureGenerator &generator, const XnChar *strGesture, const XnPoint3D *pPosition, XnFloat fProgress, void *pCookie){}void COpenNI::HandCreate(HandsGenerator &rHands, XnUserID xUID, const XnPoint3D *pPosition, XnFloat fTime, void *pCookie){COpenNI *openni = (COpenNI*)pCookie;XnPoint3D project_pos;openni->depth_generator_.ConvertRealWorldToProjective(1, pPosition, &project_pos);pair<XnUserID, XnPoint3D> hand_point_pair(xUID, XnPoint3D());//在进行pair类型的定义时,可以将第2个设置为空hand_point_pair.second = project_pos;openni->hand_points_.insert(hand_point_pair);//将检测到的手部存入map类型的hand_points_中。pair<XnUserID, vector<XnPoint3D>> hand_track_point(xUID, vector<XnPoint3D>());hand_track_point.second.push_back(project_pos);openni->hands_track_points_.insert(hand_track_point);}void COpenNI::HandUpdate(HandsGenerator &rHands, XnUserID xUID, const XnPoint3D *pPosition, XnFloat fTime, void *pCookie){COpenNI *openni = (COpenNI*)pCookie;XnPoint3D project_pos;openni->depth_generator_.ConvertRealWorldToProjective(1, pPosition, &project_pos);openni->hand_points_.find(xUID)->second = project_pos;openni->hands_track_points_.find(xUID)->second.push_back(project_pos);}void COpenNI::HandDestroy(HandsGenerator &rHands, XnUserID xUID, XnFloat fTime, void *pCookie){COpenNI *openni = (COpenNI*)pCookie;openni->hand_points_.erase(openni->hand_points_.find(xUID));openni->hands_track_points_.erase(openni->hands_track_points_.find(xUID ));}
3 深度学习方法做手势识别
3.1 经典的卷积神经网络
卷积神经网络的优势就在于它能够从常见的视觉任务中自动学习目 标数据的特征, 然后将这些特征用于某种特定任务的模型。 随着时代的发展,
深度学习也形成了一些经典的卷积神经网络。
3.2 YOLO系列
YOLO 系列的网络模型最早源于 2016 年, 之后几年经过不断改进相继推出YOLOv2、 YOLOv3
等网络,直到今日yoloV5也诞生了,不得不感慨一句,darknet是真的肝。
最具代表性的yolov3的结构
3.3 SSD
SSD 作为典型的一阶段网络模型, 具有更高的操作性, 端到端的学习模式同样受到众多研究者的喜爱
3.4 实现步骤
3.4.1 数据集
手势识别的数据集来自于丹成学长实验室,由于中国手势表示3的手势根据地区有略微差异,按照这个数据集的手势训练与测试即可。
- 图像大小:100*100 像素
- 颜色空间:RGB 种类:
- 图片种类:6 种(0,1,2,3,4,5)
- 每种图片数量:200 张
一共6种手势,每种手势200张图片,共1200张图片(100x100RGB)
3.4.2 图像预处理
实际图片处理展示:resize前先高斯模糊,提取边缘后可以根据实际需要增加一次中值滤波去噪:
3.4.3 构建卷积神经网络结构
使用tensorflow的框架,构建一个简单的网络结构
Dropout: 增加鲁棒性帮助正则化和避免过拟合
一个相关的早期使用这种技术的论文((ImageNet Classification with Deep Convolutional Neural
Networks, by Alex Krizhevsky, Ilya Sutskever, and Geoffrey Hinton
(2012).))中启发性的dropout解释是:
因为一个神经元不能依赖其他特定的神经元。因此,不得不去学习随机子集神经元间的鲁棒性的有用连接。换句话说。想象我们的神经元作为要给预测的模型,dropout是一种方式可以确保我们的模型在丢失一个个体线索的情况下保持健壮的模型。在这种情况下,可以说他的作用和L1和L2范式正则化是相同的。都是来减少权重连接,然后增加网络模型在缺失个体连接信息情况下的鲁棒性。在提高神经网络表现方面效果较好。
3.4.4 实验训练过程及结果
经过约4800轮的训练后,loss基本收敛,在0.6左右,在120份的测试样本上的模型准确率能够达到约96%
3.5 关键代码
import tensorflow as tfIMAGE_SIZE = 100NUM_CHANNELS = 1CONV1_SIZE = 4CONV1_KERNEL_NUM = 8CONV2_SIZE = 2CONV2_KERNEL_NUM = 16FC_SIZE = 512OUTPUT_NODE = 6def get_weight(shape, regularizer):w = tf.Variable(tf.truncated_normal(shape,stddev=0.1))if regularizer != None: tf.add_to_collection('losses', tf.contrib.layers.l2_regularizer(regularizer)(w)) return wdef get_bias(shape): b = tf.Variable(tf.zeros(shape)) return bdef conv2d(x,w): return tf.nn.conv2d(x, w, strides=[1, 1, 1, 1], padding='SAME')def max_pool_8x8(x): return tf.nn.max_pool(x, ksize=[1, 8, 8, 1], strides=[1, 4, 4, 1], padding='SAME')def max_pool_4x4(x): return tf.nn.max_pool(x, ksize=[1, 4, 4, 1], strides=[1, 2, 2, 1], padding='SAME')def forward(x, train, regularizer):conv1_w = get_weight([CONV1_SIZE, CONV1_SIZE, NUM_CHANNELS, CONV1_KERNEL_NUM], regularizer) conv1_b = get_bias([CONV1_KERNEL_NUM]) conv1 = conv2d(x, conv1_w) relu1 = tf.nn.relu(tf.nn.bias_add(conv1, conv1_b)) pool1 = max_pool_8x8(relu1) conv2_w = get_weight([CONV2_SIZE, CONV2_SIZE, CONV1_KERNEL_NUM, CONV2_KERNEL_NUM],regularizer) conv2_b = get_bias([CONV2_KERNEL_NUM])conv2 = conv2d(pool1, conv2_w) relu2 = tf.nn.relu(tf.nn.bias_add(conv2, conv2_b))pool2 = max_pool_4x4(relu2)pool_shape = pool2.get_shape().as_list() nodes = pool_shape[1] * pool_shape[2] * pool_shape[3] reshaped = tf.reshape(pool2, [pool_shape[0], nodes]) fc1_w = get_weight([nodes, FC_SIZE], regularizer) fc1_b = get_bias([FC_SIZE]) fc1 = tf.nn.relu(tf.matmul(reshaped, fc1_w) + fc1_b) if train: fc1 = tf.nn.dropout(fc1, 0.5)fc2_w = get_weight([FC_SIZE, OUTPUT_NODE], regularizer)fc2_b = get_bias([OUTPUT_NODE])y = tf.matmul(fc1, fc2_w) + fc2_breturn y import tensorflow as tfimport numpy as npimport gesture_forwardimport gesture_backwardfrom image_processing import func5,func6import cv2def restore_model(testPicArr):with tf.Graph().as_default() as tg:x = tf.placeholder(tf.float32,[1,gesture_forward.IMAGE_SIZE,gesture_forward.IMAGE_SIZE,gesture_forward.NUM_CHANNELS]) #y_ = tf.placeholder(tf.float32, [None, mnist_lenet5_forward.OUTPUT_NODE])y = gesture_forward.forward(x,False,None)preValue = tf.argmax(y, 1)variable_averages = tf.train.ExponentialMovingAverage(gesture_backward.MOVING_AVERAGE_DECAY)variables_to_restore = variable_averages.variables_to_restore()saver = tf.train.Saver(variables_to_restore)with tf.Session() as sess:ckpt = tf.train.get_checkpoint_state(gesture_backward.MODEL_SAVE_PATH)if ckpt and ckpt.model_checkpoint_path:saver.restore(sess, ckpt.model_checkpoint_path)#global_step = ckpt.model_checkpoint_path.split('/')[-1].split('-')[-1] preValue = sess.run(preValue, feed_dict={x:testPicArr})return preValueelse:print("No checkpoint file found")return -1def application01():testNum = input("input the number of test pictures:")testNum = int(testNum)for i in range(testNum):testPic = input("the path of test picture:")img = func5(testPic)cv2.imwrite(str(i)+'ttt.jpg',img) # cv2.waitKey(0)# cv2.destroyAllWindows()img = img.reshape([1,100,100,1])img = img.astype(np.float32)img = np.multiply(img, 1.0/255.0)# print(img.shape)# print(type(img)) preValue = restore_model(img)print ("The prediction number is:", preValue)def application02():#vc = cv2.VideoCapture('testVideo.mp4')vc = cv2.VideoCapture(0)# 设置每秒传输帧数fps = vc.get(cv2.CAP_PROP_FPS)# 获取视频的大小size = (int(vc.get(cv2.CAP_PROP_FRAME_WIDTH)),int(vc.get(cv2.CAP_PROP_FRAME_HEIGHT)))# 生成一个空的视频文件# 视频编码类型# cv2.VideoWriter_fourcc('X','V','I','D') MPEG-4 编码类型# cv2.VideoWriter_fourcc('I','4','2','0') YUY编码类型# cv2.VideoWriter_fourcc('P','I','M','I') MPEG-1 编码类型# cv2.VideoWriter_fourcc('T','H','E','O') Ogg Vorbis类型,文件名为.ogv# cv2.VideoWriter_fourcc('F','L','V','1') Flask视频,文件名为.flv#vw = cv2.VideoWriter('ges_pro.avi',cv2.VideoWriter_fourcc('X','V','I','D'), fps, size)# 读取视频第一帧的内容success, frame = vc.read()# rows = frame.shape[0] # cols = frame.shape[1]# t1 = int((cols-rows)/2)# t2 = int(cols-t1)# M = cv2.getRotationMatrix2D((cols/2,rows/2),90,1)# frame = cv2.warpAffine(frame,M,(cols,rows))# frame = frame[0:rows, t1:t2]# cv2.imshow('sd',frame)# cv2.waitKey(0)# cv2.destroyAllWindows()while success:#90度旋转 # img = cv2.warpAffine(frame,M,(cols,rows))# img = img[0:rows, t1:t2]img = func6(frame)img = img.reshape([1,100,100,1])img = img.astype(np.float32)img = np.multiply(img, 1.0/255.0)preValue = restore_model(img)# 写入视频cv2.putText(frame,"Gesture:"+str(preValue),(50,50),cv2.FONT_HERSHEY_PLAIN,2.0,(0,0,255),1)#vw.write(frame)cv2.imshow('gesture',frame)if cv2.waitKey(1) & 0xFF == ord('q'):break# 读取视频下一帧的内容success, frame = vc.read()vc.release()cv2.destroyAllWindows() print('viedo app over!')def main():#application01()application02()if __name__ == '__main__':main()
4 最后
🧿 更多资料, 项目分享:
https://gitee.com/dancheng-senior/postgraduate
相关文章:

竞赛 题目:基于机器视觉opencv的手势检测 手势识别 算法 - 深度学习 卷积神经网络 opencv python
文章目录 1 简介2 传统机器视觉的手势检测2.1 轮廓检测法2.2 算法结果2.3 整体代码实现2.3.1 算法流程 3 深度学习方法做手势识别3.1 经典的卷积神经网络3.2 YOLO系列3.3 SSD3.4 实现步骤3.4.1 数据集3.4.2 图像预处理3.4.3 构建卷积神经网络结构3.4.4 实验训练过程及结果 3.5 …...

11. Spring源码篇之实例化前的后置处理器
简介 spring在创建Bean的过程中,提供了很多个生命周期,实例化前就是比较早的一个生命周期,顾名思义就是在Bean被实例化之前的处理,这个时候还没实例化,只能拿到该Bean的Class对象,如果在这个时候直接返回一…...

Python-Python高阶技巧:HTTP协议、静态Web服务器程序开发、循环接收客户端的连接请求
版本说明 当前版本号[20231114]。 版本修改说明20231114初版 目录 文章目录 版本说明目录HTTP协议1、网址1.1 网址的概念1.2 URL的组成1.3 知识要点 2、HTTP协议的介绍2.1 HTTP协议的概念及作用2.2 HTTP协议的概念及作用2.3 浏览器访问Web服务器的过程 3、HTTP请求报文3.1 H…...

P1304 哥德巴赫猜想
题目描述 输入一个偶数 N,验证 4∼N 所有偶数是否符合哥德巴赫猜想:任一大于 22 的偶数都可写成两个质数之和。如果一个数不止一种分法,则输出第一个加数相比其他分法最小的方案。例如 1010,10=3+7=5+510=3+7=5+5,则 10=5+510=5+5 是错误答案。 输入格式 第一行输入一个…...

CSDN每日一题学习训练——Python版(搜索插入位置、最大子序和)
版本说明 当前版本号[20231118]。 版本修改说明20231118初版 目录 文章目录 版本说明目录搜索插入位置题目解题思路代码思路参考代码 最大子序和题目解题思路代码思路参考代码 搜索插入位置 题目 给定一个排序数组和一个目标值,在数组中找到目标值,…...

Java在物联网中的重要性
【点我-这里送书】 本人详解 作者:王文峰,参加过 CSDN 2020年度博客之星,《Java王大师王天师》 公众号:JAVA开发王大师,专注于天道酬勤的 Java 开发问题中国国学、传统文化和代码爱好者的程序人生,期待你的关注和支持!本人外号:神秘小峯 山峯 转载说明:务必注明来源(…...

动态规划解背包问题
题目 题解 def knapsac(W: int, N: int, wt: List[int], val: List[int]) -> int:# 定义状态动作价值函数: dp[i][j],对于前i个物品,当前背包容量为j,最大的可装载价值dp [[0 for j in range(W1)] for i in range(N1)]# 状态动作转移for…...

PCL内置点云类型
PCL内置了许多点云类型供我们使用,下面先介绍PLC内置的点云数据类型 PCL中的点云类型为PointT;至于为什么是PointT类型需要追随到原来的ros开发中去,因为PCL库也是从原来的ROS中剥离出来的;大家都一致的认为点云结构是离散的N维信…...

clickhouse数据结构和常用数据操作
背景, 大数据中查询用mysql时间太长, 使用clickhouse 速度快, 数据写入mysql后同步到clickhouse中 测试1千万数据模糊搜索 mysql 需要30-40秒 clickhouse 约 100ms 一 数据结构和存储引擎 1 查看clickhouse所有数据类型 select * from system.data_type_families; 2 …...

upload-labs关卡9(基于win特性data流绕过)通关思路
文章目录 前言一、靶场需要了解的知识1::$data是什么 二、靶场第九关通关思路1、看源码2、bp抓包修改后缀名3、检查是否成功上传 总结 前言 此文章只用于学习和反思巩固文件上传漏洞知识,禁止用于做非法攻击。注意靶场是可以练习的平台,不能随意去尚未授…...

C++过河卒问题
#include <iostream> #include <cstring> using namespace std;int board[20][20]; // 棋盘 int dp[20][20][20][20]; // 动态规划数组int main() {int x0, y0, x1, y1;cin >> x0 >> y0 >> x1 >> y1; // 输入卒的起点和终点memset(board,…...

【机器学习12】集成学习
1 集成学习分类 1.1 Boosting 训练基分类器时采用串行的方式, 各个基分类器之间有依赖。每一层在训练的时候, 对前一层基分类器分错的样本, 给予更高的权重。 测试时, 根据各层分类器的结果的加权得到最终结果。 1.2 Bagging …...

nodeJs基础笔记
title: nodeJs基础笔记 date: 2023-11-18 22:33:54 tags: 1. Buffer 1. 概念 Buffer 是一个类似于数组的 对象 ,用于表示固定长度的字节序列。 Buffer 本质是一段内存空间,专门用来处理 二进制数据 。 2. 特点 Buffer 大小固定且无法调整Buffer 性能…...

Skywalking流程分析_9(JDK类库中增强流程)
前言 之前的文章详细介绍了关于非JDK类库的静态方法、构造方法、实例方法的增强拦截流程,本文会详细分析JDK类库中的类是如何被增强拦截的 回到最开始的SkyWalkingAgent#premain try {/** 里面有个重点逻辑 把一些类注入到Boostrap类加载器中 为了解决Bootstrap类…...

矩阵的QR分解
矩阵的QR分解 GramSchmidt 设存在 B { x 1 , x 2 , … , x n } \mathcal{B}\left\{\mathbf{x}_{1},\mathbf{x}_{2},\ldots,\mathbf{x}_{n}\right\} B{x1,x2,…,xn}在施密特正交化过程中 q 1 x 1 ∣ ∣ x 1 ∣ ∣ q_1\frac{x_1}{||x_1||} q1∣∣x1∣∣x1 q k …...

STL总结
STL vector 头文件<vector> 初始化,定义,定义长度,定义长度并且赋值,从数组中获取数据返回元素个数size()判断是否为空empty()返回第一个元素front()返回最后一个数back()删除最后一个数pop_back()插入push_back(x)清空clear()begin()end()使用s…...

资深测试总结,现在软件测试有未来吗?“你“的底气在哪里?
目录:导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结(尾部小惊喜) 前言 1、为什么会有 “…...

Scalable Exact Inference in Multi-Output Gaussian Processes
Orthogonal Instantaneous Linear Mixing Model TY are m-dimensional summaries,ILMM means ‘Instantaneous Linear Mixing Model’,OILMM means ‘Orthogonal Instantaneous Linear Mixing Model’ 辅助信息 作者未提供代码...

sqli-labs(Less-3)
1. 通过构造id1’ 和id1’) 和id1’)–确定存在注入 可知原始url为 id(‘1’) 2.使用order by 语句猜字段数 http://127.0.0.1/sqlilabs/Less-3/?id1) order by 4 -- http://127.0.0.1/sqlilabs/Less-3/?id1) order by 3 --3. 使用联合查询union select http://127.0.0.1…...

集合框架面试题
一、集合容器的概述 1. 什么是集合 集合框架:用于存储数据的容器。 集合框架是为表示和操作集合而规定的一种统一的标准的体系结构。 任何集合框架都包含三大块内容: 对外的接口、接口的实现和对集合运算的算 法。 接口:表示集合的抽象数据…...

【LeetCode刷题日志】225.用队列实现栈
🎈个人主页:库库的里昂 🎐C/C领域新星创作者 🎉欢迎 👍点赞✍评论⭐收藏✨收录专栏:LeetCode 刷题日志🤝希望作者的文章能对你有所帮助,有不足的地方请在评论区留言指正,…...

【JavaScript】fetch 处理流式数据,实现类 chatgpt 对话
本文只包含最基础的请求后端大佬给得对话接口,大部分模型的传参是差不多的,核心还是如何处理 fetch 获取的流数据 import { defineStore } from pinia; import { ElMessage } from element-plus;type Role system | user | assistant; export interfac…...

收发电子邮件
电子邮件是Internet提供的又一个重要服务项目。早在1987年9月20日,中国首封电子邮件就是从北京经意大利向前联邦德国卡尔斯鲁厄大学发出的,在中国首次实现了与Internet的连接,使中国成为国际互联网大家庭中的一员。现在随着Internet的迅速发展…...

sql13(Leetcode570至少有5名直接下属的经理)
代码: 脑子记不住 语法全靠试.. # Write your MySQL query statement below select b.name from (select managerId,count(managerId) as numfrom Employeegroup by managerId ) a left join Employee b on a.managerIdb.id where a.num>5 and b.name is not N…...

15分钟,不,用模板做数据可视化只需5分钟
测试显示,一个对奥威BI软件不太熟悉的人来开发数据可视化报表,要15分钟,而当这个人去套用数据可视化模板做报表,只需5分钟! 数据可视化模板是奥威BI上的一个特色功能板块。用户下载后更新数据源,立即就能获…...

C 语言字符串函数
C 语言字符串函数 在本文中,您将学习使用诸如gets(),puts,strlen()等库函数在C中操作字符串。您将学习从用户那里获取字符串并对该字符串执行操作。 您通常需要根据问题的需要来操作字符串。大多数字符串操作都可以自定义方法完成ÿ…...

nvm安装详细教程(卸载旧的nodejs,安装nvm、node、npm、cnpm、yarn及环境变量配置)
文章目录 一、完全卸载旧的nodejs1、打开系统的控制面板,点击卸载程序,卸载nodejs(1)打开系统的控制面板,点击程序下的卸载程序(2)找到node.js,鼠标右击出现下拉框,点卸载…...

详细步骤记录:持续集成Jenkins自动化部署一个Maven项目
Jenkins自动化部署 提示:本教程基于CentOS Linux 7系统下进行 Jenkins的安装 1. 下载安装jdk11 官网下载地址:https://www.oracle.com/cn/java/technologies/javase/jdk11-archive-downloads.html 本文档教程选择的是jdk-11.0.20_linux-x64_bin.tar.g…...

Python学习(一)基础语法
文章目录 1. 入门1.1 解释器的作用1.2 下载1.3 基础语法输入输出语法与引号注释:变量: 数据类型与四则运算数据类型四则运算数据类型的查看type()数据类型的转换int()、int()、float() 流程控制格式化输出循环与遍历逻辑运算符list遍历字典dict遍历 跳出…...

【C刷题】day7
🎥 个人主页:深鱼~🔥收录专栏:【C】每日一练🌄欢迎 👍点赞✍评论⭐收藏 一、选择题 1、以下对C语言函数的有关描述中,正确的有【多选】( ) A: 在C语言中,一…...