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
00037 import scipy as sp
00038 import numpy as np
00039 import numpy.linalg as lin
00040 import semanticmodel.msg as sm
00041 import geometry_msgs.msg as gm
00042 import visualization_msgs.msg as vm
00043 from rospy import loginfo, logdebug
00044 import copy
00045 from std_msgs.msg import ColorRGBA
00046 from random import random
00047 import convexhull as ch
00048 import logging
00049
00050 LOG=logging.getLogger('world_model.planes')
00051
00052 DEFAULT_MAX_DISP=0.2
00053 COLORS = []
00054
00055 class BoundedPlane:
00056 "Represents a polygonal subset of a plane in 3-space"
00057
00058 def __init__(self, p):
00059 "Init from a Plane message"
00060
00061 self.header = p.header
00062
00063 workaround_incorrect_planes(p)
00064
00065
00066 self.n, self.d = normalize_eq(p)
00067
00068
00069 assert(len(p.polygon)>2)
00070 p0 = p.polygon[0]
00071 p1 = p.polygon[1]
00072 p0 = np.array([p0.x, p0.y, p0.z])
00073 p1 = np.array([p1.x, p1.y, p1.z])
00074 v1 = normalize(p1-p0)
00075 v2 = normalize(np.cross(self.n, v1))
00076 h1 = np.hstack([v1,0])
00077 h2 = np.hstack([v2,0])
00078 h3 = np.hstack([self.n,0])
00079 h4 = np.hstack([p0,1])
00080
00081
00082
00083 self.p2w = np.vstack([h1,h2,h3,h4]).T
00084
00085
00086 self.w2p = lin.inv(self.p2w)
00087
00088
00089 self.polygon = [mult(self.w2p, p)[0:2] for p in p.polygon]
00090
00091
00092 def __repr__(self):
00093 return "n=[{0}, {1}, {2}], d={3}".\
00094 format(self.n[0], self.n[1], self.n[2], self.d)
00095
00096
00097 def intersect(p1, p2, max_disp=DEFAULT_MAX_DISP):
00098 """
00099 Checks if:
00100 1) the two sets lie on the same plane (approximately)
00101 2) the polygons intersect
00102 """
00103
00104 trans = np.dot(p1.w2p, p2.p2w)
00105 poly2 = [trans2d(trans, p)[0:3] for p in p2.polygon]
00106
00107
00108 for i in range(len(poly2)):
00109 if abs(poly2[i][2])>max_disp:
00110 return False
00111 else:
00112 poly2 = [p[0:2] for p in poly2]
00113 res = polygons_intersect(p1.polygon, poly2)
00114 return res
00115
00116 def merge_planes(planes, p, max_disp=DEFAULT_MAX_DISP):
00117 """
00118 P must intersect at least one plane. Combine it with that plane, then
00119 process any additional intersections that result.
00120 """
00121 planes = planes.copy()
00122 int_planes = [id for id, existing in planes.items()
00123 if intersect(p, existing, max_disp)]
00124 assert int_planes
00125 i = int_planes[0]
00126 LOG.debug("Merging new plane with {0}".format(i))
00127 planes[i] = combine(planes[i], p)
00128
00129 merged = True
00130 while merged:
00131 merged = False
00132 LOG.debug("Checking for additional intersections")
00133 for j, pl in planes.items():
00134 LOG.debug("Checking {0}".format(j))
00135 if j != i:
00136 if intersect(planes[i], planes[j], max_disp):
00137 planes[i] = combine(planes[i], planes[j])
00138 del planes[j]
00139 merged = True
00140 LOG.debug("Merged with {0}".format(j))
00141 break
00142 return planes
00143
00144
00145 def combine(p1, p2):
00146 """
00147 Combines the two bounded planes to form a new one
00148 """
00149 combined = copy.deepcopy(p1)
00150 poly = [to_tuple(p) for p in p1.polygon]
00151 trans = np.dot(p1.w2p, p2.p2w)
00152 for p in p2.polygon:
00153 poly.append(to_tuple(trans2d(trans, p)))
00154 hull = ch.convex_hull(poly)
00155 combined.polygon = map(np.array, hull)
00156
00157
00158
00159
00160 return combined
00161
00162 def to_plane_msg(bp):
00163 "Turn a BoundedPlane object into a Plane message"
00164 p = sm.Plane()
00165 p.header = bp.header
00166 p.a, p.b, p.c = bp.n
00167 p.d = bp.d
00168 p.polygon = [trans_to_pt(bp.p2w, pt) for pt in bp.polygon]
00169 sx = sum((pt.x for pt in p.polygon))
00170 sy = sum((pt.y for pt in p.polygon))
00171 sz = sum((pt.z for pt in p.polygon))
00172 np = len(p.polygon)
00173 p.center.x = sx/np
00174 p.center.y = sy/np
00175 p.center.z = sz/np
00176 return p
00177
00178 def to_marker(planes, timestamp=None):
00179 "Convert list of bounded planes to visualization markers"
00180 assert(len(planes)>0)
00181 marker = vm.Marker()
00182 marker.header.frame_id = planes[0].header.frame_id
00183 if timestamp:
00184 marker.header.stamp = timestamp
00185 marker.ns = 'planes'
00186 marker.type = vm.Marker.LINE_LIST
00187 marker.action = vm.Marker.ADD
00188 marker.scale.x = 0.01
00189 marker.color.a = 1.0
00190 marker.color.b = 1.0
00191 marker.color.r = 0.75
00192
00193 while len(COLORS)<=len(planes):
00194 COLORS.append(ColorRGBA(a=1.0, r=random(), g=random(), b=random()))
00195
00196 for color,bp in zip(COLORS, planes):
00197 n = len(bp.polygon)
00198 for i in range(n):
00199 j = i-1 if i>0 else n-1
00200 marker.points.append(trans_to_pt(bp.p2w, bp.polygon[j]))
00201 marker.points.append(trans_to_pt(bp.p2w, bp.polygon[i]))
00202 marker.colors.append(color)
00203 marker.colors.append(color)
00204
00205 return marker
00206
00207
00208
00209
00210
00211
00212 def to_tuple(p):
00213 "Needed to convert between numpy and opencv"
00214 return (p[0], p[1])
00215
00216 def workaround_incorrect_planes(m):
00217 n = len(m.polygon)
00218 new_d = 0
00219 for pt in m.polygon:
00220 inc = (m.a*pt.x + m.b*pt.y + m.c*pt.z)
00221 new_d += inc/n
00222 m.d = new_d
00223
00224
00225
00226 def mult(m, p):
00227 "Multiply numpy homogeneous matrix * geometry_msgs.Point"
00228 return np.dot(m, [p.x, p.y, p.z, 1])
00229
00230 def trans2d(m, p):
00231 "Transform 2d pt using homogeneous matrix"
00232 return np.dot(m, np.hstack([p, 0, 1]))[0:3]
00233
00234 def trans_to_pt(m, p):
00235 "Transform 2d pt using m and convert to geometry_msgs.Point"
00236 return gm.Point(*trans2d(m, p))
00237
00238 def normalize_eq(m):
00239 v = np.array([m.a, m.b, m.c])
00240 return normalize(v), m.d/lin.norm(v)
00241
00242 def normalize(v):
00243 return v/lin.norm(v)
00244
00245 def polygons_intersect(p1, p2):
00246 return one_sided_intersect(p1, p2) or one_sided_intersect(p2, p1)
00247
00248 def one_sided_intersect(p1, p2):
00249 """
00250 Iterate over sides of P1 and see if one of them separates the two polygons.
00251 If there is, the polygons don't intersect.
00252 """
00253 n = len(p1)
00254 for i in range(n):
00255 j = i-1 if i>0 else n-1
00256 min1, max1 = project_onto_normal(p1, p1[i], p1[j])
00257 min2, max2 = project_onto_normal(p2, p1[i], p1[j])
00258 if max2<min1 or max1<min2:
00259 return False
00260 return True
00261
00262 def project_onto_normal(poly, q1, q2):
00263 "Project points of poly onto normal to line between q1 and q2"
00264 d = q2-q1
00265 perp = normalize(np.array((d[1], -d[0])))
00266 inf = None
00267 sup = None
00268 for p in poly:
00269 proj = np.dot(perp, p)
00270 if inf is None or proj<inf:
00271 inf = proj
00272 if sup is None or proj>sup:
00273 sup = proj
00274 assert inf is not None
00275 assert sup is not None
00276 return (inf, sup)
00277
00278
00279
00280
00281
00282
00283
00284
00285
00286
00287
00288
00289
00290
00291
00292
00293
00294
00295
00296
00297
00298
00299
00300
00301
00302
00303
00304
00305
00306
00307
00308
00309
00310
00311
00312
00313
00314
00315
00316
00317
00318
00319
00320
00321
00322
00323
00324