matchtemplate.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 
3 import roslib; roslib.load_manifest('jsk_perception')
4 import rospy
5 import numpy as np
6 import thread
7 from sensor_msgs.msg import Image
8 from geometry_msgs.msg import *
9 from jsk_recognition_msgs.msg import Rect
10 import cv2
11 from cv_bridge import CvBridge, CvBridgeError
12 
13 from dynamic_reconfigure.server import Server as DynamicReconfigureServer
14 from dynamic_reconfigure.msg import SensorLevels
15 from jsk_perception.cfg import matchtemplateConfig as ConfigType
16 
17 # you can set below options for each templates
18 # color space: mono8,bgr8,bgra8,hsv,hsva (OpenCV expects bgr)
19 # template size: tuple (x,y,width,height)
20 # search area: tuple (x,y,width,height)
21 # search topic: (TODO)
22 # match method: 6type
23 
24 # publish topic:
25 # current_template [sensor_msgs/Image]
26 # result [geometry_msgs/TransformStamped]
27 # debug_image [sensor_msgs/Image]
28 
29 # subscribe topic:
30 # reference [sensor_msgs/Image]
31 # search [sensor_msgs/Image]
32 # set_reference_point [geometry_msgs/PointStamped]
33 # set_search_rect [jsk_recognition_msgs/Rect]
34 
35 # return (1st-val,1st-loc,2nd-val,2nd-loc)
36 def _MinMaxLock2nd(arr,ex_size,is_min):
37  if is_min: idx = 0
38  else: idx = 1
39  status = cv2.minMaxLoc(arr)
40  pt1 = (max(status[2+idx][0]-ex_size[0]/2,0),
41  max(status[2+idx][1]-ex_size[1]/2,0))
42  pt2 = (min(status[2+idx][0]+ex_size[0]/2,arr.shape[1]),
43  min(status[2+idx][1]+ex_size[1]/2,arr.shape[0]))
44  mask = np.ones((arr.shape[0], arr.shape[1]), dtype=np.uint8) * 255
45  mask[pt1[0]:pt2[0], pt1[1]:pt2[1]] = 0
46  status2 = cv2.minMaxLoc(arr, mask)
47  return (status[0+idx],status2[0+idx],status[2+idx],status2[2+idx])
48 
49 def MinLock2nd(arr,ex_size):
50  return _MinMaxLock2nd(arr,ex_size,True)
51 def MaxLock2nd(arr,ex_size):
52  return _MinMaxLock2nd(arr,ex_size,False)
53 
54 
56  bridge = CvBridge()
57 
58  def __init__(self):
59  self.lockobj = thread.allocate_lock()
60  # variables
61  self.reference_image_msg = None
62  self.templates = {} # frame_id : ref_image,ser_frame,ser_rect
63  self.clock = [rospy.Time.now()]
64 
65  # publisher
66  self.reference_pub = rospy.Publisher(
67  "current_template", Image, queue_size=1)
68  self.result_pub = rospy.Publisher(
69  "result", TransformStamped, queue_size=1)
70  self.debug_pub = rospy.Publisher("debug_image", Image, queue_size=1)
71 
72  # subscriber
73  self.reference_image_sub = rospy.Subscriber(rospy.resolve_name("reference"),
74  Image,self.ref_image_callback,queue_size=1)
75  self.search_image_sub = rospy.Subscriber(rospy.resolve_name("search"),
76  Image,self.ser_image_callback,queue_size=1)
77  self.reference_point_sub = rospy.Subscriber(
78  rospy.resolve_name("set_reference_point"),
79  PointStamped,self.set_reference_point_callback,queue_size=1)
80  self.search_sub = rospy.Subscriber(rospy.resolve_name("set_search_rect"),
81  Rect,self.set_search_callback,queue_size=1)
82 
83  self.server = DynamicReconfigureServer(ConfigType, self.reconfigure)
84 
85  # general template modifier function
86  def set_template (self, ref_id='', ref_image=None, ref_rect=None,
87  ser_frame=None, ser_rect=None, color=None, method=None):
88  if not self.templates.has_key(ref_id):
89  self.templates[ref_id] = {'ref_orig':None,'ref_point':None,'ref_image':None,
90  'ser_frame':None, 'ser_rect':None,
91  'color':'mono8', 'method':cv2.TM_SQDIFF_NORMED} # normalized SSD
92  color_changed = True
93  else:
94  color_changed = (self.templates[ref_id]['color'] != color) and (color != None)
95 
96  something_changed = (ref_image is not None) or \
97  (ref_rect is not None) or color_changed
98 
99  # parameters for template
100  if ref_image is not None:
101  self.templates[ref_id]['ref_orig'] = ref_image.copy()
102  else:
103  ref_image = self.templates[ref_id]['ref_orig'] # old image can be set here
104 
105  if color != None:
106  if ref_image is not None and ref_image.ndim == 2:
107  color = 'mono8'
108  self.templates[ref_id]['color'] = color
109  else:
110  color = self.templates[ref_id]['color']
111 
112  # copy template from reference original image
113  if (ref_rect is not None) and (ref_image is not None) and \
114  something_changed:
115  ref_rect = (min(max(0,ref_rect[0]),ref_image.shape[1]-ref_rect[2]),
116  min(max(0,ref_rect[1]),ref_image.shape[0]-ref_rect[3]),
117  ref_rect[2],ref_rect[3])
118  ref_image_rect = ref_image[
119  ref_rect[1]:ref_rect[1] + ref_rect[3],
120  ref_rect[0]:ref_rect[0] + ref_rect[2]].copy()
121  if color == 'bgr8' or (color == 'mono8' and ref_image.ndim == 2):
122  template_image = ref_image_rect
123  elif color == 'mono8':
124  template_image = cv2.cvtColor(
125  ref_image_rect, cv2.COLOR_BGR2GRAY)
126  elif color == 'hsv8':
127  template_image = cv2.cvtColor(
128  ref_image_rect, cv2.COLOR_BGR2HSV)
129 
130  self.templates[ref_id]['ref_point'] = (ref_rect[0]+ref_rect[2]/2,
131  ref_rect[1]+ref_rect[3]/2)
132  self.templates[ref_id]['ref_image'] = template_image
133  rospy.loginfo("set ref_image id=%s, rect=%s", ref_id, ref_rect);
134 
135  # matching parameters
136  if ser_frame != None:
137  self.templates[ref_id]['ser_frame'] = ser_frame
138  rospy.loginfo("set ser_frame id=%s frame=%s", ref_id, ser_frame);
139  if ser_rect != None:
140  self.templates[ref_id]['ser_rect'] = ser_rect
141  #rospy.loginfo("set ser_rect id=%s %s", ref_id, ser_rect);
142  if method != None:
143  self.templates[ref_id]['method'] = method
144  rospy.loginfo("set method id=%s method=%s", ref_id, method);
145 
146  def set_reference (self, rect):
147  if self.reference_image_msg == None: return
148  try:
149  cv_image = self.bridge.imgmsg_to_cv2(
150  self.reference_image_msg, "bgr8")
151  self.set_template('',ref_image=cv_image, ref_rect=rect)
152  except CvBridgeError as e:
153  print(e)
154 
155  #
156  # Callback Functions (lock require)
157  #
158 
159  # simple point tracking callback
160  def set_reference_point_callback (self, msg): # PointStamped
161  self.lockobj.acquire()
162  pt = (int(msg.point.x),int(msg.point.y))
163  rect = (pt[0]-self.default_template_size[0]/2,
164  pt[1]-self.default_template_size[1]/2,
166  self.set_reference(rect)
167  print(rect)
168  search_rect = (pt[0]-self.default_search_size[0]/2,
169  pt[1]-self.default_search_size[1]/2,
171  self.set_template('',ser_frame=None, ser_rect=search_rect)
172  self.lockobj.release()
173 
174  # store the latest image
175  def set_search_callback (self, msg):
176  self.lockobj.acquire()
177  self.set_template(ser_rect=(msg.x,msg.y,msg.width,msg.height))
178  self.lockobj.release()
179 
180  # store the latest image
181  def ref_image_callback (self, msg):
182  self.lockobj.acquire()
183  self.reference_image_msg = msg
184  self.lockobj.release()
185 
186  # reconfigure
187  def reconfigure(self,config,level):
188  self.lockobj.acquire()
189  # param
190  self.default_template_size = (config['default_template_width'],
191  config['default_template_height'])
192  self.default_search_size = (config['default_search_width'],
193  config['default_search_height'])
194  self.show_debug_image = config['show_debug_image']
195  self.auto_search_area = config['auto_search_area']
196 
197  if config['current_template_id'] in self.templates.keys():
198  # set template configuration
199  template = self.templates[config['current_template_id']]
200  method_enum = [
201  cv2.TM_SQDIFF, cv2.TM_SQDIFF_NORMED, cv2.TM_CCORR,
202  cv2.TM_CCORR_NORMED, cv2.TM_CCOEFF, cv2.TM_CCOEFF_NORMED]
203  if (template['ref_point'] is not None) and \
204  (template['ref_image'] is not None):
205  ref_pt = template['ref_point']
206  ref_size = template['ref_image'].shape
207  ref_rect = (ref_pt[0]-ref_size[0]/2,ref_pt[1]-ref_size[1]/2,ref_size[0],ref_size[1])
208  self.set_template(ref_id=config['current_template_id'],
209  color=config['template_color_space'],
210  ref_rect=ref_rect,
211  method=method_enum[config['match_method']])
212  # return current configuration
213  config['template_color_method'] = template['color']
214  self.lockobj.release()
215  return config
216 
217  def ser_image_callback (self, msg):
218  # initialize debug image
219  if self.show_debug_image:
220  debug_image = self.bridge.imgmsg_to_cv2(msg, "bgr8")
221 
222  self.lockobj.acquire()
223 
224  # match for each template
225  for template_id in self.templates.keys():
226  reference_image = self.templates[template_id]['ref_image']
227  search_rect = self.templates[template_id]['ser_rect']
228  matchmethod = self.templates[template_id]['method']
229  color_space = self.templates[template_id]['color']
230  if reference_image is None:
231  continue
232  if search_rect == None: continue
233  if (self.templates[template_id]['ser_frame'] != None and
234  self.templates[template_id]['ser_frame'] != msg.header.frame_id): continue
235 
236  # search rect &= image size
237  search_rect = (max(0,search_rect[0]),
238  max(0,search_rect[1]),
239  min(msg.width,search_rect[0]+search_rect[2])- max(0,search_rect[0]),
240  min(msg.height,search_rect[1]+search_rect[3])- max(0,search_rect[1]))
241 
242  result = TransformStamped(header=msg.header)
243 
244  try:
245  if color_space == 'mono8':
246  search_image = self.bridge.imgmsg_to_cv2(msg, 'mono8')
247  else:
248  search_image = self.bridge.imgmsg_to_cv2(msg, 'bgr8')
249  if color_space != 'bgr8':
250  search_image = cv2.cvtColor(
251  search_image, cv2.COLOR_BGR2HSV)
252 
253  image_size = search_image.shape
254  reference_size = (
255  reference_image.shape[1], reference_image.shape[0])
256 
257  # search size < reference size
258  if (search_rect[2] < reference_size[0]) or (search_rect[3] < reference_size[1]):
259  continue
260 
261  results = cv2.matchTemplate(
262  search_image[
263  search_rect[1]:search_rect[1] + search_rect[3],
264  search_rect[0]:search_rect[0] + search_rect[2]],
265  reference_image, matchmethod)
266 
267  if matchmethod in [cv2.TM_SQDIFF, cv2.TM_SQDIFF_NORMED]:
268  status = MinLock2nd(results,reference_size) # minimum for SSD
269  else:
270  status = MaxLock2nd(results,reference_size) # maximum for others
271  status = (1 - status[0], 1 - status[1], status[2], status[3])
272 
273  result_pt = (status[2][0]+search_rect[0]+reference_size[0]/2,
274  status[2][1]+search_rect[1]+reference_size[1]/2)
275 
276  # publish center position as result
277  result.child_frame_id = template_id
278  result.transform.translation.x = result_pt[0]
279  result.transform.translation.y = result_pt[1]
280  result.transform.rotation.w = 1 # not rotate, temporary
281  self.result_pub.publish(result)
282 
283  if reference_image.ndim == 2 and \
284  reference_image.dtype == np.uint8:
285  ref_msg = self.bridge.cv2_to_imgmsg(
286  reference_image, "mono8")
287  else:
288  ref_msg = self.bridge.cv2_to_imgmsg(
289  reference_image, "bgr8")
290  self.reference_pub.publish(ref_msg)
291 
292  # self feedback
293  if self.auto_search_area:
294  val_x = result_pt[0]-search_rect[2]/2
295  val_y = result_pt[1]-search_rect[3]/2
296  ser_scale = max(2,5+np.log(status[0])) # ???
297  new_ser_rect = (
298  min(max(val_x,0),image_size[0]-search_rect[2]),
299  min(max(val_y,0),image_size[1]-search_rect[3]),
300  reference_size[0]*ser_scale,reference_size[1]*ser_scale)
301  self.set_template(ser_rect=new_ser_rect)
302 
303  # draw on debug image
304  if self.show_debug_image:
305  cv2.rectangle(debug_image,
306  (result_pt[0] - reference_size[0]/2,
307  result_pt[1] - reference_size[1]/2),
308  (result_pt[0] + reference_size[0]/2,
309  result_pt[1] + reference_size[1]/2),
310  color=(0, 0, 255)) # red
311  cv2.rectangle(debug_image,
312  (search_rect[0], search_rect[1]),
313  (search_rect[0] + search_rect[2],
314  search_rect[1] + search_rect[3]),
315  color=(0, 255, 0))
316  cv2.putText(
317  debug_image, str(status[0]),
318  (search_rect[0], search_rect[1] + search_rect[3]),
319  fontFace=cv2.FONT_HERSHEY_SIMPLEX, fontScale=1,
320  color=(0, 255, 0), thickness=2,
321  lineType=16) # 16 means cv2.CV_AA
322 
323  if reference_image.ndim == 2:
324  reference_color_image = cv2.cvtColor(
325  reference_image, cv2.COLOR_GRAY2BGR)
326  debug_image[
327  :reference_size[1], :reference_size[0], :] = \
328  reference_color_image.copy()
329  else:
330  debug_image[
331  :reference_size[1], :reference_size[0], :] = \
332  reference_image.copy()
333 
334  except CvBridgeError as e:
335  print(e)
336 
337  self.lockobj.release()
338 
339  if self.show_debug_image:
340  # calc and print fps (30frame avg)
341  self.clock += [rospy.Time.now()]
342  if 30 <= len(self.clock):
343  fps = 30.0 / (self.clock[29]-self.clock[0]).to_sec()
344  cv2.putText(
345  debug_image, '%.1f fps' % fps, (msg.width - 150, 40),
346  fontFace=cv2.FONT_HERSHEY_SIMPLEX, fontScale=1,
347  color=(0, 0, 255), # red
348  thickness=2, lineType=16) # 16 means cv2.CV_AA
349  self.clock = self.clock[-30:]
350  # publish debug image
351  debug_msg = self.bridge.cv2_to_imgmsg(debug_image, "bgr8")
352  self.debug_pub.publish(debug_msg)
353 
354  return
355 
356 if __name__=='__main__':
357  rospy.init_node('match_template')
358 
359  obj = MepConverter()
360 
361  ## reconfigure
362  rospy.loginfo("reference image %s, size %s", obj.reference_image_sub.resolved_name, obj.default_template_size)
363  rospy.loginfo(" search_image %s, size %s", obj.search_image_sub.resolved_name, obj.default_search_size)
364 
365  rospy.spin()
def MinLock2nd(arr, ex_size)
def reconfigure(self, config, level)
def _MinMaxLock2nd(arr, ex_size, is_min)
def set_template(self, ref_id='', ref_image=None, ref_rect=None, ser_frame=None, ser_rect=None, color=None, method=None)
def MaxLock2nd(arr, ex_size)


jsk_perception
Author(s): Manabu Saito, Ryohei Ueda
autogenerated on Mon May 3 2021 03:03:27