如何将一组像素识别为近似矩形?

How to identify a group of pixels as approximately rectangular?

将左边的像素组视为矩形的数学逻辑是什么?

这与评论之一非常相似(边界框并检查内部区域)。我建议您使用面积和周长的方程来制作更强大和可配置的形状测试仪。

-在感兴趣的形状周围获取合适的边界框。

-检查区域:blob像素数应约等于A(blob):L*W(rotated bounding box)

-检查周长:运行 边缘变换(如 Canny 或等价物)并将边缘像素的数量与矩形周长方程进行比较:P(blob):2W+2H(旋转边界框)


-运行在边缘图像上画一条霍夫线并检查正交性可能是另一个需要考虑的适应度估计器,如果它对您来说是一个重要参数的话。

基本上每次检查形状适合度时,您通常都希望根据您关心的形状(即干净的边缘、垂直角、内部填充、直边等)检查各种适合度估计量,并且您对这些估计量中的每一个进行加权或绑定以创建您所追求的适当的组合特异性。

编辑:(代码和附加说明)。因此,为了清楚说明您的示例,我同意面积是最强的健身估计。但是,在一般的形状识别中(圆形、正方形、矩形、树叶等复杂形状等),跟踪多个适应度估计量通常很有用,这样您就可以真正了解您关心和不关心的内容。我在您的样本中添加了一个额外的(完全人为的)形状,以证明单独的区域可能不太可靠(在一个干净的矩形中切入)。如您所见,面积对突起等外部形状错误非常敏感,但周长对内部形状错误更敏感。 (同样,这对您来说可能重要也可能无关紧要,但我认为它很有趣并且可能会有所帮助)。我添加了一个额外的参数(周长与面积之比),这是一个用于估计形状复杂性(特别是叶子)的简洁指标。

代码:

#include <stdio.h>
#include <opencv2/opencv.hpp>
#include <Windows.h>
#include <string>

using namespace std;
using namespace cv;

void drawOrientedBoundingBox(Mat img, RotatedRect obb, Scalar color)
{
    Point2f vertices[4];
    obb.points(vertices);
    for (int i = 0; i < 4; i++)
        line(img, vertices[i], vertices[(i + 1) % 4], color, 2);
}

int main(int argc, char** argv)
{
    //play with these to dial in what you care about (across multiple sample images)
    double areaFitHigh = 1.2;
    double areaFitLow = 0.8;
    double perimFitHigh = 1.05;
    double perimFitLow = 0.85;

    std::string fileName = "C:/Local Software/voyDICOM/resources/images/RectTester.jpg";
    Mat tempImage = imread(fileName, cv::IMREAD_GRAYSCALE);

    Mat bwImg;
    cv::threshold(tempImage, bwImg, 0, 255, cv::THRESH_OTSU);

    Mat contImg = Mat(bwImg.rows, bwImg.cols, CV_8UC3, Scalar(0, 0, 0));

    vector<vector<Point> > contours;
    findContours(bwImg, contours, RETR_LIST, CHAIN_APPROX_NONE);

    bool areaFitnessFlag = false;
    bool perimFitnessFlag = false;

    for (size_t i = 0; i < contours.size(); i++)
    {
        std::string contourName = "S_" + std::to_string(i);
        std::cout << "-------------Contour Detected------------" << std::endl;
        std::cout << contourName << std::endl;

        if (contours[i].size() >= 2 * bwImg.cols + 2 * bwImg.rows - 4)
        {
            std::cout << "image boundary... don't know how to make findContours not detect this up front" << std::endl;
            continue;
        }

        RotatedRect obb = minAreaRect(contours[i]);

        //draw graphics for debug purposes
        drawOrientedBoundingBox(contImg, obb, Scalar(255, 0, 0));
        drawContours(contImg, contours, static_cast<int>(i), Scalar(0, 0, 255), 2);
        putText(contImg, contourName, obb.center, cv::FONT_HERSHEY_DUPLEX, 1, cv::Scalar(0, 255, 0), 1, false);

        //perform fitness checks
        double areaBlob = contourArea(contours[i]);
        double areaOBB = obb.size.area();

        double perimeterBlob = contours[i].size();
        double perimeterOBB = 2 * obb.size.width + 2 * obb.size.height;

        double perimToArea = 0;
        if (areaBlob > 0) { perimToArea = perimeterBlob / areaBlob; }

        std::cout << "area: " << areaBlob << " , " << areaOBB << std::endl;
        std::cout << "perimeter: " << perimeterBlob << " , " << perimeterOBB << std::endl;
        std::cout << "Perimeter to Area Ratio: " << perimToArea << std::endl;

        double areaFitness = 0;
        if (areaOBB > 0) { areaFitness = areaBlob / areaOBB; }
        std::cout << "Area Fitness: " << areaFitness << std::endl;

        double perimeterFitness = 0;
        if (perimeterOBB > 0) { perimeterFitness = perimeterBlob / perimeterOBB; }
        std::cout << "Perimeter Fitness: " << perimeterFitness << std::endl;


        if (areaFitness > areaFitHigh || areaFitness < areaFitLow)
        { areaFitnessFlag = false; }
        else
        { areaFitnessFlag = true; }

        if (perimeterFitness > perimFitHigh || perimeterFitness < perimFitLow)
        { perimFitnessFlag = false; }
        else
        { perimFitnessFlag = true; }

        if (areaFitnessFlag && perimFitnessFlag)
        { std::cout << "This is a rectangle!" << std::endl; }
        else
        { std::cout << "This is not a rectangle..." << std::endl; }
    }

    namedWindow("Original", WINDOW_AUTOSIZE);
    imshow("Original", tempImage);

    namedWindow("Thresh", WINDOW_AUTOSIZE);
    imshow("Thresh", bwImg);

    namedWindow("Contours", WINDOW_AUTOSIZE);
    imshow("Contours", contImg);

    waitKey(0);
    system("pause");
    return 0;
}

结果图片:

有用的链接:

https://docs.opencv.org/master/df/dee/samples_2cpp_2minarea_8cpp-example.html

https://docs.opencv.org/master/db/dd6/classcv_1_1RotatedRect.html

https://learnopencv.com/contour-detection-using-opencv-python-c/

下面是评论中想法的实现。做法是:

  1. 找到旋转的矩形边界框
  2. 计算这个旋转边界框的面积和轮廓的面积
  3. 比较两者。如果轮廓区域至少是旋转边界框区域的 80%(或任意阈值),那么我们将其视为矩形

这是图像处理管道的可视化图

输入图像->阈值->检测到的旋转矩形边界框->掩码

Contour area: 17719.0
Mask area: 20603.0
Compared area percentage: 86.002%
It is a rectangle!

另一张图片

输入图像->阈值->检测到的旋转矩形边界框->掩码

Contour area: 13395.5
Mask area: 19274.5
Compared area percentage: 69.499%
It is not a rectangle!

您没有指定语言,所以这里有一个使用 Python OpenCV

的简单实现
import cv2
import numpy as np

# Load image, convert to grayscale, Otsu's threshold for binary image
image = cv2.imread('1.png')
mask = np.zeros(image.shape, dtype=np.uint8)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]

# Find contours, find rotated rectangle, find contour area
cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
contour_area = cv2.contourArea(cnts[0])
print('Contour area: {}'.format(contour_area))
rect = cv2.minAreaRect(cnts[0])
box = np.int0(cv2.boxPoints(rect))
cv2.drawContours(image, [box], 0, (36,255,12), 3)

# Find area of rotated bounding box and draw onto mask image
mask_area = cv2.contourArea(box)
cv2.drawContours(mask, [box], 0, (255,255,255), -1)
print('Mask area: {}'.format(mask_area))

# Compare areas and calculate percentage
rectangular_threshold = 80
percentage = (contour_area / mask_area) * 100
print('Compared area percentage: {:.3f}%'.format(percentage))
if percentage > rectangular_threshold:
    print('It is a rectangle!')
else:
    print('It is not a rectangle!')

# Display
cv2.imshow('image', image)
cv2.imshow('thresh', thresh)
cv2.imshow('mask', mask)
cv2.waitKey()