Indholdsfortegnelse:
Video: Sortering af robotperler: 3 trin (med billeder)
2024 Forfatter: John Day | [email protected]. Sidst ændret: 2024-01-30 08:28
I dette projekt bygger vi en robot til at sortere Perler -perler efter farve.
Jeg har altid ønsket at bygge en farvesorteringsrobot, så da min datter blev interesseret i Perler perlefremstilling, så jeg dette som en perfekt mulighed.
Perlerperler bruges til at skabe sammensmeltede kunstprojekter ved at placere mange perler på en tavle og derefter smelte dem sammen med et jern. Du køber generelt disse perler i kæmpe 22.000 perler med blandede farver, og bruger meget tid på at søge efter den farve, du ønsker, så jeg tænkte, at sortering af dem ville øge kunsteffektiviteten.
Jeg arbejder for Phidgets Inc., så jeg brugte mest Phidgets til dette projekt - men dette kunne gøres ved hjælp af passende hardware.
Trin 1: Hardware
Her er hvad jeg plejede at bygge dette. Jeg byggede den 100% med dele fra phidgets.com, og ting jeg havde liggende rundt i huset.
Phidgets tavler, motorer, hardware
- HUB0000 - VINT Hub Phidget
- 1108 - Magnetisk sensor
- 2x STC1001 - 2,5A Stepper Phidget
- 2x 3324 - 42STH38 NEMA -17 Bipolar gearfri trin
- 3x 3002 - Phidget -kabel 60cm
- 3403 - USB2.0 4 -Port Hub
- 3031 - Kvindehale 5.5x2.1mm
- 3029 - 2 -leder 100 'snoet kabel
- 3604 - 10 mm hvid LED (pose med 10)
- 3402 - USB -webcam
Andre dele
- 24VDC 2.0A strømforsyning
- Skrot træ og metal fra garagen
- Lynlåse
- Plastbeholder med bunden afskåret
Trin 2: Design robotten
Vi er nødt til at designe noget, der kan tage en enkelt perle fra inputbeholderen, placere den under webkameraet og derefter flytte den i den relevante bin.
Perle afhentning
Jeg besluttede at lave den første del med 2 stykker rund krydsfiner, hver med et hul boret samme sted. Bundstykket er fastgjort, og det øverste stykke er fastgjort til en trinmotor, som kan rotere det under en beholder fyldt med perler. Når hullet bevæger sig under beholderen, samler det en enkelt perle op. Jeg kan derefter rotere det under webkameraet og derefter rotere yderligere, indtil det matcher hullet i bundstykket, på hvilket tidspunkt det falder igennem.
På dette billede tester jeg, at systemet kan fungere. Alt er fastgjort undtagen det øverste runde stykke krydsfiner, som er fastgjort til en trinmotor uden for synsfladen nedenunder. Webkameraet er ikke monteret endnu. Jeg bruger bare Phidget Control Panel til at vende til motor på dette tidspunkt.
Perleopbevaring
Den næste del er at designe skraldespandssystemet til at holde hver farve. Jeg besluttede at bruge en anden trinmotor nedenfor til at støtte og rotere en rund beholder med jævnt fordelte rum. Dette kan bruges til at rotere det korrekte rum under hullet, som perlen vil falde ud af.
Jeg byggede dette ved hjælp af pap og gaffatape. Det vigtigste her er konsistens - hvert rum skal have samme størrelse, og det hele skal vægtes jævnt, så det snurrer uden at springe over.
Fjernelse af perler udføres ved hjælp af et tætsluttende låg, der udsætter et enkelt rum ad gangen, så perlerne kan hældes ud.
Kamera
Webkameraet er monteret over toppladen mellem beholderen og placeringen af det nederste pladehul. Dette gør det muligt for systemet at se på perlen, før den tabes. En LED bruges til at belyse perlerne under kameraet, og omgivende lys er blokeret for at skabe et ensartet lysmiljø. Dette er meget vigtigt for nøjagtig farvedetektion, da omgivelsesbelysning virkelig kan smide den opfattede farve.
Placeringsregistrering
Det er vigtigt for systemet at kunne detektere rotationen af perleseparatoren. Dette bruges til at konfigurere startpositionen ved opstart, men også til at registrere, om trinmotoren er blevet ude af synkronisering. I mit system vil en perle undertiden jamme, mens den bliver hentet, og systemet havde brug for at kunne opdage og håndtere denne situation - ved at sikkerhedskopiere lidt og prøve igen.
Der er mange måder at håndtere dette på. Jeg besluttede at bruge en 1108 magnetisk sensor, med en magnet indlejret i kanten af den øverste plade. Dette giver mig mulighed for at verificere positionen ved hver rotation. En bedre løsning ville nok være en encoder på steppermotoren, men jeg havde en 1108 liggende, så jeg brugte det.
Afslut robotten
På dette tidspunkt er alt blevet udarbejdet og testet. Det er tid til at montere alt pænt og gå videre til skrivesoftware.
De 2 trinmotorer drives af STC1001 -trinstyringer. En HUB000 - USB VINT -hub bruges til at køre stepper -controllerne, samt aflæse den magnetiske sensor og drive LED'en. Webkameraet og HUB0000 er begge tilsluttet en lille USB -hub. En 3031 grisehale og noget ledning bruges sammen med en 24V strømforsyning til at drive motorerne.
Trin 3: Skriv kode
C# og Visual Studio 2015 bruges til dette projekt. Download kilden øverst på denne side og følg med - hovedafsnittene er beskrevet nedenfor
Initialisering
Først skal vi oprette, åbne og initialisere Phidget -objekterne. Dette gøres i formindlæsningshændelsen, og Phidget vedhæfter håndterere.
private void Form1_Load (objekt afsender, EventArgs e) {
/ * Initialiser og åbn Phidgets */
top. HubPort = 0; top. Attach += Top_Attach; top. Detach += Top_Detach; top. PositionChange += Top_PositionChange; top. Open ();
bottom. HubPort = 1;
bottom. Attach += Bottom_Attach; bottom. Detach += Bottom_Detach; bottom. PositionChange += Bottom_PositionChange; bund. Åben ();
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 (); }
private void Led_Attach (objektafsender, Phidget22. Events. AttachEventArgs e) {
ledAttachedChk. Checked = true; led. State = true; ledChk. Checked = true; }
private void MagSensor_Attach (objekt afsender, Phidget22. Events. AttachEventArgs e) {
magSensorAttachedChk. Checked = true; magSensor. SensorType = VoltageRatioSensorType. PN_1108; magSensor. DataInterval = 16; }
private void Bottom_Attach (objektafsender, Phidget22. Events. AttachEventArgs e) {
bottomAttachedChk. Checked = true; bottom. CurrentLimit = bottomCurrentLimit; bottom. Engaged = true; bottom. VelocityLimit = bottomVelocityLimit; bund. Acceleration = bottomAccel; bottom. DataInterval = 100; }
private void Top_Attach (objektafsender, Phidget22. Events. AttachEventArgs e) {
topAttachedChk. Checked = true; top. CurrentLimit = topCurrentLimit; top. Engaged = true; top. RescaleFactor = -1; top. VelocityLimit = -topVelocityLimit; top. Acceleration = -topAccel; top. DataInterval = 100; }
Vi læser også alle gemte farveoplysninger ind under initialiseringen, så en tidligere kørsel kan fortsættes.
Motorpositionering
Motorhåndteringskoden består af bekvemmelighedsfunktioner til flytning af motorerne. De motorer, jeg brugte, er 3, 200 1/16 trin pr. Omdrejning, så jeg skabte en konstant for dette.
For topmotoren er der 3 positioner, vi ønsker at kunne sende til motoren til: webkameraet, hullet og positioneringsmagneten. Der er en funktion til at rejse til hver af disse positioner:
private void nextMagnet (boolsk vent = falsk) {
dobbelt posn = top. Position % stepsPerRev;
top. TargetPosition += (stepsPerRev - posn);
hvis (vent)
mens (top. IsMoving) Thread. Sleep (50); }
private void nextCamera (boolsk vent = falsk) {
dobbelt posn = top. Position % stepsPerRev; hvis (posn <Properties. Settings. Default.cameraOffset) top. TargetPosition += (Properties. Settings. Default.cameraOffset - posn); ellers top. TargetPosition + = ((Properties. Settings. Default.cameraOffset - posn) + stepsPerRev);
hvis (vent)
mens (top. IsMoving) Thread. Sleep (50); }
private void nextHole (boolsk vent = falsk) {
dobbelt posn = top. Position % stepsPerRev; hvis (posn <Properties. Settings. Default.holeOffset) top. TargetPosition += (Properties. Settings. Default.holeOffset - posn); ellers top. TargetPosition + = ((Properties. Settings. Default.holeOffset - posn) + stepsPerRev);
hvis (vent)
mens (top. IsMoving) Thread. Sleep (50); }
Før du starter et løb, justeres toppladen ved hjælp af den magnetiske sensor. AlignMotor -funktionen kan til enhver tid kaldes for at justere toppladen. Denne funktion skruer først pladen op til 1 fuld omdrejning, indtil den ser magnetdata over en tærskel. Det bakker derefter lidt op og bevæger sig langsomt fremad igen og indfanger sensordata som det går. Endelig indstiller den positionen til den maksimale magnetdataplacering og nulstiller positionsforskydningen til 0. Således bør den maksimale magnetposition altid være på (top. Position % stepsPerRev)
Thread alignMotorThread; boolsk savMagnet; dobbelt magSensorMax = 0; private void alignMotor () {
// Find magneten
top. DataInterval = top. MinDataInterval;
sawMagnet = falsk;
magSensor. SensorChange += magSensorStopMotor; top. VelocityLimit = -1000;
int tryCount = 0;
Prøv igen:
top. TargetPosition += stepsPerRev;
mens (top. IsMoving &&! sawMagnet) Thread. Sleep (25);
hvis (! sawMagnet) {
hvis (tryCount> 3) {Console. WriteLine ("Justering mislykkedes"); top. Engaged = false; bottom. Engaged = false; runtest = false; Vend tilbage; }
tryCount ++;
Console. WriteLine ("Sidder vi fast? Prøver du at tage en sikkerhedskopi …"); top. TargetPosition -= 600; mens (top. IsMoving) Thread. Sleep (100);
prøv igen;
}
top. VelocityLimit = -100;
magData = ny liste> (); magSensor. SensorChange += magSensorCollectPositionData; top. TargetPosition += 300; mens (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. Nøgle);
magSensorMax = maks. værdi;
top. TargetPosition = 0;
mens (top. IsMoving) Thread. Sleep (100);
Console. WriteLine ("Justering lykkedes");
}
Liste> magData;
private void magSensorCollectPositionData (objekt afsender, Phidget22. Events. VoltageRatioInputSensorChangeEventArgs e) {magData. Add (ny KeyValuePair (top. Position, e. SensorValue)); }
private void magSensorStopMotor (objektafsender, Phidget22. Events. VoltageRatioInputSensorChangeEventArgs e) {
if (top. IsMoving && e. SensorValue> 5) {top. TargetPosition = top. Position - 300; magSensor. SensorChange -= magSensorStopMotor; sawMagnet = sandt; }}
Endelig styres bundmotoren ved at sende den til en af perlebeholderpositionerne. Til dette projekt har vi 19 stillinger. Algoritmen vælger en korteste vej og drejer enten med eller mod uret.
private int BottomPosition {get {int posn = (int) bottom. Position % stepsPerRev; hvis (posn <0) posn += stepsPerRev;
return (int) Math. Round (((posn * beadCompartment) / (double) stepsPerRev));
} }
private void SetBottomPosition (int posn, bool wait = false) {
posn = posn % beadCompartment; dobbelt targetPosn = (posn * stepsPerRev) / beadCompartment;
double currentPosn = bottom. Position % stepsPerRev;
dobbelt posnDiff = targetPosn - currentPosn;
// Gem det som fulde trin
posnDiff = ((int) (posnDiff / 16)) * 16;
hvis (posnDiff <= 1600) bottom. TargetPosition += posnDiff; else bottom. TargetPosition - = (stepsPerRev - posnDiff);
hvis (vent)
mens (bottom. IsMoving) Thread. Sleep (50); }
Kamera
OpenCV bruges til at læse billeder fra webcam. Kameratråden startes, før hovedsorteringstråden startes. Denne tråd læser løbende i billeder, beregner en gennemsnitsfarve for en bestemt region ved hjælp af middelværdi og opdaterer en global farvevariabel. Tråden anvender også HoughCircles til at forsøge at opdage enten en perle eller hullet i den øverste plade for at forfine det område, den ser på til farvedetektering. Tærsklen og HoughCircles -tallene blev bestemt gennem forsøg og fejl og afhænger stærkt af webcam, belysning og afstand.
bool runVideo = true; bool videoRunning = false; VideoCapture -optagelse; Tråd cvThread; Farve opdagetFarve; Boolsk detektering = falsk; int detectCnt = 0;
private void cvThreadFunction () {
videoRunning = falsk;
capture = ny VideoCapture (valgt kamera);
ved hjælp af (Vinduesvindue = nyt vindue ("capture")) {
Mat billede = ny Mat (); Mat image2 = ny Mat (); mens (runVideo) {capture. Read (billede); hvis (image. Empty ()) går i stykker;
hvis (opdager)
detectCnt ++; ellers detectCnt = 0;
hvis (registrerer || circleDetectChecked || showDetectionImgChecked) {
Cv2. CvtColor (billede, image2, ColorConversionCodes. BGR2GRAY); Mat trøster = image2. Threshold ((dobbelt) Properties. Settings. Default.videoThresh, 255, ThresholdTypes. Binary); tærsker = tærsker. GaussianBlur (ny OpenCvSharp. Size (9, 9), 10);
hvis (showDetectionImgChecked)
billede = tærsk;
if (registrerer || circleDetectChecked) {
CircleSegment perle = tærske. HoughCircles (HoughMethods. Gradient, 2, /*thres. Rows/4*/ 20, 200, 100, 20, 65); hvis (bead. Length> = 1) {image. Circle (perle [0]. Center, 3, ny Scalar (0, 100, 0), -1); image. Circle (perle [0]. Center, (int) perle [0]. Radius, ny Scalar (0, 0, 255), 3); hvis (perle [0]. Radius> = 55) {Properties. Settings. Default.x = (decimal) perle [0]. Center. X + (decimal) (perle [0]. Radius / 2); Properties. Settings. Default.y = (decimal) perle [0]. Center. Y - (decimal) (perle [0]. Radius / 2); } ellers {Properties. Settings. Default.x = (decimal) perle [0]. Center. X + (decimal) (perle [0]. Radius); Properties. Settings. Default.y = (decimal) perle [0]. Center. Y - (decimal) (perle [0]. Radius); } Properties. Settings. Default.size = 15; Properties. Settings. Default.height = 15; } andet {
CircleSegment cirkler = tærske. HoughCircles (HoughMethods. Gradient, 2, /*thres. Rows/4*/ 5, 200, 100, 60, 180);
hvis (cirkler. Længde> 1) {Liste xs = cirkler. Vælg (c => c. Center. X). ToList (); xs. Sort (); Liste ys = cirkler. Vælg (c => c. Center. Y). ToList (); ys. Sort ();
int medianX = (int) xs [xs. Count / 2];
int medianY = (int) ys [ys. Count / 2];
hvis (medianX> image. Width - 15)
medianX = image. Width - 15; hvis (medianY> image. Height - 15) medianY = image. Height - 15;
image. Circle (medianX, medianY, 100, ny Scalar (0, 0, 150), 3);
hvis (detekterer) {
Properties. Settings. Default.x = medianX - 7; Properties. Settings. Default.y = medianY - 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 = ny måtte (billede, r);
Scalar avgColor = Cv2. Mean (beadSample); detectedColor = Color. FromArgb ((int) avgColor [2], (int) avgColor [1], (int) avgColor [0]);
image. Rektangel (r, ny Scalar (0, 150, 0));
window. ShowImage (billede);
Cv2. WaitKey (1); videoRunning = true; }
videoRunning = falsk;
} }
private void cameraStartBtn_Click (objekt afsender, EventArgs e) {
hvis (cameraStartBtn. Text == "start") {
cvThread = ny tråd (ny ThreadStart (cvThreadFunction)); runVideo = true; cvThread. Start (); cameraStartBtn. Text = "stop"; mens (! videoRunning) Thread. Sleep (100);
updateColorTimer. Start ();
} andet {
runVideo = falsk; cvThread. Join (); cameraStartBtn. Text = "start"; }}
Farve
Nu er vi i stand til at bestemme farven på en perle og beslutte ud fra den farve, hvilken beholder vi skal slippe den i.
Dette trin afhænger af farvesammenligning. Vi vil gerne kunne skelne farver fra hinanden for at begrænse falsk positivt, men også tillade nok tærskel til at begrænse falske negativer. Det er faktisk overraskende komplekst at sammenligne farver, fordi den måde computere lagrer farver på som RGB, og den måde mennesker opfatter farver ikke korrelerer lineært. For at gøre tingene værre skal farven på det lys, en farve, der ses under, også tages i betragtning.
Der er kompliceret algoritme til beregning af farveforskel. Vi bruger CIE2000, som udsender et tal nær 1, hvis 2 farver ikke kunne skelnes af et menneske. Vi bruger ColorMine C# biblioteket til at udføre disse komplicerede beregninger. En DeltaE -værdi på 5 har vist sig at tilbyde et godt kompromis mellem falsk positiv og falsk negativ.
Da der ofte er flere farver end containere, er den sidste position forbeholdt en opsamlingsbeholder. Jeg satte generelt disse til side til at køre selvom maskinen på et andet pas.
Liste
farver = ny liste (); liste farvepaneler = ny liste (); Liste farverTxts = ny Liste (); Liste colorCnts = ny Liste ();
const int numColorSpots = 18;
const int unknownColorIndex = 18; int findColorPosition (farve c) {
Console. WriteLine ("Find farve …");
var cRGB = ny Rgb ();
cRGB. R = c. R; cRGB. G = c. G; cRGB. B = c. B;
int bestMatch = -1;
dobbelt matchDelta = 100;
for (int i = 0; i <colors. Count; i ++) {
var RGB = ny Rgb ();
RGB. R = farver . R; RGB. G = farver . G; RGB. B = farver . B;
dobbelt delta = cRGB. Compare (RGB, new CieDe2000Comparison ());
// dobbelt delta = deltaE (c, farver ); Console. WriteLine ("DeltaE (" + i. ToString () + "):" + delta. ToString ()); hvis (delta <matchDelta) {matchDelta = delta; bestMatch = i; }}
if (matchDelta <5) {Console. WriteLine ("Fundet! (Posn:" + bestMatch + "Delta:" + matchDelta + ")"); returner bestMatch; }
if (colors. Count <numColorSpots) {Console. WriteLine ("Ny farve!"); farver. Tilføj (c); this. BeginInvoke (ny handling (setBackColor), nyt objekt {colors. Count - 1}); writeOutColors (); return (farver. Tælling - 1); } ellers {Console. WriteLine ("Ukendt farve!"); returner unknownColorIndex; }}
Sorteringslogik
Sorteringsfunktionen samler alle stykker til faktisk at sortere perler. Denne funktion kører i en dedikeret tråd; flytte toppladen, opdage perlefarven, placere den i en skraldespand, sikre at toppladen forbliver på linje, tælle perlerne osv. Det stopper også med at køre, når opsamlingsbeholderen bliver fuld - Ellers ender vi bare med overfyldte perler.
Thread colourTestThread; boolsk runtest = false; void colourTest () {
hvis (! top. Engaged)
top. Engaged = true;
hvis (! bottom. Engaged)
bottom. Engaged = true;
mens (runtest) {
nextMagnet (sand);
Thread. Sleep (100); prøv {if (magSensor. SensorValue <(magSensorMax - 4)) alignMotor (); } catch {alignMotor (); }
nextCamera (sandt);
detektering = sandt;
while (detectCnt <5) Thread. Sleep (25); Console. WriteLine ("Detect Count:" + detectCnt); detektering = falsk;
Farve c = opdagetColor;
this. BeginInvoke (ny handling (setColorDet), nyt objekt {c}); int i = findColorPosition (c);
SetBottomPosition (i, sand);
nextHole (sandt); colorCnts ++; this. BeginInvoke (ny handling (setColorTxt), nyt objekt {i}); Tråd. Sovende (250);
hvis (colorCnts [unknownColorIndex]> 500) {
top. Engaged = false; bottom. Engaged = false; runtest = false; this. BeginInvoke (ny handling (setGoGreen), null); Vend tilbage; }}}
private void colourTestBtn_Click (objekt afsender, EventArgs e) {
if (colourTestThread == null ||! colourTestThread. IsAlive) {colourTestThread = new Thread (new ThreadStart (colourTest)); runtest = true; colourTestThread. Start (); colourTestBtn. Text = "STOP"; colourTestBtn. BackColor = Color. Red; } ellers {runtest = false; colourTestBtn. Text = "GO"; colourTestBtn. BackColor = Color. Green; }}
På dette tidspunkt har vi et arbejdsprogram. Nogle stumper kode blev udeladt af artiklen, så tag et kig på kilden for faktisk at køre den.
Anden pris i optikkonkurrencen
Anbefalede:
Sådan gør du: Installation af Raspberry PI 4 Headless (VNC) med Rpi-imager og billeder: 7 trin (med billeder)
Sådan gør du: Installation af Raspberry PI 4 Headless (VNC) med Rpi-imager og billeder: Jeg planlægger at bruge denne Rapsberry PI i en masse sjove projekter tilbage i min blog. Tjek det gerne ud. Jeg ville tilbage til at bruge min Raspberry PI, men jeg havde ikke et tastatur eller en mus på min nye placering. Det var et stykke tid siden jeg konfigurerede en hindbær
Arduino Halloween Edition - Pop -out -skærm med zombier (trin med billeder): 6 trin
Arduino Halloween Edition - Zombies Pop -out -skærm (trin med billeder): Vil du skræmme dine venner og lave skrigende støj i Halloween? Eller vil du bare lave en god sjov? Denne pop-out-skærm fra Zombies kan gøre det! I denne Instructable vil jeg lære dig, hvordan du nemt laver jump-out zombier ved hjælp af Arduino. HC-SR0
Sådan adskilles en computer med nemme trin og billeder: 13 trin (med billeder)
Sådan adskilles en computer med nemme trin og billeder: Dette er en instruktion om, hvordan du adskiller en pc. De fleste af de grundlæggende komponenter er modulopbyggede og nemme at fjerne. Det er dog vigtigt, at du er organiseret omkring det. Dette hjælper med at forhindre dig i at miste dele og også ved at lave genmonteringen til
Ciclop 3d Scanner My Way Trin for trin: 16 trin (med billeder)
Ciclop 3d Scanner My Way Step by Step: Hej alle sammen, jeg kommer til at indse den berømte Ciclop 3D -scanner.Alle trin, der er godt forklaret på det originale projekt, er ikke til stede.Jeg lavede nogle rettelser for at forenkle processen, først Jeg udskriver basen, og end jeg genstarter printkortet, men fortsæt
Sådan styrer du husholdningsapparater med fjernsyn med fjernbetjening med timerfunktion: 7 trin (med billeder)
Sådan styrer du husholdningsapparater med fjernsyn med fjernbetjening med timerfunktion: Selv efter 25 års introduktion til forbrugermarkedet er infrarød kommunikation stadig meget relevant i de seneste dage. Uanset om det er dit 55 tommer 4K -fjernsyn eller dit billydsystem, har alt brug for en IR -fjernbetjening for at reagere på vores