Co jsou zač ty „podivné“ grafické procesory, které máme všichni ve svých počítačích? Máme se o ně zajímat? Zabýváme-li se úlohami strojového vidění, je dobré vědět, co jsou grafické procesory (GPU – Graphics Processing Unit) zač, kde mohou vydatně pomoci a kde je naopak jejich použití problematické.
Grafický procesor se stal běžnou a již samozřejmou součástí většiny počítačů. Současný počítač tak po hardwarové stránce představuje heterogenní výpočetní prostředí, ze kterého ovšem velká většina programového vybavení stále používá jen jeho, v určitém směru méně výkonnou část. Právě algoritmy strojového vidění jsou velmi náročné na výpočetní výkon a často je lze také velmi dobře paralelizovat. Proto plné využití možností hardwaru počítačů se může pro aplikace řešící úlohy strojového vidění a vizuální inspekce stát značnou konkurenční výhodou.
Grafické procesory mohou provádět mnoho operací s daty, které jsou tradičně realizovány pomocí CPU, a některé z těchto operací mohou velmi významně urychlit. Zatímco CPU mají jen několik výpočetních jader, GPU jich mají několik set. Grafické procesory byly původně navrženy pouze pro zrychlení počítačové grafiky a pro práci s grafickými daty. Přestože dnešní GPU mohou pracovat i s obecnými daty, jejich funkčnost stále zůstává hodně omezena a zdaleka ne všechny typy úloh je vhodné řešit právě jimi.
Na úvod si připomeňme základní koncepce architektury GPU v grafických adaptérech počítačů. Konec článku bude zaměřen na využití programovatelných GPU se systémy strojového vidění VisionLab.
Vnitřní architektura GPU a shadery
Vnitřní architektury GPU hlavních výrobců NVIDIA, AMD a Intel se navzájem hodně liší. Ve všech případech jde o velmi složité obvody. Jen jako ilustrace složitosti a výkonnosti moderních GPU je v tab. 1 uvedeno několik parametrů GPU GeForce GTX 680 (NVIDIA). Při pohledu na tyto působivé parametry je zřejmé, jak velká škoda by bylo nechat tento potenciál ležet ladem. Zmíněný procesor umožňuje ovlivňovat jednotlivé stupně řetězce zpracování grafiky speciálními programy, tzv. shadery, a je dalším důkazem, že vývoj v této oblasti směřuje k použitelnosti GPU pro obecnější výpočty než jen pro zrychlení grafiky. Tento trend začal u firmy NVIDIA uvedením na trh přelomového procesoru G80 v roce 2006. V uniformní architektuře, jež je procesoru GTX 680 vlastní, mohou všechny procesory v GPU podle aktuálních potřeb vykonávat vertexové shadery (operace s vrcholy, např. simulace pohybu vodní hladiny), fragmentové shadery (operace s obrazovými body (pixely), např. přidávání textur na povrch objektu) nebo geometrické shadery (přidávání nebo ubírání vrcholů, např. generování travního porostu na povrchu objektu).
Cesta k současné uniformní architektuře GPU NVIDIA nebyla krátká. S využitím grafických akcelerátorů pro urychlení výpočtů odborníci experimentovali již v době, kdy tyto čipy měly pouze pevně danou funkci a nebylo je možné programovat. Využívány byly hardwarové interpolátory a rasterizéry, filtry textur, míchání vektorů, operace s vyrovnávacími paměťmi accumulator buffer a stencil buffer atd. Tyto postupy od programátorů vyžadovaly hlubokou znalost principů fungování grafického řetězce a značnou kreativitu. Dalším krokem ve vývoji byly programovatelné fragmentové procesory s již standardizovanými pseudoassemblery, které se staly součástí ovladačů grafiky. Hardware i instrukční soubory jednotlivých GPU byly a doposud jsou odlišné, a právě překladače v grafických ovladačích umožnily přenositelnost kódů shaderů1). Tento způsob programování GPU byl pro většinu programátorů stále zcela nestravitelný a používala jej jen hrstka nadšenců.
Programovací jazyky vyšší úrovně a programovací nástroje
Důležitým momentem byla možnost vytvářet shadery pomocí programovacích jazyků vyšší úrovně. Všechny tři programovací jazyky, univerzální Cg (NVIDIA pro DirectX i OpenGL), HLSL (High-Level Shading Language; vyvinutý pro Microsoft DirectX) a GLSL (OpenGL Shading Language; dodnes běžný jazyk pro programování shaderů pro OpenGL), mají podobné schopnosti a jejich zápis se velmi podobá jazyku C. Tyto jazyky jsou již velmi dobře použitelné pro programování obecných výpočtů, to znamená, že jsou jim přístupné veškeré vlastnosti současných GPU, ale stále ještě z programátorů nesnímají nutnost znát vlastnosti grafického řetězce a přistupovat ke GPU prostřednictvím grafického API. Původní základní princip GPU, totiž vytváření grafických „obrázků“, je i v jazycích vyšší úrovně stále viditelný.
V současnosti existuje mnoho softwarových prostředí a nástrojů, které programátora izolují od této původní podstaty GPU a předkládají mu obecně přijatelnější programovací model. Tyto nástroje, k nimž patří např. Sh (Rapid Mind), BrookGPU, Accelerator (Microsoft), PeakStream (Google), Ashli (nadstavba nad Cg, HLSL a GLSL), CTM (AMD), CUDA (NVIDIA), ArBB (Intel) nebo OpenCL, více či méně obcházejí grafické ovladače, umožňují pracovat s pamětí v podobě proměnných a polí jazyka C, a někdy dokonce i ukazatelů (CUDA), a především zajišťují upload vstupních dat do textur v grafické paměti, download výsledků z frame bufferu grafické paměti a just-in-time kompilaci a optimalizaci kódu shaderů. Vstupní i výstupní data mají obvykle podobu polí vektorů. Objevují se systémy, kde rozhraním na GPU je virtuální stroj, který zajistí přenositelnost kódu. V této oblasti je situace stále velmi nepřehledná, probíhá zde bouřlivý vývoj a chybějí silné a hlavně dlouhodobě stabilní standardy.
Naštěstí v oboru strojového vidění uživatelé převážně pracují s obrazovými daty a tradiční podoba grafického řetězce (obr. 1) jim většinou velmi dobře vyhovuje.
Charakteristické znaky výpočtů v GPU
Při obecných výpočtech pracuje GPU velmi podobně jako při zpracování grafiky, pouze místo obrazových dat se pracuje se vstupními a výstupními bloky obecných dat a shadery jsou do jisté míry obecnými paralelními vlákny. Obraz z kamery vstupuje do GPU jako objekt textury, který musí být na začátku přenesen ze systémové paměti počítače do grafické paměti, která je přístupná pro GPU. Výsledný obrazový buffer nakonec také musí být často přenesen nazpět do systémové paměti počítače. Rovněž bloky obecných dat musí být takto přenášeny mezi systémovou a grafickou pamětí. Tento princip může být pro programátora částečně zakryt programovým rozhraním vývojového prostředí, limity z něj plynoucí však překonat nelze.
Právě přenosy velkých objemů dat mezi systémovou a grafickou pamětí (grafické adaptéry jsou obvykle připojeny prostřednictvím sběrnice PCI-Express) mohou proces citelně zpomalovat a stát se tím rozhodujícím problémem v neprospěch využití GPU.
Úloha vhodná pro GPU také musí mít několik charakteristických znaků. CPU má jen několik jader, ale každé jádro může pracovat na zcela odlišné úloze. GPU má sice velmi mnoho výpočetních jader, ale zjednodušeně řečeno, všechna jádra musí, byť s různými vstupními daty, zpracovávat tutéž úlohu. Výhody takovéto masivní paralelizace se více projeví v úlohách s vysokou výpočetní intenzitou, velkými objemy paralelně zpracovávaných dat a s minimálními závislostmi mezi jednotlivými daty. Výsledek každého paralelního výpočtu v GPU závisí jen na vstupních datech, a nikoliv na výsledcích výpočtů ostatních paralelních procesů. Jednotlivá výpočetní jádra se tedy v průběhu výpočtů nemohou ovlivňovat, pouze mohou číst mnoho vstupních vektorových dat z mnoha datových polí.
Prostředky GPU
Programovatelné procesory
V uniformní architektuře mohou programovatelné procesory realizovat vertexové, fragmentové a geometrické shadery. Při zpracování obrazu z kamery jsou nejčastěji využívány vertexové a fragmentové shadery.
– Vertexový shader je spuštěn pro každý vertex (připomeňme, že v terminologii počítačové grafiky je vertex geometrický bod v prostoru daný svými souřadnicemi; někdy je překládán jako „vrchol“, aby se odlišil od obrazového bodu – pixelu). Je-li vykreslován např. trojúhelník, je vertexový shader spuštěn celkem třikrát. Jeho výsledkem je grafický prvek, který je předán do rasterizéru.
– Fragmentový shader je spouštěn výstupem rasterizéru pro každý fragment, tedy pro každý obrazový bod (pixel) vykreslované scény. (Fragmentový shader je někdy označován také jako pixelový shader.)
Rasterizér
Rasterizér prostřednictvím interpolace vytváří z prvků obrazu jednotlivé obrazové body – fragmenty. Základním obrazovým prvkem je trojúhelník, určený svými třemi vrcholy – vertexy. V jeho ploše jsou rasterizérem interpolovány souřadnice textury nebo barvy fragmentů.
Textury
Textury jsou bloky grafické paměti, které mohou být čteny fragmentovými procesory. Data z texturových objektů lze pouze číst, zápis do nich fragmentovými shadery není možný.
Frame buffer
Frame buffer je blok paměti, do kterého fragmentové procesory zapisují výsledek běhu fragmentových shaderů. Data uvnitř tohoto paměťového bloku nelze fragmentovým procesorem číst, a dokonce každý z paralelně běžících fragmentových shaderů může zapsat svá data jen do jediné pozice v bufferu, té, která odpovídá jeho instanci v rámci plochy obrazu.
Omezení textur jako zdrojů dat, do kterých není možné zapisovat, a frame bufferů jako cílů, které nelze číst, klade další požadavky na kopírování bloků dat v grafické paměti během opakovaného vícenásobného spouštění shaderů pro daný frame buffer. Proto v současnosti existuje možnost přiřadit texturu jako cíl vykreslování. To umožňuje realizovat vícenásobné vykreslování maximálně efektivně.
Datové typy a programovatelnost
GPU byly navrženy pro práci s obrazovými daty a zpočátku tak byly podporovány pouze celočíselné typy dat, které popisovaly fragmenty (pixely). Pro všeobecné výpočty je důležitá schopnost moderních GPU pracovat s poli vektorů čísel s plovoucí řádovou čárkou s jednoduchou (FP32 – 4 bajty na jedno číslo s plovoucí řádovou čárkou – float) i dvojnásobnou (FP64 – 8 bajtů na jedno číslo s plovoucí řádovou čárkou – double) přesností. Vstupní a výstupní data mohou být obvykle v těchto formátech:
- 8 bitů na pixel – takto je reprezentován buď index do palety barev, nebo dva bity pro červenou, tři pro zelenou a tři pro modrou barvu,
- 16 bitů na pixel – obvykle pět bitů určuje červenou a po šesti bitech má k dispozici zelená a modrá barva,
- 24 bitů na pixel – osmibitový červený, zelený a modrý kanál,
- 32 bitů na pixel – osmibitový červený, zelený a modrý kanál a osmibitový alfa-kanál, reprezentující míru průhlednosti,
- jedno až čtyři čísla s plovoucí řádovou čárkou na pixel, kdy každé číslo má přesnost na 16, 24, 32 nebo 64 bitů (FP16, FP24, FP32 nebo FP64).
Podstatnou vlastností paralelních procesorů v GPU je také to, že pracují v jedné instrukci s celými vektory. Například při sčítání dvou čtyřrozměrných vektorů jsou současně sečteny všechny jejich příslušné složky.
Ovladače grafických adaptérů
Se začleněním překladačů programovacích jazyků vyšší úrovně se grafické ovladače dále zkomplikovaly a jejich dlouholetá pověstná chybovost se mohla rozšířit do nových rozsáhlých oblastí. V počátku vývoje GPU to opravdu programátoři neměli jednoduché. Relativně nejméně chyb vždy bývalo v ovladačích pro grafické adaptéry NVIDIA. V současné době již je v této oblasti situace celkem stabilní a software využívající GPU obvykle pracuje spolehlivě s ovladači všech hlavních výrobců.
Využití GPU v systému strojového vidění VisionLab
Algoritmy strojového vidění často pracují s velkými objemy dat a v těchto případech nebývá nikdy výpočetního výkonu nazbyt. Využití GPU všude tam, kde je to rozumné, nejenže podstatně zrychluje zpracování, ale dokonce umožňuje realizovat takové algoritmy, které by jinak nebyly v akceptovatelném čase realizovatelné.
V programovém systému Control Web existují dva základní typy kamerových virtuálních přístrojů. Kromě základního přístroje camera existuje ještě přístroj gl_camera, který pro zpracování obrazových dat z kamery i k jejich zobrazování používá vykreslovací stroj systému Control Web, jenž se vyznačuje architekturou klient-server a pracuje ve vlastním paralelním vláknu. Tento vykreslovací server využívá programovatelný GPU. V optimální situaci se tedy jedno jádro CPU stará o přípravu grafických dat a jejich transport do grafické paměti, kde jsou tato data paralelně zpracována grafickým procesorem. Přínosem takového řešení je výjimečně vysoký výkon při minimálním zatěžování vláken aplikace běžící v reálném čase. Dále je uvedeno několik příkladů.
Pokročilá adaptivní interpolace barevné mozaiky
Podstatné zlepšení kvality obrazu dovoluje číst z barevných kamer syrová data a interpolovat barevnou mozaiku až v počítači, a nikoliv již v kameře. Mnoho operací s obrazem je vzhledem k omezeným možnostem čipů kamerových řídicích jednotek velmi kompromisních. Kamera při interpolaci barevné mozaiky musí zůstat omezena na základní bilineární interpolaci, která je řešitelná celočíselnými výpočty. Obraz je pak zatížen všemi nežádoucími artefakty tohoto postupu, které plynou mimo jiné ze vzájemného prostorového posunu červeného a modrého barevného kanálu. Ve výsledku lze v blízkosti kontrastních hran pozorovat šachovnicové vzory a modročervené lemování. Pokročilé algoritmy, které produkují výrazně čistší a ostřejší obraz, musí využívat výpočty s plovoucí řádovou čárkou a obsahují více průchodů plochou obrazu. Takový algoritmus není možné v reálném čase na proudu dat z kamery řešit ani pomocí vícejádrových CPU, je však příkladem ideální úlohy pro masivní paralelizaci v GPU. Virtuální přístroj gl_camera umožňuje volit mezi několika algoritmy interpolace barevné mozaiky (obr. 2). Adaptivní interpolace je v CPU v reálném čase nerealizovatelná a použití jakéhokoliv, byť zastaralého GPU je velkým přínosem.
Korekce geometrických zkreslení obrazu
Programovatelné GPU umožňují velmi elegantně řešit problematiku změn geometrie obrazu, tzv. pixel displacement. Virtuální přístroj gl_camera takto vhodně nejen řeší kalibraci geometrických zkreslení objektivů, ale dokáže např. i korigovat perspektivní zkreslení, odstranit natočení obrazu, rozvinout obraz z povrchu válce či koule. Lze s jejich pomocí i srovnat libovolně nelineárně deformovaný obraz.
Tato úloha je rozdělena na dva klíčové problémy: zaprvé jak informaci o správných pozicích získat a zadruhé jak obraz kvalitně zpracovat v reálném čase bez neúnosné zátěže počítače.
Nejprve je nutné získat obrazovou mapu, která popisuje změny pozic všech pixelů výsledného obrazu. Mapu lze získat tak, že se do zobrazovací roviny vloží bodový rastr (obr. 3) a systém si uloží kalibrační data pro daný objektiv a konfiguraci měření. Algoritmus měření mapy objektivy pracuje v CPU s přesností na setiny pixelu. Zde tolik nezáleží na rychlosti, protože tvorba mapy proběhne jen jednou, ale je požadována nejvyšší přesnost.
Rychlost geometrických korekcí každého snímku z kamery je již kritická a tento algoritmus musí běžet v GPU. Ukazuje se, že kvalita hardwarových texturových filtrů a interpolátorů u GPU vyzkoušených pracovníky firmy Moravské přístroje není pro kvalitu obrazu požadovanou pro tuto úlohu dostatečná. Proto je tvorba výsledného obrazu řešena s vysokou subpixelovou přesností programem fragmentového shaderu, který poskytuje zaručeně kvalitní a stabilní výsledky na veškerých GPU.
Některé systémy strojového vidění se pokoušejí kalibraci geometrie obrazového pole řešit v CPU. Výsledky ale odpovídají tomu, že z důvodu šetření výpočetním výkonem musí být zavedeno množství omezení a kompromisů. Často se používá po částech bilineární interpolace obrazové mapy a také filtrace obrazu nemůže být dostatečně kvalitní. Výsledkem je nespojitý, schodovitý obraz. Kvalitní kalibrace geometrie obrazového pole je i prostředky nejvýkonnějších vícejádrových CPU realizovatelná jen problematicky a použití i slabšího, zastaralého GPU je velkým přínosem.
Obrazové filtry
Obrazové filtry na první pohled vypadají jako optimální úlohy pro řešení v GPU. Často tomu tak je, ale nemusí to platit univerzálně, a to proto, že mnoho jednoduchých kernelových filtrů nemá příliš velkou výpočetní intenzitu a současné vícejádrové CPU také nepočítají až tak zoufale pomalu. Režie přenosu obrazových dat mezi systémovou a grafickou pamětí potom může i stonásobně vyšší rychlost výpočtů jádra v GPU ve srovnání s CPU znehodnotit (obr. 4).
Situace se ale zásadně změní v případě filtrů s nutností složitějších výpočtů v plovoucí řádové čárce, jako je tomu např. u transformací barevných prostorů, řešení saturačních matic, šumových filtrů (obr. 5) atd. Potom může využití GPU zrychlit výpočty až o dva řády. Jednoznačnou úlohou pro GPU jsou i takové filtry, kdy pro výpočet každého pixelu je nutné číst mnoho pixelů z jeho okolí – příkladem mohou být algoritmy lokálního prahování obrazu (obr. 6). Pak je schopnost GPU načítat desítky gigapixelů za sekundu z textur nenahraditelná. Řešení s GPU může být i několiksetkrát rychlejší než snaha o totéž v CPU.
Ve prospěch GPU se situace dále přiklání ve všech případech, kdy je nutné určitý filtr vícekrát opakovat. Tento požadavek je charakteristický např. u morfologických filtrů, které nejsou samy o sobě výpočetně příliš náročné, ale často potřebují desítky i stovky opakovaných běhů – příkladem může být např. hledání koster objektů nebo segmentace ploch okolo objektů. Obrazová data během opakovaných běhů filtru neopouštějí grafickou paměť a jsou stále rychle přístupná pro GPU. Pro tyto algoritmy je masivní paralelizace velkým přínosem.
Transformační kódování
Příkladem transformačního kódování může být dvourozměrná Fourierova transformace – převod obrazu z prostorové (časové) do frekvenční domény (krok gpu_fast_fourier_transform_2D). Byť je zde použit algoritmus rychlé Fourierovy transformace (FFT), transformace obrazu je natolik výpočetně intenzivní, že v reálném čase není jinak než s využitím GPU realizovatelná. Zde není možné použít starší GPU – potřebné jsou vektory vstupních textur i výstupních bufferů s plovoucí řádovou čárkou. Takže není na výběr – bez moderního GPU to v tomto případě nejde.
Další možnosti
Veškeré dosud uváděné příklady byly celkem jednoznačné. Existují však i problematické úlohy, kde přínosy GPU nemusí být takto zřejmé. Příkladem takové úlohy může být např. rozpoznávání vzorů realizované prostřednictvím GPU (krok gpu_match_monochrome v systému VisionLab). Při hledání obrazového vzoru musí systém mnohokrát provádět normalizovanou křížovou korelaci s velkým počtem pixelů. To vyžaduje mnohonásobně opakované výpočty středních jasů, směrodatných odchylek, odmocnin atd., a to vše s plovoucí řádovou čárkou. Navíc krok hledá i pootočené nebo zvětšené či zmenšené obrazy předloh. Veškeré výpočty tedy navíc podléhají lineárním transformacím. Toto všechno vypadá na první pohled jako výborná úloha pro GPU. Avšak je zde závažná potíž – výpočetní intenzita algoritmu je natolik velká, že není „hrubou silou“ rozumně proveditelný ani nejvýkonnějšími GPU. Podobně jako v algoritmech pro CPU, musí i zde nastoupit řada optimalizací. A právě v těchto optimalizacích, bez kterých ale problém řešit nelze, se skrývá kámen úrazu. Algoritmus je nutné rozdělit do několika fází, v jejichž přechodech musí probíhat výměna dat mezi GPU a CPU. Časové ztráty způsobuje jednak kopírování bloků dat, ale také to, že CPU i GPU musí na sebe vzájemně mezi jednotlivými fázemi čekat a nemohou běžet paralelně. Tento krok je tedy výhodné realizovat jen s moderními velmi výkonnými GPU – čas hledání může být až desetinásobně kratší než na vícejádrových CPU. S méně výkonnými grafickými procesory se ale může situace obrátit a krok může být pomalejší než obdobný krok realizovaný čistě v CPU. Autor aplikace tedy musí vše zvážit a v praxi vyzkoušet.
Obdobně jako existují úlohy, pro které nelze masivně použít paralelní algoritmy v GPU, je spousta úloh, které nelze efektivně řešit dokonce ani paralelními vlákny v CPU. Neexistuje jediný lék na všechno. Pro každou aplikaci musí její autor zvolit nejlepší způsob řešení. A k tomuto rozhodování patří i to, kde využije obrovské schopnosti grafických procesorů.
Závěr
Systém strojového vidění VisionLab poskytuje krokům, které využívají GPU, značný prostor. S vykreslovacím jádrem systému Control Web i virtuálním přístrojem gl_camera komunikuje jen prostřednictvím velmi jednoduchého povelového rozhraní. Kroky dostanou k dispozici paměťový pixel buffer dané velikosti a formátu pixelů a mohou si zcela samostatně alokovat libovolné prostředky GPU – mohou si vytvářet např. vlastní texturové objekty, další objekty frame buffer objects, mohou načítat a spouštět vlastní shadery atd. Kroky strojového vidění tak mohou využívat veškerá nejnovější rozšíření, která přinášejí aktuální grafické ovladače. Tyto možnosti dovolují systému strojového vidění VisionLab stále sledovat poslední vývoj metod zpracování obrazu, a přinášet tak svým uživatelům vysoký užitek a konkurenční výhodu.
Roman Cagaš, Moravské přístroje (rc@mii.cz)
Tab. 1. Vybrané technické parametry GPU GeForce GTX 680 (NVIDIA)
Počet tranzistorů | 3,5 miliardy |
Počet výpočetních jader | 1 536 |
Hodinová frekvence GPU | 1 006 MHz |
Výkon přístupu k texturám | 128,8 miliardy pixelů za sekundu |
Výpočetní výkon (single precision) | 3,79 Tflops |
Šířka sběrnice grafické paměti | 384 bitů |
Datová propustnost | 192,2 GB/s |
Obr. 1. Řetězec zpracování grafických dat v GPU
Obr. 2. Srovnání kvality bilineární (vlevo) a adaptivní (vpravo) interpolace barevné mozaiky
Obr. 3. Znázornění obrazové mapy pro geometrickou kalibraci
Obr. 4. Rychlost i velmi komplexních GPU filtrů je omezena převážně režií přenosu dat
Obr. 5. Anizotropický šumový filtr využívá výkon GPU ve výpočtech s plovoucí řádovou čárkou
Obr. 6. Lokálně prahovací filtr využívá mohutný datový toku GPU při čtení pixelů textur
Obr. 7. GPU dokáže realizovat rychlou dvourozměrnou Fourierovu transformaci na proudu obrazových dat z kamery v reálném čase
Obr. 8. Hledání vzorů realizované prostřednictvím GPU – tento příklad trvá na procesoru G80 asi 100 ms