From 578e5abc0511c5560d8dc4c2b15f208fa0a1417b Mon Sep 17 00:00:00 2001 From: MistEO Date: Thu, 27 Apr 2023 13:54:03 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20Vision=20=E8=B6=85=E7=BA=A7?= =?UTF-8?q?=E5=A4=A7=E9=87=8D=E6=9E=84=20Phase=201?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/MaaCore/Common/AsstTypes.h | 33 +-- .../Config/Miscellaneous/OcrConfig.cpp | 4 +- src/MaaCore/Config/Miscellaneous/OcrConfig.h | 2 +- src/MaaCore/Config/Miscellaneous/OcrPack.cpp | 129 +++------ src/MaaCore/Config/Miscellaneous/OcrPack.h | 15 +- src/MaaCore/MaaCore.vcxproj | 6 +- src/MaaCore/MaaCore.vcxproj.filters | 21 +- src/MaaCore/Task/BattleHelper.cpp | 63 ++-- src/MaaCore/Task/BattleHelper.h | 2 + .../CombatRecordRecognitionTask.cpp | 56 ++-- .../Task/Infrast/InfrastAbstractTask.cpp | 12 +- src/MaaCore/Task/Infrast/InfrastDormTask.cpp | 4 +- .../Task/Infrast/InfrastProductionTask.cpp | 13 +- .../Task/Infrast/InfrastReceptionTask.cpp | 11 +- .../Miscellaneous/BattleFormationTask.cpp | 17 +- src/MaaCore/Task/ProcessTask.cpp | 10 +- .../Reclamation/ReclamationBattlePlugin.cpp | 4 +- .../ReclamationConclusionReportPlugin.cpp | 2 +- .../Roguelike/RoguelikeBattleTaskPlugin.cpp | 3 +- .../Roguelike/RoguelikeRecruitTaskPlugin.cpp | 2 +- .../RoguelikeStageEncounterTaskPlugin.cpp | 3 +- src/MaaCore/Task/SSS/SSSStageManagerTask.cpp | 3 +- src/MaaCore/Vision/AbstractImageAnalyzer.cpp | 50 +--- src/MaaCore/Vision/AbstractImageAnalyzer.h | 88 ++++-- .../BattleDeployDirectionImageAnalyzer.h | 2 +- .../Battle/BattleFormationImageAnalyzer.cpp | 7 +- .../Battle/BattleFormationImageAnalyzer.h | 2 +- .../Vision/Battle/BattleImageAnalyzer.cpp | 11 +- .../Vision/Battle/BattleImageAnalyzer.h | 2 +- .../Battle/BattleOperatorsImageAnalyzer.h | 2 +- .../Battle/BattleSkillReadyImageAnalyzer.h | 2 +- src/MaaCore/Vision/BestMatchImageAnalyzer.cpp | 82 +++--- src/MaaCore/Vision/BestMatchImageAnalyzer.h | 42 ++- src/MaaCore/Vision/Config/MatcherConfig.cpp | 47 +++ src/MaaCore/Vision/Config/MatcherConfig.h | 45 +++ src/MaaCore/Vision/Config/OCRerConfig.cpp | 68 +++++ src/MaaCore/Vision/Config/OCRerConfig.h | 53 ++++ src/MaaCore/Vision/HashImageAnalyzer.h | 3 +- .../Infrast/InfrastClueImageAnalyzer.cpp | 36 --- .../Vision/Infrast/InfrastClueImageAnalyzer.h | 23 -- .../Infrast/InfrastClueVacancyImageAnalyzer.h | 2 +- .../Infrast/InfrastFacilityImageAnalyzer.cpp | 6 +- .../Infrast/InfrastFacilityImageAnalyzer.h | 2 +- .../Infrast/InfrastOperImageAnalyzer.cpp | 4 +- .../Vision/Infrast/InfrastOperImageAnalyzer.h | 2 +- .../Infrast/InfrastSmileyImageAnalyzer.h | 2 +- src/MaaCore/Vision/MatchImageAnalyzer.cpp | 180 ++++-------- src/MaaCore/Vision/MatchImageAnalyzer.h | 63 ++-- .../Miscellaneous/CreditShopImageAnalyzer.cpp | 3 +- .../Miscellaneous/CreditShopImageAnalyzer.h | 2 +- .../Miscellaneous/DepotImageAnalyzer.cpp | 6 +- .../Vision/Miscellaneous/DepotImageAnalyzer.h | 2 +- .../Miscellaneous/OperBoxImageAnalyzer.cpp | 35 +-- .../Miscellaneous/OperBoxImageAnalyzer.h | 6 +- .../ProcessTaskImageAnalyzer.cpp | 214 ++++++-------- .../Miscellaneous/ProcessTaskImageAnalyzer.h | 45 ++- .../Miscellaneous/RecruitImageAnalyzer.cpp | 11 +- .../Miscellaneous/RecruitImageAnalyzer.h | 2 +- .../Miscellaneous/StageDropsImageAnalyzer.cpp | 19 +- .../Miscellaneous/StageDropsImageAnalyzer.h | 2 +- .../Vision/MultiMatchImageAnalyzer.cpp | 174 +++-------- src/MaaCore/Vision/MultiMatchImageAnalyzer.h | 54 ++-- src/MaaCore/Vision/OcrImageAnalyzer.cpp | 269 +++++------------- src/MaaCore/Vision/OcrImageAnalyzer.h | 67 ++--- .../Vision/OcrWithFlagTemplImageAnalyzer.cpp | 86 +++--- .../Vision/OcrWithFlagTemplImageAnalyzer.h | 51 ++-- .../Vision/OcrWithPreprocessImageAnalyzer.cpp | 70 ++--- .../Vision/OcrWithPreprocessImageAnalyzer.h | 31 +- .../RoguelikeFormationImageAnalyzer.cpp | 26 +- .../RoguelikeFormationImageAnalyzer.h | 2 +- .../RoguelikeRecruitImageAnalyzer.cpp | 24 +- .../Roguelike/RoguelikeRecruitImageAnalyzer.h | 2 +- .../RoguelikeRecruitSupportAnalyzer.cpp | 7 +- .../RoguelikeRecruitSupportAnalyzer.h | 2 +- .../RoguelikeSkillSelectionImageAnalyzer.cpp | 4 +- .../RoguelikeSkillSelectionImageAnalyzer.h | 2 +- 76 files changed, 1093 insertions(+), 1366 deletions(-) create mode 100644 src/MaaCore/Vision/Config/MatcherConfig.cpp create mode 100644 src/MaaCore/Vision/Config/MatcherConfig.h create mode 100644 src/MaaCore/Vision/Config/OCRerConfig.cpp create mode 100644 src/MaaCore/Vision/Config/OCRerConfig.h delete mode 100644 src/MaaCore/Vision/Infrast/InfrastClueImageAnalyzer.cpp delete mode 100644 src/MaaCore/Vision/Infrast/InfrastClueImageAnalyzer.h diff --git a/src/MaaCore/Common/AsstTypes.h b/src/MaaCore/Common/AsstTypes.h index 7b9b25a7ea..5ddca8d3a9 100644 --- a/src/MaaCore/Common/AsstTypes.h +++ b/src/MaaCore/Common/AsstTypes.h @@ -194,48 +194,27 @@ namespace asst struct TextRect { - TextRect() = default; - ~TextRect() = default; - TextRect(const TextRect&) = default; - TextRect(TextRect&&) noexcept = default; - TextRect(double score, const Rect& rect, const std::string& text) : score(score), rect(rect), text(text) {} - - TextRect& operator=(const TextRect&) = default; - TextRect& operator=(TextRect&&) noexcept = default; - bool operator==(const TextRect& rhs) const noexcept { return text == rhs.text && rect == rhs.rect; } - bool operator==(const std::string& rhs) const noexcept { return text == rhs; } - explicit operator Rect() const noexcept { return rect; } std::string to_string() const { return "{ " + text + ": " + rect.to_string() + ", score: " + std::to_string(score) + " }"; } explicit operator std::string() const { return to_string(); } - double score = 0.0; Rect rect; + double score = 0.0; std::string text; }; - using TextRectProc = std::function; struct MatchRect { - MatchRect() = default; - ~MatchRect() = default; - MatchRect(const MatchRect&) = default; - MatchRect(MatchRect&&) noexcept = default; - MatchRect(double score, const Rect& rect) : score(score), rect(rect) {} - - explicit operator Rect() const noexcept { return rect; } - MatchRect& operator=(const MatchRect&) = default; - MatchRect& operator=(MatchRect&&) noexcept = default; std::string to_string() const { return "{ rect: " + rect.to_string() + ", score: " + std::to_string(score) + " }"; } explicit operator std::string() const { return to_string(); } - double score = 0.0; Rect rect; + double score = 0.0; }; } // namespace asst @@ -259,14 +238,6 @@ namespace std std::hash()(rect.height); } }; - template <> - struct hash - { - size_t operator()(const asst::TextRect& tr) const noexcept - { - return std::hash()(tr.text) ^ std::hash()(tr.rect); - } - }; } namespace asst diff --git a/src/MaaCore/Config/Miscellaneous/OcrConfig.cpp b/src/MaaCore/Config/Miscellaneous/OcrConfig.cpp index 251c7a475d..6d7a5257ae 100644 --- a/src/MaaCore/Config/Miscellaneous/OcrConfig.cpp +++ b/src/MaaCore/Config/Miscellaneous/OcrConfig.cpp @@ -4,7 +4,7 @@ #include "Utils/Logger.hpp" -std::string asst::OcrConfig::process_equivalence_class(const std::string& str) +std::string asst::OcrConfig::process_equivalence_class(const std::string& str) const { std::string result = str; for (const auto& eq_class : m_eq_classes) { @@ -17,6 +17,8 @@ std::string asst::OcrConfig::process_equivalence_class(const std::string& str) bool asst::OcrConfig::parse(const json::value& json) { + LogTraceFunction; + for (const json::value& eq_class : json.at("equivalence_classes").as_array()) { equivalence_class eq_class_tmp; for (const json::value& eq_element : eq_class.as_array()) { diff --git a/src/MaaCore/Config/Miscellaneous/OcrConfig.h b/src/MaaCore/Config/Miscellaneous/OcrConfig.h index 251b45203b..04536f1afd 100644 --- a/src/MaaCore/Config/Miscellaneous/OcrConfig.h +++ b/src/MaaCore/Config/Miscellaneous/OcrConfig.h @@ -18,7 +18,7 @@ namespace asst public: virtual ~OcrConfig() override = default; - std::string process_equivalence_class(const std::string& str); + std::string process_equivalence_class(const std::string& str) const; protected: virtual bool parse(const json::value& json) override; diff --git a/src/MaaCore/Config/Miscellaneous/OcrPack.cpp b/src/MaaCore/Config/Miscellaneous/OcrPack.cpp index e6cf727e76..26c7eacdcf 100644 --- a/src/MaaCore/Config/Miscellaneous/OcrPack.cpp +++ b/src/MaaCore/Config/Miscellaneous/OcrPack.cpp @@ -32,94 +32,49 @@ bool asst::OcrPack::load(const std::filesystem::path& path) LogTraceFunction; Log.info("load", path); - auto& paddle_dir = path; + fastdeploy::RuntimeOption option; + option.UseOrtBackend(); - fastdeploy::RuntimeOption det_option; - det_option.UseOrtBackend(); - bool build_det = false; + using namespace asst::utils::path_literals; + const auto det_dir = path / "det"_p; + const auto det_model_file = det_dir / "inference.onnx"_p; - fastdeploy::RuntimeOption rec_option; - rec_option.UseOrtBackend(); - std::string rec_label; - bool build_rec = false; + if (std::filesystem::exists(det_model_file)) { + auto det_model = asst::utils::read_file(det_model_file); + option.SetModelBuffer(det_model.data(), det_model.size(), nullptr, 0, fastdeploy::ModelFormat::ONNX); + m_det = std::make_unique("dummy.onnx", std::string(), option, + fastdeploy::ModelFormat::ONNX); + } - do { - using namespace asst::utils::path_literals; - const auto det_dir = paddle_dir / "det"_p; - const auto det_model_file = det_dir / "inference.onnx"_p; - const auto rec_dir = paddle_dir / "rec"_p; - const auto rec_model_file = rec_dir / "inference.onnx"_p; - const auto rec_label_file = rec_dir / "keys.txt"_p; + const auto rec_dir = path / "rec"_p; + const auto rec_model_file = rec_dir / "inference.onnx"_p; + const auto rec_label_file = rec_dir / "keys.txt"_p; - // fastdeploy 同时加载两个实例有可能会炸,不知道为啥,加个锁 - static std::mutex load_mutex; + if (std::filesystem::exists(rec_model_file) && std::filesystem::exists(rec_label_file)) { + auto rec_model = asst::utils::read_file(rec_model_file); + std::string rec_label = asst::utils::read_file(rec_label_file); + option.SetModelBuffer(rec_model.data(), rec_model.size(), nullptr, 0, fastdeploy::ModelFormat::ONNX); + m_rec = std::make_unique("dummy.onnx", std::string(), rec_label, option, + fastdeploy::ModelFormat::ONNX); + } - if (std::filesystem::exists(det_model_file)) { - auto det_model = asst::utils::read_file(det_model_file); - det_option.SetModelBuffer(det_model.data(), det_model.size(), nullptr, 0, fastdeploy::ModelFormat::ONNX); - build_det = true; - } - - if (std::filesystem::exists(rec_model_file) && std::filesystem::exists(rec_label_file)) { - auto rec_model = asst::utils::read_file(rec_model_file); - rec_label = asst::utils::read_file(rec_label_file); - rec_option.SetModelBuffer(rec_model.data(), rec_model.size(), nullptr, 0, fastdeploy::ModelFormat::ONNX); - build_rec = true; - } - - if (build_det) { - std::unique_lock lock(load_mutex); - m_det = std::make_unique("dummy.onnx", std::string(), det_option, - fastdeploy::ModelFormat::ONNX); - } - - if (build_rec) { - std::unique_lock lock(load_mutex); - m_rec = std::make_unique("dummy.onnx", std::string(), rec_label, - rec_option, fastdeploy::ModelFormat::ONNX); - } - - if (m_det && m_rec) { - m_ocr = std::make_unique(m_det.get(), m_rec.get()); - } - else { - break; - } - } while (false); + if (m_det && m_rec) { + m_ocr = std::make_unique(m_det.get(), m_rec.get()); + } return m_det != nullptr && m_rec != nullptr && m_ocr != nullptr && m_det->Initialized() && m_rec->Initialized() && m_ocr->Initialized(); } -std::vector asst::OcrPack::recognize(const cv::Mat& image, const Rect& roi, - const asst::TextRectProc& pred, bool without_det, bool trim) +asst::OcrPack::ResultsVec asst::OcrPack::recognize(const cv::Mat& image, bool without_det) { - auto rect_cor = [&roi, &pred, &without_det](TextRect& tr) -> bool { - if (without_det) { - tr.rect = roi; - } - else { - tr.rect.x += roi.x; - tr.rect.y += roi.y; - } - return pred(tr); - }; - Log.trace("OcrPack::recognize | roi:", roi); - cv::Mat roi_img = image(make_rect(roi)); - return recognize(roi_img, rect_cor, without_det, trim); -} - -std::vector asst::OcrPack::recognize(const cv::Mat& image, const asst::TextRectProc& pred, - bool without_det, bool trim) -{ - std::string class_type = utils::demangle(typeid(*this).name()); fastdeploy::vision::OCRResult ocr_result; + + auto start_time = std::chrono::steady_clock::now(); if (!without_det) { - LogTraceScope("Ocr Pipeline with " + class_type); m_ocr->Predict(image, &ocr_result); } else { - LogTraceScope("Ocr Rec with " + class_type); std::string rec_text; float rec_score = 0; m_rec->Predict(image, &rec_text, &rec_score); @@ -131,8 +86,7 @@ std::vector asst::OcrPack::recognize(const cv::Mat& image, const cv::Mat draw = image.clone(); #endif - std::vector raw_result; - std::vector proced_result; + ResultsVec raw_results; for (size_t i = 0; i != ocr_result.text.size(); ++i) { // the box rect like ↓ // 0 - 1 @@ -153,22 +107,17 @@ std::vector asst::OcrPack::recognize(const cv::Mat& image, const #ifdef ASST_DEBUG cv::rectangle(draw, make_rect(det_rect), cv::Scalar(0, 0, 255), 2); #endif - - double score = ocr_result.rec_scores.at(i); - if (score > 2.0) { - score = 0; - } - TextRect tr(score, det_rect, std::move(ocr_result.text.at(i))); - raw_result.emplace_back(tr); - if (trim) { - utils::string_trim(tr.text); - } - if (!pred || pred(tr)) { - proced_result.emplace_back(std::move(tr)); - } + Result result { + .rect = det_rect, + .score = ocr_result.rec_scores.at(i), + .text = std::move(ocr_result.text.at(i)), + }; + raw_results.emplace_back(std::move(result)); } - Log.trace("OcrPack::recognize | raw:", raw_result); - Log.trace("OcrPack::recognize | proc:", proced_result); - return proced_result; + auto costs = + std::chrono::duration_cast(std::chrono::steady_clock::now() - start_time).count(); + std::string class_type = utils::demangle(typeid(*this).name()); + Log.trace(class_type, raw_results, without_det ? "by Rec" : "by Pipeline", ", cost", costs, "ms"); + return raw_results; } diff --git a/src/MaaCore/Config/Miscellaneous/OcrPack.h b/src/MaaCore/Config/Miscellaneous/OcrPack.h index 00633eb880..9832110dc7 100644 --- a/src/MaaCore/Config/Miscellaneous/OcrPack.h +++ b/src/MaaCore/Config/Miscellaneous/OcrPack.h @@ -1,9 +1,9 @@ #pragma once -#include "Config/AbstractResource.h" - -#include #include "Common/AsstTypes.h" +#include "Config/AbstractResource.h" + +#include namespace cv { @@ -30,15 +30,16 @@ namespace asst { class OcrPack : public AbstractResource { + public: + using Result = TextRect; + using ResultsVec = std::vector; + public: virtual ~OcrPack() override; virtual bool load(const std::filesystem::path& path) override; - std::vector recognize(const cv::Mat& image, const TextRectProc& pred = nullptr, - bool without_det = false, bool trim = true); - std::vector recognize(const cv::Mat& image, const Rect& roi, const TextRectProc& pred = nullptr, - bool without_det = false, bool trim = true); + ResultsVec recognize(const cv::Mat& image, bool without_det = false); protected: OcrPack(); diff --git a/src/MaaCore/MaaCore.vcxproj b/src/MaaCore/MaaCore.vcxproj index e34219c7f7..aa1885f44a 100644 --- a/src/MaaCore/MaaCore.vcxproj +++ b/src/MaaCore/MaaCore.vcxproj @@ -89,8 +89,9 @@ + + - @@ -241,8 +242,9 @@ + + - diff --git a/src/MaaCore/MaaCore.vcxproj.filters b/src/MaaCore/MaaCore.vcxproj.filters index a1f98afa0f..57c9674c64 100644 --- a/src/MaaCore/MaaCore.vcxproj.filters +++ b/src/MaaCore/MaaCore.vcxproj.filters @@ -92,6 +92,9 @@ {987541fc-58f0-40eb-8b93-c0cb38881d92} + + {bf83678b-e9d7-4cf1-8616-aba2009072b6} + @@ -159,9 +162,6 @@ 源文件\Vision - - 源文件\Vision\Infrast - 源文件\Vision\Infrast @@ -609,6 +609,12 @@ 源文件\Vision\Miscellaneous + + 源文件\Vision\Config + + + 源文件\Vision\Config + @@ -632,9 +638,6 @@ 源文件\Vision - - 源文件\Vision\Infrast - 源文件\Vision\Infrast @@ -1004,5 +1007,11 @@ 源文件\Vision\Miscellaneous + + 源文件\Vision\Config + + + 源文件\Vision\Config + \ No newline at end of file diff --git a/src/MaaCore/Task/BattleHelper.cpp b/src/MaaCore/Task/BattleHelper.cpp index 1f2498e194..92c3e10230 100644 --- a/src/MaaCore/Task/BattleHelper.cpp +++ b/src/MaaCore/Task/BattleHelper.cpp @@ -133,8 +133,7 @@ bool asst::BattleHelper::update_deployment(bool init, const cv::Mat& reusable) static const double cooling_threshold = Task.get("BattleAvatarCoolingData")->templ_threshold; static const auto cooling_mask_range = Task.get("BattleAvatarCoolingData")->mask_range; avatar_analyzer.set_threshold(cooling_threshold); - avatar_analyzer.set_mask_range(cooling_mask_range, true); - avatar_analyzer.set_mask_with_close(true); + avatar_analyzer.set_mask_range(cooling_mask_range.first, cooling_mask_range.second, true, true); } else { static const double threshold = Task.get("BattleAvatarData")->templ_threshold; @@ -147,7 +146,7 @@ bool asst::BattleHelper::update_deployment(bool init, const cv::Mat& reusable) avatar_analyzer.append_templ(name, avatar); } if (avatar_analyzer.analyze()) { - set_oper_name(oper, avatar_analyzer.get_result().name); + set_oper_name(oper, avatar_analyzer.get_result().templ_info.name); m_cur_deployment_opers.insert_or_assign(oper.name, oper); remove_cooling_from_battlefield(oper); } @@ -195,33 +194,7 @@ bool asst::BattleHelper::update_deployment(bool init, const cv::Mat& reusable) return false; } - auto analyze = [&](OcrImageAnalyzer& name_analyzer) { - name_analyzer.set_image(name_image); - name_analyzer.set_task_info(oper_name_ocr_task_name()); - name_analyzer.set_replace(Task.get("CharsNameOcrReplace")->replace_map, - Task.get("CharsNameOcrReplace")->replace_full); - if (!name_analyzer.analyze()) { - return std::string(); - } - name_analyzer.sort_result_by_score(); - return name_analyzer.get_result().front().text; - }; - - OcrWithPreprocessImageAnalyzer preproc_analyzer; - std::string name = analyze(preproc_analyzer); - if (BattleData.is_name_invalid(name)) { - Log.warn("ocr with preprocess got a invalid name, try to use detect model", name); - OcrImageAnalyzer det_analyzer; - std::string det_name = analyze(det_analyzer); - if (det_name.empty()) { - Log.warn("ocr with det model failed"); - } - else if (!BattleData.is_name_invalid(det_name)) { - Log.info("use ocr with det", det_name); - name = det_name; - } - } - + std::string name = analyze_detail_page_oper_name(name_image); // 这时候即使名字不合法也只能凑合用了,但是为空还是不行的 if (name.empty()) { Log.error("name is empty"); @@ -399,7 +372,7 @@ bool asst::BattleHelper::check_pause_button(const cv::Mat& reusable) cv::Mat image = reusable.empty() ? m_inst_helper.ctrler()->get_image() : reusable; MatchImageAnalyzer battle_flag_analyzer(image); battle_flag_analyzer.set_task_info("BattleOfficiallyBegin"); - bool ret = battle_flag_analyzer.analyze(); + bool ret = battle_flag_analyzer.analyze().has_value(); BattleImageAnalyzer battle_flag_analyzer_2(image); ret &= battle_flag_analyzer_2.analyze() && battle_flag_analyzer_2.get_pause_button(); return ret; @@ -644,6 +617,34 @@ bool asst::BattleHelper::move_camera(const std::pair& delta) return update_deployment(true); } +std::string asst::BattleHelper::analyze_detail_page_oper_name(const cv::Mat& image) +{ + const auto& replace_task = Task.get("CharsNameOcrReplace"); + const auto& task = Task.get(oper_name_ocr_task_name()); + + OcrWithPreprocessImageAnalyzer preproc_analyzer(image); + preproc_analyzer.set_task_info(task); + preproc_analyzer.set_replace(replace_task->replace_map, replace_task->replace_full); + auto preproc_result_opt = preproc_analyzer.analyze(); + + if (preproc_result_opt && !BattleData.is_name_invalid(preproc_result_opt->text)) { + return preproc_result_opt->text; + } + + Log.warn("ocr with preprocess got a invalid name, try to use detect model"); + OcrImageAnalyzer det_analyzer(image); + det_analyzer.set_task_info(task); + det_analyzer.set_replace(replace_task->replace_map, replace_task->replace_full); + auto det_result_opt = det_analyzer.analyze(); + if (!det_result_opt) { + return {}; + } + sort_by_score_(*det_result_opt); + const auto& det_name = det_result_opt->front().text; + + return BattleData.is_name_invalid(det_name) ? std::string() : det_name; +} + std::optional asst::BattleHelper::get_oper_rect_on_deployment(const std::string& name) const { LogTraceFunction; diff --git a/src/MaaCore/Task/BattleHelper.h b/src/MaaCore/Task/BattleHelper.h index 641c9a27c1..6e2044ab4b 100644 --- a/src/MaaCore/Task/BattleHelper.h +++ b/src/MaaCore/Task/BattleHelper.h @@ -62,6 +62,8 @@ namespace asst bool cancel_oper_selection(); bool move_camera(const std::pair& delta); + std::string analyze_detail_page_oper_name(const cv::Mat& image); + std::optional get_oper_rect_on_deployment(const std::string& name) const; std::string m_stage_name; diff --git a/src/MaaCore/Task/Experiment/CombatRecordRecognitionTask.cpp b/src/MaaCore/Task/Experiment/CombatRecordRecognitionTask.cpp index 1254620135..890e2d4fdb 100644 --- a/src/MaaCore/Task/Experiment/CombatRecordRecognitionTask.cpp +++ b/src/MaaCore/Task/Experiment/CombatRecordRecognitionTask.cpp @@ -196,7 +196,7 @@ bool asst::CombatRecordRecognitionTask::analyze_stage() OcrWithPreprocessImageAnalyzer stage_analyzer(frame); stage_analyzer.set_task_info(stage_name_task_ptr); - bool analyzed = stage_analyzer.analyze(); + bool analyzed = stage_analyzer.analyze().has_value(); show_img(stage_analyzer); if (!analyzed) { @@ -209,8 +209,7 @@ bool asst::CombatRecordRecognitionTask::analyze_stage() // } continue; } - stage_analyzer.sort_result_by_score(); - const std::string& text = stage_analyzer.get_result().front().text; + const std::string& text = stage_analyzer.get_result().text; if (text.empty() || !Tile.find(text)) { continue; @@ -302,13 +301,13 @@ bool asst::CombatRecordRecognitionTask::analyze_deployment() candidate.emplace(flag, oper.avatar); } } - bool analyzed = best_match_analyzer.analyze(); + bool analyzed = best_match_analyzer.analyze().has_value(); // show_img(best_match_analyzer); if (!analyzed) { Log.warn(m_battle_start_frame, "failed to match", name); continue; } - m_all_avatars.emplace(name, candidate.at(best_match_analyzer.get_result().name)); + m_all_avatars.emplace(name, candidate.at(best_match_analyzer.get_result().templ_info.name)); } callback(AsstMsg::SubTaskCompleted, basic_info_with_what("MatchDeployment")); @@ -790,10 +789,10 @@ void asst::CombatRecordRecognitionTask::ananlyze_deployment_names(ClipInfo& clip avatar_analyzer.append_templ(name, avatar); } } - bool analyzed = avatar_analyzer.analyze(); + bool analyzed = avatar_analyzer.analyze().has_value(); // show_img(avatar_analyzer.get_draw()); if (analyzed) { - oper.name = avatar_analyzer.get_result().name; + oper.name = avatar_analyzer.get_result().templ_info.name; } else { oper.name = "UnknownDeployment"; @@ -858,32 +857,29 @@ size_t asst::CombatRecordRecognitionTask::skip_frames(size_t count) std::string asst::CombatRecordRecognitionTask::analyze_detail_page_oper_name(const cv::Mat& frame) { - auto analyze = [&](OcrImageAnalyzer& name_analyzer) { - name_analyzer.set_image(frame); - name_analyzer.set_task_info("BattleOperName"); - name_analyzer.set_replace(Task.get("CharsNameOcrReplace")->replace_map, - Task.get("CharsNameOcrReplace")->replace_full); - if (!name_analyzer.analyze()) { - return std::string(); - } - name_analyzer.sort_result_by_score(); - return name_analyzer.get_result().front().text; - }; + const auto& replace_task = Task.get("CharsNameOcrReplace"); - OcrWithPreprocessImageAnalyzer preproc_analyzer; - std::string name = analyze(preproc_analyzer); + OcrWithPreprocessImageAnalyzer preproc_analyzer(frame); + preproc_analyzer.set_task_info("BattleOperName"); + preproc_analyzer.set_replace(replace_task->replace_map, replace_task->replace_full); + auto preproc_result_opt = preproc_analyzer.analyze(); - if (BattleData.is_name_invalid(name)) { - Log.warn("ocr with preprocess got a invalid name, try to use detect model", name); - OcrImageAnalyzer det_analyzer; - std::string det_name = analyze(det_analyzer); - if (BattleData.is_name_invalid(det_name)) { - return std::string(); - } - Log.info("use ocr with det", det_name); - return det_name; + if (preproc_result_opt && !BattleData.is_name_invalid(preproc_result_opt->text)) { + return preproc_result_opt->text; } - return name; + + Log.warn("ocr with preprocess got a invalid name, try to use detect model"); + OcrImageAnalyzer det_analyzer(frame); + det_analyzer.set_task_info("BattleOperName"); + det_analyzer.set_replace(replace_task->replace_map, replace_task->replace_full); + auto det_result_opt = det_analyzer.analyze(); + if (!det_result_opt) { + return {}; + } + sort_by_score_(*det_result_opt); + const auto& det_name = det_result_opt->front().text; + + return BattleData.is_name_invalid(det_name) ? std::string() : det_name; } void asst::CombatRecordRecognitionTask::show_img(const asst::AbstractImageAnalyzer& analyzer) diff --git a/src/MaaCore/Task/Infrast/InfrastAbstractTask.cpp b/src/MaaCore/Task/Infrast/InfrastAbstractTask.cpp index cbcbe83823..24e67a651e 100644 --- a/src/MaaCore/Task/Infrast/InfrastAbstractTask.cpp +++ b/src/MaaCore/Task/Infrast/InfrastAbstractTask.cpp @@ -281,7 +281,7 @@ bool asst::InfrastAbstractTask::select_opers_review(infrast::CustomRoomConfig co OcrWithPreprocessImageAnalyzer name_analyzer; name_analyzer.set_replace(ocr_replace->replace_map, ocr_replace->replace_full); name_analyzer.set_image(oper.name_img); - name_analyzer.set_expansion(0); + name_analyzer.set_bin_expansion(0); if (!name_analyzer.analyze()) { continue; } @@ -289,7 +289,7 @@ bool asst::InfrastAbstractTask::select_opers_review(infrast::CustomRoomConfig co break; } - const std::string& name = name_analyzer.get_result().front().text; + const std::string& name = name_analyzer.get_result().text; if (auto iter = ranges::find(room_config.names, name); iter != room_config.names.end()) { Log.info(name, "在\"operators\"中,且已选中"); room_config.names.erase(iter); @@ -337,11 +337,11 @@ bool asst::InfrastAbstractTask::select_custom_opers(std::vector& pa OcrWithPreprocessImageAnalyzer name_analyzer; name_analyzer.set_replace(ocr_replace->replace_map, ocr_replace->replace_full); name_analyzer.set_image(oper.name_img); - name_analyzer.set_expansion(0); + name_analyzer.set_bin_expansion(0); if (!name_analyzer.analyze()) { continue; } - const std::string& name = name_analyzer.get_result().front().text; + const std::string& name = name_analyzer.get_result().text; partial_result.emplace_back(name); if (auto iter = ranges::find(room_config.names, name); iter != room_config.names.end()) { @@ -394,11 +394,11 @@ void asst::InfrastAbstractTask::order_opers_selection(const std::vectorreplace_map, ocr_replace->replace_full); name_analyzer.set_image(oper.name_img); - name_analyzer.set_expansion(0); + name_analyzer.set_bin_expansion(0); if (!name_analyzer.analyze()) { continue; } - TextRect tr = name_analyzer.get_result().front(); + TextRect tr = name_analyzer.get_result(); tr.rect = oper.rect; page_result.emplace_back(std::move(tr)); } diff --git a/src/MaaCore/Task/Infrast/InfrastDormTask.cpp b/src/MaaCore/Task/Infrast/InfrastDormTask.cpp index 1eb48abcc7..02b5d93935 100644 --- a/src/MaaCore/Task/Infrast/InfrastDormTask.cpp +++ b/src/MaaCore/Task/Infrast/InfrastDormTask.cpp @@ -137,7 +137,7 @@ bool asst::InfrastDormTask::opers_choose(asst::infrast::CustomRoomConfig const& break; } - std::string opertrust = trust_analyzer.get_result().front().text; + std::string opertrust = trust_analyzer.get_result().text; std::regex rule("[^0-9]"); // 只保留数字 opertrust = std::regex_replace(opertrust, rule, ""); Log.trace("opertrust:", opertrust); @@ -170,7 +170,7 @@ bool asst::InfrastDormTask::opers_choose(asst::infrast::CustomRoomConfig const& break; } - std::string facilityname = facility_analyzer.get_result().front().text; + std::string facilityname = facility_analyzer.get_result().text; std::regex rule2("[^BF0-9]"); // 只保留B、F和数字 facilityname = std::regex_replace(facilityname, rule2, ""); diff --git a/src/MaaCore/Task/Infrast/InfrastProductionTask.cpp b/src/MaaCore/Task/Infrast/InfrastProductionTask.cpp index a571c25c5e..ab757d1aa3 100644 --- a/src/MaaCore/Task/Infrast/InfrastProductionTask.cpp +++ b/src/MaaCore/Task/Infrast/InfrastProductionTask.cpp @@ -510,7 +510,7 @@ bool asst::InfrastProductionTask::optimal_calc() if (!name_analyzer.analyze()) { continue; } - std::string name = name_analyzer.get_result().front().text; + std::string name = name_analyzer.get_result().text; hash_matched = ranges::find(opt.name_filter, name) != opt.name_filter.cend(); } if (!hash_matched) { @@ -638,7 +638,7 @@ bool asst::InfrastProductionTask::opers_choose() if (!name_analyzer.analyze()) { return false; } - std::string name = name_analyzer.get_result().front().text; + std::string name = name_analyzer.get_result().text; return ranges::find(std::as_const(opt_iter->name_filter), name) != opt_iter->name_filter.cend(); } }); @@ -747,12 +747,13 @@ bool asst::InfrastProductionTask::facility_list_detect() mm_analyzer.set_task_info("InfrastFacilityListTab" + facility_name()); - if (!mm_analyzer.analyze()) { + auto result_opt = mm_analyzer.analyze(); + if (!result_opt) { return false; } - mm_analyzer.sort_result_horizontal(); - m_facility_list_tabs.reserve(mm_analyzer.get_result().size()); - for (const auto& res : mm_analyzer.get_result()) { + sort_by_vertical_(*result_opt); + const auto result = mm_analyzer.get_result(); + for (const auto& res : *result_opt) { m_facility_list_tabs.emplace_back(res.rect); } diff --git a/src/MaaCore/Task/Infrast/InfrastReceptionTask.cpp b/src/MaaCore/Task/Infrast/InfrastReceptionTask.cpp index ea2b03e284..686a683b1b 100644 --- a/src/MaaCore/Task/Infrast/InfrastReceptionTask.cpp +++ b/src/MaaCore/Task/Infrast/InfrastReceptionTask.cpp @@ -6,7 +6,7 @@ #include "Controller/Controller.h" #include "Task/ProcessTask.h" #include "Utils/Logger.hpp" -#include "Vision/Infrast/InfrastClueImageAnalyzer.h" +#include "Vision/MultiMatchImageAnalyzer.h" #include "Vision/Infrast/InfrastClueVacancyImageAnalyzer.h" #include "Vision/MatchImageAnalyzer.h" @@ -119,12 +119,15 @@ bool asst::InfrastReceptionTask::proc_clue_vacancy() // 识别右边列表中的线索,然后用最底下的那个(一般都是剩余时间最短的) // swipe_to_the_bottom_of_clue_list_on_the_right(); image = ctrler()->get_image(); - InfrastClueImageAnalyzer clue_analyzer(image); + MultiMatchImageAnalyzer clue_analyzer(image); + clue_analyzer.set_task_info("InfrastClue"); - if (!clue_analyzer.analyze()) { + auto clue_result_opt = clue_analyzer.analyze(); + if (!clue_result_opt) { continue; } - ctrler()->click(clue_analyzer.get_result().back().first); + sort_by_horizontal_(*clue_result_opt); + ctrler()->click(clue_result_opt->back().rect); sleep(delay); } return true; diff --git a/src/MaaCore/Task/Miscellaneous/BattleFormationTask.cpp b/src/MaaCore/Task/Miscellaneous/BattleFormationTask.cpp index 3bdaaba5f7..9a15b8e953 100644 --- a/src/MaaCore/Task/Miscellaneous/BattleFormationTask.cpp +++ b/src/MaaCore/Task/Miscellaneous/BattleFormationTask.cpp @@ -150,23 +150,18 @@ std::vector asst::BattleFormationTask::analyzer_opers() if (opers_result.empty()) { return {}; } - - // 按位置排个序 - ranges::sort(opers_result, [](const TextRect& lhs, const TextRect& rhs) -> bool { - if (std::abs(lhs.rect.x - rhs.rect.x) < 5) { // x差距较小则理解为是同一列的,按y排序 - return lhs.rect.y < rhs.rect.y; - } - else { - return lhs.rect.x < rhs.rect.x; // 否则按x排序 - } - }); + sort_by_vertical_(opers_result); if (m_the_right_name == opers_result.back().text) { return {}; } m_the_right_name = opers_result.back().text; - return opers_result; + std::vector tr_res; + for (const auto& res : opers_result) { + tr_res.emplace_back(TextRect { res.rect, res.score, res.text }); + } + return tr_res; } bool asst::BattleFormationTask::enter_selection_page() diff --git a/src/MaaCore/Task/ProcessTask.cpp b/src/MaaCore/Task/ProcessTask.cpp index fb4d1ce802..199573e8bd 100644 --- a/src/MaaCore/Task/ProcessTask.cpp +++ b/src/MaaCore/Task/ProcessTask.cpp @@ -120,13 +120,15 @@ bool ProcessTask::_run() else { cv::Mat image = m_reusable.empty() ? ctrler()->get_image() : m_reusable; m_reusable = cv::Mat(); - ProcessTaskImageAnalyzer analyzer(image, m_cur_task_name_list, m_inst); + ProcessTaskImageAnalyzer analyzer(image, Rect(), m_inst); + analyzer.set_tasks(m_cur_task_name_list); - if (!analyzer.analyze()) { + auto res_opt = analyzer.analyze(); + if (!res_opt) { return false; } - m_cur_task_ptr = analyzer.get_result(); - rect = analyzer.get_rect(); + m_cur_task_ptr = res_opt->task_ptr; + rect = res_opt->rect; } if (need_exit()) { return false; diff --git a/src/MaaCore/Task/Reclamation/ReclamationBattlePlugin.cpp b/src/MaaCore/Task/Reclamation/ReclamationBattlePlugin.cpp index 5a7872985b..1068621e31 100644 --- a/src/MaaCore/Task/Reclamation/ReclamationBattlePlugin.cpp +++ b/src/MaaCore/Task/Reclamation/ReclamationBattlePlugin.cpp @@ -79,12 +79,12 @@ bool asst::ReclamationBattlePlugin::quit_action() OcrImageAnalyzer confirmAnalyzer(img); confirmAnalyzer.set_task_info("Reclamation@ExitLevelConfirm"); - bool check2 = confirmAnalyzer.analyze(); + bool check2 = confirmAnalyzer.analyze().has_value(); // 出现Loading转一会儿就结算了,没结算还有error_next OcrImageAnalyzer loadingAnalyzer(img); loadingAnalyzer.set_task_info("LoadingText"); - bool check3 = loadingAnalyzer.analyze(); + bool check3 = loadingAnalyzer.analyze().has_value(); Log.info(__FUNCTION__, "| click exit level check ", check1, check2, check3); diff --git a/src/MaaCore/Task/Reclamation/ReclamationConclusionReportPlugin.cpp b/src/MaaCore/Task/Reclamation/ReclamationConclusionReportPlugin.cpp index 268e70628d..1c83611f09 100644 --- a/src/MaaCore/Task/Reclamation/ReclamationConclusionReportPlugin.cpp +++ b/src/MaaCore/Task/Reclamation/ReclamationConclusionReportPlugin.cpp @@ -37,7 +37,7 @@ bool asst::ReclamationConclusionReportPlugin::check_page_valid(const cv::Mat& im { MatchImageAnalyzer pageAnalyzer(image); pageAnalyzer.set_task_info("Reclamation@GiveupSkipConfirm"); - return pageAnalyzer.analyze(); + return pageAnalyzer.analyze().has_value(); } void asst::ReclamationConclusionReportPlugin::analyze(const cv::Mat& image) diff --git a/src/MaaCore/Task/Roguelike/RoguelikeBattleTaskPlugin.cpp b/src/MaaCore/Task/Roguelike/RoguelikeBattleTaskPlugin.cpp index 348c9c6913..1a116f2c08 100644 --- a/src/MaaCore/Task/Roguelike/RoguelikeBattleTaskPlugin.cpp +++ b/src/MaaCore/Task/Roguelike/RoguelikeBattleTaskPlugin.cpp @@ -129,8 +129,7 @@ bool asst::RoguelikeBattleTaskPlugin::calc_stage_info() if (!name_analyzer.analyze()) { continue; } - name_analyzer.sort_result_by_score(); - const std::string& text = name_analyzer.get_result().front().text; + const std::string& text = name_analyzer.get_result().text; if (text.empty()) { continue; } diff --git a/src/MaaCore/Task/Roguelike/RoguelikeRecruitTaskPlugin.cpp b/src/MaaCore/Task/Roguelike/RoguelikeRecruitTaskPlugin.cpp index 87e5d07d32..1f28883223 100644 --- a/src/MaaCore/Task/Roguelike/RoguelikeRecruitTaskPlugin.cpp +++ b/src/MaaCore/Task/Roguelike/RoguelikeRecruitTaskPlugin.cpp @@ -165,7 +165,7 @@ bool asst::RoguelikeRecruitTaskPlugin::_run() auto image = ctrler()->get_image(); MatchImageAnalyzer temp_recruit_analyzer(image); temp_recruit_analyzer.set_task_info("Roguelike@TempRecruitFlag"); - temp_recruit_exist = temp_recruit_analyzer.analyze(); + temp_recruit_exist = temp_recruit_analyzer.analyze().has_value(); Log.trace(__FUNCTION__, "temp_recruit_exist", temp_recruit_exist); // 翻页找出所有候选干员 diff --git a/src/MaaCore/Task/Roguelike/RoguelikeStageEncounterTaskPlugin.cpp b/src/MaaCore/Task/Roguelike/RoguelikeStageEncounterTaskPlugin.cpp index 56c32a1bcb..3c6cb17246 100644 --- a/src/MaaCore/Task/Roguelike/RoguelikeStageEncounterTaskPlugin.cpp +++ b/src/MaaCore/Task/Roguelike/RoguelikeStageEncounterTaskPlugin.cpp @@ -65,8 +65,7 @@ bool asst::RoguelikeStageEncounterTaskPlugin::_run() if (!name_analyzer.analyze()) { continue; } - name_analyzer.sort_result_by_score(); - const std::string& text = name_analyzer.get_result().front().text; + const std::string& text = name_analyzer.get_result().text; if (text.empty()) { continue; } diff --git a/src/MaaCore/Task/SSS/SSSStageManagerTask.cpp b/src/MaaCore/Task/SSS/SSSStageManagerTask.cpp index 6fd97a7409..c79380a2ec 100644 --- a/src/MaaCore/Task/SSS/SSSStageManagerTask.cpp +++ b/src/MaaCore/Task/SSS/SSSStageManagerTask.cpp @@ -77,8 +77,7 @@ std::optional asst::SSSStageManagerTask::analyze_stage() return std::nullopt; } - analyzer.sort_result_by_score(); - const std::string& text = analyzer.get_result().front().text; + const std::string& text = analyzer.get_result().text; const auto& stages_data = SSSCopilot.get_data().stages_data; for (const auto& [name, stage_info] : stages_data) { diff --git a/src/MaaCore/Vision/AbstractImageAnalyzer.cpp b/src/MaaCore/Vision/AbstractImageAnalyzer.cpp index b9108e5a6d..8fb7adaa56 100644 --- a/src/MaaCore/Vision/AbstractImageAnalyzer.cpp +++ b/src/MaaCore/Vision/AbstractImageAnalyzer.cpp @@ -9,55 +9,37 @@ #include "Utils/StringMisc.hpp" #include "Utils/Time.hpp" -asst::AbstractImageAnalyzer::AbstractImageAnalyzer(const cv::Mat& image) - : m_image(image), m_roi(correct_rect(Rect(), image)) +using namespace asst; + +AbstractImageAnalyzer::AbstractImageAnalyzer(const cv::Mat& image, const Rect& roi, Assistant* inst) + : InstHelper(inst), m_image(image), m_roi(correct_rect(roi, image)) #ifdef ASST_DEBUG , m_image_draw(image.clone()) #endif {} -#ifdef ASST_DEBUG -asst::AbstractImageAnalyzer::AbstractImageAnalyzer(const cv::Mat& image, cv::Mat& draw) - : m_image(image), m_roi(correct_rect(Rect(), image)), m_image_draw(draw) -{} -#endif - -asst::AbstractImageAnalyzer::AbstractImageAnalyzer(const cv::Mat& image, Assistant* inst) - : InstHelper(inst), m_image(image), m_roi(correct_rect(Rect(), image)) -#ifdef ASST_DEBUG - , - m_image_draw(image.clone()) -#endif -{} - -void asst::AbstractImageAnalyzer::set_image(const cv::Mat& image) +void AbstractImageAnalyzer::set_image(const cv::Mat& image) { m_image = image; #ifdef ASST_DEBUG m_image_draw = image.clone(); #endif + + set_roi(m_roi); } -#ifdef ASST_DEBUG -void asst::AbstractImageAnalyzer::set_image(const cv::Mat& image, cv::Mat& draw) -{ - m_image = image; - m_image_draw = draw; -} -#endif - -void asst::AbstractImageAnalyzer::set_roi(const Rect& roi) noexcept +void AbstractImageAnalyzer::set_roi(const Rect& roi) { m_roi = correct_rect(roi, m_image); } -void asst::AbstractImageAnalyzer::set_log_tracing(bool enable) +void AbstractImageAnalyzer::set_log_tracing(bool enable) { m_log_tracing = enable; } -asst::Rect asst::AbstractImageAnalyzer::correct_rect(const Rect& rect, const cv::Mat& image) noexcept +Rect AbstractImageAnalyzer::correct_rect(const Rect& rect, const cv::Mat& image) { if (image.empty()) { Log.error(__FUNCTION__, "image is empty"); @@ -96,23 +78,17 @@ asst::Rect asst::AbstractImageAnalyzer::correct_rect(const Rect& rect, const cv: return res; } -bool asst::AbstractImageAnalyzer::save_img(const std::filesystem::path& relative_dir) +bool AbstractImageAnalyzer::save_img(const std::filesystem::path& relative_dir) { std::string stem = utils::get_time_filestem(); auto relative_path = relative_dir / (stem + "_raw.png"); Log.trace("Save image", relative_path); - bool ret = asst::imwrite(relative_path, m_image); + bool ret = imwrite(relative_path, m_image); #ifdef ASST_DEBUG - asst::imwrite(relative_dir / (stem + "_draw.png"), m_image_draw); + imwrite(relative_dir / (stem + "_draw.png"), m_image_draw); #endif return ret; } -#ifdef ASST_DEBUG -cv::Mat asst::AbstractImageAnalyzer::get_draw() const -{ - return m_image_draw; -} -#endif diff --git a/src/MaaCore/Vision/AbstractImageAnalyzer.h b/src/MaaCore/Vision/AbstractImageAnalyzer.h index 8a0c0913ff..56078b7050 100644 --- a/src/MaaCore/Vision/AbstractImageAnalyzer.h +++ b/src/MaaCore/Vision/AbstractImageAnalyzer.h @@ -4,6 +4,7 @@ #include "InstHelper.h" #include "Utils/NoWarningCVMat.h" #include "Utils/Platform.hpp" +#include "Utils/Ranges.hpp" // #ifndef ASST_DEBUG // #define ASST_DEBUG @@ -14,54 +15,99 @@ namespace asst class TaskData; class Status; class Assistant; +} +namespace asst +{ class AbstractImageAnalyzer : protected InstHelper { public: AbstractImageAnalyzer() = default; - AbstractImageAnalyzer(const cv::Mat& image); -#ifdef ASST_DEBUG - AbstractImageAnalyzer(const cv::Mat& image, cv::Mat& draw); -#endif - AbstractImageAnalyzer(const cv::Mat& image, Assistant* inst); - AbstractImageAnalyzer(const AbstractImageAnalyzer&) = delete; - AbstractImageAnalyzer(AbstractImageAnalyzer&&) = delete; + AbstractImageAnalyzer(const cv::Mat& image, const Rect& roi = Rect(), Assistant* inst = nullptr); virtual ~AbstractImageAnalyzer() = default; virtual void set_image(const cv::Mat& image); -#ifdef ASST_DEBUG - virtual void set_image(const cv::Mat& image, cv::Mat& draw); -#endif - virtual void set_roi(const Rect& roi) noexcept; - - virtual bool analyze() = 0; + virtual void set_roi(const Rect& roi); virtual void set_log_tracing(bool enable); - AbstractImageAnalyzer& operator=(const AbstractImageAnalyzer&) = delete; - AbstractImageAnalyzer& operator=(AbstractImageAnalyzer&&) = delete; - bool save_img(const std::filesystem::path& relative_dir = utils::path("debug")); #ifdef ASST_DEBUG - cv::Mat get_draw() const; + const cv::Mat& get_draw() const { return m_image_draw; } #endif protected: using InstHelper::status; protected: - static Rect correct_rect(const Rect& rect, const cv::Mat& image) noexcept; + static Rect correct_rect(const Rect& rect, const cv::Mat& image); cv::Mat m_image; - Rect m_roi; - bool m_log_tracing = true; - #ifdef ASST_DEBUG cv::Mat m_image_draw; #endif + Rect m_roi; + bool m_log_tracing = true; private: using InstHelper::ctrler; using InstHelper::need_exit; }; + + template + inline static cv::Mat make_roi(const cv::Mat& img, const RectTy& roi) + { + return img(make_rect(roi)); + } + + // | 1 2 3 4 | + // | 5 6 7 8 | + template + inline static void sort_by_horizontal_(ResultsVec& results) + { + ranges::sort(results, [](const auto& lhs, const auto& rhs) -> bool { + // y 差距较小则理解为是同一排的,按x排序 + return std::abs(lhs.rect.y - rhs.rect.y) < 5 ? lhs.rect.x < rhs.rect.x : lhs.rect.y < rhs.rect.y; + }); + } + + // | 1 3 5 7 | + // | 2 4 6 8 | + template + inline static void sort_by_vertical_(ResultsVec& results) + { + ranges::sort(results, [](const auto& lhs, const auto& rhs) -> bool { + // x 差距较小则理解为是同一排的,按y排序 + return std::abs(lhs.rect.x - rhs.rect.x) < 5 ? lhs.rect.y < rhs.rect.y : lhs.rect.x < rhs.rect.x; + }); + } + + template + inline static void sort_by_score_(ResultsVec& results) + { + ranges::sort(results, std::greater {}, std::mem_fn(&ResultsVec::value_type::score)); + } + + template + inline static void sort_by_required_(ResultsVec& results, const std::vector& required) + { + std::unordered_map req_cache; + for (size_t i = 0; i != required.size(); ++i) { + req_cache.emplace(required.at(i), i + 1); + } + + // 不在 required 中的将被排在最后 + ranges::sort(results, [&req_cache](const auto& lhs, const auto& rhs) -> bool { + size_t lvalue = req_cache[lhs.text]; + size_t rvalue = req_cache[rhs.text]; + if (lvalue == 0) { + return false; + } + else if (rvalue == 0) { + return true; + } + return lvalue < rvalue; + }); + } + } // namespace asst diff --git a/src/MaaCore/Vision/Battle/BattleDeployDirectionImageAnalyzer.h b/src/MaaCore/Vision/Battle/BattleDeployDirectionImageAnalyzer.h index 1818907e23..9dc028f1eb 100644 --- a/src/MaaCore/Vision/Battle/BattleDeployDirectionImageAnalyzer.h +++ b/src/MaaCore/Vision/Battle/BattleDeployDirectionImageAnalyzer.h @@ -15,7 +15,7 @@ namespace asst virtual ~BattleDeployDirectionImageAnalyzer() override = default; void set_base_point(const Point& pt); - virtual bool analyze() override; + bool analyze(); size_t get_class_id() const { return m_class_id; } RawResults get_raw_results() const { return m_raw_results; } diff --git a/src/MaaCore/Vision/Battle/BattleFormationImageAnalyzer.cpp b/src/MaaCore/Vision/Battle/BattleFormationImageAnalyzer.cpp index eb6f01835c..ca008ddc20 100644 --- a/src/MaaCore/Vision/Battle/BattleFormationImageAnalyzer.cpp +++ b/src/MaaCore/Vision/Battle/BattleFormationImageAnalyzer.cpp @@ -12,14 +12,15 @@ bool asst::BattleFormationImageAnalyzer::analyze() LogTraceFunction; m_result.clear(); - ProcessTaskImageAnalyzer start_button_analyzer(m_image, Task.get("BattleStartAll")->next, nullptr); + ProcessTaskImageAnalyzer start_button_analyzer(m_image); + start_button_analyzer.set_tasks(Task.get("BattleStartAll")->next); if (!start_button_analyzer.analyze()) { return false; } OcrWithFlagTemplImageAnalyzer oper_names_analyzer(m_image); oper_names_analyzer.set_task_info("BattleFormationOCRNameFlag", "BattleFormationOperNames"); - oper_names_analyzer.set_expansion(3); + oper_names_analyzer.set_bin_expansion(3); if (!oper_names_analyzer.analyze()) { return false; } @@ -49,7 +50,7 @@ bool asst::BattleFormationImageAnalyzer::analyze() // 识别到了结果,但是确实没有可用的干员名。说明游戏UI可能是老版本的,布局不一样 if (m_result.empty()) { oper_names_analyzer.set_task_info("BattleFormationOCRNameFlag", "BattleFormationOperNamesOldVersion"); - oper_names_analyzer.set_expansion(2); + oper_names_analyzer.set_bin_expansion(2); if (!oper_names_analyzer.analyze()) { return false; } diff --git a/src/MaaCore/Vision/Battle/BattleFormationImageAnalyzer.h b/src/MaaCore/Vision/Battle/BattleFormationImageAnalyzer.h index cbc93c9baf..094c1a9f59 100644 --- a/src/MaaCore/Vision/Battle/BattleFormationImageAnalyzer.h +++ b/src/MaaCore/Vision/Battle/BattleFormationImageAnalyzer.h @@ -10,7 +10,7 @@ namespace asst using AbstractImageAnalyzer::AbstractImageAnalyzer; virtual ~BattleFormationImageAnalyzer() override = default; - virtual bool analyze() override; + bool analyze(); const auto& get_result() const { return m_result; } diff --git a/src/MaaCore/Vision/Battle/BattleImageAnalyzer.cpp b/src/MaaCore/Vision/Battle/BattleImageAnalyzer.cpp index cad7089f63..a34ce0c548 100644 --- a/src/MaaCore/Vision/Battle/BattleImageAnalyzer.cpp +++ b/src/MaaCore/Vision/Battle/BattleImageAnalyzer.cpp @@ -12,6 +12,7 @@ #include "Vision/MatchImageAnalyzer.h" #include "Vision/MultiMatchImageAnalyzer.h" #include "Vision/OcrWithFlagTemplImageAnalyzer.h" +#include "Vision/OcrWithPreprocessImageAnalyzer.h" bool asst::BattleImageAnalyzer::set_target(int target) { @@ -130,11 +131,11 @@ bool asst::BattleImageAnalyzer::opers_analyze() if (!flags_analyzer.analyze()) { return false; } - flags_analyzer.sort_result_horizontal(); auto flags = flags_analyzer.get_result(); if (flags.empty()) { return false; } + sort_by_horizontal_(flags); const auto click_move = Task.get("BattleOperClickRange")->rect_move; const auto role_move = Task.get("BattleOperRoleRange")->rect_move; @@ -145,7 +146,7 @@ bool asst::BattleImageAnalyzer::opers_analyze() // const int unselected_y = flag_task_ptr->roi.y; size_t index = 0; - for (const MatchRect& flag_mrect : flags) { + for (const auto& flag_mrect : flags) { battle::DeploymentOper oper; oper.rect = flag_mrect.rect.move(click_move); @@ -223,7 +224,7 @@ asst::battle::Role asst::BattleImageAnalyzer::oper_role_analyze(const Rect& roi) return battle::Role::Unknown; } - const auto& templ_name = role_analyzer.get_result().name; + const auto& templ_name = role_analyzer.get_result().templ_info.name; std::string role_name = templ_name.substr(TaskName.size(), templ_name.size() - TaskName.size() - Ext.size()); @@ -347,7 +348,7 @@ bool asst::BattleImageAnalyzer::kills_flag_analyze() { MatchImageAnalyzer flag_analyzer(m_image); flag_analyzer.set_task_info("BattleKillsFlag"); - return flag_analyzer.analyze(); + return flag_analyzer.analyze().has_value(); } bool asst::BattleImageAnalyzer::kills_analyze() @@ -425,7 +426,7 @@ bool asst::BattleImageAnalyzer::cost_analyze() return false; } - std::string cost_str = cost_analyzer.get_result().front().text; + std::string cost_str = cost_analyzer.get_result().text; if (cost_str.empty() || !ranges::all_of(cost_str, [](const char& c) -> bool { return std::isdigit(c); })) { return false; diff --git a/src/MaaCore/Vision/Battle/BattleImageAnalyzer.h b/src/MaaCore/Vision/Battle/BattleImageAnalyzer.h index 272662a5a8..f8f7d36e0e 100644 --- a/src/MaaCore/Vision/Battle/BattleImageAnalyzer.h +++ b/src/MaaCore/Vision/Battle/BattleImageAnalyzer.h @@ -23,7 +23,7 @@ namespace asst bool set_target(int target); void set_pre_total_kills(int pre_total_kills); - virtual bool analyze() override; + bool analyze(); virtual const std::vector& get_opers() const noexcept; virtual const std::vector& get_homes() const noexcept; diff --git a/src/MaaCore/Vision/Battle/BattleOperatorsImageAnalyzer.h b/src/MaaCore/Vision/Battle/BattleOperatorsImageAnalyzer.h index 88f3ad3414..194da0c381 100644 --- a/src/MaaCore/Vision/Battle/BattleOperatorsImageAnalyzer.h +++ b/src/MaaCore/Vision/Battle/BattleOperatorsImageAnalyzer.h @@ -23,7 +23,7 @@ namespace asst using OnnxRuntimeImageAnalyzer::OnnxRuntimeImageAnalyzer; virtual ~BattleOperatorsImageAnalyzer() override = default; - virtual bool analyze() override; + bool analyze(); const std::vector& get_results() const { return m_results; } #ifdef ASST_DEBUG diff --git a/src/MaaCore/Vision/Battle/BattleSkillReadyImageAnalyzer.h b/src/MaaCore/Vision/Battle/BattleSkillReadyImageAnalyzer.h index 17dc058fd9..5855e5ca80 100644 --- a/src/MaaCore/Vision/Battle/BattleSkillReadyImageAnalyzer.h +++ b/src/MaaCore/Vision/Battle/BattleSkillReadyImageAnalyzer.h @@ -10,7 +10,7 @@ namespace asst using OnnxRuntimeImageAnalyzer::OnnxRuntimeImageAnalyzer; virtual ~BattleSkillReadyImageAnalyzer() override = default; - virtual bool analyze() override; + bool analyze(); void set_base_point(const Point& pt); }; diff --git a/src/MaaCore/Vision/BestMatchImageAnalyzer.cpp b/src/MaaCore/Vision/BestMatchImageAnalyzer.cpp index 05d20560fe..1bbc046f83 100644 --- a/src/MaaCore/Vision/BestMatchImageAnalyzer.cpp +++ b/src/MaaCore/Vision/BestMatchImageAnalyzer.cpp @@ -4,48 +4,54 @@ #include "Config/TaskData.h" #include "Config/TemplResource.h" +#include "MatchImageAnalyzer.h" #include "Utils/Logger.hpp" #include "Utils/StringMisc.hpp" -bool asst::BestMatchImageAnalyzer::analyze() -{ -#ifndef ASST_DEBUG - MatchImageAnalyzer::set_log_tracing(false); -#endif - set_use_cache(false); +using namespace asst; - MatchRect best_matched; - for (const auto& templ_info : m_templs) { - auto&& [name, templ] = templ_info; - if (templ.empty()) { - set_templ_name(name); - } - else { - set_templ(templ); - } - - if (!MatchImageAnalyzer::analyze()) { - continue; - } - const auto& cur_matched = MatchImageAnalyzer::get_result(); - if (best_matched.score < cur_matched.score) { - best_matched = cur_matched; - m_result = templ_info; - } - } - - if (m_best_log_tracing) { - Log.trace("The best match is", best_matched.to_string(), m_result.name); - } - return best_matched.score > 0; -} - -void asst::BestMatchImageAnalyzer::set_log_tracing(bool enable) -{ - m_best_log_tracing = enable; -} - -void asst::BestMatchImageAnalyzer::append_templ(std::string name, const cv::Mat& templ) +void BestMatchImageAnalyzer::append_templ(std::string name, const cv::Mat& templ) { m_templs.emplace_back(TemplInfo { std::move(name), templ }); } + +BestMatchImageAnalyzer::ResultOpt BestMatchImageAnalyzer::analyze() const +{ + MatchImageAnalyzer match_analyzer(m_image, m_roi); + match_analyzer.set_params(m_params); +#ifdef ASST_DEBUG + match_analyzer.set_log_tracing(m_log_tracing); +#else + match_analyzer.set_log_tracing(false); +#endif + + Result result; + for (const auto& templ_info : m_templs) { + auto&& [name, templ] = templ_info; + + if (templ.empty()) { + match_analyzer.set_templ(name); + } + else { + match_analyzer.set_templ(templ); + } + + const auto& cur_opt = match_analyzer.analyze(); + if (!cur_opt) { + continue; + } + const auto& cur_matched = cur_opt.value(); + if (result.score < cur_matched.score) { + result = Result { .rect = cur_matched.rect, .score = cur_matched.score, .templ_info = templ_info }; + } + } + + if (!result.score) { + return std::nullopt; + } + + if (m_log_tracing) { + Log.trace("The best match is", result.to_string(), result.templ_info.name); + } + return result; +} diff --git a/src/MaaCore/Vision/BestMatchImageAnalyzer.h b/src/MaaCore/Vision/BestMatchImageAnalyzer.h index 8480ea7627..a4d1c7d5ca 100644 --- a/src/MaaCore/Vision/BestMatchImageAnalyzer.h +++ b/src/MaaCore/Vision/BestMatchImageAnalyzer.h @@ -1,10 +1,11 @@ #pragma once -#include "MatchImageAnalyzer.h" +#include "AbstractImageAnalyzer.h" +#include "Vision/Config/MatcherConfig.h" namespace asst { - class BestMatchImageAnalyzer : public MatchImageAnalyzer + class BestMatchImageAnalyzer : public AbstractImageAnalyzer, public MatcherConfig { public: struct TemplInfo @@ -13,24 +14,39 @@ namespace asst cv::Mat templ; }; + struct Result + { + std::string to_string() const + { return "{ name: " + templ_info.name + ", matched: " + rect.to_string() + " }"; + } + explicit operator std::string() const { return to_string(); } + + Rect rect; + double score = 0.0; + TemplInfo templ_info; + }; + + using ResultOpt = std::optional; + public: - using MatchImageAnalyzer::MatchImageAnalyzer; + using AbstractImageAnalyzer::AbstractImageAnalyzer; virtual ~BestMatchImageAnalyzer() override = default; - virtual bool analyze() override; - virtual void set_log_tracing(bool enable) override; - void append_templ(std::string name, const cv::Mat& templ = cv::Mat()); - const TemplInfo& get_result() const noexcept { return m_result; } + + ResultOpt analyze() const; + // FIXME: 老接口太难重构了,先弄个这玩意兼容下,后续慢慢全删掉 + const auto& get_result() const { return m_result; } + + protected: + virtual void _set_roi(const Rect& roi) override { set_roi(roi); } private: - using MatchImageAnalyzer::set_region_of_appeared; - using MatchImageAnalyzer::set_templ; - using MatchImageAnalyzer::set_templ_name; - using MatchImageAnalyzer::set_use_cache; + using MatcherConfig::set_templ; std::vector m_templs; - TemplInfo m_result; - bool m_best_log_tracing = true; + // FIXME: 老接口太难重构了,先弄个这玩意兼容下,后续慢慢全删掉 + mutable Result m_result; }; + } diff --git a/src/MaaCore/Vision/Config/MatcherConfig.cpp b/src/MaaCore/Vision/Config/MatcherConfig.cpp new file mode 100644 index 0000000000..dba484e814 --- /dev/null +++ b/src/MaaCore/Vision/Config/MatcherConfig.cpp @@ -0,0 +1,47 @@ +#include "MatcherConfig.h" + +#include "Config/TaskData.h" + +using namespace asst; + +void MatcherConfig::set_params(Params params) +{ + m_params = std::move(params); +} + +void MatcherConfig::set_task_info(const std::shared_ptr& task_ptr) +{ + _set_task_info(*std::dynamic_pointer_cast(task_ptr)); +} + +void MatcherConfig::set_task_info(const std::string& task_name) +{ + set_task_info(Task.get(task_name)); +} + +void MatcherConfig::set_templ(std::variant templ) +{ + m_params.templ = std::move(templ); +} + +void MatcherConfig::set_threshold(double templ_thres) noexcept +{ + m_params.templ_thres = templ_thres; +} + +void MatcherConfig::set_mask_range(int lower, int upper, bool mask_with_src, bool mask_with_close) +{ + m_params.mask_range = std::make_pair(lower, upper); + m_params.mask_with_src = mask_with_src; + m_params.mask_with_close = mask_with_close; +} + +void MatcherConfig::_set_task_info(MatchTaskInfo task_info) +{ + m_params.templ = std::move(task_info.templ_name); + m_params.templ_thres = task_info.templ_threshold; + m_params.mask_range = std::move(task_info.mask_range); + + _set_roi(task_info.roi); +} + diff --git a/src/MaaCore/Vision/Config/MatcherConfig.h b/src/MaaCore/Vision/Config/MatcherConfig.h new file mode 100644 index 0000000000..a5dcd50020 --- /dev/null +++ b/src/MaaCore/Vision/Config/MatcherConfig.h @@ -0,0 +1,45 @@ +#pragma once + +#include "Common/AsstTypes.h" +#include "Utils/NoWarningCVMat.h" + +#include + +namespace asst +{ + + class MatcherConfig + { + public: + struct Params + { + std::variant templ; + double templ_thres = 0.0; + std::pair mask_range; + bool mask_with_src = false; + bool mask_with_close = false; + }; + + public: + MatcherConfig() = default; + virtual ~MatcherConfig() = default; + + void set_params(Params params); + + void set_task_info(const std::shared_ptr& task_ptr); + void set_task_info(const std::string& task_name); + + void set_templ(std::variant templ); + void set_threshold(double templ_thres) noexcept; + void set_mask_range(int lower, int upper, bool mask_with_src = false, bool mask_with_close = false); + + protected: + virtual void _set_roi(const Rect& roi) = 0; + + void _set_task_info(MatchTaskInfo task_info); + + protected: + Params m_params; + }; + +} diff --git a/src/MaaCore/Vision/Config/OCRerConfig.cpp b/src/MaaCore/Vision/Config/OCRerConfig.cpp new file mode 100644 index 0000000000..31c4928f5a --- /dev/null +++ b/src/MaaCore/Vision/Config/OCRerConfig.cpp @@ -0,0 +1,68 @@ +#include "OCRerConfig.h" + +#include "Config/Miscellaneous/OcrConfig.h" +#include "Config/TaskData.h" + +using namespace asst; + +void OCRerConfig::set_params(Params params) +{ + m_params = std::move(params); +} + +void OCRerConfig::set_required(std::vector required) noexcept +{ + ranges::transform(required, required.begin(), + [](const std::string& str) { return OcrConfig::get_instance().process_equivalence_class(str); }); + m_params.required = std::move(required); +} + +void OCRerConfig::set_replace(const std::unordered_map& replace, bool replace_full) noexcept +{ + m_params.replace.clear(); + m_params.replace.reserve(replace.size()); + + for (auto&& [key, val] : replace) { + auto new_key = OcrConfig::get_instance().process_equivalence_class(key); + // do not create new_val as val is user-provided, and can avoid issues like 夕 and katakana タ + m_params.replace.emplace(std::move(new_key), val); + } + m_params.replace_full = replace_full; +} + +void OCRerConfig::set_task_info(std::shared_ptr task_ptr) +{ + _set_task_info(*std::dynamic_pointer_cast(task_ptr)); +} + +void OCRerConfig::set_task_info(const std::string& task_name) +{ + set_task_info(Task.get(task_name)); +} + +void OCRerConfig::set_use_char_model(bool enable) noexcept +{ + m_params.use_char_model = enable; +} + +void OCRerConfig::set_bin_threshold(int lower, int upper) +{ + m_params.bin_threshold_lower = lower; + m_params.bin_threshold_upper = upper; +} + +void OCRerConfig::set_bin_expansion(int expansion) +{ + m_params.bin_expansion = expansion; +} + +void OCRerConfig::_set_task_info(OcrTaskInfo task_info) +{ + set_required(std::move(task_info.text)); + m_params.full_match = task_info.full_match; + set_replace(task_info.replace_map, task_info.replace_full); + m_params.use_char_model = task_info.is_ascii; + m_params.without_det = task_info.without_det; + + _set_roi(task_info.roi); +} diff --git a/src/MaaCore/Vision/Config/OCRerConfig.h b/src/MaaCore/Vision/Config/OCRerConfig.h new file mode 100644 index 0000000000..58bbac26a3 --- /dev/null +++ b/src/MaaCore/Vision/Config/OCRerConfig.h @@ -0,0 +1,53 @@ +#pragma once + +#include "Common/AsstTypes.h" +#include "Utils/NoWarningCVMat.h" + +#include + +namespace asst +{ + class OCRerConfig + { + public: + struct Params + { + std::vector required; + bool full_match = false; + std::unordered_map replace; + bool replace_full = false; + bool without_det = false; + bool use_char_model = false; + + int bin_threshold_lower = 140; + int bin_threshold_upper = 255; + int bin_expansion = 2; + }; + + public: + OCRerConfig() = default; + virtual ~OCRerConfig() = default; + + void set_params(Params params); + + void set_required(std::vector required) noexcept; + void set_replace(const std::unordered_map& replace, + bool replace_full = false) noexcept; + + virtual void set_task_info(std::shared_ptr task_ptr); + virtual void set_task_info(const std::string& task_name); + + void set_use_char_model(bool enable) noexcept; + + void set_bin_threshold(int lower, int upper = 255); + void set_bin_expansion(int expansion); + + protected: + virtual void _set_roi(const Rect& roi) = 0; + virtual void _set_task_info(OcrTaskInfo task_info); + + protected: + Params m_params; + }; + +} diff --git a/src/MaaCore/Vision/HashImageAnalyzer.h b/src/MaaCore/Vision/HashImageAnalyzer.h index ad0b730175..4a424c00f3 100644 --- a/src/MaaCore/Vision/HashImageAnalyzer.h +++ b/src/MaaCore/Vision/HashImageAnalyzer.h @@ -5,13 +5,14 @@ namespace asst { + // FIXME: 删掉这个类,以及对应的 task 类型 class HashImageAnalyzer : public AbstractImageAnalyzer { public: using AbstractImageAnalyzer::AbstractImageAnalyzer; virtual ~HashImageAnalyzer() override = default; - virtual bool analyze() override; + bool analyze(); void set_mask_range(int lower, int upper) noexcept; void set_mask_range(std::pair mask_range) noexcept; diff --git a/src/MaaCore/Vision/Infrast/InfrastClueImageAnalyzer.cpp b/src/MaaCore/Vision/Infrast/InfrastClueImageAnalyzer.cpp deleted file mode 100644 index b7bbd7bf19..0000000000 --- a/src/MaaCore/Vision/Infrast/InfrastClueImageAnalyzer.cpp +++ /dev/null @@ -1,36 +0,0 @@ -#include "InfrastClueImageAnalyzer.h" - -#include "Vision/MultiMatchImageAnalyzer.h" - -bool asst::InfrastClueImageAnalyzer::analyze() -{ - clue_detect(); - if (m_need_detailed) { - clue_analyze(); - } - - return !m_result.empty(); -} - -bool asst::InfrastClueImageAnalyzer::clue_detect() -{ - MultiMatchImageAnalyzer clue_analyzer(m_image); - clue_analyzer.set_task_info("InfrastClue"); - - clue_analyzer.set_roi(m_roi); // 该任务以外部设置的roi为准 - if (!clue_analyzer.analyze()) { - return false; - } - clue_analyzer.sort_result_horizontal(); - for (const auto& res : clue_analyzer.get_result()) { - m_result.emplace_back(res.rect, std::string()); - } - - return !m_result.empty(); -} - -bool asst::InfrastClueImageAnalyzer::clue_analyze() -{ - // todo - return false; -} diff --git a/src/MaaCore/Vision/Infrast/InfrastClueImageAnalyzer.h b/src/MaaCore/Vision/Infrast/InfrastClueImageAnalyzer.h deleted file mode 100644 index 7f3f246a9e..0000000000 --- a/src/MaaCore/Vision/Infrast/InfrastClueImageAnalyzer.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once -#include "Vision/AbstractImageAnalyzer.h" - -namespace asst -{ - class InfrastClueImageAnalyzer : public AbstractImageAnalyzer - { - public: - using AbstractImageAnalyzer::AbstractImageAnalyzer; - virtual ~InfrastClueImageAnalyzer() override = default; - - virtual bool analyze() override; - - const std::vector>& get_result() const noexcept { return m_result; } - - protected: - bool clue_detect(); - bool clue_analyze(); - - bool m_need_detailed = false; // 是否需要详细分析(线索号);false时只检测,不识别 - std::vector> m_result; - }; -} diff --git a/src/MaaCore/Vision/Infrast/InfrastClueVacancyImageAnalyzer.h b/src/MaaCore/Vision/Infrast/InfrastClueVacancyImageAnalyzer.h index 2b9fef44db..7024447ed7 100644 --- a/src/MaaCore/Vision/Infrast/InfrastClueVacancyImageAnalyzer.h +++ b/src/MaaCore/Vision/Infrast/InfrastClueVacancyImageAnalyzer.h @@ -10,7 +10,7 @@ namespace asst virtual ~InfrastClueVacancyImageAnalyzer() override = default; InfrastClueVacancyImageAnalyzer(const cv::Mat& image, const Rect& roi) = delete; - virtual bool analyze() override; + bool analyze(); static constexpr int MaxNumOfClue = 7; void set_to_be_analyzed(std::vector to_be_analyzed) noexcept diff --git a/src/MaaCore/Vision/Infrast/InfrastFacilityImageAnalyzer.cpp b/src/MaaCore/Vision/Infrast/InfrastFacilityImageAnalyzer.cpp index a974dc30a2..a16d58a37b 100644 --- a/src/MaaCore/Vision/Infrast/InfrastFacilityImageAnalyzer.cpp +++ b/src/MaaCore/Vision/Infrast/InfrastFacilityImageAnalyzer.cpp @@ -21,7 +21,7 @@ bool asst::InfrastFacilityImageAnalyzer::analyze() auto task_analyze = [&](const std::string& task_name) -> bool { mm_analyzer.set_task_info(task_name); - return mm_analyzer.analyze(); + return mm_analyzer.analyze().has_value(); }; int cor_suffix_index = -1; @@ -40,7 +40,6 @@ bool asst::InfrastFacilityImageAnalyzer::analyze() if (!task_analyze(task_name + task_name_suffix.at(cor_suffix_index))) { continue; } - mm_analyzer.sort_result_horizontal(); cur_facility_result = mm_analyzer.get_result(); } else { @@ -56,7 +55,6 @@ bool asst::InfrastFacilityImageAnalyzer::analyze() continue; } if (double cur_score = cur_max_iter->score; max_score < cur_score) { - mm_analyzer.sort_result_horizontal(); max_score = cur_score; cur_facility_result = cur_res; cor_suffix_index = static_cast(i); @@ -67,6 +65,8 @@ bool asst::InfrastFacilityImageAnalyzer::analyze() continue; } + sort_by_horizontal_(cur_facility_result); + #ifdef ASST_DEBUG cv::RNG rng(time(0)); cv::Scalar rand_color(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)); diff --git a/src/MaaCore/Vision/Infrast/InfrastFacilityImageAnalyzer.h b/src/MaaCore/Vision/Infrast/InfrastFacilityImageAnalyzer.h index 45211e8094..64077e0bc9 100644 --- a/src/MaaCore/Vision/Infrast/InfrastFacilityImageAnalyzer.h +++ b/src/MaaCore/Vision/Infrast/InfrastFacilityImageAnalyzer.h @@ -10,7 +10,7 @@ namespace asst virtual ~InfrastFacilityImageAnalyzer() override = default; InfrastFacilityImageAnalyzer(const cv::Mat& image, const Rect& roi) = delete; - virtual bool analyze() override; + bool analyze(); void set_to_be_analyzed(std::vector facilities) noexcept { diff --git a/src/MaaCore/Vision/Infrast/InfrastOperImageAnalyzer.cpp b/src/MaaCore/Vision/Infrast/InfrastOperImageAnalyzer.cpp index 8fc2fb736e..1ff2e45c08 100644 --- a/src/MaaCore/Vision/Infrast/InfrastOperImageAnalyzer.cpp +++ b/src/MaaCore/Vision/Infrast/InfrastOperImageAnalyzer.cpp @@ -228,7 +228,7 @@ void asst::InfrastOperImageAnalyzer::skill_analyze() MatchImageAnalyzer skill_analyzer(m_image); - skill_analyzer.set_mask_range(task_ptr->mask_range); + skill_analyzer.set_mask_range(task_ptr->mask_range.first, task_ptr->mask_range.second); skill_analyzer.set_threshold(task_ptr->templ_threshold); for (auto&& oper : m_result) { @@ -274,7 +274,7 @@ void asst::InfrastOperImageAnalyzer::skill_analyze() std::vector> possible_skills; // 逐个该设施内所有可能的技能,取得分最高的 for (const auto& skill : InfrastData.get_skills(m_facility) | views::values) { - skill_analyzer.set_templ_name(skill.templ_name); + skill_analyzer.set_templ(skill.templ_name); if (!skill_analyzer.analyze()) { continue; diff --git a/src/MaaCore/Vision/Infrast/InfrastOperImageAnalyzer.h b/src/MaaCore/Vision/Infrast/InfrastOperImageAnalyzer.h index bbae9ab7cc..a2da96bfac 100644 --- a/src/MaaCore/Vision/Infrast/InfrastOperImageAnalyzer.h +++ b/src/MaaCore/Vision/Infrast/InfrastOperImageAnalyzer.h @@ -25,7 +25,7 @@ namespace asst virtual ~InfrastOperImageAnalyzer() override = default; InfrastOperImageAnalyzer(const cv::Mat& image, const Rect& roi) = delete; - virtual bool analyze() override; + bool analyze(); void sort_by_loc(); void sort_by_mood(); diff --git a/src/MaaCore/Vision/Infrast/InfrastSmileyImageAnalyzer.h b/src/MaaCore/Vision/Infrast/InfrastSmileyImageAnalyzer.h index 6169334360..c40fef4dd8 100644 --- a/src/MaaCore/Vision/Infrast/InfrastSmileyImageAnalyzer.h +++ b/src/MaaCore/Vision/Infrast/InfrastSmileyImageAnalyzer.h @@ -11,7 +11,7 @@ namespace asst using AbstractImageAnalyzer::AbstractImageAnalyzer; virtual ~InfrastSmileyImageAnalyzer() override = default; - virtual bool analyze() override; + bool analyze(); auto get_result() const noexcept -> const std::vector& { return m_result; } diff --git a/src/MaaCore/Vision/MatchImageAnalyzer.cpp b/src/MaaCore/Vision/MatchImageAnalyzer.cpp index ac738fbc93..c13e668a64 100644 --- a/src/MaaCore/Vision/MatchImageAnalyzer.cpp +++ b/src/MaaCore/Vision/MatchImageAnalyzer.cpp @@ -7,123 +7,16 @@ #include "Utils/Logger.hpp" #include "Utils/StringMisc.hpp" -asst::MatchImageAnalyzer::MatchImageAnalyzer(const cv::Mat& image, std::string templ_name, double templ_thres) - : AbstractImageAnalyzer(image), m_templ_name(std::move(templ_name)), m_templ_thres(templ_thres) -{} +using namespace asst; -bool asst::MatchImageAnalyzer::analyze() +MatchImageAnalyzer::ResultOpt MatchImageAnalyzer::analyze() const { - const cv::Mat templ = m_templ_name.empty() ? m_templ : TemplResource::get_instance().get_templ(m_templ_name); - if (templ.empty()) { - Log.error("templ is empty!", m_templ_name); -#ifdef ASST_DEBUG - throw std::runtime_error("templ is empty: " + m_templ_name); -#endif - return false; - } - return match_templ(templ); -} + const auto& [matched, templ, templ_name] = preproc_and_match(make_roi(m_image, m_roi), m_params); -void asst::MatchImageAnalyzer::set_use_cache(bool is_use) noexcept -{ - m_use_cache = is_use; -} - -void asst::MatchImageAnalyzer::set_mask_range(int lower, int upper, bool mask_with_src) noexcept -{ - m_mask_range = std::make_pair(lower, upper); - m_mask_with_src = mask_with_src; -} - -void asst::MatchImageAnalyzer::set_mask_range(std::pair mask_range, bool mask_with_src) noexcept -{ - m_mask_range = std::move(mask_range); - m_mask_with_src = mask_with_src; -} - -void asst::MatchImageAnalyzer::set_templ_name(std::string templ_name) noexcept -{ - m_templ_name = std::move(templ_name); - m_templ = cv::Mat(); -} - -void asst::MatchImageAnalyzer::set_templ(cv::Mat templ) noexcept -{ - m_templ = std::move(templ); - m_templ_name.clear(); -} - -void asst::MatchImageAnalyzer::set_threshold(double templ_thres) noexcept -{ - m_templ_thres = templ_thres; -} - -void asst::MatchImageAnalyzer::set_task_info(const std::shared_ptr& task_ptr) -{ - set_task_info(*std::dynamic_pointer_cast(task_ptr)); -} - -void asst::MatchImageAnalyzer::set_task_info(const std::string& task_name) -{ - set_task_info(Task.get(task_name)); -} - -void asst::MatchImageAnalyzer::set_region_of_appeared(Rect region) noexcept -{ - m_region_of_appeared = region; - if (m_use_cache && !m_region_of_appeared.empty()) { - m_roi = m_region_of_appeared; - } -} - -void asst::MatchImageAnalyzer::set_mask_with_close(int with_close) noexcept -{ - m_mask_with_close = with_close; -} - -const asst::MatchRect& asst::MatchImageAnalyzer::get_result() const noexcept -{ - return m_result; -} - -void asst::MatchImageAnalyzer::set_task_info(MatchTaskInfo task_info) noexcept -{ - m_mask_range = std::move(task_info.mask_range); - m_templ_name = std::move(task_info.templ_name); - m_templ_thres = task_info.templ_threshold; - m_use_cache = task_info.cache; - - if (m_use_cache && !m_region_of_appeared.empty()) { - m_roi = m_region_of_appeared; - } - else { - set_roi(task_info.roi); - } -} -bool asst::MatchImageAnalyzer::match_templ(const cv::Mat templ) -{ - m_roi = correct_rect(m_roi, m_image); - cv::Mat image_roi = m_image(make_rect(m_roi)); - if (templ.cols > image_roi.cols || templ.rows > image_roi.rows) { - Log.error("templ size is too large", m_templ_name, "image_roi size:", image_roi.cols, image_roi.rows, - "templ size:", templ.cols, templ.rows); - return false; + if (matched.empty()) { + return std::nullopt; } - cv::Mat matched; - if (m_mask_range.first == 0 && m_mask_range.second == 0) { - cv::matchTemplate(image_roi, templ, matched, cv::TM_CCOEFF_NORMED); - } - else { - cv::Mat mask; - cv::cvtColor(m_mask_with_src ? image_roi : templ, mask, cv::COLOR_BGR2GRAY); - cv::inRange(mask, m_mask_range.first, m_mask_range.second, mask); - if (m_mask_with_close) { - cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3)); - cv::morphologyEx(mask, mask, cv::MORPH_CLOSE, kernel); - } - cv::matchTemplate(image_roi, templ, matched, cv::TM_CCOEFF_NORMED, mask); - } double min_val = 0.0, max_val = 0.0; cv::Point min_loc, max_loc; cv::minMaxLoc(matched, &min_val, &max_val, &min_loc, &max_loc); @@ -132,15 +25,66 @@ bool asst::MatchImageAnalyzer::match_templ(const cv::Mat templ) if (max_val > 2.0) { max_val = 0; } - if (m_log_tracing && max_val > m_templ_thres * 0.7) { // 得分太低的肯定不对,没必要打印 - Log.trace("match_templ |", m_templ_name, "score:", max_val, "rect:", rect, "roi:", m_roi); + if (m_log_tracing && max_val > m_params.templ_thres * 0.7) { // 得分太低的肯定不对,没必要打印 + Log.trace("match_templ |", templ_name, "score:", max_val, "rect:", rect, "roi:", m_roi); } - if (m_templ_thres <= max_val && max_val < 2.0) { - m_result = { max_val, rect }; - return true; + if (max_val < m_params.templ_thres) { + return std::nullopt; + } + + // FIXME: 老接口太难重构了,先弄个这玩意兼容下,后续慢慢全删掉 + m_result.rect = rect; + m_result.score = max_val; + return m_result; +} + +MatchImageAnalyzer::RawResult MatchImageAnalyzer::preproc_and_match(const cv::Mat& image, const MatcherConfig::Params& params) +{ + cv::Mat templ; + std::string templ_name; + + if (std::holds_alternative(params.templ)) { + templ_name = std::get(params.templ); + templ = TemplResource::get_instance().get_templ(templ_name); + } + else if (std::holds_alternative(params.templ)) { + templ = std::get(params.templ); } else { - return false; + Log.error("templ is none"); } + + if (templ.empty()) { + Log.error("templ is empty!", templ_name); +#ifdef ASST_DEBUG + throw std::runtime_error("templ is empty: " + templ_name); +#else + return {}; +#endif + } + + if (templ.cols > image.cols || templ.rows > image.rows) { + Log.error("templ size is too large", templ_name, "image size:", image.cols, image.rows, + "templ size:", templ.cols, templ.rows); + return {}; + } + + cv::Mat matched; + if (params.mask_range.first == 0 && params.mask_range.second == 0) { + cv::matchTemplate(image, templ, matched, cv::TM_CCOEFF_NORMED); + } + else { + cv::Mat mask; + cv::cvtColor(params.mask_with_src ? image : templ, mask, cv::COLOR_BGR2GRAY); + cv::inRange(mask, params.mask_range.first, params.mask_range.second, mask); + if (params.mask_with_close) { + cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3)); + cv::morphologyEx(mask, mask, cv::MORPH_CLOSE, kernel); + } + cv::matchTemplate(image, templ, matched, cv::TM_CCOEFF_NORMED, mask); + } + + return RawResult { .matched = matched, .templ = templ, .templ_name = templ_name }; } + diff --git a/src/MaaCore/Vision/MatchImageAnalyzer.h b/src/MaaCore/Vision/MatchImageAnalyzer.h index d35db08937..8ad8a8fcd4 100644 --- a/src/MaaCore/Vision/MatchImageAnalyzer.h +++ b/src/MaaCore/Vision/MatchImageAnalyzer.h @@ -1,42 +1,39 @@ #pragma once #include "AbstractImageAnalyzer.h" -namespace asst -{ - class MatchImageAnalyzer : public AbstractImageAnalyzer +#include "Vision/Config/MatcherConfig.h" + +namespace asst { + +class MatchImageAnalyzer : public AbstractImageAnalyzer, public MatcherConfig { - public: - using AbstractImageAnalyzer::AbstractImageAnalyzer; - MatchImageAnalyzer(const cv::Mat& image, std::string templ_name, double templ_thres = 0.0); - virtual ~MatchImageAnalyzer() override = default; +public: + using Result = MatchRect; + using ResultOpt = std::optional; - virtual bool analyze() override; +public: + using AbstractImageAnalyzer::AbstractImageAnalyzer; + virtual ~MatchImageAnalyzer() override = default; - void set_use_cache(bool is_use) noexcept; - void set_mask_range(int lower, int upper, bool mask_with_src = false) noexcept; - void set_mask_range(std::pair mask_range, bool mask_with_src = false) noexcept; - void set_templ_name(std::string templ_name) noexcept; - void set_templ(cv::Mat templ) noexcept; - void set_threshold(double templ_thres) noexcept; - void set_task_info(const std::shared_ptr& task_ptr); - void set_task_info(const std::string& task_name); - void set_region_of_appeared(Rect region) noexcept; - void set_mask_with_close(int with_close) noexcept; + ResultOpt analyze() const; + // FIXME: 老接口太难重构了,先弄个这玩意兼容下,后续慢慢全删掉 + const auto& get_result() const noexcept { return m_result; } - const MatchRect& get_result() const noexcept; - - protected: - virtual bool match_templ(const cv::Mat templ); - void set_task_info(MatchTaskInfo task_info) noexcept; - - std::string m_templ_name; - cv::Mat m_templ; - MatchRect m_result; - double m_templ_thres = 0.0; - bool m_use_cache = false; - Rect m_region_of_appeared; - std::pair m_mask_range; - bool m_mask_with_src = false; - bool m_mask_with_close = false; +public: + struct RawResult + { + cv::Mat matched; + cv::Mat templ; + std::string templ_name; }; + static RawResult preproc_and_match(const cv::Mat& image, const MatcherConfig::Params& params); + +protected: + virtual void _set_roi(const Rect& roi) override { set_roi(roi); } + +private: + // FIXME: 老接口太难重构了,先弄个这玩意兼容下,后续慢慢全删掉 + mutable Result m_result; +}; + } diff --git a/src/MaaCore/Vision/Miscellaneous/CreditShopImageAnalyzer.cpp b/src/MaaCore/Vision/Miscellaneous/CreditShopImageAnalyzer.cpp index 291649fb1c..ee2445a7a1 100644 --- a/src/MaaCore/Vision/Miscellaneous/CreditShopImageAnalyzer.cpp +++ b/src/MaaCore/Vision/Miscellaneous/CreditShopImageAnalyzer.cpp @@ -45,12 +45,13 @@ bool asst::CreditShopImageAnalyzer::commodities_analyze() if (!mm_analyzer.analyze()) { return false; } - mm_analyzer.sort_result_horizontal(); auto credit_points_result = mm_analyzer.get_result(); if (credit_points_result.empty()) { return false; } + sort_by_horizontal_(credit_points_result); + m_commodities.reserve(credit_points_result.size()); for (const MatchRect& mr : credit_points_result) { Rect commodity; diff --git a/src/MaaCore/Vision/Miscellaneous/CreditShopImageAnalyzer.h b/src/MaaCore/Vision/Miscellaneous/CreditShopImageAnalyzer.h index 7e337f615b..cd622345a6 100644 --- a/src/MaaCore/Vision/Miscellaneous/CreditShopImageAnalyzer.h +++ b/src/MaaCore/Vision/Miscellaneous/CreditShopImageAnalyzer.h @@ -9,7 +9,7 @@ namespace asst using AbstractImageAnalyzer::AbstractImageAnalyzer; virtual ~CreditShopImageAnalyzer() override = default; - virtual bool analyze() override; + bool analyze(); void set_black_list(std::vector black_list); void set_white_list(std::vector white_list); diff --git a/src/MaaCore/Vision/Miscellaneous/DepotImageAnalyzer.cpp b/src/MaaCore/Vision/Miscellaneous/DepotImageAnalyzer.cpp index 53b66fdf0d..7f5bf235bc 100644 --- a/src/MaaCore/Vision/Miscellaneous/DepotImageAnalyzer.cpp +++ b/src/MaaCore/Vision/Miscellaneous/DepotImageAnalyzer.cpp @@ -186,7 +186,7 @@ size_t asst::DepotImageAnalyzer::match_item(const Rect& roi, /* out */ ItemInfo& size_t matched_index = NPos; for (size_t index = begin_index, extra_count = 0; index < all_items.size(); ++index) { const std::string& item_id = all_items.at(index); - // analyzer.set_templ_name(item_id); + // analyzer.set_templ(item_id); // TODO: too slow? find a way to set mask directly cv::Mat templ = TemplResource::get_instance().get_templ(item_id).clone(); templ(cv::Rect { templ.cols - 80, templ.rows - 50, 80, 50 }) = cv::Scalar { 0, 0, 0 }; @@ -256,13 +256,13 @@ int asst::DepotImageAnalyzer::match_quantity(const ItemInfo& item) OcrWithPreprocessImageAnalyzer analyzer(m_image_resized); analyzer.set_task_info("NumberOcrReplace"); analyzer.set_roi(ocr_roi); - analyzer.set_threshold(task_ptr->mask_range.first, task_ptr->mask_range.second); + analyzer.set_bin_threshold(task_ptr->mask_range.first, task_ptr->mask_range.second); if (!analyzer.analyze()) { return 0; } - const auto& result = analyzer.get_result().front(); + const auto& result = analyzer.get_result(); #ifdef ASST_DEBUG cv::rectangle(m_image_draw_resized, make_rect(result.rect), cv::Scalar(0, 0, 255)); diff --git a/src/MaaCore/Vision/Miscellaneous/DepotImageAnalyzer.h b/src/MaaCore/Vision/Miscellaneous/DepotImageAnalyzer.h index 071dfb1889..4b72b547cb 100644 --- a/src/MaaCore/Vision/Miscellaneous/DepotImageAnalyzer.h +++ b/src/MaaCore/Vision/Miscellaneous/DepotImageAnalyzer.h @@ -19,7 +19,7 @@ namespace asst using AbstractImageAnalyzer::AbstractImageAnalyzer; virtual ~DepotImageAnalyzer() override = default; - virtual bool analyze() override; + bool analyze(); void set_match_begin_pos(size_t pos) noexcept; size_t get_match_begin_pos() const noexcept; diff --git a/src/MaaCore/Vision/Miscellaneous/OperBoxImageAnalyzer.cpp b/src/MaaCore/Vision/Miscellaneous/OperBoxImageAnalyzer.cpp index 392eff63a4..0bccad58a5 100644 --- a/src/MaaCore/Vision/Miscellaneous/OperBoxImageAnalyzer.cpp +++ b/src/MaaCore/Vision/Miscellaneous/OperBoxImageAnalyzer.cpp @@ -9,6 +9,7 @@ #include "Vision/MatchImageAnalyzer.h" #include "Vision/MultiMatchImageAnalyzer.h" #include "Vision/OcrWithFlagTemplImageAnalyzer.h" +#include "Vision/OcrWithPreprocessImageAnalyzer.h" bool asst::OperBoxImageAnalyzer::analyze() { @@ -51,8 +52,6 @@ bool asst::OperBoxImageAnalyzer::analyzer_oper_box() return false; } - sort_(); - return !m_result.empty(); } @@ -69,15 +68,12 @@ bool asst::OperBoxImageAnalyzer::opers_analyze() if (!oper_name_analyzer.analyze()) { return false; } + auto result_list = oper_name_analyzer.get_result(); + sort_by_horizontal_(result_list); - size_t size = oper_name_analyzer.get_flag_result().size(); - const auto& flag_list = oper_name_analyzer.get_flag_result(); - const auto& name_list = oper_name_analyzer.get_result(); - for (size_t i = 0; i != size; ++i) { - const auto& flag_rect = flag_list[i]; - const auto& name_tr = name_list[i]; - - const std::string& name = name_tr.text; + for (const auto& oper : result_list) { + const Rect& flag_rect = oper.flag_rect; + const std::string& name = oper.text; OperBoxInfo box; box.id = BattleData.get_id(name); @@ -118,7 +114,7 @@ bool asst::OperBoxImageAnalyzer::level_analyze() box.level = 1; continue; } - const auto& ocr_result = level_analyzer.get_result().front(); + const auto& ocr_result = level_analyzer.get_result(); const std::string& level = ocr_result.text; box.level = level_num(level); #ifdef ASST_DEBUG @@ -152,7 +148,7 @@ bool asst::OperBoxImageAnalyzer::elite_analyze() continue; } const auto& elite_templ = elite_analyzer.get_result(); - std::string elite = elite_templ.name.substr(task_name.size(), 1); + std::string elite = elite_templ.templ_info.name.substr(task_name.size(), 1); box.elite = std::stoi(elite); #ifdef ASST_DEBUG cv::rectangle(m_image_draw, make_rect(roi), cv::Scalar(0, 255, 0), 1); @@ -182,7 +178,7 @@ bool asst::OperBoxImageAnalyzer::potential_analyze() continue; } const auto& poten_templ = potential_analyzer.get_result(); - std::string potential = poten_templ.name.substr(task_name_p.size(), 1); + std::string potential = poten_templ.templ_info.name.substr(task_name_p.size(), 1); box.potential = std::stoi(potential); #ifdef ASST_DEBUG cv::rectangle(m_image_draw, make_rect(roi), cv::Scalar(0, 255, 0), 1); @@ -190,16 +186,3 @@ bool asst::OperBoxImageAnalyzer::potential_analyze() } return true; } - -void asst::OperBoxImageAnalyzer::sort_() -{ - // 按位置排个序 - ranges::sort(m_result, [](const OperBoxInfo& lhs, const OperBoxInfo& rhs) -> bool { - if (std::abs(lhs.rect.y - rhs.rect.y) < 5) { // y差距较小则理解为是同一排的,按x排序 - return lhs.rect.x < rhs.rect.x; - } - else { - return lhs.rect.y < rhs.rect.y; - } - }); -} diff --git a/src/MaaCore/Vision/Miscellaneous/OperBoxImageAnalyzer.h b/src/MaaCore/Vision/Miscellaneous/OperBoxImageAnalyzer.h index 8b56bb242c..ee2ff8972c 100644 --- a/src/MaaCore/Vision/Miscellaneous/OperBoxImageAnalyzer.h +++ b/src/MaaCore/Vision/Miscellaneous/OperBoxImageAnalyzer.h @@ -21,7 +21,7 @@ namespace asst public: using AbstractImageAnalyzer::AbstractImageAnalyzer; virtual ~OperBoxImageAnalyzer() override = default; - virtual bool analyze() override; + bool analyze(); const auto& get_result() const noexcept { return m_result; } private: @@ -32,10 +32,6 @@ namespace asst bool level_analyze(); bool elite_analyze(); bool potential_analyze(); - // 按位置排序 - // 1 - 2 - 3 - // 4 - 5 - 6 - void sort_(); std::vector m_result; }; diff --git a/src/MaaCore/Vision/Miscellaneous/ProcessTaskImageAnalyzer.cpp b/src/MaaCore/Vision/Miscellaneous/ProcessTaskImageAnalyzer.cpp index 42861ebbad..6e4e5d3649 100644 --- a/src/MaaCore/Vision/Miscellaneous/ProcessTaskImageAnalyzer.cpp +++ b/src/MaaCore/Vision/Miscellaneous/ProcessTaskImageAnalyzer.cpp @@ -10,157 +10,117 @@ #include "Vision/OcrImageAnalyzer.h" #include "Vision/OcrWithPreprocessImageAnalyzer.h" -asst::ProcessTaskImageAnalyzer::ProcessTaskImageAnalyzer(const cv::Mat& image, std::vector tasks_name, - Assistant* inst) - : AbstractImageAnalyzer(image, inst), m_ocr_analyzer(nullptr), m_ocr_with_preprocess_analyzer(nullptr), - m_match_analyzer(nullptr), m_tasks_name(std::move(tasks_name)) -{} +using namespace asst; -asst::ProcessTaskImageAnalyzer::~ProcessTaskImageAnalyzer() = default; - -bool asst::ProcessTaskImageAnalyzer::match_analyze(const std::shared_ptr& task_ptr) +ProcessTaskImageAnalyzer::ResultOpt ProcessTaskImageAnalyzer::analyze() const { - if (!m_match_analyzer) { - m_match_analyzer = std::make_unique(m_image); - } - const auto match_task_ptr = std::dynamic_pointer_cast(task_ptr); - if (match_task_ptr->templ_threshold > 1.0) { - Log.trace(match_task_ptr->name, "'s threshold is", match_task_ptr->templ_threshold, ", just skip"); - return false; - } - m_match_analyzer->set_region_of_appeared(Rect()); - m_match_analyzer->set_task_info(match_task_ptr); - if (m_inst) { - auto region_opt = status()->get_rect(match_task_ptr->name); - if (region_opt) { - m_match_analyzer->set_region_of_appeared(region_opt.value()); - } - } - - if (m_match_analyzer->analyze()) { - m_result = match_task_ptr; - m_result_rect = m_match_analyzer->get_result().rect; - if (m_inst) { - status()->set_rect(match_task_ptr->name, m_result_rect); - } - return true; - } - return false; -} - -bool asst::ProcessTaskImageAnalyzer::ocr_analyze(const std::shared_ptr& task_ptr) -{ - std::shared_ptr ocr_task_ptr = std::dynamic_pointer_cast(task_ptr); - - // 先尝试从缓存的结果里找 - // for (const TextRect& tr : m_ocr_cache) { - // TextRect temp = tr; - // for (const auto& [regex, new_str] : ocr_task_ptr->replace_map) { - // temp.text = std::regex_replace(temp.text, std::regex(regex), new_str); - // } - // for (const auto& text : ocr_task_ptr->text) { - // bool flag = false; - // if (ocr_task_ptr->full_match) { - // if (temp.text == text) { - // flag = true; - // } - // } - // else if (temp.text.find(text) != std::string::npos) { - // flag = true; - // } - // // 即使找到了,也得在ROI里才行,不然过滤掉 - // if (flag && ocr_task_ptr->roi.include(tr.rect)) { - // m_result = ocr_task_ptr; - // m_result_rect = tr.rect; - // ocr_task_ptr->region_of_appeared = m_result_rect; - // Log.trace("ProcessTaskImageAnalyzer::ocr_analyze | found in cache", tr.to_string()); - // return true; - // } - // } - //} - std::unique_ptr* analyzer_ptr = nullptr; - if (ocr_task_ptr->without_det) { - if (!m_ocr_with_preprocess_analyzer) { - m_ocr_with_preprocess_analyzer = std::make_unique(m_image); - } - analyzer_ptr = decltype(analyzer_ptr)(&m_ocr_with_preprocess_analyzer); - } - else { - if (!m_ocr_analyzer) { - m_ocr_analyzer = std::make_unique(m_image); - } - analyzer_ptr = &m_ocr_analyzer; - } - if (m_inst) { - (*analyzer_ptr)->set_region_of_appeared(status()->get_rect(ocr_task_ptr->name).value_or(Rect())); - } - (*analyzer_ptr)->set_task_info(ocr_task_ptr); - - bool ret = (*analyzer_ptr)->analyze(); - - if (ret) { - (*analyzer_ptr)->sort_result_by_required(); - const auto& ocr_result = (*analyzer_ptr)->get_result(); - auto& res = ocr_result.front(); - m_result = ocr_task_ptr; - m_result_rect = res.rect; - if (m_inst) { - status()->set_rect(ocr_task_ptr->name, m_result_rect); - } - // m_ocr_cache.insert(m_ocr_cache.end(), ocr_result.begin(), ocr_result.end()); - Log.trace(__FUNCTION__, "| found", res); - } - return ret; -} - -void asst::ProcessTaskImageAnalyzer::reset() noexcept -{ - // m_ocr_cache.clear(); - m_ocr_analyzer = nullptr; - m_match_analyzer = nullptr; -} - -bool asst::ProcessTaskImageAnalyzer::analyze() -{ - m_result = nullptr; - m_result_rect = Rect(); - for (const std::string& task_name : m_tasks_name) { - auto task_ptr = Task.get(task_name); + const auto& task_ptr = Task.get(task_name); // 可能有配置错误,导致不存在对应的任务 if (task_ptr == nullptr) { Log.error("Invalid task", task_name); +#ifdef ASST_DEBUG + throw std::runtime_error("Invalid task: " + task_name); +#endif continue; } + Log.trace("Pipeline", task_ptr->name); switch (task_ptr->algorithm) { - case AlgorithmType::JustReturn: - m_result = task_ptr; - return true; + case AlgorithmType::JustReturn: { + return Result { .task_ptr = task_ptr }; + } break; + case AlgorithmType::MatchTemplate: - if (match_analyze(task_ptr)) { - return true; + if (auto match_opt = match(task_ptr)) { + return Result { .task_ptr = task_ptr, .result = *match_opt, .rect = match_opt->rect }; } break; case AlgorithmType::OcrDetect: - if (ocr_analyze(task_ptr)) { - return true; + if (auto ocr_opt = ocr(task_ptr)) { + return Result { .task_ptr = task_ptr, .result = ocr_opt->front(), .rect = ocr_opt->front().rect }; } break; default: break; } } - return false; + return std::nullopt; } -void asst::ProcessTaskImageAnalyzer::set_image(const cv::Mat& image) +MatchImageAnalyzer::ResultOpt ProcessTaskImageAnalyzer::match(const std::shared_ptr& task_ptr) const { - AbstractImageAnalyzer::set_image(image); - reset(); + MatchImageAnalyzer match_analyzer(m_image, m_roi); + + const auto match_task_ptr = std::dynamic_pointer_cast(task_ptr); + if (match_task_ptr->templ_threshold > 1.0) { + Log.info(match_task_ptr->name, "'s threshold is", match_task_ptr->templ_threshold, ", just skip"); + return std::nullopt; + } + match_analyzer.set_task_info(match_task_ptr); + + bool use_cache = m_inst && match_task_ptr->cache; + if (use_cache) { + auto cache_opt = status()->get_rect(match_task_ptr->name); + if (cache_opt) { + match_analyzer.set_roi(*cache_opt); + } + } + + const auto& result_opt = match_analyzer.analyze(); + + if (!result_opt) { + return std::nullopt; + } + if (use_cache) { + status()->set_rect(match_task_ptr->name, result_opt->rect); + } + + return result_opt; } -void asst::ProcessTaskImageAnalyzer::set_tasks(std::vector tasks_name) +OcrImageAnalyzer::ResultsVecOpt ProcessTaskImageAnalyzer::ocr(const std::shared_ptr& task_ptr) const { - m_tasks_name = std::move(tasks_name); + const auto ocr_task_ptr = std::dynamic_pointer_cast(task_ptr); + + bool det = !ocr_task_ptr->without_det; + bool use_cache = m_inst && ocr_task_ptr->cache; + + std::optional cache_opt; + if (use_cache) { + cache_opt = status()->get_rect(ocr_task_ptr->name); + } + + OcrImageAnalyzer::ResultsVec result_vec; + + if (det) { + OcrImageAnalyzer OcrImageAnalyzer(m_image, m_roi); + OcrImageAnalyzer.set_task_info(ocr_task_ptr); + if (use_cache && cache_opt) { + OcrImageAnalyzer.set_roi(*cache_opt); + } + auto result_opt = OcrImageAnalyzer.analyze(); + if (!result_opt) { + return std::nullopt; + } + result_vec = std::move(*result_opt); + } + else { + OcrWithPreprocessImageAnalyzer OcrImageAnalyzer(m_image, m_roi); + OcrImageAnalyzer.set_task_info(ocr_task_ptr); + if (use_cache && cache_opt) { + OcrImageAnalyzer.set_roi(*cache_opt); + } + auto result_opt = OcrImageAnalyzer.analyze(); + if (!result_opt) { + return std::nullopt; + } + result_vec = { std::move(*result_opt) }; + } + + if (use_cache && !result_vec.empty()) { + status()->set_rect(ocr_task_ptr->name, result_vec.front().rect); + } + + return result_vec; } diff --git a/src/MaaCore/Vision/Miscellaneous/ProcessTaskImageAnalyzer.h b/src/MaaCore/Vision/Miscellaneous/ProcessTaskImageAnalyzer.h index e39653b729..bf969c9a4d 100644 --- a/src/MaaCore/Vision/Miscellaneous/ProcessTaskImageAnalyzer.h +++ b/src/MaaCore/Vision/Miscellaneous/ProcessTaskImageAnalyzer.h @@ -7,43 +7,34 @@ #include "Common/AsstTypes.h" +#include "Vision/MatchImageAnalyzer.h" +#include "Vision/OcrImageAnalyzer.h" + namespace asst { - class OcrImageAnalyzer; - class OcrWithPreprocessImageAnalyzer; - class MatchImageAnalyzer; - class Assistant; - - class ProcessTaskImageAnalyzer final : public AbstractImageAnalyzer + class ProcessTaskImageAnalyzer : public AbstractImageAnalyzer { public: - ProcessTaskImageAnalyzer(const cv::Mat& image, std::vector tasks_name, Assistant* inst); - virtual ~ProcessTaskImageAnalyzer() override; + struct Result + { + std::shared_ptr task_ptr; + std::variant result; + Rect rect; + }; + using ResultOpt = std::optional; - virtual bool analyze() override; - virtual void set_image(const cv::Mat& image) override; + public: + using AbstractImageAnalyzer::AbstractImageAnalyzer; + virtual ~ProcessTaskImageAnalyzer() override = default; - void set_tasks(std::vector tasks_name); + void set_tasks(std::vector tasks_name) { m_tasks_name = std::move(tasks_name); } - std::shared_ptr get_result() const noexcept { return m_result; } - const Rect& get_rect() const noexcept { return m_result_rect; } - - ProcessTaskImageAnalyzer& operator=(const ProcessTaskImageAnalyzer&) = delete; - ProcessTaskImageAnalyzer& operator=(ProcessTaskImageAnalyzer&&) = delete; + ResultOpt analyze() const; private: - // 该分析器不支持外部设置ROI - using AbstractImageAnalyzer::set_roi; - bool match_analyze(const std::shared_ptr& task_ptr); - bool ocr_analyze(const std::shared_ptr& task_ptr); - void reset() noexcept; + MatchImageAnalyzer::ResultOpt match(const std::shared_ptr& task_ptr) const; + OcrImageAnalyzer::ResultsVecOpt ocr(const std::shared_ptr& task_ptr) const; - std::unique_ptr m_ocr_analyzer; - std::unique_ptr m_ocr_with_preprocess_analyzer; - std::unique_ptr m_match_analyzer; std::vector m_tasks_name; - std::shared_ptr m_result = nullptr; - Rect m_result_rect; - // std::vector m_ocr_cache; }; } diff --git a/src/MaaCore/Vision/Miscellaneous/RecruitImageAnalyzer.cpp b/src/MaaCore/Vision/Miscellaneous/RecruitImageAnalyzer.cpp index 4ebbc7aa75..66c329500a 100644 --- a/src/MaaCore/Vision/Miscellaneous/RecruitImageAnalyzer.cpp +++ b/src/MaaCore/Vision/Miscellaneous/RecruitImageAnalyzer.cpp @@ -46,11 +46,12 @@ bool asst::RecruitImageAnalyzer::time_analyze() { MultiMatchImageAnalyzer decrement_a(m_image); decrement_a.set_task_info("RecruitTimerDecrement"); - if (!decrement_a.analyze()) return false; - if (decrement_a.get_result().size() != 2) return false; // expecting two buttons - decrement_a.sort_result_horizontal(); - m_hour_decrement = decrement_a.get_result().at(0).rect; - m_minute_decrement = decrement_a.get_result().at(1).rect; + auto result_opt = decrement_a.analyze(); + if (!result_opt) return false; + if (result_opt->size() != 2) return false; // expecting two buttons + sort_by_horizontal_(*result_opt); + m_hour_decrement = result_opt->at(0).rect; + m_minute_decrement = result_opt->at(1).rect; return true; } diff --git a/src/MaaCore/Vision/Miscellaneous/RecruitImageAnalyzer.h b/src/MaaCore/Vision/Miscellaneous/RecruitImageAnalyzer.h index 83b7da6439..09b2e47771 100644 --- a/src/MaaCore/Vision/Miscellaneous/RecruitImageAnalyzer.h +++ b/src/MaaCore/Vision/Miscellaneous/RecruitImageAnalyzer.h @@ -12,7 +12,7 @@ namespace asst RecruitImageAnalyzer(const cv::Mat& image, const Rect& roi) = delete; virtual ~RecruitImageAnalyzer() override = default; - virtual bool analyze() override; + bool analyze(); const std::vector& get_tags_result() const noexcept { return m_tags_result; } Rect get_hour_decrement_rect() const noexcept { return m_hour_decrement; } diff --git a/src/MaaCore/Vision/Miscellaneous/StageDropsImageAnalyzer.cpp b/src/MaaCore/Vision/Miscellaneous/StageDropsImageAnalyzer.cpp index a8df02969e..20174064fe 100644 --- a/src/MaaCore/Vision/Miscellaneous/StageDropsImageAnalyzer.cpp +++ b/src/MaaCore/Vision/Miscellaneous/StageDropsImageAnalyzer.cpp @@ -133,14 +133,14 @@ bool asst::StageDropsImageAnalyzer::analyze_difficulty() #endif }; - bool analyzed = analyzer.analyze(); + auto analyzed = analyzer.analyze(); if (!analyzed) { m_difficulty = StageDifficulty::Normal; log(); return true; } - cv::Mat image_roi = m_image(make_rect(analyzer.get_result().rect)); + cv::Mat image_roi = m_image(make_rect(analyzed->rect)); cv::Mat hsv; cv::cvtColor(image_roi, hsv, cv::COLOR_BGR2HSV); @@ -488,14 +488,14 @@ std::string asst::StageDropsImageAnalyzer::match_item(const Rect& roi, StageDrop auto match_item_with_templs = [&](const std::vector& templs_list) -> std::string { MatchImageAnalyzer analyzer(m_image); + analyzer.set_mask_range(0, 0, false, true); analyzer.set_task_info("StageDrops-Item"); - analyzer.set_mask_with_close(true); analyzer.set_roi(roi); double max_score = 0.0; std::string matched; for (const std::string& templ : templs_list) { - analyzer.set_templ_name(templ); + analyzer.set_templ(templ); if (!analyzer.analyze()) { continue; } @@ -587,14 +587,14 @@ std::optional asst::StageDropsImageAnalyzer::match_quantity_stri OcrWithPreprocessImageAnalyzer analyzer(m_image); analyzer.set_task_info("NumberOcrReplace"); analyzer.set_roi(Rect(quantity_roi.x + far_left, quantity_roi.y, far_right - far_left, quantity_roi.height)); - analyzer.set_threshold(task_ptr->mask_range.first, task_ptr->mask_range.second); + analyzer.set_bin_threshold(task_ptr->mask_range.first, task_ptr->mask_range.second); analyzer.set_use_char_model(!use_word_model); if (!analyzer.analyze()) { return std::nullopt; } - return analyzer.get_result().front(); + return analyzer.get_result(); } std::optional asst::StageDropsImageAnalyzer::match_quantity_string(const asst::Rect& roi, @@ -610,8 +610,7 @@ std::optional asst::StageDropsImageAnalyzer::match_quantity_stri MatchImageAnalyzer analyzer(m_image); analyzer.set_templ(templ); - analyzer.set_mask_range(1, 255); - analyzer.set_mask_with_close(true); + analyzer.set_mask_range(1, 255, false, true); analyzer.set_roi(roi); if (!analyzer.analyze()) { return std::nullopt; @@ -656,13 +655,13 @@ std::optional asst::StageDropsImageAnalyzer::match_quantity_stri Rect ocr_roi { new_roi.x + mask_rect.x, new_roi.y + mask_rect.y, mask_rect.width, mask_rect.height }; ocr.set_roi(ocr_roi); ocr.set_use_char_model(!use_word_model); - ocr.set_threshold(task_ptr->mask_range.first, task_ptr->mask_range.second); + ocr.set_bin_threshold(task_ptr->mask_range.first, task_ptr->mask_range.second); if (!ocr.analyze()) { return std::nullopt; } - return ocr.get_result().front(); + return ocr.get_result(); } int asst::StageDropsImageAnalyzer::quantity_string_to_int(const std::string& str) diff --git a/src/MaaCore/Vision/Miscellaneous/StageDropsImageAnalyzer.h b/src/MaaCore/Vision/Miscellaneous/StageDropsImageAnalyzer.h index 2bad190b99..a21572ef40 100644 --- a/src/MaaCore/Vision/Miscellaneous/StageDropsImageAnalyzer.h +++ b/src/MaaCore/Vision/Miscellaneous/StageDropsImageAnalyzer.h @@ -14,7 +14,7 @@ namespace asst using AbstractImageAnalyzer::AbstractImageAnalyzer; virtual ~StageDropsImageAnalyzer() override = default; - virtual bool analyze() override; + bool analyze(); StageKey get_stage_key() const; int get_stars() const noexcept; diff --git a/src/MaaCore/Vision/MultiMatchImageAnalyzer.cpp b/src/MaaCore/Vision/MultiMatchImageAnalyzer.cpp index 7655079a2a..6a740c3bd6 100644 --- a/src/MaaCore/Vision/MultiMatchImageAnalyzer.cpp +++ b/src/MaaCore/Vision/MultiMatchImageAnalyzer.cpp @@ -8,150 +8,61 @@ #include "Config/TaskData.h" #include "Config/TemplResource.h" #include "Utils/Logger.hpp" +#include "Vision/MatchImageAnalyzer.h" -asst::MultiMatchImageAnalyzer::MultiMatchImageAnalyzer(const cv::Mat& image, std::string templ_name, double templ_thres) - : AbstractImageAnalyzer(image), m_templ_name(std::move(templ_name)), m_templ_thres(templ_thres) -{} +using namespace asst; -bool asst::MultiMatchImageAnalyzer::analyze() +MultiMatchImageAnalyzer::ResultsVecOpt MultiMatchImageAnalyzer::analyze() const { - if (m_log_tracing) { - Log.trace("MultiMatchImageAnalyzer::analyze | ", m_templ_name); - } - m_result.clear(); + const auto& [matched, templ, templ_name] = + MatchImageAnalyzer::preproc_and_match(make_roi(m_image, m_roi), m_params); - const cv::Mat templ = TemplResource::get_instance().get_templ(m_templ_name); - if (templ.empty()) { - Log.error("templ is empty!", m_templ_name); -#ifdef ASST_DEBUG - throw std::runtime_error("templ is empty: " + m_templ_name); -#endif - return false; + if (matched.empty()) { + return std::nullopt; } - return multi_match_templ(templ); -} + std::vector results; -void asst::MultiMatchImageAnalyzer::sort_result_horizontal() -{ - // 按位置排个序 - ranges::sort(m_result, [](const MatchRect& lhs, const MatchRect& rhs) -> bool { - if (std::abs(lhs.rect.y - rhs.rect.y) < 5) { // y差距较小则理解为是同一排的,按x排序 - return lhs.rect.x < rhs.rect.x; - } - else { - return lhs.rect.y < rhs.rect.y; - } - }); -} - -void asst::MultiMatchImageAnalyzer::sort_result_vertical() -{ - // 按位置排个序 - ranges::sort(m_result, [](const MatchRect& lhs, const MatchRect& rhs) -> bool { - if (std::abs(lhs.rect.x - rhs.rect.x) < 5) { // x差距较小则理解为是同一排的,按y排序 - return lhs.rect.y < rhs.rect.y; - } - else { - return lhs.rect.x < rhs.rect.x; - } - }); -} - -void asst::MultiMatchImageAnalyzer::set_mask_range(int lower, int upper) noexcept -{ - m_mask_range = std::make_pair(lower, upper); -} - -void asst::MultiMatchImageAnalyzer::set_mask_range(std::pair mask_range) noexcept -{ - m_mask_range = std::move(mask_range); -} - -void asst::MultiMatchImageAnalyzer::set_templ_name(std::string templ_name) noexcept -{ - m_templ_name = std::move(templ_name); -} - -void asst::MultiMatchImageAnalyzer::set_threshold(double templ_thres) noexcept -{ - m_templ_thres = templ_thres; -} - -void asst::MultiMatchImageAnalyzer::set_task_info(MatchTaskInfo task_info) noexcept -{ - m_mask_range = std::move(task_info.mask_range); - m_templ_name = std::move(task_info.templ_name); - m_templ_thres = task_info.templ_threshold; - set_roi(task_info.roi); -} - -void asst::MultiMatchImageAnalyzer::set_task_info(const std::shared_ptr& task_ptr) -{ - set_task_info(*std::dynamic_pointer_cast(task_ptr)); -} - -void asst::MultiMatchImageAnalyzer::set_task_info(const std::string& task_name) -{ - set_task_info(Task.get(task_name)); -} - -const std::vector& asst::MultiMatchImageAnalyzer::get_result() const noexcept -{ - return m_result; -} - -bool asst::MultiMatchImageAnalyzer::multi_match_templ(const cv::Mat templ) -{ - cv::Mat matched; - cv::Mat image_roi = m_image(make_rect(m_roi)); - - if (templ.cols > image_roi.cols || templ.rows > image_roi.rows) { - Log.error("templ size is too large", m_templ_name, "image_roi size:", image_roi.cols, image_roi.rows, - "templ size:", templ.cols, templ.rows); - return false; - } - - if (m_mask_range.first == 0 && m_mask_range.second == 0) { - cv::matchTemplate(image_roi, templ, matched, cv::TM_CCOEFF_NORMED); - } - else { - cv::Mat mask; - cv::cvtColor(templ, mask, cv::COLOR_BGR2GRAY); - // cv::threshold(mask, mask, m_mask_range.first, 255, cv::THRESH_BINARY); - cv::inRange(mask, m_mask_range.first, m_mask_range.second, mask); - cv::matchTemplate(image_roi, templ, matched, cv::TM_CCOEFF_NORMED, mask); - } - - int mini_distance = (std::min)(templ.cols, templ.rows) / 2; + int min_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); + if (m_params.templ_thres > value || value > 2.0) { + continue; + } + + 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(results)) { + if (std::abs(j + m_roi.x - iter.rect.x) >= min_distance || + std::abs(i + m_roi.y - iter.rect.y) >= min_distance) { + continue; } + + if (iter.score < value) { + iter.rect = rect; + iter.score = value; + } // else 这个点就放弃了 + need_push = false; + break; + } + if (need_push) { + Result tmp; + tmp.rect = rect; + tmp.score = value; + results.emplace_back(std::move(tmp)); } } } + if (results.empty()) { + return std::nullopt; + } + #ifdef ASST_DEBUG - for (const auto& rect : m_result) { + for (const auto& rect : results) { cv::rectangle(m_image_draw, make_rect(rect.rect), cv::Scalar(0, 0, 255), 2); cv::putText(m_image_draw, std::to_string(rect.score), cv::Point(rect.rect.x, rect.rect.y), 1, 1, cv::Scalar(0, 0, 255)); @@ -159,8 +70,11 @@ bool asst::MultiMatchImageAnalyzer::multi_match_templ(const cv::Mat templ) #endif if (m_log_tracing) { - Log.trace("multi_match_templ | ", m_templ_name, "result:", m_result, "roi:", m_roi); + Log.trace("multi_match_templ | ", templ_name, "result:", results, "roi:", m_roi); } - return !m_result.empty(); + // FIXME: 老接口太难重构了,先弄个这玩意兼容下,后续慢慢全删掉 + m_result = std::move(results); + return m_result; } + diff --git a/src/MaaCore/Vision/MultiMatchImageAnalyzer.h b/src/MaaCore/Vision/MultiMatchImageAnalyzer.h index e6b09b8ede..ad9d1d3113 100644 --- a/src/MaaCore/Vision/MultiMatchImageAnalyzer.h +++ b/src/MaaCore/Vision/MultiMatchImageAnalyzer.h @@ -1,44 +1,30 @@ #pragma once #include "AbstractImageAnalyzer.h" +#include "Vision/Config/MatcherConfig.h" -namespace asst -{ - class MultiMatchImageAnalyzer : public AbstractImageAnalyzer +namespace asst { + +class MultiMatchImageAnalyzer : public AbstractImageAnalyzer, public MatcherConfig { - public: - using AbstractImageAnalyzer::AbstractImageAnalyzer; - MultiMatchImageAnalyzer(const cv::Mat& image, std::string templ_name, double templ_thres); - virtual ~MultiMatchImageAnalyzer() override = default; +public: + using Result = MatchRect; + using ResultsVec = std::vector; + using ResultsVecOpt = std::optional; - virtual bool analyze() override; +public: + using AbstractImageAnalyzer::AbstractImageAnalyzer; + virtual ~MultiMatchImageAnalyzer() override = default; - // 按位置排序 - // 1 - 2 - 3 - // 4 - 5 - 6 - void sort_result_horizontal(); + ResultsVecOpt analyze() const; + // FIXME: 老接口太难重构了,先弄个这玩意兼容下,后续慢慢全删掉 + const auto& get_result() const noexcept { return m_result; } - // 按位置排序 - // 1 - 3 - 5 - // 2 - 4 - 6 - void sort_result_vertical(); +protected: + virtual void _set_roi(const Rect& roi) override { set_roi(roi); } - void set_mask_range(int lower, int upper) noexcept; - void set_mask_range(std::pair mask_range) noexcept; - void set_templ_name(std::string templ_name) noexcept; - void set_threshold(double templ_thres) noexcept; +private: + // FIXME: 老接口太难重构了,先弄个这玩意兼容下,后续慢慢全删掉 + mutable ResultsVec m_result; +}; - void set_task_info(const std::shared_ptr& task_ptr); - void set_task_info(const std::string& task_name); - - const std::vector& get_result() const noexcept; - - protected: - virtual bool multi_match_templ(const cv::Mat templ); - void set_task_info(MatchTaskInfo task_info) noexcept; - - std::string m_templ_name; - double m_templ_thres = 0.0; - std::vector m_result; - std::pair m_mask_range; - }; } diff --git a/src/MaaCore/Vision/OcrImageAnalyzer.cpp b/src/MaaCore/Vision/OcrImageAnalyzer.cpp index 798dfa79af..a4ec6b3618 100644 --- a/src/MaaCore/Vision/OcrImageAnalyzer.cpp +++ b/src/MaaCore/Vision/OcrImageAnalyzer.cpp @@ -8,241 +8,106 @@ #include "Config/TaskData.h" #include "Utils/Logger.hpp" -bool asst::OcrImageAnalyzer::analyze() +using namespace asst; + +OcrImageAnalyzer::ResultsVecOpt OcrImageAnalyzer::analyze() const { - // LogTraceFunction; - - m_ocr_result.clear(); - - std::vector preds_vec; - - preds_vec.emplace_back([](TextRect& tr) -> bool { - tr.text = OcrConfig::get_instance().process_equivalence_class(tr.text); - return true; - }); - - if (!m_replace.empty()) { - if (m_replace_full) { - TextRectProc text_replace = [&](TextRect& tr) -> bool { - for (const auto& [regex, new_str] : m_replace) { - if (std::regex_search(tr.text, std::regex(regex))) { - tr.text = new_str; - } - } - return true; - }; - preds_vec.emplace_back(text_replace); - } - else { - TextRectProc text_replace = [&](TextRect& tr) -> bool { - for (const auto& [regex, new_str] : m_replace) { - tr.text = std::regex_replace(tr.text, std::regex(regex), new_str); - } - return true; - }; - preds_vec.emplace_back(text_replace); - } - } - - if (!m_required.empty()) { - if (m_full_match) { - TextRectProc required_match = [&](TextRect& tr) -> bool { - return ranges::find(m_required, tr.text) != m_required.cend(); - }; - preds_vec.emplace_back(required_match); - } - else { - TextRectProc required_search = [&](TextRect& tr) -> bool { - auto is_sub = [&tr](const std::string& str) -> bool { - if (tr.text.find(str) == std::string::npos) { - return false; - } - tr.text = str; - return true; - }; - return ranges::find_if(m_required, is_sub) != m_required.cend(); - }; - preds_vec.emplace_back(required_search); - } - } - - preds_vec.emplace_back(m_pred); - - TextRectProc all_pred = [&](TextRect& tr) -> bool { - for (const auto& pred : preds_vec) { - if (pred && !pred(tr)) { - return false; - } - } - return true; - }; - - m_roi = correct_rect(m_roi, m_image); - OcrPack* ocr_ptr = nullptr; - if (m_use_char_model) { + if (m_params.use_char_model) { ocr_ptr = &CharOcr::get_instance(); } else { ocr_ptr = &WordOcr::get_instance(); } - m_ocr_result = ocr_ptr->recognize(m_image, m_roi, all_pred, m_without_det); + ResultsVec raw_results = ocr_ptr->recognize(make_roi(m_image, m_roi), m_params.without_det); ocr_ptr = nullptr; - // log.trace("ocr result", m_ocr_result); - return !m_ocr_result.empty(); -} - -void asst::OcrImageAnalyzer::filter(const TextRectProc& filter_func) -{ - std::vector temp_result; - - for (auto&& tr : get_result()) { - if (filter_func(tr)) { - temp_result.emplace_back(std::move(tr)); + /* post process */ + ResultsVec results_vec; + for (Result& res : raw_results) { + if (res.text.empty() || res.score > 1.0) { + continue; } + + postproc_rect_(res); + postproc_trim_(res); + postproc_equivalence_(res); + postproc_replace_(res); + + if (!filter_and_replace_by_required_(res)) { + continue; + } + + results_vec.emplace_back(std::move(res)); } - get_result() = std::move(temp_result); -} -void asst::OcrImageAnalyzer::set_use_cache(bool is_use) noexcept -{ - m_use_cache = is_use; -} + Log.trace("Proced", results_vec); -void asst::OcrImageAnalyzer::set_required(std::vector required) noexcept -{ - ranges::for_each(required, - [](std::string& str) { str = OcrConfig::get_instance().process_equivalence_class(str); }); - m_required = std::move(required); -} - -void asst::OcrImageAnalyzer::set_replace(const std::unordered_map& replace, - bool replace_full) noexcept -{ - m_replace = {}; - for (auto&& [key, val] : replace) { - auto new_key = OcrConfig::get_instance().process_equivalence_class(key); - // do not create new_val as val is user-provided, and can avoid issues like 夕 and katakana タ - m_replace.emplace(std::move(new_key), val); + if (results_vec.empty()) { + return std::nullopt; } - m_replace_full = replace_full; + + m_result = std::move(results_vec); + return m_result; } -void asst::OcrImageAnalyzer::set_task_info(OcrTaskInfo task_info) noexcept +void OcrImageAnalyzer::postproc_rect_(Result& res) const { - set_required(std::move(task_info.text)); - m_full_match = task_info.full_match; - set_replace(task_info.replace_map, task_info.replace_full); - m_use_cache = task_info.cache; - m_use_char_model = task_info.is_ascii; - - if (m_use_cache && !m_region_of_appeared.empty()) { - m_roi = m_region_of_appeared; - m_without_det = true; + if (m_params.without_det) { + res.rect = m_roi; } else { - set_roi(task_info.roi); - m_without_det = task_info.without_det; + res.rect.x += m_roi.x; + res.rect.y += m_roi.y; } } -std::vector& asst::OcrImageAnalyzer::get_result() noexcept +void OcrImageAnalyzer::postproc_trim_(Result& res) const { - return m_ocr_result; + utils::string_trim(res.text); } -void asst::OcrImageAnalyzer::set_task_info(std::shared_ptr task_ptr) +void OcrImageAnalyzer::postproc_equivalence_(Result& res) const { - set_task_info(*std::dynamic_pointer_cast(task_ptr)); + auto& ocr_config = OcrConfig::get_instance(); + res.text = ocr_config.process_equivalence_class(res.text); } -void asst::OcrImageAnalyzer::set_task_info(const std::string& task_name) +void OcrImageAnalyzer::postproc_replace_(Result& res) const { - set_task_info(Task.get(task_name)); -} - -void asst::OcrImageAnalyzer::set_region_of_appeared(Rect region) noexcept -{ - m_region_of_appeared = region; - if (m_use_cache && !m_region_of_appeared.empty()) { - m_roi = m_region_of_appeared; - m_without_det = true; - } -} - -void asst::OcrImageAnalyzer::set_use_char_model(bool enable) noexcept -{ - m_use_char_model = enable; -} - -void asst::OcrImageAnalyzer::set_pred(const TextRectProc& pred) -{ - m_pred = pred; -} - -const std::vector& asst::OcrImageAnalyzer::get_result() const noexcept -{ - return m_ocr_result; -} - -void asst::OcrImageAnalyzer::sort_result_horizontal() -{ - // 按位置排个序 - ranges::sort(get_result(), [](const TextRect& lhs, const TextRect& rhs) -> bool { - if (std::abs(lhs.rect.y - rhs.rect.y) < 5) { // y差距较小则理解为是同一排的,按x排序 - return lhs.rect.x < rhs.rect.x; - } - else { - return lhs.rect.y < rhs.rect.y; - } - }); -} - -void asst::OcrImageAnalyzer::sort_result_vertical() -{ - // 按位置排个序(顺序如下) - // +---+ - // |1 3| - // |2 4| - // +---+ - ranges::sort(get_result(), [](const TextRect& lhs, const TextRect& rhs) -> bool { - if (std::abs(lhs.rect.x - rhs.rect.x) < 5) { // x差距较小则理解为是同一排的,按y排序 - return lhs.rect.y < rhs.rect.y; - } - else { - return lhs.rect.x < rhs.rect.x; - } - }); -} - -void asst::OcrImageAnalyzer::sort_result_by_score() -{ - ranges::sort(get_result(), std::greater {}, std::mem_fn(&TextRect::score)); -} - -void asst::OcrImageAnalyzer::sort_result_by_required() -{ - if (m_required.empty()) { + if (m_params.replace.empty()) { return; } - std::unordered_map req_cache; - for (size_t i = 0; i != m_required.size(); ++i) { - req_cache.emplace(m_required.at(i), i + 1); + for (const auto& [regex, new_str] : m_params.replace) { + if (m_params.replace_full) { + if (std::regex_search(res.text, std::regex(regex))) { + res.text = new_str; + } + } + else { + res.text = std::regex_replace(res.text, std::regex(regex), new_str); + } + } +} + +bool OcrImageAnalyzer::filter_and_replace_by_required_(Result& res) const +{ + if (m_params.required.empty()) { + return true; } - auto& result = get_result(); - // 不在 m_required 中的将被排在最后 - ranges::sort(result, [&req_cache](const auto& lhs, const auto& rhs) -> bool { - size_t lvalue = req_cache[lhs.text]; - size_t rvalue = req_cache[rhs.text]; - if (lvalue == 0) { - return false; - } - else if (rvalue == 0) { + if (m_params.full_match) { + return ranges::find(m_params.required, res.text) != m_params.required.cend(); + } + else { + auto is_sub = [&res](const std::string& str) -> bool { + if (res.text.find(str) == std::string::npos) { + return false; + } + res.text = str; return true; - } - return lvalue < rvalue; - }); + }; + return ranges::find_if(m_params.required, is_sub) != m_params.required.cend(); + }; } diff --git a/src/MaaCore/Vision/OcrImageAnalyzer.h b/src/MaaCore/Vision/OcrImageAnalyzer.h index 634f6ed706..1f8ce37635 100644 --- a/src/MaaCore/Vision/OcrImageAnalyzer.h +++ b/src/MaaCore/Vision/OcrImageAnalyzer.h @@ -1,56 +1,45 @@ #pragma once #include "AbstractImageAnalyzer.h" -#include -#include -#include - #include "Common/AsstTypes.h" +#include "Config/Miscellaneous/OcrPack.h" +#include "Vision/Config/OCRerConfig.h" namespace asst { - class OcrImageAnalyzer : public AbstractImageAnalyzer + + class OcrImageAnalyzer : public AbstractImageAnalyzer, public OCRerConfig { + public: + using Result = OcrPack::Result; + using ResultsVec = OcrPack::ResultsVec; + using ResultsVecOpt = std::optional; + public: using AbstractImageAnalyzer::AbstractImageAnalyzer; virtual ~OcrImageAnalyzer() override = default; - virtual bool analyze() override; - - void filter(const TextRectProc& filter_func); - - virtual void sort_result_horizontal(); // 按位置排序,左上角的排在前面,右上角在左下角前面 - virtual void sort_result_vertical(); // 按位置排序,左上角的排在前面,左下角在右上角前面 - virtual void sort_result_by_score(); // 按分数排序,得分最高的在前面 - virtual void sort_result_by_required(); // 按传入的需求数组排序,传入的在前面结果接在前面 - - void set_required(std::vector required) noexcept; - void set_replace(const std::unordered_map& replace, - bool replace_full = false) noexcept; - - virtual void set_task_info(std::shared_ptr task_ptr); - virtual void set_task_info(const std::string& task_name); - - virtual void set_use_cache(bool is_use) noexcept; - virtual void set_region_of_appeared(Rect region) noexcept; - virtual void set_use_char_model(bool enable) noexcept; - - void set_pred(const TextRectProc& pred); - virtual const std::vector& get_result() const noexcept; - virtual std::vector& get_result() noexcept; + ResultsVecOpt analyze() const; + // FIXME: 老接口太难重构了,先弄个这玩意兼容下,后续慢慢全删掉 + const auto& get_result() const noexcept { return m_result; } protected: - virtual void set_task_info(OcrTaskInfo task_info) noexcept; + virtual void _set_roi(const Rect& roi) override { set_roi(roi); } - std::vector m_ocr_result; - std::vector m_required; - bool m_full_match = false; - std::unordered_map m_replace; - bool m_replace_full = false; - TextRectProc m_pred = nullptr; - bool m_without_det = false; - bool m_use_cache = false; - Rect m_region_of_appeared; - bool m_use_char_model = false; + // Not working for OCR with detection + using OCRerConfig::set_bin_expansion; + using OCRerConfig::set_bin_threshold; + + protected: + void postproc_rect_(Result& res) const; + void postproc_trim_(Result& res) const; + void postproc_equivalence_(Result& res) const; + void postproc_replace_(Result& res) const; + + bool filter_and_replace_by_required_(Result& res) const; + + private: + // FIXME: 老接口太难重构了,先弄个这玩意兼容下,后续慢慢全删掉 + mutable ResultsVec m_result; }; } diff --git a/src/MaaCore/Vision/OcrWithFlagTemplImageAnalyzer.cpp b/src/MaaCore/Vision/OcrWithFlagTemplImageAnalyzer.cpp index 075d9d3146..34d37bde52 100644 --- a/src/MaaCore/Vision/OcrWithFlagTemplImageAnalyzer.cpp +++ b/src/MaaCore/Vision/OcrWithFlagTemplImageAnalyzer.cpp @@ -1,72 +1,62 @@ #include "OcrWithFlagTemplImageAnalyzer.h" #include "Config/TaskData.h" +#include "MultiMatchImageAnalyzer.h" +#include "OcrWithPreprocessImageAnalyzer.h" -asst::OcrWithFlagTemplImageAnalyzer::OcrWithFlagTemplImageAnalyzer(const cv::Mat& image) - : OcrWithPreprocessImageAnalyzer(image), m_multi_match_image_analyzer(image) -{} +using namespace asst; -void asst::OcrWithFlagTemplImageAnalyzer::set_image(const cv::Mat& image) +OcrWithFlagTemplImageAnalyzer::ResultsVecOpt OcrWithFlagTemplImageAnalyzer::analyze() const { - OcrWithPreprocessImageAnalyzer::set_image(image); - m_multi_match_image_analyzer.set_image(image); -} + MultiMatchImageAnalyzer flag_analyzer(m_image, m_roi); + flag_analyzer.set_params(MatcherConfig::m_params); -void asst::OcrWithFlagTemplImageAnalyzer::set_roi(const Rect& roi) noexcept -{ - OcrWithPreprocessImageAnalyzer::set_roi(roi); - m_multi_match_image_analyzer.set_roi(roi); -} - -bool asst::OcrWithFlagTemplImageAnalyzer::analyze() -{ - m_all_result.clear(); - m_flag_result.clear(); - - if (!m_multi_match_image_analyzer.analyze()) { - return false; + auto matched_vec_opt = flag_analyzer.analyze(); + if (!matched_vec_opt) { + return std::nullopt; } - for (const auto& templ_res : m_multi_match_image_analyzer.get_result()) { - Rect roi = templ_res.rect.move(m_flag_rect_move); - if (roi.x + roi.width >= WindowWidthDefault) { + auto& matched_vec = *matched_vec_opt; + + ResultsVec results; + for (const auto& matched : matched_vec) { + Rect roi = matched.rect.move(m_flag_rect_move); + + OcrWithPreprocessImageAnalyzer ocr_analyzer(m_image, roi); + ocr_analyzer.set_params(OCRerConfig::m_params); + auto ocr_opt = ocr_analyzer.analyze(); + if (!ocr_opt) { continue; } - set_roi(roi); + Result result; + result.text = ocr_opt->text; + result.rect = ocr_opt->rect; + result.score = ocr_opt->score; + result.flag_rect = matched.rect; + result.flag_score = matched.score; - if (OcrWithPreprocessImageAnalyzer::analyze()) { - m_all_result.emplace_back(m_ocr_result.front()); - m_flag_result.emplace_back(templ_res); - } + results.emplace_back(std::move(result)); } - return !m_all_result.empty(); + if (results.empty()) { + return std::nullopt; + } + + m_result = std::move(results); + return m_result; } -const std::vector& asst::OcrWithFlagTemplImageAnalyzer::get_result() const noexcept +void OcrWithFlagTemplImageAnalyzer::set_task_info(const std::string& templ_task_name, const std::string& ocr_task_name) { - return m_all_result; -} + auto match_task_ptr = Task.get(templ_task_name); + m_roi = match_task_ptr->roi; + MatcherConfig::_set_task_info(*match_task_ptr); -void asst::OcrWithFlagTemplImageAnalyzer::set_task_info(const std::string& templ_task_name, - const std::string& ocr_task_name) -{ auto ocr_task_ptr = Task.get(ocr_task_name); - set_task_info(*ocr_task_ptr); m_flag_rect_move = ocr_task_ptr->roi; - m_multi_match_image_analyzer.set_task_info(templ_task_name); + OCRerConfig::_set_task_info(*ocr_task_ptr); } -void asst::OcrWithFlagTemplImageAnalyzer::set_flag_rect_move(Rect flag_rect_move) +void OcrWithFlagTemplImageAnalyzer::set_flag_rect_move(Rect flag_rect_move) { m_flag_rect_move = flag_rect_move; } - -std::vector& asst::OcrWithFlagTemplImageAnalyzer::get_result() noexcept -{ - return m_all_result; -} - -std::vector& asst::OcrWithFlagTemplImageAnalyzer::get_flag_result() noexcept -{ - return m_flag_result; -} diff --git a/src/MaaCore/Vision/OcrWithFlagTemplImageAnalyzer.h b/src/MaaCore/Vision/OcrWithFlagTemplImageAnalyzer.h index 88f79318c4..230d384e6e 100644 --- a/src/MaaCore/Vision/OcrWithFlagTemplImageAnalyzer.h +++ b/src/MaaCore/Vision/OcrWithFlagTemplImageAnalyzer.h @@ -1,34 +1,47 @@ #pragma once -#include "MultiMatchImageAnalyzer.h" -#include "OcrWithPreprocessImageAnalyzer.h" +#include "AbstractImageAnalyzer.h" +#include "Config/MatcherConfig.h" +#include "Config/OCRerConfig.h" namespace asst { - class OcrWithFlagTemplImageAnalyzer : public OcrWithPreprocessImageAnalyzer + class OcrWithFlagTemplImageAnalyzer : public AbstractImageAnalyzer, public OCRerConfig, public MatcherConfig { public: - OcrWithFlagTemplImageAnalyzer() = default; - OcrWithFlagTemplImageAnalyzer(const cv::Mat& image); + struct Result : public TextRect + { + Rect flag_rect; + double flag_score = .0; + }; + + using ResultsVec = std::vector; + using ResultsVecOpt = std::optional; + + public: + using AbstractImageAnalyzer::AbstractImageAnalyzer; virtual ~OcrWithFlagTemplImageAnalyzer() override = default; - virtual void set_image(const cv::Mat& image) override; - virtual void set_roi(const Rect& roi) noexcept override; - - virtual bool analyze() override; - - virtual const std::vector& get_result() const noexcept override; - virtual std::vector& get_result() noexcept override; - std::vector& get_flag_result() noexcept; - void set_task_info(const std::string& templ_task_name, const std::string& ocr_task_name); void set_flag_rect_move(Rect flag_rect_move); - protected: - using OcrWithPreprocessImageAnalyzer::set_task_info; + ResultsVecOpt analyze() const; + // FIXME: 老接口太难重构了,先弄个这玩意兼容下,后续慢慢全删掉 + const auto& get_result() const noexcept { return m_result; } - MultiMatchImageAnalyzer m_multi_match_image_analyzer; + protected: + // from Config + virtual void _set_roi(const Rect& roi) override { std::ignore = roi; } + + protected: + using MatcherConfig::set_task_info; + using OCRerConfig::set_task_info; + + private: Rect m_flag_rect_move; - std::vector m_all_result; - std::vector m_flag_result; + + private: + // FIXME: 老接口太难重构了,先弄个这玩意兼容下,后续慢慢全删掉 + mutable ResultsVec m_result; }; + } diff --git a/src/MaaCore/Vision/OcrWithPreprocessImageAnalyzer.cpp b/src/MaaCore/Vision/OcrWithPreprocessImageAnalyzer.cpp index d9b781225f..c4b6b9828d 100644 --- a/src/MaaCore/Vision/OcrWithPreprocessImageAnalyzer.cpp +++ b/src/MaaCore/Vision/OcrWithPreprocessImageAnalyzer.cpp @@ -2,69 +2,47 @@ #include "Utils/NoWarningCV.h" -bool asst::OcrWithPreprocessImageAnalyzer::analyze() -{ - m_without_det = true; +using namespace asst; - m_roi = correct_rect(m_roi, m_image); - cv::Mat img_roi = m_image(make_rect(m_roi)); +OcrWithPreprocessImageAnalyzer::ResultOpt OcrWithPreprocessImageAnalyzer::analyze() const +{ + cv::Mat img_roi = make_roi(m_image, m_roi); cv::Mat img_roi_gray; cv::cvtColor(img_roi, img_roi_gray, cv::COLOR_BGR2GRAY); cv::Mat bin; - cv::inRange(img_roi_gray, m_threshold_lower, m_threshold_upper, bin); + cv::inRange(img_roi_gray, m_params.bin_threshold_lower, m_params.bin_threshold_upper, bin); cv::Rect bounding_rect = cv::boundingRect(bin); bounding_rect.x += m_roi.x; bounding_rect.y += m_roi.y; auto new_roi = make_rect(bounding_rect); if (new_roi.empty()) { - return false; + return std::nullopt; } // todo: split - if (m_expansion) { - new_roi.x -= m_expansion; - new_roi.y -= m_expansion; - new_roi.width += 2 * m_expansion; - new_roi.height += 2 * m_expansion; + int exp = m_params.bin_expansion; + if (exp) { + new_roi.x -= exp; + new_roi.y -= exp; + new_roi.width += 2 * exp; + new_roi.height += 2 * exp; } - OcrImageAnalyzer::set_roi(new_roi); + #ifdef ASST_DEBUG cv::rectangle(m_image_draw, make_rect(new_roi), cv::Scalar(0, 0, 255), 1); #endif // ASST_DEBUG - return OcrImageAnalyzer::analyze(); + OcrImageAnalyzer ocr_analyzer(m_image, new_roi); + auto config = m_params; + config.without_det = true; + ocr_analyzer.set_params(std::move(config)); + + auto result = ocr_analyzer.analyze(); + if (!result) { + return std::nullopt; + } + m_result = result->front(); + return m_result; } -void asst::OcrWithPreprocessImageAnalyzer::set_threshold(int lower, int upper) -{ - m_threshold_lower = lower; - m_threshold_upper = upper; -} - -void asst::OcrWithPreprocessImageAnalyzer::set_split(bool split) -{ - m_split = split; -} - -void asst::OcrWithPreprocessImageAnalyzer::set_expansion(int expansion) -{ - m_expansion = expansion; -} - -void asst::OcrWithPreprocessImageAnalyzer::set_task_info(std::shared_ptr task_ptr) -{ - OcrImageAnalyzer::set_task_info(task_ptr); -} - -void asst::OcrWithPreprocessImageAnalyzer::set_task_info(const std::string& task_name) -{ - OcrImageAnalyzer::set_task_info(task_name); -} - -void asst::OcrWithPreprocessImageAnalyzer::set_task_info(OcrTaskInfo task_info) noexcept -{ - task_info.cache = false; - task_info.without_det = true; - OcrImageAnalyzer::set_task_info(std::move(task_info)); -} diff --git a/src/MaaCore/Vision/OcrWithPreprocessImageAnalyzer.h b/src/MaaCore/Vision/OcrWithPreprocessImageAnalyzer.h index 9b8d90197d..1e67ca19ac 100644 --- a/src/MaaCore/Vision/OcrWithPreprocessImageAnalyzer.h +++ b/src/MaaCore/Vision/OcrWithPreprocessImageAnalyzer.h @@ -3,31 +3,26 @@ namespace asst { - class OcrWithPreprocessImageAnalyzer : public OcrImageAnalyzer + class OcrWithPreprocessImageAnalyzer : public AbstractImageAnalyzer, public OCRerConfig { public: - using OcrImageAnalyzer::OcrImageAnalyzer; - virtual ~OcrWithPreprocessImageAnalyzer() noexcept override = default; + using Result = OcrImageAnalyzer::Result; + using ResultOpt = std::optional; - virtual bool analyze() override; + public: + using AbstractImageAnalyzer::AbstractImageAnalyzer; + virtual ~OcrWithPreprocessImageAnalyzer() override = default; - void set_threshold(int lower, int upper = 255); - void set_split(bool split); - void set_expansion(int expansion); - - virtual void set_task_info(std::shared_ptr task_ptr) override; - virtual void set_task_info(const std::string& task_name) override; + ResultOpt analyze() const; + // FIXME: 老接口太难重构了,先弄个这玩意兼容下,后续慢慢全删掉 + const auto& get_result() const noexcept { return m_result; } protected: - virtual void set_task_info(OcrTaskInfo task_info) noexcept override; - - int m_threshold_lower = 140; - int m_threshold_upper = 255; - bool m_split = false; - int m_expansion = 2; + virtual void _set_roi(const Rect& roi) override { set_roi(roi); } private: - virtual void set_use_cache(bool is_use) noexcept override { std::ignore = is_use; } - virtual void set_region_of_appeared(Rect region) noexcept override { std::ignore = region; } + // FIXME: 老接口太难重构了,先弄个这玩意兼容下,后续慢慢全删掉 + mutable Result m_result; }; + } diff --git a/src/MaaCore/Vision/Roguelike/RoguelikeFormationImageAnalyzer.cpp b/src/MaaCore/Vision/Roguelike/RoguelikeFormationImageAnalyzer.cpp index 713b9505d8..903657ec5e 100644 --- a/src/MaaCore/Vision/Roguelike/RoguelikeFormationImageAnalyzer.cpp +++ b/src/MaaCore/Vision/Roguelike/RoguelikeFormationImageAnalyzer.cpp @@ -15,29 +15,19 @@ bool asst::RoguelikeFormationImageAnalyzer::analyze() analyzer.set_task_info("RoguelikeFormationOper", "RoguelikeFormationOcr"); auto replace_task = Task.get("CharsNameOcrReplace"); analyzer.set_replace(replace_task->replace_map, replace_task->replace_full); - analyzer.set_threshold(Task.get("RoguelikeFormationOcr")->special_params[0]); + analyzer.set_bin_threshold(Task.get("RoguelikeFormationOcr")->special_params[0]); - if (!analyzer.analyze()) { + auto result_opt = analyzer.analyze(); + if (!result_opt) { return false; } - - analyzer.sort_result_vertical(); - auto& rect_list = analyzer.get_flag_result(); - ranges::sort(rect_list, [](const Rect& lhs, const Rect& rhs) -> bool { - if (std::abs(lhs.x - rhs.x) < 5) { - return lhs.y < rhs.y; - } - else { - return lhs.x < rhs.x; - } - }); + sort_by_vertical_(*result_opt); int pos = 0; - for (const auto& [_, text_rect, name] : analyzer.get_result()) { - Rect rect = rect_list[pos]; + for (const auto& res : *result_opt) { FormationOper oper; - oper.rect = rect; - oper.selected = selected_analyze(rect); - oper.name = name; + oper.rect = res.flag_rect; + oper.selected = selected_analyze(res.flag_rect); + oper.name = res.text; pos++; #ifdef ASST_DEBUG diff --git a/src/MaaCore/Vision/Roguelike/RoguelikeFormationImageAnalyzer.h b/src/MaaCore/Vision/Roguelike/RoguelikeFormationImageAnalyzer.h index a28adb763f..a5e89d4b2b 100644 --- a/src/MaaCore/Vision/Roguelike/RoguelikeFormationImageAnalyzer.h +++ b/src/MaaCore/Vision/Roguelike/RoguelikeFormationImageAnalyzer.h @@ -19,7 +19,7 @@ namespace asst using AbstractImageAnalyzer::AbstractImageAnalyzer; virtual ~RoguelikeFormationImageAnalyzer() override = default; - virtual bool analyze() override; + bool analyze(); const std::vector& get_result() const noexcept; diff --git a/src/MaaCore/Vision/Roguelike/RoguelikeRecruitImageAnalyzer.cpp b/src/MaaCore/Vision/Roguelike/RoguelikeRecruitImageAnalyzer.cpp index f89adb028b..c495771ea0 100644 --- a/src/MaaCore/Vision/Roguelike/RoguelikeRecruitImageAnalyzer.cpp +++ b/src/MaaCore/Vision/Roguelike/RoguelikeRecruitImageAnalyzer.cpp @@ -5,6 +5,7 @@ #include "Utils/NoWarningCV.h" #include "Vision/MatchImageAnalyzer.h" #include "Vision/OcrWithFlagTemplImageAnalyzer.h" +#include "Vision/OcrWithPreprocessImageAnalyzer.h" bool asst::RoguelikeRecruitImageAnalyzer::analyze() { @@ -14,9 +15,10 @@ bool asst::RoguelikeRecruitImageAnalyzer::analyze() analyzer.set_task_info("RoguelikeRecruitOcrFlag", "RoguelikeRecruitOcr"); analyzer.set_replace(Task.get("CharsNameOcrReplace")->replace_map, Task.get("CharsNameOcrReplace")->replace_full); - analyzer.set_threshold(Task.get("RoguelikeRecruitOcr")->specific_rect.x); + analyzer.set_bin_threshold(Task.get("RoguelikeRecruitOcr")->specific_rect.x); - if (!analyzer.analyze()) { + auto result_opt = analyzer.analyze(); + if (!result_opt) { return false; } @@ -28,10 +30,10 @@ bool asst::RoguelikeRecruitImageAnalyzer::analyze() cv::merge(std::array { blue, blue, blue }, bbb_image); } - analyzer.sort_result_vertical(); - for (const auto& [_, rect, name] : analyzer.get_result()) { - int elite = match_elite(rect); - int level = match_level(bbb_image, rect); + sort_by_vertical_(*result_opt); + for (const auto& res : *result_opt) { + int elite = match_elite(res.rect); + int level = match_level(bbb_image, res.rect); if (level < 0) { // 要么就是识别错了,要么这个干员希望不够,是灰色的 @@ -40,12 +42,12 @@ bool asst::RoguelikeRecruitImageAnalyzer::analyze() } battle::roguelike::Recruitment info; - info.rect = rect; - info.name = name; + info.rect = res.rect; + info.name = res.text; info.elite = elite; info.level = level; - Log.info(__FUNCTION__, name, elite, level, rect.to_string()); + Log.info(__FUNCTION__, info.name, elite, level, info.rect); m_result.emplace_back(std::move(info)); } @@ -90,13 +92,13 @@ int asst::RoguelikeRecruitImageAnalyzer::match_level(const cv::Mat& image, const OcrWithPreprocessImageAnalyzer analyzer(image); analyzer.set_task_info("NumberOcrReplace"); analyzer.set_roi(raw_roi.move(Task.get("RoguelikeRecruitLevel")->rect_move)); - analyzer.set_expansion(1); + analyzer.set_bin_expansion(1); if (!analyzer.analyze()) { return -1; } - const std::string& level = analyzer.get_result().front().text; + const std::string& level = analyzer.get_result().text; if (level.empty() || !ranges::all_of(level, [](char c) -> bool { return std::isdigit(c); })) { return 0; } diff --git a/src/MaaCore/Vision/Roguelike/RoguelikeRecruitImageAnalyzer.h b/src/MaaCore/Vision/Roguelike/RoguelikeRecruitImageAnalyzer.h index 7938e8e1f4..08fddb6559 100644 --- a/src/MaaCore/Vision/Roguelike/RoguelikeRecruitImageAnalyzer.h +++ b/src/MaaCore/Vision/Roguelike/RoguelikeRecruitImageAnalyzer.h @@ -11,7 +11,7 @@ namespace asst using AbstractImageAnalyzer::AbstractImageAnalyzer; virtual ~RoguelikeRecruitImageAnalyzer() noexcept override = default; - bool analyze() override; + bool analyze(); const auto& get_result() const noexcept { return m_result; } diff --git a/src/MaaCore/Vision/Roguelike/RoguelikeRecruitSupportAnalyzer.cpp b/src/MaaCore/Vision/Roguelike/RoguelikeRecruitSupportAnalyzer.cpp index f908a7b41b..bb11cd49e6 100644 --- a/src/MaaCore/Vision/Roguelike/RoguelikeRecruitSupportAnalyzer.cpp +++ b/src/MaaCore/Vision/Roguelike/RoguelikeRecruitSupportAnalyzer.cpp @@ -11,6 +11,7 @@ #include "Vision/MatchImageAnalyzer.h" #include "Vision/OcrImageAnalyzer.h" #include "Vision/OcrWithFlagTemplImageAnalyzer.h" +#include "Vision/OcrWithPreprocessImageAnalyzer.h" bool asst::RoguelikeRecruitSupportAnalyzer::analyze() { @@ -192,14 +193,14 @@ int asst::RoguelikeRecruitSupportAnalyzer::match_level(const Rect& roi) OcrWithPreprocessImageAnalyzer analyzer(m_image); analyzer.set_task_info("NumberOcrReplace"); analyzer.set_roi(roi); - analyzer.set_expansion(1); + analyzer.set_bin_expansion(1); if (!analyzer.analyze()) { return -1; } - Log.info(__FUNCTION__, "| ", roi, "`", analyzer.get_result().front().text, "`"); - const std::string& level = analyzer.get_result().front().text; + Log.info(__FUNCTION__, "| ", roi, "`", analyzer.get_result().text, "`"); + const std::string& level = analyzer.get_result().text; if (level.empty() || !ranges::all_of(level, [](char c) -> bool { return std::isdigit(c); })) { return 0; } diff --git a/src/MaaCore/Vision/Roguelike/RoguelikeRecruitSupportAnalyzer.h b/src/MaaCore/Vision/Roguelike/RoguelikeRecruitSupportAnalyzer.h index 0fbcd83151..877c04957a 100644 --- a/src/MaaCore/Vision/Roguelike/RoguelikeRecruitSupportAnalyzer.h +++ b/src/MaaCore/Vision/Roguelike/RoguelikeRecruitSupportAnalyzer.h @@ -11,7 +11,7 @@ namespace asst using AbstractImageAnalyzer::AbstractImageAnalyzer; virtual ~RoguelikeRecruitSupportAnalyzer() noexcept override = default; - bool analyze() override; + bool analyze(); void set_mode(battle::roguelike::SupportAnalyzeMode mode) noexcept { m_mode = mode; } void set_required(std::vector required) noexcept { m_required = required; } diff --git a/src/MaaCore/Vision/Roguelike/RoguelikeSkillSelectionImageAnalyzer.cpp b/src/MaaCore/Vision/Roguelike/RoguelikeSkillSelectionImageAnalyzer.cpp index 906463ea1e..00b77640ab 100644 --- a/src/MaaCore/Vision/Roguelike/RoguelikeSkillSelectionImageAnalyzer.cpp +++ b/src/MaaCore/Vision/Roguelike/RoguelikeSkillSelectionImageAnalyzer.cpp @@ -50,7 +50,7 @@ std::string asst::RoguelikeSkillSelectionImageAnalyzer::name_analyze(const Rect& { OcrWithPreprocessImageAnalyzer analyzer; auto name_task_ptr = Task.get("RoguelikeSkillSelectionName"); - analyzer.set_threshold(name_task_ptr->specific_rect.x); + analyzer.set_bin_threshold(name_task_ptr->specific_rect.x); analyzer.set_task_info(name_task_ptr); analyzer.set_image(m_image); analyzer.set_roi(roi.move(name_task_ptr->roi)); @@ -60,7 +60,7 @@ std::string asst::RoguelikeSkillSelectionImageAnalyzer::name_analyze(const Rect& if (!analyzer.analyze()) { return {}; } - return analyzer.get_result().front().text; + return analyzer.get_result().text; } std::vector asst::RoguelikeSkillSelectionImageAnalyzer::skill_analyze(const Rect& roi) diff --git a/src/MaaCore/Vision/Roguelike/RoguelikeSkillSelectionImageAnalyzer.h b/src/MaaCore/Vision/Roguelike/RoguelikeSkillSelectionImageAnalyzer.h index 76c760cdfc..bbaafe64f7 100644 --- a/src/MaaCore/Vision/Roguelike/RoguelikeSkillSelectionImageAnalyzer.h +++ b/src/MaaCore/Vision/Roguelike/RoguelikeSkillSelectionImageAnalyzer.h @@ -12,7 +12,7 @@ namespace asst using AbstractImageAnalyzer::AbstractImageAnalyzer; virtual ~RoguelikeSkillSelectionImageAnalyzer() override = default; - virtual bool analyze() override; + bool analyze(); const auto& get_result() const noexcept { return m_result; } bool get_team_full() const noexcept { return m_team_full; }