У вас есть три элемента информации, определяющие дугу окружности: две точки на окружности (определяющие хорду этой окружности) и расстояние по выпуклости (называемое стрелкой дуги окружности). ).
См. следующий рисунок:
![Обозначение дуги окружности](https://i.stack.imgur.com/J4ejS.png)
Здесь s — сагитта, l — половина длины хорды, а r — конечно же, радиус. Другими важными неотмеченными позициями являются точки, в которых хорда пересекает окружность, точка, в которой стрела пересекает окружность, и центр окружности, от которого отходит радиус.
Для функции OpenCV ellipse()
мы будем использовать следующий прототип:
cv2.ellipse(img, center, axes, angle, startAngle, endAngle, color[, thickness[, lineType[, shift]]]) → img
где большинство параметров описываются следующим графиком:
![параметры cv2.ellipse](https://i.stack.imgur.com/ccIeK.png)
Поскольку мы рисуем дугу окружности, а не эллипса, большая и малая оси будут иметь одинаковый размер, и нет никакой разницы при вращении, поэтому оси будут просто (radius, radius)
, а angle
должны быть равны нулю для упрощения. Тогда единственные параметры, которые нам нужны, это центр окружности, радиус, а также начальный и конечный углы рисования, соответствующие точкам хорды. Углы легко вычислить (это просто углы на окружности). Итак, в конечном итоге нам нужно найти радиус и центр круга.
Найти радиус и центр — это то же самое, что найти уравнение окружности, поэтому существует множество способов сделать это. Но поскольку мы здесь программируем, самый простой способ, IMO, — определить третью точку на круге, где стрела касается круга, а затем найти круг по этим трем точкам.
Итак, сначала нам нужно получить середину хорды, провести перпендикулярную линию к этой средней точке и продлить ее на длину сагитты, чтобы добраться до этой третьей точки, но это достаточно просто. Я начну с pt1 = (x1, y1)
и pt2 = (x2, y2)
в качестве двух точек на круге, а sagitta
— это «глубина выпуклости» (т.е. параметры, которые у вас есть):
# extract point coordinates
x1, y1 = pt1
x2, y2 = pt2
# find normal from midpoint, follow by length sagitta
n = np.array([y2 - y1, x1 - x2])
n_dist = np.sqrt(np.sum(n**2))
if np.isclose(n_dist, 0):
# catch error here, d(pt1, pt2) ~ 0
print('Error: The distance between pt1 and pt2 is too small.')
n = n/n_dist
x3, y3 = (np.array(pt1) + np.array(pt2))/2 + sagitta * n
Теперь у нас есть третья точка на окружности. Обратите внимание, что сагитта — это всего лишь некоторая длина, поэтому она может идти в любом направлении — если бы сагитта была отрицательной, она шла бы в одном направлении от хорды, а если бы она была положительной, она шла бы в другом направлении. Не уверен, так ли это расстояние дается вам или нет.
Затем мы можем просто использовать определители для определения радиуса и центра.
# calculate the circle from three points
# see https://math.stackexchange.com/a/1460096/246399
A = np.array([
[x1**2 + y1**2, x1, y1, 1],
[x2**2 + y2**2, x2, y2, 1],
[x3**2 + y3**2, x3, y3, 1]])
M11 = np.linalg.det(A[:, (1, 2, 3)])
M12 = np.linalg.det(A[:, (0, 2, 3)])
M13 = np.linalg.det(A[:, (0, 1, 3)])
M14 = np.linalg.det(A[:, (0, 1, 2)])
if np.isclose(M11, 0):
# catch error here, the points are collinear (sagitta ~ 0)
print('Error: The third point is collinear.')
cx = 0.5 * M12/M11
cy = -0.5 * M13/M11
radius = np.sqrt(cx**2 + cy**2 + M14/M11)
Затем, наконец, поскольку нам нужны начальный и конечный углы для рисования эллипса с помощью OpenCV, мы можем просто использовать atan2()
, чтобы получить углы от центра к начальным точкам:
# calculate angles of pt1 and pt2 from center of circle
pt1_angle = 180*np.arctan2(y1 - cy, x1 - cx)/np.pi
pt2_angle = 180*np.arctan2(y2 - cy, x2 - cx)/np.pi
Итак, я упаковал все это в одну функцию:
def convert_arc(pt1, pt2, sagitta):
# extract point coordinates
x1, y1 = pt1
x2, y2 = pt2
# find normal from midpoint, follow by length sagitta
n = np.array([y2 - y1, x1 - x2])
n_dist = np.sqrt(np.sum(n**2))
if np.isclose(n_dist, 0):
# catch error here, d(pt1, pt2) ~ 0
print('Error: The distance between pt1 and pt2 is too small.')
n = n/n_dist
x3, y3 = (np.array(pt1) + np.array(pt2))/2 + sagitta * n
# calculate the circle from three points
# see https://math.stackexchange.com/a/1460096/246399
A = np.array([
[x1**2 + y1**2, x1, y1, 1],
[x2**2 + y2**2, x2, y2, 1],
[x3**2 + y3**2, x3, y3, 1]])
M11 = np.linalg.det(A[:, (1, 2, 3)])
M12 = np.linalg.det(A[:, (0, 2, 3)])
M13 = np.linalg.det(A[:, (0, 1, 3)])
M14 = np.linalg.det(A[:, (0, 1, 2)])
if np.isclose(M11, 0):
# catch error here, the points are collinear (sagitta ~ 0)
print('Error: The third point is collinear.')
cx = 0.5 * M12/M11
cy = -0.5 * M13/M11
radius = np.sqrt(cx**2 + cy**2 + M14/M11)
# calculate angles of pt1 and pt2 from center of circle
pt1_angle = 180*np.arctan2(y1 - cy, x1 - cx)/np.pi
pt2_angle = 180*np.arctan2(y2 - cy, x2 - cx)/np.pi
return (cx, cy), radius, pt1_angle, pt2_angle
С этими значениями вы можете создать дугу с помощью функции ellipse()
OpenCV. Однако это все значения с плавающей запятой. ellipse()
позволяет отображать значения с плавающей запятой с аргументом shift
, но если вы не знакомы с ним, это немного странно, поэтому вместо этого мы можем позаимствовать решение из этот ответ для определения функции
def draw_ellipse(
img, center, axes, angle,
startAngle, endAngle, color,
thickness=1, lineType=cv2.LINE_AA, shift=10):
# uses the shift to accurately get sub-pixel resolution for arc
# taken from https://stackoverflow.com/a/44892317/5087436
center = (
int(round(center[0] * 2**shift)),
int(round(center[1] * 2**shift))
)
axes = (
int(round(axes[0] * 2**shift)),
int(round(axes[1] * 2**shift))
)
return cv2.ellipse(
img, center, axes, angle,
startAngle, endAngle, color,
thickness, lineType, shift)
Затем использовать эти функции так же просто, как:
img = np.zeros((500, 500), dtype=np.uint8)
pt1 = (50, 50)
pt2 = (350, 250)
sagitta = 50
center, radius, start_angle, end_angle = convert_arc(pt1, pt2, sagitta)
axes = (radius, radius)
draw_ellipse(img, center, axes, 0, start_angle, end_angle, 255)
cv2.imshow('', img)
cv2.waitKey()
![Нарисованная дуга](https://i.stack.imgur.com/3Tm1U.png)
И снова обратите внимание, что отрицательная сагитта дает дуге другое направление:
center, radius, start_angle, end_angle = convert_arc(pt1, pt2, sagitta)
axes = (radius, radius)
draw_ellipse(img, center, axes, 0, start_angle, end_angle, 255)
center, radius, start_angle, end_angle = convert_arc(pt1, pt2, -sagitta)
axes = (radius, radius)
draw_ellipse(img, center, axes, 0, start_angle, end_angle, 127)
cv2.imshow('', img)
cv2.waitKey()
![Отрицательная сагитта](https://i.stack.imgur.com/gjsq6.png)
Наконец, просто чтобы расширить, я выделил два случая ошибок в функции convert_arc()
. Первый:
if np.isclose(n_dist, 0):
# catch error here, d(pt1, pt2) ~ 0
print('Error: The distance between pt1 and pt2 is too small.')
Ошибка здесь в том, что нам нужно получить единичный вектор, поэтому нам нужно разделить на длину, которая не может быть равна нулю. Конечно, это произойдет только в том случае, если pt1
и pt2
являются одной и той же точкой, поэтому вы можете просто проверить их уникальность в начале функции, а не здесь.
Второй:
if np.isclose(M11, 0):
# catch error here, the points are collinear (sagitta ~ 0)
print('Error: The third point is collinear.')
Это происходит только в том случае, если три точки лежат на одной прямой, что происходит только в том случае, если сагитта равна 0. Опять же, вы можете проверить это в верхней части своей функции (и, возможно, сказать, хорошо, если это 0, тогда просто нарисуйте линию от от pt1
до pt2
или как хотите).
person
alkasm
schedule
08.01.2018