如何使用 Python OpenCV 拉伸一条线以适应图像?

How to stretch a line to fit image with Python OpenCV?

我有一张尺寸为 W * H 的图片,我想在上面画一条线,这条线应该会自动适合图片, 例如,如果我画它:

我想要这个:

如何在 Python 和 OpenCV 中执行此操作?谢谢

我假设你的意思是: 您有 2 分 - 例如p1, p2 在图像中。 您不需要在 p1 和 p2 之间画一条线,而是希望包含这些点但一直延伸到图像边缘的线。

您将需要处理几种情况,具体取决于线应该到达的边缘。这取决于原始线段的位置和角度。

下面是一个代码示例,说明如何处理其中一种情况 - 线条应到达顶部和右侧边缘。 您将需要对其进行修改以类似地处理其他情况,并确定应应用哪种情况。

import cv2
import numpy as np

# At the moment only a line that hits the top and right edges is handled.
# You need to handle the rest of the cases in a similar way.
def draw_line_fit_topright(img, p1, p2, color, thinkness):
    h = img.shape[0]
    w = img.shape[1]
    dx = float(p2[0]-p1[0])
    dy = float(p2[1]-p1[1])
    p1new = (int(p1[0] - dx/dy*p1[1]), 0)
    p2new = (w-1, int(p2[1] + dy/dx*(w-p2[0]-1)))
    cv2.line(img, p1new, p2new, color, thickness=thinkness)
    cv2.line(img, p1, p2, (0,0,255), thickness=thinkness-3)

w = 1024
h = 768
p1 = (240,50)
p2 = (440,150)
img1 = np.zeros((h, w, 3), dtype = "uint8")
draw_line_fit_topright(img1, p1, p2, (255, 0, 0), 5)
cv2.imshow("img1", img1)
cv2.waitKey(0)

注意:我知道我的代码没有完全解决问题。但我认为它会引导您朝着正确的方向前进。

方法一:只画延长线(无坐标)

之前->之后

这是一个函数,当给定点 p1p2 时,将 仅绘制 延长线。默认情况下,线条会被图像边界剪裁。还有一个 distance 参数来确定从原始起点绘制多远或直到线条到达图像的边界。如果您需要新的 (x1, y1)(x2, y2) 坐标,请参阅第 #2

部分
import cv2
import numpy as np

"""
@param: p1 - starting point (x, y)
@param: p2 - ending point (x, y)
@param: distance - distance to extend each side of the line
"""
def extend_line(p1, p2, distance=10000):
    diff = np.arctan2(p1[1] - p2[1], p1[0] - p2[0])
    p3_x = int(p1[0] + distance*np.cos(diff))
    p3_y = int(p1[1] + distance*np.sin(diff))
    p4_x = int(p1[0] - distance*np.cos(diff))
    p4_y = int(p1[1] - distance*np.sin(diff))
    return ((p3_x, p3_y), (p4_x, p4_y))

# Create blank black image using Numpy
original = np.zeros((500,500,3), dtype=np.uint8)
image1 = original.copy()
p1 = (250, 100)
p2 = (375, 250)
cv2.line(image1, p1, p2, [255,255,255], 2)

# Second image, calculate new extended points
image2 = original.copy()
p3, p4 = extend_line(p1, p2)
cv2.line(image2, p3, p4, [255,255,255], 2)

cv2.imshow('image1', image1)
cv2.imshow('image2', image2)
cv2.waitKey()

方法二:带坐标的全图

如果您需要新的 (x1, y1)(x2, y2) 坐标,它会变得有点复杂,因为我们需要为每种可能的情况计算生成的新点。可能的情况是水平、垂直、正斜率、负斜率和精确对角线。这是具有新的两个坐标点的每种情况的结果:白色是原始线,绿色是延长线

垂直

(250, 0) (250, 500)

水平

(0, 300) (500, 300)

正斜率

(0, 450) (450, 0)

负斜率

(0, 142) (500, 428)

左角对角线

(0, 0) (500, 500)

右角对角线

(0, 0) (500, 500)

代码

import numpy as np
import cv2
import math

"""
@param: dimensions - image shape from Numpy (h, w, c)
@param: p1 - starting point (x1, y1)
@param: p2 - ending point (x2, y2)
@param: SCALE - default parameter to ensure that extended lines go through borders
"""
def extend_line(dimensions, p1, p2, SCALE=10):
    # Calculate the intersection point given (x1, y1) and (x2, y2)
    def line_intersection(line1, line2):
        x_diff = (line1[0][0] - line1[1][0], line2[0][0] - line2[1][0])
        y_diff = (line1[0][1] - line1[1][1], line2[0][1] - line2[1][1])

        def detect(a, b):
            return a[0] * b[1] - a[1] * b[0]

        div = detect(x_diff, y_diff)
        if div == 0:
           raise Exception('lines do not intersect')

        dist = (detect(*line1), detect(*line2))
        x = detect(dist, x_diff) / div
        y = detect(dist, y_diff) / div
        return int(x), int(y)

    x1, x2 = 0, 0
    y1, y2 = 0, 0
    
    # Extract w and h regardless of grayscale or BGR image
    if len(dimensions) == 3:
        h, w, _ = dimensions
    elif len(dimensions) == 2:
        h, w = dimensions
    
    # Take longest dimension and use it as maxed out distance
    if w > h:
        distance = SCALE * w
    else:
        distance = SCALE * h
    
    # Reorder smaller X or Y to be the first point
    # and larger X or Y to be the second point
    try:
        slope = (p2[1] - p1[1]) / (p1[0] - p2[0])
        # HORIZONTAL or DIAGONAL
        if p1[0] <= p2[0]:
            x1, y1 = p1
            x2, y2 = p2
        else:
            x1, y1 = p2
            x2, y2 = p1
    except ZeroDivisionError:
        # VERTICAL
        if p1[1] <= p2[1]:
            x1, y1 = p1
            x2, y2 = p2
        else:
            x1, y1 = p2
            x2, y2 = p1
    
    # Extend after end-point A
    length_A = math.sqrt((x2 - x1)**2 + (y2 - y1)**2)
    p3_x = int(x1 + (x1 - x2) / length_A * distance)
    p3_y = int(y1 + (y1 - y2) / length_A * distance)

    # Extend after end-point B
    length_B = math.sqrt((x1 - x2)**2 + (y1 - y2)**2)
    p4_x = int(x2 + (x2 - x1) / length_B * distance)
    p4_y = int(y2 + (y2 - y1) / length_B * distance)
   
    # -------------------------------------- 
    # Limit coordinates to borders of image
    # -------------------------------------- 
    # HORIZONTAL
    if y1 == y2:
        if p3_x < 0: 
            p3_x = 0
        if p4_x > w: 
            p4_x = w
        return ((p3_x, p3_y), (p4_x, p4_y))
    # VERTICAL
    elif x1 == x2:
        if p3_y < 0: 
            p3_y = 0
        if p4_y > h: 
            p4_y = h
        return ((p3_x, p3_y), (p4_x, p4_y))
    # DIAGONAL
    else:
        A = (p3_x, p3_y)
        B = (p4_x, p4_y)

        C = (0, 0)  # C-------D
        D = (w, 0)  # |-------|
        E = (w, h)  # |-------|
        F = (0, h)  # F-------E
        
        if slope > 0:
            # 1st point, try C-F side first, if OTB then F-E
            new_x1, new_y1 = line_intersection((A, B), (C, F))
            if new_x1 > w or new_y1 > h:
                new_x1, new_y1 = line_intersection((A, B), (F, E))

            # 2nd point, try C-D side first, if OTB then D-E
            new_x2, new_y2 = line_intersection((A, B), (C, D))
            if new_x2 > w or new_y2 > h:
                new_x2, new_y2 = line_intersection((A, B), (D, E))

            return ((new_x1, new_y1), (new_x2, new_y2))
        elif slope < 0:
            # 1st point, try C-F side first, if OTB then C-D
            new_x1, new_y1 = line_intersection((A, B), (C, F))
            if new_x1 < 0 or new_y1 < 0:
                new_x1, new_y1 = line_intersection((A, B), (C, D))
            # 2nd point, try F-E side first, if OTB then E-D
            new_x2, new_y2 = line_intersection((A, B), (F, E))
            if new_x2 > w or new_y2 > h:
                new_x2, new_y2 = line_intersection((A, B), (E, D))
            return ((new_x1, new_y1), (new_x2, new_y2))

# Vertical
# -------------------------------
# p1 = (250, 100)
# p2 = (250, 300)
# -------------------------------

# Horizontal
# -------------------------------
# p1 = (100, 300)
# p2 = (400, 300)
# -------------------------------

# Positive slope
# -------------------------------
# C-F, C-D
# p1 = (50, 400)
# p2 = (400, 50)

# C-F, E-D
# p1 = (50, 400)
# p2 = (400, 50)

# F-E, E-D
# p2 = (250, 400)
# p1 = (400, 250)

# F-E, C-D
# p2 = (250, 400)
# p1 = (300, 250)
# -------------------------------

# Negative slope
# -------------------------------
# C-F, E-D
# p1 = (100, 200)
# p2 = (450, 400)

# C-F, F-E
# p2 = (100, 200)
# p1 = (250, 400)

# C-D, D-E
# p1 = (100, 50)
# p2 = (450, 400)

# C-D, F-E
p1 = (100, 50)
p2 = (250, 400)
# -------------------------------

# Exact corner diagonals
# -------------------------------
# p1 = (50,50)
# p2 = (300, 300)

# p2 = (375, 125)
# p1 = (125, 375)
# -------------------------------

image = np.zeros((500,500,3), dtype=np.uint8)
p3, p4 = extend_line(image.shape, p1, p2)
print(p3, p4)
cv2.line(image, p3, p4, [255,255,255], 2)
cv2.line(image, p1, p3, [36,255,12], 2)
cv2.line(image, p2, p4, [36,255,12], 2)
cv2.imshow('image', image)
cv2.waitKey()

据推测,您的线路由其两个端点已知,设 PQ(您应该这么说)。沿着这条线的一般点的坐标由 P + t (Q - P) 向量给出。现在您必须获取与图像边缘的交点,例如与上 (Y=0) 或下 (Y=Height-1) 侧。这给出坐标点(X, Y),其中

X = Px + (Y - Py) (Qx - Px) / (Qy - Py)

并且您需要检查是否 0 ≤ X < Width。对四个边重复此操作(在适当的地方切换 XY),最后你会发现两个点。


这为您提供了主要思想。在实践中,存在一些极端情况,例如垂直线或水平线,或者穿过一两个角的线。