Reputation: 2122
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.
Upvotes: 15
Views: 22328
Reputation: 20370
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;
}
Upvotes: 0
Reputation: 11
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 );
}
}
Upvotes: 0
Reputation: 2122
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 );
}
Upvotes: 17
Reputation: 355
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)
Upvotes: 12
Reputation: 1970
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
Upvotes: 4