程式設計師奶爸用樹莓派製作嬰兒監護儀:哭聲自動通知,還能分析何時喂奶

2020-11-16     AI科技大本營

原標題:程式設計師奶爸用樹莓派製作嬰兒監護儀:哭聲自動通知,還能分析何時喂奶

作者 | Fabio Manganiello

譯者 | 彎月,責編 | 楊碧玉

頭圖 | CSDN 下載自東方 IC

來源 | CSDN(ID:CSDNnews)

首先,告訴大家一個好消息,我當爸爸了!我不得不暫停一下我的項目來承擔一些育兒的重任。

我就在想,這些育兒任務可以自動化嗎?我們真的可以將給孩子換尿布的任務交給一個機器人嗎?我想我們距離那一天還很遙遙。想一想父母的心得多大才能在自己孩子身上測試這類的設備,但如果能夠通過自動化提供一定的幫助的話,也能減輕父母的重擔。

初為人父,我領悟到的第一件事就是嬰兒真的很愛哭,而且即便我在家,也無法保證時刻能夠聽到孩子寶寶的哭聲。通常我們都會使用嬰兒監護儀,這就相當於對講機,即使你在另一個房間也可以聽到寶寶的聲音。

然而,很快我就意識到商用嬰兒監護儀非常笨,比我想要的理想設備差得很遠。嬰兒監護儀無法檢測到寶寶的哭聲,它們就像對講機一樣,只是簡單地將聲音從信號源傳遞到揚聲器。

而且,由於這些監護儀無法將聲音播放到其他音頻設備上,因此父母必須將揚聲器帶到不同的房間。通常,嬰兒監護儀都帶有低功率的揚聲器,而且通常無法連接到外部揚聲器上。

這意味著,如果我在揚聲器所在的房間裡播放音樂,而我本人即使與監護儀在同一個房間內,也聽不到寶寶的哭聲。此外,大多數監護儀使用的都是低功率無線電波,這意味著,如果寶寶在自己房間裡,而你需要去地下室的時候,監護儀就無法正常工作了。

因此,我構思了一份智能嬰兒監護儀的需求:

  • 能夠在任何帶有廉價USB麥克風的廉價設備上運行,比如樹莓派等。
  • 能夠檢測到嬰兒的哭聲,並在寶寶開始哭或停止哭泣的時候通知我(最好通過手機);或者在寶寶哭的時候在儀錶板上顯示跟蹤的數據點;或者執行任何我希望執行的任務。不僅僅是充當啞巴對講機,將聲音從源頭傳遞到唯一的兼容設備上。
  • 能夠在任何設備上傳輸音頻流,我自己的揚聲器、我的智慧型手機、我的計算機等。
  • 無論聲源與揚聲器之間的距離是多少,都能夠正常工作,我不需要在房屋內移動揚聲器。
  • 配有攝像頭,這樣我就可以實時檢查寶寶的狀況;或者在寶寶哭的時候,我可以看到嬰兒床的圖片或簡短的錄像,以檢查一切是否正常。

下面,我們來看看如何使用我們最喜歡的開源工具來完成這項工作。

錄製一些音頻樣本

首先,入手一款樹莓派,在SD卡上安裝任何一款兼容的Linux作業系統。推薦使用樹莓派3或更高版本來運行Tensorflow模型。此外,還需要入手一個兼容的USB麥克風,任何型號都可以。

然後,安裝我們需要的依賴項:

[sudo]apt-get install ffmpeg lame libatlas-base-dev alsa-utils[sudo]pip3 install tensorflow

第一步,我們必須記錄足夠的音頻樣本,記錄寶寶什麼時候哭,什麼時候不哭,稍後我們需要這些樣本來訓練音頻檢測模型。注意,在此示例中,我將展示如何使用聲音檢測來識別嬰兒的哭聲,你也可以使用相同的方法來檢測任何類型的聲音,只要聲音足夠長即可(例如警報聲或鄰居的聲音),而且聲源必須比背景噪音更大。

首先,看一下音頻輸入設備:

arecord-l

我的樹莓派獲得了以下輸出(注意我有兩個USB麥克風):

****List of CAPTURE Hardware Devices ****card1: Device [USB PnP Sound Device], device 0: USB Audio [USB Audio]Subdevices: 0/1Subdevice #0: subdevice #0card2: Device_1 [USB PnP Sound Device], device 0: USB Audio [USB Audio]Subdevices: 0/1Subdevice #0: subdevice #0

我想使用第二個麥克風來記錄聲音,也就是card 2,device 0。ALSA識別它的方式是hw:2,0(直接訪問硬體設備)或plughw:2,0(根據需要推斷採樣率和格式轉換插件)。確保你的SD卡上有足夠的空間,或者你也可以使用外部USB驅動器,然後開始錄製一些音頻:

arecord-D plughw:2,0 -c 1 -f cd | lame - audio.mp3

在你寶寶的房間中,錄製幾分鐘或幾小時的音頻(最好分別錄製很長一段時間的安靜、寶寶哭泣和其他不相關的聲音),完成後按Ctrl-C結束。你可以根據需要多重複幾次這個過程,獲取一天中不同時刻或不同天的音頻樣本。

標記音頻樣本

在收集到足夠的音頻樣本後,我們需要將它們複製到計算機上,用於訓練模型,你可以使用scp複製這些文件,也可以直接從SD卡和USB驅動器上複製。

我們把這些文件保存在同一個目錄下,比如~/datasets/sound-detect/audio。此外,我們還需要為每個樣本新建一個文件夾。每個文件夾都包含一個音頻文件(名為audio.mp3)和一個標籤文件(名為labels.json),稍後我們將使用標籤文件來標記音頻文件,比如「positive」/「negative」音頻片段。原始資料庫的結構大致如下:

~/datasets/sound-detect/audio-> sample_1-> audio.mp3-> labels.json-> sample_2-> audio.mp3-> labels.json...

下面是比較無聊的部分:標記我們錄製的音頻文件。這些文件中包含數個小時的你家寶寶的哭聲,可能有點自虐的傾向。在你喜歡的音頻播放器或Audacity中打開每個數據集的音頻文件,並在每個樣本的目錄中新建一個labels.json文件。確定哭聲開始和結束的確切時間,並以time_string -> label的形式將它們的鍵值結構寫入labels.json。例如:

{"00:00": "negative","02:13": "positive","04:57": "negative","15:41": "positive","18:24": "negative"}

在上面的示例中,00:00-02:12之間的所有音頻片段都被標記為negative,而02:13-04:56之間的所有音頻片段都被標記為positive,以此類推。

生成數據集

在標記完所有音頻樣本後,接下來我們來生成需要傳遞給Tensorflow模型的數據集。我創建了一個通用庫和一套名為micmon的聲音監控實用程序。首先我們來安裝這個庫:

gitclone [email protected]:/BlackLight/micmon.gitcdmicmon[sudo]pip3 install -r requirements.txt[sudo]python3 setup.py build install

這個模型的設計初衷是處理頻率樣本,而不是原始的音頻。原因是,特定的聲音具有特定的「頻譜」特徵,即基準頻率(或者基準頻率通常所在的狹窄範圍)以及基準頻率按照一定比例構成的一組特定諧頻。而且,這些頻率之間的比率既不受振幅的影響(無論輸入音量如何,頻率的比率都是恆定的),也不受相位的影響(連續的聲音具有相同的頻譜特徵,無論何時開始記錄)。與我們將原始音頻樣本直接發送到模型相比,這種振幅和不隨時間變化的特性可以確保這種方法訓練出健壯的聲音檢測模型的機率更大。此外,該模型也更簡單(我們可以簡單地將頻率分組,同時還不會影響性能,如此一來就可以有效地降低維度),更輕量級(無論樣本持續時長多少,該模型都擁有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

在上面的示例中,我們根據存儲在~/dataset/sound-detect/audio下的原始音頻樣本生成數據集,並將生成的頻譜數據存儲到~/datasets/sound-detect/data。--low和--high分別表示生成的頻譜中的最低頻率和最高頻率。默認值分別為20Hz(人耳可以聽到的最低頻率)和20kHz(人耳可以聽到且無害的最高頻率)。但是,最好再進一步限制這個範圍,以儘可能捕獲我們想要檢測的聲音,並儘可能去除任何其他類型的音頻背景和不相關的諧波。我發現250-2500Hz的範圍足以檢測嬰兒的哭聲。嬰兒哭聲通常音調很高(考慮到歌劇女高音可以達到的最高音調約為1000Hz),通常我們需要將最高頻率加倍以確保獲得足夠高的諧波(諧波是較高的頻率,可以產生音色),但是也不需要太高,以免其他背景聲音的諧波污染頻譜。此外,我還可以去掉250Hz以下的任何聲音,嬰兒的啼哭聲不太可能發生在這些低頻上,將它們包含在內可能會導致檢測偏斜。有一個好方法是,用Audacity等均衡器/頻譜分析儀打開一些positive音頻樣本,檢查哪些頻率在positive樣本中占主導地位,並將數據集中在這些頻率附近。--bins指定了頻率空間的組數(默認值:100)。bin的數量越多,意味著頻率解析度(粒度)越也越高,但如果過高,則可能導致模型過度擬合。

該腳本將原始音頻分割為較小的片段,並計算出了每個片段的頻譜「簽名」。--sample-duration指定了每個片段應為多長時間(默認值:2秒)。這個值越大,則持續時間越長的聲音效果越好,但是會縮短檢測時間,並且可能在聲音很短時失敗。這個值越小,則持續時間越短的聲音效果越好,但是如果聲音較長,則捕獲的片段可能沒有足夠的信息來可靠地識別聲音。

除了micmon-datagen腳本之外,你也可以製作自己的腳本,通過我提供的micmonAPI生成數據集。例如:

importos

frommicmon.audio import AudioDirectory, AudioPlayer, AudioFilefrommicmon.dataset import DatasetWriter

basedir= 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.foraudio_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

無論你使用micmon-datagen還是micmon Python API,這一步結束後,你應該能在~/datasets/sound-detect/data找到一堆.npz文件,原始數據集中的每個標記音頻文件都有一個。我們可以使用該數據集來訓練聲音檢測的神經網絡。

訓練模型

micmon使用Tensorflow+ Keras定義和訓練模型。我們可以使用提供的Python API輕鬆完成此操作。例如:

importosfromtensorflow.keras import layers

frommicmon.dataset import Datasetfrommicmon.model import Model

#This is a directory that contains the saved .npz dataset filesdatasets_dir= os.path.expanduser('~/datasets/sound-detect/data')

#This is the output directory where the model will be savedmodel_dir= os.path.expanduser('~/models/sound-detect')

#This is the number of training epochs for each dataset sampleepochs= 2

#Load the datasets from the compressed files.#70% of the data points will be included in the training set,#30% of the data points will be included in the evaluation set#and used to evaluate the performance of the model.datasets= Dataset.scan(datasets_dir, validation_split=0.3)labels= ['negative', 'positive']freq_bins= len(datasets[0].samples[0])

#Create a network with 4 layers (one input layer, two intermediate layers andone output layer).#The first intermediate layer in this example will have twice the number ofunits as the number#of input units, while the second intermediate layer will have 75% of the numberof#input units. We also specify the names for the labels and the low and high frequencyrange#used when sampling.model= Model([layers.Input(shape=(freq_bins,)),layers.Dense(int(2 * freq_bins),activation='relu'),layers.Dense(int(0.75 * freq_bins),activation='relu'),layers.Dense(len(labels),activation='softmax'),],labels=labels,low_freq=datasets[0].low_freq,high_freq=datasets[0].high_freq)

#Train the modelforepoch in range(epochs):for i, dataset in enumerate(datasets):print(f'[epoch {epoch+1}/{epochs}][audio sample {i+1}/{len(datasets)}]')model.fit(dataset)evaluation = model.evaluate(dataset)print(f'Validation set loss andaccuracy: {evaluation}')

#Save the modelmodel.save(model_dir,overwrite=True)

在運行該腳本後(對模型的準確性感到滿意之後),你會發現新模型保存在~/models/sound-detect。就我的情況而言,從嬰兒的房間中收集約5個小時的聲音就足夠了,然後再定義一個好的頻率範圍,將模型的準確度訓練到>98%,就萬事俱備了。如果你在計算機上訓練了該模型,則只需將其複製到樹莓派,然後就可以開始下一步了。

使用模型進行預測

下面我們來製作一個腳本,使用麥克風收集到的實時音頻數據來運行前面訓練好的模型,並在寶寶啼哭時通知我們:

importos

frommicmon.audio import AudioDevicefrommicmon.model import Model

model_dir= os.path.expanduser('~/models/sound-detect')model= Model.load(model_dir)audio_system= 'alsa' # Supported: alsa andpulseaudio_device= 'plughw:2,0' # Get list of recognizedinput devices with arecord -l

withAudioDevice(audio_system, device=audio_device) as source:for sample in source:source.pause # Pause recording while we process the frameprediction = model.predict(sample)print(prediction)source.resume #Resume recording

在樹莓派上運行這個腳本,並運行一會兒,如果在過去2秒鐘內未檢測到哭聲,則輸出negative,否則輸出positive。

但是,我們努力了半天,結果只是在寶寶哭的時候,在標準輸出中列印消息也沒有太大用處,如果能收到通知就好了!我們可以使用Platypush來實現這部分功能。在這個示例中,當檢測到哭泣時,我們將使用Pushbullet集成向手機發送消息。下面,我們來安裝帶有HTTP和Pushbullet集成的Redis(用於Platypush接收消息)和Platypush:

[sudo]apt-get install redis-server[sudo]systemctl start redis-server.service[sudo]systemctl enable redis-server.service[sudo]pip3 install 'platypush[http,pushbullet]'

在智慧型手機上安裝Pushbullet應用,然後去pushbullet.com獲取API令牌。然後再創建一個~/.config/platypush/config.yaml文件,以啟用HTTP和Pushbullet集成:

backend.http:enabled: Truepushbullet:token: YOUR_TOKEN

下面,我們來修改前面的腳本,將在標準輸出列印消息的部分改成觸發Platypush可以捕捉到的CustomEvent:

#!/usr/bin/python3

importargparseimportloggingimportosimportsys

fromplatypush import RedisBusfromplatypush.message.event.custom import CustomEvent

frommicmon.audio import AudioDevicefrommicmon.model import Model

logger= logging.getLogger('micmon')

defget_args:parser = argparse.ArgumentParserparser.add_argument('model_path',help='Path to the file/directory containing the saved Tensorflow model')parser.add_argument('-i', help='Input sounddevice (e.g. hw:0,1 or default)', required=True, dest='sound_device')parser.add_argument('-e', help='Name of theevent that should be raised when a positive event occurs', required=True,dest='event_type')parser.add_argument('-s', '--sound-server',help='Sound server to be used (available: alsa, pulse)', required=False,default='alsa', dest='sound_server')parser.add_argument('-P','--positive-label', help='Model output label name/index to indicate a positivesample (default: positive)', required=False, default='positive',dest='positive_label')parser.add_argument('-N','--negative-label', help='Model output label name/index to indicate a negativesample (default: negative)', required=False, default='negative',dest='negative_label')parser.add_argument('-l','--sample-duration', help='Length of the FFT audio samples (default: 2seconds)', required=False, type=float, default=2., dest='sample_duration')parser.add_argument('-r', '--sample-rate',help='Sample rate (default: 44100 Hz)', required=False, type=int,default=44100, dest='sample_rate')parser.add_argument('-c', '--channels',help='Number of audio recording channels (default: 1)', required=False,type=int, default=1, dest='channels')parser.add_argument('-f', '--ffmpeg-bin',help='FFmpeg executable path (default: ffmpeg)', required=False,default='ffmpeg', dest='ffmpeg_bin')parser.add_argument('-v', '--verbose',help='Verbose/debug mode', required=False, action='store_true', dest='debug')parser.add_argument('-w','--window-duration', help='Duration of the look-back window (default: 10seconds)', required=False, type=float, default=10., dest='window_length')parser.add_argument('-n','--positive-samples', help='Number of positive samples detected over the windowduration to trigger the event (default: 1)', required=False, type=int,default=1, dest='positive_samples')

opts, args =parser.parse_known_args(sys.argv[1:])return opts

defmain:args = get_argsif args.debug:logger.setLevel(logging.DEBUG)

model_dir =os.path.abspath(os.path.expanduser(args.model_path))model = Model.load(model_dir)window = []cur_prediction = args.negative_labelbus = RedisBus

with AudioDevice(system=args.sound_server,device=args.sound_device,sample_duration=args.sample_duration,sample_rate=args.sample_rate,channels=args.channels,ffmpeg_bin=args.ffmpeg_bin,debug=args.debug) assource:for sample in source:source.pause # Pause recording while we process the frameprediction = model.predict(sample)logger.debug(f'Sample prediction:{prediction}')has_change = False

if len(window) < args.window_length:window += [prediction]else:window = window[1:] +[prediction]

positive_samples = len([pred forpred in window if pred == args.positive_label])if args.positive_samples <=positive_samples and prediction ==args.positive_label and cur_prediction !=args.positive_label:cur_prediction =args.positive_labelhas_change = Truelogging.info(f'Positive samplethreshold detected ({positive_samples}/{len(window)})')elif args.positive_samples >positive_samples and prediction ==args.negative_label and cur_prediction !=args.negative_label:cur_prediction = args.negative_labelhas_change = Truelogging.info(f'Negative samplethreshold detected ({len(window)-positive_samples}/{len(window)})')

if has_change:evt = CustomEvent(subtype=args.event_type,state=prediction)bus.post(evt)

source.resume # Resume recording

if__name__ == '__main__':main

將上述腳本保存成~/bin/micmon_detect.py。這個腳本只會在window_length秒的滑動窗口中檢測到至少positive_samples個樣本時才觸發事件(這是為了減少預測錯誤或臨時故障引發的噪聲),並且僅在當前預測從negative變為positive時才觸發事件,反之亦然。接下來,通過RedisBus將該事件發送到Platypush。這個腳本的通用性非常好,可以處理任何聲音模型(不一定是檢測嬰兒啼哭的聲音模型)、任何positive/negative標籤、任何頻率範圍以及任何類型的輸出事件。

下面,我們來創建一個Platypush鉤子,對事件做出反應並將通知發送到我們的設備上。首先,準備尚未創建的Platypush腳本目錄:

mkdir-p ~/.config/platypush/scd~/.config/platypush/s#Define the directory as a moduletouch__init__.py#Create a for the baby-cry eventsvibabymonitor.py

babymonitor.py的內容如下:

fromplatypush.context import get_pluginfromplatypush.event.hook import hookfromplatypush.message.event.custom import CustomEvent@hook(CustomEvent,subtype='baby-cry', state='positive')defon_baby_cry_start(event, **_):pb = get_plugin('pushbullet')pb.send_note(title='Baby cry status',body='The baby is crying!')@hook(CustomEvent,subtype='baby-cry', state='negative')defon_baby_cry_stop(event, **_):pb = get_plugin('pushbullet')pb.send_note(title='Baby cry status',body='The baby stopped crying - good job!')

接下來,創建一個Platypush的服務文件(如果尚不存在的話),然後啟動服務,這樣它會在意外退出或系統重啟時自動啟動:

mkdir-p ~/.config/systemd/userwget-O ~/.config/systemd/user/platypush.service https://raw.githubusercontent.com/BlackLight/platypush/master/examples/systemd/platypush.servicesystemctl--user start platypush.servicesystemctl--user enable platypush.service

另外,還需要為嬰兒監護儀創建一個服務,例如:

~/.config/systemd/user/babymonitor.service

內容如下:

[Unit]Deion=Monitorto detect my baby's criesAfter=network.targetsound.target[Service]ExecStart=/home/pi/bin/micmon_detect.py-i plughw:2,0 -e baby-cry -w 10 -n 2 ~/models/sound-detectRestart=alwaysRestartSec=10[Install]WantedBy=default.target

這個服務會啟動ALSA設備plughw:2,0上的麥克風監視儀。如果在過去10秒鐘內檢測到至少2個2秒的positive樣本,且前一個狀態為negative,則該服務會觸發事件baby-cry,並設置state=positive;如果在過去10秒鐘內檢測到的positive樣本少於兩個,且前一個狀態為positive,則設置state=negative。我們可以通過以下方法啟動該服務:

systemctl--user start babymonitor.servicesystemctl--user enable babymonitor.service

檢查在寶寶開始哭泣時,是否會收到手機通知。如果沒有收到通知,則可以查看音頻樣本的標籤、神經網絡的結構和參數、或樣本長度/窗口/頻帶等參數。

另外,這是一個相對比較基本的自動化示例,你可以在此基礎之上實驗各種自動化任務。例如,你可以使用tts插件向另一個Platypush設備(例如臥室或客廳)發送請求,大聲呼喊:「寶寶哭啦」。你還可以擴展micmon_detect.py腳本,以便捕獲的音頻樣本可以通過HTTP流傳輸,使用Flask打包器和ffmpeg進行音頻轉換。

還有一個有趣的實驗,在嬰兒開始/停止哭泣時,將數據點發送到本地資料庫。這是一組非常有意義的數據,你可以通過這些數據來記錄寶寶何時入睡,何時醒著,何時需要喂奶。監護寶寶是我開發micmon的主要動機,你可以利用相同的過程來訓練和使用模型來檢測任何類型的聲音。最後,你還可以考慮使用優質的移動電源或鋰電池來構建移動版的聲音監視儀。

嬰兒攝像頭

在擁有良好的音頻輸入,並通過某種方法檢測到positive音頻序列何時開始/停止後,我們可以添加視頻輸入,以密切關注寶寶。在我的第一次嘗試中,我在檢測音頻的樹莓派上搭載了一台PiCamera,但我發現這種配置不切實際。如果你正在尋找一款輕量級的攝像頭,可以輕鬆安裝在某個底座或伸縮架上,你可以轉動攝像頭,隨時隨地關注你的寶寶,那麼你可以將樹莓派放在自己的外殼中,然後搭載一盒電池,並在頂部固定一個攝像頭就可以了,儘管看上去非常笨重。最終我選擇了一款帶有PiCamera兼容機殼和小型移動電源的小型樹莓派Zero。

圖:我的第一個帶有攝像頭的嬰兒監護儀原型

就像在其他設備上一樣,你可以將SD卡插入與樹莓派兼容的OS。然後將與樹莓派兼容的攝像頭插入插槽中,確保已在raspi-config中啟用攝像頭模塊,並通過PiCamera集成安裝Platypush:

[sudo]pip3 install 'platypush[http,camera,picamera]'

然後在~/.config/platypush/config.yaml中加入攝像頭的配置:

camera.pi:listen_port: 5001

你可以檢查Platypush重啟時的這個配置,並通過HTTP獲取攝像機的快照:

wgethttp://raspberry-pi:8008/camera/pi/photo.jpg

或者在瀏覽器中打開視頻:

http://raspberry-pi:8008/camera/pi/video.mjpg

你還可以創建一個鉤子,當應用程式啟動時,通過TCP/H264傳輸視頻流:

mkdir-p ~/.config/platypush/scd~/.config/platypush/stouch__init__.pyvicamera.py

camera.py的內容如下:

fromplatypush.context import get_pluginfromplatypush.event.hook import hookfromplatypush.message.event.application import ApplicationStartedEvent

@hook(ApplicationStartedEvent)defon_application_started(event, **_):cam = get_plugin('camera.pi')cam.start_streaming

你可以在VLC中播放視頻:

vlctcp/h264://raspberry-pi:5001

或通過VLC應用、RPi Camera Viewer等應用在手機上播放視頻。

原文:https://towardsdatascience.com/create-your-own-smart-baby-monitor-with-a-raspberrypi-and-tensorflow-5b25713410ca

本文為 CSDN 翻譯,轉載請註明來源出處。

文章來源: https://twgreatdaily.com/zh-tw/BMU31HUBxV5JH8q_dL6p.html










CSDN湘苗培優

2020-12-24