poniedziałek, 9 stycznia 2012

Aplikacja interaktywna w OpenCV reagującą na ruch

5 komentarze
Pełny kod źródłowy
Kurs programowania systemów interaktywnych z wykorzystaniem OpenCV, który jakiś czas temu rozpocząłem na łamach tego bloga (patrz: etykieta OpenCV), byłby niekompletny, gdyby zabrakło opisu interakcji ruchowej. Obsługa klawiatury i myszki są oczywiście ważne, ale już na nikim nie robią szczególnego wrażenia. OpenCV jak wiadomo jest przeznaczony do śledzenia i analizy ruchu i to jest jego główna siła.

W dzisiejszym wpisie zostanie pokazane jak dodać kolejną warstwę interakcji poprzez analizę i detekcję ruchu. Wpis ten będzie naturalnym rozszerzeniem przykładów omawianych wcześniej, dlatego zachęcam do ich wcześniejszej analizy. Szczególnie istotne są dwa z nich:
Dobra wiadomość jest taka, że dzięki wprowadzeniu obiektowego szkieletu aplikacji, rozszerzenie aplikacji o interakcję ruchową będzie niezwykle proste i nie będzie wymagało wielu zmian w kodzie. Dla przykładu kod kulki praktycznie pozostaje bez zmian. Logika aplikacji jest również taka sama jak dla poprzedniego przykładu.

Pierwszą rzeczą, którą należy zrobić jest dodanie kilku pomocniczych zmiennych, które będą zawierać pośrednie etapu przetwarzania obrazu.

// plik: interactive_app_tutorial-MainApp.h
class MainApp
{
private:
  Mat cam_frame, img_gray, img_first, img_prev, img_diff, img_bin;

Następnie należy rozszerzyć funkcję update klasy MainApp o sprawdzanie na ile każda z kulek pokrywa się z fragmentami obrazu binarnego, gdzie został wykryty ruch.

// plik: interactive_app_tutorial-MainApp.cpp
for (int k = 0; k < myBall.size(); k++) {
  movementAmount = 0;
  ball_dim = myBall[k].getDim();
  ball_x   = myBall[k].getX();
  ball_y   = myBall[k].getY();
  
  for (int i=(-1*ball_dim); i<ball_dim; i++)
    for (int j=(-1*ball_dim); j<ball_dim; j++)
      if ( dist(0,0,i,j) < ball_dim ) {          
        canvas_ind_x = ball_x + i;
        canvas_ind_y = ball_y + j;
        
        if ( canvas_ind_x<=0 || canvas_ind_y<=0 || 
             canvas_ind_x>CANVAS_WIDTH || canvas_ind_y>CANVAS_HEIGHT) 
          continue; 
        
        if ( img_bin.at<uchar>(canvas_ind_y,canvas_ind_x) > 30) {
          movementAmount++;
        }
      }
      
      if (movementAmount > max_motion_points)
        myBall[k].decrementLives();
      else
        if (!pauseBallsMove) myBall[k].update();
}

W powyższym przykładzie widać, że jeśli więcej niż 30 pikseli leży na masce (zmienna img_bin) wskazującej miejsce detekcji ruchu to możemy uznać, że kulka została "dotknięta". Oczywiście można zmienić tą wartość na inną, lub nawet ustalać ją w zależności od wielkości elementu zbijanego. To prawdopodobnie byłoby lepsze rozwiązanie od obecnego, dlatego zachęcam czytelników do samodzielnych eksperymentów.