diff --git a/doc/pattern_tools/gen_pattern.py b/doc/pattern_tools/gen_pattern.py index a6ffc7ca7e..83ed115d36 100755 --- a/doc/pattern_tools/gen_pattern.py +++ b/doc/pattern_tools/gen_pattern.py @@ -6,13 +6,14 @@ python gen_pattern.py -o out.svg -r 11 -c 8 -T circles -s 20.0 -R 5.0 -u mm -w 2 -o, --output - output file (default out.svg) -r, --rows - pattern rows (default 11) -c, --columns - pattern columns (default 8) --T, --type - type of pattern, circles, acircles, checkerboard (default circles) +-T, --type - type of pattern, circles, acircles, checkerboard, radon_checkerboard (default circles) -s, --square_size - size of squares in pattern (default 20.0) -R, --radius_rate - circles_radius = square_size/radius_rate (default 5.0) -u, --units - mm, inches, px, m (default mm) -w, --page_width - page width in units (default 216) -h, --page_height - page height in units (default 279) -a, --page_size - page size (default A4), supersedes -h -w arguments +-m, --markers - list of cells with markers for the radon checkerboard -H, --help - show help """ @@ -22,7 +23,7 @@ from svgfig import * class PatternMaker: - def __init__(self, cols, rows, output, units, square_size, radius_rate, page_width, page_height): + def __init__(self, cols, rows, output, units, square_size, radius_rate, page_width, page_height, markers): self.cols = cols self.rows = rows self.output = output @@ -31,6 +32,7 @@ class PatternMaker: self.radius_rate = radius_rate self.width = page_width self.height = page_height + self.markers = markers self.g = SVG("g") # the svg group container def make_circles_pattern(self): @@ -70,6 +72,74 @@ class PatternMaker: height=spacing, fill="black", stroke="none") self.g.append(square) + @staticmethod + def _make_round_rect(x, y, diam, corners=("right", "right", "right", "right")): + rad = diam / 2 + cw_point = ((0, 0), (diam, 0), (diam, diam), (0, diam)) + mid_cw_point = ((0, rad), (rad, 0), (diam, rad), (rad, diam)) + res_str = "M{},{} ".format(x + mid_cw_point[0][0], y + mid_cw_point[0][1]) + n = len(cw_point) + for i in range(n): + if corners[i] == "right": + res_str += "L{},{} L{},{} ".format(x + cw_point[i][0], y + cw_point[i][1], + x + mid_cw_point[(i + 1) % n][0], y + mid_cw_point[(i + 1) % n][1]) + elif corners[i] == "round": + res_str += "A{},{} 0,0,1 {},{} ".format(rad, rad, x + mid_cw_point[(i + 1) % n][0], + y + mid_cw_point[(i + 1) % n][1]) + else: + raise TypeError("unknown corner type") + return res_str + + def _get_type(self, x, y): + corners = ["right", "right", "right", "right"] + is_inside = True + if x == 0: + corners[0] = "round" + corners[3] = "round" + is_inside = False + if y == 0: + corners[0] = "round" + corners[1] = "round" + is_inside = False + if x == self.cols - 1: + corners[1] = "round" + corners[2] = "round" + is_inside = False + if y == self.rows - 1: + corners[2] = "round" + corners[3] = "round" + is_inside = False + return corners, is_inside + + def make_radon_checkerboard_pattern(self): + spacing = self.square_size + xspacing = (self.width - self.cols * self.square_size) / 2.0 + yspacing = (self.height - self.rows * self.square_size) / 2.0 + for x in range(0, self.cols): + for y in range(0, self.rows): + if x % 2 == y % 2: + corner_types, is_inside = self._get_type(x, y) + if is_inside: + square = SVG("rect", x=x * spacing + xspacing, y=y * spacing + yspacing, width=spacing, + height=spacing, fill="black", stroke="none") + else: + square = SVG("path", d=self._make_round_rect(x * spacing + xspacing, y * spacing + yspacing, + spacing, corner_types), fill="black", stroke="none") + self.g.append(square) + if self.markers is not None: + r = self.square_size * 0.17 + pattern_width = ((self.cols - 1.0) * spacing) + (2.0 * r) + pattern_height = ((self.rows - 1.0) * spacing) + (2.0 * r) + x_spacing = (self.width - pattern_width) / 2.0 + y_spacing = (self.height - pattern_height) / 2.0 + for x, y in self.markers: + color = "black" + if x % 2 == y % 2: + color = "white" + dot = SVG("circle", cx=(x * spacing) + x_spacing + r, + cy=(y * spacing) + y_spacing + r, r=r, fill=color, stroke="none") + self.g.append(dot) + def save(self): c = canvas(self.g, width="%d%s" % (self.width, self.units), height="%d%s" % (self.height, self.units), viewBox="0 0 %d %d" % (self.width, self.height)) @@ -85,7 +155,7 @@ def main(): type=int) parser.add_argument("-r", "--rows", help="pattern rows", default="11", action="store", dest="rows", type=int) parser.add_argument("-T", "--type", help="type of pattern", default="circles", action="store", dest="p_type", - choices=["circles", "acircles", "checkerboard"]) + choices=["circles", "acircles", "checkerboard", "radon_checkerboard"]) parser.add_argument("-u", "--units", help="length unit", default="mm", action="store", dest="units", choices=["mm", "inches", "px", "m"]) parser.add_argument("-s", "--square_size", help="size of squares in pattern", default="20.0", action="store", @@ -96,8 +166,12 @@ def main(): dest="page_width", type=float) parser.add_argument("-h", "--page_height", help="page height in units", default=argparse.SUPPRESS, action="store", dest="page_height", type=float) - parser.add_argument("-a", "--page_size", help="page size, superseded if -h and -w are set", default="A4", action="store", - dest="page_size", choices=["A0", "A1", "A2", "A3", "A4", "A5"]) + parser.add_argument("-a", "--page_size", help="page size, superseded if -h and -w are set", default="A4", + action="store", dest="page_size", choices=["A0", "A1", "A2", "A3", "A4", "A5"]) + parser.add_argument("-m", "--markers", help="list of cells with markers for the radon checkerboard. Marker " + "coordinates as list of numbers: -m 1 2 3 4 means markers in cells " + "[1, 2] and [3, 4]", + action="store", dest="markers", nargs="+", type=int) args = parser.parse_args() show_help = args.show_help @@ -121,10 +195,19 @@ def main(): "A5": [148, 210]} page_width = page_sizes[page_size][0] page_height = page_sizes[page_size][1] - pm = PatternMaker(columns, rows, output, units, square_size, radius_rate, page_width, page_height) + if len(args.markers) % 2 == 1: + raise ValueError("The length of the markers array={} must be even".format(len(args.markers))) + markers = set() + for x, y in zip(args.markers[::2], args.markers[1::2]): + if x in range(0, columns) and y in range(0, rows): + markers.add((x, y)) + else: + raise ValueError("The marker {},{} is outside the checkerboard".format(x, y)) + + pm = PatternMaker(columns, rows, output, units, square_size, radius_rate, page_width, page_height, markers) # dict for easy lookup of pattern type mp = {"circles": pm.make_circles_pattern, "acircles": pm.make_acircles_pattern, - "checkerboard": pm.make_checkerboard_pattern} + "checkerboard": pm.make_checkerboard_pattern, "radon_checkerboard": pm.make_radon_checkerboard_pattern} mp[p_type]() # this should save pattern to output pm.save() diff --git a/doc/tutorials/calib3d/camera_calibration_pattern/camera_calibration_pattern.markdown b/doc/tutorials/calib3d/camera_calibration_pattern/camera_calibration_pattern.markdown index c87f9f95f8..b13f09756b 100644 --- a/doc/tutorials/calib3d/camera_calibration_pattern/camera_calibration_pattern.markdown +++ b/doc/tutorials/calib3d/camera_calibration_pattern/camera_calibration_pattern.markdown @@ -36,6 +36,10 @@ create a circle board pattern in file acircleboard.svg with 7 rows, 5 columns an python gen_pattern.py -o acircleboard.svg --rows 7 --columns 5 --type acircles --square_size 10 --radius_rate 2 +create a radon checkerboard for findChessboardCornersSB() with markers in (7 4), (7 5), (8 5) cells: + + python gen_pattern.py -o radon_checkerboard.svg --rows 10 --columns 15 --type radon_checkerboard -s 12.1 -m 7 4 7 5 8 5 + If you want to change unit use -u option (mm inches, px, m) If you want to change page size use -w and -h options diff --git a/modules/calib3d/include/opencv2/calib3d.hpp b/modules/calib3d/include/opencv2/calib3d.hpp index 37c5e7089a..198e405f6d 100644 --- a/modules/calib3d/include/opencv2/calib3d.hpp +++ b/modules/calib3d/include/opencv2/calib3d.hpp @@ -1489,6 +1489,8 @@ Sample usage of detecting and drawing chessboard corners: : the board to make the detection more robust in various environments. Otherwise, if there is no border and the background is dark, the outer black squares cannot be segmented properly and so the square grouping and ordering algorithm fails. + +Use gen_pattern.py (@ref tutorial_camera_calibration_pattern) to create checkerboard. */ CV_EXPORTS_W bool findChessboardCorners( InputArray image, Size patternSize, OutputArray corners, int flags = CALIB_CB_ADAPTIVE_THRESH + CALIB_CB_NORMALIZE_IMAGE ); @@ -1545,6 +1547,8 @@ transformation it is beneficial to use round corners for the field corners which are located on the outside of the board. The following figure illustrates a sample checkerboard optimized for the detection. However, any other checkerboard can be used as well. + +Use gen_pattern.py (@ref tutorial_camera_calibration_pattern) to create checkerboard. ![Checkerboard](pics/checkerboard_radon.png) */ CV_EXPORTS_AS(findChessboardCornersSBWithMeta)