当前位置: 首页 > 知识库问答 >
问题:

基于hough变换的opencv-android矩形文档检测

干永丰
2023-03-14

我正在尝试使用opencv 4 android sdk检测矩形文档。首先,我试图通过查找轮廓来检测它,但它不适用于多色文档。您可以查看此链接以获得更好的想法:使用OpenCV4Android检测多色文档

我做了很多研究,发现可以用houghline变换来完成。所以我按照以下方法检测文档:

原始图像-

我对hough线变换所做的是:

Imgproc.HoughLinesP(watershedMat, lines, 1, Math.PI / 180, 50, 100, 50);

    List<Line> horizontals = new ArrayList<>();
    List<Line> verticals = new ArrayList<>();
    for (int x = 0; x < lines.rows(); x++)
    {
        double[] vec = lines.get(x, 0);
        double x1 = vec[0],
                y1 = vec[1],
                x2 = vec[2],
                y2 = vec[3];
        Point start = new Point(x1, y1);
        Point end = new Point(x2, y2);
        Line line = new Line(start, end);
        if (Math.abs(x1 - x2) > Math.abs(y1-y2)) {
            horizontals.add(line);
        } else if (Math.abs(x2 - x1) < Math.abs(y2 - y1)){
            verticals.add(line);
        }
    }

从上面的水平线和垂直线列表中,我找到了以下交叉点:

protected Point computeIntersection (Line l1, Line l2) {
    double x1 = l1._p1.x, x2= l1._p2.x, y1 = l1._p1.y, y2 = l1._p2.y;
    double x3 = l2._p1.x, x4 = l2._p2.x, y3 = l2._p1.y, y4 = l2._p2.y;
    double d = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);

   // double angle = angleBetween2Lines(l1,l2);
    Log.e("houghline","angle between 2 lines = "+angle);
    Point pt = new Point();
    pt.x = ((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / d;
    pt.y = ((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / d;


  return pt;
}

从这四个交点我画出了线。到目前为止,我能够通过它检测文档。请参见下图:

但是,当其他对象与文档相关时,它也会尝试检测它们。我将从上到下的行和从左到右的列来查找最大矩形的交点。我遇到以下问题:

正如您在上面的图像中看到的,当其他对象出现在屏幕上时,它也会检测到它。如何仅检测文档?忽略其他对象?这是我的原始图像:

我们将非常感谢您的帮助!!提前感谢

共有1个答案

南宫天逸
2023-03-14
  • 我在Windows 10上使用OpenCV 3.2.0,但是所有提到的功能都应该在2.4和或Android中可用

您以前的方法不起作用的原因有几个。在我们得出解决方案之前,需要考虑以下一些观察结果:

  • 您有一个对象,它包含比背景更暗和更亮的元素

考虑到上述观察,我认为简单的阈值化或边缘检测不会产生任何可靠的结果,尤其是在查看同一场景的不同图像之间的变化时。作为一种解决方案,我建议通过LAB或HSV颜色空间进行前景和/或背景颜色检测和分类。应使用最突出颜色的样本图像来对相应区域进行分类。例如,对于前景,书的深红色和鲜红色以及金色/黄色。背景由相当均匀的灰色组成,可用于检测。潜在算法:

  1. 根据实验室颜色空间对前背景进行检测和分类。使用一个合理的色差阈值(对我来说,大约8-10%在实验室空间工作-AB空间可能工作5-7%)。如果亮度变化导致的颜色变化成为问题,则切换到与亮度无关的方法(例如,只需突出AB分量而忽略L分量)

优势:

  • 非常准确
  • 适用于多种场景(更改背景、不同照明-如果使用了正确的颜色空间)

缺点:

  • 初学者很难实现(实验室或HSV知识、颜色距离、支持多色分类等)

目前的方法虽然先进,但仍不能满足一般应用的需要(不同的书籍、不同的背景等),这是有原因的。

如果你想要一个通用系统,可以自动检测不同背景下的不同书籍,那么你就有麻烦了。这达到了难以解决的困难程度。这让我想起了车牌的检测:不同的照明、噪音、染色的物体、强烈变化的背景、糟糕的对比度等。即使你能做到这一点,这里有一个陷阱:这样的系统只适用于特定类型的车牌。这同样适用于你的书。

由于您发布了一个非常类似的问题(使用OpenCV4Android检测多色文档),我冒昧地使用了发布在那里的图像以及您在这里提供的图像。因为其中一张图像只有红色ROI,所以我使用了我的Photoshop技能水平

用于背景分类的示例图像

用于前景分类的示例图像

图片

背景分类

前景分类

检测到的对象

由于颜色空间的理论非常广泛,您应该首先阅读一些基础知识和要点。我的快速搜索找到了这个网站,它很好地解释了一些要点:http://www.learnopencv.com/color-spaces-in-opencv-cpp-python/-我们将使用OpenCV的浮点变量,因为它是最简单的一种(未更改的实验室范围、无缩放、无调整等)。-实验室值范围:L*轴(亮度)范围从0到100 a*和b*(颜色属性)轴范围从-128到127来源和参考:CIELAB颜色空间中的坐标范围是什么?http://www.colourphil.co.uk/lab_lch_colour_space.shtml

https://en.wikipedia.org/wiki/Color_difference

本质上,我们使用两种颜色之间的欧几里得距离。当然,我们可以从我们比较的两种颜色中省略分量,例如亮度分量(L)。

为了得到直观的颜色距离度量,我们可以简单地将颜色距离归一化为0.0到1.0之间的范围。这样我们就可以将颜色距离交织成百分比偏差。

让我们使用上面发布的教程页面中的图像,并在示例中使用它们。示例应用程序显示了以下内容:-BGR到LAB的转换-(L)AB距离计算-(L)AB距离归一化-根据BGR/LAB值和颜色距离阈值进行颜色分类-对象的颜色如何在不同的照明条件下变化-到其他颜色的距离如何变大/关闭图像变暗/变亮(如果仔细阅读发布的链接,这一点也会变得很清楚)。

额外提示:该示例应该表明,在强烈变化的照明条件下,单一颜色通常不足以检测颜色对象。一种解决方案可以是通过经验分析为每种颜色使用不同的颜色距离阈值。另一种方法是为您想要找到的每种颜色使用许多分类样本颜色。您必须计算与这些样本颜色中的每种颜色的颜色距离,并通过ORing结果组合找到的值。

(图片取自http://www.learnopencv.com/color-spaces-in-opencv-cpp-python/-Satya Mallick的教程)

#include <opencv2/opencv.hpp>

// Normalization factors for (L)AB distance calculation
// LAB range:
// L: 0.0 - 100.0
// A: -128.0 - 127.0
// B: -128.0 - 127.0
static const float labNormalizationFactor = (float)(1.f / (std::sqrt(std::pow(100, 2) + std::pow(255, 2) + std::pow(255, 2))));
static const float abNormalizationFactor = (float)(1.f / (std::sqrt(std::pow(255, 2) + std::pow(255, 2))));

float labExample_calculateLabDistance(const cv::Vec3f& c1, const cv::Vec3f& c2)
{
    return (float)cv::norm(c1, c2) * labNormalizationFactor;
}

float labExample_calculateAbDistance(const cv::Vec3f& c1, const cv::Vec3f& c2)
{
    cv::Vec2f c1Temp(c1(1), c1(2));
    cv::Vec2f c2Temp(c2(1), c2(2));
    return (float)cv::norm(c1Temp, c2Temp) * abNormalizationFactor;
}

void labExample_calculateLabDistance(
    cv::Mat& imgLabFloat,
    cv::Mat& distances,
    const cv::Vec3f labColor,
    const bool useOnlyAbDistance
)
{
    // Get size for general usage
    const auto& size = imgLabFloat.size();

    distances = cv::Mat::zeros(size, CV_32F);
    distances = 1.f;

    for (int y = 0; y < size.height; ++y)
    {       
        for (int x = 0; x < size.width; ++x)
        {   
            // Read LAB value
            const auto& value = imgLabFloat.at<cv::Vec3f>(y,x);

            // Calculate distance
            float distanceValue;
            if (useOnlyAbDistance)
            {
                distanceValue = labExample_calculateAbDistance(value, labColor);
            }
            else
            {
                distanceValue = labExample_calculateLabDistance(value, labColor);
            }

            distances.at<float>(y,x) = distanceValue;
        }
    }
}

// Small hacky function to convert a single 
// BGR color value to LAB float.
// Since the conversion function is not directly available
// we just use a Mat object to do the conversion.
cv::Vec3f labExample_bgrUchar2LabFloat(const cv::Scalar bgr)
{
    // Build Mat with single bgr pixel
    cv::Mat matWithSinglePixel = cv::Mat::zeros(1, 1, CV_8UC3);
    matWithSinglePixel.setTo(bgr);

    // Convert to float and scale accordingly
    matWithSinglePixel.convertTo(matWithSinglePixel, CV_32FC3, 1.0 / 255.0);

    // Convert to LAB and return value
    cv::cvtColor(matWithSinglePixel, matWithSinglePixel, CV_BGR2Lab);
    auto retval = matWithSinglePixel.at<cv::Vec3f>(0, 0);

    return retval;
}

void labExample_convertImageBgrUcharToLabFloat(cv::Mat& src, cv::Mat& dst)
{
    src.convertTo(dst, CV_32FC3, 1.0 / 255.0);
    cv::cvtColor(dst, dst, CV_BGR2Lab);
}

void labExample()
{
    // Load image
    std::string path = "./Testdata/Stackoverflow lab example/";
    std::string filename1 = "1.jpg";
    std::string fqn1 = path + filename1;
    cv::Mat img1 = cv::imread(fqn1, cv::IMREAD_COLOR);
    std::string filename2 = "2.jpg";
    std::string fqn2 = path + filename2;
    cv::Mat img2 = cv::imread(fqn2, cv::IMREAD_COLOR);

    // Combine images by scaling the second image so both images have the same number of columns and then combining them.
    float scalingFactorX = (float)img1.cols / img2.cols;
    float scalingFactorY = scalingFactorX;
    cv::resize(img2, img2, cv::Size(), scalingFactorX, scalingFactorY);

    std::vector<cv::Mat> mats;
    mats.push_back(img1);
    mats.push_back(img2);
    cv::Mat img;
    cv::vconcat(mats, img);

    // Lets use some reference colors.
    // Remember: OpenCV uses BGR as default color space so all colors
    // are BGR by default, too.
    cv::Scalar bgrColorRed(52, 42, 172);
    cv::Scalar bgrColorOrange(3, 111, 219);
    cv::Scalar bgrColorYellow(1, 213, 224);
    cv::Scalar bgrColorBlue(187, 95, 0);
    cv::Scalar bgrColorGray(127, 127, 127);

    // Build LAB image
    cv::Mat imgLabFloat;
    labExample_convertImageBgrUcharToLabFloat(img, imgLabFloat);

    // Convert bgr ref color to lab float.
    // INSERT color you want to analyze here:
    auto colorLabFloat = labExample_bgrUchar2LabFloat(bgrColorRed);

    cv::Mat colorDistancesWithL;
    cv::Mat colorDistancesWithoutL;
    labExample_calculateLabDistance(imgLabFloat, colorDistancesWithL, colorLabFloat, false);
    labExample_calculateLabDistance(imgLabFloat, colorDistancesWithoutL, colorLabFloat, true);

    // Color distances. They can differ for every color being analyzed.
    float maxColorDistanceWithL = 0.07f;
    float maxColorDistanceWithoutL = 0.07f;

    cv::Mat detectedValuesWithL = colorDistancesWithL <= maxColorDistanceWithL;
    cv::Mat detectedValuesWithoutL = colorDistancesWithoutL <= maxColorDistanceWithoutL;

    cv::Mat imgWithDetectedValuesWithL = cv::Mat::zeros(img.size(), CV_8UC3);
    cv::Mat imgWithDetectedValuesWithoutL = cv::Mat::zeros(img.size(), CV_8UC3);

    img.copyTo(imgWithDetectedValuesWithL, detectedValuesWithL);
    img.copyTo(imgWithDetectedValuesWithoutL, detectedValuesWithoutL);

    cv::imshow("img", img);
    cv::imshow("colorDistancesWithL", colorDistancesWithL);
    cv::imshow("colorDistancesWithoutL", colorDistancesWithoutL);
    cv::imshow("detectedValuesWithL", detectedValuesWithL);
    cv::imshow("detectedValuesWithoutL", detectedValuesWithoutL);
    cv::imshow("imgWithDetectedValuesWithL", imgWithDetectedValuesWithL);
    cv::imshow("imgWithDetectedValuesWithoutL", imgWithDetectedValuesWithoutL);
    cv::waitKey();
}

int main(int argc, char** argv)
{
    labExample();
}
 类似资料:
  • 我需要一个我一直在工作的算法的帮助。我试图检测阈值图像中的所有线条,检测所有线条,然后只输出那些平行的线条。阈值图像输出我感兴趣的对象,然后我通过canny边缘检测器过滤该图像。然后,该边缘图像通过概率Hough变换。现在,我希望该算法能够检测任何图像中的平行线。我想通过检测所有直线的坐标并计算它们的斜率(然后是角度)来实现这一点。平行线必须具有相同或几乎相同的角度,这样我只能输出具有相同角度的线

  • 我正在尝试使用Python OpenCV和Hough变换算法检测表线并从图像中提取完整的表。我需要有每条线的所有坐标,目的是绘制相同比例的相同表格。我了解Hough变换的工作原理,并尝试在没有OpenCV的情况下实现它,但在大图像上速度非常慢。 这是来自示例OpenCV Hough Transfrom的代码 Canny边缘检测返回图像Canny边缘检测结果 但是检测的结果是豪斯变换的结果 我不知道

  • 本文向大家介绍opencv实现矩形检测,包括了opencv实现矩形检测的使用技巧和注意事项,需要的朋友参考一下 本文实例为大家分享了opencv实现矩形检测的具体代码,供大家参考,具体内容如下 效果: 以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持呐喊教程。

  • 我尝试过各种演示代码,如“opencv查找:轮廓”,模板匹配(效果不好,因为它无法检测到顶盖的旋转) 我发现最好的方法是将Canny边缘检测和Hough变换圆相结合,这样Canny边缘检测的输出结果可以是Hough变换圆的输入图像,结果如下。 不幸的是,并非所有圆圈都被检测到,如果我更改

  • 本文向大家介绍使用OpenCV检测图像中的矩形,包括了使用OpenCV检测图像中的矩形的使用技巧和注意事项,需要的朋友参考一下 本文实例为大家分享了OpenCV检测图像中矩形的具体代码,供大家参考,具体内容如下 前言 1.OpenCV没有内置的矩形检测的函数,如果想检测矩形,要自己去实现。 2.我这里使用的OpenCV版本是3.30. 矩形检测 1.得到原始图像之后,代码处理的步骤是: (1)滤波

  • 问题内容: 我在这里可能会变得非常愚蠢,但是我在使用Android的OpenCV进行Mat乘法时遇到了麻烦。 我有两个同类型的垫子, 大小:3行,3列 大小:3行,1列 我想将它们乘以给出大小为3行1列的乘积。 我试过使用: 但我得到一个错误: CvException [org.opencv.core.CvException:/home/andreyk/OpenCV2/trunk/opencv_2