ROS dan Robot Pengemis Kotak Saraf

Biasanya, dua pertanyaan seperti itu muncul untuk kerajinan seperti: "bagaimana?" dan untuk apa?" Publikasi itu sendiri dikhususkan untuk pertanyaan pertama, dan saya akan segera menjawab pertanyaan kedua:

Saya memulai proyek ini untuk menguasai robot, dimulai dengan Raspberry Pi dan kamera. Seperti yang Anda ketahui, salah satu cara terbaik untuk mempelajari sesuatu adalah dengan membuat tugas teknis dan berusaha memenuhinya, sambil mendapatkan keterampilan yang diperlukan.

Pada waktu itu, saya masih belum memiliki ide cemerlang di bidang robotika, jadi saya memutuskan untuk membuat proyek yang sangat menyenangkan - robot pengemis. Hasilnya adalah robot mandiri pada Raspberry Pi dan ROS, menggunakan Movidius Neural Cumpute Stick untuk mendeteksi wajah. Dia berkeliaran di sekitar ruangan, mencari orang, dan mengocok kaleng di depan mereka. Seperti apa bentuk robot ini:



Robot bergerak secara acak di sekitar ruangan, dan jika memperhatikan seseorang, ia menggulungnya dan mengocok botol untuk hal-hal kecil. Untuk bersenang-senang, saya menambahkan sedikit ekspresi wajah kepadanya - dia tahu bagaimana menggerakkan alisnya:



Setelah upaya pertama, robot mencoba untuk menemukan wajahnya lagi dalam pandangan, menoleh ke orang itu dan mengguncang bank lagi. Tetapi apa yang terjadi jika Anda pergi saat ini:



Robot


Saya mengambil ide robot pengemis dari majalah Popular Mechanics . Penulisan prototipe dari Chris Eckert bernama Gimme terlihat sangat estetis.

gambar

Saya ingin lebih berkonsentrasi pada fungsionalitas, jadi case ini dirakit dari bahan improvisasi. Secara khusus, sudut PVC terbukti menjadi bahan paling serbaguna yang dapat Anda gunakan untuk menghubungkan hampir dua bagian. Tampaknya saat ini robot tersebut terdiri dari lima persen sudut PVC dan sekrup M3. Kasing itu sendiri terdiri dari tiga platform laminasi di mana kepala dan semua elektronik dipasang.

Basis robot adalah Raspberry Pi 2B , dan kode ini ditulis dalam C ++ dan terletak di GitHub .

Penglihatan


Untuk memahami kenyataan, robot menggunakan kamera Paspberry Pi Camera Module v2 , yang dapat dikontrol menggunakan perpustakaan RaspiCam .

Untuk deteksi wajah, saya mencoba beberapa pendekatan berbeda. Kualitas detektor klasik dari OpenCV tidak memuaskan saya, jadi pada akhirnya saya sampai pada solusi yang agak tidak standar. Deteksi orang yang terlibat dalam jaringan saraf, berjalan pada perangkat Movidius Neural Compute Stick (NCS) di bawah kerangka kendali OpenVINO .

NCS adalah perangkat keras untuk peluncuran jaringan saraf yang efektif, di dalamnya terdapat beberapa prosesor vektor yang dirancang khusus untuk ini. Perangkat terhubung melalui USB dan hanya mengkonsumsi daya 1 Watt. Dengan demikian, NCS bertindak sebagai co-prosesor untuk Raspberry Pi, yang tidak menarik jaringan saraf. Sementara NCS sedang memproses frame berikutnya, prosesor Paspberry gratis untuk operasi lain. Perlu dicatat bahwa untuk pengoperasian perangkat yang optimal, diperlukan antarmuka USB 3.0, yang tidak tersedia pada Raspberry versi lama; dengan USB 2.0 juga berfungsi, lebih lambat. Selain itu, agar tidak memblokir konektor USB Raspberry, saya menghubungkan NCS ke kabel USB pendek. Saya menulis secara terperinci tentang bekerja dengan Neural Compute Stick di artikel saya sebelumnya .

Awalnya saya mencoba berlatihdetektor wajah sendiri dengan arsitektur MobileNet + SSD pada kumpulan data terbuka. Detektor benar-benar bekerja, tetapi tidak terlalu stabil: dengan kemunduran yang tidak terhindarkan dari kondisi pemotretan (paparan dan gambar buram), kualitas detektor sangat menurun. Namun, setelah beberapa waktu, detektor wajah siap pakai muncul di OpenVINO, dan saya beralih ke detektor dengan lampu SqueezeNet + SSD arsitektur , yang tidak hanya bekerja lebih baik dalam berbagai kondisi pengambilan gambar, tetapi juga lebih cepat.

Sebelum mengunggah gambar ke NCS untuk mendapatkan prediksi detektor, gambar harus diproses terlebih dahulu. Detektor pilihan saya berfungsi dengan gambar berwarna300ร—300, jadi gambar perlu dikompres dulu. Untuk melakukan ini, saya menggunakan algoritma penskalaan paling ringan - metode tetangga terdekat (INTER_NEAREST di perpustakaan OpenCV). Ini bekerja sedikit lebih cepat daripada metode interpolasi, dan hampir tidak mempengaruhi hasilnya. Penting juga memperhatikan urutan saluran gambar: detektor mengharapkan urutan BGR, jadi Anda perlu mengatur yang sama untuk kamera.

Saya juga mencoba memisahkan pemrosesan video menjadi dua aliran, yang satu menerima frame berikutnya dari kamera dan memprosesnya, dan yang lain pada waktu itu mengunggah frame sebelumnya ke NCS dan menunggu hasil detektor. Dengan skema ini, secara teknis, kecepatan pemrosesan meningkat, tetapi penundaan antara menerima frame dan menerima deteksi untuknya juga meningkat. Karena ketertinggalan di belakang kenyataan ini, pemantauan wajah menjadi lebih sulit, jadi pada akhirnya saya menolak skema ini.

Selain benar-benar mendeteksi wajah, mereka juga harus dilacak untuk menghindari kesalahan detektor. Untuk melakukan ini, saya menggunakan pelacak ringan Simple Online Realtime Tracker (SORT) . Pelacak sederhana ini terdiri dari dua bagian: algoritma Hungaria digunakan untuk mencocokkan objek pada bingkai yang berdekatan, dan untuk memprediksi lintasan objek, jika tiba-tiba menghilang - filter Kalman . Ketika saya bermain dengan pelacakan wajah, saya menemukan bahwa lintasan yang diprediksi oleh filter Kalman bisa sangat tidak masuk akal dengan gerakan tiba-tiba, yang lagi-lagi hanya mempersulit proses.

Oleh karena itu, saya mematikan filter Kalman, hanya menyisakan algoritma pencocokan wajah dan penghitung jumlah frame berurutan di mana wajah terdeteksi - dengan cara ini saya menghilangkan positif palsu dari detektor.

Platform atas, dari kiri ke kanan: kamera, servos untuk mengontrol kepala dan alis, sakelar, terminal daya, Tombol Merah Besar.


Lalu lintas


Untuk gerakan, robot memiliki lima servos: dua FS5103R rotasi servos terus menerus memutar roda; Ada dua FS5109Ms biasa, satu di antaranya memutar kepala, dan yang lain mengocok kaleng; akhirnya, SG90 kecil menggerakkan alisnya.

Sejujurnya, mini-servos SG90 tampak seperti sampah bagi saya - salah satu servos saya memiliki lebar pulsa kontrol yang salah, dan hanya satu yang selamat di antara empat lainnya. Dalam keadilan, saya tidak sengaja mengambil salah satu pelayan dengan siku saya, tetapi dua yang lain tidak tahan beban (saya dulu menggunakannya untuk kepala dan kaleng). Bahkan servo terakhir, yang mendapat pekerjaan paling sederhana - menggerakkan alis, harus menyodok tongkat dari waktu ke waktu agar tidak mengganjal. Dengan servos lain, saya tidak melihat adanya masalah. Benar, servos rotasi kontinu kadang-kadang harus dikalibrasi agar tidak berputar dalam keadaan tidak aktif - untuk ini ada regulator kecil di atasnya yang dapat diputar dengan obeng jam-kepala.

Mengelola servos dengan Raspberry, ternyata, tidak begitu sederhana. Pertama, mereka dikendalikan olehmodulasi lebar-pulsa (PWM / PWM) , dan pada Raspberry hanya ada dua pin yang didukung PWM oleh perangkat keras . Kedua, tentu saja, Raspberry tidak akan dapat memberi daya pada servos, tidak akan tahan terhadap hal ini. Untungnya, masalah ini diselesaikan menggunakan pengontrol PWM eksternal.

Adafruit PCA9685 adalah pengontrol PWM 16-kanal yang dapat dikontrol melalui antarmuka I2C . Juga sangat nyaman karena memiliki terminal untuk memasok daya untuk servos. Selain itu, [secara teoritis] dimungkinkan untuk menghubungkan hingga 62 pengontrol, sambil menerima hingga 992 pin kendali - untuk ini Anda perlu menetapkan alamat unik untuk setiap pengontrol menggunakan jumper khusus. Jadi jika Anda tiba-tiba membutuhkan pasukan servos - Anda tahu apa yang harus dilakukan.

Untuk mengontrol PCA9685, ada perpustakaan tingkat tinggi yang bertindak sebagai ekstensi WiringPi. Bekerja dengan hal ini cukup mudah - selama inisialisasi, itu menciptakan 16 pin virtual di mana Anda dapat menulis sinyal PWM, tetapi pertama-tama Anda harus menghitung jumlah kutu. Untuk mengubah tuas servo ke sudut tertentu dalam rentang [0, 180], Anda harus terlebih dahulu menerjemahkan sudut ini ke dalam rentang panjang pulsa kontrol dalam milidetik [SERVO_MS_MIN, SERVO_MS_MAX]. Untuk semua servos saya, nilai-nilai ini sekitar 0,6 ms dan 2,4 ms, masing-masing. Secara umum, nilai-nilai ini dapat ditemukan dalam lembar data servo, tetapi praktik telah menunjukkan bahwa mereka dapat berbeda, sehingga mereka mungkin perlu dipilih. Kemudian bagi nilai yang dihasilkan dengan 20 ms (nilai standar dari panjang siklus kontrol) dan kalikan dengan jumlah maksimum kutu PCA9685 (4096):

void driveDegs(float angle, int pin) {
    int ticks = (int) (PCA_MAX_PWM * (angle/180.0f*(SERVO_MS_MAX-SERVO_MS_MIN) + SERVO_MS_MIN) / 20.0f); 
    pwmWrite(pin, ticks);
}

Demikian pula, ini dilakukan dengan rotasi servos terus menerus - alih-alih sudut, kami mengatur kecepatan dalam kisaran [-1,1].

Saya merakit sasis robot, dan juga bodi, dari cara improvisasi: Saya meletakkan roda furnitur pada drive servo rotasi terus menerus, dan penopang bola furnitur bertindak sebagai roda ketiga. Sebelumnya, bukannya itu, roda berdiri di atas dukungan berputar, tetapi dengan sasis seperti itu sulit untuk membuat tikungan yang tepat, jadi saya harus menggantinya. Ada juga roda kecil di bawah kaleng untuk memindahkan sebagian berat dari servo ke housing. Suatu hal sederhana yang tidak jelas bagi saya pada awalnya adalah bahwa tuas servo harus diperbaiki dengan sekrup, terutama untuk roda, sehingga mereka tidak jatuh sepanjang jalan. Karena kebodohan seperti itu, saya harus mengulang sasis sekali. Saya juga membuat robot bemper lebar yang terbuat dari sudut PVC sehingga tidak sering terjebak.

Sekarang tentang apa yang dapat Anda lakukan. Pertama, Anda bisa mengguncang toples dan menggerakkan alis - untuk ini, Anda hanya perlu memutar tuas servo ke sudut yang telah dipilih sebelumnya.

Kedua, Anda bisa memutar kepala Anda. Saya tidak ingin kepala berputar pada kecepatan maksimum servo, karena memiliki kamera di dalamnya. Oleh karena itu, saya memutuskan untuk mengurangi kecepatan secara terprogram: Saya perlu memutar tuas sedikit, kemudian menunggu beberapa milidetik - dan seterusnya hingga sudut yang diinginkan tercapai. Dalam hal ini, perlu untuk mengingat posisi absolut kepala saat ini dan setiap kali memeriksa apakah ia telah melampaui batas yang diizinkan (pada robot saya itu berada dalam kisaran [10, 90] derajat).

Ketiga, Anda dapat mengubah arah gerakan dengan mengubah kecepatan rotasi roda. Dengan cara yang sama, Anda dapat memutar platform, misalnya, untuk mengikuti wajah. Kecepatan sudut rotasi tergantung pada servos sendiri dan pada lokasi mereka pada sasis, sehingga lebih mudah untuk mengukurnya sekali dan kemudian memperhitungkannya saat menikung. Untuk menemukan penundaan yang diperlukan antara menyalakan motor untuk rotasi dan mematikannya, Anda perlu membagi modul sudut dengan kecepatan sudut.

Terakhir, Anda dapat memutar kepala dan sasis secara bersamaan dan tidak sinkron agar tidak membuang waktu. Saya melakukannya seperti ini:

auto waitRotation = std::async(std::launch::async, rotatePlatform, platformAngle);
success = driveHead(headAngle);
waitRotation.wait();

Platform pusat, dari kiri ke kanan: PCA9685, power bus, Raspberry Pi, MCP3008 ADC


Navigasi


Lalu saya tidak menyulitkan apa pun, jadi robot hanya menggunakan dua pencari jarak inframerah Sharp GP2Y0A02YK untuk navigasi. Ini juga tidak begitu sederhana, karena sensornya analog, tetapi Raspberry, tidak seperti Arduino, tidak memiliki input analog. Masalah ini diselesaikan oleh konverter analog-ke-digital (ADC / ADC) - Saya menggunakan MCP3008 10-bit, 8-channel. Itu dijual sebagai microcircuit terpisah, sehingga harus disolder ke papan sirkuit tercetak dan pin juga disolder di sana untuk membuatnya lebih mudah untuk terhubung. Juga, atas saran bati saya, yang lebih banyak meraba-raba dalam sirkuit, saya menyolder dua kapasitor (keramik dan elektrolitik) antara kaki-kaki catu daya dan tanah untuk menyerap suara dari bagian digital dari seluruh rangkaian. Output sensor tidak lebih dari tiga volt pada output, sehingga 3.3v dengan Raspberry dapat dihubungkan sebagai tegangan ADC referensi (VREF) - sama seperti untuk catu daya MCP3008 (VDD).

MCP3008 dapat dikontrol melalui antarmuka SPI , dan untuk ini bahkan lebih mudah untuk menemukan kode yang sudah jadi di GitHub .

Meskipun demikian, untuk pekerjaan yang mudah dengan ADC, Anda akan memerlukan beberapa tarian dengan rebana.
unsigned int analogRead(mcp3008Spi &adc, unsigned char channel)
{
    unsigned char spi_data[3];
    unsigned int val = 0;

    spi_data[0] = 1;  // start bit
    spi_data[1] = 0b10000000 | ( channel << 4); // mode and channel
    spi_data[2] = 0; // anything
    adc.spiWriteRead(spi_data, sizeof(spi_data));
  
    // read value, combine last two bits of second byte with whole third byte
    val = (spi_data[1]<< 8) & 0b1100000000; 
    val |= (spi_data[2] & 0xff);
    return val;
}


Tiga byte harus dikirim ke MCP3008, di mana bit mulai ditulis dalam byte pertama, dan mode dan nomor saluran (0-7) di byte kedua. Kita juga mendapatkan kembali tiga byte, setelah itu kita perlu merekatkan dua bit paling tidak signifikan dari byte kedua dengan semua bit yang ketiga.

Sekarang kita bisa mendapatkan nilai dari sensor, kita perlu mengkalibrasi mereka, karena kedua sensor mungkin sedikit berbeda satu sama lain. Secara umum, tampilan dari kejauhan karena kekuatan sinyal dari sensor ini adalah non-linear dan tidak terlalu sederhana ( untuk lebih jelasnya lihat lembar data, pdf ). Oleh karena itu, cukup untuk mengambil dua koefisien, ketika dikalikan dengan mana sensor akan memberikan nilai 1,0 pada jarak yang berarti dan sama.

Pembacaan sensor bisa sangat berisik, terutama pada hambatan yang sulit, jadi saya menggunakan EWMA untuk menghaluskan sinyal dari masing-masing sensor. Saya memilih parameter penghalusan oleh mata, sehingga sinyal tidak membuat kebisingan dan tidak jauh tertinggal dari kenyataan.

Tampak depan: bank, pengukur jarak dan bemper.


Nutrisi


Pertama, mari kita evaluasi apa yang dikonsumsi robot saat ini ( tentang konsumsi Raspberry dan peripheral saat ini ):

  • Raspberry Pi 2B: tidak kurang dari 350 mA, tetapi lebih banyak di bawah beban (hingga 750-820 mA (?));
  • Kamera: sekitar 250 mA;
  • Neural Compute Stick: menyatakan konsumsi daya 1 watt, pada tegangan 5 volt pada USB adalah 200 mA;
  • Sensor IR: masing-masing 33 mA ( lembar data, pdf );
  • MCP3008: , 0.5 (, pdf);
  • PCA9685: , 6 (, pdf);
  • : ~150-200 1500-2000 (stall current), ( FS5109M, pdf)
  • HDMI ( ): 50 ;
  • + ( ): ~200 .

Secara total, dapat diperkirakan bahwa 1,5-2,5 ampere harus cukup, asalkan semua servos tidak bergerak secara bersamaan di bawah beban berat. Pada saat yang sama, Raspberry membutuhkan tegangan 5 volt bersyarat, dan untuk servos - 4,8-6 volt. Masih menemukan sumber daya yang memenuhi persyaratan ini.

Akibatnya, saya memutuskan untuk menghidupkan robot dari baterai format 18650. Jika Anda mengambil dua baterai ROBITON 3.4 / Li18650 (3,6 volt, 3400 mAh, arus keluaran maksimum 4875 mA) dan menghubungkannya secara seri, mereka dapat menghasilkan hingga 4,8 ampere pada tegangan 7,2 volt. Dengan arus konsumsi 1,5-2,5 ampere, mereka harus cukup untuk satu atau dua jam.

Baterai, bagaimanapun, memiliki daya tarik: terlepas dari faktor bentuk yang ditunjukkan 18650, ukurannya jauh dari18ร—650mm - lebih panjang beberapa milimeter karena sirkuit kontrol pengisian daya bawaan. Karena itu, saya harus menusuk kompartemen baterai dengan pisau agar pas di sana.

Tetap hanya untuk menurunkan tegangan hingga 5 volt. Untuk ini, saya menggunakan dua konverter DC-DC step-down terpisah DFRobot Power Module. Sepotong besi ini memungkinkan Anda untuk menurunkan tegangan pada tegangan input 3,6-25 volt dan perbedaan tegangan setidaknya 0,6 volt. Untuk kenyamanan, ini memiliki saklar yang memungkinkan Anda untuk memilih tepat 5 volt pada output, atau Anda dapat mengkonfigurasi tegangan output sewenang-wenang menggunakan regulator khusus. Saya mengatur kedua konverter menjadi 5 volt; salah satunya memberi makan Raspberry melalui konektor Micro-USB, dan yang kedua memberi makan servos melalui terminal PCA9685. Ini diperlukan untuk memaksimalkan catu daya dari bagian logis dan daya robot sehingga mereka tidak saling mengganggu.

Pada tahap debugging, saya menggunakan catu daya Cina 9 volt, 2 ampere sebagai ganti baterai, dan itu sudah cukup bagi robot untuk bekerja - saya menghubungkannya, seperti baterai, ke dua konverter DC-DC. Oleh karena itu, untuk kenyamanan, saya membuat terminal pada robot, di mana Anda dapat menghubungkan catu daya atau baterai untuk dipilih. Ini sangat membantu ketika saya benar-benar menulis ulang semua kode pada ROS, dan saya harus men-debug robot untuk waktu yang lama, termasuk servos.

Untuk kenyamanan, saya juga harus membuat "bus listrik" - pada kenyataannya, hanya sepotong papan dengan tiga baris pin yang terhubung untuk ground, masing-masing 3.3v dan 5v. Bus terhubung ke pin Raspberry yang sesuai. Hanya pengukur jarak IR yang diaktifkan dari bus 5v, dan MCP3008 dan PCA9685 dari bus 3.3v.

Dan tentu saja, sesuai dengan tradisi lama yang baik, saya menempatkan Tombol Merah Besar pada robot - ketika ditekan, itu hanya mengganggu seluruh rangkaian daya. Itu tidak perlu digunakan untuk berhenti darurat, tetapi menyalakan robot dengan bantuan tombol benar-benar lebih nyaman.

Platform bawah, dari kiri ke kanan: kompartemen baterai, NCS, konverter DC-DC, drive servo dengan roda, pengukur jarak.


Kontrol robot


Tidak ada Wi-Fi di Raspberry Pi 2B, jadi saya harus terhubung melalui ssh melalui kabel Ethernet (omong-omong, ini bisa dilakukan langsung dari laptop, tanpa menggunakan router ). Ternyata skema ini: kita terhubung melalui ssh melalui kabel, mulai robot dan cabut kabel. Kemudian dapat dikembalikan ke tempatnya untuk mengakses Raspberry lagi. Ada solusi yang lebih elegan, tetapi saya memutuskan untuk tidak menyulitkan.

Agar robot dapat dengan mudah dihentikan tanpa mematikan, saya menambahkan saklar Soviet besar-besaran (dari kapal selam?) Untuk itu, ketika Anda mematikannya, program berakhir dan robot berhenti.

Saklar terhubung ke tanah dan ke salah satu pin GPIO Raspberry, dan Anda bisa membacanya menggunakan perpustakaan WiringPi :

wiringPiSetup();
pinMode(PIN_SWITCH, INPUT);
pullUpDnControl(PIN_SWITCH, PUD_UP);
bool value = digitalRead(BB_PIN_SWITCH);

Perlu dicatat bahwa dengan koneksi ini, tegangan pada pin harus ditarik hingga 3.3V, dan pada saat yang sama itu akan menghasilkan sinyal tinggi dalam keadaan terbuka, dan sinyal rendah dalam keadaan tertutup.

Menyatukan semuanya


Utas

Sekarang semua hal di atas perlu digabungkan menjadi satu program yang mengendalikan robot. Di versi pertama robot, saya melakukan ini menggunakan utas ( pthread ). Versi ini ada di cabang utama , tetapi kode di sana cukup menyeramkan.

Program ini bekerja dalam empat utas: satu utas mengambil bingkai dari kamera dan memulai detektor pada NCS; aliran kedua membaca data dari pengukur jarak; utas ketiga memonitor sakelar dan mengatur variabel global is_runningmenjadifalsejika tidak aktif; Utas utama bertanggung jawab atas perilaku robot dan kontrol servo. Utas memiliki petunjuk yang sama dengan utas utama, yang dengannya mereka menulis hasil pekerjaan mereka. Saya membatasi vektor yang menyimpan informasi tentang wajah yang ditemukan oleh detektor ke mutex, dan menyatakan variabel umum lainnya yang lebih sederhana sebagai atom. Untuk mengoordinasikan aliran detektor wajah dengan utas utama, ada bendera face_processedyang direset ketika hasil baru berasal dari detektor, dan naik ketika utas utama menggunakan hasil ini untuk memilih perilaku - ini diperlukan agar tidak memproses data lama yang mungkin tidak relevan setelah bergerak.

Versi ROS

dengan stream berfungsi dengan baik, tetapi saya memulai semua ini untuk mempelajari sesuatu, jadi mengapa tidak pada saat yang sama menguasaiRos ? Saya telah mendengar kerangka ini sejak lama, dan saya bahkan harus bekerja sedikit dengan hackathon, jadi pada akhirnya saya memutuskan untuk menulis ulang semua kode pada ROS. Versi kode ini terletak di cabang default ros dan terlihat jauh lebih baik. Jelas bahwa implementasi pada ROS hampir pasti akan lebih lambat daripada implementasi pada arus karena overhead pengiriman pesan dan yang lainnya - satu-satunya pertanyaan adalah berapa banyak?

Konsep ROS
ROS (Robot Operating System) โ€” , , , .

, , , (node), , , .

(topic) (message) , - .

โ€” (service). , , . ยซ ยป, .

.msg .srv . .

ROS .

Untuk robot saya, saya tidak menggunakan paket yang sudah jadi dengan algoritma dari ROS, saya hanya merancang kode robot dalam paket terpisah yang terdiri dari lima node yang saling berkomunikasi menggunakan pesan dan layanan ROS.

Node paling sederhana switch_node,, memonitor keadaan sakelar. Segera setelah sakelar dimatikan, node mulai mengirim spam ke pesan yang tidak informatif dari jenis booltopik terminator. Ini adalah sinyal ke simpul utama bahwa sudah waktunya untuk menyelesaikan pekerjaan.

Node kedua, sensor_nodesecara berkala membaca bacaan kedua pengukur jarak IR dan mengirimkannya ke topik dalam sensor_statesatu pesan. Juga, simpul ini bertanggung jawab untuk pemrosesan sinyal: penskalaan oleh faktor kalibrasi dan rata-rata bergerak.

Simpul ketigacamera_nodeDia bertanggung jawab untuk segala sesuatu yang berkaitan dengan wajah: dia mengambil gambar dari kamera, memprosesnya, menerima hasil detektor, melewati mereka melalui pelacak, dan kemudian menemukan wajah yang paling dekat dengan pusat bingkai - robot toh tidak menggunakan sisanya, tetapi Anda ingin membuat pesan yang lebih kecil. Pesan yang dikirim node ke topik camera_stateberisi nomor bingkai, fakta memiliki wajah (karena Anda juga perlu tahu tentang tidak adanya wajah), koordinat relatif sudut kiri atas, lebar dan tinggi wajah. Ini adalah bagaimana deskripsi jenis pesan dalam file terlihat seperti DetectionBox.msg:

int64 count
bool present
float32 x
float32 y
float32 width
float32 height

Simpul keempat ,, servo_nodebertanggung jawab atas servos. Pertama, ia mendukung layanan servo_actionyang memungkinkan salah satu tindakan dilakukan oleh servos dengan nomornya: untuk menempatkan seluruh simpul dalam keadaan awal (alis, bank, kepala, hentikan sasis); pindahkan kepala ke kondisi awal; kocok botol; menggambarkan dengan satu alis satu dari tiga ekspresi (baik, netral, jahat). Kedua, menggunakan layanan ini, servo_speedAnda dapat mengatur kecepatan baru untuk kedua roda dengan mengirimkannya dalam permintaan. Kedua layanan tidak mengembalikan apa pun. Akhirnya, ada layanan servo_head_platformyang memungkinkan Anda untuk memutar kepala dan / atau sasis sudut tertentu relatif terhadap posisi saat ini. Layanan ini kembali truejika dimungkinkan untuk memutar kepala setidaknya sebagian, danfalsejika tidak, dalam kasus ketika kepala sudah berada di perbatasan sudut yang diizinkan, dan kami mencoba untuk mengubahnya lebih jauh. Jika kedua sudut dalam permintaan adalah nol, layanan berputar secara tidak sinkron, seperti ditunjukkan di atas. Dalam loop utama, simpul servo tidak melakukan apa pun.

Di sini, misalnya, adalah deskripsi layanan servo_head_platform:

float32 head_delta
float32 platform_delta
---
bool head_success

Setiap node yang terdaftar mendukung layanan terminate_{switch, camera, sensor, servo}dengan permintaan respons kosong, yang menghentikan operasi node. Diimplementasikan dengan cara ini:

Beberapa kode
...
std::atomic_bool is_running; // global

bool terminate_node(std_srvs::Empty::Request &req, std_srvs::Empty::Response &ignored) {
    is_running = false;
    return true;
}

int main(int argc, char **argv) {
    is_running = true;
    ...
    while (is_running && ros::ok()) {
        // do stuff
    }
    ...
}


Node memiliki variabel global is_running, nilai yang menentukan siklus utama dari node. Layanan hanya me-reset variabel ini, dan loop utama terputus.

Ada juga simpul utama beggar_botdi mana logika dasar robot diimplementasikan. Sebelum dimulainya loop utama, ia berlangganan topik sensor_statedan camera_statemenyimpan konten pesan dalam variabel global dalam fungsi panggilan balik. Dia juga berlangganan ke topik terminator, panggilan balik yang me-reset bendera is_running, mengganggu loop utama. Juga, sebelum siklus dimulai, node mengumumkan antarmuka untuk layanan dari node servo dan menunggu beberapa detik untuk memulai node lain. Setelah loop utama berakhir, simpul ini memanggil layananterminate_{switch, camera, sensor, servo}, dengan demikian mematikan semua node lain, dan kemudian mematikannya sendiri. Yaitu, ketika sakelar dimatikan, kelima simpul menyelesaikan operasi.

Beralih ke ROS memaksa saya untuk mengubah struktur program cukup banyak. Sebagai contoh, sebelumnya dimungkinkan untuk mengubah kecepatan roda dengan frekuensi tinggi, dan ini bekerja dengan baik, tetapi layanan ROS bekerja dengan urutan besarnya lebih lambat, jadi saya harus menulis ulang kode sehingga layanan dipanggil hanya ketika kecepatannya benar-benar berubah (dalam "mode malas").

ROS juga memungkinkan Anda menjalankan semua simpul robot dengan cukup mudah. Untuk melakukan ini, Anda perlu menulis file peluncuran .launch yang mencantumkan semua node dan atribut lain dari robot dalam format xml, dan kemudian jalankan perintah:

roslaunch beggar_bot robot.launch

ROS vs pthread

Sekarang, akhirnya, Anda dapat membandingkan kecepatan versi ROS dan versi pthread. Saya melakukannya dengan cara ini: utas / simpul yang bertanggung jawab untuk bekerja dengan kamera menganggap FPS-nya (sebagai elemen paling lambat), asalkan semua yang lain juga berfungsi. Untuk versi pthread, saya secara konsisten mendapatkan FPS 9,99 atau lebih, untuk versi ROS ternyata sekitar 8,3. Sebenarnya, ini cukup untuk mainan seperti itu, tetapi biaya overhead cukup terlihat.

Perilaku robot


Idenya cukup sederhana: jika robot melihat seseorang, dia harus menyetir ke arahnya dan mengocok kaleng. Mengocok botol cukup sederhana dan menyenangkan, tetapi pertama-tama Anda harus menghubungi orang tersebut.

Ada fungsi follow_faceyang, jika ada wajah di bingkai, memutar sasis dan kepala robot ke arahnya (hanya wajah yang paling dekat dengan pusat yang diperhitungkan). Ini diperlukan agar robot selalu mempertahankan arahnya pada seseorang, jika ia berada dalam bingkai, dan juga melihat langsung ke wajah ketika ia mengocok botol. Variabel yang sama digunakan

untuk menyinkronkan fungsi ini dengan topik camera_state.face_processed, seperti dalam versi dengan stream. Idenya sama - kami hanya ingin memproses data sekali saja, karena robot terus bergerak. Fungsi pertama menunggu sampai panggilan balik topik dengan deteksi menurunkan bendera bahwa frame terakhir telah diproses. Sementara dia menunggu, dia terus-menerus menelepon ros::spinOnce()untuk menerima pesan baru (secara umum, ini harus dilakukan di mana pun program mengharapkan data baru). Jika ada wajah dalam bingkai, sudut dihitung, yang perlu memutar platform dan kepala - ini dapat dilakukan dengan mengetahui koordinat relatif dari pusat wajah dan bidang pandang kamera secara horizontal dan vertikal. Setelah itu, Anda dapat memanggil layanan servo_head_platformdan memindahkan robot.

Ada satu titik halus: informasi tentang posisi wajah tertinggal di belakang gerakan wajah yang sebenarnya dan mungkin tertinggal di belakang gerakan robot itu sendiri. Oleh karena itu, robot dapat melebih-lebihkan sudut rotasi, karena itu kepala mulai bergerak bolak-balik dengan peningkatan amplitudo. Untuk mencegah ini, saya membuat penundaan setelah pindah (300 ms), dan juga melewatkan satu bingkai setelah pindah. Untuk tujuan yang sama, sudut rotasi sasis dan kepala dikalikan dengan faktor 0,8 (komponen-P dari pengontrol PID masuk akal ).

Fungsifollow_facemengembalikan status seseorang. Seseorang dapat: absen, cukup dekat dengan pusat, terlalu jauh dari robot; Pilihan lain - ketika kita menghidupkan robot dan tidak tahu apa yang terjadi pada wajah (dalam proses pencarian); masih ada kasus langka ketika kepala berada di perbatasan, itulah sebabnya tidak mungkin untuk beralih ke wajah.

Suatu hal yang agak sederhana terjadi di loop utama:

  1. Telepon follow_facesampai orang tersebut memiliki status tertentu (apa pun, kecuali untuk "dalam proses pencarian"). Pada akhir langkah ini, robot akan melihat langsung ke wajah.
  2. Jika wajah ditemukan dan dekat:
    1. Kocok kaleng;
    2. Temukan wajahnya lagi;
    3. Jika wajah sudah di tempat, buatlah ekspresi yang baik dengan alis dan kocok botol lagi;
    4. Jika wajah telah menghilang, buatlah ekspresi marah dengan alis;
    5. Berbaliklah, pergi ke awal siklus.

  3. Jika tidak ada orang (atau jauh) - navigasi di sekitar ruangan:
    1. Jika ada jauh dari rintangan di kedua sisi, maju terus (jika wajah ditemukan, tetapi ternyata terlalu jauh, robot akan pergi ke orang tersebut);
    2. Jika rintangan dekat di kedua sisi, lakukan putaran acak di kisaran [90,180]โˆช[โˆ’180,โˆ’90];
    3. Jika rintangan hanya ada di satu sisi, putar ke arah yang berlawanan dengan sudut acak [0,90];
    4. Jika gerakan maju berlanjut terlalu lama (mungkin macet), tarik sedikit ke belakang dan belok secara acak di kisaran [90,180]โˆช[โˆ’180,โˆ’90];


Algoritma ini tidak mengklaim sebagai kecerdasan buatan yang kuat, namun, perilaku acak dan bemper lebar memungkinkan robot untuk keluar dari hampir semua posisi, cepat atau lambat.

Kesimpulan


Terlepas dari kesederhanaannya, proyek ini mencakup banyak topik non-sepele: bekerja dengan sensor analog, bekerja dengan PWM, visi komputer, koordinasi tugas-tugas yang tidak sinkron. Ditambah lagi, ini sangat menyenangkan. Mungkin, lebih lanjut saya akan melakukan sesuatu yang lebih bermakna, tetapi lebih dengan bias dalam pembelajaran yang mendalam.

Sebagai bonus - galeri:








All Articles