Obsah:

Robotické třídění korálků: 3 kroky (s obrázky)
Robotické třídění korálků: 3 kroky (s obrázky)

Video: Robotické třídění korálků: 3 kroky (s obrázky)

Video: Robotické třídění korálků: 3 kroky (s obrázky)
Video: Say NO to WAR! ❌ Share this video to everyone you can! 🙏 2024, Červenec
Anonim
Image
Image
Robotické třídění korálků
Robotické třídění korálků
Robotické třídění korálků
Robotické třídění korálků
Robotické třídění korálků
Robotické třídění korálků

V tomto projektu budeme stavět robota, který bude Perlerovy korálky třídit podle barev.

Vždy jsem chtěl postavit robota pro třídění barev, takže když se moje dcera začala zajímat o výrobu perliček Perler, viděl jsem to jako ideální příležitost.

Perlerovy korálky se používají k vytváření fúzovaných uměleckých projektů tak, že se mnoho korálků položí na věšák a poté se roztaví pomocí žehličky. Obvykle nakupujete tyto korálky v obřích baleních 22 000 smíšených barev a trávíte spoustu času hledáním požadované barvy, takže jsem si myslel, že jejich tříděním zvýšíte efektivitu umění.

Pracuji pro Phidgets Inc., takže jsem pro tento projekt používal převážně Phidgets - ale to bylo možné provést pomocí jakéhokoli vhodného hardwaru.

Krok 1: Hardware

Zde je to, co jsem použil k vytvoření tohoto. Postavil jsem to na 100% pomocí dílů z phidgets.com a věcí, které jsem měl povalující se po domě.

Phidgets Desky, Motory, Hardware

  • HUB0000 - VINT Hub Phidget
  • 1108 - Magnetický senzor
  • 2x STC1001 - 2,5A krokový phidget
  • 2x 3324 - 42STH38 NEMA -17 bipolární bezpřevodový stepper
  • 3x 3002 - kabel Phidget 60 cm
  • 3403 - 4portový rozbočovač USB2.0
  • 3031 - ženský pigtail 5,5 x 2,1 mm
  • 3029 - 2vodičový 100 'kroucený kabel
  • 3604 - 10 mm bílá LED (sáček po 10)
  • 3402 - USB webová kamera

Ostatní díly

  • Napájecí zdroj 24 V DC 2,0 A
  • Vyhoďte dřevo a kov z garáže
  • Kravaty na zip
  • Plastová nádoba s odříznutým dnem

Krok 2: Navrhněte robota

Navrhněte robota
Navrhněte robota
Navrhněte robota
Navrhněte robota
Navrhněte robota
Navrhněte robota

Potřebujeme navrhnout něco, co může ze vstupního zásobníku odebrat jeden korálek, umístit jej pod webovou kameru a poté přesunout do příslušného koše.

Vyzvednutí korálků

Rozhodl jsem se udělat 1. část se 2 kusy kulaté překližky, každý s otvorem vyvrtaným na stejném místě. Spodní část je pevná a horní část je připevněna k krokovému motoru, který ji může otáčet pod násypkou naplněnou korálky. Když díra putuje pod násypku, sebere jeden korálek. Poté jej mohu otočit pod webovou kamerou a poté dále otáčet, dokud se neshoduje s otvorem ve spodním dílu, v kterém místě propadne.

Na tomto obrázku testuji, že systém může fungovat. Všechno je pevné, kromě horního kulatého kusu překližky, který je připevněn k krokovému motoru mimo pohled zespodu. Webová kamera ještě nebyla připojena. V tuto chvíli používám ovládací panel Phidget k přepnutí na motor.

Skladování korálků

Další částí je návrh systému přihrádek pro uložení každé barvy. Rozhodl jsem se použít druhý krokový motor níže k podepření a otočení kulatého kontejneru s rovnoměrně rozmístěnými oddíly. To lze použít k otočení správné přihrádky pod otvorem, ze kterého kulička vypadne.

Postavil jsem to pomocí lepenky a lepicí pásky. Nejdůležitější je zde konzistence - každé oddělení by mělo mít stejnou velikost a celé by mělo být rovnoměrně vyváženo, aby se točilo bez přeskakování.

Odstranění korálků se provádí pomocí těsně přiléhajícího víka, které odhaluje jednu komoru najednou, takže je možné kuličky vylévat.

Fotoaparát

Webová kamera je namontována přes horní desku mezi násypku a umístění otvoru spodní desky. To umožňuje systému podívat se na korálek, než jej upustí. K osvětlení kuliček pod kamerou se používá LED dioda a okolní světlo je blokováno, aby bylo zajištěno konzistentní osvětlení. To je velmi důležité pro přesnou detekci barev, protože okolní osvětlení může skutečně odhodit vnímanou barvu.

Detekce polohy

Je důležité, aby systém dokázal detekovat otáčení separátoru kuliček. To se používá k nastavení počáteční polohy při spuštění, ale také ke zjištění, zda se krokový motor nedostal ze synchronizace. V mém systému se při vyzvednutí někdy zasekne korálek a systém potřeboval, aby dokázal tuto situaci detekovat a zvládnout - tak, že trochu zazálohuji a zkusím to znovu.

Existuje mnoho způsobů, jak to zvládnout. Rozhodl jsem se použít magnetický senzor 1108 s magnetem vloženým do okraje horní desky. To mi umožňuje ověřit polohu při každém otočení. Lepším řešením by pravděpodobně byl kodér na krokovém motoru, ale měl jsem ležet 1108, takže jsem to použil.

Dokončete robota

V tuto chvíli bylo vše zpracováno a testováno. Je na čase vše pěkně připojit a přejít na software pro psaní.

2 krokové motory jsou poháněny krokovými regulátory STC1001. Rozbočovač HUB000 - USB VINT se používá ke spouštění krokových ovladačů, čtení magnetického senzoru a ovládání LED. Webová kamera a HUB0000 jsou připojeny k malému rozbočovači USB. K napájení motorů je použit 3031 pigtail a nějaký drát.

Krok 3: Napište kód

Image
Image

Pro tento projekt se používají C# a Visual Studio 2015. Stáhněte si zdroj v horní části této stránky a postupujte podle něj - hlavní části jsou uvedeny níže

Inicializace

Nejprve musíme vytvořit, otevřít a inicializovat objekty Phidget. To se provádí v případě načtení formuláře a obslužných rutin připojení Phidget.

private void Form1_Load (odesílatel objektů, EventArgs e) {

/ * Inicializujte a otevřete 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; dole. 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 (odesílatel objektů, Phidget22. Events. AttachEventArgs e) {

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

private void MagSensor_Attach (odesílatel objektů, Phidget22. Events. AttachEventArgs e) {

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

private void Bottom_Attach (odesílatel objektů, 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 (odesílatel objektů, 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; }

Během inicializace také čteme uložené informace o barvách, takže v předchozím běhu lze pokračovat.

Polohování motoru

Manipulační kód motoru se skládá z praktických funkcí pro pohyb motorů. Motory, které jsem použil, jsou 3 200 1/16 kroků na otáčku, takže jsem pro to vytvořil konstantu.

U horního motoru existují 3 polohy, do kterých chceme odeslat motor: webová kamera, otvor a polohovací magnet. K dispozici je funkce pro cestování do každé z těchto pozic:

private void nextMagnet (Boolean wait = false) {

double posn = top. Position % stepsPerRev;

top. TargetPosition += (stepsPerRev - posn);

jestli (počkej)

while (top. IsMoving) Thread. Sleep (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);

pokud (počkat)

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

pokud (počkat)

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

Před zahájením běhu se horní deska zarovná pomocí magnetického senzoru. K zarovnání horní desky lze kdykoli vyvolat funkci alignMotor. Tato funkce nejprve rychle otočí desku až o 1 plnou otáčku, dokud neuvidí magnetická data nad prahovou hodnotou. Poté se trochu zazálohuje a pomalu se pohybuje vpřed a zachycuje data senzorů, jak to jde. Nakonec nastaví polohu na maximální umístění dat magnetu a resetuje posunutí polohy na 0. Maximální poloha magnetu by tedy měla být vždy na (top. Position % stepsPerRev)

Zarovnání závitu MotorThread; Boolean sawMagnet; double magSensorMax = 0; private void alignMotor () {

// Najděte magnet

top. DataInterval = top. MinDataInterval;

sawMagnet = false;

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

int tryCount = 0;

Zkus to znovu:

top. TargetPosition += stepsPerRev;

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

if (! sawMagnet) {

if (tryCount> 3) {Console. WriteLine ("Align failed"); top. Engaged = false; bottom. Engaged = false; runtest = false; vrátit se; }

tryCount ++;

Console. WriteLine ("Jsme zaseknutí? Zkoušíte zálohu …"); top. TargetPosition -= 600; while (top. IsMoving) Thread. Sleep (100);

zkuste to znovu;

}

top. VelocityLimit = -100;

magData = nový seznam> (); 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 ("Zarovnání se podařilo");

}

Seznam> magData;

private void magSensorCollectPositionData (odesílatel objektů, Phidget22. Events. VoltageRatioInputSensorChangeEventArgs e) {magData. Add (nový KeyValuePair (top. Position, e. SensorValue)); }

private void magSensorStopMotor (odesílatel objektů, Phidget22. Events. VoltageRatioInputSensorChangeEventArgs e) {

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

Nakonec je spodní motor řízen jeho vysláním do jedné z poloh nádobky na korálky. Pro tento projekt máme 19 pozic. Algoritmus vybírá nejkratší cestu a otáčí se ve směru nebo proti směru hodinových ručiček.

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;

double currentPosn = bottom. Position % stepsPerRev;

double posnDiff = targetPosn - currentPosn;

// Ponechte si to jako úplné kroky

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

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

jestli (počkej)

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

Fotoaparát

OpenCV slouží ke čtení obrázků z webové kamery. Vlákno kamery se spustí před spuštěním hlavního vlákna třídění. Toto vlákno nepřetržitě čte obrázky, vypočítává průměrnou barvu pro konkrétní oblast pomocí Mean a aktualizuje globální barevnou proměnnou. Vlákno také využívá HoughCircles, aby se pokusil detekovat buď korálek, nebo díru v horní desce, aby upřesnil oblast, na kterou se dívá pro detekci barev. Prahová a HoughCircles čísla byla stanovena metodou pokusu a omylu a do značné míry závisí na webové kameře, osvětlení a vzdálenosti.

bool runVideo = true; bool videoRunning = false; Zachycení VideoCapture; Vlákno cvThread; Detekována barva Barva; Booleovská detekce = false; int detectCnt = 0;

private void cvThreadFunction () {

videoRunning = false;

capture = new VideoCapture (selectedCamera);

pomocí (Okno okna = nové okno ("zachytit")) {

Mat image = new Mat (); Mat image2 = new Mat (); while (runVideo) {capture. Read (obrázek); if (image. Empty ()) break;

pokud (detekce)

detectCnt ++; else detectCnt = 0;

if (detekce || circleDetectChecked || showDetectionImgChecked) {

Cv2. CvtColor (image, image2, ColorConversionCodes. BGR2GREY); Mat thres = image2. Threshold ((double) Properties. Settings. Default.videoThresh, 255, ThresholdTypes. Binary); thres = thres. GaussianBlur (nový OpenCvSharp. Size (9, 9), 10);

if (showDetectionImgChecked)

image = thres;

if (detekce || circleDetectChecked) {

CircleSegment korálek = 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); image. Circle (korálek [0]. Center, (int) korálek [0]. Radius, nový skalární (0, 0, 255), 3); if (bead [0]. Radius> = 55) {Properties. Settings. Default.x = (decimal) bead [0]. Center. X + (decimal) (bead [0]. Radius / 2); Properties. Settings. Default.y = (desítková) kulička [0]. Center. Y - (desetinná) (kulička [0]. Radius / 2); } else {Properties. Settings. Default.x = (desítková) kulička [0]. Center. X + (desetinná) (kulička [0]. Radius); Properties. Settings. Default.y = (desetinná) kulička [0]. Center. Y - (desetinná) (kulič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 (); Seznam ys = kruhy. Vyberte (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 = obrázek. Šířka - 15; if (medianY> image. Height - 15) medianY = image. Height - 15;

obrázek. Kruh (medianX, medianY, 100, new Scalar (0, 0, 150), 3);

pokud (detekuje) {

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 = new Mat (obrázek, r);

Skalární avgColor = Cv2. Mean (beadSample); detekovaná barva = Color. FromArgb ((int) avgColor [2], (int) avgColor [1], (int) avgColor [0]);

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

window. ShowImage (obrázek);

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

videoRunning = false;

} }

soukromá prázdná kameraStartBtn_Click (odesílatel objektů, EventArgs e) {

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

cvThread = new Thread (new ThreadStart (cvThreadFunction)); runVideo = true; cvThread. Start (); cameraStartBtn. Text = "stop"; while (! videoRunning) Thread. Sleep (100);

updateColorTimer. Start ();

} else {

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

Barva

Nyní jsme schopni určit barvu kuličky a podle této barvy se rozhodnout, do které nádoby ji hodíme.

Tento krok závisí na porovnání barev. Chceme být schopni rozeznat barvy od sebe, abychom omezili falešně pozitivní výsledky, ale také umožnili dostatečný práh pro omezení falešných negativů. Porovnávání barev je ve skutečnosti překvapivě složité, protože způsob, jakým počítače ukládají barvy jako RGB, a způsob, jakým lidé barvy vnímají, nekorelují lineárně. Aby toho nebylo málo, je třeba vzít v úvahu také barvu světla, pod kterou je barva pozorována.

Existují komplikované algoritmy pro výpočet rozdílu barev. Používáme CIE2000, který vydává číslo blízko 1, pokud by 2 barvy byly pro člověka nerozeznatelné. K provádění těchto komplikovaných výpočtů používáme knihovnu ColorMine C#. Bylo zjištěno, že hodnota DeltaE 5 nabízí dobrý kompromis mezi falešně pozitivním a falešně negativním.

Protože často existuje více barev než kontejnerů, je poslední pozice vyhrazena jako odpadkový koš. Obecně jsem je nechal stranou, aby běžel stroj na druhý průchod.

Seznam

barvy = nový seznam (); seznam colorPanels = nový seznam (); List colorsTxts = new List (); Seznam colorCnts = nový List ();

const int numColorSpots = 18;

const int neznámýColorIndex = 18; int findColorPosition (barva c) {

Console. WriteLine ("Hledání barvy …");

var cRGB = nový Rgb ();

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

int bestMatch = -1;

dvojitá shoda Delta = 100;

pro (int i = 0; i <barvy. Count; i ++) {

var RGB = nový Rgb ();

RGB. R = barvy . R; RGB. G = barvy . G; RGB. B = barvy . B;

dvojitá delta = cRGB. Compare (RGB, nové CieDe2000Comparison ());

// dvojitá delta = deltaE (c, barvy ); 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átit bestMatch; }

if (colors. Count <numColorSpots) {Console. WriteLine ("New Color!"); barvy. Add (c); this. BeginInvoke (nová akce (setBackColor), nový objekt {colors. Count - 1}); writeOutColors (); návrat (barvy. Count - 1); } else {Console. WriteLine ("Neznámá barva!"); vrátit neznámýColorIndex; }}

Logika řazení

Funkce třídění spojuje všechny kousky a skutečně třídí korálky. Tato funkce běží ve vyhrazeném vlákně; pohyb horní desky, detekce barvy korálků, umístění do koše, ujistěte se, že horní deska zůstane zarovnaná, počítání korálků atd. Také se zastaví, když se odpadkový koš zaplní - jinak skončíme s přetékajícími korálky.

Barva vláknaTestThread; Boolean runtest = false; neplatná barvaTest () {

if (! top. Engaged)

top. Engaged = true;

if (! bottom. Engaged)

bottom. Engaged = true;

while (runtest) {

nextMagnet (true);

Thread. Sleep (100); zkuste {if (magSensor. SensorValue <(magSensorMax - 4)) alignMotor (); } catch {alignMotor (); }

nextCamera (true);

detekce = pravda;

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

Barva c = detekována Barva;

this. BeginInvoke (nová akce (setColorDet), nový objekt {c}); int i = findColorPosition (c);

SetBottomPosition (i, true);

nextHole (true); colorCnts ++; this. BeginInvoke (nová akce (setColorTxt), nový objekt {i}); Thread. Sleep (250);

if (colorCnts [neznámýColorIndex]> 500) {

top. Engaged = false; bottom. Engaged = false; runtest = false; this. BeginInvoke (nová akce (setGoGreen), null); vrátit se; }}}

private void colourTestBtn_Click (odesílatel objektů, 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 tuto chvíli máme pracovní program. Některé kousky kódu byly z článku vynechány, takže se podívejte na zdroj, abyste ho skutečně spustili.

Optická soutěž
Optická soutěž

Druhá cena v optické soutěži

Doporučuje: