解决手识别偏移的问题,增加手跟踪(未完成)
This commit is contained in:
parent
f3104482c1
commit
98797842f4
@ -8,8 +8,8 @@ import numpy as np
|
|||||||
from camera_info_manager import CameraInfoManager
|
from camera_info_manager import CameraInfoManager
|
||||||
from sensor_msgs.msg import CameraInfo
|
from sensor_msgs.msg import CameraInfo
|
||||||
|
|
||||||
CAMERA_WIDTH = int(1280)
|
CAMERA_WIDTH = 1280
|
||||||
CAMERA_HEIGHT = int(480)
|
CAMERA_HEIGHT = 480
|
||||||
|
|
||||||
FRAME_WIDTH = int(CAMERA_WIDTH / 2)
|
FRAME_WIDTH = int(CAMERA_WIDTH / 2)
|
||||||
FRAME_HEIGHT = int(CAMERA_HEIGHT / 2)
|
FRAME_HEIGHT = int(CAMERA_HEIGHT / 2)
|
||||||
|
|||||||
156
src/openarm_camera/openarm_camera/hand_to_moveit_planning.py
Normal file
156
src/openarm_camera/openarm_camera/hand_to_moveit_planning.py
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
import rclpy
|
||||||
|
from rclpy.node import Node
|
||||||
|
from rclpy.action import ActionClient
|
||||||
|
import numpy as np
|
||||||
|
import time
|
||||||
|
|
||||||
|
from moveit_msgs.action import MoveGroup
|
||||||
|
from moveit_msgs.msg import Constraints, PositionConstraint
|
||||||
|
from geometry_msgs.msg import PointStamped, PoseStamped
|
||||||
|
from shape_msgs.msg import SolidPrimitive
|
||||||
|
|
||||||
|
import tf2_ros
|
||||||
|
import tf2_geometry_msgs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class HandFollowMoveIt(Node):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__('hand_follow_moveit')
|
||||||
|
|
||||||
|
self.client = ActionClient(self, MoveGroup, '/move_action')
|
||||||
|
|
||||||
|
self.last_sent = time.time()
|
||||||
|
self.send_period = 0.3 # 约 3 Hz
|
||||||
|
|
||||||
|
self.p_prev = None
|
||||||
|
|
||||||
|
self.plan_period = 1.0 # 秒(规划频率)
|
||||||
|
self.min_delta = 0.05 # m,手移动超过才重新规划
|
||||||
|
self.z_min = 0.15 # 防止撞桌
|
||||||
|
self.z_max = 0.60
|
||||||
|
self.latest_hand = None
|
||||||
|
self.last_target = None
|
||||||
|
|
||||||
|
self.base_frame = 'openarm_body_link0'
|
||||||
|
|
||||||
|
# ====== TF ======
|
||||||
|
self.tf_buffer = tf2_ros.Buffer()
|
||||||
|
self.tf_listener = tf2_ros.TransformListener(self.tf_buffer, self)
|
||||||
|
|
||||||
|
# ====== 订阅 ======
|
||||||
|
self.sub = self.create_subscription(
|
||||||
|
PointStamped,
|
||||||
|
'/hand_3d',
|
||||||
|
self.hand_cb,
|
||||||
|
10
|
||||||
|
)
|
||||||
|
self.timer = self.create_timer(self.plan_period, self.plan_timer)
|
||||||
|
|
||||||
|
self.get_logger().info('Hand → MoveIt Planning node started')
|
||||||
|
# ---------- 回调 ----------
|
||||||
|
def hand_cb(self, msg):
|
||||||
|
try:
|
||||||
|
hand = self.tf_buffer.transform(
|
||||||
|
msg,
|
||||||
|
self.base_frame,
|
||||||
|
timeout=rclpy.duration.Duration(seconds=0.1)
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
return
|
||||||
|
print(f"手坐标({self.base_frame}):{hand.point.x:.3f}, {hand.point.y:.3f}, {hand.point.z:.3f}")
|
||||||
|
self.latest_hand = np.array([
|
||||||
|
hand.point.x,
|
||||||
|
hand.point.y,
|
||||||
|
hand.point.z
|
||||||
|
])
|
||||||
|
|
||||||
|
def update_hand(self, p):
|
||||||
|
""" p: np.array([x,y,z]) in base_link """
|
||||||
|
|
||||||
|
# 1️⃣ 限幅(按你机械臂改)
|
||||||
|
p[0] = np.clip(p[0], 0.25, 0.55)
|
||||||
|
p[1] = np.clip(p[1], -0.30, 0.30)
|
||||||
|
p[2] = np.clip(p[2], 0.25, 0.30)
|
||||||
|
|
||||||
|
# 2️⃣ 低通滤波
|
||||||
|
if self.p_prev is None:
|
||||||
|
self.p_prev = p
|
||||||
|
return
|
||||||
|
|
||||||
|
alpha = 0.2
|
||||||
|
p = alpha * p + (1 - alpha) * self.p_prev
|
||||||
|
|
||||||
|
# 3️⃣ 死区
|
||||||
|
if np.linalg.norm(p - self.p_prev) < 0.015:
|
||||||
|
return
|
||||||
|
|
||||||
|
# 4️⃣ 控制频率
|
||||||
|
if time.time() - self.last_sent < self.send_period:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.send_goal(p)
|
||||||
|
self.p_prev = p
|
||||||
|
self.last_sent = time.time()
|
||||||
|
|
||||||
|
def send_goal(self, p):
|
||||||
|
pose = PoseStamped()
|
||||||
|
pose.header.frame_id = "openarm_body_link0"
|
||||||
|
pose.pose.position.x = float(p[0])
|
||||||
|
pose.pose.position.y = float(p[1])
|
||||||
|
pose.pose.position.z = float(p[2])
|
||||||
|
pose.pose.orientation.w = 1.0
|
||||||
|
# pose.pose.position.x = 0.0
|
||||||
|
# pose.pose.position.y = 0.153
|
||||||
|
# pose.pose.position.z = 0.262
|
||||||
|
goal = MoveGroup.Goal()
|
||||||
|
req = goal.request
|
||||||
|
req.group_name = "left_arm"
|
||||||
|
req.allowed_planning_time = 2.0
|
||||||
|
req.max_velocity_scaling_factor = 0.2
|
||||||
|
|
||||||
|
pc = PositionConstraint()
|
||||||
|
pc.header = pose.header
|
||||||
|
pc.link_name = "openarm_left_link7"
|
||||||
|
|
||||||
|
box = SolidPrimitive()
|
||||||
|
box.type = SolidPrimitive.BOX
|
||||||
|
box.dimensions = [0.02, 0.02, 0.02]
|
||||||
|
pc.constraint_region.primitives.append(box)
|
||||||
|
pc.constraint_region.primitive_poses.append(pose.pose)
|
||||||
|
|
||||||
|
constraints = Constraints()
|
||||||
|
constraints.position_constraints.append(pc)
|
||||||
|
req.goal_constraints.append(constraints)
|
||||||
|
|
||||||
|
self.client.wait_for_server()
|
||||||
|
self.client.send_goal_async(goal)
|
||||||
|
|
||||||
|
def plan_timer(self):
|
||||||
|
if self.latest_hand is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
# 限制 workspace
|
||||||
|
target = self.latest_hand.copy()
|
||||||
|
target[2] = np.clip(target[2], self.z_min, self.z_max)
|
||||||
|
|
||||||
|
# 判断是否值得重新规划
|
||||||
|
if self.last_target is not None:
|
||||||
|
if np.linalg.norm(target - self.last_target) < self.min_delta:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.last_target = target.copy()
|
||||||
|
self.send_goal(target)
|
||||||
|
print(f"发送新目标:{target[0]:.3f}, {target[1]:.3f}, {target[2]:.3f}")
|
||||||
|
|
||||||
|
def main():
|
||||||
|
rclpy.init()
|
||||||
|
node = HandFollowMoveIt()
|
||||||
|
rclpy.spin(node)
|
||||||
|
rclpy.shutdown()
|
||||||
|
# while True:
|
||||||
|
# p_hand_base = get_hand_3d() # 你已有
|
||||||
|
# node.update_hand(p_hand_base)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@ -132,7 +132,7 @@ class StereoHandDepthNode(Node):
|
|||||||
if results.multi_hand_landmarks:
|
if results.multi_hand_landmarks:
|
||||||
wrist = results.multi_hand_landmarks[0].landmark[0]
|
wrist = results.multi_hand_landmarks[0].landmark[0]
|
||||||
|
|
||||||
u = int(wrist.x * w)
|
u = int(wrist.x * (w / 2)) # 注意这里是左图宽度
|
||||||
v = int(wrist.y * h)
|
v = int(wrist.y * h)
|
||||||
p.header = msg.header
|
p.header = msg.header
|
||||||
p.point.x = float(u)
|
p.point.x = float(u)
|
||||||
@ -142,7 +142,7 @@ class StereoHandDepthNode(Node):
|
|||||||
# 转灰度
|
# 转灰度
|
||||||
left_gray = cv2.cvtColor(left_rect, cv2.COLOR_BGR2GRAY)
|
left_gray = cv2.cvtColor(left_rect, cv2.COLOR_BGR2GRAY)
|
||||||
right_gray = cv2.cvtColor(right_rect, cv2.COLOR_BGR2GRAY)
|
right_gray = cv2.cvtColor(right_rect, cv2.COLOR_BGR2GRAY)
|
||||||
# cv2.imshow('left', left_gray)
|
|
||||||
|
|
||||||
# 计算视差
|
# 计算视差
|
||||||
disparity = self.stereo.compute(left_gray, right_gray).astype(np.float32) / 16.0
|
disparity = self.stereo.compute(left_gray, right_gray).astype(np.float32) / 16.0
|
||||||
@ -150,11 +150,14 @@ class StereoHandDepthNode(Node):
|
|||||||
# ----------- 这里先用“假手点”(画面中心)-----------
|
# ----------- 这里先用“假手点”(画面中心)-----------
|
||||||
x = int(p.point.x)
|
x = int(p.point.x)
|
||||||
y = int(p.point.y)
|
y = int(p.point.y)
|
||||||
print(f'Wrist pixel: ({x}, {y})')
|
cv2.circle(left_gray, (x, y), 5, (255, 0, 0), -1)
|
||||||
|
# print("left_gray.shape:", left_gray.shape)
|
||||||
|
# cv2.imshow('left', left_gray)
|
||||||
|
# print(f'Wrist pixel: ({x}, {y})')
|
||||||
# ROI 取中值,稳很多
|
# ROI 取中值,稳很多
|
||||||
roi = disparity[y-3:y+3, x-3:x+3]
|
roi = disparity[y-3:y+3, x-3:x+3]
|
||||||
d = np.nanmedian(roi)
|
d = np.nanmedian(roi)
|
||||||
print(f'Disparity at wrist: {roi}')
|
# print(f'Disparity at wrist: {roi}')
|
||||||
|
|
||||||
if d <= 0:
|
if d <= 0:
|
||||||
return
|
return
|
||||||
|
|||||||
@ -6,6 +6,12 @@
|
|||||||
<description>TODO: Package description</description>
|
<description>TODO: Package description</description>
|
||||||
<maintainer email="shen@todo.todo">shen</maintainer>
|
<maintainer email="shen@todo.todo">shen</maintainer>
|
||||||
<license>TODO: License declaration</license>
|
<license>TODO: License declaration</license>
|
||||||
|
<exec_depend>rclpy</exec_depend>
|
||||||
|
<exec_depend>sensor_msgs</exec_depend>
|
||||||
|
<exec_depend>cv_bridge</exec_depend>
|
||||||
|
<exec_depend>usb_cam</exec_depend>
|
||||||
|
<exec_depend>stereo_image_proc</exec_depend>
|
||||||
|
|
||||||
|
|
||||||
<test_depend>ament_copyright</test_depend>
|
<test_depend>ament_copyright</test_depend>
|
||||||
<test_depend>ament_flake8</test_depend>
|
<test_depend>ament_flake8</test_depend>
|
||||||
|
|||||||
@ -32,6 +32,7 @@ setup(
|
|||||||
'camera_node = openarm_camera.camera:main',
|
'camera_node = openarm_camera.camera:main',
|
||||||
'camera_pub_node = openarm_camera.camera_pub:main',
|
'camera_pub_node = openarm_camera.camera_pub:main',
|
||||||
'stereo_hand_depth_node = openarm_camera.stereo_hand_depth_node:main',
|
'stereo_hand_depth_node = openarm_camera.stereo_hand_depth_node:main',
|
||||||
|
'hand_to_moveit_planning = openarm_camera.hand_to_moveit_planning:main',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user