自动对焦例程检测模糊的非常小的差异

时间:2013-03-12 14:40:54

标签: c# image algorithm image-processing aforge

我正在开发一种用于微米级定位的自动对焦程序,因此我需要在图像之间找到非常小的聚焦/模糊差异。幸运的是,图像模式将始终相同(这些是原始2 MP图像的256x256中心裁剪):

0 µm off 50 µm off

         Perfect focus         |           50 µm off

找到上面两个更好的聚焦图像不是问题,我猜大多数算法都会这样做。但我真的需要比较焦点差异较小的图像,如下所示:

5 µm off 10 µm off

           5 µm off            |           10 µm off

越靠近最佳焦点的另一种选择是找到两个在焦平面相对侧具有相同模糊量的图像。例如,可以从-50μm保存图像,然后尝试在+50μm附近找到模糊相等的图像。可以说,图像是在+58μm处发现的,那么焦平面应该位于+4μm。

有关合适算法的任何想法吗?

3 个答案:

答案 0 :(得分:12)

令人惊讶的是,许多非常简单的自动对焦算法实际上在这个问题上表现得非常好。我实现了Liu,Wang& amp;和文章Dynamic evaluation of autofocusing for automated microscopic analysis of blood smear and pap smear中概述的16种算法中的11种。太阳。由于我无法找到设置阈值的建议,我还添加了一些没有阈值的变体。我还在SO上添加了一个简单而聪明的建议:比较JPEG压缩图像的文件大小(更大的尺寸=更多细节=更好的焦点)。

我的自动对焦例程执行以下操作:

  • 以2μm焦距,总范围±20μm的间隔保存21张图像。
  • 计算每张图像的焦点值。
  • 使结果适合二次多项式。
  • 找到给出多项式最大值的位置。

除直方图范围外的所有算法都给出了良好的结果。一些算法略有修改,例如它们使用X和X中的亮度差异。 Y方向。我还必须更改StdevBasedCorrelation,Entropy,ThresholdedContent,ImagePower和ThresholdedImagePower算法的符号,以在焦点位置获得最大值而不是最小值。算法需要24位灰度图像,其中R = G = B.如果在彩色图像上使用,则只计算蓝色通道(当然可以轻松校正)。

通过运行阈值为0,8,16,24等最多255的算法并选择最佳值来找到最佳阈值:

  • 稳定的焦点位置
  • 大的负x²系数导致焦点位置的峰值
  • 来自多项式拟合的低残差平方和

值得注意的是,ThresholdedSquaredGradient和ThresholdedBrennerGradient算法具有几乎平坦的焦点位置,x²系数和残差平方和。他们对阈值的变化非常不敏感。

算法的实现:

public unsafe List<Result> CalculateFocusValues(string filename)
{
  using(Bitmap bmp = new Bitmap(filename))
  {
    int width  = bmp.Width;
    int height = bmp.Height;
    int bpp = Bitmap.GetPixelFormatSize(bmp.PixelFormat) / 8;
    BitmapData data = bmp.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, bmp.PixelFormat);

    long sum = 0, squaredSum = 0;
    int[] histogram = new int[256];

    const int absoluteGradientThreshold = 148;
    long absoluteGradientSum = 0;
    long thresholdedAbsoluteGradientSum = 0;

    const int squaredGradientThreshold = 64;
    long squaredGradientSum = 0;
    long thresholdedSquaredGradientSum = 0;

    const int brennerGradientThreshold = 184;
    long brennerGradientSum = 0;
    long thresholdedBrennerGradientSum = 0;

    long autocorrelationSum1 = 0;
    long autocorrelationSum2 = 0;

    const int contentThreshold = 35;
    long thresholdedContentSum = 0;

    const int pixelCountThreshold = 76;
    long thresholdedPixelCountSum = 0;

    const int imagePowerThreshold = 40;
    long imagePowerSum = 0;
    long thresholdedImagePowerSum = 0;

    for(int row = 0; row < height - 1; row++)
    {
      for(int col = 0; col < width - 1; col++)
      {
        int current = *((byte *) (data.Scan0 + (row + 0) * data.Stride + (col + 0) * bpp));
        int col1    = *((byte *) (data.Scan0 + (row + 0) * data.Stride + (col + 1) * bpp));
        int row1    = *((byte *) (data.Scan0 + (row + 1) * data.Stride + (col + 0) * bpp));

        int squared = current * current;
        sum += current;
        squaredSum += squared;
        histogram[current]++;

        int colDiff1 = col1 - current;
        int rowDiff1 = row1 - current;

        int absoluteGradient = Math.Abs(colDiff1) + Math.Abs(rowDiff1);
        absoluteGradientSum += absoluteGradient;
        if(absoluteGradient >= absoluteGradientThreshold)
          thresholdedAbsoluteGradientSum += absoluteGradient;

        int squaredGradient = colDiff1 * colDiff1 + rowDiff1 * rowDiff1;
        squaredGradientSum += squaredGradient;
        if(squaredGradient >= squaredGradientThreshold)
          thresholdedSquaredGradientSum += squaredGradient;

        if(row < bmp.Height - 2 && col < bmp.Width - 2)
        {
          int col2    = *((byte *) (data.Scan0 + (row + 0) * data.Stride + (col + 2) * bpp));
          int row2    = *((byte *) (data.Scan0 + (row + 2) * data.Stride + (col + 0) * bpp));

          int colDiff2 = col2 - current;
          int rowDiff2 = row2 - current;
          int brennerGradient = colDiff2 * colDiff2 + rowDiff2 * rowDiff2;
          brennerGradientSum += brennerGradient;
          if(brennerGradient >= brennerGradientThreshold)
            thresholdedBrennerGradientSum += brennerGradient;

          autocorrelationSum1 += current * col1 + current * row1;
          autocorrelationSum2 += current * col2 + current * row2;
        }

        if(current >= contentThreshold)
          thresholdedContentSum += current;
        if(current <= pixelCountThreshold)
          thresholdedPixelCountSum++;

        imagePowerSum += squared;
        if(current >= imagePowerThreshold)
          thresholdedImagePowerSum += current * current;
      }
    }
    bmp.UnlockBits(data);

    int pixels = width * height;
    double mean = (double) sum / pixels;
    double meanDeviationSquared = (double) squaredSum / pixels;

    int rangeMin = 0;
    while(histogram[rangeMin] == 0)
      rangeMin++;
    int rangeMax = histogram.Length - 1;
    while(histogram[rangeMax] == 0)
      rangeMax--;

    double entropy = 0.0;
    double log2 = Math.Log(2);
    for(int i = rangeMin; i <= rangeMax; i++)
    {
      if(histogram[i] > 0)
      {
        double p = (double) histogram[i] / pixels;
        entropy -= p * Math.Log(p) / log2;
      }
    }

    return new List<Result>()
    {
      new Result("AbsoluteGradient",            absoluteGradientSum),
      new Result("ThresholdedAbsoluteGradient", thresholdedAbsoluteGradientSum),
      new Result("SquaredGradient",             squaredGradientSum),
      new Result("ThresholdedSquaredGradient",  thresholdedSquaredGradientSum),
      new Result("BrennerGradient",             brennerGradientSum),
      new Result("ThresholdedBrennerGradient",  thresholdedBrennerGradientSum),
      new Result("Variance",                    meanDeviationSquared - mean * mean),
      new Result("Autocorrelation",             autocorrelationSum1 - autocorrelationSum2),
      new Result("StdevBasedCorrelation",       -(autocorrelationSum1 - pixels * mean * mean)),
      new Result("Range",                       rangeMax - rangeMin),
      new Result("Entropy",                     -entropy),
      new Result("ThresholdedContent",          -thresholdedContentSum),
      new Result("ThresholdedPixelCount",       thresholdedPixelCountSum),
      new Result("ImagePower",                  -imagePowerSum),
      new Result("ThresholdedImagePower",       -thresholdedImagePowerSum),
      new Result("JpegSize",                    new FileInfo(filename).Length),
    };
  }
}

public class Result
{
  public string Algorithm { get; private set; }
  public double Value     { get; private set; }

  public Result(string algorithm, double value)
  {
    Algorithm = algorithm;
    Value     = value;
  }
}

为了能够绘制和比较不同算法的焦点值,将它们缩放到0到1之间的值(scaled = (value - min)/(max - min))。

所有算法的绘图范围为±20μm:

±20 µm

0 µm 20 µm

              0 µm             |             20 µm

对于±50μm的范围,情况看起来非常相似:

±50 µm

0 µm 50 µm

              0 µm             |             50 µm

当使用±500μm的范围时,事情变得更有趣。四种算法表现出更多的四次多项式形状,而其他算法开始看起来更像高斯函数。此外,直方图范围算法的开始性能优于较小范围。

±500 µm

0 µm 500 µm

              0 µm             |             500 µm

总的来说,我对这些简单算法的性能和一致性印象深刻。用肉眼很难说,即使是50微米的图像也没有聚焦,但算法在比较相距几微米的图像时没有问题。

答案 1 :(得分:3)

NindzAl对原始答案的评论的额外答案:

我使用Extreme Optimization库将锐度值拟合为二次多项式。然后使用多项式的一阶导数提取最大锐度的距离。

对于单个开发许可证,Extreme Optimization库的成本为999美元,但我确信有开源数学库也可以执行拟合。

// Distances (in µm) where the images were saved
double[] distance = new double[]
{
  -50,
  -40,
  -30,
  -20,
  -10,
    0,
  +10,
  +20,
  +30,
  +40,
  +50,
};

// Sharpness value of each image, as returned by CalculateFocusValues()
double[] sharpness = new double[]
{
  3960.9,
  4065.5,
  4173.0,
  4256.1,
  4317.6,
  4345.4,
  4339.9,
  4301.4,
  4230.0,
  4131.1,
  4035.0,
};

// Fit data to y = ax² + bx + c (second degree polynomial) using the Extreme Optimization library
const int SecondDegreePolynomial = 2;
Extreme.Mathematics.Curves.LinearCurveFitter fitter = new Extreme.Mathematics.Curves.LinearCurveFitter();
fitter.Curve = new Extreme.Mathematics.Curves.Polynomial(SecondDegreePolynomial);
fitter.XValues = new Extreme.Mathematics.LinearAlgebra.GeneralVector(distance,  true);
fitter.YValues = new Extreme.Mathematics.LinearAlgebra.GeneralVector(sharpness, true);
fitter.Fit();

// Find distance of maximum sharpness using the first derivative of the polynomial
// Using the sample data above, the focus point is located at distance +2.979 µm
double focusPoint = fitter.Curve.GetDerivative().FindRoots().First();

答案 2 :(得分:0)

对于免费图书馆,Math.Net将为此目的而工作