вычисление внутренних углов многоугольника

#opencv

Вопрос:

У меня есть следующий массив массивов:

 [[[623 284]]

 [[526 256]]

 [[532 189]]

 [[504 166]]

 [[323 175]]

 [[276 219]]

 [[119 221]]

 [[  1 272]]

 [[  0 473]]

 [[615 479]]]
 

Это мои точки (координаты) многоугольника. Что мне нужно сделать, так это перебрать этот массив, взяв каждую точку для вычисления каждого внутреннего угла в многоугольнике. У меня есть эта функция для вычисления угла между 3 точками: getAngle((px1, py1), (px2, py2), (px3, py3)) .
В принципе, я хочу поместить эту функцию в некоторый цикл, который будет отбирать точки выборочно, например:

 getAngle((623, 284), (526, 256), (532, 189)),
getAngle((526, 256), (532, 189), (504, 166)),
getAngle((532, 189), (504, 166), (323, 175)),
 

и так далее до самого конца…
Какой это должен быть цикл и как его реализовать?

Комментарии:

1. вместо того, чтобы повторять, вы можете попытаться написать своего рода рекурсивную функцию, которая перемещает своего рода 3-узел кадра вниз по строке при вычислении текущего значения с тремя активными узлами

2. это похоже на массив numpy .

Ответ №1:

Поскольку у вас есть точки (в виде массива чисел), и вам нужны углы (углы со знаком), вот полное решение.

  • вычисление векторов
  • используйте перекрестное изделие: перекрестный продукт

При этом используются операции со всем массивом.

Обратите внимание на очень верное замечание Ива Дауста в комментариях. Острые углы не обрабатываются должным образом этой демонстрацией, но они могут быть с дополнительной проверкой векторов и коррекцией угла.

 import numpy as np

points = np.array([
 [[623, 284]],
 [[526, 256]],
 [[532, 189]],
 [[504, 166]],
 [[323, 175]],
 [[276, 219]],
 [[119, 221]],
 [[  1, 272]],
 [[  0, 473]],
 [[615, 479]]])
# funny shape because OpenCV. it's a Nx1 vector of 2-channel elements
# fix that up, remove the silly dimension
points.shape = (-1, 2)

# the vectors are differences of coordinates
# a points into the point, b out of the point
a = points - np.roll(points, 1, axis=0)
b = np.roll(a, -1, axis=0) # same but shifted

# we'll need to know the length of those vectors
alengths = np.linalg.norm(a, axis=1)
blengths = np.linalg.norm(b, axis=1)

# we need only the length of the cross product,
# and we work in 2D space anyway (not 3D),
# so the cross product can't result in a vector, just its z-component
crossproducts = np.cross(a, b) / alengths / blengths

angles = np.arcsin(crossproducts)
angles_degrees = angles / np.pi * 180

print("angles in degrees:")
print(angles_degrees)

# this is just for printing/displaying, not useful in code
print("point and angle:")
print(np.hstack([points, angles_degrees.reshape((-1, 1))]))

 
 angles in degrees:
[-76.24798  79.01601 -55.71665 -42.24728 -40.2652   42.38197 -22.64432 -66.34078 -89.72609 -88.20969]
point and angle:
[[623.      284.      -76.24798]
 [526.      256.       79.01601]
 [532.      189.      -55.71665]
 [504.      166.      -42.24728]
 [323.      175.      -40.2652 ]
 [276.      219.       42.38197]
 [119.      221.      -22.64432]
 [  1.      272.      -66.34078]
 [  0.      473.      -89.72609]
 [615.      479.      -88.20969]]
 

какой-то рисунок:

 import cv2 as cv

canvas = np.zeros((600, 700, 3)) # floats, range 0..1

cv.polylines(canvas, [points], isClosed=True, color=(1,1,1))

for i,angle in enumerate(angles_degrees):
    cv.circle(canvas, center=tuple(points[i]), radius=5, color=(0,0,1), thickness=cv.FILLED)
    cv.putText(
        canvas,
        f"{angle: .1f}",
        org=tuple(points[i]),
        fontFace=cv.FONT_HERSHEY_SIMPLEX,
        fontScale=0.75,
        color=(0,1,1),
        thickness=2)

cv.imshow("canvas", canvas)
cv.waitKey(-1)
cv.destroyWindow("canvas")
 

рисунок

Комментарии:

1. Хорошая идея-предварительно вычислить попарные различия. Но имейте в виду, что использование перекрестного продукта возвращает угол только в двух квадрантах.

2. @Кристоф Раквиц, всегда помнит, что на этом сайте вы должны ответить на вопрос ОП, не думайте о вопросе и отвечайте, как вам нравится, если вы считаете, что ваш ответ хорош, создайте новый вопрос и ответьте сами.

3. @ChristophRackwitz, ты молодец, и твой ответ очень хорош, но не для этого вопроса

4. вопрос явно говорил о том, что цель была достигнута. как индексировать массив и писать цикл на python, это описано в каждом учебнике. из всех «причудливых» ответов я выбрал свой, чтобы показать что-то мощное (numpy и математика). Я думаю, что более ценно сообщать людям информацию, которую они не ожидали, но попросили бы, если бы знали. в любом случае, если вам интересно, я здесь ничего не снижал.


Ответ №2:

more_itertools это хорошая библиотека для подобных вещей:

 import more_itertools

points = [[623,284], [526, 256], [532, 189], [504, 166], [323, 175], [276, 219], [119, 221], [  1, 272], [  0, 473]]
for triplet in more_itertools.windowed(points, n=3, step=3):
    getAngle(*triplet)
 

Ответ №3:

Как насчет простого

 for i in range(len(A) - 2):
    getAngle(A[0][i], A[0][i 1], A[0][i 2])
 

?


Erratum:

 for i in range(len(A) - 2):
    getAngle(A[i][0], A[i 1][0], A[i 2][0])
 

Комментарии:

1. IndexError: index 1 is out of bounds for axis 0 with size 1

2. @user16731999: да, просто нужно поменять местами индексации.

3. это самое простое решение, следовательно, должно быть предпочтительнее других, которые добавляют ненужную сложность

Ответ №4:

попробуйте это:

 lst = [[[623,284]] , [[526, 256]] , [[532, 189]]]

tuple(map((lambda x : tuple(x[0])), lst))
 

выход:

 ((623, 284), (526, 256), (532, 189))
 

ОТРЕДАКТИРУЙТЕ как свой комментарий:

 lst = [[[623,284]], [[526, 256]], [[532, 189]], [[504, 166]], [[323, 175]], [[276, 219]], [[119, 221]], [[  1, 272]], [[  0, 473]] , [[615, 479]]]


for l in range(0,len(lst)-2):
    f , s , t = (tuple(map((lambda x : tuple(x[0])), lst[l:l 3])))
    getAngle(f , s, t)
 

Комментарии:

1. Мне нужно вызвать функцию внутри цикла foor, а не создавать кортеж из массива.

2. это дает мне ошибку, что функции getAngle нужно 3 аргумента, но есть только один

3. @user16731999 попробуйте сейчас, я добавлю одну строку

4. @user16731999, хорошо, подожди секунду, я понимаю

5. прежде всего, я отредактировал ваш код как for l in range(0,len(lst)-2,3): потому, что он не работал. Во-вторых, да, это работает, но не так, как я хочу. он повторяется через КАЖДЫЙ кортеж, поэтому он берет ((623, 284), (526, 256), (532, 189)) , потом ((504, 166), (323, 175), (276, 219)) , потом ((119, 221), (1, 272), (0, 473)) и останавливается. Я описал в своем вопросе, как я хочу, чтобы это повторялось.