js: 识别队伍角色 (#1149)
* Add files via upload * Add files via upload * Add files via upload * Add files via upload * Add files via upload 更新了读取五位数的原石和4位数的纠缠 * Add files via upload 减少了空间占用 * Add files via upload * Delete repo/js/OCR读取当前原石以及纠缠之缘记录并发送通知 directory * Add files via upload * Add files via upload * Delete 识别队伍角色 directory * Delete repo/js/识别队伍角色 directory * Add files via upload * Delete repo/js/识别队伍角色/main.js * Delete repo/js/识别队伍角色 directory * Add files via upload * Add files via upload * Add files via upload
This commit is contained in:
1
repo/js/OCR读取主界面队伍/Character_log.txt
Normal file
1
repo/js/OCR读取主界面队伍/Character_log.txt
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
BIN
repo/js/OCR读取主界面队伍/assets/paimon_menu.png
Normal file
BIN
repo/js/OCR读取主界面队伍/assets/paimon_menu.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.3 KiB |
BIN
repo/js/OCR读取主界面队伍/assets/wanderer_icon.png
Normal file
BIN
repo/js/OCR读取主界面队伍/assets/wanderer_icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.5 KiB |
BIN
repo/js/OCR读取主界面队伍/assets/wanderer_icon_no_active.png
Normal file
BIN
repo/js/OCR读取主界面队伍/assets/wanderer_icon_no_active.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.2 KiB |
BIN
repo/js/OCR读取主界面队伍/assets/yin.png
Normal file
BIN
repo/js/OCR读取主界面队伍/assets/yin.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
BIN
repo/js/OCR读取主界面队伍/assets/yin2.png
Normal file
BIN
repo/js/OCR读取主界面队伍/assets/yin2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.8 KiB |
246
repo/js/OCR读取主界面队伍/main.js
Normal file
246
repo/js/OCR读取主界面队伍/main.js
Normal file
@@ -0,0 +1,246 @@
|
||||
// ===== 配置与工具函数 =====
|
||||
// 字符替换映射表:修正OCR识别的常见错误字符
|
||||
const replacementMap = {
|
||||
"监": "盐",
|
||||
"姐": "妲",
|
||||
"干": "千",
|
||||
"卵": "卯",
|
||||
"凌": "绫",
|
||||
"疑": "凝",
|
||||
"沙": "砂",
|
||||
"谣": "瑶",
|
||||
"萤": "荧",
|
||||
"霄": "宵"
|
||||
};
|
||||
|
||||
// 原神角色列表:用于OCR识别后的字符匹配
|
||||
const genshinImpactCharacters = [
|
||||
"荧", "空", "神里绫华", "琴", "丽莎", "芭芭拉", "凯亚", "迪卢克", "雷泽", "安柏", "温迪", "香菱",
|
||||
"北斗", "行秋", "魈", "凝光", "可莉", "钟离", "菲谢尔", "班尼特", "达达利亚", "诺艾尔", "七七",
|
||||
"重云", "甘雨", "阿贝多", "迪奥娜", "莫娜", "刻晴", "砂糖", "辛焱", "罗莎莉亚", "胡桃", "枫原万叶",
|
||||
"烟绯", "宵宫", "托马", "优菈", "雷电将军", "早柚", "珊瑚宫心海", "五郎", "九条裟罗", "荒泷一斗",
|
||||
"八重神子", "鹿野院平藏", "夜兰", "绮良良", "埃洛伊", "申鹤", "云堇", "久岐忍", "神里绫人", "柯莱",
|
||||
"多莉", "提纳里", "妮露", "赛诺", "坎蒂丝", "纳西妲", "莱依拉", "流浪者", "珐露珊", "瑶瑶",
|
||||
"艾尔海森", "迪希雅", "米卡", "卡维", "白术", "琳妮特", "林尼", "菲米尼", "莱欧斯利", "那维莱特",
|
||||
"夏洛蒂", "芙宁娜", "夏沃蕾", "娜维娅", "嘉明", "闲云", "千织", "希格雯", "阿蕾奇诺", "赛索斯",
|
||||
"克洛琳德", "艾梅莉埃", "卡齐娜", "基尼奇", "玛拉妮", "希诺宁", "恰斯卡", "欧洛伦", "玛薇卡",
|
||||
"茜特菈莉", "蓝砚", "梦见月瑞希", "伊安珊", "瓦雷莎", "爱可菲", "伊法", "丝柯克", "塔利雅"
|
||||
];
|
||||
|
||||
// 预加载所有模板图像:避免循环中重复读取文件,提升性能
|
||||
const templates = {
|
||||
wanderer: file.ReadImageMatSync("assets/wanderer_icon.png"), // 流浪者图标(活跃状态)
|
||||
wandererInactive: file.ReadImageMatSync("assets/wanderer_icon_no_active.png"), // 流浪者图标(非活跃状态)
|
||||
yin: file.ReadImageMatSync("assets/yin.png"), // 荧的图标模板1
|
||||
yin2: file.ReadImageMatSync("assets/yin2.png"), // 荧的图标模板2
|
||||
};
|
||||
|
||||
// 创建模板匹配对象的工具函数:统一参数格式,减少重复代码
|
||||
function createTemplateMatch(templateKey, x, y, width, height) {
|
||||
return RecognitionObject.TemplateMatch(
|
||||
templates[templateKey], // 使用预加载的模板图像
|
||||
x, y, // 识别区域左上角坐标
|
||||
width, height // 识别区域宽高
|
||||
);
|
||||
}
|
||||
|
||||
// 过滤非中文字符的工具函数:处理OCR结果中的无效字符
|
||||
function filterChineseChars(text) {
|
||||
return text?.replace(/[^\u4e00-\u9fa5]/g, '') || ''; // 保留中文字符,其他字符替换为空
|
||||
}
|
||||
|
||||
// 图像识别函数:返回布尔值表示是否识别成功
|
||||
async function recognizeImage(recognitionObject) {
|
||||
await sleep(500); // 延迟500ms,避免识别请求过于频繁
|
||||
try {
|
||||
const imageResult = captureGameRegion().find(recognitionObject);
|
||||
// 当识别结果存在且坐标不为(0,0)时(排除无效识别)
|
||||
return !!imageResult && imageResult.x !== 0 && imageResult.y !== 0;
|
||||
} catch (error) {
|
||||
log.error(`识别图像时发生异常: ${error.message}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// OCR文本识别函数:在指定区域重试识别直到超时
|
||||
async function recognizeTextInRegion(ocrRegion, timeout = 5000) {
|
||||
let startTime = Date.now(); // 记录开始时间
|
||||
let retryCount = 0; // 重试次数计数
|
||||
|
||||
// 循环直到超时
|
||||
while (Date.now() - startTime < timeout) {
|
||||
try {
|
||||
// 创建OCR识别区域对象
|
||||
const region = RecognitionObject.ocr(
|
||||
ocrRegion.x, ocrRegion.y, ocrRegion.width, ocrRegion.height
|
||||
);
|
||||
// 绘制识别区域红框(调试用)
|
||||
//captureGameRegion().find(region).DrawSelf("debug");
|
||||
|
||||
const ocrResult = captureGameRegion().find(region);
|
||||
if (ocrResult) {
|
||||
// 应用字符替换映射表修正识别结果
|
||||
let correctedText = ocrResult.text;
|
||||
for (const [wrongChar, correctChar] of Object.entries(replacementMap)) {
|
||||
correctedText = correctedText.replace(new RegExp(wrongChar, 'g'), correctChar);
|
||||
}
|
||||
return correctedText; // 返回修正后的文本
|
||||
} else {
|
||||
log.warn(`OCR 识别区域未找到内容`);
|
||||
return null;
|
||||
}
|
||||
} catch (error) {
|
||||
// 记录重试日志
|
||||
retryCount++;
|
||||
log.warn(`OCR 识别失败,正在进行第 ${retryCount} 次重试...`);
|
||||
}
|
||||
await sleep(500); // 每次重试间隔500ms
|
||||
}
|
||||
log.warn(`经过多次尝试,仍然无法在指定区域识别到文字`);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 定义识别对象
|
||||
const paimonMenuRo = RecognitionObject.TemplateMatch(
|
||||
file.ReadImageMatSync("assets/paimon_menu.png"),
|
||||
0,
|
||||
0,
|
||||
genshin.width / 3.0,
|
||||
genshin.width / 5.0
|
||||
);
|
||||
|
||||
// 判断是否在主界面的函数
|
||||
const isInMainUI = () => {
|
||||
let captureRegion = captureGameRegion();
|
||||
let res = captureRegion.Find(paimonMenuRo);
|
||||
return !res.isEmpty();
|
||||
};
|
||||
|
||||
// ===== 主流程逻辑 =====
|
||||
(async function () {
|
||||
// 初始化游戏界面(设置分辨率等)
|
||||
setGameMetrics(1920, 1080, 1);
|
||||
await genshin.returnMainUi(); // 返回游戏主界面
|
||||
await sleep(2000); // 等待界面响应
|
||||
|
||||
if (isInMainUI(paimonMenuRo)) {
|
||||
|
||||
|
||||
// 按下ESC键(可能用于打开菜单或其他操作)
|
||||
keyPress("ESCAPE");
|
||||
await sleep(1500); // 等待界面响应
|
||||
|
||||
// 识别玩家名称(主角名称)
|
||||
const playerNameRegion = { x: 270, y: 40, width: 400, height: 40 };
|
||||
let playerName = await recognizeTextInRegion(playerNameRegion);
|
||||
playerName = filterChineseChars(playerName); // 过滤非中文字符
|
||||
log.info(`识别到的主角为:${playerName}`);
|
||||
|
||||
// 返回主界面并等待加载
|
||||
await genshin.returnMainUi();
|
||||
await sleep(1000);
|
||||
|
||||
if (!isInMainUI(paimonMenuRo)) {
|
||||
log.error(`返回主界面失败`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 初始化角色识别结果数组
|
||||
const CharacterJudgment = [false, false, false, false]; // 记录每个角色是否识别成功
|
||||
const Character = []; // 存储识别到的角色名称
|
||||
|
||||
// 并行识别4个角色(使用Promise.all提升效率)
|
||||
const recognitionPromises = Array(4).fill().map(async (_, i) => {
|
||||
try {
|
||||
// 计算第i个角色的OCR识别区域(垂直排列,间隔100px)
|
||||
const ocrRegion = {
|
||||
x: 1620,
|
||||
y: 225 + i * 100,
|
||||
width: 160,
|
||||
height: 60
|
||||
};
|
||||
// 执行OCR识别并过滤字符
|
||||
const recognizedText = await recognizeTextInRegion(ocrRegion);
|
||||
const filteredText = filterChineseChars(recognizedText);
|
||||
//log.info(`[角色${i + 1}] 识别到的文本:${filteredText}`);
|
||||
|
||||
// 与角色列表进行匹配
|
||||
let isCharacterMatched = false;
|
||||
for (let j = 0; j < genshinImpactCharacters.length; j++) {
|
||||
if (genshinImpactCharacters[j] === filteredText) {
|
||||
// 匹配成功,记录结果
|
||||
CharacterJudgment[i] = true;
|
||||
log.info(`[角色${i + 1}] ${filteredText} 匹配成功`);
|
||||
Character[i] = filteredText;
|
||||
await sleep(500); // 等待后续操作
|
||||
isCharacterMatched = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 当OCR未匹配到角色时,尝试通过图像模板识别(散兵/旅行者)
|
||||
if (!isCharacterMatched) {
|
||||
const { x, y, width, height } = ocrRegion; // 解构区域坐标
|
||||
|
||||
// 创建4个模板匹配对象(流浪者和旅行者的不同状态)
|
||||
const wanderer1 = createTemplateMatch("wanderer", x + 100, y, width + 20, height + 20);
|
||||
const wanderer2 = createTemplateMatch("wandererInactive", x + 100, y - 10, width + 20, height + 30);
|
||||
const yin3 = createTemplateMatch("yin", x + 100, y - 10, width + 20, height + 30);
|
||||
const yin4 = createTemplateMatch("yin2", x + 100, y - 10, width + 20, height + 30);
|
||||
|
||||
// 执行图像识别并处理结果(添加catch避免异常中断)
|
||||
const result1 = await recognizeImage(wanderer1).catch(() => false);
|
||||
const result2 = await recognizeImage(wanderer2).catch(() => false);
|
||||
|
||||
if (result1 || result2) {
|
||||
// 识别到流浪者
|
||||
Character[i] = "流浪者";
|
||||
log.info(`[角色${i + 1}] 流浪者匹配成功`);
|
||||
CharacterJudgment[i] = true;
|
||||
} else {
|
||||
// 尝试识别旅行者(荧或空)
|
||||
const result3 = await recognizeImage(yin3).catch(() => false);
|
||||
const result4 = await recognizeImage(yin4).catch(() => false);
|
||||
if ((result3 || result4) && filteredText === playerName) {
|
||||
Character[i] = "荧";
|
||||
log.info(`[角色${i + 1}] 荧匹配成功`);
|
||||
CharacterJudgment[i] = true;
|
||||
} else if (filteredText === playerName) {
|
||||
Character[i] = "空";
|
||||
log.info(`[角色${i + 1}] 空匹配成功`);
|
||||
CharacterJudgment[i] = true;
|
||||
} else {
|
||||
log.error(`[角色${i + 1}] 散兵/旅行者识别失败`);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// 记录详细错误到日志文件
|
||||
const errorMsg = `[角色${i + 1}] 识别失败: ${error.message}`;
|
||||
file.WriteTextSync("Resources_log.txt", errorMsg + "\n", true);
|
||||
log.warn(errorMsg);
|
||||
}
|
||||
});
|
||||
|
||||
// 等待所有角色识别完成
|
||||
await Promise.all(recognitionPromises);
|
||||
|
||||
// 检查是否全部识别成功
|
||||
const allRecognized = CharacterJudgment.every(Boolean);
|
||||
if (allRecognized) {
|
||||
// 记录识别结果到日志文件
|
||||
const now = new Date();
|
||||
const logContent = `${now.toLocaleString()} —— 1: ${Character[0]} ——- 2:${Character[1]} ——- 3:${Character[2]} ———— 4:${Character[3]}\n`;
|
||||
const writeSuccess = file.WriteTextSync("Character_log.txt", logContent, true);
|
||||
log.info(writeSuccess ? "成功将队伍信息写入日志文件" : "写入日志文件失败");
|
||||
} else {
|
||||
log.error("未能完全识别队伍角色");
|
||||
}
|
||||
|
||||
// 等待后返回主界面
|
||||
await sleep(500);
|
||||
await genshin.returnMainUi();
|
||||
} else {
|
||||
log.error(`返回主界面失败,跳过识别`);
|
||||
}
|
||||
})();
|
||||
12
repo/js/OCR读取主界面队伍/manifest.json
Normal file
12
repo/js/OCR读取主界面队伍/manifest.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"manifest_version": 1,
|
||||
"name": "OCR主界面读取队伍 ",
|
||||
"version": "1.0",
|
||||
"description": "至少0.44.3版本。OCR识别队伍角色",
|
||||
"authors": [
|
||||
{
|
||||
"name": "未知_"
|
||||
}
|
||||
],
|
||||
"main": "main.js"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
|
||||
@@ -113,10 +113,10 @@ async function recognizeTextInRegion(ocrRegion, timeout = 5000) {
|
||||
// 如果识别到了“常驻祈愿”图标,则识别“原石以及纠缠之缘到数值”
|
||||
if (recognized) {
|
||||
//原石
|
||||
let ocrRegionYuanShi = { x: 1480, y: 30, width: 160, height: 36 }; // 设置对应的识别区域
|
||||
let ocrRegionYuanShi = { x: 1470, y: 25, width: 180, height: 46 }; // 设置对应的识别区域
|
||||
let recognizedText1 = await recognizeTextInRegion(ocrRegionYuanShi);
|
||||
//纠缠之缘
|
||||
let ocrRegionInterwinedFate = { x: 1660, y: 30, width: 120, height: 36 }; // 设置对应的识别区域
|
||||
let ocrRegionInterwinedFate = { x: 1650, y: 25, width: 140, height: 46 }; // 设置对应的识别区域
|
||||
let recognizedText2 = await recognizeTextInRegion(ocrRegionInterwinedFate);
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"manifest_version": 1,
|
||||
"name": "读取当前原石以及纠缠之缘记录并发送通知",
|
||||
"version": "1.2",
|
||||
"version": "1.4",
|
||||
"description": "至少0.44.3版本。OCR识别原石以及纠缠之缘数值输出到本地",
|
||||
"authors": [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user