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
129
130
131
132
133
|
// Copyright 2011 The Graphics-Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package detect
import (
"image"
"math"
)
// Feature is a Haar-like feature.
type Feature struct {
Rect image.Rectangle
Weight float64
}
// Classifier is a set of features with a threshold.
type Classifier struct {
Feature []Feature
Threshold float64
Left float64
Right float64
}
// CascadeStage is a cascade of classifiers.
type CascadeStage struct {
Classifier []Classifier
Threshold float64
}
// Cascade is a degenerate tree of Haar-like classifiers.
type Cascade struct {
Stage []CascadeStage
Size image.Point
}
// Match returns true if the full image is classified as an object.
func (c *Cascade) Match(m image.Image) bool {
return c.classify(newWindow(m))
}
// Find returns a set of areas of m that match the feature cascade c.
func (c *Cascade) Find(m image.Image) []image.Rectangle {
// TODO(crawshaw): Consider de-duping strategies.
matches := []image.Rectangle{}
w := newWindow(m)
b := m.Bounds()
origScale := c.Size
for s := origScale; s.X < b.Dx() && s.Y < b.Dy(); s = s.Add(s.Div(10)) {
// translate region and classify
tx := image.Pt(s.X/10, 0)
ty := image.Pt(0, s.Y/10)
for r := image.Rect(0, 0, s.X, s.Y).Add(b.Min); r.In(b); r = r.Add(ty) {
for r1 := r; r1.In(b); r1 = r1.Add(tx) {
if c.classify(w.subWindow(r1)) {
matches = append(matches, r1)
}
}
}
}
return matches
}
type window struct {
mi *integral
miSq *integral
rect image.Rectangle
invArea float64
stdDev float64
}
func (w *window) init() {
w.invArea = 1 / float64(w.rect.Dx()*w.rect.Dy())
mean := float64(w.mi.sum(w.rect)) * w.invArea
vr := float64(w.miSq.sum(w.rect))*w.invArea - mean*mean
if vr < 0 {
vr = 1
}
w.stdDev = math.Sqrt(vr)
}
func newWindow(m image.Image) *window {
mi, miSq := newIntegrals(m)
res := &window{
mi: mi,
miSq: miSq,
rect: m.Bounds(),
}
res.init()
return res
}
func (w *window) subWindow(r image.Rectangle) *window {
res := &window{
mi: w.mi,
miSq: w.miSq,
rect: r,
}
res.init()
return res
}
func (c *Classifier) classify(w *window, pr *projector) float64 {
s := 0.0
for _, f := range c.Feature {
s += float64(w.mi.sum(pr.rect(f.Rect))) * f.Weight
}
s *= w.invArea // normalize to maintain scale invariance
if s < c.Threshold*w.stdDev {
return c.Left
}
return c.Right
}
func (s *CascadeStage) classify(w *window, pr *projector) bool {
sum := 0.0
for _, c := range s.Classifier {
sum += c.classify(w, pr)
}
return sum >= s.Threshold
}
func (c *Cascade) classify(w *window) bool {
pr := newProjector(w.rect, image.Rectangle{image.Pt(0, 0), c.Size})
for _, s := range c.Stage {
if !s.classify(w, pr) {
return false
}
}
return true
}
|