feat: 自动战斗费用击杀数缓存, 减少性能消耗

This commit is contained in:
status102
2025-05-28 21:03:02 +08:00
parent f72b834d12
commit 9c79a8d09c
8 changed files with 90 additions and 21 deletions

View File

@@ -3864,7 +3864,9 @@
"algorithm": "OcrDetect",
"isAscii": true,
"text": [],
"roi": [50, 0, 100, 40]
"roi": [50, 0, 100, 40],
"specialParams_doc": "Kill区域变化阈值, 模板匹配, 100=1.0; 正常在 0.997-1 之间波动, 少有0.995; _5->_6 的分数最高, 可达0.94",
"specialParams": [98]
},
"BattleCostData": {
"baseTask": "NumberOcrReplace",

View File

@@ -3,6 +3,7 @@
#include <array>
#include <climits>
#include <cmath>
#include <concepts>
#include <functional>
#include <optional>
#include <ostream>
@@ -215,6 +216,37 @@ struct Rect
Rect move(Rect move) const { return { x + move.x, y + move.y, move.width, move.height }; }
// 创建一个包含所有传入Rect的最小包围盒
static Rect bounding_box(const std::vector<Rect>& rects)
{
if (rects.empty()) {
return {};
}
int min_x = INT_MAX;
int min_y = INT_MAX;
int max_x = INT_MIN;
int max_y = INT_MIN;
for (const auto& rect : rects) {
min_x = std::min<int>(min_x, rect.x);
min_y = std::min<int>(min_y, rect.y);
max_x = std::max<int>(max_x, rect.x + rect.width);
max_y = std::max<int>(max_y, rect.y + rect.height);
}
return { min_x, min_y, max_x - min_x, max_y - min_y };
}
// 创建一个包含所有传入Rect的最小包围盒
template <typename... Args>
requires(std::same_as<std::remove_cvref_t<Args>, Rect> && ...)
static Rect bounding_box(const Rect& first, const Args&... args)
{
std::vector<Rect> rects = { first, args... };
return bounding_box(rects);
}
int x = 0;
int y = 0;
int width = 0;

View File

@@ -330,19 +330,21 @@ cv::Mat asst::BattleHelper::get_top_view(const cv::Mat& cam_img, bool side)
return result;
}
bool asst::BattleHelper::update_kills(const cv::Mat& reusable)
bool asst::BattleHelper::update_kills(const cv::Mat& image, const cv::Mat& image_prev)
{
cv::Mat image = reusable.empty() ? m_inst_helper.ctrler()->get_image() : reusable;
BattlefieldMatcher analyzer(image);
analyzer.set_object_of_interest({ .kills = true });
analyzer.set_image_prev(image_prev);
if (m_total_kills) {
analyzer.set_total_kills_prompt(m_total_kills);
}
auto result_opt = analyzer.analyze();
if (!result_opt || !result_opt->kills) {
if (!result_opt || result_opt->kills.status == BattlefieldMatcher::MatchStatus::Invalid) {
return false;
}
std::tie(m_kills, m_total_kills) = result_opt->kills.value();
if (result_opt->kills.status == BattlefieldMatcher::MatchStatus::Success) {
std::tie(m_kills, m_total_kills) = result_opt->kills.value;
}
return true;
}
@@ -924,7 +926,7 @@ bool asst::BattleHelper::move_camera(const std::pair<double, double>& delta)
LogTraceFunction;
Log.info("move", delta.first, delta.second);
update_kills();
update_kills(m_inst_helper.ctrler()->get_image());
// 还没转场的时候
if (m_kills != 0) {

View File

@@ -53,7 +53,7 @@ protected:
std::vector<battle::DeploymentOper>& cur_opers,
const std::vector<battle::DeploymentOper>& old_deployment_opers,
bool stop_on_unknown);
bool update_kills(const cv::Mat& reusable = cv::Mat());
bool update_kills(const cv::Mat& image, const cv::Mat& image_prev = cv::Mat());
bool update_cost(const cv::Mat& image, const cv::Mat& image_prev = cv::Mat());
cv::Mat get_top_view(const cv::Mat& cam_img, bool side = true);

View File

@@ -377,8 +377,8 @@ bool asst::CombatRecordRecognitionTask::slice_video()
m_battle_end_frame = 0;
not_in_battle_count = 0;
if (result_opt->kills) {
auto& [cur_kills, cur_total_kills] = *result_opt->kills;
if (result_opt->kills.status == BattlefieldMatcher::MatchStatus::Success) {
auto& [cur_kills, cur_total_kills] = result_opt->kills.value;
if (cur_kills != latest_kills) {
m_frame_kills.emplace_back(std::make_pair(i, cur_kills));
}

View File

@@ -340,7 +340,7 @@ bool asst::BattleProcessTask::wait_condition(const Action& action)
if (m_kills < action.kills) {
update_image_if_empty();
while (!need_exit() && m_kills < action.kills) {
update_kills(image);
update_kills(image, image_prev);
if (m_kills >= action.kills) {
break;
}

View File

@@ -49,7 +49,7 @@ BattlefieldMatcher::ResultOpt BattlefieldMatcher::analyze() const
if (m_object_of_interest.kills) {
result.kills = kills_analyze();
if (!result.kills) {
if (result.kills.status == MatchStatus::Invalid) {
return std::nullopt;
}
}
@@ -271,15 +271,18 @@ bool BattlefieldMatcher::kills_flag_analyze() const
return flag_analyzer.analyze().has_value();
}
std::optional<std::pair<int, int>> BattlefieldMatcher::kills_analyze() const
BattlefieldMatcher::MatchResult<std::pair<int, int>> BattlefieldMatcher::kills_analyze() const
{
if (hit_kills_cache()) {
return { .status = MatchStatus::HitCache };
}
TemplDetOCRer kills_analyzer(m_image);
kills_analyzer.set_task_info("BattleKillsFlag", "BattleKills");
kills_analyzer.set_replace(Task.get<OcrTaskInfo>("NumberOcrReplace")->replace_map);
auto kills_opt = kills_analyzer.analyze();
if (!kills_opt) {
return std::nullopt;
return {};
}
const std::string& kills_text = kills_opt->front().text;
@@ -292,7 +295,7 @@ std::optional<std::pair<int, int>> BattlefieldMatcher::kills_analyze() const
// 第一次识别就识别错了,识别成了 "0141"
if (kills_text.at(0) != '0') {
Log.error("m_total_kills_prompt is zero");
return std::nullopt;
return {};
}
pos = 1;
}
@@ -300,7 +303,7 @@ std::optional<std::pair<int, int>> BattlefieldMatcher::kills_analyze() const
size_t pre_pos = kills_text.find(std::to_string(m_total_kills_prompt));
if (pre_pos == std::string::npos || pre_pos == 0) {
Log.error("can't get pre_pos");
return std::nullopt;
return {};
}
Log.trace("pre total kills pos:", pre_pos);
pos = pre_pos - 1;
@@ -310,7 +313,7 @@ std::optional<std::pair<int, int>> BattlefieldMatcher::kills_analyze() const
// 例子中的"0"
std::string kills_count = kills_text.substr(0, pos);
if (kills_count.empty() || !ranges::all_of(kills_count, [](char c) -> bool { return std::isdigit(c); })) {
return std::nullopt;
return {};
}
int kills = std::stoi(kills_count);
@@ -327,7 +330,35 @@ std::optional<std::pair<int, int>> BattlefieldMatcher::kills_analyze() const
total_kills = std::max(total_kills, m_total_kills_prompt);
Log.trace("Kills:", kills, "/", total_kills);
return std::make_pair(kills, total_kills);
return { .value = std::make_pair(kills, total_kills), .status = MatchStatus::Success };
}
bool asst::BattlefieldMatcher::hit_kills_cache() const
{
if (m_image_prev.empty() || m_image.cols != m_image_prev.cols || m_image.rows != m_image_prev.rows) {
return false;
}
Matcher flag_match(m_image);
flag_match.set_task_info("BattleKillsFlag");
if (!flag_match.analyze()) {
return false;
}
const auto& flag_rect = flag_match.get_result().rect;
const auto& task = Task.get("BattleKills");
const auto& roi = flag_rect.move(task->roi);
cv::Mat kills_image_cache = make_roi(m_image_prev, roi);
cv::Mat kills_image = make_roi(m_image, roi);
cv::cvtColor(kills_image_cache, kills_image_cache, cv::COLOR_BGR2GRAY);
cv::cvtColor(kills_image, kills_image, cv::COLOR_BGR2GRAY);
cv::Mat match;
cv::matchTemplate(kills_image, kills_image_cache, match, cv::TM_CCOEFF_NORMED);
double mark;
cv::minMaxLoc(match, nullptr, &mark);
// 正常在 0.997-1 之间波动, 少有0.995
// _5->_6 的分数最高, 可达0.94
const double threshold = static_cast<double>(task->special_params[0]) / 100;
return mark > threshold;
}
bool BattlefieldMatcher::cost_symbol_analyze() const

View File

@@ -37,7 +37,7 @@ public:
{
ObjectOfInterest object_of_interest;
std::vector<battle::DeploymentOper> deployment;
std::optional<std::pair<int, int>> kills; // kills / total_kills
MatchResult<std::pair<int, int>> kills; // kills / total_kills
MatchResult<int> costs;
// bool in_detail = false;
@@ -67,9 +67,11 @@ protected:
int oper_cost_analyze(const Rect& roi) const;
bool oper_available_analyze(const Rect& roi) const;
std::optional<std::pair<int, int>> kills_analyze() const; // 识别击杀数
bool cost_symbol_analyze() const; // 识别费用左侧图标
MatchResult<int> costs_analyze() const; // 识别费用
MatchResult<std::pair<int, int>> kills_analyze() const; // 识别击杀数
// 识别是否持有费用是否命中缓存
bool hit_kills_cache() const;
bool cost_symbol_analyze() const; // 识别费用左侧图标
MatchResult<int> costs_analyze() const; // 识别费用
// 识别是否持有费用是否命中缓存
bool hit_costs_cache() const;
bool in_detail_analyze() const; // 识别是否在详情页