cluster_bounding_box_finder.py
Go to the documentation of this file.
00001 #!/usr/bin/python
00002 # Software License Agreement (BSD License)
00003 #
00004 # Copyright (c) 2009, Willow Garage, Inc.
00005 # All rights reserved.
00006 #
00007 # Redistribution and use in source and binary forms, with or without
00008 # modification, are permitted provided that the following conditions
00009 # are met:
00010 #
00011 #  * Redistributions of source code must retain the above copyright
00012 #    notice, this list of conditions and the following disclaimer.
00013 #  * Redistributions in binary form must reproduce the above
00014 #    copyright notice, this list of conditions and the following
00015 #    disclaimer in the documentation and/or other materials provided
00016 #    with the distribution.
00017 #  * Neither the name of the Willow Garage nor the names of its
00018 #    contributors may be used to endorse or promote products derived
00019 #    from this software without specific prior written permission.
00020 #
00021 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00022 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00023 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00024 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00025 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00026 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
00027 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00028 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00029 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00030 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
00031 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00032 # POSSIBILITY OF SUCH DAMAGE.
00033 #
00034 # author: Kaijen Hsiao
00035 
00036 #Use PCA to find the principal directions and bounding box for a cluster
00037 
00038 from __future__ import division
00039 import roslib
00040 import rospy
00041 import scipy
00042 import pdb
00043 import random
00044 import math
00045 import scipy.linalg
00046 from geometry_msgs.msg import PoseStamped, Point, Pose, Vector3
00047 from convert_functions import *
00048 import geometry_msgs
00049 
00050 ## class for using PCA to find the principal directions and bounding
00051 # box for a point cluster
00052 class ClusterBoundingBoxFinder:
00053 
00054     def __init__(self, tf_listener, tf_broadcaster, base_frame=""):
00055         assert(tf_listener)
00056         assert(tf_broadcaster)
00057         self.tf_listener = tf_listener
00058         self.tf_broadcaster = tf_broadcaster
00059         self.base_frame = base_frame
00060         #get the name of the frame to use with z-axis being "up" or "normal to surface"
00061         #(the cluster will be transformed to this frame, and the resulting box z will be this frame's z)
00062         if self.base_frame == "":
00063           self.base_frame = rospy.get_param("~z_up_frame", "")
00064 
00065     ##run eigenvector PCA for a 2xn scipy matrix, return the directions
00066     #(list of 2x1 scipy arrays)
00067     def pca(self, points):
00068         cov_mat = points[0:3,:] * points[0:3,:].T/scipy.shape(points)[1]
00069         (values, vectors) = scipy.linalg.eig(cov_mat)
00070         if values[1] >  values[0]:
00071             directions = [vectors[:,1], vectors[:,0]]
00072         else:
00073             directions = [vectors[:,0], vectors[:,1]]
00074 
00075         return directions
00076 
00077 
00078     ##shift the points to be around the mean for x and y (matrix is 4xn)
00079     def mean_shift_xy(self, points):
00080         mean = [0]*2
00081         shifted_points = points.copy()
00082         for i in range(2):
00083             mean[i] = scipy.mean(points[i,:])
00084             shifted_points[i,:] -= mean[i]
00085         return (shifted_points, mean)
00086 
00087 
00088     ##remove outliers from the cluster (4xn scipy matrix)
00089     def remove_outliers(self, points):
00090 
00091         points_filtered = points[:]
00092         empty_space_width = .02
00093         check_percent = .02
00094         edge_width = .005
00095 
00096         #remove outliers in each dimension (x,y,z)
00097         for dim in range(3):
00098 
00099             #sort the points by their values in that dimension
00100             num_points = scipy.shape(points_filtered)[1]
00101             points_sorted = points_filtered[:, points_filtered[dim, :].argsort().tolist()[0]]
00102 
00103             #chop off the top points if they are more than empty_space_width away from the bounding box edge
00104             ind = int(math.floor(num_points*(1-check_percent)))
00105             if points_sorted[dim, -1] - points_sorted[dim, ind] > empty_space_width:
00106 
00107                 #find the first point that isn't within edge_width of the point at ind, and lop off all points after
00108                 searcharr = scipy.array(points_sorted[dim, :]).flatten()
00109                 searchval = points_sorted[dim, ind] + edge_width
00110                 thres_ind = scipy.searchsorted(searcharr, searchval)
00111                 if thres_ind != 0 and thres_ind != len(searcharr):
00112                     points_filtered = points_sorted[:, 0:thres_ind]
00113                     rospy.loginfo("chopped points off of dim %d, highest val = %5.3f, searchval = %5.3f"%(dim, points_sorted[dim, -1], searchval))
00114             else:
00115                 points_filtered = points_sorted
00116 
00117             #do both sides for x and y
00118             if dim != 2:
00119                 ind = int(math.floor(num_points*check_percent))
00120                 if points_filtered[dim, ind] - points_filtered[dim, 0] > empty_space_width:
00121 
00122                     #find the first point that isn't within edge_width of the point at ind, and lop off all points before
00123                     searcharr = scipy.array(points_sorted[dim, :]).flatten()
00124                     searchval = points_sorted[dim, ind] - edge_width
00125                     thres_ind = scipy.searchsorted(searcharr, searchval)
00126                     if thres_ind != 0 and thres_ind != len(searcharr):
00127                         points_filtered = points_filtered[:, thres_ind:-1]
00128                         rospy.loginfo("chopped points off of dim -%d, lowest val = %5.3f, searchval = %5.3f"%(dim, points_sorted[dim, 0], searchval))
00129         return points_filtered
00130 
00131 
00132     ##find the object frame and bounding box for a PointCloud or PointCloud2
00133     #use the local rosparam z_up_frame to specify the desired frame to use where the z-axis is special (box z will be frame z)
00134     #if not specified, the point cloud's frame is assumed to be the desired z_up_frame
00135     def find_object_frame_and_bounding_box(self, point_cloud):
00136 
00137         #if param is not set, assumes the point cloud's frame is such
00138         # if it was given in as init arg, use that
00139         if self.base_frame == "":
00140           self.base_frame = rospy.get_param("~z_up_frame", point_cloud.header.frame_id)
00141 
00142         #convert from PointCloud to 4xn scipy matrix in the base_frame
00143         cluster_frame = point_cloud.header.frame_id
00144 
00145         (points, cluster_to_base_frame) = transform_point_cloud(self.tf_listener, point_cloud, self.base_frame)
00146         if points == None:
00147             return (None, None, None)
00148         #print "cluster_to_base_frame:\n", ppmat(cluster_to_base_frame)
00149 
00150         #find the lowest point in the cluster to use as the 'table height'
00151         table_height = points[2,:].min()
00152 
00153         #run PCA on the x-y dimensions to find the tabletop orientation of the cluster
00154         (shifted_points, xy_mean) = self.mean_shift_xy(points)
00155         directions = self.pca(shifted_points[0:2, :])
00156 
00157         #convert the points to object frame:
00158         #rotate all the points about z so that the shortest direction is parallel to the y-axis (long side of object is parallel to x-axis)
00159         #and translate them so that the table height is z=0 (x and y are already centered around the object mean)
00160         y_axis = scipy.mat([directions[1][0], directions[1][1], 0.])
00161         z_axis = scipy.mat([0.,0.,1.])
00162         x_axis = scipy.cross(y_axis, z_axis)
00163         rotmat = scipy.matrix(scipy.identity(4))
00164         rotmat[0:3, 0] = x_axis.T
00165         rotmat[0:3, 1] = y_axis.T
00166         rotmat[0:3, 2] = z_axis.T
00167         rotmat[2, 3] = table_height
00168         object_points = rotmat**-1 * shifted_points
00169 
00170         #remove outliers from the cluster
00171         object_points = self.remove_outliers(object_points)
00172 
00173         #find the object bounding box in the new object frame as [[xmin, ymin, zmin], [xmax, ymax, zmax]] (coordinates of opposite corners)
00174         object_bounding_box = [[0]*3 for i in range(2)]
00175         object_bounding_box_dims = [0]*3
00176         for dim in range(3):
00177             object_bounding_box[0][dim] = object_points[dim,:].min()
00178             object_bounding_box[1][dim] = object_points[dim,:].max()
00179             object_bounding_box_dims[dim] = object_bounding_box[1][dim] - object_bounding_box[0][dim]
00180 
00181         #now shift the object frame and bounding box so that the z-axis is centered at the middle of the bounding box
00182         x_offset = object_bounding_box[1][0] - object_bounding_box_dims[0]/2.
00183         y_offset = object_bounding_box[1][1] - object_bounding_box_dims[1]/2.
00184         for i in range(2):
00185             object_bounding_box[i][0] -= x_offset
00186             object_bounding_box[i][1] -= y_offset
00187         object_points[0, :] -= x_offset
00188         object_points[1, :] -= y_offset
00189         offset_mat = scipy.mat(scipy.identity(4))
00190         offset_mat[0,3] = x_offset
00191         offset_mat[1,3] = y_offset
00192         rotmat = rotmat * offset_mat
00193         #pdb.set_trace()
00194 
00195         #record the transforms from object frame to base frame and to the original cluster frame,
00196         #broadcast the object frame to tf, and draw the object frame in rviz
00197         unshift_mean = scipy.identity(4)
00198         unshift_mean[0,3] = xy_mean[0]
00199         unshift_mean[1,3] = xy_mean[1]
00200         object_to_base_frame = unshift_mean*rotmat
00201         object_to_cluster_frame = cluster_to_base_frame**-1 * object_to_base_frame
00202 
00203         #broadcast the object frame to tf
00204         (object_frame_pos, object_frame_quat) = mat_to_pos_and_quat(object_to_cluster_frame)
00205         #self.tf_broadcaster.sendTransform(object_frame_pos, object_frame_quat, rospy.Time.now(), "object_frame", cluster_frame)
00206 
00207         object_pose = PoseStamped()
00208         object_pose.pose.position.x = object_frame_pos[0]
00209         object_pose.pose.position.y = object_frame_pos[1]
00210         object_pose.pose.position.z = object_frame_pos[2]
00211         object_pose.pose.orientation.x = object_frame_quat[0]
00212         object_pose.pose.orientation.y = object_frame_quat[1]
00213         object_pose.pose.orientation.z = object_frame_quat[2]
00214         object_pose.pose.orientation.w = object_frame_quat[3]
00215         object_pose.header.frame_id = cluster_frame
00216 
00217         return (object_points, object_bounding_box_dims, object_bounding_box, object_pose)
00218 


object_recognition_clusters
Author(s): Bence Magyar , Kaijen Hsiao
autogenerated on Wed Aug 26 2015 14:58:56