文章结构
- 线段的绘制
- 矩形的绘制
- 圆形的绘制
- 多边形的绘制
- 文字的绘制
- 文字的斜体效果
- 文字的垂直镜像效果
- 在图像上绘制文字
- 动态绘制图形
线段的绘制
使用 line()
方法可绘制长短不一的、粗细各异的、五颜六色的线段。
img = cv2.line(img,pt1,pt2,color,thickness)
- img: 画布
- pt1: 线段的起点坐标
- pt2: 线段的终点坐标
- color: 绘制线段时的线条颜色
- thickness: 绘制线段时的线条宽度
当使用line()方法绘制线段时,要指定线条颜色。
实例1: 绘制线段
使用line()方法分别绘制颜色为蓝色,线条宽度为5的线段。
import cv2
import numpy as np
# np.zeros() 创建了一个画布
# (300,300,3) 一个300×300,具有3个颜色空间(即Red、Green和Blue)的画布
# 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)
cv2.imshow("line", canvas)
cv2.waitKey()
cv2.destroyAllWindows()
结果如下:
如果想换成白色背景,则可以使用以下代码:
canvas = np.ones((300,300,3),np.uint8)*255
如果想随机绘制线段,需要借助numpy.random下的uniform()方法。uniform()方法的作用是从一个均匀分布的数据中随机采样。
numpy.random.uniform(low,high,size)
- low: 采样下界,float类型,默认值为0.0
- high: 采样上界,float类型,默认值为1.0
- size: 输出样本数目,int类型或者元组类型,默认值为None
需要注意两个问题:一个是样本的取值范围,即包含low,但不包含high;另一个是uniform()方法返回的是一个float类型的值,为了能将这个值应用到本实例,需要将其转换成int类型的值。
实例2: 绘制99条长度、方向、宽度、颜色随机的线段
import cv2
import numpy as np
# np.zeros() 创建了一个画布
# (300,300,3) 一个300×300,具有3个颜色空间(即Red、Green和Blue)的画布
# np.uint8 OpenCV中的灰度图像和RGB图像都是以uint8存储的,因此这里的类型也是uint8
canvas = np.zeros((300, 300, 3), np.uint8)
for i in range(0, 100):
pt1x = int(np.random.uniform(0, 300)) # 线段的起点横坐标在1 ~ 299的范围内随机取值
pt1y = int(np.random.uniform(0, 300)) # 线段的起点纵坐标在1 ~ 299的范围内随机取值
pt2x = int(np.random.uniform(0, 300)) # 线段的终点横坐标在1 ~ 299的范围内随机取值
pt2y = int(np.random.uniform(0, 300)) # 线段的终点纵坐标在1 ~ 299的范围内随机取值
r = int(np.random.uniform(0, 256)) # R通道在0 ~ 255的范围内随机取值
g = int(np.random.uniform(0, 256)) # G通道在0 ~ 255的范围内随机取值
b = int(np.random.uniform(0, 256)) # B通道在0 ~ 255的范围内随机取值
thickness = int(np.random.uniform(1, 6)) # 线条宽度在1 ~ 5的范围内随机取值
canvas = cv2.line(canvas, (pt1x, pt1y), (pt2x, pt2y), (r, g, b), thickness)
cv2.imshow("line", canvas)
cv2.waitKey()
cv2.destroyAllWindows()
结果如下:
矩形的绘制
rectang()方法既可以绘制矩形边框,也可以绘制实心矩形。
img = cv2.rectangle(img, pt1, pt2, color, thickness)
- img: 画布
- pt1: 矩形的左上角坐标
- pt2: 矩形的右下角坐标
- color: 绘制矩形时的线条颜色
- thickness: 绘制矩形时的线条宽度
- img: 画布
实例3: 绘制一个矩形边框
绘制一个青色的、线条宽度为20的矩形边框。
import numpy as np
import cv2
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()
结果如下:
如果将代码改成以下形式,将会出现一个实心矩形:
import numpy as np
import cv2
canvas = np.zeros((300,300,3),np.uint8)
# 在画布上绘制一矩形边框
canvas = cv2.rectangle(canvas,(50,50),(200,150),(255,255,0),-1) # 将thickness改为-1
cv2.imshow("Rectangle",canvas)
cv2.waitKey()
cv2.destroyAllWindows()
结果如下:
圆形的绘制
circle()方法既可以绘制圆形边框,也可以绘制实心圆形。
img = cv2.circle(img,center,radius,color,thickness)
- img: 画布
- center: 圆形的圆心坐标
- radius: 圆形的半径
- color: 绘制圆形时的线条颜色
- thickness: 绘制圆形时的线条宽度
- img: 画布
实例4: 绘制同心圆
import numpy as np
import cv2
canvas = np.zeros((300, 300, 3), np.uint8)
# shape[1]表示画布的宽度,center_X表示圆心的横坐标
# 圆心的横坐标等于画布宽度的一半
center_X = int(canvas.shape[1] / 2)
# shape[0]表示画布的高度,center_Y表示圆心的纵坐标
# 圆心的纵坐标等于画布高度的一半
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("Circles", canvas)
cv2.waitKey()
cv2.destroyAllWindows()
结果如下:
实例5: 绘制27个随机实心圆
import numpy as np
import cv2
canvas = np.zeros((300, 300, 3), np.uint8)
for i in range(0,28):
# 获得随机的圆心横坐标,这个横坐标在[0,299]范围内取值
center_X = np.random.randint(0,high=300)
# 获得随机的圆心纵坐标,这个横坐标在[0,299]范围内取值
center_Y = np.random.randint(0,high=300)
# 获得随机的半径,这个半径在[11,70]范围内取值
radius = np.random.randint(11,high=71)
# 获得随机的线条颜色,这个颜色由3个在[0,255]范围内的随机数组成的列表表示
color = np.random.randint(0,high=256,size=(3,)).tolist()
# 绘制一个圆心坐标为(center_X,center_Y)、半径为radius、颜色为color的实心圆形
cv2.circle(canvas, (center_X, center_Y), radius, color, -1)
cv2.imshow("Circles", canvas)
cv2.waitKey()
cv2.destroyAllWindows()
结果如下:
注意:因为OpenCV中的颜色值是一个列表,而randint返回的是一个数值或者数组,所以color = np.random.randint(0,high = 256,size = (3,)).tolist()中的.tolist()不能忽略。
多边形的绘制
使用polylines()方法绘制的多边形既可以是封闭的,也可以是不封闭的。
img = cv2.polylines(img, pts, isClosed, color, thickness)
- img: 画布
- pts: 由多边形各个顶点的坐标组成的列表,这个列表是一个NumPy的数组类型
- isClosed: 如果值为True,表示一个闭合的多边形;如果值为False,表示一个不闭合的多边形
- color: 绘制多边形时的线条颜色
- thickness: 绘制多边形时的线条宽度
实例6: 绘制一个等腰梯形边框
import numpy as np # 导入Python中的numpy模块
import cv2
# np.zeros():创建了一个画布
# (300, 300, 3):一个300 x 300,具有3个颜色空间(即Red、Green和Blue)的画布
# np.uint8:OpenCV中的灰度图像和RGB图像都是以uint8存储的,因此这里的类型也是uint8
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)
# 在画布上根据4个顶点的坐标,绘制一个闭合的、红色的、线条宽度为5的等腰梯形边框
canvas = cv2.polylines(canvas, [pts], True, (0, 0, 255), 5)
cv2.imshow("Polylines", canvas) # 显示画布
cv2.waitKey()
cv2.destroyAllWindows()
结果如下:
如果改成以下代码,会得到一个不封闭的矩形:
canvas = cv2.polylines(canvas, [pts], False, (0, 0, 255), 5)
结果如下:
文字的绘制
使用用于绘制文字的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:(可选)绘制文字时的方向(默认值为False)
字体样式及其含义如下:
实例7: 绘制文字“OpenCV”
import numpy as np # 导入Python中的numpy模块
import cv2
# np.zeros():创建了一个画布
# (100, 300, 3):一个100 x 300,具有3个颜色空间(即Red、Green和Blue)的画布
# np.uint8:OpenCV中的灰度图像和RGB图像都是以uint8存储的,因此这里的类型也是uint8
canvas = np.zeros((100, 300, 3), np.uint8)
# 在画布上绘制文字“OpenCV”,文字左下角的坐标为(20, 70)
# 字体样式为FONT_HERSHEY_TRIPLEX
# 字体大小为2,线条颜色是绿色,线条宽度为5
cv2.putText(canvas, "OpenCV", (20, 70), cv2.FONT_HERSHEY_TRIPLEX, 2, (0, 255, 0), 5)
cv2.imshow("Text", canvas) # 显示画布
cv2.waitKey()
cv2.destroyAllWindows()
结果如下:
如果不借助其他库或者模块的话,使用putText()方法绘制中文时会出现乱码。
文字的斜体效果
FONT_ITALIC可以与其他文字类型一起使用,使字体在呈现指定字体样式效果的同时,也呈现斜体效果。
实例8: 绘制指定字体样式的文字并呈现斜体效果
import numpy as np # 导入Python中的numpy模块
import cv2
# np.zeros():创建了一个画布
# (100, 300, 3):一个100 x 300,具有3个颜色空间(即Red、Green和Blue)的画布
# np.uint8:OpenCV中的灰度图像和RGB图像都是以uint8存储的,因此这里的类型也是uint8
canvas = np.zeros((100, 300, 3), np.uint8)
# 字体样式为FONT_HERSHEY_TRIPLEX和FONT_ITALIC
fontStyle = cv2.FONT_HERSHEY_TRIPLEX + cv2.FONT_ITALIC
# 在画布上绘制文字“OpenCV”,文字左下角的坐标为(20, 70)
# 字体样式为fontStyle,字体大小为2,线条颜色是绿色,线条宽度为5
cv2.putText(canvas, "OpenCV", (20, 70), fontStyle, 2, (0, 255, 0), 5)
cv2.imshow("Text", canvas) # 显示画布
cv2.waitKey()
cv2.destroyAllWindows()
结果如下:
文字的垂直镜像效果
当bottomLeftOrigin为True时,文字将呈现垂直镜像效果。
实例9: 绘制呈现垂直镜像效果的“OpenCV”
import numpy as np # 导入Python中的numpy模块
import cv2
# np.zeros():创建了一个画布
# (200, 300, 3):一个200 x 300,具有3个颜色空间(即Red、Green和Blue)的画布
# np.uint8:OpenCV中的灰度图像和RGB图像都是以uint8存储的,因此这里的类型也是uint8
canvas = np.zeros((200, 300, 3), np.uint8)
# 字体样式为FONT_HERSHEY_TRIPLEX
fontStyle = cv2.FONT_HERSHEY_TRIPLEX
# 在画布上绘制文字“OpenCV”,文字左下角的坐标为(20, 70)
# 字体样式为fontStyle,字体大小为2,线条颜色是绿色,线条宽度为5
cv2.putText(canvas, "OpenCV", (20, 70), fontStyle, 2, (0, 255, 0), 5)
# 使文字“OpenCV”呈现垂直镜像效果,这时lineType和bottomLeftOrigin变成了必须参数
# 其中,lineType取默认值8,bottomLeftOrigin的值为True
cv2.putText(canvas, "OpenCV", (20, 100), fontStyle, 2, (0, 255, 0), 5, 8, True)
cv2.imshow("Text", canvas) # 显示画布
cv2.waitKey()
cv2.destroyAllWindows()
结果如下:
在图像上绘制文字
当在图像上绘制文字时,不需要再导入NumPy模块。
实例10: 在图像上绘制文字
import cv2
image = cv2.imread("2.jpg") # 读取2.jpg
# 字体样式为FONT_HERSHEY_TRIPLEX
fontStyle = cv2.FONT_HERSHEY_TRIPLEX
# 在image上绘制文字“OpenCV”,文字左下角的坐标为(20, 90),
# 字体样式为fontStyle,字体大小为1,线条颜色是黄色
cv2.putText(image, "Flower", (20, 90), fontStyle, 1, (0, 255, 255))
cv2.imshow("Text", image) # 显示画布
cv2.waitKey()
cv2.destroyAllWindows()
结果如下:
动态绘制图形
实例11: 弹球动画
在一个宽高都为200像素的白色图像中,绘制一个半径为20像素的蓝色小球。让小球做匀速直线运动,一旦圆形碰触到图像边界则发生反弹。
想要实现这个功能需要解决两个问题:如何计算运动轨迹和如何实现动画。
(1)通过图像坐标系计算运动轨迹
小球在运动的过程中可以把移动速度划分为上、下、左、右四个方向。左右为横坐标移动速度,上下为纵坐标移动速度。小球向右移动时横坐标不断变大,向左移动时横坐标不断变小,由此可以认为:小球向右的移动速度为正数,向左的移动速度为负数。纵坐标同理,因为图像坐标系的原点为背景左上角顶点,越往下延伸纵坐标越大,所以小球向上的移动速度为负数,向下的移动速度为正数。
假设小球移动一段时间之后,移动的轨迹如图4.24 所示,分别达到了四个位置,2号位置和3号位置发生了反弹,也就是移动速度发生了变化,导致了移动方向发生变化。整个过程中,四个位置的速度分别如下所示:
- 1:右下方向移动,横坐标向右,横坐标速度为 +Vx,纵坐标向下,纵标速度为+Vy
- 2:右上方向移动,横坐标向右,横坐标速度为 +Vx,纵坐标向上,纵坐标速度为-Vy
- 3:左上方向移动,横坐标向左,横坐标速度为 -Vx,纵坐标向上,纵坐标速度为-Vy
- 4:左上方向移动,没有碰到边界,依然保持着与3号位置相同移动速度。
由此可以得出,小球只需要改变速度的正负号就可以改变移动的方向,所以在程序中可以将小球的横坐标速度和纵坐标速度设定成一个不变的值,每次小球碰到左右边界,就更改横坐标速度的正负号,碰到上下边界,就更改纵坐标速度的正负号。
(2)通过time模块实现动画效果
Python自带一个time时间模块,该模块提供了一个sleep()方法可以让当前线程休眠一段时间。
time.sleep(seconds)
- seconds: 休眠的秒数,可以是小数。
动画实际上是有多个画面在短时间内交替放映实现的视觉效果。每一个画面被称为一帧。使用time模块每1/60s计算一次小球的移动轨迹,并将移动后的结果绘制到图像上。
import cv2
import time
import numpy as np
width, height = 200, 200 # 画面的宽和高
r = 20 # 圆半径
x = r + 20 # 圆心和坐标起始坐标
y = r + 100 # 圆形纵坐标起始坐标
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/60s,也就是每秒60帧
cv2.destroyAllWindows() # 释放所有窗体
如果waitKey括号里的值为0、空或者负数,则它在按下按键之前只执行一次循环,它会无限等待按下按键而x,y的值不变。看上去就是圆形不动。所以将括号内的值设置为一毫秒,当未按下按键时,每一毫秒都会执行一次循环,x,y的值都会改变,这看起来就能让圆形动起来,直到按下任意按键,跳出循环,释放所有窗体。
结果如下: