绘制具有颜色渐变的矩阵 "Spectrogram"
Drawing a matrix with a gradient of colors "Spectrogram"
使用 STFT(Short-time 傅里叶变换)后,输出是一个表示 3d 图的矩阵,就好像 (A[X, Y] = M)
A 是输出矩阵,X 是时间,Y 是频率,第三维M是像素颜色强度所表示的幅度,如下图:
Spectrogram 2
如何在 C# 中绘制具有颜色渐变的输出矩阵 A,如图片所示?
是否有包含 C# 频谱图控件的库?
更新:
对给定的算法进行一些修改后,我可以绘制频谱图,我没有更改调色板,只是第一种颜色更改为黑色,但我不知道为什么它很褪色!
这个代表一个声音
Bye Bye
Bye Bye Spectrogram
这是 pure sine wave
中的一个,所以几乎一直都是相同的频率
Pure sine wave Spectrogram
输出被接受,它代表了预期的输入信号的频率,但我认为有一种方法可以使频谱图像示例中的那样得到很好的说明,你能看看我的代码并提出修改建议吗?
这是事件处理程序:
private void SpectrogramButton_Click(object sender, EventArgs e)
{
Complex[][] SpectrogramData = Fourier_Transform.STFT(/*signal:*/ samples, /*windowSize:*/ 512, /*hopSize:*/ 512);
SpectrogramBox.Image = Spectrogram.DrawSpectrogram(SpectrogramData, /*Interpolation Factor:*/ 1000, /*Height:*/ 256);
}
这是我修改后的绘图功能:
public static Bitmap DrawSpectrogram(Complex[][] Data, int InterpolationFactor, int Height)
{
// target size:
Size sz = new Size(Data.GetLength(0), Height);
Bitmap bmp = new Bitmap(sz.Width, sz.Height);
// the data array:
//double[,] data = new double[222, 222];
// step sizes:
float stepX = 1f * sz.Width / Data.GetLength(0);
float stepY = 1f * sz.Height / Data[0].GetLength(0);
// create a few stop colors:
List<Color> baseColors = new List<Color>(); // create a color list
baseColors.Add(Color.Black);
baseColors.Add(Color.LightSkyBlue);
baseColors.Add(Color.LightGreen);
baseColors.Add(Color.Yellow);
baseColors.Add(Color.Orange);
baseColors.Add(Color.Red);
// and the interpolate a larger number of grdient colors:
List<Color> colors = interpolateColors(baseColors, InterpolationFactor);
// a few boring test data
//Random rnd = new Random(1);
//for (int x = 0; x < data.GetLength(0); x++)
// for (int y = 0; y < data.GetLength(1); y++)
// {
// //data[x, y] = rnd.Next((int)(300 + Math.Sin(x * y / 999) * 200)) +
// // rnd.Next(x + y + 111);
// data[x, y] = 0;
// }
// now draw the data:
float Max = Complex.Max(Data);
using (Graphics G = Graphics.FromImage(bmp))
for (int x = 0; x < Data.GetLength(0); x++)
for (int y = 0; y < Data[0].GetLength(0); y++)
{
int Val = (int)Math.Ceiling((Data[x][y].Magnitude / Max) * (InterpolationFactor - 1));
using (SolidBrush brush = new SolidBrush(colors[(int)Val]))
G.FillRectangle(brush, x * stepX, (Data[0].GetLength(0) - y) * stepY, stepX, stepY);
}
// and display the result
return bmp;
}
我不太明白你在回答中说的 log
事情,很抱歉我的知识很少。
更新:
这是将 log10
添加到幅度后的输出(忽略负值):
我认为这个输出是可以接受的,它与我一开始带的例子不同,但我认为它更好。
不,据我所知没有开箱即用的控件。当然,您可能还可以购买外部图书馆,但是嘘,您不能在 SO 上询问它们..
理论上你可以使用,或者我想我应该说滥用一个Chart
控制。但由于 DataPoints
是相当昂贵的物品,或者至少比它们看起来更昂贵,这似乎不可取。
相反,您可以自己简单地将图表绘制成 Bitmap
。
第一步是决定颜色的渐变。有关此示例,请参阅 interpolateColors function here!
然后您只需使用 floats
对步长和像素大小对数据进行双循环,然后在那里执行 Graphics.FillRectangle
。
这里是一个简单的例子,使用GDI+
创建一个Bitmap
和一个Winforms PictureBox
用于显示。它不会向图形添加任何轴并完全填充它。
它首先创建一些样本数据和一个具有 1000
颜色的渐变。然后它绘制成一个Bitmap
并显示结果:
private void button6_Click(object sender, EventArgs e)
{
// target size:
Size sz = pictureBox1.ClientSize;
Bitmap bmp = new Bitmap(sz.Width, sz.Height);
// the data array:
double[,] data = new double[222, 222];
// step sizes:
float stepX = 1f * sz.Width / data.GetLength(0);
float stepY = 1f * sz.Height / data.GetLength(1);
// create a few stop colors:
List<Color> baseColors = new List<Color>(); // create a color list
baseColors.Add(Color.RoyalBlue);
baseColors.Add(Color.LightSkyBlue);
baseColors.Add(Color.LightGreen);
baseColors.Add(Color.Yellow);
baseColors.Add(Color.Orange);
baseColors.Add(Color.Red);
// and the interpolate a larger number of grdient colors:
List<Color> colors = interpolateColors(baseColors, 1000);
// a few boring test data
Random rnd = new Random(1);
for (int x = 0; x < data.GetLength(0); x++)
for (int y = 0; y < data.GetLength(1); y++)
{
data[x, y] = rnd.Next( (int) (300 + Math.Sin(x * y / 999) * 200 )) +
rnd.Next( x + y + 111);
}
// now draw the data:
using (Graphics G = Graphics.FromImage(bmp))
for (int x = 0; x < data.GetLength(0); x++)
for (int y = 0; y < data.GetLength(1); y++)
{
using (SolidBrush brush = new SolidBrush(colors[(int)data[x, y]]))
G.FillRectangle(brush, x * stepX, y * stepY, stepX, stepY);
}
// and display the result
pictureBox1.Image = bmp;
}
这是来自 link 的函数:
List<Color> interpolateColors(List<Color> stopColors, int count)
{
SortedDictionary<float, Color> gradient = new SortedDictionary<float, Color>();
for (int i = 0; i < stopColors.Count; i++)
gradient.Add(1f * i / (stopColors.Count - 1), stopColors[i]);
List<Color> ColorList = new List<Color>();
using (Bitmap bmp = new Bitmap(count, 1))
using (Graphics G = Graphics.FromImage(bmp))
{
Rectangle bmpCRect = new Rectangle(Point.Empty, bmp.Size);
LinearGradientBrush br = new LinearGradientBrush
(bmpCRect, Color.Empty, Color.Empty, 0, false);
ColorBlend cb = new ColorBlend();
cb.Positions = new float[gradient.Count];
for (int i = 0; i < gradient.Count; i++)
cb.Positions[i] = gradient.ElementAt(i).Key;
cb.Colors = gradient.Values.ToArray();
br.InterpolationColors = cb;
G.FillRectangle(br, bmpCRect);
for (int i = 0; i < count; i++) ColorList.Add(bmp.GetPixel(i, 0));
br.Dispose();
}
return ColorList;
}
您可能希望绘制带有标签等的坐标轴。您可以使用 Graphics.DrawString
或 TextRenderer.DrawText
来完成此操作。在绘图区周围留足够的space即可!
我使用转换为 int
的数据值作为指向颜色 table 的直接指针。
根据您的数据,您将需要缩小它们甚至使用对数转换。您的第一张图片显示 对数 比例从 100 到 20k,第二张看起来是 线性 从 0 到 100。
如果您向我们展示您的数据结构,我们可以进一步提示您如何调整代码以使用它..
您可以按照其他答案创建位图。使用颜色查找 table 将 FFT 对数幅度转换为用于每个像素或小矩形的颜色也很常见。
使用 STFT(Short-time 傅里叶变换)后,输出是一个表示 3d 图的矩阵,就好像 (A[X, Y] = M)
A 是输出矩阵,X 是时间,Y 是频率,第三维M是像素颜色强度所表示的幅度,如下图:
Spectrogram 2
如何在 C# 中绘制具有颜色渐变的输出矩阵 A,如图片所示?
是否有包含 C# 频谱图控件的库?
更新:
对给定的算法进行一些修改后,我可以绘制频谱图,我没有更改调色板,只是第一种颜色更改为黑色,但我不知道为什么它很褪色!
这个代表一个声音
Bye Bye
Bye Bye Spectrogram
这是 pure sine wave
中的一个,所以几乎一直都是相同的频率
Pure sine wave Spectrogram
输出被接受,它代表了预期的输入信号的频率,但我认为有一种方法可以使频谱图像示例中的那样得到很好的说明,你能看看我的代码并提出修改建议吗?
这是事件处理程序:
private void SpectrogramButton_Click(object sender, EventArgs e)
{
Complex[][] SpectrogramData = Fourier_Transform.STFT(/*signal:*/ samples, /*windowSize:*/ 512, /*hopSize:*/ 512);
SpectrogramBox.Image = Spectrogram.DrawSpectrogram(SpectrogramData, /*Interpolation Factor:*/ 1000, /*Height:*/ 256);
}
这是我修改后的绘图功能:
public static Bitmap DrawSpectrogram(Complex[][] Data, int InterpolationFactor, int Height)
{
// target size:
Size sz = new Size(Data.GetLength(0), Height);
Bitmap bmp = new Bitmap(sz.Width, sz.Height);
// the data array:
//double[,] data = new double[222, 222];
// step sizes:
float stepX = 1f * sz.Width / Data.GetLength(0);
float stepY = 1f * sz.Height / Data[0].GetLength(0);
// create a few stop colors:
List<Color> baseColors = new List<Color>(); // create a color list
baseColors.Add(Color.Black);
baseColors.Add(Color.LightSkyBlue);
baseColors.Add(Color.LightGreen);
baseColors.Add(Color.Yellow);
baseColors.Add(Color.Orange);
baseColors.Add(Color.Red);
// and the interpolate a larger number of grdient colors:
List<Color> colors = interpolateColors(baseColors, InterpolationFactor);
// a few boring test data
//Random rnd = new Random(1);
//for (int x = 0; x < data.GetLength(0); x++)
// for (int y = 0; y < data.GetLength(1); y++)
// {
// //data[x, y] = rnd.Next((int)(300 + Math.Sin(x * y / 999) * 200)) +
// // rnd.Next(x + y + 111);
// data[x, y] = 0;
// }
// now draw the data:
float Max = Complex.Max(Data);
using (Graphics G = Graphics.FromImage(bmp))
for (int x = 0; x < Data.GetLength(0); x++)
for (int y = 0; y < Data[0].GetLength(0); y++)
{
int Val = (int)Math.Ceiling((Data[x][y].Magnitude / Max) * (InterpolationFactor - 1));
using (SolidBrush brush = new SolidBrush(colors[(int)Val]))
G.FillRectangle(brush, x * stepX, (Data[0].GetLength(0) - y) * stepY, stepX, stepY);
}
// and display the result
return bmp;
}
我不太明白你在回答中说的 log
事情,很抱歉我的知识很少。
更新:
这是将 log10
添加到幅度后的输出(忽略负值):
我认为这个输出是可以接受的,它与我一开始带的例子不同,但我认为它更好。
不,据我所知没有开箱即用的控件。当然,您可能还可以购买外部图书馆,但是嘘,您不能在 SO 上询问它们..
理论上你可以使用,或者我想我应该说滥用一个Chart
控制。但由于 DataPoints
是相当昂贵的物品,或者至少比它们看起来更昂贵,这似乎不可取。
相反,您可以自己简单地将图表绘制成 Bitmap
。
第一步是决定颜色的渐变。有关此示例,请参阅 interpolateColors function here!
然后您只需使用
floats
对步长和像素大小对数据进行双循环,然后在那里执行Graphics.FillRectangle
。
这里是一个简单的例子,使用GDI+
创建一个Bitmap
和一个Winforms PictureBox
用于显示。它不会向图形添加任何轴并完全填充它。
它首先创建一些样本数据和一个具有 1000
颜色的渐变。然后它绘制成一个Bitmap
并显示结果:
private void button6_Click(object sender, EventArgs e)
{
// target size:
Size sz = pictureBox1.ClientSize;
Bitmap bmp = new Bitmap(sz.Width, sz.Height);
// the data array:
double[,] data = new double[222, 222];
// step sizes:
float stepX = 1f * sz.Width / data.GetLength(0);
float stepY = 1f * sz.Height / data.GetLength(1);
// create a few stop colors:
List<Color> baseColors = new List<Color>(); // create a color list
baseColors.Add(Color.RoyalBlue);
baseColors.Add(Color.LightSkyBlue);
baseColors.Add(Color.LightGreen);
baseColors.Add(Color.Yellow);
baseColors.Add(Color.Orange);
baseColors.Add(Color.Red);
// and the interpolate a larger number of grdient colors:
List<Color> colors = interpolateColors(baseColors, 1000);
// a few boring test data
Random rnd = new Random(1);
for (int x = 0; x < data.GetLength(0); x++)
for (int y = 0; y < data.GetLength(1); y++)
{
data[x, y] = rnd.Next( (int) (300 + Math.Sin(x * y / 999) * 200 )) +
rnd.Next( x + y + 111);
}
// now draw the data:
using (Graphics G = Graphics.FromImage(bmp))
for (int x = 0; x < data.GetLength(0); x++)
for (int y = 0; y < data.GetLength(1); y++)
{
using (SolidBrush brush = new SolidBrush(colors[(int)data[x, y]]))
G.FillRectangle(brush, x * stepX, y * stepY, stepX, stepY);
}
// and display the result
pictureBox1.Image = bmp;
}
这是来自 link 的函数:
List<Color> interpolateColors(List<Color> stopColors, int count)
{
SortedDictionary<float, Color> gradient = new SortedDictionary<float, Color>();
for (int i = 0; i < stopColors.Count; i++)
gradient.Add(1f * i / (stopColors.Count - 1), stopColors[i]);
List<Color> ColorList = new List<Color>();
using (Bitmap bmp = new Bitmap(count, 1))
using (Graphics G = Graphics.FromImage(bmp))
{
Rectangle bmpCRect = new Rectangle(Point.Empty, bmp.Size);
LinearGradientBrush br = new LinearGradientBrush
(bmpCRect, Color.Empty, Color.Empty, 0, false);
ColorBlend cb = new ColorBlend();
cb.Positions = new float[gradient.Count];
for (int i = 0; i < gradient.Count; i++)
cb.Positions[i] = gradient.ElementAt(i).Key;
cb.Colors = gradient.Values.ToArray();
br.InterpolationColors = cb;
G.FillRectangle(br, bmpCRect);
for (int i = 0; i < count; i++) ColorList.Add(bmp.GetPixel(i, 0));
br.Dispose();
}
return ColorList;
}
您可能希望绘制带有标签等的坐标轴。您可以使用 Graphics.DrawString
或 TextRenderer.DrawText
来完成此操作。在绘图区周围留足够的space即可!
我使用转换为 int
的数据值作为指向颜色 table 的直接指针。
根据您的数据,您将需要缩小它们甚至使用对数转换。您的第一张图片显示 对数 比例从 100 到 20k,第二张看起来是 线性 从 0 到 100。
如果您向我们展示您的数据结构,我们可以进一步提示您如何调整代码以使用它..
您可以按照其他答案创建位图。使用颜色查找 table 将 FFT 对数幅度转换为用于每个像素或小矩形的颜色也很常见。