optical_flow.cpp
Go to the documentation of this file.
1 /*
2  * Optical Flow node for PX4
3  * Copyright (C) 2018 Copter Express Technologies
4  *
5  * Author: Oleg Kalachev <okalachev@gmail.com>
6  *
7  * Distributed under MIT License (available at https://opensource.org/licenses/MIT).
8  * The above copyright notice and this permission notice shall be included in all
9  * copies or substantial portions of the Software.
10  */
11 
12 #include <vector>
13 #include <cmath>
14 #include <nodelet/nodelet.h>
17 #include <cv_bridge/cv_bridge.h>
18 #include <opencv2/opencv.hpp>
19 #include <tf/transform_datatypes.h>
20 #include <tf2/exceptions.h>
21 #include <tf2/convert.h>
22 #include <tf2/utils.h>
25 #include <mavros_msgs/OpticalFlowRad.h>
26 #include <sensor_msgs/Imu.h>
27 #include <geometry_msgs/Vector3Stamped.h>
28 #include <geometry_msgs/PointStamped.h>
29 #include <geometry_msgs/TwistStamped.h>
30 
31 using cv::Mat;
32 
34 {
35 public:
37  camera_matrix_(3, 3, CV_64F)
38  {}
39 
40 private:
46  mavros_msgs::OpticalFlowRad flow_;
47  int roi_px_;
48  double roi_rad_;
49  cv::Rect roi_;
50  Mat hann_;
51  Mat prev_, curr_;
53  std::unique_ptr<tf2_ros::Buffer> tf_buffer_;
54  std::unique_ptr<tf2_ros::TransformListener> tf_listener_;
56 
57  void onInit()
58  {
62  image_transport::ImageTransport it_priv(nh_priv);
63 
64  tf_buffer_.reset(new tf2_ros::Buffer());
65  tf_listener_.reset(new tf2_ros::TransformListener(*tf_buffer_, nh));
66 
67  local_frame_id_ = nh.param<std::string>("mavros/local_position/tf/frame_id", "map");
68  fcu_frame_id_ = nh.param<std::string>("mavros/local_position/tf/child_frame_id", "base_link");
69  roi_px_ = nh_priv.param("roi", 128);
70  roi_rad_ = nh_priv.param("roi_rad", 0.0);
71  calc_flow_gyro_ = nh_priv.param("calc_flow_gyro", false);
72 
73  img_sub_ = it.subscribeCamera("image_raw", 1, &OpticalFlow::flow, this);
74  img_pub_ = it_priv.advertise("debug", 1);
75  flow_pub_ = nh.advertise<mavros_msgs::OpticalFlowRad>("mavros/px4flow/raw/send", 1);
76  velo_pub_ = nh_priv.advertise<geometry_msgs::TwistStamped>("angular_velocity", 1);
77  shift_pub_ = nh_priv.advertise<geometry_msgs::Vector3Stamped>("shift", 1);
78 
79  flow_.integrated_xgyro = NAN; // no IMU available
80  flow_.integrated_ygyro = NAN;
81  flow_.integrated_zgyro = NAN;
82  flow_.time_delta_distance_us = 0;
83  flow_.distance = -1; // no distance sensor available
84  flow_.temperature = 0;
85 
86  NODELET_INFO("Optical Flow initialized");
87  }
88 
89  void parseCameraInfo(const sensor_msgs::CameraInfoConstPtr &cinfo) {
90  for (int i = 0; i < 3; ++i) {
91  for (int j = 0; j < 3; ++j) {
92  camera_matrix_.at<double>(i, j) = cinfo->K[3 * i + j];
93  }
94  }
95  dist_coeffs_ = cv::Mat(cinfo->D, true);
96  }
97 
98  void drawFlow(Mat& frame, double x, double y, double quality) const
99  {
100  double brightness = (1 - quality) * 25;;
101  cv::Scalar color(brightness, brightness, brightness);
102  double radius = std::sqrt(x * x + y * y);
103 
104  // draw a circle and line indicating the shift direction...
105  cv::Point center(frame.cols >> 1, frame.rows >> 1);
106  cv::circle(frame, center, (int)(radius*5), color, 3, cv::LINE_AA);
107  cv::line(frame, center, cv::Point(center.x + (int)(x*5), center.y + (int)(y*5)), color, 3, cv::LINE_AA);
108  }
109 
110  void flow(const sensor_msgs::ImageConstPtr& msg, const sensor_msgs::CameraInfoConstPtr& cinfo)
111  {
112  parseCameraInfo(cinfo);
113 
114  auto img = cv_bridge::toCvShare(msg, "mono8")->image;
115 
116  if (roi_.width == 0) { // ROI is not calculated
117  // Calculate ROI
118  if (roi_rad_ != 0) {
119  std::vector<cv::Point3f> object_points = {
120  cv::Point3f(-sin(roi_rad_ / 2), -sin(roi_rad_ / 2), cos(roi_rad_ / 2)),
121  cv::Point3f(sin(roi_rad_ / 2), sin(roi_rad_ / 2), cos(roi_rad_ / 2)),
122  };
123 
124  std::vector<double> vec { 0, 0, 0 };
125  std::vector<cv::Point2f> img_points;
126  cv::projectPoints(object_points, vec, vec, camera_matrix_, dist_coeffs_, img_points);
127 
128  roi_ = cv::Rect(cv::Point2i(round(img_points[0].x), round(img_points[0].y)),
129  cv::Point2i(round(img_points[1].x), round(img_points[1].y)));
130 
131  ROS_INFO("ROI: %d %d - %d %d ", roi_.tl().x, roi_.tl().y, roi_.br().x, roi_.br().y);
132 
133  } else if (roi_px_ != 0) {
134  roi_ = cv::Rect((msg->width / 2 - roi_px_ / 2), (msg->height / 2 - roi_px_ / 2), roi_px_, roi_px_);
135  }
136  }
137 
138  if (roi_.width != 0) { // ROI is set
139  // Apply ROI
140  img = img(roi_);
141  }
142 
143  img.convertTo(curr_, CV_32F);
144 
145  if (prev_.empty()) {
146  prev_ = curr_.clone();
147  prev_stamp_ = msg->header.stamp;
148  cv::createHanningWindow(hann_, curr_.size(), CV_32F);
149 
150  } else {
151  double response;
152  cv::Point2d shift = cv::phaseCorrelate(prev_, curr_, hann_, &response);
153 
154  // Publish raw shift in pixels
155  static geometry_msgs::Vector3Stamped shift_vec;
156  shift_vec.header.stamp = msg->header.stamp;
157  shift_vec.header.frame_id = msg->header.frame_id;
158  shift_vec.vector.x = shift.x;
159  shift_vec.vector.y = shift.y;
160  shift_pub_.publish(shift_vec);
161 
162  // Undistort flow in pixels
163  uint32_t flow_center_x = msg->width / 2;
164  uint32_t flow_center_y = msg->height / 2;
165  shift.x += flow_center_x;
166  shift.y += flow_center_y;
167 
168  std::vector<cv::Point2d> points_dist = { shift };
169  std::vector<cv::Point2d> points_undist(1);
170 
171  cv::undistortPoints(points_dist, points_undist, camera_matrix_, dist_coeffs_, cv::noArray(), camera_matrix_);
172  points_undist[0].x -= flow_center_x;
173  points_undist[0].y -= flow_center_y;
174 
175  // Calculate flow in radians
176  double focal_length_x = camera_matrix_.at<double>(0, 0);
177  double focal_length_y = camera_matrix_.at<double>(1, 1);
178  double flow_x = atan2(points_undist[0].x, focal_length_x);
179  double flow_y = atan2(points_undist[0].y, focal_length_y);
180 
181  // // Convert to FCU frame
182  static geometry_msgs::Vector3Stamped flow_camera, flow_fcu;
183  flow_camera.header.frame_id = msg->header.frame_id;
184  flow_camera.header.stamp = msg->header.stamp;
185  flow_camera.vector.x = flow_y; // +y means counter-clockwise rotation around Y axis
186  flow_camera.vector.y = -flow_x; // +x means clockwise rotation around X axis
187  try {
188  tf_buffer_->transform(flow_camera, flow_fcu, fcu_frame_id_);
189  } catch (const tf2::TransformException& e) {
190  // transform is not available yet
191  return;
192  }
193 
194  // Calculate integration time
195  ros::Duration integration_time = msg->header.stamp - prev_stamp_;
196  uint32_t integration_time_us = integration_time.toSec() * 1.0e6;
197 
198  if (calc_flow_gyro_) {
199  try {
200  auto flow_gyro_camera = calcFlowGyro(msg->header.frame_id, prev_stamp_, msg->header.stamp);
201  static geometry_msgs::Vector3Stamped flow_gyro_fcu;
202  tf_buffer_->transform(flow_gyro_camera, flow_gyro_fcu, fcu_frame_id_);
203  flow_.integrated_xgyro = flow_gyro_fcu.vector.x;
204  flow_.integrated_ygyro = flow_gyro_fcu.vector.y;
205  flow_.integrated_zgyro = flow_gyro_fcu.vector.z;
206  } catch (const tf2::TransformException& e) {
207  // Invalidate previous frame
208  prev_.release();
209  return;
210  }
211  }
212 
213  // Publish flow in fcu frame
214  flow_.header.stamp = /*prev_stamp_*/ msg->header.stamp;
215  flow_.integration_time_us = integration_time_us;
216  flow_.integrated_x = flow_fcu.vector.x;
217  flow_.integrated_y = flow_fcu.vector.y;
218  flow_.quality = (uint8_t)(response * 255);
219  flow_pub_.publish(flow_);
220 
221  // Publish debug image
222  if (img_pub_.getNumSubscribers() > 0) {
223  // publish debug image
224  drawFlow(img, shift_vec.vector.x, shift_vec.vector.y, response);
225  cv_bridge::CvImage out_msg;
226  out_msg.header.frame_id = msg->header.frame_id;
227  out_msg.header.stamp = msg->header.stamp;
229  out_msg.image = img;
230  img_pub_.publish(out_msg.toImageMsg());
231  }
232 
233  // Publish estimated angular velocity
234  static geometry_msgs::TwistStamped velo;
235  velo.header.stamp = msg->header.stamp;
236  velo.header.frame_id = fcu_frame_id_;
237  velo.twist.angular.x = flow_.integrated_x / integration_time.toSec();
238  velo.twist.angular.y = flow_.integrated_y / integration_time.toSec();
239  velo_pub_.publish(velo);
240 
241  prev_ = curr_.clone();
242  prev_stamp_ = msg->header.stamp;
243  }
244  }
245 
246  geometry_msgs::Vector3Stamped calcFlowGyro(const std::string& frame_id, const ros::Time& prev, const ros::Time& curr)
247  {
248  tf2::Quaternion prev_rot, curr_rot;
249  tf2::fromMsg(tf_buffer_->lookupTransform(frame_id, local_frame_id_, prev).transform.rotation, prev_rot);
250  tf2::fromMsg(tf_buffer_->lookupTransform(frame_id, local_frame_id_, curr, ros::Duration(0.1)).transform.rotation, curr_rot);
251 
252  geometry_msgs::Vector3Stamped flow;
253  flow.header.frame_id = frame_id;
254  flow.header.stamp = curr;
255  // https://en.wikipedia.org/wiki/Rotation_formalisms_in_three_dimensions#Quaternion_↔_angular_velocities
256  auto diff = ((curr_rot - prev_rot) * prev_rot.inverse()) * 2.0f;
257  flow.vector.x = -diff.x();
258  flow.vector.y = -diff.y();
259  flow.vector.z = -diff.z();
260 
261  return flow;
262  }
263 };
264 
ros::Publisher flow_pub_
CvImageConstPtr toCvShare(const sensor_msgs::ImageConstPtr &source, const std::string &encoding=std::string())
ros::Time prev_stamp_
ros::NodeHandle nh
image_transport::CameraSubscriber img_sub_
uint32_t getNumSubscribers() const
CameraSubscriber subscribeCamera(const std::string &base_topic, uint32_t queue_size, const CameraSubscriber::Callback &callback, const ros::VoidPtr &tracked_object=ros::VoidPtr(), const TransportHints &transport_hints=TransportHints())
ros::NodeHandle & getNodeHandle() const
std::string encoding
Publisher advertise(const std::string &base_topic, uint32_t queue_size, bool latch=false)
std::string local_frame_id_
std::unique_ptr< tf2_ros::TransformListener > tf_listener_
ros::NodeHandle & getPrivateNodeHandle() const
mavlink::common::MAV_FRAME frame
std::string fcu_frame_id_
mavros_msgs::OpticalFlowRad flow_
ros::Publisher shift_pub_
sensor_msgs::ImagePtr toImageMsg() const
bool param(const std::string &param_name, T &param_val, const T &default_val) const
void publish(const boost::shared_ptr< M > &message) const
std::string frame_id
#define ROS_INFO(...)
void fromMsg(const A &, B &b)
Quaternion inverse() const
std::unique_ptr< tf2_ros::Buffer > tf_buffer_
void parseCameraInfo(const sensor_msgs::CameraInfoConstPtr &cinfo)
Publisher advertise(const std::string &topic, uint32_t queue_size, bool latch=false)
geometry_msgs::Vector3Stamped calcFlowGyro(const std::string &frame_id, const ros::Time &prev, const ros::Time &curr)
int16_t x
Definition: rc.cpp:247
void flow(const sensor_msgs::ImageConstPtr &msg, const sensor_msgs::CameraInfoConstPtr &cinfo)
void publish(const sensor_msgs::Image &message) const
int16_t y
Definition: rc.cpp:247
cv::Rect roi_
#define NODELET_INFO(...)
ros::Publisher velo_pub_
INLINE Rall1d< T, V, S > atan2(const Rall1d< T, V, S > &y, const Rall1d< T, V, S > &x)
double roi_rad_
image_transport::Publisher img_pub_
bool calc_flow_gyro_
void drawFlow(Mat &frame, double x, double y, double quality) const
INLINE Rall1d< T, V, S > cos(const Rall1d< T, V, S > &arg)
#define PLUGINLIB_EXPORT_CLASS(class_type, base_class_type)
INLINE Rall1d< T, V, S > sin(const Rall1d< T, V, S > &arg)
std_msgs::Header header
def response


clover
Author(s): Oleg Kalachev , Artem Smirnov
autogenerated on Mon Feb 28 2022 22:08:29