refactor: use preprocessed objects from objects-detection service

This commit is contained in:
2024-03-10 11:30:13 +01:00
parent 8e6b7edec9
commit 6a59f55f97
91 changed files with 32 additions and 18805 deletions

View File

@@ -1,62 +0,0 @@
package steering
import (
"github.com/cyrilix/robocar-protobuf/go/events"
"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)
}
func GroupObjects(objects []*events.Object, imgWidth, imgHeight int) []*events.Object {
if len(objects) == 0 {
return []*events.Object{}
}
if len(objects) == 1 {
return []*events.Object{objects[0]}
}
rectangles := make([]image.Rectangle, 0, len(objects))
for _, o := range objects {
rectangles = append(rectangles, *objectToRect(o, imgWidth, imgHeight))
}
grp := gocv.GroupRectangles(rectangles, 1, 0.2)
result := make([]*events.Object, 0, len(grp))
for _, r := range grp {
result = append(result, rectToObject(&r, imgWidth, imgHeight))
}
return result
}
func objectToRect(object *events.Object, imgWidth, imgHeight int) *image.Rectangle {
r := image.Rect(
int(object.Left*float32(imgWidth)),
int(object.Top*float32(imgHeight)),
int(object.Right*float32(imgWidth)),
int(object.Bottom*float32(imgHeight)),
)
return &r
}
func sizeObject(object *events.Object, imgWidth, imgHeight int) float64 {
r := objectToRect(object, imgWidth, imgHeight)
return float64(r.Dx()) * float64(r.Dy())
}
func rectToObject(r *image.Rectangle, imgWidth, imgHeight int) *events.Object {
return &events.Object{
Type: events.TypeObject_ANY,
Left: float32(r.Min.X) / float32(imgWidth),
Top: float32(r.Min.Y) / float32(imgHeight),
Right: float32(r.Max.X) / float32(imgWidth),
Bottom: float32(r.Max.Y) / float32(imgHeight),
Confidence: -1,
}
}

View File

@@ -1,304 +0,0 @@
package steering
import (
"encoding/json"
"fmt"
"github.com/cyrilix/robocar-protobuf/go/events"
"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
dataObjects map[string][]*events.Object
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))
dataObjects = make(map[string][]*events.Object, 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())
dataObjects[dataName] = bboxesToObjects(bb)
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 bboxesToObjects(bboxes []BBox) []*events.Object {
objects := make([]*events.Object, 0, len(bboxes))
for _, bb := range bboxes {
objects = append(objects, &events.Object{
Type: events.TypeObject_ANY,
Left: bb.Left,
Top: bb.Top,
Right: bb.Right,
Bottom: bb.Bottom,
Confidence: bb.Confidence,
})
}
return objects
}
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)
}
})
}
}
func TestGroupObjects(t *testing.T) {
type args struct {
dataName string
}
tests := []struct {
name string
args args
want []*events.Object
}{
{
name: "groupbbox-01",
args: args{
dataName: "01",
},
want: []*events.Object{
{Left: 0.26660156, Top: 0.1706543, Right: 0.5258789, Bottom: 0.47583008, Confidence: 0.4482422},
},
},
{
name: "groupbbox-02",
args: args{
dataName: "02",
},
want: []*events.Object{
{Left: 0.15625, Top: 0.108333334, Right: 0.6875, Bottom: 0.6666667, Confidence: -1},
},
},
{
name: "groupbbox-03",
args: args{
dataName: "03",
},
want: []*events.Object{
{Top: 0.14166667, Right: 0.21875, Bottom: 0.64166665, Confidence: -1},
},
},
{
name: "groupbbox-04",
args: args{
dataName: "04",
},
want: []*events.Object{
{Left: 0.80625, Top: 0.083333336, Right: 0.99375, Bottom: 0.53333336, Confidence: -1},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
img := dataImages[tt.args.dataName]
got := GroupObjects(dataObjects[tt.args.dataName], img.Cols(), img.Rows())
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("GroupObjects() = %v, want %v", got, tt.want)
}
})
}
}

View File

@@ -60,33 +60,17 @@ func loadConfig(configPath string) (*GridMap, error) {
return &gm, nil
}
func WithImageSize(width, height int) OptionCorrector {
return func(c *GridCorrector) {
c.imgWidth = width
c.imgHeight = height
}
}
func WithSizeThreshold(sizeThreshold float64) OptionCorrector {
return func(c *GridCorrector) {
c.sizeThreshold = sizeThreshold
}
}
func WidthDeltaMiddle(d float64) OptionCorrector {
return func(c *GridCorrector) {
c.deltaMiddle = d
}
}
func NewGridCorrector(options ...OptionCorrector) *GridCorrector {
c := &GridCorrector{
gridMap: &defaultGridMap,
objectMoveFactors: &defaultObjectFactors,
deltaMiddle: 0.1,
imgWidth: 160,
imgHeight: 120,
sizeThreshold: 0.75,
}
for _, o := range options {
o(c)
@@ -95,11 +79,9 @@ func NewGridCorrector(options ...OptionCorrector) *GridCorrector {
}
type GridCorrector struct {
gridMap *GridMap
objectMoveFactors *GridMap
deltaMiddle float64
imgWidth, imgHeight int
sizeThreshold float64
gridMap *GridMap
objectMoveFactors *GridMap
deltaMiddle float64
}
/*
@@ -139,21 +121,15 @@ AdjustFromObjectPosition modify steering value according object positions
: | ... | ... | ... | ... | ... | ... |
*/
func (c *GridCorrector) AdjustFromObjectPosition(currentSteering float64, objs []*events.Object) float64 {
objects := c.filter_big_objects(objs, c.imgWidth, c.imgHeight, c.sizeThreshold)
objects = c.filter_bottom_images(objects)
objects := objs
zap.S().Debugf("%v objects to avoid", len(objects))
if len(objects) == 0 {
return currentSteering
}
grpObjs := GroupObjects(objects, c.imgWidth, c.imgHeight)
// get nearest object
nearest, err := c.nearObject(grpObjs)
if err != nil {
zap.S().Warnf("unexpected error on nearest search object, ignore objects: %v", err)
return currentSteering
}
nearest := objs[0]
if currentSteering > -1*c.deltaMiddle && currentSteering < c.deltaMiddle {
// Straight
@@ -206,45 +182,6 @@ func (c *GridCorrector) computeDeviation(nearest *events.Object) float64 {
return delta
}
func (c *GridCorrector) 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 (c *GridCorrector) filter_big_objects(objts []*events.Object, imgWidth int, imgHeight int, sizeThreshold float64) []*events.Object {
objectFiltred := make([]*events.Object, 0, len(objts))
sizeLimit := float64(imgWidth*imgHeight) * sizeThreshold
for _, o := range objts {
if sizeObject(o, imgWidth, imgHeight) < sizeLimit {
objectFiltred = append(objectFiltred, o)
}
}
return objectFiltred
}
func (c *GridCorrector) filter_bottom_images(objts []*events.Object) []*events.Object {
objectFiltred := make([]*events.Object, 0, len(objts))
for _, o := range objts {
if o.Top > 0.90 {
objectFiltred = append(objectFiltred, o)
}
}
return objectFiltred
}
func NewGridMapFromJson(fileName string) (*GridMap, error) {
content, err := os.ReadFile(fileName)
if err != nil {

View File

@@ -167,56 +167,6 @@ func TestCorrector_AdjustFromObjectPosition(t *testing.T) {
}
}
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 := &GridCorrector{}
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