Sadržaj:

Robotsko sortiranje perlica: 3 koraka (sa slikama)
Robotsko sortiranje perlica: 3 koraka (sa slikama)

Video: Robotsko sortiranje perlica: 3 koraka (sa slikama)

Video: Robotsko sortiranje perlica: 3 koraka (sa slikama)
Video: ЗАПРЕЩЁННЫЕ ТОВАРЫ с ALIEXPRESS 2023 ШТРАФ и ТЮРЬМА ЛЕГКО! 2024, Juli
Anonim
Image
Image
Robotsko sortiranje perli
Robotsko sortiranje perli
Robotsko sortiranje perli
Robotsko sortiranje perli
Robotsko sortiranje perli
Robotsko sortiranje perli

U ovom projektu izgradit ćemo robota za sortiranje Perlerovih perlica po boji.

Oduvijek sam želio izgraditi robota za sortiranje boja, pa kad se moja kći zainteresirala za izradu Perler perlica, vidio sam ovo kao savršenu priliku.

Perlerove perle koriste se za stvaranje spojenih umjetničkih projekata postavljanjem mnogo perlica na ploču s pločama, a zatim ih istopiti peglom. Obično kupujete ove perle u ogromnim pakovanjima od 22 000 perlica u različitim bojama i trošite puno vremena na traženje boje koju želite, pa sam mislio da će njihovo sortiranje povećati umjetničku efikasnost.

Radim za Phidgets Inc. pa sam za ovaj projekt uglavnom koristio Phidgets - ali to se može učiniti pomoću bilo kojeg prikladnog hardvera.

Korak 1: Hardver

Evo šta sam koristio da ovo napravim. Napravio sam ga 100% sa dijelovima sa stranice phidgets.com i stvarima koje sam imao po kući.

Ploče Phidgets, Motori, Hardver

  • HUB0000 - VINT Hub Phidget
  • 1108 - Magnetski senzor
  • 2x STC1001 - 2.5A Stepper Phidget
  • 2x 3324 - 42STH38 NEMA -17 Bipolarni stepen bez zupčanika
  • 3x 3002 - Phidget kabel 60 cm
  • 3403 - USB2.0 4 -portno čvorište
  • 3031 - Ženski pigtail 5.5x2.1mm
  • 3029 - 2 -žilni 100 'upleteni kabel
  • 3604 - 10 mm bijela LED dioda (vreća od 10)
  • 3402 - USB web kamera

Ostali dijelovi

  • Napajanje 24VDC 2.0A
  • Stari otpad i metal iz garaže
  • Zip kravate
  • Plastična posuda s odrezanim dnom

Korak 2: Dizajnirajte robota

Dizajnirajte robota
Dizajnirajte robota
Dizajnirajte robota
Dizajnirajte robota
Dizajnirajte robota
Dizajnirajte robota

Moramo dizajnirati nešto što može uzeti jednu kuglicu iz ulaznog lijevka, postaviti je ispod web kamere, a zatim je premjestiti u odgovarajuću korpu.

Preuzimanje perli

Odlučio sam napraviti prvi dio s 2 komada okrugle šperploče, svaki s rupom izbušenom na istom mjestu. Donji dio je fiksiran, a gornji dio je pričvršćen za koračni motor, koji ga može rotirati ispod lijevka ispunjenog kuglicama. Kad rupa prođe ispod lijevka, uhvati jednu kuglicu. Zatim ga mogu rotirati ispod web kamere, a zatim dalje rotirati dok se ne poklapa s rupom na donjem dijelu, u kojem trenutku prolazi.

Na ovoj slici testiram da li sistem može raditi. Sve je fiksirano osim gornjeg okruglog komada šperploče, koji je pričvršćen na koračni motor ispod vidljivog polja. Web kamera još nije montirana. U ovom trenutku samo koristim Phidget Control Panel za okretanje motora.

Skladištenje perli

Sljedeći dio je dizajniranje sistema kanti za držanje svake boje. Odlučio sam upotrijebiti drugi koračni motor ispod za podršku i rotaciju okruglog spremnika s ravnomjerno raspoređenim odjeljcima. Ovo se može koristiti za okretanje odgovarajućeg odjeljka ispod rupe iz koje će perla ispasti.

Napravio sam ovo koristeći karton i ljepljivu traku. Najvažnija stvar ovdje je dosljednost - svaki odjeljak trebao bi biti iste veličine, a cijela bi stvar trebala biti podjednako ponderirana tako da se okreće bez preskakanja.

Uklanjanje perlica postiže se pomoću čvrsto zatvorenog poklopca koji izlaže jedan pretinac odjednom, tako da se perle mogu izliti.

Kamera

Web kamera je postavljena preko gornje ploče između lijevka i donje rupe. Ovo omogućava sistemu da pogleda zrno pre nego što ga spusti. LED se koristi za osvjetljavanje perlica ispod kamere, a ambijentalno svjetlo je blokirano, kako bi se osiguralo dosljedno svjetlosno okruženje. Ovo je vrlo važno za precizno otkrivanje boja, jer ambijentalno osvjetljenje zaista može odbaciti uočenu boju.

Otkrivanje lokacije

Važno je da sistem može otkriti rotaciju separatora zrna. Ovo se koristi za postavljanje početnog položaja pri pokretanju, ali i za otkrivanje da li se koračni motor nije sinkronizirao. U mom sistemu, zrnce će se ponekad zaglaviti dok se podiže, a sistem je morao biti u stanju da otkrije i reši ovu situaciju - tako što će napraviti malu rezervnu kopiju i isprobati.

Postoji mnogo načina za rješavanje ovoga. Odlučio sam koristiti magnetski senzor 1108, s magnetom ugrađenim u rub gornje ploče. To mi omogućava da provjerim položaj pri svakoj rotaciji. Bolje rješenje bi vjerojatno bio koder na koračnom motoru, ali imao sam 1108 koji je ležao pa sam to iskoristio.

Dovrši robota

U ovom trenutku sve je razrađeno i testirano. Vrijeme je da sve lijepo montirate i pređete na softver za pisanje.

Dva koračna motora pokreću stepenasti kontroleri STC1001. HUB000 - USB VINT čvorište koristi se za pokretanje koračnih kontrolera, kao i za čitanje magnetskog senzora i pokretanje LED diode. Web kamera i HUB0000 su spojeni na mali USB hub. Za napajanje motora koristi se 3031 pigtail i neka žica zajedno s napajanjem od 24V.

Korak 3: Napišite kôd

Image
Image

Za ovaj projekt koriste se C# i Visual Studio 2015. Preuzmite izvor na vrhu ove stranice i slijedite ga dalje - glavni odjeljci navedeni su u nastavku

Inicijalizacija

Prvo moramo stvoriti, otvoriti i pokrenuti Phidget objekte. To se radi u obliku učitavanja obrasca i rukovateljima za dodavanje Phidget -a.

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

/ * Inicirajte i otvorite 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; bottom. 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 (pošiljalac objekta, Phidget22. Events. AttachEventArgs e) {

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

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

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

private void Bottom_Attach (pošiljalac objekta, 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 (pošiljalac objekta, 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; }

Tokom inicijalizacije čitamo i sve sačuvane podatke o boji, tako da se prethodni rad može nastaviti.

Pozicioniranje motora

Kôd za rukovanje motorom sastoji se od praktičnih funkcija za pomicanje motora. Motori koje sam koristio su 3, 200 1/16 koraka po okretu, pa sam za to stvorio konstantu.

Za gornji motor postoje 3 pozicije koje želimo poslati na motor: web kameru, rupu i magnet za pozicioniranje. Postoji funkcija za putovanje na svaku od ovih pozicija:

private void nextMagnet (Boolean wait = false) {

double posn = top. Position % stepsPerRev;

top. TargetPosition += (stepsPerRev - posn);

ako (čekaj)

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

ako (čekaj)

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

ako (čekaj)

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

Prije početka trčanja gornja ploča se poravnava pomoću magnetskog senzora. Funkcija alignMotor može se pozvati u bilo kojem trenutku radi poravnavanja gornje ploče. Ova funkcija prvo brzo okreće ploču do 1 punog okreta dok ne vidi podatke magneta iznad praga. Zatim se malo sigurnosno kopira i polako se ponovno pomiče prema naprijed, bilježeći podatke senzora u hodu. Konačno, postavlja položaj na maksimalnu lokaciju podataka o magnetu i vraća pomak položaja na 0. Dakle, maksimalna pozicija magneta uvijek treba biti na (vrh. Pozicija % stepsPerRev)

Boolean sawMagnet; dvostruki magSensorMax = 0; privatna void alignMotor () {

// Pronađite magnet

top. DataInterval = top. MinDataInterval;

sawMagnet = false;

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

int tryCount = 0;

pokušaj ponovo:

top. TargetPosition += stepsPerRev;

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

if (! sawMagnet) {

if (tryCount> 3) {Console. WriteLine ("Poravnanje nije uspjelo"); top. Engaged = false; bottom. Engaged = false; runtest = false; return; }

tryCount ++;

Console. WriteLine ("Jesmo li zaglavljeni? Pokušavamo izraditi sigurnosnu kopiju …"); top. TargetPosition -= 600; while (top. IsMoving) Thread. Sleep (100);

goto tryagain;

}

top. VelocityLimit = -100;

magData = nova 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 par u magData) 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 ("Poravnanje uspjelo");

}

Lista> magData;

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

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

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

Na kraju, donjim motorom se upravlja slanjem u jedan od položaja spremnika perlica. Za ovaj projekt imamo 19 pozicija. Algoritam bira najkraći put i okreće se u smjeru kazaljke na satu ili u suprotnom smjeru.

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; dvostruki targetPosn = (posn * stepsPerRev) / beadCompartments;

dvostruka currentPosn = bottom. Position % stepsPerRev;

dvostruki posnDiff = targetPosn - currentPosn;

// Zadržite to kao potpune korake

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

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

ako (čekaj)

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

Kamera

OpenCV se koristi za čitanje slika s web kamere. Nit kamere se pokreće prije pokretanja glavne niti za sortiranje. Ova nit kontinuirano čita slike, izračunava prosječnu boju za određenu regiju koristeći Mean i ažurira globalnu varijablu boja. Nit također koristi HoughCircles kako bi pokušao otkriti ili perlicu, ili rupu na gornjoj ploči, kako bi poboljšao područje koje gleda radi otkrivanja boje. Prag i HoughCircles brojevi određeni su pokušajem i greškom i uvelike ovise o web kameri, osvjetljenju i razmaku.

bool runVideo = true; bool videoRunning = false; VideoCapture snimanje; Tema cvThread; Boja otkrivenaBoja; Logičko otkrivanje = netačno; int detectionCnt = 0;

private void cvThreadFunction () {

videoRunning = false;

hvatanje = nova VideoCapture (odabrana kamera);

koristeći (Window window = new Window ("capture")) {

Mat slika = nova Mat (); Mat slika2 = nova Mat (); while (runVideo) {hvatanje. Read (slika); if (image. Empty ()) break;

ako (otkrivanje)

detectCnt ++; else detektujCnt = 0;

if (otkrivanje || circleDetectChecked || showDetectionImgChecked) {

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

if (showDetectionImgChecked)

slika = mlata;

if (otkrivanje || circleDetectChecked) {

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

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

if (krugovi. Dužina> 1) {Lista xs = krugovi. Odaberite (c => c. Center. X). ToList (); xs. Sort (); Lista ys = krugovi. Odaberite (c => c. Center. Y). ToList (); ys. Sort ();

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

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

if (medianX> slika. Širina - 15)

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

image. Circle (medijanX, medijanY, 100, novi skalar (0, 0, 150), 3);

if (otkrivanje) {

Properties. Settings. Default.x = medianX - 7; Properties. Settings. Default.y = medijanY - 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 (slika, r);

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

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

window. ShowImage (slika);

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

videoRunning = false;

} }

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

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

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

updateColorTimer. Start ();

} else {

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

Boja

Sada smo u mogućnosti odrediti boju zrna i na osnovu te boje odlučiti u koji spremnik ga staviti.

Ovaj korak se oslanja na poređenje boja. Želimo biti u mogućnosti razlikovati boje kako bismo ograničili lažno pozitivne rezultate, ali i dopustiti dovoljan prag za ograničavanje lažnih negativa. Poređenje boja je zapravo iznenađujuće složeno, jer način na koji računari skladište boje kao RGB i način na koji ljudi percipiraju boje nemaju linearnu korelaciju. Da stvar bude gora, mora se uzeti u obzir i boja svjetla pod kojom se posmatra boja.

Postoje složeni algoritmi za izračunavanje razlike u boji. Koristimo CIE2000, koji daje broj blizu 1 ako se 2 boje ne razlikuju od čovjeka. Za ove komplikovane proračune koristimo biblioteku ColorMine C#. Utvrđeno je da vrijednost DeltaE od 5 nudi dobar kompromis između lažno pozitivnog i lažno negativnog.

Budući da često postoji više boja nego spremnika, posljednja pozicija je rezervirana kao spremna kanta. Općenito sam ovo ostavio po strani da bih prošao kroz mašinu na drugom prolazu.

Lista

colours = new List (); List colorPanels = new List (); Boje listeTxts = nova Lista (); List colorCnts = novi List ();

const int numColorSpots = 18;

const int unknownColorIndex = 18; int findColorPosition (Boja c) {

Console. WriteLine ("Traženje boje …");

var cRGB = novi Rgb ();

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

int bestMatch = -1;

dvostruko podudaranjeDelta = 100;

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

var RGB = novi Rgb ();

RGB. R = boje . R; RGB. G = boje . G; RGB. B = boje . B;

double delta = cRGB. Compare (RGB, novi CieDe2000Comparison ());

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

if (matchDelta <5) {Console. WriteLine ("Pronađeno! (Posn:" + bestMatch + "Delta:" + matchDelta + ")"); return bestMatch; }

if (colors. Count <numColorSpots) {Console. WriteLine ("Nova boja!"); boje. Dodaj (c); this. BeginInvoke (nova akcija (setBackColor), novi objekt {colours. Count - 1}); writeOutColors (); return (boje. Broj - 1); } else {Console. WriteLine ("Nepoznata boja!"); return unknownColorIndex; }}

Logika sortiranja

Funkcija sortiranja objedinjuje sve dijelove kako bi zapravo sortirali perlice. Ova funkcija radi u namjenskoj niti; pomicanje gornje ploče, otkrivanje boje perli, stavljanje u kantu, paženje da gornja ploča ostane poravnata, brojanje perlica itd. Također prestaje s radom kada se kanta za hvatanje napuni - U protivnom jednostavno završimo s prelivanjem perlica.

Boolean runtest = false; void colourTest () {

if (! top.zaposlen)

top. Engaged = true;

if (! dno. Angažirano)

bottom. Engaged = true;

while (runtest) {

nextMagnet (istina);

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

nextCamera (true);

otkrivanje = tačno;

while (detectionCnt <5) Thread. Sleep (25); Console. WriteLine ("Detect Count:" + detectionCnt); otkrivanje = lažno;

Boja c = otkrivenaBoja;

this. BeginInvoke (nova akcija (setColorDet), novi objekt {c}); int i = findColorPosition (c);

SetBottomPosition (i, tačno);

nextHole (true); colorCnts ++; this. BeginInvoke (nova akcija (setColorTxt), novi objekt {i}); Thread. Sleep (250);

if (colorCnts [unknownColorIndex]> 500) {

top. Engaged = false; bottom. Engaged = false; runtest = false; this. BeginInvoke (nova akcija (setGoGreen), null); return; }}}

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

if (colourTestThread == null ||! colourTestThread. IsAlive) {colourTestThread = nova nit (novi ThreadStart (colourTest)); runtest = true; colourTestThread. Start (); colourTestBtn. Text = "STOP"; colourTestBtn. BackColor = Color. Red; } else {runtest = false; colourTestBtn. Text = "IDI"; colourTestBtn. BackColor = Color. Green; }}

U ovom trenutku imamo radni program. Neki dijelovi koda izostavljeni su iz članka, pa pogledajte izvor da biste ga zapravo pokrenuli.

Takmičenje u optici
Takmičenje u optici

Druga nagrada na takmičenju iz optike

Preporučuje se: