Skip to content

Commit

Permalink
Merge pull request opencv#20735 from AleksandrPanov:radon_checkerboard
Browse files Browse the repository at this point in the history
generate radon checkerboard

* added _make_round_rect

* added round rect to make_checkerboard_pattern

* added markers

* update docs

* removed links to findChessboardCornersSB() and added checks to markers
  • Loading branch information
Alexander Panov authored Sep 28, 2021
1 parent 8fa8d47 commit 982745f
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 7 deletions.
97 changes: 90 additions & 7 deletions doc/pattern_tools/gen_pattern.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@
-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
"""

Expand All @@ -22,7 +23,7 @@


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
Expand All @@ -31,6 +32,7 @@ def __init__(self, cols, rows, output, units, square_size, radius_rate, page_wid
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):
Expand Down Expand Up @@ -70,6 +72,74 @@ def make_checkerboard_pattern(self):
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))
Expand All @@ -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",
Expand All @@ -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
Expand All @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions modules/calib3d/include/opencv2/calib3d.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit 982745f

Please sign in to comment.