python机器人编程——一种3D骨架动画逆解算法的启示(上)
目录
- 一、前言
- 二、fabrik 算法
- 三、python实现
- 结论
- PS.扩展阅读
- ps1.六自由度机器人相关文章资源
- ps2.四轴机器相关文章资源
- ps3.移动小车相关文章资源
- ps3.wifi小车控制相关文章资源
一、前言
我们用blender等3D动画软件时,会用到骨骼的动画,通过逆向IK动力学,可以实现控制少量点,就能控制一个复杂的骨架运动。这种IK动力学几乎是实时的,非常的高效。这种IK动力学算法的代表是fabrik 算法,该算法应被用于UE虚幻引擎、Unity等3D软件中。大至效果是,可以实现骨架的目标跟随,而且几乎是“实时”的:

这种感觉,不就是机械臂的虚拟拖拽吗?是否可以给机械臂的逆解,或者是虚拟化示教带来一些启发,是一个有意思的应用。本篇先来初步研究一下fabrik 算法。
二、fabrik 算法
该算法在文章 FABRIK: A fast, iterative solver for the Inverse Kinematics problem中有详细说明,FABRIK算法是一种用于解决逆运动学问题的启发式方法。它通过迭代地调整关节链,使末端执行器逐渐接近目标位置。与传统方法相比,FABRIK算法不需要使用旋转角度或矩阵,而是通过在线段上定位点来找到每个关节的位置,这使得它在计算上更加高效,并且能够产生视觉上现实的关节姿势。

三、python实现
网上已经有很多实现的python算法,这里,主要是利用实现的3D算法,实现在matplot中的IK,即,任意点击3D坐标点,实现IK,骨架的末端移动到目标点,就像开始的blender一样。

首先,我们需要导入一些必要的Python库,包括NumPy、Math、Matplotlib等,用于数学运算和图形绘制。
import numpy as np
import math
import matplotlib as mpl
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import axes3d
import re
单位向量函数
定义了一个函数 unitVector,它接受一个向量作为输入,并返回该向量的单位向量。
def unitVector(vector):return vector / np.linalg.norm(vector)
Segment3D 类用于存储逆运动学链的一部分。它接受参考点坐标、段长度和初始角度作为参数,并计算出该段的新坐标。
class Segment3D:def __init__(self, referenceX, referenceY, referenceZ, length, zAngle, yAngle):self.zAngle = zAngleself.yAngle = yAngleself.length = lengthdeltaX = math.cos(math.radians(zAngle)) * lengthdeltaY = math.sin(math.radians(zAngle)) * lengthdeltaZ = math.sin(math.radians(yAngle)) * lengthnewX = referenceX + deltaXnewY = referenceY + deltaYnewZ = referenceZ + deltaZself.point = np.array([newX, newY, newZ])
FabrikSolver3D 类是FABRIK算法的核心,它初始化了一个3D逆运动学求解器,并提供了添加段、检查可达性、迭代求解和绘图等功能。
class FabrikSolver3D:""" An inverse kinematics solver in 3D. Uses the Fabrik Algorithm."""def __init__(self, baseX=0, baseY=0, baseZ=0, marginOfError=0.01):# Create the base of the chain.self.basePoint = np.array([baseX, baseY, baseZ])# Initialize empty segment array -> [].self.segments = []# Initialize length of the chain -> 0.self.armLength = 0# Initialize the margin of error.self.marginOfError = marginOfErrorself.targetpoint=Noneself.fig = plt.figure()self.ax1 = self.fig.add_subplot(111, projection="3d")self.mousep=Nonedef addSegment(self, length, zAngle, yAngle):if len(self.segments) > 0:segment = Segment3D(self.segments[-1].point[0], self.segments[-1].point[1], self.segments[-1].point[2], length, zAngle + self.segments[-1].zAngle, self.segments[-1].yAngle + yAngle)else:# Maak een segment van de vector beginpoint, lengte en hoek.segment = Segment3D(self.basePoint[0], self.basePoint[1], self.basePoint[2], length, zAngle, yAngle)# Voeg lengte toe aan de totale armlengte.self.armLength += segment.length# Voeg de nieuwe segment toe aan de list.self.segments.append(segment)def isReachable(self, targetX, targetY, targetZ):if np.linalg.norm(self.basePoint - np.array([targetX, targetY, targetZ])) < self.armLength:return Truereturn Falsedef inMarginOfError(self, targetX, targetY, targetZ):if np.linalg.norm(self.segments[-1].point - np.array([targetX, targetY, targetZ])) < self.marginOfError:return Truereturn False def iterate(self, targetX, targetY, targetZ):target = np.array([targetX, targetY, targetZ])# Backwards.for i in range(len(self.segments) - 1, 0, -1):if i == len(self.segments) - 1:self.segments[i-1].point = (unitVector(self.segments[i-1].point - target) * self.segments[i].length) + targetelse:self.segments[i-1].point = (unitVector(self.segments[i-1].point - self.segments[i].point) * self.segments[i].length) + self.segments[i].point# Forwards.for i in range(len(self.segments)):if i == 0:self.segments[i].point = (unitVector(self.segments[i].point - self.basePoint) * self.segments[i].length) + self.basePointelif i == len(self.segments) - 1:self.segments[i].point = (unitVector(self.segments[i-1].point - target) * self.segments[i].length * -1) + self.segments[i-1].pointelse:self.segments[i].point = (unitVector(self.segments[i].point - self.segments[i-1].point) * self.segments[i].length) + self.segments[i-1].pointdef compute(self, targetX, targetY, targetZ):if self.isReachable(targetX, targetY, targetZ):while not self.inMarginOfError(targetX, targetY, targetZ):self.iterate(targetX, targetY, targetZ) self.targetpoint=[targetX, targetY, targetZ]else:print('Target not reachable.')sys.exit()def plot(self, save=False, name="graph"):self.ax1.clear() # 清除当前轴ax1=self.ax1# Plot arm.for i, segment in enumerate(self.segments):#ax1.scatter(segment.point[2], segment.point[0], segment.point[1], c='b')ax1.scatter(segment.point[0], segment.point[1], segment.point[2], c='b')if i > 0: # Connect to the previous segmentax1.plot([self.segments[i-1].point[0], segment.point[0]],[self.segments[i-1].point[1], segment.point[1]],[self.segments[i-1].point[2], segment.point[2]], 'b-')"""ax1.plot([self.segments[i-1].point[2], segment.point[2]],[self.segments[i-1].point[0], segment.point[0]],[self.segments[i-1].point[1], segment.point[1]], 'b-')"""# Connect the last segment to the base point"""ax1.plot([self.basePoint[2], self.segments[0].point[2]],[self.basePoint[0], self.segments[0].point[0]],[self.basePoint[1], self.segments[0].point[1]], 'b-')"""ax1.plot([self.basePoint[0], self.segments[0].point[0]],[self.basePoint[1], self.segments[0].point[1]],[self.basePoint[2], self.segments[0].point[2]], 'b-')# Start point#ax1.scatter(self.basePoint[2], self.basePoint[0], self.basePoint[1], c='g')ax1.scatter(self.basePoint[0], self.basePoint[1], self.basePoint[2], c='g')ax1.scatter(self.targetpoint[0],self.targetpoint[1],self.targetpoint[2], c='r',alpha=0.6, s=100)ax1.set_ylabel('y-axis')ax1.set_zlabel('z-axis')ax1.set_xlabel('x-axis')# Set the view angle so that the z-axis is pointing upwardsax1.view_init(elev=45., azim=45.)plt.pause(0.01)def extract_coordinates(self,s):# 使用正则表达式匹配x, y, z的值# 注意:负号前面加上了反斜杠进行转义# 使用正则表达式提取实数值,包括负号pattern = r'−?\d+\.\d+'matches = re.findall(pattern, s)values = [float(match.replace('−', '-')) for match in matches]x, y, z=values return x, y, zdef show(self):self.plot()self.fig.canvas.mpl_connect('button_press_event', self.on_click) # 连接点击事件self.fig.canvas.mpl_connect('motion_notify_event', self.on_motion) plt.show()def on_motion(self,event):if event.inaxes is not None:x, y = event.xdata, event.ydatasting = self.ax1.format_coord(x, y) x,y,z=self.extract_coordinates(sting)#print(x,y,z) self.mousep=(x,y,z)def on_click(self, event):# 检查点击事件是否在坐标轴内if event.inaxes is not None:print(f'Clicked on axis {event.inaxes}')# 触发你的函数print(self.mousep)if not type(self.mousep) == type(None): x,y,z=self.mousepprint("compute")self.compute(x, y, z)self.plot()# 获取点击的坐标值#self.compute(x, y, z)# 重绘图形#
可以通过如下步骤实现逆解:
-
添加关节段:通过addSegment方法,我们可以为机械臂添加多个关节段,每个关节段都有自己的长度和初始角度。
-
检查可达性:isReachable方法检查目标位置是否在机械臂的可达范围内。
-
迭代计算:iterate方法执行一次FABRIK算法迭代,调整关节位置以接近目标。
-
绘图显示:plot方法用于在3D空间中绘制机械臂的当前状态,show方法则显示最终的图形界面,并允许用户通过点击来选择目标位置。
-
事件处理:on_click和on_motion方法用于处理用户的点击和鼠标移动事件,以便动态地调整目标位置。
if __name__ == "__main__": arm = FabrikSolver3D() arm.addSegment(0, 0, 0)arm.addSegment(50, 0, 0)arm.addSegment(50, 0, 0)arm.addSegment(50, 0, 0)arm.addSegment(50, 0, 0)arm.compute(50, 50, 100) arm.show()

结论
通过这个Python实现,我们可以看到FABRIK算法在解决3D逆运动学问题中的强大能力。它不仅能够快速找到解决方案,还能够实时响应用户的交互,这在模拟和实际应用中都是非常有价值的。接下来我们尝试丰富这个算法,在各关节添加约束,并应用到6轴机械臂的IK计算中,看看能否获得预期效果。
[------------本篇完-------------]
PS.扩展阅读
————————————————————————————————————————
对于python机器人编程感兴趣的小伙伴,可以进入如下链接阅读相关咨询
ps1.六自由度机器人相关文章资源
(1) 对六自由度机械臂的运动控制及python实现(附源码)

(2) N轴机械臂的MDH正向建模,及python算法

ps2.四轴机器相关文章资源
(1) 文章:python机器人编程——用python实现一个写字机器人


(2)python机器人实战——0到1创建一个自动是色块机器人项目-CSDN直播
(3)博文《我从0开始搭建了一个色块自动抓取机器人,并实现了大模型的接入和语音控制-(上基础篇)》的vrep基础环境
(3)博文《我从0开始搭建了一个色块自动抓取机器人,并实现了大模型的接入和语音控制-(上基础篇)》的vrep基础环境
(4)实现了语音输入+大模型指令解析+机器视觉+机械臂流程打通


ps3.移动小车相关文章资源
(1)python做了一个极简的栅格地图行走机器人,到底能干啥?[第五弹]——解锁蒙特卡洛定位功能-CSDN博客
(2) 对应python资源:源码地址


(3)python机器人编程——差速AGV机器、基于视觉和预测控制的循迹、自动行驶(上篇)_agv编程-CSDN博客
(4)python机器人编程——差速AGV机器、基于视觉和预测控制的循迹、自动行驶(下篇)_agv路线规划原则python-CSDN博客
对应python及仿真环境资源:源码链接



ps3.wifi小车控制相关文章资源
web端配套资源源代码已经上传(竖屏版),下载地址
仿真配套资源已经上传:下载地址
web端配套资源源代码已经上传(横屏版),下载地址
在这里插入代码片
相关文章:
python机器人编程——一种3D骨架动画逆解算法的启示(上)
目录 一、前言二、fabrik 算法三、python实现结论PS.扩展阅读ps1.六自由度机器人相关文章资源ps2.四轴机器相关文章资源ps3.移动小车相关文章资源ps3.wifi小车控制相关文章资源 一、前言 我们用blender等3D动画软件时,会用到骨骼的动画,通过逆向IK动力学…...
Flutter开发者必备面试问题与答案02
Flutter开发者必备面试问题与答案02 视频 https://youtu.be/XYSxTb0iA9I https://www.bilibili.com/video/BV1Zk2dYyEBr/ 前言 原文 Flutter 完整面试问题及答案02 本文是 flutter 面试问题的第二讲,高频问答 10 题。 正文 11. PageRoute 是什么? …...
拥抱真实:深度思考之路,行动力的源泉
在纷繁复杂的现代社会,人们往往被表象迷惑,忙碌于各种事务之中,却很少停下来进行深度思考。这种忙碌往往是表面的、无效的,因为它缺乏对自我和目标的深刻理解与追求。提升行动力,避免假勤奋,关键在于深度思…...
【Python爬虫实战】深入理解Python异步编程:从协程基础到高效爬虫实现
#1024程序员节|征文# 🌈个人主页:易辰君-CSDN博客 🔥 系列专栏:https://blog.csdn.net/2401_86688088/category_12797772.html 目录 前言 一、异步 (一)核心概念 (二)…...
OpenCV图像处理方法:腐蚀操作
腐蚀操作 前提 图像数据为二值的(黑/白) 作用 去掉图片中字上的毛刺 显示图片 读取一个图像文件,并在一个窗口中显示它。用户可以查看这个图像,直到按下任意键,然后程序会关闭显示图像的窗口 # cv2是OpenCV库的P…...
PG数据库之流复制详解
一、流复制的定义 PostgreSQL流复制(Streaming Replication)是一种数据复制技术,它允许实时传输数据更改,从而在主服务器和一个或多个备用服务器之间保持数据同步。流复制是PostgreSQL数据库管理系统(DBMS)…...
Python酷库之旅-第三方库Pandas(174)
目录 一、用法精讲 801、pandas.Categorical类 801-1、语法 801-2、参数 801-3、功能 801-4、返回值 801-5、说明 801-6、用法 801-6-1、数据准备 801-6-2、代码示例 801-6-3、结果输出 802、pandas.Categorical.from_codes方法 802-1、语法 802-2、参数 802-3、…...
【Linux网络】基于TCP的全连接队列与文件、套接字、内核之间的关系
W...Y的主页 😊 代码仓库管理💕 前言:之前我们已经学习了TCP传输协议,而无论是TCP还是UDP都是使用socket套接字进行网络传输的,而TCP的socket是比UDP复杂的,当时我们学习TCPsocket编程时使用listen函数进行…...
IDE(集成开发环境)
IDE(集成开发环境)是软件开发过程中不可或缺的工具,它集成了代码编写功能、分析功能、编译器、调试器等开发工具,旨在提高开发效率。不同的IDE支持不同的语言和框架,下面是一些通用的IDE使用技巧和插件推荐,…...
一键导入Excel到阿里云PolarDB-MySQL版
今天,我将分享如何一键导入Excel到阿里云PolarDB-MySQL版数据库。 准备数据 这里,我们准备了一张excel表格如下: 连接到阿里云PolarDB 打开的卢导表,点击新建连接-选择阿里云PolarDB-MySQL版。如果你还没有这个工具,…...
Oracle有哪些版本
目录 Oracle 1(1979年) Oracle 2(1983年) Oracle 7(1992年) Oracle 8i(1999年) Oracle 9i(2001年) Oracle 10g(2004年) Oracle 11g(2007年) Oracle 12c(2013年) Oracle 18c(2018年) Oracle 19c(2019年) Oracle 21c(2023年) Oracle 23ai(202…...
先来先服务(FCFS,First-Come, First-Served)调度算法
有利于CPU繁忙作业的原因 充分利用CPU资源: 当一个CPU繁忙型的作业到达后,它会立即被执行,并且在没有其他作业等待的情况下,可以一直占用CPU直到完成。这使得CPU能够持续地执行作业,最大化利用CPU资源。 减少上下文切换…...
Windows操作系统忘记密码怎么办 这个方法屡试不爽 还不来试一下
Windows操作系统重置密码的操作步骤如下: 本方法适用于Windows Server 2008R2及其之后的操作系统。 第一步:从Windows 2008R2之后的操作系统光盘启动到安装界面,一直下一步到磁盘分区界面,按shiftF10调出cmd命令行界面。 第二步&…...
基于java的山区环境监督管理系统(源码+定制+开发)环境数据可视化、环境数据监测、 环境保护管理 、污染防治监测系统 大数据分析
博主介绍: ✌我是阿龙,一名专注于Java技术领域的程序员,全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师,我在计算机毕业设计开发方面积累了丰富的经验。同时,我也是掘金、华为云、阿里云、InfoQ等平台…...
jQuery Mobile 表单输入
jQuery Mobile 表单输入 引言 在移动设备上,表单输入是用户与移动应用交互的重要方式。jQuery Mobile 是一个基于 jQuery 的移动设备友好的开发框架,它提供了丰富的组件和工具来帮助开发者创建响应式和交互式的移动界面。本文将详细介绍如何使用 jQuery Mobile 来创建和定制…...
IoC详解
共有两类注解类型可以实现: 1. 类注解:Controller、Service、Repository、Component、Configuration. 2. 方法注解:Bean. 类注解 Controller(控制器存储) 使⽤Controller存储bean的代码如下所⽰: Con…...
基于 ThinkPHP+Mysql 灵活用工_灵活用工系统_灵活用工平台
基于 ThinkPHPMysql 灵活用工灵活用工平台灵活用工系统灵活用工小程序灵活用工源码灵活用工系统源码 开发语言 ThinkPHPMysql 源码合作 提供完整源代码 软件界面展示 一、企业管理后台 二、运用管理平台 三、手机端...
etcd之etcd分布式锁及事务(四)
1、etcd分布式锁及事务 1.1 前言 分布式锁是控制分布式系统之间同步访问共享资源的一种方式。在分布式系统中,常常需要协调他们的动作。如 果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要…...
智慧旅游微信小程序平台
作者介绍:✌️大厂全栈码农|毕设实战开发,专注于大学生项目实战开发、讲解和毕业答疑辅导。 🍅获取源码联系方式请查看文末🍅 推荐订阅精彩专栏 👇🏻 避免错过下次更新 Springboot项目精选实战案例 更多项目…...
C++设计模式创建型模式———简单工厂模式、工厂方法模式、抽象工厂模式
文章目录 一、引言二、简单工厂模式三、工厂方法模式三、抽象工厂模式四、总结 一、引言 创建一个类对象的传统方式是使用关键字new , 因为用 new 创建的类对象是一个堆对象,可以实现多态。工厂模式通过把创建对象的代码包装起来,实现创建对…...
智能在线客服平台:数字化时代企业连接用户的 AI 中枢
随着互联网技术的飞速发展,消费者期望能够随时随地与企业进行交流。在线客服平台作为连接企业与客户的重要桥梁,不仅优化了客户体验,还提升了企业的服务效率和市场竞争力。本文将探讨在线客服平台的重要性、技术进展、实际应用,并…...
【OSG学习笔记】Day 16: 骨骼动画与蒙皮(osgAnimation)
骨骼动画基础 骨骼动画是 3D 计算机图形中常用的技术,它通过以下两个主要组件实现角色动画。 骨骼系统 (Skeleton):由层级结构的骨头组成,类似于人体骨骼蒙皮 (Mesh Skinning):将模型网格顶点绑定到骨骼上,使骨骼移动…...
多模态大语言模型arxiv论文略读(108)
CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文标题:CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文作者:Sayna Ebrahimi, Sercan O. Arik, Tejas Nama, Tomas Pfister ➡️ 研究机构: Google Cloud AI Re…...
ABAP设计模式之---“简单设计原则(Simple Design)”
“Simple Design”(简单设计)是软件开发中的一个重要理念,倡导以最简单的方式实现软件功能,以确保代码清晰易懂、易维护,并在项目需求变化时能够快速适应。 其核心目标是避免复杂和过度设计,遵循“让事情保…...
人工智能(大型语言模型 LLMs)对不同学科的影响以及由此产生的新学习方式
今天是关于AI如何在教学中增强学生的学习体验,我把重要信息标红了。人文学科的价值被低估了 ⬇️ 转型与必要性 人工智能正在深刻地改变教育,这并非炒作,而是已经发生的巨大变革。教育机构和教育者不能忽视它,试图简单地禁止学生使…...
华为OD机考-机房布局
import java.util.*;public class DemoTest5 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseSystem.out.println(solve(in.nextLine()));}}priv…...
GitHub 趋势日报 (2025年06月06日)
📊 由 TrendForge 系统生成 | 🌐 https://trendforge.devlive.org/ 🌐 本日报中的项目描述已自动翻译为中文 📈 今日获星趋势图 今日获星趋势图 590 cognee 551 onlook 399 project-based-learning 348 build-your-own-x 320 ne…...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...
第一篇:Liunx环境下搭建PaddlePaddle 3.0基础环境(Liunx Centos8.5安装Python3.10+pip3.10)
第一篇:Liunx环境下搭建PaddlePaddle 3.0基础环境(Liunx Centos8.5安装Python3.10pip3.10) 一:前言二:安装编译依赖二:安装Python3.10三:安装PIP3.10四:安装Paddlepaddle基础框架4.1…...
Spring Boot + MyBatis 集成支付宝支付流程
Spring Boot MyBatis 集成支付宝支付流程 核心流程 商户系统生成订单调用支付宝创建预支付订单用户跳转支付宝完成支付支付宝异步通知支付结果商户处理支付结果更新订单状态支付宝同步跳转回商户页面 代码实现示例(电脑网站支付) 1. 添加依赖 <!…...
