openvla / snap.py
stellanwu's picture
Upload folder using huggingface_hub
1975a6f verified
Raw
History Blame Contribute Delete
7.44 kB
import cv2
import dlib
import numpy as np
import urx
import time
import math
from datetime import datetime
from math import cos, sin
# 配置参数
UR_ROBOT_IP = "192.168.10.99"
CAMERA_INDEX = 0 # 默认摄像头索引
FILTER_WINDOW = 5 # 移动平均窗口大小
DAMPING = 0.1 # 阻尼系数
SAFE_DISTANCE = 0.8 # 相机到人脸的安全距离(米)
Y_CONSTRAINT = 0.4 # y轴固定位置(米)
CONTROL_FREQ = 0.05 # 控制周期(秒)
# 3D面部特征点模型(世界坐标系)
MODEL_POINTS = np.array([
(0.0, 0.0, 0.0), # 鼻尖
(0.0, -330.0, -65.0), # 下巴
(-225.0, 170.0, -135.0), # 左眼角
(225.0, 170.0, -135.0), # 右眼角
(-150.0, -150.0, -125.0), # 左嘴角
(150.0, -150.0, -125.0) # 右嘴角
])
class HeadPoseEstimator:
def __init__(self):
# 初始化dlib人脸检测器和特征点预测器
self.detector = dlib.get_frontal_face_detector()
self.predictor = dlib.shape_predictor("shape_predictor_68_face_landmarks.dat")
self.camera = cv2.VideoCapture(CAMERA_INDEX)
# 相机内参(需要根据实际相机校准)
self.camera_matrix = np.array(
[[640, 0, 320],
[0, 640, 240],
[0, 0, 1]], dtype="double"
)
self.dist_coeffs = np.zeros((4, 1)) # 畸变系数
def get_head_pose(self):
"""获取头部姿态:返回(旋转角, 位置)"""
ret, frame = self.camera.read()
if not ret:
return None
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
faces = self.detector(gray)
if len(faces) == 0:
return None
# 取第一个检测到的人脸
shape = self.predictor(gray, faces[0])
image_points = self._shape_to_np(shape)
# 提取用于姿态估计的6个特征点
selected_points = [30, 8, 36, 45, 48, 54] # 对应MODEL_POINTS的索引
image_points = image_points[selected_points]
# 求解PnP问题
success, rotation_vector, translation_vector = cv2.solvePnP(
MODEL_POINTS, image_points, self.camera_matrix, self.dist_coeffs,
flags=cv2.SOLVEPNP_ITERATIVE
)
# 旋转向量转欧拉角(弧度)
rotation_matrix, _ = cv2.Rodrigues(rotation_vector)
euler_angles = self._rotation_matrix_to_euler_angles(rotation_matrix)
# 返回(旋转角, 位置):(yaw, pitch, roll, x, y, z)
return np.concatenate([euler_angles, translation_vector.flatten() / 1000.0]) # 毫米转米
def _shape_to_np(self, shape):
"""将dlib特征点转换为numpy数组"""
return np.array([(p.x, p.y) for p in shape.parts()], dtype="double")
def _rotation_matrix_to_euler_angles(self, R):
"""旋转矩阵转欧拉角(Z-Y-X顺序)"""
sy = math.sqrt(R[0,0] * R[0,0] + R[1,0] * R[1,0])
singular = sy < 1e-6
if not singular:
x = math.atan2(R[2,1], R[2,2])
y = math.atan2(-R[2,0], sy)
z = math.atan2(R[1,0], R[0,0])
else:
x = math.atan2(-R[1,2], R[1,1])
y = math.atan2(-R[2,0], sy)
z = 0
return np.array([z, y, x]) # yaw, pitch, roll
def release(self):
self.camera.release()
class URHeadTracker:
def __init__(self):
self.robot = urx.Robot(UR_ROBOT_IP)
self.estimator = HeadPoseEstimator()
self.prev_deltas = [] # 用于移动平均滤波
self.current_pose = self.robot.getl()
print(f"机械臂初始位姿: {self.current_pose}")
def compute_face_direction(self, head_rot):
"""计算面部方向向量"""
yaw, pitch, roll = head_rot
# 方向向量计算 (单位向量)
dir_x = sin(yaw) * cos(pitch)
dir_z = cos(yaw) * cos(pitch)
return np.array([dir_x, 0, dir_z]) # y方向约束为0
def damping_least_squares(self, delta, current_joints):
"""阻尼最小二乘法优化关节运动"""
# 简化实现:基于关节当前位置添加阻尼
joint_weights = np.array([1.0, 1.0, 1.0, 0.5, 0.5, 0.5]) # 末端关节权重降低
damping = DAMPING * np.eye(6)
return delta / (np.linalg.norm(joint_weights) + damping.diagonal())
def moving_average_filter(self, delta):
"""移动平均滤波"""
self.prev_deltas.append(delta)
if len(self.prev_deltas) > FILTER_WINDOW:
self.prev_deltas.pop(0)
return np.mean(self.prev_deltas, axis=0)
def compute_target_pose(self, head_pose):
"""计算相机目标位姿"""
head_rot = head_pose[:3] # yaw, pitch, roll
head_pos = head_pose[3:] # x, y, z (米)
# 计算目标位置
face_dir = self.compute_face_direction(head_rot)
target_x = head_pos[0] - face_dir[0] * SAFE_DISTANCE
target_z = head_pos[2] - face_dir[2] * SAFE_DISTANCE
# 计算目标姿态(相机对准人脸)
target_rx = -head_rot[1] # 俯仰角反向
target_ry = 0 # 消除侧倾
target_rz = -head_rot[0] # 偏航角反向
return np.array([target_x, Y_CONSTRAINT, target_z, target_rx, target_ry, target_rz])
def run(self):
try:
while True:
# 获取头部姿态
head_pose = self.estimator.get_head_pose()
if head_pose is None:
print("未检测到人脸,等待...")
time.sleep(CONTROL_FREQ)
continue
# 计算目标位姿
target_pose = self.compute_target_pose(head_pose)
# 计算相对位移
current = np.array(self.current_pose)
delta = target_pose - current
# 应用阻尼最小二乘法
current_joints = self.robot.getj()
delta = self.damping_least_squares(delta, current_joints)
# 移动平均滤波
delta_smoothed = self.moving_average_filter(delta)
# 约束y轴不移动
delta_smoothed[1] = 0
# 执行运动
new_pose = current + delta_smoothed
self.robot.movel(new_pose.tolist(), acc=0.05, vel=0.05, relative=False)
self.current_pose = self.robot.getl()
# 打印状态
print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}]")
print(f"目标位姿: {target_pose}")
print(f"当前位姿: {self.current_pose}\n")
time.sleep(CONTROL_FREQ)
except KeyboardInterrupt:
print("用户终止程序")
finally:
self.robot.close()
self.estimator.release()
cv2.destroyAllWindows()
if __name__ == "__main__":
# 提示:需要先下载dlib特征点模型
# 下载地址:http://dlib.net/files/shape_predictor_68_face_landmarks.dat.bz2
tracker = URHeadTracker()
tracker.run()