00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038
00039
00040
00041
00042
00043
00044 #include <ros/ros.h>
00045 #include <nodelet/nodelet.h>
00046 #include <tf2_ros/transform_listener.h>
00047 #include <tf2_ros/transform_broadcaster.h>
00048 #include <image_rotate/ImageRotateConfig.h>
00049 #include <geometry_msgs/Vector3Stamped.h>
00050 #include <image_transport/image_transport.h>
00051 #include <cv_bridge/cv_bridge.h>
00052 #include <opencv2/imgproc/imgproc.hpp>
00053 #include <dynamic_reconfigure/server.h>
00054 #include <math.h>
00055 #include <tf2_geometry_msgs/tf2_geometry_msgs.h>
00056
00057 namespace image_rotate {
00058 class ImageRotateNodelet : public nodelet::Nodelet
00059 {
00060 tf2_ros::Buffer tf_buffer_;
00061 boost::shared_ptr<tf2_ros::TransformListener> tf_sub_;
00062 tf2_ros::TransformBroadcaster tf_pub_;
00063
00064 image_rotate::ImageRotateConfig config_;
00065 dynamic_reconfigure::Server<image_rotate::ImageRotateConfig> srv;
00066
00067 image_transport::Publisher img_pub_;
00068 image_transport::Subscriber img_sub_;
00069 image_transport::CameraSubscriber cam_sub_;
00070
00071 geometry_msgs::Vector3Stamped target_vector_;
00072 geometry_msgs::Vector3Stamped source_vector_;
00073
00074 boost::shared_ptr<image_transport::ImageTransport> it_;
00075 ros::NodeHandle nh_;
00076
00077 int subscriber_count_;
00078 double angle_;
00079 ros::Time prev_stamp_;
00080
00081 void reconfigureCallback(image_rotate::ImageRotateConfig &new_config, uint32_t level)
00082 {
00083 config_ = new_config;
00084 target_vector_.vector.x = config_.target_x;
00085 target_vector_.vector.y = config_.target_y;
00086 target_vector_.vector.z = config_.target_z;
00087
00088 source_vector_.vector.x = config_.source_x;
00089 source_vector_.vector.y = config_.source_y;
00090 source_vector_.vector.z = config_.source_z;
00091 if (subscriber_count_)
00092 {
00093 unsubscribe();
00094 subscribe();
00095 }
00096 }
00097
00098 const std::string &frameWithDefault(const std::string &frame, const std::string &image_frame)
00099 {
00100 if (frame.empty())
00101 return image_frame;
00102 return frame;
00103 }
00104
00105 void imageCallbackWithInfo(const sensor_msgs::ImageConstPtr& msg, const sensor_msgs::CameraInfoConstPtr& cam_info)
00106 {
00107 do_work(msg, cam_info->header.frame_id);
00108 }
00109
00110 void imageCallback(const sensor_msgs::ImageConstPtr& msg)
00111 {
00112 do_work(msg, msg->header.frame_id);
00113 }
00114
00115 void do_work(const sensor_msgs::ImageConstPtr& msg, const std::string input_frame_from_msg)
00116 {
00117 try
00118 {
00119 std::string input_frame_id = frameWithDefault(config_.input_frame_id, input_frame_from_msg);
00120 std::string target_frame_id = frameWithDefault(config_.target_frame_id, input_frame_from_msg);
00121 std::string source_frame_id = frameWithDefault(config_.source_frame_id, input_frame_from_msg);
00122
00123
00124 target_vector_.header.stamp = msg->header.stamp;
00125 target_vector_.header.frame_id = target_frame_id;
00126 geometry_msgs::Vector3Stamped target_vector_transformed;
00127 geometry_msgs::TransformStamped transform = tf_buffer_.lookupTransform(target_frame_id, input_frame_id, msg->header.stamp, msg->header.stamp-prev_stamp_);
00128 tf2::doTransform(target_vector_, target_vector_transformed, transform);
00129
00130
00131 source_vector_.header.stamp = msg->header.stamp;
00132 source_vector_.header.frame_id = source_frame_id;
00133 geometry_msgs::Vector3Stamped source_vector_transformed;
00134 transform = tf_buffer_.lookupTransform(source_frame_id, input_frame_id, msg->header.stamp, msg->header.stamp-prev_stamp_);
00135 tf2::doTransform(source_vector_, source_vector_transformed, transform);
00136
00137
00138
00139
00140
00141
00142
00143 double angle = angle_;
00144 if ((target_vector_transformed.vector.x != 0 || target_vector_transformed.vector.y != 0) &&
00145 (source_vector_transformed.vector.x != 0 || source_vector_transformed.vector.y != 0))
00146 {
00147 angle = atan2(target_vector_transformed.vector.y, target_vector_transformed.vector.x);
00148 angle -= atan2(source_vector_transformed.vector.y, source_vector_transformed.vector.x);
00149 }
00150
00151
00152 if (config_.max_angular_rate == 0)
00153 angle_ = angle;
00154 else
00155 {
00156 double delta = fmod(angle - angle_, 2.0 * M_PI);
00157 if (delta > M_PI)
00158 delta -= 2.0 * M_PI;
00159 else if (delta < - M_PI)
00160 delta += 2.0 * M_PI;
00161
00162 double max_delta = config_.max_angular_rate * (msg->header.stamp - prev_stamp_).toSec();
00163 if (delta > max_delta)
00164 delta = max_delta;
00165 else if (delta < -max_delta)
00166 delta = - max_delta;
00167
00168 angle_ += delta;
00169 }
00170 angle_ = fmod(angle_, 2.0 * M_PI);
00171 }
00172 catch (tf2::TransformException &e)
00173 {
00174 NODELET_ERROR("Transform error: %s", e.what());
00175 }
00176
00177
00178
00179
00180 geometry_msgs::TransformStamped transform;
00181 transform.transform.translation.x = 0;
00182 transform.transform.translation.y = 0;
00183 transform.transform.translation.z = 0;
00184 tf2::convert(tf2::Quaternion(tf2::Vector3(0.0, 0.0, 1.0), angle_), transform.transform.rotation);
00185 transform.header.frame_id = msg->header.frame_id;
00186 transform.child_frame_id = frameWithDefault(config_.output_frame_id, msg->header.frame_id + "_rotated");
00187 transform.header.stamp = msg->header.stamp;
00188 tf_pub_.sendTransform(transform);
00189
00190
00191 try
00192 {
00193
00194 cv::Mat in_image = cv_bridge::toCvShare(msg, msg->encoding)->image;
00195
00196
00197 int max_dim = in_image.cols > in_image.rows ? in_image.cols : in_image.rows;
00198 int min_dim = in_image.cols < in_image.rows ? in_image.cols : in_image.rows;
00199 int noblack_dim = min_dim / sqrt(2);
00200 int diag_dim = sqrt(in_image.cols*in_image.cols + in_image.rows*in_image.rows);
00201 int out_size;
00202 int candidates[] = { noblack_dim, min_dim, max_dim, diag_dim, diag_dim };
00203 int step = config_.output_image_size;
00204 out_size = candidates[step] + (candidates[step + 1] - candidates[step]) * (config_.output_image_size - step);
00205
00206
00207
00208 cv::Mat rot_matrix = cv::getRotationMatrix2D(cv::Point2f(in_image.cols / 2.0, in_image.rows / 2.0), 180 * angle_ / M_PI, 1);
00209 cv::Mat translation = rot_matrix.col(2);
00210 rot_matrix.at<double>(0, 2) += (out_size - in_image.cols) / 2.0;
00211 rot_matrix.at<double>(1, 2) += (out_size - in_image.rows) / 2.0;
00212
00213
00214 cv::Mat out_image;
00215 cv::warpAffine(in_image, out_image, rot_matrix, cv::Size(out_size, out_size));
00216
00217
00218 sensor_msgs::Image::Ptr out_img = cv_bridge::CvImage(msg->header, msg->encoding, out_image).toImageMsg();
00219 out_img->header.frame_id = transform.child_frame_id;
00220 img_pub_.publish(out_img);
00221 }
00222 catch (cv::Exception &e)
00223 {
00224 NODELET_ERROR("Image processing error: %s %s %s %i", e.err.c_str(), e.func.c_str(), e.file.c_str(), e.line);
00225 }
00226
00227 prev_stamp_ = msg->header.stamp;
00228 }
00229
00230 void subscribe()
00231 {
00232 NODELET_DEBUG("Subscribing to image topic.");
00233 if (config_.use_camera_info && config_.input_frame_id.empty())
00234 cam_sub_ = it_->subscribeCamera("image", 3, &ImageRotateNodelet::imageCallbackWithInfo, this);
00235 else
00236 img_sub_ = it_->subscribe("image", 3, &ImageRotateNodelet::imageCallback, this);
00237 }
00238
00239 void unsubscribe()
00240 {
00241 NODELET_DEBUG("Unsubscribing from image topic.");
00242 img_sub_.shutdown();
00243 cam_sub_.shutdown();
00244 }
00245
00246 void connectCb(const image_transport::SingleSubscriberPublisher& ssp)
00247 {
00248 if (subscriber_count_++ == 0) {
00249 subscribe();
00250 }
00251 }
00252
00253 void disconnectCb(const image_transport::SingleSubscriberPublisher&)
00254 {
00255 subscriber_count_--;
00256 if (subscriber_count_ == 0) {
00257 unsubscribe();
00258 }
00259 }
00260
00261 public:
00262 virtual void onInit()
00263 {
00264 nh_ = getNodeHandle();
00265 it_ = boost::shared_ptr<image_transport::ImageTransport>(new image_transport::ImageTransport(nh_));
00266 subscriber_count_ = 0;
00267 angle_ = 0;
00268 prev_stamp_ = ros::Time(0, 0);
00269 tf_sub_.reset(new tf2_ros::TransformListener(tf_buffer_));
00270 image_transport::SubscriberStatusCallback connect_cb = boost::bind(&ImageRotateNodelet::connectCb, this, _1);
00271 image_transport::SubscriberStatusCallback disconnect_cb = boost::bind(&ImageRotateNodelet::disconnectCb, this, _1);
00272 img_pub_ = image_transport::ImageTransport(ros::NodeHandle(nh_, "rotated")).advertise("image", 1, connect_cb, disconnect_cb);
00273
00274 dynamic_reconfigure::Server<image_rotate::ImageRotateConfig>::CallbackType f =
00275 boost::bind(&ImageRotateNodelet::reconfigureCallback, this, _1, _2);
00276 srv.setCallback(f);
00277 }
00278 };
00279 }
00280 #include <pluginlib/class_list_macros.h>
00281 PLUGINLIB_EXPORT_CLASS(image_rotate::ImageRotateNodelet, nodelet::Nodelet);