Я работаю над инструментом CV для анализа изображений и использую 4 точки для формирования матрицы преобразования (карты) для коррекции изображения (перспективная проекция — cv.four_point_transform
).
У меня есть набор исходных изображений, полученных с тепловизора с наличием дисторсии. Я успешно применяю конвейер стандартных функций OpenCV. Вы можете увидеть это на рисунках ниже (рис. 1). Обратите внимание, что основной пайплайн состоит из следующих шагов:
К сожалению, мне попадались случаи, когда детектор углов Харриса не справляется и не определяет тупые углы. Я начал тестировать различные подходы, такие как:
Как видите, некоторые из подходов можно использовать, но они образуют ряд паразитных точек. Боюсь, что бороться с ними будет сложнее, чем кажется на первый взгляд (рис. 2).
Рис. 1 — Успешное обнаружение
Рис. 2 - Неудачное обнаружение
@CrisLuengo Я согласен с вами в том, что нет особой причины использовать какой-либо из угловых детекторов. Я выбрал это только потому, что моя форма почти всегда представляет собой простой четырехугольник, а детектор Харриса доступен здесь и сейчас в одной строке. Спасибо за ваши идеи, также планирую реализовать один из способов определения 4-х линий - подгонку прямых линий к точкам контура. К сожалению, я использовал только вероятностные линии Хафа, но не мог вручную подобрать хорошие параметры. Поскольку первый подход к упрощению контура до четырех линий вносит очень большую погрешность.
@CrisLuengo Подгонка четырехугольника к точкам контура - stackoverflow.com/a/41143028/12799969 Подгонка прямых линий к точкам контура - stackoverflow.com/a/59905978/12799969
Вы можете компенсировать дисторсию объектива. вы также можете настроить свои пороги для auxPolyDP. или вы можете отфильтровать углы «неудачного» обнаружения по углу угла.
Я думаю, что надежное обнаружение только необходимых 4 углов для всех возможных изображений является сложной задачей.
Поэтому...
Я бы подумал об использовании некоторого процесса обнаружения углов, который дает «достаточный» результат обнаружения. Где «достаточно» означает, что по крайней мере необходимые 4 угла могут быть обнаружены (не обнаружение их - самая серьезная проблема).
Другими словами, разрешение «Дополнительно могут быть обнаружены некоторые ненужные углы». (конечно, "слишком много" нельзя)
И затем я бы рассмотрел некоторый подход, подобный RANSAC, для получения матрицы преобразования.
Спасибо за вашу идею. Действительно, подход RANSAC выглядит многообещающе, когда у нас есть существенный набор точек. Теоретически для этого случая подойдет, с практической точки зрения я нашел более примитивный подход, о котором расскажу ниже.
Вы могли бы...
компенсировать дисторсию объектива. Тогда у вас вообще не было бы тупых углов, и все края были бы прямыми. Я считаю, что это лучший путь вперед.
см. о настройке фокуса камеры. Да, я понимаю, что это тепловизионная камера. Они могут это сделать.
отрегулируйте пороги для approxPolyDP
, чтобы убрать некоторые тупые углы.
просто фильтровать углы "неудачного" обнаружения по углу угла, отбрасывая достаточно тупые. Либо сделайте это по порогу угла, либо упорядочив все углы и отбросив все, кроме четырех лучших. Вы должны быть чувствительны к длинам ребер, попадающих в угол. Если они короткие, угол в любом случае может не иметь значения.
Спасибо за подсказку интересной функции для упрощения (аппроксимации) ломаных на основе алгоритма Дугласа-Пекера. Это может пригодиться мне в будущем. Я «достаточно» успешно исправил дисторсию на основе калибровки тепловизионной камеры. К тому же мне удалось найти более простой и примитивный подход, поделюсь им в ответе.
Не так давно Дев Аггарвал (@dev-aggarwal) опубликовал интересный и простой подход к определению подогнанного четырехугольника. Этот подход работает для меня. Смотрите оригинальный пост по ссылке.
Теоретическая схема алгоритма представлена в статье Оптимизация усеченного представления для увеличения области изображения объекта.
Дублирую исходный код здесь:
import sympy
def appx_best_fit_ngon(img, n: int = 4) -> list[(int, int)]:
# convex hull of the input mask
img_gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
contours, _ = cv2.findContours(
img_gray, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE
)
hull = cv2.convexHull(contours[0])
hull = np.array(hull).reshape((len(hull), 2))
# to sympy land
hull = [sympy.Point(*pt) for pt in hull]
# run until we cut down to n vertices
while len(hull) > n:
best_candidate = None
# for all edges in hull ( <edge_idx_1>, <edge_idx_2> ) ->
for edge_idx_1 in range(len(hull)):
edge_idx_2 = (edge_idx_1 + 1) % len(hull)
adj_idx_1 = (edge_idx_1 - 1) % len(hull)
adj_idx_2 = (edge_idx_1 + 2) % len(hull)
edge_pt_1 = sympy.Point(*hull[edge_idx_1])
edge_pt_2 = sympy.Point(*hull[edge_idx_2])
adj_pt_1 = sympy.Point(*hull[adj_idx_1])
adj_pt_2 = sympy.Point(*hull[adj_idx_2])
subpoly = sympy.Polygon(adj_pt_1, edge_pt_1, edge_pt_2, adj_pt_2)
angle1 = subpoly.angles[edge_pt_1]
angle2 = subpoly.angles[edge_pt_2]
# we need to first make sure that the sum of the interior angles the edge
# makes with the two adjacent edges is more than 180°
if sympy.N(angle1 + angle2) <= sympy.pi:
continue
# find the new vertex if we delete this edge
adj_edge_1 = sympy.Line(adj_pt_1, edge_pt_1)
adj_edge_2 = sympy.Line(edge_pt_2, adj_pt_2)
intersect = adj_edge_1.intersection(adj_edge_2)[0]
# the area of the triangle we'll be adding
area = sympy.N(sympy.Triangle(edge_pt_1, intersect, edge_pt_2).area)
# should be the lowest
if best_candidate and best_candidate[1] < area:
continue
# delete the edge and add the intersection of adjacent edges to the hull
better_hull = list(hull)
better_hull[edge_idx_1] = intersect
del better_hull[edge_idx_2]
best_candidate = (better_hull, area)
if not best_candidate:
raise ValueError("Could not find the best fit n-gon!")
hull = best_candidate[0]
# back to python land
hull = [(int(x), int(y)) for x, y in hull]
return hull
Используйте следующим образом:
hull = appx_best_fit_ngon(img)
# add lines
for idx in range(len(hull)):
next_idx = (idx + 1) % len(hull)
cv2.line(img, hull[idx], hull[next_idx], (0, 255, 0), 1)
# add point markers
for pt in hull:
cv2.circle(img, pt, 2, (255, 0, 0), 2)
Из минусов вижу только одну дополнительную зависимость от пакета SymPy, но это не критично.
Зачем вам использовать Harris или любой другой угловой детектор? Вам нужно обнаружить четыре прямые линии, а затем найти точки их пересечения. Упрощение контурного многоугольника до тех пор, пока у вас не останется четыре вершины, — это один способ, подгонка четырехугольника к точкам контура — другой, а подгонка прямых линий к точкам контура — другой. Углы трудно разместить точно, линии и параметризованные формы можно подобрать гораздо точнее.