opencv
302 строки · 14.6 Кб
1#!/usr/bin/env python
2
3"""gen_pattern.py
4Usage example:
5python gen_pattern.py -o out.svg -r 11 -c 8 -T circles -s 20.0 -R 5.0 -u mm -w 216 -h 279
6-o, --output - output file (default out.svg)
7-r, --rows - pattern rows (default 11)
8-c, --columns - pattern columns (default 8)
9-T, --type - type of pattern: circles, acircles, checkerboard, radon_checkerboard, charuco_board. default circles.
10-s, --square_size - size of squares in pattern (default 20.0)
11-R, --radius_rate - circles_radius = square_size/radius_rate (default 5.0)
12-u, --units - mm, inches, px, m (default mm)
13-w, --page_width - page width in units (default 216)
14-h, --page_height - page height in units (default 279)
15-a, --page_size - page size (default A4), supersedes -h -w arguments
16-m, --markers - list of cells with markers for the radon checkerboard
17-p, --aruco_marker_size - aruco markers size for ChAruco pattern (default 10.0)
18-f, --dict_file - file name of custom aruco dictionary for ChAruco pattern
19-H, --help - show help
20"""
21
22import argparse
23import numpy as np
24import json
25import gzip
26from svgfig import *
27
28
29class PatternMaker:
30def __init__(self, cols, rows, output, units, square_size, radius_rate, page_width, page_height, markers, aruco_marker_size, dict_file):
31self.cols = cols
32self.rows = rows
33self.output = output
34self.units = units
35self.square_size = square_size
36self.radius_rate = radius_rate
37self.width = page_width
38self.height = page_height
39self.markers = markers
40self.aruco_marker_size = aruco_marker_size #for charuco boards only
41self.dict_file = dict_file
42
43self.g = SVG("g") # the svg group container
44
45def make_circles_pattern(self):
46spacing = self.square_size
47r = spacing / self.radius_rate
48pattern_width = ((self.cols - 1.0) * spacing) + (2.0 * r)
49pattern_height = ((self.rows - 1.0) * spacing) + (2.0 * r)
50x_spacing = (self.width - pattern_width) / 2.0
51y_spacing = (self.height - pattern_height) / 2.0
52for x in range(0, self.cols):
53for y in range(0, self.rows):
54dot = SVG("circle", cx=(x * spacing) + x_spacing + r,
55cy=(y * spacing) + y_spacing + r, r=r, fill="black", stroke="none")
56self.g.append(dot)
57
58def make_acircles_pattern(self):
59spacing = self.square_size
60r = spacing / self.radius_rate
61pattern_width = ((self.cols-1.0) * 2 * spacing) + spacing + (2.0 * r)
62pattern_height = ((self.rows-1.0) * spacing) + (2.0 * r)
63x_spacing = (self.width - pattern_width) / 2.0
64y_spacing = (self.height - pattern_height) / 2.0
65for x in range(0, self.cols):
66for y in range(0, self.rows):
67dot = SVG("circle", cx=(2 * x * spacing) + (y % 2)*spacing + x_spacing + r,
68cy=(y * spacing) + y_spacing + r, r=r, fill="black", stroke="none")
69self.g.append(dot)
70
71def make_checkerboard_pattern(self):
72spacing = self.square_size
73xspacing = (self.width - self.cols * self.square_size) / 2.0
74yspacing = (self.height - self.rows * self.square_size) / 2.0
75for x in range(0, self.cols):
76for y in range(0, self.rows):
77if x % 2 == y % 2:
78square = SVG("rect", x=x * spacing + xspacing, y=y * spacing + yspacing, width=spacing,
79height=spacing, fill="black", stroke="none")
80self.g.append(square)
81
82@staticmethod
83def _make_round_rect(x, y, diam, corners=("right", "right", "right", "right")):
84rad = diam / 2
85cw_point = ((0, 0), (diam, 0), (diam, diam), (0, diam))
86mid_cw_point = ((0, rad), (rad, 0), (diam, rad), (rad, diam))
87res_str = "M{},{} ".format(x + mid_cw_point[0][0], y + mid_cw_point[0][1])
88n = len(cw_point)
89for i in range(n):
90if corners[i] == "right":
91res_str += "L{},{} L{},{} ".format(x + cw_point[i][0], y + cw_point[i][1],
92x + mid_cw_point[(i + 1) % n][0], y + mid_cw_point[(i + 1) % n][1])
93elif corners[i] == "round":
94res_str += "A{},{} 0,0,1 {},{} ".format(rad, rad, x + mid_cw_point[(i + 1) % n][0],
95y + mid_cw_point[(i + 1) % n][1])
96else:
97raise TypeError("unknown corner type")
98return res_str
99
100def _get_type(self, x, y):
101corners = ["right", "right", "right", "right"]
102is_inside = True
103if x == 0:
104corners[0] = "round"
105corners[3] = "round"
106is_inside = False
107if y == 0:
108corners[0] = "round"
109corners[1] = "round"
110is_inside = False
111if x == self.cols - 1:
112corners[1] = "round"
113corners[2] = "round"
114is_inside = False
115if y == self.rows - 1:
116corners[2] = "round"
117corners[3] = "round"
118is_inside = False
119return corners, is_inside
120
121def make_radon_checkerboard_pattern(self):
122spacing = self.square_size
123xspacing = (self.width - self.cols * self.square_size) / 2.0
124yspacing = (self.height - self.rows * self.square_size) / 2.0
125for x in range(0, self.cols):
126for y in range(0, self.rows):
127if x % 2 == y % 2:
128corner_types, is_inside = self._get_type(x, y)
129if is_inside:
130square = SVG("rect", x=x * spacing + xspacing, y=y * spacing + yspacing, width=spacing,
131height=spacing, fill="black", stroke="none")
132else:
133square = SVG("path", d=self._make_round_rect(x * spacing + xspacing, y * spacing + yspacing,
134spacing, corner_types), fill="black", stroke="none")
135self.g.append(square)
136if self.markers is not None:
137r = self.square_size * 0.17
138pattern_width = ((self.cols - 1.0) * spacing) + (2.0 * r)
139pattern_height = ((self.rows - 1.0) * spacing) + (2.0 * r)
140x_spacing = (self.width - pattern_width) / 2.0
141y_spacing = (self.height - pattern_height) / 2.0
142for x, y in self.markers:
143color = "black"
144if x % 2 == y % 2:
145color = "white"
146dot = SVG("circle", cx=(x * spacing) + x_spacing + r,
147cy=(y * spacing) + y_spacing + r, r=r, fill=color, stroke="none")
148self.g.append(dot)
149
150@staticmethod
151def _create_marker_bits(markerSize_bits, byteList):
152
153marker = np.zeros((markerSize_bits+2, markerSize_bits+2))
154bits = marker[1:markerSize_bits+1, 1:markerSize_bits+1]
155
156for i in range(markerSize_bits):
157for j in range(markerSize_bits):
158bits[i][j] = int(byteList[i*markerSize_bits+j])
159
160return marker
161
162def make_charuco_board(self):
163if (self.aruco_marker_size>self.square_size):
164print("Error: Aruco marker cannot be lager than chessboard square!")
165return
166
167if (self.dict_file.split(".")[-1] == "gz"):
168with gzip.open(self.dict_file, 'r') as fin:
169json_bytes = fin.read()
170json_str = json_bytes.decode('utf-8')
171dictionary = json.loads(json_str)
172
173else:
174f = open(self.dict_file)
175dictionary = json.load(f)
176
177if (dictionary["nmarkers"] < int(self.cols*self.rows/2)):
178print("Error: Aruco dictionary contains less markers than it needs for chosen board. Please choose another dictionary or use smaller board than required for chosen board")
179return
180
181markerSize_bits = dictionary["markersize"]
182
183side = self.aruco_marker_size / (markerSize_bits+2)
184spacing = self.square_size
185xspacing = (self.width - self.cols * self.square_size) / 2.0
186yspacing = (self.height - self.rows * self.square_size) / 2.0
187
188ch_ar_border = (self.square_size - self.aruco_marker_size)/2
189if ch_ar_border < side*0.7:
190print("Marker border {} is less than 70% of ArUco pin size {}. Please increase --square_size or decrease --marker_size for stable board detection".format(ch_ar_border, int(side)))
191marker_id = 0
192for y in range(0, self.rows):
193for x in range(0, self.cols):
194
195if x % 2 == y % 2:
196square = SVG("rect", x=x * spacing + xspacing, y=y * spacing + yspacing, width=spacing,
197height=spacing, fill="black", stroke="none")
198self.g.append(square)
199else:
200img_mark = self._create_marker_bits(markerSize_bits, dictionary["marker_"+str(marker_id)])
201marker_id +=1
202x_pos = x * spacing + xspacing
203y_pos = y * spacing + yspacing
204
205square = SVG("rect", x=x_pos+ch_ar_border, y=y_pos+ch_ar_border, width=self.aruco_marker_size,
206height=self.aruco_marker_size, fill="black", stroke="none")
207self.g.append(square)
208for x_ in range(len(img_mark[0])):
209for y_ in range(len(img_mark)):
210if (img_mark[y_][x_] != 0):
211square = SVG("rect", x=x_pos+ch_ar_border+(x_)*side, y=y_pos+ch_ar_border+(y_)*side, width=side,
212height=side, fill="white", stroke="white", stroke_width = spacing*0.01)
213self.g.append(square)
214
215def save(self):
216c = canvas(self.g, width="%d%s" % (self.width, self.units), height="%d%s" % (self.height, self.units),
217viewBox="0 0 %d %d" % (self.width, self.height))
218c.save(self.output)
219
220
221def main():
222# parse command line options
223parser = argparse.ArgumentParser(description="generate camera-calibration pattern", add_help=False)
224parser.add_argument("-H", "--help", help="show help", action="store_true", dest="show_help")
225parser.add_argument("-o", "--output", help="output file", default="out.svg", action="store", dest="output")
226parser.add_argument("-c", "--columns", help="pattern columns", default="8", action="store", dest="columns",
227type=int)
228parser.add_argument("-r", "--rows", help="pattern rows", default="11", action="store", dest="rows", type=int)
229parser.add_argument("-T", "--type", help="type of pattern", default="circles", action="store", dest="p_type",
230choices=["circles", "acircles", "checkerboard", "radon_checkerboard", "charuco_board"])
231parser.add_argument("-u", "--units", help="length unit", default="mm", action="store", dest="units",
232choices=["mm", "inches", "px", "m"])
233parser.add_argument("-s", "--square_size", help="size of squares in pattern", default="20.0", action="store",
234dest="square_size", type=float)
235parser.add_argument("-R", "--radius_rate", help="circles_radius = square_size/radius_rate", default="5.0",
236action="store", dest="radius_rate", type=float)
237parser.add_argument("-w", "--page_width", help="page width in units", default=argparse.SUPPRESS, action="store",
238dest="page_width", type=float)
239parser.add_argument("-h", "--page_height", help="page height in units", default=argparse.SUPPRESS, action="store",
240dest="page_height", type=float)
241parser.add_argument("-a", "--page_size", help="page size, superseded if -h and -w are set", default="A4",
242action="store", dest="page_size", choices=["A0", "A1", "A2", "A3", "A4", "A5"])
243parser.add_argument("-m", "--markers", help="list of cells with markers for the radon checkerboard. Marker "
244"coordinates as list of numbers: -m 1 2 3 4 means markers in cells "
245"[1, 2] and [3, 4]",
246default=argparse.SUPPRESS, action="store", dest="markers", nargs="+", type=int)
247parser.add_argument("-p", "--marker_size", help="aruco markers size for ChAruco pattern (default 10.0)", default="10.0",
248action="store", dest="aruco_marker_size", type=float)
249parser.add_argument("-f", "--dict_file", help="file name of custom aruco dictionary for ChAruco pattern", default="DICT_ARUCO_ORIGINAL.json",
250action="store", dest="dict_file", type=str)
251args = parser.parse_args()
252
253show_help = args.show_help
254if show_help:
255parser.print_help()
256return
257output = args.output
258columns = args.columns
259rows = args.rows
260p_type = args.p_type
261units = args.units
262square_size = args.square_size
263radius_rate = args.radius_rate
264aruco_marker_size = args.aruco_marker_size
265dict_file = args.dict_file
266
267if 'page_width' and 'page_height' in args:
268page_width = args.page_width
269page_height = args.page_height
270else:
271page_size = args.page_size
272# page size dict (ISO standard, mm) for easy lookup. format - size: [width, height]
273page_sizes = {"A0": [840, 1188], "A1": [594, 840], "A2": [420, 594], "A3": [297, 420], "A4": [210, 297],
274"A5": [148, 210]}
275page_width = page_sizes[page_size][0]
276page_height = page_sizes[page_size][1]
277markers = None
278if p_type == "radon_checkerboard" and "markers" in args:
279if len(args.markers) % 2 == 1:
280raise ValueError("The length of the markers array={} must be even".format(len(args.markers)))
281markers = set()
282for x, y in zip(args.markers[::2], args.markers[1::2]):
283if x in range(0, columns) and y in range(0, rows):
284markers.add((x, y))
285else:
286raise ValueError("The marker {},{} is outside the checkerboard".format(x, y))
287
288if p_type == "charuco_board" and aruco_marker_size >= square_size:
289raise ValueError("ArUco markers size must be smaller than square size")
290
291pm = PatternMaker(columns, rows, output, units, square_size, radius_rate, page_width, page_height, markers, aruco_marker_size, dict_file)
292# dict for easy lookup of pattern type
293mp = {"circles": pm.make_circles_pattern, "acircles": pm.make_acircles_pattern,
294"checkerboard": pm.make_checkerboard_pattern, "radon_checkerboard": pm.make_radon_checkerboard_pattern,
295"charuco_board": pm.make_charuco_board}
296mp[p_type]()
297# this should save pattern to output
298pm.save()
299
300
301if __name__ == "__main__":
302main()
303