mirror of
https://github.com/MaaAssistantArknights/MaaAssistantArknights.git
synced 2026-07-15 17:30:27 +08:00
feat: 支持自定义 MatchTemplate 匹配方法
This commit is contained in:
@@ -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<std::string, MatchMethod> 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<MatchMethod, std::string> 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<std::string> templ_names; // 匹配模板图片文件名
|
||||
std::vector<double> templ_thresholds; // 模板匹配阈值
|
||||
std::pair<int, int> mask_range; // 掩码的二值化范围
|
||||
std::vector<MatchMethod> methods; // 匹配方法
|
||||
};
|
||||
using MatchTaskPtr = std::shared_ptr<MatchTaskInfo>;
|
||||
using MatchTaskConstPtr = std::shared_ptr<const MatchTaskInfo>;
|
||||
|
||||
@@ -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<MatchTaskInfo>();
|
||||
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<AlgorithmType, std::unordered_set<std::string>> 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,
|
||||
{
|
||||
|
||||
@@ -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<FirstT, SecondT> <- [first, second]
|
||||
template <typename FirstT, typename SecondT>
|
||||
requires(requires(const json::value& input, FirstT x, SecondT y) {
|
||||
|
||||
@@ -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<MatchMethod> 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);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ namespace asst
|
||||
std::vector<std::variant<std::string, cv::Mat>> templs;
|
||||
std::vector<double> templ_thres;
|
||||
std::pair<int, int> mask_range;
|
||||
std::vector<MatchMethod> 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<double> 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<MatchMethod> methods) noexcept;
|
||||
|
||||
protected:
|
||||
virtual void _set_roi(const Rect& roi) = 0;
|
||||
|
||||
@@ -49,7 +49,15 @@ Matcher::ResultOpt Matcher::analyze() const
|
||||
std::vector<Matcher::RawResult> Matcher::preproc_and_match(const cv::Mat& image, const MatcherConfig::Params& params)
|
||||
{
|
||||
std::vector<Matcher::RawResult> 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::RawResult> 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::RawResult> 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 });
|
||||
|
||||
Reference in New Issue
Block a user