| 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
|
| CONTROL_FREQ = 0.05
|
|
|
|
|
| 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):
|
|
|
| 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)
|
|
|
|
|
| selected_points = [30, 8, 36, 45, 48, 54]
|
| image_points = image_points[selected_points]
|
|
|
|
|
| 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)
|
|
|
|
|
| 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])
|
|
|
| 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])
|
|
|
| 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]
|
| head_pos = head_pose[3:]
|
|
|
|
|
| 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)
|
|
|
|
|
| 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__":
|
|
|
|
|
| tracker = URHeadTracker()
|
| tracker.run()
|
|
|