feat: 支持多模板匹配,任一达到阈值即视为命中

MultiMatcher 暂不支持,仅使用第一个模板
This commit is contained in:
MistEO
2023-10-01 13:54:56 +08:00
parent 34e46e653e
commit 41d0c507da
13 changed files with 160 additions and 105 deletions

View File

@@ -11,79 +11,89 @@ using namespace asst;
Matcher::ResultOpt Matcher::analyze() const
{
const auto& [matched, templ, templ_name] = preproc_and_match(make_roi(m_image, m_roi), m_params);
const auto match_results = preproc_and_match(make_roi(m_image, m_roi), m_params);
if (matched.empty()) {
return std::nullopt;
}
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);
Rect rect(max_loc.x + m_roi.x, max_loc.y + m_roi.y, templ.cols, templ.rows);
if (std::isnan(max_val) || std::isinf(max_val)) {
max_val = 0;
}
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 (max_val < m_params.templ_thres) {
return std::nullopt;
}
// FIXME: 老接口太难重构了,先弄个这玩意兼容下,后续慢慢全删掉
m_result.rect = rect;
m_result.score = max_val;
return m_result;
}
Matcher::RawResult Matcher::preproc_and_match(const cv::Mat& image, const MatcherConfig::Params& params)
{
cv::Mat templ;
std::string templ_name;
if (std::holds_alternative<std::string>(params.templ)) {
templ_name = std::get<std::string>(params.templ);
templ = TemplResource::get_instance().get_templ(templ_name);
}
else if (std::holds_alternative<cv::Mat>(params.templ)) {
templ = std::get<cv::Mat>(params.templ);
}
else {
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);
for (size_t i = 0; i < match_results.size(); ++i) {
const auto& [matched, templ, templ_name] = match_results[i];
if (matched.empty()) {
continue;
}
cv::matchTemplate(image, 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);
Rect rect(max_loc.x + m_roi.x, max_loc.y + m_roi.y, templ.cols, templ.rows);
if (std::isnan(max_val) || std::isinf(max_val)) {
max_val = 0;
}
if (m_log_tracing && max_val > 0.5) { // 得分太低的肯定不对,没必要打印
Log.trace("match_templ |", templ_name, "score:", max_val, "rect:", rect, "roi:", m_roi);
}
double threshold = m_params.templ_thres[i];
if (max_val < threshold) {
continue;
}
// FIXME: 老接口太难重构了,先弄个这玩意兼容下,后续慢慢全删掉
m_result.rect = rect;
m_result.score = max_val;
return m_result;
}
return RawResult { .matched = matched, .templ = templ, .templ_name = templ_name };
return std::nullopt;
}
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) {
cv::Mat templ;
std::string templ_name;
if (std::holds_alternative<std::string>(ptempl)) {
templ_name = std::get<std::string>(ptempl);
templ = TemplResource::get_instance().get_templ(templ_name);
}
else if (std::holds_alternative<cv::Mat>(ptempl)) {
templ = std::get<cv::Mat>(ptempl);
}
else {
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);
}
results.emplace_back(RawResult { .matched = matched, .templ = templ, .templ_name = templ_name });
}
return results;
}