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:
1xygyty1
2025-07-10 09:31:37 +08:00
committed by GitHub
parent 9b7fc9b141
commit f9bf443a3b
11 changed files with 263 additions and 3 deletions

View File

@@ -0,0 +1 @@

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View 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(`返回主界面失败,跳过识别`);
}
})();

View File

@@ -0,0 +1,12 @@
{
"manifest_version": 1,
"name": "OCR主界面读取队伍 ",
"version": "1.0",
"description": "至少0.44.3版本。OCR识别队伍角色",
"authors": [
{
"name": "未知_"
}
],
"main": "main.js"
}

View File

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

View File

@@ -1,7 +1,7 @@
{
"manifest_version": 1,
"name": "读取当前原石以及纠缠之缘记录并发送通知",
"version": "1.2",
"version": "1.4",
"description": "至少0.44.3版本。OCR识别原石以及纠缠之缘数值输出到本地",
"authors": [
{