Obsah
1. Knihovna Gift pro zpracování rastrových obrázků
2. Získání testovacího obrázku používaného demonstračními příklady
3. Kostra demonstračních příkladů
4. Typy obrazových filtrů nabízených knihovnou Gift
5. Konverze obrázku na stupně šedi a použití filtru „sepia“
7. Kombinace několika filtrů postupně aplikovaných na jediný vstupní obrázek
8. Změna světlosti, kontrastu a saturace barev
11. Filtry pracující s nejbližším okolím pixelů
12. Výběr lokálního minima, maxima, průměru a mediánu
14. Příklady jednoduchých 2D konvolučních filtrů
17. Automatická normalizace pixelu po aplikaci kernelu
18. Obsah následující části seriálu
19. Repositář s demonstračními příklady
1. Knihovna Gift pro zpracování rastrových obrázků
V předchozích dvou částech [1] [2] seriálu o programovacím jazyce Go jsme se zabývali problematikou spolupráce mezi překladačem jazyka Go a vestavěným assemblerem. Dnes se tímto tématem (zdánlivě) nebudeme zabývat, protože si namísto toho popíšeme knihovnu nazvanou Gift, která slouží ke zpracování rastrových obrázků. Ostatně i samotný název této knihovny je vlastně zkratkou získanou z „Go Image Filtering Toolkit “. Jak toto téma souvisí s assemblerem si vysvětlíme příště, takže jen v krátkosti – velkou část operací prováděných nad rastrovými obrázky lze velmi dobře (a mnohdy i významně) urychlit použitím SIMD instrukcí, což konkrétně na platformě x86–64 znamená použití instrukcí ze sady SSE, SSE2, SSE3 či AVX. Knihovna Gift tato rozšíření přímo nepoužívá, protože je kompletně psána v programovacím jazyce Go bez použití assembleru a možnosti Go při automatické vektorizaci prozatím nejsou příliš velké. Ovšem příště si tyto metody optimalizace popíšeme a ukážeme.
Samotnou knihovnu Gift lze nainstalovat stejně snadno jako jakýkoli jiný balíček určený pro ekosystém programovacího jazyka Go:
$ go get -u github.com/disintegration/gift
2. Získání testovacího obrázku používaného demonstračními příklady
Před popisem jednotlivých operací, které knihovna Gift podporuje, a před spuštěním demonstračních příkladů je nutné získat testovací obrázek, který bude do příkladů načítán a dále zpracováván. Vzhledem k tomu, že si budeme mj. popisovat i různé konvoluční filtry aplikované na rastrové obrázky, použijeme dnes již legendární testovací obrázek s fotkou Lenny (Leny), který se v oblasti počítačové grafiky a zpracování obrazu používá již několik desetiletí, konkrétně od roku 1973 (více viz stránka Lenna 97: A Complete Story of Lenna).
Obrázek 1: Klasický testovací obrázek Lenny v původním rozlišení 512×512 pixelů.
Testovací obrázek, který má dnes již „klasické“ rozlišení 512×512 pixelů, je možné získat například z Wikipedie, a to následujícím jednoduchým skriptem. Skript je vhodné spustit ve stejném adresáři, kde se nachází i demonstrační příklady získané z repositáře popsaného v devatenácté kapitole:
$ original_image_address="https://upload.wikimedia.org/wikipedia/en/7/7d/Lenna_%28test_image%29.png" $ wget -v -O Lenna.png $original_image_address
Po spuštění tohoto skriptu by se měl v pracovním adresáři objevit nový soubor nazvaný Lenna.png. O tom se samozřejmě můžeme velmi snadno přesvědčit pomocí příkazů ls a file:
$ ls -l Lenna.png -rw-rw-r--. 1 ptisnovs ptisnovs 473831 10. kvě 17.16 Lenna.png
$ file Lenna.png Lenna.png: PNG image data, 512 x 512, 8-bit/color RGB, non-interlaced
Pro účely tohoto článku však obrázek zmenšíme na polovinu v obou směrech. Výsledkem této operace tedy bude bitmapa o rozměrech 256×256 pixelů:
$ convert Lenna.png -resize 256x256 Lenna.png
Zkontrolujeme výsledek:
$ ls -l Lenna.png -rw-r--r-- 1 tester tester 118234 úno 4 21:27 Lenna.png $ file Lenna.png Lenna.png: PNG image data, 256 x 256, 8-bit/color RGB, non-interlaced
Obrázek 2: Testovací obrázek Lenny zmenšený na rozlišení 256×256 pixelů, tedy tak, jak je použitý ve všech dnešních demonstračních příkladech.
3. Kostra demonstračních příkladů
Všechny dnešní demonstrační příklady budou postaveny na podobném základu, který je zobrazen a popsán níže. Ve zdrojovém kódu příkladu můžeme vidět dvojici pomocných funkcí určených pro načtení rastrového obrázku ve formátu PNG a pro uložení výsledného obrázku, taktéž do formátu PNG. Jedná se o funkce se jmény loadImage a saveImage.
Důležitou součástí všech demonstračních příkladů je však aplikace filtrů, která vypadá takto:
g := gift.New() destinationImage := image.NewRGBA(g.Bounds(sourceImage.Bounds())) g.Draw(destinationImage, sourceImage)
Můžeme zde vidět konstrukci „pipeline“ s filtry, zde konkrétně prázdné „pipeline“, v níž se žádné filtry nenachází:
g := gift.New()
Dále se na základě analýzy filtrů vytvoří nový prázdný obrázek a následně se do tohoto obrázku provede vykreslení s využitím aplikací filtru/filtrů na zdrojový obrázek. Velikost cílového obrázku může být odlišná, což platí například po aplikaci filtru pro otočení atd. Právě z tohoto důvodu je důležité velikost zjistit zavoláním:
g.Bounds(sourceImage.Bounds())
Nakonec je filtr/filtry aplikován a obrázek je vykreslen metodou Draw:
g.Draw(destinationImage, sourceImage)
Výsledek bude vypadat takto (bude se jednat o kopii původního obrázku):
Úplná kostra dnešních příkladů vypadá následovně:
package main import ( "github.com/disintegration/gift" "image" "image/png" "log" "os" ) const SourceImageFileName = "Lenna.png" const DestinationImageFileName = "01.png" func loadImage(filename string) (image.Image, error) { infile, err := os.Open(filename) if err != nil { return nil, err } defer infile.Close() src, _, err := image.Decode(infile) if err != nil { return nil, err } return src, nil } func saveImage(filename string, img image.Image) error { outfile, err := os.Create(filename) if err != nil { return err } defer outfile.Close() png.Encode(outfile, img) return nil } func main() { sourceImage, err := loadImage(SourceImageFileName) if err != nil { log.Fatal(err) } g := gift.New() destinationImage := image.NewRGBA(g.Bounds(sourceImage.Bounds())) g.Draw(destinationImage, sourceImage) err = saveImage(DestinationImageFileName, destinationImage) if err != nil { log.Fatal(err) } }
4. Typy obrazových filtrů nabízených knihovnou Gift
V knihovně Gift najdeme několik typů filtrů, které se od sebe odlišují zejména tím, jaké manipulace s rastrovými obrázky jsou prováděny:
- Filtry, které pouze modifikují barvy jednotlivých pixelů na základě jejich původní barvy (tedy bez ohledu na to, jak vypadá okolí měněného pixelu). Jedná se o nejjednodušší a obecně nejrychlejší typ filtrů, které slouží například pro změnu kontrastu, k barevné korekci, negaci obrázku, převodu obrázku na stupně šedi atd. Z hlediska implementace (o které se budeme bavit příště) je možné zpracování výrazně zparalelizovat.
- Druhé filtry jsou konvoluční. Ty již pracují nejenom s barvou měněného pixelu, ale i s jeho nejbližším okolím, jehož velikost je typicky nastavena na 3×3 pixely, 5×5 pixelů, ale a u některých filtrů může být i větší. Tyto filtry slouží k detekci hran, zaostření, rozostření atd.
- Třetí typ filtrů obrázek (jako celek) převrací či otáčí. Výsledkem je v obecném případě nový obrázek s odlišným rozlišením, což platí zejména pro otáčení obrázků a samozřejmě taktéž pro filtry typu resize a crop.
5. Konverze obrázku na stupně šedi a použití filtru „sepia“
V prvním skutečném demonstračním příkladu je ukázána konverze původně plnobarevného (truecolor) obrázku na stupně šedi. Pro tento účel se používá filtr pojmenovaný Grayscale, jenž se aplikuje následovně:
g := gift.New( gift.Grayscale())
Výsledkem je skutečně černobílý obrázek:
Druhý implementovaný filtr je podobný filtru prvnímu, ovšem používá se zde efekt známý pod jménem sepia, který se snaží navodit dojem starých fotografií. Parametr udává míru „zastarávání“ fotografie:
g = gift.New( gift.Sepia(50.0))
Výsledek v tomto případě vypadá následovně:
Ukažme si nyní úplný zdrojový kód tohoto demonstračního příkladu:
package main import ( "github.com/disintegration/gift" "image" "image/png" "log" "os" ) const SourceImageFileName = "Lenna.png" const DestinationImageFileName1 = "02_grayscale.png" const DestinationImageFileName2 = "02_sepia.png" func loadImage(filename string) (image.Image, error) { infile, err := os.Open(filename) if err != nil { return nil, err } defer infile.Close() src, _, err := image.Decode(infile) if err != nil { return nil, err } return src, nil } func saveImage(filename string, img image.Image) error { outfile, err := os.Create(filename) if err != nil { return err } defer outfile.Close() png.Encode(outfile, img) return nil } func main() { sourceImage, err := loadImage(SourceImageFileName) if err != nil { log.Fatal(err) } g := gift.New( gift.Grayscale()) destinationImage := image.NewRGBA(g.Bounds(sourceImage.Bounds())) g.Draw(destinationImage, sourceImage) err = saveImage(DestinationImageFileName1, destinationImage) if err != nil { log.Fatal(err) } g = gift.New( gift.Sepia(50.0)) destinationImage = image.NewRGBA(g.Bounds(sourceImage.Bounds())) g.Draw(destinationImage, sourceImage) err = saveImage(DestinationImageFileName2, destinationImage) if err != nil { log.Fatal(err) } }
6. Inverze obrázku
Další demonstrační příklad je jednoduchý, protože v něm pouze provedeme inverzi obrázku. Z hlediska interní reprezentace je možné právě tuto operaci plně a bez problémů paralelizovat, a to velmi úspěšně (mnohem lépe, než je tomu u ostatních filtrů):
g := gift.New( gift.Invert())
Výsledek aplikace tohoto filtru vypadá následovně:
Následuje výpis úplného zdrojového kódu tohoto demonstračního příkladu, v němž se filtr Invert používá:
package main import ( "github.com/disintegration/gift" "image" "image/png" "log" "os" ) const SourceImageFileName = "Lenna.png" const DestinationImageFileName = "03_invert.png" func loadImage(filename string) (image.Image, error) { infile, err := os.Open(filename) if err != nil { return nil, err } defer infile.Close() src, _, err := image.Decode(infile) if err != nil { return nil, err } return src, nil } func saveImage(filename string, img image.Image) error { outfile, err := os.Create(filename) if err != nil { return err } defer outfile.Close() png.Encode(outfile, img) return nil } func main() { sourceImage, err := loadImage(SourceImageFileName) if err != nil { log.Fatal(err) } g := gift.New( gift.Invert()) destinationImage := image.NewRGBA(g.Bounds(sourceImage.Bounds())) g.Draw(destinationImage, sourceImage) err = saveImage(DestinationImageFileName, destinationImage) if err != nil { log.Fatal(err) } }
7. Kombinace několika filtrů postupně aplikovaných na jediný vstupní obrázek
Jednotlivé filtry poskytované knihovnou Gift, popř. uživatelem definované filtry, je možné zřetězit. Všechny aplikované filtry, popř. i jejich parametry se předávají do konstruktoru gift.New:
g := gift.New( gift.Grayscale(), gift.Invert())
S výsledkem, který je očekávatelný – obrázek je nejdříve převeden na stupně šedi a posléze invertován:
Opět se podívejme na to, jak vypadá zdrojový kód takto upraveného příkladu:
package main import ( "github.com/disintegration/gift" "image" "image/png" "log" "os" ) const SourceImageFileName = "Lenna.png" const DestinationImageFileName = "04_grayscale_invert.png" func loadImage(filename string) (image.Image, error) { infile, err := os.Open(filename) if err != nil { return nil, err } defer infile.Close() src, _, err := image.Decode(infile) if err != nil { return nil, err } return src, nil } func saveImage(filename string, img image.Image) error { outfile, err := os.Create(filename) if err != nil { return err } defer outfile.Close() png.Encode(outfile, img) return nil } func main() { sourceImage, err := loadImage(SourceImageFileName) if err != nil { log.Fatal(err) } g := gift.New( gift.Grayscale(), gift.Invert()) destinationImage := image.NewRGBA(g.Bounds(sourceImage.Bounds())) g.Draw(destinationImage, sourceImage) err = saveImage(DestinationImageFileName, destinationImage) if err != nil { log.Fatal(err) } }
8. Změna světlosti, kontrastu a saturace barev
Další jednoduchý filtr se jmenuje Brightness a používá se ke změně celkové světlosti všech pixelů v obrázku. Tento filtr akceptuje parametr, kterým může být hodnota typu float32, přičemž záporné hodnoty značí, že se obrázek ztmaví a kladné hodnoty slouží k jeho zesvětlení. Příklad aplikace filtru s parametry –50, –30, –10, 10, 30 a 50:
Tyto obrázky byly získány kódem:
package main import ( "fmt" "github.com/disintegration/gift" "image" "image/png" "log" "os" ) const SourceImageFileName = "Lenna.png" const DestinationImageFileNameTemplate = "05_brightness_%d.png" func loadImage(filename string) (image.Image, error) { infile, err := os.Open(filename) if err != nil { return nil, err } defer infile.Close() src, _, err := image.Decode(infile) if err != nil { return nil, err } return src, nil } func saveImage(filename string, img image.Image) error { outfile, err := os.Create(filename) if err != nil { return err } defer outfile.Close() png.Encode(outfile, img) return nil } func main() { sourceImage, err := loadImage(SourceImageFileName) if err != nil { log.Fatal(err) } for brightness := -50; brightness <= 50; brightness += 20 { g := gift.New( gift.Brightness(float32(brightness))) destinationImage := image.NewRGBA(g.Bounds(sourceImage.Bounds())) g.Draw(destinationImage, sourceImage) filename := fmt.Sprintf(DestinationImageFileNameTemplate, brightness) err = saveImage(filename, destinationImage) if err != nil { log.Fatal(err) } } }
Podobně existuje filtr Contrast pro změnu kontrastu. Následuje příklad aplikace filtru s parametry –50, –30, –10, 10, 30 a 50:
A příslušný zdrojový kód:
package main import ( "fmt" "github.com/disintegration/gift" "image" "image/png" "log" "os" ) const SourceImageFileName = "Lenna.png" const DestinationImageFileNameTemplate = "06_contrast_%d.png" func loadImage(filename string) (image.Image, error) { infile, err := os.Open(filename) if err != nil { return nil, err } defer infile.Close() src, _, err := image.Decode(infile) if err != nil { return nil, err } return src, nil } func saveImage(filename string, img image.Image) error { outfile, err := os.Create(filename) if err != nil { return err } defer outfile.Close() png.Encode(outfile, img) return nil } func main() { sourceImage, err := loadImage(SourceImageFileName) if err != nil { log.Fatal(err) } for contrast := -50; contrast <= 50; contrast += 20 { g := gift.New( gift.Contrast(float32(contrast))) destinationImage := image.NewRGBA(g.Bounds(sourceImage.Bounds())) g.Draw(destinationImage, sourceImage) filename := fmt.Sprintf(DestinationImageFileNameTemplate, contrast) err = saveImage(filename, destinationImage) if err != nil { log.Fatal(err) } } }
A konečně si ukažme filtr pro změnu barevné saturace obrázku, opět nejdříve na několika výsledcích, tentokrát ovšem pro hodnoty od –80 (nižší saturace) po +80 (vysoká saturace):
Tyto obrázky byly vytvořeny příkladem:
package main import ( "fmt" "github.com/disintegration/gift" "image" "image/png" "log" "os" ) const SourceImageFileName = "Lenna.png" const DestinationImageFileNameTemplate = "07_saturation_%d.png" func loadImage(filename string) (image.Image, error) { infile, err := os.Open(filename) if err != nil { return nil, err } defer infile.Close() src, _, err := image.Decode(infile) if err != nil { return nil, err } return src, nil } func saveImage(filename string, img image.Image) error { outfile, err := os.Create(filename) if err != nil { return err } defer outfile.Close() png.Encode(outfile, img) return nil } func main() { sourceImage, err := loadImage(SourceImageFileName) if err != nil { log.Fatal(err) } for saturation := -80; saturation <= 80; saturation += 40 { g := gift.New( gift.Saturation(float32(saturation))) destinationImage := image.NewRGBA(g.Bounds(sourceImage.Bounds())) g.Draw(destinationImage, sourceImage) filename := fmt.Sprintf(DestinationImageFileNameTemplate, saturation) err = saveImage(filename, destinationImage) if err != nil { log.Fatal(err) } } }
9. Gamma korekce
Dalším často používaným filtrem (zejména při zpracování obrázků získaných na jiném zařízení) je takzvaná gamma korekce. Jedná se o nelineární změnu jasu pixelů, kterou budeme aplikovat na černobílý obrázek, kde je efekt gamma korekce lépe viditelný:
V těchto obrázcích se hodnota gamma měnila podle smyčky:
for gamma := 0.25; gamma <= 4.0; gamma *= 2 { g := gift.New( gift.Grayscale(), gift.Gamma(float32(gamma)))
Úplný zdrojový kód příkladu:
package main import ( "fmt" "github.com/disintegration/gift" "image" "image/png" "log" "os" ) const SourceImageFileName = "Lenna.png" const DestinationImageFileNameTemplate = "08_gamma_%4.2f.png" func loadImage(filename string) (image.Image, error) { infile, err := os.Open(filename) if err != nil { return nil, err } defer infile.Close() src, _, err := image.Decode(infile) if err != nil { return nil, err } return src, nil } func saveImage(filename string, img image.Image) error { outfile, err := os.Create(filename) if err != nil { return err } defer outfile.Close() png.Encode(outfile, img) return nil } func main() { sourceImage, err := loadImage(SourceImageFileName) if err != nil { log.Fatal(err) } for gamma := 0.25; gamma <= 4.0; gamma *= 2 { g := gift.New( gift.Grayscale(), gift.Gamma(float32(gamma))) destinationImage := image.NewRGBA(g.Bounds(sourceImage.Bounds())) g.Draw(destinationImage, sourceImage) filename := fmt.Sprintf(DestinationImageFileNameTemplate, gamma) err = saveImage(filename, destinationImage) if err != nil { log.Fatal(err) } } }
10. Posun v barvovém prostoru
Posledním jednoduchým filtrem je filtr, který provádí posun barev pixelů v barvovém prostoru. Nemění se tedy ani kontrast ani celková světlost, ale „jen“ barvový odstín, tak, jak je to patrné na následujících obrázcích:
Tento filtr se nastavuje s využitím funkce gift.Hue, které se předá hodnota typu float32 určující otočení v barvovém prostoru (HSV, HLS):
g := gift.New( gift.Hue(float32(hue)))
Celý příklad, který vygeneroval předchozí sedmici obrázků:
package main import ( "fmt" "github.com/disintegration/gift" "image" "image/png" "log" "os" ) const SourceImageFileName = "Lenna.png" const DestinationImageFileNameTemplate = "09_hue_%d.png" func loadImage(filename string) (image.Image, error) { infile, err := os.Open(filename) if err != nil { return nil, err } defer infile.Close() src, _, err := image.Decode(infile) if err != nil { return nil, err } return src, nil } func saveImage(filename string, img image.Image) error { outfile, err := os.Create(filename) if err != nil { return err } defer outfile.Close() png.Encode(outfile, img) return nil } func main() { sourceImage, err := loadImage(SourceImageFileName) if err != nil { log.Fatal(err) } for hue := -120; hue <= 120; hue += 40 { g := gift.New( gift.Hue(float32(hue))) destinationImage := image.NewRGBA(g.Bounds(sourceImage.Bounds())) g.Draw(destinationImage, sourceImage) filename := fmt.Sprintf(DestinationImageFileNameTemplate, hue) err = saveImage(filename, destinationImage) if err != nil { log.Fatal(err) } } }
11. Filtry pracující s nejbližším okolím pixelů
Základním filtrem, který pracuje nejenom s jedním pixelem, ale i s jeho okolím, je filtr pro efekt „pixelize“ či „pixelate“. U tohoto filtru se zadává velikost čtverců se shodnou barvou:
Opět si samozřejmě ukážeme úplný zdrojový kód tohoto demonstračního příkladu:
package main import ( "fmt" "github.com/disintegration/gift" "image" "image/png" "log" "os" ) const SourceImageFileName = "Lenna.png" const DestinationImageFileNameTemplate = "10_pixelate_%02d.png" func loadImage(filename string) (image.Image, error) { infile, err := os.Open(filename) if err != nil { return nil, err } defer infile.Close() src, _, err := image.Decode(infile) if err != nil { return nil, err } return src, nil } func saveImage(filename string, img image.Image) error { outfile, err := os.Create(filename) if err != nil { return err } defer outfile.Close() png.Encode(outfile, img) return nil } func main() { sourceImage, err := loadImage(SourceImageFileName) if err != nil { log.Fatal(err) } for size := 2; size <= 16; size *= 2 { g := gift.New( gift.Pixelate(size)) destinationImage := image.NewRGBA(g.Bounds(sourceImage.Bounds())) g.Draw(destinationImage, sourceImage) filename := fmt.Sprintf(DestinationImageFileNameTemplate, size) err = saveImage(filename, destinationImage) if err != nil { log.Fatal(err) } } }
12. Výběr lokálního minima, maxima, průměru a mediánu
Další filtry pracují podobně jako filtr z předchozí kapitoly. Filtr vybírá barvu pixelů na základě lokálního minima, maxima, průměru, popř. mediánu zjištěného v okolí právě zpracovávaného pixelu. Okolí může být buď čtvercové nebo kruhové.
Efekt výběru lokálního maxima pro postupně se zvětšující okolí:
Efekt výběru lokálního minima pro postupně se zvětšující okolí:
Efekt výpočtu a výběru průměru pro postupně se zvětšující okolí:
Efekt výpočtu a výběru mediánu pro postupně se zvětšující okolí:
Následující demonstrační příklad vypočte a vykreslí všech šestnáct předchozích obrázků:
package main import ( "fmt" "github.com/disintegration/gift" "image" "image/png" "log" "os" ) const SourceImageFileName = "Lenna.png" const DestinationImageFileNameTemplateMin = "11_min_%02d.png" const DestinationImageFileNameTemplateMax = "11_max_%02d.png" const DestinationImageFileNameTemplateMean = "11_mean_%02d.png" const DestinationImageFileNameTemplateMedian = "11_median_%02d.png" func loadImage(filename string) (image.Image, error) { infile, err := os.Open(filename) if err != nil { return nil, err } defer infile.Close() src, _, err := image.Decode(infile) if err != nil { return nil, err } return src, nil } func saveImage(filename string, img image.Image) error { outfile, err := os.Create(filename) if err != nil { return err } defer outfile.Close() png.Encode(outfile, img) return nil } func main() { sourceImage, err := loadImage(SourceImageFileName) if err != nil { log.Fatal(err) } for size := 2; size <= 16; size *= 2 { g := gift.New(gift.Minimum(size, false)) destinationImage := image.NewRGBA(g.Bounds(sourceImage.Bounds())) g.Draw(destinationImage, sourceImage) filename := fmt.Sprintf(DestinationImageFileNameTemplateMin, size) err = saveImage(filename, destinationImage) if err != nil { log.Fatal(err) } g = gift.New(gift.Maximum(size, false)) destinationImage = image.NewRGBA(g.Bounds(sourceImage.Bounds())) g.Draw(destinationImage, sourceImage) filename = fmt.Sprintf(DestinationImageFileNameTemplateMax, size) err = saveImage(filename, destinationImage) if err != nil { log.Fatal(err) } g = gift.New(gift.Mean(size, false)) destinationImage = image.NewRGBA(g.Bounds(sourceImage.Bounds())) g.Draw(destinationImage, sourceImage) filename = fmt.Sprintf(DestinationImageFileNameTemplateMean, size) err = saveImage(filename, destinationImage) if err != nil { log.Fatal(err) } g = gift.New(gift.Median(size, false)) destinationImage = image.NewRGBA(g.Bounds(sourceImage.Bounds())) g.Draw(destinationImage, sourceImage) filename = fmt.Sprintf(DestinationImageFileNameTemplateMedian, size) err = saveImage(filename, destinationImage) if err != nil { log.Fatal(err) } } }
13. Konvoluční filtry
Potenciálně velmi užitečná funkce z balíčku (knihovny) Gift se skrývá pod jménem Convolution. Ta slouží k aplikaci konvolučního filtru na zdrojové obrázky. Pod pojmem filtrace si přitom můžeme představit soubor lokálních transformací rastrového obrazu, kterými se v případě monochromatických obrazů převádí hodnoty jasu původního obrazu na nové hodnoty jasu obrazu výstupního. Barevný obraz si můžeme pro účely filtrace představit jako tři monochromatické obrazy, z nichž každý obsahuje jas jedné barvové složky (ovšem interní reprezentace obrázku je odlišná). Podle vlastností funkčního vztahu pro výpočet jasu výsledného okolí na základě okolí O ve vstupním obrazu můžeme rozdělit metody filtrace na lineární a nelineární.
Lineární operace vyjadřují výslednou hodnotu jasu jako konvoluci okolí O příslušného bodu [i, j] a takzvaného konvolučního jádra (kernel). Mezi postupy předzpracování obrazu patří i algoritmy obnovení, které se obvykle také vyjadřují ve formě konvoluce. Okolím O, které se používá k výpočtu, je ale obecně celý obraz. Jedná se tedy o výpočetně velmi náročnou operaci. Obnovení se používá pro odstranění poruch s předem známými vlastnostmi jako například rozostření objektivu (též zrcadla) nebo rozmazání vlivem pohybu při snímání.
V dalším textu se však budeme zabývat pouze velmi jednoduchými konvolučními filtry, které pracují nad poměrně malým okolím zpracovávaných pixelů. Velikost konvolučního jádra určuje i velikost zpracovávaného okolí.
Nejpoužívanějším konvolučním filtrem je při práci s rastrovými obrazy bezesporu dvojdimenzionální konvoluční filtr. Jeho užitečnost spočívá především ve velkých možnostech změny rastrového obrazu, které přesahují možnosti jednodimenzionálních filtrů. Pomocí dvojdimenzionálních konvolučních filtrů je možné provádět ostření obrazu, rozmazávání, zvýrazňování hran nebo tvorbu reliéfů (vytlačeného vzoru) ze zadaného rastrového obrazu. Navíc je možné filtry řetězit a dosahovat tak různých složitějších efektů (což vše knihovna Gift pochopitelně podporuje).
14. Příklady jednoduchých 2D konvolučních filtrů
Podívejme se nyní na několik často používaných konvolučních filtrů, které většinou mají jádro o velikosti 3×3.
Obyčejné průměrování filtruje obraz tím, že nová hodnota jasu se spočítá jako aritmetický průměr jasu čtvercového nebo (méně často) obdélníkového okolí. Velikost skvrn šumu by měla být menší než velikost okolí a to by mělo být menší než nejmenší významný detail v obrazu, což je sice pěkná teorie, ovšem těžko dosažitelná. Při aplikaci tohoto filtru vždy dojde k rozmazání hran (alespoň v minimální míře podle velikosti jádra).
Konvoluční jádro filtru velikosti 3×3 pro obyčejné průměrování má tvar:
1 |1 1 1| h= - |1 1 1| 9 |1 1 1|
Jednoduchým rozšířením obyčejného průměrování je průměrování s Gaussovským rozložením. Toto rozložení samozřejmě nelze použít bez dalších úprav, protože by velikost konvoluční masky byla nekonečná. Proto se konvoluční maska filtru vytvoří tak, že se zvýší váhy středového bodu masky a/nebo i jeho 4-okolí (tj. bodů, které mají se středovým bodem společnou jednu souřadnici, druhá se o jednotku liší). Jedna z možných podob konvoluční masky má tvar:
1 |1 2 1| h= -- |2 4 2| 16 |1 2 1|
Všimněte si, že součet všech položek konvoluční matice dává po vynásobení vahou před maticí výslednou hodnotu 1. To zjednodušeně znamená, že se nemění celková světlost obrázku.
Mezi filtry používané pro zvýraznění hran patří Sobelův operátor. Pomocí tohoto operátoru jsou aproximovány první parciální derivace 2D funkce představované obrazem, jedná se tedy o operátor směrově závislý. Směr se u těchto operátorů udává podle světových stran. Sobelův operátor ve směru „sever“ má například tvar:
| 1 2 1| h= | 0 0 0| |-1 -2 -1|
Sobelův operátor v jiném směru lze získat rotací této matice.
15. Sobelův operátor
Sobelův operátor se na rastrový obrázek aplikuje takto:
g := gift.New( gift.Sobel())
S výsledkem:
Vidíme, že tento filtr skutečně dokáže zvýraznit hrany:
package main import ( "github.com/disintegration/gift" "image" "image/png" "log" "os" ) const SourceImageFileName = "Lenna.png" const DestinationImageFileName = "12_sobel.png" func loadImage(filename string) (image.Image, error) { infile, err := os.Open(filename) if err != nil { return nil, err } defer infile.Close() src, _, err := image.Decode(infile) if err != nil { return nil, err } return src, nil } func saveImage(filename string, img image.Image) error { outfile, err := os.Create(filename) if err != nil { return err } defer outfile.Close() png.Encode(outfile, img) return nil } func main() { sourceImage, err := loadImage(SourceImageFileName) if err != nil { log.Fatal(err) } g := gift.New( gift.Sobel()) destinationImage := image.NewRGBA(g.Bounds(sourceImage.Bounds())) g.Draw(destinationImage, sourceImage) err = saveImage(DestinationImageFileName, destinationImage) if err != nil { log.Fatal(err) } }
16. Obecný konvoluční filtr
Obecný konvoluční filtr se vytváří funkcí gift.Convolution, které se musí předat konvoluční jádro a další parametry – zda se má provádět normalizace, aplikovat filtr na alfa složku, posouvat výslednou barvovou složku o nějakou hodnotu atd. Typickým příkladem je filtr, který vytváří efekt vytlačeného vzorku, jehož jádro lze zadat následujícím způsobem:
filter := gift.Convolution([]float32{ -1, -1, 0, -1, 1, 1, 0, 1, 1, }, false, false, false, 0.0)
Výsledek aplikace takového filtru:
Příklad s aplikací filtru:
package main import ( "github.com/disintegration/gift" "image" "image/png" "log" "os" ) const SourceImageFileName = "Lenna.png" const DestinationImageFileName = "13_emboss.png" func loadImage(filename string) (image.Image, error) { infile, err := os.Open(filename) if err != nil { return nil, err } defer infile.Close() src, _, err := image.Decode(infile) if err != nil { return nil, err } return src, nil } func saveImage(filename string, img image.Image) error { outfile, err := os.Create(filename) if err != nil { return err } defer outfile.Close() png.Encode(outfile, img) return nil } func main() { sourceImage, err := loadImage(SourceImageFileName) if err != nil { log.Fatal(err) } filter := gift.Convolution([]float32{ -1, -1, 0, -1, 1, 1, 0, 1, 1, }, false, false, false, 0.0) g := gift.New(filter) destinationImage := image.NewRGBA(g.Bounds(sourceImage.Bounds())) g.Draw(destinationImage, sourceImage) err = saveImage(DestinationImageFileName, destinationImage) if err != nil { log.Fatal(err) } }
Můžeme samozřejmě aplikovat i filtr, kde součet položek v jádru nebude roven jedné, což je příklad následujícího ostřícího filtru:
filter := gift.Convolution([]float32{ 0, -1, 0, -1, 5, 1, 0, -1, 0, }, false, false, false, 0.0)
Výsledkem ovšem bude obraz, jehož barvy budou posunuty směrem k bílé (protože součet složek je větší než jedna):
Zdrojový kód příkladu:
package main import ( "github.com/disintegration/gift" "image" "image/png" "log" "os" ) const SourceImageFileName = "Lenna.png" const DestinationImageFileName = "14_sharpen.png" func loadImage(filename string) (image.Image, error) { infile, err := os.Open(filename) if err != nil { return nil, err } defer infile.Close() src, _, err := image.Decode(infile) if err != nil { return nil, err } return src, nil } func saveImage(filename string, img image.Image) error { outfile, err := os.Create(filename) if err != nil { return err } defer outfile.Close() png.Encode(outfile, img) return nil } func main() { sourceImage, err := loadImage(SourceImageFileName) if err != nil { log.Fatal(err) } filter := gift.Convolution([]float32{ 0, -1, 0, -1, 5, 1, 0, -1, 0, }, false, false, false, 0.0) g := gift.New(filter) destinationImage := image.NewRGBA(g.Bounds(sourceImage.Bounds())) g.Draw(destinationImage, sourceImage) err = saveImage(DestinationImageFileName, destinationImage) if err != nil { log.Fatal(err) } }
17. Automatická normalizace pixelu po aplikaci kernelu
Jedním z parametrů funkce Convolution je i přepínač určující, jestli se má provést automatická normalizace vah hodnot v jádru filtru. V případě, že je normalizace povolena, bude výsledný obrázek zaostřený a bude mít shodné barvy s obrázkem zdrojovým:
package main import ( "github.com/disintegration/gift" "image" "image/png" "log" "os" ) const SourceImageFileName = "Lenna.png" const DestinationImageFileName = "15_sharpen.png" func loadImage(filename string) (image.Image, error) { infile, err := os.Open(filename) if err != nil { return nil, err } defer infile.Close() src, _, err := image.Decode(infile) if err != nil { return nil, err } return src, nil } func saveImage(filename string, img image.Image) error { outfile, err := os.Create(filename) if err != nil { return err } defer outfile.Close() png.Encode(outfile, img) return nil } func main() { sourceImage, err := loadImage(SourceImageFileName) if err != nil { log.Fatal(err) } filter := gift.Convolution([]float32{ 0, -1, 0, -1, 5, 1, 0, -1, 0, }, true, false, false, 0.0) g := gift.New(filter) destinationImage := image.NewRGBA(g.Bounds(sourceImage.Bounds())) g.Draw(destinationImage, sourceImage) err = saveImage(DestinationImageFileName, destinationImage) if err != nil { log.Fatal(err) } }
18. Obsah následující části seriálu
V další části seriálu o programovacím jazyce Go nejdříve dokončíme popis knihovny Gift a posléze si ukážeme využití vybraných „vektorových“ instrukcí pro urychlení operací s rastrovými obrázky (ovšem i s jinými typy dat).
19. Repositář s demonstračními příklady
Zdrojové kódy všech dnes použitých demonstračních příkladů byly uloženy do Git repositáře, který je dostupný na adrese https://github.com/tisnik/go-root (stále na GitHubu :-). V případě, že nebudete chtít klonovat celý repositář (ten je ovšem – alespoň prozatím – velmi malý, dnes má přibližně šest až sedm megabajtů), 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
- Repositář projektu Gift
https://github.com/disintegration/gift - Dokumentace k projektu Gift
https://godoc.org/github.com/disintegration/gift - Online x86 / x64 Assembler and Disassembler
https://defuse.ca/online-x86-assembler.htm#disassembly2 - The Design of the Go Assembler
https://talks.golang.org/2016/asm.slide#1 - A Quick Guide to Go's Assembler
https://golang.org/doc/asm - AssemblyPolicy
https://github.com/golang/go/wiki/AssemblyPolicy - Geohash in Golang Assembly
https://mmcloughlin.com/posts/geohash-assembly - Command objdump
https://golang.org/cmd/objdump/ - Assembly
https://goroutines.com/asm - Go & Assembly
http://www.doxsey.net/blog/go-and-assembly - A Foray Into Go Assembly Programming
https://blog.sgmansfield.com/2017/04/a-foray-into-go-assembly-programming/ - Golang Capturing log.Println And fmt.Println Output
https://medium.com/@hau12a1/golang-capturing-log-println-and-fmt-println-output-770209c791b4 - Stránka projektu plotly
https://plot.ly/ - Plotly JavaScript Open Source Graphing Library
https://plot.ly/javascript/ - Domain coloring
https://en.wikipedia.org/wiki/Domain_coloring - Michael Fogleman's projects
https://www.michaelfogleman.com/projects/tagged/graphics/ - Color Graphs of Complex Functions
https://web.archive.org/web/20120511021419/http://w.american.edu/cas/mathstat/lcrone/ComplexPlot.html - A Gallery of Complex Functions
http://wismuth.com/complex/gallery.html - package glot
https://godoc.org/github.com/Arafatk/glot - Gnuplotting: Output terminals
http://www.gnuplotting.org/output-terminals/ - Introducing Glot the plotting library for Golang
https://medium.com/@Arafat./introducing-glot-the-plotting-library-for-golang-3133399948a1 - Introducing Glot the plotting library for Golang
https://blog.gopheracademy.com/advent-2018/introducing-glot/ - Glot is a plotting library for Golang built on top of gnuplot
https://github.com/Arafatk/glot - Example plots (gonum/plot)
https://github.com/gonum/plot/wiki/Example-plots - A repository for plotting and visualizing data (gonum/plot)
https://github.com/gonum/plot - golang library to make https://chartjs.org/ plots
https://github.com/brentp/go-chartjs - Gorilla REPL: interaktivní prostředí pro programovací jazyk Clojure
https://www.root.cz/clanky/gorilla-repl-interaktivni-prostredi-pro-programovaci-jazyk-clojure/ - The Gonum Numerical Computing Package
https://www.gonum.org/post/introtogonum/ - Gomacro na GitHubu
https://github.com/cosmos72/gomacro - gophernotes – Use Go in Jupyter notebooks and nteract
https://github.com/gopherdata/gophernotes - gonum
https://github.com/gonum - go-gota/gota – DataFrames and data wrangling in Go (Golang)
https://porter.io/github.com/go-gota/gota - A repository for plotting and visualizing data
https://github.com/gonum/plot - Gonum Numerical Packages
https://www.gonum.org/ - Stránky projektu MinIO
https://min.io/ - MinIO Quickstart Guide
https://docs.min.io/docs/minio-quickstart-guide.html - MinIO Go Client API Reference
https://docs.min.io/docs/golang-client-api-reference - MinIO Python Client API Reference
https://docs.min.io/docs/python-client-api-reference.html - Performance at Scale: MinIO Pushes Past 1.4 terabits per second with 256 NVMe Drives
https://blog.min.io/performance-at-scale-minio-pushes-past-1–3-terabits-per-second-with-256-nvme-drives/ - Benchmarking MinIO vs. AWS S3 for Apache Spark
https://blog.min.io/benchmarking-apache-spark-vs-aws-s3/ - MinIO Client Quickstart Guide
https://docs.min.io/docs/minio-client-quickstart-guide.html - Analýza kvality zdrojových kódů Minia
https://goreportcard.com/report/github.com/minio/minio - This is MinIO
https://www.youtube.com/watch?v=vF0lQh0XOCs - Running MinIO Standalone
https://www.youtube.com/watch?v=dIQsPCHvHoM - „Amazon S3 Compatible Storage in Kubernetes“ – Rob Girard, Principal Tech Marketing Engineer, Minio
https://www.youtube.com/watch?v=wlpn8K0jJ4U - Ginkgo
http://onsi.github.io/ginkgo/ - Gomega
https://onsi.github.io/gomega/ - Ginkgo's Preferred Matcher Library na GitHubu
https://github.com/onsi/gomega/ - Provided Matchers
http://onsi.github.io/gomega/#provided-matchers - Dokumentace k balíčku goexpect
https://godoc.org/github.com/google/goexpect - Balíček goexpect
https://github.com/google/goexpect - Balíček go-expect
https://github.com/Netflix/go-expect - Balíček gexpect
https://github.com/ThomasRooney/gexpect - Expect (originál naprogramovaný v TCL)
https://core.tcl-lang.org/expect/index - Expect (Wikipedia)
https://en.wikipedia.org/wiki/Expect - Pexpect
https://pexpect.readthedocs.io/en/stable/ - Golang SSH Client: Multiple Commands, Crypto & Goexpect Examples
http://networkbit.ch/golang-ssh-client/ - goblin na GitHubu
https://github.com/franela/goblin - Mocha framework
https://mochajs.org/ - frisby na GitHubu
https://github.com/verdverm/frisby - package frisby
https://godoc.org/github.com/verdverm/frisby - Frisby alternatives and similar packages (generováno)
https://go.libhunt.com/frisby-alternatives - Cucumber for golang
https://github.com/DATA-DOG/godog - How to Use Godog for Behavior-driven Development in Go
https://semaphoreci.com/community/tutorials/how-to-use-godog-for-behavior-driven-development-in-go - Comparative Analysis Of GoLang Testing Frameworks
https://www.slideshare.net/DushyantBhalgami/comparative-analysis-of-golang-testing-frameworks - A Quick Guide to Testing in Golang
https://caitiem.com/2016/08/18/a-quick-guide-to-testing-in-golang/ - Tom's Obvious, Minimal Language.
https://github.com/toml-lang/toml - xml.org
http://www.xml.org/ - Soubory .properties
https://en.wikipedia.org/wiki/.properties - Soubory INI
https://en.wikipedia.org/wiki/INI_file - JSON to YAML
https://www.json2yaml.com/ - Data Format Converter
https://toolkit.site/format.html - Viper na GitHubu
https://github.com/spf13/viper - GoDotEnv na GitHubu
https://github.com/joho/godotenv - The fantastic ORM library for Golang
http://gorm.io/ - Dokumentace k balíčku gorilla/mux
https://godoc.org/github.com/gorilla/mux - Gorilla web toolkitk
http://www.gorillatoolkit.org/ - Metric types
https://prometheus.io/docs/concepts/metric_types/ - Histograms with Prometheus: A Tale of Woe
http://linuxczar.net/blog/2017/06/15/prometheus-histogram-2/ - Why are Prometheus histograms cumulative?
https://www.robustperception.io/why-are-prometheus-histograms-cumulative - Histograms and summaries
https://prometheus.io/docs/practices/histograms/ - Instrumenting Golang server in 5 min
https://medium.com/@gsisimogang/instrumenting-golang-server-in-5-min-c1c32489add3 - Semantic Import Versioning in Go
https://www.aaronzhuo.com/semantic-import-versioning-in-go/ - Sémantické verzování
https://semver.org/ - Getting started with Go modules
https://medium.com/@fonseka.live/getting-started-with-go-modules-b3dac652066d - Create projects independent of $GOPATH using Go Modules
https://medium.com/mindorks/create-projects-independent-of-gopath-using-go-modules-802260cdfb51o - Anatomy of Modules in Go
https://medium.com/rungo/anatomy-of-modules-in-go-c8274d215c16 - Modules
https://github.com/golang/go/wiki/Modules - Go Modules Tutorial
https://tutorialedge.net/golang/go-modules-tutorial/ - Module support
https://golang.org/cmd/go/#hdr-Module_support - Go Lang: Memory Management and Garbage Collection
https://vikash1976.wordpress.com/2017/03/26/go-lang-memory-management-and-garbage-collection/ - Golang Internals, Part 4: Object Files and Function Metadata
https://blog.altoros.com/golang-part-4-object-files-and-function-metadata.html - What is REPL?
https://pythonprogramminglanguage.com/repl/ - What is a REPL?
https://codewith.mu/en/tutorials/1.0/repl - Programming at the REPL: Introduction
https://clojure.org/guides/repl/introduction - What is REPL? (Quora)
https://www.quora.com/What-is-REPL - Gorilla REPL: interaktivní prostředí pro programovací jazyk Clojure
https://www.root.cz/clanky/gorilla-repl-interaktivni-prostredi-pro-programovaci-jazyk-clojure/ - Read-eval-print loop (Wikipedia)
https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop - Vim as a Go (Golang) IDE using LSP and vim-go
https://octetz.com/posts/vim-as-go-ide - gopls
https://github.com/golang/go/wiki/gopls - IDE Integration Guide
https://github.com/stamblerre/gocode/blob/master/docs/IDE_integration.md - How to instrument Go code with custom expvar metrics
https://sysdig.com/blog/golang-expvar-custom-metrics/ - Golang expvar metricset (Metricbeat Reference)
https://www.elastic.co/guide/en/beats/metricbeat/7.x/metricbeat-metricset-golang-expvar.html - Package expvar
https://golang.org/pkg/expvar/#NewInt - Java Platform Debugger Architecture: Overview
https://docs.oracle.com/en/java/javase/11/docs/specs/jpda/jpda.html - The JVM Tool Interface (JVM TI): How VM Agents Work
https://www.oracle.com/technetwork/articles/javase/index-140680.html - JVM Tool Interface Version 11.0
https://docs.oracle.com/en/java/javase/11/docs/specs/jvmti.html - Creating a Debugging and Profiling Agent with JVMTI
http://www.oracle.com/technetwork/articles/javase/jvmti-136367.html - JVM TI (Wikipedia)
http://en.wikipedia.org/wiki/JVM_TI - IBM JVMTI extensions
http://publib.boulder.ibm.com/infocenter/realtime/v2r0/index.jsp?topic=%2Fcom.ibm.softrt.doc%2Fdiag%2Ftools%2Fjvmti_extensions.html - Go & cgo: integrating existing C code with Go
http://akrennmair.github.io/golang-cgo-slides/#1 - Using cgo to call C code from within Go code
https://wenzr.wordpress.com/2018/06/07/using-cgo-to-call-c-code-from-within-go-code/ - Package trace
https://golang.org/pkg/runtime/trace/ - Introducing HTTP Tracing
https://blog.golang.org/http-tracing - Command trace
https://golang.org/cmd/trace/ - A StreamLike, Immutable, Lazy Loading and smart Golang Library to deal with slices
https://github.com/wesovilabs/koazee - Funkce vyššího řádu v knihovně Underscore
https://www.root.cz/clanky/funkce-vyssiho-radu-v-knihovne-underscore/ - Delve: a debugger for the Go programming language.
https://github.com/go-delve/delve - Příkazy debuggeru Delve
https://github.com/go-delve/delve/tree/master/Documentation/cli - Debuggery a jejich nadstavby v Linuxu
http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu/ - Debuggery a jejich nadstavby v Linuxu (2. část)
http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-2-cast/ - Debuggery a jejich nadstavby v Linuxu (3): Nemiver
http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-3-nemiver/ - Debuggery a jejich nadstavby v Linuxu (4): KDbg
http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-4-kdbg/ - Debuggery a jejich nadstavby v Linuxu (5): ladění aplikací v editorech Emacs a Vim
http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-5-ladeni-aplikaci-v-editorech-emacs-a-vim/ - Debugging Go Code with GDB
https://golang.org/doc/gdb - Debugging Go (golang) programs with gdb
https://thornydev.blogspot.com/2014/01/debugging-go-golang-programs-with-gdb.html - GDB – Dokumentace
http://sourceware.org/gdb/current/onlinedocs/gdb/ - GDB – Supported Languages
http://sourceware.org/gdb/current/onlinedocs/gdb/Supported-Languages.html#Supported-Languages - GNU Debugger (Wikipedia)
https://en.wikipedia.org/wiki/GNU_Debugger - The LLDB Debugger
http://lldb.llvm.org/ - Debugger (Wikipedia)
https://en.wikipedia.org/wiki/Debugger - 13 Linux Debuggers for C++ Reviewed
http://www.drdobbs.com/testing/13-linux-debuggers-for-c-reviewed/240156817 - Go is on a Trajectory to Become the Next Enterprise Programming Language
https://hackernoon.com/go-is-on-a-trajectory-to-become-the-next-enterprise-programming-language-3b75d70544e - Go Proverbs: Simple, Poetic, Pithy
https://go-proverbs.github.io/ - Handling Sparse Files on Linux
https://www.systutorials.com/136652/handling-sparse-files-on-linux/ - Gzip (Wikipedia)
https://en.wikipedia.org/wiki/Gzip - Deflate
https://en.wikipedia.org/wiki/DEFLATE - 10 tools written in Go that every developer needs to know
https://gustavohenrique.net/en/2019/01/10-tools-written-in-go-that-every-dev-needs-to-know/ - Hexadecimální prohlížeče a editory s textovým uživatelským rozhraním
https://www.root.cz/clanky/hexadecimalni-prohlizece-a-editory-s-textovym-uzivatelskym-rozhranim/ - Hex dump
https://en.wikipedia.org/wiki/Hex_dump - Rozhraní io.ByteReader
https://golang.org/pkg/io/#ByteReader - Rozhraní io.RuneReader
https://golang.org/pkg/io/#RuneReader - Rozhraní io.ByteScanner
https://golang.org/pkg/io/#ByteScanner - Rozhraní io.RuneScanner
https://golang.org/pkg/io/#RuneScanner - Rozhraní io.Closer
https://golang.org/pkg/io/#Closer - Rozhraní io.Reader
https://golang.org/pkg/io/#Reader - Rozhraní io.Writer
https://golang.org/pkg/io/#Writer - Typ Strings.Reader
https://golang.org/pkg/strings/#Reader - VACUUM (SQL)
https://www.sqlite.org/lang_vacuum.html - VACUUM (Postgres)
https://www.postgresql.org/docs/8.4/sql-vacuum.html - go-cron
https://github.com/rk/go-cron - gocron
https://github.com/jasonlvhit/gocron - clockwork
https://github.com/whiteShtef/clockwork - clockwerk
https://github.com/onatm/clockwerk - JobRunner
https://github.com/bamzi/jobrunner - Rethinking Cron
https://adam.herokuapp.com/past/2010/4/13/rethinking_cron/ - In the Beginning was the Command Line
https://web.archive.org/web/20180218045352/http://www.cryptonomicon.com/beginning.html - repl.it (REPL pro různé jazyky)
https://repl.it/languages - GOCUI – Go Console User Interface (celé uživatelské prostředí, nejenom input box)
https://github.com/jroimartin/gocui - Read–eval–print loop
https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop - go-prompt
https://github.com/c-bata/go-prompt - readline
https://github.com/chzyer/readline - A pure golang implementation for GNU-Readline kind library
https://golangexample.com/a-pure-golang-implementation-for-gnu-readline-kind-library/ - go-readline
https://github.com/fiorix/go-readline - 4 Python libraries for building great command-line user interfaces
https://opensource.com/article/17/5/4-practical-python-libraries - prompt_toolkit 2.0.3 na PyPi
https://pypi.org/project/prompt_toolkit/ - python-prompt-toolkit na GitHubu
https://github.com/jonathanslenders/python-prompt-toolkit - The GNU Readline Library
https://tiswww.case.edu/php/chet/readline/rltop.html - GNU Readline (Wikipedia)
https://en.wikipedia.org/wiki/GNU_Readline - readline — GNU readline interface (Python 3.x)
https://docs.python.org/3/library/readline.html - readline — GNU readline interface (Python 2.x)
https://docs.python.org/2/library/readline.html - GNU Readline Library – command line editing
https://tiswww.cwru.edu/php/chet/readline/readline.html - gnureadline 6.3.8 na PyPi
https://pypi.org/project/gnureadline/ - Editline Library (libedit)
http://thrysoee.dk/editline/ - Comparing Python Command-Line Parsing Libraries – Argparse, Docopt, and Click
https://realpython.com/comparing-python-command-line-parsing-libraries-argparse-docopt-click/ - libedit or editline
http://www.cs.utah.edu/~bigler/code/libedit.html - WinEditLine
http://mingweditline.sourceforge.net/ - rlcompleter — Completion function for GNU readline
https://docs.python.org/3/library/rlcompleter.html - rlwrap na GitHubu
https://github.com/hanslub42/rlwrap - rlwrap(1) – Linux man page
https://linux.die.net/man/1/rlwrap - readline(3) – Linux man page
https://linux.die.net/man/3/readline - history(3) – Linux man page
https://linux.die.net/man/3/history - Dokumentace k balíčku oglematchers
https://godoc.org/github.com/jacobsa/oglematchers - Balíček oglematchers
https://github.com/jacobsa/oglematchers - Dokumentace k balíčku ogletest
https://godoc.org/github.com/jacobsa/ogletest - Balíček ogletest
https://github.com/jacobsa/ogletest - Dokumentace k balíčku assert
https://godoc.org/github.com/stretchr/testify/assert - Testify – Thou Shalt Write Tests
https://github.com/stretchr/testify/ - package testing
https://golang.org/pkg/testing/ - Golang basics – writing unit tests
https://blog.alexellis.io/golang-writing-unit-tests/ - An Introduction to Programming in Go / Testing
https://www.golang-book.com/books/intro/12 - An Introduction to Testing in Go
https://tutorialedge.net/golang/intro-testing-in-go/ - Advanced Go Testing Tutorial
https://tutorialedge.net/golang/advanced-go-testing-tutorial/ - GoConvey
http://goconvey.co/ - Testing Techniques
https://talks.golang.org/2014/testing.slide - 5 simple tips and tricks for writing unit tests in #golang
https://medium.com/@matryer/5-simple-tips-and-tricks-for-writing-unit-tests-in-golang-619653f90742 - Afinní transformace
https://cs.wikibooks.org/wiki/Geometrie/Afinn%C3%AD_transformace_sou%C5%99adnic - package gg
https://godoc.org/github.com/fogleman/gg - Generate an animated GIF with Golang
http://tech.nitoyon.com/en/blog/2016/01/07/go-animated-gif-gen/ - Generate an image programmatically with Golang
http://tech.nitoyon.com/en/blog/2015/12/31/go-image-gen/ - The Go image package
https://blog.golang.org/go-image-package - Balíček draw2D: 2D rendering for different output (raster, pdf, svg)
https://github.com/llgcode/draw2d - Draw a rectangle in Golang?
https://stackoverflow.com/questions/28992396/draw-a-rectangle-in-golang - YAML
https://yaml.org/ - edn
https://github.com/edn-format/edn - Smile
https://github.com/FasterXML/smile-format-specification - Protocol-Buffers
https://developers.google.com/protocol-buffers/ - Marshalling (computer science)
https://en.wikipedia.org/wiki/Marshalling_(computer_science) - Unmarshalling
https://en.wikipedia.org/wiki/Unmarshalling - Introducing JSON
http://json.org/ - Package json
https://golang.org/pkg/encoding/json/ - The Go Blog: JSON and Go
https://blog.golang.org/json-and-go - Go by Example: JSON
https://gobyexample.com/json - Writing Web Applications
https://golang.org/doc/articles/wiki/ - Golang Web Apps
https://www.reinbach.com/blog/golang-webapps-1/ - Build web application with Golang
https://legacy.gitbook.com/book/astaxie/build-web-application-with-golang/details - Golang Templates – Golang Web Pages
https://www.youtube.com/watch?v=TkNIETmF-RU - Simple Golang HTTPS/TLS Examples
https://github.com/denji/golang-tls - Playing with images in HTTP response in golang
https://www.sanarias.com/blog/1214PlayingwithimagesinHTTPresponseingolang - MIME Types List
https://www.freeformatter.com/mime-types-list.html - Go Mutex Tutorial
https://tutorialedge.net/golang/go-mutex-tutorial/ - Creating A Simple Web Server With Golang
https://tutorialedge.net/golang/creating-simple-web-server-with-golang/ - Building a Web Server in Go
https://thenewstack.io/building-a-web-server-in-go/ - How big is the pipe buffer?
https://unix.stackexchange.com/questions/11946/how-big-is-the-pipe-buffer - How to turn off buffering of stdout in C
https://stackoverflow.com/questions/7876660/how-to-turn-off-buffering-of-stdout-in-c - setbuf(3) – Linux man page
https://linux.die.net/man/3/setbuf - setvbuf(3) – Linux man page (stejný obsah jako předchozí stránka)
https://linux.die.net/man/3/setvbuf - Select waits on a group of channels
https://yourbasic.org/golang/select-explained/ - Rob Pike: Simplicity is Complicated (video)
http://www.golang.to/posts/dotgo-2015-rob-pike-simplicity-is-complicated-youtube-16893 - Algorithms to Go
https://yourbasic.org/ - Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů
https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu/ - Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů: vlastní filtry a lexery
https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu-vlastni-filtry-a-lexery/ - Go Defer Simplified with Practical Visuals
https://blog.learngoprogramming.com/golang-defer-simplified-77d3b2b817ff - 5 More Gotchas of Defer in Go — Part II
https://blog.learngoprogramming.com/5-gotchas-of-defer-in-go-golang-part-ii-cc550f6ad9aa - The Go Blog: Defer, Panic, and Recover
https://blog.golang.org/defer-panic-and-recover - The defer keyword in Swift 2: try/finally done right
https://www.hackingwithswift.com/new-syntax-swift-2-defer - Swift Defer Statement
https://andybargh.com/swift-defer-statement/ - Modulo operation (Wikipedia)
https://en.wikipedia.org/wiki/Modulo_operation - Node.js vs Golang: Battle of the Next-Gen Languages
https://www.hostingadvice.com/blog/nodejs-vs-golang/ - The Go Programming Language (home page)
https://golang.org/ - GoDoc
https://godoc.org/ - Go (programming language), Wikipedia
https://en.wikipedia.org/wiki/Go_(programming_language) - Go Books (kniha o jazyku Go)
https://github.com/dariubs/GoBooks - The Go Programming Language Specification
https://golang.org/ref/spec - Go: the Good, the Bad and the Ugly
https://bluxte.net/musings/2018/04/10/go-good-bad-ugly/ - Package builtin
https://golang.org/pkg/builtin/ - Package fmt
https://golang.org/pkg/fmt/ - The Little Go Book (další kniha)
https://github.com/dariubs/GoBooks - The Go Programming Language by Brian W. Kernighan, Alan A. A. Donovan
https://www.safaribooksonline.com/library/view/the-go-programming/9780134190570/ebook_split010.html - Learning Go
https://www.miek.nl/go/ - Go Bootcamp
http://www.golangbootcamp.com/ - Programming in Go: Creating Applications for the 21st Century (další kniha o jazyku Go)
http://www.informit.com/store/programming-in-go-creating-applications-for-the-21st-9780321774637 - Introducing Go (Build Reliable, Scalable Programs)
http://shop.oreilly.com/product/0636920046516.do - Learning Go Programming
https://www.packtpub.com/application-development/learning-go-programming - The Go Blog
https://blog.golang.org/ - Getting to Go: The Journey of Go's Garbage Collector
https://blog.golang.org/ismmkeynote - Go (programovací jazyk, Wikipedia)
https://cs.wikipedia.org/wiki/Go_(programovac%C3%AD_jazyk) - Rychle, rychleji až úplně nejrychleji s jazykem Go
https://www.root.cz/clanky/rychle-rychleji-az-uplne-nejrychleji-s-jazykem-go/ - Installing Go on the Raspberry Pi
https://dave.cheney.net/2012/09/25/installing-go-on-the-raspberry-pi - How the Go runtime implements maps efficiently (without generics)
https://dave.cheney.net/2018/05/29/how-the-go-runtime-implements-maps-efficiently-without-generics - Niečo málo o Go – Golang (slovensky)
http://golangsk.logdown.com/ - How Many Go Developers Are There?
https://research.swtch.com/gophercount - Most Popular Technologies (Stack Overflow Survery 2018)
https://insights.stackoverflow.com/survey/2018/#most-popular-technologies - Most Popular Technologies (Stack Overflow Survery 2017)
https://insights.stackoverflow.com/survey/2017#technology - JavaScript vs. Golang for IoT: Is Gopher Winning?
https://www.iotforall.com/javascript-vs-golang-iot/ - The Go Programming Language: Release History
https://golang.org/doc/devel/release.html - Go 1.11 Release Notes
https://golang.org/doc/go1.11 - Go 1.10 Release Notes
https://golang.org/doc/go1.10 - Go 1.9 Release Notes (tato verze je stále používána)
https://golang.org/doc/go1.9 - Go 1.8 Release Notes (i tato verze je stále používána)
https://golang.org/doc/go1.8 - Go on Fedora
https://developer.fedoraproject.org/tech/languages/go/go-installation.html - Writing Go programs
https://developer.fedoraproject.org/tech/languages/go/go-programs.html - The GOPATH environment variable
https://tip.golang.org/doc/code.html#GOPATH - Command gofmt
https://tip.golang.org/cmd/gofmt/ - The Go Blog: go fmt your code
https://blog.golang.org/go-fmt-your-code - C? Go? Cgo!
https://blog.golang.org/c-go-cgo - Spaces vs. Tabs: A 20-Year Debate Reignited by Google’s Golang
https://thenewstack.io/spaces-vs-tabs-a-20-year-debate-and-now-this-what-the-hell-is-wrong-with-go/ - 400,000 GitHub repositories, 1 billion files, 14 terabytes of code: Spaces or Tabs?
https://medium.com/@hoffa/400–000-github-repositories-1-billion-files-14-terabytes-of-code-spaces-or-tabs-7cfe0b5dd7fd - Gofmt No Longer Allows Spaces. Tabs Only
https://news.ycombinator.com/item?id=7914523 - Why does Go „go fmt“ uses tabs instead of whitespaces?
https://www.quora.com/Why-does-Go-go-fmt-uses-tabs-instead-of-whitespaces - Interactive: The Top Programming Languages 2018
https://spectrum.ieee.org/static/interactive-the-top-programming-languages-2018 - Go vs. Python
https://www.peterbe.com/plog/govspy - PackageManagementTools
https://github.com/golang/go/wiki/PackageManagementTools - A Tour of Go: Type inference
https://tour.golang.org/basics/14 - Go Slices: usage and internals
https://blog.golang.org/go-slices-usage-and-internals - Go by Example: Slices
https://gobyexample.com/slices - What is the point of slice type in Go?
https://stackoverflow.com/questions/2098874/what-is-the-point-of-slice-type-in-go - The curious case of Golang array and slices
https://medium.com/@hackintoshrao/the-curious-case-of-golang-array-and-slices-2565491d4335 - Introduction to Slices in Golang
https://www.callicoder.com/golang-slices/ - Golang: Understanding ‚null‘ and nil
https://newfivefour.com/golang-null-nil.html - What does nil mean in golang?
https://stackoverflow.com/questions/35983118/what-does-nil-mean-in-golang - nils In Go
https://go101.org/article/nil.html - Go slices are not dynamic arrays
https://appliedgo.net/slices/ - Go-is-no-good (nelze brát doslova)
https://github.com/ksimka/go-is-not-good - Rust vs. Go
https://news.ycombinator.com/item?id=13430108 - Seriál Programovací jazyk Rust
https://www.root.cz/serialy/programovaci-jazyk-rust/ - Modern garbage collection: A look at the Go GC strategy
https://blog.plan99.net/modern-garbage-collection-911ef4f8bd8e - Go GC: Prioritizing low latency and simplicity
https://blog.golang.org/go15gc - Is Golang a good language for embedded systems?
https://www.quora.com/Is-Golang-a-good-language-for-embedded-systems - Running GoLang on an STM32 MCU. A quick tutorial.
https://www.mickmake.com/post/running-golang-on-an-mcu-a-quick-tutorial - Go, Robot, Go! Golang Powered Robotics
https://gobot.io/ - Emgo: Bare metal Go (language for programming embedded systems)
https://github.com/ziutek/emgo - UTF-8 history
https://www.cl.cam.ac.uk/~mgk25/ucs/utf-8-history.txt - Less is exponentially more
https://commandcenter.blogspot.com/2012/06/less-is-exponentially-more.html - Should I Rust, or Should I Go
https://codeburst.io/should-i-rust-or-should-i-go-59a298e00ea9 - Setting up and using gccgo
https://golang.org/doc/install/gccgo - Elastic Tabstops
http://nickgravgaard.com/elastic-tabstops/ - Strings, bytes, runes and characters in Go
https://blog.golang.org/strings - Datový typ
https://cs.wikipedia.org/wiki/Datov%C3%BD_typ - Seriál o programovacím jazyku Rust: Základní (primitivní) datové typy
https://www.root.cz/clanky/programovaci-jazyk-rust-nahrada-c-nebo-slepa-cesta/#k09 - Seriál o programovacím jazyku Rust: Vytvoření „řezu“ z pole
https://www.root.cz/clanky/prace-s-poli-v-programovacim-jazyku-rust/#k06 - Seriál o programovacím jazyku Rust: Řezy (slice) vektoru
https://www.root.cz/clanky/prace-s-vektory-v-programovacim-jazyku-rust/#k05 - Printf Format Strings
https://www.cprogramming.com/tutorial/printf-format-strings.html - Java: String.format
https://docs.oracle.com/javase/8/docs/api/java/lang/String.html#format-java.lang.String-java.lang.Object…- - Java: format string syntax
https://docs.oracle.com/javase/8/docs/api/java/util/Formatter.html#syntax - Selectors
https://golang.org/ref/spec#Selectors - Calling Go code from Python code
http://savorywatt.com/2015/09/18/calling-go-code-from-python-code/ - Go Data Structures: Interfaces
https://research.swtch.com/interfaces - How to use interfaces in Go
http://jordanorelli.com/post/32665860244/how-to-use-interfaces-in-go - Interfaces in Go (part I)
https://medium.com/golangspec/interfaces-in-go-part-i-4ae53a97479c - Part 21: Goroutines
https://golangbot.com/goroutines/ - Part 22: Channels
https://golangbot.com/channels/ - [Go] Lightweight eventbus with async compatibility for Go
https://github.com/asaskevich/EventBus - What about Trait support in Golang?
https://www.reddit.com/r/golang/comments/8mfykl/what_about_trait_support_in_golang/ - Don't Get Bitten by Pointer vs Non-Pointer Method Receivers in Golang
https://nathanleclaire.com/blog/2014/08/09/dont-get-bitten-by-pointer-vs-non-pointer-method-receivers-in-golang/ - Control Flow
https://en.wikipedia.org/wiki/Control_flow - Structured programming
https://en.wikipedia.org/wiki/Structured_programming - Control Structures
https://www.golang-book.com/books/intro/5 - Control structures – Go if else statement
http://golangtutorials.blogspot.com/2011/06/control-structures-if-else-statement.html - Control structures – Go switch case statement
http://golangtutorials.blogspot.com/2011/06/control-structures-go-switch-case.html - Control structures – Go for loop, break, continue, range
http://golangtutorials.blogspot.com/2011/06/control-structures-go-for-loop-break.html - Goroutine IDs
https://blog.sgmansfield.com/2015/12/goroutine-ids/ - Different ways to pass channels as arguments in function in go (golang)
https://stackoverflow.com/questions/24868859/different-ways-to-pass-channels-as-arguments-in-function-in-go-golang - justforfunc #22: using the Go execution tracer
https://www.youtube.com/watch?v=ySy3sR1LFCQ - Single Function Exit Point
http://wiki.c2.com/?SingleFunctionExitPoint - Entry point
https://en.wikipedia.org/wiki/Entry_point - Why does Go have a GOTO statement?!
https://www.reddit.com/r/golang/comments/kag5q/why_does_go_have_a_goto_statement/ - Effective Go
https://golang.org/doc/effective_go.html - GoClipse: an Eclipse IDE for the Go programming language
http://goclipse.github.io/ - GoClipse Installation
https://github.com/GoClipse/goclipse/blob/latest/documentation/Installation.md#installation - The zero value of a slice is not nil
https://stackoverflow.com/questions/30806931/the-zero-value-of-a-slice-is-not-nil - Go-tcha: When nil != nil
https://dev.to/pauljlucas/go-tcha-when-nil–nil-hic - Nils in Go
https://www.doxsey.net/blog/nils-in-go