Compare Image Data for Similarities - Console C#

This tutorial shows how to use various image processing commands to compare two images to determine how similar they are in a C# Windows Console application using the LEADTOOLS SDK.

Summary This tutorial covers how to use various image processing commands to compare images in a C# Windows Console application.
Completion Time 45 minutes
Visual Studio Project Download tutorial project (5 KB)
Platform C# Windows Console Application
IDE Visual Studio 2017, 2019
Development License Download LEADTOOLS
Try it in another language
  • C#: .NET Framework (Console)
  • Apple: macOS

Required Knowledge

Get familiar with the basic steps of creating a project by reviewing the Add References and Set a License tutorial, before working on the Compare Image Data for Similarities - Console C# tutorial.

Create the Project and Add LEADTOOLS References

Start with a copy of the project created in the Add References and Set a License tutorial. If you do not have that project, follow the steps in that tutorial to create it.

The references needed depend upon the purpose of the project. References can be added by one or the other of the following two methods (but not both).

If using NuGet references, this tutorial requires the following NuGet package:

If using local DLL references, the following DLLs are needed.

The DLLs are located at <INSTALL_DIR>\LEADTOOLS22\Bin\Dotnet4\x64:

For a complete list of which DLL files are required for your application, refer to Files to be Included in your Application.

Set the License File

The License unlocks the features needed for the project. It must be set before any toolkit function is called. For details, including tutorials for different platforms, refer to Setting a Runtime License.

There are two types of runtime licenses:


Adding LEADTOOLS NuGet and local references and setting a license are covered in more detail in the Add References and Set a License tutorial.

Add the Image Comparison Code

With the project created, the references added, and the license set, coding can begin.

This tutorial will compare two images by calculating the proportion of black pixels after using the CombineCommand with the OperationXor flag, the average intensity of all pixels using the StatisticsInformationCommand, the average hue and saturation values using the ColorSeparateCommand, and the average of the difference of magnitudes of each color frequency with the FastFourierTransformCommand.

In the Solution Explorer, open Program.cs. Add the following statements to the using block at the top of Program.cs:

using System; 
using System.IO; 
using Leadtools; 
using Leadtools.Codecs; 
using Leadtools.ImageProcessing; 
using Leadtools.ImageProcessing.Color; 
using Leadtools.ImageProcessing.Core; 
using Leadtools.ImageProcessing.Effects; 

Inside the Main method add two new string variables and set them equal to the two separate image file paths, as shown below. For the purposes of this tutorial you can download the test images here. For example, compare 1stRed.png with 2ndRed.png.

static void Main(string[] args) 
   string image1Filename = @"FILEPATH TO FIRST IMAGE"; 
   string image2Filename = @"FILEPATH TO SECOND IMAGE"; 
   // RasterImages are IDisposable so they must be in using blocks if not calling .Dispose() later 
   using (RasterImage image1 = LoadImage(image1Filename)) 
   using (RasterImage image2 = LoadImage(image2Filename)) 
      Console.WriteLine($"Image 1: {image1Filename}"); 
      Console.WriteLine($"Image 2: {image2Filename}\n"); 
      CompareXOR(image1, image2); 
      CompareIntensities(image1, image2); 
      CompareHSV(image1, image2); 
      CompareFourier(image1, image2); 
   Console.WriteLine("Press any key to exit..."); 

Add a new method to the Program class named LoadImage(string _filePath). Call the method twice inside using statements in the Main method, below the call to the SetLicense method, as shown above. Add the code below to the LoadImage method to load the given files as RasterImage objects.

static RasterImage LoadImage(string filename)  
   using (RasterCodecs codecs = new RasterCodecs())  
      return codecs.Load(filename);  

Add four new methods to the Program class named CompareXOR(RasterImage image1, RasterImage image2), CompareIntensities(RasterImage image1, RasterImage image2), CompareHSV(RasterImage image1, RasterImage image2), and CompareFourier(RasterImage image1, RasterImage image2). Call all four of these methods below the calls to the LoadImage method, inside the using statements, as shown above.

Add the below code for the CompareXOR method. In this method we will use the CombineCommand. This places one image onto another at a certain location and within a certain rectangle. The way the pixels of the underlying image are affected by the overlaying image depends on the flags we use. In this case we will use a LeadRect that is the same size as the bottom image to use the whole image. We will use the OperationXor flag which will perform a bitwise XOR operation onto each set of top and bottom pixels from each image. This should create black pixels where the two pixels in the same location in either images are the same, and white everywhere else. To calculate how much is the same between the two images, we will create a region out of only the black pixels and compare it to the sum of all pixels.

private static void CompareXOR(RasterImage image1, RasterImage image2) 
   using (RasterImage image2Copy = image2.Clone()) 
      // Combine the images with XOR 
      new CombineCommand() 
         DestinationRectangle = new LeadRect(0, 0, image1.Width, image1.Height), 
         Flags = CombineCommandFlags.OperationXor, 
         SourceImage = image1, 
         SourcePoint = new LeadPoint(0, 0) 
      // Calculate the percentage of black pixels (where XOR was identical) 
      image2Copy.AddColorToRegion(RasterColor.Black, RasterRegionCombineMode.Set); 
      double ratio = (double)image2Copy.CalculateRegionArea() / (image2Copy.Width * image2Copy.Height); 
      Console.WriteLine($"Individual pixels: {100.0 * ratio:0.00}% match"); 

Add the below code for the CompareIntensities method to create an instance of the StatisticsInformationCommand, which allows you to only include pixels of a certain range or color in the statistics population. We will use all pixels, from values 0-255 from the master channel. We will gather the images' mean values and compare the two, rescaling it to 0-1.00 instead of 0-255. This will give us the difference in average intensity or brightness.

We can also use this command to derive intensity of the difference of these images by passing it through an XOR CombineCommand like before. This will be calculated from the ratio of identical pixel values to differing pixel values.

private static void CompareIntensities(RasterImage image1, RasterImage image2) 
   using (RasterImage image1Gray = image1.Clone()) 
   using (RasterImage image2Gray = image1.Clone()) 
      // Convert both images to grayscale 
      GrayscaleCommand grayscale = new GrayscaleCommand() 
         BitsPerPixel = 8 
      // Compare the average overall intensities 
      StatisticsInformationCommand statistics = new StatisticsInformationCommand() 
         Channel = RasterColorChannel.Master, 
         End = 255, 
         Start = 0 
      double leftIntensity = statistics.Mean; 
      double rightIntensity = statistics.Mean; 
      double similarity = 1.0 - (Math.Abs(leftIntensity - rightIntensity) / 255.0); 
      Console.WriteLine($"Overall image intensities: {100.0 * similarity:0.00}% match"); 
      // Check the intensity of the difference 
      new CombineCommand() 
         DestinationRectangle = new LeadRect(0, 0, image1Gray.Width, image1Gray.Height), 
         Flags = CombineCommandFlags.OperationXor, 
         SourceImage = image1Gray, 
         SourcePoint = new LeadPoint(0, 0) 
      similarity = 1.0 - (statistics.Mean / 255.0); 
      Console.WriteLine($"Image difference intensity: {100.0 * similarity:0.00}% match"); 

Add the below code for the CompareHSV method to separate the images into Hue, Saturation, and Value channels instead of Red, Green, and Blue. This creates a DestinationImage object with three pages, one for each channel. We can then use the StatisticsInformationCommand again to find the average Hue and Saturation for both images by running it on each page of the separated image. The average Value is essentially the image's brightness which is not as useful. We can then compare the difference in average Hue and Saturation, again scaling it to 0-100 from 0-255.

private static void CompareHSV(RasterImage image1, RasterImage image2) 
   ColorSeparateCommand colorSeparate = new ColorSeparateCommand() 
      Type = ColorSeparateCommandType.Hsv 
   StatisticsInformationCommand statistics = new StatisticsInformationCommand() 
      Channel = RasterColorChannel.Master, 
      End = 255, 
      Start = 0 
   // Separate the first image, and calculate the average values 
   double image1Hue, image1Saturation; 
   using (RasterImage image1Separated = colorSeparate.DestinationImage) 
      image1Separated.Page = 1; 
      image1Hue = statistics.Mean; 
      image1Separated.Page = 2; 
      image1Saturation = statistics.Mean; 
   // Separate the second image, and calculate the average values 
   double image2Hue, image2Saturation; 
   using (RasterImage image2Separated = colorSeparate.DestinationImage) 
      image2Separated.Page = 1; 
      image2Hue = statistics.Mean; 
      image2Separated.Page = 2; 
      image2Saturation = statistics.Mean; 
   // Calculate the hue difference 
   // Note: 0 is next to 255, so need to compensate for this 
   double hueDiff = Math.Abs(image1Hue - image2Hue); 
   if (hueDiff > 127) 
      hueDiff = Math.Abs(hueDiff - 255); 
   // Log the hue similarity 
   double similarity = 1.0 - (hueDiff / 255.0); 
   Console.WriteLine($"Average hue values: {100.0 * similarity:0.00}% match"); 
   // Log the saturation similarity 
   similarity = 1.0 - (Math.Abs(image1Saturation - image2Saturation) / 255.0); 
   Console.WriteLine($"Average saturation values: {100.0 * similarity:0.00}% match"); 

Lastly, add the below code for the CompareHSV method to compare the frequency distribution of the two images using the FastFourierTransformCommand. The fast fourier transform, or FFT, works by converting the image from a spacial domain to a frequency domain which is useful for things like filtering out noise. We will use this to compare the magnitudes of each frequency. Once we run the command, we can then use the info object's Data property to get the Complex numbers associated with each frequency, calculate the magnitude for each frequency, and calculate the difference between the two averages.

private static void CompareFourier(RasterImage image1, RasterImage image2) 
   using (RasterImage image1Copy = image1.Clone()) 
   using (RasterImage image2Copy = image2.Clone()) 
      // Ensure the images are the same size 
      if (image1Copy.Width != image2Copy.Width || image1Copy.Height != image2Copy.Height) 
         // Could use the minimum size, but we'll just set it to a fixed 256 by 256 
         SizeCommand size = new SizeCommand() 
            Flags = RasterSizeFlags.Bicubic, 
            Height = 256, 
            Width = 256 
      // Setup the Fast Fourier Transform (FFT) data 
      // Note: We'll want the padding flag so this can handle sizes which aren't a power a two 
      FastFourierTransformCommandFlags fftFlags = FastFourierTransformCommandFlags.FastFourierTransform 
         | FastFourierTransformCommandFlags.Gray | FastFourierTransformCommandFlags.PadOptimally; 
      FourierTransformInformation image1Info = new FourierTransformInformation(image1Copy, fftFlags); 
      FourierTransformInformation image2Info = new FourierTransformInformation(image2Copy, fftFlags); 
      // Apply FFT to both images 
      FastFourierTransformCommand fft = new FastFourierTransformCommand() 
         Flags = fftFlags, 
         FourierTransformInformation = image1Info 
      fft.FourierTransformInformation = image2Info; 
      // Calculate the similarity of frequency based on the magnitudes of each component 
      double magnitudeTotal = 0; 
      double magnitudeTotalDiff = 0; 
      int length = image1Info.Data.Length; 
      Complex[] image1Data = image1Info.Data; 
      Complex[] image2Data = image2Info.Data; 
      for (int i = 0; i < length; i++) 
         // Calculate the magnitude for each entry 
         Complex image1Entry = image1Data[i]; 
         double image1Magnitude = Math.Sqrt(image1Entry.R * image1Entry.R + image1Entry.I * image1Entry.I); 
         Complex image2Entry = image2Data[i]; 
         double image2Magnitude = Math.Sqrt(image2Entry.R * image2Entry.R + image2Entry.I * image2Entry.I); 
         // Add to the totals 
         magnitudeTotal += image1Magnitude + image2Magnitude; 
         magnitudeTotalDiff += Math.Abs(image1Magnitude - image2Magnitude); 
      // Ensure we don't divide by zero 
      if (magnitudeTotal == 0) 
         magnitudeTotal = 1; 
      // Log the result 
      double similarity = (magnitudeTotal - magnitudeTotalDiff) / magnitudeTotal; 
      Console.WriteLine($"Frequency comparison: {100.0 * similarity:0.00}% match"); 

Run the Project

Run the project by pressing F5, or by selecting Debug -> Start Debugging.

If the steps were followed correctly, the application runs and performs multiple calculations on the two images outputting the similarity statistics to the console.


This tutorial showed how to use various Image Processing commands including the CombineCommand, GrayscaleCommand, StatisticsInformationCommand, ColorSeparateCommand, SizeCommand, and FastFourierTransformCommand, to obtain similarity statistics of two images.

See Also

Help Version 22.0.2021.10.18
Products | Support | Contact Us | Intellectual Property Notices
© 1991-2021 LEAD Technologies, Inc. All Rights Reserved.

Products | Support | Contact Us | Intellectual Property Notices
© 1991-2021 LEAD Technologies, Inc. All Rights Reserved.