INHOUDSOPGAWE:

Robotiese kralsortering: 3 stappe (met foto's)
Robotiese kralsortering: 3 stappe (met foto's)

Video: Robotiese kralsortering: 3 stappe (met foto's)

Video: Robotiese kralsortering: 3 stappe (met foto's)
Video: Geld verdienen op YouTube met video's van 5 minuten en verdien $ 40.000 per maand! 2024, Julie
Anonim
Image
Image
Robotiese kralsortering
Robotiese kralsortering
Robotiese kralsortering
Robotiese kralsortering
Robotiese kralsortering
Robotiese kralsortering

In hierdie projek bou ons 'n robot om Perler -krale volgens kleur te sorteer.

Ek wou nog altyd 'n kleursorteringsrobot bou, so toe my dogter belangstel in Perler -kralewerk, het ek dit as 'n perfekte geleentheid beskou.

Perler -krale word gebruik om gesmelte kunsprojekte te skep deur baie krale op 'n plankbord te plaas en dit dan saam met 'n yster te smelt. U koop gewoonlik hierdie krale in reuse 22 000 krale met gemengde kleur, en u spandeer baie tyd om te soek na die kleur wat u wil hê, so ek het gedink dat die sortering daarvan die kunsdoeltreffendheid sou verhoog.

Ek werk vir Phidgets Inc., en ek gebruik meestal Phidgets vir hierdie projek - maar dit kan gedoen word met behulp van enige geskikte hardeware.

Stap 1: Hardeware

Hier is wat ek gebruik het om dit te bou. Ek het dit 100% gebou met onderdele van phidgets.com en dinge wat ek in die huis gehad het.

Phidgets borde, motors, hardeware

  • HUB0000 - VINT Hub Phidget
  • 1108 - Magnetiese sensor
  • 2x STC1001 - 2.5A Stepper Phidget
  • 2x 3324 - 42STH38 NEMA -17 Bipolêre ratlose stapper
  • 3x 3002 - Phidget -kabel 60 cm
  • 3403 - USB2.0 4 -poort -hub
  • 3031 - Varkstertjie vroulik 5.5x2.1mm
  • 3029 - 2 -draads 100 'gedraaide kabel
  • 3604 - 10 mm wit LED (sak van 10)
  • 3402 - USB -webkamera

Ander onderdele

  • 24VDC 2.0A kragtoevoer
  • Skrap hout en metaal uit die motorhuis
  • Ritssluitings
  • Plastiekhouer met onderkant afgesny

Stap 2: Ontwerp die robot

Ontwerp die robot
Ontwerp die robot
Ontwerp die robot
Ontwerp die robot
Ontwerp die robot
Ontwerp die robot

Ons moet iets ontwerp wat 'n enkele kraal uit die invoerhouer kan haal, dit onder die webcam kan plaas en dit dan in die toepaslike as kan plaas.

Kraal afhaal

Ek het besluit om die eerste deel te doen met 2 stukke ronde laaghout, elk met 'n gat wat op dieselfde plek geboor is. Die onderste stuk is vasgemaak en die boonste stuk is vasgemaak aan 'n stapmotor wat dit kan draai onder 'n bak vol krale. As die gat onder die houer beweeg, tel dit 'n enkele kraal op. Ek kan dit dan onder die webcam draai, en dan verder draai totdat dit by die gat in die onderste deel pas, en dan val dit deur.

Op hierdie foto toets ek of die stelsel kan werk. Alles is reggemaak, behalwe die boonste ronde stuk laaghout, wat onderaan 'n stapmotor geheg is. Die webkamera is nog nie geïnstalleer nie. Ek gebruik net die Phidget -bedieningspaneel om op hierdie stadium na die motor te skakel.

Kraalberging

Die volgende deel is om die asblikstelsel vir elke kleur te ontwerp. Ek het besluit om 'n tweede stapmotor hieronder te gebruik om 'n ronde houer met eweredig verdeelde kompartemente te ondersteun en te draai. Dit kan gebruik word om die korrekte kompartement te draai onder die gat waaruit die kraal sal val.

Ek het dit gebou met karton en kleefband. Die belangrikste ding hier is konsekwentheid - elke kompartement moet dieselfde grootte hê, en die hele ding moet eweredig geweeg word sodat dit draai sonder om oor te slaan.

Kraal verwyder word deur middel van 'n styfpassende deksel wat 'n enkele kompartement op 'n slag blootstel, sodat die krale uitgegooi kan word.

Kamera

Die webcam is gemonteer op die boonste plaat tussen die houer en die onderste gatgat. Dit laat die stelsel na die kraal kyk voordat hy dit laat val. 'N LED word gebruik om die krale onder die kamera te verlig, en omringende lig word geblokkeer om 'n konsekwente beligting te verseker. Dit is baie belangrik vir akkurate kleuropsporing, aangesien omgevingsbeligting die waargeneemde kleur werklik kan laat val.

Liggingopsporing

Dit is belangrik dat die stelsel die rotasie van die kralskeider kan opspoor. Dit word gebruik om die beginposisie by die aanvang op te stel, maar ook om vas te stel of die stapmotor nie sinchroniseer het nie. In my stelsel kan daar soms 'n kraal stamp terwyl dit opgetel word, en die stelsel moes hierdie situasie kan opspoor en hanteer - deur 'n bietjie te rugsteun en weer te probeer.

Daar is baie maniere om dit te hanteer. Ek het besluit om 'n 1108 magnetiese sensor te gebruik, met 'n magneet ingebed in die rand van die boonste plaat. Dit stel my in staat om die posisie by elke rotasie te verifieer. 'N Beter oplossing sou waarskynlik 'n enkodeerder op die stepper motor wees, maar ek het 'n 1108 laat lê, so ek het dit gebruik.

Voltooi die robot

Op hierdie stadium is alles uitgewerk en getoets. Dit is tyd om alles mooi te monteer en oor te gaan na skryfprogrammatuur.

Die 2 stepper motors word aangedryf deur STC1001 stepper controllers. 'N HUB000 - USB VINT -hub word gebruik om die stepper -beheerders te bestuur, sowel as om die magnetiese sensor te lees en die LED aan te dryf. Die webkamera en HUB0000 is albei aan 'n klein USB -hub gekoppel. 'N 3031 -varkstaart en 'n bietjie draad word gebruik, asook 'n 24V -kragtoevoer om die motors aan te dryf.

Stap 3: Skryf kode

Image
Image

C# en Visual Studio 2015 word vir hierdie projek gebruik. Laai die bron bo -aan hierdie bladsy af en volg dit - die hoofafdelings word hieronder uiteengesit

Inisialisering

Eerstens moet ons die Phidget -voorwerpe skep, oopmaak en inisialiseer. Dit word gedoen in die vorm laai gebeurtenis, en die Phidget heg hanteerders.

private leemte Form1_Load (voorwerp sender, EventArgs e) {

/ * Initialiseer en maak Phidgets oop */

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

bottom. HubPort = 1;

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

magSensor. HubPort = 2;

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

led. HubPort = 5;

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

private leemte Led_Attach (voorwerp sender, Phidget22. Events. AttachEventArgs e) {

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

private leemte MagSensor_Attach (voorwerp sender, Phidget22. Events. AttachEventArgs e) {

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

private leegte Bottom_Attach (voorwerp sender, Phidget22. Events. AttachEventArgs e) {

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

private leemte Top_Attach (voorwerp sender, 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; }

Ons lees ook alle gestoorde kleurinligting tydens die inisialisering, sodat u met die vorige lopie kan voortgaan.

Motorposisionering

Die motorhanteringskode bestaan uit geriefsfunksies vir die verskuiwing van die motors. Die motors wat ek gebruik het, is 3 200 1/16 stappe per omwenteling, so ek het 'n konstante hiervoor geskep.

Vir die boonste motor is daar drie posisies wat ons na die motor wil stuur: die webcam, die gat en die posisioneringsmagneet. Daar is 'n funksie om na elk van hierdie posisies te reis:

private leemte nextMagnet (Booleaanse wag = vals) {

double posn = top. Position % stepsPerRev;

top. TargetPosition += (stepsPerRev - posn);

as (wag)

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

private leegte volgende kamera (Boole wag = vals) {

double posn = top. Position % stepsPerRev; if (posn <Properties. Settings. Default.cameraOffset) top. TargetPosition += (Properties. Settings. Default.cameraOffset - posn); anders top. TargetPosition + = ((Properties. Settings. Default.cameraOffset - posn) + stepsPerRev);

as (wag)

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

private leemte nextHole (Boolean wait = false) {

double posn = top. Position % stepsPerRev; if (posn <Properties. Settings. Default.holeOffset) top. TargetPosition += (Properties. Settings. Default.holeOffset - posn); anders top. TargetPosition + = ((Properties. Settings. Default.holeOffset - posn) + stepsPerRev);

as (wag)

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

Voordat u met 'n lopie begin, word die boonste plaat met die magnetiese sensor in lyn gebring. Die alignMotor -funksie kan te eniger tyd opgeroep word om die boonste plaat in lyn te bring. Hierdie funksie draai die plaat eers vinnig tot 1 volle omwenteling totdat dit magneetdata bo 'n drumpel sien. Dit maak dan 'n bietjie terug en beweeg stadig vorentoe en neem sensordata vas soos dit gaan. Uiteindelik stel dit die posisie in op die maksimum magneetdatalokasie en stel die posisieverstelling terug op 0. Dus moet die maksimum magneetposisie altyd op wees (top. Position % stepsPerRev)

DraadlynMotorThread; Boole saagmagneet; dubbel magSensorMax = 0; private leemte alignMotor () {

// Soek die magneet

top. DataInterval = top. MinDataInterval;

sawMagnet = vals;

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

int tryCount = 0;

probeer weer:

top. TargetPosition += stepsPerRev;

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

as (! sawMagnet) {

if (tryCount> 3) {Console. WriteLine ("Belyning misluk"); top. Engaged = false; bottom. Engaged = false; runtest = vals; terugkeer; }

tryCount ++;

Console. WriteLine ("sit ons vas? Probeer 'n rugsteun …"); top. TargetPosition -= 600; terwyl (top. IsMoving) Thread. Sleep (100);

weer probeer;

}

top. VelocityLimit = -100;

magData = nuwe Lys> (); magSensor. SensorChange += magSensorCollectPositionData; top. TargetPosition += 300; terwyl (top. IsMoving) Thread. Sleep (100);

magSensor. SensorChange -= magSensorCollectPositionData;

top. VelocityLimit = -topVelocityLimit;

KeyValuePair max = magData [0];

foreach (KeyValuePair -paar in magData) as (pair. Walue> max. Walue) max = paar;

top. AddPositionOffset (-max. Key);

magSensorMax = maksimum waarde;

top. TargetPosition = 0;

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

Console. WriteLine ("Uitlijnen geslaag");

}

Lys> magData;

private void magSensorCollectPositionData (voorwerp sender, Phidget22. Events. VoltageRatioInputSensorChangeEventArgs e) {magData. Add (nuwe KeyValuePair (top. Position, e. SensorValue)); }

private void magSensorStopMotor (voorwerp sender, Phidget22. Events. VoltageRatioInputSensorChangeEventArgs e) {

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

Laastens word die onderste motor beheer deur dit na een van die kraalhouerposisies te stuur. Vir hierdie projek het ons 19 posisies. Die algoritme kies 'n kortste pad en draai met die kloksgewys of teen die kloksgewys.

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

return (int) Math. Round (((posn * beadCompartements) / (double) stepsPerRev));

} }

private leemte SetBottomPosition (int posn, bool wag = vals) {

posn = posn % beadCompartiments; dubbele targetPosn = (posn * stepsPerRev) / beadCompartiments;

double currentPosn = bottom. Position % stepsPerRev;

double posnDiff = targetPosn - currentPosn;

// Hou dit as volledige stappe

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

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

as (wag)

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

Kamera

OpenCV word gebruik om beelde van die webkamera te lees. Die kameradraad word begin voordat die hoofsorteringsdraad begin word. Hierdie draad lees voortdurend in beelde, bereken 'n gemiddelde kleur vir 'n spesifieke streek met behulp van gemiddelde en werk 'n globale kleurveranderlike by. Die draad gebruik ook HoughCircles om 'n kraal of die gat in die boonste plaat te probeer opspoor om die gebied waarna dit kyk vir die kleuropsporing te verfyn. Die drempel- en HoughCircles -getalle is bepaal deur middel van proef en fout, en hang baie af van die webcam, beligting en afstand.

bool runVideo = true; bool videoRunning = false; VideoCapture -opname; Draad cvThread; Kleur opgespoorKleur; Booleaanse opsporing = vals; int detectCnt = 0;

private leegte cvThreadFunction () {

videoRunning = vals;

capture = nuwe VideoCapture (geselekteerde kamera);

met behulp van (Venstervenster = nuwe venster ("vang")) {

Matbeeld = nuwe Mat (); Mat image2 = nuwe Mat (); terwyl (runVideo) {capture. Read (image); as (image. Empty ()) breek;

as (opspoor)

detectCnt ++; anders detectCnt = 0;

if (detecting || circleDetectChecked || showDetectionImgChecked) {

Cv2. CvtColor (image, image2, ColorConversionCodes. BGR2GRAY); Mat dors = image2. Threshold ((dubbel) Properties. Settings. Default.videoThresh, 255, ThresholdTypes. Binary); dors = dors. GaussianBlur (nuwe OpenCvSharp. Size (9, 9), 10);

as (showDetectionImgChecked)

beeld = dors;

if (detecting || circleDetectChecked) {

CircleSegment kraal = dors. HoughCircles (HoughMethods. Gradient, 2, /*thres. Rows/4*/ 20, 200, 100, 20, 65); if (bead. Length> = 1) {image. Circle (kraal [0]. Center, 3, nuwe Scalar (0, 100, 0), -1); image. Circle (kraal [0]. Sentrum, (int) kraal [0]. Radius, nuwe Scalar (0, 0, 255), 3); if (kraal [0]. Radius> = 55) {Properties. Settings. Default.x = (desimale) kraal [0]. Center. X + (desimaal) (kraal [0]. Radius / 2); Properties. Settings. Default.y = (desimale) kraal [0]. Center. Y - (desimaal) (kraal [0]. Radius / 2); } anders {Properties. Settings. Default.x = (desimale) kraal [0]. Center. X + (desimaal) (kraal [0]. Radius); Properties. Settings. Default.y = (desimale) kraal [0]. Center. Y - (desimale) (kraal [0]. Radius); } Properties. Settings. Default.size = 15; Eiendomme. Settings. Default.height = 15; } anders {

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

if (sirkels. Lengte> 1) {Lys xs = sirkels. Kies (c => c. Center. X). ToList (); xs. Sort (); Lys ys = sirkels. Kies (c => c. Center. Y). ToList (); ys. Sort ();

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

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

if (mediaanX> image. Width - 15)

mediaanX = beeld. Breedte - 15; as (mediaanY> beeld. Hoogte - 15) mediaanY = beeld. Hoogte - 15;

image. Circle (mediaanX, mediaanY, 100, nuwe skalaar (0, 0, 150), 3);

as (opspoor) {

Properties. Settings. Default.x = mediaanX - 7; Properties. Settings. Default.y = mediaan Y - 7; Properties. Settings. Default.size = 15; Eiendomme. Settings. Default.height = 15; }}}}}

Rekt r = nuwe Rect ((int) Properties. Settings. Default.x, (int) Properties. Settings. Default.y, (int) Properties. Settings. Default.size, (int) Properties. Settings. Default.height);

Mat beadSample = nuwe mat (beeld, r);

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

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

venster. ShowImage (beeld);

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

videoRunning = vals;

} }

private void cameraStartBtn_Click (voorwerp sender, EventArgs e) {

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

cvThread = nuwe Thread (nuwe ThreadStart (cvThreadFunction)); runVideo = waar; cvThread. Start (); cameraStartBtn. Text = "stop"; terwyl (! videoRunning) Thread. Sleep (100);

updateColorTimer. Start ();

} anders {

runVideo = vals; cvThread. Sluit aan (); cameraStartBtn. Text = "begin"; }}

Kleur

Nou kan ons die kleur van 'n kraal bepaal en op grond van die kleur besluit in watter houer dit moet val.

Hierdie stap maak staat op kleurvergelyking. Ons wil kleure kan onderskei om vals positief te beperk, maar ook genoeg drempels toelaat om vals negatiewe te beperk. Die vergelyking van kleure is eintlik verbasend ingewikkeld, want die manier waarop rekenaars kleure as RGB stoor, en die manier waarop mense kleure sien, korreleer nie lineêr nie. Om die saak erger te maak, moet ook die kleur van die lig waarin 'n kleur gekyk word, in ag geneem word.

Daar is ingewikkelde algoritme vir die berekening van kleurverskil. Ons gebruik CIE2000, wat 'n getal naby 1 gee as 2 kleure vir 'n mens nie onderskei kan word nie. Ons gebruik die ColorMine C# -biblioteek om hierdie ingewikkelde berekeninge te doen. 'N DeltaE -waarde van 5 bied 'n goeie kompromie tussen vals positief en vals negatief.

Aangesien daar dikwels meer kleure as houers is, word die laaste posisie gereserveer as 'n opvangbak. Ek het dit gewoonlik opsy gesit om deur die masjien op 'n tweede pas te werk.

Lys

kleure = nuwe lys (); lys kleurpanele = nuwe lys (); Lys colourTxts = nuwe Lys (); Lys colorCnts = nuwe Lys ();

const int numColorSpots = 18;

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

Console. WriteLine ("Soek kleur …");

var cRGB = nuwe Rgb ();

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

int bestMatch = -1;

dubbele wedstrydDelta = 100;

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

var RGB = nuwe Rgb ();

RGB. R = kleure . R; RGB. G = kleure . G; RGB. B = kleure . B;

dubbele delta = cRGB. Vergelyk (RGB, nuwe CieDe2000Comparison ());

// dubbele delta = deltaE (c, kleure ); Console. WriteLine ("DeltaE (" + i. ToString () + "):" + delta. ToString ()); as (delta <matchDelta) {matchDelta = delta; bestMatch = i; }}

if (matchDelta <5) {Console. WriteLine ("Gevind! (Posn:" + bestMatch + "Delta:" + matchDelta + ")"); gee bestMatch terug; }

if (colors. Count <numColorSpots) {Console. WriteLine ("Nuwe kleur!"); kleure. Voeg by (c); this. BeginInvoke (nuwe Aksie (setBackColor), nuwe voorwerp {colors. Count - 1}); writeOutColors (); opgawe (colours. Count - 1); } anders {Console. WriteLine ("Onbekende kleur!"); terugkeer unknownColorIndex; }}

Sorteer logika

Die sorteerfunksie bring al die stukke bymekaar om krale eintlik te sorteer. Hierdie funksie loop in 'n toegewyde draad; beweeg die boonste plaat, ontdek die kleur van die kraal, plaas dit in 'n asblik, sorg dat die boonste plaat in lyn bly, die krale tel, ens. Dit hou ook op om te loop as die opvangbak vol word - anders loop ons net oorvol krale op.

Thread colourTestThread; Boolean runtest = false; leegte colourTest () {

as (! top. Engaged)

top. Engaged = true;

as (! bottom. Engaged)

bottom. Engaged = true;

terwyl (runtest) {

nextMagnet (waar);

Draad. Slaap (100); probeer {if (magSensor. SensorValue <(magSensorMax - 4)) alignMotor (); } vang {alignMotor (); }

nextCamera (waar);

opspoor = waar;

terwyl (detectCnt <5) Thread. Sleep (25); Console. WriteLine ("Detect Count:" + detectCnt); opsporing = vals;

Kleur c = detectColor;

this. BeginInvoke (nuwe aksie (setColorDet), nuwe voorwerp {c}); int i = findColorPosition (c);

SetBottomPosition (i, true);

nextHole (waar); colorCnts ++; this. BeginInvoke (nuwe aksie (setColorTxt), nuwe voorwerp {i}); Draad. Slaap (250);

as (colorCnts [unknownColorIndex]> 500) {

top. Engaged = false; bottom. Engaged = false; runtest = vals; this. BeginInvoke (nuwe aksie (setGoGreen), null); terugkeer; }}}

private leegte colourTestBtn_Click (voorwerp sender, EventArgs e) {

if (colourTestThread == null ||! colourTestThread. IsAlive) {colourTestThread = new Thread (new ThreadStart (colourTest)); runtest = waar; colourTestThread. Start (); colourTestBtn. Text = "STOP"; colourTestBtn. BackColor = Color. Red; } anders {runtest = false; colourTestBtn. Text = "GO"; colourTestBtn. BackColor = Kleur. Groen; }}

Op hierdie stadium het ons 'n werkende program. Sommige stukkies kode is uit die artikel gelaat, dus kyk na die bron om dit werklik te laat loop.

Optiese wedstryd
Optiese wedstryd

Tweede prys in die optiese wedstryd

Aanbeveel: