depth_error_calibration.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 import cv2
3 import numpy as np
4 import matplotlib
5 matplotlib.use('Agg') # NOQA
6 import matplotlib.pyplot as plt
7 import matplotlib.animation as animation
8 from jsk_recognition_msgs.msg import DepthErrorResult
9 from sklearn import linear_model
10 import time
11 import threading
12 import sys
13 import rospy
14 import argparse
15 import csv
16 import math
17 import datetime
18 from jsk_recognition_msgs.srv import SetDepthCalibrationParameter
19 from jsk_recognition_msgs.msg import DepthCalibrationParameter
20 from sensor_msgs.msg import Image
21 from cv_bridge import CvBridge, CvBridgeError
22 
23 xs = []
24 raw_xs = []
25 ys = []
26 us = []
27 vs = []
28 c_us = []
29 c_vs = []
30 value_cache = dict() # (u, v) -> [z]
31 eps_z = 0.1 # 10cm
32 lock = threading.Lock()
33 u_min = None
34 u_max = None
35 v_min = None
36 v_max = None
37 model = None
38 set_param = None
39 MODELS = ["linear", "quadratic", "quadratic-uv", "quadratic-uv-abs", "quadratic-uv-quadratic", "quadratic-uv-quadratic-abs"]
40 
41 def query_yes_no(question, default=None):
42  """Ask a yes/no question via raw_input() and return their answer.
43 
44  "question" is a string that is presented to the user.
45  "default" is the presumed answer if the user just hits <Enter>.
46  It must be "yes" (the default), "no" or None (meaning
47  an answer is required of the user).
48 
49  The "answer" return value is one of "yes" or "no".
50  """
51  valid = {"yes": True, "y": True, "ye": True,
52  "no": False, "n": False}
53  if default is None:
54  prompt = " [y/n] "
55  elif default == "yes":
56  prompt = " [Y/n] "
57  elif default == "no":
58  prompt = " [y/N] "
59  else:
60  raise ValueError("invalid default answer: '%s'" % default)
61 
62  while True:
63  sys.stdout.write(question + prompt)
64  choice = raw_input().lower()
65  if default is not None and choice == '':
66  return valid[default]
67  elif choice in valid:
68  return valid[choice]
69  else:
70  sys.stdout.write("Please respond with 'yes' or 'no' "
71  "(or 'y' or 'n').\n")
72 
73 
75  if model == "linear":
76  return v[0]
77  elif model == "quadratic":
78  return v[1]
79  elif model == "quadratic-uv" or model == "quadratic-uv-abs":
80  return v[-3]
81  elif model == "quadratic-uv-quadratic" or model == "quadratic-uv-quadratic-abs":
82  return v[-5]
83 
84 def genFeatureVector(x, u, v, cu, cv):
85  global model
86  x2 = x * x
87  u2 = u * u
88  v2 = v * v
89  if model == "linear":
90  return [x]
91  elif model == "quadratic":
92  return [x2, x]
93  elif model == "quadratic-uv":
94  return [u * x2, v * x2, x2,
95  u * x, v * x, x,
96  u, v]
97  elif model == "quadratic-uv-abs":
98  u = abs(u - cu)
99  v = abs(v - cv)
100  return [u * x2, v * x2, x2,
101  u * x, v * x, x,
102  u, v]
103  elif model == "quadratic-uv-quadratic":
104  return [u2 * x2, u * x2, v2 * x2, v * x2, x2,
105  u2 * x, u * x, v2 * x, v * x, x,
106  u2, u, v2, v]
107  elif model == "quadratic-uv-quadratic-abs":
108  u = abs(u - cu)
109  v = abs(v - cv)
110  u2 = u * u
111  v2 = v * v
112  return [u2 * x2, u * x2, v2 * x2, v * x2, x2,
113  u2 * x, u * x, v2 * x, v * x, x,
114  u2, u, v2, v]
115 
116 def isValidClassifier(classifier):
117  # before classifire outputs meaningful value,
118  # intercept is a list, so we skip the value
119  c0 = classifier.intercept_
120  return not hasattr(c0, "__len__");
121 
122 def setParameter(classifier):
123  global set_param
124  c = classifier.coef_
125  c0 = classifier.intercept_
126  param = DepthCalibrationParameter()
127  if not isValidClassifier(classifier):
128  print("parameters are list")
129  return
130  if model == "linear":
131  param.coefficients2 = [0, 0, 0, 0, 0]
132  param.coefficients1 = [0, 0, 0, 0, c[0]]
133  param.coefficients0 = [0, 0, 0, 0, c0]
134  param.use_abs = False
135  elif model == "quadratic":
136  param.coefficients2 = [0, 0, 0, 0, c[0]]
137  param.coefficients1 = [0, 0, 0, 0, c[1]]
138  param.coefficients0 = [0, 0, 0, 0, c0]
139  param.use_abs = False
140  elif model == "quadratic-uv":
141  param.coefficients2 = [0, c[0], 0, c[1], c[2]]
142  param.coefficients1 = [0, c[3], 0, c[4], c[5]]
143  param.coefficients0 = [0, c[6], 0, c[7], c0]
144  param.use_abs = False
145  elif model == "quadratic-uv-abs":
146  param.coefficients2 = [0, c[0], 0, c[1], c[2]]
147  param.coefficients1 = [0, c[3], 0, c[4], c[5]]
148  param.coefficients0 = [0, c[6], 0, c[7], c0]
149  param.use_abs = True
150  elif model == "quadratic-uv-quadratic":
151  param.coefficients2 = c[0:5]
152  param.coefficients1 = c[5:10]
153  param.coefficients0 = [c[10], c[11], c[12], c[13], c0]
154  param.use_abs = False
155  elif model == "quadratic-uv-quadratic-abs":
156  param.coefficients2 = c[0:5]
157  param.coefficients1 = c[5:10]
158  param.coefficients0 = [c[10], c[11], c[12], c[13], c0]
159  param.use_abs = True
160  set_param(param)
161 
162 def processData(x, y, u, v, cu, cv, fit = True):
163  global xs, ys, classifier, u_min, u_max, v_min, v_max, raw_xs
164  uu = int(u/10)
165  vv = int(v/10)
166  with lock:
167  if value_cache.has_key((uu, vv)):
168  zs = value_cache[(uu, vv)]
169  for z in zs:
170  if abs(z - y) < eps_z:
171  print("duplicated value")
172  return
173  else:
174  value_cache[(uu, vv)].append(y)
175  else:
176  value_cache[(uu, vv)] = [y]
177  raw_xs.append(x)
178  us.append(u)
179  vs.append(v)
180  c_us.append(cu)
181  c_vs.append(cv)
182  if u > u_min and u < u_max and v < v_max and v > v_min:
183  print((x, y))
184  xs.append(genFeatureVector(x, u, v, cu, cv))
185  ys.append(y)
186  if fit:
187  classifier.fit(xs, ys)
188  try:
189  setParameter(classifier)
190  except rospy.service.ServiceException as e:
191  rospy.logfatal("failed to call service: %s" % (e.message))
192  try:
193  print(modelEquationString(classifier))
194  except Exception as e:
195  rospy.logwarn("failed to print model: %s" % e.message)
196 
197  else:
198  print("(%d, %d) is out of range" % (u, v))
199 
200 def callback(msg):
201  global xs, ys, classifier, u_min, u_max, v_min, v_max, raw_xs
202 
203  x = msg.observed_depth
204  y = msg.true_depth
205  u = msg.u
206  v = msg.v
207  if math.isnan(x) or math.isnan(y):
208  return
209  processData(x, y, u, v, msg.center_u, msg.center_v)
210  updatePlot()
211 
212 def uvCoefString(c, absolute=False):
213  if absolute:
214  return "%f|u| + %f|v| + %f" % (c[0], c[1], c[2])
215  else:
216  return "%fu + %fv + %f" % (c[0], c[1], c[2])
217 def uvQuadraticCoefString(c, absolute=False):
218  if absolute:
219  return "%f|u|^2 + %f|u| + %f|v|^2 + %f|v| + %f" % (c[0], c[1], c[2], c[3], c[4])
220  else:
221  return "%fu^2 + %fu + %fv^2 + %fv + %f" % (c[0], c[1], c[2], c[3], c[4])
222 
223 def modelEquationString(classifier):
224  global model, xs, ys
225  c = classifier.coef_
226  i = classifier.intercept_
227  if model == "linear":
228  return "%fz + %f\n(score: %f)" % (
229  c[0], i,
230  classifier.score(xs, ys))
231  elif model == "quadratic":
232  return "%fz^2 + %fz + %f\n(score: %f)" % (
233  c[0],
234  c[1],
235  i,
236  classifier.score(xs, ys))
237  elif model == "quadratic-uv":
238  return "(%s)z^2 +\n(%s)z +\n%s\n(score: %f)" % (
239  uvCoefString(c[0:3]),
240  uvCoefString(c[3:6]),
241  uvCoefString([c[6], c[7], i]),
242  classifier.score(xs, ys))
243  elif model == "quadratic-uv-abs":
244  return "(%s)z^2 +\n(%s)z +\n%s\n(score: %f)" % (
245  uvCoefString(c[0:3], True),
246  uvCoefString(c[3:6], True),
247  uvCoefString([c[6], c[7], i], True),
248  classifier.score(xs, ys))
249  elif model == "quadratic-uv-quadratic":
250  return "(%s)z^2 +\n(%s)z +\n%s\n(score: %f)" % (
251  uvQuadraticCoefString(c[0:5]),
252  uvQuadraticCoefString(c[5:10]),
253  uvQuadraticCoefString([c[10], c[11], c[12], c[13], i]),
254  classifier.score(xs, ys))
255  elif model == "quadratic-uv-quadratic-abs":
256  return "(%s)z^2 +\n(%s)z +\n%s\n(score: %f)" % (
257  uvQuadraticCoefString(c[0:5], True),
258  uvQuadraticCoefString(c[5:10], True),
259  uvQuadraticCoefString([c[10], c[11], c[12], c[13], i], True),
260  classifier.score(xs, ys))
261 
262 def applyModel(x, u, v, cu, cv, clssifier):
263  global model
264  c = classifier.coef_
265  i = classifier.intercept_
266  if model == "linear":
267  return c[0] * x + i
268  elif model == "quadratic":
269  return c[0] * x * x + c[1] * x + i
270  elif model == "quadratic-uv":
271  return ((c[0] * u + c[1] * v + c[2]) * x * x +
272  (c[3] * u + c[4] * v + c[5]) * x +
273  c[6] * u + c[7] * v + i)
274  elif model == "quadratic-uv-abs":
275  u = abs(u - cu)
276  v = abs(v - cv)
277  return ((c[0] * u + c[1] * v + c[2]) * x * x +
278  (c[3] * u + c[4] * v + c[5]) * x +
279  c[6] * u + c[7] * v + i)
280  elif model == "quadratic-uv-quadratic":
281  u2 = u * u
282  v2 = v * v
283  return ((c[0] * u2 + c[1] * u + c[2] * v2 + c[3] * v + c[4]) * x * x +
284  (c[5] * u2 + c[6] * u + c[7] * v2 + c[8] * v + c[9]) * x +
285  (c[10] * u2 + c[11] * u + c[12] * v2 + c[13] * v + i))
286  elif model == "quadratic-uv-quadratic-abs":
287  u = abs(u - cu)
288  v = abs(v - cv)
289  u2 = u * u
290  v2 = v * v
291  return ((c[0] * u2 + c[1] * u + c[2] * v2 + c[3] * v + c[4]) * x * x +
292  (c[5] * u2 + c[6] * u + c[7] * v2 + c[8] * v + c[9]) * x +
293  (c[10] * u2 + c[11] * u + c[12] * v2 + c[13] * v + i))
294 
295 
297  global xs, ax, width, height
298  if rospy.is_shutdown():
299  plt.close()
300  return
301  with lock:
302  if len(xs) == 0:
303  return
304  try:
305  plt.cla()
306  plt.xlabel('Z from depth image')
307  plt.ylabel('Z from checker board')
308  plt.grid(True)
309  plt.title(model)
310  plt.scatter([getXFromFeatureVector(x) for x in xs[:-1]],
311  ys[:-1], s=10, c='b', zorder=10, alpha=0.1)
312  plt.scatter([getXFromFeatureVector(xs[-1])],
313  [ys[-1]], s=10, c='r', zorder=10, alpha=1.0)
314  xmin = np.amin([getXFromFeatureVector(x) for x in xs])
315  xmax = np.amax([getXFromFeatureVector(x) for x in xs])
316  X_test = [[xmin * xmin, xmin], [xmax * xmax, xmax]]
317  X_range = np.linspace(xmin, xmax, 100)
318  ax.plot(X_range, X_range, linewidth=2, color='green', alpha=0.5)
319  ax.plot(X_range,
320  applyModel(X_range,
321  width / 2, height / 2,
322  width / 2, height / 2,
323  classifier),
324  linewidth=2, color='red', alpha=0.5)
325  plt.text(xmin, xmax - 0.1,
326  modelEquationString(classifier),
327  fontsize=12)
328  # publish frequency image
329  bridge = CvBridge()
330  img = generateFrequencyMap()
331  pub_image.publish(bridge.cv2_to_imgmsg(img, "bgr8"))
332  # publish error plot
333  fig = plt.gcf()
334  fig.canvas.draw()
335  w, h = fig.canvas.get_width_height()
336  plot_img = np.fromstring(fig.canvas.tostring_rgb(), dtype=np.uint8)
337  fig.clf()
338  plot_img.shape = (h, w, 3)
339  plt.close()
340  pub_error_plot.publish(bridge.cv2_to_imgmsg(plot_img, "bgr8"))
341  except Exception as e:
342  rospy.logerr(e.message)
343 
345  global width, height
346  # bgr
347  img = np.tile(np.uint8([0,0,0]), (height / 10, width / 10, 1))
348  frequency = dict()
349  for (u, v) in value_cache.keys():
350  min_color = np.uint8([255, 0, 0])
351  max_color = np.uint8([0, 0, 255])
352  uu = u
353  vv = v
354  if frequency.has_key((uu, vv)):
355  frequency[(uu, vv)] = frequency[(uu, vv)] + len(value_cache[(u, v)])
356  else:
357  frequency[(uu, vv)] = len(value_cache[(u, v)])
358  for (u, v) in frequency.keys():
359  r = min(frequency[(u, v)] / 10.0, 1)
360  img[v, u] = min_color * (1 - r) + max_color * r
361  return img
362 
363 def main():
364  global ax, xs, ys, classifier, u_min, u_max, v_min, v_max, model, set_param
365  global width, height, pub_image, pub_error_plot
366  pub_image = rospy.Publisher("~frequency_image", Image, queue_size=1)
367  pub_error_plot = rospy.Publisher("~error_plot_image", Image, queue_size=1)
368  set_param = rospy.ServiceProxy("/camera_remote/depth_calibration/set_calibration_parameter",
369  SetDepthCalibrationParameter)
370  # parse argument
371  parser = argparse.ArgumentParser()
372  parser.add_argument('--csv')
373  parser.add_argument('--model', default="linear")
374  parser.add_argument('--models', action="store_true", default=False)
375  parser.add_argument("--width", default=640)
376  parser.add_argument("--height", default=480)
377  args = parser.parse_args(rospy.myargv()[1:])
378  width = args.width
379  height = args.height
380  if args.models:
381  for m in MODELS:
382  print(m)
383  return
384  model = args.model
385  if model not in MODELS:
386  raise Exception("Unknown Model: %s" % (model))
387 
388  if not args.csv:
389  sub = rospy.Subscriber("depth_image_error/output",
390  DepthErrorResult, callback)
391  #plt.ion()
392  fig = plt.figure()
393  ax = plt.axes([.12, .12, .8, .8])
394  classifier = linear_model.LinearRegression()
395  u_min = rospy.get_param("~u_min", 0)
396  u_max = rospy.get_param("~u_max", 4096)
397  v_min = rospy.get_param("~v_min", 0)
398  v_max = rospy.get_param("~v_max", 4096)
399 
400  if args.csv:
401  for row in csv.reader(open(args.csv, "rb")):
402  x = float(row[0])
403  y = float(row[1])
404  u = float(row[2])
405  v = float(row[3])
406  cu = float(row[4])
407  cv = float(row[5])
408  processData(x, y, u, v, cu, cv, fit = False)
409  classifier.fit(xs, ys)
410  try:
411  setParameter(classifier)
412  except rospy.service.ServiceException as e:
413  rospy.logfatal("failed to call service: %s" % (e.message))
414  try:
415  plt.show()
416  finally:
417  if not args.csv:
418  csv_filename = "calibration-%s.csv" % datetime.datetime.now().strftime('%Y-%m-%d-%H-%M-%S')
419  print("Save calibration parameters to %s" % (csv_filename))
420  with open(csv_filename, "w") as f:
421  for x, y, u, v, cu, cv in zip(raw_xs, ys, us, vs, c_us, c_vs):
422  f.write("%f,%f,%d,%d,%f,%f\n" % (x, y, u, v, cu, cv))
423  dump = rospy.get_param("~dump_result_into_yaml", "query")
424  if dump is True or \
425  (dump == "query" and query_yes_no("Dump result into yaml file?")):
426  yaml_filename = "calibration_parameter_%s.yaml" % datetime.datetime.now().strftime('%Y_%m_%d_%H_%M_%S')
427  print("writing to %s" % yaml_filename)
428  c = classifier.coef_
429  if model == "quadratic-uv-abs" or model == "quadratic-uv-quadratic-abs":
430  use_abs = "True"
431  else:
432  use_abs = "False"
433 
434  with open(yaml_filename, "w") as f:
435  f.write("""
436 coefficients2: [%s, %s, %s, %s, %s]
437 coefficients1: [%s, %s, %s, %s, %s]
438 coefficients0: [%s, %s, %s, %s, %s]
439 use_abs: %s
440  """ % (
441  repr(c[0]), repr(c[1]), repr(c[2]), repr(c[3]), repr(c[4]),
442  repr(c[5]), repr(c[6]), repr(c[7]), repr(c[8]), repr(c[9]),
443  repr(c[10]), repr(c[11]), repr(c[12]), repr(c[13]), repr(classifier.intercept_),
444  use_abs))
445 
446 if __name__ == "__main__":
447  rospy.init_node("depth_error_logistic_regression")
448  main()
449  rospy.spin()
double min(const OneDataStat &d)
wrapper function for min method for boost::function
Definition: one_data_stat.h:94
def applyModel(x, u, v, cu, cv, clssifier)
def uvQuadraticCoefString(c, absolute=False)
def uvCoefString(c, absolute=False)
def query_yes_no(question, default=None)
def processData(x, y, u, v, cu, cv, fit=True)
def genFeatureVector(x, u, v, cu, cv)


jsk_pcl_ros
Author(s): Yohei Kakiuchi
autogenerated on Mon May 3 2021 03:03:46