refactor: 重新封装了一下战斗技能识别。 BattleSkillReadyImageAnalyzer::analyze 还没写完

This commit is contained in:
MistEO
2022-12-13 18:50:18 +08:00
parent 20ba8dab1a
commit 4fc5878ddf
11 changed files with 142 additions and 173 deletions

View File

@@ -35,6 +35,7 @@
<ClInclude Include="Vision\Infrast\InfrastSmileyImageAnalyzer.h" />
<ClInclude Include="Vision\MatchImageAnalyzer.h" />
<ClInclude Include="Vision\Miscellaneous\BattleImageAnalyzer.h" />
<ClInclude Include="Vision\Miscellaneous\BattleSkillReadyImageAnalyzer.h" />
<ClInclude Include="Vision\Miscellaneous\CreditShopImageAnalyzer.h" />
<ClInclude Include="Vision\Miscellaneous\DepotImageAnalyzer.h" />
<ClInclude Include="Vision\Miscellaneous\ProcessTaskImageAnalyzer.h" />
@@ -120,7 +121,6 @@
<ClInclude Include="Utils\Http.hpp" />
<ClInclude Include="Utils\ImageIo.hpp" />
<ClInclude Include="Utils\Locale.hpp" />
<ClInclude Include="Utils\MoreCV.hpp" />
<ClInclude Include="Utils\Logger.hpp" />
<ClInclude Include="Utils\Meta.hpp" />
<ClInclude Include="Utils\NoWarningCV.h" />
@@ -150,6 +150,7 @@
<ClCompile Include="Vision\Infrast\InfrastSmileyImageAnalyzer.cpp" />
<ClCompile Include="Vision\MatchImageAnalyzer.cpp" />
<ClCompile Include="Vision\Miscellaneous\BattleImageAnalyzer.cpp" />
<ClCompile Include="Vision\Miscellaneous\BattleSkillReadyImageAnalyzer.cpp" />
<ClCompile Include="Vision\Miscellaneous\CreditShopImageAnalyzer.cpp" />
<ClCompile Include="Vision\Miscellaneous\DepotImageAnalyzer.cpp" />
<ClCompile Include="Vision\Miscellaneous\ProcessTaskImageAnalyzer.cpp" />

View File

@@ -270,9 +270,6 @@
<ClInclude Include="Utils\Meta.hpp">
<Filter>源文件\Utils</Filter>
</ClInclude>
<ClInclude Include="Utils\MoreCV.hpp">
<Filter>源文件\Utils</Filter>
</ClInclude>
<ClInclude Include="Utils\NoWarningCV.h">
<Filter>源文件\Utils</Filter>
</ClInclude>
@@ -429,6 +426,9 @@
<ClInclude Include="Vision\Miscellaneous\StageDropsImageAnalyzer.h">
<Filter>源文件\Vision\Miscellaneous</Filter>
</ClInclude>
<ClInclude Include="Vision\Miscellaneous\BattleSkillReadyImageAnalyzer.h">
<Filter>源文件\Vision\Miscellaneous</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="Vision\AbstractImageAnalyzer.cpp">
@@ -710,5 +710,8 @@
<ClCompile Include="Vision\Miscellaneous\StageDropsImageAnalyzer.cpp">
<Filter>源文件\Vision\Miscellaneous</Filter>
</ClCompile>
<ClCompile Include="Vision\Miscellaneous\BattleSkillReadyImageAnalyzer.cpp">
<Filter>源文件\Vision\Miscellaneous</Filter>
</ClCompile>
</ItemGroup>
</Project>

View File

@@ -7,17 +7,16 @@
#include "Utils/NoWarningCV.h"
#include "Controller.h"
#include "Config/Miscellaneous/CopilotConfig.h"
#include "Config/Miscellaneous/TilePack.h"
#include "Config/TaskData.h"
#include "Config/TemplResource.h"
#include "Vision/MatchImageAnalyzer.h"
#include "Vision/Miscellaneous/BattleImageAnalyzer.h"
#include "Vision/OcrWithPreprocessImageAnalyzer.h"
#include "Controller.h"
#include "Task/ProcessTask.h"
#include "Utils/Logger.hpp"
#include "Utils/MoreCV.hpp"
#include "Vision/MatchImageAnalyzer.h"
#include "Vision/Miscellaneous/BattleImageAnalyzer.h"
#include "Vision/Miscellaneous/BattleSkillReadyImageAnalyzer.h"
#include "Vision/OcrWithPreprocessImageAnalyzer.h"
#include "Utils/ImageIo.hpp"
@@ -653,21 +652,16 @@ bool asst::BattleProcessTask::wait_to_end(const BattleAction& action)
bool asst::BattleProcessTask::try_possible_skill(const cv::Mat& image)
{
auto task_ptr = Task.get<MatchTaskInfo>("BattleAutoSkillFlag");
const Rect& skill_roi_move = task_ptr->rect_move;
cv::Mat temp = TemplResource::get_instance().get_templ(task_ptr->templ_name);
BattleSkillReadyImageAnalyzer skill_analyzer(image);
bool used = false;
for (auto& info : m_used_opers | views::values) {
if (info.info.skill_usage != BattleSkillUsage::Possibly && info.info.skill_usage != BattleSkillUsage::Once) {
continue;
}
auto roi = make_rect<cv::Rect>(Rect { info.pos.x, info.pos.y, 0, 0 }.move(skill_roi_move));
roi &= cv::Rect(0, 0, image.cols, image.rows);
const auto result = find_skill_ready(image(roi), temp);
if (result.empty()) continue;
skill_analyzer.set_base_point(info.pos);
if (!skill_analyzer.analyze()) {
continue;
}
ctrler()->click(info.pos);
sleep(Task.get("BattleUseOper")->pre_delay);

View File

@@ -10,15 +10,14 @@
#include "Config/Miscellaneous/TilePack.h"
#include "Config/Roguelike/RoguelikeCopilotConfig.h"
#include "Config/TaskData.h"
#include "Config/TemplResource.h"
#include "Controller.h"
#include "Status.h"
#include "Task/ProcessTask.h"
#include "Utils/ImageIo.hpp"
#include "Utils/Logger.hpp"
#include "Utils/MoreCV.hpp"
#include "Vision/MatchImageAnalyzer.h"
#include "Vision/Miscellaneous/BattleImageAnalyzer.h"
#include "Vision/Miscellaneous/BattleSkillReadyImageAnalyzer.h"
#include "Vision/OcrWithPreprocessImageAnalyzer.h"
bool asst::RoguelikeBattleTaskPlugin::verify(AsstMsg msg, const json::value& details) const
@@ -902,11 +901,7 @@ bool asst::RoguelikeBattleTaskPlugin::try_possible_skill(const cv::Mat& image)
return false;
}
auto task_ptr = Task.get<MatchTaskInfo>("BattleAutoSkillFlag");
const Rect& skill_roi_move = task_ptr->rect_move;
cv::Mat temp = TemplResource::get_instance().get_templ(task_ptr->templ_name);
BattleSkillReadyImageAnalyzer skill_analyzer(image);
bool used = false;
for (auto& [loc, oper_name] : m_used_tiles) {
std::string status_key = Status::RoguelikeSkillUsagePrefix + oper_name;
@@ -920,16 +915,11 @@ bool asst::RoguelikeBattleTaskPlugin::try_possible_skill(const cv::Mat& image)
continue;
}
const Point pos = m_normal_tile_info.at(loc).pos;
const Rect pos_rect(pos.x, pos.y, 1, 1);
auto roi = make_rect<cv::Rect>(pos_rect.move(skill_roi_move));
roi &= cv::Rect(0, 0, image.cols, image.rows);
auto result = find_skill_ready(image(roi), temp);
if (result.empty()) continue;
Log.trace(result.front().second);
ctrler()->click(pos_rect);
skill_analyzer.set_base_point(pos);
if (!skill_analyzer.analyze()) {
continue;
}
ctrler()->click(pos);
sleep(Task.get("BattleUseOper")->pre_delay);
bool ret = ProcessTask(*this, { "BattleSkillReadyOnClick" }).set_retry_times(2).run();
if (!ret) {

View File

@@ -1,79 +0,0 @@
#pragma once
#include "Utils/NoWarningCV.h"
#include <algorithm>
#include <functional>
#include <vector>
namespace asst
{
inline void key_color(cv::InputArray src, cv::OutputArray dst, const cv::Scalar& color,
const cv::Scalar& tolerance = {})
{
return cv::inRange(src, color - tolerance, color + tolerance, dst);
}
template <typename Pred, typename Compare = std::less<>>
inline auto match_template_helper(
const cv::Mat1f& image, const cv::Size& repel_box = { 0, 0 }, Pred&& filter = [](auto&&) { return true; },
Compare&& cmp = Compare {})
{
using value_type = std::pair<cv::Point, double>;
std::vector<value_type> result {};
for (auto i = 0; i < image.cols; ++i)
for (auto j = 0; j < image.rows; ++j) {
const cv::Point pt = { i, j };
const auto value = image.at<float>(pt);
if (!filter(value)) continue;
auto iter = std::find_if(result.rbegin(), result.rend(), [&](const value_type& pair) {
const auto diff = pair.first - pt;
return std::abs(diff.x) < repel_box.width && std::abs(diff.y) < repel_box.height;
});
if (iter != result.rend()) {
if (cmp(iter->second, value)) {
iter->first = pt;
iter->second = value;
}
}
else {
result.emplace_back(pt, value);
}
}
return result;
}
inline auto find_skill_ready(const cv::Mat& image, const cv::Mat& temp)
{
auto preprocess = [](cv::Mat &img) {
cv::Mat mask_y2;
key_color(img, mask_y2, { 40, 94, 103 }, { 10, 10, 10 }); // BGR
img.setTo(cv::Scalar { 2, 216, 255 }, mask_y2); // BGR
cv::Mat mask_y1;
key_color(img, mask_y1, { 2, 216, 255 }, { 20, 20, 20 });
cv::dilate(mask_y1, mask_y1, cv::getStructuringElement(cv::MORPH_RECT, { 4, 4 }));
cv::bitwise_not(mask_y1, mask_y1);
img.setTo(cv::Scalar { 0, 0, 0 }, mask_y1);
};
cv::Mat tmp = temp.clone();
cv::Mat img = image.clone();
cv::Mat template_mask;
key_color(tmp, template_mask, { 0, 255, 0 }, { 0, 0, 0 });
cv::bitwise_not(template_mask, template_mask);
preprocess(img);
preprocess(tmp);
cv::Mat match;
cv::matchTemplate(img, tmp, match, cv::TM_SQDIFF, template_mask);
match /= template_mask.cols * template_mask.rows;
cv::sqrt(match, match);
auto result = match_template_helper(
match, { tmp.cols, tmp.rows }, [](float v) { return v < 130.; }, std::greater<> {});
return result;
}
} // namespace asst

View File

@@ -51,4 +51,4 @@ namespace asst
using InstHelper::ctrler;
using InstHelper::need_exit;
};
}
} // namespace asst

View File

@@ -12,10 +12,8 @@
#include "Vision/MultiMatchImageAnalyzer.h"
#include "Vision/OcrWithFlagTemplImageAnalyzer.h"
#include "Utils/Logger.hpp"
#include "Utils/MoreCV.hpp"
bool
asst::BattleImageAnalyzer::set_target(int target)
bool asst::BattleImageAnalyzer::set_target(int target)
{
m_target = target;
return true;
@@ -43,18 +41,7 @@ bool asst::BattleImageAnalyzer::analyze()
// 可能没有干员(全上场了),所以干员识别结果不影响返回值
if (m_target & Target::Oper) {
bool oper_ret = opers_analyze();
if (m_target == Target::Skill) {
ret = oper_ret;
}
}
// 可能没有可使用的技能,所以技能识别结果不影响返回值
if (m_target & Target::Skill) {
bool skill_ret = skill_analyze();
if (m_target == Target::Skill) {
ret = skill_ret;
}
opers_analyze();
}
if (m_target & Target::Kills) {
@@ -82,11 +69,6 @@ const std::vector<asst::Rect>& asst::BattleImageAnalyzer::get_homes() const noex
return m_homes;
}
const std::vector<asst::Rect>& asst::BattleImageAnalyzer::get_ready_skills() const noexcept
{
return m_ready_skills;
}
int asst::BattleImageAnalyzer::get_hp() const noexcept
{
return m_hp;
@@ -358,30 +340,6 @@ bool asst::BattleImageAnalyzer::home_analyze()
return true;
}
bool asst::BattleImageAnalyzer::skill_analyze()
{
LogTraceFunction;
const auto skill_task_ptr = Task.get<MatchTaskInfo>("BattleSkillReady");
const Rect& rect_move = skill_task_ptr->rect_move;
cv::Mat temp = TemplResource::get_instance().get_templ(skill_task_ptr->templ_name);
const auto result = find_skill_ready(m_image(make_rect<cv::Rect>(skill_task_ptr->roi)), temp);
if (result.empty()) return false;
ranges::transform(result, std::back_inserter(m_ready_skills), [&](auto&& pair) {
const cv::Point& pos = pair.first;
return Rect { pos.x + skill_task_ptr->roi.x, pos.y + skill_task_ptr->roi.y, temp.cols, temp.rows }.move(
rect_move);
});
Log.trace(m_ready_skills);
return true;
}
bool asst::BattleImageAnalyzer::hp_analyze()
{
// 识别 HP 的那个蓝白色图标

View File

@@ -14,7 +14,7 @@ namespace asst
HP = 1, // 剩余生命值
Home = 2, // 蓝色的家门
Oper = 4, // 下方的干员信息
Skill = 8, // cd 转好了可以使用的技能
//Skill = 8, // cd 转好了可以使用的技能
Kills = 16, // 击杀数
Cost = 32, // 费用
Vacancies = 64, // 剩余可部署干员数
@@ -33,7 +33,6 @@ namespace asst
virtual const std::vector<BattleRealTimeOper>& get_opers() const noexcept;
virtual const std::vector<Rect>& get_homes() const noexcept;
const std::vector<Rect>& get_ready_skills() const noexcept;
int get_hp() const noexcept;
int get_kills() const noexcept;
int get_total_kills() const noexcept;
@@ -50,7 +49,7 @@ namespace asst
bool oper_available_analyze(const Rect& roi);
bool home_analyze(); // 识别蓝色的家门
bool skill_analyze(); // 识别技能是否可用
//bool skill_analyze(); // 识别技能是否可用
bool hp_analyze(); // 识别剩余生命值
bool kills_analyze(); // 识别击杀数
bool cost_analyze(); // 识别费用

View File

@@ -0,0 +1,66 @@
#include "BattleSkillReadyImageAnalyzer.h"
#include "Utils/NoWarningCV.h"
#include <algorithm>
#include <functional>
#include <vector>
#include "Config/TaskData.h"
#include "Config/TemplResource.h"
#include "Utils/Logger.hpp"
bool asst::BattleSkillReadyImageAnalyzer::analyze()
{
auto task_ptr = Task.get<MatchTaskInfo>("BattleAutoSkillFlag");
const cv::Mat& templ = TemplResource::get_instance().get_templ(task_ptr->templ_name);
auto key_color = [](cv::InputArray src, cv::OutputArray dst, const cv::Scalar& color,
const cv::Scalar& tolerance = {}) {
cv::inRange(src, color - tolerance, color + tolerance, dst);
};
auto preprocess = [&](cv::Mat& img) {
cv::Mat mask_y2;
key_color(img, mask_y2, { 40, 94, 103 }, { 10, 10, 10 }); // BGR
img.setTo(cv::Scalar { 2, 216, 255 }, mask_y2); // BGR
cv::Mat mask_y1;
key_color(img, mask_y1, { 2, 216, 255 }, { 20, 20, 20 });
cv::dilate(mask_y1, mask_y1, cv::getStructuringElement(cv::MORPH_RECT, { 4, 4 }));
cv::bitwise_not(mask_y1, mask_y1);
img.setTo(cv::Scalar { 0, 0, 0 }, mask_y1);
};
cv::Mat tmp = templ.clone();
cv::Mat img = m_image.clone()(make_rect<cv::Rect>(m_roi));
cv::Mat template_mask;
key_color(tmp, template_mask, { 0, 255, 0 }, { 0, 0, 0 });
cv::bitwise_not(template_mask, template_mask);
preprocess(img);
preprocess(tmp);
cv::Mat match;
cv::matchTemplate(img, tmp, match, cv::TM_SQDIFF, template_mask);
match /= template_mask.cols * template_mask.rows;
cv::sqrt(match, match);
// TODO: 这块我没太看懂,等 horror 改改
//auto all_matched = match_template_helper(
// match, { tmp.cols, tmp.rows }, [](float v) { return v < 130.; }, std::greater<> {});
//if (all_matched.empty()) {
// return false;
//}
//m_result = all_matched.front();
return true;
}
void asst::BattleSkillReadyImageAnalyzer::set_base_point(const Point& pt)
{
auto task_ptr = Task.get<MatchTaskInfo>("BattleAutoSkillFlag");
const Rect& skill_roi_move = task_ptr->rect_move;
set_roi(Rect(pt.x, pt.y, 0, 0).move(skill_roi_move));
}

View File

@@ -0,0 +1,20 @@
#pragma once
#include "Vision/AbstractImageAnalyzer.h"
namespace asst
{
class BattleSkillReadyImageAnalyzer final : public AbstractImageAnalyzer
{
public:
using AbstractImageAnalyzer::AbstractImageAnalyzer;
virtual ~BattleSkillReadyImageAnalyzer() override = default;
virtual bool analyze() override;
void set_base_point(const Point& pt);
private:
MatchRect m_result;
};
}

View File

@@ -3,7 +3,6 @@
#include "Utils/Ranges.hpp"
#include <utility>
#include "Utils/MoreCV.hpp"
#include "Utils/NoWarningCV.h"
#include "Config/TaskData.h"
@@ -122,14 +121,32 @@ bool asst::MultiMatchImageAnalyzer::multi_match_templ(const cv::Mat templ)
cv::matchTemplate(image_roi, templ, matched, cv::TM_CCOEFF_NORMED, mask);
}
const auto result =
match_template_helper(matched, { static_cast<int>(templ.cols * 0.6), static_cast<int>(templ.rows * 0.6) },
[this](float value) -> bool { return m_templ_thres < value && value < 2.0; });
ranges::transform(result, std::back_inserter(m_result), [&, this](const auto& pair) -> MatchRect {
const cv::Point roi_pt = pair.first;
return { pair.second, { roi_pt.x + m_roi.x, roi_pt.y + m_roi.y, templ.cols, templ.rows } };
});
int mini_distance = (std::min)(templ.cols, templ.rows) / 2;
for (int i = 0; i != matched.rows; ++i) {
for (int j = 0; j != matched.cols; ++j) {
auto value = matched.at<float>(i, j);
if (m_templ_thres <= value && value < 2.0) {
Rect rect(j + m_roi.x, i + m_roi.y, templ.cols, templ.rows);
bool need_push = true;
// 如果有两个点离得太近,只取里面得分高的那个
// 一般相邻的都是刚刚push进去的这里倒序快一点
for (auto& iter : ranges::reverse_view(m_result)) {
if (std::abs(j + m_roi.x - iter.rect.x) < mini_distance &&
std::abs(i + m_roi.y - iter.rect.y) < mini_distance) {
if (iter.score < value) {
iter.rect = rect;
iter.score = value;
} // else 这个点就放弃了
need_push = false;
break;
}
}
if (need_push) {
m_result.emplace_back(value, rect);
}
}
}
}
#ifdef ASST_DEBUG
for (const auto& rect : m_result) {