首页 > 解决方案 > 使用 OpenCV 扫描文档

问题描述

我正在开发与此类似的 Android 文档扫描仪。

我对此进行了搜索,发现这可以通过 OpenCV 实现,因此从 OpenCV 开始。

我尝试了很多示例来从图像中检测文档,但无法检测图像是否具有浅色背景。检查示例图像以进行测试。

示例图像

我正在使用 OpenCV Android SDK 并使用 java 代码进行图像处理。这是代码:

public void scanDocument(Bitmap mBitmap)
{
    Mat mOriginalMat = convertToMat(mBitmap);
    int mRatio = getRadio(mOriginalMat);
    Size mSize = getImageFitSize(mOriginalMat, mRatio);

    Mat resizedMat = resizeMat(mOriginalMat, mSize);
    Mat colorMat = grayMat(resizedMat, mSize);
    Mat blurMat = medianBlurMat(colorMat, mSize);
    Mat thresholdMat = cannyEdgeMat(blurMat, mSize);

    ArrayList<MatOfPoint> contourList = findContours(thresholdMat, Imgproc.RETR_LIST, Imgproc.CHAIN_APPROX_SIMPLE);
    double maxArea = 0.0;
    int maxAreaIdx = -1;
    Collections.sort(contourList, new Comparator<MatOfPoint>() {
        @Override
        public int compare(MatOfPoint lhs, MatOfPoint rhs)
        {
        return Double.valueOf(Imgproc.contourArea(rhs)).compareTo(Imgproc.contourArea(lhs));
        }
    });

    ArrayList<MatOfPoint> contourListMax = new ArrayList<>();
    for (int idx = 0; idx < contourList.size(); idx++)
    {
        MatOfPoint contour = contourList.get(idx);

        MatOfPoint2f c2f = new MatOfPoint2f(contour.toArray());
        MatOfPoint2f approx = new MatOfPoint2f();
        double epsilon = Imgproc.arcLength(c2f, true);
        Imgproc.approxPolyDP(c2f, approx, epsilon * 0.02, true);

        Point[] points = approx.toArray();
        MatOfPoint approxTemp = new MatOfPoint(approx.toArray());

        if (points.length == 4 && Imgproc.isContourConvex(approxTemp) && maxArea < Imgproc.contourArea(approxTemp))
        {
            maxArea = Imgproc.contourArea(approxTemp);
            maxAreaIdx = idx;
            Point[] foundPoints = sortPoints(points);

            contourListMax.add(approxTemp);

            mPointFMap = new HashMap<>();
            mPointFMap.put(0, new PointF((float) foundPoints[0].x + xGap, (float) foundPoints[0].y + yGap));
            mPointFMap.put(1, new PointF((float) foundPoints[1].x + xGap, (float) foundPoints[1].y + yGap));
            mPointFMap.put(2, new PointF((float) foundPoints[3].x + xGap, (float) foundPoints[3].y + yGap));
            mPointFMap.put(3, new PointF((float) foundPoints[2].x + xGap, (float) foundPoints[2].y + yGap));
            break;
        }
    }

    Imgproc.drawContours(resizedMat, contourListMax, -1, new Scalar(255, 165, 0), 2);
    showMatToImageView(resizedMat);
}

private Mat convertToMat(Bitmap bitmap)
{
    Mat mat = Imgcodecs.imread(mFilePath);// new Mat(bitmap.getWidth(), bitmap.getHeight(), CvType.CV_8UC1);
    Imgproc.cvtColor(mat, mat, Imgproc.COLOR_BGR2RGB);
    return mat;
}

private double getRadio(Mat mat)
{
    double ratio;
    if (mat.size().width > mat.size().height)
        ratio = mat.size().height / mMainLayout.getHeight();
    else
        ratio = mat.size().width / mMainLayout.getWidth();
    return ratio;
}

private Size getImageFitSize(Mat mat, double ratio)
{
    int height = Double.valueOf(mat.size().height / ratio).intValue();
    int width = Double.valueOf(mat.size().width / ratio).intValue();
    return new Size(width, height);
}

private void showMatToImageView(Mat mat)
{
    final Bitmap bitmap = Bitmap.createBitmap(mat.width(), mat.height(), Bitmap.Config.ARGB_8888);
    Utils.matToBitmap(mat, bitmap);
    runOnUiThread(new Runnable()
    {
        @Override
        public void run()
        {
            mSourceImageView.setImageBitmap(bitmap);
            mProgressBar.setVisibility(View.GONE);
        }
    });
}

private Mat resizeMat(Mat mat, Size size)
{
    Mat resizedMat = new Mat(size, CvType.CV_8UC4);
    Imgproc.resize(mat, resizedMat, size);
    return resizedMat;
}

private Mat grayMat(Mat mat, Size size)
{
    Mat grayMat = new Mat(size, CvType.CV_8UC4);
    Imgproc.cvtColor(mat, grayMat, Imgproc.COLOR_RGB2GRAY, 4);
    return grayMat;
}

private Mat medianBlurMat(Mat mat, Size size)
{
    Mat blurMat = new Mat(size, CvType.CV_8UC4);
    Imgproc.medianBlur(mat, blurMat, 3);
    return blurMat;
}

private Mat cannyEdgeMat(Mat mat, Size size)
{
    if (thresholdVal <= 0)
        thresholdVal = 200;
    Mat cannyEdgeMat = new Mat(size, CvType.CV_8UC1);
    Imgproc.Canny(mat, cannyEdgeMat, thresholdVal * 0.5, thresholdVal, 3, true);
    return cannyEdgeMat;
}

private ArrayList<MatOfPoint> findContours(Mat mat, int retrievalMode, int approximationMode)
{
    ArrayList<MatOfPoint> contourList = new ArrayList<>();
    Mat hierarchy = new Mat();
    Imgproc.findContours(mat, contourList, hierarchy, retrievalMode, approximationMode);
    hierarchy.release();

    return contourList;
}

我想在透视变换的帮助下检测文档点,我会将文档转换为直线并进行其他图像过滤。

获取此结果图像。

结果图像

请帮我解决这个问题。

标签: javaandroidopencvimage-processing

解决方案


您的问题是在资源非常有限的领域。事实上,我已经从字面上复制粘贴了上面的代码,因为我什至不知道从哪里开始。但是,您也没有包括该行中使用的sortPoints()函数Point[] foundPoints = sortPoints(points);thresholdValxGapyGap整数。它们是如何初始化的?通过分享其余的代码,你会帮我一个大忙。谢谢你。


推荐阅读