Files
better-genshin-impact/BetterGenshinImpact/Core/Config/OtherConfig.cs
Takaranoao e9d11f7267 文本识别的模糊匹配功能 (#2799)
* chore: add AGENTS.md to .gitignore

* feat(config): 新增 AllowDuplicateChar OCR配置项

* refactor(ocr): Rec 暴露protected成员、提取RunInference、支持AllowDuplicateChar

* feat(ocr): 打通 AllowDuplicateChar 参数链 PaddleOcrService → Rec

* feat(ocr): OcrUtils 新增 CreateLabelDict/CreateWeights 工具方法

* feat(helpers): 新增 LruCache 缓存工具类

* feat(ocr): 新增 RecMatch DP模糊匹配识别器

* test(helpers): 新增 LruCache 单元测试

* test(ocr): 新增 RecMatch.GetTarget / CreateLabelDict 单元测试

* fix(ocr): 修复 RecMatch 中权重矩阵乘法的使用方式

* refactor(ocr): 合并 RecMatch 到 Rec,提取可测试静态方法,补充单元测试

将 RecMatch 子类合并到 Rec 中,消除继承关系和重复的批处理逻辑(提取 RunBatch<T>)。
将 GetTarget 核心逻辑和 GetMaxScoreDP 提取为 OcrUtils 静态方法以便独立测试。
重命名测试文件并新增 16 个单元测试覆盖 MapStringToLabelIndices、GetMaxScoreDP、CreateWeights。

* feat(ocr): 将 Rec.RunMatch 暴露给 JS 引擎和内部 C# 代码

新增 IOcrMatchService 接口,提供基于 DP 模糊匹配的 OcrMatch/OcrMatchDirect 方法,
返回 0~1 置信度分数。PaddleOcrService 实现该接口,OcrFactory.PaddleMatch 保证
非 null 返回(引擎不支持时自动回退到普通 OCR + 编辑距离字符串比较)。
BvPage 新增 OcrMatch/WaitForOcrMatch 供 JS 脚本使用,阈值可通过配置调整。

* feat(ui): 为 OCR 配置添加允许重复字符和模糊匹配阈值的设置项

在通用设置页 OCR 配置区域新增两个控件:
- 允许连续重复字符(AllowDuplicateChar)开关
- OCR模糊匹配阈值(OcrMatchDefaultThreshold)输入框

* fix: 修复 PR #2799 代码审查中发现的多项问题

- 修复 Rec.cs 空文本时 score/sb.Length 除零产生 NaN
- 修复 BvPage.cs rect==default 时同一对象被双重 Dispose
- 移除 Rec.cs Finalizer 避免 GC 线程加锁死锁
- 移除 CacheHelper WeakKey 无效功能,简化为直接 Dictionary 查找
- 添加 weights 数组长度与模型输出维度校验
- 修复 CreateLabelDict 空格标签索引冲突
- 修复 GetMaxScoreDP availableCount=0 除零
- 修复 OcrMatchFallbackService Contains 大小写敏感
- 修复 BvPage.cs DefaultRetryInterval=0 除零
- 添加 OcrMatchDefaultThreshold [0,1] 范围约束
- 提取 PaddleOcrService BGRA→BGR 转换辅助方法
- 使用 Interlocked.CompareExchange 修复 OcrFactory Fallback 线程安全
- 增大 LruCacheTests BuilderTest TTL 裕量避免 CI 不稳定
- 更新 .gitignore 注释

* fix: 修复 OcrMatch 归一化分母导致多区域匹配分数过低的 bug,改进 UI

- 修复 GetMaxScoreFlat 中 availableCount 使用非空图像数作为分母,
  导致多文字区域场景下匹配分数被过度稀释的问题,改为使用 target.Length
- AllowDuplicateChar 设置项添加"需重新加载OCR引擎"的提示
- OCR模糊匹配阈值控件从 TextBox 改为 Slider + 数值显示
- 移除 Det 类中有问题的 finalizer(含锁的析构函数可能导致死锁)
- 补充多区域场景的单元测试

* feat(ocr): 添加队伍切换时使用OcrMatch模糊匹配的选项和相关配置

* fix(ui): 更新匹配成功阈值默认值为 0.8

* fix(ocr): 修复队伍切换逻辑中的空值处理和优化代码结构

* refactor: 简化 LruCache,移除弱引用支持和 Builder 模式

- 移除有 TOCTOU bug 的 WeakReference 支持(且无实际使用方)
- CacheItem 类改为 ValueTuple 减少堆分配
- 无过期时不再赋值 DateTime.MaxValue,过期检查短路跳过
- 移除仅剩两参数的 LruCacheBuilder,直接使用构造函数

* fix(ocr): 修复 CreateWeights 中空格字符权重写入错误索引的 bug

复用 CreateLabelDict 构建索引映射,确保空格映射到 labels.Count+1,
与 CreateLabelDict 保持一致。添加对应测试用例。

* fix(ocr): 修复 GCHandle.Alloc 失败时 finally 中 Free 掩盖原始异常的问题

* fix(ocr): 添加队伍选择按钮存在性检查,避免 PartySetupFailedException

* fix(ocr): 调整 OcrMatchDefaultThreshold 的 TickFrequency 为 0.01

* fix(ocr): 修复区域裁剪逻辑,确保裁剪尺寸不为负值

* fix(ocr): 优化字符置信度提取逻辑,直接按目标字符索引查找置信度

* fix(ocr): 修正变量命名以保持一致性,调整方法名大小写

* fix(ocr): 修改 CreateWeights 方法以使用标签字典和标签计数,优化权重创建逻辑

* fix(ocr): 更新 OCR 置信度阈值设置,确保阈值范围为 0.01 到 0.99,并优化相关逻辑
2026-02-20 15:08:46 +08:00

165 lines
4.8 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using BetterGenshinImpact.Core.Recognition;
using BetterGenshinImpact.Model;
using CommunityToolkit.Mvvm.ComponentModel;
namespace BetterGenshinImpact.Core.Config;
[Serializable]
public partial class OtherConfig : ObservableObject
{
//调度器任务和部分独立任务,失去焦点,自动激活游戏窗口
[ObservableProperty]
private bool _restoreFocusOnLostEnabled = false;
//自动领取派遣任务城市
[ObservableProperty]
private string _autoFetchDispatchAdventurersGuildCountry = "无";
//服务器时区偏移量
[ObservableProperty]
private TimeSpan _serverTimeZoneOffset = TimeSpan.FromHours(8);
[ObservableProperty]
private AutoRestart _autoRestartConfig = new();
//锄地规划
[ObservableProperty]
private FarmingPlan _farmingPlanConfig = new();
[ObservableProperty]
private Miyoushe _miyousheConfig = new();
//OCR配置
[ObservableProperty]
private Ocr _ocrConfig = new();
public partial class AutoRestart : ObservableObject
{
[ObservableProperty]
private bool _enabled = false;
//调度器任务连续异常退出几次任务自动重启
[ObservableProperty]
private int _failureCount = 5;
//是否同时重启游戏,需开启首页启动配置:同时启动原神、自动进入游戏,此配置才会生效
[ObservableProperty]
private bool _restartGameTogether = false;
//锄地脚本,如果打架次数不一致,则判定任务失败。
[ObservableProperty]
private bool _isFightFailureExceptional = false;
//任何追踪任务,未走完全路径结束,视为失败。
[ObservableProperty]
private bool _isPathingFailureExceptional = false;
}
public partial class Miyoushe : ObservableObject
{
//cookie
[ObservableProperty]
private string _cookie = "";
//与调度器日志处相互同步cookie
[ObservableProperty]
private bool _logSyncCookie = true;
}
public partial class MiyousheDataSupport : ObservableObject
{
[ObservableProperty]
private bool _enabled = false;
//日精英上限
[ObservableProperty]
private int _dailyEliteCap = 400;
//日小怪上限
[ObservableProperty]
private int _dailyMobCap = 2000;
}
public partial class FarmingPlan : ObservableObject
{
[ObservableProperty]
private MiyousheDataSupport _miyousheDataConfig = new();
[ObservableProperty]
private bool _enabled = false;
//日精英上限
[ObservableProperty]
private int _dailyEliteCap = 400;
//日小怪上限
[ObservableProperty]
private int _dailyMobCap = 2000;
}
public partial class Ocr : ObservableObject
{
/// <summary>
/// PaddleOCR模型配置
/// </summary>
[ObservableProperty]
private PaddleOcrModelConfig _paddleOcrModelConfig = PaddleOcrModelConfig.V4Auto;
/// <summary>
/// 允许OCR结果中出现连续重复字符关闭CTC重复字符折叠
/// </summary>
[ObservableProperty]
private bool _allowDuplicateChar;
/// <summary>
/// 切换队伍时使用 OcrMatch 模糊匹配代替正则表达式匹配
/// </summary>
[ObservableProperty]
private bool _useOcrMatchForPartySwitch = true;
/// <summary>
/// OcrMatch 模糊匹配的默认阈值 (0~1),分数 ≥ 阈值视为匹配成功
/// </summary>
[ObservableProperty]
private double _ocrMatchDefaultThreshold = 0.8;
partial void OnOcrMatchDefaultThresholdChanged(double value)
{
if (value is <= 0 or > 1)
{
OcrMatchDefaultThreshold = Math.Clamp(value, 0.01, 1);
}
}
/// <summary>
/// PaddleOCR 识别置信度阈值 (0~1),低于此阈值的字符将被过滤
/// </summary>
[ObservableProperty]
private double _paddleOcrThreshold = 0.5;
partial void OnPaddleOcrThresholdChanged(double value)
{
if (value is < 0 or >= 1)
{
PaddleOcrThreshold = Math.Clamp(value, 0, 0.99);
}
}
}
//public partial class OtherConfig : ObservableObject
/// <summary>
/// 游戏语言名称
/// </summary>
[ObservableProperty]
private string _gameCultureInfoName = "zh-Hans";
/// <summary>
/// BGI界面语言名称
/// </summary>
[ObservableProperty]
private string _uiCultureInfoName = "zh-Hans";
}