Multi-robot hand-eye calibration system with PyQt5 GUI
- ๋ฐ๋ชจ
- ๊ฐ์
- ์ฃผ์ ๊ธฐ๋ฅ
- ์์คํ ๊ตฌ์กฐ
- ํ๋ก์ ํธ ๊ตฌ์กฐ
- ๋น ๋ฅธ ์์
- ์์คํ ์๊ตฌ์ฌํญ
- ์ค์น
- ๋น๋
- ์คํ
- ์ฌ์ฉ๋ฒ
- ์ค์
- API / ROS2 ์ธํฐํ์ด์ค
- ๋ฌธ์ ํด๊ฒฐ
- ๋ผ์ด์ ์ค
- Maintainer
๋ก๋ด ์๋์ดํํฐ์ ์นด๋ฉ๋ผ ์ฌ์ด์ ๋ณํ ๊ด๊ณ(AX=XB)๋ฅผ ๊ตฌํ๋ Hand-Eye Calibration ์์คํ ์ ๋๋ค. ROS2 Humble ๊ธฐ๋ฐ์ PyQt5 GUI์์ ๋ฐ์ดํฐ ์์ง, ์บ๋ฆฌ๋ธ๋ ์ด์ ์คํ, ๊ฒฐ๊ณผ ์ ์ฅ๊น์ง ํ๋ฒ์ ์ฒ๋ฆฌํฉ๋๋ค.
- gui_node (Python): ROS2 ๋ ธ๋ + PyQt5 GUI, ์นด๋ฉ๋ผ ์์ ํ์, ํฌ์ฆ ์์ง, ์บ๋ฆฌ๋ธ๋ ์ด์ ์คํ
- ์นด๋ฉ๋ผ ๋๋ผ์ด๋ฒ: ์ฌ์ฉ ์ผ์์ ๋ฐ๋ผ VCS๋ก ์ ํ
(RealSense:realsense2_camera, Orbbec:OrbbecSDK_ROS2) - calibration (Python): ArUco ๋ง์ปค ๊ฒ์ถ, Tsai-Lenz / DQ RANSAC ์๋ฒ ๋ชจ๋
- robot_interface (Python): UR Direct (read-only ์์ผ) ๋๋ ROS2 TF๋ฅผ ํตํ ๋ก๋ด ํฌ์ฆ ํ๋
- dsr_pose_reader (C++): Doosan ๋ก๋ด ์ ์ฉ ํฌ์ฆ ๋ฆฌ๋, DRFL ๋ชจ๋ํฐ๋ง์ผ๋ก ํ๋ํธ ์ ์ด ์ ์ง
DQ RANSAC (๊ธฐ๋ณธ, ๊ถ์ฅ)
- ์ถ์ฒ: Daniilidis, "Hand-Eye Calibration Using Dual Quaternions", IEEE 1999
- ๋ผ์ด๋ธ๋ฌ๋ฆฌ: ethz-asl/hand_eye_calibration
- ๋ฐฉ์: ํ์ ๊ณผ ์ด๋์ Dual Quaternion์ผ๋ก ๋์์ ํ์ด, RANSAC์ผ๋ก ์ด์์น ์๋ ์ ๊ฑฐ
- ์ฅ์ : ๋ ธ์ด์ฆ/์ด์์น์ ๊ฐ๊ฑด
- ๊ถ์ฅ ํฌ์ฆ ์: 15๊ฐ ์ด์
Tsai-Lenz
- ์ถ์ฒ: Tsai & Lenz, IEEE 1989
- ๋ฐฉ์: ํ์ (R)์ SVD๋ก ๋จผ์ ํ๊ณ , ์ด๋(t)์ ์ต์์์น๋ฒ์ผ๋ก ํ์ด
- ์ฅ์ : ๋งค์ฐ ๋น ๋ฆ, ๊ฐ๋จ
- ๋จ์ : ์ด์์น ์ฒ๋ฆฌ ์์ -- ๋ฐ์ดํฐ๊ฐ ๊นจ๋ํด์ผ ํจ
4-DOF Calibrator (ํฅํ)
- ์ถ์ฒ: QuantuMope/handeye-4dof
- SCARA ๋ฑ 4์ถ ๋ก๋ด์ฉ, ํ์ฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ง ํฌํจ (GUI ํตํฉ ์์ )
- Eye-in-hand ๋ก๋ด ์บ๋ฆฌ๋ธ๋ ์ด์ (์นด๋ฉ๋ผ๊ฐ ์๋์ดํํฐ์ ๋ถ์ฐฉ๋ ๊ฒฝ์ฐ)
- ์ฐ์ ์๋ํ ์์คํ ์ ๋ก๋ด-์นด๋ฉ๋ผ ์ ๋ฐ ์ ํฉ
- ๋ค์ค ๋ก๋ด ํ๊ฒฝ (UR, Doosan ๋ฑ)
- ์ฐ๊ตฌ ๊ฐ๋ฐ ๋ฐ ๊ต์ก
- ๋ค์ค ๋ก๋ด ์ง์: UR Direct (read-only ์์ผ), Doosan DRFL (๋ชจ๋ํฐ๋ง), ROS2 TF๋ฅผ ํตํด ํ๋ํธ ์ ์ด๋ฅผ ์ ์งํ๋ฉฐ ํฌ์ฆ ํ๋
- 2๊ฐ์ง ์บ๋ฆฌ๋ธ๋ ์ด์ ์๊ณ ๋ฆฌ์ฆ: DQ RANSAC (์ด์์น์ ๊ฐ๊ฑด), Tsai-Lenz (๋น ๋ฅธ ๊ฒฐ๊ณผ ํ์ธ)
- PyQt5 GUI: ์นด๋ฉ๋ผ ์์ ์ค์๊ฐ ํ์, ArUco ๋ง์ปค ์ค๋ฒ๋ ์ด, ํฌ์ฆ ์์ง/์ญ์ , ์บ๋ฆฌ๋ธ๋ ์ด์ ์คํ์ ํ ํ๋ฉด์์ ์ฒ๋ฆฌ
- 3D ์๊ฐํ: ์์ง๋ ํฌ์ฆ ์๊ณผ ์บ๋ฆฌ๋ธ๋ ์ด์ ๊ฒฐ๊ณผ๋ฅผ Matplotlib 3D ํ๋กฏ์ผ๋ก ํ์ธ
- ROS2 ๋ค์ดํฐ๋ธ: ์นด๋ฉ๋ผ ์์์ ROS2 ํ ํฝ์ผ๋ก ์์ (RealSense / Orbbec ๋ฑ), TF2๋ก ๋ก๋ด ํฌ์ฆ ํ๋ ๊ฐ๋ฅ
- Docker ์ง์: ๋น๋ ๋ฐ ์คํ ์คํฌ๋ฆฝํธ ํฌํจ, ์ํด๋ฆญ ์ปจํ ์ด๋ ํ๊ฒฝ
- ๋ฐ์ดํฐ ์ ์ฅ/๋ก๋: CSV ํฌ๋งท์ผ๋ก ํฌ์ฆ ๋ฐ์ดํฐ ์ ์ฅ, ๋์ค์ ๋ถ๋ฌ์ ์ฌ์บ๋ฆฌ๋ธ๋ ์ด์ ๊ฐ๋ฅ
โโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Vision Sensor โ โ UR Robot โ โ Doosan Robot โ
โ RealSense / Orbbec โ โ โ โ โ
โโโโโโโโโโโโโฌโโโโโโโโโโโโ โโโโโโโโโโฌโโโโโโโโโโ โโโโโโโโโโโโโฌโโโโโโโโโโโโโ
โ USB 3.0 โ TCP:30003 โ TCP:12345
โ โ (read-only) โ (DRFL monitoring)
โโโโโโโโโโโโโดโโโโโโโโโโโโ โ โโโโโโโโโโโโโโโโดโโโโโโโโโโโโโ
โ Camera ROS2 Driver โ โ โ dsr_pose_reader (C++) โ
โ - Color streaming โ โ โ - No access control โ
โ - Camera info pub โ โ โ - Pendant preserved โ
โโโโโโโโโโโโโฌโโโโโโโโโโโโ โ โโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโ
โ โ โ
โ /camera/color/ โ โ /tf, /tcp_pose
โ image_raw โ โ
โ camera_info โ โ
โ โ โ
โโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโ
โ hand_eye_calibration (gui_node) โ
โ โ
โ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ ArUco โ โ Robot Interface โ โ
โ โ Detector โ โ - UR Direct (read-only socket) โ โ
โ โ โ โ - ROS2 TF (dsr_pose_reader, etc.) โ โ
โ โโโโโโโโฌโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ โ
โ โโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโ โ
โ โ Capture Pose (marker T + robot T ์ ์ฅ) โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Calibration Solver โ โ
โ โ - DQ RANSAC (๊ธฐ๋ณธ) - Tsai-Lenz โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ Result: X (4x4, camera <-> end-effector) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Hand-eye_calibration/ # ROS2 ์ํฌ์คํ์ด์ค ๋ฃจํธ
โโโ .github/workflows/
โ โโโ release.yml # ํ๊ทธ push ์ GitHub Release ์๋ ์์ฑ
โโโ camera_realsense.repos # VCS: realsense-ros v4.55.1
โโโ camera_orbbec.repos # VCS: OrbbecSDK_ROS2 v2-main
โโโ docker/
โ โโโ Dockerfile # ROS2 Humble + ์์กด์ฑ
โ โโโ config.sh.example # Docker ๊ณตํต ์ค์ (์ด๋ฏธ์ง๋ช
, ROS_DOMAIN_ID)
โ โโโ build.sh # Docker ์ด๋ฏธ์ง ๋น๋
โ โโโ run.sh # Docker ์ปจํ
์ด๋ ์คํ
โ โโโ entrypoint.sh # ROS2 ํ๊ฒฝ ์ค์ + RMW(CycloneDDS) ์ค์
โ โโโ cyclonedds.xml # CycloneDDS ํ๋ (๋์ฉ๋ ์ผ์ ๋ฉ์์ง์ฉ)
โ โโโ commands.sh # ์ปจํ
์ด๋ ๋ด ๋ช
๋ น์ด ํจ์ ์ ์ (build, camera-*, gui-*, doosan)
โ
โโโ src/
โโโ (camera driver) # VCS๋ก ๊ฐ์ ธ์ด: realsense-ros ๋๋ OrbbecSDK_ROS2
โ
โโโ dsr_pose_reader/ # Doosan ๋ก๋ด ํฌ์ฆ ๋ฆฌ๋ (C++)
โ โโโ package.xml
โ โโโ CMakeLists.txt
โ โโโ doosan_api/ # DRFL ๋ผ์ด๋ธ๋ฌ๋ฆฌ (Doosan Robotics, BSD)
โ โ โโโ include/ # DRFL.h, DRFC.h, DRFLEx.h, DRFS.h
โ โ โโโ lib/ # libDRFL.a, libPoco*.so
โ โโโ include/dsr_pose_reader/
โ โ โโโ pose_reader_node.hpp
โ โโโ config/
โ โ โโโ default.yaml # Doosan ์ฐ๊ฒฐ ํ๋ผ๋ฏธํฐ
โ โโโ launch/
โ โ โโโ dsr_pose_reader.launch.py
โ โโโ src/
โ โโโ pose_reader_node.cpp
โ
โโโ hand_eye_calibration/ # ์บ๋ฆฌ๋ธ๋ ์ด์
ํจํค์ง (Python)
โโโ package.xml
โโโ setup.py
โโโ config/
โ โโโ default.yaml # ROS2 ํ๋ผ๋ฏธํฐ (ํ ํฝ, ๋ง์ปค, ๋ก๋ด ์ค์ )
โ
โโโ hand_eye_calibration/ # Python ํจํค์ง
โโโ gui_node.py # ๋ฉ์ธ ROS2 ๋
ธ๋ + PyQt5 GUI
โ
โโโ calibration/ # ์บ๋ฆฌ๋ธ๋ ์ด์
์๊ณ ๋ฆฌ์ฆ
โ โโโ aruco_detector.py # ArUco ๋ง์ปค ๊ฒ์ถ
โ โโโ tsai_lenz.py # Tsai-Lenz ์๋ฒ
โ โโโ dual_quaternion_ransac.py # DQ RANSAC ๋ํผ
โ โโโ visualization.py # 3D ์๊ฐํ (Matplotlib)
โ โโโ data_io.py # ํฌ์ฆ ๋ฐ์ดํฐ CSV ์ ์ฅ/๋ก๋
โ
โโโ robot_interface/ # ๋ก๋ด ํฌ์ฆ ํ๋
โ โโโ pose_reader_interface.py # PoseReader ABC
โ โโโ ur_direct_pose_reader.py # UR read-only ์์ผ (ํฌํธ 30003)
โ โโโ ros2_tf_pose_reader.py # ROS2 TF ๋ฐฉ์
โ
โโโ hand_eye_calibration_lib/ # ethz-asl DQ ๋ผ์ด๋ธ๋ฌ๋ฆฌ (์ถ์ถ)
โ โโโ dual_quaternion.py
โ โโโ quaternion.py
โ โโโ dual_quaternion_hand_eye_calibration.py
โ
โโโ handeye_4dof_lib/ # 4-DOF ์๋ฒ (์ถ์ถ)
โโโ calibrator.py
โโโ pose_selector.py
# 1. Docker ์ด๋ฏธ์ง ๊ฐ์ ธ์ค๊ธฐ
docker pull hhanoo/project:hand-eye-calibration-humble
# 2. (Orbbec ์ฌ์ฉ ์, ์ต์ด 1ํ) ํธ์คํธ์์ udev rules ์ค์น
sudo bash src/OrbbecSDK_ROS2/orbbec_camera/scripts/install_udev_rules.sh
sudo udevadm control --reload-rules && sudo udevadm trigger
# ์ดํ ์นด๋ฉ๋ผ USB๋ฅผ ๋ฝ์๋ค ๋ค์ ์ฐ๊ฒฐ
# 3. ์ปจํ
์ด๋ ์คํ (X11 ํฌ์๋ฉ ํฌํจ)
cd Hand-eye_calibration/docker
./run.sh
# 4. ๋น๋ (์ปจํ
์ด๋ ๋ด๋ถ) โ build ํจ์๊ฐ Release ๋น๋ + source๋ฅผ ํจ๊ป ์คํ
build
# 5. ์นด๋ฉ๋ผ์ GUI๋ฅผ ๊ฐ๊ฐ ์คํ (ํฐ๋ฏธ๋ 2๊ฐ, ์ถ๊ฐ ์
ธ์ ./run.sh ์ฌ์คํ์ผ๋ก attach)
# ํฐ๋ฏธ๋ 1: ์นด๋ฉ๋ผ (RealSense ๋๋ Orbbec)
camera-realsense # ๋๋ camera-orbbec
# ํฐ๋ฏธ๋ 2: GUI (์ฌ์ฉ ์นด๋ฉ๋ผ์ ๋ง์ถฐ ํจ์ ์ ํ)
gui-realsense # ๋๋ gui-orbbec์ผ์๋ณ launch ์ต์ ยทํ ํฝ์ ์คํ > ๊ฐ๋ณ ๋ชจ๋ ์ฐธ๊ณ .
์ฌ์ฉ ์ผ์์ ๋๋ผ์ด๋ฒ๋ฅผ ๋จผ์ ์ค์น โ ์ ์ค์น > Method 2 > ์์คํ ์์กด์ฑ ์ฐธ๊ณ .
rosdep install --from-paths src --ignore-src -r -y
colcon build --symlink-install \
--event-handlers console_direct+ \
--cmake-args -DCMAKE_BUILD_TYPE=Release
source install/setup.bash-DCMAKE_BUILD_TYPE=Release: ์ ์ฒด ํจํค์ง๋ฅผ-O3 -DNDEBUG๋ก ์ต์ ํ ๋น๋ (์นด๋ฉ๋ผ ๋๋ผ์ด๋ฒ ์ค์๊ฐ์ฑ ํ๋ณด์ ํ์)--event-handlers console_direct+: ๋น๋ ๋ก๊ทธ๋ฅผ ์ค์๊ฐ ์คํธ๋ฆผ์ผ๋ก ์ถ๋ ฅ (์ค๋ ๊ฑธ๋ฆฌ๋ C++ ํจํค์ง ์งํ ์ํฉ ํ์ธ)
์ดํ ์นด๋ฉ๋ผ์ GUI๋ฅผ ๊ฐ๊ฐ ์คํ โ ์คํ > ๊ฐ๋ณ ๋ชจ๋ ์ฐธ๊ณ .
| ํญ๋ชฉ | ์๊ตฌ์ฌํญ |
|---|---|
| OS | Ubuntu 22.04 |
| ROS2 | Humble |
| Python | 3.10 |
| Camera | Intel RealSense D415 / D435, Orbbec Femto Mega/Bolt, Gemini 2 ๋ฑ (USB 3.0) |
| ๋ก๋ด | ์ฐ๊ฒฐ ๋ฐฉ์ | ๋น๊ณ |
|---|---|---|
| UR | TCP ์์ผ (ํฌํธ 30003, read-only) | ํ๋ํธ ์ ์ด ์ ์ง |
| Doosan | DRFL ๋ชจ๋ํฐ๋ง (dsr_pose_reader) | ํ๋ํธ ์ ์ด ์ ์ง |
| ๊ธฐํ | ROS2 TF ๋ฐฉ์ | TF ํผ๋ธ๋ฆฌ์ ๋ ธ๋ ํ์ |
ROS2 ํจํค์ง:
- rclpy, rclcpp, sensor_msgs, geometry_msgs, cv_bridge, tf2_ros
- ์นด๋ฉ๋ผ ๋๋ผ์ด๋ฒ:
realsense2_camera(v4.55.1) ๋๋OrbbecSDK_ROS2(v2-main) โ VCS๋ก ์ ํ ์ค์น
Python ํจํค์ง:
- numpy (<2.0), scipy, sympy
- opencv-contrib-python-headless 4.10.0.84 (ArUco ๋ชจ๋ ํฌํจ)
- PyQt5, matplotlib
C++ ๋ผ์ด๋ธ๋ฌ๋ฆฌ (vendored):
- DRFL (Doosan Robotics, BSD)
- Poco (PocoFoundation, PocoNet)
| ํจํค์ง | ์ถ์ฒ | ์ฉ๋ |
|---|---|---|
| hand_eye_calibration | https://github.com/ethz-asl/hand_eye_calibration | DQ RANSAC ์๋ฒ |
| handeye-4dof | https://github.com/QuantuMope/handeye-4dof | 4-DOF ์บ๋ฆฌ๋ธ๋ ์ด์ |
| realsense-ros | https://github.com/realsenseai/realsense-ros (v4.55.1) | RealSense ROS2 ๋๋ผ์ด๋ฒ |
| OrbbecSDK_ROS2 | https://github.com/orbbec/OrbbecSDK_ROS2 (v2-main) | Orbbec ROS2 ๋๋ผ์ด๋ฒ |
| DRFL | https://robotlab.doosanrobotics.com/ko/Index | Doosan ๋ก๋ด ๋ชจ๋ํฐ๋ง |
# 1. Docker ์ด๋ฏธ์ง ๊ฐ์ ธ์ค๊ธฐ
docker pull hhanoo/project:hand-eye-calibration-humble
# 2. (Orbbec ์ฌ์ฉ ์, ์ต์ด 1ํ) ํธ์คํธ์์ udev rules ์ค์น
# OrbbecSDK_ROS2๋ ์ปจํ
์ด๋์์ ์์ค ๋น๋๋๋ฏ๋ก udev rules๋ ํธ์คํธ์์ ์๋ ์ค์น ํ์
sudo bash src/OrbbecSDK_ROS2/orbbec_camera/scripts/install_udev_rules.sh
sudo udevadm control --reload-rules && sudo udevadm trigger
# ์ดํ ์นด๋ฉ๋ผ USB๋ฅผ ๋ฝ์๋ค ๋ค์ ์ฐ๊ฒฐ
# 3. ์ปจํ
์ด๋ ์คํ
cd Hand-eye_calibration/docker
./run.sh์ง์ ๋น๋ (๊ฐ๋ฐ์์ฉ)
# 0. ํ๋ก์ ํธ ๋ฃจํธ๋ก ์ด๋
cd Hand-eye_calibration/docker
# 1. ์ค์ ํ์ผ ์์ฑ ํ IMAGE_NAME์ ๋ก์ปฌ ์ด๋ฆ์ผ๋ก ๋ณ๊ฒฝ
cp config.sh.example config.sh
# config.sh์์ IMAGE_NAME="hand-eye-calibration-humble" ๋ก ์์
# 2. Docker ์ด๋ฏธ์ง ๋น๋
./build.sh
# 3. ์ปจํ
์ด๋ ์คํ
./run.sh# ROS2 ๊ณตํต ํจํค์ง ์์กด์ฑ
sudo apt install -y \
ros-humble-cv-bridge \
ros-humble-tf2-ros \
ros-humble-tf2-geometry-msgs \
ros-humble-image-transportpip3 install \
'numpy>=1.21.0,<2.0' \
scipy sympy \
opencv-contrib-python-headless==4.10.0.84 \
PyQt5 PyQt5-sip matplotlibsudo apt update && sudo apt install -y ros-humble-realsense2-*
# ์คํ ํ์ธ
ros2 launch realsense2_camera rs_launch.pyUSB 3.0 ์ฐ๊ฒฐ ํ์. ์ธ์ ๋ถ๊ฐ ์ ๋ฌธ์ ํด๊ฒฐ > RealSense ์นด๋ฉ๋ผ ์ธ์ ์ ๋จ ์ฐธ๊ณ .
sudo apt update && sudo apt install -y \
ros-humble-orbbec-camera \
ros-humble-orbbec-description
# ์ฅ์น ์ธ์ ํ์ธ & ์คํ
ros2 run orbbec_camera list_devices_node
ros2 launch orbbec_camera <model>.launch.pyudev rules์ apt ํจํค์ง์ ํฌํจ๋์ด ์๋ ์ค์น๋จ. ์ธ์ ๋ถ๊ฐ ์ sudo udevadm control --reload-rules && sudo udevadm trigger ์ฌ์คํ ๋๋ ์ฅ์น ์ฌ์ฐ๊ฒฐ.
์ฃผ์ <model> launch ํ์ผ ๋งคํ:
| ๋ชจ๋ธ | launch ํ์ผ |
|---|---|
| Femto Mega / Mega I | femto_mega.launch.py |
| Femto Bolt | femto_bolt.launch.py |
| Gemini 2 | gemini2.launch.py |
| Gemini 2 L | gemini2L.launch.py |
| Gemini 330 / 335 / 336 ์๋ฆฌ์ฆ | gemini_330_series.launch.py |
| Astra 2 | astra2.launch.py |
Docker ์ฌ์ฉ ์ ์ค์ > Docker ์ค์ ์
ORBBEC_MODEL์ ์ฌ์ฉ ๋ชจ๋ธ๋ก ์ง์ ํ๋ฉด ์ปจํ ์ด๋ ๋ด๋ถ์์camera-orbbecํจ์๋ก ๋ฐ๋ก ์คํ ๊ฐ๋ฅ.ํน์ ๋ฒ์ ๊ณ ์ ์ด๋ ์ต์ ์์ค ๋น๋๊ฐ ํ์ํ ๊ฒฝ์ฐ
camera_realsense.repos/camera_orbbec.repos๋ฅผ ์ด์ฉํvcs import+colcon build๋ฐฉ์๋ ์ฌ์ฉ ๊ฐ๋ฅ.
cd ~/ros2_ws
colcon build --symlink-install \
--event-handlers console_direct+ \
--cmake-args -DCMAKE_BUILD_TYPE=Release
source install/setup.bash-DCMAKE_BUILD_TYPE=Release๋ ์ํฌ์คํ์ด์ค ์ ์ฒด ํจํค์ง์ ์ ์ฉ๋ฉ๋๋ค. Orbbec OrbbecSDK_ROS2๋ Release ๋น๋๊ฐ ํนํ ๊ถ์ฅ๋ฉ๋๋ค. --event-handlers console_direct+๋ ๋น๋ ๋ก๊ทธ๋ฅผ ์ค์๊ฐ ์คํธ๋ฆผ์ผ๋ก ์ถ๋ ฅํฉ๋๋ค.
colcon build --symlink-install --packages-select hand_eye_calibration
colcon build --symlink-install --packages-select dsr_pose_readerrm -rf build install log
colcon build --symlink-install \
--event-handlers console_direct+ \
--cmake-args -DCMAKE_BUILD_TYPE=Release
source install/setup.bash์นด๋ฉ๋ผ์ GUI๋ ํญ์ ๋ณ๋ ํฐ๋ฏธ๋(๋๋ alias)๋ก ์คํํฉ๋๋ค. ์ฌ์ฉํ๋ ๋น์ ์ผ์๋ง๋ค launch ๋ช ๋ น์ด ๋ค๋ฅด๊ธฐ ๋๋ฌธ์, ํตํฉ launch ํ์ผ์ ์ ๊ณตํ์ง ์์ต๋๋ค.
์นด๋ฉ๋ผ: Intel RealSense (D415 / D435 ๋ฑ)
ros2 launch realsense2_camera rs_launch.py \
rgb_camera.color_profile:=1280x720x30 \
depth_module.depth_profile:=1280x720x30 \
align_depth.enable:=true- ํ ํฝ:
/camera/camera/color/image_raw,/camera/camera/color/camera_info - Docker:
camera-realsenseํจ์๋ก ๋์ผ ๋ช ๋ น ์คํ
์นด๋ฉ๋ผ: Orbbec (Femto Mega/Bolt, Gemini 2 / 330 ์๋ฆฌ์ฆ ๋ฑ)
ros2 run orbbec_camera list_devices_node # ์ฅ์น ์ธ์ ํ์ธ
ros2 launch orbbec_camera <model>.launch.py \
color_width:=1280 color_height:=720 color_fps:=30<model>:femto_mega,femto_bolt,gemini2,gemini2L,gemini_330_series,astra2๋ฑ- Docker:
camera-orbbecํจ์ ์ฌ์ฉ (๋ชจ๋ธยทํด์๋๋ config.sh์ORBBEC_MODEL,ORBBEC_COLOR_*๋ก ์ง์ ) - ํ ํฝ prefix๊ฐ RealSense(
/camera/camera/color/...)์ ๋ค๋ฅด๊ฒ/camera/color/...๋ก ํผ๋ธ๋ฆฌ์๋จ. Docker ํจ์gui-orbbec์ด ํด๋น ํ ํฝ์ ์๋ ์ค๋ฒ๋ผ์ด๋ํ์ฌ ๊ตฌ๋ (Native ์คํ ์ ์๋ GUI ๋จ๋ ์คํ ์์ ์ฐธ๊ณ )
GUI ๋จ๋ ์คํ:
# RealSense (config/default.yaml์ ํ ํฝ ๊ทธ๋๋ก ์ฌ์ฉ)
ros2 run hand_eye_calibration gui_node
# Orbbec (ํ ํฝ prefix๊ฐ ๋ค๋ฅด๋ฏ๋ก ํ๋ผ๋ฏธํฐ ์ค๋ฒ๋ผ์ด๋)
ros2 run hand_eye_calibration gui_node --ros-args \
-p image_topic:=/camera/color/image_raw \
-p camera_info_topic:=/camera/color/camera_infoUR ๋ก๋ด (UR Direct ๋ชจ๋):
GUI์์ UR Direct ๋ชจ๋๋ก ์ง์ ์ฐ๊ฒฐํ๋ฏ๋ก ๋ณ๋ ๋๋ผ์ด๋ฒ๊ฐ ํ์ ์์ต๋๋ค.
Doosan ๋ก๋ด (dsr_pose_reader + ROS2 TF ๋ชจ๋):
dsr_pose_reader๋ฅผ ๋จผ์ ์คํํ๊ณ , GUI์์ ROS2 TF ๋ชจ๋๋ก ์ฐ๊ฒฐํฉ๋๋ค.
# ํฐ๋ฏธ๋ 1: Doosan ํฌ์ฆ ๋ฆฌ๋
ros2 launch dsr_pose_reader dsr_pose_reader.launch.py
# ์ปค์คํ
yaml ์ง์
ros2 launch dsr_pose_reader dsr_pose_reader.launch.py config_file:=/path/to/custom.yaml
# ํฐ๋ฏธ๋ 2/3: ์นด๋ฉ๋ผ + GUI (์ "์นด๋ฉ๋ผ" ํญ๋ชฉ ์ฐธ๊ณ )
camera-realsense # ๋๋ camera-orbbec
gui-realsense # ๋๋ gui-orbbec๊ถ์ฅ: ์ง์
docker exec๋ก ์ปจํ ์ด๋์ ์ง์ ํ์ง ๋ง๊ณ ํญ์ run.sh๋ฅผ ์ฌ์ฉํ์ธ์.
run.sh๋ ๋์ปค ์ด๋ฏธ์ง ํ์ธ ยท X11 ๊ถํ ยท ๋ง์ดํธ ยท ํธ์คํธ ๊ถํ ๋ณต์(HOST_UID/HOST_GID) ยท ๊ธฐ์กด ์ปจํ ์ด๋ ์ฌ์ฌ์ฉ์ ํ ๋ฒ์ ์ฒ๋ฆฌํฉ๋๋ค.
cd docker
./run.sh
# ์ปจํ
์ด๋ ๋ด๋ถ โ build ํจ์๊ฐ Release ๋น๋ + source๋ฅผ ํจ๊ป ์คํ
build
# ์นด๋ฉ๋ผ์ GUI๋ฅผ ๋ณ๋ ์
ธ์์ ์คํ (์ถ๊ฐ ์
ธ์ ./run.sh ์ฌ์คํ์ผ๋ก attach)
camera-realsense # ๋๋ camera-orbbec
gui-realsense # ๋๋ gui-orbbec์ ์ฒด ๋ช ๋ น์ด ์ ์๋ commands.sh๋ฅผ ์ฐธ๊ณ ํ์ธ์.
| Command | ์ค๋ช | ์ฐธ๊ณ |
|---|---|---|
camera-realsense |
RealSense ์นด๋ฉ๋ผ ์คํ | โ |
camera-orbbec |
Orbbec ์นด๋ฉ๋ผ ์คํ | config.sh์ ORBBEC_MODEL (๋ชจ๋ธ), ORBBEC_COLOR_WIDTH/HEIGHT/FPS (ํด์๋) ์ฌ์ฉ |
gui-realsense |
GUI ์คํ (RealSense ํ ํฝ) | gui_node.py |
gui-orbbec |
GUI ์คํ (Orbbec ํ ํฝ) | image_topic / camera_info_topic ํ๋ผ๋ฏธํฐ๋ฅผ /camera/color/...๋ก ์ค๋ฒ๋ผ์ด๋ |
doosan |
Doosan ํฌ์ฆ ๋ฆฌ๋ ์คํ | dsr_pose_reader.launch.py |
build |
์ํฌ์คํ์ด์ค ๋น๋ | Release ๋น๋ + install/setup.bash ์ ์ฉ |
cmd-help |
์ฌ์ฉ ๊ฐ๋ฅํ ๋ช ๋ น์ด ๋ชฉ๋ก ์ถ๋ ฅ | ์ปจํ ์ด๋ ์ ์ ์ ์๋ ์ถ๋ ฅ |
๋ก๋ด ์ฐ๊ฒฐ โโโโโถ ๋ง์ปค ํ์ธ โโโโโถ ํฌ์ฆ ์์ง (15~30) โโโโโถ ์บ๋ฆฌ๋ธ๋ ์ด์
โโโโโถ ๊ฒฐ๊ณผ ํ์ธ โโโโโถ ์ ์ฅ
GUI ์๋จ์์:
- ๋ก๋ด ๋ชจ๋ ์ ํ:
UR Direct๋๋ROS2 TF - UR Direct: IP/Port ์ ๋ ฅ โ Connect (read-only ์์ผ, ํ๋ํธ ์ ์ด ์ ์ง)
- ROS2 TF: Base Frame / EE Frame ์ ๋ ฅ โ Connect (dsr_pose_reader ๋ฑ TF ํผ๋ธ๋ฆฌ์ ๋ ธ๋ ํ์)
- ์นด๋ฉ๋ผ ์์์ ArUco ๋ง์ปค๊ฐ ๋ณด์ด๋์ง ํ์ธ (์ด๋ก์ ์ถ์ด ๊ทธ๋ ค์ง)
- ๋ก๋ด์ ํ๋ํธ๋ก ๋ค์ํ ์์ธ๋ก ์ด๋
- Capture Pose ํด๋ฆญ โ ๋ก๋ด ํฌ์ฆ + ๋ง์ปค ํฌ์ฆ๊ฐ ์ ์ฅ๋จ
- 15~30๊ฐ ํฌ์ฆ๋ฅผ ๋ค์ํ ๊ฐ๋๋ก ์์ง
- ์๋ชป๋ ๋ฐ์ดํฐ๋ ์ ํ ํ Delete Selected
- ์๊ณ ๋ฆฌ์ฆ ์ ํ: DQ RANSAC (๊ธฐ๋ณธ, ๊ถ์ฅ) ๋๋ Tsai-Lenz
- Calibrate ํด๋ฆญ
- ๊ฒฐ๊ณผ: 4x4 ๋ณํ ํ๋ ฌ + RMSE ํ์
- Calibration Result: ์บ๋ฆฌ๋ธ๋ ์ด์ ๊ฒฐ๊ณผ๋ฅผ 3D ์ขํ๊ณ๋ก ์๊ฐํ
- Data Poses: ์์ง๋ ํฌ์ฆ ์์ 3D ํ๋กฏ์ผ๋ก ํ์ธ
- Save Result: ์บ๋ฆฌ๋ธ๋ ์ด์
๊ฒฐ๊ณผ๋ฅผ
data/calibration_result_*.txt์ ์ ์ฅ - Save Data: ํฌ์ฆ ๋ฐ์ดํฐ๋ฅผ
data/pose_pairs.csv์ ์ ์ฅ - Load Data: ์ด์ ์ ์ ์ฅํ CSV ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์ ์ฌ์บ๋ฆฌ๋ธ๋ ์ด์ ๊ฐ๋ฅ
IMAGE_NAME="hhanoo/project:hand-eye-calibration-humble" # Docker Hub ์ด๋ฏธ์ง (๊ธฐ๋ณธ๊ฐ)
CONTAINER_NAME="hand-eye-calibration-humble" # Docker ์ปจํ
์ด๋ ์ด๋ฆ
ROS_DOMAIN_ID=98 # ROS2 domain
XAUTHORITY_PATH="$HOME/.Xauthority" # ํธ์คํธ์ .Xauthority (Qt GUI / RViz ํ์์ฉ)
ORBBEC_MODEL="femto_bolt" # camera-orbbec ํจ์๊ฐ ์คํํ Orbbec ๋ชจ๋ธ
# (femto_mega, femto_bolt, gemini2, gemini2L, gemini_330_series ๋ฑ)
ORBBEC_COLOR_WIDTH=1280 # Orbbec color ์คํธ๋ฆผ ๋๋น
ORBBEC_COLOR_HEIGHT=720 # Orbbec color ์คํธ๋ฆผ ๋์ด
ORBBEC_COLOR_FPS=30 # Orbbec color ์คํธ๋ฆผ FPS
run.sh์คํ ์ docker pull hhanoo/project:hand-eye-calibration-humble๋ก ์ด๋ฏธ์ง๋ฅผ ๊ฐ์ ธ์ค์ธ์.์ง์ ๋น๋ํ๋ ค๋ฉด
IMAGE_NAME์"hand-eye-calibration-humble"๋ฑ์ผ๋ก ๋ณ๊ฒฝ ํ./build.sh๋ฅผ ์คํํ์ธ์.
RealSense / Orbbec์ HD ํ๋ ์์ ๊ธฐ๋ณธ Fast DDS์์ "Sequence Size Exceeds remaining buffer" ์ค๋ฅ๋ก drop๋๋ ๊ฒฝ์ฐ๊ฐ ์์ด, Docker ํ๊ฒฝ์ CycloneDDS๋ฅผ ๊ธฐ๋ณธ RMW๋ก ์ฌ์ฉํฉ๋๋ค.
- entrypoint.sh: ์ปจํ
์ด๋ ์ง์
์
RMW_IMPLEMENTATION=rmw_cyclonedds_cpp,CYCLONEDDS_URI=file:///etc/cyclonedds.xmlexport - cyclonedds.xml: RTPS ๋ ๋ฒจ์์ ๋์ฉ๋ ๋ฉ์์ง ์ฒ๋ฆฌ๋ฅผ ์์ ํํ๋ ํ๋ ๊ฐ
MaxMessageSize=65500B-- IP ๋ ๋ฒจ fragmentation ๋์ Cyclone์ด ์ง์ ๋ถํSocketReceiveBufferSize min="10MB"-- HD ํ๋ ์ burst ๋ฒํผ๋ง
- run.sh: ์ ๋ฒํผ ๊ฐ์ด ์ค์ ๋ก ์ ์ฉ๋๋๋ก ํธ์คํธ ์ปค๋ sysctl ํ๋ ์ํ (
sudoํ์)net.core.rmem_max / wmem_max = 64MBnet.ipv4.ipfrag_time=3,ipfrag_high_thresh=128MB
Native ํ๊ฒฝ์์๋ ๋์ฉ๋ ์ผ์ ๋ฉ์์ง๋ฅผ ๋ค๋ฃฐ ๋๋ ๋์ผํ RMW / sysctl ํ๋์ ๊ถ์ฅํฉ๋๋ค.
sudo apt install ros-humble-rmw-cyclonedds-cppํ ์ ํ๊ฒฝ๋ณ์๋ฅผ export ํ๋ฉด ๋ฉ๋๋ค.
hand_eye_calibration:
ros__parameters:
# ์นด๋ฉ๋ผ ํ ํฝ
image_topic: "/camera/camera/color/image_raw"
camera_info_topic: "/camera/camera/color/camera_info"
# ArUco ๋ง์ปค ์ค์
marker_dict: 10 # cv2.aruco.DICT_6X6_250
board_grid_shape: [5, 7] # ๊ทธ๋ฆฌ๋ ๋ณด๋ (cols, rows)
marker_length: 0.037 # ๋ง์ปค ํฌ๊ธฐ (m)
marker_separation: 0.003 # ๋ง์ปค ๊ฐ๊ฒฉ (m)
# ๋ก๋ด ์ค์
robot_mode: "ur_direct" # "ur_direct" ๋๋ "ros2_tf"
# ROS2 TF ์ค์
tf_base_frame: "base_link"
tf_ee_frame: "tool0"
# ๋ฐ์ดํฐ ์ ์ฅ ๊ฒฝ๋ก
data_dir: "data/"dsr_pose_reader:
ros__parameters:
robot_ip: "192.168.137.101" # Doosan ์ปจํธ๋กค๋ฌ IP
robot_port: 12345 # Doosan ์ปจํธ๋กค๋ฌ ํฌํธ
base_frame: "base_link" # TF base frame
ee_frame: "tool0" # TF end-effector frame
publish_rate: 30.0 # Hz
publish_tf: true # TF ๋ธ๋ก๋์บ์คํธ ํ์ฑํ| ํ๋ผ๋ฏธํฐ | ํ์ | ๊ธฐ๋ณธ๊ฐ | ์ค๋ช |
|---|---|---|---|
marker_length |
float | 0.037 | ArUco ๋ง์ปค ํ ๋ณ์ ์ค์ ํฌ๊ธฐ (m) -- ์ ํ๋์ ๋งค์ฐ ์ค์ |
marker_separation |
float | 0.003 | ๊ทธ๋ฆฌ๋ ๋ณด๋์์ ๋ง์ปค ๊ฐ ๊ฐ๊ฒฉ (m) |
board_grid_shape |
list | [5, 7] | ๊ทธ๋ฆฌ๋ ๋ณด๋์ (์ด, ํ) ์ |
robot_mode |
string | "ur_direct" | ๋ก๋ด ํฌ์ฆ ํ๋ ๋ฐฉ์ ("ur_direct" / "ros2_tf") |
dsr_pose_reader.launch.py:
| ์ธ์ | ๊ธฐ๋ณธ๊ฐ | ์ค๋ช |
|---|---|---|
config_file |
config/default.yaml |
ํ๋ผ๋ฏธํฐ YAML ํ์ผ ๊ฒฝ๋ก |
ros2 launch dsr_pose_reader dsr_pose_reader.launch.py config_file:=/path/to/custom.yaml| ๋ ธ๋ | ์ธ์ด | ํจํค์ง | ์ค๋ช |
|---|---|---|---|
hand_eye_calibration |
Python | hand_eye_calibration | ์บ๋ฆฌ๋ธ๋ ์ด์ GUI + ROS2 ๋ ธ๋ |
dsr_pose_reader |
C++ | dsr_pose_reader | Doosan ๋ก๋ด TCP ํฌ์ฆ ํผ๋ธ๋ฆฌ์ |
| Topic | Type | ๋ ธ๋ | ์ค๋ช |
|---|---|---|---|
/camera/camera/color/image_raw |
sensor_msgs/msg/Image |
hand_eye_calibration | RGB ์นด๋ฉ๋ผ ์ด๋ฏธ์ง |
/camera/camera/color/camera_info |
sensor_msgs/msg/CameraInfo |
hand_eye_calibration | ์นด๋ฉ๋ผ ๋ด๋ถ ํ๋ผ๋ฏธํฐ |
| Topic | Type | ๋ ธ๋ | ์ค๋ช |
|---|---|---|---|
tcp_pose |
geometry_msgs/msg/PoseStamped |
dsr_pose_reader | Doosan TCP ํฌ์ฆ (base ๊ธฐ์ค) |
/tf |
tf2_msgs/msg/TFMessage |
dsr_pose_reader | base_link โ tool0 ๋ณํ (์ต์ ) |
ํด๋น ํ๋ก์ ํธ๋ ROS2 ์๋น์ค๋ฅผ ์ฌ์ฉํ์ง ์์ต๋๋ค.
ํด๋น ํ๋ก์ ํธ๋ ์ปค์คํ ๋ฉ์์ง๋ฅผ ์ ์ํ์ง ์์ต๋๋ค. ๋ชจ๋ ์ธํฐํ์ด์ค๋ ํ์ค ROS2 ๋ฉ์์ง ํ์ ์ ์ฌ์ฉํฉ๋๋ค.
| ๋์ | ํ๋กํ ์ฝ | ํฌํธ | ๋ฐฉํฅ | ๋น๊ณ |
|---|---|---|---|---|
| UR ๋ก๋ด | TCP | 30003 | read-only | ๋ก๋ด์ ๋ช ๋ น ์ ์กํ์ง ์์ |
| Doosan ๋ก๋ด | TCP | 12345 | monitoring | DRFL ๋ชจ๋ํฐ๋ง ์ ์ฉ |
| RealSense ์นด๋ฉ๋ผ | USB 3.0 | - | - | ROS2 ํ ํฝ์ผ๋ก ์์ |
์ฆ์:
qt.qpa.plugin: Could not load the Qt platform plugin "xcb"
ํด๊ฒฐ:
- Dockerfile์์
opencv-contrib-python-headless์ฌ์ฉ (OpenCV ๋ฒ๋ค Qt ์ ๊ฑฐ) - ๋๋
gui_node.py์๋จ์os.environ.pop("QT_QPA_PLATFORM_PLUGIN_PATH", None)ํ์ธ
์ฆ์:
lsusb | grep Intel # ๊ฒฐ๊ณผ ์์
ํด๊ฒฐ:
sudo usermod -aG video $USER
sudo usermod -aG plugdev $USER- ์กฐ๋ช ์ด ์ถฉ๋ถํ์ง ํ์ธ
- ์นด๋ฉ๋ผ์์ ๋ง์ปค๊น์ง ๊ฑฐ๋ฆฌ: 0.3m ~ 2m ๊ถ์ฅ
marker_length๊ฐ์ด ์ค์ ๋ง์ปค ํฌ๊ธฐ์ ์ผ์นํ๋์ง ํ์ธ
์ฆ์:
AttributeError: module 'numpy' has no attribute 'bool'
ํด๊ฒฐ:
pip3 install 'numpy>=1.21.0,<2.0'์ฆ์:
cannot open display: :0
ํด๊ฒฐ:
# ํธ์คํธ์์ X11 ์ ๊ทผ ํ์ฉ
xhost +local:docker
# DISPLAY ํ๊ฒฝ๋ณ์ ํ์ธ
echo $DISPLAY์ฆ์:
list_devices_node๋ ์นด๋ฉ๋ผ๋ฅผ ์ ์์ ์ผ๋ก ์ฐพ์ง๋ง, launch ์งํ ์ฝ 10์ด ๋ค ํ์์์์ผ๋ก ํ๋ก์ธ์ค๊ฐ ์ข
๋ฃ๋จ.
ํด๊ฒฐ ์์:
- ํธ์คํธ์์ OrbbecViewer ๋ฑ ์นด๋ฉ๋ผ๋ฅผ ์ ์ ์ค์ธ ํ๋ก์ธ์ค ์ข ๋ฃ
- ์ปจํ
์ด๋๋ฅผ ์์ ํ ๋ด๋ฆผ:
docker stop <name> && docker rm <name> - ์นด๋ฉ๋ผ USB + 12V ์ ์ ์ด๋ํฐ ๋ชจ๋ ๋ฝ๊ณ 5์ด ํ ์ฌ์ฐ๊ฒฐ
- ์ปจํ ์ด๋ ์ฌ์คํ ํ launch ์ฌ์๋
Femto Bolt๋ 12V ์ธ๋ถ ์ ์ ์ด๋ํฐ๊ฐ ํ์์ ๋๋ค (USB ๋ฒ์ค ํ์๋ง์ผ๋ก๋ ๋ถ์กฑ).
์ฆ์:
RealSense / Orbbec HD ํ๋ ์ ๊ตฌ๋ ์ Fast DDS๊ฐ ๋ฉ์์ง๋ฅผ dropํ๊ฑฐ๋ ์ ์ค๋ฅ ๋ก๊ทธ ์ถ๋ ฅ.
ํด๊ฒฐ:
Docker ํ๊ฒฝ์ ์ค์ > DDS (CycloneDDS) ์ค์ ์ ๋ฐ๋ผ ์ด๋ฏธ CycloneDDS + sysctl ํ๋์ด ์ ์ฉ๋์ด ์์ต๋๋ค. Native ํ๊ฒฝ์ด๊ฑฐ๋ run.sh๋ฅผ ๊ฑฐ์น์ง ์๊ณ ์ปจํ
์ด๋๋ฅผ ์คํํ ๊ฒฝ์ฐ ํธ์คํธ์์ ๋ค์์ ์๋ ์ ์ฉํ์ธ์.
sudo sysctl -w net.core.rmem_max=67108864
sudo sysctl -w net.core.wmem_max=67108864
sudo sysctl -w net.ipv4.ipfrag_high_thresh=134217728
export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp
export CYCLONEDDS_URI=file:///etc/cyclonedds.xml # ๋๋ ๋ก์ปฌ ๊ฒฝ๋ก์ด ํ๋ก์ ํธ๋ MIT ๋ผ์ด์ ์ค๋ก ๋ฐฐํฌ๋ฉ๋๋ค. ์์ธํ ๋ด์ฉ์ LICENSE ํ์ผ์ ์ฐธ์กฐํ์ธ์.
hhanoo (woo980711@gmail.com)


