mirror of
https://github.com/MaaAssistantArknights/MaaAssistantArknights.git
synced 2026-07-15 17:30:27 +08:00
feat: 自动战斗费用击杀数缓存, 减少性能消耗
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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; // 识别是否在详情页
|
||||
|
||||
Reference in New Issue
Block a user