使用霍夫变换opencv android进行矩形文档检测

Posted

技术标签:

【中文标题】使用霍夫变换opencv android进行矩形文档检测【英文标题】:rectangle document detection using hough transform opencv android 【发布时间】:2017-12-03 03:47:06 【问题描述】:

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

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

原始图像 -> cvtColor -> GaussianBlur 过滤器 -> 扩张它以锐化边缘 -> 应用分水岭图像分割算法 -> 使用动态 otsu 阈值进行精明边缘检测 -> 然后应用霍夫线变换

我对霍夫线变换所做的是:

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;

从那四个交点开始,我正在画线。所以,到目前为止我能够通过它检测文档。见下图:

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

如上图所示,当屏幕上出现其他物体时,它也会检测到它。如何只检测文件?并忽略其他对象? 这是我的原图:

任何帮助将不胜感激!提前致谢

【问题讨论】:

如果文档始终相同,您可以在两个图像(您捕获的和文档的副本)上使用检测器 (ORB/SIFT/SURF) 和描述符并匹配描述符。这肯定会解决您的问题。您还可以使 HoughLines 方法更严格,以便它只找到一定长度的线段并拒绝另一个。 我正在尝试检测不同的文档,所以我可能无法使用 SIFT/SURF。另外,我也尝试了具有不同 rho 阈值的 houghlines。但是,没有任何工作.. 可以上传原图吗?没有覆盖? hii @Rick M. 感谢您的回复。我更新了原图 【参考方案1】:

一般信息

我在 Windows 10 上使用 OpenCV 3.2.0,但是所有提到的功能都应该在 2.4 和/或 Android 中可用。 我调整了图像的大小以获得更好的可视化效果。这不影响当前解决问题的方法, 但是,如果我们要使用某种边缘检测,我们绝对应该使用原始图像大小。 当前提供的解决方案使用了很多自定义功能(LAB 颜色检测、轮廓尺寸分析等),这些功能无法 被推到这里。如果您在特定领域需要帮助,当然可以在 cmets 寻求帮助。

问题的一般观察

您以前的方法不起作用有几个原因。 在我们找到解决方案之前,需要考虑以下几点:

与背景相比,您的对象包含更暗和更亮的元素。 您有一个对象,该对象由关于亮度和颜色以及一般同质性的相当不同的部分组成。 事实上,对象被一个看起来很像背景的部分分割。 您的背景对象可以明显区别于一般背景(例如右上角的黑色对象)。 通常从略微倾斜的角度拍摄对象。这会导致矩形对象的透视变换。

解决方案

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

    根据 LAB 颜色空间检测和分类前景和背景。使用合理的颜色距离阈值(对我来说,大约 8-10% 在 LAB 空间中工作 - AB 空间可能在 5-7% 中工作)。 如果由于亮度变化导致颜色变化成为问题,则切换到与亮度无关的方法(例如,只使用 AB 分量并忽略 L 分量) 从前景检测中排除部分背景(分类中可能存在一些重叠,因此此顺序可以防止混淆)。 在剩余的二值图像上,应用轮廓搜索并丢弃区域过小的轮廓。 剩余的轮廓形成书本。创建一个可以用作对象 ROI 的凸包。

优点:

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

缺点:

对于初学者来说难以实施(了解 LAB 或 HSV、颜色距离、支持多颜色分类等) 颜色检测完全依赖于背景和前景。这意味着如果这本书发生了变化并且是例如蓝色,必须调整示例图像。 如果书的所有顶部、底部或侧面看起来都像背景,则此方法将不起作用。如果是这样,本书的这些部分将被归类为背景。

一般解的难度

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

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

测试

由于您发布了一个非常相似的问题 (detecting multi color document with OpenCV4Android),我冒昧地 使用那里发布的图像以及您在此处提供的图像。 由于其中一张图像仅具有红色 ROI,因此我使用我的 Photoshop 技能水平 > 9000 来移除红色 ROI:)。

用于背景分类的示例图像

用于前景分类的示例图像

图片

背景分类

前景分类

检测到的物体



更新

快速实验室速成课程

由于色彩空间的理论非常广泛,您应该首先阅读一些基础知识和关键点。 我快速搜索发现这个网站很好地解释了一些要点:http://www.learnopencv.com/color-spaces-in-opencv-cpp-python/ - 我们将使用 OpenCV 的 float 变体,因为它是最简单的一种(不变的 LAB 范围、没有缩放、没有 shfiting 等)。 - LAB值范围: L* 轴(亮度)范围从 0 到 100 a* 和 b*(颜色属性)轴范围从 -128 到 +127 来源和参考: What are the ranges of coordinates in the CIELAB color space? 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/*** 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();

【讨论】:

感谢好友的努力。但是,我是opencv的新手,对实验室一无所知。也许你可以帮助我应用 LAB 的代码示例! @ImLearning 我添加了一个代码示例以及几个解释。我认为通过使用示例并阅读外部教程(和其他可用的东西),您应该很好地掌握该主题。 我还没有检查你的代码,但我感谢你的努力,伙计。

以上是关于使用霍夫变换opencv android进行矩形文档检测的主要内容,如果未能解决你的问题,请参考以下文章

使用霍夫变换进行矩形检测

使用 Opencv python 进行霍夫变换

学习 opencv---(13)opencv霍夫变换:霍夫线变换,霍夫圆变换

OpenCV霍夫变换进行形状检测

即使图像在 Python 中的 OpenCV 中包含多行,霍夫线变换也只能识别一行

OpenCV中的霍夫线变换概率霍夫线变换