Detekcja ruchu za pomocą skryptu w języku Python oraz biblioteki OpenCV

Biblioteka OpenCV pozwala na zaawansowane przetwarzanie obrazów przy użyciu języków C++, Python i Java. Jak wykonać przydatną operację porównania dwóch obrazów (co w konsekwencji pozwoli na wykrycie ruchu)? — zobaczymy na praktycznym przykładzie skryptu w języku Python.

Kolejne kroki algorytmu będą wyglądały jak następuje:

  1. wczytamy dwa wybrane obrazy
  2. nałożymy maskę na wybrany prostokątny obszar (bo zazwyczaj interesujący jest tylko fragment całego obrazu)
  3. skonwertujemy obraz do skali szarości
  4. obliczymy współczynnik SSIM (liczbową wartość określającą względną różnicę między obrazami)
  5. oznaczymy kontury zmienionych obszarów
  6. wygenerujemy mapę zmienionych obszarów
  7. stworzymy mapę zmienionych obszarów przy zastosowaniu progowania (białym kolorem oznaczone będą obszary zmienione)

Otrzymany rezultat warto poddać dalszemu przetwarzaniu: sprawdzić, gdzie dokładnie leży liczbowa granica pomiędzy brakiem ruchu a ruchem, albowiem nie każda dodatnia wartość oznacza faktyczny ruch (niskie wartości mogą oznaczać niewielkie zmiany w obrazie, np. ruch liści na drzewie).

pierwotny obraz

{importujemy niezbędne pakiety}
from skimage.measure import compare_ssim
import argparse
import imutils
import cv2
import numpy as np

ap = argparse.ArgumentParser()
ap.add_argument(„-f”, „–first”, required=True,
help=”first input image”)
ap.add_argument(„-s”, „–second”, required=True,
help=”second”)
args = vars(ap.parse_args())

{wczytujemy dwa obrazki, które będą porównywane ze sobą}
imageA = cv2.imread(args[„first”])
imageB = cv2.imread(args[„second”])

{operujemy na masce…}
mask = np.zeros(imageA.shape, dtype=np.uint8)
roi_corners = np.array([[{współrzędne wybranego prostokątnego obszaru, np. (x1,y1),(x2,y2),(x3,y3),(x4,y4)}]], dtype=np.int32)
channel_count = imageA.shape[2]
ignore_mask_color = (255,)*channel_count
cv2.fillPoly(mask, roi_corners, ignore_mask_color)
imageA = cv2.bitwise_and(imageA, mask)
{… tutaj kończymy operację na masce}

{kod tak samo jak wyżej, ale tym razem dla obrazka imageB}

{konwertujemy obrazy do skali szarości}
grayA = cv2.cvtColor(imageA, cv2.COLOR_BGR2GRAY)
grayB = cv2.cvtColor(imageB, cv2.COLOR_BGR2GRAY)

{obliczamy wartość SSIM}
(score, diff) = compare_ssim(grayA, grayB, full=True)
diff = (diff * 255).astype(„uint8”)

{otrzymujemy wynik, tutaj wyrażony jako dopełnienie do wartości 10000}
print( int( round(1e4*(1-score)) ) )

Wygenerowanie tablicy konturów wskazujących zmienione obszary wygląda wtedy następująco:

thresh = cv2.threshold(diff, 0, 255,
cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]
cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if imutils.is_cv2() else cnts[1]

wizualizacja zmienionych obszarów (zakreślonych czerwonymi prostokątami)
wykryte zmiany (oznaczone kolorem czarnym)
lepiej widoczne wykryte zmiany (po wykonaniu progowania, oznaczone kolorem białym)

W środowisku systemów linuksowych z rodziny Debian uruchomienie skryptu po uprzedniej instalacji pakietów python3, python-opencv oraz pip wraz z bibliotekami libsm6, libxext6 oraz aktualizacji pakietów Pythona scikit-image i imutils odbywa się według schematu:

python3 c.py -f a.jpg -s b.jpg

Czas wykonania skryptu Pythona wykrywającego ruch według powyższego przepisu to około 1,5 sekundy (przy użyciu pojedynczego procesora Intel Xeon 2,2 GHz).


Przydatne pozycje książkowe:
A. Pajankar ’Raspberry Pi Computer Vision Programming’ (2015)
J. Minichino, J. Howse ’Learning OpenCV 3 Computer Vision with Python’ (2015)

Wzorcowa implementacja:
Image Difference with OpenCV and Python
https://www.pyimagesearch.com/2017/06/19/image-difference-with-opencv-and-python/

Definicja miary SSIM w Wikipedii:
https://en.wikipedia.org/wiki/Structural_similarity

Oficjalna witryna biblioteki OpenCV:
https://opencv.org/