00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
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
00028 using namespace std;
00029
00049 class Sp1Node {
00050 public:
00051 Sp1Node(): frameNum(0) {
00052 }
00053
00054 ~Sp1Node() {
00055 }
00056
00060 void init() {
00061 ros::NodeHandle privateNh("~");
00062
00063
00064 if (!privateNh.getParam("point_cloud_intensity_channel", intensityChannel)) {
00065 intensityChannel = true;
00066 }
00067
00068 if (!privateNh.getParam("color_code_disparity_map", colorCodeDispMap)) {
00069 colorCodeDispMap = true;
00070 }
00071
00072 if (!privateNh.getParam("color_code_legend", colorCodeLegend)) {
00073 colorCodeLegend = false;
00074 }
00075
00076 if (!privateNh.getParam("frame", frame)) {
00077 frame = "world";
00078 }
00079
00080 if (!privateNh.getParam("remote_port", remotePort)) {
00081 remotePort = "7681";
00082 }
00083
00084 if (!privateNh.getParam("remote_host", remoteHost)) {
00085 remoteHost = "0.0.0.0";
00086 }
00087
00088 if (!privateNh.getParam("local_port", localPort)) {
00089 localPort = "7681";
00090 }
00091
00092 if (!privateNh.getParam("local_host", localHost)) {
00093 localHost = "0.0.0.0";
00094 }
00095
00096 if (!privateNh.getParam("use_tcp", useTcp)) {
00097 useTcp = false;
00098 }
00099
00100 if (!privateNh.getParam("ros_coordinate_system", rosCoordinateSystem)) {
00101 rosCoordinateSystem = true;
00102 }
00103
00104 if (!privateNh.getParam("calibration_file", calibFile)) {
00105 calibFile = "";
00106 }
00107
00108 if (!privateNh.getParam("delay_execution", execDelay)) {
00109 execDelay = 0;
00110 }
00111
00112 if (!privateNh.getParam("max_depth", maxDepth)) {
00113 maxDepth = -1;
00114 }
00115
00116
00117 ros::Duration(execDelay).sleep();
00118
00119
00120 disparityPublisher.reset(new ros::Publisher(nh.advertise<sensor_msgs::Image>(
00121 "/nerian_sp1/disparity_map", 5)));
00122 leftImagePublisher.reset(new ros::Publisher(nh.advertise<sensor_msgs::Image>(
00123 "/nerian_sp1/left_image", 5)));
00124 rightImagePublisher.reset(new ros::Publisher(nh.advertise<sensor_msgs::Image>(
00125 "/nerian_sp1/right_image", 5)));
00126
00127 loadCameraCalibration();
00128
00129 cameraInfoPublisher.reset(new ros::Publisher(nh.advertise<nerian_sp1::StereoCameraInfo>(
00130 "/nerian_sp1/stereo_camera_info", 1)));
00131 cloudPublisher.reset(new ros::Publisher(nh.advertise<sensor_msgs::PointCloud2>(
00132 "/nerian_sp1/point_cloud", 5)));
00133 }
00134
00138 int run() {
00139 try {
00140 ros::Time lastLogTime;
00141 int lastLogFrames = 0;
00142
00143 AsyncTransfer asyncTransfer(useTcp ? ImageTransfer::TCP_CLIENT : ImageTransfer::UDP,
00144 remoteHost.c_str(), remotePort.c_str(), localHost.c_str(), localPort.c_str());
00145
00146 while(ros::ok()) {
00147
00148 ImagePair imagePair;
00149 if(!asyncTransfer.collectReceivedImagePair(imagePair, 0.5)) {
00150 continue;
00151 }
00152
00153 ros::Time stamp = ros::Time::now();
00154
00155
00156 if(leftImagePublisher->getNumSubscribers() > 0) {
00157 publishImageMsg(imagePair, stamp);
00158 }
00159
00160 if(disparityPublisher->getNumSubscribers() > 0 || rightImagePublisher->getNumSubscribers() > 0) {
00161 publishDispMapMsg(imagePair, stamp);
00162 }
00163
00164 if(cloudPublisher->getNumSubscribers() > 0) {
00165 if(recon3d == nullptr) {
00166
00167 initPointCloud();
00168 }
00169
00170 publishPointCloudMsg(imagePair, stamp);
00171 }
00172
00173 if(cameraInfoPublisher != NULL && cameraInfoPublisher->getNumSubscribers() > 0) {
00174 publishCameraInfo(stamp, imagePair);
00175 }
00176
00177
00178 frameNum++;
00179 if(stamp.sec != lastLogTime.sec) {
00180 if(lastLogTime != ros::Time()) {
00181 double dt = (stamp - lastLogTime).toSec();
00182 double fps = (frameNum - lastLogFrames) / dt;
00183 ROS_INFO("%.1f fps", fps);
00184 }
00185 lastLogFrames = frameNum;
00186 lastLogTime = stamp;
00187 }
00188 }
00189 } catch(const std::exception& ex) {
00190 ROS_FATAL("Exception occured: %s", ex.what());
00191 }
00192 }
00193
00194 private:
00195
00196 ros::NodeHandle nh;
00197 boost::scoped_ptr<ros::Publisher> cloudPublisher;
00198 boost::scoped_ptr<ros::Publisher> disparityPublisher;
00199 boost::scoped_ptr<ros::Publisher> leftImagePublisher;
00200 boost::scoped_ptr<ros::Publisher> rightImagePublisher;
00201 boost::scoped_ptr<ros::Publisher> cameraInfoPublisher;
00202
00203
00204 bool intensityChannel;
00205 bool useTcp;
00206 bool colorCodeDispMap;
00207 bool colorCodeLegend;
00208 bool rosCoordinateSystem;
00209 std::string remotePort;
00210 std::string localPort;
00211 std::string frame;
00212 std::string remoteHost;
00213 std::string localHost;
00214 std::string calibFile;
00215 double execDelay;
00216 double maxDepth;
00217
00218
00219 int frameNum;
00220 boost::scoped_ptr<Reconstruct3D> recon3d;
00221 boost::scoped_ptr<ColorCoder> colCoder;
00222 cv::Mat_<cv::Vec3b> colDispMap;
00223 sensor_msgs::PointCloud2Ptr pointCloudMsg;
00224 cv::FileStorage calibStorage;
00225 nerian_sp1::StereoCameraInfoPtr camInfoMsg;
00226 ros::Time lastCamInfoPublish;
00227
00231 void loadCameraCalibration() {
00232 if(calibFile == "" ) {
00233 ROS_WARN("No camera calibration file configured. Cannot publish detailed camera information!");
00234 } else {
00235 bool success = false;
00236 try {
00237 if (calibStorage.open(calibFile, cv::FileStorage::READ)) {
00238 success = true;
00239 }
00240 } catch(...) {
00241 }
00242
00243 if(!success) {
00244 ROS_WARN("Error reading calibration file: %s\n"
00245 "Cannot publish detailed camera information!", calibFile.c_str());
00246 }
00247 }
00248 }
00249
00253 void publishImageMsg(const ImagePair& imagePair, ros::Time stamp) {
00254 cv_bridge::CvImage cvImg;
00255 cvImg.header.frame_id = frame;
00256 cvImg.header.stamp = stamp;
00257 cvImg.header.seq = imagePair.getSequenceNumber();
00258
00259 cvImg.image = cv::Mat_<unsigned char>(imagePair.getHeight(),
00260 imagePair.getWidth(), imagePair.getPixelData(0), imagePair.getRowStride(0));
00261 sensor_msgs::ImagePtr msg = cvImg.toImageMsg();
00262
00263
00264 msg->encoding = "mono8";
00265 leftImagePublisher->publish(msg);
00266 }
00267
00272 void publishDispMapMsg(const ImagePair& imagePair, ros::Time stamp) {
00273 cv_bridge::CvImage cvImg;
00274 cvImg.header.frame_id = frame;
00275 cvImg.header.stamp = stamp;
00276 cvImg.header.seq = imagePair.getSequenceNumber();
00277
00278 bool format12Bit = (imagePair.getPixelFormat(1) == ImagePair::FORMAT_12_BIT);
00279 cv::Mat monoImg(imagePair.getHeight(), imagePair.getWidth(),
00280 format12Bit ? CV_16UC1 : CV_8UC1,
00281 imagePair.getPixelData(1), imagePair.getRowStride(1));
00282 string encoding = "";
00283
00284 if(!colorCodeDispMap || !format12Bit) {
00285 cvImg.image = monoImg;
00286 encoding = (format12Bit ? "mono16": "mono8");
00287 } else {
00288 if(colCoder == NULL) {
00289 colCoder.reset(new ColorCoder(ColorCoder::COLOR_RED_BLUE_BGR, 0, 16*111, true, true));
00290 if(colorCodeLegend) {
00291
00292 colDispMap = colCoder->createLegendBorder(monoImg.cols, monoImg.rows, 1.0/16.0);
00293 } else {
00294 colDispMap = cv::Mat_<cv::Vec3b>(monoImg.rows, monoImg.cols);
00295 }
00296 }
00297
00298 cv::Mat_<cv::Vec3b> dispSection = colDispMap(cv::Rect(0, 0, monoImg.cols, monoImg.rows));
00299
00300 colCoder->codeImage(cv::Mat_<unsigned short>(monoImg), dispSection);
00301 cvImg.image = colDispMap;
00302 encoding = "bgr8";
00303 }
00304
00305 sensor_msgs::ImagePtr msg = cvImg.toImageMsg();
00306 msg->encoding = encoding;
00307
00308 if(disparityPublisher->getNumSubscribers() > 0 && format12Bit) {
00309 disparityPublisher->publish(msg);
00310 }
00311 if(rightImagePublisher->getNumSubscribers() > 0 && !format12Bit) {
00312 rightImagePublisher->publish(msg);
00313 }
00314 }
00315
00320 void qMatrixToRosCoords(const float* src, float* dst) {
00321 dst[0] = src[8]; dst[1] = src[9];
00322 dst[2] = src[10]; dst[3] = src[11];
00323
00324 dst[4] = -src[0]; dst[5] = -src[1];
00325 dst[6] = -src[2]; dst[7] = -src[3];
00326
00327 dst[8] = -src[4]; dst[9] = -src[5];
00328 dst[10] = -src[6]; dst[11] = -src[7];
00329
00330 dst[12] = src[12]; dst[13] = src[13];
00331 dst[14] = src[14]; dst[15] = src[15];
00332 }
00333
00338 void publishPointCloudMsg(ImagePair& imagePair, ros::Time stamp) {
00339 if(imagePair.getPixelFormat(1) != ImagePair::FORMAT_12_BIT) {
00340 return;
00341 }
00342
00343
00344 float qRos[16];
00345 if(rosCoordinateSystem) {
00346 qMatrixToRosCoords(imagePair.getQMatrix(), qRos);
00347 imagePair.setQMatrix(qRos);
00348 }
00349
00350
00351 float* pointMap = nullptr;
00352 try {
00353 pointMap = recon3d->createPointMap(imagePair, 0);
00354 } catch(std::exception& ex) {
00355 cerr << "Error creating point cloud: " << ex.what() << endl;
00356 return;
00357 }
00358
00359
00360 pointCloudMsg->header.stamp = stamp;
00361 pointCloudMsg->header.frame_id = frame;
00362 pointCloudMsg->header.seq = imagePair.getSequenceNumber();
00363
00364
00365 if(pointCloudMsg->data.size() != imagePair.getWidth()*imagePair.getHeight()*4*sizeof(float)) {
00366
00367 pointCloudMsg->data.resize(imagePair.getWidth()*imagePair.getHeight()*4*sizeof(float));
00368
00369
00370 pointCloudMsg->width = imagePair.getWidth();
00371 pointCloudMsg->height = imagePair.getHeight();
00372 pointCloudMsg->is_bigendian = false;
00373 pointCloudMsg->point_step = 4*sizeof(float);
00374 pointCloudMsg->row_step = imagePair.getWidth() * pointCloudMsg->point_step;
00375 pointCloudMsg->is_dense = false;
00376 }
00377
00378 if(maxDepth < 0) {
00379
00380 memcpy(&pointCloudMsg->data[0], pointMap,
00381 imagePair.getWidth()*imagePair.getHeight()*4*sizeof(float));
00382 } else {
00383
00384 if(rosCoordinateSystem) {
00385 copyPointCloudClamped<0>(pointMap, reinterpret_cast<float*>(&pointCloudMsg->data[0]),
00386 imagePair.getWidth()*imagePair.getHeight());
00387 } else {
00388 copyPointCloudClamped<2>(pointMap, reinterpret_cast<float*>(&pointCloudMsg->data[0]),
00389 imagePair.getWidth()*imagePair.getHeight());
00390 }
00391 }
00392
00393
00394 if(intensityChannel) {
00395
00396 unsigned char* cloudStart = &pointCloudMsg->data[0];
00397 unsigned char* cloudEnd = &pointCloudMsg->data[0]
00398 + imagePair.getWidth()*imagePair.getHeight()*4*sizeof(float);
00399
00400
00401 unsigned char* imagePtr = imagePair.getPixelData(0);
00402 unsigned char* rowEndPtr = imagePtr + imagePair.getWidth();
00403 int rowIncrement = imagePair.getRowStride(0) - imagePair.getWidth();
00404
00405 for(unsigned char* cloudPtr = cloudStart + 3*sizeof(float);
00406 cloudPtr < cloudEnd; cloudPtr+= 4*sizeof(float)) {
00407 *cloudPtr = *imagePtr;
00408
00409 imagePtr++;
00410 if(imagePtr == rowEndPtr) {
00411
00412 imagePtr += rowIncrement;
00413 rowEndPtr = imagePtr + imagePair.getWidth();
00414 }
00415 }
00416 }
00417
00418 cloudPublisher->publish(pointCloudMsg);
00419 }
00420
00425 template <int coord> void copyPointCloudClamped(float* src, float* dst, int size) {
00426
00427 float* endPtr = src + 4*size;
00428 for(float *srcPtr = src, *dstPtr = dst; srcPtr < endPtr; srcPtr+=4, dstPtr+=4) {
00429 if(srcPtr[coord] > maxDepth) {
00430 dstPtr[0] = std::numeric_limits<float>::quiet_NaN();
00431 dstPtr[1] = std::numeric_limits<float>::quiet_NaN();
00432 dstPtr[2] = std::numeric_limits<float>::quiet_NaN();
00433 } else {
00434 dstPtr[0] = srcPtr[0];
00435 dstPtr[1] = srcPtr[1];
00436 dstPtr[2] = srcPtr[2];
00437 }
00438 }
00439 }
00440
00445 void initPointCloud() {
00446 ros::NodeHandle privateNh("~");
00447
00448
00449 recon3d.reset(new Reconstruct3D);
00450
00451
00452 pointCloudMsg.reset(new sensor_msgs::PointCloud2);
00453
00454
00455 sensor_msgs::PointField fieldX;
00456 fieldX.name ="x";
00457 fieldX.offset = 0;
00458 fieldX.datatype = sensor_msgs::PointField::FLOAT32;
00459 fieldX.count = 1;
00460 pointCloudMsg->fields.push_back(fieldX);
00461
00462 sensor_msgs::PointField fieldY;
00463 fieldY.name ="y";
00464 fieldY.offset = sizeof(float);
00465 fieldY.datatype = sensor_msgs::PointField::FLOAT32;
00466 fieldY.count = 1;
00467 pointCloudMsg->fields.push_back(fieldY);
00468
00469 sensor_msgs::PointField fieldZ;
00470 fieldZ.name ="z";
00471 fieldZ.offset = 2*sizeof(float);
00472 fieldZ.datatype = sensor_msgs::PointField::FLOAT32;
00473 fieldZ.count = 1;
00474 pointCloudMsg->fields.push_back(fieldZ);
00475
00476 if(intensityChannel) {
00477 sensor_msgs::PointField fieldI;
00478 fieldI.name ="intensity";
00479 fieldI.offset = 3*sizeof(float);
00480 fieldI.datatype = sensor_msgs::PointField::UINT8;
00481 fieldI.count = 1;
00482 pointCloudMsg->fields.push_back(fieldI);
00483 }
00484 }
00485
00489 void publishCameraInfo(ros::Time stamp, const ImagePair& imagePair) {
00490 if(camInfoMsg == NULL) {
00491
00492 camInfoMsg.reset(new nerian_sp1::StereoCameraInfo);
00493
00494 camInfoMsg->header.frame_id = frame;
00495 camInfoMsg->header.seq = imagePair.getSequenceNumber();
00496
00497 if(calibFile != "") {
00498 std::vector<int> sizeVec;
00499 calibStorage["size"] >> sizeVec;
00500 if(sizeVec.size() != 2) {
00501 std::runtime_error("Calibration file format error!");
00502 }
00503
00504 camInfoMsg->left_info.header = camInfoMsg->header;
00505 camInfoMsg->left_info.width = sizeVec[0];
00506 camInfoMsg->left_info.height = sizeVec[1];
00507 camInfoMsg->left_info.distortion_model = "plumb_bob";
00508 calibStorage["D1"] >> camInfoMsg->left_info.D;
00509 readCalibrationArray("M1", camInfoMsg->left_info.K);
00510 readCalibrationArray("R1", camInfoMsg->left_info.R);
00511 readCalibrationArray("P1", camInfoMsg->left_info.P);
00512 camInfoMsg->left_info.binning_x = 1;
00513 camInfoMsg->left_info.binning_y = 1;
00514 camInfoMsg->left_info.roi.do_rectify = false;
00515 camInfoMsg->left_info.roi.height = 0;
00516 camInfoMsg->left_info.roi.width = 0;
00517 camInfoMsg->left_info.roi.x_offset = 0;
00518 camInfoMsg->left_info.roi.y_offset = 0;
00519
00520 camInfoMsg->right_info.header = camInfoMsg->header;
00521 camInfoMsg->right_info.width = sizeVec[0];
00522 camInfoMsg->right_info.height = sizeVec[1];
00523 camInfoMsg->right_info.distortion_model = "plumb_bob";
00524 calibStorage["D2"] >> camInfoMsg->right_info.D;
00525 readCalibrationArray("M2", camInfoMsg->right_info.K);
00526 readCalibrationArray("R2", camInfoMsg->right_info.R);
00527 readCalibrationArray("P2", camInfoMsg->right_info.P);
00528 camInfoMsg->right_info.binning_x = 1;
00529 camInfoMsg->right_info.binning_y = 1;
00530 camInfoMsg->right_info.roi.do_rectify = false;
00531 camInfoMsg->right_info.roi.height = 0;
00532 camInfoMsg->right_info.roi.width = 0;
00533 camInfoMsg->right_info.roi.x_offset = 0;
00534 camInfoMsg->right_info.roi.y_offset = 0;
00535
00536 readCalibrationArray("Q", camInfoMsg->Q);
00537 readCalibrationArray("T", camInfoMsg->T_left_right);
00538 readCalibrationArray("R", camInfoMsg->R_left_right);
00539 }
00540 }
00541
00542 double dt = (stamp - lastCamInfoPublish).toSec();
00543 if(dt > 1.0) {
00544
00545 const float* qMatrix = imagePair.getQMatrix();
00546 if(qMatrix[0] != 0.0) {
00547 for(int i=0; i<16; i++) {
00548 camInfoMsg->Q[i] = static_cast<double>(qMatrix[i]);
00549 }
00550 }
00551
00552
00553 camInfoMsg->header.stamp = stamp;
00554 camInfoMsg->left_info.header.stamp = stamp;
00555 camInfoMsg->right_info.header.stamp = stamp;
00556 cameraInfoPublisher->publish(camInfoMsg);
00557
00558 lastCamInfoPublish = stamp;
00559 }
00560 }
00561
00565 template<class T>
00566 void readCalibrationArray(const char* key, T& dest) {
00567 std::vector<double> doubleVec;
00568 calibStorage[key] >> doubleVec;
00569
00570 if(doubleVec.size() != dest.size()) {
00571 std::runtime_error("Calibration file format error!");
00572 }
00573
00574 std::copy(doubleVec.begin(), doubleVec.end(), dest.begin());
00575 }
00576 };
00577
00578 int main(int argc, char** argv) {
00579 try {
00580 ros::init(argc, argv, "nerian_sp1");
00581 Sp1Node node;
00582 node.init();
00583 return node.run();
00584 } catch(const std::exception& ex) {
00585 ROS_FATAL("Exception occured: %s", ex.what());
00586 return 1;
00587 }
00588 }