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)
|
|||
|
|
#
|
|||
|
|
#
|