Բովանդակություն:
- Քայլ 1: Բաղադրիչների հավաքում
- Քայլ 2. Raspberry Pi- ի վրա OpenCV- ի տեղադրում և հեռակա ցուցադրման կարգավորում
- Քայլ 3: Միացրեք մասերը միասին
- Քայլ 4: Առաջին փորձարկում
- Քայլ 5. Գծերի գծերի հայտնաբերում և վերնագրի գծի հաշվարկ
- Քայլ 6. PD Control- ի կիրառում
- Քայլ 7: Արդյունքներ
Video: Ինքնավար գոտի պահող մեքենա ՝ օգտագործելով Raspberry Pi և OpenCV. 7 քայլ (նկարներով)
2024 Հեղինակ: John Day | [email protected]. Վերջին փոփոխված: 2024-01-30 09:47
Այս հրահանգների մեջ կիրականացվի ինքնավար գոտի պահող ռոբոտ և կանցնի հետևյալ քայլերով.
- Մասերի հավաքում
- 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- ի տեղադրում և հեռակա ցուցադրման կարգավորում
Այս քայլը մի փոքր նյարդայնացնում է և որոշ ժամանակ կպահանջի:
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- ի կիրառում
Այժմ մենք ունենք մեր ղեկի անկյունը, որը պատրաստ է սնվել շարժիչներին: Ինչպես արդեն նշվեց, եթե ղեկի անկյունը 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- ի հետ կապված լարեր կան: Հետքը գծելու համար ես օգտագործել եմ փրփուր տախտակներ:
Ես սպասում եմ լսել ձեր առաջարկությունները `այս նախագիծը ավելի լավը դարձնելու համար: Հուսով եմ, որ այս հրահանգները բավական լավն էին ՝ ձեզ նոր տեղեկություններ հաղորդելու համար:
Խորհուրդ ենք տալիս:
Կառուցեք մինի փոխադրիչ գոտի ՝ որպես անշարժ մեքենա. 7 քայլ (նկարներով)
Կառուցեք մինի կոնվեյերային գոտի ՝ որպես անթափանց մեքենա. Այս փոքրիկ նախագիծը դեղին շարժիչով շարժիչ է օգտագործում ՝ 1 մ երկարությամբ PVC խողովակից պատրաստված 1 մետր երկարությամբ փոխակրիչ, 1 -ից 4 սոճու փայտից և նկարչի կտավից (գոտու համար): Ես անցա մի քանի տարբերակի, մինչև այն սկսեր գործել, թույլ տալով պարզ և ակնհայտ սխալ
Raspberry Pi - ինքնավար մարսագնաց OpenCV օբյեկտների հետքերով. 7 քայլ (նկարներով)
Raspberry Pi - ինքնավար մարսագնաց OpenCV օբյեկտների հետագծմամբ. Սնուցվում է Raspberry Pi 3 -ով, բաց CV օբյեկտի ճանաչմամբ, ուլտրաձայնային տվիչներով և շարժիչով շարժիչներով: Այս արբանյակը կարող է հետևել իր պատրաստած ցանկացած օբյեկտի և շարժվել ցանկացած տեղանքով
Rանկացած մեքենա/մեքենա դարձնել Bluetooth հավելվածի վերահսկման մեքենա ՝ 9 քայլ
Rանկացած R/C մեքենա վերածել Bluetooth ծրագրի կառավարման R/C մեքենայի. Այս նախագիծը ցույց է տալիս սովորական հեռակառավարման մեքենան Bluetooth (BLE) կառավարման մեքենայի Wombatics SAM01 ռոբոտաշինական տախտակով, Blynk App- ով և MIT App Inventor- ով փոխելու քայլերը: շատ ցածր գնով RC մեքենաներ են `բազմաթիվ հնարավորություններով, ինչպիսիք են LED լուսարձակները և
ՔԵՎԻՆ ամբողջական ինքնավար մեքենա. 17 քայլ (նկարներով)
ՔԵՎԻՆ Լրիվ ինքնավար մեքենա. Սա Քևինն է: Այն ռադիոկառավարվող մեքենա է `լիարժեք ինքնավար քշում կատարելու ունակությամբ: Իմ առաջին նպատակը Arduino- ի կողմից վերահսկվող ինքնավար մեքենա սարքելն էր: Այսպիսով, ես գնեցի չինական էժան շասսի: Բայց դա սարսափելի էր, քանի որ ես ի վիճակի չէի որևէ կապ ամրացնել
Ինքնավար մեքենա. 7 քայլ (նկարներով)
Ինքնավար փոխադրամիջոց. Այս նախագիծը ինքնավար նավարկող ռոբոտ է, որը փորձում է հասնել իր նպատակային դիրքին ՝ խուսափելով իր ճանապարհին խոչընդոտներից: Ռոբոտը հագեցած կլինի LiDAR սենսորով, որը կօգտագործվի իր շրջակայքում գտնվող օբյեկտների հայտնաբերման համար: Երբ օբյեկտները հայտնաբերվում են