558 lines
20 KiB
Python
558 lines
20 KiB
Python
#!/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()
|