ввода файлов с микрофона, перевода в формат wav и для редактирования файлов.
Автор реализовал представленную выше идею на сайте с адресом http://5.23.55.2. Доступ к сайту свободный, без регистрации. Заметим, что сайт не использует сертификат достоверности, поэтому браузер порождает предупреждение об этом, однако, для эксперимента достаточно только загрузить на сайт речевой файл в любом формате. Такая загрузка не может повредить пользователю. Целью создания сайта является проверка работы алгоритмов, поэтому автор будет благодарен всем пользователям, если они поделятся результатом своего эксперимента. Надо сравнить реакцию программы на отклонение в параметрах с ощущением человека при прослушивании речи. Адрес для связи: ystolov@list.ru.
Выводы об эмоциональном состоянии диктора основаны на измерении определенных параметров звукового файла. Если речь идет о скрытых эмоциях, то, скорее всего, эти эмоции проявляются при произнесении отдельных слов.
Выделение "фраз" и "слов " в речевом файле
Изменение эмоционального состояния может проявиться во время произнесения очередной фразы в потоке или даже при произнесении отдельного слова в фразе. Нашей ближайшей целью является научиться выделять отдельные слова в потоке и формировать из них фразы. Рассмотрим более детально произвольный отрезок обычного речевого файла.
Видно, что этот отрезок делится на небольшие фрагменты повышенной мощности и промежутки между ними. Отмеченные промежутки это либо заполненные шумом от дыхания интервалы (интервалы между словами или отдельными слогами), либо переходные процессы в речи. Переходный процесс это промежуток между концом гласного и началом согласного или наоборот. Назовем такие интервалы шумовыми интервалами (ШИ), а оставшиеся — информационными (ИнИ). Естественно считать словом промежуток в речевом файле между двумя последовательными ШИ. На самом деле ситуация более сложная, поскольку ШИ может появиться и между двумя слогами одного слова либо в позиции переходного процесса. В этом случае такой интервал должен быть проигнорирован. Следует отметить, что выделением информационных фрагментов в речевом файле занимались многие исследователи. Так или иначе, для этой цели используется мощность сигнала, однако, установка пределе для этой мощности и является предметом исследования. Мы берем за основу выделение ШИ и последующий анализ их распределения. Это означает, что для вычисления порога исследуется мощность сигнала в некотором маленьком интервале. После этого все интервалы в автоматическом режиме разбиваются на два класса. Последующая процедура выделения слов основана на этом разбиении. Сначала требуется выбрать размер интервала. Естественно выбирать этот параметр в зависимости от частоты стробирования , например, =/1000 — одна мс. Теперь функция createStdDistr вычисляет стандартные отклонения внутри каждого из выбранных интервалов из входного массива In.
import nunpy as np
def createStdDistr(In,SizeFragm):
In = np.float_(In)
In — = np.mean(In)
Ln = len(In)
Vary = []
I =0; End = SizeFragm
while End<= Ln:
Fragm = In[I: End]
Vary.append(Fragm)
I += SizeFragm
End += SizeFragm
VaryArray = np.float_(Vary)
Std = np.std(VaryArray)
return Std
Следующий шаг — классификация интервалов по мощности. Его реализует функция getFeat. В ее основе лежит стандартная процедура kmeans.
from scipy.cluster.vq import kmeans,vq
def getFeat(Std):
Cent,_ = kmeans(Std,2)
Cent = sorted(Cent)
Out = vq(Std,Cent)
Features = Out[0]
return Features
В этой процедуре функция kmeans порождает два центроида, центры скопления значений стандартных отклонений интервалов. Процедура сортировки ставит не первое место меньшее из значений центроидов. Функция vq присваивает метку 0 или 1 каждому интервалу, при этом метка 0 означает, что данный интервал близок к меньшему из центроидов. Это означает, что такой интервал мы считаем шумовым.
Окрасим интервалы в разные цвета в зависимости от метки. Вот так выглядит размеченная часть речевого файла, состоящая из 17 фрагментов.
from matplotlib import pyplot as plt
Std = createStdDistr(In,SizeFragm)
Features = getFeat(Std)
NumFragm = 17
Beg = 10 * SizeFragm
for I in range(10,10 + NumFragm):
End = Beg + SizeFragm
if Features[I] == 0:
Col = 'k'
else:
Col ='r'
Arg = np.arange(Beg,End)
plt.plot(Arg,In[Beg: End],Col)
Beg += SizeFragm
Следует заметить, что конечный результат зависит способа вычисления характеристики интервала. Например, заменив стандартное отклонение на дисперсию, мы получим другое разбиение интервалов на классы. Использование максимального значения в качестве характеристики приводит к чувствительности решения к случайным выбросам. Достоинством процедуры kmeans является то, что не делается предположений о распределении стандартных отклонений.
Как было отмечено выше, результат классификации интервалов зависит от выбора характеристики интервала, однако, и сама классификация с помощью kmeans не является единственной возможной. Рассмотрим еще одну процедуру классификации пригодную для бинарного случая и используемую для построения черно-белых изображений. Здесь также не делается предположений о распределении исходных данных. Это алгоритм Отсу, а в его основе лежит гистограмма найденных характеристик интервалов (стандартное отклонение в нашем случае).
def otsu(Bins,Interv):
def oneStep(T):
'''
One step of the Otsu algo
0<T<NumBins
'''
Bins1 = Bins[: T]
Bins2 = Bins[T:]
Prob1 = Bins1.sum()
Prob2 = Bins2.sum()
Aver1 = sum(Bins1 * Middles[: T])/Prob1
Aver2 = sum(Bins2 * Middles[T: ])/Prob2
return Prob1 * (Aver1 — Aver)**2
+ Prob2 *(Aver2 — Aver) **2
Bins = np.float_(Bins)
NumBins = len(Bins)
Middles = np.zeros(NumBins)
for I in range(len(Middles)):
Middles[I] = Interv[I] + Interv[I+1]
Middles *= 0.5
BinsSum = Bins.sum()
Bins /= BinsSum # Probabilities
Aver = sum(Bins * Middles)
Results = np.zeros(NumBins — 1)
for I in range(1,NumBins):
Results[I -1] = oneStep(I)
MxRes = np.amax(Results)
Pos = np.where(Results == MxRes)
return Middles[Pos[0][0] + 1]
Посмотрим на результат обработки того-же файла с помощью алгоритма Отсу
Bins,Interv = np.histogram(Std)
Level = otsu(Bins,Interv)
Result = np.where(Std<Level,0,1)
Мы разбили все интервалы на два класса, и теперь можем раскрасить тот же файл согласно новом разбиению. Полученный график имеет вид схожий с рисунком, полученным на основе kmeans.
Имея классификацию интервалов, можно попытаться выделить отдельные слова в файле. В основе процедуры "разделение" лежит следующая гипотеза. Слова разделяются последовательностью ШИ. Если ШИ оказался внутри слова, то это интервал между слогами. ШИ предшествующие слову и завершающие его включаются в слово. Заменяя каждый интервал нулем и единицей в зависимости от отнесения его к шуму или информации, получим ступенчатую последовательность Step. Разбиение на слова производится на основе этой последовательности.
Первая проблема, которую нужно решить — найти длины интервалов из нулей (или единиц) в этой ступенчатой последовательности. Таким образом вычисляется истинная длина интервала между отдельными информационным отрезками файла.