Innholdsfortegnelse:

Sortering av robotperler: 3 trinn (med bilder)
Sortering av robotperler: 3 trinn (med bilder)

Video: Sortering av robotperler: 3 trinn (med bilder)

Video: Sortering av robotperler: 3 trinn (med bilder)
Video: Охотимся на оленей . Загоняем их в Кораль. Жизнь лесотундры Вынгаяхи 2. 2024, November
Anonim
Image
Image
Robotsortering av perler
Robotsortering av perler
Robotsortering av perler
Robotsortering av perler
Robotsortering av perler
Robotsortering av perler

I dette prosjektet skal vi bygge en robot for å sortere Perler -perler etter farge.

Jeg har alltid ønsket å bygge en fargesorteringsrobot, så da datteren min ble interessert i Perler -perler, så jeg dette som en perfekt mulighet.

Perlerperler brukes til å lage smeltede kunstprosjekter ved å plassere mange perler på en tappeplate, og deretter smelte dem sammen med et jern. Du kjøper vanligvis disse perlene i gigantiske pakker med 22 000 perler, og bruker mye tid på å lete etter fargen du vil ha, så jeg tenkte å sortere dem for å øke kunsteffektiviteten.

Jeg jobber for Phidgets Inc., så jeg brukte mest Phidgets til dette prosjektet - men dette kan gjøres med passende maskinvare.

Trinn 1: Maskinvare

Her er hva jeg brukte til å bygge dette. Jeg bygde den 100% med deler fra phidgets.com, og ting jeg hadde liggende rundt huset.

Phidgets Boards, Motors, Hardware

  • HUB0000 - VINT Hub Phidget
  • 1108 - Magnetisk sensor
  • 2x STC1001 - 2,5A Stepper Phidget
  • 2x 3324 - 42STH38 NEMA -17 Bipolar Gearless Stepper
  • 3x 3002 - Phidget -kabel 60cm
  • 3403 - USB2.0 4 -Port Hub
  • 3031 - Kvinnehale 5,5x2,1 mm
  • 3029 - 2 -leder 100 'vridd kabel
  • 3604 - 10 mm hvit LED (pose med 10)
  • 3402 - USB -webkamera

Andre deler

  • 24VDC 2.0A strømforsyning
  • Skrap tre og metall fra garasjen
  • Glidelås
  • Plastbeholder med bunnen avskåret

Trinn 2: Design roboten

Design roboten
Design roboten
Design roboten
Design roboten
Design roboten
Design roboten

Vi må designe noe som kan ta en enkelt perle fra inndragingsbeholderen, plassere den under webkameraet og deretter flytte den inn i den aktuelle beholderen.

Bead Pickup

Jeg bestemte meg for å gjøre den første delen med 2 stykker rund kryssfiner, hver med et hull boret på samme sted. Bunnstykket er festet, og toppstykket er festet til en trinnmotor, som kan rotere det under en beholder fylt med perler. Når hullet beveger seg under beholderen, plukker det opp en enkelt perle. Jeg kan deretter rotere det under webkameraet, og deretter rotere det ytterligere til det stemmer overens med hullet i bunnstykket, og da faller det gjennom.

I dette bildet tester jeg at systemet kan fungere. Alt er fikset bortsett fra det øverste runde kryssfinerstykket, som er festet til en trinnmotor uten syn under. Webkameraet er ikke montert ennå. Jeg bruker bare Phidget Control Panel for å slå til motor på dette tidspunktet.

Perleoppbevaring

Den neste delen er å designe kurvsystemet for å holde hver farge. Jeg bestemte meg for å bruke en andre trinnmotor nedenfor for å støtte og rotere en rund beholder med jevnt mellomrom. Dette kan brukes til å rotere det korrekte rommet under hullet som perlen faller ut av.

Jeg bygde dette med papp og duct tape. Det viktigste her er konsistens - hvert rom skal ha samme størrelse, og det hele skal veies jevnt slik at det snurrer uten å hoppe over.

Fjernelse av perler utføres ved hjelp av et tettsittende lokk som avslører et enkelt rom om gangen, slik at perlene kan helles ut.

Kamera

Webkameraet er montert over topplaten mellom beholderen og det nedre platehullet. Dette gjør at systemet kan se på perlen før den slippes. En LED brukes til å belyse perlene under kameraet, og omgivelseslys blokkeres for å gi et konsistent lysmiljø. Dette er veldig viktig for nøyaktig fargedeteksjon, ettersom omgivelsesbelysning virkelig kan kaste bort oppfattet farge.

Stedsdeteksjon

Det er viktig for systemet å kunne detektere rotasjonen av perleseparatoren. Dette brukes til å sette opp startposisjonen ved oppstart, men også for å oppdage om trinnmotoren har gått ut av synkronisering. I systemet mitt vil en perle noen ganger jamme mens den blir hentet, og systemet trengte å kunne oppdage og håndtere denne situasjonen - ved å sikkerhetskopiere litt og prøve igjen.

Det er mange måter å håndtere dette på. Jeg bestemte meg for å bruke en 1108 magnetisk sensor, med en magnet innebygd i kanten av topplaten. Dette lar meg kontrollere posisjonen ved hver rotasjon. En bedre løsning ville trolig være en encoder på trinnmotoren, men jeg hadde en 1108 liggende, så jeg brukte det.

Fullfør roboten

På dette tidspunktet har alt blitt utarbeidet og testet. Det er på tide å montere alt pent og gå videre til skriveprogramvare.

De to trinnmotorene drives av STC1001 trinnkontrollere. En HUB000 - USB VINT -hub brukes til å kjøre trinnkontrollerne, i tillegg til å lese den magnetiske sensoren og kjøre LED -en. Webkameraet og HUB0000 er begge koblet til en liten USB -hub. En 3031 pigtail og litt ledning brukes sammen med en 24V strømforsyning for å drive motorene.

Trinn 3: Skriv kode

Image
Image

C# og Visual Studio 2015 brukes til dette prosjektet. Last ned kilden øverst på denne siden og følg med - hoveddelene er beskrevet nedenfor

Initialisering

Først må vi opprette, åpne og initialisere Phidget -objektene. Dette gjøres i formbelastningshendelsen, og Phidget fester håndterere.

private void Form1_Load (objektavsender, EventArgs e) {

/ * Initialiser og åpne Phidgets */

top. HubPort = 0; top. Attach += Top_Attach; top. Detach += Top_Detach; top. PositionChange += Top_PositionChange; topp. Åpen ();

bottom. HubPort = 1;

bottom. Attach += Bottom_Attach; bottom. Detach += Bottom_Detach; bottom. PositionChange += Bottom_PositionChange; bottom. Open ();

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 (objektavsender, Phidget22. Events. AttachEventArgs e) {

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

private void MagSensor_Attach (objektavsender, Phidget22. Events. AttachEventArgs e) {

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

private void Bottom_Attach (objektavsender, Phidget22. Events. AttachEventArgs e) {

bottomAttachedChk. Checked = true; bottom. CurrentLimit = bottomCurrentLimit; bottom. Engaged = true; bottom. VelocityLimit = bottomVelocityLimit; bottom. Acceleration = bottomAccel; bottom. DataInterval = 100; }

private void Top_Attach (objektavsender, 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 leser også inn all lagret fargeinformasjon under initialisering, slik at en tidligere kjøring kan fortsette.

Motorposisjonering

Motorhåndteringskoden består av bekvemmelighetsfunksjoner for å flytte motorene. Motorene jeg brukte er 3 200 1/16 trinn per omdreining, så jeg skapte en konstant for dette.

For toppmotoren er det 3 posisjoner vi ønsker å kunne sende til motoren til: webkameraet, hullet og posisjonsmagneten. Det er en funksjon for å reise til hver av disse posisjonene:

private void nextMagnet (boolsk vent = feil) {

dobbel posn = topp. Posisjon % trinnPerRev;

top. TargetPosition += (stepsPerRev - posn);

hvis (vent)

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

private void nextCamera (boolsk vent = feil) {

dobbel posn = topp. Posisjon % trinnPerRev; if (posn <Properties. Settings. Default.cameraOffset) top. TargetPosition += (Properties. Settings. Default.cameraOffset - posn); else top. TargetPosition + = ((Properties. Settings. Default.cameraOffset - posn) + stepsPerRev);

hvis (vent)

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

private void nextHole (boolsk vent = feil) {

dobbel posn = topp. Posisjon % trinnPerRev; if (posn <Properties. Settings. Default.holeOffset) top. TargetPosition += (Properties. Settings. Default.holeOffset - posn); else top. TargetPosition + = ((Properties. Settings. Default.holeOffset - posn) + stepsPerRev);

hvis (vent)

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

Før du starter et løp, justeres topplaten ved hjelp av den magnetiske sensoren. AlignMotor -funksjonen kan når som helst kalles for å justere topplaten. Denne funksjonen slår først platen opp til 1 full omdreining til den ser magnetdata over en terskel. Den sikkerhetskopierer deretter litt og beveger seg sakte fremover igjen, og fanger sensordata etter hvert. Til slutt setter den posisjonen til maksimal magnetdataposisjon, og tilbakestiller posisjonsforskyvningen til 0. Dermed bør maks. Magnetposisjonen alltid være på (topp. Posisjon % trinnPerRev)

Thread alignMotorThread; Boolean sawMagnet; dobbel magSensorMax = 0; private void alignMotor () {

// Finn magneten

top. DataInterval = top. MinDataInterval;

sawMagnet = false;

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

int tryCount = 0;

Prøv igjen:

top. TargetPosition += stepsPerRev;

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

hvis (! sawMagnet) {

if (tryCount> 3) {Console. WriteLine ("Justering mislyktes"); top. Engaged = false; bottom. Engaged = false; runtest = false; komme tilbake; }

tryCount ++;

Console. WriteLine ("Er vi fast? Prøver du å ta en sikkerhetskopi …"); top. TargetPosition -= 600; mens (top. IsMoving) Thread. Sleep (100);

skal prøve igjen;

}

top. VelocityLimit = -100;

magData = ny liste> (); 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 = maks. verdi;

top. TargetPosition = 0;

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

Console. WriteLine ("Juster lyktes");

}

Liste> magData;

private void magSensorCollectPositionData (objektavsender, Phidget22. Events. VoltageRatioInputSensorChangeEventArgs e) {magData. Add (ny KeyValuePair (top. Position, e. SensorValue)); }

private void magSensorStopMotor (objektavsender, Phidget22. Events. VoltageRatioInputSensorChangeEventArgs e) {

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

Til slutt styres bunnmotoren ved å sende den til en av perlebeholderposisjonene. For dette prosjektet har vi 19 stillinger. Algoritmen velger den korteste banen, og svinger enten med eller mot klokken.

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;

double posnDiff = targetPosn - currentPosn;

// Behold det som fulle trinn

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

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

hvis (vent)

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

Kamera

OpenCV brukes til å lese bilder fra webkameraet. Kameratråden startes før hovedsorteringstråden starter. Denne tråden leser kontinuerlig i bilder, beregner en gjennomsnittlig farge for en bestemt region ved hjelp av Mean og oppdaterer en global fargevariabel. Tråden bruker også HoughCircles for å prøve å oppdage enten en perle eller hullet i topplaten for å avgrense området den ser på for fargedeteksjon. Terskelen og HoughCircles -tallene ble bestemt gjennom prøving og feiling, og er sterkt avhengig av webkamera, belysning og avstand.

bool runVideo = true; bool videoRunning = false; VideoCapture -fangst; Tråd cvThread; Farge detektertColor; Boolsk oppdagelse = usann; int detectCnt = 0;

private void cvThreadFunction () {

videoRunning = false;

capture = ny VideoCapture (valgt kamera);

bruker (Vindusvindu = nytt vindu ("capture")) {

Matbilde = ny Mat (); Mat image2 = ny Mat (); while (runVideo) {capture. Read (image); hvis (image. Empty ()) brytes;

hvis (oppdager)

detectCnt ++; annet detectCnt = 0;

if (oppdager || circleDetectChecked || showDetectionImgChecked) {

Cv2. CvtColor (image, image2, ColorConversionCodes. BGR2GRAY); Matters = image2. Threshold ((double) Properties. Settings. Default.videoThresh, 255, ThresholdTypes. Binary); tresk = tres. GaussianBlur (ny OpenCvSharp. Size (9, 9), 10);

hvis (showDetectionImgChecked)

image = tresker;

if (oppdager || circleDetectChecked) {

CircleSegment perle = tres. HoughCircles (HoughMethods. Gradient, 2, /*thres. Rows/4*/ 20, 200, 100, 20, 65); if (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); if (perle [0]. Radius> = 55) {Properties. Settings. Default.x = (desimal) perle [0]. Center. X + (desimal) (perle [0]. Radius / 2); Properties. Settings. Default.y = (desimal) perle [0]. Center. Y - (desimal) (perle [0]. Radius / 2); } annet {Properties. Settings. Default.x = (desimal) perle [0]. Center. X + (desimal) (perle [0]. Radius); Properties. Settings. Default.y = (desimal) perle [0]. Center. Y - (desimal) (perle [0]. Radius); } Properties. Settings. Default.size = 15; Properties. Settings. Default.height = 15; } annet {

CircleSegment sirkler = tres. HoughCircles (HoughMethods. Gradient, 2, /*thres. Rows/4*/ 5, 200, 100, 60, 180);

hvis (sirkler. Lengde> 1) {Liste xs = sirkler. Velg (c => c. Center. X). ToList (); xs. Sort (); Liste ys = sirkler. Velg (c => c. Center. Y). ToList (); ys. Sort ();

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

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

if (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 (oppdager) {

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);

Matt beadSample = ny Mat (bilde, r);

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

image. Rectangle (r, new Scalar (0, 150, 0));

window. ShowImage (bilde);

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

videoRunning = false;

} }

private void cameraStartBtn_Click (objektavsender, EventArgs e) {

if (cameraStartBtn. Text == "start") {

cvThread = ny tråd (ny ThreadStart (cvThreadFunction)); runVideo = true; cvThread. Start (); cameraStartBtn. Text = "stopp"; mens (! videoRunning) Thread. Sleep (100);

updateColorTimer. Start ();

} annet {

runVideo = false; cvThread. Join (); cameraStartBtn. Text = "start"; }}

Farge

Nå er vi i stand til å bestemme fargen på en perle, og bestemme ut fra fargen hvilken beholder vi skal slippe den i.

Dette trinnet er avhengig av fargesammenligning. Vi ønsker å kunne skille farger fra hverandre for å begrense falskt positivt, men også tillate nok terskel til å begrense falske negativer. Å sammenligne farger er faktisk overraskende komplekst, fordi måten datamaskiner lagrer farger på som RGB, og måten mennesker oppfatter farger på, korrelerer ikke lineært. For å gjøre saken verre må fargen på lyset en farge du ser på, også tas i betraktning.

Det er komplisert algoritme for å beregne fargeforskjell. Vi bruker CIE2000, som gir et tall nær 1 hvis 2 farger ikke kan skilles fra mennesker. Vi bruker ColorMine C# -biblioteket til å gjøre disse kompliserte beregningene. En DeltaE -verdi på 5 har vist seg å tilby et godt kompromiss mellom falskt positivt og falskt negativt.

Siden det ofte er flere farger enn beholdere, er den siste posisjonen reservert som en oppsamlingsbeholder. Jeg setter vanligvis disse til side for å kjøre gjennom maskinen på et nytt pass.

Liste

farger = ny Liste (); Liste colorPanels = ny Liste (); Liste fargerTxts = ny Liste (); Liste colorCnts = ny List ();

const int numColorSpots = 18;

const int unknownColorIndex = 18; int findColorPosition (farge c) {

Console. WriteLine ("Finner farge …");

var cRGB = ny Rgb ();

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

int bestMatch = -1;

dobbel matchDelta = 100;

for (int i = 0; i <colors. Count; i ++) {

var RGB = ny Rgb ();

RGB. R = farger . R; RGB. G = farger . G; RGB. B = farger . B;

dobbelt delta = cRGB. Compare (RGB, new CieDe2000Comparison ());

// dobbelt delta = deltaE (c, farger ); Console. WriteLine ("DeltaE (" + i. ToString () + "):" + delta. ToString ()); hvis (delta <matchDelta) {matchDelta = delta; bestMatch = i; }}

if (matchDelta <5) {Console. WriteLine ("Found! (Posn:" + bestMatch + "Delta:" + matchDelta + ")"); returner bestMatch; }

if (colors. Count <numColorSpots) {Console. WriteLine ("Ny farge!"); farger. Legg til (c); this. BeginInvoke (ny handling (setBackColor), nytt objekt {colors. Count - 1}); writeOutColors (); retur (farger. Antall - 1); } annet {Console. WriteLine ("Ukjent farge!"); retur unknownColorIndex; }}

Sorteringslogikk

Sorteringsfunksjonen samler alle brikkene for å faktisk sortere perler. Denne funksjonen kjøres i en dedikert tråd; flytte topplaten, oppdage perlefargen, plassere den i en kasse, sørge for at topplaten forblir på linje, telle perlene osv. Den slutter også å løpe når oppsamlingsbeholderen blir full - Ellers ender vi bare med overfylte perler.

Thread colourTestThread; boolsk runtest = false; void colourTest () {

hvis (! top. Engaged)

top. Engaged = true;

hvis (! bottom. Engaged)

bottom. Engaged = true;

mens (runtest) {

nextMagnet (true);

Tråd. Sovn (100); prøv {if (magSensor. SensorValue <(magSensorMax - 4)) alignMotor (); } fang {alignMotor (); }

nextCamera (true);

oppdage = sant;

while (detectCnt <5) Thread. Sleep (25); Console. WriteLine ("Detect Count:" + detectCnt); oppdage = usant;

Farge c = detectColor;

this. BeginInvoke (ny handling (setColorDet), nytt objekt {c}); int i = findColorPosition (c);

SetBottomPosition (i, true);

nextHole (true); colorCnts ++; this. BeginInvoke (ny handling (setColorTxt), nytt objekt {i}); Tråd. Sovende (250);

hvis (colorCnts [unknownColorIndex]> 500) {

top. Engaged = false; bottom. Engaged = false; runtest = false; this. BeginInvoke (ny handling (setGoGreen), null); komme tilbake; }}}

private void colourTestBtn_Click (objektavsender, EventArgs e) {

if (colourTestThread == null ||! colourTestThread. IsAlive) {colourTestThread = new Thread (new ThreadStart (colourTest)); runtest = true; colourTestThread. Start (); colourTestBtn. Text = "STOPP"; colourTestBtn. BackColor = Color. Red; } annet {runtest = false; colourTestBtn. Text = "GO"; colourTestBtn. BackColor = Color. Green; }}

På dette tidspunktet har vi et arbeidsprogram. Noen biter av kode ble utelatt av artikkelen, så ta en titt på kilden for å faktisk kjøre den.

Optikkkonkurranse
Optikkkonkurranse

Andre pris i optikkonkurransen

Anbefalt: