Arduino 2 Blender 串口通讯实现动作捕捉

Xsens动作捕捉 2023-04-11 3321

整个架构是:

Arduino 读取 4 个电位器的模拟电压值,通过 ADC 采集并转换为浮点数据,通过串口发送给上位机(电脑)

上位机上运行 Blender 中的 Python 代码,读取串口数据并转换,将数据转送给全局的关节数据字典,同时另一个线程中读取字典的值并将关节角度绑定到骨架上


Arduino 程序

/** Blender_Serial_Comm_Arduino_Code.ino

*

* Demonstrates reading four(4) potentiometer sensor readings from a robotics arm

* and sending the data to Blender running Python via the Serial port.

*/

/* 取消下面这样的注释来输出 DEBUG 信息 | uncomment the line below to view floating point data via Serial monitor. */

//#define DEBUG

void setup() {

/* 初始化串口通讯 | setup the serial port */

Serial.begin(115200);

}

void loop() {

float data[4];

/* 读取四个电位器的值 | read and process the potentiometer data */

data[0] = analogRead(A0) - 100;

data[1] = analogRead(A1) - 500;

data[2] = analogRead(A2) - 540;

data[3] = analogRead(A3) - 870;

data[0] *= 0.0013;

data[1] *= 0.0013;

data[2] *= 0.0013;

data[3] *= 0.0013;

#ifndef DEBUG

for (int i=0; i<4; i+=1) {

/* 将 32 位的 float 型转换为 4 个字节并从串口输出

| convert the 32-bit floating point into 4 8-bit uint8_t buffer array */

uint8_t *buffer;

buffer = (uint8_t *)&data[i];

Serial.write(buffer[0]);

Serial.write(buffer[1]);

Serial.write(buffer[2]);

Serial.write(buffer[3]);

}

#else

Serial.print(data[0]); Serial.print("\t");

Serial.print(data[1]); Serial.print("\t");

Serial.print(data[2]); Serial.print("\t");

Serial.print(data[3]); Serial.print("\t");

#endif

/* 输出一个换行代表消息结束 | this is the end-of-message flag */

Serial.print("\n\n");

delay(20);

}


Python 这边需要安装 pyserial 这个第三方库,而且需要安装到 Blender 自带的 py 环境中

在 Blender 中的 Python 是独立于系统的单独环境,所以需要在这个环境下安装第三方的库。方法如下:


在 Blender 的 Python Terminal 中输入

import sys

sys.exec_prefix

即可得到 Blender 的 Python 的文件位置

Arduino 2 Blender 串口通讯实现动作捕捉  第1张


打开 Powershell, cd 到那个文件路径下的 bin 文件夹中 (python 的版本是 3+,那个 2.82 是 Blender 版本)

cd D:\Documents\Blender\2.82\python\bin

该文件路径下有一个 python.exe,这个就是 Blender 所使用的 Python 文件

运行 pip

.\python.exe -m pip install pyserial
Arduino 2 Blender 串口通讯实现动作捕捉  第2张

现在即可在 Blender 中调用这个第三方 serial 库


Blender Python 程序

import math

import struct

from threading import Thread

import bpy

import serial

# 定义全局变量 | define global variables

stop_flag = 0

position_data = []

# 从 Blender 获取场景物体 | get the blender scene objects

# 先获取骨架

armature = bpy.data.objects["Armature"]

bone0 = armature.pose.bones.get("Bone")

bone0.rotation_mode = AXIS_ANGLE

bone1 = armature.pose.bones.get("Bone.001")

bone1.rotation_mode = AXIS_ANGLE

# 这里改变一下骨头的旋转轴,设置成围绕 y 轴旋转

bone2 = armature.pose.bones.get("Bone.002")

bone2.rotation_mode = AXIS_ANGLE

bone2.rotation_axis_angle[1:] = [0, 1, 0]

bone3 = armature.pose.bones.get("Bone.003")

bone3.rotation_mode = AXIS_ANGLE

# 定义一个定时器来负责场景的刷新

class ModalTimerOperator(bpy.types.Operator):

# 这两个名字是必须的,是 Blender 的标识符

bl_idname = "wm.modal_timer_operator"

bl_label = "Modal Timer Operator"

_timer = None

def __init__(self):

pass

def modal(self, context, event):

global stop_flag

# 侦听 Blender 窗口的活动,如果按下了 ESC 键就停止所有线程

if event.type == ESC:

stop_flag = 1

# bpy.utils.unregister_class(ModalTimerOperator)

return self.cancel(context)

# 如果是定时器更新,说明我们需要刷新页面了

if event.type == TIMER:

# 确保接收到串口的数据后,更新四个骨骼的旋转

if len(position_data) >= 4:

bone0.rotation_axis_angle[0] = (position_data[0] * math.pi)

bone1.rotation_axis_angle[0] = (position_data[1] * math.pi)

bone2.rotation_axis_angle[0] = (position_data[2] * math.pi)

bone3.rotation_axis_angle[0] = -(position_data[3] * math.pi)

# 返回 "PASS_THROUGH",这样可以让其他 Handler 依旧接收到所有事件

return {PASS_THROUGH}

def execute(self, context):

# 启动计时器,间隔为 0.1 秒

self._timer = context.window_manager.event_timer_add(0.1, window=context.window)

context.window_manager.modal_handler_add(self)

return {RUNNING_MODAL}

def cancel(self, context):

# 停止计时器

context.window_manager.event_timer_remove(self._timer)

return {CANCELLED}

# 定义一个从串口读取数据的函数

def readData(ser):

c = b""

buffer = b""

while True:

c = ser.read(1)

if c == b"\n":

# detect for end-of-message flag

if ser.read(1) == b"\n":

break

buffer += c

return buffer

# 这个函数负责管理串口的连接和通讯

def serialCommHandler():

global position_data

# 开启串口 | open serial port

ser = serial.Serial(port="COM3", baudrate=115200)

while not stop_flag:

try:

# 读取串口数据并按照四个 float 的格式解码

position_data = struct.unpack("<ffff", readData(ser))

except Exception as e:

print(e)

print("Exit condition detected.")

ser.close()

if __name__ == "__main__":

bpy.utils.register_class(ModalTimerOperator)

# 串口通讯在子线程中运行

t = Thread(target=serialCommHandler, args=())

t.start()

# 启动 Blender 的 timer | run blender loop

bpy.ops.wm.modal_timer_operator()


另外还有一段单独的 Python 程序用来调试

""" read_serial.py

"""

import struct

import serial

ser = serial.Serial(port="COM3", baudrate=115200)

print(ser.name)

def readData():

c = b""

buffer = b""

while True:

c = ser.read(1)

if c == b"\n":

if ser.read(1) == b"\n":

break

buffer += c

return buffer

while True:

data = struct.unpack("<ffff", readData())

print(data)

The End