news 2025/12/24 12:04:46

用OpenCV实现烟花动画

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
用OpenCV实现烟花动画

Python中可以使用PyGame实现动画,这里抛弃PyGame,使用OpenCV(cv2)实现绘图

import random import time import cv2 import numpy as np import local import math # 自定义向量类,替代pygame.math.Vector2 class Vector2: def __init__(self, x=0.0, y=0.0): self.x = float(x) self.y = float(y) def __add__(self, other): return Vector2(self.x + other.x, self.y + other.y) def __sub__(self, other): return Vector2(self.x - other.x, self.y - other.y) def __mul__(self, scalar): return Vector2(self.x * scalar, self.y * scalar) def __truediv__(self, scalar): return Vector2(self.x / scalar, self.y / scalar) def __repr__(self): return f"Vector2({self.x:.2f}, {self.y:.2f})" def distance_to(self, other): return math.sqrt((self.x - other.x) ** 2 + (self.y - other.y) ** 2) # 定义向量和重力 vector = Vector2 gravity = vector(0, 0.3) # 模拟重力效果 DISPLAY_WIDTH = local.VIDEO_WIDTH DISPLAY_HEIGHT = local.VIDEO_HEIGHT # 窗口大小 # 定义尾迹颜色 trail_colours = [(45, 45, 45), (60, 60, 60), (75, 75, 75), (125, 125, 125), (150, 150, 150)] dynamic_offset = 1 # 动态尾迹偏移 static_offset = 5 # 静态尾迹偏移 class Firework: def __init__(self): # 初始化烟花的颜色和粒子 self.colour = tuple(random.randint(0, 255) for _ in range(3)) # 随机颜色 self.colours = [tuple(random.randint(0, 255) for _ in range(3)) for _ in range(3)] # 粒子颜色 self.firework = Particle(random.randint(0, DISPLAY_WIDTH), DISPLAY_HEIGHT, True, self.colour) # 创建烟花粒子 self.exploded = False # 标记烟花是否已爆炸 self.particles = [] # 存储爆炸后的粒子 self.min_max_particles = vector(200, 300) # 粒子数量范围 def update(self, canvas): # 更新烟花状态 if not self.exploded: self.firework.apply_force(gravity) # 应用重力 self.firework.move() # 移动烟花粒子 for tf in self.firework.trails: tf.show(canvas) # 显示尾迹 self.show(canvas) # 显示烟花 if self.firework.vel.y >= 0: # 检查烟花是否达到最高点 self.exploded = True # 标记为已爆炸 self.explode() # 执行爆炸 else: # 更新爆炸后的粒子 for particle in self.particles: particle.apply_force(vector(gravity.x + random.uniform(-0.1, 0.1), gravity.y / 2 + random.uniform(0.01, 0.08))) particle.move() # 移动粒子 for t in particle.trails: t.show(canvas) # 显示粒子的尾迹 particle.show(canvas) # 显示粒子 def explode(self): # 生成爆炸后的粒子 amount = random.randint(int(self.min_max_particles.x), int(self.min_max_particles.y)) self.particles.extend( Particle(self.firework.pos.x, self.firework.pos.y, False, self.colours) for _ in range(amount) ) def show(self, canvas): # 在画布上绘制烟花 cv2.circle(canvas, (int(self.firework.pos.x), int(self.firework.pos.y)), self.firework.size, self.colour, -1) def remove(self): # 移除已标记的粒子 self.particles = [p for p in self.particles if not p.remove] return self.exploded and not self.particles # 返回是否可以移除烟花 class Particle: def __init__(self, x, y, firework, colour): # 初始化粒子的属性 self.firework = firework self.pos = vector(x, y) # 当前粒子位置 self.origin = vector(x, y) # 粒子起始位置 self.radius = 20 # 粒子半径 self.remove = False # 标记粒子是否需要移除 self.explosion_radius = random.randint(10, 25) # 随机爆炸半径 self.life = 0 # 粒子生命周期 self.acc = vector(0, 0) # 粒子加速度 # 创建尾迹 self.trails = [Trail(i, 5 if firework else random.randint(2, 4), firework) for i in range(5)] self.prev_posx = [-10] * 10 # 存储前10帧的x坐标 self.prev_posy = [-10] * 10 # 存储前10帧的y坐标 # 根据粒子类型设置速度、大小和颜色 if self.firework: self.vel = vector(0, -random.randint(12, 16)) # 烟花粒子向上发射 self.size = 5 # 烟花粒子大小 self.colour = colour # 烟花颜色 else: # 普通粒子随机速度和大小 self.vel = vector(random.uniform(-2, 2), random.uniform(-2, 2)) # 随机初始速度 self.vel *= random.randint(10, self.explosion_radius + 5) # 根据爆炸半径调整速度 self.size = random.randint(3, 5) # 随机粒子大小 self.colour = random.choice(colour) # 从颜色列表中随机选择颜色 def apply_force(self, force): # 应用外力到粒子的加速度 self.acc += force def move(self): # 更新粒子的位置和状态 if not self.firework: self.vel *= 0.9 # 普通粒子速度衰减 self.vel += self.acc # 更新速度 self.pos += self.vel # 更新位置 self.acc *= 0 # 重置加速度 # 检查普通粒子是否超出爆炸半径 if self.life == 0 and not self.firework: distance = self.pos.distance_to(self.origin) if distance > self.explosion_radius: self.remove = True # 超出范围则标记为移除 self.decay() # 处理粒子的衰减 self.trail_update() # 更新尾迹 self.life += 1 # 增加生命周期 def show(self, canvas): # 在画布上绘制粒子 cv2.circle(canvas, (int(self.pos.x), int(self.pos.y)), self.size, self.colour, -1) def decay(self): # 根据粒子的生命周期决定是否移除 if 70 > self.life > 20: # 在20到70之间的粒子 if random.randint(0, 30) == 0: # 有小概率移除 self.remove = True elif self.life > 70: # 超过70的粒子 if random.randint(0, 5) == 0: # 有更高概率移除 self.remove = True def trail_update(self): # 更新粒子的尾迹位置 self.prev_posx.pop() # 移除最旧的x坐标 self.prev_posx.insert(0, int(self.pos.x)) # 插入当前x坐标 self.prev_posy.pop() # 移除最旧的y坐标 self.prev_posy.insert(0, int(self.pos.y)) # 插入当前y坐标 # 更新每个尾迹的位置 for n, t in enumerate(self.trails): if t.dynamic: t.get_pos(self.prev_posx[n + dynamic_offset], self.prev_posy[n + dynamic_offset]) else: t.get_pos(self.prev_posx[n + static_offset], self.prev_posy[n + static_offset]) class Trail: def __init__(self, n, size, dynamic): # 初始化尾迹的属性 self.pos_in_line = n # 尾迹在粒子尾迹中的位置索引 self.pos = vector(-10, -10) # 尾迹的初始位置,设置为无效值 self.dynamic = dynamic # 布尔值,指示尾迹是否为动态 # 根据尾迹的动态性设置颜色 self.colour = trail_colours[n] if dynamic else (255, 255, 200) # 动态尾迹使用预定义颜色,静态尾迹为淡黄色 # 根据尾迹的动态性和位置设置大小 self.size = max(size - n // 2, 0) if dynamic else max(size - 2, 0) # 动态尾迹大小随位置变化,静态尾迹大小固定 def get_pos(self, x, y): # 更新尾迹的位置 self.pos = vector(x, y) # 将尾迹位置设置为传入的坐标 def show(self, canvas): # 在画布上绘制尾迹 cv2.circle(canvas, (int(self.pos.x), int(self.pos.y)), self.size, self.colour, -1) def update_fireworks(canvas, fireworks): """更新所有烟花状态""" # 清空画布(使用深灰色背景) canvas[:] = (20, 20, 30) # 更新所有烟花 fireworks_to_remove = [] for fw in fireworks: fw.update(canvas) if fw.remove(): fireworks_to_remove.append(fw) # 移除需要删除的烟花 for fw in fireworks_to_remove: fireworks.remove(fw) return canvas def main(): """带窗口显示的主函数""" # 创建OpenCV窗口 window_name = 'Fireworks' cv2.namedWindow(window_name, cv2.WINDOW_NORMAL) # 设置为全屏 cv2.setWindowProperty(window_name, cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN) cv2.resizeWindow(window_name, DISPLAY_WIDTH, DISPLAY_HEIGHT) # 创建画布 canvas = np.zeros((DISPLAY_HEIGHT, DISPLAY_WIDTH, 3), dtype=np.uint8) # 初始化烟花 fireworks = [Firework() for _ in range(2)] # 使用time模块控制帧率 frame_time = 1.0 / 30.0 # 30 FPS running = True frame_count = 0 print("Fireworks 程序启动") print("按 ESC 或 Q 键退出") print("按 R 键重置烟花") print("按 S 键保存当前帧") last_time = time.time() while running: # 计算帧时间 current_time = time.time() elapsed = current_time - last_time # 控制帧率 if elapsed < frame_time: time.sleep(frame_time - elapsed) current_time = time.time() last_time = current_time frame_count += 1 # 清空画布 canvas[:] = (20, 20, 30) # 随机添加新烟花 if random.randint(0, 20) == 1: fireworks.append(Firework()) # 更新所有烟花 fireworks_to_remove = [] for fw in fireworks: fw.update(canvas) if fw.remove(): fireworks_to_remove.append(fw) # 移除需要删除的烟花 for fw in fireworks_to_remove: fireworks.remove(fw) # 显示图像 cv2.imshow(window_name, canvas) # 处理按键 key = cv2.waitKey(1) & 0xFF if key == ord('q') or key == 27: # Q或ESC running = False elif key == ord('r'): # 重置烟花 fireworks = [Firework() for _ in range(2)] print(f"烟花已重置 ({frame_count}帧)") elif key == ord('s'): # 保存当前帧 filename = f"fireworks_{frame_count}.png" cv2.imwrite(filename, canvas) print(f"已保存: {filename}") elif key == ord('f'): # 切换全屏 current = cv2.getWindowProperty(window_name, cv2.WND_PROP_FULLSCREEN) new_value = cv2.WINDOW_NORMAL if current == cv2.WINDOW_FULLSCREEN else cv2.WINDOW_FULLSCREEN cv2.setWindowProperty(window_name, cv2.WND_PROP_FULLSCREEN, new_value) # 每100帧显示状态 if frame_count % 100 == 0: print(f"帧: {frame_count}, 烟花数量: {len(fireworks)}") cv2.destroyAllWindows() print(f"程序退出,总共运行 {frame_count} 帧") def debug_colors(): """调试颜色显示""" # 创建测试图像 test_img = np.zeros((200, 600, 3), dtype=np.uint8) # BGR颜色测试 colors = [ ("Blue", (255, 0, 0)), # 蓝色 (B=255) ("Green", (0, 255, 0)), # 绿色 (G=255) ("Red", (0, 0, 255)), # 红色 (R=255) ("Yellow", (0, 255, 255)), # 黄色 (G=255, R=255) ("Cyan", (255, 255, 0)), # 青色 (B=255, G=255) ("Magenta", (255, 0, 255)) # 洋红 (B=255, R=255) ] for i, (name, color) in enumerate(colors): x1, x2 = i*100, (i+1)*100 cv2.rectangle(test_img, (x1, 50), (x2, 150), color, -1) cv2.putText(test_img, name, (x1+10, 180), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255,255,255), 1) cv2.imshow('Color Test (BGR format)', test_img) print("颜色测试窗口已打开,按任意键关闭") cv2.waitKey(0) cv2.destroyAllWindows() if __name__ == "__main__": # 先运行颜色测试 # debug_colors() # 选择运行模式 print("=== Fireworks 程序 ===") print("1. 带窗口显示") print("2. 颜色测试") try: choice = int(input("请选择模式 (1-2): ")) except: choice = 1 if choice == 1: main() elif choice == 2: debug_colors() else: print("无效选择,使用默认模式 (带窗口)") main()
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2025/12/24 12:04:44

揭秘Open-AutoGLM本地部署难题:3大常见错误及一键解决方案

第一章&#xff1a;Open-AutoGLM本地部署概述Open-AutoGLM 是一个基于 AutoGLM 架构的开源自动化语言模型推理框架&#xff0c;支持本地化部署与私有化模型调用。其设计目标是为开发者提供轻量、高效且可扩展的本地大模型运行环境&#xff0c;适用于数据敏感场景下的自然语言处…

作者头像 李华
网站建设 2025/12/24 12:03:40

mybatis和ibatis有什么区别?

大家好&#xff0c;我是jobleap.cn的小九。 如果你想了解 MyBatis 和 iBatis 之间的核心区别&#xff0c;同时明确这两个持久层框架的维护状态——包括哪个目前维护更活跃、哪个已经停止维护。 一、MyBatis 和 iBatis 的核心关系与区别 首先要明确核心关系&#xff1a;iBatis 是…

作者头像 李华
网站建设 2025/12/24 12:02:16

Redis定时任务

“Redis 定时任务”这个概念通常有两种层面的解读&#xff1a;内部原理&#xff1a; Redis 自身是如何管理 key 的过期时间&#xff08;TTL&#xff09;的&#xff1f;它是怎么知道并在某个时间点删除数据的&#xff1f;应用实现&#xff1a; 开发者如何利用 Redis 实现分布式的…

作者头像 李华
网站建设 2025/12/24 12:01:11

硬件学习规划

找到发表的论文或者项目复现他们

作者头像 李华
网站建设 2025/12/24 12:00:39

(Open-AutoGLM部署黄金法则)资深IT架构师20年经验浓缩6大要点

第一章&#xff1a;质谱Open-AutoGLM部署概述项目背景与核心目标 质谱Open-AutoGLM 是一个面向质谱数据分析场景的自动化大语言模型部署框架&#xff0c;旨在将自然语言处理能力深度集成至质谱数据解析流程中。该系统通过构建领域特定的知识图谱&#xff0c;并结合微调后的生成…

作者头像 李华