如何在对象内调整或重新调整矩形大小,而不包括(或只包括少量)背景像素?

8

在应用阈值和查找对象轮廓后,我使用了以下代码来获取对象周围的直矩形(或输入其指令的旋转矩形):

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)

请查看图片

接下来,我使用以下代码计算了直矩形内部的物体像素和背景像素数量:

# 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

现在我想将对象的矩形作为背景像素和对象像素之间关系的函数进行调整,即使矩形占据对象的较大部分而没有或减少背景像素,例如: 我该如何创建这个呢?

minAreaRect会找到最小面积的旋转矩形,也就是背景较少的那个。 - Miki
@Miki 是的,但在我的情况下,我希望矩形仅包含白色像素(我希望将其调整到构成矩形的对象的更大部分上,没有或至少很少的背景像素):http://i.stack.imgur.com/oJAw4.png - H. Chamsaddin
4个回答

9
这个问题可以表述为“在一个非凸多边形中找到最大的内接矩形”。
可以在此链接中找到一个近似的解决方案。
这个问题也可以表述为:“对于每个角度,在一个矩阵中找到只包含零的最大矩形”,在这个SO 问题中探讨过。
我的解决方案基于这个答案。它只能找到轴对齐的矩形,所以你可以很容易地通过给定的角度旋转图像,并对每个角度应用此解决方案。
我的解决方案是C++,但你可以很容易地将其移植到Python,因为我主要使用OpenCV函数,或者根据旋转调整上述提到的解决方案。
#include <opencv2\opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;


// https://dev59.com/J3E95IYBdhLWcg3wDpmg#30418912
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://dev59.com/J3E95IYBdhLWcg3wDpmg
        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;
}

结果图片为:

enter image description here

注意

我想指出这段代码并不是最优化的,所以它可能会有更好的表现。对于一个近似解,请参见这里和那里报告的论文。

这个答案给了我正确的方向。


1
我也看到了,但如果我没记错的话,它只适用于轴对齐的矩形。在我提到的链接中,您会找到针对轴对齐情况的Python代码,您只需要添加旋转部分即可。 - Miki
1
您可以使用点(或在OpenCV 3中的boxPoints)获取顶点坐标,而旋转矩形的中心、宽度和高度是其属性(我现在不确定是函数还是公共成员)。 - Miki
1
那样行不通。这是针对“Rect”,而不是“RotatedRect”。它们是不同的类。你可以尝试:1)设置旋转矩形的宽度和高度,并检查顶点是否已更新(可能会起作用),或者2)对你的斑点进行形态膨胀,并在这个更大的斑点上找到一个矩形。 - Miki
嗯,这很困难,我证明了这两个点,希望能有结果。谢谢。 - H. Chamsaddin
1
@MohamedChamsaddin 实际上非常简单:RotatedRect r = largestRectInNonConvexPoly(img); r.size = Size(r.size.width + 10, r.size.height + 10); - Miki
显示剩余4条评论

4

现在有一个Python库可以计算多边形内的最大可绘制矩形。

: maxrect


使用pip安装:

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

用法:

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/


1

这里 是我写的一个包含旋转功能的 Python 代码。我尝试过加速它,但是我认为它可以被优化。


1
你能提供一下你所拥有的那张图片吗?它似乎没有给出正确的结果。 - paugam
1
我发现了一个问题。不确定它是否能解决你的问题。输入数组的维数需要是奇数。这有助于旋转在图像中心对齐。代码已在此处更新(https://github.com/pogam/ExtractRect)。 - paugam
1
谢谢。我修改了算法以处理不同的图像尺寸。在您的特定情况下,它现在给出了-66度的角度,而您的C++示例给出了-64度。请查看git存储库中的新代码。 - paugam
1
感谢@paugam,它比以前表现得更好了,但当我使用另一张图片时,它给了我同样的问题;它应该能够工作!!!这是我使用的新图片:“http://i.stack.imgur.com/o943j.png”,这些是Python和C++的结果(由@Miki建议):“http://i.stack.imgur.com/fBeve.png”。 - H. Chamsaddin
显示剩余2条评论

0

给未来的谷歌搜索者们:

由于您提供的样本解决方案允许矩形内包含背景像素,我想您希望找到覆盖白色像素80%的最小矩形。

可以使用类似的方法找到一组数据的误差椭圆(在这种情况下,数据是所有白色像素,并且需要修改误差椭圆为矩形)

以下链接可能会有所帮助

如何从协方差矩阵和平均位置获取最佳匹配边界框?

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接