Обнаружение скрытых эмоций в голосе [Евгений Столов] (fb2) читать постранично, страница - 3


 [Настройки текста]  [Cбросить фильтры]

позиции переходного процесса. В этом случае такой интервал должен быть проигнорирован. Следует отметить, что выделением информационных фрагментов в речевом файле занимались многие исследователи. Так или иначе, для этой цели используется мощность сигнала, однако, установка пределе для этой мощности и является предметом исследования. Мы берем за основу выделение ШИ и последующий анализ их распределения. Это означает, что для вычисления порога исследуется мощность сигнала в некотором маленьком интервале. После этого все интервалы в автоматическом режиме разбиваются на два класса. Последующая процедура выделения слов основана на этом разбиении. Сначала требуется выбрать размер интервала. Естественно выбирать этот параметр в зависимости от частоты стробирования 𝐹𝑟, например, 𝑆𝑖𝑧𝑒𝐹𝑟𝑎𝑔𝑚=𝐹𝑟/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. Разбиение на слова производится на основе этой последовательности.

Первая проблема, которую нужно решить — найти длины интервалов из нулей (или единиц) в этой ступенчатой последовательности. Таким образом вычисляется истинная длина интервала между отдельными информационным