Hlavní navigace

Detekce reklam v TV podle loga

10. 10. 2014 16:08 (aktualizováno) | Tomas Matějíček

Všechny televizní stanice (alespoň ty které mám chuť někdy sledovat) do vysílaného programu do rohu obrazovky přidávají svoje logo, například Prima, ČT1, atd. Nicméně reklamy jdou zatím na všech stanicích bez loga. Proto se mi detekce loga jeví jako výborná možnost jak detekovat (alespoň s určitou nepřesnou přesností) kde jsou reklamy, a ty pak třeba přeskakovat či rovnou vystřihnout. Často nahrávám pohádky dětem, abych měl od nich aspoň na chvíli přes den klid, když je žena v sauně, v kině, na masáži, nebo na školení osobního rozvoje (sic). A kde to jde, tam reklamy stříhám, protože už tak děckám vymyje mozek samotná TV, natož pak ty reklamy…

Jelikož neumím na internetu hledat, žádný tool na detekci reklam jsem nenašel. A proto jsem se rozhodl, že vymyslím vlastní. Takže, jak na to.

Bod první, dosáhnout cíle bez čtení kompletního videa.

Celý video soubor může mít třeba 3GB, a nejkratší reklama má 30 sekund. Takže načíst a analyzovat celé video mi přijde jako kanón na vrabce, musí to jít líp. Pro detekci loga by tedy logicky měly stačit pouze sample frejmy, řekněme jeden frame každých 30 vteřin. Dá se přistupovat k frejmům bez čtení celého videa? Odpoveď je naštěstí kladná, ano dá. Obecně je ve videu několik různých typů frejmů. Označují se nějak podobně jako I, P a B, a pro nás je důležité, že v televizním vysílání je pravděpodobně dost I frejmů, které představují plně vykreslený obrázek, a které přesně potřebujeme, stačí na ně skočit. Proč si myslím že je jich dost? Je to proto, že když přepnu z jedné stanice na jinou, vidím video obraz poměrně hned, a ne až za pár sekund. Pomocí funkcí ffmpeg je možné seekovat (tedy skákat ve videu) na konkrétní časovou značku a od ní pak na nejbližší I frejm. Takže se dá velmi snadno najít celý frame každých cca 30 vteřin, a vůbec není nutné číst ze souboru žádná data navíc. To ušetří moře času.

Bod druhý, detekce loga

V tuto chvíli tedy máme přístup k obrazové reprezentaci videa každých řekněme 30 vteřin. U dvouhodinové nahrávky to může být nějakých 240 obrázků (budeme jim říkat pivoty, nevím proč). Na některých logo stanice je (tam kde byl pořad), na některých není (tam kde byla reklama). Metod detekce bude asi nespočet, ale já jsem vymyslel vlastní. Jde o to, zprůměrovat všechny pixely ze všech pivotů. Ve videu je každý bod určený svou pozicí „x“ a „y“, a má tři složky (červená, zelená, modrá, tedy RGB). Takže se napíše funkce, která vezme RGB barvy pixelu 1×1 ze všech 240 obrázků, vytvoří průměr (například aritmetický), a výsledkem je nový pixel pro pozici 1×1, který má červenou složku jako průměr všech červených složek, zelenou jako průměr všech zelených, atd, a vše se zopakuje pro všechny pixely videa. Výsledkem je zprůměrovaný obrázek. Ten sám o sobě vypadá dost hnusně, ale poslouží. Zde můžete vidět zprůměrování všech 140 pivot frejmů z nahrávky Show Jana Krause:

Show Jana Krause - average

Bod třetí, edge detection

Na zprůměrovaném screenu je logo dobře patrné, ale pro naše účely je zde stále hodně ruchu. Většina toho ruchu je ale rozmazaná, a pouze logo má hezké okraje. Toho musíme využít. Pomocí edge detection (vyhledání hran) můžeme logo najít úplně přesně. Jak edge detection funguje? Dá se říct, že algoritmus je celkem jednoduchý. Obrázek se nejprve převede do odstínu šedi (každý pixel se vytvoří sečtením 30% červené složky, 59% zelené a 11% modré, neptejte se mě proč). Zjednodušeně pak na takovémto výsledku se jde pixel po pixelu, a porovnává se, zda pixely okolo jsou výrazně světlejší či výrazně tmavší. Odečte se hodnota levého od pravého, a hodnota horního od dolního, a oba výsledky se sečtou. Trochu složitější algoritmus je popsaný tady (berte to PHP jako pseudo kód, dělat tohle v PHP není úplne optimální). Po odfiltrování příliš světlých bodů dostaneme téměř čistě jen logo stanice:

Show Jana Krause - edge detection

Bod čtvrtý, pozice loga

V našem výsledku se stále mohou objevovat body mimo logo stanice, proto pro zjištění přesného umístění loga budeme potřebovat spočítat, pro každý řádek a pro každý sloupec, kolik bodů se v nich nachází, a každý bude mít váhu podle své světlosti (tudíž tmavé body vyznačující ostrou hranu budou váženější). Ze získaných dat vypočítáme geometrický průměr a vyhodíme všechny sloupce a všechny řádky, ve kterých se nachází méně bodů než je průměr. Z toho co zbyde pak nejmenší řádek a nejmenší sloupec udávají XY souřadnici horního levého rohu loga, největší řádek a největší sloupec pak dolní pravý roh. Výsledku můžeme dát pár pixelů padding, pro zichr. Získali jsme souřadnice loga, i jak vypadá:

Bod pátý, test frejmů na logo

Teď už jen otestovat všech 240 pivot frejmů, které jsme získali v kroku 1, abychom dostali hrubý odhad toho, kde jsou reklamy a kde nejsou. Na každý pivot frejm v daném čtverci, kde by mělo být logo, použijeme algoritmus na detekci hran („ohraníme“) a porovnáme, na kolik procent se pixely z „ohraněného“ loga nacházejí i v „ohraněném“ čtverci v pivot frejmu. Možná existuje i elegantnější a přesnější metoda, tu jsem ale zatím nevymyslel ani nenašel. A když už jsme schopni najít přibližné intervaly s loga a bez log, můžeme pak několika málo seek()y jednotlivé úseky zpřesnit, a ve výsledku třeba vypsat vteřinové intervaly s logem a bez loga.

Implementace v C++

Celý tenhle koncept zatím existuje jen na úrovni jednotlivých kroků, které jsem si naprogramoval v PHP (neb jinde se na úroveň pixelů v obrazu dostat neumím), což je neoptimální jak hovado. Kdyby chtěl nějaký student co umí pracovat s ffmpeg brigádu, že mi to napíše celé v C++ jako commandline utilitu pro Linux, mám pro to vyhrazeno pár dvoutisícovek, kontaktujte mě v komentářích :) Také uvítám nápady na to, jak lépe poznat, že nějaký frejm obsahuje logo.