HOWTO · Python
OpenCV ArUco Markers
One can detect ArUco markers using the cv2.aruco.detectMarkers() function of OpenCV.
On this page
To find ArUco markers in an image, load the image, choose the same ArUco dictionary used to create the markers, and pass both to OpenCV’s cv2.aruco.detectMarkers() function. The result contains corner coordinates and IDs for decoded markers, plus candidate shapes that were rejected.
Detect ArUco Markers with OpenCV
An ArUco marker is a square visual code with a black border and a binary pattern inside. These markers can provide known image points for pose estimation, camera calibration, robotics, and augmented reality; detection alone does not calculate a marker’s pose.
This illustrative Python example first checks that every OpenCV and ArUco attribute it will call is available, then reads an image, selects a predefined dictionary, and calls cv2.aruco.detectMarkers(). Replace the image path with your own file. If the installed build lacks a required attribute, the example prints a guidance message and stops before using it. The example has not been executed here; consult the OpenCV documentation for the API available in your installation.
try:
import cv2
except ImportError as error:
print("OpenCV is unavailable:", type(error).__name__)
else:
aruco = getattr(cv2, "aruco", None)
required_cv2 = ("imread", "putText", "FONT_HERSHEY_SIMPLEX", "imwrite")
required_aruco = (
"DICT_4X4_100",
"getPredefinedDictionary",
"detectMarkers",
"drawDetectedMarkers",
)
if (
aruco is None
or any(not hasattr(cv2, name) for name in required_cv2)
or any(not hasattr(aruco, name) for name in required_aruco)
):
print("The installed OpenCV build lacks an API required by this example")
else:
image_path = "path/to/your-image.png"
image = cv2.imread(image_path)
if image is None:
print("Could not load image:", image_path)
else:
dictionary = aruco.getPredefinedDictionary(aruco.DICT_4X4_100)
corners, ids, rejected = aruco.detectMarkers(image, dictionary)
if ids is None:
print("No markers detected")
else:
for marker_corners, marker_id in zip(corners, ids.flatten()):
print(f"ID {marker_id}: corners {marker_corners[0].tolist()}")
annotated = image.copy()
aruco.drawDetectedMarkers(annotated, corners, ids)
for marker_corners, marker_id in zip(corners, ids.flatten()):
label_position = tuple(marker_corners[0][0].astype(int))
cv2.putText(
annotated,
str(marker_id),
label_position,
cv2.FONT_HERSHEY_SIMPLEX,
0.6,
(0, 255, 0),
2,
)
if cv2.imwrite("annotated-markers.png", annotated):
print("Saved annotated image: annotated-markers.png")
else:
print("Could not save annotated image")
DICT_4X4_100 has 4-by-4 payload bits and contains 100 marker codes. The dictionary’s capacity is distinct from an individual marker ID; each ID identifies one code in that dictionary. Use the same dictionary for generation and detection so the detector checks the expected patterns. The example prints the ID alongside its four corner coordinates, draws outlines around the returned detections, adds each ID near its first corner, and saves the annotated copy. The rejected candidates are available in rejected for diagnostics; they are not confirmed markers.
Understand ArUco Dictionaries, IDs, and Detection Results
A dictionary defines the set of bit patterns OpenCV will recognize. In DICT_4X4_100, each code has 4-by-4 payload bits, and the dictionary contains 100 codes. IDs identify individual entries; the highest valid ID is not the number of entries. The detector searches candidate regions against the selected dictionary, so selecting a different dictionary from the one used to create a marker can cause it to be missed or decoded differently.
detectMarkers() returns three values: corners, ids, and rejected. Each item in corners gives the four image-coordinate points for one detected quadrilateral. The corresponding row in ids gives its marker ID. ids is None when there are no decoded markers. rejected contains quadrilateral candidates that did not decode as a marker in the selected dictionary; it is useful diagnostic information, not proof that every rejected shape is a marker.
The function locates and decodes markers; it does not estimate camera pose. Pose estimation needs camera calibration and the physical marker size in addition to detection results.
Draw and Label Detected Markers
For a quick overlay, pass the image, returned corner arrays, and IDs to cv2.aruco.drawDetectedMarkers(). The example also shows custom labels: it places each ID at that marker’s first corner with cv2.putText(). For more control, draw the quadrilateral edges with cv2.line() or mark the center with cv2.circle(). The related cv2.putText() guide explains text placement and styling.
OpenCV drawing colors use BGR channel order for color images, so (255, 0, 0) is blue, not red. For a grayscale image, use a single intensity value. Keep annotation coordinates within the image and choose a line width and text scale that remain visible at the displayed image size.
Check the Detection Output
Open or display the saved annotated image to confirm that the outlines follow the detected marker corners and that each label matches the corresponding ID printed with those corners. If corners is empty and ids is None, no marker was decoded. Check that the image loaded, the marker is large and clear enough to resolve, and the selected dictionary matches the one used to create it. Blur, glare, perspective, cropping, or a small marker can make a code harder to read; improving the input image may help.
For API details, consult the OpenCV ArUco detection documentation. The example is illustrative and unexecuted, so its messages and saved-image description are not observed output.