Klárka
by Klárka

Krátká vsuvka - předminulý týden jsme si ve škole hráli s barevnými histogramy. Úkol byl jasný, pro několik snímků z videa udělat 3D histogram barev, v prostoru RGB, přepočtený tak, aby byly hodnoty v každé ose přepočítány místo z 0-255 na 0-3.

h1

Tady jedna ilustrace z videa s oranžovou rybou, co jsem udělala už ve škole. Ale na videu s rybou se toho moc nemění.

Proč neudělat pěkné video side-by-side s původním? Proč si celý histogram nechat zastínit pozadím? Ve výsledku, na který se můžete podívat níže, je video, které jsem stáhla z youtube (kanál @PetrGanaj), ke kterému jsou “přilepené” histogramy (spočítaný je eden histogram na šest snímků původního videa, tedy čtyři za sekundu), naanimované tak, aby běžely synchronizovaně. Histogram ignoruje černou (#000000), aby odfiltroval pozadí - čistě černá se totiž v kvetoucím tulipánu objevuje naprosto minimálně. Velikosti kroužků v histogramu odpovídají poměrnému zastoupení daného “chlívečku”, do kterého barva spadne, barva je dotyčného “chlívečku” a střed kolečka je umístěn uprostřed. Počítání histogramů bylo realizováno v pythonu, pro rozdělení videa na framy a slepení původních framů se zpracovanými histogramy byl použit ffmpeg.

Výsledek můžete shlédnout zde: Video

h2

A jak se to vlastně dělá?

FFMPEG - rozdělení na framy
#!/bin/sh
rm -rf in
mkdir in
ffmpeg -i input.mp4 -ss 0:03 -t 0:26 in/%05d.png
PYTHON - zpracování histogramů
import numpy as np
import matplotlib.pyplot as plt
import math
from PIL import Image

# protože se obrázky ve velkém rozlišení počítaly pomalu, tohle je zmenší, což nám ale moc nevadíí
def resize_image(img, max_size=200):
    w, h = img.size
    scale = max_size / max(w, h)                    # kolik "procent" ? 
    new_size = (int(w * scale), int(h * scale))  
    return img.resize(new_size, Image.LANCZOS)  # Resize using high-quality filter      https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.windows.lanczos.html

# tohle vykreslí vlastní histogram
def drawHistogram3D(points, sizes, colors):
    fig = plt.figure(figsize=(5, 5))
    ax = fig.add_subplot(111, projection='3d')
    ax.scatter(points[:, 0], points[:, 1], points[:, 2], s=sizes, c=colors, alpha=1)  
    ax.set_xlabel("R")
    ax.set_ylabel("G")
    ax.set_zlabel("B")
    return fig  

q_levels = 4                # "měříkto" histogramu ("na kolik binů"?)
step = 256 / q_levels
histogramRGB = np.zeros((q_levels, q_levels, q_levels))  
j = 0

while j < 780:                                              # měnit si to na počet framů v in (na které se rozsekalo video)
    for i in range(1, 6):                                       # pro kazdy obrázek ze skpiny po 6, z těch bude histogram
        image_path = f"in/{i+j:05d}.png" 
        img = Image.open(image_path)
        img = resize_image(img, max_size=200)           # zmenšit, jinak je to pomalý
        img_array = np.array(img)
        height, width, _ = img_array.shape
                                            
        for y in range(height):                             # pro každý pixel v obrázku, na ktrý právě koukáme
            for x in range(width):
                r, g, b = img_array[y, x]
                r_batch = math.floor(r / step)
                g_batch = math.floor(g / step)
                b_batch = math.floor(b / step)
                if r_batch != 0 and g_batch != 0 and b_batch != 0:      # jen pokud ma obrázek črné pozadí, tohle ho ignoruje, lze kompenzovat jakékoli    
                    histogramRGB[r_batch, g_batch, b_batch] += 1        # pro každý px spočítá, kam spadne a tomu zvýší value

    points = []
    sizes = []
    colors = []

    for r in range(q_levels):
        for g in range(q_levels):
            for b in range(q_levels):  # přes všchny barevné kombinace
                count = histogramRGB[r, g, b]
                if count > 0:           # pokud tam ta barva je, ignoruj nezastoupené 
                    points.append((r, g, b))
                    sizes.append((count / np.max(histogramRGB)) * 4000)  # rescale, protože kfyž to není znormované, vypadá hnusně
                    colors.append('#{:02x}{:02x}{:02x}'.format(int((r + 0.5) * step), int((g + 0.5) * step), int((b + 0.5) * step)))        # zprostředka kroku, tedy velikosti krychliky

    points = np.array(points)
    
    fig = drawHistogram3D(points, sizes, colors)
    plt_filename = f"out/plt{(j//6 + 1):03d}.png"          # uloží jako plt001.png atd...
    plt.savefig(plt_filename)
    plt.close(fig)

    j = j + 6
FFMPEG - opětovné slepení a přidání histogramů
#!/bin/sh
rm -f output.mp4
ffmpeg -r 24 -i in/%05d.png -r 4 -i out/plt%03d.png -filter_complex '[1]scale=h=738:w=-1[1s];[0][1s]hstack' -c:v libx264 output.mp4

Další obrázky z mojí dílny si můžete prohlédnout v Galerii.