Visual Servoing Platform version 3.7.0
Loading...
Searching...
No Matches
visp_python_apriltag_detection.py
2import argparse
3import sys
4from pathlib import Path
5
6from visp.core import CameraParameters, HomogeneousMatrix, ImageGray, ImageConvert, Color, Point, ImagePoint
7from visp.detection import DetectorAprilTag
8from visp.core import Display
9
10from visp.python.display_utils import get_display
11
12import numpy as np
13from typing import Generator, List
14
15
16
17# Table for argument name to DetectorAprilTag family conversion
18family_mapping = {
19 '16h5': DetectorAprilTag.AprilTagFamily.TAG_16h5,
20 '25h7': DetectorAprilTag.AprilTagFamily.TAG_25h7,
21 '25h9': DetectorAprilTag.AprilTagFamily.TAG_25h9,
22 '36h10': DetectorAprilTag.AprilTagFamily.TAG_36h10,
23 '36h11': DetectorAprilTag.AprilTagFamily.TAG_36h11,
24}
25
26method_mapping = {
27 'homography_vvs': DetectorAprilTag.PoseEstimationMethod.HOMOGRAPHY_VIRTUAL_VS,
28 'dementhon_vvs': DetectorAprilTag.PoseEstimationMethod.DEMENTHON_VIRTUAL_VS,
29 'lagrange_vvs': DetectorAprilTag.PoseEstimationMethod.LAGRANGE_VIRTUAL_VS
30}
31
32
33def build_detector_from_args(args) -> DetectorAprilTag:
34 detector = DetectorAprilTag()
35 detector.setAprilTagDecisionMarginThreshold(args.tag_decision_margin)
36 detector.setAprilTagHammingDistanceThreshold(args.tag_hamming_distance)
37 detector.setAprilTagQuadDecimate(args.tag_decimate)
38 detector.setAprilTagFamily(family_mapping[args.tag_family])
39 detector.setAprilTagPoseEstimationMethod(method_mapping[args.tag_pose_method])
40 return detector
41
42
43
44
46 def intrinsics(self) -> CameraParameters:
47 raise NotImplementedError()
48 def frames(self):
49 raise NotImplementedError()
50
52 def __init__(self, args):
53 import pyrealsense2 as rs
54 self.pipe = rs.pipeline()
55 self.config = rs.config()
56 self.h, self.w = args.height, args.width
57 self.config.enable_stream(rs.stream.color, self.w, self.h, rs.format.rgb8, args.fps)
58
59 self.cfg = self.pipe.start(self.config)
60 self.profile = self.cfg.get_stream(rs.stream.color)
61 intr = self.profile.as_video_stream_profile().get_intrinsics() # Downcast to video_stream_profile and fetch intrinsics
62 self.cam = CameraParameters(intr.fx, intr.fy, intr.ppx, intr.ppy)
63 print(f'Opened Realsense camera with intrinsics:\n{self.cam}')
64
65 def intrinsics(self) -> CameraParameters:
66 return self.cam
67
68 def frames(self):
69
70 def generator():
71 while True:
72 frame = self.pipe.wait_for_frames()
73 img = ImageGray(self.h, self.w)
74 I_np = np.asanyarray(frame.get_color_frame().as_frame().get_data())
75 ImageConvert.RGBToGrey(I_np, img)
76 yield img
77
78 return generator()
79
81 def __init__(self, args):
82 self.source_path = args.source
83 from visp.io import VideoReader
84 self.reader = VideoReader()
85 self.reader.setFileName(self.source_path)
86
87 self.cam = CameraParameters()
88 if any(v == 0 for v in [args.px, args.py, args.u0, args.v0]):
89 raise RuntimeError('Camera intrinsics need to be specified through the command line arguments')
90
91 if args.kud != 0 or args.kdu != 0:
92 self.cam.initPersProjWithDistortion(args.px, args.py, args.u0, args.v0, args.kud, args.kdu)
93 else:
94 self.cam.initPersProjWithoutDistortion(args.px, args.py, args.u0, args.v0)
95
96 def intrinsics(self):
97 return self.cam
98
99 def frames(self):
100 def generator():
101 I = ImageGray()
102 self.reader.open(I)
103 while not self.reader.end():
104 self.reader.acquire(I)
105 yield I
106
107 return generator()
108
109
110
111def build_source_from_args(args) -> FrameSource:
112 if args.source == 'rs2':
113 return RealSenseSource(args)
114 else:
115 return FileSource(args)
116
118 def __init__(self, log_path: Path):
119 self.log_path = log_path
120 if self.log_path.exists():
121 raise FileExistsError(f'File {str(self.log_path.absolute())} already exists')
122 self.data = []
123 def log_frame(self, detector: DetectorAprilTag, tag_size: float, cam: CameraParameters):
124 ids = detector.getTagsId()
125 tag_size_dict = {tag_id: tag_size for tag_id in ids}
126 points_3d: List[List[Point]] = detector.getTagsPoints3D(ids, tag_size_dict)
127 points_2d: List[List[ImagePoint]] = detector.getTagsCorners()
128 frame_log = {}
129 for i, tag_id in enumerate(ids):
130 id_log = {
131 'points3d': [p.get_oP().numpy().tolist() for p in points_3d[i]],
132 'points2d': [(p.get_i(), p.get_j()) for p in points_2d[i]],
133 'cMo': detector.getPose(i, tag_size, cam)[1].numpy().tolist()
134 }
135
136 frame_log[tag_id] = id_log
137 self.data.append(frame_log)
138
140 print('Writing detections to log file: ', str(self.log_path))
141 with open(self.log_path, 'w') as log_file:
142 import json
143 json.dump(self.data, log_file, indent=4)
144
145def main():
146 parser = argparse.ArgumentParser(description='AprilTag detection program from a Realsense camera')
147 tag_parser = parser.add_argument_group('AprilTag options')
148 tag_parser.add_argument('--tag-family', choices=family_mapping.keys(), default='36h11', help='The family of apriltags to detect')
149 tag_parser.add_argument('--tag-size', type=float, default=0.053, help='The size of the tags to detect, in meters')
150 tag_parser.add_argument('--tag-pose-method', choices=method_mapping.keys(), default='homography_vvs', help='The method used to estimated the 6D pose of the tags')
151 tag_parser.add_argument('--tag-decimate', type=float, required=False, default=1.0)
152 tag_parser.add_argument('--tag-decision-margin', type=float, required=False, default=2.0)
153 tag_parser.add_argument('--tag-hamming-distance', type=int, required=False, default=50)
154
155 source_parser = parser.add_argument_group('Image acquisition parsing')
156 source_help = '''Image source. if rs2, we open the realsense stream (if available).
157 Otherwise we consider that it is a stored sequence. It should be specified as /path/to/img-%%d.jpg.
158 See the visp.io.VideoReader documentation for more information'''
159 source_parser.add_argument('--source', help=source_help, default='rs2')
160 source_parser.add_argument('--width', help='Image width', default=640)
161 source_parser.add_argument('--height', help='Image height', default=480)
162 source_parser.add_argument('--fps', help='Framerate', default=30)
163
164 camera_parser = parser.add_argument_group('Camera intrinsics')
165 camera_parser.add_argument('--px', default=0.0, type=float)
166 camera_parser.add_argument('--py', default=0.0, type=float)
167 camera_parser.add_argument('--u0', default=0.0, type=float)
168 camera_parser.add_argument('--v0', default=0.0, type=float)
169 camera_parser.add_argument('--kud', default=0.0, type=float)
170 camera_parser.add_argument('--kdu', default=0.0, type=float)
171
172
173 parser.add_argument('--log-path', default=None, required=False, help='The path to the JSON file where to log the detections.')
174
175 args, unknown_args = parser.parse_known_args()
176
177
178 if len(unknown_args) > 0:
179 print(f'There were unknown arguments: {unknown_args}', file=sys.stderr)
180 raise RuntimeError()
181
182 detector = build_detector_from_args(args)
183 tag_size = args.tag_size
184
185 frame_source = build_source_from_args(args)
186 frames = frame_source.frames()
187
188 logger = None
189 if args.log_path is not None:
190 log_path = Path(args.log_path)
191 logger = DetectionsLogger(log_path)
192
193 I_disp = ImageGray()
194
195 cam = frame_source.intrinsics()
196
197 first_frame = True
198 for frame in frames:
199 if first_frame:
200 I_disp.resize(frame.getHeight(), frame.getWidth())
201 gray_display = get_display()
202 gray_display.init(I_disp, 0, 0, 'Image')
203 first_frame = False
204
205 # Copy into I_disp to display tags
206 np.copyto(dst=I_disp.numpy(), src=frame.numpy())
207
208 has_tag, tag_poses = detector.detect(frame, tag_size, cam)
209 if logger is not None:
210 logger.log_frame(detector, tag_size, cam)
211
212 Display.display(I_disp)
213 Display.displayText(I_disp, 20, 20, 'Click to stop detection', Color.red)
214 detector.displayFrames(I_disp, tag_poses, cam, 0.05, Color.none)
215 detector.displayTags(I_disp, detector.getTagsCorners())
216 Display.flush(I_disp)
217
218 clicked = Display.getClick(I_disp, blocking=False)
219 if clicked:
220 break
221
222 if logger is not None:
223 logger.finalize_logging()
224
225
226if __name__ == '__main__':
227 main()
log_frame(self, DetectorAprilTag detector, float tag_size, CameraParameters cam)