Идентификация пользователя по голосу
Недавно дали задание для курсовой, распознавание диктора по голосу (диктор может говорить разные слова). Ну чтобы мне было легче, преподаватель дал две статьи с Хабра
https://habr.com/ru/post/144491/
https://habr.com/ru/post/144580/
До этого вроде не пользовался гитхабом, но попросили выложить код туда.
https://github.com/Sweet-heart-bakho/Cursed
И казалось вот целые две статьи, но проблем в том что мне реально они не понятны, причем один сначала делает Фурье потом оконную функцию, а другой оконную потом Фурье. И вот уже какой день пишу код результата ноль. С некоторым вроде работает, а с некоторыми нет. Правильно ли я понимаю следующие действия:
- Извлечение данных из wav файла
double[] data = readAmplitudeValues(true);
double[] readAmplitudeValues(bool isBigEndian) //перевод байтов в 16 биты
{
int MSB, LSB; // старший и младший байты
byte[] buffer = File.ReadAllBytes(outfile); // читаем данные откуда-нибудь
double[] data = new double[buffer.Length / 2];
for (int i = 0; i < data.Length; i += 2)
{
if (isBigEndian) // задает порядок байтов во входном сигнале
{
// первым байтом будет MSB
MSB = buffer[2 * i];
// вторым байтом будет LSB
LSB = buffer[2 * i + 1];
}
else
{
// наоборот
LSB = buffer[2 * i];
MSB = buffer[2 * i + 1];
}
// склеиваем два байта, чтобы получить 16-битное вещественное число
// все значения делятся на максимально возможное - 2^15
data[i] = ((MSB << 8) | LSB);
}
for (int i = 0; i < data.Length; i++)
data[i] = data[i];
return data;
}
List<double> datalist = new List<double>();
for (int i = 1000; i < data.Length; i++)
datalist.Add(data[i] / 32768.0);
Деление на 32768 обусловленно для нормализации кажется, но везде видел что точно необходимо, почему пропускаю первые 1000 битов? Потому что обычно информация самого файла и тишини вполне.
- Далее идет фильтрация взял из второй статьи
datalist = Filtr(datalist);
List<double> Filtr (List<double> datalist)
{
for (int i = 1; i < datalist.Count; i++)
datalist[i] = (datalist[i] - (0.9 * datalist[i - 1])) * (0.54 - 0.46 * Math.Cos((i - 6)*(2 * Math.PI / 180)));
datalist.RemoveAt(0);
return datalist;
}
- Так как следующие действия разные в обоих статьях, я решил следующие действие будет функция Фурье
int start = -4000;
int end = 0;
List<double> arr = new List<double>();
for (int i = 0; i < datalist.Count; i++)
{
start += 4000;
end += 4000;
if (end > datalist.Count)
{
arr.Add(Furie(datalist, start, datalist.Count, i, datalist.Count - start));
break;
}
arr.Add(Furie(datalist, start, end, i, end - start));
}
double Furie(List<double> slices, int start, int end, int f, int fr) //преобразование Фурье
{
double temp = 0.0;
for (int t = start; t < end; t++)
{
temp += (slices[t] * Math.Cos((2 * Math.PI * t * f) / fr) - slices[t] * Math.Sin((2 * Math.PI * t * f) / fr));
}
return temp;
}
Почему 4000? Ну видимо потому там просилась порезка кадров, о ней говорилось в 1 статье, но тут тоже самое, тем более как мне понять сколько битов в 128мс. 3. Далее оконная функция Hemming, так как С Кайзером я так и не понял.
datalist = Hemming(datalist);
List<double> Hemming(List<double> slices) //окно Хемминга для устранения нежелательных эффектов
{
for (int i = 0; i < slices.Count; i++)
slices[i] = 0.53836 - (0.46164 * Math.Cos((Math.PI * 2 * slices[i]) / (slices.Count - 1)));
return slices;
}
- И на последок переход к мел шкале, в которой требуется от нас Гц в то время как у меня амплитуды, где взять герцы? И что с ними потом делать?
- Сравнение я уже взял из 2 статьи так как Коррелляция Пирсона явно лучше, так как получаем значение от 0 до 1. Хотя бы это я смог реализовать.
Дополняю, пробовал способ извлечения данных из .wav с помощью библиотеки NAudio
double[] data;
using (WaveFileReader reader = new WaveFileReader(outfile))
{
byte[] bytesBuffer = new byte[reader.Length];
int read = reader.Read(bytesBuffer, 0, bytesBuffer.Length);
double[] floatSamples = new double[read / 2];
for (int sampleIndex = 0; sampleIndex < read / 2; sampleIndex++)
{
var intSampleValue = BitConverter.ToInt16(bytesBuffer, sampleIndex * 2);
floatSamples[sampleIndex] = intSampleValue / 32768.0;
}
data = floatSamples;
}
Кстати данные вообще разные на выход
Для меня эти две статьи все еще остаются не понятыми. Надеюсь на вашу помощь.
Дополнение от 15.12.2021
Так как мне не совсем понятна функция Фурье в данной программе, а именно момент с окном (нужно самому выбрать размер вашего окна), я начал проводить тесты с разменом окна и вы удивитесь но от размера окна результат всегда разный причём порой меняется кардинально. Скоро скину результаты прогонов.
P.S. по первым прогонам можно точно сказать +- хорошие резултаты выходят при окне от 17000 и выше. В разных источников писались разные оконные промежутки, но проблема в том что там описывается время (20мс - 40мс), а у нас 16-биты. Я пробовал уже узнать количество переменных в 40мс файле, около 36000.
Дополнение от 16.12.2021
Входе тестов выявил следующие размеры что дают +- нужный результат, конечно среди них можно все еще выявить лучшие. Там просто случайный набор чисел, не уверен что хотя бы один из чисел имеет смысл.
Дополнение от 20.12.2021
У файлов wav оказывается есть битрейт и держу пари что оно влияет на выходные данные так что перед тем как сверять амплитуды, стоит изменить бирейт всех на файлов 8000, 16, 1 (гц, бирейт, моно) Ниже код это испралвяет не забываем библиотеку NAudio
using (var reader = new WaveFileReader("input.wav"))
{
var newFormat = new WaveFormat(8000, 16, 1);
using (var conversionStream = new WaveFormatConversionStream(newFormat, reader))
{
WaveFileWriter.CreateWaveFile("output.wav", conversionStream);
}
}
Для проверки битрейта файла
int bitrate;
using (var f = File.OpenRead(outfile))
{
f.Seek(28, SeekOrigin.Begin);
byte[] val = new byte[4];
f.Read(val, 0, 4);
bitrate = BitConverter.ToInt32(val, 0);
}