BlenderCN论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

扫一扫,访问微社区

查看: 2689|回复: 11

Blender无人机控制

[复制链接]
发表于 2015-11-23 16:02:22 | 显示全部楼层 |阅读模式
本帖最后由 蒜薹 于 2016-5-18 20:59 编辑

目标:使用Blender对多架无人机进行控制
方法1:实时控制
方法2:预定轨迹

方法分析-方法1
(1) 死循环
    实时控制是在Blender内,对无人机模型的操作,如平移,可以实时地反映到真实世界中的无人机上。
    这里所谓的实时,其实是时间间隔很短的位置信息采集。不妨以1秒为例,在python中编写一个每隔1秒采集一次所关心物体的位置,并输出。这里不妨将输出定义为在终端中显示出来。
    上述流程在python中实现比较简单,在blender中也已经验证通过。这个脚本是一个死循环,一直持续到无人机表演结束,而问题是在这个脚本运行过程中,blender一直处于一个假死的状态,无法进行任何操作,除了在终端中关闭这个脚本的control+C命令。
    如果在采集坐标的程序运行中,无法对blender内对应无人机的模型进行移动操作,那这个脚本也就失去了其存在价值。因此,需要解决脚本运行中,blender假死的问题。
(2) 多线程
    根据BlenderCN论坛找到的信息,使用多线程是潜在的解决方案。
    在blender.stackexchange.com中找到了相似的问题,并且得到了回答http://blender.stackexchange.com/questions/18809/blender-game-engine-via-python/18813#18813
    在blendercn代码开发讨论的qq群内得到了许多帮助,其中imdjs建议使用动态链接库,并给出了相应的代码方案,文章名叫《[经验分享]在blender里用py导入c/c++编译的dll》,这是在blendercn手机版上提出的,我在电脑浏览器上不会打开,因此不给链接了。
  
    多线程的方法很简单,直接继承python提供的threading类,将提取坐标的代码写入其中的run()函数即可。具体见附录。
(3) 键盘鼠标模块自带时间控制
    在Blender代码开发讨论qq群的帮助下,了解到Blender事例脚本代码中有一个名为operator_modal_timer.py的脚本,其功能是在不影响Blender正常使用的前提下随时间变化背景颜色。这一功能正是所需要的!因此,简单的将修改背景颜色的代码替换成取得各个物体的坐标并输出即可。
    此外,本文目标在于控制无人机,与无人机工程师的讨论结果是使用Socket作为网络通信方法。在此一并将python的socket通信相关代码也放出来。

方法分析-方法2
    预定轨迹的做法有很多,根据目前对Blender的理解,配合Blender帧动画输出位置坐标的方法是十分理想的。
    选取特定帧是一个重点,blender.stackexchange.com提到了一种不用获取帧又可以得到特定帧下位置的简单处理方法,就是获取F-Curve。





 楼主| 发表于 2016-6-21 08:50:46 | 显示全部楼层
无人机三维控制界面第二次联合调试
    测试于2016-6-17完成,控制端位于哈尔滨,图中右侧即是,被控制端位于深圳,同样采用blender,图中左侧QQ视频中的笔记本电脑屏幕中即是。服务器由阿里云提供,图中没有给出。远程控制的联合测试成功。
    测试中,延迟在1s量级,但由于QQ视频自身传输有时间延迟,因而不能准确判定数据传输延迟。
人机交互方案测试
  • (1) 实时控制
    在数据传输测试中,若未说明,则全部使用的是实时控制方案,方案测试可行。
    后续研究中,考虑加入blender刚体模拟,以达到无人机碰撞检测和防止的目的。
(2) 预定义轨迹控制-关键帧动画
    于2016-6-16测试完成。在网络数据传输(内环测试)基础上,通过加入Blender的关键帧动画,达到预定义轨迹的目的,测试成功!下一步,等待与深圳联合调试的同时,考虑使用blender刚体物理模拟来完成碰撞检测和防止。左侧是服务器端,作数据中转站;右上是控制端,其中的立方体是控制用的无人机,圆柱体是从被控制端反馈回来的无人机实际位置,五边形是编排队形时的参照;右下是被控制端,其中的圆柱体显示的是从控制端发送过来的数据。



回复 支持 1 反对 0

使用道具 举报

 楼主| 发表于 2016-11-8 09:59:23 | 显示全部楼层
本帖最后由 蒜薹 于 2016-11-8 21:39 编辑

Blender平无人机控制界面-第二轮开发
开始时间:2016年10月
目标:连接真实的无人机飞行控制电路板和blender

三 开发细节记录
     2016年10月末完成了各个模块的独立调试,包括USB转串口模块的驱动安装、电脑端sscom程序的控制,无线数传模块的USB转TTL模块接入、波特率调整和两个无线数传模块的通信,飞控板的USB转TTL模块接入、上电运行、输出数据在电脑端sscom程序中的检查。
    2016年11月初开始进行飞控板的具体调试。在深圳做无人机的朋友的远程协助下,成功地进行了飞行控制程序的修改、编译、烧写,并且通过USB转串口,成功地在电脑上看到了无人机的传感器数据,包括陀螺仪和加速度传感器。
    接着使用USB串口通信和socket网络传输,实现了用单片机生成数据并且通过本地电脑(win)读取,再通过服务器将数据传输至远程电脑(mac)的blender中,但数据并不是由飞控板产生的,而是使用两个USB转TTL模块,各连接一个无线数传模块,一个端口用sscom等时间间隔地发送数据,另一个端口用python接收。

接着将无人机飞控板的数据通过USB转TTL模块接入本地电脑(windows)的USB端口,配合本地电脑(windows)的串口通信将数据取得,再通过socket上传到服务器(Mac)。虚拟显示方面,作为显示平台的blender在Mac系统中,与windows下的数据上传程序一样,都属于客户端,两者的数据在服务器程序中进行交互。具体的交互方式和程序框架沿袭自无人机控制界面第一轮开发的结果,搞懂不大,可以参考。
    接着测试无线传输模块。无人机的数据原本是通过USB转TTL模块直接接入电脑的,但是受到信号线(这里使用排线)长度限制,飞控芯片不能自由变换姿态,因此在飞控板和电脑USB接口(实际上是USB转TTL模块)之间使用无线数传模块。飞控板方面,无线数传模块同样适用飞控板的电池供电,因此飞控板旁边有两个部件——电池和无线数传模块。
    由于目前的飞控板程序内只有三个姿态角(俯仰、翻滚和航向)的数据,没有坐标数据,这次(2016年11月7日)调试的目标是将无人机姿态实时传输到blender内,并在blender中同步显示。由于之前的工作已经解决了网络数据传输和串口通信的问题,数据传输方面已经可以正常工作了,本次的重心在blender变量上。具体而言,就是找出飞机俯仰、翻滚和航向在blender中的对应的表示方式。
    飞机的姿态角实际是与笛卡尔坐标系中三个轴的夹角,常用的坐标系有两个,一个是相对于地球不变的坐标系(blender中的全局坐标系),一个是相对于飞控板不变的坐标系(blender中的局部坐标系),大翰科技的飞控程序所使用的坐标系属于前者。在blender中,物体的旋转使用三套数学表示方式,坐标轴角度(Axis Angle)、欧拉角(Euler)和四元数(Quaternion),其中的欧拉角与姿态角很像,坐标轴角度不懂,四元数是另外一套方式。因此我们使用欧拉角做对应,参考图3。其实欧拉角还是不懂,目前的结果是试出来的,接下来需要了解blender中的欧拉角的具体设定。
    实际上针对blender旋转控制,调试中还使用过一个方法,就是使用bpy.ops.transform.rotate()函数,这个函数是在物体现有的姿态基础上进行旋转,可以选择全局坐标系或者局部坐标系。使用这个函数之前需要将被旋转的物体设置为当前激活(active)的物体,代码方面使用bpy.context.scene.objects.active = bpy.data.objects[‘drone1’]即可,具体参考官方的Python API的Types, Object(ID)章节(https://www.blender.org/api/blender_python_api_2_78a_release/bpy.types.Object.html#bpy.types.Object )。


   我所写的程序均遵循开源协议,允许大家封装和商业化使用。
   附件中包括了服务器(transfering.py)、blender端(recording_multi_controll.py)和无人机数据接收端(recording_multi_real.py)的代码
   感谢blenderCN社区朋友们的长期帮助和支持!


回复 支持 1 反对 0

使用道具 举报

 楼主| 发表于 2015-11-23 16:04:24 | 显示全部楼层
本帖最后由 蒜薹 于 2015-11-24 10:02 编辑

说帖子太长不能发,只能把代码放到这里了代码已经托管在github,git@github.comascalXie/Blender_DroneControl.git
代码:
1 实时控制
# Client - A blener script
import bpy
import time
from socket import *

time_start = time.time()

scn = bpy.context.scene
obj = bpy.context.active_object
obj_each = obj

class TcpClient:
        HOST='127.0.0.1'
        PORT=12345
        BUFSIZ=1024
        ADDR=(HOST, PORT)
        def __init__(self):
                self.client=socket(AF_INET, SOCK_STREAM)
                self.client.connect(self.ADDR)

        def SendMassage(self, message):
                data='Hello'
                data=message
                self.client.send(data.encode('utf8'))
                data=self.client.recv(self.BUFSIZ)
                print(data.decode('utf8'))
                self.client.close()

class ModalTimerOperator(bpy.types.Operator):
        """Operator which runs its self from a timer"""
        bl_idname = "wm.modal_timer_operator"
        bl_label = "Modal Timer Operator"
        
        _timer = None
        
        def op_coor(self, obj_each):
                "Output coordinates of each object"
                print(obj_each.name, obj_each.location)
                DroneData = str(obj_each.name)        +'        '+ str(obj_each.location)
                client=TcpClient()
                client.SendMassage(DroneData)
                return

        def modal(self, context, event):
                if event.type in {'ESC'}:
                        self.cancel(context)
                        return {'CANCELLED'}
                        
                if event.type == 'TIMER':
                        # change theme color, silly!
                        print("Time: {0} secends".format(time.time()-time_start))
                        for obj_each in scn.objects:
                                self.op_coor(obj_each)

               
                return {'PASS_THROUGH'}
        
        def execute(self, context):
                wm = context.window_manager
                self._timer = wm.event_timer_add(0.1, context.window)
                wm.modal_handler_add(self)
                return {'RUNNING_MODAL'}
        
        def cancel(self, context):
                wm = context.window_manager
                wm.event_timer_remove(self._timer)
        
def register():
        bpy.utils.register_class(ModalTimerOperator)


def unregister():
        bpy.utils.unregister_class(ModalTimerOperator)


if __name__ == "__main__":
        register()

        # test call
        bpy.ops.wm.modal_timer_operator()

# Server
#-*- coding: utf-8 -*-
from socket import *
from time import ctime

HOST=''
PORT=12345
BUFSIZ=1024
ADDR=(HOST, PORT)
sock=socket(AF_INET, SOCK_STREAM)

sock.bind(ADDR)

sock.listen(5)
while True:
    print('waiting for connection')
    tcpClientSock, addr=sock.accept()
    print('connect from ', addr)
    while True:
        try:
            data=tcpClientSock.recv(BUFSIZ)
        except:
            print(e)
            tcpClientSock.close()
            break
        if not data:
            break
        s='Hi,you send me :[%s] %s' %(ctime(), data.decode('utf8'))
        tcpClientSock.send(s.encode('utf8'))
        print([ctime()], ':', data.decode('utf8'))
tcpClientSock.close()
sock.close()



回复

使用道具 举报

 楼主| 发表于 2015-11-23 16:05:44 | 显示全部楼层
本帖最后由 蒜薹 于 2015-11-24 10:05 编辑

代码已经托管到github,git@github.com:(去掉这个括号)PascalXie/Blender_DroneControl.git

2 预定轨迹
根据目前需求,脚本2更有效,因为脚本2直接操作帧数
脚本1:
import bpy

action = bpy.data.actions['CubeAction']
for fc in action.fcurves:
    if fc.data_path == 'location' and fc.array_index == 0:
        break
val = fc.evaluate(5.5)

脚本2:
import bpy

scn = bpy.context.scene
obj = bpy.context.active_object

for f in range(scn.frame_start, scn.frame_end):
    # use frame_set() so that keyed values are updated
    scn.frame_set(f)
    print(scn.frame_current, obj.location)

验证可用脚本如下
file=open("/Users/pascal/myBlender/deforming/coordinates.data","w+")

import bpy

scn = bpy.context.scene
obj = bpy.context.active_object

def writeFCurve(scn, obj):
        for f in range(scn.frame_start, scn.frame_end):
                # use frame_set() so that keyed values are updated
                scn.frame_set(f)
                print(scn.frame_current, obj.location)
                file.write("{0}        {1} {2}\n".format(scn.frame_current, obj.name, obj.location))

for obj_each in scn.objects:
        writeFCurve(scn, obj_each)
file.close()


回复 支持 反对

使用道具 举报

 楼主| 发表于 2015-11-23 16:13:13 | 显示全部楼层

图中上方终端显示的是客户端,下方终端显示的是服务器端

图中上方终端显示的是客户端,下方终端显示的是服务器端

回复 支持 反对

使用道具 举报

 楼主| 发表于 2015-12-22 20:38:53 | 显示全部楼层
本帖最后由 蒜薹 于 2015-12-22 20:58 编辑

无人机三维控制界面第一次联合调试
时间:2015年12月18日
地点:深圳
一 目标
    测试socket在局域网内的网络连接,将Blender的坐标数据从客户端上传至服务器端。
二 测试过程及结果
    使用socket进行网络数据传输,客户端为MacBook pro,由python调用socket客户端(client),负责操作blender;服务器端为windows笔记本,由python调用socket服务器端(sever),负责连接无人机。使用无线路由器搭建的局域网。
    测试中,客户端由blender输出的物体坐标能够传输到服务器端,且服务器端数据可以返回到客户端。
    测试结果表明局域网下的socket数据传输成功,blender数据导出成功。
    同时,测试中也暴露了几个问题,首先,由于服务器端未能调通无人机数据收发,不能进行客户端对无人机的操作;其次,由于网络传输存在延迟,而socket客户端程序占用blender主进程,导致blender操作卡顿。针对第一个问题,无人机工程师给予解决;针对第二个问题,潜在的解决方案是将socket客户端交由单独的进程处理,该进程需要独立于blender的主进程。

回复 支持 反对

使用道具 举报

 楼主| 发表于 2015-12-22 21:01:27 | 显示全部楼层
无人机三维控制界面第一次联合调试
时间:2015年12月18日
地点:深圳
P51218-154112-small.jpg
图1 测试过程图,左侧Macbook是客户端,右侧Windows是服务器端,macbook后方是无人机

回复 支持 反对

使用道具 举报

 楼主| 发表于 2016-5-18 21:01:19 | 显示全部楼层
使用python多线程的坐标提取方法(仅客户端,服务器端与之前相同):
import bpy
import time
import threading
from socket import *

scn = bpy.context.scene
obj = bpy.context.active_object
obj_each = obj

# Socket - client
class TcpClient:
        HOST='127.0.0.1'
        PORT=6000
        BUFSIZ=1024
        ADDR=(HOST, PORT)
        def __init__(self):
                self.client=socket(AF_INET, SOCK_STREAM)
                self.client.connect(self.ADDR)

        def SendMassage(self, message):
                data='Hello'
                data=message
                self.client.send(data.encode('utf8'))
                data=self.client.recv(self.BUFSIZ)
                print(data.decode('utf8'))
                #self.client.close()

# inheriting threading class
# Getting position and transfering them to Unity
class myThread (threading.Thread):
        """Getting positoins of all objects and sending them to server"""
        client = TcpClient()

        # Overriding __init__
        def __init__(self, name):
                threading.Thread.__init__(self)
                self.name = name
       
        def run(self):
                counter = 100
                while counter:
                        print("hello")
                        time.sleep(0.1)
                        counter -= 1
                        #print_name()

                        #Getting information about each object
                        #Sending them to server
                        for obj_each in scn.objects:
                                DroneData = str(obj_each.name)        +'        '+ str(obj_each.location)
                                self.client.SendMassage(DroneData)
                       

def print_name():
        for obj_each in scn.objects:
                print("{} : {}".format(obj_each.name, obj_each.location))


#------------------------------------------------
# main
if __name__ == "__main__":
        #print(obj_each.name)
        #for obj_each in scn.objects:
        #        print(obj_each.name)

        thread1 = myThread("test")

        thread1.start()



回复 支持 反对

使用道具 举报

 楼主| 发表于 2016-6-13 17:04:50 | 显示全部楼层
Blender平台第二版无人机控制界面目标:内环测试
地点:哈尔滨

时间:2016年6月8日

一 简介
1 背景介绍
    无人机群体控制,分为控制端和被控制端。被控制端的电脑负责对真实世界中的无人机进行控制。控制端以Blender为平台,建立虚拟的三维控制界面,在其中设置无人机模型,每隔固定时间(如0.1s)将虚拟界面中的无人机名称、坐标等参数传给被控制端的电脑。
    当前,我们负责控制端的操作界面编写,地点在哈尔滨,而负责无人机硬件在深圳,作为被控制端。两方均为局域网,无法直接实现数据互传。解决方案是,使用一台阿里巴巴的服务器作为数据传输中转站,同时连接哈尔滨和深圳。

2 任务目标
    以服务器的视角,任务目标有两个,分别针对在哈尔滨的控制端和在深圳的被控制端。
    对控制端,服务器需要接收并存储控制端发送的数据,将被控制端的对应飞机的数据返回至控制端。
    对被控制端,服务器需要接收并存储被控制端发送的数据,将控制端的对应飞机的数据返回至被控制端。
二 程序架构
1 程序组成
    一共有三个程序用来完成数据传输,分别安置在哈尔滨、阿里服务器和深圳。在哈尔滨和深圳的程序都是客户端(client),在阿里服务器上部署的是服务器端(server)。两个客户端分别与服务器端相连,而这两个客户端之间没有连接,也就是没有数据通信。
    位于哈尔滨的客户端是用来操控无人机的,以Blender为平台,使用Python编写,后文中也以控制端(Controll)为代号,代码中同样如此。位于深圳的客户端是用来接收操控端的数据,并将真实中的无人机数据通过服务器返回至操控端,由此在后文也称作被操控端,或者叫真实世界端(Real)。被操控端在我们的调试中依然以Blender为平台,Python编写脚本。
    最后,位于阿里服务器上的,就叫做服务器端(Server),Python语言编写,不依托Blender平台。
2 服务器端程序架构
    服务器端负责连接两个客户端,每个客户端由一个服务器负责连接。服务器端在主进程下开启两个子进程,分别以Controll和RealWorld指代哈尔滨和深圳两个客户端。
    子进程类(MyThread_Server)以threading.Thread为父类,重写(Override)__init__()和run()函数,将服务器类(TcpServer)添加到run()函数中,通过主进程调用start()函数完成run()函数的调用,通过run()函数的调用完成服务器开启。
    服务器分为两个,以名称”Controll”和”RealWorld”区别。两个服务器具体工作内容相似,以”Controll”为例。在设定服务器IP(HOST)和端口(PORT)之后,进入等待连接状态。在控制端(哈尔滨)获取连接并传输数据后,服务器将数据解析成无人机名称、坐标(笛卡尔坐标系下的三个轴向分量),接着通过函数SetDronePosition()将这些数据存储在droneData_aim(字典)内,再将真实世界中的对应这架无人机的数据(存储在字典droneData_real内)传输回控制端(哈尔滨)。
3 控制端程序介绍
(1) 程序功能介绍
    控制端位于哈尔滨,以Blender为平台,使用Python脚本语言,使用Socket网络传输的客户端,专门建立一个子线程用于客户端运行。客户端与阿里的服务器连接,将本地的blender中的无人机模型的名称、坐标等信息上传到服务器中,并接收服务器传回的真实世界中的无人机信息。
(2) 程序架构
    与服务器端的程序架构类似,子进程类(MyThread)以threading.Thread为父类,重写(Override)__init__()和run()函数,在子进程中实例化Socket的客户端类TcpClient,并在run()中调用。客户端类中,准备一个SendMessage()函数,用于完成两个功能,一个是发送Blender中虚拟飞机的数据,一个是接收真实世界数据并解析显示在Blender中。
    客户端的Blender界面中,准备了两套飞机,一套是用于控制的飞机,一套是显示真实世界中无人机位置的飞机。客户端会将用于控制的飞机的数据发送给服务器,将服务器传回的真实世界的无人机数据(名称最后以”_real”结束)解析后变成另外一套飞机的坐标。
(3) 人机交互
    控制端的blender界面中,有两套飞机,可以以任何几何体代表(客户端程序中尚未添加对几何体外形的识别),以名称尾部是否带有”_real”区分,具体名称根据用户需求定义。称尾部不带有”_real”的飞机为控制飞机,称尾部带有”_real”的飞机为现实世界飞机。
    用户可以根据自身需求任意定义飞机的名称。两套飞机中,所有定义过的控制飞机的数据都会上传并且保存在服务器中,详细数据会以固定时间间隔进行更新上传;所有定义过的现实世界飞机的信息不会上传至服务器,但是会与服务器内存储的现实世界飞机的信息同步。
    需要注意,现实世界飞机中,只有名称前半部分,即去除”_real”之后的部分,与控制飞机名称完全相同(字母大小写敏感)的才会被控制端程序识别,并且与服务器进行同步。
    根据上述界定,简单的人机交互基础就是两套飞机的名称需要对应,如控制飞机名为“Test_Drone”,则对应的现实世界飞机名为“Test_Drone_real”,且飞机数量无限制,但数量越多,需要传输的数据就越大,目前还不能估计潜在的问题。两套飞机中,只有控制飞机需要用户操作,现实世界飞机不需要操作,但可以操作。
4 真实世界端程序介绍
(1) 程序功能介绍
    真实世界端位于深圳,目的是模拟真实世界中的飞机机群,同样以Blender为平台,使用Python脚本语言,使用Socket网络传输的客户端,专门建立一个子线程用于客户端运行。客户端与阿里的服务器连接,将本地的blender中的无人机模型的名称、坐标等信息上传到服务器中,并接收服务器传回的控制端中的无人机信息。
(2) 程序架构
    真实世界端与控制端的架构在Socket网络传输方面完全相同,这里不再赘述。在数据显示方面,真实世界端仅仅只是送出自身现有的无人机坐标,再刷新这些飞机。
    未来可在此基础上增加模拟现实情况的函数,如飞机速度限制、随机抖动等等。
(3) 人机交互
    目前的真实世界端无人机交互。
三 测试结果
    测试分为内环测试、哈尔滨-服务器-哈尔滨传输测试、哈尔滨-深圳数据传输测试以及最终的哈尔滨-深圳无人机控制测试。
    目前已经完成内环测试,可以正常操作,但卡顿明显。由于是内环测试,网络延迟极低,不会造成0.1s量级的卡顿,因而造成卡顿的主要原因是控制端和真实世界端的采样间隔较长,均为0.1s。


回复 支持 反对

使用道具 举报

 楼主| 发表于 2016-6-13 17:07:34 | 显示全部楼层
无人机控制界面,blender平台,第二版(内环测试版)代码再附件中,欢迎大家讨论!

drone_controll_20160613.zip

4.63 KB, 下载次数: 772

回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关闭

站长推荐上一条 /1 下一条

Blender最新中文教学视频|Blender头条|小黑屋|手机版|Archiver|Blender中国 ( 蜀ICP备17002929号 )360网站安全检测平台

GMT+8, 2019-9-24 00:56 , Processed in 0.036144 second(s), 21 queries .

Powered by Discuz! X3.4

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表