2017-07-28 10:18:24 -07:00
|
|
|
// Copyright 2017 The goimagehash Authors. All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
package goimagehash
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"image"
|
2022-05-25 23:56:47 -07:00
|
|
|
"sync"
|
2017-07-28 10:18:24 -07:00
|
|
|
|
|
|
|
"github.com/corona10/goimagehash/etcs"
|
|
|
|
"github.com/corona10/goimagehash/transforms"
|
|
|
|
"github.com/nfnt/resize"
|
|
|
|
)
|
|
|
|
|
2017-07-31 07:24:04 -07:00
|
|
|
// AverageHash fuction returns a hash computation of average hash.
|
2017-07-28 10:18:24 -07:00
|
|
|
// Implementation follows
|
|
|
|
// http://www.hackerfactor.com/blog/index.php?/archives/432-Looks-Like-It.html
|
|
|
|
func AverageHash(img image.Image) (*ImageHash, error) {
|
|
|
|
if img == nil {
|
2022-09-07 23:20:33 -07:00
|
|
|
return nil, errors.New("image object can not be nil")
|
2017-07-28 10:18:24 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Create 64bits hash.
|
|
|
|
ahash := NewImageHash(0, AHash)
|
|
|
|
resized := resize.Resize(8, 8, img, resize.Bilinear)
|
|
|
|
pixels := transforms.Rgb2Gray(resized)
|
|
|
|
flattens := transforms.FlattenPixels(pixels, 8, 8)
|
|
|
|
avg := etcs.MeanOfPixels(flattens)
|
|
|
|
|
|
|
|
for idx, p := range flattens {
|
|
|
|
if p > avg {
|
2019-03-16 01:22:57 -07:00
|
|
|
ahash.leftShiftSet(len(flattens) - idx - 1)
|
2017-07-28 10:18:24 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return ahash, nil
|
|
|
|
}
|
|
|
|
|
2017-07-31 07:24:04 -07:00
|
|
|
// DifferenceHash function returns a hash computation of difference hash.
|
2017-07-28 10:18:24 -07:00
|
|
|
// Implementation follows
|
|
|
|
// http://www.hackerfactor.com/blog/?/archives/529-Kind-of-Like-That.html
|
|
|
|
func DifferenceHash(img image.Image) (*ImageHash, error) {
|
|
|
|
if img == nil {
|
2022-09-07 23:20:33 -07:00
|
|
|
return nil, errors.New("image object can not be nil")
|
2017-07-28 10:18:24 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
dhash := NewImageHash(0, DHash)
|
|
|
|
resized := resize.Resize(9, 8, img, resize.Bilinear)
|
|
|
|
pixels := transforms.Rgb2Gray(resized)
|
|
|
|
idx := 0
|
|
|
|
for i := 0; i < len(pixels); i++ {
|
|
|
|
for j := 0; j < len(pixels[i])-1; j++ {
|
|
|
|
if pixels[i][j] < pixels[i][j+1] {
|
2019-03-16 01:22:57 -07:00
|
|
|
dhash.leftShiftSet(64 - idx - 1)
|
2017-07-28 10:18:24 -07:00
|
|
|
}
|
|
|
|
idx++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return dhash, nil
|
|
|
|
}
|
|
|
|
|
2017-07-31 07:24:04 -07:00
|
|
|
// PerceptionHash function returns a hash computation of phash.
|
2017-07-28 10:18:24 -07:00
|
|
|
// Implementation follows
|
|
|
|
// http://www.hackerfactor.com/blog/index.php?/archives/432-Looks-Like-It.html
|
|
|
|
func PerceptionHash(img image.Image) (*ImageHash, error) {
|
|
|
|
if img == nil {
|
2022-09-07 23:20:33 -07:00
|
|
|
return nil, errors.New("image object can not be nil")
|
2017-07-28 10:18:24 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
phash := NewImageHash(0, PHash)
|
|
|
|
resized := resize.Resize(64, 64, img, resize.Bilinear)
|
2022-05-25 23:56:47 -07:00
|
|
|
|
|
|
|
pixels := pixelPool64.Get().(*[]float64)
|
|
|
|
|
|
|
|
transforms.Rgb2GrayFast(resized, pixels)
|
2022-09-07 23:20:33 -07:00
|
|
|
flattens := transforms.DCT2DFast64(pixels)
|
2022-05-25 23:56:47 -07:00
|
|
|
|
|
|
|
pixelPool64.Put(pixels)
|
|
|
|
|
2022-09-07 23:20:33 -07:00
|
|
|
median := etcs.MedianOfPixelsFast64(flattens[:])
|
2017-07-28 10:18:24 -07:00
|
|
|
|
|
|
|
for idx, p := range flattens {
|
|
|
|
if p > median {
|
2022-05-25 23:56:47 -07:00
|
|
|
phash.leftShiftSet(64 - idx - 1) // leftShiftSet
|
2017-07-28 10:18:24 -07:00
|
|
|
}
|
|
|
|
}
|
2022-05-25 23:56:47 -07:00
|
|
|
|
2017-07-28 10:18:24 -07:00
|
|
|
return phash, nil
|
|
|
|
}
|
2019-02-08 01:02:25 -08:00
|
|
|
|
2022-05-25 23:56:47 -07:00
|
|
|
var pixelPool64 = sync.Pool{
|
|
|
|
New: func() interface{} {
|
|
|
|
p := make([]float64, 4096)
|
|
|
|
return &p
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2019-03-18 07:53:09 -07:00
|
|
|
// ExtPerceptionHash function returns phash of which the size can be set larger than uint64
|
2019-02-08 01:02:25 -08:00
|
|
|
// Some variable name refer to https://github.com/JohannesBuchner/imagehash/blob/master/imagehash/__init__.py
|
2019-03-18 05:40:16 -07:00
|
|
|
// Support 64bits phash (width=8, height=8) and 256bits phash (width=16, height=16)
|
2019-03-18 21:52:44 -07:00
|
|
|
// Important: width * height should be the power of 2
|
2019-03-18 07:53:09 -07:00
|
|
|
func ExtPerceptionHash(img image.Image, width, height int) (*ExtImageHash, error) {
|
2019-03-18 21:52:44 -07:00
|
|
|
imgSize := width * height
|
2019-02-08 01:02:25 -08:00
|
|
|
if img == nil {
|
2022-09-07 23:20:33 -07:00
|
|
|
return nil, errors.New("image object can not be nil")
|
2019-02-08 01:02:25 -08:00
|
|
|
}
|
2019-03-18 21:52:44 -07:00
|
|
|
if imgSize <= 0 || imgSize&(imgSize-1) != 0 {
|
|
|
|
return nil, errors.New("width * height should be power of 2")
|
|
|
|
}
|
2019-03-16 07:15:32 -07:00
|
|
|
var phash []uint64
|
2019-02-08 01:02:25 -08:00
|
|
|
resized := resize.Resize(uint(imgSize), uint(imgSize), img, resize.Bilinear)
|
|
|
|
pixels := transforms.Rgb2Gray(resized)
|
|
|
|
dct := transforms.DCT2D(pixels, imgSize, imgSize)
|
2019-03-18 05:40:16 -07:00
|
|
|
flattens := transforms.FlattenPixels(dct, width, height)
|
2019-02-08 01:02:25 -08:00
|
|
|
median := etcs.MedianOfPixels(flattens)
|
|
|
|
|
|
|
|
lenOfUnit := 64
|
2019-03-16 07:15:32 -07:00
|
|
|
if imgSize%lenOfUnit == 0 {
|
|
|
|
phash = make([]uint64, imgSize/lenOfUnit)
|
|
|
|
} else {
|
|
|
|
phash = make([]uint64, imgSize/lenOfUnit+1)
|
|
|
|
}
|
2019-02-08 01:02:25 -08:00
|
|
|
for idx, p := range flattens {
|
2019-03-16 01:22:57 -07:00
|
|
|
indexOfArray := idx / lenOfUnit
|
|
|
|
indexOfBit := lenOfUnit - idx%lenOfUnit - 1
|
2019-02-08 01:02:25 -08:00
|
|
|
if p > median {
|
|
|
|
phash[indexOfArray] |= 1 << uint(indexOfBit)
|
|
|
|
}
|
|
|
|
}
|
2019-03-16 23:42:07 -07:00
|
|
|
return NewExtImageHash(phash, PHash, imgSize), nil
|
2019-02-08 01:02:25 -08:00
|
|
|
}
|
2019-03-15 08:26:38 -07:00
|
|
|
|
2019-03-18 07:53:09 -07:00
|
|
|
// ExtAverageHash function returns ahash of which the size can be set larger than uint64
|
2019-03-18 05:40:16 -07:00
|
|
|
// Support 64bits ahash (width=8, height=8) and 256bits ahash (width=16, height=16)
|
2019-03-18 07:53:09 -07:00
|
|
|
func ExtAverageHash(img image.Image, width, height int) (*ExtImageHash, error) {
|
2019-03-15 08:26:38 -07:00
|
|
|
if img == nil {
|
2022-09-07 23:20:33 -07:00
|
|
|
return nil, errors.New("image object can not be nil")
|
2019-03-15 08:26:38 -07:00
|
|
|
}
|
2019-03-16 07:15:32 -07:00
|
|
|
var ahash []uint64
|
2019-03-18 05:40:16 -07:00
|
|
|
imgSize := width * height
|
2019-03-15 08:26:38 -07:00
|
|
|
|
2019-03-18 05:40:16 -07:00
|
|
|
resized := resize.Resize(uint(width), uint(height), img, resize.Bilinear)
|
2019-03-15 08:26:38 -07:00
|
|
|
pixels := transforms.Rgb2Gray(resized)
|
2019-03-18 05:40:16 -07:00
|
|
|
flattens := transforms.FlattenPixels(pixels, width, height)
|
2019-03-15 08:26:38 -07:00
|
|
|
avg := etcs.MeanOfPixels(flattens)
|
|
|
|
|
|
|
|
lenOfUnit := 64
|
2019-03-16 07:15:32 -07:00
|
|
|
if imgSize%lenOfUnit == 0 {
|
|
|
|
ahash = make([]uint64, imgSize/lenOfUnit)
|
|
|
|
} else {
|
|
|
|
ahash = make([]uint64, imgSize/lenOfUnit+1)
|
|
|
|
}
|
2019-03-15 08:26:38 -07:00
|
|
|
for idx, p := range flattens {
|
2019-03-16 01:22:57 -07:00
|
|
|
indexOfArray := idx / lenOfUnit
|
|
|
|
indexOfBit := lenOfUnit - idx%lenOfUnit - 1
|
2019-03-15 08:26:38 -07:00
|
|
|
if p > avg {
|
|
|
|
ahash[indexOfArray] |= 1 << uint(indexOfBit)
|
|
|
|
}
|
|
|
|
}
|
2019-03-16 23:42:07 -07:00
|
|
|
return NewExtImageHash(ahash, AHash, imgSize), nil
|
2019-03-15 08:26:38 -07:00
|
|
|
}
|
2019-03-15 08:47:57 -07:00
|
|
|
|
2019-03-18 07:53:09 -07:00
|
|
|
// ExtDifferenceHash function returns dhash of which the size can be set larger than uint64
|
2019-03-18 05:40:16 -07:00
|
|
|
// Support 64bits dhash (width=8, height=8) and 256bits dhash (width=16, height=16)
|
2019-03-18 07:53:09 -07:00
|
|
|
func ExtDifferenceHash(img image.Image, width, height int) (*ExtImageHash, error) {
|
2019-03-15 08:47:57 -07:00
|
|
|
if img == nil {
|
2022-09-07 23:20:33 -07:00
|
|
|
return nil, errors.New("image object can not be nil")
|
2019-03-15 08:47:57 -07:00
|
|
|
}
|
|
|
|
|
2019-03-16 07:15:32 -07:00
|
|
|
var dhash []uint64
|
2019-03-18 05:40:16 -07:00
|
|
|
imgSize := width * height
|
2019-03-15 08:47:57 -07:00
|
|
|
|
2019-03-18 05:40:16 -07:00
|
|
|
resized := resize.Resize(uint(width)+1, uint(height), img, resize.Bilinear)
|
2019-03-15 08:47:57 -07:00
|
|
|
pixels := transforms.Rgb2Gray(resized)
|
|
|
|
|
|
|
|
lenOfUnit := 64
|
2019-03-16 07:15:32 -07:00
|
|
|
if imgSize%lenOfUnit == 0 {
|
|
|
|
dhash = make([]uint64, imgSize/lenOfUnit)
|
|
|
|
} else {
|
|
|
|
dhash = make([]uint64, imgSize/lenOfUnit+1)
|
|
|
|
}
|
2019-03-15 08:47:57 -07:00
|
|
|
idx := 0
|
|
|
|
for i := 0; i < len(pixels); i++ {
|
|
|
|
for j := 0; j < len(pixels[i])-1; j++ {
|
2019-03-16 01:22:57 -07:00
|
|
|
indexOfArray := idx / lenOfUnit
|
|
|
|
indexOfBit := lenOfUnit - idx%lenOfUnit - 1
|
2019-03-15 08:47:57 -07:00
|
|
|
if pixels[i][j] < pixels[i][j+1] {
|
|
|
|
dhash[indexOfArray] |= 1 << uint(indexOfBit)
|
|
|
|
}
|
|
|
|
idx++
|
|
|
|
}
|
|
|
|
}
|
2019-03-16 23:42:07 -07:00
|
|
|
return NewExtImageHash(dhash, DHash, imgSize), nil
|
2019-03-15 08:47:57 -07:00
|
|
|
}
|