OpenCV C++案例实战三十一《动态时钟》
- 前言
- 一、绘制表盘
- 二、绘制刻线
- 三、获取系统时间
- 四、结果展示
- 五、源码
- 总结
前言
本案例将使用OpenCV C++实现动态时钟效果。原理也很简单,主要分为绘制表盘、以及获取系统时间两步。
一、绘制表盘
首先为了效果显示美观一点,选取一张背景图。
接着需要绘制一个圆形表盘,直接上代码、效果演示。注释都在源码上有标注。
Point center(background.cols / 2, background.rows / 2);//圆心
int radius = min(background.cols / 2, background.rows / 2) - 20; //时钟半径
Mat mask = Mat::zeros(background.size(), CV_8UC3);
circle(mask, center, radius, Scalar::all(255), -1);//掩模
Mat canvas = Mat(background.size(), CV_8UC3, Scalar(175, 165, 0));//创建画布
background.copyTo(canvas, mask);//将背景拷贝到画布中,形成表盘背景
circle(canvas, center, radius, Scalar::all(0), 3);//表盘
二、绘制刻线
接下来,需要在表盘上绘制时针、分针刻线。其中原理就是计算点的旋转坐标。可以参考一下我的这篇博文OpenCV C++案例实战二十七《角度测量》
int margin = 5;//若margin为0,则点在表盘上
//画分针刻线
int minute_len = 10; //刻线长度
for (int i = 0; i < 60; i++)
{
//圆上坐标点计算公式,对于分针刻线,360/60=6,即每隔6°一刻线
int x1 = center.x + (radius - margin) * cos(i*6.0*CV_PI / 180.0);
int y1 = center.y + (radius - margin) * sin(i*6.0*CV_PI / 180.0);
int x2 = center.x + (radius - minute_len) * cos(i*6.0*CV_PI / 180.0);
int y2 = center.y + (radius - minute_len) * sin(i*6.0*CV_PI / 180.0);
line(canvas, Point(x1, y1), Point(x2, y2), Scalar::all(0), 2, LINE_AA);
}
//画时针刻线
int hour_len = 20;
for (int i = 0; i < 12; i++)
{
//对于时针刻线,每隔360/12=30,即每隔30°一刻线
int x1 = center.x + (radius - margin) * cos(i*30.0*CV_PI / 180.0);
int y1 = center.y + (radius - margin) * sin(i*30.0*CV_PI / 180.0);
int x2 = center.x + (radius - hour_len)*cos(i*30.0*CV_PI / 180.0);
int y2 = center.y + (radius - hour_len)*sin(i*30.0*CV_PI / 180.0);
line(canvas, Point(x1, y1), Point(x2, y2), Scalar::all(0), 3, LINE_AA);
//在表盘上显示3、6、9、12时,坐标位置自行根据图像大小设定
if (i == 0)
{
putText(canvas, to_string(i + 3), Point(x2 - 30, y2 + 10), FONT_HERSHEY_SIMPLEX, 1, Scalar::all(0), 3);
}
else if (i == 3)
{
putText(canvas, to_string(i + 3), Point(x2 - 10, y2 - 10), FONT_HERSHEY_SIMPLEX, 1, Scalar::all(0), 3);
}
else if (i == 6)
{
putText(canvas, to_string(i + 3), Point(x2 + 10, y2 + 10), FONT_HERSHEY_SIMPLEX, 1, Scalar::all(0), 3);
}
else if (i == 9)
{
putText(canvas, to_string(i + 3), Point(x2 - 20, y2 + 30), FONT_HERSHEY_SIMPLEX, 1, Scalar::all(0), 3);
}
}
效果如上图所示,至此前期的预处理工作已经完成了。接下来,需要获取系统时间,然后绘制到表盘上。
三、获取系统时间
当我们获取到相应的系统时间后,有一点需要注意的是,opencv是以3点钟方向为起点,即0°方向,且为顺时针旋转,故秒针、分针、时针在0~3点钟区间需要换算。具体换算请看源码注释。
//使用while循环,不断更新时间
while (true)
{
char key = waitKey(1000);
if (key == 27)break;
Mat clockImg = canvas.clone();//将表盘复制一份,用于不断更新时钟刻线
//获取系统时间
SYSTEMTIME Time;
GetLocalTime(&Time);
int second = Time.wSecond; //秒
int minute = Time.wMinute; //分
int hour = Time.wHour; //时
int day = Time.wDay; //日
int month = Time.wMonth; //月
int year = Time.wYear; //年
printf("%4d/%02d/%02d %02d:%02d:%02d\n", year, month, day, hour, minute, second);
//由于opencv是以3点钟方向为起点,且为顺时针旋转,故秒针、分针、时针在0~3点钟区间需要换算
//秒针
int sec_angle = 0;
if (second <= 15)
{
//当秒针处于0~15秒时,对应角度应处于270~360°,每隔6°走一刻线
sec_angle = second * 6 + 270;
}
else
{
sec_angle = (second - 15) * 6;
}
int sec_x = center.x + (radius - margin * 12) *cos(sec_angle*CV_PI / 180);
int sec_y = center.y + (radius - margin * 12) *sin(sec_angle*CV_PI / 180);
line(clockImg, center, Point(sec_x, sec_y), Scalar(0, 255, 0), 2, LINE_AA);
//分针
int min_angle = 0;
if (minute <= 15)
{
//当分针处于0~15分时,对应角度应处于270~360°,每隔6°走一刻线
min_angle = minute * 6 + 270;
}
else
{
min_angle = (minute - 15) * 6;
}
int min_x = center.x + (radius - margin*18)*cos(min_angle*CV_PI / 180);
int min_y = center.y + (radius - margin*18)*sin(min_angle*CV_PI / 180);
line(clockImg, center, Point(min_x, min_y), Scalar(0, 255, 255), 4, LINE_AA);
//时针
int hour_angle = 0;
if (hour <= 3)
{
//当时针处于0~3时,对应角度应处于270~360°,每隔30°走一刻线
hour_angle = hour * 30 + 270;
}
else
{
hour_angle = (hour - 3) * 30;
}
int hour_x = center.x + (radius - margin * 24)*cos(hour_angle*CV_PI / 180);
int hour_y = center.y + (radius - margin * 24)*sin(hour_angle*CV_PI / 180);
line(clockImg, center, Point(hour_x, hour_y), Scalar(255, 255, 0), 6, LINE_AA);
circle(clockImg, center, 5, Scalar::all(0), -1);
//将时间显示在表盘上
char text1[100], text2[100];
sprintf_s(text1, "%04d%s%02d%s%02d", year, "/", month, "/", day);
sprintf_s(text2, "%02d%s%02d%s%02d", hour, ":", minute, ":", second);
putText(clockImg, text1, Point(center.x-100, center.y+200), FONT_HERSHEY_SIMPLEX, 1, Scalar(21, 23, 161), 3);
putText(clockImg, text2, Point(center.x-70, center.y+250), FONT_HERSHEY_SIMPLEX, 1, Scalar(21, 23, 161), 2);
imshow("dynamic clock", clockImg);
imwrite("dynamic clock.jpg", clockImg);
}
四、结果展示
五、源码
#include<iostream>
#include<opencv2/opencv.hpp>
#include<Windows.h>
using namespace std;
using namespace cv;
int main()
{
Mat background = imread("background.jpg");
if (background.empty())
{
cout << "can not read the image..." << endl;
system("pause");
return -1;
}
Point center(background.cols / 2, background.rows / 2);//圆心
int radius = min(background.cols / 2, background.rows / 2) - 20; //时钟半径
Mat mask = Mat::zeros(background.size(), CV_8UC3);
circle(mask, center, radius, Scalar::all(255), -1);//掩模
Mat canvas = Mat(background.size(), CV_8UC3, Scalar(175, 165, 0));//创建画布
background.copyTo(canvas, mask);//将背景拷贝到画布中,形成表盘背景
circle(canvas, center, radius, Scalar::all(0), 3);//表盘
int margin = 5;//若margin为0,则点在表盘上
//画分针刻线
int minute_len = 10; //刻线长度
for (int i = 0; i < 60; i++)
{
//圆上坐标点计算公式,对于分针刻线,360/60=6,即每隔6°一刻线
int x1 = center.x + (radius - margin) * cos(i*6.0*CV_PI / 180.0);
int y1 = center.y + (radius - margin) * sin(i*6.0*CV_PI / 180.0);
int x2 = center.x + (radius - minute_len) * cos(i*6.0*CV_PI / 180.0);
int y2 = center.y + (radius - minute_len) * sin(i*6.0*CV_PI / 180.0);
line(canvas, Point(x1, y1), Point(x2, y2), Scalar::all(0), 2, LINE_AA);
}
//画时针刻线
int hour_len = 20;
for (int i = 0; i < 12; i++)
{
//对于时针刻线,每隔360/12=30,即每隔30°一刻线
int x1 = center.x + (radius - margin) * cos(i*30.0*CV_PI / 180.0);
int y1 = center.y + (radius - margin) * sin(i*30.0*CV_PI / 180.0);
int x2 = center.x + (radius - hour_len)*cos(i*30.0*CV_PI / 180.0);
int y2 = center.y + (radius - hour_len)*sin(i*30.0*CV_PI / 180.0);
line(canvas, Point(x1, y1), Point(x2, y2), Scalar::all(0), 3, LINE_AA);
//在表盘上显示3、6、9、12时,坐标位置自行根据图像大小设定
if (i == 0)
{
putText(canvas, to_string(i + 3), Point(x2 - 30, y2 + 10), FONT_HERSHEY_SIMPLEX, 1, Scalar::all(0), 3);
}
else if (i == 3)
{
putText(canvas, to_string(i + 3), Point(x2 - 10, y2 - 10), FONT_HERSHEY_SIMPLEX, 1, Scalar::all(0), 3);
}
else if (i == 6)
{
putText(canvas, to_string(i + 3), Point(x2 + 10, y2 + 10), FONT_HERSHEY_SIMPLEX, 1, Scalar::all(0), 3);
}
else if (i == 9)
{
putText(canvas, to_string(i + 3), Point(x2 - 20, y2 + 30), FONT_HERSHEY_SIMPLEX, 1, Scalar::all(0), 3);
}
}
//使用while循环,不断更新时间
while (true)
{
char key = waitKey(1000);
if (key == 27)break;
Mat clockImg = canvas.clone();//将表盘复制一份,用于不断更新时钟刻线
//获取系统时间
SYSTEMTIME Time;
GetLocalTime(&Time);
int second = Time.wSecond; //秒
int minute = Time.wMinute; //分
int hour = Time.wHour; //时
int day = Time.wDay; //日
int month = Time.wMonth; //月
int year = Time.wYear; //年
printf("%4d/%02d/%02d %02d:%02d:%02d\n", year, month, day, hour, minute, second);
//由于opencv是以3点钟方向为起点,且为顺时针旋转,故秒针、分针、时针在0~3点钟区间需要换算
//秒针
int sec_angle = 0;
if (second <= 15)
{
//当秒针处于0~15秒时,对应角度应处于270~360°,每隔6°走一刻线
sec_angle = second * 6 + 270;
}
else
{
sec_angle = (second - 15) * 6;
}
int sec_x = center.x + (radius - margin * 12) *cos(sec_angle*CV_PI / 180);
int sec_y = center.y + (radius - margin * 12) *sin(sec_angle*CV_PI / 180);
line(clockImg, center, Point(sec_x, sec_y), Scalar(0, 255, 0), 2, LINE_AA);
//分针
int min_angle = 0;
if (minute <= 15)
{
//当分针处于0~15分时,对应角度应处于270~360°,每隔6°走一刻线
min_angle = minute * 6 + 270;
}
else
{
min_angle = (minute - 15) * 6;
}
int min_x = center.x + (radius - margin*18)*cos(min_angle*CV_PI / 180);
int min_y = center.y + (radius - margin*18)*sin(min_angle*CV_PI / 180);
line(clockImg, center, Point(min_x, min_y), Scalar(0, 255, 255), 4, LINE_AA);
//时针
int hour_angle = 0;
if (hour <= 3)
{
//当时针处于0~3时,对应角度应处于270~360°,每隔30°走一刻线
hour_angle = hour * 30 + 270;
}
else
{
hour_angle = (hour - 3) * 30;
}
int hour_x = center.x + (radius - margin * 24)*cos(hour_angle*CV_PI / 180);
int hour_y = center.y + (radius - margin * 24)*sin(hour_angle*CV_PI / 180);
line(clockImg, center, Point(hour_x, hour_y), Scalar(255, 255, 0), 6, LINE_AA);
circle(clockImg, center, 5, Scalar::all(0), -1);
//将时间显示在表盘上
char text1[100], text2[100];
sprintf_s(text1, "%04d%s%02d%s%02d", year, "/", month, "/", day);
sprintf_s(text2, "%02d%s%02d%s%02d", hour, ":", minute, ":", second);
putText(clockImg, text1, Point(center.x-100, center.y+200), FONT_HERSHEY_SIMPLEX, 1, Scalar(21, 23, 161), 3);
putText(clockImg, text2, Point(center.x-70, center.y+250), FONT_HERSHEY_SIMPLEX, 1, Scalar(21, 23, 161), 2);
imshow("dynamic clock", clockImg);
imwrite("dynamic clock.jpg", clockImg);
}
destroyAllWindows();
system("pause");
return 0;
}
总结
本文使用OpenCV C++ 进行动态时钟绘制,主要操作有以下几点。
1、图像预处理,绘制表盘
2、绘制表盘刻线
3、获取系统时间,注意角度与时间之间的转换