Program Listing for File detect_aruco_markers_behavior.cpp

Return to documentation for file (/tmp/ws/src/aerostack2/as2_behaviors/as2_behaviors_perception/detect_aruco_markers_behavior/src/detect_aruco_markers_behavior.cpp)

// Copyright 2024 Universidad Politécnica de Madrid
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
//    * Redistributions of source code must retain the above copyright
//      notice, this list of conditions and the following disclaimer.
//
//    * Redistributions in binary form must reproduce the above copyright
//      notice, this list of conditions and the following disclaimer in the
//      documentation and/or other materials provided with the distribution.
//
//    * Neither the name of the Universidad Politécnica de Madrid nor the names of its
//      contributors may be used to endorse or promote products derived from
//      this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

/*!*******************************************************************************************
 *  \file       detect_aruco_markers_behavior.cpp
 *  \brief      Aruco detector implementation file.
 *  \authors    David Perez Saura
 *  \copyright  Copyright (c) 2022 Universidad Politécnica de Madrid
 *              All Rights Reserved
 ********************************************************************************/

#include "detect_aruco_markers_behavior.hpp"

DetectArucoMarkersBehavior::DetectArucoMarkersBehavior()
: as2_behavior::BehaviorServer<as2_msgs::action::DetectArucoMarkers>(
    "detect_aruco_markers_behavior")
{
  loadParameters();
}

void DetectArucoMarkersBehavior::setup()
{
  aruco_pose_pub_ = this->create_publisher<as2_msgs::msg::PoseStampedWithID>(
    this->generate_local_name("aruco_pose"), rclcpp::SensorDataQoS());
  aruco_img_transport_ = std::make_shared<as2::sensors::Camera>("aruco_img_topic", this);

  std::shared_ptr<const rclcpp::QoS> camera_qos;
  if (camera_qos_reliable_) {
    RCLCPP_INFO(get_logger(), "QoS Camera subscription: Reliable");
    camera_qos = std::make_shared<const rclcpp::QoS>(rclcpp::QoS(2));
  } else {
    RCLCPP_INFO(get_logger(), "QoS Camera subscription: Sensor");
    camera_qos = std::make_shared<const rclcpp::QoS>(rclcpp::SensorDataQoS());
  }

  cam_image_sub_ = this->create_subscription<sensor_msgs::msg::Image>(
    camera_image_topic_, *camera_qos,
    std::bind(&DetectArucoMarkersBehavior::imageCallback, this, std::placeholders::_1));

  cam_info_sub_ = this->create_subscription<sensor_msgs::msg::CameraInfo>(
    camera_info_topic_, as2_names::topics::sensor_measurements::qos,
    std::bind(&DetectArucoMarkersBehavior::camerainfoCallback, this, std::placeholders::_1));
}

void DetectArucoMarkersBehavior::loadParameters()
{
  // std::string aruco_dictionary;
  this->declare_parameter<float>("aruco_size");
  this->declare_parameter<bool>("camera_qos_reliable");
  this->declare_parameter<std::string>("camera_image_topic");
  this->declare_parameter<std::string>("camera_info_topic");
  this->declare_parameter<std::string>("camera_model");

  this->get_parameter("aruco_size", aruco_size_);
  this->get_parameter("camera_qos_reliable", camera_qos_reliable_);
  this->get_parameter("camera_image_topic", camera_image_topic_);
  this->get_parameter("camera_info_topic", camera_info_topic_);
  this->get_parameter("camera_model", camera_model_);

  RCLCPP_INFO(get_logger(), "Params: aruco_size: %.3f m", aruco_size_);

  // TODO(david): Load dictionary from param
  aruco_dict_ = cv::aruco::getPredefinedDictionary(cv::aruco::DICT_6X6_250);
  // aruco_dict_ = cv::aruco::getPredefinedDictionary(cv::aruco::DICT_5X5_1000);
}

// SIMULATION
void DetectArucoMarkersBehavior::setCameraParameters(
  const sensor_msgs::msg::CameraInfo & _camera_info)
{
  camera_matrix_ = cv::Mat(3, 3, CV_64F);

  camera_matrix_.at<double>(0, 0) = _camera_info.k[0];
  camera_matrix_.at<double>(0, 1) = _camera_info.k[1];
  camera_matrix_.at<double>(0, 2) = _camera_info.k[2];
  camera_matrix_.at<double>(1, 0) = _camera_info.k[3];
  camera_matrix_.at<double>(1, 1) = _camera_info.k[4];
  camera_matrix_.at<double>(1, 2) = _camera_info.k[5];
  camera_matrix_.at<double>(2, 0) = _camera_info.k[6];
  camera_matrix_.at<double>(2, 1) = _camera_info.k[7];
  camera_matrix_.at<double>(2, 2) = _camera_info.k[8];

  int n_discoeff = _camera_info.d.size();
  dist_coeffs_ = cv::Mat(1, n_discoeff, CV_64F);

  for (int i; i < n_discoeff; i++) {
    dist_coeffs_.at<double>(0, i) = _camera_info.d[i];
  }

  // std::cout << dist_coeffs_ << std::endl;

  distorsion_model_ = _camera_info.distortion_model;

  if (camera_model_ == "fisheye") {
    RCLCPP_INFO(get_logger(), "Using FISHEYE camera model");
    if (n_discoeff != 4) {
      RCLCPP_ERROR(get_logger(), "FISHEYE distortion coefficients must be 4");
    }
  }
  if (camera_model_ == "pinhole") {
    RCLCPP_INFO(get_logger(), "Using PINHOLE camera model");
  }

  img_encoding_ = sensor_msgs::image_encodings::BGR8;

  camera_params_available_ = true;
}

void DetectArucoMarkersBehavior::camerainfoCallback(
  const sensor_msgs::msg::CameraInfo::SharedPtr info)
{
  RCLCPP_DEBUG(this->get_logger(), "Camera info received by callback");
  if (camera_params_available_) {
    return;
  }
  RCLCPP_INFO(get_logger(), "Setting camera info");
  setCameraParameters(*info);
}

void DetectArucoMarkersBehavior::imageCallback(const sensor_msgs::msg::Image::SharedPtr img)
{
  RCLCPP_DEBUG(this->get_logger(), "Image received by callback");

  cv_bridge::CvImagePtr cv_ptr;
  try {
    cv_ptr = cv_bridge::toCvCopy(img, img_encoding_);
  } catch (cv_bridge::Exception & ex) {
    RCLCPP_ERROR(this->get_logger(), "CV_bridge exception: %s\n", ex.what());
  }

  if (!camera_params_available_) {
    RCLCPP_WARN(get_logger(), "No camera parameters available");
    return;
  }

  // init ArUco detection
  std::vector<int> marker_ids;
  std::vector<std::vector<cv::Point2f>> marker_corners, rejected_candidates;
  cv::Ptr<cv::aruco::DetectorParameters> detector_params = cv::aruco::DetectorParameters::create();

  // detect markers on the fisheye, it's no worth it to detect over the rectified image
  cv::aruco::detectMarkers(
    cv_ptr->image, aruco_dict_, marker_corners, marker_ids, detector_params, rejected_candidates);
  cv::Mat output_image = cv_ptr->image.clone();
  cv::Vec3d aruco_position, aruco_rotation;

  if (marker_ids.size() > 0) {
    cv::aruco::drawDetectedMarkers(
      output_image, marker_corners,
      marker_ids);  // Draw markers to ensure orientation
    std::vector<cv::Vec3d> rvecs, tvecs;
    cv::aruco::estimatePoseSingleMarkers(
      marker_corners, aruco_size_, camera_matrix_, dist_coeffs_, rvecs, tvecs);

    for (int i = 0; i < rvecs.size(); ++i) {
      int id = marker_ids[i];

      if (!checkIdIsTarget(id)) {
        continue;
      }

      RCLCPP_INFO(this->get_logger(), "Marker %d detected", id);
      cv::Vec3d rvec = rvecs[i];
      cv::Vec3d tvec = tvecs[i];
      cv::Vec3d rout, tout;
      cv::composeRT(rvec * 0, tvec * 0, rvec, tvec, rout, tout);
      aruco_position = tout;
      aruco_rotation = rout;
      cv::drawFrameAxes(output_image, camera_matrix_, dist_coeffs_, rout, tout, 0.08625, 3);
    }
  }

  cv::Mat undistort_camera_matrix;
  cv::Mat rectified_image, cropped_image;
  cv::Rect roi;
  float alpha = 0.0;

  if (camera_model_ == "pinhole") {
    RCLCPP_INFO_ONCE(get_logger(), "Undistort image with pinhole model");
    cv::undistort(output_image, rectified_image, camera_matrix_, dist_coeffs_);
  }

  if (camera_model_ == "fisheye") {
    RCLCPP_INFO_ONCE(get_logger(), "Undistort image with fisheye model");
    cv::fisheye::undistortImage(output_image, rectified_image, camera_matrix_, dist_coeffs_);
  }

  std::string output_img_encoding = sensor_msgs::image_encodings::BGR8;

  sensor_msgs::msg::Image output_image_msg =
    *(cv_bridge::CvImage(std_msgs::msg::Header(), output_img_encoding, rectified_image)
    .toImageMsg()
    .get());
  aruco_img_transport_->updateData(output_image_msg);

  for (int i = 0; i < marker_ids.size(); i++) {
    int id = marker_ids[i];
    if (checkIdIsTarget(id)) {
      // std::cout << aruco_position << std::endl;
      as2_msgs::msg::PoseStampedWithID pose;
      pose.pose.header = img->header;
      pose.id = std::to_string(id);
      pose.pose.pose.position.x = aruco_position[0];
      pose.pose.pose.position.y = aruco_position[1];
      pose.pose.pose.position.z = aruco_position[2];
      tf2::Quaternion rot;
      rot.setRPY(aruco_rotation[2], aruco_rotation[0], aruco_rotation[1]);
      rot = rot.normalize();
      pose.pose.pose.orientation.x = static_cast<double>(rot.x());
      pose.pose.pose.orientation.y = static_cast<double>(rot.y());
      pose.pose.pose.orientation.z = static_cast<double>(rot.z());
      pose.pose.pose.orientation.w = static_cast<double>(rot.w());

      aruco_pose_pub_->publish(pose);
    }
  }
}

bool DetectArucoMarkersBehavior::checkIdIsTarget(const int _id)
{
  // if target_ids_ is not empty
  if (target_ids_.size() > 0) {
    // if id is not in target_ids_
    if (std::find(target_ids_.begin(), target_ids_.end(), _id) == target_ids_.end()) {
      return false;
    }
  }
  return true;
}

//** AS2 Behavior methods **//
bool DetectArucoMarkersBehavior::on_activate(
  std::shared_ptr<const as2_msgs::action::DetectArucoMarkers::Goal> goal)
{
  setup();
  target_ids_ = goal->target_ids;
  RCLCPP_INFO(get_logger(), "Goal accepted");
  RCLCPP_INFO(get_logger(), "Target IDs: %s", targetIds2string(target_ids_).c_str());
  return true;
}

bool DetectArucoMarkersBehavior::on_modify(
  std::shared_ptr<const as2_msgs::action::DetectArucoMarkers::Goal> goal)
{
  target_ids_ = goal->target_ids;
  RCLCPP_INFO(get_logger(), "Goal modified");
  RCLCPP_INFO(get_logger(), "New target IDs: %s", targetIds2string(target_ids_).c_str());
  return true;
}

bool DetectArucoMarkersBehavior::on_deactivate(const std::shared_ptr<std::string> & message)
{
  RCLCPP_INFO(get_logger(), "DetectArucoMarkersBehavior cancelled");
  return true;
}

bool DetectArucoMarkersBehavior::on_pause(const std::shared_ptr<std::string> & message)
{
  RCLCPP_INFO(get_logger(), "DetectArucoMarkersBehavior paused");
  return true;
}

bool DetectArucoMarkersBehavior::on_resume(const std::shared_ptr<std::string> & message)
{
  RCLCPP_INFO(get_logger(), "DetectArucoMarkersBehavior resumed");
  return true;
}

as2_behavior::ExecutionStatus DetectArucoMarkersBehavior::on_run(
  const std::shared_ptr<const as2_msgs::action::DetectArucoMarkers::Goal> & goal,
  std::shared_ptr<as2_msgs::action::DetectArucoMarkers::Feedback> & feedback_msg,
  std::shared_ptr<as2_msgs::action::DetectArucoMarkers::Result> & result_msg)
{
  return as2_behavior::ExecutionStatus::RUNNING;
}

void DetectArucoMarkersBehavior::on_execution_end(const as2_behavior::ExecutionStatus & status)
{
  cam_image_sub_.reset();
  cam_info_sub_.reset();
  aruco_pose_pub_.reset();
  aruco_img_transport_.reset();
  target_ids_.clear();

  RCLCPP_INFO(get_logger(), "DetectArucoMarkersBehavior execution ended");
  return;
}

std::string targetIds2string(const std::vector<uint16_t> & target_ids)
{
  int max_id_show = 10;
  std::string target_ids_str = "";
  if (target_ids.size() == 0) {
    return "All";
  }
  if (target_ids.size() < max_id_show) {
    for (int i = 0; i < target_ids.size(); i++) {
      target_ids_str += std::to_string(target_ids[i]);
      if (i != target_ids.size() - 1) {
        target_ids_str += ", ";
      }
    }
  } else {
    target_ids_str = std::to_string(target_ids[0]);
    target_ids_str += ", ... , ";
    target_ids_str += std::to_string(target_ids[target_ids.size() - 1]);
  }
  return target_ids_str;
}