0
0
mirror of https://github.com/opencv/opencv.git synced 2026-01-18 17:21:42 +01:00

Merge pull request #27833 from asmorkalov:as/move_gen_pattern

Moved pattern generator to apps and rewrote tutorial #27833

### Pull Request Readiness Checklist

See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request

- [x] I agree to contribute to the project under Apache 2 License.
- [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV
- [ ] The PR is proposed to the proper branch
- [ ] There is a reference to the original bug report and related work
- [ ] There is accuracy test, performance test and test data in opencv_extra repository, if applicable
      Patch to opencv_extra has the same branch name.
- [ ] The feature is well documented and sample code can be built with the project CMake
This commit is contained in:
Alexander Smorkalov
2025-10-01 09:42:22 +03:00
committed by GitHub
parent 7994f88ecb
commit e9bded6ff3
33 changed files with 152 additions and 39 deletions

View File

@@ -1,8 +1,3 @@
if (NOT CMAKE_CROSSCOMPILING)
file(RELATIVE_PATH __loc_relative "${OpenCV_BINARY_DIR}" "${CMAKE_CURRENT_LIST_DIR}/pattern_tools\n")
file(APPEND "${OpenCV_BINARY_DIR}/opencv_apps_python_tests.cfg" "${__loc_relative}")
endif()
if(NOT BUILD_DOCS)
return()
endif()
@@ -190,6 +185,7 @@ if(DOXYGEN_FOUND)
list(APPEND CMAKE_DOXYGEN_HTML_FILES "${CMAKE_CURRENT_SOURCE_DIR}/opencv.ico")
list(APPEND CMAKE_DOXYGEN_HTML_FILES "${CMAKE_CURRENT_SOURCE_DIR}/pattern.png")
list(APPEND CMAKE_DOXYGEN_HTML_FILES "${CMAKE_CURRENT_SOURCE_DIR}/acircles_pattern.png")
list(APPEND CMAKE_DOXYGEN_HTML_FILES "${CMAKE_CURRENT_SOURCE_DIR}/charuco_board_pattern.png")
list(APPEND CMAKE_DOXYGEN_HTML_FILES "${CMAKE_CURRENT_SOURCE_DIR}/bodybg.png")
# list(APPEND CMAKE_DOXYGEN_HTML_FILES "${CMAKE_CURRENT_SOURCE_DIR}/mymath.sty")
list(APPEND CMAKE_DOXYGEN_HTML_FILES "${CMAKE_CURRENT_SOURCE_DIR}/tutorial-utils.js")

View File

@@ -1,4 +0,0 @@
use
python gen_pattern.py --help
to generate various calibration svg calibration patterns.

View File

@@ -1,307 +0,0 @@
#!/usr/bin/env python
"""gen_pattern.py
Usage example:
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
-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, radon_checkerboard, charuco_board. 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
-p, --aruco_marker_size - aruco markers size for ChAruco pattern (default 10.0)
-f, --dict_file - file name of custom aruco dictionary for ChAruco pattern
-do, --dict_offset - index of the first ArUco index used
-H, --help - show help
"""
import argparse
import numpy as np
import json
import gzip
from svgfig import *
class PatternMaker:
def __init__(self, cols, rows, output, units, square_size, radius_rate, page_width, page_height, markers, aruco_marker_size, dict_file, dict_offset):
self.cols = cols
self.rows = rows
self.output = output
self.units = units
self.square_size = square_size
self.radius_rate = radius_rate
self.width = page_width
self.height = page_height
self.markers = markers
self.aruco_marker_size = aruco_marker_size #for charuco boards only
self.dict_file = dict_file
self.dict_offset = dict_offset
self.g = SVG("g") # the svg group container
def make_circles_pattern(self):
spacing = self.square_size
r = spacing / self.radius_rate
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 in range(0, self.cols):
for y in range(0, self.rows):
dot = SVG("circle", cx=(x * spacing) + x_spacing + r,
cy=(y * spacing) + y_spacing + r, r=r, fill="black", stroke="none")
self.g.append(dot)
def make_acircles_pattern(self):
spacing = self.square_size
r = spacing / self.radius_rate
pattern_width = ((self.cols-1.0) * 2 * spacing) + 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 in range(0, self.cols):
for y in range(0, self.rows):
dot = SVG("circle", cx=(2 * x * spacing) + (y % 2)*spacing + x_spacing + r,
cy=(y * spacing) + y_spacing + r, r=r, fill="black", stroke="none")
self.g.append(dot)
def make_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:
square = SVG("rect", x=x * spacing + xspacing, y=y * spacing + yspacing, width=spacing,
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)
@staticmethod
def _create_marker_bits(markerSize_bits, byteList):
marker = np.zeros((markerSize_bits+2, markerSize_bits+2))
bits = marker[1:markerSize_bits+1, 1:markerSize_bits+1]
for i in range(markerSize_bits):
for j in range(markerSize_bits):
bits[i][j] = int(byteList[i*markerSize_bits+j])
return marker
def make_charuco_board(self):
if (self.aruco_marker_size>self.square_size):
print("Error: Aruco marker cannot be lager than chessboard square!")
return
if (self.dict_file.split(".")[-1] == "gz"):
with gzip.open(self.dict_file, 'r') as fin:
json_bytes = fin.read()
json_str = json_bytes.decode('utf-8')
dictionary = json.loads(json_str)
else:
f = open(self.dict_file)
dictionary = json.load(f)
if (dictionary["nmarkers"] < int(self.cols*self.rows/2)):
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")
return
markerSize_bits = dictionary["markersize"]
side = self.aruco_marker_size / (markerSize_bits+2)
spacing = self.square_size
xspacing = (self.width - self.cols * self.square_size) / 2.0
yspacing = (self.height - self.rows * self.square_size) / 2.0
ch_ar_border = (self.square_size - self.aruco_marker_size)/2
if ch_ar_border < side*0.7:
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)))
marker_id = self.dict_offset
for y in range(0, self.rows):
for x in range(0, self.cols):
if x % 2 == y % 2:
square = SVG("rect", x=x * spacing + xspacing, y=y * spacing + yspacing, width=spacing,
height=spacing, fill="black", stroke="none")
self.g.append(square)
else:
img_mark = self._create_marker_bits(markerSize_bits, dictionary["marker_"+str(marker_id)])
marker_id +=1
x_pos = x * spacing + xspacing
y_pos = y * spacing + yspacing
square = SVG("rect", x=x_pos+ch_ar_border, y=y_pos+ch_ar_border, width=self.aruco_marker_size,
height=self.aruco_marker_size, fill="black", stroke="none")
self.g.append(square)
for x_ in range(len(img_mark[0])):
for y_ in range(len(img_mark)):
if (img_mark[y_][x_] != 0):
square = SVG("rect", x=x_pos+ch_ar_border+(x_)*side, y=y_pos+ch_ar_border+(y_)*side, width=side,
height=side, fill="white", stroke="white", stroke_width = spacing*0.01)
self.g.append(square)
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))
c.save(self.output)
def main():
# parse command line options
parser = argparse.ArgumentParser(description="generate camera-calibration pattern", add_help=False)
parser.add_argument("-H", "--help", help="show help", action="store_true", dest="show_help")
parser.add_argument("-o", "--output", help="output file", default="out.svg", action="store", dest="output")
parser.add_argument("-c", "--columns", help="pattern columns", default="8", action="store", dest="columns",
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", "radon_checkerboard", "charuco_board"])
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",
dest="square_size", type=float)
parser.add_argument("-R", "--radius_rate", help="circles_radius = square_size/radius_rate", default="5.0",
action="store", dest="radius_rate", type=float)
parser.add_argument("-w", "--page_width", help="page width in units", default=argparse.SUPPRESS, action="store",
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("-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]",
default=argparse.SUPPRESS, action="store", dest="markers", nargs="+", type=int)
parser.add_argument("-p", "--marker_size", help="aruco markers size for ChAruco pattern (default 10.0)", default="10.0",
action="store", dest="aruco_marker_size", type=float)
parser.add_argument("-f", "--dict_file", help="file name of custom aruco dictionary for ChAruco pattern", default="DICT_ARUCO_ORIGINAL.json",
action="store", dest="dict_file", type=str)
parser.add_argument("-do", "--dict_offset", help="index of the first ArUco index used", default=0,
action="store", dest="dict_offset", type=int)
args = parser.parse_args()
show_help = args.show_help
if show_help:
parser.print_help()
return
output = args.output
columns = args.columns
rows = args.rows
p_type = args.p_type
units = args.units
square_size = args.square_size
radius_rate = args.radius_rate
aruco_marker_size = args.aruco_marker_size
dict_file = args.dict_file
dict_offset = args.dict_offset
if 'page_width' and 'page_height' in args:
page_width = args.page_width
page_height = args.page_height
else:
page_size = args.page_size
# page size dict (ISO standard, mm) for easy lookup. format - size: [width, height]
page_sizes = {"A0": [840, 1188], "A1": [594, 840], "A2": [420, 594], "A3": [297, 420], "A4": [210, 297],
"A5": [148, 210]}
page_width = page_sizes[page_size][0]
page_height = page_sizes[page_size][1]
markers = None
if p_type == "radon_checkerboard" and "markers" in args:
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))
if p_type == "charuco_board" and aruco_marker_size >= square_size:
raise ValueError("ArUco markers size must be smaller than square size")
pm = PatternMaker(columns, rows, output, units, square_size, radius_rate, page_width, page_height, markers, aruco_marker_size, dict_file, dict_offset)
# dict for easy lookup of pattern type
mp = {"circles": pm.make_circles_pattern, "acircles": pm.make_acircles_pattern,
"checkerboard": pm.make_checkerboard_pattern, "radon_checkerboard": pm.make_radon_checkerboard_pattern,
"charuco_board": pm.make_charuco_board}
mp[p_type]()
# this should save pattern to output
pm.save()
if __name__ == "__main__":
main()

File diff suppressed because it is too large Load Diff

View File

@@ -1,130 +0,0 @@
from __future__ import print_function
import os, tempfile, numpy as np
import sys
import cv2 as cv
from tests_common import NewOpenCVTests
import gen_pattern
class aruco_objdetect_test(NewOpenCVTests):
def test_aruco_dicts(self):
try:
from svglib.svglib import svg2rlg
from reportlab.graphics import renderPM
except:
raise self.skipTest("libraies svglib and reportlab not found")
else:
cols = 3
rows = 5
square_size = 100
aruco_type = [cv.aruco.DICT_4X4_1000, cv.aruco.DICT_5X5_1000, cv.aruco.DICT_6X6_1000,
cv.aruco.DICT_7X7_1000, cv.aruco.DICT_ARUCO_ORIGINAL, cv.aruco.DICT_APRILTAG_16h5,
cv.aruco.DICT_APRILTAG_25h9, cv.aruco.DICT_APRILTAG_36h10, cv.aruco.DICT_APRILTAG_36h11]
aruco_type_str = ['DICT_4X4_1000','DICT_5X5_1000', 'DICT_6X6_1000',
'DICT_7X7_1000', 'DICT_ARUCO_ORIGINAL', 'DICT_APRILTAG_16h5',
'DICT_APRILTAG_25h9', 'DICT_APRILTAG_36h10', 'DICT_APRILTAG_36h11']
marker_size = 0.8*square_size
board_width = cols*square_size
board_height = rows*square_size
for aruco_type_i in range(len(aruco_type)):
#draw desk using opencv
aruco_dict = cv.aruco.getPredefinedDictionary(aruco_type[aruco_type_i])
board = cv.aruco.CharucoBoard((cols, rows), square_size, marker_size, aruco_dict)
charuco_detector = cv.aruco.CharucoDetector(board)
from_cv_img = board.generateImage((cols*square_size, rows*square_size))
#draw desk using svg
fd1, filesvg = tempfile.mkstemp(prefix="out", suffix=".svg")
os.close(fd1)
fd2, filepng = tempfile.mkstemp(prefix="svg_marker", suffix=".png")
os.close(fd2)
try:
basedir = os.path.abspath(os.path.dirname(__file__))
pm = gen_pattern.PatternMaker(cols, rows, filesvg, "px", square_size, 0, board_width,
board_height, "charuco_checkboard", marker_size,
os.path.join(basedir, aruco_type_str[aruco_type_i]+'.json.gz'), 0)
pm.make_charuco_board()
pm.save()
drawing = svg2rlg(filesvg)
renderPM.drawToFile(drawing, filepng, fmt='PNG', dpi=72)
from_svg_img = cv.imread(filepng)
_charucoCorners, _charuco_ids_svg, marker_corners_svg, marker_ids_svg = charuco_detector.detectBoard(from_svg_img)
_charucoCorners, _charuco_ids_cv, marker_corners_cv, marker_ids_cv = charuco_detector.detectBoard(from_cv_img)
marker_corners_svg_map, marker_corners_cv_map = {}, {}
for i in range(len(marker_ids_svg)):
marker_corners_svg_map[int(marker_ids_svg[i][0])] = marker_corners_svg[i]
for i in range(len(marker_ids_cv)):
marker_corners_cv_map[int(marker_ids_cv[i][0])] = marker_corners_cv[i]
for key_svg in marker_corners_svg_map.keys():
marker_svg = marker_corners_svg_map[key_svg]
marker_cv = marker_corners_cv_map[key_svg]
np.testing.assert_allclose(marker_svg, marker_cv, 0.1, 0.1)
finally:
if os.path.exists(filesvg):
os.remove(filesvg)
if os.path.exists(filepng):
os.remove(filepng)
def test_aruco_marker_sizes(self):
try:
from svglib.svglib import svg2rlg
from reportlab.graphics import renderPM
except:
raise self.skipTest("libraies svglib and reportlab not found")
else:
cols = 3
rows = 5
square_size = 100
aruco_type = cv.aruco.DICT_5X5_1000
aruco_type_str = 'DICT_5X5_1000'
marker_sizes_rate = [0.25, 0.5, 0.75, 0.9]
board_width = cols*square_size
board_height = rows*square_size
for marker_s_rate in marker_sizes_rate:
marker_size = marker_s_rate*square_size
#draw desk using opencv
aruco_dict = cv.aruco.getPredefinedDictionary(aruco_type)
board = cv.aruco.CharucoBoard((cols, rows), square_size, marker_size, aruco_dict)
charuco_detector = cv.aruco.CharucoDetector(board)
from_cv_img = board.generateImage((cols*square_size, rows*square_size))
#draw desk using svg
fd1, filesvg = tempfile.mkstemp(prefix="out", suffix=".svg")
os.close(fd1)
fd2, filepng = tempfile.mkstemp(prefix="svg_marker", suffix=".png")
os.close(fd2)
try:
basedir = os.path.abspath(os.path.dirname(__file__))
pm = gen_pattern.PatternMaker(cols, rows, filesvg, "px", square_size, 0, board_width,
board_height, "charuco_checkboard", marker_size, os.path.join(basedir, aruco_type_str+'.json.gz'), 0)
pm.make_charuco_board()
pm.save()
drawing = svg2rlg(filesvg)
renderPM.drawToFile(drawing, filepng, fmt='PNG', dpi=72)
from_svg_img = cv.imread(filepng)
#test
_charucoCorners, _charuco_ids_svg, marker_corners_svg, marker_ids_svg = charuco_detector.detectBoard(from_svg_img)
_charucoCorners, _charuco_ids_cv, marker_corners_cv, marker_ids_cv = charuco_detector.detectBoard(from_cv_img)
marker_corners_svg_map, marker_corners_cv_map = {}, {}
for i in range(len(marker_ids_svg)):
marker_corners_svg_map[int(marker_ids_svg[i][0])] = marker_corners_svg[i]
for i in range(len(marker_ids_cv)):
marker_corners_cv_map[int(marker_ids_cv[i][0])] = marker_corners_cv[i]
for key_svg in marker_corners_svg_map.keys():
marker_svg = marker_corners_svg_map[key_svg]
marker_cv = marker_corners_cv_map[key_svg]
np.testing.assert_allclose(marker_svg, marker_cv, 0.1, 0.1)
finally:
if os.path.exists(filesvg):
os.remove(filesvg)
if os.path.exists(filepng):
os.remove(filepng)

View File

@@ -1,2 +0,0 @@
svglib>=1.5.1
reportlab>=4.0.0

View File

@@ -1,5 +1,5 @@
Create calibration pattern {#tutorial_camera_calibration_pattern}
=========================================
Create Calibration Pattern {#tutorial_camera_calibration_pattern}
==========================
@tableofcontents
@@ -7,54 +7,166 @@ Create calibration pattern {#tutorial_camera_calibration_pattern}
| | |
| -: | :- |
| Original author | Laurent Berger |
| Compatibility | OpenCV >= 3.0 |
| Authors | Laurent Berger, Alexander Panov, Alexander Smorkalov |
| Compatibility | OpenCV > 4.12 |
The goal of this tutorial is to learn how to create a calibration pattern.
The tutorial describes all pattern supported by OpenCV for camera(s) calibration and pose estimation
with their strength, pitfalls and practical recommendations.
You can find a chessboard pattern in https://github.com/opencv/opencv/blob/4.x/doc/pattern.png
What is calibration pattern? why I need it?
-------------------------------------------
You can find a circleboard pattern in https://github.com/opencv/opencv/blob/4.x/doc/acircles_pattern.png
The flat printable pattern may be used:
You can find a ChAruco board pattern in https://github.com/opencv/opencv/blob/4.x/doc/charuco_board_pattern.png
(7X5 ChAruco board, square size: 30 mm , marker size: 15 mm, aruco dict: DICT_5X5_100, page width: 210 mm, page height: 297 mm)
1. For camera intrinsics (internal parameters) calibration. See @ref tutorial_camera_calibration.
2. For stereo or multi-camera system extrinsics (external parameters: rotation and translation
of each camera) calibration. See cv::stereoCalibrate for details.
3. Camera pose registration relative to well known point in 3d world. See multiview calibration
tutorial in OpenCV 5.x.
Create your own pattern
---------------
Pattern Types
-------------
Now, if you want to create your own pattern, you will need python to use https://github.com/opencv/opencv/blob/4.x/doc/pattern_tools/gen_pattern.py
**Chessboard**. Classic calibration pattern of black and white squares. The all calibration algorithms
use internal chessboard corners as features. See cv::findChessboardCorners and cv::cornerSubPix to
detect the board and refine corners coordinates with sub-pixel accuracy. The board size is defined
as amount of internal corners, but not amount of black or white squares. Also pay attention, that
the board with even size is symmetric. If board has even amount of corners by one of direction then
its pose is defined up to 180 degrees (2 solutions). It the board is square with size N x N then its
pose is defined up to 90 degrees (4 solutions). The last two cases are not suitable for calibration.
Example code to generate features coordinates for calibration (object points):
```
std::vector<cv::Point3f> objectPoints;
for (int i = 0; i < boardSize.height; ++i) {
for (int j = 0; j < boardSize.width; ++j) {
objectPoints.push_back(Point3f(j*squareSize, i*squareSize, 0));
}
}
```
Printable chessboard pattern: https://github.com/opencv/opencv/blob/4.x/doc/pattern.png
(9x6 chessboard, page width: 210 mm, page height: 297 mm (A4))
Example
**Circles Grid**. The circles grid is symmetric or asymmetric (each even row shifted) grid of black
circles on a white background or vice verse. See cv::findCirclesGrid function to detect the board
with OpenCV. The detector produces sub-pixel coordinates of the circle centers and does not require
additional refinement. The board size is defined as amount of circles in grid by x and y axis.
In case of asymmetric grid the shifted rows are taken into account too. The board is suitable for
intrinsics calibration. Symmetric grids suffer from the same issue as chessboard pattern with even
size. It's pose is defined up to 180 degrees.
Example code to generate features coordinates for calibration with symmetric grid (object points):
```
std::vector<cv::Point3f> objectPoints;
for (int i = 0; i < boardSize.height; ++i) {
for (int j = 0; j < boardSize.width; ++j) {
objectPoints.push_back(Point3f(j*squareSize, i*squareSize, 0));
}
}
```
Example code to generate features corrdinates for calibration with asymmetic grid (object points):
```
std::vector<cv::Point3f> objectPoints;
for (int i = 0; i < boardSize.height; i++) {
for (int j = 0; j < boardSize.width; j++) {
objectPoints.push_back(Point3f((2 * j + i % 2)*squareSize, i*squareSize, 0));
}
}
```
Printable asymmetric circles grid pattern: https://github.com/opencv/opencv/blob/4.x/doc/acircles_pattern.png
(11x4 asymmetric circles grid, page width: 210 mm, page height: 297 mm (A4))
**ChAruco board**. Chessboard unreached with ArUco markers. Each internal corner of the board is
described by 2 neighborhood ArUco markers that makes it unique. The board size is defined in number
of units, but not internal corners. ChAruco board of size N x M is equivalent to chessboard pattern
of size N-1 x M-1. OpenCV provides `cv::aruco::CharucoDetector` class for the board detection.
The detector algorithm finds ArUco markers first and them "assembles" the board using knowledge
about ArUco pairs. In opposite to the previous pattern partially occluded board may be used as all
corners are labeled. The board is rotation invariant, but set of ArUco markers and their order
should be known to detector apriori. It cannot detect ChAruco board with predefined size and random
set of markers.
Example code to generate features corrdinates for calibration (object points) for board size in units:
```
std::vector<cv::Point3f> objectPoints;
for (int i = 0; i < boardSize.height-1; ++i) {
for (int j = 0; j < boardSize.width-1; ++j) {
objectPoints.push_back(Point3f(j*squareSize, i*squareSize, 0));
}
}
```
Printable ChAruco board pattern: https://github.com/opencv/opencv/blob/4.x/doc/charuco_board_pattern.png
(7X5 ChAruco board, square size: 30 mm, marker size: 15 mm, ArUco dict: DICT_5X5_100, page width:
210 mm, page height: 297 mm (A4))
Create Your Own Pattern
-----------------------
In case if ready pattern does not satisfy your requirements, you can generate your own. OpenCV
provides generate_pattern.py tool in `apps/pattern-tools` of source repository or your binary
distribution. The only requirement is Python 3.
Examples:
create a checkerboard pattern in file chessboard.svg with 9 rows, 6 columns and a square size of 20mm:
python gen_pattern.py -o chessboard.svg --rows 9 --columns 6 --type checkerboard --square_size 20
python generate_pattern.py -o chessboard.svg --rows 9 --columns 6 --type checkerboard --square_size 20
create a circle board pattern in file circleboard.svg with 7 rows, 5 columns and a radius of 15 mm:
python gen_pattern.py -o circleboard.svg --rows 7 --columns 5 --type circles --square_size 15
python generate_pattern.py -o circleboard.svg --rows 7 --columns 5 --type circles --square_size 15
create a circle board pattern in file acircleboard.svg with 7 rows, 5 columns and a square size of 10mm and less spacing between circle:
create a circle board pattern in file acircleboard.svg with 7 rows, 5 columns and a square size of
10mm and less spacing between circle:
python gen_pattern.py -o acircleboard.svg --rows 7 --columns 5 --type acircles --square_size 10 --radius_rate 2
python generate_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
python generate_pattern.py -o radon_checkerboard.svg --rows 10 --columns 15 --type radon_checkerboard -s 12.1 -m 7 4 7 5 8 5
create a ChAruco board pattern in charuco_board.svg with 7 rows, 5 columns, square size 30 mm, aruco marker size 15 mm and using DICT_5X5_100 as dictionary for aruco markers (it contains in DICT_ARUCO.json file):
create a ChAruco board pattern in charuco_board.svg with 7 rows, 5 columns, square size 30 mm, aruco
marker size 15 mm and using DICT_5X5_100 as dictionary for aruco markers (it contains in DICT_ARUCO.json file):
python gen_pattern.py -o charuco_board.svg --rows 7 --columns 5 -T charuco_board --square_size 30 --marker_size 15 -f DICT_5X5_100.json.gz
python generate_pattern.py -o charuco_board.svg --rows 7 --columns 5 -T charuco_board --square_size 30 --marker_size 15 -f DICT_5X5_100.json.gz
If you want to change the measurement units, use the -u option (e.g. mm, inches, px, m)
If you want to change the page size, use the -w (width) and -h (height) options
If you want to use your own dictionary for the ChAruco board, specify the name of your dictionary file. For example
If you want to use your own dictionary for the ChAruco board, specify the name of your dictionary
file. For example:
python gen_pattern.py -o charuco_board.svg --rows 7 --columns 5 -T charuco_board -f my_dictionary.json
python generate_pattern.py -o charuco_board.svg --rows 7 --columns 5 -T charuco_board -f my_dictionary.json
You can generate your dictionary in the file my_dictionary.json with 30 markers and a marker size of 5 bits using the utility provided in opencv/samples/cpp/aruco_dict_utils.cpp.
You can generate your dictionary in the file my_dictionary.json with 30 markers and a marker size of
5 bits using the utility provided in `samples/cpp/aruco_dict_utils.cpp`.
bin/example_cpp_aruco_dict_utils.exe my_dict.json -nMarkers=30 -markerSize=5
Pattern Size
------------
Pattern is defined by it's physical board size, element (square or circle) physical size and amount
of elements. Factors that affect calibration quality:
- **Amount of features**. Most of OpenCV functions that work with detected patterns use optimization
or some random consensus strategies inside. More features on board means more points for optimization
and better estimation quality. Calibration process requires several images. It means that in most
of cases lower amount of pattern features may be compensated by higher amount frames.
- **Element size**. The physical size of elements depends on the distance and size in pixels.
Each detector defines some minimal size for reliable detection. For circles grid it's circle
radius, for chessboard it's square size, for ChAruco board it's ArUco marker element size.
General recommendation: larger elements (in frame pixels) reduces detection uncertainty.
- **Board size**. The board should be fully visible, sharp and reliably detected by OpenCV algorithms.
So, the board size should satisfy previous items, if it's used with typical target distance.
Usually larger board is better, but smaller boards allow to calibrate corners better.
Generic Recommendations
-----------------------
1. The final pattern should be as flat as possible. It improves calibration accuracy.
2. Glance pattern is worse than matte. Blinks and shadows on glance surface degrades board detection
significantly.
3. Most of detection algorithms expect white (black) border around the markers. Please do not cut
them or cover them.

View File

@@ -68,4 +68,4 @@ Answer: After obtaining the camera pose using solvePnP, the rotation (rvec) and
// assuming 'point' is the 3D position of a chessboard corner in the camera coordinate system
double distance = norm(point);
This is equivalent to applying the L2 norm on the 3D points coordinates (x, y, z).
This is equivalent to applying the L2 norm on the 3D points coordinates (x, y, z).

View File

@@ -127,7 +127,7 @@ in ascending order starting on 0, so they will be 0, 1, 2, ..., 34.
After creating a grid board, we probably want to print it and use it.
There are two ways to do this:
1. By using the script `doc/patter_tools/gen_pattern.py `, see @subpage tutorial_camera_calibration_pattern.
1. By using the script `apps/pattern_tools/generate_pattern.py `, see @subpage tutorial_camera_calibration_pattern.
2. By using the function `cv::aruco::GridBoard::generateImage()`.
The function `cv::aruco::GridBoard::generateImage()` is provided in cv::aruco::GridBoard class and

View File

@@ -77,7 +77,7 @@ through `board.ids`, like in the `cv::aruco::Board` parent class.
Once we have our `cv::aruco::CharucoBoard` object, we can create an image to print it. There are
two ways to do this:
1. By using the script `doc/patter_tools/gen_pattern.py `, see @subpage tutorial_camera_calibration_pattern.
1. By using the script `apps/pattern_tools/generate_pattern.py `, see @subpage tutorial_camera_calibration_pattern.
2. By using the function `cv::aruco::CharucoBoard::generateImage()`.
The function `cv::aruco::CharucoBoard::generateImage()` is provided in cv::aruco::CharucoBoard class