OpenCV绘制贴合近似平行四边形轮廓的线条为何有时无法精准对齐?
问题描述
我正在处理一个近似平行四边形的单轮廓,需求是绘制一条完全贴合该轮廓的贯穿线条。以下是最小复现代码:
import numpy as np import cv2 from math import dist # setup stuff filler_img = np.zeros((1600, 1600), dtype=np.uint8) cnt = np.array([[490, 885], [648, 1065], [636, 1201], [486, 1026]]) # [[733, 712], [897, 886], [688, 1006], [528, 828]] c1, c2, c3 = cnt[:-1] moments = cv2.moments(cnt) center = (int(moments["m10"] / moments["m00"]), int(moments["m01"] / moments["m00"])) # (x, y) # calculating pass-through line y_to_x_ratio = (c2[1] - c3[1]) / (c3[0] - c2[0]) point1 = (0, int(y_to_x_ratio*center[0] + center[1])) point2 = (1600, int(-y_to_x_ratio*(1600-center[0]) + center[1])) cv2.line(filler_img, point1, point2, 100, thickness=int(dist(c1, c2))) # problems with thickness argument # filling circles at c1, c2, and c3 disp_img = cv2.drawContours(cv2.cvtColor(filler_img, cv2.COLOR_GRAY2BGR), [cnt], -1, (255, 255, 255), 5) cv2.circle(disp_img, c1, 20, (0, 0, 255), -1) # red cv2.circle(disp_img, c2, 20, (0, 255, 0), -1) # green cv2.circle(disp_img, c3, 20, (255, 0, 0), -1) # blue imshow(disp_img)
注:imshow()为Jupyter Notebook中的图像显示函数,实现如下:
def imshow(*imgs: cv2.Mat) -> None: def helper(img: cv2.Mat) -> None: plt.axis('off') plt.grid(False) plt.imshow(cv2.cvtColor(img, cv2.COLOR_RGB2BGR)) plt.show() for img in imgs: helper(img)
当前代码通过轮廓顶点c2、c3计算线条角度(与c2-c3边平行),以c1与c2的距离作为线条厚度,但绘制出的线条厚度明显偏大;更换注释中的另一组轮廓参数时,线条能完美贴合轮廓。请问如何实现始终精准贴合的线条绘制?
解决方案
问题核心是线条厚度的计算逻辑错误:你用了c1到c2的欧氏距离作为厚度,但这个值是平行四边形的边长,而我们需要的是平行四边形两条对边之间的垂直距离(即高),这才是线条贴合轮廓所需的精准厚度。
具体修正步骤:
- 计算c2到c3的向量,利用向量叉积的几何意义,求出c1到直线c2-c3的垂直距离,这个距离就是平行四边形的高,也就是线条需要的厚度。
- 保持线条方向不变,将计算得到的垂直距离作为
cv2.line的厚度参数。
修正后的完整代码:
import numpy as np import cv2 from math import dist # setup stuff filler_img = np.zeros((1600, 1600), dtype=np.uint8) cnt = np.array([[490, 885], [648, 1065], [636, 1201], [486, 1026]]) # [[733, 712], [897, 886], [688, 1006], [528, 828]] c1, c2, c3 = cnt[:-1] moments = cv2.moments(cnt) center = (int(moments["m10"] / moments["m00"]), int(moments["m01"] / moments["m00"])) # (x, y) # 计算c2-c3的向量 vec_c2c3 = np.array([c3[0] - c2[0], c3[1] - c2[1]]) # 用向量叉积计算c1到直线c2-c3的垂直距离(平行四边形的高) cross_product = np.cross(np.array(c1) - np.array(c2), vec_c2c3) line_length = np.linalg.norm(vec_c2c3) thickness = int(abs(cross_product) / line_length) # calculating pass-through line y_to_x_ratio = (c2[1] - c3[1]) / (c3[0] - c2[0]) point1 = (0, int(y_to_x_ratio*center[0] + center[1])) point2 = (1600, int(-y_to_x_ratio*(1600-center[0]) + center[1])) cv2.line(filler_img, point1, point2, 100, thickness=thickness) # 使用垂直距离作为厚度 # filling circles at c1, c2, and c3 disp_img = cv2.drawContours(cv2.cvtColor(filler_img, cv2.COLOR_GRAY2BGR), [cnt], -1, (255, 255, 255), 5) cv2.circle(disp_img, c1, 20, (0, 0, 255), -1) # red cv2.circle(disp_img, c2, 20, (0, 255, 0), -1) # green cv2.circle(disp_img, c3, 20, (255, 0, 0), -1) # blue imshow(disp_img)
关键说明
- 向量叉积的绝对值等于以两个向量为邻边的平行四边形面积,除以底边长度(c2-c3的长度)就得到平行四边形的高,这个值就是线条需要的精准厚度。
- 该方法对任意近似平行四边形轮廓都适用,不受顶点顺序影响,只要能正确取到一组对边和对应的顶点,就能计算出贴合轮廓的线条厚度。
内容的提问来源于stack exchange,提问作者vs07
相关产品推荐
相关产品推荐

