基于cv2的手势识别-计算机视觉

news/2024/7/21 5:19:24 标签: 计算机视觉, opencv, 图像处理

  闲的无聊做的一个小玩意,可以调用你的计算机相机,识别框内的手势(剪刀、石头和布),提供一个判决平台,感兴趣的可以继续完善。
用到的参考小文献:
在这里插入图片描述

具体实现结果如下

在这里插入图片描述
在这里插入图片描述
并且我另写了一个框架平台,可以进行下一步的功能拓展,发在我的资源界面了;
在这里插入图片描述

手势识别与控制系统

简介

  我们系统的手势识别与控制功能主要采用 OpenCV库实现 , OpenCV是一个基于BSD许可(开源)发行的跨平台计算机视觉库, 可以运行在Linux, Windows, Android和Mac-OS操作系统上. 它轻量级而且高效—由一系列 C 函数和少量 C++类构成, 同时提供了Python, Ruby, MATLAB等语言的接口, 实现了图像处理计算机视觉方面的很多通用算法。
  本系统主要使用了OpenCV的视频采集, 图像色域转换, 颜色通道分割, 高斯滤波, OSTU自动阈值, 凸点检测, 边缘检测, 余弦定理计算手势等功能。

手势检测:#pic_center

水平翻转

设置ROI

   ROI是region of interest首字母的简写,翻译为感性趣的区域。ROI是在一定图片中取一小部分进行重点处理的区域。图片就是一个二维数组,把数组分块求取就可以得到你想要的ROI区域。

定义肤色范围

   人为定义RGB颜色范围,截取红黄蓝三原色上下限选出符合人肤色的区域。

提取肤色

  在ROI选区范围内提取出肤色转化为灰度图像
  在进行颜色空间转换时,RGD各个通道的范围应当根据实际  需要来进行归一化,如RGB转LUV通道时,需要将RGB归一化为32位浮点数,即各通道的值的变化范围为0到1,转化为HSV时也是这样。
  RGB空间下标(R、G、B)代表着相应的颜色空间,且R、G、B∈(0,1).OpenCV中从RGB颜色空间转换为HSV颜色空间按照下式进行:
V = m a x ( R , G , B ) V=max(R,G,B) V=max(R,G,B)
f ( x ) = { V − min ⁡ ( R , G , B ) V , i f V ≠ 0 0 , i f otherwise f(x)=\begin{cases}\dfrac{V-\min(R,G,B)}{V},&\quad if V\neq0\\ 0,&\quad if \textit{otherwise}\end{cases} f(x)= VVmin(R,G,B),0,ifV=0ifotherwise
H = { 60 ( G − B ) V − min ( R , G , B ) , i f V = R 120 + 60 ( B − R ) V − min ( R , G , B ) , i f V = G 250 + 60 ( R − G ) V − min ( R , G , B ) , i f V = B H=\begin{cases}\dfrac{60(G-B)}{V-\text{min}(R,G,B)},ifV=R\\ 120+\dfrac{60(B-R)}{V-\text{min}(R,G,B)},ifV=G\\ 250+\dfrac{60(R-G)}{V-\text{min}(R,G,B)},ifV=B\end{cases} H= Vmin(R,G,B)60(GB),ifV=R120+Vmin(R,G,B)60(BR),ifV=G250+Vmin(R,G,B)60(RG),ifV=B
HSV色表如下:
在这里插入图片描述

图形经过膨胀与腐蚀后进行高斯滤波模糊图像

实现原理
  图像的膨胀(Dilation)和腐蚀(Erosion)是两种基本的形态学运算,主要用来寻找图像中的极大区域和极小区域。其中膨胀类似于“领域扩张”,将图像中的高亮区域或白色部分进行扩张,其运行结果图比原图的高亮区域更大;腐蚀类似于“领域被蚕食”,将图像中的高亮区域或白色部分进行缩减细化,其运行结果图比原图的高亮区域更小。
  膨胀的运算符是”⊕”,其定义如下:
A ⊕ B = { x ∣ ( B ) x ∩ A ≠ Θ } \mathrm{A}\oplus\mathrm{B}=\left\{\mathrm{x}\left|\left(\mathrm{B}\right)_x\cap\mathrm{A}\neq\Theta\right\}\right. AB={x(B)xA=Θ}
  该公式表示用B来对图像A进行膨胀处理,其中B是一个卷积模板或卷积核,其形状可以为正方形或圆形,通过模板B与图像A进行卷积计算,扫描图像中的每一个像素点,用模板元素与二值图像元素做“与”运算,如果都为0,那么目标像素点为0,否则为1。从而计算B覆盖区域的像素点最大值,并用该值替换参考点的像素值实现膨胀。
图像腐蚀
  该公式表示图像A用卷积模板B来进行腐蚀处理,通过模板B与图像A进行卷积计算,得出B覆盖区域的像素点最小值,并用这个最小值来替代参考点的像素值。
  a. 图像二值化处理,将图像的灰度值根据阈值进行0,1处理得到的图像;
  b. 卷积核,对应信号处理中的高低频滤波器。常用numpy函数去设置,np.ones((m,n), np.uint8) 表示指定m*n的卷积核;
  c. 图像的膨胀或腐蚀,cv2.dilate(二值化图像, 卷积核, 迭代次数),完成图片的高斯滤波模糊。

轮廓检测和查找最大面积轮廓

  轮廓处理的话主要用到函数cv2.findContours和这两个函数的使用使用方法很容易搜到就不说了,这部分主要的问题是提取到的轮廓有很多个,但是我们只需要手的轮廓,所以我们要用sorted函数找到最大的轮廓。

估算轮廓然后绘制轮廓曲线

查找手指凸包

  凸包(Convex Hull)是一个计算几何(图形学)中的概念,它的严格的数学定义为:在一个向量空间V中,对于给定集合X,所有包含X的凸集的交集S被称为X的凸包。   
  在图像处理过程中,我们常常需要寻找图像中包围某个物体的凸包。凸包跟多边形逼近很像,只不过它是包围物体最外层的一个凸集,这个凸集是所有能包围这个物体的凸集的交集。
  在opencv中,通过函数convexHulll能很容易的得到一系列点的凸包,比如由点组成的轮廓,通过convexHull函数,我们就能得到轮廓的凸包。寻找图像的凸包,就能够实现手势识别的功能。
对如下手势进行凸包查找:
在这里插入图片描述

手指凹陷查找{求三角形三边长度、点和曲线凸处的距离、余弦定理、去噪、手指轮廓描述曲线}

  利用opencv提供的 convexityDefects 凹点检测函数检测图像凹陷的点, 然后利用, 然后根据凹陷点中的 (开始点, 结束点, 远点)的坐标, 利用余弦定理计算两根手指之间的夹角, 其必为锐角, 根据锐角的个数判别手势.
  其中,锐角个数为0 ,表示 手势是 拳头 或 一,
    锐角个数为0 ,表示 手势是 拳头 或 一,
    锐角个数为1 ,表示 手势是 剪刀
    锐角个数为2 ,表示 手势是 三,
    锐角个数为3 ,表示 手势是 四,
    锐角个数为4 ,表示 手势是 布
  具体原理如下:利用findContours检测图像中的轮廓, 其中返回值contours包含了图像中所有轮廓的坐标点,它们的值分别为起点,终点,最远的点,到最远点的近似距离。根据图像中凹凸点中的 (开始点, 结束点, 远点)的坐标, 利用余弦定理计算两根手指之间的夹角, 其必为锐角, 根据锐角的个数判别手势。

输出手势

划拳(三次训练):

  • 构造3*3的结构元素,得到ROI分析块
  • 将膨胀后的图像和腐蚀相减得到轮廓边(灰度图)
  • 将上面结果二进制化并进行反色
  • 对反色结果进行中值滤波
  • 计算手势面积
  • 设定一定的判定时间,等到倒计时结束的时候将取得的图片进行处理和判定,经过训练三次得到判定的结果。

代码实现过程

import cv2
import time
import os

def judge():
    # 构造一个3×3的结构元素
    # return 0 stone ,1 jiandao, 2 bu
    img = cv2.imread("wif.jpg", 0)

    element = cv2.getStructuringElement(cv2.MORPH_RECT, (11, 11))  # 矩形:MORPH_RECT  交叉形:MORPH_CROSS  椭圆形:MORPH_ELLIPSE

    dilate = cv2.dilate(img, element)
    erode = cv2.erode(img, element)

    # 将两幅图像相减获得边,第一个参数是膨胀后的图像,第二个参数是腐蚀后的图像
    result = cv2.absdiff(dilate, erode);

    # 上面得到的结果是灰度图,将其二值化以便更清楚的观察结果
    retval, result = cv2.threshold(result, 40, 255, cv2.THRESH_BINARY);

    # 反色,即对二值图每个像素取反 图像非运算的效果:一个二值图,将黑色转为白色,白色转为黑色。
    result = cv2.bitwise_not(result);

    # 非线性过滤——中值滤波
    result = cv2.medianBlur(result, 23)

    #  计算手势面积
    a = []
    posi = []
    width = []
    count = 0
    area = 0

    for i in range(result.shape[1]):

        for j in range(result.shape[0]):

            if (result[j][i] == 0):

                area += 1

    for i in range(result.shape[1]):

        if (result[5 * result.shape[0] // 16][i] == 0 and result[5 * result.shape[0] // 16][i - 1] != 0):

            count += 1

            width.append(0)

            posi.append(i)

        if (result[5 * result.shape[0] // 16][i] == 0):

            width[count - 1] += 1  # 如果在这里报错,是因为背景问题,请让手的背景尽量整洁

    print('the pic width is ', result.shape[1], '\n')

    for i in range(count):

        print('the ', i, 'th', ' ', 'is')

        print('width ', width[i])

        print('posi ', posi[i], '\n')

    print(count, '\n')

    print('area is ', area, '\n')

    cv2.line(result, (0, 5 * result.shape[0] // 16), (214, 5 * result.shape[0] // 16), (0, 0, 0))

    cv2.namedWindow("fcuk")

    cv2.imshow("fcuk", result)

    cv2.waitKey(0)

    # 判定时间
    width_length = 0

    width_jiandao = True

    for i in range(count):

        if width[i] > 45:

            return 2;

        if width[i] <= 20 or width[i] >= 40:

            width_jiandao = False

        width_length += width[i]

    if width_jiandao == True and count == 2:

        return 1;

    if (area < 8500):

        print('shi tou')

        return 0;

    print("width_leng", width_length)

    if (width_length < 35):

        # 这个时候说明照片是偏下的,所以需要重新测定。
        a = []
        posi = []
        width = []
        count = 0

        for i in range(result.shape[1]):

            if (result[11 * result.shape[0] // 16][i] == 0 and result[11 * result.shape[0] // 16][i - 1] != 0):

                count += 1

                width.append(0)

                posi.append(i)

            if (result[11 * result.shape[0] // 16][i] == 0):

                width[count - 1] += 1

    width_length = 0

    width_jiandao = True

    for i in range(count):

        if width[i] > 45:

            print('bu1')

            return 2;

        if width[i] <= 20 or width[i] >= 40:

            width_jiandao = False

        width_length += width[i]

    if width_jiandao == True and count == 2:

        return 1;

    if (area > 14000 or count >= 3):

        print('bu2')

        return 2;

    if (width_length < 110):

        print('jian dao')

        return 1;

    else:

        print('bu3')

        return 2;


def show():

    box = []

    box.append("石头")

    box.append("剪刀")

    box.append("布")

    capture = cv2.VideoCapture(0)

    cv2.namedWindow("camera", 1)

    start_time = time.time()

    while (1):

        ha, img = capture.read()

        end_time = time.time()

        cv2.rectangle(img, (426, 0), (640, 250), (0, 255, 0))

        # (426, 0), (640, 250), 分别代表左上角和右下角的两个坐标点 (0, 255, 0)分别代指B G R
        cv2.putText(img, str(int(5 - (end_time - start_time))), (100, 100), cv2.FONT_HERSHEY_TRIPLEX, 2, 255, 0)
        # cv2.putText(图片, 添加的文字,左上角坐标,字体,字体大小,颜色,字体粗细)
        # cv2.FONT_HERSHEY_SIMPLEX 字体效果 https://www.pianshen.com/article/2216678823/

        cv2.imshow("camera", img)

        if (end_time - start_time > 5):

            break

        if (cv2.waitKey(30) >= 0):

            break

    ha, img = capture.read()

    capture.release()

    cv2.imshow("camera", img)

    img = img[0:210, 426:640]

    cv2.imwrite("wif.jpg", img)

    p1 = judge()

    print('你出的是',box[p1],'\n')

    cv2.destroyAllWindows()

def main():

    i=0

    while (i<5):

        i=i+1
        #  清空屏幕
        os.system('cls')

        show()

main()

编写不易,求个点赞!!!!!!!
“你是谁?”

“一个看帖子的人。”

“看帖子不点赞啊?”

“你点赞吗?”

“当然点了。”

“我也会点。”

“谁会把经验写在帖子里。”

“写在帖子里的那能叫经验贴?”

“上流!”
cheer!!!

在这里插入图片描述


http://www.niftyadmin.cn/n/268167.html

相关文章

==、equals区别 | java学习笔记

做一些java基础知识的记录&#x1f4d5; java基本类型&#xff1a;byte short int long float double char boolean&#xff08;指向具体的数值&#xff09; java引用类型&#xff1a;类 接口 数组等。指向的不是具体的数值&#xff0c;而是指向了对象的地址。 用于判断基本类…

软件开发需求类术语

主版本号 当功能模块有较大的变动&#xff0c;比如增加多个模块而发生变化。此版本号用来表示产品功能的主要增强。 次版本号 相对于主版本号而言&#xff0c;次版本号的升级对应的只是局部的变动&#xff0c;比如局部bug修正&#xff0c;或者是功能上有一定的改进或增强。SS…

一篇简单的文章带你玩转SpringBoot 之定时任务详解

序言 使用SpringBoot创建定时任务非常简单&#xff0c;目前主要有以下三种创建方式&#xff1a; 一、基于注解(Scheduled)二、基于接口&#xff08;SchedulingConfigurer&#xff09; 前者相信大家都很熟悉&#xff0c;但是实际使用中我们往往想从数据库中读取指定时间来动态…

【C++初阶9-list实现】封装——我主沉浮,何不能至?

使用 翻阅文档即可。 是什么 带头双向循环链表。 实现 1. 类成员设计 template <class T> struct list_node {T _val;list_node* _next;list_node* _prev; };template <class T> class list { private:list_node<T> _head; };2. 类方法设计 namespace…

springboot整合flowable的简单使用

内容来自网络整理&#xff0c;文章最下有引用地址&#xff0c;可跳转至相关资源页面。若有侵权请联系删除 环境&#xff1a; mysql5.7.2 springboot 2.3.9.RELEASE flowable 6.7.2 采坑&#xff1a; 1.当前flowable sql需要与引用的pom依赖一致&#xff0c;否则会报library…

centos7安装nginx及uwsgi部署django项目

1、安装配置uwsgi pip install uwsgi 2、在项目根目录下创建image_ocr_uwsgi.ini配置文件 [uwsgi] # 对外提供http服务的端口 http :9000 # 用于和nginx进行数据交互的端口 socket 127.0.0.1:8001 # django程序的主目录 chdir /home/image_process/image_ocr/image_ocr #…

ADAS辅助驾驶之:BSD盲区监测功能

摘要&#xff1a; 盲点监测系统从技术上主要分为影像和雷达2种&#xff0c;2种技术路线各有优劣。 目录 1、车辆盲区监测系统的定义 2、车辆盲区监测系统原理 3、车辆盲区监测系统硬件安装及标定 4、车辆盲区监测系统发展 1、车辆盲区监测系统的定义 盲区监测系统&#x…

猿辅导学子入选IMO中国国家队,奥数天才的炼成源于高效学习

日前&#xff0c;2023年第64届数学奥赛IMO中国国家队名单公布&#xff0c;全国共有6人入选。其中&#xff0c;上海共有两名高中生进入国家队名单&#xff0c;均来自上海中学。 值得一提的是&#xff0c;入选的成员中&#xff0c;其中两位学员是猿辅导的学员。一位是仅仅高一&a…