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

时间:2011-07-02 07:12:24

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

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

我目前的方法是:

RGB&gt;灰色&gt; <阈值边缘检测与阈值处理>膨胀(1)>删除小物体(6)&gt;明确的边界对象&gt;选择基于Convex Area的大型博客。 &GT; [角落检测 - 未实施]

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

更广泛的背景:

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

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

case 1 http://madteckhead.com/tmp/IMG_0773_sml.jpg case 2 http://madteckhead.com/tmp/IMG_0774_sml.jpg case 3 http://madteckhead.com/tmp/IMG_0775_sml.jpg case 4 http://madteckhead.com/tmp/IMG_0776_sml.jpg

在最好的情况下,这给出:

case 1 - canny http://madteckhead.com/tmp/IMG_0773_canny.jpg case 1 - post canny http://madteckhead.com/tmp/IMG_0773_postcanny.jpg case 1 - largest blog http://madteckhead.com/tmp/IMG_0773_blob.jpg

然而,在其他情况下它很容易失败:

case 2 - canny http://madteckhead.com/tmp/IMG_0774_canny.jpg case 2 - post canny http://madteckhead.com/tmp/IMG_0774_postcanny.jpg case 2 - largest blog http://madteckhead.com/tmp/IMG_0774_blob.jpg

提前感谢所有好主意!我爱你!

编辑:霍夫变换进展

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

case 1 http://madteckhead.com/tmp/IMG_0773_hough.jpg case 2 http://madteckhead.com/tmp/IMG_0774_hough.jpg case 3 http://madteckhead.com/tmp/IMG_0775_hough.jpg case 4 http://madteckhead.com/tmp/IMG_0776_hough.jpg

8 个答案:

答案 0 :(得分:26)

我是马丁的朋友,今年早些时候正在研究这个问题。这是我的第一个编码项目,有点匆忙结束,所以代码需要一些错误...解码... 我会从我已经看到你做过的事情中给出一些提示,然后在明天休息时对我的代码进行排序。

第一个提示,OpenCVpython非常棒,请尽快转移给他们。 :d

不是删除小物体和/或噪声,而是降低canny限制,因此它接受更多边缘,然后找到最大的闭合轮廓(在OpenCV中使用findcontour()使用一些简单的参数,我想我使用了{{ 1}})。当它在一张白纸上时,它仍然可能会挣扎,但肯定会提供最好的结果。

对于CV_RETR_LIST转换,请尝试Houghline2()而不是CV_HOUGH_STANDARD,它会给 rho theta 值,定义极坐标中的线,然后您可以将线分组到一定的容差范围内。

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

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

希望这有帮助。

答案 1 :(得分:17)

我大学的一个学生团队最近演示了一个他们写的iPhone应用程序(以及python OpenCV应用程序)。我记得,步骤是这样的:

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

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

答案 2 :(得分:16)

这是经过一些实验后我想出的:

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()

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

1 2 3 4

答案 3 :(得分:8)

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

Marvin Framework为此提供了Moravec算法的实现。您可以找到论文的角落作为起点。低于Moravec算法的输出:

enter image description here

答案 4 :(得分:4)

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

但是这种检测对于实时检测非常有用,而不是单个图像并不能总是返回最佳结果。

result

答案 5 :(得分:3)

边缘检测后,使用Hough变换。 然后,将这些点放在带有标签的SVM(支持向量机)中,如果示例上有平滑的线条,SVM将没有任何困难来划分示例和其他部分的必要部分。我对SVM的建议,提出了连接和长度等参数。也就是说,如果点数是连接的并且很长,它们很可能是收据的一条线。然后,您可以消除所有其他点。

答案 6 :(得分:3)

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

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);

答案 7 :(得分:1)

  1. 转换为实验室空间

  2. 使用kmeans segment 2 cluster

  3. 然后在其中一个星团上使用轮廓或霍夫(内心)