Programování | Pojďme programovat elektroniku | Raspberry Pi

Pojďme programovat elektroniku: Proměníme Raspberry s kamerou v detektor obličeje

  • Kameru na Raspberry Pi jsme si už vyzkoušeli
  • Dnes dodáme nějakou tu inteligenci
  • Pomocí knihovny OpenCV bude rozpoznávat obličeje

Nedávno jsme si v našem seriálu Pojďme programovat elektroniku vyzkoušeli základní práci s CSI kamerou pro linuxový mikropočítač Raspberry Pi. Tentokrát se k ní vrátíme, ale namísto primitivního snímání scény si vyzkoušíme detekci lidského obličeje a s pomocí infračerveného detektoru PIR a RGB LED diody si postavíme první stupeň jakéhosi hypotetického docházkového, nebo naopak bezpečnostního systému, který bude detekovat lidský pohyb a pořizovat fotog
rafie tváří.

Abychom to zvládli, na pomoc povoláme velmi mocnou knihovnu OpenCV (Open Source Computer Vision Library) předinstalovanou na Raspbianu a původní maličké Raspberry Pi Zero W nahradíme nejvýkonnějším modelem Raspberry Pi 3.

Zároveň si vyzkoušíme jednoduchou práci s piny GPIO v Pythonu a to díky knihovně RPi.GPIO, pomocí které budeme číst stav PIR detektoru pohybu a pomocí PWM nastavovat barvu RGB LED diody, která bude signalizovat aktuální stav.

864507994
Číslování GPIO a jejich sekundární funkce na Raspberry Pi 3 (Zdroj: Pinout.xyz)

Knihovnu RPi.GPIO doinstalujete třeba tímto příkazem:

sudo apt install python-rpi.gpio

Lepší širokoúhlá kamera

Takže pěkně popořadě. K portu CSI na Raspberry Pi připojíme kameru. Já tentokrát použiji oproti té základní lehce sofistikovanější (a také dražší) širokoúhlý model zhruba se 160° úhlem záběru, který více odpovídá i úhlové citlivosti detektoru pohybu.

337193597 854361079 406686841
Širokoúhlá kamera s CSI konektorem a celý automat před vchodem do redakce

RGB LED dioda

Ke GPIO pinům pak připojím podobně jako na Arduinu a jiných prototypovacích destičkách PIR a RGB LED diodu. Zatímco PIR má vedle napájení pouze jeden výstupní signální vodič, na kterém se při detekci lidského pohybu objeví dle konfigurace buď HIGH, nebo LOW, RGB LED se skládá z napájecího pinu pro každý barevný kanál (červený, zelený a modrý) a GND (zem/-).

49850141 541997904
Levný modul RGB LED diody z eBaye a hromada F-F kablíků. Pozor, podobné RGB LED moduly mají často chybně popsané kanály, čili R je ve skutečnosti třeba G a podobně. Do obvodu bychom měli zároveň zapojit slabé rezistory.

Pokud bychom na všech barevných kanálech nastavili jednoduše HIGH, LED se rozsvítí plnou silou a bude zářit v ideálním případě relativně bílou barvou. Když však nastavíme HIGH třeba jen na zeleném kanálu, bude LED svítit zeleně.

Kouzlo RGB LED diod však spočívá v tom, že můžeme vytvářet tisíce dalších odstínů, i když konstrukce levných diod reálně rozliší spíše desítky barev. Stejně jako v grafickém editoru na počítači tedy můžeme namíchat třeba odstín R=255, G=100 a B=0. Výsledkem by byla svěží oranžová barva.

Pulzně-šířková modulace PWM

Jak toho docílit u digitální logiky? Leckoho napadne, že by stačilo na konkrétním kanálu adekvátně snížit napětí. Jenže LED není žárovka! LED jednoduše svítí, anebo nesvítí. Tečka! Jak tedy docílit stmívání diody? Pomocí PWM, tedy pulzně-šířkové modulace digitálního signálu, která simuluje analogově-spojitý pokles, nebo naopak růst napětí jednoduše tak, že při vysoké frekvenci střídá HIGH a LOW pulzy.

927005088 6068564
Vlevo: LED pomocí PWM svítí jasně. Vpravo: LED pomocí PWM svítí slabě (Zdroj: Wikipedie)

Každý z barevných kanálů tedy vlastně bliká. Pokud jsou prodlevy mezi delšími bliky krátké (pulz HIGH je mnohem delší než pulz LOW), lidskému oku se jeví, že svítí plnou barvou a z hlediska modelu RGB tedy třeba sytou zelenou. Když však prodlevy změníme tak, že se naopak prodlouží pulz LOW a zkrátí pulz HIGH, zelená LED bude po většinu času vypnutá a jen krátce blikne.

Jelikož se vše děje opravdu velmi rychle, pro lidské oko a mozek se to jeví jako spojitý jev a my tedy vidíme buď krásně sytou zelenou (R =255), nebo naopak velmi slabou zelenou (R=50). Kombinací délek světelných pulzů každé barevné diody pak stvoříme finální RGB světlo, které vydává celý modul.

Rozpoznání obličeje

Po teoretické odbočce zpět k našemu Raspberry Pi 3, protože nám jde dnes přeci o trošku jinou vědu než o samotné blikání diody.

Na mikropočítači poběží drobný program napsaný v Pythonu, který bude v nekonečné smyčce číst hodnotu na signálním pinu PIR detektoru. Když detekuje HIGH a tedy pohyb, RGB dioda se rozsvítí červeně.

Jelikož stavíme základní kámen jakéhosi docházkového systému a naše zařízení by bylo kdesi u vstupních dveří, barevné světýlko dává zaměstnanci najevo, ať přistoupí ke kameře.

Krátce poté se světýlko rozsvítí modře, čímž dává příchozímu zaměstnanci najevo, že se nesmí hýbat a hledět jako u fotografa přímo do objektivu. Kamera po dvou sekundách pořídí snímek a světýlko se rozsvítí zeleně.

Zaměstnanec může odejít do kanceláře.

Haarovy příznaky

Nu dobrá, ale co se mezitím děje uvnitř drobného Raspberry Pi 3? To zdaleka nejzajímavější! Snímek z kamery si vzala do parády knihovna OpenCV a pomocí tzv. Haarových příznaků, drobných geometrických struktur, v něm dle zadání hledá objekty, které chceme – lidskou tvář.

110556518
Drobné geometrické příznaky popisující lidskou tvář. Pokud je software najde na fotografii, bude tyto oblasti považovat za lidskou tvář.

Lidská tvář z čelní strany samozřejmě vypadá jinak než třeba z profilu, oním zadáním je tedy speciální předpočítaný soubor (XML), který říká: Tady mám data, jak vypadají příznaky lidské tváře z čelní strany.

Když tedy budeme chtít pomocí OpenCV hledat ve fotografii tváře z čelní strany, použijeme jiný model, než když budeme hledat třeba lidské oči.

298849466
Detekce tváře se provádí v odstínech šedi a dle parametrů, které nastavíme, bude moci ignorovat příliš malé tváře. Proto software ignoruje Tomáše v pozadí, protože čtverec jeho tváře je příliš malý. Tímto omezením snížím i pravděpodobnost chyb.

OpenCV na svém GitHubu nabízí modely pro různé situace včetně detekce lidského úsměvu, celé osoby nebo i koček. Stačí tedy stáhnout ten, který budeme potřebovat, a pak jej načíst v kódu programu. Stejně tak ale OpenCV nabízí postup, jak si vytvořit vlastní modely pro rozpoznávání.

Tváře vyznačené zelenými čtverečky

Fajn, zpět do našeho programu. Pokud knihovna OpenCV najde ve fotografii tváře, náš program je ve snímku vyznačí zeleným obdélníkem a zároveň výřez s tváří uloží jako samostatný soubor. Nakonec se uloží i snímek celé scény.

777825196 534436419 103675049 620869086
Náš rozpoznávač používá model pro čelní pohled, stačí se tedy nedívat přímo do kamery a tvář nerozpozná

V tento okamžik zelené světýlko RGB LED diody zhasne a analýza obrazu skončila. Pokud detektor po bezpečné prodlevě opět zachytí pohyb, bude se celé kolečko opakovat.

3233aa06-b66b-4ba4-84fd-539752fc098858d072a7-0c8c-425b-8d5d-44dd96703f1f2d5ab12f-f812-468c-b7f3-7773a8821f5ddbe87810-bf32-44cf-8a7e-d6f3f5089391
Samostatně uložené a normalizované výřezy tváří, které bych mohl použít třeba pro jednoduché strojové učení identifikace jednotlivých osob

A takhle vypadá nekonečná smyčka v SSH terminálu Raspberry Pi 3:

725303267
Jak vidno, detektor třikrát zaregistroval pohyb, ovšem pouze ve dvou případech rozpoznal lidskou tvář. Obrázek níže napoví, proč v tom posledním selhal.
114249497
PIR zachytil pohyb (ruka), ale tvář psa v klobouku neidentifikoval

Data třeba pro strojové učení a A.I. identifikaci konkrétních osob

Co bychom mohli udělat v dalším kroku? Kdybych svůj prototyp nainstaloval třeba u nás v redakci, po několika týdnech bych měl hromadu drobných fotografií lidské tváře každého z kolegů. No, a to už bych mohl na pomoc povolat strojové učení, kterému bych tyto tváře předložil s tím, že bych nejprve jasně popsal, kdo je kdo.

Jakmile by si neuronová síť vytvořila dostatečně kvalitní model tváří kolegů, mohl bych ji snadno napojit do původního programu v Pythonu. Snímek výřezu tváře by se předal ke zpracování neuronové síti, která by poté s jistou pravděpodobností odhadla, o koho se jedná, a do informačního systému by se zapsal čas jeho příchodu do práce.

Podobné vycvičení umělé inteligence může být přitom docela snadné, k dispozici dnes totiž máte jak weby, které přesně toto nabízejí jako službu skrze vlastní API (třeba Vize.ai), anebo si podobný identifikátor konkrétních osob můžete vycvičit třeba pomocí frameworku pro strojové učení Google TensorFlow. Jak na to napoví třeba návod TensorFlow for Poets nebo tento o něco pokročilejší.

Na závěr samozřejmě nesmím zapomenout na tradiční komentovaný kód:

# Knihovny pro praci s CSI kamerou,
# GPIO porty, casem a OpenCV (cv2)
from picamera import PiCamera
from picamera.array import PiRGBArray
import cv2
import RPi.GPIO as GPIO
from time import sleep
from datetime import datetime

# Uvodni konfigurace GPIO
# Reset predchozich stavu aj.
GPIO.setwarnings(False)
GPIO.cleanup()
GPIO.setmode(GPIO.BCM)

# Nastaveni GPIO 4 na vstup (PIR)
GPIO.setup(4, GPIO.IN)
# Nastaveni GPIO 13, 19 a 26 na vystup (RGB LED)
GPIO.setup(13, GPIO.OUT)
GPIO.setup(19, GPIO.OUT)
GPIO.setup(26, GPIO.OUT)

# Nastaveni PWM na 100 Hz
R = GPIO.PWM(19, 100)
G = GPIO.PWM(26, 100)
B = GPIO.PWM(13, 100)

# Pomocna promenna, abych nezacal znovu fotit,
# kdyz je pobliz osoba, kterou jsem prave fotil
detekuji = False

# Nastartovani PWM
# Ve vychozim stavu zadny kanal neblika 
R.start(0)
G.start(0)
B.start(0)

# Funkce pro nastaveni RGB odstinu
# HIGH pulz muze mit PWM sirku 0-100 %
# Staci tedy prepocitat RGB rozsahy 0-255 na 0-100
def rgb(r, g, b):
    R.ChangeDutyCycle((r / 255.0) * 100)
    G.ChangeDutyCycle((g / 255.0) * 100)
    B.ChangeDutyCycle((b / 255.0) * 100)


# Funkce pro ziskani snimku z CSI kamery
def ziskejFotku():
    # Nastav barvu LED na modrou
    rgb(0, 0, 255)
    with PiCamera() as kamera:
        # Rozliseni snimku bude 1280x720 px
        kamera.resolution = (1280, 720)
        # Pockej 2 sekundy
        sleep(2)
        print("Porizuji fotku")
        # Porid snimek jako pole pixelu
        raw = PiRGBArray(kamera)
        kamera.capture(raw, format="bgr")
    # Nastav barvu LED na zelenou
    rgb(0, 255, 0)
    # Vrat pole pixelu, se kterym umi pracovat OpenCV
    return raw.array


try:
    # Nekonecna smycka, mohu ji ukoncit zabitim procesu,
    # nebo treba zkratkou CTRL+C
    while(1):
        # Pokud PIR vraci HIGH a zatim nic nedetekuji
        if GPIO.input(4) == 1 and detekuji == False:
            # Cas ve formatu YYMMDDHHMMSS
            cas = datetime.now().strftime("%y%m%d%H%M%S")
            # Nastav barvu LED na cervenou
            rgb(255, 0, 0)
            # Pockej 3 sekundy, aby osoba mohla dojit ke kamere
            sleep(3)
            detekuji = True
            print("Detekuji pohyb")
            # Porizeni fotky do promenne, se kterou pracuje OpenCV
            fotka = ziskejFotku()
            # Prevod fotky do odstinu sedi kvuli lepsi detekci
            fotka = cv2.cvtColor(fotka, cv2.COLOR_BGR2GRAY)
            # Nacteni modelu pro celni detekci obliceje
            # stazeneho z GitHubu: https://goo.gl/JnB9iJ
            model = cv2.CascadeClassifier("model.xml")
            print("Detekuji tvare")
            # Souradnice nalezenych tvari se ulozi do pole
            # minNeighbours: Cim vyssi, tim prisnejsi detekce
            # minSize: Mensi tvare budu ignorovat
            tvare = model.detectMultiScale(
                fotka,
                scaleFactor=1.1,
                minNeighbors=5,
                minSize=(100, 100),
                flags=cv2.CASCADE_SCALE_IMAGE
            )
            # Pokud jsem nasel alespon jednu tvar
            # (v nasem pripade idealne prave jednu)
            if len(tvare) > 0:
                print("Pocet nalezenych tvari: " + str(len(tvare)))
                # Preved fotku zase do formatu RGB
                fotka = cv2.cvtColor(fotka, cv2.COLOR_GRAY2RGB)
                # Projdi pole se souradnicemi tvari
                for (x, y, w, h) in tvare:
                    # Vyrez tvare se ulozi ve formatu:
                    # osoba_YYMMDDHHMMSS.jpg
                    soubor_tvare = "/var/www/html/osoba_" + cas + ".jpg"
                    # Uloz vyrez tvare do souboru
                    cv2.imwrite(soubor_tvare, fotka[y:y+h, x:x+w])
                    # Na velkou fotografii napis zeleny text:
                    # "detekovan humanoid" 
                    cv2.putText(
                        fotka,
                        "Detekovan humanoid",
                        (x, y-10),
                        cv2.FONT_HERSHEY_SIMPLEX,
                        0.5,
                        (0, 255, 0),
                        1
                    )
                    # A nakresli okolo tvare zeleny obdelnik
                    cv2.rectangle(
                        fotka,
                        (x, y),
                        (x+w, y+h),
                        (0, 255, 0),
                        2
                    )
            else:
                print("Zadnou tvar jsem bohuzel nenasel :-(")
            # Velkou fotografii ulozim ve formatu:
            # YYMMDDHHMMSS.jpg
            filename = "/var/www/html/" + cas + ".jpg"
            cv2.imwrite(filename, fotka)
            # Detekce skoncila, zhasnu LED diodu
            rgb(0, 0, 0)
        else:
            # Pokud PIR opet zaznamenal pohyb, ale prave skoncila
            # detekce, pockam deset sekund, aby clovek mohl odejit
            # mimo dosah PIRu a zmeni stav promenne detekuji
            if detekuji == True:
                sleep(10)
                detekuji = False
# Pokud prerusim program treba zkratkou CTRL+C,
# musim jeste resetovat nastaveni GPIO, aby mi treba 
# porad nesvitila RGB LED
finally:
    GPIO.cleanup()
Diskuze (8) Další článek: Super tenká tkanina vytváří energii díky našim pohybům

Témata článku: , , , , , , , , , , , , , , , , , , , , , , , , ,