pyapriltags 3.3.0.3__py3-none-win32.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,6 @@
1
+ from .apriltags import Detection, Detector
2
+
3
+ __all__ = [
4
+ 'Detector',
5
+ 'Detection',
6
+ ]
@@ -0,0 +1,458 @@
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
+ Apriltags 3 version: Aleksandar Petrov, Spring 2019
12
+ Current maintainer: Will Barber
13
+
14
+ """
15
+ import ctypes
16
+ import os
17
+ import sys
18
+ from typing import Any, Dict, List, NamedTuple, Optional, Tuple, Union
19
+
20
+ import numpy
21
+
22
+ dir_path = os.path.dirname(os.path.realpath(__file__))
23
+
24
+
25
+ ######################################################################
26
+
27
+ # pylint: disable=R0903
28
+
29
+ class _ImageU8(ctypes.Structure):
30
+ """Wraps image_u8 C struct."""
31
+ _fields_ = [
32
+ ('width', ctypes.c_int),
33
+ ('height', ctypes.c_int),
34
+ ('stride', ctypes.c_int),
35
+ ('buf', ctypes.POINTER(ctypes.c_uint8))
36
+ ]
37
+
38
+
39
+ class _Matd(ctypes.Structure):
40
+ """Wraps matd C struct."""
41
+ _fields_ = [
42
+ ('nrows', ctypes.c_int),
43
+ ('ncols', ctypes.c_int),
44
+ ('data', ctypes.c_double * 1),
45
+ ]
46
+
47
+
48
+ class _ZArray(ctypes.Structure):
49
+ """Wraps zarray C struct."""
50
+ _fields_ = [
51
+ ('el_sz', ctypes.c_size_t),
52
+ ('size', ctypes.c_int),
53
+ ('alloc', ctypes.c_int),
54
+ ('data', ctypes.c_void_p)
55
+ ]
56
+
57
+
58
+ class _ApriltagFamily(ctypes.Structure):
59
+ """Wraps apriltag_family C struct."""
60
+ _fields_ = [
61
+ ('ncodes', ctypes.c_uint32),
62
+ ('codes', ctypes.POINTER(ctypes.c_uint64)),
63
+ ('width_at_border', ctypes.c_int),
64
+ ('total_width', ctypes.c_int),
65
+ ('reversed_border', ctypes.c_bool),
66
+ ('nbits', ctypes.c_uint32),
67
+ ('bit_x', ctypes.POINTER(ctypes.c_int32)),
68
+ ('bit_y', ctypes.POINTER(ctypes.c_int32)),
69
+ ('h', ctypes.c_int32),
70
+ ('name', ctypes.c_char_p),
71
+ ]
72
+
73
+
74
+ class _ApriltagDetection(ctypes.Structure):
75
+ """Wraps apriltag_detection C struct."""
76
+ _fields_ = [
77
+ ('family', ctypes.POINTER(_ApriltagFamily)),
78
+ ('id', ctypes.c_int),
79
+ ('hamming', ctypes.c_int),
80
+ ('decision_margin', ctypes.c_float),
81
+ ('H', ctypes.POINTER(_Matd)),
82
+ ('c', ctypes.c_double * 2),
83
+ ('p', (ctypes.c_double * 2) * 4)
84
+ ]
85
+
86
+
87
+ class _ApriltagDetector(ctypes.Structure):
88
+ """Wraps apriltag_detector C struct."""
89
+ _fields_ = [
90
+ ('nthreads', ctypes.c_int),
91
+ ('quad_decimate', ctypes.c_float),
92
+ ('quad_sigma', ctypes.c_float),
93
+ ('refine_edges', ctypes.c_int),
94
+ ('decode_sharpening', ctypes.c_double),
95
+ ('debug', ctypes.c_int)
96
+ ]
97
+
98
+
99
+ class _ApriltagDetectionInfo(ctypes.Structure):
100
+ """Wraps apriltag_detection_info C struct."""
101
+ _fields_ = [
102
+ ('det', ctypes.POINTER(_ApriltagDetection)),
103
+ ('tagsize', ctypes.c_double),
104
+ ('fx', ctypes.c_double),
105
+ ('fy', ctypes.c_double),
106
+ ('cx', ctypes.c_double),
107
+ ('cy', ctypes.c_double)
108
+ ]
109
+
110
+
111
+ class _ApriltagPose(ctypes.Structure):
112
+ """Wraps apriltag_pose C struct."""
113
+ _fields_ = [
114
+ ('R', ctypes.POINTER(_Matd)),
115
+ ('t', ctypes.POINTER(_Matd))
116
+ ]
117
+
118
+
119
+ ######################################################################
120
+
121
+ def _ptr_to_array2d(datatype, ptr, rows, cols):
122
+ array_type = (datatype * cols) * rows
123
+ array_buf = array_type.from_address(ctypes.addressof(ptr))
124
+ return numpy.ctypeslib.as_array(array_buf, shape=(rows, cols))
125
+
126
+
127
+ def _image_u8_get_array(img_ptr):
128
+ return _ptr_to_array2d(ctypes.c_uint8,
129
+ img_ptr.contents.buf.contents,
130
+ img_ptr.contents.height,
131
+ img_ptr.contents.stride)
132
+
133
+
134
+ def _matd_get_array(mat_ptr):
135
+ return _ptr_to_array2d(ctypes.c_double,
136
+ mat_ptr.contents.data,
137
+ int(mat_ptr.contents.nrows),
138
+ int(mat_ptr.contents.ncols))
139
+
140
+
141
+ def zarray_get(za, idx, ptr):
142
+ # memcpy(p, &za->data[idx*za->el_sz], za->el_sz);
143
+ #
144
+ # p = ptr
145
+ # za->el_sz = za.contents.el_sz
146
+ # &za->data[idx*za->el_sz] = za.contents.data+idx*za.contents.el_sz
147
+
148
+ ctypes.memmove(ptr, za.contents.data + idx * za.contents.el_sz, za.contents.el_sz)
149
+
150
+
151
+ ######################################################################
152
+
153
+ class Detection(NamedTuple):
154
+ """
155
+ Combined pythonic wrapper for apriltag_detection and apriltag_pose
156
+ """
157
+ tag_family: bytes
158
+ tag_id: int
159
+ hamming: int
160
+ decision_margin: float
161
+ homography: numpy.ndarray
162
+ center: numpy.ndarray
163
+ corners: numpy.ndarray
164
+ pose_R: Optional[numpy.ndarray] = None
165
+ pose_t: Optional[numpy.ndarray] = None
166
+ pose_err: Optional[float] = None
167
+ tag_size: Optional[float] = None
168
+
169
+ def __str__(self):
170
+ return ('Detection object:' +
171
+ '\ntag_family = ' + str(self.tag_family) +
172
+ '\ntag_id = ' + str(self.tag_id) +
173
+ '\ntag_size = ' + str(self.tag_size) +
174
+ '\nhamming = ' + str(self.hamming) +
175
+ '\ndecision_margin = ' + str(self.decision_margin) +
176
+ '\nhomography = ' + str(self.homography) +
177
+ '\ncenter = ' + str(self.center) +
178
+ '\ncorners = ' + str(self.corners) +
179
+ '\npose_R = ' + str(self.pose_R) +
180
+ '\npose_t = ' + str(self.pose_t) +
181
+ '\npose_err = ' + str(self.pose_err) + '\n')
182
+
183
+ def __repr__(self):
184
+ return self.__str__()
185
+
186
+
187
+ ######################################################################
188
+
189
+ class Detector(object):
190
+ """
191
+ Pythonic wrapper for apriltag_detector.
192
+
193
+ families: Tag families, separated with a space, default: tag36h11
194
+ nthreads: Number of threads, default: 1
195
+ quad_decimate: Detection of quads can be done on a lower-resolution image, improving
196
+ speed at a cost of pose accuracy and a slight decrease in detection
197
+ rate. Decoding the binary payload is still done at full resolution,
198
+ default: 2.0
199
+ quad_sigma: What Gaussian blur should be applied to the segmented image (used for
200
+ quad detection?) Parameter is the standard deviation in pixels.
201
+ Very noisy images benefit from non-zero values (e.g. 0.8),
202
+ default: 0.0
203
+ refine_edges: When non-zero, the edges of the each quad are adjusted to "snap to"
204
+ strong gradients nearby. This is useful when decimation is employed,
205
+ as it can increase the quality of the initial quad estimate
206
+ substantially. Generally recommended to be on (1). Very
207
+ computationally inexpensive. Option is ignored if quad_decimate = 1,
208
+ default: 1
209
+ decode_sharpening: How much sharpening should be done to decoded images? This can help
210
+ decode small tags but may or may not help in odd lighting conditions
211
+ or low light conditions, default = 0.25
212
+ searchpath: Where to look for the Apriltag 3 library, must be a list,
213
+ default: ['apriltags']
214
+ debug: If 1, will save debug images. Runs very slow, default: 0
215
+ """
216
+
217
+ _SUPPORTED_FAMILIES = (
218
+ 'tag16h5',
219
+ 'tag25h9',
220
+ 'tag36h11',
221
+ 'tagCircle21h7',
222
+ 'tagCircle49h12',
223
+ 'tagCustom48h12',
224
+ 'tagStandard41h12',
225
+ 'tagStandard52h13',
226
+ )
227
+
228
+ def __init__(self,
229
+ families: str = 'tag36h11',
230
+ nthreads: int = 1,
231
+ quad_decimate: float = 2.0,
232
+ quad_sigma: float = 0.0,
233
+ refine_edges: int = 1,
234
+ decode_sharpening: float = 0.25,
235
+ debug: int = 0,
236
+ searchpath: List[str] = ['apriltags', '.', dir_path]):
237
+
238
+ # Parse the parameters
239
+ self.params: Dict[str, Any] = dict()
240
+ self.params['families'] = families.split()
241
+ self.params['nthreads'] = nthreads
242
+ self.params['quad_decimate'] = quad_decimate
243
+ self.params['quad_sigma'] = quad_sigma
244
+ self.params['refine_edges'] = refine_edges
245
+ self.params['decode_sharpening'] = decode_sharpening
246
+ self.params['debug'] = debug
247
+
248
+ # detect OS to get extension for DLL
249
+ platform = sys.platform
250
+ if platform.startswith('linux'):
251
+ extension = '.so'
252
+ elif platform.startswith('darwin'):
253
+ extension = '.dylib'
254
+ elif platform == 'win32':
255
+ extension = '.dll'
256
+ else:
257
+ raise NotImplementedError(f"The platform {platform} is not supported")
258
+
259
+ filename = 'libapriltag' + extension
260
+
261
+ self.libc = None
262
+ self.tag_detector = None
263
+ self.tag_detector_ptr = None
264
+
265
+ for path in searchpath:
266
+ relpath = os.path.join(os.path.dirname(__file__), path, filename)
267
+ if os.path.exists(relpath):
268
+ self.libc = ctypes.CDLL(relpath)
269
+ break
270
+
271
+ # if full path not found just try opening the raw filename;
272
+ # this should search whatever paths dlopen is supposed to
273
+ # search.
274
+ if self.libc is None:
275
+ self.libc = ctypes.CDLL(os.path.join(os.path.dirname(__file__), filename))
276
+
277
+ if self.libc is None:
278
+ raise RuntimeError('could not find DLL named ' + filename)
279
+
280
+ # setup the return types for all the functions used from the DLL
281
+ self._setup_restype()
282
+
283
+ # create the c-_apriltag_detector object
284
+ self.tag_detector_ptr = self.libc.apriltag_detector_create()
285
+
286
+ # create the family
287
+ self.tag_families = dict()
288
+ for family in self.params['families']:
289
+ if family in self._SUPPORTED_FAMILIES:
290
+ # Call the family's create method
291
+ self.tag_families[family] = getattr(self.libc, f'{family}_create')()
292
+ self.libc.apriltag_detector_add_family_bits(
293
+ self.tag_detector_ptr,
294
+ self.tag_families[family],
295
+ 2,
296
+ )
297
+ else:
298
+ raise Exception(
299
+ 'Unrecognized tag family name. Use e.g. \'tag36h11\'.\n')
300
+
301
+ # configure the parameters of the detector
302
+ self.tag_detector_ptr.contents.nthreads = int(self.params['nthreads'])
303
+ self.tag_detector_ptr.contents.quad_decimate = float(self.params['quad_decimate'])
304
+ self.tag_detector_ptr.contents.quad_sigma = float(self.params['quad_sigma'])
305
+ self.tag_detector_ptr.contents.refine_edges = int(self.params['refine_edges'])
306
+ self.tag_detector_ptr.contents.decode_sharpening = int(self.params['decode_sharpening']) # noqa: E501
307
+ self.tag_detector_ptr.contents.debug = int(self.params['debug'])
308
+
309
+ def _setup_restype(self):
310
+ """Setup the return types for all the functions used from the DLL."""
311
+ # Functions used in __init__
312
+ self.libc.apriltag_detector_create.restype = ctypes.POINTER(_ApriltagDetector)
313
+ self.libc.apriltag_detector_add_family_bits.restype = None
314
+ self.libc.apriltag_detector_destroy.restype = None
315
+
316
+ # Tag family constructors and destructors
317
+ for family in self._SUPPORTED_FAMILIES:
318
+ getattr(self.libc, f'{family}_create').restype = ctypes.POINTER(_ApriltagFamily)
319
+ getattr(self.libc, f'{family}_destroy').restype = None
320
+
321
+ # Functions used by detect()
322
+ self.libc.apriltag_detector_detect.restype = ctypes.POINTER(_ZArray)
323
+ self.libc.estimate_tag_pose.restype = ctypes.c_double
324
+ self.libc.matd_destroy.restype = None
325
+ self.libc.matd_destroy.restype = None
326
+ self.libc.image_u8_destroy.restype = None
327
+ self.libc.apriltag_detections_destroy.restype = None
328
+ self.libc.image_u8_create.restype = ctypes.POINTER(_ImageU8)
329
+
330
+ def __del__(self):
331
+ if self.tag_detector_ptr is not None:
332
+ # destroy the detector
333
+ self.libc.apriltag_detector_destroy(self.tag_detector_ptr)
334
+
335
+ # destroy the tag families
336
+ for family, tf in self.tag_families.items():
337
+ if family in self._SUPPORTED_FAMILIES:
338
+ # Call the family's destroy method
339
+ getattr(self.libc, f'{family}_destroy')(tf)
340
+
341
+ def detect(
342
+ self, img: numpy.ndarray, estimate_tag_pose: bool = False,
343
+ camera_params: Union[numpy.ndarray, Tuple[float, float, float, float], None] = None,
344
+ tag_size: Union[float, None, Dict[int, float]] = None,
345
+ ) -> List[Detection]:
346
+ """
347
+ Run detectons on the provided image. The image must be a grayscale
348
+ image of type numpy.uint8.
349
+ """
350
+ assert len(img.shape) == 2
351
+ assert img.dtype == numpy.uint8
352
+
353
+ c_img = self._convert_image(img)
354
+
355
+ return_info = []
356
+
357
+ if self.libc is None:
358
+ raise RuntimeError('No DLL found')
359
+
360
+ # detect apriltags in the image
361
+ detections = self.libc.apriltag_detector_detect(self.tag_detector_ptr, c_img)
362
+
363
+ apriltag = ctypes.POINTER(_ApriltagDetection)()
364
+
365
+ for i in range(0, detections.contents.size):
366
+
367
+ # extract the data for each apriltag that was identified
368
+ zarray_get(detections, i, ctypes.byref(apriltag))
369
+
370
+ tag = apriltag.contents
371
+
372
+ homography = _matd_get_array(tag.H).copy()
373
+ center = numpy.ctypeslib.as_array(tag.c, shape=(2,)).copy()
374
+ corners = numpy.ctypeslib.as_array(tag.p, shape=(4, 2)).copy()
375
+
376
+ if estimate_tag_pose:
377
+ if camera_params is None:
378
+ raise Exception(
379
+ 'camera_params must be provided to detect if estimate_tag_pose is '
380
+ 'set to True')
381
+ if tag_size is None:
382
+ raise Exception(
383
+ 'tag_size must be provided to detect if estimate_tag_pose is set to '
384
+ 'True')
385
+
386
+ if isinstance(tag_size, dict):
387
+ individual_tag_size = tag_size.get(tag.id, 0)
388
+ else:
389
+ individual_tag_size = tag_size
390
+
391
+ if individual_tag_size != 0:
392
+ camera_fx, camera_fy, camera_cx, camera_cy = [c for c in camera_params]
393
+
394
+ info = _ApriltagDetectionInfo(det=apriltag,
395
+ tagsize=individual_tag_size,
396
+ fx=camera_fx,
397
+ fy=camera_fy,
398
+ cx=camera_cx,
399
+ cy=camera_cy)
400
+ pose = _ApriltagPose()
401
+
402
+ err = self.libc.estimate_tag_pose(ctypes.byref(info), ctypes.byref(pose))
403
+
404
+ pose_R = _matd_get_array(pose.R).copy()
405
+ pose_t = _matd_get_array(pose.t).copy()
406
+ pose_err = err
407
+
408
+ self.libc.matd_destroy(pose.R)
409
+
410
+ self.libc.matd_destroy(pose.t)
411
+ else:
412
+ pose_R = None
413
+ pose_t = None
414
+ pose_err = None
415
+ else:
416
+ pose_R = None
417
+ pose_t = None
418
+ pose_err = None
419
+ individual_tag_size = None
420
+
421
+ detection = Detection(
422
+ tag_family=ctypes.string_at(tag.family.contents.name),
423
+ tag_id=tag.id,
424
+ hamming=tag.hamming,
425
+ decision_margin=tag.decision_margin,
426
+ homography=homography,
427
+ center=center,
428
+ corners=corners,
429
+ pose_R=pose_R,
430
+ pose_t=pose_t,
431
+ pose_err=pose_err,
432
+ tag_size=individual_tag_size,
433
+ )
434
+
435
+ # append this dict to the tag data array
436
+ return_info.append(detection)
437
+
438
+ self.libc.image_u8_destroy(c_img)
439
+
440
+ self.libc.apriltag_detections_destroy(detections)
441
+
442
+ return return_info
443
+
444
+ def _convert_image(self, img):
445
+ height = img.shape[0]
446
+ width = img.shape[1]
447
+
448
+ c_img = self.libc.image_u8_create(width, height)
449
+
450
+ tmp = _image_u8_get_array(c_img)
451
+
452
+ # copy the opencv image into the destination array, accounting for the
453
+ # difference between stride & width.
454
+ tmp[:, :width] = img
455
+
456
+ # tmp goes out of scope here but we don't care because
457
+ # the underlying data is still in c_img.
458
+ return c_img
Binary file
pyapriltags/py.typed ADDED
@@ -0,0 +1 @@
1
+ # Marker file for PEP 561. pyapriltags uses inline type hints. See PEP 484 for more information.
@@ -0,0 +1,59 @@
1
+ Original Apriltag code is:
2
+
3
+ (C) 2013-2015, The Regents of The University of Michigan
4
+
5
+ All rights reserved.
6
+
7
+ This software may be available under alternative licensing
8
+ terms. Contact Edwin Olson, ebolson@umich.edu, for more information.
9
+
10
+ Redistribution and use in source and binary forms, with or without
11
+ modification, are permitted provided that the following conditions are met:
12
+
13
+ 1. Redistributions of source code must retain the above copyright notice, this
14
+ list of conditions and the following disclaimer.
15
+
16
+ 2. Redistributions in binary form must reproduce the above copyright notice,
17
+ this list of conditions and the following disclaimer in the documentation
18
+ and/or other materials provided with the distribution.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
21
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
24
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
26
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
27
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
+ The views and conclusions contained in the software and documentation are those
31
+ of the authors and should not be interpreted as representing official policies,
32
+ either expressed or implied, of the FreeBSD Project.
33
+
34
+ ---
35
+
36
+ AprilTag bindings have been adapted from: (C) 2015-2018, Matt Zucker
37
+
38
+ All rights reserved.
39
+
40
+ Redistribution and use in source and binary forms, with or without modification,
41
+ are permitted provided that the following conditions are met:
42
+
43
+ 1. Redistributions of source code must retain the above copyright notice, this
44
+ list of conditions and the following disclaimer.
45
+
46
+ 2. Redistributions in binary form must reproduce the above copyright notice, this
47
+ list of conditions and the following disclaimer in the documentation and/or
48
+ other materials provided with the distribution.
49
+
50
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
51
+ EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
52
+ OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
53
+ SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
54
+ INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
55
+ TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
56
+ BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
57
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
58
+ ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
59
+ SUCH DAMAGE.
@@ -0,0 +1,144 @@
1
+ Metadata-Version: 2.1
2
+ Name: pyapriltags
3
+ Version: 3.3.0.3
4
+ Summary: Python bindings for the Apriltags library
5
+ Home-page: https://github.com/WillB97/pyapriltags
6
+ Author: Aleksandar Petrov
7
+ Author-email: alpetrov@ethz.ch
8
+ Maintainer: Will Barber
9
+ License: BSD
10
+ Platform: UNKNOWN
11
+ Description-Content-Type: text/markdown
12
+ Requires-Dist: numpy
13
+
14
+ # pyapriltags: Python bindings for the Apriltags library
15
+
16
+ These are Python bindings for the [Apriltags 3](https://github.com/AprilRobotics/apriltags) library developed by [AprilRobotics](https://april.eecs.umich.edu/).
17
+ Inspired by the [Apriltags2 bindings](https://github.com/swatbotics/apriltag) by [Matt Zucker](https://github.com/mzucker).
18
+ Forked from [dt-apriltags](https://github.com/duckietown/lib-dt-apriltags).
19
+
20
+ The original library is published with a [BSD 2-Clause license](https://github.com/AprilRobotics/apriltag/blob/master/LICENSE.md).
21
+
22
+ ## Installation
23
+
24
+ ### The easy way
25
+ You can install using `pip` (or `pip3` for Python 3):
26
+ ```
27
+ pip install pyapriltags
28
+ ```
29
+
30
+ And if you want a particular release, add it like this:
31
+ ```
32
+ pip install pyapriltags@v3.3.0
33
+ ```
34
+
35
+ ### Build it yourself
36
+
37
+ Clone this repository and navigate in it. Then initialize the Apriltags submodule:
38
+ ```
39
+ $ git submodule init
40
+ $ git submodule update
41
+ ```
42
+
43
+ Build the Apriltags C library and embed the newly-built library into the pip wheel.
44
+ ```
45
+ $ make build
46
+ ```
47
+
48
+ The new wheel will be available in the directory `dist/`.
49
+ You can now install the wheel
50
+ ```
51
+ pip install pyapriltags-VERSION-py3-none-ARCH.whl
52
+ ```
53
+ NOTE: based on the current `VERSION` of this library and your OS, together with the architecture of your CPU `ARCH`, the filename above varies.
54
+
55
+
56
+ ## Release wheels
57
+
58
+ All the wheels built inside `dist/` can be released (pushed to Pypi.org) by running the command
59
+ ```
60
+ make upload
61
+ ```
62
+
63
+
64
+ ### Release all
65
+
66
+ Use the following command to build and release wheels for Python 3 and CPU architecture `amd64`, `aarch64` and `arm32v7`.
67
+ ```
68
+ make release-all
69
+ ```
70
+
71
+
72
+ ## Usage
73
+
74
+ Some examples of usage can be seen in the `test.py` file.
75
+ The `Detector` class is a wrapper around the Apriltags functionality. You can initialize it as following:
76
+
77
+ ```
78
+ at_detector = Detector(searchpath=['apriltags'],
79
+ families='tag36h11',
80
+ nthreads=1,
81
+ quad_decimate=1.0,
82
+ quad_sigma=0.0,
83
+ refine_edges=1,
84
+ decode_sharpening=0.25,
85
+ debug=0)
86
+ ```
87
+
88
+ The options are:
89
+
90
+ | **Option** | **Default** | **Explanation** |
91
+ |------------------- |--------------- |---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
92
+ | families | 'tag36h11' | Tag families, separated with a space |
93
+ | nthreads | 1 | Number of threads |
94
+ | quad_decimate | 2.0 | Detection of quads can be done on a lower-resolution image, improving speed at a cost of pose accuracy and a slight decrease in detection rate. Decoding the binary payload is still done at full resolution. Set this to 1.0 to use the full resolution. |
95
+ | quad_sigma | 0.0 | What Gaussian blur should be applied to the segmented image. Parameter is the standard deviation in pixels. Very noisy images benefit from non-zero values (e.g. 0.8) |
96
+ | refine_edges | 1 | When non-zero, the edges of the each quad are adjusted to "snap to" strong gradients nearby. This is useful when decimation is employed, as it can increase the quality of the initial quad estimate substantially. Generally recommended to be on (1). Very computationally inexpensive. Option is ignored if quad_decimate = 1 |
97
+ | decode_sharpening | 0.25 | How much sharpening should be done to decoded images? This can help decode small tags but may or may not help in odd lighting conditions or low light conditions |
98
+ | searchpath | ['apriltags'] | Where to look for the Apriltag 3 library, must be a list |
99
+ | debug | 0 | If 1, will save debug images. Runs very slow
100
+
101
+ Detection of tags in images is done by running the `detect` method of the detector:
102
+
103
+ ```
104
+ tags = at_detector.detect(img, estimate_tag_pose=False, camera_params=None, tag_size=None)
105
+ ```
106
+
107
+ If you also want to extract the tag pose, `estimate_tag_pose` should be set to `True` and `camera_params` (`[fx, fy, cx, cy]`) and `tag_size` (in meters) should be supplied. The `detect` method returns a list of `Detection` objects each having the following attributes (note that the ones with an asterisks are computed only if `estimate_tag_pose=True`):
108
+
109
+ | **Attribute** | **Explanation** |
110
+ |----------------- |------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
111
+ | tag_family | The family of the tag. |
112
+ | tag_id | The decoded ID of the tag. |
113
+ | hamming | How many error bits were corrected? Note: accepting large numbers of corrected errors leads to greatly increased false positive rates. NOTE: As of this implementation, the detector cannot detect tags with a Hamming distance greater than 2. |
114
+ | decision_margin | A measure of the quality of the binary decoding process: the average difference between the intensity of a data bit versus the decision threshold. Higher numbers roughly indicate better decodes. This is a reasonable measure of detection accuracy only for very small tags-- not effective for larger tags (where we could have sampled anywhere within a bit cell and still gotten a good detection.) |
115
+ | homography | The 3x3 homography matrix describing the projection from an "ideal" tag (with corners at (-1,1), (1,1), (1,-1), and (-1, -1)) to pixels in the image. |
116
+ | center | The center of the detection in image pixel coordinates. |
117
+ | corners | The corners of the tag in image pixel coordinates. These always wrap counter-clock wise around the tag. |
118
+ | pose_R* | Rotation matrix of the pose estimate. |
119
+ | pose_t* | Translation of the pose estimate. |
120
+ | pose_err* | Object-space error of the estimation. |
121
+
122
+ ## Custom layouts
123
+
124
+ If you want to use a custom layout, you need to create the C source and header files for it and then build the library again. Then use the new `libapriltag.so` library. You can find more information on the original [Apriltags repository](https://github.com/AprilRobotics/apriltags).
125
+
126
+
127
+ ## Developer notes
128
+
129
+ The wheel is built inside a Docker container. The Dockerfile in the root of this repository is a template for the build environment. The build environment is based on `ubuntu:latest` and python3 is installed on the fly.
130
+ The `make build` command will create the build environment if it does not exist before building the wheel.
131
+
132
+ Once the build environment (Docker image) is ready, a Docker container is launched with the following configuration:
133
+ - the root of this repository mounted to `/apriltag`;
134
+ - the directory `dist/` is mounted as destination directory under `/out`;
135
+
136
+ The building script from `assets/build.sh` will be executed inside the container. The build steps are:
137
+ - configure a cmake build in `/builds/<arch>` from the apriltag library from submodule `apriltags/`
138
+ - run cmake build
139
+ - copy so/.dylib/.dll library file to `/dist/<arch>` (inside the container)
140
+ - repeat above steps for: `win64`, `macos arm64`, `macos x86_64`, `linux x86_64`, `linux aarch64`, `linux armv7l`
141
+ - build python wheel (the .so library is embedded as `package_data`)
142
+ - copy wheel file to `/out` (will pop up in `dist/` outside the container)
143
+
144
+
@@ -0,0 +1,9 @@
1
+ pyapriltags/__init__.py,sha256=S3uf8pSD4venPPYD1YA9Q3w5k0oZVV66ZDMUNn_JI0s,91
2
+ pyapriltags/apriltags.py,sha256=PxbPK2b8GKxI-1hBNDri--uGQa-_ueN21bv82ik3jR4,16781
3
+ pyapriltags/libapriltag.dll,sha256=O6aWdLhCCkq5cC53AxHe54fyOgCLNwLoIpdQhGkEKTo,1939947
4
+ pyapriltags/py.typed,sha256=qDGZVT98wnqGGe5eyNJGlayNkP5QxORhXujGHzTPrnE,97
5
+ pyapriltags-3.3.0.3.dist-info/LICENSE.md,sha256=bUZetg0KmQ99yduHpQ7EMooMRjdfDcbincED4aQh3Ig,3044
6
+ pyapriltags-3.3.0.3.dist-info/METADATA,sha256=bGC5dpyhAMnDsniaBZ2APL7tTQZoHJ_M0F75AzVHxd8,12740
7
+ pyapriltags-3.3.0.3.dist-info/WHEEL,sha256=B-MEufKsIKRyyg0orbkY9mg4Nvh0GwM_cF_UcS57u8I,94
8
+ pyapriltags-3.3.0.3.dist-info/top_level.txt,sha256=D17ubblm5a_QJj7B7t4wSOqaMCSEoXWIMns0Wc2Tmvs,12
9
+ pyapriltags-3.3.0.3.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: bdist_wheel (0.34.2)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-win32
5
+
@@ -0,0 +1 @@
1
+ pyapriltags