Skip to content

Instantly share code, notes, and snippets.

@csmoore
Created December 1, 2018 00:24
Show Gist options
  • Save csmoore/ec2e3bbba2353f02e5f8febaab77711b to your computer and use it in GitHub Desktop.
Save csmoore/ec2e3bbba2353f02e5f8febaab77711b to your computer and use it in GitHub Desktop.
Basic Normalized Histogram Equalization Compare
/* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 */
using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
namespace HistogramTest
{
class Program
{
class FolderImageComparer
{
// Class/sample adapted from .NET SDK sample:
// https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/linq/how-to-compare-the-contents-of-two-folders-linq
class FileNameComparer : System.Collections.Generic.IEqualityComparer<FileInfo>
{
public FileNameComparer() { }
public bool Equals(FileInfo f1, FileInfo f2)
{
return (f1.Name == f2.Name);
}
public int GetHashCode(FileInfo fi)
{
if ((fi == null) || string.IsNullOrEmpty(fi.Name))
return 0;
return fi.Name.GetHashCode();
}
}
private List<string> logLines = new List<string>();
private void log(string logLine)
{
Console.WriteLine(logLine);
logLines.Add(logLine);
}
private void writeLogToFile(string logPath)
{
File.WriteAllLines(logPath, logLines);
}
public void CompareDirectoriesOfImages(string pathFirst, string pathSecond,
string pathCopyDiffs, double confidencePercent)
{
string[] foldersToCheck = new string[] { pathFirst, pathSecond, pathCopyDiffs };
foreach (string folder in foldersToCheck)
{
if (!Directory.Exists(folder))
{
log("ERROR: Required folder does *NOT* exist: " + folder);
return;
}
}
const string logFile = "zImageCompareLog.txt";
string logPath = Path.Combine(pathCopyDiffs, logFile);
if ((confidencePercent < 0.5) || (confidencePercent > 1.0))
{
log("ERROR: Confidence Percent not between 50-100% (0.50-1.00): " + confidencePercent.ToString("0.00"));
writeLogToFile(logPath);
return;
}
log("---------------------------------------------------");
log("Running with options:");
log(" Folder 1: " + pathFirst);
log(" Folder 2: " + pathSecond);
log(" Output Diff Folder: " + pathCopyDiffs);
log(" Confidence Percent: " + confidencePercent.ToString("0.0000"));
log("---------------------------------------------------");
DirectoryInfo dirFirst = new DirectoryInfo(pathFirst);
DirectoryInfo dirSecond = new DirectoryInfo(pathSecond);
const string file1DiffSuffix = "_1";
const string file2DiffSuffix = "_2";
const string extensionToCompare = ".png";
const string wildCardExpression = "*" + extensionToCompare;
IEnumerable<FileInfo> listFirst = dirFirst.GetFiles(wildCardExpression, SearchOption.TopDirectoryOnly);
IEnumerable<FileInfo> listSecond = dirSecond.GetFiles(wildCardExpression, SearchOption.TopDirectoryOnly);
FileNameComparer fileCompare = new FileNameComparer();
var listFirstOnly = (from file in listFirst
select file).Except(listSecond, fileCompare);
var listSecondOnly = (from file in listSecond
select file).Except(listFirst, fileCompare);
var listCommonFiles = listFirst.Intersect(listSecond, fileCompare);
foreach (var file in listFirstOnly)
{
FileInfo fi = file;
log("Found in First Folder Only, " + fi.Name);
}
foreach (var file in listSecondOnly)
{
FileInfo fi = file;
log("Found in Second Folder Only, " + fi.Name);
}
log("---------------------------------------------------");
log("Issue, Filename, Match Confidence, Total Difference"); // add a header row
foreach (var file in listCommonFiles)
{
FileInfo fi = file;
string fname = fi.Name;
string pngPath1 = Path.Combine(pathFirst, fname);
string pngPath2 = Path.Combine(pathSecond, fname);
System.Drawing.Bitmap b1 = new System.Drawing.Bitmap(pngPath1);
System.Drawing.Bitmap b2 = new System.Drawing.Bitmap(pngPath2);
ImageHistogram ih1 = new ImageHistogram(b1);
ImageHistogram ih2 = new ImageHistogram(b2);
if (ih1.Equals(ih2))
{
// log("Images are identical, " + fname);
}
else
{
int delta = ih1.CalculateDifference(ih2);
double confidence = ih1.CalculateConfidencePercent(ih2);
if (confidence > confidencePercent)
{
// Not an exact but falls above the confidence level set
log("Images are not identical-but probable match, " + fname + ", " +
confidence.ToString("0.0000") + ", " + delta);
}
else
{
// Below confidence level - log and copy to output folder
log("Images are not equal, " + fname + ", " +
confidence.ToString("0.0000") + ", " + delta);
string renamedFirst = fname.Replace(extensionToCompare, file1DiffSuffix + extensionToCompare);
string renamedSecond = fname.Replace(extensionToCompare, file2DiffSuffix + extensionToCompare);
string pngDiffPath1 = Path.Combine(pathCopyDiffs, renamedFirst);
string pngDiffPath2 = Path.Combine(pathCopyDiffs, renamedSecond);
try
{
// Console.WriteLine("Copying file: " + pngPath1 + " to " + pngDiffPath1);
// Console.WriteLine("Copying file: " + pngPath2 + " to " + pngDiffPath2);
File.Copy(pngPath1, pngDiffPath1, true);
File.Copy(pngPath2, pngDiffPath2, true);
}
catch (Exception ex)
{
log("File copy ERROR: " + ex.Message);
}
}
}
}
writeLogToFile(logPath);
}
}
static void Usage()
{
Console.WriteLine("HistogramTest [Folder 1] [Folder 2] [Diff Folder]");
}
static void Main(string[] args)
{
// defaults
string folderFirst = @"C:\Test-Folder-Of-Images-1";
string folderSecond = @"C:\Test-Folder-Of-Images-2";
string folderCopyDiffs = @"C:\Test-Diffs";
double confidencePercent = 0.99;
if (args.Length < 3)
{
Usage();
}
else
{
folderFirst = args[0];
folderSecond = args[1];
folderCopyDiffs = args[2];
// optional
if (args.Length > 3)
confidencePercent = double.Parse(args[3]);
}
FolderImageComparer fic = new FolderImageComparer();
fic.CompareDirectoriesOfImages(folderFirst, folderSecond,
folderCopyDiffs, confidencePercent);
}
}
}
/* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 */
using System;
using System.Drawing;
namespace HistogramTest
{
/// <summary>
/// Compares 2 images using normalized histogram equalization
/// compare method, see: http://en.wikipedia.org/wiki/Histogram_equalization
/// </summary>
class ImageHistogram
{
Bitmap image = null;
int[] mapR = null;
int[] mapG = null;
int[] mapB = null;
double[] mapNormalR = null;
double[] mapNormalG = null;
double[] mapNormalB = null;
protected Color[] histogramEqualizationTable = null;
public ImageHistogram(Bitmap bitmapIn)
{
image = bitmapIn;
calculateHistogram();
calculateHistogramEqualizationTable();
}
public override bool Equals(Object other)
{
ImageHistogram ih = other as ImageHistogram;
if (ih == null)
return false;
return (this.CalculateDifference(ih) == 0);
}
public override int GetHashCode()
{
if (image == null)
return 0;
return image.GetHashCode();
}
public double CalculateConfidencePercent(ImageHistogram other)
{
if (image == null)
return 0.0;
int difference = CalculateDifference(other);
int maxPossibleError = 256 * 256;
double confidence = 1.0 - ((double)difference / (double)maxPossibleError);
return confidence;
}
public int CalculateDifference(ImageHistogram other)
{
if ((this.image == null) || (other.image == null))
return 1000000;
byte valueR;
byte valueG;
byte valueB;
byte valueROther;
byte valueGOther;
byte valueBOther;
int totalDistance = 0;
for (int i = 0; i < 256; i++)
{
Color color = histogramEqualizationTable[i];
valueR = color.R;
valueG = color.G;
valueB = color.B;
Color colorOther = other.histogramEqualizationTable[i];
valueROther = colorOther.R;
valueGOther = colorOther.G;
valueBOther = colorOther.B;
int deltaR = Math.Abs((int)(valueR - valueROther));
int deltaG = Math.Abs((int)(valueG - valueGOther));
int deltaB = Math.Abs((int)(valueB - valueBOther));
totalDistance += deltaR;
totalDistance += deltaG;
totalDistance += deltaB;
}
return totalDistance;
}
private void calculateHistogram()
{
byte valueR;
byte valueG;
byte valueB;
byte valueAlpha;
mapR = new int[256];
mapG = new int[256];
mapB = new int[256];
for (int y = 0; y < image.Height; y++)
{
for (int x = 0; x < image.Width; x++)
{
Color pixelColor = image.GetPixel(x, y);
valueR = pixelColor.R;
valueG = pixelColor.G;
valueB = pixelColor.B;
valueAlpha = pixelColor.A;
// Implementation Specific Change to Standard Histogram Compare Here:
if (((valueR == 255) && (valueG == 255) && (valueB == 255)) ||
(valueAlpha == 0))
// TRICKY: Don't count whites or transparents (background colors in this case)
// NOTE: this will affect the correctness if an image really has white/transparent in it
// since these are ignored
continue;
mapR[valueR] = mapR[valueR] + 1;
mapG[valueG] = mapG[valueG] + 1;
mapB[valueB] = mapB[valueB] + 1;
}
}
// Normalize the histogram
double size = (double)(image.Height * image.Width);
mapNormalR = new double[256];
mapNormalG = new double[256];
mapNormalB = new double[256];
for (int i = 0; i < 256; i++)
{
mapNormalR[i] = mapR[i] / size;
mapNormalG[i] = mapG[i] / size;
mapNormalB[i] = mapB[i] / size;
}
}
private void calculateHistogramEqualizationTable()
{
histogramEqualizationTable = new Color[256];
double sumR = 0.0;
double sumG = 0.0;
double sumB = 0.0;
// Compute the CDF
for (int i = 0; i < 256; i++)
{
sumR += mapNormalR[i];
sumG += mapNormalG[i];
sumB += mapNormalB[i];
histogramEqualizationTable[i] =
Color.FromArgb((int)(sumR * 255.0 + 0.5),
(int)(sumG * 255.0 + 0.5),
(int)(sumB * 255.0 + 0.5));
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment