diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..2f8d64b
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,18 @@
+---
+Language: Cpp
+BasedOnStyle: Google
+
+AccessModifierOffset: -2
+AlignAfterOpenBracket: AlwaysBreak
+BraceWrapping:
+ AfterClass: true
+ AfterFunction: true
+ AfterNamespace: true
+ AfterStruct: true
+BreakBeforeBraces: Custom
+ColumnLimit: 100
+ConstructorInitializerIndentWidth: 0
+ContinuationIndentWidth: 2
+DerivePointerAlignment: false
+PointerAlignment: Middle
+ReflowComments: false
\ No newline at end of file
diff --git a/.clang-tidy b/.clang-tidy
new file mode 100644
index 0000000..bf3d848
--- /dev/null
+++ b/.clang-tidy
@@ -0,0 +1,55 @@
+---
+Checks: '-*,
+ performance-*,
+ -performance-unnecessary-value-param,
+ llvm-namespace-comment,
+ modernize-redundant-void-arg,
+ modernize-use-nullptr,
+ modernize-use-default,
+ modernize-use-override,
+ modernize-loop-convert,
+ modernize-make-shared,
+ modernize-make-unique,
+ misc-unused-parameters,
+ readability-named-parameter,
+ readability-redundant-smartptr-get,
+ readability-redundant-string-cstr,
+ readability-simplify-boolean-expr,
+ readability-container-size-empty,
+ readability-identifier-naming,
+ '
+HeaderFilterRegex: ''
+AnalyzeTemporaryDtors: false
+CheckOptions:
+ - key: llvm-namespace-comment.ShortNamespaceLines
+ value: '10'
+ - key: llvm-namespace-comment.SpacesBeforeComments
+ value: '2'
+ - key: misc-unused-parameters.StrictMode
+ value: '1'
+ - key: readability-braces-around-statements.ShortStatementLines
+ value: '2'
+ # type names
+ - key: readability-identifier-naming.ClassCase
+ value: CamelCase
+ - key: readability-identifier-naming.EnumCase
+ value: CamelCase
+ - key: readability-identifier-naming.UnionCase
+ value: CamelCase
+ # method names
+ - key: readability-identifier-naming.MethodCase
+ value: camelBack
+ # variable names
+ - key: readability-identifier-naming.VariableCase
+ value: lower_case
+ - key: readability-identifier-naming.ClassMemberSuffix
+ value: '_'
+ # const static or global variables are UPPER_CASE
+ - key: readability-identifier-naming.EnumConstantCase
+ value: UPPER_CASE
+ - key: readability-identifier-naming.StaticConstantCase
+ value: UPPER_CASE
+ - key: readability-identifier-naming.ClassConstantCase
+ value: UPPER_CASE
+ - key: readability-identifier-naming.GlobalVariableCase
+ value: UPPER_CASE
\ No newline at end of file
diff --git a/.github/workflows/ros_ci.yml b/.github/workflows/ros_ci.yml
new file mode 100644
index 0000000..17f4850
--- /dev/null
+++ b/.github/workflows/ros_ci.yml
@@ -0,0 +1,30 @@
+name: Build and Test (humble)
+on:
+ push:
+ branches: [ main ]
+ pull_request:
+ branches: [ main ]
+jobs:
+ build-and-test:
+ runs-on: ubuntu-22.04
+ steps:
+ - name: Setup ROS 2
+ uses: ros-tooling/setup-ros@v0.4
+ with:
+ required-ros-distributions: humble
+ - name: Build rm_auto_aim
+ uses: ros-tooling/action-ros-ci@v0.2
+ with:
+ package-name: rm_auto_aim
+ target-ros2-distro: humble
+ skip-tests: true
+ - name: Test armor_detector
+ run: |
+ source /opt/ros/humble/setup.sh
+ cd ros_ws
+ colcon test --packages-select armor_detector --event-handlers console_cohesion+ --return-code-on-test-failure
+ - name: Test armor_tracker
+ run: |
+ source /opt/ros/humble/setup.sh
+ cd ros_ws
+ colcon test --packages-select armor_tracker --event-handlers console_cohesion+ --return-code-on-test-failure
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..3f4f07a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,170 @@
+
+# Created by https://www.gitignore.io/api/c++,linux,macos,clion
+# Edit at https://www.gitignore.io/?templates=c++,linux,macos,clion
+
+### C++ ###
+# Prerequisites
+*.d
+
+# Compiled Object files
+*.slo
+*.lo
+*.o
+*.obj
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Compiled Dynamic libraries
+*.so
+*.dylib
+*.dll
+
+# Fortran module files
+*.mod
+*.smod
+
+# Compiled Static libraries
+*.lai
+*.la
+*.a
+*.lib
+
+# Executables
+*.exe
+*.out
+*.app
+
+### CLion ###
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn. Uncomment if using
+# auto-import.
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
+### CLion Patch ###
+# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
+
+# *.iml
+# modules.xml
+# .idea/misc.xml
+# *.ipr
+
+# Sonarlint plugin
+.idea/**/sonarlint/
+
+# SonarQube Plugin
+.idea/**/sonarIssues.xml
+
+# Markdown Navigator plugin
+.idea/**/markdown-navigator.xml
+.idea/**/markdown-navigator/
+
+### Linux ###
+*~
+
+# temporary files which can be created if a process still has a handle open of a deleted file
+.fuse_hidden*
+
+# KDE directory preferences
+.directory
+
+# Linux trash folder which might appear on any partition or disk
+.Trash-*
+
+# .nfs files are created when an open file is removed but is still being accessed
+.nfs*
+
+### macOS ###
+# General
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+# End of https://www.gitignore.io/api/c++,linux,macos,clion
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..f5c6472
--- /dev/null
+++ b/README.md
@@ -0,0 +1,61 @@
+# rm_auto_aim
+
+## Overview
+
+RoboMaster 装甲板自瞄算法模块
+
+
+
+该项目为 [rm_vision](https://github.com/chenjunnn/rm_vision) 的子模块
+
+若有帮助请Star这个项目,感谢~
+
+### License
+
+The source code is released under a [MIT license](rm_auto_aim/LICENSE).
+
+[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
+
+Author: Chen Jun
+
+运行环境:Ubuntu 22.04 / ROS2 Humble (未在其他环境下测试)
+
+![Build Status](https://github.com/chenjunnn/rm_auto_aim/actions/workflows/ros_ci.yml/badge.svg)
+
+## Building from Source
+
+### Building
+
+在 Ubuntu 22.04 环境下安装 [ROS2 Humble](https://docs.ros.org/en/humble/Installation/Ubuntu-Install-Debians.html)
+
+创建 ROS 工作空间后 clone 项目,使用 rosdep 安装依赖后编译代码
+
+ cd ros_ws/src
+ git clone https://github.com/chenjunnn/rm_auto_aim.git
+ cd ..
+ rosdep install --from-paths src --ignore-src -r -y
+ colcon build --symlink-install --packages-up-to auto_aim_bringup
+
+### Testing
+
+Run the tests with
+
+ colcon test --packages-up-to auto_aim_bringup
+
+## Packages
+
+- [armor_detector](armor_detector)
+
+ 订阅相机参数及图像流进行装甲板的识别并解算三维位置,输出识别到的装甲板在输入frame下的三维位置 (一般是以相机光心为原点的相机坐标系)
+
+- [armor_tracker](armor_tracker)
+
+ 订阅识别节点发布的装甲板三维位置及机器人的坐标转换信息,将装甲板三维位置变换到指定惯性系(一般是以云台中心为原点,IMU 上电时的 Yaw 朝向为 X 轴的惯性系)下,然后将装甲板目标送入跟踪器中,输出跟踪机器人在指定惯性系下的状态
+
+- auto_aim_interfaces
+
+ 定义了识别节点和处理节点的接口以及定义了用于 Debug 的信息
+
+- auto_aim_bringup
+
+ 包含启动识别节点和处理节点的默认参数文件及 launch 文件
diff --git a/armor_detector/CMakeLists.txt b/armor_detector/CMakeLists.txt
new file mode 100644
index 0000000..f1cd143
--- /dev/null
+++ b/armor_detector/CMakeLists.txt
@@ -0,0 +1,68 @@
+cmake_minimum_required(VERSION 3.10)
+project(armor_detector)
+
+## Use C++14
+set(CMAKE_CXX_STANDARD 14)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+## By adding -Wall and -Werror, the compiler does not ignore warnings anymore,
+## enforcing cleaner code.
+add_definitions(-Wall -Werror)
+
+## Export compile commands for clangd
+set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
+
+#######################
+## Find dependencies ##
+#######################
+
+find_package(ament_cmake_auto REQUIRED)
+find_package(OpenCV REQUIRED)
+ament_auto_find_build_dependencies()
+
+###########
+## Build ##
+###########
+
+ament_auto_add_library(${PROJECT_NAME} SHARED
+ DIRECTORY src
+)
+
+target_include_directories(${PROJECT_NAME} PUBLIC ${OpenCV_INCLUDE_DIRS})
+target_link_libraries(${PROJECT_NAME} ${OpenCV_LIBS})
+
+rclcpp_components_register_node(${PROJECT_NAME}
+ PLUGIN rm_auto_aim::ArmorDetectorNode
+ EXECUTABLE armor_detector_node
+)
+
+#############
+## Testing ##
+#############
+
+if(BUILD_TESTING)
+ find_package(ament_lint_auto REQUIRED)
+ list(APPEND AMENT_LINT_AUTO_EXCLUDE
+ ament_cmake_copyright
+ ament_cmake_uncrustify
+ ament_cmake_cpplint
+ )
+ ament_lint_auto_find_test_dependencies()
+
+ find_package(ament_cmake_gtest)
+ ament_add_gtest(test_node_startup test/test_node_startup.cpp)
+ target_link_libraries(test_node_startup ${PROJECT_NAME})
+
+ ament_add_gtest(test_number_cls test/test_number_cls.cpp)
+ target_link_libraries(test_number_cls ${PROJECT_NAME})
+
+endif()
+
+#############
+## Install ##
+#############
+
+ament_auto_package(
+ INSTALL_TO_SHARE
+ model
+)
diff --git a/armor_detector/README.md b/armor_detector/README.md
new file mode 100644
index 0000000..d5d49db
--- /dev/null
+++ b/armor_detector/README.md
@@ -0,0 +1,104 @@
+# armor_detector
+
+- [DetectorNode](#basedetectornode)
+ - [Detector](#detector)
+ - [NumberClassifier](#numberclassifier)
+ - [PnPSolver](#pnpsolver)
+
+## 识别节点
+
+订阅相机参数及图像流进行装甲板的识别并解算三维位置,输出识别到的装甲板在输入frame下的三维位置 (一般是以相机光心为原点的相机坐标系)
+
+### DetectorNode
+装甲板识别节点
+
+包含[Detector](#detector)
+包含[PnPSolver](#pnpsolver)
+
+订阅:
+- 相机参数 `/camera_info`
+- 彩色图像 `/image_raw`
+
+发布:
+- 识别目标 `/detector/armors`
+
+静态参数:
+- 筛选灯条的参数 `light`
+ - 长宽比范围 `min/max_ratio`
+ - 最大倾斜角度 `max_angle`
+- 筛选灯条配对结果的参数 `armor`
+ - 两灯条的最小长度之比(短边/长边)`min_light_ratio `
+ - 装甲板两灯条中心的距离范围(大装甲板)`min/max_large_center_distance`
+ - 装甲板两灯条中心的距离范围(小装甲板)`min/max_small_center_distance`
+ - 装甲板的最大倾斜角度 `max_angle`
+
+动态参数:
+- 是否发布 debug 信息 `debug`
+- 识别目标颜色 `detect_color`
+- 二值化的最小阈值 `binary_thres`
+- 数字分类器 `classifier`
+ - 置信度阈值 `threshold`
+
+## Detector
+装甲板识别器
+
+### preprocessImage
+预处理
+
+| ![](docs/raw.png) | ![](docs/hsv_bin.png) | ![](docs/gray_bin.png) |
+| :---------------: | :-------------------: | :--------------------: |
+| 原图 | 通过颜色二值化 | 通过灰度二值化 |
+
+由于一般工业相机的动态范围不够大,导致若要能够清晰分辨装甲板的数字,得到的相机图像中灯条中心就会过曝,灯条中心的像素点的值往往都是 R=B。根据颜色信息来进行二值化效果不佳,因此此处选择了直接通过灰度图进行二值化,将灯条的颜色判断放到后续处理中。
+
+### findLights
+寻找灯条
+
+通过 findContours 得到轮廓,再通过 minAreaRect 获得最小外接矩形,对其进行长宽比和倾斜角度的判断,可以高效的筛除形状不满足的亮斑。
+
+判断灯条颜色这里采用了对轮廓内的的R/B值求和,判断两和的的大小的方法,若 `sum_r > sum_b` 则认为是红色灯条,反之则认为是蓝色灯条。
+
+| ![](docs/red.png) | ![](docs/blue.png) |
+| :---------------: | :----------------: |
+| 提取出的红色灯条 | 提取出的蓝色灯条 |
+
+### matchLights
+配对灯条
+
+根据 `detect_color` 选择对应颜色的灯条进行两两配对,首先筛除掉两条灯条中间包含另一个灯条的情况,然后根据两灯条的长度之比、两灯条中心的距离、配对出装甲板的倾斜角度来筛选掉条件不满足的结果,得到形状符合装甲板特征的灯条配对。
+
+## NumberClassifier
+数字分类器
+
+### extractNumbers
+提取数字
+
+| ![](docs/num_raw.png) | ![](docs/num_warp.png) | ![](docs/num_roi.png) | ![](docs/num_bin.png) |
+| :-------------------: | :--------------------: | :-------------------: | :-------------------: |
+| 原图 | 透视变换 | 取ROI | 二值化 |
+
+将每条灯条上下的角点拉伸到装甲板的上下边缘作为待变换点,进行透视变换,再对变换后的图像取ROI。考虑到数字图案实质上就是黑色背景+白色图案,所以此处使用了大津法进行二值化。
+
+### Classify
+分类
+
+由于上一步对于数字的提取效果已经非常好,数字图案的特征非常清晰明显,装甲板的远近、旋转都不会使图案产生过多畸变,且图案像素点少,所以我们使用多层感知机(MLP)进行分类。
+
+网络结构中定义了两个隐藏层和一个分类层,将二值化后的数字展平成 20x28=560 维的输入,送入网络进行分类。
+
+网络结构:
+
+![](docs/model.svg)
+
+
+
+
+
+## PnPSolver
+PnP解算器
+
+[Perspective-n-Point (PnP) pose computation](https://docs.opencv.org/4.x/d5/d1f/calib3d_solvePnP.html)
+
+PnP解算器将 `cv::solvePnP()` 封装,接口中传入 `Armor` 类型的数据即可得到 `geometry_msgs::msg::Point` 类型的三维坐标。
+
+考虑到装甲板的四个点在一个平面上,在PnP解算方法上我们选择了 `cv::SOLVEPNP_IPPE` (Method is based on the paper of T. Collins and A. Bartoli. ["Infinitesimal Plane-Based Pose Estimation"](https://link.springer.com/article/10.1007/s11263-014-0725-5). This method requires coplanar object points.)
diff --git a/armor_detector/docs/blue.png b/armor_detector/docs/blue.png
new file mode 100644
index 0000000..f10db16
Binary files /dev/null and b/armor_detector/docs/blue.png differ
diff --git a/armor_detector/docs/gray_bin.png b/armor_detector/docs/gray_bin.png
new file mode 100644
index 0000000..5c35e71
Binary files /dev/null and b/armor_detector/docs/gray_bin.png differ
diff --git a/armor_detector/docs/hsv_bin.png b/armor_detector/docs/hsv_bin.png
new file mode 100644
index 0000000..075e1c6
Binary files /dev/null and b/armor_detector/docs/hsv_bin.png differ
diff --git a/armor_detector/docs/model.svg b/armor_detector/docs/model.svg
new file mode 100644
index 0000000..48b6f52
--- /dev/null
+++ b/armor_detector/docs/model.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/armor_detector/docs/num_bin.png b/armor_detector/docs/num_bin.png
new file mode 100644
index 0000000..1d47d99
Binary files /dev/null and b/armor_detector/docs/num_bin.png differ
diff --git a/armor_detector/docs/num_raw.png b/armor_detector/docs/num_raw.png
new file mode 100644
index 0000000..302c852
Binary files /dev/null and b/armor_detector/docs/num_raw.png differ
diff --git a/armor_detector/docs/num_roi.png b/armor_detector/docs/num_roi.png
new file mode 100644
index 0000000..b542bc5
Binary files /dev/null and b/armor_detector/docs/num_roi.png differ
diff --git a/armor_detector/docs/num_warp.png b/armor_detector/docs/num_warp.png
new file mode 100644
index 0000000..3fe6d74
Binary files /dev/null and b/armor_detector/docs/num_warp.png differ
diff --git a/armor_detector/docs/raw.png b/armor_detector/docs/raw.png
new file mode 100644
index 0000000..af656a0
Binary files /dev/null and b/armor_detector/docs/raw.png differ
diff --git a/armor_detector/docs/red.png b/armor_detector/docs/red.png
new file mode 100644
index 0000000..6e6bca0
Binary files /dev/null and b/armor_detector/docs/red.png differ
diff --git a/armor_detector/docs/result.png b/armor_detector/docs/result.png
new file mode 100644
index 0000000..df8bb14
Binary files /dev/null and b/armor_detector/docs/result.png differ
diff --git a/armor_detector/docs/train_acc.png b/armor_detector/docs/train_acc.png
new file mode 100644
index 0000000..5814d6b
Binary files /dev/null and b/armor_detector/docs/train_acc.png differ
diff --git a/armor_detector/docs/train_loss.png b/armor_detector/docs/train_loss.png
new file mode 100644
index 0000000..12a5c6b
Binary files /dev/null and b/armor_detector/docs/train_loss.png differ
diff --git a/armor_detector/docs/val_acc.png b/armor_detector/docs/val_acc.png
new file mode 100644
index 0000000..c77a786
Binary files /dev/null and b/armor_detector/docs/val_acc.png differ
diff --git a/armor_detector/docs/val_loss.png b/armor_detector/docs/val_loss.png
new file mode 100644
index 0000000..28793eb
Binary files /dev/null and b/armor_detector/docs/val_loss.png differ
diff --git a/armor_detector/include/armor_detector/armor.hpp b/armor_detector/include/armor_detector/armor.hpp
new file mode 100644
index 0000000..fec27bd
--- /dev/null
+++ b/armor_detector/include/armor_detector/armor.hpp
@@ -0,0 +1,73 @@
+// Copyright 2022 Chen Jun
+// Licensed under the MIT License.
+
+#ifndef ARMOR_DETECTOR__ARMOR_HPP_
+#define ARMOR_DETECTOR__ARMOR_HPP_
+
+#include
+
+// STL
+#include
+#include
+
+namespace rm_auto_aim
+{
+const int RED = 0;
+const int BLUE = 1;
+
+enum class ArmorType { SMALL, LARGE, INVALID };
+const std::string ARMOR_TYPE_STR[3] = {"small", "large", "invalid"};
+
+struct Light : public cv::RotatedRect
+{
+ Light() = default;
+ explicit Light(cv::RotatedRect box) : cv::RotatedRect(box)
+ {
+ cv::Point2f p[4];
+ box.points(p);
+ std::sort(p, p + 4, [](const cv::Point2f & a, const cv::Point2f & b) { return a.y < b.y; });
+ top = (p[0] + p[1]) / 2;
+ bottom = (p[2] + p[3]) / 2;
+
+ length = cv::norm(top - bottom);
+ width = cv::norm(p[0] - p[1]);
+
+ tilt_angle = std::atan2(std::abs(top.x - bottom.x), std::abs(top.y - bottom.y));
+ tilt_angle = tilt_angle / CV_PI * 180;
+ }
+
+ int color;
+ cv::Point2f top, bottom;
+ double length;
+ double width;
+ float tilt_angle;
+};
+
+struct Armor
+{
+ Armor() = default;
+ Armor(const Light & l1, const Light & l2)
+ {
+ if (l1.center.x < l2.center.x) {
+ left_light = l1, right_light = l2;
+ } else {
+ left_light = l2, right_light = l1;
+ }
+ center = (left_light.center + right_light.center) / 2;
+ }
+
+ // Light pairs part
+ Light left_light, right_light;
+ cv::Point2f center;
+ ArmorType type;
+
+ // Number part
+ cv::Mat number_img;
+ std::string number;
+ float confidence;
+ std::string classfication_result;
+};
+
+} // namespace rm_auto_aim
+
+#endif // ARMOR_DETECTOR__ARMOR_HPP_
diff --git a/armor_detector/include/armor_detector/detector.hpp b/armor_detector/include/armor_detector/detector.hpp
new file mode 100644
index 0000000..5e3cd74
--- /dev/null
+++ b/armor_detector/include/armor_detector/detector.hpp
@@ -0,0 +1,83 @@
+// Copyright 2022 Chen Jun
+// Licensed under the MIT License.
+
+#ifndef ARMOR_DETECTOR__DETECTOR_HPP_
+#define ARMOR_DETECTOR__DETECTOR_HPP_
+
+// OpenCV
+#include
+#include
+
+// STD
+#include
+#include
+#include
+
+#include "armor_detector/armor.hpp"
+#include "armor_detector/number_classifier.hpp"
+#include "auto_aim_interfaces/msg/debug_armors.hpp"
+#include "auto_aim_interfaces/msg/debug_lights.hpp"
+
+namespace rm_auto_aim
+{
+class Detector
+{
+public:
+ struct LightParams
+ {
+ // width / height
+ double min_ratio;
+ double max_ratio;
+ // vertical angle
+ double max_angle;
+ };
+
+ struct ArmorParams
+ {
+ double min_light_ratio;
+ // light pairs distance
+ double min_small_center_distance;
+ double max_small_center_distance;
+ double min_large_center_distance;
+ double max_large_center_distance;
+ // horizontal angle
+ double max_angle;
+ };
+
+ Detector(const int & bin_thres, const int & color, const LightParams & l, const ArmorParams & a);
+
+ std::vector detect(const cv::Mat & input);
+
+ cv::Mat preprocessImage(const cv::Mat & input);
+ std::vector findLights(const cv::Mat & rbg_img, const cv::Mat & binary_img);
+ std::vector matchLights(const std::vector & lights);
+
+ // For debug usage
+ cv::Mat getAllNumbersImage();
+ void drawResults(cv::Mat & img);
+
+ int binary_thres;
+ int detect_color;
+ LightParams l;
+ ArmorParams a;
+
+ std::unique_ptr classifier;
+
+ // Debug msgs
+ cv::Mat binary_img;
+ auto_aim_interfaces::msg::DebugLights debug_lights;
+ auto_aim_interfaces::msg::DebugArmors debug_armors;
+
+private:
+ bool isLight(const Light & possible_light);
+ bool containLight(
+ const Light & light_1, const Light & light_2, const std::vector & lights);
+ ArmorType isArmor(const Light & light_1, const Light & light_2);
+
+ std::vector lights_;
+ std::vector armors_;
+};
+
+} // namespace rm_auto_aim
+
+#endif // ARMOR_DETECTOR__DETECTOR_HPP_
diff --git a/armor_detector/include/armor_detector/detector_node.hpp b/armor_detector/include/armor_detector/detector_node.hpp
new file mode 100644
index 0000000..0cd88f3
--- /dev/null
+++ b/armor_detector/include/armor_detector/detector_node.hpp
@@ -0,0 +1,81 @@
+// Copyright 2022 Chen Jun
+// Licensed under the MIT License.
+
+#ifndef ARMOR_DETECTOR__DETECTOR_NODE_HPP_
+#define ARMOR_DETECTOR__DETECTOR_NODE_HPP_
+
+// ROS
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+// STD
+#include
+#include
+#include
+
+#include "armor_detector/detector.hpp"
+#include "armor_detector/number_classifier.hpp"
+#include "armor_detector/pnp_solver.hpp"
+#include "auto_aim_interfaces/msg/armors.hpp"
+
+namespace rm_auto_aim
+{
+
+class ArmorDetectorNode : public rclcpp::Node
+{
+public:
+ ArmorDetectorNode(const rclcpp::NodeOptions & options);
+
+private:
+ void imageCallback(const sensor_msgs::msg::Image::ConstSharedPtr img_msg);
+
+ std::unique_ptr initDetector();
+ std::vector detectArmors(const sensor_msgs::msg::Image::ConstSharedPtr & img_msg);
+
+ void createDebugPublishers();
+ void destroyDebugPublishers();
+
+ void publishMarkers();
+
+ // Armor Detector
+ std::unique_ptr detector_;
+
+ // Detected armors publisher
+ auto_aim_interfaces::msg::Armors armors_msg_;
+ rclcpp::Publisher::SharedPtr armors_pub_;
+
+ // Visualization marker publisher
+ visualization_msgs::msg::Marker armor_marker_;
+ visualization_msgs::msg::Marker text_marker_;
+ visualization_msgs::msg::MarkerArray marker_array_;
+ rclcpp::Publisher::SharedPtr marker_pub_;
+
+ // Camera info part
+ rclcpp::Subscription::SharedPtr cam_info_sub_;
+ cv::Point2f cam_center_;
+ std::shared_ptr cam_info_;
+ std::unique_ptr pnp_solver_;
+
+ // Image subscrpition
+ rclcpp::Subscription::SharedPtr img_sub_;
+
+ // Debug information
+ bool debug_;
+ std::shared_ptr debug_param_sub_;
+ std::shared_ptr debug_cb_handle_;
+ rclcpp::Publisher::SharedPtr lights_data_pub_;
+ rclcpp::Publisher::SharedPtr armors_data_pub_;
+ image_transport::Publisher binary_img_pub_;
+ image_transport::Publisher number_img_pub_;
+ image_transport::Publisher result_img_pub_;
+};
+
+} // namespace rm_auto_aim
+
+#endif // ARMOR_DETECTOR__DETECTOR_NODE_HPP_
diff --git a/armor_detector/include/armor_detector/number_classifier.hpp b/armor_detector/include/armor_detector/number_classifier.hpp
new file mode 100644
index 0000000..7a68799
--- /dev/null
+++ b/armor_detector/include/armor_detector/number_classifier.hpp
@@ -0,0 +1,40 @@
+// Copyright 2022 Chen Jun
+
+#ifndef ARMOR_DETECTOR__NUMBER_CLASSIFIER_HPP_
+#define ARMOR_DETECTOR__NUMBER_CLASSIFIER_HPP_
+
+// OpenCV
+#include
+
+// STL
+#include
+#include
+#include