实用指南:OpenCV C++ 心形雨动画

❤️ 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++ 创建一个简单的粒子动画。我们涵盖了对象的定义、绘制、随机生成、状态更新和动画循环。希望你能从中获得乐趣,并尝试进行自己的修改和扩展!

posted @ 2025-07-14 20:45  yjbjingcha  阅读(17)  评论(0)    收藏  举报