点击上方“3D视觉工坊”,选择“星标”

干货第一时间送达

作者丨磐怼怼

来源丨深度学习与计算机视觉

介绍

大家好!虽然赛博朋克还没有进入我们的生活,神经接口也远非理想,但激光雷达可以成为机械手未来之路的第一阶段。因此,为了在假期期间不感到无聊,我决定幻想一下计算机的控制,可能还有任何设备,比如挖掘机、宇宙飞船、无人机或火炉。

主要思想是移动鼠标,不是移动整只手,而是只移动食指,这将使你可以在不将手离开键盘的情况下浏览菜单,按下按钮,并与热键一起变成一个真正的键盘忍者!如果添加滑动或滚动手势会发生什么?我想会有炸弹!(但这一刻我们还需要等待几年)

让我们开始组装我们未来机械手的原型

你需要:

  1. 带有 LiDAR Intel Realsense L515 的摄像头。

  2. 能够在python中编程

  3. 记住学校数学

  4. 安装在监视器上的相机又名三脚架

我们用全球速卖通的三脚架将相机固定在上,结果证明非常方便,轻便且便宜

我们弄清楚如何制作原型

有许多方法可以完成这项任务。你可以自己训练检测器或手部分割,裁剪右手的结果图像,然后将这个来自 Facebook 研究的精彩存储库应用于图像,获得出色的结果。

要使用媒体管道存储库,阅读此链接后,你可以知道这是当今最好的选择之一:https://ai.googleblog.com/2019/08/on-device-real-time-hand-tracking-with.html

首先,一切都已经开箱即用 - 安装和启动将需要 30 分钟,考虑到所有先决条件。

其次,得益于强大的开发团队,他们不仅采用了手部姿势估计的最新技术,还提供了易于理解的 API。

第三,网络已准备好在 CPU 上运行,因此进入门槛极低。

可能你会问我为什么不使用本次比赛获胜者的存储库。

事实上,我详细研究了他们的解决方案,他们已经准备好了,没有成堆的数百万个网格等。但在我看来,最大的问题是他们处理深度图像。

由于他们是学者,他们毫不犹豫地通过Matlab转换了所有数据,另外,拍摄深度的分辨率在我看来很小。这可能会对结果产生深远的影响。

因此,似乎最简单的方法是获取RGB图片中的关键点,并通过XY坐标取深度帧中沿Z轴的值。现在的任务不是对某些东西进行太多的优化。因此我们将从开发的角度进行优化,因为它更快。

记住学校数学

正如我已经写过的,为了获得鼠标光标所在点的坐标,我们需要构建一条穿过手指指骨的两个关键点的线,并找到该线与该点的交点显示器的平面。

图片示意性地显示了显示器的平面和与其相交的线。你可以看看此处的数学。

使用两点,我们得到空间中直线的参数化表示。

我不会过多关注学校的数学课程。

安装用于使用相机的库

这可能是这项工作中最难的部分。事实证明,Ubuntu 相机的软件非常粗糙,充斥着各种各样的bug。

直到现在,我还是没能打败相机的奇怪行为,有时它在启动时不加载参数。

重启电脑后相机只能工作一次!但是有一个解决方案:在每次启动之前,对相机进行软件硬重置,重置USB,也许一切都会好起来。

顺便说一下,对于 Windows 10,一切都很好。但是,开发人员想像一个基于 Windows 的机器人这件事情就很奇怪。

要真正了解 Ubuntu 20,请执行以下操作:

$ sudo apt-get install libusb-1.0-0-dev
Then rerun cmake and make install. Here is a complete recipe that worked for me:
$ sudo apt-get install libusb-1.0-0-dev
$ git clone https://github.com/IntelRealSense/librealsense.git
$ cd librealsense/
$ mkdir build && cd build

通过分类收集,它将或多或少地稳定。一个月的技术支持沟通表明,你需要安装Ubuntu16,否则将遭受痛苦。懂的都懂。

我们继续了解神经网络的复杂性

现在我们看另一个手指和鼠标操作的视频。请注意,指针不能停留在一个地方,而是围绕预定点浮动的。同时,我可以轻松地将其指向我需要的单词,但是对于一个字母,则比较困难,我必须小心地移动光标:

正如你所理解的,这不是在和我握手,这完全是从激光雷达获得的,关于基于值的关键点和 Z 坐标的不断波动。

让我们仔细看看:

在我们的媒体管道 SOTA 中,波动肯定较少,但它们也存在。事实证明,他们通过在当前帧和训练网络中使用过去帧热图中的 prokid vaniya 来解决这个问题——它提供了更多的稳定性,但不是 100%的稳定。

此外,在我看来,标记的特殊性也起作用。在这么多帧上做相同的标记几乎是不可能的,更何况帧的分辨率到处都不同,而且不是很大。

此外,我们没有看到光的闪烁,这很可能不是恒定的,因为操作时间和相机曝光量不同。并且网络还从热图中返回一个sandwich ,它等于屏幕上关键点的数量,这个张量的大小是 BxNx96x96,其中 N 是关键点的数量,当然,在阈值和调整到原始帧大小后,我们得到:

渲染热图示例:

代码审查

所有代码都在这个存储库中,并且非常简短。让我们看看主文件,然后自己看看其余的。

import cv2
import mediapipe as mp
import numpy as np
import pyautogui
import pyrealsense2.pyrealsense2 as rs
from google.protobuf.json_format import MessageToDict
from mediapipe.python.solutions.drawing_utils import _normalized_to_pixel_coordinates
from pynput import keyboard
from utils.common import get_filtered_values, draw_cam_out, get_right_index
from utils.hard_reset import hardware_reset
from utils.set_options import set_short_range
pyautogui.FAILSAFE = False
mp_drawing = mp.solutions.drawing_utils
mp_hands = mp.solutions.hands
# Hand Pose Estimation
hands = mp_hands.Hands(max_num_hands=2, min_detection_confidence=0.9)
def on_press(key):
if key == keyboard.Key.ctrl:pyautogui.leftClick()
if key == keyboard.Key.alt:pyautogui.rightClick()
def get_color_depth(pipeline, align, colorizer):frames = pipeline.wait_for_frames(timeout_ms=15000) # waiting for a frame from the cameraaligned_frames = align.process(frames) depth_frame = aligned_frames.get_depth_frame()color_frame = aligned_frames.get_color_frame()
if not depth_frame or not color_frame:
return None, None, Nonedepth_ima = np.asanyarray(depth_frame.get_data())depth_col_img = np.asanyarray(colorizer.colorize(depth_frame).get_data())color_image = np.asanyarray(color_frame.get_data())depth_col_img = cv2.cvtColor(cv2.flip(cv2.flip(depth_col_img, 1), 0), cv2.COLOR_BGR2RGB)color_img = cv2.cvtColor(cv2.flip(cv2.flip(color_img, 1), 0), cv2.COLOR_BGR2RGB)depth_img = np.flipud(np.fliplr(depth_img))depth_col_img = cv2.resize(depth_col_img, (1280 * 2, 720 * 2))col_img = cv2.resize(col_img, (1280 * 2, 720 * 2))depth_img = cv2.resize(depth_img, (1280 * 2, 720 * 2))
return color_image, depth_color_image, depth_image
def get_right_hand_coords(color_image, depth_color_image):color_image.flags.writeable = Falseresults = hands.process(color_image)color_image.flags.writeable = Truecolor_image = cv2.cvtColor(color_image, cv2.COLOR_RGB2BGR)handedness_dict = []idx_to_coordinates = {}xy0, xy1 = None, None
if results.multi_hand_landmarks:
for idx, hand_handedness in enumerate(results.multi_handedness):handedness_dict.append(MessageToDict(hand_handedness))right_hand_index = get_right_index(handedness_dict)
if right_hand_index != -1:
for i, landmark_list in enumerate(results.multi_hand_landmarks):
if i == right_hand_index:image_rows, image_cols, _ = color_image.shape
for idx, landmark in enumerate(landmark_list.landmark):landmark_px = _normalized_to_pixel_coordinates(landmark.x, landmark.y,image_cols, image_rows)
if landmark_px:idx_to_coordinates[idx] = landmark_px
for i, landmark_px in enumerate(idx_to_coordinates.values()):
if i == 5:xy0 = landmark_px
if i == 7:xy1 = landmark_px
break
return col_img, depth_col_img, xy0, xy1, idx_to_coordinates
def start():pipeline = rs.pipeline() # initialize librealsenseconfig = rs.config() print("Start load conf")config.enable_stream(rs.stream.depth, 1024, 768, rs.format.z16, 30)config.enable_stream(rs.stream.color, 1280, 720, rs.format.bgr8, 30)profile = pipeline.start(config)
depth_sensor = profile.get_device (). first_depth_sensor ()set_short_range (depth_sensor) # load parameters for working at a short distancecolorizer = rs.colorizer ()print ("Conf loaded")align_to = rs.stream.coloralign = rs.align (align_to) # combine depth map and color imagetry:while True:col_img, depth_col_img, depth_img = get_col_depth (pipelin, align, colorize)if color_img is None and color_img is None and color_img is None:continuecolor_img, depth_col_img, xy00, xy11, idx_to_coordinates = get_right_hand_coords (col_img,depth_col_img)if xy00 is not None or xy11 is not None:z_val_f, z_val_s, m_xy, c_xy, xy00_f, xy11_f, x, y, z = get_filtered_values (depth_img, xy00, xy11)pyautogui.moveTo (int (x), int (3500 - z)) # 3500 hardcode specific to my monitorif draw_cam_out (col_img, depth_col_img, xy00_f, xy11_f, c_xy, m_xy):breakfinally:hands.close ()pipeline.stop ()
hardware_reset () # reboot the camera and wait for it to appear
listener = keyboard.Listener (on_press = on_press) # set a listener for keyboard button presses
listener.start ()
start () # start the program

我没有使用类或流,因为对于这样一个简单的情况,在无限的 while 循环中执行主线程中的所有内容就足够了。

一开始,媒体管道、相机被初始化,短距离和辅助变量的相机设置被加载。

接下来是称为“alight depth to color”的魔法——这个函数匹配RGB图像中的每个点,深度帧上的一个点,也就是说,它让我们有机会获得XY坐标,Z值。

据了解,需要在你的显示器上进行校准。我特意没有把这些参数单独拎出来,让决定运行代码的读者自己做,同时在代码中重复使用。

接下来,我们从整个预测中仅提取右手编号为 5 和 7 的点。

剩下要做的就是使用移动平均值过滤获得的坐标。当然,可以应用更严格的过滤算法,但是在查看了它们的可视化并拉动了各种杠杆之后,很明显深度为 5 帧的移动平均线对于演示就足够了,我想指出的是对于 XY,2-3 帧就足够了。但 Z 的情况更糟。

deque_l = 5
x0_d = collections.deque(deque_l * [0.], deque_l)
y0_d = collections.deque(deque_l * [0.], deque_l)
x1_d = collections.deque(deque_l * [0.], deque_l)
y1_d = collections.deque(deque_l * [0.], deque_l)
z_val_f_d = collections.deque(deque_l * [0.], deque_l)
z_val_s_d = collections.deque(deque_l * [0.], deque_l)
m_xy_d = collections.deque(deque_l * [0.], deque_l)
c_xy_d = collections.deque(deque_l * [0.], deque_l)
x_d = collections.deque(deque_l * [0.], deque_l)
y_d = collections.deque(deque_l * [0.], deque_l)
z_d = collections.deque(deque_l * [0.], deque_l)
def get_filtered_values(depth_image, xy0, xy1):
global x0_d, y0_d, x1_d, y1_d, m_xy_d, c_xy_d, z_val_f_d, z_val_s_d, x_d, y_d, z_dx0_d.append(float(xy0[1]))x0_f = round(mean(x0_d))y0_d.append(float(xy0[0]))y0_f = round(mean(y0_d))x1_d.append(float(xy1[1]))x1_f = round(mean(x1_d))y1_d.append(float(xy1[0]))y1_f = round(mean(y1_d))z_val_f = get_area_mean_z_val(depth_image, x0_f, y0_f)z_val_f_d.append(float(z_val_f))z_val_f = mean(z_val_f_d)z_val_s = get_area_mean_z_val(depth_image, x1_f, y1_f)z_val_s_d.append(float(z_val_s))z_val_s = mean(z_val_s_d)points = [(y0_f, x0_f), (y1_f, x1_f)]x_coords, y_coords = zip(*points)A = np.vstack([x_coords, np.ones(len(x_coords))]).Tm, c = lstsq(A, y_coords)[0]m_xy_d.append(float(m))m_xy = mean(m_xy_d)c_xy_d.append(float(c))c_xy = mean(c_xy_d)a0, a1, a2, a3 = equation_plane()x, y, z = line_plane_intersection(y0_f, x0_f, z_v_s, y1_f, x1_f, z_v_f, a0, a1, a2, a3)x_d.append(float(x))x = round(mean(x_d))y_d.append(float(y))y = round(mean(y_d))z_d.append(float(z))z = round(mean(z_d))
return z_v_f, z_v_s, m_xy, c_xy, (y00_f, x0_f), (y11_f, x1_f), x, y, z

我们创建了一个长度为 5 帧的双端队列,并对一行中的所有内容求平均值。

另外,我们计算 y = mx + c,Ax + By + Cz + d = 0,直线的方程是 RGB 中的光线图片和监视器平面的方程,我们得到y = 0。

本文仅做学术分享,如有侵权,请联系删文。

3D视觉精品课程推荐:

1.面向自动驾驶领域的多传感器数据融合技术
2.彻底搞透视觉三维重建:原理剖析、代码讲解、及优化改进
3.国内首个面向工业级实战的点云处理课程
4.激光-视觉-IMU-GPS融合SLAM算法梳理和代码讲解
5.彻底搞懂视觉-惯性SLAM:基于VINS-Fusion正式开课啦
6.彻底搞懂基于LOAM框架的3D激光SLAM: 源码剖析到算法优化
7.彻底剖析室内、室外激光SLAM关键算法原理、代码和实战(cartographer+LOAM +LIO-SAM)

干货领取:

1. 在「3D视觉工坊」公众号后台回复:3D视觉即可下载 3D视觉相关资料干货,涉及相机标定、三维重建、立体视觉、SLAM、深度学习、点云后处理、多视图几何等方向。

2. 在「3D视觉工坊」公众号后台回复:3D视觉github资源汇总即可下载包括结构光、标定源码、缺陷检测源码、深度估计与深度补全源码、点云处理相关源码、立体匹配源码、单目、双目3D检测、基于点云的3D检测、6D姿态估计源码汇总等。

3. 在「3D视觉工坊」公众号后台回复:相机标定即可下载独家相机标定学习课件与视频网址;后台回复:立体匹配即可下载独家立体匹配学习课件与视频网址。

重磅!3DCVer-学术论文写作投稿 交流群已成立

扫码添加小助手微信,可申请加入3D视觉工坊-学术论文写作与投稿 微信交流群,旨在交流顶会、顶刊、SCI、EI等写作与投稿事宜。

同时也可申请加入我们的细分方向交流群,目前主要有3D视觉CV&深度学习SLAM三维重建点云后处理自动驾驶、多传感器融合、CV入门、三维测量、VR/AR、3D人脸识别、医疗影像、缺陷检测、行人重识别、目标跟踪、视觉产品落地、视觉竞赛、车牌识别、硬件选型、学术交流、求职交流、ORB-SLAM系列源码交流、深度估计等微信群。

一定要备注:研究方向+学校/公司+昵称,例如:”3D视觉 + 上海交大 + 静静“。请按照格式备注,可快速被通过且邀请进群。原创投稿也请联系。

▲长按加微信群或投稿

▲长按关注公众号

3D视觉从入门到精通知识星球:针对3D视觉领域的视频课程(三维重建系列三维点云系列结构光系列、手眼标定、相机标定、orb-slam3知识点汇总、入门进阶学习路线、最新paper分享、疑问解答五个方面进行深耕,更有各类大厂的算法工程人员进行技术指导。与此同时,星球将联合知名企业发布3D视觉相关算法开发岗位以及项目对接信息,打造成集技术与就业为一体的铁杆粉丝聚集区,近2000星球成员为创造更好的AI世界共同进步,知识星球入口:

学习3D视觉核心技术,扫描查看介绍,3天内无条件退款

圈里有高质量教程资料、答疑解惑、助你高效解决问题

觉得有用,麻烦给个赞和在看~  

30分钟内基于激光雷达的手部姿态估计相关推荐

  1. 30分钟内使用MongoDB

    最近,我被NoSQL错误咬住了-或是我的同事Mark Atwell提出的"燃烧在哪里!" 运动. 尽管我无意于在不久的将来或可预见的将来回避友好的" SELECT ... ...

  2. 10分钟内基于gpu的目标检测

    10分钟内基于gpu的目标检测 Object Detection on GPUs in 10 Minutes 目标检测仍然是自动驾驶和智能视频分析等应用的主要驱动力.目标检测应用程序需要使用大量数据集 ...

  3. k8s aws 部署_如何在短短30分钟内使用CircleCI设置到AWS S3的持续部署

    k8s aws 部署 by Adam Watt 通过亚当·瓦特 如何在短短30分钟内使用CircleCI设置到AWS S3的持续部署 (How to setup Continuous Deployme ...

  4. 请使用recaptcha_如何在30分钟内使用ReCaptcha和PHP构建Bootstrap电子邮件表单

    请使用recaptcha by Ondrej Svestka 通过Ondrej Svestka 如何在30分钟内使用ReCaptcha和PHP构建Bootstrap电子邮件表单 (How to bui ...

  5. 机器人坐标系建立_如何在30分钟内建立一个简单的搜索机器人

    机器人坐标系建立 by Quinn Langille 奎因·兰吉尔(Quinn Langille) 如何在30分钟内建立一个简单的搜索机器人 (How to Build A Simple Search ...

  6. Retrace AV推出新型涂料添加剂,可在30分钟内灭杀新冠病毒

    新型涂料添加剂可在30分钟内消灭新冠病毒,为家庭和工作场所提供高达99.99%的保护   伦敦--(美国商业资讯)--全球数十亿人正尝试重返办公室.学校和社交场所.政府和企业主需要尽其所能提振信心,让 ...

  7. 如何零基础零费用的在30分钟内用hugo+github pages创建一个专属于你的个人博客 - 简单快捷到建议人手一个

    写这篇文章的原因是在网上看了很多的教程,踩了不少的坑,更多的白费了很多功夫,也没找到一篇从头到尾完整有效的个人建站方法. 有些教程年代久远,有些教程极为繁琐,有些教程压根跑不通. 为了方便自己,做个记 ...

  8. 如何实现生成订单30分钟内未支付则自动取消?

    如何实现生成订单30分钟内未支付则自动取消? 数据库轮询 JDK的延迟队列 Quartz 时间轮算法 使用消息队列 数据库轮询 不是很推荐的一种方式,需要定时扫描数据库,借助定时任务工具,如果是多服务 ...

  9. 自监督3D手部姿态估计方法

    作者 | 镜子@知乎 来源 | https://zhuanlan.zhihu.com/p/446726196 编辑 | 极市平台 导读 手部姿态估计任务作为一个对空间信息敏感的下游任务,任何改变空间信 ...

  10. 基于图像的摄像机姿态估计方法评析

    作者丨黄浴@知乎 来源丨https://zhuanlan.zhihu.com/p/467776433 编辑丨3D视觉工坊 arXiv在2022年1月15日上传论文"A Critical An ...

最新文章

  1. 重学《动手学深度学习》转
  2. Android 利用源码调试 详解TouchEvent 事件分发机制
  3. torch 的 unsqueeze用法
  4. [云炬python3玩转机器学习] 5-7,8 多元线性回归正规解及其实现
  5. 符合.net准则的事件
  6. Mysql8.0可以使用解压版 这个比较快 好像现在都是解压版了
  7. mysql if 多个_MySQL使用IF语句CONCAT多个字段
  8. hdu 1297 递推难题
  9. delphi 提示class tparamlistbox not found_通达信主图K线变色波段提示指标公式
  10. 小程序,修改数组或对象中的值,通过input动态修改数组对象中的值
  11. java词频统计——web版支持
  12. K近邻模型(k-NN)
  13. ThinkPHP去除url中的index.php
  14. powerbuilder11.5 免安装 时的注意事项
  15. 数据分析项目实战项目四:亚马逊Kindle书籍多渠道商业分析项目
  16. JavaScript 注册登录页面的简单实现
  17. 在蚂蚁金服工作是一种什么体验
  18. Revit导入lumion渲染
  19. 企业微信公众号怎么建立和运营?
  20. Jenkins邮箱配置过程(qq + 163)

热门文章

  1. Emscripten实现把C/C++文件转成wasm,wast(wasm的可读形式),llvm字节码(bc格式),ll格式(llvm字节码的可读形式)并执行wasm...
  2. zznu 1914 asd的甩锅计划
  3. 高数——齐次方程中齐次的解释
  4. 如何用发票查验软件快速批量查验发票(返回官网查验截图)
  5. 2017年世界各国GDP总值排名预测榜单
  6. 相对丰度会歪曲实际丰度,联合16S扩增子测序和总菌qPCR获得的绝对丰度可靠吗?...
  7. Linux下parity联盟链的实现
  8. 模拟赛DAY 2 T2不老梦
  9. VisionMobile:Apple和三星利润的秘诀
  10. html中如何设计圆形图案,纯CSS绘制漂亮的圆形图案效果