mirror of
https://github.com/MaaAssistantArknights/MaaAssistantArknights.git
synced 2026-07-20 10:57:45 +08:00
feat!: 增加图像匹配算法 RGBCount, HSVCount (#9795)
新增基于找色的图像匹配算法( - RGBCount 先将待匹配区域和模板图片依据 maskRange 二值化,以 F1-score 为指标计算 RGB 颜色空间内的相似度 - HSVCount 类似 RGBCount,颜色空间换为 HSV
This commit is contained in:
@@ -110,6 +110,9 @@ icon: material-symbols:task
|
||||
// 不填写时默认为 Ccoeff
|
||||
// - Ccoeff: 对应 cv::TM_CCOEFF_NORMED
|
||||
// - CcoeffHSV: 转换为 HSV 颜色空间后再进行 CCOEFF
|
||||
// - RGBCount: 先将待匹配区域和模板图片依据 maskRange 二值化,
|
||||
// 以 F1-score 为指标计算 RGB 颜色空间内的相似度
|
||||
// - HSVCount: 类似 RGBCount,颜色空间换为 HSV
|
||||
|
||||
/* 以下字段仅当 algorithm 为 OcrDetect 时有效 */
|
||||
|
||||
|
||||
@@ -9887,6 +9887,13 @@
|
||||
},
|
||||
"Sarkaz@Roguelike@StageBoskyPassageEnter": {
|
||||
"baseTask": "Sarkaz@Roguelike@StageEnter",
|
||||
"method": "HSVCount",
|
||||
"maskRange": [
|
||||
[
|
||||
[137, 150, 40],
|
||||
[138, 180, 180]
|
||||
]
|
||||
],
|
||||
"next": [
|
||||
"Sarkaz@Roguelike@StageBoskyPassageEnter",
|
||||
"Sarkaz@Roguelike@StageEncounterJudgeClick"
|
||||
@@ -9937,7 +9944,15 @@
|
||||
"Sarkaz@Roguelike@Stages#next"
|
||||
]
|
||||
},
|
||||
"Sarkaz@Roguelike@StageCombatDpsEnter": {},
|
||||
"Sarkaz@Roguelike@StageCombatDpsEnter": {
|
||||
"method": "HSVCount",
|
||||
"maskRange": [
|
||||
[
|
||||
[130, 130, 40],
|
||||
[131, 180, 180]
|
||||
]
|
||||
]
|
||||
},
|
||||
"Sarkaz@Roguelike@StageConfrontation": {
|
||||
"Doc": "狭路相逢",
|
||||
"baseTask": "Sarkaz@Roguelike@Stage",
|
||||
@@ -10185,6 +10200,13 @@
|
||||
]
|
||||
},
|
||||
"Sarkaz@Roguelike@StageSafeHouseEnter": {
|
||||
"method": "HSVCount",
|
||||
"maskRange": [
|
||||
[
|
||||
[106, 100, 50],
|
||||
[107, 160, 180]
|
||||
]
|
||||
],
|
||||
"next": [
|
||||
"Sarkaz@Roguelike@StageSafeHouseEnter",
|
||||
"Sarkaz@Roguelike@StageEncounterJudgeClick"
|
||||
|
||||
@@ -362,6 +362,8 @@ namespace asst
|
||||
Invalid = -1,
|
||||
Ccoeff = 0,
|
||||
CcoeffHSV,
|
||||
RGBCount,
|
||||
HSVCount,
|
||||
};
|
||||
|
||||
inline MatchMethod get_match_method(std::string method_str)
|
||||
@@ -369,6 +371,7 @@ namespace asst
|
||||
utils::tolowers(method_str);
|
||||
static const std::unordered_map<std::string, MatchMethod> method_map = {
|
||||
{ "ccoeff", MatchMethod::Ccoeff }, { "ccoeffhsv", MatchMethod::CcoeffHSV },
|
||||
{ "rgbcount", MatchMethod::RGBCount }, { "hsvcount", MatchMethod::HSVCount },
|
||||
};
|
||||
if (auto it = method_map.find(method_str); it != method_map.end()) {
|
||||
return it->second;
|
||||
@@ -382,6 +385,8 @@ namespace asst
|
||||
{ MatchMethod::Invalid, "Invalid" },
|
||||
{ MatchMethod::Ccoeff, "Ccoeff" },
|
||||
{ MatchMethod::CcoeffHSV, "CcoeffHSV" },
|
||||
{ MatchMethod::RGBCount, "RGBCount" },
|
||||
{ MatchMethod::HSVCount, "HSVCount" },
|
||||
};
|
||||
if (auto it = method_map.find(method); it != method_map.end()) {
|
||||
return it->second;
|
||||
|
||||
@@ -17,7 +17,7 @@ asst::DebugTask::DebugTask(const AsstCallback& callback, Assistant* inst) : Inte
|
||||
|
||||
bool asst::DebugTask::run()
|
||||
{
|
||||
test_drops();
|
||||
test_match_template();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -79,14 +79,40 @@ void asst::DebugTask::test_match_template()
|
||||
cv::Mat image = asst::imread(utils::path("../../test/match_template/1.png"));
|
||||
cv::Mat resized;
|
||||
cv::resize(image, resized, cv::Size(1280, 720), 0, 0, cv::INTER_AREA);
|
||||
Matcher match_analyzer(resized, Rect(0, 0, 1280, 720));
|
||||
const auto& task_ptr = Task.get("Sami@Roguelike@StageEncounterOptionLeave");
|
||||
const auto match_task_ptr = std::dynamic_pointer_cast<MatchTaskInfo>(task_ptr);
|
||||
match_analyzer.set_task_info(match_task_ptr);
|
||||
match_analyzer.analyze();
|
||||
const auto& result_opt = match_analyzer.analyze();
|
||||
if (result_opt) {
|
||||
const auto& result = result_opt.value().to_string();
|
||||
Log.info(__FUNCTION__, result);
|
||||
|
||||
{
|
||||
Matcher match_analyzer(resized, Rect(0, 0, 1280, 720));
|
||||
const auto& task_ptr = Task.get("Sarkaz@Roguelike@StageSafeHouseEnter");
|
||||
const auto match_task_ptr = std::dynamic_pointer_cast<MatchTaskInfo>(task_ptr);
|
||||
match_analyzer.set_task_info(match_task_ptr);
|
||||
const auto& result_opt = match_analyzer.analyze();
|
||||
if (result_opt) {
|
||||
const auto& result = result_opt.value().to_string();
|
||||
Log.info(__FUNCTION__, result);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
Matcher match_analyzer(resized, Rect(0, 0, 1280, 720));
|
||||
const auto& task_ptr = Task.get("Sarkaz@Roguelike@StageBoskyPassageEnter");
|
||||
const auto match_task_ptr = std::dynamic_pointer_cast<MatchTaskInfo>(task_ptr);
|
||||
match_analyzer.set_task_info(match_task_ptr);
|
||||
const auto& result_opt = match_analyzer.analyze();
|
||||
if (result_opt) {
|
||||
const auto& result = result_opt.value().to_string();
|
||||
Log.info(__FUNCTION__, result);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
Matcher match_analyzer(resized, Rect(0, 0, 1280, 720));
|
||||
const auto& task_ptr = Task.get("Sarkaz@Roguelike@StageCombatDpsEnter");
|
||||
const auto match_task_ptr = std::dynamic_pointer_cast<MatchTaskInfo>(task_ptr);
|
||||
match_analyzer.set_task_info(match_task_ptr);
|
||||
const auto& result_opt = match_analyzer.analyze();
|
||||
if (result_opt) {
|
||||
const auto& result = result_opt.value().to_string();
|
||||
Log.info(__FUNCTION__, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ std::vector<Matcher::RawResult> Matcher::preproc_and_match(const cv::Mat& image,
|
||||
cv::Mat matched;
|
||||
cv::Mat image_for_match;
|
||||
cv::Mat templ_for_match;
|
||||
if (method == MatchMethod::CcoeffHSV) {
|
||||
if (method == MatchMethod::CcoeffHSV || method == MatchMethod::HSVCount) {
|
||||
cv::cvtColor(image, image_for_match, cv::COLOR_BGR2HSV);
|
||||
cv::cvtColor(templ, templ_for_match, cv::COLOR_BGR2HSV);
|
||||
}
|
||||
@@ -152,6 +152,28 @@ std::vector<Matcher::RawResult> Matcher::preproc_and_match(const cv::Mat& image,
|
||||
cv::matchTemplate(image_for_match, templ_for_match, matched, match_algorithm, mask_opt.value());
|
||||
}
|
||||
}
|
||||
else if (method == MatchMethod::RGBCount || method == MatchMethod::HSVCount) {
|
||||
// 待匹配图像与模板中指定颜色的像素数量比值,越接近1越相似
|
||||
auto templ_active_opt = calc_mask(templ_for_match, false);
|
||||
auto image_active_opt = calc_mask(image_for_match, false);
|
||||
if (!image_active_opt || !templ_active_opt) [[unlikely]] {
|
||||
return {};
|
||||
}
|
||||
const auto& templ_active = templ_active_opt.value();
|
||||
const auto& image_active = image_active_opt.value();
|
||||
cv::Mat zero = cv::Mat::zeros(templ_active.size(), CV_8U);
|
||||
cv::threshold(image_active, image_active, 1, 1, cv::THRESH_BINARY);
|
||||
// 把 SQDIFF 当 count 用,计算 image_active 在 templ_active 形状内的像素数量
|
||||
cv::Mat tp, fp;
|
||||
int tp_fn = cv::countNonZero(templ_active);
|
||||
cv::matchTemplate(image_active, zero, tp, cv::TM_SQDIFF, templ_active);
|
||||
tp.convertTo(tp, CV_32S);
|
||||
cv::Mat templ_inactive;
|
||||
cv::bitwise_not(templ_active, templ_inactive);
|
||||
cv::matchTemplate(image_active, zero, fp, cv::TM_SQDIFF, templ_inactive);
|
||||
fp.convertTo(fp, CV_32S);
|
||||
cv::divide(2 * tp, tp + fp + tp_fn, matched, 1, CV_32F); // matched = f1 score
|
||||
}
|
||||
results.emplace_back(RawResult { .matched = matched, .templ = templ, .templ_name = templ_name });
|
||||
}
|
||||
return results;
|
||||
|
||||
Reference in New Issue
Block a user