Мазмуну:

Робот мончок сорттоо: 3 кадам (сүрөттөр менен)
Робот мончок сорттоо: 3 кадам (сүрөттөр менен)

Video: Робот мончок сорттоо: 3 кадам (сүрөттөр менен)

Video: Робот мончок сорттоо: 3 кадам (сүрөттөр менен)
Video: Хамдам Собиров - Тентакчам (Премьера клипа, 2022) 2024, Июль
Anonim
Image
Image
Робот шарикти сорттоо
Робот шарикти сорттоо
Робот шарикти сорттоо
Робот шарикти сорттоо
Робот шарикти сорттоо
Робот шарикти сорттоо

Бул долбоордо биз Перлер мончокторун түсү боюнча сорттоо үчүн робот курабыз.

Мен ар дайым түстөрдү сорттоочу роботту кургум келет, ошондуктан кызым Перлердин шуруларын жасоого кызыгып калганда, мен муну эң сонун мүмкүнчүлүк катары көрдүм.

Перлер мончоктору көптөгөн шуруларды казыкка коюп, анан темир менен бирге эритип, бириктирилген көркөм долбоорлорду түзүү үчүн колдонулат. Сиз жалпысынан бул мончокторду 22 000 гигант түстүү пакеттерден сатып аласыз жана сиз каалаган түстү издөөгө көп убакыт бөлөсүз, андыктан аларды иреттөө искусствонун эффективдүүлүгүн жогорулатат деп ойлогом.

Мен Phidgets Inc үчүн иштейм, ошондуктан мен бул проект үчүн көбүнчө Phidgetsти колдондум - бирок муну каалаган шайманды колдонуп жасаса болот.

1 -кадам: Аппараттык

Мына, мен муну мурда курган элем. Мен аны 100% phidgets.comдун бөлүктөрү жана үйдүн тегерегинде болгон нерселер менен кургам.

Фиджет такталары, Моторлор, Аппараттык жабдуулар

  • HUB0000 - VINT Hub Phidget
  • 1108 - Магниттик сенсор
  • 2x STC1001 - 2.5A Stepper Phidget
  • 2x 3324 - 42STH38 NEMA -17 биполярдык тишсиз кадам
  • 3x 3002 - Phidget Cable 60см
  • 3403 - USB2.0 4 -Порт Хабы
  • 3031 - Аял Pigtail 5.5x2.1mm
  • 3029 - 2 зым 100 'Twisted Cable
  • 3604 - 10мм Ак LED (10 сумка)
  • 3402 - USB веб -камерасы

Башка бөлүктөр

  • 24VDC 2.0A электр менен камсыздоо
  • Гараждан жыгачты жана металлды сындырыңыз
  • Zip галстуктары
  • Пластикалык контейнер түбү кесилген

2 -кадам: Дизайн робот

Роботту долбоорлоо
Роботту долбоорлоо
Роботту долбоорлоо
Роботту долбоорлоо
Роботту долбоорлоо
Роботту долбоорлоо

Киргизүү бункеринен бир мончокту алып, веб -камеранын астына коюп, анан аны тиешелүү челекке жылдыра турган нерсени долбоорлообуз керек.

Мончок алуу

Мен 1 -бөлүктү 2 даана тегерек фанерадан жасоону чечтим, алардын ар бири бир жерде тешилген. Төмөнкү бөлүгү бекитилген, ал эми үстүнкү бөлүгү мончоктор менен толтурулган бункердин астына айландыра турган тепкичтүү моторго бекитилген. Тешик бункердин астында жүргөндө, ал бир мончокту алат. Мен аны веб -камеранын астына бура алам, андан кийин аны астыңкы бөлүктөгү тешикке дал келгенге чейин бура алам, ошондо ал түшүп калат.

Бул сүрөттө мен системанын иштей аларын текшерип жатам. Төмөндөгү фанеранын үстүңкү тегерек бөлүгүнөн башкасынын баары жөнгө салынган, ал астына тепкич моторго бекитилген. Веб -камера азырынча орнотула элек. Мен бул учурда моторду иштетүү үчүн Phidget Control Panelди колдонуп жатам.

Мончок сактоо

Кийинки бөлүк ар бир түстү кармоо үчүн бин системасын иштеп чыгуу. Мен тегиз контейнерлери бар тегерек контейнерди колдоо жана айландыруу үчүн төмөндөгү экинчи тепкич моторун колдонууну чечтим. Бул мончок түшө турган тешиктин астындагы туура бөлүмдү айландыруу үчүн колдонулушу мүмкүн.

Мен муну картон жана скотч менен жасадым. Бул жерде эң негизгиси - ырааттуулук - ар бир бөлүм бирдей өлчөмдө болушу керек жана бүт нерсенин тегиз салмагы болушу керек, ал секирбей айланат.

Мончокторду алып салуу бир убакта бир бөлүктү ачып турган бекем жабылган капкактын жардамы менен ишке ашат, андыктан шуруларды төгүүгө болот.

Камера

Веб -камера бункер менен ылдыйкы табактын тешигинин ортосундагы үстүңкү плитанын үстүнө орнотулган. Бул система мончокту түшүрүүдөн мурун кароого мүмкүндүк берет. Светодиод камеранын астындагы мончокторду жарыктандыруу үчүн колдонулат жана туруктуу жарык чөйрөсүн камсыз кылуу үчүн айланадагы жарык тосулат. Түстү так аныктоо үчүн бул абдан маанилүү, анткени айланадагы жарык чынында кабыл алынган түстү ыргытып жибериши мүмкүн.

Жайгашкан жерди аныктоо

Системанын шуру бөлгүчүнүн айлануусун аныктай алышы маанилүү. Бул баштапкы абалды орнотуу үчүн, ошондой эле тепкич мотору шайкештешип кеткенин аныктоо үчүн колдонулат. Менин тутумумда, мончок кээде көтөрүлүп жатканда тыгылып калат, жана система бул абалды аныктап, чече алышы керек болчу - бир аз колдоп, agian аракет кылып.

Муну чечүүнүн көптөгөн жолдору бар. Мен 1108 магниттик сенсорун колдонууну чечтим, магнит үстүңкү плитанын четине камтылган. Бул мага ар бир ротация боюнча позицияны текшерүүгө мүмкүндүк берет. Жакшыраак чечим, балким, тепкич моторундагы коддогуч болмок, бирок менде 1108 жаткан, ошондуктан мен аны колдондум.

Роботту бүтүрүү

Бул учурда, баары иштелип чыккан жана сыналган. Баарын сонун орнотуп, программалык камсыздоого өтүү мезгили келди.

2 тепкичтүү мотор STC1001 тепкич контроллери тарабынан башкарылууда. A HUB000 - USB VINT хабы степперлерди иштетүү үчүн, ошондой эле магниттик сенсорду окуу жана LEDди айдоо үчүн колдонулат. Веб -камера жана HUB0000 экөө тең кичинекей USB хабына бекитилген. Моторлорду иштетүү үчүн 3031 чочко куйругу жана кээ бир зым 24В электр энергиясы менен бирге колдонулат.

3 -кадам: Код жазуу

Image
Image

Бул долбоор үчүн C# жана Visual Studio 2015 колдонулат. Булактын жогору жагындагы булакты жүктөп алыңыз жана ээрчиңиз - негизги бөлүмдөр төмөндө көрсөтүлгөн

Баштоо

Биринчиден, биз Phidget объектилерин түзүп, ачып жана баштообуз керек. Бул форма жүктөө иш -чарасында жасалат жана Phidget иштетүүчүлөрдү тиркейт.

жеке боштук Form1_Load (объект жөнөтүүчү, EventArgs e) {

/ * Phidgetsти баштоо жана ачуу */

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

түб. HubPort = 1;

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

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 e) {

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

купуя боштук MagSensor_Attach (объект жөнөтүүчү, Phidget22. Events. AttachEventArgs e) {

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

жеке боштук Bottom_Attach (объект жөнөтүүчү, Phidget22. Events. AttachEventArgs e) {

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

жеке боштук Top_Attach (объект жөнөтүүчү, 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; }

Биз инициализация учурунда сакталган түстүү маалыматты окуйбуз, андыктан мурунку иштетүүнү улантууга болот.

Мотордун жайгашуусу

Моторду башкаруу коду моторлорду жылдыруу үчүн ыңгайлуу функцияларынан турат. Мен колдонгон моторлор революциянын 3 200 200 1/16 кадамы, ошондуктан мен бул үчүн туруктуу түздүм.

Жогорку мотор үчүн биз моторго жөнөтө алгыбыз келген 3 позиция бар: веб -камера, тешик жана позициялоочу магнит. Бул кызматтардын ар бирине баруу үчүн функция бар:

private void nextMagnet (логикалык күтүү = жалган) {

кош posn = top. Position % stepsPerRev;

top. TargetPosition += (stepsPerRev - посн);

эгер (күт)

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

private void nextCamera (логикалык күтүү = жалган) {

кош 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); }

private void nextHole (логикалык күтүү = жалган) {

кош 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 -ге жылдырат. Ошентип, максималдуу магнит позициясы ар дайым (top. Position % stepsPerRev)

Thread alignMotorThread; Boolean sawMagnet; кош magSensorMax = 0; жеке боштук 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);

if (! sawMagnet) {

if (tryCount> 3) {Console. WriteLine ("Тегиздөө ишке ашкан жок"); top. Engaged = false; bottom. Engaged = false; runtest = false; кайтуу; }

tryCount ++;

Console. WriteLine ("Биз тыгылып калдыкпы? Резервдик көчүрмөнү колдонуп…"); top. TargetPosition -= 600; while (top. IsMoving) Thread. Sleep (100);

goto tryagain;

}

top. VelocityLimit = -100;

magData = жаңы тизме> (); magSensor. SensorChange += magSensorCollectPositionData; top. TargetPosition += 300; while (top. IsMoving) Thread. Sleep (100);

magSensor. SensorChange -= magSensorCollectPositionData;

top. VelocityLimit = -topVelocityLimit;

KeyValuePair max = magData [0];

foreach (magDataдагы KeyValuePair түгөйү) 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 ("Тегиздөө ийгиликтүү болду");

}

List> magData;

жеке боштук magSensorCollectPositionData (объект жөнөтүүчү, Phidget22. Events. VoltageRatioInputSensorChangeEventArgs e) {magData. Add (new KeyValuePair (top. Position, e. SensorValue))); }

жеке боштук magSensorStopMotor (объект жөнөтүүчү, Phidget22. Events. VoltageRatioInputSensorChangeEventArgs e) {

if (top. IsMoving &&. 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 % шарик бөлүктөрү; кош targetPosn = (posn * stepsPerRev) / beadCompartments;

double currentPosn = bottom. Position % stepsPerRev;

кош posnDiff = targetPosn - currentPosn;

// Толук кадам катары сактаңыз

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

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

эгер (күт)

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

Камера

OpenCV веб -камерадан сүрөттөрдү окуу үчүн колдонулат. Камеранын жиби негизги сорттоочу жипти баштоодон мурун башталат. Бул жип үзгүлтүксүз сүрөттөрдө окуйт, Орточо аркылуу белгилүү бир аймактын орточо түсүн эсептейт жана глобалдык түс өзгөрмөсүн жаңыртат. Жип HoughCirclesди мончокту же үстүңкү плитанын тешигин табууга аракет кылып, түстү аныктоо үчүн издеп жаткан жерди тазалайт. Босого жана HoughCircles сандары сыноо жана ката аркылуу аныкталган жана веб -камерага, жарыктандырууга жана аралыкка көз каранды.

bool runVideo = true; bool videoRunning = false; VideoCapture тартуу; Thread cvThread; Түс аныкталдыColor; Логикалык аныктоо = жалган; int detectCnt = 0;

жеке боштук cvThreadFunction () {

videoRunning = жалган;

тартуу = жаңы VideoCapture (тандалган Камера);

колдонуу (Терезе терезеси = жаңы терезе ("тартуу"))) {

Mat image = жаңы Mat (); Mat image2 = жаңы Mat (); while (runVideo) {capture. Read (image); if (image. Empty ()) break;

эгер (аныктоо)

detectCnt ++; else detectCnt = 0;

if (аныктоо || 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)

image = thres;

if (аныктоо || CircleDetectChecked) {

CircleSegment шуру = thres. HoughCircles (HoughMethods. Gradient, 2, /*thres. Rows/4*/ 20, 200, 100, 20, 65); if (мончок. Узундугу> = 1) {image. Circle (мончок [0]. Борбор, 3, жаңы Скаляр (0, 100, 0), -1); image. Circle (мончок [0]. Борбор, (int) мончок [0]. Радиус, жаңы Скаляр (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]. Радиус / 2); } else {Properties. Settings. Default.x = (ондук) шар [0]. Center. X + (ондук) (мончок [0]. Радиус); Properties. Settings. Default.y = (ондук) мончок [0]. Center. Y - (ондук) (мончок [0]. Радиус); } Properties. Settings. Default.size = 15; Properties. Settings. Default.height = 15; } башка {

CircleSegment чөйрөлөр = thres. HoughCircles (HoughMethods. Gradient, 2, /*thres. Rows/4*/ 5, 200, 100, 60, 180);

if (circle. Length> 1) {List xs = circle. Select (c => c. Center. X). ToList (); xs. Sort (); Тизме ys = чөйрөлөр. Тандоо (c => c. Center. Y). ToList (); ys. Sort ();

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

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

if (medianX> image. Width - 15)

medianX = image. Width - 15; if (medianY> image. Height - 15) medianY = image. Height - 15;

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

эгер (аныктоо) {

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

Rect r = жаңы Rect ((int) Properties. Settings. Default.x, (int) Properties. Settings. Default.y, (int) Properties. Settings. Default.size, (int) Properties. Settings. Default.height);

Mat beadSample = жаңы Мат (сүрөт, r);

Скалярдык avgColor = Cv2. Mean (beadSample); detectColor = Color. FromArgb ((int) avgColor [2], (int) avgColor [1], (int) avgColor [0]);

image. Тик бурчтук (r, жаңы Скаляр (0, 150, 0));

window. ShowImage (сүрөт);

Cv2. WaitKey (1); videoRunning = чындык; }

videoRunning = жалган;

} }

жеке бош камераStartBtn_Click (объект жөнөтүүчү, EventArgs e) {

эгер (cameraStartBtn. Text == "баштоо") {

cvThread = жаңы тема (жаңы ThreadStart (cvThreadFunction)); runVideo = true; cvThread. Start (); cameraStartBtn. Text = "токтотуу"; while (! videoRunning) Thread. Sleep (100);

updateColorTimer. Start ();

} башка {

runVideo = false; cvThread. Join (); cameraStartBtn. Text = "баштоо"; }}

Түс

Эми биз мончоктун түсүн аныктай алабыз жана аны кайсы идишке түшүрүү керек экенин ошол түстүн негизинде чече алабыз.

Бул кадам түс салыштырууга таянат. Биз жалган позицияны чектөө үчүн түстөрдү айырмалай алгыбыз келет, бирок жалган терс нерселерди чектөөгө жетиштүү чекке жол беребиз. Түстөрдү салыштыруу чындыгында таң калыштуу татаал, анткени компьютерлердин түстөрдү RGB катары сактоосу жана адамдардын түстөрдү кабыл алуусу сызыктуу корреляциялашпайт. Андан да жаманы, астында каралып жаткан жарыктын түсү да эске алынышы керек.

Түс айырмасын эсептөө үчүн татаал алгоритм бар. Биз CIE2000ди колдонобуз, ал 1ге жакын сан чыгарат, эгерде 2 түс адам үчүн айырмаланбаса. Биз бул татаал эсептөөлөрдү аткаруу үчүн ColorMine C# китепканасын колдонуп жатабыз. 5 DeltaE мааниси жалган позитив менен жалган терс ортосунда жакшы компромисс сунуштай тургандыгы аныкталды.

Көбүнчө контейнерлерге караганда көбүрөөк түстөр болгондуктан, акыркы позиция кармоочу урна катары корголгон. Мен көбүнчө бул машинаны экинчи өтүүдө иштетүү үчүн бөлдүм.

Тизме

түстөр = жаңы тизме (); тизме colorPanels = жаңы тизме (); Түс тизмесиTxts = new List (); 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;

double matchDelta = 100;

үчүн (int i = 0; i <colors. Count; i ++) {

var RGB = жаңы Rgb ();

RGB. R = түстөр . R; RGB. G = түстөр . G; RGB. B = түстөр . B;

кош дельта = cRGB. Compare (RGB, жаңы CieDe2000Comparison ());

// кош дельта = deltaE (с, түстөр ); Console. WriteLine ("DeltaE (" + i. ToString () + "):" + delta. ToString ()); if (delta <matchDelta) {matchDelta = delta; bestMatch = i; }}

if (matchDelta <5) {Console. WriteLine ("Табылды! (Posn:" + bestMatch + "Delta:" + matchDelta + ")"); return bestMatch; }

if (colors. Count <numColorSpots) {Console. WriteLine ("New Color!"); colors. Add (c); this. BeginInvoke (жаңы Action (setBackColor), жаңы объект {colors. Count - 1}); writeOutColors (); кайтуу (түстөрдүн саны - 1); } else {Console. WriteLine ("Белгисиз түс!"); кайтаруу unknownColorIndex; }}

Сорттоо логикасы

Сорттоо функциясы мончокторду чынында сорттоо үчүн бардык бөлүктөрдү бириктирет. Бул функция арналган жипте иштейт; үстүнкү табакты жылдыруу, мончоктун түсүн аныктоо, урнага салуу, үстүнкү плитанын тегиз болушун камсыз кылуу, шуруларды эсептөө ж. Ал ошондой эле кармоочу контейнер толгондо иштебей калат - Болбосо мончоктор толуп кетет.

Thread colourTestThread; Логикалык runtest = false; жараксыз colourTest () {

if (! top. Engaged)

top. Engaged = true;

эгер (! асты. Жазылган)

bottom. Engaged = true;

while (runtest) {

nextMagnet (чыныгы);

Thread. Sleep (100); аракет {if (magSensor. SensorValue <(magSensorMax - 4)) alignMotor (); } кармоо {alignMotor (); }

nextCamera (чыныгы);

аныктоо = чындык;

while (detectCnt <5) Thread. Sleep (25); Console. WriteLine ("Detect Count:" + detectCnt); аныктоо = жалган;

Түс с = foundColor;

this. BeginInvoke (жаңы Action (setColorDet), жаңы объект {c}); int i = findColorPosition (c);

SetBottomPosition (i, true);

nextHole (чыныгы); colorCnts ++; this. BeginInvoke (жаңы аракет (setColorTxt), жаңы объект {i}); Thread. Sleep (250);

эгер (colorCnts [unknownColorIndex]> 500) {

top. Engaged = false; bottom. Engaged = false; runtest = false; this. BeginInvoke (new Action (setGoGreen), null); кайтуу; }}}

жеке боштук colourTestBtn_Click (объект жөнөтүүчү, EventArgs e) {

if (colourTestThread == null ||! colourTestThread. IsAlive) {colourTestThread = new Thread (new ThreadStart (colourTest)); runtest = true; colourTestThread. Start (); colourTestBtn. Text = "ТОКТО"; colourTestBtn. BackColor = Color. Red; } else {runtest = false; colourTestBtn. Text = "GO"; colourTestBtn. BackColor = Color. Green; }}

Бул жерде бизде жумушчу программа бар. Кээ бир коддор макаланын сыртында калган, андыктан аны иштетүү үчүн булакты карап көрүңүз.

Оптика сынагы
Оптика сынагы

Оптика сынагынын экинчи сыйлыгы

Сунушталууда: