diff --git a/BetterGenshinImpact.Test/CharacterOrientationTest.cs b/BetterGenshinImpact.Test/CharacterOrientationTest.cs index ffe07adc..6b7ec3df 100644 --- a/BetterGenshinImpact.Test/CharacterOrientationTest.cs +++ b/BetterGenshinImpact.Test/CharacterOrientationTest.cs @@ -37,11 +37,18 @@ public class CharacterOrientationTest return Math.Sqrt(deltaX * deltaX + deltaY * deltaY); } - static Point2f Midpoint(Point2f p1, Point2f p2) + // static Point2f Midpoint(Point2f p1, Point2f p2) + // { + // var midX = (p1.X + p2.X) / 2; + // var midY = (p1.Y + p2.Y) / 2; + // return new Point2f(midX, midY); + // } + + static Point Midpoint(Point p1, Point p2) { var midX = (p1.X + p2.X) / 2; var midY = (p1.Y + p2.Y) / 2; - return new Point2f(midX, midY); + return new Point(midX, midY); } public static void Triangle(Mat src, Mat gray) @@ -196,7 +203,7 @@ public class CharacterOrientationTest } - public static void Watershed() + public static void FloodFill() { var mat = Cv2.ImRead(@"E:\HuiTask\更好的原神\自动秘境\箭头识别\s1.png", ImreadModes.Color); Cv2.GaussianBlur(mat, mat, new Size(3, 3), 0); @@ -207,14 +214,159 @@ public class CharacterOrientationTest // Cv2.ImShow($"splitMat{i}", splitMat[i]); //} - // 红蓝通道按位与 + // 1. 红蓝通道按位与 var red = new Mat(mat.Size(), MatType.CV_8UC1); Cv2.InRange(splitMat[0], new Scalar(250), new Scalar(255), red); //Cv2.ImShow("red", red); var blue = new Mat(mat.Size(), MatType.CV_8UC1); Cv2.InRange(splitMat[2], new Scalar(0), new Scalar(10), blue); //Cv2.ImShow("blue", blue); - var andMat = red & blue; + var andMat = new Mat(mat.Size(), MatType.CV_8UC1); + + Cv2.BitwiseAnd(red, blue, andMat); Cv2.ImShow("andMat2", andMat); + + // 寻找轮廓 + Cv2.FindContours(andMat, out var contours, out var hierarchy, RetrievalModes.External, ContourApproximationModes.ApproxSimple); + Mat dst = Mat.Zeros(andMat.Size(), MatType.CV_8UC3); + for (int i = 0; i < contours.Length; i++) + { + Cv2.DrawContours(dst, contours, i, Scalar.Red, 1, LineTypes.Link4, hierarchy); + } + + Cv2.ImShow("寻找轮廓", dst); + + // 计算最大外接矩形 + if (contours.Length > 0) + { + var boxes = contours.Select(Cv2.BoundingRect).Where(w => w.Height >= 2); + var boxArray = boxes as Rect[] ?? boxes.ToArray(); + if (boxArray.Count() != 1) + { + throw new Exception("找到多个外接矩形"); + } + + var box = boxArray.First(); + + // 剪裁出准备泛洪的区域(放大4倍的区域) + var newSrcMat = new Mat(mat, new Rect(box.X - box.Width / 2, box.Y - box.Height / 2, box.Width * 2, box.Height * 2)); + Cv2.ImShow("剪裁出准备泛洪的区域", newSrcMat); + + // 中心点作为种子点 + var seedPoint = new Point(newSrcMat.Width / 2, newSrcMat.Height / 2); + + // 泛洪填充 + Cv2.FloodFill(newSrcMat, seedPoint, Scalar.White, out _, new Scalar()); + } + } + + + public static void Hsv() + { + var mat = Cv2.ImRead(@"E:\HuiTask\更好的原神\自动秘境\箭头识别\e1.png", ImreadModes.Color); + // Cv2.GaussianBlur(mat, mat, new Size(3, 3), 0); + var splitMat = mat.Split(); + + // 1. 红蓝通道按位与 + var red = new Mat(mat.Size(), MatType.CV_8UC1); + Cv2.InRange(splitMat[0], new Scalar(250), new Scalar(255), red); + var blue = new Mat(mat.Size(), MatType.CV_8UC1); + Cv2.InRange(splitMat[2], new Scalar(0), new Scalar(10), blue); + var andMat = new Mat(mat.Size(), MatType.CV_8UC1); + + Cv2.BitwiseAnd(red, blue, andMat); + Cv2.ImShow("andMat2", andMat); + + // 寻找轮廓 + Cv2.FindContours(andMat, out var contours, out var hierarchy, RetrievalModes.External, ContourApproximationModes.ApproxSimple); + Mat dst = Mat.Zeros(andMat.Size(), MatType.CV_8UC3); + for (int i = 0; i < contours.Length; i++) + { + Cv2.DrawContours(dst, contours, i, Scalar.Red, 1, LineTypes.Link4, hierarchy); + } + + Cv2.ImShow("寻找轮廓", dst); + + // 计算最大外接矩形 + + if (contours.Length > 0) + { + var maxRect = Rect.Empty; + var maxIndex = 0; + for (int i = 0; i < contours.Length; i++) + { + var box = Cv2.BoundingRect(contours[i]); + if (box.Width * box.Height > maxRect.Width * maxRect.Height) + { + maxRect = box; + maxIndex = i; + } + } + + var maxContour = contours[maxIndex]; + + // 计算轮廓的周长 + var perimeter = Cv2.ArcLength(maxContour, true); + + // 近似多边形拟合 + var approx = Cv2.ApproxPolyDP(maxContour, 0.08 * perimeter, true); + + // 如果拟合的多边形有三个顶点,认为是三角形 + if (approx.Length == 3) + { + // 在图像上绘制三角形的轮廓 + Cv2.DrawContours(mat, new OpenCvSharp.Point[][] { approx }, -1, Scalar.Green, 1); + + // 剪裁出三角形所在区域 + var newSrcMat = new Mat(mat, maxRect); + + + // HSV 阈值取出中心飞镖 + var hsvMat = new Mat(); + Cv2.CvtColor(newSrcMat, hsvMat, ColorConversionCodes.BGR2HSV); + // var lowScalar = new Scalar(95, 255, 255); + // var highScalar = new Scalar(255, 255, 255); + var lowScalar = new Scalar(93, 155, 170); + var highScalar = new Scalar(255, 255, 255); + var hsvThresholdMat = new Mat(); + Cv2.InRange(hsvMat, lowScalar, highScalar, hsvThresholdMat); + Cv2.ImShow("剪裁出三角形所在区域", hsvMat); + Cv2.ImShow("HSV 阈值取出中心飞镖", hsvThresholdMat); + + // 循环计算三条边的中点,并计算中点到顶点的所有点中连续黑色像素的个数 + var maxBlackCount = 0; + Point correctP1 = new(), correctP2 = new(); + var offset = new Point(maxRect.X, maxRect.Y); + for (int i = 0; i < 3; i++) + { + var midPoint = Midpoint(approx[i], approx[(i + 1) % 3]); + var targetPoint = approx[(i + 2) % 3]; + + // 中点到顶点的所有点 + var lineIterator = new LineIterator(hsvThresholdMat, midPoint - offset, targetPoint - offset, PixelConnectivity.Connectivity8); + + // 计算连续黑色像素的个数 + var blackCount = 0; + foreach (var item in lineIterator) + { + if (item.GetValue().Item0 == 255) + { + break; + } + + blackCount++; + } + + if (blackCount > maxBlackCount) + { + maxBlackCount = blackCount; + correctP1 = midPoint; + correctP2 = targetPoint; + } + } + Cv2.Line(mat, correctP1, correctP2 + (correctP2 - correctP1) * 3, Scalar.Red, 1); + Cv2.ImShow("最终结果", mat); + } + } } } \ No newline at end of file diff --git a/BetterGenshinImpact.Test/HsvTestWindow.cs b/BetterGenshinImpact.Test/HsvTestWindow.cs new file mode 100644 index 00000000..0c79515d --- /dev/null +++ b/BetterGenshinImpact.Test/HsvTestWindow.cs @@ -0,0 +1,78 @@ +using OpenCvSharp; + +namespace BetterGenshinImpact.Test; + +internal class HsvTestWindow +{ + private const int MaxValueH = 360 / 2; + static readonly int MaxValue = 255; + //private const string WindowCaptureName = "Video Capture"; + private const string _windowDetectionName = "Object Detection"; + static int _lowH = 0, _lowS = 0, _lowV = 0; + static int _highH = MaxValueH, _highS = MaxValue, _highV = MaxValue; + + static void on_low_H_thresh_trackbar(int pos, IntPtr userdata) + { + _lowH = Math.Min(_highH - 1, _lowH); + Cv2.SetTrackbarPos("Low H", _windowDetectionName, _lowH); + } + static void on_high_H_thresh_trackbar(int pos, IntPtr userdata) + { + _highH = Math.Max(_highH, _lowH + 1); + Cv2.SetTrackbarPos("High H", _windowDetectionName, _highH); + } + static void on_low_S_thresh_trackbar(int pos, IntPtr userdata) + { + _lowS = Math.Min(_highS - 1, _lowS); + Cv2.SetTrackbarPos("Low S", _windowDetectionName, _lowS); + } + static void on_high_S_thresh_trackbar(int pos, IntPtr userdata) + { + _highS = Math.Max(_highS, _lowS + 1); + Cv2.SetTrackbarPos("High S", _windowDetectionName, _highS); + } + static void on_low_V_thresh_trackbar(int pos, IntPtr userdata) + { + _lowV = Math.Min(_highV - 1, _lowV); + Cv2.SetTrackbarPos("Low V", _windowDetectionName, _lowV); + } + static void on_high_V_thresh_trackbar(int pos, IntPtr userdata) + { + _highV = Math.Max(_highV, _lowV + 1); + Cv2.SetTrackbarPos("High V", _windowDetectionName, _highV); + } + + public void Run() + { + + //Cv2.NamedWindow(WindowCaptureName); + Cv2.NamedWindow(_windowDetectionName); + // Trackbars to set thresholds for HSV values + Cv2.CreateTrackbar("Low H", _windowDetectionName, ref _lowH, MaxValueH, on_low_H_thresh_trackbar); + Cv2.CreateTrackbar("High H", _windowDetectionName, ref _highH, MaxValueH, on_high_H_thresh_trackbar); + Cv2.CreateTrackbar("Low S", _windowDetectionName, ref _lowS, MaxValue, on_low_S_thresh_trackbar); + Cv2.CreateTrackbar("High S", _windowDetectionName, ref _highS, MaxValue, on_high_S_thresh_trackbar); + Cv2.CreateTrackbar("Low V", _windowDetectionName, ref _lowV, MaxValue, on_low_V_thresh_trackbar); + Cv2.CreateTrackbar("High V", _windowDetectionName, ref _highV, MaxValue, on_high_V_thresh_trackbar); + var frame = Cv2.ImRead(@"E:\HuiTask\更好的原神\自动秘境\箭头识别\s1.png", ImreadModes.Color); + Mat frameHsv = new Mat(); + // Convert from BGR to HSV colorspace + Cv2.CvtColor(frame, frameHsv, ColorConversionCodes.BGR2HSV); + Mat frameThreshold = new Mat(); + + while (true) + { + // Detect the object based on HSV Range Values + Cv2.InRange(frameHsv, new Scalar(_lowH, _lowS, _lowV), new Scalar(_highH, _highS, _highV), frameThreshold); + // Show the frames + // Cv2.ImShow(WindowCaptureName, frame); + Cv2.ImShow(_windowDetectionName, frameThreshold); + + char key = (char)Cv2.WaitKey(30); + if (key == 'q' || key == 27) + { + break; + } + } + } +} \ No newline at end of file diff --git a/BetterGenshinImpact.Test/MainWindow.xaml.cs b/BetterGenshinImpact.Test/MainWindow.xaml.cs index 5e244b65..f7b72622 100644 --- a/BetterGenshinImpact.Test/MainWindow.xaml.cs +++ b/BetterGenshinImpact.Test/MainWindow.xaml.cs @@ -19,8 +19,10 @@ namespace BetterGenshinImpact.Test public MainWindow() { InitializeComponent(); + + // new HsvTestWindow().Run(); // CharacterOrientationTest.TestArrow(); - CharacterOrientationTest.Watershed(); + CharacterOrientationTest.Hsv(); } } } \ No newline at end of file diff --git a/BetterGenshinImpact/GameTask/Placeholder/PlaceholderTrigger.cs b/BetterGenshinImpact/GameTask/Placeholder/PlaceholderTrigger.cs index 17e26ece..cc088571 100644 --- a/BetterGenshinImpact/GameTask/Placeholder/PlaceholderTrigger.cs +++ b/BetterGenshinImpact/GameTask/Placeholder/PlaceholderTrigger.cs @@ -1,8 +1,10 @@ using BetterGenshinImpact.View.Drawable; using OpenCvSharp; +using SharpDX; using System; using System.Drawing; using System.Linq; +using System.Net; using Point = OpenCvSharp.Point; using Size = OpenCvSharp.Size; @@ -95,94 +97,102 @@ public class TestTrigger : ITaskTrigger public static void TestArrow(CaptureContent content) { var mat = new Mat(content.CaptureRectArea.SrcMat, new Rect(0,0,300,240)); - Cv2.GaussianBlur(mat, mat, new Size(3, 3), 0); + var splitMat = mat.Split(); - - // 红蓝通道按位与 + // 1. 红蓝通道按位与 var red = new Mat(mat.Size(), MatType.CV_8UC1); Cv2.InRange(splitMat[0], new Scalar(250), new Scalar(255), red); var blue = new Mat(mat.Size(), MatType.CV_8UC1); Cv2.InRange(splitMat[2], new Scalar(0), new Scalar(10), blue); - var andMat = red & blue; + var andMat = new Mat(mat.Size(), MatType.CV_8UC1); - Triangle(andMat); - } + Cv2.BitwiseAnd(red, blue, andMat); - public static void Rectangle(Mat andMat) - { - //腐蚀 - var kernel = Cv2.GetStructuringElement(MorphShapes.Rect, new Size(3, 3)); - var res = new Mat(andMat.Size(), MatType.CV_8UC1); - Cv2.Erode(andMat, res, kernel); - Cv2.ImShow("erode", res); + // 寻找轮廓 + Cv2.FindContours(andMat, out var contours, out var hierarchy, RetrievalModes.External, ContourApproximationModes.ApproxSimple); + Mat dst = Mat.Zeros(andMat.Size(), MatType.CV_8UC3); - Cv2.FindContours(res, out var contours, out var hierarchy, RetrievalModes.External, ContourApproximationModes.ApproxNone); - for (int i = 0; i < contours.Length; i++) + // 计算最大外接矩形 + + if (contours.Length > 0) { - // 最小外接矩形 - var rect = Cv2.MinAreaRect(contours[i]); - // 矩形的四个顶点 - var points = Cv2.BoxPoints(rect); - // 绘制矩形 - for (int j = 0; j < 4; ++j) + var maxRect = Rect.Empty; + var maxIndex = 0; + for (int i = 0; i < contours.Length; i++) { - //Cv2.Line(mat, (Point)points[j], (Point)points[(j + 1) % 4], Scalar.Red, 1); + var box = Cv2.BoundingRect(contours[i]); + if (box.Width * box.Height > maxRect.Width * maxRect.Height) + { + maxRect = box; + maxIndex = i; + } } - } - } - public static void Triangle(Mat gray) - { - Cv2.FindContours(gray, out var contours, out var hierarchy, RetrievalModes.External, ContourApproximationModes.ApproxNone); - Mat dst = Mat.Zeros(gray.Size(), MatType.CV_8UC3); - for (int i = 0; i < contours.Length; i++) - { - Cv2.DrawContours(dst, contours, i, new Scalar(0, 255, 0), 1, LineTypes.Link4, hierarchy); - } + var maxContour = contours[maxIndex]; - // 遍历轮廓 - for (int i = 0; i < contours.Length; i++) - { // 计算轮廓的周长 - double perimeter = Cv2.ArcLength(contours[i], true); + var perimeter = Cv2.ArcLength(maxContour, true); // 近似多边形拟合 - OpenCvSharp.Point[] approx = Cv2.ApproxPolyDP(contours[i], 0.04 * perimeter, true); + var approx = Cv2.ApproxPolyDP(maxContour, 0.08 * perimeter, true); // 如果拟合的多边形有三个顶点,认为是三角形 if (approx.Length == 3) { - // 计算三条边的长度 - var sideLengths = new double[3]; - sideLengths[0] = Distance(approx[1], approx[2]); - sideLengths[1] = Distance(approx[2], approx[0]); - sideLengths[2] = Distance(approx[0], approx[1]); + // 剪裁出三角形所在区域 + var newSrcMat = new Mat(mat, maxRect); - var result = sideLengths - .Select((value, index) => new { Value = value, Index = index }) - .OrderBy(item => item.Value) - .First(); + // HSV 阈值取出中心飞镖 + var hsvMat = new Mat(); + Cv2.CvtColor(newSrcMat, hsvMat, ColorConversionCodes.BGR2HSV); + // var lowScalar = new Scalar(95, 255, 255); + // var highScalar = new Scalar(255, 255, 255); + var lowScalar = new Scalar(93, 155, 170); + var highScalar = new Scalar(255, 255, 255); + var hsvThresholdMat = new Mat(); + Cv2.InRange(hsvMat, lowScalar, highScalar, hsvThresholdMat); - // 计算最短线的中点 - var residue = approx.ToList(); - residue.RemoveAt(result.Index); - var midPoint = new OpenCvSharp.Point((residue[0].X + residue[1].X) / 2, (residue[0].Y + residue[1].Y) / 2); + // 循环计算三条边的中点,并计算中点到顶点的所有点中连续黑色像素的个数 + var maxBlackCount = 0; + Point correctP1 = new(), correctP2 = new(); + var offset = new Point(maxRect.X, maxRect.Y); + for (int i = 0; i < 3; i++) + { + var midPoint = Midpoint(approx[i], approx[(i + 1) % 3]); + var targetPoint = approx[(i + 2) % 3]; - // 在图像上绘制直线 - //Cv2.Line(dst2, approx[result.Index], midPoint, Scalar.Red, 1); + // 中点到顶点的所有点 + var lineIterator = new LineIterator(hsvThresholdMat, midPoint - offset, targetPoint - offset, PixelConnectivity.Connectivity8); - VisionContext.Instance().DrawContent.PutLine("co", new LineDrawable(midPoint, approx[result.Index] + (approx[result.Index] - midPoint) * 3)); + // 计算连续黑色像素的个数 + var blackCount = 0; + foreach (var item in lineIterator) + { + if (item.GetValue().Item0 == 255) + { + break; + } + + blackCount++; + } + + if (blackCount > maxBlackCount) + { + maxBlackCount = blackCount; + correctP1 = midPoint; + correctP2 = targetPoint; + } + } + VisionContext.Instance().DrawContent.PutLine("co", new LineDrawable(correctP1, correctP2 + (correctP2 - correctP1) * 3)); } } - } - static double Distance(OpenCvSharp.Point pt1, OpenCvSharp.Point pt2) + static Point Midpoint(Point p1, Point p2) { - int deltaX = Math.Abs(pt2.X - pt1.X); - int deltaY = Math.Abs(pt2.Y - pt1.Y); - - return Math.Sqrt(deltaX * deltaX + deltaY * deltaY); + var midX = (p1.X + p2.X) / 2; + var midY = (p1.Y + p2.Y) / 2; + return new Point(midX, midY); } } \ No newline at end of file