feat: 干员识别增加等级和精英度 (#4438)
* core返回json object * fix:冲突 * 等级ok * 等级和精英度 * core:等级,精英度识别 * 优化识别方式 * fix:ci * 添加潜能模板,fix ci * 潜能识别 * clang * fix:直接使用stoi * 修改识别完成条件 * 修复等级判断条件 * fix * 修改类继承,使用char_model,函数拆分 * 缩小名字识别框,添加替换字符。权宜之计 ( * 纠错,优化代码
@@ -2327,6 +2327,30 @@
|
||||
"[Uu]-[O0o]f{1,2}icial",
|
||||
"U-Official"
|
||||
],
|
||||
[
|
||||
"^4",
|
||||
""
|
||||
],
|
||||
[
|
||||
"^α",
|
||||
""
|
||||
],
|
||||
[
|
||||
"^-",
|
||||
""
|
||||
],
|
||||
[
|
||||
"—",
|
||||
""
|
||||
],
|
||||
[
|
||||
".*12F",
|
||||
"12F"
|
||||
],
|
||||
[
|
||||
"^_",
|
||||
""
|
||||
],
|
||||
[
|
||||
".*威龙陈",
|
||||
"假日威龙陈"
|
||||
@@ -9745,18 +9769,64 @@
|
||||
327
|
||||
]
|
||||
},
|
||||
"OperBoxFlagElite1": {
|
||||
"doc": "精英一 模板"
|
||||
},
|
||||
"OperBoxFlagElite2": {
|
||||
"doc": "精英二 模板"
|
||||
},
|
||||
"OperBoxFlagElite": {
|
||||
"doc": "偏移,识别精英度,扩大两个像素点",
|
||||
"template": "empty.png",
|
||||
"templThreshold": 0.9,
|
||||
"roi": [
|
||||
-7,
|
||||
-52,
|
||||
24,
|
||||
35
|
||||
]
|
||||
},
|
||||
"OperBoxPotential": {
|
||||
"doc": "偏移,识别潜能,扩大两个像素点",
|
||||
"template": "empty.png",
|
||||
"templThreshold": 0.85,
|
||||
"roi": [
|
||||
81,
|
||||
-29,
|
||||
24,
|
||||
24
|
||||
]
|
||||
},
|
||||
"OperBoxPotential1": {},
|
||||
"OperBoxPotential2": {},
|
||||
"OperBoxPotential3": {},
|
||||
"OperBoxPotential4": {},
|
||||
"OperBoxPotential5": {},
|
||||
"OperBoxNameOCR": {
|
||||
"algorithm": "OcrDetect",
|
||||
"fullMatch": true,
|
||||
"text": [],
|
||||
"roi": [
|
||||
-10,
|
||||
5,
|
||||
42,
|
||||
120,
|
||||
105,
|
||||
20
|
||||
],
|
||||
"postDelay": 100
|
||||
},
|
||||
"OperBoxLevelOCR": {
|
||||
"algorithm": "OcrDetect",
|
||||
"fullMatch": true,
|
||||
"isAscii": true,
|
||||
"text": [],
|
||||
"roi": [
|
||||
-13,
|
||||
7,
|
||||
37,
|
||||
23
|
||||
],
|
||||
"postDelay": 100
|
||||
},
|
||||
"RoguelikeRecruitSwipeMaxTime": {
|
||||
"algorithm": "JustReturn",
|
||||
"maxTimes_Doc": "这个参数是肉鸽招募时,向右划动的最大次数",
|
||||
|
||||
BIN
resource/template/OperBoxFlagElite1.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
resource/template/OperBoxFlagElite2.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
resource/template/OperBoxPotential1.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
resource/template/OperBoxPotential2.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
resource/template/OperBoxPotential3.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
resource/template/OperBoxPotential4.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
resource/template/OperBoxPotential5.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
@@ -23,7 +23,9 @@ bool asst::OperBoxRecognitionTask::swipe_and_analyze()
|
||||
{
|
||||
LogTraceFunction;
|
||||
std::string current_page_last_oper_name = "";
|
||||
std::string current_page_first_oper_name = "";
|
||||
m_own_opers.clear();
|
||||
|
||||
while (!need_exit()) {
|
||||
OperBoxImageAnalyzer analyzer(ctrler()->get_image());
|
||||
|
||||
@@ -33,10 +35,12 @@ bool asst::OperBoxRecognitionTask::swipe_and_analyze()
|
||||
break;
|
||||
}
|
||||
const auto& opers_result = analyzer.get_result();
|
||||
if (opers_result.back().name == current_page_last_oper_name) {
|
||||
if (opers_result.back().name == current_page_last_oper_name &&
|
||||
opers_result.front().name == current_page_first_oper_name) {
|
||||
break;
|
||||
}
|
||||
else {
|
||||
current_page_first_oper_name = opers_result.front().name;
|
||||
current_page_last_oper_name = opers_result.back().name;
|
||||
for (const auto& box_info : opers_result) {
|
||||
m_own_opers.emplace(box_info.name, box_info);
|
||||
@@ -72,13 +76,17 @@ void asst::OperBoxRecognitionTask::callback_analyze_result(bool done)
|
||||
{ "name", name },
|
||||
{ "own", own },
|
||||
});
|
||||
if (own) {
|
||||
own_opers.emplace_back(json::object {
|
||||
{ "id", BattleData.get_id(name) }, { "name", name }, { "own", own },
|
||||
// TODO
|
||||
// { "elite", 0 }, { "level", 0 },
|
||||
});
|
||||
}
|
||||
}
|
||||
for (const auto& [name,box_info] : m_own_opers) {
|
||||
own_opers.emplace_back(json::object {
|
||||
{ "id", box_info.id },
|
||||
{ "name", box_info.name },
|
||||
{ "own", box_info.own },
|
||||
{ "elite", box_info.elite },
|
||||
{ "level", box_info.level },
|
||||
{ "potential", box_info.potential },
|
||||
// TODO
|
||||
});
|
||||
}
|
||||
|
||||
callback(AsstMsg::SubTaskExtraInfo, info);
|
||||
|
||||
@@ -5,48 +5,244 @@
|
||||
#include "Config/Miscellaneous/BattleDataConfig.h"
|
||||
#include "Config/TaskData.h"
|
||||
#include "Utils/Logger.hpp"
|
||||
#include "Vision/BestMatchImageAnalyzer.h"
|
||||
#include "Vision/MatchImageAnalyzer.h"
|
||||
#include "Vision/OcrWithFlagTemplImageAnalyzer.h"
|
||||
#include "Vision/MultiMatchImageAnalyzer.h"
|
||||
#include "Vision/OcrWithPreprocessImageAnalyzer.h"
|
||||
|
||||
bool asst::OperBoxImageAnalyzer::analyze()
|
||||
{
|
||||
LogTraceFunction;
|
||||
|
||||
m_current_page_opers.clear();
|
||||
bool oper_box = analyzer_opers();
|
||||
#ifdef ASST_DEBUG
|
||||
m_image_draw = m_image_draw_oper;
|
||||
#endif
|
||||
bool oper_box = analyzer_oper_box();
|
||||
save_img(utils::path("debug") / utils::path("oper"));
|
||||
return oper_box;
|
||||
}
|
||||
|
||||
bool asst::OperBoxImageAnalyzer::analyzer_opers()
|
||||
int asst::OperBoxImageAnalyzer::level_num(const std::string &level)
|
||||
{
|
||||
std::vector<asst::TextRect> oper_names_result;
|
||||
const auto& ocr_replace = Task.get<OcrTaskInfo>("CharsNameOcrReplace");
|
||||
if (level.empty() || !ranges::all_of(level, [](const char& c) -> bool { return std::isdigit(c); })) {
|
||||
return 1;
|
||||
}
|
||||
return std::stoi(level);
|
||||
}
|
||||
|
||||
OcrWithFlagTemplImageAnalyzer oper_name_analyzer(m_image);
|
||||
bool asst::OperBoxImageAnalyzer::analyzer_oper_box()
|
||||
{
|
||||
LogTraceFunction;
|
||||
|
||||
oper_name_analyzer.set_task_info("OperBoxFlagLV", "OperBoxNameOCR");
|
||||
oper_name_analyzer.set_replace(ocr_replace->replace_map, ocr_replace->replace_full);
|
||||
const auto& all_opers = BattleData.get_all_oper_names();
|
||||
oper_name_analyzer.set_required(std::vector(all_opers.begin(), all_opers.end()));
|
||||
if (!oper_name_analyzer.analyze()) {
|
||||
#ifdef ASST_DEBUG
|
||||
m_image_draw_oper = m_image_draw;
|
||||
#endif // ASST_DEBUG
|
||||
|
||||
if (!lv_flag_analyzer()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto& opername : oper_name_analyzer.get_result()) {
|
||||
OperBoxInfo oper_info;
|
||||
oper_info.name = std::move(opername.text);
|
||||
m_current_page_opers.emplace_back(oper_info);
|
||||
if (!names_analyzer()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!level_analyzer()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!elite_analyzer()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!potential_analyzer()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
sort_oper_horizontal(m_current_page_opers);
|
||||
for (const auto& box : m_current_page_opers) {
|
||||
Log.trace("operBox{", "\"rect_lv\":[", box.rect.x, box.rect.y, box.rect.width, box.rect.height,
|
||||
"], \"id\": ", box.id, ", \"name\": ", box.name, ", \"elite\": ", box.elite,
|
||||
", \"level\": ", box.level, ", \"potential\": ", box.potential, "}");
|
||||
}
|
||||
|
||||
#ifdef ASST_DEBUG
|
||||
m_image_draw_oper = m_image;
|
||||
cv::rectangle(m_image_draw_oper, make_rect<cv::Rect>(opername.rect), cv::Scalar(0, 255, 0), 2);
|
||||
cv::putText(m_image_draw_oper, opername.text, cv::Point(opername.rect.x, opername.rect.y - 10),
|
||||
cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0, 0, 255), 2);
|
||||
m_image_draw = m_image_draw_oper;
|
||||
#endif
|
||||
|
||||
return !m_current_page_opers.empty();
|
||||
}
|
||||
|
||||
// 获取lv_flag,必须是第一步
|
||||
bool asst::OperBoxImageAnalyzer::lv_flag_analyzer()
|
||||
{
|
||||
MultiMatchImageAnalyzer m_multi_match_image_analyzer(m_image);
|
||||
m_multi_match_image_analyzer.set_task_info("OperBoxFlagLV");
|
||||
m_multi_match_image_analyzer.analyze();
|
||||
const auto& m_lv_flags = m_multi_match_image_analyzer.get_result();
|
||||
|
||||
if (m_lv_flags.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 填入rect
|
||||
for (const auto& lv_flag : m_lv_flags) {
|
||||
OperBoxInfo box;
|
||||
box.rect = lv_flag.rect;
|
||||
box.own = true;
|
||||
m_current_page_opers.emplace_back(box);
|
||||
|
||||
#ifdef ASST_DEBUG
|
||||
cv::rectangle(m_image_draw_oper, make_rect<cv::Rect>(lv_flag.rect), cv::Scalar(0, 255, 0), 1);
|
||||
#endif // ASST_DEBUG
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 识别名字
|
||||
bool asst::OperBoxImageAnalyzer::names_analyzer()
|
||||
{
|
||||
OcrWithPreprocessImageAnalyzer name_analyzer(m_image);
|
||||
name_analyzer.set_task_info("OperBoxNameOCR");
|
||||
const Rect name_roi = Task.get<OcrTaskInfo>("OperBoxNameOCR")->roi;
|
||||
|
||||
const auto& ocr_replace = Task.get<OcrTaskInfo>("CharsNameOcrReplace");
|
||||
|
||||
name_analyzer.set_replace(ocr_replace->replace_map, ocr_replace->replace_full);
|
||||
|
||||
for (auto& box : m_current_page_opers) {
|
||||
Rect roi = box.rect.move(name_roi);
|
||||
if (roi.x + roi.width >= WindowWidthDefault) {
|
||||
// 超出范围提示,框width该缩小。
|
||||
Log.error("name rect:[", roi.x, roi.y, roi.width, roi.height, " out of range ");
|
||||
return false;
|
||||
}
|
||||
name_analyzer.set_roi(roi);
|
||||
if (name_analyzer.analyze()) {
|
||||
const auto& ocr_result = name_analyzer.get_result().front();
|
||||
std::string name = ocr_result.text;
|
||||
box.id = BattleData.get_id(name);
|
||||
box.name = std::move(name);
|
||||
#ifdef ASST_DEBUG
|
||||
cv::rectangle(m_image_draw_oper, make_rect<cv::Rect>(ocr_result.rect), cv::Scalar(0, 255, 0), 1);
|
||||
cv::putText(m_image_draw_oper, box.id, cv::Point(roi.x, roi.y + 35), cv::FONT_HERSHEY_SIMPLEX, 0.5,
|
||||
cv::Scalar(0, 0, 255), 2);
|
||||
#endif // ASST_DEBUG
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 识别等级
|
||||
bool asst::OperBoxImageAnalyzer::level_analyzer()
|
||||
{
|
||||
OcrWithPreprocessImageAnalyzer level_analyzer(m_image);
|
||||
level_analyzer.set_task_info("OperBoxLevelOCR");
|
||||
const Rect level_roi = Task.get<OcrTaskInfo>("OperBoxLevelOCR")->roi;
|
||||
|
||||
const auto& ocr_replace_num = Task.get<OcrTaskInfo>("NumberOcrReplace");
|
||||
level_analyzer.set_replace(ocr_replace_num->replace_map, ocr_replace_num->replace_full);
|
||||
|
||||
for (auto& box : m_current_page_opers) {
|
||||
Rect roi = box.rect.move(level_roi);
|
||||
if (roi.x < 0) {
|
||||
// 等级在lv的左,lv的识别框x该右移
|
||||
Log.error("level rect:[", roi.x, roi.y, roi.width, roi.height, " out of range ");
|
||||
return false;
|
||||
}
|
||||
level_analyzer.set_roi(roi);
|
||||
if (level_analyzer.analyze()) {
|
||||
const auto& ocr_result = level_analyzer.get_result().front();
|
||||
std::string level = ocr_result.text;
|
||||
box.level = std::move(level_num(level));
|
||||
#ifdef ASST_DEBUG
|
||||
cv::rectangle(m_image_draw_oper, make_rect<cv::Rect>(ocr_result.rect), cv::Scalar(0, 255, 0), 1);
|
||||
cv::putText(m_image_draw_oper, level, cv::Point(roi.x, roi.y - 10), cv::FONT_HERSHEY_SIMPLEX, 0.5,
|
||||
cv::Scalar(0, 0, 255), 2);
|
||||
#endif // ASST_DEBUG
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// 识别精英度
|
||||
bool asst::OperBoxImageAnalyzer::elite_analyzer()
|
||||
{
|
||||
const std::string task_name = "OperBoxFlagElite";
|
||||
const Rect elite_roi = Task.get(task_name)->roi;
|
||||
BestMatchImageAnalyzer elite_analyzer(m_image);
|
||||
elite_analyzer.set_task_info(task_name);
|
||||
elite_analyzer.append_templ("OperBoxFlagElite1.png");
|
||||
elite_analyzer.append_templ("OperBoxFlagElite2.png");
|
||||
for (auto& box : m_current_page_opers) {
|
||||
Rect roi = box.rect.move(elite_roi);
|
||||
if (roi.x < 0) {
|
||||
// 等级在lv的左,lv的识别框x该右移
|
||||
Log.error("elite rect:[", roi.x, roi.y, roi.width, roi.height, " out of range ");
|
||||
return false;
|
||||
}
|
||||
elite_analyzer.set_roi(roi);
|
||||
if (!elite_analyzer.analyze()) {
|
||||
box.elite = 0;
|
||||
}
|
||||
else {
|
||||
const auto& elite_templ = elite_analyzer.get_result();
|
||||
std::string elite = elite_templ.name.substr(task_name.size(), 1);
|
||||
box.elite = std::stoi(elite);
|
||||
#ifdef ASST_DEBUG
|
||||
cv::rectangle(m_image_draw_oper, make_rect<cv::Rect>(roi), cv::Scalar(0, 255, 0), 1);
|
||||
#endif // ASST_DEBUG
|
||||
}
|
||||
#ifdef ASST_DEBUG
|
||||
cv::putText(m_image_draw_oper, std::to_string(box.elite), cv::Point(roi.x, roi.y - 10),
|
||||
cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0, 0, 255), 2);
|
||||
#endif // ASST_DEBUG
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// 识别潜能
|
||||
bool asst::OperBoxImageAnalyzer::potential_analyzer()
|
||||
{
|
||||
const std::string task_name_p = "OperBoxPotential";
|
||||
const Rect potential_roi = Task.get(task_name_p)->roi;
|
||||
BestMatchImageAnalyzer potential_analyzer(m_image);
|
||||
potential_analyzer.set_task_info(task_name_p);
|
||||
for (int i = 1; i < 6; i++) {
|
||||
std::string potential_temp_name = task_name_p + std::to_string(i) + ".png";
|
||||
potential_analyzer.append_templ(potential_temp_name);
|
||||
}
|
||||
for (auto& box : m_current_page_opers) {
|
||||
Rect roi = box.rect.move(potential_roi);
|
||||
if (roi.x + roi.width < 0) {
|
||||
// 潜能在lv的右,lv的识别框width该缩小
|
||||
Log.error("level rect:[", roi.x, roi.y, roi.width, roi.height, " out of range ");
|
||||
return false;
|
||||
}
|
||||
potential_analyzer.set_roi(roi);
|
||||
if (!potential_analyzer.analyze()) {
|
||||
box.potential = 0;
|
||||
}
|
||||
else {
|
||||
const auto& poten_templ = potential_analyzer.get_result();
|
||||
std::string potential = poten_templ.name.substr(task_name_p.size(), 1);
|
||||
box.potential = std::stoi(potential);
|
||||
#ifdef ASST_DEBUG
|
||||
cv::rectangle(m_image_draw_oper, make_rect<cv::Rect>(roi), cv::Scalar(0, 255, 0), 1);
|
||||
#endif // ASST_DEBUG
|
||||
}
|
||||
#ifdef ASST_DEBUG
|
||||
cv::putText(m_image_draw_oper, std::to_string(box.potential), cv::Point(roi.x, roi.y - 10),
|
||||
cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0, 0, 255), 2);
|
||||
#endif // ASST_DEBUG
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void asst::OperBoxImageAnalyzer::sort_oper_horizontal(std::vector<asst::OperBoxInfo> m_oper_boxs)
|
||||
{
|
||||
// 按位置排个序
|
||||
ranges::sort(m_oper_boxs, [](const OperBoxInfo& lhs, const OperBoxInfo& rhs) -> bool {
|
||||
if (std::abs(lhs.rect.y - rhs.rect.y) < 5) { // y差距较小则理解为是同一排的,按x排序
|
||||
return lhs.rect.x < rhs.rect.x;
|
||||
}
|
||||
else {
|
||||
return lhs.rect.y < rhs.rect.y;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -6,11 +6,13 @@ namespace asst
|
||||
{
|
||||
struct OperBoxInfo
|
||||
{
|
||||
std::string name;
|
||||
std::string id;
|
||||
std::string name;
|
||||
int level = 0; // 等级
|
||||
int elite = 0; // 精英度
|
||||
bool own = false;
|
||||
int potential = 0; // 潜能
|
||||
Rect rect;
|
||||
};
|
||||
class OperBoxImageAnalyzer final : public AbstractImageAnalyzer
|
||||
{
|
||||
@@ -21,9 +23,19 @@ namespace asst
|
||||
const auto& get_result() const noexcept { return m_current_page_opers; }
|
||||
|
||||
private:
|
||||
bool analyzer_opers();
|
||||
|
||||
std::vector<OperBoxInfo> m_current_page_opers;
|
||||
int level_num(const std::string& level);
|
||||
bool analyzer_oper_box();
|
||||
// 获取lv rect
|
||||
bool lv_flag_analyzer();
|
||||
bool names_analyzer();
|
||||
bool level_analyzer();
|
||||
bool elite_analyzer();
|
||||
bool potential_analyzer();
|
||||
// 按位置排序
|
||||
// 1 - 2 - 3
|
||||
// 4 - 5 - 6
|
||||
void sort_oper_horizontal(std::vector<asst::OperBoxInfo> m_oper_boxs);
|
||||
std::vector<asst::OperBoxInfo> m_current_page_opers;
|
||||
#ifdef ASST_DEBUG
|
||||
cv::Mat m_image_draw_oper;
|
||||
#endif
|
||||
|
||||