【问题标题】:Detection of four corners of a document under different circumstances不同情况下文档四个角的检测
【发布时间】:2018-02-21 18:11:42
【问题描述】:

我尝试了以下两种方法:-

  1. 图像到 Mat 的转换

  2. 应用高斯模糊

  3. 然后canny边缘检测

  4. 寻找轮廓

这个方法的问题是:

  1. 检测到的轮廓过多
  2. 大部分是开放的轮廓
  3. 没有检测到我想要检测的内容

然后我改变了方法,在高斯模糊/中值模糊之后尝试了自适应阈值处理,效果好得多,我能够在 50% 的情况下检测到角

我目前面临的问题是页面检测需要对比鲜明的背景,没有任何反射。我认为它对于现实世界的使用来说太理想化了。

这是我需要帮助的地方。甚至在解决方案的方向上也受到高度赞赏,尤其是在 java 中。感谢期待 在这样的对比鲜明的背景下工作得非常好

检测到 4 个角 这张照片很麻烦,因为背景并不是最对比鲜明的

找到初始最大轮廓

更新: 中值模糊没有太大帮助,所以我追查了原因,发现页面边界是零碎检测的,而不是单个轮廓,因此它检测到最大轮廓作为页面边界因此执行了一些形态学操作来关闭相对较小的间隙,由此产生的最大轮廓肯定得到了改善,但它不是最优的。有什么想法可以改善巨大的差距吗?

变形原图

在变形图像中找到的最大轮廓

在理想情况下对图像进行 PS 变形会导致检测到错误的轮廓边界。在变形图像之前可以检查的任何条件也是一个奖励。谢谢

【问题讨论】:

  • 如果您的页面颜色一直是白色,那么您也可以尝试颜色分割。在这种情况下可以使用inRange 方法。
  • @ZdaR 不。颜色可能会有所不同,因为我还想扫描可能具有可变颜色背景的身份证
  • 如果您的目标是扫描身份证,您可以尝试查找不是 4 个角,而是 BoundingRect - 它总是关闭的。比您可以按区域过滤已建立的 BoundingRects 并获得最大的。
  • @AndriiOmelchenko 主要功能是扫描文件(可能是身份证或任何页面)。如果方向是理想的,BoundingRect 就可以工作。它将提供模糊点。另外,它还取决于检测我卡住的最大轮廓,我总是以不连续的轮廓结束,而 BoundingRect 只会围绕一条线/轮廓绘制一个矩形
  • "如果方向理想,BoundingRect 可以工作。" - 似乎没有:BoundingRect 每次都能正常工作,不仅是为了理想的方向 - 它是旋转的矩形。

标签: android opencv image-processing edge-detection


【解决方案1】:

如果你使用这样的方法:

public static RotatedRect getBestRectByArea(List<RotatedRect> boundingRects) {
    RotatedRect bestRect = null;

    if (boundingRects.size() >= 1) {
        RotatedRect boundingRect;
        Point[] vertices = new Point[4];
        Rect rect;
        double maxArea;
        int ixMaxArea = 0;

        // find best rect by area
        boundingRect = boundingRects.get(ixMaxArea);
        boundingRect.points(vertices);
        rect = Imgproc.boundingRect(new MatOfPoint(vertices));
        maxArea = rect.area();

        for (int ix = 1; ix < boundingRects.size(); ix++) {
            boundingRect = boundingRects.get(ix);
            boundingRect.points(vertices);
            rect = Imgproc.boundingRect(new MatOfPoint(vertices));

            if (rect.area() > maxArea) {
                maxArea = rect.area();
                ixMaxArea = ix;
            }
        }

        bestRect = boundingRects.get(ixMaxArea);
    }

    return bestRect;
}

private static Bitmap findROI(Bitmap sourceBitmap) {
    Bitmap roiBitmap = Bitmap.createBitmap(sourceBitmap.getWidth(), sourceBitmap.getHeight(), Bitmap.Config.ARGB_8888);

    Mat sourceMat = new Mat(sourceBitmap.getWidth(), sourceBitmap.getHeight(), CV_8UC3);
    Utils.bitmapToMat(sourceBitmap, sourceMat);

    final Mat mat = new Mat();
    sourceMat.copyTo(mat);

    Imgproc.cvtColor(mat, mat, Imgproc.COLOR_RGB2GRAY);
    Imgproc.threshold(mat, mat, 146, 250, Imgproc.THRESH_BINARY);

    // find contours
    List<MatOfPoint> contours = new ArrayList<>();
    List<RotatedRect> boundingRects = new ArrayList<>();
    Imgproc.findContours(mat, contours, new Mat(), Imgproc.RETR_LIST, Imgproc.CHAIN_APPROX_SIMPLE);

    // find appropriate bounding rectangles
    for (MatOfPoint contour : contours) {
        MatOfPoint2f areaPoints = new MatOfPoint2f(contour.toArray());
        RotatedRect boundingRect = Imgproc.minAreaRect(areaPoints);
        boundingRects.add(boundingRect);
    }

    RotatedRect documentRect = getBestRectByArea(boundingRects);
    if (documentRect != null) {
        Point rect_points[] = new Point[4];
        documentRect.points(rect_points);
        for (int i = 0; i < 4; ++i) {
            Imgproc.line(sourceMat, rect_points[i], rect_points[(i + 1) % 4], ROI_COLOR, ROI_WIDTH);
        }
    }
    Utils.matToBitmap(sourceMat, roiBitmap);
    return roiBitmap;
}

您可以为您的源图像实现如下结果:

或者那个:

如果您调整阈值并应用过滤器,您可以获得更好的结果。

【讨论】:

  • 感谢您的时间和努力。我已经尝试过这个解决方案,但它实际上不符合我的标准。如您所见,第一张图片的左上角和右上角有点偏离。这是因为图片是从一个角度拍摄的,而不是鸟瞰图,使得这张长方形的纸看起来有点像一个扭曲的长方形。现在如果我将这4个角的坐标传递给perspectiveTransformation函数,这个小间隙会变得更大,这对于实际目的是不可接受的。您能否针对同样的问题提出一种替代方法?
  • 您可以使用适当的contour,而不是矩形:首先-您可以找到最佳矩形,然后获得由该矩形界定的轮廓。
  • 我遵循的方法是转换为灰色垫子,然后是高斯模糊,精明,找到轮廓,然后提取我用来将其绑定在矩形内的最大轮廓。我认为您使用了相同的方法。如果我错了,请纠正我
【解决方案2】:

您可以使用以下一项或两项来选择单个轮廓:

  • 使用BoundingRectContourArea 评估squareness of each contourboundingRect() 返回正交矩形。为了更好地处理任意旋转,请使用 minAreaRect() 返回最佳旋转的矩形。

  • 反复使用 Cv.ApproxPoly 以减少到 4 边形状

            var approxIter = 1;
            while (true)
            {
                var approxCurve = Cv.ApproxPoly(largestContour, 0, null, ApproxPolyMethod.DP, approxIter, true);
                var approxCurvePointsTmp = new[] { approxCurve.Select(p => new CvPoint2D32f((int)p.Value.X, (int)p.Value.Y)).ToArray() }.ToArray();
                if (approxCurvePointsTmp[0].Length == 4)
                {
                    corners = approxCurvePointsTmp[0];
                    break;
                }
                else if (approxCurvePointsTmp[0].Length < 4) throw new InvalidOperationException("Failed to decimate corner points");
                approxIter++;
            }
    

但是,如果轮廓检测由于噪声/对比度而为您提供两个单独的轮廓,则这些都无济于事。

我认为可以使用霍夫线变换来帮助检测一条线被分成两个轮廓的情况。

如果是这样,可以对连接轮廓的所有组合重复搜索,以查看是否找到更大/更矩形的匹配。

【讨论】:

  • 我无法正确使用 HoughLine 方法。它为 80 阈值提供了太多行,最糟糕的部分是没有行似乎是页面边界。能否提供资源最好在java中实现?
  • 很遗憾,我没有任何 Java 示例。
  • 我不介意其他语言,只要它能为我的问题提供具体的解决方案
  • 我之前使用了方形/近似方法,然后是亚像素角细化和反向透视 - 它有效,但取决于阈值结果。我没有解决轮廓孔的问题。
  • 嘿彼得,我能够弄清楚如何检测图像中连续且可靠的轮廓。现在我认为找到轮廓角的最佳方法是使用霍夫线,因为有时轮廓检测是由于使用的模糊而导致的不规则图形,而霍夫线总是有助于检测正确的矩形。现在,问题是检测到的行太多且太不准确。非常感谢一些指导或一些资源。
【解决方案3】:

停止依赖边缘检测,这是宇宙中最糟糕的方法,转而使用某种形式的图像分割。

纸是白色的,背景是对比色的,这是你应该使用的信息。

【讨论】:

  • 你能提出一些具体的建议来帮助我解决问题吗?另外,纸张背景不一定是白色的
  • @Gurankas:分段。
  • 什么样的分割?
  • 区域分割。
  • 对不起,如果我听起来很笨,但你能指定任何可能有助于分割的 OpenCV 方法吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-04-26
  • 2018-02-13
  • 2013-10-30
  • 2012-02-24
  • 1970-01-01
  • 2011-05-01
  • 1970-01-01
相关资源
最近更新 更多