16

I have a line of code here that uses the python binding for opencv:

cv2.rectangle(img, (box[1], box[0]), (box[3], box[2]), (255,0,0), 4)

This draws a red rectangle on image img of thickness 4.

But is there a way the lines of the rectangles can be stylized? Not too much. Just dotted, or dashed, that's it really.

user961627
  • 12,379
  • 42
  • 136
  • 210
  • Two suggestions: 1) use python subprocess call to Imagemagick, which can draw dashed lines (or use Python Wand, which uses Imagemagick). Or 2) draw your rectangle as white lines on black as a mask, then draw a grid of black lines (horizontal and vertical lines) over the the mask to put gaps in the white lines. The line thickness will control the gaps. Then use the mask to draw black on your image where the lines are white. – fmw42 Mar 17 '23 at 17:33

7 Answers7

18
import cv2
import numpy as np
def drawline(img,pt1,pt2,color,thickness=1,style='dotted',gap=20):
    dist =((pt1[0]-pt2[0])**2+(pt1[1]-pt2[1])**2)**.5
    pts= []
    for i in  np.arange(0,dist,gap):
        r=i/dist
        x=int((pt1[0]*(1-r)+pt2[0]*r)+.5)
        y=int((pt1[1]*(1-r)+pt2[1]*r)+.5)
        p = (x,y)
        pts.append(p)

    if style=='dotted':
        for p in pts:
            cv2.circle(img,p,thickness,color,-1)
    else:
        s=pts[0]
        e=pts[0]
        i=0
        for p in pts:
            s=e
            e=p
            if i%2==1:
                cv2.line(img,s,e,color,thickness)
            i+=1

def drawpoly(img,pts,color,thickness=1,style='dotted',):
    s=pts[0]
    e=pts[0]
    pts.append(pts.pop(0))
    for p in pts:
        s=e
        e=p
        drawline(img,s,e,color,thickness,style)

def drawrect(img,pt1,pt2,color,thickness=1,style='dotted'):
    pts = [pt1,(pt2[0],pt1[1]),pt2,(pt1[0],pt2[1])] 
    drawpoly(img,pts,color,thickness,style)

im = np.zeros((800,800,3),dtype='uint8')
s=(234,222)
e=(500,700)
drawrect(im,s,e,(0,255,255),1,'dotted')

cv2.imshow('im',im)
cv2.waitKey()      
Zaw Lin
  • 5,629
  • 1
  • 23
  • 41
7

OpenCV does not (currently) support line properties beyond thickness and anti-aliasing.

Adi Shavit
  • 16,743
  • 5
  • 67
  • 137
7
  • openCV is opensource library so you can find source code for drawing and modify it slightly
  • you can use LineIterator and get any style you want in a few lines of code

    void lineDot(OutputArray img, const Point& pt1, const Point& pt2, const Scalar& color, const vector<bool>& pattern){
        LineIterator it(img.getMat(), pt1, pt2, LINE_8); // LINE_AA is not supported here
        for(auto i=0; i<it.count; i++, it++){
            if(pattern[i%pattern.size()]){ // use any pattern of any length, dotted is {0,0,1}, dashed is {0,0,0,1,1,1} etc
                (*it)[0] = color.val[0];
                (*it)[1] = color.val[1];
                (*it)[2] = color.val[2];
            }
        }
    }
    
Vit
  • 793
  • 4
  • 17
0

OpenCV does not currently have a built-in method for drawing dashed lines, as Adi Shavit and others here have stated.

However, there is a workaround that you can use to draw a dashed line using the cv2.line() function. One approach is to draw short line segments with gaps in between them to give the appearance of a dashed line. Here's an example code snippet that demonstrates this technique:

import cv2

# Create a black image
img = np.zeros((512, 512, 3), np.uint8)

# Draw a dashed line
start_point = (100, 100)
end_point = (400, 100)
color = (0, 255, 0)
thickness = 2

# Define the length of the line segments and gaps
segment_length = 10
gap_length = 5

# Calculate the length and direction of the line
dx = end_point[0] - start_point[0]
dy = end_point[1] - start_point[1]
line_length = np.sqrt(dx*dx + dy*dy)
x_unit = dx / line_length
y_unit = dy / line_length

# Draw the line segments
current_point = start_point
while line_length >= segment_length:
    end_point = (int(current_point[0] + segment_length*x_unit), int(current_point[1] + segment_length*y_unit))
    cv2.line(img, current_point, end_point, color, thickness)
    current_point = (int(end_point[0] + gap_length*x_unit), int(end_point[1] + gap_length*y_unit))
    line_length -= segment_length + gap_length

# Display the image
cv2.imshow('Dashed Line', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

In this example, a green dashed line is drawn on a black image from point (100, 100) to point (400, 100) with a thickness of 2 pixels. The segment_length and gap_length variables control the length of the line segments and gaps, respectively. The code calculates the length and direction of the line, and then iteratively draws short line segments with gaps in between them until the entire line has been drawn. This approach can be adjusted to produce different dash patterns and lengths as needed.

Another alternative is to use the other libraries instead, such as PIL or Pillow, e.g.:

from PIL import Image, ImageDraw

# Create a black image
img = Image.new('RGB', (512, 512), (0, 0, 0))

# Draw a dashed line
draw = ImageDraw.Draw(img)
start_point = (100, 100)
end_point = (400, 100)
color = (0, 255, 0)
dash_length = 10
gap_length = 5
dash = [dash_length, gap_length]
draw.line((start_point, end_point), fill=color, width=2, joint='curve', dash=dash)

# Display the image
img.show()
Shahar Gino
  • 155
  • 3
  • 11
0

Here is one way to draw dashed lines in Python/OpenCV.

Draw your rectangle as white lines on black as a mask, then draw a grid of black lines (horizontal and vertical lines) over the the mask to put gaps in the white lines. The line thickness will control the gaps. Then use the mask to draw black on your image where the lines are white

Input:

enter image description here

import cv2
import numpy as np

# read the image
img = cv2.imread('lena.jpg')

# draw white lines on black as mask
mask = np.zeros_like(img)
cv2.rectangle(mask, (50,50), (200,200), (255,255,255), 4)

# draw grid of black lines on white
grid = np.full_like(img, (255,255,255))
dx, dy = 23, 23    # spacing cannot be evenly divisible with dimensions of rectangle
grid[:,::dx,:] = [0,0,0]
grid[::dy,:,:] = [0,0,0]

# thicken grid lines
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))
grid2 = cv2.morphologyEx(grid, cv2.MORPH_ERODE, kernel)

# multiply grid with mask
mask2 = cv2.multiply(mask, grid2)
mask2 = cv2.cvtColor(mask2, cv2.COLOR_BGR2GRAY)

# use mask to draw blue dashed rectangle on image
result = img.copy()
result[mask2==255] = [255,0,0]

# save results
cv2.imwrite('lena_mask.jpg', mask)
cv2.imwrite('lena_grid.jpg', grid)
cv2.imwrite('lena_grid2.jpg', grid2)
cv2.imwrite('lena_mask2.jpg', mask2)
cv2.imwrite('lena_dashed_lines.jpg', result)

# show results
cv2.imshow('mask', mask)
cv2.imshow('grid', grid)
cv2.imshow('grid2', grid2)
cv2.imshow('mask2', mask2)
cv2.imshow('result', result)
cv2.waitKey(0)

Rectangle Mask:

enter image description here

Grid on Black:

enter image description here

Grid Thickened:

enter image description here

Dashed Mask:

enter image description here

Result:

enter image description here

fmw42
  • 46,825
  • 10
  • 62
  • 80
  • This only works well when the rectangle is in the middle of the image. If not, there's a high chance that the rectangle will have uneven dashes, especially when it's small. – MattieG4 Jun 08 '23 at 16:28
  • Not true. As long as the rectangle and grid are aligned it should work. – fmw42 Jun 08 '23 at 16:40
0

This is taken from my Python class:

@staticmethod
def draw_dashed_line(image, point1: tuple[int, int], point2: tuple[int, int], color: tuple[int, int, int], thickness: int = 1,
                     dash_size: int = 20):
    """
    This assumes the line is vertical or horizontal
    :param image: image to draw the line onto
    :param point1: (x, y)
    :param point2: (x, y)
    :param color: color of the line
    :param thickness: thickness of the line
    :param dash_size: length of the gap and dash
    :return:
    """
    x1 = point1[0]
    y1 = point1[1]
    x2 = point2[0]
    y2 = point2[1]
    if x1 == x2:
        # vertical line
        if y2 > y1:
            max_y = y2
            min_y = y1
        elif y1 > y2:
            max_y = y1
            min_y = y2
        else:
            raise ValueError("Both points cannot be identical")

        for y in range(min_y, max_y, dash_size):
            cv2.line(image, (x1, y), (x1, y + round(dash_size/2)), color, thickness)
    elif y1 == y2:
        # horizontal line
        if x2 > x1:
            max_x = x2
            min_x = x1
        elif x1 > x2:
            max_x = x1
            min_x = x2

        for x in range(min_x, max_x, dash_size):
            cv2.line(image, (x, y1), (x + round(dash_size/2), y1), color, thickness)
    else:
        raise ValueError("Line must be horizontal or vertical")

def draw_dashed_bounding_box(self, image, centre_point: tuple[int, int], size: tuple[int, int], color: tuple[int, int, int],
                             thickness: int, dash_length: int = 20) -> None:
    """
    Draw a rectangular bounding box
    :param image: image to draw the bounding box onto
    :param centre_point: centre point of the bounding box
    :param size: (height, width) of the bounding box
    :param color: RGB colour of the bounding box
    :param thickness: thickness of lines
    :param dash_length: length of gap and lines for dashes in bounding box lines
    :return:
    """
    min_x = int(centre_point[0] - (size[0] / 2))
    min_y = int(centre_point[1] - (size[1] / 2))
    max_x = int(centre_point[0] + (size[0] / 2))
    max_y = int(centre_point[1] + (size[1] / 2))
    points = [(min_x, min_y), (max_x, min_y), (max_x, max_y), (min_x, max_y)]
    end = points[0]
    points.append(points.pop(0))
    for point in points:
        start = end
        end = point
        self.draw_dashed_line(image, start, end, color, thickness, dash_length)
MattieG4
  • 150
  • 2
  • 12
-3

Try something like this:

cv::Point P1, P2;

P1.y = 50;
P2.y = 50;

int dot_gap = 50;
int dot_width = 50;

for( int i=0 ; i < in_img.cols; (i = i+d_width) ) {

    P1.x = i;
    P2.x = i + dot_width;
    cv::line(in_img, P1, P2, cv::Scalar(0, 255, 255), 2, cv::LINE_8);
}
Sam
  • 169
  • 1
  • 13