İçindekiler:

Robotik Boncuk Sıralama: 3 Adım (Resimli)
Robotik Boncuk Sıralama: 3 Adım (Resimli)

Video: Robotik Boncuk Sıralama: 3 Adım (Resimli)

Video: Robotik Boncuk Sıralama: 3 Adım (Resimli)
Video: Elimi kestim. Demir kanaması deneyi #kimya #shorts 2024, Temmuz
Anonim
Image
Image
Robotik Boncuk Sıralama
Robotik Boncuk Sıralama
Robotik Boncuk Sıralama
Robotik Boncuk Sıralama
Robotik Boncuk Sıralama
Robotik Boncuk Sıralama

Bu projede Perler boncuklarını renge göre sıralayan bir robot yapacağız.

Her zaman bir renk sıralama robotu yapmak istemişimdir, bu yüzden kızım Perler boncuk işçiliğine ilgi duyduğunda bunu mükemmel bir fırsat olarak gördüm.

Perler boncukları, birçok boncuk bir pegboard üzerine yerleştirerek ve ardından bunları bir demir ile eriterek kaynaşmış sanat projeleri oluşturmak için kullanılır. Genellikle bu boncukları 22.000 boncuk karışık renkli dev paketlerde satın alıyorsunuz ve istediğiniz rengi aramak için çok zaman harcıyorsunuz, bu yüzden onları ayırmanın sanat verimliliğini artıracağını düşündüm.

Phidgets Inc. için çalışıyorum bu yüzden bu proje için çoğunlukla Phidgets kullandım - ancak bu uygun herhangi bir donanım kullanılarak yapılabilir.

Adım 1: Donanım

İşte bunu oluşturmak için kullandığım şey. % 100 phidgets.com'dan parçalar ve evin etrafında duran şeylerle yaptım.

Phidgets Panoları, Motorlar, Donanım

  • HUB0000 - VINT Hub Phidget'ı
  • 1108 - Manyetik Sensör
  • 2x STC1001 - 2.5A Step Phidget
  • 2x 3324 - 42STH38 NEMA-17 Bipolar Dişlisiz Step
  • 3x 3002 - Phidget Kablo 60cm
  • 3403 - USB2.0 4 Bağlantı Noktalı Hub
  • 3031 - Dişi Pigtail 5.5x2.1mm
  • 3029 - 2 telli 100' Bükümlü Kablo
  • 3604 - 10mm Beyaz LED (10'lu Çanta)
  • 3402 - USB Web Kamerası

Diğer bölümler

  • 24VDC 2.0A Güç Kaynağı
  • Garajdan ahşap ve metal hurdası
  • Zip bağları
  • Alt kısmı kesilmiş plastik kap

Adım 2: Robotu Tasarlayın

Robotu Tasarla
Robotu Tasarla
Robotu Tasarla
Robotu Tasarla
Robotu Tasarla
Robotu Tasarla

Giriş haznesinden tek bir boncuk alabilecek, onu web kamerasının altına yerleştirebilecek ve ardından uygun bölmeye taşıyabilecek bir şey tasarlamamız gerekiyor.

Boncuk Toplama

1. kısmı 2 adet yuvarlak kontrplak ile yapmaya karar verdim, her biri aynı yerde delik açılmış. Alt parça sabittir ve üst parça, boncuklarla dolu bir haznenin altında dönebilen bir kademeli motora bağlanmıştır. Delik haznenin altından geçtiğinde tek bir boncuk alır. Daha sonra onu web kamerasının altında döndürebilirim ve ardından alt parçadaki, içinden geçtiği delikle eşleşene kadar daha fazla döndürebilirim.

Bu resimde sistemin çalışıp çalışmadığını test ediyorum. Altında bir step motora bağlı olan üst yuvarlak kontrplak parçası dışında her şey sabittir. Web kamerası henüz monte edilmedi. Bu noktada motora geçmek için sadece Phidget Kontrol Panelini kullanıyorum.

Boncuk Depolama

Bir sonraki kısım, her rengi tutmak için bin sistemini tasarlamaktır. Eşit aralıklı bölmelere sahip yuvarlak bir kabı desteklemek ve döndürmek için aşağıda ikinci bir step motor kullanmaya karar verdim. Bu, boncuğun düşeceği deliğin altındaki doğru bölmeyi döndürmek için kullanılabilir.

Bunu karton ve koli bandı kullanarak yaptım. Buradaki en önemli şey tutarlılıktır - her bölme aynı boyutta olmalıdır ve her şey atlamadan dönmesi için eşit ağırlıkta olmalıdır.

Boncukların dışarı dökülebilmesi için her seferinde tek bir bölmeyi ortaya çıkaran sıkı oturan bir kapak vasıtasıyla boncuk çıkarma işlemi gerçekleştirilir.

Kamera

Web kamerası, hazne ile alt plaka deliği konumu arasındaki üst plakanın üzerine monte edilmiştir. Bu, sistemin boncuğa düşürmeden önce bakmasını sağlar. Tutarlı bir aydınlatma ortamı sağlamak için kameranın altındaki boncukları aydınlatmak için bir LED kullanılır ve ortam ışığı engellenir. Ortam aydınlatması algılanan rengi gerçekten yansıtabileceğinden, bu doğru renk tespiti için çok önemlidir.

Konum Algılama

Sistemin boncuk ayırıcının dönüşünü algılayabilmesi önemlidir. Bu, başlatma sırasında başlangıç konumunu ayarlamak için ve aynı zamanda step motorun senkronizasyondan çıkıp çıkmadığını tespit etmek için kullanılır. Sistemimde, bir boncuk bazen alınırken sıkışıyor ve sistemin bu durumu algılayabilmesi ve ele alabilmesi gerekiyordu - biraz yedekleyip agian'ı deneyerek.

Bununla başa çıkmanın birçok yolu var. Üst plakanın kenarına gömülü bir mıknatıs ile 1108 manyetik sensör kullanmaya karar verdim. Bu, her dönüşte konumu doğrulamamı sağlıyor. Daha iyi bir çözüm, muhtemelen step motor üzerinde bir kodlayıcı olurdu, ancak etrafta yatan bir 1108'im vardı, onu kullandım.

Robotu Bitir

Bu noktada, her şey çalıştı ve test edildi. Her şeyi güzelce monte etmenin ve yazılım yazmaya geçmenin zamanı geldi.

2 step motor, STC1001 step kontrolörleri tarafından tahrik edilmektedir. Adım denetleyicilerini çalıştırmanın yanı sıra manyetik sensörü okumak ve LED'i sürmek için bir HUB000 - USB VINT hub kullanılır. Web kamerası ve HUB0000, küçük bir USB hub'ına bağlıdır. Motorlara güç sağlamak için 24V güç kaynağı ile birlikte 3031 örgü ve bazı teller kullanılır.

3. Adım: Kod Yaz

Image
Image

Bu proje için C# ve Visual Studio 2015 kullanılmaktadır. Bu sayfanın en üstündeki kaynağı indirin ve takip edin - ana bölümler aşağıda özetlenmiştir

başlatma

İlk olarak, Phidget nesnelerini oluşturmalı, açmalı ve başlatmalıyız. Bu, form load olayında ve Phidget eklenti işleyicilerinde yapılır.

özel geçersiz Form1_Load(nesne gönderen, EventArgs e) {

/* Phidget'ları başlat ve aç */

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

alt. HubPort = 1;

alt. Ekle += Bottom_Attach; alt. Detach += Bottom_Detach; alt. PositionChange += Bottom_PositionChange; alt. Open();

magSensor. HubPort = 2;

magSensor. IsHubPortDevice = doğru; magSensor. Attach += MagSensor_Attach; magSensor. Detach += MagSensor_Detach; magSensor. SensorChange += MagSensor_SensorChange; magSensor. Open();

led. HubPort = 5;

led. IsHubPortDevice = doğru; led. Kanal = 0; led. Attach += Led_Attach; led. Detach += Led_Detach; led. Open(); }

private void Led_Attach(nesne gönderen, Phidget22. Events. AttachEventArgs e) {

ledAttachedChk. Checked = true; led. Durum = doğru; ledChk. Checked = doğru; }

özel geçersiz MagSensor_Attach(nesne gönderen, Phidget22. Events. AttachEventArgs e) {

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

private void Bottom_Attach(nesne gönderen, Phidget22. Events. AttachEventArgs e) {

altAttachedChk. Checked = true; alt. CurrentLimit = altCurrentLimit; alt. Etkileşimli = doğru; alt. VelocityLimit = altVelocityLimit; alt. Hızlanma = altAccel; alt. DataInterval = 100; }

private void Top_Attach(nesne gönderen, 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; }

Başlatma sırasında kaydedilen tüm renk bilgilerini de okuruz, böylece bir önceki çalıştırmaya devam edilebilir.

Motor Konumlandırma

Motor kullanım kodu, motorları hareket ettirmek için kolaylık işlevlerinden oluşur. Kullandığım motorlar devir başına 3.200 1/16. adım, bu yüzden bunun için bir sabit oluşturdum.

Üst motor için motora göndermek istediğimiz 3 konum vardır: web kamerası, delik ve konumlandırma mıknatısı. Bu konumların her birine seyahat etmek için bir işlev vardır:

private void nextMagnet(Boolean bekle = yanlış) {

double posn = top. Position %stepPerRev;

top. TargetPosition += (stepsPerRev - posn);

eğer (bekle)

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

private void nextCamera(Boolean bekle = yanlış) {

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

eğer (bekle)

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

private void nextHole(Boolean bekle = yanlış) {

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

eğer (bekle)

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

Çalışmaya başlamadan önce üst plaka manyetik sensör kullanılarak hizalanır. alignMotor işlevi, üst plakayı hizalamak için herhangi bir zamanda çağrılabilir. Bu işlev, önce mıknatıs verilerini bir eşiğin üzerinde görene kadar plakayı hızlı bir şekilde 1 tam devire kadar döndürür. Daha sonra biraz geri çekilir ve yavaş yavaş tekrar ileri doğru hareket eder, ilerledikçe sensör verilerini yakalar. Son olarak, konumu maksimum mıknatıs veri konumuna ayarlar ve konum sapmasını 0'a sıfırlar. Bu nedenle, maksimum mıknatıs konumu her zaman (top. Position %stepPerRev) konumunda olmalıdır.

Diş hizalamaMotorThread;Boole testeresiMagnet; çift magSensorMax = 0; özel boşluk hizalamaMotor() {

//Mıknatısı bul

top. DataInterval = top. MinDataInterval;

testereMagnet = yanlış;

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

int tryCount = 0;

Tekrar deneyin:

top. TargetPosition +=stepPerRev;

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

if (!sawMagnet) {

if (tryCount > 3) { Console. WriteLine("Hizalama başarısız"); top. Engaged = yanlış; alt. Engaged = yanlış; çalışma testi = yanlış; dönüş; }

tryCount++;

Console. WriteLine("Takılıp mı kaldık? Yedekleme deniyor…"); top. TargetPosition -= 600; while (top. IsMoving) Thread. Sleep(100);

tekrar dene;

}

top. VelocityLimit = -100;

magData = yeni Liste>(); magSensor. SensorChange += magSensorCollectPositionData; top. TargetPosition += 300; while (top. IsMoving) Thread. Sleep(100);

magSensor. SensorChange -= magSensorCollectPositionData;

top. VelocityLimit = -topVelocityLimit;

KeyValuePair max = magData[0];

foreach (magData'da KeyValuePair çifti) if (pair. Value > max. Value) max = çift;

top. AddPositionOffset(-max. Key);

magSensorMax = max. Value;

top. TargetPosition = 0;

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

Console. WriteLine("Hizalama başarılı");

}

Liste> magData;

private void magSensorCollectPositionData(nesne gönderici, Phidget22. Events. VoltageRatioInputSensorChangeEventArgs e) { magData. Add(new KeyValuePair(top. Position, e. SensorValue)); }

özel void magSensorStopMotor(nesne gönderen, Phidget22. Events. VoltageRatioInputSensorChangeEventArgs e) {

if (top. IsMoving && e. SensorValue > 5) { top. TargetPosition = top. Position - 300; magSensor. SensorChange -= magSensorStopMotor; testereMagnet = doğru; } }

Son olarak, alt motor, damak kabı konumlarından birine gönderilerek kontrol edilir. Bu proje için 19 pozisyonumuz var. Algoritma en kısa yolu seçiyor ve saat yönünde veya saat yönünün tersine dönüyor.

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

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

} }

private void SetBottomPosition(int posn, bool bekle = yanlış) {

posn = posn % boncukBölmeleri; double targetPosn = (posn *stepPerRev) / beadCompartments;

double currentPosn = alt. Konum %stepPerRev;

çift posnDiff = targetPosn - currentPosn;

// Tam adım olarak tut

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

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

eğer (bekle)

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

Kamera

OpenCV, web kamerasından görüntüleri okumak için kullanılır. Ana sıralama dizisine başlamadan önce kamera dizisi başlatılır. Bu iş parçacığı sürekli olarak görüntüleri okur, Ortalama'yı kullanarak belirli bir bölge için ortalama bir renk hesaplar ve genel bir renk değişkenini günceller. İplik ayrıca, renk algılaması için baktığı alanı hassaslaştırmak için bir boncuk veya üst plakadaki deliği algılamaya çalışmak için HoughCircles'ı kullanır. Eşik ve HoughCircles sayıları deneme yanılma yoluyla belirlendi ve büyük ölçüde web kamerasına, aydınlatmaya ve aralığa bağlıdır.

bool runVideo = true;bool videoRunning = false; VideoCapture yakalama; Konu cvThread; Renk algılandıRenk; Boole algılama = yanlış; int algılamaCnt = 0;

özel geçersiz cvThreadFunction() {

videoRunning = yanlış;

yakalama = yeni VideoCapture(seçilen Kamera);

kullanarak (Pencere penceresi = yeni Pencere ("yakala")) {

Mat görüntüsü = yeni Mat(); Mat image2 = yeni Mat(); while (runVideo) { yakalama. Okuma(resim); if (image. Empty()) break;

eğer (tespit ediliyor)

algılamaCnt++; başka algılamaCnt = 0;

if (algılanıyor || CircleDetectChecked || showDetectionImgChecked) {

Cv2. CvtColor(image, image2, ColorConversionCodes. BGR2GRAY); Mat eşik = image2. Threshold((double)Properties. Settings. Default.videoThresh, 255, ThresholdTypes. Binary); thres = thres. GaussianBlur(yeni OpenCvSharp. Size(9, 9), 10);

if (showDetectionImgChecked)

görüntü = üç;

if (algılanıyor || CircleDetectChecked) {

CircleSegment boncuk = 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 (boncuk[0]. Yarıçap >= 55) { Properties. Settings. Default.x = (ondalık)bead[0]. Center. X + (ondalık)(boncuk[0]. Yarıçap / 2); Properties. Settings. Default.y = (ondalık)boncuk[0]. Center. Y - (ondalık)(boncuk[0]. Yarıçap / 2); } else { Properties. Settings. Default.x = (ondalık)bead[0]. Center. X + (ondalık)(boncuk[0]. Yarıçap); Properties. Settings. Default.y = (ondalık)boncuk[0]. Center. Y - (ondalık)(boncuk[0]. Yarıçap); } Properties. Settings. Default.size = 15; Properties. Settings. Default.height = 15; } Başka {

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

if (circles. Length > 1) { Liste xs = çevreler. Select(c => c. Center. X). ToList(); xs. Sort(); Liste ys = çevreler. Select(c => c. Center. Y). ToList(); ys. Sort();

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

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

if (medyanX > görüntü. Genişlik - 15)

medyanX = görüntü. Genişlik - 15; if (ortanca > görüntü. Yükseklik - 15) ortancaY = görüntü. Yükseklik - 15;

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

if (tespit ediliyor) {

Properties. Settings. Default.x = medyanX - 7; Properties. Settings. Default.y = ortancaY - 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(resim, r);

Skaler avgColor = Cv2. Mean(beadSample); algılananColor = Color. FromArgb((int)avgColor[2], (int)avgColor[1], (int)avgColor[0]);

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

pencere. ShowImage(resim);

Cv2. WaitKey(1); videoRunning = doğru; }

videoRunning = yanlış;

} }

özel void cameraStartBtn_Click(nesne gönderen, EventArgs e) {

if (cameraStartBtn. Text == "başlat") {

cvThread = new Thread(new ThreadStart(cvThreadFunction)); runVideo = doğru; cvThread. Start(); cameraStartBtn. Text = "dur"; while (!videoRunning) Thread. Sleep(100);

updateColorTimer. Start();

} Başka {

runVideo = yanlış; cvThread. Join(); cameraStartBtn. Text = "başlat"; } }

Renk

Artık bir boncuğun rengini belirleyebilir ve o renge göre hangi kaba bırakacağımıza karar verebiliriz.

Bu adım, renk karşılaştırmasına dayanır. Yanlış pozitifleri sınırlamak için renkleri birbirinden ayırt edebilmek, aynı zamanda yanlış negatifleri sınırlamak için yeterli eşiğe izin vermek istiyoruz. Renkleri karşılaştırmak aslında şaşırtıcı derecede karmaşıktır, çünkü bilgisayarların renkleri RGB olarak depolama şekli ve insanların renkleri algılama şekli doğrusal olarak ilişkili değildir. Daha da kötüsü, bir rengin altında görüntülenen ışığın rengi de dikkate alınmalıdır.

Renk farkını hesaplamak için karmaşık algoritmalar vardır. Bir insan için 2 renk ayırt edilemezse 1'e yakın bir sayı veren CIE2000 kullanıyoruz. Bu karmaşık hesaplamaları yapmak için ColorMine C# kitaplığını kullanıyoruz. 5'lik bir DeltaE değerinin, yanlış pozitif ve yanlış negatif arasında iyi bir uzlaşma sağladığı bulunmuştur.

Genellikle kaplardan daha fazla renk olduğundan, son konum bir toplama kutusu olarak ayrılmıştır. Bunları genellikle makineyi ikinci bir geçişte çalıştırmak için bir kenara koyarım.

Liste

renkler = yeni Liste ();Liste colorPanels = yeni Liste (); Liste renkleriTxts = new List(); Liste colorCnts = new List();

const int numColorSpots = 18;

const int bilinmeyenColorIndex = 18; int findColorPosition(Renk c) {

Console. WriteLine("Renk Bulunuyor…");

var cRGB = new RGB();

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

int bestMatch = -1;

çift eşleşmeDelta = 100;

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

var RGB = new RGB();

RGB. R = renkler. R; RGB. G = renkler. G; RGB. B = renkler. B;

çift delta = cRGB. Compare(RGB, yeni CieDe2000Comparison());

//çift delta = deltaE(c, renkler); Console. WriteLine("DeltaE (" + i. ToString() + "): " + delta. ToString()); if (delta < eşleşmeDelta) { eşleşmeDelta = delta; en iyiMatch = ben; } }

if (matchDelta < 5) { Console. WriteLine("Bulunan! (Posn: " + bestMatch + " Delta: " + matchDelta + ")"); bestMatch'i döndür; }

if (colors. Count < numColorSpots) { Console. WriteLine("Yeni Renk!"); renkler. Ekle(c); this. BeginInvoke(new Action(setBackColor), new object { colors. Count - 1 }); writeOutColors(); dönüş (renkler. Sayı - 1); } else { Console. WriteLine("Bilinmeyen Renk!"); bilinmeyenColorIndex'i döndür; } }

Sıralama Mantığı

Sıralama işlevi, boncukları gerçekten sıralamak için tüm parçaları bir araya getirir. Bu işlev, özel bir iş parçacığında çalışır; üst plakayı hareket ettirmek, boncuk rengini tespit etmek, bir kutuya yerleştirmek, üst plakanın hizalı kalmasını sağlamak, boncukları saymak vb. Ayrıca, toplama kutusu dolduğunda çalışmayı durdurur - Aksi takdirde, taşan boncuklarla sonuçlanırız.

Diş rengi colorTestThread;Boole çalışma testi = false; geçersiz colorTest() {

if (!top. Nişanlı)

top. Engaged = true;

if (!bottom. Engaged)

alt. Etkileşimli = doğru;

while (çalışma testi) {

sonrakiMagnet(doğru);

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

sonrakiKamera(doğru);

tespit = doğru;

while (detectCnt < 5) Thread. Sleep(25); Console. WriteLine("Sayıyı Algıla: " + algılamaCnt); algılama = yanlış;

Renk c = algılananRenk;

this. BeginInvoke(yeni Eylem (setColorDet), yeni nesne { c }); int i = findColorPosition(c);

SetBottomPosition(i, doğru);

sonrakiDelik(doğru); colorCnts++; this. BeginInvoke(new Action(setColorTxt), new object { i }); Thread. Sleep(250);

if (colorCnts[unknownColorIndex] > 500) {

top. Engaged = yanlış; alt. Engaged = yanlış; çalışma testi = yanlış; this. BeginInvoke(new Action(setGoGreen), null); dönüş; } } }

özel geçersiz colorTestBtn_Click(nesne gönderen, EventArgs e) {

if (colorTestThread == null || !colorTestThread. IsAlive) { colorTestThread = new Thread(new ThreadStart(colorTest)); çalışma testi = doğru; colorTestThread. Start(); colorTestBtn. Text = "DUR"; colorTestBtn. BackColor = Color. Red; } başka { çalışma testi = yanlış; colorTestBtn. Text = "GİT"; colorTestBtn. BackColor = Color. Green; } }

Bu noktada bir çalışma programımız var. Bazı kod parçaları makalenin dışında bırakıldı, bu yüzden gerçekten çalıştırmak için kaynağa bir göz atın.

Optik Yarışması
Optik Yarışması

Optik Yarışmasında İkincilik Ödülü

Önerilen: