cmvr-es-cli/biohead.py

558 lines
20 KiB
Python
Raw Permalink Normal View History

2025-10-21 09:33:47 +08:00
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
biohead.py 基于gRPC的仿生头控制脚本
功能
1. 控制单个表情参数
2. 控制多个表情参数
3. 退出
4. 自动增减模式从0到1再到0循环变化
5. 设置自动模式参数数量
6. 眨眼模式执行预设的眨眼动作
7. 打哈欠模式模拟打哈欠动作
8. 说话模式模拟嘴巴开合动作
"""
import sys
import time
import random
import os
import grpc
from typing import Generator
# 添加项目根目录到Python路径
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
# 导入gRPC相关模块
from generated.cmvr.api import biohead_command_pb2
from cmvr import CMVRGrpcClient, CMVRErrorCode, FacialExpressionState
def angle_to_normalized(angle):
"""将角度值(0-180)转换为归一化值(0-1)"""
return max(0.0, min(1.0, angle / 180.0))
def normalized_to_angle(normalized):
"""将归一化值(0-1)转换为角度值(0-180)"""
return int(normalized * 180)
def init_biohead_client(server_addr="192.168.1.222:50051"):
"""初始化仿生头gRPC客户端"""
client = CMVRGrpcClient(server_addr)
if not client.is_connected():
print("连接服务器失败")
return None
biohead = client.get_biohead("bio_head")
if not biohead:
print("获取仿生头客户端失败")
client.close()
return None
return client, biohead
def set_single_expression(biohead):
"""设置单个表情参数"""
print("\n--- 设置单个表情参数 ---")
# 表情参数映射表
param_map = {
1: "left_eyebrow_outside_y",
2: "left_eyebrow_inside_y",
3: "right_eyebrow_outside_y",
4: "right_eyebrow_inside_y",
5: "left_eye_upper_lid_y",
6: "left_eye_lower_lid_y",
7: "right_eye_upper_lid_y",
8: "right_eye_lower_lid_y",
9: "left_eye_ball_x",
10: "left_eye_ball_y",
11: "right_eye_ball_x",
12: "right_eye_ball_y",
13: "upper_lip_y",
14: "lower_lip_y",
15: "jaw_x",
16: "jaw_y"
}
print("可用表情参数:")
for idx, name in param_map.items():
print(f"{idx}: {name}")
try:
param_id = int(input("选择参数ID: "))
if param_id not in param_map:
print("无效的参数ID")
return
angle = int(input("输入角度(0-180): "))
if angle < 0 or angle > 180:
print("角度必须在0-180之间")
return
# 转换为归一化值
value = angle_to_normalized(angle)
# 创建表情对象
expression = FacialExpressionState()
# 设置对应的参数
setattr(expression, param_map[param_id], value)
# 发送表情设置
result = biohead.set_expression(expression)
if result == CMVRErrorCode.CMVR_SUCCESS:
print(f"设置 {param_map[param_id]} 成功,角度: {angle}°,归一化值: {value:.2f}")
else:
print(f"设置失败,错误码: {result}")
except ValueError:
print("输入无效,请输入数字")
def set_multiple_expressions(biohead):
"""设置多个表情参数"""
print("\n--- 设置多个表情参数 ---")
# 表情参数映射表
param_map = {
1: "left_eyebrow_outside_y",
2: "left_eyebrow_inside_y",
3: "right_eyebrow_outside_y",
4: "right_eyebrow_inside_y",
5: "left_eye_upper_lid_y",
6: "left_eye_lower_lid_y",
7: "right_eye_upper_lid_y",
8: "right_eye_lower_lid_y",
9: "left_eye_ball_x",
10: "left_eye_ball_y",
11: "right_eye_ball_x",
12: "right_eye_ball_y",
13: "upper_lip_y",
14: "lower_lip_y",
15: "jaw_x",
16: "jaw_y"
}
print("可用表情参数:")
for idx, name in param_map.items():
print(f"{idx}: {name}")
try:
count = int(input("输入要设置的参数数量: "))
if count <= 0:
print("数量必须大于0")
return
# 创建表情对象
expression = FacialExpressionState()
for i in range(count):
print(f"\n{i+1}个参数:")
param_id = int(input("选择参数ID: "))
if param_id not in param_map:
print("无效的参数ID跳过该参数")
continue
angle = int(input("输入角度(0-180): "))
if angle < 0 or angle > 180:
print("角度必须在0-180之间跳过该参数")
continue
# 转换为归一化值
value = angle_to_normalized(angle)
# 设置对应的参数
setattr(expression, param_map[param_id], value)
print(f"已添加: {param_map[param_id]} = {value:.2f} (对应角度: {angle}°)")
# 发送表情设置
result = biohead.set_expression(expression)
if result == CMVRErrorCode.CMVR_SUCCESS:
print("设置多个表情参数成功")
else:
print(f"设置失败,错误码: {result}")
except ValueError:
print("输入无效,请输入数字")
def auto_mode(biohead, params):
"""自动增减模式0→1→0循环变化"""
print("\n--- 自动增减模式 ---")
try:
# 表情参数映射表
param_map = {
1: "left_eyebrow_outside_y",
2: "left_eyebrow_inside_y",
3: "right_eyebrow_outside_y",
4: "right_eyebrow_inside_y",
5: "left_eye_upper_lid_y",
6: "left_eye_lower_lid_y",
7: "right_eye_upper_lid_y",
8: "right_eye_lower_lid_y",
9: "left_eye_ball_x",
10: "left_eye_ball_y",
11: "right_eye_ball_x",
12: "right_eye_ball_y",
13: "upper_lip_y",
14: "lower_lip_y",
15: "jaw_x",
16: "jaw_y"
}
# 过滤有效的参数ID
valid_params = [p for p in params if p in param_map]
if not valid_params:
print("没有有效的参数,返回菜单")
return
print(f"将自动控制以下参数: {[param_map[p] for p in valid_params]}")
frequency = 50.0 # 50Hz
interval = 1.0 / frequency
value = 0.0
direction = 0.02 # 步长
# 启动流式控制
result = biohead.start_stream()
if result != CMVRErrorCode.CMVR_SUCCESS:
print(f"开始流式会话失败,错误码: {result}")
return
print("启动自动模式按Ctrl+C停止")
while True:
# 创建protobuf消息
proto_expr = biohead_command_pb2.FacialExpression()
# 设置所有参数
for param_id in valid_params:
param_name = param_map[param_id]
# 根据参数类型设置到不同的字段
if param_name in ["left_eyebrow_outside_y", "left_eyebrow_inside_y",
"right_eyebrow_outside_y", "right_eyebrow_inside_y"]:
setattr(proto_expr.eyebrow, param_name, value)
elif param_name in ["left_eye_upper_lid_y", "left_eye_lower_lid_y",
"right_eye_upper_lid_y", "right_eye_lower_lid_y"]:
setattr(proto_expr.eyelid, param_name, value)
elif param_name in ["left_eye_ball_x", "left_eye_ball_y",
"right_eye_ball_x", "right_eye_ball_y"]:
setattr(proto_expr.eye_ball, param_name, value)
elif param_name in ["upper_lip_y", "lower_lip_y"]:
setattr(proto_expr.mouth, param_name, value)
elif param_name in ["jaw_x", "jaw_y"]:
setattr(proto_expr.jaw, param_name, value)
# 发送流式表情
result = biohead.stream_expression(proto_expr)
if result != CMVRErrorCode.CMVR_SUCCESS:
print(f"流式控制失败,错误码: {result}")
break
# 更新值
value += direction
if value >= 1.0:
value = 1.0
direction = -direction
elif value <= 0.0:
value = 0.0
direction = -direction
time.sleep(interval)
except KeyboardInterrupt:
print("\n自动模式已停止")
finally:
# 结束流式会话
biohead.end_stream()
def set_auto_params():
"""设置自动模式的参数列表"""
print("\n--- 设置自动模式参数 ---")
# 表情参数映射表
param_map = {
1: "left_eyebrow_outside_y",
2: "left_eyebrow_inside_y",
3: "right_eyebrow_outside_y",
4: "right_eyebrow_inside_y",
5: "left_eye_upper_lid_y",
6: "left_eye_lower_lid_y",
7: "right_eye_upper_lid_y",
8: "right_eye_lower_lid_y",
9: "left_eye_ball_x",
10: "left_eye_ball_y",
11: "right_eye_ball_x",
12: "right_eye_ball_y",
13: "upper_lip_y",
14: "lower_lip_y",
15: "jaw_x",
16: "jaw_y"
}
print("可用表情参数:")
for idx, name in param_map.items():
print(f"{idx}: {name}")
try:
count = int(input("输入要自动控制的参数数量: "))
if count <= 0:
print("数量必须大于0")
return None
params = []
for i in range(count):
param_id = int(input(f"{i+1}个参数ID: "))
if param_id not in param_map:
print("无效的参数ID跳过")
continue
params.append(param_id)
if not params:
print("未设置有效的参数")
return None
print(f"已设置自动模式参数: {[param_map[p] for p in params]}")
return params
except ValueError:
print("输入无效,请输入数字")
return None
def blink_mode(biohead):
"""眨眼模式:执行预设的眨眼动作"""
print("\n--- 眨眼模式 ---")
try:
blink_count = int(input("输入眨眼次数(默认10次): ") or "10")
interval = float(input("输入间隔时间(秒默认0.1): ") or "0.1")
# 启动流式控制
result = biohead.start_stream()
if result != CMVRErrorCode.CMVR_SUCCESS:
print(f"开始流式会话失败,错误码: {result}")
return
print(f"启动眨眼模式,共{blink_count}次,间隔{interval}")
for i in range(blink_count):
print(f"{i+1}次眨眼...")
# 闭眼动作
proto_expr = biohead_command_pb2.FacialExpression()
proto_expr.eyelid.left_upper_lid_y = angle_to_normalized(90)
proto_expr.eyelid.left_lower_lid_y = angle_to_normalized(90)
proto_expr.eyelid.right_upper_lid_y = angle_to_normalized(90)
proto_expr.eyelid.right_lower_lid_y = angle_to_normalized(90)
result = biohead.stream_expression(proto_expr)
if result != CMVRErrorCode.CMVR_SUCCESS:
print(f"闭眼动作失败,错误码: {result}")
break
time.sleep(interval)
# 睁眼动作
proto_expr = biohead_command_pb2.FacialExpression()
proto_expr.eyelid.left_upper_lid_y = angle_to_normalized(50)
proto_expr.eyelid.left_lower_lid_y = angle_to_normalized(130)
proto_expr.eyelid.right_upper_lid_y = angle_to_normalized(130)
proto_expr.eyelid.right_lower_lid_y = angle_to_normalized(60)
result = biohead.stream_expression(proto_expr)
if result != CMVRErrorCode.CMVR_SUCCESS:
print(f"睁眼动作失败,错误码: {result}")
break
time.sleep(interval)
print("眨眼模式结束")
except KeyboardInterrupt:
print("\n眨眼模式已停止")
except ValueError:
print("输入无效,请输入数字")
finally:
# 结束流式会话
biohead.end_stream()
def yawn_mode(biohead):
"""打哈欠模式:模拟打哈欠动作"""
print("\n--- 打哈欠模式 ---")
try:
yawn_count = int(input("输入打哈欠次数(默认5次): ") or "5")
interval = float(input("输入间隔时间(秒默认1): ") or "1")
# 启动流式控制
result = biohead.start_stream()
if result != CMVRErrorCode.CMVR_SUCCESS:
print(f"开始流式会话失败,错误码: {result}")
return
print(f"启动打哈欠模式,共{yawn_count}次,间隔{interval}")
for i in range(yawn_count):
print(f"{i+1}次打哈欠...")
# 打哈欠动作
proto_expr = biohead_command_pb2.FacialExpression()
# 眉毛
proto_expr.eyebrow.left_eyebrow_outside_y = angle_to_normalized(70)
proto_expr.eyebrow.right_eyebrow_outside_y = angle_to_normalized(110)
# 眼睑
proto_expr.eyelid.left_upper_lid_y = angle_to_normalized(60)
proto_expr.eyelid.left_lower_lid_y = angle_to_normalized(130)
proto_expr.eyelid.right_upper_lid_y = angle_to_normalized(120)
proto_expr.eyelid.right_lower_lid_y = angle_to_normalized(80)
# 嘴巴和下巴
proto_expr.mouth.upper_lip_y = angle_to_normalized(120)
proto_expr.mouth.lower_lip_y = angle_to_normalized(45)
proto_expr.jaw.x = angle_to_normalized(60)
proto_expr.jaw.y = angle_to_normalized(120)
result = biohead.stream_expression(proto_expr)
if result != CMVRErrorCode.CMVR_SUCCESS:
print(f"打哈欠动作失败,错误码: {result}")
break
time.sleep(interval)
# 恢复动作
proto_expr = biohead_command_pb2.FacialExpression()
# 恢复到正常状态
proto_expr.eyebrow.left_eyebrow_outside_y = angle_to_normalized(90)
proto_expr.eyebrow.right_eyebrow_outside_y = angle_to_normalized(90)
proto_expr.eyelid.left_upper_lid_y = angle_to_normalized(90)
proto_expr.eyelid.left_lower_lid_y = angle_to_normalized(90)
proto_expr.eyelid.right_upper_lid_y = angle_to_normalized(90)
proto_expr.eyelid.right_lower_lid_y = angle_to_normalized(90)
proto_expr.mouth.upper_lip_y = angle_to_normalized(90)
proto_expr.mouth.lower_lip_y = angle_to_normalized(90)
proto_expr.jaw.x = angle_to_normalized(90)
proto_expr.jaw.y = angle_to_normalized(90)
result = biohead.stream_expression(proto_expr)
if result != CMVRErrorCode.CMVR_SUCCESS:
print(f"恢复动作失败,错误码: {result}")
break
time.sleep(interval)
print("打哈欠模式结束")
except KeyboardInterrupt:
print("\n打哈欠模式已停止")
except ValueError:
print("输入无效,请输入数字")
finally:
# 结束流式会话
biohead.end_stream()
def talk_mode(biohead):
"""说话模式:模拟嘴巴开合动作"""
print("\n--- 说话模式 ---")
try:
talk_count = int(input("输入说话循环次数(默认50次): ") or "50")
# 启动流式控制
result = biohead.start_stream()
if result != CMVRErrorCode.CMVR_SUCCESS:
print(f"开始流式会话失败,错误码: {result}")
return
print(f"启动说话模式,共{talk_count}")
for i in range(talk_count):
print(f"{i+1}次说话...")
# 开口动作
proto_expr = biohead_command_pb2.FacialExpression()
proto_expr.mouth.upper_lip_y = angle_to_normalized(120)
proto_expr.mouth.lower_lip_y = angle_to_normalized(45)
proto_expr.jaw.x = angle_to_normalized(80)
proto_expr.jaw.y = angle_to_normalized(110)
result = biohead.stream_expression(proto_expr)
if result != CMVRErrorCode.CMVR_SUCCESS:
print(f"开口动作失败,错误码: {result}")
break
# 随机间隔
interval = random.uniform(0, 0.3)
time.sleep(interval)
# 闭口动作
proto_expr = biohead_command_pb2.FacialExpression()
proto_expr.mouth.upper_lip_y = angle_to_normalized(120)
proto_expr.mouth.lower_lip_y = angle_to_normalized(45)
proto_expr.jaw.x = angle_to_normalized(90)
proto_expr.jaw.y = angle_to_normalized(90)
result = biohead.stream_expression(proto_expr)
if result != CMVRErrorCode.CMVR_SUCCESS:
print(f"闭口动作失败,错误码: {result}")
break
# 随机间隔
interval = random.uniform(0, 0.3)
time.sleep(interval)
print("说话模式结束")
except KeyboardInterrupt:
print("\n说话模式已停止")
except ValueError:
print("输入无效,请输入数字")
finally:
# 结束流式会话
biohead.end_stream()
def main():
# 初始化gRPC客户端
server_addr = "192.168.1.222:50051"
if len(sys.argv) > 1:
server_addr = sys.argv[1]
print(f"连接到仿生头服务器: {server_addr}")
client, biohead = init_biohead_client(server_addr)
if not client or not biohead:
sys.exit(1)
# 默认自动模式参数
auto_params = [5, 6, 7, 8] # 默认控制眼睑参数
try:
while True:
print("\n=== 仿生头控制菜单 ===")
print("1. 控制单个表情参数")
print("2. 控制多个表情参数")
print("3. 退出")
print("4. 自动增减模式(50Hz)")
print("5. 设置自动模式参数")
print("6. 眨眼模式")
print("7. 打哈欠模式")
print("8. 说话模式")
opt = input("选择操作: ").strip()
if opt == '1':
set_single_expression(biohead)
elif opt == '2':
set_multiple_expressions(biohead)
elif opt == '3':
print("退出程序")
break
elif opt == '4':
auto_mode(biohead, auto_params)
elif opt == '5':
new_params = set_auto_params()
if new_params:
auto_params = new_params
elif opt == '6':
blink_mode(biohead)
elif opt == '7':
yawn_mode(biohead)
elif opt == '8':
talk_mode(biohead)
else:
print("无效选项,请重试")
except KeyboardInterrupt:
print("\n用户中断,退出程序")
finally:
client.close()
print("已关闭连接")
if __name__ == '__main__':
main()