Python Panoráma 6: Mozgásban a világ | Mozgásérzékelő építése Python-nal11 perc olvasás

Nagy embertömegektől forgalmas buszmegállók, dugóban rostokló autók, kamion konvojok, teherszállító hajók és nyüzsgő repterek. A mozgás megfigyelése rengeteg információval szolgálhat számunkra legyen szó tárgyakról, állatokról vagy akár emberekről. A Python Panoráma sorozat mostani részében is ezt a lehetőséget aknázzuk ki, ugyanis egy mozgásérzékelő programot fogunk létrehozni.

CÉL

Célunk ezúttal egy olyan alkalmazás előállítása, amelyet ha lefuttattunk, akkor az képes laptopunk webkamerájának vagy más külsőleg csatlakoztatott kamera segítségével egy ablakban megjeleníteni a felvételt és az újonnan belépő, mozgó objektumokat felismerni. A felismert objektumokat pedig egy olyan téglalappal megjelölni, amely képes lekövetni is a mozgásban lévő testek helyzetét a képen. Bár jelen esetben webkameránk élőképével fogunk dolgozni, lehetséges akár videófájlokra is alkalmazni az alábbi módszereket.

MEGVALÓSÍTÁS

Ahhoz, hogy ezt az alkalmazást megfelelően tudjuk létrehozni, szükségünk van az OpenCV python könyvtár ismeretére és használatára. Az OpenCV egy olyan python package, amely kifejezetten képek és videókkal kapcsolatos műveletekre lett kifejlesztve. A következő pár lépésben az OpenCV néhány alapvető funkcióit nézzük át, majd olyan elemeket próbálunk meg beépíteni scriptünkbe, amelyekkel képesek lesz programunk mozgó objektumokat felismerni a képen.

Elsőként azt nézzük meg, hogy hogyan is lehet az OpenCV használatával egy képet készíteni, majd azon különböző változtatásokat végrehajtani. Ezt követően úgy alakítjuk át a programunkat, hogy az ne csak egy képet rögzítsen, hanem egy videofelvételt. Végül pedig beépítjük azokat az eszközöket a scriptbe, amelyekkel képesek leszünk lekövetni a mozgó objektumot.

Kezdésként tehát nézzük meg, hogyan tudunk egy olyan python scriptet írni, amely készít egy képet, azt fekete-fehér képpé alakítja, végül pedig egy ablakban megnyitja azt. Ahogy azt már megszoktuk, első lépésként importálnunk kell a “pip install cv2” Command Line paranccsal letöltött OpenCV könyvtárat a scriptünkbe, hogy az abban lévő funkciókat fel tudjuk használni a programunkban. A könyvtár importálása után az OpenCV “VideoCapture” method segítségével indítsunk el egy felvételt, amit tároljunk is el egy változóban. Ekkor azt is meg kell adnunk, hogy melyik kamerával szeretnénk elkészíteni a felvételt. Egy laptop webkamerájának esetében ehhez a VideoCapture argumentumaként a 0-t kell megadni.

Amikor az OpenCV segítségével felvételt készítünk egy kamerán keresztül, akkor azt a felvételt alapvetően két paraméterrel írjuk le. Az első egy logikai érték, tehát az, hogy a kameránk éppen felvesz vagy nem. Ha igen akkor True lesz, ha nem akkor nyilván False. A második pedig maga a kép lesz egy olyan n-dimenziós tömb formájában, amelynek elemei az egyes pixelekre vonatkozó intenzitási adatokat tartalmazzák. Ez egy színes kép esetében egy 3-dimenziós tömböt jelent. Ez azt jelenti, hogy 3 olyan mátrixot vagy úgymond színréteget (BGR) tartalmaz a tömb, amelyek egy-egy adott pixelhez határozzák meg annak színintenzitását.

Ezt a két felvételt jellemző paramétert mentsük el egy-egy változóban, mondjuk a “check” és “frame” változóban. Ahhoz, hogy képünk fekete-fehér legyen csak fognunk kell a “frame”-ben tárolt képünket és alkalmazni rá a cv2 “cvtColor” method-jét a “COLOR_BGR2GRAY” paraméter megadásával. A képünkön elvégeztük a kívánt változtatást, most már csak meg kell jelenítenünk az “imshow” method segítségével.

Az “imshow” esetében is két paraméterről beszélhetünk, az első arra szolgál, hogy a megjelenítendő ablaknak nevet adjunk, a második pedig, hogy megmondjuk melyik képet szeretnénk megjeleníteni. Ezek után be kell állítanunk, hogy az ablak milyen hosszú ideig legyen nyitva. Ezt a “waitKey”-vel tudjuk kivitelezni, amelynek argumentumába vagy egy milimásodperc értéket írunk, vagy egyszerűen nullát, amely esetén csak akkor záródik be az ablak, ha mi azt bezárjuk. Végső lépésként pedig kikapcsoljuk a kamerát és bezárunk minden ablakot.

import cv2

video=cv2.VideoCapture(0) #captures video through webcam but it is also able to use a video file

check, frame = video.read() # check is a boolean and it is true when the camera is recording. The frame is a numpy array image.

print(check)
print(frame)

gray=cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY)
cv2.imshow("Capturing",gray)

key=cv2.waitKey(5000)

video.release()
cv2.destroyAllWindows()

Eddig a programunk arra képes, hogy készítsen egy darab képet. Ha átakarjuk írni úgy, hogy annak végeredménye egy videofelvétel legyen, akkor nincs másra szükségünk csupán egy while loop-ra. Amelynek belsejében maga a képalkotási folyamat fog ismétlődni és úgy lehet majd belőle kilépni, hogy a felhasználó megnyom egy adott gombot, mondjuk a “q” billentyűt.

import cv2

video=cv2.VideoCapture(0) #captures video through webcam but it is also able to use a video file

while True:
    check, frame = video.read() # check is a boolean and it is true when the camera is recording. The frame is a numpy array image.

    print(check)
    print(frame)

    gray=cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY)
    cv2.imshow("Capturing",gray)

    key=cv2.waitKey(1)
    if key == ord('q'):
        break

video.release()
cv2.destroyAllWindows()

Most, hogy már videófelvételeket is létre tudunk hozni vágjunk bele a mozgásérzékelő funkciók beépítésébe. Első lépésként meg kell határoznunk valamilyen viszonyítási alapot, amelyhez hasonlítva a programunknak döntést kell hoznia, hogy történt-e mozgás, változás a képen. Ez a viszonyítási alapunk a felvétel első képkockája lesz. Ennek a kiragadása a képeket generáló while loop-ból úgy történhet, hogy programunk elején létrehozunk egy üres változót, majd beépítünk a while loop-ba egy olyan feltételes blokkot, amely azt vizsgálja, hogy az előállított változónknak van-e már értéke.

Most, hogy van mihez viszonyítanunk már csak a különbséget kell kiszámolnunk. Azonban ehhez szükséges a fekete-fehér képek elhomályosítása a jobb végeredmény érdekében. Erre a célra szolgál a cv2 “GaussianBlur” method-je. A különbségszámítást pedig az “absdiff” method segítségével tudjuk kivitelezni, majd ezt a különbséget eltároljuk egy változóban.

previous arrow
next arrow
Slider

 

A döntést, hogy a képen történt-e változás úgy tudja a programunk meghozni, hogy a különbségek mértékéhez rendel egy határt. Tehát ha különbség nagyobb, mint a megadott érték, akkor azokat a pixeleket mondjuk fehérre színezi. Ezt a threshold method-el tudjuk elérni, azonban azon még finomítanunk kell dilate method használatával.

import cv2

first_frame=None

video=cv2.VideoCapture(0)

while True:
    check, frame = video.read()
    gray=cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY)
    gray=cv2.GaussianBlur(gray,(21,21),0)

    if first_frame is None:
        first_frame=gray
        continue

    delta_frame=cv2.absdiff(first_frame,gray)
    thresh_frame=cv2.threshold(delta_frame, 30, 255, cv2.THRESH_BINARY)[1]
    thresh_frame=cv2.dilate(thresh_frame, None, iterations=2)

    (_,cnts,_)=cv2.findContours(thresh_frame.copy(),cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

    for contour in cnts:
        if cv2.contourArea(contour) < 10000:
            continue

        (x, y, w, h)=cv2.boundingRect(contour)
        cv2.rectangle(frame, (x, y), (x+w, y+h), (0,255,0), 3)

    cv2.imshow("Gray Frame",gray)
    cv2.imshow("Delta Frame",delta_frame)
    cv2.imshow("Threshold Frame",thresh_frame)
    cv2.imshow("Color Frame",frame)

    key=cv2.waitKey(1)

    if key==ord('q'):
        break

video.release()
cv2.destroyAllWindows

Miután a különböző method-öknek köszönhetően sikerült mozgás hatására létrejövő összefüggő fehér területek létrehozni, amelyeket contour-nak nevezhetünk, arra van szükségünk, hogy ezekből kiszűrjük azokat, amelyek olyan kis területtel rendelkeznek, hogy azokat kizárhatjuk a mozgó objektumok közül. Ehhez először meg kell találnunk ezeket a fehér területeket a “findcontours” method-del, majd azokon végig haladva el ki kell szűrnünk azokat, amelyek túl kicsi területtel rendelkeznek ahhoz, hogy mozgó objektumnak tudjuk minősíteni. Viszont azon contour-ok esetében, amelyek meghaladják a területhatárt egy téglalapot szeretnénk kirajzoltatni. Ezzel a lépéssel pedig el is készült az alkalmazásunk.

TOVÁBBFEJLESZTÉS ÉS ÜZLETI HASZNOSÍTÁS

Programunk esetében egy igen hasznos előrelépést jelentene az, ha képesek lennénk azt is megmondani, hogy mikor lépett be a mozgó objektum a felvételbe és mikor hagyta el azt. Ezeket az adatokat pedig érdemes lenne egy cvs fájlban elmenteni, majd valamilyen módon azt látványosan ábrázolni annak érdekében, hogy könnyebben tudjunk levonni következtetéseket belőle és azt továbbítani másoknak. Ahhoz, hogy ezt meg tudjuk valósítani alapvetően három dolgot kell beépítenünk az alkalmazásunkba. Először meg kell határoznunk a felvétel során, hogy van-e belépő objektum a képen vagy nincs (status). Másodszor azt is meg kell állapítanunk, hogy a státuszváltozás pillanatában mennyi volt az idő (times). Harmadszor pedig a times listában tárolt belépési és kilépési időpontokat egy cvs fájlként kell elmentenünk.

Ez a továbbfejlesztett alkalmazás pedig így néz ki:

import cv2, time, pandas
from datetime import datetime

first_frame=None
status_list=[None,None]
times=[]

df=pandas.DataFrame(columns=["Start","End"])
video=cv2.VideoCapture(0)


a=0
while True:
    a=a+1
    check, frame = video.read()
    status=0
    gray=cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY)
    gray=cv2.GaussianBlur(gray,(21,21),0)

    if a in [1,2]:
        first_frame=gray
        continue

    delta_frame=cv2.absdiff(first_frame,gray)
    thresh_frame=cv2.threshold(delta_frame, 30, 255, cv2.THRESH_BINARY)[1]
    thresh_frame=cv2.dilate(thresh_frame,None,iterations=2)

    (_,cnts,_)=cv2.findContours(thresh_frame.copy(),cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

    for contour in cnts:
        if cv2.contourArea(contour) < 10000:
            continue
        status=1


        (x,y,w,h)=cv2.boundingRect(contour)
        cv2.rectangle(frame,(x,y),(x+w,y+h), (0,255,0),3)
    status_list.append(status)

    status_list=status_list[-2:] #show only the last two item of the list, this is because of memory issues

    if status_list[-1]==1 and status_list[-2]==0 :
        times.append(datetime.now())
    if status_list[-1]==0 and status_list[-2]==1:
        times.append(datetime.now())
    cv2.imshow("Gray Frame",gray)
    cv2.imshow("Delta Frame",delta_frame)
    cv2.imshow("Threshold Frame", thresh_frame)
    cv2.imshow("Color Frame",frame)

    key=cv2.waitKey(1)

    if key==ord('q'):
        if status==1:
            times.append(datetime.now())
        break

print(status_list)
print(times)

for i in range(0,len(times),2):
    df=df.append({"Start":times[i],"End":times[i+1]},ignore_index=True)

df.to_csv("Movements.csv")

video.release()
cv2.destroyAllWindows

Az adatok megfelelő ábrázolása pedig ebben a fájlban alkalmazott eszközök alapján történhet. A végeredménynek valahogy így kell kinéznie:

Ezt a jelentősen továbbfejlesztett applikációt sokféleképpen hasznosíthatjuk, például alkalmas lehet közúti forgalom ellenőrzésére, vadállatok megfigyelésére vagy magánterületek biztonsági rendszerébe történő integrációjára. Úgy gondolom ebben az alkalmazásban nagy potenciál van. Egy lehetséges végső cél lehet például egy biometria azonosító rendszer kifejlesztése is, azonban mindenképpen gondolnunk kell ilyen esetekben az optimalizációval járó nehézségekkel.
A részhez tartozó fájlokat innen érheted el.

Köszönöm, hogy végigolvastad a posztomat és ha érdekesnek találtad, akkor nézz bele a sorozat többi részébe is.

Gulácsy Dominik

About Dominik Gulácsy

Sophomore at Corvinus University of Budapest studying International Business who is motivated to use relevant academic knowledge to solve problems through optimisation. Dedicated to fully support the development of new business solutions in close collaboration with team members by IoT and data science applications. Gained experience in SQL and VBA but looking forward to learning more. A keen supporter of the circular economy.

View all posts by Dominik Gulácsy →

Leave a Reply

Your email address will not be published. Required fields are marked *