Hledáme jehlu v kupce sena pomocí počítačového vidění
Tento příběh začíná smutně, ale nakonec dobro zvítězí. A to díky data analytice. Což je vždy dobrá zpráva!
Před asi rokem jsem si postavil freestyle FPV (first person view) koptéru. Takovou jako je na obrázku.
Celý příběh jejího stavění a vylepšování najdete na mém osobním blogu.
O víkendu jsem chtěl vyzkoušet kvalitu přenosu videosignálu nově namontované antény. Vybral jsem si pole bez lidí a začal testovat. Pomalý nízký let až několik set metrů ode mně.
Bohužel jsem nenabil baterii brýlí a tak mi z ničeho nic vypadl obraz. Než jsem pochopil co se stalo uplynulo několik sekund a stroj mezitím havaroval. Syn sice modýlek sledoval, ale i tak jsme neměli prakticky představu, kde leží. Věděli jsme, že někde tady.
Což je naprosto zoufalé. V 10–20cm vysokém osení je šance na nalezení skoro nulová. Zejména pokud při pádu vypadne baterie a model nevysílá, ani nepípá. Což byl tento případ. Můžete stát dva metry daleko a stejně jej nevidíte.
Ale nevzdal jsem se a rozhodl se použít sílu data procesingu. Plán byl zhruba takový:
- Nasnímat pole z výšky pomocí Mavic Pro.
- Pokusit se v tomto videu nalézt červenou barvu vrtulí
- Dojít si na to místo a profit!
Pořízení vstupních dat
Data jsem snímal pomocí Mavic Pro ve 4K rozlišení letem ve výšce asi 8 metrů nad povrchem. Celkem jsem podnikl tři lety v celkové době 45minut. Letová data jsem přenesl do aplikace https://airdata.com, ze které lze následně snadno zjistit polohu v každém okamžiku letu.
Ve výsledku tedy máme 20GB 4K videa a dataset s letovou telemetrikou. Z nich je vidět, že samotné křižování prostoru je dost komplikované. Díky větru a dalším faktorům vznikají mezery a je nutné křížení drah při snímání. Bylo by lepší je plně automatizovat a nespoléhat se na manuální řízení.
Pro představu — těch 45 minut vypadá takto. Prosím pusťte si tento cca. 26 sec dlouhý úsek než budete číst dále, abyste získali představu jaká práce nás čeká.
Tak co našli jste? Video je totiž výsek z místa, kde jsem jej nakonec skutečně našel a pokud jste super pozorní všimnete si jej. Nicméně je nesmyslné snažit se takto ručně projít skoro hodinu pohledů na trávu. To asi nikdo nedá.
Proto jsem se rozhodl tuto práci nechat počítači.
Zpracování obrazu a data procesing
Pro zpracování dat jsem využil knihovnu pro počítačové vidění OpenCV (https://opencv.org), která umožňuje velmi komplexní zpracování obrazu, filtrování, transformace, detekce objektů, machinelearning atd.
Plán byl zhruba takovýto:
- Vytvořil jsem si testovací video, kde byly v poli položené náhradní červené vrtule se stejnou barvou jako měl ztracený stroj. Zkoušel jsem snímat z různých výšek a úhlů.
- V první fázi jsem se rozhodl zkusit jednoduše detekovat v obraze pixely s přibližně červenou barvou. Snímky s více pixely podobné barvy pak uložím do JPEG souboru s informací na kterém snímku videa se vyskytl. Tyto obrázky pak manuálně projdu.
- Pokud tento jednoduchý algoritmus nepomůže, pokusím se o machine learning kde testovací video použiji jako tréningovou množinu. K této fázi jsem se nakonec nedostal, zafungoval už předchozí bod.
Vytvoření podobného zpracování obrazu v OPENCV je překvapivě jednoduché. Používám python3, nicméně použití je podobné v C++ nebo Android či iOS sdk.
Nejprve otevřeme video ze souboru a definujeme si rozsah hledané barvy. Pozor, knihovna zákeřně používá ne RGB, ale BGR.
import numpy as np
import cv2 # open capturing device and check
cap = cv2.VideoCapture("DJI160.MOV")# define color range - bottom and top of color space
# in GBR (reverse RGB)
boundaries = ([00, 00, 123], [88, 88 , 190])
Hledáme tedy barvu s výraznou červenou složkou. Poté spustíme hlavní cyklus čtení videa.
frames=0 #frame counter# main loop - frame by frame proceesing
while(True):
# Capture next frame
ret, frame = cap.read()
frame = frame + 1 # create NumPy arrays from the color boundaries
lower = np.array(boundaries[0], dtype = "uint8")
upper = np.array(boundaries[1], dtype = "uint8")
# create range color mas and apply
mask = cv2.inRange(frame, lower, upper)
filteredImg = cv2.bitwise_and(frame, frame, mask = mask) #count number of not zero pixels
grayImg = cv2.cvtColor(filteredImg, cv2.COLOR_BGR2GRAY)
count = cv2.countNonZero(grayImg) print("Frame: %d\tRed pixels:%s" % (frame,count))
A to je vlastně celá magie. Nejprve vytvoříme barevnou masku, tu aplikujeme. Výsledek převedeme na černobílý obrázek a necháme spočítat nenulové pixely.
Heuréka! V konzoli dostaneme informaci o počtu pixelů v daném barevném rozsahu v každém frame.
Pro reálné použití jsem program pochopitelně doplnil. Přidal jsem zobrazení pozice nalezených pixelů pomocí ohraničení, zobrazuji zpracovaný obraz (ale zmenšený na HD) a hlavně ukládám podezřelé frames jako obrázky pro pozdější manuální kontrolu. Celý kód můžete najít na konci článku.
A jak to funguje? Zde je výstup na testovacím videu s poházenými vrtulkami:
Video je výstupem zpracování vstupního 4K videa výše uvedeným programem. Rychlost je cca 5frames/sec na běžném notebooku. Video ukazuje jak program hledá pohozené vrtulky ze dvou různých výšek a v závěru je i ukázka falešné detekce u suché trávy. S tímto jsem si nějakou dobu hrál, aby vrtulky byly nalezeny vždy a tráva co nejméně.
Výsledek
Poté co jsem byl spokojen s nastavením filtrování, pustil jsem program na těch 45minut videa. A za 5 hodin jsem měl 153 obrázků s detekovanými červenými pixely. Ty jsem musel projít manuálně, ale to je chvíle. Zhruba 100x byl falešný poplach se suchou trávou. Asi 45x se jednalo o jiný červený objekt. V poli jsem tak našel pohozené dvě plechovky a ještě něco co jsem neidentifikoval.
A co je podstatné: pět snímků obsahovalo ztracenou koptéru. Jeden snímek přikládám včetně zvětšení předmětné části. Je to přibližně sedmá sekunda v úvodním videu. Má radost byla nezměrná.
Z názvu jsem pak snadno odvodil místo, kde ve videu nacházel a tím i přesný čas zachycení. Z letového logu jsem pak snadno získal GPS souřadnice kýženého místa.
A to je vše. Po příjezdu na místo mi samotné nalezení zabralo skoro 15 minut. Kdybych pozitivně nevěděl, že tam je tak to vzdám. Nakonec jsem jej prakticky nepoškozeného našel. Pádem se pouze rozpojila baterie, takže nevysílal a nepípal.
Bez počítačového zpracování obrazu by něco podobného nebylo možné. Člověk prostě nemá takovou pozornost a trpělivost jako stroj. A přitom stačila velmi jednoduchá heuristika, obešel jsem se bez složitých operací a strojového učení. Navíc je takové zpracování velmi rychlé. Na mém notebooku běželo zpracování 4K videa zhruba 5x pomaleji než realtime, ale FullHD již byl ok (cca. 22 frames/sec). Pokud bychom tedy snímali obraz třeba kamerou v HD, zvládl by program detekci bez problémů v reálném čase. A to i na zařízení typu RasberyPI.
A v kombinaci se silou cloudu a machine learningu jsou možnosti až děsivé. Nicméně jsou tu a tak je pojďme využívat k co nejlepším účelům.
Plný kód pro zájemce
Komentovaný kód, který není nejkrásnější. Přeci jenom cílem bylo rychle najít ztracenou hračku. Nicméně posloužil dobře a můžete si jej zkusit sami ;-)
https://gist.github.com/jiristepan/58e6538fd01bf12621e7a58cbefe8103