作为一名新晋奶爸和程序员,我在新身份中最常思考的问题就是 “照料婴儿的工作真的无法自动化吗?”
当然,这也许能够实现,就算有给孩子换尿布的机器人(假设有足够多的父母同意在自己蹒跚学步的孩子身上测试这样的设备),愿意自动化照料婴儿的父母还真为数不多。
作为父亲,我首先意识到的事情是:婴儿很多时候都会在哭,即使我在家,也不可能总是能听到孩子的哭声。
通常,商用婴儿监视器可以填补这一空白,它们充当对讲机,让你在另一个房间也能听到婴儿的哭声。
但我很快意识到:商用婴儿监视器没有我想象中的理想设备智能:
- 它们只能充当一个传声筒:把声音从源头带到扬声器,却无法发现孩子哭声的含义;
- 当家长要去到另一个房间里时,相应要把扬声器带到另一个房间,无法在任何其他现有的音频设备上播放声音;
- 扬声器通常是低功率扬声器,无法连接到外部扬声器 - 这意味着,如果我在另一个房间播放音乐,我可能会听不到孩子的哭声,即便监控器和我在同一个房间也无法听到;
- 大多数扬声器都是在低功率无线电波上工作的,这意味着如果婴儿在他 / 她的房间里,而你必须走到楼下,它们才能工作。
因此,我萌生了自制一个更好用的 “智能婴儿监视器”的想法。
说干就干,我先给这个 “智能婴儿监视器”定义了一些需要的功能。
- 它可以运行于价廉物美的树莓派(RaspberryPI)与 USB 麦克风。
- 当孩子开始 / 停止哭泣时,它应该检测到孩子的哭声,并通知我(理想情况下是在我的手机上),或者跟踪我仪表板上的数据点,或者运行相应的任务。它不应该是一个单纯的对讲器,简单地将声音从一个源传递到另一个兼容的设备。
- 它能够在扬声器,智能手机,电脑等设备上传输音频。
- 它不受源和扬声器之间距离的影响,无需在整个房子里将扬声器移来移去。
- 它还应该有一个摄像头,可以利用摄像头对孩子实时监控,当他一开始哭,我便可以抓拍到图片或婴儿床的短视频,以检查有什么不对劲。
来看看一个新晋奶爸如何使用工程师的大脑和开源工具来完成这项任务吧。
采集音频样本首先,购买一块树莓派(RaspberryPi),在 SD 卡上烧录好 Linux 操作系统(建议使用 RaspberryPI3 或更高版本),运行 Tensorflow 模型。还可以购买一个与树莓派兼容的 USB 麦克风。
然后安装需要的相关项:
[sudo] apt-get install ffmpeg lame libatlas-base-dev alsa-utils[sudo] pip3 install tensorflow[/pre]第一步,必须记录足够的音频样本,婴儿在什么时候哭,在什么时候不哭。稍后将利用这些样本来训练音频检测模型。
注意:在这个例子中,我将展示如何利用声音检测来识别婴儿的哭声,同样的精准程序可以用来检测任何其它类型的声音 - 只要它们足够长 (例如:警报或邻居家的钻孔声)。
首先,查看音频输入设备:
arecord -l[/pre]在树莓派(RaspberryPI)上,得到以下输出 (注意,有两个 USB 麦克风):
**** List of CAPTURE Hardware Devices ****card 1: Device [USB PnP Sound Device], device 0: USB Audio [USB Audio] Subdevices: 0/1 Subdevice #0: subdevice #0card 2: Device_1 [USB PnP Sound Device], device 0: USB Audio [USB Audio] Subdevices: 0/1 Subdevice #0: subdevice #0[/pre]我利用第二个麦克风来记录声音 - 即卡 2,设备 0。识别它的 ALSA 方法要么是 hw:2,0(直接访问硬件设备),要么是 plughw:2,0(如果需要的话,它会输入采样率和格式转换插件)。确保 SD 卡上有足够的空间,然后开始录制一些音频:
arecord -D plughw:2,0 -c 1 -f cd | lame - audio.mp3[/pre]和孩子在同一个房间里,记录几分钟或几个小时的音频 - 最好是长时间的沉默、婴儿哭声和其他与之无关的声音 -,录音完成后按 Ctrl-C。尽可能多的重复这个过程多次,在一天中的不同时刻或不同的日子里获取不同的音频样本。
标注音频示例一旦有了足够的音频样本,就可以把它们复制到电脑上来训练模型了 - 可以使用 SCP 复制文件,也可以直接从 SD 卡上复制。
把它们都存储在相同目录下,例如:~/datasets/sound-detect/audio。另外,为每个示例音频文件创建一个新文件夹,它包含一个音频文件 (名为 audio.mp3)和一个标注文件 (名为 labels.json),利用它来标记音频文件中的负 / 正音频段,原始数据集的结构如下:
~/datasets/sound-detect/audio
-> sample_1
-> audio.mp3
-> labels.json
-> sample_2
-> audio.mp3
-> labels.json
...[/pre]下面:标注录制的音频文件 - 如果它包含了孩子几个小时的哭声,可能会特别受虐。在你最喜欢的音频播放器或 Audacity 中打开每个数据集音频文件,并在每个示例目录中创建一个新的 label.json 文件。确定哭泣开始的确切时间和结束时间,并在 labels.json 中标注为 time_string -> label 的关键值结构。例:
{ "00:00": "negative", "02:13": "positive", "04:57": "negative", "15:41": "positive", "18:24": "negative" }[/pre]在上面的例子中,00:00 到 02:12 之间的所有音频段将被标记为负,02:13 到 04:56 之间的所有音频段将被标记为正,以此类推。
生成数据集对所有的音频示例标注完成之后,接下来是生成数据集,最后将它输入到 Tensorflow 模型中去。首先,创建了一个名为 micmon 的通用库和一组用于声音监视的实用工具。然后,开始安装:
git clone git@github.com:/BlackLight/micmon.gitcd micmon[sudo] pip3 install -r requirements.txt[sudo] python3 setup.py build install[/pre]本模型设计基于音频的频率样本而非原始音频,因为,在这里我们想检测到一个特定的声音,这个声音有着特定的 “频谱”标签,即:基频(或基频下降的窄带范围)和一组特定的谐波。这些谐波频率与基波之间的比率既不受振幅的影响(频率比恒定,与输入幅度无关),也不受相位的影响 (无论何时开始记录,连续的声音都会有相同的频谱特征)。
这种与振幅和相位无关的特性使得这种方法更有可能训练出一个鲁棒的声音检测模型,而不是简单地将原始音频样本馈送到模型中。此外,该模型可以更简单(可以在不影响性能的情况下将多个频率分为一组,从而可以有效地实现降维),无论样本持续时间多长,该模型将 50~ 100 个频带作为输入值,一秒钟的原始音频通常包含 44100 个数据点,并且输入的长度随着样本的持续时间而增加,并且不太容易发生过拟合。
micmon 能计算音频样本某些段的 FFT(快速傅里叶变换),将结果频谱分为低通和高通滤波器的频带,并将结果保存到一组 numpy 压缩 (.npz)文件中。可以通过在命令行上执行 micmon-datagen 命令来实现:
micmon-datagen \ --low 250 --high 2500 --bins 100 \ --sample-duration 2 --channels 1 \ ~/datasets/sound-detect/audio ~/datasets/sound-detect/data[/pre]在上面的示例中,我们从存储在~/dataset/sound-detect/audio 下的原始音频样本生成一个数据集,并将生成的频谱数据存储到~/datasets/sound-detect/data. –low 和~/datasets/sound-detect/data. --high 中,low 和 high 分别表示最低和最高频率,最低频率的默认值为 20Hz(人耳可闻的最低频率),最高频率的默认值为 20kHz(健康的年轻人耳可闻的最高频率)。
通过对此范围做出限定,尽可能多地捕获希望检测到的其他类型的音频背景和无关谐波的声音。在本案例中,250-2500 赫兹的范围足以检测婴儿的哭声。
婴儿的哭声通常是高频的(歌剧女高音能达到的最高音符在 1000 赫兹左右),在这里设置了至少双倍的最高频率,以确保能获得足够高的谐波 (谐波是更高的频率),但也不要将最高频率设得太高,以防止其他背景声音的谐波。我剪切掉了频率低于 250 赫兹的音频信号 - 婴儿的哭声不太可能发生在低频段,例如,可以打开一些 positive 音频样本,利用均衡器 / 频谱分析仪,检查哪些频率在 positive 样本中占主导地位,并将数据集集中在这些频率上。--bins 指定了频率空间的组数(默认值:100),更大的数值意味着更高的频率分辨率 / 粒度,但如果太高,可能会使模型容易发生过度拟合。
脚本将原始音频分割成较小的段,并计算每个段的频谱标签。示例持续时间指定每个音频段有多长时间(默认:2 秒)。对于持续时间较长的声音,取更大的值会起到更好的作用,但它同时会减少检测的时间,而且可能会在短音上失效。对于持续时间较短的声音,可以取较低的值,但捕获的片段可能没有足够的信息量来可靠地识别声音。
除了 micmon-datagen 脚本之外,也可以利用 micmonAPI,编写脚本来生成数据集。例:
import osfrom micmon.audio import AudioDirectory, AudioPlayer, AudioFile from micmon.dataset import DatasetWriterbasedir = os.path.expanduser('~/datasets/sound-detect')audio_dir = os.path.join(basedir, 'audio')datasets_dir = os.path.join(basedir, 'data')cutoff_frequencies = [250, 2500] # Scan the base audio_dir for labelled audio samplesaudio_dirs = AudioDirectory.scan(audio_dir) # Save the spectrum information and labels of the samples to a # different compressed file for each audio file. for audio_dir in audio_dirs: dataset_file = os.path.join(datasets_dir, os.path.basename(audio_dir.path) + '.npz') print(f'Processing audio sample {audio_dir.path}') with AudioFile(audio_dir) as reader, \ DatasetWriter(dataset_file, low_freq=cutoff_frequencies[0], high_freq=cutoff_frequencies[1]) as writer: for sample in reader: writer += sample[/pre]无论是使用 micmon-datagen 还是使用 micmon Python API 生成数据集,在过程结束时,应该在~/datasets/sound-detect/data 目录下找到一堆 . npz 文件,每个标注后的音频原始文件对应一个数据集。之后,便可以利用这个数据集来训练神经网络进行声音检测。