diff --git a/src/MaaCore/MaaCore.vcxproj b/src/MaaCore/MaaCore.vcxproj index 9b4ae492f1..b73052e7f1 100644 --- a/src/MaaCore/MaaCore.vcxproj +++ b/src/MaaCore/MaaCore.vcxproj @@ -35,6 +35,7 @@ + @@ -120,7 +121,6 @@ - @@ -150,6 +150,7 @@ + diff --git a/src/MaaCore/MaaCore.vcxproj.filters b/src/MaaCore/MaaCore.vcxproj.filters index 9862636263..40f5d8e07a 100644 --- a/src/MaaCore/MaaCore.vcxproj.filters +++ b/src/MaaCore/MaaCore.vcxproj.filters @@ -270,9 +270,6 @@ 源文件\Utils - - 源文件\Utils - 源文件\Utils @@ -429,6 +426,9 @@ 源文件\Vision\Miscellaneous + + 源文件\Vision\Miscellaneous + @@ -710,5 +710,8 @@ 源文件\Vision\Miscellaneous + + 源文件\Vision\Miscellaneous + \ No newline at end of file diff --git a/src/MaaCore/Task/Miscellaneous/BattleProcessTask.cpp b/src/MaaCore/Task/Miscellaneous/BattleProcessTask.cpp index 85a9b79e07..64420167ac 100644 --- a/src/MaaCore/Task/Miscellaneous/BattleProcessTask.cpp +++ b/src/MaaCore/Task/Miscellaneous/BattleProcessTask.cpp @@ -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("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(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); diff --git a/src/MaaCore/Task/Roguelike/RoguelikeBattleTaskPlugin.cpp b/src/MaaCore/Task/Roguelike/RoguelikeBattleTaskPlugin.cpp index d17187b198..2e332d1b5c 100644 --- a/src/MaaCore/Task/Roguelike/RoguelikeBattleTaskPlugin.cpp +++ b/src/MaaCore/Task/Roguelike/RoguelikeBattleTaskPlugin.cpp @@ -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("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(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) { diff --git a/src/MaaCore/Utils/MoreCV.hpp b/src/MaaCore/Utils/MoreCV.hpp deleted file mode 100644 index 998fa154d0..0000000000 --- a/src/MaaCore/Utils/MoreCV.hpp +++ /dev/null @@ -1,79 +0,0 @@ -#pragma once - -#include "Utils/NoWarningCV.h" - -#include -#include -#include - -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 > - 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; - std::vector 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(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 diff --git a/src/MaaCore/Vision/AbstractImageAnalyzer.h b/src/MaaCore/Vision/AbstractImageAnalyzer.h index e7d2047750..63efee8f3e 100644 --- a/src/MaaCore/Vision/AbstractImageAnalyzer.h +++ b/src/MaaCore/Vision/AbstractImageAnalyzer.h @@ -51,4 +51,4 @@ namespace asst using InstHelper::ctrler; using InstHelper::need_exit; }; -} +} // namespace asst diff --git a/src/MaaCore/Vision/Miscellaneous/BattleImageAnalyzer.cpp b/src/MaaCore/Vision/Miscellaneous/BattleImageAnalyzer.cpp index 7c7b1339eb..f4e86664ff 100644 --- a/src/MaaCore/Vision/Miscellaneous/BattleImageAnalyzer.cpp +++ b/src/MaaCore/Vision/Miscellaneous/BattleImageAnalyzer.cpp @@ -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::BattleImageAnalyzer::get_homes() const noex return m_homes; } -const std::vector& 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("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(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 的那个蓝白色图标 diff --git a/src/MaaCore/Vision/Miscellaneous/BattleImageAnalyzer.h b/src/MaaCore/Vision/Miscellaneous/BattleImageAnalyzer.h index b0c337d611..caf43a87d5 100644 --- a/src/MaaCore/Vision/Miscellaneous/BattleImageAnalyzer.h +++ b/src/MaaCore/Vision/Miscellaneous/BattleImageAnalyzer.h @@ -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& get_opers() const noexcept; virtual const std::vector& get_homes() const noexcept; - const std::vector& 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(); // 识别费用 diff --git a/src/MaaCore/Vision/Miscellaneous/BattleSkillReadyImageAnalyzer.cpp b/src/MaaCore/Vision/Miscellaneous/BattleSkillReadyImageAnalyzer.cpp new file mode 100644 index 0000000000..de650268ae --- /dev/null +++ b/src/MaaCore/Vision/Miscellaneous/BattleSkillReadyImageAnalyzer.cpp @@ -0,0 +1,66 @@ +#include "BattleSkillReadyImageAnalyzer.h" + +#include "Utils/NoWarningCV.h" + +#include +#include +#include + +#include "Config/TaskData.h" +#include "Config/TemplResource.h" +#include "Utils/Logger.hpp" + +bool asst::BattleSkillReadyImageAnalyzer::analyze() +{ + auto task_ptr = Task.get("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(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("BattleAutoSkillFlag"); + const Rect& skill_roi_move = task_ptr->rect_move; + + set_roi(Rect(pt.x, pt.y, 0, 0).move(skill_roi_move)); +} diff --git a/src/MaaCore/Vision/Miscellaneous/BattleSkillReadyImageAnalyzer.h b/src/MaaCore/Vision/Miscellaneous/BattleSkillReadyImageAnalyzer.h new file mode 100644 index 0000000000..4411943910 --- /dev/null +++ b/src/MaaCore/Vision/Miscellaneous/BattleSkillReadyImageAnalyzer.h @@ -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; + }; +} diff --git a/src/MaaCore/Vision/MultiMatchImageAnalyzer.cpp b/src/MaaCore/Vision/MultiMatchImageAnalyzer.cpp index 51cadfb761..a2e1bcf0c2 100644 --- a/src/MaaCore/Vision/MultiMatchImageAnalyzer.cpp +++ b/src/MaaCore/Vision/MultiMatchImageAnalyzer.cpp @@ -3,7 +3,6 @@ #include "Utils/Ranges.hpp" #include -#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(templ.cols * 0.6), static_cast(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(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) {