Съдържание:

Роботизирано сортиране на мъниста: 3 стъпки (със снимки)
Роботизирано сортиране на мъниста: 3 стъпки (със снимки)

Видео: Роботизирано сортиране на мъниста: 3 стъпки (със снимки)

Видео: Роботизирано сортиране на мъниста: 3 стъпки (със снимки)
Видео: Чернобыль: Разгадка тайных манипуляций историей ! Зомбирование в православной церкви. 2024, Юли
Anonim
Image
Image
Роботизирано сортиране на мъниста
Роботизирано сортиране на мъниста
Роботизирано сортиране на мъниста
Роботизирано сортиране на мъниста
Роботизирано сортиране на мъниста
Роботизирано сортиране на мъниста

В този проект ще изградим робот, който да сортира мъниста Perler по цвят.

Винаги съм искал да създам робот за сортиране на цветове, така че когато дъщеря ми се заинтересува от изработката на перлери от Perler, видях това като перфектна възможност.

Мъниста Перлер се използват за създаване на слети художествени проекти, като се поставят много мъниста върху дъска и след това се топят заедно с ютия. Обикновено купувате тези мъниста в гигантски опаковки със смесени цветове от 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 кабел 60см
  • 3403 - USB2.0 4 -Port Hub
  • 3031 - Женска косичка 5.5x2.1mm
  • 3029 - 2 жичен 100 'усукан кабел
  • 3604 - 10 мм бял светодиод (чанта от 10)
  • 3402 - USB уеб камера

Други части

  • 24VDC 2.0A захранване
  • Скрап от дърво и метал от гаража
  • Връзки с цип
  • Пластмасов контейнер с отрязано дъно

Стъпка 2: Проектирайте робота

Проектирайте робота
Проектирайте робота
Проектирайте робота
Проектирайте робота
Проектирайте робота
Проектирайте робота

Трябва да проектираме нещо, което може да вземе едно зърно от входния бункер, да го поставим под уеб камерата и след това да го преместим в съответното кошче.

Пикап от мъниста

Реших да направя 1 -ва част с 2 парчета кръгъл шперплат, всеки с дупка, пробита на същото място. Долната част е фиксирана, а горната част е прикрепена към стъпков двигател, който може да я завърти под бункер, пълен с мъниста. Когато отворът преминава под бункера, той вдига единично топче. След това мога да го завъртя под уеб камерата и след това да завъртя допълнително, докато съвпадне с отвора в долната част, в който момент тя пада.

На тази снимка тествам, че системата може да работи. Всичко е фиксирано, с изключение на горното кръгло парче шперплат, което е прикрепено към стъпков двигател, за да не се вижда отдолу. Уеб камерата все още не е монтирана. Просто използвам контролния панел на Phidget, за да се обърна към двигателя в този момент.

Съхранение на мъниста

Следващата част е да се проектира системата за кошчета за задържане на всеки цвят. Реших да използвам втори стъпков двигател по -долу, за да поддържам и завъртя кръгъл контейнер с равномерно разположени отделения. Това може да се използва за завъртане на правилното отделение под отвора, от който перлата ще изпаднат.

Изградих това с помощта на картон и тиксо. Най -важното тук е последователността - всяко отделение трябва да бъде с еднакъв размер, а цялото нещо трябва да бъде равномерно претеглено, така че да се върти, без да прескача.

Отстраняването на мъниста се осъществява чрез плътно прилепнал капак, който излага едно отделение наведнъж, така че мънистата могат да се изливат.

Камера

Уеб камерата е монтирана върху горната плоча между бункера и мястото на отвора на долната плоча. Това позволява на системата да погледне топчето, преди да го пусне. За осветяване на перлите под камерата се използва светодиод, а околната светлина се блокира, за да се осигури постоянна осветена среда. Това е много важно за точното разпознаване на цветовете, тъй като околното осветление наистина може да изхвърли възприемания цвят.

Откриване на местоположение

Важно е системата да може да открие въртенето на сепаратора за перли. Това се използва за задаване на началната позиция при стартиране, но също така и за откриване дали стъпковият двигател е излязъл от синхронизация. В моята система, мънисто понякога ще заседне, докато бъде взето, и системата трябваше да може да открие и да се справи с тази ситуация - като направи малко резервно копие и опита агиан.

Има много начини да се справите с това. Реших да използвам магнитен сензор 1108, с магнит, вграден в ръба на горната плоча. Това ми позволява да проверя позицията при всяко завъртане. По -добро решение вероятно би било енкодер на стъпковия двигател, но аз имах 1108, който лежа наоколо, така че го използвах.

Завършете робота

На този етап всичко е разработено и тествано. Време е да монтирате всичко добре и да преминете към софтуер за писане.

Двата стъпкови двигателя се задвижват от стъпкови контролери STC1001. HUB000 - USB VINT концентратор се използва за управление на стъпковите контролери, както и за четене на магнитния сензор и задвижване на светодиода. Уеб камерата и HUB0000 са свързани към малък USB хъб. 3031 косичка и малко тел се използват заедно с 24V захранване за захранване на двигателите.

Стъпка 3: Напишете код

Image
Image

За този проект се използват C# и Visual Studio 2015. Изтеглете източника в горната част на тази страница и следвайте - основните раздели са описани по -долу

Инициализация

Първо, трябва да създадем, отворим и инициализираме обектите на Phidget. Това се прави в събитието за зареждане на формуляра и манипулаторите на Phidget прикачени файлове.

private void Form1_Load (изпращач на обект, EventArgs e) {

/ * Инициализирайте и отворете Phidgets */

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

bottom. HubPort = 1;

долу. Прикрепяне += Долно_прикачване; bottom. Detach += Bottom_Detach; bottom. PositionChange += Bottom_PositionChange; отдолу. 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 (); }

private void Led_Attach (изпращач на обект, Phidget22. Events. AttachEventArgs e) {

ledAttachedChk. Checked = вярно; led. State = вярно; ledChk. Checked = вярно; }

private void MagSensor_Attach (изпращач на обект, Phidget22. Events. AttachEventArgs e) {

magSensorAttachedChk. Checked = вярно; magSensor. SensorType = VoltageRatioSensorType. PN_1108; magSensor. DataInterval = 16; }

private void Bottom_Attach (изпращач на обект, Phidget22. Events. AttachEventArgs e) {

bottomAttachedChk. Checked = вярно; bottom. CurrentLimit = bottomCurrentLimit; bottom. Engaged = true; bottom. VelocityLimit = bottomVelocityLimit; bottom. Acceleration = bottomAccel; bottom. DataInterval = 100; }

private void Top_Attach (подател на обект, Phidget22. Events. AttachEventArgs e) {

topAttachedChk. Checked = вярно; top. CurrentLimit = topCurrentLimit; top. Engaged = true; top. RescaleFactor = -1; top. VelocityLimit = -topVelocityLimit; top. Acceleration = -topAccel; top. 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 (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);

ако (изчакайте)

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

ако (изчакайте)

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

Преди да стартирате бягане, горната плоча се подравнява с помощта на магнитния сензор. Функцията alignMotor може да бъде извикана по всяко време за подравняване на горната плоча. Тази функция първо бързо завърта плочата до 1 пълен оборот, докато види магнитни данни над праг. След това се архивира малко и отново бавно се придвижва напред, улавяйки сензорните данни. И накрая, той задава позицията на максималното местоположение на магнитните данни и нулира позицията на отместване на 0. По този начин максималната позиция на магнита винаги трябва да е на (отгоре. Позиция % stepsPerRev)

Логически sawMagnet; двоен magSensorMax = 0; private void alignMotor () {

// Намерете магнита

top. DataInterval = top. MinDataInterval;

sawMagnet = false;

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 ("Align failed"); top. Engaged = false; bottom. Engaged = false; runtest = false; връщане; }

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 = magData [0];

foreach (двойка KeyValuePair в magData) if (pair. Value> max. Value) max = двойка;

top. AddPositionOffset (-max. Key);

magSensorMax = макс. стойност;

top. TargetPosition = 0;

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

Console. WriteLine ("Подравняването е успешно");

}

Списък> magData;

private void magSensorCollectPositionData (изпращач на обект, Phidget22. Events. VoltageRatioInputSensorChangeEventArgs д) {magData. Add (нов KeyValuePair (top. Position, e. SensorValue)); }

private void magSensorStopMotor (изпращач на обекти, Phidget22. Events. VoltageRatioInputSensorChangeEventArgs e) {

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

} }

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

posn = posn % beadCompartments; двоен targetPosn = (posn * stepsPerRev) / beadCompartments;

двоен currentPosn = bottom. Position % stepsPerRev;

двоен posnDiff = targetPosn - currentPosn;

// Запазете го като пълни стъпки

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

if (posnDiff <= 1600) отдолу. TargetPosition += posnDiff; else bottom. TargetPosition - = (stepsPerRev - posnDiff);

ако (изчакайте)

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

Камера

OpenCV се използва за четене на изображения от уеб камерата. Нишката на камерата се стартира преди стартиране на основната нишка за сортиране. Тази нишка непрекъснато чете в изображения, изчислява среден цвят за конкретен регион, използвайки Mean и актуализира глобална цветова променлива. Конецът също използва HoughCircles, за да се опита да открие или мънисто, или дупката в горната плоча, за да подобри зоната, която разглежда, за разпознаване на цвят. Праговите стойности и числата на HoughCircles бяха определени чрез опит и грешка и силно зависят от уеб камерата, осветлението и разстоянието.

bool runVideo = true; bool videoRunning = false; Заснемане на VideoCapture; Тема cvThread; Цвят е открит Цвят; Булево откриване = невярно; int detectCnt = 0;

private void cvThreadFunction () {

videoRunning = false;

улавяне = нов VideoCapture (избрана камера);

използвайки (Window window = new Window ("улавяне")) {

Mat изображение = нов Mat (); Mat изображение2 = нов мат (); while (runVideo) {улавяне. Четене (изображение); if (image. Empty ()) break;

ако (откриване)

detectCnt ++; иначе detectCnt = 0;

if (откриване || circleDetectChecked || showDetectionImgChecked) {

Cv2. CvtColor (изображение, изображение2, ColorConversionCodes. BGR2GREY); Mat Mom = image2. Threshold ((double) Properties. Settings. Default.videoThresh, 255, ThresholdTypes. Binary); молот = вършилка. GaussianBlur (нов OpenCvSharp. Size (9, 9), 10);

if (showDetectionImgChecked)

изображение = вършилка;

if (откриване || circleDetectChecked) {

CircleSegment топче = молот. 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); изображение. Кръг (мънисто [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]. Radius / 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; } else {

CircleSegment кръгове = ham. HoughCircles (HoughMethods. Gradient, 2, /*thres. Rows/4*/ 5, 200, 100, 60, 180);

if (кръгове. Дължина> 1) {Списък xs = кръгове. Изберете (c => c. Center. X). ToList (); xs. Sort (); Списък ys = кръгове. Изберете (c => c. Center. Y). ToList (); ys. Sort ();

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

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

if (medianX> изображение. Ширина - 15)

medianX = изображение. Ширина - 15; if (medianY> image. Height - 15) mediaanY = image. Height - 15;

image. Circle (medianX, medianY, 100, нов скаларен (0, 0, 150), 3);

if (откриване) {

Properties. Settings. Default.x = medianX - 7; Properties. Settings. Default.y = mediaanY - 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);

Скаларен avgColor = Cv2. Mean (beadSample); detectedColor = Color. FromArgb ((int) avgColor [2], (int) avgColor [1], (int) avgColor [0]);

image. Rectangle (r, нов скаларен (0, 150, 0));

window. ShowImage (изображение);

Cv2. WaitKey (1); videoRunning = вярно; }

videoRunning = false;

} }

private void cameraStartBtn_Click (изпращач на обект, EventArgs e) {

if (cameraStartBtn. Text == "старт") {

cvThread = нова нишка (нов ThreadStart (cvThreadFunction)); runVideo = вярно; cvThread. Start (); cameraStartBtn. Text = "стоп"; while (! videoRunning) Thread. Sleep (100);

updateColorTimer. Start ();

} else {

runVideo = false; cvThread. Join (); cameraStartBtn. Text = "старт"; }}

Цвят

Сега можем да определим цвета на мъниста и да решим въз основа на този цвят в кой контейнер да го пуснем.

Тази стъпка разчита на цветовото сравнение. Искаме да можем да различаваме цветовете, за да ограничим фалшивите положителни резултати, но също така да позволим достатъчно праг за ограничаване на фалшивите отрицателни. Сравняването на цветовете всъщност е изненадващо сложно, защото начинът, по който компютрите съхраняват цветовете като RGB, и начинът, по който хората възприемат цветовете, не корелират линейно. За да влоши положението, трябва да се вземе предвид и цветът на светлината, под която се разглежда цвят.

Има сложен алгоритъм за изчисляване на цветовата разлика. Използваме CIE2000, който извежда число близо до 1, ако 2 цвята са неразличими за човека. Използваме библиотеката ColorMine C#, за да направим тези сложни изчисления. Установено е, че стойност 5 на DeltaE предлага добър компромис между фалшиво положителен и фалшиво отрицателен.

Тъй като често има повече цветове от контейнерите, последната позиция е запазена като кошче за прихващане. Обикновено ги оставих настрана, за да пусна машината при втори проход.

Списък

цветове = нов List (); List colorPanels = нов List (); Цветове на списъкаTxts = нов List (); List colorCnts = нов List ();

const int numColorSpots = 18;

const int unknownColorIndex = 18; int findColorPosition (Цвят c) {

Console. WriteLine ("Намиране на цвят …");

var cRGB = нов Rgb ();

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

int bestMatch = -1;

двойно съвпадениеDelta = 100;

for (int i = 0; i <цветове. брой; i ++) {

var RGB = нов Rgb ();

RGB. R = цветове . R; RGB. G = цветове . G; RGB. B = цветове . B;

двойна делта = cRGB. Compare (RGB, нов CieDe2000Comparison ());

// двойна делта = deltaE (c, цветове ); Console. WriteLine ("DeltaE (" + i. ToString () + "):" + delta. ToString ()); if (delta <matchDelta) {matchDelta = делта; bestMatch = i; }}

if (matchDelta <5) {Console. WriteLine ("Намерено! (Posn:" + bestMatch + "Delta:" + matchDelta + ")"); връщане на bestMatch; }

if (colors. Count <numColorSpots) {Console. WriteLine ("Нов цвят!"); цветове. Добавяне (c); this. BeginInvoke (ново действие (setBackColor), нов обект {colors. Count - 1}); writeOutColors (); връщане (цветове. Брой - 1); } else {Console. WriteLine ("Неизвестен цвят!"); връщане неизвестенColorIndex; }}

Логика за сортиране

Функцията за сортиране обединява всички части, за да сортира истински мъниста. Тази функция работи в специална нишка; преместване на горната плоча, откриване на цвета на перлите, поставяне в кошче, като се уверите, че горната плоча остава подравнена, броене на мънистата и т.н. Той също така спира да работи, когато контейнерът за улов се напълни - В противен случай ние просто завършваме с препълнени мъниста.

Boolean runtest = false; void colourTest () {

ако (! топ. Ангажиран)

top. Engaged = true;

ако (! дъно. Ангажирано)

bottom. Engaged = true;

while (runtest) {

nextMagnet (вярно);

Thread. Sleep (100); опитайте {if (magSensor. SensorValue <(magSensorMax - 4)) alignMotor (); } catch {alignMotor (); }

nextCamera (вярно);

откриване = вярно;

while (detectCnt <5) Thread. Sleep (25); Console. WriteLine ("Detect Count:" + detectCnt); откриване = невярно;

Цвят c = откритColor;

this. BeginInvoke (ново действие (setColorDet), нов обект {c}); int i = findColorPosition (c);

SetBottomPosition (i, true);

nextHole (вярно); colorCnts ++; this. BeginInvoke (ново действие (setColorTxt), нов обект {i}); Thread. Sleep (250);

if (colorCnts [unknownColorIndex]> 500) {

top. Engaged = false; bottom. Engaged = false; runtest = false; this. BeginInvoke (ново действие (setGoGreen), null); връщане; }}}

private void colourTestBtn_Click (подател на обект, EventArgs e) {

if (colourTestThread == null ||! colourTestThread. IsAlive) {colourTestThread = нова нишка (нов ThreadStart (colourTest)); runtest = true; colourTestThread. Start (); colourTestBtn. Text = "СТОП"; colourTestBtn. BackColor = Цвят. Червен; } else {runtest = false; colourTestBtn. Text = "ИДЕТЕ"; colourTestBtn. BackColor = Цвят. Зелен; }}

На този етап имаме работеща програма. Някои битове код бяха оставени извън статията, така че погледнете източника, за да го стартирате.

Конкурс по оптика
Конкурс по оптика

Втора награда в конкурса по оптика

Препоръчано: