first commit
This commit is contained in:
commit
4bd5209110
2 changed files with 190 additions and 0 deletions
17
Compare.Test.csproj
Normal file
17
Compare.Test.csproj
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="CoenM.ImageSharp.ImageHash" Version="1.3.6" />
|
||||||
|
<PackageReference Include="Serilog" Version="4.3.0" />
|
||||||
|
<PackageReference Include="Serilog.Sinks.Console" Version="6.1.1" />
|
||||||
|
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
173
Program.cs
Normal file
173
Program.cs
Normal file
|
|
@ -0,0 +1,173 @@
|
||||||
|
namespace Compare.Test;
|
||||||
|
|
||||||
|
using CoenM.ImageHash;
|
||||||
|
using CoenM.ImageHash.HashAlgorithms;
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
|
public static class Program
|
||||||
|
{
|
||||||
|
public static void Main()
|
||||||
|
{
|
||||||
|
Log.Logger = new LoggerConfiguration()
|
||||||
|
.MinimumLevel.Debug()
|
||||||
|
.WriteTo.Console()
|
||||||
|
.WriteTo.File(
|
||||||
|
"logs/log-.txt",
|
||||||
|
rollingInterval: RollingInterval.Day,
|
||||||
|
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}")
|
||||||
|
.CreateLogger();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Log.Information("Application starting up...");
|
||||||
|
RunImageHashing();
|
||||||
|
Log.Information("Application has finished successfully.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Fatal(ex, "An unhandled exception occurred, terminating the application.");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Log.CloseAndFlush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RunImageHashing()
|
||||||
|
{
|
||||||
|
// --- Configuration folders ---
|
||||||
|
// Takes the folder directories of each contestant
|
||||||
|
// Each image should be .PNG and have "XX_" prefix e.g : 01_Image.png, 02_Image2.png, etc
|
||||||
|
string[] inputDirectories =
|
||||||
|
[
|
||||||
|
"/home/ibnufadhil/Documents/projects/StitcherResultTest/Test2.0/renjayarz",
|
||||||
|
"/home/ibnufadhil/Documents/projects/StitcherResultTest/Test2.0/reinardras",
|
||||||
|
"/home/ibnufadhil/Documents/projects/StitcherResultTest/Test2.0/meizar",
|
||||||
|
"/home/ibnufadhil/Documents/projects/StitcherResultTest/Test2.0/olymrifki",
|
||||||
|
"/home/ibnufadhil/Documents/projects/StitcherResultTest/Test2.0/fikribahru",
|
||||||
|
"/home/ibnufadhil/Documents/projects/StitcherResultTest/Test2.0/benscode",
|
||||||
|
"/home/ibnufadhil/Documents/projects/StitcherResultTest/Test2.0/dennisarfan"
|
||||||
|
];
|
||||||
|
|
||||||
|
string outputCsvPath = "hash_analysis_results.csv";
|
||||||
|
IImageHash hashAlgorithm = new PerceptualHash();
|
||||||
|
|
||||||
|
Log.Information("Generating hashes using {AlgorithmName}...", hashAlgorithm.GetType().Name);
|
||||||
|
|
||||||
|
var groupedImages = new Dictionary<string, List<string>>();
|
||||||
|
|
||||||
|
// Group images by prefix
|
||||||
|
foreach (string dir in inputDirectories)
|
||||||
|
{
|
||||||
|
if (!Directory.Exists(dir))
|
||||||
|
{
|
||||||
|
Log.Warning("Directory not found: {DirectoryPath}", dir);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (string file in Directory.GetFiles(dir, "*.png"))
|
||||||
|
{
|
||||||
|
string prefix = Path.GetFileName(file).Split('_')[0];
|
||||||
|
if (!groupedImages.ContainsKey(prefix))
|
||||||
|
groupedImages[prefix] = new List<string>();
|
||||||
|
|
||||||
|
groupedImages[prefix].Add(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var csvLines = new List<string> { "TestPrefix,FileName,ImageHash,SimilarityToRepresentative(%)" };
|
||||||
|
|
||||||
|
foreach (var group in groupedImages.OrderBy(g => g.Key))
|
||||||
|
{
|
||||||
|
string prefix = group.Key;
|
||||||
|
var imagePaths = group.Value;
|
||||||
|
|
||||||
|
if (imagePaths.Count < 2)
|
||||||
|
{
|
||||||
|
Log.Warning("Skipping {Prefix}: Not enough images to compare ({ImageCount}).", prefix, imagePaths.Count);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Information("=== Processing group {Prefix} ({ImageCount} images) ===", prefix, imagePaths.Count);
|
||||||
|
|
||||||
|
// Calculate hashes
|
||||||
|
var hashResults = new Dictionary<string, ulong>();
|
||||||
|
foreach (var path in imagePaths)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var stream = File.OpenRead(path);
|
||||||
|
ulong hash = hashAlgorithm.Hash(stream);
|
||||||
|
hashResults[path] = hash;
|
||||||
|
string relativePath = $"{Path.GetFileName(Path.GetDirectoryName(path))}/{Path.GetFileName(path)}";
|
||||||
|
|
||||||
|
Log.Debug("Hashed {ImagePath}: {ImageHash}", relativePath, hash);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "Error hashing {ImagePath}", path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hashResults.Count < 2)
|
||||||
|
{
|
||||||
|
Log.Warning("Skipping {Prefix}: Not enough valid images after hashing ({HashedCount}).", prefix, hashResults.Count);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Compute Representative Hash ---
|
||||||
|
ulong representativeHash = ComputeRepresentativeHash(hashResults.Values.ToList());
|
||||||
|
Log.Information("-> Representative Hash for {Prefix}: {RepresentativeHash}", prefix, representativeHash);
|
||||||
|
Log.Information("-----------------------------------------------------");
|
||||||
|
|
||||||
|
// --- Compute similarity to representative hash ---
|
||||||
|
foreach (var entry in hashResults)
|
||||||
|
{
|
||||||
|
double similarity = CompareHash.Similarity(entry.Value, representativeHash);
|
||||||
|
string fileName = $"{Path.GetFileName(Path.GetDirectoryName(entry.Key))}/{Path.GetFileName(entry.Key)}";
|
||||||
|
csvLines.Add($"{prefix},{fileName},{entry.Value},{similarity:F6}");
|
||||||
|
Log.Information("{FileName,-70} | Similarity: {Similarity:F2}%", fileName, similarity);
|
||||||
|
}
|
||||||
|
Log.Information("-----------------------------------------------------\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Write CSV output ---
|
||||||
|
try
|
||||||
|
{
|
||||||
|
File.WriteAllLines(outputCsvPath, csvLines);
|
||||||
|
Log.Information("Results successfully saved to: {CsvPath}", Path.GetFullPath(outputCsvPath));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "Failed to write CSV file to {CsvPath}", outputCsvPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Compute the bitwise representative hash (majority vote using consensus) ===
|
||||||
|
private static ulong ComputeRepresentativeHash(List<ulong> hashes)
|
||||||
|
{
|
||||||
|
int[] bitCounts = new int[64];
|
||||||
|
|
||||||
|
// Count how many 1s are in each bit
|
||||||
|
foreach (ulong hash in hashes)
|
||||||
|
{
|
||||||
|
for (int bit = 0; bit < 64; bit++)
|
||||||
|
{
|
||||||
|
if (((hash >> bit) & 1UL) == 1UL)
|
||||||
|
bitCounts[bit]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong result = 0UL;
|
||||||
|
int threshold = hashes.Count / 2;
|
||||||
|
|
||||||
|
// If more than half of the hashes have a 1 in a bit, set the corresponding bit in the result, otherwise 0
|
||||||
|
for (int bit = 0; bit < 64; bit++)
|
||||||
|
{
|
||||||
|
if (bitCounts[bit] > threshold)
|
||||||
|
result |= 1UL << bit;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue