How to draw a rounded rectangle (rectangle with rounded corners) with OpenCV?
Asked Answered
M

5

15

How can I draw a rectangle with rounded corners in OpenCV? I know that the functions ellipse() and line() can be simply put together to draw it. I just wonder if someone has done it before and has put it in a proper function so I can use it? Ideally the corner radius is to calibrate in a parameter.

I searched a lot for that, but it seems no one had that problem before. If no one has such I function I will probably post my own solution here in a few days.

Mcadoo answered 24/9, 2013 at 4:45 Comment(0)
M
17

I realized, this is much easier that I thought. Here is my function. I hope it is helpful for someone.

/**
 * Draws a rectangle with rounded corners, the parameters are the same as in the OpenCV function @see rectangle();
 * @param cornerRadius A positive int value defining the radius of the round corners.
 * @author K
 */
void rounded_rectangle( Mat& src, Point topLeft, Point bottomRight, const Scalar lineColor, const int thickness, const int lineType , const int cornerRadius)
{
    /* corners:
     * p1 - p2
     * |     |
     * p4 - p3
     */
    Point p1 = topLeft;
    Point p2 = Point (bottomRight.x, topLeft.y);
    Point p3 = bottomRight;
    Point p4 = Point (topLeft.x, bottomRight.y);
    
    // draw straight lines
    line(src, Point (p1.x + cornerRadius, p1.y), Point (p2.x - cornerRadius, p2.y), lineColor, thickness, lineType);
    line(src, Point (p2.x, p2.y + cornerRadius), Point (p3.x, p3.y - cornerRadius), lineColor, thickness, lineType);
    line(src, Point (p4.x + cornerRadius, p4.y), Point (p3.x-cornerRadius, p3.y), lineColor, thickness, lineType);
    line(src, Point (p1.x, p1.y + cornerRadius), Point (p4.x, p4.y - cornerRadius), lineColor, thickness, lineType);
    
    // draw arcs
    ellipse( src, p1 + Point(cornerRadius, cornerRadius), Size( cornerRadius, cornerRadius ), 180.0, 0, 90, lineColor, thickness, lineType );
    ellipse( src, p2 + Point(-cornerRadius, cornerRadius), Size( cornerRadius, cornerRadius ), 270.0, 0, 90, lineColor, thickness, lineType );
    ellipse( src, p3 + Point(-cornerRadius, -cornerRadius), Size( cornerRadius, cornerRadius ), 0.0, 0, 90, lineColor, thickness, lineType );
    ellipse( src, p4 + Point(cornerRadius, -cornerRadius), Size( cornerRadius, cornerRadius ), 90.0, 0, 90, lineColor, thickness, lineType );
}
Mcadoo answered 24/9, 2013 at 7:25 Comment(2)
It will be good that the cornerRadius be calculated automatically, because when you draw a small size Rectangle, then it will look like a circle, so the cornerRadius can be calculated like this cornerRadius = (rect.width+rect.height)*0.1Naphthalene
I upvoted since this answer was the inspiration behind my own answer. But note the angle parameters in this answer are incorrect and will not produce rounded corners that perfectly line up with the straight segments. Compare with my own answer.Burress
A
11

Here is the python version with filled/not filled feature and corner_radius automatically calculated based on the image's height.

import cv2
import numpy as np

def rounded_rectangle(src, top_left, bottom_right, radius=1, color=255, thickness=1, line_type=cv2.LINE_AA):

    #  corners:
    #  p1 - p2
    #  |     |
    #  p4 - p3

    p1 = top_left
    p2 = (bottom_right[1], top_left[1])
    p3 = (bottom_right[1], bottom_right[0])
    p4 = (top_left[0], bottom_right[0])

    height = abs(bottom_right[0] - top_left[1])

    if radius > 1:
        radius = 1

    corner_radius = int(radius * (height/2))

    if thickness < 0:

        #big rect
        top_left_main_rect = (int(p1[0] + corner_radius), int(p1[1]))
        bottom_right_main_rect = (int(p3[0] - corner_radius), int(p3[1]))

        top_left_rect_left = (p1[0], p1[1] + corner_radius)
        bottom_right_rect_left = (p4[0] + corner_radius, p4[1] - corner_radius)

        top_left_rect_right = (p2[0] - corner_radius, p2[1] + corner_radius)
        bottom_right_rect_right = (p3[0], p3[1] - corner_radius)

        all_rects = [
        [top_left_main_rect, bottom_right_main_rect], 
        [top_left_rect_left, bottom_right_rect_left], 
        [top_left_rect_right, bottom_right_rect_right]]

        [cv2.rectangle(src, rect[0], rect[1], color, thickness) for rect in all_rects]

    # draw straight lines
    cv2.line(src, (p1[0] + corner_radius, p1[1]), (p2[0] - corner_radius, p2[1]), color, abs(thickness), line_type)
    cv2.line(src, (p2[0], p2[1] + corner_radius), (p3[0], p3[1] - corner_radius), color, abs(thickness), line_type)
    cv2.line(src, (p3[0] - corner_radius, p4[1]), (p4[0] + corner_radius, p3[1]), color, abs(thickness), line_type)
    cv2.line(src, (p4[0], p4[1] - corner_radius), (p1[0], p1[1] + corner_radius), color, abs(thickness), line_type)

    # draw arcs
    cv2.ellipse(src, (p1[0] + corner_radius, p1[1] + corner_radius), (corner_radius, corner_radius), 180.0, 0, 90, color ,thickness, line_type)
    cv2.ellipse(src, (p2[0] - corner_radius, p2[1] + corner_radius), (corner_radius, corner_radius), 270.0, 0, 90, color , thickness, line_type)
    cv2.ellipse(src, (p3[0] - corner_radius, p3[1] - corner_radius), (corner_radius, corner_radius), 0.0, 0, 90,   color , thickness, line_type)
    cv2.ellipse(src, (p4[0] + corner_radius, p4[1] - corner_radius), (corner_radius, corner_radius), 90.0, 0, 90,  color , thickness, line_type)

    return src

Usage:

top_left = (0, 0)
bottom_right = (500, 800)
color = (255, 255, 255)
image_size = (500, 800, 3)
img = np.zeros(image_size)
img = rounded_rectangle(img, top_left, bottom_right, color=color, radius=0.5, thickness=-1)

cv2.imshow('rounded_rect', img)
cv2.waitKey(0)

Rounded rectangle Filled

Rounded rectangle Not Filled

Ahmednagar answered 13/2, 2020 at 14:58 Comment(1)
I wonder how it went unnoticed for over 3 years, but your x/y coordinates are unclear. You need to give this function top_left=(x1, y1), bottom_right=(y2, x2). Since you used top_left=(0/0) this went unnoticed! At least in the full rectangleLienlienhard
M
4

Here's a Python implementation(in case anyone was looking for one): it draws a rounded corner (of random radius and line thickness --- change that if you want) border around an image:

def addRoundedRectangleBorder(img):
    height, width, channels = img.shape

    border_radius = int(width * random.randint(1, 10)/100.0)
    line_thickness = int(max(width, height) * random.randint(1, 3)/100.0)
    edge_shift = int(line_thickness/2.0)

    red = random.randint(230,255)
    green = random.randint(230,255)
    blue = random.randint(230,255)
    color = (blue, green, red)

    #draw lines
    #top
    cv2.line(img, (border_radius, edge_shift), 
    (width - border_radius, edge_shift), (blue, green, red), line_thickness)
    #bottom
    cv2.line(img, (border_radius, height-line_thickness), 
    (width - border_radius, height-line_thickness), (blue, green, red), line_thickness)
    #left
    cv2.line(img, (edge_shift, border_radius), 
    (edge_shift, height  - border_radius), (blue, green, red), line_thickness)
    #right
    cv2.line(img, (width - line_thickness, border_radius), 
    (width - line_thickness, height  - border_radius), (blue, green, red), line_thickness)

    #corners
    cv2.ellipse(img, (border_radius+ edge_shift, border_radius+edge_shift), 
    (border_radius, border_radius), 180, 0, 90, color, line_thickness)
    cv2.ellipse(img, (width-(border_radius+line_thickness), border_radius), 
    (border_radius, border_radius), 270, 0, 90, color, line_thickness)
    cv2.ellipse(img, (width-(border_radius+line_thickness), height-(border_radius + line_thickness)), 
    (border_radius, border_radius), 10, 0, 90, color, line_thickness)
    cv2.ellipse(img, (border_radius+edge_shift, height-(border_radius + line_thickness)), 
    (border_radius, border_radius), 90, 0, 90, color, line_thickness)

    return img
Metamorphic answered 1/4, 2017 at 16:37 Comment(0)
P
0

based on the code from @author K. make the function have ability to draw filled rounded rectangle if pass -1 as thickness

python version

def DrawRoundedRectangle(img, topLeft, bottomRight, radius=1, color=255, thickness=1, line_type=cv.LINE_AA):

min_half = int(min((bottomRight[0] - topLeft[0]), (bottomRight[1] - topLeft[1])) * 0.5)
radius = min(radius, min_half)

# /* corners:
#  * p1 - p2
#  * |     |
#  * p4 - p3
#  */
p1 = topLeft
p2 = (bottomRight[0], topLeft[1])
p3 = bottomRight
p4 = (topLeft[0], bottomRight[1])

if(thickness < 0):
    # // draw rectangle
    cv.rectangle(img, (p1[0] + radius, p1[1]),  (p3[0] - radius, p3[1]), color, thickness, line_type)
    cv.rectangle(img, (p1[0], p1[1] + radius),  (p3[0], p3[1] - radius), color, thickness, line_type)
else:
    # // draw straight lines
    cv.line(img, (p1[0] + radius, p1[1]),  (p2[0] - radius, p2[1]), color, thickness, line_type);
    cv.line(img, (p2[0], p2[1] + radius),  (p3[0], p3[1] - radius), color, thickness, line_type);
    cv.line(img, (p4[0] + radius, p4[1]),  (p3[0]-radius, p3[1]), color, thickness, line_type);
    cv.line(img, (p1[0], p1[1] + radius),  (p4[0], p4[1] - radius), color, thickness, line_type);

# // draw arcs
if(radius > 0):
    cv.ellipse( img, (p1[0] + radius, p1[1] + radius), ( radius, radius ), 180.0, 0, 90, color, thickness, line_type );
    cv.ellipse( img, (p2[0] - radius, p2[1] + radius), ( radius, radius ), 270.0, 0, 90, color, thickness, line_type );
    cv.ellipse( img, (p3[0] - radius, p3[1] - radius), ( radius, radius ), 0.0, 0, 90, color, thickness, line_type );
    cv.ellipse( img, (p4[0] + radius, p4[1] - radius), ( radius, radius ), 90.0, 0, 90, color, thickness, line_type );

javascript version

function DrawRoundedRectangle(img, topLeft, bottomRight, radius=1, color=255, thickness=1, line_type=cv.LINE_AA){

let min_half = Math.floor(Math.min((bottomRight.x - topLeft.x), (bottomRight.y - topLeft.y)) * 0.5)
radius = Math.min(radius, min_half)

/* corners:
#  * p1 - p2
#  * |     |
#  * p4 - p3
#  */
let p1 = topLeft
let p2 = new cv.Point(bottomRight.x, topLeft.y)
let p3 = bottomRight
let p4 = new cv.Point(topLeft.x, bottomRight.y)

if(thickness < 0){
    // draw rectangle
    cv.rectangle(img, new cv.Point(p1.x + radius, p1.y),  new cv.Point(p3.x - radius, p3.y), color, thickness, line_type)
    cv.rectangle(img, new cv.Point(p1.x, p1.y + radius),  new cv.Point(p3.x, p3.y - radius), color, thickness, line_type)
}
else{
    // draw straight lines
    cv.line(img, new cv.Point(p1.x + radius, p1.y),  new cv.Point(p2.x - radius, p2.y), color, thickness, line_type);
    cv.line(img, new cv.Point(p2.x, p2.y + radius),  new cv.Point(p3.x, p3.y - radius), color, thickness, line_type);
    cv.line(img, new cv.Point(p4.x + radius, p4.y),  new cv.Point(p3.x-radius, p3.y), color, thickness, line_type);
    cv.line(img, new cv.Point(p1.x, p1.y + radius),  new cv.Point(p4.x, p4.y - radius), color, thickness, line_type);
}
// draw arcs
if(radius > 0){
    cv.ellipse( img, new cv.Point(p1.x + radius, p1.y + radius), new cv.Size( radius, radius ), 180.0, 0, 90, color, thickness, line_type );
    cv.ellipse( img, new cv.Point(p2.x - radius, p2.y + radius), new cv.Size( radius, radius ), 270.0, 0, 90, color, thickness, line_type );
    cv.ellipse( img, new cv.Point(p3.x - radius, p3.y - radius), new cv.Size( radius, radius ), 0.0, 0, 90, color, thickness, line_type );
    cv.ellipse( img, new cv.Point(p4.x + radius, p4.y - radius), new cv.Size( radius, radius ), 90.0, 0, 90, color, thickness, line_type );
}
}
Piaffe answered 15/2, 2023 at 18:58 Comment(0)
S
0

The answer from Kenyakorn Ketsombut was not quite correct. Not sure if perhaps the parameters changed from OpenCV 10+ years ago, or if those mistakes were always there, but some of the angle parameters are not quite correct. If used like that post was written, the corners don't align correctly with the straight lines.

This is my similar C++ solution, inspired from that older answer.

void draw_rounded_rectangle(cv::Mat & mat, const cv::Rect & r)
{
    const auto linetype = cv::LineTypes::LINE_AA;
    const auto linethickness = 3;
    const cv::Scalar colour(0, 0, 255); // red (BGR format)
    
    const cv::Point tl(r.tl());
    const cv::Point br(r.br());
    const cv::Point tr(br.x, tl.y);
    const cv::Point bl(tl.x, br.y);
    
    const int hoffset = std::round((tr.x - tl.x) / 8.0f);
    const int voffset = std::round((bl.y - tl.y) / 8.0f);
    
    // draw horizontal and vertical segments
    cv::line(mat, cv::Point(tl.x + hoffset, tl.y), cv::Point(tr.x - hoffset, tr.y), colour, linethickness, linetype);
    cv::line(mat, cv::Point(tr.x, tr.y + voffset), cv::Point(br.x, br.y - voffset), colour, linethickness, linetype);
    cv::line(mat, cv::Point(br.x - hoffset, br.y), cv::Point(bl.x + hoffset, bl.y), colour, linethickness, linetype);
    cv::line(mat, cv::Point(bl.x, bl.y - voffset), cv::Point(tl.x, tl.y + voffset), colour, linethickness, linetype);

    // draw each of the corners
    cv::ellipse(mat, tl + cv::Point(+hoffset, +voffset), cv::Size(hoffset, voffset), 0.0, 180.0 , 270.0 , colour, linethickness, linetype);
    cv::ellipse(mat, tr + cv::Point(-hoffset, +voffset), cv::Size(hoffset, voffset), 0.0, 270.0 , 360.0 , colour, linethickness, linetype);
    cv::ellipse(mat, br + cv::Point(-hoffset, -voffset), cv::Size(hoffset, voffset), 0.0, 0.0   , 90.0  , colour, linethickness, linetype);
    cv::ellipse(mat, bl + cv::Point(+hoffset, -voffset), cv::Size(hoffset, voffset), 0.0, 90.0  , 180.0 , colour, linethickness, linetype);
    
    return;
}
Selfdriven answered 31/7 at 2:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.