dct: Improve DCT1D to O(nlogn) algorithm (#29)

* hashcompute_test: Add benchmark

* dct: Improve DCT1D to O(nlogn) algorithm

AS-IS:
BenchmarkPerceptionHash-8  500  2893930 ns/op  456698 B/op  4455 allocs/op

TO-BE:
BenchmarkPerceptionHash-8  2000  890306 ns/op  456382 B/op  4455 allocs/op

reference: DCT type II, unscaled. Algorithm by Byeong Gi Lee, 1984.
This commit is contained in:
Dong-hee Na 2019-03-19 13:52:44 +09:00 committed by GitHub
parent c61f6c69fb
commit 14aa1e136f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 54 additions and 28 deletions

View File

@ -29,6 +29,8 @@ func main() {
hash2, _ = goimagehash.PerceptionHash(img2)
distance, _ = hash1.Distance(hash2)
fmt.Printf("Distance between images: %v\n", distance)
fmt.Println(hash1.ToString())
fmt.Println(hash2.ToString())
fmt.Println(hash1.Bits())
fmt.Println(hash2.Bits())
}

View File

@ -19,9 +19,9 @@ func main() {
foo := bufio.NewWriter(&b)
img1, _ := jpeg.Decode(file1)
img2, _ := jpeg.Decode(file2)
width, height := 15, 15
hash1, _ := goimagehash.ExtAverageHash(img1, width, height)
hash2, _ := goimagehash.ExtAverageHash(img2, width, height)
width, height := 16, 16
hash1, _ := goimagehash.ExtPerceptionHash(img1, width, height)
hash2, _ := goimagehash.ExtPerceptionHash(img2, width, height)
hash1024, _ := goimagehash.ExtAverageHash(img2, 32, 32)
distance, _ := hash1.Distance(hash2)
fmt.Printf("Distance between images: %v\n", distance)

View File

@ -87,12 +87,16 @@ func PerceptionHash(img image.Image) (*ImageHash, error) {
// ExtPerceptionHash function returns phash of which the size can be set larger than uint64
// Some variable name refer to https://github.com/JohannesBuchner/imagehash/blob/master/imagehash/__init__.py
// Support 64bits phash (width=8, height=8) and 256bits phash (width=16, height=16)
// Important: width * height should be the power of 2
func ExtPerceptionHash(img image.Image, width, height int) (*ExtImageHash, error) {
imgSize := width * height
if img == nil {
return nil, errors.New("Image object can not be nil")
}
if imgSize <= 0 || imgSize&(imgSize-1) != 0 {
return nil, errors.New("width * height should be power of 2")
}
var phash []uint64
imgSize := width * height
resized := resize.Resize(uint(imgSize), uint(imgSize), img, resize.Bilinear)
pixels := transforms.Rgb2Gray(resized)
dct := transforms.DCT2D(pixels, imgSize, imgSize)

View File

@ -192,10 +192,6 @@ func TestExtImageHashCompute(t *testing.T) {
{"_examples/sample1.jpg", "_examples/sample4.jpg", 16, 16, ExtPerceptionHash, "ExtPerceptionHash", 122},
{"_examples/sample2.jpg", "_examples/sample3.jpg", 16, 16, ExtPerceptionHash, "ExtPerceptionHash", 118},
{"_examples/sample2.jpg", "_examples/sample4.jpg", 16, 16, ExtPerceptionHash, "ExtPerceptionHash", 104},
{"_examples/sample1.jpg", "_examples/sample1.jpg", 17, 17, ExtPerceptionHash, "ExtPerceptionHash", 0},
{"_examples/sample2.jpg", "_examples/sample2.jpg", 17, 17, ExtPerceptionHash, "ExtPerceptionHash", 0},
{"_examples/sample3.jpg", "_examples/sample3.jpg", 17, 17, ExtPerceptionHash, "ExtPerceptionHash", 0},
{"_examples/sample4.jpg", "_examples/sample4.jpg", 17, 17, ExtPerceptionHash, "ExtPerceptionHash", 0},
{"_examples/sample1.jpg", "_examples/sample1.jpg", 8, 8, ExtDifferenceHash, "ExtDifferenceHash", 0},
{"_examples/sample2.jpg", "_examples/sample2.jpg", 8, 8, ExtDifferenceHash, "ExtDifferenceHash", 0},
{"_examples/sample3.jpg", "_examples/sample3.jpg", 8, 8, ExtDifferenceHash, "ExtDifferenceHash", 0},
@ -281,3 +277,21 @@ func BenchmarkExtImageHashDistanceDifferent(b *testing.B) {
}
}
}
func BenchmarkPerceptionHash(b *testing.B) {
file1, err := os.Open("_examples/sample3.jpg")
if err != nil {
b.Errorf("%s", err)
}
defer file1.Close()
img1, err := jpeg.Decode(file1)
if err != nil {
b.Errorf("%s", err)
}
for i := 0; i < b.N; i++ {
_, err := ExtPerceptionHash(img1, 8, 8)
if err != nil {
b.Errorf("%s", err)
}
}
}

View File

@ -10,25 +10,31 @@ import (
)
// DCT1D function returns result of DCT-II.
// Follows Matlab dct().
// Implementation reference:
// https://unix4lyfe.org/dct-1d/
// DCT type II, unscaled. Algorithm by Byeong Gi Lee, 1984.
func DCT1D(input []float64) []float64 {
n := len(input)
out := make([]float64, n)
for i := 0; i < n; i++ {
z := 0.0
for j := 0; j < n; j++ {
z += input[j] * math.Cos(math.Pi*(float64(j)+0.5)*float64(i)/float64(n))
}
if i == 0 {
z *= math.Sqrt(0.5)
}
out[i] = z * math.Sqrt(2.0/float64(n))
temp := make([]float64, len(input))
forwardTransform(input, temp, len(input))
return input
}
func forwardTransform(input, temp []float64, Len int) {
if Len == 1 {
return
}
return out
halfLen := Len / 2
for i := 0; i < halfLen; i++ {
x, y := input[i], input[Len-1-i]
temp[i] = x + y
temp[i+halfLen] = (x - y) / (math.Cos((float64(i)+0.5)*math.Pi/float64(Len)) * 2)
}
forwardTransform(temp, input, halfLen)
forwardTransform(temp[halfLen:], input, halfLen)
for i := 0; i < halfLen-1; i++ {
input[i*2+0] = temp[i]
input[i*2+1] = temp[i+halfLen] + temp[i+halfLen+1]
}
input[Len-2], input[Len-1] = temp[halfLen-1], temp[Len-1]
}
// DCT2D function returns a result of DCT2D by using the seperable property.

View File

@ -17,7 +17,7 @@ func TestDCT1D(t *testing.T) {
input []float64
output []float64
}{
{[]float64{1.0, 1.0, 1.0, 1.0}, []float64{2.0, 0, 0, 0}},
{[]float64{1.0, 1.0, 1.0, 1.0}, []float64{4.0, 0, 0, 0}},
} {
out := DCT1D(tt.input)
@ -50,10 +50,10 @@ func TestDCT2D(t *testing.T) {
{5.0, 6.0, 7.0, 8.0},
{9.0, 10.0, 11.0, 12.0},
{13.0, 14.0, 15.0, 16.0}},
[][]float64{{34.0, -4.46088499, 0.0, -0.31702534},
{-17.84353998, 0.0, 0.0, 0.0},
[][]float64{{136.0, -12.6172881195958, 0.0, -0.8966830583359305},
{-50.4691524783832, 0.0, 0.0, 0.0},
{0.0, 0.0, 0.0, 0.0},
{-1.26810134, 0.0, 0.0, 0.0}},
{-3.586732233343722, 0.0, 0.0, 0.0}},
4, 4},
} {
out := DCT2D(tt.input, tt.w, tt.h)