繁体   English   中英

使用 OpenCV.js 检测角中有正方形的框架

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM