Lerobot--so100,不仅能用来强化学习更能通过大模型控制!!
Lerobot--so100,不仅能用来强化学习更能通过大模型控制!!_lerobot 逆解-CSDN博客
这是之前发布的一篇文章,当时答应在优化后开源,但是最近一直忙着其他事,没时间优化,但是五一之前,也先填坑吧,后续有其他改动,在进行补充。
lerobot_keycon_gposAddllm111.py;;;这是仓库中代码的名称;
import os
import mujoco
import mujoco.viewer
import numpy as np
import time
from lerobot_kinematics import lerobot_IK, lerobot_FK, get_robot
import rospy
from std_msgs.msg import String
import threading
os
:用于处理文件路径和环境变量。mujoco
和mujoco.viewer
:MuJoCo 物理仿真引擎及其可视化工具。numpy
:用于数值计算。time
:用于时间管理。lerobot_kinematics
:自定义模块,包含逆运动学(lerobot_IK
)、正运动学(lerobot_FK
)函数和获取机器人模型的函数(get_robot
)。(这个呢是盒子桥作的工作,整体仓库基本也是他所作的工作,但是为了验证想法,在其中作了一点点改动)rospy
和std_msgs.msg.String
:ROS 相关库,用于节点初始化、消息订阅。threading
:用于多线程编程,保证线程安全。这个还是有必要的,前期没有进行多线程的话就会出现报错,以及响应差。
np.set_printoptions(linewidth=200)
os.environ["MUJOCO_GL"] = "glfw"
JOINT_NAMES = ["Rotation", "Pitch", "Elbow", "Wrist_Pitch", "Wrist_Roll", "Jaw"]
current_script_path = os.path.dirname(os.path.abspath(__file__))
xml_path = os.path.join(current_script_path, "scene.xml")
mjmodel = mujoco.MjModel.from_xml_path(xml_path)
qpos_indices = np.array([mjmodel.jnt_qposadr[mjmodel.joint(name).id] for name in JOINT_NAMES])
mjdata = mujoco.MjData(mjmodel)
JOINT_INCREMENT = 0.005
POSITION_INSERMENT = 0.0008
robot = get_robot('so100')
control_qlimit = [[-2.1, -3.1, -0.0, -1.375, -1.57, -0.15],
[ 2.1, 0.0, 3.1, 1.475, 3.1, 1.5]]
control_glimit = [[0.125, -0.4, 0.046, -3.1, -0.75, -1.5],
[0.340, 0.4, 0.23, 2.0, 1.57, 1.5]]
init_qpos = np.array([0.0, -3.14, 3.14, 0.0, -1.57, -0.157])
target_qpos = init_qpos.copy()
init_gpos = lerobot_FK(init_qpos[1:5], robot=robot)
target_gpos = init_gpos.copy()
lock = threading.Lock()
np.set_printoptions(linewidth=200)
:设置numpy
数组打印的行宽。os.environ["MUJOCO_GL"] = "glfw"
:指定 MuJoCo 使用glfw
作为渲染后端。JOINT_NAMES
:定义机器人关节名称。xml_path
:获取 MuJoCo 模型的 XML 文件路径。这些你们看着修改就行mjmodel
和mjdata
:加载 MuJoCo 模型和数据。JOINT_INCREMENT
和POSITION_INSERMENT
:分别为关节角度和位置的增量。control_qlimit
和control_glimit
:关节角度和位置的限制范围。init_qpos
和init_gpos
:初始关节角度和位置。target_qpos
和target_gpos
:目标关节角度和位置。lock
:线程锁,用于保证线程安全。
keys_received = {}
def ros_message_callback(msg):
key = msg.data
if key in key_to_joint_increase:
with lock:
keys_received[key] = 1 # Increase direction
elif key in key_to_joint_decrease:
with lock:
keys_received[key] = -1 # Decrease direction
elif key == "0":
with lock:
global target_qpos, target_gpos
target_qpos = init_qpos.copy() # Reset to initial position
target_gpos = init_gpos.copy() # Reset to initial gripper position
key_to_joint_increase
和key_to_joint_decrease
:定义按键与关节控制的映射关系。keys_received
:用于记录当前接收到的按键及其方向。ros_message_callback
:ROS 消息回调函数,依据接收到的按键消息更新keys_received
字典,若收到"0"
则将机器人重置到初始位置。
这里比较巧妙的用ros参与 ,接收从LLM发送过来的话题,然后将话题的信息给回调函数,进行控制。
然后就是关于:voiceLLMcom.py的代码部分::
import openai
from httpx_socks import SyncProxyTransport
import httpx
import os
import rospy
from std_msgs.msg import String
import sounddevice as sd
from scipy.io.wavfile import write
import speech_recognition as sr
from pynput import keyboard
import numpy as np
openai
:用于调用百炼(这里使用了兼容 OpenAI 接口的形式)的 API 来处理自然语言指令。SyncProxyTransport
和httpx
:SyncProxyTransport
用于设置代理,httpx
是一个 HTTP 客户端库,用于发送 HTTP 请求。os
:用于处理文件路径和环境变量等操作系统相关操作。rospy
和std_msgs.msg.String
:ROS(机器人操作系统)相关库,rospy
用于初始化 ROS 节点、发布消息等操作,String
是 ROS 中消息类型的一种。sounddevice
:用于录制音频。scipy.io.wavfile
中的write
:用于将录制的音频数据保存为 WAV 文件格式。speech_recognition
:用于语音识别,将录制的音频转换为文本。pynput
中的keyboard
:用于监听键盘按键事件,以控制录音的开始和停止。numpy
:用于数值计算和处理音频数据。 ---------
transport = SyncProxyTransport.from_url('
client = httpx.Client(transport=transport)
openai_client = openai.OpenAI(
api_key=
base_url="
http_client=client
)
相关的api之类的需要你们自己去配一下,我使用的是通义千问,相关的你们可以在官网找到。
- 首先设置了一个代理,这里使用了 SOCKS5 代理,地址为
127.x.x.x
。 - 创建了一个
httpx
客户端,并将代理传输设置给它。 - 初始化
openai
客户端,使用环境变量DASHSCOPE_API_KEY
作为 API 密钥,设置 API 的基础 URL 为百炼的兼容模式 URL,并使用之前创建的httpx
客户端来发送请求。
设计提示词:
prompt_template = """
你是一个非常专业的机械臂控制工程师,负责将自然语言指令转换为机械臂控制按键。并且你的输出结果只有纯粹的字母,没有任何多余的文字
按键映射如下:
- 'w': 机械臂向前移动
- 'a': 机械臂向右移动
- 'r': 机械臂向上移动
- 'q': 机械臂夹子向左转
- 'g': 机械臂俯仰增加
- 'z': 夹爪打开
- 's': 机械臂向后移动
- 'd': 机械臂向左移动
- 'f': 机械臂向下移动
- 'e': 机械臂夹子向右转
- 't': 机械臂俯仰减少
- 'c': 夹爪关闭
请根据以下自然语言指令输出对应的按键:
{input}
"""
定义了一个提示模板,用于向百炼模型提供指令,使其能够根据给定的自然语言指令,按照机械臂按键映射关系输出对应的按键字母。
调用百炼模型将自然语言转换为按键:
def get_key_from_model(question):
prompt = prompt_template.format(input=question)
try:
response = openai_client.chat.completions.create(
model="qwen-plus",
messages=[
{'role': 'system', 'content': 'You are a helpful assistant.'},
{'role': 'user', 'content': prompt}
]
)
key = response.choices[0].message.content.strip()
remove_texts = [
"根据指令“打开夹子”对应的按键映射是:",
"根据指令“闭合夹子”,对应的按键是:",
"根据按键映射,打开夹爪对应的按键是:",
"因此指令转换结果为:"
]
for text in remove_texts:
if key.startswith(text):
key = key.replace(text, "").strip()
if key.startswith("`") and key.endswith("`"):
key = key[1:-1].strip()
key = ''.join(filter(str.isalpha, key))
return key
except Exception as e:
print(f"百炼API调用失败:{str(e)}")
return None
- 函数
get_key_from_model
接受一个自然语言问题作为参数,将问题填充到提示模板中。 - 调用百炼模型(模型名称为
qwen-plus
),发送系统角色消息和用户角色消息(包含填充后的提示)。 - 从模型的响应中提取出按键信息,并对提取的按键信息进行处理,去除一些可能存在的前缀文本和多余的字符,只保留字母。
- 如果 API 调用失败,捕获异常并打印错误信息,返回
None
。 - 录制音频:
def record_audio():
print("开始录音... 按 'q' 键停止")
frames = []
recording = True
def on_press(key):
nonlocal recording
if key.char == 'q':
recording = False
listener = keyboard.Listener(on_press=on_press)
listener.start()
try:
with sd.InputStream(
samplerate=44100,
channels=1,
dtype=np.int16,
blocksize=1024
) as stream:
while recording:
data, _ = stream.read(1024)
frames.append(data)
except KeyboardInterrupt:
recording = False
listener.stop()
if frames:
data = np.concatenate(frames, axis=0)
os.makedirs(os.path.dirname("/home/zzq/voice_qwen/outputs/temp_audio.wav"), exist_ok=True)
write("/home/zzq/voice_qwen/outputs/temp_audio.wav", 44100, data)
print(f"录音保存为:/home/zzq/voice_qwen/outputs/temp_audio.wav")
return "/home/zzq/voice_qwen/outputs/temp_audio.wav"
else:
print("未录制到音频数据")
return None
- 函数
record_audio
用于录制音频。首先打印提示信息,初始化一个空列表frames
用于存储音频数据帧,设置recording
标志为True
。 - 定义一个内部函数
on_press
,当按下q
键时,将recording
标志设置为False
。 - 使用
keyboard.Listener
监听键盘按键事件,并启动监听器。 - 使用
sounddevice.InputStream
创建一个音频输入流,设置采样率、通道数、数据类型和块大小。这里面的通道采样率数据类型等,需要你们根据你们自己的电脑调整 - 在
recording
为True
的情况下,不断从音频流中读取数据帧并添加到frames
列表中。 - 如果捕获到
KeyboardInterrupt
异常,将recording
设置为False
。 - 停止监听器后,将所有音频数据帧连接成一个数组,并将音频数据保存为 WAV 文件,返回保存的音频文件路径。如果没有录制到音频数据,返回
None
。
语音识别:
def recognize_speech(audio_file):
recognizer = sr.Recognizer()
with sr.AudioFile(audio_file) as source:
audio_data = recognizer.record(source)
try:
text = recognizer.recognize_google(audio_data, language="zh-CN")
print("语音识别结果:", text)
return text
except sr.UnknownValueError:
print("语音识别失败:无法识别音频内容")
return None
except sr.RequestError as e:
print(f"语音识别失败:{e}")
return None
- 函数
recognize_speech
接受一个音频文件路径作为参数。 - 使用
speech_recognition.Recognizer
创建一个语音识别器对象。 - 使用
sr.AudioFile
打开音频文件,并通过识别器的record
方法将音频数据读取为AudioData
对象。 - 尝试使用
recognize_google
方法将音频数据转换为文本,设置语言为中文(zh-CN
)。如果识别成功,打印识别结果并返回文本;如果识别失败,捕获相应的异常并打印错误信息,返回None
- 主函数:
-
def main(): rospy.init_node('robot_key_publisher', anonymous=True) pub = rospy.Publisher('robot_key_command', String, queue_size=10) rate = rospy.Rate(1) # 1 Hz while not rospy.is_shutdown(): print("请开始语音输入,按 'q' 键结束录音(输入'exit'可退出程序)...") audio_file = record_audio() if not audio_file: continue user_input = recognize_speech(audio_file) if user_input: if user_input.lower() == 'exit': print("接收到退出指令,程序即将退出...") break print(f"\n你输入的指令:{user_input}") key = get_key_from_model(user_input) if key: pub.publish(key) print(f"已发布按键: {key}") else: print("未能从模型获取有效按键") else: print("未成功识别语音内容,请重试。") rate.sleep()
- 函数
main
是程序的入口点。首先初始化 ROS 节点,名称为robot_key_publisher
,设置anonymous=True
以确保节点名称唯一。 - 创建一个 ROS 发布者,发布到
robot_key_command
话题,消息类型为String
,队列大小为 10。 - 设置循环的频率为 1Hz。
- 在 ROS 节点未关闭的情况下,循环执行以下操作:
- 提示用户开始语音输入,按
q
键结束录音,并说明输入exit
可退出程序。 - 调用
record_audio
录制音频,若未成功录制,跳过本次循环。 - 调用
recognize_speech
对录制的音频进行语音识别,若识别成功:- 如果识别的文本为
exit
,打印退出信息并跳出循环。 - 否则,打印用户输入的指令,调用
get_key_from_model
将自然语言指令转换为按键。 - 如果获取到有效按键,通过 ROS 发布者发布按键消息,并打印已发布的按键信息;否则,打印未能获取有效按键的信息。
- 如果识别的文本为
- 如果语音识别未成功,打印提示信息。
- 按照设置的频率休眠。
- 提示用户开始语音输入,按
最后,如何启动在仓库中的readme中有
https://github.com/box2ai-robotics/lerobot-kinematics?tab=readme-ov-file ---这个是盒子桥大佬的github地址,先根据他的配置将环境搭建好,然后去我的仓库中将代码放进去,就ok
https://github.com/NANBEI132/lerobot_kinematics_LLM ---这个是放我的两个代码的地方