Wykrywanie ruchu z OpenCV
OpenCV (Open Source Computer Vision) jest otwartą, wieloplaformową biblioteką służącą do analizy obrazu w czasie rzeczywistym. Napisana w języku C posiada jednak nakładki pozwalające na jej wykorzystanie między innymi w językach C++, C# czy Python. Jak zwykle, zapoznanie z tą biblioteką przeprowadzimy w ostatnim z wymienionych języków.
W tym artykule chcę przedstawić kilka podstawowych możliwości, jakie daje OpenCV i być może zachęcić czytelnika do poszerzenia wiedzy na temat tej biblioteki. Przedstawione niżej przykłady omówią pokrótce zagadnienia wykrywania ruchu oraz detekcji kształtów na obrazach.
Wykrywanie ruchu
Rozważmy następujący problem:
Dysponujemy kilkoma kolejnymi klatkami filmu. W jaki sposób wykryć ruch postaci pomijając statyczne tło? Ilu klatek potrzebujemy by usunąć tło?
Rozpoczynając nasze rozważanie w najbardziej intuicyjny sposób możemy stwierdzić, że jeżeli dwie kolejne klatki różnią się od siebie w jakikolwiek sposób to nastąpiła zmiana - ruch. Jest to rozwiązanie częściowe, ponieważ wciąż bierzemy pod uwagę tło. Weźmy zatem dodatkową informację i rozważmy wektor trzech obrazów następujących po sobie w czasie:
W celi zignorowania tła należy policzyć zmiany częściowe, czyli różnice pomiędzy pierwszą i drugą oraz drugą i trzecią klatką:
Znając różnice, wyliczamy część wspólną zmian czyli tę zmianę, która nie zależy od tła:
Mając przygotowaną metodę opisaną matematycznie, zapiszmy algorytm z użyciem OpenCV.
def frameDiff(It0, It1, It2):
# Wyliczamy sumy częściowe
dI1 = cv2.absdiff(It2, It1)
dI2 = cv2.absdiff(It1, It0)
# Zwracamy część wspólną
return cv2.bitwise_and(dI1, dI2)
Wiemy jak wykryć różnice. Kolejnym krokiem będzie przeprowadzenie progowania, czyli wyróżnienia obiektów na obrazie. Dokładniejsze informacje o tej operacji znajdziemy w dokumentacji. Wyróżnione na obrazie różnicowym obrazy należy powiększyć, w OpenCV zastosujemy w tym celu metodę dilate().
Teraz pozostaje napisanie kodu, który iterując klatka po klatce dane ze źródła będzie wyliczał różnice i informował nas o ich wystąpieniu.
#!/usr/bin/python
import cv2
def frameDiff(It0, It1, It2):
# Wyliczamy sumy częściowe
dI1 = cv2.absdiff(It2, It1)
dI2 = cv2.absdiff(It1, It0)
# Zwracamy część wspólną
return cv2.bitwise_and(dI1, dI2)
cam = cv2.VideoCapture(0) # Wybieramy kamerę jako źródło obrazu
winName = "Detektor ruchu"
cv2.namedWindow(winName, cv2.CV_WINDOW_AUTOSIZE)
# Odczytujemy trzy pierwsze klatki, zmieniamy kolorystykę na czerń i biel
t_minus = cv2.cvtColor(cam.read()[1], cv2.COLOR_RGB2GRAY)
t = cv2.cvtColor(cam.read()[1], cv2.COLOR_RGB2GRAY)
t_plus = cv2.cvtColor(cam.read()[1], cv2.COLOR_RGB2GRAY)
while True:
# przepisanie dwóch poprzednich klatek
t_minus = t
t = t_plus
# pobranie kolejnej klatki
frame = cam.read()[1]
t_plus = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY)
# Progowanie
thresh = cv2.threshold(diffImg(t_minus, t, t_plus), 25, 255, cv2.THRESH_BINARY)[1]
# Powiększenie progowania
thresh = cv2.dilate(thresh, None, iterations=2)
# Wykrywanie krawędzi
(cnts, _) = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
for c in cnts:
# Ignorowanie zbyt małych obszarów
if cv2.contourArea(c) < 500:
continue
# Naniesienie ramki na obszar w którym wykryto zmianę
# na ostatnio pobraną klatkę
(x, y, w, h) = cv2.boundingRect(c)
cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
# Wyświetlenie klatki z naniesionymi zaznaczeniami
cv2.imshow( winName, frame )
key = cv2.waitKey(10)
if key == 27:
cv2.destroyWindow(winName)
break
Obraz do analizy może również pochodzić z pliku, wystarczy zmodyfikować wywołanie metody VideoCapture
podając ścieżkę do pliku: cv2.VideoCapture("/sciezka/do/pliku.mp4")
. Poniżej dwa zrzuty ekranu z analizy pliku demonstracyjnego pochodzącego ze strony http://sample-videos.com/:
Tak zmodyfikowany plik wideo możemy oczywiście zapisać korzystając z metod klasy VideoWriter()
opisanej w dokumentacji. Zagadnienie to pozostawiam do opracowania czytelnikowi. Przejdźmy teraz do wykrywania kształtów.
Wykrywanie twarzy
Wykrywanie kształtów wymaga zastosowania pliku klasyfikatora, który będzie służył za wzorzec do detekcji. OpenCV dostarcza kilka takich wzorców oraz narzędzia pozwalające stworzyć własny klasyfikator. W tym przykładzie zastosujemy dostarczany z biblioteką plik haarcascade_frontalface_alt.xml
. Wystarczy, że z obiektu biblioteki załadujemy klasyfikator, przekonwertujemy analizowaną ramkę na odcienie szarości i zastosować metodę detectMultiScale
klasyfikatora. Poniżej przykład wykrywający twarz na strumieniu wideo z kamery.
#!/usr/bin/python
import argparse
import imutils
import time
import cv2
# inicjalizacja parsera argumentow
ap = argparse.ArgumentParser()
ap.add_argument("-v", "--video", help="sciezka do pliku wideo")
args = vars(ap.parse_args())
# jesli nie wskazano pliku wybierz kamere
if args.get("video", None) is None:
camera = cv2.VideoCapture(0)
time.sleep(0.25)
# jesli podano sciezke, zaladuj plik
else:
camera = cv2.VideoCapture(args["video"])
while True:
# pobierz bierzaca klatke
frame = camera.read()[1]
# Zaladuj plik kaskadowy do wykrycia twarzy
face_cascade = cv2.CascadeClassifier('haarcascade_frontalface_alt.xml')
# zmniejsz klatke (optymalizacja)
frame = imutils.resize(frame, width=500)
# przekonwertuj na odcienie szarosci
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# wykryj twarze
faces = face_cascade.detectMultiScale(gray, 1.2, 6)
# dodaj do klatki tekst mowiacy o ilosci wykrytych twarzy
cv2.putText(frame, "Twarzy: {}".format(str(len(faces))), (10, 20),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2)
# wyroznij twarze umieszczajac je w ramkach
for (x,y,w,h) in faces:
cv2.rectangle(frame,(x,y),(x+w,y+h),(0,255,0),2)
cv2.imshow("Detektor", frame)
key = cv2.waitKey(10)
if key == 27:
cv2.destroyWindow(winName)
break
camera.release()
cv2.destroyAllWindows()
Komentarze w kodzie wyczerpują opis poszczególnych metod. Warto zauważyć zmianę w stosunku do poprzedniego przykładu. Zastosowałem w nim parser argumentów. Uruchomienie skryptu z parametrem -v /sciezka/do/pliku.mp4
spowoduje analizę pliku zamiast strumienia danych z kamery.
Podsumowanie
OpenCV jest biblioteką dającą ogromne możliwości, a pokazane powyżej przykłady mają jedynie zobrazować część z nich i zachęcić do bliższego poznania biblioteki. Dla zainteresowanych źródła, z których czerpałem tworząc ten artykuł: