Skip to content

Commit

Permalink
✨ 增加 Anchor 类,并支持圆形、矩形以及十字交叉特征
Browse files Browse the repository at this point in the history
  • Loading branch information
zhaoxi-scut committed Feb 9, 2025
1 parent d48a836 commit 22a71e1
Show file tree
Hide file tree
Showing 11 changed files with 605 additions and 1 deletion.
2 changes: 2 additions & 0 deletions cmake/templates/para_generator_module.in
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

//! @defgroup para_@module_name@ @module_name@ 的参数模块
//! @{

#include <rmvl/rmvl_modules.hpp>
@para_module_header_details@
//! @} para_@module_name@

Expand Down
12 changes: 11 additions & 1 deletion extra/feature/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@ rmvl_add_module(
DEPENDS types algorithm
)

# anchor
rmvl_generate_para(
anchor
MODULE feature
)
rmvl_add_module(
anchor
DEPENDS feature
)

# light_blob
rmvl_generate_para(
light_blob
Expand Down Expand Up @@ -83,7 +93,7 @@ endif()
if(BUILD_TESTS)
rmvl_add_test(
feature Unit
DEPENDS light_blob pilot rune_center rune_target
DEPENDS anchor light_blob pilot rune_center rune_target
EXTERNAL GTest::gtest_main
)
endif(BUILD_TESTS)
Expand Down
5 changes: 5 additions & 0 deletions extra/feature/include/rmvl/feature.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@

#include "feature/feature.h"

#ifdef HAVE_RMVL_ANCHOR
#include "feature/anchor.h"
#endif // HAVE_RMVL_ANCHOR

#ifdef HAVE_RMVL_LIGHT_BLOB
#include "feature/light_blob.h"
#endif // HAVE_RMVL_LIGHT_BLOB
Expand All @@ -45,3 +49,4 @@
#ifdef HAVE_RMVL_TAG
#include "feature/tag.h"
#endif // HAVE_RMVL_TAG

67 changes: 67 additions & 0 deletions extra/feature/include/rmvl/feature/anchor.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/**
* @file anchor.h
* @author zhaoxi ([email protected])
* @brief 定位点特征
* @version 1.0
* @date 2025-02-06
*
* @copyright Copyright 2025 (c), zhaoxi
*
*/

#pragma once

#include <rmvl/feature/feature.h>

namespace rm
{

//! 定位点类型
enum class AnchorType
{
Unknown, //!< 未知
Circle, //!< 圆形
Square, //!< 正方形
Cross, //!< 十字交叉
};

/**
* @brief 定位点特征,具有以下状态类型
* - "anchor": "circle" 或 "square" 或 "triangle" 或 "cross" 均为字符串状态,标识定位点类型
* - 当 anchor 为 "circle" 时,具有
* - "e": <number of eccentricity> 为数值状态,表示图像中测量的离心率
*/
class Anchor final : public rm::feature
{
public:
using ptr = std::shared_ptr<Anchor>;
using const_ptr = std::shared_ptr<const Anchor>;

Anchor() = default;

/**
* @brief Anchor 构造接口
*
* @param[in] contour 定位点轮廓
* @param[in] type 定位点类型
* @return Anchor 共享指针
*/
static ptr make_feature(const std::vector<cv::Point> &contour, AnchorType type);

/**
* @brief 从另一个特征进行构造
*
* @return 指向新特征的共享指针
*/
feature::ptr clone() override { return std::make_shared<Anchor>(*this); }

RMVL_FEATURE_CAST(Anchor)

//! 获取定位点类型的字符串表示
static std::string_view to_string(AnchorType type);

//! 从字符串类型获取定位点类型
static AnchorType from_string(std::string_view type);
};

} // namespace rm
8 changes: 8 additions & 0 deletions extra/feature/param/anchor.para
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
##################### 圆形特征拟合参数 #####################
double MAX_FIT_ACCURACY = 0.05 # 椭圆拟合允许的最大均方根误差
float MIN_RADIUS = 10.f # 圆形特征拟合时,允许的最小半径(单位:像素)
float MAX_RADIUS = 80.f # 圆形特征拟合时,允许的最大半径(单位:像素)
float MAX_ECCENTRICITY = 0.85f # 圆形特征拟合时,允许的最大离心率

################### 十字交叉特征拟合参数 ###################
double MAX_COMPACTNESS = 0.01 # 十字交叉特征拟合时,允许的最大紧凑度
101 changes: 101 additions & 0 deletions extra/feature/src/anchor/anchor.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/**
* @file anchor.cpp
* @author zhaoxi ([email protected])
* @brief
* @version 1.0
* @date 2025-02-06
*
* @copyright Copyright 2025 (c), zhaoxi
*
*/

#include "anchor_def.hpp"

namespace rm
{

Anchor::ptr Anchor::make_feature(const std::vector<cv::Point> &contour, AnchorType type)
{
if (type == AnchorType::Circle)
{
auto circle_info = createAnchorFromCircleContour(contour);
if (circle_info.has_value())
{
auto value = circle_info.value();
auto retval = std::make_shared<Anchor>();
auto &state = retval->_state;
state["anchor"] = "circle";
state["e"] = value.eccentricity;
retval->_center = value.center;
retval->_width = retval->_height = value.radius * 2;
return retval;
}
else
return nullptr;
}
else if (type == AnchorType::Square)
{
auto square_info = createAnchorFromSqaureContour(contour);
if (square_info.has_value())
{
auto value = std::move(square_info.value());
auto retval = std::make_shared<Anchor>();
auto &state = retval->_state;
state["anchor"] = "square";
retval->_center = value.center;
retval->_width = retval->_height = value.length;
retval->_corners = value.corners;
retval->_angle = value.angle;
return retval;
}
else
return nullptr;
}
else if (type == AnchorType::Cross)
{
auto cross_info = createAnchorFromCrossContour(contour);
if (cross_info.has_value())
{
auto value = cross_info.value();
auto retval = std::make_shared<Anchor>();
auto &state = retval->_state;
state["anchor"] = "cross";
retval->_center = value.center;
retval->_width = retval->_height = value.length;
retval->_corners = value.corners;
retval->_angle = value.angle;
return retval;
}
else
return nullptr;
}
return nullptr;
}

std::string_view Anchor::to_string(AnchorType type)
{
switch (type)
{
case AnchorType::Circle:
return "circle";
case AnchorType::Square:
return "square";
case AnchorType::Cross:
return "cross";
default:
return "unknown";
}
}

AnchorType Anchor::from_string(std::string_view type)
{
if (type == "circle")
return AnchorType::Circle;
if (type == "square")
return AnchorType::Square;
if (type == "cross")
return AnchorType::Cross;
return AnchorType::Unknown;
}

} // namespace rm
62 changes: 62 additions & 0 deletions extra/feature/src/anchor/anchor_def.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/**
* @file anchor_def.hpp
* @author zhaoxi ([email protected])
* @brief
* @version 1.0
* @date 2025-02-06
*
* @copyright Copyright 2025 (c), zhaoxi
*
*/

#pragma once

#include <optional>

#include "rmvl/feature/anchor.h"

namespace rm
{

/////////////////////////////// 圆形特征点 ///////////////////////////////

// 圆形特征
struct CircleInfo
{
cv::Point2f center{}; // 中心点
float radius{}; // 半径
double eccentricity{}; // 离心率
};

// 从圆形轮廓构造特征
std::optional<CircleInfo> createAnchorFromCircleContour(const std::vector<cv::Point> &contour);

/////////////////////////////// 矩形特征点 ///////////////////////////////

// 矩形特征
struct SquareInfo
{
cv::Point2f center{}; // 中心点
float length{}; // 边长
std::vector<cv::Point2f> corners{}; // 轮廓
float angle{}; // 角度
};

// 从矩形轮廓构造特征
std::optional<SquareInfo> createAnchorFromSqaureContour(const std::vector<cv::Point> &contour);

///////////////////////////// 十字交叉特征点 /////////////////////////////

// 十字交叉特征
struct CrossInfo
{
cv::Point2f center{}; // 中心点
float length{}; // 长度
std::vector<cv::Point2f> corners{}; // 轮廓
float angle{}; // 角度
};

// 从十字交叉轮廓构造特征
std::optional<CrossInfo> createAnchorFromCrossContour(const std::vector<cv::Point> &contour);

} // namespace rm
67 changes: 67 additions & 0 deletions extra/feature/src/anchor/circle.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/**
* @file circle.cpp
* @author zhaoxi ([email protected])
* @brief 圆形轮廓特征点检测
* @version 1.0
* @date 2025-02-06
*
* @copyright Copyright 2025 (c), zhaoxi
*
*/

#include <opencv2/imgproc.hpp>

#include "anchor_def.hpp"
#include "rmvlpara/feature/anchor.h"

namespace rm
{

// 判断椭圆拟合的平均拟合精度
static double fitEllipseAccuracy(const std::vector<cv::Point> &contour, const cv::RotatedRect &rrect)
{
double totalError{};
for (const auto &point : contour)
{
// 将点转换到椭圆坐标系中
cv::Point2f center = rrect.center;
double angle = rrect.angle * CV_PI / 180.0;
double a = rrect.size.width / 2.0; // 长轴
double b = rrect.size.height / 2.0; // 短轴

double cos_angle = std::cos(angle);
double sin_angle = std::sin(angle);

double x = point.x - center.x;
double y = point.y - center.y;

double x_ellipse = x * cos_angle + y * sin_angle;
double y_ellipse = -x * sin_angle + y * cos_angle;

// 计算点到椭圆的距离
totalError += std::abs(std::sqrt((x_ellipse * x_ellipse) / (a * a) + (y_ellipse * y_ellipse) / (b * b)) - 1);
}
return totalError / (contour.size() + 1);
}

std::optional<CircleInfo> createAnchorFromCircleContour(const std::vector<cv::Point> &contour)
{
// 椭圆拟合
if (contour.size() < 5)
return std::nullopt;
auto rrect = cv::fitEllipse(contour);
if (fitEllipseAccuracy(contour, rrect) > para::anchor_param.MAX_FIT_ACCURACY)
return std::nullopt;
// 离心率
float a = std::max(rrect.size.width, rrect.size.height) / 2;
float b = std::min(rrect.size.width, rrect.size.height) / 2;
if (b < rm::para::anchor_param.MIN_RADIUS)
return std::nullopt;
if (a > rm::para::anchor_param.MAX_RADIUS)
return std::nullopt;
auto eccentricity = std::sqrt(1 - (b * b) / (a * a));

return eccentricity < rm::para::anchor_param.MAX_ECCENTRICITY ? std::make_optional(CircleInfo{rrect.center, a, eccentricity}) : std::nullopt;
}

} // namespace rm
Loading

0 comments on commit 22a71e1

Please sign in to comment.