cmvr-biohead/grpc_cli.py
2025-08-21 13:49:41 +08:00

389 lines
13 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import sys
sys.path.insert(0, "./generated")
import cv2
import yaml
import numpy as np
import mediapipe as mp
import pyrealsense2 as rs
from collections import deque
import grpc
import threading
import queue
import datetime
from biohead.algo import *
from biohead.utils import calc_feature
from biohead.utils import norm
from google.protobuf import timestamp_pb2
from generated.cmvr.api import common_pb2
from generated.cmvr.api import biohead_service_pb2
from generated.cmvr.api import biohead_command_pb2
from generated.cmvr.api import biohead_service_pb2_grpc
def build_command_header(device_id):
"""Helper to build CommandHeader.Request"""
now = datetime.datetime.utcnow()
timestamp = timestamp_pb2.Timestamp()
timestamp.FromDatetime(now)
return common_pb2.CommandHeader.Request(
device_id=device_id,
timestamp=timestamp
)
def build_facial_expression(result):
"""Map your local HeadJoints result to proto FacialExpression"""
expr = biohead_command_pb2.FacialExpression()
# Eyebrow
expr.eyebrow.left_outside_y = result.left_eyebrow_outside_y
expr.eyebrow.left_inside_y = result.left_eyebrow_inside_y
expr.eyebrow.right_outside_y = result.right_eyebrow_outside_y
expr.eyebrow.right_inside_y = result.right_eyebrow_inside_y
# Eyelid
expr.eyelid.left_upper_y = result.left_eye_upper_lid_y
expr.eyelid.left_lower_y = result.left_eye_lower_lid_y
expr.eyelid.right_upper_y = result.right_eye_upper_lid_y
expr.eyelid.right_lower_y = result.right_eye_lower_lid_y
# Eyeball
expr.eyeball.left_x = result.left_eye_ball_x
expr.eyeball.left_y = result.left_eye_ball_y
expr.eyeball.right_x = result.right_eye_ball_x
expr.eyeball.right_y = result.right_eye_ball_y
# Mouth
expr.mouth.upper_lip_y = result.upper_lip_y
expr.mouth.upper_lip_z = result.upper_lip_z
expr.mouth.lower_lip_y = result.lower_lip_y
expr.mouth.lower_lip_z = result.lower_lip_z
expr.mouth.left_lip.upper_x = result.upper_left_lip_x
expr.mouth.left_lip.upper_y = result.upper_left_lip_y
expr.mouth.left_lip.corner_x = result.left_corner_lip_x
expr.mouth.left_lip.corner_y = result.left_corner_lip_y
expr.mouth.left_lip.lower_x = result.lower_left_lip_x
expr.mouth.left_lip.lower_y = result.lower_left_lip_y
expr.mouth.right_lip.upper_x = result.upper_right_lip_x
expr.mouth.right_lip.upper_y = result.upper_right_lip_y
expr.mouth.right_lip.corner_x = result.right_corner_lip_x
expr.mouth.right_lip.corner_y = result.right_corner_lip_y
expr.mouth.right_lip.lower_x = result.lower_right_lip_x
expr.mouth.right_lip.lower_y = result.lower_right_lip_y
# Jaw
expr.jaw.x = result.jaw_x
expr.jaw.y = result.jaw_y
return expr
def main(calib_file):
# ====== Load config ======
with open(r"./config/config.yaml", "r") as f:
config = yaml.load(f, Loader=yaml.FullLoader)
with open(calib_file, "r") as f:
calib = yaml.load(f, Loader=yaml.FullLoader)
# ====== Init RealSense ======
w, h, fps = config['Camera']['image_width'], config['Camera']['image_height'], config['Camera']['fps']
pipe = rs.pipeline()
cfg = rs.config()
cfg.enable_stream(rs.stream.color, w, h, rs.format.bgr8, fps)
pipe.start(cfg)
align = rs.align(rs.stream.color)
# ====== Init MediaPipe ======
mp_mesh = mp.solutions.face_mesh
mesh = mp_mesh.FaceMesh(
max_num_faces=1,
refine_landmarks=True,
min_detection_confidence=config['MediaPipe']['min_detection_confidence'],
min_tracking_confidence=config['MediaPipe']['min_tracking_confidence']
)
ray_origins = deque(maxlen=config['Smooth'])
ray_directions = deque(maxlen=config['Smooth'])
result = HeadJoints()
# ====== Init gRPC ======
device_id = "your_device_id"
frame_queue = queue.Queue(maxsize=10)
result_queue = queue.Queue()
channel = grpc.insecure_channel('localhost:50051')
stub = biohead_service_pb2_grpc.BioHeadServiceStub(channel)
def request_stream():
while True:
item = frame_queue.get()
if item is None:
break
header, expr = item
yield biohead_service_pb2.StreamFacialExpression.Request(
header=header,
expr=expr,
eof=False
)
def response_reader(responses):
for response in responses:
if response.HasField("expr_diff"):
result_queue.put(response.expr_diff)
responses = stub.StreamExpression(request_stream())
reader_thread = threading.Thread(target=response_reader, args=(responses,), daemon=True)
reader_thread.start()
print("[INFO] Started RealSense + gRPC streaming client.")
while True:
frames = align.process(pipe.wait_for_frames())
color_f = frames.get_color_frame()
if not color_f:
continue
color = np.asanyarray(color_f.get_data())
h, w, _ = color.shape
rgb = cv2.cvtColor(color, cv2.COLOR_BGR2RGB)
res = mesh.process(rgb)
if not res.multi_face_landmarks:
cv2.imshow("RGB", color)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
continue
uv = calc_feature(color, res, ray_origins, ray_directions)
result = calc_eyebrow(uv, result)
result = calc_eyelid(uv, result)
result = calc_eyeball(uv, result)
result = calc_mouth(uv, result)
result = calc_jaw(uv, result)
result = norm(result, calib)
# ====== Build proto request ======
header = build_command_header(device_id)
expr = build_facial_expression(result)
if not frame_queue.full():
frame_queue.put((header, expr))
# ====== Draw server feedback if any ======
# if not result_queue.empty():
# expr_diff = result_queue.get()
# y0 = 30
# dy = 30
#
# # Draw expr_diff fields on screen
# # For simplicity, let's just show some sample values
# if expr_diff.HasField("eyebrow"):
# txt = f"Eyebrow L-out:{expr_diff.eyebrow.left_outside_y:.2f}"
# cv2.putText(color, txt, (30, y0), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
# y0 += dy
#
# if expr_diff.HasField("eyelid"):
# txt = f"Eyelid L-upper:{expr_diff.eyelid.left_upper_y:.2f}"
# cv2.putText(color, txt, (30, y0), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
# y0 += dy
#
# if expr_diff.HasField("jaw"):
# txt = f"Jaw X:{expr_diff.jaw.x:.2f} Y:{expr_diff.jaw.y:.2f}"
# cv2.putText(color, txt, (30, y0), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
# y0 += dy
cv2.imshow("RGB", color)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
frame_queue.put(None)
pipe.stop()
cv2.destroyAllWindows()
print("[INFO] Client shut down.")
if __name__ == '__main__':
main("./config/calibrated_interval.yaml")
# import sys
# sys.path.insert(0, "./generated")
# import grpc
# import time
# import random
# import sys
# import traceback
# import logging
# from google.protobuf import timestamp_pb2
# from generated.cmvr.api import common_pb2
# from generated.cmvr.api import biohead_command_pb2
# from generated.cmvr.api import biohead_service_pb2
# from generated.cmvr.api import biohead_service_pb2_grpc
#
# # 打印出biohead_command_pb2的生成类结构
# print(dir(biohead_command_pb2))
#
# # 设置详细日志
# logging.basicConfig(level=logging.DEBUG)
# logger = logging.getLogger('grpc_test')
# logger.setLevel(logging.DEBUG)
#
# # 添加控制台处理器
# console_handler = logging.StreamHandler()
# console_handler.setLevel(logging.DEBUG)
# formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
# console_handler.setFormatter(formatter)
# logger.addHandler(console_handler)
#
# # 检查生成的代码结构
# logger.debug("在biohead_service_pb2中生成的类")
# for attr in dir(biohead_service_pb2):
# if "FacialExpression" in attr or "Stream" in attr:
# logger.debug(f" - {attr}")
#
# def create_facial_expression():
# """创建随机的面部表情数据"""
# expr = biohead_command_pb2.FacialExpression()
#
# # 眉毛
# expr.eyebrow.left_outside_y = random.uniform(0.0, 1.0)
# expr.eyebrow.left_inside_y = random.uniform(0.0, 1.0)
# expr.eyebrow.right_outside_y = random.uniform(0.0, 1.0)
# expr.eyebrow.right_inside_y = random.uniform(0.0, 1.0)
#
# # 眼睑
# expr.eyelid.left_upper_y = random.uniform(0.0, 1.0)
# expr.eyelid.left_lower_y = random.uniform(0.0, 1.0)
# expr.eyelid.right_upper_y = random.uniform(0.0, 1.0)
# expr.eyelid.right_lower_y = random.uniform(0.0, 1.0)
#
# # 眼球
# expr.eyeball.left_y = random.uniform(0, 1.0) # 动态值
# expr.eyeball.right_y = random.uniform(0, 1.0) # 动态值
#
#
# # 嘴巴
# expr.mouth.upper_lip_y = random.uniform(0.0, 1.0)
# expr.mouth.lower_lip_y = random.uniform(0.0, 1.0)
#
#
# # 左唇角
# expr.mouth.left_lip.upper_y = random.uniform(0.0, 1.0)
# expr.mouth.left_lip.corner_y = random.uniform(0.0, 1.0)
#
# # 右唇角
# expr.mouth.right_lip.upper_y = random.uniform(0.0, 1.0)
# expr.mouth.right_lip.corner_y = random.uniform(0.0, 1.0)
#
# # 下巴
# expr.jaw.x = random.uniform(0, 1.0)
# expr.jaw.y = random.uniform(0, 1.0)
#
# return expr
#
# def create_request(device_id):
# """创建流式请求"""
# # 使用正确的请求类名
# # 根据proto文件请求类名应该是 StreamFacialExpressionRequest
# request = biohead_command_pb2.StreamFacialExpression.Request()
#
# # 设置请求头
# request.header.device_id = device_id
# now = time.time()
# request.header.timestamp.seconds = int(now)
# request.header.timestamp.nanos = int((now - int(now)) * 1e9)
#
# # 设置表情数据
# expr = create_facial_expression()
# request.expr.CopyFrom(expr)
# request.eof = False
#
# # 记录请求详情
# logger.debug(f"为设备 {device_id} 创建请求")
# logger.debug(f"表情字段: {expr.ListFields()}")
#
# return request
#
# def stream_expression_test(device_id="bio_head", num_requests=5, interval=1.0):
# """测试流式表情接口"""
# logger.info(f"开始测试流式表情接口,设备: {device_id}")
# logger.info(f"将发送 {num_requests} 个请求,频率为 {1/interval:.1f} Hz")
#
# # 创建gRPC通道
# channel = grpc.insecure_channel('localhost:50051')
# stub = biohead_service_pb2_grpc.BioHeadServiceStub(channel)
#
# # 创建生成器函数
# def request_generator():
# try:
# for i in range(num_requests):
# request = create_request(device_id)
# logger.info(f"发送请求 #{i+1}")
# logger.debug(f"请求内容: {request}")
# yield request
# time.sleep(interval)
#
# # 发送结束标志
# end_request = biohead_command_pb2.SetFacialExpression.Feedback()
# end_request.header.device_id = device_id
# end_request.eof = True
# logger.info("发送EOF请求")
# yield end_request
# except Exception as e:
# logger.error(f"请求生成器出错: {str(e)}")
# logger.error(traceback.format_exc())
#
# # 调用流式方法
# try:
# responses = stub.StreamExpression(request_generator())
#
# # 处理响应
# response_count = 0
# for response in responses:
# response_count += 1
# header = response.header
# logger.info(f"收到响应 #{response_count}")
# logger.info(f" 成功: {header.success}")
# logger.info(f" 时间戳: {header.timestamp.seconds}.{header.timestamp.nanos:09d}")
#
# if not header.success:
# logger.error(f" 错误: {header.error_message}")
#
# if response.HasField("expr_diff"):
# diff = response.expr_diff
# logger.info(" 收到表情差异")
# # 记录差异详情
# logger.debug(f" 眉毛差异: L-out: {diff.eyebrow.left_outside_y:.4f}")
#
# logger.info(f"总共收到 {response_count} 个响应")
#
# except grpc.RpcError as e:
# logger.error(f"gRPC错误: {e.code()}: {e.details()}")
# logger.error(f"调试错误信息: {e.debug_error_string()}")
# except Exception as e:
# logger.error(f"意外错误: {str(e)}")
# logger.error(traceback.format_exc())
#
# logger.info("流式表情测试完成")
#
# if __name__ == '__main__':
# # 测试参数
# DEVICE_ID = "bio_head"
# NUM_REQUESTS = 5
# INTERVAL = 1.0
#
# try:
# stream_expression_test(device_id=DEVICE_ID,
# num_requests=NUM_REQUESTS,
# interval=INTERVAL)
# except Exception as e:
# logger.error(f"测试失败: {str(e)}")
# logger.error(traceback.format_exc())
# sys.exit(1)
#
#