自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

手勢(shì)圖像識(shí)別實(shí)戰(zhàn)(LeNet模型) 原創(chuàng)

發(fā)布于 2024-12-5 11:13
瀏覽
0收藏

前言

上一章內(nèi)容我們初步了解了卷積、卷積神經(jīng)網(wǎng)絡(luò)、卷積神經(jīng)網(wǎng)絡(luò)的搭建過程以及經(jīng)典的LeNet網(wǎng)絡(luò)結(jié)構(gòu),本篇內(nèi)容將基于LeNet網(wǎng)絡(luò)結(jié)構(gòu),實(shí)現(xiàn)手勢(shì)識(shí)別。

手勢(shì)識(shí)別

數(shù)據(jù)集介紹

在開展手勢(shì)識(shí)別之前,我們需要先下載并初步了解數(shù)據(jù)集的情況。

數(shù)據(jù)下載地址

下載地址:手勢(shì)識(shí)別數(shù)據(jù)集

├── train                   # 訓(xùn)練集
├── G0                  # 手勢(shì)0
├── IMG_1118.jpg    # 手勢(shì)0的圖片
├──...
├── G1                  # 手勢(shì)1
├── IMG_1119.jpg
├──...
├──...
├── G9                  # 手勢(shì)9
├──test# 測(cè)試集
├── G0
├──...
├── G9

手勢(shì)圖像識(shí)別實(shí)戰(zhàn)(LeNet模型)-AI.x社區(qū)

項(xiàng)目流程

在《【課程總結(jié)】Day8(上):深度學(xué)習(xí)基本流程》中,我們已了解到深度學(xué)習(xí)的基本流程為:

  1. 數(shù)據(jù)預(yù)處理 1.1 數(shù)據(jù)讀取 1.2 數(shù)據(jù)切分 1.3 數(shù)據(jù)規(guī)范化
  2. 批量化打包數(shù)據(jù)
  3. 模型搭建
  4. 籌備訓(xùn)練
  5. 訓(xùn)練模型 5.1 定義監(jiān)控指標(biāo)和方法 5.2 實(shí)現(xiàn)訓(xùn)練過程 5.3 開始訓(xùn)練

因此,本次項(xiàng)目也采用如上的基本流程。

數(shù)據(jù)預(yù)處理

由上述目錄結(jié)構(gòu)可知,我們需要在訓(xùn)練前使用DataLoader將數(shù)據(jù)集打包成適合訓(xùn)練的格式,因此需要解決2個(gè)問題:

問題1:如何記錄標(biāo)簽數(shù)據(jù)和圖片數(shù)據(jù)

解決方法:

  • 獲取標(biāo)簽:上述目錄中的G0、G1、G2...G9文件夾名稱即為手勢(shì)標(biāo)簽,因此我們可以通過os.listdir()函數(shù)獲取文件夾名稱。
  • 保存標(biāo)簽:將上述遍歷的G0、G1、G2...G9文件夾名稱保存到列表??label_train??中,方便后續(xù)使用。
  • 獲取圖片路徑:通過os.listdir()函數(shù)獲取文件夾中的圖片名稱,從而獲取圖片路徑。
  • 保存圖片路徑:將上述遍歷的圖片路徑保存到列表??img_train??中,方便后續(xù)使用。

# 讀取gestures\train\G0目錄下的所有圖片路徑,添加至list中
import os
import random
import numpy as np
import cv2


defload_img_label(train_root):
    img_train =[]
    label_train =[]
for label in os.listdir(train_root):
        label_path = os.path.join(train_root, label)
# 排除掉.開頭的文件
if label.startswith('.'):
continue
for img_name in os.listdir(label_path):
            img_path = os.path.join(label_path, img_name)
            img_train.append(img_path)
            label_train.append(label)
return img_train, label_train

# 1,讀取基圖像的本信息
root ="gestures"

# 1,訓(xùn)練集
train_root = os.path.join(root,'train')
train_img, train_label = load_img_label(train_root)


# 2,測(cè)試集
test_root = os.path.join(root,'test')
test_img, test_label = load_img_label(test_root)

lable_list =list(set(train_label))
lable_list.sort()

# 3,構(gòu)建標(biāo)簽字典
label2idx ={label: idx for idx, label inenumerate(lable_list)}
idx2label ={idx: label for idx, label inenumerate(lable_list)}
print(label2idx)
print(idx2label)

問題2:如何將圖片和標(biāo)簽數(shù)據(jù)打包成適合訓(xùn)練的格式

解決方法:

  • 構(gòu)建自定義數(shù)據(jù)集類GesturesDataset
  • 重寫__getitem__(),len(),init()方法
  • 在__getitem__()方法中:
  1. 使用cv2.imread()讀取圖片
  2. 使用cv2.resize()調(diào)整圖片大小
  3. 將圖像轉(zhuǎn)為numpy數(shù)組
  4. 對(duì)矩陣數(shù)組中的數(shù)據(jù)進(jìn)行歸一化處理,規(guī)范化為[-1, 1]
  5. 使用torch將數(shù)據(jù)轉(zhuǎn)為張量
  6. 將數(shù)據(jù)從圖片數(shù)據(jù)的[H(高度), W(寬度), C(通道數(shù))]轉(zhuǎn)維度為[N(批量個(gè)數(shù)), H(高度), W(寬度), C(通道數(shù))]
  7. 將標(biāo)簽轉(zhuǎn)為數(shù)字,例如:G0 -> 0, G1 -> 1, G2 -> 2, ..., G9 -> 9
  8. 將標(biāo)簽轉(zhuǎn)為張量

import torch
from torch.utils.data importDataset

classGesturesDataset(Dataset):
"""
    自定義數(shù)據(jù)集
    """
def__init__(self, X, y):
        self.X = X
        self.y = y

def__len__(self):
returnlen(self.X)

def__getitem__(self, idx):
        img_path = self.X[idx]
        img_label = self.y[idx]

# 1,讀取圖像
        img = cv2.imread(img_path)

# 2,圖像轉(zhuǎn)為32*32
        img = cv2.resize(img,(32,32))

# 3,圖像轉(zhuǎn)為numpy數(shù)組
        img = np.array(img)

# 4,數(shù)據(jù)規(guī)范化到 [-1, 1]
        img = img /255.0
        img =(img -0.5)/0.5

# 5,數(shù)據(jù)轉(zhuǎn)為torch張量
        img = torch.tensor(img, dtype=torch.float32)

# 6,數(shù)據(jù)轉(zhuǎn)維度 [H, W, C]
        img = img.permute(2,0,1)

# 7,標(biāo)簽轉(zhuǎn)為數(shù)字
        label = label2idx[img_label]
        label = torch.tensor(label, dtype=torch.long)

return img, label

模型搭建

本次模型使用LeNet網(wǎng)絡(luò)結(jié)構(gòu),相關(guān)結(jié)構(gòu)已在《【課程總結(jié)】Day10:卷積網(wǎng)絡(luò)的基本組件》闡述,本次過程不再贅述。

import torch
from torch import nn


classConvBlock(nn.Module):
"""
        一層卷積:
            - 卷積層
            - 批規(guī)范化層
            - 激活層
    """
def__init__(self, in_channels, out_channels, 
                 kernel_size=3, stride=1, padding=1):
super().__init__()
        self.conv = nn.Conv2d(in_channels=in_channels, out_channels=out_channels,
                             kernel_size=kernel_size, stride=stride,padding=padding)
        self.bn = nn.BatchNorm2d(num_features=out_channels)
        self.relu = nn.ReLU()

defforward(self, x):
        x = self.conv(x)
        x = self.bn(x)
        x = self.relu(x)
return x

classLeNet(nn.Module):
def__init__(self, num_classes=10):
super().__init__()
# 1, 特征抽取部分
        self.feature_extractor = nn.Sequential(
# 卷積層1
ConvBlock(in_channels=3,
                      out_channels=6,
                      kernel_size=5,
                      stride=1,
                      padding=0),

# 亞采樣(池化)
            nn.MaxPool2d(kernel_size=2, stride=2, padding=0),

# 卷積層2
ConvBlock(in_channels=6,
                      out_channels=16,
                      kernel_size=5,
                      stride=1,
                      padding=0),

# 亞采樣(池化)
            nn.MaxPool2d(kernel_size=2, stride=2, padding=0),

)

# 2, 分類
        self.classifier = nn.Sequential(
            nn.Flatten(),
            nn.Linear(in_features=400, out_features=120),
            nn.ReLU(),
            nn.Linear(in_features=120, out_features=84),
            nn.ReLU(),
            nn.Linear(in_features=84, out_features=num_classes)
)

defforward(self, x):
# 1, 提取特征
        x = self.feature_extractor(x)
# 2, 分類輸出
        x = self.classifier(x)
return x
  • 將以上代碼單獨(dú)封裝為model.py文件,方便后續(xù)直接import。
  • 在主程序中使用以下方式直接調(diào)用即可:??from models import LeNet
    model = LeNet()?
    ?

籌備訓(xùn)練

由于計(jì)算的數(shù)據(jù)量較大,所以我們需要借助torch以及GPU來提升訓(xùn)練速度。

# 檢測(cè)是否有可用的CUDA設(shè)備,如果有則使用第一個(gè)可用的CUDA設(shè)備,否則使用CPU
device ="cuda:0"if torch.cuda.is_available()else"cpu"

# 將模型移動(dòng)到指定的設(shè)備(CUDA或CPU)
model.to(device=device)

# 設(shè)置訓(xùn)練的總輪數(shù)
epochs =80

# 設(shè)置學(xué)習(xí)率
lr =1e-3

# 定義損失函數(shù)為交叉熵?fù)p失
loss_fn = nn.CrossEntropyLoss()

# 定義優(yōu)化器為隨機(jī)梯度下降(SGD),傳入模型的參數(shù)和學(xué)習(xí)率
optimizer = torch.optim.SGD(params=model.parameters(), lr=lr)

模型評(píng)估

為了觀察訓(xùn)練過程情況,定義模型評(píng)估函數(shù):

# 準(zhǔn)確率計(jì)算
defget_acc(data_loader):
    accs =[]
    model.eval()
with torch.no_grad():
for X, y in data_loader:
            X = X.to(device=device)
            y = y.to(device=device)
            y_pred = model(X)
            y_pred = y_pred.argmax(dim=-1)
            acc =(y_pred == y).to(torch.float32).mean().item()
            accs.append(acc)
    final_acc =round(number=sum(accs)/len(accs), ndigits=5)
return final_acc

實(shí)現(xiàn)訓(xùn)練過程

# 訓(xùn)練過程
deftrain():

    train_accs =[]
    test_accs =[]
    cur_test_acc =0

# 1,訓(xùn)練之前,檢測(cè)一下準(zhǔn)確率
    train_acc = get_acc(data_loader=train_dataloader)
    test_acc = get_acc(data_loader=test_dataloader)
    train_accs.append(train_acc)
    test_accs.append(test_acc)

print(f"訓(xùn)練之前:train_acc: {train_acc},test_acc: {test_acc}")

# 每一輪次
for epoch inrange(epochs):
# 模型設(shè)置為 train 模式
        model.train()
# 計(jì)時(shí)
        start_train = time.time()
# 每一批量
for X, y in train_dataloader:
# 數(shù)據(jù)搬家
            X = X.to(device=device)
            y = y.to(device=device)
# 1,正向傳播
            y_pred = model(X)
# 2,計(jì)算損失
            loss = loss_fn(y_pred, y)
# 3,反向傳播
            loss.backward()
# 4,優(yōu)化一步
            optimizer.step()
# 5,清空梯度
            optimizer.zero_grad()
# 計(jì)時(shí)結(jié)束
        stop_train = time.time()
# 測(cè)試準(zhǔn)確率
        train_acc = get_acc(data_loader=train_dataloader)
        test_acc = get_acc(data_loader=test_dataloader)
        train_accs.append(train_acc)
        test_accs.append(test_acc)
# 保存模型
if cur_test_acc < test_acc:
            cur_test_acc = test_acc
# 保存最好模型
            torch.save(obj=model.state_dict(), f="lenet_best.pt")
# 保存最后模型
        torch.save(obj=model.state_dict(), f="lenet_last.pt")

# 格式化輸出日志
print(f"""
        當(dāng)前是第 {epoch + 1} 輪:
        ------------------------------------------------------------
        | 訓(xùn)練準(zhǔn)確率 (train_acc) | 測(cè)試準(zhǔn)確率 (test_acc) | 運(yùn)行時(shí)間 (elapsed_time) |
        ------------------------------------------------------------
        | {train_acc:<18} | {test_acc:<17} | {round(number=stop_train - start_train, ndigits=3)} 秒    |
        ------------------------------------------------------------
        """)
return train_accs, test_accs

開始訓(xùn)練

train_accs, test_accs = train()

圖形化監(jiān)控?cái)?shù)據(jù)

plt.plot(train_accs, label="train_acc")
plt.plot(test_accs, label="train_acc")
plt.legend()
plt.grid()
plt.xlabel(xlabel='epoch')
plt.ylabel(ylabel="acc")
plt.title(label="LeNet Training Process")

運(yùn)行結(jié)果:

手勢(shì)圖像識(shí)別實(shí)戰(zhàn)(LeNet模型)-AI.x社區(qū)

手勢(shì)圖像識(shí)別實(shí)戰(zhàn)(LeNet模型)-AI.x社區(qū)

通過以上執(zhí)行過程可以看到,經(jīng)過80輪訓(xùn)練后,LeNet模型在訓(xùn)練集上的準(zhǔn)確率達(dá)到99%,在測(cè)試集上的準(zhǔn)確率達(dá)到94%。

模型預(yù)測(cè)

接下來,我們使用streamlit實(shí)現(xiàn)一個(gè)前端頁面,用戶在頁面上輸入圖片,模型會(huì)自動(dòng)識(shí)別圖片中的手勢(shì)。

整體實(shí)現(xiàn)流程:

  1. 創(chuàng)建一個(gè)streamlit應(yīng)用,并導(dǎo)入相關(guān)依賴。
  2. 顯示當(dāng)前設(shè)備是GPU設(shè)備還是CPU
  3. 加載模型
  4. 使用streamlit.file_uploader顯示上傳圖片控件
  5. 使用streamlit.image顯示上傳的圖片
  6. 使用加載的模型進(jìn)行預(yù)測(cè) 6.1 讀取圖像 6.2 圖像預(yù)處理 6.3 圖形轉(zhuǎn)為張量 6.4 轉(zhuǎn)換圖形的維度為[C, H, W] 6.5 新建一個(gè)批量維度[N, C, H, W] 6.6 數(shù)據(jù)搬家 6.7 模型設(shè)為評(píng)估模式 6.8 模型預(yù)測(cè) 6.9 預(yù)測(cè)結(jié)果轉(zhuǎn)為標(biāo)簽 0 → G0, 1 → G1, 2 → G2, 3 → G3, 4 → G4, 5 → G5 6.10 返回標(biāo)簽結(jié)果

import streamlit
import torch
import os
import numpy as np
from PIL importImage
from models importLeNet


# 生成idx2label字典,用于顯示預(yù)測(cè)結(jié)果
idx2label ={
0:'G0',
1:'G1',
2:'G2',
3:'G3',
4:'G4',
5:'G5',
6:'G6',
7:'G7',
8:'G8',
9:'G9'
}

definfer(img_path, model, device, idx2label):
"""
        輸入:圖像地址
        輸出:預(yù)測(cè)類別
    """
# 1,讀取圖像
ifnot os.path.exists(img_path):
raiseFileNotFoundError("文件沒找到")

# 2, 判斷當(dāng)前局部變量中是否有model
# if "m1" not in globals() or not isinstance(globals()["m1"], LeNet):
#     raise ValueError("m1模型不存在")

# 3,讀取圖像
    img =Image.open(fp=img_path)

# 4,預(yù)處理
    img = img.resize((32,32))
    img = np.array(img)
    img = img /255
    img =(img -0.5)/0.5

# 5, 轉(zhuǎn)張量
    img = torch.tensor(data=img, dtype=torch.float32)

# 6, 轉(zhuǎn)換維度
    img = img.permute(dims=(2,0,1))

# 7, 新增一個(gè)批量維度
    img = img.unsqueeze(dim=0)

# 8,數(shù)據(jù)搬家
    img = img.to(device=device)

# 9,模型設(shè)為評(píng)估模式
    model.eval()

# 10,無梯度環(huán)境
with torch.no_grad():
# 11,正向傳播
        y_pred = m1(img)

# 12, 解析結(jié)果
        y_pred = y_pred.argmax(dim=-1).item()

# 13,標(biāo)簽轉(zhuǎn)換
        label = idx2label.get(y_pred)

# 14, 返回結(jié)果
return label

if __name__ =="__main__":
# 1, 顯示當(dāng)前設(shè)備是GPU設(shè)備還是CPU
# 檢測(cè)設(shè)備
    device ="cuda"if torch.cuda.is_available()else"cpu"
    streamlit.write(f"當(dāng)前設(shè)備是{device}設(shè)備")


# 2, 加載模型
    m1 =LeNet()
    m1.to(device=device)
# 加載權(quán)重
    m1.load_state_dict(state_dict=torch.load(f="lenet_best.pt", map_locatinotallow=device),
                    strict=False)
ifnotisinstance(m1,LeNet):
raiseValueError("模型加載失敗")

# 3, 上傳一張圖片
    img_path = streamlit.file_uploader(label="上傳一張圖片",type=["png","jpg","jpeg"])
# 3.1, 將上傳的圖像文件保存到臨時(shí)文件
if img_path:
withopen(file="temp_img.jpg", mode="wb")as f:
            f.write(img_path.getvalue())
        img_path ="temp_img.jpg"
# 4, 顯示上傳的圖片
if img_path:
        img =Image.open(fp=img_path)
        streamlit.image(image=img, captinotallow="上傳的圖片", use_column_width=True)

# 5, 加載本地的lenet_best.pt模型
if img_path:
        label = infer(img_path=img_path, model=m1, device=device, idx2label=idx2label)
        streamlit.write(f"預(yù)測(cè)結(jié)果是{label}")

運(yùn)行結(jié)果:

手勢(shì)圖像識(shí)別實(shí)戰(zhàn)(LeNet模型)-AI.x社區(qū)圖片

內(nèi)容小結(jié)

  • 回顧深度學(xué)習(xí)的整體流程,仍然是:數(shù)據(jù)預(yù)處理→批量化打包數(shù)據(jù)→模型搭建→訓(xùn)練模型→模型評(píng)估→模型預(yù)測(cè)
  • 圖片數(shù)據(jù)預(yù)處理時(shí),批量化打包數(shù)據(jù)需要構(gòu)造為[N, C, H, W]的格式
  • 預(yù)處理的過程大致為:讀取圖片→調(diào)整圖片大小→轉(zhuǎn)為numpy數(shù)組→歸一化→轉(zhuǎn)為張量→調(diào)整維度→標(biāo)簽轉(zhuǎn)為數(shù)字→轉(zhuǎn)為張量,該過程需要在自定義數(shù)據(jù)集的__getitem__函數(shù)中完成
  • 模型構(gòu)建使用的是LeNet模型,該模型定義可以單獨(dú)在models.py中實(shí)現(xiàn),訓(xùn)練代碼中直接import引用即可
  • 訓(xùn)練過程以及訓(xùn)練時(shí)的監(jiān)控過程,與前兩章學(xué)習(xí)的深度學(xué)習(xí)訓(xùn)練過程是一樣的


本文轉(zhuǎn)載自公眾號(hào)一起AI技術(shù) 作者:熱情的Dongming

原文鏈接:??https://mp.weixin.qq.com/s/uLcszdSP99AepL5d47e6ZQ??

?著作權(quán)歸作者所有,如需轉(zhuǎn)載,請(qǐng)注明出處,否則將追究法律責(zé)任
已于2024-12-5 11:14:19修改
收藏
回復(fù)
舉報(bào)
回復(fù)
相關(guān)推薦