You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 16:59:53