5

I am working on an implementation where I have a rectangle shaped image in an big background image. I am trying to programmatically retrieve the rectangle shaped image from the big image and retrieve text information from that particular rectangle image. I am trying to use Open-CV third party framework, but couldn't able to retrieve the rectangle image from the big background image. Could someone please guide me, how i can achieve this?

UPDATED:

I found the Link to find out the square shapes using OpenCV. Can i get it modified for finding Rectangle shapes? Can someone guide me on this?

UPDATED LATEST:

I got the code finally, here is it below.

    - (cv::Mat)cvMatWithImage:(UIImage *)image
{
    CGColorSpaceRef colorSpace = CGImageGetColorSpace(image.CGImage);
    CGFloat cols = image.size.width;
    CGFloat rows = image.size.height;

    cv::Mat cvMat(rows, cols, CV_8UC4); // 8 bits per component, 4 channels

    CGContextRef contextRef = CGBitmapContextCreate(cvMat.data,                 // Pointer to backing data
                                                    cols,                       // Width of bitmap
                                                    rows,                       // Height of bitmap
                                                    8,                          // Bits per component
                                                    cvMat.step[0],              // Bytes per row
                                                    colorSpace,                 // Colorspace
                                                    kCGImageAlphaNoneSkipLast |
                                                    kCGBitmapByteOrderDefault); // Bitmap info flags

    CGContextDrawImage(contextRef, CGRectMake(0, 0, cols, rows), image.CGImage);
    CGContextRelease(contextRef);

    return cvMat;
}
-(UIImage *)UIImageFromCVMat:(cv::Mat)cvMat
{
    NSData *data = [NSData dataWithBytes:cvMat.data length:cvMat.elemSize()*cvMat.total()];
    CGColorSpaceRef colorSpace;
    if ( cvMat.elemSize() == 1 ) {
        colorSpace = CGColorSpaceCreateDeviceGray();
    }
    else {
        colorSpace = CGColorSpaceCreateDeviceRGB();
    }

    //CFDataRef data;
    CGDataProviderRef provider = CGDataProviderCreateWithCFData( (CFDataRef) data ); // It SHOULD BE (__bridge CFDataRef)data
    CGImageRef imageRef = CGImageCreate( cvMat.cols, cvMat.rows, 8, 8 * cvMat.elemSize(), cvMat.step[0], colorSpace, kCGImageAlphaNone|kCGBitmapByteOrderDefault, provider, NULL, false, kCGRenderingIntentDefault );
    UIImage *finalImage = [UIImage imageWithCGImage:imageRef];
    CGImageRelease( imageRef );
    CGDataProviderRelease( provider );
    CGColorSpaceRelease( colorSpace );
    return finalImage;
}
-(void)forOpenCV
{
    imageView = [UIImage imageNamed:@"myimage.jpg"];
    if( imageView != nil )
    {
        cv::Mat tempMat = [imageView CVMat];

        cv::Mat greyMat = [self cvMatWithImage:imageView];
        cv::vector<cv::vector<cv::Point> > squares;

        cv::Mat img= [self debugSquares: squares: greyMat];

        imageView = [self UIImageFromCVMat: img];

        self.imageView.image = imageView;
    }
}

double angle( cv::Point pt1, cv::Point pt2, cv::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);
}

- (cv::Mat) debugSquares: (std::vector<std::vector<cv::Point> >) squares : (cv::Mat &)image
{
    NSLog(@"%lu",squares.size());

    // blur will enhance edge detection

    //cv::Mat blurred(image);
    cv::Mat blurred = image.clone();
    medianBlur(image, blurred, 9);

    cv::Mat gray0(image.size(), CV_8U), gray;
    cv::vector<cv::vector<cv::Point> > contours;

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

        // try several threshold levels
        const int threshold_level = 2;
        for (int l = 0; l < threshold_level; l++)
        {
            // Use Canny instead of zero threshold level!
            // Canny helps to catch squares with gradient shading
            if (l == 0)
            {
                Canny(gray0, gray, 10, 20, 3); //

                // Dilate helps to remove potential holes between edge segments
                dilate(gray, gray, cv::Mat(), cv::Point(-1,-1));
            }
            else
            {
                gray = gray0 >= (l+1) * 255 / threshold_level;
            }

            // Find contours and store them in a list
            findContours(gray, contours, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE);

            // Test contours
            cv::vector<cv::Point> approx;
            for (size_t i = 0; i < contours.size(); i++)
            {
                // approximate contour with accuracy proportional
                // to the contour perimeter
                approxPolyDP(cv::Mat(contours[i]), approx, arcLength(cv::Mat(contours[i]), true)*0.02, true);

                // 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(cv::Mat(approx))) > 1000 &&
                    isContourConvex(cv::Mat(approx)))
                {
                    double maxCosine = 0;

                    for (int j = 2; j < 5; j++)
                    {
                        double cosine = fabs(angle(approx[j%4], approx[j-2], approx[j-1]));
                        maxCosine = MAX(maxCosine, cosine);
                    }

                    if (maxCosine < 0.3)
                        squares.push_back(approx);
                }
            }
        }
    }

    NSLog(@"squares.size(): %lu",squares.size());


    for( size_t i = 0; i < squares.size(); i++ )
    {
        cv::Rect rectangle = boundingRect(cv::Mat(squares[i]));
        NSLog(@"rectangle.x: %d", rectangle.x);
        NSLog(@"rectangle.y: %d", rectangle.y);

        if(i==squares.size()-1)////Detecting Rectangle here
        {
            const cv::Point* p = &squares[i][0];

            int n = (int)squares[i].size();

            NSLog(@"%d",n);

            line(image, cv::Point(507,418), cv::Point(507+1776,418+1372), cv::Scalar(255,0,0),2,8);

            polylines(image, &p, &n, 1, true, cv::Scalar(255,255,0), 5, CV_AA);

            int fx1=rectangle.x;
                NSLog(@"X: %d", fx1);
            int fy1=rectangle.y;
                NSLog(@"Y: %d", fy1);
            int fx2=rectangle.x+rectangle.width;
                NSLog(@"Width: %d", fx2);
            int fy2=rectangle.y+rectangle.height;
                NSLog(@"Height: %d", fy2);

            line(image, cv::Point(fx1,fy1), cv::Point(fx2,fy2), cv::Scalar(0,0,255),2,8);

        }

    }

    return image;
}

Thank you.

Getsy
  • 4,887
  • 16
  • 78
  • 139
  • Hi, Anyone help me on this please. Is there any sample for detecting a rectangle shaped image in a background image? – Getsy Dec 20 '12 at 19:56
  • If you could [help me to install openCV correctly](http://stackoverflow.com/questions/13905471/opencv-2-4-3-ios-framework-compiler-trouble-recognising-some-c-headers), then I would be happy to help you with your problem! – foundry Dec 21 '12 at 15:02
  • Have you actually tried _running_ squares.c (or [squares.cpp](https://github.com/Itseez/opencv/blob/master/samples/cpp/squares.cpp), the c++ version)? I've looked through the code - I can't run it (see above comment) - and it seems to me it will detect any kind of rectangle, not just squares. – foundry Dec 21 '12 at 16:32
  • I have not run that c++ file. But, from my program how would i call those methods to find the rectangle shapes? Note: I already modified OpenCV sample project and trying it, not yet succeeded. If you could share me some sample, that would be good. – Getsy Dec 21 '12 at 19:03
  • Hi He Was, I am not any errors with opencv. I have included sqaures.cpp file as well now. But, how do i call findSqaure method from my obj c code and use for finding out rectangle? If you could advise me more, that would be grateful to you. Let me know if we can chat further on this rather than here? – Getsy Dec 24 '12 at 09:23
  • You can mix C++ an Objective C in a single file by using a .mm file instead of a .m file. I don' know if this answers your question. – Victor Engel Dec 27 '12 at 04:00
  • Hi He Was, I have updated my latest code in my question, which is a working code, please see the question above. – Getsy Dec 27 '12 at 18:22
  • For now, just one question ... Is the line " cv::Mat tempMat = [imageView CVMat];" redundant in -forOpenCV? If not what is it doing? – foundry Dec 28 '12 at 04:15
  • Another question... Are you getting rectangles (as i suspected) or just squares in your output? – foundry Dec 28 '12 at 04:16
  • I have your version of the code running now, and can answer my own questions... [1] `cv::Mat tempMat = [imageView CVMat]` is redundant and throws up errors (CVMat is not a method known to imageView ... it looks like code intended for use with an imageView category to convert to and from CVMats); [2] the original 'squares.cpp' code does, as I expected, find rectangles not just squares - although I see you've been playing with that aspect since you got the code running! – foundry Dec 28 '12 at 13:55
  • @Getsy, you should change your debugSquares obj-c method ...it's lacking a keyword signature for the second parameter ... `- debugSquares:squares :image` would read better as `-debugSquares:squares forImage:image` - much more objC-like, which is important when you are playing with c++ in the same file, just to keep things clear. I still intend to look at your code in more detail when I get a chance later today, but really, congratulations on getting it to work, it is running fine for me too. – foundry Dec 28 '12 at 18:34
  • Hi, Sorry for the late reply, as i've been busy with something else. The code i pasted is working fine for you. I think, it detects rectangle. Whatever the image i provide, it draws the same image at this line "self.imageView.image = imageView;" . I am little confused that how we can find each rectangle images from the given full image? Please let me know if we can char further to work on this issue and complete it. – Getsy Dec 29 '12 at 04:23
  • Hi He Was, Can we chat on this issue further? – Getsy Dec 29 '12 at 19:32
  • @Getsy, I just posted a more detailed reply. I may get something up on github later. – foundry Jan 02 '13 at 14:21
  • Hi He Was, Thank you for the reply. I want to detect all the available rectangle (NOT square shape) from the given bigger image. Please share me your idea and sample source, i'm waiting. Thank you very much for the detailed earlier reply. – Getsy Jan 02 '13 at 17:37
  • @Getsy, I've [posted the project on github](https://github.com/foundry/OpenCVSquares) ... it's functionally the same as your solution, but cleaner as most of the c++ is still in a c++ file very similar to the original. There are some sliders in the demo so you can see the effect of changing a few parameters. Mostly, 'tolerance' will select from rectangle (with strict angle tolerance) to quadrilaterals (with looser angle tolerance). – foundry Jan 03 '13 at 01:51
  • Hi He Was, Thanks a lot! I am using your code you mentioned in detail in your below answer like "It took a while, and I had to raise another question on stackoverflow to deal with my poor c++ knowledge.... ". Is this fine? Is this same uploaded in Github too? It detects the rectangle as well, but sometimes if the rectangle is not very clear in the image, it couldn't be able to detect that shape. – Getsy Jan 03 '13 at 04:36

2 Answers2

6

Here is a full answer using a small wrapper class to separate the c++ from objective-c code.

I had to raise another question on stackoverflow to deal with my poor c++ knowledge - but I have worked out everything we need to interface c++ cleanly with objective-c code, using the squares.cpp sample code as an example. The aim is to keep the original c++ code as pristine as possible, and to keep the bulk of the work with openCV in pure c++ files for (im)portability.

I have left my original answer in place as this seems to go beyond an edit. The complete demo project is on github

CVViewController.h / CVViewController.m

  • pure Objective-C

  • communicates with openCV c++ code via a WRAPPER... it neither knows nor cares that c++ is processing these method calls behind the wrapper.

CVWrapper.h / CVWrapper.mm

  • objective-C++

does as little as possible, really only two things...

  • calls to UIImage objC++ categories to convert to and from UIImage <> cv::Mat
  • mediates between CVViewController's obj-C methods and CVSquares c++ (class) function calls

CVSquares.h / CVSquares.cpp

  • pure C++
  • CVSquares.cpp declares public functions inside a class definition (in this case, one static function).
    This replaces the work of main{} in the original file.
  • We try to keep CVSquares.cpp as close as possible to the C++ original for portability.

CVViewController.m

//remove 'magic numbers' from original C++ source so we can manipulate them from obj-C
#define TOLERANCE 0.01
#define THRESHOLD 50
#define LEVELS 9

UIImage* image =
        [CVSquaresWrapper detectedSquaresInImage:self.image
                                       tolerance:TOLERANCE
                                       threshold:THRESHOLD
                                          levels:LEVELS];

CVSquaresWrapper.h

//  CVSquaresWrapper.h

#import <Foundation/Foundation.h>

@interface CVSquaresWrapper : NSObject

+ (UIImage*) detectedSquaresInImage:(UIImage*)image
                          tolerance:(CGFloat)tolerance
                          threshold:(NSInteger)threshold
                             levels:(NSInteger)levels;

@end

CVSquaresWrapper.mm

//  CVSquaresWrapper.mm
//  wrapper that talks to c++ and to obj-c classes

#import "CVSquaresWrapper.h"
#import "CVSquares.h"
#import "UIImage+OpenCV.h"

@implementation CVSquaresWrapper

+ (UIImage*) detectedSquaresInImage:(UIImage*) image
                          tolerance:(CGFloat)tolerance
                          threshold:(NSInteger)threshold
                             levels:(NSInteger)levels
{
    UIImage* result = nil;

        //convert from UIImage to cv::Mat openCV image format
        //this is a category on UIImage
    cv::Mat matImage = [image CVMat]; 


        //call the c++ class static member function
        //we want this function signature to exactly 
        //mirror the form of the calling method 
    matImage = CVSquares::detectedSquaresInImage (matImage, tolerance, threshold, levels);


        //convert back from cv::Mat openCV image format
        //to UIImage image format (category on UIImage)
    result = [UIImage imageFromCVMat:matImage]; 

    return result;
}

@end

CVSquares.h

//  CVSquares.h

#ifndef __OpenCVClient__CVSquares__
#define __OpenCVClient__CVSquares__

    //class definition
    //in this example we do not need a class 
    //as we have no instance variables and just one static function. 
    //We could instead just declare the function but this form seems clearer

class CVSquares
{
public:
    static cv::Mat detectedSquaresInImage (cv::Mat image, float tol, int threshold, int levels);
};

#endif /* defined(__OpenCVClient__CVSquares__) */

CVSquares.cpp

//  CVSquares.cpp

#include "CVSquares.h"

using namespace std;
using namespace cv;

static int thresh = 50, N = 11;
static float tolerance = 0.01;

    //declarations added so that we can move our 
    //public function to the top of the file
static void findSquares(  const Mat& image,   vector<vector<Point> >& squares );
static void drawSquares( Mat& image, vector<vector<Point> >& squares );

    //this public function performs the role of 
    //main{} in the original file (main{} is deleted)
cv::Mat CVSquares::detectedSquaresInImage (cv::Mat image, float tol, int threshold, int levels)
{
    vector<vector<Point> > squares;

    if( image.empty() )
        {
        cout << "Couldn't load " << endl;
        }

    tolerance = tol;
    thresh = threshold;
    N = levels;
    findSquares(image, squares);
    drawSquares(image, squares);

    return image;
}


// the rest of this file is identical to the original squares.cpp except:
// main{} is removed
// this line is removed from drawSquares: 
// imshow(wndname, image); 
// (obj-c will do the drawing)

UIImage+OpenCV.h

The UIImage category is an objC++ file containing the code to convert between UIImage and cv::Mat image formats. This is where you move your two methods -(UIImage *)UIImageFromCVMat:(cv::Mat)cvMat and - (cv::Mat)cvMatWithImage:(UIImage *)image

//UIImage+OpenCV.h

#import <UIKit/UIKit.h>

@interface UIImage (UIImage_OpenCV)

    //cv::Mat to UIImage
+ (UIImage *)imageFromCVMat:(cv::Mat&)cvMat;

    //UIImage to cv::Mat
- (cv::Mat)CVMat;


@end        

The method implementations here are unchanged from your code (although we don't pass a UIImage in to convert, instead we refer to self)

Community
  • 1
  • 1
foundry
  • 31,615
  • 9
  • 90
  • 125
  • I tried to compile your GitHub project in xcode, but it doesn't compile it, and shows the error at "CV_XADD" in 'operations.hpp' file as "xxxx/Headers/core/operations.hpp:2313:17:{2313:17-2313:43}: error: expected '(' for function-style cast or type construction [1]" – Getsy Jan 03 '13 at 18:55
  • thanks for letting me know, i will clone from github on a different machine and check it. – foundry Jan 03 '13 at 19:35
  • I have now tested the repo on 3 versions of OSX (10.6,7 & 8), 3 versions of Xcode (4.2, 4.4.1, 4.5.2), 2 machines (2008 macbook pro, 2010 mac mini). I can replicate your error _only_ on OSX10.6.8/Xcode4.2 (produces the error on both machines). The max iOS target on this set-up is iOS5.0, but that is not the issue as I can target 5.0 and run on 5.0 simulator successfully on OSX10.8.2/Xcode4.5.2. So... what is your setup? – foundry Jan 04 '13 at 00:24
  • Then you will need to compile openCV for your set-up. You should already have done that as you have something up and working, so try taking out the openCV library included in my github project and replacing it with your version. You may need to change the path to import headers (`#import ` in `OpenCVSquares-Prefix.pch`). Good luck.. – foundry Jan 04 '13 at 02:01
  • Hi He Was, I need to ask you some doubts, for me not all the images are getting rectangle deducted. Can we chat, if you could please provide me skype or gtalk id? – Getsy Jan 04 '13 at 07:41
  • @Getsy, this question is becoming too chatty and my answers lengthy. I suggest you accept this answer and open a new question posting where you are at with sample images etc. You should also try to get my project running - the sliders may help you to get a feel for how variables can affect the result (you could start a new project with your working library and copy my code into it or better still upgrade to 10.7/10.8+Xcode4.5.2). On Saturday I _may_ be able to Skype via was.he if you are still getting problems. – foundry Jan 04 '13 at 17:44
2

Here is a partial answer. It is not complete because I am attempting to do the exact same thing and experiencing huge difficulties every step of the way. My knowledge is quite strong on objective-c but really weak on C++

You should read this guide to wrapping c++

And everything on Ievgen Khvedchenia's Computer Vision Talks blog, especially the openCV tutorial. Ievgen has also posted an amazingly complete project on github to go with the tutorial.

Having said that, I am still having a lot of trouble getting openCV to compile and run smoothly.

For example, Ievgen's tutorial runs fine as a finished project, but if I try to recreate it from scratch I get the same openCV compile errors that have been plaguing me all along. It's probably my poor understanding of C++ and it's integration with obj-C.

Regarding squares.cpp

What you need to do

  • remove int main(int /*argc*/, char** /*argv*/) from squares.cpp
  • remove imshow(wndname, image); from drawSquares (obj-c will do the drawing)
  • create a header file squares.h
  • make one or two public functions in the header file which you can call from obj-c (or from an obj-c/c++ wrapper)

Here is what I have so far...

class squares
{
public:
         static cv::Mat& findSquares( const cv::Mat& image, cv::vector<cv::vector<cv::Point> >& squares );
         static cv::Mat& drawSquares( cv::Mat& image, const cv::vector<cv::vector<cv::Point> >& squares );

};

you should be able to reduce this to a single method, say processSquares with one input cv::Mat& image and one return cv::Mat& image. That method would declare squares and call findSquares and drawSquares within the .cpp file.

The wrapper will take an input UIImage, convert it to cv::Mat image, call processSquares with that input, and get a result cv::Mat image. That result it will convert back to NSImage and pass back to the objc calling function.

SO that's a neat sketch of what we need to do, I will try and expand this answer once I've actually managed to do any of it!

Community
  • 1
  • 1
foundry
  • 31,615
  • 9
  • 90
  • 125
  • Hi He Was, I have updated my latest code in my question, which is a working code, please see the question above. – Getsy Dec 27 '12 at 18:21
  • @getsy, that's great news that you got it working! I won't be by a real computer until tomorrow, but I am keen to run your result as soon as I am able. – foundry Dec 28 '12 at 04:14