机械人的浪漫:嘉然为你画爱心(python实现)

Posted 可可卷

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了机械人的浪漫:嘉然为你画爱心(python实现)相关的知识,希望对你有一定的参考价值。

👨‍🎓 作者简介:大家好,我是可可卷,欢迎大家关注,一起学习交流 ~
📜主攻领域:【python算法】【数据分析】【数学建模】【机器学习】【深度学习】【数据可视化】
📖个人主页:可可卷的博客

0 写在前面

🔥多图预警,请连WIFI!
全文图片较多,建议大家先收藏再阅读哦~

1 必要准备

安装依赖:

numpy

matplotlib

嘉⭐然:

2 生成爱❤心轨迹

计算爱❤心坐标

# 0.01为步长,步长越小,爱心曲线越逼真
x_ = np.arange(-1.15,1.16,0.01).astype(np.complex64)
y1 = (1/2*((x_**2)**(1/3)+((x_**4)**(1/3)-4*x_**2+4)**(1/2))).real
y2 = (1/2*((x_**2)**(1/3)-((x_**4)**(1/3)-4*x_**2+4)**(1/2))).real

画出爱❤心曲线

plt.plot(x_,y1)	# 爱心上半部分
plt.plot(x_,y2)	# 爱心下半部分
plt.show()

过滤烦人的警告

# 过滤烦人的warnings
import warnings
warnings.filterwarnings('ignore')

设置爱心为粉色

plt.plot(x_,y1,c='pink')
plt.plot(x_,y2,c='pink')

效果如下

3 放入嘉⭐然

ax=plt.subplot(111)
ax.plot(x_,y1)
ax.plot(x_,y2)
img=plt.imread('Diana.jpg')
im = OffsetImage(img,zoom=.520)
ab = AnnotationBbox(im, (0.520, 0.520), xycoords='axes fraction')
ax.add_artist(ab)
ax.figure.savefig("Diana.svg", transparent=True, dpi=600, bbox_inches="tight")
plt.show()

效果如下

眼尖的鼠鼠可能已经发现了,为什么

im = OffsetImage(img,zoom=.520)
ab = AnnotationBbox(im, (0.520, 0.520), xycoords='axes fraction')

参数里会出现520呢?

因为这是♥的坐标~

到目前为止,全部的代码如下

import numpy as np
from matplotlib import pyplot as plt
from matplotlib.offsetbox import OffsetImage, AnnotationBbox

# 过滤烦人的warnings
import warnings
warnings.filterwarnings('ignore')

x_ = np.arange(-1.15,1.16,0.01).astype(np.complex64)
y1 = (1/2*((x_**2)**(1/3)+((x_**4)**(1/3)-4*x_**2+4)**(1/2))).real
y2 = (1/2*((x_**2)**(1/3)-((x_**4)**(1/3)-4*x_**2+4)**(1/2))).real

ax=plt.subplot(111)
ax.plot(x_,y1,c='pink')
ax.plot(x_,y2,c='pink')
img=plt.imread('Diana.jpg')
im = OffsetImage(img,zoom=.520)
ab = AnnotationBbox(im, (0.520, 0.520), xycoords='axes fraction')
ax.add_artist(ab)
ax.figure.savefig("Diana.svg", transparent=True, dpi=600, bbox_inches="tight")
plt.show()

4 机械臂的概览

4.0 必要准备

import numpy as np
from matplotlib.offsetbox import OffsetImage, AnnotationBbox
from numpy import cos, sin, arccos, arctan2
import matplotlib.pyplot as plt

# 过滤烦人的warnings
import warnings
warnings.filterwarnings('ignore')

# 目标点
(target_x, target_y) = (0, 0)

# 爱心的x,y坐标
x_ = np.arange(-1.15,1.16,0.05).astype(np.complex64)
y1 = (1/2*((x_**2)**(1/3)+((x_**4)**(1/3)-4*x_**2+4)**(1/2))).real
y2 = (1/2*((x_**2)**(1/3)-((x_**4)**(1/3)-4*x_**2+4)**(1/2))).real

# 用于坐标迭代的生成器
sx=(float(x) for x in x_)
sy=(float(y) for y in y1)
flag=False
forever=True

# 记录机械臂arm2的坐标
dx,dy=[],[]

4.1 创建对象

# 创建对象,嘉然小姐~
class Diana:
    def __init__(self):
       pass

    def update_arms(self, angles):
        pass

    # 前向运动
    def forward_kinematics(self):
        pass

    # 反解位姿
    def inverse_kinematic(self, x, y):
       pass
    
    def plot(self):
        pass

    def animation(self, x, y):
        pass

    def 画爱心(self):
        pass

4.2 来到天堂

def Heaven():
    嘉然 = Diana()
    while forever:
        嘉然.画爱心()

if __name__ == "__main__":
    Heaven()

5 机械臂的细节

5.0 初始化对象

def __init__(self, angles=[0, 0]):
    # 第0个关节固定坐标
    self.arm0 = np.array([-0.1314, -0.520])
    # 两段机械臂的长度
    self.link_lengths = [1, 1]
    # 机械臂初始角度
    self.update_arms(angles)

这时又会有眼尖的鼠鼠提问了,老师老师,为什么arm0的坐标是[-0.1314, -0.520]呢?

别问,问就是♥的坐标

5.1 更新机械臂

def update_arms(self, angles):
    self.arm_angles = angles
    self.forward_kinematics()

5.2 前向运动

# 前向运动
def forward_kinematics(self):
    # 计算关节1的位置(a0,a1分别是第0和第1个关节的关节角)
    a0 = self.arm_angles[0]
    l0 = self.link_lengths[0]
    self.arm1 = self.arm0 + [l0 * cos(a0), l0 * sin(a0)]

    # 计算关节2的位置
    a1 = self.arm_angles[1]
    l1 = self.link_lengths[1]
    self.arm2 = self.arm1 + [l1 * cos(a0 + a1), l1 * sin(a0 + a1)]

5.3 逆向运动

# 反解位姿
def inverse_kinematic(self, x, y):
    l0 = self.link_lengths[0]
    l1 = self.link_lengths[1]
    a1 = arccos((x ** 2 + y ** 2 - l0 ** 2 - l1 ** 2) / (2 * l0 * l1))
    a0 = arctan2(y, x) - arctan2(l1 * sin(a1), l1 * cos(a1) + l0)
    return [a0, a1]

5.4 轨迹作图

def plot(self):
    # 清理坐标系中的内容
    plt.cla()

    # 显示嘉然小姐~
    ax = plt.subplot(111)
    ax.plot(dx[5:],dy[5:],zorder=5,c='pink')
    img = plt.imread('Diana.jpg')
    im = OffsetImage(img, zoom=0.520,zorder=0)
    ab = AnnotationBbox(im, (0.5, 0.520), xycoords='axes fraction',frameon=False)
    ax.add_artist(ab)

    # 三个关节的坐标
    x = [self.arm0[0], self.arm1[0], self.arm2[0]+0.1314]
    y = [self.arm0[1], self.arm1[1], self.arm2[1]+0.520]

    # 记录爱心坐标
    if len(dx)<520:
        dx.append(x[-1])
        dy.append(y[-1])

    # magic point
    if dx[-1]==0.9761696712838817:
        dx[-1]*=-1
        dy[-1]=-0.04573089606848885


    # 绘制机械臂
    ax.plot(x, y, c="red", zorder=4)

    # 绘制机械臂的关节
    ax.scatter(x[1:], y[1:], c="black", marker='*',zorder=5)

    # 固定坐标系
    plt.xlim(-1.314, 1.314)
    plt.ylim(-1.314, 1.314)

让我们来统计一下,这里面包含了多大的♥~

5201个
0.5203个
0.13141个
1.3144个

这是巧合吗?

这是♥捏~

5.5 绘制动作

def animation(self, x, y):
    angles = self.inverse_kinematic(x, y)

    # 分解N步慢动作
    actions_num = 2
    angles_per_action = (np.array(angles) - np.array(self.arm_angles)) / actions_num
    for action_i in range(actions_num):
        self.arm_angles = np.array(self.arm_angles) + angles_per_action
        self.update_arms(self.arm_angles)
        self.plot()
        plt.pause(0.001)    # 暂停的时间

5.6 画爱♥心

def 画爱心(self):
    global target_x, target_y
    global sx,sy,flag
    try:
        target_x = next(sx)
        target_y = next(sy)
        self.animation(target_x, target_y)
    except:
        if flag:
            sx = (float(x) for x in x_)
            sy = (float(y) for y in y1)
            flag=False
        else:
            sx = (float(x) for x in reversed(x_))
            sy = (float(y) for y in y2)
            flag=True

6 全部代码

import numpy as np
from matplotlib.offsetbox import OffsetImage, AnnotationBbox
from numpy import cos, sin, arccos, arctan2
import matplotlib.pyplot as plt

# 过滤烦人的warnings
import warnings
warnings.filterwarnings('ignore')

# 目标点
(target_x, target_y) = (0, 0)

# 爱心的x,y坐标
x_ = np.arange(-1.15,1.16,0.05).astype(np.complex64)
y1 = (1/2*((x_**2)**(1/3)+((x_**4)**(1/3)-4*x_**2+4)**(1/2))).real
y2 = (1/2*((x_**2)**(1/3)-((x_**4)**(1/3)-4*x_**2+4)**(1/2))).real

# 用于坐标迭代的生成器
sx=(float(x) for x in x_)
sy=(float(y) for y in y1)
flag=False
forever=True

# 记录机械臂arm2的坐标
dx,dy=[],[]

# 嘉然小姐~
class Diana:
    def __init__(self, angles=[0, 0]):
        # 第0个关节固定坐标
        self.arm0 = np.array([-0.1314, -0.520])
        # 两段机械臂的长度
        self.link_lengths = [1, 1]
        # 机械臂初始角度
        self.update_arms(angles)

    def update_arms(self, angles):
        self.arm_angles = angles
        self.forward_kinematics()

    # 前向运动
    def forward_kinematics(self):
        # 计算关节1的位置(a0,a1分别是第0和第1个关节的关节角)
        a0 = self.arm_angles[0]
        l0 = self.link_lengths[0]
        self.arm1 = self.arm0 + [l0 * cos(a0), l0 * sin(a0)]

        # 计算关节2的位置
        a1 = self.arm_angles[1]
        l1 = self.link_lengths[1]
        self.arm2 = self.arm1 + [l1 * cos(a0 + a1), l1 * sin(a0 + a1)]

    # 反解位姿
    def inverse_kinematic(self, x, y):
        l0 = self.link_lengths[0]
        l1 = self.link_lengths[1]
        a1 = arccos((x ** 2 + y ** 2 - l0 ** 2 - l1 ** 2) / (2 * l0 * l1))
        a0 = arctan2(y, x) - arctan2(l1 * sin(a1), l1 * cos(a1) + l0)
        return [a0, a1]

    def plot(self):
        # 清理坐标系中的内容
        plt.cla()

        # 显示嘉然小姐~
        ax = plt.subplot(111)
        ax.plot(dx[5:],dy[5:],zorder=5,c='pink')
        img = plt.imread('Diana.jpg')
        im = OffsetImage(img, zoom=0.520,zorder=0)
        ab = AnnotationBbox(im, (0.5, 0.520), xycoords='axes fraction',frameon=False)
        ax.add_artist(ab)

        # 三个关节的坐标
        x = [self.arm0[0], self.arm1[0], self.arm2[0]+0.1314]
        y = [self.arm0[1], self.arm1[1], self.arm2[1]+0.520]

        # 记录爱心坐标
        if len(dx)<520:
            dx.append(x[-1])
            dy.append(y[-1])

        # magic point
        if dx[-1]==0.9761696712838817:
            dx[-1]*=-1
            dy[-1]=-0.04573089606848885


        # 绘制机械臂
        ax.plot(x, y, c="red", zorder=4)

        # 绘制机械臂的关节
        ax.scatter(x[1:], y[1:], c="black", marker='*',zorder=5)

        # 固定坐标系
        plt.xlim(-1.314, 1.314)
        plt.ylim(-1.314, 1.314)

    def animation(self, x, y):
        angles = self.inverse_kinematic(x, y)

        # 分解N步慢动作
        actions_num = 2
        angles_per_action = (np.array(angles) - np.array(self.arm_angles)) / actions_num
        for action_i in range(actions_num):
            self.arm_angles = np.array(self.arm_angles) + angles_per_action
            self.update_arms(self.arm_angles)
            self.plot()
            plt.pause(0.001)    # 暂停的时间

    def 画爱心(self):
        global target_x, target_y
        global sx,sy,flag
        try:
            target_x = next(sx)
            target_y = next(sy)
            self.animation(target_x, target_y)
        except:
            if flag:
                sx = (float(x) for x in x_)
                sy = (float(y) for y in y1)
                flag=False
            else:
                sx = (float(x) for x in reversed(x_))
                sy = (float(y) for y in y2)
                flag=True



def Heaven():
    嘉然 = Diana()
    while forever:
        嘉然.画爱心()



if __name__ == "__main__":
    Heaven()

效果如下

7 全是细节

7.0 好学的嘉心糖

作为一个好学的嘉心糖,在写完代码后自然要好好总结捏~

7.1 中文变量

python3是支持中文作为变量的哦,而且不需要引入任何库和第三方工具

大家可以测试以下代码,试试中文变量名的使用效果

love=True
嘉然=True
print(嘉然 is love)
''' 我的测试结果 '''
>>> love=True
>>> 嘉然=True
>>> 嘉然 is love
True

7.2 坐标迭代

  1. 因为我采用了分别画出上、下部分爱心再结合的方式,因此在坐标迭代部分需要通过flag对上、下部分爱心做一个标记

  2. 在具体迭代上,通过(float(x) for x in x_)构造生成器,通过next()取出生成器的下一个元素

    当元素取尽时会抛出StopIteration异常,这时通过except重新构造新的生成器

  3. 大家可以通过下面的测试代码来体会生成器的用法~

    >>> x=[1,2,3]
    >>> y=(i for i in x)
    >>> next(y)
    1
    >>> next(y)
    2
    >>> next(y)
    3
    >>> next(y)
    Traceback (most recent call last):
      File "<pyshell#7>", line 1, in <module>
        next(y)
    StopIteration
    >>> 
    

7.3 zorder

matplotlib中轴的默认绘制顺序是补丁,线条,文本。 此顺序由zorder属性确定。 设置以下默认值

ArtistZ-order
Patch / PatchCollection1
Line2D / LineCollection2
Text3

类比PS,zorder可以看成一个控制图层上下顺序的参数

更多详细用法,可以查看官方文档:https://www.matplotlib.org.cn/gallery/misc/zorder_demo.html

8 写在后面

  1. 本文爱心曲线参考了@FrigidWinter大佬的博客📚工程师的浪漫:用机械臂画一个爱心

  2. 同时本文机械臂的逆运动学实现部分参考了GitHub项目:https://github.com/varyshare/easy_slam_tutorial/tree/master/joint_robot_simulation

  3. 更多机器人相关课程,详见:
    https://robotacademy.net.au/lesson/inverse-kinematics-for-a-2-joint-robot-arm-using-geometry/

9 偷偷的说

如果把嘉然偷偷换成海子姐,会怎么样呢QAQ

以上是关于机械人的浪漫:嘉然为你画爱心(python实现)的主要内容,如果未能解决你的问题,请参考以下文章

用代码写出浪漫__合集(pythonmatplotlibMatlabjava绘制爱心玫瑰花前端特效玫瑰爱心)

MATLAB 还只会画爱心?,教你画水晶簇以及水晶爱心

MATLAB 还只会画爱心?,教你画水晶簇以及水晶爱心

MATLAB 还只会画爱心?,教你画水晶簇以及水晶爱心

MATLAB 还只会画爱心?,教你画水晶簇以及水晶爱心

用 Python 实现浪漫表白程序