Elé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.