Kazalo:

Robotsko razvrščanje kroglic: 3 koraki (s slikami)
Robotsko razvrščanje kroglic: 3 koraki (s slikami)

Video: Robotsko razvrščanje kroglic: 3 koraki (s slikami)

Video: Robotsko razvrščanje kroglic: 3 koraki (s slikami)
Video: Как сделать кружевной браслет ZigZag 2024, Julij
Anonim
Image
Image
Robotsko sortiranje kroglic
Robotsko sortiranje kroglic
Robotsko sortiranje kroglic
Robotsko sortiranje kroglic
Robotsko sortiranje kroglic
Robotsko sortiranje kroglic

V tem projektu bomo zgradili robota, ki bo Perlerjeve kroglice razvrščal po barvah.

Vedno sem si želel zgraditi robota za razvrščanje barv, zato se je moja hči, ko se je začela zanimati za izdelavo perlerjev Perler, videl kot odlično priložnost.

Perlerjeve kroglice se uporabljajo za ustvarjanje združenih umetniških projektov, tako da na kroglico položite veliko kroglic in jih nato stopite skupaj z železom. Na splošno te kroglice kupite v velikanskih 22.000 pakiranjih mešanih barv in porabite veliko časa za iskanje želene barve, zato sem mislil, da bi njihovo razvrščanje povečalo umetniško učinkovitost.

Delam za Phidgets Inc., zato sem za ta projekt uporabljal predvsem Phidgets - vendar je to mogoče storiti s katero koli primerno strojno opremo.

1. korak: Strojna oprema

Tukaj je tisto, kar sem uporabil za to. 100% sem ga zgradil z deli iz phidgets.com in stvarmi, ki sem jih imel po hiši.

Plošče Phidgets, motorji, strojna oprema

  • HUB0000 - VINT Središče Phidget
  • 1108 - Magnetni senzor
  • 2x STC1001 - 2.5A Stepper Phidget
  • 2x 3324 - 42STH38 NEMA -17 Bipolarni brezstopenjski korak
  • 3x 3002 - kabel Phidget 60 cm
  • 3403 - 4 -vratno zvezdišče USB2.0
  • 3031 - Ženski prašič 5,5x2,1 mm
  • 3029 - 2 -žilni 100 'zvit kabel
  • 3604 - 10 mm bela LED (vreča 10)
  • 3402 - Spletna kamera USB

Drugi deli

  • 24VDC 2.0A napajalnik
  • Odpadni les in kovina iz garaže
  • Zip vezi
  • Plastična posoda z odrezanim dnom

2. korak: Oblikujte robota

Oblikujte robota
Oblikujte robota
Oblikujte robota
Oblikujte robota
Oblikujte robota
Oblikujte robota

Oblikovati moramo nekaj, kar lahko iz vhodnega lijaka vzame eno samo kroglico, jo postavi pod spletno kamero in nato premakne v ustrezen koš.

Pobiranje kroglic

Odločil sem se, da bom prvi del naredil z 2 kosoma okrogle vezane plošče, vsaka z luknjo, izvrtano na istem mestu. Spodnji del je pritrjen, zgornji del pa pritrjen na koračni motor, ki ga lahko vrti pod lijakom, napolnjenim s kroglicami. Ko luknja potuje pod lijakom, pobere eno samo kroglico. Nato ga lahko zasukam pod spletno kamero in nato še naprej vrtim, dokler se ne ujema z luknjo v spodnjem delu, in takrat pade skozi.

Na tej sliki preizkušam, ali sistem lahko deluje. Vse je pritrjeno, razen zgornjega okroglega vezanega lesa, ki je spodaj pritrjen na koračni motor. Spletna kamera še ni nameščena. Na tej točki uporabljam samo nadzorno ploščo Phidget, da se obrnem na motor.

Skladiščenje kroglic

Naslednji del je oblikovanje sistema za shranjevanje vsake barve. Odločil sem se, da bom spodaj uporabil drugi koračni motor za podporo in vrtenje okrogle posode z enakomerno razporejenimi predelki. To lahko uporabite za obračanje ustreznega predelka pod luknjo, iz katere bo kroglica padla.

To sem zgradil s kartonom in lepilnim trakom. Najpomembnejša stvar tukaj je doslednost - vsak predel mora biti enake velikosti, celotna stvar pa mora biti enakomerno obtežena, da se vrti brez preskakovanja.

Odstranjevanje kroglic se izvede s tesno prilegajočim se pokrovom, ki naenkrat razkrije en sam predel, tako da se lahko kroglice izlijejo.

Kamera

Spletna kamera je nameščena na zgornji plošči med lijakom in lokacijo luknje na spodnji plošči. To omogoča sistemu, da si ogleda kroglico, preden jo spusti. Za osvetlitev kroglic pod kamero se uporablja LED, osvetlitev okolice pa je blokirana, da se zagotovi dosledno svetlobno okolje. To je zelo pomembno za natančno zaznavanje barv, saj lahko osvetlitev okolice resnično odvrže zaznano barvo.

Zaznavanje lokacije

Pomembno je, da sistem lahko zazna vrtenje ločevalnika kroglic. To se uporablja za nastavitev začetnega položaja pri zagonu, pa tudi za odkrivanje, ali je koračni motor prišel do sinhronizacije. V mojem sistemu se bo včasih med pobiranjem kroglica zagozdila, sistem pa je moral biti sposoben zaznati in obvladati to situacijo - tako, da naredi malo varnostne kopije in poskusi.

Obstaja veliko načinov za reševanje tega. Odločil sem se za uporabo magnetnega senzorja 1108 z magnetom, vgrajenim v rob zgornje plošče. To mi omogoča preverjanje položaja pri vsaki rotaciji. Boljša rešitev bi bila verjetno dajalnik na koračnem motorju, vendar sem imel 1108 naokoli, zato sem to uporabil.

Dokončajte robota

Na tej točki je bilo vse urejeno in preizkušeno. Čas je, da vse lepo namestite in preidete na programsko opremo za pisanje.

Dva koračna motorja poganjata krmilna enota STC1001. Središče HUB000 - USB VINT se uporablja za izvajanje koračnih krmilnikov, pa tudi za branje magnetnega senzorja in pogon LED. Spletna kamera in HUB0000 sta pritrjena na majhno zvezdišče USB. Za napajanje motorjev se uporablja 3031 pigtail in nekaj žice skupaj z 24V napajalnikom.

3. korak: Napišite kodo

Image
Image

Za ta projekt se uporabljata C# in Visual Studio 2015. Prenesite vir na vrhu te strani in sledite temu - glavni razdelki so opisani spodaj

Inicializacija

Najprej moramo ustvariti, odpreti in inicializirati objekte Phidget. To se naredi v dogodku nalaganja obrazca in upravljalcih prilog Phidget.

private void Form1_Load (pošiljatelj objekta, EventArgs e) {

/ * Inicializirajte in odprite Phidgets */

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

spodaj. HubPort = 1;

bottom. Attach += Bottom_Attach; spodaj. Detach += Bottom_Detach; bottom. PositionChange += Bottom_PositionChange; spodaj. 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 = res; led. Kanal = 0; led. Pritrditev += Led_Attach; led. Detach += Led_Detach; led. Open (); }

private void Led_Attach (pošiljatelj objekta, Phidget22. Events. AttachEventArgs e) {

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

private void MagSensor_Attach (pošiljatelj objekta, Phidget22. Events. AttachEventArgs e) {

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

private void Bottom_Attach (pošiljatelj objekta, Phidget22. Events. AttachEventArgs e) {

bottomAttachedChk. Checked = true; bottom. CurrentLimit = bottomCurrentLimit; bottom. Engaged = true; bottom. VelocityLimit = bottomVelocityLimit; bottom. Acceleration = bottomAccel; spodaj. DataInterval = 100; }

private void Top_Attach (pošiljatelj objekta, Phidget22. Events. AttachEventArgs e) {

topAttachedChk. Checked = true; top. CurrentLimit = topCurrentLimit; top. Engaged = res; top. RescaleFactor = -1; top. VelocityLimit = -topVelocityLimit; top. Acceleration = -topAccel; top. DataInterval = 100; }

Med inicializacijo preberemo tudi vse shranjene podatke o barvah, zato se lahko prejšnji zagon nadaljuje.

Pozicioniranje motorja

Koda za ravnanje z motorjem je sestavljena iz priročnih funkcij za premikanje motorjev. Motorji, ki sem jih uporabil, so 3, 200 1/16 korakov na vrtljaj, zato sem za to ustvaril konstanto.

Za zgornji motor obstajajo tri pozicije, ki jih želimo poslati motorju: spletno kamero, luknjo in magnet za pozicioniranje. Obstaja funkcija za potovanje na vsako od teh pozicij:

private void nextMagnet (Boolean wait = false) {

double posn = top. Position % stepsPerRev;

top. TargetPosition += (stepsPerRev - posn);

če (počakaj)

medtem ko (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);

če (počakaj)

medtem ko (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);

če (počakaj)

medtem ko (top. IsMoving) Thread. Sleep (50); }

Pred začetkom teka je zgornja plošča poravnana z magnetnim senzorjem. Za poravnavo zgornje plošče lahko kadar koli pokličete funkcijo alignMotor. Ta funkcija najprej hitro obrne ploščo do 1 polnega obrata, dokler ne vidi podatkov magneta nad pragom. Nato se nekoliko varnostno kopira in se počasi spet premika naprej, pri tem pa zajema podatke senzorja. Nazadnje nastavi položaj na največjo lokacijo podatkov o magnetu in ponastavi položaj zamika na 0. Tako mora biti največji položaj magneta vedno na vrhu (top. Position % stepsPerRev)

Boolean sawMagnet; dvojni magSensorMax = 0; zasebna void alignMotor () {

// Poiščite magnet

top. DataInterval = top. MinDataInterval;

sawMagnet = false;

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

int tryCount = 0;

poskusi ponovno:

top. TargetPosition += stepsPerRev;

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

if (! sawMagnet) {

if (tryCount> 3) {Console. WriteLine ("Poravnava ni uspela"); top. Engaged = false; bottom. Engaged = false; runtest = false; vrnitev; }

tryCount ++;

Console. WriteLine ("Ali smo obtičali? Poskušamo narediti varnostno kopijo …"); top. TargetPosition -= 600; medtem ko (top. IsMoving) Thread. Sleep (100);

pojdi poskusiti znova;

}

top. VelocityLimit = -100;

magData = nov seznam> (); magSensor. SensorChange += magSensorCollectPositionData; top. TargetPosition += 300; medtem ko (top. IsMoving) Thread. Sleep (100);

magSensor. SensorChange -= magSensorCollectPositionData;

top. VelocityLimit = -topVelocityLimit;

KeyValuePair max = magData [0];

foreach (par KeyValuePair v magData) if (pair. Value> max. Value) max = par;

top. AddPositionOffset (-max. Key);

magSensorMax = max. Vrednost;

top. TargetPosition = 0;

medtem ko (top. IsMoving) Thread. Sleep (100);

Console. WriteLine ("Poravnava uspela");

}

Seznam> magData;

private void magSensorCollectPositionData (pošiljatelj objekta, Phidget22. Events. VoltageRatioInputSensorChangeEventArgs e) {magData. Add (new KeyValuePair (top. Position, e. SensorValue)); }

private void magSensorStopMotor (pošiljatelj objekta, Phidget22. Events. VoltageRatioInputSensorChangeEventArgs e) {

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

Nazadnje, spodnji motor nadzirate tako, da ga pošljete v enega od položajev posode za kroglice. Za ta projekt imamo 19 mest. Algoritem izbere najkrajšo pot in se obrne v smeri urinega kazalca ali v nasprotni smeri.

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

return (int) Math. Round ((((posn * beadCompartments) / (dvojni) korakiPerRev));

} }

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

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

dvojni currentPosn = bottom. Position % stepsPerRev;

dvojni posnDiff = targetPosn - currentPosn;

// Naj bo to popoln korak

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

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

če (počakaj)

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

Kamera

OpenCV se uporablja za branje slik s spletne kamere. Nit kamere se zažene pred zagonom glavne niti za razvrščanje. Ta nit nenehno bere slike, izračuna povprečno barvo za določeno regijo z uporabo Mean in posodobi globalno barvno spremenljivko. Nit uporablja tudi HoughCircles, da poskuša zaznati kroglico ali luknjo na zgornji plošči, da izboljša površino, ki jo išče za zaznavanje barv. Prag in število HoughCircles sta bila določena s poskusi in napakami ter sta močno odvisna od spletne kamere, osvetlitve in razmika.

bool runVideo = true; bool videoRunning = false; VideoCapture zajem; Nit cvThread; Barva zaznana Barva; Logično zaznavanje = napačno; int detektirajCnt = 0;

zasebna void cvThreadFunction () {

videoRunning = false;

zajem = nov VideoCapture (izbrana kamera);

using (Window window = novo okno ("zajem")) {

Mat slika = nova Mat (); Mat image2 = nov Mat (); while (runVideo) {zajem. Read (slika); if (image. Empty ()) prelom;

če (zaznavanje)

detektirajCnt ++; else detektirajCnt = 0;

if (zaznavanje || circleDetectChecked || showDetectionImgChecked) {

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

if (showDetectionImgChecked)

slika = mlatilnica;

if (zaznavanje || circleDetectChecked) {

CircleSegment kroglica = ham. HoughCircles (HoughMethods. Gradient, 2, /*thres. Rows/4*/ 20, 200, 100, 20, 65); if (bead. Length> = 1) {image. Circle (kroglica [0]. Center, 3, nov skalar (0, 100, 0), -1); image. Circle (kroglica [0]. Center, (int) perlica [0]. Radius, nov skalar (0, 0, 255), 3); if (kroglica [0]. Radius> = 55) {Properties. Settings. Default.x = (decimalna) kroglica [0]. Center. X + (decimalna številka) (kroglica [0]. Radius / 2); Properties. Settings. Default.y = (decimalna) kroglica [0]. Center. Y - (decimalna) (kroglica [0]. Radius / 2); } else {Properties. Settings. Default.x = (decimalna) kroglica [0]. Center. X + (decimalna) (kroglica [0]. Radius); Properties. Settings. Default.y = (decimalna) kroglica [0]. Center. Y - (decimalna) (kroglica [0]. Radius); } Properties. Settings. Default.size = 15; Properties. Settings. Default.height = 15; } drugo {

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

if (krogi. Dolžina> 1) {Seznam xs = krogi. Izberite (c => c. Center. X). ToList (); xs. Sort (); Seznam ys = krogi. Izberite (c => c. Center. Y). ToList (); ys. Sort ();

int medianaX = (int) xs [xs. Count / 2];

int medianaY = (int) ys [ys. Count / 2];

if (medianX> slika. Širina - 15)

medianaX = slika. Širina - 15; if (medianY> image. Height - 15) mediaanY = image. Height - 15;

image. Circle (medianaX, medianaY, 100, nov skalar (0, 0, 150), 3);

if (zaznavanje) {

Properties. Settings. Default.x = medianX - 7; Properties. Settings. Default.y = medianaY - 7; Properties. Settings. Default.size = 15; Properties. Settings. Default.height = 15; }}}}}

Rect r = nove lastnosti Rect ((int). Nastavitve. Default.x, (int) Properties. Settings. Default.y, (int) Properties. Settings. Default.size, (int) Properties. Settings. Default.height);

Mat beadSample = nov Mat (slika, r);

Skalarni avgColor = Cv2. Mean (vzorec kroglic); foundColor = Color. FromArgb ((int) avgColor [2], (int) avgColor [1], (int) avgColor [0]);

image. Rectangle (r, nov skalar (0, 150, 0));

window. ShowImage (slika);

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

videoRunning = false;

} }

private void cameraStartBtn_Click (pošiljatelj objekta, EventArgs e) {

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

cvThread = nova nit (nov začetek niti (cvThreadFunction)); runVideo = res; cvThread. Start (); cameraStartBtn. Text = "stop"; while (! videoRunning) Thread. Sleep (100);

updateColorTimer. Start ();

} drugo {

runVideo = false; cvThread. Join (); cameraStartBtn. Text = "začetek"; }}

Barva

Zdaj lahko določimo barvo kroglice in se na podlagi te barve odločimo, v katero posodo jo bomo spustili.

Ta korak temelji na primerjavi barv. Želimo biti sposobni ločiti barve za omejitev lažno pozitivnih rezultatov, hkrati pa omogočiti dovolj praga za omejitev lažno negativnih. Primerjava barv je pravzaprav presenetljivo zapletena, saj način, kako računalniki shranjujejo barve kot RGB, in način, kako ljudje dojemajo barve, nista linearno povezani. Da bi bilo stanje še slabše, je treba upoštevati tudi barvo svetlobe, pod katero se gleda barva.

Obstajajo zapleteni algoritmi za izračun barvne razlike. Uporabljamo CIE2000, ki prikaže številko blizu 1, če se 2 barvi človeku ne bi mogli razlikovati. Za te zapletene izračune uporabljamo knjižnico ColorMine C#. Ugotovljeno je bilo, da vrednost 5 DeltaE ponuja dober kompromis med lažno pozitivnim in lažno negativnim.

Ker je pogosto več barv kot posod, je zadnje mesto rezervirano kot lovilec. Na splošno sem jih postavil na stran, da bi tekel skozi stroj na drugem prehodu.

Seznam

colours = new List (); List colorPanels = nov List (); Barve seznamaTxts = nov List (); List colorCnts = nov List ();

const int numColorSpots = 18;

const int neznanoColorIndex = 18; int findColorPosition (Barva c) {

Console. WriteLine ("Iskanje barve …");

var cRGB = nov Rgb ();

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

int bestMatch = -1;

dvojno ujemanjeDelta = 100;

for (int i = 0; i <barve.števanje; i ++) {

var RGB = nov Rgb ();

RGB. R = barve . R; RGB. G = barve . G; RGB. B = barve . B;

dvojna delta = cRGB. Primerjaj (RGB, nova CieDe2000Comparison ());

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

if (matchDelta <5) {Console. WriteLine ("Najdeno! (Posn:" + bestMatch + "Delta:" + matchDelta + ")"); vrni bestMatch; }

if (colors. Count <numColorSpots) {Console. WriteLine ("Nova barva!"); barve. Dodaj (c); this. BeginInvoke (novo dejanje (setBackColor), nov objekt {barve. Število - 1}); writeOutColors (); return (barve. Število - 1); } else {Console. WriteLine ("Neznana barva!"); return unknownColorIndex; }}

Logika razvrščanja

Funkcija razvrščanja združuje vse kose, da dejansko razvrsti kroglice. Ta funkcija deluje v namenski niti; premikanje zgornje plošče, zaznavanje barve kroglic, polaganje v koš, pazljivost, da zgornja plošča ostane poravnana, štetje kroglic itd. Prav tako se preneha izvajati, ko se košara za napolnitev napolni.

Boolean runtest = false; void colourTest () {

if (! top. Angaged)

top. Engaged = res;

if (! spodaj. Angažirano)

bottom. Engaged = true;

while (runtest) {

nextMagnet (res);

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

nextCamera (res);

zaznavanje = res;

while (detectionCnt <5) Thread. Sleep (25); Console. WriteLine ("Število zaznav:" + detektirajCnt); zaznavanje = napačno;

Barva c = zaznanaBarva;

this. BeginInvoke (novo dejanje (setColorDet), nov objekt {c}); int i = findColorPosition (c);

SetBottomPosition (i, res);

nextHole (res); colorCnts ++; this. BeginInvoke (novo dejanje (setColorTxt), nov objekt {i}); Thread. Sleep (250);

if (colorCnts [unknownColorIndex]> 500) {

top. Engaged = false; bottom. Engaged = false; runtest = false; this. BeginInvoke (novo dejanje (setGoGreen), nič); vrnitev; }}}

private void colourTestBtn_Click (pošiljatelj objekta, EventArgs e) {

if (colourTestThread == null ||! colourTestThread. IsAlive) {colourTestThread = nova nit (nov ThreadStart (colourTest)); runtest = res; colourTestThread. Start (); colourTestBtn. Text = "STOP"; colourTestBtn. BackColor = Barva. Rdeča; } else {runtest = false; colourTestBtn. Text = "GO"; colourTestBtn. BackColor = Barva. Zelena; }}

Na tej točki imamo delovni program. Nekateri koščki kode so ostali v članku, zato si oglejte vir, da ga dejansko zaženete.

Natečaj optike
Natečaj optike

Druga nagrada na tekmovanju optike

Priporočena: