Sisukord:
Video: Robothelmeste sorteerimine: 3 sammu (koos piltidega)
2024 Autor: John Day | [email protected]. Viimati modifitseeritud: 2024-01-30 08:49
Selles projektis ehitame roboti, mis sorteerib Perleri helmeid värvi järgi.
Olen alati tahtnud ehitada värvide sorteerimisroboti, nii et kui mu tütar Perleri helmeste meisterdamise vastu huvi tundis, nägin seda suurepärase võimalusena.
Perleri helmeid kasutatakse sulatatud kunstiprojektide loomiseks, asetades paljud helmed tahvlile ja sulatades need seejärel triikrauaga kokku. Tavaliselt ostate neid helmeid hiiglaslikes 22 000 helmestega värvipakendites ja kulutate palju aega soovitud värvi otsimiseks, nii et arvasin, et nende sorteerimine suurendab kunsti efektiivsust.
Töötan ettevõttes Phidgets Inc., seega kasutasin selle projekti jaoks enamasti Phidgets'i, kuid seda saab teha mis tahes sobiva riistvara abil.
Samm: riistvara
Siin on see, mida ma selle ehitamiseks kasutasin. Ehitasin selle 100% osadega saidilt phidgets.com ja asjad, mis mul maja ümber lebasid.
Phidgetsi lauad, mootorid, riistvara
- HUB0000 - VINT Hub Phidget
- 1108 - Magnetandur
- 2x STC1001 - 2,5A Stepper Phidget
- 2x 3324 - 42STH38 NEMA -17 bipolaarne käigukastita samm
- 3x 3002 - Phidget kaabel 60cm
- 3403 - USB2.0 4 -pordiline jaotur
- 3031 - emane pats 5,5x2,1 mm
- 3029 - 2 juhtmega 100 'keerdkaabel
- 3604 - 10 mm valge LED (kott 10)
- 3402 - USB veebikaamera
Muud osad
- 24VDC 2.0A toiteallikas
- Garaaži puit ja metall
- Tõmblukud
- Plastmahuti, mille põhi on ära lõigatud
Samm: kujundage robot
Peame kavandama midagi, mis suudab sisendpunkrist võtta ühe helme, asetada see veebikaamera alla ja seejärel viia see vastavasse prügikasti.
Helmeste pikap
Otsustasin teha 1. osa 2 tükki ümmarguse vineeriga, millest igaühes on samasse kohta puuritud auk. Alumine tükk on fikseeritud ja ülemine osa on kinnitatud samm -mootori külge, mis saab seda pöörata helmestega täidetud punkri all. Kui auk liigub punkri alla, võtab see üles ühe helme. Seejärel saan seda veebikaamera all pöörata ja seejärel edasi pöörata, kuni see langeb kokku alumise osa auguga ja sel hetkel kukub see läbi.
Sellel pildil katsetan, kas süsteem töötab. Kõik on fikseeritud, välja arvatud ülemine ümmargune vineeritükk, mis on altpoolt vaateväljast kinnitatud samm -mootori külge. Veebikaamerat pole veel paigaldatud. Ma kasutan praegu Phidget'i juhtpaneeli, et mootori poole pöörduda.
Helmeste hoiustamine
Järgmine osa on prügikasti süsteemi kujundamine iga värvi hoidmiseks. Otsustasin kasutada allpool asuvat teist samm -mootorit, et toestada ja pöörata ümmargune anum ühtlaste vahedega sektsioonidega. Seda saab kasutada õige lahtri pööramiseks augu all, millest helmes välja kukub.
Ma ehitasin selle papist ja kleeplindist. Siin on kõige tähtsam järjepidevus - iga sektsioon peaks olema sama suur ja kogu asi peaks olema ühtlaselt kaalutud, nii et see pöörleb ilma vahele jätmata.
Helmeste eemaldamine toimub tihedalt liibuva kaane abil, mis paljastab korraga ühe sektsiooni, nii et helmeid saab välja valada.
Kaamera
Veebikaamera on paigaldatud ülemise plaadi kohale punkri ja alumise plaadi ava vahel. See võimaldab süsteemil enne helme kukutamist pilku vaadata. Kaamera all olevate helmeste valgustamiseks kasutatakse LED -i ja ümbritsev valgus on blokeeritud, et tagada ühtlane valgustuskeskkond. See on värvide täpseks tuvastamiseks väga oluline, kuna ümbritsev valgustus võib tajutud värvi tõepoolest ära visata.
Asukoha tuvastamine
On oluline, et süsteem oleks võimeline tuvastama helmeste eraldaja pöörlemist. Seda kasutatakse algseisundi seadistamiseks käivitamisel, aga ka tuvastamaks, kas samm -mootor on sünkroonist väljunud. Minu süsteemis jääb rant mõnikord ülesvõtmise ajal kinni ja süsteem pidi seda olukorda tuvastama ja sellega toime tulema - natuke varundades ja uuesti proovides.
Selle lahendamiseks on palju viise. Otsustasin kasutada 1108 magnetandurit, mille ülemise plaadi serva on kinnitatud magnet. See võimaldab mul kontrollida positsiooni igal pöörlemisel. Parem lahendus oleks tõenäoliselt samm -mootoril olev kodeerija, kuid mul oli 1108 ümberringi, nii et ma kasutasin seda.
Lõpeta robot
Sel hetkel on kõik välja töötatud ja testitud. On aeg kõik ilusti kokku panna ja kirjutamistarkvarale üle minna.
Kahte samm -mootorit juhivad STC1001 samm -kontrollerid. HUB000 - USB VINT jaoturit kasutatakse samm -kontrollerite käitamiseks, samuti magnetanduri lugemiseks ja LED -i juhtimiseks. Veebikaamera ja HUB0000 on mõlemad ühendatud väikese USB -jaoturiga. Mootorite toiteks kasutatakse 3031 patsi ja mõnda traati koos 24 V toiteallikaga.
Samm: kirjutage kood
Selle projekti jaoks kasutatakse C# ja Visual Studio 2015. Laadige allikas alla selle lehe ülaosas ja järgige - peamised jaotised on toodud allpool
Initsialiseerimine
Esiteks peame looma, avama ja initsialiseerima Phidgeti objektid. Seda tehakse vormi laadimisüritusel ja Phidgeti manuste töötlejad.
private void Form1_Load (objekti saatja, EventArgs e) {
/ * Initsialiseeri ja ava Phidgets */
top. HubPort = 0; top. Attach += Top_Attach; top. Detach += Top_Detach; top. PositionChange += Top_PositionChange; ülaosa. Avatud ();
alt. HubPort = 1;
bottom. Attach += Alumine_kinnitus; bottom. Detach += Bottom_Detach; bottom. PositionChange += Bottom_PositionChange; alt. Avatud ();
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. Kanal = 0; led. Attach += Led_Attach; led. Detach += Led_Detach; led. Open (); }
private void Led_Attach (objekti saatja, Phidget22. Events. AttachEventArgs e) {
ledAttachedChk. Checked = true; led. Riik = tõsi; ledChk. Checked = true; }
private void MagSensor_Attach (objekti saatja, Phidget22. Events. AttachEventArgs e) {
magSensorAttachedChk. Checked = true; magSensor. SensorType = VoltageRatioSensorType. PN_1108; magSensor. DataInterval = 16; }
private void Bottom_Attach (objekti saatja, Phidget22. Events. AttachEventArgs e) {
bottomAttachedChk. Checked = true; bottom. CurrentLimit = altCurrentLimit; alumine. Tegeleb = tõsi; bottom. VelocityLimit = altVelocityLimit; alt. Kiirendus = bottomAccel; alt. DataInterval = 100; }
private void Top_Attach (objekti saatja, Phidget22. Events. AttachEventArgs e) {
topAttachedChk. Checked = tõsi; top. CurrentLimit = topCurrentLimit; ülaosas. Tegevusega = tõsi; top. RescaleFactor = -1; top. VelocityLimit = -topVelocityLimit; top. Kiirendus = -topAccel; top. DataInterval = 100; }
Samuti loeme initsialiseerimise ajal sisse kõik salvestatud värvilised andmed, nii et eelmist käiku saab jätkata.
Mootori positsioneerimine
Mootori käsitsemiskood koosneb mugavusfunktsioonidest mootorite teisaldamiseks. Mootorid, mida kasutasin, on 3 200 1/16 sammu pöörde kohta, seega lõin selleks konstandi.
Ülemise mootori jaoks on 3 positsiooni, mida tahame mootorile saata: veebikaamera, auk ja positsioneerimismagnet. Kõigile nendele positsioonidele reisimiseks on funktsioon:
private void nextMagnet (Boolean wait = false) {
double posn = top. Position % stepsPerRev;
top. TargetPosition += (stepsPerRev - posn);
kui (oota)
samas (top. IsMoving) Thread. Une (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);
kui (oota)
samas (top. IsMoving) Thread. Une (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);
kui (oota)
samas (top. IsMoving) Thread. Une (50); }
Enne jooksu alustamist joondatakse ülemine plaat magnetanduri abil. Ülemise plaadi joondamiseks saab igal ajal välja kutsuda funktsiooni alignMotor. See funktsioon pöörab kõigepealt plaadi kiiresti kuni 1 täispöörde, kuni see näeb magnetandmeid üle künnise. Seejärel varundab see veidi ja liigub aeglaselt edasi, jäädvustades anduri andmeid. Lõpuks seab see positsiooni maksimaalsele magnetandmete asukohale ja lähtestab positsiooni nihke väärtuseks 0. Seega peaks maksimaalne magnetasend olema alati (ülaosas. Positsioon % stepsPerRev)
Teema joondusMotorThread; Boolean sawMagnet; topelt magSensorMax = 0; private void alignMotor () {
// Leidke magnet
top. DataInterval = top. MinDataInterval;
sawMagnet = vale;
magSensor. SensorChange += magSensorStopMotor; top. VelocityLimit = -1000;
int tryCount = 0;
proovi uuesti:
top. TargetPosition += stepsPerRev;
while (top. IsMoving &&! sawMagnet) Thread. Une (25);
kui (! sawMagnet) {
if (tryCount> 3) {Console. WriteLine ("Joondamine nurjus"); ülaosas. Tegevusega = vale; alt. Kangas = vale; runtest = vale; tagasipöördumine; }
tryCount ++;
Console. WriteLine ("Kas me oleme ummikus? Proovime varundamist …"); top. TargetPosition -= 600; samas (top. IsMoving) Thread. Une (100);
proovige uuesti;
}
top. VelocityLimit = -100;
magData = uus nimekiri> (); magSensor. SensorChange += magSensorCollectPositionData; top. Sihtpositsioon += 300; samas (top. IsMoving) Thread. Une (100);
magSensor. SensorChange -= magSensorCollectPositionData;
top. VelocityLimit = -topVelocityLimit;
KeyValuePair max = magData [0];
foreach (KeyValuePair paar magData -s) if (pair. Value> max. Value) max = paar;
top. AddPositionOffset (-max. Võti);
magSensorMax = max. Value;
top. TargetPosition = 0;
samas (top. IsMoving) Thread. Une (100);
Console. WriteLine ("Joondamine õnnestus");
}
Loend> magData;
private void magSensorCollectPositionData (objekti saatja, Phidget22. Events. VoltageRatioInputSensorChangeEventArgs e) {magData. Add (uus KeyValuePair (top. Position, e. SensorValue)); }
private void magSensorStopMotor (objekti saatja, Phidget22. Events. VoltageRatioInputSensorChangeEventArgs e) {
if (top. IsMoving && e. SensorValue> 5) {top. TargetPosition = top. Position - 300; magSensor. SensorChange -= magSensorStopMotor; sawMagnet = tõsi; }}
Lõpuks juhitakse alumist mootorit, saates selle ühte rantkonteineri asendisse. Selle projekti jaoks on meil 19 ametikohta. Algoritm valib lühima tee ja pöörleb kas päripäeva või vastupäeva.
private int BottomPosition {get {int posn = (int) bottom. Position % stepsPerRev; if (posn <0) posn += stepsPerRev;
return (int) Math. Round ((((posn * beadCompartments) / (topelt) sammudPerRev));
} }
private void SetBottomPosition (int posn, bool wait = false) {
posn = posn % beadCompartments; topeltmärkPosn = (posn * stepsPerRev) / beadCompartments;
topeltvoolPosn = alumine. Positsioon % sammudPerRev;
topelt posnDiff = targetPosn - praegunePosn;
// Hoidke seda täieliku sammuna
posnDiff = ((int) (posnDiff / 16)) * 16;
if (posnDiff <= 1600) bottom. TargetPosition += posnDiff; muu alt. TargetPosition - = (stepsPerRev - posnDiff);
kui (oota)
samas (alt. IsMoving) Thread. Une (50); }
Kaamera
OpenCV -d kasutatakse veebikaamerast piltide lugemiseks. Kaamera niit käivitatakse enne peamise sortimisniidi alustamist. See lõim loeb pidevalt pilte, arvutab keskmise väärtuse konkreetse piirkonna jaoks keskmise abil ja värskendab globaalset värvimuutujat. Niit kasutab ka HoughCircles'i, et proovida tuvastada kas helmeid või ülemise plaadi auku, et täpsustada piirkonda, mida värvide tuvastamiseks vaadatakse. Läve ja HoughCirclesi numbrid määrati katse -eksituse meetodil ning sõltuvad suuresti veebikaamerast, valgustusest ja vahekaugusest.
bool runVideo = tõsi; bool videoRunning = false; VideoCapture püüdmine; Teema cvThread; Värv tuvastatudVärv; Boolean tuvastamine = vale; int avastamaCnt = 0;
private void cvThreadFunction () {
videoRunning = vale;
püüdmine = uus VideoCapture (valitud kaamera);
kasutades (Aknaaken = uus aken ("püüdmine")) {
Mat pilt = uus Mat (); Mat pilt2 = uus Mat (); while (runVideo) {capture. Read (pilt); kui (pilt. Tühi ()) murda;
kui (tuvastab)
detectCnt ++; muidu detectCnt = 0;
if (tuvastamine || circleDetectChecked || showDetectionImgChecked) {
Cv2. CvtColor (pilt, pilt2, ColorConversionCodes. BGR2GRAY); Mat thres = image2. Threshold ((double) Properties. Settings. Default.videoThresh, 255, ThresholdTypes. Binary); thres = thres. GaussianBlur (uus OpenCvSharp. Size (9, 9), 10);
if (showDetectionImgChecked)
pilt = thres;
if (tuvastamine || circleDetectChecked) {
CircleSegment rant = thres. HoughCircles (HoughMethods. Gradient, 2, /*thres. Rows/4*/ 20, 200, 100, 20, 65); if (bead. Length> = 1) {image. Circle (rant [0]. Keskus, 3, uus skalaar (0, 100, 0), -1); pilt. Ring (rant [0]. Keskus, (int) rant [0]. Kiirgus, uus skalaar (0, 0, 255), 3); if (rant [0]. Radius> = 55) {Properties. Settings. Default.x = (kümnend) rant [0]. Center. X + (kümnendkoht) (rant [0]. Radius / 2); Properties. Settings. Default.y = (kümnendarv) rant [0]. Center. Y - (kümnendkoht) (rant [0]. Radius / 2); } else {Properties. Settings. Default.x = (kümnendarv) rant [0]. Keskus. X + (kümnendkoht) (rant [0]. Radius); Properties. Settings. Default.y = (kümnend) rant [0]. Center. Y - (kümnendkoht) (rant [0]. Radius); } Properties. Settings. Default.size = 15; Properties. Settings. Default.height = 15; } muud {
CircleSegment ringid = thres. HoughCircles (HoughMethods. Gradient, 2, /*thres. Rows/4*/ 5, 200, 100, 60, 180);
if (ringid. Pikkus> 1) {Nimekiri xs = ringid. Valige (c => c. Center. X). ToList (); xs. Sort (); Nimekiri ys = ringid. Valige (c => c. Center. Y). ToList (); ys. Sort ();
int mediaanX = (int) xs [xs. Count / 2];
int mediaanY = (int) ys [ys. Count / 2];
if (mediaanX> pilt. Laius - 15)
mediaanX = pilt. Laius - 15; if (mediaanY> pilt. Kõrgus - 15) mediaanY = pilt. Kõrgus - 15;
pilt. ring (mediaanX, mediaanY, 100, uus skalaar (0, 0, 150), 3);
kui (tuvastab) {
Properties. Settings. Default.x = mediaanX - 7; Properties. Settings. Default.y = mediaanY - 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 = uus Mat (pilt, r);
Scalar avgColor = Cv2. Mean (beadSample); detectColor = Color. FromArgb ((int) avgColor [2], (int) avgColor [1], (int) avgColor [0]);
pilt. Ristkülik (r, uus skalaar (0, 150, 0));
window. ShowImage (pilt);
Cv2. OotaKey (1); videoRunning = tõsi; }
videoRunning = vale;
} }
private void cameraStartBtn_Click (objekti saatja, EventArgs e) {
if (cameraStartBtn. Text == "start") {
cvThread = new Thread (uus ThreadStart (cvThreadFunction)); runVideo = tõsi; cvThread. Start (); cameraStartBtn. Text = "peatus"; while (! videoRunning) Thread. Une (100);
updateColorTimer. Start ();
} muud {
runVideo = vale; cvThread. Join (); cameraStartBtn. Text = "algus"; }}
Värv
Nüüd saame määrata helmeste värvi ja selle värvi põhjal otsustada, millisesse anumasse see visata.
See samm põhineb värvide võrdlemisel. Tahame eristada värve, et piirata valepositiivseid, kuid lubada ka piisavalt piirmäära valenegatiivide piiramiseks. Värvide võrdlemine on tegelikult üllatavalt keeruline, sest see, kuidas arvutid värve RGB -na salvestavad ja kuidas inimesed värve tajuvad, ei korreleeru lineaarselt. Asja teeb veelgi hullemaks see, et arvesse tuleb võtta ka valguse värvi, mille all värvi vaadatakse.
Värvivahe arvutamiseks on keeruline algoritm. Kasutame CIE2000, mis väljastab numbri 1 lähedal, kui 2 värvi poleks inimesele eristatavad. Nende keeruliste arvutuste tegemiseks kasutame teeki ColorMine C#. Leiti, et DeltaE väärtus 5 pakub head kompromissi valepositiivse ja vale -negatiivse vahel.
Kuna värve on sageli rohkem kui konteinereid, on viimane koht reserveeritud prügikasti kujul. Üldiselt panin need kõrvale, et masin teist korda läbida.
Nimekiri
värvid = uus loend (); loendi värvipaneelid = uus loend (); Nimekirja värvidTxts = uus Loend (); Loetelu colorCnts = uus Loend ();
const int numColorSpots = 18;
const int teadmataColorIndex = 18; int findColorPosition (värv c) {
Console. WriteLine ("Värvi leidmine …");
var cRGB = uus Rgb ();
cRGB. R = c. R; cRGB. G = c. G; cRGB. B = c. B;
int bestMatch = -1;
kahekordne vasteDelta = 100;
jaoks (int i = 0; i <värvid.arv; i ++) {
var RGB = uus Rgb ();
RGB. R = värvid . R; RGB. G = värvid . G; RGB. B = värvid . B;
topelt delta = cRGB. Compare (RGB, uus CieDe2000Comparison ());
// topelt delta = deltaE (c, värvid ); Console. WriteLine ("DeltaE (" + i. ToString () + "):" + delta. ToString ()); if (delta <matchDelta) {matchDelta = delta; bestMatch = i; }}
if (matchDelta <5) {Console. WriteLine ("Leitud! (Posn:" + bestMatch + "Delta:" + matchDelta + ")"); return bestMatch; }
if (colors. Count <numColorSpots) {Console. WriteLine ("Uus värv!"); värvid. Lisa (c); this. BeginInvoke (uus toiming (setBackColor), uus objekt {colors. Count - 1}); writeOutColors (); return (värvid. Loend - 1); } else {Console. WriteLine ("Tundmatu värv!"); tagastada tundmatuColorIndex; }}
Sortimisloogika
Sortimisfunktsioon ühendab kõik tükid pärlite päriselt sortimiseks. See funktsioon töötab spetsiaalses lõimes; ülemise plaadi liigutamine, helmeste värvi tuvastamine, prügikasti asetamine, pealmise plaadi joondamise jälgimine, helmeste loendamine jne. Samuti peatub see jooksmine, kui prügikast saab täis - muidu satume lihtsalt ülevoolavate helmestega.
Lõime värvusTestThread; Boolean runtest = false; void colourTest () {
kui (! top. Engaged)
ülaosas. Tegevusega = tõsi;
kui (! alt. Kihlatud)
alumine. Tegeleb = tõsi;
while (runtest) {
nextMagnet (tõsi);
Teema. Une (100); proovige {if (magSensor. SensorValue <(magSensorMax - 4)) alignMotor (); } püüdma {alignMotor (); }
nextCamera (tõsi);
avastamine = tõsi;
while (detekteerima <5) Thread. Sleep (25); Console. WriteLine ("Detect Count:" + detectCnt); avastamine = vale;
Värv c = tuvastatudVärv;
this. BeginInvoke (uus toiming (setColorDet), uus objekt {c}); int i = leidaVärviPositsioon (c);
SetBottomPosition (i, true);
nextHole (tõsi); colorCnts ++; this. BeginInvoke (uus toiming (setColorTxt), uus objekt {i}); Teema. Une (250);
if (colorCnts [unknownColorIndex]> 500) {
ülaosas. Tegevusega = vale; alt. Kangas = vale; runtest = vale; this. BeginInvoke (uus toiming (setGoGreen), null); tagasipöördumine; }}}
private void colourTestBtn_Click (objekti saatja, EventArgs e) {
if (colourTestThread == null ||! colourTestThread. IsAlive) {colourTestThread = new Thread (new ThreadStart (colourTest)); runtest = tõsi; colourTestThread. Start (); colourTestBtn. Text = "STOP"; colourTestBtn. BackColor = Värv. Punane; } else {runtest = false; colourTestBtn. Text = "Mine"; colourTestBtn. BackColor = Värv. Roheline; }}
Sel hetkel on meil tööprogramm. Mõned koodibitid jäeti artiklist välja, nii et vaadake allikat, et seda tegelikult käivitada.
Optikavõistluse teine auhind
Soovitan:
Castle Planter (koos Tinkercadi koodiplokkidega): 25 sammu (koos piltidega)
Castle Planter (koos Tinkercadi koodiplokkidega): selle disaini teostamine võttis mul üsna kaua aega ja kuna minu kodeerimisoskus on vähemalt öeldes piiratud, loodan, et see õnnestus hästi :) Kasutades juhiseid, peaksite saama taaslooge selle disaini kõik aspektid ilma
Diy makroobjektiiv koos teravustamisega (erinev kui kõik muud DIY makroobjektiivid): 4 sammu (koos piltidega)
Diy makroobjektiiv koos teravustamisega (erinev kui kõik muud DIY makroobjektiivid): olen näinud palju inimesi, kes teevad makroläätsi tavalise komplekti objektiiviga (tavaliselt 18–55 mm). Enamik neist on objektiiv, mis on lihtsalt tagurpidi kaamera külge kinnitatud või esielement eemaldatud. Mõlemal variandil on varjuküljed. Objektiivi kinnitamiseks
Kitroniku leiutajakomplekti kasutamine koos Adafruit CLUE -ga: 4 sammu (koos piltidega)
Kitroniku leiutajakomplekti kasutamine koos Adafruit CLUE -ga: Kitronik Leiutaja komplekt BBC micro: bit jaoks on suurepärane sissejuhatus elektroonikaga mikrokontrolleritele, kasutades leivaplaati. See komplekti versioon on mõeldud kasutamiseks koos odava BBC mikro: bitiga. Üksikasjalik õpetusraamat, mis tuleb
Värvide sorteerimine: 6 sammu
Värvisorteerija: selle värvisorteerijate eesmärk on liigutada m & ms erinevatesse kuhjadesse nende värvi alusel
Sorteerimiskast - prügikasti tuvastamine ja sorteerimine: 9 sammu
Sorteerimiskast - leidke ja sorteerige oma prügi: kas olete kunagi näinud kedagi, kes ei tee ringlusse või ei tee seda halvasti? Kas olete kunagi soovinud masinat, mis saaks teie jaoks ringlusse võtta? Jätkake meie projekti lugemist, te ei kahetse seda! Sorter bin on projekt, millel on selge motivatsioon aidata