122 lines
3.2 KiB
Go
122 lines
3.2 KiB
Go
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
|
|
}
|