detect.go 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. // Copyright 2011 The Graphics-Go Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. package detect
  5. import (
  6. "image"
  7. "math"
  8. )
  9. // Feature is a Haar-like feature.
  10. type Feature struct {
  11. Rect image.Rectangle
  12. Weight float64
  13. }
  14. // Classifier is a set of features with a threshold.
  15. type Classifier struct {
  16. Feature []Feature
  17. Threshold float64
  18. Left float64
  19. Right float64
  20. }
  21. // CascadeStage is a cascade of classifiers.
  22. type CascadeStage struct {
  23. Classifier []Classifier
  24. Threshold float64
  25. }
  26. // Cascade is a degenerate tree of Haar-like classifiers.
  27. type Cascade struct {
  28. Stage []CascadeStage
  29. Size image.Point
  30. }
  31. // Match returns true if the full image is classified as an object.
  32. func (c *Cascade) Match(m image.Image) bool {
  33. return c.classify(newWindow(m))
  34. }
  35. // Find returns a set of areas of m that match the feature cascade c.
  36. func (c *Cascade) Find(m image.Image) []image.Rectangle {
  37. // TODO(crawshaw): Consider de-duping strategies.
  38. matches := []image.Rectangle{}
  39. w := newWindow(m)
  40. b := m.Bounds()
  41. origScale := c.Size
  42. for s := origScale; s.X < b.Dx() && s.Y < b.Dy(); s = s.Add(s.Div(10)) {
  43. // translate region and classify
  44. tx := image.Pt(s.X/10, 0)
  45. ty := image.Pt(0, s.Y/10)
  46. for r := image.Rect(0, 0, s.X, s.Y).Add(b.Min); r.In(b); r = r.Add(ty) {
  47. for r1 := r; r1.In(b); r1 = r1.Add(tx) {
  48. if c.classify(w.subWindow(r1)) {
  49. matches = append(matches, r1)
  50. }
  51. }
  52. }
  53. }
  54. return matches
  55. }
  56. type window struct {
  57. mi *integral
  58. miSq *integral
  59. rect image.Rectangle
  60. invArea float64
  61. stdDev float64
  62. }
  63. func (w *window) init() {
  64. w.invArea = 1 / float64(w.rect.Dx()*w.rect.Dy())
  65. mean := float64(w.mi.sum(w.rect)) * w.invArea
  66. vr := float64(w.miSq.sum(w.rect))*w.invArea - mean*mean
  67. if vr < 0 {
  68. vr = 1
  69. }
  70. w.stdDev = math.Sqrt(vr)
  71. }
  72. func newWindow(m image.Image) *window {
  73. mi, miSq := newIntegrals(m)
  74. res := &window{
  75. mi: mi,
  76. miSq: miSq,
  77. rect: m.Bounds(),
  78. }
  79. res.init()
  80. return res
  81. }
  82. func (w *window) subWindow(r image.Rectangle) *window {
  83. res := &window{
  84. mi: w.mi,
  85. miSq: w.miSq,
  86. rect: r,
  87. }
  88. res.init()
  89. return res
  90. }
  91. func (c *Classifier) classify(w *window, pr *projector) float64 {
  92. s := 0.0
  93. for _, f := range c.Feature {
  94. s += float64(w.mi.sum(pr.rect(f.Rect))) * f.Weight
  95. }
  96. s *= w.invArea // normalize to maintain scale invariance
  97. if s < c.Threshold*w.stdDev {
  98. return c.Left
  99. }
  100. return c.Right
  101. }
  102. func (s *CascadeStage) classify(w *window, pr *projector) bool {
  103. sum := 0.0
  104. for _, c := range s.Classifier {
  105. sum += c.classify(w, pr)
  106. }
  107. return sum >= s.Threshold
  108. }
  109. func (c *Cascade) classify(w *window) bool {
  110. pr := newProjector(w.rect, image.Rectangle{image.Pt(0, 0), c.Size})
  111. for _, s := range c.Stage {
  112. if !s.classify(w, pr) {
  113. return false
  114. }
  115. }
  116. return true
  117. }