当前位置: 首页 > news >正文

计算机视觉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:每次采集的手势,要标记在视频中,防止忘记采集的手势是多少导致实际类别与真实采集结果不一致从而成为噪声!\n count:用来命名每次保存的图片,这里直接用记录图片数量来命名\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: break roi=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_dir self.transform = transform self.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-5 def __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) # 转换为RGB label=self.labels[idx] if self.transform: image=self.transform(image) return image, label def 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_size train_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 x def 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.0 correct=0 total=0 for 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 = 0 test_total = 0 with 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_total print(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=gestures with 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: break cap.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.jsqmd.com/news/1008306/

相关文章:

  • 别再只玩游戏了!用LabVIEW解析X-Plane的UDP数据流,解锁飞行数据的二次开发
  • 影刀RPA实操指南_自动发送邮件从SMTP配置到带附件定时报告
  • 掌控AMD Ryzen性能的钥匙:SMUDebugTool全面解析与实战指南
  • Lucky服务网关架构深度解析与高级配置实战指南
  • 2026温州龙港二手家电回收排行榜,推荐电话超实用(28字) - 资讯速览
  • 参考创建生产版本,从 SAP S/4HANA 物料主数据复制到主数据治理的那一步
  • 以自己为本:你生来拥有身体、时间、意志的全部主权。 别轻易把它,交出去。
  • 2026常德市权威认证贵金属回收 TOP5+黄金回收白银回收铂金回收门店地址电话推荐
  • 通过QKeyMapper解决Windows平台输入设备统一管理问题
  • 2026广州幕墙清洗服务商权威测评:资质合规与项目实力综合排名 - 互联网科技品牌测评
  • 2026年高端手工蛋卷实力品牌推荐排行榜:JAOLIS角力士凭匠心与品质稳居榜首 - 变量人生001
  • 2026年专业AI标书检测工具实测与选型指南:哪家比较靠谱? - 资讯速览
  • DRG Save Editor终极指南:3分钟学会深岩银河存档修改
  • Blender MMD Tools完整教程:5分钟学会导入MMD模型与动画
  • 如何在5分钟内实现智能图像分层?Layerdivider终极指南
  • 东莞快递袋工厂推荐:这3家实力厂家性价比拉满! - 资讯速览
  • Android Studio中文界面汉化终极教程:3步打造母语开发环境
  • 跨平台音乐格式壁垒的技术突破:ncmdump实现NCM加密文件无缝转换方案
  • BLIP-image-captioning-large在NPU上的极致优化:性能提升300%的秘密
  • Java计算机毕设之基于 SpringBoot 的闲置物品交易与订单管理系统 绿色低碳视角下闲置物品交易平台设计(完整前后端代码+说明文档+LW,调试定制等)
  • 厦门瓷砖空鼓翘边拱起怎么解决?2026专业修复方法攻略 - 苏易修缮
  • 嵌入式系统运行时完整性检查:RTIC硬件配置与安全实践
  • Display Driver Uninstaller:专业显卡驱动彻底清理终极指南
  • 如何彻底改变你的OBS录制工作流?源独立录制插件终极指南
  • 2026年全国旅游旺季到来,在烟台选择旅游包车需要注意什么? - 资讯速览
  • 三步搞定Windows电脑安装安卓应用:APK安装器终极指南
  • 2026广州工程保洁服务商权威测评:合规资质与服务能力深度对比 - 互联网科技品牌测评
  • 2026武汉奢侈品行业深度调查:行业现状,避坑指南以及五家诚信靠谱商家全景评测 - 资讯速览
  • 如何快速构建可视化AI聊天界面:终极LangGraph集成方案
  • IDM永久激活脚本完整指南:5种简单方法告别30天试用期限制