基于计算机视觉的手势识别技术

一个不知名大学生,江湖人称菜狗
original author: Jacky Li
Email : 3435673055@qq.com

Time of completion:2023.5.2
Last edited: 2023.5.2

手语是一种主要由听力困难或耳聋的人使用的交流方式。这种基于手势的语言可以让人们轻松地表达想法和想法,克服听力问题带来的障碍。

这种便捷的交流方式的一个主要问题是,全球绝大多数人缺乏语言知识。就像其他语言一样,学习手语需要花费大量时间和精力,这让人很沮丧,无法被更多的人学习。

然而,在机器学习和图像检测领域,这一问题的一个明显解决方案已经存在。实现预测模型技术来自动分类手语符号可以用于为Zoom会议等虚拟会议创建实时字幕。

这将大大增加听力障碍者获得此类服务的机会,因为它将与基于语音的字幕同步,为听力障碍者创建一个双向在线通信系统。


许多手语的大型训练数据集都可以在Kaggle上找到,Kaggle是一个流行的数据科学资源。该模型中使用的一个被称为“手语MNIST”,是一个公共领域,可免费使用的数据集,其中包含24个ASL字母中每一个的大约1000张图像的像素信息,不包括J和Z,因为它们是基于手势的符号。

Sign Language MNIST | KaggleDrop-In Replacement for MNIST for Hand Gesture Recognition Taskshttps://www.kaggle.com/datasets/datamunge/sign-language-mnist

准备用于训练的数据的第一步是将数据集中的所有像素数据转换并整形为图像,以便算法可以读取这些数据。

import matplotlib.pyplot as plt
import seaborn as sns
from keras.models import Sequential
from keras.layers import Dense, Conv2D , MaxPool2D , Flatten , Dropout , BatchNormalization
from keras.preprocessing.image import ImageDataGenerator
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report,confusion_matrix
import pandas as pd

train_df = pd.read_csv("sign_mnist_train.csv")
test_df = pd.read_csv("sign_mnist_test.csv")

y_train = train_df['label']
y_test = test_df['label']
del train_df['label']
del test_df['label']

from sklearn.preprocessing import LabelBinarizer
label_binarizer = LabelBinarizer()
y_train = label_binarizer.fit_transform(y_train)
y_test = label_binarizer.fit_transform(y_test)

x_train = train_df.values
x_test = test_df.values

x_train = x_train / 255
x_test = x_test / 255

x_train = x_train.reshape(-1,28,28,1)
x_test = x_test.reshape(-1,28,28,1)

上面的代码从重塑所有MNIST训练图像文件开始,以便模型理解输入文件。除此之外,LabelBinarizer变量获取数据集中的类并将它们转换为二进制,这一过程大大加快了模型的训练。

下一步是创建数据生成器,以随机实现对数据的更改,增加训练示例的数量,并通过向不同实例添加噪声和变换使图像更真实。

datagen = ImageDataGenerator(
        featurewise_center=False,
        samplewise_center=False, 
        featurewise_std_normalization=False,
        samplewise_std_normalization=False,
        zca_whitening=False,
        rotation_range=10,
        zoom_range = 0.1, 
        width_shift_range=0.1,
        height_shift_range=0.1,
        horizontal_flip=False,
        vertical_flip=False)

datagen.fit(x_train)

在处理图像之后,必须编译CNN模型以识别数据中使用的所有类别的信息,即24个不同的图像组。还必须将数据的标准化添加到数据中,以较少的图像平衡类。

model = Sequential()
model.add(Conv2D(75 , (3,3) , strides = 1 , padding = 'same' , activation = 'relu' , input_shape = (28,28,1)))
model.add(BatchNormalization())
model.add(MaxPool2D((2,2) , strides = 2 , padding = 'same'))
model.add(Conv2D(50 , (3,3) , strides = 1 , padding = 'same' , activation = 'relu'))
model.add(Dropout(0.2))
model.add(BatchNormalization())
model.add(MaxPool2D((2,2) , strides = 2 , padding = 'same'))
model.add(Conv2D(25 , (3,3) , strides = 1 , padding = 'same' , activation = 'relu'))
model.add(BatchNormalization())
model.add(MaxPool2D((2,2) , strides = 2 , padding = 'same'))
model.add(Flatten())
model.add(Dense(units = 512 , activation = 'relu'))
model.add(Dropout(0.3))
model.add(Dense(units = 24 , activation = 'softmax'))

请注意,通过添加变量(如Conv2D模型)初始化算法,并将其浓缩为24个特征。我们还使用批处理技术让CNN更有效地处理数据。

最后,定义损失函数和度量,并将模型与数据相匹配

model.compile(optimizer = 'adam' , loss = 'categorical_crossentropy' , metrics = ['accuracy'])
model.summary()

history = model.fit(datagen.flow(x_train,y_train, batch_size = 128) ,epochs = 20 , validation_data = (x_test, y_test))

model.save('smnist.h5')

这段代码有很多需要解包的地方。让我们分几节来看。

第1行:

model.compile函数接受许多参数,其中三个参数显示在代码中。优化器和损失参数与下一行中的epoch语句一起工作,通过逐步改变数据的计算方法,有效地减少模型中的错误量。

除此之外,要优化的度量标准是精度函数,它确保模型在设定的epoch数之后具有可达到的最大精度。

第4行:

这里运行的函数将设计的模型与第一位代码中开发的图像数据中的数据相匹配。它还定义了模型为提高图像检测的准确性所必须的时期或迭代次数。这里还调用了验证集,以向模型引入测试方面。该模型使用该数据计算精度。

第5行:

在代码位中的所有语句中,model.save函数可能是这段代码中最重要的部分,因为它可以在实现模型时节省数小时的时间。

开发的模型准确地检测和分类手语符号,训练准确率约为95%。


现在,使用两个流行的实时视频处理库,即Mediapipe和OpenCV,我们可以获取网络摄像头输入,并在实时视频流上运行我们之前开发的模型。

首先,我们需要导入程序所需的包。

import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' 
import tensorflow as tf
import cv2
import mediapipe as mp
from keras.models import load_model
import numpy as np
import time

 

开始时运行的OS命令只会阻止Mediapipe使用的Tensorflow库发出不必要的警告。这使程序提供的未来输出更加清晰易懂。

在我们启动代码的主while循环之前,我们需要首先定义一些变量,例如保存的模型和OpenCV相机上的信息。

model = load_model('smnist.h5')

mphands = mp.solutions.hands
hands = mphands.Hands()
mp_drawing = mp.solutions.drawing_utils

cap = cv2.VideoCapture(0)
_, frame = cap.read()
h, w, c = frame.shape

analysisframe = ''
letterpred = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y']

这里设置的每个变量都分为四个类别之一。一开始的类别与我们在本文第一部分中训练的模型直接相关。

代码的第二和第三部分定义了运行和启动Mediapipe和OpenCV所需的变量。最终类别主要用于在检测到帧时分析帧,并创建用于图像模型提供的数据的交叉引用的字典。

该程序的下一部分是主while True循环,其中大部分程序都在该循环中运行。

while True:
    _, frame = cap.read()

    k = cv2.waitKey(1)
    if k%256 == 27:
        # ESC pressed
        print("Escape hit, closing...")
        break

    framergb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
    result = hands.process(framergb)
    hand_landmarks = result.multi_hand_landmarks
    if hand_landmarks:
        for handLMs in hand_landmarks:
            x_max = 0
            y_max = 0
            x_min = w
            y_min = h
            for lm in handLMs.landmark:
                x, y = int(lm.x * w), int(lm.y * h)
                if x > x_max:
                    x_max = x
                if x < x_min:
                    x_min = x
                if y > y_max:
                    y_max = y
                if y < y_min:
                    y_min = y
            y_min -= 20
            y_max += 20
            x_min -= 20
            x_max += 20
            cv2.rectangle(frame, (x_min, y_min), (x_max, y_max), (0, 255, 0), 2)
            mp_drawing.draw_landmarks(frame, handLMs, mphands.HAND_CONNECTIONS)
    cv2.imshow("Frame", frame)

cap.release()
cv2.destroyAllWindows()

该程序的这一部分从你的相机获取输入,并使用我们导入的图像处理库将设备的输入显示到计算机。这部分代码专注于从相机获取一般信息,并在新窗口中简单地显示出来。然而,使用Mediapipe库,我们可以检测手的主要标志,如手指和手掌,并在手周围创建一个边界框。

边界框的概念是所有形式的图像分类和分析的关键组成部分。该框允许模型直接聚焦于功能所需的图像部分。如果没有这一点,算法会在错误的位置找到模式,并可能导致错误的结果。

例如,在训练过程中,缺少边界框可能会导致模型将诸如时钟或椅子等图像的特征与标签相关联。这可能会导致程序注意到图像中的时钟,并仅根据时钟存在的事实来决定显示什么手语字符。

快完成了!该程序的倒数第二部分是根据提示捕获单个帧,并将其裁剪到边界框的尺寸。

while True:
    _, frame = cap.read()
    
    k = cv2.waitKey(1)
    if k%256 == 27:
        # ESC pressed
        print("Escape hit, closing...")
        break
    elif k%256 == 32:
        # SPACE pressed
        # SPACE pressed
        analysisframe = frame
        showframe = analysisframe
        cv2.imshow("Frame", showframe)
        framergbanalysis = cv2.cvtColor(analysisframe, cv2.COLOR_BGR2RGB)
        resultanalysis = hands.process(framergbanalysis)
        hand_landmarksanalysis = resultanalysis.multi_hand_landmarks
        if hand_landmarksanalysis:
            for handLMsanalysis in hand_landmarksanalysis:
                x_max = 0
                y_max = 0
                x_min = w
                y_min = h
                for lmanalysis in handLMsanalysis.landmark:
                    x, y = int(lmanalysis.x * w), int(lmanalysis.y * h)
                    if x > x_max:
                        x_max = x
                    if x < x_min:
                        x_min = x
                    if y > y_max:
                        y_max = y
                    if y < y_min:
                        y_min = y
                y_min -= 20
                y_max += 20
                x_min -= 20
                x_max += 20 

        analysisframe = cv2.cvtColor(analysisframe, cv2.COLOR_BGR2GRAY)
        analysisframe = analysisframe[y_min:y_max, x_min:x_max]
        analysisframe = cv2.resize(analysisframe,(28,28))


        nlist = []
        rows,cols = analysisframe.shape
        for i in range(rows):
            for j in range(cols):
                k = analysisframe[i,j]
                nlist.append(k)
        
        datan = pd.DataFrame(nlist).T
        colname = []
        for val in range(784):
            colname.append(val)
        datan.columns = colname

        pixeldata = datan.values
        pixeldata = pixeldata / 255
        pixeldata = pixeldata.reshape(-1,28,28,1)

此代码看起来与程序的最后一部分非常相似。这主要是因为两个部分中涉及生成边界框的过程是相同的。

然而,在代码的这个分析部分,我们使用OpenCV中的图像重塑功能将图像调整到边界框的尺寸,而不是在其周围创建一个视觉对象。

此外,我们还使用NumPy和OpenCV修改图像,使其具有与模型所训练的图像相同的特征。

我们还使用panda使用保存的图像中的像素数据创建一个数据帧,因此我们可以用与创建模型相同的方式规范数据。

最后,我们需要在处理后的图像上运行训练后的模型,并处理信息输出。

prediction = model.predict(pixeldata)
predarray = np.array(prediction[0])
letter_prediction_dict = {letterpred[i]: predarray[i] for i in range(len(letterpred))}
predarrayordered = sorted(predarray, reverse=True)
high1 = predarrayordered[0]
high2 = predarrayordered[1]
high3 = predarrayordered[2]
for key,value in letter_prediction_dict.items():
    if value==high1:
        print("Predicted Character 1: ", key)
        print('Confidence 1: ', 100*value)
    elif value==high2:
        print("Predicted Character 2: ", key)
        print('Confidence 2: ', 100*value)
    elif value==high3:
        print("Predicted Character 3: ", key)
        print('Confidence 3: ', 100*value)
time.sleep(5)

 

在代码的这一部分中有很多信息。我们将逐一剖析这部分代码。

前两条线描绘了手部图像是Keras的任何不同类别的预测概率。数据以2个张量的形式呈现,其中第一个张量包含概率信息。张量本质上是特征向量的集合,非常类似于数组。该模型产生的张量是一维的,允许它与线性代数库NumPy一起使用,以将信息解析成更为Python的形式。

从这里开始,我们使用变量letterpred下先前创建的类列表来创建一个字典,将张量的值与关键字进行匹配。这允许我们将每个字符的概率与其对应的类进行匹配。

在这一步之后,我们使用列表生成式对值从最高到最低进行排序。这样,我们就可以获取列表中的前几项,并将它们指定为与所示手语图像最接近的3个字符。

最后,我们使用for循环循环遍历字典中的所有键:值对,以将最高值与其对应的键相匹配,并输出每个字符的概率。

如图所示,该模型准确地预测了从相机中显示的角色。除了预测特征,该程序还显示了CNN Keras模型分类的可信度。


所开发的模型可以以各种方式实现,主要用途是用于视频通话(如Facetime)的字幕设备。要创建这样的应用程序,模型必须逐帧运行,预测显示的符号。

该程序允许通过使用Keras图像分析模型,从手语到英语进行简单易行的交流。

作者有言

如果需要代码,请私聊博主,博主看见回。
如果感觉博主讲的对您有用,请点个关注支持一下吧,将会对此类问题持续更新……

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/23660.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

雷达中的无源和有源的区别

常规雷达探测目标时&#xff0c;需要源源不断地发射无线电波&#xff0c;所以叫有源雷达( active radar)。有源雷达的优点是能自主搜索目标&#xff0c;因为它接收的是自己发射的电磁波&#xff0c;所以灵敏度高&#xff0c;分辨率好。但这种雷达易受目标的电磁干扰&#xff0c…

C语言进阶——字符函数和字符串函数(下)

在前面我们已经学习了strlen、strcpy、strcat、strcmp几个库函数&#xff0c;今天我们继续学习剩余的库函数。 上期链接&#xff1a; C语言进阶——字符函数和字符串函数&#xff08;上&#xff09;_wangjiushun的博客-CSDN博客 目录&#xff1a; 3、长度受限制的字符串函数…

不愧是字节出来的,太厉害了...

前段时间公司缺人&#xff0c;也面了许多测试&#xff0c;一开始瞄准的就是中级水准&#xff0c;当然也没指望能来大牛&#xff0c;提供的薪资在15-20k这个范围&#xff0c;来面试的人有很多&#xff0c;但是平均水平真的让人很失望。看了简历很多上面都是写有4年工作经验&…

文件包含的本质、预处理符号、# vs ##

何为头文件&#xff1f; 在C语言中&#xff0c;文件包含是一种常见的编程技术&#xff0c;它允许程序员在一个源文件中使用另一个源文件中的函数或变量。 文件包含通常使用#include预处理指令来实现。#include指令告诉预处理器将文件的内容插入到当前文件的指定位置中。 例如&a…

python学习-基础知识总结

&#xff08;一&#xff09;基础语法 1.1、注释 程序添加注释&#xff0c;可以用来解释程序某些部分的作用和功能&#xff0c;提高程序的可读性&#xff0c;注释有两种形式&#xff1a; 单行注释&#xff1a;#多行注释&#xff1a;单引号&#xff08;注释内容&#xff09;或双…

笔试强训 Day6

选择题 1.十进制变量i的值为100&#xff0c;那么八进制的变量i的值为&#xff08;&#xff09; A 146 B 148C 144 D 142 本题很简单&#xff1a;100除8&#xff0c;取余数&#xff0c;直到商为零&#xff0c;最后反向的串起余数即可 2.执行下面语句后的输出为&#xff08;&…

【Python从入门到进阶】21、爬虫相关概念介绍

接上篇《20、HTML页面结构的介绍》 上一篇我们正式进入了Python爬虫的实战教程&#xff0c;主要讲解了要爬取的HTML页面的结构。本篇我们来介绍爬虫的相关概念。 一、什么是互联网爬虫 如果我们把互联网比作一张大的蜘蛛网&#xff0c;那一台计算机上的数据便是蜘蛛网上的一个…

unity制作一款塔防游戏

文章目录 介绍寻路系统怪物生成器制作3种初级炮台、3种升级炮台设置炮台属性选择炮台&#xff0c;添加监听事件炮弹追踪攻击敌人拖动鼠标实现相机视角转换鼠标光标放在cube上变色文字动画 介绍 关键技术&#xff1a; 寻路系统 生成怪物算法 粒子系统 line renderer制作追踪射线…

通过Python的pdfplumber库提取pdf中表格数据

文章目录 前言一、pdfplumber库是什么&#xff1f;二、安装pdfplumber库三、查看pdfplumber库版本四、提取pdf中表格数据1.引入库2.定义pdf文件路径3.打开pdf文件4.获取pdf文件中的页数5.遍历每一页6.获取当前页内容7.提取表格数据8.输出表格数据9.效果 总结 前言 大家好&#…

Scala学习(九)---List集合

文章目录 1.List1.1 不可变List集合1.2 可变集合ListBuffer 1.List List集合默认为不可变集合&#xff0c;List集合在实例化的时候&#xff0c;无法通过new关键字进行实例化&#xff0c;只能通过伴生apply方法来对其进行实例化 1.1 不可变List集合 创建一个不可变list集合 …

HUD(抬头显示)的方案介绍

目录 一、基于DLP3030-Q1的HUD电路设计 二、DLP3030-Q1的介绍 三、DLP3030-Q1工作原理 四、DLPC120-Q1DMD 显示控制器 五、TMS320F2802332 位 MCU 六、 HUD显示实例 HUD主板实例 七、HUD的软件环境 一、基于DLP3030-Q1的HUD电路设计 本设计采用了DLP3030-Q1 芯片组&…

设计模式 -第1部分 避免浪费- 第1章 Flyweight 模式 - 共享对象避免浪费

第1部分 避免浪费 注&#xff1a;其内容主要来自于【日】-结城浩 著《图解设计模式》20章节 极力推荐大家阅读原著 第1章 Flyweight 模式 - 共享对象避免浪费 1.1 Flyweight 模式 Flyweight 的意思"轻量级"&#xff0c;其在英文中的原意指比赛中选手体重最轻等级的一…

【C语言】实现猜数字游戏——随机数

&#x1f6a9;纸上得来终觉浅&#xff0c; 绝知此事要躬行。 &#x1f31f;主页&#xff1a;June-Frost &#x1f680;专栏&#xff1a;C语言 该篇将对 选择与循环语句 进行运用&#xff0c;实现猜数字游戏。 需求&#xff1a;游戏后可以选择再次进行游戏&#xff0c;也可以选择…

「实在RPA·烟草数字员工」助力烟草行业数字化转型加速度

烟草行业作为烟草产业链上重要一环&#xff0c;外部连接烟草工业企业、零售客户、消费者&#xff0c;内部包含营销、专卖、烟叶、物流等诸多业务&#xff0c;信息系统众多&#xff0c;企业数据量庞大。因此&#xff0c;清楚地了解自身存在的痛点&#xff0c;找到适合自身业务需…

如何在华为OD机试中获得满分?Java实现【寻找峰值】一文详解!

✅创作者&#xff1a;陈书予 &#x1f389;个人主页&#xff1a;陈书予的个人主页 &#x1f341;陈书予的个人社区&#xff0c;欢迎你的加入: 陈书予的社区 &#x1f31f;专栏地址: Java华为OD机试真题&#xff08;2022&2023) 文章目录 1. 题目描述2. 输入描述3. 输出描述…

gitbook在centos上安装

1&#xff09;官网下载Node.js的Linux64位的二进制包:Download | Node.js 或者在线下载&#xff1a; wget https://nodejs.org/dist/v12.16.1/node-v12.16.1-linux-x64.tar.xz ​​2)到指定目录​解压 cd /opt/gitbook tar -xJf node-v12.16.1-linux-x64.tar.xz mv node-…

STM32采集传感器数据通过排序取稳定值

一、前言 在物联网、单片机开发中,经常需要采集各种传感器的数据。比如:温度、湿度、MQ2、MQ3、MQ4等等传感器数据。这些数据采集过程中可能有波动,偶尔不稳定,为了得到稳定的值,我们可以对数据多次采集,进行排序,去掉最大和最小的值,然后取平均值返回。 二、排序算法…

运维工程师面试总结(含答案)

运维工程师面试总结 原文链接&#xff1a;https://www.cuiliangblog.cn/detail/article/2 一、linux 1. linux系统启动流程 第一步&#xff1a;开机自检&#xff0c;加载BIOS第二步&#xff1a;读取&#xff2d;&#xff22;&#xff32;第三步&#xff1a;Boot Loader grub…

华为OD机试之真正的密码(Java源码)

真正的密码 题目描述 一行中输入一个字符串数组&#xff0c;如果其中一个字符串的所有以索引0开头的子串在数组中都有&#xff0c;那么这个字符串就是潜在密码在所有潜在密码中最长的是真正的密码&#xff0c;如果有多个长度相同的真正的密码&#xff0c;那么取字典序最大的为…

【ClickHouse】

文章目录 一、表引擎1、表引擎的作用2、TinyLog3、Memory4、MergeTree二、数据库引擎1、作用--跨种类交换数据2、示例 三、MergeTree引擎1、简单使用2、分区partition by3、主键primary key4、order by&#xff08;必填&#xff09; 一、表引擎 1、表引擎的作用 CK表引擎决定…