Ask Your Question

Calibrate a page

asked 2015-06-29 12:41:20 -0500

kore gravatar image

updated 2015-06-30 13:06:33 -0500


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[0] + rect1[2])*0.5;
        Point middle2 = (rect2[0] + rect2[2])*0.5;

        Point littlediag = rect1[2] - rect1[0]; //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);



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 : page_to_scan

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 ... (more)

edit retag flag offensive close merge delete


Could you provide a sample image

sturkmen gravatar imagesturkmen ( 2015-06-29 20:47:32 -0500 )edit

1 answer

Sort by ยป oldest newest most voted

answered 2015-06-30 14:53:33 -0500

updated 2015-07-01 20:55:27 -0500

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 )
    Mat img = imread(argv[1]);
    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

    // Use Canny instead of threshold to catch squares with gradient shading
    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))

        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
    imshow("src", src);
    return 0;

final revised resultimage description

edit flag offensive delete link 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.

kore gravatar imagekore ( 2015-07-01 13:59:10 -0500 )edit

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 .

sturkmen gravatar imagesturkmen ( 2015-07-01 18:30:05 -0500 )edit

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

sturkmen gravatar imagesturkmen ( 2015-07-01 20:58:45 -0500 )edit

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

sturkmen gravatar imagesturkmen ( 2015-07-01 21:14:36 -0500 )edit

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......)

kore gravatar imagekore ( 2015-07-02 13:56:14 -0500 )edit
Login/Signup to Answer

Question Tools

1 follower


Asked: 2015-06-29 12:41:20 -0500

Seen: 598 times

Last updated: Jul 01 '15