Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

增加 Anchor 类,并支持圆形、矩形以及十字交叉特征 #221

Merged
merged 1 commit into from
Feb 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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