feat!: 增加图像匹配算法 RGBCount, HSVCount (#9795)

新增基于找色的图像匹配算法(

- RGBCount
   先将待匹配区域和模板图片依据 maskRange 二值化,以 F1-score 为指标计算 RGB 颜色空间内的相似度
- HSVCount
   类似 RGBCount,颜色空间换为 HSV
This commit is contained in:
zzyyyl
2024-07-22 22:31:10 +08:00
committed by GitHub
5 changed files with 90 additions and 12 deletions

View File

@@ -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 */

View File

@@ -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"

View File

@@ -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;

View File

@@ -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);
}
}
}

View File

@@ -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;