Πίνακας περιεχομένων:

Ταξινόμηση ρομποτικής χάντρας: 3 βήματα (με εικόνες)
Ταξινόμηση ρομποτικής χάντρας: 3 βήματα (με εικόνες)

Βίντεο: Ταξινόμηση ρομποτικής χάντρας: 3 βήματα (με εικόνες)

Βίντεο: Ταξινόμηση ρομποτικής χάντρας: 3 βήματα (με εικόνες)
Βίντεο: Ρομποτικά συστήματα στην παραγωγή κομπόστας | 08/02/2021 | ΕΡΤ 2024, Νοέμβριος
Anonim
Image
Image
Ταξινόμηση ρομποτικής χάντρας
Ταξινόμηση ρομποτικής χάντρας
Ρομποτική ταξινόμηση χαντρών
Ρομποτική ταξινόμηση χαντρών
Ρομποτική ταξινόμηση χαντρών
Ρομποτική ταξινόμηση χαντρών

Σε αυτό το έργο, θα χτίσουμε ένα ρομπότ για να ταξινομήσει τις χάντρες Perler κατά χρώμα.

Πάντα ήθελα να φτιάξω ένα ρομπότ διαλογής χρωμάτων, οπότε όταν η κόρη μου ενδιαφέρθηκε για το χειροποίητο σφαιρίδιο Perler, το είδα ως μια τέλεια ευκαιρία.

Οι χάντρες Perler χρησιμοποιούνται για τη δημιουργία συντηγμένων έργων τέχνης τοποθετώντας πολλές χάντρες σε μια σανίδα και στη συνέχεια τις λιώνουν μαζί με ένα σίδερο. Συνήθως αγοράζετε αυτές τις χάντρες σε γιγαντιαίες συσκευασίες 22.000 χάντρες και περνάτε πολύ χρόνο αναζητώντας το χρώμα που θέλετε, οπότε σκέφτηκα ότι η ταξινόμησή τους θα αυξήσει την αποδοτικότητα της τέχνης.

Δουλεύω για την Phidgets Inc., οπότε χρησιμοποίησα κυρίως Phidgets για αυτό το έργο - αλλά αυτό θα μπορούσε να γίνει χρησιμοποιώντας οποιοδήποτε κατάλληλο υλικό.

Βήμα 1: Υλικό

Ιδού τι χρησιμοποίησα για να το φτιάξω. Το έφτιαξα 100% με εξαρτήματα από το phidgets.com και πράγματα που είχα στο σπίτι.

Πίνακες Phidgets, Motors, Hardware

  • HUB0000 - VINT Hub Phidget
  • 1108 - Μαγνητικός αισθητήρας
  • 2x STC1001 - 2.5A Stepper Phidget
  • 2x 3324 - 42STH38 NEMA -17 Διπολικό Stepper χωρίς κιβώτιο ταχυτήτων
  • 3x 3002 - Καλώδιο Phidget 60εκ
  • 3403 - Διανομέας 4 θυρών USB2.0
  • 3031 - Γυναίκα Pigtail 5,5x2,1mm
  • 3029 - 2 σύρματα 100 'Twisted Cable
  • 3604 - Λευκό LED 10mm (τσάντα 10)
  • 3402 - Κάμερα Web USB

Αλλα μέρη

  • Τροφοδοτικό 24VDC 2.0A
  • Αποκόψτε ξύλο και μέταλλο από το γκαράζ
  • Φερμουάρ
  • Πλαστικό δοχείο με κομμένο το κάτω μέρος

Βήμα 2: Σχεδιάστε το ρομπότ

Σχεδιάστε το ρομπότ
Σχεδιάστε το ρομπότ
Σχεδιάστε το ρομπότ
Σχεδιάστε το ρομπότ
Σχεδιάστε το ρομπότ
Σχεδιάστε το ρομπότ

Πρέπει να σχεδιάσουμε κάτι που μπορεί να πάρει ένα μόνο σφαιρίδιο από τη χοάνη εισόδου, να το τοποθετήσει κάτω από την κάμερα και μετά να το μετακινήσουμε στον κατάλληλο κάδο.

Παραλαβή από χάντρα

Αποφάσισα να κάνω το 1ο μέρος με 2 κομμάτια στρογγυλό κόντρα πλακέ, το καθένα με μια τρύπα τρυπημένη στο ίδιο μέρος. Το κάτω κομμάτι είναι σταθερό και το επάνω κομμάτι είναι στερεωμένο σε ένα βηματικό μοτέρ, το οποίο μπορεί να το περιστρέψει κάτω από μια χοάνη γεμάτη με χάντρες. Όταν η τρύπα ταξιδεύει κάτω από τη χοάνη, παίρνει ένα μόνο σφαιρίδιο. Στη συνέχεια, μπορώ να το περιστρέψω κάτω από την κάμερα web και στη συνέχεια να περιστρέψω περαιτέρω μέχρι να ταιριάξει με την τρύπα στο κάτω κομμάτι, οπότε και πέφτει.

Σε αυτήν την εικόνα, δοκιμάζω ότι το σύστημα μπορεί να λειτουργήσει. Όλα είναι σταθερά εκτός από το επάνω στρογγυλό κομμάτι κόντρα πλακέ, το οποίο είναι προσαρτημένο σε ένα βηματικό μοτέρ χωρίς θέαση από κάτω. Η κάμερα δεν έχει τοποθετηθεί ακόμα. Απλώς χρησιμοποιώ τον Πίνακα Ελέγχου Phidget για να στραφώ στον κινητήρα σε αυτό το σημείο.

Χώρος αποθήκευσης

Το επόμενο μέρος είναι ο σχεδιασμός του συστήματος κάδου για τη συγκράτηση κάθε χρώματος. Αποφάσισα να χρησιμοποιήσω ένα δεύτερο βηματικό μοτέρ παρακάτω για να υποστηρίξω και να περιστρέψω ένα στρογγυλό δοχείο με ομοιόμορφα διαχωρισμένα διαμερίσματα. Αυτό μπορεί να χρησιμοποιηθεί για περιστροφή του σωστού διαμερίσματος κάτω από την τρύπα από την οποία θα πέσει η χάντρα.

Το έφτιαξα χρησιμοποιώντας χαρτόνι και κολλητική ταινία. Το πιο σημαντικό πράγμα εδώ είναι η συνέπεια - κάθε διαμέρισμα πρέπει να έχει το ίδιο μέγεθος και το σύνολο πρέπει να σταθμίζεται ομοιόμορφα ώστε να περιστρέφεται χωρίς να παραλείπεται.

Η αφαίρεση των σφαιριδίων επιτυγχάνεται με ένα σφιχτό καπάκι που εκθέτει ένα μόνο διαμέρισμα κάθε φορά, έτσι ώστε τα σφαιρίδια να μπορούν να χυθούν έξω.

ΦΩΤΟΓΡΑΦΙΚΗ ΜΗΧΑΝΗ

Η κάμερα είναι τοποθετημένη πάνω από την επάνω πλάκα μεταξύ της χοάνης και της θέσης της οπής της κάτω πλάκας. Αυτό επιτρέπει στο σύστημα να κοιτάξει τη χάντρα πριν την ρίξει. Μια λυχνία LED χρησιμοποιείται για να φωτίζει τις χάντρες κάτω από την κάμερα και το φως του περιβάλλοντος αποκλείεται, προκειμένου να παρέχει ένα σταθερό περιβάλλον φωτισμού. Αυτό είναι πολύ σημαντικό για την ακριβή ανίχνευση χρώματος, καθώς ο φωτισμός περιβάλλοντος μπορεί πραγματικά να πετάξει το αντιληπτό χρώμα.

Ανίχνευση τοποθεσίας

Είναι σημαντικό για το σύστημα να μπορεί να ανιχνεύσει την περιστροφή του διαχωριστή σφαιριδίων. Αυτό χρησιμοποιείται για τη ρύθμιση της αρχικής θέσης κατά την εκκίνηση, αλλά και για τον εντοπισμό αν ο βηματικός κινητήρας έχει βγει εκτός συγχρονισμού. Στο σύστημά μου, μια χάντρα θα μπλοκάρει μερικές φορές κατά την παραλαβή και το σύστημα έπρεπε να είναι σε θέση να ανιχνεύσει και να χειριστεί αυτήν την κατάσταση - κάνοντας λίγο αντίγραφο ασφαλείας και δοκιμάζοντας άλλο.

Υπάρχουν πολλοί τρόποι να το χειριστείς αυτό. Αποφάσισα να χρησιμοποιήσω έναν μαγνητικό αισθητήρα 1108, με έναν μαγνήτη ενσωματωμένο στην άκρη της επάνω πλάκας. Αυτό μου επιτρέπει να επαληθεύσω τη θέση σε κάθε περιστροφή. Μια καλύτερη λύση θα ήταν πιθανώς ένας κωδικοποιητής στο βηματικό μοτέρ, αλλά είχα ένα 1108 ξαπλωμένο και το χρησιμοποίησα.

Τελειώστε το ρομπότ

Σε αυτό το σημείο, όλα έχουν επεξεργαστεί και δοκιμαστεί. It'sρθε η ώρα να τοποθετήσετε τα πάντα όμορφα και να προχωρήσετε στο λογισμικό γραφής.

Οι 2 βηματικοί κινητήρες οδηγούνται από βηματικά χειριστήρια STC1001. Ένας διανομέας HUB000 - USB VINT χρησιμοποιείται για τη λειτουργία των βηματικών ελεγκτών, καθώς και για την ανάγνωση του μαγνητικού αισθητήρα και την οδήγηση του LED. Η κάμερα web και το HUB0000 συνδέονται και τα δύο σε ένα μικρό διανομέα USB. Χρησιμοποιείται ένα κοτσιδάκι 3031 και κάποιο σύρμα μαζί με ένα τροφοδοτικό 24V για την τροφοδοσία των κινητήρων.

Βήμα 3: Γράψτε κώδικα

Image
Image

C# και Visual Studio 2015 χρησιμοποιούνται για αυτό το έργο. Κατεβάστε την πηγή στο επάνω μέρος αυτής της σελίδας και ακολουθήστε - οι κύριες ενότητες περιγράφονται παρακάτω

Αρχικοποίηση

Πρώτον, πρέπει να δημιουργήσουμε, να ανοίξουμε και να αρχικοποιήσουμε τα αντικείμενα Phidget. Αυτό γίνεται στο συμβάν φόρτωσης φόρμας και οι χειριστές επισυνάπτουν το Phidget.

ιδιωτικό κενό Form1_Load (αποστολέας αντικειμένων, EventArgs e) {

/ * Αρχικοποίηση και άνοιγμα Phidgets */

top. HubPort = 0; top. Attach += Top_Attach; top. Detach += Top_Detach; top. PositionChange += Top_PositionChange; κορυφή. Ανοιχτό ();

κάτω. HubPort = 1;

κάτω. Attach += Bottom_Attach; κάτω. Detach += Bottom_Detach; κάτω. PositionChange += Bottom_PositionChange; κάτω. Ανοιχτό ();

magSensor. HubPort = 2;

magSensor. IsHubPortDevice = true; magSensor. Attach += MagSensor_Attach; magSensor. Detach += MagSensor_Detach; magSensor. SensorChange += MagSensor_SensorChange; magSensor. Open ();

led. HubPort = 5;

led. IsHubPortDevice = true; led. Channel = 0; led. Attach += Led_Attach; led. Detach += Led_Detach; led. Open (); }

ιδιωτικό κενό Led_Attach (αποστολέας αντικειμένων, Phidget22. Events. AttachEventArgs ε) {

ledAttachedChk. Checked = true; led. State = true; ledChk. Checked = true; }

ιδιωτικό κενό MagSensor_Attach (αποστολέας αντικειμένων, Phidget22. Events. AttachEventArgs ε) {

magSensorAttachedChk. Checked = true; magSensor. SensorType = VoltageRatioSensorType. PN_1108; magSensor. DataInterval = 16; }

ιδιωτικό κενό Bottom_Attach (αποστολέας αντικειμένων, Phidget22. Events. AttachEventArgs ε) {

bottomAttachedChk. Checked = true; bottom. CurrentLimit = bottomCurrentLimit; κάτω. Συμμετοχή = αλήθεια bottom. VelocityLimit = bottomVelocityLimit; bottom. Acceleration = bottomAccel; κάτω. DataInterval = 100; }

ιδιωτικό κενό Top_Attach (αποστολέας αντικειμένων, Phidget22. Events. AttachEventArgs ε) {

topAttachedChk. Checked = true; top. CurrentLimit = topCurrentLimit; κορυφή. Συμμετοχή = αλήθεια; top. RescaleFactor = -1; top. VelocityLimit = -topVelocityLimit; κορυφή. Επιτάχυνση = -topAccel; top. DataInterval = 100; }

Διαβάζουμε επίσης σε τυχόν αποθηκευμένες πληροφορίες χρώματος κατά την αρχικοποίηση, έτσι ώστε να μπορεί να συνεχιστεί μια προηγούμενη εκτέλεση.

Θέση κινητήρα

Ο κωδικός χειρισμού κινητήρα αποτελείται από λειτουργίες ευκολίας για τη μετακίνηση των κινητήρων. Οι κινητήρες που χρησιμοποίησα είναι 3, 200 1/16 βήματα ανά περιστροφή, οπότε δημιούργησα μια σταθερά για αυτό.

Για τον επάνω κινητήρα, υπάρχουν 3 θέσεις που θέλουμε να μπορούμε να στείλουμε στο μοτέρ: η κάμερα web, η τρύπα και ο μαγνήτης εντοπισμού θέσης. Υπάρχει μια λειτουργία για ταξίδια σε καθεμία από αυτές τις θέσεις:

private void nextMagnet (Boolean wait = false) {

double posn = top. Position % stepsPerRev;

top. TargetPosition += (stepsPerRev - posn);

αν (περιμένετε)

while (top. IsMoving) Thread. Sleep (50); }

private void nextCamera (Boolean wait = false) {

double posn = top. Position % stepsPerRev; if (posn <Properties. Settings. Default.cameraOffset) top. TargetPosition += (Properties. Settings. Default.cameraOffset - posn); else top. TargetPosition + = ((Properties. Settings. Default.cameraOffset - posn) + stepsPerRev);

αν (περιμένετε)

while (top. IsMoving) Thread. Sleep (50); }

private void nextHole (Boolean wait = false) {

double posn = top. Position % stepsPerRev; if (posn <Properties. Settings. Default.holeOffset) top. TargetPosition += (Properties. Settings. Default.holeOffset - posn); else top. TargetPosition + = ((Properties. Settings. Default.holeOffset - posn) + stepsPerRev);

αν (περιμένετε)

while (top. IsMoving) Thread. Sleep (50); }

Πριν ξεκινήσετε μια διαδρομή, η επάνω πλάκα ευθυγραμμίζεται χρησιμοποιώντας τον μαγνητικό αισθητήρα. Η συνάρτηση alignMotor μπορεί να κληθεί ανά πάσα στιγμή για να ευθυγραμμίσει την επάνω πλάκα. Αυτή η λειτουργία πρώτα γυρίζει γρήγορα την πλάκα σε 1 πλήρη περιστροφή μέχρι να δει δεδομένα μαγνήτη πάνω από ένα κατώφλι. Στη συνέχεια, κάνει λίγο αντίγραφο ασφαλείας και προχωρά ξανά αργά, καταγράφοντας δεδομένα αισθητήρα όσο πάει. Τέλος, ορίζει τη θέση στη θέση δεδομένων μέγιστου μαγνήτη και επαναφέρει τη θέση μετατόπισης στο 0. Έτσι, η μέγιστη θέση μαγνήτη θα πρέπει να είναι πάντα στο (πάνω. Θέση % βήματαPerRev)

Thread alignMotorThread; Boolean sawMagnet; διπλό magSensorMax = 0; private void alignMotor () {

// Βρείτε τον μαγνήτη

top. DataInterval = top. MinDataInterval;

sawMagnet = false?

magSensor. SensorChange += magSensorStopMotor; top. VelocityLimit = -1000;

int tryCount = 0;

προσπάθησε ξανά:

top. TargetPosition += stepsPerRev;

while (top. IsMoving &&! sawMagnet) Thread. Sleep (25);

αν (! sawMagnet) {

if (tryCount> 3) {Console. WriteLine ("Η ευθυγράμμιση απέτυχε"); top. Engaged = false; κάτω. Συμμετοχή = ψευδής; runtest = ψευδής? ΕΠΙΣΤΡΟΦΗ; }

tryCount ++;

Console. WriteLine ("Έχουμε κολλήσει; Δοκιμάζουμε ένα αντίγραφο ασφαλείας …"); top. TargetPosition -= 600; while (top. IsMoving) Thread. Sleep (100).

πήγα να προσπαθήσω ξανά?

}

top. VelocityLimit = -100;

magData = νέα λίστα> (); magSensor. SensorChange += magSensorCollectPositionData; top. TargetPosition += 300; while (top. IsMoving) Thread. Sleep (100).

magSensor. SensorChange -= magSensorCollectPositionData;

top. VelocityLimit = -topVelocityLimit;

KeyValuePair max = magData [0];

foreach (KeyValuePair pair in magData) if (pair. Value> max. Value) max = pair;

top. AddPositionOffset (-max. Key);

magSensorMax = max. Value;

top. TargetPosition = 0;

while (top. IsMoving) Thread. Sleep (100).

Console. WriteLine ("Η ευθυγράμμιση πέτυχε");

}

Λίστα> magData;

private void magSensorCollectPositionData (αντικείμενο αποστολέας, Phidget22. Events. VoltageRatioInputSensorChangeEventArgs ε) {magData. Add (νέο KeyValuePair (επάνω. Θέση, e. SensorValue)); }

private void magSensorStopMotor (αποστολέας αντικειμένων, Phidget22. Events. VoltageRatioInputSensorChangeEventArgs ε) {

if (top. IsMoving && e. SensorValue> 5) {top. TargetPosition = top. Position - 300; magSensor. SensorChange -= magSensorStopMotor; sawMagnet = true? }}

Τέλος, ο κάτω κινητήρας ελέγχεται στέλνοντάς τον σε μία από τις θέσεις του δοχείου σφαιριδίων. Για αυτό το έργο, έχουμε 19 θέσεις. Ο αλγόριθμος επιλέγει τη συντομότερη διαδρομή και γυρίζει είτε δεξιόστροφα είτε αριστερόστροφα.

private int BottomPosition {get {int posn = (int) bottom. Position % stepsPerRev; εάν (posn <0) posn += stepsPerRev;

return (int) Math. Round (((posn * beadCompartments) / (διπλά) βήματαPerRev));

} }

ιδιωτικό κενό SetBottomPosition (int posn, bool wait = false) {

posn = posn % beadCompartments; double targetPosn = (posn * stepsPerRev) / beadCompartments;

διπλό ρεύμαPosn = κάτω. Θέση % βήματαPerRev;

double posnDiff = targetPosn - currentPosn;

// Κρατήστε το ως πλήρη βήματα

posnDiff = ((int) (posnDiff / 16)) * 16;

if (posnDiff <= 1600) bottom. TargetPosition += posnDiff; else bottom. TargetPosition - = (stepsPerRev - posnDiff);

αν (περιμένετε)

while (bottom. IsMoving) Thread. Sleep (50); }

ΦΩΤΟΓΡΑΦΙΚΗ ΜΗΧΑΝΗ

Το OpenCV χρησιμοποιείται για την ανάγνωση εικόνων από την κάμερα web. Το νήμα της κάμερας ξεκινά πριν ξεκινήσει το κύριο νήμα ταξινόμησης. Αυτό το νήμα διαβάζει συνεχώς σε εικόνες, υπολογίζει ένα μέσο χρώμα για μια συγκεκριμένη περιοχή χρησιμοποιώντας το Mean και ενημερώνει μια καθολική μεταβλητή χρώματος. Το νήμα χρησιμοποιεί επίσης HoughCircles για να προσπαθήσει να ανιχνεύσει είτε μια χάντρα, είτε την τρύπα στην επάνω πλάκα, για να βελτιώσει την περιοχή που αναζητά για την ανίχνευση χρώματος. Οι αριθμοί κατωφλίου και HoughCircles προσδιορίστηκαν μέσω δοκιμής και σφάλματος και εξαρτώνται σε μεγάλο βαθμό από την κάμερα web, τον φωτισμό και το διάστημα.

bool runVideo = true; bool videoRunning = false; Καταγραφή βίντεο Νήμα cvThread; Χρώμα ανιχνευμένο Boolean detection = false? int deteCnt = 0;

private void cvThreadFunction () {

videoRunning = false;

σύλληψη = νέα λήψη βίντεο (επιλεγμένη κάμερα).

χρησιμοποιώντας (Παράθυρο παραθύρου = νέο παράθυρο ("σύλληψη")) {

Εικόνα ματ = νέο ματ (); Mat εικόνα2 = νέο Mat (); while (runVideo) {capture. Read (εικόνα); εάν (εικόνα. Κενό ()) σπάσει.

εάν (ανίχνευση)

deteCnt ++; else deteCnt = 0;

εάν (ανίχνευση || κύκλοDetectChecked || εμφάνισηDetectionImgChecked) {

Cv2. CvtColor (εικόνα, εικόνα2, ColorConversionCodes. BGR2GRAY); Mat thres = image2. Threshold ((double) Properties. Settings. Default.videoThresh, 255, ThresholdTypes. Binary); thres = thres. GaussianBlur (νέο OpenCvSharp. Size (9, 9), 10);

εάν (showDetectionImgChecked)

εικόνα = thres;

εάν (ανίχνευση || circleDetectChecked) {

CircleSegment bead = thres. HoughCircles (HoughMethods. Gradient, 2, /*thres. Rows/4*/ 20, 200, 100, 20, 65); if (bead. Length> = 1) {image. Circle (bead [0]. Center, 3, new Scalar (0, 100, 0), -1); εικόνα. Κύκλος (χάντρα [0]. Κέντρο, (int) χάντρα [0]. Radius, νέο Scalar (0, 0, 255), 3). if (σφαιρίδιο [0]. Radius> = 55) {Properties. Settings. Default.x = (δεκαδικό) σφαιρίδιο [0]. Center. X + (δεκαδικό) (χάντρα [0]. Radius / 2); Properties. Settings. Default.y = (δεκαδικό) σφαιρίδιο [0]. Center. Y - (δεκαδικό) (bead [0]. Radius / 2); } else {Properties. Settings. Default.x = (δεκαδικό) σφαιρίδιο [0]. Center. X + (δεκαδικό) (χάντρα [0]. Radius); Properties. Settings. Default.y = (δεκαδικό) σφαιρίδιο [0]. Center. Y - (δεκαδικό) (bead [0]. Radius); } Properties. Settings. Default.size = 15; Properties. Settings. Default.height = 15; } αλλο {

CircleSegment κύκλοι = thres. HoughCircles (HoughMethods. Gradient, 2, /*thres. Rows/4*/ 5, 200, 100, 60, 180);

εάν (κύκλοι. Μήκος> 1) {Λίστα xs = κύκλοι. Επιλέξτε (c => c. Center. X). ToList (); xs. Sort (); Λίστα ys = κύκλοι. Επιλέξτε (c => c. Center. Y). ToList (); ys. Sort ();

int medianX = (int) xs [xs. Count / 2];

int medianY = (int) ys [ys. Count / 2];

εάν (medianX> εικόνα. Πλάτος - 15)

medianX = εικόνα. Πλάτος - 15; εάν (διάμεσος Υ> εικόνα. eψος - 15) διάμεσος Υ = εικόνα. ightψος - 15;

image. Circle (medianX, medianY, 100, new Scalar (0, 0, 150), 3).

εάν (ανίχνευση) {

Properties. Settings. Default.x = medianX - 7; Properties. Settings. Default.y = διάμεσοςY - 7; Properties. Settings. Default.size = 15; Properties. Settings. Default.height = 15; }}}}}

Rect r = new Rect ((int) Properties. Settings. Default.x, (int) Properties. Settings. Default.y, (int) Properties. Settings. Default.size, (int) Properties. Settings. Default.height);

Mat beadSample = νέο χαλάκι (εικόνα, r);

Scalar avgColor = Cv2. Mean (beadSample); deteColor = Color. FromArgb ((int) avgColor [2], (int) avgColor [1], (int) avgColor [0]);

εικόνα. Ορθογώνιο (r, νέο Scalar (0, 150, 0))

παράθυρο. ShowImage (εικόνα);

Cv2. WaitKey (1); videoRunning = true; }

videoRunning = false;

} }

ιδιωτική κενή κάμεραStartBtn_Click (αποστολέας αντικειμένων, EventArgs e) {

εάν (cameraStartBtn. Text == "έναρξη") {

cvThread = νέο νήμα (νέο ThreadStart (cvThreadFunction)); runVideo = true; cvThread. Start (); cameraStartBtn. Text = "διακοπή"; while (! videoRunning) Thread. Sleep (100)?

updateColorTimer. Start ();

} αλλο {

runVideo = false; cvThread. Join (); cameraStartBtn. Text = "έναρξη"; }}

Χρώμα

Τώρα, είμαστε σε θέση να καθορίσουμε το χρώμα μιας χάντρας και να αποφασίσουμε με βάση αυτό το χρώμα σε ποιο δοχείο θα την ρίξουμε.

Αυτό το βήμα βασίζεται στη σύγκριση χρωμάτων. Θέλουμε να είμαστε σε θέση να ξεχωρίσουμε τα χρώματα για να περιορίσουμε το ψευδώς θετικό, αλλά και να επιτρέψουμε αρκετό όριο για να περιορίσουμε τα ψευδώς αρνητικά. Η σύγκριση των χρωμάτων είναι πραγματικά εκπληκτικά περίπλοκη, επειδή ο τρόπος που οι υπολογιστές αποθηκεύουν τα χρώματα ως RGB και ο τρόπος με τον οποίο οι άνθρωποι αντιλαμβάνονται τα χρώματα δεν συσχετίζονται γραμμικά. Για να γίνουν τα πράγματα χειρότερα, πρέπει επίσης να ληφθεί υπόψη το χρώμα του φωτός στο οποίο εξετάζεται ένα χρώμα.

Υπάρχουν περίπλοκοι αλγόριθμοι για τον υπολογισμό της διαφοράς χρώματος. Χρησιμοποιούμε το CIE2000, το οποίο εξάγει έναν αριθμό κοντά στο 1 εάν 2 χρώματα δεν θα μπορούσαν να διακριθούν σε έναν άνθρωπο. Χρησιμοποιούμε τη βιβλιοθήκη ColorMine C# για να κάνουμε αυτούς τους περίπλοκους υπολογισμούς. Έχει βρεθεί ότι η τιμή 5 του DeltaE προσφέρει καλό συμβιβασμό μεταξύ ψευδώς θετικών και ψευδώς αρνητικών.

Καθώς συχνά υπάρχουν περισσότερα χρώματα από τα δοχεία, η τελευταία θέση διατηρείται ως κάδος. Γενικά τα άφησα στην άκρη για να τρέξουν αν και το μηχάνημα σε δεύτερο πέρασμα.

Λίστα

χρώματα = νέα λίστα (); λίστα λίστας colorPanels = νέα λίστα (); Χρώματα λίσταςTxts = νέα λίστα (); Λίστα colorCnts = νέα λίστα ();

const int numColorSpots = 18;

const int unknownColorIndex = 18; int findColorPosition (Χρώμα c) {

Console. WriteLine ("Εύρεση χρώματος …");

var cRGB = νέο Rgb ();

cRGB. R = c. R; cRGB. G = c. G; cRGB. B = c. B;

int bestMatch = -1;

διπλή αντιστοίχισηDelta = 100;

για (int i = 0; i <colors. Count; i ++) {

var RGB = νέο Rgb ();

RGB. R = χρώματα . R; RGB. G = χρώματα . G; RGB. B = χρώματα . B;

διπλό δέλτα = cRGB. Σύγκριση (RGB, νέα CieDe2000Comparison ());

// διπλό δέλτα = deltaE (c, χρώματα ); Console. WriteLine ("DeltaE (" + i. ToString () + "):" + delta. ToString ()); αν (δέλτα <matchDelta) {matchDelta = δέλτα; bestMatch = i; }}

if (matchDelta <5) {Console. WriteLine ("Βρέθηκε! (Posn:" + bestMatch + "Delta:" + matchDelta + ")"); επιστροφή bestMatch? }

if (colors. Count <numColorSpots) {Console. WriteLine ("Νέο χρώμα!"); χρώματα. Προσθήκη (γ) this. BeginInvoke (νέα ενέργεια (setBackColor), νέο αντικείμενο {colors. Count - 1}); writeOutColors (); επιστροφή (χρώματα. Αριθμός - 1). } else {Console. WriteLine ("Άγνωστο χρώμα!"); επιστροφή άγνωστοColorIndex; }}

Ταξινόμηση λογικής

Η λειτουργία ταξινόμησης συγκεντρώνει όλα τα κομμάτια για να ταξινομήσει πραγματικά τις χάντρες. Αυτή η λειτουργία εκτελείται σε ένα ειδικό νήμα. μετακίνηση της επάνω πλάκας, ανίχνευση του χρώματος της χάντρας, τοποθέτησή της σε κάδο, βεβαιωθείτε ότι η επάνω πλάκα παραμένει ευθυγραμμισμένη, μετρώντας τις χάντρες κ.λπ. Επίσης, σταματά να λειτουργεί όταν γεμίσει ο κάδος του κάστρου - Διαφορετικά καταλήγουμε σε ξεχειλισμένες χάντρες.

Νήμα colourTestThread; Boolean runtest = false; void colourTest () {

αν (! κορυφή. Συμμετοχή)

κορυφή. Συμμετοχή = αλήθεια;

αν (! κάτω. Συμμετοχή)

κάτω. Συμμετοχή = αλήθεια

ενώ (πιο χάλια) {

nextMagnet (αληθινό);

Θέμα. Leepπνος (100). δοκιμάστε {if (magSensor. SensorValue <(magSensorMax - 4)) alignMotor (); } catch {alignMotor (); }

nextCamera (αληθινή)

ανίχνευση = αλήθεια.

while (deteCnt <5) Thread. Sleep (25); Console. WriteLine ("Detect Count:" + detectnt); ανίχνευση = ψευδές?

Χρώμα c = deteColor;

this. BeginInvoke (νέα ενέργεια (setColorDet), νέο αντικείμενο {c}); int i = findColorPosition (c);

SetBottomPosition (i, true);

nextHole (αληθινό)? colorCnts ++; this. BeginInvoke (νέα ενέργεια (setColorTxt), νέο αντικείμενο {i}); Θέμα. Leepπνος (250).

εάν (colorCnts [unknownColorIndex]> 500) {

top. Engaged = false; κάτω. Συμμετοχή = ψευδής; runtest = ψευδής? this. BeginInvoke (νέα δράση (setGoGreen), null); ΕΠΙΣΤΡΟΦΗ; }}}

private void colourTestBtn_Click (αποστολέας αντικειμένων, EventArgs e) {

if (colourTestThread == null ||! colourTestThread. IsAlive) {colourTestThread = νέο νήμα (νέο ThreadStart (colourTest)); runtest = αλήθεια? colourTestThread. Start (); colourTestBtn. Text = "STOP"; colourTestBtn. BackColor = Color. Red; } else {runtest = false; colourTestBtn. Text = "GO"; colourTestBtn. BackColor = Color. Green; }}

Σε αυτό το σημείο, έχουμε ένα πρόγραμμα εργασίας. Μερικά κομμάτια κώδικα έμειναν εκτός του άρθρου, οπότε ρίξτε μια ματιά στην πηγή για να το εκτελέσετε.

Διαγωνισμός Οπτικής
Διαγωνισμός Οπτικής

Δεύτερο Βραβείο στον Διαγωνισμό Οπτικής

Συνιστάται: