Dzisiaj trochę o śledzeniu blobów. OpenCV udostępnia tracker o budowie modułowej do śledzenia takich struktur. Niestety nie jest on za dobrze opisany, jedyne co na jego temat znalazłem to ta
nieoficjalna dokumentacja. Tracker to klasa
CvBlobTrackerAuto. Jako parametr przyjmuje strukturę
CvBlobTrackerAutoParam1. Struktura ta przechowuje moduły trackera. Jakie to moduły? Segmentator tła, detektor blobów, tracker blobów, moduł wygładzania trajektorii ruchu i inne. Ładny schemat blokowy znajduje się w opisie na "nieoficjalnej" dokumentacji. Struktura parametrów wygląda następująco:
struct CvBlobTrackerAutoParam1
{
int FGTrainFrames;
CvFGDetector* pFG; // segmentator tła
CvBlobDetector* pBD; // detektor blobów
CvBlobTracker* pBT; // tracker blobów
CvBlobTrackGen* pBTGen;
CvBlobTrackPostProc* pBTPP;
int UsePPData;
CvBlobTrackAnalysis* pBTA;
};
Komentarz dodałem obok najważniejszych modułów (reszta jest opcjonalna). Tracker tworzymy za pomocą funkcji
CvBlobTrackerAuto* cvCreateBlobTrackerAuto1(CvBlobTrackerAutoParam1* param = NULL);, gdzie, jak widać, przekazujemy nasze parametry. Dla standardowo dostępnych modułów mamy funkcje budujące odpowiednie obiekty. I tak np. dla detektora blobów mamy funkcję
cvCreateBlobDetectorXXX gdzie XXX to
Simple lub
CC. Moduły są klasami wirtualnymi, nic nie stoi na przeszkodzie pisania własnych. Tak też uczynimy, żeby móc śledzić skórę. Użyłem prostego algorytmu segmentacji z
poprzedniego wpisu. Klasa CvFGDetector wygląda następująco:
class CV_EXPORTS CvFGDetector: public CvVSModule
{
public:
CvFGDetector(){SetTypeName("FGDetector");};
virtual IplImage* GetMask() = 0;
/* Process current image: */
virtual void Process(IplImage* pImg) = 0;
/* Release foreground detector: */
virtual void Release() = 0;
};
Trzy metody do przedefiniowania :) Uzyskujemy nową klasę
SimpleSkinDetector :
class SimpleSkinDetector : public CvFGDetector
{
IplImage * maska;
public:
SimpleSkinDetector()
{
maska = 0;
SetTypeName("SSD");
};
virtual IplImage* GetMask()
{
return maska;
}
/* Process current image: */
virtual void Process(IplImage* img)
{
if (!maska)
maska = cvCreateImage(cvGetSize(img), 8, 1);
for (int y = 0; y < img->height; y++)
{
uchar* ptr = (uchar*) (img->imageData + y * img->widthStep);
uchar* ptrRet = (uchar*) (maska->imageData + y * maska->widthStep);
for (int x = 0; x < img->width; x++)
{
double b, g, r;
b = ptr[3 * x];
g = ptr[3 * x + 1];
r = ptr[3 * x + 2];
double min = b;
double max = b;
if (min > g)
min = g;
if (min > r)
min = r;
if (max < g)
max = g;
if (max < r)
max = r;
if (r <= 95 ||
g <= 40 ||
b <= 20 ||
max - min <= 15 ||
fabs(r - g) <= 15 ||
r <= g ||
r <= b)
{
ptrRet[x] = 0;
}
else
{
ptrRet[x] = 255;
}
}
}
cvErode(maska, maska, NULL, 1);
cvDilate(maska, maska, NULL, 1);
};
/* Release foreground detector: */
virtual void Release()
{
if (maska)
cvReleaseImage(&maska);
}
};
Wynik działania trackera wygląda następująco
Poniżej kod programu
#include < vector >
#include < cv.h >
#include < cvaux.h >
#include < highgui.h >
#include < iostream >
#include < list >
using namespace std;
void rysujPunkty(IplImage * obr, int x, int y, CvScalar col)
{
int x1 = x - 5;
int x2 = x + 5;
int y1 = y - 5;
int y2 = y + 5;
if (x1 < 0)
x1 = 0;
if (x2 > obr->width - 1)
x2 = obr->width - 1;
if (y1 < 0)
y1 = 0;
if (y2 > obr->height - 1)
y2 = obr->height - 1;
cvLine(obr, cvPoint(x1, y), cvPoint(x2, y), col);
cvLine(obr, cvPoint(x, y1), cvPoint(x, y2), col);
}
class SimpleSkinDetector : public CvFGDetector
{
IplImage * maska;
public:
SimpleSkinDetector()
{
maska = 0;
SetTypeName("SSD");
};
virtual IplImage* GetMask()
{
return maska;
}
/* Process current image: */
virtual void Process(IplImage* img)
{
if (!maska)
maska = cvCreateImage(cvGetSize(img), 8, 1);
for (int y = 0; y < img->height; y++)
{
uchar* ptr = (uchar*) (img->imageData + y * img->widthStep);
uchar* ptrRet = (uchar*) (maska->imageData + y * maska->widthStep);
for (int x = 0; x < img->width; x++)
{
double b, g, r;
b = ptr[3 * x];
g = ptr[3 * x + 1];
r = ptr[3 * x + 2];
double min = b;
double max = b;
if (min > g)
min = g;
if (min > r)
min = r;
if (max < g)
max = g;
if (max < r)
max = r;
if (r <= 95 ||
g <= 40 ||
b <= 20 ||
max - min <= 15 ||
fabs(r - g) <= 15 ||
r <= g ||
r <= b)
{
ptrRet[x] = 0;
}
else
{
ptrRet[x] = 255;
}
}
}
cvErode(maska, maska, NULL, 1);
cvDilate(maska, maska, NULL, 1);
};
/* Release foreground detector: */
virtual void Release()
{
if (maska)
cvReleaseImage(&maska);
}
};
CvScalar kolorki[] = {
CV_RGB(255, 255, 255),
CV_RGB(255, 0, 255),
CV_RGB(0, 255, 255),
CV_RGB(255, 255, 0),
CV_RGB(0, 0, 255),
CV_RGB(255, 0, 0),
CV_RGB(0, 255, 0)
};
void rysujObszar(IplImage*dst, CvBlob* blob, CvScalar kol)
{
CvRect rect = CV_BLOB_RECT(blob);
cvSetImageROI(dst, rect);
cvAddS(dst, kol, dst);
cvResetImageROI(dst);
}
int main(int argc, char** argv)
{
CvCapture* cam = NULL;
if (argc > 1)
cam = cvCreateCameraCapture(atoi(argv[1]));
else
cam = cvCreateCameraCapture(0);
cvNamedWindow("Oryginał", CV_WINDOW_AUTOSIZE);
cvNamedWindow("Skóra", CV_WINDOW_AUTOSIZE);
cvNamedWindow("Bloby", CV_WINDOW_AUTOSIZE);
//+++++++++++++++++++++++++++++++++++++++
CvBlobTrackerAutoParam1 params;
CvBlobTrackerAuto* tracker;
SimpleSkinDetector skd;
params.pFG = &skd;
params.FGTrainFrames = 0;
params.pBD = cvCreateBlobDetectorSimple();
params.pBT = cvCreateBlobTrackerMS();
params.pBTA = cvCreateModuleBlobTrackAnalysisHistPVS();
params.pBTGen = cvCreateModuleBlobTrackGen1();
params.pBTPP = cvCreateModuleBlobTrackPostProcKalman();
tracker = cvCreateBlobTrackerAuto1(¶ms);
//+++++++++++++++++++++++++++++++++++++++
IplImage * _img = cvQueryFrame(cam);
while (true)
{
_img = cvQueryFrame(cam);
// optymalizacja przez zmniejszenie analizowanego obszaru
CvSize polowa = cvSize(_img->width / 2.0, _img->height / 2.0);
IplImage* _img2 = cvCreateImage(polowa, 8, 3);
IplImage * _skinImg = cvCreateImage(polowa, 8, 1);
IplImage* _skinImgTemp = skd.GetMask()
cvResize(_skinImgTemp, _skinImg);
cvResize(_img, _img2);
IplImage * _cross = cvCreateImage(polowa, 8, 3);
cvZero(_cross);
tracker->Process(_img2, NULL);
if (tracker->GetBlobNum() > 0)
{
for (int i = tracker->GetBlobNum(); i > 0; i--)
{
CvScalar kolor = kolorki[i % 7]; // ustalamy kolor
CvBlob* pB = tracker->GetBlob(i - 1); // pobieramy bloba
CvPoint p = cvPointFrom32f(CV_BLOB_CENTER(pB)); // makra CV_BLOB_XXX pozwalaja uzyskac dane o blobach
// rysujemy wyniki
rysujPunkty(_cross, p.x, p.y, kolor);
CvSize s = cvSize(MAX(1, cvRound(CV_BLOB_RX(pB))), MAX(1, cvRound(CV_BLOB_RY(pB))));
rysujObszar(_img2, pB, cvScalar(255, 0, 0));
cvRectangle(_cross, cvPoint(p.x - s.width, p.y - s.height), cvPoint(p.x + s.width, p.y + s.height), kolor, 2, CV_AA);
}
}
cvShowImage("Oryginał", _img2);
cvShowImage("Skóra", _skinImg);
cvShowImage("Bloby", _cross);
cvReleaseImage(&_skinImg);
cvReleaseImage(&_skinImgTemp);
cvReleaseImage(&_cross);
cvReleaseImage(&_img2);
int key = cvWaitKey(3);
if (key == ' ')
break;
}
cvDestroyAllWindows();
// sprzatamy
if (cam)cvReleaseCapture(&cam);
if (params.pBT)cvReleaseBlobTracker(¶ms.pBT);
if (params.pBD)cvReleaseBlobDetector(¶ms.pBD);
if (params.pBTGen)cvReleaseBlobTrackGen(¶ms.pBTGen);
if (params.pBTA)cvReleaseBlobTrackAnalysis(¶ms.pBTA);
if (params.pFG)cvReleaseFGDetector(¶ms.pFG);
if (tracker)cvReleaseBlobTrackerAuto(&tracker);
return 0;
}