فهرست مطالب:

خودروی خودران با استفاده از Raspberry Pi و OpenCV در 7 خط (همراه با تصاویر)
خودروی خودران با استفاده از Raspberry Pi و OpenCV در 7 خط (همراه با تصاویر)

تصویری: خودروی خودران با استفاده از Raspberry Pi و OpenCV در 7 خط (همراه با تصاویر)

تصویری: خودروی خودران با استفاده از Raspberry Pi و OpenCV در 7 خط (همراه با تصاویر)
تصویری: Lesson 28: Car-6 SunFounder self Driving Arduino car using | Robojax 2024, نوامبر
Anonim
خودروی خودران با استفاده از Raspberry Pi و OpenCV
خودروی خودران با استفاده از Raspberry Pi و OpenCV

در این دستورالعمل ، یک ربات نگهدارنده خط خودکار اجرا می شود و مراحل زیر را طی می کند:

  • جمع آوری قطعات
  • نصب پیش نیازهای نرم افزاری
  • مونتاژ سخت افزار
  • اولین آزمون
  • تشخیص خطوط خط و نمایش خط راهنما با استفاده از openCV
  • پیاده سازی کنترلر PD
  • نتایج

مرحله 1: جمع آوری اجزاء

جمع آوری اجزاء
جمع آوری اجزاء
جمع آوری اجزاء
جمع آوری اجزاء
جمع آوری اجزاء
جمع آوری اجزاء
جمع آوری اجزاء
جمع آوری اجزاء

تصاویر بالا تمام اجزای مورد استفاده در این پروژه را نشان می دهد:

  • ماشین RC: من خودم را از یک مغازه محلی در کشورم تهیه کردم. مجهز به 3 موتور (2 عدد برای دریچه گاز و 1 مورد برای فرمان) است. عیب اصلی این خودرو محدود بودن فرمان بین "بدون فرمان" و "فرمان کامل" است. به عبارت دیگر ، برخلاف خودروهای RC سروو فرمان نمی تواند در زاویه خاصی هدایت شود. شما می توانید کیت ماشین مشابهی را که مخصوص رزبری پای طراحی شده است ، از اینجا پیدا کنید.
  • Raspberry pi 3 model b+: این مغز خودرو است که مراحل زیادی را طی می کند. این دستگاه بر اساس پردازنده 64 هسته ای چهار هسته ای با فرکانس 1.4 گیگاهرتز طراحی شده است. مال خودم را از اینجا گرفتم
  • ماژول دوربین رزبری پای 5 مگاپیکسل: پشتیبانی از 1080p @ 30 فریم بر ثانیه ، 720p @ 60 فریم بر ثانیه و ضبط 640x480p 60/90. همچنین از رابط سریال پشتیبانی می کند که می تواند مستقیماً به رزبری pi متصل شود. این بهترین گزینه برای برنامه های پردازش تصویر نیست ، اما برای این پروژه کافی است و همچنین بسیار ارزان است. مال خودم را از اینجا گرفتم
  • درایور موتور: برای کنترل جهت و سرعت موتورهای DC استفاده می شود. از کنترل 2 موتور DC در 1 برد پشتیبانی می کند و می تواند 1.5 آمپر را تحمل کند.
  • پاور بانک (اختیاری): من از یک پاور بانک (با درجه 5V ، 3A) برای تقویت رزبری پای جداگانه استفاده کردم. از یک مبدل گام به گام (مبدل باک: جریان خروجی 3A) باید برای تأمین نیروی تمشک pi از 1 منبع استفاده شود.
  • باتری LiPo 3s (12 V): باتری های لیتیوم پلیمر به دلیل عملکرد عالی خود در زمینه رباتیک معروف هستند. برای تغذیه راننده موتور استفاده می شود. مال خودم را از اینجا خریدم.
  • سیمهای جهنده نر به نر و ماده به ماده.
  • نوار دو طرفه: برای نصب قطعات روی ماشین RC استفاده می شود.
  • نوار آبی: این جزء بسیار مهمی از این پروژه است ، از آن برای ایجاد دو خط باندی که خودرو بین آنها حرکت می کند استفاده می شود. شما می توانید هر رنگی را که می خواهید انتخاب کنید ، اما من توصیه می کنم رنگ هایی متفاوت از رنگ های موجود در محیط اطراف انتخاب کنید.
  • کراوات زیپ دار و میله های چوبی.
  • پیچ گوشتی.

مرحله 2: نصب OpenCV بر روی Raspberry Pi و راه اندازی نمایش از راه دور

نصب OpenCV روی رزبری پای و راه اندازی نمایش از راه دور
نصب OpenCV روی رزبری پای و راه اندازی نمایش از راه دور

این مرحله کمی آزاردهنده است و مدتی طول می کشد.

OpenCV (Open Source Computer Vision) یک کتابخانه نرم افزار منبع باز کامپیوتر و یادگیری ماشین است. این کتابخانه دارای بیش از 2500 الگوریتم بهینه شده است. برای نصب openCV روی تمشک pi خود و همچنین نصب سیستم عامل raspberry pi (اگر هنوز این کار را نکرده اید) این راهنمای بسیار ساده را دنبال کنید. لطفاً توجه داشته باشید که فرآیند ساخت openCV ممکن است در یک اتاق سرد خوب 1.5 ساعت طول بکشد (زیرا دمای پردازنده بسیار بالا می رود!) بنابراین کمی چای بخورید و صبورانه منتظر بمانید: D.

برای نمایش از راه دور ، این راهنما را دنبال کنید تا دسترسی از راه دور به دستگاه Windows/Mac خود را به تمشک pi خود تنظیم کنید.

مرحله 3: اتصال قطعات با هم

اتصال قطعات با هم
اتصال قطعات با هم
اتصال قطعات با هم
اتصال قطعات با هم
اتصال قطعات با هم
اتصال قطعات با هم

تصاویر بالا ارتباط بین تمشک pi ، ماژول دوربین و درایور موتور را نشان می دهد. لطفاً توجه داشته باشید که موتورهایی که استفاده کردم 0.35 A در 9 ولت جذب می کنند که می تواند 3 موتور را همزمان اجرا کند. و از آنجا که من می خواهم سرعت 2 موتور دریچه گاز (1 عقب و 1 جلو) را دقیقاً به همان شیوه کنترل کنم ، آنها را به یک پورت متصل کردم. راننده موتور را با استفاده از نوار دوبل در سمت راست ماشین سوار کردم. در مورد ماژول دوربین ، همانطور که در تصویر بالا نشان داده شده است ، یک زیپ بین سوراخ های پیچ قرار دادم. سپس ، دوربین را روی یک میله چوبی قرار می دهم تا بتوانم موقعیت دوربین را آنطور که می خواهم تنظیم کنم. سعی کنید تا حد امکان دوربین را در وسط خودرو نصب کنید. توصیه می کنم دوربین را حداقل 20 سانتی متر بالاتر از زمین قرار دهید تا میدان دید جلوی خودرو بهتر شود. طرحواره فریتزینگ در زیر پیوست شده است.

مرحله 4: اولین آزمایش

اولین آزمون
اولین آزمون
اولین آزمون
اولین آزمون

تست دوربین:

پس از نصب دوربین و ایجاد کتابخانه openCV ، وقت آن است که اولین تصویر خود را آزمایش کنیم! ما از pi cam عکس می گیریم و آن را به عنوان "original.jpg" ذخیره می کنیم. این را می توان به 2 روش انجام داد:

1. استفاده از دستورات ترمینال:

یک پنجره ترمینال جدید باز کنید و دستور زیر را تایپ کنید:

raspistill -o original.jpg

با این کار یک عکس ثابت گرفته می شود و در فهرست "/pi/original.jpg" ذخیره می شود.

2. استفاده از هر گونه IDE پایتون (من از IDLE استفاده می کنم):

یک طرح جدید باز کنید و کد زیر را بنویسید:

واردات cv2

video = cv2. VideoCapture (0) while True: ret، frame = video.read () frame = cv2.flip (frame، -1) # برای چرخاندن عمودی تصویر cv2.imshow ('اصلی' ، قاب) cv2 استفاده می شود. imwrite ('original.jpg'، frame) key = cv2.waitKey (1) if key == 27: break video.release () cv2.destroyAllWindows ()

بیایید ببینیم در این کد چه اتفاقی افتاده است. اولین خط وارد کردن کتابخانه openCV ما برای استفاده از همه عملکردهای آن است. عملکرد VideoCapture (0) پخش ویدئوی زنده را از منبع تعیین شده توسط این عملکرد شروع می کند ، در این حالت 0 است که به معنی دوربین raspi است. اگر چندین دوربین دارید ، شماره های مختلف باید قرار داده شود. video.read () می خواند که هر فریم از دوربین آمده و آن را در متغیری به نام "frame" ذخیره می کند. تابع () flip (با توجه به محور y) به صورت عمودی تصویر را برعکس می کند ، زیرا من دوربین خود را برعکس نصب می کنم. imshow () فریم های ما را با سرفصل کلمه "اصلی" نمایش می دهد و imwrite () عکس ما را به عنوان original-j.webp

توصیه می کنم عکس خود را با روش دوم آزمایش کنید تا با عملکردهای openCV آشنا شوید. تصویر در فهرست "/pi/original.jpg" ذخیره می شود. عکس اصلی که دوربین من گرفت در بالا نشان داده شده است.

تست موتورها:

این مرحله برای تعیین جهت چرخش هر موتور ضروری است. ابتدا اجازه دهید به طور مختصر در مورد اصل کار راننده موتور آشنا شویم. تصویر بالا پین کردن راننده موتور را نشان می دهد. فعال کردن A ، ورودی 1 و ورودی 2 با کنترل موتور A مرتبط هستند. فعال کردن B ، ورودی 3 و ورودی 4 با کنترل موتور B مرتبط هستند. کنترل جهت توسط قسمت "Input" و کنترل سرعت توسط قسمت "Enable" ایجاد می شود. برای کنترل جهت موتور A به عنوان مثال ، ورودی 1 را بر روی HIGH (در این حالت 3.3 ولت از زمانی که از رزبری pi استفاده می کنیم) تنظیم کرده و ورودی 2 را روی LOW تنظیم کنید ، موتور در جهت خاصی می چرخد و با تنظیم مقادیر مخالف به ورودی 1 و ورودی 2 ، موتور در جهت مخالف می چرخد. اگر ورودی 1 = ورودی 2 = (بالا یا پایین) ، موتور نمی چرخد. پین ها را فعال کنید یک سیگنال ورودی پالس عرض مدولاسیون (PWM) را از تمشک (0 تا 3.3 ولت) بگیرید و بر این اساس موتورها را روشن کنید. به عنوان مثال ، یک سیگنال 100٪ PWM به این معنی است که ما روی حداکثر سرعت کار می کنیم و 0٪ سیگنال PWM به این معنی است که موتور نمی چرخد. از کد زیر برای تعیین جهت موتورها و آزمایش سرعت آنها استفاده می شود.

زمان واردات

وارد کردن RPi. GPIO به عنوان GPIO GPIO.setwarnings (False) # پین های موتور فرمان فرمان_قابل فعال شدن = 22 # پین فیزیکی 15 in1 = 17 # پین فیزیکی 11 in2 = 27 # پین فیزیکی 13 # پین های موتور سیلندر دریچه گاز = 25 = 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 (steering_enable، GPIO.out) # فرمان موتور کنترل GPIO.output (in1، GPIO HIGH) GPIO.output (in2، GPIO. LOW) فرمان = GPIO. PWM (فرمان پذیری ، 1000) # فرکانس سوئیچینگ را روی فرمان 1000 هرتز قرار دهید. توقف () # GPIO.putput موتورهای گاز.output (in4، GPIO. LOW) throttle = GPIO. PWM (throttle_enable، 1000) # فرکانس سوئیچینگ را روی 1000 هرتز دریچه گاز قرار دهید. توقف () time.sleep (1) دریچه گاز. شروع (25) # موتور را در 25 روشن می کند ٪ سیگنال PWM-> (0.25 * ولتاژ باتری) - درایور از دست دادن فرمان. شروع (100) # موتور را با 100٪ سیگنال PWM روشن می کند-> (1 * ولتاژ باتری) - زمان از دست دادن راننده. خواب (3) دریچه گاز (توقف) () steering.stop ()

این کد موتورهای دریچه گاز و فرمان را به مدت 3 ثانیه اجرا می کند و سپس آنها را متوقف می کند. (از دست دادن راننده) را می توان با استفاده از ولت متر اندازه گیری کرد. به عنوان مثال ، ما می دانیم که یک سیگنال 100٪ PWM باید ولتاژ کامل باتری را در ترمینال موتور بدهد. اما ، با تنظیم PWM روی 100٪ ، متوجه شدم که راننده باعث افت 3 ولت می شود و موتور به جای 12 ولت 9 ولت دریافت می کند (دقیقاً همان چیزی که من نیاز دارم!). ضرر خطی نیست ، یعنی ضرر 100 very با زیان 25 very بسیار متفاوت است. پس از اجرای کد بالا ، نتایج من به شرح زیر بود:

نتایج دریچه گاز: اگر in3 = HIGH و in4 = LOW باشد ، موتورهای دریچه گاز دارای چرخش Clock-Wise (CW) هستند ، یعنی ماشین به جلو حرکت می کند. در غیر این صورت ، ماشین به عقب حرکت می کند.

نتایج فرمان: اگر in1 = HIGH و in2 = LOW ، موتور فرمان در حداکثر چپ خود بچرخد ، یعنی ماشین به چپ بپیچد. در غیر این صورت ، ماشین به راست هدایت می شود. پس از انجام برخی آزمایشات ، دریافتم که اگر فرمان سیگنال PWM 100٪ نباشد ، موتور فرمان نمی چرخد (یعنی موتور به طور کامل به راست یا به طور کامل به سمت چپ هدایت می شود).

مرحله 5: تشخیص خطوط خط و محاسبه خط سرصفحه

تشخیص خطوط خط و محاسبه خط سرصفحه
تشخیص خطوط خط و محاسبه خط سرصفحه
تشخیص خطوط خط و محاسبه خط سرصفحه
تشخیص خطوط خط و محاسبه خط سرصفحه
تشخیص خطوط خط و محاسبه خط سرصفحه
تشخیص خطوط خط و محاسبه خط سرصفحه

در این مرحله الگوریتمی که حرکت خودرو را کنترل می کند توضیح داده می شود. تصویر اول کل فرایند را نشان می دهد. ورودی سیستم تصاویر است ، خروجی تتا (زاویه فرمان بر حسب درجه) است. توجه داشته باشید که پردازش روی 1 تصویر انجام می شود و در همه فریم ها تکرار می شود.

دوربین:

دوربین شروع به ضبط فیلم با وضوح (320 240 240) می کند. توصیه می کنم وضوح تصویر را کاهش دهید تا بتوانید نرخ فریم بهتر (fps) را بدست آورید زیرا پس از اعمال تکنیک های پردازش در هر فریم ، افت fps رخ می دهد. کد زیر حلقه اصلی برنامه خواهد بود و هر مرحله را بر روی این کد اضافه می کند.

واردات cv2

وارد کردن numpy به عنوان np video = cv2. VideoCapture (0) video.set (cv2. CAP_PROP_FRAME_WIDTH ، 320) # عرض را روی 320 p video.set (cv2. CAP_PROP_FRAME_HEIGHT ، 240) # تنظیم ارتفاع بر 240 p # حلقه در حالی که درست: ret، frame = video.read () frame = cv2.flip (frame، -1) cv2.imshow ("اصلی" ، قاب) key = cv2.wait کلید (1) در صورت کلید == 27: شکستن ویدیو. انتشار () cv2.destroyAllWindows ()

کد در اینجا تصویر اصلی به دست آمده در مرحله 4 را نشان می دهد و در تصاویر بالا نشان داده شده است.

تبدیل به فضای رنگ HSV:

اکنون پس از گرفتن فیلمبرداری به عنوان فریم از دوربین ، مرحله بعدی تبدیل هر فریم به فضای رنگی Hue ، Saturation و Value (HSV) است. مزیت اصلی این کار این است که بتوان بین رنگ ها بر اساس میزان روشنایی آنها تفاوت قائل شد. و در اینجا توضیحات خوبی در مورد فضای رنگ HSV ارائه شده است. تبدیل به HSV از طریق عملکرد زیر انجام می شود:

def convert_to_HSV (قاب):

hsv = cv2.cvtColor (قاب ، cv2. COLOR_BGR2HSV) cv2.imshow ("HSV" ، hsv) hsv بازگشت

این تابع از حلقه اصلی فراخوانی می شود و قاب را در فضای رنگی HSV باز می گرداند. قاب به دست آمده در فضای رنگ HSV در بالا نشان داده شده است.

تشخیص رنگ آبی و لبه ها:

پس از تبدیل تصویر به فضای رنگی HSV ، زمان آن رسیده است که فقط رنگ مورد علاقه خود را تشخیص دهیم (یعنی رنگ آبی چون رنگ خطوط خط است). برای استخراج رنگ آبی از یک قاب HSV ، محدوده ای از رنگ ، اشباع و مقدار باید مشخص شود. برای داشتن ایده بهتر در مورد مقادیر HSV به اینجا مراجعه کنید. پس از انجام برخی آزمایشات ، محدوده بالا و پایین رنگ آبی در کد زیر نشان داده شده است. و برای کاهش اعوجاج کلی در هر فریم ، لبه ها فقط با استفاده از آشکارساز لبه دار تشخیص داده می شوند. اطلاعات بیشتر در مورد canny edge در اینجا یافت می شود. یک قانون کلی این است که پارامترهای عملکرد Canny () را با نسبت 1: 2 یا 1: 3 انتخاب کنید.

def dete_edges (قاب):

lower_blue = np.array ([90، 120، 0]، dtype = "uint8") # حد پایین رنگ آبی above_blue = np.array ([150، 255، 255]، dtype = "uint8") # حد فوقانی ماسک رنگ آبی = cv2.inRange (hsv ، آبی_پایین ، آبی_بالایی) # این ماسک همه چیز را فیلتر می کند اما آبی # تشخیص لبه های لبه ها = cv2. Canny (mask، 50، 100) cv2.imshow ("لبه" ، لبه) لبه های بازگشتی

این تابع همچنین از حلقه اصلی فراخوانی می شود که به عنوان پارامتر قاب رنگ HSV را در نظر گرفته و قاب لبه دار را برمی گرداند. قاب لبه ای که من به دست آوردم در بالا یافت می شود.

منطقه مورد علاقه (ROI) را انتخاب کنید:

انتخاب ناحیه مورد نظر برای تمرکز تنها بر 1 ناحیه از فریم بسیار مهم است. در این مورد ، من نمی خواهم ماشین موارد زیادی را در محیط ببیند. من فقط می خواهم ماشین روی خطوط متمرکز شود و هر چیز دیگری را نادیده بگیرد. P. S: سیستم مختصات (محورهای x و y) از گوشه سمت چپ بالا شروع می شود. به عبارت دیگر ، نقطه (0 ، 0) از گوشه بالا سمت چپ شروع می شود. محور y ارتفاع و محور x عرض عرض است. کد زیر منطقه مورد نظر را انتخاب می کند تا فقط روی نیمه پایینی قاب تمرکز کند.

def area_of_interest (لبه ها):

ارتفاع ، عرض = لبه ها. شکل # استخراج ارتفاع و عرض لبه ها 4 نقطه (پایین چپ ، بالا سمت چپ ، بالا سمت راست ، پایین سمت راست) چند ضلعی = np.array (

این تابع قاب لبه را به عنوان پارامتر می گیرد و چند ضلعی را با 4 نقطه از پیش تعیین شده ترسیم می کند. فقط بر روی چیزهای داخل چند ضلعی تمرکز می کند و همه چیز را در خارج از آن نادیده می گیرد. منطقه مورد علاقه من در بالا نشان داده شده است.

تشخیص بخش های خط:

Hough transform برای تشخیص بخشهای خط از یک قاب لبه دار استفاده می شود. تبدیل Hough یک تکنیک برای تشخیص هر شکلی به شکل ریاضی است. این دستگاه می تواند تقریباً هر جسمی را تشخیص دهد حتی اگر بر اساس برخی از آراء تحریف شده باشد. در اینجا مرجع بزرگی برای Hough transform نشان داده شده است. برای این برنامه ، تابع cv2. HoughLinesP () برای تشخیص خطوط در هر فریم استفاده می شود. پارامترهای مهمی که این تابع در نظر می گیرد عبارتند از:

cv2. HoughLinesP (frame، rho، theta، min_threshold، minLineLength، maxLineGap)

  • قاب: فریمی است که می خواهیم خطوط را در آن تشخیص دهیم.
  • rho: این فاصله فاصله در پیکسل است (معمولاً = 1 است)
  • theta: دقت زاویه ای در رادیان (همیشه = np.pi/180 ~ 1 درجه)
  • min_threshold: حداقل رأی لازم است تا به عنوان یک خط در نظر گرفته شود
  • minLineLength: حداقل طول خط در پیکسل. هر سطر کوتاهتر از این عدد ، خط محسوب نمی شود.
  • maxLineGap: حداکثر فاصله در پیکسل بین 2 خط به عنوان 1 خط در نظر گرفته می شود. (در مورد من استفاده نمی شود زیرا خطوط خطی که استفاده می کنم هیچ شکافی ندارند).

این تابع نقاط پایانی یک خط را برمی گرداند. تابع زیر از حلقه اصلی من برای تشخیص خطوط با استفاده از تبدیل Hough فراخوانی می شود:

def dete_line_segment (cropped_edges):

rho = 1 theta = np.pi / 180 min_threshold = 10 line_segment = cv2. HoughLinesP (cropped_edges، rho، theta، min_threshold، np.array ()، minLineLength = 5، maxLineGap = 0) خطوط_بخش

میانگین شیب و رهگیری (متر ، ب):

به یاد بیاورید که معادله خط با y = mx + b داده شده است. جایی که m شیب خط و b قطع y است. در این قسمت ، میانگین شیب ها و رهگیری قطعات خط شناسایی شده با استفاده از تبدیل Hough محاسبه می شود. قبل از انجام این کار ، اجازه دهید نگاهی به عکس قاب اصلی نشان داده شده در بالا بیندازیم. به نظر می رسد که خط چپ به سمت بالا حرکت می کند بنابراین دارای شیب منفی است (نقطه شروع سیستم مختصات را به خاطر دارید؟). به عبارت دیگر ، خط خط چپ دارای x1 <x2 و y2 x1 و y2> y1 است که شیب مثبتی را ایجاد می کند. بنابراین ، همه خطوط با شیب مثبت ، نقاط خط راست در نظر گرفته می شوند. در مورد خطوط عمودی (x1 = x2) ، شیب بی نهایت خواهد بود. در این حالت ، برای جلوگیری از خطا ، تمام خطوط عمودی را رد می کنیم. برای افزودن دقت بیشتر به این تشخیص ، هر فریم از طریق 2 خط مرزی به دو منطقه (راست و چپ) تقسیم می شود. همه نقاط عرض (نقاط محور x) بزرگتر از خط مرز راست ، با محاسبه خط راست مرتبط هستند. و اگر تمام نقاط عرض کمتر از خط مرزی چپ باشند ، با محاسبه خط چپ مرتبط می شوند. تابع زیر قاب را تحت پردازش قرار می دهد و بخش های خط با استفاده از Hough transform شناسایی می شوند و شیب متوسط و قطع دو خط را برمی گرداند.

def average_slope_intercept (frame، line_segment):

خطوط خط = اگر خطوط_صفحه وجود ندارد: چاپ ("هیچ خط خطی شناسایی نشده است") ارتفاع خط ، عرض ، _ = frame.shape left_fit = right_fit = border = left_region_boundary = width * (1 - border) right_region_boundary = width * border for line_segment in line_segment: for x1، y1، x2، y2 in line_segment: if x1 == x2: print ("رد خطوط عمودی (شیب = بی نهایت)") fit fit = np.polyfit ((x1، x2) ، (y1 ، y2) ، 1) شیب = (y2 - y1) / (x2 - x1) intercept = y1 - (شیب * x1) در صورت شیب <0: اگر x1 <چپ_رژیم_مرز و x2 راست_رژیه_مرزی و x2> راست_ منطقه_مرزی: right_fit. append ((slope، intercept)) left_fit_average = np.average (left_fit، axis = 0) if len (left_fit)> 0: lane_lines.append (make_points (frame، left_fit_average)) right_fit_average = np.average (right_fit، axis = 0) if 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 (قاب ، خط):

ارتفاع ، عرض ، _ = frame.shape شیب ، رهگیری = خط y1 = ارتفاع # پایین قاب y2 = int (y1 / 2) # اگر از شیب == 0: شیب = 0.1 x1 = از وسط قاب به پایین امتیاز دهید int ((y1 - رهگیری) / شیب) x2 = int ((y2 - رهگیری) / شیب) بازگشت

برای جلوگیری از تقسیم بر 0 ، شرطی ارائه می شود. اگر شیب = 0 به معنی y1 = y2 (خط افقی) باشد ، مقدار نزدیک به 0 را به شیب بدهید ، این بر عملکرد الگوریتم تأثیر نمی گذارد و همچنین از حالت غیرممکن (تقسیم بر 0) جلوگیری می کند.

برای نمایش خطوط خط روی فریم ها ، از عملکرد زیر استفاده می شود:

def_ lines display (frame، lines، line_color = (0، 255، 0)، line_width = 6): # رنگ خط (B، G، R)

line_image = np.zeros_like (قاب) اگر خطوط نباشد خط_ عرض

تابع () cv2.addWeighted پارامترهای زیر را می گیرد و از آن برای ترکیب دو تصویر اما با دادن وزن به هر یک استفاده می شود.

cv2.add وزن (تصویر 1 ، آلفا ، تصویر 2 ، بتا ، گاما)

و تصویر خروجی را با استفاده از معادله زیر محاسبه می کند:

خروجی = alpha * image1 + beta * image2 + gamma

اطلاعات بیشتر در مورد تابع cv2.addWeighted () در اینجا به دست آمده است.

محاسبه و نمایش خط عنوان:

این آخرین مرحله قبل از اعمال سرعت به موتورهای ما است. خط هدایت وظیفه دارد به موتور فرمان جهت چرخش را بدهد و به موتورهای دریچه گاز سرعت عمل کند. محاسبه خط عنوان مثلثات محض است ، از توابع مثلثاتی tan و atan (tan^-1) استفاده می شود. برخی موارد شدید زمانی اتفاق می افتد که دوربین فقط یک خط خط را تشخیص دهد یا هیچ خطی را تشخیص ندهد. همه این موارد در تابع زیر نشان داده شده است:

def get_steering_angle (قاب ، خطوط خط):

height، width، _ = frame.shape if len (lane_lines) == 2: # در صورت تشخیص دو خط _، _، left_x2، _ = lane_lines [0] [0] # استخراج x2 سمت چپ از آرایه خطوط خط ، _ ، _ ، right_x2، _ = lane_lines [1] [0] # استخراج x2 راست از آرایه خطوط mid = int (عرض / 2) x_offset = (left_x2 + right_x2) / 2 - وسط y_offset = int (ارتفاع / 2) elif len (lane_lines) == 1: # اگر فقط یک خط تشخیص داده شود x1 ، _ ، x2 ، _ = خطوط خط [0] [0] x_offset = x2 - x1 y_offset = int (ارتفاع / 2) elif len (خطوط خط) == 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) steering_angle = angle_to_mid_deg + 90 بازگشت فرمان

x_offset در مورد اول این است که میانگین ((راست x2 + چپ x2) / 2) چقدر از وسط صفحه نمایش متفاوت است. y_offset همیشه ارتفاع / 2 در نظر گرفته می شود. آخرین تصویر بالا نمونه ای از خط عنوان را نشان می دهد. angle_to_mid_radians همان "theta" نشان داده شده در آخرین تصویر بالا است. اگر steering_angle = 90 باشد ، این بدان معناست که ماشین خط عمود بر خط "ارتفاع / 2" دارد و ماشین بدون فرمان به جلو حرکت می کند. اگر steering_angle> 90 باشد ، ماشین باید به راست هدایت کند در غیر این صورت باید به چپ فرمان دهد. برای نمایش خط عنوان ، از تابع زیر استفاده می شود:

def display_heading_line (قاب ، فرمان_خط ، خط_رنگ = (0 ، 0 ، 255) ، عرض_خط = 5)

heading_image = np.zeros_like (frame) ارتفاع ، عرض ، _ = frame.shape steering_angle_radian = steering_angle / 180.0 * math.pi x1 = int (width / 2) y1 = height x2 = int (x1 - height / 2 / math.tan] بازگشت heading_image

عملکرد بالا فریمی را که خط عنوان روی آن کشیده می شود و زاویه فرمان را به عنوان ورودی می گیرد. تصویر خط عنوان را برمی گرداند. قاب خط عنوان گرفته شده در مورد من در تصویر بالا نشان داده شده است.

ترکیب همه کد ها با هم:

اکنون کد آماده مونتاژ است. کد زیر حلقه اصلی برنامه را که هر تابع را فرا می خواند نشان می دهد:

واردات cv2

وارد کردن 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 = تبدیل_به_HSV (قاب) لبه ها = لبه های_تشخیص (hsv) roi = منطقه_اینستر (حاشیه) خطوط_خشها = تشخیص_قسمتهای_خط (روئی) خطوط خط = میانگین_ شیب_مفهوم (قاب ، خطوط_خطوط) خط_خط_صویر ، خطوط_صویر ، خطوط_صحیح ، = get_steering_angle (frame، lane_lines) heading_image = display_heading_line (lane_lines_image، steering_angle) key = cv2.waitKey (1) if key == 27: break video.release () cv2.destroyAllWindows ()

مرحله 6: اعمال کنترل PD

اعمال کنترل PD
اعمال کنترل PD

اکنون ما زاویه فرمان خود را آماده کرده ایم تا به موتورها تغذیه شود. همانطور که قبلاً ذکر شد ، اگر زاویه فرمان بیشتر از 90 باشد ، خودرو باید به راست بپیچد در غیر این صورت باید به چپ بپیچد. من یک کد ساده به کار بردم که موتور فرمان را در صورتی که زاویه بالای 90 باشد به راست بچرخاند و در صورتی که زاویه فرمان کمتر از 90 باشد با سرعت گشتاور ثابت (10٪ PWM) به چپ بپیچد اما خطاهای زیادی دریافت کردم. خطای اصلی من این است که وقتی ماشین به هر پیچ می رسد ، موتور فرمان مستقیماً عمل می کند اما موتورهای دریچه گاز گیر می کنند. من سعی کردم سرعت گشتاور را در پیچها (20٪ PWM) افزایش دهم ، اما با بیرون آمدن ربات از خط پایان یافت. من به چیزی نیاز داشتم که اگر زاویه فرمان بسیار زیاد باشد سرعت گشتاور را زیاد می کند و اگر زاویه فرمان آنقدر بزرگ نباشد سرعت را کمی افزایش می دهد و با نزدیک شدن 90 درجه خودرو (حرکت مستقیم) سرعت را به مقدار اولیه کاهش می دهد. راه حل استفاده از کنترلر PD بود.

PID controller مخفف عبارت Proportional، Integral and Derivative controller است. این نوع کنترلرهای خطی به طور گسترده ای در برنامه های رباتیک استفاده می شود. تصویر بالا حلقه کنترل بازخورد PID معمولی را نشان می دهد. هدف این کنترل کننده رسیدن به "نقطه تنظیم" با کارآمدترین روش بر خلاف کنترل کننده های "روشن - خاموش" است که بر اساس برخی شرایط ، نیروگاه را روشن یا خاموش می کنند. برخی از کلمات کلیدی باید شناخته شوند:

  • Setpoint: مقدار مورد نظر است که می خواهید سیستم شما به آن برسد.
  • مقدار واقعی: مقدار واقعی است که توسط حسگر حس می شود.
  • خطا: تفاوت بین نقطه تنظیم و مقدار واقعی است (خطا = نقطه تعیین - مقدار واقعی).
  • متغیر کنترل شده: از نام آن ، متغیری است که می خواهید کنترل کنید.
  • Kp: ثابت متناسب.
  • کی: ثابت انتگرال.
  • Kd: ثابت مشتق.

به طور خلاصه ، حلقه سیستم کنترل PID به شرح زیر عمل می کند:

  • کاربر نقطه تنظیم مورد نیاز برای دسترسی سیستم را مشخص می کند.
  • خطا محاسبه می شود (خطا = نقطه تنظیم - واقعی).
  • کنترلر P یک عمل متناسب با مقدار خطا ایجاد می کند. (خطا افزایش می یابد ، عمل P نیز افزایش می یابد)
  • I controller خطا را در طول زمان ادغام می کند که خطای حالت پایدار سیستم را برطرف می کند اما افزایش بیش از حد آن را افزایش می دهد.
  • کنترلر D به سادگی مشتق زمان خطا است. به عبارت دیگر ، شیب خطا است. این عمل متناسب با مشتق خطا انجام می دهد. این کنترل کننده ثبات سیستم را افزایش می دهد.
  • خروجی کنترلر مجموع سه کنترل کننده خواهد بود. اگر خطا 0 شود ، خروجی کنترل 0 می شود.

توضیحات عالی در مورد کنترل کننده PID را می توانید در اینجا پیدا کنید.

با بازگشت به ماشین نگهدارنده خط ، متغیر کنترل شده من سرعت را کاهش می داد (زیرا فرمان فقط دو حالت راست یا چپ دارد). برای این منظور از کنترلر PD استفاده می شود ، زیرا اگر تغییر خطا بسیار زیاد باشد (یعنی انحراف زیاد) سرعت دریچه گاز را بسیار افزایش می دهد و اگر این تغییر خطا به 0 نزدیک شود خودرو را کند می کند. برای پیاده سازی PD مراحل زیر را انجام دادم. کنترل کننده:

  • نقطه تنظیم را روی 90 درجه تنظیم کنید (من همیشه می خواهم ماشین مستقیم حرکت کند)
  • زاویه انحراف از وسط را محاسبه کرد
  • این انحراف دو اطلاعات را ارائه می دهد: خطا چقدر است (میزان انحراف) و موتور فرمان چه مسیری را باید طی کند (نشانه انحراف). در صورت مثبت بودن انحراف ، خودرو باید به راست فرمان دهد در غیر این صورت باید به چپ بپیچد.
  • از آنجا که انحراف منفی یا مثبت است ، یک متغیر "خطا" تعریف می شود و همیشه برابر مقدار مطلق انحراف است.
  • خطا در Kp ثابت ضرب می شود.
  • این خطا دچار تمایز زمانی می شود و در Kd ثابت ضرب می شود.
  • سرعت موتورها به روز می شود و حلقه دوباره شروع می شود.

کد زیر در حلقه اصلی برای کنترل سرعت موتورهای دریچه گاز استفاده می شود:

سرعت = 10 # سرعت کار در٪ PWM

# متغیرها باید در هر حلقه به روز شوند lastTime = 0 lastError = 0 # PD ثابت Kp = 0.4 Kd = Kp * 0.65 در حالیکه True: now = time.time () # متغیر زمان فعلی dt = now - lastTime انحراف = فرمان فرمان - 90 # معادل به angle_to_mid_deg متغیر خطا = abs (انحراف) در صورت انحراف -5: # در صورت وجود انحراف محدوده خطای 10 درجه = 0 خطا = 0 GPIO.output (in1، GPIO. LOW) GPIO.output (in2، GPIO. LOW) steering.stop () elif انحراف> 5: در صورت مثبت بودن انحراف GPIO.upput (in1، GPIO. LOW) GPIO.output (in2، GPIO. HIGH) steering.start (100) elif انحراف < -5: # هدایت سمت چپ در صورت انحراف GPIO منفی. خروجی (in1 ، GPIO. HIGH) GPIO.output (in2 ، GPIO. LOW) steering.start (100) مشتق = kd * (error - lastError) / dt نسبت = kp * خطا PD = int (سرعت + مشتق + متناسب) spd = abs (PD) اگر spd> 25: spd = 25 throttle.start (spd) lastError = error lastTime = time.time ()

اگر خطا بسیار بزرگ باشد (انحراف از وسط زیاد است) ، اقدامات متناسب و مشتق زیاد است و در نتیجه سرعت لرزش زیاد است. هنگامی که خطا به 0 نزدیک می شود (انحراف از وسط کم است) ، عمل مشتق برعکس عمل می کند (شیب منفی است) و سرعت دریچه گاز پایین می آید تا پایداری سیستم حفظ شود. کد کامل در زیر پیوست شده است.

مرحله 7: نتایج

فیلم های بالا نتایج بدست آمده را نشان می دهد. به تنظیم بیشتر و تنظیمات بیشتر نیاز دارد. من تمشک pi را به صفحه نمایش LCD خود متصل می کردم زیرا پخش ویدئو از طریق شبکه من تاخیر زیادی داشت و کار با آن بسیار ناامید کننده بود ، به همین دلیل در ویدیو سیم هایی به تمشک pi متصل شده است. من از تخته های فوم برای ترسیم مسیر استفاده کردم.

من منتظر شنیدن توصیه های شما برای بهتر شدن این پروژه هستم! امیدوارم این دستورالعمل ها به اندازه کافی خوب بوده باشد تا اطلاعات جدیدی در اختیار شما قرار دهد.

توصیه شده: