جدول المحتويات:
- الخطوة 1: تجميع المكونات
- الخطوة 2: تثبيت OpenCV على Raspberry Pi وإعداد جهاز العرض عن بعد
- الخطوة 3: توصيل الأجزاء معًا
- الخطوة 4: الاختبار الأول
- الخطوة 5: الكشف عن خطوط المسار وحساب خط العنوان
- الخطوة 6: تطبيق تحكم PD
- الخطوة 7: النتائج
فيديو: سيارة ذاتية التحكم في المسار باستخدام Raspberry Pi و OpenCV: 7 خطوات (بالصور)
2024 مؤلف: John Day | [email protected]. آخر تعديل: 2024-01-30 07:38
في هذه التعليمات ، سيتم تنفيذ روبوت لحفظ الممرات المستقلة وسيمر عبر الخطوات التالية:
- تجميع الأجزاء
- متطلبات تثبيت البرنامج
- تجميع الأجهزة
- الاختبار الأول
- كشف خطوط الحارة وعرض الخط الإرشادي باستخدام OpenCV
- تنفيذ وحدة تحكم PD
- نتائج
الخطوة 1: تجميع المكونات
توضح الصور أعلاه جميع المكونات المستخدمة في هذا المشروع:
- سيارة RC: حصلت على سيارتي من متجر محلي في بلدي. وهي مجهزة بثلاثة محركات (2 للاختناق و 1 للتوجيه). العيب الرئيسي لهذه السيارة هو أن التوجيه محدود بين "بدون توجيه" و "توجيه كامل". بمعنى آخر ، لا يمكن توجيهها بزاوية معينة ، على عكس سيارات RC ذات التوجيه المؤازر. يمكنك العثور على طقم سيارة مماثل مصمم خصيصًا لـ Raspberry Pi من هنا.
- Raspberry pi 3 model b +: هذا هو عقل السيارة الذي سيتعامل مع الكثير من مراحل المعالجة. وهو يعتمد على معالج رباعي النواة 64 بت بسرعة 1.4 جيجاهرتز. حصلت على خاصتي من هنا.
- وحدة كاميرا Raspberry pi 5 mp: تدعم تسجيل 1080p @ 30 fps و 720 p @ 60 fps و 640x480p 60/90. كما أنه يدعم الواجهة التسلسلية التي يمكن توصيلها مباشرة بـ raspberry pi. إنه ليس الخيار الأفضل لتطبيقات معالجة الصور ولكنه كافٍ لهذا المشروع بالإضافة إلى أنه رخيص جدًا. حصلت على خاصتي من هنا.
- محرك المحرك: يستخدم للتحكم في اتجاهات وسرعات محركات التيار المستمر. يدعم التحكم في محركي تيار مستمر في لوحة واحدة ويمكن أن يتحمل 1.5 أمبير.
- باور بانك (اختياري): لقد استخدمت بنك طاقة (مصنّف عند 5 فولت ، 3 أمبير) لتشغيل raspberry pi بشكل منفصل. يجب استخدام محول تنحي (محول باك: تيار خرج 3A) من أجل تشغيل raspberry Pi من مصدر واحد.
- بطارية LiPo 3s (12 V): تشتهر بطاريات ليثيوم بوليمر بأدائها الممتاز في مجال الروبوتات. يتم استخدامه لتشغيل سائق المحرك. اشتريت خاصتي من هنا.
- ذكر إلى ذكر وأنثى أسلاك العبور.
- شريط مزدوج الجوانب: يستخدم لتركيب المكونات على سيارة RC.
- الشريط الأزرق: هذا عنصر مهم جدًا في هذا المشروع ، حيث يتم استخدامه لعمل خطي المسارين اللذين ستسير فيهما السيارة. يمكنك اختيار أي لون تريده ولكني أوصي باختيار ألوان مختلفة عن تلك الموجودة في البيئة المحيطة.
- العلاقات البريديه والقضبان الخشبية.
- مفك براغي.
الخطوة 2: تثبيت OpenCV على Raspberry Pi وإعداد جهاز العرض عن بعد
هذه الخطوة مزعجة بعض الشيء وستستغرق بعض الوقت.
OpenCV (Open Source Computer Vision) هي مكتبة برمجيات رؤية الكمبيوتر والتعلم الآلي مفتوحة المصدر. تحتوي المكتبة على أكثر من 2500 خوارزمية محسّنة. اتبع هذا الدليل المباشر للغاية لتثبيت openCV على raspberry pi بالإضافة إلى تثبيت نظام التشغيل raspberry pi (إذا لم تقم بذلك). يرجى ملاحظة أن عملية بناء الـ openCV قد تستغرق حوالي 1.5 ساعة في غرفة مُبردة جيدًا (نظرًا لأن درجة حرارة المعالج سترتفع جدًا!) لذا تناول بعض الشاي وانتظر بصبر: د.
بالنسبة للشاشة عن بُعد ، اتبع أيضًا هذا الدليل لإعداد الوصول عن بُعد إلى raspberry pi من جهاز Windows / Mac.
الخطوة 3: توصيل الأجزاء معًا
توضح الصور أعلاه الروابط بين raspberry pi ووحدة الكاميرا ومحرك المحرك. يرجى ملاحظة أن المحركات التي استخدمتها تمتص 0.35 أمبير عند 9 فولت لكل منها مما يجعلها آمنة لسائق المحرك لتشغيل 3 محركات في نفس الوقت. وبما أنني أريد التحكم في سرعة المحركين الخانقين (1 خلفي و 1 أمامي) بنفس الطريقة تمامًا ، فقد قمت بتوصيلهما بنفس المنفذ. ركبت سائق المحرك على الجانب الأيمن من السيارة باستخدام شريط مزدوج. بالنسبة لوحدة الكاميرا ، قمت بإدخال رابط مضغوط بين فتحات المسامير كما تظهر الصورة أعلاه. بعد ذلك ، أقوم بتثبيت الكاميرا على شريط خشبي حتى أتمكن من ضبط موضع الكاميرا كما أريد. حاول تثبيت الكاميرا في منتصف السيارة قدر الإمكان. أوصي بوضع الكاميرا على ارتفاع 20 سم على الأقل فوق سطح الأرض حتى يتحسن مجال الرؤية أمام السيارة. مخطط فريتزينج مرفق أدناه.
الخطوة 4: الاختبار الأول
اختبار الكاميرا:
بمجرد تثبيت الكاميرا وإنشاء مكتبة OpenCV ، حان الوقت لاختبار صورتنا الأولى! سنلتقط صورة من pi cam ونحفظها باسم "original.jpg". يمكن أن يتم ذلك بطريقتين:
1. استخدام أوامر المحطة الطرفية:
افتح نافذة طرفية جديدة واكتب الأمر التالي:
raspistill -o original.jpg
سيؤدي ذلك إلى التقاط صورة ثابتة وحفظها في دليل "/pi/original.jpg".
2. استخدام أي Python IDE (أستخدم IDLE):
افتح رسمًا جديدًا واكتب الكود التالي:
استيراد السيرة الذاتية 2
video = cv2. VideoCapture (0) بينما True: ret ، frame = video.read () frame = cv2.flip (frame، -1) # تستخدم لقلب الصورة رأسياً cv2.imshow ('original'، frame) cv2. imwrite ('original.jpg'، frame) key = cv2.waitKey (1) if key == 27: break video.release () cv2.destroyAllWindows ()
دعونا نرى ما حدث في هذا الكود. السطر الأول هو استيراد مكتبة OpenCV الخاصة بنا لاستخدام جميع وظائفها. تبدأ وظيفة VideoCapture (0) في بث فيديو مباشر من المصدر الذي تحدده هذه الوظيفة ، وفي هذه الحالة يكون 0 مما يعني كاميرا raspi. إذا كان لديك عدة كاميرات ، فيجب وضع أرقام مختلفة. video.read () سيقرأ كل إطار يأتي من الكاميرا ويحفظه في متغير يسمى "الإطار". وظيفة flip () ستقلب الصورة فيما يتعلق بالمحور y (عموديًا) لأنني أقوم بتركيب الكاميرا بشكل عكسي. سيعرض imshow () إطاراتنا برأسها كلمة "original" وسيحفظ imwrite () صورتنا على أنها original.jpg. سوف ينتظر waitKey (1) لمدة 1 مللي ثانية حتى يتم الضغط على أي زر لوحة مفاتيح ويعيد رمز ASCII الخاص به. إذا تم الضغط على زر escape (esc) ، فسيتم إرجاع القيمة العشرية 27 وسيتم كسر الحلقة وفقًا لذلك. video.release () سيتوقف عن التسجيل وسيغلق تدمير AllWindows () كل صورة يتم فتحها بواسطة وظيفة imshow ().
أوصي باختبار صورتك بالطريقة الثانية للتعرف على وظائف OpenCV. يتم حفظ الصورة في دليل "/pi/original.jpg". تظهر الصورة الأصلية التي التقطتها الكاميرا أعلاه.
اختبار المحركات:
هذه الخطوة ضرورية لتحديد اتجاه دوران كل محرك. أولاً ، دعنا نحصل على مقدمة موجزة عن مبدأ عمل سائق المحرك. توضح الصورة أعلاه سائق المحرك. تمكين A ، الإدخال 1 والمدخل 2 مرتبطان بالتحكم في المحرك A. تمكين B والمدخلات 3 والمدخلات 4 مرتبطة بالتحكم في المحرك B. يتم إنشاء التحكم في الاتجاه بواسطة جزء "الإدخال" ويتم إنشاء التحكم في السرعة بواسطة جزء "تمكين". للتحكم في اتجاه المحرك A على سبيل المثال ، اضبط الإدخال 1 على HIGH (3.3 فولت في هذه الحالة نظرًا لأننا نستخدم raspberry pi) وقم بتعيين الإدخال 2 إلى LOW ، سيدور المحرك في اتجاه معين وبضبط القيم المعاكسة إلى المدخلات 1 والمدخل 2 ، سوف يدور المحرك في الاتجاه المعاكس. إذا كان الإدخال 1 = الإدخال 2 = (مرتفع أو منخفض) ، فلن يدور المحرك. قم بتمكين المسامير بأخذ إشارة إدخال تعديل عرض النبض (PWM) من التوت (من 0 إلى 3.3 فولت) وتشغيل المحركات وفقًا لذلك. على سبيل المثال ، تعني إشارة 100٪ PWM أننا نعمل على السرعة القصوى و 0٪ تعني إشارة PWM أن المحرك لا يدور. يستخدم الكود التالي لتحديد اتجاهات المحركات واختبار سرعاتها.
وقت الاستيراد
استيراد RPi. GPIO كـ GPIO GPIO.set warnings (False) # دبابيس محرك التوجيهeering_enable = 22 # الدبوس المادي 15 in1 = 17 # الدبوس المادي 11 in2 = 27 # الدبوس المادي 13 # دبابيس المحركات الخانقة throttle_enable = 25 # الدبوس المادي 22 in3 = 23 # الدبوس المادي 16 in4 = 24 # الدبوس المادي 18 GPIO.setmode (GPIO. BCM) # استخدم ترقيم GPIO بدلاً من الترقيم الفعلي GPIO.setup (in1، GPIO.out) GPIO.setup (in2، GPIO.out) GPIO. setup (in3، GPIO.out) GPIO.setup (in4، GPIO.out) GPIO.setup (throttle_enable، GPIO.out) GPIO.setup (eering_enable، GPIO.out) # التحكم في محرك التوجيه GPIO.output (in1، GPIO. عالية) GPIO.output (in2 ، GPIO. LOW) التوجيه = GPIO. PWM (توجيه_تمكين ، 1000) # اضبط تردد التبديل على 1000 هرتز للتوجيه. توقف () # التحكم في المحركات الخانقة GPIO.output (in3 ، GPIO. HIGH).output (in4، GPIO. LOW) دواسة الوقود = GPIO. PWM (throttle_enable، 1000) # اضبط تردد التبديل على 1000 Hz throttle.stop () time.sleep (1) throttle.start (25) # يبدأ تشغيل المحرك عند 25 ٪ إشارة PWM-> (0.25 * جهد البطارية) - السائق بدء فقدان التوجيه (100) # يبدأ المحرك عند إشارة 100٪ PWM-> (1 * جهد البطارية) - وقت فقدان السائق. النوم (3) throttle.stop ()eering.stop ()
سيعمل هذا الرمز على تشغيل محركات الاختناق ومحرك التوجيه لمدة 3 ثوانٍ ثم إيقافها. يمكن تحديد (فقدان السائق) باستخدام مقياس الفولتميتر. على سبيل المثال ، نعلم أن إشارة PWM بنسبة 100٪ يجب أن تعطي جهد البطارية بالكامل عند طرف المحرك. ولكن ، من خلال ضبط PWM على 100٪ ، وجدت أن السائق يتسبب في انخفاض 3 فولت وأن المحرك يحصل على 9 فولت بدلاً من 12 فولت (بالضبط ما أحتاجه!). الخسارة ليست خطية ، أي أن الخسارة عند 100٪ تختلف اختلافًا كبيرًا عن الخسارة عند 25٪. بعد تشغيل الكود أعلاه ، كانت نتائجي على النحو التالي:
نتائج الخانق: إذا كانت in3 = عالية و in4 = منخفضة ، فإن المحركات الخانقة سيكون لها دوران على مدار الساعة (CW) ، أي أن السيارة ستتحرك للأمام. خلاف ذلك ، ستتحرك السيارة للخلف.
نتائج التوجيه: إذا كان in1 = HIGH و in2 = LOW ، فسوف يدور محرك التوجيه عند أقصى اليسار ، أي أن السيارة ستتوجه إلى اليسار. خلاف ذلك ، سوف تتجه السيارة إلى اليمين. بعد بعض التجارب ، وجدت أن محرك التوجيه لن يدور إذا لم تكن إشارة PWM 100٪ (أي أن المحرك سيوجه إما بالكامل إلى اليمين أو كليًا إلى اليسار).
الخطوة 5: الكشف عن خطوط المسار وحساب خط العنوان
في هذه الخطوة ، سيتم شرح الخوارزمية التي ستتحكم في حركة السيارة. الصورة الأولى توضح العملية برمتها. مدخلات النظام عبارة عن صور ، والمخرج هو ثيتا (زاوية التوجيه بالدرجات). لاحظ أن المعالجة تتم على صورة واحدة وسوف تتكرر على جميع الإطارات.
الة تصوير:
ستبدأ الكاميرا في تسجيل مقطع فيديو بدقة (320 × 240). أوصي بخفض الدقة حتى تتمكن من الحصول على معدل إطارات أفضل (fps) حيث سيحدث انخفاض fps بعد تطبيق تقنيات المعالجة على كل إطار. سيكون الكود أدناه هو الحلقة الرئيسية للبرنامج وسيضيف كل خطوة فوق هذا الرمز.
استيراد السيرة الذاتية 2
import numpy as np video = cv2. VideoCapture (0) video.set (cv2. CAP_PROP_FRAME_WIDTH، 320) # اضبط العرض على 320 بكسل video.set (cv2. CAP_PROP_FRAME_HEIGHT، 240) # اضبط الارتفاع على 240 p # الحلقة أثناء صواب: ret، frame = video.read () frame = cv2.flip (frame، -1) cv2.imshow ("original"، frame) key = cv2.waitKey (1) if key == 27: break video.release () cv2.destroyAllWindows ()
سيظهر الرمز هنا الصورة الأصلية التي تم الحصول عليها في الخطوة 4 ويظهر في الصور أعلاه.
التحويل إلى مساحة ألوان HSV:
الآن بعد أخذ تسجيل الفيديو كإطارات من الكاميرا ، فإن الخطوة التالية هي تحويل كل إطار إلى مساحة ألوان Hue و Saturation و Value (HSV). الميزة الرئيسية للقيام بذلك هي القدرة على التمييز بين الألوان حسب مستوى لمعانها. وهنا شرح جيد لمساحة اللون HSV. يتم التحويل إلى HSV من خلال الوظيفة التالية:
def convert_to_HSV (إطار):
hsv = cv2.cvtColor (frame، cv2. COLOR_BGR2HSV) cv2.imshow ("HSV"، hsv) تُرجع hsv
سيتم استدعاء هذه الوظيفة من الحلقة الرئيسية وستقوم بإرجاع الإطار في مساحة ألوان HSV. يظهر الإطار الذي حصلت عليه في مساحة ألوان HSV أعلاه.
كشف اللون الأزرق والحواف:
بعد تحويل الصورة إلى مساحة لونية HSV ، حان الوقت لاكتشاف اللون الذي نهتم به فقط (أي اللون الأزرق لأنه لون خطوط الحارة). لاستخراج اللون الأزرق من إطار HSV ، يجب تحديد نطاق من الصبغة والتشبع والقيمة. راجع هنا للحصول على فكرة أفضل عن قيم HSV. بعد بعض التجارب ، تظهر الحدود العليا والسفلى للون الأزرق في الكود أدناه. ولتقليل التشوه الكلي في كل إطار ، يتم اكتشاف الحواف فقط باستخدام كاشف الحواف. تم العثور على المزيد حول الحافة الحادة هنا. تتمثل القاعدة الأساسية في تحديد معلمات وظيفة Canny () بنسبة 1: 2 أو 1: 3.
def Discover_edges (الإطار):
Lower_blue = np.array ([90، 120، 0]، dtype = "uint8") # الحد الأدنى للون الأزرق upper_blue = np.array ([150، 255، 255]، dtype = "uint8") # حد أعلى من قناع اللون الأزرق = cv2.inRange (hsv، Lower_blue، upper_blue) # سيقوم هذا القناع بتصفية كل شيء ما عدا الأزرق
سيتم استدعاء هذه الوظيفة أيضًا من الحلقة الرئيسية التي تأخذ إطار مساحة اللون HSV كمعامل وتعيد الإطار ذي الحواف. الإطار ذو الحواف الذي حصلت عليه موجود أعلاه.
حدد منطقة الاهتمام (ROI):
يعد اختيار منطقة الاهتمام أمرًا بالغ الأهمية للتركيز فقط على منطقة واحدة من الإطار. في هذه الحالة ، لا أريد أن ترى السيارة الكثير من العناصر في البيئة. أريد فقط أن تركز السيارة على خطوط الحارة وأن تتجاهل أي شيء آخر. ملاحظة: يبدأ نظام الإحداثيات (محور x و y) من الزاوية اليسرى العليا. بمعنى آخر ، تبدأ النقطة (0 ، 0) من الزاوية اليسرى العليا. المحور y هو الارتفاع والمحور x هو العرض. يحدد الكود أدناه منطقة الاهتمام للتركيز فقط على النصف السفلي من الإطار.
def region_of_interest (الحواف):
height، width = edges.shape # استخراج ارتفاع وعرض الحواف قناع الإطار = np.zeros_like (الحواف) # إنشاء مصفوفة فارغة بنفس أبعاد إطار الحواف # فقط قم بالتركيز على النصف السفلي من الشاشة # حدد إحداثيات 4 نقاط (أسفل اليسار ، أعلى اليسار ، أعلى اليمين ، أسفل اليمين) مضلع = np.array (
ستأخذ هذه الوظيفة الإطار ذي الحواف كمعلمة وترسم مضلعًا بأربع نقاط محددة مسبقًا. سوف يركز فقط على ما بداخل المضلع ويتجاهل كل شيء خارجه. يظهر إطار منطقة الاهتمام أعلاه.
كشف المقاطع الخطية:
يتم استخدام Hough transform لاكتشاف مقاطع الخط من إطار ذي حواف. Hough transform هي تقنية لاكتشاف أي شكل في شكل رياضي. يمكنه اكتشاف أي كائن تقريبًا حتى لو تم تشويهه وفقًا لعدد معين من الأصوات. يتم عرض مرجع كبير لتحويل هوغ هنا. بالنسبة لهذا التطبيق ، تُستخدم وظيفة cv2. HoughLinesP () لاكتشاف الخطوط في كل إطار. المعلمات الهامة التي تتخذها هذه الوظيفة هي:
cv2. HoughLinesP (frame، rho، theta، min_threshold، minLineLength، maxLineGap)
- الإطار: هو الإطار الذي نريد اكتشاف الخطوط فيه.
- rho: هي دقة المسافة بالبكسل (عادةً ما تكون = 1)
- ثيتا: الدقة الزاوية بالتقدير الدائري (دائمًا = np.pi / 180 ~ 1 درجة)
- min_threshold: الحد الأدنى من التصويت الذي يجب أن تحصل عليه حتى يتم اعتباره سطرًا
- minLineLength: الحد الأدنى لطول الخط بالبكسل. أي سطر أقصر من هذا الرقم لا يعتبر خطاً.
- maxLineGap: أقصى فجوة بالبكسل بين سطرين يتم التعامل معها على أنها سطر واحد. (لا يتم استخدامه في حالتي لأن خطوط الممر التي أستخدمها لا تحتوي على أي فجوة).
ترجع هذه الدالة نقاط نهاية الخط. يتم استدعاء الوظيفة التالية من الحلقة الرئيسية الخاصة بي لاكتشاف الخطوط باستخدام تحويل هوغ:
def Discover_line_segments (cropped_edges):
rho = 1 ثيتا = np.pi / 180 min_threshold = 10 line_segments = cv2. HoughLinesP (cropped_edges، rho، theta، min_threshold، np.array ()، minLineLength = 5، maxLineGap = 0) عودة line_segments
متوسط الانحدار والتقاطع (م ، ب):
تذكر أن معادلة الخط معطاة بالصيغة y = mx + b. حيث m هو ميل الخط المستقيم و b هو الجزء المقطوع من المحور y. في هذا الجزء ، سيتم حساب متوسط المنحدرات وتقاطعات مقاطع الخط المكتشفة باستخدام تحويل هوغ. قبل القيام بذلك ، دعنا نلقي نظرة على صورة الإطار الأصلية الموضحة أعلاه. يبدو أن الممر الأيسر يتجه لأعلى لذا فإن ميله سلبي (تذكر نقطة بداية نظام الإحداثيات؟). بعبارة أخرى ، يحتوي خط الممر الأيسر على x1 <x2 و y2 x1 و y2> y1 مما يعطي ميلًا موجبًا. لذلك ، تعتبر جميع الخطوط ذات الميل الموجب نقاطًا للممر الأيمن. في حالة الخطوط العمودية (x1 = x2) ، سيكون الميل إلى ما لا نهاية. في هذه الحالة ، سنتخطى جميع الخطوط الرأسية لمنع حدوث خطأ. لإضافة المزيد من الدقة إلى هذا الاكتشاف ، يتم تقسيم كل إطار إلى منطقتين (يمين ويسار) من خلال خطين حدوديين. جميع نقاط العرض (نقاط المحور x) الأكبر من خط الحد الأيمن ، مرتبطة بحساب الممر الأيمن. وإذا كانت جميع نقاط العرض أقل من خط الحد الأيسر ، فإنها ترتبط بحساب الممر الأيسر. تأخذ الوظيفة التالية الإطار تحت المعالجة ومقاطع الممر التي تم اكتشافها باستخدام تحويل Hough وتعيد متوسط الانحدار والاعتراض لخطين من الحارات.
def average_slope_intercept (الإطار ، line_segments):
lane_lines = إذا كانت line_segments هي بلا: طباعة ("لم يتم اكتشاف مقطع خط") إرجاع خطوط lane_lines الارتفاع والعرض _ = frame.shape left_fit = right_fit = الحدود = left_region_boundary = العرض * (1 - الحدود) right_region_boundary = العرض * حدود line_segment في line_segments: لـ x1، y1، x2، y2 في line_segment: إذا كانت x1 == x2: طباعة ("تخطي الخطوط العمودية (المنحدر = ما لا نهاية)") تابع الملاءمة = np.polyfit ((x1، x2) ، (y1، y2)، 1) الميل = (y2 - y1) / (x2 - x1) التقاطع = y1 - (الميل * x1) إذا كان الميل <0: إذا كان x1 <left_region_boundary و x2 right_region_boundary و x2> right_region_boundary: right_fit. إلحاق ((منحدر ، تقاطع)) left_fit_average = np.average (left_fit ، محور = 0) إذا كان len (left_fit)> 0: lane_lines.append (make_points (frame، left_fit_average)) right_fit_average = np.average (right_fit، محور = 0) إذا كان len (right_fit)> 0: lane_lines.append (make_points (frame، right_fit_average)) # lane_lines عبارة عن مصفوفة ثنائية الأبعاد تتكون من إحداثيات خطوط الحارة اليمنى واليسرى # على سبيل المثال: lan e_lines =
make_points () هي دالة مساعدة لوظيفة average_slope_intercept () والتي ستعيد إحداثيات حدود خطوط الممر (من أسفل الإطار إلى منتصفه).
def make_points (إطار ، خط):
الارتفاع ، العرض ، _ = الإطار ، منحدر الشكل ، التقاطع = الخط y1 = الارتفاع # أسفل الإطار y2 = int (y1 / 2) # ضع نقاطًا من منتصف الإطار لأسفل إذا كان المنحدر == 0: المنحدر = 0.1 x1 = int ((y1 - intercept) / slope) x2 = int ((y2 - intercept) / slope) return
لمنع القسمة على 0 ، يتم تقديم شرط. إذا كان الميل = 0 وهو ما يعني y1 = y2 (الخط الأفقي) ، أعط المنحدر قيمة بالقرب من 0. لن يؤثر هذا على أداء الخوارزمية كما سيمنع حدوث حالة مستحيلة (قسمة على 0).
لعرض خطوط الحارة على الإطارات ، يتم استخدام الوظيفة التالية:
تعريف display_lines (الإطار ، الخطوط ، line_color = (0 ، 255 ، 0) ، line_width = 6): # لون الخط (B ، G ، R)
line_image = np.zeros_like (frame) إذا لم تكن الأسطر بلا: للخط في الأسطر: لـ x1، y1، x2، y2 في السطر: cv2.line (line_image، (x1، y1)، (x2، y2)، line_color، line_width) line_image = cv2.addWeighted (frame، 0.8، line_image، 1، 1) إرجاع line_image
تأخذ الدالة cv2.addWeighted () المعلمات التالية وتُستخدم لدمج صورتين ولكن مع إعطاء وزن لكل واحدة.
cv2.addWeighted (image1، alpha، image2، beta، gamma)
وتحسب الصورة الناتجة باستخدام المعادلة التالية:
الإخراج = alpha * image1 + beta * image2 + gamma
مزيد من المعلومات حول دالة cv2.addWeighted () مشتقة هنا.
حساب وعرض سطر العنوان:
هذه هي الخطوة الأخيرة قبل تطبيق السرعات على محركاتنا. يعتبر سطر العنوان مسؤولاً عن إعطاء محرك التوجيه الاتجاه الذي يجب أن يدور فيه وإعطاء المحركات الخانقة السرعة التي ستعمل بها. حساب سطر العنوان هو حساب المثلثات الخالص ، يتم استخدام الدوال المثلثية tan و atan (tan ^ -1). بعض الحالات القصوى هي عندما تكتشف الكاميرا خطًا واحدًا فقط من الحارة أو عندما لا تكتشف أي خط. كل هذه الحالات موضحة في الوظيفة التالية:
def get_steering_angle (الإطار ، lane_lines):
height ، width ، _ = frame.shape if len (lane_lines) == 2: # إذا تم اكتشاف خطين من الممرات _، _، left_x2، _ = lane_lines [0] [0] # استخراج x2 الأيسر من lane_lines array _، _ ، right_x2، _ = lane_lines [1] [0] # extract right x2 من lane_lines array mid = int (width / 2) x_offset = (left_x2 + right_x2) / 2 - mid y_offset = int (height / 2) elif len (lane_lines) == 1: # إذا تم اكتشاف سطر واحد فقط x1، _، x2، _ = lane_lines [0] [0] x_offset = x2 - x1 y_offset = int (height / 2) elif len (lane_lines) == 0: # إذا لم يتم اكتشاف أي خط x_offset = 0 y_offset = int (height / 2) angle_to_mid_radian = math.atan (x_offset / y_offset) angle_to_mid_deg = int (angle_to_mid_radian * 180.0 / math.pi)
x_offset في الحالة الأولى هو مدى اختلاف المتوسط ((يمين × 2 + يسار × 2) / 2) عن منتصف الشاشة. يتم دائمًا اعتبار y_offset ارتفاعًا / 2. تُظهر الصورة الأخيرة أعلاه مثالاً على سطر العنوان. angle_to_mid_radians هي نفسها "ثيتا" الموضحة في الصورة السابقة أعلاه. إذا كانت عجلة القيادة = 90 ، فهذا يعني أن السيارة بها خط عنوان عمودي على خط "الارتفاع / 2" وأن السيارة ستتحرك للأمام بدون توجيه. إذا كانت عجلة القيادة> 90 ، يجب أن تتجه السيارة إلى اليمين وإلا يجب أن تتجه يسارًا. لعرض سطر العنوان ، يتم استخدام الوظيفة التالية:
def display_heading_line (frame ،eering_angle، line_color = (0، 0، 255)، line_width = 5)
عنوان_صورة = np.zeros_like (إطار) ارتفاع ، عرض ، _ = frame.shapeeering_angle_radian =eering_angle / 180.0 * math.pi x1 = int (width / 2) y1 = height x2 = int (x1 - height / 2 / math.tan (eering_angle_radian)) y2 = int (الارتفاع / 2) cv2.line (العنوان_صورة ، (x1 ، y1) ، (x2 ، y2) ، line_color ، line_width) العنوان_صورة = cv2.addWeighted (frame، 0.8 ،eader_image، 1، 1) إرجاع العنوان _ الصورة
تأخذ الوظيفة أعلاه الإطار الذي سيتم فيه رسم خط العنوان وزاوية التوجيه كإدخال. تقوم بإرجاع صورة سطر العنوان. يظهر إطار سطر العنوان الذي تم التقاطه في حالتي في الصورة أعلاه.
دمج كل التعليمات البرمجية معًا:
الكود جاهز الآن للتجميع. يُظهر الكود التالي الحلقة الرئيسية للبرنامج الذي يستدعي كل وظيفة:
استيراد السيرة الذاتية 2
استيراد numpy كـ np video = cv2. VideoCapture (0) video.set (cv2. CAP_PROP_FRAME_WIDTH ، 320) video.set (cv2. CAP_PROP_FRAME_HEIGHT ، 240) بينما True: ret ، frame = video.read () frame = cv2.flip (الإطار ، -1) # استدعاء الوظائف hsv = convert_to_HSV (frame) edges = det_edges (hsv) roi = region_of_interest (edges) line_segments = det_line_segments (roi) lane_lines = average_slope_intercept (frame، line_segments) lane_lines_image frame = display_lines frame = display_lines = get_steering_angle (frame، lane_lines) calling_image = display_heading_line (lane_lines_image ،eering_angle) key = cv2.waitKey (1) if key == 27: break video.release () cv2.destroyAllWindows ()
الخطوة 6: تطبيق تحكم PD
الآن لدينا زاوية التوجيه الخاصة بنا جاهزة لتغذية المحركات. كما ذكرنا سابقًا ، إذا كانت زاوية التوجيه أكبر من 90 ، فينبغي للسيارة أن تنعطف يمينًا وإلا ستنعطف يسارًا. لقد طبقت رمزًا بسيطًا يحول محرك التوجيه إلى اليمين إذا كانت الزاوية أعلى من 90 ويديره يسارًا إذا كانت زاوية التوجيه أقل من 90 بسرعة خنق ثابتة تبلغ (10٪ PWM) لكنني حصلت على الكثير من الأخطاء. الخطأ الرئيسي الذي حصلت عليه هو عندما تقترب السيارة من أي منعطف ، يعمل محرك التوجيه بشكل مباشر ولكن المحركات الخانقة تتعطل. حاولت زيادة سرعة الاختناق لتكون (20٪ PWM) عند المنعطفات لكنني انتهيت بخروج الروبوت من الممرات. كنت بحاجة إلى شيء يزيد من سرعة الاختناق كثيرًا إذا كانت زاوية التوجيه كبيرة جدًا وتزيد السرعة قليلاً إذا لم تكن زاوية التوجيه كبيرة ، ثم خفض السرعة إلى قيمة أولية عندما تقترب السيارة من 90 درجة (تتحرك بشكل مستقيم). كان الحل هو استخدام وحدة تحكم PD.
وحدة تحكم PID تعني وحدة تحكم تناسبية وتكاملية ومشتقة. يستخدم هذا النوع من أجهزة التحكم الخطية على نطاق واسع في تطبيقات الروبوتات. توضح الصورة أعلاه حلقة التحكم في التغذية المرتدة PID النموذجية. الهدف من وحدة التحكم هذه هو الوصول إلى "نقطة الضبط" بأكثر الطرق فعالية على عكس وحدات التحكم "تشغيل - إيقاف" التي تقوم بتشغيل أو إيقاف تشغيل المحطة وفقًا لبعض الشروط. يجب معرفة بعض الكلمات الرئيسية:
- نقطة الضبط: هي القيمة المطلوبة التي تريد أن يصل إليها نظامك.
- القيمة الفعلية: هي القيمة الفعلية التي يشعر بها المستشعر.
- الخطأ: هو الفرق بين نقطة الضبط والقيمة الفعلية (الخطأ = نقطة الضبط - القيمة الفعلية).
- المتغير المتحكم به: من اسمه ، المتغير الذي ترغب في التحكم فيه.
- Kp: ثابت نسبي.
- Ki: ثابت لا يتجزأ.
- Kd: ثابت مشتق.
باختصار ، تعمل حلقة نظام التحكم PID على النحو التالي:
- يحدد المستخدم نقطة الضبط اللازمة للوصول إلى النظام.
- تم حساب الخطأ (الخطأ = نقطة الضبط - فعلية).
- تولد وحدة التحكم P إجراء يتناسب مع قيمة الخطأ. (يزداد الخطأ ، يزيد إجراء P أيضًا)
- أنا وحدة التحكم سوف تدمج الخطأ بمرور الوقت مما يزيل خطأ الحالة المستقرة للنظام ولكنه يزيد من تجاوزه.
- D المتحكم هو ببساطة مشتق الوقت للخطأ. بمعنى آخر ، إنه ميل الخطأ. يقوم بعمل يتناسب مع مشتق الخطأ. وحدة التحكم هذه تزيد من استقرار النظام.
- سيكون خرج وحدة التحكم هو مجموع وحدات التحكم الثلاثة. سيصبح خرج وحدة التحكم 0 إذا أصبح الخطأ 0.
يمكن العثور على شرح رائع لوحدة التحكم PID هنا.
بالعودة إلى حارة حفظ السيارة ، كان المتغير المتحكم به هو سرعة الاختناق (نظرًا لأن التوجيه له حالتان فقط إما يمينًا أو يسارًا). يتم استخدام وحدة تحكم PD لهذا الغرض لأن إجراء D يزيد من سرعة الاختناق كثيرًا إذا كان تغيير الخطأ كبيرًا جدًا (أي انحراف كبير) ويبطئ السيارة إذا اقترب تغيير الخطأ هذا من 0. لقد قمت بالخطوات التالية لتنفيذ PD مراقب:
- اضبط نقطة الضبط على 90 درجة (أريد دائمًا أن تتحرك السيارة بشكل مستقيم)
- يحسب زاوية الانحراف عن الوسط
- يعطي الانحراف معلومتين: ما حجم الخطأ (حجم الانحراف) والاتجاه الذي يجب أن يتخذه محرك التوجيه (علامة الانحراف). إذا كان الانحراف موجبًا ، يجب أن تتجه السيارة إلى اليمين وإلا يجب أن تتجه يسارًا.
- نظرًا لأن الانحراف إما سالب أو موجب ، يتم تعريف متغير "خطأ" ويساوي دائمًا القيمة المطلقة للانحراف.
- يتم ضرب الخطأ بـ Kp ثابت.
- يخضع الخطأ لتفاضل زمني ويضرب بـ Kd ثابت.
- يتم تحديث سرعة المحركات وتبدأ الحلقة من جديد.
يتم استخدام الكود التالي في الحلقة الرئيسية للتحكم في سرعة المحركات الخانقة:
السرعة = 10 # سرعة التشغيل في٪ PWM
# المتغيرات المراد تحديثها كل حلقة lastTime = 0 lastError = 0 # PD ثوابت Kp = 0.4 Kd = Kp * 0.65 بينما صحيح: الآن = time.time () # متغير الوقت الحالي dt = الآن - انحراف الوقت الأخير =eering_angle - 90 # مكافئ إلى angle_to_mid_deg خطأ متغير = القيمة المطلقة (الانحراف) إذا كان الانحراف -5: # لا توجه إذا كان هناك انحراف في نطاق الخطأ بمقدار 10 درجات = 0 خطأ = 0 GPIO.output (in1 ، GPIO. LOW) GPIO.output (in2 ، GPIO. LOW)eering.stop () elif deviation> 5: # توجيه لليمين إذا كان الانحراف موجبًا GPIO.output (in1، GPIO. LOW) GPIO.output (in2، GPIO. HIGH)eering.start (100) elif deviation < -5: # توجيه يسارًا إذا كان الانحراف سلبيًا GPIO.output (in1، GPIO. HIGH) GPIO.output (in2، GPIO. LOW) توجيه.بدء (100) مشتق = kd * (خطأ - خطأ أخير) / dt متناسب = kp * خطأ PD = int (سرعة + مشتق + متناسب) spd = abs (PD) إذا كان spd> 25: spd = 25 throttle.start (spd) lastError = خطأ lastTime = time.time ()
إذا كان الخطأ كبيرًا جدًا (الانحراف عن الوسط مرتفعًا) ، تكون الإجراءات المتناسبة والمشتقة عالية مما يؤدي إلى سرعة اختناق عالية. عندما يقترب الخطأ من 0 (الانحراف عن الوسط منخفض) ، يعمل الإجراء المشتق بشكل عكسي (المنحدر سلبي) وتنخفض سرعة الاختناق للحفاظ على استقرار النظام. الكود الكامل مرفق أدناه.
الخطوة 7: النتائج
تظهر مقاطع الفيديو أعلاه النتائج التي حصلت عليها. إنها تحتاج إلى مزيد من الضبط والتعديلات الإضافية. كنت أقوم بتوصيل raspberry pi بشاشة عرض LCD الخاصة بي لأن تدفق الفيديو عبر شبكتي كان له زمن انتقال مرتفع وكان محبطًا للغاية للعمل معه ، ولهذا السبب توجد أسلاك متصلة بـ raspberry pi في الفيديو. لقد استخدمت ألواح الرغوة لرسم المسار.
أنا في انتظار الاستماع إلى توصياتكم لجعل هذا المشروع أفضل! كما آمل أن تكون هذه التعليمات جيدة بما يكفي لتزويدك ببعض المعلومات الجديدة.
موصى به:
سيارة يتم التحكم فيها عن بعد - يتم التحكم فيها باستخدام وحدة تحكم Xbox 360 اللاسلكية: 5 خطوات
سيارة يتم التحكم فيها عن بعد - يتم التحكم فيها باستخدام وحدة تحكم Xbox 360 اللاسلكية: هذه هي التعليمات من أجل بناء سيارتك الخاصة التي يتم التحكم فيها عن بعد ، والتي يتم التحكم فيها باستخدام وحدة تحكم Xbox 360 اللاسلكية
سيارة RC ذاتية التحكم: 7 خطوات
سيارة RC ذاتية القيادة: مع ظهور السيارات ذاتية القيادة اليوم ، قررت مواجهة التحدي المتمثل في صنع واحدة خاصة بي. كان هذا المشروع أيضًا بمثابة مشروع تتويجا لي في فصولي التصميم الهندسي والتطوير والروبوتات وحصلت على جائزة لـ
كيف تصنع سيارة تحكم عن بعد في المنزل بطريقة سهلة - سيارة RC لاسلكية ذاتية الصنع: 7 خطوات
كيف تصنع سيارة تحكم عن بعد في المنزل بطريقة سهلة - سيارة تعمل بالتحكم عن بعد لاسلكية تعمل بالتحكم عن بعد: مرحبًا بأصدقاء في هذه التعليمات ، سأوضح لك كيفية صنع سيارة RC يتم التحكم فيها عن بُعد بطريقة سهلة ، يرجى متابعة القراءة …… هذا حقًا مشروع رائع لذا يرجى محاولة بناء واحد
مركبة ذات عجلة واحدة ذاتية التوازن ذاتية الصنع: 8 خطوات (بالصور)
مركبة ذات عجلة واحدة ذاتية التوازن ذاتية الصنع: مهتمة ببعض اتجاهات منتجات الموازنة الذاتية مثل segway و solowheel.yes ، يمكنك الذهاب إلى أي مكان بركوب العجلة الخاصة بك دون تعب. ولكن من الرائع أن تحصل عليه بنفسك. حسنًا ، لنقم ببنائه
كيف تصنع: سيارة أردوينو ذاتية القيادة: 7 خطوات (بالصور)
كيفية البناء: سيارة اردوينو ذاتية القيادة: سيارة اردوينو ذاتية القيادة هي مشروع يتكون من هيكل سيارة ، عجلتين بمحرك ، واحدة 360 درجة ؛ عجلة (غير مزودة بمحركات) وعدد قليل من أجهزة الاستشعار. يتم تشغيله بواسطة بطارية 9 فولت باستخدام Arduino Nano متصل بلوحة توصيل صغيرة للتحكم في الوضع