关于图像处理:检测照片中纸张角落的算法

Algorithm to detect corners of paper sheet in photo

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

我目前的方法是:

RGB>灰色>用阈值进行canny边缘检测>膨胀(1)>移除小对象(6)>清除边界对象>基于凸区域选取Larges博客。>[角检测-未实现]

我忍不住想,必须有一种更强大的"智能/统计方法"来处理这种类型的分割。我没有太多的训练实例,但我可能可以收集到100张图片。

更广泛的背景:

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

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

案例1 http://madteckhead.com/tmp/img_0773_sml.jpg案例2 http://madteckhead.com/tmp/img_0774_sml.jpg案例3 http://madteckhead.com/tmp/img__sml.jpg案例4 http://madteckhead.com/tmp/img_0776_sml.jpg

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

案例1-Canny http://madteckhead.com/tmp/img__canny.jpg案例1-Canny后http://madteckhead.com/tmp/img__post canny.jpg案例1-最大的博客http://madteckhead.com/tmp/img__blob.jpg

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

案例2-Canny http://madteckhead.com/tmp/img__canny.jpg案例2-Canny后http://madteckhead.com/tmp/img__post canny.jpg案例2-最大的博客http://madteckhead.com/tmp/img__blob.jpg

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

编辑:Hough转换进度

问:什么算法可以将Hough线聚类以找到角?根据答案中的建议,我可以使用hough转换、选择行并过滤它们。我目前的方法相当粗糙。我假设发票与图片的偏差总是小于15度。如果是这样的话,我会得到一些合理的结果(见下文)。但我不完全确定一个合适的算法来聚类线(或投票)来外推角落。Hough线不是连续的。在噪声图像中,可能存在平行线,因此需要某种形式或距离线原点的度量。有什么想法吗?

案例1 http://madteckhead.com/tmp/img_0773_hough.jpg案例2 http://madteckhead.com/tmp/img_0774_hough.jpg案例3 http://madteckhead.com/tmp/img_0775_hough.jpg案例4 http://madteckhead.com/tmp/img_0776_hough.jpg


我是马丁的朋友,今年早些时候在做这个。这是我有史以来第一个编码项目,有点匆忙结束了,所以代码需要一些错误…解码…我会从你已经做的事情中给出一些提示,然后在明天我休假的时候整理我的代码。

第一个建议是,OpenCVpython太棒了,尽快转移到他们那里。D

不要移除小物体和或噪音,降低Canny限制,这样它可以接受更多的边缘,然后找到最大的闭合轮廓(在OpenCV中,使用findcontour()和一些简单参数,我认为我使用了CV_RETR_LIST)。当它在白纸上的时候可能仍然很困难,但肯定能提供最好的效果。

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

我的分组作为一个查找表工作,对于从hough转换输出的每一行,它将给出一对rho和theta。如果这些值在表中一对值的5%以内,则会丢弃这些值;如果这些值在5%以外,则会向表中添加一个新条目。

然后可以更容易地分析平行线或线之间的距离。

希望这有帮助。


我大学的一个学生小组最近展示了一款iPhone应用程序(以及python opencv应用程序),他们正是为了做到这一点而编写的。我记得,步骤是这样的:

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

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


以下是我经过一番试验后得出的结论:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
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()

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

1234


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

Marvin框架为此提供了Moravec算法的实现。你可以找到报纸的角作为起点。以下是Moravec算法的输出:

enter image description here


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

但是这种检测对于实时检测非常有用,而不仅仅是一张不总是返回最佳结果的图片。

result


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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);


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


  • 转换为实验室空间

  • 使用kmeans段2集群

  • 然后在其中一个集群上使用轮廓或小时(内部)