ROS والشبكة العصبية متسول الروبوت

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

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

في ذلك الوقت ، لم يكن لدي أي أفكار مشرقة في مجال الروبوتات ، لذلك قررت أن أصنع مشروعًا ممتعًا حصريًا - روبوت متسول. والنتيجة هي روبوت مستقل على Raspberry Pi و ROS ، باستخدام عصا Movidius Neural Cumpute Stick لاكتشاف الوجوه. يتجول في الغرفة ، يبحث عن الناس ، ويهز علبة أمامهم. إليك ما يبدو عليه هذا الروبوت:



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



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



إنسان آلي


أخذت فكرة روبوت التسول من مجلة Popular Mechanics . يبدو أن تأليف النموذج كريس إيكيرت الذي يدعى Gimme ممتع من الناحية الجمالية.

صورة

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

أساس الروبوت هو Raspberry Pi 2B ، والكتابة مكتوبة بلغة C ++ وتقع على GitHub .

رؤية


لإدراك الواقع ، يستخدم الروبوت كاميرا Paspberry Pi Camera Module v2 ، والتي يمكن التحكم فيها باستخدام مكتبة RaspiCam .

لاكتشاف الوجه ، جربت عدة طرق مختلفة. جودة أجهزة الكشف الكلاسيكية من OpenCV لم ترضيني ، لذلك في النهاية توصلت إلى حل غير قياسي إلى حد ما. الكشف عن الأشخاص العاملين في الشبكة العصبية ، التي تعمل على جهاز Movidius Neural Compute Stick (NCS) ضمن إطار التحكم OpenVINO .

إن NCS هي قطعة من الأجهزة من أجل الإطلاق الفعال للشبكات العصبية ، حيث يوجد بداخلها العديد من المعالجات الموجهة المصممة خصيصًا لهذا الغرض. الجهاز متصل عبر USB ويستهلك فقط 1 واط من الطاقة. وبالتالي ، يعمل NCS كمعالج مساعد لـ Raspberry Pi ، الذي لا يسحب الشبكة العصبية. بينما يقوم NCS بمعالجة الإطار التالي ، فإن معالج Paspberry مجاني لعمليات أخرى. تجدر الإشارة إلى أنه من أجل التشغيل الأمثل للجهاز ، يلزم وجود واجهة USB 3.0 ، والتي لا تتوفر في الإصدارات القديمة من Raspberry ؛ مع USB 2.0 ، يعمل أيضًا بشكل أبطأ. أيضًا ، حتى لا أحجب موصلات Raspberry USB ، أقوم بتوصيل NCS به عبر كابل USB قصير. لقد كتبت بالتفصيل حول العمل مع Neute Compute Stick في مقالتي السابقة .

في البداية حاولت التدريبجهاز كشف الوجه الخاص بهندسة MobileNet + SSD على مجموعات البيانات المفتوحة. عمل الكاشف حقًا ، ولكنه ليس مستقرًا جدًا: مع التدهور الحتمي لظروف التصوير (التعرض والطلقات الباهتة) ، تراجعت جودة الكاشف بشكل كبير. ومع ذلك ، بعد مرور بعض الوقت ، ظهرت كاشفات الوجه الجاهزة في OpenVINO ، وتحولت إلى كاشف مع بنية SqueezeNet light + SSD ، والتي لم تعمل بشكل أفضل فقط في مجموعة متنوعة من ظروف التصوير ، ولكنها كانت أيضًا أسرع.

قبل تحميل الصورة إلى NCS للحصول على تنبؤات الكاشف ، يجب معالجة الصورة مسبقًا. يعمل كاشف من اختياري مع الصور الملونة300×300، لذا يجب ضغط الصورة أولاً. للقيام بذلك ، أستخدم خوارزمية التحجيم الأخف - أقرب طريقة جار (INTER_NEAREST في مكتبة OpenCV). يعمل بشكل أسرع قليلاً من طرق الاستيفاء ، ولا يؤثر تقريبًا على النتيجة. يجدر أيضًا الانتباه إلى ترتيب قنوات الصور: يتوقع الكاشف ترتيب BGR ، لذلك تحتاج إلى تعيين الشيء نفسه للكاميرا.

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

بالإضافة إلى اكتشاف الوجوه فعليًا ، يجب أيضًا تتبعها لتجنب أخطاء الكاشف. للقيام بذلك ، أستخدم جهاز تعقب خفيف الوزن Simple Online Realtime Tracker (SORT) . يتكون هذا المتتبع البسيط من جزأين: يتم استخدام الخوارزمية المجرية لمطابقة الكائنات على الإطارات المجاورة، والتنبؤ بمسار الكائن ، إذا اختفى فجأة - مرشح كالمان . بينما كنت ألعب بتتبع الوجه ، وجدت أن المسارات التي تنبأ بها مرشح كالمان يمكن أن تكون غير معقولة للغاية مع الحركات المفاجئة ، الأمر الذي يعقد العملية مرة أخرى.

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

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


حركة المرور


للحركة ، يحتوي الروبوت على خمسة أجهزة: اثنان من أجهزة FS5103R ذات الدوران المستمر تدوران العجلات ؛ هناك نوعان من FS5109Ms عاديان ، أحدهما يدور الرأس ، والآخر يهز العلبة ؛ وأخيرًا ، يحرك SG90 الصغير حاجبيه.

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

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

Adafruit PCA9685 هو جهاز تحكم PWM 16 قناة يمكن التحكم فيه عبر واجهة I2C . كما أنه من الملائم للغاية أنه يحتوي على محطات لتزويد الطاقة للمعدات. علاوة على ذلك ، [نظريًا] من الممكن ربط ما يصل إلى 62 وحدة تحكم ، أثناء تلقي ما يصل إلى 992 دبابيس تحكم - لهذا تحتاج إلى تعيين عنوان فريد لكل وحدة تحكم باستخدام وصلات مرور خاصة. لذلك إذا كنت بحاجة فجأة إلى جيش من الماكينات - فأنت تعرف ماذا تفعل.

للتحكم في PCA9685 ، هناك مكتبة عالية المستوى تعمل كملحق WiringPi. العمل مع هذا الشيء مناسب تمامًا - أثناء التهيئة ، يقوم بإنشاء 16 دبابيس افتراضية يمكنك كتابة إشارة PWM فيها ، ولكن عليك أولاً حساب عدد القراد. لتحويل ذراع المؤازرة إلى زاوية معينة في النطاق [0 ، 180] ، يجب عليك أولاً ترجمة هذه الزاوية إلى نطاق أطوال نبضة التحكم بالمللي ثانية [SERVO_MS_MIN، SERVO_MS_MAX]. بالنسبة لجميع أجهزة بلدي ، هذه القيم هي حوالي 0.6 مللي ثانية و 2.4 مللي ثانية على التوالي. بشكل عام ، يمكن العثور على هذه القيم في ورقة بيانات المؤازرة ، ولكن الممارسة أظهرت أنها يمكن أن تختلف ، لذلك قد تحتاج إلى التحديد. ثم اقسم القيمة الناتجة على 20 مللي ثانية (القيمة القياسية لطول دورة التحكم) واضرب في الحد الأقصى لعدد علامات التجزئة PCA9685 (4096):

void driveDegs(float angle, int pin) {
    int ticks = (int) (PCA_MAX_PWM * (angle/180.0f*(SERVO_MS_MAX-SERVO_MS_MIN) + SERVO_MS_MIN) / 20.0f); 
    pwmWrite(pin, ticks);
}

وبالمثل ، يتم ذلك باستخدام أجهزة الدوران المستمر - بدلاً من الزاوية ، نقوم بتعيين السرعة في النطاق [-1،1].

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

الآن حول ما يمكنك القيام به حيال ذلك. أولاً ، يمكنك هز البرطمان وتحريك الحاجبين - لهذا تحتاج فقط إلى تحويل ذراع المؤازرة إلى زوايا محددة مسبقًا.

ثانيًا ، يمكنك تدوير رأسك. لم أكن أريد أن يدور الرأس بأقصى سرعة للمكابح ، لأنه يحتوي على كاميرا. لذلك ، قررت تقليل السرعة برمجيًا: أحتاج إلى تحويل الرافعة بزاوية صغيرة ، ثم الانتظار لبضع ثوان - وهكذا حتى يتم الوصول إلى الزاوية المطلوبة. في هذه الحالة ، من الضروري تذكر الوضع المطلق الحالي للرأس وفي كل مرة تحقق مما إذا كان قد تجاوز الحدود المسموح بها (على الروبوت الخاص بي في نطاق [10 ، 90] درجة).

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

أخيرًا ، يمكنك تدوير الرأس والهيكل في وقت واحد وبشكل غير متزامن حتى لا تضيع الوقت. أفعل ذلك على هذا النحو:

auto waitRotation = std::async(std::launch::async, rotatePlatform, platformAngle);
success = driveHead(headAngle);
waitRotation.wait();

المنصة المركزية ، من اليسار إلى اليمين: PCA9685 ، ناقل الطاقة ، Raspberry Pi ، MCP3008 ADC


التنقل


ثم لم أقم بتعقيد أي شيء ، لذلك يستخدم الروبوت اثنين فقط من محددات الأشعة تحت الحمراء Sharp GP2Y0A02YK للتنقل. هذا أيضًا ليس بهذه البساطة ، لأن المستشعرات تناظرية ، لكن Raspberry ، على عكس Arduino ، ليس لديه مدخلات تناظرية. يتم حل هذه المشكلة عن طريق المحول التناظري إلى الرقمي (ADC / ADC) - أستخدم MCP3008 10 بت و 8 قنوات. يتم بيعها كدائرة صغيرة منفصلة ، لذلك كان لا بد من لحامها على لوحة الدوائر المطبوعة وتم لحام الدبابيس هناك أيضًا لجعلها أكثر ملاءمة للتوصيل. أيضًا ، بناء على نصيحة من بلدي الخفافيش ، الذي يتخبط أكثر في الدوائر ، قمت بحاميل مكثفين (السيراميك والكهرباء) بين أرجل مزود الطاقة والأرض لامتصاص الضوضاء من الجزء الرقمي من الدائرة بأكملها. لا ينتج المستشعر أكثر من ثلاثة فولت عند الإخراج ، لذلك يمكن توصيل 3.3 فولت مع Raspberry كجهد ADC (VREF) مرجعي - وهو نفسه بالنسبة لمزود الطاقة MCP3008 (VDD).

يمكن التحكم في MCP3008 عبر واجهة SPI ، ولهذا يسهل العثور على كود جاهز على GitHub .

على الرغم من ذلك ، من أجل العمل المريح مع ADC ، يلزم بعض الرقصات مع الدف.
unsigned int analogRead(mcp3008Spi &adc, unsigned char channel)
{
    unsigned char spi_data[3];
    unsigned int val = 0;

    spi_data[0] = 1;  // start bit
    spi_data[1] = 0b10000000 | ( channel << 4); // mode and channel
    spi_data[2] = 0; // anything
    adc.spiWriteRead(spi_data, sizeof(spi_data));
  
    // read value, combine last two bits of second byte with whole third byte
    val = (spi_data[1]<< 8) & 0b1100000000; 
    val |= (spi_data[2] & 0xff);
    return val;
}


يجب إرسال ثلاثة بايتات إلى MCP3008 ، حيث تتم كتابة بت البداية في البايت الأول ، والوضع ورقم القناة (0-7) في الثانية. نستعيد أيضًا ثلاث وحدات بايت ، وبعد ذلك نحتاج إلى لصق البتتين الأقل أهمية من البايت الثاني مع جميع وحدات البت الثالثة.

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

يمكن أن تكون قراءات المستشعر صاخبة تمامًا ، خاصة على العوائق الصعبة ، لذلك أستخدم متوسط ​​متحرك موزون بشكل كبير (EWMA) لتنعيم الإشارة من كل مستشعر. لقد اخترت معلمات التنعيم بالعين ، بحيث لا تصدر الإشارة ضوضاء ولا تتأخر كثيرًا عن الواقع.

المنظر الأمامي: البنك ، وجهاز ضبط المسافات ، والمصد.


التغذية


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

  • Raspberry Pi 2B: لا يقل عن 350 مللي أمبير ، ولكن أكثر تحت الحمل (حتى 750-820 مللي أمبير (؟)) ؛
  • الكاميرا: حوالي 250 مللي أمبير.
  • Neute Compute Stick: استهلاك الطاقة المعلن عنه 1 واط ، بجهد 5 فولت على USB ، 200 مللي أمبير ؛
  • أجهزة استشعار الأشعة تحت الحمراء: 33 مللي أمبير لكل منها ( ورقة بيانات ، pdf ) ؛
  • MCP3008: , 0.5 (, pdf);
  • PCA9685: , 6 (, pdf);
  • : ~150-200 1500-2000 (stall current), ( FS5109M, pdf)
  • HDMI ( ): 50 ;
  • + ( ): ~200 .

في المجموع ، يمكن تقدير أن 1.5-2.5 أمبير يجب أن تكون كافية ، بشرط ألا تتحرك جميع الماكينات في وقت واحد تحت الحمل الثقيل. في الوقت نفسه ، يحتاج توت العليق المشروط إلى 5 فولت من الجهد ، وللخدمات المؤازرة - 4.8-6 فولت. يبقى العثور على مصدر طاقة يلبي هذه المتطلبات.

ونتيجة لذلك ، قررت تشغيل الروبوت من 18650 بطارية. إذا كنت تأخذ بطاريتين ROBITON 3.4 / Li18650 (3.6 فولت ، 3400 مللي أمبير ، تيار التفريغ الأقصى 4875 مللي أمبير) وربطهما في سلسلة ، فيمكنهما إنتاج ما يصل إلى 4.8 أمبير بجهد 7.2 فولت. مع تيار استهلاك 1.5-2.5 أمبير ، يجب أن تكون كافية لمدة ساعة أو ساعتين.

بالمناسبة ، البطاريات لها مصيد: على الرغم من عامل الشكل المشار إليه 18650 ، فإن أحجامها بعيدة عن18×650مم - يبلغ طولها عدة مليمترات بسبب دائرة التحكم في الشحن المدمجة. وبسبب هذا ، اضطررت إلى طعن حجرة البطارية بسكين بحيث تناسبهم هناك.

يبقى فقط لخفض الجهد إلى 5 فولت. لهذا ، أستخدم محولين DC-DC منفصلين DC-DC وحدة الطاقة DFRobot. تسمح لك هذه القطعة من الحديد بخفض الجهد عند جهد دخل من 3.6-25 فولت وفرق جهد لا يقل عن 0.6 فولت. للراحة ، يحتوي على مفتاح يسمح لك بتحديد 5 فولت بالضبط عند الإخراج ، أو يمكنك تكوين جهد خرج تعسفي باستخدام منظم خاص. قمت بتعيين كلا المحولين إلى 5 فولت ؛ واحد منهم يغذي التوت من خلال موصل Micro-USB ، والثاني يغذي الماكينات من خلال محطات PCA9685. يعد ذلك ضروريًا لزيادة إمدادات الطاقة للأجزاء المنطقية والطاقة من الروبوت بحيث لا تتداخل مع بعضها البعض.

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

من أجل الراحة ، كان علي أيضًا عمل "حافلة كهربائية" - في الواقع ، مجرد قطعة من اللوحة مع ثلاثة صفوف من المسامير المتصلة للأرض ، 3.3 فولت و 5 فولت ، على التوالي. تتصل الحافلة بدبابيس التوت المقابلة. يتم تشغيل محددات المدى IR فقط من ناقل 5 فولت و MCP3008 و PCA9685 من ناقل 3.3 فولت.

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

المنصة السفلية ، من اليسار إلى اليمين: حجرة البطارية ، NCS ، محولات DC-DC ، محركات مؤازرة بعجلات ، محددات المدى.


التحكم في الروبوت


لا توجد شبكة Wi-Fi على Raspberry Pi 2B ، لذلك يجب علي الاتصال عبر ssh عبر كابل إيثرنت (بالمناسبة ، يمكن القيام بذلك مباشرة من الكمبيوتر المحمول ، دون استخدام جهاز توجيه ). اتضح هذا المخطط: نقوم بالاتصال عبر ssh عبر الكابل ، وبدء الروبوت وإزالة الكابل. ثم يمكن إعادته إلى مكانه للوصول إلى التوت مرة أخرى. هناك حلول أكثر أناقة ، لكنني قررت ألا أعقد.

حتى يمكن إيقاف الروبوت بسهولة دون إيقاف التشغيل ، أضفت مفتاحًا سوفيتيًا ضخمًا (من غواصة؟) إليه ، عند إيقاف تشغيله ، ينتهي البرنامج ويتوقف الروبوت.

يتصل المفتاح بالأرض وبأحد دبابيس Raspberry GPIO ، ويمكنك القراءة منه باستخدام مكتبة WiringPi :

wiringPiSetup();
pinMode(PIN_SWITCH, INPUT);
pullUpDnControl(PIN_SWITCH, PUD_UP);
bool value = digitalRead(BB_PIN_SWITCH);

تجدر الإشارة إلى أنه مع هذا الاتصال ، يجب سحب الجهد على الدبوس إلى 3.3 فولت ، وفي الوقت نفسه سوف ينتج إشارة عالية في الحالة المفتوحة وإشارة منخفضة في الحالة المغلقة.

ضع كل شيء معا


الخيوط

الآن يجب دمج كل ما سبق في برنامج واحد يتحكم في الروبوت. في الإصدار الأول من الروبوت ، فعلت ذلك باستخدام الخيوط ( pthread ). هذه النسخة موجودة في الفرع الرئيسي ، لكن الكود هناك مخيف جدًا.

يعمل البرنامج في أربعة خيوط: خيط واحد يأخذ إطارات من الكاميرا ويبدأ الكاشف على NCS ؛ يقرأ التيار الثاني البيانات من محددات المدى ؛ الخيط الثالث يراقب التبديل ويضبط المتغير العام is_runningعلىfalseإذا كانت متوقفة ؛ الخيط الرئيسي مسؤول عن سلوك الروبوت والتحكم المؤازر. للخيوط مؤشرات مشتركة مع الخيط الرئيسي ، والتي من خلالها يكتبون نتائج عملهم. اقتصرت المتجهات التي تخزن المعلومات حول الوجوه التي عثر عليها الكاشف على كائن المزامنة ، وأعلنت المتغيرات الشائعة الأخرى الأبسط على أنها ذرية. لتنسيق تدفق كاشف الوجه مع الخيط الرئيسي ، هناك إشارة face_processedيتم إعادة تعيينها عندما تأتي نتيجة جديدة من الكاشف ، وترتفع عندما يستخدم الخيط الرئيسي هذه النتيجة لتحديد سلوك - وهذا ضروري حتى لا تتم معالجة البيانات القديمة التي قد لا تكون ذات صلة بعد الانتقال. عملت نسخة

ROS

مع التدفقات بشكل جيد ، لكنني بدأت كل هذا لتعلم شيء ما ، فلماذا لا تتقن في نفس الوقتروس ؟ لقد سمعت هذا الإطار لفترة طويلة ، واضطررت حتى للعمل معه قليلاً على hackathon ، لذلك في النهاية قررت إعادة كتابة جميع التعليمات البرمجية على ROS. يكمن هذا الإصدار من الشفرة في الفرع الافتراضي من ros ويبدو أكثر لائقًا. من الواضح أن التنفيذ على ROS سيكون على الأغلب أبطأ من التنفيذ على التدفقات بسبب النفقات العامة لإرسال الرسائل وكل شيء آخر - السؤال الوحيد هو كم؟

مفهوم ROS
ROS (Robot Operating System) — , , , .

, , , (node), , , .

(topic) (message) , - .

— (service). , , . « », .

.msg .srv . .

ROS .

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

أبسط عقدة switch_node،، تراقب حالة التبديل. بمجرد إيقاف تشغيل التبديل ، تبدأ العقدة في إرسال رسائل غير إخبارية من النوع boolفي الموضوع إلى الرسائل غير المرغوب فيها terminator. هذه إشارة إلى العقدة الرئيسية بأن الوقت قد حان لإكمال العمل.

العقدة الثانية ، sensor_nodeتقرأ بشكل دوري قراءات كل من محددات المدى IR وترسلها إلى الموضوع في sensor_stateرسالة واحدة. كما أن هذه العقدة مسؤولة عن معالجة الإشارة: التحجيم بواسطة عوامل المعايرة والمتوسط ​​المتحرك.

العقدة الثالثةcamera_nodeإنه مسؤول عن كل ما يتعلق بالوجوه: فهو يأخذ صورًا من الكاميرا ، ويعالجها ، ويتلقى نتائج الكاشف ، ويمررها من خلال المتتبع ، ثم يجد الوجه الأقرب إلى مركز الإطار - لا يستخدم الروبوت الباقي على أي حال ، لكنك تريد إنشاء رسائل أصغر. تحتوي الرسائل التي ترسلها العقدة إلى الموضوع camera_stateعلى رقم الإطار ، وحقيقة وجود وجه (لأنك تحتاج أيضًا إلى معرفة عدم وجود وجه) ، والإحداثيات النسبية للركن العلوي الأيسر ، وعرض وارتفاع الوجه. هكذا يبدو وصف نوع الرسالة في الملف DetectionBox.msg:

int64 count
bool present
float32 x
float32 y
float32 width
float32 height

العقدة الرابعة ، servo_nodeهي المسؤولة عن الماكينات. أولاً ، إنها تدعم خدمة servo_actionتسمح بتنفيذ أحد الإجراءات بواسطة أجهزة مؤازرة من خلال رقمها: لوضع العقدة بأكملها في حالتها الأولية (الحاجبين ، البنك ، الرأس ، إيقاف الهيكل) ؛ نقل الرأس إلى حالته الأولية ؛ هز الجرة. يصور بحاجب واحد من ثلاثة تعبيرات (جيد ، محايد ، شرير). ثانيًا ، باستخدام الخدمة ، servo_speedيمكنك تعيين سرعات جديدة لكلتا العجلتين عن طريق إرسالهما في الطلب. لا تعيد كلتا الخدمتين أي شيء. أخيرًا ، هناك خدمة servo_head_platformتسمح لك بتدوير الرأس و / أو الهيكل بزاوية معينة بالنسبة إلى الوضع الحالي. تعود هذه الخدمة trueإذا كان من الممكن إدارة الرأس جزئيًا على الأقل ، وfalseخلاف ذلك ، في حالة عندما يكون الرأس بالفعل على حدود الزاوية المسموح بها ، ونحن نحاول تحويلها إلى أبعد من ذلك. إذا كانت الزوايا في الطلب غير صفرية ، فإن الخدمة تدور بشكل غير متزامن ، كما هو موضح أعلاه. في الحلقة الرئيسية ، لا تفعل العقدة المؤازرة أي شيء.

هنا ، على سبيل المثال ، وصف الخدمة servo_head_platform:

float32 head_delta
float32 platform_delta
---
bool head_success

تدعم كل عقد مدرجة خدمة terminate_{switch, camera, sensor, servo}بطلب استجابة فارغ ، مما يوقف تشغيل العقدة. يتم تنفيذه بهذه الطريقة:

بعض التعليمات البرمجية
...
std::atomic_bool is_running; // global

bool terminate_node(std_srvs::Empty::Request &req, std_srvs::Empty::Response &ignored) {
    is_running = false;
    return true;
}

int main(int argc, char **argv) {
    is_running = true;
    ...
    while (is_running && ros::ok()) {
        // do stuff
    }
    ...
}


تحتوي العقدة على متغير عام is_running، تحدد قيمته الدورة الرئيسية للعقدة. تقوم الخدمة ببساطة بإعادة تعيين هذا المتغير ، ويتم مقاطعة الحلقة الرئيسية.

هناك أيضًا عقدة رئيسية يتم beggar_botفيها تنفيذ المنطق الأساسي للروبوت. قبل بدء الحلقة الرئيسية، فإنه يشترك في المواضيع sensor_stateو camera_stateيحفظ محتويات الرسائل في المتغيرات العالمية في وظائف الاستدعاء. كما أنه مشترك في الموضوع terminator، وهو رد الاتصال الذي يعيد تعيين العلم is_running، ويقطع الحلقة الرئيسية. أيضًا ، قبل بدء الدورة ، تعلن العقدة عن واجهات خدمات العقدة المؤازرة وتنتظر بضع ثوان حتى تبدأ العقد الأخرى. بعد انتهاء الحلقة الرئيسية ، تستدعي هذه العقدة الخدماتterminate_{switch, camera, sensor, servo}، وبالتالي إيقاف تشغيل جميع العقد الأخرى ، ثم إيقاف تشغيلها بنفسها. أي عندما يتم إيقاف تشغيل المفتاح ، تكمل جميع العقد الخمس العملية.

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

يسمح لك ROS أيضًا بتشغيل جميع عقد الروبوت بشكل ملائم. للقيام بذلك ، تحتاج إلى كتابة ملف تشغيل .lunch يسرد جميع العقد والسمات الأخرى للروبوت بتنسيق xml ، ثم قم بتشغيل الأمر:

roslaunch beggar_bot robot.launch

ROS مقابل pthread

الآن ، أخيرًا ، يمكنك مقارنة سرعة إصدار ROS وإصدار pthread. أفعل ذلك بهذه الطريقة: يعتبر الخيط / العقدة المسؤولة عن العمل مع الكاميرا FPS (كأبطأ عنصر) ، شريطة أن يعمل كل شيء آخر أيضًا. بالنسبة لإصدار pthread ، كنت أحصل دائمًا على FPS 9.99 أو ما شابه ، بالنسبة لإصدار ROS ، تم إصداره حوالي 8.3. في الواقع ، هذا يكفي تمامًا لمثل هذه اللعبة ، لكن النفقات العامة ملحوظة تمامًا.

سلوك الروبوت


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

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

لمزامنة هذه الوظيفة مع الموضوع camera_state.face_processed، كما في الإصدار مع الجداول. الفكرة هي نفسها - نريد معالجة البيانات مرة واحدة فقط ، لأن الروبوت يتحرك باستمرار. تنتظر الوظيفة أولاً حتى يؤدي رد الموضوع مع عمليات الكشف إلى تقليل علامة معالجة الإطار الأخير. أثناء انتظارها ، تتصل باستمرار ros::spinOnce()لتلقي رسائل جديدة (بشكل عام ، يجب أن يتم ذلك في أي مكان يتوقع فيه البرنامج بيانات جديدة). إذا كان هناك وجه في الإطار ، يتم حساب الزوايا ، والتي تحتاج إلى تدوير المنصة والرأس - يمكن القيام بذلك من خلال معرفة الإحداثيات النسبية لمركز الوجه ومجال رؤية الكاميرا أفقيًا وعموديًا. بعد ذلك ، يمكنك الاتصال بالخدمة servo_head_platformوتحريك الروبوت.

هناك نقطة واحدة خفية: معلومات حول وضع الوجه متخلفة عن الحركة الفعلية للوجه وقد تتخلف عن حركات الروبوت نفسه. لذلك ، يمكن للروبوت أن يبالغ في تقدير زاوية الدوران ، والتي بسببها يبدأ الرأس في التحرك ذهابًا وإيابًا مع السعة المتزايدة. لمنع حدوث ذلك ، أقوم بالتأخير بعد النقل (300 مللي ثانية) ، وأتخطى أيضًا إطارًا واحدًا بعد النقل. لنفس الغرض ، يتم ضرب زوايا دوران الهيكل والرأس بعامل 0.8 (تكون المكونات P لوحدة التحكم PID منطقية ).

وظيفةfollow_faceيعيد حالة الشخص. يمكن للشخص: أن يكون غائبًا ، وأن يكون قريبًا بما فيه الكفاية من المركز ، وأن يكون بعيدًا جدًا عن الروبوت ؛ خيار آخر - عندما قمنا بتشغيل الروبوت ولا نعرف ما حدث للوجه (في عملية البحث) ؛ لا تزال هناك حالة نادرة عندما يكون الرأس على الحدود ، وهذا هو السبب في أنه من المستحيل اللجوء إلى الوجه.

يحدث شيء بسيط إلى حد ما في الحلقة الرئيسية:

  1. اتصل follow_faceحتى يحصل الشخص على حالة معينة (أي ، باستثناء "في عملية البحث"). في نهاية هذه الخطوة ، سينظر الروبوت مباشرة في الوجه.
  2. إذا تم العثور على الوجه وهو قريب:
    1. هز العلبة
    2. ابحث عن الوجه مرة أخرى ؛
    3. إذا كان الوجه في مكانه ، قم بعمل تعبير جيد بالحاجبين وهز البرطمان مرة أخرى ؛
    4. إذا اختفى الوجه ، قم بعمل تعبير غاضب بالحاجبين.
    5. إستدر ، إذهب إلى بداية الدورة.

  3. ( ) — :
    1. — ( , , );
    2. , [90,180][180,90];
    3. , [0,90];
    4. (, ), [90,180][180,90]؛


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

استنتاج


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

كمكافأة - المعرض:








All Articles