1、前言
直方图统计是图像处理中一种非常重要的操作。VTK中的vtkmageAccumulate 类用于实现直方图统计功能。它将每个组分的数值范围划分为离散的间隔,然后统计每个灰度间隔上的像素数目。vtkImageAccumulate 输入和输出都是 vtkImageData 类型,因此直方图也可以看作一幅图像;输入图像的像素数据类型可以是任意的,但是最大支持3个组分像素类型,而输出图像的象素数据类型为int 型。
2、灰度图像直方图
对于灰度图像使用该 Filter 会产生一个一维图像。
private void TestAccumulate(){vtkJPEGReader reader = vtkJPEGReader.New();reader.SetFileName("F:\\code\\VTK\\TestActiViz\\bin\\Debug\\data\\lena-gray.jpg");reader.Update(); ;int bins = 16; //表示图像灰度直方图的间隔数目,直方图一维数组的维数int comps = 1;vtkImageAccumulate histogram = vtkImageAccumulate.New();histogram.SetInputData(reader.GetOutput());//设置要计算每个组分的直方图的最小值 和最大值。histogram.SetComponentExtent(0, bins - 1, 0, 0, 0, 0);//设置 统计每个组分直方图时的起始灰度值histogram.SetComponentOrigin(0, 0, 0);//设置直方图每个间隔代表的灰度范围histogram.SetComponentSpacing(256.0 / bins, 0, 0);histogram.Update(); //计算直方图//对直方图数据进行类型转换IntPtr outPtr = histogram.GetOutput().GetScalarPointer();vtkIntArray frequencies = vtkIntArray.New();frequencies.SetNumberOfComponents(1);int[] outputs = new int[bins * comps];Marshal.Copy(outPtr, outputs, 0, outputs.Length);int k = 0;for (int j = 0; j < bins; j++){for (int i = 0; i < comps; i++){frequencies.InsertNextTuple1(outputs[k++]);}}vtkDataObject dataObject = vtkDataObject.New();dataObject.GetFieldData().AddArray(frequencies);//用来显示条形图 输入为vtkDataObject类型vtkBarChartActor barChart = vtkBarChartActor.New();barChart.SetInput(dataObject);barChart.SetTitle("Histogram"); //标题//设置窗口中显示图像的所在矩形坐标 左下角、右上角barChart.GetPositionCoordinate().SetValue(0.10, 0.05, 0.0);barChart.GetPosition2Coordinate().SetValue(0.95, 0.9, 0.0);barChart.GetProperty().SetColor(0, 0, 0);barChart.GetTitleTextProperty().SetColor(0, 0, 0);barChart.GetLabelTextProperty().SetColor(0, 0, 0);barChart.GetLegendActor().SetNumberOfEntries((int)dataObject.GetFieldData().GetArray(0).GetNumberOfTuples());barChart.LegendVisibilityOff();barChart.LabelVisibilityOff();double[,] colors ={{1,0,0 },{0,1,0 },{0,0,1 },};int count = 0;for (int i = 0; i < bins; i++){for (int j = 0; j < comps; j++){barChart.SetBarColor(count++, colors[j, 0], colors[j, 1], colors[j, 2]);}}vtkRenderer renderer = vtkRenderer.New();renderer.AddActor(barChart);renderer.SetBackground(1, 1, 1);vtkRenderWindow renderWindow = renderWindowControl.RenderWindow;renderWindow.AddRenderer(renderer);renderWindow.Render();}
示例先读入一幅灰度图像。一般灰度图像的灰度范围为0~255。定义了一个变量 bins=16,表示图像灰度直方图的间隔数目,也可以理解为直方图一维数组的维数。
然后定义vtkImageAccumulate 对象,并设置输入数据为读入的图像数据,接着调用了如下三个函数。
① SetComponentExtent(0,bins-1,0,0,0,0)。
该函数设置要计算每个组分的直方图的最小值和最大值。vtkImageAccumulate最大支持像素值为三个组分(如RGB图像)的直方图,该方法共有六个参数,分别表示每个组分的直方图最小值和最大值。该例中由于计算的是灰度图像直方图,只有一个组分,因此第二个组分和第三个组分都设置为0:而第一组分直方图维数为 bins=16,那么其灰度范围是[0,bins-1]。
②SetComponentOrigin(0,0,0)。
该函数设置的是统计每个组分直方图时的起始灰度值。这里设置为0,表示灰度从0开始统计直方图。由于vkImageAccumulate最大支持像素值为三个组分,因此这里也要设置三个参数。如果图像的灰度范围为[1000,2000],那么计算直方图时,其起始灰度应该设置为1000。 ③SetComponentSpacing(16,0,0)。该函数设置直方图每个间隔代表的灰度范围,例如当一个图像灰度范围为[1000,2000],统计直方图的间隔数bins为100时,对应的space 应该设置为 SetComponentSpacing(100,0,0)。参数设置完毕,执行 Update()即可计算直方图。
vtkImageAccumulate的输出结果也是一个 vtkImageData 类型数据,这样就可以方便地访问图像的每个数据。需要注意的是,输出直方图图像的数据类型为int。
虽然 vkImageAccumulate 的输出类型为 vtkImageData,但是并不能直接以图像的方式进行显示。VTK中的 vtkBarChartActor 类用来显示条形图,可以利用它来显示直方图。但是该类接收的数据类型为vtkDataObiect类型,因此需要先对直方图数据进行类型转换:先将直方图数组存储到vtkIntAray数组fequencies中,然后通过vtkDataObject 函数GetFieldData().AddArray(frequencies);将其添加到 vtkDataObject 对象中。
vtkBarChartActor 对象接收 vtkDataObject 对象作为输入,另外还需要设置图表的名字、颜色等。这里需要注意两个函数:
//设置窗口中显示图像的所在矩形坐标 左下角、右上角barChart.GetPositionCoordinate().SetValue(0.10, 0.05, 0.0);
barChart.GetPosition2Coordinate().SetValue(0.95, 0.9, 0.0);
这里设置的是窗口中显示图表的所在矩形的左下角点和右上角点坐标。VTK的坐标系原点位于左下角点,设置时需要格外注意。
3、彩色图像直方图
彩色图像有三个颜色通道,因此需要提取 RGB 三个通道数据分别计算直方图。每个通道计算直方图的方法与灰度图像直方图的计算方法一致。
红色曲线代码红色分量的直方图,绿色代表绿色分量的直方图曲线,蓝色代表蓝色分量的直方图曲线。
private void TestColorAccumulate(){vtkBMPReader reader = vtkBMPReader.New();reader.SetFileName("F:\\code\\VTK\\TestActiViz\\data\\lena.bmp");reader.Update();int numComponents = reader.GetOutput().GetNumberOfScalarComponents();//从输入数据集或字段数据生成 X-Y 图 可选功能包括指定轴标签、标签格式和打印标题vtkXYPlotActor plot = vtkXYPlotActor.New();plot.ExchangeAxesOff();plot.SetLabelFormat("%g");plot.SetXTitle("Intensity");plot.SetYTitle("Frequency");plot.SetXValuesToValue();plot.GetProperty().SetColor(0, 0, 0);plot.GetAxisLabelTextProperty().SetColor(0, 0, 0);plot.GetAxisTitleTextProperty().SetColor(0, 0, 0);double[,] colors ={{1,0,0 },{0,1,0 },{0,0,1 },};string[] labels = new string[] { "Red", "Green", "Blue" };int xmax = 0;int ymax = 0;for (int i = 0; i < numComponents; i++){//提取每个组分图像vtkImageExtractComponents extract = vtkImageExtractComponents.New();extract.SetInputConnection(reader.GetOutputPort());extract.SetComponents(i);extract.Update();double[] range = extract.GetOutput().GetScalarRange();int extent = (int)(range[1] - range[0] - 1);//统计每个组分的直方图vtkImageAccumulate histogram = vtkImageAccumulate.New();histogram.SetInputData(extract.GetOutput());histogram.SetComponentExtent(0, extent, 0, 0, 0, 0);histogram.SetComponentOrigin(range[0], 0, 0);histogram.SetComponentSpacing(1, 0, 0);//直方图的间隔 每个灰度计算统计一个频率histogram.SetIgnoreZero(1); //像素值为0的不统计histogram.Update();if (range[1] > xmax){xmax = (int)range[1];}if (histogram.GetOutput().GetScalarRange()[1] > ymax){ymax = (int)histogram.GetOutput().GetScalarRange()[1];}plot.AddDataSetInput(histogram.GetOutput());plot.SetPlotColor(i, colors[i, 0], colors[i, 1], colors[i, 2]);plot.SetPlotLabel(i, labels[i]);plot.LegendOn();}plot.SetXRange(0, xmax); //设置X轴的数据范围plot.SetYRange(0, ymax); //设置Y轴的数据范围vtkRenderer renderer = vtkRenderer.New();renderer.AddActor(plot);renderer.SetBackground(1, 1, 1);vtkRenderWindow renderWindow = renderWindowControl.RenderWindow;renderWindow.AddRenderer(renderer);renderWindow.Render();}
先通过 vtklmageExtractComponents 来提取每个组分图像,再利用 vtkImageAccumulate统计每个组分的直方图。计算直方图的间隔取(1,0,0),即每个灰度计算统计一个频率,而且灰度起点为图像的最小灰度值。同时,SetlgnoreZero(1)设置在统计直方图时,像素值为0的像素不进行统计。
使用vtkXYPlotActor 曲线来表示直方图。
vtkXYPlotActor 类可以用来显示二维曲线,它可以接收多个输入数据,示例输入了三条曲线,分别是图像红色分量的直方图曲线、绿色分量的直方图曲线和蓝色分量的直方图曲线。SetXRangeO和SetYRange0用来设置X轴和Y轴的数据范围,另外还可以设置X轴和Y轴的名字、曲线的标题等属性。vtkXYPlotActor 类是一个 vtkActor2D 的子类,因此定义相应的 vkRenderer、vtkRenderWindow 和 vtkRenderWindowInteractor 对象建立可视化管道即可显示图像直方图曲线。