Hackathon OpenVINO: تشخیص صدا و احساسات در Raspberry Pi
30 نوامبر - 1 دسامبر در نیژنی نووگورود برگزار شد هکاتون OpenVINO. از شرکت کنندگان خواسته شد تا یک نمونه اولیه از یک راه حل محصول را با استفاده از جعبه ابزار OpenVINO اینتل ایجاد کنند. سازمان دهندگان لیستی از موضوعات تقریبی را پیشنهاد کردند که در هنگام انتخاب یک کار می توان آنها را راهنمایی کرد، اما تصمیم نهایی با تیم ها باقی ماند. علاوه بر این، استفاده از مدل هایی که در محصول وجود ندارد تشویق شد.
در این مقاله به شما خواهیم گفت که چگونه نمونه اولیه محصول خود را ایجاد کردیم که در نهایت با آن مقام اول را کسب کردیم.
بیش از 10 تیم در هکاتون شرکت کردند. خوب است که برخی از آنها از مناطق دیگر آمده اند. محل برگزاری هکاتون مجتمع "Kremlinsky on Pochain" بود، جایی که عکس های باستانی نیژنی نووگورود در داخل آن آویزان شده بود، همراه همراهان! (به شما یادآوری می کنم که در حال حاضر دفتر مرکزی اینتل در نیژنی نووگورود واقع شده است). به شرکت کنندگان 26 ساعت فرصت داده شد تا کد بنویسند و در پایان باید راه حل خود را ارائه می کردند. یک مزیت جداگانه وجود یک جلسه نمایشی برای اطمینان از اینکه همه چیز برنامه ریزی شده واقعاً اجرا شده است و ایده ها در ارائه باقی نمی مانند. کالا، تنقلات، غذا، همه چیز هم آنجا بود!
علاوه بر این، اینتل به صورت اختیاری دوربینهایی، Raspberry PI، Neural Compute Stick 2 را ارائه کرد.
انتخاب کار
یکی از سختترین بخشهای آمادهسازی برای یک هکاتون آزاد، انتخاب چالش است. ما بلافاصله تصمیم گرفتیم چیزی را ارائه کنیم که هنوز در محصول وجود نداشت، زیرا در اعلامیه گفته شد که بسیار مورد استقبال قرار گرفت.
با تجزیه و تحلیل مدل هاکه در نسخه فعلی در محصول گنجانده شده است، به این نتیجه می رسیم که اکثر آنها مشکلات مختلف بینایی کامپیوتری را حل می کنند. علاوه بر این، پیدا کردن مشکلی در زمینه بینایی کامپیوتری که با استفاده از OpenVINO قابل حل نباشد، بسیار دشوار است، و حتی اگر بتوان آن را اختراع کرد، یافتن مدل های از پیش آموزش دیده در حوزه عمومی دشوار است. ما تصمیم می گیریم در جهت دیگری حفاری کنیم - به سمت پردازش گفتار و تجزیه و تحلیل. بیایید کار جالبی را برای تشخیص احساسات از گفتار در نظر بگیریم. باید گفت که OpenVINO قبلاً مدلی دارد که احساسات افراد را بر اساس چهره آنها تعیین می کند، اما:
در تئوری، می توان یک الگوریتم ترکیبی ایجاد کرد که هم روی صدا و هم روی تصویر کار کند، که باید دقت را افزایش دهد.
دوربین ها معمولاً زاویه دید باریکی دارند؛ برای پوشش یک منطقه بزرگ به بیش از یک دوربین نیاز است؛ صدا چنین محدودیتی ندارد.
بیایید این ایده را توسعه دهیم: بیایید ایده بخش خرده فروشی را به عنوان پایه در نظر بگیریم. می توانید رضایت مشتری را در صندوق های فروشگاه اندازه گیری کنید. اگر یکی از مشتریان از خدمات ناراضی بود و شروع به بالا بردن صدای خود کرد، می توانید بلافاصله با مدیر تماس بگیرید و کمک بگیرید.
در این مورد، ما باید تشخیص صدای انسانی را اضافه کنیم، این به ما امکان می دهد کارمندان فروشگاه را از مشتریان متمایز کنیم و برای هر فرد تجزیه و تحلیل ارائه کنیم. خوب، علاوه بر این، تجزیه و تحلیل رفتار کارمندان فروشگاه، ارزیابی جو در تیم، خوب به نظر می رسد!
ما الزامات راه حل خود را فرموله می کنیم:
اندازه کوچک دستگاه مورد نظر
عملیات زمان واقعی
قیمت پایین
مقیاس پذیری آسان
در نتیجه Raspberry Pi 3 c را به عنوان دستگاه مورد نظر انتخاب می کنیم اینتل NCS 2.
در اینجا مهم است که به یکی از ویژگی های مهم NCS توجه کنیم - این بهترین عملکرد را با معماری های استاندارد CNN دارد، اما اگر نیاز به اجرای یک مدل با لایه های سفارشی روی آن دارید، انتظار بهینه سازی سطح پایین را داشته باشید.
فقط یک کار کوچک وجود دارد: باید یک میکروفون تهیه کنید. یک میکروفون USB معمولی کار خواهد کرد، اما همراه با RPI خوب به نظر نمی رسد. اما حتی در اینجا نیز راه حل به معنای واقعی کلمه "در این نزدیکی است". برای ضبط صدا، تصمیم می گیریم از برد Voice Bonnet از کیت استفاده کنیم کیت صوتی Google AIY، که روی آن یک میکروفون استریو سیمی وجود دارد.
Raspbian را دانلود کنید مخزن پروژه های AIY و آن را در فلش درایو آپلود کنید، با استفاده از دستور زیر تست کنید که میکروفون کار می کند (صدا را به مدت 5 ثانیه ضبط می کند و آن را در یک فایل ذخیره می کند):
arecord -d 5 -r 16000 test.wav
بلافاصله باید توجه داشته باشم که میکروفون بسیار حساس است و نویز را به خوبی دریافت می کند. برای رفع این مشکل، بیایید به alsamixer برویم، Capture devices را انتخاب کرده و سطح سیگنال ورودی را به 50-60٪ کاهش دهیم.
بدنه را با فایل اصلاح می کنیم و همه چیز جا می شود، حتی می توانید آن را با درب ببندید
اضافه کردن دکمه نشانگر
هنگام جدا کردن کیت صوتی AIY، به یاد می آوریم که یک دکمه RGB وجود دارد که نور پس زمینه آن توسط نرم افزار قابل کنترل است. ما "Google AIY Led" را جستجو می کنیم و اسناد را پیدا می کنیم: https://aiyprojects.readthedocs.io/en/latest/aiy.leds.html
چرا از این دکمه برای نمایش احساسات شناسایی شده استفاده نکنید، ما فقط 7 کلاس داریم و دکمه دارای 8 رنگ است، فقط کافی است!
ما دکمه را از طریق GPIO به Voice Bonnet وصل می کنیم، کتابخانه های لازم را بارگیری می کنیم (آنها قبلاً در کیت توزیع پروژه های AIY نصب شده اند)
from aiy.leds import Leds, Color
from aiy.leds import RgbLeds
بیایید دیکتی بسازیم که در آن هر احساس یک رنگ متناظر به شکل یک RGB Tuple و یک شی از کلاس aiy.leds.Leds داشته باشد که از طریق آن رنگ را به روز می کنیم:
ما از pyaudio برای ضبط جریان از میکروفون و webrtcvad برای فیلتر کردن نویز و تشخیص صدا استفاده خواهیم کرد. علاوه بر این، یک صف ایجاد می کنیم که به طور ناهمزمان گزیده های صوتی را اضافه و حذف می کنیم.
از آنجایی که webrtcvad محدودیتی در اندازه قطعه ارائه شده دارد - باید برابر با 10/20/30 میلیثانیه باشد و آموزش مدل برای تشخیص احساسات (همانطور که بعداً خواهیم آموخت) روی یک مجموعه داده 48 کیلوهرتز انجام شده است. تکه هایی با اندازه 48000×20ms/1000×1(مونو)=960 بایت را ضبط کنید. Webrtcvad برای هر یک از این تکه ها True/False را برمی گرداند که مربوط به وجود یا عدم وجود رای در قطعه است.
بیایید منطق زیر را پیاده سازی کنیم:
تکه هایی را که در آن رای وجود دارد را به لیست اضافه می کنیم؛ اگر رای نداشته باشد، شمارنده تکه های خالی را افزایش می دهیم.
اگر شمارنده تکههای خالی >=30 (600 میلیثانیه) باشد، به اندازه فهرست تکههای انباشتهشده نگاه میکنیم؛ اگر بیشتر از 250 باشد، آن را به صف اضافه میکنیم، اگر نه، طول را در نظر میگیریم. رکورد برای تغذیه مدل برای شناسایی بلندگو کافی نیست.
اگر شمارنده تکههای خالی هنوز کمتر از 30 باشد، و اندازه فهرست تکههای انباشته شده از 300 بیشتر باشد، برای پیشبینی دقیقتر، قطعه را به صف اضافه میکنیم. (زیرا احساسات در طول زمان تغییر می کنند)
def to_queue(frames):
d = np.frombuffer(b''.join(frames), dtype=np.int16)
return d
framesQueue = queue.Queue()
def framesThreadBody():
CHUNK = 960
FORMAT = pyaudio.paInt16
CHANNELS = 1
RATE = 48000
p = pyaudio.PyAudio()
vad = webrtcvad.Vad()
vad.set_mode(2)
stream = p.open(format=FORMAT,
channels=CHANNELS,
rate=RATE,
input=True,
frames_per_buffer=CHUNK)
false_counter = 0
audio_frame = []
while process:
data = stream.read(CHUNK)
if not vad.is_speech(data, RATE):
false_counter += 1
if false_counter >= 30:
if len(audio_frame) > 250:
framesQueue.put(to_queue(audio_frame,timestamp_start))
audio_frame = []
false_counter = 0
if vad.is_speech(data, RATE):
false_counter = 0
audio_frame.append(data)
if len(audio_frame) > 300:
framesQueue.put(to_queue(audio_frame,timestamp_start))
audio_frame = []
وقت آن است که به دنبال مدل های از پیش آموزش دیده در حوزه عمومی بگردید، به github، گوگل بروید، اما به یاد داشته باشید که ما در معماری استفاده شده محدودیت داریم. این بخش نسبتاً دشواری است، زیرا شما باید مدل ها را روی داده های ورودی خود آزمایش کنید و علاوه بر این، آنها را به فرمت داخلی OpenVINO - IR (نمایندگی متوسط) تبدیل کنید. ما حدود 5-7 راه حل مختلف را از github امتحان کردیم، و اگر مدل تشخیص احساسات بلافاصله کار می کرد، پس با تشخیص صدا باید بیشتر منتظر بمانیم - آنها از معماری های پیچیده تری استفاده می کنند.
ما روی موارد زیر تمرکز می کنیم:
احساسات از صدا - https://github.com/alexmuhr/Voice_Emotion
طبق اصل زیر کار می کند: صدا به قسمت هایی با اندازه مشخص بریده می شود، برای هر یک از این قسمت ها ما انتخاب می کنیم MFCC و سپس آنها را به عنوان ورودی به CNN ارسال کنید
در ادامه در مورد تبدیل مدل ها صحبت خواهیم کرد و از تئوری شروع می کنیم. OpenVINO شامل چندین ماژول است:
باغ وحش مدل باز کنید، مدلهایی که میتوان از آنها استفاده کرد و در محصول شما گنجانید
Model Optimzer، به لطف آن می توانید یک مدل را از فرمت های فریمورک مختلف (Tensorflow، ONNX و غیره) به فرمت نمایش متوسط تبدیل کنید، که با آن بیشتر کار خواهیم کرد.
Inference Engine به شما امکان می دهد مدل ها را با فرمت IR روی پردازنده های اینتل، تراشه های بی شمار و شتاب دهنده های Neural Compute Stick اجرا کنید.
کارآمدترین نسخه OpenCV (با پشتیبانی Inference Engine)
هر مدل با فرمت IR توسط دو فایل توضیح داده می شود: xml. و .bin.
مدل ها به صورت زیر از طریق Model Optimizer به فرمت IR تبدیل می شوند:
--data_type به شما امکان می دهد قالب داده ای را که مدل با آن کار می کند انتخاب کنید. FP32، FP16، INT8 پشتیبانی می شوند. انتخاب نوع داده بهینه می تواند عملکرد خوبی را افزایش دهد. --input_shape ابعاد داده های ورودی را نشان می دهد. به نظر میرسد توانایی تغییر پویا در C++ API وجود دارد، اما ما آنقدرها را بررسی نکردیم و به سادگی آن را برای یکی از مدلها برطرف کردیم.
در مرحله بعد، بیایید سعی کنیم مدل از قبل تبدیل شده را با فرمت IR از طریق ماژول DNN در OpenCV بارگذاری کنیم و آن را به آن ارسال کنیم.
import cv2 as cv
emotionsNet = cv.dnn.readNet('emotions_model.bin',
'emotions_model.xml')
emotionsNet.setPreferableTarget(cv.dnn.DNN_TARGET_MYRIAD)
خط آخر در این مورد به شما امکان می دهد محاسبات را به Neural Compute Stick تغییر مسیر دهید، محاسبات اولیه روی پردازنده انجام می شود، اما در مورد Raspberry Pi این کار نمی کند، شما به یک چوب نیاز دارید.
بعد، منطق به این صورت است: ما صدای خود را به پنجره هایی با اندازه معین تقسیم می کنیم (برای ما 0.4 ثانیه است)، هر کدام از این پنجره ها را به MFCC تبدیل می کنیم، که سپس به شبکه تغذیه می کنیم:
emotionsNet.setInput(MFCC_from_window)
result = emotionsNet.forward()
در مرحله بعد، بیایید رایج ترین کلاس را برای همه ویندوزها انتخاب کنیم. یک راه حل ساده، اما برای هکاتون نیازی نیست که چیزی بیش از حد مبهم داشته باشید، فقط در صورتی که زمان داشته باشید. ما هنوز کارهای زیادی برای انجام دادن داریم، پس بیایید ادامه دهیم - با تشخیص صدا سر و کار خواهیم داشت. لازم است نوعی پایگاه داده ایجاد شود که طیفنگاری صداهای از پیش ضبط شده در آن ذخیره شود. از آنجایی که زمان کمی باقی مانده است، ما این مشکل را به بهترین شکل ممکن حل خواهیم کرد.
یعنی، ما یک اسکریپت برای ضبط یک گزیده صوتی ایجاد می کنیم (به همان روشی که در بالا توضیح داده شد کار می کند، تنها زمانی که از صفحه کلید قطع شود، صدا را در یک فایل ذخیره می کند).
بیایید تلاش کنیم:
python3 voice_db/record_voice.py test.wav
ما صدای چندین نفر را ضبط می کنیم (در مورد ما، سه عضو تیم)
در مرحله بعد، برای هر صدای ضبط شده، یک تبدیل سریع فوریه انجام می دهیم، یک طیف نگار به دست می آوریم و آن را به عنوان یک آرایه numpy (npy.) ذخیره می کنیم:
for file in glob.glob("voice_db/*.wav"):
spec = get_fft_spectrum(file)
np.save(file[:-4] + '.npy', spec)
توضیحات بیشتر در فایل create_base.py
در نتیجه، وقتی اسکریپت اصلی را اجرا می کنیم، در همان ابتدا از این طیف نگارها تعبیه هایی دریافت خواهیم کرد:
for file in glob.glob("voice_db/*.npy"):
spec = np.load(file)
spec = spec.astype('float32')
spec_reshaped = spec.reshape(1, 1, spec.shape[0], spec.shape[1])
srNet.setInput(spec_reshaped)
pred = srNet.forward()
emb = np.squeeze(pred)
پس از دریافت جاسازی از بخش صداگذاری شده، میتوانیم با فاصله کسینوس از پاساژ تا همه صداهای موجود در پایگاه داده (هرچه کوچکتر، احتمال بیشتری داشته باشد) مشخص کنیم که متعلق به چه کسی است - برای نسخه آزمایشی آستانه را تعیین میکنیم. به 0.3):
در پایان می خواهم توجه داشته باشم که سرعت استنتاج سریع بود و امکان اضافه کردن 1-2 مدل دیگر را فراهم کرد (برای یک نمونه 7 ثانیه ای برای استنتاج 2.5 طول کشید). دیگر زمانی برای اضافه کردن مدلهای جدید نداشتیم و روی نوشتن نمونه اولیه برنامه وب تمرکز کردیم.
برنامه تحت وب
یک نکته مهم: ما یک روتر از خانه با خود می بریم و شبکه محلی خود را راه اندازی می کنیم، به اتصال دستگاه و لپ تاپ از طریق شبکه کمک می کند.
باطن یک کانال پیام سرتاسر بین جلو و رزبری پای است که بر اساس فناوری وب سوکت (http over tcp پروتکل) است.
مرحله اول دریافت اطلاعات پردازش شده از رزبری است، یعنی پیش بینی کننده های بسته بندی شده در json، که در نیمه راه در پایگاه داده ذخیره می شوند تا بتوان آماری در مورد پس زمینه احساسی کاربر برای آن دوره تولید کرد. این بسته سپس به فرانت اند ارسال می شود که از اشتراک استفاده می کند و بسته ها را از نقطه پایانی وب سوکت دریافت می کند. کل مکانیسم Backend به زبان گلانگ ساخته شده است؛ این مکانیسم انتخاب شده است زیرا برای کارهای ناهمزمان مناسب است، که گوروتین ها به خوبی از پس آن بر می آیند.
هنگام دسترسی به نقطه پایانی، کاربر ثبت نام و وارد ساختار می شود، سپس پیام او دریافت می شود. هم کاربر و هم پیام وارد یک هاب مشترک میشوند که از آن پیامها قبلاً (به قسمت مشترک شده) ارسال میشوند و اگر کاربر اتصال را ببندد (رزبری یا جلو)، اشتراکش لغو میشود و از آن حذف میشود. هاب
ما منتظر اتصال از پشت هستیم
Front-end یک برنامه تحت وب است که با استفاده از کتابخانه React برای سرعت بخشیدن و ساده کردن فرآیند توسعه با جاوا اسکریپت نوشته شده است. هدف این نرم افزار تجسم داده های به دست آمده با استفاده از الگوریتم هایی است که در قسمت پشتی و مستقیماً روی Raspberry Pi اجرا می شوند. صفحه دارای مسیریابی مقطعی است که با استفاده از روتر واکنش نشان داده شده است، اما صفحه اصلی مورد علاقه صفحه اصلی است، جایی که یک جریان پیوسته از داده ها در زمان واقعی از سرور با استفاده از فناوری WebSocket دریافت می شود. Raspberry Pi یک صدا را شناسایی می کند، تعیین می کند که آیا این صدا متعلق به شخص خاصی از پایگاه داده ثبت شده است یا خیر، و لیست احتمالات را برای مشتری ارسال می کند. مشتری آخرین داده های مربوطه را نمایش می دهد، آواتار شخصی که به احتمال زیاد در میکروفون صحبت می کند و همچنین احساساتی که با آن کلمات را تلفظ می کند را نمایش می دهد.
صفحه اصلی با پیش بینی های به روز شده
نتیجه
تکمیل همه چیز طبق برنامه ممکن نبود، ما به سادگی زمان نداشتیم، بنابراین امید اصلی در نسخه ی نمایشی بود، که همه چیز کار کند. در ارائه آنها در مورد اینکه همه چیز چگونه کار می کند، چه مدل هایی را انتخاب کردند و با چه مشکلاتی مواجه شدند صحبت کردند. قسمت بعدی قسمت آزمایشی بود - کارشناسان به ترتیب تصادفی در اتاق قدم زدند و به هر تیم نزدیک شدند تا نمونه اولیه کار را ببینند. آنها از ما نیز سؤالاتی پرسیدند، همه به سهم خود پاسخ دادند، آنها وب را روی لپ تاپ ترک کردند و همه چیز واقعاً همانطور که انتظار می رفت کار کرد.
اجازه دهید توجه داشته باشم که هزینه کل راه حل ما 150 دلار بود:
Raspberry Pi 3 ~ 35 دلار
Google AIY Voice Bonnet (می توانید هزینه سخنران را دریافت کنید) ~ 15 دلار
Intel NCS 2 ~ 100 دلار
نحوه بهبود:
از ثبت نام از مشتری استفاده کنید - بخواهید متنی را که به طور تصادفی ایجاد می شود بخوانید
چند مدل دیگر اضافه کنید: می توانید جنسیت و سن را با صدا تعیین کنید
در خاتمه از برگزارکنندگان و شرکت کنندگان تشکر می کنم. در بین پروژه های تیم های دیگر، ما شخصاً راه حل نظارت بر پارکینگ های رایگان را دوست داشتیم. برای ما، این یک تجربه بسیار جالب از غوطه ور شدن در محصول و توسعه بود. من امیدوارم که رویدادهای جالب بیشتری در مناطق از جمله در موضوعات هوش مصنوعی برگزار شود.