使用 C++/OpenCV 制作跳动的爱心动画
本文将引导你如何使用 C++ 和 OpenCV 库创建一个简单但有趣的跳动爱心动画。我们将通过绘制参数方程定义的爱心形状,并利用正弦函数来模拟心跳的缩放效果。
目录
- 简介
- 先决条件
- 核心概念
- 参数方程绘制爱心
- 动画循环
- 模拟心跳效果
- 代码实现
- 代码详解
drawHeart
函数main
函数
- 编译与运行
- 效果展示
- 总结与扩展
简介
动画是通过快速连续显示一系列静态图像来创建运动的幻觉。在这个项目中,我们将每一帧都绘制一个爱心,并通过周期性地改变爱心的大小来模拟“跳动”的效果。这是一个学习 OpenCV 绘图和基本动画原理的有趣练习。
先决条件
- C++ 编译器: 如 G++ (MinGW for Windows), Clang, MSVC。
- OpenCV 库: 版本 3.x 或 4.x,并已正确配置编译环境。
- CMake (推荐): 用于跨平台编译管理。
核心概念
参数方程绘制爱心
爱心的形状可以通过参数方程精确绘制。一个常用的爱心参数方程是:
x
=
16
sin
3
(
t
)
x = 16 \sin^3(t)
x=16sin3(t)
y
=
13
cos
(
t
)
−
5
cos
(
2
t
)
−
2
cos
(
3
t
)
−
cos
(
4
t
)
y = 13 \cos(t) - 5 \cos(2t) - 2 \cos(3t) - \cos(4t)
y=13cos(t)−5cos(2t)−2cos(3t)−cos(4t)
其中 t t t 的范围从 0 0 0 到 2 π 2\pi 2π。我们将计算出一系列由这些方程产生的 ( x , y ) (x, y) (x,y) 点,然后将这些点连接起来形成多边形,并填充颜色。
动画循环
动画的核心是一个循环,在每次迭代中:
- 清除或重新初始化画布。
- 根据当前时间或状态计算爱心的大小和/或位置。
- 在画布上绘制爱心。
- 显示画布。
- 短暂延迟,以控制动画速度。
- 检查退出条件(例如按键)。
模拟心跳效果
心跳是一个周期性的扩张和收缩过程。我们可以使用正弦函数来模拟这种大小变化。爱心的缩放比例 scale
可以表示为:
current_scale = base_scale * (1 + amplitude * sin(frequency * time_variable))
其中:
base_scale
是爱心的基础大小。amplitude
控制跳动的幅度。frequency
控制跳动的快慢。time_variable
是一个随时间递增的变量(例如帧计数器)。
代码实现
#include <opencv2/opencv.hpp>
#include <vector>
#include <cmath> // For std::sin, std::cos, std::pow, M_PI (or define PI)
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
const int WINDOW_WIDTH = 600;
const int WINDOW_HEIGHT = 600;
const std::string WINDOW_NAME = "Beating Heart";
// 函数:绘制一个爱心
void drawHeart(cv::Mat& image, double scale, const cv::Point& center, const cv::Scalar& color) {
std::vector<cv::Point> heart_points;
for (double t = 0; t <= 2 * M_PI; t += 0.01) { // t 从 0 到 2*PI
double x_param = 16 * std::pow(std::sin(t), 3);
double y_param = 13 * std::cos(t) - 5 * std::cos(2 * t) - 2 * std::cos(3 * t) - std::cos(4 * t);
// 转换到 OpenCV 坐标系并应用缩放和中心偏移
// 注意 y_param 是负的,因为参数方程通常y轴向上,而OpenCV图像y轴向下
int cv_x = static_cast<int>(center.x + scale * x_param);
int cv_y = static_cast<int>(center.y - scale * y_param); // 注意这里的负号
heart_points.push_back(cv::Point(cv_x, cv_y));
}
if (!heart_points.empty()) {
// cv::polylines(image, heart_points, true, color, 2); // 仅绘制轮廓
cv::fillPoly(image, std::vector<std::vector<cv::Point>>{heart_points}, color); // 填充爱心
}
}
int main() {
cv::Mat frame(WINDOW_HEIGHT, WINDOW_WIDTH, CV_8UC3, cv::Scalar(20, 20, 20)); // 深灰色背景
double time = 0.0;
double base_scale = 10.0; // 爱心的基础缩放系数
double beat_amplitude = 0.2; // 心跳振幅 (相对于1的比例)
double beat_frequency = 0.15; // 心跳频率 (数值越大跳动越快)
cv::Scalar heart_color = cv::Scalar(0, 0, 255); // BGR: 红色
while (true) {
// 1. 清除画布 (用背景色重新填充)
frame = cv::Scalar(20, 20, 20);
// 2. 计算当前心跳的缩放比例
// (1 + sin(t)) 范围是 [0, 2], (1 + sin(t))/2 范围是 [0, 1]
// 这里我们用 1.0 +/- amplitude*sin(t) 来使大小在 (1-amplitude)*base_scale 和 (1+amplitude)*base_scale 之间变化
double current_beat_factor = 1.0 + beat_amplitude * std::sin(beat_frequency * time);
double current_scale = base_scale * current_beat_factor;
// 3. 绘制爱心
cv::Point center(WINDOW_WIDTH / 2, WINDOW_HEIGHT / 2 + 30); // 稍微向下移动一点,让爱心尖端更居中
drawHeart(frame, current_scale, center, heart_color);
// 4. 显示帧
cv::imshow(WINDOW_NAME, frame);
// 5. 延迟并检查按键
int key = cv::waitKey(30); // 大约 33 FPS
if (key == 27) { // ESC键
break;
}
time += 1.0; // 更新时间变量
}
cv::destroyAllWindows();
return 0;
}
代码详解
drawHeart
函数
cv::Mat& image
: 要在其上绘制爱心的画布。double scale
: 控制爱心整体大小的缩放因子。const cv::Point& center
: 爱心在画布上的中心点。const cv::Scalar& color
: 爱心的颜色。- 循环变量
t
从0
到2 * M_PI
步进,计算参数方程定义的点(x_param, y_param)
。 cv_x = static_cast<int>(center.x + scale * x_param);
cv_y = static_cast<int>(center.y - scale * y_param);
:将参数点转换为 OpenCV 窗口坐标。特别注意y_param
前的负号,这是因为参数方程中定义的 y 轴通常指向上方,而 OpenCV 图像的 y 轴指向下方。- 计算出的点存储在
std::vector<cv::Point> heart_points
中。 cv::fillPoly(...)
: 使用指定的颜色填充由heart_points
定义的多边形(爱心)。cv::polylines
可以用来只画轮廓。
main
函数
cv::Mat frame(...)
: 创建一个CV_8UC3
类型(8位无符号字符型,3通道彩色)的图像作为画布,并用深灰色初始化背景。time
: 一个简单的时间变量/帧计数器,用于驱动心跳动画的周期性变化。base_scale
: 爱心参数方程中固有大小的一个基础乘数。调整它会改变爱心的整体大小。beat_amplitude
: 控制心跳时大小变化的幅度。例如,0.2 表示大小在基础大小的 (1-0.2)倍 到 (1+0.2)倍之间变化。beat_frequency
: 控制心跳的快慢。值越大,跳动越快。while (true)
: 主动画循环。frame = cv::Scalar(20, 20, 20);
: 每帧开始时,用背景色重置(清空)画布。current_beat_factor = 1.0 + beat_amplitude * std::sin(beat_frequency * time);
: 使用std::sin
函数计算当前帧的缩放因子。sin
函数产生[-1, 1]
范围内的值,因此current_beat_factor
会在[1.0 - beat_amplitude, 1.0 + beat_amplitude]
之间平滑地变化。current_scale = base_scale * current_beat_factor;
: 计算最终应用于drawHeart
函数的缩放值。drawHeart(...)
: 调用函数绘制当前帧的爱心。cv::imshow(WINDOW_NAME, frame);
: 显示包含爱心的帧。cv::waitKey(30);
: 等待 30 毫秒。这既控制了动画的帧率,也为 OpenCV 处理窗口事件(如按键)提供了机会。如果用户按下 ESC 键(ASCII码 27),循环中断。time += 1.0;
: 递增时间变量,为下一帧的计算做准备。
cv::destroyAllWindows();
: 关闭所有 OpenCV 创建的窗口。
编译与运行
假设你的 C++ 文件名为 beating_heart.cpp
。
使用 CMake (推荐):
- 创建
CMakeLists.txt
文件:cmake_minimum_required(VERSION 3.10) project(BeatingHeart) set(CMAKE_CXX_STANDARD 14) # C++14 或更高 find_package(OpenCV REQUIRED) include_directories(${OpenCV_INCLUDE_DIRS}) add_executable(BeatingHeartApp beating_heart.cpp) target_link_libraries(BeatingHeartApp ${OpenCV_LIBS})
- 编译:
mkdir build && cd build cmake .. make # 或者在 Visual Studio 中打开生成的项目并编译 ./BeatingHeartApp # Linux/macOS # .\Release\BeatingHeartApp.exe # Windows (Visual Studio Release build)
直接使用 g++ (Linux/macOS):
g++ beating_heart.cpp -o BeatingHeartApp $(pkg-config --cflags --libs opencv4)
./BeatingHeartApp
(如果你的 pkg-config
对应 OpenCV 3.x,请使用 opencv
而不是 opencv4
)
效果展示
运行程序后,你将看到一个窗口,其中有一个红色的爱心在深灰色背景上平滑地放大和缩小,模拟心跳的效果。按 ESC
键可以关闭窗口并退出程序。
总结与扩展
通过这个简单的项目,我们学习了如何使用 OpenCV 的绘图功能和参数方程来创建形状,并通过周期性改变其属性来制作动画。
可以尝试的扩展:
- 颜色变化: 让爱心的颜色随心跳一起脉动。
- 更复杂的心跳: 模拟更真实的心跳曲线(例如,快速收缩,然后稍慢舒张)。
- 背景效果: 添加动态背景或粒子效果。
- 用户交互: 允许用户通过鼠标点击改变心跳速率或颜色。
- 多个爱心: 绘制多个以不同节奏或相位跳动的爱心。
希望你喜欢这个教程!