计算机视觉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入门之实时手势检测的所有内容,如果你感到本文对你有用,还劳驾各位一键三连支持一下博主。

评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值