OpenCV图形绘制详解

894 阅读9分钟

「这是我参与2022首次更文挑战的第8天,活动详情查看:2022首次更文挑战

前言

《OpenCV绘图基础详解》中我们已经了解了绘制图形在构建计算机视觉项目时的重要性,本节将了解如何使用 OpenCV 函数绘制图形。首先,介绍基本图形的绘制,然后将详细介绍更高级的图形绘制。

基本图形的绘制

OpenCV 中的基本图形,包括直线、矩形和圆形等,它们是最常见和最容易绘制的形状。绘制图形的第一步是创建一个容纳绘制图形的画布。为此,将创建具有 3 个通道(以正确显示 BGR 图像)和 uint8 类型(8 位无符号整数)的 500 x 500 的黑色背景图像:

image = np.zeros((500, 500, 3), dtype="uint8")

之后使用颜色字典将背景设置为浅灰色:

image[:] = colors['gray']

一切准备就绪,接下来,我们准备绘制基本形状。需要注意的是,OpenCV 提供的大多数绘图函数都有共同的参数,因此首先总结介绍这些参数,如下表所示:

参数说明
img要绘制图形的画布图像
color用于绘制图形的颜色(BGR 三元组)
thickness如果此值为正,则为图形轮廓的粗细;否则,将绘制填充形状
lineType图形边线的类型。 OpenCV 提供了三种类型的线:cv2.LINE_4 :四连接线,cv2.LINE_8 :八连接线,cv2.LINE_AA :抗锯齿线
shift表示与定义图形的某些点的坐标相关的小数位数

上述参数中,lineTypecv2.LINE_AA 选项可产生更好的绘图质量,但绘制速度较慢。八连接线和四连接线都是非抗锯齿线,使用 Bresenham 算法绘制。而抗锯齿线使用高斯滤波算法。

直线

我们要了解的第一个函数是直线绘制函数 cv2.line(),函数用法如下:

img = cv2.line(img, pt1, pt2, color, thickness=1, lineType=8, shift=0)

此函数在 img 图像上画一条连接 pt1pt2 的直线:

cv2.line(image, (0, 0), (500, 500), colors['magenta'], 3)
cv2.line(image, (0, 500), (500, 0), colors['cyan'], 10)
cv2.line(image, (250, 0), (250, 500), colors['rand'], 3)
cv2.line(image, (0, 250), (500, 250), colors['yellow'], 10)

绘制图形后,调用 show_with_matplotlib(image, 'cv2.line()') 函数显示图像:

直线

矩形

矩形绘制函数 cv2.rectangle() 用法如下:

img = cv2.rectangle(img, pt1, pt2, color, thickness=1, lineType=8, shift=0)

此函数根据矩形左上角点 pt1 和 右下角点 pt2 绘制矩形:

cv2.rectangle(image, (10, 50), (60, 300), colors['green'], 3)
cv2.rectangle(image, (80, 50), (130, 300), colors['blue'], -1)
cv2.rectangle(image, (150, 50), (350, 100), colors['red'], -1)
cv2.rectangle(image, (150, 150), (350, 300), colors['cyan'], 10)

绘制这些矩形后,调用 show_with_matplotlib(image, 'cv2.rectangle()') 函数显示图形:

矩形绘制

Note:thickness 参数若为负值(例如 -1),则意味着将使用颜色填充图形。

圆形

圆形图形的绘制函数 cv2.circle() 用法如下:

img = cv2.circle(img, center, radius, color, thickness=1, lineType=8, shift=0)

此函数以点 center 为中心绘制一个半径为 radius 的圆:

cv2.circle(image, (50, 50), 40, colors['magenta'], 3)
cv2.circle(image, (150, 150), 40, colors['rand'], -1)
cv2.circle(image, (250, 250), 50, colors['yellow'], 5)
cv2.circle(image, (250, 250), 60, colors['yellow'], 2)
cv2.circle(image, (350, 350), 40, colors['cyan'], -2)
cv2.circle(image, (450, 450), 40, colors['blue'], 3)

绘制完这些圆形后,调用 show_with_matplotlib(image, 'cv2.circle()') 函数显示图像:

圆形绘制

高级图形的绘制

了解了常见基本图形的绘制后,接下来将介绍如何绘制剪裁线、箭头、椭圆和折线等。同样第一步是创建一个将绘制图形的画布:

image = np.zeros((500, 500, 3), dtype="uint8")
image[:] = colors['gray']

接下来,可以开始绘制新的图形了。

剪裁线

剪裁线绘制函数 cv2.clipLine() 使用方法如下:

retval, pt1_new, pt2_new = cv2.clipLine(imgRect, pt1, pt2)

cv2.clipLine() 函数返回矩形内的线段(由输出点 pt1_newpt2_new 定义),该函数根据定义的矩形 imgRect 裁剪线段。如果两个原始点 pt1pt2 都在矩形之外,则 retvalFalse;否则返回 True

cv2.line(image, (0, 0), (500, 500), colors['green'], 3)
cv2.rectangle(image, (100, 100), (300, 300), colors['blue'], 3)
ret, p1, p2 = cv2.clipLine((100, 100, 300, 300), (0, 0), (300, 300))
if ret:
    cv2.line(image, p1, p2, colors['magenta'], 3)
ret, p1, p2 = cv2.clipLine((100, 100, 300, 300), (250, 150), (0, 400))
if ret:
    cv2.line(image, p1, p2, colors['cyan'], 3)

调用 show_with_matplotlib(image, 'cv2.clipLine()') 函数后,在下图中,可以看到代码运行的结果:

裁剪线绘制

箭头

箭头绘制函数 cv2.arrowedLine() 的用法如下:

cv2.arrowedLine(img, pt1, pt2, color, thickness=1, lineType=8, shift=0, tipLength=0.1)

此函数用于绘制箭头,箭头从 pt1 定义的点指向 pt2 定义的点。箭头尖端的长度可以由 tipLength 参数控制,该参数是根据线段长度( pt1pt2 之间的距离)的百分比定义的:

# 箭头尖端的长度为线段长度的 10%
cv2.arrowedLine(image, (50, 50), (450, 50), colors['cyan'], 3, 8, 0, 0.1)
# 箭头尖端的长度为线段长度的 30%
cv2.arrowedLine(image, (50, 200), (450, 200), colors['magenta'], 3, cv2.LINE_AA, 0, 0.3)
# 箭头尖端的长度为线段长度的 30%
cv2.arrowedLine(image, (50, 400), (450, 400), colors['blue'], 3, 8, 0, 0.3)

以上代码定义了三个箭头,除了箭头的大小不同外,使用了不同的 lineType 参数 cv2.LINE_AA (也可以写 16 )和 8 (也可以写 cv2.LINE_8 ),调用 show_with_matplotlib(image, 'cv2.arrowedLine()') 函数后,可以观察它们之间的区别:

箭头绘制

椭圆

绘制椭圆的函数 cv2.ellipse() 用法如下:

cv2.ellipse(img, center, axes, angle, startAngle, endAngle, color, thickness=1, lineType=8, shift=0)

此函数用于绘制不同类型的椭圆:angle 参数(以度为单位)可以旋转椭圆;axes 参数控制长短轴的大小;startAngleendAngle 参数用于设置所需的椭圆弧(以度为单位),例如,需要完整闭合的椭圆,则 startAngle = 0endAngle = 360

cv2.ellipse(image, (100, 100), (60, 40), 0, 0, 360, colors['red'], -1)
cv2.ellipse(image, (100, 200), (80, 40), 0, 0, 360, colors['green'], 3)
cv2.ellipse(image, (100, 200), (10, 40), 0, 0, 360, colors['blue'], 3)
cv2.ellipse(image, (300, 300), (20, 80), 0, 0, 180, colors['yellow'], 3)
cv2.ellipse(image, (300, 100), (20, 80), 0, 0, 270, colors['cyan'], 3)
cv2.ellipse(image, (250, 250), (40, 40), 0, 0, 360, colors['magenta'], 3)
cv2.ellipse(image, (400, 100), (30, 60), 45, 0, 360, colors['rand'], 3)
cv2.ellipse(image, (400, 400), (30, 60), -45, 0, 360, colors['rand'], 3)
cv2.ellipse(image, (200, 400), (30, 60), -45, 0, 225, colors['rand'], -1)

调用 show_with_matplotlib(image, 'cv2.ellipse()') 函数后,可以在下图中看到所绘制的椭圆:

椭圆

多边形

多边形绘制函数 cv2.polylines() 用法如下:

cv2.polylines(img, pts, isClosed, color, thickness=1, lineType=8, shift=0)

cv2.polylines() 函数用于绘制多边形。其中关键参数是 pts,用于提供定义多边形的数组,这个参数的形状是 (number_vertex, 1, 2),可以通过使用 np.array 创建坐标( np.int32 类型)来定义它,然后对其进行整形以适应 pts 参数所需形状。如果要创建一个三角形,代码如下所示:

pts = np.array([[400, 5], [350, 200], [450, 200]], np.int32)
pts = pts.reshape((-1, 1, 2))
print("shape of pts '{}'".format(pts.shape))
cv2.polylines(image, [pts], True, colors['green'], 3)

另一个重要参数是 isClosed,如果此参数为 True ,则多边形将被绘制为闭合的;否则,第一个顶点和最后一个顶点之间的线段将不会被绘制,从而产生开放的多边形:

pts = np.array([[400, 250], [350, 450], [450, 450]], np.int32)
pts = pts.reshape((-1, 1, 2))
print("shape of pts '{}'".format(pts.shape))
cv2.polylines(image, [pts], False, colors['green'], 3)

以同样的方式,创建五边形和矩形,最后调用 show_with_matplotlib(image, 'cv2.polylines()') 函数,可以在下图中看到绘制完成的多边形:

多边形

绘图函数中的shift参数

上述图形绘制函数通过使用 shift 参数,可以与像素坐标相关的亚像素精度工作,此时坐标将作为定点数(被编码为整数)传递。

定点数意味着为整数(小数点左侧)和小数部分(小数点右侧)保留固定位数。

因此,shift 参数用于指定小数位数(在小数点右侧),真实点坐标计算如下:

Point(x,y)Point2f(xshift,yshift)Point(x,y)\longrightarrow Point2f(x^{-shift},y^{-shift})

例如,使用以下代码绘制了两个半径为 200 的圆。其中之一使用 shift = 2 的值来提供亚像素精度。在这种情况下,应该将原点和半径都乘以因子 4 (2shift=22^{shift= 2}):

shift = 2
factor = 2 ** shift
print("factor: '{}'".format(factor))
cv2.circle(image, (int(round(249.99 * factor)), int(round(249.99 * factor))), 200 * factor, colors['red'], 1, shift=shift)
cv2.circle(image, (249, 249), 200, colors['green'], 1)

如果 shift = 3,则因子的值将为 8 (2shift=32^{shift=3}),依此类推。乘以 2 的幂与将对应于整数二进制表示的位向左移动 1 位,这样就可以将图形浮点坐标上。

为了更方便的使用浮点坐标,也可以对绘图函数进行封装,以 cv2.circle() 为例创建函数 draw_float_circle(),它可以使用 shift 参数属性来处理浮点坐标:

def draw_float_circle(img, center, radius, color, thickness=1, lineType=8, shift=4):
	factor = 2 ** shift
	center = (int(round(center[0] * factor)), int(round(center[1] * factor)))
	radius = int(round(radius * factor))
	cv2.circle(img, center, radius, color, thickness, lineType, shift)
	
draw_float_circle(image, (250, 250), 200, colors['red'], 1, 8, 0)
draw_float_circle(image, (249.9, 249.9), 200, colors['green'], 1, 8, 1)
draw_float_circle(image, (249.99, 249.99), 200, colors['blue'], 1, 8, 2)
draw_float_circle(image, (249.999, 249.999), 200, colors['yellow'], 1, 8, 3)

调用 show_with_matplotlib(image, 'cv2.circle()') 函数,可以看到绘制完成的图像:

shift参数

绘图函数中的 lineType 参数

另一个绘图函数中常见的参数是 lineType,它可以采用三个不同的值。虽然我们已经简单了解了这三种类型之间的差异。但是为了更清楚地看到它,可以绘制了三条具有相同粗细和倾斜度的线,仅 lineType 参数值不同:

image = np.zeros((20, 20, 3), dtype="uint8")
image[:] = colors['gray']
cv2.line(image, (5, 0), (20, 15), colors['yellow'], 1, cv2.LINE_4)
cv2.line(image, (0, 0), (20, 20), colors['red'], 1, cv2.LINE_AA)
cv2.line(image, (0, 5), (15, 20), colors['green'], 1, cv2.LINE_8)
show_with_matplotlib(image, 'LINE_4  LINE_AA  LINE_8')

绘图函数中的 lineType 参数

在上图中,yellow = cv2.LINE_4red = cv2.LINE_AAgreen = cv2.LINE_8,可以清楚地看到使用三种不同线型绘制线条时的区别。

相关链接

OpenCV绘图基础详解