如何玩转手势识别 tensorflow2.0+CNN就行

先上个效果图吧 :

基本框架图,以及所涉及的内容:

第一部分-样本的获取:

我是用的opencv自己获取的自己手势的数据集、测试集等等都可以,不算很大(大约10000张左右),网上也有下载一些数据集, 小的话可以作为测试集。

原理就是导入opencv的库, 实时检测键盘按键情况,根据不同的按键保存当前的手势到特定的文件目录下, 相当于有10个按键我这里设置的是’asdfghjklz’在保存前可以对图片进行数据增强,然后再保存。我这里就没有做太多处理,以至于泛化能力还有待提高 不多说废话了,这部分相对简单代码注释也比较详细,上代码:

import win32api

import win32con

import cv2 as cv

import numpy as np

def get_roi(frame, x1, x2, y1, y2):

    dst = frame[y1:y2, x1:x2]

    cv.rectangle(frame, (x1, y1), (x2, y2), (0, 0, 255), thickness=2)

    return dst

if __name__ == "__main__":

    capture = cv.VideoCapture(0)

    m_0 = 0

    m_1 = 0

    m_2 = 0

    m_3 = 0

    m_4 = 0

    m_5 = 0

    m_6 = 0

    m_7 = 0

    m_8 = 0

    m_9 = 0

    while True:

        ret, frame = capture.read()

        roi = get_roi(frame, 100, 250, 100, 250)

        k = cv.waitKey(50)

        if k == 27:  # 按下ESC退出

            break

        elif k == ord('a'):  # 按下'A'会保存当前图片到指定目录下

            cv.imwrite("E:\\aiFile\\picture\\gesture_data\\0\\%s.jpg" % m_0, roi)

            m_0 += 1

            # flip_image = cv.flip(skin, 1)  # 这里用到的是水平翻转,因为后面的参数是一

            # cv.imwrite("E:\\aiFile\\picture\\gesture_data\\0\\%s.jpg" % m_0, flip_image)

            # m_0 += 1

            print('正在保存0-roi图片,本次图片数量:', m_0)

        elif k == ord('s'): 

            cv.imwrite("E:\\aiFile\\picture\\gesture_data\\1\\%s.jpg" % m_1, roi)

            m_1 += 1

            # flip_image = cv.flip(skin, 1)  # 这里用到的是水平翻转,因为后面的参数是一

            # cv.imwrite("E:\\aiFile\\picture\\gesture_data\\1\\%s.jpg" % m_1, flip_image)

            # m_1 += 1

            print('正在保存1-roi图片,本次图片数量:', m_1)

        elif k == ord('d'): 

            cv.imwrite("E:\\aiFile\\picture\\gesture_data\\2\\%s.jpg" % m_2, roi)

            m_2 += 1

            # flip_image = cv.flip(skin, 1)  # 这里用到的是水平翻转,因为后面的参数是一

            # cv.imwrite("E:\\aiFile\\picture\\gesture_data\\2\\%s.jpg" % m_2, flip_image)

            # m_2 += 1

            print('正在保存2-roi图片,本次图片数量:', m_2)

        elif k == ord('f'): 

            cv.imwrite("E:\\aiFile\\picture\\gesture_data\\3\\%s.jpg" % m_3, roi)

            m_3 += 1

            # flip_image = cv.flip(skin, 1)  # 这里用到的是水平翻转,因为后面的参数是一

            # cv.imwrite("E:\\aiFile\\picture\\gesture_data\\3\\%s.jpg" % m_3, flip_image)

            # m_3 += 1

            print('正在保存3-roi图片,本次图片数量:', m_3)

        elif k == ord('g'): 

            cv.imwrite("E:\\aiFile\\picture\\gesture_data\\4\\%s.jpg" % m_4, roi)

            m_4 += 1

            # flip_image = cv.flip(skin, 1)  # 这里用到的是水平翻转,因为后面的参数是一

            # cv.imwrite("E:\\aiFile\\picture\\gesture_data\\4\\%s.jpg" % m_4, flip_image)

            # m_4 += 1

            print('正在保存4-roi图片,本次图片数量:', m_4)

        elif k == ord('h'): 

            cv.imwrite("E:\\aiFile\\picture\\gesture_data\\5\\%s.jpg" % m_5, roi)

            m_5 += 1

            # flip_image = cv.flip(skin, 1)  # 这里用到的是水平翻转,因为后面的参数是一

            # cv.imwrite("E:\\aiFile\\picture\\gesture_data\\5\\%s.jpg" % m_5, flip_image)

            # m_5 += 1

            print('正在保存5-roi图片,本次图片数量:', m_5)

        elif k == ord('j'): 

            cv.imwrite("E:\\aiFile\\picture\\gesture_data\\6\\%s.jpg" % m_6, roi)

            m_6 += 1

            # flip_image = cv.flip(skin, 1)  # 这里用到的是水平翻转,因为后面的参数是一

            # cv.imwrite("E:\\aiFile\\picture\\gesture_data\\6\\%s.jpg" % m_6, flip_image)

            # m_6 += 1

            print('正在保存6-roi图片,本次图片数量:', m_6)

        elif k == ord('k'): 

            cv.imwrite("E:\\aiFile\\picture\\gesture_data\\7\\%s.jpg" % m_7, roi)

            m_7 += 1

            # flip_image = cv.flip(skin, 1)  # 这里用到的是水平翻转,因为后面的参数是一

            # cv.imwrite("E:\\aiFile\\picture\\gesture_data\\7\\%s.jpg" % m_7, flip_image)

            # m_7 += 1

            print('正在保存7-roi图片,本次图片数量:', m_7)

        elif k == ord('l'): 

            cv.imwrite("E:\\aiFile\\picture\\gesture_data\\8\\%s.jpg" % m_8, roi)

            m_8 += 1

            # flip_image = cv.flip(skin, 1)  # 这里用到的是水平翻转,因为后面的参数是一

            # cv.imwrite("E:\\aiFile\\picture\\gesture_data\\8\\%s.jpg" % m_8, flip_image)

            # m_8 += 1

            print('正在保存8-roi图片,本次图片数量:', m_8)

        elif k == ord('z'): 

            cv.imwrite("E:\\aiFile\\picture\\gesture_data\\9\\%s.jpg" % m_9, roi)

            m_9 += 1

            # flip_image = cv.flip(skin, 1)  # 这里用到的是水平翻转,因为后面的参数是一

            # cv.imwrite("E:\\aiFile\\picture\\gesture_data\\9\\%s.jpg" % m_9, flip_image)

            # m_9 += 1

            print('正在保存9-roi图片,本次图片数量:', m_9)

        cv.imshow("roi", roi)

        cv.imshow("frame", frame)

        c = cv.waitKey(50)

        if c == 27:

            break

    cv.waitKey(0)

    capture.release()

    cv.destroyAllWindows()

**说明:代码中有些部分没有用到,因为之前想尝试下单通道手势图片的训练和预测。运行之前得现在特定的文件夹下创建好0-9这十个文件夹,然后在把图片保存进去,方便之后的训练。

第二部分-dataset自定义数据集以及训练:

采用tensorflow 的dataset模块创建自己数据集和tensorflow.keras api实现模型的构建以及训练.

1、dataset自定义数据集,获得的db_train和db_test 它是个张量自带数据和标签,所以在训练的时候不需要在传入标签。

2 、构建卷积神经网络结构

3、回调函数终止训练以及模型保存断点续训

4、保存整个模型是为了让下一步分预测的时候不需要在构建神经网络结构,保存权重是为了断点续训,如果在训练完之后想要补充训练集数据 ,然后继续训练的话就不需要重新开始训练了,继续训练速度会非常快,如果只是单纯的加样本数量通常只需要再训练5轮即可,根据自己的训练情况来灵活判断。

注意:回调函数的应用可能会存在问题,因为dataset数据集不支持划分验证集,所以没办法通过validation_split=0.2 来将训练集的0.2划分为验证集,所以在使用回调函数终止训练的过程中可能会有警告,如果需要验证集只能自己在创建一个valid的dataset。

训练部分的完整代码

import tensorflow as tf

import os

import numpy as np

import cv2 as cv

import csv

from tensorflow import keras

from tensorflow.keras import datasets, layers, optimizers, Sequential, metrics

os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'

def get_image(im):

    image = cv.resize(im, (100, 100))

    # print("image.shape", len(image))

    # gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)

    # ret, binary = cv.threshold(gray, 0, 255, cv.THRESH_BINARY_INV | cv.THRESH_OTSU)

    image2 = image.reshape(-1, 100, 100,  3)

    image3 = tf.cast(image2 / 255.0, tf.float32)

    return image3

def get_roi(frame, x1, x2, y1, y2):

    dst = frame[x1:x2, y1:y2]

    cv.rectangle(frame, (x1, y1), (x2, y2), (0, 0, 255), thickness=2)

    return dst

# 获取图片,存放到对应的列表中,同时贴上标签,存放到label列表中

def get_files(file_dir):

    # 存放图片类别和标签的列表:第0类

    list_0 = []

    label_0 = []

    # 存放图片类别和标签的列表:第1类

    list_1 = []

    label_1 = []

    # 存放图片类别和标签的列表:第2类

    list_2 = []

    label_2 = []

    # 存放图片类别和标签的列表:第3类

    list_3 = []

    label_3 = []

    # 存放图片类别和标签的列表:第4类

    list_4 = []

    label_4 = []

    # 存放图片类别和标签的列表:第5类

    list_5 = []

    label_5 = []

    # 存放图片类别和标签的列表:第6类

    list_6 = []

    label_6 = []

    # 存放图片类别和标签的列表:第6类

    list_7 = []

    label_7 = []

    # 存放图片类别和标签的列表:第8类

    list_8 = []

    label_8 = []

    # 存放图片类别和标签的列表:第9类

    list_9 = []

    label_9 = []

    for file in os.listdir(file_dir):  # 获得file_dir路径下的全部文件名

        # print(file)

        # 拼接出图片文件路径

        image_file_path = os.path.join(file_dir, file)

        for image_name in os.listdir(image_file_path):

            # print('image_name',image_name)

            # 图片的完整路径

            image_name_path = os.path.join(image_file_path, image_name)

            # print('image_name_path',image_name_path)

            # 将图片存放入对应的列表

            if image_file_path[-1:] == '0':

                list_0.append(image_name_path)

                label_0.append(0)

            elif image_file_path[-1:] == '1':

                list_1.append(image_name_path)

                label_1.append(1)

            elif image_file_path[-1:] == '2':

                list_2.append(image_name_path)

                label_2.append(2)

            elif image_file_path[-1:] == '3':

                list_3.append(image_name_path)

                label_3.append(3)

            elif image_file_path[-1:] == '4':

                list_3.append(image_name_path)

                label_3.append(4)

            elif image_file_path[-1:] == '5':

                list_3.append(image_name_path)

                label_3.append(5)

            elif image_file_path[-1:] == '6':

                list_3.append(image_name_path)

                label_3.append(6)

            elif image_file_path[-1:] == '7':

                list_3.append(image_name_path)

                label_3.append(7)

            elif image_file_path[-1:] == '8':

                list_3.append(image_name_path)

                label_3.append(8)

            else:

                list_4.append(image_name_path)

                label_4.append(9)

    # 合并数据

    image_list = np.hstack((list_0, list_1, list_2, list_3, list_4, list_5, list_6, list_7, list_8, list_9))

    label_list = np.hstack((label_0, label_1, label_2, label_3, label_4, label_5, label_6, label_7, label_8, label_9))

    # 利用shuffle打乱数据

    print("imagename = ", image_list[:10])

    print("labelname = ", label_list[:10])

    temp = np.array([image_list, label_list])

    temp = temp.transpose()  # 转置

    np.random.shuffle(temp)

    # 将所有的image和label转换成list

    image_list = list(temp[:, 0])

    image_list = [i for i in image_list]

    label_list = list(temp[:, 1])

    label_list = [int(float(i)) for i in label_list]

    # print(image_list)

    # print(label_list)

    return image_list, label_list

def get_tensor(image_list, label_list):

    ims = []

    for image in image_list:

        # 读取路径下的图片

        x = tf.io.read_file(image)

        # 将路径映射为照片,3通道

        x = tf.image.decode_jpeg(x, channels=3)

        # 修改图像大小

        x = tf.image.resize(x, [32, 32])

        # 将图像压入列表中

        ims.append(x)

    # 将列表转换成tensor类型

    img = tf.convert_to_tensor(ims)

    y = tf.convert_to_tensor(label_list)

    return img, y

def train_model(train_data):

    # 构建模型

    network = keras.Sequential([

        keras.layers.Conv2D(32, kernel_size=[5, 5], padding="same", activation=tf.nn.relu),

        keras.layers.MaxPool2D(pool_size=[2, 2], strides=2, padding='same'),

        keras.layers.Conv2D(64, kernel_size=[3, 3], padding="same", activation=tf.nn.relu),

        keras.layers.MaxPool2D(pool_size=[2, 2], strides=2, padding='same'),

        keras.layers.Conv2D(64, kernel_size=[3, 3], padding="same", activation=tf.nn.relu),

        keras.layers.Flatten(),

        keras.layers.Dense(512, activation='relu'),

        keras.layers.Dropout(0.5),

        keras.layers.Dense(128, activation='relu'),

        keras.layers.Dense(10)])

    network.build(input_shape=(None, 100, 100, channels))

    network.summary()

    network.compile(optimizer=optimizers.SGD(lr=0.001),

                    loss=tf.losses.SparseCategoricalCrossentropy(from_logits=True),

                    metrics=['accuracy']

                    )

    checkpoint_filepath = "E:\\aiFile\\model_save\\gesture_recognition_model\\gestureModel"

    callbacks = [

        # 保存模型的回调函数

        tf.keras.callbacks.ModelCheckpoint(filepath=checkpoint_filepath,  # 保存路径

                                          save_weights_only=True,

                                          verbose=0,

                                          save_freq='epoch'),  # 保存频次以周期频次来计算

        # 中止训练的回调函数

        tf.keras.callbacks.EarlyStopping(monitor='val_loss', patience=5)  # 防止过拟合,如果过拟合了之后就终止1_val验证集上loss变高终止观察3个周期

    ]

    # 只要在model.fit训练模型里面加上 callbacks=callbacks  这个参数,那在训练模型的时候就会按  照我们设计的回调函数来保存模型

    # 模型训练

    network.load_weights('E:\\aiFile\\model_save\\gesture_recognition_model\\gestureModel_one.h5')

    print("载入已训练权重成功")

    network.fit(train_data, epochs=5, callbacks=callbacks)  # 因为是dataset数据集是个元组自带标签所以不用分x和y了

    # network.evaluate(test_data)

    network.save_weights('E:\\aiFile\\model_save\\gesture_recognition_model\\gestureModel_one.h5')

    print("保存模型权重成功")

    tf.saved_model.save(network, 'E:\\aiFile\\model_save\\gesture_recognition_model\\gestureModel_one')

    print("保存模型成功")

    return network

def preprocess_image(image):

    image = tf.image.decode_jpeg(image, channels=channels)

    image = tf.image.resize(image, [100, 100])

    image /= 255.0  # normalize to [0,1] range

    # image = tf.reshape(image,[100*100*3])

    return image

def load_and_preprocess_image(path, label):

    image = tf.io.read_file(path)

    return preprocess_image(image), label

if __name__ == "__main__":

    # capture = cv.VideoCapture(0)

    # x1 = 400

    # x2 = 650

    # y1 = 50

    # y2 = 300

    # 训练图片的路径

    global channels

    channels = 3

    train_dir = 'E:\\aiFile\\picture\\gesture_data'

    test_dir = 'E:\\aiFile\\gesture_picture\\testdata'

    AUTOTUNE = tf.data.experimental.AUTOTUNE

    # 训练图片与标签

    image_list, label_list = get_files(train_dir)

    # 测试图片与标签

    test_image_list, test_label_list = get_files(test_dir)

    x_train, y_train = get_tensor(image_list, label_list)

    x_test, y_test = get_tensor(test_image_list, test_label_list)

    print('--------------------------------------------------------')

    print('x_train:', x_train.shape, 'y_train:', y_train.shape)

    db_train = tf.data.Dataset.from_tensor_slices((image_list, y_train))

    # # shuffle:打乱数据,map:数据预处理,batch:一次取喂入10样本训练

    db_train = db_train.shuffle(1000).map(load_and_preprocess_image).batch(10)

    #

    # # 载入训练数据集

    db_test = tf.data.Dataset.from_tensor_slices((test_image_list, y_test))

    # # # shuffle:打乱数据,map:数据预处理,batch:一次取喂入10样本训练

    db_test = db_test.shuffle(1000).map(load_and_preprocess_image).batch(10)

    print("dataset", db_train)

    network = train_model(db_train)

第三部分-预测与应用

这部分相对简单一些,导入之前训练的完整的模型即可开始预测,不需要再创建神经网络结构。直接上代码。

import tensorflow as tf

from tensorflow import keras

from tensorflow.keras import datasets, layers, optimizers, Sequential, metrics

from tensorflow.python.framework.convert_to_constants import convert_variables_to_constants_v2

import os

import pathlib

import random

import matplotlib.pyplot as plt

import cv2 as cv

import numpy as np

os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'

def get_roi(frame, x1, x2, y1, y2):

    dst = frame[y1:y2, x1:x2]

    cv.rectangle(frame, (x1, y1), (x2, y2), (0, 0, 255), thickness=2)

    return dst

def get_image(image, network):

    # image = tf.image.decode_jpeg(image, channels=3)

    # image = tf.expand_dims(image, axis=2)  # 扩维度,让单通道图片可以resize

    print("image.shape =  ", image.shape)

    image = tf.image.resize(image, [100, 100])

    # image = cv.resize(image, (100, 100))

    # image = image.reshape(-1, 100, 100,  1)

    image1 = image / 255.0  # normalize to [0,1] range

    image1 = tf.expand_dims(image1, axis=0)

    # print(image1.shape)

    pred = network(image1)

    print("预测结果原始结果", pred)

    print()

    pred = tf.nn.softmax(pred['output_1'], axis=1)

    print("预测softmax后", pred)

    pred = tf.argmax(pred, axis=1)

    print("最终测试结果", pred.numpy())

    cv.putText(frame, "Predicted results = :" + str(pred.numpy()), (100, 400), cv.FONT_HERSHEY_SIMPLEX,

              1, [0, 255, 255])

if __name__ == "__main__":

    capture = cv.VideoCapture(0)

    creatTrackbar()

    channels = 3

    DEFAULT_FUNCTION_KEY = "serving_default"

    loaded = tf.saved_model.load('E:\\aiFile\\model_save\\gesture_recognition_model\\gestureModel_one\\')

    network = loaded.signatures[DEFAULT_FUNCTION_KEY]

    print(list(loaded.signatures.keys()))

    print('加载 weights 成功')

    while True

        ret, frame = capture.read()

        roi = get_roi(frame, 100, 250, 100, 250)

        cv.imshow("roi", roi)

        get_image(roi, network)

        cv.imshow("frame", frame)

        c = cv.waitKey(50)

        if c == 27:

            break

    cv.waitKey(0)

    capture.release()

    cv.destroyAllWindows()

github里的代码可能和这边的代码会有一丝丝的不同,因为我之前尝试过先将手部提取出来,成为一个二值图像再去训练和预测但是效果没那么好,github里的代码保留了调试那部分的代码没有删去,这边的是已经删了的,github里多的那部分代码知识注释了或者是定义了函数没有用到的,运行效果和这里的代码完全一样,只是显得代码长了一些些。如果有想尝试一下先提取手势二值图然后在训练预测的同学也可以尝试下,github代码里有调试的痕迹,改下部分参数即可

参考:

如何用TensorFlow2.0制作自己的数据集

TensorFlow2.X结合OpenCV = 手势识别

更多学习课程以及资料 加Q群313074041领取

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,657评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,662评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,143评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,732评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,837评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,036评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,126评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,868评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,315评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,641评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,773评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,859评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,584评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,676评论 2 351