diff --git a/src/MaaCore/Common/AsstTypes.h b/src/MaaCore/Common/AsstTypes.h index ecb50d8dd0..4918795de1 100644 --- a/src/MaaCore/Common/AsstTypes.h +++ b/src/MaaCore/Common/AsstTypes.h @@ -359,6 +359,38 @@ namespace asst } return "Invalid"; } + + enum class MatchMethod + { + Invalid = -1, + Ccoeff = 0, + CcoeffHSV, + }; + + inline MatchMethod get_match_method(std::string method_str) + { + utils::tolowers(method_str); + static const std::unordered_map method_map = { + { "ccoeff", MatchMethod::Ccoeff }, { "ccoeffhsv", MatchMethod::CcoeffHSV }, + }; + if (auto it = method_map.find(method_str); it != method_map.end()) { + return it->second; + } + return MatchMethod::Invalid; + } + + inline std::string enum_to_string(MatchMethod method) + { + static const std::unordered_map method_map = { + { MatchMethod::Invalid, "Invalid" }, + { MatchMethod::Ccoeff, "Ccoeff" }, + { MatchMethod::CcoeffHSV, "CcoeffHSV" }, + }; + if (auto it = method_map.find(method); it != method_map.end()) { + return it->second; + } + return "Invalid"; + } } // namespace asst namespace asst @@ -459,6 +491,7 @@ namespace asst std::vector templ_names; // 匹配模板图片文件名 std::vector templ_thresholds; // 模板匹配阈值 std::pair mask_range; // 掩码的二值化范围 + std::vector methods; // 匹配方法 }; using MatchTaskPtr = std::shared_ptr; using MatchTaskConstPtr = std::shared_ptr; diff --git a/src/MaaCore/Config/TaskData.cpp b/src/MaaCore/Config/TaskData.cpp index 8d80293e52..58e6d84f01 100644 --- a/src/MaaCore/Config/TaskData.cpp +++ b/src/MaaCore/Config/TaskData.cpp @@ -479,7 +479,45 @@ asst::TaskPtr asst::TaskData::generate_match_task_info(std::string_view name, co return nullptr; } + auto method_opt = task_json.find("method"); + if (!method_opt) { + match_task_info_ptr->methods = default_ptr->methods; + match_task_info_ptr->methods.resize(match_task_info_ptr->templ_names.size(), + default_ptr->methods.back()); + } + else if (method_opt->is_string()) { + // 单个数值时,所有模板都使用这个阈值 + match_task_info_ptr->methods.resize(match_task_info_ptr->templ_names.size(), + get_match_method(method_opt->as_string())); + } + else if (method_opt->is_array()) { + ranges::copy(method_opt->as_array() | + views::transform(&json::value::as_string) | + views::transform(&get_match_method), + std::back_inserter(match_task_info_ptr->methods)); + } + else { + Log.error("Invalid method type in task", name); + return nullptr; + } + + if (ranges::find(match_task_info_ptr->methods, MatchMethod::Invalid) != match_task_info_ptr->methods.end()) { + Log.error("Invalid method in task", name); + return nullptr; + } + + if (match_task_info_ptr->templ_names.size() != match_task_info_ptr->methods.size()) { + Log.error("Template count and method count not match in task", name); + return nullptr; + } + + if (match_task_info_ptr->templ_names.size() == 0 || match_task_info_ptr->methods.size() == 0) { + Log.error("Template or method is empty in task", name); + return nullptr; + } + utils::get_and_check_value_or(name, task_json, "maskRange", match_task_info_ptr->mask_range, default_ptr->mask_range); + return match_task_info_ptr; } @@ -675,6 +713,7 @@ asst::MatchTaskConstPtr asst::TaskData::_default_match_task_info() auto match_task_info_ptr = std::make_shared(); match_task_info_ptr->templ_names = { "__INVALID__" }; match_task_info_ptr->templ_thresholds = { TemplThresholdDefault }; + match_task_info_ptr->methods = { MatchMethod::Ccoeff }; return match_task_info_ptr; } @@ -726,18 +765,18 @@ bool asst::TaskData::syntax_check(std::string_view task_name, const json::value& static const std::unordered_map> allowed_key_under_algorithm = { { AlgorithmType::Invalid, { - "action", "algorithm", "baseTask", "cache", "exceededNext", "fullMatch", - "hash", "isAscii", "maskRange", "maxTimes", "next", "ocrReplace", - "onErrorNext", "postDelay", "preDelay", "rectMove", "reduceOtherTimes", "replaceFull", - "roi", "specialParams", "sub", "subErrorIgnored", "templThreshold", "template", - "text", "threshold", "withoutDet", + "action", "algorithm", "baseTask", "cache", "exceededNext", "fullMatch", + "hash", "isAscii", "maskRange", "maxTimes", "method", "next", + "ocrReplace", "onErrorNext", "postDelay", "preDelay", "rectMove", "reduceOtherTimes", + "replaceFull", "roi", "specialParams", "sub", "subErrorIgnored", "templThreshold", + "template", "text", "threshold", "withoutDet", } }, { AlgorithmType::MatchTemplate, { - "action", "algorithm", "baseTask", "cache", "exceededNext", "maskRange", - "maxTimes", "next", "onErrorNext", "postDelay", "preDelay", "rectMove", - "reduceOtherTimes", "roi", "sub", "subErrorIgnored", "templThreshold", "template", - "specialParams", + "action", "algorithm", "baseTask", "cache", "exceededNext", "maskRange", + "maxTimes", "method", "next", "onErrorNext", "postDelay", "preDelay", + "rectMove", "reduceOtherTimes", "roi", "sub", "subErrorIgnored", "templThreshold", + "template", "specialParams", } }, { AlgorithmType::OcrDetect, { diff --git a/src/MaaCore/Utils/JsonMisc.hpp b/src/MaaCore/Utils/JsonMisc.hpp index b5e0c1828a..8dd0ec2be8 100644 --- a/src/MaaCore/Utils/JsonMisc.hpp +++ b/src/MaaCore/Utils/JsonMisc.hpp @@ -39,6 +39,15 @@ namespace asst::utils return false; } + bool parse_json_as(const json::value& input, MatchMethod& output) + { + if (input.is_string()) { + output = get_match_method(input.as_string()); + return output != MatchMethod::Invalid; + } + return false; + } + // std::pair <- [first, second] template requires(requires(const json::value& input, FirstT x, SecondT y) { diff --git a/src/MaaCore/Vision/Config/MatcherConfig.cpp b/src/MaaCore/Vision/Config/MatcherConfig.cpp index 5b3e6b10c2..74fd720abb 100644 --- a/src/MaaCore/Vision/Config/MatcherConfig.cpp +++ b/src/MaaCore/Vision/Config/MatcherConfig.cpp @@ -46,12 +46,18 @@ void MatcherConfig::set_mask_range(int lower, int upper, bool mask_with_src, boo m_params.mask_with_close = mask_with_close; } +void MatcherConfig::set_method(std::vector methods) noexcept +{ + m_params.methods = std::move(methods); +} + void MatcherConfig::_set_task_info(MatchTaskInfo task_info) { m_params.templs.clear(); ranges::copy(task_info.templ_names, std::back_inserter(m_params.templs)); m_params.templ_thres = std::move(task_info.templ_thresholds); m_params.mask_range = std::move(task_info.mask_range); + m_params.methods = std::move(task_info.methods); _set_roi(task_info.roi); } diff --git a/src/MaaCore/Vision/Config/MatcherConfig.h b/src/MaaCore/Vision/Config/MatcherConfig.h index e735e7214f..eb25f9791f 100644 --- a/src/MaaCore/Vision/Config/MatcherConfig.h +++ b/src/MaaCore/Vision/Config/MatcherConfig.h @@ -15,6 +15,7 @@ namespace asst std::vector> templs; std::vector templ_thres; std::pair mask_range; + std::vector methods; bool mask_with_src = false; bool mask_with_close = false; }; @@ -33,6 +34,7 @@ namespace asst void set_threshold(double templ_thres) noexcept; void set_threshold(std::vector templ_thres) noexcept; void set_mask_range(int lower, int upper, bool mask_with_src = false, bool mask_with_close = false); + void set_method(std::vector methods) noexcept; protected: virtual void _set_roi(const Rect& roi) = 0; diff --git a/src/MaaCore/Vision/Matcher.cpp b/src/MaaCore/Vision/Matcher.cpp index 0464bfb263..eb63356035 100644 --- a/src/MaaCore/Vision/Matcher.cpp +++ b/src/MaaCore/Vision/Matcher.cpp @@ -49,7 +49,15 @@ Matcher::ResultOpt Matcher::analyze() const std::vector Matcher::preproc_and_match(const cv::Mat& image, const MatcherConfig::Params& params) { std::vector results; - for (auto& ptempl : params.templs) { + for (size_t i = 0; i != params.templs.size(); ++i) { + const auto& ptempl = params.templs[i]; + const auto& method = params.methods[i]; + + if (method == MatchMethod::Invalid) { + Log.error(__FUNCTION__, "| invalid method"); + return {}; + } + cv::Mat templ; std::string templ_name; @@ -80,8 +88,17 @@ std::vector Matcher::preproc_and_match(const cv::Mat& image, } cv::Mat matched; + if (method == MatchMethod::CcoeffHSV) { + cv::cvtColor(image, image, cv::COLOR_BGR2HSV); + cv::cvtColor(templ, templ, cv::COLOR_BGR2HSV); + } + int match_algorithm = cv::TM_CCOEFF_NORMED; + if (method == MatchMethod::Ccoeff || method == MatchMethod::CcoeffHSV) { + match_algorithm = cv::TM_CCOEFF_NORMED; + } + if (params.mask_range.first == 0 && params.mask_range.second == 0) { - cv::matchTemplate(image, templ, matched, cv::TM_CCOEFF_NORMED); + cv::matchTemplate(image, templ, matched, match_algorithm); } else { cv::Mat mask; @@ -91,7 +108,7 @@ std::vector Matcher::preproc_and_match(const cv::Mat& image, 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); + cv::matchTemplate(image, templ, matched, match_algorithm, mask); } results.emplace_back(RawResult { .matched = matched, .templ = templ, .templ_name = templ_name });