robocar-steering/pkg/steering/corrector.go

182 lines
5.8 KiB
Go
Raw Normal View History

2022-08-22 12:06:27 +00:00
package steering
import (
2022-08-22 21:56:13 +00:00
"encoding/json"
2022-08-22 12:06:27 +00:00
"fmt"
"github.com/cyrilix/robocar-protobuf/go/events"
"go.uber.org/zap"
2022-08-22 21:56:13 +00:00
"os"
2022-08-22 12:06:27 +00:00
)
type Corrector struct {
2022-08-23 20:08:07 +00:00
gridMap *GridMap
objectMoveFactors *GridMap
2022-08-22 12:06:27 +00:00
}
2022-08-22 17:51:44 +00:00
/*
2022-08-23 11:24:31 +00:00
AdjustFromObjectPosition modify steering value according object positions
2022-08-22 17:51:44 +00:00
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% |-----|-----|-----|-----|-----|-----|
2022-08-22 21:56:13 +00:00
: | 0.25| 0.5 | 1 | -1 |-0.5 |-0.25|
2022-08-22 17:51:44 +00:00
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.
2022-08-24 11:54:36 +00:00
: -1 -0.66 -0.33 0 0.33 0.66 1
0% |-----|-----|-----|-----|-----|-----|
: | 0 | 0 | 0 | 0 | 0 | 0 |
20% |-----|-----|-----|-----|-----|-----|
: | 0.2 | 0.1 | 0 | 0 |-0.1 |-0.2 |
40% |-----|-----|-----|-----|-----|-----|
: | ... | ... | ... | ... | ... | ... |
2022-08-22 17:51:44 +00:00
*/
2022-08-23 11:24:31 +00:00
func (c *Corrector) AdjustFromObjectPosition(currentSteering float64, objects []*events.Object) float64 {
2022-08-22 12:06:27 +00:00
// TODO, group rectangle
2022-08-23 20:08:07 +00:00
var deltaMiddle = 0.1
2022-08-22 12:06:27 +00:00
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
}
2022-08-23 20:08:07 +00:00
if currentSteering > -1*deltaMiddle && currentSteering < deltaMiddle {
// Straight
return currentSteering + c.computeDeviation(currentSteering, nearest)
2022-08-24 11:54:36 +00:00
} else {
// Turn to right or left, so search to avoid collision with objects on the right
2022-08-23 20:08:07 +00:00
// Apply factor to object to move it at middle. This factor is function of distance
2022-08-24 11:54:36 +00:00
factor, err := c.objectMoveFactors.ValueOf(float64(nearest.Right), float64(nearest.Bottom))
2022-08-23 20:08:07 +00:00
if err != nil {
zap.S().Warnf("unable to compute factor to apply to object: %v", err)
return currentSteering
2022-08-22 17:51:44 +00:00
}
2022-08-23 20:08:07 +00:00
objMoved := events.Object{
Type: nearest.Type,
2022-08-24 11:54:36 +00:00
Left: nearest.Left + float32(currentSteering*factor),
2022-08-23 20:08:07 +00:00
Top: nearest.Top,
2022-08-24 11:54:36 +00:00
Right: nearest.Right + float32(currentSteering*factor),
2022-08-23 20:08:07 +00:00
Bottom: nearest.Bottom,
Confidence: nearest.Confidence,
2022-08-23 11:24:31 +00:00
}
2022-08-24 11:54:36 +00:00
result := currentSteering + c.computeDeviation(currentSteering, &objMoved)
if result < -1. {
result = -1.
2022-08-22 17:51:44 +00:00
}
2022-08-24 11:54:36 +00:00
if result > 1. {
result = 1.
2022-08-23 20:08:07 +00:00
}
2022-08-24 11:54:36 +00:00
return result
2022-08-22 17:51:44 +00:00
}
2022-08-22 12:06:27 +00:00
}
2022-08-23 20:08:07 +00:00
func (c *Corrector) computeDeviation(currentSteering float64, nearest *events.Object) float64 {
var delta float64
var err error
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
}
2022-08-22 12:06:27 +00:00
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
}
2022-08-22 17:51:44 +00:00
2022-08-23 11:24:31 +00:00
func NewGridMapFromJson(fileName string) (*GridMap, error) {
2022-08-22 21:56:13 +00:00
content, err := os.ReadFile(fileName)
if err != nil {
return nil, fmt.Errorf("unable to read content from %s file: %w", fileName, err)
}
2022-08-23 11:24:31 +00:00
var ft GridMap
2022-08-22 21:56:13 +00:00
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
}
2022-08-23 11:24:31 +00:00
type GridMap struct {
2022-08-22 21:56:13 +00:00
DistanceSteps []float64 `json:"distance_steps"`
SteeringSteps []float64 `json:"steering_steps"`
Data [][]float64 `json:"data"`
2022-08-22 17:51:44 +00:00
}
2022-08-23 11:24:31 +00:00
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])
}
2022-08-22 21:56:13 +00:00
// 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
}
2022-08-22 17:51:44 +00:00
}
2022-08-22 21:56:13 +00:00
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
}
}
2022-08-22 17:51:44 +00:00
2022-08-23 11:24:31 +00:00
return f.Data[idxRow][idxCol], nil
2022-08-22 17:51:44 +00:00
}