Programová tvorba a nelineární editace videa s využitím knihovny MoviePy

24. 4. 2018
Doba čtení: 29 minut

Sdílet

Ilustrační obrázek
Autor: Depositphotos – stori
Ilustrační obrázek
Dnes si popíšeme možnosti knihovny MoviePy, která umožňuje programovou tvorbu videa a třeba i animovaných gifů. Kromě toho ji lze použít i pro nelineární editaci videa: aplikaci efektů, práci s titulky a podobně.

Obsah

1. Programová tvorba a nelineární editace videa s využitím knihovny MoviePy

2. Instalace knihovny MoviePy

3. Doinstalování dalších knihoven z interaktivní smyčky Pythonu

4. První příklad – vytvoření videa obsahujícího sérii tmavých snímků

5. Vytvoření animovaného GIFu se stejným obsahem

6. Editace videa na úrovni jednotlivých snímků a pixelů

7. Konstruktory datové struktury ndarray

8. Přístup k prvkům polí, využití takzvaných řezů polí

9. Základní operace s datovou strukturou ndarray

10. Druhý příklad – pohybující se úsečka v desetisekundovém videu

11. Výsledky druhého příkladu

12. Třetí příklad – vykreslení všech 16 milionů barev v desetisekundovém videu

13. Výsledky třetího příkladu

14. Parametry předávané videokodeku

15. Čtvrtý příklad – animace „průletu“ Mandelbrotovou množinou

16. Přepočet parametrů obrázku v průběhu animace a výpočet snímku Mandelbrotovy množiny

17. Pomocný modul s barvovou paletou

18. Výsledek vytvořený čtvrtým příkladem

19. Repositář s demonstračními příklady

20. Odkazy na Internetu

1. Programová tvorba a nelineární editace videa s využitím knihovny MoviePy

V některých případech se dostaneme do situace, kdy je nutné například při prezentaci projektu či při vizualizaci dat vytvořit soubory s videem, a to programově. Například se může jednat o animovaný průběh nějaké vícerozměrné veličiny, výstup ze simulace apod. Dnes již existuje poměrně velké množství nástrojů, které lze pro tuto činnost použít; ovšem vývojáři používající programovací jazyk Python pravděpodobně v prvé řadě použijí knihovnu nazvanou jednoduše a přímočaře MoviePy. Dnes si ukážeme jen základní možnosti této knihovny, ovšem příště se budeme zabývat i pokročilejšími tématy, například propojením MovipePy s knihovnou Matplotlib, tvorbou video efektů atd.

2. Instalace knihovny MoviePy

Knihovnu MoviePy si nejdříve nainstalujeme, a to klasicky s využitím nástroje pip3 (nebo pip), protože tato knihovna je samozřejmě registrována i na PyPI (Python Package Index). Pro jednoduchost provedeme instalaci jen pro právě aktivního uživatele:

$ pip3 install --user moviepy

Samotná instalace může být provedena za relativně krátkou dobu (samozřejmě v závislosti na rychlosti připojení k Internetu):

Collecting moviepy
  Downloading https://files.pythonhosted.org/packages/ee/88/1b57f7318b3079b41a5b4299ae99caedcd00b5f9897dd765c2e553b694c3/moviepy-0.2.3.3.tar.gz (397kB)
    100% |████████████████████████████████| 399kB 1.2MB/s
Requirement already satisfied: decorator<5.0,>=4.0.2 in /usr/lib/python3.6/site-packages (from moviepy)
Collecting imageio<3.0,>=2.1.2 (from moviepy)
  Downloading https://files.pythonhosted.org/packages/a7/1d/33c8686072148b3b0fcc12a2e0857dd8316b8ae20a0fa66c8d6a6d01c05c/imageio-2.3.0-py2.py3-none-any.whl (3.3MB)
    100% |████████████████████████████████| 3.3MB 455kB/s
Collecting tqdm<5.0,>=4.11.2 (from moviepy)
  Downloading https://files.pythonhosted.org/packages/78/bc/de067ab2d700b91717dc5459d86a1877e2df31abfb90ab01a5a5a5ce30b4/tqdm-4.23.0-py2.py3-none-any.whl (42kB)
    100% |████████████████████████████████| 51kB 9.9MB/s
Collecting numpy (from moviepy)
  Downloading https://files.pythonhosted.org/packages/6e/dc/92c0f670e7b986829fc92c4c0208edb9d72908149da38ecda50d816ea057/numpy-1.14.2-cp36-cp36m-manylinux1_x86_64.whl (12.2MB)
    100% |████████████████████████████████| 12.2MB 137kB/s
Requirement already satisfied: pillow in /usr/lib64/python3.6/site-packages (from imageio<3.0,>=2.1.2->moviepy)
Requirement already satisfied: olefile in /usr/lib/python3.6/site-packages (from pillow->imageio<3.0,>=2.1.2->moviepy)
Installing collected packages: numpy, imageio, tqdm, moviepy
  Running setup.py install for moviepy ... done
Successfully installed imageio-2.3.0 moviepy-0.2.3.3 numpy-1.14.2 tqdm-4.23.0
You are using pip version 9.0.1, however version 10.0.1 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.
Poznámka: takto krátká bude instalace pouze v tom případě, kdy již máte ve svém systému nainstalovanou knihovnu Numpy. Ta je automaticky nainstalována například společně s Matplotlib a dalšími užitečnými nástroji. Pokud ovšem Numpy ještě ve svém systému nemáte, bude průběh instalace poněkud delší a také komplikovanější, protože Numpy obsahuje i nativní části, které je zapotřebí přeložit (pokud se ovšem Numpy neinstaluje přes standardního správce balíčků typu apt či dnf, příslušný balíček se většinou jmenuje python-numpy nebo python3-numpy).

3. Doinstalování dalších knihoven z interaktivní smyčky Pythonu

Ve druhém kroku provedeme doinstalování dalších požadovaných knihoven. Použijeme přitom samotný interpret Pythonu, tj. interaktivní smyčku REPL. Nejdříve interpret spustíme:

$ python3
Python 3.6.3 (default, Oct  9 2017, 12:11:29)
[GCC 7.2.1 20170915 (Red Hat 7.2.1-2)] on linux
Type "help", "copyright", "credits" or "license" for more information.

Následně spustíme jediný příkaz, který zajistí import třídy VideoClip:

>>> from moviepy.editor import VideoClip

Pokud v této chvíli nejsou některé potřebné moduly a kodeky nainstalovány, spustí se automatická (lokální) instalace, která může vypadat takto:

Imageio: 'ffmpeg-linux64-v3.3.1' was not found on your computer; downloading it now.
Try 1. Download from https://github.com/imageio/imageio-binaries/raw/master/ffmpeg/ffmpeg-linux64-v3.3.1 (43.8 MB)
Downloading: 45929032/45929032 bytes (100.0%)
  Done
File saved as /home/tester/.imageio/ffmpeg/ffmpeg-linux64-v3.3.1.
Poznámka: pokud se po spuštění příkazu import zdánlivě neprovede žádná operace, znamená to, že všechny knihovny jsou již úspěšně doinstalovány a další demonstrační příklady by měly být funkční.

4. První příklad – vytvoření videa obsahujícího sérii tmavých snímků

Tvorba videa sice vyžaduje alespoň základní znalost datové struktury ndarray nabízené knihovnou Numpy, ovšem již nyní si můžeme ukázat strukturu jednoduchého skriptu, kterým se nový soubor s videem vytvoří. Nejdříve si ukažme, jak vypadá celý zdrojový kód; v dalším textu si podrobněji popíšeme význam jednotlivých řádků:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
from moviepy.editor import VideoClip
import numpy
 
WIDTH = 320
HEIGHT = 240
 
 
def make_frame(t):
    print("time: {t}".format(t=t))
    # vyplneni trojrozmerneho pole nulami
    frame = numpy.zeros((HEIGHT, WIDTH, 3))
    return frame
 
 
# vytvoreni video klipu
animation = VideoClip(make_frame, duration=10)
 
# export videa do formatu MPEG-4
animation.write_videofile("dark_scene.mp4", fps=24)
 
# export videa do formatu Ogg Video File
animation.write_videofile("dark_scene.ogv", fps=24)

Na začátku skriptu pouze provedeme import třídy VideoClip nabízené knihovnou MoviePy a taktéž import základních funkcí nabízených knihovnou Numpy:

from moviepy.editor import VideoClip
import numpy

Další dva řádky obsahují deklaraci konstant s rozlišením výsledného videa:

WIDTH = 320
HEIGHT = 240

Následuje velmi důležitá část skriptu. Jedná se o callback funkci volanou při vytváření jednotlivých video snímků (frame). Funkci je předán čas snímku a výsledkem by měla být datová struktura typu ndarray; konkrétně trojrozměrné pole o rozměrech výškaךířka×3, kde výška a šířka odpovídá rozlišení jednotlivých snímků a poslední dimenze je rovna třem z toho důvodu, že každý pixel je reprezentován trojicí barvových složek Red, Green, Blue. Náš první video klip bude sestávat pouze ze sady tmavých snímků, takže potřebné pole reprezentující snímek vytvoříme konstruktorem numpy.zeros, kterému předáme n-tici s tvarem pole (pozor – jde o n-tici, takže závorky jsou zdvojeny):

def make_frame(t):
    print("time: {t}".format(t=t))
    # vyplneni trojrozmerneho pole nulami
    frame = numpy.zeros((HEIGHT, WIDTH, 3))
    return frame

Dále vytvoříme objekt představující budoucí video klip. Konstruktoru předáme jak referenci na callback funkci, tak i délku klipu uvedenou v sekundách:

# vytvoreni video klipu
animation = VideoClip(make_frame, duration=10)

Další dva řádky spustí generování videa s jeho postupným ukládáním na disk s využitím zvoleného kodeku. Typicky se dnes používá formát (kontejner) MPEG-4 s H.264 popř. kontejner Ogg s kodekem Theora. Můžeme si samozřejmě vyzkoušet obě možnosti. Povšimněte si, že se předává i požadovaný počet snímků za sekundu, který společně s výše specifikovanou délkou videa v sekundách určuje přibližný celkový počet snímků (přibližný proto, že fps nemusí být všemi kodeky dodrženo zcela přesně a taktéž kodeky vyžadují určité řazení mezisnímků):

# export videa do formatu MPEG-4
animation.write_videofile("dark_scene.mp4", fps=24)
 
# export videa do formatu Ogg Video File
animation.write_videofile("dark_scene.ogv", fps=24)

Co se vlastně stane při zavolání metody write_videofile?

  1. Postupně je volána callback funkce make_frame s předáním časového razítka
  2. Struktura ndarray vrácená touto funkcí je transformována a předána do video kodeku
  3. Ten postupně vytváří výsledné video – v paměti si přitom drží jen několik posledních snímků (kvůli predikci pohybu atd., to nás však v tuto chvíli nemusí trápit)

Výsledná videa jsou dosti nudná, nicméně by měla jít přehrát:

https://tisnik.github.io/moviepy-videos/video1.htm

Poznámka: všechna videa je nutné zobrazit z jiné stránky, protože Rootovský redakční systém nepodporuje tag <video>.

5. Vytvoření animovaného GIFu se stejným obsahem

Ve skutečnosti knihovna MoviePy dokáže vytvořit animaci i v dalších formátech, nejenom s využitím video kodeků. Jednou z možností je série statických snímků a taktéž – což je asi praktičtější – vytvoření animovaného GIFu. Pro některé účely se může jednat o ideální výstupní formát, ovšem musíme znát i některá jeho omezení.

Obrázek 1: GIF s deseti rámci zobrazovanými jako animace (kromě toho je v souboru přítomen i rozšiřující textový blok s poznámkou a programu, který GIF vytvořil; všimněte si potenciálně citlivých informací).

Ke stále trvající popularitě grafického formátu GIF nepochybně patří i jeho schopnost zaznamenat jednoduché animace. Pokud se podíváme do specifikace GIFu 89a, zjistíme, že se do rozšiřujícího grafického řídicího bloku (GCE – Graphic Control Extension) mohou zapsat i informace o délce prodlevy před zobrazením dalšího rámce. Tato délka je zadávána v setinách sekundy, a vzhledem k tomu, že je údaj zapsaný ve dvou bytech, je minimální prodleva rovna 1/100 s a maximální prodleva 655,36 s, což je zhruba 11 minut.

Ovšem samotná prodleva mezi zobrazením jednotlivých rámců postačuje pouze pro tvorbu animace, která proběhne jednou a poté se již neopakuje – ve specifikacích se nic o smyčce v animaci nemluví. Zde však přichází na řadu další užitečná vlastnost GIFů, která spočívá v možnosti zápisu přidaných informací od „třetích stran“. Prohlížeče buď těmto přidaným informacím rozumí (a zpracují je), nebo je ignorují.

To je ve formátu GIF zajištěno tím způsobem, že každá přidaná informace obsahuje hlavičku s její identifikací a délkou. V případě animací se jedná o rozšíření zavedené firmou Netscape pro její (kdysi) slavný webový prohlížeč Netscape Navigator. V tomto rozšíření je vlastně pouze specifikováno, kolikrát se má animace opakovat a popř. zda se má opakovat neustále (v prohlížečích bylo většinou možné animaci GIFů zastavit klávesou Esc).

Pokud budete chtít vytvořit nikoli video klip, ale animaci uloženou do formátu GIF, změní se kód skriptu následovně:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
from moviepy.editor import VideoClip
import numpy
 
WIDTH = 320
HEIGHT = 240
 
 
def make_frame(t):
    print("time: {t}".format(t=t))
    # vyplneni trojrozmerneho pole nulami
    frame = numpy.zeros((HEIGHT, WIDTH, 3))
    return frame
 
 
# vytvoreni video klipu
animation = VideoClip(make_frame, duration=10)
 
# export videa do formatu GIF
animation.write_gif("dark_scene.gif", fps=24)
Poznámka: povšimněte si, že se nezměnilo pouze jméno výstupního souboru (resp. jeho koncovka), ale i jméno metody – write_gif namísto write_videofile.

6. Editace videa na úrovni jednotlivých snímků a pixelů

V prvním příkladu jsme každý snímek vytvořili s využitím konstruktoru numpy.zeros. Jaký objekt se však tímto konstruktorem vytvořil? Jedná se o trojrozměrné pole typu ndarray, které tvoří základní datovou strukturu známé knihovny Numpy. Tato datová struktura je v mnoha ohledech dosti zajímavá, takže se s ní seznámíme podrobněji. Je to nutné, protože právě manipulace s trojrozměrnými poli nám umožňuje editaci videa na té nejnižší rozumné úrovni abstrakce – jednotlivých pixelech reprezentovaných trojicemi hodnot R, G a B.

Poznámka: následující text a současně i text v kapitolách 7, 8 a 9 ve stručnosti shrnuje základní operace s datovou strukturou ndarray. Dnes nám budou postačovat jen tři operace – vytvoření trojrozměrného pole s nulovými prvky, zápis nové hodnoty do vybraného prvku pole a použití řezu polem. Další operace oceníme až ve druhém pokračování článku (takže v případě netrpělivosti klidně přeskočte až na desátou kapitolu).

Funkce a objekty nabízené knihovnou Numpy se sice volají přímo z Pythonu, ve skutečnosti se však interní datové struktury dosti podstatným způsobem odlišují od datových struktur využívaných samotným Pythonem. V knihovně Numpy tvoří základ datová struktura nazvaná ndarray, která reprezentuje pole o prakticky libovolném počtu dimenzí (ostatně „nd“ ve jménu „ndarray“ značí N-dimensional). Tato pole se liší od běžných seznamů či n-tic v Pythonu, protože ndarray jsou homogenní datovou strukturou: všechny prvky totiž mají shodný typ a navíc všechny prvky leží za sebou, zatímco seznamy v Pythonu jsou měnitelné (prvky lze přidávat a odebírat) a obecně nehomogenní (každý prvek může mít odlišný datový typ). Za tuto velkou flexibilitu se samozřejmě platí, a to jak většími nároky na operační paměť (reference na objekty), tak i pomalejším zpracováním.

Při vytváření polí typu ndarray, ať již se jedná o vektory, matice či o pole s větším množstvím dimenzí, lze specifikovat datový typ všech prvků a dokonce i uspořádání prvků v paměti (buď podle zvyklostí jazyka Fortran nebo jazyka C). Podívejme se nyní na tabulku, v níž jsou vypsány možné typy prvků polí ndarray:

# Typ Formát Rozsah Jednoznakový kód
1 bool uloženo po bajtech True/False ‚?‘
         
2 int8 celočíselný se znaménkem –128..127 ‚b‘
3 int16 celočíselný se znaménkem –32768..32767 ‚h‘
4 int32 celočíselný se znaménkem –2147483648..2147483647 ‚i‘
5 int64 celočíselný se znaménkem –9223372036854775808..9223372036854775807 ‚l‘
         
6 uint8 celočíselný bez znaménka 0..255 ‚B‘
7 uint16 celočíselný bez znaménka 0..65535 ‚H‘
8 uint32 celočíselný bez znaménka 0..4294967295 ‚I‘
9 uint64 celočíselný bez znaménka 0..18446744073709551615 ‚L‘
         
10 float16 plovoucí řádová čárka poloviční přesnost (half) ‚e‘
11 float32 plovoucí řádová čárka jednoduchá přesnost (single) ‚f‘
12 float64 plovoucí řádová čárka dvojitá přesnost (double) ‚d‘
         
13 complex64 komplexní číslo (dvojice) 2×float32 ‚F‘
14 complex128 komplexní číslo (dvojice) 2×float64 ‚D‘
Poznámka: jednoznakové kódy je možné použít namísto jména typu, viz též funkce popsané v navazujících kapitolách.

7. Konstruktory datové struktury ndarray

Pole typu ndarray je možné vytvořit několika různými způsoby. Základní funkcí sloužící k převodu různých typů Pythonovských objektů na pole je funkce nazvaná jednoduše numpy.array. Této funkci se předá objekt (což je typicky seznam, n-tice či objekt typu range) a popř. i další nepovinné parametry, které určují typ prvků nově vzniklého pole (dtype), zda se má provést kopie prvků (copy, většinou ano) či způsob uspořádání prvků v poli (order). Hlavička této funkce tedy vypadá následovně:

array(object, dtype=None, copy=True, order=None, subok=False, ndmin=0)

Nepovinný parametr order může nabývat hodnot:

Order Význam
‚C‘ prvky jsou uspořádány stejně jako v jazyku C
‚F‘ prvky jsou uspořádány stejně jako v jazyku Fortran
‚A‘ ponecháme na implementaci, který způsob uspořádání zvolit

Jaký je tedy vlastně rozdíl mezi uspořádáním prvků podle ‚C‘ a ‚F‘? Předpokládejme klasickou matici o rozměrech 3×3 prvky:

| 1 2 3 |
| 4 5 6 |
| 7 8 9 |

Tato matice může být v operační paměti uložena následujícím způsobem:

1 2 3 4 5 6 7 8 9 - 'C'

Alternativně je však možné prohodit řádky a sloupce (což více odpovídá matematickému zápisu matice):

1 4 7 2 5 8 3 6 9 - 'F'

Podívejme se nyní na několik praktických použití funkce numpy.array. Všechny příkazy je možné zadat do interaktivní konzole Pythonu, použít IPython, Jupyter atd.:

# vytvoření pole ze seznamu
numpy.array([1,2,3,4])
array([1, 2, 3, 4])
 
# vytvoření pole z typu 'range'
numpy.array(range(10))
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
 
# explicitní specifikace typu všech prvků pole
numpy.array(range(10), dtype=numpy.float)
array([ 0.,  1.,  2.,  3.,  4.,  5.,  6.,  7.,  8.,  9.])
 
# explicitní specifikace uspořádání prvků pole
# (nemá velký význam pro 1D pole=vektory)
numpy.array(range(10), order='C')
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
 
# explicitní specifikace uspořádání prvků pole
# (nemá velký význam pro 1D pole=vektory)
numpy.array(range(10), order='F')
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
 
# vytvoření dvourozměrné matice
numpy.array([[1,2,3],[4,5,6]])
array([[1, 2, 3],
       [4, 5, 6]])

Kromě funkce numpy.array se poměrně často setkáme s nutností vytvořit vektor či matici s nulovými prvky. V tomto případě samozřejmě není nutné složitě vytvářet a předávat takové pole do funkce numpy.array, ale lze namísto toho využít funkci nazvanou numpy.zeros, což je rychlejší a současně i méně paměťově náročnější. Této funkci se předá n-tice (musí se skutečně jednat o n-tici, nikoli o seznam parametrů, proto nezapomeňte na závorky) specifikující dimenzi vektoru, matice či N-dimenzionálního pole:

zeros(shape, dtype=float, order='C')

Nepovinný parametr dtype se nastavuje buď na typ nebo na jednoznakový kód uvedený v tabulce v předchozí kapitole.

Podívejme se nyní na způsob použití této funkce:

# jednorozměrný vektor s jediným prvkem
numpy.zeros(1)
array([ 0.])
 
# jednorozměrný vektor s deseti prvky
numpy.zeros(10)
array([ 0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.])
 
# matice o velikosti 5x5 prvků, každý prvek je typu float
numpy.zeros((5,5))
 
array([[ 0.,  0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.,  0.]])
 
# matice o velikosti 5x5 prvků, každý prvek je typu int
numpy.zeros((5,5),dtype=int)
 
array([[0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0]])
 
# použití komplexních čísel
numpy.zeros((2,2),dtype=numpy.complex)
 
array([[ 0.+0.j,  0.+0.j],
       [ 0.+0.j,  0.+0.j]])

Dalším velmi často používaným typem vektoru či matice je taková struktura, jejíž všechny prvky mají hodnotu 1. Takový vektor popř. matice je možné vytvořit funkcí numpy.ones:

numpy.ones(shape, dtype=None, order='C')

Nepovinný parametr dtype se, podobně jako u předchozí funkce, nastavuje buď na typ nebo na jednoznakový kód uvedený v tabulce v předchozí kapitole.

Opět se podívejme na několik příkladů použití této funkce v praxi:

# jednorozměrný vektor s deseti prvky
numpy.ones(10)
array([ 1.,  1.,  1.,  1.,  1.,  1.,  1.,  1.,  1.,  1.])
 
# matice se třemi řádky a čtyřmi sloupci
numpy.ones((3,4))
 
array([[ 1.,  1.,  1.,  1.],
       [ 1.,  1.,  1.,  1.],
       [ 1.,  1.,  1.,  1.]])
 
# matice se třemi řádky a čtyřmi sloupci
# s explicitní specifikací typu prvků
numpy.ones((3,4), dtype=int)
 
array([[1, 1, 1, 1],
       [1, 1, 1, 1],
       [1, 1, 1, 1]])
 
# trojrozměrné pole s prvky typu int
numpy.ones((3,4,5), dtype=int)
 
array([[[1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1]],
 
       [[1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1]],
 
       [[1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1]]])
 
# trojrozměrné pole s prvky typu int
# (oproti předchozímu příkladu se velikosti v jednotlivých dimenzích liší)
numpy.ones((5,4,3), dtype=int)
 
array([[[1, 1, 1],
        [1, 1, 1],
        [1, 1, 1],
        [1, 1, 1]],
 
       [[1, 1, 1],
        [1, 1, 1],
        [1, 1, 1],
        [1, 1, 1]],
 
       [[1, 1, 1],
        [1, 1, 1],
        [1, 1, 1],
        [1, 1, 1]],
 
       [[1, 1, 1],
        [1, 1, 1],
        [1, 1, 1],
        [1, 1, 1]],
 
       [[1, 1, 1],
        [1, 1, 1],
        [1, 1, 1],
        [1, 1, 1]]])
 
# zde může být použití typu komplexní číslo možná poněkud překvapující,
# ovšem stále platí, že 1=1+0j
numpy.ones((3,2),dtype=numpy.complex)
 
array([[ 1.+0.j,  1.+0.j],
       [ 1.+0.j,  1.+0.j],
       [ 1.+0.j,  1.+0.j]])

Další funkcí určenou pro konstrukci vektoru je funkce pojmenovaná numpy.arange, přičemž poněkud zavádějící název „arange“ vznikl složením slov „array“ a „range“. Této funkci se předávají parametry s podobným významem, jaký mají u funkce xrange (Python 2.x) či range (Python 3.x), samozřejmě s tím rozdílem, že návratovou hodnotou funkce numpy.arange se skutečný vektor typu ndarray. Podívejme se na několik možností použití této poměrně nenápadné, ale o to užitečnější funkce:

# při použití jednoho parametru má tento parametr význam hodnoty "stop"
# vytvoří se vektor s prvky od 0 do "stop" (kromě)
numpy.arange(10)
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
 
# specifikace hodnot "start" (včetně) a "stop" (kromě)
numpy.arange(10, 20)
array([10, 11, 12, 13, 14, 15, 16, 17, 18, 19])
 
# třetí nepovinný parametr určuje krok použitý při generování prvků vektoru
numpy.arange(10, 20, 2)
array([10, 12, 14, 16, 18])
 
# krok může být samozřejmě záporný
numpy.arange(20, 10, -2)
array([20, 18, 16, 14, 12])
 
# nemusíme zůstat pouze u celých čísel, protože pracovat je možné i s hodnotami
# typu float a complex
numpy.arange(0,5, 0.1)
 
array([ 0. ,  0.1,  0.2,  0.3,  0.4,  0.5,  0.6,  0.7,  0.8,  0.9,  1. ,
        1.1,  1.2,  1.3,  1.4,  1.5,  1.6,  1.7,  1.8,  1.9,  2. ,  2.1,
        2.2,  2.3,  2.4,  2.5,  2.6,  2.7,  2.8,  2.9,  3. ,  3.1,  3.2,
        3.3,  3.4,  3.5,  3.6,  3.7,  3.8,  3.9,  4. ,  4.1,  4.2,  4.3,
        4.4,  4.5,  4.6,  4.7,  4.8,  4.9])
 
# použití komplexních konstant
numpy.arange(0+0j, 10+10j, 2+0j)
array([ 0.+0.j,  2.+0.j,  4.+0.j,  6.+0.j,  8.+0.j])

Další velmi důležitou funkcí, s níž se v praxi často setkáme, je funkce nazvaná numpy.reshape, která dokáže změnit velikost matice a vhodným způsobem přeorganizovat prvky v původní matici. Této funkci se předávají dva parametry – prvním parametrem je vstupní pole (vektor, matice, …), druhým parametrem pak specifikace tvaru výsledného pole, přičemž tvar je reprezentován n-ticí, ve které jsou uloženy velikosti pole v jednotlivých dimenzích. Podívejme se na několik příkladů:

# běžná matice se dvěma řádky a třemi sloupci
b=numpy.array([[1,2,3],[4,5,6]])
 
# změna tvaru matice na 3x2 prvky
numpy.reshape(b,(3,2))
 
array([[1, 2],
       [3, 4],
       [5, 6]])
 
# zde vlastně dostaneme původní matici
numpy.reshape(b,(2,3))
 
array([[1, 2, 3],
       [4, 5, 6]])
 
# vytvoření matice s jediným řádkem
numpy.reshape(b,(1,6))
 
array([[1, 2, 3, 4, 5, 6]])
 
# vytvoření matice s jediným sloupcem
numpy.reshape(b,(6,1))
 
array([[1],
       [2],
       [3],
       [4],
       [5],
       [6]])

8. Přístup k prvkům polí, využití takzvaných řezů polí

Jakým způsobem se pole s využitím knihovny Numpy vytváří již víme. Ještě si však musíme říct, jak se prvky uložené v polích vybírají neboli indexují. V případě jednorozměrných polí je to ve skutečnosti velmi jednoduché – prvky jsou totiž číslovány od nuly a díky přetížení operátoru [] (operátor indexování) je možné prvky v případě potřeby indexovat i od konce pole. V tomto případě se musí použít záporné číslo, takže a[1] značí druhý prvek pole zatímco a[-1] první prvek od konce:

a=numpy.arange(12)
 
a
array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11])
 
a[0]
0
 
a[5]
5
 
a[-1]
11
 
a[-5]
7

U dvourozměrných či vícerozměrných polí je situace poněkud komplikovanější, neboť v tomto případě je nutné použít dva či větší počet indexů (jeden index pro každou dimenzi). Vzhledem k tomu, že v různých programovacích jazycích a rozličných specializovaných nástrojích typu R či Matlab, se používají odlišné způsoby zápisu více indexů, podporuje knihovna Numpy dva způsoby zápisu:

  • buď se všechny indexy oddělí čárkou a vloží se do jediného bloku omezeného hranatými závorkami []
  • nebo se alternativně pro každou dimenzi použije zvláštní hranatá závorka (syntaxe odvozená od Céčka)

Podívejme se na příklady:

import numpy
 
m=numpy.reshape(numpy.arange(12), (3,4))
 
m
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])
 
m[0]
array([0, 1, 2, 3])
 
m[0][2]
2
 
m[0,2]
2

V mnoha případech je nutné z polí získat hodnoty většího množství prvků tvořících souvislý blok. Může se například jednat o všechny prvky pole kromě prvku prvního a posledního (typické pro některé filtry), prvky z první poloviny pole atd. I v tomto případě knihovna Numpy nabízí vývojářům velmi elegantní řešení, a to ve formě takzvaných řezů (slices). Namísto jediného indexu je totiž možné zadat dva indexy oddělené dvojtečkou, které potom reprezentují začátek a konec řezu. Opět se podívejme na demonstrační příklad:

a=numpy.arange(12)
 
a
array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11])
 
a[3:7]
array([3, 4, 5, 6])

Pokud se vynechá první index, automaticky se za něj dosadí 0, pokud se vynechá index druhý, dosadí se za něj velikost dimenze pole-1. Vynechat je možné i oba indexy; v tomto případě je řezem původní pole (tento zápis je sice možný, ale poněkud postrádá smysl):

a=numpy.arange(12)
 
a[:7]
array([0, 1, 2, 3, 4, 5, 6])
 
a[5:]
array([ 5,  6,  7,  8,  9, 10, 11])
 
a[:]
array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11])

Někdy může být řez polem prázdný:

a=numpy.arange(12)
 
a
array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11])
 
a[-4:-6]
array([], dtype=int64)

Použít je možné i záporné indexy popř. první či druhý index zcela vynechat:

a[-6:-4]
array([6, 7])
 
a[-6:]
array([ 6,  7,  8,  9, 10, 11])
 
a[:-4]
array([0, 1, 2, 3, 4, 5, 6, 7])

Řezy je možné provádět i u dvourozměrných či vícerozměrných polí. V tomto případě se zkombinuje již popsaný zápis s dvojtečkou:

import numpy
 
m=numpy.reshape(numpy.arange(25), (5,5))
 
m
array([[ 0,  1,  2,  3,  4],
       [ 5,  6,  7,  8,  9],
       [10, 11, 12, 13, 14],
       [15, 16, 17, 18, 19],
       [20, 21, 22, 23, 24]])
 
m[2:4]
array([[10, 11, 12, 13, 14],
       [15, 16, 17, 18, 19]])
 
m[2:4,3]
 
array([13, 18])
 
m[2:4,3:5]
 
array([[13, 14],
       [18, 19]])
 
m[1:4,1:4]
 
array([[ 6,  7,  8],
       [11, 12, 13],
       [16, 17, 18]])
 
m[-4:-2,-4:-2]
 
array([[ 6,  7],
       [11, 12]])

9. Základní operace s datovou strukturou ndarray

V případě importu knihovny Numpy dojde k přetížení mnoha běžných operátorů. Jedná se zejména o relační operátory, tj. o takové operátory, které slouží k porovnání dvou hodnot. Ve svém původním významu tyto operátory vrací jedinou pravdivostní hodnotu True nebo False. Ovšem pokud se relační operátory použijí ve své přetížené variantě pro porovnání polí (vektorů, matic), je výsledkem opět pole, ovšem pouze s hodnotami True a False vzniklými porovnáním prvků polí se stejným indexem. Při porovnávání musí mít obě pole stejný tvar, což je kontrolováno za běhu aplikace:

a=numpy.arange(1,11)
b=numpy.array([100,0,100,0,100,0,100,0,100,0])
 
a
array([ 1,  2,  3,  4,  5,  6,  7,  8,  9, 10])
 
b
array([100,   0, 100,   0, 100,   0, 100,   0, 100,   0])
 
a==b
array([False, False, False, False, False, False, False, False, False, False], dtype=bool)
 
a!=b
array([ True,  True,  True,  True,  True,  True,  True,  True,  True,  True], dtype=bool)
 
a<b
array([ True, False,  True, False,  True, False,  True, False,  True, False], dtype=bool)

Relační operátory je možné použít i tak, že jedním z operandů je pole a druhým operandem je skalární hodnota. Výsledkem takového porovnání je opět pole, tentokrát vytvořené porovnáním každého prvku zdrojového pole s jedinou skalární hodnotou:

a=numpy.arange(12)
 
a
array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11])
 
a==5
array([False, False, False, False,  True, False, False, False, False, False], dtype=bool)
 
a<6
array([ True,  True,  True,  True,  True,  True, False, False, False,
       False, False, False], dtype=bool)

Podobným způsobem můžeme vytvořit „Booleovská“ dvourozměrná pole:

m=numpy.arange(24)
 
m
 
array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16,
       17, 18, 19, 20, 21, 22, 23])
 
x=numpy.reshape(m, (6,4), order='F')
 
x>10
 
array([[False, False,  True,  True],
       [False, False,  True,  True],
       [False, False,  True,  True],
       [False, False,  True,  True],
       [False, False,  True,  True],
       [False,  True,  True,  True]], dtype=bool)
 
x%2==1
 
array([[False, False, False, False],
       [ True,  True,  True,  True],
       [False, False, False, False],
       [ True,  True,  True,  True],
       [False, False, False, False],
       [ True,  True,  True,  True]], dtype=bool)

10. Druhý příklad – pohybující se úsečka v desetisekundovém videu

Nyní již máme k dispozici všechny informace, které je možné použít pro tvorbu skutečného programově generovaného videa. Ve druhém příkladu upravíme callback funkci make_frame takovým způsobem, aby se do snímků vkládala vodorovná bílá úsečka. Na každém dalším snímku bude úsečka posunuta o jeden obrazový řádek níže, takže výsledkem bude (prozatím velmi primitivní) animace postupně klesající bílé čáry. Počet řádků výsledného videa je roven 240, což velmi dobře koresponduje s desetisekundovým trváním videa a 24 snímky za sekundu (úsečka by tedy za oněch deset sekund měla proběhnout přes celý snímek). Opět si nejprve ukažme celý zdrojový kód:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
from moviepy.editor import VideoClip
import numpy
 
WIDTH = 320
HEIGHT = 240
 
line = 0
 
 
def make_frame(t):
    global line
    print("time: {t}, line: {l}".format(t=t, l=line))
 
    # vyplneni trojrozmerneho pole nulami
    frame = numpy.zeros((HEIGHT, WIDTH, 3))
 
    # vykresleni jedine vodorovne usecky
    if line < HEIGHT:
        frame[line].fill(255)
        line += 1
    return frame
 
 
# vytvoreni video klipu
animation = VideoClip(make_frame, duration=10)
 
# export videa do formatu MPEG-4
animation.write_videofile("line.mp4", fps=24)
 
# znovunastaveni pocitadla
line = 0
 
# export videa do formatu Ogg Video File
animation.write_videofile("line.ogv", fps=24)
 
# znovunastaveni pocitadla
line = 0
 
# export videa do formatu GIF
animation.write_gif("line.gif", fps=24)

Tento kód je již nepatrně složitější, a to proto, že musíme mít pomocné počitadlo s vertikální pozicí úsečky. Počitadlo se s každým snímkem zvětšuje o jedničku:

line = 0
 
def make_frame(t):
    global line
    print("time: {t}, line: {l}".format(t=t, l=line))
 
...
...
...
 
    line += 1
    return frame

Zajímavý je i způsob vykreslení úsečky. Využíváme zde totiž „array slicing“ neboli přístup přes řez pole vysvětlený v předchozích kapitolách. Následující příkaz:

frame[line].fill(255)

Nejdříve z trojrozměrného pole vybere dvourozměrnou matici šířka×3 a posléze tuto matici vyplní hodnotami 255. Ostatně si to můžeme vyzkoušet i interaktivně na mnohem menší matici:

>>> import numpy
>>> frame=numpy.zeros((5,4,3))
>>> frame
array([[[ 0.,  0.,  0.],
        [ 0.,  0.,  0.],
        [ 0.,  0.,  0.],
        [ 0.,  0.,  0.]],
 
       [[ 0.,  0.,  0.],
        [ 0.,  0.,  0.],
        [ 0.,  0.,  0.],
        [ 0.,  0.,  0.]],
 
       [[ 0.,  0.,  0.],
        [ 0.,  0.,  0.],
        [ 0.,  0.,  0.],
        [ 0.,  0.,  0.]],
 
       [[ 0.,  0.,  0.],
        [ 0.,  0.,  0.],
        [ 0.,  0.,  0.],
        [ 0.,  0.,  0.]],
 
       [[ 0.,  0.,  0.],
        [ 0.,  0.,  0.],
        [ 0.,  0.,  0.],
        [ 0.,  0.,  0.]]])
 
>>> frame[3].fill(255)
>>> frame
array([[[   0.,    0.,    0.],
        [   0.,    0.,    0.],
        [   0.,    0.,    0.],
        [   0.,    0.,    0.]],
 
       [[   0.,    0.,    0.],
        [   0.,    0.,    0.],
        [   0.,    0.,    0.],
        [   0.,    0.,    0.]],
 
       [[   0.,    0.,    0.],
        [   0.,    0.,    0.],
        [   0.,    0.,    0.],
        [   0.,    0.,    0.]],
 
       [[ 255.,  255.,  255.],
        [ 255.,  255.,  255.],
        [ 255.,  255.,  255.],
        [ 255.,  255.,  255.]],
 
       [[   0.,    0.,    0.],
        [   0.,    0.,    0.],
        [   0.,    0.,    0.],
        [   0.,    0.,    0.]]])

11. Výsledky druhého příkladu

Opět se podívejme na výsledná videa vytvořená dnešním druhým demonstračním příkladem:

Obrázek 2: Animovaný GIF vytvořený druhým příkladem.

https://tisnik.github.io/moviepy-videos/video2.htm

12. Třetí příklad – vykreslení všech 16 milionů barev v desetisekundovém videu

Nyní již máme k dispozici všechny informace nutné pro to, abychom byli schopni vytvořit video, v němž se postupně (konkrétně na 256 snímcích) zobrazí celá barvová paleta. Připomeňme si, že datová struktura typu ndarray, která je pro každý snímek vytvořena, obsahuje šířka×výška hodnot, přičemž každá hodnota je vektorem tří čísel představujících hodnoty barvových složek red, green, blue. Každá barvová složka je reprezentována celým číslem od 0 do 255. Animace bude vytvořena následovně: každý snímek bude mít rozlišení 256×256 pixelů, což znamená, že jedna barvová složka bude ve snímku konstantní a ostatní dvě barvové složky budou použity ve všech možných vzájemných kombinacích (256×256 kombinací). Pokud vytvoříme 256 takových snímků, přičemž na každém budeme měnit první barvovou složku, zobrazíme postupně všech zhruba šestnáct milionů barev, které dokážou generovat grafické karty (a zobrazit dobře nakalibrované monitory).

Funkce make_frame bude vypadat následovně (předpokládáme, že se bude volat 256×, přičemž počitadlo bude uloženo v globální proměnné index):

def make_frame(t):
    """Vytvoreni jednoho snimku videa."""
    global index
    print("time: {t}, index: {i}".format(t=t, i=index))
 
    # vyplneni trojrozmerneho pole nulami
    frame = numpy.zeros((HEIGHT, WIDTH, 3))
 
    # vyplneni barvovym prechodem
    for y in range(HEIGHT):
        for x in range(WIDTH):
            frame[y][x][0] = x
            frame[y][x][2] = y
            frame[y][x][1] = index
    index += 1
    return frame

Zdrojový kód celého demonstračního příkladu:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
from moviepy.editor import VideoClip
import numpy
 
WIDTH = 256
HEIGHT = 256
 
index = 0
 
 
def make_frame(t):
    """Vytvoreni jednoho snimku videa."""
    global index
    print("time: {t}, index: {i}".format(t=t, i=index))
 
    # vyplneni trojrozmerneho pole nulami
    frame = numpy.zeros((HEIGHT, WIDTH, 3))
 
    # vyplneni barvovym prechodem
    for y in range(HEIGHT):
        for x in range(WIDTH):
            frame[y][x][0] = x
            frame[y][x][2] = y
            frame[y][x][1] = index
    index += 1
    return frame
 
 
# vytvoreni video klipu
animation = VideoClip(make_frame, duration=10)
 
# export videa do formatu Ogg Video File
animation.write_videofile("colors.ogv", fps=25)
 
# znovunastaveni pocitadla
index = 0
 
# export videa do formatu MPEG-4
animation.write_videofile("colors.mp4", fps=25)
 
# znovunastaveni pocitadla
index = 0
 
# export videa do formatu GIF
animation.write_gif("colors.gif", fps=25)

13. Výsledky třetího příkladu

Znovu si ukážeme výsledná videa vytvořená dnešním třetím demonstračním příkladem:

Obrázek 3: Animovaný GIF vytvořený třetím příkladem.

https://tisnik.github.io/moviepy-videos/video3.htm

14. Parametry předávané videokodeku

Při ukládání videa je možné zvolit několik parametrů předávaných video kodeku. Především se jedná o parametr progress_bar, který určuje, jestli budou výpisy z funkce make_frame přerušovány zobrazovaným progress barem (což působí rušivě). Důležitější je však parametr bitrate, kterým se určuje přibližný mezní bitový tok (bity za sekundu). Čím nižší bude zapsaná hodnota, tím horší bude kvalita výsledného videa a naopak (i když vztah není lineární). Podívejme se na příklad:

animation.write_videofile("mandelbrot_zoom.ogv", fps=20, progress_bar=False, bitrate="900000")

Rozdíly mezi videi vytvořenými s rozdílným bitovým tokem si můžete otestovat na této stránkce:

https://tisnik.github.io/moviepy-videos/video4.htm

15. Čtvrtý příklad – animace „průletu“ Mandelbrotovou množinou

V dnešním posledním příkladu si ukážeme, jak je možné vytvořit působivou animaci „průletu“ Mandelbrotovou množinou. Tématem vykreslování Mandelbrotovy množiny jsme se podrobně zabývali v seriálu o fraktálech v počítačové grafice:

  1. Výpočet bodů ležících v Mandelbrotově množině
    https://www.root.cz/clanky/fraktaly-v-pocitacove-grafice-xii#k05
  2. Fraktály v počítačové grafice XIII – zobrazení Mandelbrotovy množiny
    https://www.root.cz/clanky/fraktaly-v-pocitacove-grafice-xiii
  3. Fraktály v počítačové grafice XVI – Animace průletu Mandelbrotovou množinou
    https://www.root.cz/clanky/fraktaly-v-pocitacove-grafice-xvi

Výsledná animace se vytvoří tak, že se postupně spočítá větší množství obrázků Mandelbrotovy množiny, přičemž každý obrazec bude mít nastavené odlišné parametry výpočtu. Je přitom jasné, že čím menší budou rozdíly mezi jednotlivými parametry, tím plynulejší bude výsledná animace. Vytvořené rastrové obrázky se následně seřadí do sekvence a vytvoří se z nich animace, tj. postupný přechod mezi obrázky. Animaci lze samozřejmě uložit v různých formátech, přičemž my prozatím zůstaneme u kombinace MPEG4+H.264 a Ogg+Theora. Parametry pro tvorbu jednotlivých obrázků jsou nastaveny takovým způsobem, že mapování prvního obrazu do komplexní roviny vytvoří osově orientovaný obdélník o délce stran 4,0 a 3,0 se středem v bodě specifikovaném uživatelem ve skriptu. Ve druhém a každém dalším obrázku animace je obdélník proporcionálně zmenšen a zmenšování probíhá tak dlouho, aby se u posledního obrázku dosáhlo zadaného místa v Mandelbrotově množině. Příklad BEZ funkce pro výpočet Mandelbrotovy množiny bude vypadat takto:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
from moviepy.editor import VideoClip
import numpy
import palette_mandmap
 
 
WIDTH = 320
HEIGHT = 240
 
MAXITER = 255
 
# pocatecni podminky
 
x0 = -0.7913539
y0 = 0.161779
scale = 1.0000
scale_factor = 0.97
 
 
def make_frame(t):
    """Vytvoreni jednoho snimku videa."""
    global scale
    print("time: {t}, scale: {s}".format(t=t, s=scale))
 
    # vyplneni trojrozmerneho pole nulami
    frame = numpy.zeros((HEIGHT, WIDTH, 3))
 
    calc_mandelbrot(WIDTH, HEIGHT, MAXITER, palette_mandmap.palette, x0, y0, scale, frame)
    scale *= scale_factor
 
    return frame
 
 
# vytvoreni video klipu
animation = VideoClip(make_frame, duration=15)
 
# export videa do formatu Ogg Video File
animation.write_videofile("mandelbrot_zoom.ogv", fps=20, progress_bar=False, bitrate="900000")
 
# export videa do formatu MPEG-4
# animation.write_videofile("colors.mp4", fps=25)
 
# export videa do formatu GIF
# animation.write_gif("colors.gif", fps=25)

16. Přepočet parametrů obrázku v průběhu animace a výpočet snímku Mandelbrotovy množiny

Samotný výpočet Mandelbrotovy množiny je rozdělen na dvě části:

  1. Výpočet mezních hodnot v komplexní rovině, což zajišťuje funkcecalc_corner
  2. Výpočet barev pixelů v Mandelbrotově množine, což zajišťuje funkcecalc_mandelbrot

První zmíněná funkce na základě středu snímku (v komplexní rovině), velikosti obdélníkového výřezu a měřítka spočítá všechny čtyři mezní hodnoty:

def calc_corner(c_width, c_height, xpos, ypos, scale):
    return xpos - c_width * scale, \
           ypos - c_height * scale, \
           xpos + c_width * scale, \
           ypos + c_height * scale

Druhá funkce vypočte barvy všech pixelů s využitím barvové palety popsané v navazující kapitole:

def calc_mandelbrot(width, height, maxiter, palette, xpos, ypos, scale, array):
    xmin, ymin, xmax, ymax = calc_corner(2.0, 1.5, xpos, ypos, scale)
    c = complex(xmin, ymin)
    for y in range(0, height):
        c = complex(xmin, c.imag)
        for x in range(0, width):
            z = 0.0 + 0.0J
            i = 0
 
            # iteracni smycka
            while i < maxiter:
                if abs(z) > 4.0:
                    break
                z = z**2 + c
                i += 1
 
            # vypocet barvy
            r = palette[i][0]
            g = palette[i][1]
            b = palette[i][2]
            array[y][x][0] = r
            array[y][x][1] = g
            array[y][x][2] = b
 
            # posun na dalsi bod na radku
            c += (xmax - xmin) / width
 
        # posun na dalsi radek
        c += 1J*(ymax - ymin) / height

V×Mandelbrotově množině se nachází velké množství zajímavých oblastí, přičemž zobrazované tvary jsou soběpodobné, tj. při různém zvětšení se opakují. Některé typické tvary byly podle svého vzhledu dokonce pojmenovány. Popis zajímavých oblastí začneme částí Mandelbrotovy množiny, která se nachází v okolí takzvaného Feigenbaumova bodu. S prací Feigenbauma jsme se setkali již při popisování dynamických systémů a bifurkačních diagramů. Feigenbaumův bod je místo v Mandelbrotově množině, v jehož okolí nastává stejný jev jako v bifurkačním diagramu – prudké zdvojování period vedoucích až k chaosu. Tento bod má v komplexní rovině souřadnice –1,401155+0,0i, leží tedy v záporné části reálné osy.

Další zajímavé oblasti, které můžete vybrat a vytvořit tak zajímavé video, jsou popsány v následujících kapitolách článku ze seriálu o Fraktálech v počítačové grafice:

  1. Oblast nazývaná „Elephant valley“
  2. Oblast nazývaná „Seahorse valley“
  3. Oblast nazývaná „West seahorse valley“
  4. Oblast nazývaná „Triple spiral valley“
  5. Oblast nazývaná „Quad spiral valley“

17. Pomocný modul s barvovou paletou

Fraktály či obecně procedurální textury vykreslené ve stupních šedi sice mohou být pro některé projekty zajímavé, ovšem většinou požadujeme textury barevné. Ve skutečnosti je řešení jednoduché – postačuje ke každému vypočtenému indexu vybrat vhodnou barvu z barvové palety. Tradičně mají barvové palety 256 barev, ovšem samozřejmě je v případě potřeby možné vytvořit rozsáhlejší či naopak menší palety. Pro účely demonstračního příkladu použijeme barvovou paletu získanou z datových souborů programu Fractint a převedenou na Pythonovský seznam:

# taken from Fractint
palette = (
        (255, 255, 255), (224, 224, 224), (216, 216, 216), (208, 208, 208),
        (200, 200, 200), (192, 192, 192), (184, 184, 184), (176, 176, 176),
        (168, 168, 168), (160, 160, 160), (152, 152, 152), (144, 144, 144),
        (136, 136, 136), (128, 128, 128), (120, 120, 120), (112, 112, 112),
        (104, 104, 104),  (96,  96,  96),  (88,  88,  88),  (80,  80,  80),
        (72,   72,  72),  (64,  64,  64),  (56,  56,  56),  (48,  48,  56),
        (40,   40,  56),  (32,  32,  56),  (24,  24,  56),  (16,  16,  56),
        (8,     8,  56), (000, 000,  60), (000, 000,  64), (000, 000,  72),
        (000, 000,  80), (000, 000,  88), (000, 000,  96), (000, 000, 104),
        (000, 000, 108), (000, 000, 116), (000, 000, 124), (000, 000, 132),
        (000, 000, 140), (000, 000, 148), (000, 000, 156), (000, 000, 160),
        (000, 000, 168), (000, 000, 176), (000, 000, 184), (000, 000, 192),
        (000, 000, 200), (000, 000, 204), (000, 000, 212), (000, 000, 220),
        (000, 000, 228), (000, 000, 236), (000, 000, 244), (000, 000, 252),
        (000,   4, 252),   (4,  12, 252),   (8,  20, 252),  (12,  28, 252),
        (16,   36, 252),  (20,  44, 252),  (20,  52, 252),  (24,  60, 252),
        (28,   68, 252),  (32,  76, 252),  (36,  84, 252),  (40,  92, 252),
        (40,  100, 252),  (44, 108, 252),  (48, 116, 252),  (52, 120, 252),
        (56,  128, 252),  (60, 136, 252),  (60, 144, 252),  (64, 152, 252),
        (68,  160, 252),  (72, 168, 252),  (76, 176, 252),  (80, 184, 252),
        (80,  192, 252),  (84, 200, 252),  (88, 208, 252),  (92, 216, 252),
        (96,  224, 252), (100, 232, 252), (100, 228, 248),  (96, 224, 244),
        (92,  216, 240),  (88, 212, 236),  (88, 204, 232),  (84, 200, 228),
        (80,  192, 220),  (76, 188, 216),  (76, 180, 212),  (72, 176, 208),
        (68,  168, 204),  (64, 164, 200),  (64, 156, 196),  (60, 152, 188),
        (56,  144, 184),  (52, 140, 180),  (52, 132, 176),  (48, 128, 172),
        (44,  120, 168),  (40, 116, 160),  (40, 108, 156),  (36, 104, 152),
        (32,   96, 148),  (28,  92, 144),  (28,  84, 140),  (24,  80, 136),
        (20,   72, 128),  (16,  68, 124),  (16,  60, 120),  (12,  56, 116),
        (8,    48, 112),   (4,  44, 108), (000,  36, 100),   (4,  36, 104),
        (12,   40, 108),  (16,  44, 116),  (24,  48, 120),  (28,  52, 128),
        (36,   56, 132),  (40,  60, 140),  (48,  64, 144),  (52,  64, 148),
        (60,   68, 156),  (64,  72, 160),  (72,  76, 168),  (76,  80, 172),
        (84,   84, 180),  (88,  88, 184),  (96,  92, 192), (104, 100, 192),
        (112, 112, 196), (124, 120, 200), (132, 132, 204), (144, 140, 208),
        (152, 152, 212), (164, 160, 216), (172, 172, 220), (180, 180, 224),
        (192, 192, 228), (200, 200, 232), (212, 212, 236), (220, 220, 240),
        (232, 232, 244), (240, 240, 248), (252, 252, 252), (252, 240, 244),
        (252, 224, 232), (252, 208, 224), (252, 192, 212), (252, 176, 204),
        (252, 160, 192), (252, 144, 184), (252, 128, 172), (252, 112, 164),
        (252,  96, 152), (252,  80, 144), (252,  64, 132), (252,  48, 124),
        (252,  32, 112), (252,  16, 104), (252, 000,  92), (236, 000,  88),
        (228, 000,  88), (216,   4,  84), (204,   4,  80), (192,   8,  76),
        (180,   8,  76), (168,  12,  72), (156,  16,  68), (144,  16,  64),
        (132,  20,  60), (124,  20,  60), (112,  24,  56), (100,  24,  52),
        (88,   28,  48),  (76,  32,  44),  (64,  32,  44),  (52,  36,  40),
        (40,   36,  36),  (28,  40,  32),  (16,  44,  28),  (20,  52,  32),
        (24,   60,  36),  (28,  68,  44),  (32,  76,  48),  (36,  88,  56),
        (40,   96,  60),  (44, 104,  64),  (48, 112,  72),  (52, 120,  76),
        (56,  132,  84),  (48, 136,  84),  (40, 144,  80),  (52, 148,  88),
        (68,  156, 100),  (80, 164, 112),  (96, 168, 124), (108, 176, 136),
        (124, 184, 144), (136, 192, 156), (152, 196, 168), (164, 204, 180),
        (180, 212, 192), (192, 220, 200), (208, 224, 212), (220, 232, 224),
        (236, 240, 236), (252, 248, 248), (252, 252, 252), (252, 252, 240),
        (252, 252, 228), (252, 252, 216), (248, 248, 204), (248, 248, 192),
        (248, 248, 180), (248, 248, 164), (244, 244, 152), (244, 244, 140),
        (244, 244, 128), (244, 244, 116), (240, 240, 104), (240, 240,  92),
        (240, 240,  76), (240, 240,  64), (236, 236,  52), (236, 236,  40),
        (236, 236,  28), (236, 236,  16), (232, 232,   0), (232, 232,  12),
        (232, 232,  28), (232, 232,  40), (236, 236,  56), (236, 236,  68),
        (236, 236,  84), (236, 236,  96), (240, 240, 112), (240, 240, 124),
        (240, 240, 140), (244, 244, 152), (244, 244, 168), (244, 244, 180),
        (244, 244, 196), (248, 248, 208), (248, 248, 224), (248, 248, 236),
        (252, 252, 252), (248, 248, 248), (240, 240, 240), (232, 232, 232))

18. Výsledek vytvořený čtvrtým příkladem

Dnes již naposledy si ukážeme výsledná videa vytvořená čtvrtým demonstračním příkladem:

zabbix_tip

https://tisnik.github.io/moviepy-videos/video4.htm

19. Repositář s demonstračními příklady

Zdrojové kódy všech čtyř dnes popsaných demonstračních příkladů určených pro interpret Pythonu 3 byly uloženy do Git repositáře dostupného na adrese https://github.com/tisnik/moviepy-examples. V případě, že nebudete chtít klonovat celý repositář (ten je ovšem prozatím velmi malý, doslova několik kilobajtů), můžete namísto toho použít odkazy na jednotlivé demonstrační příklady, které naleznete v následující tabulce:

20. Odkazy na Internetu

  1. MoviePy 0.2.3.3 na PyPi
    https://pypi.org/project/moviepy/
  2. MoviePy na GitHubu
    https://github.com/Zulko/moviepy
  3. MoviePy – dokumentace
    http://zulko.github.io/moviepy/
  4. MoviePy – galerie
    http://zulko.github.io/mo­viepy/gallery.html
  5. Data Animations With Python and MoviePy
    https://zulko.github.io/blog/2014/11/29/da­ta-animations-with-python-and-moviepy/
  6. Porovnání formátů Ogg Theora a H.264
    https://www.root.cz/zpravic­ky/porovnani-formatu-ogg-theora-a-h-264/
  7. Případ GIF
    https://www.root.cz/clanky/pripad-gif/
  8. Pravda a mýty o GIFu
    https://www.root.cz/clanky/pravda-a-myty-o-gifu/
  9. Anatomie grafického formátu GIF
    https://www.root.cz/clanky/anatomie-grafickeho-formatu-gif/
  10. GIF: animace a konkurence
    https://www.root.cz/clanky/gif-animace-a-konkurence/
  11. Two python modules : MoviePy and images2gif – part 001
    http://free-tutorials.org/two-python-modules-moviepy-and-images2gif-part-001/
  12. images2gif
    https://pypi.org/project/images2gif/
  13. Making GIFs from video files with Python
    https://www.devbattles.com/en/san­d/post-345-Making+GIFs+From+Video+Fi­les+With+Python
  14. GIF89a specification
    https://www.w3.org/Graphics/GIF/spec-gif89a.txt
  15. MPEG-4 Part 14
    https://en.wikipedia.org/wiki/MPEG-4_Part14
  16. Theora video compression
    https://www.theora.org/
  17. Theora
    https://en.wikipedia.org/wiki/Theora
  18. NumPy
    http://www.numpy.org/
  19. numpy 1.14.2 (on PyPi)
    https://pypi.org/project/numpy/
  20. Integrovaná vývojová prostředí ve Fedoře: praktické použití IPython Notebooku a knihovny Numpy
    https://mojefedora.cz/integrovana-vyvojova-prostredi-ve-fedore-prakticke-pouziti-ipython-notebooku-a-knihovny-numpy/
  21. Integrovaná vývojová prostředí ve Fedoře: praktické použití IPython Notebooku a knihovny Numpy (2.část)
    https://mojefedora.cz/integrovana-vyvojova-prostredi-ve-fedore-prakticke-pouziti-ipython-notebooku-a-knihovny-numpy-2-cast/
  22. Non-linear editing system
    https://en.wikipedia.org/wiki/Non-linear_editing_system

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.