Daftar Isi:

Penyortiran Manik Robot: 3 Langkah (dengan Gambar)
Penyortiran Manik Robot: 3 Langkah (dengan Gambar)

Video: Penyortiran Manik Robot: 3 Langkah (dengan Gambar)

Video: Penyortiran Manik Robot: 3 Langkah (dengan Gambar)
Video: UANG JATUH (UDAH JANGAN RIBUT MASING2 AMBIL SATU YA) 2024, Juli
Anonim
Image
Image
Penyortiran Manik Robot
Penyortiran Manik Robot
Penyortiran Manik Robot
Penyortiran Manik Robot
Penyortiran Manik Robot
Penyortiran Manik Robot

Dalam proyek ini, kami akan membuat robot untuk menyortir manik-manik Perler berdasarkan warna.

Saya selalu ingin membuat robot pemilah warna, jadi ketika putri saya tertarik dengan kerajinan manik-manik Perler, saya melihat ini sebagai peluang yang sempurna.

Manik-manik Perler digunakan untuk membuat proyek seni menyatu dengan menempatkan banyak manik-manik ke papan pasak, dan kemudian melelehkannya bersama-sama dengan besi. Anda biasanya membeli manik-manik ini dalam 22.000 paket warna campuran manik-manik raksasa, dan menghabiskan banyak waktu mencari warna yang Anda inginkan, jadi saya pikir menyortirnya akan meningkatkan efisiensi seni.

Saya bekerja untuk Phidgets Inc. jadi saya menggunakan sebagian besar Phidget untuk proyek ini - tetapi ini dapat dilakukan dengan menggunakan perangkat keras apa pun yang sesuai.

Langkah 1: Perangkat Keras

Inilah yang saya gunakan untuk membangun ini. Saya membangunnya 100% dengan suku cadang dari phidgets.com, dan barang-barang yang saya miliki tergeletak di sekitar rumah.

Papan Phidget, Motor, Perangkat Keras

  • HUB0000 - Phidget Hub VINT
  • 1108 - Sensor Magnetik
  • 2x STC1001 - 2.5A Stepper Phidget
  • 2x 3324 - 42STH38 NEMA-17 Bipolar Gearless Stepper
  • 3x 3002 - Kabel Phidget 60cm
  • 3403 - Hub 4-Port USB2.0
  • 3031 - Kuncir Betina 5.5x2.1mm
  • 3029 - 2 kawat 100' Kabel Twisted
  • 3604 - LED Putih 10mm (Kantong 10)
  • 3402 - Kamera Web USB

Bagian lain

  • Catu Daya 24VDC 2.0A
  • Memo kayu dan logam dari garasi
  • ikatan zip
  • Wadah plastik dengan bagian bawah terpotong

Langkah 2: Rancang Robot

Desain Robotnya
Desain Robotnya
Desain Robot
Desain Robot
Desain Robot
Desain Robot

Kita perlu merancang sesuatu yang dapat mengambil satu manik dari hopper input, meletakkannya di bawah webcam, dan kemudian memindahkannya ke tempat sampah yang sesuai.

Pickup Manik

Saya memutuskan untuk melakukan bagian pertama dengan 2 lembar kayu lapis bundar, masing-masing dengan lubang yang dibor di tempat yang sama. Bagian bawah diperbaiki, dan bagian atas dipasang ke motor stepper, yang dapat memutarnya di bawah hopper yang diisi dengan manik-manik. Ketika lubang bergerak di bawah hopper, ia mengambil satu manik. Saya kemudian dapat memutarnya di bawah webcam, dan kemudian memutar lebih lanjut hingga cocok dengan lubang di bagian bawah, di mana titik itu jatuh.

Pada gambar ini, saya menguji bahwa sistem dapat bekerja. Semuanya sudah diperbaiki kecuali potongan kayu lapis bundar atas, yang dipasang pada motor stepper yang tidak terlihat di bawahnya. Webcam belum dipasang. Saya hanya menggunakan Panel Kontrol Phidget untuk beralih ke motor pada saat ini.

Penyimpanan Manik-manik

Bagian selanjutnya adalah merancang sistem bin untuk menampung setiap warna. Saya memutuskan untuk menggunakan motor stepper kedua di bawah ini untuk menopang dan memutar wadah bundar dengan kompartemen dengan jarak yang sama. Ini dapat digunakan untuk memutar kompartemen yang benar di bawah lubang tempat manik akan jatuh.

Saya membuat ini menggunakan karton dan lakban. Yang paling penting di sini adalah konsistensi - setiap kompartemen harus berukuran sama, dan semuanya harus berbobot merata sehingga berputar tanpa melompat.

Penghapusan manik-manik dilakukan dengan menggunakan tutup yang pas yang memperlihatkan satu kompartemen pada satu waktu, sehingga manik-manik dapat dicurahkan.

Kamera

Webcam dipasang di atas pelat atas antara hopper dan lokasi lubang pelat bawah. Ini memungkinkan sistem untuk melihat manik-manik sebelum menjatuhkannya. LED digunakan untuk menerangi manik-manik di bawah kamera, dan cahaya sekitar diblokir, untuk memberikan lingkungan pencahayaan yang konsisten. Ini sangat penting untuk deteksi warna yang akurat, karena pencahayaan sekitar dapat benar-benar menghilangkan warna yang dirasakan.

Deteksi Lokasi

Sangat penting bagi sistem untuk dapat mendeteksi rotasi pemisah manik. Ini digunakan untuk mengatur posisi awal saat memulai, tetapi juga untuk mendeteksi apakah motor stepper tidak sinkron. Di sistem saya, manik terkadang macet saat diambil, dan sistem harus dapat mendeteksi dan menangani situasi ini - dengan mencadangkan sedikit dan mencoba lagi.

Ada banyak cara untuk menangani ini. Saya memutuskan untuk menggunakan sensor magnetik 1108, dengan magnet yang tertanam di tepi pelat atas. Ini memungkinkan saya untuk memverifikasi posisi pada setiap rotasi. Solusi yang lebih baik mungkin adalah encoder pada motor stepper, tetapi saya memiliki 1108 tergeletak di sekitar jadi saya menggunakannya.

Selesaikan Robotnya

Pada titik ini, semuanya telah dikerjakan, dan diuji. Saatnya memasang semuanya dengan baik dan beralih ke perangkat lunak penulisan.

2 motor stepper digerakkan oleh pengontrol stepper STC1001. HUB000 - USB VINT hub digunakan untuk menjalankan pengontrol stepper, serta membaca sensor magnetik dan menggerakkan LED. Webcam dan HUB0000 keduanya terpasang ke hub USB kecil. Kuncir 3031 dan beberapa kawat digunakan bersama dengan catu daya 24V untuk memberi daya pada motor.

Langkah 3: Tulis Kode

Image
Image

C# dan Visual Studio 2015 digunakan untuk proyek ini. Unduh sumber di bagian atas halaman ini dan ikuti - bagian utama diuraikan di bawah ini

inisialisasi

Pertama, kita harus membuat, membuka dan menginisialisasi objek Phidget. Ini dilakukan dalam acara pemuatan formulir, dan penangan melampirkan Phidget.

private void Form1_Load(pengirim objek, EventArgs e) {

/* Inisialisasi dan buka Phidget */

atas. HubPort = 0; atas. Lampirkan += Atas_Lampirkan; top. Lepaskan += Top_Detach; top. PositionChange += Top_PositionChange; atas. Buka();

bawah. HubPort = 1;

bawah. Lampirkan += Bawah_Lampirkan; bottom. Detach += Bottom_Detach; bottom. PositionChange += Bottom_PositionChange; bawah. Buka();

magSensor. HubPort = 2;

magSensor. IsHubPortDevice = benar; magSensor. Attach += MagSensor_Attach; magSensor. Detach += MagSensor_Detach; magSensor. SensorChange += MagSensor_SensorChange; magSensor. Buka();

led. HubPort = 5;

led. IsHubPortDevice = benar; dipimpin. Saluran = 0; led. Lampirkan += Led_Lampirkan; led. Lepaskan += Led_Detach; dipimpin. Buka(); }

private void Led_Attach(pengirim objek, Phidget22. Events. AttachEventArgs e) {

ledAttachedChk. Checked = true; led. Negara = benar; ledChk. Checked = benar; }

private void MagSensor_Attach(pengirim objek, Phidget22. Events. AttachEventArgs e) {

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

private void Bottom_Attach(pengirim objek, Phidget22. Events. AttachEventArgs e) {

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

private void Top_Attach(pengirim objek, Phidget22. Events. AttachEventArgs e) {

topAttachedChk. Checked = true; top. CurrentLimit = topCurrentLimit; atas. Terlibat = benar; top. RescaleFactor = -1; top. VelocityLimit = -topVelocityLimit; top. Acceleration = -topAccel; top. DataInterval = 100; }

Kami juga membaca informasi warna yang disimpan selama inisialisasi, sehingga proses sebelumnya dapat dilanjutkan.

Pemosisian Motor

Kode penanganan motor terdiri dari fungsi kenyamanan untuk menggerakkan motor. Motor yang saya gunakan adalah 3,200 langkah 1/16 per putaran, jadi saya membuat konstanta untuk ini.

Untuk motor atas, ada 3 posisi yang ingin kita kirim ke motor ke: webcam, lubang, dan magnet posisi. Ada fungsi untuk bepergian ke masing-masing posisi ini:

private void nextMagnet(Boolean wait = false) {

double posn = atas. Posisi % langkahPerRev;

top. TargetPosition += (stepsPerRev - posn);

jika (tunggu)

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

private void nextCamera(Boolean wait = false) {

double posn = atas. Posisi % langkahPerRev; if (posn < Properties. Settings. Default.cameraOffset) top. TargetPosition += (Properties. Settings. Default.cameraOffset - posn); else top. TargetPosition += ((Properties. Settings. Default.cameraOffset - posn) + stepsPerRev);

jika (tunggu)

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

private void nextHole(Boolean wait = false) {

double posn = atas. Posisi % langkahPerRev; if (posn < Properties. Settings. Default.holeOffset) top. TargetPosition += (Properties. Settings. Default.holeOffset - posn); else top. TargetPosition += ((Properties. Settings. Default.holeOffset - posn) + stepsPerRev);

jika (tunggu)

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

Sebelum memulai lari, pelat atas disejajarkan menggunakan sensor magnetik. Fungsi alignMotor dapat dipanggil kapan saja untuk menyelaraskan pelat atas. Fungsi ini pertama-tama dengan cepat memutar pelat hingga 1 putaran penuh hingga melihat data magnet di atas ambang batas. Kemudian mundur sedikit dan bergerak maju lagi perlahan, menangkap data sensor saat berjalan. Terakhir, ini mengatur posisi ke lokasi data magnet maksimum, dan mengatur ulang posisi offset ke 0. Dengan demikian, posisi magnet maksimum harus selalu di (top. Position % stepsPerRev)

Thread alignMotorThread;Boolean sawMagnet; double magSensorMax = 0; private void alignMotor() {

//Temukan magnetnya

top. DataInterval = atas. MinDataInterval;

sawMagnet = salah;

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

int jumlah coba = 0;

coba lagi:

top. TargetPosition += langkahPerRev;

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

jika (!sawMagnet) {

if (tryCount > 3) { Console. WriteLine("Perataan gagal"); top. Bertunangan = false; bawah. Terlibat = salah; uji coba = salah; kembali; }

cobaHitung++;

Console. WriteLine("Apakah kita stuck? Mencoba backup…"); atas. TargetPosition -= 600; while (top. IsMoving) Thread. Sleep(100);

harus mencoba lagi;

}

atas. VelocityLimit = -100;

magData = Daftar baru>(); magSensor. SensorChange += magSensorCollectPositionData; atas. Posisi Target += 300; while (top. IsMoving) Thread. Sleep(100);

magSensor. SensorChange -= magSensorCollectPositionData;

top. VelocityLimit = -topVelocityLimit;

KeyValuePair maks = magData[0];

foreach (pasangan KeyValuePair di magData) if (pair. Value > max. Value) max = pair;

top. AddPositionOffset(-max. Key);

magSensorMax = maks. Nilai;

atas. TargetPosition = 0;

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

Console. WriteLine("Perataan berhasil");

}

Daftar> magData;

private void magSensorCollectPositionData(pengirim objek, Phidget22. Events. VoltageRatioInputSensorChangeEventArgs e) { magData. Add(New KeyValuePair(top. Position, e. SensorValue)); }

private void magSensorStopMotor(pengirim objek, Phidget22. Events. VoltageRatioInputSensorChangeEventArgs e) {

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

Terakhir, motor bawah dikendalikan dengan mengirimkannya ke salah satu posisi wadah manik. Untuk proyek ini, kami memiliki 19 posisi. Algoritma ini memilih jalur terpendek, dan berbelok searah jarum jam atau berlawanan arah jarum jam.

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

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

} }

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

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

double currentPosn = bawah. Posisi % langkahPerRev;

posnDiff ganda = targetPosn - currentPosn;

// Simpan sebagai langkah penuh

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

if (posnDiff <= 1600) bawah. TargetPosition += posnDiff; lain bawah. TargetPosition -= (stepsPerRev - posnDiff);

jika (tunggu)

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

Kamera

OpenCV digunakan untuk membaca gambar dari webcam. Utas kamera dimulai sebelum memulai utas penyortiran utama. Utas ini terus membaca dalam gambar, menghitung warna rata-rata untuk wilayah tertentu menggunakan Mean dan memperbarui variabel warna global. Utas juga menggunakan HoughCircles untuk mencoba mendeteksi manik, atau lubang di pelat atas, untuk menyempurnakan area yang dicari untuk deteksi warna. Ambang batas dan nomor HoughCircles ditentukan melalui coba-coba, dan sangat bergantung pada webcam, pencahayaan, dan jarak.

bool runVideo = true;bool videoRunning = false; Tangkapan VideoCapture; Utas cvBenang; Warna terdeteksiWarna; Deteksi Boolean = salah; int deteksiCnt = 0;

private void cvThreadFunction() {

videoRunning = salah;

capture = new VideoCapture(Kamera terpilih);

menggunakan (Jendela jendela = Jendela baru("capture")) {

Gambar tikar = Mat baru(); Mat image2 = Mat baru(); while (runVideo) { tangkap. Baca(gambar); if (image. Empty()) break;

jika (mendeteksi)

deteksiCnt++; lain detectCnt = 0;

if (mendeteksi || circleDetectChecked || showDetectionImgChecked) {

Cv2. CvtColor(gambar, gambar2, ColorConversionCodes. BGR2GRAY); Mat thres = image2. Threshold((double)Properties. Settings. Default.videoThresh, 255, ThresholdTypes. Binary); thres = thres. GaussianBlur(baru OpenCvSharp. Size(9, 9), 10);

jika (showDetectionImgChecked)

gambar = tiga;

if (mendeteksi || circleDetectChecked) {

CircleSegment bead = 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 (bead[0]. Radius >= 55) { Properties. Settings. Default.x = (desimal)bead[0]. Center. X + (desimal)(bead[0]. Radius / 2); Properties. Settings. Default.y = (desimal)bead[0]. Center. Y - (desimal)(bead[0]. Radius / 2); } else { Properti. Pengaturan. Default.x = (desimal)bead[0]. Center. X + (desimal)(bead[0]. Radius); Properties. Settings. Default.y = (desimal)bead[0]. Center. Y - (desimal)(bead[0]. Radius); } Properties. Settings. Default.size = 15; Properties. Settings. Default.height = 15; } lain {

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

if (lingkaran. Panjang > 1) { Daftar xs = lingkaran. Pilih(c => c. Pusat. X). ToList(); xs. Urutkan(); Daftar ys = lingkaran. Pilih(c => c. Pusat. Y). ToList(); ys. Urutkan();

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

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

if (medianX > image. Width - 15)

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

image. Lingkaran(medianX, medianY, 100, Skalar baru(0, 0, 150), 3);

jika (mendeteksi) {

Properties. Settings. Default.x = medianX - 7; Properties. Settings. Default.y = medianY - 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 = Mat baru(gambar, r);

Skalar avgColor = Cv2. Mean(beadSample); terdeteksiWarna = Warna. DariArgb((int)avgColor[2], (int)avgColor[1], (int)avgColor[0]);

image. Rectangle(r, Skalar baru(0, 150, 0));

jendela. ShowImage(gambar);

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

videoRunning = salah;

} }

private void cameraStartBtn_Click(pengirim objek, EventArgs e) {

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

cvThread = Utas baru(UtasMulai baru(cvThreadFunction)); runVideo = benar; cvThread. Mulai(); cameraStartBtn. Text = "berhenti"; while (!videoRunning) Thread. Sleep(100);

updateColorTimer. Start();

} lain {

runVideo = salah; cvThread. Gabung(); cameraStartBtn. Text = "mulai"; } }

Warna

Sekarang, kita dapat menentukan warna manik-manik, dan memutuskan berdasarkan warna itu wadah mana yang akan dijatuhkan.

Langkah ini bergantung pada perbandingan warna. Kami ingin dapat membedakan warna untuk membatasi positif palsu, tetapi juga memungkinkan ambang batas yang cukup untuk membatasi negatif palsu. Membandingkan warna sebenarnya sangat kompleks, karena cara komputer menyimpan warna sebagai RGB, dan cara manusia melihat warna tidak berkorelasi secara linier. Lebih buruk lagi, warna cahaya yang dilihat di bawah juga harus dipertimbangkan.

Ada algoritma yang rumit untuk menghitung perbedaan warna. Kami menggunakan CIE2000, yang menghasilkan angka mendekati 1 jika 2 warna tidak dapat dibedakan dengan manusia. Kami menggunakan perpustakaan ColorMine C# untuk melakukan perhitungan rumit ini. Nilai DeltaE 5 telah ditemukan menawarkan kompromi yang baik antara positif palsu dan negatif palsu.

Karena seringkali ada lebih banyak warna daripada wadah, posisi terakhir dicadangkan sebagai tempat sampah penampung. Saya biasanya menyisihkan ini untuk menjalankan mesin pada lintasan kedua.

Daftar

warna = Daftar baru (); Daftar colorPanels = Daftar baru (); Daftar colorsTxts = Daftar baru(); Daftar colorCnts = Daftar baru();

const int numColorSpots = 18;

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

Console. WriteLine("Menemukan warna…");

var cRGB = new RGB();

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

int Pencocokan Terbaik = -1;

pertandingan gandaDelta = 100;

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

var RGB = RGB baru();

RGB. R = warna. R; RGB. G = warna. G; RGB. B = warna. B;

delta ganda = cRGB. Compare(RGB, CieDe2000Comparison baru());

//delta ganda = deltaE(c, warna); Console. WriteLine("DeltaE (" + i. ToString() + "): " + delta. ToString()); if (delta < matchDelta) { matchDelta = delta; pertandingan terbaik = saya; } }

if (matchDelta < 5) { Console. WriteLine("Ditemukan! (Posn: " + bestMatch + " Delta: " + matchDelta + ")"); kembalikan pertandingan terbaik; }

if (colors. Count < numColorSpots) { Console. WriteLine("Warna Baru!"); warna. Tambahkan(c); this. BeginInvoke(Aksi baru(setBackColor), objek baru { colors. Count - 1 }); writeOutColors(); kembali (colors. Count - 1); } else { Console. WriteLine("Warna Tidak Diketahui!"); kembalikan unknownColorIndex; } }

Logika Pengurutan

Fungsi penyortiran menyatukan semua bagian untuk benar-benar menyortir manik-manik. Fungsi ini berjalan di utas khusus; memindahkan pelat atas, mendeteksi warna manik-manik, menempatkannya di tempat sampah, memastikan pelat atas tetap sejajar, menghitung manik-manik, dll. Itu juga berhenti berjalan ketika bak penampung menjadi penuh - Jika tidak, kita hanya akan berakhir dengan manik-manik yang meluap.

Thread colorTestThread;Boolean runtest = false; batal tes warna() {

jika (!top. Terlibat)

atas. Terlibat = benar;

jika (!bottom. Terlibat)

bawah. Terlibat = benar;

while (uji coba) {

magnet berikutnya(benar);

Benang. Tidur(100); coba { if (magSensor. SensorValue < (magSensorMax - 4)) alignMotor(); } tangkap { alignMotor(); }

kamera berikutnya(benar);

mendeteksi = benar;

while (detectCnt < 5) Thread. Sleep(25); Console. WriteLine("Deteksi Hitungan: " + detectCnt); mendeteksi = salah;

Warna c = warna terdeteksi;

this. BeginInvoke(Aksi baru (setColorDet), objek baru { c }); int i = findColorPosition(c);

SetBottomPosition(i, benar);

lubang berikutnya(benar); colorCnts++; this. BeginInvoke(Aksi baru(setColorTxt), objek baru { i }); Benang. Tidur(250);

if (colorCnts[unknownColorIndex] > 500) {

top. Bertunangan = false; bawah. Terlibat = salah; uji coba = salah; this. BeginInvoke(Aksi baru(setGoGreen), null); kembali; } } }

private void colourTestBtn_Click(pengirim objek, EventArgs e) {

if (colourTestThread == null || !colourTestThread. IsAlive) { colorTestThread = Thread baru(ThreadStart baru(colourTest)); uji coba = benar; colorTestThread. Start(); colorTestBtn. Text = "BERHENTI"; colorTestBtn. BackColor = Warna. Merah; } else { uji coba = salah; colorTestBtn. Text = "PERGI"; colorTestBtn. BackColor = Warna. Hijau; } }

Pada titik ini, kami memiliki program kerja. Beberapa bit kode tidak disertakan dalam artikel, jadi lihat sumbernya untuk benar-benar menjalankannya.

Kontes Optik
Kontes Optik

Juara Kedua dalam Lomba Optik

Direkomendasikan: