mirror of
https://github.com/opencv/opencv.git
synced 2026-07-21 19:33:03 +04:00
Merge pull request #23363 from vovka643:4.x_generate_charuco
Added charuco board generation to gen_pattern.py #23363 added charuco board generation in gen_pattern.py moved aruco_dict_utils.cpp to samples from opencv_contrib (https://github.com/opencv/opencv_contrib/pull/3464) ### 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 - [x] The PR is proposed to the proper branch - [x] There is a reference to the original bug report and related work - [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [x] The feature is well documented and sample code can be built with the project CMake
This commit is contained in:
committed by
GitHub
parent
f2311d1bfd
commit
9931da772d
Executable
+56
@@ -0,0 +1,56 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import sys
|
||||
sys.dont_write_bytecode = True # Don't generate .pyc files / __pycache__ directories
|
||||
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
# Python 3 moved urlopen to urllib.requests
|
||||
try:
|
||||
from urllib.request import urlopen
|
||||
except ImportError:
|
||||
from urllib import urlopen
|
||||
|
||||
basedir = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
sys.path.append(os.path.join(os.path.split(basedir)[0], "modules", "python", "test"))
|
||||
from tests_common import NewOpenCVTests
|
||||
|
||||
def load_tests(loader, tests, pattern):
|
||||
cwd = os.getcwd()
|
||||
config_file = 'opencv_apps_python_tests.cfg'
|
||||
locations = [cwd, basedir]
|
||||
if os.path.exists(config_file):
|
||||
with open(config_file, 'r') as f:
|
||||
locations += [str(s).strip() for s in f.readlines()]
|
||||
else:
|
||||
print('WARNING: OpenCV tests config file ({}) is missing, running subset of tests'.format(config_file))
|
||||
|
||||
tests_pattern = os.environ.get('OPENCV_APPS_TEST_FILTER', 'test_*') + '.py'
|
||||
if tests_pattern != 'test_*.py':
|
||||
print('Tests filter: {}'.format(tests_pattern))
|
||||
|
||||
processed = set()
|
||||
for l in locations:
|
||||
if not os.path.isabs(l):
|
||||
l = os.path.normpath(os.path.join(cwd, l))
|
||||
if l in processed:
|
||||
continue
|
||||
processed.add(l)
|
||||
print('Discovering python tests from: {}'.format(l))
|
||||
sys_path_modify = l not in sys.path
|
||||
if sys_path_modify:
|
||||
sys.path.append(l) # Hack python loader
|
||||
discovered_tests = loader.discover(l, pattern=tests_pattern, top_level_dir=l)
|
||||
print(' found {} tests'.format(discovered_tests.countTestCases()))
|
||||
tests.addTests(loader.discover(l, pattern=tests_pattern))
|
||||
if sys_path_modify:
|
||||
sys.path.remove(l)
|
||||
return tests
|
||||
|
||||
if __name__ == '__main__':
|
||||
NewOpenCVTests.bootstrap()
|
||||
@@ -1,3 +1,8 @@
|
||||
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()
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 59 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -6,7 +6,7 @@ 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, radon_checkerboard (default circles)
|
||||
-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)
|
||||
@@ -14,16 +14,20 @@ python gen_pattern.py -o out.svg -r 11 -c 8 -T circles -s 20.0 -R 5.0 -u mm -w 2
|
||||
-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
|
||||
-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):
|
||||
def __init__(self, cols, rows, output, units, square_size, radius_rate, page_width, page_height, markers, aruco_marker_size, dict_file):
|
||||
self.cols = cols
|
||||
self.rows = rows
|
||||
self.output = output
|
||||
@@ -33,6 +37,9 @@ class PatternMaker:
|
||||
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.g = SVG("g") # the svg group container
|
||||
|
||||
def make_circles_pattern(self):
|
||||
@@ -140,6 +147,69 @@ class PatternMaker:
|
||||
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
|
||||
marker_id = 0
|
||||
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))
|
||||
@@ -155,7 +225,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", "radon_checkerboard"])
|
||||
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",
|
||||
@@ -172,6 +242,10 @@ def main():
|
||||
"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)
|
||||
args = parser.parse_args()
|
||||
|
||||
show_help = args.show_help
|
||||
@@ -185,6 +259,9 @@ def main():
|
||||
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
|
||||
|
||||
if 'page_width' and 'page_height' in args:
|
||||
page_width = args.page_width
|
||||
page_height = args.page_height
|
||||
@@ -206,10 +283,11 @@ def main():
|
||||
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)
|
||||
pm = PatternMaker(columns, rows, output, units, square_size, radius_rate, page_width, page_height, markers, aruco_marker_size, dict_file)
|
||||
# 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}
|
||||
"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()
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
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*10, rows*square_size*10))
|
||||
|
||||
#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'))
|
||||
pm.make_charuco_board()
|
||||
pm.save()
|
||||
drawing = svg2rlg(filesvg)
|
||||
renderPM.drawToFile(drawing, filepng, fmt='PNG', dpi=720)
|
||||
from_svg_img = cv.imread(filepng)
|
||||
|
||||
#test
|
||||
_charucoCorners, _charucoIds, markerCorners_svg, markerIds_svg = charuco_detector.detectBoard(from_svg_img)
|
||||
_charucoCorners, _charucoIds, markerCorners_cv, markerIds_cv = charuco_detector.detectBoard(from_cv_img)
|
||||
|
||||
np.testing.assert_allclose(markerCorners_svg, markerCorners_cv, 0.1, 0.1)
|
||||
np.testing.assert_allclose(markerIds_svg, markerIds_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*10, rows*square_size*10))
|
||||
|
||||
#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'))
|
||||
pm.make_charuco_board()
|
||||
pm.save()
|
||||
drawing = svg2rlg(filesvg)
|
||||
renderPM.drawToFile(drawing, filepng, fmt='PNG', dpi=720)
|
||||
from_svg_img = cv.imread(filepng)
|
||||
|
||||
#test
|
||||
_charucoCorners, _charucoIds, markerCorners_svg, markerIds_svg = charuco_detector.detectBoard(from_svg_img)
|
||||
_charucoCorners, _charucoIds, markerCorners_cv, markerIds_cv = charuco_detector.detectBoard(from_cv_img)
|
||||
|
||||
np.testing.assert_allclose(markerCorners_svg, markerCorners_cv, 0.1, 0.1)
|
||||
np.testing.assert_allclose(markerIds_svg, markerIds_cv, 0.1, 0.1)
|
||||
finally:
|
||||
if os.path.exists(filesvg):
|
||||
os.remove(filesvg)
|
||||
if os.path.exists(filepng):
|
||||
os.remove(filepng)
|
||||
@@ -0,0 +1,2 @@
|
||||
svglib>=1.5.1
|
||||
reportlab>=4.0.0
|
||||
+14
-6
@@ -17,6 +17,9 @@ You can find a chessboard pattern in https://github.com/opencv/opencv/blob/4.x/d
|
||||
|
||||
You can find a circleboard pattern in https://github.com/opencv/opencv/blob/4.x/doc/acircles_pattern.png
|
||||
|
||||
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)
|
||||
|
||||
Create your own pattern
|
||||
---------------
|
||||
|
||||
@@ -40,13 +43,18 @@ create a radon checkerboard for findChessboardCornersSB() with markers in (7 4),
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
@cond HAVE_opencv_aruco
|
||||
If you want to create a ChArUco board read @ref tutorial_charuco_detection "tutorial Detection of ChArUco Corners" in opencv_contrib tutorial.
|
||||
@endcond
|
||||
@cond !HAVE_opencv_aruco
|
||||
If you want to create a ChArUco board read tutorial Detection of ChArUco Corners in opencv_contrib tutorial.
|
||||
@endcond
|
||||
If you want to use your own dictionary for ChAruco board your should write name of file with your dictionary. For example
|
||||
|
||||
python gen_pattern.py -o charuco_board.svg --rows 7 --columns 5 -T charuco_board -f my_dictionary.json
|
||||
|
||||
You can generate your dictionary in my_dictionary.json file with number of markers 30 and markers size 5 bits by using opencv/samples/cpp/aruco_dict_utils.cpp.
|
||||
|
||||
bin/example_cpp_aruco_dict_utils.exe my_dict.json -nMarkers=30 -markerSize=5
|
||||
|
||||
@@ -0,0 +1,348 @@
|
||||
#include <opencv2/objdetect/aruco_detector.hpp>
|
||||
#include <iostream>
|
||||
|
||||
using namespace cv;
|
||||
using namespace std;
|
||||
|
||||
static int _getSelfDistance(const Mat &marker) {
|
||||
|
||||
Mat bytes = aruco::Dictionary::getByteListFromBits(marker);
|
||||
|
||||
double minHamming = (double)marker.total() + 1;
|
||||
for(int r = 1; r < 4; r++) {
|
||||
cv::Mat tmp1(1, bytes.cols, CV_8UC1, Scalar::all(0));
|
||||
cv::Mat tmp2(1, bytes.cols, CV_8UC1, Scalar::all(0));
|
||||
uchar* rot0 = tmp1.ptr();
|
||||
uchar* rot1 = tmp2.ptr();
|
||||
|
||||
for (int i = 0; i < bytes.cols; ++i) {
|
||||
rot0[i] = bytes.ptr()[i];
|
||||
rot1[i] = bytes.ptr()[bytes.cols*r + i];
|
||||
}
|
||||
|
||||
double currentHamming = cv::norm(tmp1, tmp2, cv::NORM_HAMMING);
|
||||
if (currentHamming < minHamming) minHamming = currentHamming;
|
||||
}
|
||||
Mat b;
|
||||
flip(marker, b, 0);
|
||||
Mat flipBytes = aruco::Dictionary::getByteListFromBits(b);
|
||||
for(int r = 0; r < 4; r++) {
|
||||
cv::Mat tmp1(1, flipBytes.cols, CV_8UC1, Scalar::all(0));
|
||||
cv::Mat tmp2(1, bytes.cols, CV_8UC1, Scalar::all(0));
|
||||
uchar* rot0 = tmp1.ptr();
|
||||
uchar* rot1 = tmp2.ptr();
|
||||
|
||||
for (int i = 0; i < bytes.cols; ++i) {
|
||||
rot0[i] = flipBytes.ptr()[i];
|
||||
rot1[i] = bytes.ptr()[bytes.cols*r + i];
|
||||
}
|
||||
|
||||
double currentHamming = cv::norm(tmp1, tmp2, cv::NORM_HAMMING);
|
||||
if(currentHamming < minHamming) minHamming = currentHamming;
|
||||
}
|
||||
flip(marker, b, 1);
|
||||
flipBytes = aruco::Dictionary::getByteListFromBits(b);
|
||||
for(int r = 0; r < 4; r++) {
|
||||
cv::Mat tmp1(1, flipBytes.cols, CV_8UC1, Scalar::all(0));
|
||||
cv::Mat tmp2(1, bytes.cols, CV_8UC1, Scalar::all(0));
|
||||
uchar* rot0 = tmp1.ptr();
|
||||
uchar* rot1 = tmp2.ptr();
|
||||
|
||||
for (int i = 0; i < bytes.cols; ++i) {
|
||||
rot0[i] = flipBytes.ptr()[i];
|
||||
rot1[i] = bytes.ptr()[bytes.cols*r + i];
|
||||
}
|
||||
|
||||
double currentHamming = cv::norm(tmp1, tmp2, cv::NORM_HAMMING);
|
||||
if(currentHamming < minHamming) minHamming = currentHamming;
|
||||
}
|
||||
return cvRound(minHamming);
|
||||
}
|
||||
|
||||
static inline int getFlipDistanceToId(const aruco::Dictionary& dict, InputArray bits, int id, bool allRotations = true) {
|
||||
Mat bytesList = dict.bytesList;
|
||||
CV_Assert(id >= 0 && id < bytesList.rows);
|
||||
|
||||
unsigned int nRotations = 4;
|
||||
if(!allRotations) nRotations = 1;
|
||||
|
||||
Mat candidateBytes = aruco::Dictionary::getByteListFromBits(bits.getMat());
|
||||
double currentMinDistance = int(bits.total() * bits.total());
|
||||
for(unsigned int r = 0; r < nRotations; r++) {
|
||||
|
||||
cv::Mat tmp1(1, candidateBytes.cols, CV_8UC1, Scalar::all(0));
|
||||
cv::Mat tmp2(1, candidateBytes.cols, CV_8UC1, Scalar::all(0));
|
||||
uchar* rot0 = tmp1.ptr();
|
||||
uchar* rot1 = tmp2.ptr();
|
||||
|
||||
for (int i = 0; i < candidateBytes.cols; ++i) {
|
||||
rot0[i] = bytesList.ptr(id)[r*candidateBytes.cols + i];
|
||||
rot1[i] = candidateBytes.ptr()[i];
|
||||
}
|
||||
|
||||
double currentHamming = cv::norm(tmp1, tmp2, cv::NORM_HAMMING);
|
||||
if(currentHamming < currentMinDistance) {
|
||||
currentMinDistance = currentHamming;
|
||||
}
|
||||
}
|
||||
Mat b;
|
||||
flip(bits.getMat(), b, 0);
|
||||
candidateBytes = aruco::Dictionary::getByteListFromBits(b);
|
||||
for(unsigned int r = 0; r < nRotations; r++) {
|
||||
cv::Mat tmp1(1, candidateBytes.cols, CV_8UC1, Scalar::all(0));
|
||||
cv::Mat tmp2(1, candidateBytes.cols, CV_8UC1, Scalar::all(0));
|
||||
uchar* rot0 = tmp1.ptr();
|
||||
uchar* rot1 = tmp2.ptr();
|
||||
|
||||
for (int i = 0; i < candidateBytes.cols; ++i) {
|
||||
rot0[i] = bytesList.ptr(id)[r*candidateBytes.cols + i];
|
||||
rot1[i] = candidateBytes.ptr()[i];
|
||||
}
|
||||
|
||||
double currentHamming = cv::norm(tmp1, tmp2, cv::NORM_HAMMING);
|
||||
if (currentHamming < currentMinDistance) {
|
||||
currentMinDistance = currentHamming;
|
||||
}
|
||||
}
|
||||
|
||||
flip(bits.getMat(), b, 1);
|
||||
candidateBytes = aruco::Dictionary::getByteListFromBits(b);
|
||||
for(unsigned int r = 0; r < nRotations; r++) {
|
||||
cv::Mat tmp1(1, candidateBytes.cols, CV_8UC1, Scalar::all(0));
|
||||
cv::Mat tmp2(1, candidateBytes.cols, CV_8UC1, Scalar::all(0));
|
||||
uchar* rot0 = tmp1.ptr();
|
||||
uchar* rot1 = tmp2.ptr();
|
||||
|
||||
for (int i = 0; i < candidateBytes.cols; ++i) {
|
||||
rot0[i] = bytesList.ptr(id)[r*candidateBytes.cols + i];
|
||||
rot1[i] = candidateBytes.ptr()[i];
|
||||
}
|
||||
|
||||
double currentHamming = cv::norm(tmp1, tmp2, cv::NORM_HAMMING);
|
||||
if (currentHamming < currentMinDistance) {
|
||||
currentMinDistance = currentHamming;
|
||||
}
|
||||
}
|
||||
return cvRound(currentMinDistance);
|
||||
}
|
||||
|
||||
static inline aruco::Dictionary generateCustomAsymmetricDictionary(int nMarkers, int markerSize,
|
||||
const aruco::Dictionary &baseDictionary,
|
||||
int randomSeed) {
|
||||
RNG rng((uint64)(randomSeed));
|
||||
|
||||
aruco::Dictionary out;
|
||||
out.markerSize = markerSize;
|
||||
|
||||
// theoretical maximum intermarker distance
|
||||
// See S. Garrido-Jurado, R. Muñoz-Salinas, F. J. Madrid-Cuevas, and M. J. Marín-Jiménez. 2014.
|
||||
// "Automatic generation and detection of highly reliable fiducial markers under occlusion".
|
||||
// Pattern Recogn. 47, 6 (June 2014), 2280-2292. DOI=10.1016/j.patcog.2014.01.005
|
||||
int C = (int)std::floor(float(markerSize * markerSize) / 4.f);
|
||||
int tau = 2 * (int)std::floor(float(C) * 4.f / 3.f);
|
||||
|
||||
// if baseDictionary is provided, calculate its intermarker distance
|
||||
if(baseDictionary.bytesList.rows > 0) {
|
||||
CV_Assert(baseDictionary.markerSize == markerSize);
|
||||
out.bytesList = baseDictionary.bytesList.clone();
|
||||
|
||||
int minDistance = markerSize * markerSize + 1;
|
||||
for(int i = 0; i < out.bytesList.rows; i++) {
|
||||
Mat markerBytes = out.bytesList.rowRange(i, i + 1);
|
||||
Mat markerBits = aruco::Dictionary::getBitsFromByteList(markerBytes, markerSize);
|
||||
minDistance = min(minDistance, _getSelfDistance(markerBits));
|
||||
for(int j = i + 1; j < out.bytesList.rows; j++) {
|
||||
minDistance = min(minDistance, getFlipDistanceToId(out, markerBits, j));
|
||||
}
|
||||
}
|
||||
tau = minDistance;
|
||||
}
|
||||
|
||||
// current best option
|
||||
int bestTau = 0;
|
||||
Mat bestMarker;
|
||||
|
||||
// after these number of unproductive iterations, the best option is accepted
|
||||
const int maxUnproductiveIterations = 5000;
|
||||
int unproductiveIterations = 0;
|
||||
|
||||
while(out.bytesList.rows < nMarkers) {
|
||||
Mat currentMarker(markerSize, markerSize, CV_8UC1, Scalar::all(0));
|
||||
rng.fill(currentMarker, RNG::UNIFORM, 0, 2);
|
||||
|
||||
int selfDistance = _getSelfDistance(currentMarker);
|
||||
int minDistance = selfDistance;
|
||||
|
||||
// if self distance is better or equal than current best option, calculate distance
|
||||
// to previous accepted markers
|
||||
if(selfDistance >= bestTau) {
|
||||
for(int i = 0; i < out.bytesList.rows; i++) {
|
||||
int currentDistance = getFlipDistanceToId(out, currentMarker, i);
|
||||
minDistance = min(currentDistance, minDistance);
|
||||
if(minDistance <= bestTau) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if distance is high enough, accept the marker
|
||||
if(minDistance >= tau) {
|
||||
unproductiveIterations = 0;
|
||||
bestTau = 0;
|
||||
Mat bytes = aruco::Dictionary::getByteListFromBits(currentMarker);
|
||||
out.bytesList.push_back(bytes);
|
||||
} else {
|
||||
unproductiveIterations++;
|
||||
|
||||
// if distance is not enough, but is better than the current best option
|
||||
if(minDistance > bestTau) {
|
||||
bestTau = minDistance;
|
||||
bestMarker = currentMarker;
|
||||
}
|
||||
|
||||
// if number of unproductive iterarions has been reached, accept the current best option
|
||||
if(unproductiveIterations == maxUnproductiveIterations) {
|
||||
unproductiveIterations = 0;
|
||||
tau = bestTau;
|
||||
bestTau = 0;
|
||||
Mat bytes = aruco::Dictionary::getByteListFromBits(bestMarker);
|
||||
out.bytesList.push_back(bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// update the maximum number of correction bits for the generated dictionary
|
||||
out.maxCorrectionBits = (tau - 1) / 2;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
static inline int getMinDistForDict(const aruco::Dictionary& dict) {
|
||||
const int dict_size = dict.bytesList.rows;
|
||||
const int marker_size = dict.markerSize;
|
||||
int minDist = marker_size * marker_size;
|
||||
for (int i = 0; i < dict_size; i++) {
|
||||
Mat row = dict.bytesList.row(i);
|
||||
Mat marker = dict.getBitsFromByteList(row, marker_size);
|
||||
for (int j = 0; j < dict_size; j++) {
|
||||
if (j != i) {
|
||||
minDist = min(dict.getDistanceToId(marker, j), minDist);
|
||||
}
|
||||
}
|
||||
}
|
||||
return minDist;
|
||||
}
|
||||
|
||||
static inline int getMinAsymDistForDict(const aruco::Dictionary& dict) {
|
||||
const int dict_size = dict.bytesList.rows;
|
||||
const int marker_size = dict.markerSize;
|
||||
int minDist = marker_size * marker_size;
|
||||
for (int i = 0; i < dict_size; i++)
|
||||
{
|
||||
Mat row = dict.bytesList.row(i);
|
||||
Mat marker = dict.getBitsFromByteList(row, marker_size);
|
||||
for (int j = 0; j < dict_size; j++)
|
||||
{
|
||||
if (j != i)
|
||||
{
|
||||
minDist = min(getFlipDistanceToId(dict, marker, j), minDist);
|
||||
}
|
||||
}
|
||||
}
|
||||
return minDist;
|
||||
}
|
||||
|
||||
const char* keys =
|
||||
"{@outfile |<none> | Output file with custom dict }"
|
||||
"{r | false | Calculate the metric considering flipped markers }"
|
||||
"{d | | Dictionary Name: DICT_4X4_50, DICT_4X4_100, DICT_4X4_250,"
|
||||
"DICT_4X4_1000, DICT_5X5_50, DICT_5X5_100, DICT_5X5_250, DICT_5X5_1000, "
|
||||
"DICT_6X6_50, DICT_6X6_100, DICT_6X6_250, DICT_6X6_1000, DICT_7X7_50,"
|
||||
"DICT_7X7_100, DICT_7X7_250, DICT_7X7_1000, DICT_ARUCO_ORIGINAL,"
|
||||
"DICT_APRILTAG_16h5, DICT_APRILTAG_25h9, DICT_APRILTAG_36h10,"
|
||||
"DICT_APRILTAG_36h11}"
|
||||
"{nMarkers | | Number of markers in the dictionary }"
|
||||
"{markerSize | | Marker size }"
|
||||
"{cd | | Input file with custom dictionary }";
|
||||
|
||||
const char* about =
|
||||
"This program can be used to calculate the ArUco dictionary metric.\n"
|
||||
"To calculate the metric considering flipped markers use -'r' flag.\n"
|
||||
"This program can be used to create and write the custom ArUco dictionary.\n";
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
CommandLineParser parser(argc, argv, keys);
|
||||
parser.about(about);
|
||||
if(argc < 2) {
|
||||
parser.printMessage();
|
||||
return 0;
|
||||
}
|
||||
string outputFile = parser.get<String>(0);
|
||||
int nMarkers = parser.get<int>("nMarkers");
|
||||
int markerSize = parser.get<int>("markerSize");
|
||||
bool checkFlippedMarkers = parser.get<bool>("r");
|
||||
|
||||
aruco::Dictionary dictionary = aruco::getPredefinedDictionary(0);
|
||||
|
||||
if (parser.has("d")) {
|
||||
string arucoDictName = parser.get<string>("d");
|
||||
cv::aruco::PredefinedDictionaryType arucoDict;
|
||||
if (arucoDictName == "DICT_4X4_50") { arucoDict = cv::aruco::DICT_4X4_50; }
|
||||
else if (arucoDictName == "DICT_4X4_100") { arucoDict = cv::aruco::DICT_4X4_100; }
|
||||
else if (arucoDictName == "DICT_4X4_250") { arucoDict = cv::aruco::DICT_4X4_250; }
|
||||
else if (arucoDictName == "DICT_4X4_1000") { arucoDict = cv::aruco::DICT_4X4_1000; }
|
||||
else if (arucoDictName == "DICT_5X5_50") { arucoDict = cv::aruco::DICT_5X5_50; }
|
||||
else if (arucoDictName == "DICT_5X5_100") { arucoDict = cv::aruco::DICT_5X5_100; }
|
||||
else if (arucoDictName == "DICT_5X5_250") { arucoDict = cv::aruco::DICT_5X5_250; }
|
||||
else if (arucoDictName == "DICT_5X5_1000") { arucoDict = cv::aruco::DICT_5X5_1000; }
|
||||
else if (arucoDictName == "DICT_6X6_50") { arucoDict = cv::aruco::DICT_6X6_50; }
|
||||
else if (arucoDictName == "DICT_6X6_100") { arucoDict = cv::aruco::DICT_6X6_100; }
|
||||
else if (arucoDictName == "DICT_6X6_250") { arucoDict = cv::aruco::DICT_6X6_250; }
|
||||
else if (arucoDictName == "DICT_6X6_1000") { arucoDict = cv::aruco::DICT_6X6_1000; }
|
||||
else if (arucoDictName == "DICT_7X7_50") { arucoDict = cv::aruco::DICT_7X7_50; }
|
||||
else if (arucoDictName == "DICT_7X7_100") { arucoDict = cv::aruco::DICT_7X7_100; }
|
||||
else if (arucoDictName == "DICT_7X7_250") { arucoDict = cv::aruco::DICT_7X7_250; }
|
||||
else if (arucoDictName == "DICT_7X7_1000") { arucoDict = cv::aruco::DICT_7X7_1000; }
|
||||
else if (arucoDictName == "DICT_ARUCO_ORIGINAL") { arucoDict = cv::aruco::DICT_ARUCO_ORIGINAL; }
|
||||
else if (arucoDictName == "DICT_APRILTAG_16h5") { arucoDict = cv::aruco::DICT_APRILTAG_16h5; }
|
||||
else if (arucoDictName == "DICT_APRILTAG_25h9") { arucoDict = cv::aruco::DICT_APRILTAG_25h9; }
|
||||
else if (arucoDictName == "DICT_APRILTAG_36h10") { arucoDict = cv::aruco::DICT_APRILTAG_36h10; }
|
||||
else if (arucoDictName == "DICT_APRILTAG_36h11") { arucoDict = cv::aruco::DICT_APRILTAG_36h11; }
|
||||
else {
|
||||
cout << "incorrect name of aruco dictionary \n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
dictionary = aruco::getPredefinedDictionary(arucoDict);
|
||||
}
|
||||
else if (parser.has("cd")) {
|
||||
FileStorage fs(parser.get<std::string>("cd"), FileStorage::READ);
|
||||
bool readOk = dictionary.readDictionary(fs.root());
|
||||
if(!readOk) {
|
||||
cerr << "Invalid dictionary file" << endl;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else if (outputFile.empty() || nMarkers == 0 || markerSize == 0) {
|
||||
cerr << "Dictionary not specified" << endl;
|
||||
return 0;
|
||||
}
|
||||
if (!outputFile.empty() && nMarkers > 0 && markerSize > 0)
|
||||
{
|
||||
FileStorage fs(outputFile, FileStorage::WRITE);
|
||||
if (checkFlippedMarkers)
|
||||
dictionary = generateCustomAsymmetricDictionary(nMarkers, markerSize, aruco::Dictionary(), 0);
|
||||
else
|
||||
dictionary = aruco::extendDictionary(nMarkers, markerSize, aruco::Dictionary(), 0);
|
||||
dictionary.writeDictionary(fs);
|
||||
}
|
||||
|
||||
if (checkFlippedMarkers) {
|
||||
cout << "Hamming distance: " << getMinAsymDistForDict(dictionary) << endl;
|
||||
}
|
||||
else {
|
||||
cout << "Hamming distance: " << getMinDistForDict(dictionary) << endl;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user