polygon_array_to_box_array.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 # -*- coding:utf-8 -*-
3 
4 import dynamic_reconfigure.server
5 import geometry_msgs.msg
6 import jsk_recognition_msgs.msg
7 import message_filters
8 import numpy as np
9 import rospy
10 import shapely.geometry
11 from jsk_recognition_utils.cfg import PolygonArrayToBoxArrayConfig
12 from jsk_topic_tools import ConnectionBasedTransport
13 from tf.transformations import quaternion_from_matrix as matrix2quaternion
14 from tf.transformations import unit_vector as normalize_vector
15 
16 
18  return np.array([[0, -v[2], v[1]],
19  [v[2], 0, -v[0]],
20  [-v[1], v[0], 0]])
21 
22 
23 def cross_product(a, b):
24  return np.dot(outer_product_matrix(a), b)
25 
26 
27 # minimum_rotated_rectangle is available since 1.6.0 (melodic)
28 # https://github.com/shapely/shapely/pull/361
29 from distutils.version import LooseVersion
30 if LooseVersion(shapely.__version__) < LooseVersion('1.6.0'):
31  import math
32  from itertools import islice
33  from shapely.affinity import affine_transform
34  @property
36  """Returns the general minimum bounding rectangle of
37  the geometry. Can possibly be rotated. If the convex hull
38  of the object is a degenerate (line or point) this same degenerate
39  is returned.
40  """
41  # first compute the convex hull
42  hull = polygon.convex_hull
43  try:
44  coords = hull.exterior.coords
45  except AttributeError: # may be a Point or a LineString
46  return hull
47  # generate the edge vectors between the convex hull's coords
48  edges = ((pt2[0]-pt1[0], pt2[1]-pt1[1]) for pt1, pt2 in zip(coords, islice(coords, 1, None)))
49 
50  def _transformed_rects():
51  for dx, dy in edges:
52  # compute the normalized direction vector of the edge vector
53  length = math.sqrt(dx**2 + dy**2)
54  ux, uy = dx/length, dy/length
55  # compute the normalized perpendicular vector
56  vx, vy = -uy, ux
57  # transform hull from the original coordinate system to the coordinate system
58  # defined by the edge and compute the axes-parallel bounding rectangle
59  transf_rect = affine_transform(hull, (ux,uy,vx,vy,0,0)).envelope
60  # yield the transformed rectangle and a matrix to transform it back
61  # to the original coordinate system
62  yield (transf_rect, (ux,vx,uy,vy,0,0))
63 
64  # check for the minimum area rectangle and return it
65  transf_rect, inv_matrix = min(_transformed_rects(), key=lambda r : r[0].area)
66  return affine_transform(transf_rect, inv_matrix)
67  shapely.geometry.Polygon.minimum_rotated_rectangle = minimum_rotated_rectangle
68 
70  first_axis=(1, 0, 0), second_axis=(0, 1, 0), axes='xy'):
71  if axes not in ['xy', 'yx', 'xz', 'zx', 'yz', 'zy']:
72  raise ValueError("Valid axes are 'xy', 'yx', 'xz', 'zx', 'yz', 'zy'.")
73  e1 = normalize_vector(first_axis)
74  e2 = normalize_vector(second_axis - np.dot(second_axis, e1) * e1)
75  if axes in ['xy', 'zx', 'yz']:
76  third_axis = cross_product(e1, e2)
77  else:
78  third_axis = cross_product(e2, e1)
79  e3 = normalize_vector(
80  third_axis - np.dot(third_axis, e1) * e1 - np.dot(third_axis, e2) * e2)
81  first_index = ord(axes[0]) - ord('x')
82  second_index = ord(axes[1]) - ord('x')
83  third_index = ((first_index + 1) ^ (second_index + 1)) - 1
84  indices = [first_index, second_index, third_index]
85  return np.vstack([e1, e2, e3])[np.argsort(indices)].T
86 
87 
88 def angle_between_vectors(v1, v2, normalize=True,
89  directed=True):
90  if normalize:
91  v1 = normalize_vector(v1)
92  v2 = normalize_vector(v2)
93  dot = np.dot(v1, v2)
94  return np.arccos(np.clip(dot if directed else np.fabs(dot), -1.0, 1.0))
95 
96 
97 def rotate_points(points, a, b):
98  if points.ndim == 1:
99  points = points[None, :]
100 
101  a = normalize_vector(a)
102  b = normalize_vector(b)
103  angle = angle_between_vectors(a, b, normalize=False, directed=False)
104  if np.isclose(angle, 0.0):
105  k = np.array([0, 0, 0], 'f')
106  else:
107  k = normalize_vector(np.cross(a, b))
108  theta = angle_between_vectors(a, b, normalize=False)
109  points_rot = points * np.cos(theta) \
110  + np.cross(k, points) * np.sin(theta) \
111  + k * np.dot(k, points.T).reshape(-1, 1) * (1 - np.cos(theta))
112  return points_rot
113 
114 
115 class PolygonArrayToRectBoxArray(ConnectionBasedTransport):
116 
117  def __init__(self):
118  super(PolygonArrayToRectBoxArray, self).__init__()
119  dynamic_reconfigure.server.Server(
120  PolygonArrayToBoxArrayConfig,
121  self._config_callback)
122  self.polygons_pub = self.advertise(
123  '~output/polygons',
124  jsk_recognition_msgs.msg.PolygonArray,
125  queue_size=1)
126  self.coeffs_pub = self.advertise(
127  '~output/coefficients',
128  jsk_recognition_msgs.msg.ModelCoefficientsArray,
129  queue_size=1)
130  self.boxes_pub = self.advertise(
131  '~output/boxes',
132  jsk_recognition_msgs.msg.BoundingBoxArray,
133  queue_size=1)
134 
135  def _config_callback(self, config, level):
136  self.thickness = config.thickness
137  return config
138 
139  def subscribe(self):
140  queue_size = rospy.get_param('~queue_size', 10)
141  sub_polygon = message_filters.Subscriber(
142  '~input/polygons',
143  jsk_recognition_msgs.msg.PolygonArray,
144  queue_size=1)
145  sub_coefficients = message_filters.Subscriber(
146  '~input/coefficients',
147  jsk_recognition_msgs.msg.ModelCoefficientsArray,
148  queue_size=1)
149  self.subs = [sub_polygon, sub_coefficients]
150  if rospy.get_param('~approximate_sync', False):
151  slop = rospy.get_param('~slop', 0.1)
152  sync = message_filters.ApproximateTimeSynchronizer(
153  fs=self.subs, queue_size=queue_size, slop=slop)
154  else:
156  fs=self.subs, queue_size=queue_size)
157  sync.registerCallback(self._cb)
158 
159  def unsubscribe(self):
160  for s in self.subs:
161  s.unregister()
162 
163  def _cb(self, polygons_msg, coeffs_msg):
164  new_polygons_msg = jsk_recognition_msgs.msg.PolygonArray()
165  new_polygons_msg.header = polygons_msg.header
166  boxes_msg = jsk_recognition_msgs.msg.BoundingBoxArray(
167  header=polygons_msg.header)
168  base_normal = [0, 0, 1]
169  for coeff, polygon in zip(
170  coeffs_msg.coefficients,
171  polygons_msg.polygons):
172  a, b, c, d = coeff.values
173  points = np.array(
174  [[point.x, point.y, point.z]
175  for point in polygon.polygon.points],
176  dtype=np.float32)
177  normal = [a, b, c]
178  # Project 3d points onto a plane.
179  projected_points = rotate_points(
180  points,
181  normal,
182  base_normal)
183  # Calculate the smallest rectangle on the plane.
184  shapely_polygon = shapely.geometry.Polygon(
185  projected_points)
186  rect = shapely_polygon.minimum_rotated_rectangle
187  x, y = rect.exterior.xy
188  rect_polygon_2d = - np.ones((5, 3), 'f') * d
189  rect_polygon_2d[:, 0] = x
190  rect_polygon_2d[:, 1] = y
191  # Return the rectangle to 3d space.
192  rect_polygon = rotate_points(
193  rect_polygon_2d,
194  base_normal,
195  normal)
196  # Save as ros message.
197  polygon = geometry_msgs.msg.PolygonStamped(
198  header=polygon.header)
199  for x, y, z in rect_polygon:
200  polygon.polygon.points.append(
201  geometry_msgs.msg.Point32(x, y, z))
202  new_polygons_msg.polygons.append(polygon)
203 
204  center_x, center_y, center_z = rect_polygon[:4].mean(axis=0)
205 
206  box_msg = jsk_recognition_msgs.msg.BoundingBox(
207  header=polygon.header)
208  base_matrix = np.eye(4)
209  # The long side is the x-axis and
210  # the normal direction is the z-axis.
211  matrix = rotation_matrix_from_axis(
212  rect_polygon[1] - rect_polygon[0], normal, 'xz')
213  base_matrix[:3, :3] = matrix
214  q_x, q_y, q_z, q_w = matrix2quaternion(base_matrix)
215  dim_x = np.linalg.norm(rect_polygon[1] - rect_polygon[0])
216  dim_y = np.linalg.norm(rect_polygon[2] - rect_polygon[1])
217  box_msg.pose.position.x = center_x
218  box_msg.pose.position.y = center_y
219  box_msg.pose.position.z = center_z
220  box_msg.pose.orientation.w = q_w
221  box_msg.pose.orientation.x = q_x
222  box_msg.pose.orientation.y = q_y
223  box_msg.pose.orientation.z = q_z
224  box_msg.dimensions.x = dim_x
225  box_msg.dimensions.y = dim_y
226  box_msg.dimensions.z = self.thickness
227  boxes_msg.boxes.append(box_msg)
228  self.polygons_pub.publish(new_polygons_msg)
229  self.coeffs_pub.publish(coeffs_msg)
230  self.boxes_pub.publish(boxes_msg)
231 
232 
233 if __name__ == '__main__':
234  rospy.init_node('polygon_array_to_box_array')
236  rospy.spin()
polygon_array_to_box_array.PolygonArrayToRectBoxArray._cb
def _cb(self, polygons_msg, coeffs_msg)
Definition: polygon_array_to_box_array.py:163
polygon_array_to_box_array.PolygonArrayToRectBoxArray._config_callback
def _config_callback(self, config, level)
Definition: polygon_array_to_box_array.py:135
polygon_array_to_box_array.outer_product_matrix
def outer_product_matrix(v)
Definition: polygon_array_to_box_array.py:17
polygon_array_to_box_array.PolygonArrayToRectBoxArray.subscribe
def subscribe(self)
Definition: polygon_array_to_box_array.py:139
polygon_array_to_box_array.PolygonArrayToRectBoxArray.thickness
thickness
Definition: polygon_array_to_box_array.py:136
polygon_array_to_box_array.PolygonArrayToRectBoxArray.boxes_pub
boxes_pub
Definition: polygon_array_to_box_array.py:130
polygon_array_to_box_array.PolygonArrayToRectBoxArray.unsubscribe
def unsubscribe(self)
Definition: polygon_array_to_box_array.py:159
message_filters::Subscriber
polygon_array_to_box_array.angle_between_vectors
def angle_between_vectors(v1, v2, normalize=True, directed=True)
Definition: polygon_array_to_box_array.py:88
polygon_array_to_box_array.minimum_rotated_rectangle
minimum_rotated_rectangle
Definition: polygon_array_to_box_array.py:67
polygon_array_to_box_array.rotate_points
def rotate_points(points, a, b)
Definition: polygon_array_to_box_array.py:97
polygon_array_to_box_array.PolygonArrayToRectBoxArray.__init__
def __init__(self)
Definition: polygon_array_to_box_array.py:117
polygon_array_to_box_array.rotation_matrix_from_axis
def rotation_matrix_from_axis(first_axis=(1, 0, 0), second_axis=(0, 1, 0), axes='xy')
Definition: polygon_array_to_box_array.py:69
polygon_array_to_box_array.PolygonArrayToRectBoxArray.coeffs_pub
coeffs_pub
Definition: polygon_array_to_box_array.py:126
message_filters::TimeSynchronizer
polygon_array_to_box_array.PolygonArrayToRectBoxArray.polygons_pub
polygons_pub
Definition: polygon_array_to_box_array.py:122
polygon_array_to_box_array.PolygonArrayToRectBoxArray.subs
subs
Definition: polygon_array_to_box_array.py:149
polygon_array_to_box_array.PolygonArrayToRectBoxArray
Definition: polygon_array_to_box_array.py:115
polygon_array_to_box_array.cross_product
def cross_product(a, b)
Definition: polygon_array_to_box_array.py:23


jsk_recognition_utils
Author(s):
autogenerated on Tue Jan 7 2025 04:04:52