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

图像处理算法识别手势

目录

1 实验目的

2 项目介绍

3 项目思路

3.1 图像处理模块

3.2 GUI模块

4 源代码

5 运行结果图

6 调试日志

7 项目优化


1 实验目的

使用OpenCV库来获取摄像头的视频流,并通过手势分析来快速准确地检测出1至5的手势。该程序通过采集视频帧并对其进行处理来分析手势。它使用图像处理算法来提取手势的形状,并根据手部的位置和轮廓来确定手势是哪个数字。运行程序后,摄像头将启动,并在摄像头下方的区域显示视频流。将手放在这个区域中,并展示你想识别的数字。程序会在摄像头捕捉到手势后立即返回相应的数字。虽然这种方法没有使用机器学习技术,但同样可以识别手势,并且运算速度较快,适合在一些低配置的设备上进行使用。

2 项目介绍

本项目目前使用到和未来可能使用的第三方库或者模块有:

介绍

tkinter

Tkinter是Python内置的GUI库,它提供了一个简单、易于使用的界面设计工具,可以创建各种窗口和控件,如文本框、标签、按钮等,方便用户与程序交互。Tkinter是Tcl/Tk GUI工具包在Python中的接口,Tcl/Tk是一个跨平台的图形用户界面(GUI)工具包,它是在现代操作系统中提供标准GUI工具包的一种选择。

cv2(OpenCV)

OpenCV是一个基于开源发行的跨平台计算机视觉库,包含了数百个计算机视觉算法和工具,它可以跨平台使用,并提供了Python、C++等语言支持,常用于图像和视频处理、目标检测、人脸识别等方面的应用程序开发。cv2是Python中OpenCV库的接口,可以使用Python调用OpenCV提供的算法库和工具,用来完成各种计算机视觉任务,包括:

1)图像和视频读写、显示和保存

2)图像预处理、滤波和边缘检测

3)物体检测和跟踪、人脸识别、光流估计等

4)图像变换和几何变换

5)相机标定和姿态估计等

PIL(pillow)

Image库是PIL中最核心的库之一,用于读取、处理和保存图像数据。

以下是一些Image库的主要功能:

打开和保存图像文件,支持JPEG、PNG、BMP、GIF、ICO和TIFF等格式。

1)提供了基本的图像处理方法,如裁剪、旋转、缩放、镜像、转换为黑白图等。

2)提供了滤镜效果,如模糊、轮廓、锐化、浮雕、柔化、颜色空间转换等。

3)支持像素级别的操作,包括像素的读取、修改、合并等。

4)支持文本渲染、字体调用和排版等。

ImageTk是Python的一个模块,用于将PIL库的图像转换为Tkinter库中的图像格式,方便在Tkinter GUI程序中显示和操作图像。

sklearn.metrics

sklearn.metrics是Scikit-learn库的一个模块,提供了一系列的评估指标和工具,用于评估模型的性能和质量,包括分类、回归、聚类等算法的评估。

sys

sys是Python中的一个内置模块,提供了对Python解释器的访问和控制,包括操作系统、环境变量、标准输入输出等功能。

numpy

NumPy是Python的一个非常重要的数值计算库,提供了各种多维数组操作、线性代数、傅里叶变换、随机数生成等函数,是数据分析、科学计算和机器学习等领域中必不可少的工具之一。NumPy的核心是ndarray(N-dimensional array,多维数组),是一个具有相同类型和大小的元素网格。

SciPy

SciPy 是一个开源的,基于 Python 的计算生态系统,用于执行科学、工程和技术计算。它构建在 NumPy 库之上,为高级计算提供了许多实用的模块和函数。这些模块包括线性代数、优化算法、信号和图像处理、统计和随机模拟等,比如图像二值化和形态学操作等。

表1 此项目所涉及的第三方库或模块

用到的方法

用法

root = tk.Tk()

使用 tkinter 模块创建了一个 GUI 窗口的根节点对象(即一个 Tk() 对象)。

root.geometry("宽x高+

X轴偏移量+

Y轴偏移量")

设置窗口位置和大小的方法:“宽”表示窗口的宽度;“高”表示窗口的高度;“ X轴偏移量”表示窗口左上角相对于屏幕左边缘的横向偏移量;“Y轴偏移量”表示窗口左上角相对于屏幕上边缘的纵向偏移量。root.geometry('400x400')只设置宽度和高度,不设置偏移量,这样窗口将会出现在屏幕的中央

root.title("窗口标题")

设置窗口的标题,即在窗口的顶部栏中显示的文字。

filedialog.askopenfilename() 

方法用于打开一个文件对话框,并返回用户所选择文件的路径。该方法有以下参数:initialdir指定对话框中显示的初始目录;title指定对话框的标题;filetypes指定要显示哪些类型的文件,这里限定只能选择.jpg和.png两种类型的文件。iledialog.askopenfilename(initialdir='/',title='SelectImage',   filetypes=(('JPEG iles', '*.jpg'), ('PNG files', '*.png')))
参数设置为 '/',表示打开对话框时默认显示的目录为操作系统的根目录。具体来说,对于 Windows 系统,根目录为 C:\

background=frame.copy().

astype("float")

这行代码将一个名为"frame"的变量复制,并将其转换为浮点类型,并将其分配给名为"background"的新变量。这可能是用于计算视频分离技术中的背景模型的一部分。

root.mainloop()

进入窗口事件循环,等待并处理用户的交互,直到窗口被关闭为止,必须调用的方法。在创建窗口和完成窗口设置之后,该方法将进入一个无限循环,等待处理窗口事件(如鼠标点击、键盘按键、窗口关闭等)。只有当窗口被关闭时,该循环才会停止。

cv2.accumulateWeighted(

frame,

background,

accumulated_weight)

这段代码使用了OpenCV中的accumulateWeighted函数,用于在获取视频流过程中实时计算当前视频帧和背景的加权平均值,以更新背景模型。frame是当前读取的视频帧,background是当前背景模型,accumulated_weight是加权平均值的系数。通过计算加权平均值,程序可以利用背景模型来准确分析视频帧中的移动对象。随着时间的推移,背景模型将因为被动了多次而不断更新,以适应环境中的变化和移动对象。

cv2.absdiff(background.

astype('uint8'), frame)

这段代码使用了OpenCV中的absdiff函数,用于计算两个图像的差异(绝对值差分)。background是背景模型,即计算平均加权值得到的图像;frame是当前读取的视频帧。函数会返回两个图像的差分结果,表示这两个图像之间的绝对值差异。通过计算背景模型和当前视频帧之间的差异,程序可以检测到当前视频帧中的像素变化,例如移动对象、光线变化等。

ret, thresholded =

cv2.threshold(

diff, threshold_min, 255, cv2.THRESH_BINARY)

这段代码使用了OpenCV中的threshold函数,对输入图像进行二值化处理。

这个函数接受四个参数:源图像、阈值下限、最大值、二值化类型。在这段代码中,源图像是前面计算得到的绝对值差分图像diff,即被背景减去的当前视频帧图像。阈值下限是指二值化的阈值,这个值越大,二值化后图像中白色像素的数量会增加。最大值为255,表示二值化后白色像素的最大值为255。二值化类型选用的是THRESH_BINARY,意为将源图像中大于阈值的像素设为最大值,其余设为0。程序可以将绝对值差分图像进行二值化处理,进一步突出视频帧中的像素变化,方便后续步骤对移动目标进行跟踪和检测。

contours, hierarchy =

cv2.findContours(

thresholded.copy(),

cv2.RETR_EXTERNAL,

cv2.CHAIN_

APPROX_SIMPLE)

这段代码使用了OpenCV中的findContours函数,用于在二值图像中查找轮廓。三个参数分别表示二值图像,轮廓检索模式和轮廓逼近方法。传入的二值图像是前面通过阈值处理得到的图像,RETR_EXTERNAL表示只检测最外层轮廓,CHAIN_APPROX_SIMPLE表示对所有轮廓点只存储起点和终点,即不存储中间点。findContour函数将返回两个值:contours表示所有轮廓点的集合,每个轮廓是一组点坐标。hierarchy表示检测到的轮廓关系。通过该函数的处理,程序可以在二值化后的图像中查找出所有的轮廓,并将结果保存到contours和hierarchy变量中。在后续的处理过程中,程序可以利用这些轮廓来标记图像中的移动目标和区域。

hand_segment_max_cont =

max(contours, key=cv2.contourArea)

这段代码使用了Python中的内置函数max和OpenCV中的contourArea函数,用于找出所有轮廓中面积最大的轮廓。max函数需要传入两个参数:第一个参数是一个可迭代对象,这里是contours轮廓列表。第二个参数是一个关键字参数,用来指定按照哪个条件进行比较。在这里使用cv2.contourArea函数来指定比较条件,即按照轮廓的面积进行比较。程序通过这段代码的处理,能够找到所有轮廓中面积最大的轮廓,也就是视频帧中的手部轮廓。该处理过程是基于轮廓的面积大小,将面积最大的轮廓作为手部轮廓进行进一步处理和分析,以识别出手势信息、手势类型等。

conv_hull = cv2.

convexHull(

hand_segment)

这段代码使用了OpenCV中的convexHull函数,即对手部轮廓进行凸包检测。

该函数需要输入一个参数,即要检测凸包的轮廓。在这里,传入的参数是前面处理得到的手部轮廓,即hand_segment。函数的返回值是凸包的轮廓,即可以包住所有凸起部位的最小凸多边形,在这里保存在conv_hull变量中。通过这个处理过程,可以进一步对手部轮廓进行简化和优化,方便后续进行手势识别和跟踪。

cv2.circle(circular_roi,

(cX, cY),

radius, 255, 10)

这段代码使用了OpenCV中的circle函数,用于在图像上绘制一个圆形。

该函数需要输入五个参数:目标图像circular_roi,圆心坐标(cX, cY),半径大小radius,颜色255表示白色,线条宽度10。通过这个处理过程,在circular_roi图像上绘制了一个以手部轮廓重心为中心点,以radius为半径的白色圆形。这个圆形将作为手势识别的重要参考,并可以用于判断手部的位置和移动情况。

(x, y, w, h) = cv2.boundingRect(cnt)

这段代码使用了OpenCV中的boundingRect函数,用于计算轮廓的边界矩形。

该函数需要输入一个参数,即要计算边界矩形的轮廓。在这里,传入的参数是cnt,即前面处理得到的手部凸包轮廓。函数的返回值是一个包含四个值的元组,即(x, y, w, h),分别表示边界矩形的左上角点坐标和宽度、高度。通过这个处理过程,可以得到手部凸包轮廓的外接矩形,即可以用一个矩形框选出手部轮廓。这个矩形也可以作为手势识别的参考,用于规范化手部轮廓的形状和大小。

camera = cv2.

VideoCapture(0)

这段代码使用了OpenCV中的VideoCapture函数,即用于连接和初始化摄像头。

该函数需要输入一个参数,即要打开的摄像头索引。传入的参数是0,表示使用默认的摄像头,也可以传入其他索引值,如果有多个摄像头,则需要指定对应的索引值。函数的返回值是一个VideoCapture对象,可以用来检查摄像头是否成功打开,并进行后续操作。通过这个处理过程,可以将计算机连接的摄像头初始化,打开摄像头的实时视频流,用于后续的手势识别和跟踪。

frame =cv2.flip(frame, 5)

调用OpenCV中的flip函数,用于翻转图像。要翻转的图像frame和翻转类型5,传入的参数frame是用VideoCapture对象捕获的当前帧图像,翻转类型为5,表示对图像进行水平和垂直方向的翻转操作。函数的返回值是翻转后的图像。通过这个处理过程,可以将摄像头捕获的图像进行翻转,从而获得更合适的显示效果,并方便后续手势识别和跟踪的处理。

gray_frame = cv2.

cvtColor(roi,

cv2.COLOR

_BGR2GRAY)

调用OpenCV中的cvtColor函数,用于将图像从一种颜色空间转换为另一种。传入的参数roi是摄像头捕获图像中手部轮廓的区域,转换类型为cv2.COLOR_BGR2GRAY,表示将BGR三通道彩色图像转换为灰度图像。函数的返回值是转换后的图像。通过这个处理过程,可以将彩色图像转换成灰度图像,便于进行图像的二值化、边缘检测等后续操作,也可以减少计算量、提高处理速度。

gray_frame = cv2.

GaussianBlur(

gray_frame,

(7, 7), 0)

这段代码使用了OpenCV中的GaussianBlur函数,用于对图像进行高斯平滑滤波。

该函数需要输入三个参数,即要进行滤波的图像、滤波核的大小和标准差。在这里,传入的参数gray_frame表示要进行滤波的灰度图像,(7, 7)表示滤波核的大小为7x7,0表示标准差取默认值。函数的返回值是滤波后的图像。通过这个处理过程,可以消除图像中的高频噪声和不规则形状,平滑图像轮廓,便于后续处理。在手势识别中,这个处理过程可以提高图像的质量,从而提高识别精度。

cv2.putText(frame,

"Gesture:ONE",

(70,375), cv2.FONT_HERSHEY

_SIMPLEX,

1,(0,0,255),2)

这段代码使用了OpenCV中的putText函数,用于向图像中添加文本信息。该函数需要输入七个参数,分别是要添加文本信息的原始图像、要添加的文本内容、文本位置、字体、字体大小、颜色和线条粗细。在这里,传入的参数是frame表示原始图像、"Gesture: ONE"表示要添加的文本内容、(70, 375)表示文本位置、cv2.FONT_HERSHEY_SIMPLEX表示字体类型、1表示字体大小、(0, 0, 255)表示文本颜色、2表示线条粗细。函数的返回值是添加文本后的图像。通过这个处理过程,可以在视频中添加文本信息,如手势的种类和名称等,这有助于用户更好地理解视频内容,并方便后续处理。

cv2.drawContours(

frame, [hand_segment

+ (roi_right, roi_top)],

-1, (255, 0, 0), 1)

这段代码使用了OpenCV中的drawContours函数,用于在图像中绘制轮廓。该函数需要输入五个参数,分别是要绘制轮廓的原始图像、手部轮廓坐标点集合、轮廓索引、颜色和线条粗细。在这里,传入的参数是frame表示原始图像、手部轮廓的坐标点集合hand_segment + (roi_right, roi_top)表示绘制的轮廓,-1表示绘制所有轮廓,(255, 0, 0)表示轮廓颜色为蓝色,1表示轮廓线条粗细。函数的返回值是绘制轮廓后的图像。通过这个处理过程,可以在原始图像中标记出手部轮廓的范围,方便后续的处理和分析,同时也可以提高视频的可读性和观赏性。

cv2.imshow("Hand Gestures", frame)

这段代码使用了OpenCV中的imshow函数,用于显示图像。该函数需要输入两个参数,分别是窗口名称和要显示的图像。在这里,传入的参数是"Hand Gestures"表示窗口名称,frame表示要显示的图像。函数没有返回值,它会创建一个新的窗口并显示图像。通过这个处理过程,可以实时显示视频中的图像,便于用户观察手势识别的效果和结果。

keypress = cv2.

waitKey(1) & 0xFF

这段代码使用了OpenCV中的waitKey函数,用于等待用户按下键盘上的某个按键。函数需要输入一个参数,即等待的时间。在这里,传入的参数是1,表示等待1毫秒。函数的返回值是用户按下的键盘ASCII码值,这个值可能为-1(表示没有键被按下)或者一个0~255之间的整数值。这段代码中还执行了一个位运算操作& 0xFF,这是因为在某些平台上,waitKey函数返回的是一个32位的整数,在这种情况下,取低8位才能得到正确的ASCII码值。通过这个处理过程,可以实现用户与程序的交互。例如,在进行视频播放的时候,可以通过waitKey函数等待用户按下键盘上的某个键来实现暂停、快进、快退等操作。

cv2.destroyAllWindows()

这段代码使用了OpenCV中的destroyAllWindows函数,用于关闭所有已经打开的窗口。该函数无需输入参数,直接调用即可。函数没有返回值,它会关闭已经打开的所有OpenCV窗口。通过这个处理过程,可以及时释放内存资源,避免程序运行过程中发生内存泄漏。该函数通常用于退出程序前的操作。

表2 项目使用的主要方法

3 项目思路

这段代码实现了一个基于 OpenCV 和 Tkinter 的手势识别程序。程序以摄像头获取的图像为输入,使用计算机视觉的技术,对手部图像进行分割,并利用图像处理技术检测手指的数量,从而实现对手势的识别。

程序的主要流程如下:

1.创建一个桌面应用程序,包括一个基于 Tkinter 的 GUI 界面。

   

图3-1 GUI界面                                        图3-2获取摄像头图像

2.连接摄像头并实时获取摄像头的图像。

3 对图像进行预处理,包括灰度转换、高斯模糊、背景校准等步骤。

4.对手部图像进行分割,提取出手的区域。

5.计算手部图像中手指的数量,从而实现对手势的识别。

6.将识别结果显示在 GUI 界面中,并提供一个按钮,用于用户启动手势识别的主函数。

该程序主要分为两个功能模块:图像处理模块和GUI模块。

3.1 图像处理模块

该模块涉及 OpenCV 库的使用,主要任务是对摄像头获取到的图像进行预处理,提取手势部分图像,并计算手势数量,实现对手势的识别。具体步骤如下:

(1)连接摄像头并实时获取摄像头的图像,并进行水平翻转处理;

(2)选择感兴趣区域(ROI),即手的部分图像;

(3)将感兴趣区域转换为灰度图,并进行高斯模糊处理,以平滑化图像;

(4)在前60帧图像中进行背景校准,建立背景模型;

(5)对手部图像进行分割,提取出手的区域;

(6)计算手部图像中手指的数量,从而实现对手势的识别。

3.2 GUI模块

该模块涉及 Tkinter 库的使用,主要任务是在 GUI 界面中显示程序的输出结果,并提供一个按钮,用于启动手势识别的主函数。具体步骤如下:

(1)创建基于 Tkinter 的 GUI 界面,并提供一个文本部件,用于在 GUI 界面中显示程序的输出结果;

(2)将 print 函数的输出重定向到 write_to_text_widget 函数,以便将程序的输出结果添加到文本部件中进行显示;

(3)创建一个按钮,用于用户启动手势识别的主函数;

(4)进入消息循环,以等待用户与 GUI 界面进行交互。

4 源代码

import cv2
import numpy as np
from sklearn.metrics import pairwise
import tkinter as tk
from tkinter import filedialog
from PIL import Image, ImageTk
import sys

# 全局变量
background = None
accumulated_weight = 0.5
roi_top = 20
roi_bottom = 300
roi_right = 300
roi_left = 600

# 创建主窗口
root = tk.Tk()
root.geometry('400x400')
root.title("Output Results")


# 创建滚动条框架
# scroll_frame = tk.Frame(root)
# scroll_frame.pack(fill=tk.BOTH, expand=tk.YES)

# 创建滚动条
# scrollbar = tk.Scrollbar(scroll_frame)
# scrollbar.pack(side=tk.RIGHT, fill=tk.Y)

# 创建画布
# canvas = tk.Canvas(scroll_frame, yscrollcommand=scrollbar.set)
# canvas.pack(fill=tk.BOTH, expand=tk.YES)

# 选择图片文件
def select_image():
    filename = filedialog.askopenfilename(initialdir='/', title='Select Image',
                                          filetypes=(('JPEG files', '*.jpg'), ('PNG files', '*.png')))
    load_image(filename)


# 加载和显示图片
def load_image(filename):
    # 在 Canvas 中绘制图片
    image = Image.open(filename)
    # 缩放图片大小以适应 Canvas
    while image.width > canvas.winfo_width() or image.height > canvas.winfo_height():
        image = image.resize((int(image.width * 0.9), int(image.height * 0.9)))
    photo = ImageTk.PhotoImage(image)
    canvas.create_image(0, 0, image=photo, anchor=tk.NW)
    canvas.image = photo


# 初始化函数
def calc_accum_avg(frame, accumulated_weight):
    global background

    if background is None:
        background = frame.copy().astype("float")
        return None

    cv2.accumulateWeighted(frame, background, accumulated_weight)


# 获取ROI区域
def segment(frame, threshold_min=25):
    global background

    diff = cv2.absdiff(background.astype('uint8'), frame)

    ret, thresholded = cv2.threshold(diff, threshold_min, 255, cv2.THRESH_BINARY)

    contours, hierarchy = cv2.findContours(thresholded.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

    if len(contours) == 0:
        return None
    else:
        # 获得最大的轮廓区域
        hand_segment_max_cont = max(contours, key=cv2.contourArea)

        return (thresholded, hand_segment_max_cont)


# 手指数计算函数
def count_fingers(thresholded, hand_segment):
    conv_hull = cv2.convexHull(hand_segment)

    top = tuple(conv_hull[conv_hull[:, :, 1].argmin()][0])
    bottom = tuple(conv_hull[conv_hull[:, :, 1].argmax()][0])
    left = tuple(conv_hull[conv_hull[:, :, 0].argmin()][0])
    right = tuple(conv_hull[conv_hull[:, :, 0].argmax()][0])

    cX = (left[0] + right[0]) // 2
    cY = (top[1] + bottom[1]) // 2

    distance = pairwise.euclidean_distances([(cX, cY)], Y=[left, right, top, bottom])[0]
    max_distance = distance.max()
    radius = int(0.9 * max_distance)
    circumference = (2 * np.pi * radius)
    circular_roi = np.zeros(thresholded.shape[:2], dtype="uint8")
    cv2.circle(circular_roi, (cX, cY), radius, 255, 10)
    circular_roi = cv2.bitwise_and(thresholded, thresholded, mask=circular_roi)
    contours, hierarchy = cv2.findContours(circular_roi.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
    count = 0

    for cnt in contours:
        (x, y, w, h) = cv2.boundingRect(cnt)
        out_of_wrist = (cY + (cY * 0.25)) > (y + h)
        limit_points = ((circumference * 0.25) > cnt.shape[0])
        if out_of_wrist and limit_points:
            count += 1

    return count


# 识别手势主函数
def identify_gesture():
    global accumulated_weight
    camera = cv2.VideoCapture(0)  # 摄像头

    num_frames = 0
    calibrated = False

    while True:
        (grabbed, frame) = camera.read()
        frame = cv2.flip(frame, 5)

        roi = frame[roi_top:roi_bottom, roi_right:roi_left]

        gray_frame = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY)
        gray_frame = cv2.GaussianBlur(gray_frame, (7, 7), 0)

        if num_frames < 60:
            calc_accum_avg(gray_frame, accumulated_weight)
            if num_frames < 59:
                cv2.putText(frame, "CALIBRATING...", (100, 100), cv2.FONT_HERSHEY_TRIPLEX, 1, (0, 0, 255), 2)
        else:
            hand = segment(gray_frame)

            if hand is not None:
                (thresholded, hand_segment) = hand

                cv2.drawContours(frame, [hand_segment + (roi_right, roi_top)], -1, (255, 0, 0), 1)

                fingers = count_fingers(thresholded, hand_segment)

                if fingers == 1:
                    cv2.putText(frame, "Gesture: ONE", (70, 375), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
                    print("Gesture: ONE")
                elif fingers == 2:
                    cv2.putText(frame, "Gesture: TWO", (70, 375), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
                    print("Gesture: TWO")
                elif fingers == 3:
                    cv2.putText(frame, "Gesture: THREE", (70, 375), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
                    print("Gesture: THREE")
                elif fingers == 4:
                    cv2.putText(frame, "Gesture: FOUR", (70, 375), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
                    print("Gesture: FOUR")
                elif fingers == 5:
                    cv2.putText(frame, "Gesture: FIVE", (70, 375), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
                    print("Gesture: FIVE")
                elif fingers == 6:
                    cv2.putText(frame, "Gesture: SIX", (70, 375), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
                    print("Gesture: SIX")
                elif fingers == 7:
                    cv2.putText(frame, "Gesture: SEVEN", (70, 375), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
                    print("Gesture: SEVEN")
                elif fingers == 8:
                    cv2.putText(frame, "Gesture: EIGHT", (70, 375), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
                    print("Gesture: EIGHT")
                elif fingers == 9:
                    cv2.putText(frame, "Gesture: NINE", (70, 375), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
                    print("Gesture: NINE")
                else:
                    cv2.putText(frame, "Gesture: NONE", (70, 375), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
            else:
                cv2.putText(frame, "No hand detected", (200, 200), cv2.FONT_HERSHEY_TRIPLEX, 1, (0, 0, 255), 2)

        # 显示帧
        cv2.imshow("Hand Gestures", frame)
        keypress = cv2.waitKey(1) & 0xFF

        # 按 'q' 退出循环
        if keypress == ord("q"):
            break

        num_frames += 1
    # 关闭窗口
    cv2.destroyAllWindows()
    # 释放摄像头资源
    camera.release()


# 向gui界面中写入内容
def write_to_text_widget(s):
    # 在文本区域中添加内容
    output_text.insert(tk.END, s)


output_text = tk.Text(root)
output_text.pack()

# 重定向标准输出到 write_to_text_widget 函数
sys.stdout.write = write_to_text_widget

# 创建按钮
button = tk.Button(text='开始识别', command=identify_gesture)  # 与图片的识别相关联
button.pack(side=tk.TOP)

# 进入消息循环
root.mainloop()

5 运行结果图

(由于需要人脸出境,具体截图我就不放了,大家可以自己试一下)

图5-1 运行结果为“1”

图5-2 运行结果为“2”

图5-3 运行结果为“3”

图5-4 运行结果为“4”

图5-5 运行结果为“5”

图5-6 每一帧检测结果

6 调试日志

1.在最开始No module named 'cv2',修改方法:pip install opencv-python。

2.在刚确定选题的时候,想借助网上的学习模型,也找到了一个很合适的学习模型,但是是外网的。首先,到Kaggle网站上注册一个账https://www.kaggle.com/登录后,搜索“American Sign Language”或“ASL”,可以找到该数据集:https://www.kaggle.com/grassknoted/asl-alphabet。下一步就是下载,在这之前不需要登陆,但下载必须要登录。要登录必须要先有账号,但是在注册账号的时候并不能正常的显示人机验证的验证码,报错如图显示,具体内容为“Captcha must be filled out.”意思就是验证码必须要填写,但是我看不到验证码。

图6-1 Kaggle网站注册界面(图上有隐私,暂不放了)

借助网络求助,给出的方法是可以借助浏览器插件显示验证码,安装了两个插件,因为两个名字虽然一样,但是来自于不同的发布者,并且其中一个已经不能用了。所以又下载了另外一个插件,开启后,依旧不显示相关的验证码,于是放弃了机器学习的这个方法。

图6-2 插件安装成功界面

3.摄像头关不上。在代码中运用了一个循环,while true,就是默认在所有的情况下都能执行,因为每一帧都需要展示在画面中,每一帧都需要,所以这个循环默认一直可以执行。当大部分代码都快写完的时候,运行时却发现关不上摄像头,尝试了很多办法,一直不能解决,当我以为我写的这些东西都不对的时候,我发现是因为循环的原因,需要加一个break条件语句,这样才可以直接关闭。我本想的是直接点击那个叉号,但是关不掉,关闭又能弹出来,想设置一条语句,能够解决这个问题,但是并不能很好的解决,会有问题在里面。所以,采用了键盘输入某个值的方法,其实是利用了Ascll码来确定输入的内容是不是和已经确定的字符ascll码相同。

4.module 'cv2' has no attribute 'waitkey'这个错误信息提示是cv2库中没有waitkey函数,其中的一个原因可能是waitkey() 函数名前拼写错误了,修正为waitKey()函数即可。尝试修改cv2.waitkey()为cv2.waitKey(),然后再运行代码。

5.图片太大了无法全部显示。在刚开始想利用学习模型开始写的时候,想的是先从图片开始,然后再加上视频模块进行识别,但是机器学习模型的学习数据没有下载下来,自己确定学习模型的话需要很长的周期。所以在最后的时候,选择创建一个画布,然后加上了canvas = tk.Canvas(scroll_frame, yscrollcommand= scrollbar.set) canvas.pack(fill=tk.BOTH, expand=tk.YES)代码,便能够正常显示。

图6-3 画布无法显示

图6-4 可以适应图片的GUI界面

6.gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)报错:从网上查找相关的内容,表示定位到转灰度时图片要为三通道,否则报错,在转灰度前,加上一部分代码将图转为三通道,但是由于版本的问题仍然报错,换了一个程序后便可以正常运行。

7.当我把输出结果放在gui界面上时,button按钮不见了。把输出结果的text关闭了,button能够显示出来,但是依旧在最下面,考虑是不是因为滚动条的原因,不是画布的原因,当我把滚动条关闭后,可以显示出来了。

图6-5 关闭画布后可以正常显示button

8.感觉不稳定,识别的有问题。后续需要对肤色、轮廓、凸包的确定和计算以及轮廓的逼近方法再进行新的尝试。

图6-6 控制台识别不准确

图6-7 错误识别结果为“4”(暂不放)

图6-8 错误识别结果为“1”

9.利用pyinstaller -F -w hand.py打包成为可执行程序时,也可以正常地执行,但打开后出现问题,从网络上查找相关地内容,表示将原有的文件删掉,再执行一遍这个代码,就可以了。但我删掉原来的.exe文件后,依旧报错。根据报错内容,可能是因为我的那部分将控制台上的输出内容放到gui界面中的问题,没有找到合适的替换方法。

图6-9 利用dos界面打包

图6-10 打包完成

图6-11 打包为可执行程序文件后打不开

尝试https://blog.csdn.net/lwww3333/article/details/119292678文章中的方法,首先是安装库pip install pipenv,如图所示。但是很多库怎样在虚拟环境中下载,以及下载会遇到很多很多我自己解决不了的问题,所以最终也没有打包成功。如果我将最后的那个将控制台的信息放到gui界面中,就能够正常打包了。

图6-12 使用dos安装pipenv界面

图6-13 使用dos建立虚拟背景

10.最开始是在jupyter notebook中写的代码,所以很多库都安装在了anaconda的目录下,所以后来将代码在pycharm中运行时显示没有那几个库,显示如图。解决方法有两个:第一个是将其他目录下的库复制到pycharm的目录下,即可使用。但我尝试了这个方法,并不能解决我的问题。第二种方法就是在pycharm中安装库,如图所示。这种方法可以保证能够库的引用和正常的使用。

图6-14 使用dos安装sckit-learn库出现问题

图6-15 使用pycharm安装库

11.在代码中尝试测试程序使其运行结果为“6-9”,但最终由于轮廓、光线以及背景的原因,并没有成功,所以并未测试成功。

图6-16 尝试手势6的测试

图6-17 尝试手势7的测试

图6-18 正常测试由于环境因素出现错误

12.在本次项目的实现过程中,还有很多认知以及代码上的错误,这些错误有些是因为粗心大意,有些是因为自己的认知不够,导致出现的一系列问题。但是由于这些问题比较普通,以后在项目的优化以及学习中都会着重注意这些问题,便不在此处赘述了。

7 项目优化

由运行结果以及多次实践可以看出来,程序对环境的要求较高。后期项目进行优化,需要从背景替换、肤色检测、创建机器学习模型、轮廓获取的优化等问题方面进行调整和改正,从而降低对环境的要求。以下为网络上已成熟的检测结果及相关图片:

图7-1 分割图及手势分割

图7-2 灰度图的提取

图7-3 图像色度的调整


http://www.kler.cn/a/408228.html

相关文章:

  • OCR-free Document Understanding Transformer
  • 【django】扩展
  • TCP为什么需要三次握手?两次握手或四次握手可以吗?
  • LeetCode 904.水果成篮
  • YOLOv10改进,YOLOv10添加KANConv卷积,CVPR2024
  • Spring Boot OA:打造现代化企业办公环境
  • Web 端语音对话 AI 示例:使用 Whisper 和 llama.cpp 构建语音聊天机器人
  • 4.1_未授权漏洞
  • DataGrip 连接 Redis、TongRDS
  • 【FPGA-MicroBlaze】串口收发以及相关函数讲解
  • docker搭建私有的仓库
  • Python和R基因组及蛋白质组学和代谢组学
  • Java技术分享
  • 运维Tips:Docker或K8s集群拉取Harbor私有容器镜像仓库配置指南
  • 《泰拉瑞亚》PC+手机版安装教程
  • ubuntu 安装 yum 无法定位问题
  • 云计算-华为HCIA-学习笔记
  • 经验笔记:Git 中的远程仓库链接及上下游关系管理
  • mybatis学习(一)
  • Transformer详解及衍生模型GPT|T5|LLaMa