【问题标题】:Algorithm to detect corners of paper sheet in photo检测照片中纸张角落的算法
【发布时间】:2011-09-27 04:35:16
【问题描述】:

检测照片中发票/收据/纸张角落的最佳方法是什么?这将用于 OCR 之前的后续透视校正。

我目前的做法是:

RGB > Gray > Canny Edge Detection with thresholding > Dilate(1) > Remove small objects(6) > clearborder objects > 根据凸面区域挑选大型博客。 > [角落检测 - 未实现]

我不禁认为必须有一种更强大的“智能”/统计方法来处理这种类型的细分。我没有很多训练示例,但我可能会得到 100 张图像。

更广泛的背景:

我正在使用 matlab 进行原型设计,并计划在 OpenCV 和 Tesserect-OCR 中实现该系统。这是我需要为这个特定应用解决的许多图像处理问题中的第一个。因此,我希望推出自己的解决方案并重新熟悉图像处理算法。

以下是我希望算法处理的一些示例图像:如果您想接受挑战,大图像位于 http://madteckhead.com/tmp


(来源:madteckhead.com


(来源:madteckhead.com


(来源:madteckhead.com


(来源:madteckhead.com

最好的情况是:


(来源:madteckhead.com


(来源:madteckhead.com


(来源:madteckhead.com

但是在其他情况下它很容易失败:


(来源:madteckhead.com


(来源:madteckhead.com


(来源:madteckhead.com

提前感谢所有伟大的想法!我喜欢!

编辑:霍夫变换进度

问:什么算法可以对霍夫线进行聚类以找到角点? 根据答案的建议,我能够使用霍夫变换,挑选线条并过滤它们。我目前的方法相当粗糙。我假设发票与图像的偏差始终小于 15 度。如果是这种情况,我最终会得到合理的线路结果(见下文)。但我并不完全确定一个合适的算法来聚类线条(或投票)以推断角落。霍夫线不是连续的。在嘈杂的图像中,可能存在平行线,因此需要与线原点度量的某种形式或距离。有什么想法吗?


(来源:madteckhead.com

【问题讨论】:

  • 是的,我让它在大约 95% 的情况下工作。由于时间短缺,我不得不搁置代码。我会在某个阶段发布跟进,如果您需要紧急帮助,请随时委托我。很抱歉没有很好的跟进。我很想重新开始开发此功能。
  • Nathan,您能否就您最终如何做到这一点进行跟进?我一直在同一点识别纸的角落/外轮廓。我遇到了和你一样的问题,所以我对解决方案非常感兴趣。
  • 这篇文章中的所有图片现在都是 404。

标签: image-processing opencv edge-detection hough-transform image-segmentation


【解决方案1】:

这里有 @Vanuan 使用 C++ 的代码:

cv::cvtColor(mat, mat, CV_BGR2GRAY);
cv::GaussianBlur(mat, mat, cv::Size(3,3), 0);
cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Point(9,9));
cv::Mat dilated;
cv::dilate(mat, dilated, kernel);

cv::Mat edges;
cv::Canny(dilated, edges, 84, 3);

std::vector<cv::Vec4i> lines;
lines.clear();
cv::HoughLinesP(edges, lines, 1, CV_PI/180, 25);
std::vector<cv::Vec4i>::iterator it = lines.begin();
for(; it!=lines.end(); ++it) {
    cv::Vec4i l = *it;
    cv::line(edges, cv::Point(l[0], l[1]), cv::Point(l[2], l[3]), cv::Scalar(255,0,0), 2, 8);
}
std::vector< std::vector<cv::Point> > contours;
cv::findContours(edges, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_TC89_KCOS);
std::vector< std::vector<cv::Point> > contoursCleaned;
for (int i=0; i < contours.size(); i++) {
    if (cv::arcLength(contours[i], false) > 100)
        contoursCleaned.push_back(contours[i]);
}
std::vector<std::vector<cv::Point> > contoursArea;

for (int i=0; i < contoursCleaned.size(); i++) {
    if (cv::contourArea(contoursCleaned[i]) > 10000){
        contoursArea.push_back(contoursCleaned[i]);
    }
}
std::vector<std::vector<cv::Point> > contoursDraw (contoursCleaned.size());
for (int i=0; i < contoursArea.size(); i++){
    cv::approxPolyDP(Mat(contoursArea[i]), contoursDraw[i], 40, true);
}
Mat drawing = Mat::zeros( mat.size(), CV_8UC3 );
cv::drawContours(drawing, contoursDraw, -1, cv::Scalar(0,255,0),1);

【讨论】:

  • 行变量定义在哪里?必须是 std::vector<:vec4i> 行;
  • @CanÜrek 你是对的。 std::vector&lt;cv::Vec4i&gt; lines; 在我的项目的全局范围内声明。
  • 嘿@GBF_Gabriel,你能告诉我如何找到所需图像的四个角或边缘而不是画线。
  • 我收到此错误 E/cv::error(): OpenCV(4.3.0) 错误: cvDrawContours 中的断言失败(reader.ptr != NULL)
【解决方案2】:

我是 Martin 的朋友,他今年早些时候正在研究这个问题。这是我第一个编码项目,有点匆忙结束,所以代码需要一些错误......解码...... 我将根据我已经看到您所做的事情提供一些提示,然后在明天休息时对我的代码进行排序。

第一个提示,OpenCVpython 很棒,请尽快转向它们。 :D

不是去除小物体和/或噪音,而是降低canny约束,使其接受更多边缘,然后找到最大的闭合轮廓(在OpenCV中使用findcontour()和一些简单的参数,我想我使用了CV_RETR_LIST) .当它在一张白纸上时可能仍然会挣扎,但肯定会提供最佳效果。

对于Houghline2() 变换,尝试使用CV_HOUGH_STANDARD 而不是CV_HOUGH_PROBABILISTIC,它将给出rhotheta 值,定义线在极坐标中,然后您可以将线分组在一定的公差范围内。

我的分组用作查找表,对于从霍夫变换输出的每一行,它都会给出一个 rho 和 theta 对。如果这些值在表中一对值的 5% 之内,则将它们丢弃,如果它们在 5% 之外,则将新条目添加到表中。

然后您可以更轻松地分析平行线或线之间的距离。

希望这会有所帮助。

【讨论】:

  • 您好丹尼尔,感谢您的参与。我喜欢你的接近。它实际上是我目前取得好成绩的路线。甚至还有检测到矩形的 OpenCV 示例。只需要对结果进行一些过滤。正如您所说,用这种方法很难检测到白色上的白色。但这是一种比霍夫更简单且成本更低的方法。我实际上已经把霍夫方法从我的算法中排除了,并做了一个多近似,看看 opencv 中的正方形示例。我想看看你对霍夫投票的实施。在此先感谢,内森
  • 我在使用这种方法时遇到了问题,如果我能设计出更好的方法供将来参考,我会发布解决方案
  • @AnshumanKumar 我真的需要这个问题的帮助,你能帮帮我吗? stackoverflow.com/questions/61216402/…
【解决方案3】:

您还可以在 Sobel 算子结果上使用MSER(最大稳定极值区域)来查找图像的稳定区域。对于 MSER 返回的每个区域,您可以应用凸包和多边形近似来获得如下结果:

但是这种检测对于实时检测比单张图片更有用,它并不总是返回最佳结果。

【讨论】:

  • 你能否分享一些更多细节,也许是一些代码,提前谢谢
  • 我在 cv2.CHAIN_APPROX_SIMPLE 中收到一个错误,说要解压的值太多。任何想法?我使用 1024*1024 的图像作为我的示例
  • 谢谢大家,刚刚弄清楚当前Opencv分支中的语法变化answers.opencv.org/question/40329/…
  • MSER 不是要提取 blob 吗?我试过了,它只检测到大部分文本
【解决方案4】:
  1. 转换为实验室空间

  2. 使用 kmeans 段 2 集群

  3. 然后在其中一个集群(内部)上使用轮廓或霍夫

【讨论】:

    【解决方案5】:

    这是我经过一番实验后得出的结论:

    import cv, cv2, numpy as np
    import sys
    
    def get_new(old):
        new = np.ones(old.shape, np.uint8)
        cv2.bitwise_not(new,new)
        return new
    
    if __name__ == '__main__':
        orig = cv2.imread(sys.argv[1])
    
        # these constants are carefully picked
        MORPH = 9
        CANNY = 84
        HOUGH = 25
    
        img = cv2.cvtColor(orig, cv2.COLOR_BGR2GRAY)
        cv2.GaussianBlur(img, (3,3), 0, img)
    
    
        # this is to recognize white on white
        kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(MORPH,MORPH))
        dilated = cv2.dilate(img, kernel)
    
        edges = cv2.Canny(dilated, 0, CANNY, apertureSize=3)
    
        lines = cv2.HoughLinesP(edges, 1,  3.14/180, HOUGH)
        for line in lines[0]:
             cv2.line(edges, (line[0], line[1]), (line[2], line[3]),
                             (255,0,0), 2, 8)
    
        # finding contours
        contours, _ = cv2.findContours(edges.copy(), cv.CV_RETR_EXTERNAL,
                                       cv.CV_CHAIN_APPROX_TC89_KCOS)
        contours = filter(lambda cont: cv2.arcLength(cont, False) > 100, contours)
        contours = filter(lambda cont: cv2.contourArea(cont) > 10000, contours)
    
        # simplify contours down to polygons
        rects = []
        for cont in contours:
            rect = cv2.approxPolyDP(cont, 40, True).copy().reshape(-1, 2)
            rects.append(rect)
    
        # that's basically it
        cv2.drawContours(orig, rects,-1,(0,255,0),1)
    
        # show only contours
        new = get_new(img)
        cv2.drawContours(new, rects,-1,(0,255,0),1)
        cv2.GaussianBlur(new, (9,9), 0, new)
        new = cv2.Canny(new, 0, CANNY, apertureSize=3)
    
        cv2.namedWindow('result', cv2.WINDOW_NORMAL)
        cv2.imshow('result', orig)
        cv2.waitKey(0)
        cv2.imshow('result', dilated)
        cv2.waitKey(0)
        cv2.imshow('result', edges)
        cv2.waitKey(0)
        cv2.imshow('result', new)
        cv2.waitKey(0)
    
        cv2.destroyAllWindows()
    

    并不完美,但至少适用于所有样本:

    【讨论】:

    • 我正在做类似的项目。我在代码上方运行,它给了我错误“No module named cv”。我安装了 Open CV 2.4 版本,并且 import cv2 对我来说效果很好。
    • 您是否愿意更新此代码以使其正常工作? pastebin.com/PMH5Y0M8 它只是给了我一个黑页。
    • 你知道如何将下面的代码转换成java:for line in lines[0]: cv2.line(edges, (line[0], line[1]), (line[2], line[3]), (255,0,0), 2, 8) # finding contours contours, _ = cv2.findContours(edges.copy(), cv.CV_RETR_EXTERNAL, cv.CV_CHAIN_APPROX_TC89_KCOS) contours = filter(lambda cont: cv2.arcLength(cont, False) &gt; 100, contours) contours = filter(lambda cont: cv2.contourArea(cont) &gt; 10000, contours)
    • Vanuan 这个问题我真的需要帮助,你能帮帮我吗? stackoverflow.com/questions/61216402/…
    【解决方案6】:

    您可以使用角检测,而不是从边缘检测开始。

    Marvin Framework 为此提供了一个 Moravec 算法的实现。你可以找到纸的角落作为起点。下面是 Moravec 算法的输出:

    【讨论】:

      【解决方案7】:

      边缘检测后,使用霍夫变换。 然后,将这些点与它们的标签一起放入一个 SVM(支持向量机)中,如果示例上有平滑的线条,那么 SVM 将不会有任何困难来划分示例的必要部分和其他部分。我对 SVM 的建议是设置一个参数,例如连接性和长度。也就是说,如果点是连接的并且很长,它们很可能是收据的一条线。然后,您可以消除所有其他点。

      【讨论】:

      • 嗨,战神,感谢您的想法!我已经实现了霍夫变换(见上文)。鉴于误报和非连续线,我无法找到一种可靠的方法来找到角落。你有什么进一步的想法吗?自从我研究 SVM 技术以来已经有一段时间了。这是一种有监督的方法吗?我没有任何训练数据,但我可以生成一些。我有兴趣探索这种方法,因为我渴望了解更多关于 SVM 的信息。你能推荐任何资源。亲切的问候。内森
      【解决方案8】:

      我大学的一个学生小组最近演示了一个 iPhone 应用程序(和 python OpenCV 应用程序),他们为此编写了该应用程序。我记得,步骤是这样的:

      • 中值过滤器可完全去除纸上的文字(这是白纸上的手写文字,光线相当好,可能不适用于印刷文字,效果很好)。原因是它使角点检测变得更加容易。
      • 线的霍夫变换
      • 在 Hough 变换累加器空间中找到峰值,并在整个图像上绘制每条线。
      • 分析线条并删除任何彼此非常接近且角度相似的线条(将线条聚集成一条线)。这是必要的,因为霍夫变换并不完美,因为它在离散的样本空间中工作。
      • 找到大致平行且与其他线对相交的线对,以查看哪些线形成四边形。

      这似乎工作得很好,他们能够拍摄一张纸或书的照片,执行角落检测,然后几乎实时地将图像中的文档映射到平面上(只有一个 OpenCV函数来执行映射)。我看到它工作时没有 OCR。

      【讨论】:

      • 感谢马丁的好主意。我接受了您的建议并实施了霍夫变换方法。 (见上面的结果)。我正在努力确定一种强大的算法,该算法将推断线以找到交叉点。行不多,误报也很少。您对我如何最好地合并和丢弃行有什么建议吗?如果您的学生有兴趣,请鼓励他们联系。我很想听听他们在让算法在移动平台上运行方面的经验。 (这是我的下一个目标)。非常感谢您的想法。
      • 看起来线的 HT 在除您的第二张图像之外的所有图像中都运行良好,但是您是否为累加器中的开始值和结束值定义了阈值容差? HT 并没有真正定义开始和结束位置,而是定义 y=mx+c 中的 m 和 c 值。请参阅here - 请注意,这是在累加器中使用极坐标而不是笛卡尔坐标。通过这种方式,您可以按 c 和 m 对线条进行分组,以便将它们细化,并且通过将线条想象成在整个图像上延伸,您会发现更多有用的交叉点。
      • @MartinFoot 我真的需要这个问题的帮助,你能帮帮我吗? stackoverflow.com/questions/61216402/…
      猜你喜欢
      • 2010-11-11
      • 2018-09-19
      • 2017-05-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-10-28
      • 1970-01-01
      相关资源
      最近更新 更多