5.9. Aritmetické operace

Rodina kreslení z předchozí sekce maluje do obrazu. Aritmetická rodina kombinuje dva obrazy do třetího – sčítá jejich hodnoty pixelů dohromady, odečítá jeden od druhého, bere minimum nebo maximum na každé pozici. Tato malá množina pixelových aritmetických operací je tím, na čem stojí rozdíl snímků (frame differencing), odečítání pozadí, skládání expozic a hrstka dalších klasických vzorů.

Aritmetická rodina ve třídě Image je dostatečně malá na to, aby se dala vyjmenovat najednou:

  • add() – po pixelech self + other, oříznuto na maximum daného formátu.

  • sub() – po pixelech self - other, oříznuto zdola na 0.

  • rsub() – po pixelech other - self, oříznuto na 0 (stejná aritmetika jako sub s prohozenými operandy).

  • min() – minimum z obou hodnot po pixelech.

  • max() – maximum po pixelech.

  • difference() – po pixelech |self - other|, absolutní rozdíl.

Plus dvě související operace s jedním obrazem:

  • invert() – nahradí každý pixel hodnotou 255 - pixel (nebo ekvivalentním maximem pro daný formát).

  • negate() – alias pro invert().

Dva vodorovné gradientní pruhy nahoře představující zdrojové obrazy A a B -- A jde zleva doprava od tmavé ke světlé, B jde zleva doprava od světlé k tmavé. Pod nimi pět gradientních pruhů představujících výsledek každé párové operace aplikované na A a B: A.add(B) se jeví jednolitě bílý, protože každá pozice se sečte přes 255 a ořízne se; A.sub(B) je v levé polovině nulový a zesvětluje směrem doprava; A.difference(B) ukazuje tvar V, světlý na obou koncích a tmavý uprostřed; A.min(B) je tmavý na koncích a světlejší uprostřed; A.max(B) je světlý na koncích a šedý uprostřed.

Dva zdrojové gradienty A a B a výsledek každé párové operace na ně aplikované. Každá operace běží pozici po pozici – to, co se ve výsledku zobrazí v kterémkoli místě, závisí pouze na dvou zdrojových pixelech v daném místě.

5.9.1. Dvě formy operandu

Každá z metod pro dva obrazy přijímá pro svůj druhý operand jednu z těchto forem:

  • Další Image stejných rozměrů. Aritmetika běží pozici po pozici – výsledek na (x, y) je operace aplikovaná na zdrojové pixely na (x, y) obou obrazů.

  • Skalární hodnota – celé číslo pro stupně šedi, n-tice (r, g, b) pro RGB565. Stejný skalár se uplatní na každé pozici.

Skalární forma je užitečná, když chce aplikace posunout každý pixel o konstantní hodnotu. img.add(40) zesvětlí celý obraz o 40; img.sub((20, 20, 20)) ztmaví každý pixel o 20 na kanál; img.max(50) zvedne každý pixel pod 50 na 50 a zbytek nechá být – druh operace, která promění téměř černou spodní mez senzoru na rovnoměrnou tmavě šedou, vůči níž mohou pracovat následující fáze.

5.9.2. Ořezávání

Hodnoty pixelů zůstávají uvnitř rozsahu daného formátu při každé operaci. Pro 8bitový kanál to znamená 0255: cokoli, co by přeteklo přes 255, se ořízne zpět na 255, a cokoli, co by kleslo pod 0, se ořízne nahoru na 0. Nedochází k přetečení s obtočením.

Tato volba je v praxi důležitá. add zesvětlující pixely nikdy nevytvoří náhlý ztmavovací artefakt na světlém konci, kde by jinak matematika přetekla; sub ztmavující pixely nikdy nevytvoří náhlý zesvětlovací artefakt na tmavém konci, kde by jinak došlo k podtečení. Výsledky zůstávají vizuálně smysluplné za cenu jisté ztráty informace v saturovaných extrémech.

Ořezávání je také důvodem, proč sub a rsub vracejí navzájem odlišné výsledky. img_a.sub(img_b) dává tu část a, která je světlejší než b, a všude jinde nulu; img_a.rsub(img_b) dává tu část b, která je světlejší než a. Obojí je užitečné pro jednostrannou detekci změn – pokud aplikaci zajímají jen pixely, které zesvětlaly, nebo jen pixely, které ztmavly – ale ani jedno nezachytí veškerou změnu mezi dvěma snímky.

5.9.3. Operace rozdílu

Pro oboustrannou detekci změn je operací, po které sáhnout, difference(), která počítá |self - other| na každé pozici – absolutní rozdíl bez znaménka. Každý pixel, který se změnil v kterémkoli směru, se ve výsledku projeví jako nenulová hodnota, přičemž velikost je úměrná tomu, jak moc se na dané pozici změnil.

Tato vlastnost – nenulová přesně tam, kde se oba obrazy neshodují – činí z difference tahouna detekce změn snímek po snímku. Referenční snímek uložený při startu a čerstvě zachycený snímek, prohnané skrze difference, vytvoří obraz, jehož nenulové pixely označují každou pozici, kde se ve scéně něco pohnulo nebo změnilo jas.

5.9.4. Vymezení rozsahu pomocí masky

Všechny aritmetické metody přijímají argument klíčového slova mask představený na stránce o oblastech a maskách. Když je maska předána, operace běží pouze na pozicích, kde je maska nenulová; všude jinde se cílový obraz ponechá beze změny.

Tato kompozice se objevuje ve dvou vzorech. První je omezení operace na známou oblast: například sčítání dvou snímků dohromady jen uvnitř ohraničujícího rámečku detekované značky. Druhým je postupné budování složeného snímku kus po kuse – min přes posloupnost snímků uvnitř masky popředí, max přes tutéž posloupnost uvnitř doplňkové masky – druh takového vzoru.

5.9.5. Na místě a se zachováním vstupů

Aritmetické metody se všechny řídí provozní konvencí zavedenou dříve: každá modifikuje zdrojový obraz na místě a vrací tentýž obraz pro řetězení. Pixely zdroje jsou po volání pryč – nahrazeny výsledkem operace vůči tomu, co bylo předáno jako druhý operand.

Když aplikace potřebuje zachovat oba vstupy, bezpečným vzorem je nejprve jeden z nich zkopírovat:

diff = current.copy()       # leaves current intact
diff.difference(reference)  # diff now holds the absolute difference

Tento vzor – zkopírovat, pak operovat – je páteří každé pipeline rozdílu snímků, kde referenční snímek musí porovnání přežít, aby mohl být znovu použit na příštím zachyceném snímku.

Se šesti kombinujícími operacemi, dvěma operacemi s jedním obrazem, tahounem absolutního rozdílu a klíčovým slovem mask pro vymezení rozsahu pokrývá sada nástrojů pixelové aritmetiky kombinace jasu a kanálů, které klasické strojové vidění potřebuje. Zbývající nástroje podobné aritmetice na povrchu pracují bit po bitu spíše než hodnotu po hodnotě.