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ł: