389 lines
13 KiB
Python
389 lines
13 KiB
Python
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)
|
||
#
|
||
#
|