2026, Jan 07 18:02

Почему cv2.convexityDefects в OpenCV падает на float32 и как исправить

Разбираем ошибку cv2.convexityDefects в OpenCV при передаче контура с float32: в чем причина assert, почему нужны int32 и как исправить проблему с типами.

При вызове OpenCV cv2.convexityDefects на контуре, построенном из координат с плавающей точкой, можно столкнуться с непонятной ошибкой-ассертом. На первый взгляд сбой кажется никак не связанным с данными, но первопричина проста и сводится исключительно к типам данных.

Минимальный воспроизводимый пример

Ниже скрипт, который формирует небольшой контур с координатами float32, вычисляет индексы выпуклой оболочки, а затем запрашивает дефекты выпуклости. Он падает на проверке утверждений (assert).

import numpy as np, cv2 as cv
pts = np.array([[0, 0], [1, 0], [1, 1], [0.5, 0.2], [0, 0]], dtype=np.float32)
h_idx = cv.convexHull(pts, returnPoints=False)
defects_out = cv.convexityDefects(pts, h_idx)

Сообщение об ошибке выглядит так:

cv2.error: OpenCV(4.11.0) /io/opencv/modules/imgproc/src/convhull.cpp:319: error: (-215:Assertion failed) npoints >= 0 in function 'convexityDefects'

Что на самом деле происходит

convexityDefects ожидает, что координаты точек контура будут int32, а не числа с плавающей запятой. Хотя это требование явно не прописано в документации, на практике функция нуждается в целочисленных координатах. При подаче float32 внутри срабатывает проверка, и вызов завершается с ошибкой ещё до выдачи результата.

Это согласуется с тем, что контуры в OpenCV действительно имеют целочисленные координаты. В библиотеке нет алгоритмов субпиксельного выделения контуров, а исторически работы с int могли быть дешевле по вычислениям, чем с float. При этом API не предупреждает о несовпадении типов — это реальная недоработка и повод для отчёта об ошибке. Теоретически поддержку чисел с плавающей точкой можно было бы реализовать, но сейчас её нет.

Как исправить: используйте контуры int32

Если создать контур в формате int32, проблема исчезает. Следующий код отрабатывает как положено:

import cv2 as cv
import numpy as np
cnt_i = np.array([[0, 0], [10, 0], [10, 10], [5, 2], [0, 0]], dtype=np.int32)
h_ids = cv.convexHull(cnt_i, returnPoints=False)
d_pts = cv.convexityDefects(cnt_i, h_ids)
print(d_pts)

Вывод:

[[[  2   0   3 543]]]

Результат — это вектор из четырёх элементов: start_index, end_index, farthest_pt_index, fixpt_depth. Последнее значение — фиксированная точка с 8 дробными битами.

Аппроксимация в фиксированной точке (с 8 дробными битами) расстояния между наиболее удалённой точкой контура и оболочкой. То есть, чтобы получить значение глубины в виде числа с плавающей точкой, нужно вычислить fixpt_depth/256.0.

Почему это важно

Подобные рассинхронизации типов легко появляются, когда ваш конвейер естественным образом выдаёт координаты float — например, после нормализации или геометрических преобразований. Режим отказа здесь малоинформативен, и не зная про ожидание int32, можно долго отлаживать не то. Осознание того, что представление контуров в OpenCV изначально целочисленное, помогает избежать ловушек и делает последующие вызовы вроде cv2.convexityDefects предсказуемыми.

Выводы

Если convexityDefects падает на, казалось бы, корректных данных, проверьте dtype контура. Используйте координаты int32 — так вызов будет стабильным, и не забывайте, что глубина возвращается в фиксированной точке с масштабом 1/256. Помня об этих деталях, вы сэкономите время и избежите непонятных сбоев в продакшен-коде обработки изображений.