# Calibrate a page

Hello,

I'm trying to find whenever checkboxes on a scanned sheet of paper are checked or not. Before analyzing the average color of the checkbox and compare it to the average color of blank area on the page, I need to find position of checkboxes.

To do so, I need to "calibrate" my page (because the page can be slightly rotated, and because the scanner autocrop the sheet of paper, not everytime the same way) . I have drawn 3 black squares on the sheet of paper, that I have put near the top right corner, bottom left corner and bottom right corner of the page. I'm trying to find the position of this 3 squares to be able to find the position of checkboxes (I know their position in centimeter, relativly to the calibrating squares, and I know the distance in centimeter between the calibrating squares).

I used the findsquares function from opencv square example. It works, but it finds multiples times the same square (It detect about 500 to 1000 time each square). So I need to filter the results from findsquares. I tried first to write some code to detect square which is most on the top and on the right and the same for the 2 other squares. But I realized after some trials, that it is not possible to define a"top right" corner when there is more than 3 squares (i got thousand of them) randomly disposed on a plane.

Then, I tried to find which squares are really close of the other and delete them. But I got error on execution (list iterator not incrementable), as I must be out of range in some way. Below the code i use. The "squares" variable comes from findSquares(const Mat& image, vector<vector<point> >& squares) from opencv example.

std::list<vector<Point>> liste_square(squares.begin(), squares.end());

for (list<vector<Point>>::iterator x = liste_square.begin(); x != liste_square.end();x++) {
vector<Point> rect1 = *x;
for (list<vector<Point>>::iterator y = x++; y != liste_square.end();)
{

vector<Point> rect2 = *y;
Point middle1 = (rect1 + rect1)*0.5;
Point middle2 = (rect2 + rect2)*0.5;

Point littlediag = rect1 - rect1; //diag of a square
Point bigdiag = middle2 - middle1; //vector between the two centers of squares

double diago = cv::sqrt(littlediag.x*littlediag.x + littlediag.y*littlediag.y);
double distance = cv::sqrt(bigdiag.x*bigdiag.x + bigdiag.y*bigdiag.y);

if (distance  <= diago)
{
y = liste_square.erase(y);
}
else
{
y++;
}

}

}


So now, I'm a little bit lost. I don't know which way is the better : Try to fix the "list iterator not incrementable" error, find another way to calibrate the page than 3 black squares, improve findsquares function...

Do you have any suggestion?

EDIT: As requested, the image : It's not the original image, as it makes 3.2Mo and 25048x3507 and I cannot upload such big image. I resized the original image using Lanczos filter in Irfanview. In my code, i take the orignal ...

edit retag close merge delete

Sort by » oldest newest most voted i simplified the sample code as below. i hope it helps you. also look this and this

#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>

using namespace cv;
using namespace std;

/**
* Helper function to find a cosine of angle between vectors
* from pt0->pt1 and pt0->pt2
*/
static double angle(Point pt1, Point pt2, Point pt0)
{
double dx1 = pt1.x - pt0.x;
double dy1 = pt1.y - pt0.y;
double dx2 = pt2.x - pt0.x;
double dy2 = pt2.y - pt0.y;
return (dx1*dx2 + dy1*dy2)/sqrt((dx1*dx1 + dy1*dy1)*(dx2*dx2 + dy2*dy2) + 1e-10);
}

int main( int argc, char** argv )
{
if (img.empty())
return -1;

Mat src,gray;

resize(img, src, Size(img.cols/2, img.rows/2));

cvtColor(src, gray, CV_BGR2GRAY); // Convert to grayscale

Mat bw;
Canny(gray, bw, 0, 50, 5);

// Find contours
vector<vector<Point> > contours;
findContours(bw.clone(), contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);

vector<Point> squares;
vector<Point> approx;
Mat dst = src.clone();

for (int i = 0; i < contours.size(); i++)
{
// Approximate contour with accuracy proportional
// to the contour perimeter
approxPolyDP(Mat(contours[i]), approx, arcLength(Mat(contours[i]), true)*0.02, true);

// Skip small or non-convex objects
if (fabs(contourArea(contours[i])) < 100 || !isContourConvex(approx))
continue;

if (approx.size() == 4 )
{
// Number of vertices of polygonal curve
int vtc = approx.size();

// Get the cosines of all corners
vector<double> cos;
for (int j = 2; j < vtc+1; j++)
cos.push_back(angle(approx[j%vtc], approx[j-2], approx[j-1]));

// Sort ascending the cosine values
sort(cos.begin(), cos.end());

// Get the lowest and the highest cosine
double mincos = cos.front();
double maxcos = cos.back();

if (vtc == 4 && mincos >= -0.1 && maxcos <= 0.3)

{
for(int j=0; j<4 ;j++ )  // to find topmost,bottommost,rightmost,leftmost contours
squares.push_back(approx[j]); // here all points accumulated first

drawContours( src, contours, i, Scalar(0,0,255), CV_FILLED, 8 );
}
}
}

Rect box = boundingRect(Mat(squares)); // finds outer rectangle of contours
rectangle(src,box,Scalar(0,255,0),1);
imshow("src", src);
waitKey(0);
return 0;
}


final revised result more

Thanks! I've tested your example which indeed return only 10 squarse instead of the ~1000 I got earlier. Now I'm trying to understand why it's working better, and how I can find the top right, bottom left and right corner between the 10 squares. If i did not suceed, I'm thinking of using template matching as suggested in one of your link.

squares.cpp has an algorithm that try to find every square in every color level. so it finds one square many times. in this example image has filtered only one time and found squares .

i edited the code. please try it. and you can acccept the answer if it solve your problem.

i wonder what is the functionality of qr code at the document. indeed you can make the calibration with only qr code i think.

The qrcode is here to identify the type of document. Indeed I can use it to calibrate, but I just though the QRCode was too small to have good calibration result.(but i didn't test it......)

Official site

GitHub

Wiki

Documentation