Alexandr Alexandr - 1 month ago 17
Javascript Question

QR code detection with JavaScript and OpenCV - augmented reality web app

I am working on a augmented reality app for browsers which detects a QR code on a DIN A4 paper and projects an 3D oject in the room.
So far i have a working solution which is working with ARUCO codes, but for my app i need an QR code to project an 3D object in the right perspective. This works also with ARUCO codes, but just on close distance. If the marker is to far away it does not work for me. The solution of this is, to scan a QR code, because the contours can be detected on larger dinstances.

I have a solution which is working with QR codes but the code is wirtten in C++.
I have tried to recode te C++ program to JavaScript.

This is the woking solution which works fine with ARUCO codes in JavaScript:

var JS = http://jeromeetienne.github.io/slides/augmentedrealitywiththreejs/

this is the basic file: https://github.com/jeromeetienne/arplayerforthreejs

This is the code in C++ which is working with QR codes:

var C++ = https://github.com/xingdi-eric-yuan/qr-decoder

So far i have wrote the code from crdecoder.cpp in JavaScript.
Instead of the ARUCO tracking I want wo use the script from qrdecoder.cpp to detect a QR code and get the position.
The code should already detect the contours from an QR code and write it in "this.vecpair;" but it is still not working...

The interface for the decoding in JS code is in the file "threex.jsarucomarker.js" and the function is "QR.Detector();"

And this is my still not finished JS script which is a mix of the ARUCO aruco.js code from JS and the QR logic from the C++ qrdecoder.cpp script.

var QR = QR || {};

QR.Marker = function(id, corners){
this.id = id;
this.corners = corners;
};

QR.Detector = function(){
this.grey = new CV.Image();
this.thres = new CV.Image();
this.homography = new CV.Image();
this.binary = [];
this.cont = [];
this.vec4i = [];
this.contours = this.cont.contours = [];
};

QR.Detector.prototype.detect = function(image){
CV.grayscale(image, this.grey);
CV.adaptiveThreshold(this.grey, this.thres, 2, 7);

this.contours = CV.findContours(this.thres, this.binary);

//this.contours = this.findLimitedConturs(this.thres, 8.00, 0.2 * image.width * image.height);

// console.log(this.contours);

this.vecpair = this.getContourPair(this.contours);

console.log(this.vecpair);

// ARUCO CODE.. MAYBE NOT NECESSARY
//this.candidates = this.findCandidates(this.contours, image.width * 0.10, 0.05, 10);
//this.candidates = this.clockwiseCorners(this.candidates);
//this.candidates = this.notTooNear(this.candidates, 10);

//return this.findMarkers(this.grey, this.candidates, 49);
};

/* C++
struct FinderPattern{
Point topleft;
Point topright;
Point bottomleft;
FinderPattern(Point a, Point b, Point c) : topleft(a), topright(b), bottomleft(c) {}
};

bool compareContourAreas ( std::vector<cv::Point> contour1, std::vector<cv::Point> contour2 ) {
double i = fabs( contourArea(cv::Mat(contour1)) );
double j = fabs( contourArea(cv::Mat(contour2)) );
return ( i > j );
}
*/

QR.Detector.prototype.compareContourAreas = function(c1,c2){
var i = abs(CV.contourArea(c1));
var j = abs(CV.contourArea(c2));

console.log(i+' -- '+j);

return (i > j);
};


/* C++
Point getContourCentre(CONT& vec){
double tempx = 0.0, tempy = 0.0;
for(int i=0; i<vec.size(); i++){
tempx += vec[i].x;
tempy += vec[i].y;
}
return Point(tempx / (double)vec.size(), tempy / (double)vec.size());
}
*/
QR.Detector.prototype.getContourCentre = function(vec){

};


/* C++
bool isContourInsideContour(CONT& in, CONT& out){
for(int i = 0; i<in.size(); i++){
if(pointPolygonTest(out, in[i], false) <= 0) return false;
}
return true;
}
*/
QR.Detector.prototype.isContourInsideContour = function(c_in, c_out){
for(var i = 0; i<c_in.length; i++){

//console.log('-- '+c_out+' -- '+c_in[i]);

if(CV.pointPolygonTest(c_out, c_in[i]) == false) return false;
}
return true;
};

/* C++
vector<CONT > findLimitedConturs(Mat contour, float minPix, float maxPix){
vector<CONT > contours;
vector<Vec4i> hierarchy;
findContours(contour, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE);
cout<<"contours.size = "<<contours.size()<<endl;
int m = 0;
while(m < contours.size()){
if(contourArea(contours[m]) <= minPix){
contours.erase(contours.begin() + m);
}else if(contourArea(contours[m]) > maxPix){
contours.erase(contours.begin() + m);
}else ++ m;
}
cout<<"contours.size = "<<contours.size()<<endl;
return contours;
}
*/
QR.Detector.prototype.findLimitedConturs = function(contour, minPix, maxPix){

this.contours = this.cont.contours = [];
this.hierarchy = this.vec4i.hierarchy = [];

CV.findContours(contour, this.contours);

// console.log(this.contours);

var m = 0;
while(m < this.contours.length){
if(CV.contourArea(this.contours[m]) <= minPix){
this.contours.splice(this.contours[0] + m,1);
}else if(CV.contourArea(this.contours[m]) > maxPix){
this.contours.splice(this.contours[0] + m,1);
}else ++ m;
}

// console.log(this.contours.length);

return this.contours;

};

/*
vector<vector<CONT > > getContourPair(vector<CONT > &contours){
vector<vector<CONT > > vecpair;
vector<bool> bflag(contours.size(), false);

for(int i = 0; i<contours.size() - 1; i++){
if(bflag[i]) continue;
vector<CONT > temp;
temp.push_back(contours[i]);
for(int j = i + 1; j<contours.size(); j++){
if(isContourInsideContour(contours[j], contours[i])){
temp.push_back(contours[j]);
bflag[j] = true;
}
}
if(temp.size() > 1){
vecpair.push_back(temp);
}
}
bflag.clear();
for(int i=0; i<vecpair.size(); i++){
sort(vecpair[i].begin(), vecpair[i].end(), compareContourAreas);
}
return vecpair;
}
*/
QR.Detector.prototype.getContourPair = function(contours){
this.vecpair = this.cont.vecpair = [];
var bflag = new Array(contours.length, false); // similar to c++: vector<bool> bflag(contours.size(), false);?

for(var i = 0; i<contours.length - 1; i++){
if(bflag[i] == false){ //similar to c++: if(bflag[i]) continue; ??
var temp = this.cont.temp = [];

//console.log(contours[i]);

temp.push(contours[i]); //similar to c++: temp.push_back(contours[i]); ??
for(var j = i + 1; j<contours.length; j++){
if(this.isContourInsideContour(contours[j], contours[i])){
temp.push(contours[j]);
bflag[j] = true;

// console.log('true');
}
}
if(temp.length > 1){
this.vecpair.push(temp);
}
}
}

//console.log(this.vecpair);

bflag = [];

//console.log(this.vecpair.length);

for(i=0; i<this.vecpair.length; i++){
// sort(this.vecpair[0], this.vecpair[this.vecpair.length], compareContourAreas);

this.vecpair.sort(function(){

console.log('hier');

this.compareContourAreas(this.vecpair[i], this.vecpair[i].length);
});

// console.log(this.vecpair);
}


return this.vecpair;
};

/* C++
void eliminatePairs(vector<vector<CONT > >& vecpair, double minRatio, double maxRatio){
cout<<"maxRatio = "<<maxRatio<<endl;
int m = 0;
bool flag = false;
while(m < vecpair.size()){
flag = false;
if(vecpair[m].size() < 3){
vecpair.erase(vecpair.begin() + m);
continue;
}
for(int i=0; i<vecpair[m].size() - 1; i++){
double area1 = contourArea(vecpair[m][i]);
double area2 = contourArea(vecpair[m][i + 1]);
if(area1 / area2 < minRatio || area1 / area2 > maxRatio){
vecpair.erase(vecpair.begin() + m);
flag = true;
break;
}
}
if(!flag){
++ m;
}
}
if(vecpair.size() > 3){
eliminatePairs(vecpair, minRatio, maxRatio * 0.9);
}
}
*/
QR.Detector.prototype.eliminatePairs = function(){};

/* C++
double getDistance(Point a, Point b){
return sqrt(pow((a.x - b.x), 2) + pow((a.y - b.y), 2));
}
*/
QR.Detector.prototype.getDistance = function(){};

/* C++
FinderPattern getFinderPattern(vector<vector<CONT > > &vecpair){
Point pt1 = getContourCentre(vecpair[0][vecpair[0].size() - 1]);
Point pt2 = getContourCentre(vecpair[1][vecpair[1].size() - 1]);
Point pt3 = getContourCentre(vecpair[2][vecpair[2].size() - 1]);
double d12 = getDistance(pt1, pt2);
double d13 = getDistance(pt1, pt3);
double d23 = getDistance(pt2, pt3);
double x1, y1, x2, y2, x3, y3;
double Max = max(d12, max(d13, d23));
Point p1, p2, p3;
if(Max == d12){
p1 = pt1;
p2 = pt2;
p3 = pt3;
}else if(Max == d13){
p1 = pt1;
p2 = pt3;
p3 = pt2;
}else if(Max == d23){
p1 = pt2;
p2 = pt3;
p3 = pt1;
}
x1 = p1.x;
y1 = p1.y;
x2 = p2.x;
y2 = p2.y;
x3 = p3.x;
y3 = p3.y;
if(x1 == x2){
if(y1 > y2){
if(x3 < x1){
return FinderPattern(p3, p2, p1);
}else{
return FinderPattern(p3, p1, p2);
}
}else{
if(x3 < x1){
return FinderPattern(p3, p1, p2);
}else{
return FinderPattern(p3, p2, p1);
}
}
}else{
double newy = (y2 - y1) / (x2 - x1) * x3 + y1 - (y2 - y1) / (x2 - x1) * x1;
if(x1 > x2){
if(newy < y3){
return FinderPattern(p3, p2, p1);
}else{
return FinderPattern(p3, p1, p2);
}
}else{
if(newy < y3){
return FinderPattern(p3, p1, p2);
}else{
return FinderPattern(p3, p2, p1);
}
}
}
}
*/

QR.Detector.prototype.getFinderPattern = function(){};


This are my added CV functions for the detector
The basic file is "cv.js" from the JavaScript project above https://github.com/jeromeetienne/arplayerforthreejs

This functions should work similar to the C++ versions

pointPolygonTest() = http://docs.opencv.org/2.4/doc/tutorials/imgproc/shapedescriptors/point_polygon_test/point_polygon_test.html

contourArea() = http://docs.opencv.org/2.4/modules/imgproc/doc/structural_analysis_and_shape_descriptors.html#double

//src: http://jsfromhell.com/math/is-point-in-poly
CV.pointPolygonTest = function(poly, pt){
for(var c = false, i = -1, l = poly.length, j = l - 1; ++i < l; j = i)
((poly[i].y <= pt.y && pt.y < poly[j].y) || (poly[j].y <= pt.y && pt.y < poly[i].y))
&& (pt.x < (poly[j].x - poly[i].x) * (pt.y - poly[i].y) / (poly[j].y - poly[i].y) + poly[i].x)
&& (c = !c);
return c;
};

//http://stackoverflow.com/questions/16285134/calculating-polygon-area
CV.contourArea = function(cont){
//console.log('cont: '+cont);

var area = 0; // Accumulates area in the loop
var j = cont.length-1; // The last vertex is the 'previous' one to the first

for (var i=0; i<cont.length; i++)
{
area = area + (cont[j].x+cont[i].x) * (cont[j].y+cont[i].y)
//area = area + (X[j]+X[i]) * (Y[j]-Y[i]);
j = i; //j is previous vertex to i
}
return area/2;

};

Answer

Working JavaScript Version wich detects the contours of an QR Code

var CV = CV || {};

CV.Image = function(width, height, data){
  this.width = width || 0;
  this.height = height || 0;
  this.data = data || [];
};

CV.grayscale = function(imageSrc, imageDst){
  var src = imageSrc.data, dst = imageDst.data, len = src.length,
      i = 0, j = 0;

  for (; i < len; i += 4){
    dst[j ++] =
      (src[i] * 0.299 + src[i + 1] * 0.587 + src[i + 2] * 0.114 + 0.5) & 0xff;
  }

  imageDst.width = imageSrc.width;
  imageDst.height = imageSrc.height;

  return imageDst;
};

CV.threshold = function(imageSrc, imageDst, threshold){
  var src = imageSrc.data, dst = imageDst.data,
      len = src.length, tab = [], i;

  for (i = 0; i < 256; ++ i){
    tab[i] = i <= threshold? 0: 255;
  }

  for (i = 0; i < len; ++ i){
    dst[i] = tab[ src[i] ];
  }

  imageDst.width = imageSrc.width;
  imageDst.height = imageSrc.height;

  return imageDst;
};

CV.adaptiveThreshold = function(imageSrc, imageDst, kernelSize, threshold){
  var src = imageSrc.data, dst = imageDst.data, len = src.length, tab = [], i;

  CV.stackBoxBlur(imageSrc, imageDst, kernelSize);

  for (i = 0; i < 768; ++ i){
    tab[i] = (i - 255 <= -threshold)? 255: 0;
  }

  for (i = 0; i < len; ++ i){
    dst[i] = tab[ src[i] - dst[i] + 255 ];
  }

  imageDst.width = imageSrc.width;
  imageDst.height = imageSrc.height;

  return imageDst;
};

CV.otsu = function(imageSrc){
  var src = imageSrc.data, len = src.length, hist = [],
      threshold = 0, sum = 0, sumB = 0, wB = 0, wF = 0, max = 0,
      mu, between, i;

  for (i = 0; i < 256; ++ i){
    hist[i] = 0;
  }

  for (i = 0; i < len; ++ i){
    hist[ src[i] ] ++;
  }

  for (i = 0; i < 256; ++ i){
    sum += hist[i] * i;
  }

  for (i = 0; i < 256; ++ i){
    wB += hist[i];
    if (0 !== wB){

      wF = len - wB;
      if (0 === wF){
        break;
      }

      sumB += hist[i] * i;

      mu = (sumB / wB) - ( (sum - sumB) / wF );

      between = wB * wF * mu * mu;

      if (between > max){
        max = between;
        threshold = i;
      }
    }
  }

  return threshold;
};

CV.stackBoxBlurMult =
  [1, 171, 205, 293, 57, 373, 79, 137, 241, 27, 391, 357, 41, 19, 283, 265];

CV.stackBoxBlurShift =
  [0, 9, 10, 11, 9, 12, 10, 11, 12, 9, 13, 13, 10, 9, 13, 13];

CV.BlurStack = function(){
  this.color = 0;
  this.next = null;
};

CV.stackBoxBlur = function(imageSrc, imageDst, kernelSize){
  var src = imageSrc.data, dst = imageDst.data,
      height = imageSrc.height, width = imageSrc.width,
      heightMinus1 = height - 1, widthMinus1 = width - 1,
      size = kernelSize + kernelSize + 1, radius = kernelSize + 1,
      mult = CV.stackBoxBlurMult[kernelSize],
      shift = CV.stackBoxBlurShift[kernelSize],
      stack, stackStart, color, sum, pos, start, p, x, y, i;

  stack = stackStart = new CV.BlurStack();
  for (i = 1; i < size; ++ i){
    stack = stack.next = new CV.BlurStack();
  }
  stack.next = stackStart;

  pos = 0;

  for (y = 0; y < height; ++ y){
    start = pos;

    color = src[pos];
    sum = radius * color;

    stack = stackStart;
    for (i = 0; i < radius; ++ i){
      stack.color = color;
      stack = stack.next;
    }
    for (i = 1; i < radius; ++ i){
      stack.color = src[pos + i];
      sum += stack.color;
      stack = stack.next;
    }

    stack = stackStart;
    for (x = 0; x < width; ++ x){
      dst[pos ++] = (sum * mult) >>> shift;

      p = x + radius;
      p = start + (p < widthMinus1? p: widthMinus1);
      sum -= stack.color - src[p];

      stack.color = src[p];
      stack = stack.next;
    }
  }

  for (x = 0; x < width; ++ x){
    pos = x;
    start = pos + width;

    color = dst[pos];
    sum = radius * color;

    stack = stackStart;
    for (i = 0; i < radius; ++ i){
      stack.color = color;
      stack = stack.next;
    }
    for (i = 1; i < radius; ++ i){
      stack.color = dst[start];
      sum += stack.color;
      stack = stack.next;

      start += width;
    }

    stack = stackStart;
    for (y = 0; y < height; ++ y){
      dst[pos] = (sum * mult) >>> shift;

      p = y + radius;
      p = x + ( (p < heightMinus1? p: heightMinus1) * width );
      sum -= stack.color - dst[p];

      stack.color = dst[p];
      stack = stack.next;

      pos += width;
    }
  }

  return imageDst;
};

CV.gaussianBlur = function(imageSrc, imageDst, imageMean, kernelSize){
  var kernel = CV.gaussianKernel(kernelSize);

  imageDst.width = imageSrc.width;
  imageDst.height = imageSrc.height;

  imageMean.width = imageSrc.width;
  imageMean.height = imageSrc.height;

  CV.gaussianBlurFilter(imageSrc, imageMean, kernel, true);
  CV.gaussianBlurFilter(imageMean, imageDst, kernel, false);

  return imageDst;
};

CV.gaussianBlurFilter = function(imageSrc, imageDst, kernel, horizontal){
  var src = imageSrc.data, dst = imageDst.data,
      height = imageSrc.height, width = imageSrc.width,
      pos = 0, limit = kernel.length >> 1,
      cur, value, i, j, k;

  for (i = 0; i < height; ++ i){

    for (j = 0; j < width; ++ j){
      value = 0.0;

      for (k = -limit; k <= limit; ++ k){

        if (horizontal){
          cur = pos + k;
          if (j + k < 0){
            cur = pos;
          }
          else if (j + k >= width){
            cur = pos;
          }
        }else{
          cur = pos + (k * width);
          if (i + k < 0){
            cur = pos;
          }
          else if (i + k >= height){
            cur = pos;
          }
        }

        value += kernel[limit + k] * src[cur];
      }

      dst[pos ++] = horizontal? value: (value + 0.5) & 0xff;
    }
  }

  return imageDst;
};

CV.gaussianKernel = function(kernelSize){
  var tab =
    [ [1],
      [0.25, 0.5, 0.25],
      [0.0625, 0.25, 0.375, 0.25, 0.0625],
      [0.03125, 0.109375, 0.21875, 0.28125, 0.21875, 0.109375, 0.03125] ],
    kernel = [], center, sigma, scale2X, sum, x, i;

  if ( (kernelSize <= 7) && (kernelSize % 2 === 1) ){
    kernel = tab[kernelSize >> 1];
  }else{
    center = (kernelSize - 1.0) * 0.5;
    sigma = 0.8 + (0.3 * (center - 1.0) );
    scale2X = -0.5 / (sigma * sigma);
    sum = 0.0;
    for (i = 0; i < kernelSize; ++ i){
      x = i - center;
      sum += kernel[i] = Math.exp(scale2X * x * x);
    }
    sum = 1 / sum;
    for (i = 0; i < kernelSize; ++ i){
      kernel[i] *= sum;
    }  
  }

  return kernel;
};

CV.findContours = function(imageSrc, binary){
  var width = imageSrc.width, height = imageSrc.height, contours = [],
      src, deltas, pos, pix, nbd, outer, hole, i, j;

  src = CV.binaryBorder(imageSrc, binary);

  deltas = CV.neighborhoodDeltas(width + 2);

  pos = width + 3;
  nbd = 1;

  for (i = 0; i < height; ++ i, pos += 2){

    for (j = 0; j < width; ++ j, ++ pos){
      pix = src[pos];

      if (0 !== pix){
        outer = hole = false;

        if (1 === pix && 0 === src[pos - 1]){
          outer = true;
        }
        else if (pix >= 1 && 0 === src[pos + 1]){
          hole = true;
        }

        if (outer || hole){
          ++ nbd;

          contours.push( CV.borderFollowing(src, pos, nbd, {x: j, y: i}, hole, deltas) );
        }
      }
    }
  }  

  return contours;
};

CV.borderFollowing = function(src, pos, nbd, point, hole, deltas){
  var contour = [], pos1, pos3, pos4, s, s_end, s_prev;

  contour.hole = hole;

  s = s_end = hole? 0: 4;
  do{
    s = (s - 1) & 7;
    pos1 = pos + deltas[s];
    if (src[pos1] !== 0){
      break;
    }
  }while(s !== s_end);

  if (s === s_end){
    src[pos] = -nbd;
    contour.push( {x: point.x, y: point.y} );

  }else{
    pos3 = pos;
    s_prev = s ^ 4;

    while(true){
      s_end = s;

      do{
        pos4 = pos3 + deltas[++ s];
      }while(src[pos4] === 0);

      s &= 7;

      if ( ( (s - 1) >>> 0) < (s_end >>> 0) ){
        src[pos3] = -nbd;
      }
      else if (src[pos3] === 1){
        src[pos3] = nbd;
      }

      contour.push( {x: point.x, y: point.y} );

      s_prev = s;

      point.x += CV.neighborhood[s][0];
      point.y += CV.neighborhood[s][1];

      if ( (pos4 === pos) && (pos3 === pos1) ){
        break;
      }

      pos3 = pos4;
      s = (s + 4) & 7;
    }
  }

  return contour;
};

CV.neighborhood = 
  [ [1, 0], [1, -1], [0, -1], [-1, -1], [-1, 0], [-1, 1], [0, 1], [1, 1] ];

CV.neighborhoodDeltas = function(width){
  var deltas = [], len = CV.neighborhood.length, i = 0;

  for (; i < len; ++ i){
    deltas[i] = CV.neighborhood[i][0] + (CV.neighborhood[i][1] * width);
  }

  return deltas.concat(deltas);
};

CV.approxPolyDP = function(contour, epsilon){
  var slice = {start_index: 0, end_index: 0},
      right_slice = {start_index: 0, end_index: 0},
      poly = [], stack = [], len = contour.length,
      pt, start_pt, end_pt, dist, max_dist, le_eps,
      dx, dy, i, j, k;

  epsilon *= epsilon;

  k = 0;

  for (i = 0; i < 3; ++ i){
    max_dist = 0;

    k = (k + right_slice.start_index) % len;
    start_pt = contour[k];
    if (++ k === len) {k = 0;}

    for (j = 1; j < len; ++ j){
      pt = contour[k];
      if (++ k === len) {k = 0;}

      dx = pt.x - start_pt.x;
      dy = pt.y - start_pt.y;
      dist = dx * dx + dy * dy;

      if (dist > max_dist){
        max_dist = dist;
        right_slice.start_index = j;
      }
    }
  }

  if (max_dist <= epsilon){
    poly.push( {x: start_pt.x, y: start_pt.y} );

  }else{
    slice.start_index = k;
    slice.end_index = (right_slice.start_index += slice.start_index);

    right_slice.start_index -= right_slice.start_index >= len? len: 0;
    right_slice.end_index = slice.start_index;
    if (right_slice.end_index < right_slice.start_index){
      right_slice.end_index += len;
    }

    stack.push( {start_index: right_slice.start_index, end_index: right_slice.end_index} );
    stack.push( {start_index: slice.start_index, end_index: slice.end_index} );
  }

  while(stack.length !== 0){
    slice = stack.pop();

    end_pt = contour[slice.end_index % len];
    start_pt = contour[k = slice.start_index % len];
    if (++ k === len) {k = 0;}

    if (slice.end_index <= slice.start_index + 1){
      le_eps = true;

    }else{
      max_dist = 0;

      dx = end_pt.x - start_pt.x;
      dy = end_pt.y - start_pt.y;

      for (i = slice.start_index + 1; i < slice.end_index; ++ i){
        pt = contour[k];
        if (++ k === len) {k = 0;}

        dist = Math.abs( (pt.y - start_pt.y) * dx - (pt.x - start_pt.x) * dy);

        if (dist > max_dist){
          max_dist = dist;
          right_slice.start_index = i;
        }
      }

      le_eps = max_dist * max_dist <= epsilon * (dx * dx + dy * dy);
    }

    if (le_eps){
      poly.push( {x: start_pt.x, y: start_pt.y} );

    }else{
      right_slice.end_index = slice.end_index;
      slice.end_index = right_slice.start_index;

      stack.push( {start_index: right_slice.start_index, end_index: right_slice.end_index} );
      stack.push( {start_index: slice.start_index, end_index: slice.end_index} );
    }
  }

  return poly;
};

CV.warp = function(imageSrc, imageDst, contour, warpSize){
  var src = imageSrc.data, dst = imageDst.data,
      width = imageSrc.width, height = imageSrc.height,
      pos = 0,
      sx1, sx2, dx1, dx2, sy1, sy2, dy1, dy2, p1, p2, p3, p4,
      m, r, s, t, u, v, w, x, y, i, j;

  m = CV.getPerspectiveTransform(contour, warpSize - 1);

  r = m[8];
  s = m[2];
  t = m[5];

  for (i = 0; i < warpSize; ++ i){
    r += m[7];
    s += m[1];
    t += m[4];

    u = r;
    v = s;
    w = t;

    for (j = 0; j < warpSize; ++ j){
      u += m[6];
      v += m[0];
      w += m[3];

      x = v / u;
      y = w / u;

      sx1 = x >>> 0;
      sx2 = (sx1 === width - 1)? sx1: sx1 + 1;
      dx1 = x - sx1;
      dx2 = 1.0 - dx1;

      sy1 = y >>> 0;
      sy2 = (sy1 === height - 1)? sy1: sy1 + 1;
      dy1 = y - sy1;
      dy2 = 1.0 - dy1;

      p1 = p2 = sy1 * width;
      p3 = p4 = sy2 * width;

      dst[pos ++] = 
        (dy2 * (dx2 * src[p1 + sx1] + dx1 * src[p2 + sx2]) +
         dy1 * (dx2 * src[p3 + sx1] + dx1 * src[p4 + sx2]) ) & 0xff;

    }
  }

  imageDst.width = warpSize;
  imageDst.height = warpSize;

  return imageDst;
};

CV.getPerspectiveTransform = function(src, size){
  var rq = CV.square2quad(src);

  rq[0] /= size;
  rq[1] /= size;
  rq[3] /= size;
  rq[4] /= size;
  rq[6] /= size;
  rq[7] /= size;

  return rq;
};

CV.square2quad = function(src){
  var sq = [], px, py, dx1, dx2, dy1, dy2, den;

  px = src[0].x - src[1].x + src[2].x - src[3].x;
  py = src[0].y - src[1].y + src[2].y - src[3].y;

  if (0 === px && 0 === py){
    sq[0] = src[1].x - src[0].x;
    sq[1] = src[2].x - src[1].x;
    sq[2] = src[0].x;
    sq[3] = src[1].y - src[0].y;
    sq[4] = src[2].y - src[1].y;
    sq[5] = src[0].y;
    sq[6] = 0;
    sq[7] = 0;
    sq[8] = 1;

  }else{
    dx1 = src[1].x - src[2].x;
    dx2 = src[3].x - src[2].x;
    dy1 = src[1].y - src[2].y;
    dy2 = src[3].y - src[2].y;
    den = dx1 * dy2 - dx2 * dy1;

    sq[6] = (px * dy2 - dx2 * py) / den;
    sq[7] = (dx1 * py - px * dy1) / den;
    sq[8] = 1;
    sq[0] = src[1].x - src[0].x + sq[6] * src[1].x;
    sq[1] = src[3].x - src[0].x + sq[7] * src[3].x;
    sq[2] = src[0].x;
    sq[3] = src[1].y - src[0].y + sq[6] * src[1].y;
    sq[4] = src[3].y - src[0].y + sq[7] * src[3].y;
    sq[5] = src[0].y;
  }

  return sq;
};

CV.isContourConvex = function(contour){
  var orientation = 0, convex = true,
      len = contour.length, i = 0, j = 0,
      cur_pt, prev_pt, dxdy0, dydx0, dx0, dy0, dx, dy;

  prev_pt = contour[len - 1];
  cur_pt = contour[0];

  dx0 = cur_pt.x - prev_pt.x;
  dy0 = cur_pt.y - prev_pt.y;

  for (; i < len; ++ i){
    if (++ j === len) {j = 0;}

    prev_pt = cur_pt;
    cur_pt = contour[j];

    dx = cur_pt.x - prev_pt.x;
    dy = cur_pt.y - prev_pt.y;
    dxdy0 = dx * dy0;
    dydx0 = dy * dx0;

    orientation |= dydx0 > dxdy0? 1: (dydx0 < dxdy0? 2: 3);

    if (3 === orientation){
        convex = false;
        break;
    }

    dx0 = dx;
    dy0 = dy;
  }

  return convex;
};

CV.perimeter = function(poly){
  var len = poly.length, i = 0, j = len - 1,
      p = 0.0, dx, dy;

  for (; i < len; j = i ++){
    dx = poly[i].x - poly[j].x;
    dy = poly[i].y - poly[j].y;

    p += Math.sqrt(dx * dx + dy * dy) ;
  }

  return p;
};

CV.minEdgeLength = function(poly){
  var len = poly.length, i = 0, j = len - 1, 
      min = Infinity, d, dx, dy;

  for (; i < len; j = i ++){
    dx = poly[i].x - poly[j].x;
    dy = poly[i].y - poly[j].y;

    d = dx * dx + dy * dy;

    if (d < min){
      min = d;
    }
  }

  return Math.sqrt(min);
};

CV.countNonZero = function(imageSrc, square){
  var src = imageSrc.data, height = square.height, width = square.width,
      pos = square.x + (square.y * imageSrc.width),
      span = imageSrc.width - width,
      nz = 0, i, j;

  for (i = 0; i < height; ++ i){

    for (j = 0; j < width; ++ j){

      if ( 0 !== src[pos ++] ){
        ++ nz;
      }
    }

    pos += span;
  }

  return nz;
};

CV.binaryBorder = function(imageSrc, dst){
  var src = imageSrc.data, height = imageSrc.height, width = imageSrc.width,
      posSrc = 0, posDst = 0, i, j;

  for (j = -2; j < width; ++ j){
    dst[posDst ++] = 0;
  }

  for (i = 0; i < height; ++ i){
    dst[posDst ++] = 0;

    for (j = 0; j < width; ++ j){
      dst[posDst ++] = (0 === src[posSrc ++]? 0: 1);
    }

    dst[posDst ++] = 0;
  }

  for (j = -2; j < width; ++ j){
    dst[posDst ++] = 0;
  }

  return dst;
};


//src: http://jsfromhell.com/math/is-point-in-poly
CV.pointPolygonTest = function(poly, pt){
    for(var c = false, i = -1, l = poly.length, j = l - 1; ++i < l; j = i)
        ((poly[i].y <= pt.y && pt.y < poly[j].y) || (poly[j].y <= pt.y && pt.y < poly[i].y))
        && (pt.x < (poly[j].x - poly[i].x) * (pt.y - poly[i].y) / (poly[j].y - poly[i].y) + poly[i].x)
        && (c = !c);
    return c;
};

//http://stackoverflow.com/questions/16285134/calculating-polygon-area
CV.contourArea = function(cont){    
    //console.log('cont: '+cont);

    var area = 0;  // Accumulates area in the loop   
    var j = cont.length-1;  // The last vertex is the 'previous' one to the first

      for (var i=0; i<cont.length; i++)
      { 
          area = area + (cont[j].x+cont[i].x) * (cont[j].y+cont[i].y);
          //area = area +  (X[j]+X[i]) * (Y[j]-Y[i]); 
          j = i;  //j is previous vertex to i
      }   
      return area/2;

};