gitee.com/h79/goutils@v1.22.10/common/images/phash.go (about) 1 package images 2 3 import ( 4 "bufio" 5 "github.com/disintegration/imaging" 6 "image" 7 "io" 8 "math" 9 "os" 10 ) 11 12 // PHash 感知哈希算法, 值越小相识度越高,10之内可以简单判断这两张图片内容一致 13 type PHash struct { 14 size int 15 smallerSize int 16 c []float64 17 hash string 18 } 19 20 func NewPHash() *PHash { 21 return NewPHashBy(32, 8) 22 } 23 24 func NewPHashBy(size int, smallerSize int) *PHash { 25 hash := &PHash{ 26 size: size, 27 smallerSize: smallerSize, 28 } 29 hash.initCoefficients() 30 return hash 31 } 32 33 func (hash *PHash) initCoefficients() { 34 hash.c = make([]float64, hash.size) 35 for i := 1; i < hash.size; i++ { 36 hash.c[i] = 1 37 } 38 hash.c[0] = 1 / math.Sqrt(2.0) 39 } 40 41 func (hash *PHash) OpenFile(filename string) error { 42 file, err := os.Open(filename) 43 if err != nil { 44 return err 45 } 46 return hash.Open(bufio.NewReader(file)) 47 } 48 49 func (hash *PHash) Open(reader io.Reader) error { 50 51 img, _, err := image.Decode(reader) 52 if err != nil { 53 return err 54 } 55 return hash.OpenImage(img) 56 } 57 58 func (hash *PHash) OpenImage(img image.Image) error { 59 60 /* 61 * 1. Reduce size. Like Average Hash, pHash starts with a small image. 62 * However, the image is larger than 8x8; 32x32 is a good size. This is 63 * really done to simplify the DCT computation and not because it is 64 * needed to reduce the high frequencies. 65 */ 66 img = imaging.Resize(img, hash.size, hash.size, imaging.Linear) 67 68 /* 69 * 2. Reduce color. The image is reduced to a grayscale just to further 70 * simplify the number of computations. 71 */ 72 img = imaging.Grayscale(img) 73 74 var col = make([][]float64, hash.size) 75 for x := 0; x < img.Bounds().Dx(); x++ { 76 col[x] = make([]float64, hash.size) 77 for y := 0; y < img.Bounds().Dy(); y++ { 78 _, _, b, _ := img.At(x, y).RGBA() 79 col[x][y] = float64(b >> 8) 80 } 81 } 82 83 /* 84 * 3. Compute the DCT. The DCT separates the image into a collection of 85 * frequencies and scalars. While JPEG uses an 8x8 DCT, this algorithm 86 * uses a 32x32 DCT. 87 */ 88 dct := DCT2D(col, hash.size, hash.size) 89 90 /* 91 * 4. Reduce the DCT. This is the magic step. While the DCT is 32x32, 92 * just keep the top-left 8x8. Those represent the lowest frequencies in 93 * the picture. 94 */ 95 /* 96 * 5. Compute the average value. Like the Average Hash, compute the mean 97 * DCT value (using only the 8x8 DCT low-frequency values and excluding 98 * the first term since the DC coefficient can be significantly 99 * different from the other values and will throw off the average). 100 */ 101 total := float64(0) 102 for x := 0; x < hash.smallerSize; x++ { 103 for y := 0; y < hash.smallerSize; y++ { 104 total += dct[x][y] 105 } 106 } 107 total -= dct[0][0] 108 109 avg := total / float64(hash.smallerSize*hash.smallerSize-1) 110 111 /* 112 * 6. Further reduce the DCT. This is the magic step. Set the 64 hash 113 * bits to 0 or 1 depending on whether each of the 64 DCT values is 114 * above or below the average value. The result doesn't tell us the 115 * actual low frequencies; it just tells us the very-rough relative 116 * scale of the frequencies to the mean. The result will not vary as 117 * long as the overall structure of the image remains the same; this can 118 * survive gamma and color histogram adjustments without a problem. 119 */ 120 hash.hash = "" 121 for x := 0; x < hash.smallerSize; x++ { 122 for y := 0; y < hash.smallerSize; y++ { 123 if x != 0 && y != 0 { 124 if dct[x][y] > avg { 125 hash.hash += "1" 126 } else { 127 hash.hash += "0" 128 } 129 } 130 } 131 } 132 return nil 133 } 134 135 // Returns a 'binary string' (like. 001010111011100010) which is easy to do 136 // a hamming distance on. 137 func (hash *PHash) String() string { 138 return hash.hash 139 } 140 141 // Match 值越小相识度越高,10之内可以简单判断这两张图片内容一致 142 func (hash *PHash) Match(dest *PHash) int { 143 s1 := hash.String() 144 s2 := dest.String() 145 counter := 0 146 for k := 0; k < len(s1); k++ { 147 if s1[k] != s2[k] { 148 counter++ 149 } 150 } 151 return counter 152 }