cell histogram
This commit is contained in:
@@ -0,0 +1,258 @@
|
||||
using LindtLeerformPlugin.Models;
|
||||
using OpenCvSharp;
|
||||
|
||||
namespace LindtLeerformPlugin.Services;
|
||||
|
||||
public class LeerformPatternRecognitionService
|
||||
{
|
||||
private static readonly Scalar GoodColor = new(0, 255, 0); // BGR Green
|
||||
private static readonly Scalar BadColor = new(0, 0, 255); // BGR Red
|
||||
|
||||
public IReadOnlyList<CellRegion> ComputeCells(CellPattern pattern)
|
||||
{
|
||||
var cells = new List<CellRegion>();
|
||||
if (pattern.Rows <= 0 || pattern.Cols <= 0 || pattern.RoiWidth <= 0 || pattern.RoiHeight <= 0)
|
||||
return cells;
|
||||
|
||||
var cellW = pattern.RoiWidth / pattern.Cols;
|
||||
var cellH = pattern.RoiHeight / pattern.Rows;
|
||||
var padding = Math.Max(0, pattern.Padding);
|
||||
|
||||
var index = 0;
|
||||
for (var r = 0; r < pattern.Rows; r++)
|
||||
{
|
||||
for (var c = 0; c < pattern.Cols; c++)
|
||||
{
|
||||
var x = pattern.RoiX + c * cellW + padding;
|
||||
var y = pattern.RoiY + r * cellH + padding;
|
||||
var w = Math.Max(1, cellW - 2 * padding);
|
||||
var h = Math.Max(1, cellH - 2 * padding);
|
||||
var rect = new Rect(x, y, w, h);
|
||||
var center = new Point(x + w / 2, y + h / 2);
|
||||
var radius = Math.Max(1, Math.Min(w, h) / 2);
|
||||
|
||||
cells.Add(new CellRegion
|
||||
{
|
||||
Index = index++,
|
||||
Row = r,
|
||||
Col = c,
|
||||
BoundingBox = rect,
|
||||
Center = center,
|
||||
Radius = radius
|
||||
});
|
||||
}
|
||||
}
|
||||
return cells;
|
||||
}
|
||||
|
||||
public Mat BuildMask(CellPattern pattern, Size imageSize, int? cellIndex = null)
|
||||
{
|
||||
var mask = new Mat(imageSize, MatType.CV_8UC1, Scalar.All(0));
|
||||
var cells = ComputeCells(pattern);
|
||||
var color = Scalar.All(255);
|
||||
var imageRect = new Rect(0, 0, imageSize.Width, imageSize.Height);
|
||||
|
||||
foreach (var cell in cells)
|
||||
{
|
||||
if (cellIndex.HasValue && cellIndex.Value != cell.Index)
|
||||
continue;
|
||||
|
||||
var clipped = cell.BoundingBox & imageRect;
|
||||
if (clipped.Width <= 0 || clipped.Height <= 0)
|
||||
continue;
|
||||
|
||||
if (pattern.Shape == CellShape.Square)
|
||||
{
|
||||
Cv2.Rectangle(mask, clipped, color, thickness: -1);
|
||||
}
|
||||
else
|
||||
{
|
||||
Cv2.Circle(mask, cell.Center, cell.Radius, color, thickness: -1);
|
||||
}
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
|
||||
public float[] ComputeReferenceHistogram(Mat bgrImage, CellPattern pattern, int[]? bins = null)
|
||||
{
|
||||
bins ??= [5, 5, 5];
|
||||
using var mask = BuildMask(pattern, new Size(bgrImage.Cols, bgrImage.Rows));
|
||||
using var hist = CalcHistogram(bgrImage, mask, bins);
|
||||
return HistogramToArray(hist);
|
||||
}
|
||||
|
||||
public IReadOnlyList<CellResult> EvaluateCells(Mat bgrImage, LeerformRecipe recipe)
|
||||
{
|
||||
var cells = ComputeCells(recipe.Pattern);
|
||||
var bins = recipe.HistogramBins is { Length: 3 } ? recipe.HistogramBins : [5, 5, 5];
|
||||
var imageSize = new Size(bgrImage.Cols, bgrImage.Rows);
|
||||
var results = new List<CellResult>(cells.Count);
|
||||
|
||||
using var refHist = ArrayToHistogram(recipe.ReferenceHistogram, bins);
|
||||
|
||||
foreach (var cell in cells)
|
||||
{
|
||||
using var mask = BuildMask(recipe.Pattern, imageSize, cell.Index);
|
||||
using var hist = CalcHistogram(bgrImage, mask, bins);
|
||||
var distance = Cv2.CompareHist(refHist, hist, HistCompMethods.Bhattacharyya);
|
||||
results.Add(new CellResult
|
||||
{
|
||||
Index = cell.Index,
|
||||
IsGood = distance <= recipe.Tolerance,
|
||||
Distance = distance
|
||||
});
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
public Mat RenderOverlay(Mat bgrImage, CellPattern pattern, IReadOnlyList<CellResult> results)
|
||||
{
|
||||
var overlay = bgrImage.Clone();
|
||||
var cells = ComputeCells(pattern);
|
||||
var resultByIndex = results.ToDictionary(r => r.Index);
|
||||
const int thickness = 2;
|
||||
|
||||
foreach (var cell in cells)
|
||||
{
|
||||
if (!resultByIndex.TryGetValue(cell.Index, out var result))
|
||||
continue;
|
||||
|
||||
var color = result.IsGood ? GoodColor : BadColor;
|
||||
|
||||
if (pattern.Shape == CellShape.Square)
|
||||
{
|
||||
Cv2.Rectangle(overlay, cell.BoundingBox, color, thickness);
|
||||
}
|
||||
else
|
||||
{
|
||||
Cv2.Circle(overlay, cell.Center, cell.Radius, color, thickness);
|
||||
}
|
||||
}
|
||||
return overlay;
|
||||
}
|
||||
|
||||
public void DrawPattern(Mat target, CellPattern pattern, Scalar color, int thickness = 2)
|
||||
{
|
||||
var cells = ComputeCells(pattern);
|
||||
foreach (var cell in cells)
|
||||
{
|
||||
if (pattern.Shape == CellShape.Square)
|
||||
Cv2.Rectangle(target, cell.BoundingBox, color, thickness);
|
||||
else
|
||||
Cv2.Circle(target, cell.Center, cell.Radius, color, thickness);
|
||||
}
|
||||
}
|
||||
|
||||
public int? FindCellAtPoint(int x, int y, CellPattern pattern)
|
||||
{
|
||||
var cells = ComputeCells(pattern);
|
||||
foreach (var cell in cells)
|
||||
{
|
||||
if (pattern.Shape == CellShape.Square)
|
||||
{
|
||||
if (cell.BoundingBox.Contains(x, y))
|
||||
return cell.Index;
|
||||
}
|
||||
else
|
||||
{
|
||||
var dx = x - cell.Center.X;
|
||||
var dy = y - cell.Center.Y;
|
||||
if (dx * dx + dy * dy <= cell.Radius * cell.Radius)
|
||||
return cell.Index;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public float[] ComputeCellHistogram(Mat bgrImage, CellPattern pattern, int cellIndex, int[]? bins = null)
|
||||
{
|
||||
bins ??= [5, 5, 5];
|
||||
using var mask = BuildMask(pattern, new Size(bgrImage.Cols, bgrImage.Rows), cellIndex);
|
||||
using var hist = CalcHistogram(bgrImage, mask, bins);
|
||||
return HistogramToArray(hist);
|
||||
}
|
||||
|
||||
public double CompareHistograms(float[] reference, float[] candidate, int[]? bins = null)
|
||||
{
|
||||
bins ??= [5, 5, 5];
|
||||
using var refMat = ArrayToHistogram(reference, bins);
|
||||
using var candMat = ArrayToHistogram(candidate, bins);
|
||||
return Cv2.CompareHist(refMat, candMat, HistCompMethods.Bhattacharyya);
|
||||
}
|
||||
|
||||
public static Mat RenderHistogramChart(float[] histogram, int[] bins, int width = 600, int height = 220)
|
||||
{
|
||||
var chart = new Mat(height, width, MatType.CV_8UC3, new Scalar(30, 30, 30));
|
||||
if (histogram == null || histogram.Length == 0)
|
||||
return chart;
|
||||
|
||||
var totalBins = bins[0] * bins[1] * bins[2];
|
||||
var max = 0f;
|
||||
for (var i = 0; i < histogram.Length; i++)
|
||||
if (histogram[i] > max) max = histogram[i];
|
||||
if (max <= 0)
|
||||
return chart;
|
||||
|
||||
const int leftPad = 10;
|
||||
const int bottomPad = 15;
|
||||
var barWidth = Math.Max(1, (width - 2 * leftPad) / totalBins);
|
||||
var maxBarHeight = height - bottomPad - 10;
|
||||
|
||||
for (var i = 0; i < totalBins && i < histogram.Length; i++)
|
||||
{
|
||||
var binB = i / (bins[1] * bins[2]);
|
||||
var binG = (i / bins[2]) % bins[1];
|
||||
var binR = i % bins[2];
|
||||
|
||||
var b = (int)((binB + 0.5) * 256 / bins[0]);
|
||||
var g = (int)((binG + 0.5) * 256 / bins[1]);
|
||||
var r = (int)((binR + 0.5) * 256 / bins[2]);
|
||||
|
||||
var barHeight = (int)(histogram[i] / max * maxBarHeight);
|
||||
var x = leftPad + i * barWidth;
|
||||
var y = height - bottomPad - barHeight;
|
||||
Cv2.Rectangle(chart, new Rect(x, y, barWidth, barHeight), new Scalar(b, g, r), -1);
|
||||
}
|
||||
|
||||
return chart;
|
||||
}
|
||||
|
||||
private static Mat CalcHistogram(Mat bgrImage, Mat mask, int[] bins)
|
||||
{
|
||||
var hist = new Mat();
|
||||
Cv2.CalcHist(
|
||||
images: new[] { bgrImage },
|
||||
channels: new[] { 0, 1, 2 },
|
||||
mask: mask,
|
||||
hist: hist,
|
||||
dims: 3,
|
||||
histSize: bins,
|
||||
ranges: new[] { new Rangef(0, 256), new Rangef(0, 256), new Rangef(0, 256) });
|
||||
Cv2.Normalize(hist, hist);
|
||||
|
||||
var totalBins = bins[0] * bins[1] * bins[2];
|
||||
var reshaped = hist.Reshape(1, totalBins).Clone();
|
||||
hist.Dispose();
|
||||
return reshaped;
|
||||
}
|
||||
|
||||
private static float[] HistogramToArray(Mat hist)
|
||||
{
|
||||
var totalBins = hist.Rows * hist.Cols;
|
||||
var arr = new float[totalBins];
|
||||
for (var i = 0; i < totalBins; i++)
|
||||
arr[i] = hist.At<float>(i, 0);
|
||||
return arr;
|
||||
}
|
||||
|
||||
private static Mat ArrayToHistogram(float[] data, int[] bins)
|
||||
{
|
||||
var totalBins = bins[0] * bins[1] * bins[2];
|
||||
var mat = new Mat(totalBins, 1, MatType.CV_32F, Scalar.All(0));
|
||||
var count = Math.Min(data.Length, totalBins);
|
||||
for (var i = 0; i < count; i++)
|
||||
mat.Set<float>(i, 0, data[i]);
|
||||
return mat;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user