آموزش پردازش تصویرآموزش رزبری پایپروژه رزبری پای

تشخیص خط جاده با پردازش تصویر OpenCV و رزبری پای

در این پروژه قصد داریم با استفاده از Raspberry Pi و OpenCV یک ماشین با قابلیت تشخیص خط جاده بسازیم. قبل از ادامه این پروژه، مطمئن شوید که  نحوه استفاده از OpenCV با استفاده از پایتون در Raspberry Pi را میدانید. مشکل تشخیص خط معمولاً به عنوان یک مسئله تقسیم بندی معنایی یا نمونه ای با هدف شناسایی پیکسل هایی که به خط تعلق دارند، در نظر گرفته می شود. برای پیاده‌سازی پروژه تشخیص خط در Raspberry Pi به تکنیک‌های پردازش تصویر نیاز داریم. کتابخانه OpenCV دارای تکنیک های پردازش تصویر مورد نیاز برای انجام پروژه تشخیص خط است.

مراحل ساخت خودروی تشخیص خط خودران

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

در ابتدا:

  • مونتاژ سخت افزار
  • نصب نرم افزار پیش نیاز
  • تهیه کد رانندگی
  • اولین تست درایو

سپس:

  • تشخیص خطوط خطوط با استفاده از OpenCV
  • کالیبره کردن موتورها با تشخیص خط
  • درایو تست نهایی

بنابراین این مراحلی است که باید دنبال کنید. بیایید هر مرحله را یکی یکی اجرا کنیم.

قطعات مورد نیاز خودروی تشخیص خط خودران

برای ساخت خودروی تشخیص خط خودران، مطمئن شوید که اجزای زیر را دارید.

شاسی خودرو

شاسی خودرو

رزبری پای 4: من از Raspberry Pi 4 به عنوان واحد کنترل اصلی ماشین استفاده کردم. می‌توانید از Raspberry Pi 3 b+ استفاده کنید.

رزبری پای 4

ماژول دوربین Raspberry Pi: می تواند با کیفیت 1080p با سرعت 30 فریم در ثانیه، 720p با سرعت 60 فریم در ثانیه و 640x480p با سرعت 60/90 فریم در ثانیه ضبط کند. اگرچه این راه حل ایده آل برای برنامه های کاربردی پردازش تصویر نیست، اما برای هدف ما کافی است و نسبتاً ارزان است.

ماژول دوربین Raspberry Pi

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

پایه نصب دوربین

درایور موتور: من از درایور موتور L298N در این پروژه استفاده کرده ام که برای تنظیم جهت و سرعت موتورهای DC استفاده می شود.

اگر در مورد این مطلب سوالی دارید در قسمت نظرات بپرسید

درایور موتور

پاور بانک: من از یک پاوربانک برای روشن کردن رزبری پای خود استفاده کرده ام. اگر پاور بانک ندارید، می توانید از باتری 5 ولتی استفاده کنید یا می توانید یک مدار برق برای تبدیل ولتاژ به 5 ولت با جریان 2.2 آمپر بسازید.

بسته باتری 18650 2S: برای روشن کردن درایور موتور (L298N)، از بسته باتری 2 سلولی 18650 با ولتاژ حداکثر 7.4 ولت استفاده کردم. باتری‌های لیتیوم یونی به دلیل عملکرد فوق‌العاده‌شان در حوزه رباتیک مشهور هستند و برای تامین برق درایور موتور استفاده می‌شوند.

کاغذ سیاه و نوار سفید: من از مقداری کاغذ نمودار سیاه و نوار سفید برای آماده کردن جاده استفاده کردم.

همه اینها اجزای اصلی برای ساخت ماشین تشخیص خط شما هستند. من فرض می کنم که شما اجزای مشترکی مانند سیم های اتصال، پیچ درایورها و … را دارید که تقریباً در هر پروژه الکترونیکی مورد نیاز است. اکنون، بیایید مراحل ساخت ماشین تشخیص خط خود را درک کنیم.

مونتاژ خودروی تشخیص خط جاده

شما باید شاسی ماشین خود را آماده کنید. من از ماشین RC استفاده کردم، بنابراین مدار، بسته باتری و هدر بالایی ماشین را همانطور که در تصویر زیر می بینید حذف کردم.

مونتاژ خودروی تشخیص خط خودران

سپس یک پایه برای دوربین روی ماشین قرار دادم. میتوانید این پایه را با چاپ سه بعدی طراحی کنید یا با خلاقیت خودتان دوربین را روی شاسی مستقر کنید.

قرار دادن دوربین رزبری پای روی شاسی من اتصالات سیم کشی بین سه موتور، درایور موتور و Raspberry Pi 4 را انجام دادم. اتصالات بسیار ساده است. برای انجام اتصال سیم کشی می توانید به شماتیک و تصاویر زیر مراجعه کنید. اطمینان حاصل کنید که موتورهایی که برای دریچه گاز استفاده می شوند را به یک طرف درایور موتور و موتوری که برای فرمان استفاده می شود به ترمینال مخالف درایور موتور متصل شده است.

شماتیک پروژه تشخیص خط جاده پردازش تصویر

کد رانندگی خودکار و تشخیص جاده

در ابتدا باید نصب OpenCV در رزبری پای را انجام دهید.

کد کامل این پروژه در فایل دانلودی انتهای صفحه قرار داده شده است. ما در اینجا قسمت های مهم کد را توضیح میدهیم. در این بخش، ما بر روی فایل‌های “motors.py”، “keyboardmodule.py” و “testdrivemodule.py” تمرکز خواهیم کرد. با استفاده از این سه فایل ما ماشین خود را با ورودی های صفحه کلید کنترل می کنیم.

فایل “motors.py”:

در فایل motors.py، تمامی عملکردها را برای کنترل جهت موتورها و هدایت موتورها به جلو یا عقب پیدا خواهید کرد. ما RPi.GPIO را به عنوان GPIO وارد کرده‌ایم تا پین‌های GPIO برد Raspberry Pi 4 را کنترل کنیم.

import RPi.GPIO as GPIO
from time import sleep

توابع زیر برای راندن ماشین به جهت “جلو”، “عقب”، “جلو به سمت چپ”، “جلو به سمت راست”، “عقب راست”، “عقب چپ” استفاده می شود. برای توقف خودرو می توان از عملکرد stop استفاده کرد.

def frontmiddle():
    GPIO.output(in3, GPIO.LOW)
    GPIO.output(in4, GPIO.LOW)
def frontright():
    p2.ChangeDutyCycle(100)
    GPIO.output(in3, GPIO.LOW)
    GPIO.output(in4, GPIO.HIGH)
def frontleft():
    p2.ChangeDutyCycle(100)
    GPIO.output(in3, GPIO.HIGH)
    GPIO.output(in4, GPIO.LOW)
def forward(speed=50,time=0):
    p1.ChangeDutyCycle(speed)
    GPIO.output(in1, GPIO.HIGH)
    GPIO.output(in2, GPIO.LOW)
    frontmiddle()
    sleep(time)
def backward(speed=50,time=0):
    p1.ChangeDutyCycle(speed)
    GPIO.output(in1, GPIO.LOW)
    GPIO.output(in2, GPIO.HIGH)
    frontmiddle()
    sleep(time)
def stop(time=0):
    frontmiddle()
    GPIO.output(in1, GPIO.LOW)
    GPIO.output(in2, GPIO.LOW)
    sleep(time)
def fright(speed=50,time=0):
    forward(speed)
    frontright()
    sleep(time)
def fleft(speed=50,time=0):
    forward(speed)
    frontleft()
    sleep(time)
def bright(speed=50,time=0):
    backward(speed)
    frontright()
    sleep(time)
def bleft(speed=50,time=0):
    backward(speed)
    frontleft()
    sleep(time)

فایل “keyboardmodule.py”:

keyboardmodule.py از تابع getKey() برای دریافت کلید فشرده شده در صفحه کلید تشکیل شده است. من از کتابخانه pygame برای دریافت کلید فشرده صفحه کلید در زمان اجرا استفاده کردم.

def getKey(keyName):
    ans= False
    running = True
    for event in pygame.event.get():  # error is here
        if event.type == pygame.QUIT:
            running = False
            pygame.quit()
    if running:
        pygame.display.flip()
    keyInput = pygame.key.get_pressed()
    myKey = getattr(pygame,'K_{}'.format(keyName))
    if keyInput[myKey]:
        ans = True
    pygame.display.update()
    return ans

فایل “tesdrivemodule.py”:

ما باید motors.py و keyboardmodule.py را در فایل testdrivemodule.py وارد کنیم. km.init() برای مقداردهی اولیه keyboardmodule.py استفاده می شود. در زیر حلقه while می‌توانید «km.getKey()» را پیدا کنید که برای دریافت کلیدهای ورودی «w»، «s»، «q»، «e»، «a» و «d» استفاده می‌شود. ما آن کلیدها را فشار خواهیم داد. و ما از mot.forward()، mot.backward()، mot.fleft()، mot.fright()، mot.bright() و mot.bleft() برای حرکت ربات استفاده خواهیم کرد.

import motors as mot
import keyboardmodule as km
km.init()
while True:
    if km.getKey('w'):
        print('forward')
        mot.forward(100)
    elif km.getKey('s'):
        print('backward')
        mot.backward(100)
    elif km.getKey('q'):
        print('fleft')
        mot.fleft(100)
    elif km.getKey('e'):
        print('fright')
        mot.fright(100)
    elif km.getKey('a'):
        print('bleft')
        mot.bright(100)
    elif km.getKey('d'):
        print('bright')
        mot.bleft(100)
    else:
        mot.stop(0)

شما باید کد را در Raspberry Pi 4 خود آپلود کنید. من از نرم افزار mobaxterm برای باز کردن ترمینال Raspberry Pi استفاده کرده ام. سپس باید “testdrivemodule.py” را با استفاده از دستور زیر در ترمینال اجرا کنید. مطمئن شوید که “motors.py” و “keyboardmodule.py” را در یک پوشه قرار دادید.

python3 testdrivemodule.py

تا اینجا ربات ما مطابق با فیلم زیر عمل میکند:

حال قصد داریم کدی بنویسیم تا با استفاده از OpenCV خطوط جاده را شناسایی کنیم، موتورها را با تشخیص خط کالیبره کنیم و خودرو را مطابق با خطوط شناسایی شده هدایت کنیم.

همانطور که قبلاً گفته شد، ما از کتابخانه OpenCV برای شناسایی خطوط و پردازش تصاویر استفاده خواهیم کرد. بنابراین، قبل از ادامه این آموزش، حتما OpenCV Library را روی Raspberry Pi نصب کنید. همچنین، برد رزبری پای خود را با یک آداپتور 2 آمپری تغذیه کنید و برای رفع اشکال راحت‌تر، آن را به مانیتور متصل کنید.

این آموزش توضیح نمی دهد که OpenCV دقیقا چگونه کار می کند، اگر علاقه مند به یادگیری پردازش تصویر هستید، این آموزش اصول اولیه OpenCV را بررسی کنید.

مراحل طراحی ربات تشخیص خودکار جاده

تشخیص خطوط جاده شامل سه مرحله اصلی است. مراحل به شرح زیر است

  1. تغییر پرسپکتیو: اولین قدم برای تشخیص خطوط جاده، دریافت نمای پرسپکتیو از قاب است. Perspective Transformation می‌تواند برای تغییر چشم‌انداز یک تصویر یا ویدیوی داده شده برای دریافت بینش بهتر در مورد اطلاعات مورد نیاز استفاده شود.
  2. Image Thresholding و Canny Edge Detection: هنگامی که نمای پرسپکتیو خطوط جاده را دریافت کردیم، مرحله بعدی اعمال آستانه تصویر و تشخیص لبه Canny خواهد بود. Image Thresholding یک تکنیک رایج تقسیم بندی تصویر است که معمولاً برای جدا کردن یک شی در نظر گرفته شده به عنوان پیش زمینه از پس زمینه آن استفاده می شود.
  3. تبدیل خط Hough: روش Hough Transform برای تشخیص هر شکل (شکلی که می تواند به صورت ریاضی نمایش داده شود) در یک تصویر یا ویدیو استفاده می شود. در اینجا از روش تبدیل خط Hough برای تشخیص خطوط جاده استفاده خواهیم کرد.

1. تغییر پرسپکتیو

اولین گام در تشخیص مسیر Raspberry Pi، دریافت نمای پرسپکتیو از مسیر است. برای این کار ابتدا یک تصویر نمونه از جاده می گیریم یا می توانید آن را مستقیماً در فید ویدیو نیز اجرا کنید. تصویر اصلی جاده در زیر نشان داده شده است.

تغییر پرسپکتیو جاده در رزبری پای

اکنون همانطور که قبلا ذکر شد، برای دریافت نمای پرسپکتیو یک تصویر، باید نقاط روی تصویر را که می خواهیم اطلاعات را از آنها جمع آوری کنیم و نقاط فریم خروجی که می خواهیم تصویر خود را در داخل آن نمایش دهیم، ارائه دهیم. سپس این نقاط در آرایه های NumPy ذخیره می شوند تا به تابع Transformation پرسپکتیو وارد شوند.

width, height = 320,240
pts1 = [[0,240], [320,240], [290,30], [30,30]]
pts2 = [[0, height], [width, height], [width,0], [0,0]]
target = np.float32(pts1)
destination = np.float32(pts2)

پس از گرفتن نقاط، تبدیل پرسپکتیو را از دو مجموعه نقطه داده شده دریافت می کنیم و با استفاده از تابع ()cv2.getPerspectiveTransform و ()cv2.warpPerspective آن را با تصویر اصلی می بندیم. سینتکس هر دو تابع در زیر آمده است:

cv2.getPerspectiveTransform(src, dst)

جایی که:

  • src: مختصات منطقه مورد نظر که برای آن نمای پرسپکتیو می خواهید.
  • dst: مختصات فریم خروجی که می خواهید تصویر را در داخل آن نمایش دهید.
cv2.warpPerspective(src, dst, dsize)

جایی که:

  • src: تصویر منبع
  • dst: تصویر خروجی که دارای اندازه، dsize و همان نوع src است.
  • dsize: اندازه تصویر خروجی
matrix = cv2.getPerspectiveTransform(target, destination)
result = cv2.warpPerspective(frame, matrix, (width, height))
cv2.imshow('Result', result)

تغییر پرسپکتیو در پردازش تصویر OpenCV

2. Image Thresholding و Canny Edge Detection

حالا بعد از نمای پرسپکتیو، می‌خواهیم Image Thresholding و Canny Edge Detection را انجام دهیم. اما قبل از انجام این کار ابتدا فیلتر Grey Scale را روی تصویر اعمال می کنیم. مقیاس بندی خاکستری در تمام مراحل پردازش تصویر رایج است. این روند دیگری را تسریع می‌کند، زیرا دیگر مجبور نیستیم هنگام پردازش تصویر با جزئیات رنگ سروکار داشته باشیم. با انجام این مرحله، تصویر به چیزی شبیه به این تغییر می کند:

gray = cv2.cvtColor(result, cv2.COLOR_BGR2GRAY)

Thresholding برای جدا کردن یک شی در نظر گرفته شده به عنوان پیش زمینه از پس زمینه آن استفاده می شود. دستور برای آستانه گذاری در زیر آورده شده است:

cv2.inRange(src, lowerb, upperb)

جایی که:

  • src: آرایه تصویر ورودی (باید در مقیاس خاکستری باشد).
  • lowb: مرز پایینی ناحیه آستانه را نشان می دهد
  • upperb: مرز بالایی ناحیه آستانه را نشان می دهد
threshold = cv2.inRange(gray, 80, 200)      # THRESHOLD IMAGE OF GRAY IMAGE

خاکستری کردن فید ورودی پردازش تصویر

در مرحله بعد، تشخیص لبه را انجام می دهیم. راه‌های زیادی برای انجام آن وجود دارد، ساده‌ترین و محبوب‌ترین راه استفاده از روش canny edge از OpenCV است. تابع انجام این کار در زیر نشان داده شده است:

edges = cv2.Canny(gray, 1, 100, apertureSize=3)

دستور  cv2.Canny (src، thresholdValue 1، thresholdValue 2) خواهد بود. Threshold Value 1 و Threshold Value 2 مقادیر حداقل و حداکثر آستانه و src تصویر ورودی است.

تشخیص لبه در پردازش تصویر

3. تبدیل خط Hough

اکنون پس از دریافت نمای پرسپکتیو و اعمال Thresholding و Canny Edge Detection، در مرحله بعدی از Hough Line Transformation برای تشخیص خطوط در مسیر استفاده خواهیم کرد. ما با پیدا کردن مرکز کادر با استفاده از نقاط تصویر پرسپکتیو که قبلا تعریف کردیم شروع می کنیم.

firstSquareCenters1 = findCenter((pts2[1][0], pts2[1][1]), (pts2[2][0], pts2[2][1]))
firstSquareCenters2 = findCenter((pts2[3][0], pts2[3][1]), (pts2[0][0], pts2[0][1]))
cv2.line(result, firstSquareCenters1, firstSquareCenters2, (0, 255, 0), 1)
mainFrameCenter = findCenter(firstSquareCenters1,firstSquareCenters2)

پس از یافتن مرکز کادر، اکنون از روش Hough Line Transform برای یافتن خطوط در مسیر استفاده می کنیم. سینتکس این تابع در زیر نشان داده شده است:

cv2.HoughLines (image, lines, rho, theta, threshold)

جایی که:

  • image: تصویر ورودی، باید یک تصویر باینری باشد، بنابراین ابتدا تشخیص لبه آستانه را اعمال کنید.
  • lines: بردار را ذخیره می کند که پارامترهای (r, Φ) خطوط را ذخیره می کند.
  • rho: وضوح پارامتر r را بر حسب پیکسل نشان می دهد.
  • theta: وضوح پارامتر Φ را بر حسب رادیان نشان می دهد.
  • threshold: حداقل تعداد تقاطع ها را برای “تشخیص” یک خط نشان می دهد.
lines = cv2.HoughLinesP(mergedImage,1,np.pi/180,10,minLineLength=120,maxLineGap=250)

تعیین خطوط جاده با Hough Line

پس از یافتن خطوط، مرحله بعدی یافتن مرکز هر دو خط با استفاده از مختصاتی است که می توانیم از تابع تبدیل خط هاف بدست آوریم. پس از یافتن مرکز، از تابع left.append و right.append برای انجام تنظیمات با توجه به تغییرات موقعیت خطوط استفاده می کنیم.

برای خط در خطوط:

for line in lines:
            x1,y1,x2,y2 = line[0]
            if 0<=x1 <=width and 0<= x2 <=width :
                center = findCenter((x1,y1),(x2,y2))
                if center[0] < (width//2):
                    center1 = center
                    left.append((x1, y1))
                    left.append((x2,y2))
                else:
                    center2 = center
                    right.append((x1, y1))
                    right.append((x2,y2))

تعیین موقعیت خطوط جاده با OpenCV

4. حرکت ماشین

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

if maincenter <= 6 and maincenter > -6:
            mot.frontmiddle()
            speed = 25
        elif maincenter > 6 and frame_counter%10 ==0:
            mot.frontleft()
            speed = 25
            print("Right")
        elif(frame_counter%10 ==0):
            print("Forward")
            mot.forward(speed)
        elif maincenter < -6 and frame_counter%10 ==0:
            mot.frontright()
            speed = 25
            print("left")

آزمایش ربات خودران در مسیر

حالا که خطوط و یک نقطه مرکزی برای هدایت ماشین پیدا کردیم، بیایید ماشین را در مسیر واقعی قرار دهیم و ببینیم که چگونه کار می کند.

آزمایش ربات خودران در مسیر

این خودرو در خطوط مستقیم و در پیچ های آهسته عملکرد خوبی دارد.

تست رانندگی ماشین خودران

وضعیت خط مرکزی پردازش تصویر

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

وضعیت ماشین در پیچ تند

گمراهی ماشین خودکار ساخته شده با رزبری پای

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

موارد موجود در فایل : سورس

4.2 (5 نفر)

برای دریافت مطالب جدید کانال تلگرام یا پیج اینستاگرام ما را دنبال کنید.

محمد رحیمی

محمد رحیمی هستم. سعی میکنم در آیرنکس مطالب مفید قرار بدهم. سوالات مربوط به این مطلب را در قسمت نظرات همین مطلب اعلام کنید. سعی میکنم در اسرع وقت به نظرات شما پاسخ بدهم.

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *