00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019 #ifndef LKTracker_H
00020 #define LKTracker_H
00021
00022 #include "Matrix.h"
00023 #include <vector>
00024 #include <algorithm>
00025 #include <math.h>
00026
00027 #define KERNEL_WIDTH 2
00028 #define MAX_PYRAMID_LEVEL 5
00029
00030 #define LK_ITERATIONS 30
00031
00032 #define GRID_SIZE_X 10
00033 #define GRID_SIZE_Y 10
00034
00035
00036 #define GRID_PADDING 0.15
00037
00038 #define N_CORNERNESS_POINTS 0
00039
00040 #define GRID_MARGIN 3
00041 #define KERNEL_SIZE ((KERNEL_WIDTH*2+1)*(KERNEL_WIDTH*2+1))
00042
00043 namespace motld {
00044
00047 class LKTracker
00048 {
00049 public:
00051 LKTracker(int width, int height) : ivWidth(width), ivHeight(height),
00052 ivPrevPyramid(NULL), ivIndex(1) {};
00054 ~LKTracker() {delete ivPrevPyramid;};
00056 void initFirstFrame(unsigned char * img);
00058 void initFirstFrame(const Matrix& img);
00064 void processFrame(const Matrix& curImage, std::vector<ObjectBox>& bbox, std::vector<bool>& isDefined);
00066 bool processFrame(const Matrix& curImage, ObjectBox& bbox, bool dotracking = true);
00068 const std::vector<int> * getDebugPoints() const { return &ivDebugPoints; };
00069
00070 private:
00072 struct LKPyramid
00073 {
00074 std::vector<Matrix> I,Ix,Iy;
00075 LKPyramid(){};
00076 LKPyramid(int nLevels){
00077 I = std::vector<Matrix>(nLevels);
00078 Ix = std::vector<Matrix>(nLevels);
00079 Iy = std::vector<Matrix>(nLevels);
00080 };
00081 };
00083 struct Point2D
00084 {
00085 float x, y;
00086 };
00087 int ivWidth;
00088 int ivHeight;
00089 LKPyramid* ivPrevPyramid;
00090 int ivIndex;
00091 std::vector<int> ivDebugPoints;
00094 inline float median(std::vector<float> * vec, bool compSqrt = false) const;
00099 inline double NCC(const Matrix& aMatrix, const Matrix& bMatrix) const;
00103 inline void pyramidLK(const LKPyramid *prevPyramid, const LKPyramid *curPyramid,
00104 const Point2D *prevPts, Point2D *nextPts,
00105 char *status, int count) const;
00106 };
00107
00108
00109
00110
00111
00112
00113 void LKTracker::initFirstFrame(unsigned char * img)
00114 {
00115 Matrix curImage(ivWidth, ivHeight);
00116 curImage.copyFromCharArray(img);
00117 initFirstFrame(curImage);
00118 }
00119 void LKTracker::initFirstFrame(const Matrix& img)
00120 {
00121 ivPrevPyramid = new LKPyramid(MAX_PYRAMID_LEVEL+1);
00122 ivPrevPyramid->I[0] = img;
00123 for (int i = 0; i <= MAX_PYRAMID_LEVEL; ++i)
00124 {
00125 ivPrevPyramid->I[i].scharrDerivativeX(ivPrevPyramid->Ix[i]);
00126 ivPrevPyramid->I[i].scharrDerivativeY(ivPrevPyramid->Iy[i]);
00127 if (i < MAX_PYRAMID_LEVEL)
00128 ivPrevPyramid->I[i].halfSizeImage(ivPrevPyramid->I[i+1]);
00129 #if DEBUG > 1
00130 char filename[255];
00131 sprintf(filename, "output/img%05d-%d.ppm", 0, i);
00132 ivPrevPyramid->I[i].writeToPGM(filename);
00133 #endif
00134 }
00135 #if DEBUG
00136 std::cout << "#1 LKTracker: initialized, image size = (" << img.xSize() << "," << img.ySize() << ")" << std::endl;
00137 #endif
00138 }
00139
00140 bool LKTracker::processFrame(const Matrix& curImage, ObjectBox& bbox, bool dotracking)
00141 {
00142 std::vector<ObjectBox> boxes;
00143 std::vector<bool> isDefined;
00144 boxes.push_back(bbox);
00145 isDefined.push_back(dotracking);
00146 processFrame(curImage, boxes, isDefined);
00147 bbox = boxes[0];
00148 return isDefined[0];
00149 }
00150
00151 void LKTracker::processFrame(const Matrix& curImage, std::vector<ObjectBox>& bbox, std::vector<bool>& isDefined)
00152 {
00153 int nobs = bbox.size();
00154 if (nobs > 0 && !ivPrevPyramid)
00155 initFirstFrame(curImage);
00156 #if DEBUG
00157 std::cout << "#" << (ivIndex+1) << " LKTracker: ";
00158 #endif
00159 ivDebugPoints.clear();
00160 LKPyramid* curPyramid = new LKPyramid(MAX_PYRAMID_LEVEL+1);
00161 curPyramid->I[0] = curImage;
00162 for (int i = 0; i < MAX_PYRAMID_LEVEL; ++i)
00163 {
00164 curPyramid->I[i].halfSizeImage(curPyramid->I[i+1]);
00165 #if DEBUG > 1
00166 char filename[255];
00167 sprintf(filename, "output/img%05d-%d.ppm", ivIndex, i);
00168 curPyramid->I[i].writeToPGM(filename);
00169 #endif
00170 }
00171
00172 #pragma omp parallel sections
00173 {
00174 #pragma omp section
00175 {
00176
00177 for (int i = 0; i <= MAX_PYRAMID_LEVEL; ++i)
00178 curPyramid->I[i].scharrDerivativeX(curPyramid->Ix[i]);
00179 }
00180 #pragma omp section
00181 {
00182
00183 for (int i = 0; i <= MAX_PYRAMID_LEVEL; ++i)
00184 curPyramid->I[i].scharrDerivativeY(curPyramid->Iy[i]);
00185 }
00186 }
00187
00188 #if DEBUG > 1
00189 Matrix debugFlow(ivWidth, ivHeight, 0);
00190 #endif
00191
00192
00193 for (int obj = 0; obj < nobs; obj++)
00194 {
00195 #if DEBUG
00196 std::cout << "\tObj" << obj << ": ";
00197 #endif
00198 if (isDefined[obj])
00199 {
00200 float oldwidth = bbox[obj].width,
00201 oldheight = bbox[obj].height,
00202 oldcenterx = bbox[obj].x + oldwidth*0.5,
00203 oldcentery = bbox[obj].y + oldheight*0.5;
00204
00205 Point2D points0[N_CORNERNESS_POINTS + GRID_SIZE_X*GRID_SIZE_Y];
00206 Point2D points1[N_CORNERNESS_POINTS + GRID_SIZE_X*GRID_SIZE_Y];
00207 Point2D points2[N_CORNERNESS_POINTS + GRID_SIZE_X*GRID_SIZE_Y];
00208 char status[N_CORNERNESS_POINTS + GRID_SIZE_X*GRID_SIZE_Y];
00209 float fb[N_CORNERNESS_POINTS + GRID_SIZE_X*GRID_SIZE_Y];
00210 float ncc[N_CORNERNESS_POINTS + GRID_SIZE_X*GRID_SIZE_Y];
00211 int count = 0;
00212
00213
00214 float stepX = oldwidth * (1 - 2*GRID_PADDING) / (GRID_SIZE_X-1),
00215 stepY = oldheight * (1 - 2*GRID_PADDING) / (GRID_SIZE_Y-1);
00216 for (int y = 0; y < GRID_SIZE_Y; ++y)
00217 for (int x = 0; x < GRID_SIZE_X; ++x)
00218 {
00219 points0[count].x = bbox[obj].x + GRID_PADDING * oldwidth + x*stepX;
00220 points0[count].y = bbox[obj].y + GRID_PADDING * oldheight + y*stepY;
00221 status[count] = 1;
00222 count++;
00223 }
00224
00225
00226 #if N_CORNERNESS_POINTS > 0
00227 int xstart = std::max(0, (int)(bbox[obj].x)),
00228 xend = std::min(ivWidth, (int)(bbox[obj].x + oldwidth + 1)),
00229 ystart = std::max(0, (int)(bbox[obj].y)),
00230 yend = std::min(ivHeight, (int)(bbox[obj].y + oldheight + 1)),
00231 xsize = xend - xstart,
00232 ysize = yend - ystart;
00233 Matrix Ix2 (xsize, ysize);
00234 Matrix IxIy(xsize, ysize);
00235 Matrix Iy2 (xsize, ysize);
00236 Matrix cornerness(xsize, ysize, 0);
00237 for (int y = ystart; y < yend; y++)
00238 for (int x = xstart; x < xend; x++)
00239 {
00240 Ix2(x-xstart,y-ystart) = ivPrevPyramid->Ix[0](x,y) * ivPrevPyramid->Ix[0](x,y);
00241 IxIy(x-xstart,y-ystart) = ivPrevPyramid->Ix[0](x,y) * ivPrevPyramid->Iy[0](x,y);
00242 Iy2(x-xstart,y-ystart) = ivPrevPyramid->Iy[0](x,y) * ivPrevPyramid->Iy[0](x,y);
00243 }
00244 Ix2.gaussianSmooth(2.0, 3);
00245 IxIy.gaussianSmooth(2.0, 3);
00246 Iy2.gaussianSmooth(2.0, 3);
00247
00248 std::vector<float> cns;
00249 for (int y = GRID_MARGIN; y < ysize - GRID_MARGIN; y++)
00250 for (int x = GRID_MARGIN; x < xsize - GRID_MARGIN; x++)
00251 {
00252 cornerness(x,y) = (Ix2(x,y) + Iy2(x,y)) / 2.0
00253 - sqrt(((Ix2(x,y) + Iy2(x,y)) / 2.0) * ((Ix2(x,y) + Iy2(x,y)) / 2.0)
00254 - Ix2(x,y) * Iy2(x,y) + IxIy(x,y)*IxIy(x,y));
00255 if (cornerness(x,y) > 1.0)
00256 cns.push_back(cornerness(x, y));
00257 }
00258 float threshold = 0;
00259 if (cns.size() > N_CORNERNESS_POINTS)
00260 {
00261 std::nth_element(cns.begin(),cns.end() - N_CORNERNESS_POINTS, cns.end());
00262 threshold = *(cns.end() - N_CORNERNESS_POINTS);
00263 }
00264 for (int y = GRID_MARGIN; y < ysize - GRID_MARGIN; y++)
00265 for (int x = GRID_MARGIN; x < xsize - GRID_MARGIN; x++)
00266 {
00267 if (cornerness(x,y) > threshold && count < N_CORNERNESS_POINTS)
00268 {
00269 points0[count].x = x + xstart;
00270 points0[count].y = y + ystart;
00271 status[count] = 1;
00272 count++;
00273 }
00274 }
00275 #endif //N_CORNERNESS_POINTS > 0
00276
00277
00278 pyramidLK(ivPrevPyramid, curPyramid, points0, points1, status, count);
00279
00280 #if DEBUG > 1
00281 int nfwd = 0;
00282 for (int i = 0; i<count; ++i)
00283 if (status[i] > 0)
00284 ++nfwd;
00285 std::cout << "\t#fwd=" << nfwd;
00286 #endif
00287
00288
00289 pyramidLK(curPyramid, ivPrevPyramid, points1, points2, status, count);
00290
00291
00292 std::vector<float> fbs, nccs;
00293 int nbwd = 0;
00294
00295 for (int i = 0; i < count; ++i)
00296 {
00297 if (status[i] > 0)
00298 {
00299 fb[i] = sqrt((points2[i].x - points0[i].x) * (points2[i].x - points0[i].x)
00300 + (points2[i].y - points0[i].y) * (points2[i].y - points0[i].y));
00301
00302 fbs.push_back(fb[i]);
00303 Matrix mA = ivPrevPyramid->I[0].getRectSubPix(points0[i].x, points0[i].y, 10, 10);
00304 Matrix mB = curImage.getRectSubPix(points2[i].x, points2[i].y, 10, 10);
00305 ncc[i] = NCC(mA, mB);
00306
00307 nccs.push_back(ncc[i]);
00308 ++nbwd;
00309 }
00310 }
00311
00312 float medFB = median(&fbs),
00313 medNCC = median(&nccs);
00314
00315 #if DEBUG > 1
00316 std::cout << ", #bwd=" << nbwd;
00317 std::cout << " \tmedFB=" << medFB << "\tmedNCC=" << medNCC;
00318 #endif
00319
00320
00321 for (int i = 0; i < count; ++i)
00322 {
00323 if (status[i] > 0)
00324 {
00325 if (fb[i] > medFB || fb[i] > 8 || ncc[i] < medNCC)
00326 status[i] = 0;
00327 else
00328
00329 {
00330 ivDebugPoints.push_back(round(points1[i].x));
00331 ivDebugPoints.push_back(round(points1[i].y));
00332 }
00333 }
00334 }
00335
00336
00337 std::vector<float> deltax;
00338 std::vector<float> deltay;
00339 int num = 0;
00340 for (int i = 0; i < count; ++i)
00341 {
00342 if (status[i] > 0)
00343 {
00344 deltax.push_back(points1[i].x - points0[i].x);
00345 deltay.push_back(points1[i].y - points0[i].y);
00346 ++num;
00347 #if DEBUG > 1
00348 debugFlow.drawLine(points0[i].x, points0[i].y, points1[i].x, points1[i].y, 255);
00349 debugFlow.drawCross(points1[i].x, points1[i].y, 255);
00350 #endif
00351 }
00352 }
00353 if (num < 4)
00354 {
00355 #if DEBUG
00356 std::cout << "n=" << num << " => FAILURE: lost object" << std::endl;
00357 #endif
00358 isDefined[obj] = false;
00359 continue;
00360 }
00361
00362
00363 float dx = median(&deltax),
00364 dy = median(&deltay);
00365
00366
00367
00368
00369
00370
00371
00372
00373
00374
00375
00376
00377
00378
00379 float s = 1;
00380 if (num >= 16){
00381 std::vector<float> d2;
00382 float dpx,dpy,ddx,ddy;
00383 for (int i = 0; i < count; ++i)
00384 if (status[i] > 0)
00385 for (int j = i + 1; j < count; ++j)
00386 if (status[j] > 0)
00387 {
00388 ddx = points0[i].x - points0[j].x;
00389 ddy = points0[i].y - points0[j].y;
00390 dpx = points1[i].x - points1[j].x;
00391 dpy = points1[i].y - points1[j].y;
00392 d2.push_back((dpx*dpx + dpy*dpy) / (ddx*ddx + ddy*ddy));
00393 }
00394
00395 if (!d2.empty())
00396 {
00397 s = median(&d2, true);
00398
00399
00400 }
00401 }
00402
00403
00404
00405 float centerx = oldcenterx + dx,
00406 centery = oldcentery + dy;
00407
00408 bbox[obj].x = (centerx - s * oldwidth * 0.5);
00409 bbox[obj].y = (centery - s * oldheight * 0.5);
00410 bbox[obj].width = s * oldwidth;
00411 bbox[obj].height = s * oldheight;
00412 #if DEBUG
00413 std::cout << "n = " << num
00414 << ", new BB: (" << round(bbox[obj].x) << "," << round(bbox[obj].y) << ", "
00415 << round(bbox[obj].width) << "," << round(bbox[obj].height) << ")";
00416
00417 #endif
00418 }else{
00419 #if DEBUG
00420 std::cout << "not defined";
00421 #endif
00422 }
00423 }
00424
00425 #if DEBUG > 1
00426 char filename[255];
00427 sprintf(filename, "output/flow%05d.ppm", ivIndex);
00428 writePPM(filename, ivPrevPyramid->I[0], curImage, debugFlow);
00429 #endif
00430
00431 delete ivPrevPyramid;
00432 ivPrevPyramid = curPyramid;
00433 ++ivIndex;
00434 }
00435
00436 inline void LKTracker::pyramidLK(const LKPyramid *prevPyramid, const LKPyramid *curPyramid,
00437 const Point2D *prevPts, Point2D *nextPts,
00438 char *status, int count) const
00439 {
00440 for (int l = MAX_PYRAMID_LEVEL; l >= 0; --l)
00441 {
00442 int xSize = prevPyramid->I[l].xSize(),
00443 ySize = prevPyramid->I[l].ySize();
00444 #if DEBUG > 2
00445 std::cout << "l=" << l << ", Size=(" << xSize << "," << ySize << ")" << std::endl;
00446 #endif
00447
00448 #pragma omp parallel for default(shared)
00449 for (int i = 0; i < count; i++)
00450 {
00451 if (status[i] > 0)
00452 {
00453
00454 if (l == MAX_PYRAMID_LEVEL)
00455 {
00456 nextPts[i].x = prevPts[i].x;
00457 nextPts[i].y = prevPts[i].y;
00458 }else{
00459 nextPts[i].x *= 2.0;
00460 nextPts[i].y *= 2.0;
00461 }
00462 float px = prevPts[i].x * 1.0/(1<<l), py = prevPts[i].y * 1.0/(1<<l);
00463 int px0 = (int)px, py0 = (int)py;
00464 float pxa = px - px0, pya = py - py0;
00465 #if DEBUG > 2
00466 std::cout << " p=(" << px0 << "+" << pxa << ", " << py0 << "+" << pya << ")" << std::endl;
00467 #endif
00468 if (px < KERNEL_WIDTH || py < KERNEL_WIDTH || px >= xSize-KERNEL_WIDTH-1
00469 || py >= ySize-KERNEL_WIDTH-1)
00470 {
00471 if (l >= MAX_PYRAMID_LEVEL-1){
00472
00473 nextPts[i].x = px;
00474 nextPts[i].y = py;
00475 }else{
00476 status[i] = 0;
00477 }
00478
00479 }else{
00480
00481 float Gx2 = 0, Gxy = 0, Gy2 = 0;
00482 for (int x = px0-KERNEL_WIDTH; x <= px0+KERNEL_WIDTH+1; ++x)
00483 {
00484 float factor = (x == px0-KERNEL_WIDTH ? (1-pxa) : (x == px0+KERNEL_WIDTH+1 ? pxa : 1));
00485 for (int y = py0-KERNEL_WIDTH; y <= py0+KERNEL_WIDTH+1; ++y)
00486 {
00487 factor *= (y == py0-KERNEL_WIDTH ? (1-pya) : (y == py0+KERNEL_WIDTH+1 ? pya : 1));
00488 Gx2 += factor * prevPyramid->Ix[l](x,y)*prevPyramid->Ix[l](x,y);
00489 Gxy += factor * prevPyramid->Ix[l](x,y)*prevPyramid->Iy[l](x,y);
00490 Gy2 += factor * prevPyramid->Iy[l](x,y)*prevPyramid->Iy[l](x,y);
00491 }
00492 }
00493 double denom = Gx2*Gy2 - Gxy*Gxy;
00494 #if DEBUG > 2
00495 std::cout << "\tGx2 = " << Gx2 << "\tGxy = " << Gxy << "\tGy2 = " << Gy2 << "\tdenom = " << denom << std::endl;
00496 #endif
00497 if (denom <= 1e-30)
00498 {
00499 status[i] = 0;
00500
00501 }else{
00502
00503 for (int k = 1; k <= LK_ITERATIONS; k++)
00504 {
00505
00506 float qx = nextPts[i].x, qy = nextPts[i].y;
00507 if (qx < KERNEL_WIDTH || qy < KERNEL_WIDTH || qx >= xSize-KERNEL_WIDTH-1 || qy >= ySize-KERNEL_WIDTH-1)
00508 {
00509 if (l >= MAX_PYRAMID_LEVEL-1){
00510
00511 nextPts[i].x = px;
00512 nextPts[i].y = py;
00513 }else{
00514 status[i] = 0;
00515 }
00516 break;
00517 }
00518 int vx0 = (int)qx - px0, vy0 = (int)qy - py0;
00519 float vxa = fmod(qx, 1), vya = fmod(qx, 1);
00520
00521
00522 float bx = 0, by = 0;
00523 for (int x = px0 - KERNEL_WIDTH; x <= px0 + KERNEL_WIDTH; ++x)
00524 {
00525 for (int y = py0 - KERNEL_WIDTH; y <= py0 + KERNEL_WIDTH; ++y)
00526 {
00527 float dIk = (1-pxa) * ((1-pya)*prevPyramid->I[l](x, y) + pya*prevPyramid->I[l](x, y+1))
00528 + pxa * ((1-pya)*prevPyramid->I[l](x+1, y) + pya*prevPyramid->I[l](x+1, y+1))
00529 - (1-vxa) * ((1-vya)*curPyramid->I[l](x+vx0, y+vy0) + vya*curPyramid->I[l](x+vx0, y+vy0+1))
00530 - vxa * ((1-vya)*curPyramid->I[l](x+vx0+1, y+vy0) + vya*curPyramid->I[l](x+vx0+1, y+vy0+1));
00531 bx += dIk * ((1-pxa) * ((1-pya)*prevPyramid->Ix[l](x, y) + pya*prevPyramid->Ix[l](x, y+1))
00532 + pxa * ((1-pya)*prevPyramid->Ix[l](x+1, y) + pya*prevPyramid->Ix[l](x+1, y+1)));
00533 by += dIk * ((1-pxa) * ((1-pya)*prevPyramid->Iy[l](x, y) + pya*prevPyramid->Iy[l](x, y+1))
00534 + pxa * ((1-pya)*prevPyramid->Iy[l](x+1, y) + pya*prevPyramid->Iy[l](x+1, y+1)));
00535 }
00536 }
00537 float dx = (bx*Gy2 - by*Gxy) / denom,
00538 dy = (by*Gx2 - bx*Gxy) / denom;
00539 nextPts[i].x += dx;
00540 nextPts[i].y += dy;
00541 #if DEBUG > 2
00542 std::cout << "\tf=(" << (nextPts[i].x - prevPts[i].x) << "," << (nextPts[i].y - prevPts[i].y) << ")" << std::endl;
00543 #endif
00544
00545 if (fabs(dx) > 3.5 || fabs(dy) > 3.5)
00546 {
00547 if (l >= 1){
00548 nextPts[i].x = px;
00549 nextPts[i].y = py;
00550 }else{
00551 status[i] = 0;
00552 }
00553 break;
00554 }
00555 }
00556 }
00557 }
00558 }
00559 }
00560 }
00561 }
00562
00563
00564
00565 inline float LKTracker::median(std::vector<float> * vec, bool compSqrt) const
00566 {
00567 int n = vec->size();
00568 if (n == 0)
00569 return 0;
00570 if (n % 2)
00571 {
00572 std::nth_element(vec->begin(), vec->begin() + n/2, vec->end());
00573 return compSqrt ? sqrt(*(vec->begin() + n/2)) : *(vec->begin() + n/2);
00574 }else{
00575 std::nth_element(vec->begin(), vec->begin() + (n/2-1), vec->end());
00576 float tmp = (compSqrt ? sqrt(*(vec->begin() + (n/2-1))) : *(vec->begin() + (n/2-1)));
00577 std::nth_element(vec->begin() + n/2, vec->begin() + n/2, vec->end());
00578 return 0.5 * (tmp + (compSqrt ? sqrt(*(vec->begin() + n/2)) : *(vec->begin() + n/2)));
00579 }
00580 return 0;
00581 }
00582
00583 inline double LKTracker::NCC(const Matrix& aMatrix, const Matrix& bMatrix) const
00584 {
00585 int size = aMatrix.size();
00586 if (size != bMatrix.size()){
00587 std::cerr << "ncc called for matrices with unequal size!" << std::endl;
00588 return 0;
00589 }
00590 float aMean = aMatrix.avg(), bMean = bMatrix.avg();
00591 double sumA = 0, sumB = 0, sumDiff = 0;
00592 for (int i = 0; i < size; ++i)
00593 {
00594 sumA += (aMatrix.data()[i] - aMean) * (aMatrix.data()[i] - aMean);
00595 sumB += (bMatrix.data()[i] - bMean) * (bMatrix.data()[i] - bMean);
00596 sumDiff += (aMatrix.data()[i] - aMean) * (bMatrix.data()[i] - bMean);
00597 }
00598 if (sumA == 0 || sumB == 0)
00599 return 0;
00600 return sumDiff / sqrt(sumA*sumB);
00601 }
00602 }
00603
00604 #endif