图像处理算法识别手势
目录
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'))) |
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 图像色度的调整