【问题标题】:Drawing a matrix with a gradient of colors "Spectrogram"绘制具有渐变颜色“频谱图”的矩阵
【发布时间】:2015-12-27 14:03:40
【问题描述】:

使用 STFT(短时傅里叶变换)后,输出是一个矩阵,表示 3d 图,好像(A[X, Y] = M) A 是输出矩阵,X 是时间,Y 是频率,第三维 M 是由像素颜色的强度表示的幅度如下图:

Spectrogram 2

如何使用 C# 中的图片中的颜色渐变绘制输出矩阵 A?
是否有包含 C# 频谱图控件的库?



更新:
在对给定的算法进行一些修改后,我可以绘制频谱图,除了第一种颜色变为黑色之外,我没有更改调色板,但我不知道为什么它非常褪色!

这个代表一个声音说

再见

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 添加到幅度后的输出(忽略负值):

  1. This one of "Bye bye" from before:

  1. A Shotgun Blast:

  1. A Music Box:

我认为这个输出是可以接受的,它与我一开始带来的例子不同,但我认为它更好。

【问题讨论】:

  • 我有点纠结 atm 但稍后会在我的答案中添加一些内容以演示使用对数刻度,因为它通常是声波数据所必需的。但第一张图片看起来不错 imo,所以你在那里没有问题。但是,如果您将第一种颜色更改为黑色,您可能应该在其后插入深色和中等蓝色停止颜色。玩弄颜色来感受它们!您可以使用它们中的任意数量。另外:您需要知道您的值的范围!这是声波的正常范围(16-16k)吗?如果是这样..
  • ..直接映射到线性颜色列表将无法正常工作。该列表是线性的,只有 1000 种颜色,即使您将其放大到 16k 或 20k,它仍然是不正确的。相反,您需要对它们进行对数查找..
  • 我没有直接使用Magnitude,而是直接使用Val = 20d * Math.Log10(Data[x][y].Magnitude),我不知道这是否是您的意思,但我不得不忽略出现在Magnitude values &lt; 1 的所有负值成为原始图像中非常暗的区域,所以我想将log 之后的整个值范围映射到我拥有的颜色范围,所以我确保显示所有值。
  • 听起来和我推荐的差不多。您是否在黑色停止色之后添加了一些颜色?随意发布当前结果!
  • 是的,这是现在的颜色列表:baseColors.Add(Color.Black); baseColors.Add(Color.DarkBlue); baseColors.Add(Color.MediumBlue); baseColors.Add(Color.LightSkyBlue); baseColors.Add(Color.LightGreen); baseColors.Add(Color.Yellow); baseColors.Add(Color.Orange); baseColors.Add(Color.Red);

标签: c# audio drawing fft spectrogram


【解决方案1】:

不,我知道没有开箱即用的控件。当然,你很可能可以购买外部库,但是嘘,你不能在 SO 上询问它们......

理论上你可以使用,或者我想我应该说滥用一个Chart控制。但由于DataPoints 是相当昂贵的对象,或者至少比看起来更昂贵,这似乎是不可取的。

相反,您可以简单地将图表绘制成 Bitmap 自己。

  • 第一步是决定颜色的渐变。有关此示例,请参阅 interpolateColors function here

  • 然后,您只需使用floats 对数据进行双循环以获取步长和像素大小,然后在此处执行Graphics.FillRectangle

这是一个使用GDI+ 创建BitmapWinforms 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;
}

这是链接中的功能:

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.DrawStringTextRenderer.DrawText 来执行此操作。只需在绘图区域周围留出足够的空间!

我使用转换为int 的数据值作为指向颜色表的直接指针。

根据您的数据,您需要按比例缩小它们,甚至使用对数转换。您的第一张图片显示了从 100 到 20k 的 对数 刻度,第二张看起来 线性 从 0 到 100。

如果您向我们展示您的数据结构,我们可以进一步提示您如何调整代码以使用它..

【讨论】:

    【解决方案2】:

    您可以根据其他答案创建位图。使用颜色查找表将 FFT 对数幅度转换为用于每个像素或小矩形的颜色也很常见。

    【讨论】:

    • 我不明白 log 部分我正在使用幅度,因为某些幅度小于 1 导致负的 log 并且在绘图时会出现问题,你能说得清楚一点吗?
    • 通常,一个偏移量和缩放对数幅度,使其范围适合您的配色方案查找表的大小(0-255 种颜色等)。但是在获取日志之前,您确实需要忽略零的大小。而是将它们设置为索引查找表底部的值。 Weber-Fechner 人类感知定律是使用对数幅度对数的原因之一。
    • 能否提供一个代码示例?也许您可以使用我添加到我的问题正文中的代码示例来澄清它。
    猜你喜欢
    • 1970-01-01
    • 2020-07-12
    • 1970-01-01
    • 1970-01-01
    • 2014-06-10
    • 1970-01-01
    • 1970-01-01
    • 2015-08-27
    • 1970-01-01
    相关资源
    最近更新 更多