squares.cpp 11.6 KB
Newer Older
1 2 3 4
// The "Square Detector" program.
// It loads several images sequentially and tries to find squares in
// each image

5 6
#include "opencv2/core.hpp"
#include "opencv2/core/utility.hpp"
7 8 9 10 11 12 13 14 15 16
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/ocl/ocl.hpp"
#include <iostream>
#include <math.h>
#include <string.h>

using namespace cv;
using namespace std;

17
#define ACCURACY_CHECK
18

19
#ifdef ACCURACY_CHECK
20 21 22 23 24 25
// check if two vectors of vector of points are near or not
// prior assumption is that they are in correct order
static bool checkPoints(
    vector< vector<Point> > set1,
    vector< vector<Point> > set2,
    int maxDiff = 5)
26
{
27 28 29 30 31 32 33 34 35 36
    if(set1.size() != set2.size())
    {
        return false;
    }

    for(vector< vector<Point> >::iterator it1 = set1.begin(), it2 = set2.begin();
            it1 < set1.end() && it2 < set2.end(); it1 ++, it2 ++)
    {
        vector<Point> pts1 = *it1;
        vector<Point> pts2 = *it2;
37 38


39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
        if(pts1.size() != pts2.size())
        {
            return false;
        }
        for(size_t i = 0; i < pts1.size(); i ++)
        {
            Point pt1 = pts1[i], pt2 = pts2[i];
            if(std::abs(pt1.x - pt2.x) > maxDiff ||
                    std::abs(pt1.y - pt2.y) > maxDiff)
            {
                return false;
            }
        }
    }
    return true;
}
#endif

57 58 59
int thresh = 50, N = 11;
const char* wndname = "OpenCL Square Detection Demo";

60

61 62 63
// helper function:
// finds a cosine of angle between vectors
// from pt0->pt1 and from pt0->pt2
64
static double angle( Point pt1, Point pt2, Point pt0 )
65 66 67 68 69 70 71 72
{
    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);
}

73

74 75
// returns sequence of squares detected on the image.
// the sequence is stored in the specified memory storage
76
static void findSquares( const Mat& image, vector<vector<Point> >& squares )
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
{
    squares.clear();
    Mat pyr, timg, gray0(image.size(), CV_8U), gray;

    // down-scale and upscale the image to filter out the noise
    pyrDown(image, pyr, Size(image.cols/2, image.rows/2));
    pyrUp(pyr, timg, image.size());
    vector<vector<Point> > contours;

    // find squares in every color plane of the image
    for( int c = 0; c < 3; c++ )
    {
        int ch[] = {c, 0};
        mixChannels(&timg, 1, &gray0, 1, ch, 1);

        // try several threshold levels
        for( int l = 0; l < N; l++ )
        {
            // hack: use Canny instead of zero threshold level.
            // Canny helps to catch squares with gradient shading
            if( l == 0 )
            {
                // apply Canny. Take the upper threshold from slider
                // and set the lower to 0 (which forces edges merging)
                Canny(gray0, gray, 0, thresh, 5);
                // dilate canny output to remove potential
                // holes between edge segments
                dilate(gray, gray, Mat(), Point(-1,-1));
            }
            else
            {
                // apply threshold if l!=0:
                //     tgray(x,y) = gray(x,y) < (l+1)*255/N ? 255 : 0
                cv::threshold(gray0, gray, (l+1)*255/N, 255, THRESH_BINARY);
            }

            // find contours and store them all as a list
114
            findContours(gray, contours, RETR_LIST, CHAIN_APPROX_SIMPLE);
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158

            vector<Point> approx;

            // test each contour
            for( size_t 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);

                // square contours should have 4 vertices after approximation
                // relatively large area (to filter out noisy contours)
                // and be convex.
                // Note: absolute value of an area is used because
                // area may be positive or negative - in accordance with the
                // contour orientation
                if( approx.size() == 4 &&
                        fabs(contourArea(Mat(approx))) > 1000 &&
                        isContourConvex(Mat(approx)) )
                {
                    double maxCosine = 0;

                    for( int j = 2; j < 5; j++ )
                    {
                        // find the maximum cosine of the angle between joint edges
                        double cosine = fabs(angle(approx[j%4], approx[j-2], approx[j-1]));
                        maxCosine = MAX(maxCosine, cosine);
                    }

                    // if cosines of all angles are small
                    // (all angles are ~90 degree) then write quandrange
                    // vertices to resultant sequence
                    if( maxCosine < 0.3 )
                        squares.push_back(approx);
                }
            }
        }
    }
}


// returns sequence of squares detected on the image.
// the sequence is stored in the specified memory storage
static void findSquares_ocl( const Mat& image, vector<vector<Point> >& squares )
159 160
{
    squares.clear();
161 162 163

    Mat gray;
    cv::ocl::oclMat pyr_ocl, timg_ocl, gray0_ocl, gray_ocl;
164 165 166 167 168 169

    // down-scale and upscale the image to filter out the noise
    ocl::pyrDown(ocl::oclMat(image), pyr_ocl);
    ocl::pyrUp(pyr_ocl, timg_ocl);

    vector<vector<Point> > contours;
170 171
    vector<cv::ocl::oclMat> gray0s;
    ocl::split(timg_ocl, gray0s); // split 3 channels into a vector of oclMat
172 173 174
    // find squares in every color plane of the image
    for( int c = 0; c < 3; c++ )
    {
175
        gray0_ocl = gray0s[c];
176 177 178 179 180 181 182 183 184 185 186 187 188
        // try several threshold levels
        for( int l = 0; l < N; l++ )
        {
            // hack: use Canny instead of zero threshold level.
            // Canny helps to catch squares with gradient shading
            if( l == 0 )
            {
                // do canny on OpenCL device
                // apply Canny. Take the upper threshold from slider
                // and set the lower to 0 (which forces edges merging)
                cv::ocl::Canny(gray0_ocl, gray_ocl, 0, thresh, 5);
                // dilate canny output to remove potential
                // holes between edge segments
189
                ocl::dilate(gray_ocl, gray_ocl, Mat(), Point(-1,-1));
190 191 192 193 194 195
                gray = Mat(gray_ocl);
            }
            else
            {
                // apply threshold if l!=0:
                //     tgray(x,y) = gray(x,y) < (l+1)*255/N ? 255 : 0
196 197
                cv::ocl::threshold(gray0_ocl, gray_ocl, (l+1)*255/N, 255, THRESH_BINARY);
                gray = gray_ocl;
198 199 200
            }

            // find contours and store them all as a list
201
            findContours(gray, contours, RETR_LIST, CHAIN_APPROX_SIMPLE);
202 203 204 205 206 207 208 209

            vector<Point> approx;
            // test each contour
            for( size_t 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);
210

211 212 213 214 215 216 217
                // square contours should have 4 vertices after approximation
                // relatively large area (to filter out noisy contours)
                // and be convex.
                // Note: absolute value of an area is used because
                // area may be positive or negative - in accordance with the
                // contour orientation
                if( approx.size() == 4 &&
218 219
                        fabs(contourArea(Mat(approx))) > 1000 &&
                        isContourConvex(Mat(approx)) )
220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
                {
                    double maxCosine = 0;
                    for( int j = 2; j < 5; j++ )
                    {
                        // find the maximum cosine of the angle between joint edges
                        double cosine = fabs(angle(approx[j%4], approx[j-2], approx[j-1]));
                        maxCosine = MAX(maxCosine, cosine);
                    }

                    // if cosines of all angles are small
                    // (all angles are ~90 degree) then write quandrange
                    // vertices to resultant sequence
                    if( maxCosine < 0.3 )
                        squares.push_back(approx);
                }
            }
        }
    }
}


// the function draws all the squares in the image
242
static void drawSquares( Mat& image, const vector<vector<Point> >& squares )
243 244 245 246 247
{
    for( size_t i = 0; i < squares.size(); i++ )
    {
        const Point* p = &squares[i][0];
        int n = (int)squares[i].size();
248
        polylines(image, &p, &n, 1, true, Scalar(0,255,0), 3, LINE_AA);
249
    }
250 251
}

252

253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274
// draw both pure-C++ and ocl square results onto a single image
static Mat drawSquaresBoth( const Mat& image,
                            const vector<vector<Point> >& sqsCPP,
                            const vector<vector<Point> >& sqsOCL
)
{
    Mat imgToShow(Size(image.cols * 2, image.rows), image.type());
    Mat lImg = imgToShow(Rect(Point(0, 0), image.size()));
    Mat rImg = imgToShow(Rect(Point(image.cols, 0), image.size()));
    image.copyTo(lImg);
    image.copyTo(rImg);
    drawSquares(lImg, sqsCPP);
    drawSquares(rImg, sqsOCL);
    float fontScale = 0.8f;
    Scalar white = Scalar::all(255), black = Scalar::all(0);

    putText(lImg, "C++", Point(10, 20), FONT_HERSHEY_COMPLEX_SMALL, fontScale, black, 2);
    putText(rImg, "OCL", Point(10, 20), FONT_HERSHEY_COMPLEX_SMALL, fontScale, black, 2);
    putText(lImg, "C++", Point(10, 20), FONT_HERSHEY_COMPLEX_SMALL, fontScale, white, 1);
    putText(rImg, "OCL", Point(10, 20), FONT_HERSHEY_COMPLEX_SMALL, fontScale, white, 1);

    return imgToShow;
275 276 277
}


278
int main(int argc, char** argv)
279
{
280 281
    const char* keys =
        "{ i | input   |                    | specify input image }"
282 283
        "{ o | output  | squares_output.jpg | specify output save path}"
        "{ h | help    | false              | print help message }";
284 285 286
    CommandLineParser cmd(argc, argv, keys);
    string inputName = cmd.get<string>("i");
    string outfile = cmd.get<string>("o");
287 288

    if(cmd.get<bool>("help"))
289
    {
290
        cout << "Usage : squares [options]" << endl;
291
        cout << "Available options:" << endl;
292
        cmd.printMessage();
293
        return EXIT_SUCCESS;
294
    }
295

296
    int iterations = 10;
297
    namedWindow( wndname, WINDOW_AUTOSIZE );
298
    vector<vector<Point> > squares_cpu, squares_ocl;
299

300 301
    Mat image = imread(inputName, 1);
    if( image.empty() )
302
    {
303
        cout << "Couldn't load " << inputName << endl;
304
        return EXIT_FAILURE;
305
    }
306

307 308 309 310 311 312 313 314
    int j = iterations;
    int64 t_ocl = 0, t_cpp = 0;
    //warm-ups
    cout << "warming up ..." << endl;
    findSquares(image, squares_cpu);
    findSquares_ocl(image, squares_ocl);


315
#ifdef ACCURACY_CHECK
316 317 318 319 320 321 322 323
    cout << "Checking ocl accuracy ... " << endl;
    cout << (checkPoints(squares_cpu, squares_ocl) ? "Pass" : "Failed") << endl;
#endif
    do
    {
        int64 t_start = cv::getTickCount();
        findSquares(image, squares_cpu);
        t_cpp += cv::getTickCount() - t_start;
324

325

326 327 328 329
        t_start  = cv::getTickCount();
        findSquares_ocl(image, squares_ocl);
        t_ocl += cv::getTickCount() - t_start;
        cout << "run loop: " << j << endl;
330
    }
331 332 333 334 335 336 337
    while(--j);
    cout << "cpp average time: " << 1000.0f * (double)t_cpp / getTickFrequency() / iterations << "ms" << endl;
    cout << "ocl average time: " << 1000.0f * (double)t_ocl / getTickFrequency() / iterations << "ms" << endl;

    Mat result = drawSquaresBoth(image, squares_cpu, squares_ocl);
    imshow(wndname, result);
    imwrite(outfile, result);
338
    waitKey(0);
339

340
    return EXIT_SUCCESS;
341
}