00001
00002 #include <boost/make_shared.hpp>
00003 #include <ros/ros.h>
00004 #include <ros/package.h>
00005 #include <nodelet/nodelet.h>
00006 #include <pluginlib/class_list_macros.h>
00007 #include <cv_bridge/cv_bridge.h>
00008 #include <image_transport/image_transport.h>
00009 #include <opencv2/core/core.hpp>
00010 #include <opencv2/imgproc/imgproc.hpp>
00011 #include <sensor_msgs/image_encodings.h>
00012 #include <tf/transform_broadcaster.h>
00013 #include <tf/transform_datatypes.h>
00014
00015 #include "omnicamera/LookupStitcher.h"
00016
00017
00018
00019 namespace omnicamera {
00020
00021 class LadybugPanoNodelet : public nodelet::Nodelet {
00022 public:
00023 LadybugPanoNodelet();
00024 void onInit();
00025 void imageCallback(const sensor_msgs::ImageConstPtr& msg);
00026 private:
00027 int inWidth;
00028 int inHeight;
00029 int outWidth;
00030 int outHeight;
00031 double pan;
00032 string omnicamFrameId;
00033 string frameId;
00034 bool alpha;
00035 int radius;
00036 string lutPath;
00037 omnicamera::LookupStitcher stitcher;
00041 boost::shared_ptr<image_transport::ImageTransport> it;
00042 image_transport::Subscriber imageSub;
00043 image_transport::Publisher outPub;
00044 ros::Publisher camPub;
00045 bool lookupTablesLoaded;
00046 tf::TransformBroadcaster tfBroadcaster;
00047 };
00048
00049 LadybugPanoNodelet::LadybugPanoNodelet() {
00050 }
00051
00052 void LadybugPanoNodelet::onInit() {
00053 NODELET_INFO("Initializing LadybugPanoNodelet...");
00054 #ifdef USE_MT_NODE_HANDLE
00055 ros::NodeHandle &nh = getMTNodeHandle();
00056 ros::NodeHandle &pnh = getMTPrivateNodeHandle();
00057 #else
00058 ros::NodeHandle &nh = getNodeHandle();
00059 ros::NodeHandle &pnh = getPrivateNodeHandle();
00060 #endif
00061
00062 pnh.param("in_width", inWidth, 1616);
00063 pnh.param("in_height", inHeight, 1232);
00064 NODELET_INFO("Input image size: %dx%d.", inWidth, inHeight);
00065
00066 pnh.param("out_width", outWidth, 960);
00067 pnh.param("out_height", outHeight, 480);
00068 pnh.param("pan", pan, 0.0);
00069 pnh.param("frame_id", frameId, string("pano"));
00070 pnh.param("omnicam_frame_id", omnicamFrameId, string("omnicam"));
00071 NODELET_INFO("Output image size: %dx%d, frame id: %s, omnicam frame id: %s.", outWidth, outHeight, frameId.data(), omnicamFrameId.data());
00072
00073 pnh.param("alpha", alpha, true);
00074 NODELET_INFO("Alpha blending: %s.", alpha ? "enabled" : "disabled");
00075
00076 pnh.param("radius", radius, 20);
00077 NODELET_INFO("Sphere radius: %d.", radius);
00078
00079 std::string omnicameraPath = ros::package::getPath("omnicamera");
00080 pnh.param("lut_format", lutPath, string("/res/lut/LUT_img_%dx%d_pano_%dx%dr%d_%s%d.png"));
00081 lutPath = omnicameraPath + lutPath;
00082 NODELET_INFO("LUT path format: %s.", lutPath.data());
00083
00084 lookupTablesLoaded = false;
00085
00086
00087 it.reset(new image_transport::ImageTransport(nh));
00088 imageSub = it->subscribe("in", 1, &LadybugPanoNodelet::imageCallback, this);
00089 outPub = it->advertise("out", 1);
00090 camPub = nh.advertise<sensor_msgs::CameraInfo>("camera_info", 1);
00091 }
00092
00093 void LadybugPanoNodelet::imageCallback(const sensor_msgs::ImageConstPtr& msg) {
00094
00095
00096 if (!lookupTablesLoaded) {
00097 NODELET_INFO("Loading look-up tables...");
00098 stitcher.clearLookupTables();
00099 for (int iCam = 1; iCam <= 6; iCam++) {
00100 int len = 1024;
00101 char xPath[len], yPath[len], aPath[len];
00102 snprintf(xPath, len, lutPath.data(), inWidth, inHeight, outWidth, outHeight, radius, "x", iCam);
00103 snprintf(yPath, len, lutPath.data(), inWidth, inHeight, outWidth, outHeight, radius, "y", iCam);
00104 snprintf(aPath, len, lutPath.data(), inWidth, inHeight, outWidth, outHeight, radius, "a", iCam);
00105 NODELET_INFO("Camera %d look-up tables: %s, %s, %s.", iCam, xPath, yPath, aPath);
00106 stitcher.addLookupTables(string(xPath), string(yPath), string(aPath));
00107 }
00108 stitcher.fixBounds(inWidth, inHeight);
00109
00110
00111 int shiftRight = round(outWidth * pan / 360.0);
00112 if (shiftRight != 0) {
00113 NODELET_INFO("Using pan %f. Shifting look-up tables right by %d pixels.", pan, shiftRight);
00114 cv::Mat tempLut, tempColRange;
00115 int cols = stitcher.getLutCount() > 0 ? stitcher.lutX[0].cols : 0;
00116 for (int i = 0; shiftRight != 0 && i < stitcher.getLutCount(); i++) {
00117 if (shiftRight > 0) {
00118
00119
00120 stitcher.lutA[i].copyTo(tempLut);
00121
00122 NODELET_DEBUG("Valid range <%d, %d>.", 0, cols - 1);
00123 NODELET_DEBUG("Copy <%d, %d> to <%d, %d>", 0, cols - shiftRight - 1, shiftRight, cols - 1);
00124 NODELET_DEBUG("Copy <%d, %d> to <%d, %d>", cols - shiftRight, cols - 1, 0, shiftRight - 1);
00125 tempColRange = tempLut.colRange(shiftRight, cols - 1);
00126 stitcher.lutA[i].colRange(0, cols - shiftRight - 1).copyTo(tempColRange);
00127 tempColRange = tempLut.colRange(0, shiftRight - 1);
00128 stitcher.lutA[i].colRange(cols - shiftRight, cols - 1).copyTo(tempColRange);
00129
00130 tempLut.copyTo(stitcher.lutA[i]);
00131
00132 stitcher.lutX[i].copyTo(tempLut);
00133 tempColRange = tempLut.colRange(shiftRight, cols - 1);
00134 stitcher.lutX[i].colRange(0, cols - shiftRight - 1).copyTo(tempColRange);
00135 tempColRange = tempLut.colRange(0, shiftRight - 1);
00136 stitcher.lutX[i].colRange(cols - shiftRight, cols - 1).copyTo(tempColRange);
00137 tempLut.copyTo(stitcher.lutX[i]);
00138
00139 stitcher.lutY[i].copyTo(tempLut);
00140 tempColRange = tempLut.colRange(shiftRight, cols - 1);
00141 stitcher.lutY[i].colRange(0, cols - shiftRight - 1).copyTo(tempColRange);
00142 tempColRange = tempLut.colRange(0, shiftRight - 1);
00143 stitcher.lutY[i].colRange(cols - shiftRight, cols - 1).copyTo(tempColRange);
00144 tempLut.copyTo(stitcher.lutY[i]);
00145
00146 } else {
00147
00148 int shiftLeft = -shiftRight;
00149 stitcher.lutA[i].copyTo(tempLut);
00150 NODELET_DEBUG("Valid range <%d, %d>.", 0, cols - 1);
00151 NODELET_DEBUG("Copy <%d, %d> to <%d, %d>", shiftLeft, cols - 1, 0, cols - shiftLeft - 1);
00152 NODELET_DEBUG("Copy <%d, %d> to <%d, %d>", 0, shiftLeft - 1, cols - shiftLeft, cols - 1);
00153 tempColRange = tempLut.colRange(0, cols - shiftLeft - 1);
00154 stitcher.lutA[i].colRange(shiftLeft, cols - 1).copyTo(tempColRange);
00155 tempColRange = tempLut.colRange(cols - shiftLeft, cols - 1);
00156 stitcher.lutA[i].colRange(0, shiftLeft - 1).copyTo(tempColRange);
00157 tempLut.copyTo(stitcher.lutA[i]);
00158
00159 stitcher.lutX[i].copyTo(tempLut);
00160 tempColRange = tempLut.colRange(0, cols - shiftLeft - 1);
00161 stitcher.lutX[i].colRange(shiftLeft, cols - 1).copyTo(tempColRange);
00162 tempColRange = tempLut.colRange(cols - shiftLeft, cols - 1);
00163 stitcher.lutX[i].colRange(0, shiftLeft - 1).copyTo(tempColRange);
00164 tempLut.copyTo(stitcher.lutX[i]);
00165
00166 stitcher.lutY[i].copyTo(tempLut);
00167 tempColRange = tempLut.colRange(0, cols - shiftLeft - 1);
00168 stitcher.lutY[i].colRange(shiftLeft, cols - 1).copyTo(tempColRange);
00169 tempColRange = tempLut.colRange(cols - shiftLeft, cols - 1);
00170 stitcher.lutY[i].colRange(0, shiftLeft - 1).copyTo(tempColRange);
00171 tempLut.copyTo(stitcher.lutY[i]);
00172 }
00173 }
00174 }
00175
00176 lookupTablesLoaded = true;
00177 }
00178
00179 if (outPub.getNumSubscribers() == 0 && camPub.getNumSubscribers() == 0) {
00180 return;
00181 }
00182
00183 cv_bridge::CvImageConstPtr cvPtr;
00184 try {
00185 cvPtr = cv_bridge::toCvShare(msg);
00186 if (cvPtr->encoding.compare(sensor_msgs::image_encodings::BGR8)) {
00187 NODELET_ERROR("Encoding not supported: %s.", cvPtr->encoding.data());
00188 return;
00189 }
00190 } catch (cv_bridge::Exception& e) {
00191 NODELET_ERROR("cv_bridge exception: %s", e.what());
00192 return;
00193 }
00194
00195 if (cvPtr->image.rows != inHeight || cvPtr->image.cols != inWidth) {
00196 NODELET_ERROR("Unexpected image dimensions: %dx%d.", cvPtr->image.cols, cvPtr->image.rows);
00197 }
00198
00199 Mat image;
00200 NODELET_DEBUG("Stitching started.");
00201 stitcher.stitchImage(cvPtr->image, image);
00202 NODELET_DEBUG("Stitching finished.");
00203
00204 tf::Transform transform;
00205 double realPan = 2 * M_PI * round(outWidth * pan / 360.0) / outWidth;
00206 transform.setRotation(tf::createQuaternionFromRPY(0, 0, realPan));
00207 tfBroadcaster.sendTransform(tf::StampedTransform(transform, msg->header.stamp, omnicamFrameId, frameId));
00208
00209 cv_bridge::CvImage out;
00210 out.encoding = sensor_msgs::image_encodings::BGR8;
00211 out.header = msg->header;
00212 out.header.frame_id = frameId;
00213 out.image = image;
00214 outPub.publish(out.toImageMsg());
00215
00216 sensor_msgs::CameraInfo camMsg;
00217 camMsg.header = out.header;
00218 camMsg.height = out.image.rows;
00219 camMsg.width = out.image.cols;
00220 camPub.publish(camMsg);
00221 }
00222
00223 }
00224
00225 PLUGINLIB_DECLARE_CLASS(omnicamera, ladybug_pano, omnicamera::LadybugPanoNodelet, nodelet::Nodelet)