Moh*_*ali 0 opencv image-processing dxf python-imaging-library
使用脚本将dxf转换为png时,我需要绘制仅具有三个参数的圆弧,即圆弧的起点,圆弧的终点和凸出距离。
我已经检查了OpenCV和PIL,它们都需要起点和终点角度来绘制此弧。我可以使用某些几何来找出这些角度,但想知道是否还有其他解决方案我错过了。
定义圆弧的信息有三部分:圆上的两个点(定义该圆的弦)和凸出距离(称为圆弧的弧矢)。
参见下图:
这里小号是矢,升是弦长的一半,和- [R是当然的半径。其他重要的非标记位置是和弦与圆相交的点,箭矢与圆相交的点以及半径从其延伸的圆心。
对于OpenCV的ellipse()功能,我们将使用以下原型:
cv2.ellipse(img, center, axes, angle, startAngle, endAngle, color[, thickness[, lineType[, shift]]]) ? img
Run Code Online (Sandbox Code Playgroud)
下图描述了大多数参数:
由于我们正在绘制一个圆形,而不是椭圆弧,主要/次要轴将具有相同的大小,而且也没有差进行旋转,所以轴将仅仅是(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
Run Code Online (Sandbox Code Playgroud)
现在,我们在圆上有了第三点。请注意,箭矢只有一定长度,因此可以向任一方向移动-如果箭矢为负,则它会从弦向一个方向移动;如果箭矢为正,则它会向另一个方向移动。不确定这是否是给您的距离。
然后,我们可以简单地使用行列式来求解半径和中心。
# 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)
Run Code Online (Sandbox Code Playgroud)
最后,由于我们需要使用开始和结束角度来使用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
Run Code Online (Sandbox Code Playgroud)
因此,我将所有这些打包为一个函数:
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
Run Code Online (Sandbox Code Playgroud)
使用这些值,您便可以使用OpenCV的ellipse()功能进行圆弧处理。但是,这些都是浮点值。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 /sf/answers/3142462221/
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)
Run Code Online (Sandbox Code Playgroud)
然后使用这些功能就很简单:
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()
Run Code Online (Sandbox Code Playgroud)
再次注意,负的箭矢会给圆弧带来另一个方向:
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()
Run Code Online (Sandbox Code Playgroud)
最后只是为了扩展,我在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.')
Run Code Online (Sandbox Code Playgroud)
这里的错误捕获是因为我们需要获取单位向量,所以我们需要除以不能为零的长度。当然,只有在pt1和pt2是同一点时才会发生这种情况,因此您可以在函数顶部检查它们是否唯一,而不用在此处进行检查。
第二:
if np.isclose(M11, 0):
# catch error here, the points are collinear (sagitta ~ 0)
print('Error: The third point is collinear.')
Run Code Online (Sandbox Code Playgroud)
仅在三个点是共线的情况下才会发生,而仅在矢状点为0时才会发生。因此,再次,您可以在函数顶部进行检查(也许说,确定,如果为0,则只需从中画一条线即可)。pt1到pt2或任何你想做的事)。
| 归档时间: |
|
| 查看次数: |
2070 次 |
| 最近记录: |