00001
00008
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
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
00075
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
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
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
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
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
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
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
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
00235 if (!transformListener.waitForTransform(images[iCam]->header.frame_id, images[iCam]->header.stamp,
00236 cloudFrame, cloudMsg->header.stamp,
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,
00244 cloudFrame, cloudMsg->header.stamp,
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
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
00259 if (useFirstValid && dist[iPoint] < DBL_MAX) {
00260 continue;
00261 }
00262 const double z = objectPoints.at<double>(iPoint, 2);
00263
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
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
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
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
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 }
00296
00297 PLUGINLIB_DECLARE_CLASS(point_cloud_color, point_cloud_color, point_cloud_color::PointCloudColor, nodelet::Nodelet)