我的数学有点生疏。我正在尝试均衡2D数组的直方图,该直方图表示0-255范围内的灰度值(由于它们的计算方式,值可能不是整数)。
我在维基百科上找到this article,但我不太明白他们提出的公式。
ni
,n
和L
我可以计算,但我不太确定如何实现此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;
}
答案 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数组来计算每个像素的新值。你这样做:
freqDist > 0
并称之为我imin 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。