opencv

Форк
0
/
gen_pattern.py 
302 строки · 14.6 Кб
1
#!/usr/bin/env python
2

3
"""gen_pattern.py
4
Usage example:
5
python 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

22
import argparse
23
import numpy as np
24
import json
25
import gzip
26
from svgfig import *
27

28

29
class PatternMaker:
30
    def __init__(self, cols, rows, output, units, square_size, radius_rate, page_width, page_height, markers, aruco_marker_size, dict_file):
31
        self.cols = cols
32
        self.rows = rows
33
        self.output = output
34
        self.units = units
35
        self.square_size = square_size
36
        self.radius_rate = radius_rate
37
        self.width = page_width
38
        self.height = page_height
39
        self.markers = markers
40
        self.aruco_marker_size = aruco_marker_size #for charuco boards only
41
        self.dict_file = dict_file
42

43
        self.g = SVG("g")  # the svg group container
44

45
    def make_circles_pattern(self):
46
        spacing = self.square_size
47
        r = spacing / self.radius_rate
48
        pattern_width = ((self.cols - 1.0) * spacing) + (2.0 * r)
49
        pattern_height = ((self.rows - 1.0) * spacing) + (2.0 * r)
50
        x_spacing = (self.width - pattern_width) / 2.0
51
        y_spacing = (self.height - pattern_height) / 2.0
52
        for x in range(0, self.cols):
53
            for y in range(0, self.rows):
54
                dot = SVG("circle", cx=(x * spacing) + x_spacing + r,
55
                          cy=(y * spacing) + y_spacing + r, r=r, fill="black", stroke="none")
56
                self.g.append(dot)
57

58
    def make_acircles_pattern(self):
59
        spacing = self.square_size
60
        r = spacing / self.radius_rate
61
        pattern_width = ((self.cols-1.0) * 2 * spacing) + spacing + (2.0 * r)
62
        pattern_height = ((self.rows-1.0) * spacing) + (2.0 * r)
63
        x_spacing = (self.width - pattern_width) / 2.0
64
        y_spacing = (self.height - pattern_height) / 2.0
65
        for x in range(0, self.cols):
66
            for y in range(0, self.rows):
67
                dot = SVG("circle", cx=(2 * x * spacing) + (y % 2)*spacing + x_spacing + r,
68
                          cy=(y * spacing) + y_spacing + r, r=r, fill="black", stroke="none")
69
                self.g.append(dot)
70

71
    def make_checkerboard_pattern(self):
72
        spacing = self.square_size
73
        xspacing = (self.width - self.cols * self.square_size) / 2.0
74
        yspacing = (self.height - self.rows * self.square_size) / 2.0
75
        for x in range(0, self.cols):
76
            for y in range(0, self.rows):
77
                if x % 2 == y % 2:
78
                    square = SVG("rect", x=x * spacing + xspacing, y=y * spacing + yspacing, width=spacing,
79
                                 height=spacing, fill="black", stroke="none")
80
                    self.g.append(square)
81

82
    @staticmethod
83
    def _make_round_rect(x, y, diam, corners=("right", "right", "right", "right")):
84
        rad = diam / 2
85
        cw_point = ((0, 0), (diam, 0), (diam, diam), (0, diam))
86
        mid_cw_point = ((0, rad), (rad, 0), (diam, rad), (rad, diam))
87
        res_str = "M{},{} ".format(x + mid_cw_point[0][0], y + mid_cw_point[0][1])
88
        n = len(cw_point)
89
        for i in range(n):
90
            if corners[i] == "right":
91
                res_str += "L{},{} L{},{} ".format(x + cw_point[i][0], y + cw_point[i][1],
92
                                                   x + mid_cw_point[(i + 1) % n][0], y + mid_cw_point[(i + 1) % n][1])
93
            elif corners[i] == "round":
94
                res_str += "A{},{} 0,0,1 {},{} ".format(rad, rad, x + mid_cw_point[(i + 1) % n][0],
95
                                                        y + mid_cw_point[(i + 1) % n][1])
96
            else:
97
                raise TypeError("unknown corner type")
98
        return res_str
99

100
    def _get_type(self, x, y):
101
        corners = ["right", "right", "right", "right"]
102
        is_inside = True
103
        if x == 0:
104
            corners[0] = "round"
105
            corners[3] = "round"
106
            is_inside = False
107
        if y == 0:
108
            corners[0] = "round"
109
            corners[1] = "round"
110
            is_inside = False
111
        if x == self.cols - 1:
112
            corners[1] = "round"
113
            corners[2] = "round"
114
            is_inside = False
115
        if y == self.rows - 1:
116
            corners[2] = "round"
117
            corners[3] = "round"
118
            is_inside = False
119
        return corners, is_inside
120

121
    def make_radon_checkerboard_pattern(self):
122
        spacing = self.square_size
123
        xspacing = (self.width - self.cols * self.square_size) / 2.0
124
        yspacing = (self.height - self.rows * self.square_size) / 2.0
125
        for x in range(0, self.cols):
126
            for y in range(0, self.rows):
127
                if x % 2 == y % 2:
128
                    corner_types, is_inside = self._get_type(x, y)
129
                    if is_inside:
130
                        square = SVG("rect", x=x * spacing + xspacing, y=y * spacing + yspacing, width=spacing,
131
                                     height=spacing, fill="black", stroke="none")
132
                    else:
133
                        square = SVG("path", d=self._make_round_rect(x * spacing + xspacing, y * spacing + yspacing,
134
                                      spacing, corner_types), fill="black", stroke="none")
135
                    self.g.append(square)
136
        if self.markers is not None:
137
            r = self.square_size * 0.17
138
            pattern_width = ((self.cols - 1.0) * spacing) + (2.0 * r)
139
            pattern_height = ((self.rows - 1.0) * spacing) + (2.0 * r)
140
            x_spacing = (self.width - pattern_width) / 2.0
141
            y_spacing = (self.height - pattern_height) / 2.0
142
            for x, y in self.markers:
143
                color = "black"
144
                if x % 2 == y % 2:
145
                    color = "white"
146
                dot = SVG("circle", cx=(x * spacing) + x_spacing + r,
147
                          cy=(y * spacing) + y_spacing + r, r=r, fill=color, stroke="none")
148
                self.g.append(dot)
149

150
    @staticmethod
151
    def _create_marker_bits(markerSize_bits, byteList):
152

153
        marker = np.zeros((markerSize_bits+2, markerSize_bits+2))
154
        bits = marker[1:markerSize_bits+1, 1:markerSize_bits+1]
155

156
        for i in range(markerSize_bits):
157
            for j in range(markerSize_bits):
158
                bits[i][j] = int(byteList[i*markerSize_bits+j])
159

160
        return marker
161

162
    def make_charuco_board(self):
163
        if (self.aruco_marker_size>self.square_size):
164
            print("Error: Aruco marker cannot be lager than chessboard square!")
165
            return
166

167
        if (self.dict_file.split(".")[-1] == "gz"):
168
            with gzip.open(self.dict_file, 'r') as fin:
169
                json_bytes = fin.read()
170
                json_str = json_bytes.decode('utf-8')
171
                dictionary = json.loads(json_str)
172

173
        else:
174
            f = open(self.dict_file)
175
            dictionary = json.load(f)
176

177
        if (dictionary["nmarkers"] < int(self.cols*self.rows/2)):
178
            print("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")
179
            return
180

181
        markerSize_bits = dictionary["markersize"]
182

183
        side = self.aruco_marker_size / (markerSize_bits+2)
184
        spacing = self.square_size
185
        xspacing = (self.width - self.cols * self.square_size) / 2.0
186
        yspacing = (self.height - self.rows * self.square_size) / 2.0
187

188
        ch_ar_border = (self.square_size - self.aruco_marker_size)/2
189
        if ch_ar_border < side*0.7:
190
            print("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)))
191
        marker_id = 0
192
        for y in range(0, self.rows):
193
            for x in range(0, self.cols):
194

195
                if x % 2 == y % 2:
196
                    square = SVG("rect", x=x * spacing + xspacing, y=y * spacing + yspacing, width=spacing,
197
                                 height=spacing, fill="black", stroke="none")
198
                    self.g.append(square)
199
                else:
200
                    img_mark = self._create_marker_bits(markerSize_bits, dictionary["marker_"+str(marker_id)])
201
                    marker_id +=1
202
                    x_pos = x * spacing + xspacing
203
                    y_pos = y * spacing + yspacing
204

205
                    square = SVG("rect", x=x_pos+ch_ar_border, y=y_pos+ch_ar_border, width=self.aruco_marker_size,
206
                                             height=self.aruco_marker_size, fill="black", stroke="none")
207
                    self.g.append(square)
208
                    for x_ in range(len(img_mark[0])):
209
                        for y_ in range(len(img_mark)):
210
                            if (img_mark[y_][x_] != 0):
211
                                square = SVG("rect", x=x_pos+ch_ar_border+(x_)*side, y=y_pos+ch_ar_border+(y_)*side, width=side,
212
                                             height=side, fill="white", stroke="white", stroke_width = spacing*0.01)
213
                                self.g.append(square)
214

215
    def save(self):
216
        c = canvas(self.g, width="%d%s" % (self.width, self.units), height="%d%s" % (self.height, self.units),
217
                   viewBox="0 0 %d %d" % (self.width, self.height))
218
        c.save(self.output)
219

220

221
def main():
222
    # parse command line options
223
    parser = argparse.ArgumentParser(description="generate camera-calibration pattern", add_help=False)
224
    parser.add_argument("-H", "--help", help="show help", action="store_true", dest="show_help")
225
    parser.add_argument("-o", "--output", help="output file", default="out.svg", action="store", dest="output")
226
    parser.add_argument("-c", "--columns", help="pattern columns", default="8", action="store", dest="columns",
227
                        type=int)
228
    parser.add_argument("-r", "--rows", help="pattern rows", default="11", action="store", dest="rows", type=int)
229
    parser.add_argument("-T", "--type", help="type of pattern", default="circles", action="store", dest="p_type",
230
                        choices=["circles", "acircles", "checkerboard", "radon_checkerboard", "charuco_board"])
231
    parser.add_argument("-u", "--units", help="length unit", default="mm", action="store", dest="units",
232
                        choices=["mm", "inches", "px", "m"])
233
    parser.add_argument("-s", "--square_size", help="size of squares in pattern", default="20.0", action="store",
234
                        dest="square_size", type=float)
235
    parser.add_argument("-R", "--radius_rate", help="circles_radius = square_size/radius_rate", default="5.0",
236
                        action="store", dest="radius_rate", type=float)
237
    parser.add_argument("-w", "--page_width", help="page width in units", default=argparse.SUPPRESS, action="store",
238
                        dest="page_width", type=float)
239
    parser.add_argument("-h", "--page_height", help="page height in units", default=argparse.SUPPRESS, action="store",
240
                        dest="page_height", type=float)
241
    parser.add_argument("-a", "--page_size", help="page size, superseded if -h and -w are set", default="A4",
242
                        action="store", dest="page_size", choices=["A0", "A1", "A2", "A3", "A4", "A5"])
243
    parser.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]",
246
                        default=argparse.SUPPRESS, action="store", dest="markers", nargs="+", type=int)
247
    parser.add_argument("-p", "--marker_size", help="aruco markers size for ChAruco pattern (default 10.0)", default="10.0",
248
                        action="store", dest="aruco_marker_size", type=float)
249
    parser.add_argument("-f", "--dict_file", help="file name of custom aruco dictionary for ChAruco pattern", default="DICT_ARUCO_ORIGINAL.json",
250
                        action="store", dest="dict_file", type=str)
251
    args = parser.parse_args()
252

253
    show_help = args.show_help
254
    if show_help:
255
        parser.print_help()
256
        return
257
    output = args.output
258
    columns = args.columns
259
    rows = args.rows
260
    p_type = args.p_type
261
    units = args.units
262
    square_size = args.square_size
263
    radius_rate = args.radius_rate
264
    aruco_marker_size = args.aruco_marker_size
265
    dict_file = args.dict_file
266

267
    if 'page_width' and 'page_height' in args:
268
        page_width = args.page_width
269
        page_height = args.page_height
270
    else:
271
        page_size = args.page_size
272
        # page size dict (ISO standard, mm) for easy lookup. format - size: [width, height]
273
        page_sizes = {"A0": [840, 1188], "A1": [594, 840], "A2": [420, 594], "A3": [297, 420], "A4": [210, 297],
274
                      "A5": [148, 210]}
275
        page_width = page_sizes[page_size][0]
276
        page_height = page_sizes[page_size][1]
277
    markers = None
278
    if p_type == "radon_checkerboard" and "markers" in args:
279
        if len(args.markers) % 2 == 1:
280
            raise ValueError("The length of the markers array={} must be even".format(len(args.markers)))
281
        markers = set()
282
        for x, y in zip(args.markers[::2], args.markers[1::2]):
283
            if x in range(0, columns) and y in range(0, rows):
284
                markers.add((x, y))
285
            else:
286
                raise ValueError("The marker {},{} is outside the checkerboard".format(x, y))
287

288
    if p_type == "charuco_board" and aruco_marker_size >= square_size:
289
        raise ValueError("ArUco markers size must be smaller than square size")
290

291
    pm = 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
293
    mp = {"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}
296
    mp[p_type]()
297
    # this should save pattern to output
298
    pm.save()
299

300

301
if __name__ == "__main__":
302
    main()
303

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.