如何'规范化'灰度图像
本文关键字:灰度图像 规范化 如何 | 更新日期: 2023-09-27 18:14:58
我的数学有点生疏。我试图平衡一个2D数组的直方图,它代表0-255范围内的灰度值(值可能不是整数,因为它们是如何计算的)。
我在维基百科上找到了这篇文章,但是我不太明白他们给出的公式。
ni
, n
和L
我可以计算,但我不太确定如何实现这个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的i
freqDist > 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得到完美的反转。
假设你的图像已经是灰度的