模糊图像的阈值 - 第 2 部分

Posted

技术标签:

【中文标题】模糊图像的阈值 - 第 2 部分【英文标题】:Threshold of blurry image - part 2 【发布时间】:2012-11-18 07:50:21 【问题描述】:

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

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

从那时起,我尝试使用this post 中描述的形态闭合操作来使图像的亮度均匀:

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

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

    我不得不手动选择阈值。虽然关闭操作会产生均匀的亮度,但其他图像的亮度级别可能会有所不同。 图像的不同部分在阈值水平稍有变化时效果会更好。例如,左上角的 9 和 7 出现部分褪色,应该有一个较低的阈值,而一些 6 已经融合成 8,应该有一个更高的阈值。

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

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

Edit2: mmgp suggested 使用维纳滤镜去除模糊。我将this code for Wiener filtering in OpenCV 改编为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)

【问题讨论】:

对您的问题有不同的看法(如果不是奇怪的话):如果您可以控制使用的字体,请将其更改为更好的字体。 “更好”意味着让 6 或 9 更难变成 8。也许也让它更大胆。我想在某些时候您会尝试识别这些数字,这就是您提出问题的原因。 不幸的是,我将“在野外”从 Android 用户的相机中识别这些图像,因此无法控制字体。尽管否则这将是一个有用的解决方案。 用另一种方式解决问题:你为什么要让数字更清晰?之后是对它们进行 OCR 吗?或许,您可以通过使用这些数字训练 OCR 并使用 OCR 在图像中检测它们来获得相当不错的结果。 如果您对用户要从中拍照的每个可能的数字都有一个很好的样本,那么直接从这些数字训练可能是明智的。我对前一个短语的措辞使情况不太可能如此。也许它可以使用一类分类器来实现,例如一类 SVM,因为您也缺乏对您不希望成为数字的内容的良好表示。现在,如果您没有断数字或错误连接的数字,那么训练分类器会容易得多。细化它们后,任务就容易多了,也更容易给出正确的结果。 检查 niblack 算法。参见例如***.com/questions/9871084/niblack-thresholding 【参考方案1】:

您可能会尝试的一些提示:

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

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

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

【讨论】:

CV_OTSU 标志的好主意,效果很好!不幸的是,形态开放在我的阈值图像上效果不佳(请参阅我编辑的帖子)。此外,Astor 在阈值之前有tried median/bilateral filters,它的效果不如关闭+正常阈值。 可能先打开然后关闭会改善仅打开的结果。【参考方案2】:

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

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

【讨论】:

对此类文档的有效二值化是 Sauvola 技术(google sauvola + 二值化)。在OpenCV中没有实现,但是很容易实现,可以使用积分图compute the mean and standard deviation of image patches extremely fast。 我在您的图像上尝试了 suvola,我设法获得了相当不错的结果,但实际上,正如 mmgp 所说,通过微调参数。并且可能这组参数仅适用于该图像,但不适用于具有不同条件的图像。【参考方案3】:

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

其中一个“事物”是Wiener filter,例如,在 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):

@ @@ 987654324 987654325 987654326 @@ @@ 搜索@@ 987654330 987654331 987654332 @@ @@ 搜索@@ 987654336 987654337 @结果结果@ 987654352@

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

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

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

编辑:

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

【讨论】:

这是一个很有前途的建议。顺便说一句,就上下文而言,我正试图让它在带有手机图片的 Android 上运行。这种方法会通过任意方向的模糊来提高图像质量吗?它会改善您可能在手机图片中看到的其他类型的图像缺陷吗? 我将把我的答案限制在模糊部分,因为其他图像缺陷太广泛了。我在这里展示的是一个具有已知点扩散函数 (PSF) 估计值的反卷积,它是一个垂直函数。有一种称为盲反卷积的方法,它会尝试猜测一个合适的 PSF,可能比我猜的要好。如果您对“去模糊”的一般方法感兴趣,则应该研究这些。 我主要是在寻找能够始终如一地提供高质量数字的东西。如果它只是修复模糊,如果我可以使用适当的二值化算法补偿模糊、照明和其他缺陷,这可能不值得实现(OpenCV 没有内置实现)。你怎么看? 我不认为你可以用二值化算法来补偿模糊,这是一种不同形式的问题。如果您已经确定了数独板,我认为这里的照明不是问题。此外,反卷积有很好的机会提供更强的边缘,使得更简单的二值化器更容易。请记住,在图像处理中没有“始终如一地为 X 提供高质量”之类的东西,其中 X 是正在调查的任何问题。 我在 opencv 中搜索了反卷积并找到了一些可能对您有所帮助的内容:github.com/Itseez/opencv/blob/master/samples/python2/… 和 ibm.com/developerworks/mydeveloperworks/blogs/theTechTrek/entry/…(没有阅读它们,但听起来很有帮助)。【参考方案4】:

如果您愿意花一些时间来处理它,可以使用去模糊技术在处理之前对图片进行锐化。 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 假设的黑色更好的结果。

【讨论】:

以上是关于模糊图像的阈值 - 第 2 部分的主要内容,如果未能解决你的问题,请参考以下文章

快速图像阈值处理

OpenCV中的图像处理 —— 图像阈值+图像平滑+形态转换

#yyds干货盘点# Python Opencv实战之图像阈值和模糊处理,万字实战,收藏起来吧~

《学习OpenCV3》第10章 滤波与卷积

Matlab系列一维信号/数字图像小波阈值去噪(软阈值,硬阈值,固定阈值)

2.5基本的阈值操作