diff --git a/BetterGenshinImpact/Assets/Model/PaddleOCR/ch_PP-OCRv4_det/inference.pdiparams.info b/BetterGenshinImpact/Assets/Model/PaddleOCR/ch_PP-OCRv4_det/inference.pdiparams.info
deleted file mode 100644
index 082c148e..00000000
Binary files a/BetterGenshinImpact/Assets/Model/PaddleOCR/ch_PP-OCRv4_det/inference.pdiparams.info and /dev/null differ
diff --git a/BetterGenshinImpact/Assets/Model/PaddleOCR/ch_PP-OCRv4_det/inference.pdmodel b/BetterGenshinImpact/Assets/Model/PaddleOCR/ch_PP-OCRv4_det/inference.pdmodel
deleted file mode 100644
index 223b8614..00000000
Binary files a/BetterGenshinImpact/Assets/Model/PaddleOCR/ch_PP-OCRv4_det/inference.pdmodel and /dev/null differ
diff --git a/BetterGenshinImpact/Assets/Model/PaddleOCR/ch_PP-OCRv4_det/inference.pdiparams b/BetterGenshinImpact/Assets/Model/PaddleOCR/ch_PP-OCRv4_det/slim_model.onnx
similarity index 95%
rename from BetterGenshinImpact/Assets/Model/PaddleOCR/ch_PP-OCRv4_det/inference.pdiparams
rename to BetterGenshinImpact/Assets/Model/PaddleOCR/ch_PP-OCRv4_det/slim_model.onnx
index 089594ae..96242d46 100644
Binary files a/BetterGenshinImpact/Assets/Model/PaddleOCR/ch_PP-OCRv4_det/inference.pdiparams and b/BetterGenshinImpact/Assets/Model/PaddleOCR/ch_PP-OCRv4_det/slim_model.onnx differ
diff --git a/BetterGenshinImpact/Assets/Model/PaddleOCR/ch_PP-OCRv4_rec/inference.pdiparams.info b/BetterGenshinImpact/Assets/Model/PaddleOCR/ch_PP-OCRv4_rec/inference.pdiparams.info
deleted file mode 100644
index 923329f5..00000000
Binary files a/BetterGenshinImpact/Assets/Model/PaddleOCR/ch_PP-OCRv4_rec/inference.pdiparams.info and /dev/null differ
diff --git a/BetterGenshinImpact/Assets/Model/PaddleOCR/ch_PP-OCRv4_rec/inference.pdmodel b/BetterGenshinImpact/Assets/Model/PaddleOCR/ch_PP-OCRv4_rec/inference.pdmodel
deleted file mode 100644
index dccddcc7..00000000
Binary files a/BetterGenshinImpact/Assets/Model/PaddleOCR/ch_PP-OCRv4_rec/inference.pdmodel and /dev/null differ
diff --git a/BetterGenshinImpact/Assets/Model/PaddleOCR/ch_PP-OCRv4_rec/inference.pdiparams b/BetterGenshinImpact/Assets/Model/PaddleOCR/ch_PP-OCRv4_rec/slim_model.onnx
similarity index 88%
rename from BetterGenshinImpact/Assets/Model/PaddleOCR/ch_PP-OCRv4_rec/inference.pdiparams
rename to BetterGenshinImpact/Assets/Model/PaddleOCR/ch_PP-OCRv4_rec/slim_model.onnx
index 4c3d9e9c..606b4814 100644
Binary files a/BetterGenshinImpact/Assets/Model/PaddleOCR/ch_PP-OCRv4_rec/inference.pdiparams and b/BetterGenshinImpact/Assets/Model/PaddleOCR/ch_PP-OCRv4_rec/slim_model.onnx differ
diff --git a/BetterGenshinImpact/Assets/Model/PaddleOCR/chinese_cht_PP-OCRv3_rec_infer/inference.pdiparams.info b/BetterGenshinImpact/Assets/Model/PaddleOCR/chinese_cht_PP-OCRv3_rec_infer/inference.pdiparams.info
deleted file mode 100644
index 1cdccfce..00000000
Binary files a/BetterGenshinImpact/Assets/Model/PaddleOCR/chinese_cht_PP-OCRv3_rec_infer/inference.pdiparams.info and /dev/null differ
diff --git a/BetterGenshinImpact/Assets/Model/PaddleOCR/chinese_cht_PP-OCRv3_rec_infer/inference.pdmodel b/BetterGenshinImpact/Assets/Model/PaddleOCR/chinese_cht_PP-OCRv3_rec_infer/inference.pdmodel
deleted file mode 100644
index 95acc925..00000000
Binary files a/BetterGenshinImpact/Assets/Model/PaddleOCR/chinese_cht_PP-OCRv3_rec_infer/inference.pdmodel and /dev/null differ
diff --git a/BetterGenshinImpact/Assets/Model/PaddleOCR/chinese_cht_PP-OCRv3_rec_infer/inference.pdiparams b/BetterGenshinImpact/Assets/Model/PaddleOCR/chinese_cht_PP-OCRv3_rec_infer/slim_model.onnx
similarity index 66%
rename from BetterGenshinImpact/Assets/Model/PaddleOCR/chinese_cht_PP-OCRv3_rec_infer/inference.pdiparams
rename to BetterGenshinImpact/Assets/Model/PaddleOCR/chinese_cht_PP-OCRv3_rec_infer/slim_model.onnx
index 1b1ea1a9..0b8052b4 100644
Binary files a/BetterGenshinImpact/Assets/Model/PaddleOCR/chinese_cht_PP-OCRv3_rec_infer/inference.pdiparams and b/BetterGenshinImpact/Assets/Model/PaddleOCR/chinese_cht_PP-OCRv3_rec_infer/slim_model.onnx differ
diff --git a/BetterGenshinImpact/Assets/Model/PaddleOCR/en_PP-OCRv3_det_infer/inference.pdiparams b/BetterGenshinImpact/Assets/Model/PaddleOCR/en_PP-OCRv3_det_infer/inference.pdiparams
deleted file mode 100644
index 2efedca1..00000000
Binary files a/BetterGenshinImpact/Assets/Model/PaddleOCR/en_PP-OCRv3_det_infer/inference.pdiparams and /dev/null differ
diff --git a/BetterGenshinImpact/Assets/Model/PaddleOCR/en_PP-OCRv3_det_infer/inference.pdiparams.info b/BetterGenshinImpact/Assets/Model/PaddleOCR/en_PP-OCRv3_det_infer/inference.pdiparams.info
deleted file mode 100644
index 622d87ba..00000000
Binary files a/BetterGenshinImpact/Assets/Model/PaddleOCR/en_PP-OCRv3_det_infer/inference.pdiparams.info and /dev/null differ
diff --git a/BetterGenshinImpact/Assets/Model/PaddleOCR/en_PP-OCRv3_det_infer/inference.pdmodel b/BetterGenshinImpact/Assets/Model/PaddleOCR/en_PP-OCRv3_det_infer/inference.pdmodel
deleted file mode 100644
index 0a6bf1e4..00000000
Binary files a/BetterGenshinImpact/Assets/Model/PaddleOCR/en_PP-OCRv3_det_infer/inference.pdmodel and /dev/null differ
diff --git a/BetterGenshinImpact/Assets/Model/PaddleOCR/en_PP-OCRv3_det_infer/slim_model.onnx b/BetterGenshinImpact/Assets/Model/PaddleOCR/en_PP-OCRv3_det_infer/slim_model.onnx
new file mode 100644
index 00000000..5d8910b0
Binary files /dev/null and b/BetterGenshinImpact/Assets/Model/PaddleOCR/en_PP-OCRv3_det_infer/slim_model.onnx differ
diff --git a/BetterGenshinImpact/Assets/Model/PaddleOCR/latin_PP-OCRv3_rec_infer/inference.pdiparams.info b/BetterGenshinImpact/Assets/Model/PaddleOCR/latin_PP-OCRv3_rec_infer/inference.pdiparams.info
deleted file mode 100644
index 1cdccfce..00000000
Binary files a/BetterGenshinImpact/Assets/Model/PaddleOCR/latin_PP-OCRv3_rec_infer/inference.pdiparams.info and /dev/null differ
diff --git a/BetterGenshinImpact/Assets/Model/PaddleOCR/latin_PP-OCRv3_rec_infer/inference.pdmodel b/BetterGenshinImpact/Assets/Model/PaddleOCR/latin_PP-OCRv3_rec_infer/inference.pdmodel
deleted file mode 100644
index 144978c1..00000000
Binary files a/BetterGenshinImpact/Assets/Model/PaddleOCR/latin_PP-OCRv3_rec_infer/inference.pdmodel and /dev/null differ
diff --git a/BetterGenshinImpact/Assets/Model/PaddleOCR/latin_PP-OCRv3_rec_infer/inference.pdiparams b/BetterGenshinImpact/Assets/Model/PaddleOCR/latin_PP-OCRv3_rec_infer/slim_model.onnx
similarity index 58%
rename from BetterGenshinImpact/Assets/Model/PaddleOCR/latin_PP-OCRv3_rec_infer/inference.pdiparams
rename to BetterGenshinImpact/Assets/Model/PaddleOCR/latin_PP-OCRv3_rec_infer/slim_model.onnx
index 6a7305b7..1d31b0b8 100644
Binary files a/BetterGenshinImpact/Assets/Model/PaddleOCR/latin_PP-OCRv3_rec_infer/inference.pdiparams and b/BetterGenshinImpact/Assets/Model/PaddleOCR/latin_PP-OCRv3_rec_infer/slim_model.onnx differ
diff --git a/BetterGenshinImpact/BetterGenshinImpact.csproj b/BetterGenshinImpact/BetterGenshinImpact.csproj
index 4bd41176..011d09e0 100644
--- a/BetterGenshinImpact/BetterGenshinImpact.csproj
+++ b/BetterGenshinImpact/BetterGenshinImpact.csproj
@@ -41,47 +41,47 @@
-
+
-
-
-
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
+
+
+
-
-
-
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
@@ -170,6 +170,11 @@
Wpf
Designer
+
+ MSBuild:Compile
+ Wpf
+ Designer
+
diff --git a/BetterGenshinImpact/Core/Config/AllConfig.cs b/BetterGenshinImpact/Core/Config/AllConfig.cs
index 6d1b954d..cf4105fe 100644
--- a/BetterGenshinImpact/Core/Config/AllConfig.cs
+++ b/BetterGenshinImpact/Core/Config/AllConfig.cs
@@ -66,11 +66,11 @@ public partial class AllConfig : ObservableObject
[ObservableProperty]
private bool _autoFixWin11BitBlt = true;
- ///
- /// 推理使用的设备
- ///
- [ObservableProperty]
- private string _inferenceDevice = "CPU";
+ // ///
+ // /// 推理使用的设备
+ // ///
+ // [ObservableProperty]
+ // private string _inferenceDevice = "CPU";
[ObservableProperty]
private List> _nextScheduledTask = [];
@@ -135,7 +135,7 @@ public partial class AllConfig : ObservableObject
/// 自动战斗配置
///
public AutoFightConfig AutoFightConfig { get; set; } = new();
-
+
///
/// 自动乐曲配置 - 千音雅集
///
@@ -182,20 +182,28 @@ public partial class AllConfig : ObservableObject
/// 原神按键绑定配置
///
public KeyBindingsConfig KeyBindingsConfig { get; set; } = new();
+
///
/// 其他配置
///
public OtherConfig OtherConfig { get; set; } = new();
+
///
/// 传送相关配置
///
public TpConfig TpConfig { get; set; } = new();
-
+
///
/// 开发者配置
///
public DevConfig DevConfig { get; set; } = new();
-
+
+
+ ///
+ /// 硬件加速设置
+ ///
+ public HardwareAccelerationConfig HardwareAccelerationConfig { get; set; } = new();
+
[JsonIgnore]
public Action? OnAnyChangedAction { get; set; }
@@ -224,6 +232,7 @@ public partial class AllConfig : ObservableObject
ScriptConfig.PropertyChanged += OnAnyPropertyChanged;
PathingConditionConfig.PropertyChanged += OnAnyPropertyChanged;
DevConfig.PropertyChanged += OnAnyPropertyChanged;
+ HardwareAccelerationConfig.PropertyChanged += OnAnyPropertyChanged;
}
public void OnAnyPropertyChanged(object? sender, EventArgs args)
diff --git a/BetterGenshinImpact/Core/Config/HardwareAccelerationConfig.cs b/BetterGenshinImpact/Core/Config/HardwareAccelerationConfig.cs
new file mode 100644
index 00000000..5dcae9db
--- /dev/null
+++ b/BetterGenshinImpact/Core/Config/HardwareAccelerationConfig.cs
@@ -0,0 +1,75 @@
+using System;
+using BetterGenshinImpact.Core.Recognition.ONNX;
+using CommunityToolkit.Mvvm.ComponentModel;
+
+namespace BetterGenshinImpact.Core.Config;
+
+[Serializable]
+public partial class HardwareAccelerationConfig : ObservableObject
+{
+ ///
+ /// 推理使用的设备。默认CPU
+ ///
+ [ObservableProperty]
+ private InferenceDeviceType _inferenceDevice = InferenceDeviceType.Cpu;
+
+ ///
+ /// 是否强制OCR使用CPU推理。在某些环境上使用GPU进行OCR推理会导致性能下降(比如很多使用DirectML推理的情况下)。默认开。
+ ///
+ [ObservableProperty]
+ private bool _cpuOcr = true;
+
+ #region 一般GPU加速设置
+
+ ///
+ /// 强制指定gpu设备,默认为0(使用默认设备)
+ ///
+ [ObservableProperty]
+ private int _gpuDevice = 0;
+
+ ///
+ /// 附加path,用;分割。默认为空。
+ ///
+ [ObservableProperty]
+ private string _additionalPath = "";
+
+ ///
+ /// 是否输出优化后的模型文件到缓存。注意:在不支持的执行器上使用会导致异常。默认关闭。
+ ///
+ [ObservableProperty]
+ private bool _optimizedModel = false;
+
+ #endregion
+
+ #region cuda设置
+
+ ///
+ /// 强制指定cuda设备,默认为0(使用默认设备)
+ ///
+ [ObservableProperty]
+ private int _cudaDevice = 0;
+
+ ///
+ /// 自动附加cuda的path。一般情况下用这个就足够了。默认开启。
+ ///
+ [ObservableProperty]
+ private bool _autoAppendCudaPath = true;
+
+ #endregion
+
+ #region TensorRT缓存设置
+
+ ///
+ /// 启用TensorRT缓存。默认开启。不开的话使用TensorRT每次加载模型会卡爆。
+ ///
+ [ObservableProperty]
+ private bool _enableTensorRtCache = true;
+
+ ///
+ /// 嵌入式引擎缓存。将引擎缓存嵌入到模型中。默认开启。关闭它可能会提高性能(如果不爆炸的话)。
+ ///
+ [ObservableProperty]
+ private bool _embedTensorRtCache = true;
+
+ #endregion
+}
\ No newline at end of file
diff --git a/BetterGenshinImpact/Core/Recognition/OCR/IOcrService.cs b/BetterGenshinImpact/Core/Recognition/OCR/IOcrService.cs
index f30e0d35..9d9f938e 100644
--- a/BetterGenshinImpact/Core/Recognition/OCR/IOcrService.cs
+++ b/BetterGenshinImpact/Core/Recognition/OCR/IOcrService.cs
@@ -1,5 +1,4 @@
using OpenCvSharp;
-using Sdcb.PaddleOCR;
namespace BetterGenshinImpact.Core.Recognition.OCR;
@@ -9,5 +8,5 @@ public interface IOcrService
public string OcrWithoutDetector(Mat mat);
- public PaddleOcrResult OcrResult(Mat mat);
-}
+ public OcrResult OcrResult(Mat mat);
+}
\ No newline at end of file
diff --git a/BetterGenshinImpact/Core/Recognition/OCR/OcrFactory.cs b/BetterGenshinImpact/Core/Recognition/OCR/OcrFactory.cs
index 0338a2bc..dc82f453 100644
--- a/BetterGenshinImpact/Core/Recognition/OCR/OcrFactory.cs
+++ b/BetterGenshinImpact/Core/Recognition/OCR/OcrFactory.cs
@@ -1,6 +1,4 @@
using System;
-using System.Diagnostics;
-using System.Threading;
using System.Threading.Tasks;
namespace BetterGenshinImpact.Core.Recognition.OCR;
@@ -15,7 +13,7 @@ public class OcrFactory
return type switch
{
OcrEngineTypes.Paddle => new PaddleOcrService(cultureInfoName),
- _ => throw new ArgumentOutOfRangeException(nameof(type), type, null),
+ _ => throw new ArgumentOutOfRangeException(nameof(type), type, null)
};
}
@@ -30,4 +28,4 @@ public class OcrFactory
}
});
}
-}
+}
\ No newline at end of file
diff --git a/BetterGenshinImpact/Core/Recognition/OCR/OcrResult.cs b/BetterGenshinImpact/Core/Recognition/OCR/OcrResult.cs
new file mode 100644
index 00000000..31a858c9
--- /dev/null
+++ b/BetterGenshinImpact/Core/Recognition/OCR/OcrResult.cs
@@ -0,0 +1,68 @@
+using System.Linq;
+using OpenCvSharp;
+
+namespace BetterGenshinImpact.Core.Recognition.OCR;
+
+///
+/// Represents a region detected in an OCR result using Paddle OCR.
+/// Sdcb.PaddleOCR
+///
+public record struct OcrResultRegion(RotatedRect Rect, string Text, float Score);
+
+public readonly record struct OcrRecognizerResult
+{
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The recognized text from the image.
+ /// The confidence score of the text recognition.
+ public OcrRecognizerResult(string text, float score)
+ {
+ Text = text;
+ Score = score;
+ }
+
+ ///
+ /// The recognized text from the image.
+ ///
+ public string Text { get; init; }
+
+ ///
+ /// The confidence score of the text recognition.
+ ///
+ public float Score { get; init; }
+}
+
+///
+/// Represents the OCR result of a paddle object detection model.
+///
+public record OcrResult
+{
+ ///
+ /// Initializes a new instance of the class with the specified .
+ ///
+ /// An array of objects representing the detected text regions.
+ public OcrResult(OcrResultRegion[] Regions)
+ {
+ this.Regions = Regions;
+ }
+
+ ///
+ /// Gets an array of objects representing the detected text regions.
+ ///
+ /// An array of objects representing the detected text regions.
+ public OcrResultRegion[] Regions { get; }
+
+ ///
+ /// Concatenates the text from each object in
+ /// and returns the resulting string, ordered by the region's center positions.
+ ///
+ ///
+ /// A string containing the concatenated text from each object
+ /// in , ordered by the region's center positions.
+ ///
+ public string Text => string.Join("\n", Regions
+ .OrderBy(x => x.Rect.Center.Y)
+ .ThenBy(x => x.Rect.Center.X)
+ .Select(x => x.Text));
+}
\ No newline at end of file
diff --git a/BetterGenshinImpact/Core/Recognition/OCR/OcrResultExtension.cs b/BetterGenshinImpact/Core/Recognition/OCR/OcrResultExtension.cs
new file mode 100644
index 00000000..8ea169da
--- /dev/null
+++ b/BetterGenshinImpact/Core/Recognition/OCR/OcrResultExtension.cs
@@ -0,0 +1,55 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Linq;
+using BetterGenshinImpact.GameTask.AutoSkip.Model;
+using BetterGenshinImpact.View.Drawable;
+using OpenCvSharp;
+
+namespace BetterGenshinImpact.Core.Recognition.OCR;
+
+public static class OcrResultExtension
+{
+ public static bool RegionHasText(this OcrResult result, ReadOnlySpan text)
+ {
+ foreach (ref readonly var item in result.Regions.AsSpan())
+ if (item.Text.AsSpan().Contains(text, StringComparison.InvariantCulture))
+ return true;
+
+ return false;
+ }
+
+ public static OcrResultRegion FindRegionByText(this OcrResult result, ReadOnlySpan text)
+ {
+ foreach (ref readonly var item in result.Regions.AsSpan())
+ if (item.Text.AsSpan().Contains(text, StringComparison.InvariantCulture))
+ return item;
+
+ return default;
+ }
+
+ public static Rect FindRectByText(this OcrResult result, string text)
+ {
+ foreach (ref var item in result.Regions.AsSpan())
+ if (item.Text.Contains(text))
+ return item.Rect.BoundingRect();
+
+ return default;
+ }
+
+ public static List ToRectDrawableList(this OcrResult result, Pen? pen = null)
+ {
+ return result.Regions.Select(item => item.Rect.BoundingRect().ToRectDrawable(pen)).ToList();
+ }
+
+ public static List ToRectDrawableListOffset(this OcrResult result, int offsetX, int offsetY,
+ Pen? pen = null)
+ {
+ return result.Regions.Select(item => item.Rect.BoundingRect().ToRectDrawable(offsetX, offsetY, pen)).ToList();
+ }
+
+ public static PaddleOcrResultRect ToOcrResultRect(this OcrResultRegion region)
+ {
+ return new PaddleOcrResultRect(region.Rect.BoundingRect(), region.Text, region.Score);
+ }
+}
\ No newline at end of file
diff --git a/BetterGenshinImpact/Core/Recognition/OCR/PaddleOcrResultExtension.cs b/BetterGenshinImpact/Core/Recognition/OCR/PaddleOcrResultExtension.cs
deleted file mode 100644
index 794c8062..00000000
--- a/BetterGenshinImpact/Core/Recognition/OCR/PaddleOcrResultExtension.cs
+++ /dev/null
@@ -1,67 +0,0 @@
-using BetterGenshinImpact.GameTask.AutoSkip.Model;
-using BetterGenshinImpact.View.Drawable;
-using OpenCvSharp;
-using Sdcb.PaddleOCR;
-using System;
-using System.Collections.Generic;
-using System.Drawing;
-using System.Linq;
-
-namespace BetterGenshinImpact.Core.Recognition.OCR;
-
-public static class PaddleOcrResultExtension
-{
- public static bool RegionHasText(this PaddleOcrResult result, ReadOnlySpan text)
- {
- foreach (ref readonly PaddleOcrResultRegion item in result.Regions.AsSpan())
- {
- if (item.Text.AsSpan().Contains(text, StringComparison.InvariantCulture))
- {
- return true;
- }
- }
-
- return false;
- }
-
- public static PaddleOcrResultRegion FindRegionByText(this PaddleOcrResult result, ReadOnlySpan text)
- {
- foreach (ref readonly PaddleOcrResultRegion item in result.Regions.AsSpan())
- {
- if (item.Text.AsSpan().Contains(text, StringComparison.InvariantCulture))
- {
- return item;
- }
- }
-
- return default;
- }
-
- public static Rect FindRectByText(this PaddleOcrResult result, string text)
- {
- foreach (ref PaddleOcrResultRegion item in result.Regions.AsSpan())
- {
- if (item.Text.Contains(text))
- {
- return item.Rect.BoundingRect();
- }
- }
-
- return default;
- }
-
- public static List ToRectDrawableList(this PaddleOcrResult result, Pen? pen = null)
- {
- return result.Regions.Select(item => item.Rect.BoundingRect().ToRectDrawable(pen)).ToList();
- }
-
- public static List ToRectDrawableListOffset(this PaddleOcrResult result, int offsetX, int offsetY, Pen? pen = null)
- {
- return result.Regions.Select(item => item.Rect.BoundingRect().ToRectDrawable(offsetX, offsetY, pen)).ToList();
- }
-
- public static PaddleOcrResultRect ToOcrResultRect(this PaddleOcrResultRegion region)
- {
- return new PaddleOcrResultRect(region.Rect.BoundingRect(), region.Text, region.Score);
- }
-}
diff --git a/BetterGenshinImpact/Core/Recognition/OCR/PaddleOcrService.cs b/BetterGenshinImpact/Core/Recognition/OCR/PaddleOcrService.cs
deleted file mode 100644
index 2dc7a648..00000000
--- a/BetterGenshinImpact/Core/Recognition/OCR/PaddleOcrService.cs
+++ /dev/null
@@ -1,102 +0,0 @@
-using BetterGenshinImpact.Core.Config;
-using OpenCvSharp;
-using Sdcb.PaddleInference;
-using Sdcb.PaddleOCR;
-using Sdcb.PaddleOCR.Models;
-using System;
-using System.Diagnostics;
-using System.IO;
-using static Vanara.PInvoke.Gdi32;
-
-namespace BetterGenshinImpact.Core.Recognition.OCR;
-
-public class PaddleOcrService : IOcrService
-{
- private static readonly object locker = new();
-
- ///
- /// Usage:
- /// https://github.com/sdcb/PaddleSharp/blob/master/docs/ocr.md
- /// 模型列表:
- /// https://github.com/PaddlePaddle/PaddleOCR/blob/release/2.5/doc/doc_ch/models_list.md
- ///
- private readonly PaddleOcrAll _paddleOcrAll;
-
- public PaddleOcrService(string? cultureInfoName = null)
- {
- var path = Global.Absolute(@"Assets\Model\PaddleOcr");
- DetectionModel localDetModel;
- RecognizationModel localRecModel;
- FullOcrModel model;
- switch (cultureInfoName)
- {
- case "zh-Hant":
- localDetModel = DetectionModel.FromDirectory(Path.Combine(path, "ch_PP-OCRv4_det"), ModelVersion.V4); // 和简中共用一下det
- localRecModel = RecognizationModel.FromDirectory(Path.Combine(path, "chinese_cht_PP-OCRv3_rec_infer"), Path.Combine(path, "chinese_cht_dict.txt"), ModelVersion.V3);
- //model = OnlineFullModels.TraditionalChineseV3.DownloadAsync().Result;
- break;
- case "fr":
- localDetModel = DetectionModel.FromDirectory(Path.Combine(path, "en_PP-OCRv3_det_infer"), ModelVersion.V3);
- localRecModel = RecognizationModel.FromDirectory(Path.Combine(path, "latin_PP-OCRv3_rec_infer"), Path.Combine(path, "latin_dict.txt"), ModelVersion.V3);
- break;
- default:
- localDetModel = DetectionModel.FromDirectory(Path.Combine(path, "ch_PP-OCRv4_det"), ModelVersion.V4);
- localRecModel = RecognizationModel.FromDirectory(Path.Combine(path, "ch_PP-OCRv4_rec"), Path.Combine(path, "ppocr_keys_v1.txt"), ModelVersion.V4);
-
- break;
- }
- model = new FullOcrModel(localDetModel, localRecModel);
- // Action device = TaskContext.Instance().Config.InferenceDevice switch
- // {
- // "CPU" => PaddleDevice.Onnx(),
- // "GPU_DirectML" => PaddleDevice.Onnx(),
- // _ => throw new InvalidEnumArgumentException("无效的推理设备")
- // };
- _paddleOcrAll = new PaddleOcrAll(model, PaddleDevice.Onnx())
- {
- AllowRotateDetection = false, /* 允许识别有角度的文字 */
- Enable180Classification = false /* 允许识别旋转角度大于90度的文字 */
- };
-
- // System.AccessViolationException
- // https://github.com/babalae/better-genshin-impact/releases/latest
- // 下载并解压到相同目录下
- }
-
- public string Ocr(Mat mat)
- {
- return OcrResult(mat).Text;
- }
-
- public PaddleOcrResult OcrResult(Mat mat)
- {
- if (mat.Channels() == 4)
- {
- using var mat3 = mat.CvtColor(ColorConversionCodes.BGRA2BGR);
- return _OcrResult(mat3);
- }
- return _OcrResult(mat);
- }
-
- private PaddleOcrResult _OcrResult(Mat mat)
- {
- lock (locker)
- {
- long startTime = Stopwatch.GetTimestamp();
- var result = _paddleOcrAll.Run(mat);
- TimeSpan time = Stopwatch.GetElapsedTime(startTime);
- Debug.WriteLine($"PaddleOcr 耗时 {time.TotalMilliseconds}ms 结果: {result.Text}");
- return result;
- }
- }
-
- public string OcrWithoutDetector(Mat mat)
- {
- lock (locker)
- {
- var str = _paddleOcrAll.Recognizer.Run(mat).Text;
- Debug.WriteLine($"PaddleOcrWithoutDetector 结果: {str}");
- return str;
- }
- }
-}
diff --git a/BetterGenshinImpact/Core/Recognition/OCR/engine/OcrOperationImpl.cs b/BetterGenshinImpact/Core/Recognition/OCR/engine/OcrOperationImpl.cs
new file mode 100644
index 00000000..80217718
--- /dev/null
+++ b/BetterGenshinImpact/Core/Recognition/OCR/engine/OcrOperationImpl.cs
@@ -0,0 +1,41 @@
+using OpenCvSharp;
+
+namespace BetterGenshinImpact.Core.Recognition.OCR.engine;
+
+///
+/// 实现PPOCR的自定义操作,代码翻自python
+///
+public class OcrOperationImpl
+{
+ ///
+ /// 不支持 chw 之类的顺序
+ /// https://github.com/PaddlePaddle/PaddleOCR/blob/0ee4094988c568077bba35ddb239030ced1ff270/ppocr/data/imaug/operators.py#L62
+ ///
+ public static Mat NormalizeImageOperation(Mat data,
+ float? scale, // scale float32
+ float[]? mean, //mean
+ float[]? std //std
+ )
+ {
+ scale ??= 0.00392156862745f;
+ mean ??= [0.485f, 0.456f, 0.406f];
+ std ??= [0.229f, 0.224f, 0.225f];
+ var result = new Mat();
+ data.ConvertTo(result, MatType.CV_32FC3, (double)scale);
+ Mat[] bgr = [];
+ try
+ {
+ bgr = result.Split();
+ for (var i = 0; i < bgr.Length; ++i)
+ bgr[i].ConvertTo(bgr[i], MatType.CV_32FC1, 1 / std[i], (0.0 - mean[i]) / std[i]);
+
+ Cv2.Merge(bgr, result);
+ }
+ finally
+ {
+ foreach (var channel in bgr) channel.Dispose();
+ }
+
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/BetterGenshinImpact/Core/Recognition/OCR/engine/OcrUtils.cs b/BetterGenshinImpact/Core/Recognition/OCR/engine/OcrUtils.cs
new file mode 100644
index 00000000..f8271b68
--- /dev/null
+++ b/BetterGenshinImpact/Core/Recognition/OCR/engine/OcrUtils.cs
@@ -0,0 +1,180 @@
+using System;
+using System.Buffers;
+using System.Collections.Generic;
+using System.Linq;
+using BetterGenshinImpact.Core.Recognition.OCR.engine;
+using BetterGenshinImpact.Core.Recognition.OCR.paddle.data;
+using BetterGenshinImpact.Core.Recognition.OpenCv;
+using Microsoft.ML.OnnxRuntime.Tensors;
+using OpenCvSharp;
+using OpenCvSharp.Dnn;
+
+namespace BetterGenshinImpact.Core.Recognition.OCR;
+
+public static class OcrUtils
+{
+ ///
+ /// 预处理速度比unsafe快5倍以上,且吃的资源还少
+ ///
+ /// 输入图像,若不是灰度图会转换
+ /// tensor的Memory,用完需要释放
+ ///
+ public static Tensor ToTensorYapDnn(Mat inputImage, out IMemoryOwner tensorMemoryOwnser)
+ {
+ using var rt = new ResourcesTracker();
+ Mat dst;
+ // 221*32是个什么鬼
+ if (inputImage.Channels() > 1)
+ {
+ var resize = rt.T(ResizeHelper.ResizeTo(inputImage, 221, 32));
+ dst = rt.NewMat(resize.Size(), MatType.CV_8UC1, Scalar.Black);
+ Cv2.CvtColor(resize, dst, ColorConversionCodes.BGR2GRAY);
+ }
+ else
+ {
+ dst = rt.T(ResizeHelper.ResizeTo(inputImage, 221, 32));
+ }
+
+ // 填充到 384x32
+ var padded = rt.NewMat(new Size(384, 32), MatType.CV_8UC1, Scalar.Black);
+ padded[new Rect(0, 0, 221, 32)] = dst;
+ // 使用向量运算代替循环
+ var blob = rt.T(CvDnn.BlobFromImage(padded, 1.0 / 255.0, default, default, false, false));
+ var nCols = padded.Cols * padded.Rows;
+ tensorMemoryOwnser = MemoryPool.Shared.Rent(nCols);
+ // 内存复制,如果直接传指针构建的话速度还不如多复制一份
+ blob.AsSpan().CopyTo(tensorMemoryOwnser.Memory.Span);
+ return new DenseTensor(tensorMemoryOwnser.Memory[..nCols], [1, 1, 32, 384]);
+ }
+
+ ///
+ /// 用于Det模型
+ /// 归一化,标准化并返回Tensor。
+ ///
+ /// 归一化:固定范围归一化
+ ///
+ /// 标准化:
+ /// Z-Score Normalization
+ ///
+ public static Tensor NormalizeToTensorDnn(Mat src,
+ float? scale, // scale float32
+ float[]? mean, //mean
+ float[]? std, //std
+ out IMemoryOwner tensorMemoryOwner, bool swapRb = false, bool crop = false, Size size = default)
+
+ {
+ using var rt = new ResourcesTracker();
+ // 获取图像参数
+ var channels = src.Channels();
+ if (channels != 3)
+ throw new ArgumentException($"图像通道数必须为3,当前为{channels}");
+ var data = rt.T(OcrOperationImpl.NormalizeImageOperation(src, scale, mean, std));
+ // 使用DNN模块创建blob
+ var blob = rt.T(CvDnn.BlobFromImage(
+ data,
+ 1.0,
+ size,
+ default,
+ swapRb,
+ crop
+ ));
+ // 租用内存并复制数据
+ var total = blob.Total();
+ tensorMemoryOwner = MemoryPool.Shared.Rent((int)total);
+ blob.AsSpan().CopyTo(tensorMemoryOwner.Memory.Span);
+ // 计算输出形状
+ return new DenseTensor(
+ tensorMemoryOwner.Memory[..(int)total],
+ new[] { 1, channels, data.Rows, data.Cols }
+ );
+ }
+
+ ///
+ /// 不支持通道转换
+ ///
+ /// 用于PP-OCR的Rec模型,调整大小之后再归一化到-1~1,之后转换为Tensor
+ ///
+ public static Tensor resize_norm_img(Mat img, OcrShape image_shape,
+ out IMemoryOwner tensorMemoryOwner, bool padding = true,
+ InterpolationFlags interpolation = InterpolationFlags.Linear)
+ {
+ using var rt = new ResourcesTracker();
+ var imgC = image_shape.Channel;
+ var imgH = image_shape.Height;
+ var imgW = image_shape.Width;
+
+ var h = img.Height;
+ var w = img.Width;
+
+ int resized_w;
+
+ var resizedImage = rt.NewMat();
+ if (!padding)
+ {
+ Cv2.Resize(img, resizedImage, new Size(imgW, imgH), 0, 0, interpolation);
+ // resized_w = imgW;
+ }
+ else
+ {
+ var ratio = w / (double)h;
+ resized_w = Math.Ceiling(imgH * ratio) > imgW ? imgW : (int)Math.Ceiling(imgH * ratio);
+ Cv2.Resize(img, resizedImage, new Size(resized_w, imgH), 0, 0, interpolation);
+ }
+
+ /*
+ resized_image / 255
+ resized_image -= 0.5
+ resized_image /= 0.5
+ */
+ // 归一化
+ // resizedImage.ConvertTo(resizedImage, MatType.CV_32F, 2 / 255f, 1);
+ var blob = rt.T(CvDnn.BlobFromImage(
+ resizedImage,
+ 2 / 255f,
+ default,
+ new Scalar(127.5, 127.5, 127.5),
+ false,
+ false
+ ));
+
+ var total = blob.Total();
+ tensorMemoryOwner = MemoryPool.Shared.Rent((int)total);
+ blob.AsSpan().CopyTo(tensorMemoryOwner.Memory.Span);
+ return new DenseTensor(
+ tensorMemoryOwner.Memory[..(int)total],
+ new[] { 1, resizedImage.Channels(), resizedImage.Rows, resizedImage.Cols }
+ );
+ }
+
+ ///
+ /// Gets a label by its index.
+ ///
+ /// The index of the label.
+ /// The labels to search for the index.
+ /// The label at the specified index.
+ public static string GetLabelByIndex(int i, IReadOnlyList labels)
+ {
+ return i switch
+ {
+ var x when x > 0 && x <= labels.Count => labels[x - 1],
+ var x when x == labels.Count + 1 => " ",
+ _ => throw new Exception(
+ $"Unable to GetLabelByIndex: index {i} out of range {labels.Count}, OCR model or labels not matched?")
+ };
+ }
+
+ public static Mat Tensor2Mat(Tensor tensor)
+ {
+ var dimensions = tensor.Dimensions;
+ if (dimensions.Length !=4 || dimensions[0] != 1 || dimensions[1] != 1)
+ {
+ throw new ArgumentException($"wrong tensor shape: {string.Join(",", dimensions.ToArray())}");
+ }
+ if (tensor is not DenseTensor denseTensor)
+ return Mat.FromPixelData(dimensions[2], dimensions[3], MatType.CV_32FC1, tensor.ToArray());
+ var mat = new Mat(new Size(dimensions[3], dimensions[2]), MatType.CV_32FC1);
+ denseTensor.Buffer.Span.CopyTo(mat.AsSpan());
+ return mat;
+
+ }
+}
\ No newline at end of file
diff --git a/BetterGenshinImpact/Core/Recognition/OCR/engine/OcrVersionConfig.cs b/BetterGenshinImpact/Core/Recognition/OCR/engine/OcrVersionConfig.cs
new file mode 100644
index 00000000..d0c6c58d
--- /dev/null
+++ b/BetterGenshinImpact/Core/Recognition/OCR/engine/OcrVersionConfig.cs
@@ -0,0 +1,37 @@
+using BetterGenshinImpact.Core.Recognition.OCR.paddle.data;
+
+namespace BetterGenshinImpact.Core.Recognition.OCR;
+
+///
+/// ppocr的版本配置
+///
+public readonly record struct OcrVersionConfig(
+ string Name,
+ OcrImgMode Mode,
+ bool ChannelFirst,
+ OcrNormalizeImage NormalizeImage,
+ OcrShape Shape)
+{
+ // 参数来自 https://github.com/PaddlePaddle/PaddleOCR/tree/main/configs/det/PP-OCRv3
+
+ public static OcrVersionConfig PpOcrV3 = new(
+ "PP-OCRv3",
+ OcrImgMode.BGR,
+ false,
+ new OcrNormalizeImage(
+ 1.0f / 255.0f,
+ [0.485f, 0.456f, 0.406f],
+ [0.229f, 0.224f, 0.225f]
+ ), new OcrShape(3, 320, 48)
+ );
+
+ public static OcrVersionConfig PpOcrV4 = new(
+ "PP-OCRv4",
+ OcrImgMode.BGR,
+ false,
+ new OcrNormalizeImage(
+ 1.0f / 255.0f,
+ [0.485f, 0.456f, 0.406f],
+ [0.229f, 0.224f, 0.225f]
+ ), new OcrShape(3, 320, 48));
+}
\ No newline at end of file
diff --git a/BetterGenshinImpact/Core/Recognition/OCR/engine/data/OcrImgMode.cs b/BetterGenshinImpact/Core/Recognition/OCR/engine/data/OcrImgMode.cs
new file mode 100644
index 00000000..7d734627
--- /dev/null
+++ b/BetterGenshinImpact/Core/Recognition/OCR/engine/data/OcrImgMode.cs
@@ -0,0 +1,9 @@
+namespace BetterGenshinImpact.Core.Recognition.OCR;
+///
+/// 图像的颜色顺序
+///
+public enum OcrImgMode
+{
+ BGR,
+ RGB
+}
\ No newline at end of file
diff --git a/BetterGenshinImpact/Core/Recognition/OCR/engine/data/OcrMatOrder.cs b/BetterGenshinImpact/Core/Recognition/OCR/engine/data/OcrMatOrder.cs
new file mode 100644
index 00000000..28f5a899
--- /dev/null
+++ b/BetterGenshinImpact/Core/Recognition/OCR/engine/data/OcrMatOrder.cs
@@ -0,0 +1,11 @@
+namespace BetterGenshinImpact.Core.Recognition.OCR;
+///
+/// Mat的通道顺序
+/// hwc: height width channel
+/// chw: channel height width
+///
+public enum OcrMatOrder
+{
+ Hwc,
+ Chw
+}
\ No newline at end of file
diff --git a/BetterGenshinImpact/Core/Recognition/OCR/engine/data/OcrNormalizeImage.cs b/BetterGenshinImpact/Core/Recognition/OCR/engine/data/OcrNormalizeImage.cs
new file mode 100644
index 00000000..b5077403
--- /dev/null
+++ b/BetterGenshinImpact/Core/Recognition/OCR/engine/data/OcrNormalizeImage.cs
@@ -0,0 +1,5 @@
+namespace BetterGenshinImpact.Core.Recognition.OCR;
+///
+/// 标准归一化的三个参数
+///
+public record OcrNormalizeImage(float Scale, float[] Mean, float[] Std);
\ No newline at end of file
diff --git a/BetterGenshinImpact/Core/Recognition/OCR/engine/data/OcrShape.cs b/BetterGenshinImpact/Core/Recognition/OCR/engine/data/OcrShape.cs
new file mode 100644
index 00000000..82bfe847
--- /dev/null
+++ b/BetterGenshinImpact/Core/Recognition/OCR/engine/data/OcrShape.cs
@@ -0,0 +1,6 @@
+namespace BetterGenshinImpact.Core.Recognition.OCR.paddle.data;
+
+///
+/// 图像形状表示
+///
+public readonly record struct OcrShape(int Channel, int Width, int Height);
\ No newline at end of file
diff --git a/BetterGenshinImpact/Core/Recognition/OCR/paddle/Det.cs b/BetterGenshinImpact/Core/Recognition/OCR/paddle/Det.cs
new file mode 100644
index 00000000..e41af092
--- /dev/null
+++ b/BetterGenshinImpact/Core/Recognition/OCR/paddle/Det.cs
@@ -0,0 +1,184 @@
+using System;
+using System.Linq;
+using BetterGenshinImpact.Core.Recognition.ONNX;
+using Microsoft.ML.OnnxRuntime;
+using Microsoft.ML.OnnxRuntime.Tensors;
+using OpenCvSharp;
+
+namespace BetterGenshinImpact.Core.Recognition.OCR.paddle;
+
+public class Det
+{
+ private readonly OcrVersionConfig _config;
+ private readonly InferenceSession _session;
+
+ public Det(BgiOnnxModel model, OcrVersionConfig config)
+ {
+ _config = config;
+ _session = BgiOnnxFactory.Instance.CreateInferenceSession(model,true);
+ }
+
+ /// Gets or sets the maximum size for resizing the input image.
+ public int? MaxSize { get; set; } = 1536;
+
+ /// Gets or sets the size for dilation during preprocessing.
+ public int? DilatedSize { get; set; } = 2;
+
+ /// Gets or sets the score threshold for filtering out possible text boxes.
+ public float? BoxScoreThreshold { get; set; } = 0.7f;
+
+ /// Gets or sets the threshold to binarize the text region.
+ public float? BoxThreshold { get; set; } = 0.3f;
+
+ /// Gets or sets the minimum size of the text boxes to be considered as valid.
+ public int MinSize { get; set; } = 3;
+
+ /// Gets or sets the ratio for enlarging text boxes during post-processing.
+ public float UnclipRatio { get; set; } = 2.0f;
+
+ ~Det()
+ {
+ _session.Dispose();
+ }
+
+ public RotatedRect[] Run(Mat src)
+ {
+ using var pred = RunRaw(src, out var resizedSize);
+ using Mat cbuf = new();
+ //OpenCvSharp.OpenCVException: 0 <= _colRange.start && _colRange.start <= _colRange.end && _colRange.end <= m.cols
+ using var roi = pred[0, resizedSize.Height, 0, resizedSize.Width];
+ roi.ConvertTo(cbuf, MatType.CV_8UC1, 255);
+ using Mat dilated = new();
+ using var binary = BoxThreshold != null
+ ? cbuf.Threshold((int)(BoxThreshold * 255), 255, ThresholdTypes.Binary)
+ : cbuf;
+ if (DilatedSize != null)
+ {
+ using var ones =
+ Cv2.GetStructuringElement(MorphShapes.Rect, new Size(DilatedSize.Value, DilatedSize.Value));
+ Cv2.Dilate(binary, dilated, ones);
+ }
+ else
+ {
+ Cv2.CopyTo(binary, dilated);
+ }
+
+ var contours = dilated.FindContoursAsArray(RetrievalModes.List, ContourApproximationModes.ApproxSimple);
+ // var size = src.Size();
+ var scaleRate = 1.0 * src.Width / resizedSize.Width;
+
+ var rects = contours
+ .Where(x => BoxScoreThreshold == null || GetScore(x, pred) > BoxScoreThreshold)
+ .Select(Cv2.MinAreaRect)
+ .Where(x => x.Size.Width > MinSize && x.Size.Height > MinSize)
+ .Select(rect =>
+ {
+ var minEdge = Math.Min(rect.Size.Width, rect.Size.Height);
+ Size2f newSize = new(
+ (rect.Size.Width + UnclipRatio * minEdge) * scaleRate,
+ (rect.Size.Height + UnclipRatio * minEdge) * scaleRate);
+ RotatedRect largerRect = new(rect.Center * scaleRate, newSize, rect.Angle);
+ return largerRect;
+ })
+ .OrderBy(v => v.Center.Y)
+ .ThenBy(v => v.Center.X)
+ .ToArray();
+ //{
+ // using Mat demo = dilated.CvtColor(ColorConversionCodes.GRAY2RGB);
+ // demo.DrawContours(contours, -1, Scalar.Red);
+ // Image(demo).Dump();
+ //}
+ return rects;
+ }
+
+ public Mat RunRaw(Mat src, out Size resizedSize)
+ {
+ var padded = src.Channels() switch
+ {
+ 4 => src.CvtColor(ColorConversionCodes.BGRA2BGR),
+ 1 => src.CvtColor(ColorConversionCodes.GRAY2BGR),
+ 3 => src,
+ var x => throw new Exception($"Unexpect src channel: {x}, allow: (1/3/4)")
+ };
+ using (var resized = MatResize(padded, MaxSize))
+ {
+ resizedSize = new Size(resized.Width, resized.Height);
+ padded = MatPadding32(resized);
+ }
+
+ using (var _ = padded)
+ {
+ var inputTensor = OcrUtils.NormalizeToTensorDnn(padded, _config.NormalizeImage.Scale,
+ _config.NormalizeImage.Mean, _config.NormalizeImage.Std, out var owner);
+ using (owner)
+ {
+ lock (_session)
+ {
+ using IDisposableReadOnlyCollection results = _session.Run([
+ NamedOnnxValue.CreateFromTensor(_session.InputNames[0], inputTensor)
+ ]);
+ var output = results[0];
+ if (output.ElementType is not TensorElementType.Float)
+ throw new Exception($"Unexpected output tensor type: {output.ElementType}");
+
+ if (output.ValueType is not OnnxValueType.ONNX_TYPE_TENSOR)
+ throw new Exception($"Unexpected output tensor value type: {output.ValueType}");
+ var outputTensor = output.AsTensor();
+ return OcrUtils.Tensor2Mat(tensor: outputTensor);
+ // 因为一个已知bug,tensor中内存在dml下使用完后会被释放掉,锁之外的代码会报错
+ }
+ }
+ }
+ }
+
+ private static Mat MatPadding32(Mat src)
+ {
+ var size = src.Size();
+ Size newSize = new(
+ 32 * Math.Ceiling(1.0 * size.Width / 32),
+ 32 * Math.Ceiling(1.0 * size.Height / 32));
+ return src.CopyMakeBorder(0, newSize.Height - size.Height, 0, newSize.Width - size.Width, BorderTypes.Constant,
+ Scalar.Black);
+ }
+
+ private static Mat MatResize(Mat src, int? maxSize)
+ {
+ if (maxSize == null) return src.Clone();
+
+ var size = src.Size();
+ var longEdge = Math.Max(size.Width, size.Height);
+ var scaleRate = 1.0 * maxSize.Value / longEdge;
+ return scaleRate < 1.0 ? src.Resize(default, scaleRate, scaleRate) : src.Clone();
+ }
+
+ private static float GetScore(Point[] contour, Mat pred)
+ {
+ var width = pred.Width;
+ var height = pred.Height;
+ var boxX = contour.Select(v => v.X).ToArray();
+ var boxY = contour.Select(v => v.Y).ToArray();
+
+ var xmin = Math.Clamp(boxX.Min(), 0, width - 1);
+ var xmax = Math.Clamp(boxX.Max(), 0, width - 1);
+ var ymin = Math.Clamp(boxY.Min(), 0, height - 1);
+ var ymax = Math.Clamp(boxY.Max(), 0, height - 1);
+
+ var rootPoints = contour
+ .Select(v => new Point(v.X - xmin, v.Y - ymin))
+ .ToArray();
+ using Mat mask = new(ymax - ymin + 1, xmax - xmin + 1, MatType.CV_8UC1, Scalar.Black);
+ mask.FillPoly(new[] { rootPoints }, new Scalar(1));
+
+ using var croppedMat = pred[ymin, ymax + 1, xmin, xmax + 1];
+ var score = (float)croppedMat.Mean(mask).Val0;
+
+ // Debug
+ //{
+ // using Mat cu = new Mat();
+ // croppedMat.ConvertTo(cu, MatType.CV_8UC1, 255);
+ // Util.HorizontalRun(true, Image(cu), Image(mask), score).Dump();
+ //}
+
+ return score;
+ }
+}
\ No newline at end of file
diff --git a/BetterGenshinImpact/Core/Recognition/OCR/paddle/PaddleOcrService.cs b/BetterGenshinImpact/Core/Recognition/OCR/paddle/PaddleOcrService.cs
new file mode 100644
index 00000000..a208eb56
--- /dev/null
+++ b/BetterGenshinImpact/Core/Recognition/OCR/paddle/PaddleOcrService.cs
@@ -0,0 +1,130 @@
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using BetterGenshinImpact.Core.Config;
+using BetterGenshinImpact.Core.Recognition.OCR.paddle;
+using BetterGenshinImpact.Core.Recognition.ONNX;
+using OpenCvSharp;
+
+namespace BetterGenshinImpact.Core.Recognition.OCR;
+
+public class PaddleOcrService : IOcrService
+{
+ ///
+ /// Usage:
+ /// https://github.com/sdcb/PaddleSharp/blob/master/docs/ocr.md
+ /// 模型列表:
+ /// https://github.com/PaddlePaddle/PaddleOCR/blob/release/2.5/doc/doc_ch/models_list.md
+ ///
+ private readonly Det localDetModel;
+
+ private readonly Rec localRecModel;
+
+ public PaddleOcrService(string? cultureInfoName = null)
+ {
+ var path = Global.Absolute(@"Assets\Model\PaddleOcr");
+
+ switch (cultureInfoName)
+ {
+ case "zh-Hant":
+ localDetModel = new Det(BgiOnnxModel.PaddleOcrChDet, OcrVersionConfig.PpOcrV4);
+ localRecModel = new Rec(BgiOnnxModel.PaddleOcrChtRec, Path.Combine(path, "chinese_cht_dict.txt"),
+ OcrVersionConfig.PpOcrV3);
+ break;
+ case "fr":
+ localDetModel = new Det(BgiOnnxModel.PaddleOcrEnDet, OcrVersionConfig.PpOcrV3);
+ localRecModel = new Rec(BgiOnnxModel.PaddleOcrLatinRec, Path.Combine(path, "latin_dict.txt"),
+ OcrVersionConfig.PpOcrV3);
+ break;
+ default:
+ localDetModel = new Det(BgiOnnxModel.PaddleOcrChDet, OcrVersionConfig.PpOcrV4);
+ localRecModel = new Rec(BgiOnnxModel.PaddleOcrChRec, Path.Combine(path, "ppocr_keys_v1.txt"),
+ OcrVersionConfig.PpOcrV4);
+
+ break;
+ }
+ }
+
+ ///
+ /// 推荐传入三通道BGR mat,虽然四通道和单通道也做了兼容,但是三通道最快
+ ///
+ public string Ocr(Mat mat)
+ {
+ return OcrResult(mat).Text;
+ }
+
+ ///
+ /// 推荐传入三通道BGR mat,虽然四通道和单通道也做了兼容,但是三通道最快
+ ///
+ public OcrResult OcrResult(Mat mat)
+ {
+ if (mat.Channels() == 4)
+ {
+ using var mat3 = mat.CvtColor(ColorConversionCodes.BGRA2BGR);
+ return _OcrResult(mat3);
+ }
+
+ return _OcrResult(mat);
+ }
+
+ ///
+ /// 推荐传入三通道BGR mat,虽然四通道和单通道也做了兼容,但是三通道最快
+ ///
+ public string OcrWithoutDetector(Mat mat)
+ {
+ var str = localRecModel.Run(mat).Text;
+ Debug.WriteLine($"PaddleOcrWithoutDetector 结果: {str}");
+ return str;
+ }
+
+ private OcrResult _OcrResult(Mat mat)
+ {
+ var startTime = Stopwatch.GetTimestamp();
+ var result = RunAll(mat);
+ var time = Stopwatch.GetElapsedTime(startTime);
+ Debug.WriteLine($"PaddleOcr 耗时 {time.TotalMilliseconds}ms 结果: {result.Text}");
+ return result;
+ }
+
+ ///
+ /// 推荐传入三通道BGR mat,虽然四通道和单通道也做了兼容,但是三通道最快
+ ///
+ private OcrResult RunAll(Mat src, int recognizeBatchSize = 0)
+ {
+ var rects = localDetModel.Run(src);
+ Mat[] mats =
+ rects.Select(rect =>
+ {
+ var roi = src[GetCropedRect(rect.BoundingRect(), src.Size())];
+ return roi;
+ })
+ .ToArray();
+ try
+ {
+ return new OcrResult(localRecModel.Run(mats, recognizeBatchSize)
+ .Select((result, i) => new OcrResultRegion(rects[i], result.Text, result.Score))
+ .ToArray());
+ }
+ finally
+ {
+ foreach (var mat in mats) mat.Dispose();
+ }
+ }
+
+ ///
+ /// Gets the cropped region of the source image specified by the given rectangle, clamping the rectangle coordinates to
+ /// the image bounds.
+ ///
+ /// The rectangle to crop.
+ /// The size of the source image.
+ /// The cropped rectangle.
+ private static Rect GetCropedRect(Rect rect, Size size)
+ {
+ return Rect.FromLTRB(
+ Math.Clamp(rect.Left, 0, size.Width),
+ Math.Clamp(rect.Top, 0, size.Height),
+ Math.Clamp(rect.Right, 0, size.Width),
+ Math.Clamp(rect.Bottom, 0, size.Height));
+ }
+}
\ No newline at end of file
diff --git a/BetterGenshinImpact/Core/Recognition/OCR/paddle/Rec.cs b/BetterGenshinImpact/Core/Recognition/OCR/paddle/Rec.cs
new file mode 100644
index 00000000..f5fe1cb2
--- /dev/null
+++ b/BetterGenshinImpact/Core/Recognition/OCR/paddle/Rec.cs
@@ -0,0 +1,177 @@
+using System;
+using System.Buffers;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using BetterGenshinImpact.Core.Recognition.OCR.paddle.data;
+using BetterGenshinImpact.Core.Recognition.ONNX;
+using Microsoft.ML.OnnxRuntime;
+using Microsoft.ML.OnnxRuntime.Tensors;
+using OpenCvSharp;
+
+namespace BetterGenshinImpact.Core.Recognition.OCR.paddle;
+
+public class Rec
+{
+ private readonly OcrVersionConfig _config;
+ private readonly IReadOnlyList _labels;
+ private readonly InferenceSession _session;
+
+ public Rec(BgiOnnxModel model, string labelFilePath, OcrVersionConfig config)
+ {
+ _config = config;
+ _session = BgiOnnxFactory.Instance.CreateInferenceSession(model,true);
+
+
+ _labels = File.ReadAllLines(labelFilePath);
+ }
+
+ ~Rec()
+ {
+ lock (_session)
+ {
+ _session.Dispose();
+ }
+ }
+
+ ///
+ /// Run OCR recognition on multiple images in batches.
+ ///
+ /// Array of images for OCR recognition.
+ /// Size of the batch to run OCR recognition on.
+ /// Array of instances corresponding to OCR recognition results of the images.
+ public OcrRecognizerResult[] Run(Mat[] srcs, int batchSize = 0)
+ {
+ if (srcs.Length == 0) return [];
+
+ var chooseBatchSize = batchSize != 0 ? batchSize : Math.Min(8, Environment.ProcessorCount);
+
+ return srcs
+ .Select((x, i) => (mat: x, i))
+ .OrderBy(x => x.mat.Width)
+ .Chunk(chooseBatchSize)
+ .Select(x => (result: RunMulti(x.Select(x1 => x1.mat).ToArray()), ids: x.Select(x1 => x1.i).ToArray()))
+ .SelectMany(x => x.result.Zip(x.ids, (result, i) => (result, i)))
+ .OrderBy(x => x.i)
+ .Select(x => x.result)
+ .ToArray();
+ }
+
+ public OcrRecognizerResult Run(Mat src)
+ {
+ return RunMulti([src]).Single();
+ }
+
+ private OcrRecognizerResult[] RunMulti(Mat[] srcs)
+ {
+ if (srcs.Length == 0) return [];
+
+ for (var i = 0; i < srcs.Length; ++i)
+ {
+ var src = srcs[i];
+ if (src.Empty())
+ throw new ArgumentException($"src[{i}] size should not be 0, wrong input picture provided?");
+ }
+
+ var modelHeight = _config.Shape.Height;
+ var maxWidth = (int)Math.Ceiling(srcs.Max(src =>
+ {
+ var size = src.Size();
+ return 1.0 * size.Width / size.Height * modelHeight;
+ }));
+ List> owners = [];
+ (int[], float[])[] resultTensors;
+ try
+ {
+ resultTensors = srcs
+ .Select(src =>
+ {
+ using var channel3 = src.Channels() switch
+ {
+ 4 => src.CvtColor(ColorConversionCodes.BGRA2BGR),
+ 1 => src.CvtColor(ColorConversionCodes.GRAY2BGR),
+ 3 => src,
+ var x => throw new Exception($"Unexpect src channel: {x}, allow: (1/3/4)")
+ };
+ var result = OcrUtils.resize_norm_img(channel3, new OcrShape(3, maxWidth, modelHeight),
+ out var owner);
+ lock (owners)
+ {
+ owners.Add(owner);
+ }
+
+ return result;
+ })
+ .Select(
+ inputTensor =>
+ {
+ lock (_session)
+ {
+ // 多线程推理会出现问题,加锁解决。
+ using IDisposableReadOnlyCollection results = _session.Run([
+ NamedOnnxValue.CreateFromTensor(_session.InputNames[0], inputTensor)
+ ]);
+ var output = results[0];
+ if (output.ElementType is not TensorElementType.Float)
+ throw new Exception($"Unexpected output tensor type: {output.ElementType}");
+
+ if (output.ValueType is not OnnxValueType.ONNX_TYPE_TENSOR)
+ throw new Exception($"Unexpected output tensor value type: {output.ValueType}");
+ var tensor = output.AsTensor();
+ // 因为一个已知bug,tensor中内存在dml下使用完后会被释放掉,锁之外的代码会报错
+ return (tensor.Dimensions.ToArray(), tensor.ToArray());
+ }
+ }
+ ).ToArray();
+ }
+ finally
+ {
+ owners.ForEach(x => { x.Dispose(); });
+ }
+
+ return resultTensors.SelectMany(
+ resultTensor =>
+ {
+ var resultArray = resultTensor.Item2;
+ var resultShape = resultTensor.Item1;
+ GCHandle dataHandle = default;
+ try
+ {
+ dataHandle = GCHandle.Alloc(resultArray, GCHandleType.Pinned);
+ var dataPtr = dataHandle.AddrOfPinnedObject();
+ var labelCount = resultShape[2];
+ var charCount = resultShape[1];
+
+ return Enumerable.Range(0, resultShape[0])
+ .Select(i =>
+ {
+ StringBuilder sb = new();
+ var lastIndex = 0;
+ float score = 0;
+ for (var n = 0; n < charCount; ++n)
+ {
+ using var mat = Mat.FromPixelData(1, labelCount, MatType.CV_32FC1,
+ dataPtr + (n + i * charCount) * labelCount * sizeof(float));
+ var maxIdx = new int[2];
+ mat.MinMaxIdx(out _, out var maxVal, [], maxIdx);
+
+ if (maxIdx[1] > 0 && !(n > 0 && maxIdx[1] == lastIndex))
+ {
+ score += (float)maxVal;
+ sb.Append(OcrUtils.GetLabelByIndex(maxIdx[1], _labels));
+ }
+ lastIndex = maxIdx[1];
+ }
+ return new OcrRecognizerResult(sb.ToString(), score / sb.Length);
+ })
+ .ToArray();
+ }
+ finally
+ {
+ dataHandle.Free();
+ }
+ }).ToArray();
+ }
+}
\ No newline at end of file
diff --git a/BetterGenshinImpact/Core/Recognition/ONNX/BgiOnnxFactory.cs b/BetterGenshinImpact/Core/Recognition/ONNX/BgiOnnxFactory.cs
new file mode 100644
index 00000000..b7303554
--- /dev/null
+++ b/BetterGenshinImpact/Core/Recognition/ONNX/BgiOnnxFactory.cs
@@ -0,0 +1,473 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using BetterGenshinImpact.Core.Config;
+using BetterGenshinImpact.GameTask;
+using BetterGenshinImpact.Model;
+using Microsoft.Extensions.Logging;
+using Microsoft.ML.OnnxRuntime;
+using Microsoft.Win32;
+using Vanara;
+
+namespace BetterGenshinImpact.Core.Recognition.ONNX;
+
+public class BgiOnnxFactory : Singleton
+{
+ private static readonly ILogger Logger = App.GetLogger();
+
+
+ public ProviderType[] ProviderTypes { get; }
+ public int DmlDeviceId { get; }
+ public int CudaDeviceId { get; }
+ public bool OptimizedModel { get; }
+ public bool TrtUseEmbedMode { get; }
+ public bool EnableCache { get; }
+ public bool CpuOcr { get; }
+
+ ///
+ /// 缓存模型路径。如果一开始使用缓存就一直使用缓存文件,如果没有使用缓存就一直使用原始模型路径。
+ ///
+ /// 这样能避免并发加载模型问题。比如使用了未完全构建好的缓存文件,导致模型加载失败。
+ ///
+ private ConcurrentDictionary _cachedModelPaths = new();
+
+
+ public BgiOnnxFactory()
+ {
+ var config = TaskContext.Instance().Config.HardwareAccelerationConfig;
+ if (config.AutoAppendCudaPath)
+ {
+ AppendCudaPath();
+ }
+
+ if (string.IsNullOrWhiteSpace(config.AdditionalPath))
+ {
+ AppendPath(config.AdditionalPath.Split(Path.PathSeparator));
+ }
+
+ ProviderTypes = GetProviderType(config.InferenceDevice, CudaDeviceId, DmlDeviceId);
+ OptimizedModel = config.OptimizedModel;
+ CudaDeviceId = config.CudaDevice;
+ DmlDeviceId = config.GpuDevice;
+ TrtUseEmbedMode = config.EmbedTensorRtCache;
+ EnableCache = config.EnableTensorRtCache;
+ CpuOcr = config.CpuOcr;
+ Logger.LogDebug(
+ "[ONNX]启用的provider:{Device},初始化参数: InferenceDevice={InferenceDevice}, OptimizedModel={OptimizedModel}, CudaDeviceId={CudaDeviceId}, DmlDeviceId={DmlDeviceId}, EmbedTensorRtCache={EmbedTensorRtCache}, EnableTensorRtCache={EnableTensorRtCache}, CpuOcr={CpuOcr}",
+ string.Join(",", ProviderTypes.Select(Enum.GetName)),
+ config.InferenceDevice,
+ OptimizedModel,
+ CudaDeviceId,
+ DmlDeviceId,
+ TrtUseEmbedMode,
+ EnableCache,
+ CpuOcr);
+ }
+
+
+ ///
+ /// 根据InferenceDeviceType选择Provider
+ ///
+ /// InferenceDeviceType
+ /// cuda设备id
+ /// dml设备id
+ ///
+ ///
+ private static ProviderType[] GetProviderType(InferenceDeviceType inferenceDeviceType, int cudaDeviceId,
+ int dmlDeviceId)
+ {
+ switch (inferenceDeviceType)
+ {
+ case InferenceDeviceType.Cpu:
+ return [ProviderType.Cpu];
+ case InferenceDeviceType.GpuDirectMl:
+ //只用dml不加cpu的话在很多场景下性能很差。
+ return [ProviderType.Dml, ProviderType.Cpu];
+ case InferenceDeviceType.Gpu:
+ List list = [];
+ SessionOptions? testSession = null;
+ var hasGpu = false;
+ if (!hasGpu && cudaDeviceId >= 0)
+ {
+ // tensorrt本身包含cuda,设备id也是cuda的id,且比纯cuda效果好很多。
+ try
+ {
+ testSession = SessionOptions.MakeSessionOptionWithTensorrtProvider(cudaDeviceId);
+ list.Add(ProviderType.TensorRt);
+ hasGpu = true;
+ }
+ catch (Exception e)
+ {
+ Logger.LogDebug("[init]无法加载TensorRt。可能不支持,跳过。({Err})", e.Message);
+ }
+ finally
+ {
+ testSession?.Dispose();
+ }
+ }
+
+ if (!hasGpu && dmlDeviceId >= 0)
+ {
+ // dml效果不如tensorrt,但是比纯cuda稳定性强
+ try
+ {
+ testSession = new SessionOptions();
+ testSession.AppendExecutionProvider_DML(dmlDeviceId);
+ list.Add(ProviderType.Dml);
+ hasGpu = true;
+ }
+ catch (Exception e)
+ {
+ Logger.LogDebug("[init]无法加载DML。可能不支持,跳过。({Err})", e.Message);
+ }
+ finally
+ {
+ testSession?.Dispose();
+ }
+ }
+
+ if (!hasGpu && cudaDeviceId >= 0)
+ {
+ // cuda优先级比较低,因为跑起来并不太理想。
+ try
+ {
+ testSession = SessionOptions.MakeSessionOptionWithCudaProvider(cudaDeviceId);
+ list.Add(ProviderType.Cuda);
+ hasGpu = true;
+ }
+ catch (Exception e)
+ {
+ Logger.LogDebug("[init]无法加载Cuda。可能不支持,跳过。({Err})", e.Message);
+ }
+ finally
+ {
+ testSession?.Dispose();
+ }
+ }
+
+ if (!hasGpu)
+ {
+ Logger.LogWarning("[init]GPU自动选择失败,回退到CPU处理");
+ }
+
+ //无论如何都要加入cpu,一些计算在纯gpu上不被支持或性能很烂
+ list.Add(ProviderType.Cpu);
+ return list.ToArray();
+ default:
+ throw new InvalidEnumArgumentException("无效的推理设备");
+ }
+ }
+
+ ///
+ /// 自动嗅探并修改path以加载cuda
+ ///
+ private static void AppendCudaPath()
+ {
+ var cudaVersion =
+ Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\NVIDIA Corporation\GPU Computing Toolkit\CUDA",
+ "FirstVersionInstalled", null)?.ToString() ?? "v12.8";
+ string[] filePrefix = ["cudnn", "nvrtc", "cudart", "nvinfer", "cublas", "onnx"];
+ string[] environmentVariableNames = ["PATH", "CUDA_PATH", "CUDNN_PATH", "LD_LIBRARY_PATH"];
+
+ // 例如: CUDNN\v9.8\lib\12.8\x64
+ var validPaths = environmentVariableNames.SelectMany(s => Environment
+ // 获取所有可能包含CUDA/cuDNN路径的环境变量
+ .GetEnvironmentVariable(s, EnvironmentVariableTarget.Process)?
+ .Split(Path.PathSeparator) ?? []).Distinct()
+ // 环境变量下层文件夹
+ .SelectMany(s =>
+ // lib路径
+ [s, Path.Combine(s, cudaVersion), Path.Combine(s, "bin"), Path.Combine(s, "lib")])
+ .SelectMany(
+ // cuda的版本
+ s => cudaVersion.StartsWith("v", StringComparison.InvariantCultureIgnoreCase)
+ ? [s, Path.Combine(s, cudaVersion), Path.Combine(s, cudaVersion[1..])]
+ : [s, Path.Combine(s, cudaVersion)])
+ .SelectMany(s =>
+ {
+ // 体系架构
+ var architecture = Enum.GetName(RuntimeInformation.ProcessArchitecture);
+ if (architecture is null)
+ {
+ return [s];
+ }
+
+ return
+ [
+ s, Path.Combine(s, architecture), Path.Combine(s, architecture.ToLowerInvariant()),
+ Path.Combine(s, architecture.ToUpperInvariant())
+ ];
+ })
+ .Where(basePath => !string.IsNullOrWhiteSpace(basePath))
+ //构建完了需要搜索的路径,去重。
+ .Distinct()
+
+ //确定路径是否真的存在
+ .Where(Directory.Exists)
+ .SelectMany(s =>
+ //确定需要的文件是否存在
+ filePrefix.SelectMany(se =>
+ Directory.GetFiles(s, $"{se}*.dll").Select(Path.GetDirectoryName).WhereNotNull()))
+ //去重
+ .Distinct();
+
+ AppendPath(validPaths.ToArray());
+ }
+
+ ///
+ /// 将附加的path应用进来
+ ///
+ /// 附加的path字符串
+ private static void AppendPath(string[] extraPath)
+ {
+ if (extraPath.Length <= 0)
+ {
+ return;
+ }
+
+ var pathVariables = Environment.GetEnvironmentVariable("PATH", EnvironmentVariableTarget.Process)
+ ?.Split(Path.PathSeparator).ToList() ?? new List();
+ pathVariables.AddRange(extraPath);
+ if (pathVariables.Count <= 0)
+ {
+ Logger.LogWarning("[GpuAuto]SetCudaPath:No valid paths found.");
+ return;
+ }
+
+ var updatedPath = string.Join(Path.PathSeparator, pathVariables.Distinct());
+ Logger.LogDebug("[GpuAuto]修改进程PATH为:{UpdatedPath}", updatedPath);
+ Environment.SetEnvironmentVariable("PATH", updatedPath, EnvironmentVariableTarget.Process);
+ }
+
+ ///
+ /// 根据模型创建一个YoloPredictor
+ ///
+ /// 模型
+ /// BgiYoloPredictor
+ public BgiYoloPredictor CreateYoloPredictor(BgiOnnxModel model)
+ {
+ Logger.LogDebug("[Yolo]创建yolo预测器,模型: {ModelName}", model.Name);
+ if (!EnableCache)
+ {
+ return new BgiYoloPredictor(model, model.ModalPath, CreateSessionOptions(model, false));
+ }
+
+ var cached = GetCached(model);
+ return cached == null
+ ? new BgiYoloPredictor(model, model.ModalPath, CreateSessionOptions(model, true))
+ : new BgiYoloPredictor(model, cached, CreateSessionOptions(model, false));
+ }
+
+ ///
+ /// 根据模型创建一个onnx运行时的InferenceSession
+ ///
+ /// 模型
+ /// 是否是用于ocr的模型,默认false
+ /// InferenceSession
+ public InferenceSession CreateInferenceSession(BgiOnnxModel model, bool ocr = false)
+ {
+ Logger.LogDebug("[ONNX]创建推理会话,模型: {ModelName}", model.Name);
+ ProviderType[]? providerTypes = null;
+ if (CpuOcr && ocr)
+ {
+ providerTypes = [ProviderType.Cpu];
+ }
+
+ if (!EnableCache)
+ {
+ return new InferenceSession(model.ModalPath, CreateSessionOptions(model, false, providerTypes));
+ }
+
+ var cached = GetCached(model);
+ return cached == null
+ ? new InferenceSession(model.ModalPath, CreateSessionOptions(model, true, providerTypes))
+ : new InferenceSession(cached, CreateSessionOptions(model, false, providerTypes));
+ }
+
+ ///
+ /// 获取带有缓存的模型(目前只支持TensorRT)
+ ///
+ /// 模型
+ /// 带有缓存的模型绝对路径,null表示尚未创建缓存
+ private string? GetCached(BgiOnnxModel model)
+ {
+ // 目前只支持TensorRT
+ if (!ProviderTypes.Contains(ProviderType.TensorRt)) return null;
+ var result = _cachedModelPaths.GetOrAdd(model, _GetCached);
+ if (result is null)
+ {
+ return result;
+ }
+
+ // 判断文件是否存在
+ if (File.Exists(result))
+ {
+ return result;
+ }
+
+ Logger.LogWarning("[ONNX]模型 {Model} 的缓存文件可能已被删除,使用原始模型文件。", model.Name);
+ return null;
+ }
+
+ private string? _GetCached(BgiOnnxModel model)
+ {
+ if (model.ModelRelativePath.StartsWith(BgiOnnxModel.ModelCacheRelativePath) &&
+ model.ModelRelativePath.EndsWith("_ctx.onnx"))
+ {
+ // 这已经是带有缓存的文件路径了
+ return model.ModalPath;
+ }
+
+ var ctxA = Path.Combine(model.CachePath, "trt", "_ctx.onnx");
+ if (File.Exists(ctxA))
+ {
+ Logger.LogDebug("[ONNX]模型 {Model} 命中TRT匿名缓存文件: {Path}", model.Name, ctxA);
+ return ctxA;
+ }
+
+ var ctxB = Path.Combine(model.CachePath, "trt",
+ Path.GetFileNameWithoutExtension(model.ModalPath) + "_ctx.onnx");
+ if (File.Exists(ctxB))
+ {
+ Logger.LogDebug("[ONNX]模型 {Model} 命中TRT命名缓存文件: {Path}", model.Name, ctxB);
+ return ctxB;
+ }
+
+ Logger.LogDebug("[ONNX]没有找到模型 {Model} 的模型缓存文件。", model.Name);
+ return null;
+ }
+
+
+ ///
+ /// 通过模型路径生成SessionOptions
+ /// 如果加载的模型文件已经是带有缓存的模型,请将cacheFolder设为null避免重复生成。
+ ///
+ /// 模型路径
+ /// 是否生成缓存。有几种情况下不生成缓存:1为用户主动关闭,即enableCache为false。2为即将加载的模型文件已经是带有缓存的模型文件。
+ /// 强制使用的Provider,为空或null则不强制
+ ///
+ ///
+ private SessionOptions CreateSessionOptions(BgiOnnxModel path, bool genCache, ProviderType[]? forcedProvider = null)
+ {
+ var sessionOptions = new SessionOptions();
+ foreach (var type in
+ forcedProvider is null || forcedProvider.Length == 0 ? ProviderTypes : forcedProvider)
+ {
+ try
+ {
+ switch (type)
+ {
+ case ProviderType.Dml:
+ // DirectML 执行提供程序不支持在 onnxruntime 中使用内存模式优化或并行执行。在创建 InferenceSession 期间提供会话选项时,必须禁用这些选项,否则将返回错误。
+ sessionOptions.AppendExecutionProvider_DML(DmlDeviceId);
+ sessionOptions.EnableMemoryPattern = false;
+ sessionOptions.ExecutionMode = ExecutionMode.ORT_SEQUENTIAL;
+ break;
+ case ProviderType.Cpu:
+ sessionOptions.AppendExecutionProvider_CPU();
+ break;
+ case ProviderType.TensorRt:
+ using (var options = new OrtTensorRTProviderOptions())
+ {
+ options.UpdateOptions(GetTrtProviderConfig(genCache ? path.CachePath : null));
+ sessionOptions.AppendExecutionProvider_Tensorrt(options);
+ }
+
+ break;
+ case ProviderType.Cuda:
+ using (var options = new OrtCUDAProviderOptions())
+ {
+ options.UpdateOptions(GetCudaProviderConfig());
+ sessionOptions.AppendExecutionProvider_CUDA();
+ }
+
+ break;
+ default:
+ throw new InvalidEnumArgumentException("无效的推理设备");
+ }
+ }
+ catch (Exception e)
+ {
+ Logger.LogError("无法加载指定的 ONNX provider {Provider},跳过。请检查推理设备配置是否正确。({Err})", Enum.GetName(type),
+ e.Message);
+ }
+ }
+
+ if (!OptimizedModel) return sessionOptions;
+ if (!genCache) return sessionOptions;
+ var optPath = Path.Combine(path.CachePath, "optimized");
+ if (!Directory.Exists(optPath))
+ {
+ Directory.CreateDirectory(optPath);
+ }
+
+ sessionOptions.OptimizedModelFilePath = optPath;
+ return sessionOptions;
+ }
+
+
+ ///
+ /// 获取TensorRT的配置
+ ///
+ /// 缓存生成的目录
+ /// trt配置
+ private Dictionary GetTrtProviderConfig(string? cacheFolder)
+ {
+ if (cacheFolder is null)
+ {
+ // 不使用缓存目录
+ var r = new Dictionary
+ {
+ ["device_id"] = CudaDeviceId.ToString(),
+ };
+ return r;
+ }
+
+ var result = new Dictionary
+ {
+ ["trt_engine_cache_enable"] = "1",
+ ["trt_dump_ep_context_model"] = "1",
+ ["trt_ep_context_file_path"] = Global.Absolute(Path.Combine(cacheFolder, "trt")),
+ // ["trt_ep_context_embed_mode"] = "1", // 因为yoloSharp是把模型转为嵌入式运行,不这样会爆炸
+ // ["trt_engine_cache_path"] = ".\\" // 没必要了
+ ["trt_timing_cache_enable"] = "1",
+ ["trt_timing_cache_path"] =
+ Global.Absolute(Path.Combine(BgiOnnxModel.ModelCacheRelativePath, "trt_timing")),
+ // ["trt_force_timing_cache"] = "1",
+ ["device_id"] = CudaDeviceId.ToString(),
+ };
+ if (TrtUseEmbedMode)
+ {
+ result["trt_ep_context_embed_mode"] = "1";
+ }
+ else
+ {
+ result["trt_ep_context_embed_mode"] = "0";
+ result["trt_engine_cache_path"] = ".\\";
+ }
+
+ if (!Directory.Exists(result["trt_ep_context_file_path"]))
+ {
+ Directory.CreateDirectory(result["trt_ep_context_file_path"]);
+ }
+
+ return result;
+ }
+
+ ///
+ /// 获取cuda provider的配置
+ ///
+ /// cuda配置
+ private Dictionary GetCudaProviderConfig()
+ {
+ var result = new Dictionary
+ {
+ ["device_id"] = CudaDeviceId.ToString(),
+ };
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/BetterGenshinImpact/Core/Recognition/ONNX/BgiOnnxModel.cs b/BetterGenshinImpact/Core/Recognition/ONNX/BgiOnnxModel.cs
new file mode 100644
index 00000000..e5aae73d
--- /dev/null
+++ b/BetterGenshinImpact/Core/Recognition/ONNX/BgiOnnxModel.cs
@@ -0,0 +1,124 @@
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.IO;
+using BetterGenshinImpact.Core.Config;
+
+namespace BetterGenshinImpact.Core.Recognition.ONNX;
+
+public class BgiOnnxModel
+{
+ ///
+ /// 模型使用的缓存文件的相对目录
+ ///
+ public static readonly string ModelCacheRelativePath = Path.Combine("Cache", Global.Version, "Model");
+
+ private static readonly List RegisteredModels = [];
+ public string Name { get; private init; }
+ public string ModelRelativePath { get; private init; }
+ public string ModalPath => Global.Absolute(ModelRelativePath);
+ public string CacheRelativePath { get; private init; }
+ public string CachePath => Global.Absolute(CacheRelativePath);
+
+ #region 模型注册
+
+ // 模型注册在这里,这样可以方便预先对模型预热和缓存管理等操作,避免冲突。
+ // 硬编码虽然不那么优雅,但是也没想到什么好的解决办法
+ ///
+ /// yap文字识别
+ ///
+ public static readonly BgiOnnxModel YapModelTraining =
+ Register("YapModelTraining", @"Assets\Model\Yap\model_training.onnx");
+
+ ///
+ /// 钓鱼模型
+ ///
+ public static readonly BgiOnnxModel BgiFish = Register("BgiFish", @"Assets\Model\Fish\bgi_fish.onnx");
+
+ ///
+ /// 秘境中古树
+ ///
+ public static readonly BgiOnnxModel BgiTree = Register("BgiTree", @"Assets\Model\Domain\bgi_tree.onnx");
+
+ ///
+ /// 用于捡东西等的大世界模型
+ ///
+ public static readonly BgiOnnxModel BgiWorld = Register("BgiTree", @"Assets\Model\World\bgi_world.onnx");
+
+ ///
+ /// 角色识别
+ ///
+ public static readonly BgiOnnxModel BgiAvatarSide =
+ Register("BgiAvatarSide", @"Assets\Model\Common\avatar_side_classify_sim.onnx");
+
+ ///
+ /// paddleOCR V4 简体中文 检测模型
+ ///
+ public static readonly BgiOnnxModel PaddleOcrChDet =
+ Register("ch_PP-OCRv4_det", @"Assets\Model\PaddleOCR\ch_PP-OCRv4_det\slim_model.onnx");
+
+ ///
+ /// paddleOCR V4 简体中文 识别模型
+ ///
+ public static readonly BgiOnnxModel PaddleOcrChRec =
+ Register("ch_PP-OCRv4_rec", @"Assets\Model\PaddleOCR\ch_PP-OCRv4_rec\slim_model.onnx");
+
+ ///
+ /// paddleOCR V3 繁体中文 识别模型
+ ///
+ public static readonly BgiOnnxModel PaddleOcrChtRec =
+ Register("chinese_cht_PP-OCRv3_rec", @"Assets\Model\PaddleOCR\chinese_cht_PP-OCRv3_rec_infer\slim_model.onnx");
+
+ ///
+ /// paddleOCR V3 英文 检测模型
+ ///
+ public static readonly BgiOnnxModel PaddleOcrEnDet =
+ Register("en_PP-OCRv3_det", @"Assets\Model\PaddleOCR\en_PP-OCRv3_det_infer\slim_model.onnx");
+
+ ///
+ /// paddleOCR V3 拉丁文 识别模型
+ ///
+ public static readonly BgiOnnxModel PaddleOcrLatinRec =
+ Register("latin_PP-OCRv3_rec", @"Assets\Model\PaddleOCR\latin_PP-OCRv3_rec_infer\slim_model.onnx");
+
+ #endregion
+
+ private BgiOnnxModel(string name, string modelRelativePath, string cacheRelativePath)
+ {
+ Name = name;
+ ModelRelativePath = modelRelativePath;
+ CacheRelativePath = cacheRelativePath;
+ }
+
+ public static bool IsModelExist(BgiOnnxModel model)
+ {
+ return File.Exists(model.ModalPath);
+ }
+
+
+ ///
+ /// 获取全部已注册的模型文件
+ ///
+ ///
+ public static ImmutableList GetAll()
+ {
+ return RegisteredModels.ToImmutableList();
+ }
+
+ private static BgiOnnxModel Register(string name, string modelRelativePath)
+ {
+ return Register(name, modelRelativePath, Path.Combine(ModelCacheRelativePath, name));
+ }
+
+ private static BgiOnnxModel Register(string name, string modelRelativePath, string cacheRelativePath)
+ {
+ var model = new BgiOnnxModel(name, modelRelativePath, cacheRelativePath);
+ var cachePath = model.CachePath;
+ if (!Directory.Exists(cachePath))
+ {
+ Directory.CreateDirectory(cachePath);
+ }
+
+ RegisteredModels.Add(model);
+ return model;
+ }
+}
\ No newline at end of file
diff --git a/BetterGenshinImpact/Core/Recognition/ONNX/BgiSessionOption.cs b/BetterGenshinImpact/Core/Recognition/ONNX/BgiSessionOption.cs
deleted file mode 100644
index 6bd02057..00000000
--- a/BetterGenshinImpact/Core/Recognition/ONNX/BgiSessionOption.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-using BetterGenshinImpact.GameTask;
-using BetterGenshinImpact.Model;
-using Microsoft.ML.OnnxRuntime;
-using System.ComponentModel;
-
-namespace BetterGenshinImpact.Core.Recognition.ONNX;
-
-public class BgiSessionOption : Singleton
-{
- public static string[] InferenceDeviceTypes { get; } = ["CPU", "GPU_DirectML"];
-
- public SessionOptions Options { get; set; } = TaskContext.Instance().Config.InferenceDevice switch
- {
- "CPU" => new SessionOptions(),
- "GPU_DirectML" => MakeSessionOptionWithDirectMlProvider(),
- _ => throw new InvalidEnumArgumentException("无效的推理设备")
- };
-
- public static SessionOptions MakeSessionOptionWithDirectMlProvider()
- {
- var sessionOptions = new SessionOptions();
- sessionOptions.AppendExecutionProvider_DML(0);
- return sessionOptions;
- }
-
- // ///
- // /// 重新加载每个推理器(测试没用,只能重启)
- // ///
- // public void RefreshInference()
- // {
- // // 自动秘境每次都会NEW不用管
- // // Yap、自动钓鱼
- // GameTaskManager.RefreshTriggerConfigs();
- // }
-}
diff --git a/BetterGenshinImpact/Core/Recognition/ONNX/BgiYoloPredictor.cs b/BetterGenshinImpact/Core/Recognition/ONNX/BgiYoloPredictor.cs
new file mode 100644
index 00000000..296444ef
--- /dev/null
+++ b/BetterGenshinImpact/Core/Recognition/ONNX/BgiYoloPredictor.cs
@@ -0,0 +1,85 @@
+using System;
+using BetterGenshinImpact.GameTask.Model.Area;
+using OpenCvSharp;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Drawing.Imaging;
+using System.IO;
+using System.Linq;
+using System.Text.Json;
+using BetterGenshinImpact.View.Drawable;
+using Compunet.YoloSharp;
+using Microsoft.ML.OnnxRuntime;
+
+namespace BetterGenshinImpact.Core.Recognition.ONNX;
+
+public class BgiYoloPredictor : IDisposable
+{
+ private readonly BgiOnnxModel _model;
+
+
+ private readonly Lazy _lazyPredictor;
+
+ ///
+ /// 使用 BgiOnnxFactory 创建这个类的实例
+ ///
+ /// 模型
+ /// 实际要加载的模型文件的绝对路径,在使用模型缓存的场景下可能有差别
+ /// sessionOptions
+ protected internal BgiYoloPredictor(BgiOnnxModel onnxModel, string modelPath, SessionOptions sessionOptions)
+ {
+ _model = onnxModel;
+ _lazyPredictor = new Lazy(() => new YoloPredictor(modelPath,
+ new YoloPredictorOptions
+ {
+ SessionOptions = sessionOptions
+ }));
+ }
+
+ public YoloPredictor Predictor => _lazyPredictor.Value;
+
+ ///
+ /// 检测
+ ///
+ /// 图像
+ /// 类别-矩形框
+ public Dictionary> Detect(ImageRegion region)
+ {
+ using var memoryStream = new MemoryStream();
+ region.SrcBitmap.Save(memoryStream, ImageFormat.Bmp);
+ memoryStream.Seek(0, SeekOrigin.Begin);
+ var result = Predictor.Detect(memoryStream);
+
+
+ var dict = new Dictionary>();
+ foreach (var box in result)
+ {
+ if (!dict.TryGetValue(box.Name.Name, out var value))
+ {
+ dict[box.Name.Name] = [new Rect(box.Bounds.X, box.Bounds.Y, box.Bounds.Width, box.Bounds.Height)];
+ }
+ else
+ {
+ value.Add(new Rect(box.Bounds.X, box.Bounds.Y, box.Bounds.Width, box.Bounds.Height));
+ }
+ }
+
+ Debug.WriteLine("YOLO识别结果:" + JsonSerializer.Serialize(dict));
+
+ var list = result
+ .Select(box => new Rect(box.Bounds.X, box.Bounds.Y, box.Bounds.Width, box.Bounds.Height))
+ .Select(rect => region.ToRectDrawable(rect, _model.Name)).ToList();
+
+ VisionContext.Instance().DrawContent.PutOrRemoveRectList(_model.Name, list);
+
+ return dict;
+ }
+
+ public void Dispose()
+ {
+ if (_lazyPredictor.IsValueCreated)
+ {
+ Predictor.Dispose();
+ }
+ }
+}
\ No newline at end of file
diff --git a/BetterGenshinImpact/Core/Recognition/ONNX/BgiYoloV8Predictor.cs b/BetterGenshinImpact/Core/Recognition/ONNX/BgiYoloV8Predictor.cs
deleted file mode 100644
index 2ce81e3a..00000000
--- a/BetterGenshinImpact/Core/Recognition/ONNX/BgiYoloV8Predictor.cs
+++ /dev/null
@@ -1,66 +0,0 @@
-using System;
-using BetterGenshinImpact.Core.Config;
-using BetterGenshinImpact.GameTask.Model.Area;
-using Compunet.YoloV8;
-using OpenCvSharp;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Drawing.Imaging;
-using System.IO;
-using System.Text.Json;
-using BetterGenshinImpact.View.Drawable;
-
-namespace BetterGenshinImpact.Core.Recognition.ONNX;
-
-public class BgiYoloV8Predictor(string modelRelativePath) : IDisposable
-{
- private readonly YoloV8Predictor _predictor = YoloV8Builder.CreateDefaultBuilder()
- .UseOnnxModel(Global.Absolute(modelRelativePath))
- .WithSessionOptions(BgiSessionOption.Instance.Options)
- .Build();
-
- public YoloV8Predictor Predictor => _predictor;
-
- ///
- /// 检测
- ///
- /// 图像
- /// 类别-矩形框
- public Dictionary> Detect(ImageRegion region)
- {
- using var memoryStream = new MemoryStream();
- region.SrcBitmap.Save(memoryStream, ImageFormat.Bmp);
- memoryStream.Seek(0, SeekOrigin.Begin);
- var result = _predictor.Detect(memoryStream);
-
- var dict = new Dictionary>();
- foreach (var box in result.Boxes)
- {
- if (!dict.ContainsKey(box.Class.Name))
- {
- dict[box.Class.Name] = [new Rect(box.Bounds.X, box.Bounds.Y, box.Bounds.Width, box.Bounds.Height)];
- }
- else
- {
- dict[box.Class.Name].Add(new Rect(box.Bounds.X, box.Bounds.Y, box.Bounds.Width, box.Bounds.Height));
- }
- }
- Debug.WriteLine("YOLOv8识别结果:" + JsonSerializer.Serialize(dict));
-
- var list = new List();
- foreach (var box in result.Boxes)
- {
- var rect = new Rect(box.Bounds.X, box.Bounds.Y, box.Bounds.Width, box.Bounds.Height);
- list.Add(region.ToRectDrawable(rect, modelRelativePath));
- }
-
- VisionContext.Instance().DrawContent.PutOrRemoveRectList(modelRelativePath, list);
-
- return dict;
- }
-
- public void Dispose()
- {
- _predictor.Dispose();
- }
-}
diff --git a/BetterGenshinImpact/Core/Recognition/ONNX/BgiYoloV8PredictorFactory.cs b/BetterGenshinImpact/Core/Recognition/ONNX/BgiYoloV8PredictorFactory.cs
deleted file mode 100644
index dbad7e95..00000000
--- a/BetterGenshinImpact/Core/Recognition/ONNX/BgiYoloV8PredictorFactory.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-using System.Collections.Generic;
-
-namespace BetterGenshinImpact.Core.Recognition.ONNX;
-
-public class BgiYoloV8PredictorFactory
-{
- static Dictionary _predictors = new();
-
- public static BgiYoloV8Predictor GetPredictor(string modelRelativePath)
- {
- if (!_predictors.ContainsKey(modelRelativePath))
- {
- _predictors[modelRelativePath] = new BgiYoloV8Predictor(modelRelativePath);
- }
-
- return _predictors[modelRelativePath];
- }
-}
\ No newline at end of file
diff --git a/BetterGenshinImpact/Core/Recognition/ONNX/InferenceDeviceType.cs b/BetterGenshinImpact/Core/Recognition/ONNX/InferenceDeviceType.cs
new file mode 100644
index 00000000..b46fdd8b
--- /dev/null
+++ b/BetterGenshinImpact/Core/Recognition/ONNX/InferenceDeviceType.cs
@@ -0,0 +1,8 @@
+namespace BetterGenshinImpact.Core.Recognition.ONNX;
+
+public enum InferenceDeviceType
+{
+ Cpu,
+ GpuDirectMl,
+ Gpu
+}
\ No newline at end of file
diff --git a/BetterGenshinImpact/Core/Recognition/ONNX/ProviderType.cs b/BetterGenshinImpact/Core/Recognition/ONNX/ProviderType.cs
new file mode 100644
index 00000000..df7e0068
--- /dev/null
+++ b/BetterGenshinImpact/Core/Recognition/ONNX/ProviderType.cs
@@ -0,0 +1,11 @@
+
+
+namespace BetterGenshinImpact.Core.Recognition.ONNX;
+
+public enum ProviderType
+{
+ TensorRt,
+ Cuda,
+ Dml,
+ Cpu
+}
\ No newline at end of file
diff --git a/BetterGenshinImpact/Core/Recognition/ONNX/SVTR/PickTextInference.cs b/BetterGenshinImpact/Core/Recognition/ONNX/SVTR/PickTextInference.cs
index ceb0c6c7..38fcb3df 100644
--- a/BetterGenshinImpact/Core/Recognition/ONNX/SVTR/PickTextInference.cs
+++ b/BetterGenshinImpact/Core/Recognition/ONNX/SVTR/PickTextInference.cs
@@ -9,6 +9,7 @@ using System.Diagnostics;
using System.IO;
using System.Text;
using System.Text.Json;
+using BetterGenshinImpact.Core.Recognition.OCR;
namespace BetterGenshinImpact.Core.Recognition.ONNX.SVTR;
@@ -23,23 +24,21 @@ public class PickTextInference : ITextInference
public PickTextInference()
{
- var modelPath = Global.Absolute(@"Assets\Model\Yap\model_training.onnx");
- if (!File.Exists(modelPath)) throw new FileNotFoundException("Yap模型文件不存在", modelPath);
-
- _session = new InferenceSession(modelPath, BgiSessionOption.Instance.Options);
+ _session = BgiOnnxFactory.Instance.CreateInferenceSession(BgiOnnxModel.YapModelTraining,true);
var wordJsonPath = Global.Absolute(@"Assets\Model\Yap\index_2_word.json");
if (!File.Exists(wordJsonPath)) throw new FileNotFoundException("Yap字典文件不存在", wordJsonPath);
var json = File.ReadAllText(wordJsonPath);
- _wordDictionary = JsonSerializer.Deserialize>(json) ?? throw new Exception("index_2_word.json deserialize failed");
+ _wordDictionary = JsonSerializer.Deserialize>(json) ??
+ throw new Exception("index_2_word.json deserialize failed");
}
public string Inference(Mat mat)
{
long startTime = Stopwatch.GetTimestamp();
// 将输入数据调整为 (1, 1, 32, 384) 形状的张量
- var reshapedInputData = ToTensorUnsafe(mat, out var owner);
+ var reshapedInputData = OcrUtils.ToTensorYapDnn(mat, out var owner);
IDisposableReadOnlyCollection results;
@@ -86,6 +85,8 @@ public class PickTextInference : ITextInference
}
}
+
+ [Obsolete("使用CV DNN替代")]
public static Tensor ToTensorUnsafe(Mat src, out IMemoryOwner tensorMemoryOwnser)
{
var channels = src.Channels();
@@ -114,4 +115,4 @@ public class PickTextInference : ITextInference
return new DenseTensor(memory, [1, 1, 32, 384]);
}
-}
+}
\ No newline at end of file
diff --git a/BetterGenshinImpact/Core/Recognition/ONNX/SVTR/TextInferenceFactory.cs b/BetterGenshinImpact/Core/Recognition/ONNX/SVTR/TextInferenceFactory.cs
index 968732a9..a83620a2 100644
--- a/BetterGenshinImpact/Core/Recognition/ONNX/SVTR/TextInferenceFactory.cs
+++ b/BetterGenshinImpact/Core/Recognition/ONNX/SVTR/TextInferenceFactory.cs
@@ -1,4 +1,6 @@
using System;
+using BetterGenshinImpact.Core.Recognition.OpenCv;
+using OpenCvSharp;
namespace BetterGenshinImpact.Core.Recognition.ONNX.SVTR;
@@ -14,4 +16,32 @@ public class TextInferenceFactory
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null),
};
}
+ public static Mat PreProcessForInference(Mat mat)
+ {
+ if (mat.Channels() == 3)
+ {
+ mat = mat.CvtColor(ColorConversionCodes.BGR2GRAY);
+ }
+ else if (mat.Channels() == 4)
+ {
+ mat = mat.CvtColor(ColorConversionCodes.BGRA2GRAY);
+ }
+ else if (mat.Channels() != 1)
+ {
+ throw new ArgumentException("mat must be 1, 3 or 4 channels");
+ }
+
+ // Yap 已经改用灰度图了 https://github.com/Alex-Beng/Yap/commit/c2ad1e7b1442aaf2d80782a032e00876cd1c6c84
+ // 二值化
+ // Cv2.Threshold(mat, mat, 0, 255, ThresholdTypes.Otsu | ThresholdTypes.Binary);
+ //Cv2.AdaptiveThreshold(mat, mat, 255, AdaptiveThresholdTypes.GaussianC, ThresholdTypes.Binary, 31, 3); // 效果不错 但是和模型不搭
+ //mat = OpenCvCommonHelper.Threshold(mat, Scalar.FromRgb(235, 235, 235), Scalar.FromRgb(255, 255, 255)); // 识别物品不太行
+ // 不知道为什么要强制拉伸到 221x32
+ mat = ResizeHelper.ResizeTo(mat, 221, 32);
+ // 填充到 384x32
+ var padded = new Mat(new Size(384, 32), MatType.CV_8UC1, Scalar.Black);
+ padded[new Rect(0, 0, mat.Width, mat.Height)] = mat;
+ //Cv2.ImWrite(Global.Absolute("padded.png"), padded);
+ return padded;
+ }
}
diff --git a/BetterGenshinImpact/GameTask/AutoDomain/AutoDomainTask.cs b/BetterGenshinImpact/GameTask/AutoDomain/AutoDomainTask.cs
index 6130505f..02a1859f 100644
--- a/BetterGenshinImpact/GameTask/AutoDomain/AutoDomainTask.cs
+++ b/BetterGenshinImpact/GameTask/AutoDomain/AutoDomainTask.cs
@@ -3,7 +3,6 @@ using BetterGenshinImpact.Core.Recognition.OCR;
using BetterGenshinImpact.Core.Recognition.ONNX;
using BetterGenshinImpact.Core.Simulator;
using BetterGenshinImpact.Core.Simulator.Extensions;
-using BetterGenshinImpact.GameTask.AutoFight;
using BetterGenshinImpact.GameTask.AutoFight.Assets;
using BetterGenshinImpact.GameTask.AutoFight.Model;
using BetterGenshinImpact.GameTask.AutoFight.Script;
@@ -14,7 +13,6 @@ using BetterGenshinImpact.GameTask.Model.Area;
using BetterGenshinImpact.Helpers;
using BetterGenshinImpact.Service.Notification;
using BetterGenshinImpact.View.Drawable;
-using Compunet.YoloV8;
using Microsoft.Extensions.Logging;
using OpenCvSharp;
using System;
@@ -31,14 +29,12 @@ using BetterGenshinImpact.GameTask.Common.BgiVision;
using BetterGenshinImpact.GameTask.Common.Element.Assets;
using BetterGenshinImpact.GameTask.Common.Job;
using BetterGenshinImpact.Service.Notification.Model.Enum;
-using Vanara.PInvoke;
using static BetterGenshinImpact.GameTask.Common.TaskControl;
-using static Vanara.PInvoke.Kernel32;
-using static Vanara.PInvoke.User32;
using Microsoft.Extensions.Localization;
using System.Globalization;
using System.Text.RegularExpressions;
using BetterGenshinImpact.GameTask.AutoArtifactSalvage;
+using Compunet.YoloSharp;
namespace BetterGenshinImpact.GameTask.AutoDomain;
@@ -48,7 +44,7 @@ public class AutoDomainTask : ISoloTask
private readonly AutoDomainParam _taskParam;
- private readonly YoloV8Predictor _predictor;
+ private readonly BgiYoloPredictor _predictor;
private readonly AutoDomainConfig _config;
@@ -66,10 +62,7 @@ public class AutoDomainTask : ISoloTask
{
AutoFightAssets.DestroyInstance();
_taskParam = taskParam;
- _predictor = YoloV8Builder.CreateDefaultBuilder()
- .UseOnnxModel(Global.Absolute(@"Assets\Model\Domain\bgi_tree.onnx"))
- .WithSessionOptions(BgiSessionOption.Instance.Options)
- .Build();
+ _predictor = BgiOnnxFactory.Instance.CreateYoloPredictor(BgiOnnxModel.BgiTree);
_config = TaskContext.Instance().Config.AutoDomainConfig;
@@ -574,7 +567,7 @@ public class AutoDomainTask : ISoloTask
using var ra = CaptureToRectArea();
var endTipsRect = ra.DeriveCrop(AutoFightAssets.Instance.EndTipsUpperRect);
- var text = OcrFactory.Paddle.Ocr(endTipsRect.SrcGreyMat);
+ var text = OcrFactory.Paddle.Ocr(endTipsRect.SrcMat);
if (Regex.IsMatch(text, this.challengeCompletedLocalizedString))
{
Logger.LogInformation("检测到秘境结束提示(挑战达成),结束秘境");
@@ -582,7 +575,7 @@ public class AutoDomainTask : ISoloTask
}
endTipsRect = ra.DeriveCrop(AutoFightAssets.Instance.EndTipsRect);
- text = OcrFactory.Paddle.Ocr(endTipsRect.SrcGreyMat);
+ text = OcrFactory.Paddle.Ocr(endTipsRect.SrcMat);
if (Regex.IsMatch(text, this.autoLeavingLocalizedString))
{
Logger.LogInformation("检测到秘境结束提示(xxx秒后自动退出),结束秘境");
@@ -828,9 +821,9 @@ public class AutoDomainTask : ISoloTask
using var memoryStream = new MemoryStream();
region.SrcBitmap.Save(memoryStream, ImageFormat.Bmp);
memoryStream.Seek(0, SeekOrigin.Begin);
- var result = _predictor.Detect(memoryStream);
+ var result = _predictor.Predictor.Detect(memoryStream);
var list = new List();
- foreach (var box in result.Boxes)
+ foreach (var box in result)
{
var rect = new Rect(box.Bounds.X, box.Bounds.Y, box.Bounds.Width, box.Bounds.Height);
list.Add(region.ToRectDrawable(rect, "tree"));
@@ -840,7 +833,7 @@ public class AutoDomainTask : ISoloTask
if (list.Count > 0)
{
- var box = result.Boxes[0];
+ var box = result[0];
return new Rect(box.Bounds.X, box.Bounds.Y, box.Bounds.Width, box.Bounds.Height);
}
@@ -1043,7 +1036,7 @@ public class AutoDomainTask : ISoloTask
// 图像右侧就是脆弱树脂数量
var countArea = ra.DeriveCrop(fragileResinCountRa.X + fragileResinCountRa.Width, fragileResinCountRa.Y,
(int)(fragileResinCountRa.Width * 3), fragileResinCountRa.Height);
- var count = OcrFactory.Paddle.Ocr(countArea.SrcGreyMat);
+ var count = OcrFactory.Paddle.Ocr(countArea.SrcMat);
fragileResinCount = StringUtils.TryParseInt(count);
}
diff --git a/BetterGenshinImpact/GameTask/AutoFight/AutoFightTask.cs b/BetterGenshinImpact/GameTask/AutoFight/AutoFightTask.cs
index f20c7e42..7408a951 100644
--- a/BetterGenshinImpact/GameTask/AutoFight/AutoFightTask.cs
+++ b/BetterGenshinImpact/GameTask/AutoFight/AutoFightTask.cs
@@ -33,7 +33,7 @@ public class AutoFightTask : ISoloTask
private CancellationToken _ct;
- private readonly BgiYoloV8Predictor _predictor;
+ private readonly BgiYoloPredictor _predictor;
private DateTime _lastFightFlagTime = DateTime.Now; // 战斗标志最近一次出现的时间
@@ -185,7 +185,7 @@ public class AutoFightTask : ISoloTask
if (_taskParam.FightFinishDetectEnabled)
{
- _predictor = BgiYoloV8PredictorFactory.GetPredictor(@"Assets\Model\World\bgi_world.onnx");
+ _predictor = BgiOnnxFactory.Instance.CreateYoloPredictor(BgiOnnxModel.BgiWorld);
}
_finishDetectConfig = new TaskFightFinishDetectConfig(_taskParam.FinishDetectConfig);
diff --git a/BetterGenshinImpact/GameTask/AutoFight/Model/CombatScenes.cs b/BetterGenshinImpact/GameTask/AutoFight/Model/CombatScenes.cs
index d4a2607b..485d69c6 100644
--- a/BetterGenshinImpact/GameTask/AutoFight/Model/CombatScenes.cs
+++ b/BetterGenshinImpact/GameTask/AutoFight/Model/CombatScenes.cs
@@ -1,16 +1,13 @@
-using BetterGenshinImpact.Core.Config;
-using BetterGenshinImpact.Core.Recognition.OCR;
+using BetterGenshinImpact.Core.Recognition.OCR;
using BetterGenshinImpact.Core.Recognition.ONNX;
using BetterGenshinImpact.Core.Recognition.OpenCv;
using BetterGenshinImpact.GameTask.AutoFight.Assets;
using BetterGenshinImpact.GameTask.AutoFight.Config;
using BetterGenshinImpact.GameTask.Model.Area;
using BetterGenshinImpact.Helpers;
-using Compunet.YoloV8;
using Microsoft.Extensions.Logging;
using OpenCvSharp;
using OpenCvSharp.Extensions;
-using Sdcb.PaddleOCR;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
@@ -21,6 +18,8 @@ using System.IO;
using System.Linq;
using System.Threading;
using BetterGenshinImpact.Core.Simulator;
+using Compunet.YoloSharp;
+using Compunet.YoloSharp.Data;
using static BetterGenshinImpact.GameTask.Common.TaskControl;
namespace BetterGenshinImpact.GameTask.AutoFight.Model;
@@ -38,11 +37,8 @@ public class CombatScenes : IDisposable
public int AvatarCount => Avatars.Length;
- private readonly YoloV8Predictor _predictor =
- YoloV8Builder.CreateDefaultBuilder()
- .UseOnnxModel(Global.Absolute(@"Assets\Model\Common\avatar_side_classify_sim.onnx"))
- .WithSessionOptions(BgiSessionOption.Instance.Options)
- .Build();
+ private readonly BgiYoloPredictor _predictor =
+ BgiOnnxFactory.Instance.CreateYoloPredictor(BgiOnnxModel.BgiAvatarSide);
public int ExpectedTeamAvatarNum { get; private set; } = 4;
@@ -167,32 +163,32 @@ public class CombatScenes : IDisposable
src.Save(memoryStream, ImageFormat.Bmp);
memoryStream.Seek(0, SeekOrigin.Begin);
speedTimer.Record("角色侧面头像图像转换");
- var result = _predictor.Classify(memoryStream);
+ var result = _predictor.Predictor.Classify(memoryStream);
speedTimer.Record("角色侧面头像分类识别");
Debug.WriteLine($"角色侧面头像识别结果:{result}");
speedTimer.DebugPrint();
-
- if (result.TopClass.Name.Name.StartsWith("Qin") || result.TopClass.Name.Name.Contains("Costume"))
+ var topClass = result.GetTopClass();
+ if (topClass.Name.Name.StartsWith("Qin") || topClass.Name.Name.Contains("Costume"))
{
// 降低琴和衣装角色的识别率要求
- if (result.TopClass.Confidence < 0.51)
+ if (topClass.Confidence < 0.51)
{
Cv2.ImWrite(@"log\avatar_side_classify_error.png", src.ToMat());
throw new Exception(
- $"无法识别第{index}位角色,置信度{result.TopClass.Confidence:F1},结果:{result.TopClass.Name.Name}。请重新阅读 BetterGI 文档中的《快速上手》!");
+ $"无法识别第{index}位角色,置信度{topClass.Confidence:F1},结果:{topClass.Name.Name}。请重新阅读 BetterGI 文档中的《快速上手》!");
}
}
else
{
- if (result.TopClass.Confidence < 0.7)
+ if (topClass.Confidence < 0.7)
{
Cv2.ImWrite(@"log\avatar_side_classify_error.png", src.ToMat());
throw new Exception(
- $"无法识别第{index}位角色,置信度{result.TopClass.Confidence:F1},结果:{result.TopClass.Name.Name}。请重新阅读 BetterGI 文档中的《快速上手》!");
+ $"无法识别第{index}位角色,置信度{topClass.Confidence:F1},结果:{topClass.Name.Name}。请重新阅读 BetterGI 文档中的《快速上手》!");
}
}
- return result.TopClass.Name.Name;
+ return topClass.Name.Name;
}
private void InitializeTeamFromConfig(string teamNames)
@@ -393,7 +389,7 @@ public class CombatScenes : IDisposable
}
[Obsolete]
- private void ParseTeamOcrResult(PaddleOcrResult result, ImageRegion rectArea)
+ private void ParseTeamOcrResult(OcrResult result, ImageRegion rectArea)
{
List names = [];
List nameRects = [];
diff --git a/BetterGenshinImpact/GameTask/AutoFishing/AutoFishingTask.cs b/BetterGenshinImpact/GameTask/AutoFishing/AutoFishingTask.cs
index 6ca5b765..6f668d37 100644
--- a/BetterGenshinImpact/GameTask/AutoFishing/AutoFishingTask.cs
+++ b/BetterGenshinImpact/GameTask/AutoFishing/AutoFishingTask.cs
@@ -13,7 +13,6 @@ using BetterGenshinImpact.Core.Simulator;
using BetterGenshinImpact.View.Drawable;
using BetterGenshinImpact.GameTask.AutoFishing.Assets;
using Vanara.PInvoke;
-using Compunet.YoloV8;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
@@ -29,6 +28,7 @@ using System.Globalization;
using Microsoft.Extensions.Localization;
using BetterGenshinImpact.Helpers;
using BetterGenshinImpact.Core.Recognition.OCR;
+using Compunet.YoloSharp;
namespace BetterGenshinImpact.GameTask.AutoFishing
{
@@ -42,6 +42,9 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
private readonly AutoFishingTaskParam param;
+ private readonly BgiYoloPredictor _predictor =
+ BgiOnnxFactory.Instance.CreateYoloPredictor(BgiOnnxModel.BgiFish);
+
public AutoFishingTask(AutoFishingTaskParam param)
{
this.param = param;
@@ -53,10 +56,11 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
IOcrService ocrService = OcrFactory.Paddle;
- IStringLocalizer stringLocalizer = App.GetService>() ?? throw new NullReferenceException(nameof(stringLocalizer));
+ IStringLocalizer stringLocalizer =
+ App.GetService>() ??
+ throw new NullReferenceException(nameof(stringLocalizer));
- var predictor = YoloV8Builder.CreateDefaultBuilder().UseOnnxModel(Global.Absolute(@"Assets\Model\Fish\bgi_fish.onnx")).WithSessionOptions(BgiSessionOption.Instance.Options).Build();
- Blackboard blackboard = new Blackboard(predictor, this.Sleep, AutoFishingAssets.Instance);
+ Blackboard blackboard = new Blackboard(_predictor, this.Sleep, AutoFishingAssets.Instance);
// @formatter:off
var behaviourTree = FluentBuilder.Create()
@@ -122,7 +126,8 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
// @formatter:on
_logger.LogInformation("→ {Text}", "自动钓鱼,启动!");
_logger.LogWarning("请不要携带任何{Msg},极有可能会误识别导致无法结束自动钓鱼!", "跟宠");
- _logger.LogInformation($"当前参数:{param.WholeProcessTimeoutSeconds},{param.ThrowRodTimeOutTimeoutSeconds},{param.FishingTimePolicy}, {param.SaveScreenshotOnKeyTick}, {param.GameCultureInfo}");
+ _logger.LogInformation(
+ $"当前参数:{param.WholeProcessTimeoutSeconds},{param.ThrowRodTimeOutTimeoutSeconds},{param.FishingTimePolicy}, {param.SaveScreenshotOnKeyTick}, {param.GameCultureInfo}");
TaskContext.Instance().Config.AutoFishingConfig.Enabled = false;
_logger.LogInformation("全自动运行时,自动切换实时任务中的半自动钓鱼功能为关闭状态");
@@ -139,7 +144,8 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
break;
}
- using var bitmap = TaskControl.CaptureGameImageNoRetry(TaskTriggerDispatcher.Instance().GameCapture);
+ using var bitmap =
+ TaskControl.CaptureGameImageNoRetry(TaskTriggerDispatcher.Instance().GameCapture);
if (bitmap == null)
{
_logger.LogWarning("截图失败");
@@ -177,7 +183,9 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
else
{
SetTimeTask setTimeTask = new SetTimeTask();
- foreach (int hour in param.FishingTimePolicy == FishingTimePolicy.Daytime ? [7] : (param.FishingTimePolicy == FishingTimePolicy.Nighttime ? [19] : new int[] { 7, 19 }))
+ foreach (int hour in param.FishingTimePolicy == FishingTimePolicy.Daytime
+ ? [7]
+ : (param.FishingTimePolicy == FishingTimePolicy.Nighttime ? [19] : new int[] { 7, 19 }))
{
setTimeTask.Start(hour, 0, ct).Wait(ct);
tickARound();
@@ -204,7 +212,8 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
///
///
///
- public WholeProcessTimeout(string name, int seconds, ILogger logger, bool saveScreenshotOnTerminate) : base(name, logger, saveScreenshotOnTerminate)
+ public WholeProcessTimeout(string name, int seconds, ILogger logger, bool saveScreenshotOnTerminate) : base(
+ name, logger, saveScreenshotOnTerminate)
{
this.seconds = seconds;
}
@@ -240,7 +249,8 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
///
/// 行为名将反映在提示语中
///
- public FindFishTimeout(string name, int seconds, Blackboard blackboard, ILogger logger, bool saveScreenshotOnTerminate) : base(name, logger, saveScreenshotOnTerminate)
+ public FindFishTimeout(string name, int seconds, Blackboard blackboard, ILogger logger,
+ bool saveScreenshotOnTerminate) : base(name, logger, saveScreenshotOnTerminate)
{
this.blackboard = blackboard;
this.seconds = seconds;
@@ -271,7 +281,8 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
private readonly IInputSimulator input;
private readonly Blackboard blackboard;
- public TurnAround(string name, Blackboard blackboard, ILogger logger, bool saveScreenshotOnTerminate, IInputSimulator input) : base(name, logger, saveScreenshotOnTerminate)
+ public TurnAround(string name, Blackboard blackboard, ILogger logger, bool saveScreenshotOnTerminate,
+ IInputSimulator input) : base(name, logger, saveScreenshotOnTerminate)
{
this.blackboard = blackboard;
this.input = input;
@@ -282,11 +293,12 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
using var memoryStream = new MemoryStream();
imageRegion.SrcBitmap.Save(memoryStream, ImageFormat.Bmp);
memoryStream.Seek(0, SeekOrigin.Begin);
- var result = blackboard.Predictor.Detect(memoryStream);
- if (result.Boxes.Any())
+ var result = blackboard.Predictor.Predictor.Detect(memoryStream);
+ if (result.Any())
{
Fishpond fishpond = new Fishpond(result);
- logger.LogInformation("定位到鱼塘:" + string.Join('、', fishpond.Fishes.GroupBy(f => f.FishType).Select(g => $"{g.Key.ChineseName}{g.Count()}条")));
+ logger.LogInformation("定位到鱼塘:" + string.Join('、',
+ fishpond.Fishes.GroupBy(f => f.FishType).Select(g => $"{g.Key.ChineseName}{g.Count()}条")));
int i = 0;
foreach (var fish in fishpond.Fishes)
{
@@ -350,12 +362,16 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
private DateTimeOffset? overallWaitEndTime;
private readonly string fishingLocalizedString;
- public EnterFishingMode(string name, Blackboard blackboard, ILogger logger, bool saveScreenshotOnTerminate, IInputSimulator input, TimeProvider? timeProvider = null, CultureInfo? cultureInfo = null) : base(name, logger, saveScreenshotOnTerminate)
+ public EnterFishingMode(string name, Blackboard blackboard, ILogger logger, bool saveScreenshotOnTerminate,
+ IInputSimulator input, TimeProvider? timeProvider = null, CultureInfo? cultureInfo = null) : base(name,
+ logger, saveScreenshotOnTerminate)
{
this.blackboard = blackboard;
this.input = input;
this.timeProvider = timeProvider ?? TimeProvider.System;
- IStringLocalizer stringLocalizer = App.GetService>() ?? throw new NullReferenceException(nameof(stringLocalizer));
+ IStringLocalizer stringLocalizer =
+ App.GetService>() ??
+ throw new NullReferenceException(nameof(stringLocalizer));
this.fishingLocalizedString = stringLocalizer.WithCultureGet(cultureInfo, "钓鱼");
}
@@ -367,13 +383,16 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
return BehaviourStatus.Running;
}
- if ((pressFWaitEndTime == null || pressFWaitEndTime < timeProvider.GetLocalNow()) && Bv.FindFAndPress(imageRegion, input.Keyboard, this.fishingLocalizedString))
+ if ((pressFWaitEndTime == null || pressFWaitEndTime < timeProvider.GetLocalNow()) &&
+ Bv.FindFAndPress(imageRegion, input.Keyboard, this.fishingLocalizedString))
{
logger.LogInformation("按下钓鱼键");
pressFWaitEndTime = timeProvider.GetLocalNow().AddSeconds(3);
return BehaviourStatus.Running;
}
- else if ((clickWhiteConfirmButtonWaitEndTime == null || clickWhiteConfirmButtonWaitEndTime < timeProvider.GetLocalNow()) && Bv.ClickWhiteConfirmButton(imageRegion))
+ else if ((clickWhiteConfirmButtonWaitEndTime == null ||
+ clickWhiteConfirmButtonWaitEndTime < timeProvider.GetLocalNow()) &&
+ Bv.ClickWhiteConfirmButton(imageRegion))
{
logger.LogInformation("点击开始钓鱼");
@@ -410,11 +429,14 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
private readonly Blackboard blackboard;
private readonly string fishingLocalizedString;
- public QuitFishingMode(string name, Blackboard blackboard, ILogger logger, bool saveScreenshotOnTerminate, IInputSimulator input, CultureInfo? cultureInfo = null) : base(name, logger, saveScreenshotOnTerminate)
+ public QuitFishingMode(string name, Blackboard blackboard, ILogger logger, bool saveScreenshotOnTerminate,
+ IInputSimulator input, CultureInfo? cultureInfo = null) : base(name, logger, saveScreenshotOnTerminate)
{
this.blackboard = blackboard;
this.input = input;
- IStringLocalizer stringLocalizer = App.GetService>() ?? throw new NullReferenceException(nameof(stringLocalizer));
+ IStringLocalizer stringLocalizer =
+ App.GetService>() ??
+ throw new NullReferenceException(nameof(stringLocalizer));
this.fishingLocalizedString = stringLocalizer.WithCultureGet(cultureInfo, "钓鱼");
}
diff --git a/BetterGenshinImpact/GameTask/AutoFishing/AutoFishingTrigger.cs b/BetterGenshinImpact/GameTask/AutoFishing/AutoFishingTrigger.cs
index a6621459..2ffb19aa 100644
--- a/BetterGenshinImpact/GameTask/AutoFishing/AutoFishingTrigger.cs
+++ b/BetterGenshinImpact/GameTask/AutoFishing/AutoFishingTrigger.cs
@@ -16,7 +16,6 @@ using Fischless.WindowsInput;
using BetterGenshinImpact.GameTask.Model.Area;
using BetterGenshinImpact.Core.Config;
using BetterGenshinImpact.Core.Recognition.ONNX;
-using Compunet.YoloV8;
using Microsoft.Extensions.Localization;
using BetterGenshinImpact.Core.Recognition.OCR;
@@ -40,6 +39,8 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
private Blackboard blackboard;
+ private readonly BgiYoloPredictor _predictor = BgiOnnxFactory.Instance.CreateYoloPredictor(BgiOnnxModel.BgiFish);
+
///
/// 辣条(误)
///
@@ -47,23 +48,25 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
public AutoFishingTrigger()
{
- AutoFishingTaskParam autoFishingTaskParam = AutoFishingTaskParam.BuildFromConfig(TaskContext.Instance().Config.AutoFishingConfig);
+ AutoFishingTaskParam autoFishingTaskParam =
+ AutoFishingTaskParam.BuildFromConfig(TaskContext.Instance().Config.AutoFishingConfig);
IOcrService ocrService = OcrFactory.Paddle;
- IStringLocalizer stringLocalizer = App.GetService>() ?? throw new NullReferenceException(nameof(stringLocalizer));
-
- var predictor = YoloV8Builder.CreateDefaultBuilder().UseOnnxModel(Global.Absolute(@"Assets\Model\Fish\bgi_fish.onnx")).WithSessionOptions(BgiSessionOption.Instance.Options).Build();
- this.blackboard = new Blackboard(predictor, this.Sleep, AutoFishingAssets.Instance);
+ IStringLocalizer stringLocalizer =
+ App.GetService>() ??
+ throw new NullReferenceException(nameof(stringLocalizer));
+ this.blackboard = new Blackboard(_predictor, this.Sleep, AutoFishingAssets.Instance);
BehaviourTreeLaTiao = FluentBuilder.Create()
.MySimpleParallel("root", policy: SimpleParallelPolicy.OnlyOneMustSucceed)
- .Do("检查是否在钓鱼界面", CheckFishingUserInterface)
- .UntilSuccess("拉条循环")
- .Sequence("拉条")
- .PushLeaf(() => new FishBite("自动提竿", blackboard, _logger, false, input, ocrService, cultureInfo: autoFishingTaskParam.GameCultureInfo, stringLocalizer: stringLocalizer))
- .PushLeaf(() => new GetFishBoxArea("等待拉条出现", blackboard, _logger, false))
- .PushLeaf(() => new Fishing("钓鱼拉条", blackboard, _logger, false, input))
- .End()
- .End()
+ .Do("检查是否在钓鱼界面", CheckFishingUserInterface)
+ .UntilSuccess("拉条循环")
+ .Sequence("拉条")
+ .PushLeaf(() => new FishBite("自动提竿", blackboard, _logger, false, input, ocrService,
+ cultureInfo: autoFishingTaskParam.GameCultureInfo, stringLocalizer: stringLocalizer))
+ .PushLeaf(() => new GetFishBoxArea("等待拉条出现", blackboard, _logger, false))
+ .PushLeaf(() => new Fishing("钓鱼拉条", blackboard, _logger, false, input))
+ .End()
+ .End()
.End()
.Build();
}
@@ -199,7 +202,6 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
/// 1. 观察周围环境,判断鱼塘位置,视角对上鱼塘位置中心
/// 2. 根据第一步的观察结果,提前选择鱼饵
///
-
[Obsolete]
private (int, int) MoveMouseToFish(Rect rect1, Rect rect2)
{
@@ -360,6 +362,5 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
//{
// ClearDraw();
//}
-
}
-}
+}
\ No newline at end of file
diff --git a/BetterGenshinImpact/GameTask/AutoFishing/Behaviours.cs b/BetterGenshinImpact/GameTask/AutoFishing/Behaviours.cs
index d578e128..4ef2ec17 100644
--- a/BetterGenshinImpact/GameTask/AutoFishing/Behaviours.cs
+++ b/BetterGenshinImpact/GameTask/AutoFishing/Behaviours.cs
@@ -5,7 +5,6 @@ using BetterGenshinImpact.GameTask.AutoFishing.Model;
using BetterGenshinImpact.GameTask.Model.Area;
using BetterGenshinImpact.Helpers;
using BetterGenshinImpact.View.Drawable;
-using Compunet.YoloV8;
using Microsoft.Extensions.Logging;
using OpenCvSharp;
using System;
@@ -22,6 +21,7 @@ using BetterGenshinImpact.Core.Recognition.OpenCv;
using BetterGenshinImpact.Core.Simulator;
using BetterGenshinImpact.GameTask.Model;
using System.Globalization;
+using Compunet.YoloSharp;
using Microsoft.Extensions.Localization;
namespace BetterGenshinImpact.GameTask.AutoFishing
@@ -60,8 +60,8 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
using var memoryStream = new MemoryStream();
imageRegion.SrcBitmap.Save(memoryStream, ImageFormat.Bmp);
memoryStream.Seek(0, SeekOrigin.Begin);
- var result = blackboard.Predictor.Detect(memoryStream);
- Debug.WriteLine($"YOLOv8识别: {result.Speed}");
+ var result = blackboard.Predictor.Predictor.Detect(memoryStream);
+ Debug.WriteLine($"YOLO识别: {result.Speed}");
var fishpond = new Fishpond(result, ignoreObtained: true);
if (fishpond.FishpondRect == default)
{
@@ -308,7 +308,7 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
using var memoryStream = new MemoryStream();
imageRegion.SrcBitmap.Save(memoryStream, ImageFormat.Bmp);
memoryStream.Seek(0, SeekOrigin.Begin);
- var result = blackboard.Predictor.Detect(memoryStream);
+ var result = blackboard.Predictor.Predictor.Detect(memoryStream);
Debug.WriteLine($"YOLOv8识别: {result.Speed}");
var fishpond = new Fishpond(result, includeTarget: timeProvider.GetLocalNow() <= ignoreObtainedEndTime);
blackboard.fishpond = fishpond;
@@ -733,8 +733,7 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
}
// OCR 提竿判断
- using Mat wordCaptureGreyMat = new Mat(imageRegion.SrcGreyMat, liftingWordsAreaRect);
- var text = ocrService.Ocr(wordCaptureGreyMat);
+ var text = ocrService.Ocr(wordCaptureMat);
if (!string.IsNullOrEmpty(text) && StringUtils.RemoveAllSpace(text).Contains(this.getABiteLocalizedString))
{
diff --git a/BetterGenshinImpact/GameTask/AutoFishing/Blackboard.cs b/BetterGenshinImpact/GameTask/AutoFishing/Blackboard.cs
index 40ae7960..f460772f 100644
--- a/BetterGenshinImpact/GameTask/AutoFishing/Blackboard.cs
+++ b/BetterGenshinImpact/GameTask/AutoFishing/Blackboard.cs
@@ -1,9 +1,8 @@
using System;
using System.Collections.Generic;
-using System.Text;
+using BetterGenshinImpact.Core.Recognition.ONNX;
using BetterGenshinImpact.GameTask.AutoFishing.Assets;
using BetterGenshinImpact.GameTask.AutoFishing.Model;
-using Compunet.YoloV8;
using OpenCvSharp;
namespace BetterGenshinImpact.GameTask.AutoFishing
@@ -73,8 +72,8 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
internal bool pitchReset = true;
#region 分层暂放
- private readonly YoloV8Predictor? predictor;
- internal YoloV8Predictor Predictor
+ private readonly BgiYoloPredictor? predictor;
+ internal BgiYoloPredictor Predictor
{
get
{
@@ -93,7 +92,7 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
}
- public Blackboard(YoloV8Predictor? predictor = null, Action? sleep = null, AutoFishingAssets? autoFishingAssets = null)
+ public Blackboard(BgiYoloPredictor? predictor = null, Action? sleep = null, AutoFishingAssets? autoFishingAssets = null)
{
this.predictor = predictor;
this.Sleep = sleep ?? (_ => throw new NotImplementedException());
diff --git a/BetterGenshinImpact/GameTask/AutoFishing/Model/Fishpond.cs b/BetterGenshinImpact/GameTask/AutoFishing/Model/Fishpond.cs
index ec5e7bdd..47898985 100644
--- a/BetterGenshinImpact/GameTask/AutoFishing/Model/Fishpond.cs
+++ b/BetterGenshinImpact/GameTask/AutoFishing/Model/Fishpond.cs
@@ -1,10 +1,8 @@
-using BetterGenshinImpact.Core.Recognition.OpenCv;
-using Compunet.YoloV8.Data;
-using OpenCvSharp;
-using System;
+using OpenCvSharp;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
+using Compunet.YoloSharp.Data;
namespace BetterGenshinImpact.GameTask.AutoFishing.Model;
@@ -37,10 +35,10 @@ public class Fishpond
///
/// 是否包含抛竿落点
/// 是否忽略“获得”物品的图标
- public Fishpond(DetectionResult result, bool includeTarget = false, bool ignoreObtained = false)
+ public Fishpond(YoloResult result, bool includeTarget = false, bool ignoreObtained = false)
{
Print(result);
- foreach (var box in result.Boxes)
+ foreach (var box in result)
{
// 可信度太低的直接放弃
if (box.Confidence < 0.4)
@@ -49,7 +47,7 @@ public class Fishpond
}
Rect rect = new Rect(box.Bounds.X, box.Bounds.Y, box.Bounds.Width, box.Bounds.Height);
- if (box.Class.Name == "rod" || box.Class.Name == "err rod")
+ if (box.Name.Name == "rod" || box.Name.Name == "err rod")
{
TargetRect = rect;
continue;
@@ -60,19 +58,19 @@ public class Fishpond
// todo:不是很重要但有机会可以从构造函数里分离逻辑
// 忽略界面左侧提示的“获得”物品的图标,当上一竿获得鱼时,会对当前竿产生干扰
// 使用估算大小和位置的方式来判断并剔除
- if (box.Bounds.Width < result.Image.Width * 0.036 && box.Bounds.Height < result.Image.Width * 0.036)
+ if (box.Bounds.Width < result.ImageSize.Width * 0.036 && box.Bounds.Height < result.ImageSize.Width * 0.036)
{
- Rect huode = new Rect((int)(0.04375 * result.Image.Width), (int)(0.4666 * result.Image.Height), (int)(0.1 * result.Image.Width), (int)(0.1 * result.Image.Width));
+ Rect huode = new Rect((int)(0.04375 * result.ImageSize.Width), (int)(0.4666 * result.ImageSize.Height), (int)(0.1 * result.ImageSize.Width), (int)(0.1 * result.ImageSize.Width));
if (huode.Contains(rect))
{
continue;
}
}
// 忽略界面中央提示的“获得”物品的图标
- if (box.Bounds.Width > result.Image.Width * 0.03 && box.Bounds.Width < result.Image.Width * 0.06 &&
- box.Bounds.Height > result.Image.Width * 0.03 && box.Bounds.Height < result.Image.Width * 0.06)
+ if (box.Bounds.Width > result.ImageSize.Width * 0.03 && box.Bounds.Width < result.ImageSize.Width * 0.06 &&
+ box.Bounds.Height > result.ImageSize.Width * 0.03 && box.Bounds.Height < result.ImageSize.Width * 0.06)
{
- Rect huode = new Rect((int)(0.4 * result.Image.Width), (int)(0.445 * result.Image.Height), (int)(0.2 * result.Image.Width), (int)(0.06125 * result.Image.Width));
+ Rect huode = new Rect((int)(0.4 * result.ImageSize.Width), (int)(0.445 * result.ImageSize.Height), (int)(0.2 * result.ImageSize.Width), (int)(0.06125 * result.ImageSize.Width));
if (huode.Contains(rect))
{
continue;
@@ -81,13 +79,13 @@ public class Fishpond
}
if (includeTarget)
{
- if (box.Class.Name == "koi") //进入抛竿的时候只看koihead
+ if (box.Name.Name == "koi") //进入抛竿的时候只看koihead
{
continue;
}
}
- var fish = new OneFish(box.Class.Name, rect, box.Confidence);
+ var fish = new OneFish(box.Name.Name, rect, box.Confidence);
Fishes.Add(fish);
}
@@ -97,10 +95,10 @@ public class Fishpond
FishpondRect = CalculateFishpondRect();
}
- private void Print(DetectionResult result)
+ private void Print(YoloResult result)
{
Debug.Write("鱼塘YOLO识别结果:");
- foreach (var box in result.Boxes)
+ foreach (var box in result)
{
Debug.Write(box.ToString());
}
diff --git a/BetterGenshinImpact/GameTask/AutoGeniusInvokation/GeniusInvokationControl.cs b/BetterGenshinImpact/GameTask/AutoGeniusInvokation/GeniusInvokationControl.cs
index f64bf2d5..3a587842 100644
--- a/BetterGenshinImpact/GameTask/AutoGeniusInvokation/GeniusInvokationControl.cs
+++ b/BetterGenshinImpact/GameTask/AutoGeniusInvokation/GeniusInvokationControl.cs
@@ -107,13 +107,13 @@ public class GeniusInvokationControl
{
return;
}
+
TaskControl.TrySuspend();
if (!SystemControl.IsGenshinImpactActiveByProcess())
{
_logger.LogWarning("当前获取焦点的窗口不是原神,暂停");
throw new RetryException("当前获取焦点的窗口不是原神");
}
-
}, TimeSpan.FromSeconds(1), 100);
if (_ct is { IsCancellationRequested: true })
@@ -162,7 +162,8 @@ public class GeniusInvokationControl
})
.ToDictionary(x => x.Key, x => x.Value);
// 打印排序后的顺序
- var msg = _assets.ActionPhaseDiceMats.Aggregate("", (current, kvp) => current + $"{kvp.Key.ToElementalType().ToChinese()}| ");
+ var msg = _assets.ActionPhaseDiceMats.Aggregate("",
+ (current, kvp) => current + $"{kvp.Key.ToElementalType().ToChinese()}| ");
_logger.LogDebug("当前骰子排序:{Msg}", msg);
}
@@ -359,7 +360,8 @@ public class GeniusInvokationControl
return dictionary;
}*/
- public static Dictionary> FindMultiPicFromOneImage2OneByOne(Mat srcMat, Dictionary imgSubDictionary, double threshold = 0.8)
+ public static Dictionary> FindMultiPicFromOneImage2OneByOne(Mat srcMat,
+ Dictionary imgSubDictionary, double threshold = 0.8)
{
var dictionary = new Dictionary>();
foreach (var kvp in imgSubDictionary)
@@ -368,11 +370,13 @@ public class GeniusInvokationControl
while (true)
{
- var point = MatchTemplateHelper.MatchTemplate(srcMat, kvp.Value, TemplateMatchModes.CCoeffNormed, null, threshold);
+ var point = MatchTemplateHelper.MatchTemplate(srcMat, kvp.Value, TemplateMatchModes.CCoeffNormed, null,
+ threshold);
if (point != new Point())
{
// 把结果给遮掩掉,避免重复识别
- Cv2.Rectangle(srcMat, point, new Point(point.X + kvp.Value.Width, point.Y + kvp.Value.Height), Scalar.Black, -1);
+ Cv2.Rectangle(srcMat, point, new Point(point.X + kvp.Value.Width, point.Y + kvp.Value.Height),
+ Scalar.Black, -1);
list.Add(point);
}
else
@@ -442,7 +446,8 @@ public class GeniusInvokationControl
// 选中重投
foreach (var point in kvp.Value)
{
- ClickCaptureArea(point.X + _assets.RollPhaseDiceMats[kvp.Key].Width / 2, point.Y + _assets.RollPhaseDiceMats[kvp.Key].Height / 2);
+ ClickCaptureArea(point.X + _assets.RollPhaseDiceMats[kvp.Key].Width / 2,
+ point.Y + _assets.RollPhaseDiceMats[kvp.Key].Height / 2);
Sleep(100);
}
}
@@ -471,7 +476,8 @@ public class GeniusInvokationControl
// 3.重投骰子
_logger.LogInformation("等待5s投骰动画...");
- var msg = holdElementalTypes.Aggregate(" ", (current, elementalType) => current + (elementalType.ToChinese() + " "));
+ var msg = holdElementalTypes.Aggregate(" ",
+ (current, elementalType) => current + (elementalType.ToChinese() + " "));
_logger.LogInformation("保留{Msg}骰子", msg);
Sleep(5000);
@@ -520,7 +526,8 @@ public class GeniusInvokationControl
var srcMat = CaptureGameMat();
Cv2.CvtColor(srcMat, srcMat, ColorConversionCodes.BGRA2BGR);
// 切割图片后再识别 加快速度 位置没啥用,所以切割后比较方便
- var dictionary = FindMultiPicFromOneImage2OneByOne(CutRight(srcMat, srcMat.Width / 5), _assets.ActionPhaseDiceMats, 0.7);
+ var dictionary =
+ FindMultiPicFromOneImage2OneByOne(CutRight(srcMat, srcMat.Width / 5), _assets.ActionPhaseDiceMats, 0.7);
var msg = "";
var result = new Dictionary();
@@ -667,16 +674,19 @@ public class GeniusInvokationControl
}
}
- int needSpecifyElementDiceCount = diceCost - diceStatus[ElementalType.Omni.ToLowerString()] - diceStatus[elementalType.ToLowerString()];
+ int needSpecifyElementDiceCount = diceCost - diceStatus[ElementalType.Omni.ToLowerString()] -
+ diceStatus[elementalType.ToLowerString()];
if (needSpecifyElementDiceCount > 0)
{
if (duel.CurrentCardCount < needSpecifyElementDiceCount)
{
- _logger.LogInformation("当前手牌数{Current}小于需要烧牌数量{Expect},无法释放技能", duel.CurrentCardCount, needSpecifyElementDiceCount);
+ _logger.LogInformation("当前手牌数{Current}小于需要烧牌数量{Expect},无法释放技能", duel.CurrentCardCount,
+ needSpecifyElementDiceCount);
return false;
}
- _logger.LogInformation("当前需要的元素骰子数量不足{Cost}个,还缺{Lack}个,当前手牌数{Current},烧牌", diceCost, needSpecifyElementDiceCount, duel.CurrentCardCount);
+ _logger.LogInformation("当前需要的元素骰子数量不足{Cost}个,还缺{Lack}个,当前手牌数{Current},烧牌", diceCost,
+ needSpecifyElementDiceCount, duel.CurrentCardCount);
for (var i = 0; i < needSpecifyElementDiceCount; i++)
{
@@ -807,7 +817,8 @@ public class GeniusInvokationControl
{
for (var i = 0; i < rects.Count; i++)
{
- if (IsOverlap(rects[i], new Rect(p.X, p.Y, _assets.CharacterDefeatedMat.Width, _assets.CharacterDefeatedMat.Height)))
+ if (IsOverlap(rects[i],
+ new Rect(p.X, p.Y, _assets.CharacterDefeatedMat.Width, _assets.CharacterDefeatedMat.Height)))
{
res[i] = true;
}
@@ -999,20 +1010,23 @@ public class GeniusInvokationControl
character.Area.Width + 40,
character.Area.Height + 10));
// 识别角色异常状态
- var pCharacterStatusFreeze = MatchTemplateHelper.MatchTemplate(characterMat, _assets.CharacterStatusFreezeMat, TemplateMatchModes.CCoeffNormed);
+ var pCharacterStatusFreeze = MatchTemplateHelper.MatchTemplate(characterMat, _assets.CharacterStatusFreezeMat,
+ TemplateMatchModes.CCoeffNormed);
if (pCharacterStatusFreeze != new Point())
{
character.StatusList.Add(CharacterStatusEnum.Frozen);
}
- var pCharacterStatusDizziness = MatchTemplateHelper.MatchTemplate(characterMat, _assets.CharacterStatusDizzinessMat, TemplateMatchModes.CCoeffNormed);
+ var pCharacterStatusDizziness = MatchTemplateHelper.MatchTemplate(characterMat,
+ _assets.CharacterStatusDizzinessMat, TemplateMatchModes.CCoeffNormed);
if (pCharacterStatusDizziness != new Point())
{
character.StatusList.Add(CharacterStatusEnum.Frozen);
}
// 识别角色能量
- var energyPointList = MatchTemplateHelper.MatchTemplateMulti(characterMat.Clone(), _assets.CharacterEnergyOnMat, 0.8);
+ var energyPointList =
+ MatchTemplateHelper.MatchTemplateMulti(characterMat.Clone(), _assets.CharacterEnergyOnMat, 0.8);
character.EnergyByRecognition = energyPointList.Count;
character.Hp = hp;
@@ -1048,7 +1062,8 @@ public class GeniusInvokationControl
var highPurple = new Scalar(255, 255, 255);
Mat gray = OpenCvCommonHelper.Threshold(bottomMat, lowPurple, highPurple);
- var kernel = Cv2.GetStructuringElement(MorphShapes.Rect, new OpenCvSharp.Size(15, 10), new OpenCvSharp.Point(-1, -1));
+ var kernel = Cv2.GetStructuringElement(MorphShapes.Rect, new OpenCvSharp.Size(15, 10),
+ new OpenCvSharp.Point(-1, -1));
Cv2.Dilate(gray, gray, kernel); //膨胀
Cv2.FindContours(gray, out var contours, out _, RetrievalModes.External,
@@ -1108,7 +1123,7 @@ public class GeniusInvokationControl
throw new System.Exception("未能获取到我方角色卡位置");
}
- var srcMat = CaptureGameGreyMat();
+ var imageRegion = CaptureToRectArea();
var hpArray = new int[3]; // 1 代表未出战 2 代表出战
for (var i = 0; i < duel.CharacterCardRects.Count; i++)
@@ -1122,7 +1137,7 @@ public class GeniusInvokationControl
var cardRect = duel.CharacterCardRects[i];
// 未出战角色的hp区域
- var hpMat = new Mat(srcMat, new Rect(cardRect.X + _config.CharacterCardExtendHpRect.X,
+ var hpMat = new Mat(imageRegion.SrcMat, new Rect(cardRect.X + _config.CharacterCardExtendHpRect.X,
cardRect.Y + _config.CharacterCardExtendHpRect.Y,
_config.CharacterCardExtendHpRect.Width, _config.CharacterCardExtendHpRect.Height));
var text = OcrFactory.Paddle.Ocr(hpMat);
@@ -1135,7 +1150,7 @@ public class GeniusInvokationControl
}
else
{
- hpMat = new Mat(srcMat, new Rect(cardRect.X + _config.CharacterCardExtendHpRect.X,
+ hpMat = new Mat(imageRegion.SrcMat, new Rect(cardRect.X + _config.CharacterCardExtendHpRect.X,
cardRect.Y + _config.CharacterCardExtendHpRect.Y - _config.ActiveCharacterCardSpace,
_config.CharacterCardExtendHpRect.Width, _config.CharacterCardExtendHpRect.Height));
text = OcrFactory.Paddle.Ocr(hpMat);
@@ -1151,7 +1166,7 @@ public class GeniusInvokationControl
hpArray[i] = 2;
duel.CurrentCharacter = duel.Characters[i + 1];
- AppendCharacterStatus(duel.CurrentCharacter, srcMat, hp);
+ AppendCharacterStatus(duel.CurrentCharacter, imageRegion.SrcGreyMat, hp);
return duel.CurrentCharacter;
}
}
@@ -1163,7 +1178,7 @@ public class GeniusInvokationControl
var index = hpArray.ToList().FindIndex(x => x != 1);
Debug.WriteLine($"通过OCR HP的方式没有识别到出战角色,但是通过排除法确认角色{index + 1}处于出战状态!");
duel.CurrentCharacter = duel.Characters[index + 1];
- AppendCharacterStatus(duel.CurrentCharacter, srcMat);
+ AppendCharacterStatus(duel.CurrentCharacter, imageRegion.SrcGreyMat);
return duel.CurrentCharacter;
}
@@ -1239,4 +1254,4 @@ public class GeniusInvokationControl
return -10;
}
}
-}
+}
\ No newline at end of file
diff --git a/BetterGenshinImpact/GameTask/AutoPathing/PathExecutor.cs b/BetterGenshinImpact/GameTask/AutoPathing/PathExecutor.cs
index 4a1703af..8dc37bd9 100644
--- a/BetterGenshinImpact/GameTask/AutoPathing/PathExecutor.cs
+++ b/BetterGenshinImpact/GameTask/AutoPathing/PathExecutor.cs
@@ -632,7 +632,7 @@ public class PathExecutor
var ra1 = CaptureToRectArea();
var textRect = new Rect(60, 20, 160, 260);
- var textMat = new Mat(ra1.SrcGreyMat, textRect);
+ var textMat = new Mat(ra1.SrcMat, textRect);
string text = OcrFactory.Paddle.Ocr(textMat);
if (text.Contains("探索派遣奖励"))
{
diff --git a/BetterGenshinImpact/GameTask/AutoPick/AutoPickTrigger.cs b/BetterGenshinImpact/GameTask/AutoPick/AutoPickTrigger.cs
index ec13dded..ea954702 100644
--- a/BetterGenshinImpact/GameTask/AutoPick/AutoPickTrigger.cs
+++ b/BetterGenshinImpact/GameTask/AutoPick/AutoPickTrigger.cs
@@ -109,6 +109,7 @@ public partial class AutoPickTrigger : ITaskTrigger
{
Thread.Sleep(1000);
}
+
var speedTimer = new SpeedTimer();
using var foundRectArea = content.CaptureRectArea.Find(_pickRo);
@@ -185,8 +186,9 @@ public partial class AutoPickTrigger : ITaskTrigger
return;
}
- var textMat = new Mat(content.CaptureRectArea.SrcGreyMat, textRect);
- var gradMat = new Mat(textMat, new Rect(0, 0, textRect.Width, Math.Min(textRect.Height, 3)));
+ // var textMat = new Mat(content.CaptureRectArea.SrcGreyMat, textRect);
+ var gradMat = new Mat(content.CaptureRectArea.SrcGreyMat,
+ new Rect(textRect.X, textRect.Y, textRect.Width, Math.Min(textRect.Height, 3)));
var avgGrad = gradMat.Sobel(MatType.CV_32F, 1, 0).Mean().Val0;
if (avgGrad < -3)
{
@@ -197,11 +199,12 @@ public partial class AutoPickTrigger : ITaskTrigger
string text;
if (config.OcrEngine == PickOcrEngineEnum.Yap.ToString())
{
- var paddedMat = PreProcessForInference(textMat);
- text = _pickTextInference.Inference(paddedMat);
+ var textMat = new Mat(content.CaptureRectArea.SrcGreyMat, textRect);
+ text = _pickTextInference.Inference(TextInferenceFactory.PreProcessForInference(textMat));
}
else
{
+ var textMat = new Mat(content.CaptureRectArea.SrcMat, textRect);
text = OcrFactory.Paddle.Ocr(textMat);
}
@@ -280,22 +283,6 @@ public partial class AutoPickTrigger : ITaskTrigger
}
- private Mat PreProcessForInference(Mat mat)
- {
- // Yap 已经改用灰度图了 https://github.com/Alex-Beng/Yap/commit/c2ad1e7b1442aaf2d80782a032e00876cd1c6c84
- // 二值化
- // Cv2.Threshold(mat, mat, 0, 255, ThresholdTypes.Otsu | ThresholdTypes.Binary);
- //Cv2.AdaptiveThreshold(mat, mat, 255, AdaptiveThresholdTypes.GaussianC, ThresholdTypes.Binary, 31, 3); // 效果不错 但是和模型不搭
- //mat = OpenCvCommonHelper.Threshold(mat, Scalar.FromRgb(235, 235, 235), Scalar.FromRgb(255, 255, 255)); // 识别物品不太行
- // 不知道为什么要强制拉伸到 221x32
- mat = ResizeHelper.ResizeTo(mat, 221, 32);
- // 填充到 384x32
- var padded = new Mat(new Size(384, 32), MatType.CV_8UC1, Scalar.Black);
- padded[new Rect(0, 0, mat.Width, mat.Height)] = mat;
- //Cv2.ImWrite(Global.Absolute("padded.png"), padded);
- return padded;
- }
-
///
/// 相同文字前后3帧内只输出一次
///
diff --git a/BetterGenshinImpact/GameTask/AutoSkip/AutoSkipTrigger.cs b/BetterGenshinImpact/GameTask/AutoSkip/AutoSkipTrigger.cs
index 03dd0ff4..c7ef6e79 100644
--- a/BetterGenshinImpact/GameTask/AutoSkip/AutoSkipTrigger.cs
+++ b/BetterGenshinImpact/GameTask/AutoSkip/AutoSkipTrigger.cs
@@ -305,7 +305,7 @@ public partial class AutoSkipTrigger : ITaskTrigger
// OCR识别选项文字
foreach (var hangoutOption in hangoutOptionList)
{
- var text = OcrFactory.Paddle.Ocr(hangoutOption.TextRect!.SrcGreyMat);
+ var text = OcrFactory.Paddle.Ocr(hangoutOption.TextRect!.SrcMat);
hangoutOption.OptionTextSrc = StringUtils.RemoveAllEnter(text);
}
diff --git a/BetterGenshinImpact/GameTask/AutoSkip/ExpeditionTask.cs b/BetterGenshinImpact/GameTask/AutoSkip/ExpeditionTask.cs
index f279e64b..7acffa66 100644
--- a/BetterGenshinImpact/GameTask/AutoSkip/ExpeditionTask.cs
+++ b/BetterGenshinImpact/GameTask/AutoSkip/ExpeditionTask.cs
@@ -4,7 +4,6 @@ using BetterGenshinImpact.GameTask.Common;
using BetterGenshinImpact.View.Drawable;
using Microsoft.Extensions.Logging;
using OpenCvSharp;
-using Sdcb.PaddleOCR;
using System;
using System.Collections.Generic;
using System.Drawing;
@@ -61,7 +60,8 @@ public class ExpeditionTask
ExpeditionCharacterList.Clear();
str = str.Replace(",", ",");
str.Split(',').ToList().ForEach(x => ExpeditionCharacterList.Add(x.Trim()));
- TaskContext.Instance().Config.AutoSkipConfig.AutoReExploreCharacter = string.Join(",", ExpeditionCharacterList);
+ TaskContext.Instance().Config.AutoSkipConfig.AutoReExploreCharacter =
+ string.Join(",", ExpeditionCharacterList);
}
}
@@ -72,13 +72,16 @@ public class ExpeditionTask
for (var i = 0; i < 5; i++)
{
- var result = CaptureAndOcr(content, new Rect(0, 0, captureRect.Width - (int)(480 * assetScale), captureRect.Height));
+ var result = CaptureAndOcr(content,
+ new Rect(0, 0, captureRect.Width - (int)(480 * assetScale), captureRect.Height));
var rect = result.FindRectByText("探险完成");
// TODO i>1 的时候,可以通过关键词“探索派遣限制 4 / 5 ”判断是否已经派遣完成?
if (rect != default)
{
// 点击探险完成下方的人物头像
- content.CaptureRectArea.Derive(new Rect(rect.X, rect.Y + (int)(50 * assetScale), rect.Width, (int)(80 * assetScale))).Click();
+ content.CaptureRectArea
+ .Derive(new Rect(rect.X, rect.Y + (int)(50 * assetScale), rect.Width, (int)(80 * assetScale)))
+ .Click();
TaskControl.Sleep(100);
// 重新截图 找领取
result = CaptureAndOcr(content);
@@ -128,7 +131,8 @@ public class ExpeditionTask
var cards = GetCharacterCards(result);
if (cards.Count > 0)
{
- var card = cards.FirstOrDefault(c => c.Idle && c.Name != null && ExpeditionCharacterList.Contains(c.Name)) ?? cards.First(c => c.Idle);
+ var card = cards.FirstOrDefault(c =>
+ c.Idle && c.Name != null && ExpeditionCharacterList.Contains(c.Name)) ?? cards.First(c => c.Idle);
var rect = card.Rects.First();
using var ra = content.CaptureRectArea.Derive(rect);
@@ -147,7 +151,7 @@ public class ExpeditionTask
///
///
///
- private List GetCharacterCards(PaddleOcrResult result)
+ private List GetCharacterCards(OcrResult result)
{
var captureRect = TaskContext.Instance().SystemInfo.CaptureAreaRect;
var assetScale = TaskContext.Instance().SystemInfo.AssetScale;
@@ -162,7 +166,8 @@ public class ExpeditionTask
var cards = new List();
foreach (var ocrResultRect in ocrResultRects)
{
- if (ocrResultRect.Text.Contains("时间缩短") || ocrResultRect.Text.Contains("奖励增加") || ocrResultRect.Text.Contains("暂无加成"))
+ if (ocrResultRect.Text.Contains("时间缩短") || ocrResultRect.Text.Contains("奖励增加") ||
+ ocrResultRect.Text.Contains("暂无加成"))
{
var card = new ExpeditionCharacterCard();
card.Rects.Add(ocrResultRect.Rect);
@@ -170,18 +175,21 @@ public class ExpeditionTask
foreach (var ocrResultRect2 in ocrResultRects)
{
if (ocrResultRect2.Rect.Y > ocrResultRect.Rect.Y - 50 * assetScale
- && ocrResultRect2.Rect.Y + ocrResultRect2.Rect.Height < ocrResultRect.Rect.Y + ocrResultRect.Rect.Height)
+ && ocrResultRect2.Rect.Y + ocrResultRect2.Rect.Height <
+ ocrResultRect.Rect.Y + ocrResultRect.Rect.Height)
{
if (ocrResultRect2.Text.Contains("探险完成") || ocrResultRect2.Text.Contains("探险中"))
{
card.Idle = false;
- var name = ocrResultRect2.Text.Replace("探险完成", "").Replace("探险中", "").Replace("/", "").Trim();
+ var name = ocrResultRect2.Text.Replace("探险完成", "").Replace("探险中", "").Replace("/", "")
+ .Trim();
if (!string.IsNullOrEmpty(name))
{
card.Name = name;
}
}
- else if (!ocrResultRect2.Text.Contains("时间缩短") && !ocrResultRect2.Text.Contains("奖励增加") && !ocrResultRect2.Text.Contains("暂无加成"))
+ else if (!ocrResultRect2.Text.Contains("时间缩短") && !ocrResultRect2.Text.Contains("奖励增加") &&
+ !ocrResultRect2.Text.Contains("暂无加成"))
{
card.Name = ocrResultRect2.Text;
}
@@ -206,7 +214,7 @@ public class ExpeditionTask
private readonly Pen _pen = new(Color.Red, 1);
- private PaddleOcrResult CaptureAndOcr(CaptureContent content)
+ private OcrResult CaptureAndOcr(CaptureContent content)
{
using var ra = TaskControl.CaptureToRectArea();
var result = OcrFactory.Paddle.OcrResult(ra.SrcGreyMat);
@@ -214,11 +222,11 @@ public class ExpeditionTask
return result;
}
- private PaddleOcrResult CaptureAndOcr(CaptureContent content, Rect rect)
+ private OcrResult CaptureAndOcr(CaptureContent content, Rect rect)
{
using var ra = TaskControl.CaptureToRectArea();
var result = OcrFactory.Paddle.OcrResult(ra.SrcGreyMat);
//VisionContext.Instance().DrawContent.PutOrRemoveRectList("OcrResultRects", result.ToRectDrawableList(_pen));
return result;
}
-}
+}
\ No newline at end of file
diff --git a/BetterGenshinImpact/GameTask/AutoWood/AutoWoodTask.cs b/BetterGenshinImpact/GameTask/AutoWood/AutoWoodTask.cs
index 0b29ca9a..70ab29f3 100644
--- a/BetterGenshinImpact/GameTask/AutoWood/AutoWoodTask.cs
+++ b/BetterGenshinImpact/GameTask/AutoWood/AutoWoodTask.cs
@@ -228,7 +228,7 @@ public partial class AutoWoodTask : ISoloTask
{
// OCR识别文本区域
var woodCountRect = CaptureToRectArea().DeriveCrop(assert.WoodCountUpperRect);
- return OcrFactory.Paddle.Ocr(woodCountRect.SrcGreyMat);
+ return OcrFactory.Paddle.Ocr(woodCountRect.SrcMat);
}
private bool HasDetectedWoodText(string recognizedText)
diff --git a/BetterGenshinImpact/GameTask/Common/Job/ScanPickTask.cs b/BetterGenshinImpact/GameTask/Common/Job/ScanPickTask.cs
index cd28b86a..a7f70f92 100644
--- a/BetterGenshinImpact/GameTask/Common/Job/ScanPickTask.cs
+++ b/BetterGenshinImpact/GameTask/Common/Job/ScanPickTask.cs
@@ -22,7 +22,7 @@ namespace BetterGenshinImpact.GameTask.Common.Job;
///
public class ScanPickTask
{
- private readonly BgiYoloV8Predictor _predictor = BgiYoloV8PredictorFactory.GetPredictor(@"Assets\Model\World\bgi_world.onnx");
+ private readonly BgiYoloPredictor _predictor = BgiOnnxFactory.Instance.CreateYoloPredictor(BgiOnnxModel.BgiWorld);
private readonly double _dpi = TaskContext.Instance().DpiScale;
private readonly RECT _realCaptureRect = TaskContext.Instance().SystemInfo.CaptureAreaRect;
diff --git a/BetterGenshinImpact/GameTask/Model/Area/ImageRegion.cs b/BetterGenshinImpact/GameTask/Model/Area/ImageRegion.cs
index b896e5ad..a57e7ff7 100644
--- a/BetterGenshinImpact/GameTask/Model/Area/ImageRegion.cs
+++ b/BetterGenshinImpact/GameTask/Model/Area/ImageRegion.cs
@@ -226,10 +226,10 @@ public class ImageRegion : Region
throw new Exception($"[OCR]识别对象{ro.Name}的匹配文本不能全为空");
}
- var roi = SrcGreyMat;
+ var roi = SrcMat;
if (ro.RegionOfInterest != default)
{
- roi = new Mat(SrcGreyMat, ro.RegionOfInterest);
+ roi = new Mat(SrcMat, ro.RegionOfInterest);
}
var result = OcrFactory.Paddle.OcrResult(roi);
@@ -323,10 +323,10 @@ public class ImageRegion : Region
}
else
{
- roi = SrcGreyMat;
+ roi = SrcMat;
if (ro.RegionOfInterest != default)
{
- roi = new Mat(SrcGreyMat, ro.RegionOfInterest);
+ roi = new Mat(SrcMat, ro.RegionOfInterest);
}
}
@@ -454,10 +454,10 @@ public class ImageRegion : Region
}
else if (RecognitionTypes.Ocr.Equals(ro.RecognitionType))
{
- var roi = SrcGreyMat;
+ var roi = SrcMat;
if (ro.RegionOfInterest != default)
{
- roi = new Mat(SrcGreyMat, ro.RegionOfInterest);
+ roi = new Mat(SrcMat, ro.RegionOfInterest);
}
var result = OcrFactory.Paddle.OcrResult(roi);
diff --git a/BetterGenshinImpact/View/MainWindow.xaml b/BetterGenshinImpact/View/MainWindow.xaml
index ee303e8c..8d579bfc 100644
--- a/BetterGenshinImpact/View/MainWindow.xaml
+++ b/BetterGenshinImpact/View/MainWindow.xaml
@@ -177,7 +177,7 @@
-
+
- 截图器启动后才能使用各项功能,
-
+ 截图器启动后才能使用各项功能,
点击展开启动相关配置
- 。
-
+ 。
+ ItemsSource="{Binding ModeNames, Mode=OneWay}"
+ SelectedValue="{Binding Config.CaptureMode, Mode=TwoWay}"
+ SelectedValuePath="EnumName">
@@ -213,29 +211,36 @@
+
-
-
-
-
-
+ SelectedItem="{Binding Config.HardwareAccelerationConfig.InferenceDevice, Mode=TwoWay}">
+
+
+
+
+
+
@@ -418,7 +423,7 @@
Grid.Column="0"
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
Text="如果你不知道什么是启动参数请不要填写。"
- TextWrapping="Wrap"/>
+ TextWrapping="Wrap" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 修改后需要重启程序生效,不正确的修改可能会导致程序异常
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 修改后需要重启程序生效。
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 修改后需要重启程序生效。
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/BetterGenshinImpact/View/Pages/View/HardwareAccelerationView.xaml.cs b/BetterGenshinImpact/View/Pages/View/HardwareAccelerationView.xaml.cs
new file mode 100644
index 00000000..49e26d7d
--- /dev/null
+++ b/BetterGenshinImpact/View/Pages/View/HardwareAccelerationView.xaml.cs
@@ -0,0 +1,15 @@
+using System.Windows.Controls;
+using BetterGenshinImpact.ViewModel.Pages.View;
+
+namespace BetterGenshinImpact.View.Pages.View;
+
+public partial class HardwareAccelerationView : UserControl
+{
+ private HardwareAccelerationViewModel ViewModel { get; }
+
+ public HardwareAccelerationView(HardwareAccelerationViewModel viewModel)
+ {
+ DataContext = ViewModel = viewModel;
+ InitializeComponent();
+ }
+}
\ No newline at end of file
diff --git a/BetterGenshinImpact/View/Pages/View/ScriptGroupConfigView.xaml b/BetterGenshinImpact/View/Pages/View/ScriptGroupConfigView.xaml
index 5209c3e0..d76e547e 100644
--- a/BetterGenshinImpact/View/Pages/View/ScriptGroupConfigView.xaml
+++ b/BetterGenshinImpact/View/Pages/View/ScriptGroupConfigView.xaml
@@ -21,7 +21,7 @@
+ IsExpanded="True">
@@ -444,8 +444,7 @@
SelectedItem="{Binding PathingConfig.AutoFightConfig.StrategyName, Mode=TwoWay}">
-
+
@@ -647,68 +646,68 @@
-
+
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/BetterGenshinImpact/ViewModel/MainWindowViewModel.cs b/BetterGenshinImpact/ViewModel/MainWindowViewModel.cs
index ca0b0d21..865cad01 100644
--- a/BetterGenshinImpact/ViewModel/MainWindowViewModel.cs
+++ b/BetterGenshinImpact/ViewModel/MainWindowViewModel.cs
@@ -248,7 +248,7 @@ public partial class MainWindowViewModel : ObservableObject, IViewModel
{
string gameCultureInfoName = TaskContext.Instance().Config.OtherConfig.GameCultureInfoName;
await OcrFactory.ChangeCulture(gameCultureInfoName);
- var s = OcrFactory.Paddle.Ocr(new Mat(Global.Absolute(@"Assets\Model\PaddleOCR\test_ocr.png"), ImreadModes.Grayscale));
+ var s = OcrFactory.Paddle.Ocr(new Mat(Global.Absolute(@"Assets\Model\PaddleOCR\test_ocr.png")));
Debug.WriteLine("PaddleOcr预热结果:" + s);
}
catch (Exception e)
diff --git a/BetterGenshinImpact/ViewModel/Pages/HomePageViewModel.cs b/BetterGenshinImpact/ViewModel/Pages/HomePageViewModel.cs
index ba72dd91..b9da6083 100644
--- a/BetterGenshinImpact/ViewModel/Pages/HomePageViewModel.cs
+++ b/BetterGenshinImpact/ViewModel/Pages/HomePageViewModel.cs
@@ -30,13 +30,14 @@ using Windows.System;
using BetterGenshinImpact.GameTask.AutoFishing;
using BetterGenshinImpact.Helpers.Extensions;
using BetterGenshinImpact.Model;
+using BetterGenshinImpact.View.Pages.View;
+using BetterGenshinImpact.ViewModel.Pages.View;
using Wpf.Ui.Controls;
namespace BetterGenshinImpact.ViewModel.Pages;
public partial class HomePageViewModel : ViewModel
{
-
[ObservableProperty]
private IEnumerable> _modeNames = EnumExtensions.ToEnumItems();
@@ -66,7 +67,7 @@ public partial class HomePageViewModel : ViewModel
private IntPtr _hWnd;
[ObservableProperty]
- private string[] _inferenceDeviceTypes = BgiSessionOption.InferenceDeviceTypes;
+ private InferenceDeviceType[] _inferenceDeviceTypes = Enum.GetValues();
public HomePageViewModel(IConfigService configService, TaskTriggerDispatcher taskTriggerDispatcher)
{
@@ -84,7 +85,9 @@ public partial class HomePageViewModel : ViewModel
// DirectML 是在 Windows 10 版本 1903 和 Windows SDK 的相应版本中引入的。
// https://learn.microsoft.com/zh-cn/windows/ai/directml/dml
- _inferenceDeviceTypes = _inferenceDeviceTypes.Where(x => x != "GPU_DirectML").ToArray();
+ _inferenceDeviceTypes = _inferenceDeviceTypes
+ .Where(x => x != InferenceDeviceType.GpuDirectMl)
+ .ToArray();
}
WeakReferenceMessenger.Default.Register>(this, (sender, msg) =>
@@ -157,10 +160,10 @@ public partial class HomePageViewModel : ViewModel
}
}
- [RelayCommand]
- private void OnInferenceDeviceTypeDropDownChanged(string value)
- {
- }
+ // [RelayCommand]
+ // private void OnInferenceDeviceTypeDropDownChanged(string value)
+ // {
+ // }
[RelayCommand]
private void OnStartCaptureTest()
@@ -437,4 +440,17 @@ public partial class HomePageViewModel : ViewModel
win.NavigateToHtml(html);
win.ShowDialog();
}
+
+ [RelayCommand]
+ public void OnOpenHardwareAccelerationSettings()
+ {
+ var dialogWindow = new Window
+ {
+ Title = "硬件加速设置",
+ Content = new HardwareAccelerationView(new HardwareAccelerationViewModel()),
+ SizeToContent = SizeToContent.WidthAndHeight,
+ WindowStartupLocation = WindowStartupLocation.CenterOwner,
+ };
+ var result = dialogWindow.ShowDialog();
+ }
}
\ No newline at end of file
diff --git a/BetterGenshinImpact/ViewModel/Pages/View/HardwareAccelerationViewModel.cs b/BetterGenshinImpact/ViewModel/Pages/View/HardwareAccelerationViewModel.cs
new file mode 100644
index 00000000..9b321010
--- /dev/null
+++ b/BetterGenshinImpact/ViewModel/Pages/View/HardwareAccelerationViewModel.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Diagnostics;
+using System.Linq;
+using BetterGenshinImpact.Core.Config;
+using BetterGenshinImpact.Core.Recognition.ONNX;
+using BetterGenshinImpact.GameTask;
+using CommunityToolkit.Mvvm.Input;
+
+namespace BetterGenshinImpact.ViewModel.Pages.View;
+
+using CommunityToolkit.Mvvm.ComponentModel;
+
+public partial class HardwareAccelerationViewModel : ObservableObject, IViewModel
+{
+ public HardwareAccelerationConfig Config { get; set; }
+ public BgiOnnxFactory Status { get; set; }
+ [ObservableProperty]
+ private InferenceDeviceType[] _inferenceDeviceTypes = Enum.GetValues();
+ [ObservableProperty]
+ private string _providerTypesText;
+ public HardwareAccelerationViewModel()
+ {
+ Config = TaskContext.Instance().Config.HardwareAccelerationConfig;
+ Status = BgiOnnxFactory.Instance;
+ _providerTypesText = string.Join(",", Status.ProviderTypes);
+ }
+ [RelayCommand]
+ public void OnOpenCacheFolder()
+ {
+ Process.Start("explorer.exe", Global.Absolute(BgiOnnxModel.ModelCacheRelativePath));
+ }
+}
\ No newline at end of file
diff --git a/Fischless.GameCapture/Fischless.GameCapture.csproj b/Fischless.GameCapture/Fischless.GameCapture.csproj
index 685c9552..aba49767 100644
--- a/Fischless.GameCapture/Fischless.GameCapture.csproj
+++ b/Fischless.GameCapture/Fischless.GameCapture.csproj
@@ -14,13 +14,13 @@
-
-
-
-
-
-
-
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Fischless.HotkeyCapture/Fischless.HotkeyCapture.csproj b/Fischless.HotkeyCapture/Fischless.HotkeyCapture.csproj
index bd52b9cd..bc9a441a 100644
--- a/Fischless.HotkeyCapture/Fischless.HotkeyCapture.csproj
+++ b/Fischless.HotkeyCapture/Fischless.HotkeyCapture.csproj
@@ -11,7 +11,7 @@
-
+
diff --git a/Fischless.WindowsInput/Fischless.WindowsInput.csproj b/Fischless.WindowsInput/Fischless.WindowsInput.csproj
index 81ef8f50..ac90d079 100644
--- a/Fischless.WindowsInput/Fischless.WindowsInput.csproj
+++ b/Fischless.WindowsInput/Fischless.WindowsInput.csproj
@@ -10,7 +10,7 @@
-
+
diff --git a/Test/BetterGenshinImpact.Test/Simple/OcrTest.cs b/Test/BetterGenshinImpact.Test/Simple/OcrTest.cs
index be8d039c..0985abe1 100644
--- a/Test/BetterGenshinImpact.Test/Simple/OcrTest.cs
+++ b/Test/BetterGenshinImpact.Test/Simple/OcrTest.cs
@@ -10,28 +10,14 @@ public class OcrTest
{
public static void TestYap()
{
- Mat mat = Cv2.ImRead(@"E:\HuiTask\更好的原神\临时文件\fuben_jueyuan.png", ImreadModes.Grayscale);
- var text = TextInferenceFactory.Pick.Inference(PreProcessForInference(mat));
+ Mat mat = Cv2.ImRead(@"E:\HuiTask\更好的原神\临时文件\fuben_jueyuan.png");
+ var text = TextInferenceFactory.Pick.Inference(TextInferenceFactory.PreProcessForInference(mat));
Debug.WriteLine(text);
- Mat mat2 = Cv2.ImRead(@"E:\HuiTask\更好的原神\临时文件\fuben_jueyuan.png", ImreadModes.Grayscale);
+ Mat mat2 = Cv2.ImRead(@"E:\HuiTask\更好的原神\临时文件\fuben_jueyuan.png");
var text2 = OcrFactory.Paddle.Ocr(mat2);
Debug.WriteLine(text2);
}
- private static Mat PreProcessForInference(Mat mat)
- {
- // Yap 已经改用灰度图了 https://github.com/Alex-Beng/Yap/commit/c2ad1e7b1442aaf2d80782a032e00876cd1c6c84
- // 二值化
- // Cv2.Threshold(mat, mat, 0, 255, ThresholdTypes.Otsu | ThresholdTypes.Binary);
- //Cv2.AdaptiveThreshold(mat, mat, 255, AdaptiveThresholdTypes.GaussianC, ThresholdTypes.Binary, 31, 3); // 效果不错 但是和模型不搭
- //mat = OpenCvCommonHelper.Threshold(mat, Scalar.FromRgb(235, 235, 235), Scalar.FromRgb(255, 255, 255)); // 识别物品不太行
- // 不知道为什么要强制拉伸到 221x32
- mat = ResizeHelper.ResizeTo(mat, 221, 32);
- // 填充到 384x32
- var padded = new Mat(new Size(384, 32), MatType.CV_8UC1, Scalar.Black);
- padded[new Rect(0, 0, mat.Width, mat.Height)] = mat;
- //Cv2.ImWrite(Global.Absolute("padded.png"), padded);
- return padded;
- }
+
}
diff --git a/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoFishingTests/BehavioursTests.GetFishpond.cs b/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoFishingTests/BehavioursTests.GetFishpond.cs
index fa52d818..80a9f460 100644
--- a/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoFishingTests/BehavioursTests.GetFishpond.cs
+++ b/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoFishingTests/BehavioursTests.GetFishpond.cs
@@ -1,15 +1,6 @@
using BetterGenshinImpact.GameTask.AutoFishing;
using BehaviourTree;
-using System;
-using System.Collections.Generic;
-using System.Drawing;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
using BetterGenshinImpact.GameTask.Model.Area;
-using BetterGenshinImpact.Core.Config;
-using Compunet.YoloV8;
-using BetterGenshinImpact.GameTask.AutoFishing.Model;
using Microsoft.Extensions.Time.Testing;
using OpenCvSharp;
diff --git a/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoFishingTests/BehavioursTests.ThrowRod.cs b/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoFishingTests/BehavioursTests.ThrowRod.cs
index 8fc18c07..bc2c1592 100644
--- a/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoFishingTests/BehavioursTests.ThrowRod.cs
+++ b/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoFishingTests/BehavioursTests.ThrowRod.cs
@@ -2,13 +2,6 @@
using BetterGenshinImpact.GameTask.AutoFishing;
using BetterGenshinImpact.GameTask.Model.Area.Converter;
using BetterGenshinImpact.GameTask.Model.Area;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using BetterGenshinImpact.Core.Config;
-using Compunet.YoloV8;
using Microsoft.Extensions.Time.Testing;
using OpenCvSharp;
using BehaviourTree.Composites;
diff --git a/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoFishingTests/BehavioursTests.cs b/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoFishingTests/BehavioursTests.cs
index a559b8df..94aa2a58 100644
--- a/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoFishingTests/BehavioursTests.cs
+++ b/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoFishingTests/BehavioursTests.cs
@@ -1,7 +1,7 @@
using BetterGenshinImpact.Core.Config;
using BetterGenshinImpact.Core.Recognition.OCR;
+using BetterGenshinImpact.Core.Recognition.ONNX;
using BetterGenshinImpact.UnitTest.CoreTests.RecognitionTests.OCRTests;
-using Compunet.YoloV8;
namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests
{
@@ -9,7 +9,7 @@ namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests
public partial class BehavioursTests
{
#pragma warning disable CS8618 // 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑添加 "required" 修饰符或声明为可为 null。
- private static YoloV8Predictor predictor;
+ private static BgiYoloPredictor predictor;
#pragma warning restore CS8618 // 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑添加 "required" 修饰符或声明为可为 null。
private readonly PaddleFixture paddle;
@@ -18,19 +18,13 @@ namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests
this.paddle = paddle;
}
- private IOcrService OcrService
- {
- get
- {
- return this.paddle.Get();
- }
- }
+ private IOcrService OcrService => paddle.Get();
- private static YoloV8Predictor Predictor
+ private static BgiYoloPredictor Predictor
{
get
{
- return LazyInitializer.EnsureInitialized(ref predictor, () => YoloV8Builder.CreateDefaultBuilder().UseOnnxModel(Global.Absolute(@"Assets\Model\Fish\bgi_fish.onnx")).Build());
+ return LazyInitializer.EnsureInitialized(ref predictor,()=>BgiOnnxFactory.Instance.CreateYoloPredictor(BgiOnnxModel.BgiFish));
}
}
}