mujoco_ros_utils
ImagePublisher.cpp
Go to the documentation of this file.
1 #include "ImagePublisher.h"
2 
3 #include <sensor_msgs/CameraInfo.h>
4 #include <sensor_msgs/Image.h>
5 
6 #include <mujoco/mujoco.h>
7 
8 #include <iostream>
9 
10 namespace MujocoRosUtils
11 {
12 
14 {
15  mjpPlugin plugin;
16  mjp_defaultPlugin(&plugin);
17 
18  plugin.name = "MujocoRosUtils::ImagePublisher";
19  plugin.capabilityflags |= mjPLUGIN_SENSOR;
20 
21  const char * attributes[] = {"frame_id", "color_topic_name", "depth_topic_name", "info_topic_name", "height",
22  "width", "publish_rate"};
23 
24  plugin.nattribute = sizeof(attributes) / sizeof(attributes[0]);
25  plugin.attributes = attributes;
26 
27  plugin.nstate = +[](const mjModel *, // m
28  int // plugin_id
29  ) { return 0; };
30 
31  plugin.nsensordata = +[](const mjModel *, // m
32  int, // plugin_id
33  int // sensor_id
34  ) { return 0; };
35 
36  plugin.needstage = mjSTAGE_VEL;
37 
38  plugin.init = +[](const mjModel * m, mjData * d, int plugin_id)
39  {
40  auto * plugin_instance = ImagePublisher::Create(m, d, plugin_id);
41  if(!plugin_instance)
42  {
43  return -1;
44  }
45  d->plugin_data[plugin_id] = reinterpret_cast<uintptr_t>(plugin_instance);
46  return 0;
47  };
48 
49  plugin.destroy = +[](mjData * d, int plugin_id)
50  {
51  auto * plugin_instance = reinterpret_cast<class ImagePublisher *>(d->plugin_data[plugin_id]);
52  plugin_instance->free();
53  delete reinterpret_cast<ImagePublisher *>(d->plugin_data[plugin_id]);
54  d->plugin_data[plugin_id] = 0;
55  };
56 
57  plugin.reset = +[](const mjModel * m, double *, // plugin_state
58  void * plugin_data, int plugin_id)
59  {
60  auto * plugin_instance = reinterpret_cast<class ImagePublisher *>(plugin_data);
61  plugin_instance->reset(m, plugin_id);
62  };
63 
64  plugin.compute = +[](const mjModel * m, mjData * d, int plugin_id, int // capability_bit
65  )
66  {
67  auto * plugin_instance = reinterpret_cast<class ImagePublisher *>(d->plugin_data[plugin_id]);
68  plugin_instance->compute(m, d, plugin_id);
69  };
70 
71  mjp_registerPlugin(&plugin);
72 }
73 
74 ImagePublisher * ImagePublisher::Create(const mjModel * m, mjData * d, int plugin_id)
75 {
76  // frame_id
77  const char * frame_id_char = mj_getPluginConfig(m, plugin_id, "frame_id");
78  std::string frame_id = "";
79  if(strlen(frame_id_char) > 0)
80  {
81  frame_id = std::string(frame_id_char);
82  }
83 
84  // color_topic_name
85  const char * color_topic_name_char = mj_getPluginConfig(m, plugin_id, "color_topic_name");
86  std::string color_topic_name = "";
87  if(strlen(color_topic_name_char) > 0)
88  {
89  color_topic_name = std::string(color_topic_name_char);
90  }
91 
92  // depth_topic_name
93  const char * depth_topic_name_char = mj_getPluginConfig(m, plugin_id, "depth_topic_name");
94  std::string depth_topic_name = "";
95  if(strlen(depth_topic_name_char) > 0)
96  {
97  depth_topic_name = std::string(depth_topic_name_char);
98  }
99 
100  // info_topic_name
101  const char * info_topic_name_char = mj_getPluginConfig(m, plugin_id, "info_topic_name");
102  std::string info_topic_name = "";
103  if(strlen(info_topic_name_char) > 0)
104  {
105  info_topic_name = std::string(info_topic_name_char);
106  }
107 
108  // height
109  const char * height_char = mj_getPluginConfig(m, plugin_id, "height");
110  int height = 240;
111  if(strlen(height_char) > 0)
112  {
113  height = static_cast<int>(strtol(height_char, nullptr, 10));
114  }
115  if(height <= 0)
116  {
117  mju_error("[ImagePublisher] `height` must be positive.");
118  return nullptr;
119  }
120 
121  // width
122  const char * width_char = mj_getPluginConfig(m, plugin_id, "width");
123  int width = 320;
124  if(strlen(width_char) > 0)
125  {
126  width = static_cast<int>(strtol(width_char, nullptr, 10));
127  }
128  if(width <= 0)
129  {
130  mju_error("[ImagePublisher] `width` must be positive.");
131  return nullptr;
132  }
133 
134  // publish_rate
135  const char * publish_rate_char = mj_getPluginConfig(m, plugin_id, "publish_rate");
136  mjtNum publish_rate = 30.0;
137  if(strlen(publish_rate_char) > 0)
138  {
139  publish_rate = strtod(publish_rate_char, nullptr);
140  }
141  if(publish_rate <= 0)
142  {
143  mju_error("[ImagePublisher] `publish_rate` must be positive.");
144  return nullptr;
145  }
146 
147  // Set sensor_id
148  int sensor_id = 0;
149  for(; sensor_id < m->nsensor; sensor_id++)
150  {
151  if(m->sensor_type[sensor_id] == mjSENS_PLUGIN && m->sensor_plugin[sensor_id] == plugin_id)
152  {
153  break;
154  }
155  }
156  if(sensor_id == m->nsensor)
157  {
158  mju_error("[ImagePublisher] Plugin not found in sensors.");
159  return nullptr;
160  }
161  if(m->sensor_objtype[sensor_id] != mjOBJ_CAMERA)
162  {
163  mju_error("[ImagePublisher] Plugin must be attached to a camera.");
164  return nullptr;
165  }
166 
167  std::cout << "[ImagePublisher] Create." << std::endl;
168 
169  return new ImagePublisher(m, d, sensor_id, frame_id, color_topic_name, depth_topic_name, info_topic_name, height,
170  width, publish_rate);
171 }
172 
174  mjData *, // d
175  int sensor_id,
176  const std::string & frame_id,
177  std::string color_topic_name,
178  std::string depth_topic_name,
179  std::string info_topic_name,
180  int height,
181  int width,
182  mjtNum publish_rate)
183 : sensor_id_(sensor_id), camera_id_(m->sensor_objid[sensor_id]), frame_id_(frame_id),
184  publish_skip_(std::max(static_cast<int>(1.0 / (publish_rate * m->opt.timestep)), 1)), viewport_({0, 0, width, height})
185 {
186  std::string camera_name = std::string(mj_id2name(m, mjOBJ_CAMERA, camera_id_));
187  if(frame_id_.empty())
188  {
189  frame_id_ = camera_name;
190  }
191  if(color_topic_name.empty())
192  {
193  color_topic_name = "mujoco/" + camera_name + "/color";
194  }
195  if(depth_topic_name.empty())
196  {
197  depth_topic_name = "mujoco/" + camera_name + "/depth";
198  }
199  if(info_topic_name.empty())
200  {
201  info_topic_name = "mujoco/" + camera_name + "/camera_info";
202  }
203 
204  // Init OpenGL
205  if(!glfwInit())
206  {
207  mju_error("[ImagePublisher] Could not initialize GLFW.");
208  }
209 
210  // Create invisible window, single-buffered
211  glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
212  glfwWindowHint(GLFW_DOUBLEBUFFER, GLFW_FALSE);
213  window_ = glfwCreateWindow(viewport_.width, viewport_.height, "MujocoRosUtils::ImagePublisher", nullptr, nullptr);
214  if(!window_)
215  {
216  mju_error("[ImagePublisher] Could not create GLFW window.");
217  }
218 
219  // Make context current
220  // \todo Is it OK to override the current context of OpenGL?
221  glfwMakeContextCurrent(window_);
222 
223  // Init default data for visualization structures
224  mjv_defaultCamera(&camera_);
225  mjv_defaultOption(&option_);
226  mjv_defaultScene(&scene_);
227  mjr_defaultContext(&context_);
228 
229  // Create scene and context
230  mjv_makeScene(m, &scene_, 1000);
231  mjr_makeContext(m, &context_, mjFONTSCALE_100);
232 
233  // Init camera
234  camera_.type = mjCAMERA_FIXED;
235  camera_.fixedcamid = camera_id_;
236 
237  mjr_setBuffer(mjFB_OFFSCREEN, &context_);
238  if(context_.currentBuffer != mjFB_OFFSCREEN)
239  {
240  mju_error("[ImagePublisher] Offscreen rendering not supported, using default/window framebuffer.");
241  }
242 
243  // Allocate buffer
244  size_t color_buffer_size = sizeof(unsigned char) * 3 * viewport_.width * viewport_.height;
245  size_t depth_buffer_size = sizeof(float) * viewport_.width * viewport_.height;
246  color_buffer_ = static_cast<unsigned char *>(std::malloc(color_buffer_size));
247  depth_buffer_ = static_cast<float *>(std::malloc(depth_buffer_size));
248  color_buffer_flipped_ = static_cast<unsigned char *>(std::malloc(color_buffer_size));
249  depth_buffer_flipped_ = static_cast<float *>(std::malloc(depth_buffer_size));
250 
251  // Init ROS
252  int argc = 0;
253  char ** argv = nullptr;
254  if(!ros::isInitialized())
255  {
256  ros::init(argc, argv, "mujoco_ros", ros::init_options::NoSigintHandler);
257  }
258 
259  nh_ = std::make_shared<ros::NodeHandle>();
260  color_pub_ = nh_->advertise<sensor_msgs::Image>(color_topic_name, 1);
261  depth_pub_ = nh_->advertise<sensor_msgs::Image>(depth_topic_name, 1);
262  info_pub_ = nh_->advertise<sensor_msgs::CameraInfo>(info_topic_name, 1);
263 }
264 
265 void ImagePublisher::reset(const mjModel *, // m
266  int // plugin_id
267 )
268 {
269 }
270 
271 void ImagePublisher::compute(const mjModel * m, mjData * d, int // plugin_id
272 )
273 {
274  sim_cnt_++;
275  if(sim_cnt_ % publish_skip_ != 0)
276  {
277  return;
278  }
279 
280  // Make context current
281  // \todo Is it OK to override the current context of OpenGL?
282  glfwMakeContextCurrent(window_);
283 
284  // Update abstract scene
285  mjv_updateScene(m, d, &option_, nullptr, &camera_, mjCAT_STATIC | mjCAT_DYNAMIC, &scene_);
286 
287  // Render scene in offscreen buffer
288  mjr_render(viewport_, &scene_, &context_);
289 
290  // Read rgb and depth pixels
291  mjr_readPixels(color_buffer_, depth_buffer_, viewport_, &context_);
292 
293  // Convert raw depth to distance and flip images
294  float near = static_cast<float>(m->vis.map.znear * m->stat.extent);
295  float far = static_cast<float>(m->vis.map.zfar * m->stat.extent);
296  for(int w = 0; w < viewport_.width; w++)
297  {
298  for(int h = 0; h < viewport_.height; h++)
299  {
300  int idx = h * viewport_.width + w;
301  int idx_flipped = (viewport_.height - 1 - h) * viewport_.width + w;
302 
303  // See
304  // https://github.com/deepmind/mujoco/blob/631b16e7ad192df936195658fe79f2ada85f755c/python/mujoco/renderer.py#L175-L178
305  depth_buffer_[idx] = near / (1.0f - depth_buffer_[idx] * (1.0f - near / far));
306 
307  for(int c = 0; c < 3; c++)
308  {
309  color_buffer_flipped_[3 * idx_flipped + c] = color_buffer_[3 * idx + c];
310  }
311  depth_buffer_flipped_[idx_flipped] = depth_buffer_[idx];
312  }
313  }
314 
315  // Publish topic
316  ros::Time stamp_now = ros::Time::now();
317 
318  sensor_msgs::Image color_msg;
319  color_msg.header.stamp = stamp_now;
320  color_msg.header.frame_id = frame_id_;
321  color_msg.height = viewport_.height;
322  color_msg.width = viewport_.width;
323  color_msg.encoding = "rgb8";
324  color_msg.is_bigendian = 0;
325  color_msg.step = static_cast<unsigned int>(sizeof(unsigned char) * 3 * viewport_.width);
326  color_msg.data.resize(sizeof(unsigned char) * 3 * viewport_.width * viewport_.height);
327  std::memcpy(&color_msg.data[0], color_buffer_flipped_,
328  sizeof(unsigned char) * 3 * viewport_.width * viewport_.height);
329  color_pub_.publish(color_msg);
330 
331  sensor_msgs::Image depth_msg;
332  depth_msg.header.stamp = stamp_now;
333  depth_msg.header.frame_id = frame_id_;
334  depth_msg.height = viewport_.height;
335  depth_msg.width = viewport_.width;
336  depth_msg.encoding = "32FC1";
337  depth_msg.is_bigendian = 0;
338  depth_msg.step = static_cast<unsigned int>(sizeof(float) * viewport_.width);
339  depth_msg.data.resize(sizeof(float) * viewport_.width * viewport_.height);
340  std::memcpy(&depth_msg.data[0], depth_buffer_flipped_, sizeof(float) * viewport_.width * viewport_.height);
341  depth_pub_.publish(depth_msg);
342 
343  sensor_msgs::CameraInfo info_msg;
344  info_msg.header.stamp = stamp_now;
345  info_msg.header.frame_id = frame_id_;
346  info_msg.height = viewport_.height;
347  info_msg.width = viewport_.width;
348  info_msg.distortion_model = "plumb_bob";
349  info_msg.D.resize(5, 0.0);
350  info_msg.K.fill(0.0);
351  info_msg.R.fill(0.0);
352  info_msg.P.fill(0.0);
353  double focal_scaling = (1.0 / std::tan((m->cam_fovy[camera_id_] * M_PI / 180.0) / 2.0)) * viewport_.height / 2.0;
354  info_msg.K[0] = info_msg.P[0] = focal_scaling;
355  info_msg.K[2] = info_msg.P[2] = static_cast<double>(viewport_.width) / 2.0;
356  info_msg.K[4] = info_msg.P[5] = focal_scaling;
357  info_msg.K[5] = info_msg.P[6] = static_cast<double>(viewport_.height) / 2.0;
358  info_msg.K[8] = info_msg.P[10] = 1.0;
359  info_pub_.publish(info_msg);
360 }
361 
363 {
364  std::free(color_buffer_);
365  std::free(depth_buffer_);
366  std::free(color_buffer_flipped_);
367  std::free(depth_buffer_flipped_);
368 
369  mjr_freeContext(&context_);
370  mjv_freeScene(&scene_);
371 
372  glfwDestroyWindow(window_);
373 }
374 
375 } // namespace MujocoRosUtils
MujocoRosUtils::ImagePublisher::depth_buffer_
float * depth_buffer_
Definition: ImagePublisher.h:94
MujocoRosUtils::ImagePublisher::frame_id_
std::string frame_id_
Frame ID of topics header or TF parent.
Definition: ImagePublisher.h:83
MujocoRosUtils::ImagePublisher::RegisterPlugin
static void RegisterPlugin()
Register plugin.
Definition: ImagePublisher.cpp:13
MujocoRosUtils::ImagePublisher::publish_skip_
int publish_skip_
Iteration interval to skip ROS publish.
Definition: ImagePublisher.h:86
MujocoRosUtils::ImagePublisher::Create
static ImagePublisher * Create(const mjModel *m, mjData *d, int plugin_id)
Create an instance.
Definition: ImagePublisher.cpp:74
MujocoRosUtils::ImagePublisher::camera_
mjvCamera camera_
Definition: ImagePublisher.h:102
MujocoRosUtils::ImagePublisher::reset
void reset(const mjModel *m, int plugin_id)
Reset.
Definition: ImagePublisher.cpp:265
MujocoRosUtils::ImagePublisher::option_
mjvOption option_
Definition: ImagePublisher.h:103
MujocoRosUtils::ImagePublisher::viewport_
mjrRect viewport_
Definition: ImagePublisher.h:105
MujocoRosUtils::ImagePublisher::sim_cnt_
int sim_cnt_
Iteration count of simulation.
Definition: ImagePublisher.h:89
MujocoRosUtils::ImagePublisher::window_
GLFWwindow * window_
Definition: ImagePublisher.h:106
MujocoRosUtils::ImagePublisher::scene_
mjvScene scene_
Definition: ImagePublisher.h:101
MujocoRosUtils::ImagePublisher::color_pub_
ros::Publisher color_pub_
Definition: ImagePublisher.h:112
MujocoRosUtils::ImagePublisher::compute
void compute(const mjModel *m, mjData *d, int plugin_id)
Compute.
Definition: ImagePublisher.cpp:271
MujocoRosUtils::ImagePublisher::free
void free()
Free buffer.
Definition: ImagePublisher.cpp:362
MujocoRosUtils::ImagePublisher::info_pub_
ros::Publisher info_pub_
Definition: ImagePublisher.h:114
MujocoRosUtils::ImagePublisher::camera_id_
int camera_id_
Camera ID.
Definition: ImagePublisher.h:80
MujocoRosUtils::ImagePublisher::depth_pub_
ros::Publisher depth_pub_
Definition: ImagePublisher.h:113
MujocoRosUtils::ImagePublisher::context_
mjrContext context_
Definition: ImagePublisher.h:104
MujocoRosUtils::ImagePublisher::color_buffer_
unsigned char * color_buffer_
Definition: ImagePublisher.h:93
MujocoRosUtils::ImagePublisher::color_buffer_flipped_
unsigned char * color_buffer_flipped_
Definition: ImagePublisher.h:95
ImagePublisher.h
MujocoRosUtils::ImagePublisher
Plugin to publish topics of color and depth images.
Definition: ImagePublisher.h:18
MujocoRosUtils::ImagePublisher::ImagePublisher
ImagePublisher(ImagePublisher &&)=default
Copy constructor.
MujocoRosUtils::ImagePublisher::depth_buffer_flipped_
float * depth_buffer_flipped_
Definition: ImagePublisher.h:96
MujocoRosUtils
Definition: ActuatorCommand.cpp:7