使用 C/C++的OpenCV 实时播放火柴人爱心舞蹈动画

使用 C++/OpenCV 实时播放火柴人爱心舞蹈动画

本文将介绍如何使用 C++/OpenCV 库实时创建一个动画窗口:一个火柴人捧着爱心跳舞,同时另一个爱心从远处飞来并逐渐变大。动画会实时在 OpenCV 窗口中播放,直到用户按下按键退出。

准备工作

确保你的系统上已经安装了 OpenCV 库,并且配置好了 C++ 的编译环境。

C++ 实时动画代码

以下代码会创建一个窗口并直接在其中循环播放动画。

stickman_heart_realtime.cpp:

cpp 复制代码
#include <iostream>
#include <vector>
#include <cmath>
#include <string>
#include <opencv2/opencv.hpp>
#ifndef M_PI
const double M_PI = 3.14159265358979323846;
#endif
using namespace cv;
using namespace std;

// --- 动画参数 ---
const int WIDTH = 800;
const int HEIGHT = 600;
const int FPS = 30; // 动画帧率 (Frames Per Second)
const Scalar BG_COLOR = Scalar(28, 28, 28); // 深灰色背景
const Scalar STICKMAN_COLOR = Scalar(255, 255, 255); // 白色火柴人
const Scalar HEART_COLOR = Scalar(70, 70, 255); // 红色爱心 (BGR)
const int STICKMAN_SCALE = 40;
const Point STICKMAN_POS(WIDTH / 3, HEIGHT - 2 * STICKMAN_SCALE);

/**
 * @brief 绘制火柴人
 * @param img 要绘制的图像
 * @param pos 火柴人的基准位置 (脚底中心)
 * @param scale 控制火柴人大小
 * @param angle 身体的摆动角度
 */
void drawStickman(Mat& img, Point pos, int scale, double angle = 0.0) {
    Point head_center = pos - Point(0, scale * 2);
    circle(img, head_center, scale / 2, STICKMAN_COLOR, 3, LINE_AA);

    Point body_top = head_center + Point(0, scale / 2);
    Point body_bottom = body_top + Point(0, scale);
    line(img, body_top, body_bottom, STICKMAN_COLOR, 3, LINE_AA);

    // 摇摆的手臂
    Point arm_joint = body_top + Point(0, scale / 10);
    Point arm_left = arm_joint + Point(-scale * 0.7 * cos(angle), scale * 0.7 * sin(angle));
    Point arm_right = arm_joint + Point(scale * 0.7 * cos(angle), scale * 0.7 * sin(angle));
    line(img, arm_joint, arm_left, STICKMAN_COLOR, 3, LINE_AA);
    line(img, arm_joint, arm_right, STICKMAN_COLOR, 3, LINE_AA);

    // 摇摆的腿
    Point leg_left = body_bottom + Point(-scale * 0.8* 1, scale * 0.8 * 1);
    Point leg_right = body_bottom + Point(scale * 0.8 *1, scale * 0.8 * 1);
    line(img, body_bottom, leg_left, STICKMAN_COLOR, 3, LINE_AA);
    line(img, body_bottom, leg_right, STICKMAN_COLOR, 3, LINE_AA);
}

/**
 * @brief 绘制实心爱心
 * @param img 要绘制的图像
 * @param center 爱心中心点
 * @param radius 爱心大小
 */
void drawHeart(Mat& img, Point center, int radius) {
    vector<Point> heart_points;
    for (double t = 0; t <= 2 * M_PI; t += 0.01) {
        double x = center.x + radius * 1.2 * sin(t) * sin(t) * sin(t);
        double y = center.y - radius * (1.1 * cos(t) - 0.4 * cos(2 * t) - 0.2 * cos(3 * t) - 0.1 * cos(4 * t));
        heart_points.push_back(Point(x, y));
    }
    fillPoly(img, vector<vector<Point>>{heart_points}, HEART_COLOR, LINE_AA);
}


int main() {
    // 在循环外创建窗口
    string winName = "Stickman Heart Dance";
    namedWindow(winName, WINDOW_AUTOSIZE);

    int frame = 0;
    // 动画主循环
    while (true) {
        // 1. 创建每一帧的画布
        Mat img(HEIGHT, WIDTH, CV_8UC3, BG_COLOR);

        // 2. 计算动画参数
        // 火柴人跳舞动画 (身体和四肢摇摆)
        double dance_angle = sin(2 * M_PI * frame / (FPS * 2.0)) * 0.8; // 摇摆角度
        drawStickman(img, STICKMAN_POS, STICKMAN_SCALE, dance_angle);

        // 火柴人手中捧着的爱心,随着身体跳动
        double bounce = sin(2 * M_PI * frame / (FPS * 1.0)) * 5;
        Point holding_heart_offset = Point(0, -STICKMAN_SCALE * 2.5 + bounce);
        drawHeart(img, STICKMAN_POS + holding_heart_offset, STICKMAN_SCALE / 3);

        // 飞过来的爱心动画
        int animation_duration = FPS * 5; // 动画总时长为5秒
        if (frame < animation_duration) {
            double progress = static_cast<double>(frame) / animation_duration;
            // 初始大小和最终大小
            double start_radius = STICKMAN_SCALE * 0.1;
            double end_radius = STICKMAN_SCALE * 2.5;
            // 初始位置和最终位置
            Point start_pos(WIDTH * 0.9, HEIGHT * 0.2);
            Point end_pos = STICKMAN_POS + Point(STICKMAN_SCALE * 2, -STICKMAN_SCALE * 2.5);

            // 使用缓动函数 (ease-out) 使动画更自然
            double ease_progress = 1 - pow(1 - progress, 3);

            int current_radius = static_cast<int>(start_radius + (end_radius - start_radius) * ease_progress);
            Point current_pos = start_pos * (1.0 - ease_progress) + end_pos * ease_progress;

            drawHeart(img, current_pos, current_radius);
        }

        // 添加操作提示
        putText(img, "Press 'q' or ESC to exit", Point(10, 25), FONT_HERSHEY_SIMPLEX, 0.7, Scalar(200, 200, 200), 1, LINE_AA);

        // 3. 显示当前帧
        imshow(winName, img);

        // 4. 控制帧率并等待按键
        // 计算每帧的延迟时间 (毫秒)
        int delay = 1000 / FPS;
        int key = waitKey(delay);

        // 如果按下 'q' 键或 ESC 键,则退出循环
        if (key == 'q' || key == 27) {
            break;
        }

        frame++; // 帧计数器增加
    }

    // 关闭所有窗口
    destroyAllWindows();
    cout << "Animation finished. Window closed." << endl;

    return 0;
}

代码逻辑解析

  1. 窗口创建 (namedWindow): 在进入主循环之前,我们先创建一个窗口。这样可以避免在每次循环时都重复创建和销毁窗口,提高效率。

  2. 动画主循环 (while(true)): 程序的核心是一个无限循环。在这个循环中,我们不断地生成和显示动画的每一帧。

  3. 实时显示 (imshow) : 在循环的内部,我们用 imshow() 函数将绘制好的图像 img 显示到之前创建的窗口中。每次循环都会刷新窗口的内容,从而形成连续的动画。

  4. 控制速度与响应 (waitKey) : waitKey(delay) 是实现实时动画的关键。

    • 控制速度 : 它会使程序暂停 delay 毫秒。我们通过 1000 / FPS 计算出每帧应该停留的时间,从而精确控制动画的播放速度。
    • 事件处理与响应 : 在暂停期间,waitKey 还会处理窗口的事件,比如检测键盘输入。我们将它的返回值存入 key 变量。
  5. 退出条件 : 我们检查 key 的值。如果用户按下了 'q' 键(ASCII码)或 Esc 键(ASCII码为 27),while 循环就会通过 break 语句中断,程序随之结束。

如何编译和运行

  1. 保存代码 : 将以上代码保存为 stickman_heart_realtime.cpp

  2. 编译 (使用g++) :

    bash 复制代码
    g++ stickman_heart_realtime.cpp -o stickman_app `pkg-config opencv4 --cflags --libs`

    (如果你的 OpenCV 不是版本4,可以尝试 pkg-config opencv --cflags --libs)

  3. 运行 :

    bash 复制代码
    ./stickman_app

运行效果

运行程序后,你会看到一个窗口弹出,并开始播放动画:

  • 一个白色的火柴人在深灰色的背景下左右摇摆四肢,模拟跳舞。
  • 他胸前捧着一个红色的小爱心,随着他的舞步上下跳动。
  • 一个更大的爱心从屏幕右上角缓缓飞入,它的轨迹是一条平滑的曲线,并且在飞行过程中体积不断增大,最终停留在火柴人附近。
  • 动画会一直循环播放。

当你想关闭时,请确保窗口是激活状态(用鼠标点击一下窗口),然后按下键盘上的 'q' 键或 Esc 键即可退出程序。

相关推荐
虾球xz3 分钟前
CppCon 2015 学习:3D Face Tracking and Reconstruction using Modern C++
开发语言·c++·学习·3d
sponge'17 分钟前
opencv学习笔记2:卷积、均值滤波、中值滤波
笔记·python·opencv·学习
small_wh1te_coder1 小时前
c语言超详细知识点总结 1500行手写源码 持续更新中ing 从25年5月到6月5日
c++·c
weixin_461259411 小时前
[C]C语言日志系统宏技巧解析
java·服务器·c语言
SteveDraw3 小时前
C++动态链接库封装,供C#/C++ 等编程语言使用——C++动态链接库概述(总)
开发语言·c++·c#·封装·动态链接库
十五年专注C++开发3 小时前
设计模式之单例模式(二): 心得体会
开发语言·c++·单例模式·设计模式
?!7143 小时前
算法打卡第18天
c++·算法
zh_xuan3 小时前
c++ std::pair
开发语言·c++
CodeWithMe4 小时前
【C/C++】EBO空基类优化介绍
开发语言·c++
k要开心4 小时前
从C到C++语法过度1
开发语言·c++