就上下文而言,该图代表我在整个视频中眼睛的 EAR(眼睛长宽比),清晰的陡峭下降代表眨眼。当我眨眼时,EAR 会迅速减小并再次增大,因为 EAR 会测量我眼睛的睁开程度。给定一维点数组,我如何检测代表我眨眼的局部最小值?在此示例中,它将检测所有 5 次眨眼。
我试过这个:
import numpy as np
from scipy.signal import find_peaks
# Invert the data to find local minima
inverted_data = -np.array(seq)
# Use find_peaks from SciPy to find the peaks in the inverted data
# Adjust the 'distance' parameter as needed for your dataset
peaks, _ = find_peaks(inverted_data, distance=5, height=-0.2)
print(peaks)
# The number of local minima is the length of the peaks array
number_of_minima = len(peaks)
print("Number of local minima:", number_of_minima)
但效果不是很好,因为虽然距离参数很有帮助,并且可能是一个好的开始,但高度参数代表了被视为眨眼所需的最小 EAR,并且这对于不同的眼睛来说是不同的。
使用一些虚假数据演示一个非常简单的带通滤波器;内嵌评论:
import matplotlib.pyplot as plt
import numpy as np
import scipy.signal
# https://en.wikipedia.org/wiki/Blinking#Adults
# Assuming adult, object focus. Delete all of these when you have the real sample rate.
blink_rate = 4/60
blink_sample_period = 35 # Eyeballed (!) from OP plot
samples = 220 # Eyeballed from OP plot
# Bogus; replace me with the real value
sample_rate = blink_rate * blink_sample_period
# Assume uniform time. Replace this if you know the actual sample times.
time = np.arange(0, samples/sample_rate, 1/sample_rate)
# Bogus, random "subsequence"
rand = np.random.default_rng(seed=0)
noise = rand.normal(scale=0.01, size=time.size)
blinks = 0.1*(0.5 + 0.5*np.cos(time*blink_rate * 2*np.pi + 0.5))**25
slope = 0.29 + (0.23 - 0.29)/time.size * time
seq = slope - blinks + noise
# Simple post-facto (not streamed) FFT bandpass filter
fft_input = np.fft.rfft(a=seq, norm='forward')
fft_freq = np.fft.rfftfreq(n=seq.size, d=1/sample_rate)
locut = 0.05 # Hz
hicut = 0.5 # Hz
fft_output = fft_input.copy()
fft_output[:round(locut*seq.size / sample_rate)] = 1e-5 # For log display. Just set these to 0
fft_output[round(hicut*seq.size / sample_rate):] = 1e-5
filtered = -np.fft.irfft(a=fft_output, norm='forward')
peaks, props = scipy.signal.find_peaks(
x=filtered,
prominence=0.5*filtered.max(),
distance=5, # samples
)
fig, (time_ax, freq_ax) = plt.subplots(ncols=2)
fig.suptitle('Eye aspect ratio blinking during video focus')
time_ax.plot(time, seq, label='input')
time_ax.plot(time, filtered, label='filtered')
time_ax.scatter(time[peaks], filtered[peaks], label='blink')
idx_ax = time_ax.secondary_xaxis('top', functions=(
lambda t: t * sample_rate,
lambda i: i / sample_rate,
))
idx_ax.set_xlabel('index')
time_ax.set_xlabel('time (s)')
time_ax.set_ylabel('Eye Aspect Ratio')
# time_ax.legend()
freq_ax.semilogy(fft_freq, np.abs(fft_input), label='input')
freq_ax.semilogy(fft_freq, np.abs(fft_output), label='output')
idx_ax = freq_ax.secondary_xaxis('top', functions=(
lambda f: f*seq.size / sample_rate,
lambda i: i/seq.size * sample_rate,
))
idx_ax.set_xlabel('index')
freq_ax.set_xlabel('freq (Hz)')
freq_ax.set_ylabel('FFT amplitude')
freq_ax.legend()
plt.show()