00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035 """
00036 Panorama capture action server.
00037
00038 Authors: Ethan Rublee, Ken Conley
00039
00040 Sample run command for using the kinect as a capture device:
00041
00042 rosrun pano_ros capture_server.py camera:=/kinect/rgb
00043 """
00044
00045
00046 import os
00047 import tempfile
00048 import threading
00049 import rospy
00050
00051 from pano_ros.msg import PanoCaptureAction, PanoCaptureResult, PanoCaptureFeedback
00052 from std_msgs.msg import Empty
00053 from sensor_msgs.msg import Image, CameraInfo
00054
00055 import actionlib
00056 import rosbag
00057 import message_filters
00058
00059 import pano_py as pano
00060 import pano_cv as pcv
00061 import cv2
00062 from cv2 import cv
00063 import numpy
00064 from cv_bridge import CvBridge, CvBridgeError
00065
00066 class ImageGrabber:
00067 """
00068 Helper class for subscribing to synchronized camera and camera
00069 info data.
00070 """
00071
00072 def __init__(self, image_topic, camera_info_topic, capture_fn):
00073
00074 self._image_sub = message_filters.Subscriber(image_topic, Image)
00075 self._info_sub = message_filters.Subscriber(camera_info_topic, CameraInfo)
00076 self._ts = message_filters.TimeSynchronizer([self._image_sub, self._info_sub], 3)
00077 self._ts.registerCallback(capture_fn)
00078
00079 def stop(self):
00080
00081 self._image_sub.sub.unregister()
00082 self._info_sub.sub.unregister()
00083
00084 del self._image_sub
00085 del self._info_sub
00086 del self._ts
00087
00088 class PanoCaptureJob(object):
00089
00090 def __init__(self, pano_id, capture_fn):
00091 self.pano_id = pano_id
00092 self.capture_fn = capture_fn
00093 self.result = None
00094
00095 def __call__(self, image, info):
00096 """
00097 Call job with latest captured imaged
00098 """
00099
00100 self.capture_fn(image, info)
00101
00102 def _get_n_captures(self):
00103 return self.capture_fn.n_captures if self.capture_fn else self.result.n_captures
00104
00105 n_captures = property(_get_n_captures)
00106
00107 def cancel(self):
00108 if self.capture_fn is None:
00109 return
00110 capturer = self.capture_fn
00111 self.capture_fn = None
00112 capturer.cancel()
00113
00114 def stop(self):
00115 if self.capture_fn is None:
00116 raise Exception("already stopped")
00117 capturer = self.capture_fn
00118 capturer.stop()
00119
00120 self.result = PanoCaptureResult(pano_id=self.pano_id,
00121 n_captures=capturer.n_captures,
00122 bag_filename=capturer.bag_filename)
00123 self.capture_fn = None
00124
00125 class CaptureInterface(object):
00126 """
00127 Common capture interface, like capture to bag, capture and republish, etc...
00128 """
00129 def start(self,pano_id, goal):
00130 """
00131 set up a new pano.
00132 """
00133 raise NotImplemented
00134 def __call__(self, image, camera_info):
00135 """
00136 capture your synced image and camera_info here
00137 """
00138 raise NotImplemented
00139
00140 def stop(self):
00141 """
00142 done capturing, do clean up, close bags, etc.
00143 """
00144 raise NotImplemented
00145
00146 def cancel(self):
00147 """
00148 no matter what stop capturing, you've been prempted
00149 """
00150 raise NotImplemented
00151 class PanoCaptureServer(object):
00152 def __init__(self, name, capture_interface = CaptureInterface):
00153 self._action_name = name
00154 self._server = actionlib.SimpleActionServer(self._action_name,
00155 PanoCaptureAction, execute_cb=self.pano_capture)
00156
00157
00158 self._snap_sub = rospy.Subscriber(rospy.names.ns_join(self._action_name, 'snap'), Empty, self.capture_snap)
00159 self._stop_sub = rospy.Subscriber(rospy.names.ns_join(self._action_name, 'stop'), Empty, self.capture_stop)
00160
00161
00162 self._capture_job = None
00163 self._snap_requested = False
00164 self._capture_interface = capture_interface
00165
00166
00167 def pano_capture(self, goal):
00168 if self._capture_job is not None:
00169 raise Exception("cannot run multile capture jobs. TODO: pre-eempt existing job")
00170
00171 rospy.loginfo('%s: Pano Capture Goal: \n\tCamera [%s]'%(self._action_name, goal.camera_topic))
00172
00173 pano_id = int(rospy.get_time())
00174
00175 capturer = self._capture_interface()
00176 capturer.start(pano_id,goal)
00177
00178 self._snap_requested = False
00179 capture_job = self._capture_job = PanoCaptureJob(pano_id, capturer )
00180 camera_topic = goal.camera_topic or rospy.resolve_name('camera')
00181
00182 image_topic = rospy.names.ns_join(camera_topic, 'image_color')
00183 camera_info_topic = rospy.names.ns_join(camera_topic, 'camera_info')
00184
00185 rospy.loginfo('%s: Starting capture of pano_id %d.\n\tImage [%s]\n\tCamera Info[%s]'
00186 %(self._action_name, pano_id, image_topic, camera_info_topic) )
00187 grabber = ImageGrabber(image_topic, camera_info_topic, self.capture_fn)
00188
00189
00190 server = self._server
00191 preempted = False
00192
00193 rospy.loginfo('%s: Executing capture of pano_id %d' % (self._action_name, pano_id))
00194
00195 while capture_job.result is None and not preempted and not rospy.is_shutdown():
00196 if server.is_preempt_requested():
00197 rospy.loginfo('%s: Preempted' % self._action_name)
00198 server.set_preempted()
00199 capture_job.cancel()
00200 preempted = True
00201 else:
00202 rospy.sleep(0.001)
00203
00204 result = capture_job.result
00205 grabber.stop()
00206
00207 if result:
00208 rospy.loginfo('%s: Succeeded, [%s] images.\nResult: %s' % (self._action_name, result.n_captures, result))
00209 server.set_succeeded(result)
00210
00211 self._capture_job = None
00212
00213 def capture_fn(self, image, info):
00214 if self._capture_job is None:
00215 rospy.logerr("cannot capture photo while capture job is not active")
00216 return
00217
00218 if self._snap_requested:
00219 rospy.loginfo("snap!")
00220 self._snap_requested = False
00221 self._capture_job.capture_fn(image, info)
00222
00223 feedback = PanoCaptureFeedback()
00224 feedback.n_captures = self._capture_job.n_captures
00225 rospy.loginfo("captured %d images so far" % feedback.n_captures)
00226 self._server.publish_feedback(feedback)
00227
00228 def capture_snap(self, empty):
00229 if self._capture_job is None:
00230 rospy.logerr("cannot snap photo while capture job is not active")
00231 return
00232 self._snap_requested = True
00233
00234 def capture_stop(self, empty):
00235 if self._capture_job is None:
00236 rospy.logerr("no active capture job to stop")
00237 return
00238
00239 self._snap_requested = False
00240 self._capture_job.stop()
00241
00242
00243
00244 class BagCapture(CaptureInterface):
00245 """
00246 Bag capture is not thread-safe.
00247 """
00248 def start(self, pano_id, goal):
00249 """
00250 set up a new pano.
00251 """
00252 self.pano_id = pano_id
00253 if goal.bag_filename is None:
00254 self.bag_filename = os.path.abspath(os.path.join(tempfile.gettempdir(), "pano_%d.bag" % pano_id))
00255 else:
00256 self.bag_filename = goal.bag_filename
00257 self.bag = rosbag.Bag(self.bag_filename, 'w')
00258 self.n_captures = 0
00259 self.lock = threading.Lock()
00260
00261 def __call__(self, image, camera_info):
00262 with self.lock:
00263 if self.bag is not None:
00264 self.bag.write("image", image)
00265 self.bag.write("camera_info", camera_info)
00266 self.n_captures += 1
00267
00268 def stop(self):
00269 with self.lock:
00270 if self.bag is not None:
00271 self.bag.close()
00272 self.bag = None
00273 else:
00274 raise IOError("already closed")
00275
00276 def cancel(self):
00277 self.bag.close()
00278 self.bag = None
00279
00280 class StitchCapture(CaptureInterface):
00281 """
00282 Bag capture is not thread-safe.
00283 """
00284 def start(self, pano_id, goal):
00285 """
00286 set up a new pano.
00287 """
00288 self.bridge = CvBridge()
00289 self._bag_capture = BagCapture()
00290 self._bag_capture.start(pano_id, goal)
00291 self.img_pub = rospy.Publisher('~stitch',Image, queue_size=5)
00292
00293
00294 def __call__(self, image, camera_info):
00295 if self._bag_capture.n_captures < 1:
00296 self.setupCamera(camera_info)
00297 self.setupStitch()
00298 try:
00299 self.img_pub.publish(image)
00300 cv_image = self.bridge.imgmsg_to_cv2(image, "rgb8")
00301 pano_image_t = cv.fromarray(cv_image)
00302 pano_image = pcv.convertCvMat2Mat(pano_image_t)
00303 self.stitcher.addNewImage(pano_image)
00304 except CvBridgeError, e:
00305 print e
00306
00307 self._bag_capture(image,camera_info)
00308
00309 def setupStitch(self):
00310 options = pano.Options()
00311 options.camera = self.camera
00312 options.stitch_size = pcv.Size(4000,2000)
00313
00314 params = options.fitter_params
00315 params.error_thresh = 6;
00316 params.inliers_thresh = 15;
00317 params.maxiters = 100;
00318 params.nNeeded = 2;
00319 self.stitcher = pano.StitchEngine(options)
00320
00321 def _get_n_captures(self):
00322 return self._bag_capture.n_captures
00323 def _get_bag_filename(self):
00324 return self._bag_capture.bag_filename
00325
00326 n_captures = property(_get_n_captures)
00327 bag_filename = property(_get_bag_filename)
00328
00329 def setupCamera(self,camera_info):
00330 self.camera = pano.Camera()
00331 K = pcv.Mat(3,3,pcv.CV_32FC1)
00332 K.fromarray(camera_info.K)
00333 img_size = pcv.Size( camera_info.width, camera_info.height)
00334 self.camera.setCameraIntrinsics(K,pcv.Mat(),img_size)
00335
00336 def stitch_cb(self, x):
00337 print "progress ",x
00338 return 0
00339
00340 def stop(self):
00341
00342
00343
00344 if(self.stitcher):
00345 blended_t = cv.CreateMat(1500,3000,cv.CV_8UC3)
00346 blended_num = numpy.asarray(blended_t)
00347 blended = pcv.convertCvMat2Mat(blended_t)
00348 self.stitcher.stitch(blended, self.stitch_cb)
00349 img_msg = self.bridge.cv2_to_imgmsg(blended_num, "rgb8")
00350 self.img_pub.publish(img_msg)
00351 self._bag_capture.stop()
00352
00353 def cancel(self):
00354 self._bag_capture.cancel()
00355
00356 if __name__ == '__main__':
00357 rospy.init_node('pano_capture')
00358 PanoCaptureServer(rospy.get_name(), StitchCapture)
00359 rospy.spin()
00360