/// <summary> /// 得到图像的散列 /// </summary> /// <param name="bitmap"></param> /// <param name="hashEnum"></param> /// <returns></returns> private static string GetImageHashes(Bitmap bitmap, HashEnum hashEnum) { // 获取hash类型 Func <Bitmap, string> hashMethod = null; switch (hashEnum) { case HashEnum.Mean: hashMethod = (imageName) => GetMeanHash(bitmap); break; case HashEnum.Difference: hashMethod = (imageName) => GetDifferenceHash(bitmap); break; case HashEnum.Perceptual: hashMethod = (imageName) => GetPerceptualHash(bitmap); break; default: break; } // 获取hash值 string hash = hashMethod(bitmap); return(hash); }
public static List <Tuple <string, string, double> > GetSimilarity( string folderPath, out int validImageCount, int precision, InterpolationMode interpolationMode, HashEnum hashEnum, double threshold) { Stopwatch watch = new Stopwatch(); if (threshold < 0 || threshold >= 1) { throw new ArgumentOutOfRangeException("Threshold should be [0,1);"); } if (!Directory.Exists(folderPath)) { throw new DirectoryNotFoundException("Directory not found."); } Debug.WriteLine($"Hash Algorithm: {hashEnum}\nPrecision: {precision}\n" + $"Interpolation Mode: {interpolationMode}\nThreshold: {threshold}"); watch.Restart(); // Set config currentPrecision = precision; currentInterpolationMode = interpolationMode; // Get hashes var imageHashPairs = GetImageHashes(folderPath, hashEnum).ToArray(); validImageCount = imageHashPairs.Length; if (validImageCount < 2) { return(null); } watch.Stop(); long hashTime = watch.ElapsedMilliseconds; watch.Restart(); var tuples = new List <Tuple <string, string, double> >(); for (int i = 0; i < imageHashPairs.Length; i++) { for (int j = imageHashPairs.Length - 1; j > i; j--) { double hammingDistance = GetHammingDistancePercent( imageHashPairs[i].Value, imageHashPairs[j].Value); if (hammingDistance > threshold) { var tuple = Tuple.Create(imageHashPairs[i].Key, imageHashPairs[j].Key, hammingDistance); tuples.Add(tuple); } } } ; watch.Stop(); long compareTime = watch.ElapsedMilliseconds; Debug.WriteLine($"GetHash: {hashTime}ms; CompareHash: {compareTime}ms"); // Sort by similarity return(tuples.OrderByDescending(u => u.Item3).ToList()); }
/// <summary> /// 获取图片对比数据 /// </summary> /// <param name="folderPath"></param> /// <param name="validImageCount"></param> /// <param name="precision"></param> /// <param name="interpolationMode"></param> /// <param name="hashEnum"></param> /// <param name="threshold"></param> /// <returns></returns> public static double GetSimilarity(Bitmap bitmap, Bitmap bitmap2, HashEnum hashEnum) { // Get hashes var imageHashPairs = GetImageHashes(bitmap, hashEnum); var imageHashPairs2 = GetImageHashes(bitmap2, hashEnum); //获取明汉差异并返回 double HammingDistance = GetHammingDistancePercent(imageHashPairs, imageHashPairs2); return(HammingDistance); }
private static KeyValuePair <string, string>[] GetImageHashes(string folderPath, HashEnum hashEnum) { // Get images DirectoryInfo di = new DirectoryInfo(folderPath); var imageNames = from imageName in Directory.EnumerateFiles(folderPath, "*.*", SearchOption.AllDirectories) where imageExtensions.Any(imageName.ToLower().EndsWith) select imageName; Debug.WriteLine($"Directory: {folderPath}\nImageCount: {imageNames.Count()}"); if (imageNames.Count() < 2) { return(null); } // Get hash algorithm Func <string, string> hashMethod = null; switch (hashEnum) { case HashEnum.Difference: hashMethod = (imageName) => GetDifferenceHash(imageName); break; case HashEnum.Mean: hashMethod = (imageName) => GetMeanHash(imageName); break; case HashEnum.Perceptual: hashMethod = (imageName) => GetPerceptualHash(imageName); break; default: break; } // Get hashes var imageHashPairs = new ConcurrentDictionary <string, string>(); Parallel.ForEach(imageNames, // TODO: Add performance limitation option //new ParallelOptions { MaxDegreeOfParallelism = Convert.ToInt32(Math.Ceiling((Environment.ProcessorCount * 0.5) * 1.0)) }, imageName => { string hash = hashMethod(imageName); if (!string.IsNullOrEmpty(hash)) { imageHashPairs.AddOrUpdate(imageName, hash, (k, v) => v = hash); } }); Debug.WriteLine($"ImageCount(Valid): {imageHashPairs.Count}"); return(imageHashPairs.ToArray()); }
private static ConcurrentDictionary <string, string> GetImageHashes(string folderPath, HashEnum hashEnum) { // Get images DirectoryInfo di = new DirectoryInfo(folderPath); var imageNames = from file in di.GetFiles() where imageExtensions.Contains(file.Extension) select file.Name; Debug.WriteLine($"Directory: {folderPath}\nImage count: {imageNames.Count()}"); // Get hash algorithm Func <string, string> hashMethod = null; switch (hashEnum) { case HashEnum.Mean: hashMethod = (imageName) => GetMeanHash(Path.Combine(folderPath, imageName)); break; case HashEnum.Difference: hashMethod = (imageName) => GetDifferenceHash(Path.Combine(folderPath, imageName)); break; case HashEnum.Perceptual: hashMethod = (imageName) => GetPerceptualHash(Path.Combine(folderPath, imageName)); break; default: break; } // Get hashes var imageHashPair = new ConcurrentDictionary <string, string>(); List <Task> taskList = new List <Task>(); foreach (string imageName in imageNames) { taskList.Add(Task.Run(() => { string hash = hashMethod(imageName); if (!string.IsNullOrEmpty(hash)) { imageHashPair.AddOrUpdate(imageName, hash, (k, v) => v = hash); } })); } Task.WaitAll(taskList.ToArray()); Debug.WriteLine($"Valid count: {imageHashPair.Count}"); return(imageHashPair); }
/// <summary> /// Generates a hash for the given plain text value and returns a /// base64-encoded result. Before the hash is computed, a random salt /// is generated and appended to the plain text. This salt is stored at /// the end of the hash value, so it can be used later for hash /// verification. /// </summary> /// <param name="plainText"> /// Plaintext value to be hashed. The function does not check whether /// this parameter is null. /// </param> /// <param name="hashAlgorithm"> /// Name of the hash algorithm. Allowed values are: "MD5", "SHA1", /// "SHA256", "SHA384", and "SHA512" (if any other value is specified /// MD5 hashing algorithm will be used). This value is case-insensitive. /// </param> /// <param name="saltBytes"> /// Salt bytes. This parameter can be null, in which case a random salt /// value will be generated. /// </param> /// <returns> /// Hash value formatted as a base64-encoded string. /// </returns> public static string ComputeHash(string plainText, HashEnum hashAlgorithm, byte[] saltBytes) { // If salt is not specified, generate it on the fly. if (saltBytes == null) { // Define min and max salt sizes. int minSaltSize = 4; int maxSaltSize = 8; // Generate a random number for the size of the salt. Random random = new Random(); int saltSize = random.Next(minSaltSize, maxSaltSize); // Allocate a byte array, which will hold the salt. saltBytes = new byte[saltSize]; // Initialize a random number generator. RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider(); // Fill the salt with cryptographically strong byte values. rng.GetNonZeroBytes(saltBytes); } // Convert plain text into a byte array. byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText); // Allocate array, which will hold plain text and salt. byte[] plainTextWithSaltBytes = new byte[plainTextBytes.Length + saltBytes.Length]; // Copy plain text bytes into resulting array. for (int i = 0; i < plainTextBytes.Length; i++) plainTextWithSaltBytes[i] = plainTextBytes[i]; // Append salt bytes to the resulting array. for (int i = 0; i < saltBytes.Length; i++) plainTextWithSaltBytes[plainTextBytes.Length + i] = saltBytes[i]; // Because we support multiple hashing algorithms, we must define // hash object as a common (abstract) base class. We will specify the // actual hashing algorithm class later during object creation. HashAlgorithm hash = GetHashType(hashAlgorithm); // Compute hash value of our plain text with appended salt. byte[] hashBytes = hash.ComputeHash(plainTextWithSaltBytes); // Create array which will hold hash and original salt bytes. byte[] hashWithSaltBytes = new byte[hashBytes.Length + saltBytes.Length]; // Copy hash bytes into resulting array. for (int i = 0; i < hashBytes.Length; i++) hashWithSaltBytes[i] = hashBytes[i]; // Append salt bytes to the result. for (int i = 0; i < saltBytes.Length; i++) hashWithSaltBytes[hashBytes.Length + i] = saltBytes[i]; // Convert result into a base64-encoded string. string hashValue = Convert.ToBase64String(hashWithSaltBytes); // Return the result. return hashValue; }
/// <summary> /// Compares a hash of the specified plain text value to a given hash /// value. Plain text is hashed with the same salt value as the original /// hash. /// </summary> /// <param name="plainText"> /// Plain text to be verified against the specified hash. The function /// does not check whether this parameter is null. /// </param> /// <param name="hashAlgorithm"> /// Name of the hash algorithm. Allowed values are: "MD5", "SHA1", /// "SHA256", "SHA384", and "SHA512" (if any other value is specified, /// MD5 hashing algorithm will be used). This value is case-insensitive. /// </param> /// <param name="hashValue"> /// Base64-encoded hash value produced by ComputeHash function. This value /// includes the original salt appended to it. /// </param> /// <returns> /// If computed hash mathes the specified hash the function the return /// value is true; otherwise, the function returns false. /// </returns> public static bool VerifyHash(string plainText, HashEnum hashAlgorithm, string hashValue) { // Convert base64-encoded hash value into a byte array. byte[] hashWithSaltBytes = Convert.FromBase64String(hashValue); // We must know size of hash (without salt). int hashSizeInBits, hashSizeInBytes; hashSizeInBits = GetHashBits(hashAlgorithm); // Convert size of hash from bits to bytes. hashSizeInBytes = hashSizeInBits / 8; // Make sure that the specified hash value is long enough. if (hashWithSaltBytes.Length < hashSizeInBytes) return false; // Allocate array to hold original salt bytes retrieved from hash. byte[] saltBytes = new byte[hashWithSaltBytes.Length - hashSizeInBytes]; // Copy salt from the end of the hash to the new array. for (int i = 0; i < saltBytes.Length; i++) saltBytes[i] = hashWithSaltBytes[hashSizeInBytes + i]; // Compute a new hash string. string expectedHashString = ComputeHash(plainText, hashAlgorithm, saltBytes); System.Text.UTF8Encoding enc = new System.Text.UTF8Encoding(); _salt = enc.GetString(saltBytes); // If the computed hash matches the specified hash, // the plain text value must be correct. return (hashValue == expectedHashString); }
private static int GetHashBits(HashEnum hashEnum) { // Because we support multiple hashing algorithms, we must define // hash object as a common (abstract) base class. We will specify the // actual hashing algorithm class later during object creation. int hashBits = 0; // Initialize appropriate hashing algorithm class. if (hashEnum == HashEnum.SHA1) { hashBits = 160; } else if (hashEnum == HashEnum.MD5) { hashBits = 128; } else if (hashEnum == HashEnum.SHA256) { hashBits = 256; } else if (hashEnum == HashEnum.SHA384) { hashBits = 384; } else if (hashEnum == HashEnum.SHA512) { hashBits = 512; } return hashBits; }
private static HashAlgorithm GetHashType(HashEnum hashEnum) { // Because we support multiple hashing algorithms, we must define // hash object as a common (abstract) base class. We will specify the // actual hashing algorithm class later during object creation. HashAlgorithm hash = null; // Initialize appropriate hashing algorithm class. if (hashEnum == HashEnum.SHA1) { hash = new SHA1Managed(); } else if (hashEnum == HashEnum.MD5) { hash = new MD5CryptoServiceProvider(); } else if (hashEnum == HashEnum.SHA256) { hash = new SHA256Managed(); } else if (hashEnum == HashEnum.SHA384) { hash = new SHA384Managed(); } else if (hashEnum == HashEnum.SHA512) { hash = new SHA512Managed(); } return hash; }