Рисование матрицы с градиентом цветов Спектрограмма

После использования STFT (кратковременное преобразование Фурье) на выходе получается матрица, представляющая трехмерный график, как если бы (A[X, Y] = M) A - это выходная матрица, X - время, Y - частота, а третье измерение M - амплитуда, проиллюстрированная интенсивность цвета пикселя как на следующих рисунках:

### Спектрограмма 1

Спектрограмма 2

Как нарисовать выходную матрицу A с градиентом цветов, как на изображениях в C #?
Есть ли библиотека, содержащая элемент управления спектрограммой для C #?



Обновление:
После некоторых модификаций данного алгоритма я смог нарисовать спектрограмму, я не изменил цветовую палитру, за исключением того, что первый цвет изменился на черный, но я не знаю, почему он очень блеклый!

Этот представляет собой звуковое высказывание

Пока-пока

До свидания, спектрограмма

И этот из pure sine wave, так что частота почти всегда одинакова.

Спектрограмма чисто синусоидальной волны

Результат принят, он представляет частоты входного сигнала, как и ожидалось, но я думаю, что есть способ сделать спектрограмму так же хорошо проиллюстрированной, как и в примерах, не могли бы вы взглянуть на мой код и предложить модификации?


Это обработчик события:

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. Это одно из предыдущих "Пока-пока":

введите описание изображения здесь

  1. Выстрел из дробовика:

введите описание изображения здесь

  1. Музыкальная шкатулка:

введите описание изображения здесь

Я думаю, что этот вывод приемлем, он отличается от примеров, которые я привел в начале, но я думаю, что он лучше.


person Mohamed Hosnie    schedule 27.12.2015    source источник
comment
Я немного привязан к атм, но позже добавлю немного к своему ответу, чтобы продемонстрировать использование логарифмической шкалы, поскольку это обычно необходимо для звуковых данных ... Но первое изображение выглядит довольно хорошо, imo, так что у вас нет проблем там. Если вы измените 1-й цвет на черный, вам, вероятно, следует вставить после него темный и средний синий стоп-цвета. Поиграйте с цветами, чтобы прочувствовать их !! Вы можете использовать любое их количество .. Также: Вам необходимо знать диапазон ваших значений! Это нормальный диапазон звуков (16-16к)? Если так..   -  person TaW    schedule 28.12.2015
comment
... прямое сопоставление с линейным списком цветов не сработает. Список линейный, всего 1000 цветов, и даже если вы увеличите его до 16 или 20 тысяч, он все равно будет неправильным. Вместо этого вам нужно найти их в логарифмическом масштабе.   -  person TaW    schedule 28.12.2015
comment
Вместо того, чтобы использовать Magnitude напрямую, я сделал это Val = 20d * Math.Log10(Data[x][y].Magnitude). Я не знаю, это ли вы имеете в виду, но мне пришлось пренебречь всеми отрицательными значениями, которые возникают в результате Magnitude values < 1, который выглядит очень темными областями в исходном изображении, поэтому я подумал отображения всего диапазона значений после log на имеющийся у меня диапазон цветов, поэтому я удостоверяюсь, что отображаются все значения.   -  person Mohamed Hosnie    schedule 28.12.2015
comment
Звучит примерно так, как я бы порекомендовал. Вы добавили несколько цветов после черных стоп-цветов? Не стесняйтесь размещать текущий результат!   -  person TaW    schedule 28.12.2015
comment
Да, это список цветов прямо сейчас: 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);   -  person Mohamed Hosnie    schedule 28.12.2015
comment
Хм, я не думаю, что это может быть вполне правильным, поскольку на всех изображениях есть большой пробел посередине .. Я попытаюсь смоделировать еще несколько и более качественных тестовых данных ..   -  person TaW    schedule 29.12.2015
comment
Хм, вы можете загрузить заархивированные данные fft, скажем «пока», в какое-нибудь бесплатное место для загрузки? Я поместил звук в Audition и посмотрел на результат . Там тоже есть пробел, гораздо меньший при переходе от 7k-11k, а верхняя полоса показывает от 11k до 15k. Ничего подобного, чего можно ожидать от mp3. так что здесь есть над чем поработать, и хорошим началом будет добавление меток к графику ..   -  person TaW    schedule 29.12.2015
comment
Что вы имеете в виду под данными fft? И что касается меток, в ходе моего поиска я нашел уравнение, которое дает значение частоты для каждой строки в матрице, которая равна fr = sampleRate / # samples, а затем значение частоты f для индекса или строки n из уравнение f = n * fr, как вы видите, связано с частотой дискретизации и количеством образцов, которое меняется от файла к файлу, я не знаю, правильно ли оно, но я не пробовал,   -  person Mohamed Hosnie    schedule 29.12.2015
comment
Ну, разве вы не сохраняете результаты БПФ (Complex[][] SpectrogramData) или вы всегда их снова вычисляете? Сериализация их должна быть скорее ... Содержат ли они линейные данные, например, одну строку на Гц или данные уже представлены в логарифмической схеме, что означает такое же количество строк / частот на октаву? Внимательно посмотрите на 1-е изображение, а также на то, что в моей ссылке: метки не линейные, а логарифмические ..   -  person TaW    schedule 29.12.2015
comment
Для каждого входного аудиофайла я вычисляю SpectrogramData с самого начала, потому что это всегда разные данные для каждого аудиофайла, что я делаю в своем STFT заключается в том, что я разделяю (сегментирую) выборки на сегменты известной длины, и для каждого сегмента я вычисляю данные БПФ, каждый индекс в SpectrogramData - это БПФ данные для одного сегмента, например SpectrogramData[0] - это данные БПФ первого сегмента и так далее ... это все, что я делал до сих пор.   -  person Mohamed Hosnie    schedule 29.12.2015
comment
В части чертежа каждый индекс в SpectrogramData является столбцом в растровом изображении.   -  person Mohamed Hosnie    schedule 29.12.2015
comment
Вы действительно используете полную сложную структуру? Если да, то как? Или вы используете только Реальную часть. Если, знаете ли вы, что, если что-то находится в Воображаемой части?   -  person TaW    schedule 29.12.2015
comment
Я использую величину sqrt(real*real + imaginary*imaginary), поэтому я использую как реальную, так и мнимую части.   -  person Mohamed Hosnie    schedule 30.12.2015
comment
Ах. Что ж, если вы можете изменить структуру данных с зубчатого массива на реальный 2-мерный массив Complex[,] fftData, это сохранит данные: void saveKs(string dataFile) { using (BinaryWriter writer = new BinaryWriter(File.Open(dataFile, FileMode.Create))) { for (int y = 0; y < fftData.GetLength(0); y++) for (int x = 0; x < fftData.GetLength(1); x++) { writer.Write((double)fftData[x, y].Real); writer.Write((double)fftData[x, y].Imaginary); } } } Сохранение зубчатого массива, конечно, только немного сложнее .., но все строки имеют одинаковую длину, верно ?   -  person TaW    schedule 30.12.2015
comment
Вот данные, но как вы собираетесь их читать не зная длины массива?   -  person Mohamed Hosnie    schedule 30.12.2015
comment
Ха, ты прав. Думаю, их хранение вместе с данными могло бы помочь; это может быть 352x256, но, может быть, вы мне подскажете? А теперь пора ложиться спать ... Вот процедура для хранения сложного неровного массива Complex[][] fftDataJ :   -  person TaW    schedule 30.12.2015
comment
..и прочитать его: Complex[][] loadJs(string dataFile) { Complex[][] fftJ = null; using (BinaryReader reader = new BinaryReader(File.Open(dataFile, FileMode.Open))) { int l0 = reader.ReadInt32(); fftJ = new Complex[l0][]; for (int y = 0; y < fftJ.GetLength(0); y++) { int l1 = reader.ReadInt32(); fftJ[y] = new Complex[l1]; for (int x = 0; x < fftJ[y].GetLength(0); x++) { double r = reader.ReadDouble(); double i = reader.ReadDouble(); fftJ[y][x] = new Complex(r, i); } } } return fftJ; } (Простите за форматирование, но комментарий может быть только таким длинным ..)   -  person TaW    schedule 30.12.2015
comment
Да, это правда, я сохранил данные с помощью вашей новой функции. Вот данные, возвращаемые функцией STFT в моем коде. . Обратите внимание, что индекс первого измерения в массиве представляет номер столбца на графике или в растровом изображении, я имею в виду.   -  person Mohamed Hosnie    schedule 30.12.2015


Ответы (2)


Нет, я знаю, что нет готового элемента управления. Конечно, могут быть и другие библиотеки, которые вы можете купить, но шшш, вы не можете спрашивать о них ТАК ..

Теоретически вы могли бы использовать, или я думаю, я лучше скажу злоупотреблять Chart контролем для этого. Но поскольку DataPoints являются довольно дорогими объектами или, по крайней мере, более дорогими, чем кажется, это кажется нецелесообразным.

Вместо этого вы можете просто нарисовать график в Bitmap самостоятельно.

  • Шаг первый - выбрать градиент цветов. См. Функцию interpolateColors вот здесь пример этого!

  • Затем вы просто выполните двойной цикл над данными, используя 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;
}

Вот функция по ссылке:

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. Просто оставьте достаточно места вокруг области рисования!

Я использовал значения данных, приведенные к int, как прямые указатели на таблицу цветов.

В зависимости от ваших данных вам нужно будет уменьшить их масштаб или даже использовать преобразование журнала. Первое изображение показывает логарифмическую шкалу от 100 до 20k, второе выглядит линейно от 0 до 100.

Если вы покажете нам свою структуру данных, мы дадим вам дополнительные советы, как адаптировать код для ее использования.

person TaW    schedule 27.12.2015

Вы можете создать растровое изображение в соответствии с другим ответом. Также часто используется таблица поиска цвета для преобразования величины журнала БПФ в цвет, используемый для каждого пикселя или небольшого прямоугольника.

person hotpaw2    schedule 27.12.2015
comment
Я не понимаю, в какой части log я использую величины как есть, потому что некоторые величины меньше единицы, что приводит к отрицательному log, и это создает проблему при рисовании, не могли бы вы прояснить это? - person Mohamed Hosnie; 27.12.2015
comment
Обычно один смещает и масштабирует величину журнала так, чтобы ее диапазон соответствовал размеру вашей таблицы поиска схемы окраски (0–255 цветов и т. Д.). Но вам нужно игнорировать нулевые величины, прежде чем брать журнал. Вместо этого установите для них значение, которое индексирует нижнюю часть таблицы поиска. Закон человеческого восприятия Вебера-Фехнера - одна из причин использования логарифма в логарифмической величине. - person hotpaw2; 28.12.2015
comment
Не могли бы вы привести пример кода? возможно, вы можете использовать мои образцы кода, добавленные в текст моего вопроса, чтобы прояснить его. - person Mohamed Hosnie; 28.12.2015