☰ Menu

Scene.hu

Magyar demoscene portál – grafikusok, zenészek, programozók alkotói közössége

OpenCV végkifejlet (3. rész)

Posted by Travis on 2013-07-15, 12:50

travis_mocapElérkeztünk OpenCV-t bemutató sorozatunk utolsó részéhez. Itt csak példákat szeretnék mutatni, mire is lehet használni ezt a programozói felületet. Sajnálatos módon az OpenCV C és C++-os felületei között jelentős különbségek vannak, ami abban is megnyilvánul, hogy ha C++-ban programozunk, több algoritmust használhatunk. Ebben a fejezetben ezért csak C++-os példaprogramok lesznek.

Kontúr keresés

Első példánkban igyekszünk meghatározni egy képen a kontúrokat. A kontúr kereséshez a Canny() metódust használjuk. Bemenetnek egycsatornás, 8 bites képet kell adnunk. A példaprogramban nem csak a képet jelenítjük meg, hanem két GUI elemet is, amivel az algoritmus paramétereit állítgatjuk. Az élkeresés hatékonyságát Gauss elmosással növeljük.

#include <opencv2/opencv.hpp>

using namespace cv;

int main(int argc, char **argv){
  Mat raw, gray;
  VideoCapture capture(0);
  bool run = true;
  int tb1 = 129, tb2 = 233;

  namedWindow("Edge", 0);
  createTrackbar("Tb1", "Edge", &tb1, 1000,0);
  createTrackbar("Tb2", "Edge", &tb2, 1000,0);

  while(run){
     capture >> raw;
     cvtColor(raw, gray, CV_BGR2GRAY);
     GaussianBlur(gray, gray, Size(7,7), 15, 15);
     Canny(gray, gray, tb1, tb2, 5, true);
     imshow("Edge", gray);
     if(waitKey(30) >= 0) run = 0;
  }

  return(0);
}

 

A GUI készítésénél vegyük észre, hogy minden elemet stringekkel azonosítunk és ugyancsak stringek segítségével határozzuk meg, melyik ablakon jelenjen meg. Ebben a példában csak egy ablakot hozunk létre “Edge” néven.

Háttér eltávolítás

Míg az előző példát akár C-ben is megírhattuk volna, ez a példa már olyan elemeket használ, amit csak C++-ban érhetünk el. A program megpróbálja a nyers képen elkülöníteni a hátteret az előtértől. Az előteret ezután elmentjük egy maszkba, amit felhasználunk arra, hogy a nyers képből az előteret átmásoljuk egy másik háttér elé. Ez a másik háttér egy időjárás térkép lesz. (Figyeljünk rá, hogy a webkamera képe és az új háttér ugyanolyan méretű legyen, mert a programban nincs méret korrekció!)

A háttér elválasztása nem működik teljesen pontosan, ez a program nem fog olyan minőséget produkálni, mint amit a TV csatornáknál megszokhattunk. Javaslom, hogy miután elindítottuk a programot, várjunk 5-10 másodpercet, mielőtt a kamera elé sétálunk, ennyi kell ugyanis, hogy “megtanulja”, mi a háttér. A backgroundRatio mező segítségével állíthatjuk be, mennyire legyen az algoritmus precíz és gyors. Az árnyékok ugyancsak zavarhatják az algoritmust. Elméletileg az árnyékokat is felismeri, gyakorlatban nálam nem működött rendesen. A példaprogram paramétereivel játszunk nyugodtan.

 

#include <opencv2/opencv.hpp>

using namespace cv;

int main(int argc, char **argv){
  Mat raw, mask, background;
  VideoCapture capture(0);
  bool run = true;
  BackgroundSubtractorMOG2 subtract;

  subtract.nmixtures = 3;
  subtract.backgroundRatio = 0.01;
  namedWindow("Forecast", 0);
  // the image should be the same size as the cam image
  background = imread("magyarorszag.jpeg", CV_LOAD_IMAGE_COLOR);

  while(run){
     capture >> raw;

     // Create mask
     subtract.operator ()(raw, mask);
     erode(mask, mask, Mat());
     dilate(mask, mask, Mat());
     threshold(mask, mask, 40, 255, THRESH_BINARY);
     // use mask to substract foreground
     Mat tmp = background.clone();
     raw.copyTo(tmp, mask);
     imshow("Forecast", tmp);

     if(waitKey(30) > 0) run = 0;
  }

  return(0);
}

 

Objektum követés

Legvégére hagytam azt a programot, ami elindította ezt a sorozatot: Aha kiváltását. A feladat tehát az, hogy több képkockán keresztül követni kell két elemet, jelen esetünkben két kezet. Azért, hogy megkönnyítsük a dolgunkat, feltűnő színekkel jelöljük a követendő objektumokat. Ezután kiszűrjük csak ezt a színt és egy k-közép klaszterezéssel megállapítjuk a ponthalmaz közepét. Ha minden feltétel megfelelő, a klaszterek közepe a követni kívánt objektumokkal meg fog egyezni.

Mi ez a k-közép klaszterezés? Ez, mint a neve is mutatja, az adathalmazok csoportosítására használható. Az adathalmazoknak számszerű tulajdonságokkal kell rendelkezniük, ami a mi esetünkben a koordináták lesznek. Hátránya, hogy előre meg kell mondanunk, hány csoportra akarjuk bontani az adatainkat. A mi esetünkben ez nem probléma, mert tudjuk, hogy jó esetben két keze van az embernek. Az algoritmus ezután véletlenszerűen valamelyik csoportba osztja az adatokat, majd a kiszámolja a csoport középpontját (veszi a koordináták átlagát, innen a neve), majd megnézi, hogy más csoportból van-e közelebbi pont. Ha van, ez a pont is része lesz a csoportnak, és kezdődik egy új ciklus. Ha minden jól megy, ciklusról ciklusra egyre közelebbi pontok lesznek egy csoportban. Ha viszont zajos az adatunk, vagy rosszul választjuk meg a klaszterek számát, furcsa eredményeket kaphatunk. (Ahogy a bemutató videón is látni lehet majd)

Az algoritmus része az OpenCV-nek, ezért nem kell leprogramoznunk. Lássuk a kódot:

 

#include <opencv2/opencv.hpp>

#define CLUSTERNUM 8

using namespace cv;

int main(int argc, char **argv){
  Mat raw;
  Mat labels, centers(CLUSTERNUM, 2, CV_32FC2);
  VideoCapture capture(argv[1]);
  bool run = true;
  int lb1 = 0, lb2 = 100, lb3 = 100;
  int hb1 = 64, hb2 = 255, hb3 = 255;

  // Create windows
  namedWindow("Param", 0);
  namedWindow("Aha", 0);
  namedWindow("Filt", 0);
  createTrackbar("lb1", "Param", &lb1, 255, 0);
  createTrackbar("lb2", "Param", &lb2, 255, 0);
  createTrackbar("lb3", "Param", &lb3, 255, 0);

  createTrackbar("hb1", "Param", &hb1, 255, 0);
  createTrackbar("hb2", "Param", &hb2, 255, 0);
  createTrackbar("hb3", "Param", &hb3, 255, 0);

  while(run){
     capture >> raw;
     Mat filtered(raw.size(),CV_8U);
     inRange(raw, Scalar(lb1, lb2, lb3), Scalar(hb1, hb2, hb3), filtered);
     Mat dummy(filtered.rows * filtered.cols,2,CV_32F);
     int z = 0;
     for(int x = 0; x < filtered.rows; x++){
        for(int y = 0; y < filtered.cols; y++){
           if(filtered.at(y,x) == 255){
              dummy.at(z,0) = x;
              dummy.at(z,1) = y;
              z++;
           }
        }
     }

     Mat points = dummy(Rect(0,0,2,z)); /* Reduce the image */

     // If the picture do not contains enough points, we can not make kmeans clustering
     if(z > CLUSTERNUM){
        kmeans(points, CLUSTERNUM, labels, TermCriteria(CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, 10, 1.0), 3, KMEANS_PP_CENTERS, centers);
        for(int i = 0; i < CLUSTERNUM; i++){
           Point ipt = centers.at(i);
           circle(raw, ipt, 20, Scalar(0,0,255), CV_FILLED, CV_AA);
        }
     }

     imshow("Aha", raw);
     imshow("Filt", filtered);
     if(waitKey(30) >= 0) run = 0;
  }

  return(0);
}

 

A videóról beolvassuk a képkockákat, majd az inRange segítségével kiszűrjük azt a színtartományt, amire szükségünk van. A tartományok beállításához (minden színcsatornához egy tartományt lehet beállítani) a createTrackbar segítségével grafikus elemeket hozunk létre.

A kmeans parancs egy speciális mátrixot vár bemenetnek, ezért a szűrt képünket (filtered) ezekkel az ocsmány egymásba ágyazott ciklusokkal kell átalakítani. Ha erre valaki tud egy szebb módszert, az ne fogja vissza magát. De hogyan is néz ki ez a speciálist mátrix? Mindenképp CV_32F típusúnak kell lennie, és minden egyes sorban az adathalmaz egy elemének kell szerepelnie. (Tehát annyi sorunk lesz, ahány adatunk) Mivel a mi esetünkben két tulajdonsága van az adatoknak (X és Y koordináta), ezért a mátrixnak két oszlopa van.

A kmeans() eredménye a centers változóban lesz. Utolsó lépésként megjelenítjük ezeket piros pöttyökkel. A lefordított program működés közben:

Multiple object tracking with OpenCV from TravisCG on Vimeo.

Remélem sok szép demó fog születni OpenCV felhasználásával.

Categories: Programozás | Tags: ,

Leave a Reply

You must be logged in to post a comment.

Ugrás a lap tetejére Ugrás a lap aljára