检测照片中纸张角落的算法
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<cv::Vec4i> lines;
在我的项目的全局范围内声明。
嘿@GBF_Gabriel,你能告诉我如何找到所需图像的四个角或边缘而不是画线。
我收到此错误 E/cv::error(): OpenCV(4.3.0) 错误: cvDrawContours 中的断言失败(reader.ptr != NULL)【参考方案2】:
我是 Martin 的朋友,他今年早些时候正在研究这个问题。这是我第一个编码项目,有点匆忙结束,所以代码需要一些错误......解码...... 我将根据我已经看到您所做的事情提供一些提示,然后在明天休息时对我的代码进行排序。
第一个提示,OpenCV
和 python
很棒,请尽快转向它们。 :D
不是去除小物体和/或噪音,而是降低canny约束,使其接受更多边缘,然后找到最大的闭合轮廓(在OpenCV中使用findcontour()
和一些简单的参数,我想我使用了CV_RETR_LIST
) .当它在一张白纸上时可能仍然会挣扎,但肯定会提供最佳效果。
对于Houghline2()
变换,尝试使用CV_HOUGH_STANDARD
而不是CV_HOUGH_PROBABILISTIC
,它将给出rho 和theta 值,定义线在极坐标中,然后您可以将线分组在一定的公差范围内。
我的分组用作查找表,对于从霍夫变换输出的每一行,它都会给出一个 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) > 100, contours) contours = filter(lambda cont: cv2.contourArea(cont) > 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 Java 实现票据纸张的四边形边缘检测与提取摆正