[英]Detect a frame with squares in corners with OpenCV.js
我一直在尝试使用 Javascript 和 OpenCV.js 创建一个填充表单扫描仪。 我基本上想做的是拍一张纸的照片,上面有一张填好的表格,并且能够扫描照片并分析表格中的答案。 第一步是实际找到图片中的形式并应用透视变换来获得纸张的“自顶向下视图”。 我所做的是我设法让脚本来检测这张纸并应用转换来很好地扫描它。 我通过应用灰度,然后是 Canny 边缘检测,遍历找到的边缘并找到最大的一个有 4 个角,并假设这一定是我的论文。
这工作得比较好,但脚本时不时会混淆纸张的实际内容 - 有时会检测到其他矩形并假定为纸张,有时拍摄纸张的背景非常轻且边缘不清楚(对比度不够)。 当脚本认为它找到了论文时,这真的会破坏我的流程,但实际上它是别的东西。 我想改进这篇论文检测部分,这样我就可以始终确保检测到正确的东西。 我想 - 让我们在表单周围添加一个自定义框架,这将更容易检测并在角落添加一些正方形(仔细检查找到的框架是否是我正在寻找的框架的 100%)。
所以我创建了这样的东西:
现在我希望能够检测框架的角落并确保角落中有“填充”方块,以确保这是我正在寻找的框架的 100%。 您能否就如何使用 openCV 实现这一目标提出建议? 这是正确的方法吗? 谢谢!
我以前处理过类似的问题。 我使用 OpenCV 的 C++ 实现,但我有一些提示。
分割纸张
要实现更好的分割,请考虑尝试Image Quantization 。 该技术将图像分割为N 个簇,即将颜色相似的像素归为一组。 该组然后由一种颜色表示。
这种技术相对于其他技术的优势,比如纯二元阈值,是它可以识别多种颜色分布——那些将被分组到N 个集群中。 检查一下(对不起,链接,我还不允许发布直接图片):
这将帮助您更好地分割论文。 该实现使用称为“K-means”的聚类算法(稍后会详细介绍)。 在我的示例中,我尝试了 3 个集群和 5 个算法“运行”(或尝试,因为 K-means 通常运行不止一次)。
cv::Mat imageQuantization( cv::Mat inputImage, int numberOfClusters = 3, int iterations = 5 ){
//step 1 : map the src to the samples
cv::Mat samples(inputImage.total(), 3, CV_32F);
auto samples_ptr = samples.ptr<float>(0);
for( int row = 0; row != inputImage.rows; ++row){
auto src_begin = inputImage.ptr<uchar>(row);
auto src_end = src_begin + inputImage.cols * inputImage.channels();
//auto samples_ptr = samples.ptr<float>(row * src.cols);
while(src_begin != src_end){
samples_ptr[0] = src_begin[0];
samples_ptr[1] = src_begin[1];
samples_ptr[2] = src_begin[2];
samples_ptr += 3; src_begin +=3;
}
}
//step 2 : apply kmeans to find labels and centers
int clusterCount = numberOfClusters; //Number of clusters to split the set by
cv::Mat labels;
int attempts = iterations; //Number of times the algorithm is executed using different initial labels
cv::Mat centers;
int flags = cv::KMEANS_PP_CENTERS;
cv::TermCriteria criteria = cv::TermCriteria( CV_TERMCRIT_ITER | CV_TERMCRIT_EPS,
10, 0.01 );
//the call to kmeans:
cv::kmeans( samples, clusterCount, labels, criteria, attempts, flags, centers );
//step 3 : map the centers to the output
cv::Mat clusteredImage( inputImage.size(), inputImage.type() );
for( int row = 0; row != inputImage.rows; ++row ){
auto clusteredImageBegin = clusteredImage.ptr<uchar>(row);
auto clusteredImageEnd = clusteredImageBegin + clusteredImage.cols * 3;
auto labels_ptr = labels.ptr<int>(row * inputImage.cols);
while( clusteredImageBegin != clusteredImageEnd ){
int const cluster_idx = *labels_ptr;
auto centers_ptr = centers.ptr<float>(cluster_idx);
clusteredImageBegin[0] = centers_ptr[0];
clusteredImageBegin[1] = centers_ptr[1];
clusteredImageBegin[2] = centers_ptr[2];
clusteredImageBegin += 3; ++labels_ptr;
}
}
//return the output:
return clusteredImage;
}
请注意,该算法还会生成两个额外的矩阵。 “标签”是用标识其簇的整数标记的实际像素。 “中心”是每个集群的平均值。
检测边缘
现在,在这个分割的图像上运行边缘检测器是微不足道的。 让我们试试康尼。 参数当然可以自己调整。 在这里,我尝试了下阈值0f 30 和上阈值90。非常标准,只需确保上阈值遵循 = 3 * 下阈值的条件,按照 Canny 的建议。 这是结果:
cv::Mat testEdges;
float lowerThreshold = 30;
float upperThreshold = 3 * lowerThreshold;
cv::Canny( testSegmented, testEdges, lowerThreshold, upperThreshold );
检测线条
好的。 想要检测边缘检测器产生的线条吗? 在这里,至少有 2 个选项。 第一个也是最直接的:使用Hough's Line Detector 。 但是,正如您肯定已经看到的那样,调整 Hough 以找到您实际需要的线条可能很困难。
过滤 Hough 返回的线的一种可能解决方案是运行“角度过滤器” ,因为我们只寻找(接近)垂直和水平线。 您还可以按长度过滤线条。
此代码片段给出了想法,您需要实际实现过滤器: // 运行 Hough's Line Detector: cv::HoughLinesP(grad,linesP, 1, CV_PI/180, minVotes, minLineLength, maxLineGap );
// Process the points (lines)
for( size_t i = 0; i < linesP.size(); i++ ) //points are stored in linesP
{
//get the line
cv::Vec4i l = linesP[i]; //get the line
//get the points:
cv::Point startPoint = cv::Point( l[0], l[1] );
cv::Point endPoint = cv::Point( l[2], l[3] );
//filter horizontal & vertical:
float dx = abs(startPoint.x - endPoint.x);
float dy = abs(startPoint.y - endPoint.y);
//angle filtering, delta y and delta x
if ( (dy < maxDy) || (dx < maxDx) ){
//got my target lines!
}
}
在上面的代码中,我实际上是在处理线组件,而不是角度。 因此,我的“角度”限制由 2 个最小组件长度定义: maxDy - y 轴的最大“delta”长度,以及 x 轴的maxDx 。
线检测的另一个解决方案是利用这样一个事实,即您只查看具有拐角或它们之间大约 90 度的线。 您可以运行形态过滤器以通过命中或未命中操作来检测这些“模式”:)
无论如何,回到 Hough,这是我在没有太多参数调整和应用角度/长度线过滤器后得到的检测:
凉爽的。 绿点代表线条的起点和终点。 如你所见,有很多。 我们如何“组合”它们? 如果我们计算这些点的平均值会怎样? 好的,但我们应该得到 PER“象限”线的平均值。 如下图所示,我将输入图像划分为 4 个象限(黄线):
每个象限 - 希望 - 将包含描述纸角的点。 对于这些象限中的每一个,检查哪些点落在给定的象限上并计算它们的平均值。 这是一般的想法。
那是相当多的代码要写。 幸运的是,如果我们稍微研究一下这个问题,我们可以看到所有的绿点都倾向于在一些非常明确的区域(或者,正如我们之前所说的“象限”)中的CLUSTER 。再次输入 K-means。
无论如何,K-means 都会对相似值的数据进行分组。 它可以是像素,也可以是空间点,可以是任何东西,只要给它数据集和你想要的簇数,它就会吐出找到的簇和所说簇的方法——很好!
如果我使用 Hough 返回的线点运行 K-means,我会得到最后一张图片中显示的结果。 我还丢弃了离平均值太远的点。 点的平均值通过“中心”矩阵返回,在这里它们以橙色呈现 - 这非常接近!
希望其中一些对您有所帮助! :)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.