Obsah:
Video: Robotické triedenie korálkov: 3 kroky (s obrázkami)
2024 Autor: John Day | [email protected]. Naposledy zmenené: 2024-01-30 11:59
V tomto projekte budeme stavať robota na triedenie perlerových perličiek podľa farby.
Vždy som chcel postaviť robot na triedenie farieb, takže keď sa moja dcéra začala zaujímať o výrobu perličiek Perler, považovala som to za perfektnú príležitosť.
Perlerove korálky sa používajú na vytváranie kondenzovaných umeleckých projektov tak, že sa mnoho korálikov položí na nástenku a potom sa roztavia spolu so železom. Spravidla kupujete tieto korálky v obrovských 22 000 baleniach zmiešaných farieb a strávite veľa času hľadaním požadovanej farby, preto som si myslel, že ich triedením zvýšite efektivitu umenia.
Pracujem pre Phidgets Inc., takže som pre tento projekt používal väčšinou Phidgets - ale to sa dalo urobiť pomocou akéhokoľvek vhodného hardvéru.
Krok 1: Hardvér
Tu je to, čo som použil na stavbu tohto. Postavil som ho na 100% s dielmi z phidgets.com a s vecami, ktoré som mal povaľované po dome.
Phidgets Dosky, motory, hardvér
- HUB0000 - VINT Hub Phidget
- 1108 - Magnetický snímač
- 2x STC1001 - 2,5A stepper Phidget
- 2x 3324 - 42STH38 NEMA -17 bipolárny bezstupňový stepper
- 3x 3002 - Kábel Phidget 60 cm
- 3403 - 4portový rozbočovač USB2.0
- 3031 - ženský pigtail 5,5 x 2,1 mm
- 3029 - 2žilový 100 'krútený kábel
- 3604 - 10 mm biela LED (vrecko po 10)
- 3402 - Webová kamera USB
Ostatné diely
- Napájanie 24VDC 2,0A
- Z garáže zošrotujte drevo a kov
- Kravaty na zips
- Plastová nádoba s odrezaným dnom
Krok 2: Navrhnite robota
Musíme navrhnúť niečo, čo môže zo vstupného zásobníka vybrať jednu guľôčku, umiestniť ho pod webovú kameru a potom ho presunúť do príslušného koša.
Odber perličiek
Rozhodol som sa urobiť 1. časť s 2 kusmi okrúhlej preglejky, každý s dierou vyvŕtanou na rovnakom mieste. Spodný diel je pevný a horný diel je pripevnený k krokovému motoru, ktorý ho môže otáčať pod násypkou naplnenou perličkami. Keď diera prejde pod násypku, zachytí jeden korálik. Potom ho môžem otočiť pod webovou kamerou a potom ho ďalej otáčať, kým sa nezhoduje s otvorom v spodnom diele, v ktorom mieste prepadne.
Na tomto obrázku testujem, či systém môže fungovať. Všetko je pevné, okrem horného okrúhleho kusu preglejky, ktorý je zospodu pripevnený k krokovému motoru. Webová kamera ešte nebola namontovaná. V tomto mieste práve používam ovládací panel Phidget na prepnutie na motor.
Skladovanie korálikov
Ďalšou časťou je návrh systému košov na uchytenie každej farby. Rozhodol som sa použiť druhý krokový motor nižšie na podoprenie a otočenie okrúhlej nádoby s rovnomerne rozmiestnenými oddeleniami. To možno použiť na otočenie správnej priehradky pod otvorom, z ktorého guľôčka vypadne.
Postavil som to pomocou lepenky a lepiacej pásky. Najdôležitejšou vecou je tu konzistencia - každé oddelenie by malo mať rovnakú veľkosť a celé by malo byť rovnomerne vyvážené, aby sa točilo bez preskakovania.
Odstránenie perličiek sa vykonáva pomocou tesne priliehajúceho veka, ktoré odhalí jednu komoru naraz, takže guľôčky je možné vyliať.
fotoaparát
Webová kamera je namontovaná nad hornou doskou medzi zásobníkom a umiestnením otvoru v spodnej doske. To umožňuje systému pozrieť sa na guľôčku pred jej odhodením. LED diódy sa používajú na osvetlenie guľôčok pod kamerou a okolité svetlo je blokované, aby sa zaistilo konzistentné osvetlenie. To je veľmi dôležité pre presnú detekciu farieb, pretože okolité osvetlenie môže skutočne odhodiť vnímanú farbu.
Detekcia polohy
Je dôležité, aby systém dokázal detekovať otáčanie oddeľovača guľôčok. Používa sa na nastavenie počiatočnej polohy pri spustení, ale aj na zistenie, či sa krokový motor synchronizoval. V mojom systéme sa pri preberaní niekedy zasekne guľôčka a systém potreboval byť schopný túto situáciu zistiť a zvládnuť - tak, že trochu zazálohujem a pokúsim sa znova.
Existuje mnoho spôsobov, ako to zvládnuť. Rozhodol som sa použiť magnetický snímač 1108 s magnetom vloženým do okraja hornej dosky. To mi umožňuje overiť polohu pri každom otočení. Lepším riešením by pravdepodobne bol kodér na krokovom motore, ale mal som ležiaci 1108, takže som to použil.
Dokončite robota
V tomto bode je všetko rozpracované a testované. Je načase všetko pekne pripojiť a prejsť na softvér na písanie.
2 krokové motory sú poháňané krokovými regulátormi STC1001. Rozbočovač HUB000 - USB VINT sa používa na spustenie krokových ovládačov, ako aj na čítanie magnetického senzora a ovládanie LED diódy. Webová kamera a HUB0000 sú pripojené k malému rozbočovaču USB. Na napájanie motorov sa používa pigtail 3031 a nejaký drôt spolu s napájaním 24 V.
Krok 3: Napíšte kód
Na tento projekt sa používajú C# a Visual Studio 2015. Stiahnite si zdroj v hornej časti tejto stránky a postupujte podľa neho - hlavné sekcie sú uvedené nižšie
Inicializácia
Najprv musíme vytvoriť, otvoriť a inicializovať objekty Phidget. To sa deje v prípade načítania formulára a obslužných rutín pripojenia Phidget.
private void Form1_Load (odosielateľ objektu, EventArgs e) {
/ * Inicializujte a otvorte Phidgets */
top. HubPort = 0; top. Attach += Top_Attach; top. Detach += Top_Detach; top. PositionChange += Top_PositionChange; hore. Otvorte ();
dno. HubPort = 1;
dole. Attach += Bottom_Attach; bottom. Detach += Bottom_Detach; bottom. PositionChange += Bottom_PositionChange; dno. Otvorené ();
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. Kanál = 0; led. Attach += Led_Attach; led. Detach += Led_Detach; led. Open (); }
private void Led_Attach (odosielateľ objektu, Phidget22. Events. AttachEventArgs e) {
ledAttachedChk. Checked = true; led. State = true; ledChk. Checked = true; }
private void MagSensor_Attach (odosielateľ objektu, Phidget22. Events. AttachEventArgs e) {
magSensorAttachedChk. Checked = true; magSensor. SensorType = VoltageRatioSensorType. PN_1108; magSensor. DataInterval = 16; }
private void Bottom_Attach (odosielateľ objektu, Phidget22. Events. AttachEventArgs e) {
bottomAttachedChk. Checked = true; bottom. CurrentLimit = bottomCurrentLimit; bottom. Engaged = true; bottom. VelocityLimit = bottomVelocityLimit; bottom. Acceleration = bottomAccel; bottom. DataInterval = 100; }
súkromné neplatné Top_Attach (odosielateľ objektu, 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; }
Počas inicializácie tiež čítame všetky uložené informácie o farbe, takže predchádzajúce spustenie môže pokračovať.
Polohovanie motora
Kód manipulácie s motorom pozostáva z praktických funkcií na pohyb motorov. Motory, ktoré som použil, majú 3 200 1/16 krokov na otáčku, takže som na to vytvoril konštantu.
Pokiaľ ide o horný motor, existujú tri polohy, do ktorých chceme byť schopní odoslať motor: webová kamera, otvor a polohovací magnet. K dispozícii je funkcia na cestovanie do každej z týchto polôh:
súkromná prázdnota nextMagnet (booleovské čakanie = nepravda) {
double posn = top. Position % stepsPerRev;
top. TargetPosition += (stepsPerRev - posn);
ak (čakať)
while (top. IsMoving) Thread. Sleep (50); }
súkromná prázdnota nextCamera (booleovské čakanie = nepravda) {
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);
ak (čakať)
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);
ak (čakať)
while (top. IsMoving) Thread. Sleep (50); }
Pred spustením behu je horná doska zarovnaná pomocou magnetického senzora. Na vyrovnanie hornej dosky je možné kedykoľvek vyvolať funkciu alignMotor. Táto funkcia najskôr rýchlo otočí tanierom až o 1 celú otáčku, kým neuvidí údaje magnetu nad prahovou hodnotou. Potom trochu cúva a pomaly sa pohybuje dopredu, pričom zaznamenáva údaje senzora, ako to ide. Nakoniec nastaví polohu na maximálne umiestnenie údajov magnetu a vynuluje posun polohy na 0. Preto by maximálna poloha magnetu mala byť vždy na (hore. Poloha % krokovPerRev)
Zarovnanie závitu Motorový závit; Boolean sawMagnet; dvojitý magSensorMax = 0; private void alignMotor () {
// Nájdi magnet
top. DataInterval = top. MinDataInterval;
sawMagnet = nepravda;
magSensor. SensorChange += magSensorStopMotor; top. VelocityLimit = -1000;
int tryCount = 0;
skús znova:
top. TargetPosition += stepsPerRev;
while (top. IsMoving &&! sawMagnet) Thread. Sleep (25);
if (! sawMagnet) {
if (tryCount> 3) {Console. WriteLine ("Zarovnanie zlyhalo"); top. Engaged = false; bottom. Engaged = false; runtest = false; návrat; }
tryCount ++;
Console. WriteLine ("Sme zaseknutí? Skúšate zálohu …"); top. TargetPosition -= 600; while (top. IsMoving) Thread. Sleep (100);
ísť znova;
}
top. VelocityLimit = -100;
magData = nový zoznam> (); magSensor. SensorChange += magSensorCollectPositionData; top. TargetPosition += 300; while (top. IsMoving) Thread. Sleep (100);
magSensor. SensorChange -= magSensorCollectPositionData;
top. VelocityLimit = -topVelocityLimit;
KeyValuePair max = magData [0];
foreach (pár KeyValuePair v magData) if (pair. Value> max. Value) max = pair;
top. AddPositionOffset (-max. Key);
magSensorMax = max.hodnota;
top. TargetPosition = 0;
while (top. IsMoving) Thread. Sleep (100);
Console. WriteLine ("Zarovnanie bolo úspešné");
}
Zoznam> magData;
private void magSensorCollectPositionData (odosielateľ objektu, Phidget22. Events. VoltageRatioInputSensorChangeEventArgs e) {magData. Add (nový KeyValuePair (top. Position, e. SensorValue)); }
private void magSensorStopMotor (odosielateľ objektu, Phidget22. Events. VoltageRatioInputSensorChangeEventArgs e) {
if (top. IsMoving && e. SensorValue> 5) {top. TargetPosition = top. Position - 300; magSensor. SensorChange -= magSensorStopMotor; sawMagnet = pravda; }}
Nakoniec je spodný motor ovládaný jeho odoslaním do jednej z polôh nádoby na perličky. Pre tento projekt máme 19 pozícií. Algoritmus vyberá najkratšiu cestu a otáča sa v smere alebo proti smeru hodinových ručičiek.
private int BottomPosition {get {int posn = (int) bottom. Position % stepsPerRev; if (posn <0) posn += stepsPerRev;
return (int) Math. Round (((posn * beadCompartments) / (double) stepsPerRev));
} }
private void SetBottomPosition (int posn, bool wait = false) {
posn = posn % beadCompartments; double targetPosn = (posn * stepsPerRev) / beadCompartments;
dvojitý prúdPosn = dno. Pozícia % krokyPerRev;
double posnDiff = targetPosn - currentPosn;
// Nechajte si to ako úplné kroky
posnDiff = ((int) (posnDiff / 16)) * 16;
if (posnDiff <= 1600) bottom. TargetPosition += posnDiff; inak dole. TargetPosition - = (stepsPerRev - posnDiff);
ak (čakať)
while (bottom. IsMoving) Thread. Sleep (50); }
fotoaparát
OpenCV slúži na čítanie obrázkov z webovej kamery. Vlákno kamery sa spustí pred spustením hlavného vlákna triedenia. Toto vlákno nepretržite číta obrázky, pomocou priemeru vypočítava priemernú farbu pre konkrétnu oblasť a aktualizuje globálnu farebnú premennú. Vlákno tiež používa HoughCircles, aby sa pokúsil detegovať buď guľôčku, alebo otvor v hornej doske, na spresnenie oblasti, na ktorú sa zameriava na detekciu farieb. Prahové hodnoty a čísla HoughCircles boli určené pokusom a omylom a vo veľkej miere závisia od webovej kamery, osvetlenia a rozstupov.
bool runVideo = true; bool videoRunning = false; Zachytávanie VideoCapture; Vlákno cvThread; Detekovaná farba Farba; Booleovské zisťovanie = nepravda; int detectCnt = 0;
private void cvThreadFunction () {
videoRunning = false;
zachytenie = nový VideoCapture (vybraná kamera);
pomocou (Okno okna = nové okno („zachytenie“)) {
Mat image = new Mat (); Mat image2 = new Mat (); while (runVideo) {capture. Read (obrázok); if (image. Empty ()) break;
ak (zisťuje)
detectCnt ++; else detectCnt = 0;
if (detekuje || circleDetectChecked || showDetectionImgChecked) {
Cv2. CvtColor (obrázok, obrázok2, ColorConversionCodes. BGR2GREY); Mat thres = image2. Threshold ((double) Properties. Settings. Default.videoThresh, 255, ThresholdTypes. Binary); thres = thres. GaussianBlur (nový OpenCvSharp. Size (9, 9), 10);
ak (showDetectionImgChecked)
obrázok = thres;
if (detekuje || circleDetectChecked) {
CircleSegment korálka = 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); obrázok. Kruh (perlička [0]. Centrum, (int) perlička [0]. Rádius, nový skalár (0, 0, 255), 3); if (perlička [0]. Radius> = 55) {Properties. Settings. Default.x = (desatinná) perlička [0]. Center. X + (desatinná) (perlička [0]. Radius / 2); Properties. Settings. Default.y = (desatinná) perlička [0]. Center. Y - (desatinná) (perlička [0]. Radius / 2); } else {Properties. Settings. Default.x = (desatinná) perlička [0]. Center. X + (desatinná) (perlička [0]. Radius); Properties. Settings. Default.y = (desatinná) perlička [0]. Center. Y - (desatinná) (perlička [0]. Radius); } Properties. Settings. Default.size = 15; Properties. Settings. Default.height = 15; } else {
CircleSegment circle = thres. HoughCircles (HoughMethods. Gradient, 2, /*thres. Rows/4*/ 5, 200, 100, 60, 180);
if (circle. Length> 1) {List xs = circle. Select (c => c. Center. X). ToList (); xs. Sort (); Zoznam ys = kruhy. Vyberte (c => c. Center. Y). ToList (); ys. Sort ();
int medianX = (int) xs [xs. Count / 2];
int medianY = (int) ys [ys. Count / 2];
ak (medianX> obrázok. Šírka - 15)
medianX = obrázok. Šírka - 15; if (medianY> image. Height - 15) medianY = image. Height - 15;
obrázok. Kruh (medianX, medianY, 100, new Scalar (0, 0, 150), 3);
ak (zisťuje) {
Properties. Settings. Default.x = medianX - 7; Properties. Settings. Default.y = mediánY - 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 = new Mat (obrázok, r);
Skalárne priem. Farba = Cv2. Priemer (vzorka korálkov); detekovaná farba = farba. FromArgb ((int) avgColor [2], (int) avgColor [1], (int) avgColor [0]);
obrázok. Obdĺžnik (r, nový skalár (0, 150, 0));
window. ShowImage (obrázok);
Cv2. WaitKey (1); videoRunning = true; }
videoRunning = false;
} }
súkromná prázdna kameraStartBtn_Click (odosielateľ objektu, EventArgs e) {
if (cameraStartBtn. Text == "start") {
cvThread = nové vlákno (nové ThreadStart (cvThreadFunction)); runVideo = true; cvThread. Start (); cameraStartBtn. Text = "stop"; while (! videoRunning) Thread. Sleep (100);
updateColorTimer. Start ();
} else {
runVideo = false; cvThread. Join (); cameraStartBtn. Text = "štart"; }}
Farba
Teraz sme schopní určiť farbu guľôčky a na základe tejto farby sa rozhodnúť, do ktorej nádoby ju vložíme.
Tento krok sa spolieha na porovnanie farieb. Chceme byť schopní rozlíšiť farby, aby sme obmedzili falošne pozitívne výsledky, ale zároveň povolili dostatok prahov na obmedzenie falošných negatívov. Porovnávanie farieb je v skutočnosti prekvapivo zložité, pretože spôsob, akým počítače ukladajú farby ako RGB, a spôsob, akým ľudia farby vnímajú, nekorelujú lineárne. Aby toho nebolo málo, je potrebné vziať do úvahy aj farbu svetla, pod ktorou sa farba pozerá.
Na výpočet rozdielu farieb existujú komplikované algoritmy. Používame CIE2000, ktorý vydáva číslo blízko 1, ak by boli 2 farby pre človeka nerozoznateľné. Na tieto komplikované výpočty používame knižnicu ColorMine C#. Zistilo sa, že hodnota DeltaE 5 ponúka dobrý kompromis medzi falošne pozitívnym a falošne negatívnym.
Keďže farieb je často viac ako kontajnerov, posledná pozícia je vyhradená ako odpadkový kôš. Spravidla som ich nechal bokom, aby bežali strojom na druhý priechod.
Zoznam
farby = nový zoznam (); zoznam colorPanels = nový zoznam (); List colorsTxts = new List (); Zoznam colorCnts = nový zoznam ();
const int numColorSpots = 18;
const int neznámyColorIndex = 18; int findColorPosition (farba c) {
Console. WriteLine ("Hľadá sa farba …");
var cRGB = nový Rgb ();
cRGB. R = c. R; cRGB. G = c. G; cRGB. B = c. B;
int bestMatch = -1;
dvojitá zhodaDelta = 100;
for (int i = 0; i <colors. Count; i ++) {
var RGB = nový Rgb ();
RGB. R = farby . R; RGB. G = farby . G; RGB. B = farby . B;
dvojitá delta = cRGB. Compare (RGB, nové CieDe2000Comparison ());
// dvojitá delta = deltaE (c, farby ); Console. WriteLine ("DeltaE (" + i. ToString () + "):" + delta. ToString ()); if (delta <matchDelta) {matchDelta = delta; bestMatch = i; }}
if (matchDelta <5) {Console. WriteLine ("Found! (Posn:" + bestMatch + "Delta:" + matchDelta + ")"); vrátiť bestMatch; }
if (colors. Count <numColorSpots) {Console. WriteLine ("New Color!"); farby. Add (c); this. BeginInvoke (nová akcia (setBackColor), nový objekt {colors. Count - 1}); writeOutColors (); návrat (farby. Počet - 1); } else {Console. WriteLine ("Neznáma farba!"); vrátiť neznámyColorIndex; }}
Logika triedenia
Funkcia triedenia spája všetky kúsky a skutočne triedi korálky. Táto funkcia beží vo vyhradenom vlákne; posunutie hornej dosky, zistenie farby guľôčok, umiestnenie do koša, uistite sa, že horná doska zostane zarovnaná, počítanie guličiek atď. Tiež sa zastaví, keď sa odpadkový kôš naplní - V opačnom prípade skončíme s pretekajúcimi korálkami.
Farba vláknaTestThread; Boolean runtest = false; neplatná farbaTest () {
if (! top. Engaged)
top. Engaged = true;
if (! bottom. Engaged)
bottom. Engaged = true;
while (runtest) {
nextMagnet (true);
Thread. Sleep (100); skúste {if (magSensor. SensorValue <(magSensorMax - 4)) alignMotor (); } catch {alignMotor (); }
nextCamera (pravda);
zisťovanie = pravda;
while (detectCnt <5) Thread. Sleep (25); Console. WriteLine ("Detect Count:" + detectCnt); detekcia = nepravda;
Farba c = detekovaná farba;
this. BeginInvoke (nová akcia (setColorDet), nový objekt {c}); int i = findColorPosition (c);
SetBottomPosition (i, true);
nextHole (pravda); colorCnts ++; this. BeginInvoke (nová akcia (setColorTxt), nový objekt {i}); Thread. Sleep (250);
if (colorCnts [neznámyColorIndex]> 500) {
top. Engaged = false; bottom. Engaged = false; runtest = false; this. BeginInvoke (nová akcia (setGoGreen), null); návrat; }}}
private void colourTestBtn_Click (odosielateľ objektu, EventArgs e) {
if (colourTestThread == null ||! colourTestThread. IsAlive) {colourTestThread = new Thread (new ThreadStart (colourTest)); runtest = true; colourTestThread. Start (); colourTestBtn. Text = "STOP"; colourTestBtn. BackColor = Color. Red; } else {runtest = false; colourTestBtn. Text = "GO"; colourTestBtn. BackColor = Color. Green; }}
V tomto momente máme pracovný program. Niektoré časti kódu boli vynechané z článku, takže sa pozrite na zdroj a skutočne ho spustite.
Druhá cena v súťaži optiky
Odporúča:
Bublinkové triedenie v dávke!: 4 kroky
Bublinkové triedenie v dávke !: Zaujímalo vás niekedy, ako vytvoriť jednoduchý triediaci algoritmus v čistej dávke? Nebojte sa, je to jednoduché ako koláč! To tiež ukazuje proces jeho triedenia. (Poznámka: Urobil som to na počítači so systémom Windows XP, takže určitý kód nemusí fungovať. Nie som si však istý. Prepáčte …)
Elektronické triedenie mincí: 7 krokov (s obrázkami)
Elektronický triedič mincí: Dávno, veľmi dávno, keď bolo ešte možné chodiť do školy, sme prišli so zaujímavým nápadom vyrobiť zariadenie, ktoré funguje pomerne jednoduchým spôsobom - po vhodení správneho množstva peňazí sme vydá konkrétny výrobok. Nemôžem sa odhaliť
Robot na triedenie odpadu: 15 krokov (s obrázkami)
Robot na triedenie odpadu: Vedeli ste, že priemerná miera kontaminácie v komunitách a podnikoch sa pohybuje až do 25%? To znamená, že jeden zo každých štyroch kusov recyklácie, ktoré vyhodíte, sa nerecykluje. Je to spôsobené ľudskou chybou v recyklačných strediskách. Tradícia
Stroj na triedenie magického mramoru LittleBits: 11 krokov (s obrázkami)
LittleBits Magical Marble Triediaci stroj: Chceli ste niekedy triediť guličky? Potom ste mohli postaviť tento stroj. Už nikdy nebudete musieť miešať vrece mramoru! Je to čarovný mramorový triediaci stroj, ktorý používa farebný senzor od spoločnosti Adafruit, typ TCS34725 a Leonardo Arduino od
Stroj na triedenie skrutiek: 7 krokov (s obrázkami)
Stroj na triedenie skrutiek: Jedného dňa som v laboratóriu (FabLab Moskva) videl svojho kolegu zaneprázdneného triedením celej škatule so skrutkami, maticami, krúžkami a iným hardvérom. Zastavil som vedľa neho, chvíľu som to sledoval a povedal som: " Pre stroj by to bola perfektná práca. &Quot; Po rýchlom pohľade