Python OpenCV入门到精通学习日记:绘制图形与文字
前言
Opencv提供了许多绘制图形的方法,包括了绘制线段line()
方法,绘制矩形的rectangle()
方法,绘制圆形的circle()
方法,绘制多边形的polylines
方法和绘制文字的putText()
方法。今天就学习这些方法的使用。
绘制图形和文字
1 线段的绘制
Opencv提供了用于绘制线段的line()
方法,使用这个方法可以创建各式各样的线段。语法如下:
img = cv2.line(img,pt1,pt2,color,thickness
参数说明:
img:画布。
pt1:线段的起点坐标。
pt2:线段的终点坐标。
color:绘制线段时的线条颜色。
thickness:绘制线段时的线条宽度。
示例:使用line()方法分别绘制颜色为蓝色、绿色、红色和黄色,线条宽度为5、10、15和20的4条线段,并且这4条线段能够拼成一个“王”字。
import numpy as np
import cv2
# np.zeros()创建一个画布
# (300,300,3):一个300*300,具有3个颜色空间的画布
# np.uint8:OPencv中的灰度图像和RGB图像都是以uint8存储的,因此这里的类型也是uint8
canvas = np.zeros((300,300,3),np.uint8)
# 在画布上绘制一条起点坐标为(50,50),终点坐标为(250,50),蓝色的,线条宽度为5的线段
canvas = cv2.line(canvas,(50,50),(250,50),(255,0,0),5)
# 在画布上,绘制一条起点坐标为(50, 150)、终点坐标为(250, 150)、绿色的、线条宽度为10的线段
canvas = cv2.line(canvas,(50,150),(250,150),(0,255,0),10)
# 在画布上,绘制一条起点坐标为(50, 250)、终点坐标为(250, 250)、红色的、线条宽度为15的线段
canvas = cv2.line(canvas,(50,250),(250,250),(0,0,255),15)
# 在画布上,绘制一条起点坐标为(150, 50)、终点坐标为(150, 250)、黄色的、线条宽度为20的线段
canvas = cv2.line(canvas,(150,50),(150,250),(0,255,255),20)
cv2.imshow("lines",canvas)
cv2.waitKey()
cv2.destroyAllWindows()
运行结果如下:
注意在这里(x,y)坐标是正常表示的
如果想要把黑色背景改为白色背景,只需要替换
canvas = np.zeros((300,300,3),np.uint8)
为
canvas = np.ones((300,300,3),np.uint8)*255
至于为什么,看我前面的文章就可以啦!
2 矩形的绘制
OpenCV提供了用于绘制矩形的rectangle()
方法,使用这个方法既可以绘制矩形边框,也可以绘制实心矩形。语法如下:
img = cv2.rectangle(img, pt1, pt2, color, thickness)
参数和之前一样,在这不再赘述。
示例:编写一个程序,使用rectangle()方法绘制一个青色的、线条宽度为20的矩形边框。绘制矩形时,矩形的左上角坐标为(50, 50),矩形的右下角坐标为(200, 150)。
canvas = np.zeros((300,300,3),np.uint8)
canvas = cv2.rectangle(canvas,(50,50),(200,150),(255,255,0),20)
cv2.imshow("rectangle",canvas)
cv2.waitKey()
cv2.destroyAllWindows()
运行结果如下:
现在我们已经绘制了一个矩形,那么如何绘制实心矩形呢?在rectangle
方法的语法格式中,我们只需要将thickness
的值修改为-1,就可以绘制实心矩形了。
关键代码:canvas = cv2.rectangle(canvas,(50,50),(200,150),(255,255,0),-1)
学到这里,我们可以来个进阶版本:
示例:编写一个程序,使用rectangle()方法分别绘制3个正方形边框和1个实心正方形。
canvas = np.zeros((300,300,3),np.uint8)
canvas = cv2.rectangle(canvas,(50,50),(250,250),(0,0,255),40)
canvas = cv2.rectangle(canvas,(90,90),(210,210),(0,255,0),30)
canvas = cv2.rectangle(canvas,(120,120),(180,180),(255,0,0),20)
canvas = cv2.rectangle(canvas,(140,140),(160,160),(0,255,255),-1)
cv2.imshow("square",canvas)
cv2.waitKey()
cv2.destroyAllWindows()
运行结果如下:
3 圆形的绘制
OpenCV提供了用于绘制圆形的circle()
方法,这个方法与rectangle()
方法的功能相同,既可以绘制圆形边框,也可以绘制实心圆形。circle()
方法的语法格式如下:
img = cv2.circle(img, center, radius, color, thickness)
参数说明:
img:画布。
center:圆形的圆心坐标。
radius:圆形的半径。
color:绘制圆形时的线条颜色。
thickness:绘制圆形时的线条宽度。
圆形的绘制和矩形的绘制很相似,只是矩形的起始坐标和终点坐标参数改为了圆心坐标和半径,大家可以尝试绘制个圆形试试。这里展示如何绘制同心圆和随机圆的示例,原理都很简单。
示例:编写一个程序,使用circle()方法和for循环绘制5个同心圆,这些圆形的圆心坐标均为画布的中心,半径的值分别为0,30,60,90和120,线条颜色均为绿色,线条宽度均为5。
canvas = np.zeros((300,300,3),np.uint8)
center_x = int(canvas.shape[1]/2)
center_y = int(canvas.shape[0]/2)
for r in range(0,150,30):
cv2.circle(canvas,(center_x,center_y),r,(0,255,0),5)
cv2.imshow("circle",canvas)
cv2.waitKey()
cv2.destroyAllWindows()
运行结果如下:
示例:编写一个程序,使用circle()方法和for循环随机绘制27个实心圆。其中,圆心的横、纵坐标在[0, 299]内取值,半径在[11, 70]内取值,线条颜色由3个在[0, 255]内的随机数组成的列表表示。
canvas = np.zeros((300,300,3),np.uint8)
for numbers in range(0,28):
center_x = np.random.randint(0,high = 300)
center_y = np.random.randint(0,high = 300)
r = np.random.randint(11,high = 71)
# 使用NumPy库的random模块中的randint函数来生成随机整数
# 这个函数可以生成指定范围内的随机整数数组
color = np.random.randint( # 调用randint函数
# 第一个参数是随机整数的最小值,这里设置为0
0,
# high参数指定随机整数的最大值,这里设置为256
# 由于randint是闭区间,所以实际生成的整数范围是[0, 255]
high=256,
# size参数定义了生成的随机整数数组的形状
# 这里设置为(3,),意味着生成一个包含3个元素的一维数组
# 这三个元素分别代表RGB颜色模型中的红色、绿色和蓝色通道
size=(3,)
).tolist() # randint函数调用结束
# tolist()方法将NumPy数组转换为Python列表
# 因为后续的cv2.circle函数需要一个列表作为颜色参数
# 所以这里将生成的NumPy数组转换为列表.tolist()
cv2.circle(canvas,(center_x,center_y),r,color,-1)
cv2.imshow("circles",canvas)
cv2.waitKey()
cv2.destroyAllWindows()
因为color的定义的内容可能新手有些难懂,我在这里打上了详细的注释,random.randint的用法在像素的操作那一章已经讲过了
运行结果如下:
4 多边形的绘制
OpenCV提供了绘制多边形的polylines()
方法,使用这个方法绘制的多边形既可以是闭合的,也可以是不闭合的。polylines()
方法的语法格式如下:
img = cv2.polylines(img, pts, isClosed, color,thickness)
参数说明:
img:画布。
pts:由多边形各个顶点的坐标组成的一个列表,这个列表是一个numpy的数组类型。
isClosed:如果值为True,表示一个闭合的多边形;如果值为False,表示一个不闭合的多边形。
color:绘制多边形时的线条颜色。
thickness:绘制多边形时的线条宽度。
示例:编写一个程序,按顺时针给出等腰梯形4个顶点的坐标,即(100,50),(200, 50),(250, 250)和(50, 250)。在画布上根据4个顶点的坐标,绘制一个闭合的、红色的、线条宽度为5的等腰梯形边框。
canvas = np.zeros((300,300,3),np.uint8)
# 按顺时针给出等腰梯形4个顶点的坐标
# 这4个顶点的坐标构成了一个大小等于“顶点个数 * 1 * 2”的数组
# 这个数组的数据类型为np.int32
pts = np.array([[100,50],[200,50],[250,250],[50,250]],np.int32)
canvas = cv2.polylines(canvas,[pts],True,(0,0,255),5)
cv2.imshow("polylines",canvas)
cv2.waitKey()
cv2.destroyAllWindows()
注意绘制边框时,给出的顶点坐标是顺时针或逆时针给出的,否则无法绘制。
运行结果如下:
5 文字的绘制
OpenCV提供了用于绘制文字的putText()
方法,使用这个方法不仅能够设置字体的样式、大小和颜色,而且能够使字体呈现斜体的效果,还能够控制文字的方向,进而使文字呈现垂直镜像的效果。putText()
方法的语法格式如下:
img = cv2.putText(img, text, org, fontFace, fontScale,color, thickness, lineType, bottomLeftOrigin)
参数说明:
img:画布。
text:要绘制的文字内容。
org:文字在画布中的左下角坐标。
fontFace:字体样式,可选参数如表所示。
fontScale:字体大小。
color:绘制文字时的线条颜色。
thickness:绘制文字时的线条宽度。
lineType:线型。(线型指的是线的产生算法,有4和8两个值,默认值为8)
bottomLeftOrigin:绘制文字时的方向。(有True和False两个值,默认值为False)
常量名称 | 描述 |
---|---|
cv2.FONT_HERSHEY_SIMPLEX | 简单字体样式 |
cv2.FONT_HERSHEY_PLAIN | 无衬线字体,较细 |
cv2.FONT_HERSHEY_DUPLEX | 双倍宽度的字体 |
cv2.FONT_HERSHEY_COMPLEX | 复杂字体 |
cv2.FONT_HERSHEY_TRIPLEX | 三倍宽度的字体 |
cv2.FONT_HERSHEY_COMPLEX_SMALL | 小号复杂字体 |
cv2.FONT_HERSHEY_SCRIPT_SIMPLEX | 手写风格字体 |
cv2.FONT_HERSHEY_SCRIPT_COMPLEX | 复杂手写风格字体 |
示例:编写一个程序,在画布上绘制文字“hello”。其中,文字左下角的坐标为(20, 70),字体样式为FONT_HERSHEY_TRIPLEX,字体大小为2,线条颜色是绿色,线条宽度为5。
canvas = np.zeros((300,300,3),np.uint8)
cv2.putText(canvas,"hello",(20,70),cv2.FONT_HERSHEY_TRIPLEX,2,(0,255,0),5)
cv2.imshow("text",canvas)
cv2.waitKey()
cv2.destroyAllWindows()
运行结果如下:
不借助其他库或者模块,绘制中文时,会出现乱码
FONT_ITALIC
可以和其他文字类型一起使用,在呈现字体样式的同时,可以呈现斜体效果。
将参数fontface
可以进行如下赋值,关键代码:
fontStyle = cv2.FONT_HERSHEY_TRIPLEX + cv2.FONT_ITALIC
5.1 文字的垂直镜像效果
在putText()
方法的语法格式中,有一个控制绘制文字时的方向的参数,即 bottomLeftOrigin
, 其默认值为False
。当bottomLeftOrigin
为True
时,文字将呈现垂直镜像效果。
关键代码:
cv2.putText(canvas, "hello", (20, 100),cv2.FONT_HERSHEY_TRIPLEX , 2,(0, 255, 0), 5, 8, True)
记住,如果使文字“mrsoft”呈现垂直镜像效果,这时lineType和bottomLeftOrigin变成了必须参数
5.2 在图像上绘制文字
OpenCV除了可以在np.zeros()
创建的画布上绘制文字外,还能够在图像上绘制文字。区别是当在图像上绘制文字时,不再需要导入Python中的numpy模块。
示例:编写一个程序,在img.png上绘制文字“women”。其中,文字左下角的坐标为(20, 90),字体样式为FONT_HERSHEY_TRIPLEX,字体大小为1,线条颜色是黄色。
img = cv2.imread("img.png")
cv2.putText(img,"women",(20,90),cv2.FONT_HERSHEY_TRIPLEX,1,(0,255,255))
cv2.imshow("text",img)
cv2.waitKey()
cv2.destroyAllWindows()
运行结果如下:
6 动态绘制图形
我们在前面已经学会了如何绘制静态的图形和文字了,那么如何让静态变动态呢?想要实现这个功能需要解决两个问题:如何计算运动轨迹和如何实现动画。
示例:在一个宽、高都为200像素的纯白色图像中,绘制一个半径为20像素的纯蓝色小球。让小球做匀速直线运动,一旦圆形碰触到图像边界则开始反弹(反弹不损失动能)。
- 计算运动轨迹
首先,我们需要定义小球的初始位置和速度。 假设水平速度为 v x ,垂直速度为 v y 。 假设水平速度为 v_x ,垂直速度为 v_y 。 假设水平速度为vx,垂直速度为vy。
小球的运动可以通过以下方式计算:
- 每帧更新小球的位置:
x new = x + v x x_{\text{new}} = x + v_x xnew=x+vx
- 每帧更新小球的位置:
y new = y + v y y_{\text{new}} = y + v_y ynew=y+vy
当小球碰到边界时,需要改变其速度的正负号。具体规则如下:
如果小球的横坐标超出图像宽度,改变
v
x
的正负号。
如果小球的横坐标超出图像宽度,改变 v_x 的正负号。
如果小球的横坐标超出图像宽度,改变vx的正负号。
如果小球的纵坐标超出图像高度,改变
v
y
的正负号。
如果小球的纵坐标超出图像高度,改变 v_y 的正负号。
如果小球的纵坐标超出图像高度,改变vy的正负号。
- 通过time模块实现动画效果
Python自带一个time时间模块,该模块提供了一个sleep()
方法可以让当前线程休眠一段时间。
语法如下:
time.sleep(seconds)
参数说明:
seconds:休眠时间,单位为s,可以是小数,如1/10表示(1/10)s。
动画实际上是由多幅画面在短时间内交替放映实现的视觉效果。每一幅画面被称为一帧,所谓的60帧就是指1s放映了60幅画面。使用time模块每(1/60)s计算一次小球的移动轨迹,并将移动后的结果绘制到图像上,这样1s有60幅图像交替放映,就可以看到弹球的动画效果了。
import cv2, time, numpy as np # 导入所需的库
width, height = 200, 200 # 设置窗口的宽度和高度
r = 20 # 圆的半径
x = r + 20 # 初始水平位置
y = r + 20 # 初始垂直位置
x_offer = y_offer = 4 # 圆的水平和垂直移动速度
while cv2.waitKey(1) == -1: # 持续循环直到用户关闭窗口
if x > width - r or x < r: # 检查圆是否到达窗口的右侧或左侧边界
x_offer *= -1 # 改变圆的水平移动方向
if y > height - r or y < r: # 检查圆是否到达窗口的底部或顶部边界
y_offer *= -1 # 改变圆的垂直移动方向
x += x_offer # 更新圆的水平位置
y += y_offer # 更新圆的垂直位置
img = np.ones((width, height, 3), np.uint8) * 255 # 创建一个全白的图像
cv2.circle(img, (x, y), r, (255, 0, 0), -1) # 在图像上绘制一个红色的圆
cv2.imshow("img", img) # 显示图像
time.sleep(1 / 60) # 等待约1/60秒,控制动画的帧率
cv2.destroyAllWindows() # 关闭所有OpenCV窗口
想要修改小球初始速度就修改xy的初始速度,矢量和就是小球初始速度。
运行代码如下:
7 小结
绘制图形和文字这一块主要还是记住那几个函数和参数,很多都大同小异。难度还是在numpy对数组计算上。得多看看。从静态到动态的过渡就是对time模块和帧的认识和使用,需要一定的逻辑性,可以试试编写一个贪吃蛇了。