OpenCV C++ 心形雨动画

news2025/6/6 17:42:06

❤️ OpenCV C++ 心形雨动画 ❤️

本文将引导你使用 C++ 和 OpenCV 库创建一个可爱的心形雨动画。在这个动画中,心形会从屏幕顶部的随机位置落下,模拟下雨的效果。使用opencv定制自己的专属背景
在这里插入图片描述

目录

  1. 简介
  2. 先决条件
  3. 核心概念
  4. 实现步骤
    • 创建项目
    • 定义心形结构体
    • 绘制心形的函数
    • 主动画循环
    • 完整代码示例
  5. 编译和运行
  6. 效果展示
  7. 进一步的改进
  8. 总结

简介

我们将创建一个窗口,在窗口中,不同大小和颜色的心形会从顶部随机出现并向下飘落。这是一个有趣的小项目,可以帮助理解 OpenCV 的基本绘图、事件处理和动画循环。


先决条件

在开始之前,请确保你具备以下条件:

  • C++ 编译器:如 GCC (MinGW for Windows), Clang, 或 MSVC。
  • OpenCV 库:版本 3.x 或 4.x 已安装并配置好编译环境。
  • 基本的 C++ 知识:包括变量、循环、函数、结构体/类以及 STL (如 std::vector)。
  • 基本的 OpenCV 知识:了解 cv::Mat, cv::Point, cv::Scalar 以及窗口和绘图函数。

核心概念

  • 心形表示:我们将通过绘制一个由特定点集定义的多边形来创建一个心形。
  • 动画帧:动画是通过连续显示略有不同的图像(帧)来创建运动的错觉。
  • 对象状态:每个心形都有自己的位置、大小、颜色和下落速度。
  • 随机生成:心形的初始位置、大小、颜色和速度将随机生成,以获得更自然的效果。
  • 主循环
    1. 清除上一帧的内容(或创建新画布)。
    2. 随机生成新的心形。
    3. 更新所有现有心形的位置。
    4. 绘制所有心形到当前帧。
    5. 显示当前帧。
    6. 移除超出屏幕的心形。
    7. 短暂延迟后重复。

实现步骤

创建项目

首先,创建一个 C++ 项目,并确保你的构建系统(如 CMake 或直接使用 g++)能够链接到 OpenCV 库。

定义心形结构体

我们需要一个结构体来存储每个心形的信息:

#include <opencv2/opencv.hpp> // 主要的OpenCV头文件
#include <vector>             // 用于存储心形
#include <random>             // 用于生成随机数
#include <iostream>           // 用于输出

// 定义心形的属性
struct Heart {
    cv::Point position; // 心形中心点当前位置
    int size;           // 心形大小
    cv::Scalar color;   // 心形颜色 (BGR格式)
    double speed;       // 心形下落速度
};

绘制心形的函数

我们将创建一个函数,根据给定的中心点、大小和颜色来绘制一个心形。心形可以通过 cv::fillPoly 绘制一个填充的多边形来近似。

// 绘制单个心形
void drawHeart(cv::Mat& image, cv::Point center, int size, const cv::Scalar& color) {
    // 定义心形轮廓点(相对于中心点)
    // 这些点可以调整以获得你喜欢的心形形状
    std::vector<cv::Point> points;
    points.push_back(cv::Point(center.x, center.y + size / 2));                         // 底部尖端
    points.push_back(cv::Point(center.x - size / 2, center.y - size / 5));              // 左侧下方点
    points.push_back(cv::Point(center.x - size / 2, center.y - size / 2));              // 左侧中间点
    points.push_back(cv::Point(center.x - size / 4, center.y - (size * 4) / 5));        // 左侧上方点(靠近凹陷处)
    points.push_back(cv::Point(center.x, center.y - size / 2));                         // 顶部凹陷处最低点
    points.push_back(cv::Point(center.x + size / 4, center.y - (size * 4) / 5));        // 右侧上方点
    points.push_back(cv::Point(center.x + size / 2, center.y - size / 2));              // 右侧中间点
    points.push_back(cv::Point(center.x + size / 2, center.y - size / 5));              // 右侧下方点

    // 转换成OpenCV fillPoly需要的格式
    const cv::Point* pts = (const cv::Point*)cv::Mat(points).data;
    int npts = points.size();

    // 绘制填充的心形
    cv::fillPoly(image, &pts, &npts, 1, color, cv::LINE_AA);
}

主动画循环

这是程序的核心部分,负责生成、更新、绘制和显示心形。

int main() {
    int windowWidth = 800;
    int windowHeight = 600;
    std::string windowName = "❤️ Heart Rain Animation ❤️";

    // 初始化随机数生成器
    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_int_distribution<> distribX(0, windowWidth); // X坐标
    std::uniform_int_distribution<> distribSize(15, 40);      // 心形大小
    std::uniform_real_distribution<> distribSpeed(1.0, 5.0);  // 下落速度
    std::uniform_int_distribution<> distribColorVal(100, 255); // 用于粉色/红色系
    std::uniform_int_distribution<> distribSpawnChance(0, 100); // 生成新心形的概率

    std::vector<Heart> hearts;
    cv::namedWindow(windowName, cv::WINDOW_AUTOSIZE);

    while (true) {
        // 1. 创建一个黑色的画布作为当前帧
        cv::Mat frame = cv::Mat::zeros(windowHeight, windowWidth, CV_8UC3);

        // 2. 随机生成新的心形
        // 每帧有一定概率生成新的心形(例如15%的概率)
        if (distribSpawnChance(gen) < 15) { // 可以调整这个概率
            Heart newHeart;
            newHeart.position.x = distribX(gen);
            newHeart.position.y = 0; // 从顶部开始
            newHeart.size = distribSize(gen);
            newHeart.speed = distribSpeed(gen);
            // 生成粉红色系或红色的心形
            // BGR: (Blue, Green, Red)
            // 为了粉色/红色,保持蓝色通道较低,绿色适中,红色较高
            int blue = distribColorVal(gen) / 3; // 较低的蓝色值
            int green = distribColorVal(gen) / 2; // 适中的绿色值 (可以更低)
            int red = distribColorVal(gen);    // 较高的红色值
            newHeart.color = cv::Scalar(blue, green, red);
            
            hearts.push_back(newHeart);
        }

        // 3. 更新并绘制所有心形
        for (size_t i = 0; i < hearts.size(); ++i) {
            // 更新位置
            hearts[i].position.y += hearts[i].speed;
            // 绘制心形
            drawHeart(frame, hearts[i].position, hearts[i].size, hearts[i].color);
        }

        // 4. 移除超出屏幕的心形 (从后往前遍历以便安全删除)
        for (int i = hearts.size() - 1; i >= 0; --i) {
            if (hearts[i].position.y - hearts[i].size > windowHeight) { // 完全移出底部
                hearts.erase(hearts.begin() + i);
            }
        }
        /* 或者使用 remove_if 和 erase-remove idiom (更C++风格)
        hearts.erase(std::remove_if(hearts.begin(), hearts.end(),
            [&](const Heart& h){
                return h.position.y - h.size > windowHeight;
            }), hearts.end());
        */


        // 5. 显示帧
        cv::imshow(windowName, frame);

        // 6. 等待按键,30毫秒延迟,如果按下ESC则退出
        int key = cv::waitKey(30); // 约33 FPS
        if (key == 27) { // ESC 键的ASCII码
            break;
        }
    }

    cv::destroyAllWindows();
    return 0;
}

完整代码示例

将以上片段组合起来,形成一个完整的 .cpp 文件。

#include <opencv2/opencv.hpp>
#include <vector>
#include <random>
#include <iostream> // 如果需要调试输出

// 定义心形的属性
struct Heart {
    cv::Point position;
    int size;
    cv::Scalar color;
    double speed;
};

// 绘制单个心形
void drawHeart(cv::Mat& image, cv::Point center, int size, const cv::Scalar& color) {
    std::vector<cv::Point> points;
    // 为了让心形看起来更正,调整y坐标的偏移
    int y_offset = size / 10; // 轻微向上调整心形绘制的视觉中心
    center.y -= y_offset;

    points.push_back(cv::Point(center.x, center.y + size / 2));                     
    points.push_back(cv::Point(center.x - size / 2, center.y - size / 5));          
    points.push_back(cv::Point(center.x - size / 2, center.y - size / 2));          
    points.push_back(cv::Point(center.x - size / 4, center.y - (size * 4) / 5));    
    points.push_back(cv::Point(center.x, center.y - size / 2));                     
    points.push_back(cv::Point(center.x + size / 4, center.y - (size * 4) / 5));    
    points.push_back(cv::Point(center.x + size / 2, center.y - size / 2));          
    points.push_back(cv::Point(center.x + size / 2, center.y - size / 5));          

    const cv::Point* pts = (const cv::Point*)cv::Mat(points).data;
    int npts = points.size();
    cv::fillPoly(image, &pts, &npts, 1, color, cv::LINE_AA);
}


int main() {
    int windowWidth = 800;
    int windowHeight = 600;
    std::string windowName = "❤️ Heart Rain Animation ❤️";

    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_int_distribution<> distribX(0, windowWidth);
    std::uniform_int_distribution<> distribSize(15, 40);
    std::uniform_real_distribution<> distribSpeed(1.0, 5.0);
    std::uniform_int_distribution<> distribColorComponent(0, 150); // 用于B和G通道
    std::uniform_int_distribution<> distribRedComponent(200, 255); // 用于R通道
    std::uniform_int_distribution<> distribSpawnChance(0, 100);

    std::vector<Heart> hearts;
    cv::namedWindow(windowName, cv::WINDOW_AUTOSIZE);

    while (true) {
        cv::Mat frame = cv::Mat::zeros(windowHeight, windowWidth, CV_8UC3);

        if (distribSpawnChance(gen) < 15) {
            Heart newHeart;
            newHeart.position.x = distribX(gen);
            newHeart.position.y = 0; // 从顶部开始,但视觉上可能需要调整到刚好在屏幕外开始
            newHeart.size = distribSize(gen);
            newHeart.speed = distribSpeed(gen);
            
            newHeart.color = cv::Scalar(
                distribColorComponent(gen),       // Blue
                distribColorComponent(gen),       // Green
                distribRedComponent(gen)          // Red (ensuring it's reddish/pinkish)
            );
            hearts.push_back(newHeart);
        }

        for (size_t i = 0; i < hearts.size(); ++i) {
            hearts[i].position.y += hearts[i].speed;
            drawHeart(frame, hearts[i].position, hearts[i].size, hearts[i].color);
        }

        for (int i = hearts.size() - 1; i >= 0; --i) {
            if (hearts[i].position.y - (hearts[i].size * 4) / 5 > windowHeight) { // 判断心形的"最高点"是否已过屏幕
                hearts.erase(hearts.begin() + i);
            }
        }

        cv::imshow(windowName, frame);

        int key = cv::waitKey(30);
        if (key == 27) {
            break;
        }
    }

    cv::destroyAllWindows();
    return 0;
}

编译和运行

你需要根据你的 OpenCV 安装方式来编译代码。

使用 GCC/G++ (Linux/macOS):

首先,确保你已经安装了 pkg-config 并且 OpenCV 的 .pc 文件在它的搜索路径中。

g++ your_file_name.cpp -o heart_rain $(pkg-config --cflags --libs opencv4)
# 如果你用的是OpenCV 3,可能是 opencv 而不是 opencv4
# g++ your_file_name.cpp -o heart_rain $(pkg-config --cflags --libs opencv)
./heart_rain

使用 CMake (推荐跨平台):

创建一个 CMakeLists.txt 文件:

cmake_minimum_required(VERSION 3.10)
project(HeartRain)

set(CMAKE_CXX_STANDARD 11) # 或更高
set(CMAKE_CXX_STANDARD_REQUIRED True)

find_package(OpenCV REQUIRED)

include_directories(${OpenCV_INCLUDE_DIRS})

add_executable(HeartRain your_file_name.cpp) # 替换 your_file_name.cpp
target_link_libraries(HeartRain ${OpenCV_LIBS})

然后编译:

mkdir build
cd build
cmake ..
make
./HeartRain # 或者在Windows上是 .\Debug\HeartRain.exe

效果展示

运行程序后,你会看到一个窗口,其中有各种粉红色或红色的心形从顶部随机落下,像下雨一样。

(这里可以放一张最终效果的 GIF 或截图)
(由于是文本格式,此处无法直接展示图片)


进一步的改进

这个基础版本可以有很多扩展:

  • 更精致的心形:使用贝塞尔曲线或导入 SVG 路径来绘制更平滑的心形。
  • 旋转:让心形在下落时轻微旋转。
  • 背景:添加一个漂亮的背景图片而不是纯黑色。
  • 风力效果:给心形增加水平方向的随机漂移。
  • 性能优化:对于非常大量的对象,考虑更高效的渲染或对象管理。
  • 使用Alpha透明度:如果绘制的心形有重叠,可以考虑带透明度的颜色。
  • 不同类型的心形:随机生成几种不同风格或图案的心形。

总结

通过这个小项目,我们学习了如何使用 OpenCV 和 C++ 创建一个简单的粒子动画。我们涵盖了对象的定义、绘制、随机生成、状态更新和动画循环。希望你能从中获得乐趣,并尝试进行自己的修改和扩展!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2400742.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Fullstack 面试复习笔记:Java 基础语法 / 核心特性体系化总结

Fullstack 面试复习笔记&#xff1a;Java 基础语法 / 核心特性体系化总结 上一篇笔记&#xff1a;Fullstack 面试复习笔记&#xff1a;操作系统 / 网络 / HTTP / 设计模式梳理 目前上来说&#xff0c;这个系列的笔记本质上来说&#xff0c;是对不理解的知识点进行的一个梳理&…

安卓Compose实现鱼骨加载中效果

安卓Compose实现鱼骨加载中效果 文章目录 安卓Compose实现鱼骨加载中效果背景与简介适用场景Compose骨架屏与传统View实现对比Shimmer动画原理简介常见问题与优化建议参考资料 本文首发地址 https://h89.cn/archives/404.html 背景与简介 在移动应用开发中&#xff0c;加载中占…

【使用JAVA调用deepseek】实现自能回复

在Spring Boot系统中接入DeepSeek服务&#xff0c;并将其提供给用户使用&#xff0c;通常需要以下步骤&#xff1a; 一、准备工作 &#xff08;1&#xff09;注册DeepSeek开发者账号 访问DeepSeek官网&#xff0c;注册并创建应用&#xff0c;获取API Key。 API文档&#xff1…

【Linux系列】rsync命令详解与实践

博客目录 高效文件同步的艺术&#xff1a;rsync 命令详解与实践rsync 命令解析rsync 的核心优势1. 增量传输&#xff1a;效率的革命2. 归档模式(-a)&#xff1a;保留文件所有属性3. 人性化输出(-h)与进度显示(--progress) 实际应用场景1. 文件备份与版本管理2. 跨设备同步3. 大…

Windows系统工具:WinToolsPlus 之 SQL Server Suspect/质疑/置疑/可疑/单用户等 修复

数据库在数据库列表状态是 Suspect/质疑/置疑/可疑/单用户等 非正常状态时&#xff0c; 使用WinToolsPlus 数据库页签 先设置 数据源 &#xff0c; 选择 需要清理日志的数据库&#xff0c; 点击 Suspect/质疑/置疑/可疑/单用户 按钮即可进修复。 修复过程会有数据库服务停止和启…

C++——智能指针 unique_ptr

unique_ptr的实现原理&#xff1a;简单粗暴的防拷贝 目录 一、使用C11中的新用法unique_ptr 二、使用c11模拟实现 三、使用c98特性实现 四、模拟实现unique_ptr 五、发现问题 一、使用C11中的新用法unique_ptr 由于限制了拷贝以及赋值 导致缺陷&#xff1a;unique_ptr管理…

【Python训练营打卡】day43 @浙大疏锦行

DAY 43 复习日 作业&#xff1a; kaggle找到一个图像数据集&#xff0c;用cnn网络进行训练并且用grad-cam做可视化 进阶&#xff1a;并拆分成多个文件 我选择的是music_instruments 链接&#xff1a;Musical Instruments (kaggle.com) #导包 import torch import torch.nn as…

1-【源码剖析】kafka核心概念

从今天开始开始在csdn上记录学习的笔记&#xff0c;主要包括以下几个方面&#xff1a; kafkaflinkdoris 本系列笔记主要记录Kafka学习相关的内容。在进行kafka源码学习之前&#xff0c;先介绍一下Kafka的核心概念。 消息 消息是kafka中最基本的数据单元&#xff0c;由key和…

思科设备网络实验

一、 总体拓扑图 图 1 总体拓扑图 二、 IP地址规划 表格 1 接口地址规划 设备名称 接口/VLAN IP 功能 PC0 VLAN580 10.80.1.1 访问外网 PC1 VLAN581 10.80.2.1 访问外网 PC2 Fa0 20.80.1.100 端口镜像监控流量 PC3 VLAN585 10.80.6.1 远程登陆多层交换机0…

AWS之数据分析

目录 数据分析产品对比 1. Amazon Athena 3. AWS Lake Formation 4. AWS Glue 5. Amazon OpenSearch Service 6. Amazon Kinesis Data Analytics 7. Amazon Redshift 8.Amazon Redshift Spectrum 搜索服务对比 核心功能与定位对比 适用场景 关键差异总结 注意事项 …

C# Onnx 动漫人物头部检测

目录 效果 模型信息 项目 代码 下载 参考 效果 模型信息 Model Properties ------------------------- date&#xff1a;2024-10-19T12:32:20.920471 description&#xff1a;Ultralytics best model trained on /root/datasets/yolo/anime_head_detection/data.yaml au…

【Ragflow】24.Ragflow-plus开发日志:增加分词逻辑,修复关键词检索失效问题

概述 在RagflowPlus v0.3.0 版本推出之后&#xff0c;反馈比较多的问题是&#xff1a;检索时&#xff0c;召回块显著变少了。 如上图所示&#xff0c;进行检索测试时&#xff0c;关键词相似度得分为0&#xff0c;导致混合相似度(加权相加得到)也被大幅拉低&#xff0c;低于设定…

Zookeeper 集群部署与故障转移

Zookeeper 介绍 Zookeeper 是一个开源的分布式协调服务&#xff0c;由Apache基金会维护&#xff0c;专为分布式应用提供高可用、强一致性的核心基础能力。它通过简单的树形命名空间&#xff08;称为ZNode树&#xff09;存储数据节点&#xff08;ZNode&#xff09;&#xff0c;…

Redis最佳实践——电商应用的性能监控与告警体系设计详解

Redis 在电商应用的性能监控与告警体系设计 一、原子级监控指标深度拆解 1. 内存维度监控 核心指标&#xff1a; # 实时内存组成分析&#xff08;单位字节&#xff09; used_memory: 物理内存总量 used_memory_dataset: 数据集占用量 used_memory_overhead: 管理开销内存 us…

区域徘徊检测算法AI智能分析网关V4助力公共场所/工厂等多场景安全升级

一、项目背景 随着数字化安全管理需求激增&#xff0c;重点场所急需强化人员异常行为监测。区域徘徊作为潜在安全威胁的早期征兆&#xff0c;例如校园围墙外的陌生逗留者&#xff0c;都可能引发安全隐患。传统人工监控模式效率低、易疏漏&#xff0c;AI智能分析网关V4的区域徘…

修复与升级suse linux

suse linux enterprise desktop 10提示&#xff1a;xxx service failed when loaded shared lib . error ibgobject.so.2.0:no such file or directory. suse linux enterprise server 12.iso 通过第一启动项引导&#xff0c;按照如下方式直接升级解决。

电力高空作业安全检测(2)数据集构建

数据集构建的重要性 在电力高空作业安全检测领域&#xff0c;利用 计算机视觉技术 进行安全监测需要大量的图像数据&#xff0c;这些数据需要准确标注不同的安全设备与作业人员行为。只有构建出包含真实场景的高质量数据集&#xff0c;才能通过深度学习等算法对高空作业中的潜…

嵌入式开发之STM32学习笔记day18

STM32F103C8T6 SPI通信读写W25Q64 1 W25Q64简介 W25Qxx系列是一种低成本、小型化且易于使用的非易失性存储器&#xff08;NOR Flash&#xff09;&#xff0c;它广泛应用于需要持久化存储数据的各种场景&#xff0c;如数据存储、字库存储以及固件程序存储等。该系列存储器采用…

[论文阅读]PPT: Backdoor Attacks on Pre-trained Models via Poisoned Prompt Tuning

PPT: Backdoor Attacks on Pre-trained Models via Poisoned Prompt Tuning PPT: Backdoor Attacks on Pre-trained Models via Poisoned Prompt Tuning | IJCAI IJCAI-22 发表于2022年的论文&#xff0c;当时大家还都在做小模型NLP的相关工作&#xff08;BERT&#xff0c;Ro…

汽车安全:功能安全FuSa、预期功能安全SOTIF与网络安全Cybersecurity 解析

汽车安全的三重防线&#xff1a;深入解析FuSa、SOTIF与网络安全技术 现代汽车已成为装有数千个传感器的移动计算机&#xff0c;安全挑战比传统车辆复杂百倍。 随着汽车智能化、网联化飞速发展&#xff0c;汽车电子电气架构已从简单的分布式控制系统演变为复杂的移动计算平台。现…