创建仓库

This commit is contained in:
shmily744 2024-04-03 15:52:08 +08:00
commit a22f80f7b6
17 changed files with 3926 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
cmake-build-release
.idea

19
CMakeLists.txt Normal file
View File

@ -0,0 +1,19 @@
cmake_minimum_required(VERSION 3.26)
project(power_rune)
set(CMAKE_CXX_STANDARD 14)
find_package( Eigen3 REQUIRED )
include_directories( ${EIGEN3_INCLUDE_DIRS} )
find_package(OpenCV REQUIRED)
include_directories(${OpenCV_INCLUDE_DIRS})
#Python
find_package(PythonLibs REQUIRED)
include_directories(${PYTHON_INCLUDE_DIRS})
include_directories(include)
#Cpp
file(GLOB_RECURSE CPP_FILES src/*.cpp)
add_executable(power_rune ${CPP_FILES})
target_link_libraries(power_rune Eigen3::Eigen ${OpenCV_LIBS} ${PYTHON_LIBRARIES})

1
README.md Normal file
View File

@ -0,0 +1 @@
合肥工业大学苍穹竞技机器人队2024赛季robomaster能量机关模块

BIN
doc/1707831388698.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 659 KiB

65
include/AdamFitter.h Normal file
View File

@ -0,0 +1,65 @@
//
// Created by sph on 2024/1/4.
//
#ifndef ADAMFITTING_ADAMFITTER_H
#define ADAMFITTING_ADAMFITTER_H
#include<Eigen/Dense>
#include <vector>
namespace fitter {
class AdamFitter {
private:
double a_;
double omega_;
double phi_;
double b_;
int num_iterations_;
double beta1_ = 0.9;
double beta2_ = 0.999;
double learning_rate_ = 0.1;
double epsilon_ = 1e-8;
double convergence_threshold_ = 1e-6;
//一阶矩和二阶矩
double m_a_;
double v_a_;
double m_omega_;
double v_omega_;
double m_phi_;
double v_phi_;
double m_b_;
double v_b_;
static double loss(const Eigen::ArrayXd &angular_velocity, const Eigen::ArrayXd &angular_velocity_pred);
public:
explicit AdamFitter(double a = 0.9125,
double omega = 1.9420,
double phi = 1.0707,
double b = 1.1775,
int num_iterations = 1000,
double beta1 = 0.9,
double beta2 = 0.999,
double learning_rate = 0.1,
double epsilon = 1e-6,
double convergence_threshold = 1e-5,
double m_a = 0.0,
double v_a = 0.0,
double m_omega = 0.0,
double v_omega = 0.0,
double m_phi = 0.0,
double v_phi = 0.0,
double m_b = 0.0,
double v_b = 0.0);
bool Fitting(const Eigen::ArrayXd &t, const Eigen::ArrayXd &angular_velocity, std::vector<double> *result);
static Eigen::ArrayXd sine_function(const Eigen::ArrayXd &t, double a, double omega, double phi, double b);
};
} // fitter
#endif //ADAMFITTING_ADAMFITTER_H

153
include/Blade.h Normal file
View File

@ -0,0 +1,153 @@
#ifndef POWER_RUNE_BLADE_H
#define POWER_RUNE_BLADE_H
#include <opencv2/opencv.hpp>
namespace rm_power_rune {
const int RED = 0;
const int BLUE = 1;
struct FarEnd : public cv::RotatedRect {
FarEnd() = default;
explicit FarEnd(const cv::RotatedRect &box, const cv::Point2f r) : cv::RotatedRect(box) {
box.points(p);
std::sort(p, p + 4,
[&r](const cv::Point2f &a, const cv::Point2f &b) { return cv::norm(a - r) < cv::norm(b - r); });
double tilt_angle_0 = std::atan2(p[0].y - r.y, p[0].x - r.x);
double tilt_angle_1 = std::atan2(p[1].y - r.y, p[1].x - r.x);
tilt_angle_0 = tilt_angle_0 / CV_PI * 180;
tilt_angle_1 = tilt_angle_1 / CV_PI * 180;
if (tilt_angle_0 < 0) {
tilt_angle_0 = -tilt_angle_0;
} else {
tilt_angle_0 = 360 - tilt_angle_0;
}
if (tilt_angle_1 < 0) {
tilt_angle_1 = -tilt_angle_1;
} else {
tilt_angle_1 = 360 - tilt_angle_1;
}
if (tilt_angle_0 < 90 && tilt_angle_1 > 270) {
corner_point_0 = p[0];
corner_point_1 = p[1];
} else if (tilt_angle_1 < 90 && tilt_angle_0 > 270) {
corner_point_1 = p[0];
corner_point_0 = p[1];
} else if (tilt_angle_0 > tilt_angle_1) {
corner_point_1 = p[1];
corner_point_0 = p[0];
} else if (tilt_angle_0 < tilt_angle_1) {
corner_point_0 = p[1];
corner_point_1 = p[0];
}
cv::Point2f center = (corner_point_0 + corner_point_1) / 2;
tilt_angle = std::atan2(center.y - r.y, center.x - r.x);
tilt_angle = tilt_angle / CV_PI * 180;
if (tilt_angle < 0) {
tilt_angle = -tilt_angle;
} else {
tilt_angle = 360 - tilt_angle;
}
}
cv::Point2f p[4];
cv::Point2f corner_point_0;
cv::Point2f corner_point_1;
double tilt_angle = 0;
};
struct NearEnd : public cv::RotatedRect {
NearEnd() = default;
NearEnd(const cv::RotatedRect &box, bool activated, const cv::Point2f r) : cv::RotatedRect(box) {
is_activated = activated;
box.points(p);
std::sort(p, p + 4,
[&r](const cv::Point2f &a, const cv::Point2f &b) { return cv::norm(a - r) < cv::norm(b - r); });
double tilt_angle_2 = std::atan2(p[2].y - r.y, p[2].x - r.x);
double tilt_angle_3 = std::atan2(p[3].y - r.y, p[3].x - r.x);
tilt_angle_2 = tilt_angle_2 / CV_PI * 180;
tilt_angle_3 = tilt_angle_3 / CV_PI * 180;
if (tilt_angle_2 < 0) {
tilt_angle_2 = -tilt_angle_2;
} else {
tilt_angle_2 = 360 - tilt_angle_2;
}
if (tilt_angle_3 < 0) {
tilt_angle_3 = -tilt_angle_3;
} else {
tilt_angle_3 = 360 - tilt_angle_3;
}
if (tilt_angle_2 < 90 && tilt_angle_3 > 270) {
corner_point_2 = p[3];
corner_point_3 = p[2];
} else if (tilt_angle_3 < 90 && tilt_angle_2 > 270) {
corner_point_3 = p[3];
corner_point_2 = p[2];
} else if (tilt_angle_2 > tilt_angle_3) {
corner_point_3 = p[2];
corner_point_2 = p[3];
} else if (tilt_angle_2 < tilt_angle_3) {
corner_point_2 = p[2];
corner_point_3 = p[3];
}
cv::Point2f center = (corner_point_2 + corner_point_3) / 2;
tilt_angle = std::atan2(center.y - r.y, center.x - r.x);
tilt_angle = tilt_angle / CV_PI * 180;
if (tilt_angle < 0) {
tilt_angle = -tilt_angle;
} else {
tilt_angle = 360 - tilt_angle;
}
}
cv::Point2f p[4];
cv::Point2f corner_point_2;
cv::Point2f corner_point_3;
double tilt_angle = 0;
bool is_activated = false;
};
struct Blade {
Blade() = default;
Blade(const NearEnd &near_end, const FarEnd &far_end) {
top_left = far_end.corner_point_0;
top_right = far_end.corner_point_1;
bottom_right = near_end.corner_point_2;
bottom_left = near_end.corner_point_3;
center = ((top_right + top_left) / 2 + (bottom_left + bottom_right) / 2) / 2;
is_activated = near_end.is_activated;
if(abs(far_end.tilt_angle - near_end.tilt_angle)>180)
tilt_angle = (far_end.tilt_angle + near_end.tilt_angle + 360) / 2;
else
tilt_angle = (far_end.tilt_angle + near_end.tilt_angle) / 2;
}
//以扇叶远端为上
cv::Point2f top_left;
cv::Point2f top_right;
cv::Point2f bottom_right;
cv::Point2f bottom_left;
cv::Point2f center;
double tilt_angle = 0;
bool is_activated = false;
};
}// namespace rm_power_rune
#endif //POWER_RUNE_BLADE_H

47
include/PnpSolver.hpp Normal file
View File

@ -0,0 +1,47 @@
// Copyright 2022 Chen Jun
// Licensed under the MIT License.
#ifndef PNP_SOLVER_HPP_
#define PNP_SOLVER_HPP_
//#include <geometry_msgs/msg/point.hpp>
#include <opencv2/core.hpp>
// STD
#include <array>
#include <vector>
#include "Blade.h"
namespace rm_power_rune
{
class PnPSolver
{
public:
PnPSolver(
const std::array<double, 9> & camera_matrix,
const std::vector<double> & distortion_coefficients);
// Get 3d position
bool solvePnP(const Blade & blade, cv::Mat & rvec, cv::Mat & tvec);
// Calculate the distance between armor center and image center
float calculateDistanceToCenter(const cv::Point2f & image_point);
private:
cv::Mat camera_matrix_;
cv::Mat dist_coeffs_;
// Unit: mm
static constexpr float SMALL_ARMOR_WIDTH = 135;
static constexpr float SMALL_ARMOR_HEIGHT = 55;
static constexpr float LARGE_ARMOR_WIDTH = 225;
static constexpr float LARGE_ARMOR_HEIGHT = 55;
// Four vertices of armor in 3d
std::vector<cv::Point3f> armor_points_;
};
} // namespace rm_power_rune
#endif // PNP_SOLVER_HPP_

65
include/RuneDetector.h Normal file
View File

@ -0,0 +1,65 @@
#ifndef POWER_RUNE_RUNEDETECTOR_H
#define POWER_RUNE_RUNEDETECTOR_H
// OpenCV
#include <opencv2/core.hpp>
#include <opencv2/core/types.hpp>
// STD
#include <cmath>
#include <string>
#include <vector>
#include <chrono>
#include "Blade.h"
#include "AdamFitter.h"
namespace rm_power_rune {
class RuneDetector {
public:
RuneDetector(const int & bin_thres, const int & channel_thres, const int & color, const int & num_points);
std::vector<cv::Point> detect(const cv::Mat & input);
cv::Mat preprocessImage(const cv::Mat & input) const;
std::pair<std::vector<FarEnd>, std::vector<NearEnd>> findRAndEnds(const cv::Mat & bin_img);
static std::vector<Blade> matchEnds(std::vector<FarEnd> &far_ends, std::vector<NearEnd> &near_ends);
void getAngularV(const std::vector<Blade>& blades);
static void rotatePoints(const cv::Point& center, std::vector<cv::Point>& Points, double angle);
int binary_thres;
int channel_thres;
int detect_color;
int num_points;
// For debug usage
cv::Mat binary_img;
void drawEnds(cv::Mat & img);
int drawResults(cv::Mat & img);
private:
int width_;
int height_;
double distance_r_to_center_;
double t_;
double time_point_;
bool is_convergence_;
std::vector<cv::Point> target_points_;
std::vector<double> vec_t_;
std::vector<double> angular_v_;
fitter::AdamFitter adam_fitter_;
cv::Point2f r_;
std::pair<std::vector<FarEnd>, std::vector<NearEnd>> ends_;
std::vector<FarEnd> far_ends_;
std::vector<NearEnd> near_ends_;
std::vector<Blade> blades_;
std::vector<double> last_tilt_angles_;
};
}
#endif //POWER_RUNE_RUNEDETECTOR_H

2985
include/matplotlibcpp.h Normal file

File diff suppressed because it is too large Load Diff

108
src/AdamFitter.cpp Normal file
View File

@ -0,0 +1,108 @@
//
// Created by sph on 2024/1/4.
//
#include <iostream>
#include "AdamFitter.h"
namespace fitter {
AdamFitter::AdamFitter(double a, double omega, double phi, double b, int num_iterations, double beta1, double beta2,
double learning_rate, double epsilon, double convergence_threshold, double m_a, double v_a,
double m_omega, double v_omega, double m_phi, double v_phi, double m_b, double v_b) :
a_(a),
omega_(omega),
phi_(phi),
b_(b),
num_iterations_(num_iterations),
beta1_(beta1),
beta2_(beta2),
learning_rate_(learning_rate),
epsilon_(epsilon),
convergence_threshold_(convergence_threshold),
m_a_(m_a),
v_a_(v_a),
m_omega_(m_omega),
v_omega_(v_omega),
m_phi_(m_phi),
v_phi_(v_phi),
m_b_(m_b),
v_b_(v_b) {}
Eigen::ArrayXd fitter::AdamFitter::sine_function(const Eigen::ArrayXd &t, double a,
double omega, double phi, double b) {
Eigen::ArrayXd result = a * (omega * t + phi).array().sin() + b;
return result;
}
double AdamFitter::loss(const Eigen::ArrayXd &angular_velocity, const Eigen::ArrayXd &angular_velocity_pred) {
double result = (angular_velocity - angular_velocity_pred).square().mean();
return result;
}
bool AdamFitter::Fitting(const Eigen::ArrayXd &t, const Eigen::ArrayXd &angular_velocity,
std::vector<double> *result) {
for (int i = 0; i <= num_iterations_; i++) {
//计算预测值、损失
Eigen::ArrayXd angular_velocity_pred = sine_function(t, a_, omega_, phi_, b_);
double current_loss = loss(angular_velocity, angular_velocity_pred);
//计算参数梯度
Eigen::ArrayXd temp = 2 * (angular_velocity_pred - angular_velocity);
Eigen::ArrayXd temp_angle = omega_ * t + phi_;
double grad_a = (temp * temp_angle.sin()).mean();
double grad_omega = (temp * a_ * t * temp_angle.cos()).mean();
double grad_phi = (temp * a_ * temp_angle.cos()).mean();
double grad_b = temp.mean();
//更新一阶矩
m_a_ = beta1_ * m_a_ + (1 - beta1_) * grad_a;
m_omega_ = beta1_ * m_omega_ + (1 - beta1_) * grad_omega;
m_phi_ = beta1_ * m_phi_ + (1 - beta1_) * grad_phi;
m_b_ = beta1_ * m_b_ + (1 - beta1_) * grad_b;
//更新二阶矩
v_a_ = beta2_ * v_a_ + (1 - beta2_) * grad_a * grad_a;
v_omega_ = beta2_ * v_omega_ + (1 - beta2_) * grad_omega * grad_omega;
v_phi_ = beta2_ * v_phi_ + (1 - beta2_) * grad_phi * grad_phi;
v_b_ = beta2_ * v_b_ + (1 - beta2_) * grad_b * grad_b;
//修正一阶矩和二阶矩的偏差
double m_a_hat = m_a_ / (1 - pow(beta1_, (i + 1)));
double m_omega_hat = m_omega_ / (1 - pow(beta1_, (i + 1)));
double m_phi_hat = m_phi_ / (1 - pow(beta1_, (i + 1)));
double m_b_hat = m_b_ / (1 - pow(beta1_, (i + 1)));
double v_a_hat = v_a_ / (1 - pow(beta2_, (i + 1)));
double v_omega_hat = v_omega_ / (1 - pow(beta2_, (i + 1)));
double v_phi_hat = v_phi_ / (1 - pow(beta2_, (i + 1)));
double v_b_hat = v_b_ / (1 - pow(beta2_, (i + 1)));
//更新参数
a_ = std::min(std::max(a_ - learning_rate_ * m_a_hat / (std::sqrt(v_a_hat) + epsilon_), 0.780), 1.045);
omega_ = std::min(
std::max(omega_ - learning_rate_ * m_omega_hat / (std::sqrt(v_omega_hat) + epsilon_), 1.884),
1.980);
phi_ = std::min(std::max(phi_ - learning_rate_ * m_phi_hat / (std::sqrt(v_phi_hat) + epsilon_), 0.000),
3.1415926);
b_ -= learning_rate_ * m_b_hat / (std::sqrt(v_b_hat) + epsilon_);
b_ = 0.8 * (b_ - learning_rate_ * m_b_hat / (std::sqrt(v_b_hat) + epsilon_)) + 0.2 * (2.090 - a_);
//新的预测值
Eigen::ArrayXd angular_velocity_hat = sine_function(t, a_, omega_, phi_, b_);
//判断收敛
if (std::abs(current_loss - loss(angular_velocity, angular_velocity_hat)) < convergence_threshold_) {
std::cout << "Converged after " << i + 1 << " iterations" << std::endl;
result->emplace_back(a_);
result->emplace_back(omega_);
result->emplace_back(phi_);
result->emplace_back(b_);
return true;
}
}
result->emplace_back(a_);
result->emplace_back(omega_);
result->emplace_back(phi_);
result->emplace_back(b_);
return false;
}
} // fitter

49
src/PnpSolver.cpp Normal file
View File

@ -0,0 +1,49 @@
// Copyright 2022 Chen Jun
#include "PnpSolver.hpp"
#include <opencv2/calib3d.hpp>
#include <vector>
namespace rm_power_rune {
PnPSolver::PnPSolver(
const std::array<double, 9> &camera_matrix, const std::vector<double> &dist_coeffs)
: camera_matrix_(cv::Mat(3, 3, CV_64F, const_cast<double *>(camera_matrix.data())).clone()),
dist_coeffs_(cv::Mat(1, 5, CV_64F, const_cast<double *>(dist_coeffs.data())).clone()) {
//Todo: 世界坐标系待修改
// Unit: m
constexpr double small_half_y = SMALL_ARMOR_WIDTH / 2.0 / 1000.0;
constexpr double small_half_z = SMALL_ARMOR_HEIGHT / 2.0 / 1000.0;
// Start from bottom left in clockwise order
// Model coordinate: x forward, y left, z up
armor_points_.emplace_back(0, small_half_y, -small_half_z);
armor_points_.emplace_back(0, small_half_y, small_half_z);
armor_points_.emplace_back(0, -small_half_y, small_half_z);
armor_points_.emplace_back(0, -small_half_y, -small_half_z);
}
bool PnPSolver::solvePnP(const Blade &blade, cv::Mat &rvec, cv::Mat &tvec) {
std::vector<cv::Point2f> image_armor_points;
// Fill in image points
image_armor_points.emplace_back(blade.bottom_left);
image_armor_points.emplace_back(blade.top_left);
image_armor_points.emplace_back(blade.bottom_right);
image_armor_points.emplace_back(blade.bottom_left);
// Solve pnp
auto object_points = armor_points_ ;
return cv::solvePnP(
object_points, image_armor_points, camera_matrix_, dist_coeffs_, rvec, tvec, false,
cv::SOLVEPNP_IPPE);
}
float PnPSolver::calculateDistanceToCenter(const cv::Point2f &image_point) {
auto cx = static_cast<float>(camera_matrix_.at<double>(0, 2));
auto cy = static_cast<float>(camera_matrix_.at<double>(1, 2));
return static_cast<float>(cv::norm(image_point - cv::Point2f(cx, cy)));
}
} // namespace rm_auto_aim

385
src/RuneDetector.cpp Normal file
View File

@ -0,0 +1,385 @@
#include "RuneDetector.h"
namespace rm_power_rune {
RuneDetector::RuneDetector(const int &bin_thres, const int &channel_thres, const int &color, const int &num_points)
: binary_thres(bin_thres), channel_thres(channel_thres),
detect_color(color), num_points(num_points) {
r_ = cv::Point2f(0, 0);
width_ = 0;
height_ = 0;
t_ = 0;
is_convergence_ = false;
auto now = std::chrono::system_clock::now();
std::chrono::duration<double> duration = now.time_since_epoch();
time_point_ = duration.count();
distance_r_to_center_ = cv::norm(
r_ - cv::Point2f(static_cast<float>(width_) / 2, static_cast<float>(height_) / 2));
}
std::vector<cv::Point> rm_power_rune::RuneDetector::detect(const cv::Mat &input) {
if (width_ == 0 && height_ == 0) {
width_ = input.cols;
height_ = input.rows;
}
binary_img = preprocessImage(input);
ends_ = findRAndEnds(binary_img);
far_ends_ = ends_.first;
near_ends_ = ends_.second;
blades_ = matchEnds(far_ends_, near_ends_);
getAngularV(blades_);
Eigen::ArrayXd t(num_points);
Eigen::ArrayXd v;
Eigen::ArrayXd y_pre;
std::vector<double> result;
target_points_.clear();
if (!vec_t_.empty() || !angular_v_.empty()) {
t = Eigen::Map<Eigen::ArrayXd>(vec_t_.data(), static_cast<Eigen::Index>(vec_t_.size()));
v = Eigen::Map<Eigen::ArrayXd>(angular_v_.data(), static_cast<Eigen::Index>(angular_v_.size()));
is_convergence_ = adam_fitter_.Fitting(t, v, &result);
if (is_convergence_) {
double a = result[0];
double omega = result[1];
double phi = result[2];
double b = result[3];
double angle = (a * sin((omega * (vec_t_.back()) + phi)) + b)*0.1;
for(const auto& blade:blades_){
if(!blade.is_activated){
target_points_.emplace_back(blade.top_left);
target_points_.emplace_back(blade.top_right);
target_points_.emplace_back(blade.bottom_right);
target_points_.emplace_back(blade.bottom_left);
rotatePoints(r_, target_points_, -angle);
}
}
}
}
return {};
}
cv::Mat rm_power_rune::RuneDetector::preprocessImage(const cv::Mat &input) const {
cv::Mat subtracted_img, gray_img, threshold_img;
cvtColor(input, gray_img, cv::COLOR_BGR2GRAY);
threshold(gray_img, threshold_img, binary_thres, 255, cv::THRESH_BINARY);
//通道相减 突出识别颜色
std::vector<cv::Mat> channels;
split(input, channels);
if (detect_color == RED)
subtract(channels[2], channels[0], subtracted_img);
else if (detect_color == BLUE)
subtract(channels[0], channels[2], subtracted_img);
//图像预处理参数 二值化、膨胀、形态学闭操作
threshold(subtracted_img, subtracted_img, channel_thres, 255, cv::THRESH_BINARY);
cv::Mat bin_img;
//与操作消除白色灯条与蓝色灯条相连部分
bin_img = (subtracted_img & threshold_img);
cv::Mat kernel = cv::getStructuringElement(0, cv::Size(2, 2));
dilate(bin_img, bin_img, kernel, cv::Point(-1, -1), 1);
kernel = cv::getStructuringElement(0, cv::Size(7, 7));
morphologyEx(bin_img, bin_img, cv::MORPH_CLOSE, kernel);
return bin_img;
}
std::pair<std::vector<FarEnd>, std::vector<NearEnd>> RuneDetector::findRAndEnds(const cv::Mat &bin_img) {
std::vector<FarEnd> far_ends;
std::vector<NearEnd> near_ends;
using std::vector;
//轮廓及其层次结构
vector<vector<cv::Point>> contours;
vector<cv::Vec4i> hierarchy;
cv::findContours(bin_img, contours, hierarchy, cv::RETR_CCOMP, cv::CHAIN_APPROX_SIMPLE);
int contour_index = 0;
for (const auto &contour: contours) {
//只检测5个点以上且最外层轮廓
if (contour.size() < 5) {
contour_index++;
continue;
}
if (hierarchy[contour_index][3] != -1) {
contour_index++;
continue;
}
auto r_rect = cv::minAreaRect(contour);
float area_rect = r_rect.size.area();
if (area_rect >= 10000.0 || area_rect < 80) {
contour_index++;
continue;
}
float width_rect;
float height_rect;
//判断长宽比
if (r_rect.size.width > r_rect.size.height) {
width_rect = r_rect.size.width;
height_rect = r_rect.size.height;
} else {
width_rect = r_rect.size.height;
height_rect = r_rect.size.width;
}
if (width_rect / height_rect < 4) {
if (area_rect > 80 && area_rect < 600) { // find R
if (height_rect / width_rect > 0.8) {
if (r_.x == 0 && r_.y == 0) {
r_ = r_rect.center;
distance_r_to_center_ = cv::norm(
r_ - cv::Point2f(static_cast<float>(width_) / 2, static_cast<float>(height_) / 2));
} else {
double distance = cv::norm(r_rect.center - cv::Point2f(static_cast<float>(width_) / 2,
static_cast<float>(height_) / 2));
if (abs(distance - distance_r_to_center_) < 100) {
r_ = r_rect.center;
distance_r_to_center_ = distance;
} else std::cout << "R jump!" << std::endl;
}
}
} else { //find ends
// find NearEnd
if (area_rect > 4000 && area_rect < 6500 && width_rect / height_rect > 0.9 &&
width_rect / height_rect < 1.6) {
if (hierarchy[contour_index][2] != -1) {
auto near_end = NearEnd(r_rect, true, r_);
near_ends.emplace_back(near_end);
} else {
auto near_end = NearEnd(r_rect, false, r_);
near_ends.emplace_back(near_end);
}
}
// find FarEnd
if (area_rect > 800 && area_rect < 1500 && width_rect / height_rect > 1.8 &&
width_rect / height_rect < 3.5) {
auto far_end = FarEnd(r_rect, r_);
far_ends.emplace_back(far_end);
}
}
}
contour_index++;
}
return std::make_pair(far_ends, near_ends);
}
std::vector<Blade>
rm_power_rune::RuneDetector::matchEnds(std::vector<FarEnd> &far_ends, std::vector<NearEnd> &near_ends) {
std::vector<Blade> blades;
if (far_ends.size() != near_ends.size()) {
std::cout << "Number mismatch between far_ends and near_ends" << std::endl;
}
std::sort(far_ends.begin(), far_ends.end(),
[](const FarEnd &a, const FarEnd &b) { return a.tilt_angle < b.tilt_angle; });
std::sort(near_ends.begin(), near_ends.end(),
[](const NearEnd &a, const NearEnd &b) { return a.tilt_angle < b.tilt_angle; });
for (const auto &near_end: near_ends) {
for (const auto &far_end: far_ends) {
if ((near_end.tilt_angle < 3 && far_end.tilt_angle > 357) ||
(far_end.tilt_angle < 3 && near_end.tilt_angle > 357)) {
Blade blade(near_end, far_end);
blades.emplace_back(blade);
break;
}
if (abs(near_end.tilt_angle - far_end.tilt_angle) < 5) {
Blade blade(near_end, far_end);
blades.emplace_back(blade);
break;
}
}
}
return blades;
}
void RuneDetector::getAngularV(const std::vector<Blade> &blades) {
std::vector<double> delta_angles;
if (last_tilt_angles_.empty()) {
for (const auto &blade: blades) {
last_tilt_angles_.emplace_back(blade.tilt_angle);
}
auto now = std::chrono::system_clock::now();
std::chrono::duration<double> duration = now.time_since_epoch();
time_point_ = duration.count();
t_ = 0;
angular_v_.clear();
vec_t_.clear();
} else {
double delta_angle;
for (double last_tilt_angle: last_tilt_angles_) {
for (const auto &blade: blades) {
if (last_tilt_angle > 355 && blade.tilt_angle < 5) {
delta_angle = 360 - last_tilt_angle + blade.tilt_angle;
delta_angles.push_back(delta_angle);
} else if (abs(blade.tilt_angle - last_tilt_angle) < 5) {
delta_angle =
(blade.tilt_angle - last_tilt_angle) > 0 ? (blade.tilt_angle - last_tilt_angle) : 0.01;
delta_angles.push_back(delta_angle);
} else if (blades.size() == 1) {
std::cout << "blade jump" << std::endl;
}
}
}
last_tilt_angles_.clear();
for (const auto &blade: blades) {
last_tilt_angles_.emplace_back(blade.tilt_angle);
}
double sum = 0;
for (auto delta_a: delta_angles) {
sum += delta_a;
}
double delta_angle_avg = (sum / static_cast<double> (delta_angles.size())) * CV_PI / 180;
auto now = std::chrono::system_clock::now();
std::chrono::duration<double> duration = now.time_since_epoch();
double delta_t = duration.count() - time_point_;
time_point_ = duration.count();
t_ += delta_t;
double angular_v;
angular_v = delta_angle_avg / delta_t < 2.09 ? delta_angle_avg / delta_t : 2.09;
if (vec_t_.size() < num_points) {
vec_t_.emplace_back(t_);
angular_v_.emplace_back(angular_v);
} else {
vec_t_.erase(vec_t_.begin());
angular_v_.erase(angular_v_.begin());
vec_t_.emplace_back(t_);
angular_v_.emplace_back(angular_v);
}
}
}
void RuneDetector::rotatePoints(const cv::Point &center, std::vector<cv::Point> &Points, double angle) {
// 对每个点执行旋转,其实对输入点顺序并无要求。。。仅是对输入的所有点绕center做相同角度的旋转
Eigen::Vector2d eigenCenter(center.x, center.y);
Eigen::Vector2d LeftTopPoint(Points[0].x, (Points[0].y));
Eigen::Vector2d RightTopPoint(Points[1].x, (Points[1].y));
Eigen::Vector2d RightLowPoint(Points[2].x, (Points[2].y));
Eigen::Vector2d LeftLowTopPoint(Points[3].x, (Points[3].y));
Eigen::Transform<double, 2, Eigen::Affine> transform;
transform = Eigen::Translation2d(eigenCenter) * Eigen::Rotation2Dd(angle) * Eigen::Translation2d(-eigenCenter);
LeftTopPoint = transform * LeftTopPoint;
RightTopPoint = transform * RightTopPoint;
RightLowPoint = transform * RightLowPoint;
LeftLowTopPoint = transform * LeftLowTopPoint;
Points.clear();
Points.emplace_back(LeftTopPoint.x(), LeftTopPoint.y());
Points.emplace_back(RightTopPoint.x(), RightTopPoint.y());
Points.emplace_back(RightLowPoint.x(), RightLowPoint.y());
Points.emplace_back(LeftLowTopPoint.x(), LeftLowTopPoint.y());
}
void RuneDetector::drawEnds(cv::Mat &img) {
using std::vector;
std::cout << "##################" << std::endl;
//draw far_ends
for (auto far_end: far_ends_) {
vector<cv::Point> p(far_end.p, far_end.p + 4);
vector<vector<cv::Point>> temp;
temp.emplace_back(p);
drawContours(img, temp, -1, cv::Scalar(0, 0, 255), 2);
cv::circle(img, far_end.corner_point_0, 5, cv::Scalar(0, 255, 255));
cv::circle(img, far_end.corner_point_1, 5, cv::Scalar(0, 255, 255));
cv::putText(img, "0", far_end.corner_point_0, 0, 0.6, cv::Scalar(0, 255, 255));
cv::putText(img, "1", far_end.corner_point_1, 0, 0.6, cv::Scalar(0, 255, 255));
std::cout << "far: " << far_end.tilt_angle << std::endl;
}
//draw near_ends
for (auto near_end: near_ends_) {
vector<cv::Point> p(near_end.p, near_end.p + 4);
vector<vector<cv::Point>> temp;
temp.emplace_back(p);
if (near_end.is_activated)
drawContours(img, temp, -1, cv::Scalar(0, 255, 0), 2);
else
drawContours(img, temp, -1, cv::Scalar(0, 0, 255), 2);
cv::circle(img, near_end.corner_point_2, 5, cv::Scalar(0, 255, 255));
cv::circle(img, near_end.corner_point_3, 5, cv::Scalar(0, 255, 255));
cv::putText(img, "2", near_end.corner_point_2, 0, 0.6, cv::Scalar(0, 255, 255));
cv::putText(img, "3", near_end.corner_point_3, 0, 0.6, cv::Scalar(0, 255, 255));
std::cout << "near: " << near_end.tilt_angle << std::endl;
}
std::cout << "##################" << std::endl;
}
int RuneDetector::drawResults(cv::Mat &img) {
using std::vector;
//draw R
cv::circle(img, r_, 15, cv::Scalar(0, 255, 255));
//std::cout << r_ << std::endl;
//drawEnds(img);
//draw blades
for (const auto &blade: blades_) {
vector<cv::Point> p;
p.emplace_back(blade.top_left);
p.emplace_back(blade.top_right);
p.emplace_back(blade.bottom_right);
p.emplace_back(blade.bottom_left);
vector<vector<cv::Point>> temp;
temp.emplace_back(p);
if (blade.is_activated) {
drawContours(img, temp, -1, cv::Scalar(0, 255, 0), 2);
cv::circle(img, blade.center, 3, cv::Scalar(0, 255, 0), 2);
} else {
drawContours(img, temp, -1, cv::Scalar(0, 0, 255), 2);
cv::circle(img, blade.center, 3, cv::Scalar(0, 0, 255), 2);
}
}
if (is_convergence_ && !target_points_.empty()) {
vector<vector<cv::Point>> a;
a.emplace_back(target_points_);
drawContours(img, a, -1, cv::Scalar(0, 255, 255), 2);
}
imshow("RawImage", img);
//imshow("BinaryImage", binary_img);
int key = cv::waitKey(6);
return key;
}
}// namespace rm_power_rune

47
src/main.cpp Normal file
View File

@ -0,0 +1,47 @@
#include <opencv2/opencv.hpp>
#include "RuneDetector.h"
int main() {
cv::VideoCapture cap(R"(../video/blue.mp4)");
if (!cap.isOpened()) {
std::cout << "Error opening video stream or file" << std::endl;
}
rm_power_rune::RuneDetector runeDetector(180, 100, rm_power_rune::BLUE, 180);
while (cap.isOpened()) {
// Initialise frame matrix
cv::Mat rawImages;
// Initialize a boolean to check if frames are there or not
bool isSuccess = cap.read(rawImages);
// If frames are present, show it
if (isSuccess) {
// 开始计时
auto startTime = std::chrono::steady_clock::now();
//display frames
runeDetector.detect(rawImages);
int key = runeDetector.drawResults(rawImages);
if (key == 'q') break;
// 计算总帧时间
auto endTime = std::chrono::steady_clock::now();
auto elapsedTime = std::chrono::duration<double>(endTime - startTime).count();
// 如果时间间隔达到了设定的秒数,计算并输出帧率
double fps = 1 / elapsedTime;
std::cout << "FPS: " << fps << std::endl;
} else {
std::cout << "Video camera is disconnected" << std::endl;
break;
}
}
// Release the video capture object
cap.release();
cv::destroyAllWindows();
return 0;
}

BIN
video/blue.avi Normal file

Binary file not shown.

BIN
video/blue.mp4 Normal file

Binary file not shown.

BIN
video/blueCut.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

BIN
video/red.avi Normal file

Binary file not shown.