跳转至

2024 RoboCup@Home What is that 项目准备记录

约 3124 个字 • 196 行代码

由于这个项目需要识别出手指向物体👉的手势,所以就在网上查找实现了相关功能的现成的库,

16日

查阅MediaPipe库说明

于是查到谷歌的MediaPipe库中有手势识别的功能,

手势识别功能的python示例代码(只识别图片)web示例(调用摄像头实时视频识别)

然后尝试运行python示例代码并安装相应的环境,

一番操作后能成功运行代码并在图像上显示手的关键点。


github开源手势识别仓库

在github上搜索手势识别相关的开源仓库,然后发现一个说明用日文写的使用mediapipe来进行手势识别的仓库

克隆并尝试运行 app.py ,发现没有tensorflow,然后安装tensorflow后就能正常运行了,

神奇的是,我已安装的python包的版本基本上都高于 requirements.txt 中的包版本,但还是可以运行😄

发现这个仓库可以识别到指👉的手势(各个方向都可以,直接使用mediapipe似乎只能识别指向上的手势),并特别显示指尖的关键点,所以感觉很符合What is that项目的要求,

于是开始理解 app.py 中的代码,

借助copilot来解释 app.py 中的 main() 函数

对话记录

RonaldLN:

@workspace /explain

 

GitHub Copilot:

已使用 1 引用
  • app.py: 41-181

这段代码是一个手势识别程序的主要部分。它使用了OpenCV和MediaPipe库来捕获和处理视频帧,以识别和跟踪手的位置和手势。

首先,代码通过get_args()函数获取命令行参数,这些参数包括设备ID、图像宽度和高度、是否使用静态图像模式以及最小检测和跟踪置信度等。然后,使用这些参数初始化一个OpenCV的VideoCapture对象,用于从指定设备捕获视频。

接下来,代码加载了MediaPipe的手部解决方案模型,并创建了一个Hands对象。这个对象会用于后续的手部关键点检测。同时,还初始化了两个分类器,一个用于关键点分类,另一个用于点历史分类。

然后,代码从CSV文件中读取了关键点分类器和点历史分类器的标签。这些标签将用于后续的手势识别。

在主循环中,代码首先获取当前的FPS值,然后检测是否按下了ESC键。如果按下了ESC键,程序将退出。然后,代码从VideoCapture对象中读取一帧图像,并将其翻转,以实现镜像显示。

接着,代码将图像从BGR格式转换为RGB格式,并将其传递给Hands对象进行处理。如果在图像中检测到了手,Hands对象将返回一个包含手部关键点的结果。

对于检测到的每一只手,代码首先计算其外接矩形和关键点列表,然后对关键点列表和点历史进行预处理,并保存学习数据。然后,代码使用关键点分类器对预处理后的关键点列表进行分类,如果检测到了指向手势,就将人指关键点添加到点历史中,否则添加[0,0]。

然后,代码检查点历史的长度,如果长度等于历史长度的两倍,就使用点历史分类器对预处理后的点历史进行分类。然后,代码将分类结果添加到手势历史中,并计算最常见的手势ID。

最后,代码将外接矩形、关键点、信息文本等绘制到图像上,并显示图像。当退出主循环后,代码释放VideoCapture对象,并销毁所有OpenCV窗口。

发现 main 中的 主循环 部分代码的结构大致是这样

app.py
while True:

    ### 获取图像并进行处理和识别,
    ### 将识别结果保存在 result 中,
    ### 如果检测到按ESC键则退出程序

    #  ####################################################################
    if results.multi_hand_landmarks is not None:
        for hand_landmarks, handedness in zip(results.multi_hand_landmarks,
                                              results.multi_handedness):
            ### 如果识别到手,进行一些处理

            ### 如果识别到是指的手势(hand_sign_id 值为 2),
            ### 就将指尖的坐标添加到历史关键点列表中

            # ハンドサイン分類
            hand_sign_id = keypoint_classifier(pre_processed_landmark_list)
            if hand_sign_id == 2:  # 指差しサイン
                point_history.append(landmark_list[8])  # 人差指座標
            else:
                point_history.append([0, 0])

            ### 剩余的一些其他处理

    else:
        point_history.append([0, 0])

    debug_image = draw_point_history(debug_image, point_history)
    debug_image = draw_info(debug_image, fps, mode, number)

    # 画面反映 #############################################################
    cv.imshow('Hand Gesture Recognition', debug_image)

github上23年RoboCup@Home What is that 项目实现代码仓库

bing上搜索时,无意中发现github上有一个仓库存了东南大学一个队伍23年完成what is that题目(以及一些其他题目的),

JeexiongRen/RoboCup-home-What-is-that (github.com)


17日

了解到似乎东南大学那个仓库不太适合,所以打算借助日本那个仓库来实现。

参考 mrsupw_vison_publisher.py 编写&测试简单的摄像头数据的读取

由于摄像头的使用需要调用 PyK4A 这个包,所以参考已有的python文件,摘取必要的代码进行运行测试

#!/usr/bin/python3
# -*- coding: utf-8 -*-
import argparse
import sys
import time

import cv2
import rospy
from geometry_msgs.msg import *
from pyk4a import PyK4A, ColorResolution, Config
from std_msgs.msg import *
from xm_msgs.msg import *


def main():
    config = Config(color_resolution=ColorResolution.RES_720P)
    camera = PyK4A(config)
    camera.start()
    capture = camera.get_capture()

    cv2.imshow("Image", capture.color)
    print(type(capture.color))
    cv2.waitkey()

if __name__ == '__main__':
    main()

运行发现能显示拍照的图像,并且 capture.color 的类型是 numpy.ndarray 所以可以直接使用 cv2.imshow 显示出来。

个人理解
config = Config(color_resolution=ColorResolution.RES_720P)
camera = PyK4A(config)
camera.start()

是设置摄像头参数并启动摄像头

capture = camera.get_capture()

读取实时的图像,

而在 capture 中有

  • color : 彩色帧
  • depth : 深度帧

值得注意的是,如果 cv2.waitkey(n) 中设置的数值太小,或没有设置,那么会显示不出图像,

对话记录

User:

key = cv.waitKey(10)

是什么意思?

 

Wenxin:

在OpenCV中,cv2.waitKey(delay)函数用于等待一个键盘事件。参数delay是以毫秒为单位的等待时间。该函数返回被按下的键的ASCII码,或者如果在这段时间内没有按键被按下,则返回-1。

在你提供的代码片段中:

key = cv2.waitKey(10)

cv2.waitKey(10)会等待10毫秒,然后返回被按下的键的ASCII码,或者如果没有按键被按下则返回-1。这通常用在循环中,以便持续显示图像并检测用户的按键事件。

例如,在显示视频或实时图像流时,你可能会这样使用cv2.waitKey

import cv2  

# 假设cap是一个VideoCapture对象,它打开了视频文件或相机流  
cap = cv2.VideoCapture(0)  

while True:  
    # 从VideoCapture对象中读取一帧图像  
    ret, frame = cap.read()  

    # 如果成功读取帧,ret为True  
    if ret:  
        # 显示图像  
        cv2.imshow('Video', frame)  

        # 等待1毫秒,检测按键  
        key = cv2.waitKey(1)  

        # 如果按下'q'键,退出循环  
        if key == ord('q'):  
            break  
    else:  
        # 如果没有成功读取帧(例如,视频播放结束),也退出循环  
        break  

# 释放VideoCapture对象并销毁所有窗口  
cap.release()  
cv2.destroyAllWindows()

在这个例子中,cv2.waitKey(1)使程序每1毫秒检查一次是否有按键被按下。如果用户按下了'q'键(其ASCII码为113),则程序通过break语句退出循环。注意,ord('q')函数用于获取字符'q'的ASCII码。

cv2.waitKey函数在实时视频处理和用户交互中非常有用,因为它允许程序在显示图像的同时响应用户的输入。

仿照 hand-gesture-recognition-using-mediapipe 中的 app.py

def main():
    config = Config(color_resolution=ColorResolution.RES_720P)
    camera = PyK4A(config)
    camera.start()

    while True:
        capture = camera.get_capture()

        cv2.imshow("Image", capture.color)
        cv2.imshow("Image2", capture.depth)

        key = cv2.waitkey(10)
        if key == 27:
            break

测试能正常显示彩色帧和深度帧的视频流。


修改 hand-gesture-recognition-using-mediapipe

先是询问了copilot一下读取图像之后进行处理的部分

对话记录

RonaldLN:

@workspace /explain Write an explanation for the active selection as paragraphs of text.

# カメラキャプチャ #####################################################
ret, image = cap.read()
if not ret:
    break
image = cv.flip(image, 1)  # ミラー表示
debug_image = copy.deepcopy(image)

 

GitHub Copilot:

已使用 6 引用
  • app.py: 110-116
  • .../copy.py: 128
  • .../cv2/init.pyi: 3312
  • .../cv2/init.pyi: 3314
  • .../cv2/init.pyi: 5614
  • .../cv2/init.pyi: 5616

这段代码是从摄像头捕获图像并进行处理的部分。

首先,使用 cap.read() 方法从摄像头捕获一帧图像。这个方法返回两个值:一个布尔值 ret 和一个图像 image。如果 retFalse,那么说明没有成功读取到图像,可能是因为摄像头没有正确打开或者已经没有更多的帧可以读取。在这种情况下,代码会执行 break 语句,跳出当前的循环。

如果成功读取到图像,那么代码会使用 OpenCV 的 cv.flip 函数将图像进行翻转。这里的参数 1 表示沿着垂直轴翻转图像,也就是实现了镜像效果。这样做的目的是让图像的显示方式更符合人的直觉。

最后,代码使用 copy.deepcopy 函数创建了图像的一个深拷贝,保存在 debug_image 变量中。深拷贝意味着 debug_imageimage 是完全独立的两个对象,修改其中一个不会影响另一个。这样做的目的是为了后续的调试或者处理,避免直接修改原始图像。

然后就修改了部分代码

# カメラ準備 ###############################################################
# cap = cv.VideoCapture(cap_device)
# cap.set(cv.CAP_PROP_FRAME_WIDTH, cap_width)
# cap.set(cv.CAP_PROP_FRAME_HEIGHT, cap_height)
config = Config(color_resolution=ColorResolution.RES_720P)
camera = PyK4A(config)
camera.start()
    while True:
        ...

        # カメラキャプチャ #####################################################
        # ret, image = cap.read()
        # if not ret:
        #     break
        image = capture.color
        image = cv.flip(image, 1)  # ミラー表示
        debug_image = copy.deepcopy(image)

尝试返回指尖的深度帧数据,

# ハンドサイン分類
hand_sign_id = keypoint_classifier(pre_processed_landmark_list)
if hand_sign_id == 2:  # 指差しサイン
    point_history.append(landmark_list[8])  # 人差指座標
    fingerltip_y, fingerltip_x = andmark_list[8]
    print(capture.depth[fingerltip_x, fingerltip_y])
else:
    point_history.append([0, 0])

运行测试,能发挥之间的深度数据,但是发现彩色帧和深度帧的大小和范围不一样(因此是错误的深度数据),需要进行对齐


18日

对齐彩色帧和深度帧,实现获取正确深度数据

发现 capture 中有 transformed_colortransformed_depth

分别是调整成和深度帧一样大小并把无深度数据位置的像素去除的彩色帧,和调整成彩色帧大小的深度帧,

于是打算使用 transformed_depth ,并做了一下修改

# カメラキャプチャ #####################################################
# ret, image = cap.read()
# if not ret:
#     break
image = capture.color
depth_image = capture.transformed_depth
image = cv.flip(image, 1)  # ミラー表示
depth_image = cv.flip(depth_image, 1)
debug_image = copy.deepcopy(image)
# ハンドサイン分類
hand_sign_id = keypoint_classifier(pre_processed_landmark_list)
if hand_sign_id == 2:  # 指差しサイン
    point_history.append(landmark_list[8])  # 人差指座標
    fingerltip_y, fingerltip_x = andmark_list[8]
    print(depth_image[fingerltip_x, fingerltip_y])
else:
    point_history.append([0, 0])

29日

解决新电脑 K4AException 的错误

由于更换使用新的主机电脑,在新电脑上尝试使用 PyK4A 来调用摄像头时,却显示了 K4AException 的错误,

而使用opencv来调用摄像头( cv2.VideoCapture(0) )却能显示出拍摄的画面,

于是尝试询问copilot

对话记录

RonaldLN:

我在启动摄像头时,引起了 K4AException 的错误,这是为什么?

 

GitHub Copilot:

K4AException 是在 _verify_error 函数中引发的,当 res 参数等于 Result.Failed 时。这通常意味着 Kinect for Azure(K4A)模块在尝试执行某项操作时遇到了错误。

这可能是由于多种原因,例如:

  • 摄像头未正确连接或配置
  • 驱动程序或库的问题
  • 硬件故障

为了更准确地确定问题,你可能需要查看引发 K4AException 的具体上下文,或者查看任何相关的错误日志或消息。

 

RonaldLN:

我使用opencv videocapture能正常调用摄像头读取视频画面,但是使用pyk4a却不能读取出图像画面

 

GitHub Copilot:

这可能是由于以下几个原因:

  1. Kinect for Azure(K4A)的设备索引与 OpenCV 的设备索引不同。在 OpenCV 中,你可能使用的是设备索引 0,但在 K4A 中,你可能需要使用不同的设备索引。
  2. K4A 需要特定的驱动程序和库,而这些可能没有正确安装或配置。
  3. K4A 可能需要更高的权限才能访问摄像头。

你可以尝试以下步骤来解决问题:

  1. 确保你已经正确安装了所有必要的 K4A 驱动程序和库。
  2. 尝试使用不同的设备索引来初始化 K4A。
  3. 确保你的程序有足够的权限来访问摄像头。
  4. 检查是否有任何与 K4A 相关的错误日志或消息,这可能会提供更多关于问题的信息。

如果问题仍然存在,你可能需要查看更详细的错误信息,或者联系 K4A 的支持团队以获取帮助。

于是我怀疑调用这个摄像头还有其他的驱动之类的需要安装,然后又在 PyK4A 仓库的 REAME.md 中看到了

Prerequisites

The Azure-Kinect-Sensor-SDK is required to build this library. To use the SDK, refer to the installation instructions here.

所以前去查看这个SDK。

Installation

To use the SDK, please refer to the installation instructions in usage

前往查看安装说明

  • AMD64 users, please follow these instructions to configure Microsoft's Package Repository on your machine.

看到配置的步骤(但不确定是不是必须)

Debian-based Linux distributions

  • Download the repo config package: curl -sSL -O https://packages.microsoft.com/config/<distribution>/<version>/packages-microsoft-prod.deb
  • Install the repo config package: sudo dpkg -i packages-microsoft-prod.deb
  • Delete the repo config package after installing: rm packages-microsoft-prod.deb
  • Update package index files: sudo apt-get update
  • To install the Microsoft product package you're after using this Linux repository (packages.microsoft.com): sudo apt-get install <package-name>

然后安装3个包

  • libk4a1.4
  • libk4a1.4-dev
  • k4a-tools

之后还要按照这里添加个文件

Azure-Kinect-Sensor-SDK/docs/usage.md#device-setup

过程中偶然搜索到一个文档,说明了ubuntu 18.04上安装azure kinect sdk的大致过程

Notes on Setting up the Microsoft Azure Kinect on Ubuntu 18.04 (github.com)

并通过这个文档发现,安装好之后可以通过

k4aviewer

来打开一个关于摄像头的界面

最后就可以正常使用 PyK4A 这个包来调用摄像头了


(5月)5日

为了节省资源的消耗,减少识别所需的区域,打算沿着手指方向延长一定长度取一个点,在以这个点为中心取一个矩形,

借助copilot得到代码

if hand_sign_id == 2:  # 指差しサイン
    point_history.append(landmark_list[8])  # 人差指座標

    point1, point2, point3 = landmark_list[8], landmark_list[6], landmark_list[5]
    # 给定两点,画线
    cv.line(debug_image, (int(point1[0]), int(point1[1])),
            (int(point3[0]), int(point3[1])), (0, 0, 255), 5)

    # 向point1方向延长指定距离,画一个点
    point1 = np.array(point1)
    point2 = np.array(point2)
    point3 = np.array(point3)
    # 两点间的向量
    vector = point1 - point3
    # 向量长度
    length = np.sqrt(np.sum(np.square(vector)))
    # 归一化向量
    unit_vector = vector / length
    # 延长长度
    point4 = point1 + unit_vector * 100

    cv.circle(debug_image, (int(point4[0]), int(point4[1])), 5, (0, 0, 255), -1)

    # 以point4为中心,画一个矩形,大小为图像的20%
    x, y = int(point4[0]), int(point4[1])
    w = int(cap_width * 0.2)
    h = int(cap_height * 0.2)
    cv.rectangle(debug_image, (x - w // 2, y - h // 2), (x + w // 2, y + h // 2), (0, 0, 255), 2)

    # 将矩形区域的图像保存下来
    cv.imwrite('test.jpg', debug_image[y - h // 2: y + h // 2, x - w // 2: x + w // 2])
  • landmark_list[8] : 食指指尖关键点
  • landmark_list[6] : 食指近侧指间关节关键点
  • landmark_list[5] : 食指指根关键点

效果如下:

what_is_that_point

由于之后发现,框有些小,于是决定在纵向上增大一倍(横向上的范围刚刚好),并且测试时发现 imwrite 有出现过错误,于是放到了 try 里面

# 以point4为中心,画一个矩形,大小为图像的20%
x, y = int(point4[0]), int(point4[1])
w = int(cap_width * 0.2)
h = int(cap_height * 0.4)
cv.rectangle(debug_image, (x - w // 2, y - h // 2), (x + w // 2, y + h // 2), (0, 0, 255), 2)

# 将矩形区域的图像保存下来
try:
    cv.imwrite('test.jpg', debug_image[y - h // 2: y + h // 2, x - w // 2: x + w // 2])
except:
    pass

最后更新: 2024-05-06
创建日期: 2024-04-16

评论