acquire_pr2_data.py
Go to the documentation of this file.
00001 #!/usr/bin/python
00002 #
00003 # Copyright (c) 2010, Georgia Tech Research Corporation
00004 # All rights reserved.
00005 #
00006 # Redistribution and use in source and binary forms, with or without
00007 # modification, are permitted provided that the following conditions are met:
00008 #     * Redistributions of source code must retain the above copyright
00009 #       notice, this list of conditions and the following disclaimer.
00010 #     * Redistributions in binary form must reproduce the above copyright
00011 #       notice, this list of conditions and the following disclaimer in the
00012 #       documentation and/or other materials provided with the distribution.
00013 #     * Neither the name of the Georgia Tech Research Corporation nor the
00014 #       names of its contributors may be used to endorse or promote products
00015 #       derived from this software without specific prior written permission.
00016 #
00017 # THIS SOFTWARE IS PROVIDED BY GEORGIA TECH RESEARCH CORPORATION ''AS IS'' AND
00018 # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
00019 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
00020 # DISCLAIMED. IN NO EVENT SHALL GEORGIA TECH BE LIABLE FOR ANY DIRECT, INDIRECT,
00021 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
00022 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
00023 # OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
00024 # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
00025 # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
00026 # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
00027 #
00028 
00029 #  \author Jason Okerman (Healthcare Robotics Lab, Georgia Tech.)
00030 
00031 
00032 """
00033 This program imports sensor data from the PR2.
00034 By default it will look for a cloud (/table_cloud), an image on wide_stereo
00035 left, and the necessary transformations, and camera_info. 
00036 It also can map the laser points into the image frame, and color the cloud with the projected camera image.
00037 
00038 This function runs well stand-alone, or can be imported and its functions called separately.
00039 
00040 Python implementation by: Jason Okerman
00041 """
00042 import roslib; roslib.load_manifest('pr2_clutter_helper')
00043 
00044 #import sys
00045 #import os
00046 #from optparse import OptionParser
00047 
00048 import rospy
00049 from sensor_msgs.msg import Image, PointCloud, CameraInfo, ChannelFloat32
00050 from geometry_msgs.msg import PointStamped,  Point32
00051 from std_msgs.msg import Bool
00052 from image_geometry.cameramodels import PinholeCameraModel
00053 from cv_bridge import CvBridge
00054 import message_filters
00055 import tf
00056 
00057 import cv as roscv #CV 2.1 from ROS 
00058 from random import random, randint
00059 import numpy as np
00060 
00061 class AcquireCloud:
00062     def __init__(self, return_empty=False):
00063         self.bool_pub = rospy.Publisher("table_bool", Bool)
00064         self.cloud_pub = rospy.Publisher("table_cloud_colored", PointCloud)
00065         self.img_pub = rospy.Publisher("table_image/color_rect_image", Image)
00066         self.cam_pub = rospy.Publisher("table_image/camera_info", CameraInfo)
00067         self.img_msg = None
00068         self.cam_msg = None
00069         self.cloud_msg = None
00070         self.tf_matrix = None
00071         self.tf_translation = None
00072         self.tf_quaternion = None
00073         self.camPts = []
00074         self.pts3d = None
00075         self.intensities = []
00076         self.camPts_idx = []  #Formerly 'idx_list'
00077         self.img = np.array([])
00078         self.N = 0 #number of 3d points.
00079         #Optional self.sub1, sub2, sub3 --> created by subscribe() function.
00080         
00081         self.tfListener = tf.TransformListener()
00082         self.fixed_frame = '/base_footprint'  #must match header on cloud
00083         self.target_frame = '/wide_stereo_optical_frame'
00084                            #'/wide_stereo_gazebo_r_stereo_camera_optical_frame'
00085         self.cam_topic = "/wide_stereo/left/camera_info"
00086         self.img_topic = "/wide_stereo/left/image_rect_color"
00087         self.cloud_topic = "/table_cloud"
00088         self.cvBridge = CvBridge()
00089         if not return_empty: #subscribe imediately - blocking function
00090             self.subscribe()
00091             self.wait_for_data()
00092             self.unsubscribe()
00093             self.get_tf()
00094             self.print_test() ## 
00095             self.pts3d, self.intensities = self.get_pts3d(self.cloud_msg)
00096             self.cam_model = self.get_cam_model(self.cam_msg)
00097             self.camPts, self.camPts_idx, self.colors, self.img_dotted = self.get_camPts()
00098             roscv.SaveImage('/home/jokerman/Desktop/most_recent_img_dotted.png', self.img_dotted)
00099             #show_image(self.img_dotted)
00100             
00101             
00102     ''' Not Internal. Passed a cloud_msg
00103         Does more than title: Sets interanal pts3d and also intensities
00104         If intensities is not part of cloud_msg, then intensities will be empty array.
00105     '''        
00106     def get_pts3d(self, cloud_msg):
00107         N = len(cloud_msg.points)
00108         pts3d = np.zeros([3,N])
00109         for i in range(N):
00110             row = cloud_msg.points[i]
00111             pts3d[:,i] = (row.x, row.y, row.z);
00112         intensities = np.zeros(N)            
00113         for ch in cloud_msg.channels:
00114             if ch.name == 'intensities':
00115                 for i in range(N):
00116                     intensities[i]=ch.values[i]
00117             break;        
00118         return np.matrix(pts3d), intensities    
00119     
00120     def pub_cloud(self):
00121         publish_cloud(self.cloud_pub, self.fixed_frame, self.pts3d, intensities=self.intensities, colors=self.colors)    
00122     
00123     def pub_cloud_bound(self):
00124         crop = self.camPts_idx
00125         publish_cloud(self.cloud_pub, self.fixed_frame, self.pts3d[:,crop], intensities=self.intensities[crop], colors=self.colors[:,crop])  
00126     
00127     def pub_cam(self, pub=None, frame=None, cam_msg_template=None):
00128         if not pub: pub = self.cam_pub
00129         #Stuff a new camera message object
00130         if not cam_msg_template: c = self.cam_msg
00131         else: c = cam_msg_template
00132         msg = CameraInfo(D=c.D,K=c.K,R=c.R,P=c.P,height=c.height,width=c.width)
00133         if not frame: frame=self.target_frame
00134         msg.header.frame_id = frame;        
00135         msg.header.stamp=rospy.Time.now()
00136         pub.publish(msg)
00137         
00138     def pub_img(self, pub=None, frame=None, image_template=None):
00139         if not pub: pub=self.img_pub
00140         #Stuff a new camera message object
00141         if not image_template: a = self.img_msg
00142         else: a = image_template
00143         msg = Image(height=a.height,width=a.width,encoding=a.encoding,data=a.data,is_bigendian=a.is_bigendian)
00144         if not frame: frame=self.target_frame
00145         msg.header.frame_id = frame;        
00146         msg.header.stamp=rospy.Time.now()
00147         pub.publish(msg)
00148     
00149     def get_cam_model(self, cam_msg):
00150         ''' Not internal.  Uses: camera_info message
00151         '''
00152         cam_model = PinholeCameraModel()
00153         cam_model.fromCameraInfo(cam_msg)
00154         return cam_model
00155         
00156     def print_test(self):
00157         print '---'
00158         print '* tf_matrix =\n', self.tf_matrix
00159         if self.img_msg:
00160             print '* img_msg has size', self.img_msg.width,'x',self.img_msg.height, '\n'
00161         if self.cam_msg:
00162             print '* cam_msg has K = \n', np.round(self.cam_msg.K)
00163         if self.cloud_msg:
00164             print '* cloud_msg has size', len(self.cloud_msg.points)
00165             print '* Cloud frame id', self.cloud_msg.header.frame_id
00166             print '* cloud_msg has channels:',
00167             for ch in self.cloud_msg.channels: print "'"+ch.name+"'",
00168         print '\n---'
00169         
00170     def wait_for_data(self):
00171         print 'wait_for_data'
00172         while not rospy.is_shutdown() and not (self.cam_msg and self.img_msg and self.cloud_msg):
00173             rospy.sleep(.5)
00174             print '\nWaiting for:',
00175             if not self.cam_msg: print 'cam_msg,',
00176             if not self.img_msg: print 'img_msg,',
00177             if not self.cloud_msg: print 'cloud_msg',
00178             
00179     ''' Internal.  Sets self.sub1, sub2, sub3 to specific callbacks.
00180     '''
00181     def subscribe(self):
00182         self.sub1 = rospy.Subscriber(self.img_topic, Image, self.image_callback )
00183         self.sub2 = rospy.Subscriber(self.cloud_topic, PointCloud , self.cloud_callback )
00184         self.sub3 = rospy.Subscriber(self.cam_topic, CameraInfo, self.camera_callback )
00185 
00186     def unsubscribe(self):
00187         self.sub1.unregister()
00188         self.sub2.unregister()
00189         self.sub3.unregister()
00190         print 'unregistering self'
00191 
00192     def image_callback(self, img_msg):
00193         if not self.img_msg: self.img_msg = img_msg; print '\n* img'
00194         else: print '..',
00195     
00196     def camera_callback(self, cam_msg):
00197         if not self.cam_msg: self.cam_msg = cam_msg; print '\n* camera'
00198         else: print '.',
00199           
00200     def cloud_callback(self, cloud_msg):
00201         if not self.cloud_msg: self.cloud_msg = cloud_msg;  print '\n* cloud'
00202         else: print '...',
00203 
00204     def get_tf(self):
00205         self.h = rospy.Header(frame_id=self.fixed_frame)  
00206         self.tf_matrix = self.tfListener.asMatrix(self.target_frame, self.h)
00207         # Alternate form
00208         self.tf_translation = tf.transformations.translation_from_matrix(self.tf_matrix)
00209         self.tf_quaternion = tf.transformations.quaternion_from_matrix(self.tf_matrix)
00210 
00211     def callback(self, img_msg, cam_msg=None, cloud_msg=None):
00212         print 'callback starting'
00213 
00214     def test_publish(self):
00215         self.bool_pub.publish(bool(randint(0,1)) )
00216         print 'publish complete'
00217         
00218     
00219     def return_data_for_segmentation(self):
00220         '''Returns: img, --> Numpy image #Formerly ROS CV 2.1 type image
00221                     pts3d, --> 3xN numpy array of 3D points
00222                     intensities, --> N length numpy array
00223                     camPts, --> 2xN numpy array of camera points.  
00224                         Points are projection from 3D. May be outside frame of camera (need camPts_idx).
00225                     colors, --> 3xN numpy array (0,1,2) = (r,g,b)
00226                     camPts_idx (Formerly idx_list) --> boolean array of size N for cropping
00227         '''
00228         return self.img, self.pts3d, self.intensities, self.camPts, self.colors, self.camPts_idx
00229 
00230   
00231     ''' Internal. Uses: cloud , img_msg, camera_model, bounds=None:
00232         Does a lot more: camPts_idx -> boolean array, true if 3D point false in camera view
00233         self.img -> from img_msg (numpy array)
00234         img_dotted -> for calibration check           
00235         
00236         *** NOTE, in absence of working "transfromPointCloud" function, this only works if 
00237         cloud is passed already in optical camera frame.  :(
00238     '''
00239     def get_camPts_already_in_cam_frame(self):
00240         self.img = self.cvBridge.imgmsg_to_cv(self.img_msg)
00241         img_dotted = np.array(self.img) # = roscv.CloneImage(np.array(img)) doesn't 
00242                                       # work between versions cv 2.0 and 2.1
00243         height = self.img_msg.height; width = self.img_msg.width;
00244         N = len(self.cloud_msg.points)
00245         colors = np.zeros( (3,N) )
00246         camPts = np.zeros( (2,N) )
00247         camPts_idx = np.array([False for i in range(N)]) #list of boolean False's
00248         #billybob = tf.TransformerROS()
00249         #cloud_msg_cam_frame = billybob.transformPointCloud('base_footprint', self.cloud_msg)
00250         i=0
00251         for row in self.cloud_msg.points:
00252             pt3d = (row.x, row.y, row.z);
00253             uv = self.cam_model.project3dToPixel(pt3d)
00254             camPts[:,i] = uv
00255             x = uv[0]; y = uv[1];
00256             if (x >=0 and y >=0 and x<width and y <height):
00257                 roscv.Circle(img_dotted, uv, 0, (255,100,100) )
00258                 row = int(y); col = int(x);
00259                 r,g,b = (0,1,2)
00260                 color = roscv.Get2D(self.img, row, col)
00261                 colors[r,i] = color[r]
00262                 colors[g,i] = color[g]
00263                 colors[b,i] = color[b]
00264                 camPts_idx[i] = True
00265             #else: print 'point ',uv,'out of bounds'
00266             i+=1;
00267         return camPts, camPts_idx, colors, img_dotted 
00268   
00269     ''' Internal. Uses: cloud , img_msg, camera_model, tf_matrix, bounds=None:
00270         Does a lot more: camPts_idx -> boolean array, true if 3D point false in camera view
00271                          self.img -> from img_msg (numpy array)
00272                          img_dotted -> for calibration check        
00273     '''
00274     def get_camPts(self):
00275         self.img = self.cvBridge.imgmsg_to_cv(self.img_msg)
00276         img_dotted = np.array(self.img) # = roscv.CloneImage(np.array(img)) doesn't 
00277                                         # work between versions cv 2.0 and 2.1
00278         height = self.img_msg.height; width = self.img_msg.width;
00279         N = len(self.cloud_msg.points)
00280         #-------------
00281         camPts = np.zeros( (2,N) )
00282         pts3d_camframe = self.get_pts3d_camframe()
00283         for i in range(N): 
00284             pt3d = pts3d_camframe[0,i], pts3d_camframe[1,i], pts3d_camframe[2,i] ##
00285             uv = self.cam_model.project3dToPixel(pt3d)
00286             camPts[:,i] = uv #2D point in camera frame
00287         #----------
00288         colors = np.zeros( (3,N) )
00289         camPts_idx = np.array([False for i in range(N)]) #list of boolean False's
00290         for i in range(N):   ##
00291             x = int(camPts[0,i]); y = int(camPts[1,i]);
00292             if (x >=0 and y >=0 and x<width and y <height):
00293                 row = int(y); col = int(x);
00294                 r,g,b = (0,1,2)
00295                 color = roscv.Get2D(self.img, row, col)
00296                 colors[r,i] = color[r]
00297                 colors[g,i] = color[g]
00298                 colors[b,i] = color[b]
00299                 camPts_idx[i] = True
00300                 roscv.Circle(img_dotted, (x,y), 0, (50,250,50) )
00301                 
00302         return camPts, camPts_idx, colors, img_dotted 
00303         
00304   
00305     ''' Internal. Uses: tf_matrix, cloud_msg, img_msg, bridge,
00306             Notes: camTlaser is now called tf_matrix
00307             #focus_matrix is like "K" but with cx and cy offset removed.
00308     '''
00309     def get_camPts_by_matrix(self):
00310         self.pts3d,_ = self.get_pts3d(self.cloud_msg)
00311         self.get_tf()
00312         K = np.matrix(self.cam_msg.K).reshape(3,3)
00313         cam_centers = ( K[0,2], K[1,2] ) #cx and cy
00314         # Keep fx, fy, but clear cx and cy.  Make homogeneous.
00315         (fx, fy) = K[0,0], K[1,1]
00316         focus_matrix =  np.matrix([[fx, 0, 0,   0],[0, fy, 0,   0],[0,  0, 1,   0]])
00317         #--------
00318         pts3d_camframe = apply_transform_matrix( self.tf_matrix, self.pts3d)
00319         camPts = focus_matrix* xyzToHomogenous(pts3d_camframe)
00320         (u,v,w) = (0,1,2)
00321         camPts[u] = camPts[u] / camPts[w] + cam_centers[u] #shift over
00322         camPts[v] = camPts[v] / camPts[w] + cam_centers[v] #shift over
00323         camPts = np.matrix( np.round(camPts[:2]), 'int')
00324         #As last step, ensure that camPts is 2xN and not 3xN, also round
00325         #--------
00326         self.img = self.cvBridge.imgmsg_to_cv(self.img_msg)
00327         img_dotted = np.array(self.img)
00328         height = self.img_msg.height; width = self.img_msg.width;
00329         N = len(self.cloud_msg.points)
00330         colors = np.zeros( (3,N) )
00331         camPts_idx = np.array([False for i in range(N)]) #list of boolean False's
00332         for i in range(N):   ##
00333             x = camPts[0,i]; y = camPts[1,i];
00334             if (x >=0 and y >=0 and x < width and y < height):
00335                 roscv.Circle(img_dotted, (x,y), 0, (0,100,255) )
00336                 row = int(y); col = int(x);
00337                 r,g,b = (0,1,2)
00338                 color = roscv.Get2D(self.img, row, col)
00339                 colors[r,i] = color[r]
00340                 colors[g,i] = color[g]
00341                 colors[b,i] = color[b]
00342                 camPts_idx[i] = True
00343         return camPts, camPts_idx, colors, img_dotted 
00344    
00345     def get_pts3d_camframe(self):
00346         if self.tf_matrix == None: self.get_tf()
00347         if self.pts3d == None: self.pts3d, self.intensities = self.get_pts3d(self.cloud_msg)
00348         #If having problems try inverted matrix: np.linalg.inv(self.tf_matrix)
00349         pts3d_camframe = apply_transform_matrix( self.tf_matrix, self.pts3d )
00350         return pts3d_camframe
00351    
00352    
00353    
00354    
00355    
00356 #---------------------      
00357 
00358 '''This appears multiply a Transform matrix by a 3xN-element point or point set.
00359    Note that xyzToHomogenous is made to convert 3XN matrix, to 4XN matrix in homog coords
00360 '''
00361 def apply_transform_matrix(T, p):
00362     pn = T * xyzToHomogenous(p)
00363     return pn[0:3] / pn[3]
00364     
00365 """This is redundantly defined in hrl_lib.transforms.py, as part of gt-ros-pkg
00366    convert 3XN matrix, to 4XN matrix in homogenous coords
00367 """
00368 def xyzToHomogenous(v, floating_vector=False):
00369     if floating_vector == False:
00370         return np.row_stack((v, np.ones(v.shape[1])))
00371     else:
00372         return np.row_stack((v, np.zeros(v.shape[1])))
00373         
00374 def publish_cloud(pub, frame, pts3d, intensities=None, labels=None, colors=None):
00375     cloud = convert_pointcloud_to_ROS(pts3d, intensities, labels, colors)
00376     if not frame: frame='/base_footprint'
00377     cloud.header.frame_id = frame
00378     cloud.header.stamp=rospy.Time.now()
00379     pub.publish(cloud)
00380 
00381 def filter_example():  #THIS COULD WORK, BUT DOESN'T BECAUSE TIMESTAMPS DO NOT MATCH
00382     rospy.init_node('rosSegmentCloud')
00383     pos_pub = rospy.Publisher("face_position", PointStamped)
00384     print 'Filter the image topic and point cloud topic to come in together'
00385     image_sub = message_filters.Subscriber("/wide_stereo/left/image_rect_color", Image)
00386     cloud_sub = message_filters.Subscriber("/tilt_scan_cloud", PointCloud )
00387     camera_sub = message_filters.Subscriber("/wide_stereo/left/camera_info", CameraInfo)
00388 
00389     ts = message_filters.TimeSynchronizer([image_sub, camera_sub], 10)
00390     ts.registerCallback(cs.callback)
00391     
00392 def show_image(img, window_name='img', wait=False):
00393     roscv.StartWindowThread(); roscv.NamedWindow(window_name)
00394     roscv.ShowImage(window_name, img)
00395     if wait: roscv.WaitKey()
00396     
00397 def save_cloud_as_py(cloud, path = './saved_cloud.py'):
00398     f = open(path,'w')
00399     f.write('#number of points = %i \n' %len(cloud.points) )
00400     f.write('points = [')
00401     for row in cloud.points:
00402         f.write('\n    [%.5f, %.5f, %.5f ],' %(row.x, row.y, row.z) )
00403     f.write(']')
00404     #Optionally print intensity array also.
00405     for channel in cloud.channels:
00406         if channel.name == "intensities":
00407             f.write('\n\nintensities = [')
00408             for row in channel.values:
00409                 f.write('%i, ' %int(row) )
00410             f.write(']\n\n') 
00411             break;
00412     f.close()
00413      
00414 ''' Expects pts3D and colors to be 3xN, intensities and labels to be N.
00415 '''
00416 def convert_pointcloud_to_ROS(pts3d, intensities = None, labels = None, colors=None, bgr=True):
00417     ROS_pointcloud = PointCloud()
00418     ROS_pointcloud.points = []
00419     ROS_pointcloud.channels = []
00420     (x,y,z) = (0,1,2)
00421     for pt in np.asarray(pts3d.T):
00422         ROS_pointcloud.points += [Point32(pt[x], pt[y], pt[z])]
00423         
00424     intensity_channel = ChannelFloat32(name = 'intensities')
00425     if intensities != None:
00426         for value in intensities:
00427             intensity_channel.values += [value]
00428         ROS_pointcloud.channels += [intensity_channel]
00429         
00430     label_channel = ChannelFloat32(name = 'labels')   
00431     if labels != None:
00432         for value in labels:
00433             label_channel.values += [value]  
00434         ROS_pointcloud.channels += [label_channel] 
00435             
00436     if colors != None:    
00437         r_ch = ChannelFloat32(name='r')   
00438         g_ch = ChannelFloat32(name='g')   
00439         b_ch = ChannelFloat32(name='b')   
00440         rgb_ch = ChannelFloat32(name='rgb')
00441         for (r,g,b) in np.asarray(colors.T):
00442             #NOTE: CV image is in bgr order.  Ros expects rgb order
00443             r_ch.values += [b/256]   
00444             g_ch.values += [g/256]  
00445             b_ch.values += [r/256]    
00446             rgb_color = int(b*0xFF*0xFF+ g*0xFF+r) #incorrect
00447             rgb_ch.values += [rgb_color]
00448         ROS_pointcloud.channels += [r_ch, g_ch, b_ch]
00449     return ROS_pointcloud
00450     
00451     #---------------        
00452     
00453 def main():
00454     print 'Get Bridge'
00455     br = CvBridge()
00456 
00457     print 'Setup the ros node'
00458     rospy.init_node('rosSegmentCloud')
00459 
00460     print 'Setup the Acquire Object'
00461     ac = AcquireCloud()
00462     
00463     ac.print_test()
00464     
00465     show_image(ac.img, 'img')
00466     show_image(ac.img_dotted, 'dotted')
00467     ###tf_br = tf.TransformBroadcaster()
00468     
00469     print 'publish test'
00470     while not rospy.is_shutdown():
00471         now = rospy.Time.now()
00472         ###tf_br.sendTransform(ac.tf_translation, ac.tf_quaternion, now, "table_camera", ac.fixed_frame)
00473         ### tf == sadness :(
00474         ac.pub_cloud_bound()
00475         
00476         im_msg = br.cv_to_imgmsg(ac.img_dotted)
00477         im_msg.header.frame_id = ac.target_frame ###"table_camera" 
00478         
00479         im_msg.header.stamp = now
00480         ac.img_pub.publish(im_msg)
00481         cam_msg = ac.cam_msg
00482         cam_msg.header = rospy.Header(frame_id=ac.target_frame, stamp=now)
00483         ###cam_msg.header = rospy.Header(frame_id="table_camera", stamp=now)
00484         ac.cam_pub.publish(cam_msg)
00485         
00486                
00487         ac.test_publish()
00488         print 'pub'
00489         rospy.sleep(1)
00490 
00491 if __name__ == '__main__':
00492     main()   
00493     
00494 
00495     


pr2_clutter_helper
Author(s): Jason Okerman, Advisors: Prof. Charlie Kemp and Jim Regh, Lab: Healthcare Robotics Lab at Georgia Tech
autogenerated on Wed Nov 27 2013 11:53:06