1
0
mirror of https://github.com/opencv/opencv.git synced 2026-07-26 13:53:03 +04:00
Files
opencv/modules/objdetect/misc/python/test/test_objdetect_aruco.py
T
Jonas Perolini 5e91b461bc Merge pull request #28289 from JonasPerolini:pr-aruco-identification
Identify ArUco markers based on threshold to reduce false positives #28289

**Goal:** parametrize the current marker identification process (pixel-based majority count) to reduce the number of false positives while maintaining high recall. Useful in high risk scenarios in which false positives are not acceptable. 

**Context:** This PR builds on top of https://github.com/opencv/opencv/pull/23190 in which we've introduced a pixel-based confidence in the marker detection.

**Solution:** Include a new parameter: `validBitIdThreshold` used to identify markers based on the pixel count of each cell. Set the parameter default either to 50% which is equivalent to the current majority count implementation or to 49% which already singnificantly reduces the number of false positives (see details below). 

**Test coverage:** 
- Unit tests: `CV_ArucoDetectionThreshold`, `CV_InvertedArucoDetectionThreshold`
- The impact of `validBitIdThreshold` on false positives was also tested using the benchmark dataset: `MIRFLICKR-25k` https://www.kaggle.com/datasets/skfrost19/mirflickr25k which contains random images without any markers. Every marker detection is a false positive. 

Example of images in the dataset:

![im2048](https://github.com/user-attachments/assets/3e38796b-67ce-44be-a91d-2fd268414515)

![im17627](https://github.com/user-attachments/assets/37253b9f-829d-4bac-b9fc-c844d16f546e)

**Results:** A threshold of 49% already allows to significantly reduce the number of false positives for the dict `DICT_4X4_1000`: 
- `5942` false positives for `validBitIdThreshold = 0.5`
- `629` false positives for `validBitIdThreshold = 0.49` and `0.46` 
   - number of false positives divided by `9.5` when compared to `validBitIdThreshold = 0.5`
- `139` false positives for `validBitIdThreshold = 0.43` and `0.4` 
   - number of false positives divided by `42` when compared to `validBitIdThreshold = 0.5`

Dicts with a higher number of cells are not as impacted since it's much harder to obtain false positives. However, the less cells in a marker the further away it can be reliably detected, so the dict `DICT_4X4_1000` is commonly used.

<img width="1280" height="800" alt="false_positive_image_rate" src="https://github.com/user-attachments/assets/1a0ee16a-221d-443e-835b-022ed6dea6b0" />

In the image attached, the values of `validBitIdThreshold` tested are:  `0.10f, 0.20f, 0.30f, 0.40f, 0.43f, 0.46f, 0.49f, 0.50f, 0.53f, 0.56f, 0.60f, 0.70f, 0.80f, 0.90f`

Summary of the results: [summary.csv](https://github.com/user-attachments/files/24315662/summary.csv)

Note that we can also analyse the number of false positives per marker `id`. For example, here's the histogram for the dict `DICT_4X4_1000`. (The CSV attached contains all the results)

<img width="1440" height="640" alt="false_positive_ids_DICT_4X4_1000_thr0 50" src="https://github.com/user-attachments/assets/af4f3ff8-9b8f-4682-9d51-a090c2610d8c" />

For example, the marker id 17 is detected 252 times with  `validBitIdThreshold = 0.5` and only 34 times with `validBitIdThreshold = 0.49`. Looking at marker 17 (see below), we understand that this simple pattern randomly occurs in images.

<img width="447" height="441" alt="Marker17" src="https://github.com/user-attachments/assets/f5d09227-b39b-4598-94f9-b529f8300703" />

Results for every dict and every `validBitIdThreshold` [per_id.csv](https://github.com/user-attachments/files/24315667/per_id.csv)

**Missing coverage:** there is no labeled dataset with images containing markers to analyse the impact of on the recall  (i.e. look at the true positive rate). For my specific use case (drones) any threshold above `0.4` allows to maintain a high recall in all conditions.

### Pull Request Readiness Checklist


- [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
2026-03-04 08:44:28 +03:00

495 lines
22 KiB
Python

#!/usr/bin/env python
# Python 2/3 compatibility
from __future__ import print_function
import os, tempfile, numpy as np
from math import pi
import cv2 as cv
from tests_common import NewOpenCVTests
def getSyntheticRT(yaw, pitch, distance):
rvec = np.zeros((3, 1), np.float64)
tvec = np.zeros((3, 1), np.float64)
rotPitch = np.array([[-pitch], [0], [0]])
rotYaw = np.array([[0], [yaw], [0]])
rvec, tvec = cv.composeRT(rotPitch, np.zeros((3, 1), np.float64),
rotYaw, np.zeros((3, 1), np.float64))[:2]
tvec = np.array([[0], [0], [distance]])
return rvec, tvec
# see test_aruco_utils.cpp
def projectMarker(img, board, markerIndex, cameraMatrix, rvec, tvec, markerBorder):
markerSizePixels = 100
markerImg = cv.aruco.generateImageMarker(board.getDictionary(), board.getIds()[markerIndex], markerSizePixels, borderBits=markerBorder)
distCoeffs = np.zeros((5, 1), np.float64)
maxCoord = board.getRightBottomCorner()
objPoints = board.getObjPoints()[markerIndex]
for i in range(len(objPoints)):
objPoints[i][0] -= maxCoord[0] / 2
objPoints[i][1] -= maxCoord[1] / 2
objPoints[i][2] -= maxCoord[2] / 2
corners, _ = cv.projectPoints(objPoints, rvec, tvec, cameraMatrix, distCoeffs)
originalCorners = np.array([
[0, 0],
[markerSizePixels, 0],
[markerSizePixels, markerSizePixels],
[0, markerSizePixels],
], np.float32)
transformation = cv.getPerspectiveTransform(originalCorners, corners)
borderValue = 127
aux = cv.warpPerspective(markerImg, transformation, img.shape, None, cv.INTER_NEAREST, cv.BORDER_CONSTANT, borderValue)
assert(img.shape == aux.shape)
mask = (aux == borderValue).astype(np.uint8)
img = img * mask + aux * (1 - mask)
return img
def projectChessboard(squaresX, squaresY, squareSize, imageSize, cameraMatrix, rvec, tvec):
img = np.ones(imageSize, np.uint8) * 255
distCoeffs = np.zeros((5, 1), np.float64)
for y in range(squaresY):
startY = y * squareSize
for x in range(squaresX):
if (y % 2 != x % 2):
continue
startX = x * squareSize
squareCorners = np.array([[startX - squaresX*squareSize/2,
startY - squaresY*squareSize/2,
0]], np.float32)
squareCorners = np.stack((squareCorners[0],
squareCorners[0] + [squareSize, 0, 0],
squareCorners[0] + [squareSize, squareSize, 0],
squareCorners[0] + [0, squareSize, 0]))
projectedCorners, _ = cv.projectPoints(squareCorners, rvec, tvec, cameraMatrix, distCoeffs)
projectedCorners = projectedCorners.astype(np.int64)
projectedCorners = projectedCorners.reshape(1, 4, 2)
img = cv.fillPoly(img, [projectedCorners], 0)
return img
def projectCharucoBoard(board, cameraMatrix, yaw, pitch, distance, imageSize, markerBorder):
rvec, tvec = getSyntheticRT(yaw, pitch, distance)
img = np.ones(imageSize, np.uint8) * 255
for indexMarker in range(len(board.getIds())):
img = projectMarker(img, board, indexMarker, cameraMatrix, rvec, tvec, markerBorder)
chessboard = projectChessboard(board.getChessboardSize()[0], board.getChessboardSize()[1],
board.getSquareLength(), imageSize, cameraMatrix, rvec, tvec)
chessboard = (chessboard != 0).astype(np.uint8)
img = img * chessboard
return img, rvec, tvec
class aruco_objdetect_test(NewOpenCVTests):
def test_board(self):
p1 = np.array([[0, 0, 0], [0, 1, 0], [1, 1, 0], [1, 0, 0]], dtype=np.float32)
p2 = np.array([[1, 0, 0], [1, 1, 0], [2, 1, 0], [2, 0, 0]], dtype=np.float32)
objPoints = np.array([p1, p2])
dictionary = cv.aruco.getPredefinedDictionary(cv.aruco.DICT_4X4_50)
ids = np.array([0, 1])
board = cv.aruco.Board(objPoints, dictionary, ids)
np.testing.assert_array_equal(board.getIds().squeeze(), ids)
np.testing.assert_array_equal(np.ravel(np.array(board.getObjPoints())), np.ravel(np.concatenate([p1, p2])))
def test_idsAccessibility(self):
ids = np.arange(17)
rev_ids = ids[::-1]
aruco_dict = cv.aruco.getPredefinedDictionary(cv.aruco.DICT_5X5_250)
board = cv.aruco.CharucoBoard((7, 5), 1, 0.5, aruco_dict)
np.testing.assert_array_equal(board.getIds().squeeze(), ids)
board = cv.aruco.CharucoBoard((7, 5), 1, 0.5, aruco_dict, rev_ids)
np.testing.assert_array_equal(board.getIds().squeeze(), rev_ids)
board = cv.aruco.CharucoBoard((7, 5), 1, 0.5, aruco_dict, ids)
np.testing.assert_array_equal(board.getIds().squeeze(), ids)
def test_identify(self):
aruco_dict = cv.aruco.getPredefinedDictionary(cv.aruco.DICT_4X4_50)
expected_idx = 9
expected_rotation = 2
bit_marker = np.array([[0, 1, 1, 0], [1, 0, 1, 0], [1, 1, 1, 1], [0, 0, 1, 1]], dtype=np.uint8)
check, idx, rotation = aruco_dict.identify(bit_marker, 0)
self.assertTrue(check, True)
self.assertEqual(idx, expected_idx)
self.assertEqual(rotation, expected_rotation)
def test_getDistanceToId(self):
aruco_dict = cv.aruco.getPredefinedDictionary(cv.aruco.DICT_4X4_50)
idx = 7
rotation = 3
bit_marker = np.array([[0, 1, 0, 1], [0, 1, 1, 1], [1, 1, 0, 0], [0, 1, 0, 0]], dtype=np.uint8)
dist = aruco_dict.getDistanceToId(bit_marker, idx)
self.assertEqual(dist, 0)
def test_aruco_detector(self):
aruco_params = cv.aruco.DetectorParameters()
aruco_dict = cv.aruco.getPredefinedDictionary(cv.aruco.DICT_4X4_250)
aruco_detector = cv.aruco.ArucoDetector(aruco_dict, aruco_params)
id = 2
marker_size = 100
offset = 10
img_marker = cv.aruco.generateImageMarker(aruco_dict, id, marker_size, aruco_params.markerBorderBits)
img_marker = np.pad(img_marker, pad_width=offset, mode='constant', constant_values=255)
gold_corners = np.array([[offset, offset],[marker_size+offset-1.0,offset],
[marker_size+offset-1.0,marker_size+offset-1.0],
[offset, marker_size+offset-1.0]], dtype=np.float32)
corners, ids, rejected = aruco_detector.detectMarkers(img_marker)
self.assertEqual(1, len(ids))
self.assertEqual(id, ids[0])
for i in range(0, len(corners)):
np.testing.assert_array_equal(gold_corners, corners[i].reshape(4, 2))
def test_aruco_detector_refine(self):
aruco_params = cv.aruco.DetectorParameters()
aruco_dict = cv.aruco.getPredefinedDictionary(cv.aruco.DICT_4X4_250)
aruco_detector = cv.aruco.ArucoDetector(aruco_dict, aruco_params)
board_size = (3, 4)
board = cv.aruco.GridBoard(board_size, 5.0, 1.0, aruco_dict)
board_image = board.generateImage((board_size[0]*50, board_size[1]*50), marginSize=10)
corners, ids, rejected = aruco_detector.detectMarkers(board_image)
self.assertEqual(board_size[0]*board_size[1], len(ids))
part_corners, part_ids, part_rejected = corners[:-1], ids[:-1], list(rejected)
part_rejected.append(corners[-1])
refine_corners, refine_ids, refine_rejected, recovered_ids = aruco_detector.refineDetectedMarkers(board_image, board, part_corners, part_ids, part_rejected)
self.assertEqual(board_size[0] * board_size[1], len(refine_ids))
self.assertEqual(1, len(recovered_ids))
self.assertEqual(ids[-1], refine_ids[-1])
self.assertEqual((1, 4, 2), refine_corners[0].shape)
np.testing.assert_array_equal(corners, refine_corners)
def test_charuco_refine(self):
aruco_dict = cv.aruco.getPredefinedDictionary(cv.aruco.DICT_6X6_50)
board_size = (3, 4)
board = cv.aruco.CharucoBoard(board_size, 1., .7, aruco_dict)
aruco_detector = cv.aruco.ArucoDetector(aruco_dict)
charuco_detector = cv.aruco.CharucoDetector(board)
cell_size = 100
image = board.generateImage((cell_size*board_size[0], cell_size*board_size[1]))
camera = np.array([[1, 0, 0.5],
[0, 1, 0.5],
[0, 0, 1]])
dist = np.array([0, 0, 0, 0, 0], dtype=np.float32).reshape(1, -1)
# generate gold corners of the ArUco markers for the test
gold_corners = np.array(board.getObjPoints())[:, :, 0:2]*cell_size
# detect corners
markerCorners, markerIds, _ = aruco_detector.detectMarkers(image)
# test refine
rejected = [markerCorners[-1]]
markerCorners, markerIds = markerCorners[:-1], markerIds[:-1]
markerCorners, markerIds, _, _ = aruco_detector.refineDetectedMarkers(image, board, markerCorners, markerIds,
rejected, cameraMatrix=camera, distCoeffs=dist)
charucoCorners, charucoIds, _, _ = charuco_detector.detectBoard(image, markerCorners=markerCorners,
markerIds=markerIds)
self.assertEqual(len(charucoIds), 6)
self.assertEqual(len(markerIds), 6)
for i, id in enumerate(markerIds.reshape(-1)):
np.testing.assert_allclose(gold_corners[id], markerCorners[i].reshape(4, 2), 0.01, 1.)
def test_write_read_dictionary(self):
try:
aruco_dict = cv.aruco.getPredefinedDictionary(cv.aruco.DICT_5X5_50)
markers_gold = aruco_dict.bytesList
# write aruco_dict
fd, filename = tempfile.mkstemp(prefix="opencv_python_aruco_dict_", suffix=".yml")
os.close(fd)
fs_write = cv.FileStorage(filename, cv.FileStorage_WRITE)
aruco_dict.writeDictionary(fs_write)
fs_write.release()
# reset aruco_dict
aruco_dict = cv.aruco.getPredefinedDictionary(cv.aruco.DICT_6X6_250)
# read aruco_dict
fs_read = cv.FileStorage(filename, cv.FileStorage_READ)
aruco_dict.readDictionary(fs_read.root())
fs_read.release()
# check equal
self.assertEqual(aruco_dict.markerSize, 5)
self.assertEqual(aruco_dict.maxCorrectionBits, 3)
np.testing.assert_array_equal(aruco_dict.bytesList, markers_gold)
finally:
if os.path.exists(filename):
os.remove(filename)
def test_charuco_detector(self):
aruco_dict = cv.aruco.getPredefinedDictionary(cv.aruco.DICT_4X4_250)
board_size = (3, 3)
board = cv.aruco.CharucoBoard(board_size, 1.0, .8, aruco_dict)
charuco_detector = cv.aruco.CharucoDetector(board)
cell_size = 100
image = board.generateImage((cell_size*board_size[0], cell_size*board_size[1]))
# Note: Expected values adjusted by -0.5px after fixing the systematic offset bug in charuco_detector.cpp
# The fix removes the incorrect +0.5 offset that was added after cornerSubPix
list_gold_corners = []
for i in range(1, board_size[0]):
for j in range(1, board_size[1]):
list_gold_corners.append((j*cell_size - 0.5, i*cell_size - 0.5))
gold_corners = np.array(list_gold_corners, dtype=np.float32)
charucoCorners, charucoIds, markerCorners, markerIds = charuco_detector.detectBoard(image)
self.assertEqual(len(charucoIds), 4)
for i in range(0, 4):
self.assertEqual(charucoIds[i], i)
np.testing.assert_allclose(gold_corners, charucoCorners.reshape(-1, 2), 0.01, 0.1)
def test_detect_diamonds(self):
aruco_dict = cv.aruco.getPredefinedDictionary(cv.aruco.DICT_6X6_250)
board_size = (3, 3)
board = cv.aruco.CharucoBoard(board_size, 1.0, .8, aruco_dict)
charuco_detector = cv.aruco.CharucoDetector(board)
cell_size = 120
image = board.generateImage((cell_size*board_size[0], cell_size*board_size[1]))
# Note: Expected values adjusted by -0.5px after fixing the systematic offset bug in charuco_detector.cpp
# The fix removes the incorrect +0.5 offset that was added after cornerSubPix
list_gold_corners = [(cell_size - 0.5, cell_size - 0.5), (2*cell_size - 0.5, cell_size - 0.5),
(2*cell_size - 0.5, 2*cell_size - 0.5), (cell_size - 0.5, 2*cell_size - 0.5)]
gold_corners = np.array(list_gold_corners, dtype=np.float32)
diamond_corners, diamond_ids, marker_corners, marker_ids = charuco_detector.detectDiamonds(image)
self.assertEqual(diamond_ids.size, 4)
self.assertEqual(marker_ids.size, 4)
for i in range(0, 4):
self.assertEqual(diamond_ids[0][0][i], i)
np.testing.assert_allclose(gold_corners, np.array(diamond_corners, dtype=np.float32).reshape(-1, 2), 0.01, 0.1)
# check no segfault when cameraMatrix or distCoeffs are not initialized
def test_charuco_no_segfault_params(self):
dictionary = cv.aruco.getPredefinedDictionary(cv.aruco.DICT_4X4_1000)
board = cv.aruco.CharucoBoard((10, 10), 0.019, 0.015, dictionary)
charuco_parameters = cv.aruco.CharucoParameters()
detector = cv.aruco.CharucoDetector(board)
detector.setCharucoParameters(charuco_parameters)
self.assertIsNone(detector.getCharucoParameters().cameraMatrix)
self.assertIsNone(detector.getCharucoParameters().distCoeffs)
def test_charuco_no_segfault_params_constructor(self):
dictionary = cv.aruco.getPredefinedDictionary(cv.aruco.DICT_4X4_1000)
board = cv.aruco.CharucoBoard((10, 10), 0.019, 0.015, dictionary)
charuco_parameters = cv.aruco.CharucoParameters()
detector = cv.aruco.CharucoDetector(board, charucoParams=charuco_parameters)
self.assertIsNone(detector.getCharucoParameters().cameraMatrix)
self.assertIsNone(detector.getCharucoParameters().distCoeffs)
# similar to C++ test CV_CharucoDetection.accuracy
def test_charuco_detector_accuracy(self):
iteration = 0
cameraMatrix = np.eye(3, 3, dtype=np.float64)
imgSize = (500, 500)
params = cv.aruco.DetectorParameters()
params.minDistanceToBorder = 3
params.validBitIdThreshold = 0.5
board = cv.aruco.CharucoBoard((4, 4), 0.03, 0.015, cv.aruco.getPredefinedDictionary(cv.aruco.DICT_6X6_250))
detector = cv.aruco.CharucoDetector(board, detectorParams=params)
cameraMatrix[0, 0] = cameraMatrix[1, 1] = 600
cameraMatrix[0, 2] = imgSize[0] / 2
cameraMatrix[1, 2] = imgSize[1] / 2
# for different perspectives
distCoeffs = np.zeros((5, 1), dtype=np.float64)
for distance in [0.2, 0.4]:
for yaw in range(-55, 51, 25):
for pitch in range(-55, 51, 25):
markerBorder = iteration % 2 + 1
iteration += 1
# create synthetic image
img, rvec, tvec = projectCharucoBoard(board, cameraMatrix, yaw * pi / 180, pitch * pi / 180, distance, imgSize, markerBorder)
params.markerBorderBits = markerBorder
detector.setDetectorParameters(params)
if (iteration % 2 != 0):
charucoParameters = cv.aruco.CharucoParameters()
charucoParameters.cameraMatrix = cameraMatrix
charucoParameters.distCoeffs = distCoeffs
detector.setCharucoParameters(charucoParameters)
charucoCorners, charucoIds, corners, ids = detector.detectBoard(img)
self.assertGreater(len(ids), 0)
copyChessboardCorners = board.getChessboardCorners()
copyChessboardCorners -= np.array(board.getRightBottomCorner()) / 2
projectedCharucoCorners, _ = cv.projectPoints(copyChessboardCorners, rvec, tvec, cameraMatrix, distCoeffs)
if charucoIds is None:
# Detection can fail at extreme viewing angles
self.assertTrue(abs(yaw) >= 45 or abs(pitch) >= 45,
f"Detection failed unexpectedly at yaw={yaw}, pitch={pitch}")
continue
for i in range(len(charucoIds)):
currentId = charucoIds[i]
self.assertLess(currentId, len(board.getChessboardCorners()))
reprErr = cv.norm(charucoCorners[i] - projectedCharucoCorners[currentId])
self.assertLessEqual(reprErr, 5)
def test_aruco_match_image_points(self):
aruco_dict = cv.aruco.getPredefinedDictionary(cv.aruco.DICT_4X4_50)
board_size = (3, 4)
board = cv.aruco.GridBoard(board_size, 5.0, 1.0, aruco_dict)
aruco_corners = np.array(board.getObjPoints())[:, :, :2]
aruco_ids = board.getIds()
obj_points, img_points = board.matchImagePoints(aruco_corners, aruco_ids)
aruco_corners = aruco_corners.reshape(-1, 2)
self.assertEqual(aruco_corners.shape[0], obj_points.shape[0])
self.assertEqual(img_points.shape[0], obj_points.shape[0])
self.assertEqual(2, img_points.shape[2])
np.testing.assert_array_equal(aruco_corners, obj_points[:, :, :2].reshape(-1, 2))
def test_charuco_match_image_points(self):
aruco_dict = cv.aruco.getPredefinedDictionary(cv.aruco.DICT_4X4_50)
board_size = (3, 4)
board = cv.aruco.CharucoBoard(board_size, 5.0, 1.0, aruco_dict)
chessboard_corners = np.array(board.getChessboardCorners())[:, :2]
chessboard_ids = board.getIds()
obj_points, img_points = board.matchImagePoints(chessboard_corners, chessboard_ids)
self.assertEqual(chessboard_corners.shape[0], obj_points.shape[0])
self.assertEqual(img_points.shape[0], obj_points.shape[0])
self.assertEqual(2, img_points.shape[2])
np.testing.assert_array_equal(chessboard_corners, obj_points[:, :, :2].reshape(-1, 2))
def test_draw_detected_markers(self):
detected_points = [[[10, 10], [50, 10], [50, 50], [10, 50]]]
img = np.zeros((60, 60), dtype=np.uint8)
# add extra dimension in Python to create Nx4 Mat with 2 channels
points1 = np.array(detected_points).reshape(-1, 4, 1, 2)
img = cv.aruco.drawDetectedMarkers(img, points1, borderColor=255)
# check that the marker borders are painted
contours, _ = cv.findContours(img, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
self.assertEqual(len(contours), 1)
self.assertEqual(img[10, 10], 255)
self.assertEqual(img[50, 10], 255)
self.assertEqual(img[50, 50], 255)
self.assertEqual(img[10, 50], 255)
# must throw Exception without extra dimension
points2 = np.array(detected_points)
with self.assertRaises(Exception):
img = cv.aruco.drawDetectedMarkers(img, points2, borderColor=255)
def test_draw_detected_charuco(self):
detected_points = [[[10, 10], [50, 10], [50, 50], [10, 50]]]
img = np.zeros((60, 60), dtype=np.uint8)
# add extra dimension in Python to create Nx1 Mat with 2 channels
points = np.array(detected_points).reshape(-1, 1, 2)
img = cv.aruco.drawDetectedCornersCharuco(img, points, cornerColor=255)
# check that the 4 charuco corners are painted
contours, _ = cv.findContours(img, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
self.assertEqual(len(contours), 4)
for contour in contours:
center_x = round(np.average(contour[:, 0, 0]))
center_y = round(np.average(contour[:, 0, 1]))
center = [center_x, center_y]
self.assertTrue(center in detected_points[0])
# must throw Exception without extra dimension
points2 = np.array(detected_points)
with self.assertRaises(Exception):
img = cv.aruco.drawDetectedCornersCharuco(img, points2, borderColor=255)
def test_draw_detected_diamonds(self):
detected_points = [[[10, 10], [50, 10], [50, 50], [10, 50]]]
img = np.zeros((60, 60), dtype=np.uint8)
# add extra dimension in Python to create Nx4 Mat with 2 channels
points = np.array(detected_points).reshape(-1, 4, 1, 2)
img = cv.aruco.drawDetectedDiamonds(img, points, borderColor=255)
# check that the diamonds borders are painted
contours, _ = cv.findContours(img, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
self.assertEqual(len(contours), 1)
self.assertEqual(img[10, 10], 255)
self.assertEqual(img[50, 10], 255)
self.assertEqual(img[50, 50], 255)
self.assertEqual(img[10, 50], 255)
# must throw Exception without extra dimension
points2 = np.array(detected_points)
with self.assertRaises(Exception):
img = cv.aruco.drawDetectedDiamonds(img, points2, borderColor=255)
def test_multi_dict_arucodetector(self):
aruco_params = cv.aruco.DetectorParameters()
aruco_dicts = [
cv.aruco.getPredefinedDictionary(cv.aruco.DICT_4X4_250),
cv.aruco.getPredefinedDictionary(cv.aruco.DICT_5X5_250)
]
aruco_detector = cv.aruco.ArucoDetector(aruco_dicts, aruco_params)
id = 2
marker_size = 100
offset = 10
img_marker1 = cv.aruco.generateImageMarker(aruco_dicts[0], id, marker_size, aruco_params.markerBorderBits)
img_marker1 = np.pad(img_marker1, pad_width=offset, mode='constant', constant_values=255)
img_marker2 = cv.aruco.generateImageMarker(aruco_dicts[1], id, marker_size, aruco_params.markerBorderBits)
img_marker2 = np.pad(img_marker2, pad_width=offset, mode='constant', constant_values=255)
img_markers = np.concatenate((img_marker1, img_marker2), axis=1)
corners, ids, rejected, dictIndices = aruco_detector.detectMarkersMultiDict(img_markers)
self.assertEqual(2, len(ids))
self.assertEqual(id, ids[0])
self.assertEqual(id, ids[1])
self.assertEqual(2, len(dictIndices))
self.assertEqual(0, dictIndices[0])
self.assertEqual(1, dictIndices[1])
if __name__ == '__main__':
NewOpenCVTests.bootstrap()