summaryrefslogtreecommitdiffstats
path: root/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/detect.go
blob: dde941cbead38ef8469e4f3fc62846f9fa2e7eee (plain)
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
}