检测照片中纸张角落的算法

Posted

技术标签:

【中文标题】检测照片中纸张角落的算法【英文标题】: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。 【参考方案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 我真的需要这个问题的帮助,你能帮帮我吗? ***.com/questions/61216402/…【参考方案3】:

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

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

【讨论】:

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

    转换为实验室空间

    使用 kmeans 段 2 集群

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

【讨论】:

【参考方案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 这个问题我真的需要帮助,你能帮帮我吗? ***.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 我真的需要这个问题的帮助,你能帮帮我吗? ***.com/questions/61216402/…

以上是关于检测照片中纸张角落的算法的主要内容,如果未能解决你的问题,请参考以下文章

基于opencv的纸张表面质量检测算法中

从纸张目标中检测圆圈和镜头

OpenCV Java 实现票据纸张的四边形边缘检测与提取摆正

图像中的矩形检测

3D 人脸的移动方式与 2D 照片不同。是不是有任何运动分析算法来检测这种差异?

checkpass系统靠谱吗?