commit f6391b9b63d9c653ddb19af9bb50e721075180a5 Author: shen <664376944@qq.com> Date: Mon Jan 26 01:06:54 2026 +0800 初始提交 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ffbfbf3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.vscode/ +build/ +install/ +log/ \ No newline at end of file diff --git a/calibrationdata.tar.gz b/calibrationdata.tar.gz new file mode 100644 index 0000000..90ce61c Binary files /dev/null and b/calibrationdata.tar.gz differ diff --git a/frames_2026-01-23_14.52.23.gv b/frames_2026-01-23_14.52.23.gv new file mode 100644 index 0000000..5eabf58 --- /dev/null +++ b/frames_2026-01-23_14.52.23.gv @@ -0,0 +1,31 @@ +digraph G { +"openarm_left_hand" -> "openarm_left_right_finger"[label=" Broadcaster: default_authority\nAverage rate: 18.541\nBuffer length: 3.29\nMost recent transform: 1769151143.718129\nOldest transform: 1769151140.428147\n"]; +"openarm_left_link7" -> "openarm_left_hand"[label=" Broadcaster: default_authority\nAverage rate: 10000.0\nBuffer length: 0.0\nMost recent transform: 0.0\nOldest transform: 0.0\n"]; +"openarm_left_hand" -> "openarm_left_left_finger"[label=" Broadcaster: default_authority\nAverage rate: 18.541\nBuffer length: 3.29\nMost recent transform: 1769151143.718129\nOldest transform: 1769151140.428147\n"]; +"openarm_left_link0" -> "openarm_left_link1"[label=" Broadcaster: default_authority\nAverage rate: 18.541\nBuffer length: 3.29\nMost recent transform: 1769151143.718129\nOldest transform: 1769151140.428147\n"]; +"openarm_body_link0" -> "openarm_left_link0"[label=" Broadcaster: default_authority\nAverage rate: 10000.0\nBuffer length: 0.0\nMost recent transform: 0.0\nOldest transform: 0.0\n"]; +"openarm_left_link1" -> "openarm_left_link2"[label=" Broadcaster: default_authority\nAverage rate: 18.541\nBuffer length: 3.29\nMost recent transform: 1769151143.718129\nOldest transform: 1769151140.428147\n"]; +"openarm_left_link2" -> "openarm_left_link3"[label=" Broadcaster: default_authority\nAverage rate: 18.541\nBuffer length: 3.29\nMost recent transform: 1769151143.718129\nOldest transform: 1769151140.428147\n"]; +"openarm_left_link3" -> "openarm_left_link4"[label=" Broadcaster: default_authority\nAverage rate: 18.541\nBuffer length: 3.29\nMost recent transform: 1769151143.718129\nOldest transform: 1769151140.428147\n"]; +"openarm_left_link4" -> "openarm_left_link5"[label=" Broadcaster: default_authority\nAverage rate: 18.541\nBuffer length: 3.29\nMost recent transform: 1769151143.718129\nOldest transform: 1769151140.428147\n"]; +"openarm_left_link5" -> "openarm_left_link6"[label=" Broadcaster: default_authority\nAverage rate: 18.541\nBuffer length: 3.29\nMost recent transform: 1769151143.718129\nOldest transform: 1769151140.428147\n"]; +"openarm_left_link6" -> "openarm_left_link7"[label=" Broadcaster: default_authority\nAverage rate: 18.541\nBuffer length: 3.29\nMost recent transform: 1769151143.718129\nOldest transform: 1769151140.428147\n"]; +"openarm_right_hand" -> "openarm_right_right_finger"[label=" Broadcaster: default_authority\nAverage rate: 18.541\nBuffer length: 3.29\nMost recent transform: 1769151143.718129\nOldest transform: 1769151140.428147\n"]; +"openarm_right_link7" -> "openarm_right_hand"[label=" Broadcaster: default_authority\nAverage rate: 10000.0\nBuffer length: 0.0\nMost recent transform: 0.0\nOldest transform: 0.0\n"]; +"openarm_right_hand" -> "openarm_right_left_finger"[label=" Broadcaster: default_authority\nAverage rate: 18.541\nBuffer length: 3.29\nMost recent transform: 1769151143.718129\nOldest transform: 1769151140.428147\n"]; +"openarm_right_link0" -> "openarm_right_link1"[label=" Broadcaster: default_authority\nAverage rate: 18.541\nBuffer length: 3.29\nMost recent transform: 1769151143.718129\nOldest transform: 1769151140.428147\n"]; +"openarm_body_link0" -> "openarm_right_link0"[label=" Broadcaster: default_authority\nAverage rate: 10000.0\nBuffer length: 0.0\nMost recent transform: 0.0\nOldest transform: 0.0\n"]; +"openarm_right_link1" -> "openarm_right_link2"[label=" Broadcaster: default_authority\nAverage rate: 18.541\nBuffer length: 3.29\nMost recent transform: 1769151143.718129\nOldest transform: 1769151140.428147\n"]; +"openarm_right_link2" -> "openarm_right_link3"[label=" Broadcaster: default_authority\nAverage rate: 18.541\nBuffer length: 3.29\nMost recent transform: 1769151143.718129\nOldest transform: 1769151140.428147\n"]; +"openarm_right_link3" -> "openarm_right_link4"[label=" Broadcaster: default_authority\nAverage rate: 18.541\nBuffer length: 3.29\nMost recent transform: 1769151143.718129\nOldest transform: 1769151140.428147\n"]; +"openarm_right_link4" -> "openarm_right_link5"[label=" Broadcaster: default_authority\nAverage rate: 18.541\nBuffer length: 3.29\nMost recent transform: 1769151143.718129\nOldest transform: 1769151140.428147\n"]; +"openarm_right_link5" -> "openarm_right_link6"[label=" Broadcaster: default_authority\nAverage rate: 18.541\nBuffer length: 3.29\nMost recent transform: 1769151143.718129\nOldest transform: 1769151140.428147\n"]; +"openarm_right_link6" -> "openarm_right_link7"[label=" Broadcaster: default_authority\nAverage rate: 18.541\nBuffer length: 3.29\nMost recent transform: 1769151143.718129\nOldest transform: 1769151140.428147\n"]; +"world" -> "openarm_body_link0"[label=" Broadcaster: default_authority\nAverage rate: 10000.0\nBuffer length: 0.0\nMost recent transform: 0.0\nOldest transform: 0.0\n"]; +"openarm_left_hand" -> "openarm_left_hand_tcp"[label=" Broadcaster: default_authority\nAverage rate: 10000.0\nBuffer length: 0.0\nMost recent transform: 0.0\nOldest transform: 0.0\n"]; +"openarm_right_hand" -> "openarm_right_hand_tcp"[label=" Broadcaster: default_authority\nAverage rate: 10000.0\nBuffer length: 0.0\nMost recent transform: 0.0\nOldest transform: 0.0\n"]; +edge [style=invis]; + subgraph cluster_legend { style=bold; color=black; label ="view_frames Result"; +"Recorded at time: 1769151143.7895906"[ shape=plaintext ] ; +}->"world"; +} \ No newline at end of file diff --git a/frames_2026-01-23_14.52.23.pdf b/frames_2026-01-23_14.52.23.pdf new file mode 100644 index 0000000..1424011 Binary files /dev/null and b/frames_2026-01-23_14.52.23.pdf differ diff --git a/handeye_data.npz b/handeye_data.npz new file mode 100644 index 0000000..be2cc60 Binary files /dev/null and b/handeye_data.npz differ diff --git a/src/demo_openarm_publish/demo_openarm_publish/__init__.py b/src/demo_openarm_publish/demo_openarm_publish/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/demo_openarm_publish/demo_openarm_publish/publish.py b/src/demo_openarm_publish/demo_openarm_publish/publish.py new file mode 100644 index 0000000..eb94f88 --- /dev/null +++ b/src/demo_openarm_publish/demo_openarm_publish/publish.py @@ -0,0 +1,113 @@ +import rclpy +from rclpy.node import Node +# from control_msgs.msg import JointTrajectoryControllerState +from control_msgs.action import FollowJointTrajectory +from rclpy.action import ActionClient +from trajectory_msgs.msg import JointTrajectory, JointTrajectoryPoint +from geometry_msgs.msg import Twist + + +# class ControlPub(Node): +# def __init__(self, node_name): +# super().__init__(node_name) +# self.status_publisher_ = self.create_publisher( +# JointTrajectoryControllerState, '/right_joint_trajectory_controller/controller_state', 100) +# self.timer = self.create_timer(0.02, self.timer_callback) + +# def timer_callback(self): +# arm_pos = [0.15, 0.15, 0.15, 0.15, 0.15, 0.15, 0.15] +# arm_zero = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] +# msg = JointTrajectoryControllerState() +# msg.reference.positions = arm_zero +# self.get_logger().info(f'发布:{msg}') +# self.status_publisher_.publish(msg) + +# def main(): +# rclpy.init() +# node = ControlPub('control_pub') +# rclpy.spin(node) +# rclpy.shutdown() +count = 0 +count1 = 0 +class ProgressActionClient(Node): + + + def __init__(self): + super().__init__('progress_action_client') + + self.subscriber_ = self.create_subscription( + Twist, + "/cmd_vel", + self.listener_callback, + 100 + ) + # 3-1.创建动作客户端; + self._action_client = ActionClient(self, FollowJointTrajectory, '/right_joint_trajectory_controller/follow_joint_trajectory') + + # 3. 创建订阅者 + # 参数1:消息类型(如String,与发布者一致) + # 参数2:话题名称(如"chatter",与发布者一致) + # 参数3:回调函数(消息到达时触发,用于处理消息) + # 参数4:队列长度(10,缓存消息的最大数量) + + self.subscriber_ # 防止未使用变量警告 + + # 4. 消息处理回调函数(核心) + # 当订阅到消息时,自动调用此函数处理消息 + def listener_callback(self, msg): + global count + global count1 + count += msg.linear.x + count1 += msg.linear.z + # 打印接收的消息(可根据需求修改处理逻辑,如存储、解析、控制硬件等) + self.get_logger().info(f"接收到消息: {msg.linear.x} {msg.linear.z} angle:{count}") + self.send_goal([count, count1, 0.0, 0.0, 0.0, 0.0, 0.0]) + + def send_goal(self, arm_pos): + arm_joint = ['openarm_right_joint1', 'openarm_right_joint2', 'openarm_right_joint3', 'openarm_right_joint4', 'openarm_right_joint5', 'openarm_right_joint6', 'openarm_right_joint7'] + arm_effort = [10.0, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0] + arm_zero = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] + # 3-2.发送请求; + goal_msg = FollowJointTrajectory.Goal() + goal_msg.trajectory.joint_names = arm_joint + goal_msg.trajectory.points.append(JointTrajectoryPoint()) + goal_msg.trajectory.points[0].positions = arm_pos + # goal_msg.trajectory.points[0].effort = arm_effort + print(goal_msg) + + self._action_client.wait_for_server() + self._send_goal_future = self._action_client.send_goal_async(goal_msg, feedback_callback=self.feedback_callback) + self._send_goal_future.add_done_callback(self.goal_response_callback) + + def goal_response_callback(self, future): + # 3-3.处理目标发送后的反馈; + goal_handle = future.result() + print(goal_handle) + if not goal_handle.accepted: + self.get_logger().info('请求被拒绝') + return + + self.get_logger().info('请求被接收,开始执行任务!') + + self._get_result_future = goal_handle.get_result_async() + self._get_result_future.add_done_callback(self.get_result_callback) + + # 3-5.处理最终响应。 + def get_result_callback(self, future): + return + # result = future.result().result + # self.get_logger().info('最终计算结果:sum = %d' % result.sum) + # 5.释放资源。 + # rclpy.shutdown() + + # 3-4.处理连续反馈; + def feedback_callback(self, feedback_msg): + feedback = (int)(feedback_msg.feedback.progress * 100) + self.get_logger().info('当前进度: %d%%' % feedback) + +def main(): + rclpy.init() + node = ProgressActionClient() + # node.send_goal([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]) + rclpy.spin(node) + rclpy.shutdown() diff --git a/src/demo_openarm_publish/package.xml b/src/demo_openarm_publish/package.xml new file mode 100644 index 0000000..648d348 --- /dev/null +++ b/src/demo_openarm_publish/package.xml @@ -0,0 +1,20 @@ + + + + demo_openarm_publish + 0.0.0 + TODO: Package description + shen + TODO: License declaration + + rclpy + + ament_copyright + ament_flake8 + ament_pep257 + python3-pytest + + + ament_python + + diff --git a/src/demo_openarm_publish/resource/demo_openarm_publish b/src/demo_openarm_publish/resource/demo_openarm_publish new file mode 100644 index 0000000..e69de29 diff --git a/src/demo_openarm_publish/setup.cfg b/src/demo_openarm_publish/setup.cfg new file mode 100644 index 0000000..fec1b9f --- /dev/null +++ b/src/demo_openarm_publish/setup.cfg @@ -0,0 +1,4 @@ +[develop] +script_dir=$base/lib/demo_openarm_publish +[install] +install_scripts=$base/lib/demo_openarm_publish diff --git a/src/demo_openarm_publish/setup.py b/src/demo_openarm_publish/setup.py new file mode 100644 index 0000000..20618d3 --- /dev/null +++ b/src/demo_openarm_publish/setup.py @@ -0,0 +1,26 @@ +from setuptools import find_packages, setup + +package_name = 'demo_openarm_publish' + +setup( + name=package_name, + version='0.0.0', + packages=find_packages(exclude=['test']), + data_files=[ + ('share/ament_index/resource_index/packages', + ['resource/' + package_name]), + ('share/' + package_name, ['package.xml']), + ], + install_requires=['setuptools'], + zip_safe=True, + maintainer='shen', + maintainer_email='shen@todo.todo', + description='TODO: Package description', + license='TODO: License declaration', + tests_require=['pytest'], + entry_points={ + 'console_scripts': [ + 'pub_node = demo_openarm_publish.publish:main' + ], + }, +) diff --git a/src/demo_openarm_publish/test/test_copyright.py b/src/demo_openarm_publish/test/test_copyright.py new file mode 100644 index 0000000..97a3919 --- /dev/null +++ b/src/demo_openarm_publish/test/test_copyright.py @@ -0,0 +1,25 @@ +# Copyright 2015 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_copyright.main import main +import pytest + + +# Remove the `skip` decorator once the source file(s) have a copyright header +@pytest.mark.skip(reason='No copyright header has been placed in the generated source file.') +@pytest.mark.copyright +@pytest.mark.linter +def test_copyright(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found errors' diff --git a/src/demo_openarm_publish/test/test_flake8.py b/src/demo_openarm_publish/test/test_flake8.py new file mode 100644 index 0000000..27ee107 --- /dev/null +++ b/src/demo_openarm_publish/test/test_flake8.py @@ -0,0 +1,25 @@ +# Copyright 2017 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_flake8.main import main_with_errors +import pytest + + +@pytest.mark.flake8 +@pytest.mark.linter +def test_flake8(): + rc, errors = main_with_errors(argv=[]) + assert rc == 0, \ + 'Found %d code style errors / warnings:\n' % len(errors) + \ + '\n'.join(errors) diff --git a/src/demo_openarm_publish/test/test_pep257.py b/src/demo_openarm_publish/test/test_pep257.py new file mode 100644 index 0000000..b234a38 --- /dev/null +++ b/src/demo_openarm_publish/test/test_pep257.py @@ -0,0 +1,23 @@ +# Copyright 2015 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_pep257.main import main +import pytest + + +@pytest.mark.linter +@pytest.mark.pep257 +def test_pep257(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found code style errors / warnings' diff --git a/src/demo_openarm_subscriber/demo_openarm_subscriber/__init__.py b/src/demo_openarm_subscriber/demo_openarm_subscriber/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/demo_openarm_subscriber/demo_openarm_subscriber/subscriber.py b/src/demo_openarm_subscriber/demo_openarm_subscriber/subscriber.py new file mode 100644 index 0000000..ff20e6f --- /dev/null +++ b/src/demo_openarm_subscriber/demo_openarm_subscriber/subscriber.py @@ -0,0 +1,39 @@ +import rclpy +from rclpy.node import Node +from control_msgs.msg import JointTrajectoryControllerState + +# 2. 定义节点类(继承Node) +class MySubscriber(Node): + def __init__(self): + # 调用父类构造函数,初始化节点名称(如"py_subscriber_node") + super().__init__("py_subscriber_node") + + # 3. 创建订阅者 + # 参数1:消息类型(如String,与发布者一致) + # 参数2:话题名称(如"chatter",与发布者一致) + # 参数3:回调函数(消息到达时触发,用于处理消息) + # 参数4:队列长度(10,缓存消息的最大数量) + self.subscriber_ = self.create_subscription( + JointTrajectoryControllerState, + "/right_joint_trajectory_controller/controller_state", + self.listener_callback, + 100 + ) + self.subscriber_ # 防止未使用变量警告 + + # 4. 消息处理回调函数(核心) + # 当订阅到消息时,自动调用此函数处理消息 + def listener_callback(self, msg): + # 打印接收的消息(可根据需求修改处理逻辑,如存储、解析、控制硬件等) + self.get_logger().info(f"接收到消息: {msg}") + + +def main(): + rclpy.init() + node = MySubscriber() + node.get_logger().info('111111111111') + node.get_logger().warn('222222222222222') + + rclpy.spin(node) # 启动事件循环(持续监听话题,等待消息) + node.destroy_node() # 退出时销毁节点 + rclpy.shutdown() # 关闭ROS 2运行时 diff --git a/src/demo_openarm_subscriber/package.xml b/src/demo_openarm_subscriber/package.xml new file mode 100644 index 0000000..211b365 --- /dev/null +++ b/src/demo_openarm_subscriber/package.xml @@ -0,0 +1,20 @@ + + + + demo_openarm_subscriber + 0.0.0 + TODO: Package description + shen + TODO: License declaration + + rclpy + ament_copyright + ament_flake8 + ament_pep257 + python3-pytest + + + + ament_python + + diff --git a/src/demo_openarm_subscriber/resource/demo_openarm_subscriber b/src/demo_openarm_subscriber/resource/demo_openarm_subscriber new file mode 100644 index 0000000..e69de29 diff --git a/src/demo_openarm_subscriber/setup.cfg b/src/demo_openarm_subscriber/setup.cfg new file mode 100644 index 0000000..baea16b --- /dev/null +++ b/src/demo_openarm_subscriber/setup.cfg @@ -0,0 +1,4 @@ +[develop] +script_dir=$base/lib/demo_openarm_subscriber +[install] +install_scripts=$base/lib/demo_openarm_subscriber diff --git a/src/demo_openarm_subscriber/setup.py b/src/demo_openarm_subscriber/setup.py new file mode 100644 index 0000000..72622b5 --- /dev/null +++ b/src/demo_openarm_subscriber/setup.py @@ -0,0 +1,26 @@ +from setuptools import find_packages, setup + +package_name = 'demo_openarm_subscriber' + +setup( + name=package_name, + version='0.0.0', + packages=find_packages(exclude=['test']), + data_files=[ + ('share/ament_index/resource_index/packages', + ['resource/' + package_name]), + ('share/' + package_name, ['package.xml']), + ], + install_requires=['setuptools'], + zip_safe=True, + maintainer='shen', + maintainer_email='shen@todo.todo', + description='TODO: Package description', + license='TODO: License declaration', + tests_require=['pytest'], + entry_points={ + 'console_scripts': [ + 'sub_node = demo_openarm_subscriber.subscriber:main' + ], + }, +) diff --git a/src/demo_openarm_subscriber/test/test_copyright.py b/src/demo_openarm_subscriber/test/test_copyright.py new file mode 100644 index 0000000..97a3919 --- /dev/null +++ b/src/demo_openarm_subscriber/test/test_copyright.py @@ -0,0 +1,25 @@ +# Copyright 2015 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_copyright.main import main +import pytest + + +# Remove the `skip` decorator once the source file(s) have a copyright header +@pytest.mark.skip(reason='No copyright header has been placed in the generated source file.') +@pytest.mark.copyright +@pytest.mark.linter +def test_copyright(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found errors' diff --git a/src/demo_openarm_subscriber/test/test_flake8.py b/src/demo_openarm_subscriber/test/test_flake8.py new file mode 100644 index 0000000..27ee107 --- /dev/null +++ b/src/demo_openarm_subscriber/test/test_flake8.py @@ -0,0 +1,25 @@ +# Copyright 2017 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_flake8.main import main_with_errors +import pytest + + +@pytest.mark.flake8 +@pytest.mark.linter +def test_flake8(): + rc, errors = main_with_errors(argv=[]) + assert rc == 0, \ + 'Found %d code style errors / warnings:\n' % len(errors) + \ + '\n'.join(errors) diff --git a/src/demo_openarm_subscriber/test/test_pep257.py b/src/demo_openarm_subscriber/test/test_pep257.py new file mode 100644 index 0000000..b234a38 --- /dev/null +++ b/src/demo_openarm_subscriber/test/test_pep257.py @@ -0,0 +1,23 @@ +# Copyright 2015 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_pep257.main import main +import pytest + + +@pytest.mark.linter +@pytest.mark.pep257 +def test_pep257(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found code style errors / warnings' diff --git a/src/media_pipe_ros2/media_pipe_ros2/FaceMesh(NAOEXCLUIR).py b/src/media_pipe_ros2/media_pipe_ros2/FaceMesh(NAOEXCLUIR).py new file mode 100644 index 0000000..d322f6a --- /dev/null +++ b/src/media_pipe_ros2/media_pipe_ros2/FaceMesh(NAOEXCLUIR).py @@ -0,0 +1,232 @@ +import cv2 +from mediapipe.framework.formats import landmark_pb2 +import mediapipe as mp +mp_drawing = mp.solutions.drawing_utils +mp_face_mesh = mp.solutions.face_mesh + +FACE_CONNECTIONS_LIPS = frozenset([ + # Lips. + (61, 146), + (146, 91), + (91, 181), + (181, 84), + (84, 17), + (17, 314), + (314, 405), + (405, 321), + (321, 375), + (375, 291), + (61, 185), + (185, 40), + (40, 39), + (39, 37), + (37, 0), + (0, 267), + (267, 269), + (269, 270), + (270, 409), + (409, 291), + (78, 95), + (95, 88), + (88, 178), + (178, 87), + (87, 14), + (14, 317), + (317, 402), + (402, 318), + (318, 324), + (324, 308), + (78, 191), + (191, 80), + (80, 81), + (81, 82), + (82, 13), + (13, 312), + (312, 311), + (311, 310), + (310, 415), + (415, 308) +]) + +FACE_CONNECTIONS_OVAL = frozenset([ + # Face oval. + (10, 338), + (338, 297), + (297, 332), + (332, 284), + (284, 251), + (251, 389), + (389, 356), + (356, 454), + (454, 323), + (323, 361), + (361, 288), + (288, 397), + (397, 365), + (365, 379), + (379, 378), + (378, 400), + (400, 377), + (377, 152), + (152, 148), + (148, 176), + (176, 149), + (149, 150), + (150, 136), + (136, 172), + (172, 58), + (58, 132), + (132, 93), + (93, 234), + (234, 127), + (127, 162), + (162, 21), + (21, 54), + (54, 103), + (103, 67), + (67, 109), + (109, 10) +]) + +FACE_CONNECTIONS_EYES= frozenset([ + # Left eye. + (263, 249), + (249, 390), + (390, 373), + (373, 374), + (374, 380), + (380, 381), + (381, 382), + (382, 362), + (263, 466), + (466, 388), + (388, 387), + (387, 386), + (386, 385), + (385, 384), + (384, 398), + (398, 362), + + # Right eye. + (33, 7), + (7, 163), + (163, 144), + (144, 145), + (145, 153), + (153, 154), + (154, 155), + (155, 133), + (33, 246), + (246, 161), + (161, 160), + (160, 159), + (159, 158), + (158, 157), + (157, 173), + (173, 133), + +]) + +FACE_CONNECTIONS_EYEBROWS= frozenset([ + + # Left eyebrow. + (276, 283), + (283, 282), + (282, 295), + (295, 285), + (300, 293), + (293, 334), + (334, 296), + (296, 336), + + # Right eyebrow. + (46, 53), + (53, 52), + (52, 65), + (65, 55), + (70, 63), + (63, 105), + (105, 66), + (66, 107), +]) + +# For static images: +IMAGE_FILES = [] +drawing_spec = mp_drawing.DrawingSpec(thickness=1, circle_radius=1) +with mp_face_mesh.FaceMesh( + static_image_mode=True, + max_num_faces=1, + min_detection_confidence=0.5) as face_mesh: + for idx, file in enumerate(IMAGE_FILES): + image = cv2.imread(file) + # Convert the BGR image to RGB before processing. + results = face_mesh.process(cv2.cvtColor(image, cv2.COLOR_BGR2RGB)) + + # Print and draw face mesh landmarks on the image. + if not results.multi_face_landmarks: + continue + annotated_image = image.copy() + for face_landmarks in results.multi_face_landmarks: + print('face_landmarks:', face_landmarks) + mp_drawing.draw_landmarks( + image=annotated_image, + landmark_list=face_landmarks, + connections=mp_face_mesh.FACE_CONNECTIONS, + landmark_drawing_spec=drawing_spec, + connection_drawing_spec=drawing_spec) + cv2.imwrite('/tmp/annotated_image' + str(idx) + '.png', annotated_image) + +# For webcam input: +drawing_spec = mp_drawing.DrawingSpec(thickness=1, circle_radius=1) +cap = cv2.VideoCapture(0) +with mp_face_mesh.FaceMesh( + min_detection_confidence=0.5, + min_tracking_confidence=0.5) as face_mesh: + while cap.isOpened(): + success, image = cap.read() + if not success: + print("Ignoring empty camera frame.") + # If loading a video, use 'break' instead of 'continue'. + continue + + # Flip the image horizontally for a later selfie-view display, and convert + # the BGR image to RGB. + image = cv2.cvtColor(cv2.flip(image, 1), cv2.COLOR_BGR2RGB) + # To improve performance, optionally mark the image as not writeable to + # pass by reference. + image.flags.writeable = False + results = face_mesh.process(image) + image_hight, image_width, _ = image.shape + # Draw the face mesh annotations on the image. + image.flags.writeable = True + image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) + teste = [] + if results.multi_face_landmarks: + index_face = 0 + while index_face <= 467: + teste.append(results.multi_face_landmarks[0].landmark[index_face]) + index_face = index_face +1 + landmark_subset = landmark_pb2.NormalizedLandmarkList( + landmark = teste + ) + print(landmark_subset) + + mp_drawing.draw_landmarks( + + image=image, + landmark_list=landmark_subset, + connections=FACE_CONNECTIONS_EYEBROWS, + landmark_drawing_spec=drawing_spec, + connection_drawing_spec=drawing_spec) + + cv2.imshow('MediaPipe FaceMesh', image) + if cv2.waitKey(5) & 0xFF == 27: + break +cap.release() +#boca cima +# teste.append(results.multi_face_landmarks[0].landmark[0]) +# teste.append(results.multi_face_landmarks[0].landmark[12]) +# # teste.append(results.multi_face_landmarks[0].landmark[13]) +# # teste.append(results.multi_face_landmarks[0].landmark[14]) +#boca baixo +# # teste.append(results.multi_face_landmarks[0].landmark[15]) diff --git a/src/media_pipe_ros2/media_pipe_ros2/__init__.py b/src/media_pipe_ros2/media_pipe_ros2/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/media_pipe_ros2/media_pipe_ros2/face_mesh_detector.py b/src/media_pipe_ros2/media_pipe_ros2/face_mesh_detector.py new file mode 100644 index 0000000..aef758d --- /dev/null +++ b/src/media_pipe_ros2/media_pipe_ros2/face_mesh_detector.py @@ -0,0 +1,117 @@ +import rclpy +import cv2 +import mediapipe as mp +from mediapipe.framework.formats import landmark_pb2 +from rclpy.node import Node +from media_pipe_ros2_msg.msg import MediaPipeHumanFaceMeshList +from mediapipe.python.solutions.pose import PoseLandmark + +mp_drawing = mp.solutions.drawing_utils +mp_face_mesh = mp.solutions.face_mesh +NAME_POSE = [ + (PoseLandmark.NOSE), (PoseLandmark.LEFT_EYE_INNER), + (PoseLandmark.LEFT_EYE), (PoseLandmark.LEFT_EYE_OUTER), + (PoseLandmark.RIGHT_EYE_INNER), ( PoseLandmark.RIGHT_EYE), + (PoseLandmark.RIGHT_EYE_OUTER), ( PoseLandmark.LEFT_EAR), + (PoseLandmark.RIGHT_EAR), ( PoseLandmark.MOUTH_LEFT), + (PoseLandmark.MOUTH_RIGHT), ( PoseLandmark.LEFT_SHOULDER), + (PoseLandmark.RIGHT_SHOULDER), ( PoseLandmark.LEFT_ELBOW), + (PoseLandmark.RIGHT_ELBOW), ( PoseLandmark.LEFT_WRIST), + (PoseLandmark.RIGHT_WRIST), ( PoseLandmark.LEFT_PINKY), + (PoseLandmark.RIGHT_PINKY), ( PoseLandmark.LEFT_INDEX), + (PoseLandmark.RIGHT_INDEX), ( PoseLandmark.LEFT_THUMB), + (PoseLandmark.RIGHT_THUMB), ( PoseLandmark.LEFT_HIP), + (PoseLandmark.RIGHT_HIP), ( PoseLandmark.LEFT_KNEE), + (PoseLandmark.RIGHT_KNEE), ( PoseLandmark.LEFT_ANKLE), + (PoseLandmark.RIGHT_ANKLE), ( PoseLandmark.LEFT_HEEL), + (PoseLandmark.RIGHT_HEEL), ( PoseLandmark.LEFT_FOOT_INDEX), + (PoseLandmark.RIGHT_FOOT_INDEX) +] +cap = cv2.VideoCapture(0) + +class FaceMeshPublisher(Node): + + def __init__(self): + super().__init__('mediapipe_face_mesh_publisher') + self.publisher_ = self.create_publisher(MediaPipeHumanFaceMeshList, '/mediapipe/human_face_mesh_list', 10) + + + def getimage_callback(self): + mediapipehumanfacemeshlist = MediaPipeHumanFaceMeshList() + + drawing_spec = mp_drawing.DrawingSpec(thickness=1, circle_radius=1) + with mp_face_mesh.FaceMesh( + min_detection_confidence=0.5, + min_tracking_confidence=0.5) as face_mesh: + while cap.isOpened(): + + success, image = cap.read() + if not success: + print("Sem camera.") + + image = cv2.cvtColor(cv2.flip(image, 1), cv2.COLOR_BGR2RGB) + image.flags.writeable = False + results = face_mesh.process(image) + image.flags.writeable = True + image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) + imageHeight, imageWidth, _ = image.shape + + # Draw the pose annotation on the image. + image.flags.writeable = True + image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) + teste = [] + if results.multi_face_landmarks: + index_face_mesh = 0 + + while index_face_mesh <= 467: + print(index_face_mesh ) + teste.append(results.multi_face_landmarks[0].landmark[index_face_mesh]) + mediapipehumanfacemeshlist.human_face_mesh_list[index_face_mesh].x = results.multi_face_landmarks[0].landmark[index_face_mesh].x + mediapipehumanfacemeshlist.human_face_mesh_list[index_face_mesh].y = results.multi_face_landmarks[0].landmark[index_face_mesh].y + mediapipehumanfacemeshlist.human_face_mesh_list[index_face_mesh].z = results.multi_face_landmarks[0].landmark[index_face_mesh].z + index_face_mesh = index_face_mesh +1 + + + landmark_subset = landmark_pb2.NormalizedLandmarkList(landmark = teste) + mp_drawing.draw_landmarks( + image=image, + landmark_list=landmark_subset, + connections=mp_face_mesh.FACE_CONNECTIONS, + landmark_drawing_spec=drawing_spec, + connection_drawing_spec=drawing_spec) + + mediapipehumanfacemeshlist.num_humans = 1 + self.publisher_.publish(mediapipehumanfacemeshlist) + else: # responsavel por mandar 0 nos topicos quando corpo nao esta na tela + index_face_mesh = 0 + while index_face_mesh <= 467: + + mediapipehumanfacemeshlist.human_face_mesh_list[index_face_mesh].x = 0.0 + mediapipehumanfacemeshlist.human_face_mesh_list[index_face_mesh].y = 0.0 + mediapipehumanfacemeshlist.human_face_mesh_list[index_face_mesh].z = 0.0 + index_face_mesh = index_face_mesh +1 + + + mediapipehumanfacemeshlist.num_humans = 1 + self.publisher_.publish(mediapipehumanfacemeshlist) + + cv2.imshow('MediaPipe Face Mesh', image) + if cv2.waitKey(5) & 0xFF == 27: + break + +def main(args=None): + rclpy.init(args=args) + + face_mesh_publisher = FaceMeshPublisher() + face_mesh_publisher.getimage_callback() + + cap.release() + + rclpy.spin(face_mesh_publisher) + + face_mesh_publisher.destroy_node() + rclpy.shutdown() + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/src/media_pipe_ros2/media_pipe_ros2/hands_detector.py b/src/media_pipe_ros2/media_pipe_ros2/hands_detector.py new file mode 100644 index 0000000..26125ee --- /dev/null +++ b/src/media_pipe_ros2/media_pipe_ros2/hands_detector.py @@ -0,0 +1,131 @@ +import rclpy +import cv2 +import mediapipe as mp +from rclpy.node import Node +from media_pipe_ros2_msg.msg import HandPoint,MediaPipeHumanHand,MediaPipeHumanHandList + + +mp_drawing = mp.solutions.drawing_utils +mp_hands = mp.solutions.hands +cap = cv2.VideoCapture(0) + +class HandsPublisher(Node): + + def __init__(self): + super().__init__('mediapipe_publisher') + self.publisher_ = self.create_publisher(MediaPipeHumanHandList, '/mediapipe/human_hand_list', 10) + + + def getimage_callback(self): + mediapipehumanlist = MediaPipeHumanHandList() + mediapipehuman = MediaPipeHumanHand() + points = HandPoint() + + with mp_hands.Hands( + static_image_mode=False, + min_detection_confidence=0.7, + min_tracking_confidence=0.7, + max_num_hands=2) as hands: + while cap.isOpened(): + + success, image = cap.read() + if not success: + print("Sem camera.") + + image = cv2.cvtColor(cv2.flip(image, 1), cv2.COLOR_BGR2RGB) + image.flags.writeable = False + results = hands.process(image) + image.flags.writeable = True + image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) + imageHeight, imageWidth, _ = image.shape + + if results.multi_hand_landmarks != None: + hand_number_screen = 0 # index de controle de quantas maos aparecem na tela + #esse for passa pela quantidades de mão na tela setada como maximo 2 no momento + for hand_landmarks, handedness in zip(results.multi_hand_landmarks,results.multi_handedness): + + if handedness.classification[0].label == "Right": + mp_drawing.draw_landmarks( + image, hand_landmarks, mp_hands.HAND_CONNECTIONS) + index_point = 0 + + for point in mp_hands.HandLandmark: + normalizedLandmark = hand_landmarks.landmark[point] + mediapipehuman.right_hand_key_points[index_point].name = str(point) + mediapipehuman.right_hand_key_points[index_point].x = normalizedLandmark.x + mediapipehuman.right_hand_key_points[index_point].y = normalizedLandmark.y + mediapipehuman.right_hand_key_points[index_point].z = normalizedLandmark.z + if hand_number_screen == 0: + mediapipehuman.left_hand_key_points[index_point].name = str(point) + mediapipehuman.left_hand_key_points[index_point].x = 0.0 + mediapipehuman.left_hand_key_points[index_point].y = 0.0 + mediapipehuman.left_hand_key_points[index_point].z = 0.0 + index_point = index_point +1 + hand_number_screen = hand_number_screen +1 + + elif handedness.classification[0].label == "Left": + mp_drawing.draw_landmarks( + image, hand_landmarks, mp_hands.HAND_CONNECTIONS) + index_point = 0 + + for point in mp_hands.HandLandmark: + + normalizedLandmark = hand_landmarks.landmark[point] + points.name = str(point) + mediapipehuman.left_hand_key_points[index_point].name = str(point) + mediapipehuman.left_hand_key_points[index_point].x = normalizedLandmark.x + mediapipehuman.left_hand_key_points[index_point].y = normalizedLandmark.y + mediapipehuman.left_hand_key_points[index_point].z = normalizedLandmark.z + + if hand_number_screen == 0: + mediapipehuman.right_hand_key_points[index_point].name = str(point) + mediapipehuman.right_hand_key_points[index_point].x = 0.0 + mediapipehuman.right_hand_key_points[index_point].y = 0.0 + mediapipehuman.right_hand_key_points[index_point].z = 0.0 + index_point = index_point +1 + hand_number_screen = hand_number_screen +1 + + mediapipehumanlist.human_hand_list.right_hand_key_points = mediapipehuman.right_hand_key_points + mediapipehumanlist.human_hand_list.left_hand_key_points = mediapipehuman.left_hand_key_points + mediapipehumanlist.num_humans = 1 + self.publisher_.publish(mediapipehumanlist) + else: # responsavel por mandar 0 nos topicos quando as duas maos nao estao na tela + index_point = 0 + for point in mp_hands.HandLandmark: + + mediapipehuman.right_hand_key_points[index_point].name = str(point) + mediapipehuman.right_hand_key_points[index_point].x = 0.0 + mediapipehuman.right_hand_key_points[index_point].y = 0.0 + mediapipehuman.right_hand_key_points[index_point].z = 0.0 + mediapipehuman.left_hand_key_points[index_point].name = str(point) + mediapipehuman.left_hand_key_points[index_point].x = 0.0 + mediapipehuman.left_hand_key_points[index_point].y = 0.0 + mediapipehuman.left_hand_key_points[index_point].z = 0.0 + index_point = index_point + 1 + + mediapipehumanlist.human_hand_list.right_hand_key_points = mediapipehuman.right_hand_key_points + mediapipehumanlist.human_hand_list.left_hand_key_points = mediapipehuman.left_hand_key_points + mediapipehumanlist.num_humans = 1 + self.publisher_.publish(mediapipehumanlist) + + cv2.imshow('MediaPipe Hands', image) + if cv2.waitKey(5) & 0xFF == 27: + break + +def main(args=None): + rclpy.init(args=args) + + hands_publisher = HandsPublisher() + hands_publisher.getimage_callback() + + + cap.release() + + rclpy.spin(hands_publisher) + + hands_publisher.destroy_node() + rclpy.shutdown() + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/src/media_pipe_ros2/media_pipe_ros2/holistic_detector.py b/src/media_pipe_ros2/media_pipe_ros2/holistic_detector.py new file mode 100644 index 0000000..48d8cbf --- /dev/null +++ b/src/media_pipe_ros2/media_pipe_ros2/holistic_detector.py @@ -0,0 +1,221 @@ +import rclpy +import cv2 +import mediapipe as mp +from rclpy.node import Node +from media_pipe_ros2_msg.msg import HandPoint,HandPoint,MediaPipeHumanHand,MediaPipeHumanHolisticList +from mediapipe.python.solutions.pose import PoseLandmark +from mediapipe.framework.formats import landmark_pb2 +mp_drawing = mp.solutions.drawing_utils +mp_holistic = mp.solutions.holistic +mp_hands = mp.solutions.hands +mp_pose = mp.solutions.pose +mp_face_mesh = mp.solutions.face_mesh +cap = cv2.VideoCapture(0) + +NAME_POSE = [ + (PoseLandmark.NOSE), (PoseLandmark.LEFT_EYE_INNER), + (PoseLandmark.LEFT_EYE), (PoseLandmark.LEFT_EYE_OUTER), + (PoseLandmark.RIGHT_EYE_INNER), ( PoseLandmark.RIGHT_EYE), + (PoseLandmark.RIGHT_EYE_OUTER), ( PoseLandmark.LEFT_EAR), + (PoseLandmark.RIGHT_EAR), ( PoseLandmark.MOUTH_LEFT), + (PoseLandmark.MOUTH_RIGHT), ( PoseLandmark.LEFT_SHOULDER), + (PoseLandmark.RIGHT_SHOULDER), ( PoseLandmark.LEFT_ELBOW), + (PoseLandmark.RIGHT_ELBOW), ( PoseLandmark.LEFT_WRIST), + (PoseLandmark.RIGHT_WRIST), ( PoseLandmark.LEFT_PINKY), + (PoseLandmark.RIGHT_PINKY), ( PoseLandmark.LEFT_INDEX), + (PoseLandmark.RIGHT_INDEX), ( PoseLandmark.LEFT_THUMB), + (PoseLandmark.RIGHT_THUMB), ( PoseLandmark.LEFT_HIP), + (PoseLandmark.RIGHT_HIP), ( PoseLandmark.LEFT_KNEE), + (PoseLandmark.RIGHT_KNEE), ( PoseLandmark.LEFT_ANKLE), + (PoseLandmark.RIGHT_ANKLE), ( PoseLandmark.LEFT_HEEL), + (PoseLandmark.RIGHT_HEEL), ( PoseLandmark.LEFT_FOOT_INDEX), + (PoseLandmark.RIGHT_FOOT_INDEX) +] +class HolisticPublisher(Node): + + def __init__(self): + super().__init__('mediapipe_publisher_holistic') + self.publisher_ = self.create_publisher(MediaPipeHumanHolisticList, '/mediapipe/human_holistic_list', 10) + + + def getimage_callback(self): + mediapipehumanholisticlist = MediaPipeHumanHolisticList() + mediapipehuman = MediaPipeHumanHand() + points = HandPoint() + drawing_spec = mp_drawing.DrawingSpec(thickness=1, circle_radius=1) + with mp_face_mesh.FaceMesh( + min_detection_confidence=0.5, + min_tracking_confidence=0.5) as face_mesh,mp_hands.Hands( + static_image_mode=False, + min_detection_confidence=0.7, + min_tracking_confidence=0.7, + max_num_hands=2) as hands, mp_pose.Pose( + min_detection_confidence=0.5, + min_tracking_confidence=0.5) as pose: + while cap.isOpened(): + + success, image = cap.read() + if not success: + print("Sem camera.") + + image = cv2.cvtColor(cv2.flip(image, 1), cv2.COLOR_BGR2RGB) + image.flags.writeable = False + results_face_mesh = face_mesh.process(image) + results_pose = pose.process(image) + results_hands= hands.process(image) + image.flags.writeable = True + image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) + imageHeight, imageWidth, _ = image.shape + landmark_temp = [] + #face process + if results_face_mesh.multi_face_landmarks: + index_face_mesh = 0 + + while index_face_mesh <= 467: + + landmark_temp.append(results_face_mesh.multi_face_landmarks[0].landmark[index_face_mesh]) + mediapipehumanholisticlist.human_face_mesh_list[index_face_mesh].x = results_face_mesh.multi_face_landmarks[0].landmark[index_face_mesh].x + mediapipehumanholisticlist.human_face_mesh_list[index_face_mesh].y = results_face_mesh.multi_face_landmarks[0].landmark[index_face_mesh].y + mediapipehumanholisticlist.human_face_mesh_list[index_face_mesh].z = results_face_mesh.multi_face_landmarks[0].landmark[index_face_mesh].z + index_face_mesh = index_face_mesh +1 + + + landmark_subset = landmark_pb2.NormalizedLandmarkList(landmark = landmark_temp) + mp_drawing.draw_landmarks( + image=image, + landmark_list=landmark_subset, + connections=mp_face_mesh.FACE_CONNECTIONS, + landmark_drawing_spec=drawing_spec, + connection_drawing_spec=drawing_spec) + else: # responsavel por mandar 0 nos topicos quando corpo nao esta na tela + index_pose = 0 + for point in mp_pose.PoseLandmark: + + mediapipehumanholisticlist.human_pose_list[index_pose].name = str(NAME_POSE[index_pose]) + mediapipehumanholisticlist.human_pose_list[index_pose].x = 0.0 + mediapipehumanholisticlist.human_pose_list[index_pose].y = 0.0 + mediapipehumanholisticlist.human_pose_list[index_pose].visibility = 0.0 + index_pose = index_pose +1 + + + + #processo pose + # Draw the pose annotation on the image. + image.flags.writeable = True + image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) + mp_drawing.draw_landmarks( + image, results_pose.pose_landmarks, mp_pose.POSE_CONNECTIONS) + if results_pose.pose_landmarks != None: + index_pose = 0 + for pose_landmarks in (results_pose.pose_landmarks.landmark): + + mediapipehumanholisticlist.human_pose_list[index_pose].name = str(NAME_POSE[index_pose]) + mediapipehumanholisticlist.human_pose_list[index_pose].x = pose_landmarks.x + mediapipehumanholisticlist.human_pose_list[index_pose].y = pose_landmarks.y + mediapipehumanholisticlist.human_pose_list[index_pose].visibility = pose_landmarks.visibility + index_pose = index_pose +1 + + mediapipehumanholisticlist.num_humans = 1 + self.publisher_.publish(mediapipehumanholisticlist) + else: # responsavel por mandar 0 nos topicos quando corpo nao esta na tela + index_pose = 0 + for point in mp_pose.PoseLandmark: + print(index_pose) + mediapipehumanholisticlist.human_pose_list[index_pose].name = str(NAME_POSE[index_pose]) + mediapipehumanholisticlist.human_pose_list[index_pose].x = 0.0 + mediapipehumanholisticlist.human_pose_list[index_pose].y = 0.0 + mediapipehumanholisticlist.human_pose_list[index_pose].visibility = 0.0 + index_pose = index_pose +1 + + #HAND PROCESS + + if results_hands.multi_hand_landmarks != None: + + hand_number_screen = 0 # index de controle de quantas maos aparecem na tela + #esse for passa pela quantidades de mão na tela setada como maximo 2 no momento + for hand_landmarks, handedness in zip(results_hands.multi_hand_landmarks,results_hands.multi_handedness): + + if handedness.classification[0].label == "Right": + mp_drawing.draw_landmarks( + image, hand_landmarks, mp_hands.HAND_CONNECTIONS) + index_point = 0 + + for point in mp_hands.HandLandmark: + normalizedLandmark = hand_landmarks.landmark[point] + mediapipehuman.right_hand_key_points[index_point].name = str(point) + mediapipehuman.right_hand_key_points[index_point].x = normalizedLandmark.x + mediapipehuman.right_hand_key_points[index_point].y = normalizedLandmark.y + mediapipehuman.right_hand_key_points[index_point].z = normalizedLandmark.z + if hand_number_screen == 0: + mediapipehuman.left_hand_key_points[index_point].name = str(point) + mediapipehuman.left_hand_key_points[index_point].x = 0.0 + mediapipehuman.left_hand_key_points[index_point].y = 0.0 + mediapipehuman.left_hand_key_points[index_point].z = 0.0 + index_point = index_point +1 + hand_number_screen = hand_number_screen +1 + + elif handedness.classification[0].label == "Left": + mp_drawing.draw_landmarks( + image, hand_landmarks, mp_hands.HAND_CONNECTIONS) + index_point = 0 + + for point in mp_hands.HandLandmark: + + normalizedLandmark = hand_landmarks.landmark[point] + points.name = str(point) + mediapipehuman.left_hand_key_points[index_point].name = str(point) + mediapipehuman.left_hand_key_points[index_point].x = normalizedLandmark.x + mediapipehuman.left_hand_key_points[index_point].y = normalizedLandmark.y + mediapipehuman.left_hand_key_points[index_point].z = normalizedLandmark.z + + if hand_number_screen == 0: + mediapipehuman.right_hand_key_points[index_point].name = str(point) + mediapipehuman.right_hand_key_points[index_point].x = 0.0 + mediapipehuman.right_hand_key_points[index_point].y = 0.0 + mediapipehuman.right_hand_key_points[index_point].z = 0.0 + index_point = index_point +1 + hand_number_screen = hand_number_screen +1 + + + else: # responsavel por mandar 0 nos topicos quando as duas maos nao estao na tela + index_point = 0 + for point in mp_hands.HandLandmark: + + mediapipehuman.right_hand_key_points[index_point].name = str(point) + mediapipehuman.right_hand_key_points[index_point].x = 0.0 + mediapipehuman.right_hand_key_points[index_point].y = 0.0 + mediapipehuman.right_hand_key_points[index_point].z = 0.0 + mediapipehuman.left_hand_key_points[index_point].name = str(point) + mediapipehuman.left_hand_key_points[index_point].x = 0.0 + mediapipehuman.left_hand_key_points[index_point].y = 0.0 + mediapipehuman.left_hand_key_points[index_point].z = 0.0 + index_point = index_point + 1 + + + + + mediapipehumanholisticlist.human_hand_list.right_hand_key_points = mediapipehuman.right_hand_key_points + mediapipehumanholisticlist.human_hand_list.left_hand_key_points = mediapipehuman.left_hand_key_points + mediapipehumanholisticlist.num_humans = 1 + self.publisher_.publish(mediapipehumanholisticlist) + cv2.imshow('MediaPipe Hands', image) + if cv2.waitKey(5) & 0xFF == 27: + break + +def main(args=None): + rclpy.init(args=args) + + holistic_publisher = HolisticPublisher() + holistic_publisher.getimage_callback() + + + cap.release() + + rclpy.spin(holistic_publisher) + + holistic_publisher.destroy_node() + rclpy.shutdown() + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/src/media_pipe_ros2/media_pipe_ros2/pose_detector.py b/src/media_pipe_ros2/media_pipe_ros2/pose_detector.py new file mode 100644 index 0000000..0b543fe --- /dev/null +++ b/src/media_pipe_ros2/media_pipe_ros2/pose_detector.py @@ -0,0 +1,110 @@ +import rclpy +import cv2 +import mediapipe as mp +from rclpy.node import Node +from media_pipe_ros2_msg.msg import MediaPipeHumanPoseList +from mediapipe.python.solutions.pose import PoseLandmark + +mp_drawing = mp.solutions.drawing_utils +mp_pose = mp.solutions.pose +NAME_POSE = [ + (PoseLandmark.NOSE), (PoseLandmark.LEFT_EYE_INNER), + (PoseLandmark.LEFT_EYE), (PoseLandmark.LEFT_EYE_OUTER), + (PoseLandmark.RIGHT_EYE_INNER), ( PoseLandmark.RIGHT_EYE), + (PoseLandmark.RIGHT_EYE_OUTER), ( PoseLandmark.LEFT_EAR), + (PoseLandmark.RIGHT_EAR), ( PoseLandmark.MOUTH_LEFT), + (PoseLandmark.MOUTH_RIGHT), ( PoseLandmark.LEFT_SHOULDER), + (PoseLandmark.RIGHT_SHOULDER), ( PoseLandmark.LEFT_ELBOW), + (PoseLandmark.RIGHT_ELBOW), ( PoseLandmark.LEFT_WRIST), + (PoseLandmark.RIGHT_WRIST), ( PoseLandmark.LEFT_PINKY), + (PoseLandmark.RIGHT_PINKY), ( PoseLandmark.LEFT_INDEX), + (PoseLandmark.RIGHT_INDEX), ( PoseLandmark.LEFT_THUMB), + (PoseLandmark.RIGHT_THUMB), ( PoseLandmark.LEFT_HIP), + (PoseLandmark.RIGHT_HIP), ( PoseLandmark.LEFT_KNEE), + (PoseLandmark.RIGHT_KNEE), ( PoseLandmark.LEFT_ANKLE), + (PoseLandmark.RIGHT_ANKLE), ( PoseLandmark.LEFT_HEEL), + (PoseLandmark.RIGHT_HEEL), ( PoseLandmark.LEFT_FOOT_INDEX), + (PoseLandmark.RIGHT_FOOT_INDEX) +] +cap = cv2.VideoCapture(0) + +class PosePublisher(Node): + + def __init__(self): + super().__init__('mediapipe_pose_publisher') + self.publisher_ = self.create_publisher(MediaPipeHumanPoseList, '/mediapipe/human_pose_list', 10) + + + def getimage_callback(self): + mediapipehumanposelist = MediaPipeHumanPoseList() + + + with mp_pose.Pose( + min_detection_confidence=0.5, + min_tracking_confidence=0.5) as pose: + while cap.isOpened(): + + success, image = cap.read() + if not success: + print("Sem camera.") + + image = cv2.cvtColor(cv2.flip(image, 1), cv2.COLOR_BGR2RGB) + image.flags.writeable = False + results = pose.process(image) + image.flags.writeable = True + image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) + imageHeight, imageWidth, _ = image.shape + + # Draw the pose annotation on the image. + image.flags.writeable = True + image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) + mp_drawing.draw_landmarks( + image, results.pose_landmarks, mp_pose.POSE_CONNECTIONS) + + + if results.pose_landmarks != None: + index_pose = 0 + for pose_landmarks in (results.pose_landmarks.landmark): + print(index_pose) + mediapipehumanposelist.human_pose_list[index_pose].name = str(NAME_POSE[index_pose]) + mediapipehumanposelist.human_pose_list[index_pose].x = pose_landmarks.x + mediapipehumanposelist.human_pose_list[index_pose].y = pose_landmarks.y + mediapipehumanposelist.human_pose_list[index_pose].visibility = pose_landmarks.visibility + index_pose = index_pose +1 + + mediapipehumanposelist.num_humans = 1 + self.publisher_.publish(mediapipehumanposelist) + else: # responsavel por mandar 0 nos topicos quando corpo nao esta na tela + index_pose = 0 + for point in mp_pose.PoseLandmark: + + mediapipehumanposelist.human_pose_list[index_pose].name = str(NAME_POSE[index_pose]) + mediapipehumanposelist.human_pose_list[index_pose].x = 0.0 + mediapipehumanposelist.human_pose_list[index_pose].y = 0.0 + mediapipehumanposelist.human_pose_list[index_pose].visibility = 0.0 + index_pose = index_pose +1 + + + mediapipehumanposelist.num_humans = 1 + self.publisher_.publish(mediapipehumanposelist) + + cv2.imshow('MediaPipe Pose', image) + if cv2.waitKey(5) & 0xFF == 27: + break + +def main(args=None): + rclpy.init(args=args) + + pose_publisher = PosePublisher() + pose_publisher.getimage_callback() + + cap.release() + + rclpy.spin(pose_publisher) + + pose_publisher.destroy_node() + rclpy.shutdown() + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/src/media_pipe_ros2/media_pipe_ros2/teste.py b/src/media_pipe_ros2/media_pipe_ros2/teste.py new file mode 100644 index 0000000..4b22a59 --- /dev/null +++ b/src/media_pipe_ros2/media_pipe_ros2/teste.py @@ -0,0 +1,47 @@ +import cv2 +import mediapipe as mp +mp_drawing = mp.solutions.drawing_utils +mp_pose = mp.solutions.pose +mp_hands = mp.solutions.hands + +# For webcam input: +cap = cv2.VideoCapture(0) +with mp_pose.Pose( + min_detection_confidence=0.5, + min_tracking_confidence=0.5) as pose, mp_hands.Hands( + min_detection_confidence=0.5, + min_tracking_confidence=0.5) as hands: + while cap.isOpened(): + success, image = cap.read() + if not success: + print("Ignoring empty camera frame.") + # If loading a video, use 'break' instead of 'continue'. + continue + + # Flip the image horizontally for a later selfie-view display, and convert + # the BGR image to RGB. + image = cv2.cvtColor(cv2.flip(image, 1), cv2.COLOR_BGR2RGB) + # To improve performance, optionally mark the image as not writeable to + # pass by reference. + image.flags.writeable = False + + + results = pose.process(image) + results_hand = hands.process(image) + # Draw the pose annotation on the image. + image.flags.writeable = True + image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) + mp_drawing.draw_landmarks( + image, results.pose_landmarks, mp_pose.POSE_CONNECTIONS) + + # Draw the hand annotations on the image. + image.flags.writeable = True + image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) + if results_hand.multi_hand_landmarks: + for hand_landmarks in results_hand.multi_hand_landmarks: + mp_drawing.draw_landmarks( + image, hand_landmarks, mp_hands.HAND_CONNECTIONS) + cv2.imshow('MediaPipe Pose', image) + if cv2.waitKey(5) & 0xFF == 27: + break +cap.release() diff --git a/src/media_pipe_ros2/package.xml b/src/media_pipe_ros2/package.xml new file mode 100644 index 0000000..394e3c7 --- /dev/null +++ b/src/media_pipe_ros2/package.xml @@ -0,0 +1,18 @@ + + + + media_pipe_ros2 + 0.0.0 + Package responsible for using the media pipe in ros2 + Dieisson Martinelli + TODO: License declaration + + ament_copyright + ament_flake8 + ament_pep257 + python3-pytest + + + ament_python + + diff --git a/src/media_pipe_ros2/resource/media_pipe_ros2 b/src/media_pipe_ros2/resource/media_pipe_ros2 new file mode 100644 index 0000000..e69de29 diff --git a/src/media_pipe_ros2/setup.cfg b/src/media_pipe_ros2/setup.cfg new file mode 100644 index 0000000..b15eb71 --- /dev/null +++ b/src/media_pipe_ros2/setup.cfg @@ -0,0 +1,4 @@ +[develop] +script_dir=$base/lib/media_pipe_ros2 +[install] +install_scripts=$base/lib/media_pipe_ros2 diff --git a/src/media_pipe_ros2/setup.py b/src/media_pipe_ros2/setup.py new file mode 100644 index 0000000..f6d5498 --- /dev/null +++ b/src/media_pipe_ros2/setup.py @@ -0,0 +1,29 @@ +from setuptools import setup + +package_name = 'media_pipe_ros2' + +setup( + name=package_name, + version='0.0.0', + packages=[package_name], + data_files=[ + ('share/ament_index/resource_index/packages', + ['resource/' + package_name]), + ('share/' + package_name, ['package.xml']), + ], + install_requires=['setuptools'], + zip_safe=True, + maintainer='Dieisson Martinelli', + maintainer_email='dmartinelli1997@gmail.com', + description='Package responsible for using the media pipe in ros2', + license='TODO: License declaration', + tests_require=['pytest'], + entry_points={ + 'console_scripts': [ + 'hands_detector = media_pipe_ros2.hands_detector:main', + 'pose_detector = media_pipe_ros2.pose_detector:main', + 'face_mesh_detector = media_pipe_ros2.face_mesh_detector:main', + 'holistic_detector = media_pipe_ros2.holistic_detector:main', + ], + }, +) diff --git a/src/media_pipe_ros2/test/test_copyright.py b/src/media_pipe_ros2/test/test_copyright.py new file mode 100644 index 0000000..cc8ff03 --- /dev/null +++ b/src/media_pipe_ros2/test/test_copyright.py @@ -0,0 +1,23 @@ +# Copyright 2015 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_copyright.main import main +import pytest + + +@pytest.mark.copyright +@pytest.mark.linter +def test_copyright(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found errors' diff --git a/src/media_pipe_ros2/test/test_flake8.py b/src/media_pipe_ros2/test/test_flake8.py new file mode 100644 index 0000000..27ee107 --- /dev/null +++ b/src/media_pipe_ros2/test/test_flake8.py @@ -0,0 +1,25 @@ +# Copyright 2017 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_flake8.main import main_with_errors +import pytest + + +@pytest.mark.flake8 +@pytest.mark.linter +def test_flake8(): + rc, errors = main_with_errors(argv=[]) + assert rc == 0, \ + 'Found %d code style errors / warnings:\n' % len(errors) + \ + '\n'.join(errors) diff --git a/src/media_pipe_ros2/test/test_pep257.py b/src/media_pipe_ros2/test/test_pep257.py new file mode 100644 index 0000000..b234a38 --- /dev/null +++ b/src/media_pipe_ros2/test/test_pep257.py @@ -0,0 +1,23 @@ +# Copyright 2015 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_pep257.main import main +import pytest + + +@pytest.mark.linter +@pytest.mark.pep257 +def test_pep257(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found code style errors / warnings' diff --git a/src/media_pipe_ros2_msg/CMakeLists.txt b/src/media_pipe_ros2_msg/CMakeLists.txt new file mode 100644 index 0000000..d3d4cb9 --- /dev/null +++ b/src/media_pipe_ros2_msg/CMakeLists.txt @@ -0,0 +1,37 @@ +cmake_minimum_required(VERSION 3.8) +project(media_pipe_ros2_msg) + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wall -Wextra -Wpedantic) +endif() + +# find dependencies +find_package(ament_cmake REQUIRED) +find_package(rosidl_default_generators REQUIRED) + +rosidl_generate_interfaces(${PROJECT_NAME} + "msg/HandPoint.msg" + "msg/MediaPipeHumanHand.msg" + "msg/MediaPipeHumanHandList.msg" + "msg/PosePoint.msg" + "msg/MediaPipeHumanPoseList.msg" + "msg/FaceMeshPoint.msg" + "msg/MediaPipeHumanFaceMeshList.msg" + "msg/MediaPipeHumanHolisticList.msg" + ) +# uncomment the following section in order to fill in +# further dependencies manually. +# find_package( REQUIRED) + +if(BUILD_TESTING) + find_package(ament_lint_auto REQUIRED) + # the following line skips the linter which checks for copyrights + # uncomment the line when a copyright and license is not present in all source files + #set(ament_cmake_copyright_FOUND TRUE) + # the following line skips cpplint (only works in a git repo) + # uncomment the line when this package is not in a git repo + #set(ament_cmake_cpplint_FOUND TRUE) + ament_lint_auto_find_test_dependencies() +endif() + +ament_package() diff --git a/src/media_pipe_ros2_msg/msg/FaceMeshPoint.msg b/src/media_pipe_ros2_msg/msg/FaceMeshPoint.msg new file mode 100644 index 0000000..681257d --- /dev/null +++ b/src/media_pipe_ros2_msg/msg/FaceMeshPoint.msg @@ -0,0 +1,3 @@ +float64 x +float64 y +float64 z \ No newline at end of file diff --git a/src/media_pipe_ros2_msg/msg/HandPoint.msg b/src/media_pipe_ros2_msg/msg/HandPoint.msg new file mode 100644 index 0000000..355ca9b --- /dev/null +++ b/src/media_pipe_ros2_msg/msg/HandPoint.msg @@ -0,0 +1,4 @@ +string name +float64 x +float64 y +float64 z \ No newline at end of file diff --git a/src/media_pipe_ros2_msg/msg/MediaPipeHumanFaceMeshList.msg b/src/media_pipe_ros2_msg/msg/MediaPipeHumanFaceMeshList.msg new file mode 100644 index 0000000..f30ec5a --- /dev/null +++ b/src/media_pipe_ros2_msg/msg/MediaPipeHumanFaceMeshList.msg @@ -0,0 +1,2 @@ +int32 num_humans +FaceMeshPoint[468] human_face_mesh_list \ No newline at end of file diff --git a/src/media_pipe_ros2_msg/msg/MediaPipeHumanHand.msg b/src/media_pipe_ros2_msg/msg/MediaPipeHumanHand.msg new file mode 100644 index 0000000..47b8e81 --- /dev/null +++ b/src/media_pipe_ros2_msg/msg/MediaPipeHumanHand.msg @@ -0,0 +1,2 @@ +HandPoint[21] right_hand_key_points +HandPoint[21] left_hand_key_points \ No newline at end of file diff --git a/src/media_pipe_ros2_msg/msg/MediaPipeHumanHandList.msg b/src/media_pipe_ros2_msg/msg/MediaPipeHumanHandList.msg new file mode 100644 index 0000000..2343c94 --- /dev/null +++ b/src/media_pipe_ros2_msg/msg/MediaPipeHumanHandList.msg @@ -0,0 +1,2 @@ +int32 num_humans +MediaPipeHumanHand human_hand_list \ No newline at end of file diff --git a/src/media_pipe_ros2_msg/msg/MediaPipeHumanHolisticList.msg b/src/media_pipe_ros2_msg/msg/MediaPipeHumanHolisticList.msg new file mode 100644 index 0000000..0a3c263 --- /dev/null +++ b/src/media_pipe_ros2_msg/msg/MediaPipeHumanHolisticList.msg @@ -0,0 +1,4 @@ +int32 num_humans +MediaPipeHumanHand human_hand_list +FaceMeshPoint[468] human_face_mesh_list +PosePoint[33] human_pose_list \ No newline at end of file diff --git a/src/media_pipe_ros2_msg/msg/MediaPipeHumanPoseList.msg b/src/media_pipe_ros2_msg/msg/MediaPipeHumanPoseList.msg new file mode 100644 index 0000000..beffcaa --- /dev/null +++ b/src/media_pipe_ros2_msg/msg/MediaPipeHumanPoseList.msg @@ -0,0 +1,2 @@ +int32 num_humans +PosePoint[33] human_pose_list \ No newline at end of file diff --git a/src/media_pipe_ros2_msg/msg/PosePoint.msg b/src/media_pipe_ros2_msg/msg/PosePoint.msg new file mode 100644 index 0000000..81c8b6d --- /dev/null +++ b/src/media_pipe_ros2_msg/msg/PosePoint.msg @@ -0,0 +1,5 @@ +string name +float64 x +float64 y +float64 z +float64 visibility \ No newline at end of file diff --git a/src/media_pipe_ros2_msg/package.xml b/src/media_pipe_ros2_msg/package.xml new file mode 100644 index 0000000..74955e5 --- /dev/null +++ b/src/media_pipe_ros2_msg/package.xml @@ -0,0 +1,21 @@ + + + + media_pipe_ros2_msg + 0.0.0 + TODO: Package description + ros2 + TODO: License declaration + + ament_cmake + + ament_lint_auto + ament_lint_common + rosidl_default_generators + rosidl_default_runtime + rosidl_interface_packages + + + ament_cmake + + diff --git a/src/openarm_camera/launch/launch_camera.launch.py b/src/openarm_camera/launch/launch_camera.launch.py new file mode 100644 index 0000000..122cbb0 --- /dev/null +++ b/src/openarm_camera/launch/launch_camera.launch.py @@ -0,0 +1,12 @@ +from launch import LaunchDescription +from launch_ros.actions import Node + +def generate_launch_description(): + stereo_proc = Node( + package='stereo_image_proc', + executable='stereo_image_proc', + output='screen' + ) + return LaunchDescription([ + stereo_proc + ]) diff --git a/src/openarm_camera/openarm_camera/__init__.py b/src/openarm_camera/openarm_camera/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/openarm_camera/openarm_camera/camera.py b/src/openarm_camera/openarm_camera/camera.py new file mode 100644 index 0000000..8b0f279 --- /dev/null +++ b/src/openarm_camera/openarm_camera/camera.py @@ -0,0 +1,130 @@ +import rclpy +from rclpy.node import Node + +import cv2 +import numpy as np + +from sensor_msgs.msg import Image +from geometry_msgs.msg import PointStamped +from cv_bridge import CvBridge + + +class StereoHandDepthNode(Node): + + def __init__(self): + super().__init__('stereo_hand_depth_node') + + # ---------- 参数 ---------- + self.fx = 600.0 # 焦距(像素)👉 后面用标定值替换 + self.fy = 600.0 + self.cx = 320.0 + self.cy = 240.0 + self.baseline = 0.06 # 双目基线(米)👉 换成你真实的 + + # ---------- OpenCV ---------- + self.bridge = CvBridge() + + self.stereo = cv2.StereoSGBM_create( + minDisparity=0, + numDisparities=128, + blockSize=7, + P1=8 * 3 * 7 ** 2, + P2=32 * 3 * 7 ** 2, + disp12MaxDiff=1, + uniquenessRatio=10, + speckleWindowSize=100, + speckleRange=32 + ) + + # ---------- ROS ---------- + # self.left = self.create_subscription( + # Image, + # '/camera/left/image_raw', + # self.image_left_callback, + # 10 + # ) + # self.right = self.create_subscription( + # Image, + # '/camera/right/image_raw', + # self.image_right_callback, + # 10 + # ) + self.image = self.create_subscription( + Image, + '/image', + self.image_callback, + 10 + ) + self.pub = self.create_publisher( + PointStamped, + '/hand_3d', + 10 + ) + + self.get_logger().info('StereoHandDepthNode started') + + def image_callback(self, msg: Image): + # 转 OpenCV + frame = self.bridge.imgmsg_to_cv2(msg, desired_encoding='bgr8') + + h, w, _ = frame.shape + mid = w // 2 + + # 左右拆分 + left = frame[:, :mid] + right = frame[:, mid:] + cv2.imshow("left", left) + cv2.imshow("right", right) + + # 转灰度 + left_gray = cv2.cvtColor(left, cv2.COLOR_BGR2GRAY) + right_gray = cv2.cvtColor(right, cv2.COLOR_BGR2GRAY) + + # 计算视差 + disparity = self.stereo.compute(left_gray, right_gray).astype(np.float32) / 16.0 + + # ----------- 这里先用“假手点”(画面中心)----------- + x = int(self.cx) + y = int(self.cy) + + # ROI 取中值,稳很多 + roi = disparity[y-3:y+3, x-3:x+3] + d = np.nanmedian(roi) + + if d <= 0: + return + + # 深度 + Z = (self.fx * self.baseline) / d + X = (x - self.cx) * Z / self.fx + Y = (y - self.cy) * Z / self.fy + + # 发布 + msg_out = PointStamped() + msg_out.header.stamp = self.get_clock().now().to_msg() + msg_out.header.frame_id = 'camera_link' + msg_out.point.x = float(X) + msg_out.point.y = float(Y) + msg_out.point.z = float(Z) + + self.pub.publish(msg_out) + + # ----------- 可视化(调试用)----------- + disp_vis = cv2.normalize(disparity, None, 0, 255, cv2.NORM_MINMAX) + disp_vis = disp_vis.astype(np.uint8) + cv2.circle(disp_vis, (x, y), 5, 255, -1) + + cv2.imshow('disparity', disp_vis) + cv2.waitKey(10) + + +def main(args=None): + rclpy.init(args=args) + node = StereoHandDepthNode() + rclpy.spin(node) + node.destroy_node() + rclpy.shutdown() + + +if __name__ == '__main__': + main() diff --git a/src/openarm_camera/openarm_camera/camera_pub.py b/src/openarm_camera/openarm_camera/camera_pub.py new file mode 100644 index 0000000..10db094 --- /dev/null +++ b/src/openarm_camera/openarm_camera/camera_pub.py @@ -0,0 +1,75 @@ +import rclpy +from rclpy.node import Node +from sensor_msgs.msg import Image +from cv_bridge import CvBridge +import cv2 +import numpy as np + +class CameraNode(Node): + def __init__(self): + super().__init__('camera_node') + + self.bridge = CvBridge() + self.publisher_left = self.create_publisher(Image, '/camera/left/image_raw', 10) + self.publisher_right = self.create_publisher(Image, '/camera/right/image_raw', 10) + self.publisher_raw = self.create_publisher(Image, '/image', 10) + self.get_logger().info('正在初始化摄像头...') + + + # 尝试打开物理摄像头 + self.cap = None + for i in range(1): # 尝试设备0,1,2 + self.get_logger().info(f'尝试打开摄像头设备 /dev/video{i}...') + self.cap = cv2.VideoCapture(i) + if self.cap.isOpened(): + # 设置摄像头参数 + self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1600) + self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 600) + self.cap.set(cv2.CAP_PROP_FPS, 30) + + # 让摄像头预热 + for _ in range(5): + self.cap.read() + + # 验证摄像头能正常读取 + ret, frame = self.cap.read() + if ret and frame is not None: + self.get_logger().info(f'成功打开物理摄像头 /dev/video{i}') + self.get_logger().info(f'图像尺寸: {frame.shape}') + break + + + self.timer = self.create_timer(0.033, self.timer_callback) # 30Hz + self.get_logger().info('摄像头节点已启动') + + def timer_callback(self): + ret, frame = self.cap.read() + if ret and frame is not None: + # frameDist = cv2.rotate(frame, cv2.ROTATE_90_COUNTERCLOCKWISE) + # cv2.imshow("camera_left", frame[:, :800]) + # cv2.imshow("camera_right", frame[:, 800:]) + cv2.waitKey(10) + msg_left= self.bridge.cv2_to_imgmsg(frame[:, :800], 'bgr8') + msg_right= self.bridge.cv2_to_imgmsg(frame[:, 800:], 'bgr8') + msg_raw= self.bridge.cv2_to_imgmsg(frame, 'bgr8') + msg_left.header.stamp = self.get_clock().now().to_msg() + msg_right.header.stamp = self.get_clock().now().to_msg() + msg_raw.header.stamp = self.get_clock().now().to_msg() + msg_left.header.frame_id = 'camera_frame_left' + msg_right.header.frame_id = 'camera_frame_right' + msg_raw.header.frame_id = 'camera_frame_raw' + self.publisher_left.publish(msg_left) + self.publisher_right.publish(msg_right) + self.publisher_raw.publish(msg_raw) + + + +def main(): + rclpy.init() + node = CameraNode() + rclpy.spin(node) + node.destroy_node() + rclpy.shutdown() + +if __name__ == '__main__': + main() diff --git a/src/openarm_camera/package.xml b/src/openarm_camera/package.xml new file mode 100644 index 0000000..c3e07f8 --- /dev/null +++ b/src/openarm_camera/package.xml @@ -0,0 +1,18 @@ + + + + openarm_camera + 0.0.0 + TODO: Package description + shen + TODO: License declaration + + ament_copyright + ament_flake8 + ament_pep257 + python3-pytest + + + ament_python + + diff --git a/src/openarm_camera/resource/openarm_camera b/src/openarm_camera/resource/openarm_camera new file mode 100644 index 0000000..e69de29 diff --git a/src/openarm_camera/setup.cfg b/src/openarm_camera/setup.cfg new file mode 100644 index 0000000..ca0fac6 --- /dev/null +++ b/src/openarm_camera/setup.cfg @@ -0,0 +1,4 @@ +[develop] +script_dir=$base/lib/openarm_camera +[install] +install_scripts=$base/lib/openarm_camera diff --git a/src/openarm_camera/setup.py b/src/openarm_camera/setup.py new file mode 100644 index 0000000..e2ad27c --- /dev/null +++ b/src/openarm_camera/setup.py @@ -0,0 +1,36 @@ +from setuptools import find_packages, setup +import os +from glob import glob + +package_name = 'openarm_camera' + +setup( + name=package_name, + version='0.0.0', + packages=find_packages(exclude=['test']), + data_files=[ + ('share/ament_index/resource_index/packages', + ['resource/' + package_name]), + ('share/' + package_name, ['package.xml']), + # launch 文件 + (os.path.join('share', package_name, 'launch'), + glob('launch/*.launch.py')), + ], + install_requires=['setuptools'], + zip_safe=True, + maintainer='shen', + maintainer_email='shen@todo.todo', + description='TODO: Package description', + license='TODO: License declaration', + extras_require={ + 'test': [ + 'pytest', + ], + }, + entry_points={ + 'console_scripts': [ + 'camera_node = openarm_camera.camera:main', + 'camera_pub_node = openarm_camera.camera_pub:main', + ], + }, +) diff --git a/src/openarm_camera/test/test_copyright.py b/src/openarm_camera/test/test_copyright.py new file mode 100644 index 0000000..97a3919 --- /dev/null +++ b/src/openarm_camera/test/test_copyright.py @@ -0,0 +1,25 @@ +# Copyright 2015 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_copyright.main import main +import pytest + + +# Remove the `skip` decorator once the source file(s) have a copyright header +@pytest.mark.skip(reason='No copyright header has been placed in the generated source file.') +@pytest.mark.copyright +@pytest.mark.linter +def test_copyright(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found errors' diff --git a/src/openarm_camera/test/test_flake8.py b/src/openarm_camera/test/test_flake8.py new file mode 100644 index 0000000..27ee107 --- /dev/null +++ b/src/openarm_camera/test/test_flake8.py @@ -0,0 +1,25 @@ +# Copyright 2017 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_flake8.main import main_with_errors +import pytest + + +@pytest.mark.flake8 +@pytest.mark.linter +def test_flake8(): + rc, errors = main_with_errors(argv=[]) + assert rc == 0, \ + 'Found %d code style errors / warnings:\n' % len(errors) + \ + '\n'.join(errors) diff --git a/src/openarm_camera/test/test_pep257.py b/src/openarm_camera/test/test_pep257.py new file mode 100644 index 0000000..b234a38 --- /dev/null +++ b/src/openarm_camera/test/test_pep257.py @@ -0,0 +1,23 @@ +# Copyright 2015 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_pep257.main import main +import pytest + + +@pytest.mark.linter +@pytest.mark.pep257 +def test_pep257(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found code style errors / warnings' diff --git a/src/openarm_moveit_control/openarm_moveit_control/__init__.py b/src/openarm_moveit_control/openarm_moveit_control/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/openarm_moveit_control/openarm_moveit_control/hand_follow_moveit.py b/src/openarm_moveit_control/openarm_moveit_control/hand_follow_moveit.py new file mode 100644 index 0000000..0877a0e --- /dev/null +++ b/src/openarm_moveit_control/openarm_moveit_control/hand_follow_moveit.py @@ -0,0 +1,228 @@ +import rclpy +from rclpy.node import Node +from rclpy.action import ActionClient +import numpy as np +import time +import cv2 + +from moveit_msgs.action import MoveGroup +from moveit_msgs.msg import Constraints, PositionConstraint +from geometry_msgs.msg import PoseStamped +from shape_msgs.msg import SolidPrimitive + +data = np.load("/home/shen/openarm_test/src/openarm_moveit_control/openarm_moveit_control/stereo_params_1920x1080.npz") +mtxL = data["mtxL"] +distL = data["distL"] +mtxR = data["mtxR"] +distR = data["distR"] +R = data["R"] +T = data["T"] +image_size = tuple(data["image_size"]) + +handey_cal = np.load("/home/shen/openarm_test/handeye_data.npz") + +R1, R2, P1, P2, Q0, roi1, roi2 = cv2.stereoRectify( + mtxL, distL, + mtxR, distR, + image_size, + R, T, + flags=cv2.CALIB_ZERO_DISPARITY, + alpha=0 +) + +mapL1, mapL2 = cv2.initUndistortRectifyMap( + mtxL, distL, R1, P1, image_size, cv2.CV_32FC1 +) +mapR1, mapR2 = cv2.initUndistortRectifyMap( + mtxR, distR, R2, P2, image_size, cv2.CV_32FC1 +) +print("Q:", Q0) + +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.cap = cv2.VideoCapture(0) # ← 改成你的相机 ID + self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, 2560) + self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720) + self.cap.set(cv2.CAP_PROP_FPS, 30) + self.cap.set(cv2.CAP_PROP_EXPOSURE, -8) + + # ========= ArUco ========= + self.aruco_dict = cv2.aruco.getPredefinedDictionary( + cv2.aruco.DICT_4X4_50 + ) + self.aruco_params = cv2.aruco.DetectorParameters() + # ========= 双目标定参数(必须填) ========= + # 方法 A:Q 矩阵(推荐) + self.Q = Q0 # ← TODO: 填你的 4x4 Q 矩阵 + self.create_timer(0.5, self.spin) + # ============================== + # 手眼标定求解 + # ============================== + def solve_handeye(self, cam, base): + cA = cam.mean(axis=0) + cB = base.mean(axis=0) + AA = cam - cA + BB = base - cB + + U, _, Vt = np.linalg.svd(AA.T @ BB) + R = Vt.T @ U.T + if np.linalg.det(R) < 0: + Vt[2] *= -1 + R = Vt.T @ U.T + + t = cB - R @ cA + return R, t + + def split_stereo(self, frame): + """2560x720 → 左右 1280x720""" + h, w, _ = frame.shape + left = frame[:, :w//2] + right = frame[:, w//2:] + return left, right + + def compute_disparity(self, left, right): + gray_l = cv2.cvtColor(left, cv2.COLOR_BGR2GRAY) + gray_r = cv2.cvtColor(right, cv2.COLOR_BGR2GRAY) + + stereo = cv2.StereoSGBM_create( + minDisparity=0, + numDisparities=128, + blockSize=7 + ) + disp = stereo.compute(gray_l, gray_r).astype(np.float32) / 16.0 + return disp + + def pixel_to_3d(self, u, v, disparity): + """ + 像素 + 视差 → 3D(camera_link) + """ + if self.Q is not None: + point_4d = np.array([u, v, disparity, 1.0]) + X = self.Q @ point_4d + X /= X[3] + return X[:3] + + 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 + + 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 get_hand_3d(self): + return None + + def spin(self): + + # frame_count = 0 + while rclpy.ok(): + ret, frame = self.cap.read() + if not ret: + continue + + left, right = self.split_stereo(frame) + rectL = cv2.remap(left, mapL1, mapL2, cv2.INTER_LINEAR) + rectR = cv2.remap(right, mapR1, mapR2, cv2.INTER_LINEAR) + # frame_count += 1 + # if frame_count % 3 == 0: + # small_l = cv2.resize(rectL, None, fx=0.5, fy=0.5) + # small_r = cv2.resize(rectR, None, fx=0.5, fy=0.5) + disp = self.compute_disparity(rectL, rectR) + gray = cv2.cvtColor(rectL, cv2.COLOR_BGR2GRAY) + corners, ids, _ = cv2.aruco.detectMarkers( + gray, self.aruco_dict, parameters=self.aruco_params + ) + + if ids is not None: + cv2.aruco.drawDetectedMarkers(gray, corners, ids) + c = corners[0][0] + center = c.mean(axis=0) + u, v = int(center[0]), int(center[1]) + d = disp[v, u] + + cv2.circle(rectL, (u, v), 5, (0, 0, 255), -1) + cv2.putText( + rectL, f"disp={d:.2f}", (u+5, v-5), + cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0), 1 + ) + self.solve_handeye(handey_cal["cam"], handey_cal["base"]) + + cv2.imshow("left", rectL) + # self.check() + key = cv2.waitKey(1) + + +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() \ No newline at end of file diff --git a/src/openarm_moveit_control/openarm_moveit_control/handeye_collect.py b/src/openarm_moveit_control/openarm_moveit_control/handeye_collect.py new file mode 100644 index 0000000..7042120 --- /dev/null +++ b/src/openarm_moveit_control/openarm_moveit_control/handeye_collect.py @@ -0,0 +1,249 @@ +#!/usr/bin/env python3 +import rclpy +from rclpy.node import Node +import numpy as np +import cv2 +import tf2_ros +import time +from rclpy.time import Time + +""" +============================== +手眼标定数据采集节点(完整版本) +Eye-to-Hand +============================== +""" +data = np.load("/home/shen/openarm/openarm_test/src/openarm_moveit_control/openarm_moveit_control/stereo_params_1920x1080.npz") +mtxL = data["mtxL"] +distL = data["distL"] +mtxR = data["mtxR"] +distR = data["distR"] +R = data["R"] +T = data["T"] +image_size = tuple(data["image_size"]) + +R1, R2, P1, P2, Q0, roi1, roi2 = cv2.stereoRectify( + mtxL, distL, + mtxR, distR, + image_size, + R, T, + flags=cv2.CALIB_ZERO_DISPARITY, + alpha=0 +) + +mapL1, mapL2 = cv2.initUndistortRectifyMap( + mtxL, distL, R1, P1, image_size, cv2.CV_32FC1 +) +mapR1, mapR2 = cv2.initUndistortRectifyMap( + mtxR, distR, R2, P2, image_size, cv2.CV_32FC1 +) +print("Q:", Q0) + +class HandEyeCollector(Node): + + def __init__(self): + super().__init__('handeye_collector') + + # ========= TF ========= + self.tf_buffer = tf2_ros.Buffer() + self.tf_listener = tf2_ros.TransformListener(self.tf_buffer, self) + + # ========= 相机 ========= + self.cap = cv2.VideoCapture(15) # ← 改成你的相机 ID + self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, 2560) + self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720) + self.cap.set(cv2.CAP_PROP_FPS, 30) + self.cap.set(cv2.CAP_PROP_EXPOSURE, -8) + + + # ========= ArUco ========= + self.aruco_dict = cv2.aruco.getPredefinedDictionary( + cv2.aruco.DICT_4X4_50 + ) + self.aruco_params = cv2.aruco.DetectorParameters() + + # ========= 数据 ========= + self.cam_points = [] + self.base_points = [] + + # ========= 双目标定参数(必须填) ========= + # 方法 A:Q 矩阵(推荐) + self.Q = Q0 # ← TODO: 填你的 4x4 Q 矩阵 + + # 方法 B:手动参数(二选一) + self.fx = None + self.fy = None + self.cx = None + self.cy = None + self.baseline = None # 单位:米 + self.get_logger().info( + "\nHand-eye collector ready\n" + "ENTER: save sample\n" + "ESC: save & quit\n" + ) + self.create_timer(0.5, self.spin) + # self.spin() + + # =============================== + # 工具函数 + # =============================== + def check(self): + try: + tf = self.tf_buffer.lookup_transform( + 'openarm_body_link0', + 'openarm_left_link7', + Time() + ) + print("Transform:", tf) + self.get_logger().info("TF OK") + except: + self.get_logger().warn("TF not available yet") + return None + + def get_tcp_position(self): + try: + # if not self.tf_buffer.can_transform( + # 'openarm_body_link0', + # 'openarm_left_link7', + # Time() + # ): + # self.get_logger().warn("TF not available yet") + # return None + + t = self.tf_buffer.lookup_transform( + 'openarm_body_link0', + 'openarm_left_link7', + Time() + ) + print("Transform:", t) + return np.array([ + t.transform.translation.x, + t.transform.translation.y, + t.transform.translation.z + ]) + except Exception as e: + self.get_logger().warn(f"TF lookup failed: {str(e)}") + return None + + + def split_stereo(self, frame): + """2560x720 → 左右 1280x720""" + h, w, _ = frame.shape + left = frame[:, :w//2] + right = frame[:, w//2:] + return left, right + + def compute_disparity(self, left, right): + gray_l = cv2.cvtColor(left, cv2.COLOR_BGR2GRAY) + gray_r = cv2.cvtColor(right, cv2.COLOR_BGR2GRAY) + + stereo = cv2.StereoSGBM_create( + minDisparity=0, + numDisparities=128, + blockSize=7 + ) + disp = stereo.compute(gray_l, gray_r).astype(np.float32) / 16.0 + return disp + + def pixel_to_3d(self, u, v, disparity): + """ + 像素 + 视差 → 3D(camera_link) + """ + if self.Q is not None: + point_4d = np.array([u, v, disparity, 1.0]) + X = self.Q @ point_4d + X /= X[3] + return X[:3] + + # =============================== + # 主循环 + # =============================== + + def spin(self): + + # frame_count = 0 + while rclpy.ok(): + ret, frame = self.cap.read() + if not ret: + continue + + left, right = self.split_stereo(frame) + rectL = cv2.remap(left, mapL1, mapL2, cv2.INTER_LINEAR) + rectR = cv2.remap(right, mapR1, mapR2, cv2.INTER_LINEAR) + # frame_count += 1 + # if frame_count % 3 == 0: + small_l = cv2.resize(rectL, None, fx=0.5, fy=0.5) + small_r = cv2.resize(rectR, None, fx=0.5, fy=0.5) + disp = self.compute_disparity(small_l, small_r) + gray = cv2.cvtColor(rectL, cv2.COLOR_BGR2GRAY) + corners, ids, _ = cv2.aruco.detectMarkers( + gray, self.aruco_dict, parameters=self.aruco_params + ) + + if ids is not None: + cv2.aruco.drawDetectedMarkers(gray, corners, ids) + c = corners[0][0] + center = c.mean(axis=0) + u, v = int(center[0]), int(center[1]) + d = disp[v, u] + + cv2.circle(rectL, (u, v), 5, (0, 0, 255), -1) + cv2.putText( + rectL, f"disp={d:.2f}", (u+5, v-5), + cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0), 1 + ) + + cv2.imshow("left", rectL) + # self.check() + key = cv2.waitKey(1) + + # ENTER:保存 + if key == 13 and ids is not None: + cam_p = self.pixel_to_3d(u, v, d) + print("cam_p:", cam_p) + base_p = self.get_tcp_position() + if cam_p is None or base_p is None: + self.get_logger().warn("Invalid sample") + continue + + self.cam_points.append(cam_p) + self.base_points.append(base_p) + + self.get_logger().info( + f"Saved #{len(self.cam_points)} " + f"cam={cam_p.round(3)} " + f"base={base_p.round(3)}" + ) + time.sleep(0.3) + + # ESC:退出 + elif key == 27: + break + + self.save_and_exit() + + def save_and_exit(self): + np.savez( + "handeye_data.npz", + cam=np.array(self.cam_points), + base=np.array(self.base_points) + ) + self.get_logger().info( + f"Saved {len(self.cam_points)} samples to handeye_data.npz" + ) + self.cap.release() + cv2.destroyAllWindows() + + +# =============================== +# main +# =============================== + +def main(): + rclpy.init() + node = HandEyeCollector() + rclpy.spin(node) + rclpy.shutdown() + +if __name__ == "__main__": + main() diff --git a/src/openarm_moveit_control/openarm_moveit_control/stereo_params_1920x1080.npz b/src/openarm_moveit_control/openarm_moveit_control/stereo_params_1920x1080.npz new file mode 100644 index 0000000..647d1b1 Binary files /dev/null and b/src/openarm_moveit_control/openarm_moveit_control/stereo_params_1920x1080.npz differ diff --git a/src/openarm_moveit_control/openarm_moveit_control/test_control.py b/src/openarm_moveit_control/openarm_moveit_control/test_control.py new file mode 100644 index 0000000..08cd18a --- /dev/null +++ b/src/openarm_moveit_control/openarm_moveit_control/test_control.py @@ -0,0 +1,81 @@ +import rclpy +from rclpy.node import Node +from rclpy.action import ActionClient + +from moveit_msgs.action import MoveGroup +from moveit_msgs.msg import Constraints, PositionConstraint +from geometry_msgs.msg import PoseStamped +from shape_msgs.msg import SolidPrimitive + +class MoveItActionClient(Node): + def __init__(self): + super().__init__('moveit_action_client') + self.client = ActionClient(self, MoveGroup, '/move_action') + + def send_pose_goal(self, pose): + goal = MoveGroup.Goal() + req = goal.request + + req.group_name = "right_arm" # ⚠️ 改成你的 planning group + req.num_planning_attempts = 5 + req.allowed_planning_time = 5.0 + req.max_velocity_scaling_factor = 0.2 + req.max_acceleration_scaling_factor = 0.2 + + pc = PositionConstraint() + pc.header = pose.header + pc.link_name = "openarm_right_link7" # ⚠️ 改成你的 TCP + + box = SolidPrimitive() + box.type = SolidPrimitive.BOX + box.dimensions = [0.01, 0.01, 0.01] + + pc.constraint_region.primitives.append(box) + pc.constraint_region.primitive_poses.append(pose.pose) + pc.weight = 1.0 + + constraints = Constraints() + constraints.position_constraints.append(pc) + req.goal_constraints.append(constraints) + + self.client.wait_for_server() + send_future = self.client.send_goal_async( + goal, + feedback_callback=self.feedback_cb + ) + send_future.add_done_callback(self.goal_response_cb) + + def goal_response_cb(self, future): + goal_handle = future.result() + if not goal_handle.accepted: + self.get_logger().error("❌ Goal 被 MoveIt 拒绝") + return + + self.get_logger().info("✅ Goal 已接受,等待执行结果") + result_future = goal_handle.get_result_async() + result_future.add_done_callback(self.result_cb) + + def result_cb(self, future): + result = future.result().result + self.get_logger().info(f"🎯 执行完成,error_code = {result.error_code.val}") + rclpy.shutdown() + + def feedback_cb(self, feedback): + pass + +def main(): + rclpy.init() + node = MoveItActionClient() + + pose = PoseStamped() + pose.header.frame_id = "openarm_body_link0" + pose.pose.position.x = 0.01 + pose.pose.position.y = -0.153 + pose.pose.position.z = 0.462 + pose.pose.orientation.w = 1.0 + + node.send_pose_goal(pose) + rclpy.spin(node) + +if __name__ == "__main__": + main() diff --git a/src/openarm_moveit_control/openarm_moveit_control/tf_test.py b/src/openarm_moveit_control/openarm_moveit_control/tf_test.py new file mode 100644 index 0000000..9f01c58 --- /dev/null +++ b/src/openarm_moveit_control/openarm_moveit_control/tf_test.py @@ -0,0 +1,32 @@ +import rclpy +from rclpy.node import Node +from geometry_msgs.msg import TransformStamped +import tf2_ros +from rclpy.time import Time + +class TestTF(Node): + def __init__(self): + super().__init__("test_tf") + + self.buffer = tf2_ros.Buffer() + self.listener = tf2_ros.TransformListener(self.buffer, self) + + self.create_timer(0.5, self.check) + + def check(self): + try: + tf = self.buffer.lookup_transform( + 'openarm_body_link0', + 'openarm_left_link7', + Time() + ) + print("Transform:", tf) + self.get_logger().info("TF OK") + except: + self.get_logger().warn("TF not available yet") + +def main(): + rclpy.init() + node = TestTF() + rclpy.spin(node) + rclpy.shutdown() diff --git a/src/openarm_moveit_control/package.xml b/src/openarm_moveit_control/package.xml new file mode 100644 index 0000000..9e32798 --- /dev/null +++ b/src/openarm_moveit_control/package.xml @@ -0,0 +1,22 @@ + + + + openarm_moveit_control + 0.0.0 + TODO: Package description + shen + TODO: License declaration + moveit_ros_planning_interface + tf2_ros + geometry_msgs + + + ament_copyright + ament_flake8 + ament_pep257 + python3-pytest + + + ament_python + + diff --git a/src/openarm_moveit_control/resource/openarm_moveit_control b/src/openarm_moveit_control/resource/openarm_moveit_control new file mode 100644 index 0000000..e69de29 diff --git a/src/openarm_moveit_control/setup.cfg b/src/openarm_moveit_control/setup.cfg new file mode 100644 index 0000000..3f31b33 --- /dev/null +++ b/src/openarm_moveit_control/setup.cfg @@ -0,0 +1,4 @@ +[develop] +script_dir=$base/lib/openarm_moveit_control +[install] +install_scripts=$base/lib/openarm_moveit_control diff --git a/src/openarm_moveit_control/setup.py b/src/openarm_moveit_control/setup.py new file mode 100644 index 0000000..65b64be --- /dev/null +++ b/src/openarm_moveit_control/setup.py @@ -0,0 +1,33 @@ +from setuptools import find_packages, setup + +package_name = 'openarm_moveit_control' + +setup( + name=package_name, + version='0.0.0', + packages=find_packages(exclude=['test']), + data_files=[ + ('share/ament_index/resource_index/packages', + ['resource/' + package_name]), + ('share/' + package_name, ['package.xml']), + ], + install_requires=['setuptools'], + zip_safe=True, + maintainer='shen', + maintainer_email='shen@todo.todo', + description='TODO: Package description', + license='TODO: License declaration', + extras_require={ + 'test': [ + 'pytest', + ], + }, + entry_points={ + 'console_scripts': [ + 'moveit_node = openarm_moveit_control.test_control:main', + 'handeye_collect = openarm_moveit_control.handeye_collect:main', + 'tf_test = openarm_moveit_control.tf_test:main', + 'hand_follow_moveit = openarm_moveit_control.hand_follow_moveit:main', + ], + }, +) diff --git a/src/openarm_moveit_control/test/test_copyright.py b/src/openarm_moveit_control/test/test_copyright.py new file mode 100644 index 0000000..97a3919 --- /dev/null +++ b/src/openarm_moveit_control/test/test_copyright.py @@ -0,0 +1,25 @@ +# Copyright 2015 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_copyright.main import main +import pytest + + +# Remove the `skip` decorator once the source file(s) have a copyright header +@pytest.mark.skip(reason='No copyright header has been placed in the generated source file.') +@pytest.mark.copyright +@pytest.mark.linter +def test_copyright(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found errors' diff --git a/src/openarm_moveit_control/test/test_flake8.py b/src/openarm_moveit_control/test/test_flake8.py new file mode 100644 index 0000000..27ee107 --- /dev/null +++ b/src/openarm_moveit_control/test/test_flake8.py @@ -0,0 +1,25 @@ +# Copyright 2017 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_flake8.main import main_with_errors +import pytest + + +@pytest.mark.flake8 +@pytest.mark.linter +def test_flake8(): + rc, errors = main_with_errors(argv=[]) + assert rc == 0, \ + 'Found %d code style errors / warnings:\n' % len(errors) + \ + '\n'.join(errors) diff --git a/src/openarm_moveit_control/test/test_pep257.py b/src/openarm_moveit_control/test/test_pep257.py new file mode 100644 index 0000000..b234a38 --- /dev/null +++ b/src/openarm_moveit_control/test/test_pep257.py @@ -0,0 +1,23 @@ +# Copyright 2015 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_pep257.main import main +import pytest + + +@pytest.mark.linter +@pytest.mark.pep257 +def test_pep257(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found code style errors / warnings' diff --git a/src/status_interfaces/CMakeLists.txt b/src/status_interfaces/CMakeLists.txt new file mode 100644 index 0000000..ebbc110 --- /dev/null +++ b/src/status_interfaces/CMakeLists.txt @@ -0,0 +1,31 @@ +cmake_minimum_required(VERSION 3.8) +project(status_interfaces) + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wall -Wextra -Wpedantic) +endif() + +# find dependencies +find_package(ament_cmake REQUIRED) +find_package(rosidl_default_generators REQUIRED) +find_package(builtin_interfaces REQUIRED) + + +rosidl_generate_interfaces(${PROJECT_NAME} + "msg/SystemStatus.msg" + DEPENDENCIES builtin_interfaces +) + +if(BUILD_TESTING) + find_package(ament_lint_auto REQUIRED) + # the following line skips the linter which checks for copyrights + # comment the line when a copyright and license is added to all source files + set(ament_cmake_copyright_FOUND TRUE) + # the following line skips cpplint (only works in a git repo) + # comment the line when this package is in a git repo and when + # a copyright and license is added to all source files + set(ament_cmake_cpplint_FOUND TRUE) + ament_lint_auto_find_test_dependencies() +endif() + +ament_package() diff --git a/src/status_interfaces/msg/SystemStatus.msg b/src/status_interfaces/msg/SystemStatus.msg new file mode 100644 index 0000000..dcfe1d5 --- /dev/null +++ b/src/status_interfaces/msg/SystemStatus.msg @@ -0,0 +1,8 @@ +builtin_interfaces/Time stamp # 记录时间戳 +string host_name # 系统名称 +float32 cpu_percent # CPU使用率 +float32 memory_percent # 内存使用率 +float32 memory_total # 内存总量 +float32 memory_available # 剩余有效内存 +float64 net_sent # 网络发送数据总量 +float64 net_recv # 网络接收数据总量 \ No newline at end of file diff --git a/src/status_interfaces/package.xml b/src/status_interfaces/package.xml new file mode 100644 index 0000000..1c65033 --- /dev/null +++ b/src/status_interfaces/package.xml @@ -0,0 +1,21 @@ + + + + status_interfaces + 0.0.0 + TODO: Package description + shen + TODO: License declaration + + ament_cmake + + rosidl_default_generators + builtin_interfaces + rosidl_interface_packages + ament_lint_auto + ament_lint_common + + + ament_cmake + + diff --git a/src/status_publish/package.xml b/src/status_publish/package.xml new file mode 100644 index 0000000..18c94a9 --- /dev/null +++ b/src/status_publish/package.xml @@ -0,0 +1,21 @@ + + + + status_publish + 0.0.0 + TODO: Package description + shen + TODO: License declaration + + rclpy + status_interfaces + + ament_copyright + ament_flake8 + ament_pep257 + python3-pytest + + + ament_python + + diff --git a/src/status_publish/resource/status_publish b/src/status_publish/resource/status_publish new file mode 100644 index 0000000..e69de29 diff --git a/src/status_publish/setup.cfg b/src/status_publish/setup.cfg new file mode 100644 index 0000000..8a0f7ac --- /dev/null +++ b/src/status_publish/setup.cfg @@ -0,0 +1,4 @@ +[develop] +script_dir=$base/lib/status_publish +[install] +install_scripts=$base/lib/status_publish diff --git a/src/status_publish/setup.py b/src/status_publish/setup.py new file mode 100644 index 0000000..cb040eb --- /dev/null +++ b/src/status_publish/setup.py @@ -0,0 +1,26 @@ +from setuptools import find_packages, setup + +package_name = 'status_publish' + +setup( + name=package_name, + version='0.0.0', + packages=find_packages(exclude=['test']), + data_files=[ + ('share/ament_index/resource_index/packages', + ['resource/' + package_name]), + ('share/' + package_name, ['package.xml']), + ], + install_requires=['setuptools'], + zip_safe=True, + maintainer='shen', + maintainer_email='shen@todo.todo', + description='TODO: Package description', + license='TODO: License declaration', + tests_require=['pytest'], + entry_points={ + 'console_scripts': [ + 'status_pub_node = status_publish.sys_status_pub:main' + ], + }, +) diff --git a/src/status_publish/status_publish/__init__.py b/src/status_publish/status_publish/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/status_publish/status_publish/sys_status_pub.py b/src/status_publish/status_publish/sys_status_pub.py new file mode 100644 index 0000000..30c8b62 --- /dev/null +++ b/src/status_publish/status_publish/sys_status_pub.py @@ -0,0 +1,37 @@ +import rclpy +from rclpy.node import Node +from status_interfaces.msg import SystemStatus # 导入消息接口 +import psutil +import platform + +class SysStatusPub(Node): + def __init__(self, node_name): + super().__init__(node_name) + self.status_publisher_ = self.create_publisher( + SystemStatus, 'sys_status', 10) + self.timer = self.create_timer(1, self.timer_callback) + + def timer_callback(self): + cpu_percent = psutil.cpu_percent() + memory_info = psutil.virtual_memory() + net_io_counters = psutil.net_io_counters() + + msg = SystemStatus() + msg.stamp = self.get_clock().now().to_msg() + msg.host_name = platform.node() + msg.cpu_percent = cpu_percent + msg.memory_percent = memory_info.percent + msg.memory_total = memory_info.total / 1024 / 1024 + msg.memory_available = memory_info.available / 1024 / 1024 + msg.net_sent = net_io_counters.bytes_sent / 1024 / 1024 + msg.net_recv = net_io_counters.bytes_recv / 1024 / 1024 + + self.get_logger().info(f'发布:{str(msg)}') + self.status_publisher_.publish(msg) + + +def main(): + rclpy.init() + node = SysStatusPub('sys_status_pub') + rclpy.spin(node) + rclpy.shutdown() diff --git a/src/status_publish/test/test_copyright.py b/src/status_publish/test/test_copyright.py new file mode 100644 index 0000000..97a3919 --- /dev/null +++ b/src/status_publish/test/test_copyright.py @@ -0,0 +1,25 @@ +# Copyright 2015 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_copyright.main import main +import pytest + + +# Remove the `skip` decorator once the source file(s) have a copyright header +@pytest.mark.skip(reason='No copyright header has been placed in the generated source file.') +@pytest.mark.copyright +@pytest.mark.linter +def test_copyright(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found errors' diff --git a/src/status_publish/test/test_flake8.py b/src/status_publish/test/test_flake8.py new file mode 100644 index 0000000..27ee107 --- /dev/null +++ b/src/status_publish/test/test_flake8.py @@ -0,0 +1,25 @@ +# Copyright 2017 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_flake8.main import main_with_errors +import pytest + + +@pytest.mark.flake8 +@pytest.mark.linter +def test_flake8(): + rc, errors = main_with_errors(argv=[]) + assert rc == 0, \ + 'Found %d code style errors / warnings:\n' % len(errors) + \ + '\n'.join(errors) diff --git a/src/status_publish/test/test_pep257.py b/src/status_publish/test/test_pep257.py new file mode 100644 index 0000000..b234a38 --- /dev/null +++ b/src/status_publish/test/test_pep257.py @@ -0,0 +1,23 @@ +# Copyright 2015 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_pep257.main import main +import pytest + + +@pytest.mark.linter +@pytest.mark.pep257 +def test_pep257(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found code style errors / warnings'