<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Robodev Blog]]></title><description><![CDATA[A blog for developers who are interested in learning and making robots.]]></description><link>https://robodev.blog</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1672262056561/AWBZKR3YL.png</url><title>Robodev Blog</title><link>https://robodev.blog</link></image><generator>RSS for Node</generator><lastBuildDate>Thu, 16 Apr 2026 09:06:49 GMT</lastBuildDate><atom:link href="https://robodev.blog/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[0. Mobile Robot Project Log]]></title><description><![CDATA[As said in the previous post, I will not wait until the robot is complete and then make tutorials. This project log contains all the newest updates (ideas, changes, problems, etc.) that I encounter during this project from the start to the end. As al...]]></description><link>https://robodev.blog/mobile-robot-project-log</link><guid isPermaLink="true">https://robodev.blog/mobile-robot-project-log</guid><category><![CDATA[autonomous mobile robots]]></category><category><![CDATA[DIY]]></category><category><![CDATA[ROS]]></category><category><![CDATA[robotics]]></category><category><![CDATA[autonomous guided vehicle]]></category><dc:creator><![CDATA[Trinh Nguyen]]></dc:creator><pubDate>Sun, 16 Apr 2023 08:08:58 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1695593599142/9a3ba203-c595-432e-8342-90d28545d02a.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>As said in <a target="_blank" href="https://robodev.blog/mobile-robot-project-plan-and-call-for-cooperation">the previous post</a>, I will not wait until the robot is complete and then make tutorials. This project log contains all the newest updates (ideas, changes, problems, etc.) that I encounter during this project from the start to the end. As always, I really appreciate any ideas or comments from you to improve the project, so don't hesitate to let me know.</p>
<h1 id="heading-16-april-2023-hardware-order">16 April 2023 - Hardware Order</h1>
<p>Today, I received all the main components that I ordered last week. Most of them are shown in the photo below. The most expensive one is the Raspberry Pi, then comes the Lidar and motors.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1681595288427/4c93de28-6d80-4b67-ac1e-b0f85c2bb318.jpeg" alt class="image--center mx-auto" /></p>
<p>I bought them from different sites (Ebay, Amazon, etc.) and more details can be found in the table below (to be updated).</p>
<p>I already tried to use popular devices that hopefully, you can easily find and buy too. But for those of you who don't have a big budget, I will also leave my recommendation in the section where I test each of them. Can't wait to test them out!</p>
<h1 id="heading-18-april-2023-install-ros-2">18 April 2023 - Install ROS 2</h1>
<p>I installed <a target="_blank" href="https://ubuntu.com/core/docs/install-nuc">Ubuntu 22</a> and <a target="_blank" href="https://docs.ros.org/en/humble/Installation.html">ROS 2 Humble</a> on my laptop (and later on the Raspberry Pi too). This is the first time I use ROS 2 so it's a bit bewildering because there are many changes.</p>
<p><img src="https://docs.ros.org/en/humble/_static/humble-small.png" alt="ROS 2 Documentation — ROS 2 Documentation: Humble documentation" class="image--center mx-auto" /></p>
<p>For the differences between ROS 2 and ROS 1, you can easily ask Google or ChatGPT or <a target="_blank" href="https://answers.ros.org/question/287470/what-is-the-difference-between-ros-and-ros2/">here</a> but some of my first impressions after going through some tutorials are:</p>
<ul>
<li><p>ROS 2 uses a new build system called <code>ament</code> instead of <code>catkin</code>. <code>ament</code> provides a number of advantages over <code>catkin</code>, including better support for cross-compiling, more flexible package structures, and better support for non-C++ languages like Python, bla bla bla. The underlying building concept is still similar between the two. ROS 2 provides a build tool named <code>colcon</code> that can build packages of multiple build systems (python, cmake, ament_cmake, ament_python, catkin...). So now instead of doing <code>catkin build</code>, we need to use <code>colcon build.</code></p>
</li>
<li><p>No <code>roscore</code> needed! One of the main changes in ROS 2 is the use of DDS (Data Distribution Service) as the default communication middleware. DDS provides a standard API for data communication, which allows nodes to communicate with each other directly, without the need for a central coordinator like <code>roscore</code>. In ROS 2, DDS is used for message passing, service invocation, and discovery of other nodes in the system.</p>
</li>
</ul>
<h1 id="heading-19-april-2023-test-lidar">19 April 2023 - Test Lidar</h1>
<p>The next thing I tested is the lidar. The one I bought is a Slamtec RPLIDAR A1 which is a 2D rotating lidar. The wire connection is quite straightforward. I just followed the instruction. The only thing I don't like is that there is no Micro-USB cable delivered without which I cannot connect it to my laptop, and I find it stupid. Luckily, I have some of them. After connecting to the laptop, the lidar ran right away.</p>
<p>In order to view the result from the lidar, I found <a target="_blank" href="https://github.com/CreedyNZ/rplidar_ros2">this repository</a> on Github from RpLidar and cloned it to the ros workspace. However, the building ran into some error saying something like <code>error: narrowing conversion of ‘rp::hal::Event::EVENT_TIMEOUT’ from ‘int’ to ‘long unsigned int’ [-Wnarrowing]</code> and I don't know how to solve it.</p>
<p>Then I found out that I can simply run <code>sudo apt install ros-humble-rplidar-ros</code> to install this package for ROS2 Humble. But when I ran it, another error <code>Error, cannot bind to the specified serial port '/dev/ttyUSB0'.</code> I checked if the serial connection <code>/dev/ttyUSB0</code> exist but it did not, so I guessed the problem may come from the cable. I tried all 3 USB cables that I have and one of them lit up the green LED from the lidar which gave me some hope, but no, the same error happened. I tried another <a target="_blank" href="https://github.com/babakhani/rplidar_ros2">package</a> which was forked from the original repository above and modified for ROS Humble, but the same error. Hmm!?</p>
<p>The last thing I tried is to restart my laptop. And, it worked. Oh my... Now I can see the point cloud from the lidar in rviz. Yay!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1681939036443/9be63a54-dfb9-47da-a826-b5de4185a819.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-01-may-2023-test-pi-and-motor">01 May 2023 - Test Pi and Motor</h1>
<p>Last week, I tried to set up the Raspberry Pi and check if it works fine. However, there was a problem that at first I thought was not a big deal but turns out it was. The board that I mistakenly ordered is a Compute Module 4 (not a Raspberry Pi 4 Model B), which has 8GB RAM and an SSD of 256 GB which are more than enough for this application. It also came with a very nice metal box. I started by flashing the Ubuntu 22 image file to the SSD to install it. However, when the Ubuntu installation window appeared, I could not use my mouse and keyboard. I googled the problem a lot and tried many suggestions like <a target="_blank" href="https://dphacks.com/2021/11/21/how-to-boot-a-pi-cm4-from-nvme-ssd/">here</a> or <a target="_blank" href="https://www.raspberrypi.com/documentation/computers/config_txt.html">here</a> but nothing works. I also tried to boot from an SD Card but wasted an hour (just trying different cards, restarting the board, etc.) just to find out that this Pi version does not accept SD Card since it comes with an SSD already. So in the end, I gave up and ordered a normal Raspberry Pi 4 Model B.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1682973230817/b0d52384-2dab-499d-b99c-176135a4b7a5.png" alt class="image--center mx-auto" /></p>
<p>In the meantime, I tested the motor directly with my laptop. This is also one tip to someone who doesn't or cannot buy a Pi, you can simply use your laptop for all the tasks in this series. The Pi is just a more compact solution and it is indeed a computer.</p>
<p>For the motor, I follow <a target="_blank" href="https://www.youtube.com/watch?v=-PCuDnpgiew">this tutorial</a> and it worked quite nicely. I used an Arduino Nano and an L298N motor driver (a 15V adapter, which is not in the image, is connected to the motor driver). The Arduino receives signals from my computer via the serial USB connection and controls the motor. It also reads the encoder values from the motor and sends them back to the laptop for close loop control for example. The code for the Arduino is from <a target="_blank" href="https://github.com/joshnewans/ros_arduino_bridge">this repo</a> and a demo with ROS can be found <a target="_blank" href="https://github.com/joshnewans/serial_motor_demo">here</a>. The theory about this step will be one big chapter later in this series so I will not go into detail here.</p>
<p>The wiring is shown below. Note that I just tested with one motor so far but for the second it should be similar. Next, I am trying to find a way to make the chassis (or the base) of the robot. Maybe I will simply start with a box or even a wooden plate. Let's see.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1682972795128/389e4da4-0966-4e14-8578-33ef5649235d.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-14-may-2023-mobile-robot-prototype">14 May 2023 - Mobile Robot Prototype</h1>
<p>First of all, sorry for such a late update. There are so many things going on in my life right now (new baby is coming, new job, finding a new flat, etc.), so it is difficult to make progress.</p>
<h2 id="heading-install-ubuntu-on-a-raspberry-pi">Install Ubuntu on a Raspberry Pi</h2>
<p>I bought a new Raspberry Pi board (Model 4B with 8GB RAM, 4GB verson should also work) and got no problem installing Ubuntu 22. First, I uploaded the installation file (as known as OS image) to an SD card (mine is a 64GB) using an application called <a target="_blank" href="https://www.raspberrypi.com/software/">Raspberry Pi Imager</a>. You can use Imager on Windows, Linux or Mac OS. After installation, open Imager and plug in the SD card. Click <strong>Choose OS</strong> &gt; <strong>Other general-purpose OS</strong> &gt; <strong>Ubuntu</strong> &gt; <strong>Ubuntu Desktop 22.04.2 LTS (64bit)</strong>, then click <strong>Choose storage</strong> and select the SD card. Finally, click write to start the uploading. When done, remove the SD card and plug it into the Pi. Turn on the Pi by connecting the power cable and connect it to a monitor with an HDMI cable. Follow the instruction on the screen to install Ubuntu which is pretty straightforward.</p>
<h2 id="heading-build-the-first-prototype">Build the first prototype</h2>
<p>I have built the first prototype of the robot using a plastic box with a size (Length x Width x Height) of 18x16x8 cm. I mounted the two wheels and a <a target="_blank" href="https://www.obi.de/rollen-raeder/moebel-lenkrolle-mit-platte-und-softrad-36-mm-35-kg/p/3911138">furniture swivel castor</a> under the box like below.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1684094764597/6cf0eb96-ed6f-4079-a2b0-aa60cb9b8162.png" alt="mobile-robot-prototype-1" class="image--center mx-auto" /></p>
<p>After that, I put the lidar and the Pi camera onto a wooden plate which is then mounted to the lid. All the other electronic parts are put inside the box. It looks a bit messy right now but I think it is fine for the first version.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1684095490874/eb67e317-7481-4961-aa72-66f67e8cbd9d.png" alt class="image--center mx-auto" /></p>
<p>Next, I will take a look into how to power the robot. Someone suggested using a LiPo battery or a power bank for the Pi plus a set of 8 AA batteries for the motor. I may try the latter first because I find it safer. The other thing I will do is to create a robot description (i.e. URDF) so that I can simulate it in ROS.</p>
<h1 id="heading-24-september-2023-access-pi-remotely">24 September 2023 - Access Pi Remotely</h1>
<p>The last few months have been a bit tough for me because I have got a second daughter (Yay!), changed my job and moved to a new place. That's why, there is no progress on this project. Sorry for that. Things are getting better now so I will bit by bit continue. Hope you are still interested.</p>
<p>Since the Pi is attached to the mobile robot, accessing it via cable is not feasible anymore. Luckily, we can access the Pi remotely from another computer (the host). The easiest way is to access via SSH (Secure Socket Shell) using tools such as <a target="_blank" href="https://www.putty.org/">PuTTY</a> or via <a target="_blank" href="https://en.wikipedia.org/wiki/Virtual_Network_Computing">VNC</a> (Virtual Network Computing). I am using <a target="_blank" href="https://www.realvnc.com/en/connect/download/viewer/">VNC Viewer</a> because it provides access to a graphical user interface (GUI). Here is how to do it:</p>
<p>Step 2: In the Pi, open <strong>Settings</strong> or simply hit the <strong>Windows</strong> button and search for <strong>Sharing</strong>. Turn on the sharing and Go to <strong>Remote Desktop</strong>. Then follow the steps in the image below: <em>Remote Desktop</em> ON -&gt; <em>Enable Legacy VNC Protocol</em> -&gt; Choose <em>Required a password</em> -&gt; <em>Remote Control</em> ON. Remember the password at the bottom. You can change the <em>User Name</em> and <em>Password</em> as you like but the password will be changed/regenerated every time you reboot the Pi. I will show you how to avoid it in Step 5.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1695591708951/0b1e2dd9-c8db-4ede-834c-070743de2a6e.png" alt class="image--center mx-auto" /></p>
<p>Step 2: Find the IP address of the internet connection on the Pi by using the following command: <code>hostname -I #captial i</code>. Mine is 192.168.0.21.</p>
<p>Step 3: Download and install <a target="_blank" href="https://www.realvnc.com/en/connect/download/viewer/">VNC Viewer</a> on your host PC. Mine is running Ubuntu 22 (VNC Viewer is available on Mac and Windows also). After installing, open VNC Viewer and enter the IP address from Step 2 plus a Name (name is optional). Hit OK to finish.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1695592619037/0ede2bab-3de3-4d57-af5d-8dc34231a01e.png" alt class="image--center mx-auto" /></p>
<p>Step 4: After that, double-click on the robodev icon in the main VNC Viewer window and select Continue. Enter the password from Step 2. Hit OK and the GUI of the Pi should appear on the host PC.</p>
<p>Step 5: Now you can access the Pi from remotely. However, there are still some issues that you may encounter.</p>
<ul>
<li><p>VNC only works when the Pi's display is active (meaning no sleep or screen saver), so we need to go to <strong>Power</strong> &gt; <strong>Screen Blank</strong> &gt; <strong>Never</strong>. You probably also want auto-login so that when you reboot your Pi you don't have to connect to a monitor to enter a password. So just go to <strong>Users</strong> -&gt; <strong>Unlock …</strong> -&gt; Enter Password  -&gt; Select <strong>Automatic Login</strong>.</p>
</li>
<li><p>By default, the remote desktop will automatically generate new authenticated password every time you reboot. To avoid that what you can do is (<a target="_blank" href="https://askubuntu.com/questions/1403943/22-04-remote-desktop-sharing-authentication-password-changes-every-reboot">source</a>): search for <strong>Passwords and Keys</strong>. On the <strong>Passwords</strong> tab on the left, remove <strong>vnc</strong> if it exists by right-clicking on it and selecting Delete. Then Right click on <strong>Login</strong> &gt; select <strong>Change Password</strong> \&gt; Enter your username password. Then it will ask you to enter a new password. DON'T type anything here and just click <strong>Continue</strong> twice. Then go back to <strong>Remote Desktop</strong>, ensure the password you want is set in <strong>Authentication</strong> part. Now each time you reboot, that password will then remain the same.</p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Mobile Robot Project: Plan and Call for Cooperation]]></title><description><![CDATA[After the ROS 101 series, I am now planning to bring the mobile robot to life. Below are my rough idea and plan, but they will be updated regularly as the project grows. So stay tuned!
The Idea
The goal of this project is simple: to understand how al...]]></description><link>https://robodev.blog/mobile-robot-project-plan-and-call-for-cooperation</link><guid isPermaLink="true">https://robodev.blog/mobile-robot-project-plan-and-call-for-cooperation</guid><category><![CDATA[ROS]]></category><category><![CDATA[DIY]]></category><category><![CDATA[robotics]]></category><category><![CDATA[mobile-robot]]></category><dc:creator><![CDATA[Trinh Nguyen]]></dc:creator><pubDate>Wed, 29 Mar 2023 20:21:35 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1680121066388/5ada1d0d-666b-4d9c-b8b9-d9c33ba0f7d9.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>After the ROS 101 series, I am now planning to bring the mobile robot to life. <strong>Below are my rough idea and plan, but they will be updated regularly as the project grows</strong>. So stay tuned!</p>
<h1 id="heading-the-idea">The Idea</h1>
<p>The goal of this project is simple: <strong>to understand how all the dots in a real robot (hardware, software, and algorithms) actually work and are connected</strong>. The reason I want to start with a mobile robot is that it is one of the simple robots with tons of applications: from vacuum cleaners and lawnmowers to Mars exploration rovers. Also, many new technologies can be deployed on a mobile robot, so we can learn a lot by making one.</p>
<p>There are already several awesome open-source projects out there helping you make your own mobile robot from scratch such as <a target="_blank" href="https://github.com/ros-mobile-robots/diffbot">this one</a> or <a target="_blank" href="https://articulatedrobotics.xyz/mobile-robot-full-list/">this one</a>, but I do not plan to just copy them. For me, a robot should serve a real purpose. Education is of course one of them here in Robodev but I want the robot to have a real-life application as well.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://giphy.com/gifs/Fsn4WJcqwlbtS">https://giphy.com/gifs/Fsn4WJcqwlbtS</a></div>
<p> </p>
<p>After doing some research, I decide to make a vacuum cleaner robot because it is a genius invention for reducing housework. It probably will cost more than a normal commercial one out there, but hey, building our own robot will enable us to do much more than just vacuum (and money may not be a big problem if someone, like you, is kind enough to <a target="_blank" href="https://robodev.blog/sponsor">sponsor</a> me😀).</p>
<h1 id="heading-the-plan">The Plan</h1>
<p>I plan to make this project agile, meaning I will not do and wait until everything is done, then make posts but I will show you all the steps and the changes along the way. That also means I need support from you. Any comments and suggestions for improvement would be really really appreciated.</p>
<p>A concrete plan will be created soon but this project in general will cover the following topics:</p>
<ol>
<li><p>Hardware</p>
<p> What components are required for a mobile robot? Why do we need them and how do they operate? Some hardware parts are mechanical links and joints, microcontrollers, motors, ultrasonic sensors, lidar, camera, IMU, etc.</p>
</li>
<li><p>Software</p>
<p> The main development platform is ROS 2. Programming languages are mainly C++ and Python.</p>
</li>
<li><p>Algorithms</p>
<p> State-of-the-art methods in navigation, control, SLAM (simultaneous Localization And Mapping), object detection, human detection, etc. will be applied.</p>
</li>
</ol>
<h1 id="heading-call-for-cooperation">Call for Cooperation</h1>
<p>To be honest, I have never made a mobile robot myself. I worked with some of them from Lego cars to KUKA industrial robots, but creating one is still on my wish list. Now is probably the best time and place to start. However, I do not want it to be a one-man show. It is not because I am unable to handle it myself but doing it alone would be very slow (I have a full-time job and 2 kids) with lots of stupid mistakes along the way (I am not an expert in everything). That's why, I am searching for partners to build up a team for this project. Basically, I need two people, one should have experience with mechanical design (know how to use CAD software, 3D printing, etc.) and the other should have some background in computer vision (SLAM, lidar, detection, etc.). We can do it completely online, or if you are living in Hamburg, Germany, we can also meet up.</p>
<p>So if you want to team up to make this robot, please reach out to me via my <a target="_blank" href="https://www.linkedin.com/in/trinh-c-nguyen/">LinkedIn</a>. I am looking forward to talking to you. It would be fun!</p>
<h1 id="heading-to-be-continued">To be continued ...</h1>
]]></content:encoded></item><item><title><![CDATA[12. Điều Khiển Robot Bằng Cử Chỉ Tay]]></title><description><![CDATA[Trong chương cuối cùng của sê-ri ROS Cơ Bản này, chúng ta sẽ kết hợp mọi thứ đã học được cho đến nay để hoàn thành một nhiệm vụ cuối cùng: di chuyển robot bằng cử chỉ tay.
Ý tưởng
Ý tưởng để làm phần này thật ra rất đơn giản và cũng không có gì mới. ...]]></description><link>https://robodev.blog/dieu-khien-robot-bang-cu-chi-tay</link><guid isPermaLink="true">https://robodev.blog/dieu-khien-robot-bang-cu-chi-tay</guid><category><![CDATA[ROS]]></category><category><![CDATA[vietnamese]]></category><category><![CDATA[robotics]]></category><category><![CDATA[Tutorial]]></category><dc:creator><![CDATA[Trinh Nguyen]]></dc:creator><pubDate>Thu, 16 Mar 2023 22:53:50 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1679007098276/5c0e88c5-a6f0-4ad7-b785-78a835b498fc.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Trong chương cuối cùng của sê-ri <a target="_blank" href="https://robodev.blog/series/ros-co-ban">ROS Cơ Bản</a> này, chúng ta sẽ kết hợp mọi thứ đã học được cho đến nay để hoàn thành một nhiệm vụ cuối cùng: di chuyển robot bằng cử chỉ tay.</p>
<h1 id="heading-y-tuong">Ý tưởng</h1>
<p>Ý tưởng để làm phần này thật ra rất đơn giản và cũng không có gì mới. Trong <a target="_blank" href="https://robodev.blog/mo-phong-robot-trong-ros-phan-2">chương trước</a>, chúng ta đã sử dụng <strong>rqt_robot_steering</strong>, một GUI đơn giản mà ROS cung cấp để điều khiển robot di động. Về cơ bản, nó cho phép bạn thay đổi vận tốc tuyến tính (thanh trượt dọc) để tiến/lùi và vận tốc góc (thanh trượt ngang) để rẽ trái/phải. Các giá trị vận tốc này sau đó được đóng gói vào một Twist message và publish ra topic <strong>/robot_diff_drive_controller/cmd_vel</strong>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1678057679052/01f4b5f5-85b5-42a0-ac75-0611f47bae78.png" alt="rqt_robot_steering" class="image--center mx-auto" /></p>
<p>Bây giờ thay vì sử dụng GUI này, bạn có thể sử dụng webcam để nhận biết các dấu tay (như ở <a target="_blank" href="https://robodev.blog/nhan-dien-cu-chi-tay-trong-ros">chương 9</a>) và chuyển đổi chúng để có các chức năng tương tự. Ngón trỏ trỏ lên/xuống (Forward/Backward) là để tiến/lùi, ngón cái (Turn Left/Turn Right) là để rẽ trái/phải và tất cả các ngón duỗi ra có nghĩa là dừng (Stop). Tất cả những gì chúng ta cần làm là viết một tập lệnh để chuyển đổi các cử chỉ này thành vận tốc và publish ra topic <code>/robot_diff_drive_controller/cmd_vel</code><em>.</em></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1678573851195/60cca149-65d3-41c6-bc77-0219722d1a5d.png" alt="hand-sign-robot-steering" class="image--center mx-auto" /></p>
<p>Nếu bạn muốn thay đổi, thêm hoặc xóa các cử chỉ tay, hãy làm theo hướng dẫn ở <a target="_blank" href="https://robodev.blog/nhan-dien-cu-chi-tay-trong-ros#heading-train-mo-hinh-nhan-dang-dau-tay">phần này</a> trong chương 9.</p>
<h1 id="heading-he-thong">Hệ thống</h1>
<p>Biểu đồ bên dưới mô tả cách toàn bộ hệ thống hoạt động. Các phần màu xanh lục là những gì chúng tôi đã làm từ các chương trước: node <code>/my_camera</code> đọc hình ảnh từ webcam và publish chúng lên topic <code>/image_raw</code> (từ các chương <a target="_blank" href="https://robodev.blog/tao-ros-publisher">7</a> và <a target="_blank" href="https://robodev.blog/tao-mot-ros-subscriber">8</a>); một Node khác là <code>/hand_sign_recognition</code> subscribe <code>/image_raw</code>, xử lý hình ảnh và publish kết quả ra topic <code>/gesture/hand_sign</code> (từ <a target="_blank" href="https://robodev.blog/nhan-dien-cu-chi-tay-trong-ros">chương 9</a>). Chúng ta cần triển khai phần màu xanh lam, thực chất là một Node và hãy gọi nó là <code>/hand_sign_control</code>. Node này subscribe topic <code>/gesture/hand_sign</code>, chuyển đổi các ký hiệu tay thành các giá trị điều khiển và publish ra topic <code>/robot_diff_drive_controller/ cmd_vel</code>. Từ topic này, node <code>/gazebo</code> (từ chương <a target="_blank" href="https://robodev.blog/mo-phong-robot-trong-ros-phan-1">10</a> và <a target="_blank" href="https://robodev.blog/mo-phong-robot-trong-ros-phan-2">11</a>) sẽ nhận các giá trị vận tốc và điều khiển rô-bốt tương ứng.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1678573736267/631ed49f-0467-436e-85a3-dcb2aaf8cf63.png" alt="hand-sign-mobile-robot-control-workflow" class="image--center mx-auto" /></p>
<h1 id="heading-trien-khai">Triển khai</h1>
<p>Sau khi có ý tưởng và hiểu cách hệ thống hoạt động, mình tin rằng việc triển khai sẽ khá đơn giản. Có nhiều cách khác nhau để làm và mình sẽ chỉ bạn một trong số đó. Hãy tạo một tập lệnh mới có tên <strong>sign_to_controller.py</strong> trong thư mục <em>ros_hand_gesture_recognition/src.</em> Về cơ bản, tập lệnh này sẽ xử lý các hoạt động trong phần màu xanh lá ở bên trên. Code được đặt <a target="_blank" href="https://github.com/TrinhNC/ros_hand_gesture_recognition/blob/main/src/sign_to_controller.py">tại đây</a> và bên dưới là giải thích cho những phần code chính.</p>
<p>Đầu tiên, lớp GestureController được tạo. Trong <code>__init__</code> của nó, các node <code>hand_sign_control</code>, <code>gesture_subscriber</code> và <code>vel_publisher</code> được khởi tạo. <code>gesture_subscriber</code> thực hiện hàm <code>callback</code> bất cứ khi nào nó nhận được mesage từ topic <code>/gesture/hand_sign</code> (tên topic này được định nghĩa trong tệp <a target="_blank" href="https://github.com/TrinhNC/ros_hand_gesture_recognition/blob/main/launch/sign_control.launch">launch</a> và được gọi bằng hàm <code>rospy.get_param</code>). <code>vel_publisher</code> publish<code>Twist</code> mesage tới topic <code>/robot_diff_drive_controller/cmd_vel</code>.</p>
<pre><code class="lang-python"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">GestureController</span>:</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self</span>):</span>
        rospy.init_node(<span class="hljs-string">'hand_sign_control'</span>, anonymous=<span class="hljs-literal">True</span>)
        <span class="hljs-comment"># Subscriber for subscribing the hand signs</span>
        self.gesture_subcriber = rospy.Subscriber(rospy.get_param(<span class="hljs-string">"hand_sign_recognition/publish_gesture_topic"</span>), String, self.callback)
        <span class="hljs-comment"># Publisher for publishing velocities </span>
        self.vel_publisher = rospy.Publisher(<span class="hljs-string">"/robot_diff_drive_controller/cmd_vel"</span>, Twist, queue_size=<span class="hljs-number">10</span>)
        <span class="hljs-comment"># Velocity message</span>
        self.vel_msg = Twist()
        <span class="hljs-comment"># Velocity in/decrements</span>
        self.linear_vel = <span class="hljs-number">0.01</span> <span class="hljs-comment">#[m/s]</span>
        self.angular_vel = <span class="hljs-number">0.1</span> <span class="hljs-comment">#[rad/s]</span>
</code></pre>
<p><a target="_blank" href="http://docs.ros.org/en/noetic/api/geometry_msgs/html/msg/Twist.html">Twist</a>, một loại ROS mesage, biểu thị vận tốc trong không gian trống. Twist được chia thành 2 phần: <em>tuyến tính</em> và <em>góc</em>. Mỗi phần là một vector có 3 thành phần con x, y, z. Trong trường hợp của chúng ta, rô-bốt được điều khiển bởi hai giá trị: vận tốc tuyến tính dọc theo trục X để tịnh tiến và vận tốc góc dọc theo trục Z để quay, do đó, chỉ có phần tử x trong <em>tuyến tính</em> và phần tử z trong <em>angular</em> là các tín hiệu điều khiển. Chúng được cập nhật trong hàm callback.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1678614693478/11d894cb-6ae7-48a1-89df-66ae993cc072.png" alt="ros-twist-mobile-robot" class="image--center mx-auto" /></p>
<p>Hàm callback là nơi diễn ra quá trình chuyển đổi và publish. Đầu tiên, nó kiểm tra cử chỉ đầu vào là gì. Nếu cử chỉ là <code>Forward</code> hoặc <code>Backward</code> (Tiến/Lùi) thì vận tốc tuyến tính được biểu thị bằng thành phần x của <code>vel_msg.linear</code> được tăng hoặc giảm tương ứng bởi <code>linear_vel</code> được đặt thành 0,01 m/s trong <code>__init__</code>. Nếu cử chỉ là <code>Turn Right</code> hoặc <code>Turn Left</code> (Rẽ Trái/Phải), vận tốc góc được biểu thị bằng thành phần z của <code>vel_msg.angular</code> sẽ được tăng hoặc giảm tương ứng bởi <code>angular_vel</code> được đặt thành 0,1 rad/s trong <code>__init__</code> . Trong các trường hợp Tiến/Lùi, vận tốc góc này được đặt bằng 0 để rô-bốt đi thẳng mà không bị quay cùng lúc. Nếu cử chỉ là <code>Stop</code> hoặc <code>NONE</code> (<code>NONE</code> có nghĩa là không dấu tay nào được phát hiện), thì tốc độ tuyến tính và tốc độ góc được đặt thành 0 tức là rô-bốt sẽ dừng lại. Cuối cùng, <code>vel_msg</code> được publish bởi <code>vel_publisher</code>.</p>
<pre><code class="lang-python">    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">callback</span>(<span class="hljs-params">self, gesture</span>):</span>
        <span class="hljs-keyword">if</span> gesture.data ==  <span class="hljs-string">"Forward"</span>:
            self.vel_msg.linear.x += self.linear_vel
            self.vel_msg.angular.z = <span class="hljs-number">0.</span>
        <span class="hljs-keyword">elif</span> gesture.data ==  <span class="hljs-string">"Backward"</span>:
            self.vel_msg.linear.x -= self.linear_vel
            self.vel_msg.angular.z = <span class="hljs-number">0.</span>
        <span class="hljs-keyword">elif</span> gesture.data == <span class="hljs-string">"Turn Right"</span>:
            self.vel_msg.angular.z -= self.angular_vel
        <span class="hljs-keyword">elif</span> gesture.data == <span class="hljs-string">"Turn Left"</span>:
            self.vel_msg.angular.z += self.angular_vel
        <span class="hljs-keyword">elif</span> gesture.data == <span class="hljs-string">"Stop"</span> <span class="hljs-keyword">or</span> gesture.data == <span class="hljs-string">"NONE"</span> :
            self.vel_msg.linear.x = <span class="hljs-number">0</span>
            self.vel_msg.angular.z = <span class="hljs-number">0</span>
        self.vel_publisher.publish(self.vel_msg)
</code></pre>
<h1 id="heading-lets-launch-them-all">Let's launch them all!</h1>
<p>Để chạy chương trình cần có 4 tệp: <em>sign_to_controller.py</em>, <em>my_cam.launch</em> từ gói my_cam, <em>hand_sign.launch</em> từ gói <em>ros_hand\ gesturerecognition</em> và <em>drive_robot.launch</em> từ gói <em>ros_mobile_robot.</em> Để tiết kiệm thời gian, bạn có thể nhóm 3 tệp launch đầu tiên vào một để chạy một thể bằng cách sử dụng thẻ <code>include</code>. Hãy gọi tệp này là <a target="_blank" href="https://github.com/TrinhNC/ros_hand_gesture_recognition/blob/main/launch/sign_control.launch">sign_control.launch</a> và đặt nó vào thư mục launch của package <em>ros_hand_gesture_recognition .</em> Sau đây là nội dung của nó.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">launch</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">include</span> <span class="hljs-attr">file</span>=<span class="hljs-string">"$(find my_cam)/launch/my_cam.launch"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">include</span> <span class="hljs-attr">file</span>=<span class="hljs-string">"$(find ros_hand_gesture_recognition)/launch/hand_sign.launch"</span> /&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">arg</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"publish_gesture_topic"</span> <span class="hljs-attr">default</span>=<span class="hljs-string">"/gesture/hand_sign"</span>/&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">arg</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"control_topic"</span> <span class="hljs-attr">default</span>=<span class="hljs-string">"/robot_diff_drive_controller/cmd_vel"</span>/&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">node</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"sign_to_controller"</span> <span class="hljs-attr">pkg</span>=<span class="hljs-string">"ros_hand_gesture_recognition"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"sign_to_controller.py"</span> <span class="hljs-attr">output</span>=<span class="hljs-string">"screen"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">param</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"publish_gesture_topic"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"string"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"$(arg publish_gesture_topic)"</span> /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">param</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"control_topic"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"string"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"$(arg control_topic)"</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">node</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">launch</span>&gt;</span>
</code></pre>
<p>Mở 2 terminal, trong cái đầu tiên chạy:</p>
<pre><code class="lang-bash">roslaunch ros_hand_gesture_recognition sign_control.launch
</code></pre>
<p>Trong cái thứ hai, chạy:</p>
<pre><code class="lang-bash">roslaunch ros_mobile_robot drive_robot.launch
</code></pre>
<p>Sau đó, bạn có thể điều khiển robot bằng tay của mình. Để cho thú vị hơn, bạn có thể thêm một số chướng ngại vật như là khối hộp, trụ và cầu trong Gazebo như tôi đã làm ở đây:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1678655235688/4c785092-98fa-436b-bc46-451c59ead609.jpeg" alt="add-box-gazebo" class="image--center mx-auto" /></p>
<p>Video này là một ví dụ:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=ZRMiATj_QH4&amp;ab_channel=robodev">https://www.youtube.com/watch?v=ZRMiATj_QH4&amp;ab_channel=robodev</a></div>
<p> </p>
<h1 id="heading-tiep-theo">Tiếp theo?</h1>
<p>Thực sự tuyệt vời khi bạn đã hoàn thành loạt bài này. Bây giờ bạn đã biết gần hết những khái niệm cơ bản về ROS và thậm chí đã tạo ứng dụng ROS đầu tiên của mình. Từ thời điểm này, tùy theo mục đích của mình, bạn có thể bắt đầu khám phá các khía cạnh khác và vô vàn các package tuyệt vời trong ROS. Mình sẽ đặt một số link bên dưới để tham khảo nhưng chắc chắn vẫn còn nhiều cái khác mà mình không biết tới. Hãy để lại bình luận nếu bạn tìm thấy bất kỳ điều thú v gì. Sẽ có thêm nhiều post về chế tạo robot thật, ROS2, AI, v.v. tại Robodev, vì vậy bạn hãy nhớ đăng ký để được cập nhật. Cuối cùng, mình chúc bạn tiếp tục có nhiều niềm vui trong việc tìm tòi và chế tạo robot!</p>
<p><strong>Nguồn tham khảo:</strong></p>
<ul>
<li><p>Awesome robotic repositories on GitHub:</p>
<ul>
<li><p><a target="_blank" href="https://github.com/shannon112/awesome-ros-mobile-robot">https://github.com/shannon112/awesome-ros-mobile-robot</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/jslee02/awesome-robotics-libraries">https://github.com/jslee02/awesome-robotics-libraries</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/fkromer/awesome-ros2">https://github.com/fkromer/awesome-ros2</a></p>
</li>
</ul>
</li>
<li><p>DIY Making real mobile robots:</p>
<ul>
<li><p><a target="_blank" href="https://articulatedrobotics.xyz/">https://articulatedrobotics.xyz/</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/ros-mobile-robots/diffbot">https://github.com/ros-mobile-robots/diffbot</a></p>
</li>
</ul>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[12. Robot Control Using Hand Gestures]]></title><description><![CDATA[In this final chapter of the series ROS 101, we are going to combine everything we have learned so far to finish one last challenge: moving the robot with our hand signs.
The idea
The idea is pretty simple and there is actually nothing new. In the pr...]]></description><link>https://robodev.blog/robot-control-using-hand-gestures</link><guid isPermaLink="true">https://robodev.blog/robot-control-using-hand-gestures</guid><category><![CDATA[ROS]]></category><category><![CDATA[robot-operating-system]]></category><category><![CDATA[mediapipe]]></category><category><![CDATA[#hand-gesture-recognition]]></category><category><![CDATA[robotics]]></category><dc:creator><![CDATA[Trinh Nguyen]]></dc:creator><pubDate>Sun, 12 Mar 2023 21:38:22 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1678656964639/206da364-b101-4ab4-9717-72345a2e0bc0.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this final chapter of the series <a target="_blank" href="https://robodev.blog/series/ros101">ROS 101</a>, we are going to combine everything we have learned so far to finish one last challenge: moving the robot with our hand signs.</p>
<h1 id="heading-the-idea">The idea</h1>
<p>The idea is pretty simple and there is actually nothing new. In the <a target="_blank" href="https://robodev.blog/simulate-a-mobile-robot-in-ros-part-2">previous chapter</a>, we used <strong>rqt_robot_steering</strong>, a simple GUI that ROS provides to drive mobile robots. It basically lets you change the linear velocity (the vertical slider) to move forward/backward and the angular velocity (the horizontal slider) to turn left/right. These velocity values are then packed into a <a target="_blank" href="http://docs.ros.org/en/noetic/api/geometry_msgs/html/msg/Twist.html">Twist message</a> and published to the topic in the box, here is <strong>/robot_diff_drive_controller/cmd_vel</strong>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1678057679052/01f4b5f5-85b5-42a0-ac75-0611f47bae78.png" alt="rqt_robot_steering" class="image--center mx-auto" /></p>
<p>Now instead of using this GUI, we can convert the hand signs detection (from <a target="_blank" href="https://robodev.blog/hand-gesture-recognition-in-ros">chapter 9</a>) to have the same functionalities. The index finger pointing up/down is for moving forward/backward, the thumb is for turning left/right, and all fingers stretched means stop. All we need is to write a script to convert these signs to velocity commands and publish them as messages to the topic <code>/robot_diff_drive_controller/cmd_vel</code><em>.</em></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1678573851195/60cca149-65d3-41c6-bc77-0219722d1a5d.png" alt="hand-sign-robot-steering" class="image--center mx-auto" /></p>
<p>If you want to change, add, or remove signs, please follow <a target="_blank" href="https://robodev.blog/hand-gesture-recognition-in-ros#heading-train-hand-sign-recognition-optional">this section</a> in chapter 9.</p>
<h1 id="heading-the-workflow">The workflow</h1>
<p>The graph below describes the workflow of the complete system (ellipses are nodes and rectangles are topics). Green parts are what we already implemented: a node <code>/my_camera</code> reads images from a webcam and publishes them to the topic <code>/image_raw</code> (from chapters <a target="_blank" href="https://robodev.blog/write-a-ros-publisher">7</a> and <a target="_blank" href="https://robodev.blog/write-a-ros-subscriber">8</a>); another Node <code>/hand_sign_recognition</code> subscribes to <code>/image_raw</code>, processes the images, and publishes results to the topic <code>/gesture/hand_sign</code> (from <a target="_blank" href="https://robodev.blog/hand-gesture-recognition-in-ros">chapter 9</a>). We need to implement the blue part which is basically a node, let's call it <code>/hand_sign_control</code>, that subscribes the <code>/gesture/hand_sign</code> topic, converts the hand signs to control values, and publishes them to the topic <code>/robot_diff_drive_controller/cmd_vel</code>. From this topic, the <code>/gazebo</code> node (from chapters <a target="_blank" href="https://robodev.blog/simulate-a-mobile-robot-in-ros-part-1">10</a> and <a target="_blank" href="https://robodev.blog/simulate-a-mobile-robot-in-ros-part-2">11</a>) will read the velocity values and drive the robot accordingly.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1678573736267/631ed49f-0467-436e-85a3-dcb2aaf8cf63.png" alt="hand-sign-mobile-robot-control-workflow" class="image--center mx-auto" /></p>
<h1 id="heading-the-implementation">The implementation</h1>
<p>After getting the idea and the workflow, I believe the implementation should be quite straightforward. There are different ways to make it work and I will show you one of them. Let's create a new script named <strong>sign_to_controller.py</strong> in the folder <em>ros_hand_gesture_recognition/src.</em> This script will basically handle the activities in the blue part above. The source code is located <a target="_blank" href="https://github.com/TrinhNC/ros_hand_gesture_recognition/blob/main/src/sign_to_controller.py">here</a> and I give below an explanation for the main parts of the code.</p>
<p>First, the class GestureController is created. In its <code>__init__</code>, the node <code>hand_sign_control</code>, the <code>gesture_subscriber</code> and the <code>vel_publisher</code> are initialized. The <code>gesture_subscriber</code> executes the function <code>callback</code> whenever it receives a String message from the topic <code>/gesture/hand_sign</code> (this topic name is defined in the <a target="_blank" href="https://github.com/TrinhNC/ros_hand_gesture_recognition/blob/main/launch/sign_control.launch">launch file</a> and is read using the function <code>rospy.get_param</code>). The <code>vel_publisher</code> will publish a <code>Twist</code> message to the topic <code>/robot_diff_drive_controller/cmd_vel</code>.</p>
<pre><code class="lang-python"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">GestureController</span>:</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self</span>):</span>
        rospy.init_node(<span class="hljs-string">'hand_sign_control'</span>, anonymous=<span class="hljs-literal">True</span>)
        <span class="hljs-comment"># Subscriber for subscribing the hand signs</span>
        self.gesture_subcriber = rospy.Subscriber(rospy.get_param(<span class="hljs-string">"hand_sign_recognition/publish_gesture_topic"</span>), String, self.callback)
        <span class="hljs-comment"># Publisher for publishing velocities </span>
        self.vel_publisher = rospy.Publisher(<span class="hljs-string">"/robot_diff_drive_controller/cmd_vel"</span>, Twist, queue_size=<span class="hljs-number">10</span>)
        <span class="hljs-comment"># Velocity message</span>
        self.vel_msg = Twist()
        <span class="hljs-comment"># Velocity in/decrements</span>
        self.linear_vel = <span class="hljs-number">0.01</span> <span class="hljs-comment">#[m/s]</span>
        self.angular_vel = <span class="hljs-number">0.1</span> <span class="hljs-comment">#[rad/s]</span>
</code></pre>
<p><a target="_blank" href="http://docs.ros.org/en/noetic/api/geometry_msgs/html/msg/Twist.html">Twist</a>, one type of ROS <a target="_blank" href="http://docs.ros.org/en/noetic/api/geometry_msgs/html/index-msg.html">geometries messages</a>, expresses velocity in free space broken into its <em>linear</em> and <em>angular</em> parts. Each part is one vector with 3 components: x, y, and z of type float. In our case, the robot is controlled by two values: the linear velocity along the X-axis for translation and the angular velocity along the Z-axis for rotation, so only the x element in the <em>linear</em> and the z element in the <em>angular</em> are the control signals. They are derived in the callback function.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1678614693478/11d894cb-6ae7-48a1-89df-66ae993cc072.png" alt="ros-twist-mobile-robot" class="image--center mx-auto" /></p>
<p>The callback function is where the conversion and publishing happen. It first checks what the input gesture is. If the gesture is <code>Forward</code> or <code>Backward</code>, the linear velocity represented by the x component of <code>vel_msg.linear</code> is incremented or decremented respectively by <code>linear_vel</code> which is set to 0.01 m/s in the <code>__init__</code>. If the gesture is <code>Turn Right</code> or <code>Turn Left</code>, the angular velocity represented by the z component of <code>vel_msg.angular</code> is incremented or decremented respectively by <code>angular_vel</code> which is set to 0.1 rad/s in the <code>__init__</code>. In Forward/Backward cases, this angular velocity is set to 0 to make the robot move without rotating at the same time. If the gesture is either <code>Stop</code> or <code>NONE</code> (<code>NONE</code> means no sign detected), the linear and angular speeds are set to zero making the robot stop. Finally, the Twist message <code>vel_msg</code> is published by <code>vel_publisher</code>.</p>
<pre><code class="lang-python">    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">callback</span>(<span class="hljs-params">self, gesture</span>):</span>
        <span class="hljs-keyword">if</span> gesture.data ==  <span class="hljs-string">"Forward"</span>:
            self.vel_msg.linear.x += self.linear_vel
            self.vel_msg.angular.z = <span class="hljs-number">0.</span>
        <span class="hljs-keyword">elif</span> gesture.data ==  <span class="hljs-string">"Backward"</span>:
            self.vel_msg.linear.x -= self.linear_vel
            self.vel_msg.angular.z = <span class="hljs-number">0.</span>
        <span class="hljs-keyword">elif</span> gesture.data == <span class="hljs-string">"Turn Right"</span>:
            self.vel_msg.angular.z -= self.angular_vel
        <span class="hljs-keyword">elif</span> gesture.data == <span class="hljs-string">"Turn Left"</span>:
            self.vel_msg.angular.z += self.angular_vel
        <span class="hljs-keyword">elif</span> gesture.data == <span class="hljs-string">"Stop"</span> <span class="hljs-keyword">or</span> gesture.data == <span class="hljs-string">"NONE"</span> :
            self.vel_msg.linear.x = <span class="hljs-number">0</span>
            self.vel_msg.angular.z = <span class="hljs-number">0</span>
        self.vel_publisher.publish(self.vel_msg)
</code></pre>
<h1 id="heading-lets-launch-them-all">Let's launch them all!</h1>
<p>There are 4 files required to be launched/executed: this <em>sign_to_controller.py</em> script, <em>my_cam.launch</em> from package my_cam, <em>hand_sign.launch</em> from package <em>ros_hand_gesture_recognition</em> and <em>drive_robot.launch</em> from package <em>ros_mobile_robot.</em> In order to save time, you can group the first 3 in one launch file using the <code>include</code> tag. Let's call this file <a target="_blank" href="https://github.com/TrinhNC/ros_hand_gesture_recognition/blob/main/launch/sign_control.launch">sign_control.launch</a> and put it in the launch folder of <em>ros_hand_gesture_recognition.</em> Following is its content.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">launch</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">include</span> <span class="hljs-attr">file</span>=<span class="hljs-string">"$(find my_cam)/launch/my_cam.launch"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">include</span> <span class="hljs-attr">file</span>=<span class="hljs-string">"$(find ros_hand_gesture_recognition)/launch/hand_sign.launch"</span> /&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">arg</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"publish_gesture_topic"</span> <span class="hljs-attr">default</span>=<span class="hljs-string">"/gesture/hand_sign"</span>/&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">arg</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"control_topic"</span> <span class="hljs-attr">default</span>=<span class="hljs-string">"/robot_diff_drive_controller/cmd_vel"</span>/&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">node</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"sign_to_controller"</span> <span class="hljs-attr">pkg</span>=<span class="hljs-string">"ros_hand_gesture_recognition"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"sign_to_controller.py"</span> <span class="hljs-attr">output</span>=<span class="hljs-string">"screen"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">param</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"publish_gesture_topic"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"string"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"$(arg publish_gesture_topic)"</span> /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">param</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"control_topic"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"string"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"$(arg control_topic)"</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">node</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">launch</span>&gt;</span>
</code></pre>
<p>Now open 2 terminals. In the first one run:</p>
<pre><code class="lang-bash">roslaunch ros_hand_gesture_recognition sign_control.launch
</code></pre>
<p>In the second:</p>
<pre><code class="lang-bash">roslaunch ros_mobile_robot drive_robot.launch
</code></pre>
<p>After that, you can drive the robot using your hand. To make it more fun, you can add some obstacles with some boxes, cylinders and spheres in Gazebo as I did here:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1678655235688/4c785092-98fa-436b-bc46-451c59ead609.jpeg" alt="add-box-gazebo" class="image--center mx-auto" /></p>
<p>This video shows you how it works:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=ZRMiATj_QH4&amp;ab_channel=robodev">https://www.youtube.com/watch?v=ZRMiATj_QH4&amp;ab_channel=robodev</a></div>
<p> </p>
<p>I hope you enjoy it!</p>
<h1 id="heading-now-what">Now what?</h1>
<p>You have done a really great job finishing this series. Now you've got to know all the basics of ROS and even created your very first ROS application. From this point on you can, depending on your interests, start to explore other aspects and many awesome packages in ROS. I will put some links below for reference but there are many more out there that I am not aware of. Feel free to leave me a comment if you find any interesting ones. I plan to make more blog posts about making real robots, ROS2, AI, etc. here in Robodev, so remember to subscribe to get updated. Finally, I wish you keep having fun making robots and happy learning!</p>
<p><strong>Resources</strong>:</p>
<ul>
<li><p>Awesome robotic repositories on GitHub:</p>
<ul>
<li><p><a target="_blank" href="https://github.com/shannon112/awesome-ros-mobile-robot">https://github.com/shannon112/awesome-ros-mobile-robot</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/jslee02/awesome-robotics-libraries">https://github.com/jslee02/awesome-robotics-libraries</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/fkromer/awesome-ros2">https://github.com/fkromer/awesome-ros2</a></p>
</li>
</ul>
</li>
<li><p>DIY Making real mobile robots:</p>
<ul>
<li><p><a target="_blank" href="https://articulatedrobotics.xyz/">https://articulatedrobotics.xyz/</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/ros-mobile-robots/diffbot">https://github.com/ros-mobile-robots/diffbot</a></p>
</li>
</ul>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[11. Mô Phỏng Robot trong ROS - Phần 2]]></title><description><![CDATA[Hãy cùng tiếp tục chương trước bằng việc dùng Xacro để cải tiến mô tả robot (URDF) và thêm nhiều thuộc tính hơn để mô phỏng và điều khiển robot trong Gazebo. Code ở phần này nhìn có vẻ dài dòng (vì XML luôn yêu cầu các thẻ mở và đóng) nhưng thực ra r...]]></description><link>https://robodev.blog/mo-phong-robot-trong-ros-phan-2</link><guid isPermaLink="true">https://robodev.blog/mo-phong-robot-trong-ros-phan-2</guid><category><![CDATA[ROS]]></category><category><![CDATA[vietnamese]]></category><category><![CDATA[robotics]]></category><category><![CDATA[robot-operating-system]]></category><category><![CDATA[Tutorial]]></category><dc:creator><![CDATA[Trinh Nguyen]]></dc:creator><pubDate>Fri, 24 Feb 2023 23:11:08 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1677280184318/9dc52d18-0d6c-427c-ae54-0ac8b9799cf1.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hãy cùng tiếp tục <a target="_blank" href="https://robodev.blog/mo-phong-robot-trong-ros-phan-1">chương trước</a> bằng việc dùng Xacro để cải tiến mô tả robot (URDF) và thêm nhiều thuộc tính hơn để mô phỏng và điều khiển robot trong Gazebo. Code ở phần này nhìn có vẻ dài dòng (vì XML luôn yêu cầu các thẻ mở và đóng) nhưng thực ra rất dễ hiểu.</p>
<h1 id="heading-xacro-la-gi">Xacro là gì?</h1>
<p>Trong <a target="_blank" href="https://robodev.blog/mo-phong-robot-trong-ros-phan-1">phần trước</a>, chúng ta đã tạo một URDF để mô tả tất cả các liên kết và khớp nối của một robot di động. Tuy nhiên, bạn có thể nhận thấy một số điểm dư thừa trong URDF này, ví dụ: 4 bánh xe có các thành phần gần như giống hệt nhau (ngoại trừ tên và vị trí). Đối với một robot phức tạp hơn, sẽ có nhiều thành phần lặp đi lặp lại hơn nữa và sẽ khiến URDF trở nên cồng kềnh và khó bảo trì. Đó là lý do tại sao Xacro ra đời. Xacro là viết tắt của <strong>X</strong>ML M<strong>acro</strong> cho phép bạn sử dụng macro trong URDF để tăng tính mô đun và giảm những đoạn mã thừa. Lưu ý rằng Xacro không phải là một giải pháp thay thế cho URDF mà chỉ là một cách viết khác vì cuối cùng, Xacro sẽ được (tự động) chuyển đổi thành URDF trong khi được sử dụng.</p>
<p>Để hiểu cú pháp Xacro, hãy tạo một tệp mô tả mới trong thư mục <em>urdf</em> và đặt tên là <em>mobile_robot.urdf.xacro</em>. Toàn bộ tập tin nằm <a target="_blank" href="https://github.com/TrinhNC/ros_mobile_robot/blob/main/urdf/mobile_robot.urdf.xacro">ở đây</a> và sau đây là giải thích cho các phần chính. Hai dòng đầu tiên tương tự như <em>mobile_robot.urdf</em>, ngoại trừ trong thẻ <code>robot</code>, có dòng <code>xmlns:xacro="</code><a target="_blank" href="http://www.ros.org/wiki/xacro"><code>http://www.ros.org/wiki/xacro</code></a><code>"</code> dùng để định nghĩa không gian tên (<a target="_blank" href="https://www.w3.org/TR/REC-xml-names/">namespace</a>) XML cho tệp để có thể đọc được các thẻ (tag) đúng cách.</p>
<pre><code class="lang-xml"><span class="hljs-meta">&lt;?xml version="1.0"?&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">robot</span> <span class="hljs-attr">xmlns:xacro</span>=<span class="hljs-string">"http://www.ros.org/wiki/xacro"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"mobile_robot"</span>&gt;</span>
</code></pre>
<p>Có ba đặc tính chúng ta có thể sử dụng với Xacro: property, math, and macro.</p>
<h2 id="heading-xacro-property">Xacro Property</h2>
<p>Trong xacro, bạn có thể định nghĩa một hằng số làm một property (thuộc tính) và dùng nó khi nào cần. Ví dụ: trong đoạn mã bên dưới, ba thuộc tính có tên: chiều cao, chiều rộng và chiều sâu (height, width, depth) được tạo với các giá trị lần lượt là 0.4, 0.4 và 0.1 (mét). Sau đó, chúng được sử dụng trong cả thẻ <code>visual</code> và thẻ <code>collision</code> trong liên kết (link) <code>chassis</code>. Để sử dụng property chỉ cần đặt nó vào trong dấu đô la và dấu ngoặc nhọn, ví dụ: <code>&lt;box size="${height} ${width} ${depth}"/&gt;</code>. <code>visual</code> và <code>collision</code> là gì sẽ được giải thích ở bên dưới.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">xacro:property</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"height"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"0.4"</span> /&gt;</span> <span class="hljs-comment">&lt;!-- [m] --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">xacro:property</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"width"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"0.4"</span> /&gt;</span> <span class="hljs-comment">&lt;!-- [m] --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">xacro:property</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"depth"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"0.1"</span> /&gt;</span> <span class="hljs-comment">&lt;!-- [m] --&gt;</span>

<span class="hljs-comment">&lt;!--Chassis--&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"chassis"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">visual</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">geometry</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">box</span> <span class="hljs-attr">size</span>=<span class="hljs-string">"${height} ${width} ${depth}"</span>/&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">geometry</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">visual</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">collision</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">geometry</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">box</span> <span class="hljs-attr">size</span>=<span class="hljs-string">"${height} ${width} ${depth}"</span>/&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">geometry</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">collision</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">inertial</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">mass</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"1.0"</span>/&gt;</span> <span class="hljs-comment">&lt;!-- [kg] --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">inertia</span> <span class="hljs-attr">ixx</span>=<span class="hljs-string">"0.014167"</span> <span class="hljs-attr">ixy</span>=<span class="hljs-string">"0.0"</span> <span class="hljs-attr">ixz</span>=<span class="hljs-string">"0.0"</span> <span class="hljs-attr">iyy</span>=<span class="hljs-string">"0.026667"</span> <span class="hljs-attr">iyz</span>=<span class="hljs-string">"0.0"</span> <span class="hljs-attr">izz</span>=<span class="hljs-string">"0.014167"</span>/&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">inertial</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">link</span>&gt;</span>
</code></pre>
<h2 id="heading-xacro-macro-va-math">Xacro Macro và Math</h2>
<p>Bây giờ đến các bánh xe. Chúng ta đã tạo 4 mô tả riêng cho 4 bánh xe trong URDF từ chương trước, nhưng bây giờ chỉ cần dùng 1 <em>macro</em> để mô tả một bánh xe chung và sau đó sử dụng các tham số để định nghĩa 4 bánh xe sau trong 4 dòng. Bạn có thể coi việc viết macro giống như tạo một hàm với các đối số.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">xacro:property</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"wheel_height"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"0.04"</span> /&gt;</span> <span class="hljs-comment">&lt;!-- [m] --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">xacro:property</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"wheel_radius"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"0.06"</span> /&gt;</span> <span class="hljs-comment">&lt;!-- [m] --&gt;</span>

<span class="hljs-comment">&lt;!-- Wheel macro--&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">xacro:macro</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"wheel"</span> <span class="hljs-attr">params</span>=<span class="hljs-string">"name reflect_x reflect_y reflect_r reflect_axis"</span>&gt;</span>

  <span class="hljs-comment">&lt;!-- Wheel link --&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"${name}_wheel"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">visual</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">geometry</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">cylinder</span> <span class="hljs-attr">length</span>=<span class="hljs-string">"${wheel_height}"</span> <span class="hljs-attr">radius</span>=<span class="hljs-string">"${wheel_radius}"</span>/&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">geometry</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">material</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"blue"</span>/&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">visual</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">collision</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">geometry</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">cylinder</span> <span class="hljs-attr">length</span>=<span class="hljs-string">"${wheel_height}"</span> <span class="hljs-attr">radius</span>=<span class="hljs-string">"${wheel_radius}"</span>/&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">geometry</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">collision</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">inertial</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">mass</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"0.3"</span>/&gt;</span>  <span class="hljs-comment">&lt;!-- [kg] --&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">inertia</span> <span class="hljs-attr">ixx</span>=<span class="hljs-string">"0.00031"</span> <span class="hljs-attr">ixy</span>=<span class="hljs-string">"0.0"</span> <span class="hljs-attr">ixz</span>=<span class="hljs-string">"0.0"</span> <span class="hljs-attr">iyy</span>=<span class="hljs-string">"0.00031"</span> <span class="hljs-attr">iyz</span>=<span class="hljs-string">"0.0"</span> <span class="hljs-attr">izz</span>=<span class="hljs-string">"0.00054"</span>/&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">inertial</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">link</span>&gt;</span>

  <span class="hljs-comment">&lt;!-- Wheel joint --&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">joint</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"${name}_wheel_joint"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"continuous"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">parent</span> <span class="hljs-attr">link</span>=<span class="hljs-string">"chassis"</span>/&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">child</span> <span class="hljs-attr">link</span>=<span class="hljs-string">"${name}_wheel"</span>/&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">origin</span> <span class="hljs-attr">xyz</span>=<span class="hljs-string">"${reflect_x*height/2} ${reflect_y*(width/2+0.025)} 0"</span> <span class="hljs-attr">rpy</span>=<span class="hljs-string">"${reflect_r*1.5707} 0 0"</span>/&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">axis</span> <span class="hljs-attr">xyz</span>=<span class="hljs-string">"0 0 ${reflect_axis}"</span>/&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">joint</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">xacro:macro</span>&gt;</span>
</code></pre>
<p>Trong đoạn code trên, <em>macro</em> bánh xe (wheel) được định nghĩa với với 5 đối số: <strong>name, reflect_x, reflect_y, reflect_r,</strong> và <strong>reflect_axis</strong>. <em>name</em> được sử dụng cho tên của liên kết và khớp. <strong><em>reflect_x, reflect_y, reflect_r,</em></strong> và <strong><em>reflect_axis</em></strong> được dùng cho vị trí của các khớp. Trong phần định nghĩa khớp nối (joint), bạn có thể thấy các phép toán cơ bản (cộng, trừ, nhân, chia) cũng có thể được sử dụng trong xacro, ví dụ: <code>&lt;origin xyz="${reflect_x*height/2} ${reflect_y*(width/2+0.025)} 0" rpy="${reflect_r*1.5707} 0 0"/&gt;</code>.</p>
<p>Khi đã có macro bánh xe này, việc khởi tạo các bánh xe rất đơn giản. Bên dưới là định nghĩa (trong 1 dòng duy nhất) của bánh xe đằng trước bên phải. Bạn có thể kiểm tra xem mô tả này có khớp với URDF cũ hay không bằng cách thay thế các đối số tương ứng trong <em>xacro</em> ở trên bằng các giá trị ở bên dưới. Và hãy tự thử thêm ba bánh còn lại nhé!</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">xacro:wheel</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"front_right"</span> <span class="hljs-attr">reflect_x</span>=<span class="hljs-string">"1"</span> <span class="hljs-attr">reflect_y</span>=<span class="hljs-string">"-1"</span> <span class="hljs-attr">reflect_r</span>=<span class="hljs-string">"1"</span> <span class="hljs-attr">reflect_axis</span>=<span class="hljs-string">"-1"</span>/&gt;</span>
</code></pre>
<h1 id="heading-nhung-thuoc-tinh-va-cham-va-vat-ly">Những Thuộc Tính Va Chạm và Vật Lý</h1>
<p>Những gì chúng ta làm cho đến nay là để mô tả các đặc điểm trực quan của robot, nghĩa là việc nó trông như thế nào. Tuy nhiên, để cho phép phát hiện va chạm và mô phỏng robot trong Gazebo một cách thực tế hơn, chúng ta cần thêm các thuộc tính khác.</p>
<h2 id="heading-collision-va-cham">Collision (Va Chạm)</h2>
<p>Bạn có thể đã thấy các thẻ <code>collision</code> trong các liên kết <code>chassis</code> và <code>wheel</code>. Các thuộc tính của <code>collision</code> là (trích dẫn trực tiếp từ <a target="_blank" href="http://wiki.ros.org/urdf/Tutorials/Adding%20Physical%20and%20Collision%20Properties%20to%20a%20URDF%20Model">ROS wiki</a>):</p>
<ul>
<li><p><code>collision</code> là phần tử con trực tiếp của <code>link</code>, ở cùng cấp độ với thẻ <code>visual</code>.</p>
</li>
<li><p>Hình dạng (shape) của <code>collision</code> được định nghĩa bằng thẻ <code>geometry</code> giống như cách thực hiện trong thẻ <code>visual</code>.</p>
</li>
</ul>
<p>Ở đây, các <code>collision</code> có dạng hình học (<code>geometry</code>) giống hệt như các <code>link</code>. Điều này đúng trong nhiều trường hợp nhưng nếu robot của bạn có các hình dạng phức tạp hơn hoặc nếu bạn muốn thể tích của <code>collision</code> to hơn để đảm bảo an toàn thì chúng phải được điều chỉnh cho phù hợp.</p>
<h2 id="heading-cac-thuoc-tinh-vat-ly">Các Thuộc Tính Vật Lý</h2>
<p>Trong các nền tảng mô phỏng 3D như Gazebo sẽ luôn có các công cụ (gọi là physical engine) để mô phỏng các đặc tính vật lý trong thế giới thực như trọng lực, ma sát, độ cứng, v.v. Do đó, chúng ta cũng cần thêm một số thuộc tính vật lý quan trọng vào URDF của mình. Một trong số đó là quán tính. Mọi phần tử liên kết được mô phỏng đều cần một thẻ quán tính mà bạn có thể thấy trong các link khung (chassis) và bánh xe (wheel) ở trên. Nó là một thành phần con của thẻ <code>link</code> và cung cấp hai thông tin: <code>mass</code>, khối lượng (tính bằng kilôgam) và <code>inertia</code>, mômen quán tính. Dành cho những bạn chưa biết "Momen quán tính, ký hiệu là I, là một đại lượng vật lý đặc trưng cho mức quán tính của các vật thể trong chuyển động quay, tương tự như khối lượng trong chuyển động thẳng" (<a target="_blank" href="https://vi.wikipedia.org/wiki/M%C3%B4_men_qu%C3%A1n_t%C3%ADnh">nguồn</a>).</p>
<p><code>inertia</code> được xác định bởi một ma trận 3x3 nhưng ma trận đối xứng nên nó có thể được biểu diễn bằng 6 phần tử (in đậm).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1676816655152/def6d179-4727-4ddd-b7c0-d635a12e5304.png" alt="moment-of-inertia.jpg" class="image--center mx-auto" /></p>
<p>Đối với các hình dạng đơn giản (hình hộp, hình trụ, hình cầu) như trong robot di động của chúng ta, ma trận quán tính có thể được tính bằng cách sử dụng các công thức từ <a target="_blank" href="https://en.wikipedia.org/wiki/List_of_moments_of_inertia#List_of_3D_inertia_tensors">danh sách</a> này. Bảng bên dưới có chỉ ra công thức tính ma trận quán tính cho khung (hình hộp) và bánh xe (hình trụ). Bạn có thể thay các giá trị vào và tính xem có khớp với mã mô tả ở trên hay không. Đối với các mô hình phức tạp hơn, thông tin này có thể được trích xuất trực tiếp từ phần mềm 3D như <a target="_blank" href="https://www.meshlab.net/">MeshLab</a>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1676817704463/cf5baf67-c5d2-4902-b2ac-5805549fe629.jpeg" alt="inertia-matrix.jpg" class="image--center mx-auto" /></p>
<p>Có thể thêm nhiều đặc tính vật lý khác như ma sát, độ cứng và giảm chấn (chi tiết hơn <a target="_blank" href="http://wiki.ros.org/urdf/Tutorials/Adding%20Physical%20and%20Collision%20Properties%20to%20a%20URDF%20Model">tại đây</a>). Tuy nhiên, vì chúng ta muốn mô phỏng trong Gazebo nên có thể thêm các thuộc tính này bằng thẻ <code>gazebo</code>. Để cho đơn giản, ở đây mình chỉ dùng thẻ <code>material</code> (vật liệu) màu xanh lam cho bánh xe nhưng bạn có thể thêm nhiều thành phần khác (chi tiết <a target="_blank" href="https://classic.gazebosim.org/tutorials?tut=ros_urdf">tại đây</a>) để mô phỏng chân thực hơn.</p>
<pre><code class="lang-xml">  <span class="hljs-tag">&lt;<span class="hljs-name">gazebo</span> <span class="hljs-attr">reference</span>=<span class="hljs-string">"${name}_wheel"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">material</span>&gt;</span>Gazebo/Blue<span class="hljs-tag">&lt;/<span class="hljs-name">material</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">gazebo</span>&gt;</span>
</code></pre>
<h1 id="heading-khoi-dong-va-dieu-khien-robot">Khởi động và Điều Khiển Robot</h1>
<p>Để điều khiển mobile robot của chúng ta trong Gazebo, bạn cần thêm hai thứ nữa vào tệp Xacro. Đầu tiên, mọi khớp truyền động, tức là tất cả các khớp bánh xe trong trường hợp của chúng ta, cần thẻ <code>&lt;transmission&gt;</code> bên trong macro wheel như bên dưới.</p>
<pre><code class="lang-xml">  <span class="hljs-comment">&lt;!-- Adding transmission to wheels --&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">transmission</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"${name}_wheel_trans"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">type</span>&gt;</span>transmission_interface/SimpleTransmission<span class="hljs-tag">&lt;/<span class="hljs-name">type</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">actuator</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"${name}_wheel_motor"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">mechanicalReduction</span>&gt;</span>1<span class="hljs-tag">&lt;/<span class="hljs-name">mechanicalReduction</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">actuator</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">joint</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"${name}_wheel_joint"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">hardwareInterface</span>&gt;</span>hardware_interface/VelocityJointInterface<span class="hljs-tag">&lt;/<span class="hljs-name">hardwareInterface</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">joint</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">transmission</span>&gt;</span>
</code></pre>
<p>Thứ hai, plugin <a target="_blank" href="https://classic.gazebosim.org/tutorials?tut=ros_control"><em>gazebo_ros_control</em></a> được thêm vào để phân tích cú pháp các thẻ <code>tranmission</code> và tải các giao diện phần cứng và trình quản lý bộ điều khiển thích hợp trong gazebo.</p>
<h2 id="heading-launch-file">Launch File</h2>
<p>Xacro đã hoàn tất và để khởi chạy nó, hãy tạo một tệp launch khác có tên <strong>drive_robot.launch</strong>. Tệp đầy đủ nằm <a target="_blank" href="https://github.com/TrinhNC/ros_mobile_robot/blob/main/launch/drive_robot.launch">ở đây</a> và mình đã thêm comment vào mỗi dòng để giải thích ý nghĩa của chúng nhưng nếu bạn vẫn chưa rõ về bất kỳ điều gì, hãy nhắn mình bằng comment dưới bài viết này.</p>
<p>Một điều quan trọng cần lưu ý là <em>drive_robot.launch</em> này cần 2 tệp (có thể được tải về <a target="_blank" href="https://github.com/TrinhNC/ros_mobile_robot/tree/main/config">tại đây</a> và đặt trong thư mục <strong>config</strong>):</p>
<ul>
<li><p><a target="_blank" href="https://github.com/TrinhNC/ros_mobile_robot/blob/main/config/diffdrive.yaml">diffdrive.yaml</a> bao gồm các cấu hình của <a target="_blank" href="http://wiki.ros.org/diff_drive_controller"><em>diff_drive_controller</em></a> (một trong nhiều bộ điều khiển mà ROS cung cấp và nó được sử dụng đặc biệt để tính toán vận tốc góc trái và phải cho rô-bốt 2 hoặc 4 bánh (bạn có thể tìm thêm chi tiết <a target="_blank" href="http://wiki.ros.org/diff_drive_controller">tại đây</a>)).</p>
</li>
<li><p><a target="_blank" href="https://github.com/TrinhNC/ros_mobile_robot/blob/main/config/gazebo_ros_control_params.yaml">gazebo_ros_control_params.yaml</a> chứa các cài đặt cho control trong Gazebo.</p>
</li>
</ul>
<h2 id="heading-dieu-khien-robot-trong-gazebo">Điều Khiển Robot Trong Gazebo</h2>
<p>Mở 2 terminal, trong cái đầu tiên chạy tệp <em>drive_robot.launch</em>:</p>
<pre><code class="lang-bash">roslaunch ros_mobile_robot drive_robot.launch
</code></pre>
<p>Đợi vài giây cho Gazebo được tải xong.</p>
<p>Trong terminal thứ hai, chạy package <strong>rqt_robot_steering</strong> cung cấp GUI (giao diện) để điều khiển robot. Về cơ bản, nó cho phép bạn thay đổi vận tốc tuyến tính và tốc độ góc được publish tới topic <strong>/robot_diff_drive_controller/cmd_vel</strong> để điều khiển rô-bốt. Nếu tên topic không phải là <em>/robot_diff_drive_controller/cmd_vel</em>, hãy gõ lại cho đúng.</p>
<pre><code class="lang-bash">rosrun rqt_robot_steering rqt_robot_steering
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1676848819444/25e6492f-87c6-42d9-8211-38f8798942c2.jpeg" alt="rqt_robot_steering" class="image--center mx-auto" /></p>
<p>Thay đổi vận tốc tuyến tính và góc trong GUI để điều khiển robot:</p>
<p><img src="https://user-images.githubusercontent.com/19979949/219981336-483be8ba-f465-431e-b6b4-164ac40c5814.gif" alt="steer-robot-gazebo" class="image--center mx-auto" /></p>
<p>Nếu bạn đang sử dụng máy ảo thì có thể thấy rằng mô phỏng bị lag. Đó là bởi vì Gazebo rất nặng, nếu bạn chạy nó trên máy tính chạy Ubuntu gốc và có card đồ họa tốt thì nó sẽ chạy mượt hơn.</p>
<p>Có thú vị khi thấy robot di chuyển không? Mình cá là có. Bạn đã đi một chặng đường dài và đã làm rất tốt! Chương tiếp theo sẽ là chương cuối của loạt bài này, trong đó bạn sẽ kết hợp mọi thứ bạn đã học được cho đến nay để điều khiển robot bằng cử chỉ tay của mình. See you there!</p>
]]></content:encoded></item><item><title><![CDATA[11. Simulate a Mobile Robot in ROS - Part 2]]></title><description><![CDATA[We continue the previous chapter by improving the robot description with Xacro and adding more properties to be able to simulate the robot in Gazebo. The code seems lengthy (since XML always requires opening and closing tags) but it is very simple to...]]></description><link>https://robodev.blog/simulate-a-mobile-robot-in-ros-part-2</link><guid isPermaLink="true">https://robodev.blog/simulate-a-mobile-robot-in-ros-part-2</guid><category><![CDATA[ROS]]></category><category><![CDATA[robot-operating-system]]></category><category><![CDATA[Tutorial]]></category><dc:creator><![CDATA[Trinh Nguyen]]></dc:creator><pubDate>Mon, 20 Feb 2023 23:19:50 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1676849586069/de7827a9-96e7-48e8-bbde-6c6331e0ab04.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>We continue the <a target="_blank" href="https://robodev.blog/simulate-a-mobile-robot-in-ros-part-1">previous chapter</a> by improving the robot description with <a target="_blank" href="http://wiki.ros.org/xacro">Xacro</a> and adding more properties to be able to simulate the robot in Gazebo. The code seems lengthy (since XML always requires opening and closing tags) but it is very simple to understand.</p>
<h1 id="heading-xacro">Xacro</h1>
<p>In the <a target="_blank" href="https://robodev.blog/simulate-a-mobile-robot-in-ros-part-1">last chapter</a>, we created a URDF to describe all the links and joints of a mobile robot. However, you may notice several redundancies in this URDF, for example, the 4 wheels have almost identical components (except for the name, position, and orientation). For a more complex robot, there will be many more repeated components making the URDF both cumbersome and difficult to maintain. That's why Xacro was introduced. Xacro stands for <strong>X</strong>ML M<strong>acro</strong> which allows you to use macros in a URDF to increase modularity and reduce redundancy. Note that Xacro is not an alternative to URDF but just another way of writing it because in the end a Xacro will be converted to URDF (automatically) while being loaded.</p>
<p>To understand Xacro syntax, let's create a new robot description file in <em>urdf</em> folder and name it <em>mobile_robot.urdf.xacro.</em> The full file is located <a target="_blank" href="https://github.com/TrinhNC/ros_mobile_robot/blob/main/urdf/mobile_robot.urdf.xacro">here</a> and below is the explanation for the main parts. The first two lines are similar to the <a target="_blank" href="https://github.com/TrinhNC/ros_mobile_robot/blob/main/urdf/mobile_robot.urdf">mobile_robot.urdf</a>, except in the <code>robot</code> tag, there is the line <code>xmlns:xacro="http://www.ros.org/wiki/xacro"</code> which basically defines <a target="_blank" href="https://www.w3.org/TR/REC-xml-names/">XML namespace</a> for the file to parse properly.</p>
<pre><code class="lang-xml"><span class="hljs-meta">&lt;?xml version="1.0"?&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">robot</span> <span class="hljs-attr">xmlns:xacro</span>=<span class="hljs-string">"http://www.ros.org/wiki/xacro"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"mobile_robot"</span>&gt;</span>
</code></pre>
<p>There are three shortcuts we can use with Xacro: property, math, and macro.</p>
<h2 id="heading-xacro-property">Xacro Property</h2>
<p>In xacro, you can define a constant as a <em>property</em> and reuse it when needed. For example, in the snippet below three properties with names: <strong>height, width,</strong> and <strong>depth</strong> are defined with values 0.4, 0.4, and 0.1 (meter) respectively. They are then used in both <code>visual</code> and <code>collision</code> tags in the chassis link. The way you use a property is simply by putting it in dollar and curly bracket, for instance, <code>&lt;box size="${height} ${width} ${depth}"/&gt;</code>. The collision and inertial parts will be explained later in this post.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">xacro:property</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"height"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"0.4"</span> /&gt;</span> <span class="hljs-comment">&lt;!-- [m] --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">xacro:property</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"width"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"0.4"</span> /&gt;</span> <span class="hljs-comment">&lt;!-- [m] --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">xacro:property</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"depth"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"0.1"</span> /&gt;</span> <span class="hljs-comment">&lt;!-- [m] --&gt;</span>

<span class="hljs-comment">&lt;!--Chassis--&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"chassis"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">visual</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">geometry</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">box</span> <span class="hljs-attr">size</span>=<span class="hljs-string">"${height} ${width} ${depth}"</span>/&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">geometry</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">visual</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">collision</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">geometry</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">box</span> <span class="hljs-attr">size</span>=<span class="hljs-string">"${height} ${width} ${depth}"</span>/&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">geometry</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">collision</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">inertial</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">mass</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"1.0"</span>/&gt;</span> <span class="hljs-comment">&lt;!-- [kg] --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">inertia</span> <span class="hljs-attr">ixx</span>=<span class="hljs-string">"0.014167"</span> <span class="hljs-attr">ixy</span>=<span class="hljs-string">"0.0"</span> <span class="hljs-attr">ixz</span>=<span class="hljs-string">"0.0"</span> <span class="hljs-attr">iyy</span>=<span class="hljs-string">"0.026667"</span> <span class="hljs-attr">iyz</span>=<span class="hljs-string">"0.0"</span> <span class="hljs-attr">izz</span>=<span class="hljs-string">"0.014167"</span>/&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">inertial</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">link</span>&gt;</span>
</code></pre>
<h2 id="heading-xacro-macro-andamp-math">Xacro Macro &amp; Math</h2>
<p>Now comes the wheels. Remember we have 4 separate descriptions for 4 wheels in the URDF from the last chapter? Here we can simply use a macro to describe a general wheel only once and use the parameters to define each wheel later in one line. You can think of writing a macro as creating a function with arguments.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">xacro:property</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"wheel_height"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"0.04"</span> /&gt;</span> <span class="hljs-comment">&lt;!-- [m] --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">xacro:property</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"wheel_radius"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"0.06"</span> /&gt;</span> <span class="hljs-comment">&lt;!-- [m] --&gt;</span>

<span class="hljs-comment">&lt;!-- Wheel macro--&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">xacro:macro</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"wheel"</span> <span class="hljs-attr">params</span>=<span class="hljs-string">"name reflect_x reflect_y reflect_r reflect_axis"</span>&gt;</span>

  <span class="hljs-comment">&lt;!-- Wheel link --&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"${name}_wheel"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">visual</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">geometry</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">cylinder</span> <span class="hljs-attr">length</span>=<span class="hljs-string">"${wheel_height}"</span> <span class="hljs-attr">radius</span>=<span class="hljs-string">"${wheel_radius}"</span>/&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">geometry</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">material</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"blue"</span>/&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">visual</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">collision</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">geometry</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">cylinder</span> <span class="hljs-attr">length</span>=<span class="hljs-string">"${wheel_height}"</span> <span class="hljs-attr">radius</span>=<span class="hljs-string">"${wheel_radius}"</span>/&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">geometry</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">collision</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">inertial</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">mass</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"0.3"</span>/&gt;</span>  <span class="hljs-comment">&lt;!-- [kg] --&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">inertia</span> <span class="hljs-attr">ixx</span>=<span class="hljs-string">"0.00031"</span> <span class="hljs-attr">ixy</span>=<span class="hljs-string">"0.0"</span> <span class="hljs-attr">ixz</span>=<span class="hljs-string">"0.0"</span> <span class="hljs-attr">iyy</span>=<span class="hljs-string">"0.00031"</span> <span class="hljs-attr">iyz</span>=<span class="hljs-string">"0.0"</span> <span class="hljs-attr">izz</span>=<span class="hljs-string">"0.00054"</span>/&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">inertial</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">link</span>&gt;</span>

  <span class="hljs-comment">&lt;!-- Wheel joint --&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">joint</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"${name}_wheel_joint"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"continuous"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">parent</span> <span class="hljs-attr">link</span>=<span class="hljs-string">"chassis"</span>/&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">child</span> <span class="hljs-attr">link</span>=<span class="hljs-string">"${name}_wheel"</span>/&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">origin</span> <span class="hljs-attr">xyz</span>=<span class="hljs-string">"${reflect_x*height/2} ${reflect_y*(width/2+0.025)} 0"</span> <span class="hljs-attr">rpy</span>=<span class="hljs-string">"${reflect_r*1.5707} 0 0"</span>/&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">axis</span> <span class="hljs-attr">xyz</span>=<span class="hljs-string">"0 0 ${reflect_axis}"</span>/&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">joint</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">xacro:macro</span>&gt;</span>
</code></pre>
<p>In the code above, the wheel macro is defined with 5 arguments: <strong>name, reflect_x, reflect_y, reflect_r,</strong> and <strong>reflect_axis</strong>. <em>name</em> is used for the link and joint names. <em>reflect_x, reflect_y, reflect_r,</em> and <em>reflect_axis</em> are used for the position of the joints. In the joint definition, it is shown how basic math operations can also be used in a xacro, e.g. <code>&lt;origin xyz="${reflect_x*height/2} ${reflect_y*(width/2+0.025)} 0" rpy="${reflect_r*1.5707} 0 0"/&gt;</code>.</p>
<p>Using this wheel macro, for example, the definition of the front right wheel looks like below. You can check if this description matches <a target="_blank" href="https://github.com/TrinhNC/ros_mobile_robot/blob/main/urdf/mobile_robot.urdf">the old URDF</a> by replacing the corresponding arguments in the xacro above with the values. Also, you should try to make the three remaining wheels by yourself.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">xacro:wheel</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"front_right"</span> <span class="hljs-attr">reflect_x</span>=<span class="hljs-string">"1"</span> <span class="hljs-attr">reflect_y</span>=<span class="hljs-string">"-1"</span> <span class="hljs-attr">reflect_r</span>=<span class="hljs-string">"1"</span> <span class="hljs-attr">reflect_axis</span>=<span class="hljs-string">"-1"</span>/&gt;</span>
</code></pre>
<h1 id="heading-collision-and-physical-properties">Collision and Physical Properties</h1>
<p>All the works we have done so far are for describing the visual characteristics of the robot meaning how it looks like. However, to enable collision detection and to simulate the robot in Gazebo more realistically, we need to add collision and physical properties.</p>
<h2 id="heading-collision">Collision</h2>
<p>You may already see the collision tags in the chassis and wheel links. The properties of <code>collision</code> are (quoted directly from the <a target="_blank" href="http://wiki.ros.org/urdf/Tutorials/Adding%20Physical%20and%20Collision%20Properties%20to%20a%20URDF%20Model">ROS wiki)</a>:</p>
<ul>
<li><p>The collision element is a direct subelement of the link object, at the same level as the visual tag.</p>
</li>
<li><p>The collision element defines its shape the same way the visual element does, with a geometry tag. The format for the geometry tag is exactly the same here as with the visual.</p>
</li>
<li><p>You can also specify an origin in the same way as a subelement of the collision tag (as with the visual).</p>
</li>
</ul>
<p>You may already notice that the collisions have exactly the same geometry as the links. This is true in many cases but if your robot has more complicated meshes or if you want to enlarge the geometry to ensure safety, then the collisions must be adjusted accordingly.</p>
<h2 id="heading-physical-properties">Physical Properties</h2>
<p>In a simulated platform like Gazebo, there is a physics engine that emulates real-world physical properties such as gravity, friction, stiffness, etc. Therefore, we also need to add several important properties to our URDF. One of them is inertia. <a target="_blank" href="http://wiki.ros.org/urdf/Tutorials/Adding%20Physical%20and%20Collision%20Properties%20to%20a%20URDF%20Model">Every link element being simulated needs an inertial tag</a> which you already can see in the chassis and wheel links above. It is a subelement of a link tag and gives two types of information: <strong>mass</strong> (in kilogram) and moments of <strong>inertia</strong>. For those of you who do not know "<a target="_blank" href="https://en.wikipedia.org/wiki/Moment_of_inertia"><strong>Moment of inertia</strong></a>, denoted by <em>I</em>, measures the extent to which an object resists <a target="_blank" href="https://en.wikipedia.org/wiki/Rotational_acceleration">rotational acceleration</a> about a <a target="_blank" href="https://en.wikipedia.org/wiki/Rotation_around_a_fixed_axis">particular axis</a>, it is the rotational analogue to <a target="_blank" href="https://en.wikipedia.org/wiki/Mass">mass</a> (which determines an object's resistance to <a target="_blank" href="https://en.wikipedia.org/wiki/Linear_acceleration"><em>linear</em> acceleration</a>). The moments of inertia of a mass have units of dimension ML<sup>2</sup>([mass] × [length]<sup>2</sup>)" (<a target="_blank" href="https://en.wikipedia.org/wiki/List_of_moments_of_inertia#List_of_3D_inertia_tensors">source</a>).</p>
<p>The <code>inertia</code> element is specified by a 3x3 matrix but can be represented by 6 elements since the matrix is symmetrical.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1676816655152/def6d179-4727-4ddd-b7c0-d635a12e5304.png" alt="moment-of-inertia.jpg" class="image--center mx-auto" /></p>
<p>For simple shapes (box, cylinder, sphere) like in our mobile robot, the inertia matrix can be calculated using the formulas from this <a target="_blank" href="https://en.wikipedia.org/wiki/List_of_moments_of_inertia#List_of_3D_inertia_tensors">list of moment of inertia tensors</a>. In the table below, you can calculate the inertia matrix by replacing the parameters in the formulas with values in the description code above. For more complicated meshes, this information can be extracted directly from 3D software such as <a target="_blank" href="https://www.meshlab.net/">MeshLab</a>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1676817704463/cf5baf67-c5d2-4902-b2ac-5805549fe629.jpeg" alt="inertia-matrix.jpg" class="image--center mx-auto" /></p>
<p>There are more physical properties such as friction, stiffness, and damping that you can add (more detail <a target="_blank" href="http://wiki.ros.org/urdf/Tutorials/Adding%20Physical%20and%20Collision%20Properties%20to%20a%20URDF%20Model">here</a>). However, as we are only working with the simulation in Gazebo, we can use the <code>gazebo</code> tag to do it. For simplicity, I just use the material color Blue here, but you can add more properties (listed <a target="_blank" href="https://classic.gazebosim.org/tutorials?tut=ros_urdf">here</a>) to make the simulation even more realistic.</p>
<pre><code class="lang-xml">  <span class="hljs-tag">&lt;<span class="hljs-name">gazebo</span> <span class="hljs-attr">reference</span>=<span class="hljs-string">"${name}_wheel"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">material</span>&gt;</span>Gazebo/Blue<span class="hljs-tag">&lt;/<span class="hljs-name">material</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">gazebo</span>&gt;</span>
</code></pre>
<h1 id="heading-launch-and-drive-the-robot">Launch and Drive the Robot</h1>
<p>To drive our mobile robot in Gazebo, there are two more things you need to add to the Xacro file. First, every actuating joint, which is all the wheel joints in our case, requires a <code>&lt;transmission&gt;</code> block like below (since this is inside the wheel macro, the <em>name</em> argument is used).</p>
<pre><code class="lang-xml">  <span class="hljs-comment">&lt;!-- Adding transmission to wheels --&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">transmission</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"${name}_wheel_trans"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">type</span>&gt;</span>transmission_interface/SimpleTransmission<span class="hljs-tag">&lt;/<span class="hljs-name">type</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">actuator</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"${name}_wheel_motor"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">mechanicalReduction</span>&gt;</span>1<span class="hljs-tag">&lt;/<span class="hljs-name">mechanicalReduction</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">actuator</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">joint</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"${name}_wheel_joint"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">hardwareInterface</span>&gt;</span>hardware_interface/VelocityJointInterface<span class="hljs-tag">&lt;/<span class="hljs-name">hardwareInterface</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">joint</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">transmission</span>&gt;</span>
</code></pre>
<p>Second, the <a target="_blank" href="https://classic.gazebosim.org/tutorials?tut=ros_control"><em>gazebo_ros_control</em></a> plugin is added to parse the transmission tags and load the appropriate hardware interfaces and controller manager in Gazebo.</p>
<pre><code class="lang-xml"><span class="hljs-comment">&lt;!-- Link Gazebo and ROS --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">gazebo</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">plugin</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"gazebo_ros_control"</span> <span class="hljs-attr">filename</span>=<span class="hljs-string">"libgazebo_ros_control.so"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">robotNamespace</span>&gt;</span>/<span class="hljs-tag">&lt;/<span class="hljs-name">robotNamespace</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">plugin</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">gazebo</span>&gt;</span>
</code></pre>
<h2 id="heading-launch-file">Launch File</h2>
<p>The Xacro is now complete and in order to launch it, create another launch file called <strong>drive_robot.launch</strong>. The full file is located <a target="_blank" href="https://github.com/TrinhNC/ros_mobile_robot/blob/main/launch/drive_robot.launch">here</a> and I already added as many comments as possible to explain the information inside this file but if you are still unclear about anything, just leave me a message.</p>
<p>One important note is that this <em>drive_robot.launch</em> loads 2 files (can be downloaded from <a target="_blank" href="https://github.com/TrinhNC/ros_mobile_robot/tree/main/config">here</a> and put in a <strong>config</strong> folder):</p>
<ul>
<li><p><a target="_blank" href="https://github.com/TrinhNC/ros_mobile_robot/tree/main/config">diffdrive.yaml</a> which includes the configurations of the <a target="_blank" href="http://wiki.ros.org/diff_drive_controller">diff_drive_controller</a> (differential drive controller), one of many controllers ROS provides and it is specifically used to calculate left and right angular velocities for wheel robots (more details can be found <a target="_blank" href="http://wiki.ros.org/diff_drive_controller">here</a>).</p>
</li>
<li><p><a target="_blank" href="https://github.com/TrinhNC/ros_mobile_robot/blob/main/config/gazebo_ros_control_params.yaml"><strong>gazebo_ros_control_params.yaml</strong></a> includes the configurations of controllers in Gazebo.</p>
</li>
</ul>
<h2 id="heading-drive-the-robot-in-gazebo">Drive the Robot in Gazebo</h2>
<p>Open 2 terminals. In the first one, launch the file drive_robot.launch:</p>
<pre><code class="lang-bash">roslaunch ros_mobile_robot drive_robot.launch
</code></pre>
<p>Wait a few seconds for Gazebo to be fully loaded.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1676848808304/8d3f59cd-0631-4864-ac85-cefbf1cd848f.jpeg" alt="mobile-robot-in-gazebo" class="image--center mx-auto" /></p>
<p>In the second terminal, run the <strong>rqt_robot_steering</strong> package which provides a GUI to steer robots. It basically lets you change the linear and angular velocities which are published to the topic <strong>/robot_diff_drive_controller/cmd_vel</strong> to drive the robot. If the topic name in the box is not <em>/robot_diff_drive_controller/cmd_vel</em> the same, manually edit it.</p>
<pre><code class="lang-bash">rosrun rqt_robot_steering rqt_robot_steering
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1676848819444/25e6492f-87c6-42d9-8211-38f8798942c2.jpeg" alt="rqt_robot_steering" class="image--center mx-auto" /></p>
<p>Change the linear and angular velocities in the GUI to steer the robot:</p>
<p><img src="https://user-images.githubusercontent.com/19979949/219981336-483be8ba-f465-431e-b6b4-164ac40c5814.gif" alt="steer-robot-gazebo" class="image--center mx-auto" /></p>
<p>If you are using a virtual machine, you may notice that the simulation is not so responsive. It is because the simulation needs a lot of computation power. If you run this on a native Linux computer with a good graphics card, it should work better.</p>
<p>Is it exciting to see your robot moves? I bet it is. You have come a long way, very well done! The next chapter will be the last post of this series in which you will combine everything you have learned so far to control the robot with your hand gestures. See you there!</p>
]]></content:encoded></item><item><title><![CDATA[10. Mô Phỏng Robot trong ROS - Phần 1]]></title><description><![CDATA[Trong chương này và chương tiếp theo, bạn sẽ học cách mô phỏng một rô-bốt và hiển thị trạng thái của nó bằng hai chương trình đi kèm với ROS là Gazebo và rviz. Gazebo là một chương trình mô phỏng 3D, nghĩa là nó cho phép bạn giả lập rô-bốt và môi trư...]]></description><link>https://robodev.blog/mo-phong-robot-trong-ros-phan-1</link><guid isPermaLink="true">https://robodev.blog/mo-phong-robot-trong-ros-phan-1</guid><category><![CDATA[ROS]]></category><category><![CDATA[robot-operating-system]]></category><category><![CDATA[vietnamese]]></category><category><![CDATA[Tutorial]]></category><category><![CDATA[robotics]]></category><dc:creator><![CDATA[Trinh Nguyen]]></dc:creator><pubDate>Mon, 20 Feb 2023 23:08:12 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1677275506495/c086fc63-c598-45cc-a4e0-04f5c880e61a.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Trong chương này và chương tiếp theo, bạn sẽ học cách mô phỏng một rô-bốt và hiển thị trạng thái của nó bằng hai chương trình đi kèm với ROS là Gazebo và rviz. <strong>Gazebo là một chương trình mô phỏng 3D</strong>, nghĩa là nó cho phép bạn giả lập rô-bốt và môi trường xung quanh với các đặc tính vật lý như trọng lực, tốc độ, gia tốc, v.v., tương tự như trong thế giới thực. Khi bạn không có rô bốt thật hoặc bạn muốn kiểm tra rô bốt của mình trong môi trường ảo trước khi thử ngoài đời thì bạn nên sử dụng Gazebo. <strong>Rviz (viết tắt của ROS Visualization) là phần mềm trực quan hóa 3D</strong> giúp theo dõi trạng thái của rô-bốt chẳng hạn như dữ liệu cảm biến (hình ảnh, pointcloud - đám mây điểm, quỹ đạo, v.v.) hoặc chuyển đổi giữa các tọa độ khác nhau. Rviz có thể được sử dụng cho cả rô-bốt thật và mô phỏng.</p>
<h1 id="heading-mo-ta-robot">Mô tả Robot</h1>
<p>Để mô phỏng rô bốt trong ROS, bạn cần mô tả nó trong trong một file URDF (Unified Robot Description Format). URDF về cơ bản là một tệp XML cho phép bạn mô tả các thuộc tính vật lý quan trọng của rô-bốt chẳng hạn như link (liên kết), joint (khớp nối), hình dạng, màu sắc, collision (va chạm), v.v. Trong hình ví dụ bên dưới, phía bên trái là hình ảnh của một Universal Robot - UR5 trong <em>rviz</em> và phía bên phải hiển thị URDF của nó, trong đó tất cả các thành phần (liên kết và khớp nối) được định nghĩa bằng cách sử dụng tag (tương tự như tệp <em>package.xml</em> và tệp <em>launch</em> từ các chương trước). Phiên bản URDF hoàn chỉnh của rô-bốt UR5 tất nhiên dài hơn nhiều so với phiên bản tối giản bên dưới nhưng đây chỉ giúp bạn hình dung định dạng của một file URDF ra sao.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673731033786/7b471337-f0a2-416c-a1a1-1ca7fcd2e8d1.jpeg" alt class="image--center mx-auto" /></p>
<h1 id="heading-tao-urdf-cho-robot-di-dong">Tạo URDF cho robot di động</h1>
<p>Trong phần này, bạn sẽ học cách tạo URDF cho một robot di động (mobile robot) đơn giản và hiển thị nó trong <em>rviz</em> (sau đó là Gazebo). Nhắc để bạn nhớ, ở phần cuối cùng của loạt bài này, bạn có thể sử dụng cử chỉ tay của mình để điều khiển robot này. Cool huh? Như mọi khi, mình cố gắng giữ các bước đơn giản nhất có thể và luôn khuyến khích bạn đọc và tự làm theo hướng dẫn thay vì copy/paste. Nếu bạn muốn tham khảo thì cả package này nằm <a target="_blank" href="https://github.com/TrinhNC/ros_mobile_robot">tại đây</a>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673810176948/6fc96ec1-d0c8-4894-a55b-53d4ee70ea27.jpeg" alt class="image--center mx-auto" /></p>
<ol>
<li><p>Tạo một ROS package mới và đặt tên là <em>ros_mobile_robot.</em> Sau đó, build catkin workspace.</p>
<pre><code class="lang-bash"> <span class="hljs-built_in">cd</span> ~/catkin_ws/src/
 catkin_create_pkg ros_mobile_robot
 <span class="hljs-built_in">cd</span> ..
 catkin build
</code></pre>
</li>
<li><p>Trong package mới, tạo 2 thư mục có tên: <em>urdf</em> và <em>launch.</em></p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1674255068791/18ee8ae6-7d8c-4c65-b839-348a7f878f70.jpeg" alt class="image--center mx-auto" /></p>
</li>
<li><p>Trong thư mục <em>launch</em>, tạo một tệp tên <em>mobile_robot.launch</em> mà bạn sẽ dùng để gọi <em>rviz</em> để hiển thị URDF mà bạn sẽ viết. Nội dung của tệp này như sau:</p>
<pre><code class="lang-xml"> <span class="hljs-meta">&lt;?xml version="1.0"?&gt;</span>
 <span class="hljs-tag">&lt;<span class="hljs-name">launch</span>&gt;</span>
   <span class="hljs-tag">&lt;<span class="hljs-name">arg</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"config"</span> <span class="hljs-attr">default</span>=<span class="hljs-string">"urdf"</span>/&gt;</span>
   <span class="hljs-tag">&lt;<span class="hljs-name">param</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"robot_description"</span> <span class="hljs-attr">textfile</span>=<span class="hljs-string">"$(find ros_mobile_robot)/urdf/mobile_robot.urdf"</span> /&gt;</span>
   <span class="hljs-tag">&lt;<span class="hljs-name">node</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"joint_state_publisher"</span> <span class="hljs-attr">pkg</span>=<span class="hljs-string">"joint_state_publisher"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"joint_state_publisher"</span> /&gt;</span>
   <span class="hljs-tag">&lt;<span class="hljs-name">node</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"robot_state_publisher"</span> <span class="hljs-attr">pkg</span>=<span class="hljs-string">"robot_state_publisher"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"robot_state_publisher"</span> /&gt;</span>
   <span class="hljs-tag">&lt;<span class="hljs-name">node</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"rviz"</span> <span class="hljs-attr">pkg</span>=<span class="hljs-string">"rviz"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"rviz"</span> <span class="hljs-attr">required</span>=<span class="hljs-string">"true"</span> <span class="hljs-attr">args</span>=<span class="hljs-string">"-d $(find ros_mobile_robot)/urdf/$(arg config).rviz"</span>/&gt;</span>
 <span class="hljs-tag">&lt;/<span class="hljs-name">launch</span>&gt;</span>
</code></pre>
<p> <strong>Giải thích</strong>: Dòng đầu tiên <code>&lt;?xml version="1.0"?&gt;</code> là một <a target="_blank" href="https://www.w3.org/TR/REC-xml/#NT-XMLDecl">khai báo XML</a> (XML declaration) dùng để chỉ định phiên bản XML. Bên trong tag <code>launch</code>, một đối số (argument) <code>config</code> được định nghĩa với giá trị mặc định là một chuỗi kí tự <code>"urdf"</code>. Sau đó, một tham số (parameter) <code>robot_description</code> được tạo và <code>textfile="$(find ros_mobile_robot)/urdf/mobile_robot.urdf"</code> chỉ đơn giản là đường dẫn đến URDF (tạo ở bước 5). Tiếp theo, hai node <code>joint_state_publisher</code> và <code>robot_state_publisher</code> được gọi để theo dõi các trạng thái của robot chẳng hạn như vị trí &amp; vận tốc của từng khớp và sự chuyển đổi (transform) giữa các liên kết. Cuối cùng, <code>rviz</code> được gọi bằng cách khởi chạy node <code>rviz</code>. Nếu có một tệp <code>.rviz</code> (fiel lưu các thiết lập của <em>rviz</em>) tồn tại trong <code>$(find ros_mobile_robot)/urdf/$(arg config).rviz</code>, nó sẽ được sử dụng. Nếu không, các thiết lập mặc định của <em>rviz</em> được khởi chạy. Vì đối số <code>config</code> theo mặc định là <code>urdf</code>, nên <code>$(arg config).rviz</code> bằng với <code>urdf.rviz</code> (ở bước 6 bạn sẽ biết cách có được file <em>urdf.rviz</em> này).</p>
</li>
<li><p>Trong thư mục <em>urdf</em>, tạo một tệp có tên <code>mobile_robot.urdf</code> và thêm các dòng sau</p>
<pre><code class="lang-xml"> <span class="hljs-meta">&lt;?xml version="1.0"?&gt;</span>
 <span class="hljs-tag">&lt;<span class="hljs-name">robot</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"my_robot"</span>&gt;</span>

  <span class="hljs-comment">&lt;!-- Tất cả các liên kết và khớp nối được khai báo ở đây --&gt;</span>

 <span class="hljs-tag">&lt;/<span class="hljs-name">robot</span>&gt;</span>
</code></pre>
<p> Một URDF luôn bắt đầu và kết thúc bằng thẻ <code>robot</code>. Bây giờ chúng ta sẽ thêm tất cả các mô tả về liên kết và khớp nối vào giữa <code>&lt;robot name="my_robot"&gt;</code> và <code>&lt;/robot&gt;</code>.</p>
<p> Nhưng thực chất liên kết (link) và khớp (joint) là gì? Các liên kết là các phần cứng của robot (giống như xương trong cơ thể chúng ta) và chúng được kết nối với nhau bằng các khớp. Khớp nối là bộ phận của rô-bốt cho phép chuyển động giữa các liên kết.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1674857923162/585da4bf-d997-4be1-b484-3540e1a725d0.jpeg" alt class="image--center mx-auto" /></p>
<p> Trong hình trên, phía bên trái là một robot có năm liên kết và hai khớp còn phía bên phải là URDF của nó. Để mô tả cụ thể các liên kết và khớp nối, bạn cần điền vào chỗ ba chấm mà bạn sẽ biết trong các bước tiếp theo.</p>
</li>
<li><p>Thẻ <code>&lt;link&gt;</code> được dùng để thêm liên kết. Thông thường người ta tạo một liên kết làm gốc (origin) cho toàn bộ robot. Đây chỉ đơn giản là một liên kết trống (vì nó chỉ có tên và không có thông tin gì khác). Mình gọi nó là <code>base_link</code>. Thêm những dòng này vào giữa <code>&lt;robot name="my_robot"&gt;</code> và <code>&lt;/robot&gt;</code>.</p>
<pre><code class="lang-xml">       <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"base_link"</span>&gt;</span>
       <span class="hljs-tag">&lt;/<span class="hljs-name">link</span>&gt;</span>
</code></pre>
<p> Tiếp theo, tạo một liên kết tên <code>chassis</code> (khung xe), về cơ bản là một hộp có kích thước (dài x rộng x cao) là 0.4 x 0.4 x 0.1 mét.</p>
<pre><code class="lang-xml">       <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"chassis"</span>&gt;</span>
         <span class="hljs-tag">&lt;<span class="hljs-name">visual</span>&gt;</span>
           <span class="hljs-tag">&lt;<span class="hljs-name">geometry</span>&gt;</span>
             <span class="hljs-tag">&lt;<span class="hljs-name">box</span> <span class="hljs-attr">size</span>=<span class="hljs-string">"0.4 0.4 0.1"</span>/&gt;</span>
           <span class="hljs-tag">&lt;/<span class="hljs-name">geometry</span>&gt;</span>
         <span class="hljs-tag">&lt;/<span class="hljs-name">visual</span>&gt;</span>
       <span class="hljs-tag">&lt;/<span class="hljs-name">link</span>&gt;</span>
</code></pre>
<p> Thẻ <code>&lt;link&gt;</code> có ba thành phần: <code>&lt;visual&gt;</code> để mô tả các thuộc tính trực quan (hình dạng của liên kết), <code>&lt;collision&gt;</code> để mô tả thể tích va chạm (để kiểm tra xem các liên kết có va vào nhau không) và <code>&lt;inertial &gt;</code> để xác định mômen quán tính (một thuộc tính vật lý quan trọng cho mô phỏng, ví dụ: trong Gazebo). Sơ đồ bên dưới hiển thị tất cả các thành phần con của <code>&lt;link&gt;</code>. Trong đoạn mã trên, chỉ <code>&lt;visual&gt;</code> và phần tử con của nó <code>&lt;geometry&gt;</code> (và phần tử "cháu" <code>&lt;box&gt;</code>) được sử dụng để tạo một hộp có kích thước 0.4 x 0.4 x 0.1 mét (dài x rộng x chiều cao). <code>&lt;collision&gt;</code> và <code>&lt;inertial&gt;</code> sẽ được thêm vào sau.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1674308133914/be595102-bb31-4395-8f00-5771d53e6802.jpeg" alt class="image--center mx-auto" /></p>
<p> Tiếp theo, chúng ta cần thêm một khớp giữa <code>base_link</code> (liên kết mẹ) và <code>chassis</code> (liên kết con). Đây là một khớp tĩnh (fixed) vì không có chuyển động.</p>
<pre><code class="lang-xml">       <span class="hljs-tag">&lt;<span class="hljs-name">joint</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"chassis_joint"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"fixed"</span>&gt;</span>
         <span class="hljs-tag">&lt;<span class="hljs-name">parent</span> <span class="hljs-attr">link</span>=<span class="hljs-string">"base_link"</span>/&gt;</span>
         <span class="hljs-tag">&lt;<span class="hljs-name">child</span> <span class="hljs-attr">link</span>=<span class="hljs-string">"chassis"</span>/&gt;</span>
       <span class="hljs-tag">&lt;/<span class="hljs-name">joint</span>&gt;</span>
</code></pre>
</li>
<li><p>Để hiển thị những gì bạn vừa tạo, hãy chạy <em>mobile_robot.launch</em> từ bước 3 bằng cách chạy lệnh sau trong một terminal:</p>
<pre><code class="lang-bash"> roslaunch ros_mobile_robot mobile_robot.launch
</code></pre>
<p> Lệnh này mở rviz lên nhưng ban đầu sẽ không có gì hện ra. Để xem mô hình rô-bốt của bạn, hãy làm theo các bước sau (xem ảnh bên dưới): Đầu tiên, thay đổi <em>Fixed Frame</em> thành <code>base_link</code> thay vì <code>map</code>. Sau đó, nhấp vào nút <em>Add</em> và chọn <em>RobotModel</em> trong danh sách. Nhấp vào <em>OK</em> và bạn sẽ thấy một chiếc hộp ở giữa cửa sổ bên phải. Dùng chuột, bạn có thể xoay (<em>nhấp chuột trái</em>), di chuyển X/Y (<em>nhấp chuột giữa</em>) và thu phóng (<em>nhấp chuột phải</em> hoặc <em>con lăn chuột)</em>.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1674309263751/555fe869-cc7a-4681-ae30-3a9dfa858a7d.jpeg" alt class="image--center mx-auto" /></p>
<p> Lưu tất cả các cấu hình bạn vừa chỉnh sửa trong <em>rviz</em> vào một tệp bằng cách đi tới <em>File &gt; Save Config As</em> hoặc sử dụng <em>Ctrl+Shift+S.</em> Sau đó, lưu file với tên <em>urdf.rviz</em> trong thư mục <code>~/catkin_ws/src/ros_mobile_robot/urdf</code>. Đường dẫn đến <em>urdf.rviz</em> này trùng với đường dẫn trong tệp launch ở bước 3, vì thế lần sau khi bạn khởi chạy nó, bạn sẽ không cần phải thêm <em>RobotModel</em> và thay đổi lại <em>Fixed Frame</em> nữa.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1674310007214/be40c681-c72b-4204-bd3d-cf7d661c0cc9.jpeg" alt class="image--center mx-auto" /></p>
</li>
<li><p>Khung xe đã xong! Giờ hãy thêm bánh xe. Dưới đây là một ví dụ cho thấy cách mô tả bánh trước bên phải (front_wheel_right) và khớp nối của nó với khung xe (chassis).</p>
<pre><code class="lang-xml">       <span class="hljs-tag">&lt;<span class="hljs-name">material</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"blue"</span>&gt;</span>
         <span class="hljs-tag">&lt;<span class="hljs-name">color</span> <span class="hljs-attr">rgba</span>=<span class="hljs-string">"0 0 0.8 1"</span>/&gt;</span>
       <span class="hljs-tag">&lt;/<span class="hljs-name">material</span>&gt;</span>

       <span class="hljs-comment">&lt;!--right front wheel--&gt;</span>
       <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"front_wheel_right"</span>&gt;</span>
         <span class="hljs-tag">&lt;<span class="hljs-name">visual</span>&gt;</span>
           <span class="hljs-tag">&lt;<span class="hljs-name">geometry</span>&gt;</span>
             <span class="hljs-tag">&lt;<span class="hljs-name">cylinder</span> <span class="hljs-attr">length</span>=<span class="hljs-string">"0.04"</span> <span class="hljs-attr">radius</span>=<span class="hljs-string">"0.06"</span>/&gt;</span>
           <span class="hljs-tag">&lt;/<span class="hljs-name">geometry</span>&gt;</span>
           <span class="hljs-tag">&lt;<span class="hljs-name">material</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"blue"</span>/&gt;</span>
         <span class="hljs-tag">&lt;/<span class="hljs-name">visual</span>&gt;</span>
       <span class="hljs-tag">&lt;/<span class="hljs-name">link</span>&gt;</span>

       <span class="hljs-tag">&lt;<span class="hljs-name">joint</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"front_wheel_right_joint"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"continuous"</span>&gt;</span>
         <span class="hljs-tag">&lt;<span class="hljs-name">parent</span> <span class="hljs-attr">link</span>=<span class="hljs-string">"chassis"</span>/&gt;</span>
         <span class="hljs-tag">&lt;<span class="hljs-name">child</span> <span class="hljs-attr">link</span>=<span class="hljs-string">"front_wheel_right"</span>/&gt;</span>
         <span class="hljs-tag">&lt;<span class="hljs-name">origin</span> <span class="hljs-attr">xyz</span>=<span class="hljs-string">"0.2 -0.225 0"</span> <span class="hljs-attr">rpy</span>=<span class="hljs-string">"1.5707 0 0"</span>/&gt;</span>
         <span class="hljs-tag">&lt;<span class="hljs-name">axis</span> <span class="hljs-attr">xyz</span>=<span class="hljs-string">"0 0 -1"</span>/&gt;</span>
       <span class="hljs-tag">&lt;/<span class="hljs-name">joint</span>&gt;</span>
</code></pre>
<p> <strong>Giải thích</strong>: Đầu tiên, một tag <em>material</em> với màu xanh lam (blue) được tạo (để sơn bánh xe). Liên kết bánh xe khá giống với khung xe chỉ khác là hình trụ thay vì hình hộp. Hình trụ có chiều dài 0.04 m và bán kính 0.06 m. Bánh xe có màu <em>xanh lam</em> (để dễ phân biệt với khung xe). Mối nối giữa khung xe (liên kết mẹ) và bánh trước bên phải (liên kết con) là <a target="_blank" href="http://wiki.ros.org/urdf/XML/joint">khớp động</a> (continuous), tức là khớp xoay liên tục quanh trục và không có giới hạn trên và dưới). Thông tin về các loại liên kết khác trong URDF có <a target="_blank" href="http://wiki.ros.org/urdf/XML/joint">tại đây</a>.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1674424686634/eb36cda8-71da-4ca9-b5c1-1febdf76dccc.jpeg" alt class="image--center mx-auto" /></p>
<p> Thẻ <code>&lt;origin&gt;</code> được dùng để mô tả vị trí và hướng của liên kết con đối với liên kết mẹ. Trong hình trên, bạn có thể thấy rằng vị trí của bánh xe so với gốc là 0.2 m ở X, -0.225 m ở Y và 0 m ở Z. Nó cũng cần xoay 90° (hoặc 1.5707 radian) ngược chiều kim đồng hồ dọc theo trục X của khung xe nếu không thì trục Z (màu xanh lam) của bánh xe sẽ bị hướng lên trên (xem hình ảnh bên dưới). Nếu bạn muốn trực quan hóa các trục, bạn nên nhấp vào nút <em>Add</em> và chọn <em>TF</em> (viết tắt của từ transformation) nằm ngay bên dưới <em>RobotModel</em> trong hộp thoại. Ở đây, X là Đỏ, Y là Lục và Z là Lam (hay XYZ &lt;-&gt; RGB).</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1674425828466/075e3877-73d1-4233-ba5f-81375f84fe76.jpeg" alt class="image--center mx-auto" /></p>
<p> Cuối cùng, thẻ <code>&lt;axis&gt;</code> xác định trục quay của bánh là trục Z. Vì vậy, nó được đặt thành <code>xyz="0 0 -1"</code> vì chỉ có chuyển động trên trục Z. Có thể bạn thắc mắc tại sao là -1 thay vì 1. Đó là vì quy ước chung về hướng quay là cùng chiều kim đồng hồ là âm và ngược chiều kim đồng hồ là dương. Trong trường hợp này, khi muốn robot di chuyển về phía trước, các bánh xe bên phải sẽ di chuyển theo chiều kim đồng hồ dọc theo trục Z của nó, nghĩa là âm, do đó -1. Mặt khác, các bánh xe bên trái di chuyển ngược chiều kim đồng hồ khi robot di chuyển về phía trước để chúng có Z dương hay +1 (xem hình ảnh bên dưới).</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1674942679357/9eda2c2f-1d48-4e5b-b693-bf0e7b2959bc.jpeg" alt="urdf-wheels" class="image--center mx-auto" /></p>
</li>
<li><p>Bạn hãy cố gắng làm theo ý tưởng trên và tự thêm 3 bánh xe còn lại. Một bài tập khác dành là thêm một hộp nhỏ ở phí trước của rô-bốt để bạn biết đầu của nó ở đâu. Bạn có thể tham khảo mã nguồn <a target="_blank" href="https://github.com/TrinhNC/ros_mobile_robot/blob/main/urdf/mobile_robot.urdf">tại đây</a>. Sau đó, khởi chạy lại <em>mobile_robot.launch</em> và kết quả cuối cùng sẽ như sau:</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1674943602056/0a8b25de-0b5b-4fec-baff-f699b812f209.jpeg" alt="mobile-robot-rviz-with-head" class="image--center mx-auto" /></p>
</li>
</ol>
<p>Và thế là xong! Bây giờ bạn đã biết cách mô tả một rô-bốt đơn giản bằng URDF và view nó trong rviz. Trong chương tiếp theo, mình sẽ chỉ cho bạn cách thêm nhiều thuộc tính vào file URDF để có thể mô phỏng và di chuyển robot trong Gazebo.</p>
]]></content:encoded></item><item><title><![CDATA[10. Simulate a Mobile Robot in ROS - Part 1]]></title><description><![CDATA[In this and the next chapter, you will learn to build a simulated robot and visualize its states with two programs coming with ROS: Gazebo and rviz. Gazebo is a 3D simulation platform, meaning it lets you model robots and environments that have physi...]]></description><link>https://robodev.blog/simulate-a-mobile-robot-in-ros-part-1</link><guid isPermaLink="true">https://robodev.blog/simulate-a-mobile-robot-in-ros-part-1</guid><category><![CDATA[robot-operating-system]]></category><category><![CDATA[autonomous mobile robots]]></category><category><![CDATA[urdf]]></category><category><![CDATA[rviz]]></category><category><![CDATA[gazebo]]></category><dc:creator><![CDATA[Trinh Nguyen]]></dc:creator><pubDate>Mon, 06 Feb 2023 22:51:48 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1675723730071/9d52da41-e4f4-450c-928d-6e8d6d86f4f1.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this and the next chapter, you will learn to build a simulated robot and visualize its states with two programs coming with ROS: Gazebo and rviz. <strong>Gazebo is a 3D simulation platform</strong>, meaning it lets you model robots and environments that have physical properties such as gravity, speed, acceleration, etc., similar to the real world. When you do not have a real robot or you want to test your robot in a synthetic environment before trying it in real then you should use Gazebo. <strong>Rviz (short for ROS Visualization) is a 3D visualization software</strong> that helps monitor the robots' states such as sensor data (image, point cloud, trajectory, etc.), transformations between different coordinates, and so on. Rviz could be used for both simulated and real robots.</p>
<h1 id="heading-robot-description">Robot Description</h1>
<p>In order to visualize or simulate a robot in ROS, you need to describe it in a robot description file called URDF (Unified Robot Description Format). A URDF is basically an XML file that allows you to describe important physical properties of a robot such as links, joints, shapes, colors, collisions, etc. In the example below, on the left side you can see the visualization of a Universal Robot UR5 in <em>rviz</em> and the right side shows its (incomplete) URDF in which all components (links and joints, see step 4) are defined using tags (similar to package.xml and launch file from previous chapters). The complete URDF version of the UR5 robot is of course much longer than the one I show below but this is just to let you see its format.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673731033786/7b471337-f0a2-416c-a1a1-1ca7fcd2e8d1.jpeg" alt class="image--center mx-auto" /></p>
<h1 id="heading-create-urdf-for-a-mobile-robot">Create URDF for a mobile robot</h1>
<p>In this part, you will learn to create URDF for a simple mobile robot and view it in rviz (then later in Gazebo). Just as a reminder, at the end of this series, you can use your hand gesture to control this robot. Cool, huh? I try to keep it as simple as possible and as always encourage you to read the instruction and type by yourself instead of copy/paste. The original package is located <a target="_blank" href="https://github.com/TrinhNC/ros_mobile_robot">here</a>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673810176948/6fc96ec1-d0c8-4894-a55b-53d4ee70ea27.jpeg" alt class="image--center mx-auto" /></p>
<ol>
<li><p>Create a new ROS package and name it <em>ros_mobile_robot.</em> Then build the workspace as well.</p>
<pre><code class="lang-bash"> <span class="hljs-built_in">cd</span> ~/catkin_ws/src/
 catkin_create_pkg ros_mobile_robot
 <span class="hljs-built_in">cd</span> ..
 catkin build
</code></pre>
</li>
<li><p>In the new package folder, make 2 new folders named: <em>urdf</em> and <em>launch.</em></p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1674255068791/18ee8ae6-7d8c-4c65-b839-348a7f878f70.jpeg" alt class="image--center mx-auto" /></p>
</li>
<li><p>In the folder <em>launch</em>, create a launch file with the name <em>mobile_robot.launch</em> which you will use to call rviz to visualize the robot description you are going to write. The content of the launch file:</p>
<pre><code class="lang-xml"> <span class="hljs-meta">&lt;?xml version="1.0"?&gt;</span>
 <span class="hljs-tag">&lt;<span class="hljs-name">launch</span>&gt;</span>
   <span class="hljs-tag">&lt;<span class="hljs-name">arg</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"config"</span> <span class="hljs-attr">default</span>=<span class="hljs-string">"urdf"</span>/&gt;</span>
   <span class="hljs-tag">&lt;<span class="hljs-name">param</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"robot_description"</span> <span class="hljs-attr">textfile</span>=<span class="hljs-string">"$(find ros_mobile_robot)/urdf/mobile_robot.urdf"</span> /&gt;</span>
   <span class="hljs-tag">&lt;<span class="hljs-name">node</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"joint_state_publisher"</span> <span class="hljs-attr">pkg</span>=<span class="hljs-string">"joint_state_publisher"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"joint_state_publisher"</span> /&gt;</span>
   <span class="hljs-tag">&lt;<span class="hljs-name">node</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"robot_state_publisher"</span> <span class="hljs-attr">pkg</span>=<span class="hljs-string">"robot_state_publisher"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"robot_state_publisher"</span> /&gt;</span>
   <span class="hljs-tag">&lt;<span class="hljs-name">node</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"rviz"</span> <span class="hljs-attr">pkg</span>=<span class="hljs-string">"rviz"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"rviz"</span> <span class="hljs-attr">required</span>=<span class="hljs-string">"true"</span> <span class="hljs-attr">args</span>=<span class="hljs-string">"-d $(find ros_mobile_robot)/urdf/$(arg config).rviz"</span>/&gt;</span>
 <span class="hljs-tag">&lt;/<span class="hljs-name">launch</span>&gt;</span>
</code></pre>
<p> <strong>Explain</strong>: The first line <code>&lt;?xml version="1.0"?&gt;</code> is an <a target="_blank" href="https://www.w3.org/TR/REC-xml/#NT-XMLDecl">XML declaration</a> which is an optional indication of the version of XML. Inside the <code>launch</code> tag, an argument name <code>config</code> is defined with the default value as a string<code>"urdf"</code>. Then, a parameter <code>robot_description</code> is created and <code>textfile="$(find ros_mobile_robot)/urdf/mobile_robot.urdf"</code> is simply the path to the URDF from step 5. Next, two nodes <code>joint_state_publisher</code> and <code>robot_state_publisher</code> are called to keep track of robot states such as the position &amp; velocity of each joint and transformation between links. Finally, the <code>rviz</code> is called by launching the <code>rviz</code> package. If there is a <code>.rviz</code> file (which contains all the settings of <em>rviz</em>) existed in <code>$(find ros_mobile_robot)/urdf/$(arg config).rviz</code>, it will be loaded (otherwise, the default settings of <em>rviz</em> are launched). Since the argument <code>config</code> is by default <code>urdf</code>, this <code>$(arg config).rviz</code> equals to <code>urdf.rviz</code>. To get this <em>urdf.rviz</em>, see step 6.</p>
</li>
<li><p>In the folder <em>urdf</em>, create a file named <code>mobile_robot.urdf</code> and add the following lines</p>
<pre><code class="lang-xml"> <span class="hljs-meta">&lt;?xml version="1.0"?&gt;</span>
 <span class="hljs-tag">&lt;<span class="hljs-name">robot</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"my_robot"</span>&gt;</span>

  <span class="hljs-comment">&lt;!--  All links and joints are declared here --&gt;</span>

 <span class="hljs-tag">&lt;/<span class="hljs-name">robot</span>&gt;</span>
</code></pre>
<p> A URDF always starts and closes with the <code>robot</code> tag. From now on, we will write all the descriptions of links and joints between <code>&lt;robot name="my_robot"&gt;</code> and <code>&lt;/robot&gt;</code>.</p>
<p> But what are links and joints? Links are the rigid pieces of a robot (like bones in our body) and they are connected to each other by joints. Joints are parts of a robot that enable motions between connected links.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1674857923162/585da4bf-d997-4be1-b484-3540e1a725d0.jpeg" alt class="image--center mx-auto" /></p>
<p> In the image above, the left side is a simple robot that has five links and two joints while the right side is its URDF. To concretely describe the links and joints, you need to fill in <code>...</code> between their tags which you will learn in the next steps.</p>
</li>
<li><p>A <a target="_blank" href="http://wiki.ros.org/urdf/XML/link">link</a> can be added using the <code>&lt;link&gt;</code> tag. It is standard in ROS to have a link as the origin of the whole robot. This is simply an empty link and I call it <code>base_link</code> here. Add these lines between <code>&lt;robot name="my_robot"&gt;</code> and <code>&lt;/robot&gt;</code>.</p>
<pre><code class="lang-xml">   <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"base_link"</span>&gt;</span>
   <span class="hljs-tag">&lt;/<span class="hljs-name">link</span>&gt;</span>
</code></pre>
<p> Next, define the chassis which is basically a box with dimensions (length x width x height) of 0.4 x 0.4 x 0.1 meters.</p>
<pre><code class="lang-xml">   <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"chassis"</span>&gt;</span>
     <span class="hljs-tag">&lt;<span class="hljs-name">visual</span>&gt;</span>
       <span class="hljs-tag">&lt;<span class="hljs-name">geometry</span>&gt;</span>
         <span class="hljs-tag">&lt;<span class="hljs-name">box</span> <span class="hljs-attr">size</span>=<span class="hljs-string">"0.4 0.4 0.1"</span>/&gt;</span>
       <span class="hljs-tag">&lt;/<span class="hljs-name">geometry</span>&gt;</span>
     <span class="hljs-tag">&lt;/<span class="hljs-name">visual</span>&gt;</span>
   <span class="hljs-tag">&lt;/<span class="hljs-name">link</span>&gt;</span>
</code></pre>
<p> A <code>&lt;link&gt;</code> has three elements: <code>&lt;visual&gt;</code> for describing the visual properties (shape of the link), <code>&lt;collision&gt;</code> for describing collision volumes (to check if links collide), and <code>&lt;inertial&gt;</code> for defining the moment of inertia (a crucial physical property for realistic simulation, e.g. in Gazebo). The diagram below shows all the subcomponents of <code>&lt;link&gt;</code>. In the code above, only the <code>&lt;visual&gt;</code> and its child <code>&lt;geometry&gt;</code> (and its child <code>&lt;box&gt;</code>) is used to create a box with dimensions of 0.4 x 0.4 x 0.1 meters (length x width x height). The <code>&lt;collision&gt;</code> and <code>&lt;inertial&gt;</code> will be added later.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1674308133914/be595102-bb31-4395-8f00-5771d53e6802.jpeg" alt class="image--center mx-auto" /></p>
<p> Next, we need to add a joint between <code>base_link</code> (parent link) and <code>chassis</code> (child link) which is a fixed joint because there is no movement here.</p>
<pre><code class="lang-xml">   <span class="hljs-tag">&lt;<span class="hljs-name">joint</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"chassis_joint"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"fixed"</span>&gt;</span>
     <span class="hljs-tag">&lt;<span class="hljs-name">parent</span> <span class="hljs-attr">link</span>=<span class="hljs-string">"base_link"</span>/&gt;</span>
     <span class="hljs-tag">&lt;<span class="hljs-name">child</span> <span class="hljs-attr">link</span>=<span class="hljs-string">"chassis"</span>/&gt;</span>
   <span class="hljs-tag">&lt;/<span class="hljs-name">joint</span>&gt;</span>
</code></pre>
</li>
<li><p>To visualize what you just created, launch the <em>mobile_robot.launch</em> from step 3 by:</p>
<pre><code class="lang-bash"> roslaunch ros_mobile_robot mobile_robot.launch
</code></pre>
<p> This opens rviz but there is nothing to see at the beginning. In order to view your robot model, follow these steps (see the photo below): First, change the <em>Fixed Frame</em> to <code>base_link</code> instead of <code>map</code>. Then, click <em>Add</em> button and select <em>RobotModel</em> in the list. Click <em>OK</em> and you should see a box in the middle of the grid. With your mouse, you can rotate (<em>left-click</em>), move X/Y (<em>middle-click</em>), and zoom (<em>right-click</em> or <em>mouse wheel)</em>.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1674309263751/555fe869-cc7a-4681-ae30-3a9dfa858a7d.jpeg" alt class="image--center mx-auto" /></p>
<p> Save all the configurations you just edited in <em>rviz</em> to a file by going to <em>File &gt; Save Config As</em> or using <em>Ctrl+Shift+S.</em> Then, save this as <em>urdf.rviz</em> in the folder <code>~/catkin_ws/src/ros_mobile_robot/urdf</code>. The path to this <em>urdf.rviz</em> was already defined in the launch file from step 3 so the next time you launch it, you don't have to add the <em>RobotModel</em>, and change the <em>Fixed Frame</em> again.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1674310007214/be40c681-c72b-4204-bd3d-cf7d661c0cc9.jpeg" alt class="image--center mx-auto" /></p>
</li>
<li><p>The chassis is done! It's time to add wheels. Below is one example showing the descriptions of the right front wheel and its joint to the chassis look like.</p>
<pre><code class="lang-xml">   <span class="hljs-tag">&lt;<span class="hljs-name">material</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"blue"</span>&gt;</span>
     <span class="hljs-tag">&lt;<span class="hljs-name">color</span> <span class="hljs-attr">rgba</span>=<span class="hljs-string">"0 0 0.8 1"</span>/&gt;</span>
   <span class="hljs-tag">&lt;/<span class="hljs-name">material</span>&gt;</span>

   <span class="hljs-comment">&lt;!--right front wheel--&gt;</span>
   <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"front_wheel_right"</span>&gt;</span>
     <span class="hljs-tag">&lt;<span class="hljs-name">visual</span>&gt;</span>
       <span class="hljs-tag">&lt;<span class="hljs-name">geometry</span>&gt;</span>
         <span class="hljs-tag">&lt;<span class="hljs-name">cylinder</span> <span class="hljs-attr">length</span>=<span class="hljs-string">"0.04"</span> <span class="hljs-attr">radius</span>=<span class="hljs-string">"0.06"</span>/&gt;</span>
       <span class="hljs-tag">&lt;/<span class="hljs-name">geometry</span>&gt;</span>
       <span class="hljs-tag">&lt;<span class="hljs-name">material</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"blue"</span>/&gt;</span>
     <span class="hljs-tag">&lt;/<span class="hljs-name">visual</span>&gt;</span>
   <span class="hljs-tag">&lt;/<span class="hljs-name">link</span>&gt;</span>

   <span class="hljs-tag">&lt;<span class="hljs-name">joint</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"front_wheel_right_joint"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"continuous"</span>&gt;</span>
     <span class="hljs-tag">&lt;<span class="hljs-name">parent</span> <span class="hljs-attr">link</span>=<span class="hljs-string">"chassis"</span>/&gt;</span>
     <span class="hljs-tag">&lt;<span class="hljs-name">child</span> <span class="hljs-attr">link</span>=<span class="hljs-string">"front_wheel_right"</span>/&gt;</span>
     <span class="hljs-tag">&lt;<span class="hljs-name">origin</span> <span class="hljs-attr">xyz</span>=<span class="hljs-string">"0.2 -0.225 0"</span> <span class="hljs-attr">rpy</span>=<span class="hljs-string">"1.5707 0 0"</span>/&gt;</span>
     <span class="hljs-tag">&lt;<span class="hljs-name">axis</span> <span class="hljs-attr">xyz</span>=<span class="hljs-string">"0 0 -1"</span>/&gt;</span>
   <span class="hljs-tag">&lt;/<span class="hljs-name">joint</span>&gt;</span>
</code></pre>
<p> <strong>Explain</strong>: First, a <em>material</em> tag with blue color is created (to paint the wheel later). The wheel link is pretty similar to the chassis with the geometry now a cylinder instead of a box. The cylinder has a length of 0.04 m and a radius of 0.06 m. The wheel has the color <em>blue</em> (to be easily distinguished from the chassis). The joint between the chassis (parent link) and the right front wheel (child link) is a <a target="_blank" href="http://wiki.ros.org/urdf/XML/joint">continuous joint</a> (i.e. a continuous hinge joint that rotates around the axis and has no upper and lower limits). More types of joints in URDF can be found <a target="_blank" href="http://wiki.ros.org/urdf/XML/joint">here</a>.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1674424686634/eb36cda8-71da-4ca9-b5c1-1febdf76dccc.jpeg" alt class="image--center mx-auto" /></p>
<p> The <code>&lt;origin&gt;</code> is used to describe the position and orientation of the child link with respect to the parent link. In the image above, you can see that the wheel is 0.2 m in X, -0.225m in Y, and 0 m in Z. It also needs to rotate 90° (or 1.5707 radians) counter-clockwise along the X-axis of the chassis otherwise the Z-axis (in blue) of the wheel is pointing upward (see image below). If you want to visualize the axes, you should click on <em>Add</em> button and select <em>TF</em> (stands for transformation) which is right under the <em>RobotModel</em> in the dialog. Here, X is Red, Y is Green and Z is Blue (so XYZ &lt;-&gt; RGB).</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1674425828466/075e3877-73d1-4233-ba5f-81375f84fe76.jpeg" alt class="image--center mx-auto" /></p>
<p> Finally, the <code>&lt;axis&gt;</code> tag defines the axis of rotation which is the Z-axis (of the wheel not the chassis) in this case. So it is set to <code>xyz="0 0 -1"</code> since there is only movement on Z-axis. You may wonder why -1 instead of 1. It is because the common convention for rotating direction is that clockwise is negative and counter-clockwise is positive. In this case, when I want the robot to move forward, the right wheels should move clockwise along its Z-axis meaning negative, hence -1. The left wheels, on the other hand, move counter-clockwise when the robot moves forward so they have positive Z or simply 1 (see image below).</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1674942679357/9eda2c2f-1d48-4e5b-b693-bf0e7b2959bc.jpeg" alt="urdf-wheels" class="image--center mx-auto" /></p>
</li>
<li><p>Try to follow the idea above and add the other 3 wheels by yourself. Another exercise for you is to add a small box on the top front of the robot so you know where its head is. You can refer to the source code <a target="_blank" href="https://github.com/TrinhNC/ros_mobile_robot/blob/main/urdf/mobile_robot.urdf">here</a>. After that, launch the <em>mobile_robot.launch</em> again and the final result should look like this:</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1674943602056/0a8b25de-0b5b-4fec-baff-f699b812f209.jpeg" alt="mobile-robot-rviz-with-head" class="image--center mx-auto" /></p>
<p> And that's it! Now you know how to describe a robot by a URDF and visualize it in rviz. To make a robot look more professional like the UR5 above, you need to create a detailed mesh (CAD) file for each part which is of course not the purpose of this series. In the next chapter, I will show you how to add more properties to links and joints to be able to simulate (and move) the robot in Gazebo. Keep up the good work!</p>
</li>
</ol>
]]></content:encoded></item><item><title><![CDATA[9. Nhận Diện Cử Chỉ Tay trong ROS]]></title><description><![CDATA[Hy vọng bạn vẫn nhớ một trong những lý do rất quan trọng mà chúng ta tìm hiểu ROS: "Don't reinvent the wheel", nghĩa là có rất nhiều package (hoặc project) thú vị có sẵn mà bạn có thể ứng dụng ngay hoặc không tốn nhiều công sức để triển khai trong RO...]]></description><link>https://robodev.blog/nhan-dien-cu-chi-tay-trong-ros</link><guid isPermaLink="true">https://robodev.blog/nhan-dien-cu-chi-tay-trong-ros</guid><category><![CDATA[ROS]]></category><category><![CDATA[vietnamese]]></category><category><![CDATA[#hand-gesture-recognition]]></category><category><![CDATA[robotics]]></category><category><![CDATA[Tutorial]]></category><dc:creator><![CDATA[Trinh Nguyen]]></dc:creator><pubDate>Sun, 08 Jan 2023 09:04:46 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1673163194743/e119a2d8-b331-41c8-82ee-3b9eda49692c.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hy vọng bạn vẫn nhớ một trong những lý do rất quan trọng mà chúng ta tìm hiểu ROS: "<strong>Don't reinvent the wheel"</strong>, nghĩa là có rất nhiều package (hoặc project) thú vị có sẵn mà bạn có thể ứng dụng ngay hoặc không tốn nhiều công sức để triển khai trong ROS. Trong chương này, bạn sẽ học cách clone (tải) và chạy một package giúp nhận dạng (recognize) các kí hiệu/cử chỉ tay trên các ảnh được publish từ webcam của bạn. Nghĩa là thay vì chỉ hiển thị hình ảnh như trong <a target="_blank" href="https://robodev.blog/tao-mot-ros-subscriber">chương 8</a>, giờ bạn có thể dùng chúng để có những thông tin hữu ích hơn. Lưu ý: Bản Tiếng Anh của bài viết <a target="_blank" href="https://robodev.blog/hand-gesture-recognition-in-ros">ở đây</a>.</p>
<h1 id="heading-cong-nghe-nhan-dien-cu-chi-tay-hand-gesture-recognition">Công nghệ nhận diện cử chỉ tay (Hand Gesture Recognition)</h1>
<p>Một hệ thống có thể hiểu cử chỉ tay của con người có vô vàn ứng dụng thực tế. Ví dụ như trong chế tạo rô-bốt, nó có thể giúp chuyển đổi nhận dạng cử chỉ thành tín hiệu điều khiển khiến rô-bốt.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://media.giphy.com/media/26AHwLqr8FKVZ9ykU/giphy.gif">https://media.giphy.com/media/26AHwLqr8FKVZ9ykU/giphy.gif</a></div>
<p> </p>
<p>Với sự phát triển gần đây của trí tuệ nhân tạo (AI), việc triển khai hệ thống đó dễ dàng hơn bao giờ hết. Trong chương này, mình muốn giới thiệu với bạn <a target="_blank" href="https://google.github.io/mediapipe/solutions/hands.html">Mediapipe Hands</a> là gói máy học (ML) mã nguồn mở của Google có thể giúp nhận dạng và theo dõi (track) bàn tay người bằng hình ảnh. Về cơ bản, Mediapipe kết hợp hai mô hình (model) học máy: một mô hình palm detection (nhận diện bàn tay) để tìm vị trí của bàn tay và một mô hình mốc (landmark) bàn tay để định vị chính xác 21 tọa độ khớp ngón tay từ vị trí tìm được ở palm detection (xem hình ảnh bên dưới). Nó được train (huấn luyện, đào tạo) với khoảng 30 nghìn dữ liệu thực có gắn nhãn (label) và chạy rất nhanh theo <a target="_blank" href="https://arxiv.org/abs/1512.02325">cách thức single-shot</a>. Bạn có thể tìm hiểu thêm thông tin chi tiết về package này <a target="_blank" href="https://google.github.io/mediapipe/solutions/hands.html">tại đây</a>.</p>
<p><img src="https://mediapipe.dev/images/mobile/hand_landmarks.png" alt="hand_landmarks.png" class="image--center mx-auto" /></p>
<p>Các keypoint (điểm màu đỏ ở trong hình landmark bên trên) được nhận diện từ Mediapipe cần được chuyển đổi thành những hành động có ý nghĩa. Chẳng hạn như từ hình landmark bàn tay ở trên, hệ thống của chúng ta sẽ cho chúng ta biết trạng thái là "Open" vì tất cả các ngón tay đều đang được duỗi thẳng. Để làm được điều đó, cần phải có một model khác để phân loại (classify) các hành động. May mắn là đã có một chương trình có sẵn trên GitHub. Repository (kho lưu trữ) <a target="_blank" href="https://github.com/kinivi/hand-gesture-recognition-mediapipe">hand-gesture-recognition-mediapipe</a> này chứa các tập lệnh Python giúp người dùng train và triển khai các model để nhận dạng dấu hiệu bàn tay và cử chỉ ngón tay. Mình đã dùng và biến nó thành một <a target="_blank" href="https://github.com/TrinhNC/ros_hand_gesture_recognition">ROS package</a> để bạn có thể sao chép (clone) và chạy ngay.</p>
<h1 id="heading-cai-dat">Cài đặt</h1>
<p>Đầu tiên, cài đặt các phần phụ thuộc (dependencies) sau bằng cách sử dụng <code>pip3</code> (hoặc <code>conda</code> nếu bạn dùng Anaconda):</p>
<ul>
<li><p>Mediapipe 0.8.1</p>
</li>
<li><p>OpenCV 3.4.2 trở lên</p>
</li>
<li><p>Tensorflow 2.3.0 trở lên</p>
</li>
</ul>
<p>Dưới đây là ba câu lệnh dùng <code>pip3</code> (bạn phải chạy riêng từng cái một):</p>
<pre><code class="lang-bash">pip3 install mediapipe
pip3 install opencv-python
pip3 install tensorflow
</code></pre>
<p>Sau đó, sao chép kho lưu trữ này vào thư mục <code>catkin_ws/src</code>:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> ~/catkin_ws/src
git <span class="hljs-built_in">clone</span> https://github.com/TrinhNC/ros_hand_gesture_recognition.git
</code></pre>
<p>Build package:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> ~/catkin_ws
catkin build
</code></pre>
<h1 id="heading-chay-package">Chạy Package</h1>
<p>Sau khi build thành công, bạn có thể chạy demo. Nhớ source không gian làm việc (catkin workspace) bằng command <code>source ~/catkin_ws/devel/setup.bash</code>. Mở 2 terminal. Trong terminal thứ nhất, chạy image publisher (từ <a target="_blank" href="https://robodev.blog/write-a-ros-publisher">chương 7</a> và nhớ kết nối webcam với máy ảo nếu bạn đang sử dụng máy ảo):</p>
<pre><code class="lang-bash">roslaunch my_cam my_cam.launch
</code></pre>
<p>Nếu bạn thắc mắc về <code>roslaunch</code> có thể xem lại <a target="_blank" href="https://robodev.blog/tao-ros-publisher#heading-chay-node-bang-roslaunch">ở đây</a>. Trong terminal thứ hai, chạy hand gesture recognition:</p>
<pre><code class="lang-bash">roslaunch ros_hand_gesture_recognition hand_sign.launch
</code></pre>
<p>Kết quả sẽ như thế này:</p>
<p><img src="https://user-images.githubusercontent.com/19979949/210186155-c21b0fb2-84ba-430c-94ab-b273f5f36c6c.gif" alt="ros-mediapipe" class="image--center mx-auto" /></p>
<p>Trong GIF ở trên, <strong>FPS</strong> là viết tắt của Frame Per Second, là số lượng hình ảnh được xử lý mỗi giây, <strong>Right</strong> là tay phải và các từ sau đó (Turn Right, Forward, v.v.) là các nhãn (label) mà mình đã gán cho các dấu tay. Bạn có thể thay đổi các nhãn này bằng cách làm theo phần bên dưới.</p>
<h1 id="heading-train-mo-hinh-nhan-dang-dau-tay">Train mô hình nhận dạng dấu tay</h1>
<p>Package hiện tại chỉ có thể phân loại được 6 dấu tay (6 lớp/classes) và mình đã gắn nhãn: Stop (Dừng), Go (Đi), Forward (Tiến), Backward (Lùi), Turn Right (Rẽ phải) and Turn Left (Rẽ trái) (xem hình ảnh bên dưới). Chúng sẽ được chuyển đổi thành tín hiệu điều khiển để di chuyển rô-bốt trong các chương sau. Nếu bạn muốn thay đổi hoặc thêm cử chỉ hoặc bạn phát hiện ra rằng trained model của mình chạy chưa tốt lắm thì bạn hoàn toàn có thể tự thu thập dữ liệu (collect data) và train lại.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1678623209549/dd8313bb-8f8e-443e-bc9b-efe214495c50.jpeg" alt="ros-hand-gesture-recognition" class="image--center mx-auto" /></p>
<p>Có hai <a target="_blank" href="https://jupyter.org/">jupyter notebook</a> trong thư mục <a target="_blank" href="https://github.com/TrinhNC/ros_hand_gesture_recognition/tree/main/src/notebooks">src/notebooks</a>:</p>
<ul>
<li><p><a target="_blank" href="https://github.com/TrinhNC/ros_hand_gesture_recognition/blob/main/src/notebooks/keypoint_classification_EN.ipynb"><strong>keypoint_classification_EN.ipynb</strong></a>: tập lệnh đào tạo mô hình để nhận dạng ký hiệu tay.</p>
</li>
<li><p><a target="_blank" href="https://github.com/TrinhNC/ros_hand_gesture_recognition/blob/main/src/notebooks/point_history_classification.ipynb"><strong>point_history_classification.ipynb</strong></a>: tập lệnh đào tạo mô hình để nhận dạng cử chỉ ngón tay (có nghĩa là mô hình sau khi đào tạo có thể phát hiện chuyển động của ngón tay của bạn chứ không chỉ là một dấu hiệu tĩnh như trong mô hình bên trên).</p>
</li>
</ul>
<p>Ở trong ROS package hiện tại mình chỉ sử dụng mô hình keypoint classification vì ứng dụng của mình chỉ cần đến thế nhưng bạn có thể dễ dàng thay đổi nó để phù hợp với ứng dụng của bạn.</p>
<p>Trong ví dụ dưới đây, mình sẽ chỉ cho bạn cách thêm một dấu tay nữa. Giả sử chúng ta muốn thêm ký hiệu này✌️và đặt tên là "Hi". <strong>Lưu ý: Phần này không liên quan gì tới ROS package tạo ở phần trước nên bạn nhớ tắt chúng đi.</strong></p>
<p>Trước hết, cài những thư viện dưới đây nếu bạn chưa có:</p>
<pre><code class="lang-python">pip3 install -U tf-nightly
pip3 install -U scikit-learn
pip3 install -U matplotlib
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1672664044490/a621899e-2321-4311-92e8-4ac8f2ee79ba.png" alt="hi_mediapipe" class="image--center mx-auto" /></p>
<p>Mở <a target="_blank" href="https://github.com/TrinhNC/ros_hand_gesture_recognition/blob/main/src/model/keypoint_classifier/keypoint_classifier_label.csv">keypoint_classifier_label.csv</a> trong thư mục <em>src/model/keypoint_classifier.</em> Ở đây có tất cả các nhãn (hiện có 6 lớp) và bạn cần thêm 'Hi' vào dòng cuối cùng như sau:</p>
<pre><code class="lang-bash">Stop
Go
Forward
Backward
Turn Right
Turn Left
Hi
</code></pre>
<p>Tiếp theo, bạn cần ghi (record) dữ liệu và thêm (append) vào tệp <a target="_blank" href="https://github.com/TrinhNC/ros_hand_gesture_recognition/blob/main/src/model/keypoint_classifier/keypoint.csv">keypoint.csv</a> trong thư mục <em>src/model/keypoint_classifier</em>. Nếu bạn mở tệp này, bạn sẽ thấy nó chứa 6410 dòng. Số đầu tiên trong mỗi dòng là class ID (số hiệu của lớp) tương ứng với danh sách trên, ví dụ: "Go" có ID 0, "Stop" có ID 1, "Forward" có ID 2, v.v. Sau đó là 42 số hoặc 21 cặp số biểu thị tọa độ của từng keypoint (tức các đốt ngón tay) đối với điểm gốc là cổ tay. Một điều cần lưu ý là ID trong hình bên dưới là ID của keypoint (từ 0 tới 20) khác với class ID (từ 0 tới 6).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1672665593202/620eaac8-0840-4b8f-9f85-365dc71bf447.png" alt="keypoint_mediapipe" class="image--center mx-auto" /></p>
<p>Để ghi lại dữ liệu, hãy chạy tập lệnh <strong>app.py</strong>:</p>
<pre><code class="lang-bash">python3 app.py
</code></pre>
<p>Nhấn <code>k</code> trên bàn phím và bạn sẽ thấy dòng <code>MODE: Logging Key Point</code> xuất hiện. Sau đó, sử dụng tay phải của bạn để hiển thị dấu✌️. Nhấn và giữ số 6 (class ID của "Hi") bằng tay trái của bạn. Thao tác này sẽ liên tục thêm dữ liệu mới vào tệp <a target="_blank" href="https://github.com/TrinhNC/ros_hand_gesture_recognition/blob/main/src/model/keypoint_classifier/keypoint.csv">keypoint.csv</a> cho đến khi bạn nhả khóa. Bạn cũng có thể thử nhấn và nhả số 6 ngay lập tức và sẽ thấy trong file <a target="_blank" href="https://github.com/TrinhNC/ros_hand_gesture_recognition/blob/main/src/model/keypoint_classifier/keypoint.csv">keypoint.csv</a> có thêm một dòng mới ở cuối bắt đầu bằng số 6 và một danh sách các số tọa tiếp theo sau. Ngoài ra, trong quá trình ghi, nhớ di chuyển tay phải của bạn đến các vị trí khác nhau để làm cho dữ liệu đa dạng hơn.</p>
<p><img src="https://user-images.githubusercontent.com/19979949/210239140-cd5998ca-5937-48f4-9f91-8a86ca10da40.gif" alt="ros-mediapipe-record-dataset" class="image--center mx-auto" /></p>
<p>Sau khi ghi khoảng 10-15 giây, dữ liệu sẽ sẵn sàng và bạn có thể dừng chương trình (bằng phím tắt <em>Ctrl + C</em> trong terminal). Mở notebook <a target="_blank" href="https://github.com/TrinhNC/ros_hand_gesture_recognition/blob/main/src/notebooks/keypoint_classification_EN.ipynb">keypoint_classification_EN.ipynb</a>. Chỉnh sửa <em>dataset</em>, <em>model_save_path</em> và <em>tflite_save_path</em> để khớp với đường dẫn của bạn. Thay đổi <code>NUM_OF_CLASSES</code> thành 7 thay vì 6: <code>NUM_CLASSES = 7</code>. Sau đó chạy notebook từ đầu đến cuối bằng cách ấn nút Run hoặc chạy từng dòng bằng phím tắt <code>Shift + Enter</code>. Quá trình training được thực hiện trong ô [13] và mất khoảng 2-3 phút. Sau đó, bạn có thể khởi chạy <code>my_cam.launch</code> và <code>hand_sign.launch</code> lại như trong Demo bên trên để xem kết quả.</p>
<p><img src="https://user-images.githubusercontent.com/19979949/210258993-de24fcb4-4e1c-44f6-b1c0-6088677c0691.gif" alt class="image--center mx-auto" /></p>
]]></content:encoded></item><item><title><![CDATA[8. Tạo một ROS Subscriber]]></title><description><![CDATA[Trong phần này, mình sẽ hướng dẫn bạn tạo một image subscriber đơn giản (hay "Người In Ấn" trong ví dụ từ phần trước). Subscriber này sẽ liên tục đọc hình ảnh từ topic image_raw (mà bạn đã tạo từ chương 7) và hiển thị chúng. Tất nhiên, bạn có thể làm...]]></description><link>https://robodev.blog/tao-mot-ros-subscriber</link><guid isPermaLink="true">https://robodev.blog/tao-mot-ros-subscriber</guid><category><![CDATA[ROS]]></category><category><![CDATA[robotics]]></category><category><![CDATA[robot-operating-system]]></category><category><![CDATA[vietnamese]]></category><category><![CDATA[Tutorial]]></category><dc:creator><![CDATA[Trinh Nguyen]]></dc:creator><pubDate>Fri, 06 Jan 2023 22:43:03 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1673045228844/994bf77e-bbbc-4d9f-88e8-2ef460b3589b.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Trong phần này, mình sẽ hướng dẫn bạn tạo một image subscriber đơn giản (hay "Người In Ấn" trong ví dụ từ <a target="_blank" href="https://robodev.blog/nhung-khai-niem-co-ban-trong-ros">phần trước</a>). Subscriber này sẽ liên tục đọc hình ảnh từ topic <code>image_raw</code> (mà bạn đã tạo từ <a target="_blank" href="https://robodev.blog/tao-ros-publisher">chương 7</a>) và hiển thị chúng. Tất nhiên, bạn có thể làm được nhiều thứ hơn là chỉ hiển thị ảnh (chẳng hạn như đưa ảnh vào một số thuật toán học máy (Machine Learning) mà bạn sẽ thực hiện trong chương tiếp theo) hoặc thậm chí bạn có thể show ảnh ngay từ publisher bẳng OpenCV nhưng hãy bắt đầu thật cơ bản. Subscriber này chỉ là một ví dụ để bạn thấy được các chương trình trong ROS giao tiếp với nhau như thế nào.</p>
<h1 id="heading-code">Code</h1>
<p>Tạo một tập lệnh Python mới trong thư mục <a target="_blank" href="https://github.com/TrinhNC/my_cam"><em>my_cam</em></a> và đặt tên cho nó là <code>image_subscriber.py</code>. Nếu bạn đã đọc và làm <a target="_blank" href="https://robodev.blog/tao-ros-publisher">chương 7</a> thì code bên dưới sẽ thấy quen vì cấu trúc khá giống với publisher. Mình vẫn khuyên bạn nên làm theo phần giải thích bên dưới và tự code thay vì copy&amp;paste.</p>
<pre><code class="lang-python"><span class="hljs-comment">#!/usr/bin/python3</span>

<span class="hljs-keyword">import</span> rospy
<span class="hljs-keyword">import</span> cv2
<span class="hljs-keyword">from</span> cv_bridge <span class="hljs-keyword">import</span> CvBridge, CvBridgeError
<span class="hljs-keyword">from</span> sensor_msgs.msg <span class="hljs-keyword">import</span> Image

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">callback</span>(<span class="hljs-params">image_msg</span>):</span>
    <span class="hljs-string">"""This function is called to handle the subscribed messages

    Args:
        image_msg (Image): message type Image from sensor_msgs
    """</span>
    <span class="hljs-keyword">try</span>:
        cv_image = bridge.imgmsg_to_cv2(image_msg)
        cv2.imshow(<span class="hljs-string">'ROS Image Subscriber'</span>, cv_image)
        cv2.waitKey(<span class="hljs-number">10</span>)
    <span class="hljs-keyword">except</span> CvBridgeError <span class="hljs-keyword">as</span> error:
        print(error)

<span class="hljs-keyword">if</span> __name__==<span class="hljs-string">"__main__"</span>:
    bridge = CvBridge()
    rospy.init_node(<span class="hljs-string">"image_subscriber"</span>, anonymous=<span class="hljs-literal">True</span>)
    print(<span class="hljs-string">"Subscribe images from topic /image_raw ..."</span>)

    image_subcriber = rospy.Subscriber(<span class="hljs-string">"image_raw"</span>, Image, callback)

    <span class="hljs-keyword">try</span>:
        <span class="hljs-comment"># spin() simply keeps python from exiting until this node is stopped</span>
        rospy.spin()
    <span class="hljs-keyword">except</span> KeyboardInterrupt:
        print(<span class="hljs-string">"Shutting down!"</span>)
</code></pre>
<h1 id="heading-giai-thich-code">Giải thích Code</h1>
<p>Phần đầu tiên giống hệt như trong <a target="_blank" href="https://github.com/TrinhNC/my_cam/blob/main/src/image_publisher.py"><em>image_publisher.py</em></a> và về cơ bản là để import tất cả các thư viện cần thiết.</p>
<pre><code class="lang-python"><span class="hljs-comment">#!/usr/bin/python3</span>

<span class="hljs-keyword">import</span> rospy
<span class="hljs-keyword">import</span> cv2
<span class="hljs-keyword">from</span> cv_bridge <span class="hljs-keyword">import</span> CvBridge, CvBridgeError
<span class="hljs-keyword">from</span> sensor_msgs.msg <span class="hljs-keyword">import</span> Image
</code></pre>
<p>Tiếp theo, một hàm <em>callback</em> được định nghĩa nhưng mình muốn giải thích hàm <em>main</em> trước và đến với hàm <em>callback</em> này sau. Tương tự như publisher, trong <em>main</em> bạn cần tạo một đối tượng CvBridge và khởi tạo một Node có tên <code>image_subscriber</code>.</p>
<pre><code class="lang-python"><span class="hljs-keyword">if</span> __name__==<span class="hljs-string">"__main__"</span>:
    bridge = CvBridge()
    rospy.init_node(<span class="hljs-string">"image_subscriber"</span>, anonymous=<span class="hljs-literal">True</span>)
    print(<span class="hljs-string">"Subscribe images from topic /image_raw ..."</span>)
</code></pre>
<p>Sau đó, một subscriber được khởi tạo bằng cách sử dụng hàm <code>rospy.Subscriber("image_raw", Image, callback)</code> trong đó <code>image_raw</code> là topic mà bạn muốn subscribe, <code>Image</code> là kiểu dữ liệu của <code>image_raw</code> và <code>callback</code> là một hàm sẽ luôn được gọi khi subscriber nhận message mới. Cuối cùng, <code>rospy.spin()</code> được gọi để giữ Node của bạn chạy liên tục cho đến khi nó bị dừng (ví dụ: bằng <em>Ctrl + C</em>).</p>
<pre><code class="lang-python">    image_subcriber = rospy.Subscriber(<span class="hljs-string">"image_raw"</span>, Image, callback)
    <span class="hljs-keyword">try</span>:
        <span class="hljs-comment"># spin() simply keeps python from exiting until this node is stopped</span>
        rospy.spin()
    <span class="hljs-keyword">except</span> KeyboardInterrupt:
        print(<span class="hljs-string">"Shutting down!"</span>)
</code></pre>
<p>Hàm <code>callback</code> luôn được định nghĩa với message là tham số (argument) đầu tiên.</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">callback</span>(<span class="hljs-params">image_msg</span>):</span>
</code></pre>
<p>Bên trong <code>callback</code>, trước hết bạn cần chuyển ROS message <code>image_msg</code> sang định dạng của OpenCV (mình đặt tên nó ở đây là <code>cv_image</code>) bằng cách sử dụng hàm <code>imgmsg_to_cv2</code>. Sau đó, bạn có thể hiển thị hình ảnh bằng cách sử dụng hàm <code>cv2.imshow</code>. Hàm này có hai tham số: thứ nhất là tên cửa sổ (ở đây là <code>ROS Image Subscriber</code>) và thứ hai là hình ảnh (<code>cv_image</code>). Cuối cùng, <code>cv2.waitKey(10)</code> được sử dụng để delay trong một phần nghìn giây (ms) nhất định (ở đây là 10 mili giây) trước khi chuyển sang khung hình tiếp theo.</p>
<pre><code class="lang-python">    <span class="hljs-keyword">try</span>:
        cv_image = bridge.imgmsg_to_cv2(image_msg)
        cv2.imshow(<span class="hljs-string">'ROS Image Subscriber'</span>, cv_image)
        cv2.waitKey(<span class="hljs-number">10</span>)
    <span class="hljs-keyword">except</span> CvBridgeError <span class="hljs-keyword">as</span> error:
        print(error)
</code></pre>
<h1 id="heading-chay-subscriber">Chạy subscriber</h1>
<p>Để chạy file Python này từ terminal, thì bạn cũng cần làm cho nó executable (có thể thực thi được) bằng cách nhấp chuột phải vào file và chọn <strong>Properties &gt; Permissions</strong> và tích <strong>Allow executing file as program</strong> hoặc sử dụng lệnh <code>sudo chmod +x image_subscriber.py</code>. Sau đó, mở 3 terminal hoặc một terminator với 3 terminal nhỏ như bên dưới.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1671896323053/73438b68-d55c-4331-8fc0-16f368b42232.jpeg" alt class="image--center mx-auto" /></p>
<p>Trong terminal thứ nhất, chạy ROS Master:</p>
<pre><code class="lang-bash">roscore
</code></pre>
<p>Trong cái thứ hai, chạy image publisher (nhớ <a target="_blank" href="https://robodev.blog/tao-ros-publisher#heading-ket-noi-camera-trong-vmware">kết nối webcam với máy ảo</a> nếu bạn dùng VMware):</p>
<pre><code class="lang-bash">rosrun my_cam image_publisher.py
</code></pre>
<p>Trong terminal thứ ba, chạy subscriber:</p>
<pre><code class="lang-bash">rosrun my_cam image_subscriber.py
</code></pre>
<p>Sau đó, bạn sẽ thấy một cửa sổ có tiêu đề ROS Image Subscriber hiện lên với hình ảnh truyền trực tiếp từ webcam của bạn giống như của mình trong ảnh trên.</p>
<p>Sau khi hoàn thành chương 7 và 8, giờ bạn đã biết cách code ROS publisher và subscriber. Bạn cũng biết cách lấy hình ảnh từ camera, một trong những cảm biến phổ biến nhất trong chế tạo robot. Trong chương tiếp theo, như đã hứa, mình sẽ giúp bạn sử dụng một trong những công cụ có sẵn để khai thác hình ảnh subscribed được thành những thông tin hữu ích thay vì chỉ xem chúng trên màn hình.</p>
<h1 id="heading-trich-dan">Trích dẫn</h1>
<ol>
<li>Ảnh bìa: <a target="_blank" href="http://wiki.ros.org/jade">http://wiki.ros.org/jade</a></li>
</ol>
]]></content:encoded></item><item><title><![CDATA[7. Tạo một ROS Publisher]]></title><description><![CDATA[Bạn có thể bắt đầu với tutorial Writing a Simple Publisher and Subscriber (Python) trên trang chủ của ROS nhưng nó không thú vị cho lắm vì chỉ in "hello world" trên màn hình của bạn. Trong bài này, bạn sẽ học cách tạo ra Node "Nhiếp Ảnh Gia" từ chươn...]]></description><link>https://robodev.blog/tao-ros-publisher</link><guid isPermaLink="true">https://robodev.blog/tao-ros-publisher</guid><category><![CDATA[ROS]]></category><category><![CDATA[robotics]]></category><category><![CDATA[robot-operating-system]]></category><category><![CDATA[vietnamese]]></category><category><![CDATA[Tutorial]]></category><dc:creator><![CDATA[Trinh Nguyen]]></dc:creator><pubDate>Thu, 05 Jan 2023 22:00:23 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1672955918975/a3fbd3c1-a152-44fa-a4e0-2cb0191cc7d1.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Bạn có thể bắt đầu với tutorial <a target="_blank" href="http://wiki.ros.org/ROS/Tutorials/WritingPublisherSubscriber%28python%29">Writing a Simple Publisher and Subscriber (Python)</a> trên trang chủ của ROS nhưng nó không thú vị cho lắm vì chỉ in "hello world" trên màn hình của bạn. Trong bài này, bạn sẽ học cách tạo ra Node "Nhiếp Ảnh Gia" từ <a target="_blank" href="https://robodev.blog/nhung-khai-niem-co-ban-trong-ros">chương 6</a>. Nếu bạn quên thì Nhiếp Ảnh Gia đại diện cho một Node dùng để đọc và publish hình ảnh từ webcam của bạn.</p>
<h1 id="heading-ket-noi-camera-trong-vmware">Kết nối Camera trong VMWare</h1>
<p>Nếu đang sử dụng máy ảo (Virtual Machine - VM), bạn cần đảm bảo rằng webcam được kết nối với máy ảo thay vì máy chủ. Trong <em>VMWare Workstation Player</em>, chọn <strong>Player &gt; Manage &gt; Virtual Machine Settings...</strong> hoặc <strong>Ctrl + D</strong>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670446255141/k7nve4iHU.jpg" alt class="image--center mx-auto" /></p>
<p>Chọn tab <strong>USB Controller</strong> và chọn <strong>USB 3.1</strong> trong mục <em>USB compatibility.</em></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670447769469/B9RBwGIrx6.jpg" alt class="image--center mx-auto" /></p>
<p>Sau đó, đi tới <strong>Removable Devices</strong>, chọn webcam của bạn (như của mình thì webcam có tên là <em>Sunplus Innovation Integrated_Webcam_HD</em>) và chọn <strong>Connect (Disconnect from host)</strong>. Nhấp vào <em>OK</em> trên bất kỳ hộp thoại bật lên nào sau đó.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670448489176/ng1QkjItE.jpg" alt class="image--center mx-auto" /></p>
<p>Nếu có lỗi hiện lên báo kết nối không thành công thì một mẹo nhỏ là lập tức chuyển sang máy chủ của bạn (trong trường hợp của mình là Windows) và mở webcam của bạn bằng app <em>Camera</em> từ <em>Start</em>. Sau đó quay trở lại máy ảo và bạn sẽ thấy một hộp thoại hiện lên hỏi chọn kết nối webcam với máy chủ hay với máy ảo (xem hình bên dưới). Chọn <strong>Connect to a virtual machine</strong>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670449224068/42BVA0AMm.jpg" alt class="image--center mx-auto" /></p>
<p>Cuối cùng, hãy kiểm tra xem kết nối có thực sự được thiết lập hay không bằng cách vào <strong>Player &gt; Removeable Devices</strong> một lần nữa và bạn sẽ thấy một dấu tích bên cạnh webcam của mình. Nếu không, hãy làm lại các bước trên.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670482874189/00xwQvGRJ.jpg" alt class="image--center mx-auto" /></p>
<h1 id="heading-tao-mot-image-publisher">Tạo một image publisher</h1>
<p>Đầu tiên, trong thư mục <em>my_cam</em>, hãy tạo một tệp mới với tên <em>image_publisher.py</em>. Toàn bộ code có sẵn <a target="_blank" href="https://github.com/TrinhNC/my_cam">tại đây</a> nhưng mình thực sự khuyên bạn làm theo lời giải thích bên dưới và tự code.</p>
<h2 id="heading-code">Code</h2>
<pre><code class="lang-python"><span class="hljs-comment">#!/usr/bin/python3</span>
<span class="hljs-keyword">import</span> rospy
<span class="hljs-keyword">import</span> cv2
<span class="hljs-keyword">from</span> cv_bridge <span class="hljs-keyword">import</span> CvBridge, CvBridgeError
<span class="hljs-keyword">from</span> sensor_msgs.msg <span class="hljs-keyword">import</span> Image

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">publish_image</span>():</span>
    <span class="hljs-string">"""Capture frames from a camera and publish it to the topic /image_raw
    """</span>
    image_pub = rospy.Publisher(<span class="hljs-string">"image_raw"</span>, Image, queue_size=<span class="hljs-number">10</span>)
    bridge = CvBridge()
    capture = cv2.VideoCapture(<span class="hljs-string">"/dev/video0"</span>)

    <span class="hljs-keyword">while</span> <span class="hljs-keyword">not</span> rospy.is_shutdown():
        <span class="hljs-comment"># Capture a frame</span>
        ret, img = capture.read()
        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> ret:
            rospy.ERROR(<span class="hljs-string">"Could not grab a frame!"</span>)
            <span class="hljs-keyword">break</span>
        <span class="hljs-comment"># Publish the image to the topic image_raw</span>
        <span class="hljs-keyword">try</span>:
            img_msg = bridge.cv2_to_imgmsg(img, <span class="hljs-string">"bgr8"</span>)
            image_pub.publish(img_msg)
        <span class="hljs-keyword">except</span> CvBridgeError <span class="hljs-keyword">as</span> error:
            print(error)

<span class="hljs-keyword">if</span> __name__==<span class="hljs-string">"__main__"</span>:
    rospy.init_node(<span class="hljs-string">"my_cam"</span>, anonymous=<span class="hljs-literal">True</span>)
    print(<span class="hljs-string">"Image is being published to the topic /image_raw ..."</span>)
    publish_image()
    <span class="hljs-keyword">try</span>:
        rospy.spin()
    <span class="hljs-keyword">except</span> KeyboardInterrupt:
        print(<span class="hljs-string">"Shutting down!"</span>)
</code></pre>
<h2 id="heading-giai-thich-code">Giải thích Code</h2>
<p>Dòng đầu tiên được gọi là dòng <a target="_blank" href="https://en.wikipedia.org/wiki/Shebang_(Unix)">shebang</a> dùng để xác định vị trí của trình thông dịch (intepreter). Ở đây, Python3 (phiên bản Python mặc định trong Ubuntu 20) được sử dụng và nó nằm trong thư mục <code>/usr/bin</code>.</p>
<pre><code class="lang-python"><span class="hljs-comment">#!/usr/bin/python3</span>
</code></pre>
<p>Tiếp đó, tất cả các thư viện cần thiết cần phải được import (liên kết). <a target="_blank" href="http://wiki.ros.org/rospy">rospy</a> là Python API để tạo các ứng dụng trong ROS bằng Python. <code>cv2</code> là <a target="_blank" href="https://en.wikipedia.org/wiki/OpenCV">OpenCV</a>, một thư viện mã nguồn mở dành cho thị giác máy tính. Ở ví dụ này chúng ta sẽ sử dụng opencv để đọc và hiển thị ảnh từ webcam. <a target="_blank" href="http://wiki.ros.org/cv_bridge">CvBridge</a> được sử dụng để chuyển đổi giữa ROS Image message và OpenCV Image vì chúng không cùng một loại (type) dữ liệu. Cuối cùng, bạn cần import loại message <code>Image</code> (thuộc package <a target="_blank" href="http://wiki.ros.org/sensor_msgs">sensor_msgs</a>) vì dữ liệu được publish ở đây là hình ảnh.</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> rospy
<span class="hljs-keyword">import</span> cv2
<span class="hljs-keyword">from</span> cv_bridge <span class="hljs-keyword">import</span> CvBridge, CvBridgeError
<span class="hljs-keyword">from</span> sensor_msgs.msg <span class="hljs-keyword">import</span> Image
</code></pre>
<p>Tiếp theo, tạo một hàm có tên <code>publish_image</code>. Trong hàm này, tạo một publisher tên <code>image_raw</code> có kiểu dữ liệu là <code>Image</code> mà mình đã nói ở trên và <code>queue_size</code> là 10 (kích thước của hàng đợi). Một ví dụ cho dễ hiểu khái niệm <code>queue_size</code> là nếu bạn publish hình ảnh ở tốc độ 20 Hz (hoặc 20 khung hình mỗi giây) nhưng <em>queue_size</em> của bạn bằng 10 thì chỉ có 10 hình ảnh được xử lý (đặt vào hàng đợi) trong khi 10 cái còn lại sẽ bị loại bỏ. Trong ví dụ của chúng ta thì bạn không phải quan tâm nhiều đến nó vì bạn chỉ phải xử lý một message. Đây là một bài viết hay về cách <a target="_blank" href="http://wiki.ros.org/rospy/Overview/Publishers%20and%20Subscribers#Choosing_a_good_queue_size">chọn queue_size thích hợp</a>.</p>
<pre><code class="lang-python">image_pub = rospy.Publisher(<span class="hljs-string">"image_raw"</span>, Image, queue_size=<span class="hljs-number">10</span>)
</code></pre>
<p>Tạo một object CvBridge:</p>
<pre><code class="lang-python">bridge = CvBridge()
</code></pre>
<p>Tạo <a target="_blank" href="https://docs.opencv.org/4.x/dd/d43/tutorial_py_video_display.html">video capture object</a> giúp bạn đọc hình ảnh từ webcam của mình. <code>/dev/video0</code> là tên thiết bị. Nếu bạn sử dụng một máy ảnh ngoài mà không phải webcam tích hợp trong laptop thì tên này có thể khác. Bạn có thể kiểm tra bằng cách sử dụng lệnh sau: <code>ls -ltrh /dev/video*</code>. Trong trường hợp của mình, nó liệt kê cả <code>/dev/video0</code> và <code>/dev/video1</code> nhưng chỉ <code>/dev/video0</code> hoạt động.</p>
<pre><code class="lang-python">capture = cv2.VideoCapture(<span class="hljs-string">"/dev/video0"</span>)
</code></pre>
<p>Sau đó, tạo một while loop. Vòng lặp này luôn kiểm tra xem Node có đang chạy hay không bằng cách check <code>rospy.is_shutdown()</code>. Nó bị ngắt nếu có gián đoạn, ví dụ như bạn dừng chương trình bằng cách sử dụng <em>Ctrl + C</em>. <code>capture.read()</code> trả về một biến boolean <code>ret</code> (<code>True</code> nếu ảnh được đọc chính xác và <code>false</code> nếu không) và một hình ảnh <code>img</code>. Ảnh này được mã hóa trong OpenCV ở định dạng 3 kênh (channel) Blue, Green và Red (hay BGR) và mỗi kênh là một ma trận (một mảng 2 chiều). Trong <a target="_blank" href="https://robodev.blog/tao-ros-publisher#heading-phu-luc">Phụ lục</a>, bên dưới, mình giải thích chi tiết hơn cách một hình ảnh được mã hóa.</p>
<p>Nếu <code>ret</code> trả về False thì đã có lỗi (nhiều khả năng là không thể kết nối được với webcam). Bạn cần check trong terminal xem lỗi là gì và sửa. Trước khi có thể publish ảnh, bạn cần chuyển đổi nó thành ROS message dạng <code>img_msg</code> bằng hàm <code>cv2_to_imgmsg</code> (từ <code>CvBridge</code>) với mã hóa màu <code>"bgr8"</code>(để những subscriber biết thứ tự màu là gì, trong trường hợp này là blue-green-red và 8-bit). Cuối cùng, bạn dùng publisher <code>image_pub</code> mà bạn đã tạo để publish message <code>img_msg</code> bằng hàm <code>publish</code>.</p>
<pre><code class="lang-python">    <span class="hljs-keyword">while</span> <span class="hljs-keyword">not</span> rospy.is_shutdown():
        <span class="hljs-comment"># Capture a frame</span>
        ret, img = capture.read()
        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> ret:
            rospy.ERROR(<span class="hljs-string">"Could not grab a frame!"</span>)
            <span class="hljs-keyword">break</span>
        <span class="hljs-comment"># Publish the image to the topic image_raw</span>
        <span class="hljs-keyword">try</span>:
            img_msg = bridge.cv2_to_imgmsg(img, <span class="hljs-string">"bgr8"</span>)
            image_pub.publish(img_msg)
        <span class="hljs-keyword">except</span> CvBridgeError <span class="hljs-keyword">as</span> error:
            print(error)
</code></pre>
<p>Trong hàm <code>main</code>, trước tiên bạn khởi tạo một Node có tên là <code>my_cam</code>. Lưu ý: trong ROS, <strong>các Node được đặt tên duy nhất</strong>. Nếu hai Node có cùng tên được khởi tạo, thì Node khởi tạo trước sẽ bị tắt. Biến <code>anonymous</code> được đặt <code>True</code> để <code>rospy</code> sẽ chọn một tên duy nhất cho Node publisher của chúng ta (bằng cách thêm các số ngẫu nhiên vào cuối tên) để nhiều publihser có thể chạy đồng thời.</p>
<pre><code class="lang-python"><span class="hljs-keyword">if</span> __name__==<span class="hljs-string">"__main__"</span>:
    rospy.init_node(<span class="hljs-string">"my_cam"</span>, anonymous=<span class="hljs-literal">True</span>)
    print(<span class="hljs-string">"Image is being published to the topic /image_raw ..."</span>)
    publish_image()
    <span class="hljs-keyword">try</span>:
        rospy.spin()
    <span class="hljs-keyword">except</span> KeyboardInterrupt:
        print(<span class="hljs-string">"Shutting down!"</span>)
</code></pre>
<p>Sau khi khởi tạo Node, hàm <code>publish_image</code> được gọi. Và bước cuối cùng là gọi <code>rospy.spin()</code>, thao tác này chỉ đơn giản là giữ Node của bạn không tắt cho đến khi Node bị dừng (ví dụ: bằng <em>Ctrl + C</em>). Và thế là xong! Publisher của bạn đã sẵn sàng để chạy.</p>
<h1 id="heading-chay-publisher">Chạy Publisher</h1>
<p>Để chạy <em>image_publisher.py</em> thì bạn phải làm cho nó execuatble (có thể thực thi) bằng cách sử dụng lệnh</p>
<pre><code class="lang-bash">sudo chmod +x image_publisher.py
</code></pre>
<p>hoặc nhấp chuột phải vào tệp, chọn <em>Properties &gt; Permissions &gt; Allow executing files as program</em>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670879724888/n8DziyKWL.jpg" alt class="image--center mx-auto" /></p>
<p>Sau đó, mở 2 terminal hoặc một terminator với 2 terminal. Trong terminal đầu tiên, chạy <code>roscore</code> để bắt đầu ROS Master. Trong terminal thứ hai, chạy <code>rosrun my_cam image_publisher.py</code> trong đó <code>my_cam</code> là tên gói và <code>image_publisher.py</code> là script. Một cách khác để chạy publisher là dùng <code>roslaunch</code> mình có đề cập ở phần <a target="_blank" href="https://robodev.blog/tao-ros-publisher#heading-chay-node-bang-roslaunch">Phụ lục</a> bên dưới.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670880437417/2wCd3vorL.jpg" alt class="image--center mx-auto" /></p>
<h2 id="heading-hien-thi-hinh-anh-duoc-publish">Hiển thị hình ảnh được publish</h2>
<p>Bạn sẽ thấy đèn LED bên cạnh webcam sáng lên. Và nếu bạn mở một terminal khác và chạy: <code>rostopic list</code>, bạn sẽ thấy topic <code>/image_raw</code> được liệt kê. Bạn thậm chí có thể nhìn thấy những gì bên trong <code>/image_raw</code> bằng cách chạy lệnh này: <code>rostopic echo /image_raw</code> và nó sẽ in ra các mảng số đại diện cho hình ảnh. Để hiển thị hình ảnh, bạn cần viết một image subscriber (hay Người In Ấn trong ví dụ trước) mà chúng ta sẽ thực hiện trong <a target="_blank" href="https://robodev.blog/write-a-ros-subscriber">chương tiếp theo</a> TODO. ROS cũng cung cấp một công cụ để trực quan hóa các loại dữ liệu được publish trong đó có hình ảnh. Chạy <code>rqt</code> trong một terminal và một cửa sổ sẽ hiện ra. Tìm đến <em>Plugin &gt; Visualization &gt; Image View</em>, sau đó chọn <em>/image_raw</em> trong danh sách topic và bạn sẽ thấy hình ảnh trực tiếp từ webcam của bạn.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670880937685/PrS86GF2k.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-phu-luc">Phụ lục</h1>
<h2 id="heading-cach-may-tinh-nhin-mot-buc-anh">Cách máy tính "nhìn" một bức ảnh</h2>
<p>Trong thị giác máy tính, một bức ảnh thường được biểu thị bằng <a target="_blank" href="https://en.wikipedia.org/wiki/RGB_color_model">Mô hình màu RGB</a>. RGB là viết tắt của Red, Green và Blue, 3 màu cơ bản mà từ đó bạn có thể tạo ra bất kỳ màu nào khác.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1672004602847/6724a545-2555-4ac1-86f1-b684dbcacde3.png" alt class="image--center mx-auto" /></p>
<p>Mỗi kênh màu (color channel) được mã hóa trong một ma trận pixel (điểm ảnh). Nếu các pixel ở dạng 8 bit, thì giá trị của chúng nằm trong khoảng từ 0 đến 255 (hoặc 2^8 - 1). Trong ví dụ bên dưới, bạn có thể thấy cách một bức ảnh (với chiều cao 140 pixel và chiều rộng 140 pixel) được phân tách thành 3 kênh khác nhau và mỗi kênh thực chất là một ma trận 140x140. Các kênh này được kết hợp thành một ma trận 3D mà mỗi phần tử chứa các pixel Blue (Xanh lam), Green (Xanh lục), Red (Đỏ). Thứ tự BGR này là thứ tự màu mặc định của OpenCV và nó có thể khác với các thư viện khác.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1672004523101/808080f8-6966-4d21-8b3c-c7527096d0f0.jpeg" alt class="image--center mx-auto" /></p>
<h2 id="heading-chay-node-bang-roslaunch">Chạy Node bằng roslaunch</h2>
<p>Ngoài <code>rosrun</code>, một cách phổ biến được dùng nhiều hơn để chạy Node là <code>roslaunch</code>. Những hạn chế của <code>rosrun</code> so với <code>roslaunch</code>:</p>
<ul>
<li><p><code>rosrun</code> chỉ có thể chạy một lúc một Node từ một package trong khi <code>roslaunch</code> có thể chạy nhiều Node từ nhiều package.</p>
</li>
<li><p><code>roslaunch</code> tự khởi động <code>roscore</code> (nếu chưa có) còn <code>rosrun</code> thì không.</p>
</li>
<li><p>Để dùng được <code>roslaunch</code> cần một tệp <em>.launch</em> và người dùng có thể tùy biến file này như gọi các launch file khác, hoặc thay đổi biến của các hàm, v.v trong khi <code>rosrun</code> không có lựa chọn này.</p>
</li>
</ul>
<p>Một tệp <em>.launch</em> thực chất là một tệp XML nên cách tạo cũng khá dễ dàng. Đầu tiên bạn tạo một folder tên <em>launch</em> rồi tạo một file text đặt tên là <em>my_cam.launch.</em> Nội dung của file này <a target="_blank" href="https://github.com/TrinhNC/my_cam/blob/main/launch/my_cam.launch">như sau</a>:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">launch</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">arg</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"camera_name"</span> <span class="hljs-attr">default</span>=<span class="hljs-string">"/dev/video0"</span>/&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">node</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"my_webcam"</span> <span class="hljs-attr">pkg</span>=<span class="hljs-string">"my_cam"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"image_publisher_launch.py"</span> <span class="hljs-attr">output</span>=<span class="hljs-string">"screen"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">param</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"camera_name"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"string"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"$(arg camera_name)"</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">node</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">launch</span>&gt;</span>
</code></pre>
<p><strong>Giải thích</strong>: Một tệp launch bắt đầu bằng tag <code>&lt;launch&gt;</code> và kết thúc bằng tag <code>&lt;/launch&gt;</code>. Đầu tiên mình tạo một biến (argument) có tên <code>camera_name</code> và giá trị mặc định của nó là <code>/dev/video0</code>. Mình sẽ giải thích cách dùng của nó ở bên dưới. Khi muốn dùng biến này trong launch file thì gọi nó bằng <code>$(arg camera_name)</code> như ở dòng thứ 4. Dòng tiếp theo định nghĩa Node mà mình muốn chạy, trong đó <code>name</code> là tên của Node (tên này sẽ được dùng nếu nó khác với tên bạn đặt trong tệp Python), <code>pkg</code> là tên package (ở đây là <code>my_cam</code>), <code>type</code> là tên file (ở đây là <code>image_publisher_launch.py</code>), <code>output</code> là để chọn cách hiển thị các thông tin (nếu là <code>screen</code> thì thông tin sẽ được print ra màn hình, còn là <code>log</code> thì sẽ được lưu trong một file log). Cuối cùng, tạo một tham số (parameter) có tên <code>camera_name</code> với type <code>string</code> và giá trị là giống với biến <code>camera_name</code> bên trên. Bạn có thể sử dụng tham số này trong code và vì thế không cần phải sửa code mỗi khi cần thay đổi một thông tin nào đó. Cụ thể, mình đã tạo một file <a target="_blank" href="https://github.com/TrinhNC/my_cam/blob/main/src/image_publisher_launch.py">image_publisher_launch.py</a>. File này nội dung gần giống y hệt như <a target="_blank" href="https://github.com/TrinhNC/my_cam/blob/main/src/image_publisher.py">image_publisher.py</a> chỉ khác ở chỗ mình cho nó thành một class và khi khởi tạo class thì có dòng:</p>
<pre><code class="lang-python">self.capture = cv2.VideoCapture(rospy.get_param(<span class="hljs-string">"my_webcam/camera_name"</span>))
</code></pre>
<p>Ở đây thay vì cho một giá trị mặc định như trước thì bạn dùng hàm <code>rospy.get_param</code> để lấy tham số từ launch file. Vì thế nếu ví dụ bạn dùng một camera có tên khác, thì bạn chỉ cần thay đổi launch file. Hoặc cách hay hơn là khi chạy <code>roslaunch</code> bạn có thể dùng biến (argument) đã được tạo ở đây <code>&lt;arg name="camera_name" default="/dev/video0"/&gt;</code></p>
<p>Chạy <code>roslaunch</code>:</p>
<pre><code class="lang-bash">roslaunch my_cam my_cam.launch camera_name:=<span class="hljs-string">"/dev/video0"</span>
</code></pre>
<p>Nếu bạn sử dụng giá trị mặc định của các biến bạn chỉ cần chạy <code>roslaunch my_cam my_cam.launch</code>. Nhưng nếu bạn dùng một camera khác hoặc tên của nó không phải là <code>/dev/video0</code> thì bạn chỉ cần thay đổi tên ở lệnh bên trên. Rất tiện lợi đúng không?</p>
<p>Ngoài ra, còn nhiều tag khác mà mình không list ra được hết. Các bạn có thể tham khảo thêm <a target="_blank" href="http://wiki.ros.org/roslaunch/XML">ở đây</a> và <a target="_blank" href="http://wiki.ros.org/roslaunch">ở đây</a>.</p>
<h1 id="heading-trich-dan">Trích dẫn</h1>
<ol>
<li>Ảnh bìa: <a target="_blank" href="http://wiki.ros.org/hydro">http://wiki.ros.org/hydro</a></li>
</ol>
]]></content:encoded></item><item><title><![CDATA[6. Những Khái Niệm Cơ Bản trong ROS]]></title><description><![CDATA[Trong phần này, mình sẽ giúp bạn hiểu các khái niệm cơ bản của ROS: Master, Node, Topic, Publisher, Subscriber và Service. Sau đó, bạn sẽ tự code những khái niệm này và hoàn thành package my_cam mà chúng ta đã tạo trong chương trước. Trước tiên , hãy...]]></description><link>https://robodev.blog/nhung-khai-niem-co-ban-trong-ros</link><guid isPermaLink="true">https://robodev.blog/nhung-khai-niem-co-ban-trong-ros</guid><category><![CDATA[ROS]]></category><category><![CDATA[robot-operating-system]]></category><category><![CDATA[Tutorial]]></category><category><![CDATA[robotics]]></category><category><![CDATA[vietnamese]]></category><dc:creator><![CDATA[Trinh Nguyen]]></dc:creator><pubDate>Thu, 05 Jan 2023 15:34:46 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1672932529926/51811128-5f7e-4256-afc2-b47d8358d8da.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Trong phần này, mình sẽ giúp bạn hiểu các khái niệm cơ bản của ROS: Master, Node, Topic, Publisher, Subscriber và Service. Sau đó, bạn sẽ tự code những khái niệm này và hoàn thành package <em>my_cam</em> mà chúng ta đã tạo trong <a target="_blank" href="https://robodev.blog/ros-package-va-workspace-la-gi">chương trước.</a> Trước tiên , hãy bắt đầu với <a target="_blank" href="http://wiki.ros.org/ROS/Tutorials/UnderstandingNodes">ROS Node</a>.</p>
<h1 id="heading-ros-master-va-node">ROS Master và Node</h1>
<p>Để dễ hiểu khái niệm Node, hãy tưởng tượng ra một câu chuyện mà trong đó node của chúng ta là một Nhiếp Ảnh Gia mới bắt đầu làm việc cho một tạp chí. Vào ngày đầu tiên đi làm, anh ta chụp một số bức ảnh và thông báo cho Sếp của mình rằng anh sẽ để những bức ảnh này trên một chiếc Bàn ở giữa văn phòng. Ông Sếp cảm ơn anh ta vì đã thông báo.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670164679323/_aQsrv102.jpg" alt="ros_master_publisher.jpg" class="image--center mx-auto" /></p>
<p>Trong câu chuyện, ông Sếp đại diện cho <a target="_blank" href="http://wiki.ros.org/Master">ROS Master</a>, thứ mà bạn luôn phải có khi sử dụng ROS. <strong>ROS Master</strong> cung cấp các dịch vụ <strong>đặt tên</strong> và <strong>đăng ký (naming</strong> and <strong>registration)</strong> cho các node còn lại trong hệ thống ROS. Nó theo dõi việc publish (truyền) và subscribe (nhận) của các node cũng như các topic và service (xem thêm bên dưới). ROS Master được gọi bằng lệnh <code>roscore</code>. Chỉ có một ROS Master được phép chạy tại một thời điểm. Nếu bạn thử gọi 2 hoặc nhiều cái cùng một lúc, sẽ có báo lỗi. Nhiếp Ảnh Gia đại diện cho một <strong>ROS Node</strong> thực hiện một tác vụ nào đó (trong trường hợp này là lấy ảnh và đặt lên Bàn) và luôn phải đăng ký (register) với Master khi nó được khởi tạo cũng như Nhiếp Ảnh Gia luôn phải báo cáo với Sếp.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670143511505/lwy-FOAdn.png" alt="roscore.jpg" class="image--center mx-auto" /></p>
<h1 id="heading-ros-topic-va-message">ROS Topic và Message</h1>
<p>Các bức ảnh là <a target="_blank" href="http://wiki.ros.org/Messages">ROS Message</a> (có thể dịch là tin nhắn hoặc dữ liệu) và chúng thuộc loại <a target="_blank" href="http://wiki.ros.org/sensor_msgs">sensor_msgs</a>. Đó là lý do tại sao bạn phải bao gồm <em>sensor_msgs</em> khi tạo gói từ <a target="_blank" href="https://robodev.blog/ros-package-va-workspace-la-gi">chương trước</a>. Hành động để ảnh trên Bàn của Nhiếp Ảnh Gia được gọi là <strong>publish</strong> (truyền, xuất bản) trong ROS. Cái bàn đóng vai trò là một <a target="_blank" href="http://wiki.ros.org/Topics">ROS Topic</a> mà các Node khác có thể <strong>subscribe</strong> (nhận, đăng ký).</p>
<p><em>Subscribe</em> là gì? Bây giờ hãy tưởng tượng có một người khác (tức là một Node khác) trong câu chuyện của chúng ta có tên là Người In Ấn. Anh chàng này muốn lấy ảnh từ cái Bàn và in chúng ra. Anh ta sẽ phải làm gì? Việc đầu tiên dĩ nhiên anh ta phải thông báo (notify) cho Sếp và sau đó nếu có ảnh trên Bàn, anh ta sẽ lấy và in chúng. Hành động lấy ảnh từ Bàn được gọi là <strong>Subscribe</strong> trong ROS.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670144596242/971gYqiIl.jpg" alt="ros_master_subscriber.jpg" class="image--center mx-auto" /></p>
<p>Sau khi đã thông báo cho Sếp, cả Nhiếp Ảnh Gia và Người In Ấn đều biết về nhau và giờ họ chỉ việc trao/nhận ảnh cho nhau.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670276541104/v5KUWPlav.jpg" alt class="image--center mx-auto" /></p>
<h1 id="heading-ros-service">ROS Service</h1>
<p>Ở phần trước, Nhiếp Ảnh Gia liên tục publish ảnh trong khi Người In Ấn liên tục subscribe những ảnh này. Người In Ấn nói bây giờ chỉ khi nào anh ta yêu cầu thì Nhiếp Ảnh Gia mới chụp ảnh và gửi cho anh ta. Việc request/reply (yêu cầu/phản hồi) này là ý tưởng cơ bản đằng sau <a target="_blank" href="http://wiki.ros.org/Services">ROS Service</a><strong>.</strong> Trong ví dụ này, sau khi cả hai đã register (tức là thông báo cho Sếp), Người In Ấn bây giờ đóng vai trò Client (khách) gửi yêu cầu đến Nhiếp Ảnh Gia người đóng vai trò Server (chủ). Sau khi nhận được yêu cầu, Nhiếp Ảnh Gia sẽ chụp ảnh và gửi cho Người In Ấn (thực ra Nhiếp Ảnh Gia vẫn chỉ để chúng trên Bàn như mọi khi).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670307838439/THveFzP9Y.png" alt /></p>
<p>Bạn vừa tìm hiểu tất cả các khái niệm cốt lõi của ROS. Tất nhiên, còn rất nhiều khái niệm nữa nhưng đối với người mới bắt đầu, đây đã là một bước khá dài rồi. Trong những phần tiếp theo, bạn sẽ học cách tự code các khái niệm này và chạy ứng dụng ROS đầu tiên của mình.</p>
<h1 id="heading-tom-tat">Tóm tắt</h1>
<ol>
<li><p><strong>ROS Master</strong> cung cấp các dịch vụ <strong>đặt tên</strong> và <strong>đăng ký (naming</strong> and <strong>registration)</strong> cho các node còn lại trong hệ thống ROS. Nó theo dõi việc publish (truyền) và subscribe (nhận) của các node cũng như các topic và service.</p>
</li>
<li><p><strong>ROS</strong> <strong>Node</strong> là một quá trình thực hiện tác vụ cụ thể. Các Node có thể giao tiếp <strong>Message</strong> (dữ liệu) với nhau bằng cách sử dụng <strong>publish/subscribe</strong> (truyền/nhận liên tục) hoặc <strong>Service</strong> (máy khách/máy chủ).</p>
</li>
</ol>
<h1 id="heading-trich-dan">Trích dẫn</h1>
<ol>
<li><p>Ảnh bìa: <a target="_blank" href="http://wiki.ros.org/fuerte">http://wiki.ros.org/fuerte</a></p>
</li>
<li><p>Biểu tượng: <a target="_blank" href="https://www.flaticon.com/">https://www.flaticon.com/</a></p>
</li>
</ol>
]]></content:encoded></item><item><title><![CDATA[5. ROS Package và Workspace là gì?]]></title><description><![CDATA[Phần mềm trong ROS được sắp xếp thành các packages - gói dữ liệu. Một package là một thư mục chứa các chương trình (executable files) và tệp hỗ trợ phục vụ cho một mục đích cụ thể. Ví dụ: bạn có thể có một package để đọc và xử lý hình ảnh từ camera. ...]]></description><link>https://robodev.blog/ros-package-va-workspace-la-gi</link><guid isPermaLink="true">https://robodev.blog/ros-package-va-workspace-la-gi</guid><category><![CDATA[ROS]]></category><category><![CDATA[robot-operating-system]]></category><category><![CDATA[Tutorial]]></category><category><![CDATA[robotics]]></category><dc:creator><![CDATA[Trinh Nguyen]]></dc:creator><pubDate>Thu, 05 Jan 2023 13:48:05 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1672926344626/37899909-d21b-4ee3-814c-6105fc357e07.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Phần mềm trong ROS được sắp xếp thành các <a target="_blank" href="http://wiki.ros.org/Packages#:~:text=A%20ROS%20package%20is%20simply,and%20the%20unit%20of%20release.">packages</a> - gói dữ liệu. <strong>Một package là một thư mục chứa các chương trình (executable files) và tệp hỗ trợ phục vụ cho một mục đích cụ thể</strong>. Ví dụ: bạn có thể có một package để đọc và xử lý hình ảnh từ camera. Sau khi build (biên dịch) package, bạn có thể gọi hoặc test chương trình của mình trong một terminal. <strong>Các ROS package được đặt trong một thư mục khác gọi là <em>catkin workspace</em></strong> vì chúng được build bằng các công cụ <em>catkin</em>. Các khái niệm này sẽ được giải thích kỹ hơn ở bên dưới. Ngoài ra, mình sẽ giữ các thuật ngữ bằng tiếng Anh vì dịch ra cũng không được chuẩn nghĩa và sẽ tốt hơn nếu bạn nhớ chúng để khi đọc các tài liệu khác bằng tiếng Anh cũng không bị bỡ ngỡ.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1669564141260/GaJNou4S2.png" alt="catkin_workspace.jpg" class="image--center mx-auto" /></p>
<p>Trong ví dụ ở hình bên trên, có 3 package tên: <em>hand_gesture_recognition</em>, <em>my_cam</em> và <em>ros_mobile_robot</em> được đặt trong thư mục <em>src</em> bên trong thư mục workspace có tên <em>catkin_ws</em>. Các thư mục khác: <em>build, devel,</em> và <em>logs</em> được tạo tự động sau khi các package này được build. Tóm lại, package và workspace trong ROS thực chất là các thư mục với một vài yêu cầu cụ thể mà mình sẽ đề cập bên dưới.</p>
<h1 id="heading-tao-mot-catkin-workspace">Tạo một catkin workspace</h1>
<p>Theo <a target="_blank" href="http://wiki.ros.org/catkin/conceptual_overview">trang web chính thức của ROS</a>, "Cái tên <em>catkin</em> xuất phát từ cụm hoa hình đuôi được tìm thấy trên cây liễu -- ám chỉ đến Nhà để xe Willow nơi catkin được tạo ra." Nếu bạn muốn biết thêm về nó, hãy đọc <a target="_blank" href="http://wiki.ros.org/catkin/conceptual_overview">bài này</a> còn hiện tại, bạn chỉ cần nhớ rằng <em>catkin</em> là hệ thống build chính thức của ROS.</p>
<p>Một workspace như đã nói ở trên là một thư mục, vì vậy bạn khởi tạo nó đơn giản bằng cách tạo một thư mục mới. Bạn có thể nhấp chuột phải vào thư mục Home (nơi bạn có thể dễ dàng truy cập thư mục đó) và chọn <strong>New Folder</strong> hoặc sử dụng phím tắt <strong>Ctrl + Shift + N</strong>. Sau đó, bạn cũng cần tạo một thư mục <strong>src</strong> bên trong thư mục workspace. Vì đang dùng Linux, hãy làm việc này bằng cách chạy lệnh dưới đây trong một terminal:</p>
<pre><code class="lang-bash">mkdir -p ~/catkin_ws/src
</code></pre>
<p><code>mkdir</code> = <strong>m</strong>a<strong>k</strong>ing <strong>dir</strong>ectory (tạo thư mục mới), <code>-p</code> = parents và <code>~</code> là viết tắt của thư mục Home. Lệnh này sẽ tạo một thư mục có tên <strong>catkin_ws</strong> (bạn có thể đặt một tên khác nếu muốn) với một thư mục con có tên <strong>src</strong>. Khá tiện đúng không? Sau đó, thay đổi đường dẫn tới workspace trong terminal hiện tại bằng cách sử dụng lệnh <code>cd</code> (<code>cd</code> = <strong>c</strong>hange <strong>d</strong>irectory):</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> ~/catkin_ws
</code></pre>
<p>Mặc dù hiện tại không có gói nào trong workspace, bạn vẫn có thể build nó bằng cách sử dụng lệnh <code>catkin_make</code> hoặc <code>catkin build</code>. Mình thích <code>catkin build</code> hơn vì với <code>catkin_make</code>, nó luôn tự động build tất cả các package nên sẽ mất nhiều thời gian nếu có nhiều package trong workpsace. Với <code>catkin build</code>, có thể build từng package riêng lẻ nếu muốn. Nhớ chạy <code>source ~/catkin_ws/build/setup.bash</code> trước nếu bạn chưa chạy khi mở terminal hoặc chưa đặt lệnh này trong <code>.bashrc</code>.</p>
<pre><code class="lang-bash">catkin build
</code></pre>
<p>Sau khi build xong, terminal của bạn sẽ trông giống như ảnh bên dưới và ba thư mục <em>build, devel, logs</em> cũng được tạo.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1669582961614/js7dhqwIs.png" alt="build_catkin_ws.jpg" class="image--center mx-auto" /></p>
<h1 id="heading-tao-mot-ros-package">Tạo một ROS package</h1>
<p>ROS package cũng là một thư mục được đặt trong thư mục <strong>src</strong> của workspace. Tuy nhiên, có một số yêu cầu bắt buộc phải có trong một package (<a target="_blank" href="http://wiki.ros.org/catkin/Tutorials/CreatingPackage">nguồn</a>):</p>
<ul>
<li><p>Tệp <strong>package.xml</strong> cung cấp các thông tin về package.</p>
</li>
<li><p>Tệp <strong>CMakeLists.txt</strong> dùng cho việc build.</p>
</li>
<li><p>Ngoài ra, mỗi package phải có thư mục riêng, nghĩa là không có package lồng nhau hoặc nhiều package chia sẻ cùng một thư mục.</p>
</li>
</ul>
<p>Vậy tạo hai tệp <em>package.xml</em> và <em>CMakeLists.txt</em> như thế nào? Chúng chính xác là gì và tại sao chúng ta cần cả hai? Hãy tìm câu trả lời bằng cách thử tạo một package. May mắn là đã có lệnh <code>catkin_create_pkg</code> để làm việc này. Giả sử bạn muốn tạo một gói có tên <em>my_cam</em> để đọc hình ảnh từ webcam và publish những hình ảnh đó (định nghĩa về <code>publish</code> sẽ có trong <a target="_blank" href="https://robodev.blog/nhung-khai-niem-co-ban-trong-ros">chương tiếp theo</a>). Mở terminal và hướng nó tới <code>~/catkin_ws/src</code>:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> ~/catkin_ws/src
</code></pre>
<p>, sau đó chạy</p>
<pre><code class="lang-bash">catkin_create_pkg my_cam rospy sensor_msgs
</code></pre>
<p>Thao tác này sẽ tạo một package (tức là thư mục) <strong>my_cam</strong> với hai thành phần phụ thuộc <em>rospy</em> và <em>sensor_msgs</em> (mình sẽ giải thích chúng là gì trong <a target="_blank" href="https://robodev.blog/nhung-khai-niem-co-ban-trong-ros">chương tiếp theo</a>). Bên trong <em>my_cam</em> có 2 tệp <strong>package.xml</strong> và <strong>CMakeLists.txt</strong> và một thư mục <strong>src</strong> như bên dưới.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1669703699384/xNzuMzmuQ.png" alt="catkin_create_pkg_example.jpg" class="image--center mx-auto" /></p>
<h2 id="heading-ben-trong-packagexml-va-cmakeliststxt">Bên trong <em>package.xml</em> và <em>CMakeLists.txt</em></h2>
<p>Mở file <em>package.xml</em> và bạn sẽ thấy tất cả thông tin về package như tên của người bảo trì, loại giấy phép và các thành phần phụ thuộc, v.v. Rất nhiều dòng là những comment (ví dụ: đây là một comment: <code>&lt;!-- The *depend tags are used to specify dependencies --&gt;</code>). Trong hình dưới đây, mình đã xóa tất cả các comment:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1669788948395/6kwdcEPXv.png" alt="ros_package_xml.jpg" class="image--center mx-auto" /></p>
<p>Đối với những bạn chưa từng làm việc với <a target="_blank" href="https://en.wikipedia.org/wiki/XML">XML</a>, đây là một ngôn ngữ đánh dấu (markup language) tương tự như [HTML](https://vi.wikipedia.org/ wiki/HTML) và nó mô tả các thành phần bằng <em>thẻ</em> (tag*)*. Chẳng hạn, dòng <code>&lt;name&gt;my_cam&lt;/name&gt;</code> có nghĩa là <em>tên package</em> được định nghĩa là <code>my_cam</code> bằng cách sử dụng thẻ mở <code>&lt;name&gt;</code> và thẻ đóng <code>&lt;/name&gt;</code>.</p>
<p>Tiếp theo, mở <em>CMakeLists.txt</em>. Nó cũng có rất nhiều comment (bắt đầu bằng <code>#</code>) chủ yếu là để hướng dẫn/gợi ý. Nếu bạn đã từng làm việc với C/C++, thì bạn đã biết rằng <a target="_blank" href="https://www.jetbrains.com/help/clion/cmakelists-txt-file.html">CMakeLists</a> được sử dụng cho các chương trình như <a target="_blank" href="https://cmake.org/">CMake</a> (catkin cũng dùng CMake) để tìm và liên kết tất cả các thư viện cần thiết để build. Tại dòng <code>find_package</code>, bạn có thể thấy hai phụ thuộc <code>rospy</code> và <code>sensor_msgs</code>, mà chúng ta đã khai báo từ lệnh trên. Nếu bạn quên khai báo chúng, chỉ cần mở <code>CMakeLists</code> lên và chỉnh sửa.</p>
<pre><code class="lang-bash">find_package(catkin REQUIRED COMPONENTS
  rospy
  sensor_msgs
)
</code></pre>
<h2 id="heading-su-khac-biet-giua-packagexml-va-cmakeliststxt">Sự khác biệt giữa <em>package.xml</em> và <em>CMakeLists.txt</em></h2>
<p>Bạn có thể nhận thấy cả <em>package.xml</em> và <em>CMakeLists.txt</em> có nhiều phần thông tin giống nhau như tên dự án, thự viện liên quan, v.v. Có một <a target="_blank" href="https://answers.ros.org/question/217475/cmakeliststxt-vs-packagexml/">câu trả lời hay</a> về sự khác biệt và lý do tại sao chúng ta cần cả hai. Bạn chỉ cần nhớ:</p>
<ul>
<li><p>package.xml chứa những thông tin (tác giả, người bảo trì, url, mô tả và giấy phép) không nhất thiết cần thiết để build (hoặc chạy) một package, nhưng vẫn cần để ROS tìm kiếm các thông tin về package (vì thông tin trong CMakeLists không dễ dàng đọc nếu không có CMake). Ngoài ra nó cũng cần cho việc hiển thị thông tin của package trên ROS wiki.</p>
</li>
<li><p>CMakeLists.txt là build script được dùng cho các chương trình như <a target="_blank" href="https://cmake.org/">CMake</a> để tìm và liên kết tất cả các thư viện cần thiết để build package.</p>
</li>
</ul>
<p>Nếu bạn thấy có quá nhiều khái niệm mới trong bài này mà bạn chưa thể nhớ hết được. Đừng nản, bạn sẽ quen với chúng sớm thôi. Hiện tại, chỉ cần nhớ rằng một ROS package luôn yêu cầu 2 tệp <em>package.xml</em> và <em>CMakeLists.txt</em>, cộng với một thư mục <em>src</em> là nơi bạn lưu code của mình.</p>
<p>Trong các phần tiếp theo, chúng ta sẽ tìm hiểu tất cả các khái niệm cốt lõi của ROS: Topic, Node, Publisher, Subscriber, v.v. và viết code để hoàn thành package <em>my_cam</em>.</p>
<h1 id="heading-tom-tat">Tóm tắt</h1>
<ul>
<li><p>ROS package là một thư mục chứa các tệp thực thi (executable file) và những tệp hỗ trợ để phục vụ một mục đích cụ thể.</p>
</li>
<li><p>ROS workspace là một thư mục nơi bạn sửa đổi, build và cài đặt các package bằng các công cụ <em>catkin</em>.</p>
</li>
<li><p>Một package phải có hai tệp <em>package.xml</em> &amp; <em>CMakeLists.txt</em>. Sử dụng lệnh <code>catkin_create_pkg</code> để tạo 1 package.</p>
</li>
</ul>
<h1 id="heading-trich-dan">Trích dẫn</h1>
<ol>
<li>Ảnh bìa: <a target="_blank" href="http://wiki.ros.org/groovy">http://wiki.ros.org/groovy</a></li>
</ol>
]]></content:encoded></item><item><title><![CDATA[4. Cài đặt ROS - Phần 2: Cài ROS Noetic và Những Thư Viện Liên Quan]]></title><description><![CDATA[Trong phần này, mình sẽ giúp bạn cài đặt ROS Noetic Ninjemys. Mỗi phiên bản ROS sẽ tương ứng với một phiên bản Ubuntu và ROS Noetic (chạy trên Ubuntu 20) là phiên bản mới nhất và cũng là phiên bản cuối cùng của ROS 1.
Đối với những bạn chưa từng dùng...]]></description><link>https://robodev.blog/cai-dat-ros-noetic</link><guid isPermaLink="true">https://robodev.blog/cai-dat-ros-noetic</guid><category><![CDATA[ROS]]></category><category><![CDATA[robotics]]></category><category><![CDATA[vietnamese]]></category><category><![CDATA[Tutorial]]></category><category><![CDATA[robot-operating-system]]></category><dc:creator><![CDATA[Trinh Nguyen]]></dc:creator><pubDate>Wed, 04 Jan 2023 20:47:11 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1672864860041/1373f38c-2f04-4609-b493-08e325297460.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Trong phần này, mình sẽ giúp bạn cài đặt <a target="_blank" href="http://wiki.ros.org/noetic/Installation/Ubuntu">ROS Noetic Ninjemys</a>. Mỗi phiên bản ROS sẽ tương ứng với một phiên bản Ubuntu và ROS Noetic (chạy trên Ubuntu 20) là phiên bản mới nhất và cũng là phiên bản cuối cùng của ROS 1.</p>
<p>Đối với những bạn chưa từng dùng Linux (Ubuntu là một bản phân phối Linux - <a target="_blank" href="https://en.wikipedia.org/wiki/Linux_distribution">Linux distribution</a>), bạn sẽ cần học cách sử dụng terminal để thực hiện các câu lệnh.</p>
<p>Mở terminal bằng tổ hợp phím <strong>Ctrl + Alt + T</strong> hoặc nhấp chuột phải vào khoảng trống trong bất kì thư mục nào và chọn <strong>Open in Terminal</strong>. Gõ lệnh sau vào:</p>
<pre><code class="lang-bash">sudo apt-get update
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1669487165749/LBV8ZdHFi.jpg" alt="ubuntu_update.jpg" class="image--center mx-auto" /></p>
<p>Câu lệnh trên dùng để update những phần mềm trong Ubuntu nếu chúng có bản cập nhật (release) mới. Trong Linux, khi bạn chạy một lệnh với <code>sudo</code> nghĩa là bạn chạy với tư cách quản trị viên (giống như Run as Administrator trong Windows). Và đừng ngạc nhiên nếu bạn không thấy gì khi nhập mật khẩu vì nó bị ẩn đi. Ấn Enter sau khi nhập và nếu mật khẩu đúng, lệnh sẽ được thực thi.</p>
<h1 id="heading-cai-dat-ros-noetic">Cài đặt ROS Noetic</h1>
<p><strong>Bước 1</strong>: Trong cùng terminal, tiếp tục chạy lệnh này để thiết lập <strong>sources.list</strong> để máy tính của bạn chấp nhận phần mềm từ <em>packages.ros.org</em>. Lệnh này khá dài, vì vậy bạn chỉ cần copy&amp;paste. Để paste một đoạn text vào terminal, bạn có thể nhấp chuột phải và chọn <strong>Paste</strong> hoặc sử dụng phím tắt <strong>Ctrl + Shift + V</strong> (tương tự, để copy nội dung nào đó từ Terminal, chỉ cần sử dụng <strong>Ctrl + Shift + C</strong>).</p>
<pre><code class="lang-bash">sudo sh -c <span class="hljs-string">'echo "deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main"&gt; /etc/apt/sources.list.d/ros-latest.list'</span>
</code></pre>
<p><strong>Bước 2</strong>: Thêm khóa xác thực (authenticated key) để máy của bạn có thể kết nối với máy chủ và tải các gói dữ liệu. Cài <strong>curl</strong> nếu bạn chưa có: <code>sudo apt install curl</code>. Sau đó, chạy lệnh này:</p>
<pre><code class="lang-bash">curl -s https://raw.githubusercontent.com/ros/rosdistro/master/ros.asc | sudo apt-key add -
</code></pre>
<p>Nếu nó in ra <strong>OK</strong>, thì key đã được thêm thành công.</p>
<p><strong>Bước 3</strong>: Cài ROS Noetic phiên bản Desktop-Full. Trước tiên, chạy cập nhật: <code>sudo apt update</code>. Sau đó bắt đầu cài đặt bằng cách chạy (bước này sẽ mất khoảng 30 phút):</p>
<pre><code class="lang-bash">sudo apt install ros-noetic-desktop-full
</code></pre>
<p><strong>Bước 4</strong>: Sau khi cài đặt xong, để chạy ROS, trước tiên bạn cần gọi lệnh <strong>source</strong> này trong terminal.</p>
<pre><code class="lang-bash"><span class="hljs-built_in">source</span> /opt/ros/noetic/setup.bash
</code></pre>
<p>Việc này chỉ cần làm một lần nhưng nếu bạn mở một terminal mới, bạn cần chạy lại nó. Để tránh điều này, bạn có thể thêm lệnh đó vào file<code>.bashrc</code> (có dấu chấm) bằng cách:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">echo</span> <span class="hljs-string">"source /opt/ros/noetic/setup.bash"</span> &gt;&gt; ~/.bashrc
</code></pre>
<p>File <code>.bashrc</code> này chứa tất cả các lệnh được tự động thực thi khi bạn mở một terminal mới. Khi bạn chỉnh sửa file <code>.bashrc</code>và muốn áp dụng ngay vào terminal hiện tại mà không muốn mở một terminal mới, bạn có thể dùng câu lệnh <strong>source</strong>:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">source</span> ~/.bashrc
</code></pre>
<p>Cuối cùng, chạy <code>roscore</code> và nếu terminal của bạn giống hình dưới đây thì xin chúc mừng! Bạn đã cài đặt ROS thành công!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1669489995409/3kX5YrVkf.png" alt="roscore.jpg" /></p>
<h1 id="heading-cai-dat-nhung-thu-vien-lien-quan">Cài đặt những thư viện liên quan</h1>
<p>Có một số phần mềm bạn cần cài đặt để có thể tạo ứng dụng của mình trong ROS. Chúng gọi là những dependencies. Chạy lệnh sau trong một terminal:</p>
<pre><code class="lang-bash">sudo apt install python3-rosdep python3-rosinstall python3-rosinstall-generator python3-wstool build-essential python3-catkin-tools python3-osrf-pycommon python3-pip
</code></pre>
<p>, sau đó:</p>
<pre><code class="lang-bash">sudo rosdep init
</code></pre>
<p>, và cuối cùng:</p>
<pre><code class="lang-bash">rosdep update
</code></pre>
<p>Xong bước này, mọi thứ đã sẵn sàng cho bạn để bắt đầu sử dụng ROS. Làm rất tốt!</p>
<h1 id="heading-nhung-phan-mem-nen-co">Những phần mềm nên có</h1>
<p>Phần này chỉ là những đề xuất của mình mà bản thân mình đang sử dụng và thấy rất hữu ích.</p>
<ul>
<li><a target="_blank" href="https://terminator-gtk3.readthedocs.io/en/latest/">Terminator</a>: Terminal mặc định của Ubuntu có rất hạn chế. Ví dụ: bạn không thể có nhiều terminal nhỏ trong cùng một cửa sổ (các terminal chỉ có thể được sắp xếp theo tab hoặc tách biệt hoàn toàn). Với <a target="_blank" href="https://terminator-gtk3.readthedocs.io/en/latest/">Terminator</a>, bạn có thể. Trong hình dưới đây, mình có 9 terminal trong một cửa sổ.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1669491142959/kCBLdiT1M.png" alt="Terminator_Ubuntu.jpg" class="image--center mx-auto" /></p>
<p>Để cài đặt Terminator, hãy chạy lệnh này:</p>
<pre><code class="lang-bash">sudo apt install terminator
</code></pre>
<p>Mình cũng khuyên bạn nên xem qua <a target="_blank" href="https://terminator-gtk3.readthedocs.io/en/latest/gettingstarted.html#layout-shortcuts">danh sách phím tắt</a> của Terminator này và nhớ một vài phím tắt cơ bản như thêm bớt terminal để sử dụng nhanh hơn.</p>
<ul>
<li><a target="_blank" href="https://code.visualstudio.com/docs/setup/linux">Visual Studio Code</a>: Đây là một trong những IDE phổ biến nhất. Nó miễn phí, chạy được trên các hệ điều hành khác nhau và có rất nhiều plugin hữu ích cho các developer. Hãy tải về, cài đặt và dùng thử. Mình tin bạn sẽ thích nó. <a target="_blank" href="https://code.visualstudio.com/Download">Link to download VS Code</a>.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1669492185222/XecJ0w0k8.png" alt="Visual_studio_code.png" /></p>
<h1 id="heading-trich-dan">Trích dẫn</h1>
<ol>
<li>Ảnh bìa: <a target="_blank" href="http://wiki.ros.org/lunar">http://wiki.ros.org/lunar</a></li>
</ol>
]]></content:encoded></item><item><title><![CDATA[3. Cài đặt ROS - Phần 1: Cài đặt Ubuntu 20.04]]></title><description><![CDATA[Trong phần này và phần tới, mình sẽ giúp các bạn cài đặt Ubuntu và ROS một cách nhanh và dễ nhất, đặc biệt dành cho các bạn chưa rành về Linux.
Nếu bạn đã có máy tính chạy Ubuntu 20, thì bạn có thể bỏ qua phần này. Mình đoán hầu hết các bạn chưa có s...]]></description><link>https://robodev.blog/cai-dat-ubuntu-2004</link><guid isPermaLink="true">https://robodev.blog/cai-dat-ubuntu-2004</guid><category><![CDATA[ROS]]></category><category><![CDATA[Ubuntu 20.04]]></category><category><![CDATA[Tutorial]]></category><category><![CDATA[vietnamese]]></category><dc:creator><![CDATA[Trinh Nguyen]]></dc:creator><pubDate>Wed, 04 Jan 2023 17:03:53 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1672851680157/2cb8c072-565e-4b38-a2d3-6afa704e0a58.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Trong phần này và phần tới, mình sẽ giúp các bạn cài đặt Ubuntu và ROS một cách nhanh và dễ nhất, đặc biệt dành cho các bạn chưa rành về Linux.</p>
<p>Nếu bạn đã có máy tính chạy Ubuntu 20, thì bạn có thể bỏ qua phần này. Mình đoán hầu hết các bạn chưa có sẵn Ubuntu, nên ở đây mình sẽ hướng dẫn bạn cài đặt Ubuntu trong VMWare. Mình đang sử dụng Windows 11 trong hướng dẫn này nhưng nó cũng tương tự như các hệ điều hành (HĐH) khác. Bắt đầu nào!</p>
<p>Có rất nhiều cách để cài đặt Ubuntu. Nếu bạn có PC (hoặc thậm chí là máy tính máy tính bo mạch đơn như Raspberry Pi 4 Modell B (8 GB)) dành riêng để chạy Ubuntu, thì bạn có thể tìm thấy nhiều hướng dẫn để cài đặt nó. Nếu không, dưới đây là một số giải pháp thay thế:</p>
<ul>
<li><p>Dual-boot (một máy có nhiều hệ điều hành): Bạn phải phân vùng ổ đĩa của mình sao cho một phần dành cho chạy một HĐH (ví dụ: Windows) và phần còn lại dành cho Ubuntu. Mỗi khi bật máy tính, bạn phải chọn HĐH mà mình muốn sử dụng. Tuy nhiên, mình không khuyến khích cách này nếu bạn là người mới bắt đầu sử dụng Linux.</p>
</li>
<li><p>WSL - Windows Subsystem for Linux (nếu bạn đang sử dụng Windows): Cách này thực ra rất hay nhưng mình cũng không khuyến khích vì đáng tiếc WSL vẫn chưa chính thức hỗ trợ các kết nối bên ngoài như webcam (cái mà chúng ta cần cho loạt bài này).</p>
</li>
<li><p>VM - Virtual Machine (Máy ảo): Đây là cách mình khuyên các bạn mới bắt đầu nên dùng (chi tiết bên dưới).</p>
</li>
</ul>
<p>Có thể bạn đang thắc mắc máy ảo (VM) là gì? Để trả lời câu hỏi này, bạn cần hiểu khái niệm ảo hóa: là quá trình chạy một hệ điều hành (guest - máy khách) bên trong một hệ điều hành khác (host - máy chủ). Phần mềm ảo hóa (hay còn gọi là virtualizer) như VMware hay VirtualBox giúp bạn tạo một máy ảo để có thể chạy guest OS. Có nhiều trình ảo hóa khác nhau nhưng VMware và VirtualBox là hai trình ảo hóa phổ biến nhất. Mình đã dùng thử VirtualBox lúc đầu vì nó miễn phí nhưng cảm thấy khó sử dụng và thử kết nối với webcam cũng không thành nên mình đã chọn VMware . Việc kết nối với các thiết bị ngoại vi như webcam, và chuyển file giữa host và guest trên VMware dễ dàng hơn rất nhiều. Hiện tại đang sử dụng phiên bản community hoàn toàn miễn phí của VMware.</p>
<h1 id="heading-cai-dat-vmware">Cài đặt VMWare</h1>
<ul>
<li><p>Bước 1: Tải <a target="_blank" href="https://releases.ubuntu.com/focal/">tệp .iso Ubuntu 20.04.5 LTS</a>. LTS là viết tắt của Long Term Support - Hỗ trợ dài hạn.</p>
</li>
<li><p>Bước 2: Tải VMware <a target="_blank" href="https://www.vmware.com/products/workstation-player.html">tại đây</a> và cài đặt.</p>
</li>
<li><p>Bước 3: Mở VMware Workstation Player và nhấp vào <strong>Create a New Virtual Machine</strong> (Tạo một máy ảo mới).</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1671484506408/FcwTuKtn9.jpg" alt class="image--center mx-auto" /></p>
</li>
<li><p>Bước 4: Chọn <strong>Installer disc image file (iso)</strong> và Duyệt đến tệp .iso mà bạn đã tải ở Bước 1. Sau đó chọn <strong>Next</strong>.</p>
</li>
<li><p>Bước 5: Nhập tên Ubuntu, tên user và mật khẩu lần lượt vào các ô: <strong>User name</strong>, <strong>Full name</strong>, và <strong>Password</strong>.</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1671484628219/9Ht1JOMoS.jpg" alt class="image--center mx-auto" /></p>
</li>
<li><p>Bước 6: Nhập tên máy ảo và duyệt đến thư mục cài đặt mong muốn (ở đây mình chọn mặc định).</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1671484657415/yZnHY0MGD.jpg" alt class="image--center mx-auto" /></p>
</li>
<li><p>Bước 7: Chỉnh dung lượng đĩa. Ở đây mình dùng 30GB.</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1671484669364/i-LlAtiEi.jpg" alt class="image--center mx-auto" /></p>
</li>
<li><p>Bước 8: Kiểm tra lại các thông tin lần cuối và nhấn <strong>Finish</strong>.</p>
</li>
<li><p>Bước 9: Sau đó quá trình cài đặt sẽ bắt đầu:</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1669475031173/tMMQ-ewjz.jpg?height=500" alt="wmware_8_install_ubuntu.jpg" class="image--center mx-auto" /></p>
<ul>
<li><p>Bước 10: Khi cài đặt xong VM sẽ tự động được mở lên. Nếu không, hãy quay lại VMware Workstation Player và bạn sẽ thấy VM mới tạo của mình ở đó trong mục <strong>Hone</strong>. Của mình là <strong>robodev</strong>.</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1671484874709/0g9SZld9Z.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Kích đúp hoặc nhấn nút <strong>Play</strong> để bật máy. Nhập mật khẩu từ Bước 5 để đăng nhập.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1669477033557/OKvHqtyKS.png" alt="image.png" class="image--center mx-auto" /></p>
<p>Và thế là xong! Bạn đã hoàn thành việc cài đặt VMware và tạo một máy ảo Ubuntu. Hãy tiếp tục và bắt đầu cài đặt ROS trong <a target="_blank" href="https://robodev.blog/cai-dat-ros-noetic">phần tiếp theo</a>.</p>
<h1 id="heading-trich-dan">Trích dẫn</h1>
<ol>
<li>Ảnh bìa: http://wiki.ros.org/lunar</li>
</ol>
]]></content:encoded></item><item><title><![CDATA[2. ROS (Robot Operating System) là gì?]]></title><description><![CDATA[Robotics là một trong những mảng phát triển nhanh nhất trong giới công nghệ. Có thể bạn đã nghe hoặc nhìn thấy những ứng dụng như xe tự lái, robot hình người của Tesla hay Boston Dynamics và bắt đầu muốn tìm hiểu hay phát triển sự nghiệp về Robotics....]]></description><link>https://robodev.blog/ros-la-gi</link><guid isPermaLink="true">https://robodev.blog/ros-la-gi</guid><category><![CDATA[ROS]]></category><category><![CDATA[vietnamese]]></category><category><![CDATA[robotics]]></category><category><![CDATA[robot-operating-system]]></category><dc:creator><![CDATA[Trinh Nguyen]]></dc:creator><pubDate>Wed, 04 Jan 2023 07:55:41 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1672818694334/f1b74ecb-70ca-4fd1-b11b-b3581f8b31e4.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Robotics là một trong những mảng phát triển nhanh nhất trong giới công nghệ. Có thể bạn đã nghe hoặc nhìn thấy những ứng dụng như xe tự lái, robot hình người của Tesla hay Boston Dynamics và bắt đầu muốn tìm hiểu hay phát triển sự nghiệp về Robotics. Trong serie này mình sẽ giúp bạn làm quen với Robot Operating System (ROS) một trong những nền tảng phát triển robot phổ biến nhất hiện nay.</p>
<p><img src="https://i.gifer.com/g2Iz.gif" alt="Boston Dynamic Atlas jumps gif" /></p>
<h1 id="heading-ros-la-gi">ROS là gì?</h1>
<p>Robot Operating System - ROS là một nền tảng mã nguồn mở (open-sourced) cung cấp những thư viện và công cụ để xây dựng các ứng dụng liên quan tới robot. Sau hơn 10 năm phát hành, ROS đã và đang được sử dụng rộng rãi trên toàn thế giới cả trong nghiên cứu lẫn trong công nghiệp. Dưới đây là một video ngắn tổng hợp những ứng dụng tiêu biểu của ROS vào dịp kỷ niệm 10 năm thành lập.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=-6yAk05et1Q">https://www.youtube.com/watch?v=-6yAk05et1Q</a></div>
<p> </p>
<h2 id="heading-ai-dang-dung-ros-va-de-lam-gi">Ai đang dùng ROS (và để làm gì)?</h2>
<p>Để giúp bạn dễ hình dung, đây là một danh sách các công ty đang dùng ROS trong nghiên cứu và phát triển sản phẩm của họ: <a target="_blank" href="https://github.com/vmayoral/ros-robotics-companies">Link tới danh sách</a>. Như các bạn có thể thấy, nền tảng này được sử dụng trong những công ty lớn như NVIDIA, Microsoft, Apple, Bosch, v.v. tới những công ty startup như ANYbotics. Ngoài ra mình tin chắc là còn rất nhiều công ty khác không được list ra ở đây. Ở những trường đại học và viện nghiên cứu về robot, ROS là một nền tảng không thể thiếu.</p>
<p>Vậy cụ thể thì người ta đang dùng ROS vào những việc gì? Có thể nói rằng bất cứ ứng dụng nào cần phải giao tiếp với hoặc điều khiển các sensor (cảm biến) hay actuator (thiết bị truyền động), người ta đều có thể dùng ROS. Video ở trên có nêu ra một số ứng dụng của ROS-Industrial (một version dành riêng cho công nghiệp) và bạn cũng có thể thấy sự đa dạng của những dự án này. Ngoài ra, ROS còn được dùng rất nhiều trong nghiên cứu ví dụ các lĩnh vực như xe tự hành (autonomous driving), robot nhiều chân (legged robot), robot hình người (humanoid robot), thiết bị bay không người lái (drone), v.v.</p>
<h2 id="heading-ros-khong-phai-la-gi">ROS không phải là gì?</h2>
<p>Trong cuốn sách <a target="_blank" href="https://jokane.net/agitr/">A Gentle Introduction to ROS</a>, tác giả đã đề cập đến 3 điều không đúng về ROS mà mình cũng đồng ý: ROS không phải là một ngôn ngữ lập trình, ROS không phải (chỉ) là một thư viện và ROS không phải là môi trường phát triển tích hợp (Integrated Development Environment - IDE). Bạn sẽ dần hiểu rõ hơn những điểm này khi bắt đầu làm quen với ROS còn hiện tại chỉ cần nhớ rằng ROS về cơ bản là một nền tảng cung cấp cho bạn các công cụ để tạo ra ứng dụng robot.</p>
<h1 id="heading-tai-sao-chon-ros">Tại sao chọn ROS?</h1>
<p>Trên trang chủ của ROS có dòng chữ "Don’t reinvent the wheel. Create something new and do it faster and better by building on ROS!" nôm na nghĩa là không cần phải mất công làm lại những cái có sẵn, ROS giúp bạn dựng lên những thứ mới nhanh và hiệu quả hơn.</p>
<h2 id="heading-uu-diem-cua-ros">Ưu điểm của ROS</h2>
<ol>
<li><p>ROS cung cấp những công cụ chuẩn để giúp việc giao tiếp dễ dàng giữa các tác vụ. Ví dụ, hệ thống của bạn có một camera và một cánh tay robot. Bạn muốn lấy hình ảnh từ camera, xử lý hình ảnh đó và yêu cầu robot gắp vật thể nếu nó xuất hiện ở trong ảnh. Có nhiều cách để thực hiện các bước này, nhưng nếu bạn dùng ROS thì việc truyền nhận thông tin giữa các bước trở nên dễ dàng hơn rất nhiều.</p>
</li>
<li><p>ROS có một cộng đồng user và developer vô cùng lớn mạnh. Như mình đã nói ở phần trước, các công ty và viện nghiên cứu lớn trên thế giới đều đã và đang dùng ROS và nó dần trở thành một nền tảng chuẩn cho việc phát triển robot. Điều này có nghĩa là nếu bạn gặp khó khăn khi làm một dự án với ROS thì nhiều khả năng là bạn chỉ cần google là ra câu trả lời, hoặc bạn có thể hỏi trên những diễn đàn và có nhiều người sẵn sàng giúp bạn.</p>
</li>
<li><p>Có vô vàn nhiều những thư viện sẵn có cho các tác vụ khác nhau. Từ những công nghệ mới trong AI, thị giác máy tính (computer vision), xử lý ngôn ngữ tự nhiên (natural language processing) hay điều khiển (control) v.v., tất cả đều có những phần mềm mới nhất (state-of-the-art) mà bạn có thể trực tiếp tải xuống và sử dụng ngay. Những phần mềm tiên tiến này đa số tới từ những tổ chức, công ty, trường đại học hoặc thậm chí cá nhân mà đang họ dùng ROS trong công việc hay nghiên cứu của họ và cho công khai (open source) code những dự án của mình.</p>
</li>
<li><p>ROS hoàn toàn free và được phép dùng trong sản phẩm thương mại. Đây là lý do vì sao các công ty, tổ chức (kể cả những tập đoàn lớn) rất ưa dùng ROS vì nó dễ dàng thử nghiệm, phát triển và thậm chí thương mại hoá sản phẩm của họ một cách tiết kiệm.</p>
</li>
</ol>
<h2 id="heading-nhuoc-diem-cua-ros">Nhược điểm của ROS</h2>
<p>Dĩ nhiên ROS không phải là một nền tảng hoàn hảo cho mọi ứng dụng mà vẫn còn một vài hạn chế như sau:</p>
<ol>
<li><p>ROS không phù hợp cho những ứng dụng yêu cầu hard real-time (thơi gian thực cứng/tức thì). Hard real-time có nghĩa là hệ thống của bạn phải hoàn thành tác vụ chính xác theo những thời hạn (deadlines) qui định. Chỉ cần lỡ một trong những deadline này thì hệ thống sẽ bị lỗi và có thể gây hậu quả nghiêm trọng. <a target="_blank" href="https://stackoverflow.com/questions/17308956/differences-between-hard-real-time-soft-real-time-and-firm-real-time">Thông tin thêm về so sánh các loại thời gian thực</a>.</p>
</li>
<li><p>ROS phải được chạy trên một cấu hình đủ mạnh để đạt hiệu suất tốt nhất. Để có thể sử dụng ROS thì bạn cần một chiếc máy tính, và tùy vào độ phức tạp của ứng dụng mà có những yêu cầu về cấu hình khác nhau. Đối với những ứng dụng phức tạp, bạn có thể cần phải có một PC với một hoặc nhiều card đồ hoạ, còn nếu ứng dụng đơn giản thì có thể chỉ một chiếc Raspberry Pi (4 - 8 GB RAM) là đủ.</p>
</li>
<li><p>Việc quản lý và bảo trì những gói (package) phần mềm trong ROS vẫn còn nhiều bất cập, đặc biệt là cho những sản phẩm thương mại.</p>
</li>
</ol>
<p>Để giải quyết những vấn đề này, ROS 2 đã ra đời và đang trong quá trình phát triển (mình sẽ làm một series về ROS 2 sau).</p>
<h1 id="heading-bat-dau-voi-ros-nhu-the-nao">Bắt đầu với ROS như thế nào?</h1>
<p>Thực ra đã có rất nhiều hướng dẫn về ROS mà bạn có thể dễ dàng tìm thấy trên Google, GitHub, v.v. Loạt bài này có thể bao gồm các chủ đề tương tự nhưng mình cố gắng hết sức để giải thích và giúp bạn thực hành với các ví dụ đơng giản và thực tế. Như mình đã nói trong <a target="_blank" href="https://robodev.blog/tong-quan-ve-series-ros-co-ban">chương đầu tiên</a>, sê-ri này bao gồm 5 bước chính từ việc trả lời cho câu hỏi ROS là gì, cài đặt nó như thế nào cho tới hiểu và áp dụng tất cả các khái niệm cơ bản vào thực tế. Thông qua việc vừa học vừa làm, mình hy vọng việc tìm hiểu về ROS nói riêng và robot nói chung sẽ vừa thú vị, vừa đầy thách thức đối với bạn.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1671146251916/2j7UMzpeM.jpg" alt class="image--center mx-auto" /></p>
<p>Sau khi trả lời xong ba câu hỏi what, why và how, mình tin bạn đã có cái nhìn tổng quan về ROS và vẫn muốn tiếp tục sê-ri này. Trong phần tiếp theo, hãy cùng bắt đầu bước quan trọng đầu tiên: <a target="_blank" href="https://robodev.blog/cai-dat-ubuntu-2004">Cài đặt</a>.</p>
<h1 id="heading-tom-tat">Tóm tắt</h1>
<ol>
<li><p>ROS là một nền tảng để phát triển robot, được sử dụng phổ biến trên toàn thế giới.</p>
</li>
<li><p>ROS có thể được dùng bất cứ ứng dụng nào (từ đơn giản tới phức tạp) cần phải giao tiếp với hoặc điều khiển các sensor (cảm biến) hay actuator (thiết bị truyền động).</p>
</li>
<li><p>Lý do khiến ROS trở nên phổ biến là vì nó cung cấp cho người dùng những công cụ hữu ích để phát triển phần mềm và có cộng đồng user rất lớn mạnh.</p>
</li>
<li><p>ROS vẫn còn nhiều bất cập như chưa dành cho hệ thống thời gian thực (real-time), yêu cầu hardware đủ mạnh và việc quản lý cũng như bảo trì vẫn chưa được tối ưu. Để giải quyết những hạn chế này, ROS 2 đang được phát triển.</p>
</li>
</ol>
<h1 id="heading-trich-dan">Trích dẫn</h1>
<ol>
<li><p>Cover photo: <a target="_blank" href="http://wiki.ros.org/noetic"><strong>http://wiki.ros.org/noetic</strong></a></p>
</li>
<li><p>Icons: <a target="_blank" href="https://www.flaticon.com/"><strong>https://www.flaticon.com/</strong></a></p>
</li>
</ol>
]]></content:encoded></item><item><title><![CDATA[1. Tổng Quan về Series ROS Cơ Bản]]></title><description><![CDATA[Trong loạt bài này, thông qua 5 bước chính (xem hình bên dưới) mình sẽ hướng dẫn bạn tìm hiểu và bắt đầu sử dụng Robot Operating System - ROS, một trong những nền tảng phổ biến nhất hiện nay cho phát triển robot.

Chúng ta sẽ bắt đầu bằng việc trả lờ...]]></description><link>https://robodev.blog/tong-quan-ve-series-ros-co-ban</link><guid isPermaLink="true">https://robodev.blog/tong-quan-ve-series-ros-co-ban</guid><category><![CDATA[ROS]]></category><category><![CDATA[vietnamese]]></category><category><![CDATA[robot-operating-system]]></category><dc:creator><![CDATA[Trinh Nguyen]]></dc:creator><pubDate>Tue, 03 Jan 2023 21:21:14 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1672780714325/64185156-2ece-4d26-b476-7f88473a5688.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Trong loạt bài này, thông qua 5 bước chính (xem hình bên dưới) mình sẽ hướng dẫn bạn tìm hiểu và bắt đầu sử dụng Robot Operating System - ROS, một trong những nền tảng phổ biến nhất hiện nay cho phát triển robot.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1668760195618/o1qxy7pJk.png" alt="image.png" /></p>
<p>Chúng ta sẽ bắt đầu bằng việc trả lời những câu hỏi: ROS là gì, tại sao mọi người (và bạn cũng nên) chọn ROS và cách bạn sẽ tìm hiểu về nó thông qua sê-ri này. Bước thứ hai là cài đặt ROS và tất cả các phần mềm cần thiết để nó chạy được trên máy tính của bạn. Tiếp theo, chúng ta sẽ xem xét các khái niệm cơ bản nhất của ROS và thực hành sử dụng chúng. Bạn cũng sẽ học cách tạo một robot di động đơn giản, mô phỏng và điều khiển nó. Cuối cùng, chúng ta sẽ tổng hợp mọi thứ đã học để hoàn thành một dự án nhỏ: điều khiển robot bằng ký hiệu tay.</p>
<p>Vậy để hoàn thành tất cả các phần đó cần những yêu cầu gì? Mình biết hầu hết các bạn là người mới bắt đầu trong lĩnh vực này và không có (hoặc thậm chí không đủ khả năng) các công cụ đắt tiền như card đồ họa, động cơ, bộ điều khiển động cơ, v.v. để chế tạo một robot thực sự nhưng không sao hết. Một trong những mục đích chính của sê-ri này là giúp bạn tiếp cận ROS với ít kinh phí và công sức nhất có thể.</p>
<p>Về phần cứng, bạn cần một máy tính và một chiếc USB webcam. Tốt nhất là một chiếc laptop vì thường nó đã tích hợp sẵn webcam. Lý do cần có webcam là vì nó là một chiếc camera hoạt động tương tự như đa số các camera dùng trong công nghiệp (<a target="_blank" href="https://en.wikipedia.org/wiki/Pinhole_camera">pinhole camera</a>). Ngoài ra, mình chọn camera thay vì các cảm biến khác vì nó phổ biến, thực tiễn và dễ hình dung.</p>
<p>Đối với phần mềm, bạn cần có một chút kiến thức về lập trình Python hoặc C++ vì chúng là hai ngôn ngữ chính được hỗ trợ bởi ROS (Python sẽ được sử dụng chủ yếu trong sê-ri này). Nếu bạn biết về Linux và đã sử dụng qua nó thì đó sẽ là một lợi thế lớn. Nếu không, cũng không có vấn đề gì vì đây là cơ hội để bạn tìm hiểu một trong những hệ điều hành tuyệt vời nhất dành cho developer. Bạn cũng không cần phải có một máy tính khác hoặc cài 2 hệ điều hành song song. Mình sẽ đề cập vấn đề này sau trong <a target="_blank" href="https://robodev.blog/cai-dat-ubuntu-2004">chương Cài đặt</a>.</p>
<p>Dưới đây là tổng hợp những yêu cầu về hardware và software bạn cần có:</p>
<ul>
<li><p>Máy tính xách tay hoặc PC (không cần hệ điều hành cụ thể) có webcam tích hợp hoặc USB webcam. Máy tính phải có ít nhất 4GB RAM, Intel® i5 lõi tứ hoặc tương đương và ít nhất 20 GB dung lượng ổ đĩa trống. Một card đồ họa (GPU) không bắt buộc nhưng là một lợi thế.</p>
</li>
<li><p>Biết một chút về lập trình với Python hoặc C++ (hoặc bất kỳ ngôn ngữ OOP nào)</p>
</li>
</ul>
<p>Và thế là bạn đã sẵn sàng cho hành trình chinh phục ROS. Cùng bắt đầu nhé!</p>
<p>Sê ri ROS cơ bản có tổng cộng 12 chương:</p>
<ol>
<li><p><a target="_blank" href="https://robodev.blog/tong-quan-ve-series-ros-co-ban">Tổng Quan về Series ROS Cơ Bản</a> (bài này)</p>
</li>
<li><p><a target="_blank" href="https://robodev.blog/ros-la-gi">ROS (Robot Operating System) là gì?</a></p>
</li>
<li><p><a target="_blank" href="https://robodev.blog/cai-dat-ubuntu-2004">Cài đặt ROS - Phần 1: Cài đặt Ubuntu 20.04</a></p>
</li>
<li><p><a target="_blank" href="https://robodev.blog/cai-dat-ros-noetic">Cài đặt ROS - Phần 2: Cài ROS Noetic và Những Thư Viện Liên Quan</a></p>
</li>
<li><p><a target="_blank" href="https://robodev.blog/ros-package-va-workspace-la-gi">ROS Package và Workspace là gì?</a></p>
</li>
<li><p><a target="_blank" href="https://robodev.blog/nhung-khai-niem-co-ban-trong-ros">Những Khái Niệm Cơ Bản trong ROS</a></p>
</li>
<li><p><a target="_blank" href="https://robodev.blog/tao-ros-publisher">Tạo một ROS Publisher</a></p>
</li>
<li><p><a target="_blank" href="https://robodev.blog/tao-mot-ros-subscriber">Tạo một ROS Subscriber</a></p>
</li>
<li><p><a target="_blank" href="https://robodev.blog/nhan-dien-cu-chi-tay-trong-ros">Nhận Diện Cử Chỉ Tay trong ROS</a></p>
</li>
<li><p><a target="_blank" href="https://robodev.blog/mo-phong-robot-trong-ros-phan-1">Mô Phỏng Robot trong ROS - Phần 1</a></p>
</li>
<li><p><a target="_blank" href="https://robodev.blog/mo-phong-robot-trong-ros-phan-2">Mô Phỏng Robot trong ROS - Phần 2</a></p>
</li>
<li><p><a target="_blank" href="https://robodev.blog/dieu-khien-robot-bang-cu-chi-tay">Điều Khiển Robot Bằng Cử Chỉ Tay</a></p>
</li>
</ol>
<h1 id="heading-trich-dan">Trích dẫn</h1>
<ol>
<li><p>Ảnh bìa: <a target="_blank" href="http://wiki.ros.org/cturtle">http://wiki.ros.org/cturtle</a></p>
</li>
<li><p>Icons: <a target="_blank" href="https://www.flaticon.com/">https://www.flaticon.com/</a></p>
</li>
</ol>
]]></content:encoded></item><item><title><![CDATA[9. Hand Gesture Recognition in ROS]]></title><description><![CDATA[You have done a great job so far by writing both the publisher and subscriber all by yourself. However, I hope you still remember one of the very important reasons we learn ROS: "Don’t reinvent the wheel", meaning there are a lot of existing cool pac...]]></description><link>https://robodev.blog/hand-gesture-recognition-in-ros</link><guid isPermaLink="true">https://robodev.blog/hand-gesture-recognition-in-ros</guid><category><![CDATA[#hand-gesture-recognition]]></category><category><![CDATA[ROS]]></category><category><![CDATA[robot-operating-system]]></category><category><![CDATA[mediapipe]]></category><category><![CDATA[Machine Learning]]></category><dc:creator><![CDATA[Trinh Nguyen]]></dc:creator><pubDate>Mon, 02 Jan 2023 16:36:19 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1672356532933/cc23f05d-3eab-4f9a-9fdb-dc1cc34a734a.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>You have done a great job so far by writing both the publisher and subscriber all by yourself. However, I hope you still remember one of the very important reasons we learn ROS: "<strong>Don’t reinvent the wheel"</strong>, meaning there are a lot of existing cool packages (or projects) which can be immediately deployed without much effort of implementation. In this chapter, you will learn how to clone and run an available package that detects hand signs on the published images from your webcam. So instead of just viewing images like in <a target="_blank" href="https://robodev.blog/write-a-ros-subscriber">chapter 8</a>, you can now use them to get really useful information. Let's get started!</p>
<h1 id="heading-hand-gesture-recognition">Hand Gesture Recognition</h1>
<p>A system that can understand human hand gestures has enormous applications. In robotics, it helps, for example, convert gesture recognitions into control signals making a robot follow users' hands.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://media.giphy.com/media/26AHwLqr8FKVZ9ykU/giphy.gif">https://media.giphy.com/media/26AHwLqr8FKVZ9ykU/giphy.gif</a></div>
<p> </p>
<p>With the recent developments in artificial intelligence (AI), integrating that system is easier than ever. In this chapter, I want to introduce to you the <a target="_blank" href="https://google.github.io/mediapipe/solutions/hands.html">Mediapipe Hands</a> which is an open-source machine learning (ML) package from Google that can help track human hands and fingers using images. Basically what it does is combine two ML models: a palm detection model for detecting initial hand locations and a hand landmark model for precisely localizing the 21 3D hand-knuckle (see image below) coordinates inside the detected hand regions. It was trained with about 30k real labeled data and works in a <a target="_blank" href="https://arxiv.org/abs/1512.02325">single-shot manner</a> which is very fast. A good introduction to this package can be found <a target="_blank" href="https://google.github.io/mediapipe/solutions/hands.html">here</a>.</p>
<p><img src="https://mediapipe.dev/images/mobile/hand_landmarks.png" alt="hand_landmarks.png" class="image--center mx-auto" /></p>
<p>Now we need to turn the detected key points from Mediapipe into meaningful actions, for instance from the hand landmark image above, our system should tell us the status is "Open" since all the fingers are stretched out. In order to do that, another model for classifying the actions is required. Luckily, there is already an existing program for that. This <a target="_blank" href="https://github.com/kinivi/hand-gesture-recognition-mediapipe">hand-gesture-recognition-mediapipe</a> repository contains Python scripts that help user train and deploy models to recognize hand signs and finger gestures. I have used it myself and converted it into this <a target="_blank" href="https://github.com/TrinhNC/ros_hand_gesture_recognition">ROS package</a> so that you can clone it directly. You're welcome!</p>
<h1 id="heading-installation">Installation</h1>
<p>First, install the following dependencies using <code>pip3</code> or <code>conda</code>:</p>
<ul>
<li><p>Mediapipe 0.8.1</p>
</li>
<li><p>OpenCV 3.4.2 or Later</p>
</li>
<li><p>Tensorflow 2.3.0 or Later</p>
</li>
</ul>
<p>For example, installing commands with <code>pip</code>:</p>
<pre><code class="lang-bash">pip3 install mediapipe
pip3 install opencv-python
pip3 install tensorflow
</code></pre>
<p>Then, clone this repository into the folder <code>catkin_ws/src</code>:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> ~/catkin_ws/src
git <span class="hljs-built_in">clone</span> https://github.com/TrinhNC/ros_hand_gesture_recognition.git
</code></pre>
<p>Build the package:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> ~/catkin_ws
catkin build
</code></pre>
<h1 id="heading-run-the-package">Run the package</h1>
<p>After building successfully, you can run a demo. Remember to source the workspace by <code>source ~/catkin_ws/devel/setup.bash</code>. Open 2 terminals. In the first terminal run the image publisher (from <a target="_blank" href="https://robodev.blog/write-a-ros-publisher">chapter 7</a> and make sure the webcam is connected to the virtual machine if you are using one):</p>
<pre><code class="lang-bash">roslaunch my_cam my_cam.launch
</code></pre>
<p>In the second terminal, run the hand pose recognition:</p>
<pre><code class="lang-bash">roslaunch ros_hand_gesture_recognition hand_sign.launch
</code></pre>
<p>The result should look like this:</p>
<p><img src="https://user-images.githubusercontent.com/19979949/210186155-c21b0fb2-84ba-430c-94ab-b273f5f36c6c.gif" alt="ros-mediapipe" class="image--center mx-auto" /></p>
<p>In the GIF above, <strong>FPS</strong> stands for Frame Per Second which is the number of images being processed per second, <strong>Right</strong> is the right hand and the words after that (Turn Right, Forward, etc.) are the labels that I assigned to specific hand signs. You can change these labels by following the section below.</p>
<h1 id="heading-train-hand-sign-recognition-optional">Train Hand Sign Recognition (Optional)</h1>
<p>The current package can classify only six signs (classes) and I labeled them: Stop, Go, Forward, Backward, Turn Right, and Turn Left (see the image below) which will be converted to control signals to move a robot later in this series. If you want to change or add gestures, or you find out that my trained model does not perform very well with your hand, you can collect data and train it again by yourself.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1678623209549/dd8313bb-8f8e-443e-bc9b-efe214495c50.jpeg" alt="ros-hand-gesture-recognition" class="image--center mx-auto" /></p>
<p>There are two <a target="_blank" href="https://jupyter.org/">jupyter notebooks</a> included in the folder <a target="_blank" href="https://github.com/TrinhNC/ros_hand_gesture_recognition/tree/main/src/notebooks">src/notebooks</a>:</p>
<ul>
<li><p><a target="_blank" href="https://github.com/TrinhNC/ros_hand_gesture_recognition/blob/main/src/notebooks/keypoint_classification_EN.ipynb"><strong>keypoint_classification_EN.ipynb</strong></a>: a model training script for hand sign recognition.</p>
</li>
<li><p><a target="_blank" href="https://github.com/TrinhNC/ros_hand_gesture_recognition/blob/main/src/notebooks/point_history_classification.ipynb"><strong>point_history_classification.ipynb</strong></a>: a model training script for finger gesture recognition (meaning the model after training can detect the movement of your fingers and not just a static sign like in the keypoint classification).</p>
</li>
</ul>
<p>I used only the keypoint classification model in the current ROS package because it is enough for the application but you can feel free to adjust it to match yours.</p>
<p>In the example below, I will show you how to add one more sign to the detection. Let's say we want to add this sign✌️and name it "Hi". <strong>Remember to end all the commands (using Ctrl + C) from the previous parts because this part is completely separate from ROS.</strong></p>
<p>You may need to install these extra libraries if you have not:</p>
<pre><code class="lang-python">pip3 install -U tf-nightly
pip install -U scikit-learn
pip3 install -U matplotlib
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1672664044490/a621899e-2321-4311-92e8-4ac8f2ee79ba.png" alt="hi_mediapipe" class="image--center mx-auto" /></p>
<p>First, open the <a target="_blank" href="https://github.com/TrinhNC/ros_hand_gesture_recognition/blob/main/src/model/keypoint_classifier/keypoint_classifier_label.csv">keypoint_classifier_label.csv</a> in the folder <em>src/model/keypoint_classifier.</em> Here you find all the labels (at the moment 6 classes) and you should add 'Hi' to the end like this:</p>
<pre><code class="lang-bash">Stop
Go
Forward
Backward
Turn Right
Turn Left
Hi
</code></pre>
<p>Next, you need to record data and append it to the file <a target="_blank" href="https://github.com/TrinhNC/ros_hand_gesture_recognition/blob/main/src/model/keypoint_classifier/keypoint.csv">keypoint.csv</a> in the folder <em>src/model/keypoint_classifier</em>. If you open this file, you will see it contains 6410 lines. The first number in each line is the class ID with respect to the list above, for example, "Go" has ID 0, "Stop" has ID 1, and so on. Then comes 42 numbers or 21 pairs of numbers which represent the coordinates of each keypoint (i.e. the hand knuckle) with respect to the origin which is the wrist. One thing to note is that the IDs in the image below are the key point IDs (0-20) and they are different from the class IDs (0-6).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1672665593202/620eaac8-0840-4b8f-9f85-365dc71bf447.png" alt="keypoint_mediapipe" class="image--center mx-auto" /></p>
<p>In order to record data, run the script <strong>app.py</strong>:</p>
<pre><code class="lang-bash">python3 app.py
</code></pre>
<p>Press <code>k</code> on the keyboard and you should see the line <code>MODE: Logging Key Point</code> shows up. Then, use your right hand to make the target sign✌️visible. Press and hold the number 6 (class ID of "Hi") with your left hand. This will continuously append the new data to the file <a target="_blank" href="https://github.com/TrinhNC/ros_hand_gesture_recognition/blob/main/src/model/keypoint_classifier/keypoint.csv">keypoint.csv</a> until you release the key. You can also try to press &amp; release 6 immediately and check the file. It should have one new line at the end starting with the number 6 and a list of numbers that follow. Also, during the recording, remember to move your right hand to different positions to make the data varied.</p>
<p><img src="https://user-images.githubusercontent.com/19979949/210239140-cd5998ca-5937-48f4-9f91-8a86ca10da40.gif" alt="ros-mediapipe-record-dataset" class="image--center mx-auto" /></p>
<p>After recording for about 10-15 seconds, the data should be ready and you can stop the program. Open the notebook file <a target="_blank" href="https://github.com/TrinhNC/ros_hand_gesture_recognition/blob/main/src/notebooks/keypoint_classification_EN.ipynb">keypoint_classification_EN.ipynb</a>. Edit <em>dataset</em>, <em>model_save_path</em> and <em>tflite_save_path</em> to match your paths. Change <code>NUM_OF_CLASSES</code> to 7 instead of 6: <code>NUM_CLASSES = 7</code>. Then run the notebook from beginning to end. The training is executed in the cell [13] and takes around 2-3 minutes. After that, you can launch <code>my_cam.launch</code> and <code>hand_sign.launch</code> like in the Demo to see the result like below.</p>
<p><img src="https://user-images.githubusercontent.com/19979949/210258993-de24fcb4-4e1c-44f6-b1c0-6088677c0691.gif" alt class="image--center mx-auto" /></p>
]]></content:encoded></item><item><title><![CDATA[8. Write a ROS Subscriber]]></title><description><![CDATA[In this part, you are going to implement an image subscriber which is "the Photocopier" in my example. This subscriber is simply continuously reading images from the topic image_raw (that you created from chapter 7 and displaying them on your screen....]]></description><link>https://robodev.blog/write-a-ros-subscriber</link><guid isPermaLink="true">https://robodev.blog/write-a-ros-subscriber</guid><category><![CDATA[ROS]]></category><category><![CDATA[robot-operating-system]]></category><category><![CDATA[Python]]></category><category><![CDATA[Tutorial]]></category><dc:creator><![CDATA[Trinh Nguyen]]></dc:creator><pubDate>Mon, 26 Dec 2022 19:38:34 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1672056605717/fc85d57b-cb10-410c-9828-6549a0a36964.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this part, you are going to implement an image subscriber which is "the Photocopier" in my example. This subscriber is simply continuously reading images from the topic <code>image_raw</code> (that you created from <a target="_blank" href="https://robodev.blog/write-a-ros-publisher">chapter 7</a> and displaying them on your screen. Of course, you can do much more than just view the images (like putting the images to some machine learning algorithms that you are going to do in the next chapter) or you can even view the image right from the publisher itself but we should start somewhere, right? This image subscriber (and publisher) is only an example to show you how different processes in ROS communicate with each other.</p>
<h1 id="heading-the-code">The Code</h1>
<p>Let's create a new Python script in the <a target="_blank" href="https://github.com/TrinhNC/my_cam"><em>my_cam</em></a> folder and name it <code>image_subscriber.py</code>. If you already did the <a target="_blank" href="https://robodev.blog/write-a-ros-publisher">last chapter</a>, the code below should look familiar because the structure is quite the same. Again, I highly recommend you follow the explanation part below and type by yourself instead of copy&amp;paste.</p>
<pre><code class="lang-python"><span class="hljs-comment">#!/usr/bin/python3</span>

<span class="hljs-keyword">import</span> rospy
<span class="hljs-keyword">import</span> cv2
<span class="hljs-keyword">from</span> cv_bridge <span class="hljs-keyword">import</span> CvBridge, CvBridgeError
<span class="hljs-keyword">from</span> sensor_msgs.msg <span class="hljs-keyword">import</span> Image

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">callback</span>(<span class="hljs-params">image_msg</span>):</span>
    <span class="hljs-string">"""This function is called to handle the subscribed messages

    Args:
        image_msg (Image): message type Image from sensor_msgs
    """</span>
    <span class="hljs-keyword">try</span>:
        cv_image = bridge.imgmsg_to_cv2(image_msg)
        cv2.imshow(<span class="hljs-string">'ROS Image Subscriber'</span>, cv_image)
        cv2.waitKey(<span class="hljs-number">10</span>)
    <span class="hljs-keyword">except</span> CvBridgeError <span class="hljs-keyword">as</span> error:
        print(error)

<span class="hljs-keyword">if</span> __name__==<span class="hljs-string">"__main__"</span>:
    bridge = CvBridge()
    rospy.init_node(<span class="hljs-string">"image_subscriber"</span>, anonymous=<span class="hljs-literal">True</span>)
    print(<span class="hljs-string">"Subscribe images from topic /image_raw ..."</span>)

    image_subcriber = rospy.Subscriber(<span class="hljs-string">"image_raw"</span>, Image, callback)

    <span class="hljs-keyword">try</span>:
        <span class="hljs-comment"># spin() simply keeps python from exiting until this node is stopped</span>
        rospy.spin()
    <span class="hljs-keyword">except</span> KeyboardInterrupt:
        print(<span class="hljs-string">"Shutting down!"</span>)
</code></pre>
<h1 id="heading-the-code-explain">The Code Explain</h1>
<p>The first part of the code is exactly the same as in the <a target="_blank" href="https://github.com/TrinhNC/my_cam/blob/main/src/image_publisher.py"><em>image_publisher.py</em></a>. It is basically to import all the necessary libraries.</p>
<pre><code class="lang-python"><span class="hljs-comment">#!/usr/bin/python3</span>

<span class="hljs-keyword">import</span> rospy
<span class="hljs-keyword">import</span> cv2
<span class="hljs-keyword">from</span> cv_bridge <span class="hljs-keyword">import</span> CvBridge, CvBridgeError
<span class="hljs-keyword">from</span> sensor_msgs.msg <span class="hljs-keyword">import</span> Image
</code></pre>
<p>Next, a <em>callback</em> function is created but I want to explain first the main function and come to this <em>callback</em> later. In the main, similar to the publisher, you need to create a CvBridge object and initialize a node whose name is <code>"image_subscriber"</code>.</p>
<pre><code class="lang-python"><span class="hljs-keyword">if</span> __name__==<span class="hljs-string">"__main__"</span>:
    bridge = CvBridge()
    rospy.init_node(<span class="hljs-string">"image_subscriber"</span>, anonymous=<span class="hljs-literal">True</span>)
    print(<span class="hljs-string">"Subscribe images from topic /image_raw ..."</span>)
</code></pre>
<p>Then, the subscriber is created by using the function <code>rospy.Subscriber("image_raw", Image, callback)</code> in which <code>image_raw</code> is the topic you want to subscribe to, <code>Image</code> is the data type of <code>image_raw</code> and <code>callback</code> is a function that will be invoked when new messages are received. Finally, <code>rospy.spin()</code> is called to keep your node from exiting until the node has been stopped (by <em>Ctrl + C</em> for instance).</p>
<pre><code class="lang-python">    image_subcriber = rospy.Subscriber(<span class="hljs-string">"image_raw"</span>, Image, callback)
    <span class="hljs-keyword">try</span>:
        <span class="hljs-comment"># spin() simply keeps python from exiting until this node is stopped</span>
        rospy.spin()
    <span class="hljs-keyword">except</span> KeyboardInterrupt:
        print(<span class="hljs-string">"Shutting down!"</span>)
</code></pre>
<p>The <code>callback</code> function is defined always with the incoming message as the first argument.</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">callback</span>(<span class="hljs-params">image_msg</span>):</span>
</code></pre>
<p>Inside the <code>callback</code>, you first convert the ROS image message <code>image_msg</code> to OpenCV format (here I named it <code>cv_image</code>) using the <code>imgmsg_to_cv2</code> function. Then, you can display the image using the <code>cv2.imshow</code> function which takes first the window name (here <code>ROS Image Subscriber</code>) as the first argument and the image <code>cv_image</code> as the second. Finally, <code>cv2.waitKey(10)</code> is used to delay for given milliseconds (here 10ms) or until any key is pressed before switching to the next frame.</p>
<pre><code class="lang-python">    <span class="hljs-keyword">try</span>:
        cv_image = bridge.imgmsg_to_cv2(image_msg)
        cv2.imshow(<span class="hljs-string">'ROS Image Subscriber'</span>, cv_image)
        cv2.waitKey(<span class="hljs-number">10</span>)
    <span class="hljs-keyword">except</span> CvBridgeError <span class="hljs-keyword">as</span> error:
        print(error)
</code></pre>
<h1 id="heading-run-the-subscriber">Run the Subscriber</h1>
<p>In order to start the script above from a terminal, you should make it executable by either right-click on the script and selecting <strong>Properties &gt; Permissions</strong> and check <strong>Allow executing file as program</strong>, or using the command <code>sudo chmod +x image_subscriber.py</code>. Then, open 3 terminals or one terminator with 3 sub-terminals as below.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1671896323053/73438b68-d55c-4331-8fc0-16f368b42232.jpeg" alt class="image--center mx-auto" /></p>
<p>In the first terminal, run ROS Master</p>
<pre><code class="lang-bash">roscore
</code></pre>
<p>In the second, start the publisher (remember to <a target="_blank" href="https://robodev.blog/write-a-ros-publisher#heading-setup-camera-in-vmware">connect the webcam to your virtual machine</a>).</p>
<pre><code class="lang-bash">rosrun my_cam image_publisher.py
</code></pre>
<p>In the third, start the subscriber:</p>
<pre><code class="lang-bash">rosrun my_cam image_subscriber.py
</code></pre>
<p>After that, you should see a window with the title <em>ROS Image Subscriber</em> showing your webcam view like what I have in the photo above (the cute cow is actually one of my assistants :)).</p>
<p>After completing chapters 7 and 8, you now know how to write a ROS publisher and subscriber. You also know how to exploit the images from a camera which is one of the most popular sensors used in robotics. Well done! In the next chapter, as promised, I will help you use one of the available tools to deploy the images and get some useful information instead of just viewing them on screen. Meet you there!</p>
<h1 id="heading-reference">Reference</h1>
<ol>
<li>Cover photo: <a target="_blank" href="http://wiki.ros.org/jade">http://wiki.ros.org/jade</a></li>
</ol>
]]></content:encoded></item><item><title><![CDATA[7. Write a ROS Publisher]]></title><description><![CDATA[You can start with the "HelloWorld" tutorial Writing a Simple Publisher and Subscriber (Python) on the ROS website but it will just print out "hello world" on your screen which is ... not very fun. Let's implement our "Photographer" from chapter 6. A...]]></description><link>https://robodev.blog/write-a-ros-publisher</link><guid isPermaLink="true">https://robodev.blog/write-a-ros-publisher</guid><category><![CDATA[ROS]]></category><category><![CDATA[robot-operating-system]]></category><category><![CDATA[Python]]></category><category><![CDATA[Tutorial]]></category><dc:creator><![CDATA[Trinh Nguyen]]></dc:creator><pubDate>Sun, 25 Dec 2022 22:01:08 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1672005261368/9f3ba01a-ccc4-4996-9488-9a23e0f36ca6.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>You can start with the "HelloWorld" tutorial <a target="_blank" href="http://wiki.ros.org/ROS/Tutorials/WritingPublisherSubscriber%28python%29">Writing a Simple Publisher and Subscriber (Python)</a> on the ROS website but it will just print out "hello world" on your screen which is ... not very fun. Let's implement our "Photographer" from <a target="_blank" href="https://robodev.blog/ros-basic-concepts">chapter 6</a>. As a reminder, the Photographer represents a node for reading and publishing images from your webcam.</p>
<h1 id="heading-setup-camera-in-vmware">Setup Camera in VMWare</h1>
<p>First, if you are using a virtual machine (VM), you need to make sure your webcam is connected to your VM instead of your host. In <em>VMWare Workstation Player</em>, go to <strong>Player &gt; Manage &gt; Virtual Machine Settings...</strong> or <strong>Ctrl + D</strong>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670446255141/k7nve4iHU.jpg" alt class="image--center mx-auto" /></p>
<p>Select <strong>USB Controller</strong> tab and choose <strong>USB 3.1</strong> in <em>USB compatibility.</em></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670447769469/B9RBwGIrx6.jpg" alt class="image--center mx-auto" /></p>
<p>Then go to <strong>Removable Devices</strong>, select your integrated webcam (in my case it is <em>Sunplus Innovation Integrated_Webcam_HD</em>), and select <strong>Connect (Disconnect from host)</strong>. Click <em>OK</em> on any popup dialogs after that.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670448489176/ng1QkjItE.jpg" alt class="image--center mx-auto" /></p>
<p>If there is an error saying something like the connection is unsuccessful, a trick is to switch your host machine (in my case Windows) and open your webcam by using the <strong>Camera</strong> app from <em>Start</em>. Return to the virtual machine and you should see a dialog to choose to connect the camera to your host or to your VM (see below). Select <strong>Connect to a virtual machine</strong>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670449224068/42BVA0AMm.jpg" alt class="image--center mx-auto" /></p>
<p>Finally, check if the connection is really established by going to <strong>Player &gt; Removeable Devices</strong> again, and you should see a tick next to your webcam. If not, just redo the steps above.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670482874189/00xwQvGRJ.jpg" alt class="image--center mx-auto" /></p>
<h1 id="heading-write-a-camera-image-publisher">Write a Camera Image Publisher</h1>
<p>First, in the folder <em>my_cam</em> create a new Python script with the name <em>image_publisher.py</em>. The whole code is available <a target="_blank" href="https://github.com/TrinhNC/my_cam">here</a> but I highly recommend you follow the explanation below and type it yourself.</p>
<h2 id="heading-the-code">The Code</h2>
<pre><code class="lang-python"><span class="hljs-comment">#!/usr/bin/python3</span>
<span class="hljs-keyword">import</span> rospy
<span class="hljs-keyword">import</span> cv2
<span class="hljs-keyword">from</span> cv_bridge <span class="hljs-keyword">import</span> CvBridge, CvBridgeError
<span class="hljs-keyword">from</span> sensor_msgs.msg <span class="hljs-keyword">import</span> Image

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">publish_image</span>():</span>
    <span class="hljs-string">"""Capture frames from a camera and publish it to the topic /image_raw
    """</span>
    image_pub = rospy.Publisher(<span class="hljs-string">"image_raw"</span>, Image, queue_size=<span class="hljs-number">10</span>)
    bridge = CvBridge()
    capture = cv2.VideoCapture(<span class="hljs-string">"/dev/video0"</span>)

    <span class="hljs-keyword">while</span> <span class="hljs-keyword">not</span> rospy.is_shutdown():
        <span class="hljs-comment"># Capture a frame</span>
        ret, img = capture.read()
        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> ret:
            rospy.ERROR(<span class="hljs-string">"Could not grab a frame!"</span>)
            <span class="hljs-keyword">break</span>
        <span class="hljs-comment"># Publish the image to the topic image_raw</span>
        <span class="hljs-keyword">try</span>:
            img_msg = bridge.cv2_to_imgmsg(img, <span class="hljs-string">"bgr8"</span>)
            image_pub.publish(img_msg)
        <span class="hljs-keyword">except</span> CvBridgeError <span class="hljs-keyword">as</span> error:
            print(error)

<span class="hljs-keyword">if</span> __name__==<span class="hljs-string">"__main__"</span>:
    rospy.init_node(<span class="hljs-string">"my_cam"</span>, anonymous=<span class="hljs-literal">True</span>)
    print(<span class="hljs-string">"Image is being published to the topic /image_raw ..."</span>)
    publish_image()
    <span class="hljs-keyword">try</span>:
        rospy.spin()
    <span class="hljs-keyword">except</span> KeyboardInterrupt:
        print(<span class="hljs-string">"Shutting down!"</span>)
</code></pre>
<h2 id="heading-the-code-explained">The Code Explained</h2>
<p>The first line is called <a target="_blank" href="https://en.wikipedia.org/wiki/Shebang_(Unix)">shebang</a> line which defines where the interpreter is located. In this case, Python3, the default Python version in Ubuntu 20, is used and it is located in the folder <code>/usr/bin</code>.</p>
<pre><code class="lang-python"><span class="hljs-comment">#!/usr/bin/python3</span>
</code></pre>
<p>All necessary libraries need to be imported. <a target="_blank" href="http://wiki.ros.org/rospy">rospy</a> is the Python API for creating ROS applications. <code>cv2</code> is <a target="_blank" href="https://en.wikipedia.org/wiki/OpenCV">OpenCV</a>, an open-source library for computer vision. Here we'll use it to capture images from our webcams. <a target="_blank" href="http://wiki.ros.org/cv_bridge">CvBridge</a> is used to convert between ROS Image messages and OpenCV images since they are not the same data type. Finally, we have to import the <code>Image</code> message type (which belongs to the package <a target="_blank" href="http://wiki.ros.org/sensor_msgs">sensor_msgs</a>) because we are dealing with images.</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> rospy
<span class="hljs-keyword">import</span> cv2
<span class="hljs-keyword">from</span> cv_bridge <span class="hljs-keyword">import</span> CvBridge, CvBridgeError
<span class="hljs-keyword">from</span> sensor_msgs.msg <span class="hljs-keyword">import</span> Image
</code></pre>
<p>Next, define a function named <code>publish_image</code>. In this function, you first create a publisher which has a name of<code>image_raw</code>, a data type of <code>Image</code> that I mentioned above, and a <code>queue_size</code> of 10 (<a target="_blank" href="http://wiki.ros.org/rospy/Overview/Publishers%20and%20Subscribers">the size of the outgoing message queue used for asynchronous publishing</a>). One example to understand <code>queue_size</code> is that if you publish images at a rate of 20 Hz (or 20 frames per second) but your <em>queue_size</em> is only set to 10 then only 10 images are processed (put into a queue) while the other 10 will be discarded. In our example, we actually don't have to care much about it since we have only one message <code>Image</code>. This is also a good article on how to <a target="_blank" href="http://wiki.ros.org/rospy/Overview/Publishers%20and%20Subscribers#Choosing_a_good_queue_size">select a proper queue size</a>.</p>
<pre><code class="lang-python">image_pub = rospy.Publisher(<span class="hljs-string">"image_raw"</span>, Image, queue_size=<span class="hljs-number">10</span>)
</code></pre>
<p>Create a CvBridge object:</p>
<pre><code class="lang-python">bridge = CvBridge()
</code></pre>
<p>Define a <a target="_blank" href="https://docs.opencv.org/4.x/dd/d43/tutorial_py_video_display.html">video capture object</a> which will help us to grab images from our webcam. <code>/dev/video0</code> is the device name. If you use an external camera or on your computer, the name may be different, you can check it by using this command: <code>ls -ltrh /dev/video*</code>. In my case, it lists out both <code>/dev/video0</code> and <code>/dev/video1</code> but only <code>/dev/video0</code> works.</p>
<pre><code class="lang-python">capture = cv2.VideoCapture(<span class="hljs-string">"/dev/video0"</span>)
</code></pre>
<p>We then jump into a loop that does the real job. The loop always checks if the node is running or not by checking the <code>rospy.is_shutdown()</code> flag. It breaks if there is an interruption, for example, you stop the program by using <em>Ctrl + C</em>. <code>capture.read()</code> returns a bool <code>ret</code> (<code>True</code> if the frame is read correctly, and <code>false</code> otherwise) and an image <code>img</code>. The image is encoded in OpenCV in the format of 3 channels Blue, Green, and Red (or BGR) and each channel is a matrix which is a 2-dimensional array. In the <a target="_blank" href="https://robodev.blog/write-a-ros-publisher#heading-appendix">Appendix</a> below, I explain in more detail how an image is encoded.</p>
<p>If the <code>ret</code> is false meaning there are some problems like the camera cannot be connected correctly, we throw out an error to the terminal. Before you can publish the image, you need to convert it to a ROS message <code>img_msg</code> using the function <code>cv2_to_imgmsg</code> (from <code>CvBridge</code>) with the color encoding <code>"bgr8"</code>(so that future subscribers will know the color order, in this case, it is blue-green-red and 8-bits). Finally, you can use the publisher <code>image_pub</code> that you created to publish <code>img_msg</code> using the function <code>publish</code>. If this does not work, just throw an exception.</p>
<pre><code class="lang-python">    <span class="hljs-keyword">while</span> <span class="hljs-keyword">not</span> rospy.is_shutdown():
        <span class="hljs-comment"># Capture a frame</span>
        ret, img = capture.read()
        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> ret:
            rospy.ERROR(<span class="hljs-string">"Could not grab a frame!"</span>)
            <span class="hljs-keyword">break</span>
        <span class="hljs-comment"># Publish the image to the topic image_raw</span>
        <span class="hljs-keyword">try</span>:
            img_msg = bridge.cv2_to_imgmsg(img, <span class="hljs-string">"bgr8"</span>)
            image_pub.publish(img_msg)
        <span class="hljs-keyword">except</span> CvBridgeError <span class="hljs-keyword">as</span> error:
            print(error)
</code></pre>
<p>The function <code>publish_image</code> is complete. In the <code>main</code> function, you first initialize a node called <code>my_cam</code>. Note that in ROS, <strong>nodes are uniquely named</strong>. If two nodes with the same name are launched, the previous one is kicked off. The flag <code>anonymous</code> is set <code>True</code> so that <code>rospy</code> will choose a unique name for our publisher node (by adding random numbers to the end of the name) so that multiple publishers can run simultaneously.</p>
<pre><code class="lang-python"><span class="hljs-keyword">if</span> __name__==<span class="hljs-string">"__main__"</span>:
    rospy.init_node(<span class="hljs-string">"my_cam"</span>, anonymous=<span class="hljs-literal">True</span>)
    print(<span class="hljs-string">"Image is being published to the topic /image_raw ..."</span>)
    publish_image()
    <span class="hljs-keyword">try</span>:
        rospy.spin()
    <span class="hljs-keyword">except</span> KeyboardInterrupt:
        print(<span class="hljs-string">"Shutting down!"</span>)
</code></pre>
<p>After initializing the node, call the <code>publish_image</code> function. And the final step is to call <code>rospy.spin()</code> which simply keeps your node from exiting until the node has been stopped (by <em>Ctrl + C</em> for instance). And that's it! Your publisher code is ready to be run.</p>
<h1 id="heading-run-the-publisher">Run the Publisher</h1>
<p>You first need to make it executable by either using the command</p>
<pre><code class="lang-bash">sudo chmod +x image_publisher.py
</code></pre>
<p>or right-click on the file, select <em>Properties &gt; Permissions &gt; Allow executing files as program.</em></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670879724888/n8DziyKWL.jpg" alt class="image--center mx-auto" /></p>
<p>Then open 2 terminals or a terminator with 2 sub-terminals. In one, run <code>roscore</code> to start the ROS Master. In the other run, <code>rosrun my_cam image_publisher.py</code> in which <code>my_cam</code> is the package name and <code>image_publisher.py</code> is the script. Another way to run the publisher is by using <code>roslaunch</code> which I mention in the <a target="_blank" href="https://robodev.blog/write-a-ros-publisher#heading-run-node-with-roslaunch">Appendix</a> below.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670880437417/2wCd3vorL.jpg" alt class="image--center mx-auto" /></p>
<h2 id="heading-view-published-images">View Published Images</h2>
<p>Now you should see the LED next to your webcam light on. And if you open another terminal and run: <code>rostopic list</code>, you should see the topic <code>/image_raw</code> listed. You can even see what's inside <code>/image_raw</code> by running this command: <code>rostopic echo /image_raw</code> and it will print out arrays of numbers that represent images. In order to display the images, you need to write a subscriber (i.e. the Photocopier) that we are going to do in the <a target="_blank" href="https://robodev.blog/write-a-ros-subscriber">next chapter</a>. Luckily, ROS already provides a tool for visualizing different types of published data including images. Run <code>rqt</code> in a terminal and a window will show up. Go to <em>Plugins &gt; Visualization &gt; Image View</em>, then select <em>/image_raw</em> in the drop-down topic list and you should see the live images.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670880937685/PrS86GF2k.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-appendix">Appendix</h1>
<h2 id="heading-how-a-computer-see-an-image">How a computer "see" an image</h2>
<p>In computer vision, an image is usually represented by the <a target="_blank" href="https://en.wikipedia.org/wiki/RGB_color_model">RGB color model</a>. RGB stands for Red, Green, and Blue, the 3 primary colors from which you can create any other colors.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1672004602847/6724a545-2555-4ac1-86f1-b684dbcacde3.png" alt class="image--center mx-auto" /></p>
<p>Each color channel is encoded in a matrix (i.e. a 2-Dimensional array) whose values are pixels. If the pixels are in 8-bits, the values should be between 0 and 255 (or 2^8 - 1). In the example below, you can see how an image (with a height of 140 pixels and width of 140 pixels) is decomposed into 3 different channels and each channel is actually a 140x140 matrix. These channels are combined into a 3D matrix whose each element contains Blue, Green, Red pixels (this BGR order is the default color order of OpenCV and it could be different from other libraries).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1672004523101/808080f8-6966-4d21-8b3c-c7527096d0f0.jpeg" alt class="image--center mx-auto" /></p>
<h2 id="heading-run-node-with-roslaunch">Run Node with roslaunch</h2>
<p>Besides <code>rosrun</code>, a more common way to run a node is by using <code>roslaunch</code>. Limitations of <code>rosrun</code> compared to <code>roslaunch</code>:</p>
<ul>
<li><p><code>rosrun</code> can only run one node from one package at a time while <code>roslaunch</code> can run multiple nodes from multiple packages.</p>
</li>
<li><p><code>roslaunch</code> automatically starts <code>roscore</code> (if not already) while <code>rosrun</code> does not.</p>
</li>
<li><p>To use <code>roslaunch</code> requires a <em>.launch</em> file and the user can customize this file such as calling other launch files, changing the variables of functions, etc. while <code>rosrun</code> does not have this option.</p>
</li>
</ul>
<p>A <em>launch</em> file is actually an XML file, so it's pretty straightforward to create. First, create a folder named <em>launch</em> and then create a text file named <em>my_cam.launch.</em> The content of <a target="_blank" href="https://github.com/TrinhNC/my_cam/tree/main/launch">this file</a>:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">launch</span>&gt;</span>
     <span class="hljs-tag">&lt;<span class="hljs-name">arg</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"camera_name"</span> <span class="hljs-attr">default</span>=<span class="hljs-string">"/dev/video0"</span>/&gt;</span>
     <span class="hljs-tag">&lt;<span class="hljs-name">node</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"my_webcam"</span> <span class="hljs-attr">pkg</span>=<span class="hljs-string">"my_cam"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"image_publisher_launch.py"</span> <span class="hljs-attr">output</span>=<span class="hljs-string">"screen"</span>&gt;</span>
         <span class="hljs-tag">&lt;<span class="hljs-name">param</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"camera_name"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"string"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"$(arg camera_name)"</span> /&gt;</span>
     <span class="hljs-tag">&lt;/<span class="hljs-name">node</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">launch</span>&gt;</span>
</code></pre>
<p><strong>Explanation</strong>: A launch file begins with the tag <code>&lt;launch&gt;</code> and ends with the tag <code>&lt;/launch&gt;</code>. I begin with creating an argument named <code>camera_name</code> and its default value is <code>/dev/video0</code>. I will explain how to use it below. When you want to use this variable in the launch file, call it with <code>$(arg camera_name)</code> as in line 4. The next line defines the node that you want to run, where <code>name</code> is the name of the Node (this name will overwrite the name you use in your code if the two names are different), <code>pkg</code> is the package name (here <code>my_cam</code>), <code>type</code> is the filename (here <code>image_publisher_launch.py</code>), <code>output</code> is for choosing how to display the information (if <code>screen</code>, the information will be printed to the screen, and if <code>log</code>, it will be saved in a log file). Finally, create a parameter named <code>camera_name</code> with type <code>string</code> and value the same as the argument <code>camera_name</code> above. You can call this parameter in your code and so you don't have to edit the code every time you need to change something. Concretely, I created a file <a target="_blank" href="https://github.com/TrinhNC/my_cam/blob/main/src/image_publisher_launch.py">image_publisher_launch.py</a>. This is very similar to <a target="_blank" href="https://github.com/TrinhNC/my_cam/blob/main/src/image_publisher.py">image_publisher.py</a> except that I made it a class and when initializing the class, there is the line:</p>
<pre><code class="lang-python">self.capture = cv2.VideoCapture(rospy.get_param(<span class="hljs-string">"my_webcam/camera_name"</span>))
</code></pre>
<p>Here instead of giving a default value as before, the <code>rospy.get_param</code> function is used to get parameters from the launch file. So if for example, you use a camera with a different name, then you just need to change the launch file. Or when running <code>roslaunch</code> you can use the argument <code>"camera_name"</code> like below.</p>
<pre><code class="lang-bash">roslaunch my_cam my_cam.launch camera_name:=<span class="hljs-string">"/dev/video0"</span>
</code></pre>
<p>If you use default values of variables (here <code>"/dev/video0"</code>), you can simply run <code>roslaunch my_cam my_cam.launch</code>. But as I said, if you use a different camera or its name is not <code>/dev/video0</code> then you just need to change the name in the command above. Very convenient, right?</p>
<p>In addition, there are many other tags that I can't list here. You can check them <a target="_blank" href="http://wiki.ros.org/roslaunch/XML">here</a> and <a target="_blank" href="http://wiki.ros.org/roslaunch">here</a>.</p>
<h1 id="heading-reference">Reference</h1>
<ol>
<li>Cover image: <a target="_blank" href="http://wiki.ros.org/hydro">http://wiki.ros.org/hydro</a></li>
</ol>
]]></content:encoded></item></channel></rss>