summaryrefslogtreecommitdiffstats
path: root/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect
diff options
context:
space:
mode:
Diffstat (limited to 'Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect')
-rw-r--r--Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/Makefile15
-rw-r--r--Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/detect.go133
-rw-r--r--Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/detect_test.go77
-rw-r--r--Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/doc.go31
-rw-r--r--Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/integral.go93
-rw-r--r--Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/integral_test.go156
-rw-r--r--Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/opencv_parser.go125
-rw-r--r--Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/opencv_parser_test.go75
-rw-r--r--Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/projector.go55
-rw-r--r--Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/projector_test.go49
10 files changed, 809 insertions, 0 deletions
diff --git a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/Makefile b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/Makefile
new file mode 100644
index 000000000..0b1c6cb3e
--- /dev/null
+++ b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/Makefile
@@ -0,0 +1,15 @@
+# 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.
+
+include $(GOROOT)/src/Make.inc
+
+TARG=code.google.com/p/graphics-go/graphics
+GOFILES=\
+ detect.go\
+ doc.go\
+ integral.go\
+ opencv_parser.go\
+ projector.go\
+
+include $(GOROOT)/src/Make.pkg
diff --git a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/detect.go b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/detect.go
new file mode 100644
index 000000000..dde941cbe
--- /dev/null
+++ b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/detect.go
@@ -0,0 +1,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
+}
diff --git a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/detect_test.go b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/detect_test.go
new file mode 100644
index 000000000..8a2df113d
--- /dev/null
+++ b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/detect_test.go
@@ -0,0 +1,77 @@
+// 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"
+ "image/draw"
+ "testing"
+)
+
+var (
+ c0 = Classifier{
+ Feature: []Feature{
+ Feature{Rect: image.Rect(0, 0, 3, 4), Weight: 4.0},
+ },
+ Threshold: 0.2,
+ Left: 0.8,
+ Right: 0.2,
+ }
+ c1 = Classifier{
+ Feature: []Feature{
+ Feature{Rect: image.Rect(3, 4, 4, 5), Weight: 4.0},
+ },
+ Threshold: 0.2,
+ Left: 0.8,
+ Right: 0.2,
+ }
+ c2 = Classifier{
+ Feature: []Feature{
+ Feature{Rect: image.Rect(0, 0, 1, 1), Weight: +4.0},
+ Feature{Rect: image.Rect(0, 0, 2, 2), Weight: -1.0},
+ },
+ Threshold: 0.2,
+ Left: 0.8,
+ Right: 0.2,
+ }
+)
+
+func TestClassifier(t *testing.T) {
+ m := image.NewGray(image.Rect(0, 0, 20, 20))
+ b := m.Bounds()
+ draw.Draw(m, image.Rect(0, 0, 20, 20), image.White, image.ZP, draw.Src)
+ draw.Draw(m, image.Rect(3, 4, 4, 5), image.Black, image.ZP, draw.Src)
+ w := newWindow(m)
+ pr := newProjector(b, b)
+
+ if res := c0.classify(w, pr); res != c0.Right {
+ t.Errorf("c0 got %f want %f", res, c0.Right)
+ }
+ if res := c1.classify(w, pr); res != c1.Left {
+ t.Errorf("c1 got %f want %f", res, c1.Left)
+ }
+ if res := c2.classify(w, pr); res != c1.Left {
+ t.Errorf("c2 got %f want %f", res, c1.Left)
+ }
+}
+
+func TestClassifierScale(t *testing.T) {
+ m := image.NewGray(image.Rect(0, 0, 50, 50))
+ b := m.Bounds()
+ draw.Draw(m, image.Rect(0, 0, 8, 10), image.White, b.Min, draw.Src)
+ draw.Draw(m, image.Rect(8, 10, 10, 13), image.Black, b.Min, draw.Src)
+ w := newWindow(m)
+ pr := newProjector(b, image.Rect(0, 0, 20, 20))
+
+ if res := c0.classify(w, pr); res != c0.Right {
+ t.Errorf("scaled c0 got %f want %f", res, c0.Right)
+ }
+ if res := c1.classify(w, pr); res != c1.Left {
+ t.Errorf("scaled c1 got %f want %f", res, c1.Left)
+ }
+ if res := c2.classify(w, pr); res != c1.Left {
+ t.Errorf("scaled c2 got %f want %f", res, c1.Left)
+ }
+}
diff --git a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/doc.go b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/doc.go
new file mode 100644
index 000000000..a0f4e94cd
--- /dev/null
+++ b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/doc.go
@@ -0,0 +1,31 @@
+// 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 implements an object detector cascade.
+
+The technique used is a degenerate tree of Haar-like classifiers, commonly
+used for face detection. It is described in
+
+ P. Viola, M. Jones.
+ Rapid Object Detection using a Boosted Cascade of Simple Features, 2001
+ IEEE Conference on Computer Vision and Pattern Recognition
+
+A Cascade can be constructed manually from a set of Classifiers in stages,
+or can be loaded from an XML file in the OpenCV format with
+
+ classifier, _, err := detect.ParseOpenCV(r)
+
+The classifier can be used to determine if a full image is detected as an
+object using Detect
+
+ if classifier.Match(m) {
+ // m is an image of a face.
+ }
+
+It is also possible to search an image for occurrences of an object
+
+ objs := classifier.Find(m)
+*/
+package detect
diff --git a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/integral.go b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/integral.go
new file mode 100644
index 000000000..814ced590
--- /dev/null
+++ b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/integral.go
@@ -0,0 +1,93 @@
+// 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"
+ "image/draw"
+)
+
+// integral is an image.Image-like structure that stores the cumulative
+// sum of the preceding pixels. This allows for O(1) summation of any
+// rectangular region within the image.
+type integral struct {
+ // pix holds the cumulative sum of the image's pixels. The pixel at
+ // (x, y) starts at pix[(y-rect.Min.Y)*stride + (x-rect.Min.X)*1].
+ pix []uint64
+ stride int
+ rect image.Rectangle
+}
+
+func (p *integral) at(x, y int) uint64 {
+ return p.pix[(y-p.rect.Min.Y)*p.stride+(x-p.rect.Min.X)]
+}
+
+func (p *integral) sum(b image.Rectangle) uint64 {
+ c := p.at(b.Max.X-1, b.Max.Y-1)
+ inY := b.Min.Y > p.rect.Min.Y
+ inX := b.Min.X > p.rect.Min.X
+ if inY && inX {
+ c += p.at(b.Min.X-1, b.Min.Y-1)
+ }
+ if inY {
+ c -= p.at(b.Max.X-1, b.Min.Y-1)
+ }
+ if inX {
+ c -= p.at(b.Min.X-1, b.Max.Y-1)
+ }
+ return c
+}
+
+func (m *integral) integrate() {
+ b := m.rect
+ for y := b.Min.Y; y < b.Max.Y; y++ {
+ for x := b.Min.X; x < b.Max.X; x++ {
+ c := uint64(0)
+ if y > b.Min.Y && x > b.Min.X {
+ c += m.at(x-1, y)
+ c += m.at(x, y-1)
+ c -= m.at(x-1, y-1)
+ } else if y > b.Min.Y {
+ c += m.at(b.Min.X, y-1)
+ } else if x > b.Min.X {
+ c += m.at(x-1, b.Min.Y)
+ }
+ m.pix[(y-m.rect.Min.Y)*m.stride+(x-m.rect.Min.X)] += c
+ }
+ }
+}
+
+// newIntegrals returns the integral and the squared integral.
+func newIntegrals(src image.Image) (*integral, *integral) {
+ b := src.Bounds()
+ srcg, ok := src.(*image.Gray)
+ if !ok {
+ srcg = image.NewGray(b)
+ draw.Draw(srcg, b, src, b.Min, draw.Src)
+ }
+
+ m := integral{
+ pix: make([]uint64, b.Max.Y*b.Max.X),
+ stride: b.Max.X,
+ rect: b,
+ }
+ mSq := integral{
+ pix: make([]uint64, b.Max.Y*b.Max.X),
+ stride: b.Max.X,
+ rect: b,
+ }
+ for y := b.Min.Y; y < b.Max.Y; y++ {
+ for x := b.Min.X; x < b.Max.X; x++ {
+ os := (y-b.Min.Y)*srcg.Stride + x - b.Min.X
+ om := (y-b.Min.Y)*m.stride + x - b.Min.X
+ c := uint64(srcg.Pix[os])
+ m.pix[om] = c
+ mSq.pix[om] = c * c
+ }
+ }
+ m.integrate()
+ mSq.integrate()
+ return &m, &mSq
+}
diff --git a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/integral_test.go b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/integral_test.go
new file mode 100644
index 000000000..0bc321a4d
--- /dev/null
+++ b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/integral_test.go
@@ -0,0 +1,156 @@
+// 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 (
+ "bytes"
+ "fmt"
+ "image"
+ "testing"
+)
+
+type integralTest struct {
+ x int
+ y int
+ src []uint8
+ res []uint8
+}
+
+var integralTests = []integralTest{
+ {
+ 1, 1,
+ []uint8{0x01},
+ []uint8{0x01},
+ },
+ {
+ 2, 2,
+ []uint8{
+ 0x01, 0x02,
+ 0x03, 0x04,
+ },
+ []uint8{
+ 0x01, 0x03,
+ 0x04, 0x0a,
+ },
+ },
+ {
+ 4, 4,
+ []uint8{
+ 0x02, 0x03, 0x00, 0x01,
+ 0x01, 0x02, 0x01, 0x05,
+ 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01,
+ },
+ []uint8{
+ 0x02, 0x05, 0x05, 0x06,
+ 0x03, 0x08, 0x09, 0x0f,
+ 0x04, 0x0a, 0x0c, 0x13,
+ 0x05, 0x0c, 0x0f, 0x17,
+ },
+ },
+}
+
+func sprintBox(box []byte, width, height int) string {
+ buf := bytes.NewBuffer(nil)
+ i := 0
+ for y := 0; y < height; y++ {
+ for x := 0; x < width; x++ {
+ fmt.Fprintf(buf, " 0x%02x,", box[i])
+ i++
+ }
+ buf.WriteByte('\n')
+ }
+ return buf.String()
+}
+
+func TestIntegral(t *testing.T) {
+ for i, oc := range integralTests {
+ src := &image.Gray{
+ Pix: oc.src,
+ Stride: oc.x,
+ Rect: image.Rect(0, 0, oc.x, oc.y),
+ }
+ dst, _ := newIntegrals(src)
+ res := make([]byte, len(dst.pix))
+ for i, p := range dst.pix {
+ res[i] = byte(p)
+ }
+
+ if !bytes.Equal(res, oc.res) {
+ got := sprintBox(res, oc.x, oc.y)
+ want := sprintBox(oc.res, oc.x, oc.y)
+ t.Errorf("%d: got\n%s\n want\n%s", i, got, want)
+ }
+ }
+}
+
+func TestIntegralSum(t *testing.T) {
+ src := &image.Gray{
+ Pix: []uint8{
+ 0x02, 0x03, 0x00, 0x01, 0x03,
+ 0x01, 0x02, 0x01, 0x05, 0x05,
+ 0x01, 0x01, 0x01, 0x01, 0x02,
+ 0x01, 0x01, 0x01, 0x01, 0x07,
+ 0x02, 0x01, 0x00, 0x03, 0x01,
+ },
+ Stride: 5,
+ Rect: image.Rect(0, 0, 5, 5),
+ }
+ img, _ := newIntegrals(src)
+
+ type sumTest struct {
+ rect image.Rectangle
+ sum uint64
+ }
+
+ var sumTests = []sumTest{
+ {image.Rect(0, 0, 1, 1), 2},
+ {image.Rect(0, 0, 2, 1), 5},
+ {image.Rect(0, 0, 1, 3), 4},
+ {image.Rect(1, 1, 3, 3), 5},
+ {image.Rect(2, 2, 4, 4), 4},
+ {image.Rect(4, 3, 5, 5), 8},
+ {image.Rect(2, 4, 3, 5), 0},
+ }
+
+ for _, st := range sumTests {
+ s := img.sum(st.rect)
+ if s != st.sum {
+ t.Errorf("%v: got %d want %d", st.rect, s, st.sum)
+ return
+ }
+ }
+}
+
+func TestIntegralSubImage(t *testing.T) {
+ m0 := &image.Gray{
+ Pix: []uint8{
+ 0x02, 0x03, 0x00, 0x01, 0x03,
+ 0x01, 0x02, 0x01, 0x05, 0x05,
+ 0x01, 0x04, 0x01, 0x01, 0x02,
+ 0x01, 0x02, 0x01, 0x01, 0x07,
+ 0x02, 0x01, 0x09, 0x03, 0x01,
+ },
+ Stride: 5,
+ Rect: image.Rect(0, 0, 5, 5),
+ }
+ b := image.Rect(1, 1, 4, 4)
+ m1 := m0.SubImage(b)
+ mi0, _ := newIntegrals(m0)
+ mi1, _ := newIntegrals(m1)
+
+ sum0 := mi0.sum(b)
+ sum1 := mi1.sum(b)
+ if sum0 != sum1 {
+ t.Errorf("b got %d want %d", sum0, sum1)
+ }
+
+ r0 := image.Rect(2, 2, 4, 4)
+ sum0 = mi0.sum(r0)
+ sum1 = mi1.sum(r0)
+ if sum0 != sum1 {
+ t.Errorf("r0 got %d want %d", sum1, sum0)
+ }
+}
diff --git a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/opencv_parser.go b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/opencv_parser.go
new file mode 100644
index 000000000..51ded1a1c
--- /dev/null
+++ b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/opencv_parser.go
@@ -0,0 +1,125 @@
+// 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 (
+ "bytes"
+ "encoding/xml"
+ "errors"
+ "fmt"
+ "image"
+ "io"
+ "io/ioutil"
+ "strconv"
+ "strings"
+)
+
+type xmlFeature struct {
+ Rects []string `xml:"grp>feature>rects>grp"`
+ Tilted int `xml:"grp>feature>tilted"`
+ Threshold float64 `xml:"grp>threshold"`
+ Left float64 `xml:"grp>left_val"`
+ Right float64 `xml:"grp>right_val"`
+}
+
+type xmlStages struct {
+ Trees []xmlFeature `xml:"trees>grp"`
+ Stage_threshold float64 `xml:"stage_threshold"`
+ Parent int `xml:"parent"`
+ Next int `xml:"next"`
+}
+
+type opencv_storage struct {
+ Any struct {
+ XMLName xml.Name
+ Type string `xml:"type_id,attr"`
+ Size string `xml:"size"`
+ Stages []xmlStages `xml:"stages>grp"`
+ } `xml:",any"`
+}
+
+func buildFeature(r string) (f Feature, err error) {
+ var x, y, w, h int
+ var weight float64
+ _, err = fmt.Sscanf(r, "%d %d %d %d %f", &x, &y, &w, &h, &weight)
+ if err != nil {
+ return
+ }
+ f.Rect = image.Rect(x, y, x+w, y+h)
+ f.Weight = weight
+ return
+}
+
+func buildCascade(s *opencv_storage) (c *Cascade, name string, err error) {
+ if s.Any.Type != "opencv-haar-classifier" {
+ err = fmt.Errorf("got %s want opencv-haar-classifier", s.Any.Type)
+ return
+ }
+ name = s.Any.XMLName.Local
+
+ c = &Cascade{}
+ sizes := strings.Split(s.Any.Size, " ")
+ w, err := strconv.Atoi(sizes[0])
+ if err != nil {
+ return nil, "", err
+ }
+ h, err := strconv.Atoi(sizes[1])
+ if err != nil {
+ return nil, "", err
+ }
+ c.Size = image.Pt(w, h)
+ c.Stage = []CascadeStage{}
+
+ for _, stage := range s.Any.Stages {
+ cs := CascadeStage{
+ Classifier: []Classifier{},
+ Threshold: stage.Stage_threshold,
+ }
+ for _, tree := range stage.Trees {
+ if tree.Tilted != 0 {
+ err = errors.New("Cascade does not support tilted features")
+ return
+ }
+
+ cls := Classifier{
+ Feature: []Feature{},
+ Threshold: tree.Threshold,
+ Left: tree.Left,
+ Right: tree.Right,
+ }
+
+ for _, rect := range tree.Rects {
+ f, err := buildFeature(rect)
+ if err != nil {
+ return nil, "", err
+ }
+ cls.Feature = append(cls.Feature, f)
+ }
+
+ cs.Classifier = append(cs.Classifier, cls)
+ }
+ c.Stage = append(c.Stage, cs)
+ }
+
+ return
+}
+
+// ParseOpenCV produces a detection Cascade from an OpenCV XML file.
+func ParseOpenCV(r io.Reader) (cascade *Cascade, name string, err error) {
+ // BUG(crawshaw): tag-based parsing doesn't seem to work with <_>
+ buf, err := ioutil.ReadAll(r)
+ if err != nil {
+ return
+ }
+ buf = bytes.Replace(buf, []byte("<_>"), []byte("<grp>"), -1)
+ buf = bytes.Replace(buf, []byte("</_>"), []byte("</grp>"), -1)
+
+ s := &opencv_storage{}
+ err = xml.Unmarshal(buf, s)
+ if err != nil {
+ return
+ }
+ return buildCascade(s)
+}
diff --git a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/opencv_parser_test.go b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/opencv_parser_test.go
new file mode 100644
index 000000000..343390499
--- /dev/null
+++ b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/opencv_parser_test.go
@@ -0,0 +1,75 @@
+// 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"
+ "os"
+ "reflect"
+ "testing"
+)
+
+var (
+ classifier0 = Classifier{
+ Feature: []Feature{
+ Feature{Rect: image.Rect(0, 0, 3, 4), Weight: -1},
+ Feature{Rect: image.Rect(3, 4, 5, 6), Weight: 3.1},
+ },
+ Threshold: 0.03,
+ Left: 0.01,
+ Right: 0.8,
+ }
+ classifier1 = Classifier{
+ Feature: []Feature{
+ Feature{Rect: image.Rect(3, 7, 17, 11), Weight: -3.2},
+ Feature{Rect: image.Rect(3, 9, 17, 11), Weight: 2.},
+ },
+ Threshold: 0.11,
+ Left: 0.03,
+ Right: 0.83,
+ }
+ classifier2 = Classifier{
+ Feature: []Feature{
+ Feature{Rect: image.Rect(1, 1, 3, 3), Weight: -1.},
+ Feature{Rect: image.Rect(3, 3, 5, 5), Weight: 2.5},
+ },
+ Threshold: 0.07,
+ Left: 0.2,
+ Right: 0.4,
+ }
+ cascade = Cascade{
+ Stage: []CascadeStage{
+ CascadeStage{
+ Classifier: []Classifier{classifier0, classifier1},
+ Threshold: 0.82,
+ },
+ CascadeStage{
+ Classifier: []Classifier{classifier2},
+ Threshold: 0.22,
+ },
+ },
+ Size: image.Pt(20, 20),
+ }
+)
+
+func TestParseOpenCV(t *testing.T) {
+ file, err := os.Open("../../testdata/opencv.xml")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer file.Close()
+
+ cascadeFile, name, err := ParseOpenCV(file)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if name != "name_of_cascade" {
+ t.Fatalf("name: got %s want name_of_cascade", name)
+ }
+
+ if !reflect.DeepEqual(cascade, *cascadeFile) {
+ t.Errorf("got\n %v want\n %v", *cascadeFile, cascade)
+ }
+}
diff --git a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/projector.go b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/projector.go
new file mode 100644
index 000000000..1ebd6db59
--- /dev/null
+++ b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/projector.go
@@ -0,0 +1,55 @@
+// 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"
+)
+
+// projector allows projecting from a source Rectangle onto a target Rectangle.
+type projector struct {
+ // rx, ry is the scaling factor.
+ rx, ry float64
+ // dx, dy is the translation factor.
+ dx, dy float64
+ // r is the clipping region of the target.
+ r image.Rectangle
+}
+
+// newProjector creates a Projector with source src and target dst.
+func newProjector(dst image.Rectangle, src image.Rectangle) *projector {
+ return &projector{
+ rx: float64(dst.Dx()) / float64(src.Dx()),
+ ry: float64(dst.Dy()) / float64(src.Dy()),
+ dx: float64(dst.Min.X - src.Min.X),
+ dy: float64(dst.Min.Y - src.Min.Y),
+ r: dst,
+ }
+}
+
+// pt projects p from the source rectangle onto the target rectangle.
+func (s *projector) pt(p image.Point) image.Point {
+ return image.Point{
+ clamp(s.rx*float64(p.X)+s.dx, s.r.Min.X, s.r.Max.X),
+ clamp(s.ry*float64(p.Y)+s.dy, s.r.Min.Y, s.r.Max.Y),
+ }
+}
+
+// rect projects r from the source rectangle onto the target rectangle.
+func (s *projector) rect(r image.Rectangle) image.Rectangle {
+ return image.Rectangle{s.pt(r.Min), s.pt(r.Max)}
+}
+
+// clamp rounds and clamps o to the integer range [x0, x1].
+func clamp(o float64, x0, x1 int) int {
+ x := int(o + 0.5)
+ if x < x0 {
+ return x0
+ }
+ if x > x1 {
+ return x1
+ }
+ return x
+}
diff --git a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/projector_test.go b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/projector_test.go
new file mode 100644
index 000000000..c6d0b0cd5
--- /dev/null
+++ b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/projector_test.go
@@ -0,0 +1,49 @@
+// 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"
+ "reflect"
+ "testing"
+)
+
+type projectorTest struct {
+ dst image.Rectangle
+ src image.Rectangle
+ pdst image.Rectangle
+ psrc image.Rectangle
+}
+
+var projectorTests = []projectorTest{
+ {
+ image.Rect(0, 0, 6, 6),
+ image.Rect(0, 0, 2, 2),
+ image.Rect(0, 0, 6, 6),
+ image.Rect(0, 0, 2, 2),
+ },
+ {
+ image.Rect(0, 0, 6, 6),
+ image.Rect(0, 0, 2, 2),
+ image.Rect(3, 3, 6, 6),
+ image.Rect(1, 1, 2, 2),
+ },
+ {
+ image.Rect(30, 30, 40, 40),
+ image.Rect(10, 10, 20, 20),
+ image.Rect(32, 33, 34, 37),
+ image.Rect(12, 13, 14, 17),
+ },
+}
+
+func TestProjector(t *testing.T) {
+ for i, tt := range projectorTests {
+ pr := newProjector(tt.dst, tt.src)
+ res := pr.rect(tt.psrc)
+ if !reflect.DeepEqual(res, tt.pdst) {
+ t.Errorf("%d: got %v want %v", i, res, tt.pdst)
+ }
+ }
+}