直方图的计算

来源:互联网 发布:模拟调音台软件 编辑:程序博客网 时间:2024/05/22 05:23
直方图的计算

    EmguCv将OpenCv的一系列直方图函数封装到了类DenseHistogram里面,可以用方法Calculate方便的计算图像的直方图。不过值得注意的是,该方法接受的第一个参数是“单通道”图像数组;而一般情况下的图像都是3通道的,在计算之前我们需要用Image<TColor,TDepth>.Split方法将其分解成单通道图像,然后选择需要参与直方图计算的通道。下面有几段计算直方图的代码,分别计算单通道(红色)直方图、色调和饱和度直方图。

///<summary>
/// 计算直方图(红色)
///</summary>
privatevoid CalcHistRed()
{
//计算
int rBins =256;
RangeF rRange
=new RangeF(0f, 255f);
Image
<Bgr, Byte> imageSource =new Image<Bgr, Byte>((Bitmap)pbSource.Image);
Image
<Gray, Byte> imageRed = imageSource.Split()[2];
DenseHistogram hist
=new DenseHistogram(rBins, rRange);
hist.Calculate(
new IImage[] { imageRed }, false,null);
//显示
pbHistogram.Image= GenerateHistImage(hist).Bitmap;
//释放资源
imageSource.Dispose();
imageRed.Dispose();
hist.Dispose();
}

///<summary>
/// 计算直方图(色调和饱和度)
///</summary>
privatevoid CalcHistHs()
{
//计算
int hBins =180;
RangeF hRange
=new RangeF(0f, 179f); //色调的范围在0~180之间
int sBins =256;
RangeF sRange
=new RangeF(0f, 255f);
Image
<Bgr, Byte> imageSource =new Image<Bgr, Byte>((Bitmap)pbSource.Image);
Image
<Hsv, Byte> imageHsv = imageSource.Convert<Hsv, Byte>();//将色彩空间从BGR转换到HSV
Image<Gray, Byte>[] imagesHsv= imageSource.Split();//分解成H、S、V三部分
DenseHistogram hist=new DenseHistogram(newint[] { hBins, sBins }, new RangeF[] { hRange, sRange });
hist.Calculate(
new IImage[] { imagesHsv[0], imagesHsv[1] }, false,null);
//显示
pbHistogram.Image= GenerateHistImage(hist).Bitmap;
//释放资源
imageSource.Dispose();
imageHsv.Dispose();
foreach (Image<Gray, Byte> image in imagesHsv)
image.Dispose();
hist.Dispose();
}

直方图的显示
    我们可以用以下方式来查看直方图:(1)使用HistogramViewer窗体显示直方图;(2)使用HistogramBox控件显示直方图;(3)用自己写的方法将直方图转换成图像,然后显示出来。这3个方式依次从易到难,不过对直方图的显示控制程度却依次提高。
1.HistogramViewer窗体
    HistogramViewer窗体的3个静态方法可以很方便的显示直方图,它们的定义如下:
    Show(IImage) 显示指定图像的直方图:它会自动为图像的每个通道生成直方图,然后显示一个或者多个一维直方图;
    Show(IImage, Int32) 显示指定图像的直方图:除了可以自己指定直方图的区间数目之外,跟前一个方法一样;
    Show(DenseHistogram, String) 显示指定的一维直方图:可以在图上显示一个标题,注意这个方法只能显示一维直方图。 
    HistogramViewer窗体还有一个名为HistogramCtrl的属性,它用来获取窗体内的HistogramBox控件。
    下面的代码演示了HistogramViewer的用法:

///<summary>
/// 计算直方图(RGB)
///</summary>
privatevoid CalcHistRgb()
{
//在HistogramViewer中显示直方图
Image<Bgr, Byte> imageSource=new Image<Bgr,byte>((Bitmap)pbSource.Image);
//直接用HistogranViewer的静态函数Show来查看直方图,不过不能控制行为
HistogramViewer.Show(imageSource,256);
//可以用HistogramViewer对象来显示直方图,有较多的控制
HistogramViewer hv=new HistogramViewer();
hv.Text
="RGB直方图";
hv.ShowInTaskbar
=false;
hv.HistogramCtrl.GenerateHistograms(imageSource,
256);
hv.WindowState
= FormWindowState.Maximized;
hv.Show(
this);
//释放资源
imageSource.Dispose();
}

2.HistogramBox控件
    HistogramBox控件有4个方法跟直方图有关:
    AddHistogram(String, Color, DenseHistogram) 向控件添加一块一维直方图:可以指定标题及绘制的颜色;
    ClearHistogram() 移除控件中的所有直方图;
    GenerateHistograms(IImage, Int32) 为指定图像的每个通道生成直方图:可以指定直方图的区间数目;
    Refresh() 绘制直方图:对控件的直方图进行任何改动之后,都要调用Refresh来重新绘制才行。

///<summary>
/// 计算直方图(色调)
///</summary>
privatevoid CalcHistHue()
{
//计算
int hBins =180;
RangeF hRange
=new RangeF(0f, 179f); //色调的范围在0~180之间
Image<Bgr, Byte> imageSource=new Image<Bgr, Byte>((Bitmap)pbSource.Image);
Image
<Hsv, Byte> imageHsv = imageSource.Convert<Hsv, Byte>();//将色彩空间从BGR转换到HSV
Image<Gray, Byte> imageHue= imageSource.Split()[0];//分解Hue部分
DenseHistogram hist=new DenseHistogram(hBins, hRange);
hist.Calculate(
new IImage[] { imageHue }, false,null);
//显示(注意:这里的变量histBox是一个HistogramBox控件的对象)
histBox.AddHistogram("色调直方图", Color.FromArgb(255,0,0), hist);
histBox.Refresh();
//释放资源
imageSource.Dispose();
imageHsv.Dispose();
imageHue.Dispose();
hist.Dispose();
}


自己写方法将直方图转换成图像
    HistogramViewer和HistogramBox很方便,但是它们只能显示一维的直方图,如果要显示二维甚至三维的直方图,那么只有自己写方法了。

//生成直方图图示
private Image<Bgr,Byte> GenerateHistImage(DenseHistogram hist)
{
Image
<Bgr, Byte> imageHist =null;
float minValue, maxValue;
int[] minLocations, maxLocations;
hist.MinMax(
out minValue, out maxValue, out minLocations, out maxLocations);
if (hist.Dimension ==1)
{
int bins = hist.BinDimension[0].Size;
int width = bins;
int height =300;
imageHist
=new Image<Bgr, Byte>(width, height, new Bgr(255d, 255d, 255d));
double heightPerTick = 1d * height/ maxValue;
Bgr color
=new Bgr(0d,0d,255d);
//遍历每个bin对应的值,并画一条线
for (int i =0; i < bins; i++)
{
LineSegment2D line
=new LineSegment2D(new Point(i, height), new Point(i, (int)(height- heightPerTick* hist[i])));
imageHist.Draw(line, color,
1);
}
}
elseif (hist.Dimension ==2)
{
int scale =2;
int width = hist.BinDimension[0].Size* scale;
int height = hist.BinDimension[1].Size* scale;
imageHist
=new Image<Bgr, Byte>(width, height, new Bgr(255d, 255d, 255d));
//遍历每个bin对应的值,并画一个矩形
for (int i =0; i < width/ scale; i++)
{
for (int j =0; j < height/ scale; j++)
{
double binValue = hist[i, j];
double intensity = 1d* binValue*255/ maxValue;
Rectangle rect
=new Rectangle(i * scale, j * scale,1,1);
Bgr color
=new Bgr(intensity, intensity, intensity);
imageHist.Draw(rect, color,
1);
}
}
}
return imageHist;
}

直方图的处理
    在计算出直方图之后,我们经常需要对其进行一些处理才能使用,下面介绍最常用的几个:
    MinMax(Single, Single, array<Int32>[], array<Int32>[]) 查找最小及最大的直方图区间及其位置;
    Normalize(Double) 归一化直方图;
    Threshold(Double) 为直方图设置一个阀值,小于阀值的区间将被置零。
    下面的代码演示了直方图的处理:

//计算直方图

DenseHistogram hist =new DenseHistogram(bins[i], ranges[i]);
hist.Calculate(
new IImage[] { images[i] }, false,null);
//阀值
if (cbThreshold.Checked && thresholdValue >0)
hist.Threshold(thresholdValue);
//归一化
if (cbNormalize.Checked)
hist.Normalize(normalizeFactor);
//显示
histBox1.AddHistogram(captions[i], colors[i], hist);
//释放
hist.Dispose();
images[i].Dispose();

直方图的对比
    OpenCv提供了5种对比直方图的方式:CORREL(相关)、CHISQR(卡方)、INTERSECT(相交)、BHATTACHARYYA、EMD(最小工作距离),其中CHISQR速度最快,EMD速度最慢且有诸多限制,但是EMD的效果最好。世界总是充满了矛盾,而我们的工作就是化解矛盾,把每种方式都测试一下,找到正好能解决问题并且速度最优的方法。
    下面是直方图对比的示例代码:

//对比直方图
privatevoid btnCompare_Click(object sender, EventArgs e)
{
Stopwatch sw
=new Stopwatch();
sw.Start();
//计算直方图
DenseHistogram hist1= CalcHist(pbSource1.Image);
DenseHistogram hist2
= CalcHist(pbSource2.Image);
//对比并显示对比结果
HISTOGRAM_COMP_METHOD compareMethod= HISTOGRAM_COMP_METHOD.CV_COMP_CORREL;
double compareResult = 0d;
if (rbCompareEmd.Checked)
{
//EMD(陆地移动距离)需要用特殊的方法计算
//将直方图转换成矩阵
Matrix<Single> matrix1 = ConvertDenseHistogramToMatrix(hist1);
Matrix
<Single> matrix2 = ConvertDenseHistogramToMatrix(hist2);
compareResult
= CvInvoke.cvCalcEMD2(matrix1.Ptr, matrix2.Ptr, DIST_TYPE.CV_DIST_L2, null, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
matrix1.Dispose();
matrix2.Dispose();
}
else
{
if (rbCompareCorrel.Checked)
compareMethod
= HISTOGRAM_COMP_METHOD.CV_COMP_CORREL;
elseif (rbCompareChisqr.Checked)
compareMethod
= HISTOGRAM_COMP_METHOD.CV_COMP_CHISQR;
elseif (rbCompareIntersect.Checked)
compareMethod
= HISTOGRAM_COMP_METHOD.CV_COMP_INTERSECT;
elseif (rbCompareBhattacharyya.Checked)
compareMethod
= HISTOGRAM_COMP_METHOD.CV_COMP_BHATTACHARYYA;
else
thrownew Exception("不存在的直方图对比方式。");
compareResult
= CvInvoke.cvCompareHist(hist1.Ptr, hist2.Ptr, compareMethod);
}
sw.Stop();
txtResult.Text
+=string.Format("对比直方图用时:{0:F05}毫秒,对比方式:{1},对比结果:{2:F05},条件({3})\r\n",
sw.Elapsed.TotalMilliseconds, rbCompareEmd.Checked
?"EMD" : compareMethod.ToString("G"), compareResult, GetOptions());
//释放资源
hist1.Dispose();
hist2.Dispose();
}

///<summary>
/// 计算指定图像的直方图,并进行相应的处理
///</summary>
///<param name="image">源图像</param>
///<returns>返回直方图</returns>
private DenseHistogram CalcHist(Image image)
{
//定义变量
Image<Bgr, Byte> imageSource=new Image<Bgr,byte>((Bitmap)image);
string colorSpace = (string)cmbColorSpace.SelectedItem;
double thresholdValue = 0d;
double.TryParse(txtThreshold.Text,out thresholdValue);
double normalizeFactor = 1d;
Image
<Gray, Byte>[] images;
int[] bins;
RangeF[] ranges;
string[] captions;
Color[] colors;
//根据色彩空间的选择,得到需要计算的色彩单通道图像、bins及范围
GetImagesBinsAndRanges(imageSource, colorSpace,out images, out bins, out ranges, out captions, out colors);
//计算各色彩通道的直方图,并进行需要的处理
DenseHistogram hist=new DenseHistogram(bins, ranges);
hist.Calculate
<Byte>(images,false,null);
//阀值
if (cbThreshold.Checked && thresholdValue >0)
hist.Threshold(thresholdValue);
//归一化
if (cbNormalize.Checked)
hist.Normalize(normalizeFactor);
return hist;
}


EMD方式要求先将直方图转换成矩阵:

///<summary>
/// 将直方图转换成矩阵;
/// 注意:只支持1、2、3维直方图
///</summary>
///<param name="hist">直方图</param>
///<returns>返回矩阵</returns>
private Matrix<Single> ConvertDenseHistogramToMatrix(DenseHistogram hist)
{
Matrix
<Single> matrix =null;
if (hist !=null)
{
int cols = hist.Dimension+1;//矩阵的列数为直方图维数加1
int rows =1;//矩阵的行数为直方图所有bin的乘积
foreach (MCvMatND.Dimension bin in hist.BinDimension)
rows
*= bin.Size;
//初始化矩阵
matrix=new Matrix<Single>(rows, cols);
//填充矩阵
if (hist.Dimension ==1)
{
// 1维直方图
for (int idx0 =0; idx0 < hist.BinDimension[0].Size; idx0++)
{
float binValue = (float)hist[idx0];
matrix[idx0,
0]= binValue;
matrix[idx0,
1]= idx0;
}
}
elseif (hist.Dimension ==2)
{
// 2维直方图
int bins0 = hist.BinDimension[0].Size;
int bins1 = hist.BinDimension[1].Size;
for (int idx0 =0; idx0 < bins0; idx0++)
{
for (int idx1 =0; idx1 < bins1; idx1++)
{
float binValue = (float)hist[idx0, idx1];
int row = idx0* bins1+ idx1;
matrix[row,
0]= binValue;
matrix[row,
1]= idx0;
matrix[row,
2]= idx1;
}
}
}
elseif (hist.Dimension ==3)
{
// 3维直方图
int bins0 = hist.BinDimension[0].Size;
int bins1 = hist.BinDimension[1].Size;
int bins2 = hist.BinDimension[2].Size;
for (int idx0 =0; idx0 < bins0; idx0++)
{
for (int idx1 =0; idx1 < bins1; idx1++)
{
for (int idx2 =0; idx2 < bins2; idx2++)
{
float binValue = (float)hist[idx0, idx1, idx2];
int row = idx0* bins1* bins2+ idx1* bins2+ idx2;
matrix[row,
0]= binValue;
matrix[row,
1]= idx0;
matrix[row,
2]= idx1;
matrix[row,
3]= idx2;
}
}
}
}
else
{
thrownew ArgumentException("直方图维数超出范围,只支持1、2、3维直方图。","DenseHistogram hist");
}
}
return matrix;
}

 EMD方法会占用很很很大量的内存,在使用前请注意直方图的维数及区间数目,不然会出现内存不足的异常。关于这点,请参看我的另一篇文章《关于使用cvCalcEMD2计算两个直方图间最小工作距离的限制(Why cvCalcEMD2 Throw Insufficient Memory Exception)》。还有一点值得注意的是,不同的对比方式对待结果的方式很不一样,结果越大不一定说明匹配度更高,具体请参看《学习OpenCv》这本书的相关章节。


0 0
原创粉丝点击