Բովանդակություն:

Robotic Bead Sorting: 3 քայլ (նկարներով)
Robotic Bead Sorting: 3 քայլ (նկարներով)

Video: Robotic Bead Sorting: 3 քայլ (նկարներով)

Video: Robotic Bead Sorting: 3 քայլ (նկարներով)
Video: Оформлені роботи - Фініші - Покупки - Марія Love2Stitch 2024, Հուլիսի
Anonim
Image
Image
Ռոբոտային ուլունքների տեսակավորում
Ռոբոտային ուլունքների տեսակավորում
Ռոբոտային ուլունքների տեսակավորում
Ռոբոտային ուլունքների տեսակավորում
Ռոբոտային ուլունքների տեսակավորում
Ռոբոտային ուլունքների տեսակավորում

Այս նախագծում մենք կառուցելու ենք ռոբոտ ՝ Perler- ի ուլունքներն ըստ գույնի տեսակավորելու:

Ես միշտ ցանկացել եմ գույնի տեսակավորման ռոբոտ կառուցել, ուստի երբ աղջիկս հետաքրքրվեց Պերլերի ուլունքների արհեստով, ես դա դիտեցի որպես կատարյալ հնարավորություն:

Պերլերի ուլունքներն օգտագործվում են միաձուլված արվեստի նախագծեր ստեղծելու համար ՝ բազմաթիվ ուլունքներ տեղադրելով տախտակի վրա, այնուհետև դրանք հալեցնելով երկաթի հետ միասին: Դուք սովորաբար գնում եք այս ուլունքները հսկայական 22, 000 ուլունքների խառը տուփերի մեջ և շատ ժամանակ եք ծախսում ձեր ուզած գույնը որոնելու համար, ուստի ես մտածեցի, որ դրանք տեսակավորելը կբարձրացնի արվեստի արդյունավետությունը:

Ես աշխատում եմ Phidgets Inc.- ում, ուստի այս նախագծի համար հիմնականում օգտագործել եմ Phidgets- ը, բայց դա հնարավոր է անել ցանկացած հարմար սարքավորման միջոցով:

Քայլ 1: Սարքավորումներ

Ահա թե ինչ էի ես օգտագործում սա կառուցելու համար: Ես այն կառուցել եմ 100% -ով ՝ phidgets.com- ի մասերով, և այն, ինչ ես ունեի ՝ տան շուրջը պառկած:

Phidgets տախտակներ, շարժիչներ, սարքավորումներ

  • HUB0000 - VINT Hub Phidget
  • 1108 - Մագնիսական տվիչ
  • 2x STC1001 - 2.5A Stepper Phidget
  • 2x 3324 - 42STH38 NEMA -17 երկբևեռ շարժիչազրկիչ
  • 3x3002 - Ֆիջեթ մալուխ 60 սմ
  • 3403 - USB2.0 4 նավահանգիստային հանգույց
  • 3031 - էգ խոզուկ 5.5x2.1 մմ
  • 3029 - 2 մետաղալար 100 'Twisted Cable
  • 3604 - 10 մմ սպիտակ LED (պայուսակ 10)
  • 3402 - USB վեբ -տեսախցիկ

Այլ մասեր

  • 24VDC 2.0A սնուցման աղբյուր
  • Ավտոտնակից փայտ և մետաղ ջարդեք
  • Zip կապեր
  • Պլաստիկ տարա, որի հատակը կտրված է

Քայլ 2: Նախագծեք ռոբոտը

Նախագծեք ռոբոտը
Նախագծեք ռոբոտը
Նախագծեք ռոբոտը
Նախագծեք ռոբոտը
Նախագծեք ռոբոտը
Նախագծեք ռոբոտը

Մենք պետք է նախագծենք մի բան, որը կարող է մեկ բշտիկ վերցնել մուտքի վազքից, տեղադրել այն տեսախցիկի տակ, այնուհետև այն տեղափոխել համապատասխան աղբարկղ:

Ուլունքների հավաքում

Ես որոշեցի 1 -ին մասը կատարել 2 կտոր կլոր նրբատախտակով, յուրաքանչյուրը նույն տեղում փորված անցքով: Ներքևի հատվածը ամրացված է, իսկ վերին հատվածը ամրացված է սանդղակի շարժիչին, որը կարող է պտտել այն ուլունքներով լցված բալի տակ: Երբ փոսը անցնում է բուֆերի տակ, այն վերցնում է մեկ հատիկ: Այնուհետև ես կարող եմ այն պտտել վեբ -տեսախցիկի տակ, այնուհետև այն հետագայում պտտել, մինչև այն չհամապատասխանի ներքևի մասի անցքին, որի ընթացքում այն ընկնում է:

Այս նկարում ես ստուգում եմ, որ համակարգը կարող է աշխատել: Ամեն ինչ ամրագրված է, բացառությամբ նրբատախտակի վերին կլոր կտորի, որը ամրացված է տափաստանային շարժիչին ՝ ներքևից տեսանելի: Տեսախցիկը դեռ տեղադրված չէ: Ես պարզապես օգտագործում եմ Phidget Control Panel- ը `այս պահին շարժիչին դիմելու համար:

Ուլունքների պահեստավորում

Հաջորդ մասը յուրաքանչյուր գույնը պահելու համար նախատեսված աղբարկղի համակարգի ձևավորումն է: Ես որոշեցի օգտագործել երկրորդ ստորին շարժիչը ներքևում ՝ հավասարաչափ տարածված խցիկներով կլոր տարա պահելու և պտտելու համար: Սա կարող է օգտագործվել ճիշտ խցիկը պտտելու համար այն անցքի տակ, որից կաթիլը դուրս կընկնի:

Սա կառուցել եմ ստվարաթղթե և կպչուն ժապավենի միջոցով: Այստեղ ամենակարևորը հետևողականությունն է. Յուրաքանչյուր խցիկ պետք է լինի նույն չափի, և ամբողջը պետք է հավասարաչափ կշռված լինի, որպեսզի այն պտտվի առանց բաց թողնելու:

Ուլունքների հեռացումն իրականացվում է սեղմված կափարիչի միջոցով, որը միաժամանակ մերկացնում է մեկ խցիկ, այնպես որ ուլունքները կարող են թափվել:

Տեսախցիկ

Տեսախցիկը տեղադրված է գագաթի և ափսեի ստորին անցքի վայրի միջև գտնվող վերին ափսեի վրա: Սա թույլ է տալիս համակարգին նայել կաթիլը գցելուց առաջ: LED- ն օգտագործվում է տեսախցիկի տակ գտնվող ուլունքները լուսավորելու համար, իսկ շրջապատող լույսն արգելափակված է `լուսավորության հետևողական միջավայր ապահովելու համար: Սա շատ կարևոր է գույնի ճշգրիտ հայտնաբերման համար, քանի որ շրջապատող լուսավորությունն իսկապես կարող է շպրտել ընկալվող գույնը:

Տեղադրության հայտնաբերում

Համակարգի համար կարևոր է, որ կարողանա հայտնաբերել բշտիկ բաժանարարի պտույտը: Սա օգտագործվում է սկզբնական դիրքը կարգավորելու համար ՝ գործարկման ժամանակ, բայց նաև որոշելու, թե արդյոք սլաքի շարժիչը համաժամեցվել է: Իմ համակարգում, մի հատիկ երբեմն խցանում է վերցնելիս, և համակարգը պետք է կարողանար հայտնաբերել և կարգավորել այս իրավիճակը `մի փոքր պահուստավորվելով և փորձելով այլ կերպ:

Սրա հետ վարվելու շատ եղանակներ կան: Ես որոշեցի օգտագործել 1108 մագնիսական տվիչ ՝ մագնիսով, որը տեղադրված է վերին ափսեի եզրին: Սա թույլ է տալիս ինձ ստուգել յուրաքանչյուր պտույտի դիրքը: Ավելի լավ լուծում, հավանաբար, կլինի կոդավորիչը տափաստանային շարժիչի վրա, բայց ես ունեի 1108 պառկած, այնպես որ ես դա օգտագործեցի:

Ավարտեք ռոբոտը

Այս պահին ամեն ինչ մշակված է և փորձարկված: Everythingամանակն է ամեն ինչ գեղեցիկ ամրացնել և անցնել գրելու ծրագրակազմ:

Երկու սլաքային շարժիչները վարում են STC1001 ստեպեր վերահսկիչները: A HUB000 - USB VINT հանգույցը օգտագործվում է սանդղակի վերահսկիչները գործարկելու, ինչպես նաև մագնիսական տվիչը կարդալու և LED- ն վարելու համար: Տեսախցիկը և HUB0000- ը երկուսն էլ կցված են փոքր USB հանգույցին: Շարժիչները սնուցելու համար օգտագործվում է 3031 խոզուկ և որոշ մետաղալարեր ՝ 24 Վ լարման աղբյուրի հետ միասին:

Քայլ 3: Գրել կոդը

Image
Image

Այս նախագծի համար օգտագործվում են C# և Visual Studio 2015 ծրագրերը: Ներբեռնեք աղբյուրը այս էջի վերևում և հետևեք դրան. Հիմնական բաժինները ներկայացված են ստորև

Նախաստորագրում

Նախ, մենք պետք է ստեղծենք, բացենք և նախաստորագրենք Phidget օբյեկտները: Դա արվում է ձևի բեռնման իրադարձության մեջ, և Phidget- ը կցում է կարգավորիչներ:

մասնավոր դատարկ Form1_Load (օբյեկտ ուղարկող, EventArgs ե) {

/ * Նախաձեռնել և բացել Phidgets */

վերևում. HubPort = 0; top. Attach += Top_Attach; top. Detach += Top_Detach; top. PositionChange += Top_PositionChange; վերև: Բաց ();

ներքև. HubPort = 1;

ներքևում. Կցել += Ներքևում `կցել; ներքևում. անջատել += ներքևից ներքև. PositionChange += Bottom_PositionChange; ներքևում: Բաց ();

magSensor. HubPort = 2;

magSensor. IsHubPortDevice = ճշմարիտ; magSensor. Attach += MagSensor_Attach; magSensor. Detach += MagSensor_Detach; magSensor. SensorChange += MagSensor_SensorChange; magSensor. Open ();

led. HubPort = 5;

led. IsHubPortDevice = ճշմարիտ; led. Channel = 0; led. Attach += Led_Attach; led. Detach += Led_Detach; led. Open (); }

մասնավոր դատարկություն Led_Attach (օբյեկտ ուղարկող, Phidget22. Events. AttachEventArgs ե) {

ledAttachedChk. Checked = ճշմարիտ; led. State = true; ledChk. Checked = ճշմարիտ; }

մասնավոր դատարկություն MagSensor_Attach (օբյեկտ ուղարկող, Phidget22. Events. AttachEventArgs ե) {

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

մասնավոր դատարկություն Bottom_Attach (օբյեկտ ուղարկող, Phidget22. Events. AttachEventArgs ե) {

bottomAttachedChk. Checked = ճշմարիտ; bottom. CurrentLimit = bottomCurrentLimit; ներքևում: Ներգրավված = ճշմարիտ; bottom. VelocityLimit = bottomVelocityLimit; ներքև. Արագացում = ներքև ներքևում. DataInterval = 100; }

մասնավոր դատարկություն Top_Attach (օբյեկտ ուղարկող, Phidget22. Events. AttachEventArgs ե) {

topAttachedChk. Checked = true; top. CurrentLimit = topCurrentLimit; վերևում: Ներգրավված = ճշմարիտ; վերև. RescaleFactor = -1; top. VelocityLimit = -topVelocityLimit; վերև. Արագացում = -topAccel; վերև. DataInterval = 100; }

Մենք նաև կարդում ենք ցանկացած պահված գույնի տեղեկատվություն սկզբնավորման ընթացքում, այնպես որ նախորդ վազքը կարող է շարունակվել:

Շարժիչի դիրքավորում

Շարժիչի բեռնաթափման ծածկագիրը բաղկացած է շարժիչների տեղափոխման հարմարավետության գործառույթներից: Իմ օգտագործած շարժիչները 3, 200 1/16 -րդ քայլ են մեկ պտույտի համար, ուստի դրա համար հաստատուն եմ ստեղծել:

Վերևի շարժիչի համար կան 3 դիրքեր, որոնք մենք ցանկանում ենք կարողանալ ուղարկել շարժիչին ՝ վեբ -տեսախցիկ, անցք և տեղադրման մագնիս: Այս պաշտոններից յուրաքանչյուրին մեկնելու գործառույթ կա.

private void nextMagnet (բուլյան սպասում = կեղծ) {

double posn = top. Position % stepsPerRev;

top. TargetPosition += (stepsPerRev - posn);

եթե (սպասիր)

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

private void nextCamera (բուլյան սպասում = կեղծ) {

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

եթե (սպասիր)

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

մասնավոր դատարկություն nextHole (բուլյան սպասում = կեղծ) {

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

եթե (սպասիր)

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

Վազք սկսելուց առաջ վերին ափսեն հավասարեցվում է մագնիսական տվիչի միջոցով: AlignMotor գործառույթը կարող է ցանկացած ժամանակ կանչվել ՝ վերին ափսեի հավասարեցման համար: Այս գործառույթը նախ արագորեն թեքում է ափսեը մինչև 1 ամբողջական պտույտ, մինչև չտեսնի մագնիսի տվյալները շեմից բարձր: Այնուհետև այն մի փոքր հետ է կանգնում և նորից դանդաղ առաջ է շարժվում ՝ անընդհատ գրավելով տվիչների տվյալները: Ի վերջո, այն սահմանում է դիրքը առավելագույն մագնիսի տվյալների գտնվելու վայրի վրա և զրոյացնում դիրքը 0 -ի համար: Այսպիսով, մագնիսի առավելագույն դիրքը միշտ պետք է լինի վերևում (վերևում:

Թելի հավասարեցումMotorThread; Բուլյան սղոց Մագնիս; կրկնակի magSensorMax = 0; private void alignMotor () {

// Գտեք մագնիսը

top. DataInterval = top. MinDataInterval;

sawMagnet = կեղծ;

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

int tryCount = 0;

կրկին փորձել:

top. TargetPosition += stepsPerRev;

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

եթե (! sawMagnet) {

if (tryCount> 3) {Console. WriteLine ("Հավասարեցումը ձախողվեց"); վերևում: Ներգրավված = կեղծ; ներքևում. Ներգրավված = կեղծ; runtest = կեղծ; վերադարձ; }

tryCount ++;

Console. WriteLine («Արդյո՞ք մենք խրված ենք. Փորձում ենք կրկնօրինակում …»); top. TargetPosition -= 600; while (top. IsMoving) Thread. Sleep (100);

նորից փորձել;

}

top. VelocityLimit = -100;

magData = նոր >անկ> (); magSensor. SensorChange += magSensorCollectPositionData; top. TargetPosition += 300; while (top. IsMoving) Thread. Sleep (100);

magSensor. SensorChange -= magSensorCollectPositionData;

top. VelocityLimit = -topVelocityLimit;

KeyValuePair max = mag Տվյալներ [0];

foreach (KeyValuePair զույգը magData- ում) if (pair. Value> max. Value) max = զույգ;

վերևում. Ավելացնել դիրքորոշում (-մաքս. բանալին);

magSensorMax = առավելագույն. Արժեք;

top. TargetPosition = 0;

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

Console. WriteLine ("Հավասարեցումը հաջողվեց");

}

Listանկ> magData;

private void magSensorCollectPositionData (օբյեկտ ուղարկող, Phidget22. Events. VoltageRatioInputSensorChangeEventArgs ե) {magData. Add (նոր KeyValuePair (վեր. դիրքը, e. SensorValue)); }

private void magSensorStopMotor (օբյեկտ ուղարկող, Phidget22. Events. VoltageRatioInputSensorChangeEventArgs ե) {

if (top. IsMoving && e. SensorValue> 5) {top. TargetPosition = top. Position - 300; magSensor. SensorChange -= magSensorStopMotor; sawMagnet = ճշմարիտ; }}

Ի վերջո, ներքևի շարժիչը վերահսկվում է ՝ այն ուղարկելով ուլունքների տարայի դիրքերից մեկին: Այս նախագծի համար մենք ունենք 19 հաստիք: Ալգորիթմը ընտրում է ամենակարճ ուղին և պտտվում է ժամացույցի սլաքի կամ հակառակ ուղղությամբ:

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

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

} }

մասնավոր դատարկություն SetBottomPosition (int posn, bool wait = false) {

posn = posn % beadCompartments; կրկնակի targetPosn = (posn * stepsPerRev) / beadCompartments;

կրկնակի ընթացիկPosn = ներքև. Դիրքը % stepsPerRev;

կրկնակի posnDiff = targetPosn - currentPosn;

// Պահեք այն որպես ամբողջական քայլեր

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

եթե (posnDiff <= 1600) ներքև. TargetPosition += posnDiff; else bottom. TargetPosition - = (stepsPerRev - posnDiff);

եթե (սպասիր)

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

Տեսախցիկ

OpenCV- ն օգտագործվում է վեբ -տեսախցիկից պատկերներ կարդալու համար: Տեսախցիկի շարանը գործարկվում է հիմնական տեսակավորման շարանը սկսելուց առաջ: Այս շարանը անընդհատ կարդում է պատկերների մեջ, Միջին գույնի միջոցով հաշվարկում է որոշակի տարածաշրջանի միջին գույնը և թարմացնում է գլոբալ գույնի փոփոխականը: Թելը նաև օգտագործում է HoughCircles- ը ՝ փորձելու հայտնաբերել կամ մի բշտիկ, կամ վերին ափսեի անցքը ՝ մաքրելու այն տարածքը, որը նա փնտրում է գույնի հայտնաբերման համար: Շեմի և HoughCircles- ի թվերը որոշվել են փորձության և սխալի միջոցով և մեծապես կախված են վեբ -տեսախցիկից, լուսավորությունից և տարածությունից:

bool runVideo = true; bool videoRunning = false; VideoCapture գրավում; Թեման cvThread; Գույնը հայտնաբերվածԳույն; Բուլյան հայտնաբերում = կեղծ; int deteCnt = 0;

private void cvThreadFunction () {

videoRunning = կեղծ;

գրավում = նոր VideoCapture (ընտրված տեսախցիկ);

օգտագործելով (Window window = new Window («գրավում»)) {

Գորգի պատկեր = նոր գորգ (); Մատ պատկեր 2 = նոր գորգ (); while (runVideo) {capture. Read (պատկերը); եթե (պատկեր. Դատարկ ()) ընդմիջում;

եթե (հայտնաբերում)

deteCnt ++; այլապես deteCnt = 0;

եթե (հայտնաբերում || circleDetectChecked || showDetectionImgChecked) {

Cv2. CvtColor (պատկեր, պատկեր 2, ColorConversionCodes. BGR2GRAY); Mat thres = image2. Threshold ((double) Properties. Settings. Default.videoThresh, 255, ThresholdTypes. Binary); thres = thres. GaussianBlur (նոր OpenCvSharp. Size (9, 9), 10);

եթե (showDetectionImgChecked)

պատկեր = երեք;

եթե (հայտնաբերում || circleDetectChecked) {

CircleSegment ուլունք = 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 (bead [0]. Center, (int) bead [0]. Radius, new Scalar (0, 0, 255), 3); if (ուլունք [0]. Radius> = 55) {Properties. Settings. Default.x = (տասնորդական) ուլունք [0]. Center. X + (տասնորդական) (ուլունք [0]. Radius / 2); Properties. Settings. Default.y = (տասնորդական) ուլունք [0]. Center. Y - (տասնորդական) (ուլունք [0]. Radius / 2); } else {Properties. Settings. Default.x = (տասնորդական) ուլունք [0]. Center. X + (տասնորդական) (ուլունք [0]. Radius); Properties. Settings. Default.y = (տասնորդական) ուլունք [0]. Center. Y - (տասնորդական) (ուլունք [0]. Radius); } Properties. Settings. Default.size = 15; Properties. Settings. Default.height = 15; } ուրիշ {

CircleSegment շրջանակներ = thres. HoughCircles (HoughMethods. Gradient, 2, /*thres. Rows/4*/ 5, 200, 100, 60, 180);

եթե (շրջանակներ. Երկարություն> 1) {Listանկ xs = շրջանակներ: Ընտրեք (c => c. Center. X). ToList (); xs. Տեսակավորել (); Listանկ ys = շրջանակներ: Ընտրեք (c => c. Center. Y). ToList (); ys. Տեսակավորել ();

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

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

եթե (medianX> պատկեր. Լայնություն - 15)

medianX = պատկեր: Լայնություն - 15; եթե (միջին Y> պատկեր. Բարձրություն - 15) միջին Y = պատկեր: Բարձրություն - 15;

image. Circle (medianX, medianY, 100, new Scalar (0, 0, 150), 3);

եթե (հայտնաբերում) {

Properties. Settings. Default.x = medianX - 7; Properties. Settings. Default.y = միջին Y - 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 = նոր գորգ (պատկեր, r);

Scalar avgColor = Cv2. Միջին (ուլունքների օրինակ); deteColor = Color. FromArgb ((int) avgColor [2], (int) avgColor [1], (int) avgColor [0]);

պատկեր. Ուղղանկյուն (r, նոր Scalar (0, 150, 0));

window. ShowImage (պատկեր);

Cv2. WaitKey (1); videoRunning = ճշմարիտ; }

videoRunning = կեղծ;

} }

մասնավոր դատարկ տեսախցիկ StartBtn_Click (օբյեկտ ուղարկող, EventArgs ե) {

եթե (cameraStartBtn. Text == "սկսել") {

cvThread = նոր թեմա (նոր ThreadStart (cvThreadFunction)); runVideo = ճշմարիտ; cvThread. Start (); cameraStartBtn. Text = "կանգառ"; while (! videoRunning) Thread. Sleep (100);

updateColorTimer. Start ();

} ուրիշ {

runVideo = կեղծ; cvThread. Join (); cameraStartBtn. Text = "սկսել"; }}

Գույն

Այժմ մենք կարողանում ենք որոշել բշտիկի գույնը և այդ գույնի հիման վրա որոշել, թե որ տարայի մեջ այն պետք է գցել:

Այս քայլը հիմնված է գույների համեմատության վրա: Մենք ցանկանում ենք, որ կարողանանք առանձնացնել գույները ՝ սահմանափակելու կեղծ դրականը, բայց նաև թույլ ենք տալիս բավականաչափ շեմ սահմանափակել կեղծ բացասականը: Գույների համեմատությունը իրականում զարմանալիորեն բարդ է, քանի որ համակարգիչների կողմից գույները RGB- ի տեսքով պահելը և այն, թե ինչպես են մարդիկ գույներն ընկալում, գծային փոխկապակցված չեն: Ավելի վատը դարձնելու համար պետք է նաև հաշվի առնել լույսի գույնը, որի տակ դիտարկվում է գույնը:

Գոյություն ունեն բարդ ալգորիթմ ՝ գույների տարբերությունը հաշվարկելու համար: Մենք օգտագործում ենք CIE2000- ը, որը թողարկում է 1 -ի մոտ մի թիվ, եթե 2 գույները անտարբեր լինեն մարդու համար: Այս բարդ հաշվարկները կատարելու համար մենք օգտագործում ենք ColorMine C# գրադարանը: Պարզվել է, որ DeltaE- ի 5 արժեքը լավ փոխզիջում է առաջարկում կեղծ դրականի և կեղծ բացասականի միջև:

Քանի որ հաճախ ավելի շատ գույներ կան, քան տարաները, վերջին դիրքը վերապահված է որպես գավազան: Ես ընդհանրապես դրանք մի կողմ եմ դնում, որպեսզի մեքենան աշխատի երկրորդ անցման վրա:

Listանկ

գույներ = նոր ցուցակ (); ցուցակ colorPanels = նոր ցուցակ (); Colorsանկի գույներՏեքստեր = նոր ցուցակ (); Ցուցակ colorCnts = նոր ցուցակ ();

const int numColorSpots = 18;

const int unknownColorIndex = 18; int findColorPosition (գույնը գ) {

Console. WriteLine ("Գտնելով գույնը …");

var cRGB = նոր Rgb ();

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

int bestMatch = -1;

կրկնակի համընկնումԴելտա = 100;

համար (int i = 0; i <colors. Count; i ++) {

var RGB = նոր Rgb ();

RGB. R = գույներ . R; RGB. G = գույներ . G; RGB. B = գույներ . B;

կրկնակի դելտա = cRGB. Համեմատել (RGB, նոր CieDe2000Comparison ());

// կրկնակի դելտա = դելտաԷ (գ, գույներ ); Console. WriteLine ("DeltaE (" + i. ToString () + "):" + delta. ToString ()); եթե (դելտա <matchDelta) {matchDelta = դելտա; bestMatch = i; }}

if (matchDelta <5) {Console. WriteLine ("Գտնվեց! (Տեղադր." + bestMatch + "Delta:" + matchDelta + ")"); վերադարձնել bestMatch; }

if (colors. Count <numColorSpots) {Console. WriteLine («Նոր գույն»); գույներ: Ավելացնել (գ); this. BeginInvoke (նոր գործողություն (setBackColor), նոր օբյեկտ {colors. Count - 1}); writeOutColors (); վերադարձ (գույներ. Հաշիվ - 1); } else {Console. WriteLine («Անհայտ գույն»); վերադարձ անհայտ ColourIndex; }}

Տրամաբանության տեսակավորում

Տեսակավորման գործառույթը միավորում է բոլոր կտորները `իրականում տեսակավորելու ուլունքները: Այս գործառույթն աշխատում է հատուկ շարանի մեջ. վերին ափսեի տեղափոխում, բշտիկի գույնի հայտնաբերում, տեղադրում այն աղբարկղի մեջ, համոզվեք, որ վերին ափսեը հավասարեցված է, հաշվում են ուլունքները և այլն:Այն նաև դադարում է աշխատել, երբ որսալարը լցվում է: Հակառակ դեպքում մենք պարզապես հայտնվում ենք լցված ուլունքներով:

Թելի colourTestThread; Բուլյան runtest = կեղծ; void colourTest () {

եթե (! վերևում. ներգրավված է)

վերևում: Ներգրավված = ճշմարիտ;

եթե (! ներքևում. ներգրավված է)

ներքևում: Ներգրավված = ճշմարիտ;

մինչդեռ (ամենախենթ) {

nextMagnet (ճշմարիտ);

Թեման. Քուն (100); փորձեք {if (magSensor. SensorValue <(magSensorMax - 4)) alignMotor (); } catch {alignMotor (); }

հաջորդ Տեսախցիկ (ճշմարիտ);

հայտնաբերում = ճշմարիտ;

while (deteCnt <5) Թեման. Քուն (25); Console. WriteLine ("Detect Count:" + detectT); հայտնաբերում = կեղծ;

Գույն c = հայտնաբերվածԳույն;

this. BeginInvoke (նոր գործողություն (setColorDet), նոր օբյեկտ {c}); int i = findColorPosition (գ);

SetBottomPosition (i, ճշմարիտ);

nextHole (ճշմարիտ); colorCnts ++; this. BeginInvoke (նոր գործողություն (setColorTxt), նոր օբյեկտ {i}); Թեման: Քուն (250);

եթե (colorCnts [unknownColorIndex]> 500) {

վերևում: Ներգրավված = կեղծ; ներքևում. Ներգրավված = կեղծ; runtest = կեղծ; this. BeginInoke (նոր գործողություն (setGoGreen), null); վերադարձ; }}}

private void colourTestBtn_Click (օբյեկտ ուղարկող, EventArgs ե) {

եթե (colourTestThread == null ||! colourTestThread. IsAlive) {colourTestThread = նոր թեմա (նոր ThreadStart (colourTest)); runtest = ճշմարիտ; colourTestThread. Start (); colourTestBtn. Text = "ԿԱՆԳՆԵԼ"; colourTestBtn. BackColor = Color. Red; } else {runtest = կեղծ; colourTestBtn. Text = "ԳՆԵԼ"; colourTestBtn. BackColor = Color. Green; }}

Այս պահին մենք ունենք աշխատանքային ծրագիր: Կոդի որոշ հատվածներ դուրս են մնացել հոդվածից, այնպես որ, իրականում գործարկելու համար, նայեք աղբյուրին:

Օպտիկայի մրցույթ
Օպտիկայի մրցույթ

Երկրորդ մրցանակ Օպտիկայի մրցույթում

Խորհուրդ ենք տալիս: