Բովանդակություն:

Ինքնավար գոտի պահող մեքենա ՝ օգտագործելով Raspberry Pi և OpenCV. 7 քայլ (նկարներով)
Ինքնավար գոտի պահող մեքենա ՝ օգտագործելով Raspberry Pi և OpenCV. 7 քայլ (նկարներով)

Video: Ինքնավար գոտի պահող մեքենա ՝ օգտագործելով Raspberry Pi և OpenCV. 7 քայլ (նկարներով)

Video: Ինքնավար գոտի պահող մեքենա ՝ օգտագործելով Raspberry Pi և OpenCV. 7 քայլ (նկարներով)
Video: Նոր Jeep AVENGER | Կոմպակտ էլեկտրական ամենագնաց 2024, Դեկտեմբեր
Anonim
Ինքնավար գոտի պահող մեքենա ՝ օգտագործելով Raspberry Pi և OpenCV
Ինքնավար գոտի պահող մեքենա ՝ օգտագործելով Raspberry Pi և OpenCV

Այս հրահանգների մեջ կիրականացվի ինքնավար գոտի պահող ռոբոտ և կանցնի հետևյալ քայլերով.

  • Մասերի հավաքում
  • Softwareրագրային ապահովման նախադրյալների տեղադրում
  • Սարքավորումների հավաքում
  • Առաջին թեստ
  • Գծի գծերի հայտնաբերում և ուղեցույցի ցուցադրում openCV- ի միջոցով
  • PD վերահսկիչի ներդրում
  • Արդյունքները

Քայլ 1: Բաղադրիչների հավաքում

Բաղադրիչների հավաքում
Բաղադրիչների հավաքում
Բաղադրիչների հավաքում
Բաղադրիչների հավաքում
Բաղադրիչների հավաքում
Բաղադրիչների հավաքում
Բաղադրիչների հավաքում
Բաղադրիչների հավաքում

Վերոնշյալ պատկերները ցույց են տալիս այս նախագծում օգտագործված բոլոր բաղադրիչները.

  • RC մեքենա. Ես իմը ստացել եմ իմ երկրի տեղական խանութից: Այն հագեցած է 3 շարժիչով (2 -ը ՝ շնչափողի և 1 -ը ՝ ղեկի համար): Այս մեքենայի հիմնական թերությունն այն է, որ ղեկը սահմանափակված է «առանց ղեկի» և «լիարժեք ղեկի» միջև: Այլ կերպ ասած, այն չի կարող ուղղորդել որոշակի անկյան տակ, ի տարբերություն servo-ղեկային RC մեքենաների: Դուք կարող եք գտնել նմանատիպ մեքենայի հավաքածու, որը հատուկ մշակված է ազնվամորի պիի համար:
  • Raspberry pi 3 մոդել b+. Սա մեքենայի ուղեղն է, որը կանցնի մշակման բազմաթիվ փուլեր: Այն հիմնված է քառամիջուկ 64-բիթանոց պրոցեսորի վրա ՝ 1,4 ԳՀց հաճախականությամբ: Իմը ստացա այստեղից:
  • Raspberry pi 5 մպ ֆոտոխցիկի մոդուլ. Այն ապահովում է 1080p @ 30 fps, 720p @ 60 fps և 640x480p 60/90 ձայնագրություն: Այն նաև աջակցում է սերիական ինտերֆեյս, որը կարող է միացվել անմիջապես ազնվամորու պիին: Դա լավագույն տարբերակը չէ պատկերի մշակման ծրագրերի համար, բայց դա բավարար է այս նախագծի համար, ինչպես նաև այն շատ էժան է: Իմը ստացա այստեղից:
  • Շարժիչի վարորդ. Այն օգտագործվում է DC շարժիչների ուղղություններն ու արագությունները վերահսկելու համար: Այն ապահովում է 1 տախտակի մեջ 2 DC շարժիչների կառավարում և կարող է դիմակայել 1.5 Ա -ին:
  • Power Bank (ըստ ցանկության). Ես օգտագործել եմ Power Bank (գնահատված 5V, 3A) `առանձին ազնվամորու պի հզորացնելու համար: Ազնվամորու pi- ն 1 աղբյուրից հզորացնելու համար պետք է օգտագործվի քայլ առ քայլ փոխարկիչ (buck փոխարկիչ ՝ 3A ելքային հոսանք):
  • 3s (12 V) LiPo մարտկոց. Լիթիումի պոլիմերային մարտկոցները հայտնի են ռոբոտաշինության ոլորտում իրենց գերազանց կատարողականությամբ: Այն օգտագործվում է շարժիչ վարորդին սնուցելու համար: Ես իմն այստեղից եմ գնել:
  • Արականից արական եւ իգականից իգական jumper լարերը:
  • Երկկողմանի ժապավեն. Օգտագործվում է բաղադրիչները RC մեքենայի վրա ամրացնելու համար:
  • Կապույտ ժապավեն. Սա այս նախագծի շատ կարևոր բաղադրիչն է, այն օգտագործվում է այն երկու գծերի գծերի պատրաստման համար, որոնց միջև մեքենան կշարժվի: Դուք կարող եք ընտրել ցանկացած գույն, որը ցանկանում եք, բայց ես խորհուրդ եմ տալիս ընտրել տարբեր գույներ, քան շրջակա միջավայրում եղածները:
  • Փայտե շերտեր և կայծակաճարմանդներ:
  • Պտուտակահան.

Քայլ 2. Raspberry Pi- ի վրա OpenCV- ի տեղադրում և հեռակա ցուցադրման կարգավորում

Raspberry Pi- ի վրա OpenCV- ի տեղադրում և հեռակա ցուցադրման կարգավորում
Raspberry Pi- ի վրա OpenCV- ի տեղադրում և հեռակա ցուցադրման կարգավորում

Այս քայլը մի փոքր նյարդայնացնում է և որոշ ժամանակ կպահանջի:

OpenCV (Open source Computer Vision) բաց կոդով համակարգչային տեսողության և մեքենայական ուսուցման ծրագրային գրադարան է: Գրադարանը ունի ավելի քան 2500 օպտիմիզացված ալգորիթմ: Հետևեք ԱՅՍ շատ պարզ ուղեցույցին ՝ բաց գույնի CV- ն ձեր ազնվամորի pi- ի վրա տեղադրելու, ինչպես նաև ազնվամորի pi OS- ի տեղադրման համար (եթե դա դեռ չեք արել): Խնդրում ենք նկատի ունենալ, որ openCV- ի կառուցման գործընթացը կարող է տևել մոտ 1.5 ժամ լավ սառեցված սենյակում (քանի որ պրոցեսորի ջերմաստիճանը շատ բարձր կլինի), ուստի խմեք թեյ և համբերատար սպասեք: D:

Հեռակա ցուցադրման համար հետևեք նաև ԱՅՍ ուղեցույցին ՝ ձեր Windows/Mac սարքից ձեր ազնվամորի pi- ին հեռավոր մուտք գործելու համար:

Քայլ 3: Միացրեք մասերը միասին

Մասերի միացում միասին
Մասերի միացում միասին
Մասերի միացում միասին
Մասերի միացում միասին
Մասերի միացում միասին
Մասերի միացում միասին

Վերոնշյալ պատկերները ցույց են տալիս կապերը ազնվամորու pi- ի, տեսախցիկի մոդուլի և շարժիչի վարորդի միջև: Խնդրում ենք նկատի ունենալ, որ իմ օգտագործած շարժիչները ներծծում են 0,35 Ա յուրաքանչյուր 9 Վ լարման դեպքում, ինչը ապահով է դարձնում շարժիչի վարորդը միաժամանակ 3 շարժիչով: Եվ քանի որ ես ուզում եմ վերահսկել 2 շարժիչ շարժիչի արագությունը (1 հետևի և 1 առջևի) ճիշտ նույն կերպ, ես դրանք միացրի նույն նավահանգստին: Մեքենայի վարորդը տեղադրեցի մեքենայի աջ կողմում ՝ օգտագործելով կրկնակի ժապավեն: Ինչ վերաբերում է տեսախցիկի մոդուլին, ապա ես, իրոք, տեղադրեցի կայծակաճարմանդ ՝ պտուտակների անցքերի միջև, ինչպես ցույց է տրված վերևում նկարում: Այնուհետև տեսախցիկը տեղադրում եմ փայտե ձողի վրա, որպեսզի կարողանամ հարմարեցնել տեսախցիկի դիրքը, ինչպես ցանկանում եմ: Փորձեք հնարավորինս տեղադրել տեսախցիկը մեքենայի մեջտեղում: Խորհուրդ եմ տալիս տեսախցիկը տեղադրել գետնից առնվազն 20 սմ բարձրության վրա, որպեսզի մեքենայի առջևի տեսադաշտը ավելի լավանա: Ֆրիտզինգի սխեման կցված է ստորև:

Քայլ 4: Առաջին փորձարկում

Առաջին թեստ
Առաջին թեստ
Առաջին թեստ
Առաջին թեստ

Տեսախցիկի փորձարկում

Տեսախցիկը տեղադրվելուց և openCV գրադարանի կառուցումից հետո ժամանակն է փորձարկել մեր առաջին պատկերը: Մենք լուսանկարելու ենք pi cam- ից և այն պահելու ենք որպես «original.jpg»: Այն կարող է իրականացվել 2 եղանակով.

1. Օգտագործելով տերմինալային հրամաններ

Բացեք տերմինալի նոր պատուհան և մուտքագրեք հետևյալ հրամանը.

raspistill -o original.jpg

Սա կվերցնի անշարժ պատկեր և կպահի այն "/pi/original.jpg" գրացուցակում:

2. Օգտագործելով ցանկացած Python 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 () - ը կկարդա տեսախցիկից եկող յուրաքանչյուր շրջանակ և կպահի այն «շրջանակ» կոչվող փոփոխականի մեջ: flip () գործառույթը կշրջի պատկերը y- առանցքի նկատմամբ (ուղղահայաց), քանի որ ես ֆոտոխցիկս հակառակն եմ ամրացնում: imshow () - ը կցուցադրի մեր շրջանակները `« բնօրինակը »բառով, իսկ imwrite () լուսանկարը կպահի որպես original.jpg: waitKey (1) կսպասի 1 ms, երբ ստեղնաշարի ցանկացած կոճակ սեղմվի և վերադարձնի իր ASCII ծածկագիրը: եթե փախչելու (esc) կոճակը սեղմվի, 27 -ի տասնորդական արժեքը վերադարձվում է և համապատասխանաբար կկոտրի օղակը: video.release- ը () կդադարեցնի ձայնագրությունը և կջնջի AllWindows- ը () կփակի imshow () գործառույթով բացված յուրաքանչյուր պատկեր:

Ես խորհուրդ եմ տալիս փորձարկել ձեր լուսանկարը երկրորդ մեթոդով ՝ openCV գործառույթներին ծանոթանալու համար: Պատկերը պահվում է «/pi/original.jpg» գրացուցակում: Իմ ֆոտոխցիկի արած սկզբնական լուսանկարը ցուցադրված է վերևում:

Փորձարկման շարժիչներ

Այս քայլը կարևոր է յուրաքանչյուր շարժիչի պտույտի ուղղությունը որոշելու համար: Նախ, եկեք համառոտ ներկայացնենք շարժիչ -վարորդի աշխատանքի սկզբունքի մասին: Վերևի պատկերը ցույց է տալիս շարժիչի վարորդի քորոցը: Միացնել A- ն, մուտքագրումը 1 -ը և մուտքը 2 -ը կապված են շարժիչի A կառավարման հետ: Միացնել B- ն, մուտքագրումը 3 -ը և մուտքը 4 -ը կապված են շարժիչի B- ի կառավարման հետ: Ուղղության վերահսկումը սահմանվում է «Մուտք» մասով, իսկ արագության վերահսկումը `« Միացնել »մասով: Շարժիչի A ուղղությունը վերահսկելու համար, օրինակ, մուտքագրեք 1 -ը HIGH (այս դեպքում 3.3 V, քանի որ մենք օգտագործում ենք ազնվամորի pi) և մուտքագրումը 2 -ը LOW- ի վրա, շարժիչը պտտվելու է որոշակի ուղղությամբ և հակառակ արժեքները սահմանելով: դեպի մուտքագրում 1 և մուտքագրում 2, շարժիչը պտտվելու է հակառակ ուղղությամբ: Եթե Մուտք 1 = Մուտք 2 = (Բարձր կամ OWԱOWՐ), շարժիչը չի շրջվի: Միացնել կապումներն ընդունում են Pulse Width Modulation (PWM) մուտքային ազդանշանը ազնվամորուց (0 -ից մինչև 3.3 Վ) և համապատասխանաբար գործարկում են շարժիչները: Օրինակ, 100% PWM ազդանշանը նշանակում է, որ մենք աշխատում ենք առավելագույն արագության վրա, իսկ 0% PWM ազդանշանը նշանակում է, որ շարժիչը չի պտտվում: Հետևյալ ծածկագիրը օգտագործվում է շարժիչների ուղղությունները որոշելու և դրանց արագությունները ստուգելու համար:

ներմուծման ժամանակը

ներմուծել RPi. GPIO- ն որպես GPIO GPIO.setwarnings (False) # Motorեկի շարժիչների պտուտակներ steering_enable = 22 # Physical Pin 15 in1 = 17 # Physical Pin 11 in2 = 27 # Physical Pin 13 #Throttle Motors Pins throttle_enable = 25 # Physical Pin 22 in3 = 23 # Ֆիզիկական կապ 16 in4 = 24 # Ֆիզիկական կապ 18 GPIO.setmode (GPIO. BCM) # Օգտագործեք GPIO համարակալում ֆիզիկական համարակալման փոխարեն GPIO.setup (in1, GPIO.out) GPIO.setup (in2, GPIO.out) GPIO: կարգավորում (in3, GPIO.out) GPIO.setup (in4, GPIO.out) GPIO.setup (throttle_enable, GPIO.out) GPIO.setup (steering_enable, GPIO.out) # eringեկի շարժիչի կառավարման GPIO.output (in1, GPIO. HIGH) GPIO.output (in2, GPIO. LOW) ղեկ = GPIO. PWM (steering_enable, 1000) # սահմանեք անջատման հաճախականությունը 1000 Հց ղեկանիվի վրա: կանգառ () # Throttle Motors Control GPIO.output (in3, GPIO. HIGH) GPIO. ելք (in4, GPIO. LOW) շնչափող = GPIO. PWM (շնչափողը միացնելու հնարավորություն, 1000) # անջատման հաճախականությունը դարձրեք 1000 Հց շնչափող: դադարեցնել () time.sleep (1) շնչափող: սկսել (25) # շարժիչը սկսում է 25 -ից % PWM ազդանշան-> (մարտկոցի լարման 0.25 *) - վարորդի ղեկի կորուստ. մեկնարկ (100) # շարժիչը սկսում է 100% PWM ազդանշանից-> (1 * մարտկոցի լարման) - վարորդի կորստի ժամանակը:

Այս ծածկագիրը 3 վայրկյան կաշխատի շնչափող և ղեկային շարժիչները, իսկ հետո դրանք կդադարեցնի: (Վարորդի կորուստը) կարելի է որոշել վոլտմետր օգտագործելով: Օրինակ, մենք գիտենք, որ 100% PWM ազդանշանը պետք է հաղորդի մարտկոցի ամբողջ լարումը շարժիչի տերմինալում: Բայց, PWM- ը 100%սահմանելով, ես գտա, որ վարորդը առաջացնում է 3 Վ լարման անկում, իսկ շարժիչը ստանում է 9 Վ 12 Վ -ի փոխարեն (հենց այն, ինչ ինձ պետք է): Կորուստը գծային չէ, այսինքն `100% կորուստը շատ տարբեր է 25% կորստից: Վերոնշյալ կոդը գործարկելուց հետո իմ արդյունքները հետևյալն էին.

Շեղման արդյունքներ. Եթե in3 = HIGH և in4 = LOW, շարժիչներն ունենալու են ժամացույց-իմաստուն (CW) պտույտ, այսինքն մեքենան առաջ է շարժվելու: Հակառակ դեպքում մեքենան հետ կշարժվի:

Resultsեկի արդյունքներ. Եթե in1 = HIGH և in2 = LOW, ղեկի շարժիչը կշրջվի առավելագույն ձախով, այսինքն մեքենան կշրջի ձախ: Հակառակ դեպքում մեքենան կշրջի աջ: Որոշ փորձերից հետո ես պարզեցի, որ ղեկի շարժիչը չի պտտվի, եթե PWM ազդանշանը 100% չլիներ (այսինքն.

Քայլ 5. Գծերի գծերի հայտնաբերում և վերնագրի գծի հաշվարկ

Գծերի գծերի հայտնաբերում և վերնագրի գծի հաշվարկ
Գծերի գծերի հայտնաբերում և վերնագրի գծի հաշվարկ
Գծերի գծերի հայտնաբերում և վերնագրի գծի հաշվարկ
Գծերի գծերի հայտնաբերում և վերնագրի գծի հաշվարկ
Գծի գծերի հայտնաբերում և վերնագրի գծի հաշվարկ
Գծի գծերի հայտնաբերում և վերնագրի գծի հաշվարկ

Այս քայլում կբացատրվի այն ալգորիթմը, որը վերահսկելու է մեքենայի շարժը: Առաջին պատկերը ցույց է տալիս ամբողջ գործընթացը: Համակարգի մուտքը պատկերներ են, ելքը ՝ տետա (ղեկի անկյունը աստիճաններով): Նշենք, որ մշակումը կատարվում է 1 պատկերի վրա և կկրկնվի բոլոր շրջանակների վրա:

Տեսախցիկ:

Տեսախցիկը կսկսի տեսագրել (320 x 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 # The loop while Trueիշտ: ret, frame = video.read () frame = cv2.flip (շրջանակ, -1) cv2.imshow («օրիգինալ», շրջանակ) key = cv2.wait Հիմնական (1), եթե բանալի == 27: ընդմիջում տեսանյութ: թողարկում () cv2.destroyAllWindows ()

Այստեղ ծածկագիրը ցույց կտա 4 -րդ քայլում ստացված սկզբնական պատկերը և ցուցադրվում է վերը նշված պատկերներում:

Փոխարկեք HSV գունային տարածության

Այժմ տեսախցիկը տեսախցիկից որպես շրջանակ վերցնելուց հետո հաջորդ քայլը յուրաքանչյուր շրջանակը փոխակերպել երանգի, հագեցվածության և արժեքի (HSV) գունային տարածության: Դրանով զբաղվելու հիմնական առավելությունն այն է, որ կարողանաք տարբերել գույները դրանց լուսավորության մակարդակով: Եվ ահա HSV գունային տարածքի լավ բացատրություն: HSV- ին փոխակերպումը կատարվում է հետևյալ գործառույթի միջոցով.

def convert_to_HSV (շրջանակ):

hsv = cv2.cvtColor (շրջանակ, cv2. COLOR_BGR2HSV) cv2.imshow ("HSV", hsv) վերադարձնել hsv

Այս գործառույթը կկոչվի հիմնական հանգույցից և կվերադարձնի շրջանակը HSV գունային տարածքում: HSV գունային տարածքում իմ կողմից ձեռք բերված շրջանակը վերևում է:

Բացահայտեք կապույտ գույնը և եզրերը

Պատկերը HSV գունային տարածության վերածելուց հետո ժամանակն է հայտնաբերել միայն մեզ հետաքրքրող գույնը (այսինքն ՝ կապույտ գույնը, քանի որ դա գծի գծերի գույնն է): HSV շրջանակից կապույտ գույնը հանելու համար պետք է նշվի երանգի, հագեցվածության և արժեքի մի շարք: անդրադարձեք այստեղ ՝ HSV արժեքների մասին ավելի լավ պատկերացում կազմելու համար: Որոշ փորձերից հետո կապույտ գույնի վերին և ստորին սահմանները ցուցադրվում են ստորև բերված ծածկագրում: Եվ յուրաքանչյուր շրջանակում ընդհանուր աղավաղումը նվազեցնելու համար եզրերը հայտնաբերվում են միայն ծայրահեղ եզրային դետեկտորի միջոցով: Մանրամասների մասին ավելին կարելի է գտնել այստեղ: Գլխավոր կանոնն է ՝ ընտրել Canny () ֆունկցիայի պարամետրերը 1: 2 կամ 1: 3 հարաբերակցությամբ:

def dete_edges (շրջանակ):

lower_blue = np.array ([90, 120, 0], dtype = "uint8") # կապույտ գույնի # ստորին սահման վերին_գույն = np.array ([150, 255, 255], dtype = "uint8") # վերին սահմանը կապույտ գույնի դիմակ = cv2.inRange (hsv, lower_blue, above_blue) # այս դիմակը կզտվի ամեն ինչից բացի, կապույտից # հայտնաբերել եզրեր եզրեր = cv2. Canny (դիմակ, 50, 100) cv2.imshow («եզրեր», եզրեր) վերադարձի եզրեր

Այս գործառույթը նույնպես կանչվելու է հիմնական օղակից, որը որպես պարամետր վերցնում է HSV գունային տարածության շրջանակը և վերադարձնում եզրով շրջանակը: Իմ ձեռք բերած եզրային շրջանակը վերևում է:

Ընտրեք հետաքրքրությունների շրջան (ROI)

Հետաքրքրության շրջանի ընտրությունը շատ կարևոր է կենտրոնանալու միայն շրջանակի 1 շրջանի վրա: Այս դեպքում ես չեմ ուզում, որ մեքենան շրջապատում շատ իրեր տեսնի: Ես պարզապես ցանկանում եմ, որ մեքենան կենտրոնանա գծի գծերի վրա և անտեսի որևէ այլ բան: P. S: կոորդինատային համակարգը (x և y առանցքները) սկսվում է վերին ձախ անկյունից: Այլ կերպ ասած, կետը (0, 0) սկսվում է վերին ձախ անկյունից: y առանցքը բարձրություն է, իսկ x առանցքը `լայնություն: Ստորև բերված ծածկագիրը ընտրում է հետաքրքրություն ներկայացնող շրջանը ՝ կենտրոնանալու միայն շրջանակի ստորին կեսի վրա:

def region_of_inrest (եզրեր):

բարձրություն, լայնություն = եզրեր. ձև # հանել եզրերի բարձրությունը և լայնությունը շրջանակի դիմակ = np.zeros_like (եզրեր) # կատարել դատարկ մատրիցա եզրերի շրջանակի նույն չափսերով # կենտրոնանալ միայն էկրանի ստորին կեսին # նշել կոորդինատները 4 միավոր (ներքևի ձախ, վերին ձախ, վերևի աջ, ներքևի աջ) պոլիգոն = np.array (

Այս գործառույթը որպես պարամետր կվերցնի եզրագծված շրջանակը և գծում է 4 նախադրված կետերով բազմանկյուն: Այն կկենտրոնանա միայն այն բանի վրա, ինչ գտնվում է բազմանկյունի ներսում և անտեսում է դրանից դուրս գտնվող ամեն ինչ: Իմ հետաքրքրությունների շրջանակը ներկայացված է վերևում:

Հայտնաբերել գծերի հատվածներ

Hough transform- ը օգտագործվում է եզրագծված շրջանակից գծերի հատվածները հայտնաբերելու համար: Hough transform- ը մաթեմատիկական ձևով ցանկացած ձև հայտնաբերելու տեխնիկա է: Այն կարող է հայտնաբերել գրեթե ցանկացած առարկա, նույնիսկ եթե այն աղավաղված է ըստ որոշ ձայների: Hough transform- ի համար մեծ հղում է ցուցադրվում այստեղ: Այս հավելվածի համար cv2. HoughLinesP () գործառույթը օգտագործվում է յուրաքանչյուր շրջանակում գծեր հայտնաբերելու համար: Այս գործառույթի ընդունման կարևոր պարամետրերն են.

cv2. HoughLinesP (շրջանակ, rho, theta, min_threshold, minLineLength, maxLineGap)

  • Շրջանակ. Այն շրջանակն է, որը մենք ցանկանում ենք հայտնաբերել գծերը:
  • rho: Դա հեռավորության ճշգրտությունն է պիքսելներով (սովորաբար դա = 1 է)
  • theta. անկյունային ճշգրտություն ռադիաններում (միշտ = np.pi/180 ~ 1 աստիճան)
  • min_threshold. այն պետք է ստանա նվազագույն քվե, որպեսզի այն համարվի որպես տող
  • minLineLength: գծի նվազագույն երկարությունը պիքսելներով: Այս թվից կարճ ցանկացած տող չի համարվում տող:
  • maxLineGap. պիքսելներում առավելագույն բացը 2 տողերի միջև, որը պետք է դիտվի որպես 1 տող: (Այն իմ դեպքում չի օգտագործվում, քանի որ այն գծերի գծերը, որոնք ես օգտագործում եմ, բացեր չունեն):

Այս գործառույթը վերադարձնում է տողի վերջնակետերը: Հետևյալ գործառույթը կանչվում է իմ հիմնական հանգույցից ՝ Hough ձևափոխման միջոցով գծեր հայտնաբերելու համար.

def dete_line_segments (cropped_edges):

rho = 1 theta = np.pi / 180 ր

Միջին թեքություն և ընդհատում (մ, բ):

հիշենք, որ գծի հավասարումը տրվում է y = mx + b- ով: Որտեղ m- ը գծի թեքությունն է, իսկ b- ը `y- միջանցքը: Այս մասում հաշվարկվելու է Hough transform- ի միջոցով հայտնաբերված գծերի հատվածների թեքությունների և միջանցքների միջինը: Նախքան դա անելը, եկեք նայենք վերևում ցուցադրված շրջանակի սկզբնական լուսանկարին: Թվում է, որ ձախ գոտին բարձրանում է դեպի վեր, ուստի այն ունի բացասական թեքություն (հիշո՞ւմ եք կոորդինատային համակարգի մեկնարկային կետը): Այլ կերպ ասած, ձախ գոտու գիծն ունի x1 <x2 և y2 x1 և y2> y1, ինչը դրական թեքություն կտա: Այսպիսով, դրական թեքությամբ բոլոր գծերը համարվում են ուղիղ գծի կետեր: Ուղղահայաց գծերի դեպքում (x1 = x2), թեքությունը կլինի անսահման: Այս դեպքում մենք բաց կթողնենք բոլոր ուղղահայաց գծերը `սխալ թույլ չտալու համար: Այս հայտնաբերմանը ավելի ճշգրիտ ավելացնելու համար յուրաքանչյուր շրջանակ բաժանվում է երկու տարածաշրջանի (աջ և ձախ) 2 սահմանագծերի միջոցով: Լայնության բոլոր կետերը (x առանցքի կետերը) ավելի մեծ են, քան աջ սահմանագիծը, կապված են աջ գոտու հաշվարկի հետ: Եվ եթե լայնության բոլոր կետերը փոքր են ձախ սահմանագծից, դրանք կապված են ձախ գոտու հաշվարկի հետ: Հետևյալ գործառույթը վերցնում է մշակման ենթակա շրջանակը և Hough- ի օգտագործմամբ հայտնաբերված գոտու հատվածները և վերադարձնում երկու գոտիների գծերի միջին թեքությունը և ընդհատումը:

def average_slope_intercept (շրջանակ, տող_ հատվածներ):

lane_lines = եթե տողերի հատվածները Չկան. տպել («տողի հատված չի հայտնաբերվել») վերադարձնել գծի գծերի բարձրությունը, լայնությունը, _ = շրջանակը: left_fit = right_fit = border = left_region_boundary = width * (1 - border) right_region_boundary = լայնություն * սահմանագիծ գծի հատվածի համար գծի_ հատվածներում. x1, y1, x2, y2 գծի հատվածում., (y1, y2), 1) թեքություն = (y2 - y1) / (x2 - x1) ընդհատում = y1 - (թեքություն * x1) եթե թեքություն <0: եթե x1 <ձախ_մարզի սահման և x2 աջ_մարզային_սահման և x2> աջ_մարզ_սահման. աջ_հարմարություն: հավելված ((թեքություն, ընդհատում)) left_fit_average = np. միջին (left_fit, axis = 0) եթե len (left_fit)> 0: lane_lines.append (make_points (շրջանակ, left_fit_average)) right_fit_average = np.average (right_fit, առանցք = 0) եթե len (right_fit)> 0: lane_lines.append (make_points (շրջանակ, right_fit_average)) # lane_lines- ը երկկողմանի զանգված է, որը բաղկացած է աջ և ձախ գոտու գծերի # կոորդինատներից, օրինակ ՝ lan e_lines =

make_points () միջին_slope_intercept () ֆունկցիայի օժանդակ գործառույթն է, որը կվերադարձնի գծի գծերի սահմանափակ կոորդինատները (ներքևից մինչև շրջանակի կեսը):

def make_points (շրջանակ, տող):

բարձրություն, լայնություն, _ = շրջանակ. ձևի թեքություն, միջանցք = տող y1 = բարձրություն # շրջանակի ներքև int ((y1 - ընդհատում) / թեքություն) x2 = int ((y2 - ընդհատում) / թեքություն) վերադարձ

0 -ի բաժանումը կանխելու համար ներկայացվում է մի պայման. Եթե թեքություն = 0, ինչը նշանակում է y1 = y2 (հորիզոնական գիծ), ապա լանջին տվեք 0 -ի մոտ արժեք: Սա չի ազդի ալգորիթմի կատարման վրա, ինչպես նաև կկանխի անհնար դեպքը (բաժանելով 0 -ի):

Շրջանակների վրա գծերի գծերը ցուցադրելու համար օգտագործվում է հետևյալ գործառույթը.

def display_lines (շրջանակ, տողեր, line_color = (0, 255, 0), line_width = 6). # տողի գույն (B, G, R)

line_image = np.zeros_like (շրջանակ) եթե տողերը Չկան: տողերի համար `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) return line_image

cv2.addWeighted () ֆունկցիան վերցնում է հետևյալ պարամետրերը և այն օգտագործվում է երկու պատկեր համատեղելու համար, բայց յուրաքանչյուրին կշիռ տալով:

cv2.add Կշռված (պատկեր 1, ալֆա, պատկեր 2, բետա, գամմա)

Եվ հաշվարկում է ելքային պատկերը ՝ օգտագործելով հետևյալ հավասարումը.

ելք = ալֆա * պատկեր 1 + բետա * պատկեր 2 + գամմա

Cv2.addWeighted () ֆունկցիայի մասին լրացուցիչ տեղեկություններ կարելի է ստանալ այստեղ:

Հաշվարկել և ցուցադրել վերնագրի տողը

Սա վերջին քայլն է, նախքան արագություն կիրառելը մեր շարժիչներին: Վերնագծի գիծը պատասխանատու է ղեկին շարժիչին տալու այն ուղղությունը, որով այն պետք է պտտվի և շնչափող շարժիչներին տալու այն արագությունը, որով նրանք կաշխատեն: Վերնագրի գծի հաշվարկը մաքուր եռանկյունաչափություն է, օգտագործվում են tan և atan (tan^-1) եռանկյունաչափական ֆունկցիաները: Որոշ ծայրահեղ դեպքեր են, երբ տեսախցիկը հայտնաբերում է միայն մեկ գծի գիծ կամ երբ այն չի հայտնաբերում: Այս բոլոր դեպքերը ցուցադրվում են հետևյալ գործառույթում.

def get_steering_angle (շրջանակ, գոտի)

բարձրություն, լայնություն, _ = շրջանակ. ձև, եթե len (lane_lines) == 2: # եթե երկու գծի գծեր են հայտնաբերվում _, _, left_x2, _ = lane_lines [0] [0] # գծի_ գծերի զանգվածից մնացորդ x2, _ աջ) == 1: # եթե միայն մեկ տող է հայտնաբերվում x1, _, x2, _ = lane_lines [0] [0] x_offset = x2 - x1 y_offset = int (բարձրություն / 2) elif len (lane_lines) == 0: # եթե ոչ մի տող չի հայտնաբերվում x_offset = 0 y_offset = int (բարձրություն / 2) անկյուն_ դեպի_միջին_ռադիան = մաթեմատիկա. 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) height, width, _ = frame.shape steering_angle_radian = steering_angle / 180.0 * math.pi x1 = int (width / 2) y1 = height x2 = int (x1 - height / 2 / math.tan (steering_angle_radian)) y2 = int (բարձրություն / 2) cv2.line (heading_image, (x1, y1), (x2, y2), line_color, line_width) heading_image = cv2.addWeighted (frame, 0.8, heading_image, 1, 1) վերադառնալ 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) #գործառույթների զանգահարում = 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 Control- ի կիրառում

Կիրառելով PD վերահսկողություն
Կիրառելով PD վերահսկողություն

Այժմ մենք ունենք մեր ղեկի անկյունը, որը պատրաստ է սնվել շարժիչներին: Ինչպես արդեն նշվեց, եթե ղեկի անկյունը 90 -ից մեծ է, մեքենան պետք է թեքվի աջ, հակառակ դեպքում ՝ թեքվի ձախ: Ես կիրառեցի մի պարզ կոդ, որը ղեկը շարժում է աջ, եթե անկյունը 90 -ից բարձր է, և այն թեքում է ձախ, եթե ղեկի անկյունը 90 -ից փոքր է `անընդհատ շնչափող արագությամբ (10% PWM), բայց ես ստացել եմ բազմաթիվ սխալներ: Հիմնական սխալը, որը ես ստացել եմ, այն է, երբ մեքենան մոտենում է ցանկացած շրջադարձին, ղեկի շարժիչը գործում է ուղղակիորեն, բայց շնչափող շարժիչները խցանում են: Փորձեցի բարձրացնել շնչափողի արագությունը (20% PWM) պտույտների ժամանակ, բայց ավարտեցի ռոբոտի ՝ գոտիներից դուրս գալով: Ինձ պետք էր մի բան, որը շատ մեծացնում էր շնչափողի արագությունը, եթե ղեկի անկյունը շատ մեծ է և մի փոքր մեծացնում է արագությունը, եթե ղեկի անկյունը այդքան էլ մեծ չէ, այնուհետև արագությունը նվազեցնում է սկզբնական արժեքին, երբ մեքենան մոտենում է 90 աստիճանի (շարժվում է ուղիղ): Լուծումը PD վերահսկիչի օգտագործումն էր:

PID վերահսկիչ նշանակում է Համամասնական, ամբողջական և ածանցյալ վերահսկիչ: Այս տիպի գծային կարգավորիչները լայնորեն կիրառվում են ռոբոտաշինության ծրագրերում: Վերևի պատկերը ցույց է տալիս բնորոշ PID հետադարձ կապի կառավարման օղակը: Այս վերահսկիչի նպատակն է հասնել «սահմանային կետին» ամենաարդյունավետ եղանակով, ի տարբերություն «միացված -անջատված» կարգավարների, որոնք միացնում կամ անջատում են կայանը ըստ որոշ պայմանների: Որոշ հիմնաբառեր պետք է հայտնի լինեն.

  • Սահմանման կետ. Այն ցանկալի արժեքն է, որին ցանկանում եք հասնել ձեր համակարգին:
  • Փաստացի արժեք. Դա սենսորով զգացված իրական արժեքն է:
  • Սխալ. Դա տարբերությունն է կետի և իրական արժեքի միջև (սխալ = Սահմանման կետ - Փաստացի արժեք):
  • Կառավարվող փոփոխական. Իր անունից `այն փոփոխականը, որը ցանկանում եք վերահսկել:
  • Kp: Համաչափ հաստատուն:
  • Ki: Ինտեգրալ հաստատուն:
  • Kd: Ածանցյալ հաստատուն:

Մի խոսքով, PID կառավարման համակարգի օղակը գործում է հետևյալ կերպ.

  • Օգտագործողը սահմանում է համակարգի հասնելու համար անհրաժեշտ սահմանման կետը:
  • Սխալը հաշվարկված է (սխալ = սահմանման կետ - փաստացի):
  • P վերահսկիչը առաջացնում է սխալի արժեքին համաչափ գործողություն: (մեծանում է սխալը, ավելանում է նաև P գործողությունը)
  • I վերահսկիչը ժամանակի ընթացքում կմիավորի սխալը, որը վերացնում է համակարգի կայուն վիճակի սխալը, բայց մեծացնում է դրա գերազանցումը:
  • D վերահսկիչը պարզապես սխալի ժամանակի ածանցյալն է: Այլ կերպ ասած, դա սխալի թեքությունն է: Այն կատարում է սխալի ածանցյալին համաչափ գործողություն: Այս վերահսկիչը մեծացնում է համակարգի կայունությունը:
  • Կարգավորիչի ելքը կլինի երեք կարգավորիչների գումարը: Հսկիչի ելքը կդառնա 0, եթե սխալը դառնա 0:

PID վերահսկիչի հիանալի բացատրություն կարելի է գտնել այստեղ:

Վերադառնալով երթևեկելի գոտի պահող մեքենային ՝ իմ վերահսկվող փոփոխականը արագությունը թռչում էր (քանի որ ղեկն ունի ընդամենը երկու վիճակ ՝ աջ կամ ձախ): Այդ նպատակով օգտագործվում է PD վերահսկիչ, քանի որ D գործողությունը մեծացնում է շնչափողի արագությունը, եթե սխալի փոփոխությունը շատ մեծ է (այսինքն ՝ մեծ շեղում) և դանդաղեցնում մեքենան, եթե այս սխալի փոփոխությունը մոտենա 0 -ին: վերահսկիչ:

  • Սահմանեք սահմանաչափը 90 աստիճանի վրա (ես միշտ ուզում եմ, որ մեքենան ուղիղ շարժվի)
  • Հաշվել է շեղման անկյունը միջինից
  • Շեղումը տալիս է երկու տեղեկատվություն. Որքա՞ն մեծ է սխալը (շեղման մեծությունը) և ինչ ուղղություն պետք է տանի ղեկային շարժիչը (շեղման նշան): Եթե շեղումը դրական է, մեքենան պետք է ուղղորդի աջ, հակառակ դեպքում ՝ ձախ:
  • Քանի որ շեղումը կա՛մ բացասական է, կա՛մ դրական, «սխալ» փոփոխական է սահմանվում և միշտ հավասար է շեղման բացարձակ արժեքին:
  • Սխալը բազմապատկվում է Kp հաստատունով:
  • Սխալը ենթարկվում է ժամանակի տարբերակման և բազմապատկվում մշտական Kd- ով:
  • Շարժիչների արագությունը թարմացվում է, և հանգույցը նորից սկսվում է:

Հետևյալ ծածկագիրը օգտագործվում է հիմնական հանգույցում `շնչափող շարժիչների արագությունը վերահսկելու համար.

արագություն = 10 # աշխատանքային արագություն % PWM- ում

# Փոփոխականներ, որոնք պետք է թարմացվեն յուրաքանչյուր հանգույց lastTime = 0 lastError = 0 # PD հաստատուններ Kp = 0.4 Kd = Kp * 0.65 Մինչդեռ True: այժմ = time.time () # ընթացիկ ժամանակի փոփոխական dt = now - lastTime շեղում = ղեկի_անկյուն - 90 # համարժեք to angle_to_mid_deg փոփոխական սխալ = abs (շեղում), եթե շեղումը -5: # չեն ուղղորդում, եթե կա 10 աստիճանի սխալի միջակայքի շեղում = 0 սխալ = 0 GPIO.output (in1, GPIO. LOW) GPIO.output (in2, GPIO. LԱՆՈ)) steering.stop () elif շեղում> 5: # ուղղել աջ, եթե շեղումը դրական է GPIO.output (in1, GPIO. LOW) GPIO.output (in2, GPIO. HIGH) steering.start (100) elif շեղում < -5: # ուղղություն դեպի ձախ, եթե շեղումը բացասական է GPIO.output (in1, GPIO. HIGH) GPIO.output (in2, GPIO. LOW) steering.start (100) ածանցյալ = kd * (սխալ - lastError) / dt համամասնական = kp * սխալ PD = int (արագություն + ածանցյալ + համամասնական) spd = abs (PD) եթե spd> 25: spd = 25 շնչափող. մեկնարկ (spd) lastError = սխալ lastTime = time.time ()

Եթե սխալը շատ մեծ է (միջինից շեղումը մեծ է), համամասնական և ածանցյալ գործողությունները մեծ են, ինչը հանգեցնում է թռիչքի բարձր արագության: Երբ սխալը մոտենում է 0 -ին (միջինից շեղումը ցածր է), ածանցյալ գործողությունը գործում է հակառակը (թեքությունը բացասական է), և շնչափողի արագությունը նվազում է `համակարգի կայունությունը պահպանելու համար: Ամբողջական ծածկագիրը կցված է ստորև:

Քայլ 7: Արդյունքներ

Վերոնշյալ տեսանյութերը ցույց են տալիս իմ ստացած արդյունքները: Այն ավելի ճշգրտման և հետագա ճշգրտումների կարիք ունի: Ես միացնում էի ազնվամորու pi- ն իմ LCD էկրանին, քանի որ իմ ցանցով տեսահոլովակը հոսում էր մեծ ուշացումով և շատ վրդովեցուցիչ էր դրա հետ աշխատելը, այդ իսկ պատճառով տեսանյութում ազնվամորի pi- ի հետ կապված լարեր կան: Հետքը գծելու համար ես օգտագործել եմ փրփուր տախտակներ:

Ես սպասում եմ լսել ձեր առաջարկությունները `այս նախագիծը ավելի լավը դարձնելու համար: Հուսով եմ, որ այս հրահանգները բավական լավն էին ՝ ձեզ նոր տեղեկություններ հաղորդելու համար:

Խորհուրդ ենք տալիս: