【发布时间】: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 算法。参见例如stackoverflow.com/questions/9871084/niblack-thresholding
标签: image-processing opencv threshold