Opencv 快速图像阈值化

Opencv 快速图像阈值化,opencv,image-processing,threshold,Opencv,Image Processing,Threshold,什么是一种快速可靠的方法来对可能出现模糊和亮度不均匀的图像进行阈值设置? 示例(模糊但亮度一致): 因为不能保证图像具有均匀的亮度,所以使用固定阈值是不可行的。自适应阈值可以正常工作,但由于模糊性,它会在特征中创建中断和扭曲(这里,重要的特征是数独数字): 我还尝试过使用直方图均衡化(使用OpenCV的equalizeHist函数)。它在不减少亮度差异的情况下增加对比度 我找到的最佳解决方案是将图像除以其形态闭合(归功于),以使亮度均匀,然后重新规范化,然后使用固定阈值(使用大津算法选择最佳

什么是一种快速可靠的方法来对可能出现模糊和亮度不均匀的图像进行阈值设置?

示例(模糊但亮度一致):

因为不能保证图像具有均匀的亮度,所以使用固定阈值是不可行的。自适应阈值可以正常工作,但由于模糊性,它会在特征中创建中断和扭曲(这里,重要的特征是数独数字):

我还尝试过使用直方图均衡化(使用OpenCV的
equalizeHist
函数)。它在不减少亮度差异的情况下增加对比度

我找到的最佳解决方案是将图像除以其形态闭合(归功于),以使亮度均匀,然后重新规范化,然后使用固定阈值(使用大津算法选择最佳阈值水平):

以下是OpenCV for Android中的代码:

Mat kernel = Imgproc.getStructuringElement(Imgproc.MORPH_ELLIPSE, new Size(19,19));
Mat closed = new Mat(); // closed will have type CV_32F
Imgproc.morphologyEx(image, closed, Imgproc.MORPH_CLOSE, kernel);
Core.divide(image, closed, closed, 1, CvType.CV_32F);
Core.normalize(closed, image, 0, 255, Core.NORM_MINMAX, CvType.CV_8U);
Imgproc.threshold(image, image, -1, 255, Imgproc.THRESH_BINARY_INV
    +Imgproc.THRESH_OTSU); 
这非常有效,但关闭操作非常缓慢。减小结构元素的大小会提高速度,但会降低准确性

编辑:根据DCS的建议,我尝试使用高通滤波器。我选择了拉普拉斯滤波器,但我希望Sobel和Scharr滤波器也能得到类似的结果。该滤波器在不包含特征的区域拾取高频噪声,并且由于模糊而遭受与自适应阈值类似的失真。它还需要大约与关闭操作一样长的时间。以下是一个带有15x15过滤器的示例:

编辑2:根据AruniRC的回答,我在图像上使用了Canny边缘检测和建议的参数:

double mean = Core.mean(image).val[0];
Imgproc.Canny(image, image, 0.66*mean, 1.33*mean);
我不知道如何可靠地自动微调参数以获得连接的数字

替代方法:

假设您的目的是将数字清楚地进行二值化。。。将焦点转移到组件,而不是整个图像

这里有一个非常简单的方法:

  • 在图像上做一个精明的边缘映射。首先,尝试将参数设置为Canny函数,范围为低阈值至0.66*[平均值],高阈值至1.33*[平均值]。(指格雷维尔值的平均值)
  • 您需要稍微修改一下参数,以获得一个主要组件/数字作为单独组件清晰可见的图像。在这个阶段,近乎完美就足够了
  • <> L> >P>考虑每个Canny边缘作为连接组件(即使用CVFIDCONTIONSURS)(或C++对应的),可以估计前景和背景灰度级并达到阈值。 最后,请看第2部分。三,。属于跳过大多数非必要的理论部分,在OpenCV中实现它应该不会太困难

    希望这有帮助

    编辑1:

    基于精明的边缘阈值,这里有一个非常粗略的想法,足以微调值。
    高\u阈值
    控制检测边缘之前必须达到的强度。基本上,边缘必须具有大于要首先检测的
    高\u阈值的梯度幅值。这就是边缘的初始检测


    现在,
    low_阈值
    处理连接附近边缘的问题。它控制附近断开连接的边合并为一条边的数量。有关更好的想法,请阅读的“步骤6”。尝试设置一个非常小的低阈值,看看事情是如何发生的。如果对这些图像不起作用,你可以放弃0.66*[平均值]的东西——不管怎样,这只是一个经验法则。

    根据沃恩·卡托和塞劳特的建议,我在关闭图像之前缩小了图像,然后将关闭的图像放大到常规大小。我还按比例减小了内核大小

    Mat kernel = Imgproc.getStructuringElement(Imgproc.MORPH_ELLIPSE, new Size(5,5));
    Mat temp = new Mat(); 
    
    Imgproc.resize(image, temp, new Size(image.cols()/4, image.rows()/4));
    Imgproc.morphologyEx(temp, temp, Imgproc.MORPH_CLOSE, kernel);
    Imgproc.resize(temp, temp, new Size(image.cols(), image.rows()));
    
    Core.divide(image, temp, temp, 1, CvType.CV_32F); // temp will now have type CV_32F
    Core.normalize(temp, image, 0, 255, Core.NORM_MINMAX, CvType.CV_8U);
    
    Imgproc.threshold(image, image, -1, 255, 
        Imgproc.THRESH_BINARY_INV+Imgproc.THRESH_OTSU);
    
    下图显示了3种不同方法的并排结果:

    左-常规大小闭合(432像素),大小19内核

    中半尺寸闭合(216像素),尺寸为9内核

    右-四分之一大小闭合(108像素),大小为5内核

    当用于闭合的图像尺寸变小时,图像质量会恶化,但恶化程度不足以影响特征识别算法。对于四分之一大小的闭合,即使调整大小,速度也会略微提高16倍以上,这表明闭合时间大致与图像中的像素数成比例


    任何关于如何进一步改进这个想法的建议(通过进一步降低速度或减少图像质量的恶化)都是非常受欢迎的

    如果您知道网格的收成很好,您可以尝试在每个瓷砖的基础上工作。处理9个子图像而不是整个图片很可能会使每个子图像的亮度更加均匀。如果你的裁剪是完美的,你甚至可以试着去为每个数字单元分别;但这一切都取决于你的收割机有多可靠。

    我们使用布拉德利算法来解决非常类似的问题(从背景中分割字母,光线不均匀,背景颜色不均匀),如下所述:,C代码如下:。它适用于积分图像,可以使用OpenCV的
    integral
    函数计算积分图像。它非常可靠和快速,但它本身并没有在OpenCV中实现,而是易于移植

    另一个选项是openCV中的adaptiveThreshold方法,但我们没有尝试:。平均值版本与bradleys相同,只是它使用一个常数来修改平均值,而不是百分比,我认为这更好


    此外,这里还有一篇好文章:

    如果与平面形状相比,椭圆形状的计算非常复杂。 尝试更改:

    Mat kernel = Imgproc.getStructuringElement(Imgproc.MORPH_ELLIPSE, new Size(19,19));
    
    致:


    可以在对精度影响较小的情况下加速足够的解决方案。

    您可以尝试在高通滤波图像上设置阈值,假设亮度变化发生在低频率。然而,我不知道这些飞行有多快
    Mat kernel = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(19,19));