在作业中,我们收到的第一个任务便是自己写获得Band的统计信息的函数,当然这部分函数不在此多做讲解,因为Gdal中自带有这样的“轮子”,我们不应该重复造“轮子”。然而在BMP图像中,就没有获得统计信息的轮子,所以我们得自己书写。这篇文章写的就是如何获得Band的统计信息,以及如何对BMP进行统计。
一、Gdal中获取Band的统计信息
首先,如何获得Band的最大值,最小值,均值,方差?我们使用这个函数:
Band.ComputeStatistics(bool approx_ok,
out double min, out double max, out double mean, out double stddev,
Gdal.GDALProgressFuncDelegate callback, string callback_data)
/*Paras:
approx_ok:在第一次统计时,会生成一个记录统计结果的文件。在之后的运行中,若这个值为true,则会直接从文件里提取统计信息,速度较快;或为false,则会重新进行统计,速度较慢
min,max,mean,stddev:将在函数前定义的用于接收最小值、最大值、均值、标准差的变量传入,在运行函数后这四个值会输出到这几个变量中
callback,callback_data:获得统计进度的参数,可以全为null
*/
//使用示例
double mymin,mymax,mymean,mystddev;
//My_Band是自己从dataset中取得的Band
My_Band.ComputeStatistics(true, out mymin, out mymax, out mymean, out mystddev, null, null)
//注意,这里获得的是标准差,并不是方差
如此便可以获得波段的最小值最大值均值方差。接下来介绍从Band里获得画直方图所需的灰度值统计信息的函数。
Band.GetHistogram(double min, double max,
int buckets, int[] panHistogram,
int include_out_of_range, int approx_ok,
Gdal.GDALProgressFuncDelegate callback, string callback_data);
/*Paras:
min,max: 函数对波段中值在 (min,max) 范围内的数据进行统计,表示统计范围
buckets: 统计的份数。比如统计8bit的图像分为256份,这个值便是256
panHistogram: 用于接收统计结果的数组,需要在函数前定义,数组的长度应该等于buckets
include_out_of_range: 是否统计 (min,max) 范围外的值
approx_ok: 是否从文件中统计(上面解释过)
callback,callback_data: 返回统计的进度,可以为null
*/
//使用实例如下,假设要统计一个8bit的波段获得统计直方图信息
//My_Band是自己从dataset中取得的Band
double[] Histogram = new double[256];
My_Band.GetHistogram(min - 0.5, max + 0.5, 256, Histogram, 0, 1, null, null);
//min与max是从Band中获得的最小最大值,这里也可以 -/+ 0.1
这样获得Histogram数组,Histogram[0]便是Band里值为0的个数,Histogram[128]便是Band里值为128的个数
二、获取BMP的统计信息
对于BMP,我们需要自己写统计的函数,其实没什么难度,统计最小值、最大值、均值、方差的函数很好写。但在这之前,我们思考一个问题:
当我们通过Bitmap.GetPixel(x,y)获取一个Color时,我们需要通过Color.R/Color.G/Color.B获得R/G/B通道的值。当统计三个通道时,我们如果去写三遍代码,如:
统计R波段
统计G波段
统计B波段
的话,未免也太傻了,不符合程序员“重复的事绝对不做”的特点。所以我们希望构造一个函数,这个函数的功能是传入一个Color和一个为 1/2/3 的整数,就可以获取 R/G/B 的值。大概意思是:
for(int i = 1; i <= 3; i++)
统计i波段
这样来看,是不是好很多呢?这个函数其实很简单,意在让同学们体会一下编程思想,在写代码中构造重用性鲁棒性灵活性更高的函数,这能很好地减轻我们的工作量,并增强我们的抽象思维。
如下:
private int Get_rgb(System.Drawing.Color color,int channel)//获得RGB
{
switch(channel)
{
case 1:
return color.R;
case 2:
return color.G;
case 3:
return color.B;
}
return 0;
}
这样,我们就可以写出统计BMP某个通道统计信息的函数:
private double[] BMP_Statistics(System.Drawing.Bitmap bitmap,int BandSelect)
{
if(BandSelect<1||BandSelect>3)
{
MessageBox.Show("这是一个编程错误,在BMP统计函数中要统计的波段索引超出了范围", "错误!");
return null;
}
double[] data = new double[4] { 255,0,0,0 };
//最小值、最大值、均值、方差
for(int y = 0;y<bitmap.Height;y++)
for (int x = 0; x < bitmap.Width; x++)
{
if (this.Get_rgb(bitmap.GetPixel(x, y), BandSelect) < data[0])//小于最小值则替换最小值
data[0] = this.Get_rgb(bitmap.GetPixel(x, y), BandSelect);
if (this.Get_rgb(bitmap.GetPixel(x, y), BandSelect) > data[1])//大于最大值则替换最大值
data[1] = this.Get_rgb(bitmap.GetPixel(x, y), BandSelect);
data[2] += this.Get_rgb(bitmap.GetPixel(x, y), BandSelect);//值的总和
}
data[2] = data[2] / (bitmap.Height * bitmap.Width); //获得平均值
for (int y = 0; y < bitmap.Height; y++)
for (int x = 0; x < bitmap.Width; x++)
{
data[3] += System.Math.Pow(this.Get_rgb(bitmap.GetPixel(x, y), BandSelect) - data[2], 2.0); //计算方差
}
data[3] = data[3] / (bitmap.Height * bitmap.Width); //获得方差
return data; //返回数组
}
如果不用Get_rgb这一函数,是否又有什么简洁的统计三个波段的方法呢?
接下来我们想要获得Bitmap的灰度统计信息,其实也很简单,就是在遍历的同时在数组的相应索引上+1。函数如下:
private int[] BMP_Histogram(System.Drawing.Bitmap bitmap, int BandSelect)//BMP直方图统计信息
{
if (BandSelect < 1 || BandSelect > 3)
{
MessageBox.Show("这是一个编程错误,在BMP统计函数中要统计的波段索引超出了范围", "错误!");
return null;
}
int[] his = new int[256]; //定义接收数据的数组
for (int y = 0; y < bitmap.Height; y++)
for (int x = 0; x < bitmap.Width; x++)
{
his[this.Get_rgb(bitmap.GetPixel(x, y), BandSelect)]++;
}
//由于Histogram[n]表示像素值为n的像素个数,所以在这里反过来用像素值作为数组的索引。
return his;
}
三、灰度统计直方图变为累计直方图
在直方图均衡化中,我们需要获得相对累计直方图,既然在上面我们已经获得了灰度直方图,那我们就可以制作一个函数,输入灰度直方图的数组,输出累计直方图/相对累计直方图的数组。先来说思想:
假设直方图数组: int[] His = new int[256]
累计直方图:
for(int i = 1; i < His.Length; i++) //注意是从1开始到最后一个结束
His[i] += His[i-1] ;
若需要相对累计直方图,则再遍历一次His数组,每个数都除以全部像素值的总和即可。函数如下:
private double[] Cumulate_Histogram(int[] Histogram, bool ToPercent = false, bool include_zero = true)//将直方图变换成累计直方图
{
double[] CuHis = new double[Histogram.Length]; //定义相同长度的double类型数组
for (int i = 0; i < Histogram.Length; i++)
{
CuHis[i] = (double)Histogram[i]; //数据转为double类型
}
if (include_zero == false) //是否统计0
CuHis[0] = 0.0;
for (int i = 1; i < CuHis.Length; i++) //获得累计的直方图
{
CuHis[i] += CuHis[i - 1];
}
if (ToPercent) //若要获得相对累计直方图
{
for (int i = 0; i < CuHis.Length; i++)
{
CuHis[i] = CuHis[i] / CuHis[CuHis.Length - 1];
}
}
return CuHis;
}
至此,便是统计信息与统计直方图的内容