Indholdsfortegnelse:

Sortering af robotperler: 3 trin (med billeder)
Sortering af robotperler: 3 trin (med billeder)

Video: Sortering af robotperler: 3 trin (med billeder)

Video: Sortering af robotperler: 3 trin (med billeder)
Video: Most Satisfying Machines and Ingenious Tools #3 2024, Juli
Anonim
Image
Image
Robotsortering af perler
Robotsortering af perler
Robotsortering af perler
Robotsortering af perler
Robotsortering af perler
Robotsortering af perler

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

Design robotten
Design robotten
Design robotten
Design robotten
Design robotten
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

Image
Image

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.

Optik konkurrence
Optik konkurrence

Anden pris i optikkonkurrencen

Anbefalede: