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、获取系统时间,注意角度与时间之间的转换

  • 5
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
课程目的:OpenCV是应用非常广泛的开源视觉处理库,在图像处理、计算机视觉和自动驾驶中有着非常重要的作用。课程设计特色:(课程当前为第一期)1、C++与Python双语教学Python语言是在计算机视觉中应用最多的一种语言,在工作中,深度学习模型的训练基本上都是使用Python语言编写的训练代码。OpenCV在这个过程中用于图像的预处理(例如图像读取、数据增强)和后处理,还可以用于显示处理的结果,功能强大,使用方便。但是在功能的部署的时候,不管是部署在服务端还是PC端,开发语言基本上用的是C++,所以如何有效的使用OpenCV进行模型或者功能的部署尤为重要。C++语言应用的好坏,在面试中可以看出一个面试者的工程实践能力的强弱,两种语言的开发掌握好了可以使工作如虎添翼。2、全模块讲解我出版了一本图书《学习OpenCV4:基于Python的算法实战》,虽然这本书是写的基于Python的算法实战,但是实际上这本书有详细的介绍算法的C++接口,还有一些C++方向的案例,是以Python为主。图书出版的时候就想双语写作,只是限于篇幅没有成行。本课程不仅采用双语教学,更是对C++的每个模块都做讲解,我们知道,很多的书其实只讲imgproc,如果你翻开一本书图像的形态学运算和图像滤波都是作为独立章节讲解的,那么这本书基本上就可以确定是只是讲解了imgproc模块,但是其他的模块在工作中也有很重要的作用。例如:core模块定义了C++的基本数据结构和基本运算(如四则运算);highgui模块是可视化与交互的模块;feature2d是特征点与特征匹配相关算法所在的模块;ml是机器学习相关的模块;dnn是深度学习相关的模块,可以使用OpenCV进行深度学习模型的部署。这些是很多的书和课程都不会讲的。3、讲解细致本课程会从环境搭建开始讲解,环境搭建尤为重要。从我多年的授课经验总结来看,如果只是给了代码,很多的入门用户环境问题处理不好的话,后面的学习很难进行下去,甚至会丧失学习的信心。4、会讲解C++和Python的开发语法问题是入门用户的一大难关,特别是C++语言。大学只是教授了C语言相关的内容,C++很多同学只懂一点皮毛,所以写代码步履维艰,我们在讲解代码的过程中会顺带讲解C++和Python的内容。我们还会讲解编译相关知识,还有库的装载与链接,这些是学校里不会教的,目前也几乎没有课程讲解。5、讲师经验丰富我讲解过C++OpenCV的多个课程,广受学员好评。我出版过两本图书《深度学习计算机视觉实战》和《学习OpenCV4》,两本书都是细致入微的讲解,主要针对的就是初学者,所以能够很好的处理课程的难易程度。6、讲义准备充分讲义准备的充分细致,标识清楚明确,重点和疑难点突出。
下面是一个简单的 C 语言时钟表盘的示例代码,使用了 Windows API 的 GDI 绘图功能: ```c #include <stdio.h> #include <stdlib.h> #include <windows.h> #define WINDOW_WIDTH 400 #define WINDOW_HEIGHT 400 #define PI 3.1415926 LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { WNDCLASS wc = { 0 }; wc.lpfnWndProc = WndProc; wc.hInstance = hInstance; wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wc.lpszClassName = "ClockWindowClass"; wc.style = CS_HREDRAW | CS_VREDRAW; RegisterClass(&wc); HWND hwnd = CreateWindow("ClockWindowClass", "时钟表盘", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, WINDOW_WIDTH, WINDOW_HEIGHT, NULL, NULL, hInstance, NULL); ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd); MSG msg = { 0 }; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { static int cxClient, cyClient; switch (message) { case WM_SIZE: cxClient = LOWORD(lParam); cyClient = HIWORD(lParam); return 0; case WM_PAINT: { PAINTSTRUCT ps; HDC hdc = BeginPaint(hwnd, &ps); // 绘制表盘 HPEN pen = CreatePen(PS_SOLID, 2, RGB(0, 0, 0)); HPEN oldPen = (HPEN)SelectObject(hdc, pen); HBRUSH brush = CreateSolidBrush(RGB(255, 255, 255)); HBRUSH oldBrush = (HBRUSH)SelectObject(hdc, brush); Ellipse(hdc, 50, 50, cxClient - 50, cyClient - 50); SelectObject(hdc, oldPen); DeleteObject(pen); SelectObject(hdc, oldBrush); DeleteObject(brush); // 绘制刻度 double radius = (cxClient - 100) / 2.0; int centerX = cxClient / 2; int centerY = cyClient / 2; for (int i = 0; i < 12; i++) { double angle = i * PI / 6.0; int x1 = (int)(centerX + (radius - 20) * cos(angle)); int y1 = (int)(centerY - (radius - 20) * sin(angle)); int x2 = (int)(centerX + radius * cos(angle)); int y2 = (int)(centerY - radius * sin(angle)); MoveToEx(hdc, x1, y1, NULL); LineTo(hdc, x2, y2); } EndPaint(hwnd, &ps); return 0; } case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProc(hwnd, message, wParam, lParam); } ``` 这个程序使用了 Windows API 来创建一个窗口,并在窗口中绘制了一个简单的时钟表盘。程序主要的代码在 WM_PAINT 事件处理中,首先绘制了一个白色的圆形表盘,然后在表盘上绘制了 12 条刻度线,每条刻度线之间相隔 30 度,最后使用 EndPaint 函数结束绘制。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Zero___Chen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值