OpenCV+python实现摄像头简单手势识别--进度条控制亮度
Posted 笑口常锴
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了OpenCV+python实现摄像头简单手势识别--进度条控制亮度相关的知识,希望对你有一定的参考价值。
文章目录
前言
例如:随着人工智能的不断发展,计算机视觉这门技术也越来越重要,很多人都开启了学习计算机视觉,本文在Opencv基础上实现了摄像头简单手势识别–进度条控制亮度的基础内容,并没有使用深度学习技术,因此准确率并不高。
一、整体框架
∙ \\bullet ∙第一步: 开启摄像头,检测每帧图片;
∙ \\bullet ∙第二步: 设置回调函数,操纵滑动条来调整亮度;
∙ \\bullet ∙第三步: 肤色检测,基于HSV颜色空间H,S,V范围筛选法。在HSV中 7<H<20,28<S<256,50<V<256;
∙ \\bullet ∙第四步: 进行高斯滤波;
∙ \\bullet ∙第五步: 边缘轮廓检测;
∙ \\bullet ∙第六步: 求出手势的凹凸点;
∙ \\bullet ∙第七步: 利用凹凸点个数判断当前手势。例如:0个凹凸点就是拳头,4个凹点就是布。
二、使用步骤;
1.引入库;
import cv2
import numpy as np
import math
2.第一步:打开摄像头;
代码如下(示例):
cap = cv2.VideoCapture(0)
while (cap.isOpened()):
ret, frame = cap.read() # 读取摄像头每帧图片
frame = cv2.flip(frame, 1) #镜像调整,将图像左右调换回来正常显示
3.第二步:设置回调函数;
def callback(object): #注意这里createTrackbar会向其传入参数即滑动条地址(几乎用不到),所以必须写一个参数
pass
cv2.createTrackbar("change", "frame", 100, 255, callback)
4.第三步:肤色检测;
# 基于hsv的肤色检测,通过HSV颜色空间来筛选所需要的像素
hsv = cv2.cvtColor(roi, cv2.COLOR_BGR2HSV)
lower_skin = np.array([0, 28, 70], dtype=np.uint8)
upper_skin = np.array([20, 255, 255], dtype=np.uint8)
5.第四步:进行高斯滤波;
# 进行高斯滤波,降低噪声的影响
mask = cv2.inRange(hsv, lower_skin, upper_skin)
mask = cv2.dilate(mask, kernel, iterations=4)
mask = cv2.GaussianBlur(mask, (5, 5), 100)
6.第五步:边缘轮廓检测;
# 找出轮廓,确定手势范围
contours, h = cv2.findContours(
mask, cv2.RETR_TREE,
cv2.CHAIN_APPROX_SIMPLE) #opencv中提供findContours()函数来寻找图像中物体的轮廓
cnt = max(contours, key=lambda x: cv2.contourArea(x))
epsilon = 0.0005 * cv2.arcLength(cnt, True)
approx = cv2.approxPolyDP(cnt, epsilon, True)
hull = cv2.convexHull(cnt)
areahull = cv2.contourArea(hull)
areacnt = cv2.contourArea(cnt)
arearatio = ((areahull - areacnt) / areacnt) * 100
7.第六步:求出手势的凹凸点;
# 求出凹凸点
hull = cv2.convexHull(approx, returnPoints=False) #convexHull能很方便的用于求多边形凸包
defects = cv2.convexityDefects(approx, hull) #使用convexityDefects计算轮廓凸缺陷
8.第七步: 利用凹凸点个数判断当前手势;
# 定义凹凸点个数初始值为0
l = 0
for i in range(defects.shape[0]):
s, e, f, d, = defects[i, 0]
start = tuple(approx[s][0])
end = tuple(approx[e][0])
far = tuple(approx[f][0])
pt = (100, 100)
a = math.sqrt((end[0] - start[0])**2 + (end[1] - start[1])**2)
b = math.sqrt((far[0] - start[0])**2 + (far[1] - start[1])**2)
c = math.sqrt((end[0] - far[0])**2 + (end[1] - far[1])**2)
s = (a + b + c) / 2
ar = math.sqrt(s * (s - a) * (s - b) * (s - c))
# 手指间角度求取
angle = math.acos((b**2 + c**2 - a**2) / (2 * b * c)) * 57
if angle <= 90 and d > 20:
l += 1
cv2.circle(roi, far, 3, [255, 0, 0], -1)
cv2.line(roi, start, end, [0, 255, 0], 2) # 画出包络线
l += 1
font = cv2.FONT_HERSHEY_SIMPLEX
成果展示
可通过滑动条来调整亮度,提高识别率。
由于肤色检测的时候是用色调来提取特征,因此会被黄色调的影响,因此准确率并不算高,只能实现基本的功能,要想准确率高还得上深度学习算法。
完整代码
# -*- coding: utf-8 -*-
"""
Created on Thu Apr 7 18:42:02 2022
@author: He Zekai
"""
import cv2
import numpy as np
import math
def callback(object): #注意这里createTrackbar会向其传入参数即滑动条地址(几乎用不到),所以必须写一个参数
pass
cap = cv2.VideoCapture(0)
cv2.namedWindow('frame')
cv2.resizeWindow('frame',600,800)
cv2.createTrackbar("change", "frame", 100, 255, callback)
while(cap.isOpened()):
ret,image = cap.read() # 读取摄像头每帧图片
image = cv2.flip(image,1)
cv2.rectangle(image,(100,100),(300,300),(0,0,255),0) # 用红线画出手势识别框
#滑动条控制颜色
value = cv2.getTrackbarPos('change', 'frame')
image_dst = np.uint8(image/100*value)
roi = image_dst[100:300,100:300]# 选取图片中固定位置作为手势输入
kernel = np.ones((2,2),np.uint8)
# 进行高斯滤波
lower_skin = np.array([0,28,70],dtype=np.uint8)
upper_skin = np.array([20, 255, 255],dtype=np.uint8)
mask = cv2.inRange(roi,lower_skin,upper_skin)
mask = cv2.dilate(mask,kernel,iterations=4)
mask = cv2.GaussianBlur(mask,(3,3),100)
# 基于hsv的肤色检测
hsv = cv2.cvtColor(roi,cv2.COLOR_BGR2HSV)
# 进行高斯滤波
mask = cv2.inRange(hsv,lower_skin,upper_skin)
mask = cv2.dilate(mask,kernel,iterations=4)
mask = cv2.GaussianBlur(mask,(5,5),100)
# 找出轮廓
contours,h = cv2.findContours(mask,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
cnt = max(contours,default=0,key=lambda x:cv2.contourArea(x))
epsilon = 0.0005*cv2.arcLength(cnt,True)
approx = cv2.approxPolyDP(cnt,0.05,True)
hull = cv2.convexHull(cnt)
areahull = cv2.contourArea(hull)
areacnt = cv2.contourArea(cnt)
arearatio = ((areahull-areacnt)/areacnt)*100
# 求出凹凸点
hull = cv2.convexHull(approx,returnPoints=False)
defects = cv2.convexityDefects(approx,hull)
# 定义凹凸点个数初始值为0
l=0
try:
for i in range(defects.shape[0]):
s,e,f,d, = defects[i,0]
start = tuple(approx[s][0])
end = tuple(approx[e][0])
far = tuple(approx[f][0])
pt = (100,100)
a = math.sqrt((end[0]-start[0])**2+(end[1]-start[1])**2)
b = math.sqrt((far[0] - start[0]) ** 2 + (far[1] - start[1]) ** 2)
c = math.sqrt((end[0]-far[0])**2+(end[1]-far[1])**2)
s = (a+b+c)/2
ar = math.sqrt(s*(s-a)*(s-b)*(s-c))
# 手指间角度求取
angle = math.acos((b**2 + c**2 -a**2)/(2*b*c))*57
if angle<=90 and d>20:
l+=1
cv2.circle(roi,far,3,[255,0,0],-1)
cv2.line(roi,start,end,[0,255,0],2) # 画出包络线
l+=1
font = cv2.FONT_HERSHEY_SIMPLEX
# 条件判断,知道手势后想实现的功能
if l==1:
if areacnt<2000:
cv2.putText(image_dst,"Please put hand in the window",(0,50),font,2,(0,0,255),3,cv2.LINE_AA)
else:
if arearatio<12:
cv2.putText(image_dst,'0',(0,50),font,2,(0,0,255),3,cv2.LINE_AA)
elif arearatio<17.5:
cv2.putText(image_dst,"1",(0,50),font,2,(0,0,255),3,cv2.LINE_AA)
else:
cv2.putText(image_dst,'1',(0,50),font,2,(0,0,255),3,cv2.LINE_AA)
elif l==2:
cv2.putText(image_dst,'2',(0,50),font,2,(0,0,255),3,cv2.LINE_AA)
elif l==3:
if arearatio<27:
cv2.putText(image_dst,'3',(0,50),font,2,(0,0,255),3,cv2.LINE_AA)
else:
cv2.putText(image_dst,'3',(0,50),font,2,(0,0,255),3,cv2.LINE_AA)
elif l==4:
cv2.putText(image_dst,'4',(0,50),font,2,(0,0,255),3,cv2.LINE_AA)
elif l==5:
cv2.putText(image_dst,'5',(0,50),font,2,(0,0,255),3,cv2.LINE_AA)
# cv2.imshow('frame',frame)
cv2.imshow('mask', mask)
cv2.imshow('frame', image_dst)
key = cv2.waitKey(25)& 0xFF
if key == ord('q'): # 键盘q键退出
break
except:
pass
cv2.destroyAllWindows()
cap.release()
总结
例如:本次基于摄像头的简单手势识别实验,让我对以往的知识掌握更加深刻。虽然现在手势识别都是通过深度学习中的CNN等实现的,但仅使用opencv的传统方法来实现这个功能对我来说还挺新颖的,让我对opencv更加充满了兴趣。
毕业设计 - 题目:基于机器视觉opencv的手势检测 手势识别 算法 - 深度学习 卷积神经网络 opencv python
文章目录
1 简介
今天学长向大家介绍一个机器视觉项目
基于机器视觉opencv的手势检测 手势识别 算法
毕设帮助,开题指导,技术解答
🇶746876041
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 <iostream>
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <opencv2/core/core.hpp>
#include "copenni.cpp"
#include <iostream>
#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矩阵声明为单通道就ok
for(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如何用Python实现简单人脸识别