見出し画像

ミュージックビジュアライザーみたいな映像 - 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 日時点 ) チャンネル刷新しましたので、よければチャンネル登録を。


いいなと思ったら応援しよう!