Home > database >  How to stretch a line to fit image with Python OpenCV?
How to stretch a line to fit image with Python OpenCV?

Time:05-03

I have an image with the size of W * H, I want to draw a line on this and the line should be automatically fit to the image, for example if I draw it:

enter image description here

I want this:

enter image description here

How can I do this in Python and OpenCV? Thanks

CodePudding user response:

Method #1: Just drawing the extended line (no coordinates)

Before -> After

Here's a function when given points p1 and p2, will only draw the extended line. By default, the line is clipped by the image boundaries. There is also a distance parameter to determine how far to draw from the original starting point or until the line hits the border of the image. If you need the new (x1, y1) and (x2, y2) coordinates, see section #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()

Method #2: Full drawing with coordinates

If you need the new (x1, y1) and (x2, y2) coordinates, it gets a little more complicated since we need to calculate the resulting new points for each possible case. The possible cases are horizontal, vertical, positively sloped, negatively sloped, and exact diagonals. Here's the result for each of the cases with the new two coordinate points: white is the original line and the green is the extended line

Vertical

(250, 0) (250, 500)

Horizontal

(0, 300) (500, 300)

Positive slope

(0, 450) (450, 0)

Negative slope

(0, 142) (500, 428)

Left corner diagonal

(0, 0) (500, 500)

Right corner diagonal

(0, 0) (500, 500)

Code

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()

CodePudding user response:

I assumed you meant the following: You have 2 points - e.g. p1, p2 within the image. Instead of drawing a line between p1 and p2 you want the line that contains these points but streches till the edge of the image.

You will need to handle several cases, depending on which edges the line should reach. This depends on the location and angle of the original line segment.

Below is a code example of how to handle one of the cases - where the line should reach the top and right edges. You will need to modify it to handle the other cases similarly and also to determine which case should be applied.

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)

Note: I am aware that my code does not solve the problem entirely. But I thought it will steer you in the right direction.

CodePudding user response:

Presumably, your line is known by its two endpoints, let P and Q (you should have said that). A general point along this line has coordinates given vectorially by P t (Q - P). Now you have to take the intersections with the image edges, for example with the upper (Y=0) or lower (Y=Height-1) side. This gives the point of coordinates (X, Y), where

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

and you need to check if 0 ≤ X < Width. Repeat this for the four sides (switching X and Y where appropriate), and you will find two points in the end.


This gives you the main idea. In practice, there are corner cases such as a vertical or horizontal line, or a line going though a corner or two.

  • Related