-
Notifications
You must be signed in to change notification settings - Fork 0
/
kmeans.go
128 lines (107 loc) · 2.41 KB
/
kmeans.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
122
123
124
125
126
127
128
package main
import (
"image"
"image/color"
"math"
"math/rand"
"sync"
)
type cluster struct {
centroid color.Color
lCentroid color.Color
plCentroid color.Color
members []image.Point
}
func getClusters(k int, set image.Image) []*cluster {
// init clusters
clr := []*cluster{}
for i := 0; i < k; i++ {
c := new(cluster)
c.centroid = getRandColor()
c.lCentroid = color.RGBA{0, 0, 0, 0}
c.plCentroid = c.lCentroid
clr = append(clr, c)
}
// run k means algorithm
for {
partition(clr, set)
if converged(clr) {
return clr
}
}
}
func partition(clr []*cluster, set image.Image) {
var w sync.WaitGroup
w.Add(set.Bounds().Max.Y * set.Bounds().Max.X)
var mtx sync.Mutex
// assign all data points to the nearest cluster
for y := set.Bounds().Min.Y; y < set.Bounds().Max.Y; y++ {
go func(y int) {
for x := set.Bounds().Min.X; x < set.Bounds().Max.X; x++ {
i := indexNewCentroid(clr, set.At(x, y))
mtx.Lock()
clr[i].members = append(clr[i].members, image.Pt(x, y))
mtx.Unlock()
w.Done()
}
}(y)
}
w.Wait()
var getAverage = func(pts []image.Point) color.Color {
lPts := len(pts)
if lPts < 1 {
lPts = 1
}
var rSum, gSum, bSum uint32
for _, p := range pts {
r, g, b, _ := set.At(p.X, p.Y).RGBA()
rSum += r
gSum += g
bSum += b
}
i := uint8(rSum / uint32(lPts))
j := uint8(gSum / uint32(lPts))
k := uint8(bSum / uint32(lPts))
return color.RGBA{R: i, G: j, B: k, A: 100}
}
// update each centroid per cluster
for _, c := range clr {
c.plCentroid = c.lCentroid
c.lCentroid = c.centroid
c.centroid = getAverage(c.members)
}
}
const stop = 1000000000
func converged(set []*cluster) bool {
for _, c := range set {
if euclidDis(c.centroid, c.lCentroid) > stop {
return false
}
}
return true
}
func getRandColor() color.Color {
r := rand.Uint32()
return color.RGBA{R: uint8(r), G: uint8(r >> 8), B: uint8(r >> 16), A: 0}
}
func indexNewCentroid(clr []*cluster, p color.Color) (idx int) {
min := uint32(math.MaxUint32)
for i, c := range clr {
// find cluster with lowest color-distance
d := euclidDis(c.centroid, p)
if d < min {
min = d
idx = i
}
}
return
}
// see image/color/color.go
func euclidDis(a, b color.Color) uint32 {
ar, ag, ab, _ := a.RGBA()
br, bg, bb, _ := b.RGBA()
var sqDiff = func(x, y uint32) uint32 {
return ((x - y) * (x - y)) >> 2
}
return sqDiff(ar, br) + sqDiff(ag, bg) + sqDiff(ab, bb)
}