nerian_sp1_node.cpp
Go to the documentation of this file.
00001 /*******************************************************************************
00002  * Copyright (c) 2015 Nerian Vision Technologies
00003  *
00004  * Permission is hereby granted, free of charge, to any person obtaining a copy
00005  * of this software and associated documentation files (the "Software"), to deal
00006  * in the Software without restriction, including without limitation the rights
00007  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
00008  * copies of the Software, and to permit persons to whom the Software is
00009  * furnished to do so, subject to the following conditions:
00010  *
00011  * The above copyright notice and this permission notice shall be included in
00012  * all copies or substantial portions of the Software.
00013  *******************************************************************************/
00014 
00015 #include <ros/ros.h>
00016 #include <visiontransfer/asynctransfer.h>
00017 #include <visiontransfer/reconstruct3d.h>
00018 #include <sensor_msgs/PointCloud2.h>
00019 #include <sensor_msgs/Image.h>
00020 #include <opencv2/opencv.hpp>
00021 #include <cv_bridge/cv_bridge.h>
00022 #include <iostream>
00023 #include <iomanip>
00024 #include <nerian_sp1/StereoCameraInfo.h>
00025 #include <boost/smart_ptr.hpp>
00026 #include <colorcoder.h>
00027 #include <sdlwindow.h>
00028 
00029 using namespace std;
00030 
00050 class Sp1Node {
00051 public:
00052     Sp1Node() {
00053     }
00054 
00055     ~Sp1Node() {
00056     }
00057 
00061     void init() {
00062         ros::NodeHandle privateNh("~");
00063 
00064         // Read all ROS parameters
00065         if (!privateNh.getParam("point_cloud_intensity_channel", intensityChannel)) {
00066             intensityChannel = true;
00067         }
00068 
00069         if (!privateNh.getParam("color_code_disparity_map", colorCodeDispMap)) {
00070             colorCodeDispMap = true;
00071         }
00072 
00073         if (!privateNh.getParam("color_code_legend", colorCodeLegend)) {
00074             colorCodeLegend = false;
00075         }
00076 
00077         if (!privateNh.getParam("frame", frame)) {
00078             frame = "world";
00079         }
00080 
00081         if (!privateNh.getParam("port", port)) {
00082             port = "7681";
00083         }
00084 
00085         if (!privateNh.getParam("use_tcp", useTcp)) {
00086             useTcp = false;
00087         }
00088 
00089         if (!privateNh.getParam("host", host)) {
00090             host = "";
00091         }
00092 
00093         if (!privateNh.getParam("ros_coordinate_system", rosCoordinateSystem)) {
00094             rosCoordinateSystem = true;
00095         }
00096 
00097         if (!privateNh.getParam("calibration_file", calibFile)) {
00098             calibFile = "";
00099         }
00100 
00101         if (!privateNh.getParam("disparity_window", dispWindow)) {
00102             dispWindow = false;
00103         }
00104 
00105         // Create debugging window
00106         if(dispWindow) {
00107             window.reset(new SDLWindow(640, 480, "Disparity Map"));
00108         }
00109 
00110         // Create publishers
00111         disparityPublisher.reset(new ros::Publisher(nh.advertise<sensor_msgs::Image>(
00112             "/nerian_sp1/disparity_map", 5)));
00113         imagePublisher.reset(new ros::Publisher(nh.advertise<sensor_msgs::Image>(
00114             "/nerian_sp1/left_image", 5)));
00115 
00116         if(calibFile == "" ) {
00117             ROS_WARN("No camera calibration file configured. Point cloud and camera information publishing is disabled!");
00118         } else {
00119              if (!calibStorage.open(calibFile, cv::FileStorage::READ)) {
00120                 throw std::runtime_error("Error reading calibration file: " + calibFile);
00121             }
00122 
00123             cameraInfoPublisher.reset(new ros::Publisher(nh.advertise<nerian_sp1::StereoCameraInfo>(
00124                 "/nerian_sp1/stereo_camera_info", 1)));
00125             cloudPublisher.reset(new ros::Publisher(nh.advertise<sensor_msgs::PointCloud2>(
00126                 "/nerian_sp1/point_cloud", 5)));
00127 
00128             // For point cloud publishing we have to do more initializations
00129             initPointCloud(privateNh);
00130         }
00131     }
00132 
00136     int run() {
00137         ros::Time stamp = ros::Time::now();
00138         ros::Time lastLogTime;
00139         int lastLogFrames = 0;
00140 
00141         int leftWidth, leftHeight, leftStride;
00142         unsigned char* leftData = NULL;
00143 
00144         int dispWidth, dispHeight, dispStride;
00145         unsigned char* dispData = NULL;
00146 
00147 
00148         AsyncTransfer asyncTransfer(useTcp ? ImageTransfer::TCP_CLIENT : ImageTransfer::UDP_CLIENT,
00149             host.c_str(), port.c_str());
00150 
00151         while(ros::ok()) {
00152             int imageNum, width, height, stride;
00153             ImageProtocol::ImageFormat format;
00154             unsigned char* data = NULL;
00155 
00156             // Receive image data
00157             data = asyncTransfer.collectReceivedImage(imageNum, width, height, stride, format, 0.5);
00158 
00159             // Find out which image this was
00160             if(data != NULL && imageNum == 0) {
00161                 stamp = ros::Time::now();
00162                 leftWidth = width;
00163                 leftHeight = height;
00164                 leftStride = stride;
00165                 leftData = data;
00166             } else if(data != NULL && imageNum == 1) {
00167                 dispWidth = width;
00168                 dispHeight = height;
00169                 dispStride = stride;
00170                 dispData = data;
00171             }
00172 
00173             if(leftData != NULL && dispData != NULL) {
00174                 // We have received the disparity map and the left camera image
00175 
00176                 if(leftWidth != dispWidth || leftHeight != dispHeight) {
00177                     throw std::runtime_error("Invalid image sizes!");
00178                 }
00179 
00180                 // Publish the selected messages
00181                 if(imagePublisher->getNumSubscribers() > 0) {
00182                     publishImageMsg(leftWidth, leftHeight, leftStride, leftData, stamp);
00183                 }
00184 
00185                 if(disparityPublisher->getNumSubscribers() > 0 || window != NULL) {
00186                     publishDispMapMsg(dispWidth, dispHeight, dispStride, dispData, stamp);
00187                 }
00188 
00189                 if(cloudPublisher != NULL && cloudPublisher->getNumSubscribers() > 0) {
00190                     publishPointCloudMsg(dispWidth, dispHeight, leftStride, dispStride,
00191                         leftData, dispData, stamp);
00192                 }
00193 
00194                 if(cameraInfoPublisher != NULL && cameraInfoPublisher->getNumSubscribers() > 0) {
00195                     publishCameraInfo(stamp);
00196                 }
00197 
00198                 // Display some simple statistics
00199                 frameNum++;
00200                 if(stamp.sec != lastLogTime.sec) {
00201                     if(lastLogTime != ros::Time()) {
00202                         double dt = (stamp - lastLogTime).toSec();
00203                         double fps = (frameNum - lastLogFrames) / dt;
00204                         ROS_INFO("%.1f fps", fps);
00205                     }
00206                     lastLogFrames = frameNum;
00207                     lastLogTime = stamp;
00208                 }
00209 
00210                 leftData = NULL;
00211                 dispData = NULL;
00212             }
00213         }
00214     }
00215 
00216 private:
00217     // ROS related objects
00218     ros::NodeHandle nh;
00219     boost::scoped_ptr<ros::Publisher> cloudPublisher;
00220     boost::scoped_ptr<ros::Publisher> disparityPublisher;
00221     boost::scoped_ptr<ros::Publisher> imagePublisher;
00222     boost::scoped_ptr<ros::Publisher> cameraInfoPublisher;
00223 
00224     // Parameters
00225     bool intensityChannel;
00226     bool useTcp;
00227     bool colorCodeDispMap;
00228     bool colorCodeLegend;
00229     bool rosCoordinateSystem;
00230     std::string port;
00231     std::string frame;
00232     std::string host;
00233     std::string calibFile;
00234     bool dispWindow;
00235 
00236     // Other members
00237     int frameNum;
00238     boost::scoped_ptr<Reconstruct3D> recon3d;
00239     boost::scoped_ptr<ColorCoder> colCoder;
00240     cv::Mat_<cv::Vec3b> colDispMap;
00241     sensor_msgs::PointCloud2Ptr pointCloudMsg;
00242     cv::FileStorage calibStorage;
00243     nerian_sp1::StereoCameraInfoPtr camInfoMsg;
00244     ros::Time lastCamInfoPublish;
00245     boost::scoped_ptr<SDLWindow> window;
00246 
00250     void publishImageMsg(int width, int height, int stride, unsigned char* data,
00251             ros::Time stamp) {
00252         cv_bridge::CvImage cvImg;
00253         cvImg.header.frame_id = frame;
00254         cvImg.header.stamp = stamp;
00255         cvImg.header.seq = frameNum;
00256 
00257         cvImg.image = cv::Mat_<unsigned char>(height, width, data, stride);
00258         sensor_msgs::ImagePtr msg = cvImg.toImageMsg();
00259         msg->encoding = "mono8";
00260         imagePublisher->publish(msg);
00261     }
00262 
00267     void publishDispMapMsg(int width, int height, int stride, unsigned char* data,
00268             ros::Time stamp) {
00269         cv_bridge::CvImage cvImg;
00270         cvImg.header.frame_id = frame;
00271         cvImg.header.stamp = stamp;
00272         cvImg.header.seq = frameNum;
00273 
00274         cv::Mat_<unsigned short> monoImg(height, width,
00275             reinterpret_cast<unsigned short*>(data), stride);
00276         string encoding = "";
00277 
00278         if(!colorCodeDispMap) {
00279             cvImg.image = monoImg;
00280             encoding = "mono16";
00281         } else {
00282             if(colCoder == NULL) {
00283                 colCoder.reset(new ColorCoder(0, 16*111, true, true));
00284                 if(colorCodeLegend) {
00285                     // Create the legend
00286                     colDispMap = colCoder->createLegendBorder(width, height, 1.0/16.0);
00287                 } else {
00288                     colDispMap = cv::Mat_<cv::Vec3b>(height, width);
00289                 }
00290             }
00291 
00292             cv::Mat_<cv::Vec3b> dispSection = colDispMap(cv::Rect(0, 0, width, height));
00293             colCoder->codeImage(monoImg, dispSection);
00294             cvImg.image = colDispMap;
00295             encoding = "bgr8";
00296         }
00297 
00298         if(disparityPublisher->getNumSubscribers() > 0) {
00299             sensor_msgs::ImagePtr msg = cvImg.toImageMsg();
00300             msg->encoding = encoding;
00301             disparityPublisher->publish(msg);
00302         }
00303 
00304         if(window != NULL) {
00305             if(window->getSize() != cvImg.image.size()) {
00306                 window->resize(cvImg.image.size());
00307             }
00308             window->displayImage(cvImg.image);
00309             window->processEvents(false);
00310         }
00311     }
00312 
00317     void publishPointCloudMsg(int width, int height, int leftStride, int dispStride,
00318             unsigned char* leftData, unsigned char* dispData, ros::Time stamp) {
00319         // Get 3D points
00320         float* pointMap = recon3d->createPointMap(reinterpret_cast<unsigned short*>(dispData),
00321             width, height, dispStride, 0);
00322 
00323         // Create message object and set header
00324         pointCloudMsg->header.stamp = stamp;
00325         pointCloudMsg->header.frame_id = frame;
00326         pointCloudMsg->header.seq = frameNum;
00327 
00328         // Copy 3D points
00329         if(pointCloudMsg->data.size() != width*height*4*sizeof(float)) {
00330             // Allocate buffer
00331             pointCloudMsg->data.resize(width*height*4*sizeof(float));
00332 
00333             // Set basic data
00334             pointCloudMsg->width = width;
00335             pointCloudMsg->height = height;
00336             pointCloudMsg->is_bigendian = false;
00337             pointCloudMsg->point_step = 4*sizeof(float);
00338             pointCloudMsg->row_step = width * pointCloudMsg->point_step;
00339             pointCloudMsg->is_dense = false;
00340 
00341             pointCloudMsg->fields[0].count = width * height;
00342             pointCloudMsg->fields[1].count = width * height;
00343             pointCloudMsg->fields[2].count = width * height;
00344 
00345             if(intensityChannel) {
00346                 pointCloudMsg->fields[3].count = width * height;
00347             }
00348         }
00349 
00350         memcpy(&pointCloudMsg->data[0], pointMap, width*height*4*sizeof(float));
00351 
00352         // Copy intensity values
00353         if(intensityChannel) {
00354             // Get pointers to the beginnig and end of the point cloud
00355             unsigned char* cloudStart = &pointCloudMsg->data[0];
00356             unsigned char* cloudEnd = &pointCloudMsg->data[0] + width*height*4*sizeof(float);
00357 
00358             // Get pointer to the current pixel and end of current row
00359             unsigned char* imagePtr = &leftData[0];
00360             unsigned char* rowEndPtr = imagePtr + width;
00361 
00362             for(unsigned char* cloudPtr = cloudStart + 3*sizeof(float);
00363                     cloudPtr < cloudEnd; cloudPtr+= 4*sizeof(float)) {
00364                 *cloudPtr = *imagePtr;
00365 
00366                 imagePtr++;
00367                 if(imagePtr == rowEndPtr) {
00368                     // Progress to next row
00369                     imagePtr += (leftStride - width);
00370                     rowEndPtr = imagePtr + width;
00371                 }
00372             }
00373         }
00374 
00375         cloudPublisher->publish(pointCloudMsg);
00376     }
00377 
00382     void initPointCloud(ros::NodeHandle& privateNh) {
00383         // Read disparity-to-depth mapping matrix from calibration data
00384         std::vector<float> qMatrix;
00385         calibStorage["Q"] >> qMatrix;
00386         if(qMatrix.size() != 16) {
00387             throw std::runtime_error("Q matrix has invalid size!");
00388         }
00389 
00390         if(rosCoordinateSystem) {
00391             // Transform Q matrix to match the ROS coordinate system:
00392             // Swap y/z axis, then swap x/y axis, then invert y and z axis.
00393             std::vector<float> qNew(16);
00394             qNew[0] = qMatrix[8];   qNew[1] = qMatrix[9];
00395             qNew[2] = qMatrix[10];  qNew[3] = qMatrix[11];
00396 
00397             qNew[4] = -qMatrix[0];  qNew[5] = -qMatrix[1];
00398             qNew[6] = -qMatrix[2];  qNew[7] = -qMatrix[3];
00399 
00400             qNew[8] = -qMatrix[4];  qNew[9] = -qMatrix[5];
00401             qNew[10] = -qMatrix[6]; qNew[11] = -qMatrix[7];
00402 
00403             qNew[12] = qMatrix[12]; qNew[13] = qMatrix[13];
00404             qNew[14] = qMatrix[14]; qNew[15] = qMatrix[15];
00405 
00406             qMatrix = qNew;
00407         }
00408 
00409         // Initialize 3D reconstruction class
00410         recon3d.reset(new Reconstruct3D(&qMatrix[0]));
00411 
00412         // Initialize message
00413         pointCloudMsg.reset(new sensor_msgs::PointCloud2);
00414 
00415         // Set channel information.
00416         sensor_msgs::PointField fieldX;
00417         fieldX.name ="x";
00418         fieldX.offset = 0;
00419         fieldX.datatype = sensor_msgs::PointField::FLOAT32;
00420         pointCloudMsg->fields.push_back(fieldX);
00421 
00422         sensor_msgs::PointField fieldY;
00423         fieldY.name ="y";
00424         fieldY.offset = sizeof(float);
00425         fieldY.datatype = sensor_msgs::PointField::FLOAT32;
00426         pointCloudMsg->fields.push_back(fieldY);
00427 
00428         sensor_msgs::PointField fieldZ;
00429         fieldZ.name ="z";
00430         fieldZ.offset = 2*sizeof(float);
00431         fieldZ.datatype = sensor_msgs::PointField::FLOAT32;
00432         pointCloudMsg->fields.push_back(fieldZ);
00433 
00434         if(intensityChannel) {
00435             sensor_msgs::PointField fieldI;
00436             fieldI.name ="intensity";
00437             fieldI.offset = 3*sizeof(float);
00438             fieldI.datatype = sensor_msgs::PointField::UINT8;
00439             pointCloudMsg->fields.push_back(fieldI);
00440         }
00441     }
00442 
00446     void publishCameraInfo(ros::Time stamp) {
00447         if(camInfoMsg == NULL) {
00448             // Initialize the camera info structure
00449             camInfoMsg.reset(new nerian_sp1::StereoCameraInfo);
00450 
00451             camInfoMsg->header.frame_id = frame;
00452             camInfoMsg->header.seq = 0;
00453 
00454             std::vector<int> sizeVec;
00455             calibStorage["size"] >> sizeVec;
00456             if(sizeVec.size() != 2) {
00457                 std::runtime_error("Calibration file format error!");
00458             }
00459 
00460             camInfoMsg->left_info.header = camInfoMsg->header;
00461             camInfoMsg->left_info.width = sizeVec[0];
00462             camInfoMsg->left_info.height = sizeVec[1];
00463             camInfoMsg->left_info.distortion_model = "plumb_bob";
00464             calibStorage["D1"] >> camInfoMsg->left_info.D;
00465             readCalibrationArray("M1", camInfoMsg->left_info.K);
00466             readCalibrationArray("R1", camInfoMsg->left_info.R);
00467             readCalibrationArray("P1", camInfoMsg->left_info.P);
00468             camInfoMsg->left_info.binning_x = 1;
00469             camInfoMsg->left_info.binning_y = 1;
00470             camInfoMsg->left_info.roi.do_rectify = false;
00471             camInfoMsg->left_info.roi.height = 0;
00472             camInfoMsg->left_info.roi.width = 0;
00473             camInfoMsg->left_info.roi.x_offset = 0;
00474             camInfoMsg->left_info.roi.y_offset = 0;
00475 
00476             camInfoMsg->right_info.header = camInfoMsg->header;
00477             camInfoMsg->right_info.width = sizeVec[0];
00478             camInfoMsg->right_info.height = sizeVec[1];
00479             camInfoMsg->right_info.distortion_model = "plumb_bob";
00480             calibStorage["D2"] >> camInfoMsg->right_info.D;
00481             readCalibrationArray("M2", camInfoMsg->right_info.K);
00482             readCalibrationArray("R2", camInfoMsg->right_info.R);
00483             readCalibrationArray("P2", camInfoMsg->right_info.P);
00484             camInfoMsg->right_info.binning_x = 1;
00485             camInfoMsg->right_info.binning_y = 1;
00486             camInfoMsg->right_info.roi.do_rectify = false;
00487             camInfoMsg->right_info.roi.height = 0;
00488             camInfoMsg->right_info.roi.width = 0;
00489             camInfoMsg->right_info.roi.x_offset = 0;
00490             camInfoMsg->right_info.roi.y_offset = 0;
00491 
00492             readCalibrationArray("Q", camInfoMsg->Q);
00493             readCalibrationArray("T", camInfoMsg->T_left_right);
00494             readCalibrationArray("R", camInfoMsg->R_left_right);
00495         }
00496 
00497         double dt = (stamp - lastCamInfoPublish).toSec();
00498         if(dt > 1.0) {
00499             // Publish once per second
00500             camInfoMsg->header.stamp = stamp;
00501             camInfoMsg->left_info.header.stamp = stamp;
00502             camInfoMsg->right_info.header.stamp = stamp;
00503             cameraInfoPublisher->publish(camInfoMsg);
00504 
00505             lastCamInfoPublish = stamp;
00506         }
00507     }
00508 
00512     template<class T>
00513     void readCalibrationArray(const char* key, T& dest) {
00514         std::vector<double> doubleVec;
00515         calibStorage[key] >> doubleVec;
00516 
00517         if(doubleVec.size() != dest.size()) {
00518             std::runtime_error("Calibration file format error!");
00519         }
00520 
00521         std::copy(doubleVec.begin(), doubleVec.end(), dest.begin());
00522     }
00523 };
00524 
00525 int main(int argc, char** argv) {
00526     try {
00527         ros::init(argc, argv, "nerian_sp1");
00528         Sp1Node node;
00529         node.init();
00530         return node.run();
00531     } catch(const std::exception& ex) {
00532         ROS_FATAL("Exception occured: %s", ex.what());
00533         return 1;
00534     }
00535 }


nerian_sp1
Author(s): Nerian Vision Technologies
autogenerated on Wed Sep 16 2015 06:40:29