├── config ├── dead_pixel.png ├── mean_compensation.png └── seekthermal_ros.yaml ├── README.md ├── launch └── seekthermal_ros.launch ├── src ├── seekthermal_ros_node.cpp └── seekthermal_ros.cpp ├── msg └── ThermalImage.msg ├── .gitignore ├── package.xml ├── LICENSE ├── include └── seekthermal_ros │ └── seekthermal_ros.h └── CMakeLists.txt /config/dead_pixel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethz-asl/seekthermal_ros/HEAD/config/dead_pixel.png -------------------------------------------------------------------------------- /config/mean_compensation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethz-asl/seekthermal_ros/HEAD/config/mean_compensation.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # seekthermal_ros 2 | ROS node for Seek thermal imaging devices 3 | 4 | ## Depedencies 5 | [libseekthermal](https://github.com/ethz-asl/libseekthermal) 6 | -------------------------------------------------------------------------------- /launch/seekthermal_ros.launch: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/seekthermal_ros_node.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace seekthermal_ros; 4 | 5 | int main(int argc, char** argv) 6 | { 7 | ros::init(argc, argv, "seekthermal_ros_node"); 8 | ros::NodeHandle nh("~"); 9 | 10 | SeekthermalRos seekthermalRos(nh); 11 | 12 | ros::spin(); 13 | return 0; 14 | } 15 | -------------------------------------------------------------------------------- /msg/ThermalImage.msg: -------------------------------------------------------------------------------- 1 | uint32 height 2 | uint32 width 3 | 4 | # float32 image, raw data 5 | float32[] data_raw # actual matrix data, size is (height*width) col1, col2, ... 6 | 7 | # uint8 image, dead pixel mask 8 | uint8[] data_mask # actual matrix data, size is (height*width) col1, col2, ... 9 | 10 | # images 11 | sensor_msgs/Image image_gray 12 | sensor_msgs/Image image_colored 13 | -------------------------------------------------------------------------------- /config/seekthermal_ros.yaml: -------------------------------------------------------------------------------- 1 | thermal_image_topic_name: image_raw 2 | thermal_image_raw_topic_name: thermal_image_raw 3 | colored_thermal_image_topic_name: image_colored 4 | camera_frame_id: thermal_camera 5 | camera_name: thermal_camera 6 | camera_info_url: file://${ROS_HOME}/camera_info/${NAME}.yaml 7 | interpolate_dead_pixels: true 8 | calibrate_dead_pixels: false 9 | mean_compensation: true 10 | calibrate_mean_compensation: false 11 | denoise: true 12 | show_debug_images: false 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | *.obj 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Compiled Dynamic libraries 12 | *.so 13 | *.dylib 14 | *.dll 15 | 16 | # Fortran module files 17 | *.mod 18 | 19 | # Compiled Static libraries 20 | *.lai 21 | *.la 22 | *.a 23 | *.lib 24 | 25 | # Executables 26 | *.exe 27 | *.out 28 | *.app 29 | 30 | # tmp files 31 | *~ 32 | 33 | # qt 34 | /build 35 | *CMakeLists.txt.user 36 | 37 | # CLion 38 | *.idea/ 39 | 40 | 41 | -------------------------------------------------------------------------------- /package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | seekthermal_ros 4 | 0.0.0 5 | The seekthermal_ros package 6 | 7 | Dominic Jud 8 | 9 | BSD 10 | 11 | Dominic Jud 12 | 13 | catkin 14 | roscpp 15 | image_transport 16 | cv_bridge 17 | sensor_msgs 18 | camera_info_manager 19 | image_geometry 20 | cmake_modules 21 | std_msgs 22 | message_generation 23 | 24 | roscpp 25 | image_transport 26 | cv_bridge 27 | sensor_msgs 28 | camera_info_manager 29 | image_geometry 30 | std_msgs 31 | message_runtime 32 | 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, ETHZ ASL 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of seekthermal_ros nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | -------------------------------------------------------------------------------- /include/seekthermal_ros/seekthermal_ros.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // libseekthermal 4 | #include 5 | #include 6 | #include 7 | 8 | // ros 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | // opencv 17 | #include 18 | #include 19 | 20 | // boost 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include 27 | 28 | // thermal image msg 29 | #include 30 | 31 | using namespace cv; 32 | using namespace SeekThermal; 33 | 34 | namespace seekthermal_ros { 35 | 36 | class SeekthermalRos 37 | { 38 | public: 39 | /*! 40 | * Constructor. 41 | */ 42 | SeekthermalRos(ros::NodeHandle nh); 43 | 44 | /*! 45 | * Destructor. 46 | */ 47 | virtual ~SeekthermalRos(); 48 | 49 | private: 50 | void captureThermalImages(const Pointer& device); 51 | void publishingThermalImages(); 52 | Mat convertFromGrayToColor(Mat &image); 53 | 54 | ros::NodeHandle nh_; 55 | image_transport::ImageTransport it_; 56 | image_transport::CameraPublisher thermal_image_publisher_; 57 | image_transport::Publisher colored_thermal_image_publisher_; 58 | ros::Publisher thermal_image_raw_publisher_; 59 | 60 | boost::shared_ptr cinfo_; 61 | 62 | std::string device_adress_; 63 | 64 | std::string thermal_image_topic_name_; 65 | std::string thermal_image_raw_topic_name_; 66 | std::string colored_thermal_image_topic_name_; 67 | 68 | std::string camera_name_; 69 | std::string camera_frame_id_; 70 | std::string camera_info_url_; 71 | 72 | std::string package_path_; 73 | 74 | Pointer device_; 75 | 76 | Frame last_calibration_frame_; 77 | bool use_inpaint_; 78 | bool calibrate_dead_pixels_; 79 | bool mean_compensation_; 80 | bool calibrate_mean_compensation_; 81 | bool denoise_; 82 | bool show_debug_images_; 83 | 84 | enum State {CALIBRATE_DEAD_PIXEL, LOAD_DEAD_PIXEL, CALIBRATE_MEAN, LOAD_MEAN, RUN}; 85 | State state_; 86 | 87 | cv::Mat inpaint_mask_; 88 | cv::Mat mean_compensation_image_; 89 | 90 | std::queue> frame_queue_; 91 | mutable boost::mutex mutex_; 92 | boost::condition_variable condition_variable_; 93 | 94 | }; 95 | 96 | } /* namespace */ 97 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8.3) 2 | project(seekthermal_ros) 3 | 4 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") 5 | 6 | ## Find catkin macros and libraries 7 | ## if COMPONENTS list like find_package(catkin REQUIRED COMPONENTS xyz) 8 | ## is used, also find other catkin packages 9 | find_package(catkin REQUIRED COMPONENTS 10 | roscpp 11 | std_msgs 12 | genmsg 13 | image_transport 14 | cv_bridge 15 | sensor_msgs 16 | camera_info_manager 17 | image_geometry 18 | ) 19 | 20 | ## Generate messages in the 'msg' folder 21 | add_message_files( 22 | DIRECTORY 23 | msg 24 | FILES 25 | ThermalImage.msg 26 | ) 27 | 28 | ## Generate added messages and services with any dependencies listed here 29 | generate_messages( 30 | DEPENDENCIES 31 | std_msgs 32 | sensor_msgs 33 | ) 34 | 35 | ## System dependencies are found with CMake's conventions 36 | find_package(cmake_modules REQUIRED) 37 | find_package(OpenCV REQUIRED) 38 | find_package(PkgConfig) 39 | pkg_check_modules(SEEKTHERMAL libseekthermal) 40 | find_package(Boost REQUIRED) 41 | 42 | ## Uncomment this if the package has a setup.py. This macro ensures 43 | ## modules and global scripts declared therein get installed 44 | ## See http://ros.org/doc/api/catkin/html/user_guide/setup_dot_py.html 45 | # catkin_python_setup() 46 | 47 | 48 | ################################### 49 | ## catkin specific configuration ## 50 | ################################### 51 | ## The catkin_package macro generates cmake config files for your package 52 | ## Declare things to be passed to dependent projects 53 | ## INCLUDE_DIRS: uncomment this if you package contains header files 54 | ## LIBRARIES: libraries you create in this project that dependent projects also need 55 | ## CATKIN_DEPENDS: catkin_packages dependent projects also need 56 | ## DEPENDS: system dependencies of this project that dependent projects also need 57 | catkin_package( 58 | # INCLUDE_DIRS include 59 | # LIBRARIES thermal_hotspot_inspection 60 | CATKIN_DEPENDS roscpp std_msgs genmsg image_transport cv_bridge sensor_msgs camera_info_manager image_geometry 61 | # DEPENDS system_libs 62 | CATKIN_DEPENDS message_runtime 63 | ) 64 | 65 | ########### 66 | ## Build ## 67 | ########### 68 | 69 | ## Specify additional locations of header files 70 | ## Your package locations should be listed before other locations 71 | include_directories(include) 72 | include_directories( 73 | ${catkin_INCLUDE_DIRS} 74 | ${SEEKTHERMAL_INCLUDE_DIRS} 75 | ${Boost_INCLUDE_DIRS} 76 | ) 77 | 78 | ## Declare a cpp library 79 | add_library(seekthermal_ros 80 | src/seekthermal_ros.cpp 81 | ) 82 | 83 | target_link_libraries(seekthermal_ros 84 | ${catkin_LIBRARIES} 85 | ${OpenCV_LIBS} 86 | ${SEEKTHERMAL_LIBRARIES} 87 | ${Boost_LIBRARIES} 88 | ) 89 | add_dependencies(seekthermal_ros 90 | seekthermal_ros_generate_messages_cpp 91 | ) 92 | 93 | ## Declare a cpp executable 94 | add_executable(seekthermal_ros_node src/seekthermal_ros_node.cpp) 95 | 96 | ## Specify libraries to link a library or executable target against 97 | target_link_libraries(seekthermal_ros_node 98 | seekthermal_ros 99 | ${catkin_LIBRARIES} 100 | ${OpenCV_LIBS} 101 | ) 102 | 103 | -------------------------------------------------------------------------------- /src/seekthermal_ros.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #define DEG2RAD 0.01745329 11 | 12 | namespace seekthermal_ros { 13 | 14 | SeekthermalRos::SeekthermalRos(ros::NodeHandle nh): nh_(nh), it_(nh) 15 | { 16 | //Get parameters from configuration file 17 | nh_.getParam("thermal_image_topic_name", thermal_image_topic_name_); 18 | nh_.getParam("thermal_image_raw_topic_name", thermal_image_raw_topic_name_); 19 | nh_.getParam("colored_thermal_image_topic_name", colored_thermal_image_topic_name_); 20 | nh_.getParam("device_adress", device_adress_); 21 | nh_.getParam("interpolate_dead_pixels", use_inpaint_); 22 | nh_.getParam("calibrate_dead_pixels", calibrate_dead_pixels_); 23 | nh_.getParam("mean_compensation", mean_compensation_); 24 | nh_.getParam("calibrate_mean_compensation", calibrate_mean_compensation_); 25 | nh_.getParam("denoise", denoise_); 26 | nh_.getParam("show_debug_images", show_debug_images_); 27 | nh_.getParam("camera_frame_id", camera_frame_id_); 28 | nh_.getParam("camera_name", camera_name_); 29 | nh_.getParam("camera_info_url", camera_info_url_); 30 | 31 | //Create a publisher for the raw thermal image 32 | thermal_image_publisher_ = it_.advertiseCamera("/" + camera_name_ + "/" + thermal_image_topic_name_, 1); 33 | colored_thermal_image_publisher_ = it_.advertise("/" + camera_name_ + "/" + colored_thermal_image_topic_name_, 1); 34 | thermal_image_raw_publisher_ = 35 | nh_.advertise("/" + camera_name_ + "/" + thermal_image_raw_topic_name_, 1); 36 | 37 | //nh_.getParam("image_width", image_width_); 38 | //nh_.getParam("image_height", image_height_); 39 | cinfo_.reset(new camera_info_manager::CameraInfoManager(nh_, camera_name_, camera_info_url_)); 40 | // check for default camera info 41 | if (camera_info_url_.size() == 0) 42 | { 43 | cinfo_->setCameraName(camera_name_); 44 | sensor_msgs::CameraInfo camera_info; 45 | camera_info.header.frame_id = camera_frame_id_; 46 | //camera_info.width = image_width_; 47 | //camera_info.height = image_height_; 48 | cinfo_->setCameraInfo(camera_info); 49 | } 50 | 51 | //Find all connected devices 52 | std::list> devices = discoverDevices(); 53 | 54 | SeekThermal::Usb::Context context; 55 | Pointer interface; 56 | 57 | //choose first device in list if no device was specified in the configuration file 58 | if (device_adress_.empty()) 59 | { 60 | if (!devices.empty()) 61 | { 62 | for (std::list >::const_iterator it = devices.begin(); it != devices.end(); ++it) 63 | { 64 | ROS_INFO_STREAM("Found " << (*it)->getInterface()->getName() << " " << (*it)->getInterface()->getAddress()); 65 | } 66 | 67 | ROS_INFO_STREAM("Trying to open first device: " << devices.front()->getInterface()->getAddress()); 68 | interface = context.getInterface(devices.front()->getInterface()->getAddress()); 69 | } 70 | else 71 | { 72 | ROS_ERROR_STREAM("No devices found and no device specified in config file"); 73 | ros::shutdown(); 74 | } 75 | } 76 | else 77 | { 78 | ROS_INFO_STREAM("Trying to open device at " << device_adress_); 79 | interface = context.getInterface(device_adress_); 80 | } 81 | 82 | device_ = interface->discoverDevice(); 83 | 84 | if (!device_.isNull()) { 85 | interface->setTimeout(1); 86 | device_->setInterface(interface); 87 | device_->connect(); 88 | device_->initialize(); 89 | ROS_INFO_STREAM("Device initialized!"); 90 | } 91 | else 92 | { 93 | ROS_ERROR_STREAM("Failed to open device"); 94 | ros::shutdown(); 95 | } 96 | 97 | if (calibrate_mean_compensation_) 98 | { 99 | state_ = CALIBRATE_MEAN; 100 | } 101 | else 102 | { 103 | state_ = LOAD_MEAN; 104 | } 105 | 106 | package_path_ = ros::package::getPath("seekthermal_ros"); 107 | 108 | boost::thread* publishing_thread = new boost::thread(boost::bind(&SeekthermalRos::publishingThermalImages, this)); 109 | 110 | captureThermalImages(device_); 111 | } 112 | 113 | SeekthermalRos::~SeekthermalRos() 114 | { 115 | device_->disconnect(); 116 | } 117 | 118 | void SeekthermalRos::captureThermalImages(const Pointer& device) 119 | { 120 | while(ros::ok()) 121 | { 122 | if (thermal_image_publisher_.getNumSubscribers()>0 || 123 | colored_thermal_image_publisher_.getNumSubscribers()>0 || 124 | thermal_image_raw_publisher_.getNumSubscribers()>0) 125 | { 126 | Pointer frame = new Frame(); 127 | device->capture(*frame); 128 | 129 | boost::mutex::scoped_lock lock(mutex_); 130 | frame_queue_.push(frame); 131 | 132 | if (frame_queue_.size()>10) 133 | { 134 | frame_queue_.pop(); 135 | } 136 | lock.unlock(); 137 | condition_variable_.notify_one(); 138 | } 139 | else 140 | ros::Duration(0.1).sleep(); 141 | } 142 | } 143 | 144 | void SeekthermalRos::publishingThermalImages() 145 | { 146 | static int seq_counter = 0; 147 | static int dead_pixel_counter = 0; 148 | static size_t frameId = 0; 149 | 150 | std::vector frame_vector; 151 | 152 | static Frame meanFrame; 153 | static Frame varianceFrame; 154 | 155 | while(ros::ok()) 156 | { 157 | boost::mutex::scoped_lock lock(mutex_); 158 | while(frame_queue_.empty()) 159 | { 160 | condition_variable_.wait(lock); 161 | } 162 | 163 | Pointer frame = frame_queue_.front(); 164 | frame_queue_.pop(); 165 | 166 | if (frame->getType() == Frame::typeNormal) { 167 | //ROS_INFO_STREAM("Normal Frame"); 168 | size_t width = frame->getWidth(); 169 | size_t height = frame->getHeight(); 170 | 171 | Frame frame_t = *frame; 172 | 173 | //subtract most recent calibration frame 174 | if (!last_calibration_frame_.isEmpty()) 175 | *frame -= last_calibration_frame_; 176 | 177 | static Frame mean_comp; 178 | 179 | // raw data 180 | Mat cvImage_raw = Mat(frame->getHeight(), frame->getWidth(), CV_32FC1); 181 | for (size_t x = 0; x < frame->getWidth(); ++x) { 182 | for (size_t y = 0; y < frame->getHeight(); ++y) { 183 | float value = (*frame)(x, y); 184 | cvImage_raw.at(y, x) = value; 185 | } 186 | } 187 | 188 | // normalize 189 | // TODO min max? 190 | frame->normalize(-1300,0); 191 | 192 | if (show_debug_images_) 193 | { 194 | Mat cvImage_normalized = Mat(frame->getHeight(), frame->getWidth(), CV_8UC3); 195 | for (size_t x = 0; x < frame->getWidth(); ++x) 196 | for (size_t y = 0; y < frame->getHeight(); ++y) { 197 | float value = (*frame)(x, y)*255.0; 198 | cvImage_normalized.at(y,x)[0] = value; 199 | cvImage_normalized.at(y,x)[1] = value; 200 | cvImage_normalized.at(y,x)[2] = value; 201 | } 202 | 203 | cv::imshow("normalized", cvImage_normalized); 204 | cv::waitKey(10); 205 | } 206 | 207 | 208 | switch (state_) 209 | { 210 | case CALIBRATE_MEAN: 211 | //collect frames for calibrating the mean 212 | if (frame_vector.size() < 20) 213 | { 214 | ROS_INFO_STREAM("mean"); 215 | frame_vector.push_back(*frame); 216 | } 217 | else 218 | { 219 | mean_comp = Frame(frame->getWidth(), frame->getHeight(), Frame::typeNormal); 220 | for(std::vector::size_type i = 0; i != frame_vector.size(); i++) 221 | { 222 | mean_comp += frame_vector[i]; 223 | } 224 | mean_comp *= 1.0/(float)frame_vector.size(); 225 | float overall_mean = 0; 226 | for (size_t x = 0; x < width; ++x) 227 | for (size_t y = 0; y < height; ++y) { 228 | float value = (mean_comp)(x, y); 229 | overall_mean += value; 230 | } 231 | overall_mean /= width*height; 232 | 233 | mean_comp -= overall_mean; 234 | 235 | mean_compensation_image_ = Mat(height, width, CV_8UC1); 236 | for (size_t x = 0; x < width; ++x) 237 | for (size_t y = 0; y < height; ++y) 238 | { 239 | float value = (mean_comp)(x, y)*255; 240 | mean_compensation_image_.at(y,x) = value+256/2; 241 | } 242 | 243 | std::vector image_pref; 244 | image_pref.push_back(CV_IMWRITE_PNG_COMPRESSION); 245 | image_pref.push_back(0); 246 | 247 | cv::imwrite(package_path_ + "/config/mean_compensation.png", mean_compensation_image_, image_pref); 248 | 249 | frame_vector.clear(); 250 | if (calibrate_dead_pixels_) 251 | { 252 | state_ = CALIBRATE_DEAD_PIXEL; 253 | } 254 | else 255 | { 256 | state_ = LOAD_DEAD_PIXEL; 257 | } 258 | } 259 | break; 260 | 261 | 262 | //Load an already existing mean compensation image 263 | case LOAD_MEAN: 264 | mean_compensation_image_ = imread(package_path_ + "/config/mean_compensation.png", CV_LOAD_IMAGE_GRAYSCALE); 265 | 266 | if(! mean_compensation_image_.data ) // Check for invalid input 267 | { 268 | ROS_ERROR_STREAM("No mean calibration file found. Forced calibration."); 269 | state_ = CALIBRATE_MEAN; 270 | } 271 | else if (calibrate_dead_pixels_) 272 | { 273 | state_ = CALIBRATE_DEAD_PIXEL; 274 | } 275 | else 276 | { 277 | state_ = LOAD_DEAD_PIXEL; 278 | } 279 | break; 280 | 281 | // find pixels that are dead by calculating the variance of each pixel 282 | case CALIBRATE_DEAD_PIXEL: 283 | if (frame_vector.size() < 50) 284 | { 285 | ROS_INFO_STREAM("dead pixel"); 286 | frame_vector.push_back(*frame); 287 | } 288 | else 289 | { 290 | Frame mean = Frame(frame->getWidth(), frame->getHeight(), Frame::typeNormal); 291 | for(std::vector::size_type i = 0; i != frame_vector.size(); i++) 292 | { 293 | mean += frame_vector[i]; 294 | } 295 | mean *= 1.0/(float)frame_vector.size(); 296 | 297 | Frame temp = Frame(frame->getWidth(), frame->getHeight(), Frame::typeNormal); 298 | for(std::vector::size_type i = 0; i != frame_vector.size(); i++) 299 | { 300 | temp += (mean-frame_vector[i])*(mean-frame_vector[i]); 301 | } 302 | Frame variance = temp*(1.0/(float)frame_vector.size()); 303 | 304 | dead_pixel_counter++; 305 | 306 | Mat variance_mat = Mat(height, width, CV_8UC1); 307 | for (size_t x = 0; x < width; ++x) 308 | for (size_t y = 0; y < height; ++y) { 309 | float value = (variance)(x, y)*255.0; 310 | variance_mat.at(y,x) = value; 311 | } 312 | 313 | inpaint_mask_ = variance_mat<0.00001; 314 | 315 | std::vector image_pref; 316 | image_pref.push_back(CV_IMWRITE_PNG_COMPRESSION); 317 | image_pref.push_back(0); 318 | 319 | cv::imwrite(package_path_ + "/config/dead_pixel.png", inpaint_mask_, image_pref); 320 | 321 | frame_vector.clear(); 322 | 323 | state_ = RUN; 324 | } 325 | 326 | break; 327 | 328 | //Load calibration image for dead pixels 329 | case LOAD_DEAD_PIXEL: 330 | inpaint_mask_ = imread(package_path_ + "/config/dead_pixel.png", CV_LOAD_IMAGE_GRAYSCALE); 331 | 332 | if(! inpaint_mask_.data ) // Check for invalid input 333 | { 334 | ROS_ERROR_STREAM("No dead pixel calibration file found. Forced calibration."); 335 | state_ = CALIBRATE_DEAD_PIXEL; 336 | } 337 | else 338 | { 339 | state_ = RUN; 340 | } 341 | break; 342 | 343 | case RUN: 344 | // raw data 345 | // build raw image msg 346 | seekthermal_ros::ThermalImage msgThermalImage; 347 | msgThermalImage.height = cvImage_raw.rows; 348 | msgThermalImage.width = cvImage_raw.cols; 349 | for (int x = 0; x < cvImage_raw.cols; ++x) { 350 | for (int y = 0; y < cvImage_raw.rows; ++y) { 351 | msgThermalImage.data_raw.push_back(cvImage_raw.at(y, x)); 352 | msgThermalImage.data_mask.push_back(inpaint_mask_.at(y, x)); 353 | } 354 | } 355 | 356 | Mat cvImage = Mat(frame->getHeight(), frame->getWidth(), CV_8UC1); 357 | for (size_t x = 0; x < frame->getWidth(); ++x) 358 | for (size_t y = 0; y < frame->getHeight(); ++y) { 359 | float value = (*frame)(x, y)*255.0; 360 | cvImage.at(y,x) = value; 361 | } 362 | 363 | if (mean_compensation_) 364 | { 365 | for (size_t x = 0; x < cvImage.cols; ++x) 366 | for (size_t y = 0; y < cvImage.rows; ++y) 367 | { 368 | float value = cvImage.at(y,x) - mean_compensation_image_.at(y,x) - 256/2; 369 | if (value > -1) 370 | cvImage.at(y,x) = -1; 371 | else 372 | cvImage.at(y,x) = value; 373 | } 374 | 375 | if (show_debug_images_) 376 | { 377 | cv::imshow("mean_comp", cvImage); 378 | cv::waitKey(10); 379 | } 380 | } 381 | 382 | cv::Mat cvImage_inpainted = Mat(height, width, CV_8UC1); 383 | 384 | if (use_inpaint_) 385 | { 386 | cv::inpaint(cvImage, inpaint_mask_, cvImage_inpainted, 1, cv::INPAINT_NS); 387 | cvImage_inpainted.copyTo(cvImage); 388 | if (show_debug_images_) 389 | { 390 | cv::imshow("inpainted frame", cvImage_inpainted); 391 | cv::waitKey(10); 392 | } 393 | } 394 | 395 | cv::Mat cvImage_denoised = Mat(height, width, CV_8UC1); 396 | 397 | if (denoise_) 398 | { 399 | cv::fastNlMeansDenoising(cvImage, cvImage_denoised, 5, 5, 31); 400 | 401 | cvImage_denoised.copyTo(cvImage); 402 | 403 | if (show_debug_images_) 404 | { 405 | cv::Point center(cvImage_denoised.cols / 2, cvImage_denoised.rows / 2); 406 | int rectSize = 9; 407 | cv::rectangle(cvImage_denoised, 408 | cv::Rect(center.x-rectSize/2, center.y-rectSize/2, rectSize, rectSize), 409 | cv::Scalar(0,0,0), 1); 410 | cv::imshow("denoised frame", cvImage_denoised); 411 | cv::waitKey(10); 412 | } 413 | } 414 | 415 | cv::Mat cvImage_colored = Mat(height, width, CV_8UC3); 416 | cvImage_colored = convertFromGrayToColor(cvImage); 417 | 418 | 419 | std_msgs::Header header; 420 | header.seq = seq_counter; 421 | header.frame_id = camera_frame_id_; 422 | //header.stamp = ros::Time(frame.getTimestamp()); 423 | cv_bridge::CvImage *cv_ptr_colored = new cv_bridge::CvImage(header, sensor_msgs::image_encodings::BGR8, cvImage_colored); 424 | cv_bridge::CvImage *cv_ptr = new cv_bridge::CvImage(header, sensor_msgs::image_encodings::MONO8, cvImage); 425 | 426 | // grab the camera info 427 | sensor_msgs::CameraInfoPtr ci(new sensor_msgs::CameraInfo(cinfo_->getCameraInfo())); 428 | ci->header.seq = seq_counter++; 429 | ci->header.frame_id = camera_frame_id_; 430 | //ci->header.stamp = frame.getTimestamp(); 431 | ci->height = frame->getHeight(); 432 | ci->width = frame->getWidth(); 433 | 434 | // publish the image 435 | thermal_image_publisher_.publish(*cv_ptr->toImageMsg(), *ci); 436 | colored_thermal_image_publisher_.publish(*cv_ptr_colored->toImageMsg()); 437 | 438 | // publish raw image 439 | msgThermalImage.image_gray = *cv_ptr->toImageMsg(); 440 | msgThermalImage.image_colored = *cv_ptr_colored->toImageMsg(); 441 | thermal_image_raw_publisher_.publish(msgThermalImage); 442 | } 443 | } 444 | else if (frame->getType() == Frame::typeCalibration) 445 | { 446 | //ROS_INFO_STREAM("Calibration Frame"); 447 | last_calibration_frame_ = *frame; 448 | } 449 | } 450 | } 451 | 452 | Mat SeekthermalRos::convertFromGrayToColor(Mat &image) 453 | { 454 | Mat cvImage_colored = Mat(image.rows, image.cols, CV_8UC3); 455 | for (size_t x = 0; x < image.cols; ++x) 456 | for (size_t y = 0; y < image.rows; ++y) { 457 | float value = image.at(y,x); 458 | float r = (sin((value / 255.0 * 360.0 - 120.0 > 0 ? value / 255.0 * 360.0 - 120.0 : 0) * DEG2RAD) * 0.5 + 0.5) * 255.0; 459 | float g = (sin((value / 255.0 * 360.0 + 60.0) * DEG2RAD) * 0.5 + 0.5) * 255.0; 460 | float b = (sin((value / 255.0 * 360.0 + 140.0) * DEG2RAD) * 0.5 + 0.5) * 255.0; 461 | cvImage_colored.at(y,x)[0] = r; 462 | cvImage_colored.at(y,x)[1] = g; 463 | cvImage_colored.at(y,x)[2] = b; 464 | } 465 | return cvImage_colored; 466 | } 467 | 468 | } /* namespace */ 469 | --------------------------------------------------------------------------------