Სარჩევი:

ავტონომიური ბილიკი, მანქანის დაცვა ჟოლოს Pi და OpenCV გამოყენებით: 7 ნაბიჯი (სურათებით)
ავტონომიური ბილიკი, მანქანის დაცვა ჟოლოს Pi და OpenCV გამოყენებით: 7 ნაბიჯი (სურათებით)

ვიდეო: ავტონომიური ბილიკი, მანქანის დაცვა ჟოლოს Pi და OpenCV გამოყენებით: 7 ნაბიჯი (სურათებით)

ვიდეო: ავტონომიური ბილიკი, მანქანის დაცვა ჟოლოს Pi და OpenCV გამოყენებით: 7 ნაბიჯი (სურათებით)
ვიდეო: შვიდი რობოტი შეცვლის სოფლის მეურნეობას ▶ ნახეთ ახლა! 2024, ივლისი
Anonim
ავტონომიური ბილიკის დაცვა მანქანა ჟოლოს Pi და OpenCV გამოყენებით
ავტონომიური ბილიკის დაცვა მანქანა ჟოლოს Pi და OpenCV გამოყენებით

ამ ინსტრუქციებში განხორციელდება ავტონომიური ზოლის შემნახველი რობოტი და გაივლის შემდეგ საფეხურებს:

  • ნაწილების შეგროვება
  • პროგრამული უზრუნველყოფის წინაპირობების დაყენება
  • აპარატურის შეკრება
  • პირველი ტესტი
  • ხაზის ხაზების გამოვლენა და სახელმძღვანელო ხაზის ჩვენება openCV გამოყენებით
  • PD კონტროლერის განხორციელება
  • შედეგები

ნაბიჯი 1: კომპონენტების შეგროვება

კომპონენტების შეგროვება
კომპონენტების შეგროვება
კომპონენტების შეგროვება
კომპონენტების შეგროვება
კომპონენტების შეგროვება
კომპონენტების შეგროვება
კომპონენტების შეგროვება
კომპონენტების შეგროვება

ზემოთ მოყვანილი სურათები ასახავს ამ პროექტში გამოყენებულ ყველა კომპონენტს:

  • RC მანქანა: მე მივიღე ჩემი ადგილობრივი მაღაზიიდან ჩემს ქვეყანაში. იგი აღჭურვილია 3 ძრავით (2 გასახსნელად და 1 საჭეზე). ამ მანქანის მთავარი მინუსი ის არის, რომ საჭე შემოიფარგლება "საჭე არ არის" და "სრული მართვა" შორის. სხვა სიტყვებით რომ ვთქვათ, მას არ შეუძლია მართოს კონკრეტული კუთხე, განსხვავებით servo-steering RC მანქანებისგან. თქვენ შეგიძლიათ იპოვოთ მსგავსი მანქანის ნაკრები, რომელიც სპეციალურად ჟოლოს პიისთვის არის შექმნილი.
  • ჟოლო pi 3 მოდელი b+: ეს არის მანქანის ტვინი, რომელიც გაუმკლავდება დამუშავების ბევრ სტადიას. იგი დაფუძნებულია ოთხ ბირთვიან 64-ბიტიან პროცესორზე, რომლის სიხშირეა 1.4 გჰც. ჩემი ავიღე აქედან.
  • Raspberry pi 5 mp კამერის მოდული: მას აქვს 1080p @ 30 fps, 720p @ 60 fps და 640x480p 60/90 ჩაწერის მხარდაჭერა. იგი ასევე მხარს უჭერს სერიულ ინტერფეისს, რომელიც შეიძლება ჩაერთოს პირდაპირ ჟოლოს პიში. ეს არ არის საუკეთესო ვარიანტი სურათების დამუშავების პროგრამებისთვის, მაგრამ ეს საკმარისია ამ პროექტისთვის, ასევე ძალიან იაფია. ჩემი ავიღე აქედან.
  • ძრავის მძღოლი: გამოიყენება DC ძრავების მიმართულებებისა და სიჩქარის გასაკონტროლებლად. ის მხარს უჭერს 2 დკ ძრავის კონტროლს 1 დაფაზე და უძლებს 1.5 ა.
  • Power Bank (სურვილისამებრ): მე გამოვიყენე დენის ბანკი (შეფასებულია 5V, 3A) ჟოლოს პი ცალკე გასაძლიერებლად. შემდგომი კონვერტორი (მამალი კონვერტორი: 3A გამომავალი დენი) უნდა იქნას გამოყენებული ჟოლოს პი 1 ენერგიის გასაძლიერებლად.
  • 3s (12 V) LiPo ბატარეა: ლითიუმის პოლიმერული ბატარეები ცნობილია რობოტიკის სფეროში მათი შესანიშნავი შესრულებით. იგი გამოიყენება ძრავის დრაივერისთვის. ჩემი შევიძინე აქედან.
  • მამაკაცი მამრობითი და მდედრობითი მდედრობითი jumper მავთულები.
  • ორმხრივი ლენტი: გამოიყენება RC მანქანაზე კომპონენტების დასამაგრებლად.
  • ლურჯი ლენტი: ეს არის ამ პროექტის ძალიან მნიშვნელოვანი კომპონენტი, იგი გამოიყენება ორი ხაზის ხაზის დასამზადებლად, რომლითაც მანქანა იმოძრავებს შორის. თქვენ შეგიძლიათ აირჩიოთ თქვენთვის სასურველი ნებისმიერი ფერი, მაგრამ მე გირჩევთ აირჩიოთ განსხვავებული ფერები, ვიდრე ირგვლივ არსებული გარემო.
  • სამაგრები და ხის ბარები.
  • ხრახნიანი მძღოლი.

ნაბიჯი 2: Raspberry Pi– ზე OpenCV– ის დაყენება და დისტანციური ჩვენების დაყენება

Raspberry Pi– ზე OpenCV– ის დაყენება და დისტანციური ჩვენების დაყენება
Raspberry Pi– ზე OpenCV– ის დაყენება და დისტანციური ჩვენების დაყენება

ეს ნაბიჯი ცოტა შემაშფოთებელია და გარკვეული დრო დასჭირდება.

OpenCV (ღია კოდის კომპიუტერული ხედვა) არის ღია კოდის კომპიუტერული ხედვისა და მანქანათმცოდნეობის პროგრამული ბიბლიოთეკა. ბიბლიოთეკას აქვს 2500 -ზე მეტი ოპტიმიზირებული ალგორითმი. მიჰყევით ამ ძალიან პირდაპირ სახელმძღვანელოს, რომ დააინსტალიროთ openCV თქვენს ჟოლოს პიზე, ასევე ჟოლოს pi OS– ს დაყენების მიზნით (თუ ეს ჯერ კიდევ არ გააკეთეთ). გთხოვთ გაითვალისწინოთ, რომ openCV- ს მშენებლობის პროცესი შეიძლება 1.5 საათს გაგრძელდეს კარგად გაციებულ ოთახში (რადგან პროცესორის ტემპერატურა ძალიან მაღალი იქნება!) ასე რომ დალიეთ ჩაი და მოთმინებით დაელოდეთ: D.

დისტანციური ეკრანისთვის ასევე მიჰყევით ამ სახელმძღვანელოს თქვენი ჟოლოს პიზე დისტანციური წვდომის დასაყენებლად თქვენი Windows/Mac მოწყობილობიდან.

ნაბიჯი 3: ნაწილების ერთმანეთთან დაკავშირება

ნაწილების ერთმანეთთან დაკავშირება
ნაწილების ერთმანეთთან დაკავშირება
ნაწილების ერთმანეთთან დაკავშირება
ნაწილების ერთმანეთთან დაკავშირება
ნაწილების ერთმანეთთან დაკავშირება
ნაწილების ერთმანეთთან დაკავშირება

ზემოთ მოყვანილი სურათები აჩვენებს კავშირებს ჟოლოს პი, კამერის მოდულსა და ძრავის დრაივერს შორის. გთხოვთ გაითვალისწინოთ, რომ ჩემს მიერ გამოყენებული ძრავები შთანთქავს 0.35 A- ს 9 V- ზე, რაც ძრავის მძღოლს უსაფრთხოდ აყენებს 3 ძრავის მუშაობას ერთდროულად. და რადგანაც მსურს აკონტროლოს 2 ძრავის სიჩქარე (1 უკანა და 1 წინა) ზუსტად იგივენაირად, მე დავუკავშირე ისინი იმავე პორტს. მე დავამატე ძრავის მძღოლი მანქანის მარჯვენა მხარეს ორმაგი ლენტით. რაც შეეხება კამერის მოდულს, მე ჩავამატე zip ჰალსტუხი ხრახნიან ხვრელებს შორის, როგორც ზემოთ გამოსახულია. შემდეგ, მე ვუყენებ კამერას ხის ზოლზე ისე, რომ შემიძლია შეცვალო კამერის პოზიცია, როგორც მინდა. ეცადეთ მაქსიმალურად დააინსტალიროთ კამერა შუა მანქანაში. მე გირჩევთ კამერა მოათავსოთ მიწიდან მინიმუმ 20 სმ სიმაღლეზე, ასე რომ მანქანის წინ ხედვის ველი უკეთესდება. Fritzing სქემა მოცემულია ქვემოთ.

ნაბიჯი 4: პირველი ტესტი

პირველი ტესტი
პირველი ტესტი
პირველი ტესტი
პირველი ტესტი

კამერის ტესტირება:

მას შემდეგ რაც კამერა დამონტაჟდება და შეიქმნება openCV ბიბლიოთეკა, დროა შევამოწმოთ ჩვენი პირველი სურათი! ჩვენ გადავიღებთ ფოტოს pi cam- დან და შევინახავთ როგორც "original.jpg". ეს შეიძლება გაკეთდეს 2 გზით:

1. ტერმინალური ბრძანებების გამოყენება:

გახსენით ახალი ტერმინალის ფანჯარა და ჩაწერეთ შემდეგი ბრძანება:

raspistill -o original.jpg

ეს გადაიღებს კადრს და შეინახავს "/pi/original.jpg" დირექტორიაში.

2. ნებისმიერი პითონის IDE გამოყენებით (მე ვიყენებ IDLE):

გახსენით ესკიზი და ჩაწერეთ შემდეგი კოდი:

იმპორტი cv2

ვიდეო = cv2. VideoCapture (0) ხოლო 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 რაც ნიშნავს რასპის კამერას. თუ თქვენ გაქვთ რამოდენიმე კამერა, სხვადასხვა ნომერი უნდა იყოს განთავსებული. video.read () წაიკითხავს თითოეული ჩარჩო კამერიდან და შეინახავს მას ცვლადში სახელწოდებით "frame". flip () ფუნქცია გადაატრიალებს გამოსახულებას y ღერძთან მიმართებით (ვერტიკალურად), ვინაიდან მე კამერას უკანა მხარეს ვაყენებ. imshow () გამოჩნდება ჩვენი ჩარჩოები სათაურით სიტყვა "ორიგინალი" და imwrite () შეინახავს ჩვენს ფოტოს ორიგინალში. jpg. waitKey (1) დაელოდება 1 ms სანამ რომელიმე კლავიატურის ღილაკს დააჭერს და აბრუნებს თავის ASCII კოდს. თუ გაქცევის (esc) ღილაკს დააჭერთ, ათწილადის მნიშვნელობა 27 ბრუნდება და შესაბამისად გაწყვეტს მარყუჟს. video.release () შეწყვეტს ჩაწერას და განადგურებაAllWindows () დახურავს imshow () ფუნქციით გახსნილ ყველა სურათს.

მე გირჩევთ შეამოწმოთ თქვენი ფოტო მეორე მეთოდით, რათა გაეცნოთ openCV ფუნქციებს. სურათი ინახება "/pi/original.jpg" დირექტორიაში. ჩემი ფოტოაპარატი გადაღებული ორიგინალური ფოტო ნაჩვენებია ზემოთ.

ტესტირება ძრავები:

ეს ნაბიჯი აუცილებელია თითოეული ძრავის ბრუნვის მიმართულების დასადგენად. პირველ რიგში, მოდით მოკლედ შემოვიღოთ ძრავის მძღოლის მუშაობის პრინციპი. ზემოთ მოყვანილი სურათი გვიჩვენებს ძრავის მძღოლის ამობრუნებას. ჩართეთ A, Input 1 და Input 2 დაკავშირებულია A ძრავის კონტროლთან. B ჩართვა, შეყვანა 3 და შეყვანა 4 დაკავშირებულია B ძრავის კონტროლთან. მიმართულების კონტროლი დადგენილია "შეყვანის" ნაწილით და სიჩქარის კონტროლი დადგენილია "ჩართვის" ნაწილის მიერ. მაგალითად, ძრავის A მიმართულების გასაკონტროლებლად, დააყენეთ შეყვანის 1 მაღალი (3.3 V ამ შემთხვევაში, ვინაიდან ჩვენ ვიყენებთ ჟოლოს pi) და დააყენეთ შემავალი 2 LOW, ძრავა დატრიალდება კონკრეტული მიმართულებით და საპირისპირო მნიშვნელობების დაყენებით 1 და 2 შეყვანისას ძრავა ბრუნავს საპირისპირო მიმართულებით. თუ შეყვანა 1 = შეყვანა 2 = (მაღალი ან დაბალი), ძრავა არ ბრუნავს. ქინძისთავების ჩართვა აიღეთ პულსის სიგანის მოდულაციის (PWM) შეყვანის სიგნალი ჟოლოდან (0 -დან 3.3 ვ -მდე) და გაუშვით ძრავები შესაბამისად. მაგალითად, 100% PWM სიგნალი ნიშნავს, რომ ჩვენ ვმუშაობთ მაქსიმალურ სიჩქარეზე და 0% PWM სიგნალი ნიშნავს, რომ ძრავა არ ბრუნავს. შემდეგი კოდი გამოიყენება ძრავების მიმართულებების დასადგენად და მათი სიჩქარის შესამოწმებლად.

იმპორტის დრო

იმპორტი RPi. GPIO როგორც GPIO GPIO.setwarnings (false) # საჭე 23 # ფიზიკური პინ 16 in4 = 24 # ფიზიკური Pin 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) # საჭის მართვის მართვის GPIO.output (in1, GPIO HIGH) GPIO.output (in2, GPIO. LOW) საჭე = GPIO. PWM (steering_enable, 1000) # დააყენეთ გადართვის სიხშირე 1000 Hz მართვაზე. შეჩერება () # Throttle Motors Control GPIO.output (in3, GPIO. HIGH) GPIO.გამომავალი (in4, GPIO. LOW) throttle = GPIO. PWM (throttle_enable, 1000) # დააყენეთ გადართვის სიხშირე 1000 Hz throttle.stop () time.sleep (1) throttle.start (25) # იწყებს ძრავას 25 -ზე % PWM სიგნალი-> (0.25 * ბატარეის ძაბვა) - მძღოლის საჭის დაკარგვა. დაწყება (100) # იწყებს ძრავას 100% PWM სიგნალით -> (1 * ბატარეის ძაბვა) - მძღოლის დაკარგვის დრო. ძილი (3) ბარიერი. გაჩერება () საჭე. შეჩერება ()

ეს კოდი ამოძრავებს დამრტყმელ ძრავებს და საჭის ძრავას 3 წამის განმავლობაში და შემდეგ გააჩერებს მათ. (მძღოლის დაკარგვა) შეიძლება განისაზღვროს ვოლტმეტრის გამოყენებით. მაგალითად, ჩვენ ვიცით, რომ 100% PWM სიგნალმა უნდა მისცეს ბატარეის სრული ძაბვა ძრავის ტერმინალში. მაგრამ, PWM– ის 100%–ზე დაყენებით, აღმოვაჩინე, რომ მძღოლი იწვევს 3 V ვარდნას და ძრავა იღებს 9 V– ს 12 V– ის ნაცვლად (ზუსტად ის, რაც მე მჭირდება!). ზარალი არ არის წრფივი ანუ ზარალი 100% ძალიან განსხვავდება ზარალისგან 25%. ზემოაღნიშნული კოდის გაშვების შემდეგ, ჩემი შედეგები ასეთი იყო:

ჩაძირვის შედეგები: თუ in3 = HIGH და in4 = LOW, ჩამქრალ ძრავებს ექნებათ საათი-ბრძენი (CW) ბრუნვა, ანუ მანქანა წინ მიიწევს. წინააღმდეგ შემთხვევაში, მანქანა უკან დაიხევს.

მართვის შედეგები: თუ in1 = HIGH და in2 = LOW, საჭის ძრავა მოუხვევს მაქსიმალურად მარცხნივ, ანუ მანქანა მიემართება მარცხნივ. წინააღმდეგ შემთხვევაში, მანქანა მართავს მარჯვნივ. გარკვეული ექსპერიმენტების შემდეგ, აღმოვაჩინე, რომ საჭის ძრავა არ ბრუნდება, თუ PWM სიგნალი არ არის 100% (ანუ ძრავა იმოძრავებს ან მთლიანად მარჯვნივ, ან მთლიანად მარცხნივ).

ნაბიჯი 5: ხაზის ხაზების გამოვლენა და სათაურის ხაზის გამოთვლა

ხაზის ხაზების გამოვლენა და სათაურის ხაზის გამოთვლა
ხაზის ხაზების გამოვლენა და სათაურის ხაზის გამოთვლა
ხაზის ხაზების გამოვლენა და სათაურის ხაზის გამოთვლა
ხაზის ხაზების გამოვლენა და სათაურის ხაზის გამოთვლა
ხაზის ხაზების გამოვლენა და სათაურის ხაზის გამოთვლა
ხაზის ხაზების გამოვლენა და სათაურის ხაზის გამოთვლა

ამ ნაბიჯში განმარტებულია ალგორითმი, რომელიც გააკონტროლებს მანქანის მოძრაობას. პირველი სურათი აჩვენებს მთელ პროცესს. სისტემის შეყვანა არის სურათები, გამომავალი არის თეტა (მართვის კუთხე გრადუსებში). გაითვალისწინეთ, რომ დამუშავება ხდება 1 სურათზე და განმეორდება ყველა ჩარჩოზე.

კამერა:

კამერა დაიწყებს ვიდეოს ჩაწერას (320 x 240) გარჩევადობით. მე გირჩევთ შეამციროთ რეზოლუცია, რათა მიიღოთ უკეთესი კადრების სიხშირე (fps), რადგან fps ვარდნა მოხდება თითოეულ ჩარჩოზე დამუშავების ტექნიკის გამოყენების შემდეგ. ქვემოთ მოყვანილი კოდი იქნება პროგრამის მთავარი მარყუჟი და დაამატებს თითოეულ საფეხურს ამ კოდს.

იმპორტი cv2

იმპორტის numpy როგორც np ვიდეო = cv2. VideoCapture (0) video.set (cv2. CAP_PROP_FRAME_WIDTH, 320) # დააყენეთ სიგანე 320 p ვიდეოზე. set (cv2. CAP_PROP_FRAME_HEIGHT, 240) # დააყენეთ სიმაღლე 240 p # მარყუჟამდე მართალია: ret, frame = video.read () frame = cv2.flip (frame, -1) cv2.imshow ("ორიგინალური", ჩარჩო) გასაღები = cv2.wait გასაღები (1) თუ გასაღები == 27: ვიდეო შესვენება. გამოშვება () cv2.destroyAllWindows ()

აქ კოდი აჩვენებს მე –4 საფეხურზე მიღებულ თავდაპირველ სურათს და ნაჩვენებია ზემოთ მოცემულ სურათებში.

გადაიყვანეთ HSV ფერის სივრცეში:

ახლა კამერის ჩარჩოებად ვიდეოჩანაწერის გადაღების შემდეგ, შემდეგი ნაბიჯი არის თითოეული კადრის გადაქცევა ფერში, Hue, Saturation და Value (HSV) ფერში. ამის მთავარი უპირატესობა ისაა, რომ შეძლოთ ფერების გამიჯვნა მათი სიკაშკაშის დონით. და აქ არის კარგი ახსნა HSV ფერის სივრცის შესახებ. HSV– ზე გადაყვანა ხდება შემდეგი ფუნქციის საშუალებით:

def convert_to_HSV (ჩარჩო):

hsv = cv2.cvtColor (frame, cv2. COLOR_BGR2HSV) cv2.imshow ("HSV", hsv) hsv დაბრუნება

ეს ფუნქცია გამოიძახება ძირითადი მარყუჟიდან და დააბრუნებს ჩარჩოს HSV ფერის სივრცეში. HSV ფერის სივრცეში ჩემ მიერ მოპოვებული ჩარჩო ნაჩვენებია ზემოთ.

აღმოაჩინეთ ლურჯი ფერი და კიდეები:

სურათის HSV ფერის სივრცეში გადაყვანის შემდეგ, დროა გამოვავლინოთ მხოლოდ ის ფერი, რომელიც გვაინტერესებს (ანუ ლურჯი ფერი, ვინაიდან ეს არის ზოლის ხაზების ფერი). HSV ჩარჩოდან ლურჯი ფერის ამოსაღებად, უნდა იყოს მითითებული შეფერილობის, გაჯერების და მნიშვნელობის დიაპაზონი. მიმართეთ აქ, რომ გქონდეთ უკეთესი წარმოდგენა HSV ღირებულებებზე. გარკვეული ექსპერიმენტების შემდეგ, ლურჯი ფერის ზედა და ქვედა საზღვრები ნაჩვენებია ქვემოთ მოცემულ კოდში. თითოეულ ჩარჩოში საერთო დამახინჯების შესამცირებლად, კიდეები გამოვლენილია მხოლოდ მბზინავი დეტექტორის გამოყენებით. დაწვრილებით canny edge არის აქ ნაპოვნი. ცერის წესია კანინის () ფუნქციის პარამეტრების შერჩევა 1: 2 ან 1: 3 თანაფარდობით.

def dete_edges (ჩარჩო):

ქვედა_ ლურჯი = np.array ([90, 120, 0], dtype = "uint8") # ქვედა ზღვარი ლურჯი ფერის ზედა_ ლურჯი = np.array ([150, 255, 255], dtype = "uint8") # ზედა ზღვარი ლურჯი ფერის ნიღაბი = cv2.inRange (hsv, ქვედა_ ლურჯი, ზედა_ ლურჯი) # ეს ნიღაბი გაფილტროს ყველაფერს გარდა ლურჯისა # აღმოაჩენს კიდეებს კიდეები = cv2. Canny (ნიღაბი, 50, 100) cv2.imshow ("კიდეები", კიდეები) კიდეების დაბრუნება

ეს ფუნქცია ასევე გამოიძახება ძირითადი მარყუჟისგან, რომელიც პარამეტრებად იღებს HSV ფერის სივრცის ჩარჩოს და აბრუნებს კიდეებით ჩარჩოს. Edged ჩარჩო მე მივიღე არის ნაპოვნი ზემოთ.

აირჩიეთ ინტერესის რეგიონი (ROI):

ინტერესის რეგიონის არჩევა გადამწყვეტი მნიშვნელობა აქვს მხოლოდ ჩარჩოს 1 რეგიონზე ფოკუსირებისთვის. ამ შემთხვევაში, მე არ მინდა მანქანამ დაინახოს ბევრი ნივთი გარემოში. მე უბრალოდ მინდა, რომ მანქანა ფოკუსირდეს ხაზის ხაზებზე და იგნორირება მოახდინოს სხვაზე. P. S: საკოორდინატო სისტემა (x და y ღერძი) იწყება ზედა მარცხენა კუთხიდან. სხვა სიტყვებით რომ ვთქვათ, წერტილი (0, 0) იწყება ზედა მარცხენა კუთხიდან. y ღერძი არის სიმაღლე და x ღერძი არის სიგანე. ქვემოთ მოყვანილი კოდი ირჩევს ინტერესის სფეროს, რომ ფოკუსირდეს მხოლოდ ჩარჩოს ქვედა ნახევარზე.

def რეგიონის_ინტერესტი (კიდეები):

სიმაღლე, სიგანე = კიდეები. ფორმა # ამოიღეთ კიდეების სიმაღლე და სიგანე ჩარჩოს ნიღაბი = np.zeros_like (კიდეები) # გააკეთეთ ცარიელი მატრიცა კიდეების ჩარჩოს იგივე ზომებით # მხოლოდ ეკრანის ქვედა ნახევრის ფოკუსირება # მიუთითეთ კოორდინატები 4 ქულა (ქვედა მარცხენა, ზედა მარცხენა, ზედა მარჯვენა, ქვედა მარჯვენა) პოლიგონი = np. მასივი

ეს ფუნქცია მიიღებს edged ჩარჩოს, როგორც პარამეტრს და ხატავს პოლიგონს 4 წინასწარ განსაზღვრული წერტილით. ის მხოლოდ ფოკუსირდება პოლიგონის შიგნით არსებულზე და იგნორირებას უკეთებს ყველაფერს მის გარეთ. ჩემი ინტერესის სფერო ჩარჩო ნაჩვენებია ზემოთ.

ხაზის სეგმენტების ამოცნობა:

უხეში გარდაქმნა გამოიყენება ხაზის სეგმენტების ამოსაღებად ჩარჩოდან. მიუხედავად იმისა, რომ ტრანსფორმაცია არის ტექნიკა ნებისმიერი ფორმის გამოსათვლელად მათემატიკური ფორმით. მას შეუძლია აღმოაჩინოს თითქმის ნებისმიერი ობიექტი, თუნდაც მისი დამახინჯებული ხმების გარკვეული რაოდენობის მიხედვით. აქ არის ნაჩვენები Hough transform- ის დიდი მითითება. ამ პროგრამისთვის, cv2. HoughLinesP () ფუნქცია გამოიყენება თითოეული ჩარჩოში ხაზების გამოვლენის მიზნით. ამ პარამეტრის მნიშვნელოვანი პარამეტრები არის:

cv2. HoughLinesP (ჩარჩო, rho, theta, min_threshold, minLineLength, maxLineGap)

  • ჩარჩო: არის ჩარჩო, რომელშიც ჩვენ გვინდა ხაზების გამოვლენა.
  • rho: ეს არის მანძილის სიზუსტე პიქსელებში (ჩვეულებრივ ეს არის = 1)
  • თეტა: კუთხის სიზუსტე რადიანებში (ყოველთვის = np.pi/180 ~ 1 ხარისხი)
  • min_threshold: მინიმალური ხმა, რომელიც მან უნდა მიიღოს იმისათვის, რომ იგი განიხილებოდეს როგორც ხაზი
  • minLineLength: ხაზის მინიმალური სიგრძე პიქსელებში. ამ რიცხვზე უფრო მოკლე ხაზი არ ითვლება ხაზად.
  • maxLineGap: პიქსელებში მაქსიმალური უფსკრული 2 ხაზს შორის, რომელიც განიხილება როგორც 1 ხაზი. (ის ჩემს შემთხვევაში არ გამოიყენება, ვინაიდან იმ ხაზის ხაზებს, რომელსაც მე ვიყენებ, არანაირი ხარვეზი არ აქვს).

ეს ფუნქცია აბრუნებს ხაზის ბოლო წერტილებს. შემდეგი ფუნქცია იძახება ჩემი ძირითადი მარყუჟიდან Hough ტრანსფორმაციის გამოყენებით ხაზების გამოსავლენად:

def dete_line_segments (cropped_edges):

rho = 1 theta = np.pi / 180 min_threshold = 10 line_segments = 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- ის ტრანსფორმაციისას გამოვლენილ სეგმენტებს და აბრუნებს ორი ხაზის საშუალო ფერდობსა და გადაკვეთას.

def average_slope_intercept (frame, line_segments):

lane_lines = თუ ხაზის_სეგმენტები არ არის: ბეჭდვა ("ხაზის სეგმენტი არ არის გამოვლენილი") დაბრუნება lane_lines სიმაღლე, სიგანე, _ = ჩარჩო. ფორმა left_fit = right_fit = border = left_region_boundary = width * (1 - border) right_region_boundary = სიგანე * საზღვარი ხაზის_სეგმენტისთვის ხაზის_სეგმენტებში: x1, y1, x2, y2 ხაზის_სეგმენტში: თუ x1 == x2: ბეჭდვა ("ვერტიკალური ხაზების გამოტოვება (ფერდობზე = უსასრულობა)") გააგრძელეთ მორგება = np.polyfit ((x1, x2), (y1, y2), 1) ფერდობზე = (y2 - y1) / (x2 - x1) ჩაჭრა = y1 - (ფერდობზე * x1) თუ დახრილობა <0: თუ x1 <მარცხენა_რეგიონი_საზღვარი და x2 მარჯვენა_რეგიონის_საზღვარი და x2> მარჯვენა_რეგიონის_საზღვარი: right_fit. დამატება ((ფერდობზე, ჩაჭრა)) left_fit_average = np. საშუალო (left_fit, axis = 0) თუ len (left_fit)> 0: lane_lines.append (make_points (frame, left_fit_average)) right_fit_average = np.average (right_fit, axis = 0) თუ len (right_fit)> 0: lane_lines.append (make_points (frame, right_fit_average)) # lane_lines არის 2-D მასივი, რომელიც შედგება მარჯვენა და მარცხენა ხაზის კოორდინატები # მაგალითად: lan e_lines =

make_points () არის დამხმარე ფუნქცია average_slope_intercept () ფუნქციისთვის, რომელიც დააბრუნებს ზოლის ხაზების შეზღუდულ კოორდინატებს (ქვემოდან ჩარჩოს შუაკენ).

def make_points (ჩარჩო, ხაზი):

სიმაღლე, სიგანე, _ = ჩარჩო. ფორმის დახრილობა, შეკვეთა = ხაზი y1 = სიმაღლე # ჩარჩოს ქვედა ნაწილი y2 = int (y1 / 2) # გააკეთეთ წერტილები ჩარჩოს შუიდან ქვემოთ, თუ დახრილობა == 0: ფერდობი = 0.1 x1 = int ((y1 - ჩაჭრა) / ფერდობზე) x2 = int ((y2 - ჩაჭრა) / ფერდობზე) დაბრუნება

0 -ზე გაყოფის თავიდან ასაცილებლად, წარმოდგენილია პირობა. თუ ფერდობი = 0 რაც ნიშნავს y1 = y2 (ჰორიზონტალური ხაზი), მიეცით ფერდობზე მნიშვნელობა 0. ეს არ იმოქმედებს ალგორითმის მუშაობაზე, ასევე ხელს შეუშლის შეუძლებელ შემთხვევას (0 -ზე გაყოფას).

ჩარჩოებზე ხაზის ხაზების გამოსახატავად გამოიყენება შემდეგი ფუნქცია:

def display_lines (frame, 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 ზოლის ხაზების მასივიდან _, _, right_x2, _ = lane_lines [1] [0] # ამონაწერი მარჯვენა x2 ზოლის ხაზებიდან მასივი mid = int (width / 2) x_offset = (left_x2 + right_x2) / 2 - mid y_offset = int (სიმაღლე / 2) elif len (lane_lines) == 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) კუთხე_მდე_მიდ_რადიანი = მათემატიკა. ატანი (x_offset / y_offset) angle_to_mid_deg = int (angle_to_mid_radian * 180.0 / math.pi) steering_angle = angle_to_mid_deg + 90 steering

x_offset პირველ შემთხვევაში არის რამდენად განსხვავდება საშუალო ((მარჯვნივ x2 + მარცხენა x2) / 2) ეკრანის შუაგულისგან. y_offset ყოველთვის ითვლება სიმაღლე / 2. ბოლო სურათი ზემოთ აჩვენებს სათაურის ხაზის მაგალითს. angle_to_mid_radians არის იგივე "თეტა" ნაჩვენები ბოლო სურათზე ზემოთ. თუ steering_angle = 90, ეს ნიშნავს, რომ მანქანას აქვს "სიმაღლის / 2" ხაზის პერპენდიკულარულად მიმავალი ხაზი და მანქანა წინ მიიწევს საჭის გარეშე. თუ steering_angle> 90, მანქანა უნდა მიემართოს მარჯვნივ, წინააღმდეგ შემთხვევაში უნდა იაროს მარცხნივ. სათაურის ხაზის საჩვენებლად გამოიყენება შემდეგი ფუნქცია:

def display_heading_line (frame, steering_angle, line_color = (0, 0, 255), line_width = 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 (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 ვიდეო = 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 კონტროლის გამოყენება

PD კონტროლის გამოყენება
PD კონტროლის გამოყენება

ახლა ჩვენ გვაქვს საჭის მართვის კუთხე ძრავებისათვის. როგორც უკვე აღვნიშნეთ, თუ საჭის კუთხე 90 -ზე მეტია, მანქანა მარჯვნივ უნდა მოუხვიოს, წინააღმდეგ შემთხვევაში მარცხნივ. მე გამოვიყენე მარტივი კოდი, რომელიც მართავს ძრავას მარჯვნივ, თუ კუთხე 90 -ზე მეტია და უხვევს მარცხნივ, თუ მართვის კუთხე 90 -ზე ნაკლებია მუდმივი დარტყმის სიჩქარით (10% PWM), მაგრამ მე მივიღე ბევრი შეცდომა. მთავარი შეცდომა, რაც მე მივიღე არის ის, როდესაც მანქანა უახლოვდება ნებისმიერ შემობრუნებას, საჭის ძრავა მოქმედებს პირდაპირ, მაგრამ დამრტყმელი ძრავები იჭედება. ვცდილობდი გამეზარდა სიჩქარე (20% PWM) მორიგეობით, მაგრამ დამთავრდა რობოტი ბილიკებიდან გასვლით. მე მჭირდებოდა ისეთი რამ, რაც მნიშვნელოვნად გაზრდის დარტყმის სიჩქარეს, თუ საჭის კუთხე ძალიან დიდია და ოდნავ გაზრდის სიჩქარეს, თუ საჭის კუთხე არც თუ ისე დიდია, შემდეგ სიჩქარეს ამცირებს საწყის მნიშვნელობამდე, როდესაც მანქანა უახლოვდება 90 გრადუსს (მოძრაობს პირდაპირ). გამოსავალი იყო PD კონტროლერის გამოყენება.

PID კონტროლერი ნიშნავს პროპორციულ, ინტეგრალურ და წარმოებულ კონტროლერს. ამ ტიპის ხაზოვანი კონტროლერები ფართოდ გამოიყენება რობოტიკის პროგრამებში. ზემოთ მოყვანილი სურათი გვიჩვენებს ტიპიური PID უკუკავშირის კონტროლის მარყუჟს. ამ კონტროლერის მიზანია მიაღწიოს "მითითებულ წერტილს" ყველაზე ეფექტური გზით, განსხვავებით "ჩართული - გამორთული" კონტროლერებისგან, რომლებიც ჩართავს ან გამორთავს ქარხანას გარკვეული პირობების შესაბამისად. ზოგიერთი საკვანძო სიტყვა უნდა იყოს ცნობილი:

  • Setpoint: არის სასურველი მნიშვნელობა, რომლის მიღწევაც გსურთ თქვენი სისტემისთვის.
  • ფაქტობრივი მნიშვნელობა: არის ფაქტობრივი მნიშვნელობა, რომელიც იგრძნობა სენსორის მიერ.
  • შეცდომა: არის განსხვავება მითითებულსა და რეალურ მნიშვნელობას შორის (შეცდომა = მითითებული წერტილი - ფაქტობრივი მნიშვნელობა).
  • კონტროლირებადი ცვლადი: მისი სახელიდან, ცვლადი, რომლის კონტროლიც გსურთ.
  • Kp: პროპორციული მუდმივა.
  • კი: ინტეგრალური მუდმივა.
  • Kd: წარმოებული მუდმივა.

მოკლედ, PID კონტროლის სისტემის მარყუჟი მუშაობს შემდეგნაირად:

  • მომხმარებელი განსაზღვრავს სისტემის მიღწევისათვის საჭირო მითითებულ წერტილს.
  • შეცდომა გამოითვლება (შეცდომა = მითითებული წერტილი - ფაქტობრივი).
  • P კონტროლერი ქმნის ქმედებას პროპორციული შეცდომის მნიშვნელობასთან. (შეცდომა იზრდება, P მოქმედებაც იზრდება)
  • I კონტროლერი დროთა განმავლობაში შეცვლის შეცდომას, რაც გამორიცხავს სისტემის სტაბილური მდგომარეობის შეცდომას, მაგრამ ზრდის მის გადაჭარბებას.
  • D კონტროლერი უბრალოდ არის დროის წარმოებული შეცდომისთვის. სხვა სიტყვებით რომ ვთქვათ, ეს არის შეცდომის დახრილობა. ის აკეთებს შეცდომის წარმოებულის პროპორციულ მოქმედებას. ეს კონტროლერი ზრდის სისტემის სტაბილურობას.
  • კონტროლერის გამომუშავება იქნება სამი კონტროლერის ჯამი. კონტროლერის გამომავალი იქნება 0 თუ შეცდომა ხდება 0.

PID კონტროლერის შესანიშნავი ახსნა შეგიძლიათ იხილოთ აქ.

დავბრუნდი მანქანის შესანახად, ჩემი კონტროლირებადი ცვლა იყო სიჩქარის შემცირება (რადგან საჭეს აქვს მხოლოდ ორი მდგომარეობა მარჯვნივ ან მარცხნივ). ამ მიზნით გამოიყენება PD კონტროლერი, ვინაიდან D მოქმედება ზრდის დარტყმის სიჩქარეს ძალიან, თუ შეცდომის ცვლილება ძალიან დიდია (ანუ დიდი გადახრა) და ანელებს მანქანას, თუ ეს შეცდომა შეცვლის 0 -ს. მე გავაკეთე შემდეგი ნაბიჯები PD- ის განსახორციელებლად კონტროლერი:

  • დააყენეთ მითითებული წერტილი 90 გრადუსზე (მე ყოველთვის მინდა მანქანა პირდაპირ მოძრაობდეს)
  • გამოითვლება გადახრის კუთხე შუიდან
  • გადახრა იძლევა ორ ინფორმაციას: რამდენად დიდია შეცდომა (გადახრის სიდიდე) და რა მიმართულება უნდა მიიღოს საჭის ძრავამ (გადახრის ნიშანი). თუ გადახრა დადებითია, მანქანამ უნდა იმოძრაოს მარჯვნივ, თორემ მარცხნივ.
  • ვინაიდან გადახრა არის ან უარყოფითი ან დადებითი, "შეცდომის" ცვლადი განისაზღვრება და ყოველთვის ტოლია გადახრის აბსოლუტურ მნიშვნელობას.
  • შეცდომა გამრავლებულია მუდმივი Kp.
  • შეცდომა განიცდის დროის დიფერენციაციას და მრავლდება მუდმივი Kd.
  • მოტორსის სიჩქარე განახლებულია და მარყუჟი კვლავ იწყება.

შემდეგი კოდი გამოიყენება მთავარ მარყუჟში, რომ გააკონტროლოს ძრავის სიჩქარე:

სიჩქარე = 10 # მუშაობის სიჩქარე % PWM- ში

# ცვლადი განახლებადი თითოეული მარყუჟის lastTime = 0 lastError = 0 # PD მუდმივები Kp = 0.4 Kd = Kp * 0.65 მიუხედავად იმისა, რომ მართალია: ახლა = დრო. დრო () # მიმდინარე დროის ცვლადი dt = ახლა - lastTime გადახრა = მართვის_კუთხედი - 90 # ექვივალენტი to angle_to_mid_deg ცვლადი შეცდომა = abs (გადახრა) თუ გადახრა -5: # არ იმოძრაოთ თუ არის 10 გრადუსიანი შეცდომის დიაპაზონი გადახრა = 0 შეცდომა = 0 GPIO.output (in1, GPIO. LOW) GPIO.output (in2, GPIO. LOW) steering.stop () elif deviation> 5: # გადაუხვიეთ მარჯვნივ, თუ გადახრა დადებითია -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 throttle.start (spd) lastError = error lastTime = time.time ()

თუ შეცდომა ძალიან დიდია (გადახრა შუიდან მაღალია), პროპორციული და წარმოებული მოქმედებები მაღალია, რაც იწვევს დარტყმის მაღალ სიჩქარეს. როდესაც შეცდომა უახლოვდება 0 -ს (გადახრა საშუალოდან დაბალია), წარმოებული მოქმედება მოქმედებს პირიქით (დახრილობა უარყოფითია) და დარტყმის სიჩქარე მცირდება სისტემის სტაბილურობის შესანარჩუნებლად. სრული კოდი მოცემულია ქვემოთ.

ნაბიჯი 7: შედეგები

ზემოთ მოყვანილი ვიდეოები აჩვენებს ჩემს მიერ მიღებულ შედეგებს. მას სჭირდება მეტი მორგება და შემდგომი კორექტირება. მე ჟოლოს პი ვუკავშირებდი ჩემს LCD ეკრანს, რადგან ჩემს ქსელში გადაცემულ ვიდეოს ჰქონდა მაღალი შეფერხება და ძალიან იმედგაცრუებული იყო მუშაობა, ამიტომაც არის ვიდეოში ჟოლოს პითან დაკავშირებული მავთულები. მე გამოვიყენე ქაფის დაფები სიმღერის დასაწერად.

მე ველოდები თქვენს რეკომენდაციებს ამ პროექტის გასაუმჯობესებლად! ვიმედოვნებ, რომ ეს ინსტრუქციები იყო საკმარისი იმისათვის, რომ მოგაწოდოთ ახალი ინფორმაცია.

გირჩევთ: