Created
February 23, 2023 03:19
-
-
Save bradmartin333/f0d2b875d05596f17216eff90fdc8192 to your computer and use it in GitHub Desktop.
Calculate entropy-based image focus score
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
internal class Focus | |
{ | |
public string ElapsedTime; | |
public Bitmap ScaledImage; | |
private int ScanSize, Width, Height; | |
private const double ImageScaling = 0.2; | |
private const int GridSize = 20; // N x N grid | |
private const double DataPercentage = 0.2; // Highest % of available data from training grid | |
private int[] Tiles; | |
private double[] Scores; | |
public Focus(Bitmap bmp) | |
{ | |
ScaledImage = new Bitmap(bmp, new Size((int)Math.Round(bmp.Width * ImageScaling), (int)Math.Round(bmp.Height * ImageScaling))); | |
Stopwatch stopwatch = new Stopwatch(); | |
stopwatch.Start(); | |
ScoreImageGrid(); | |
stopwatch.Stop(); | |
ElapsedTime = $"{Math.Round((decimal)stopwatch.ElapsedMilliseconds, 3)} ms"; | |
HighlightTiles(); | |
} | |
/// Handy tool used as a method | |
private void BitmapCrop(Rectangle crop, Bitmap src, ref Bitmap target) | |
{ | |
using var g = Graphics.FromImage(target); | |
g.DrawImage(src, new Rectangle(0, 0, crop.Width, crop.Height), crop, GraphicsUnit.Pixel); | |
} | |
// Gets grid pixels and then scores the image | |
private double ScoreImageFocus(Bitmap img) | |
{ | |
ScanSize = (int)Math.Ceiling((Math.Min(img.Height, img.Width) * (1.0 / GridSize))); | |
Width = img.Width / ScanSize; | |
Height = img.Height / ScanSize; | |
// Get red values for every pixel in a 2D array | |
double[,] PxlVals = new double[Width, Height]; | |
for (int i = 0; i < Width; i++) | |
for (int j = 0; j < Height; j++) | |
PxlVals[i, j] = img.GetPixel(i * ScanSize, j * ScanSize).R; | |
return GetNeighborSharpness(PxlVals); | |
} | |
// The core of AutoFocus | |
private double GetNeighborSharpness(double[,] PxlVals) | |
{ | |
List<double> PDiff = new List<double>(); | |
for (int i = 0; i < Width; i++) | |
for (int j = 0; j < Height; j++) | |
{ | |
double LocalPDiff = 0.0; | |
for (int k = i - ScanSize; k <= i + ScanSize; k++) | |
for (int l = j - ScanSize; l <= j + ScanSize; l++) | |
if (k > 0 && k < Width - 1 && l > 0 && l < Height - 1) | |
{ | |
double p1 = PxlVals[i, j]; | |
double p2 = PxlVals[k, l]; | |
if (p1 == 0 && p2 == 0) continue; | |
LocalPDiff += Math.Abs(p1 - p2) / ((p1 + p2) / 2); | |
} | |
PDiff.Add(LocalPDiff / (ScanSize * ScanSize)); | |
} | |
// Return average sharpness | |
double score = PDiff.Sum() / PDiff.Count; | |
return score; | |
} | |
/// <summary> | |
/// Scores an image based off the tiles from a trained grid only | |
/// </summary> | |
/// <param name="img"></param> | |
/// <param name="tiles"></param> | |
/// <returns></returns> | |
private double ScoreImageGrid() | |
{ | |
Tiles = GetTiles(); | |
Scores = new double[Tiles.Length]; | |
int tileScanSize = (int)(Math.Min(ScaledImage.Height, ScaledImage.Width) * (1.0 / GridSize)); | |
int tileIDX = 0; | |
List<double> scores = new List<double>(); | |
for (int i = 0; i < ScaledImage.Width; i += tileScanSize) | |
for (int j = 0; j < ScaledImage.Height; j += tileScanSize) | |
{ | |
if (Tiles.Contains(tileIDX)) | |
{ | |
Bitmap tile = new Bitmap(tileScanSize, tileScanSize); | |
BitmapCrop(new Rectangle(i, j, tileScanSize, tileScanSize), ScaledImage, ref tile); | |
scores.Add(ScoreImageFocus(tile)); | |
Scores[Tiles.ToList().IndexOf(tileIDX)] = scores.Last(); | |
} | |
tileIDX++; | |
if (scores.Count == Tiles.Length) | |
break; | |
} | |
GC.Collect(); | |
return scores.Sum() / scores.Count(); | |
} | |
// Returns the tiles within a grid that have entropies in the highest 2 histogram bins | |
private int[] GetTiles() | |
{ | |
int tileScanSize = (int)(Math.Min(ScaledImage.Height, ScaledImage.Width) * (1.0 / GridSize)); | |
Dictionary<int, double> entropyDict = new Dictionary<int, double>(); | |
int entropyIDX = 0; | |
for (int i = 0; i < ScaledImage.Width; i += tileScanSize) | |
for (int j = 0; j < ScaledImage.Height; j += tileScanSize) | |
{ | |
Bitmap tile = new Bitmap(tileScanSize, tileScanSize); | |
BitmapCrop(new Rectangle(i, j, tileScanSize, tileScanSize), ScaledImage, ref tile); | |
List<double> entropyList = new List<double>(); | |
for (int k = 0; k < tile.Width; k++) | |
for (int l = 0; l < tile.Height; l++) | |
entropyList.Add(tile.GetPixel(k, l).ToArgb()); | |
entropyDict.Add(entropyIDX, Entropy(entropyList)); | |
entropyIDX++; | |
} | |
IEnumerable<int> sortedTiles = entropyDict.OrderBy(x => x.Value).Select(x => x.Key).Reverse(); | |
int[] tiles = sortedTiles.Take((int)(sortedTiles.Count() * DataPercentage)).ToArray(); | |
return tiles; | |
} | |
/// <summary> | |
/// Colorize with the grid layout of the grid training image | |
/// </summary> | |
private void HighlightTiles() | |
{ | |
int tileScanSize = (int)(Math.Min(ScaledImage.Height, ScaledImage.Width) * (1.0 / GridSize)); | |
int tileIDX = 0; | |
using Graphics g = Graphics.FromImage(ScaledImage); | |
for (int i = 0; i < ScaledImage.Width; i += tileScanSize) | |
for (int j = 0; j < ScaledImage.Height; j += tileScanSize) | |
{ | |
if (Tiles.Contains(tileIDX)) | |
g.FillRectangle( | |
new SolidBrush(Color.FromArgb(150, Colorizer.GetScoreColor(Scores[Tiles.ToList().IndexOf(tileIDX)]))), | |
new Rectangle(i, j, tileScanSize, tileScanSize)); | |
tileIDX++; | |
} | |
} | |
/// <summary> | |
/// This is what lies behind MathNet's Entropy calculation | |
/// </summary> | |
/// <param name="redPixels"> | |
/// An image deconstructed into a list of it's Red channel pixel intensities | |
/// </param> | |
/// <returns> | |
/// Calculated Entropy | |
/// </returns> | |
private static double Entropy(List<double> redPixels) | |
{ | |
Dictionary<double, double> dictionary = new Dictionary<double, double>(); | |
int num = 0; | |
foreach (double num2 in redPixels) | |
{ | |
if (double.IsNaN(num2)) | |
return double.NaN; | |
if (dictionary.TryGetValue(num2, out double num3)) | |
num3 = (dictionary[num2] = num3 + 1.0); | |
else | |
dictionary.Add(num2, 1.0); | |
num++; | |
} | |
double num4 = 0.0; | |
foreach (KeyValuePair<double, double> keyValuePair in dictionary) | |
{ | |
double num5 = keyValuePair.Value / (double)num; | |
num4 += num5 * Math.Log(num5, 2.0); | |
} | |
return -num4; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment