?? OpenCV C++ 心形雨動畫 ??
本文將引導你使用 C++ 和 OpenCV 庫創建一個可愛的心形雨動畫。在這個動畫中,心形會從屏幕頂部的隨機位置落下,模擬下雨的效果。使用opencv定制自己的專屬背景
目錄
- 簡介
- 先決條件
- 核心概念
- 實現步驟
- 創建項目
- 定義心形結構體
- 繪制心形的函數
- 主動畫循環
- 完整代碼示例
- 編譯和運行
- 效果展示
- 進一步的改進
- 總結
簡介
我們將創建一個窗口,在窗口中,不同大小和顏色的心形會從頂部隨機出現并向下飄落。這是一個有趣的小項目,可以幫助理解 OpenCV 的基本繪圖、事件處理和動畫循環。
先決條件
在開始之前,請確保你具備以下條件:
- C++ 編譯器:如 GCC (MinGW for Windows), Clang, 或 MSVC。
- OpenCV 庫:版本 3.x 或 4.x 已安裝并配置好編譯環境。
- 基本的 C++ 知識:包括變量、循環、函數、結構體/類以及 STL (如
std::vector
)。 - 基本的 OpenCV 知識:了解
cv::Mat
,cv::Point
,cv::Scalar
以及窗口和繪圖函數。
核心概念
- 心形表示:我們將通過繪制一個由特定點集定義的多邊形來創建一個心形。
- 動畫幀:動畫是通過連續顯示略有不同的圖像(幀)來創建運動的錯覺。
- 對象狀態:每個心形都有自己的位置、大小、顏色和下落速度。
- 隨機生成:心形的初始位置、大小、顏色和速度將隨機生成,以獲得更自然的效果。
- 主循環:
- 清除上一幀的內容(或創建新畫布)。
- 隨機生成新的心形。
- 更新所有現有心形的位置。
- 繪制所有心形到當前幀。
- 顯示當前幀。
- 移除超出屏幕的心形。
- 短暫延遲后重復。
實現步驟
創建項目
首先,創建一個 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 FPSif (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), // BluedistribColorComponent(gen), // GreendistribRedComponent(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++ 創建一個簡單的粒子動畫。我們涵蓋了對象的定義、繪制、隨機生成、狀態更新和動畫循環。希望你能從中獲得樂趣,并嘗試進行自己的修改和擴展!