wtorek, 2 marca 2010

Śledzenie blobów

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;
}

1 komentarz:

Anonimowy pisze...

Ten program wywala błąd przy pierwszej próbie skalowania obrazu :) Zmienna maska nie ma rozmiaru więc nie można jej użyć jako wzorca do skalowania.