10

如何对这个模糊的图像进行阈值处理以使数字尽可能清晰?

上一篇文章中,我尝试对模糊图像(左)进行自适应阈值处理,结果导致数字失真和断开连接(右):

在此处输入图像描述

从那时起,我尝试使用本文所述的形态闭合操作来使图像的亮度均匀:

在此处输入图像描述

如果我自适应地对该图像进行阈值处理,我不会得到明显更好的结果。但是,由于亮度大致均匀,我现在可以使用普通阈值:

在此处输入图像描述

这比以前好多了,但我有两个问题:

  1. 我不得不手动选择阈值。尽管关闭操作会导致亮度均匀,但其他图像的亮度级别可能会有所不同。
  2. 如果阈值水平略有变化,图像的不同部分会做得更好。例如,左上角的 9 和 7 出现部分褪色,应该有一个较低的阈值,而一些 6s 已经融合成 8s,应该有一个更高的阈值。

我认为回到自适应阈值,但块大小非常大(图像的 1/9)可以解决这两个问题。相反,我最终得到了一个奇怪的“光环效果”,其中图像的中心更亮,但边缘与正常阈值图像大致相同:

在此处输入图像描述

编辑:remi建议从形态上打开这篇文章右上角的阈值图像。这不太好用。使用椭圆内核,只有 3x3 足够小,可以避免完全消除图像,即使这样,数字也会出现明显的破损:

在此处输入图像描述

Edit2: mmgp建议使用 Wiener 过滤器来消除模糊。我将OpenCV 中的 Wiener 过滤代码改编为 OpenCV4Android,但它使图像更加模糊!这是使用我的代码和 5x5 内核过滤之前(左)和之后的图像:

在此处输入图像描述

这是我改编的代码,它就地过滤:

private void wiener(Mat input, int nRows, int nCols) { // I tried nRows=5 and nCols=5

    Mat localMean = new Mat(input.rows(), input.cols(), input.type());
    Mat temp = new Mat(input.rows(), input.cols(), input.type());
    Mat temp2 = new Mat(input.rows(), input.cols(), input.type());

    // Create the kernel for convolution: a constant matrix with nRows rows 
    // and nCols cols, normalized so that the sum of the pixels is 1.
    Mat kernel = new Mat(nRows, nCols, CvType.CV_32F, new Scalar(1.0 / (double) (nRows * nCols)));

    // Get the local mean of the input.  localMean = convolution(input, kernel)
    Imgproc.filter2D(input, localMean, -1, kernel, new Point(nCols/2, nRows/2), 0); 

    // Get the local variance of the input.  localVariance = convolution(input^2, kernel) - localMean^2 
    Core.multiply(input, input, temp);  // temp = input^2
    Imgproc.filter2D(temp, temp, -1, kernel, new Point(nCols/2, nRows/2), 0); // temp = convolution(input^2, kernel)
    Core.multiply(localMean, localMean, temp2); //temp2 = localMean^2
    Core.subtract(temp, temp2, temp); // temp = localVariance = convolution(input^2, kernel) - localMean^2  

    // Estimate the noise as mean(localVariance)
    Scalar noise = Core.mean(temp);

    // Compute the result.  result = localMean + max(0, localVariance - noise) / max(localVariance, noise) * (input - localMean)

    Core.max(temp, noise, temp2); // temp2 = max(localVariance, noise)

    Core.subtract(temp, noise, temp); // temp = localVariance - noise
    Core.max(temp, new Scalar(0), temp); // temp = max(0, localVariance - noise)

    Core.divide(temp, temp2, temp);  // temp = max(0, localVar-noise) / max(localVariance, noise)

    Core.subtract(input, localMean, input);  // input = input - localMean
    Core.multiply(temp, input, input); // input = max(0, localVariance - noise) / max(localVariance, noise) * (input - localMean)
    Core.add(input, localMean, input); // input = localMean + max(0, localVariance - noise) / max(localVariance, noise) * (input - localMean)
}
4

4 回答 4

6

您可以尝试的一些提示:

  • 在原始阈值图像(第一张图片右侧嘈杂的图像)中应用形态开口。您应该摆脱大部分背景噪音并能够重新连接数字。

  • 对原始图像使用不同的预处理而不是形态关闭,例如中值滤波器(倾向于模糊边缘)或双边滤波,这将更好地保留边缘但计算速度较慢。

  • 就阈值而言,您可以使用 cv::threshold 中的 CV_OTSU 标志来确定全局阈值的最佳值。局部阈值处理可能仍然更好,但与双边或中值滤波器一起使用会更好

于 2012-11-30T08:38:56.347 回答
6

我尝试使用 Otsu 算法(CV_OTSU - 感谢 remi!)分别对每个 3x3 框进行阈值处理,以确定每个框的最佳阈值。这比对整个图像进行阈值处理要好一些,并且可能更健壮一些。

在此处输入图像描述

不过,欢迎更好的解决方案。

于 2012-12-01T01:35:04.557 回答
6

如果您愿意花一些时间在它上面,可以使用去模糊技术在处理之前锐化图片。OpenCV 中还没有任何内容,但如果这是一种成败攸关的事情,您可以添加它。

有很多关于这个主题的文献: http ://www.cse.cuhk.edu.hk/~leojia/projects/motion_deblurring/index.html http://www.google.com/search?q=motion+deblurring

还有一些关于 OpenCV 邮件列表的讨论:http: //tech.groups.yahoo.com/group/OpenCV/message/20938

您看到的奇怪的“光环效应”可能是由于当自适应阈值位于/接近图像边缘时,OpenCV 假设颜色为黑色,并且它使用的窗口“悬在”边缘进入非图像领土。有一些方法可以解决这个问题,很可能你会制作一个比相机图像高和宽至少两个完整块大小的临时图像。然后将相机图像复制到它的中间。然后将临时图像的周围“空白”部分设置为相机图像的平均颜色。现在,当您执行自适应阈值时,边缘/附近的数据将更接近准确。它不会是完美的,因为它不是真实的图片,但它会产生比 OpenCV 假设的黑色更好的结果。

于 2012-12-06T18:01:07.143 回答
5

我的建议假设您可以识别数独单元,我认为这并没有要求太多。在我看来,尝试应用形态运算符(尽管我真的很喜欢它们)和/或二值化方法作为第一步是错误的方法。无论出于何种原因(原始摄像机角度和/或移动,以及其他原因),您的图像至少部分模糊。所以你需要通过执行反卷积来恢复它。当然要求完美的反卷积是太多了,但我们可以尝试一些事情。

这些“事物”之一是Wiener 滤波器,例如,在 Matlab 中,该函数被命名为deconvwnr。我注意到垂直方向的模糊,所以我们可以使用一定长度的垂直内核(以下示例中为 10)执行反卷积,并假设输入不是无噪声的(假设为 5%)——我我只是想在这里给出一个非常肤浅的看法,放轻松。在 Matlab 中,您的问题至少可以通过以下方式部分解决:

f = imread('some_sudoku_cell.png');
g = deconvwnr(f, fspecial('motion', 10, 90), 0.05));
h = im2bw(g, graythresh(g)); % graythresh is the Otsu method

以下是您的一些细胞的结果(原始、otsu、区域生长的 otsu、形态增强图像、具有区域生长的形态增强图像的 otsu、反卷积的 otsu):

在此处输入图像描述 在此处输入图像描述 在此处输入图像描述在此处输入图像描述 在此处输入图像描述 在此处输入图像描述
在此处输入图像描述 在此处输入图像描述 在此处输入图像描述 在此处输入图像描述 在此处输入图像描述 在此处输入图像描述
在此处输入图像描述 在此处输入图像描述 在此处输入图像描述 在此处输入图像描述 在此处输入图像描述 在此处输入图像描述
在此处输入图像描述 在此处输入图像描述 在此处输入图像描述 在此处输入图像描述 在此处输入图像描述 在此处输入图像描述
在此处输入图像描述 在此处输入图像描述 在此处输入图像描述 在此处输入图像描述 在此处输入图像描述 在此处输入图像描述
在此处输入图像描述 在此处输入图像描述 在此处输入图像描述 在此处输入图像描述 在此处输入图像描述 在此处输入图像描述

增强图像是通过使用半径为 3 的扁平圆盘执行 original + tophat(original) - bottomhat(original) 生成的。我手动选择了用于区域生长的种子点并手动选择了最佳阈值。

对于空单元格,您会得到奇怪的结果(解卷积的原始和大津):

在此处输入图像描述 在此处输入图像描述

但我认为你不会有麻烦检测一个单元格是否为空(全局阈值已经解决了它)。

编辑:

添加了我可以通过不同方法获得的最佳结果:区域增长。我还尝试了其他一些方法,但这是第二好的方法。

于 2012-12-03T00:19:52.740 回答