Created
December 1, 2018 00:24
-
-
Save csmoore/ec2e3bbba2353f02e5f8febaab77711b to your computer and use it in GitHub Desktop.
Basic Normalized Histogram Equalization Compare
This file contains hidden or 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
/* 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); | |
} | |
} | |
} |
This file contains hidden or 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
/* 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