Advertisement
Guest User

Untitled

a guest
Jan 23rd, 2019
110
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 21.20 KB | None | 0 0
  1. #!/usr/bin/env python
  2.  
  3. """Python wrapper for C version of apriltags. This program creates two
  4. classes that are used to detect apriltags and extract information from
  5. them. Using this module, you can identify all apriltags visible in an
  6. image, and get information about the location and orientation of the
  7. tags.
  8.  
  9. Original author: Isaac Dulin, Spring 2016
  10. Updates: Matt Zucker, Fall 2016
  11.  
  12. """
  13. from __future__ import division
  14. from __future__ import print_function
  15.  
  16. import ctypes
  17. import collections
  18. import os
  19. import re
  20. import numpy
  21.  
  22. _HAVE_CV2 = False
  23.  
  24. if __name__ == '__main__':
  25. try:
  26. import cv2
  27. _HAVE_CV2 = True
  28. except:
  29. pass
  30.  
  31. ######################################################################
  32.  
  33. # pylint: disable=R0903
  34.  
  35. class _ImageU8(ctypes.Structure):
  36. '''Wraps image_u8 C struct.'''
  37. _fields_ = [
  38. ('width', ctypes.c_int),
  39. ('height', ctypes.c_int),
  40. ('stride', ctypes.c_int),
  41. ('buf', ctypes.POINTER(ctypes.c_uint8))
  42. ]
  43.  
  44. class _Matd(ctypes.Structure):
  45. '''Wraps matd C struct.'''
  46. _fields_ = [
  47. ('nrows', ctypes.c_int),
  48. ('ncols', ctypes.c_int),
  49. ('data', ctypes.c_double*1),
  50. ]
  51.  
  52. class _ZArray(ctypes.Structure):
  53. '''Wraps zarray C struct.'''
  54. _fields_ = [
  55. ('el_sz', ctypes.c_size_t),
  56. ('size', ctypes.c_int),
  57. ('alloc', ctypes.c_int),
  58. ('data', ctypes.c_void_p)
  59. ]
  60.  
  61. class _ApriltagFamily(ctypes.Structure):
  62. '''Wraps apriltag_family C struct.'''
  63. _fields_ = [
  64. ('ncodes', ctypes.c_int32),
  65. ('codes', ctypes.POINTER(ctypes.c_int64)),
  66. ('black_border', ctypes.c_int32),
  67. ('d', ctypes.c_int32),
  68. ('h', ctypes.c_int32),
  69. ('name', ctypes.c_char_p),
  70. ]
  71.  
  72. class _ApriltagDetection(ctypes.Structure):
  73. '''Wraps apriltag_detection C struct.'''
  74. _fields_ = [
  75. ('family', ctypes.POINTER(_ApriltagFamily)),
  76. ('id', ctypes.c_int),
  77. ('hamming', ctypes.c_int),
  78. ('goodness', ctypes.c_float),
  79. ('decision_margin', ctypes.c_float),
  80. ('H', ctypes.POINTER(_Matd)),
  81. ('c', ctypes.c_double*2),
  82. ('p', (ctypes.c_double*2)*4)
  83. ]
  84.  
  85. class _ApriltagDetector(ctypes.Structure):
  86. '''Wraps apriltag_detector C struct.'''
  87. _fields_ = [
  88. ('nthreads', ctypes.c_int),
  89. ('quad_decimate', ctypes.c_float),
  90. ('quad_sigma', ctypes.c_float),
  91. ('refine_edges', ctypes.c_int),
  92. ('refine_decode', ctypes.c_int),
  93. ('refine_pose', ctypes.c_int),
  94. ('debug', ctypes.c_int),
  95. ('quad_contours', ctypes.c_int),
  96. ]
  97.  
  98. ######################################################################
  99.  
  100. def _ptr_to_array2d(datatype, ptr, rows, cols):
  101. array_type = (datatype*cols)*rows
  102. array_buf = array_type.from_address(ctypes.addressof(ptr))
  103. return numpy.ctypeslib.as_array(array_buf, shape=(rows, cols))
  104.  
  105. def _image_u8_get_array(img_ptr):
  106. return _ptr_to_array2d(ctypes.c_uint8,
  107. img_ptr.contents.buf.contents,
  108. img_ptr.contents.height,
  109. img_ptr.contents.stride)
  110.  
  111. def _matd_get_array(mat_ptr):
  112. return _ptr_to_array2d(ctypes.c_double,
  113. mat_ptr.contents.data,
  114. int(mat_ptr.contents.nrows),
  115. int(mat_ptr.contents.ncols))
  116.  
  117.  
  118. ######################################################################
  119.  
  120. DetectionBase = collections.namedtuple(
  121. 'DetectionBase',
  122. 'tag_family, tag_id, hamming, goodness, decision_margin, '
  123. 'homography, center, corners')
  124.  
  125. class Detection(DetectionBase):
  126.  
  127. '''Pythonic wrapper for apriltag_detection which derives from named
  128. tuple class.
  129.  
  130. '''
  131.  
  132. _print_fields = [
  133. 'Family', 'ID', 'Hamming error', 'Goodness',
  134. 'Decision margin', 'Homography', 'Center', 'Corners'
  135. ]
  136.  
  137. _max_len = max(len(field) for field in _print_fields)
  138.  
  139. def tostring(self, values=None, indent=0):
  140.  
  141. '''Converts this object to a string with the given level of indentation.'''
  142.  
  143. rval = []
  144. indent_str = ' '*(self._max_len+2+indent)
  145.  
  146. if not values:
  147. values = collections.OrderedDict(zip(self._print_fields, self))
  148.  
  149. for label in values:
  150.  
  151. value_str = str(values[label])
  152.  
  153. if value_str.find('\n') > 0:
  154. value_str = value_str.split('\n')
  155. value_str = [value_str[0]] + [indent_str+v for v in value_str[1:]]
  156. value_str = '\n'.join(value_str)
  157.  
  158. rval.append('{:>{}s}: {}'.format(
  159. label, self._max_len+indent, value_str))
  160.  
  161. return '\n'.join(rval)
  162.  
  163. def __str__(self):
  164. return self.tostring().encode('ascii')
  165.  
  166. ######################################################################
  167.  
  168.  
  169. class DetectorOptions(object):
  170.  
  171. '''Convience wrapper for object to pass into Detector
  172. initializer. You can also pass in the output of an
  173. argparse.ArgumentParser on which you have called add_arguments.
  174.  
  175. '''
  176.  
  177. # pylint: disable=R0902
  178. # pylint: disable=R0913
  179.  
  180. def __init__(self,
  181. families='tag36h11',
  182. border=1,
  183. nthreads=4,
  184. quad_decimate=1.0,
  185. quad_blur=0.0,
  186. refine_edges=True,
  187. refine_decode=False,
  188. refine_pose=False,
  189. debug=False,
  190. quad_contours=True):
  191.  
  192. self.families = families
  193. self.border = int(border)
  194.  
  195. self.nthreads = int(nthreads)
  196. self.quad_decimate = float(quad_decimate)
  197. self.quad_sigma = float(quad_blur)
  198. self.refine_edges = int(refine_edges)
  199. self.refine_decode = int(refine_decode)
  200. self.refine_pose = int(refine_pose)
  201. self.debug = int(debug)
  202. self.quad_contours = quad_contours
  203.  
  204. ######################################################################
  205.  
  206. def add_arguments(parser):
  207.  
  208. '''Add arguments to the given argparse.ArgumentParser object to enable
  209. passing in the resulting parsed arguments into the initializer for
  210. Detector.
  211.  
  212. '''
  213.  
  214. defaults = DetectorOptions()
  215.  
  216. show_default = ' (default %(default)s)'
  217.  
  218. parser.add_argument('-f', metavar='FAMILIES',
  219. dest='families', default=defaults.families,
  220. help='Tag families' + show_default)
  221.  
  222. parser.add_argument('-B', metavar='N',
  223. dest='border', type=int, default=defaults.border,
  224. help='Tag border size in pixels' + show_default)
  225.  
  226. parser.add_argument('-t', metavar='N',
  227. dest='nthreads', type=int, default=defaults.nthreads,
  228. help='Number of threads' + show_default)
  229.  
  230. parser.add_argument('-x', metavar='SCALE',
  231. dest='quad_decimate', type=float,
  232. default=defaults.quad_decimate,
  233. help='Quad decimation factor' + show_default)
  234.  
  235. parser.add_argument('-b', metavar='SIGMA',
  236. dest='quad_sigma', type=float, default=defaults.quad_sigma,
  237. help='Apply low-pass blur to input' + show_default)
  238.  
  239. parser.add_argument('-0', dest='refine_edges', default=True,
  240. action='store_false',
  241. help='Spend less time trying to align edges of tags')
  242.  
  243. parser.add_argument('-1', dest='refine_decode', default=False,
  244. action='store_true',
  245. help='Spend more time trying to decode tags')
  246.  
  247. parser.add_argument('-2', dest='refine_pose', default=False,
  248. action='store_true',
  249. help='Spend more time trying to precisely localize tags')
  250.  
  251. parser.add_argument('-c', dest='quad_contours', default=False,
  252. action='store_true',
  253. help='Use new contour-based quad detection')
  254.  
  255.  
  256. ######################################################################
  257.  
  258. class Detector(object):
  259.  
  260. '''Pythonic wrapper for apriltag_detector. Initialize by passing in
  261. the output of an argparse.ArgumentParser on which you have called
  262. add_arguments; or an instance of the DetectorOptions class. You can
  263. also optionally pass in a list of paths to search for the C dynamic
  264. library used by ctypes.
  265.  
  266. '''
  267.  
  268. def __init__(self, options=None, searchpath=[]):
  269.  
  270. if options is None:
  271. options = DetectorOptions()
  272.  
  273. self.options = options
  274.  
  275. # detect OS to get extension for DLL
  276. uname0 = os.uname()[0]
  277. if uname0 == 'Darwin':
  278. extension = '.dylib'
  279. else:
  280. extension = '.so' # TODO test on windows?
  281.  
  282. filename = 'libapriltag'+extension
  283.  
  284. self.libc = None
  285. self.tag_detector = None
  286.  
  287. for path in searchpath:
  288. relpath = os.path.join(path, filename)
  289. if os.path.exists(relpath):
  290. self.libc = ctypes.CDLL(relpath)
  291. break
  292.  
  293. # if full path not found just try opening the raw filename;
  294. # this should search whatever paths dlopen is supposed to
  295. # search.
  296. if self.libc is None:
  297. self.libc = ctypes.CDLL(filename)
  298.  
  299. if self.libc is None:
  300. raise RuntimeError('could not find DLL named ' + filename)
  301.  
  302. # declare return types of libc function
  303. self._declare_return_types()
  304.  
  305. # create the c-_apriltag_detector object
  306. self.tag_detector = self.libc.apriltag_detector_create()
  307. self.tag_detector.contents.nthreads = int(options.nthreads)
  308. self.tag_detector.contents.quad_decimate = float(options.quad_decimate)
  309. self.tag_detector.contents.quad_sigma = float(options.quad_sigma)
  310. self.tag_detector.refine_edges = int(options.refine_edges)
  311. self.tag_detector.refine_decode = int(options.refine_decode)
  312. self.tag_detector.refine_pose = int(options.refine_pose)
  313.  
  314. if options.quad_contours:
  315. self.libc.apriltag_detector_enable_quad_contours(self.tag_detector, 1)
  316.  
  317. self.families = []
  318.  
  319. flist = self.libc.apriltag_family_list()
  320.  
  321. for i in range(flist.contents.size):
  322. ptr = ctypes.c_char_p()
  323. self.libc.zarray_get(flist, i, ctypes.byref(ptr))
  324. self.families.append(ctypes.string_at(ptr))
  325.  
  326. self.libc.apriltag_family_list_destroy(flist)
  327.  
  328. if options.families == 'all':
  329. families_list = self.families
  330. elif isinstance(options.families, list):
  331. families_list = options.families
  332. else:
  333. families_list = [n for n in re.split(r'\W+', options.families) if n]
  334.  
  335. # add tags
  336. for family in families_list:
  337. self.add_tag_family(family)
  338.  
  339. def __del__(self):
  340. if self.tag_detector is not None:
  341. self.libc.apriltag_detector_destroy(self.tag_detector)
  342.  
  343. def detect(self, img, return_image=False):
  344.  
  345. '''Run detectons on the provided image. The image must be a grayscale
  346. image of type numpy.uint8.'''
  347.  
  348. assert len(img.shape) == 2
  349. assert img.dtype == numpy.uint8
  350.  
  351. c_img = self._convert_image(img)
  352.  
  353. return_info = []
  354.  
  355. #detect apriltags in the image
  356. detections = self.libc.apriltag_detector_detect(self.tag_detector, c_img)
  357.  
  358. apriltag = ctypes.POINTER(_ApriltagDetection)()
  359.  
  360. for i in range(0, detections.contents.size):
  361.  
  362. #extract the data for each apriltag that was identified
  363. self.libc.zarray_get(detections, i, ctypes.byref(apriltag))
  364.  
  365. tag = apriltag.contents
  366.  
  367. homography = _matd_get_array(tag.H).copy()
  368. center = numpy.ctypeslib.as_array(tag.c, shape=(2,)).copy()
  369. corners = numpy.ctypeslib.as_array(tag.p, shape=(4, 2)).copy()
  370.  
  371. detection = Detection(
  372. ctypes.string_at(tag.family.contents.name),
  373. tag.id,
  374. tag.hamming,
  375. tag.goodness,
  376. tag.decision_margin,
  377. homography,
  378. center,
  379. corners)
  380.  
  381. #Append this dict to the tag data array
  382. return_info.append(detection)
  383.  
  384. self.libc.image_u8_destroy(c_img)
  385.  
  386. if return_image:
  387.  
  388. dimg = self._vis_detections(img.shape, detections)
  389. rval = return_info, dimg
  390.  
  391. else:
  392.  
  393. rval = return_info
  394.  
  395. self.libc.apriltag_detections_destroy(detections)
  396.  
  397. return rval
  398.  
  399.  
  400. def add_tag_family(self, name):
  401.  
  402. '''Add a single tag family to this detector.'''
  403.  
  404. family = self.libc.apriltag_family_create(name.encode('ascii'))
  405.  
  406. if family:
  407. family.contents.border = self.options.border
  408. self.libc.apriltag_detector_add_family(self.tag_detector, family)
  409. else:
  410. print('Unrecognized tag family name. Try e.g. tag36h11')
  411.  
  412. def detection_pose(self, detection, camera_params, tag_size=1, z_sign=1):
  413.  
  414. fx, fy, cx, cy = [ ctypes.c_double(c) for c in camera_params ]
  415.  
  416. H = self.libc.matd_create(3, 3)
  417. arr = _matd_get_array(H)
  418. arr[:] = detection.homography
  419. corners = detection.corners.flatten().astype(numpy.float64)
  420.  
  421. dptr = ctypes.POINTER(ctypes.c_double)
  422.  
  423. corners = corners.ctypes.data_as(dptr)
  424.  
  425. init_error = ctypes.c_double(0)
  426. final_error = ctypes.c_double(0)
  427.  
  428. Mptr = self.libc.pose_from_homography(H, fx, fy, cx, cy,
  429. ctypes.c_double(tag_size),
  430. ctypes.c_double(z_sign),
  431. corners,
  432. dptr(init_error),
  433. dptr(final_error))
  434.  
  435. M = _matd_get_array(Mptr).copy()
  436. self.libc.matd_destroy(H)
  437. self.libc.matd_destroy(Mptr)
  438.  
  439. return M, init_error.value, final_error.value
  440.  
  441. def _vis_detections(self, shape, detections):
  442.  
  443. height, width = shape
  444. c_dimg = self.libc.image_u8_create(width, height)
  445. self.libc.apriltag_vis_detections(detections, c_dimg)
  446. tmp = _image_u8_get_array(c_dimg)
  447.  
  448. rval = tmp[:, :width].copy()
  449.  
  450. self.libc.image_u8_destroy(c_dimg)
  451.  
  452. return rval
  453.  
  454. def _declare_return_types(self):
  455.  
  456. self.libc.apriltag_detector_create.restype = ctypes.POINTER(_ApriltagDetector)
  457. self.libc.apriltag_family_create.restype = ctypes.POINTER(_ApriltagFamily)
  458. self.libc.apriltag_detector_detect.restype = ctypes.POINTER(_ZArray)
  459. self.libc.image_u8_create.restype = ctypes.POINTER(_ImageU8)
  460. self.libc.image_u8_write_pnm.restype = ctypes.c_int
  461. self.libc.apriltag_family_list.restype = ctypes.POINTER(_ZArray)
  462. self.libc.apriltag_vis_detections.restype = None
  463.  
  464. self.libc.pose_from_homography.restype = ctypes.POINTER(_Matd)
  465. self.libc.matd_create.restype = ctypes.POINTER(_Matd)
  466.  
  467. def _convert_image(self, img):
  468.  
  469. height = img.shape[0]
  470. width = img.shape[1]
  471. c_img = self.libc.image_u8_create(width, height)
  472.  
  473. tmp = _image_u8_get_array(c_img)
  474.  
  475. # copy the opencv image into the destination array, accounting for the
  476. # difference between stride & width.
  477. tmp[:, :width] = img
  478.  
  479. # tmp goes out of scope here but we don't care because
  480. # the underlying data is still in c_img.
  481. return c_img
  482.  
  483. ######################################################################
  484.  
  485. def _get_demo_searchpath():
  486.  
  487. return [
  488. os.path.join(os.path.dirname(__file__), '../build/lib'),
  489. os.path.join(os.getcwd(), '../build/lib')
  490. ]
  491.  
  492. ######################################################################
  493.  
  494. def _camera_params(pstr):
  495.  
  496. pstr = pstr.strip()
  497.  
  498. if pstr[0] == '(' and pstr[-1] == ')':
  499. pstr = pstr[1:-1]
  500.  
  501. params = tuple( [ float(param.strip()) for param in pstr.split(',') ] )
  502.  
  503. assert( len(params) == 4)
  504.  
  505. return params
  506.  
  507. ######################################################################
  508.  
  509. def _draw_pose(overlay, camera_params, tag_size, pose, z_sign=1):
  510.  
  511. opoints = numpy.array([
  512. -1, -1, 0,
  513. 1, -1, 0,
  514. 1, 1, 0,
  515. -1, 1, 0,
  516. -1, -1, -2*z_sign,
  517. 1, -1, -2*z_sign,
  518. 1, 1, -2*z_sign,
  519. -1, 1, -2*z_sign,
  520. ]).reshape(-1, 1, 3) * 0.5*tag_size
  521.  
  522. edges = numpy.array([
  523. 0, 1,
  524. 1, 2,
  525. 2, 3,
  526. 3, 0,
  527. 0, 4,
  528. 1, 5,
  529. 2, 6,
  530. 3, 7,
  531. 4, 5,
  532. 5, 6,
  533. 6, 7,
  534. 7, 4
  535. ]).reshape(-1, 2)
  536.  
  537. fx, fy, cx, cy = camera_params
  538.  
  539. K = numpy.array([fx, 0, cx, 0, fy, cy, 0, 0, 1]).reshape(3, 3)
  540.  
  541. rvec, _ = cv2.Rodrigues(pose[:3,:3])
  542. tvec = pose[:3, 3]
  543.  
  544. dcoeffs = numpy.zeros(5)
  545.  
  546. ipoints, _ = cv2.projectPoints(opoints, rvec, tvec, K, dcoeffs)
  547.  
  548. ipoints = numpy.round(ipoints).astype(int)
  549.  
  550. ipoints = [tuple(pt) for pt in ipoints.reshape(-1, 2)]
  551.  
  552. for i, j in edges:
  553. cv2.line(overlay, ipoints[i], ipoints[j], (0, 255, 0), 1, 16)
  554.  
  555.  
  556.  
  557. ######################################################################
  558.  
  559. def main():
  560.  
  561. '''Test function for this Python wrapper.'''
  562.  
  563. from argparse import ArgumentParser
  564.  
  565. # for some reason pylint complains about members being undefined :(
  566. # pylint: disable=E1101
  567.  
  568. parser = ArgumentParser(
  569. description='test apriltag Python bindings')
  570.  
  571. parser.add_argument('filenames', metavar='IMAGE', nargs='+',
  572. help='files to scan')
  573.  
  574. parser.add_argument('-n', '--no-gui', action='store_true',
  575. help='suppress OpenCV gui')
  576.  
  577. parser.add_argument('-d', '--debug-images', action='store_true',
  578. help='output debug detection image')
  579.  
  580. parser.add_argument('-k', '--camera-params', type=_camera_params,
  581. default=None,
  582. help='intrinsic parameters for camera (in the form fx,fy,cx,cy)')
  583.  
  584. parser.add_argument('-s', '--tag-size', type=float,
  585. default=1.0,
  586. help='tag size in user-specified units (default=1.0)')
  587.  
  588. add_arguments(parser)
  589.  
  590. options = parser.parse_args()
  591.  
  592. # set up a reasonable search path for the apriltag DLL inside the
  593. # github repo this file lives in;
  594. #
  595. # for "real" deployments, either install the DLL in the appropriate
  596. # system-wide library directory, or specify your own search paths
  597. # as needed.
  598.  
  599. det = Detector(options, searchpath=_get_demo_searchpath())
  600.  
  601. use_gui = not options.no_gui
  602.  
  603. if use_gui and not _HAVE_CV2:
  604. use_gui = False
  605. print('suppressing GUI because cv2 module not found')
  606.  
  607. if not _HAVE_CV2:
  608. from PIL import Image
  609.  
  610. for filename in options.filenames:
  611.  
  612. if _HAVE_CV2:
  613. orig = cv2.imread(filename)
  614. if len(orig.shape) == 3:
  615. gray = cv2.cvtColor(orig, cv2.COLOR_RGB2GRAY)
  616. else:
  617. gray = orig
  618. else:
  619. pil_image = Image.open(filename)
  620. orig = numpy.array(pil_image)
  621. gray = numpy.array(pil_image.convert('L'))
  622.  
  623. detections, dimg = det.detect(gray, return_image=True)
  624.  
  625. if len(orig.shape) == 3:
  626. overlay = orig // 2 + dimg[:, :, None] // 2
  627. else:
  628. overlay = gray // 2 + dimg // 2
  629.  
  630. num_detections = len(detections)
  631. print('Detected {} tags in {}\n'.format(
  632. num_detections, os.path.split(filename)[1]))
  633.  
  634. for i, detection in enumerate(detections):
  635. print( 'Detection {} of {}:'.format(i+1, num_detections))
  636. print()
  637. print(detection.tostring(indent=2))
  638.  
  639. if options.camera_params is not None:
  640.  
  641. pose, e0, e1 = det.detection_pose(detection,
  642. options.camera_params,
  643. options.tag_size)
  644.  
  645. if _HAVE_CV2:
  646. _draw_pose(overlay,
  647. options.camera_params,
  648. options.tag_size,
  649. pose)
  650.  
  651. print(detection.tostring(
  652. collections.OrderedDict([('Pose',pose),
  653. ('InitError', e0),
  654. ('FinalError', e1)]),
  655. indent=2))
  656.  
  657. print()
  658.  
  659.  
  660. if options.debug_images:
  661. if _HAVE_CV2:
  662. cv2.imwrite('detections.png', overlay)
  663. else:
  664. output = Image.fromarray(overlay)
  665. output.save('detections.png')
  666.  
  667. if use_gui:
  668. cv2.imshow('win', overlay)
  669. while cv2.waitKey(5) < 0:
  670. pass
  671.  
  672.  
  673. if __name__ == '__main__':
  674. main()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement