8

After I applied thresholding and finding the contours of the object, I used the following code to get the straight rectangle around the object (or the rotated rectangle inputting its instruction):

img = cv2.imread('image.png')
imgray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret,thresh = cv2.threshold(imgray,127,255,cv2.THRESH_BINARY)
# find contours 
contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
cnt = contours[0]
# straight rectangle
x,y,w,h = cv2.boundingRect(cnt)
img= cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2)

see the image

Then I have calculated the number of object and background pixels inside the straight rectangle using the following code:

# rectangle area (total number of object and background pixels inside the rectangle)
area_rect = w*h
# white or object pixels (inside the rectangle)
obj = cv2.countNonZero(imgray)
# background pixels (inside the rectangle)
bac = area_rect - obj

Now I want to adapt the rectangle of the object as a function of the relationship between the background pixel and those of the object, ie to have a rectangle occupying the larger part of the object without or with less background pixel, for example:

How do I create this?

Miki
  • 40,887
  • 13
  • 123
  • 202
H. Chamsaddin
  • 95
  • 2
  • 7
  • [minAreaRect](http://docs.opencv.org/modules/imgproc/doc/structural_analysis_and_shape_descriptors.html?#minarearect) will find the minimum area rotated rectangle, i.e. the one with less background. – Miki Sep 19 '15 at 23:41
  • @Miki yes, but in my case I want the rectangle contains only white pixels (I want adapt it on a greater part of the object which constitutes a rectangle, without or at least with very few background pixels): http://i.stack.imgur.com/oJAw4.png – H. Chamsaddin Sep 19 '15 at 23:54

4 Answers4

9

This problem can be stated as the find the largest rectangle inscribed in a non-convex polygon.

An approximate solution can be found at this link.

This problem can be formulated also as: for each angle, find the largest rectangle containing only zeros in a matrix, explored in this SO question.

My solution is based on this answer. This will find only axis aligned rectangles, so you can easily rotate the image by a given angle and apply this solution for every angle. My solution is C++, but you can easily port it to Python, since I'm using mostly OpenCV function, or adjust the solution in the above mentioned answer accounting for rotation.

Here we are:

#include <opencv2\opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;


// https://stackoverflow.com/a/30418912/5008845
Rect findMinRect(const Mat1b& src)
{
    Mat1f W(src.rows, src.cols, float(0));
    Mat1f H(src.rows, src.cols, float(0));

    Rect maxRect(0,0,0,0);
    float maxArea = 0.f;

    for (int r = 0; r < src.rows; ++r)
    {
        for (int c = 0; c < src.cols; ++c)
        {
            if (src(r, c) == 0)
            {
                H(r, c) = 1.f + ((r>0) ? H(r-1, c) : 0);
                W(r, c) = 1.f + ((c>0) ? W(r, c-1) : 0);
            }

            float minw = W(r,c);
            for (int h = 0; h < H(r, c); ++h)
            {
                minw = min(minw, W(r-h, c));
                float area = (h+1) * minw;
                if (area > maxArea)
                {
                    maxArea = area;
                    maxRect = Rect(Point(c - minw + 1, r - h), Point(c+1, r+1));
                }
            }
        }
    }

    return maxRect;
}


RotatedRect largestRectInNonConvexPoly(const Mat1b& src)
{
    // Create a matrix big enough to not lose points during rotation
    vector<Point> ptz;
    findNonZero(src, ptz);
    Rect bbox = boundingRect(ptz); 
    int maxdim = max(bbox.width, bbox.height);
    Mat1b work(2*maxdim, 2*maxdim, uchar(0));
    src(bbox).copyTo(work(Rect(maxdim - bbox.width/2, maxdim - bbox.height / 2, bbox.width, bbox.height)));

    // Store best data
    Rect bestRect;
    int bestAngle = 0;

    // For each angle
    for (int angle = 0; angle < 90; angle += 1)
    {
        cout << angle << endl;

        // Rotate the image
        Mat R = getRotationMatrix2D(Point(maxdim,maxdim), angle, 1);
        Mat1b rotated;
        warpAffine(work, rotated, R, work.size());

        // Keep the crop with the polygon
        vector<Point> pts;
        findNonZero(rotated, pts);
        Rect box = boundingRect(pts);
        Mat1b crop = rotated(box).clone();

        // Invert colors
        crop = ~crop; 

        // Solve the problem: "Find largest rectangle containing only zeros in an binary matrix"
        // https://stackoverflow.com/questions/2478447/find-largest-rectangle-containing-only-zeros-in-an-n%C3%97n-binary-matrix
        Rect r = findMinRect(crop);

        // If best, save result
        if (r.area() > bestRect.area())
        {
            bestRect = r + box.tl();    // Correct the crop displacement
            bestAngle = angle;
        }
    }

    // Apply the inverse rotation
    Mat Rinv = getRotationMatrix2D(Point(maxdim, maxdim), -bestAngle, 1);
    vector<Point> rectPoints{bestRect.tl(), Point(bestRect.x + bestRect.width, bestRect.y), bestRect.br(), Point(bestRect.x, bestRect.y + bestRect.height)};
    vector<Point> rotatedRectPoints;
    transform(rectPoints, rotatedRectPoints, Rinv);

    // Apply the reverse translations
    for (int i = 0; i < rotatedRectPoints.size(); ++i)
    {
        rotatedRectPoints[i] += bbox.tl() - Point(maxdim - bbox.width / 2, maxdim - bbox.height / 2);
    }

    // Get the rotated rect
    RotatedRect rrect = minAreaRect(rotatedRectPoints);

    return rrect;
}



int main()
{
    Mat1b img = imread("path_to_image", IMREAD_GRAYSCALE);

    // Compute largest rect inside polygon
    RotatedRect r = largestRectInNonConvexPoly(img);

    // Show
    Mat3b res;
    cvtColor(img, res, COLOR_GRAY2BGR);

    Point2f points[4];
    r.points(points);

    for (int i = 0; i < 4; ++i)
    {
        line(res, points[i], points[(i + 1) % 4], Scalar(0, 0, 255), 2);
    }

    imshow("Result", res);
    waitKey();

    return 0;
}

The result image is:

enter image description here

NOTE

I'd like to point out that this code is not optimized, so it can probably perform better. For an approximized solution, see here, and the papers reported there.

This answer to a related question put me in the right direction.

Community
  • 1
  • 1
Miki
  • 40,887
  • 13
  • 123
  • 202
  • Thanks a lot @Miki, it's similar to what I want, that's ok too; I found an answer similar to yours that satisfies me a lot: http://stackoverflow.com/questions/21410449/how-do-i-crop-to-largest-interior-bounding-box-in-opencv/21479072#21479072   But the problem I'm rather new with opencv-python, and I can not change the code in the link that I found from C ++ to Python. :( – H. Chamsaddin Sep 20 '15 at 19:51
  • 1
    I saw that too, but if I'm not wrong it works only for axis aligned rects. In the links I mentioned you'll find python code for the axis aligned case you just need to add the rotation part – Miki Sep 20 '15 at 19:58
  • Thanks @Miki. I have a last question, how to display the length of the two sides of rectangle and the coordinates of its centroid (always in C++) – H. Chamsaddin Sep 23 '15 at 15:38
  • 1
    You get vertices ccodinates using points (or boxPoints in opencv 3), center, width and height are properties (don't remember right now if functions or public members) of the rotatedrect. – Miki Sep 23 '15 at 15:52
  • yes now I 've displayed the parameters, because I wanted to extend the rectangle proportionally (for example increase of 10 pixels all sides), but I don't know why the algorithm gives me problems, I used this procedure as it is described in the link, but it doesn't function !!!!!!!!!: http://stackoverflow.com/a/15941998 – H. Chamsaddin Sep 24 '15 at 15:18
  • 1
    That won't work. Is for "Rect", not for "RotatedRect". They are different classes. You can try: 1) set the width and height of the rotated Rect, and check if the vertices are updated (probably this will work) or 2) morphologically dilate your blob and find a rectangle on this bigger blob – Miki Sep 24 '15 at 17:50
  • mmm so it is difficult, I prove the 2 point, I hope to have results. Thanks – H. Chamsaddin Sep 27 '15 at 13:43
  • 1
    @MohamedChamsaddin actually is quite easy: `RotatedRect r = largestRectInNonConvexPoly(img); r.size = Size(r.size.width + 10, r.size.height + 10);` – Miki Sep 27 '15 at 13:50
  • Hi @Miki, apropos is it possible with this algorithm that you have suggested upload and process at one time 4 images rather than one by one (for example I have 4 images: img1.png, img2.png, img3.png, img4.png), and display the 4 images in one window ?? Do me a great favor if you can. Thanks in advance – H. Chamsaddin Oct 14 '15 at 08:49
4

There's now a python library calculating the maximum drawable rectangle inside a polygon.

Library: maxrect


Install through pip:

pip install git+https://${GITHUB_TOKEN}@github.com/planetlabs/maxrect.git

Usage:

from maxrect import get_intersection, get_maximal_rectangle, rect2poly

# For a given convex polygon
coordinates1 = [ [x0, y0], [x1, y1], ... [xn, yn] ]
coordinates2 = [ [x0, y0], [x1, y1], ... [xn, yn] ]

# find the intersection of the polygons
_, coordinates = get_intersection([coordinates1, coordinates2])

# get the maximally inscribed rectangle
ll, ur = get_maximal_rectangle(coordinates)

# casting the rectangle to a GeoJSON-friendly closed polygon
rect2poly(ll, ur)

Source: https://pypi.org/project/maxrect/

Constantin
  • 848
  • 8
  • 23
1

here is a python code I wrote with rotation included. I tried to speed it up, but I guess it can be improved.

paugam
  • 150
  • 2
  • 12
  • Thanks @ paugam, your code in Python is correct and function well, but in most cases does not give a correct result !!, I don't know why ?!. I noticed this comparing with the first answer suggested in C ++. There is a possibility to get the same result (in Python) ? – H. Chamsaddin Nov 16 '15 at 20:58
  • 1
    could you give the image you have that does not give a correct result. – paugam Nov 18 '15 at 09:48
  • 1
    I spotted an issue. Not sure if it will solve yours. The dimensions of the input array need to be odd. It helps the rotation to be centered in the middle of the image. the code was updated [here](https://github.com/pogam/ExtractRect). – paugam Nov 18 '15 at 11:49
  • 1
    Yes @paugam, this is the image: "http://i.stack.imgur.com/QlbyX.png" and this is the result in C ++ (suggested in the first answer from @Miki): "http://i.stack.imgur.com/vHm6P.png" – H. Chamsaddin Nov 18 '15 at 20:20
  • 1
    Thanks. I modified the algo to handle different image sizes. In your particular case it now gives an angle of -66 degree while your C++ example gave -64. see the new code on the git repository. – paugam Nov 21 '15 at 13:03
  • 1
    Thanks @paugam, It worked well better than before, but when I used another image it gave me the same problem; It should work !!!. This is the new image that I used: "http://i.stack.imgur.com/o943j.png" and these are the results in Python and C ++ (suggested by @Miki): "http://i.stack.imgur.com/fBeve.png" – H. Chamsaddin Nov 22 '15 at 20:31
  • 1
    thanks for testing the algo. The issue comes from the fine optimization which is apparently seeing 0 as a local minimum in the case you provided. I reduced the range of angle to scan in the brute force optimization (which feed the fine optimization) as we have symmetry. without loosing on speed this now works in your last test and previous too. thanks. Not sure how to properly solve the opt issue but for now it seems to work. git updated. – paugam Nov 23 '15 at 23:04
0

For future googlers,

Since your provided sample solution allows background pixels to be within the rectangle, I suppose you wish to find the the smallest rectangle that covers perhaps 80% of the white pixels.

This can be done using a similar method of finding the error ellipse given a set of data (in this case, the data is all the white pixels, and the error ellipse needs to be modified to be a rectangle)

The following links would hence be helpful

How to get the best fit bounding box from covariance matrix and mean position?

http://www.visiondummy.com/2014/04/draw-error-ellipse-representing-covariance-matrix/

Community
  • 1
  • 1
Rufus
  • 5,111
  • 4
  • 28
  • 45