2025, Oct 18 18:16
Правильные ids для ChArUco в OpenCV: как избежать сегфолта
Разбираем ошибку при создании доски ChArUco в OpenCV: почему 2D-сетка id приводит к сегфолту и как сформировать плоский список id для белых клеток.
При создании доски ChArUco с собственными идентификаторами маркеров частая ошибка — передавать сетку id, повторяющую раскладку шахматной доски. Это нередко приводит к ошибке сегментации на этапе построения доски. Причина тонкая, но очевидная: ChArUco размещает по одному маркеру в каждой белой клетке, а не на пересечениях и не в каждой ячейке.
Постановка проблемы
Доска инициализируется двумерным массивом id, рассчитанным по размеру внутренней сетки; на первый взгляд это логично, но не соответствует ожиданиям ChArUco.
def __init__(self, cols=11, rows=8, sq_len=0.015, tag_len=0.011, aruco_set=cv2.aruco.DICT_5X5_250, first_id=0):
    self.cols = cols
    self.rows = rows
    self.sq_len = sq_len
    self.tag_len = tag_len
    self.dict_obj = cv2.aruco.getPredefinedDictionary(aruco_set)
    self.first_id = first_id
    inner_x = cols - 1
    inner_y = rows - 1
    total_tags = inner_x * inner_y
    if first_id + total_tags > self.dict_obj.bytesList.shape[0]:
        raise ValueError(f"Not enough markers in dictionary for board (required: {total_tags})")
    id_grid = np.arange(first_id, first_id + total_tags, dtype=np.int32).reshape(inner_y, inner_x)
    self.board = cv2.aruco.CharucoBoard(
        (self.cols, self.rows),
        self.sq_len,
        self.tag_len,
        self.dict_obj,
        id_grid
    )
Создание доски с таким id_grid может завершиться падением с ошибкой сегментации, даже при использовании показанных выше значений по умолчанию.
Почему происходит сбой
Доска ChArUco размещает маркеры ArUco только внутри белых клеток шахматной доски. Поэтому массив ids должен содержать по одному id на каждую белую клетку, а не двумерную сетку для углов или всех клеток. В документации OpenCV это сформулировано однозначно:
Доска ChArUco — это плоская шахматная доска, где маркеры располагаются внутри белых клеток.
Иными словами, передача двумерного массива с количеством id, превышающим число белых клеток, нарушает внутренние ожидания и вызывает наблюдаемый сбой. Правильный вариант — одномерный массив, длина которого равна числу белых клеток для заданного размера доски.
Рабочий подход
Решение — вычислить количество белых клеток и передать плоскую последовательность id ровно для этих позиций. Целочисленное деление корректно обрабатывает как чётные, так и нечётные размеры. Для чётных сеток белых и чёрных клеток поровну. Для сеток с нечётным числом колонок и строк угловые клетки у ChArUco — чёрные, поэтому белых на одну меньше, чем чёрных, и целочисленное деление естественным образом даёт нужное округление вниз.
import cv2
import numpy as np
cols = 11
rows = 8
cell_size = 0.015
aruco_size = 0.011
dict_kind = cv2.aruco.DICT_5X5_250
base_id = 0
white_count = (cols * rows) // 2
ids = np.arange(base_id, base_id + white_count)
board_obj = cv2.aruco.CharucoBoard(
    (cols, rows),
    cell_size,
    aruco_size,
    cv2.aruco.getPredefinedDictionary(dict_kind),
    ids
)
img = board_obj.generateImage((1000, 1000), None, 0, 1)
cv2.imshow("charuco", img)
cv2.waitKey(0)
Этот код формирует корректное изображение доски ChArUco. Если нужно убедиться, что в выбранном словаре достаточно маркеров, сохраните проверку вместимости из вашей исходной логики, но применяйте её к числу белых клеток.
Почему это важно
Правильно сформированная последовательность id предотвращает низкоуровневые сбои и избавляет от долгой отладки, которая поначалу не кажется связанной с раскладкой id. Кроме того, это гарантирует согласованность описания доски с тем, как детектор ChArUco сопоставляет id белым клеткам. Наконец, так проще планировать диапазоны id, когда нужны воспроизводимые или непересекающиеся диапазоны для нескольких досок.
Практические заметки
Используйте одномерный массив id — по одному на каждую белую клетку — с правильным количеством и порядком. Считайте это количество как (cols * rows) // 2. Не называйте переменную dict, чтобы не затенять встроенный тип dict в Python; лучше выберите имя вроде dict_kind.
Вывод
ChArUco не принимает двумерную сетку id. Ему нужна плоская последовательность, соответствующая только белым клеткам. Формируйте ids, опираясь на целочисленное деление, чтобы получить верное количество как для чётных, так и для нечётных размеров; проверьте, что в словаре хватает маркеров на это число — и вы избавитесь от ошибки сегментации, сохранив генерацию доски прозрачной и управляемой.
Материал основан на вопросе на StackOverflow от Tommy Llewellyn и ответе от simon.