point_cloud2_color.cpp
Go to the documentation of this file.
00001 
00008 //#define USE_MT_NODE_HANDLE
00009 
00010 #include <boost/bind.hpp>
00011 #include <boost/format.hpp>
00012 #include <cv_bridge/cv_bridge.h>
00013 #include <image_transport/image_transport.h>
00014 #include <limits>
00015 #include <nodelet/nodelet.h>
00016 #include <opencv2/opencv.hpp>
00017 #include <pcl/io/io.h>
00018 #include <pcl_ros/point_cloud.h>
00019 #include <pluginlib/class_list_macros.h>
00020 #include <ros/ros.h>
00021 #include <sensor_msgs/CameraInfo.h>
00022 #include <sensor_msgs/image_encodings.h>
00023 #include <sensor_msgs/PointCloud2.h>
00024 #include <tf/transform_datatypes.h>
00025 #include <tf/transform_listener.h>
00026 
00027 namespace point_cloud_color {
00028 
00029 int getFieldIndex(const sensor_msgs::PointCloud2 &cloud, const std::string &fieldName) {
00030   for (size_t i = 0; i < cloud.fields.size(); i++) {
00031     if (cloud.fields[i].name == fieldName) {
00032       return i;
00033     }
00034   }
00035   return -1;
00036 }
00037 
00038 cv::Mat rotationFromTransform(const tf::Transform &t) {
00039   return (cv::Mat_<double>(3, 3) << t.getBasis()[0][0], t.getBasis()[0][1], t.getBasis()[0][2],
00040                                     t.getBasis()[1][0], t.getBasis()[1][1], t.getBasis()[1][2],
00041                                     t.getBasis()[2][0], t.getBasis()[2][1], t.getBasis()[2][2]);
00042 }
00043 
00044 cv::Mat translationFromTransform(const tf::Transform &t) {
00045   return (cv::Mat_<double>(3, 1) << t.getOrigin()[0], t.getOrigin()[1], t.getOrigin()[2]);
00046 }
00047 
00048 cv::Mat matFromCloud(sensor_msgs::PointCloud2::Ptr cloud, const std::string fieldName, const int numElements) {
00049   const int numPoints = cloud->width * cloud->height;
00050   int fieldIndex = getFieldIndex(*cloud, fieldName);
00051   void *data = static_cast<void *>(&cloud->data[cloud->fields[fieldIndex].offset]);
00052   int matType;
00053   switch (cloud->fields[fieldIndex].datatype) {
00054   case sensor_msgs::PointField::FLOAT32: matType = CV_32FC1; break;
00055   case sensor_msgs::PointField::FLOAT64: matType = CV_64FC1; break;
00056   case sensor_msgs::PointField::UINT8:   matType = CV_8UC1;  break;
00057   default:
00058     assert (0);
00059     break;
00060   }
00061   return cv::Mat(numPoints, numElements, matType, data, cloud->point_step);
00062 }
00063 
00064 sensor_msgs::PointCloud2::Ptr createCloudWithColorFrom(const sensor_msgs::PointCloud2::ConstPtr inCloud) {
00065   // Create a copy to fix fields - use correct count 1 instead of 0.
00066   sensor_msgs::PointCloud2 inCloudFixed = *inCloud;
00067   BOOST_FOREACH(sensor_msgs::PointField &f, inCloudFixed.fields) {
00068     if (f.count == 0) {
00069       f.count = 1;
00070     }
00071   }
00072 
00073   sensor_msgs::PointField rgbField;
00074 //  rgbField.name = "argb";
00075 //  rgbField.datatype = sensor_msgs::PointField::UINT32;
00076   rgbField.name = "rgb";
00077   rgbField.datatype = sensor_msgs::PointField::FLOAT32;
00078   rgbField.count = 1;
00079   rgbField.offset = 0;
00080   sensor_msgs::PointCloud2 rgbCloud;
00081   rgbCloud.header = inCloudFixed.header;
00082   rgbCloud.width = inCloudFixed.width;
00083   rgbCloud.height = inCloudFixed.height;
00084   rgbCloud.fields.push_back(rgbField);
00085   rgbCloud.is_bigendian = inCloudFixed.is_bigendian;
00086   rgbCloud.is_dense = inCloudFixed.is_dense;
00087   rgbCloud.point_step = 4;
00088   rgbCloud.row_step = rgbCloud.width * rgbCloud.point_step;
00089   rgbCloud.data.resize(rgbCloud.row_step * rgbCloud.height, 0);
00090 
00091   sensor_msgs::PointCloud2::Ptr outCloud(new sensor_msgs::PointCloud2);
00092   pcl::concatenateFields(inCloudFixed, rgbCloud, *outCloud);
00093   return outCloud;
00094 }
00095 
00099 class PointCloudColor : public nodelet::Nodelet {
00100 public:
00101   PointCloudColor();
00102   virtual ~PointCloudColor();
00103   void onInit();
00104 private:
00105   tf::TransformListener transformListener;
00106   std::vector<image_transport::CameraSubscriber> cameraSubscribers;
00107   ros::Subscriber pointCloudSub;
00108   ros::Publisher pointCloudPub;
00109   std::vector<cv_bridge::CvImage::ConstPtr> images;
00110   std::vector<sensor_msgs::CameraInfo::ConstPtr> cameraInfos;
00111   std::vector<cv::Mat> cameraMasks;
00112   float defaultColor;
00113   std::string fixedFrame;
00114   int numCameras;
00115   double maxImageAge;
00116   bool useFirstValid;
00117   int imageQueueSize;
00118   int pointCloudQueueSize;
00119   double waitForTransform;
00120   void cameraCallback(const sensor_msgs::Image::ConstPtr &imageMsg, const sensor_msgs::CameraInfo::ConstPtr &cameraInfoMsg, const int iCam);
00121   void pointCloudCallback(const sensor_msgs::PointCloud2::ConstPtr &cloudMsg);
00122 };
00123 
00124 PointCloudColor::PointCloudColor() :
00125     transformListener(ros::Duration(10.0)),
00126     fixedFrame("/odom"),
00127     numCameras(1),
00128     maxImageAge(10.0),
00129     useFirstValid(true),
00130     imageQueueSize(1),
00131     pointCloudQueueSize(1),
00132     waitForTransform(1.0) {
00133 }
00134 
00135 PointCloudColor::~PointCloudColor() {
00136 }
00137 
00138 void PointCloudColor::onInit() {
00139   NODELET_INFO("PointCloudColor::onInit: Initializing...");
00140 
00141 #ifdef USE_MT_NODE_HANDLE
00142   ros::NodeHandle &nh = getMTNodeHandle();
00143   ros::NodeHandle &pnh = getMTPrivateNodeHandle();
00144 #else
00145   ros::NodeHandle &nh = getNodeHandle();
00146   ros::NodeHandle &pnh = getPrivateNodeHandle();
00147 #endif
00148 
00149   // Get and process parameters.
00150   pnh.param("fixed_frame", fixedFrame, fixedFrame);
00151   NODELET_INFO("PointCloudColor::onInit: Fixed frame: %s.", fixedFrame.c_str());
00152   std::string defaultColorStr("0x00000000");
00153   pnh.param("default_color", defaultColorStr, defaultColorStr);
00154   uint32_t defaultColorUl = 0xfffffffful & strtoul(defaultColorStr.c_str(), NULL, 0);
00155   defaultColor = *reinterpret_cast<float *>(&defaultColorUl);
00156   NODELET_INFO("PointCloudColor::onInit: Default color: %#x.", defaultColorUl);
00157   pnh.param("num_cameras", numCameras, numCameras);
00158   numCameras = numCameras >= 0 ? numCameras : 0;
00159   NODELET_INFO("PointCloudColor::onInit: Number of cameras: %i.", numCameras);
00160   pnh.param("max_image_age", maxImageAge, maxImageAge);
00161   NODELET_INFO("PointCloudColor::onInit: Maximum image age: %.1f s.", maxImageAge);
00162   pnh.param("use_first_valid", useFirstValid, useFirstValid);
00163   NODELET_INFO("PointCloudColor::onInit: Use first valid projection: %s.", useFirstValid ? "yes" : "no");
00164   pnh.param("image_queue_size", imageQueueSize, imageQueueSize);
00165   imageQueueSize = imageQueueSize >= 1 ? imageQueueSize : 1;
00166   NODELET_INFO("PointCloudColor::onInit: Image queue size: %i.", imageQueueSize);
00167   pnh.param("point_cloud_queue_size", pointCloudQueueSize, pointCloudQueueSize);
00168   pointCloudQueueSize = pointCloudQueueSize >= 1 ? pointCloudQueueSize : 1;
00169   NODELET_INFO("PointCloudColor::onInit: Point cloud queue size: %i.", pointCloudQueueSize);
00170   pnh.param("wait_for_transform", waitForTransform, waitForTransform);
00171   waitForTransform = waitForTransform >= 0.0 ? waitForTransform : 0.0;
00172   NODELET_INFO("PointCloudColor::onInit: Wait for transform timeout: %.2f s.", waitForTransform);
00173 
00174   // Subscribe list of camera topics.
00175   image_transport::ImageTransport it(nh);
00176   cameraSubscribers.resize(numCameras);
00177   cameraMasks.resize(numCameras);
00178   images.resize(numCameras);
00179   cameraInfos.resize(numCameras);
00180   for (int iCam = 0; iCam < numCameras; iCam++) {
00181     std::string cameraTopic = nh.resolveName((boost::format("camera_%i/image") % iCam).str(), true);
00182     std::string cameraMaskParam = (boost::format("camera_%i/mask") % iCam).str();
00183     std::string cameraMaskPath("");
00184     pnh.param(cameraMaskParam, cameraMaskPath, cameraMaskPath);
00185     if (!cameraMaskPath.empty()) {
00186       cameraMasks[iCam] = cv::imread(cameraMaskPath, CV_LOAD_IMAGE_GRAYSCALE);
00187       NODELET_INFO("PointCloudColor::onInit: Camera %i: Using camera mask from %s.", iCam, cameraMaskPath.c_str());
00188     }
00189     NODELET_INFO("PointCloudColor::onInit: Camera %i: Subscribing camera topic %s.", iCam, cameraTopic.c_str());
00190     cameraSubscribers[iCam] = it.subscribeCamera(cameraTopic, imageQueueSize, (boost::bind(&PointCloudColor::cameraCallback, this, _1, _2, iCam)));
00191   }
00192 
00193   // Subscribe point cloud topic.
00194   std::string pclInTopic = nh.resolveName("cloud_in", true);
00195   NODELET_INFO("PointCloudColor::onInit: Subscribing point cloud %s.", pclInTopic.c_str());
00196   pointCloudSub = nh.subscribe<sensor_msgs::PointCloud2>(pclInTopic, pointCloudQueueSize, &PointCloudColor::pointCloudCallback, this);
00197 
00198   // Advertise colored point cloud topic.
00199   std::string pclOutTopic = nh.resolveName("cloud_out", true);
00200   NODELET_INFO("PointCloudColor::onInit: Advertising colored point cloud %s.", pclOutTopic.c_str());
00201   pointCloudPub = nh.advertise<sensor_msgs::PointCloud2>(pclOutTopic, pointCloudQueueSize, false);
00202 }
00203 
00204 void PointCloudColor::cameraCallback(const sensor_msgs::Image::ConstPtr &imageMsg, const sensor_msgs::CameraInfo::ConstPtr &cameraInfoMsg, const int iCam) {
00205   NODELET_DEBUG("PointCloudColor::cameraCallback: Camera %i: Image frame: %s, camera info frame: %s.", iCam, imageMsg->header.frame_id.c_str(), cameraInfoMsg->header.frame_id.c_str());
00206   // Use frame id from image message for both image and camera info to ensure consistency.
00207   images[iCam] = cv_bridge::toCvShare(imageMsg, sensor_msgs::image_encodings::BGR8);
00208   cameraInfos[iCam] = cameraInfoMsg;
00209 }
00210 
00211 void PointCloudColor::pointCloudCallback(const sensor_msgs::PointCloud2::ConstPtr &cloudMsg) {
00212   const int numPoints = cloudMsg->width * cloudMsg->height;
00213   const std::string cloudFrame = cloudMsg->header.frame_id;
00214   if (numPoints == 0 || cloudMsg->data.size() == 0) {
00215     NODELET_WARN("PointCloudColor::pointCloudCallback: Skipping empty point cloud in frame %s.", cloudFrame.c_str());
00216     return;
00217   }
00218   sensor_msgs::PointCloud2::Ptr outCloud = createCloudWithColorFrom(cloudMsg);
00219   cv::Mat rgbMat = matFromCloud(outCloud, "rgb", 1);
00220   rgbMat.setTo(defaultColor);
00221   // Initialize vector with projection distances from image center, used as a quality measure.
00222   std::vector<float> dist(numPoints, std::numeric_limits<float>::infinity());
00223   for (int iCam = 0; iCam < numCameras; iCam++) {
00224     if (!images[iCam] || !cameraInfos[iCam]) {
00225       NODELET_WARN("PointCloudColor::pointCloudCallback: Camera image %i has not been received yet...", iCam);
00226       continue;
00227     }
00228     // Check relative age of the point cloud and the image. Skip the image if the time span is to large.
00229     const double imageAge = (outCloud->header.stamp - images[iCam]->header.stamp).toSec();
00230     if (imageAge > maxImageAge) {
00231       NODELET_WARN("PointCloudColor::pointCloudCallback: Skipping image %.1f (> %.1f) s older than point cloud...", imageAge, maxImageAge);
00232       continue;
00233     }
00234     // Wait for transform from cloud to image.
00235     if (!transformListener.waitForTransform(images[iCam]->header.frame_id, images[iCam]->header.stamp, // target frame and time
00236                                             cloudFrame, cloudMsg->header.stamp, // source frame and time
00237                                             fixedFrame, ros::Duration(waitForTransform))) {
00238       NODELET_WARN("PointCloudColor::pointCloudCallback: Could not transform point cloud from frame %s to camera frame %s. Skipping the image...",
00239                    cloudFrame.c_str(), images[iCam]->header.frame_id.c_str());
00240       continue;
00241     }
00242     tf::StampedTransform cloudToCamStamped;
00243     transformListener.lookupTransform(images[iCam]->header.frame_id, images[iCam]->header.stamp, // target frame and time
00244                                       cloudFrame, cloudMsg->header.stamp, // source frame and time
00245                                       fixedFrame, cloudToCamStamped);
00246     cv::Mat camRotation = rotationFromTransform(cloudToCamStamped);
00247     cv::Mat camTranslation = translationFromTransform(cloudToCamStamped);
00248     cv::Mat objectPoints;
00249     matFromCloud(outCloud, "x", 3).convertTo(objectPoints, CV_64FC1);
00250     // Rigid transform with points in rows: (X_C)^T = (R * X_L + t)^T.
00251     objectPoints = objectPoints * camRotation.t() + cv::repeat(camTranslation.t(), objectPoints.rows, 1);
00252     cv::Mat cameraMatrix(3, 3, CV_64FC1, const_cast<void *>(reinterpret_cast<const void *>(&cameraInfos[iCam]->K[0])));
00253     cv::Mat distCoeffs(1, static_cast<int>(cameraInfos[iCam]->D.size()), CV_64FC1, const_cast<void *>(reinterpret_cast<const void *>(&cameraInfos[iCam]->D[0])));
00254     cv::Mat imagePoints;
00255     cv::projectPoints(objectPoints.reshape(3), cv::Mat::zeros(3, 1, CV_64FC1), cv::Mat::zeros(3, 1, CV_64FC1), cameraMatrix, distCoeffs, imagePoints);
00256 
00257     for (int iPoint = 0; iPoint < numPoints; iPoint++) {
00258       // Continue if we already have got a color.
00259       if (useFirstValid && dist[iPoint] < DBL_MAX) {
00260         continue;
00261       }
00262       const double z = objectPoints.at<double>(iPoint, 2);
00263       // Skip points behind the camera.
00264       if (z <= 0.0) {
00265         continue;
00266       }
00267       const cv::Vec2d pt = imagePoints.at<cv::Vec2d>(iPoint, 0);
00268       const double x = round(pt.val[0]);
00269       const double y = round(pt.val[1]);
00270       // Skip points outside the image.
00271       if (x < 0.0 || y < 0.0 || x >= static_cast<double>(cameraInfos[iCam]->width) || y >= static_cast<double>(cameraInfos[iCam]->height)) {
00272         continue;
00273       }
00274       // Apply static mask with image ROI to be used for coloring.
00275       const int yi = static_cast<int>(y);
00276       const int xi = static_cast<int>(x);
00277       if (!cameraMasks[iCam].empty() && !cameraMasks[iCam].at<uint8_t>(yi, xi)) {
00278         // Pixel masked out.
00279         continue;
00280       }
00281       const float r = hypot(static_cast<float>(cameraInfos[iCam]->width / 2 - xi), static_cast<float>(cameraInfos[iCam]->height / 2 - yi));
00282       if (r >= dist[iPoint]) {
00283         // Keep color from the previous projection closer to image center.
00284         continue;
00285       }
00286       dist[iPoint] = r;
00287       const cv::Vec3b px = images[iCam]->image.at<cv::Vec3b>(static_cast<int>(y), static_cast<int>(x));
00288       uint32_t rgb = static_cast<uint32_t>(px.val[2]) << 16 | static_cast<uint32_t>(px.val[1]) << 8 | static_cast<uint32_t>(px.val[0]);
00289       rgbMat.at<float>(iPoint, 0) = *reinterpret_cast<float*>(&rgb);
00290     }
00291   }
00292   pointCloudPub.publish(outCloud);
00293 }
00294 
00295 } /* namespace point_cloud_color */
00296 
00297 PLUGINLIB_DECLARE_CLASS(point_cloud_color, point_cloud_color, point_cloud_color::PointCloudColor, nodelet::Nodelet)
 All Classes Namespaces Files Functions Variables Typedefs


point_cloud_color
Author(s): Tomas Petricek / petrito1@cmp.felk.cvut.cz
autogenerated on Tue Dec 10 2013 14:28:16