ミュージックビジュアライザーみたいな映像 - Stable DiffusionとPythonの画像処理と自動化(8)
こちらの映像、映像は全部 Python で作っちゃいました。
蝶の画像を3次元空間で処理したり、mp3 ファイルの音声への反応をさせたり、結局ミュージックビジュアライザーそのものをつくっちゃった。なんだかんだでソースが結構長くなっちゃって、ここに掲載できるような量ではありません。すいません。
なので、ちょっと試しにソースを、みなさんよければどうぞ。
import pygame
import numpy as np
from random import randint, uniform, choice
from tqdm import tqdm
import imageio
import os
from datetime import datetime
import math
pygame.init()
WIDTH = 960
HEIGHT = 540
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Glowing Smoke Simulation")
def create_random_shape_points(size, num_points=None):
if num_points is None:
num_points = randint(6, 12)
points = []
center = (size, size)
base_radius = size * 0.8
for i in range(num_points):
angle = 2 * math.pi * i / num_points
radius = base_radius * uniform(0.8, 1.2)
x = center[0] + radius * math.cos(angle)
y = center[1] + radius * math.sin(angle)
points.append((x, y))
return points
def create_noise_texture(size):
texture = pygame.Surface((size, size), pygame.SRCALPHA)
for x in range(size):
for y in range(size):
noise_val = uniform(0.0, 1.0)
alpha = int(noise_val * 255)
texture.set_at((x, y), (255, 255, 255, alpha))
return texture
class Particle:
def __init__(self, x, y, rgb_color=(200, 200, 200), persistence=0.7, alpha_reduction=1.0,
size_ratio=1.0, lifetime_max=400, lifetime_range=200):
self.x = x
self.y = y
base_size = uniform(0.5, 3)
self.size = base_size * size_ratio
self.color = rgb_color
self.alpha = randint(150, 255)
self.glow_radius = self.size * 4 # 光の広がりの半径
self.vel_x = uniform(0.3, 1.0)
self.vel_y = uniform(-2.0, -1.0)
min_lifetime = lifetime_max - lifetime_range
self.lifetime = randint(min_lifetime, lifetime_max)
self.turbulence = uniform(-0.05, 0.05)
base_expansion_rate = uniform(0.001, 0.003)
self.expansion_rate = base_expansion_rate * size_ratio
self.alpha_reduction_rate = (1.0 - persistence) * alpha_reduction
self.shape_points = create_random_shape_points(self.size)
self.rotation = uniform(0, 360)
self.rotation_speed = uniform(-2, 2)
noise_size = int(self.glow_radius * 2)
self.noise_texture = create_noise_texture(noise_size)
def update(self):
self.turbulence += uniform(-0.01, 0.01)
self.turbulence = max(min(self.turbulence, 0.1), -0.1)
self.vel_x += uniform(-0.03, 0.08) + self.turbulence
self.vel_y += uniform(-0.05, 0.05)
self.vel_x = max(-0.3, min(self.vel_x, 2.0))
self.x += self.vel_x
self.y += self.vel_y
old_size = self.size
self.size += self.expansion_rate
size_ratio = self.size / old_size
self.shape_points = [(x * size_ratio, y * size_ratio) for x, y in self.shape_points]
self.rotation += self.rotation_speed
relative_height = max(0, HEIGHT - self.y) / HEIGHT
alpha_reduction = uniform(0.5, 1.5) * self.alpha_reduction_rate * relative_height
self.alpha = max(0, self.alpha - alpha_reduction)
self.lifetime -= 1
self.glow_radius = self.size * 4
def draw(self, screen):
if self.alpha <= 0:
return
glow_size = int(self.glow_radius * 2)
glow_surface = pygame.Surface((glow_size, glow_size), pygame.SRCALPHA)
center_alpha = int(self.alpha * 0.8)
center = (glow_size // 2, glow_size // 2)
max_radius = int(self.glow_radius)
scaled_noise = pygame.transform.scale(self.noise_texture, (glow_size, glow_size))
inner_radius = max_radius * 0.3
inner_surface = pygame.Surface((glow_size, glow_size), pygame.SRCALPHA)
for radius in range(int(inner_radius), 0, -1):
ratio = radius / inner_radius
intensity = 1 - (ratio * ratio)
base_alpha = int(center_alpha * (0.5 + 0.5 * intensity))
circle_surface = pygame.Surface((glow_size, glow_size), pygame.SRCALPHA)
pygame.draw.circle(circle_surface, (*self.color, base_alpha), center, radius)
inner_surface.blit(circle_surface, (0, 0), special_flags=pygame.BLEND_RGBA_MAX)
outer_surface = pygame.Surface((glow_size, glow_size), pygame.SRCALPHA)
for radius in range(max_radius, int(inner_radius), -1):
ratio = (radius - inner_radius) / (max_radius - inner_radius)
intensity = 1 - ratio
base_alpha = int(center_alpha * 0.3 * intensity)
circle_surface = pygame.Surface((glow_size, glow_size), pygame.SRCALPHA)
pygame.draw.circle(circle_surface, (*self.color, base_alpha), center, radius)
outer_surface.blit(circle_surface, (0, 0), special_flags=pygame.BLEND_RGBA_MAX)
inner_surface.blit(scaled_noise, (0, 0), special_flags=pygame.BLEND_RGBA_MULT)
noise_factor = 0.7
outer_surface.blit(scaled_noise, (0, 0), special_flags=pygame.BLEND_RGBA_MULT)
glow_surface.blit(outer_surface, (0, 0), special_flags=pygame.BLEND_RGBA_MAX)
glow_surface.blit(inner_surface, (0, 0), special_flags=pygame.BLEND_RGBA_MAX)
core_radius = int(inner_radius * 0.3)
core_surface = pygame.Surface((glow_size, glow_size), pygame.SRCALPHA)
core_alpha = int(min(255, center_alpha * 1.5))
pygame.draw.circle(core_surface, (*self.color, core_alpha), center, core_radius)
glow_surface.blit(core_surface, (0, 0), special_flags=pygame.BLEND_RGBA_MAX)
screen.blit(glow_surface, (int(self.x - glow_size/2), int(self.y - glow_size/2)))
class EmissionPoint:
def __init__(self, x, phase=0):
self.x = x
self.phase = phase
self.move_speed = 2
def update(self):
self.x += self.move_speed
if self.x >= WIDTH:
self.x = 0
class SmokeSimulation:
def __init__(self, width_ratio=0.95, persistence=0.7, max_particles=5000, size_ratio=1.0,
emission_rate=0.85, lifetime_max=400, lifetime_range=200,
rgb_color=(200, 200, 200)):
self.width_ratio = max(0.0, min(1.0, width_ratio))
self.persistence = max(0.0, min(1.0, persistence))
self.max_particles = max_particles
self.size_ratio = max(0.1, size_ratio)
self.emission_rate = max(0.0, min(1.0, emission_rate))
self.lifetime_max = max(100, lifetime_max)
self.lifetime_range = min(lifetime_range, self.lifetime_max - 50)
self.rgb_color = rgb_color
self.emission_width = int(WIDTH * self.width_ratio)
self.num_emission_points = max(5, min(30, int(20 * self.width_ratio)))
self.emission_points = [
EmissionPoint(x, phase=0)
for x in np.linspace(0, self.emission_width, self.num_emission_points)
]
self.particles = []
self.frame_count = 0
def emit_particles(self):
for point in self.emission_points:
if len(self.particles) < self.max_particles and uniform(0, 1) < self.emission_rate:
x_offset = uniform(-10, 10)
y_offset = uniform(-5, 5)
emission_x = point.x + x_offset
emission_y = HEIGHT + 20 + y_offset
self.particles.append(Particle(
emission_x, emission_y,
self.rgb_color,
self.persistence,
size_ratio=self.size_ratio,
lifetime_max=self.lifetime_max,
lifetime_range=self.lifetime_range
))
def update(self):
self.frame_count += 1
for point in self.emission_points:
point.update()
self.particles = [p for p in self.particles if p.lifetime > 0]
for particle in self.particles:
particle.update()
def draw(self, screen):
for particle in self.particles:
particle.draw(screen)
def capture_frame(screen):
frame_data = pygame.surfarray.array3d(screen)
return frame_data.transpose([1, 0, 2])
def apply_gaussian_blur(surface, sigma):
if sigma <= 0:
return surface
original_size = surface.get_size()
width = int(original_size[0] * 1.1)
height = int(original_size[1] * 1.1)
temp_surface = pygame.transform.smoothscale(surface, (width, height))
iterations = int(sigma)
for _ in range(max(1, iterations)):
temp_surface = pygame.transform.smoothscale(
pygame.transform.smoothscale(temp_surface,
(int(width * 1.1), int(height * 1.1))),
(width, height)
)
return pygame.transform.smoothscale(temp_surface, original_size)
def main(duration_seconds=20, fps=60, width_ratio=0.95, persistence=0.7, max_particles=5000,
size_ratio=1.0, emission_rate=0.85, lifetime_max=400, lifetime_range=200,
rgb_color=(200, 200, 200), blur_strength=0.0):
output_filename = "output_smoke.mp4"
clock = pygame.time.Clock()
simulation = SmokeSimulation(
width_ratio, persistence, max_particles, size_ratio,
emission_rate, lifetime_max, lifetime_range,
rgb_color
)
running = True
frames = []
TOTAL_FRAMES = duration_seconds * fps
if os.path.exists(output_filename):
os.remove(output_filename)
print(f"Simulation Settings:")
print(f"- Duration: {duration_seconds} seconds")
print(f"- Width Ratio: {width_ratio:.2f} ({simulation.emission_width}px)")
print(f"- Emission Points: {simulation.num_emission_points}")
print(f"- Persistence: {persistence:.2f}")
print(f"- Max Particles: {max_particles}")
print(f"- Size Ratio: {size_ratio:.2f}")
print(f"- Emission Rate: {emission_rate:.2f}")
print(f"- Lifetime Range: {lifetime_max-lifetime_range}-{lifetime_max}")
print(f"- RGB Color: {rgb_color}")
print(f"- Blur Strength: {blur_strength}")
render_surface = pygame.Surface((WIDTH, HEIGHT))
for frame in tqdm(range(TOTAL_FRAMES), desc=f"Generating {duration_seconds}s smoke simulation"):
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
break
if not running:
break
render_surface.fill((0, 0, 0))
simulation.emit_particles()
simulation.update()
simulation.draw(render_surface)
if blur_strength > 0:
render_surface = apply_gaussian_blur(render_surface, blur_strength)
screen.blit(render_surface, (0, 0))
pygame.display.flip()
frame_data = capture_frame(screen)
frames.append(frame_data)
clock.tick(fps)
pygame.quit()
print(f"\nSaving animation to {output_filename}...")
imageio.mimsave(output_filename, frames, fps=fps, quality=8)
print("Animation saved successfully!")
if __name__ == "__main__":
main(
duration_seconds=20,
fps=15,
width_ratio=1.0,
persistence=1.0,
max_particles=100000,
size_ratio=2,
emission_rate=0.25,
lifetime_max=300,
lifetime_range=150,
rgb_color=(64, 128, 255),
blur_strength=8.0
)
ながっ。
こちら、近日公開予定 ( 2024 年 11 月 3 日時点 ) チャンネル刷新しましたので、よければチャンネル登録を。