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  }