使用 C++/OpenCV 實時播放火柴人愛心舞蹈動畫
本文將介紹如何使用 C++/OpenCV 庫實時創建一個動畫窗口:一個火柴人捧著愛心跳舞,同時另一個愛心從遠處飛來并逐漸變大。動畫會實時在 OpenCV 窗口中播放,直到用戶按下按鍵退出。
準備工作
確保你的系統上已經安裝了 OpenCV 庫,并且配置好了 C++ 的編譯環境。
C++ 實時動畫代碼
以下代碼會創建一個窗口并直接在其中循環播放動畫。
stickman_heart_realtime.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;
}
代碼邏輯解析
-
窗口創建 (
namedWindow
): 在進入主循環之前,我們先創建一個窗口。這樣可以避免在每次循環時都重復創建和銷毀窗口,提高效率。 -
動畫主循環 (
while(true)
): 程序的核心是一個無限循環。在這個循環中,我們不斷地生成和顯示動畫的每一幀。 -
實時顯示 (
imshow
): 在循環的內部,我們用imshow()
函數將繪制好的圖像img
顯示到之前創建的窗口中。每次循環都會刷新窗口的內容,從而形成連續的動畫。 -
控制速度與響應 (
waitKey
):waitKey(delay)
是實現實時動畫的關鍵。- 控制速度: 它會使程序暫停
delay
毫秒。我們通過1000 / FPS
計算出每幀應該停留的時間,從而精確控制動畫的播放速度。 - 事件處理與響應: 在暫停期間,
waitKey
還會處理窗口的事件,比如檢測鍵盤輸入。我們將它的返回值存入key
變量。
- 控制速度: 它會使程序暫停
-
退出條件: 我們檢查
key
的值。如果用戶按下了 ‘q’ 鍵(ASCII碼)或 Esc 鍵(ASCII碼為 27),while
循環就會通過break
語句中斷,程序隨之結束。
如何編譯和運行
- 保存代碼: 將以上代碼保存為
stickman_heart_realtime.cpp
。 - 編譯 (使用g++):
(如果你的 OpenCV 不是版本4,可以嘗試g++ stickman_heart_realtime.cpp -o stickman_app `pkg-config opencv4 --cflags --libs`
pkg-config opencv --cflags --libs
) - 運行:
./stickman_app
運行效果
運行程序后,你會看到一個窗口彈出,并開始播放動畫:
- 一個白色的火柴人在深灰色的背景下左右搖擺四肢,模擬跳舞。
- 他胸前捧著一個紅色的小愛心,隨著他的舞步上下跳動。
- 一個更大的愛心從屏幕右上角緩緩飛入,它的軌跡是一條平滑的曲線,并且在飛行過程中體積不斷增大,最終停留在火柴人附近。
- 動畫會一直循環播放。
當你想關閉時,請確保窗口是激活狀態(用鼠標點擊一下窗口),然后按下鍵盤上的 ‘q’ 鍵或 Esc 鍵即可退出程序。