diff --git a/resource/tasks.json b/resource/tasks.json index f088d2264c..4a76d56392 100644 --- a/resource/tasks.json +++ b/resource/tasks.json @@ -2327,6 +2327,30 @@ "[Uu]-[O0o]f{1,2}icial", "U-Official" ], + [ + "^4", + "" + ], + [ + "^α", + "" + ], + [ + "^-", + "" + ], + [ + "—", + "" + ], + [ + ".*12F", + "12F" + ], + [ + "^_", + "" + ], [ ".*威龙陈", "假日威龙陈" @@ -9745,18 +9769,64 @@ 327 ] }, + "OperBoxFlagElite1": { + "doc": "精英一 模板" + }, + "OperBoxFlagElite2": { + "doc": "精英二 模板" + }, + "OperBoxFlagElite": { + "doc": "偏移,识别精英度,扩大两个像素点", + "template": "empty.png", + "templThreshold": 0.9, + "roi": [ + -7, + -52, + 24, + 35 + ] + }, + "OperBoxPotential": { + "doc": "偏移,识别潜能,扩大两个像素点", + "template": "empty.png", + "templThreshold": 0.85, + "roi": [ + 81, + -29, + 24, + 24 + ] + }, + "OperBoxPotential1": {}, + "OperBoxPotential2": {}, + "OperBoxPotential3": {}, + "OperBoxPotential4": {}, + "OperBoxPotential5": {}, "OperBoxNameOCR": { "algorithm": "OcrDetect", "fullMatch": true, "text": [], "roi": [ - -10, + 5, 42, - 120, + 105, 20 ], "postDelay": 100 }, + "OperBoxLevelOCR": { + "algorithm": "OcrDetect", + "fullMatch": true, + "isAscii": true, + "text": [], + "roi": [ + -13, + 7, + 37, + 23 + ], + "postDelay": 100 + }, "RoguelikeRecruitSwipeMaxTime": { "algorithm": "JustReturn", "maxTimes_Doc": "这个参数是肉鸽招募时,向右划动的最大次数", diff --git a/resource/template/OperBoxFlagElite1.png b/resource/template/OperBoxFlagElite1.png new file mode 100644 index 0000000000..3f78e0712e Binary files /dev/null and b/resource/template/OperBoxFlagElite1.png differ diff --git a/resource/template/OperBoxFlagElite2.png b/resource/template/OperBoxFlagElite2.png new file mode 100644 index 0000000000..b1fd8af6c1 Binary files /dev/null and b/resource/template/OperBoxFlagElite2.png differ diff --git a/resource/template/OperBoxPotential1.png b/resource/template/OperBoxPotential1.png new file mode 100644 index 0000000000..c91d7abbfd Binary files /dev/null and b/resource/template/OperBoxPotential1.png differ diff --git a/resource/template/OperBoxPotential2.png b/resource/template/OperBoxPotential2.png new file mode 100644 index 0000000000..1c677fcf6f Binary files /dev/null and b/resource/template/OperBoxPotential2.png differ diff --git a/resource/template/OperBoxPotential3.png b/resource/template/OperBoxPotential3.png new file mode 100644 index 0000000000..9cee9ba29c Binary files /dev/null and b/resource/template/OperBoxPotential3.png differ diff --git a/resource/template/OperBoxPotential4.png b/resource/template/OperBoxPotential4.png new file mode 100644 index 0000000000..6496c75638 Binary files /dev/null and b/resource/template/OperBoxPotential4.png differ diff --git a/resource/template/OperBoxPotential5.png b/resource/template/OperBoxPotential5.png new file mode 100644 index 0000000000..35dd6b93aa Binary files /dev/null and b/resource/template/OperBoxPotential5.png differ diff --git a/src/MaaCore/Task/Miscellaneous/OperBoxRecognitionTask.cpp b/src/MaaCore/Task/Miscellaneous/OperBoxRecognitionTask.cpp index c59e1d35b3..d71fd9594e 100644 --- a/src/MaaCore/Task/Miscellaneous/OperBoxRecognitionTask.cpp +++ b/src/MaaCore/Task/Miscellaneous/OperBoxRecognitionTask.cpp @@ -23,7 +23,9 @@ bool asst::OperBoxRecognitionTask::swipe_and_analyze() { LogTraceFunction; std::string current_page_last_oper_name = ""; + std::string current_page_first_oper_name = ""; m_own_opers.clear(); + while (!need_exit()) { OperBoxImageAnalyzer analyzer(ctrler()->get_image()); @@ -33,10 +35,12 @@ bool asst::OperBoxRecognitionTask::swipe_and_analyze() break; } const auto& opers_result = analyzer.get_result(); - if (opers_result.back().name == current_page_last_oper_name) { + if (opers_result.back().name == current_page_last_oper_name && + opers_result.front().name == current_page_first_oper_name) { break; } else { + current_page_first_oper_name = opers_result.front().name; current_page_last_oper_name = opers_result.back().name; for (const auto& box_info : opers_result) { m_own_opers.emplace(box_info.name, box_info); @@ -72,13 +76,17 @@ void asst::OperBoxRecognitionTask::callback_analyze_result(bool done) { "name", name }, { "own", own }, }); - if (own) { - own_opers.emplace_back(json::object { - { "id", BattleData.get_id(name) }, { "name", name }, { "own", own }, - // TODO - // { "elite", 0 }, { "level", 0 }, - }); - } + } + for (const auto& [name,box_info] : m_own_opers) { + own_opers.emplace_back(json::object { + { "id", box_info.id }, + { "name", box_info.name }, + { "own", box_info.own }, + { "elite", box_info.elite }, + { "level", box_info.level }, + { "potential", box_info.potential }, + // TODO + }); } callback(AsstMsg::SubTaskExtraInfo, info); diff --git a/src/MaaCore/Vision/Miscellaneous/OperBoxImageAnalyzer.cpp b/src/MaaCore/Vision/Miscellaneous/OperBoxImageAnalyzer.cpp index 0cc72e6656..a0c74b60d7 100644 --- a/src/MaaCore/Vision/Miscellaneous/OperBoxImageAnalyzer.cpp +++ b/src/MaaCore/Vision/Miscellaneous/OperBoxImageAnalyzer.cpp @@ -5,48 +5,244 @@ #include "Config/Miscellaneous/BattleDataConfig.h" #include "Config/TaskData.h" #include "Utils/Logger.hpp" +#include "Vision/BestMatchImageAnalyzer.h" #include "Vision/MatchImageAnalyzer.h" -#include "Vision/OcrWithFlagTemplImageAnalyzer.h" +#include "Vision/MultiMatchImageAnalyzer.h" #include "Vision/OcrWithPreprocessImageAnalyzer.h" bool asst::OperBoxImageAnalyzer::analyze() { LogTraceFunction; + m_current_page_opers.clear(); - bool oper_box = analyzer_opers(); -#ifdef ASST_DEBUG - m_image_draw = m_image_draw_oper; -#endif + bool oper_box = analyzer_oper_box(); save_img(utils::path("debug") / utils::path("oper")); return oper_box; } -bool asst::OperBoxImageAnalyzer::analyzer_opers() +int asst::OperBoxImageAnalyzer::level_num(const std::string &level) { - std::vector oper_names_result; - const auto& ocr_replace = Task.get("CharsNameOcrReplace"); + if (level.empty() || !ranges::all_of(level, [](const char& c) -> bool { return std::isdigit(c); })) { + return 1; + } + return std::stoi(level); +} - OcrWithFlagTemplImageAnalyzer oper_name_analyzer(m_image); +bool asst::OperBoxImageAnalyzer::analyzer_oper_box() +{ + LogTraceFunction; - oper_name_analyzer.set_task_info("OperBoxFlagLV", "OperBoxNameOCR"); - oper_name_analyzer.set_replace(ocr_replace->replace_map, ocr_replace->replace_full); - const auto& all_opers = BattleData.get_all_oper_names(); - oper_name_analyzer.set_required(std::vector(all_opers.begin(), all_opers.end())); - if (!oper_name_analyzer.analyze()) { +#ifdef ASST_DEBUG + m_image_draw_oper = m_image_draw; +#endif // ASST_DEBUG + + if (!lv_flag_analyzer()) { return false; } - for (auto& opername : oper_name_analyzer.get_result()) { - OperBoxInfo oper_info; - oper_info.name = std::move(opername.text); - m_current_page_opers.emplace_back(oper_info); + if (!names_analyzer()) { + return false; + } + + if (!level_analyzer()) { + return false; + } + + if (!elite_analyzer()) { + return false; + } + + if (!potential_analyzer()) { + return false; + } + + sort_oper_horizontal(m_current_page_opers); + for (const auto& box : m_current_page_opers) { + Log.trace("operBox{", "\"rect_lv\":[", box.rect.x, box.rect.y, box.rect.width, box.rect.height, + "], \"id\": ", box.id, ", \"name\": ", box.name, ", \"elite\": ", box.elite, + ", \"level\": ", box.level, ", \"potential\": ", box.potential, "}"); + } #ifdef ASST_DEBUG - m_image_draw_oper = m_image; - cv::rectangle(m_image_draw_oper, make_rect(opername.rect), cv::Scalar(0, 255, 0), 2); - cv::putText(m_image_draw_oper, opername.text, cv::Point(opername.rect.x, opername.rect.y - 10), - cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0, 0, 255), 2); + m_image_draw = m_image_draw_oper; #endif + + return !m_current_page_opers.empty(); +} + +// 获取lv_flag,必须是第一步 +bool asst::OperBoxImageAnalyzer::lv_flag_analyzer() +{ + MultiMatchImageAnalyzer m_multi_match_image_analyzer(m_image); + m_multi_match_image_analyzer.set_task_info("OperBoxFlagLV"); + m_multi_match_image_analyzer.analyze(); + const auto& m_lv_flags = m_multi_match_image_analyzer.get_result(); + + if (m_lv_flags.empty()) { + return false; + } + + // 填入rect + for (const auto& lv_flag : m_lv_flags) { + OperBoxInfo box; + box.rect = lv_flag.rect; + box.own = true; + m_current_page_opers.emplace_back(box); + +#ifdef ASST_DEBUG + cv::rectangle(m_image_draw_oper, make_rect(lv_flag.rect), cv::Scalar(0, 255, 0), 1); +#endif // ASST_DEBUG } return true; } + +// 识别名字 +bool asst::OperBoxImageAnalyzer::names_analyzer() +{ + OcrWithPreprocessImageAnalyzer name_analyzer(m_image); + name_analyzer.set_task_info("OperBoxNameOCR"); + const Rect name_roi = Task.get("OperBoxNameOCR")->roi; + + const auto& ocr_replace = Task.get("CharsNameOcrReplace"); + + name_analyzer.set_replace(ocr_replace->replace_map, ocr_replace->replace_full); + + for (auto& box : m_current_page_opers) { + Rect roi = box.rect.move(name_roi); + if (roi.x + roi.width >= WindowWidthDefault) { + // 超出范围提示,框width该缩小。 + Log.error("name rect:[", roi.x, roi.y, roi.width, roi.height, " out of range "); + return false; + } + name_analyzer.set_roi(roi); + if (name_analyzer.analyze()) { + const auto& ocr_result = name_analyzer.get_result().front(); + std::string name = ocr_result.text; + box.id = BattleData.get_id(name); + box.name = std::move(name); +#ifdef ASST_DEBUG + cv::rectangle(m_image_draw_oper, make_rect(ocr_result.rect), cv::Scalar(0, 255, 0), 1); + cv::putText(m_image_draw_oper, box.id, cv::Point(roi.x, roi.y + 35), cv::FONT_HERSHEY_SIMPLEX, 0.5, + cv::Scalar(0, 0, 255), 2); +#endif // ASST_DEBUG + } + } + return true; +} + +// 识别等级 +bool asst::OperBoxImageAnalyzer::level_analyzer() +{ + OcrWithPreprocessImageAnalyzer level_analyzer(m_image); + level_analyzer.set_task_info("OperBoxLevelOCR"); + const Rect level_roi = Task.get("OperBoxLevelOCR")->roi; + + const auto& ocr_replace_num = Task.get("NumberOcrReplace"); + level_analyzer.set_replace(ocr_replace_num->replace_map, ocr_replace_num->replace_full); + + for (auto& box : m_current_page_opers) { + Rect roi = box.rect.move(level_roi); + if (roi.x < 0) { + // 等级在lv的左,lv的识别框x该右移 + Log.error("level rect:[", roi.x, roi.y, roi.width, roi.height, " out of range "); + return false; + } + level_analyzer.set_roi(roi); + if (level_analyzer.analyze()) { + const auto& ocr_result = level_analyzer.get_result().front(); + std::string level = ocr_result.text; + box.level = std::move(level_num(level)); +#ifdef ASST_DEBUG + cv::rectangle(m_image_draw_oper, make_rect(ocr_result.rect), cv::Scalar(0, 255, 0), 1); + cv::putText(m_image_draw_oper, level, cv::Point(roi.x, roi.y - 10), cv::FONT_HERSHEY_SIMPLEX, 0.5, + cv::Scalar(0, 0, 255), 2); +#endif // ASST_DEBUG + } + } + return true; +} +// 识别精英度 +bool asst::OperBoxImageAnalyzer::elite_analyzer() +{ + const std::string task_name = "OperBoxFlagElite"; + const Rect elite_roi = Task.get(task_name)->roi; + BestMatchImageAnalyzer elite_analyzer(m_image); + elite_analyzer.set_task_info(task_name); + elite_analyzer.append_templ("OperBoxFlagElite1.png"); + elite_analyzer.append_templ("OperBoxFlagElite2.png"); + for (auto& box : m_current_page_opers) { + Rect roi = box.rect.move(elite_roi); + if (roi.x < 0) { + // 等级在lv的左,lv的识别框x该右移 + Log.error("elite rect:[", roi.x, roi.y, roi.width, roi.height, " out of range "); + return false; + } + elite_analyzer.set_roi(roi); + if (!elite_analyzer.analyze()) { + box.elite = 0; + } + else { + const auto& elite_templ = elite_analyzer.get_result(); + std::string elite = elite_templ.name.substr(task_name.size(), 1); + box.elite = std::stoi(elite); +#ifdef ASST_DEBUG + cv::rectangle(m_image_draw_oper, make_rect(roi), cv::Scalar(0, 255, 0), 1); +#endif // ASST_DEBUG + } +#ifdef ASST_DEBUG + cv::putText(m_image_draw_oper, std::to_string(box.elite), cv::Point(roi.x, roi.y - 10), + cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0, 0, 255), 2); +#endif // ASST_DEBUG + } + return true; +} +// 识别潜能 +bool asst::OperBoxImageAnalyzer::potential_analyzer() +{ + const std::string task_name_p = "OperBoxPotential"; + const Rect potential_roi = Task.get(task_name_p)->roi; + BestMatchImageAnalyzer potential_analyzer(m_image); + potential_analyzer.set_task_info(task_name_p); + for (int i = 1; i < 6; i++) { + std::string potential_temp_name = task_name_p + std::to_string(i) + ".png"; + potential_analyzer.append_templ(potential_temp_name); + } + for (auto& box : m_current_page_opers) { + Rect roi = box.rect.move(potential_roi); + if (roi.x + roi.width < 0) { + // 潜能在lv的右,lv的识别框width该缩小 + Log.error("level rect:[", roi.x, roi.y, roi.width, roi.height, " out of range "); + return false; + } + potential_analyzer.set_roi(roi); + if (!potential_analyzer.analyze()) { + box.potential = 0; + } + else { + const auto& poten_templ = potential_analyzer.get_result(); + std::string potential = poten_templ.name.substr(task_name_p.size(), 1); + box.potential = std::stoi(potential); +#ifdef ASST_DEBUG + cv::rectangle(m_image_draw_oper, make_rect(roi), cv::Scalar(0, 255, 0), 1); +#endif // ASST_DEBUG + } +#ifdef ASST_DEBUG + cv::putText(m_image_draw_oper, std::to_string(box.potential), cv::Point(roi.x, roi.y - 10), + cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0, 0, 255), 2); +#endif // ASST_DEBUG + } + return true; +} + +void asst::OperBoxImageAnalyzer::sort_oper_horizontal(std::vector m_oper_boxs) +{ + // 按位置排个序 + ranges::sort(m_oper_boxs, [](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 2bb5e3c7bb..31a7c533ae 100644 --- a/src/MaaCore/Vision/Miscellaneous/OperBoxImageAnalyzer.h +++ b/src/MaaCore/Vision/Miscellaneous/OperBoxImageAnalyzer.h @@ -6,11 +6,13 @@ namespace asst { struct OperBoxInfo { - std::string name; std::string id; + std::string name; int level = 0; // 等级 int elite = 0; // 精英度 bool own = false; + int potential = 0; // 潜能 + Rect rect; }; class OperBoxImageAnalyzer final : public AbstractImageAnalyzer { @@ -21,9 +23,19 @@ namespace asst const auto& get_result() const noexcept { return m_current_page_opers; } private: - bool analyzer_opers(); - - std::vector m_current_page_opers; + int level_num(const std::string& level); + bool analyzer_oper_box(); + // 获取lv rect + bool lv_flag_analyzer(); + bool names_analyzer(); + bool level_analyzer(); + bool elite_analyzer(); + bool potential_analyzer(); + // 按位置排序 + // 1 - 2 - 3 + // 4 - 5 - 6 + void sort_oper_horizontal(std::vector m_oper_boxs); + std::vector m_current_page_opers; #ifdef ASST_DEBUG cv::Mat m_image_draw_oper; #endif