-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathdetected_object.go
More file actions
167 lines (151 loc) · 4.93 KB
/
detected_object.go
File metadata and controls
167 lines (151 loc) · 4.93 KB
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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
package odam
import (
"fmt"
"image"
reflect "reflect"
blob "github.com/LdDl/gocv-blob/v2/blob"
"github.com/pkg/errors"
"gocv.io/x/gocv"
)
// DetectedObject Store detected object info
type DetectedObject struct {
// Bounding box
Rect image.Rectangle
// Class identifier
ClassID int
// Class description (in most of cases this is just another class unique identifier)
ClassName string
// The probability that an object belongs to the specified class
Confidence float32
// Unexported
speed float32
/* Wrap blob.Blobie for duck typing */
blob.Blobie
}
// String returns something we call 'hash' for detected object
func (d *DetectedObject) String() string {
return fmt.Sprintf("DetectedObject{classID: %d, conf: %.5f, rect: ((%d, %d), (%d, %d))}", d.ClassID, d.Confidence, d.Rect.Min.X, d.Rect.Min.Y, d.Rect.Max.X, d.Rect.Max.Y)
}
// DetectedObjects Just alias to slice of DetectedObject
type DetectedObjects []*DetectedObject
// CastBlobToDetectedObject Handy interface caster
// blob.Blobie -> *odam.DetectedObject
func CastBlobToDetectedObject(b blob.Blobie) (*DetectedObject, error) {
switch b.(type) {
case *DetectedObject:
return b.(*DetectedObject), nil
default:
return nil, fmt.Errorf("Blob should be of type *odam.DetectedObject, but got %s", reflect.TypeOf(b))
}
}
// SetSpeed Sets last measured speed value
func (do *DetectedObject) SetSpeed(v float32) {
do.speed = v
}
// GetSpeed Returns last measured value of speed
func (do *DetectedObject) GetSpeed() float32 {
return do.speed
}
const (
yoloScaleFactor = 1.0 / 255.0
yoloHeight = 608
yoloWidth = 608
)
var (
yoloSize = image.Point{yoloHeight, yoloWidth}
yoloMean = gocv.NewScalar(0.0, 0.0, 0.0, 0.0)
yoloBlobName = ""
)
// DetectObjects Detect objects for provided Go's image via neural network
//
// app - Application instance containing pointer to neural network for object detection
// img - gocv.Mat image object
// netClasses - neural network predefined classes
// filters - List of classes for which you need to filter detected objects
//
func DetectObjects(app *Application, img gocv.Mat, netClasses []string, filters ...string) ([]*DetectedObject, error) {
blobImg := gocv.BlobFromImage(img, yoloScaleFactor, yoloSize, yoloMean, true, false)
defer blobImg.Close()
app.neuralNetwork.SetInput(blobImg, yoloBlobName)
detections := app.neuralNetwork.ForwardLayers(app.layersNames)
detected, err := postprocess(detections, 0.5, 0.4, float32(img.Cols()), float32(img.Rows()), netClasses, filters)
for i := range detections {
err := detections[i].Close()
if err != nil {
return detected, errors.Wrap(err, "Can't deallocate gocv.Mat")
}
}
return detected, err
}
func postprocess(detections []gocv.Mat, confidenceThreshold, nmsThreshold float32, frameWidth, frameHeight float32, netClasses []string, filters []string) ([]*DetectedObject, error) {
detectedObjects := []*DetectedObject{}
bboxes := []image.Rectangle{}
confidences := []float32{}
for i, yoloLayer := range detections {
cols := yoloLayer.Cols()
data, err := detections[i].DataPtrFloat32()
if err != nil {
return nil, errors.Wrap(err, "Can't extract data")
}
for j := 0; j < yoloLayer.Total(); j += cols {
row := data[j : j+cols]
scores := row[5:]
classID, confidence := getClassIDAndConfidence(scores)
className := netClasses[classID]
if stringInSlice(&className, filters) {
if confidence > confidenceThreshold {
confidences = append(confidences, confidence)
boundingBox := calculateBoundingBox(frameWidth, frameHeight, row)
bboxes = append(bboxes, boundingBox)
detectedObjects = append(detectedObjects, &DetectedObject{
Rect: boundingBox,
ClassName: className,
ClassID: classID,
Confidence: confidence,
})
}
}
}
}
if len(bboxes) == 0 {
return nil, nil
}
indices := make([]int, len(bboxes))
for i := range indices {
indices[i] = -1
}
gocv.NMSBoxes(bboxes, confidences, confidenceThreshold, nmsThreshold, indices)
filteredDetectedObjects := make([]*DetectedObject, 0, len(detectedObjects))
for i, idx := range indices {
if idx < 0 || (i != 0 && idx == 0) {
// Eliminate zeros, since they are filtered by NMS (except first element)
// Also filter all '-1' which are undefined by default
continue
}
filteredDetectedObjects = append(filteredDetectedObjects, detectedObjects[idx])
}
return filteredDetectedObjects, nil
}
func getClassIDAndConfidence(x []float32) (int, float32) {
res := 0
max := float32(0.0)
for i, y := range x {
if y > max {
max = y
res = i
}
}
return res, max
}
func calculateBoundingBox(frameWidth, frameHeight float32, row []float32) image.Rectangle {
if len(row) < 4 {
return image.Rect(0, 0, 0, 0)
}
centerX := int(row[0] * frameWidth)
centerY := int(row[1] * frameHeight)
width := int(row[2] * frameWidth)
height := int(row[3] * frameHeight)
left := (centerX - width/2)
top := (centerY - height/2)
return image.Rect(left, top, left+width, top+height)
}