本期的主要内容是利用OpenCV中包含的绘图函数,例如绘制线段、绘制矩形、绘制圆形等来绘制一个动态时钟的表盘。
完成本期内容,你可以:
-
掌握OpenCV常见的绘图函数
-
学会使用绘图函数绘制简单的图像
若要运行案例代码,你需要有:
-
操作系统:Ubuntu 16 以上 或者 Windows10
-
工具软件:VScode 或者其他源码编辑器
-
硬件环境:无特殊要求
-
核心库:python 3.6.13, opencv-contrib-python 3.4.11.39,opencv-python 3.4.2.16
绘制线段
OpenCV 中提供的绘制线段的函数是 cv2.line()。
函数原型: img = cv2.line(img, plt1, plt2, color, thickness);
img为绘制线段后得到的图像。
参数描述如下:
参数 | 描述 |
---|---|
plt1 | 线段的起点坐标 |
plt2 | 线段的终点坐标 |
color | 绘制线段的线条颜色 |
thickness | 绘制线段时的线条宽度 |
绘制矩形
OpenCV 中提供的绘制矩形的函数是 cv2.rectangle()。
函数原型:img = cv2.rectangle(img, plt1, plt2, color, thickness);
img为绘制矩形后得到的图像。
参数描述如下:
参数 | 描述 |
---|---|
plt1 | 矩形的左上角坐标 |
plt2 | 矩形的右下角坐标 |
color | 绘制矩阵时的线条颜色 |
thickness | 绘制矩阵时的线条宽度,当thickness的值为-1时,可以绘制一个实心矩形 |
绘制圆形
OpenCV中提供的绘制圆形的函数是cv2.circle()。
函数原型:img = cv2.circle(img, center, radius, color, thickness);
img为绘制圆形后得到的图像。
参数描述如下:
参数 | 描述 |
---|---|
center | 圆形的圆心坐标 |
radius | 圆形的半径 |
color | 绘制圆形时的线条颜色 |
thickness | 绘制圆形时的线条宽度,当thickness的值为-1时,可以绘制一个实心圆形 |
绘制多边形
OpenCV中提供的绘制多边形的函数是cv2.polylines()。
函数原型:img = cv2.polylines(img, pts, isClosed, color, thickness);
img为绘制多边形后得到的图像。
参数描述如下:
参数 | 描述 |
---|---|
pts | 由多边形各个顶点坐标组成的一个列表。 |
isClosed | 指示绘制的折线是否闭合的标志。 |
color | 绘制多边形时的线条颜色 |
thickness | 绘制多边形时的线条宽度 |
绘制文字
OpenCV 中提供的绘制文字的函数是cv2.putText()。
函数原型:img = cv2.putText(img, text, org, fontFace, fontScale, color, thickness, lineType,bottomLeftOrigin)
img为绘制文字后得到的图像。
参数描述如下:
参数 | 描述 |
---|---|
text | 要绘制的文本内容 |
org | 文字在画布中的左下角坐标 |
fontFace | 字体样式 |
fontScale | 字体大小 |
color | 绘制文字时的线条颜色 |
thickness | 绘制文字时的线条宽度 |
lineType | 线型 |
bottomLeftOrigin | 绘制文字的方向 |
可选的字体样式,如下:
字体样式 | 含义 |
---|---|
cv.FONT_HERSHEY_SIMPLEX | 正常大小sans-serif字体 |
cv.FONT_HERSHEY_PLAIN | 小尺寸sans-serif字体 |
cv.FONT_HERSHEY_DUPLEX | 正常大小的sans-serif字体(比FONT_HERSHEY_SIMPLEX更复杂) |
cv.FONT_HERSHEY_COMPLEX | 正常大小serif字体 |
cv.FONT_HERSHEY_TRIPLEX | 正常大小serif字体(比FONT_HERSHEY_COMPLEX更复杂) |
cv.FONT_HERSHEY_COMPLEX_SMALL | FONT_HERSHEY_COMPLEX的简化版本 |
cv.FONT_HERSHEY_SCRIPT_SIMPLEX | 手写样式字体 |
cv.FONT_HERSHEY_SCRIPT_COMPLEX | 更复杂的FONT_HERSHEY_SCRIPT_SIMPLEX变体 |
cv.FONT_ITALIC | 斜体字体 |
4.5 表盘刻度线
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NkNNqUPt-1684908365737)(image\md_img.png)]
通过观察可以发现,时钟主要由两部分组成,分别是静态表盘和动态表针。整个表盘其实只有3根表针在动,所以可以先画出静态表盘,然后获取系统当前时间,根据时间实时动态绘制3根表针就解决了。
4.5.1 绘制刻度
表盘上只有60条分/秒刻线和12条小时刻线,当然还有表盘的外部轮廓圆,也就是重点在如何画72根线。
前面我们使用OpenCV画直线的时候,需知道直线的起点和终点坐标,那么画72根线就变成了获取72组坐标。
在平面坐标系下,已知半径和角度的话,A点的坐标可以表示为:
$\ x = r \times cos\alpha $ $\ y = r \times sin\alpha $
先只考虑将坐标系原点移动到左上角,角度依然是平面坐标系中的逆时针计算,那么新坐标是:
x = r + r × c o s α x= r+ r\times\ cos\alpha x=r+r× cosα y = r + r × s i n α y =r+r\times\ sin\alpha y=r+r× sinα
对于60条分/秒刻线,刻线间的夹角是360°/60=6°,对于小时刻线,角度是360°/12=30°,这样就得到了72组起点坐标,那怎么得到终点坐标呢?其实同样的原理,用一个同心的小圆来计算得到终点:
-
角度换算
接下来算是一个小难点,首先时钟的起始坐标在正常二维坐标系的90°方向,其次时钟跟图像一样,都是顺时针计算角度的,所以三者需要统一下:
因对于时钟坐标和图像坐标,时钟0的0°对应图像的270°,时钟15的90°对应图像的360°,时钟30的180°对应图像的450°(270°+180°)…
所以两者之间的关系便是:
计算角度 = 时钟角度+270°
计算角度 = 计算角度 if 计算角度<=360° else 计算角度-360°
具体步骤
1. 创建项目结构
创建项目名为使用OpenCV绘制动态时钟
,项目根目录下新建code
文件夹储存代码,新建dataset
文件夹储存数据,项目结构如下:
使用OpenCV绘制动态时钟 # 项目名称
├── code # 储存代码文件
├── dataset # 储存数据文件
注:如项目结构已存在,无需再创建。
2. 创建画布并绘制表盘
- 在
code
文件夹下创建clock.py
文件; - 导入需要用到的模块
datetime, opencv, math, numpy
; - 设置表盘距上下左右的边距,圆心和半径;
- 创建一个450*450的画布,并填充为白色;
- 画出表盘,线条颜色为黑色,线条宽度为5;
- 展示表盘。
代码实现
# 导入所需模块
import cv2
import math
import datetime
import numpy as np
import matplotlib.pyplot as plt
# 设置边距、半径、圆心
margin = 5 # 上下左右边距
radius = 220 # 圆的半径
center = (center_x, center_y) = (225, 225) # 圆心
# 新建一个画板并填充成白色
img = np.zeros((450, 450, 3), np.uint8)
img[:] = (255, 255, 255)
# 画出圆盘
cv2.circle(img, center, radius, (0, 0, 0), thickness=5)
cv2.imshow('clocking',img)
3. 绘制表盘分钟刻度线
- 计算每一条秒和分刻度线的起点和终点的坐标;
- 画出60条秒和分的刻度线,颜色为黑色,线条宽度为2;
- 展示表盘。
代码实现
# 绘制表盘刻度线
pt1 = []
# 3. 画出60条秒和分钟的刻线
for i in range(60):
# 最外部圆,计算A点
x1 = center_x+(radius-margin)*math.cos(i*6*np.pi/180.0)
y1 = center_y+(radius-margin)*math.sin(i*6*np.pi/180.0)
pt1.append((int(x1), int(y1)))
# 同心小圆,计算B点
x2 = center_x+(radius-15)*math.cos(i*6*np.pi/180.0)
y2 = center_y+(radius-15)*math.sin(i*6*np.pi/180.0)
cv2.line(img, pt1[i], (int(x2), int(y2)), (0, 0, 0), thickness=2)
cv2.imshow('clocking',img)
4. 绘制表盘小时刻度线
- 计算每一条小时刻度线的起点和终点的坐标;
- 画出12条小时刻度线,颜色为黑色,线条宽度为5;
- 展示表盘。
代码实现
# 画出12条小时的刻线
for i in range(12):
# 12条小时刻线应该更长一点
x = center_x+(radius-25)*math.cos(i*30*np.pi/180.0)
y = center_y+(radius-25)*math.sin(i*30*np.pi/180.0)
# 这里用到了前面的pt1
cv2.line(img, pt1[i*5], (int(x), int(y)), (0, 0, 0), thickness=5)
cv2.imshow('clocking',img)
5. 同步时间并绘制指针
- 拷贝表盘;
- 获取系统时间,拆分时-分-秒;
- 根据系统时间,绘制秒刻度线;
- 根据系统时间,绘制分刻度线;
- 根据系统时间,绘制小时刻度线;
- 添加当前日期文字;
- 控制按键输入,当输入‘esc’按键时退出,并销毁窗口
代码实现
#同步时间
while True:
# 不断拷贝表盘图,才能更新绘制,不然会重叠在一起
temp = np.copy(img)
# 获取系统时间,画出动态的时-分-秒三条刻线
now_time = datetime.datetime.now()
hour, minute, second = now_time.hour, now_time.minute, now_time.second
# 画秒刻线
# OpenCV中的角度是顺时针计算的,所以需要转换下
sec_angle = second*6+270 if second <= 15 else (second-15)*6
sec_x = center_x+(radius-margin)*math.cos(sec_angle*np.pi/180.0)
sec_y = center_y+(radius-margin)*math.sin(sec_angle*np.pi/180.0)
cv2.line(temp, center, (int(sec_x), int(sec_y)), (203, 222, 166), 2)
# 画分刻线
min_angle = minute*6+270 if minute <= 15 else (minute-15)*6
min_x = center_x+(radius-35)*math.cos(min_angle*np.pi/180.0)
min_y = center_y+(radius-35)*math.sin(min_angle*np.pi/180.0)
cv2.line(temp, center, (int(min_x), int(min_y)), (186, 199, 137), 8)
# 画时刻线
hour_angle = hour*30+270 if hour <= 3 else (hour-3)*30
hour_x = center_x+(radius-65)*math.cos(hour_angle*np.pi/180.0)
hour_y = center_y+(radius-65)*math.sin(hour_angle*np.pi/180.0)
cv2.line(temp, center, (int(hour_x), int(hour_y)), (169, 198, 26), 15)
# 添加当前日期文字
font = cv2.FONT_HERSHEY_SIMPLEX
time_str = now_time.strftime("%d/%m/%Y")
cv2.putText(img, time_str, (135, 275), font, 1, (0, 0, 0), 2)
cv2.imshow('clocking', temp)
if cv2.waitKey(300) == 27: # 按下ESC键退出
break
cv2.destroyAllWindows()
OpenCV中包含的绘图函数有很多,我们可以通过这些简单图形的组合,在结合一些之前学习的知识可以画出很多好玩的图像。