-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsimilarity.go
107 lines (92 loc) · 2.92 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
package images3
const (
// Euclidean similarity parameters.
// Cutoff value for color distance.
colorDiff = 50
// Cutoff coefficient for Euclidean distance (squared).
euclCoeff = 0.2
// Coefficient of sensitivity for Cb/Cr channels vs Y.
chanCoeff = 2
// Euclidean distance threshold (squared) for Y-channel.
thY = float32(iconSize*iconSize) * float32(colorDiff*colorDiff) * euclCoeff
// Euclidean distance threshold (squared) for Cb and Cr channels.
thCbCr = thY * chanCoeff
// Proportion similarity threshold 5%.
thProp = 0.05
)
// Similar returns similarity verdict based on Euclidean
// and proportion similarity.
func Similar(iconA, iconB IconT) bool {
if propSimilar(iconA, iconB) {
if eucSimilar(iconA, iconB) {
return true
}
}
return false
}
// 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 && m2 < thCbCr && m3 < thCbCr
}
// EucMetric returns Euclidean distances between 2 icons.
// These are 3 metrics corresponding to each color channel.
// The distances are squared to avoid square root calculations.
// Note that the channels are not RGB, but YCbCr, thus their
// importance for similarity might be not the same.
func EucMetric(iconA, iconB IconT) (m1, m2, m3 float32) {
numIconPixels := iconSize * iconSize
var cA, cB float32
for i := 0; i < numIconPixels; i++ {
// Channel 1.
cA = iconA.Pixels[i]
cB = iconB.Pixels[i]
m1 += (cA - cB) * (cA - cB)
// Channel 2.
cA = iconA.Pixels[i+numIconPixels]
cB = iconB.Pixels[i+numIconPixels]
m2 += (cA - cB) * (cA - cB)
// Channel 3.
cA = iconA.Pixels[i+2*numIconPixels]
cB = iconB.Pixels[i+2*numIconPixels]
m3 += (cA - cB) * (cA - cB)
}
return m1, m2, m3
}