使用端点和凸出距离绘制圆弧。在OpenCV或PIL中
使用脚本将dxf转换为png,我需要绘制只有三个参数的圆弧,即圆弧起点、圆弧终点和凸出距离使用端点和凸出距离绘制圆弧。在OpenCV或PIL中,opencv,image-processing,python-imaging-library,dxf,Opencv,Image Processing,Python Imaging Library,Dxf,使用脚本将dxf转换为png,我需要绘制只有三个参数的圆弧,即圆弧起点、圆弧终点和凸出距离 我已经检查了OpenCV和PIL,它们需要开始和结束角度来绘制此弧。我可以使用一些几何图形找出这些角度,但我想知道是否还有其他解决方案我遗漏了。定义圆弧的信息有三条:圆上的两点(定义圆的弦)和凸出距离(称为圆弧的矢状线) 请参见下图: 这里s是矢状,l是弦长的一半,r当然是半径。其他重要的未标记位置是弦与圆相交的点、矢状与圆相交的点以及半径从其延伸的圆心 对于OpenCV的功能,我们将使用以下原型: c
我已经检查了OpenCV和PIL,它们需要开始和结束角度来绘制此弧。我可以使用一些几何图形找出这些角度,但我想知道是否还有其他解决方案我遗漏了。定义圆弧的信息有三条:圆上的两点(定义圆的弦)和凸出距离(称为圆弧的矢状线) 请参见下图: 这里s是矢状,l是弦长的一半,r当然是半径。其他重要的未标记位置是弦与圆相交的点、矢状与圆相交的点以及半径从其延伸的圆心 对于OpenCV的功能,我们将使用以下原型:
cv2.ellipse(img, center, axes, angle, startAngle, endAngle, color[, thickness[, lineType[, shift]]]) → img
其中大多数参数由下图描述:
因为我们画的是圆弧,而不是椭圆弧,长轴/短轴的大小相同,旋转时没有区别,所以轴的大小就是(半径,半径)
,角度应该为零以简化。然后,我们需要的唯一参数是圆心、半径以及与弦点对应的绘图的开始角和结束角。角度很容易计算(它们只是圆上的一些角度)。所以最终我们需要找到圆的半径和中心
求半径和圆心和求圆的方程是一样的,所以有很多方法。但是因为我们在这里编程,IMO最简单的方法是定义圆上的第三个点,通过箭头与圆的接触点,然后从这三个点求解圆
首先,我们需要得到弦的中点,得到一条与该中点垂直的线,并将其延伸到矢状面的长度,以到达第三点,但这很容易。我将开始给定pt1=(x1,y1)
和pt2=(x2,y2)
作为我在圆上的两点,sagitta
是“凸出深度”(即您拥有的参数):
现在我们得到了圆上的第三个点。请注意,矢状线只是一段长度,所以它可以向任意方向移动——如果矢状线为负,它将从弦向一个方向移动,如果矢状线为正,它将向另一个方向移动。不确定这是不是给你的距离
然后我们就可以简单地
最后,由于我们需要开始和结束角度来使用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
使用这些值,您可以使用OpenCV的eliple()
函数创建圆弧。但是,这些都是浮点值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()
再次注意,负矢状线给出了另一个方向的弧:
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()
最后,为了扩展,我在
convert\u 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
或您想做的任何事情划一条线即可)。在没有给出任何理由的情况下否决一个问题是很糟糕的。让我知道原因,如果需要,我可以更新问题。使用几何有什么错?这并不是一件需要大量处理的事情,它只是一些操作和完成。请注意,至少对于OpenCV,其想法是绘制一个椭圆,但仅从一个起始角度到一个结束角度(这毕竟是圆弧)。如果您发布尝试上述解决方案的代码(使用您喜欢的任何库),并发布您对此有何问题?取一个向量(长度为给定的喇叭距离),从端点形成的直线的中间垂直延伸,以获得圆上的第三个点。众所周知,通过这三个点,可以计算通过这些点的圆的圆心和半径(即圆弧)。然后使用圆心,您可以简单地计算到原始端点的角度,给出角度、中心、半径等,以使用OpenCV或PIL绘制圆弧。感谢@AlexanderReynolds指出此解决方案。我是用椭圆曲线来解的,但是把三个点放在一个圆上(半径和圆心不同)的想法要好得多。谢谢你的精确解。
if np.isclose(n_dist, 0):
# catch error here, d(pt1, pt2) ~ 0
print('Error: The distance between pt1 and pt2 is too small.')
if np.isclose(M11, 0):
# catch error here, the points are collinear (sagitta ~ 0)
print('Error: The third point is collinear.')