Isi kandungan:

Penyusun Manik Robotik: 3 Langkah (dengan Gambar)
Penyusun Manik Robotik: 3 Langkah (dengan Gambar)

Video: Penyusun Manik Robotik: 3 Langkah (dengan Gambar)

Video: Penyusun Manik Robotik: 3 Langkah (dengan Gambar)
Video: Paling Kreatif - Buat Mobil Batmobile Paling Keren dari Magnetic Balls (Memuaskan) 2024, Julai
Anonim
Image
Image
Menyusun Manik Robot
Menyusun Manik Robot
Menyusun Manik Robot
Menyusun Manik Robot
Menyusun Manik Robot
Menyusun Manik Robot

Dalam projek ini, kami akan membina robot untuk menyusun manik Perler mengikut warna.

Saya selalu ingin membina robot penyusun warna, jadi ketika anak perempuan saya berminat dengan pembuatan manik Perler, saya melihat ini sebagai peluang yang tepat.

Manik-manik perler digunakan untuk membuat proyek seni bersatu dengan meletakkan banyak manik ke papan peg, dan kemudian mencairkannya dengan besi. Anda biasanya membeli manik-manik ini dalam bungkusan warna campuran 22, 000 manik raksasa, dan menghabiskan banyak masa untuk mencari warna yang anda mahukan, jadi saya fikir menyusunnya akan meningkatkan kecekapan seni.

Saya bekerja untuk Phidgets Inc. jadi saya sering menggunakan Phidgets untuk projek ini - tetapi ini dapat dilakukan dengan menggunakan perkakasan yang sesuai.

Langkah 1: Perkakasan

Inilah yang saya gunakan untuk membina ini. Saya membuatnya 100% dengan bahagian dari phidgets.com, dan perkara-perkara yang saya ada di sekitar rumah.

Papan Phidget, Motor, Perkakasan

  • 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 - Hab 4-Port USB2.0
  • 3031 - Pigtail Wanita 5.5x2.1mm
  • 3029 - 2 wayar 100 'Twisted Cable
  • 3604 - 10mm LED Putih (Beg 10)
  • 3402 - Kamera Web USB

Bahagian lain

  • Bekalan Kuasa 24VDC 2.0A
  • Kikis kayu dan logam dari garaj
  • Ikatan zip
  • Bekas plastik dengan bahagian bawahnya dipotong

Langkah 2: Reka bentuk Robot

Reka Robot
Reka Robot
Reka Robot
Reka Robot
Reka Robot
Reka Robot

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

Pengambilan Manik

Saya memutuskan untuk melakukan bahagian pertama dengan 2 keping papan lapis bulat, masing-masing dengan lubang yang digerudi di tempat yang sama. Bahagian bawah dipasang, dan bahagian atas dipasang pada motor stepper, yang dapat memutarnya di bawah hopper yang diisi dengan manik. Apabila lubang bergerak di bawah hopper, ia mengambil sebiji manik. Saya kemudian boleh memutarnya di bawah kamera web, dan kemudian memutarnya lebih jauh sehingga sesuai dengan lubang di bahagian bawah, di mana ia jatuh.

Dalam gambar ini, saya menguji bahawa sistem boleh berfungsi. Semuanya diperbaiki kecuali bahagian papan lapis bulat atas, yang dipasang pada motor stepper yang tidak kelihatan di bawahnya. Kamera web belum dipasang. Saya hanya menggunakan Panel Kawalan Phidget untuk beralih ke motor pada ketika ini.

Penyimpanan Manik

Bahagian seterusnya adalah merancang sistem tong sampah untuk menahan setiap warna. Saya memutuskan untuk menggunakan motor stepper kedua di bawah untuk menyokong dan memutar bekas bulat dengan ruang yang sama rata. Ini dapat digunakan untuk memutar petak yang betul di bawah lubang yang akan dikeluarkan manik.

Saya membina ini menggunakan kadbod dan pita saluran. Perkara yang paling penting di sini adalah ketekalan - setiap petak mestilah berukuran sama, dan keseluruhannya harus ditimbang sama rata sehingga berputar tanpa melangkau.

Penyingkiran manik dilakukan dengan menggunakan penutup yang ketat yang memaparkan satu petak pada satu masa, sehingga manik dapat dicurahkan.

Kamera

Kamera web dipasang di atas plat atas antara hopper dan lokasi lubang plat bawah. Ini membolehkan sistem melihat manik sebelum menjatuhkannya. LED digunakan untuk menerangi manik-manik di bawah kamera, dan cahaya sekeliling disekat, untuk menyediakan persekitaran pencahayaan yang konsisten. Ini sangat penting untuk pengesanan warna yang tepat, kerana pencahayaan ambien benar-benar dapat menghilangkan warna yang dirasakan.

Pengesanan Lokasi

Penting untuk sistem dapat mengesan putaran pemisah manik. Ini digunakan untuk mengatur posisi awal ketika memulai, tetapi juga untuk mengesan apakah motor stepper tidak segerak. Dalam sistem saya, sebiji manik kadangkala macet semasa diambil, dan sistem perlu dapat mengesan dan menangani keadaan ini - dengan membuat sedikit sokongan dan mencuba lagi.

Terdapat banyak cara untuk mengatasi ini. Saya memutuskan untuk menggunakan sensor magnetik 1108, dengan magnet yang tertanam di tepi plat atas. Ini membolehkan saya mengesahkan kedudukan pada setiap putaran. Penyelesaian yang lebih baik mungkin adalah pengekod pada motor stepper, tetapi saya mempunyai 1108 yang tergeletak sehingga saya menggunakannya.

Selesaikan Robot

Pada ketika ini, semuanya telah diselesaikan dan diuji. Sudah waktunya untuk memasangkan semuanya dengan baik dan beralih ke perisian penulisan.

Motor 2 stepper digerakkan oleh pengawal stepper STC1001. Hab HUB000 - USB VINT digunakan untuk menjalankan pengawal stepper, serta membaca sensor magnetik dan menggerakkan LED. Kamera web dan HUB0000 dilampirkan ke hab USB kecil. Pigtail 3031 dan beberapa wayar digunakan bersama dengan bekalan kuasa 24V untuk menghidupkan motor.

Langkah 3: Tulis Kod

Image
Image

C # dan Visual Studio 2015 digunakan untuk projek ini. Muat turun sumber di bahagian atas halaman ini dan ikuti - bahagian utama digariskan di bawah

Permulaan

Pertama, kita mesti membuat, membuka dan memulakan objek Phidget. Ini dilakukan dalam acara memuatkan bentuk, dan pengendali lampiran Phidget.

kekosongan peribadi Form1_Load (penghantar objek, EventArgs e) {

/ * Memulakan dan membuka Phidgets * /

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

bawah. HubPort = 1;

bawah. Attach + = Bawah_Lampirkan; bawah. Detach + = Bawah_Detas; bawah. PosisiChange + = Bawah_PosisiChange; 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; led. Channel = 0; led. Attach + = Led_Attach; led. Detach + = Led_Detach; diketuai. Buka (); }

kekosongan peribadi Led_Attach (penghantar objek, Phidget22. Events. AttachEventArgs e) {

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

kekosongan peribadi MagSensor_Attach (penghantar objek, Phidget22. Events. AttachEventArgs e) {

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

kekosongan peribadi Bottom_Attach (penghantar objek, Phidget22. Events. AttachEventArgs e) {

bawahAttachedChk. Checked = true; bawah. CurrentLimit = bawahCurrentLimit; bawah. Engaged = true; bawah. VelocityLimit = bottomVelocityLimit; bawah. Acceleration = bottomAccel; bawah. DataInterval = 100; }

kekosongan peribadi Top_Attach (penghantar objek, Phidget22. Events. AttachEventArgs e) {

atasAttachedChk. Checked = true; atas. CurrentLimit = topCurrentLimit; atas. Engaged = true; atas. RescaleFactor = -1; atas. VelocityLimit = -topVelocityLimit; atas. Acceleration = -topAccel; atas. DataInterval = 100; }

Kami juga membaca maklumat warna yang disimpan semasa inisialisasi, sehingga jalan sebelumnya dapat dilanjutkan.

Kedudukan Motor

Kod pengendalian motor terdiri daripada fungsi kemudahan untuk menggerakkan motor. Motor yang saya gunakan adalah 3, 200 1/16 langkah per revolusi, jadi saya membuat pemalar untuk ini.

Untuk motor teratas, ada 3 posisi yang ingin kita hantar ke motor ke: kamera web, lubang, dan magnet penentu kedudukan. Terdapat fungsi untuk melakukan perjalanan ke setiap posisi berikut:

kekosongan peribadi nextMagnet (Boolean waiting = false) {

double posn = top. Posisi% langkahPerRev;

atas. TargetPosition + = (stepPerRev - posn);

jika (tunggu)

sambil (atas. IsMoving) Thread. Sleep (50); }

kekosongan peribadi nextCamera (Boolean waiting = false) {

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

jika (tunggu)

sambil (atas. IsMoving) Thread. Sleep (50); }

kekosongan peribadi nextHole (tunggu Boolean = false) {

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

jika (tunggu)

sambil (atas. IsMoving) Thread. Sleep (50); }

Sebelum memulakan larian, plat atas diselaraskan menggunakan sensor magnet. Fungsi alignMotor dapat dipanggil setiap saat untuk menyelaraskan plat atas. Fungsi ini terlebih dahulu menjadikan plat menjadi 1 revolusi penuh sehingga ia melihat data magnet di atas ambang. Ia kemudian menyandarkan sedikit dan bergerak ke depan lagi perlahan-lahan, menangkap data sensor semasa berjalan. Akhirnya, ia menetapkan kedudukan ke lokasi data magnet maksimum, dan menetapkan semula kedudukan diimbangi ke 0. Oleh itu, kedudukan magnet maksimum harus selalu berada di (atas. Posisi% langkahPerRev)

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

// Cari magnet

atas. DataInterval = atas. MinDataInterval;

sawMagnet = palsu;

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

int tryCount = 0;

cuba lagi:

atas. TargetPosition + = stepPerRev;

sambil (atas. IsMoving &&! sawMagnet) Thread. Sleep (25);

jika (! sawMagnet) {

if (tryCount> 3) {Console. WriteLine ("Penjajaran gagal"); atas. Engaged = false; bawah. Engaged = false; runtest = palsu; kembali; }

cubaCount ++;

Console. WriteLine ("Adakah kita buntu? Mencuba sandaran …"); atas. TargetPosition - = 600; sambil (atas. IsMoving) Thread. Sleep (100);

goto tryagain;

}

atas. VelocityLimit = -100;

magData = Senarai baru> (); magSensor. SensorChange + = magSensorCollectPositionData; atas. TargetPosition + = 300; sambil (atas. IsMoving) Thread. Sleep (100);

magSensor. SensorChange - = magSensorCollectPositionData;

atas. VelocityLimit = -topVelocityLimit;

KeyValuePair max = magData [0];

foreach (pasangan KeyValuePair dalam magData) jika (pair. Value> max. Value) max = pair;

atas. AddPositionOffset (-max. Key);

magSensorMax = nilai maksimum.

atas. TargetPosition = 0;

sambil (atas. IsMoving) Thread. Sleep (100);

Console. WriteLine ("Sejajarkan berjaya");

}

Senarai> magData;

priv void magSensorCollectPositionData (penghantar objek, Phidget22. Events. VoltageRatioInputSensorChangeEventArgs e) {magData. Add (KeyValuePair baru (top. Position, e. SensorValue)); }

priv void magSensorStopMotor (penghantar objek, Phidget22. Events. VoltageRatioInputSensorChangeEventArgs e) {

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

Terakhir, motor bawah dikendalikan dengan menghantarnya ke salah satu kedudukan bekas manik. Untuk projek ini, kami mempunyai 19 kedudukan. Algoritma memilih jalan terpendek, dan berpusing sama arah jam atau lawan jam.

peribadi int BottomPosition {dapatkan {int posn = (int) bawah. Posisi% langkahPerRev; jika (posn <0) posn + = stepPerRev;

kembali (int) Math. Round (((posn * beadCompartments) / (double) stepPerRev));

} }

kekosongan peribadi SetBottomPosition (int posn, bool waiting = false) {

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

double currentPosn = bawah. Posisi% langkahPerRev;

double posnDiff = targetPosn - currentPosn;

// Simpan sebagai langkah penuh

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

jika (posnDiff <= 1600) bawah. TargetPosition + = posnDiff; bawah lain. TargetPosition - = (stepPerRev - posnDiff);

jika (tunggu)

sambil (bawah. IsMoving) Thread. Sleep (50); }

Kamera

OpenCV digunakan untuk membaca gambar dari kamera web. Benang kamera dimulakan sebelum memulakan utas penyortiran utama. Benang ini terus membaca dalam gambar, mengira warna rata-rata untuk kawasan tertentu menggunakan Mean dan mengemas kini pemboleh ubah warna global. Benang ini juga menggunakan HoughCircles untuk mengesan manik, atau lubang di plat atas, untuk memperbaiki kawasan yang dicarinya untuk mengesan warna. Angka ambang dan HoughCircles ditentukan melalui percubaan dan kesalahan, dan sangat bergantung pada kamera web, pencahayaan, dan jarak.

bool runVideo = true; bool videoRunning = false; Rakaman VideoCapture; Benang cvThread; Warna dikesanWarna; Boolean mengesan = salah; int detectCnt = 0;

kekosongan peribadi cvThreadFunction () {

videoRunning = palsu;

tangkap = VideoCapture baru (kamera yang dipilih);

menggunakan (Window window = new Window ("capture")) {

Gambar tikar = Mat baru (); Mat gambar2 = Mat baru (); sementara (runVideo) {capture. Read (gambar); jika (gambar. Kosong ()) pecah;

jika (mengesan)

mengesanCnt ++; lain boleh dikesanCnt = 0;

jika (mengesan || circleDetectChecked || showDetectionImgChecked) {

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

jika (showDetectionImgChecked)

gambar = thres;

jika (mengesan || circleDetectChecked) {

CircleSegment bead = thres. HoughCircles (HoughMethods. Gradient, 2, /*thres. Rows/4*/ 20, 200, 100, 20, 65); if (bead. Length> = 1) {image. Circle (manik [0]. Pusat, 3, Skalar baru (0, 100, 0), -1); image. Circle (bead [0]. Center, (int) bead [0]. Radius, Scalar baru (0, 0, 255), 3); if (manik [0]. Radius> = 55) {Properties. Settings. Default.x = (perpuluhan) manik [0]. Center. X + (perpuluhan) (manik [0]. Radius / 2); Properties. Settings. Default.y = (perpuluhan) manik [0]. Center. Y - (perpuluhan) (manik [0]. Radius / 2); } lain-lain {Properties. Settings. Default.x = (perpuluhan) manik [0]. Pusat. X + (perpuluhan) (manik [0]. Radius); Properties. Settings. Default.y = (perpuluhan) manik [0]. Center. Y - (perpuluhan) (manik [0]. Radius); } Properties. Settings. Default.size = 15; Properties. Settings. Default.height = 15; } lain {

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

if (circle. Length> 1) {List xs = circle. Pilih (c => c. Center. X). ToList (); xs. Susun (); Senaraikan ys = circle. Pilih (c => c. Center. Y). ToList (); ys. Susun ();

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

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

jika (medianX> gambar. Lebar - 15)

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

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

jika (mengesan) {

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 baru, (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); dikesanColor = Color. FromArgb ((int) avgColor [2], (int) avgColor [1], (int) avgColor [0]);

gambar. Segi empat tepat (r, Skalar baru (0, 150, 0));

window. ShowImage (gambar);

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

videoRunning = palsu;

} }

kamera kosong peribadiStartBtn_Click (penghantar objek, EventArgs e) {

jika (cameraStartBtn. Text == "mula") {

cvThread = Thread baru (ThreadStart baru (cvThreadFunction)); runVideo = benar; cvThread. Start (); cameraStartBtn. Text = "berhenti"; sambil (! videoRunning) Thread. Sleep (100);

kemas kiniColorTimer. Start ();

} lain {

runVideo = palsu; cvThread. Sertai (); cameraStartBtn. Text = "mula"; }}

Warna

Sekarang, kita dapat menentukan warna manik, dan memutuskan berdasarkan warna mana bekas yang akan diturunkan.

Langkah ini bergantung pada perbandingan warna. Kami ingin dapat membezakan warna untuk membatasi positif positif, tetapi juga membiarkan ambang yang cukup untuk mengehadkan negatif palsu. Membandingkan warna sebenarnya sangat rumit, kerana cara komputer menyimpan warna sebagai RGB, dan cara manusia melihat warna tidak berkorelasi secara linear. Untuk memperburuk keadaan, warna cahaya yang sedang dilihat juga harus dipertimbangkan.

Terdapat algoritma rumit untuk mengira perbezaan warna. Kami menggunakan CIE2000, yang menghasilkan angka dekat 1 jika 2 warna tidak dapat dibezakan oleh manusia. Kami menggunakan pustaka ColorMine C # untuk melakukan pengiraan yang rumit ini. Nilai DeltaE 5 didapati menawarkan kompromi yang baik antara positif palsu dan negatif palsu.

Oleh kerana terdapat lebih banyak warna daripada bekas, kedudukan terakhir disediakan sebagai tong sampah. Saya secara amnya mengetepikannya untuk berjalan walaupun mesin pada hantaran kedua.

Senaraikan

warna = Senarai baru (); Senarai colorPanels = Senarai baru (); Senaraikan warnaTxts = Senarai baru (); Senarai warnaCnts = Senarai baru ();

const int numColorSpots = 18;

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

Console. WriteLine ("Mencari warna …");

var cRGB = Rgb baru ();

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

int bestMatch = -1;

perlawanan bergandaDelta = 100;

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

var RGB = Rgb baru ();

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

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

// double delta = deltaE (c, warna ); Console. WriteLine ("DeltaE (" + i. ToString () + "):" + delta. ToString ()); jika (delta <matchDelta) {matchDelta = delta; bestMatch = i; }}

if (matchDelta <5) {Console. WriteLine ("Found! (Posn:" + bestMatch + "Delta:" + matchDelta + ")"); pulangkan bestMatch; }

if (colors. Count <numColorSpots) {Console. WriteLine ("Warna Baru!"); warna. Tambah (c); ini. BeginInvoke (Action baru (setBackColor), objek baru {colors. Count - 1}); tulisOutColors (); pulangan (warna. Kiraan - 1); } lain {Console. WriteLine ("Warna Tidak Dikenal!"); kembali tidak diketahuiColorIndex; }}

Logik Menyusun

Fungsi menyusun menyatukan semua kepingan untuk menyusun manik. Fungsi ini dijalankan dalam utas khusus; menggerakkan plat atas, mengesan warna manik, meletakkannya di tong sampah, memastikan plat atas tetap sejajar, mengira manik, dll. Ia juga berhenti berjalan apabila tong sampah menjadi penuh - Jika tidak, kita hanya berakhir dengan manik yang melimpah.

Thread colourTestThread; Boolean runtest = palsu; ujian warna tidak betul () {

jika (! atas. Terlibat)

atas. Engaged = true;

jika (! bawah. Terlibat)

bawah. Engaged = true;

semasa (ujian larian) {

nextMagnet (benar);

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

nextCamera (benar);

mengesan = benar;

sambil (DetikCnt <5) Thread. Sleep (25); Console. WriteLine ("Detect Count:" + detectCnt); mengesan = salah;

Warna c = dikesanWarna;

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

SetBottomPosition (i, benar);

nextHole (benar); colorCnts ++; ini. BeginInvoke (Action baru (setColorTxt), objek baru {i}); Benang. Tidur (250);

jika (colorCnts [unknownColorIndex]> 500) {

atas. Engaged = false; bawah. Engaged = false; runtest = palsu; ini. BeginInvoke (Tindakan baru (setGoGreen), null); kembali; }}}

colour void peribadiTestBtn_Click (penghantar objek, EventArgs e) {

if (colourTestThread == null ||! colourTestThread. IsAlive) {colourTestThread = Thread baru (ThreadStart baru (colourTest)); runtest = benar; colourTestThread. Start (); colourTestBtn. Text = "BERHENTI"; colourTestBtn. BackColor = Warna. Red; } lain {runtest = false; colourTestBtn. Text = "GO"; colourTestBtn. BackColor = Warna. Green; }}

Pada ketika ini, kami mempunyai program yang berfungsi. Beberapa bit kod ditinggalkan dari artikel, jadi perhatikan sumbernya untuk benar-benar menjalankannya.

Peraduan Optik
Peraduan Optik

Hadiah Kedua dalam Peraduan Optik

Disyorkan: