Tartalomjegyzék:

Robot gyöngyök rendezése: 3 lépés (képekkel)
Robot gyöngyök rendezése: 3 lépés (képekkel)

Videó: Robot gyöngyök rendezése: 3 lépés (képekkel)

Videó: Robot gyöngyök rendezése: 3 lépés (képekkel)
Videó: APA FALHOZ VÁGTA A TABLETEM! 😱 #shorts 2024, Július
Anonim
Image
Image
Robot gyöngyök rendezése
Robot gyöngyök rendezése
Robot gyöngyök rendezése
Robot gyöngyök rendezése
Robot gyöngyök rendezése
Robot gyöngyök rendezése

Ebben a projektben robotot fogunk építeni a Perler gyöngyök szín szerinti válogatásához.

Mindig szerettem volna színválogató robotot építeni, így amikor a lányom érdeklődni kezdett a Perler gyöngyök készítése iránt, ezt tökéletes lehetőségnek láttam.

A Perler -gyöngyöket olvadt művészeti projektek készítésére használják, sok gyöngyöt egy rácsra helyezve, majd vasalóval összeolvasztva. Általában ezeket a gyöngyöket óriási, 22 000 gyöngyös vegyes színű csomagban vásárolja meg, és sok időt tölt a kívánt szín keresésével, ezért azt gondoltam, hogy a válogatás növeli a művészet hatékonyságát.

A Phidgets Inc. -nél dolgozom, ezért többnyire Phidgets -t használtam ehhez a projekthez - de ezt bármilyen megfelelő hardverrel meg lehet tenni.

1. lépés: Hardver

Íme, amit én szoktam építeni. 100% -ban a phidgets.com alkatrészeiből építettem, és a ház körül fekvő dolgokból.

Phidgets táblák, motorok, hardverek

  • HUB0000 - VINT Hub Phidget
  • 1108 - Mágneses érzékelő
  • 2x STC1001 - 2,5A léptető Phidget
  • 2x 3324 - 42STH38 NEMA -17 bipoláris sebességváltó
  • 3x 3002 - Phidget kábel 60 cm
  • 3403 - USB2.0 4 portos hub
  • 3031 - Női copf 5,5x2,1 mm
  • 3029 - 2 vezetékes 100 'csavart kábel
  • 3604 - 10 mm -es fehér LED (10 db -os táska)
  • 3402 - USB webkamera

Más részek

  • 24VDC 2.0A tápegység
  • Fa- és fémhulladék a garázsból
  • Zip kötések
  • Műanyag edény, levágva az alját

2. lépés: Tervezze meg a robotot

Tervezze meg a robotot
Tervezze meg a robotot
Tervezze meg a robotot
Tervezze meg a robotot
Tervezze meg a robotot
Tervezze meg a robotot

Valamit meg kell terveznünk, amely egyetlen gyöngyöt vehet ki a bemeneti garatból, a webkamera alá kell helyezni, majd áthelyezni a megfelelő kukába.

Gyöngyszedés

Úgy döntöttem, hogy az 1. részt 2 darab kerek rétegelt lemezből készítem, mindegyiken lyukat fúrva ugyanott. Az alsó darab rögzítve van, a felső pedig egy léptetőmotorhoz van rögzítve, amely el tudja forgatni a gyöngyökkel töltött garat alatt. Amikor a lyuk a garat alatt halad, egyetlen gyöngyöt vesz fel. Ezt követően forgathatom a webkamera alatt, majd tovább forgathatom, amíg össze nem egyezik az alsó rész lyukával, és ekkor átesik.

Ezen a képen azt tesztelem, hogy a rendszer működik -e. Minden rögzítve van, kivéve a felső kerek rétegelt lemezdarabot, amely alatta egy látómezőhöz van rögzítve. A webkamera még nincs felszerelve. Most csak a Phidget vezérlőpultot használom, hogy motorhoz forduljak.

Gyöngyök tárolása

A következő rész az egyes színek tárolására szolgáló tárolórendszer kialakítása. Úgy döntöttem, hogy egy második léptetőmotort használok egy kerek tartály alátámasztására és elforgatására, egyenletesen elhelyezett rekeszekkel. Ezzel elforgatható a megfelelő rekesz a lyuk alatt, amelyből a gyöngy kiesik.

Ezt kartonból és ragasztószalagból építettem. A legfontosabb itt a következetesség - minden rekesznek azonos méretűnek kell lennie, és az egészet egyenletesen kell súlyozni, így ugrás nélkül forog.

A gyöngyök eltávolítása egy szorosan illeszkedő fedél segítségével történik, amely egyszerre egyetlen rekeszt tár fel, így a gyöngyök kiönthetők.

Kamera

A webkamera a felső lemez fölé van szerelve a garat és az alsó lemezlyuk helye között. Ez lehetővé teszi a rendszer számára, hogy megnézze a gyöngyöt, mielőtt leejti. A fényképezőgép alatti gyöngyök megvilágítására LED -et használnak, a környezeti fény pedig blokkolva van a következetes megvilágítási környezet biztosítása érdekében. Ez nagyon fontos a pontos színérzékeléshez, mivel a környezeti megvilágítás valóban kivetheti az érzékelt színeket.

Helyérzékelés

Fontos, hogy a rendszer képes legyen érzékelni a gyöngyleválasztó forgását. Ez a beállítás a kezdeti pozíció beállítására szolgál indításkor, de annak észlelésére is, hogy a léptetőmotor ki lett -e szinkronizálva. Az én rendszeremben egy gyöngy néha elakad, miközben felveszi, és a rendszernek képesnek kell lennie észlelni és kezelni ezt a helyzetet - egy kicsit mentve és megpróbálva.

Ennek kezelésére sokféle módszer létezik. Úgy döntöttem, hogy 1108 -as mágneses érzékelőt használok, mágnessel a felső lemez szélébe ágyazva. Ez lehetővé teszi, hogy minden fordulaton ellenőrizhessem a pozíciót. A jobb megoldás valószínűleg a léptetőmotor kódolója lenne, de egy 1108 -as feküdt, ezért ezt használtam.

Fejezd be a robotot

Ezen a ponton mindent kidolgoztak és teszteltek. Itt az ideje, hogy mindent szépen telepítsen, és áttérjen az írószoftverre.

A 2 léptetőmotort STC1001 léptetővezérlők hajtják. A HUB000 - USB VINT hub a léptetővezérlők futtatására, valamint a mágneses érzékelő leolvasására és a LED meghajtására szolgál. A webkamera és a HUB0000 egyaránt egy kis USB -elosztóhoz van csatlakoztatva. A motorok táplálásához 3031 -es copfot és néhány vezetéket használnak 24V -os tápegységgel együtt.

Lépés: Írja be a kódot

Image
Image

Ehhez a projekthez a C# és a Visual Studio 2015 szolgál. Töltse le a forrást az oldal tetején, és kövesse - a fő szakaszokat az alábbiakban ismertetjük

Inicializálás

Először létre kell hoznunk, meg kell nyitnunk és inicializálnunk kell a Phidget objektumokat. Ez az űrlap betöltése eseményben történik, és a Phidget csatolók kezelői.

private void Form1_Load (objektumküldő, EventArgs e) {

/ * Inicializálja és nyissa meg a Phidgeteket */

top. HubPort = 0; top. Attach += Top_Attach; top. Detach += Top_Detach; top. PositionChange += Top_PositionChange; fel. Nyissa ();

alsó. HubPort = 1;

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

magSensor. HubPort = 2;

magSensor. IsHubPortDevice = igaz; magSensor. Attach += MagSensor_Attach; magSensor. Detach += MagSensor_Detach; magSensor. SensorChange += MagSensor_SensorChange; magSensor. Open ();

led. HubPort = 5;

led. IsHubPortDevice = igaz; led. Channel = 0; led. Attach += Led_Attach; led. Detach += Led_Detach; led. Open (); }

private void Led_Attach (objektumküldő, Phidget22. Events. AttachEventArgs e) {

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

private void MagSensor_Attach (objektumküldő, Phidget22. Events. AttachEventArgs e) {

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

private void Bottom_Attach (objektumküldő, Phidget22. Events. AttachEventArgs e) {

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

private void Top_Attach (objektumküldő, Phidget22. Events. AttachEventArgs e) {

topAttachedChk. Checked = igaz; top. CurrentLimit = topCurrentLimit; top. Engaged = igaz; top. RescaleFactor = -1; top. VelocityLimit = -topVelocityLimit; fel. Gyorsulás = -topAccel; top. DataInterval = 100; }

Az inicializálás során beolvasunk minden elmentett színinformációt is, így folytatható az előző futtatás.

Motor pozicionálás

A motorkezelési kód a motorok mozgatásának kényelmi funkcióiból áll. Az általam használt motorok fordulatonként 3, 200 1/16 lépést jelentenek, ezért ehhez konstansot hoztam létre.

A felső motor esetében 3 pozíciót szeretnénk elküldeni a motornak: a webkamerát, a lyukat és a pozicionáló mágnest. Van egy funkció az egyes pozíciókba való utazáshoz:

private void nextMagnet (logikai várakozás = hamis) {

double posn = top. Position % stepsPerRev;

top. TargetPosition += (stepsPerRev - posn);

ha (várj)

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

private void nextCamera (Boole -várakozás = hamis) {

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

ha (várj)

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

private void nextHole (Boole -várakozás = hamis) {

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

ha (várj)

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

A futás megkezdése előtt a felső lemezt a mágneses érzékelő segítségével igazítják. Az alignMotor funkció bármikor meghívható a felső lemez igazításához. Ez a funkció először gyorsan felforgatja a lemezt 1 teljes fordulatra, amíg meg nem látja a mágneses adatokat egy küszöbérték felett. Ezután egy kicsit biztonsági mentést készít, és lassan halad előre, lassan rögzítve az érzékelő adatait. Végül beállítja a pozíciót a mágnesadatok maximális helyére, és visszaállítja a pozícióeltolást 0. Így a maximális mágnespozíciónak mindig a (felső. Pozíció % lépésekPerRev) értéken kell lennie.

Menet összehangolásaMotorThread; Boolean sawMagnet; dupla magSensorMax = 0; private void alignMotor () {

// Keresse meg a mágnest

top. DataInterval = top. MinDataInterval;

sawMagnet = hamis;

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

int tryCount = 0;

próbáld újra:

top. TargetPosition += stepsPerRev;

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

ha (! sawMagnet) {

if (tryCount> 3) {Console. WriteLine ("Az igazítás nem sikerült"); top. Engaged = hamis; alsó. Engaged = hamis; runtest = hamis; Visszatérés; }

tryCount ++;

Console. WriteLine ("Elakadtunk? Próbálunk biztonsági másolatot készíteni …"); top. TargetPosition -= 600; while (top. IsMoving) Thread. Sleep (100);

újra megpróbálkozni;

}

top. VelocityLimit = -100;

magData = új lista> (); magSensor. SensorChange += magSensorCollectPositionData; top. TargetPosition += 300; while (top. IsMoving) Thread. Sleep (100);

magSensor. SensorChange -= magSensorCollectPositionData;

top. VelocityLimit = -topVelocityLimit;

KeyValuePair max = magData [0];

foreach (KeyValuePair pár a magDatában) if (pair. Value> max. Value) max = pair;

top. AddPositionOffset (-max. Key);

magSensorMax = max. Value;

top. TargetPosition = 0;

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

Console. WriteLine ("Az igazítás sikerült");

}

Lista> magData;

private void magSensorCollectPositionData (objektumküldő, Phidget22. Events. VoltageRatioInputSensorChangeEventArgs e) {magData. Add (új KeyValuePair (top. Position, e. SensorValue)); }

private void magSensorStopMotor (objektumküldő, Phidget22. Events. VoltageRatioInputSensorChangeEventArgs e) {

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

Végül az alsó motort úgy irányítják, hogy elküldik a gyöngytartó egyik pozíciójába. Ehhez a projekthez 19 pozíciónk van. Az algoritmus a legrövidebb utat választja, és az óramutató járásával megegyező vagy az óramutató járásával ellentétes irányba fordul.

private int BottomPosition {get {int posn = (int) bottom. Position % stepsPerRev; if (posn <0) posn += stepsPerRev;

return (int) Math. Round ((((posn * beadCompartments) / (kettős) lépésekPerRev));

} }

private void SetBottomPosition (int posn, bool wait = false) {

posn = posn % beadCompartments; double targetPosn = (posn * stepsPerRev) / beadCompartments;

dupla áramPosn = alsó. Pozíció % lépésekPerRev;

dupla posnDiff = targetPosn - currentPosn;

// Tartsa teljes lépésekben

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

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

ha (várj)

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

Kamera

Az OpenCV a webkameráról származó képek olvasására szolgál. A kamera szála a fő rendezési szál elindítása előtt kezdődik. Ez a szál folyamatosan olvassa a képeket, kiszámítja az átlagos színt egy adott régióhoz az Mean segítségével, és frissíti a globális színváltozót. A szál a HoughCircles alkalmazásával egy gyöngyöt vagy a felső lemezen lévő lyukat is észleli, hogy finomítsa azt a területet, amelyet a színérzékelésre néz. A küszöbértéket és a HoughCircles számokat próba és hiba útján határozták meg, és nagymértékben függnek a webkamerától, a megvilágítástól és a távolságtól.

bool runVideo = igaz; bool videoRunning = false; VideoCapture rögzítés; Szál cvThread; Szín észlelveSzín; Logikai észlelés = hamis; int detectCnt = 0;

private void cvThreadFunction () {

videoRunning = hamis;

capture = új VideoCapture (kiválasztottKamera);

using (Ablak ablak = új ablak ("rögzítés")) {

Mat kép = új Mat (); Mat kép2 = új Mat (); while (runVideo) {capture. Read (kép); if (image. Empty ()) break;

ha (észlel)

detectCnt ++; else detectCnt = 0;

if (észlelés || circleDetectChecked || showDetectionImgChecked) {

Cv2. CvtColor (kép, kép2, ColorConversionCodes. BGR2GRAY); Mat thres = image2. Threshold ((dupla) Properties. Settings. Default.videoThresh, 255, ThresholdTypes. Binary); thres = thres. GaussianBlur (új OpenCvSharp. Size (9, 9), 10);

if (showDetectionImgChecked)

image = thres;

if (észleli || circleDetectChecked) {

CircleSegment gyöngy = thres. HoughCircles (HoughMethods. Gradient, 2, /*thres. Rows/4*/ 20, 200, 100, 20, 65); if (gyöngy.hossz> = 1) {image. Kör (gyöngy [0]. Center, 3, new Scalar (0, 100, 0), -1); image. Kör (gyöngy [0]. Központ, (int) gyöngy [0]. Sugár, új skalár (0, 0, 255), 3); if (gyöngy [0]. Rádius> = 55) {Properties. Settings. Default.x = (decimális) gyöngy [0]. Center. X + (decimális) (gyöngy [0]. Radius / 2); Tulajdonságok. Beállítások. Default.y = (tizedes) gyöngy [0]. Központ. Y - (tizedes) (gyöngy [0]. Rádius / 2); } else {Properties. Settings. Default.x = (decimális) gyöngy [0]. Center. X + (tizedes) (gyöngy [0]. Sugár); Properties. Settings. Default.y = (decimális) gyöngy [0]. Center. Y - (decimális) (gyöngy [0]. Sugár); } Properties. Settings. Default.size = 15; Properties. Settings. Default.height = 15; } más {

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

if (körök.hossz> 1) {Lista xs = körök. Kiválasztás (c => c. Center. X). ToList (); xs. Sort (); Lista ys = körök. Válassza a (c => c. Center. Y). ToList (); ys. Sort ();

int mediánX = (int) xs [xs. Count / 2];

int mediánY = (int) ys [ys. Count / 2];

if (medianX> kép. Szélesség - 15)

medianX = kép. Szélesség - 15; if (medianY> image. Height - 15) medianY = image. Height - 15;

kép. Kör (mediánX, mediánY, 100, új skalár (0, 0, 150), 3);

ha (észlel) {

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 = új Mat (kép, r);

Skalár avgColor = Cv2. Mean (gyöngyminta); detectColor = Color. FromArgb ((int) avgColor [2], (int) avgColor [1], (int) avgColor [0]);

kép. Téglalap (r, új skalár (0, 150, 0));

window. ShowImage (kép);

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

videoRunning = hamis;

} }

private void cameraStartBtn_Click (objektumküldő, EventArgs e) {

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

cvThread = új szál (új ThreadStart (cvThreadFunction)); runVideo = igaz; cvThread. Start (); cameraStartBtn. Text = "stop"; while (! videoRunning) Thread. Sleep (100);

updateColorTimer. Start ();

} más {

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

Szín

Most már meg tudjuk határozni a gyöngy színét, és a szín alapján eldönthetjük, hogy melyik tartályba dobjuk.

Ez a lépés a színek összehasonlításán alapul. Szeretnénk megkülönböztetni a színeket, hogy korlátozzuk a hamis pozitív eredményeket, de elegendő küszöbértéket is lehetővé teszünk a hamis negatívok korlátozásához. A színek összehasonlítása valójában meglepően bonyolult, mivel az, ahogyan a számítógépek tárolják a színeket RGB -ként, és ahogyan az emberek érzékelik a színeket, nem korrelálnak lineárisan. Ami még rosszabb, a fény színét is figyelembe kell venni.

Vannak bonyolult algoritmusok a színkülönbség kiszámítására. A CIE2000 -et használjuk, amely 1 közeli számot ad ki, ha 2 szín nem lenne megkülönböztethető az ember számára. A bonyolult számítások elvégzéséhez a ColorMine C# könyvtárat használjuk. Az 5 -ös DeltaE érték jó kompromisszumot kínál a hamis pozitív és a hamis negatív között.

Mivel gyakran több szín van, mint a konténerek, az utolsó helyet foglalatos szemetesként tartják fenn. Ezeket általában félreteszem, hogy a gépen futhassanak második menetben.

Lista

színek = új lista (); lista színpanelek = új lista (); Lista színeiTxts = new List (); Lista colorCnts = új List ();

const int számColorSpots = 18;

const int ismeretlenColorIndex = 18; int findColorPosition (C szín) {

Console. WriteLine ("Szín keresése …");

var cRGB = új Rgb ();

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

int bestMatch = -1;

kettős mérkőzésDelta = 100;

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

var RGB = új Rgb ();

RGB. R = színek . R; RGB. G = színek . G; RGB. B = színek . B;

dupla delta = cRGB. Compare (RGB, új CieDe2000Comparison ());

// dupla delta = deltaE (c, színek ); Console. WriteLine ("DeltaE (" + i. ToString () + "):" + delta. ToString ()); if (delta <matchDelta) {matchDelta = delta; bestMatch = i; }}

if (matchDelta <5) {Console. WriteLine ("Talált! (Posn:" + bestMatch + "Delta:" + matchDelta + ")"); return bestMatch; }

if (colors. Count <numColorSpots) {Console. WriteLine ("Új szín!"); színek. Add (c); this. BeginInvoke (új művelet (setBackColor), új objektum {colors. Count - 1}); writeOutColors (); visszatérés (színek. Szám - 1); } else {Console. WriteLine ("Ismeretlen szín!"); return ismeretlenColorIndex; }}

Rendezési logika

A rendezési funkció összegyűjti az összes darabot a gyöngyök rendezéséhez. Ez a funkció egy dedikált szálban fut; mozgassa a felső lemezt, érzékelje a gyöngy színét, tegye a kukába, győződjön meg arról, hogy a felső lemez egy vonalban marad, számolja a gyöngyöket stb. A futás akkor is leáll, amikor megtelik a zsákgyűjtő tartály - Különben csak túlcsorduló gyöngyökkel végzünk.

Szál colourTestThread; Boolean runtest = false; void colourTest () {

ha (! top. Engaged)

top. Engaged = igaz;

ha (! alul. Engaged)

alsó. Engaged = igaz;

while (runtest) {

nextMagnet (igaz);

Menet. Alvás (100); try {if (magSensor. SensorValue <(magSensorMax - 4)) alignMotor (); } catch {alignMotor (); }

nextCamera (igaz);

észlelés = igaz;

while (detectCnt <5) Thread. Sleep (25); Console. WriteLine ("Detect Count:" + detectCnt); észlelés = hamis;

C szín = észlelt szín;

this. BeginInvoke (új művelet (setColorDet), új objektum {c}); int i = findColorPosition (c);

SetBottomPosition (i, igaz);

nextHole (igaz); colorCnts ++; this. BeginInvoke (új művelet (setColorTxt), új objektum {i}); Menet. Alvás (250);

if (colorCnts [ismeretlenColorIndex]> 500) {

top. Engaged = hamis; alsó. Engaged = hamis; runtest = hamis; this. BeginInvoke (új művelet (setGoGreen), null); Visszatérés; }}}

private void colourTestBtn_Click (objektumküldő, EventArgs e) {

if (colourTestThread == null ||! colourTestThread. IsAlive) {colourTestThread = new Thread (new ThreadStart (colourTest)); runtest = igaz; colourTestThread. Start (); colourTestBtn. Text = "STOP"; colourTestBtn. BackColor = Szín. Vörös; } else {runtest = hamis; colourTestBtn. Text = "UGRÁS"; colourTestBtn. BackColor = Szín. Zöld; }}

Ezen a ponton van egy munkaprogramunk. Néhány kódrészlet kimaradt a cikkből, ezért nézze meg a forrást a tényleges futtatáshoz.

Optikai verseny
Optikai verseny

Második díj az optikai versenyen

Ajánlott: