OpenVINO hackathon: التعرف على الصوت والعواطف على Raspberry Pi

30 نوفمبر - 1 ديسمبر عقدت في نيجني نوفغورود هاكاثون OpenVINO. طُلب من المشاركين إنشاء نموذج أولي لحل المنتج باستخدام مجموعة أدوات Intel OpenVINO. اقترح المنظمون قائمة بالموضوعات التقريبية التي يمكن الاسترشاد بها عند اختيار المهمة، لكن القرار النهائي ظل للفرق. بالإضافة إلى ذلك، تم تشجيع استخدام النماذج غير المدرجة في المنتج.

OpenVINO hackathon: التعرف على الصوت والعواطف على Raspberry Pi

سنخبرك في هذه المقالة عن كيفية إنشاء النموذج الأولي للمنتج، والذي حصلنا به في النهاية على المركز الأول.

وشارك في الهاكاثون أكثر من 10 فرق. ومن الجميل أن بعضهم جاء من مناطق أخرى. كان مكان الهاكاثون هو مجمع "Kremlinsky on Pochain"، حيث تم تعليق الصور القديمة لنيجني نوفغورود في الداخل، في حاشية! (أذكرك أنه في الوقت الحالي يقع المكتب المركزي لشركة Intel في نيجني نوفغورود). تم منح المشاركين 26 ساعة لكتابة التعليمات البرمجية، وفي النهاية كان عليهم تقديم حلهم. كانت الميزة المنفصلة هي وجود جلسة تجريبية للتأكد من أن كل ما تم التخطيط له قد تم تنفيذه فعليًا ولم يبقى أفكارًا في العرض التقديمي. البضائع والوجبات الخفيفة والطعام، كل شيء كان هناك أيضًا!

بالإضافة إلى ذلك، توفر إنتل اختياريًا الكاميرات وRaspberry PI وNeural Compute Stick 2.

اختيار المهمة

أحد أصعب أجزاء التحضير للهاكاثون الحر هو اختيار التحدي. قررنا على الفور التوصل إلى شيء لم يكن موجودًا في المنتج بعد، حيث ذكر الإعلان أن هذا موضع ترحيب كبير.

بعد التحليل نموذج، والتي تم تضمينها في المنتج في الإصدار الحالي، توصلنا إلى استنتاج مفاده أن معظمها يحل مشاكل رؤية الكمبيوتر المختلفة. علاوة على ذلك، من الصعب جدًا التوصل إلى مشكلة في مجال رؤية الكمبيوتر لا يمكن حلها باستخدام OpenVINO، وحتى لو كان من الممكن اختراع واحدة، فمن الصعب العثور على نماذج مدربة مسبقًا في المجال العام. قررنا التعمق في اتجاه آخر - نحو معالجة الكلام والتحليلات. دعونا نفكر في مهمة مثيرة للاهتمام تتمثل في التعرف على المشاعر من الكلام. ويجب القول أن OpenVINO لديه بالفعل نموذج يحدد مشاعر الشخص بناءً على وجهه، ولكن:

  • من الناحية النظرية، من الممكن إنشاء خوارزمية مشتركة تعمل على كل من الصوت والصورة، والتي ينبغي أن تعطي زيادة في الدقة.
  • عادة ما تكون للكاميرات زاوية رؤية ضيقة، ويلزم وجود أكثر من كاميرا واحدة لتغطية مساحة كبيرة، أما الصوت فلا يوجد مثل هذا القيد.

دعونا نطور الفكرة: لنأخذ فكرة قطاع البيع بالتجزئة كأساس. يمكنك قياس رضا العملاء عند الخروج من المتجر. إذا كان أحد العملاء غير راضٍ عن الخدمة وبدأ في رفع نبرة صوته، فيمكنك الاتصال بالمسؤول على الفور للحصول على المساعدة.
في هذه الحالة، نحتاج إلى إضافة خاصية التعرف على الصوت البشري، وهذا سيسمح لنا بتمييز موظفي المتجر عن العملاء وتوفير التحليلات لكل فرد. حسنا، بالإضافة إلى ذلك، سيكون من الممكن تحليل سلوك موظفي المتجر أنفسهم، وتقييم الجو في الفريق، يبدو جيدا!

نقوم بصياغة متطلبات الحل الخاص بنا:

  • صغر حجم الجهاز المستهدف
  • عملية في الوقت الحقيقي
  • انخفاض السعر
  • سهولة التوسع

ونتيجة لذلك، قمنا باختيار Raspberry Pi 3 c باعتباره الجهاز المستهدف إنتل إن سي إس 2.

من المهم هنا ملاحظة إحدى الميزات المهمة لـ NCS - فهي تعمل بشكل أفضل مع بنيات CNN القياسية، ولكن إذا كنت بحاجة إلى تشغيل نموذج بطبقات مخصصة عليه، فتوقع تحسينًا منخفض المستوى.

هناك شيء واحد صغير يجب القيام به: تحتاج إلى الحصول على ميكروفون. سيفي ميكروفون USB العادي بالغرض، لكنه لن يبدو جيدًا مع RPI. ولكن حتى هنا فإن الحل حرفياً «يقع في مكان قريب». لتسجيل الصوت، قررنا استخدام لوحة Voice Bonnet الموجودة في المجموعة جوجل AIY مجموعة صوتية، حيث يوجد ميكروفون استريو سلكي.

تحميل راسبيان من مستودع مشاريع AIY وقم بتحميله على محرك أقراص فلاش، واختبر أن الميكروفون يعمل باستخدام الأمر التالي (سيسجل الصوت لمدة 5 ثوانٍ ويحفظه في ملف):

arecord -d 5 -r 16000 test.wav

يجب أن أشير على الفور إلى أن الميكروفون حساس للغاية ويلتقط الضوضاء جيدًا. لإصلاح ذلك، دعنا نذهب إلى alsamixer، وحدد أجهزة الالتقاط وقم بتقليل مستوى إشارة الإدخال إلى 50-60%.

OpenVINO hackathon: التعرف على الصوت والعواطف على Raspberry Pi
نقوم بتعديل الجسم بملف وكل شيء مناسب، يمكنك حتى إغلاقه بغطاء

إضافة زر المؤشر

أثناء تفكيك AIY Voice Kit، نتذكر أن هناك زر 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، والذي من خلاله سنقوم بتحديث اللون:

led_dict = {'neutral': (255, 255, 255), 'happy': (0, 255, 0), 'sad': (0, 255, 255), 'angry': (255, 0, 0), 'fearful': (0, 0, 0), 'disgusted':  (255, 0, 255), 'surprised':  (255, 255, 0)} 
leds = Leds()

وأخيرا، بعد كل توقع جديد للعاطفة، سنقوم بتحديث لون الزر وفقا له (عن طريق المفتاح).

leds.update(Leds.rgb_on(led_dict.get(classes[prediction])))

OpenVINO hackathon: التعرف على الصوت والعواطف على Raspberry Pi
زر، حرق!

العمل بالصوت

سنستخدم pyaudio لالتقاط البث من الميكروفون وwebrtcvad لتصفية الضوضاء واكتشاف الصوت. بالإضافة إلى ذلك، سنقوم بإنشاء قائمة انتظار نضيف إليها ونزيل مقتطفات صوتية بشكل غير متزامن.

نظرًا لأن webrtcvad لديه قيود على حجم الجزء المقدم - يجب أن يكون مساويًا لـ 10/20/30 مللي ثانية، وتم تنفيذ تدريب النموذج للتعرف على المشاعر (كما سنتعلم لاحقًا) على مجموعة بيانات تبلغ 48 كيلو هرتز، فسنقوم بذلك التقاط قطع بحجم 48000 × 20 مللي ثانية/1000 × 1 (أحادي) = 960 بايت. سيعرض Webrtcvad صواب/خطأ لكل مجموعة من هذه المقاطع، وهو ما يتوافق مع وجود أو عدم وجود تصويت في المجموعة.

دعونا ننفذ المنطق التالي:

  • سنضيف إلى القائمة تلك القطع التي يوجد بها تصويت؛ إذا لم يكن هناك تصويت، فسنقوم بزيادة عداد القطع الفارغة.
  • إذا كان عداد القطع الفارغة >=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، Google، لكن تذكر أن لدينا قيودًا على البنية المستخدمة. يعد هذا جزءًا صعبًا إلى حد ما، لأنه يتعين عليك اختبار النماذج على بيانات الإدخال الخاصة بك، بالإضافة إلى تحويلها إلى التنسيق الداخلي لـ OpenVINO - IR (التمثيل المتوسط). لقد جربنا حوالي 5-7 حلول مختلفة من جيثب، وإذا كان نموذج التعرف على المشاعر يعمل على الفور، فمع التعرف على الصوت كان علينا الانتظار لفترة أطول - فهم يستخدمون بنيات أكثر تعقيدًا.

ونحن نركز على ما يلي:

  • العواطف من الصوت - https://github.com/alexmuhr/Voice_Emotion
    يعمل وفق المبدأ التالي: يتم تقطيع الصوت إلى مقاطع بحجم معين، لكل مقطع من هذه المقاطع نختار MFCC ومن ثم إرسالها كمدخلات إلى CNN
  • التعرف على الصوت - https://github.com/linhdvu14/vggvox-speaker-identification
    هنا، بدلاً من MFCC، نعمل باستخدام مخطط طيفي، بعد FFT نقوم بتغذية الإشارة إلى CNN، حيث نحصل عند الإخراج على تمثيل متجه للصوت.

بعد ذلك سنتحدث عن تحويل النماذج، بدءًا من النظرية. يتضمن OpenVINO عدة وحدات:

  • افتح Model Zoo، النماذج التي يمكن استخدامها وتضمينها في منتجك
  • نموذج Optimzer، والذي بفضله يمكنك تحويل نموذج من تنسيقات إطار عمل مختلفة (Tensorflow، ONNX، إلخ) إلى تنسيق Intermediate Representation، والذي سنعمل معه بشكل أكبر
  • يتيح لك Inference Engine تشغيل النماذج بتنسيق IR على معالجات Intel ورقائق Myriad ومسرعات Neural Compute Stick
  • الإصدار الأكثر كفاءة من OpenCV (مع دعم محرك الاستدلال)
    يتم وصف كل نموذج بتنسيق IR بواسطة ملفين: .xml و.bin.
    يتم تحويل النماذج إلى تنسيق IR عبر Model Optimizer كما يلي:

    python /opt/intel/openvino/deployment_tools/model_optimizer/mo_tf.py --input_model speaker.hdf5.pb --data_type=FP16 --input_shape [1,512,1000,1]

    --data_type يسمح لك بتحديد تنسيق البيانات الذي سيعمل النموذج به. ويدعم FP32، FP16، INT8. يمكن أن يؤدي اختيار نوع البيانات الأمثل إلى تعزيز الأداء بشكل جيد.
    --input_shape يشير إلى أبعاد بيانات الإدخال. يبدو أن القدرة على التغيير ديناميكيًا موجودة في واجهة برمجة تطبيقات C++، لكننا لم نتعمق في هذا الأمر وقمنا ببساطة بإصلاحها لأحد النماذج.
    بعد ذلك، دعونا نحاول تحميل النموذج الذي تم تحويله بالفعل بتنسيق 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):

            dist_list = cdist(emb, enroll_embs, metric="cosine")
            distances = pd.DataFrame(dist_list, columns = df.speaker)

    في النهاية، أود أن أشير إلى أن سرعة الاستدلال كانت سريعة وجعلت من الممكن إضافة نموذج أو نموذجين آخرين (لعينة مدتها 1 ثوانٍ، استغرق الاستدلال 2 ثانية). لم يعد لدينا الوقت لإضافة نماذج جديدة وركزنا على كتابة نموذج أولي لتطبيق الويب.

    تطبيق الويب

    نقطة مهمة: نأخذ جهاز توجيه معنا من المنزل ونقوم بإعداد شبكتنا المحلية، فهو يساعد على توصيل الجهاز وأجهزة الكمبيوتر المحمولة عبر الشبكة.

    الواجهة الخلفية عبارة عن قناة رسائل شاملة بين الواجهة الأمامية وRaspberry Pi، استنادًا إلى تقنية websocket (http عبر بروتوكول TCP).

    تتمثل المرحلة الأولى في تلقي المعلومات المعالجة من Raspberry، أي المتنبئات المعبأة في ملف json، والتي يتم حفظها في قاعدة البيانات في منتصف رحلتها بحيث يمكن إنشاء إحصائيات حول الخلفية العاطفية للمستخدم لهذه الفترة. يتم بعد ذلك إرسال هذه الحزمة إلى الواجهة الأمامية، والتي تستخدم الاشتراك وتستقبل الحزم من نقطة نهاية websocket. تم بناء آلية الواجهة الخلفية بأكملها بلغة golang، وقد تم اختيارها لأنها مناسبة تمامًا للمهام غير المتزامنة، والتي تتعامل معها goroutines بشكل جيد.
    عند الوصول إلى نقطة النهاية، يتم تسجيل المستخدم وإدخاله في البنية، ثم يتم استلام رسالته. يتم إدخال كل من المستخدم والرسالة في مركز مشترك، حيث يتم إرسال الرسائل بالفعل (إلى الواجهة المشتركة)، وإذا أغلق المستخدم الاتصال (التوت أو الواجهة)، فسيتم إلغاء اشتراكه ويتم إزالته من المحور.

    OpenVINO hackathon: التعرف على الصوت والعواطف على Raspberry Pi
    نحن في انتظار اتصال من الخلف

    الواجهة الأمامية هي تطبيق ويب مكتوب بلغة JavaScript باستخدام مكتبة React لتسريع عملية التطوير وتبسيطها. الغرض من هذا التطبيق هو تصور البيانات التي تم الحصول عليها باستخدام الخوارزميات التي تعمل على الجانب الخلفي ومباشرة على Raspberry Pi. تحتوي الصفحة على توجيه مقطعي يتم تنفيذه باستخدام جهاز توجيه التفاعل، ولكن الصفحة الرئيسية محل الاهتمام هي الصفحة الرئيسية، حيث يتم تلقي دفق مستمر من البيانات في الوقت الفعلي من الخادم باستخدام تقنية WebSocket. يكتشف Raspberry Pi الصوت، ويحدد ما إذا كان ينتمي إلى شخص معين من قاعدة البيانات المسجلة، ويرسل قائمة احتمالية إلى العميل. يعرض العميل أحدث البيانات ذات الصلة، ويعرض الصورة الرمزية للشخص الذي على الأرجح تحدث في الميكروفون، بالإضافة إلى العاطفة التي ينطق بها الكلمات.

    OpenVINO hackathon: التعرف على الصوت والعواطف على Raspberry Pi
    الصفحة الرئيسية مع التوقعات المحدثة

    اختتام

    لم يكن من الممكن إكمال كل شيء كما هو مخطط له، ولم يكن لدينا الوقت ببساطة، لذلك كان الأمل الرئيسي في العرض التوضيحي هو أن كل شيء سيعمل. تحدثوا في العرض التقديمي عن كيفية عمل كل شيء، وما هي النماذج التي اتخذوها، وما هي المشاكل التي واجهوها. بعد ذلك كان الجزء التجريبي - حيث تجول الخبراء حول الغرفة بترتيب عشوائي واقتربوا من كل فريق لإلقاء نظرة على النموذج الأولي للعمل. لقد طرحوا علينا أسئلة أيضًا، وأجاب الجميع من جانبهم، وتركوا الويب على الكمبيوتر المحمول، وكل شيء سار حقًا كما هو متوقع.

    اسمحوا لي أن أشير إلى أن التكلفة الإجمالية لحلنا كانت 150 دولارًا:

    • راسبيري باي 3 ~ 35 دولارًا
    • Google AIY Voice Bonnet (يمكنك الحصول على رسوم مكبر الصوت) ~ 15 دولارًا
    • إنتل NCS 2 ~ 100 دولار

    كيفية التحسين:

    • استخدم التسجيل من العميل - اطلب قراءة النص الذي تم إنشاؤه بشكل عشوائي
    • أضف المزيد من النماذج: يمكنك تحديد الجنس والعمر عن طريق الصوت
    • أصوات منفصلة في وقت واحد (التسجيل)

    مخزن: https://github.com/vladimirwest/OpenEMO

    OpenVINO hackathon: التعرف على الصوت والعواطف على Raspberry Pi
    متعبون ولكننا سعداء

    وفي الختام أود أن أشكر المنظمين والمشاركين. من بين مشاريع الفرق الأخرى، أحببنا شخصيًا حل مراقبة أماكن وقوف السيارات المجانية. بالنسبة لنا، كانت تجربة رائعة للغاية للانغماس في المنتج والتطوير. آمل أن يتم عقد المزيد والمزيد من الأحداث المثيرة للاهتمام في المناطق، بما في ذلك موضوعات الذكاء الاصطناعي.

المصدر: www.habr.com

إضافة تعليق