mirror of
https://github.com/babalae/better-genshin-impact.git
synced 2026-03-15 07:43:20 +08:00
尝试修复一些ROI越界 (#2808)
* fix: 修复多处 OpenCV ROI 越界导致的断言失败 在低分辨率(如 1280x720)下,多处 Rect 坐标计算未做边界保护, 直接传入 SubMat / new Mat(mat, rect) 时触发 OpenCV ROI 断言崩溃。 修复位置: - Behaviours.cs: fishBoxRect 计算结果钳位到图像边界,修复钓鱼任务越界 - GridScreen.cs: PostProcess 中幻影格子(插值生成)越界时直接丢弃 - ImageRegion.cs: DeriveCrop 两个重载统一加入坐标钳位与有效性校验 - GetGridIconsTask.cs: CropResizeArtifactSetFilterGridIcon X/Y 坐标加非负保护 - GeniusInvokationControl.cs: 角色区域扩展和 HP 区域 Y 偏移各加边界保护 * chore: 为 AutoFishingTask 鱼饵图标裁剪补充说明注释 * refactor: 提取 Rect 钳位逻辑为共享扩展方法 ClampTo 将 6 处重复的 ROI 钳位代码统一为 CommonExtension.ClampTo 扩展方法, 采用交集语义(坐标钳位时宽高同步缩减,不会扩大矩形)。 删除 AutoLeyLineOutcropTask 中的私有 ClampRect 方法。
This commit is contained in:
@@ -87,4 +87,24 @@ public static class CommonExtension
|
|||||||
{
|
{
|
||||||
return list.ConvertAll(ToPoint2d);
|
return list.ConvertAll(ToPoint2d);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将矩形钳位到指定尺寸范围内(交集语义),防止 OpenCV ROI 越界
|
||||||
|
/// </summary>
|
||||||
|
public static Rect ClampTo(this Rect rect, int maxWidth, int maxHeight)
|
||||||
|
{
|
||||||
|
int x1 = Math.Clamp(rect.X, 0, maxWidth);
|
||||||
|
int y1 = Math.Clamp(rect.Y, 0, maxHeight);
|
||||||
|
int x2 = Math.Clamp(rect.X + rect.Width, 0, maxWidth);
|
||||||
|
int y2 = Math.Clamp(rect.Y + rect.Height, 0, maxHeight);
|
||||||
|
return new Rect(x1, y1, x2 - x1, y2 - y1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将矩形钳位到 Mat 范围内(交集语义),防止 OpenCV ROI 越界
|
||||||
|
/// </summary>
|
||||||
|
public static Rect ClampTo(this Rect rect, Mat mat)
|
||||||
|
{
|
||||||
|
return rect.ClampTo(mat.Cols, mat.Rows);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -391,6 +391,9 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
|
|||||||
clickWhiteConfirmButtonWaitEndTime < timeProvider.GetLocalNow()) &&
|
clickWhiteConfirmButtonWaitEndTime < timeProvider.GetLocalNow()) &&
|
||||||
Bv.ClickWhiteConfirmButton(imageRegion))
|
Bv.ClickWhiteConfirmButton(imageRegion))
|
||||||
{
|
{
|
||||||
|
// 截取鱼饵图标区域(正方形,宽高均取 6.5% 的屏幕宽度)
|
||||||
|
// 最后一个参数有意用 Width 而非 Height,目的是保持正方形裁剪
|
||||||
|
// 经验算在 16:9 常见分辨率(720p/1080p/1440p)下 Y+H 不会超出图像高度,暂不加钳位
|
||||||
using Mat subMat = imageRegion.SrcMat.SubMat(new Rect((int)(0.824 * imageRegion.Width), (int)(0.669 * imageRegion.Height), (int)(0.065 * imageRegion.Width), (int)(0.065 * imageRegion.Width)));
|
using Mat subMat = imageRegion.SrcMat.SubMat(new Rect((int)(0.824 * imageRegion.Width), (int)(0.669 * imageRegion.Height), (int)(0.065 * imageRegion.Width), (int)(0.065 * imageRegion.Width)));
|
||||||
using Mat resized = subMat.Resize(new Size(125, 125));
|
using Mat resized = subMat.Resize(new Size(125, 125));
|
||||||
(string predName, _) = GridIconsAccuracyTestTask.Infer(resized, this.session, this.prototypes);
|
(string predName, _) = GridIconsAccuracyTestTask.Infer(resized, this.session, this.prototypes);
|
||||||
|
|||||||
@@ -857,8 +857,13 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
|
|||||||
}
|
}
|
||||||
|
|
||||||
int hExtra = _cur.Height, vExtra = _cur.Height / 4;
|
int hExtra = _cur.Height, vExtra = _cur.Height / 4;
|
||||||
blackboard.fishBoxRect = new Rect(_cur.X - hExtra, _cur.Y - vExtra,
|
{
|
||||||
(topMat.Width / 2 - _cur.X) * 2 + hExtra * 2, _cur.Height + vExtra * 2);
|
int rx = _cur.X - hExtra;
|
||||||
|
int ry = _cur.Y - vExtra;
|
||||||
|
int rw = (topMat.Width / 2 - _cur.X) * 2 + hExtra * 2;
|
||||||
|
int rh = _cur.Height + vExtra * 2;
|
||||||
|
blackboard.fishBoxRect = new Rect(rx, ry, rw, rh).ClampTo(imageRegion.SrcMat);
|
||||||
|
}
|
||||||
using var boxRa = imageRegion.Derive(blackboard.fishBoxRect);
|
using var boxRa = imageRegion.Derive(blackboard.fishBoxRect);
|
||||||
boxRa.DrawSelf("FishBox", System.Drawing.Pens.LightPink);
|
boxRa.DrawSelf("FishBox", System.Drawing.Pens.LightPink);
|
||||||
logger.LogInformation(" 识别到钓鱼框");
|
logger.LogInformation(" 识别到钓鱼框");
|
||||||
|
|||||||
@@ -998,11 +998,10 @@ public class GeniusInvokationControl
|
|||||||
|
|
||||||
public void AppendCharacterStatus(Character character, Mat greyMat, int hp = -2)
|
public void AppendCharacterStatus(Character character, Mat greyMat, int hp = -2)
|
||||||
{
|
{
|
||||||
// 截取出战角色区域扩展
|
// 截取出战角色区域扩展,钳位到图像边界防止越界
|
||||||
using var characterMat = new Mat(greyMat, new Rect(character.Area.X,
|
var charRect = new Rect(character.Area.X, character.Area.Y,
|
||||||
character.Area.Y,
|
character.Area.Width + 40, character.Area.Height + 10).ClampTo(greyMat);
|
||||||
character.Area.Width + 40,
|
using var characterMat = new Mat(greyMat, charRect);
|
||||||
character.Area.Height + 10));
|
|
||||||
// 识别角色异常状态
|
// 识别角色异常状态
|
||||||
var pCharacterStatusFreeze = MatchTemplateHelper.MatchTemplate(characterMat, _assets.CharacterStatusFreezeMat,
|
var pCharacterStatusFreeze = MatchTemplateHelper.MatchTemplate(characterMat, _assets.CharacterStatusFreezeMat,
|
||||||
TemplateMatchModes.CCoeffNormed);
|
TemplateMatchModes.CCoeffNormed);
|
||||||
@@ -1144,9 +1143,13 @@ public class GeniusInvokationControl
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
hpMat = new Mat(imageRegion.SrcMat, new Rect(cardRect.X + _config.CharacterCardExtendHpRect.X,
|
// 出战角色 HP 区域向上偏移,钳位到图像边界防止越界
|
||||||
|
var activeHpRect = new Rect(
|
||||||
|
cardRect.X + _config.CharacterCardExtendHpRect.X,
|
||||||
cardRect.Y + _config.CharacterCardExtendHpRect.Y - _config.ActiveCharacterCardSpace,
|
cardRect.Y + _config.CharacterCardExtendHpRect.Y - _config.ActiveCharacterCardSpace,
|
||||||
_config.CharacterCardExtendHpRect.Width, _config.CharacterCardExtendHpRect.Height));
|
_config.CharacterCardExtendHpRect.Width,
|
||||||
|
_config.CharacterCardExtendHpRect.Height).ClampTo(imageRegion.SrcMat);
|
||||||
|
hpMat = new Mat(imageRegion.SrcMat, activeHpRect);
|
||||||
text = OcrFactory.Paddle.Ocr(hpMat);
|
text = OcrFactory.Paddle.Ocr(hpMat);
|
||||||
//Cv2.ImWrite($"log\\hp_active_{i}.jpg", hpMat);
|
//Cv2.ImWrite($"log\\hp_active_{i}.jpg", hpMat);
|
||||||
Debug.WriteLine($"角色{i}出战HP位置识别结果{text}");
|
Debug.WriteLine($"角色{i}出战HP位置识别结果{text}");
|
||||||
|
|||||||
@@ -870,7 +870,7 @@ public class AutoLeyLineOutcropTask : ISoloTask
|
|||||||
return capture.Find(ro);
|
return capture.Find(ro);
|
||||||
}
|
}
|
||||||
|
|
||||||
var clamped = ClampRect(roi, capture.Width, capture.Height);
|
var clamped = roi.ClampTo(capture.Width, capture.Height);
|
||||||
if (clamped.Width <= 0 || clamped.Height <= 0)
|
if (clamped.Width <= 0 || clamped.Height <= 0)
|
||||||
{
|
{
|
||||||
return new Region();
|
return new Region();
|
||||||
@@ -886,16 +886,6 @@ public class AutoLeyLineOutcropTask : ISoloTask
|
|||||||
return capture.Find(cloned);
|
return capture.Find(cloned);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Rect ClampRect(Rect roi, int maxWidth, int maxHeight)
|
|
||||||
{
|
|
||||||
// Clamp ROI to avoid OpenCV exceptions when the rectangle is out of bounds.
|
|
||||||
var x = Math.Clamp(roi.X, 0, Math.Max(0, maxWidth - 1));
|
|
||||||
var y = Math.Clamp(roi.Y, 0, Math.Max(0, maxHeight - 1));
|
|
||||||
var w = Math.Clamp(roi.Width, 0, Math.Max(0, maxWidth - x));
|
|
||||||
var h = Math.Clamp(roi.Height, 0, Math.Max(0, maxHeight - y));
|
|
||||||
return new Rect(x, y, w, h);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<bool> AutoFight(int timeoutSeconds)
|
private async Task<bool> AutoFight(int timeoutSeconds)
|
||||||
{
|
{
|
||||||
var fightCts = CancellationTokenSource.CreateLinkedTokenSource(_ct);
|
var fightCts = CancellationTokenSource.CreateLinkedTokenSource(_ct);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using BetterGenshinImpact.Core.Recognition.OCR;
|
using BetterGenshinImpact.Core.Recognition.OCR;
|
||||||
|
using BetterGenshinImpact.Core.Recognition.OpenCv;
|
||||||
using BetterGenshinImpact.Core.Simulator;
|
using BetterGenshinImpact.Core.Simulator;
|
||||||
using BetterGenshinImpact.GameTask.AutoArtifactSalvage;
|
using BetterGenshinImpact.GameTask.AutoArtifactSalvage;
|
||||||
using BetterGenshinImpact.GameTask.Common;
|
using BetterGenshinImpact.GameTask.Common;
|
||||||
@@ -243,7 +244,11 @@ public class GetGridIconsTask : ISoloTask
|
|||||||
double scale = (systemInfo ?? TaskContext.Instance().SystemInfo).AssetScale;
|
double scale = (systemInfo ?? TaskContext.Instance().SystemInfo).AssetScale;
|
||||||
double width = 60;
|
double width = 60;
|
||||||
double height = 60; // 宽高缩放似乎不一致,似乎在2.05:2.15之间,但不知道怎么测定
|
double height = 60; // 宽高缩放似乎不一致,似乎在2.05:2.15之间,但不知道怎么测定
|
||||||
Rect iconRect = new Rect((int)(itemRegion.Width / 2 - 237 * scale - width / 2), (int)(itemRegion.Height / 2 - height / 2), (int)width, (int)height);
|
// 低分辨率下 237 * scale 的偏移量可能大于 itemRegion 中心位置,导致 X 为负,加保护
|
||||||
|
Rect iconRect = new Rect(
|
||||||
|
(int)(itemRegion.Width / 2 - 237 * scale - width / 2),
|
||||||
|
(int)(itemRegion.Height / 2 - height / 2),
|
||||||
|
(int)width, (int)height).ClampTo(itemRegion.SrcMat);
|
||||||
using Mat crop = itemRegion.SrcMat.SubMat(iconRect);
|
using Mat crop = itemRegion.SrcMat.SubMat(iconRect);
|
||||||
return crop.Resize(new Size(125, 125));
|
return crop.Resize(new Size(125, 125));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,7 +69,12 @@ public class ImageRegion : Region
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public ImageRegion DeriveCrop(int x, int y, int w, int h)
|
public ImageRegion DeriveCrop(int x, int y, int w, int h)
|
||||||
{
|
{
|
||||||
return new ImageRegion(new Mat(SrcMat, new Rect(x, y, w, h)), x, y, this, new TranslationConverter(x, y));
|
var rect = new Rect(x, y, w, h).ClampTo(SrcMat);
|
||||||
|
if (rect.Width <= 0 || rect.Height <= 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(rect), $"DeriveCrop 裁剪区域无效: ({x},{y},{w},{h}),图像大小: {SrcMat.Cols}x{SrcMat.Rows}");
|
||||||
|
}
|
||||||
|
return new ImageRegion(new Mat(SrcMat, rect), rect.X, rect.Y, this, new TranslationConverter(rect.X, rect.Y));
|
||||||
}
|
}
|
||||||
|
|
||||||
public ImageRegion DeriveCrop(double dx, double dy, double dw, double dh)
|
public ImageRegion DeriveCrop(double dx, double dy, double dw, double dh)
|
||||||
@@ -78,7 +83,12 @@ public class ImageRegion : Region
|
|||||||
var y = (int)Math.Round(dy);
|
var y = (int)Math.Round(dy);
|
||||||
var w = (int)Math.Round(dw);
|
var w = (int)Math.Round(dw);
|
||||||
var h = (int)Math.Round(dh);
|
var h = (int)Math.Round(dh);
|
||||||
return new ImageRegion(new Mat(SrcMat, new Rect(x, y, w, h)), x, y, this, new TranslationConverter(x, y));
|
var rect = new Rect(x, y, w, h).ClampTo(SrcMat);
|
||||||
|
if (rect.Width <= 0 || rect.Height <= 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(rect), $"DeriveCrop 裁剪区域无效: ({x},{y},{w},{h}),图像大小: {SrcMat.Cols}x{SrcMat.Rows}");
|
||||||
|
}
|
||||||
|
return new ImageRegion(new Mat(SrcMat, rect), rect.X, rect.Y, this, new TranslationConverter(rect.X, rect.Y));
|
||||||
}
|
}
|
||||||
|
|
||||||
public ImageRegion DeriveCrop(Rect rect)
|
public ImageRegion DeriveCrop(Rect rect)
|
||||||
|
|||||||
@@ -339,6 +339,14 @@ namespace BetterGenshinImpact.GameTask.Model.GameUI
|
|||||||
var result = cells.ToList();
|
var result = cells.ToList();
|
||||||
foreach (var cell in cells.Where(c => c.IsPhantom))
|
foreach (var cell in cells.Where(c => c.IsPhantom))
|
||||||
{
|
{
|
||||||
|
// 幻影格子由插值生成,低分辨率下可能坐标越界,直接丢弃
|
||||||
|
if (cell.Rect.X < 0 || cell.Rect.Y < 0 ||
|
||||||
|
cell.Rect.X + cell.Rect.Width > mat.Cols ||
|
||||||
|
cell.Rect.Y + cell.Rect.Height > mat.Rows)
|
||||||
|
{
|
||||||
|
result.Remove(cell);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
using Mat cellMat = mat.SubMat(cell.Rect);
|
using Mat cellMat = mat.SubMat(cell.Rect);
|
||||||
using Mat bottom = cellMat.GetGridBottom();
|
using Mat bottom = cellMat.GetGridBottom();
|
||||||
if (!IsCorrectBottomColor(bottom))
|
if (!IsCorrectBottomColor(bottom))
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
using BetterGenshinImpact.Core.Recognition.OpenCv;
|
||||||
|
using OpenCvSharp;
|
||||||
|
|
||||||
|
namespace BetterGenshinImpact.UnitTest.CoreTests.RecognitionTests;
|
||||||
|
|
||||||
|
public class RectClampTests
|
||||||
|
{
|
||||||
|
[Theory]
|
||||||
|
[InlineData(10, 10, 50, 50, 200, 200, 10, 10, 50, 50)] // 完全在范围内
|
||||||
|
[InlineData(-10, 20, 100, 50, 200, 200, 0, 20, 90, 50)] // 左侧越界
|
||||||
|
[InlineData(20, -15, 50, 100, 200, 200, 20, 0, 50, 85)] // 上方越界
|
||||||
|
[InlineData(150, 10, 100, 50, 200, 200, 150, 10, 50, 50)] // 右侧越界
|
||||||
|
[InlineData(10, 150, 50, 100, 200, 200, 10, 150, 50, 50)] // 下方越界
|
||||||
|
[InlineData(-10, -20, 300, 400, 100, 100, 0, 0, 100, 100)] // 四边都越界
|
||||||
|
[InlineData(-100, 10, 50, 50, 200, 200, 0, 10, 0, 50)] // 完全在外(左),宽为0
|
||||||
|
[InlineData(0, 0, 10, 10, 0, 0, 0, 0, 0, 0)] // 零尺寸图像
|
||||||
|
public void ClampTo_IntOverload_ReturnsExpected(
|
||||||
|
int x, int y, int w, int h,
|
||||||
|
int maxW, int maxH,
|
||||||
|
int ex, int ey, int ew, int eh)
|
||||||
|
{
|
||||||
|
var rect = new Rect(x, y, w, h);
|
||||||
|
var result = rect.ClampTo(maxW, maxH);
|
||||||
|
Assert.Equal(new Rect(ex, ey, ew, eh), result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ClampTo_MatOverload_MatchesIntOverload()
|
||||||
|
{
|
||||||
|
var rect = new Rect(-10, -5, 100, 80);
|
||||||
|
using var mat = new Mat(200, 200, MatType.CV_8UC3);
|
||||||
|
|
||||||
|
var result = rect.ClampTo(mat);
|
||||||
|
|
||||||
|
Assert.Equal(rect.ClampTo(mat.Cols, mat.Rows), result);
|
||||||
|
Assert.Equal(new Rect(0, 0, 90, 75), result);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user