计算机视觉cv2入门之实时手势检测

        前边我们已经讲解了使用cv2进行图像预处理以及针对实时视频流文件的操作方法,这里我们通过实时手势检测这一案例来学习和实操一下。

大致思路

  1. 根据手势的种类以及指定手势图片数量来构建一个自己的手势图片数据集
  2. CNN模型训练手势图片数据集
  3. 使用训练好的模型进行实时预测

手势图片数据集的构建

        经典的手势图片数据集有很多,但是都比较大,下载费时且模型训练时间长,因此这里我决定自行采集手势图片来构建一个小型数据集。手势图片的获取方法比较简单,就是使用cv2.VideoCapture函数打开摄像头来进行采集。这里我把我的方法分享给大家。

采集手势图片

import cv2
import os
DATASET_DIR='GesturesPhotos'#保存所有待采集手势的图片的文件夹的路径
gesture_kinds=5#手势种类:单手可以是1-10,我这里是1-5
photo_num=10#图片数量
classes=list(range(1,gesture_kinds+1,1))#使用1-gesture_kinds来表示所有待预测类别
###############################################
gestures=photo_num//gesture_kinds*classes#photo_num//gesture_kinds=10//5=2,2*[1,2,3,4,5]=[1,2,3,4,5,1,2,3,4,5]
gestures.extend(classes[:photo_num%gesture_kinds])#photo_num%5=10%5=0,extend([:0])相当于extend([])
'''
经过这两步运算,gestures为长度与图片数量一致且由类别构成的列表
gestures主要用来标定每次采集的种类
比如,gesture_kinds=5,photo_num=7,手势种类为5,那么这7次要采集的顺序为[1,2,3,4,5,1,2]
'''
###############################################
os.makedirs(DATASET_DIR, exist_ok=True)#exist_ok=True可以避免二次采集时重建新文件夹
def capture_gestures(gesture:str,count:int):'''Args:gesture:每次采集的手势,要标记在视频中,防止忘记采集的手势是多少导致实际类别与真实采集结果不一致从而成为噪声!\ncount:用来命名每次保存的图片,这里直接用记录图片数量来命名\n'''cv2.namedWindow('Data Collection', cv2.WND_PROP_FULLSCREEN)cv2.setWindowProperty('Data Collection', cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN)cap=cv2.VideoCapture(0)print(f'采集手势{gesture}(按ESC保存并退出)') while True:ret,frame=cap.read()if not ret: breakroi=frame[160:440,50:250]#roi区域,可以自行修改cv2.rectangle(frame, (50,160),(250,440),(0,255,0), 2)#roi区域处绘制方框cv2.putText(frame,text=f'No.{count+1} Photo gesture {gesture}',org=(250,100),fontScale=2,thickness=5,color=(0,0,255),fontFace=1)cv2.imshow(f'Data Collection',frame)key=cv2.waitKey(1)if key==27:#按下ESC保存并退出img_path=f'{DATASET_DIR}/{count}.jpg'cv2.imwrite(img_path,roi)break cap.release()cv2.destroyAllWindows()
for i in range(len(gestures)):capture_gestures(gestures[i],i)

         运行上述代码后,便可以开始采集手势图片了,这里我使用上述代码总共采集了200张图片用于后续CNN模型的训练。 

说明

        采集时,将右手放置在视频中的绿色框内,尽可能的放置在中央,gesture后的数字表示当前要表示的手势种类。如果采集时出现错误,那么只需要删除掉原来的图片,自行指定新的类别(gesture)以及原来图片的编号,调用一次capture_gestures函数重新采集即可。

采集效果 

采集结果(0-199 40组1-5的手势图片)

        这里我没有对背景进行太多处理,如果有大佬愿意,可以尝试将采集到的图片的背景虚化,突出手掌主体。

 数据预处理

           这里的数据预处理主要就是将我们的图像数据划分训练集与测试集后转换为tensor类型的DataLoder。

#数据预处理
from torch.utils.data import Dataset, DataLoader
import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import transforms
class GestureDataset(Dataset):def __init__(self, data_dir=DATASET_DIR,gesture_kinds=gesture_kinds,transform=None):self.data_dir = data_dirself.transform = transformself.image_paths = []self.labels = []# 读取数据集for img_name in os.listdir(data_dir):if img_name.endswith('.jpg'):self.image_paths.append(os.path.join(data_dir, img_name))self.labels.append(int(img_name.split('.')[0])%gesture_kinds)#0-4对于1-5def __len__(self):return len(self.image_paths)def __getitem__(self, idx):img_path=self.image_paths[idx]image=cv2.imread(img_path)image=cv2.cvtColor(image, cv2.COLOR_BGR2RGB)  # 转换为RGBlabel=self.labels[idx]if self.transform:image=self.transform(image)return image, labeldef process_data(data_dir=DATASET_DIR, batch_size=4):# 数据预处理transform = transforms.Compose([transforms.ToPILImage(),transforms.Resize((64, 64)),transforms.ToTensor(),transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])])dataset=GestureDataset(data_dir, transform=transform)train_size=int(0.8 * len(dataset))test_size=len(dataset) - train_sizetrain_dataset, test_dataset = torch.utils.data.random_split(dataset, [train_size, test_size])train_loader=DataLoader(train_dataset, batch_size=batch_size, shuffle=True)test_loader=DataLoader(test_dataset, batch_size=batch_size, shuffle=False)return train_loader, test_loader

CNN模型训练

        考虑到我的数据集比较少且该分类问题比较简单,所以这里我的模型也没有太复杂只是使用了2层卷积操作。倘若你的数据集比较大,分类种类比较多,可以尝试使用一些其他的CNN模型,比如mobilenet,resnet等。

#CNN模型
class GestureCNN(nn.Module):def __init__(self, num_classes=5):super(GestureCNN, self).__init__()self.conv1=nn.Conv2d(3, 16, kernel_size=3, stride=1, padding=1)self.relu=nn.ReLU()self.maxpool=nn.MaxPool2d(kernel_size=2, stride=2)self.conv2=nn.Conv2d(16, 32, kernel_size=3, stride=1, padding=1)self.fc1=nn.Linear(32*16*16, 128)self.fc2=nn.Linear(128, num_classes)def forward(self, x):x=self.conv1(x)x=self.relu(x)x=self.maxpool(x)x=self.conv2(x)x=self.relu(x)x=self.maxpool(x)x=x.view(x.size(0), -1)x=self.fc1(x)x=self.relu(x)x=self.fc2(x)return xdef train_model(train_loader, test_loader, num_epochs=10):device=torch.device('cuda' if torch.cuda.is_available() else 'cpu')model=GestureCNN(num_classes=5).to(device)criterion=nn.CrossEntropyLoss()optimizer=optim.Adam(model.parameters(), lr=0.001)for epoch in range(num_epochs):model.train()running_loss=0.0correct=0total=0for images, labels in train_loader:images=images.to(device)labels=labels.to(device)optimizer.zero_grad()outputs=model(images)loss=criterion(outputs, labels)loss.backward()optimizer.step()running_loss+=loss.item()_, predicted=torch.max(outputs.data, 1)total+=labels.size(0)correct+=(predicted==labels).sum().item()train_loss = running_loss / len(train_loader)train_acc = 100 * correct / total# 测试集评估model.eval()test_correct = 0test_total = 0with torch.no_grad():for images, labels in test_loader:images=images.to(device)labels=labels.to(device)outputs=model(images)_, predicted=torch.max(outputs.data, 1)test_total+=labels.size(0)test_correct+=(predicted==labels).sum().item()test_acc=100*test_correct/test_totalprint(f'Epoch [{epoch+1}/{num_epochs}], 'f'Train Loss: {train_loss:.4f}, 'f'Train Acc: {train_acc:.2f}%, 'f'Test Acc: {test_acc:.2f}%')# 保存模型torch.save(model.state_dict(), 'gesture_cnn.pth')print('训练完成,模型已保存为 gesture_cnn.pth')return model

实时预测 

        实时预测的思路是:打开摄像头,获取实时视频流文件中的每一帧图片中的手势,使用训练好的模型预测并将结果标注在视频流文件的每一帧上。

#实时预测
def realtime_prediction(model_path='gesture_cnn.pth'):device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')#加载模型model = GestureCNN(num_classes=5).to(device)model.load_state_dict(torch.load(model_path))model.eval()#预处理transform=transforms.Compose([transforms.ToPILImage(),transforms.Resize((64, 64)),transforms.ToTensor(),transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])])cap=cv2.VideoCapture(0)cv2.namedWindow('Gesture Recognition', cv2.WND_PROP_FULLSCREEN)cv2.setWindowProperty('Gesture Recognition', cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN)CLASSES=gestureswith torch.no_grad():while True:ret, frame = cap.read()if not ret: break  # 手势检测区域roi = frame[160:440, 50:250]cv2.rectangle(frame, (50, 160), (250, 440), (0, 255, 0), 2)try:input_tensor = transform(cv2.cvtColor(roi, cv2.COLOR_BGR2RGB)).unsqueeze(0).to(device)output = model(input_tensor)_, pred=torch.max(output, 1)probabilities=torch.nn.functional.softmax(output[0], dim=0) confidence, pred=torch.max(probabilities, 0)confidence=confidence.item()*100 #转换为百分比confidence=round(confidence,2)cv2.putText(frame, f'Prediction: {CLASSES[pred.item()]}', (50, 40), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)cv2.putText(frame,f'confidence:{confidence}',(70,70),cv2.FONT_HERSHEY_SIMPLEX,0.5, (0, 0, 255), 2)except Exception as e:print(f"预测错误: {e}")cv2.imshow('Gesture Recognition', frame)if cv2.waitKey(1)==27: breakcap.release()cv2.destroyAllWindows()train_loader, test_loader = process_data()
model=train_model(train_loader, test_loader, num_epochs=10)
realtime_prediction()

 

效果:

 

cv2不支持中文字体,因此只能使用英文来标注…… 

总结

        以上便是计算机视觉cv2入门之实时手势检测的所有内容,如果你感到本文对你有用,还劳驾各位一键三连支持一下博主。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java 安全:如何防止 SQL 注入与 XSS 攻击?

Java 安全:如何防止 SQL 注入与 XSS 攻击? 在 Java 开发领域,安全问题至关重要,而 SQL 注入和 XSS 攻击是两种常见的安全威胁。本文将深入探讨如何有效防止这两种攻击,通过详细代码实例为您呈现解决方案。 一、SQL 注…

Itext进行PDF的编辑开发

这周写了一周的需求,是制作一个PDF生成功能,其中用到了Itext来制作PDF的视觉效果。其中一些功能不是很懂,仅作记录,若要学习请仔细甄别正确与否。 开始之前,我还是想说,这傻福需求怎么想出来的&#xff0c…

android编译使用共享缓存

注意 服务器端与客户端系统的版本号需为Ubuntu20.04ccache版本不能低于4.4执行用户需要为sudo权限服务器端nfs目录权限必须为nobody:nogroup 一、服务端配置: 在服务器192.168.60.142上配置 NFS 共享 1.安装 NFS 服务器: 1 sudo apt-get install nfs…

工作中sql总结

sql总结 场景1分组后失败的成功数据带入场景2完全性质的一对一匹配场景3虚拟户的特殊匹配场景4多对多匹配场景5一对一匹配场景6 一对多匹配 场景1分组后失败的成功数据带入 现有一批交易表的数据,根据户名,日期,金额分组,存在TRA…

QML FontDialog:使用FontDialog实现字体选择功能

目录 引言相关阅读FontDialog基本介绍字体属性 实例演示项目结构代码实现Main.qmlmain.cpp 代码解析运行效果 总结 引言 在桌面应用程序开发中,字体选择是一个常见的需求。Qt Quick提供了FontDialog组件来实现这一功能。本文将介绍如何在Qt Quick应用程序中使用Fon…

MCP(3):在CherryStudio中使用MCPServer

上一文章讲述了如何新建一个MCP Server,并在MCP Inspector完成测试。本文讲述如何在CherryStudio中进行测试。 Cherry Studio 是一款由 CherryHQ 开发的多模型支持的 AI 桌面助手,兼容 Windows、Linux 和 macOS 系统,旨在为用户提供更便捷、…

面试题-链表(2)

1.合并两个有序链表: 21. 合并两个有序链表 - 力扣(LeetCode) public ListNode mergeTwoLists(ListNode headA, ListNode headB){ListNode newheadnew ListNode(-1);ListNode curnewhead;while(headA!null&&headB!null){if(headA.va…

微软Entra新安全功能引发大规模账户锁定事件

误报触发大规模锁定 多家机构的Windows管理员报告称,微软Entra ID新推出的"MACE"(泄露凭证检测应用)功能在部署过程中产生大量误报,导致用户账户被大规模锁定。这些警报和锁定始于昨夜,部分管理员认为属于误…

【MATLAB第117期】#源码分享 | 基于MATLAB的SSM状态空间模型多元时间序列预测方法(多输入单输出)

【MATLAB第117期】#源码分享 | 基于MATLAB的SSM状态空间模型多元时间序列预测方法(多输入单输出) 引言 本文使用状态空间模型实现失业率递归预测,状态空间模型(State Space Model, SSM)是一种用于描述动态系统行为的…

谷歌浏览器搜索后的页面总是覆盖当前页面

最近将搜索引擎换为谷歌后,发现,每次搜索完的结果页面总是覆盖当前页面,非常不方便,在浏览器设置中又找不到类似设置的选项,然后终于在一个博主“如何设置使谷歌浏览器打开链接自动跳转到新标签页而不是覆盖当前页面?…

记录学习的第三十天

今天终于又开始写博客了。 还是滑动窗口问题,这段时间不出意外都是这了 上面的思路是我自己做的,但是不知道为什么不行,有没有大佬能指点一下我。 接下来这道题是进阶的。不过我之前的基础都做的很艰难,道阻且长啊。

QTextDocument 入门

一、QTextDocument QTextDocument 是 Qt 中用于处理富文本文档的核心类,支持文本格式、图片、表格等复杂内容。 1. QTextDocument 入门 1.1 基本概念 QTextDocument 是 Qt 中用于处理富文本内容的核心类,它提供了: 结构化文本存储&#x…

WebRTC服务器Coturn服务器相关测试工具

1、概述 在安装开源的webrtc服务器coturn服务器后,会附带安装coturn的相关工具,主要有以下几种工具 2、turnadmin工具 说明:服务器命令行工具,提供添加用户、添加管理员、生成TURN密钥等功能,turnadmin -h查看详细用…

如何创建Vue3工程

1.首先下载环境 (默认下好了VS code) Node.js: Node.js 中文网 — 下载 Node.js 选择要下载的版本 检查环境: 在命令行中输入 node ,检查版本号 2.创建工程 1.找到自己要创建工程的文件目录,右键打开打开终端 在终端输入创…

基于大模型的肛裂手术全流程预测与治疗方案研究报告

目录 一、引言 1.1 研究背景与意义 1.2 研究目标与创新点 1.3 研究方法与技术路线 二、肛裂概述与大模型技术原理 2.1 肛裂的医学定义与分类 2.2 肛裂的发病机制与临床症状 2.3 大模型技术简介 三、大模型在肛裂术前预测的应用 3.1 术前风险因素分析与数据收集 3.2 …

【趣味小游戏】--扫雷游戏

目录 一.test.c部分 二.game.h部分 三.game.c部分 前言:前面学习了数组和函数等c语言相关知识,这篇文章我们将通过这些知识分为三个文件来完成扫雷游戏; 1.test.c //文件中写游戏的测试逻辑 2.game.c //文件中写游戏中函数的实现等 3.game.h. //文件中写…

【微服务】SpringBoot制作Docker镜像接入SkyWalking详解

目录 一、前言 二、SkyWalking介绍 2.1 SkyWalking是什么 2.2 SkyWalking核心功能 2.3 SkyWalking整体架构 2.4 SkyWalking主要工作流程 三、前置准备 3.1 搭建SkyWalking服务 3.1.1 下载安装包 3.1.2 上传服务器目录 2.1.3 数据库持久化配置说明 3.1.4 启动skywalk…

从零开始构建微博爬虫与数据分析系统

从零开始构建微博爬虫与数据分析系统 引言 社交媒体平台蕴含着海量的信息和数据,通过对这些数据的收集和分析,我们可以挖掘出有价值的见解。本文将详细介绍如何构建一个完整的微博爬虫和数据分析系统,从数据爬取、清洗、到多维度分析与可视…

深入探索RAG:用LlamaIndex为大语言模型扩展知识,实现智能检索增强生成

大型语言模型(LLM),如ChatGPT和Llama,在回答问题方面表现出色,但它们的知识仅限于训练时所获取的信息。它们无法访问私有数据,也无法在训练截止日期之后学习新知识。那么,核心问题就是……我们如…

【延迟双删】简单解析

使用场景:【高并发】情况下的做【更新操作】 什么是延迟双删 首次删除:当需要更新某个数据项时,首先删除缓存中的该项。 更新数据库:接着,更新数据库中的该项。 短暂延迟:然后等待一段很短的时间&#xff…