如何“标准化”灰度图像?

时间:2013-05-22 04:07:10

标签: c# image algorithm normalization

我的数学有点生疏。我正在尝试均衡2D数组的直方图,该直方图表示0-255范围内的灰度值(由于它们的计算方式,值可能不是整数)。

我在维基百科上找到this article,但我不太明白他们提出的公式。

ninL我可以计算,但我不太确定如何实现此cdf函数。可能this function有用吗?

这是我到目前为止所得到的:

static double[,] Normalize(double[,] mat)
{
    int width = mat.GetLength(0);
    int height = mat.GetLength(1);
    int nPixels = width*height;

    double sum = 0;
    double max = double.MinValue;
    double min = double.MaxValue;
    var grayLevels = new Dictionary<double, int>();

    foreach (var g in mat)
    {
        sum += g;
        if (g > max) max = g;
        if (g < min) min = g;
        if (!grayLevels.ContainsKey(g)) grayLevels[g] = 0;
        ++grayLevels[g];
    }
    double avg = sum/nPixels;
    double range = max - min;

    var I = new double[width,height];

    // how to normalize?

    return I;
}

6 个答案:

答案 0 :(得分:3)

找到一些你可能会觉得有用的东西

http://sonabstudios.blogspot.in/2011/01/histogram-equalization-algorithm.html

希望有所帮助

答案 1 :(得分:2)

计算累积分布函数需要几个步骤。

首先,您将获得灰度值的频率分布。

类似于:

freqDist = new int[256];

for each (var g in mat)
{
    int grayscaleInt = (int)g;
    freqDist[grayscaleInt]++;
}

然后获得你的CDF,例如:

cdf = new int[256];
int total = 0;

for (int i = 0; i < 256; i++)
{
    total += freqDist[i];
    cdf[i] = total;
}

答案 2 :(得分:2)

我可以帮助您理解your link

首先,计算代表图像的值,显示在该链接中,

Value   Count   Value   Count   Value   Count   Value   Count   Value   Count
   52       1      64       2      72       1      85       2     113       1
   55       3      65       3      73       2      87       1     122       1
   58       2      66       2      75       1      88       1     126       1
   59       3      67       1      76       1      90       1     144       1
   60       1      68       5      77       1      94       1     154       1
   61       4      69       3      78       1     104       2   
   62       1      70       4      79       2     106       1
   63       2      71       2      83       1     109       1

这意味着,图像是用上面的值创建的,没有别的。

秒,将该值累加从52到154

Value   cdf Value   cdf Value   cdf Value   cdf Value   cdf
   52     1    64    19    72    40    85    51   113    60
   55     4    65    22    73    42    87    52   122    61
   58     6    66    24    75    43    88    53   126    62
   59     9    67    25    76    44    90    54   144    63
   60    10    68    30    77    45    94    55   154    64
   61    14    69    33    78    46   104    57 
   62    15    70    37    79    48   106    58
   63    17    71    39    83    49   109    59

是手段,

value 52 have 1 cdf cause it is initial value, 
value 55 have 4 cdf cause it has 3 count in image plus 1 cdf from 52, 
value 58 have 6 cdf cause it has 2 count in image plus 4 cdf from 55,
and so on.. till..
value 154 have 64 cdf cause it has 1 count in image plus 63 cdf from 144.

然后,基于函数计算每个图像值的直方图均衡公式

cdf(v)表示当前图像值的当前cdf值,

在这种情况下,如果h(v) = 61 cdf(v) = 14

cdfmin表示初始cdf值,在这种情况下,是来自值52的1 cdf

快乐的编码.. ^^

答案 3 :(得分:2)

这是我的实施:

private static byte[,] Normalize(byte[,] mat)
{
    int width = mat.GetLength(0);
    int height = mat.GetLength(1);
    int nPixels = width*height;

    var freqDist = new int[256];

    foreach (var g in mat)
    {
        ++freqDist[g];
    }

    var cdf = new int[256];
    int total = 0;

    for (int i = 0; i < 256; ++i)
    {
        total += freqDist[i];
        cdf[i] = total;
    }

    int cdfmin = 0;

    for (int i = 0; i < 256; ++i)
    {
        if (cdf[i] > 0)
        {
            cdfmin = cdf[i];
            break;
        }
    }

    var I = new byte[width,height];
    double div = (nPixels - cdfmin) / 255d;

    for (int y = 0; y < height; ++y)
    {
        for (int x = 0; x < width; ++x)
        {
            I[x, y] = (byte)Math.Round((cdf[mat[x, y]] - cdfmin) / div);
        }
    }

    return I;
}

我将它从使用双精度更改为字节,以便更好地使用直方图(freqDist)。

答案 4 :(得分:1)

除了John所说的,你还需要使用cdf数组来计算每个像素的新值。你这样做:

  • 调整John的第二次迭代,以获得第一个拥有a的i freqDist > 0并称之为我imin
  • 逐个像素地进行i,j分别在0和宽度之间以及0和高度之间 评估round((cdf[pixel[i,j]]-cdf[imin])/(width*height-cdf[imin]))*255), 这是该位置的标准化像素值。

答案 5 :(得分:1)

你可以使用我刚刚写的这个函数:

public static Bitmap ContrastStretch(Bitmap srcImage, double blackPointPercent = 0.02, double whitePointPercent = 0.01)
{
    BitmapData srcData = srcImage.LockBits(new Rectangle(0, 0, srcImage.Width, srcImage.Height), ImageLockMode.ReadOnly,
        PixelFormat.Format32bppArgb);
    Bitmap destImage = new Bitmap(srcImage.Width, srcImage.Height);
    BitmapData destData = destImage.LockBits(new Rectangle(0, 0, destImage.Width, destImage.Height),
        ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
    int stride = srcData.Stride;
    IntPtr srcScan0 = srcData.Scan0;
    IntPtr destScan0 = destData.Scan0;
    var freq = new int[256];

    unsafe
    {
        byte* src = (byte*) srcScan0;
        for (int y = 0; y < srcImage.Height; ++y)
        {
            for (int x = 0; x < srcImage.Width; ++x)
            {
                ++freq[src[y*stride + x*4]];
            }
        }

        int numPixels = srcImage.Width*srcImage.Height;
        int minI = 0;
        var blackPixels = numPixels*blackPointPercent;
        int accum = 0;

        while (minI < 255)
        {
            accum += freq[minI];
            if (accum > blackPixels) break;
            ++minI;
        }

        int maxI = 255;
        var whitePixels = numPixels*whitePointPercent;
        accum = 0;

        while (maxI > 0)
        {
            accum += freq[maxI];
            if (accum > whitePixels) break;
            --maxI;
        }
        double spread = 255d/(maxI - minI);
        byte* dst = (byte*) destScan0;
        for (int y = 0; y < srcImage.Height; ++y)
        {
            for (int x = 0; x < srcImage.Width; ++x)
            {
                int i = y*stride + x*4;

                byte val = (byte) Clamp(Math.Round((src[i] - minI)*spread), 0, 255);
                dst[i] = val;
                dst[i + 1] = val;
                dst[i + 2] = val;
                dst[i + 3] = 255;
            }
        }
    }

    srcImage.UnlockBits(srcData);
    destImage.UnlockBits(destData);

    return destImage;
}

static double Clamp(double val, double min, double max)
{
    return Math.Min(Math.Max(val, min), max);
}

默认值意味着最暗的2%像素将变为黑色,最轻的1%将变为白色,并且其间的所有内容都将被拉伸以填充色彩空间。这与default for ImageMagick相同。

此算法具有有趣的副作用,如果您使用高于50%的值,那么它将反转图像!设置为.5,.5以获得黑色&amp;白色图像(2个阴影)或1,1以获得完美的反转。

假设您的图片为already grayscale