// Test the methods /* * z = [139 144 149 153 155 155 155 155; * 144 151 153 156 159 156 156 156; * 150 155 160 163 158 156 156 156; * 159 161 162 160 160 159 159 159; * 159 160 161 162 162 155 155 155; * 161 161 161 161 160 157 157 157; * 162 162 161 163 162 157 157 157; * 162 162 161 161 163 158 158 158]; * * octave:55> x = dct(z) * x = * 436.99 444.06 448.31 452.19 452.19 443 443 443 * -21.818 -14.969 -9.3908 -6.4728 -5.921 -1.7745 -1.7745 -1.7745 * -8.8097 -7.5031 -7.3446 -4.6522 -1.2737 -0.46194 -0.46194 -0.46194 * -2.4118 -3.7457 -3.9958 -3.0683 -1.4969 -1.7704 -1.7704 -1.7704 * 0.70711 -0.70711 -0.70711 -2.4749 0.35355 0.35355 0.35355 0.35355 * 1.365 0.22465 0.90791 0.57409 -1.7777 1.2224 1.2224 1.2224 * -0.94311 -1.4843 0.74614 0.77897 -2.1512 -0.19134 -0.19134 -0.19134 * -1.8165 -1.685 0.14561 2.9764 0.20231 -2.3922 -2.3922 -2.3922 * * octave:56> idct(x) * ans = * 139 144 149 153 155 155 155 155 * 144 151 153 156 159 156 156 156 * 150 155 160 163 158 156 156 156 * 159 161 162 160 160 159 159 159 * 159 160 161 162 162 155 155 155 * 161 161 161 161 160 157 157 157 * 162 162 161 163 162 157 157 157 * 162 162 161 161 163 158 158 158 */ public static void test() { double[][] vals = new double[][] { new double[] { 139.0, 144.0, 149.0, 153.0, 155.0, 155.0, 155.0, 155.0 }, new double[] { 144.0, 151.0, 153.0, 156.0, 159.0, 156.0, 156.0, 156.0 }, new double[] { 150.0, 155.0, 160.0, 163.0, 158.0, 156.0, 156.0, 156.0 }, new double[] { 159.0, 161.0, 162.0, 160.0, 160.0, 159.0, 159.0, 159.0 }, new double[] { 159.0, 160.0, 161.0, 162.0, 162.0, 155.0, 155.0, 155.0 }, new double[] { 161.0, 161.0, 161.0, 161.0, 160.0, 157.0, 157.0, 157.0 }, new double[] { 162.0, 162.0, 161.0, 163.0, 162.0, 157.0, 157.0, 157.0 }, new double[] { 162.0, 162.0, 161.0, 161.0, 163.0, 158.0, 158.0, 158.0 } }; DctMethods.PrintMatrix(vals); DctComirva dctCom = new DctComirva(vals.Length, vals[0].Length); // dct double[][] dctVals = dctCom.dct(vals); DctMethods.PrintMatrix(dctVals); // idct double[][] idctVals = dctCom.idct(dctVals); DctMethods.PrintMatrix(idctVals); }
// test 1 // Octave results: // format short g // z = [1 2 3; 4 5 6; 7 8 9; 10 11 12]; // //octave:5> dct(z) //ans = // 11 13 15 // -6.6913 -6.6913 -6.6913 // 0 0 0 // -0.47554 -0.47554 -0.47554 // //octave:6> dct2(z) //ans = // 22.517 -2.8284 0 // -11.59 0 0 // 0 0 0 // -0.82366 0 0 // #endregion private static void Test1(bool _2D = true) { var vals = new double[][] { new double[] { 1.0, 2.0, 3.0 }, new double[] { 4.0, 5.0, 6.0 }, new double[] { 7.0, 8.0, 9.0 }, new double[] { 10.0, 11.0, 12.0 } }; const double offset = 0.0; Console.WriteLine("vals: "); DctMethods.PrintMatrix(vals); Stopwatch stopWatch = Stopwatch.StartNew(); long startS = stopWatch.ElapsedTicks; double[][] result; if (_2D) { result = DctMethods.dct2(vals, offset); Console.WriteLine("dct2 result: "); } else { result = DctMethods.dct(vals, offset); Console.WriteLine("dct result: "); } long endS = stopWatch.ElapsedTicks; DctMethods.PrintMatrix(result); Console.WriteLine("time in ticks: " + (endS - startS)); //result = Filter(result, 1.0); //Console.WriteLine("dct2 filtered: "); //PrintMatrix(result); long startE = stopWatch.ElapsedTicks; double[][] ivals; if (_2D) { ivals = DctMethods.idct2(result, -offset); Console.WriteLine("idct2 result: "); } else { ivals = DctMethods.idct(result, -offset); Console.WriteLine("idct result: "); } long endE = stopWatch.ElapsedTicks; DctMethods.PrintMatrix(ivals); Console.WriteLine("Time in ticks: " + (endE - startE)); Assert.Equal(vals, ivals, new JaggedDoubleComparer(0.001)); // Assert.That(ivals, Is.EqualTo(vals).AsCollection.Within(0.001), "fail at [0]"); }
// test 2 // Octave results: // format short g /* * octave:43> * z = [139 144 149 153 155 155 155 155; * 144 151 153 156 159 156 156 156; * 150 155 160 163 158 156 156 156; * 159 161 162 160 160 159 159 159; * 159 160 161 162 162 155 155 155; * 161 161 161 161 160 157 157 157; * 162 162 161 163 162 157 157 157; * 162 162 161 161 163 158 158 158]; * * octave:43> g = dct2(z-128) * g = * 235.62 -1.0333 -12.081 -5.2029 2.125 -1.6724 -2.708 1.3238 * -22.59 -17.484 -6.2405 -3.1574 -2.8557 -0.069456 0.43417 -1.1856 * -10.949 -9.2624 -1.5758 1.5301 0.20295 -0.94186 -0.56694 -0.062924 * -7.0816 -1.9072 0.22479 1.4539 0.89625 -0.079874 -0.042291 0.33154 * -0.625 -0.83811 1.4699 1.5563 -0.125 -0.66099 0.60885 1.2752 * 1.7541 -0.20286 1.6205 -0.34244 -0.77554 1.4759 1.041 -0.99296 * -1.2825 -0.35995 -0.31694 -1.4601 -0.48996 1.7348 1.0758 -0.76135 * -2.5999 1.5519 -3.7628 -1.8448 1.8716 1.2139 -0.56788 -0.44564 * * octave:44> idct2(g)+128 * ans = * 139 144 149 153 155 155 155 155 * 144 151 153 156 159 156 156 156 * 150 155 160 163 158 156 156 156 * 159 161 162 160 160 159 159 159 * 159 160 161 162 162 155 155 155 * 161 161 161 161 160 157 157 157 * 162 162 161 163 162 157 157 157 * 162 162 161 161 163 158 158 158 * * octave:49> g = dct(z-128) * g = * * 74.953 82.024 86.267 90.156 90.156 80.964 80.964 80.964 * -21.818 -14.969 -9.3908 -6.4728 -5.921 -1.7745 -1.7745 -1.7745 * -8.8097 -7.5031 -7.3446 -4.6522 -1.2737 -0.46194 -0.46194 -0.46194 * -2.4118 -3.7457 -3.9958 -3.0683 -1.4969 -1.7704 -1.7704 -1.7704 * 0.70711 -0.70711 -0.70711 -2.4749 0.35355 0.35355 0.35355 0.35355 * 1.365 0.22465 0.90791 0.57409 -1.7777 1.2224 1.2224 1.2224 * -0.94311 -1.4843 0.74614 0.77897 -2.1512 -0.19134 -0.19134 -0.19134 * -1.8165 -1.685 0.14561 2.9764 0.20231 -2.3922 -2.3922 -2.3922 * * octave:50> idct(g)+128 * ans = * * 139 144 149 153 155 155 155 155 * 144 151 153 156 159 156 156 156 * 150 155 160 163 158 156 156 156 * 159 161 162 160 160 159 159 159 * 159 160 161 162 162 155 155 155 * 161 161 161 161 160 157 157 157 * 162 162 161 163 162 157 157 157 * 162 162 161 161 163 158 158 158 */ #endregion public static void Test2(bool _2D = true, bool random = false) { double[][] vals; if (random) { // Generate random integers between 0 and 255 const int N = 8; var generator = new Random(); vals = new double[N][]; int val; for (int x = 0; x < N; x++) { vals[x] = new double[N]; for (int y = 0; y < N; y++) { val = generator.Next(255); vals[x][y] = val; } } } else { vals = new double[][] { new double[] { 139.0, 144.0, 149.0, 153.0, 155.0, 155.0, 155.0, 155.0 }, new double[] { 144.0, 151.0, 153.0, 156.0, 159.0, 156.0, 156.0, 156.0 }, new double[] { 150.0, 155.0, 160.0, 163.0, 158.0, 156.0, 156.0, 156.0 }, new double[] { 159.0, 161.0, 162.0, 160.0, 160.0, 159.0, 159.0, 159.0 }, new double[] { 159.0, 160.0, 161.0, 162.0, 162.0, 155.0, 155.0, 155.0 }, new double[] { 161.0, 161.0, 161.0, 161.0, 160.0, 157.0, 157.0, 157.0 }, new double[] { 162.0, 162.0, 161.0, 163.0, 162.0, 157.0, 157.0, 157.0 }, new double[] { 162.0, 162.0, 161.0, 161.0, 163.0, 158.0, 158.0, 158.0 } }; } const double offset = -128.0; Console.WriteLine("vals: "); DctMethods.PrintMatrix(vals); Stopwatch stopWatch = Stopwatch.StartNew(); long startS = stopWatch.ElapsedTicks; double[][] result; if (_2D) { result = DctMethods.dct2(vals, offset); Console.WriteLine("dct2 result: "); } else { result = DctMethods.dct(vals, offset); Console.WriteLine("dct result: "); } long endS = stopWatch.ElapsedTicks; DctMethods.PrintMatrix(result); Console.WriteLine("Time in ticks: " + (endS - startS)); //result = Filter(result, 0.25); //result = CutLeastSignificantCoefficients(result); //Console.WriteLine("dct2 filtered: "); //PrintMatrix(result); long startE = stopWatch.ElapsedTicks; double[][] ivals; if (_2D) { ivals = DctMethods.idct2(result, -offset); Console.WriteLine("idct2 result: "); } else { ivals = DctMethods.idct(result, -offset); Console.WriteLine("idct result: "); } long endE = stopWatch.ElapsedTicks; DctMethods.PrintMatrix(ivals); Console.WriteLine("Time in ticks: " + (endE - startE)); Assert.That(ivals, Is.EqualTo(vals).AsCollection.Within(0.001), "fail at [0]"); }
public void TestDctMatrix() { #region Output from Octave /* * z = [139 144 149 153 155 155 155 155; * 144 151 153 156 159 156 156 156; * 150 155 160 163 158 156 156 156; * 159 161 162 160 160 159 159 159; * 159 160 161 162 162 155 155 155; * 161 161 161 161 160 157 157 157; * 162 162 161 163 162 157 157 157; * 162 162 161 161 163 158 158 158]; * * octave:55> x = dct(z) * x = * 436.99 444.06 448.31 452.19 452.19 443 443 443 * -21.818 -14.969 -9.3908 -6.4728 -5.921 -1.7745 -1.7745 -1.7745 * -8.8097 -7.5031 -7.3446 -4.6522 -1.2737 -0.46194 -0.46194 -0.46194 * -2.4118 -3.7457 -3.9958 -3.0683 -1.4969 -1.7704 -1.7704 -1.7704 * 0.70711 -0.70711 -0.70711 -2.4749 0.35355 0.35355 0.35355 0.35355 * 1.365 0.22465 0.90791 0.57409 -1.7777 1.2224 1.2224 1.2224 * -0.94311 -1.4843 0.74614 0.77897 -2.1512 -0.19134 -0.19134 -0.19134 * -1.8165 -1.685 0.14561 2.9764 0.20231 -2.3922 -2.3922 -2.3922 * * octave:56> idct(x) * ans = * 139 144 149 153 155 155 155 155 * 144 151 153 156 159 156 156 156 * 150 155 160 163 158 156 156 156 * 159 161 162 160 160 159 159 159 * 159 160 161 162 162 155 155 155 * 161 161 161 161 160 157 157 157 * 162 162 161 163 162 157 157 157 * 162 162 161 161 163 158 158 158 */ #endregion var vals = new double[][] { new double[] { 139.0, 144.0, 149.0, 153.0, 155.0, 155.0, 155.0, 155.0 }, new double[] { 144.0, 151.0, 153.0, 156.0, 159.0, 156.0, 156.0, 156.0 }, new double[] { 150.0, 155.0, 160.0, 163.0, 158.0, 156.0, 156.0, 156.0 }, new double[] { 159.0, 161.0, 162.0, 160.0, 160.0, 159.0, 159.0, 159.0 }, new double[] { 159.0, 160.0, 161.0, 162.0, 162.0, 155.0, 155.0, 155.0 }, new double[] { 161.0, 161.0, 161.0, 161.0, 160.0, 157.0, 157.0, 157.0 }, new double[] { 162.0, 162.0, 161.0, 163.0, 162.0, 157.0, 157.0, 157.0 }, new double[] { 162.0, 162.0, 161.0, 161.0, 163.0, 158.0, 158.0, 158.0 } }; DctMethods.PrintMatrix(vals); var dctCom = new DctMatrix(vals.Length, vals[0].Length); // dct double[][] dctVals = dctCom.Dct(vals); DctMethods.PrintMatrix(dctVals); // idct double[][] idctVals = dctCom.InverseDct(dctVals); DctMethods.PrintMatrix(idctVals); Assert.That(idctVals, Is.EqualTo(vals).AsCollection.Within(0.001), "fail at [0]"); }
/// <summary> /// Calcutate the perceptual hash of an image according to the algorithm given by Dr. Neal Krawetz /// on his blog: http://www.hackerfactor.com/blog/index.php?/archives/432-Looks-Like-It.html. /// </summary> /// <param name="image">The image to hash.</param> /// <returns>Returns a 'binary string' (aka bitstring) (like. 001010111011100010) which is easy to do a hamming distance on.</returns> private string GetHash(Bitmap img) { // 1. Reduce size. // Like Average Hash, pHash starts with a small image. // However, the image is larger than 8x8; 32x32 is a good size. // This is really done to simplify the DCT computation and not // because it is needed to reduce the high frequencies. var simg = CommonUtils.ImageUtils.Resize(img, size, size); // 2. Reduce color. // The image is reduced to a grayscale just to further simplify // the number of computations. var gimg = CommonUtils.ImageUtils.MakeGrayscale(simg); double[][] vals = new double[size][]; // for faster pixels access // http://csharpexamples.com/fast-image-processing-c/ unsafe { BitmapData bitmapData = gimg.LockBits(new Rectangle(0, 0, gimg.Width, gimg.Height), ImageLockMode.ReadWrite, gimg.PixelFormat); int bytesPerPixel = System.Drawing.Bitmap.GetPixelFormatSize(gimg.PixelFormat) / 8; int heightInPixels = bitmapData.Height; int widthInBytes = bitmapData.Width * bytesPerPixel; byte *PtrFirstPixel = (byte *)bitmapData.Scan0; for (int x = 0; x < widthInBytes; x = x + bytesPerPixel) { vals[x / 4] = new double[size]; for (int y = 0; y < heightInPixels; y++) { byte *currentLine = PtrFirstPixel + (y * bitmapData.Stride); // Console.WriteLine("x ({0}) y ({1})", x, y); // when the image is grayscale RGB has the same value vals[x / 4][y] = currentLine[x]; } } gimg.UnlockBits(bitmapData); simg.Dispose(); gimg.Dispose(); } // 3. Calcutate the DCT. // The DCT separates the image into a collection of frequencies // and scalars. While JPEG uses an 8x8 DCT, this algorithm uses // a 32x32 DCT. double[][] dctVals = DctMethods.dct2(vals); //double[][] dctVals = DctMethods.idct2(vals); // 4. Reduce the DCT. // This is the magic step. While the DCT is 32x32, just keep the // top-left 8x8. Those represent the lowest frequencies in the // picture. // 5 a) Calcutate the average value. // Like the Average Hash, compute the mean DCT value (using only // the 8x8 DCT low-frequency values and excluding the first term // since the DC coefficient can be significantly different from // the other values and will throw off the average). double total = 0; for (int x = 0; x < smallerSize; x++) { for (int y = 0; y < smallerSize; y++) { total += dctVals[x][y]; } } total -= dctVals[0][0]; // 5. b) Calcutate the average value. double avg = total / (double)((smallerSize * smallerSize) - 1); // 6. Further reduce the DCT. // This is the magic step. Set the 64 hash bits to 0 or 1 // depending on whether each of the 64 DCT values is above or // below the average value. The result doesn't tell us the // actual low frequencies; it just tells us the very-rough // relative scale of the frequencies to the mean. The result // will not vary as long as the overall structure of the image // remains the same; this can survive gamma and color histogram // adjustments without a problem. string hash = String.Empty; for (int x = 0; x < smallerSize; x++) { for (int y = 0; y < smallerSize; y++) { if (x != 0 && y != 0) { hash += (dctVals[x][y] > avg ? "1" : "0"); } } } return(hash); }
/// <summary> /// Computes the perceptual hash of an image according to the algorithm given by Dr. Neal Krawetz /// on his blog: http://www.hackerfactor.com/blog/index.php?/archives/432-Looks-Like-It.html. /// </summary> /// <param name="img">The image to hash.</param> /// <returns>Returns a 'binary string' (aka bitstring) (like. 001010111011100010) which is easy to do a hamming distance on.</returns> public string GetHash(Bitmap img) { #if DEBUG img.Save("ImagePHash 1-orig.png"); #endif // 1. Reduce size. // Like Average Hash, pHash starts with a small image. // However, the image is larger than 8x8; 32x32 is a good size. // This is really done to simplify the DCT computation and not // because it is needed to reduce the high frequencies. img = ImageUtils.Resize(img, size, size); #if DEBUG img.Save("ImagePHash 2-reduced.png"); #endif // 2. Reduce color. // The image is reduced to a grayscale just to further simplify // the number of computations. img = ImageUtils.MakeGrayscale(img); #if DEBUG img.Save("ImagePHash 3-grayscale.png"); #endif var vals = new double[size][]; for (int x = 0; x < img.Width; x++) { vals[x] = new double[size]; for (int y = 0; y < img.Height; y++) { // when the image is grayscale RGB has the same value vals[x][y] = img.GetPixel(x, y).B; } } #if DEBUG // create byte array to be able to save the image var grayscaleByteArray = new byte[size * size]; for (int x = 0; x < size; x++) { for (int y = 0; y < size; y++) { // add to byte array grayscaleByteArray[x + (y * size)] = Convert.ToByte(vals[x][y]); } } Image grayscale = ImageUtils.ByteArrayGrayscaleToImage(grayscaleByteArray, size, size); grayscale.Save("ImagePHash 4-grayscale-array.png"); #endif // 3. Compute the DCT. // The DCT separates the image into a collection of frequencies // and scalars. While JPEG uses an 8x8 DCT, this algorithm uses // a 32x32 DCT. double[][] dctVals = DctMethods.dct2(vals); #if DEBUG // create image array to be able to save DCT image // array to keep only the highest frequency items var dctValsOnlyHighFreq = new double[size][]; // Compressing Range By taking Log var dctLogVals = new double[dctVals.Length][]; for (int x = 0; x < size; x++) { dctValsOnlyHighFreq[x] = new double[dctVals[x].Length]; dctLogVals[x] = new double[dctVals[x].Length]; for (int y = 0; y < size; y++) { dctLogVals[x][y] = Math.Log(1 + Math.Abs((int)dctVals[x][y])); } } // Normalizing Array double dctMin = 0; double dctMax = 0; MathUtils.ComputeMinAndMax(dctLogVals, out dctMin, out dctMax); var dctPixels = new byte[size * size]; for (int x = 0; x < size; x++) { for (int y = 0; y < size; y++) { // Constrain Range between 0 and 255 int dctPixelVal = (int)(((float)(dctLogVals[x][y] - dctMin) / (float)(dctMax - dctMin)) * 255); dctPixels[x + (y * size)] = Convert.ToByte(dctPixelVal); } } Image dctImage = ImageUtils.ByteArrayGrayscaleToImage(dctPixels, size, size); dctImage.Save("ImagePHash 5-dct.png"); #endif // 4. Reduce the DCT. // This is the magic step. While the DCT is 32x32, just keep the // top-left 8x8. Those represent the lowest frequencies in the // picture. #if DEBUG // create image array to be able to save DCT image var dctPixelsSmallerSize = new byte[smallerSize * smallerSize]; #endif // 5 a) Compute the average value. // Like the Average Hash, compute the mean DCT value (using only // the 8x8 DCT low-frequency values and excluding the first term // since the DC coefficient can be significantly different from // the other values and will throw off the average). double total = 0; for (int x = 0; x < smallerSize; x++) { for (int y = 0; y < smallerSize; y++) { #if DEBUG dctValsOnlyHighFreq[x][y] = dctVals[x][y]; // store in new dct val array // convert to pixel values int dctPixelSmallerVal = (int)(((float)(dctLogVals[x][y] - dctMin) / (float)(dctMax - dctMin)) * 255); dctPixelsSmallerSize[x + (y * smallerSize)] = Convert.ToByte(dctPixelSmallerVal); #endif total += dctVals[x][y]; } } total -= dctVals[0][0]; #if DEBUG Image dctImageSmaller = ImageUtils.ByteArrayGrayscaleToImage(dctPixelsSmallerSize, smallerSize, smallerSize); dctImageSmaller.Save("ImagePHash 6-dct-smaller.png"); // Inverse DCT double[][] inverseDctVals = DctMethods.idct2(dctValsOnlyHighFreq); var idctPixels = new byte[size * size]; for (int x = 0; x < size; x++) { for (int y = 0; y < size; y++) { double inverseDctVal = inverseDctVals[x][y]; inverseDctVal = inverseDctVal < 0 ? 0 : inverseDctVal; inverseDctVal = inverseDctVal > 255 ? 255 : inverseDctVal; idctPixels[x + (y * size)] = Convert.ToByte(inverseDctVal); } } Image idctImage = ImageUtils.ByteArrayGrayscaleToImage(idctPixels, size, size); idctImage.Save("ImagePHash 7-idct.png"); #endif // 5. b) Compute the average value. double avg = total / (double)((smallerSize * smallerSize) - 1); // 6. Further reduce the DCT. // This is the magic step. Set the 64 hash bits to 0 or 1 // depending on whether each of the 64 DCT values is above or // below the average value. The result doesn't tell us the // actual low frequencies; it just tells us the very-rough // relative scale of the frequencies to the mean. The result // will not vary as long as the overall structure of the image // remains the same; this can survive gamma and color histogram // adjustments without a problem. string hash = ""; for (int x = 0; x < smallerSize; x++) { for (int y = 0; y < smallerSize; y++) { if (x != 0 && y != 0) { hash += (dctVals[x][y] > avg ? "1" : "0"); } } } return(hash); }