diff --git a/README.en.md b/README.en.md index 8666cd24..68e5b3e6 100644 --- a/README.en.md +++ b/README.en.md @@ -23,17 +23,6 @@ ROS 2 package suite of CRANE-X7. ## Supported ROS 2 distributions -<<<<<<< HEAD -## Supported ROS distributions - -- Melodic -- Noetic - -### ROS 2 - -- [Foxy](https://github.com/rt-net/crane_x7_ros/tree/foxy-devel) -- [Humble](https://github.com/rt-net/crane_x7_ros/tree/humble-devel) -======= - [Foxy](https://github.com/rt-net/crane_x7_ros/tree/foxy-devel) - Humble @@ -50,7 +39,6 @@ ROS 2 package suite of CRANE-X7. - Ubuntu 20.04 - ROS - [Humble Hawksbill](https://docs.ros.org/en/humble/Installation.html) ->>>>>>> ros2 ## Installation diff --git a/README.md b/README.md index a7962e7b..8eb5fc2e 100644 --- a/README.md +++ b/README.md @@ -24,17 +24,6 @@ ROS 2 package suite of CRANE-X7. ## Supported ROS 2 distributions -<<<<<<< HEAD -## サポートするROSディストリビューション - -- Melodic -- Noetic - -### ROS 2 - -- [Foxy](https://github.com/rt-net/crane_x7_ros/tree/foxy-devel) -- [Humble](https://github.com/rt-net/crane_x7_ros/tree/humble-devel) -======= - [Foxy](https://github.com/rt-net/crane_x7_ros/tree/foxy-devel) - Humble @@ -42,7 +31,6 @@ ROS 2 package suite of CRANE-X7. - [Melodic](https://github.com/rt-net/crane_x7_ros/tree/master) - [Noetic](https://github.com/rt-net/crane_x7_ros/tree/master) ->>>>>>> ros2 ## Requirements diff --git a/crane_x7_examples/README.md b/crane_x7_examples/README.md index b9bb4a77..7b669146 100644 --- a/crane_x7_examples/README.md +++ b/crane_x7_examples/README.md @@ -40,11 +40,7 @@ CRANE-X7本体をPCに接続します。 接続方法は製品マニュアルを参照してください。 -<<<<<<< HEAD -### 簡易シミュレータを使う場合 -======= **※CRANE-X7本体が接触しないように、十分なスペースを確保してください。** ->>>>>>> ros2 ### 2. USB通信ポートの接続を確認する @@ -64,23 +60,17 @@ controller (`crane_x7_control`)を起動します。 ros2 launch crane_x7_examples demo.launch.py port_name:=/dev/ttyUSB0 ``` -<<<<<<< HEAD -物理演算やセンサを含めたシミュレーションは、 -後述の[「Gazeboを使う場合」](#gazeboを使う場合)の手順に従ってください。 - -### 実機を使う場合 - -実機で動作を確認する場合、 -制御信号ケーブルを接続した状態で次のコマンドを実行します。 -======= #### RealSense D435マウンタ搭載モデルを使用する場合 [RealSense D435マウンタ](https://github.com/rt-net/crane_x7_Hardware/blob/master/3d_print_parts/v1.0/CRANE-X7_HandA_RealSenseD435マウンタ.stl)を搭載している場合は次のコマンドを実行します。RealSense D435が起動し、camera_linkがロボットモデルに追加されます。 ->>>>>>> ros2 ```sh -ros2 launch crane_x7_examples demo.launch.py port_name:=/dev/ttyUSB0 use_d435:=true +roslaunch crane_x7_bringup demo.launch fake_execution:=true ``` +<<<<<<< HEAD +物理演算やセンサを含めたシミュレーションは、 +後述の[「Gazeboを使う場合」](#gazeboを使う場合)の手順に従ってください。 + ## 準備 (Gazeboを使う場合) ![crane_x7_gazebo](https://rt-net.github.io/images/crane-x7/crane_x7_gazebo_ros2.png) @@ -302,6 +292,7 @@ ros2 launch crane_x7_examples camera_example.launch.py example:='color_detection ### point_cloud_detection +点群から物体を検出して掴むコード例です。 点群から物体を検出して掴むコード例です。 検出された物体位置はtfのフレームとして配信されます。 diff --git a/crane_x7_examples/package.xml b/crane_x7_examples/package.xml index 64228b1f..f5157997 100644 --- a/crane_x7_examples/package.xml +++ b/crane_x7_examples/package.xml @@ -21,7 +21,6 @@ image_geometry libopencv-dev moveit_ros_planning_interface - libpcl-all pcl_ros rclcpp realsense2_camera diff --git a/crane_x7_examples/src/color_detection.cpp b/crane_x7_examples/src/color_detection.cpp index bd1e853c..7ca8fabe 100644 --- a/crane_x7_examples/src/color_detection.cpp +++ b/crane_x7_examples/src/color_detection.cpp @@ -187,3 +187,193 @@ int main(int argc, char * argv[]) rclcpp::shutdown(); return 0; } + +// Copyright 2023 RT Corporation +// +// 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. + +// Reference: +// https://www.opencv-srf.com/2010/09/object-detection-using-color-seperation.html + +#include +#include +#include +#include + +#include "rclcpp/rclcpp.hpp" +#include "geometry_msgs/msg/transform_stamped.hpp" +#include "sensor_msgs/msg/camera_info.hpp" +#include "sensor_msgs/msg/image.hpp" +#include "tf2/LinearMath/Quaternion.h" +#include "tf2/LinearMath/Matrix3x3.h" +#include "tf2_ros/transform_broadcaster.h" +#include "opencv2/opencv.hpp" +#include "opencv2/imgproc/imgproc.hpp" +#include "cv_bridge/cv_bridge.h" +#include "image_geometry/pinhole_camera_model.h" +using std::placeholders::_1; + +class ImageSubscriber : public rclcpp::Node +{ +public: + ImageSubscriber() + : Node("color_detection") + { + image_subscription_ = this->create_subscription( + "/camera/color/image_raw", 10, std::bind(&ImageSubscriber::image_callback, this, _1)); + + depth_subscription_ = this->create_subscription( + "/camera/aligned_depth_to_color/image_raw", 10, + std::bind(&ImageSubscriber::depth_callback, this, _1)); + + camera_info_subscription_ = this->create_subscription( + "/camera/color/camera_info", 10, std::bind(&ImageSubscriber::camera_info_callback, this, _1)); + + image_thresholded_publisher_ = + this->create_publisher("image_thresholded", 10); + + tf_broadcaster_ = + std::make_unique(*this); + } + +private: + rclcpp::Subscription::SharedPtr image_subscription_; + rclcpp::Subscription::SharedPtr depth_subscription_; + rclcpp::Subscription::SharedPtr camera_info_subscription_; + rclcpp::Publisher::SharedPtr image_thresholded_publisher_; + sensor_msgs::msg::CameraInfo::SharedPtr camera_info_; + sensor_msgs::msg::Image::SharedPtr depth_image_; + std::unique_ptr tf_broadcaster_; + + void image_callback(const sensor_msgs::msg::Image::SharedPtr msg) + { + // カメラのパラメータを取得してから処理を行う + if (camera_info_ && depth_image_) { + // 青い物体を検出するようにHSVの範囲を設定 + // 周囲の明るさ等の動作環境に合わせて調整 + const int LOW_H = 100, HIGH_H = 125; + const int LOW_S = 100, HIGH_S = 255; + const int LOW_V = 30, HIGH_V = 255; + + // ウェブカメラの画像を受け取る + auto cv_img = cv_bridge::toCvShare(msg, msg->encoding); + + // 画像をRGBからHSVに変換 + cv::cvtColor(cv_img->image, cv_img->image, cv::COLOR_RGB2HSV); + + // 画像処理用の変数を用意 + cv::Mat img_thresholded; + + // 画像の二値化 + cv::inRange( + cv_img->image, + cv::Scalar(LOW_H, LOW_S, LOW_V), + cv::Scalar(HIGH_H, HIGH_S, HIGH_V), + img_thresholded); + + // ノイズ除去の処理 + cv::morphologyEx( + img_thresholded, + img_thresholded, + cv::MORPH_OPEN, + cv::getStructuringElement(cv::MORPH_RECT, cv::Size(5, 5))); + + // 穴埋めの処理 + cv::morphologyEx( + img_thresholded, + img_thresholded, + cv::MORPH_CLOSE, + cv::getStructuringElement(cv::MORPH_RECT, cv::Size(5, 5))); + + // 画像の検出領域におけるモーメントを計算 + cv::Moments moment = moments(img_thresholded); + double d_m01 = moment.m01; + double d_m10 = moment.m10; + double d_area = moment.m00; + + // 検出した領域のピクセル数が10000より大きい場合 + if (d_area > 10000) { + // カメラモデル作成 + image_geometry::PinholeCameraModel camera_model; + + // カメラのパラメータを設定 + camera_model.fromCameraInfo(camera_info_); + + // 画像座標系における把持対象物の位置(2D) + const double pixel_x = d_m10 / d_area; + const double pixel_y = d_m01 / d_area; + const cv::Point2d point(pixel_x, pixel_y); + + // 補正後の画像座標系における把持対象物の位置を取得(2D) + const cv::Point2d rect_point = camera_model.rectifyPoint(point); + + // カメラ座標系から見た把持対象物の方向(Ray)を取得する + const cv::Point3d ray = camera_model.projectPixelTo3dRay(rect_point); + + // 把持対象物までの距離を取得 + // 把持対象物の表面より少し奥を掴むように設定 + const double DEPTH_OFFSET = 0.015; + const auto cv_depth = cv_bridge::toCvShare(depth_image_, depth_image_->encoding); + // カメラから把持対象物の表面までの距離 + const auto front_distance = cv_depth->image.at(point) / 1000.0; + const auto center_distance = front_distance + DEPTH_OFFSET; + + // 距離を取得できないか遠すぎる場合は把持しない + const double DEPTH_MAX = 0.5; + const double DEPTH_MIN = 0.2; + if (center_distance < DEPTH_MIN || center_distance > DEPTH_MAX) { + RCLCPP_INFO_STREAM(this->get_logger(), "Failed to get depth at" << point << "."); + return; + } + + // 把持対象物の位置を計算 + cv::Point3d object_position( + ray.x * center_distance, + ray.y * center_distance, + ray.z * center_distance); + + // 把持対象物の位置をTFに配信 + geometry_msgs::msg::TransformStamped t; + t.header = msg->header; + t.child_frame_id = "target_0"; + t.transform.translation.x = object_position.x; + t.transform.translation.y = object_position.y; + t.transform.translation.z = object_position.z; + tf_broadcaster_->sendTransform(t); + + // 閾値による二値化画像を配信 + sensor_msgs::msg::Image::SharedPtr img_thresholded_msg = + cv_bridge::CvImage(msg->header, "mono8", img_thresholded).toImageMsg(); + image_thresholded_publisher_->publish(*img_thresholded_msg); + } + } + } + + void camera_info_callback(const sensor_msgs::msg::CameraInfo::SharedPtr msg) + { + camera_info_ = msg; + } + + void depth_callback(const sensor_msgs::msg::Image::SharedPtr msg) + { + depth_image_ = msg; + } +}; + +int main(int argc, char * argv[]) +{ + rclcpp::init(argc, argv); + rclcpp::spin(std::make_shared()); + rclcpp::shutdown(); + return 0; +} diff --git a/crane_x7_moveit_config/config/controllers.yaml b/crane_x7_moveit_config/config/controllers.yaml index 58c44026..e5c8c323 100644 --- a/crane_x7_moveit_config/config/controllers.yaml +++ b/crane_x7_moveit_config/config/controllers.yaml @@ -1,27 +1,23 @@ -# MoveIt uses this configuration for controller management -moveit_controller_manager: moveit_simple_controller_manager/MoveItSimpleControllerManager +controller_names: + - crane_x7_arm_controller + - crane_x7_gripper_controller -moveit_simple_controller_manager: - controller_names: - - crane_x7_arm_controller - - crane_x7_gripper_controller +crane_x7_arm_controller: + action_ns: follow_joint_trajectory + type: FollowJointTrajectory + default: true + joints: + - crane_x7_shoulder_fixed_part_pan_joint + - crane_x7_shoulder_revolute_part_tilt_joint + - crane_x7_upper_arm_revolute_part_twist_joint + - crane_x7_upper_arm_revolute_part_rotate_joint + - crane_x7_lower_arm_fixed_part_joint + - crane_x7_lower_arm_revolute_part_joint + - crane_x7_wrist_joint - crane_x7_arm_controller: - action_ns: follow_joint_trajectory - type: FollowJointTrajectory - default: true - joints: - - crane_x7_shoulder_fixed_part_pan_joint - - crane_x7_shoulder_revolute_part_tilt_joint - - crane_x7_upper_arm_revolute_part_twist_joint - - crane_x7_upper_arm_revolute_part_rotate_joint - - crane_x7_lower_arm_fixed_part_joint - - crane_x7_lower_arm_revolute_part_joint - - crane_x7_wrist_joint - - crane_x7_gripper_controller: - action_ns: gripper_cmd - type: GripperCommand - default: true - joints: - - crane_x7_gripper_finger_a_joint \ No newline at end of file +crane_x7_gripper_controller: + action_ns: gripper_cmd + type: GripperCommand + default: true + joints: + - crane_x7_gripper_finger_a_joint \ No newline at end of file diff --git a/crane_x7_moveit_config/launch/run_move_group.launch.py b/crane_x7_moveit_config/launch/run_move_group.launch.py index 0edfca4c..001246af 100644 --- a/crane_x7_moveit_config/launch/run_move_group.launch.py +++ b/crane_x7_moveit_config/launch/run_move_group.launch.py @@ -1,4 +1,4 @@ -# Copyright 2020 RT Corporation +# Copyright 2022 RT Corporation # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,16 +15,15 @@ import os from ament_index_python.packages import get_package_share_directory +from crane_x7_description.robot_description_loader import RobotDescriptionLoader from launch import LaunchDescription from launch.actions import DeclareLaunchArgument from launch.substitutions import LaunchConfiguration -from moveit_configs_utils import MoveItConfigsBuilder -from moveit_configs_utils.launch_utils import DeclareBooleanLaunchArg -from moveit_configs_utils.launches import generate_move_group_launch -from moveit_configs_utils.launches import generate_moveit_rviz_launch -from moveit_configs_utils.launches \ - import generate_static_virtual_joint_tfs_launch -from moveit_configs_utils.launches import generate_rsp_launch +from launch_ros.actions import Node +import yaml + +# Reference: https://github.com/ros-planning/moveit2_tutorials/blob/humble/doc/ +# examples/move_group_interface/launch/move_group.launch.py def load_file(package_name, file_path): @@ -34,9 +33,7 @@ def load_file(package_name, file_path): try: with open(absolute_file_path, 'r') as file: return file.read() - except ( - EnvironmentError - ): # parent of IOError, OSError *and* WindowsError where available + except EnvironmentError: # parent of IOError, OSError *and* WindowsError where available return None @@ -47,81 +44,114 @@ def load_yaml(package_name, file_path): try: with open(absolute_file_path, 'r') as file: return yaml.safe_load(file) - except ( - EnvironmentError - ): # parent of IOError, OSError *and* WindowsError where available + except EnvironmentError: # parent of IOError, OSError *and* WindowsError where available return None def generate_launch_description(): - ld = LaunchDescription() + description_loader = RobotDescriptionLoader() declare_loaded_description = DeclareLaunchArgument( 'loaded_description', - default_value='', + default_value=description_loader.load(), description='Set robot_description text. \ - It is recommended to use RobotDescriptionLoader() in \ - crane_x7_description.', - ) - - ld.add_action(declare_loaded_description) - - ld.add_action(DeclareBooleanLaunchArg('debug', default_value=False)) - - ld.add_action( - DeclareLaunchArgument( - 'rviz_config', - default_value=get_package_share_directory('crane_x7_moveit_config') - + '/config/moveit.rviz', - description='Set the path to rviz configuration file.', - ) + It is recommended to use RobotDescriptionLoader() in crane_x7_description.' ) - moveit_config = ( - MoveItConfigsBuilder('crane_x7') - .planning_scene_monitor( - publish_robot_description=True, - publish_robot_description_semantic=True, - ) - .robot_description( - file_path=os.path.join( - get_package_share_directory('crane_x7_description'), - 'urdf', - 'crane_x7.urdf.xacro', - ), - mappings={}, - ) - .robot_description_semantic( - file_path='config/crane_x7.srdf', - mappings={'model': 'crane_x7'}, - ) - .joint_limits(file_path='config/joint_limits.yaml') - .trajectory_execution( - file_path='config/controllers.yaml', moveit_manage_controllers=True - ) - .planning_pipelines(pipelines=['ompl']) - .robot_description_kinematics(file_path='config/kinematics.yaml') - .to_moveit_configs() + declare_rviz_config_file = DeclareLaunchArgument( + 'rviz_config_file', + default_value=get_package_share_directory( + 'crane_x7_moveit_config') + '/launch/run_move_group.rviz', + description='Set the path to rviz configuration file.' ) - moveit_config.robot_description = { - 'robot_description': LaunchConfiguration('loaded_description') - } + robot_description = {'robot_description': LaunchConfiguration('loaded_description')} - moveit_config.move_group_capabilities = { - 'capabilities': '' - } + robot_description_semantic_config = load_file( + 'crane_x7_moveit_config', 'config/crane_x7.srdf') + robot_description_semantic = { + 'robot_description_semantic': robot_description_semantic_config} - # Move group - ld.add_entity(generate_move_group_launch(moveit_config)) + joint_limits_yaml = load_yaml( + "crane_x7_moveit_config", "config/joint_limits.yaml" + ) + robot_description_planning = { + "robot_description_planning": joint_limits_yaml} + + kinematics_yaml = load_yaml('crane_x7_moveit_config', 'config/kinematics.yaml') + + # Planning Functionality + ompl_planning_pipeline_config = {'move_group': { + 'planning_plugin': 'ompl_interface/OMPLPlanner', + 'request_adapters': 'default_planner_request_adapters/AddTimeOptimalParameterization \ + default_planner_request_adapters/FixWorkspaceBounds \ + default_planner_request_adapters/FixStartStateBounds \ + default_planner_request_adapters/FixStartStateCollision \ + default_planner_request_adapters/FixStartStatePathConstraints', + 'start_state_max_bounds_error': 0.1}} + ompl_planning_yaml = load_yaml('crane_x7_moveit_config', 'config/ompl_planning.yaml') + ompl_planning_pipeline_config['move_group'].update(ompl_planning_yaml) + + # Trajectory Execution Functionality + controllers_yaml = load_yaml('crane_x7_moveit_config', 'config/controllers.yaml') + moveit_controllers = { + 'moveit_simple_controller_manager': controllers_yaml, + 'moveit_controller_manager': + 'moveit_simple_controller_manager/MoveItSimpleControllerManager'} + + trajectory_execution = {'moveit_manage_controllers': True, + 'trajectory_execution.allowed_execution_duration_scaling': 1.2, + 'trajectory_execution.allowed_goal_duration_margin': 0.5, + 'trajectory_execution.allowed_start_tolerance': 0.1} + + planning_scene_monitor_parameters = {'publish_planning_scene': True, + 'publish_geometry_updates': True, + 'publish_state_updates': True, + 'publish_transforms_updates': True} + + # Start the actual move_group node/action server + run_move_group_node = Node(package='moveit_ros_move_group', + executable='move_group', + output='screen', + parameters=[robot_description, + robot_description_semantic, + robot_description_planning, + kinematics_yaml, + ompl_planning_pipeline_config, + trajectory_execution, + moveit_controllers, + planning_scene_monitor_parameters]) # RViz - ld.add_entity(generate_moveit_rviz_launch(moveit_config)) + rviz_config_file = LaunchConfiguration('rviz_config_file') + rviz_node = Node(package='rviz2', + executable='rviz2', + name='rviz2', + output='log', + arguments=['-d', rviz_config_file], + parameters=[robot_description, + robot_description_semantic, + ompl_planning_pipeline_config, + kinematics_yaml]) # Static TF - ld.add_entity(generate_static_virtual_joint_tfs_launch(moveit_config)) + static_tf = Node(package='tf2_ros', + executable='static_transform_publisher', + name='static_transform_publisher', + output='log', + arguments=['0.0', '0.0', '0.0', '0.0', '0.0', '0.0', 'world', 'base_link']) # Publish TF - ld.add_entity(generate_rsp_launch(moveit_config)) - - return ld + robot_state_publisher = Node(package='robot_state_publisher', + executable='robot_state_publisher', + name='robot_state_publisher', + output='both', + parameters=[robot_description]) + + return LaunchDescription([declare_loaded_description, + declare_rviz_config_file, + run_move_group_node, + rviz_node, + static_tf, + robot_state_publisher, + ])