-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathsimilarity.go
121 lines (101 loc) · 3.17 KB
/
similarity.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
package images4
import "fmt"
// Similar returns similarity verdict based on Euclidean
// and proportion similarity.
func Similar(iconA, iconB IconT) bool {
if !propSimilar(iconA, iconB) {
return false
}
if !eucSimilar(iconA, iconB) {
return false
}
return true
}
// propSimilar gives a similarity verdict for image A and B based on
// their height and width. When proportions are similar, it returns
// true.
func propSimilar(iconA, iconB IconT) bool {
return PropMetric(iconA, iconB) < thProp
}
// PropMetric gives image proportion similarity metric for image A
// and B. The smaller the metric the more similar are images by their
// x-y size.
func PropMetric(iconA, iconB IconT) (m float64) {
// Filtering is based on rescaling a narrower side of images to 1,
// then cutting off at threshold of a longer image vs shorter image.
xA, yA := float64(iconA.ImgSize.X), float64(iconA.ImgSize.Y)
xB, yB := float64(iconB.ImgSize.X), float64(iconB.ImgSize.Y)
if xA <= yA { // x to 1.
yA = yA / xA
yB = yB / xB
if yA > yB {
m = (yA - yB) / yA
} else {
m = (yB - yA) / yB
}
} else { // y to 1.
xA = xA / yA
xB = xB / yB
if xA > xB {
m = (xA - xB) / xA
} else {
m = (xB - xA) / xB
}
}
return m
}
// eucSimilar gives a similarity verdict for image A and B based
// on Euclidean distance between pixel values of their icons.
// When the distance is small, the function returns true.
// iconA and iconB are generated with the Icon function.
// eucSimilar wraps EucMetric with well-tested thresholds.
func eucSimilar(iconA, iconB IconT) bool {
m1, m2, m3 := EucMetric(iconA, iconB)
return m1 < thY && // Luma as most sensitive.
m2 < thCbCr &&
m3 < thCbCr
}
// EucMetric returns Euclidean distances between 2 icons.
// These are 3 metrics corresponding to each color channel.
// Distances are squared, not to waste CPU on square root calculations.
// Note: color channels of icons are YCbCr (not RGB).
func EucMetric(iconA, iconB IconT) (m1, m2, m3 float64) {
var cA, cB uint16
for i := 0; i < numPix; i++ {
// Channel 1.
cA = iconA.Pixels[i]
cB = iconB.Pixels[i]
m1 += ((float64(cA) - float64(cB)) * one255th2 * (float64(cA) - float64(cB)))
// Channel 2.
cA = iconA.Pixels[i+numPix]
cB = iconB.Pixels[i+numPix]
m2 += ((float64(cA) - float64(cB)) * one255th2 * (float64(cA) - float64(cB)))
// Channel 3.
cA = iconA.Pixels[i+2*numPix]
cB = iconB.Pixels[i+2*numPix]
m3 += ((float64(cA) - float64(cB)) * one255th2 * (float64(cA) - float64(cB)))
}
return m1, m2, m3
}
// Print default thresholds for func Similar.
func DefaultThresholds() {
fmt.Printf("*** Default thresholds ***")
fmt.Printf("\nEuclidean distance thresholds (YCbCr): m1=%v, m2=%v, m3=%v", thY, thCbCr, thCbCr)
fmt.Printf("\nProportion threshold: m=%v\n\n", thProp)
}
// Similar90270 works like Similar, but also considers rotations of ±90°.
// Those are rotations users might reasonably often do.
func Similar90270(iconA, iconB IconT) bool {
if Similar(iconA, iconB) {
return true
}
// iconB rotated 90 degrees.
if Similar(iconA, Rotate90(iconB)) {
return true
}
// As if iconB was rotated 270 degrees.
if Similar(Rotate90(iconA), iconB) {
return true
}
return false
}