一步步制作下棋机器人之 完善XY坐标控制

时间:2023-04-27
本文章向大家介绍一步步制作下棋机器人之 完善XY坐标控制,主要内容包括说明、内容分解、1:获取坐标、比例换算、2:获取点击坐标,显示示意图、3:交点和角度的计算、4:发送给Coppeliasim、总结、使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

匆匆忙忙,又是一周。
马上五一,凑了十天假期,想想就开心。
但是假期中是生日,又老了一岁了。很多目标都没有实现,就马上要到30了,可怕。
30啊!!
唉,时光匀速又决绝的前行不息,推动了没有返程票的人生旅程。总想着不断提升自己,不断丰富生命的意义,不断拓宽人生的界限,让人世这一遭不至于太单调。可是回过头去看,已经走过的路却是如此的平庸和枯燥。没有热血与冲动,没有懵懂与暧昧,没有舍我其谁,没有谁与争锋。
也没有钱。
那些曾向往过的青春热血,怒发冲冠,呼朋唤友,学富五车,抱得佳人,亦或是为梦前行,科学前沿,安得广厦,高端制造,AI智能,名垂青史等等等等,如今,也还都是向往。
也庆幸,还有梦,不至于连人生腐烂的理由都没有。
但是这个借口还能用多久呢?
最近搬家,几大箱子的书,很多都是没翻过几页的。这些都是懒惰的凭证。
所以,懒惰是会积累的。当积累到一定的量,就会量变成为平凡。如果此时还没被生活磨灭梦想,磨平棱角,就会堆积出不甘。如果此时还是孤身一人,没有能够开解的人或是另一伴,就会累积成痛苦。如果此时再用游戏等快消品短暂缓解,就会衍生出空虚。此时,早已无力对抗生活。
还有一年,不知道到时候能不能变为魔法师。
继续努力吧。

说明

上一次的运动由于时间关系,最终没有实现理想的效果。本次做了优化,并实现了更加便捷的可视化效果。
经过调试,是因为在label主类中获取的坐标和子类中获取的坐标不一致导致的。
已定义数值如下:
第一臂长度:R1 = 30cm
第二臂长度:R2 = 20cm
坐标原点:(x1,y1)=(0,0),这个也是第一个圆的圆心坐标
目标点坐标:(x2,y2),这个也是第二个圆的圆心坐标
运动结果示意:

内容分解

目标是鼠标点击label的位置,获取对应成机械臂长度的坐标位置,然后换算为点击运动角度,发送给 【Coppeliasim】来控制机械臂运动到指定位置,实现鼠标点击运动。
所以具体内容需要实现:

  • 1:鼠标在label中试试获取坐标
  • 2:比例换算,换算为预定的机械臂坐标
  • 3:点击后获取点击坐标,并在label中直接显示辅助示意图
  • 4:根据坐标,换算两个电机的运动角度
  • 5:将角度发送给 【Coppeliasim】

1:获取坐标、比例换算

  • 具体实现方式在 【ArmPositionControlTest.py】实现。

首先是重写一个label的实现方式为 【 class MyLabel(QLabel) 】,传递一个父类label用于继承,用于将label子类绑定到主界面中的label中,并使用【 setMouseTracking(True) 】方法设置鼠标追踪,实现鼠标位置的实时追踪,这个方法开启后,才能在 MyLabel 子类中实现移动鼠标就能主动触发 【mouseMoveEvent()】 (鼠标移动)事件。在【mouseMoveEvent()】事件中实时获取鼠标坐标,显示在一个小的label中并实时移动这个label就实现了坐标值跟随鼠标指针运动的效果。里面同步实现了比例换算。图片是【660X440】的比例,原点坐标在(330,110)处,则左右、下面各是330的宽度,对应机械臂最长50CM,可以正好换算过去。后续为了方便计算,多数部分的取值都是保留小数点后四位。
实际上,可以在子类中加上点击移动角度换算和发送,就能实现鼠标移动,机械臂也跟着鼠标实时运动。但这与我想要的不符,就没这么写。
MyLabel 子类如下

class MyLabel(QLabel):
    '''
    重写label,用于获取位置调用
    '''
    def __init__(self, parent=QLabel):
        super().__init__(parent)
        self.setMouseTracking(True)  # 开启鼠标追踪功能
        self.setGeometry(0,0,660,440)#设置大小,设置成和输入label一致的大小,覆盖掉原来的label,可以更改为传递大小的
        

    def mouseMoveEvent(self, e):
        '''
        鼠标移动触发的回调事件
        名字是固定的
        '''
        x = e.localPos().x() #获取控件内的相对坐标。注意与父类label中的点击事件获取的坐标的区分
        y = e.localPos().y()
        
        MoveX,MoveY =round((x-330)/660*100,2), round((y-110)/330*50,2)
        if math.sqrt(MoveX*MoveX+MoveY*MoveY) >50.0:
            text = "Out of ArmRange"
        elif math.sqrt(MoveX*MoveX+MoveY*MoveY) <10.0:
            text = "Out of ArmRange"
        else:
            text = 'x: {0}, y: {1}'.format(round((x-330)/660*100,2), round((y-110)/330*50,2))

        MainWindow.MouseMoveLabel.setText(text)   
        MainWindow.MouseMoveLabel.adjustSize() # 自适应宽度
        MainWindow.MouseMoveLabel.move((int)(e.localPos().x()),(int)(e.localPos().y()))  ##移动用于显示的label到鼠标位置,实现跟随显示
        MainWindow.InforShowLable.setText(text)#左下角的label也显示一下

2:获取点击坐标,显示示意图

在窗口的主类中,调用鼠标释放事件【mouseReleaseEvent()】,来处理点击事件(实际测试鼠标单击事件【mouseClickEvent()】没有反应,双击事件倒是没问题。因为鼠标单击释放事件效果和单击事件一样,所以不多做探究)。
在鼠标释放事件中,同样实现鼠标坐标获取和比例换算。要注意,此处获取的是相对于窗体的坐标,而不是相对于label的坐标,所以需要获取并减去label的左上角的坐标才是鼠标相对于label的坐标,这也是上一次怎么计算角度都不对的主要原因。
换算完后,就可以进行角度换算了。换算部分放到后面。
换算完成后,我们会得到以下几个值:两臂所在两端点圆的两个交点坐标、两交点与原点连线形成的夹角的角度、鼠标点击点与原点连线所在直线与X轴夹角的角度等值。我们可以用这些值在label中绘制辅助图形。
因为一开始,我们就用 QPixmap 相关的函数实现了背景图显示:

self.png = QPixmap('ArmRange.png') #画布 背景图 画笔等
self.pngcopy = self.png.copy() #复制一份背景图用于覆盖,因为画笔画完后会保留,所以覆盖原图实现画笔清空
self.label_GetPos.setPixmap(self.pngcopy) #设置背景图    

此处我们就用此作为画布实现辅助曲线的绘制:

 self.painter = QPainter(self.pngcopy)

绑定画笔后,就可以使用画笔绘图了。我们实现以下几个辅助图的绘制:

  • 1:两臂所在的圆的绘制
  • 2:经过两个交点的机械臂的示意图
  • 3:两交点的连线示意图
  • 4:末端点与原点连线的示意图
  • 5:各个点的坐标显示

(注意,由于背景图的原图没保存,只有上一次由于时间关系手绘的不规则范围图,所以画的示意图还有上次的不规则图,懒得再去制作干净的背景图了,可以自行更换干净的背景图)
相关的绘制都在鼠标释放事件【mouseReleaseEvent】中
显示后的图如下:

3:交点和角度的计算

首先,我们将机械臂的两个臂视为以各自末端端点为圆心的圆的某一个半径直线,它们的交点就是这两个圆的交点,也是我们想要求的坐标。

求圆的交点的坐标的方法有很多,此处给出两种较简单的实现方法,分别是【 CalPointOfSection1 】和【CalPointOfSection2 】,都位于MyArmTest.py中。
方法一是三角函数法,具体参考了【 求解两圆相交的交点坐标 】,使用了里面的方法一。
另一种方法是以前做机械臂时使用的一种方法。以前还实现过其他算法,比如参考【 已知圆心坐标和半径的两个圆 的交点坐标】,不过这个计算量较多,此处不放了,可自行参考实现。

得到交点坐标后,就可以开始求对应坐标下的移动角度了。此处由于时间关系,我们只求相对靠左一侧的机械臂的运动角度,靠右一侧实现方法类似,只是有一些小区别。

示例图示:

求角度的方法也有很多,此处我们选择较为稳妥的方法。

  • 1:先求出鼠标点击的目标点与原点连线与Y轴的夹角的角度,即图示 ∠ZOY,使用 【CalAngleOfPoint1】函数
  • 2:求出两圆交点分别与原点连线所在直线的夹角角度,这个角度被上面的直线平分为两半,即图示 ∠aOb,使用【CalAngleBetweenTwoPoint】函数
  • 3:根据这两个角就能求出臂一的运动角度,∠aOY = ∠ZOY - ∠aOb/2

开始求臂二的角度:
使用函数【CalAngleOfArm2】

  • 1:上一步已求出∠aOZ,又已知臂一 L(Oa) 的长度,利用三角函数可得 L(an)的长度(也可以用L(ab)的距离除以2得到),同理,可以得到L(On)的长度,已知Z的坐标,得到L(OZ)的长度,减去L(On)得到L(Zn)的长度。注意,L(an)或L(Zn)只要求出其中一个就够了
  • 2:已知L(aZ)的长度,加上上面求出的一个边,可以得到∠naZ
  • 3:∠Oan = 90-∠ZOa,这样得到了∠OAZ= ∠Oan+∠naZ,就求出了臂二在a点的旋转角度
  • 4:注意,臂二默认的角度是垂直于臂一的,所以要用90度减去上面计算的值才是【Coppeliasim】中实际旋转的角度。可以自行在【Coppeliasim】软件中修改角度的默认值。
    以上具体代码详见【MyArmTest.py】和 【ArmPositionControlTest.py】

4:发送给Coppeliasim

至此,完成了示意图的显示、角度的计算,将计算得到的角度发送给【Coppeliasim】软件进行运动就好了。

此处由于时间关系,发送的还是最终的计算结果,没对过程进行插值处理,导致机械臂立刻就转到目标点了,没有转动过程。
实际上,有几种方法实现转动过程。

  • 方法一:就是将计算后的角度按照想要的细分度,分为若干份,然后开启一个定时器,依次发送细分的角度值,实现平滑运动。定时器的间隔和细分的数量决定了运动速度和运动平滑度,可以使用按钮设置参数实现调速。
  • 方法二:将目标点与当前点之间按照直线关系或是更高级的曲线关系连接起来,并将连线细分为若干份,开启一个定时器,让末端依次移动到细分位置处实现平滑运动。定时器的间隔和细分的数量决定了运动速度和运动平滑度,可以使用按钮设置参数实现调速。

这两种方法各有优缺点。方法一由于只专注于运动角度,所以末端的位置运动不规则,末端运动速度也很难把控,可能在不同段速度都不同。好处是可以实现绝大多数位置的过度。 方法二是末端轨迹明确,末端速度可控,但是对于某些位置的移动,单纯的直线实现不了时(比如直线中经过超范围区,或者需要运动到两个臂的夹角翻转的位置),改为曲线运动会很复杂。
由于时间关系,本次不做插值的实现。

总结

本次实现了点击后直接显示结果示意图、修正了角度计算结果,最终实现了既定的目标。

但是有些必要的功能还是没有实现的,比如插值运动,比如两个圆的交点目前只是简单的使用了其中一个,另一个没有使用。实际情况中,两个交点会出现交替使用才能满足运动结果的情况,此处由于时间关系没有实现。

另外,本次结果还是有些瑕疵的,比如机械臂无法实现伸直的情况(交点接近或是直接就是一个),如图:

当然,若是时间充足,解决上述小瑕疵还是没问题的,只是需要进一步调试。此处不再深入研究。

后续会开启硬件方面的探究,最终需要用到时再根据实际情况实现和优化必要的功能。

以上代码位于:【执念执战Gitee

  • 本文水平有限,内容很多词语由于知识水平问题不严谨或很离谱,但主要作为记录作用,希望以后的自己和路过的大神对必要的错误提出批评与指点,对可笑的错误请指出来,我会改正的。

  • 另外,转载使用请注明作者和出处,不要删除文档中的关于作者的注释。

随梦,随心,随愿,恒执念,为梦执战,执战苍天! ------------------执念执战

原文地址:https://www.cnblogs.com/zhinianzhizhan/p/17360467.html