267 lines
10 KiB
C#
267 lines
10 KiB
C#
// ============================================================================
|
|
// Copyright 穢 2026 Hexagon Technology Center GmbH. All Rights Reserved.
|
|
// ��辣�? HistogramOverlayProcessor.cs
|
|
// �讛膩: �湔䲮�曉��删�摮琜�霈∠��啣漲�湔䲮�曉僎隞亥��脫��嗅㦛蝏睃��啁��𨅯㦛�誩椰銝𡃏�
|
|
// �蠘�:
|
|
// - 霈∠�颲枏��曉����摨衣凒�孵㦛
|
|
// - 撠�凒�孵㦛蝏睃�銝箄��脣��𤩺��梁𠶖�曉��惩��曉�撌虫�閫?
|
|
// - 颲枏枂�湔䲮�曄�霈∟”�潭㺭�?
|
|
// 蝞埈�: �啣漲�湔䲮�曄�霈?+ 敶抵𠧧�曉��惩�
|
|
// 雿𡏭�? �𦒘� wei.lw.li@hexagon.com
|
|
// ============================================================================
|
|
|
|
using Emgu.CV;
|
|
using Emgu.CV.CvEnum;
|
|
using Emgu.CV.Structure;
|
|
using Serilog;
|
|
using System.Drawing;
|
|
using System.Text;
|
|
using XP.ImageProcessing.Core;
|
|
|
|
namespace XP.ImageProcessing.Processors;
|
|
|
|
/// <summary>
|
|
/// �湔䲮�曉��删�摮琜�霈∠��啣漲�湔䲮�曉僎隞亥��脫��嗅㦛蝏睃��啁��𨅯㦛�誩椰銝𡃏�嚗���嗉��箇�霈∟”�?
|
|
/// </summary>
|
|
public class HistogramOverlayProcessor : ImageProcessorBase
|
|
{
|
|
private static readonly ILogger _logger = Log.ForContext<HistogramOverlayProcessor>();
|
|
|
|
// �箏���㺭
|
|
private const int ChartWidth = 256; // �梁𠶖�曄��曉躹摰賢漲
|
|
|
|
private const int ChartHeight = 200; // �梁𠶖�曄��曉躹擃睃漲
|
|
private const int AxisMarginLeft = 50; // Y頧湔�蝑暸��坔捐摨?
|
|
private const int AxisMarginBottom = 25; // X頧湔�蝑暸��䠷�摨?
|
|
private const int Padding = 8; // �峕艶憸嘥���器頝?
|
|
private const int PaddingRight = 25; // �喃儒憸嘥���器頝嘅�摰寧熙X頧湔錰撠曉�摨行�摮梹�
|
|
private const int Margin = 10; // 頝嘥㦛�誩椰銝𡃏�颲寡�
|
|
private const float BgAlpha = 0.6f;
|
|
private const double FontScale = 0.35;
|
|
private const int FontThickness = 1;
|
|
|
|
public HistogramOverlayProcessor()
|
|
{
|
|
Name = LocalizationHelper.GetString("HistogramOverlayProcessor_Name");
|
|
Description = LocalizationHelper.GetString("HistogramOverlayProcessor_Description");
|
|
}
|
|
|
|
protected override void InitializeParameters()
|
|
{
|
|
// �惩虾靚���?
|
|
}
|
|
|
|
public override Image<Gray, byte> Process(Image<Gray, byte> inputImage)
|
|
{
|
|
int h = inputImage.Height;
|
|
int w = inputImage.Width;
|
|
var srcData = inputImage.Data;
|
|
|
|
// === 1. 霈∠��啣漲�湔䲮�?===
|
|
var hist = new int[256];
|
|
for (int y = 0; y < h; y++)
|
|
for (int x = 0; x < w; x++)
|
|
hist[srcData[y, x, 0]]++;
|
|
|
|
int maxCount = 0;
|
|
long totalPixels = (long)h * w;
|
|
for (int i = 0; i < 256; i++)
|
|
if (hist[i] > maxCount) maxCount = hist[i];
|
|
|
|
// === 2. 霈∠�蝏蠘恣靽⊥� ===
|
|
double mean = 0, variance = 0;
|
|
int minVal = 255, maxVal = 0;
|
|
int modeVal = 0, modeCount = 0;
|
|
long medianTarget = totalPixels / 2, cumulative = 0;
|
|
int medianVal = 0;
|
|
bool medianFound = false;
|
|
|
|
for (int i = 0; i < 256; i++)
|
|
{
|
|
if (hist[i] > 0)
|
|
{
|
|
if (i < minVal) minVal = i;
|
|
if (i > maxVal) maxVal = i;
|
|
}
|
|
if (hist[i] > modeCount) { modeCount = hist[i]; modeVal = i; }
|
|
mean += (double)i * hist[i];
|
|
cumulative += hist[i];
|
|
if (!medianFound && cumulative >= medianTarget) { medianVal = i; medianFound = true; }
|
|
}
|
|
mean /= totalPixels;
|
|
for (int i = 0; i < 256; i++)
|
|
variance += hist[i] * (i - mean) * (i - mean);
|
|
variance /= totalPixels;
|
|
double stdDev = Math.Sqrt(variance);
|
|
|
|
// === 3. 颲枏枂銵冽聢�唳旿 ===
|
|
var sb = new StringBuilder();
|
|
sb.AppendLine("=== �啣漲�湔䲮�曄�霈?===");
|
|
sb.AppendLine($"�曉�撠箏站: {w} x {h}");
|
|
sb.AppendLine($"�餃�蝝䭾㺭: {totalPixels}");
|
|
sb.AppendLine($"��撠讐�摨? {minVal}");
|
|
sb.AppendLine($"��憭抒�摨? {maxVal}");
|
|
sb.AppendLine($"撟喳��啣漲: {mean:F2}");
|
|
sb.AppendLine($"銝凋��啣漲: {medianVal}");
|
|
sb.AppendLine($"隡埈㺭�啣漲: {modeVal} (�箇緵 {modeCount} 甈?");
|
|
sb.AppendLine($"���撌? {stdDev:F2}");
|
|
sb.AppendLine();
|
|
sb.AppendLine("�啣漲�墦t�讐��豹t�䭾�(%)");
|
|
for (int i = 0; i < 256; i++)
|
|
{
|
|
if (hist[i] > 0)
|
|
sb.AppendLine($"{i}\t{hist[i]}\t{(double)hist[i] / totalPixels * 100.0:F4}");
|
|
}
|
|
|
|
OutputData["HistogramTable"] = sb.ToString();
|
|
OutputData["Histogram"] = hist;
|
|
|
|
// === 4. ���敶抵𠧧�惩��曉�嚗���脫��嗅㦛 + XY頧游���� ===
|
|
var colorImage = inputImage.Convert<Bgr, byte>();
|
|
var colorData = colorImage.Data;
|
|
|
|
// 撣��嚗朞��臬躹�笔��?Padding + Y頧湔�蝑?+ 蝏睃㦛�?+ Padding嚗�偌撟喉�
|
|
// Padding + 蝏睃㦛�?+ X頧湔�蝑?+ Padding嚗���湛�
|
|
int totalW = Padding + AxisMarginLeft + ChartWidth + PaddingRight;
|
|
int totalH = Padding + ChartHeight + AxisMarginBottom + Padding;
|
|
int bgW = Math.Min(totalW, w - Margin);
|
|
int bgH = Math.Min(totalH, h - Margin);
|
|
|
|
if (bgW > Padding + AxisMarginLeft && bgH > Padding + AxisMarginBottom)
|
|
{
|
|
int plotW = Math.Min(ChartWidth, bgW - Padding - AxisMarginLeft - PaddingRight);
|
|
int plotH = Math.Min(ChartHeight, bgH - Padding - AxisMarginBottom - Padding);
|
|
if (plotW <= 0 || plotH <= 0) goto SkipOverlay;
|
|
|
|
// 蝏睃㦛�箏椰銝𡃏��典㦛�譍葉����?
|
|
int plotX0 = Margin + Padding + AxisMarginLeft;
|
|
int plotY0 = Margin + Padding;
|
|
|
|
// 霈∠�瘥誩��梢�
|
|
double binWidth = (double)plotW / 256.0;
|
|
var barHeights = new int[plotW];
|
|
for (int px = 0; px < plotW; px++)
|
|
{
|
|
int bin = Math.Min((int)(px / binWidth), 255);
|
|
barHeights[px] = maxCount > 0 ? (int)((long)hist[bin] * (plotH - 1) / maxCount) : 0;
|
|
}
|
|
|
|
float alpha = BgAlpha;
|
|
float inv = 1.0f - alpha;
|
|
|
|
// 蝏睃��𢠃�𤩺�暺𤏸𠧧�峕艶嚗���𡝗㟲銝芸躹�笔鉄�鞉�頧游���器頝嘅�
|
|
Parallel.For(0, bgH, dy =>
|
|
{
|
|
int imgY = Margin + dy;
|
|
if (imgY >= h) return;
|
|
for (int dx = 0; dx < bgW; dx++)
|
|
{
|
|
int imgX = Margin + dx;
|
|
if (imgX >= w) break;
|
|
colorData[imgY, imgX, 0] = (byte)(int)(colorData[imgY, imgX, 0] * inv);
|
|
colorData[imgY, imgX, 1] = (byte)(int)(colorData[imgY, imgX, 1] * inv);
|
|
colorData[imgY, imgX, 2] = (byte)(int)(colorData[imgY, imgX, 2] * inv);
|
|
}
|
|
});
|
|
|
|
// 蝏睃��肽𠧧�梁𠶖�?
|
|
Parallel.For(0, plotH, dy =>
|
|
{
|
|
int imgY = plotY0 + dy;
|
|
if (imgY >= h) return;
|
|
int rowFromBottom = plotH - 1 - dy;
|
|
|
|
for (int dx = 0; dx < plotW; dx++)
|
|
{
|
|
int imgX = plotX0 + dx;
|
|
if (imgX >= w) break;
|
|
|
|
if (rowFromBottom < barHeights[dx])
|
|
{
|
|
byte curB = colorData[imgY, imgX, 0];
|
|
byte curG = colorData[imgY, imgX, 1];
|
|
byte curR = colorData[imgY, imgX, 2];
|
|
colorData[imgY, imgX, 0] = (byte)Math.Clamp(curB + (int)(255 * alpha), 0, 255);
|
|
colorData[imgY, imgX, 1] = (byte)Math.Clamp(curG + (int)(50 * alpha), 0, 255);
|
|
colorData[imgY, imgX, 2] = (byte)Math.Clamp(curR + (int)(50 * alpha), 0, 255);
|
|
}
|
|
}
|
|
});
|
|
|
|
// === 5. 蝏睃��鞉�頧渡瑪���摨行�瘜?===
|
|
var white = new MCvScalar(255, 255, 255);
|
|
var gray = new MCvScalar(180, 180, 180);
|
|
|
|
// Y頧渡瑪
|
|
CvInvoke.Line(colorImage,
|
|
new Point(plotX0, plotY0),
|
|
new Point(plotX0, plotY0 + plotH),
|
|
white, 1);
|
|
|
|
// X頧渡瑪
|
|
CvInvoke.Line(colorImage,
|
|
new Point(plotX0, plotY0 + plotH),
|
|
new Point(plotX0 + plotW, plotY0 + plotH),
|
|
white, 1);
|
|
|
|
// X頧游�摨? 0, 64, 128, 192, 255
|
|
int[] xTicks = { 0, 64, 128, 192, 255 };
|
|
foreach (int tick in xTicks)
|
|
{
|
|
int tx = plotX0 + (int)(tick * binWidth);
|
|
if (tx >= w) break;
|
|
CvInvoke.Line(colorImage,
|
|
new Point(tx, plotY0 + plotH),
|
|
new Point(tx, plotY0 + plotH + 4),
|
|
white, 1);
|
|
string label = tick.ToString();
|
|
CvInvoke.PutText(colorImage, label,
|
|
new Point(tx - 8, plotY0 + plotH + 18),
|
|
FontFace.HersheySimplex, FontScale, white, FontThickness);
|
|
}
|
|
|
|
// Y頧游�摨? 0%, 25%, 50%, 75%, 100%
|
|
for (int i = 0; i <= 4; i++)
|
|
{
|
|
int val = maxCount * i / 4;
|
|
int ty = plotY0 + plotH - (int)((long)plotH * i / 4);
|
|
CvInvoke.Line(colorImage,
|
|
new Point(plotX0 - 4, ty),
|
|
new Point(plotX0, ty),
|
|
white, 1);
|
|
// 蝵烐聢�𡁶瑪
|
|
if (i > 0 && i < 4)
|
|
{
|
|
for (int gx = plotX0 + 2; gx < plotX0 + plotW; gx += 6)
|
|
{
|
|
int gxEnd = Math.Min(gx + 2, plotX0 + plotW);
|
|
CvInvoke.Line(colorImage,
|
|
new Point(gx, ty),
|
|
new Point(gxEnd, ty),
|
|
gray, 1);
|
|
}
|
|
}
|
|
string label = FormatCount(val);
|
|
CvInvoke.PutText(colorImage, label,
|
|
new Point(Margin + Padding, ty + 4),
|
|
FontFace.HersheySimplex, FontScale, white, FontThickness);
|
|
}
|
|
}
|
|
|
|
SkipOverlay:
|
|
OutputData["PseudoColorImage"] = colorImage;
|
|
|
|
_logger.Debug("Process completed: histogram overlay, mean={Mean:F2}, stdDev={Std:F2}", mean, stdDev);
|
|
return inputImage.Clone();
|
|
}
|
|
|
|
/// <summary>
|
|
/// �澆��硋�蝝㰘恣�唬蛹蝝批�摮㛖泵銝莎�憒?12345 �?"12.3K"嚗?
|
|
/// </summary>
|
|
private static string FormatCount(int count)
|
|
{
|
|
if (count >= 1_000_000) return $"{count / 1_000_000.0:F1}M";
|
|
if (count >= 1_000) return $"{count / 1_000.0:F1}K";
|
|
return count.ToString();
|
|
}
|
|
} |