From 226d35f5d5015ab8f984ad46140db6d1ab98ca3e Mon Sep 17 00:00:00 2001 From: Alek-ban Date: Mon, 8 Jun 2026 07:37:38 +0300 Subject: [PATCH] feat: some analysis changes --- .../Analysis/AmineContent/AnalyzerService.cs | 115 +++++++++++++----- 1 file changed, 87 insertions(+), 28 deletions(-) diff --git a/GSS2.Core/Analysis/AmineContent/AnalyzerService.cs b/GSS2.Core/Analysis/AmineContent/AnalyzerService.cs index ce47b5e..195aa80 100644 --- a/GSS2.Core/Analysis/AmineContent/AnalyzerService.cs +++ b/GSS2.Core/Analysis/AmineContent/AnalyzerService.cs @@ -20,14 +20,14 @@ public partial class AnalyzerService public const int SEPARATOR_ROI_THRESHOLD = 5; public const float CONTOUR_MAX_SIZE = 100; - public const float CONTOUR_MAX_AREA = (float)Math.PI * (CONTOUR_MAX_SIZE * CONTOUR_MAX_SIZE) / 4.0f; - public const float CONTOUR_MIN_SIZE = 12; - public const float CONTOUR_MIN_AREA = (float)Math.PI * (CONTOUR_MIN_SIZE * CONTOUR_MIN_SIZE) / 4.0f / 4.0f; + public const float CONTOUR_MAX_AREA = (float)Math.PI * (CONTOUR_MAX_SIZE * CONTOUR_MAX_SIZE) / 5.0f; + public const float CONTOUR_MIN_SIZE = 10; + public const float CONTOUR_MIN_AREA = (float)Math.PI * (CONTOUR_MIN_SIZE * CONTOUR_MIN_SIZE) / 5.0f / 5.0f; public const int SEPARATOR_X = 519; public const int SEPARATOR_Y = 17; - public const int SEPARATOR_WIDTH = 2989; - public const int SEPARATOR_HEIGHT = 2985; + public const int SEPARATOR_WIDTH = 2969; + public const int SEPARATOR_HEIGHT = 2965; private class ClusterizationData { @@ -143,6 +143,8 @@ public partial class AnalyzerService Features = r.Values }) .ToList(); + trainDataSet = trainDataSet.Shuffle().ToList(); + _logger.LogInformation("Записей для обучения: {}", trainDataSet.Count()); var trainData = _ml.Data.LoadFromEnumerable(trainDataSet); cancellationToken.ThrowIfCancellationRequested(); @@ -160,10 +162,9 @@ public partial class AnalyzerService if (_ml is null) throw new InvalidOperationException(); - cancellationToken.ThrowIfCancellationRequested(); - _logger.LogInformation("Создание конвейера"); - var pipeline = _ml.Regression.Trainers.Sdca(); + + var pipeline = _ml.Regression.Trainers.FastTree(); cancellationToken.ThrowIfCancellationRequested(); @@ -184,13 +185,14 @@ public partial class AnalyzerService Features = r.Values }) .ToList(); + trainDataSet = trainDataSet.Shuffle().ToList(); + _logger.LogInformation("Записей для обучения: {}", trainDataSet.Count()); var trainData = _ml.Data.LoadFromEnumerable(trainDataSet); cancellationToken.ThrowIfCancellationRequested(); _logger.LogInformation("Обучение модели"); var model = pipeline.Fit(trainData); - _regressionEngine = _ml.Model.CreatePredictionEngine(model); model.Dispose(); @@ -230,7 +232,7 @@ public partial class AnalyzerService for (var row = 0; row < ANALYSIS_IMAGE_SIZE; row++) for (var col = 0; col < ANALYSIS_IMAGE_SIZE; col++) { - if (mask.At(row, col) != 255) + if (mask.At(row, col) == 0) continue; var vector = new float[FEATURES_LENGTH]; foreach (var (i, image) in images.Enumerate()) @@ -250,17 +252,72 @@ public partial class AnalyzerService }); if (prediction is null) continue; - result.Set(row, col, prediction.Value); - values[row * ANALYSIS_IMAGE_SIZE + col] = prediction.Value; + if (prediction.Value >= 0) + { + result.Set(row, col, prediction.Value); + values[row * ANALYSIS_IMAGE_SIZE + col] = prediction.Value; + } } - var oldMean = Cv2.Mean(result, mask).Val0; - values.Sort(); - float p999 = values[(int)(values.Length * 0.999)]; - Cv2.Threshold(result, result, p999, p999, ThresholdTypes.Trunc); - var newMean = Cv2.Mean(result, mask).Val0; - var scale = oldMean / newMean; - Cv2.ConvertScaleAbs(result, result, scale); + // foreach (var contour in contours) + // { + // var contourMask = new Mat(new Size(ANALYSIS_IMAGE_SIZE, ANALYSIS_IMAGE_SIZE), MatType.CV_8UC1, new Scalar(0)); + // var contourDistance = new Mat(new Size(ANALYSIS_IMAGE_SIZE, ANALYSIS_IMAGE_SIZE), MatType.CV_32FC1, new Scalar(0)); + // Cv2.DrawContours(contourMask, [contour], -1, new Scalar(255), -1); + // Cv2.DistanceTransform(contourMask, contourDistance, DistanceTypes.L2, DistanceTransformMasks.Mask5); + // Cv2.Pow(contourDistance, 2, contourDistance); + // Cv2.MinMaxLoc(contourDistance, out double minVal, out double maxVal); + // contourDistance.ConvertTo(contourDistance, MatType.CV_32FC1, 1 / (maxVal - minVal), 1 * minVal / (maxVal - minVal)); + // // Cv2.ConvertScaleAbs(contourDistance, contourDistance, -1, 1); + // Cv2.BitwiseNot(contourMask, contourMask); + // contourDistance.SetTo(new Scalar(1), contourMask); + // Cv2.Multiply(result, contourDistance, result); + // contourMask.Dispose(); + // contourDistance.Dispose(); + // } + + // var maskDistance = new Mat(); + // var resultDistance = new Mat(); + // var temp = new Mat(); + // Cv2.DistanceTransform(mask, maskDistance, DistanceTypes.L2, DistanceTransformMasks.Mask5); + + // Cv2.MinMaxLoc(result, out double minValue, out double maxValue, out _, out _, mask); + // result.ConvertTo(temp, MatType.CV_8UC1, 255 / (maxValue - 0), 255 * 0 / (maxValue - 0)); + // Cv2.DistanceTransform(temp, resultDistance, DistanceTypes.L2, DistanceTransformMasks.Mask5); + + // Cv2.Multiply(maskDistance, resultDistance, temp); + // Cv2.Threshold(temp, temp, 0, 255, ThresholdTypes.Binary); + + // for (var row = 0; row < ANALYSIS_IMAGE_SIZE; row++) + // for (var col = 0; col < ANALYSIS_IMAGE_SIZE; col++) + // if (mask.At(row, col) == 0) + // { + // result.Set(row, col, 0); + // mask.Set(row, col, 0); + // } + + // maskDistance.Dispose(); + // resultDistance.Dispose(); + // temp.Dispose(); + + // var oldMean = Cv2.Mean(result, mask).Val0; + // float p001 = values[values.Count(v => v == 0) + 1]; + // float p999 = values[(int)(values.Length * 0.999)]; + // Cv2.Threshold(result, result, p999, p999, ThresholdTypes.Trunc); + // var zeroMask = result.LessThanOrEqual(0); + // result.SetTo(p001, zeroMask); + // zeroMask.Dispose(); + // var newMean = Cv2.Mean(result, mask).Val0; + // var scale = oldMean / newMean; + // Cv2.ConvertScaleAbs(result, result, scale); + + // Добавляем эрозию к результатм и маске, удаляющую края, и пересчитываем контура + // поскольку края грунул подсвечиваются близлежайщими гранулами и сепаратором, + // их нельзя считаль представительными + Cv2.Erode(result, result, Cv2.GetStructuringElement(MorphShapes.Ellipse, new Size(3, 3))); + Cv2.Erode(mask, mask, Cv2.GetStructuringElement(MorphShapes.Ellipse, new Size(3, 3))); + Cv2.FindContours(mask, out var newContours, out _, RetrievalModes.List, ContourApproximationModes.ApproxSimple); + return (avgImage, roi, mask, contours, result, separatorType, sampleBrand); } catch (Exception ex) @@ -335,7 +392,7 @@ public partial class AnalyzerService continue; if (!keyMap.ContainsKey(prediction.Label)) keyMap.Add(prediction.Label, ++lastKey); - result.Set(row, col, keyMap[prediction.Label]); + result.Set(row, col, keyMap[prediction.Label]); } foreach (var image in images) @@ -344,7 +401,6 @@ public partial class AnalyzerService var valueMap = keyMap.ToDictionary(kv => kv.Value, kv => kv.Key); var frequency = new Dictionary(); - for (var row = 0; row < ANALYSIS_IMAGE_SIZE; row++) for (var col = 0; col < ANALYSIS_IMAGE_SIZE; col++) { @@ -358,13 +414,13 @@ public partial class AnalyzerService if (valueMap[value].StartsWith(separatorPrefix)) result.Set(row, col, 0); if (valueMap[value].StartsWith(samplePrefix)) - result.Set(row, col, ushort.MaxValue); + result.Set(row, col, ushort.MaxValue); } result.ConvertTo(result, MatType.CV_8UC1, 1 / 255.0); - Cv2.MorphologyEx(result, result, MorphTypes.Close, Cv2.GetStructuringElement(MorphShapes.Ellipse, new Size(3, 3)), iterations: 2); - Cv2.MorphologyEx(result, result, MorphTypes.Open, Cv2.GetStructuringElement(MorphShapes.Ellipse, new Size(3, 3)), iterations: 2); + Cv2.MorphologyEx(result, result, MorphTypes.Close, Cv2.GetStructuringElement(MorphShapes.Ellipse, new Size(3, 3))); + Cv2.MorphologyEx(result, result, MorphTypes.Open, Cv2.GetStructuringElement(MorphShapes.Ellipse, new Size(3, 3))); Cv2.Dilate(result, result, Cv2.GetStructuringElement(MorphShapes.Ellipse, new Size(3, 3))); Cv2.GaussianBlur(result, result, new Size(5, 5), 1); Cv2.Threshold(result, result, 127, 255, ThresholdTypes.Binary); @@ -374,6 +430,9 @@ public partial class AnalyzerService result = new Mat(new Size(ANALYSIS_IMAGE_SIZE, ANALYSIS_IMAGE_SIZE), MatType.CV_8UC1, new Scalar(0)); Cv2.DrawContours(result, contours.Where(ContourIsValid), -1, new Scalar(255), -1); + _logger.LogInformation("Найдено гранул: {}", contours.Where(ContourIsValid).Count()); + _logger.LogInformation("Детекторованные обекты:\n\t{}", string.Join("\n\t", frequency.Select(f => $"{f.Key}: {f.Value}"))); + return ( avgImage, roi.Value, @@ -504,7 +563,7 @@ public partial class AnalyzerService // Mat avgImageClone = new Mat(); // Mat mask = new Mat(); // Rect? roi = null; - + // avgImage.CopyTo(avgImageClone); // avgImageClone = avgImageClone.CvtColor(ColorConversionCodes.BGR2GRAY); @@ -524,9 +583,9 @@ public partial class AnalyzerService // } private static bool ContourIsValid(Point[] contour) { - var bbox = Cv2.BoundingRect(contour); + var bbox = Cv2.MinAreaRect(contour); var area = Cv2.ContourArea(contour); - return CONTOUR_MIN_SIZE < bbox.Width && CONTOUR_MIN_SIZE < bbox.Height && CONTOUR_MIN_AREA < area && - CONTOUR_MAX_SIZE > bbox.Width && CONTOUR_MAX_SIZE > bbox.Height && CONTOUR_MAX_AREA > area; + return CONTOUR_MIN_SIZE < bbox.Size.Width && CONTOUR_MIN_SIZE < bbox.Size.Height && CONTOUR_MIN_AREA < area && + CONTOUR_MAX_SIZE > bbox.Size.Width && CONTOUR_MAX_SIZE > bbox.Size.Height && CONTOUR_MAX_AREA > area; } }