/// *********************************************************************************************************
/// © 2014 www.jakemdrew.com All rights reserved.
/// This source code is licensed under The GNU General Public License (GPLv3):
/// http://opensource.org/licenses/gpl-3.0.html
/// *********************************************************************************************************
/// *********************************************************************************************************
/// RgbProjector - Luminosity Histograms for Image Similarity.
///
/// The code in the this file was entirely adapted from the EyeOpen.SimilarImagesFinder project:
/// https://similarimagesfinder.codeplex.com/
///
/// Adapted By - Jake Drew
/// Version - 1.0, 06/23/2014
/// *********************************************************************************************************
using System;
using System.Collections.Generic;
using System.Linq;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
///
/// ***The code in the this file was entirely adapted from the EyeOpen.SimilarImagesFinder project:
/// https://similarimagesfinder.codeplex.com/
///
/// The following classes:
/// 1. Extact / create RGB projections from Bitmap images.
/// 2. Compare RGB projections for similarity.
///
namespace ImageClustering
{
///
/// Holds an RGB projection extracted from a Bitmap image.
///
public class RgbProjection
{
public double[] horizontalProjection;
public double[] verticalProjection;
public RgbProjection(double[] horizontal, double[] vertical)
{
horizontalProjection = horizontal;
verticalProjection = vertical;
}
}
///
/// Creates RGB projections from an image and compares projections for similarity.
///
public class RgbProjector
{
///
/// Extract the RBG projection from a Bitmap Image.
///
/// The image to process.
/// Return horizontal RGB projection in value [0] and vertical RGB projection in value [1].
public static RgbProjection GetRgbProjections(Bitmap bitmap)
{
var width = bitmap.Width - 1;
var height = bitmap.Width - 1;
var horizontalProjection = new double[width];
var verticalProjection = new double[height];
var bitmapData1 = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
unsafe
{
var imagePointer1 = (byte*)bitmapData1.Scan0;
for (var y = 0; y < height; y++)
{
for (var x = 0; x < width; x++)
{
var blu = imagePointer1[0];
var green = imagePointer1[1];
var red = imagePointer1[2];
int luminosity = (byte)(((0.2126 * red) + (0.7152 * green)) + (0.0722 * blu));
horizontalProjection[x] += luminosity;
verticalProjection[y] += luminosity;
imagePointer1 += 4;
}
imagePointer1 += bitmapData1.Stride - (bitmapData1.Width * 4);
}
}
MaximizeScale(ref horizontalProjection, height);
MaximizeScale(ref verticalProjection, width);
bitmap.UnlockBits(bitmapData1);
return new RgbProjection(horizontalProjection, verticalProjection);
}
///
/// Optimize the range of values.
///
/// The array to process.
/// The max value for the elements.
private static void MaximizeScale(ref double[] projection, double max)
{
var minValue = double.MaxValue;
var maxValue = double.MinValue;
for (var i = 0; i < projection.Length; i++)
{
if (projection[i] > 0)
{
projection[i] = projection[i] / max;
}
if (projection[i] < minValue)
{
minValue = projection[i];
}
if (projection[i] > maxValue)
{
maxValue = projection[i];
}
}
if (maxValue == 0)
{
return;
}
for (var i = 0; i < projection.Length; i++)
{
if (maxValue == 255)
{
projection[i] = 1;
}
else
{
projection[i] = (projection[i] - minValue) / (maxValue - minValue);
}
}
}
///
/// Calculate the similarity between two RGB projections, horizontal and vertical.
///
/// The RGB projection to compare with.
/// Return the max similarity value betweem horizontal and vertical RGB projections.
public static double CalculateSimilarity(RgbProjection source, RgbProjection compare)
{
var horizontalSimilarity = CalculateProjectionSimilarity(source.horizontalProjection, compare.horizontalProjection);
var verticalSimilarity = CalculateProjectionSimilarity(source.verticalProjection, compare.verticalProjection);
return (horizontalSimilarity + verticalSimilarity) / 2;
}
///
/// Calculate the similarity to another RGB projection.
///
/// The source RGB projection.
/// The RGB projection to compare with.
/// Return a value from 0 to 1 that is the similarity.
private static double CalculateProjectionSimilarity(double[] source, double[] compare)
{
if (source.Length != compare.Length)
{
throw new ArgumentException();
}
var frequencies = new Dictionary();
////Calculate frequencies
for (var i = 0; i < source.Length; i++)
{
var difference = source[i] - compare[i];
difference = Math.Round(difference, 2);
difference = Math.Abs(difference);
if (frequencies.ContainsKey(difference))
{
frequencies[difference] = frequencies[difference] + 1;
}
else
{
frequencies.Add(difference, 1);
}
}
var deviation = frequencies.Sum(value => (value.Key * value.Value));
////Calculate "weighted mean"
////http://en.wikipedia.org/wiki/Weighted_mean
deviation /= source.Length;
////Maximize scale
deviation = (0.5 - deviation) * 2;
return deviation;
}
///
/// Resize an image in high resolution
///
/// The image to resize.
/// The expected width.
/// the expected height.
///
public static Bitmap ResizeBitmap(Bitmap bitmap, int width, int height)
{
var result = new Bitmap(width, height);
using (var graphic = Graphics.FromImage((System.Drawing.Image)result))
{
graphic.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphic.DrawImage(bitmap, 0, 0, width - 1, height - 1);
}
return result;
}
}
}