如何'规范化'灰度图像

本文关键字:灰度图像 规范化 如何 | 更新日期: 2023-09-27 18:14:58

我的数学有点生疏。我试图平衡一个2D数组的直方图,它代表0-255范围内的灰度值(值可能不是整数,因为它们是如何计算的)。

我在维基百科上找到了这篇文章,但是我不太明白他们给出的公式。

ni, nL我可以计算,但我不太确定如何实现这个cdf函数。这个函数可能有用吗?

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

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;
}

如何'规范化'灰度图像

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

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

希望有帮助

计算累积分布函数包括两个步骤。

首先得到灰度值的频率分布。

比如:

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;
}

我可以帮助你理解你的链接,

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

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值,

在本例中,if h(v) = 61 so cdf(v) = 14

cdfmin表示初始CDF值,在本例中,从值52取1 CDF

编码快乐. .^ ^

这是我的实现:

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)。

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

  • 调整John的第二次迭代以获得第一个具有a的ifreqDist > 0并将其称为imin
  • 逐像素i,j分别在0和宽度之间,0和高度之间和计算round((cdf[pixel[i,j]]-cdf[imin])/(width*height-cdf[imin]))*255);即该位置的归一化像素值。

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

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%像素将变成白色,两者之间的所有像素将被拉伸以填充颜色空间。这与ImageMagick的默认值相同。

这个算法有一个有趣的副作用,如果你使用超过50%的值,那么它将反转图像!设置为0.5,0.5得到黑色&白色图像(2个阴影)或1,1得到完美的反转。

假设你的图像已经是灰度的