огибающая#
- scipy.signal.огибающая(z, bp_in=(1, None), *, n_out=None, квадрат=False, остаток='lowpass', ось=-1)[источник]#
Вычисление огибающей вещественного или комплексного сигнала.
- Параметры:
- zndarray
Вещественный или комплексный входной сигнал, который предполагается состоящим из
nвыборки и имея интервал выборкиT. z также может быть многомерным массивом с осью времени, определенной через ось.- bp_intuple[int | None, int | None], опционально
2-кортеж, определяющий частотную полосу
bp_in[0]:bp_in[1]входного фильтра. Граничные частоты указываются как целые кратные1/(n*T)с-n//2 <= bp_in[0] < bp_in[1] <= (n+1)//2являясь допустимым диапазоном частот.Noneзаписи заменены на-n//2или(n+1)//2соответственно. Значение по умолчанию(1, None)удаляет среднее значение, а также компоненты отрицательной частоты.- n_outint | None, optional
Если не
Noneвыходные данные будут передискретизированы до n_out образцов. Значение по умолчаниюNoneустанавливает выходную длину равной входной z.- квадратbool, необязательно
Если установлено, возвращается квадрат огибающей. Полоса пропускания квадрата огибающей часто меньше полосы пропускания не возведённой в квадрат огибающей из-за нелинейной природы используемой функции абсолютного значения. Т.е. встроенная функция квадратного корня обычно создаёт дополнительные гармоники. По умолчанию
False.- остатокLiteral[‘lowpass’, ‘all’, None], опционально
Эта опция определяет, какой тип остатка, т.е. часть сигнала, которую удаляет входной полосовой фильтр, возвращается.
'all'возвращает всё, кроме содержимого частотной полосыbp_in[0]:bp_in[1],'lowpass'возвращает содержимое частотной полосы< bp_in[0]. ЕслиNoneтогда возвращается только огибающая. По умолчанию:'lowpass'.- осьint, необязательный
Ось z по которому вычисляется огибающая. По умолчанию последняя ось.
- Возвращает:
- ndarray
Если параметр остаток является
Noneзатем массивz_envс той же формой, что и входные данные z возвращается, содержащий его огибающую. В противном случае массив с формой(2, *z.shape), содержащий массивыz_envиz_res, сложенные вдоль первой оси, возвращаются. Это позволяет распаковку, т.е.,z_env, z_res = envelope(z, residual='all'). Остатокz_resсодержит часть сигнала, которую входной полосовой фильтр удалил, в зависимости от параметра остаток. Обратите внимание, что для вещественных сигналов возвращается вещественный остаток. Следовательно, компоненты отрицательной частоты bp_in игнорируются.
Смотрите также
hilbertВычислите аналитический сигнал с помощью преобразования Гильберта.
Примечания
Любой комплекснозначный сигнал \(z(t)\) может быть описана вещественной мгновенной амплитудой \(a(t)\) и вещественную мгновенную фазу \(\phi(t)\), т.е., \(z(t) = a(t) \exp\!\big(j \phi(t)\big)\). Огибающая определяется как абсолютное значение амплитуды \(|a(t)| = |z(t)|\), что одновременно является абсолютным значением сигнала. Следовательно, \(|a(t)|\) «огибает» класс всех сигналов с амплитудой \(a(t)\) и произвольная фаза \(\phi(t)\). Для вещественных сигналов, \(x(t) = a(t) \cos\!\big(\phi(t)\big)\) это аналогичная формулировка. Следовательно, \(|a(t)|\) может быть определено путем преобразования \(x(t)\) в аналитический сигнал \(z_a(t)\) с помощью преобразования Гильберта, т.е., \(z_a(t) = a(t) \cos\!\big(\phi(t)\big) + j a(t) \sin\!\big(\phi(t) \big)\), что даёт комплексный сигнал с той же огибающей \(|a(t)|\).
Реализация основана на вычислении БПФ входного сигнала и последующем выполнении необходимых операций в пространстве Фурье. Следовательно, необходимо учитывать типичные ограничения БПФ:
Предполагается, что сигнал периодический. Разрывы между началом и концом сигнала могут привести к нежелательным результатам из-за явления Гиббса.
БПФ работает медленно, если длина сигнала является простым числом или очень большой. Также требования к памяти обычно выше, чем у реализации на основе КИХ/БИХ-фильтров.
Частотный интервал
1 / (n*T)для частот среза полосового фильтра соответствует частотам, создаваемымscipy.fft.fftfreq(len(z), T).
Если огибающая комплекснозначного сигнала z без полосовой фильтрации требуется, т.е.
bp_in=(None, None), тогда огибающая соответствует абсолютному значению. Следовательно, более эффективно использоватьnp.abs(z)вместо этой функции.Хотя вычисление огибающей на основе аналитического сигнала [1] является естественным методом для вещественных сигналов, другие методы также часто используются. Самой популярной альтернативой, вероятно, является так называемый «квадратичный» детектор огибающей и его родственные методы [2]. Они не всегда вычисляют правильный результат для всех видов сигналов, но обычно корректны и, как правило, вычислительно более эффективны для большинства видов узкополосных сигналов. Определение огибающей, представленное здесь, является общим, когда важны мгновенная амплитуда и фаза (например, как описано в [3]). Существуют также другие концепции, основанные на общей математической идее оболочки [4]: Прагматичный подход заключается в определении всех верхних и нижних пиков сигнала и использовании сплайн-интерполяции для определения кривых [5].
Ссылки
[1]"Аналитический сигнал", Википедия, https://en.wikipedia.org/wiki/Analytic_signal
[2]Лайонс, Ричард, «Цифровое детектирование огибающей: хорошее, плохое и ужасное», IEEE Signal Processing Magazine 34.4 (2017): 183-187. PDF
[3]T.G. Kincaid, “The complex representation of signals.”, TIS R67# MH5, General Electric Co. (1966). PDF
[4]“Огибающая (математика)”, Википедия, https://en.wikipedia.org/wiki/Envelope_(mathematics)
Примеры
Следующий график иллюстрирует огибающую сигнала с переменной частотой и низкочастотным дрейфом. Чтобы отделить дрейф от огибающей, используется фильтр верхних частот 4 Гц. Низкочастотный остаток входного полосового фильтра используется для определения асимметричной верхней и нижней границ, охватывающих сигнал. Из-за гладкости результирующей огибающей она прореживается с 500 до 40 отсчётов. Обратите внимание, что мгновенная амплитуда
a_xи вычисленная огибающаяx_envне являются абсолютно идентичными. Это связано с тем, что сигнал не является идеально периодическим, а также с существованием некоторого спектрального перекрытияx_carrierиx_drift. Следовательно, они не могут быть полностью разделены полосовым фильтром.>>> import matplotlib.pyplot as plt >>> import numpy as np >>> from scipy.signal.windows import gaussian >>> from scipy.signal import envelope ... >>> n, n_out = 500, 40 # number of signal samples and envelope samples >>> T = 2 / n # sampling interval for 2 s duration >>> t = np.arange(n) * T # time stamps >>> a_x = gaussian(len(t), 0.4/T) # instantaneous amplitude >>> phi_x = 30*np.pi*t + 35*np.cos(2*np.pi*0.25*t) # instantaneous phase >>> x_carrier = a_x * np.cos(phi_x) >>> x_drift = 0.3 * gaussian(len(t), 0.4/T) # drift >>> x = x_carrier + x_drift ... >>> bp_in = (int(4 * (n*T)), None) # 4 Hz highpass input filter >>> x_env, x_res = envelope(x, bp_in, n_out=n_out) >>> t_out = np.arange(n_out) * (n / n_out) * T ... >>> fg0, ax0 = plt.subplots(1, 1, tight_layout=True) >>> ax0.set_title(r"$4\,$Hz Highpass Envelope of Drifting Signal") >>> ax0.set(xlabel="Time in seconds", xlim=(0, n*T), ylabel="Amplitude") >>> ax0.plot(t, x, 'C0-', alpha=0.5, label="Signal") >>> ax0.plot(t, x_drift, 'C2--', alpha=0.25, label="Drift") >>> ax0.plot(t_out, x_res+x_env, 'C1.-', alpha=0.5, label="Envelope") >>> ax0.plot(t_out, x_res-x_env, 'C1.-', alpha=0.5, label=None) >>> ax0.grid(True) >>> ax0.legend() >>> plt.show()
Второй пример дает геометрическую интерпретацию огибающей для комплекснозначных сигналов: следующие два графика показывают комплекснозначный сигнал как синюю 3d-траекторию и огибающую как оранжевую круглую трубку с переменным диаметром, т.е., как \(|a(t)| \exp(j\rho(t))\), с \(\rho(t)\in[-\pi,\pi]\). Также проекция в 2d-плоскости действительных и мнимых координат траектории и трубки изображена. Каждая точка комплекснозначного сигнала касается поверхности трубки.
Левый график показывает аналитический сигнал, т.е. разность фаз между мнимой и действительной частями всегда составляет 90 градусов, что приводит к спиральной траектории. Видно, что в этом случае действительная часть также имеет ожидаемую огибающую, т.е. представляет абсолютное значение мгновенной амплитуды.
Правый график показывает действительную часть этого аналитического сигнала, интерпретируемого как комплекснозначный сигнал, т.е. имеющего нулевую мнимую часть. Там результирующая огибающая не такая гладкая, как в аналитическом случае, и мгновенная амплитуда в действительной плоскости не восстанавливается. Если
z_reбыл передан как вещественный сигнал, т.е. какz_re = z.realвместоz_re = z.real + 0j, результат был бы идентичен левому графику. Причина в том, что вещественные сигналы интерпретируются как вещественная часть комплексного аналитического сигнала.>>> import matplotlib.pyplot as plt >>> import numpy as np >>> from scipy.signal.windows import gaussian >>> from scipy.signal import envelope ... >>> n, T = 1000, 1/1000 # number of samples and sampling interval >>> t = np.arange(n) * T # time stamps for 1 s duration >>> f_c = 3 # Carrier frequency for signal >>> z = gaussian(len(t), 0.3/T) * np.exp(2j*np.pi*f_c*t) # analytic signal >>> z_re = z.real + 0j # complex signal with zero imaginary part ... >>> e_a, e_r = (envelope(z_, (None, None), residual=None) for z_ in (z, z_re)) ... >>> # Generate grids to visualize envelopes as 2d and 3d surfaces: >>> E2d_t, E2_amp = np.meshgrid(t, [-1, 1]) >>> E2d_1 = np.ones_like(E2_amp) >>> E3d_t, E3d_phi = np.meshgrid(t, np.linspace(-np.pi, np.pi, 300)) >>> ma = 1.8 # maximum axis values in real and imaginary direction ... >>> fg0 = plt.figure(figsize=(6.2, 4.)) >>> ax00 = fg0.add_subplot(1, 2, 1, projection='3d') >>> ax01 = fg0.add_subplot(1, 2, 2, projection='3d', sharex=ax00, ... sharey=ax00, sharez=ax00) >>> ax00.set_title("Analytic Signal") >>> ax00.set(xlim=(0, 1), ylim=(-ma, ma), zlim=(-ma, ma)) >>> ax01.set_title("Real-valued Signal") >>> for z_, e_, ax_ in zip((z, z.real), (e_a, e_r), (ax00, ax01)): ... ax_.set(xlabel="Time $t$", ylabel="Real Amp. $x(t)$", ... zlabel="Imag. Amp. $y(t)$") ... ax_.plot(t, z_.real, 'C0-', zs=-ma, zdir='z', alpha=0.5, label="Real") ... ax_.plot_surface(E2d_t, e_*E2_amp, -ma*E2d_1, color='C1', alpha=0.25) ... ax_.plot(t, z_.imag, 'C0-', zs=+ma, zdir='y', alpha=0.5, label="Imag.") ... ax_.plot_surface(E2d_t, ma*E2d_1, e_*E2_amp, color='C1', alpha=0.25) ... ax_.plot(t, z_.real, z_.imag, 'C0-', label="Signal") ... ax_.plot_surface(E3d_t, e_*np.cos(E3d_phi), e_*np.sin(E3d_phi), ... color='C1', alpha=0.5, shade=True, label="Envelope") ... ax_.view_init(elev=22.7, azim=-114.3) >>> fg0.subplots_adjust(left=0.08, right=0.97, wspace=0.15) >>> plt.show()