feat(on-objects): Consume objects from mqtt topic
This commit is contained in:
16
pkg/steering/bbox.go
Normal file
16
pkg/steering/bbox.go
Normal file
@ -0,0 +1,16 @@
|
||||
package steering
|
||||
|
||||
import (
|
||||
"gocv.io/x/gocv"
|
||||
"image"
|
||||
)
|
||||
|
||||
func GroupBBoxes(bboxes []image.Rectangle) []image.Rectangle {
|
||||
if len(bboxes) == 0 {
|
||||
return []image.Rectangle{}
|
||||
}
|
||||
if len(bboxes) == 1 {
|
||||
return []image.Rectangle{bboxes[0]}
|
||||
}
|
||||
return gocv.GroupRectangles(bboxes, 1, 0.2)
|
||||
}
|
230
pkg/steering/bbox_test.go
Normal file
230
pkg/steering/bbox_test.go
Normal file
@ -0,0 +1,230 @@
|
||||
package steering
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"go.uber.org/zap"
|
||||
"gocv.io/x/gocv"
|
||||
"image"
|
||||
"image/color"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type ObjectsList struct {
|
||||
BBoxes []BBox `json:"bboxes"`
|
||||
}
|
||||
type BBox struct {
|
||||
Left float32 `json:"left"`
|
||||
Top float32 `json:"top"`
|
||||
Bottom float32 `json:"bottom"`
|
||||
Right float32 `json:"right"`
|
||||
Confidence float32 `json:"confidence"`
|
||||
}
|
||||
|
||||
var (
|
||||
dataBBoxes map[string][]image.Rectangle
|
||||
dataImages map[string]*gocv.Mat
|
||||
)
|
||||
|
||||
func init() {
|
||||
// TODO: empty img without bbox
|
||||
dataNames := []string{"01", "02", "03", "04"}
|
||||
dataBBoxes = make(map[string][]image.Rectangle, len(dataNames))
|
||||
dataImages = make(map[string]*gocv.Mat, len(dataNames))
|
||||
|
||||
for _, dataName := range dataNames {
|
||||
img, bb, err := loadData(dataName)
|
||||
if err != nil {
|
||||
zap.S().Panicf("unable to load data test: %v", err)
|
||||
}
|
||||
dataBBoxes[dataName] = bboxesToRectangles(bb, img.Cols(), img.Rows())
|
||||
dataImages[dataName] = img
|
||||
}
|
||||
}
|
||||
|
||||
func bboxesToRectangles(bboxes []BBox, imgWidth, imgHeiht int) []image.Rectangle {
|
||||
rects := make([]image.Rectangle, 0, len(bboxes))
|
||||
for _, bb := range bboxes {
|
||||
rects = append(rects, bb.toRect(imgWidth, imgHeiht))
|
||||
}
|
||||
return rects
|
||||
}
|
||||
|
||||
func (bb *BBox) toRect(imgWidth, imgHeight int) image.Rectangle {
|
||||
return image.Rect(
|
||||
int(bb.Left*float32(imgWidth)),
|
||||
int(bb.Top*float32(imgHeight)),
|
||||
int(bb.Right*float32(imgWidth)),
|
||||
int(bb.Bottom*float32(imgHeight)),
|
||||
)
|
||||
}
|
||||
|
||||
func loadData(dataName string) (*gocv.Mat, []BBox, error) {
|
||||
contentBBoxes, err := os.ReadFile(fmt.Sprintf("test_data/bboxes-%s.json", dataName))
|
||||
if err != nil {
|
||||
return nil, []BBox{}, fmt.Errorf("unable to load json file for bbox of '%v': %w", dataName, err)
|
||||
}
|
||||
|
||||
var obj ObjectsList
|
||||
err = json.Unmarshal(contentBBoxes, &obj)
|
||||
if err != nil {
|
||||
return nil, []BBox{}, fmt.Errorf("unable to unmarsh json file for bbox of '%v': %w", dataName, err)
|
||||
}
|
||||
|
||||
imgContent, err := os.ReadFile(fmt.Sprintf("test_data/img-%s.jpg", dataName))
|
||||
if err != nil {
|
||||
return nil, []BBox{}, fmt.Errorf("unable to load jpg file of '%v': %w", dataName, err)
|
||||
}
|
||||
img, err := gocv.IMDecode(imgContent, gocv.IMReadUnchanged)
|
||||
if err != nil {
|
||||
return nil, []BBox{}, fmt.Errorf("unable to load jpg of '%v': %w", dataName, err)
|
||||
}
|
||||
return &img, obj.BBoxes, nil
|
||||
}
|
||||
|
||||
func drawImage(img *gocv.Mat, bboxes []BBox) {
|
||||
for _, bb := range bboxes {
|
||||
gocv.Rectangle(img, bb.toRect(img.Cols(), img.Rows()), color.RGBA{R: 0, G: 255, B: 0, A: 0}, 2)
|
||||
gocv.PutText(
|
||||
img,
|
||||
fmt.Sprintf("%.2f", bb.Confidence),
|
||||
image.Point{
|
||||
X: int(bb.Left*float32(img.Cols()) + 10.),
|
||||
Y: int(bb.Top*float32(img.Rows()) + 10.),
|
||||
},
|
||||
gocv.FontHersheyTriplex,
|
||||
0.4,
|
||||
color.RGBA{R: 0, G: 0, B: 0, A: 0},
|
||||
1)
|
||||
}
|
||||
}
|
||||
|
||||
func drawRectangles(img *gocv.Mat, rects []image.Rectangle, c color.RGBA) {
|
||||
for _, r := range rects {
|
||||
gocv.Rectangle(img, r, c, 2)
|
||||
}
|
||||
}
|
||||
|
||||
func saveImage(name string, img *gocv.Mat) error {
|
||||
err := os.MkdirAll("test_result", os.ModePerm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create directory for test result: %w", err)
|
||||
}
|
||||
jpg, err := gocv.IMEncode(gocv.JPEGFileExt, *img)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to encode jpg image: %w", err)
|
||||
}
|
||||
defer jpg.Close()
|
||||
|
||||
err = os.WriteFile(fmt.Sprintf("test_result/%s.jpg", name), jpg.GetBytes(), os.ModePerm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to write jpeg file: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func DisplayImageAndBBoxes(dataName string) error {
|
||||
img, bboxes, err := loadData(dataName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to load image and bboxes: %w", err)
|
||||
}
|
||||
drawImage(img, bboxes)
|
||||
err = saveImage(dataName, img)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to save image: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestDisplayBBox(t *testing.T) {
|
||||
|
||||
type args struct {
|
||||
dataName string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
}{
|
||||
{
|
||||
name: "default",
|
||||
args: args{dataName: "01"},
|
||||
},
|
||||
{
|
||||
name: "02",
|
||||
args: args{dataName: "02"},
|
||||
},
|
||||
{
|
||||
name: "03",
|
||||
args: args{dataName: "03"},
|
||||
},
|
||||
{
|
||||
name: "04",
|
||||
args: args{dataName: "04"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := DisplayImageAndBBoxes(tt.args.dataName)
|
||||
if err != nil {
|
||||
t.Errorf("unable to draw image: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestGroupBBoxes(t *testing.T) {
|
||||
type args struct {
|
||||
dataName string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []image.Rectangle
|
||||
}{
|
||||
{
|
||||
name: "groupbbox-01",
|
||||
args: args{
|
||||
dataName: "01",
|
||||
},
|
||||
want: []image.Rectangle{{Min: image.Point{X: 42, Y: 20}, Max: image.Point{X: 84, Y: 57}}},
|
||||
},
|
||||
{
|
||||
name: "groupbbox-02",
|
||||
args: args{
|
||||
dataName: "02",
|
||||
},
|
||||
want: []image.Rectangle{{Min: image.Point{X: 25, Y: 13}, Max: image.Point{X: 110, Y: 80}}},
|
||||
},
|
||||
{
|
||||
name: "groupbbox-03",
|
||||
args: args{
|
||||
dataName: "03",
|
||||
},
|
||||
want: []image.Rectangle{{Min: image.Point{X: 0, Y: 17}, Max: image.Point{X: 35, Y: 77}}},
|
||||
},
|
||||
{
|
||||
name: "groupbbox-04",
|
||||
args: args{
|
||||
dataName: "04",
|
||||
},
|
||||
want: []image.Rectangle{{Min: image.Point{X: 129, Y: 10}, Max: image.Point{X: 159, Y: 64}}},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := GroupBBoxes(dataBBoxes[tt.args.dataName])
|
||||
img := gocv.NewMat()
|
||||
defer img.Close()
|
||||
dataImages[tt.args.dataName].CopyTo(&img)
|
||||
drawRectangles(&img, got, color.RGBA{R: 0, G: 0, B: 255, A: 0})
|
||||
saveImage(tt.name, &img)
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("GroupBBoxes() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -9,13 +9,14 @@ import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
func NewController(client mqtt.Client, steeringTopic, driveModeTopic, rcSteeringTopic, tfSteeringTopic string, debug bool) *Controller {
|
||||
func NewController(client mqtt.Client, steeringTopic, driveModeTopic, rcSteeringTopic, tfSteeringTopic, objectsTopic string) *Controller {
|
||||
return &Controller{
|
||||
client: client,
|
||||
steeringTopic: steeringTopic,
|
||||
driveModeTopic: driveModeTopic,
|
||||
rcSteeringTopic: rcSteeringTopic,
|
||||
tfSteeringTopic: tfSteeringTopic,
|
||||
objectsTopic: objectsTopic,
|
||||
driveMode: events.DriveMode_USER,
|
||||
}
|
||||
|
||||
@ -28,8 +29,11 @@ type Controller struct {
|
||||
muDriveMode sync.RWMutex
|
||||
driveMode events.DriveMode
|
||||
|
||||
cancel chan interface{}
|
||||
driveModeTopic, rcSteeringTopic, tfSteeringTopic string
|
||||
cancel chan interface{}
|
||||
driveModeTopic, rcSteeringTopic, tfSteeringTopic, objectsTopic string
|
||||
|
||||
muObjects sync.RWMutex
|
||||
objects []*events.Object
|
||||
|
||||
debug bool
|
||||
}
|
||||
@ -50,6 +54,19 @@ func (p *Controller) Stop() {
|
||||
service.StopService("throttle", p.client, p.driveModeTopic, p.rcSteeringTopic, p.tfSteeringTopic)
|
||||
}
|
||||
|
||||
func (p *Controller) onObjects(_ mqtt.Client, message mqtt.Message) {
|
||||
var msg events.ObjectsMessage
|
||||
err := proto.Unmarshal(message.Payload(), &msg)
|
||||
if err != nil {
|
||||
zap.S().Errorf("unable to unmarshal protobuf %T message: %v", msg, err)
|
||||
return
|
||||
}
|
||||
|
||||
p.muObjects.Lock()
|
||||
defer p.muObjects.Unlock()
|
||||
p.objects = msg.GetObjects()
|
||||
}
|
||||
|
||||
func (p *Controller) onDriveMode(_ mqtt.Client, message mqtt.Message) {
|
||||
var msg events.DriveModeMessage
|
||||
err := proto.Unmarshal(message.Payload(), &msg)
|
||||
@ -115,6 +132,11 @@ var registerCallbacks = func(p *Controller) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = service.RegisterCallback(p.client, p.objectsTopic, p.onObjects)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -33,50 +33,58 @@ func TestDefaultSteering(t *testing.T) {
|
||||
driveModeTopic := "topic/driveMode"
|
||||
rcSteeringTopic := "topic/rcSteering"
|
||||
tfSteeringTopic := "topic/tfSteering"
|
||||
objectsTopic := "topic/objects"
|
||||
|
||||
p := NewController(nil, steeringTopic, driveModeTopic, rcSteeringTopic, tfSteeringTopic, true)
|
||||
p := NewController(nil, steeringTopic, driveModeTopic, rcSteeringTopic, tfSteeringTopic, objectsTopic)
|
||||
|
||||
cases := []struct {
|
||||
driveMode events.DriveModeMessage
|
||||
rcSteering events.SteeringMessage
|
||||
tfSteering events.SteeringMessage
|
||||
expectedSteering events.SteeringMessage
|
||||
objects events.ObjectsMessage
|
||||
}{
|
||||
{
|
||||
events.DriveModeMessage{DriveMode: events.DriveMode_USER},
|
||||
events.SteeringMessage{Steering: 0.3, Confidence: 1.0},
|
||||
events.SteeringMessage{Steering: 0.4, Confidence: 1.0},
|
||||
events.SteeringMessage{Steering: 0.3, Confidence: 1.0},
|
||||
events.ObjectsMessage{},
|
||||
},
|
||||
{
|
||||
events.DriveModeMessage{DriveMode: events.DriveMode_PILOT},
|
||||
events.SteeringMessage{Steering: 0.5, Confidence: 1.0},
|
||||
events.SteeringMessage{Steering: 0.6, Confidence: 1.0},
|
||||
events.SteeringMessage{Steering: 0.6, Confidence: 1.0},
|
||||
events.ObjectsMessage{},
|
||||
},
|
||||
{
|
||||
events.DriveModeMessage{DriveMode: events.DriveMode_PILOT},
|
||||
events.SteeringMessage{Steering: 0.4, Confidence: 1.0},
|
||||
events.SteeringMessage{Steering: 0.7, Confidence: 1.0},
|
||||
events.SteeringMessage{Steering: 0.7, Confidence: 1.0},
|
||||
events.ObjectsMessage{},
|
||||
},
|
||||
{
|
||||
events.DriveModeMessage{DriveMode: events.DriveMode_USER},
|
||||
events.SteeringMessage{Steering: 0.5, Confidence: 1.0},
|
||||
events.SteeringMessage{Steering: 0.8, Confidence: 1.0},
|
||||
events.SteeringMessage{Steering: 0.5, Confidence: 1.0},
|
||||
events.ObjectsMessage{},
|
||||
},
|
||||
{
|
||||
events.DriveModeMessage{DriveMode: events.DriveMode_USER},
|
||||
events.SteeringMessage{Steering: 0.4, Confidence: 1.0},
|
||||
events.SteeringMessage{Steering: 0.9, Confidence: 1.0},
|
||||
events.SteeringMessage{Steering: 0.4, Confidence: 1.0},
|
||||
events.ObjectsMessage{},
|
||||
},
|
||||
{
|
||||
events.DriveModeMessage{DriveMode: events.DriveMode_USER},
|
||||
events.SteeringMessage{Steering: 0.6, Confidence: 1.0},
|
||||
events.SteeringMessage{Steering: -0.3, Confidence: 1.0},
|
||||
events.SteeringMessage{Steering: 0.6, Confidence: 1.0},
|
||||
events.ObjectsMessage{},
|
||||
},
|
||||
}
|
||||
|
||||
@ -88,6 +96,7 @@ func TestDefaultSteering(t *testing.T) {
|
||||
p.onDriveMode(nil, testtools.NewFakeMessageFromProtobuf(driveModeTopic, &c.driveMode))
|
||||
p.onRCSteering(nil, testtools.NewFakeMessageFromProtobuf(rcSteeringTopic, &c.rcSteering))
|
||||
p.onTFSteering(nil, testtools.NewFakeMessageFromProtobuf(tfSteeringTopic, &c.tfSteering))
|
||||
p.onObjects(nil, testtools.NewFakeMessageFromProtobuf(objectsTopic, &c.objects))
|
||||
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
|
145
pkg/steering/corrector.go
Normal file
145
pkg/steering/corrector.go
Normal file
@ -0,0 +1,145 @@
|
||||
package steering
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/cyrilix/robocar-protobuf/go/events"
|
||||
"go.uber.org/zap"
|
||||
"os"
|
||||
)
|
||||
|
||||
type Corrector struct {
|
||||
gridMap *GridMap
|
||||
}
|
||||
|
||||
/*
|
||||
AdjustFromObjectPosition modify steering value according object positions
|
||||
|
||||
1. To compute steering correction, split in image in zones and define correction value for each zone
|
||||
|
||||
Steering computed
|
||||
: -1 -0.66 -0.33 0 0.33 0.66 1
|
||||
0% |-----|-----|-----|-----|-----|-----|
|
||||
: | 0 | 0 | 0 | 0 | 0 | 0 |
|
||||
20% |-----|-----|-----|-----|-----|-----|
|
||||
: | 0 | 0 | 0 | 0 | 0 | 0 |
|
||||
40% |-----|-----|-----|-----|-----|-----|
|
||||
: | 0 | 0 | 0.25|-0.25| 0 | 0 |
|
||||
60% |-----|-----|-----|-----|-----|-----|
|
||||
: | 0 | 0.25| 0.5 |-0.5 |-0.25| 0 |
|
||||
80% |-----|-----|-----|-----|-----|-----|
|
||||
: | 0.25| 0.5 | 1 | -1 |-0.5 |-0.25|
|
||||
100%|-----|-----|-----|-----|-----|-----|
|
||||
|
||||
2. For straight (current steering near of 0), search nearest object and if:
|
||||
|
||||
* left and right values < 0: use correction from right value according image splitting
|
||||
* left and right values > 0: use correction from left value according image splitting
|
||||
* left < 0 and right values > 0: use (right + (right - left) / 2) value
|
||||
|
||||
3. If current steering != 0 (turn on left or right), shift right and left values proportionnaly to current steering and
|
||||
apply 2.
|
||||
*/
|
||||
func (c *Corrector) AdjustFromObjectPosition(currentSteering float64, objects []*events.Object) float64 {
|
||||
// TODO, group rectangle
|
||||
|
||||
if len(objects) == 0 {
|
||||
return currentSteering
|
||||
}
|
||||
// get nearest object
|
||||
nearest, err := c.nearObject(objects)
|
||||
if err != nil {
|
||||
zap.S().Warnf("unexpected error on nearest seach object, ignore objects: %v", err)
|
||||
return currentSteering
|
||||
}
|
||||
|
||||
if currentSteering > -0.1 && currentSteering < 0.1 {
|
||||
|
||||
var delta float64
|
||||
|
||||
if nearest.Left < 0 && nearest.Right < 0 {
|
||||
delta, err = c.gridMap.ValueOf(float64(nearest.Right)*2-1., float64(nearest.Bottom))
|
||||
}
|
||||
if nearest.Left > 0 && nearest.Right > 0 {
|
||||
delta, err = c.gridMap.ValueOf(float64(nearest.Left)*2-1., float64(nearest.Bottom))
|
||||
} else {
|
||||
delta, err = c.gridMap.ValueOf(float64(float64(nearest.Left)+(float64(nearest.Right)-float64(nearest.Left))/2.)*2.-1., float64(nearest.Bottom))
|
||||
}
|
||||
if err != nil {
|
||||
zap.S().Warnf("unable to compute delta to apply to steering, skip correction: %v", err)
|
||||
delta = 0
|
||||
}
|
||||
return currentSteering + delta
|
||||
}
|
||||
|
||||
// Search if current steering is near of Right or Left
|
||||
|
||||
return currentSteering
|
||||
}
|
||||
|
||||
func (c *Corrector) nearObject(objects []*events.Object) (*events.Object, error) {
|
||||
if len(objects) == 0 {
|
||||
return nil, fmt.Errorf("list objects must contain at least one object")
|
||||
}
|
||||
if len(objects) == 1 {
|
||||
return objects[0], nil
|
||||
}
|
||||
|
||||
var result *events.Object
|
||||
for _, obj := range objects {
|
||||
if result == nil || obj.Bottom > result.Bottom {
|
||||
result = obj
|
||||
continue
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func NewGridMapFromJson(fileName string) (*GridMap, error) {
|
||||
content, err := os.ReadFile(fileName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to read content from %s file: %w", fileName, err)
|
||||
}
|
||||
var ft GridMap
|
||||
err = json.Unmarshal(content, &ft)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to unmarshal json content from %s file: %w", fileName, err)
|
||||
}
|
||||
// TODO: check structure is valid
|
||||
return &ft, nil
|
||||
}
|
||||
|
||||
type GridMap struct {
|
||||
DistanceSteps []float64 `json:"distance_steps"`
|
||||
SteeringSteps []float64 `json:"steering_steps"`
|
||||
Data [][]float64 `json:"data"`
|
||||
}
|
||||
|
||||
func (f *GridMap) ValueOf(steering float64, distance float64) (float64, error) {
|
||||
if steering < f.SteeringSteps[0] || steering > f.SteeringSteps[len(f.SteeringSteps)-1] {
|
||||
return 0., fmt.Errorf("invalid steering value: %v, must be between %v and %v", steering, f.SteeringSteps[0], f.SteeringSteps[len(f.SteeringSteps)-1])
|
||||
}
|
||||
if distance < f.DistanceSteps[0] || distance > f.DistanceSteps[len(f.DistanceSteps)-1] {
|
||||
return 0., fmt.Errorf("invalid distance value: %v, must be between %v and %v", steering, f.DistanceSteps[0], f.DistanceSteps[len(f.DistanceSteps)-1])
|
||||
}
|
||||
// search column index
|
||||
var idxCol int
|
||||
// Start loop at 1 because first column should be skipped
|
||||
for i := 1; i < len(f.SteeringSteps); i++ {
|
||||
if steering < f.SteeringSteps[i] {
|
||||
idxCol = i - 1
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var idxRow int
|
||||
// Start loop at 1 because first column should be skipped
|
||||
for i := 1; i < len(f.DistanceSteps); i++ {
|
||||
if distance < f.DistanceSteps[i] {
|
||||
idxRow = i - 1
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return f.Data[idxRow][idxCol], nil
|
||||
}
|
401
pkg/steering/corrector_test.go
Normal file
401
pkg/steering/corrector_test.go
Normal file
@ -0,0 +1,401 @@
|
||||
package steering
|
||||
|
||||
import (
|
||||
"github.com/cyrilix/robocar-protobuf/go/events"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var (
|
||||
objectOnMiddleDistant = events.Object{
|
||||
Type: events.TypeObject_ANY,
|
||||
Left: 0.4,
|
||||
Top: 0.1,
|
||||
Right: 0.6,
|
||||
Bottom: 0.2,
|
||||
Confidence: 0.9,
|
||||
}
|
||||
objectOnLeftDistant = events.Object{
|
||||
Type: events.TypeObject_ANY,
|
||||
Left: 0.1,
|
||||
Top: 0.09,
|
||||
Right: 0.3,
|
||||
Bottom: 0.19,
|
||||
Confidence: 0.9,
|
||||
}
|
||||
objectOnRightDistant = events.Object{
|
||||
Type: events.TypeObject_ANY,
|
||||
Left: 0.7,
|
||||
Top: 0.21,
|
||||
Right: 0.9,
|
||||
Bottom: 0.11,
|
||||
Confidence: 0.9,
|
||||
}
|
||||
objectOnMiddleNear = events.Object{
|
||||
Type: events.TypeObject_ANY,
|
||||
Left: 0.4,
|
||||
Top: 0.8,
|
||||
Right: 0.6,
|
||||
Bottom: 0.9,
|
||||
Confidence: 0.9,
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
defaultGridMap = GridMap{
|
||||
DistanceSteps: []float64{0., 0.2, 0.4, 0.6, 0.8, 1.},
|
||||
SteeringSteps: []float64{-1., -0.66, -0.33, 0., 0.33, 0.66, 1.},
|
||||
Data: [][]float64{
|
||||
{0., 0., 0., 0., 0., 0.},
|
||||
{0., 0., 0., 0., 0., 0.},
|
||||
{0., 0., 0.25, -0.25, 0., 0.},
|
||||
{0., 0.25, 0.5, -0.5, -0.25, 0.},
|
||||
{0.25, 0.5, 1, -1, -0.5, -0.25},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func TestCorrector_AdjustFromObjectPosition(t *testing.T) {
|
||||
type args struct {
|
||||
currentSteering float64
|
||||
objects []*events.Object
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want float64
|
||||
}{
|
||||
{
|
||||
name: "run straight without objects",
|
||||
args: args{
|
||||
currentSteering: 0.,
|
||||
objects: []*events.Object{},
|
||||
},
|
||||
want: 0.,
|
||||
},
|
||||
{
|
||||
name: "run to left without objects",
|
||||
args: args{
|
||||
currentSteering: -0.9,
|
||||
objects: []*events.Object{},
|
||||
},
|
||||
want: -0.9,
|
||||
},
|
||||
{
|
||||
name: "run to right without objects",
|
||||
args: args{
|
||||
currentSteering: 0.9,
|
||||
objects: []*events.Object{},
|
||||
}, want: 0.9,
|
||||
},
|
||||
|
||||
{
|
||||
name: "run straight with 1 distant object",
|
||||
args: args{
|
||||
currentSteering: 0.,
|
||||
objects: []*events.Object{&objectOnMiddleDistant},
|
||||
},
|
||||
want: 0.,
|
||||
},
|
||||
{
|
||||
name: "run to left with 1 distant object",
|
||||
args: args{
|
||||
currentSteering: -0.9,
|
||||
objects: []*events.Object{&objectOnMiddleDistant},
|
||||
},
|
||||
want: -0.9,
|
||||
},
|
||||
{
|
||||
name: "run to right with 1 distant object",
|
||||
args: args{
|
||||
currentSteering: 0.9,
|
||||
objects: []*events.Object{&objectOnMiddleDistant},
|
||||
},
|
||||
want: 0.9,
|
||||
},
|
||||
|
||||
{
|
||||
name: "run straight with 1 near object",
|
||||
args: args{
|
||||
currentSteering: 0.,
|
||||
objects: []*events.Object{&objectOnMiddleNear},
|
||||
},
|
||||
want: 1,
|
||||
},
|
||||
{
|
||||
name: "run to left with 1 near object",
|
||||
args: args{
|
||||
currentSteering: -0.9,
|
||||
objects: []*events.Object{&objectOnMiddleNear},
|
||||
},
|
||||
want: -0.4,
|
||||
},
|
||||
{
|
||||
name: "run to right with 1 near object",
|
||||
args: args{
|
||||
currentSteering: 0.9,
|
||||
objects: []*events.Object{&objectOnMiddleNear},
|
||||
},
|
||||
want: 0.4,
|
||||
},
|
||||
|
||||
// Todo Object on left/right near/distant
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &Corrector{
|
||||
gridMap: &defaultGridMap,
|
||||
}
|
||||
if got := c.AdjustFromObjectPosition(tt.args.currentSteering, tt.args.objects); got != tt.want {
|
||||
t.Errorf("AdjustFromObjectPosition() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCorrector_nearObject(t *testing.T) {
|
||||
type args struct {
|
||||
objects []*events.Object
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *events.Object
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "List object is empty",
|
||||
args: args{
|
||||
objects: []*events.Object{},
|
||||
},
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "List with only one object",
|
||||
args: args{
|
||||
objects: []*events.Object{&objectOnMiddleNear},
|
||||
},
|
||||
want: &objectOnMiddleNear,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "List with many objects",
|
||||
args: args{
|
||||
objects: []*events.Object{&objectOnLeftDistant, &objectOnMiddleNear, &objectOnRightDistant, &objectOnMiddleDistant},
|
||||
},
|
||||
want: &objectOnMiddleNear,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &Corrector{}
|
||||
got, err := c.nearObject(tt.args.objects)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("nearObject() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("nearObject() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewGridMapFromJson(t *testing.T) {
|
||||
type args struct {
|
||||
fileName string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *GridMap
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "default config",
|
||||
args: args{
|
||||
fileName: "test_data/config.json",
|
||||
},
|
||||
want: &defaultGridMap,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := NewGridMapFromJson(tt.args.fileName)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("NewGridMapFromJson() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(*got, *tt.want) {
|
||||
t.Errorf("NewGridMapFromJson() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
if !reflect.DeepEqual(got.SteeringSteps, tt.want.SteeringSteps) {
|
||||
t.Errorf("NewGridMapFromJson(), bad steering limits: got = %v, want %v", got.SteeringSteps, tt.want.SteeringSteps)
|
||||
}
|
||||
if !reflect.DeepEqual(got.DistanceSteps, tt.want.DistanceSteps) {
|
||||
t.Errorf("NewGridMapFromJson(), bad distance limits: got = %v, want %v", got.DistanceSteps, tt.want.DistanceSteps)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGridMap_ValueOf(t *testing.T) {
|
||||
type fields struct {
|
||||
DistanceSteps []float64
|
||||
SteeringSteps []float64
|
||||
Data [][]float64
|
||||
}
|
||||
type args struct {
|
||||
steering float64
|
||||
distance float64
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want float64
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "nominal",
|
||||
fields: fields{
|
||||
DistanceSteps: defaultGridMap.DistanceSteps,
|
||||
SteeringSteps: defaultGridMap.SteeringSteps,
|
||||
Data: defaultGridMap.Data,
|
||||
},
|
||||
args: args{
|
||||
steering: 0.,
|
||||
distance: 0.,
|
||||
},
|
||||
want: 0,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "limit distance <",
|
||||
fields: fields{
|
||||
DistanceSteps: defaultGridMap.DistanceSteps,
|
||||
SteeringSteps: defaultGridMap.SteeringSteps,
|
||||
Data: defaultGridMap.Data,
|
||||
},
|
||||
args: args{
|
||||
steering: 0,
|
||||
distance: 0.39999,
|
||||
},
|
||||
want: 0,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "limit distance >",
|
||||
fields: fields{
|
||||
DistanceSteps: defaultGridMap.DistanceSteps,
|
||||
SteeringSteps: defaultGridMap.SteeringSteps,
|
||||
Data: defaultGridMap.Data,
|
||||
},
|
||||
args: args{
|
||||
steering: 0,
|
||||
distance: 0.400001,
|
||||
},
|
||||
want: -0.25,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "limit steering <",
|
||||
fields: fields{
|
||||
DistanceSteps: defaultGridMap.DistanceSteps,
|
||||
SteeringSteps: defaultGridMap.SteeringSteps,
|
||||
Data: defaultGridMap.Data,
|
||||
},
|
||||
args: args{
|
||||
steering: -0.660001,
|
||||
distance: 0.85,
|
||||
},
|
||||
want: 0.25,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "limit steering >",
|
||||
fields: fields{
|
||||
DistanceSteps: defaultGridMap.DistanceSteps,
|
||||
SteeringSteps: defaultGridMap.SteeringSteps,
|
||||
Data: defaultGridMap.Data,
|
||||
},
|
||||
args: args{
|
||||
steering: -0.66,
|
||||
distance: 0.85,
|
||||
},
|
||||
want: 0.5,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "steering < min value",
|
||||
fields: fields{
|
||||
DistanceSteps: defaultGridMap.DistanceSteps,
|
||||
SteeringSteps: defaultGridMap.SteeringSteps,
|
||||
Data: defaultGridMap.Data,
|
||||
},
|
||||
args: args{
|
||||
steering: defaultGridMap.SteeringSteps[0] - 0.1,
|
||||
distance: 0.85,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "steering > max value",
|
||||
fields: fields{
|
||||
DistanceSteps: defaultGridMap.DistanceSteps,
|
||||
SteeringSteps: defaultGridMap.SteeringSteps,
|
||||
Data: defaultGridMap.Data,
|
||||
},
|
||||
args: args{
|
||||
steering: defaultGridMap.SteeringSteps[len(defaultGridMap.SteeringSteps)-1] + 0.1,
|
||||
distance: 0.85,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "distance < min value",
|
||||
fields: fields{
|
||||
DistanceSteps: defaultGridMap.DistanceSteps,
|
||||
SteeringSteps: defaultGridMap.SteeringSteps,
|
||||
Data: defaultGridMap.Data,
|
||||
},
|
||||
args: args{
|
||||
steering: -0.65,
|
||||
distance: defaultGridMap.DistanceSteps[0] - 0.1,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "distance > max value",
|
||||
fields: fields{
|
||||
DistanceSteps: defaultGridMap.DistanceSteps,
|
||||
SteeringSteps: defaultGridMap.SteeringSteps,
|
||||
Data: defaultGridMap.Data,
|
||||
},
|
||||
args: args{
|
||||
steering: -0.65,
|
||||
distance: defaultGridMap.DistanceSteps[len(defaultGridMap.DistanceSteps)-1] + 0.1,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
f := &GridMap{
|
||||
DistanceSteps: tt.fields.DistanceSteps,
|
||||
SteeringSteps: tt.fields.SteeringSteps,
|
||||
Data: tt.fields.Data,
|
||||
}
|
||||
got, err := f.ValueOf(tt.args.steering, tt.args.distance)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("ValueOf() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("ValueOf() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
12
pkg/steering/test_data/bboxes-01.json
Normal file
12
pkg/steering/test_data/bboxes-01.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"bboxes": [
|
||||
{
|
||||
"right": 0.5258789,
|
||||
"top": 0.1706543,
|
||||
"left": 0.26660156,
|
||||
"bottom": 0.47583008,
|
||||
"confidence": 0.4482422
|
||||
}
|
||||
]
|
||||
}
|
||||
|
25
pkg/steering/test_data/bboxes-02.json
Normal file
25
pkg/steering/test_data/bboxes-02.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"bboxes": [
|
||||
{
|
||||
"right": 0.6879883,
|
||||
"top": 0.115234375,
|
||||
"left": 0.1586914,
|
||||
"bottom": 0.66796875,
|
||||
"confidence": 0.82714844
|
||||
},
|
||||
{
|
||||
"right": 0.2794531,
|
||||
"top": 0.301816406,
|
||||
"left": 0.15698242,
|
||||
"bottom": 0.65748047,
|
||||
"confidence": 0.83447266
|
||||
},
|
||||
{
|
||||
"right": 0.6875,
|
||||
"top": 0.11328125,
|
||||
"left": 0.15673828,
|
||||
"bottom": 0.66748047,
|
||||
"confidence": 0.85253906
|
||||
}
|
||||
]
|
||||
}
|
27
pkg/steering/test_data/bboxes-03.json
Normal file
27
pkg/steering/test_data/bboxes-03.json
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"bboxes": [
|
||||
{
|
||||
"right": 0.2211914,
|
||||
"top": 0.14953613,
|
||||
"left": 0.0015258789,
|
||||
"bottom": 0.64941406,
|
||||
"confidence": 0.5595703
|
||||
},
|
||||
{
|
||||
"right": 0.22192383,
|
||||
"top": 0.14819336,
|
||||
"left": 0.0014038086,
|
||||
"bottom": 0.64941406,
|
||||
"confidence": 0.5493164
|
||||
},
|
||||
{
|
||||
"right": 0.21948242,
|
||||
"top": 0.1459961,
|
||||
"left": 0.0015258789,
|
||||
"bottom": 0.65185547,
|
||||
"confidence": 0.5595703
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
25
pkg/steering/test_data/bboxes-04.json
Normal file
25
pkg/steering/test_data/bboxes-04.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"bboxes": [
|
||||
{
|
||||
"right": 0.99902344,
|
||||
"top": 0.08947754,
|
||||
"left": 0.8095703,
|
||||
"bottom": 0.54296875,
|
||||
"confidence": 0.4741211
|
||||
},
|
||||
{
|
||||
"right": 0.99902344,
|
||||
"top": 0.08666992,
|
||||
"left": 0.80859375,
|
||||
"bottom": 0.54003906,
|
||||
"confidence": 0.453125
|
||||
},
|
||||
{
|
||||
"right": 0.99902344,
|
||||
"top": 0.09423828,
|
||||
"left": 0.8095703,
|
||||
"bottom": 0.54345703,
|
||||
"confidence": 0.44995117
|
||||
}
|
||||
]
|
||||
}
|
11
pkg/steering/test_data/config.json
Normal file
11
pkg/steering/test_data/config.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"steering_steps":[-1, -0.66, -0.33, 0, 0.33, 0.66, 1],
|
||||
"distance_steps": [0, 0.2, 0.4, 0.6, 0.8, 1],
|
||||
"data": [
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0.25, -0.25, 0, 0],
|
||||
[0, 0.25, 0.5, -0.5, -0.25, 0],
|
||||
[0.25, 0.5, 1, -1, -0.5, -0.25]
|
||||
]
|
||||
}
|
BIN
pkg/steering/test_data/img-01.jpg
Executable file
BIN
pkg/steering/test_data/img-01.jpg
Executable file
Binary file not shown.
After Width: | Height: | Size: 5.6 KiB |
BIN
pkg/steering/test_data/img-02.jpg
Executable file
BIN
pkg/steering/test_data/img-02.jpg
Executable file
Binary file not shown.
After Width: | Height: | Size: 6.4 KiB |
BIN
pkg/steering/test_data/img-03.jpg
Executable file
BIN
pkg/steering/test_data/img-03.jpg
Executable file
Binary file not shown.
After Width: | Height: | Size: 6.1 KiB |
BIN
pkg/steering/test_data/img-04.jpg
Executable file
BIN
pkg/steering/test_data/img-04.jpg
Executable file
Binary file not shown.
After Width: | Height: | Size: 5.0 KiB |
Reference in New Issue
Block a user