robocar-road/pkg/part/opencv.go

180 lines
4.9 KiB
Go
Raw Permalink Normal View History

2020-01-04 13:10:36 +00:00
package part
import (
"github.com/cyrilix/robocar-protobuf/go/events"
2021-10-12 21:15:05 +00:00
"go.uber.org/zap"
2020-01-04 13:10:36 +00:00
"gocv.io/x/gocv"
"image"
"image/color"
)
const FILLED = -1
type RoadDetector struct {
kernelSize int
morphoIterations int
approxPolyEpsilonFactor float64
previousBoundingBox *image.Rectangle
previousRoad *[]image.Point
thresholdLowerBound, thresholdUpperBound gocv.Mat
}
func (rd *RoadDetector) Close() error {
var err error
err = nil
if err1 := rd.thresholdLowerBound.Close(); err1 != nil {
2021-10-12 21:15:05 +00:00
zap.S().Errorf("unable to close thresholdLowerBound resource: %v", err1)
2020-01-04 13:10:36 +00:00
err = err1
}
if err2 := rd.thresholdUpperBound.Close(); err2 != nil {
2021-10-12 21:15:05 +00:00
zap.S().Errorf("unable to close thresholdUpperBound resource: %v", err2)
2020-01-04 13:10:36 +00:00
err = err2
}
return err
}
func NewRoadDetector() *RoadDetector {
return &RoadDetector{
kernelSize: 4,
morphoIterations: 3,
approxPolyEpsilonFactor: 0.01,
thresholdLowerBound: gocv.NewMatFromScalar(gocv.NewScalar(120.0, 120.0, 120.0, 120.0), gocv.MatTypeCV8U),
thresholdUpperBound: gocv.NewMatFromScalar(gocv.NewScalar(250.0, 250.0, 250.0, 250.0), gocv.MatTypeCV8U),
}
}
2021-10-12 21:05:10 +00:00
func (rd *RoadDetector) DetectRoadContour(imgGray *gocv.Mat, horizonRow int) *gocv.PointVector {
2020-01-04 13:10:36 +00:00
kernel := gocv.NewMatWithSizeFromScalar(gocv.NewScalar(1, 1, 1, 1), rd.kernelSize, rd.kernelSize, gocv.MatTypeCV8U)
img := imgGray.Clone()
defer func() {
if err := img.Close(); err != nil {
2021-10-12 21:15:05 +00:00
zap.S().Warnf("unable to close mat resource: %v", err)
2020-01-04 13:10:36 +00:00
}
}()
for i := rd.morphoIterations; i > 0; i-- {
gocv.Dilate(img, &img, kernel)
}
for i := rd.morphoIterations; i > 0; i-- {
gocv.Erode(img, &img, kernel)
}
gocv.Dilate(img, &img, kernel)
gocv.Threshold(img, &img, 180, 255, gocv.ThresholdBinaryInv)
// Draw black rectangle above horizon
horizon := gocv.NewMatWithSize(1, 4, gocv.MatTypeCV32S)
horizon.SetIntAt(0, 0, 0) // X1
horizon.SetIntAt(0, 1, int32(horizonRow)) // Y1
horizon.SetIntAt(0, 2, int32(imgGray.Cols())) // X2
horizon.SetIntAt(0, 3, int32(horizonRow)) // Y2
rectangle := image.Rect(0, 0, int(horizon.GetIntAt(0, 2)), int(horizon.GetIntAt(0, 3)))
gocv.Rectangle(&img, rectangle, color.RGBA{0, 0, 0, 0}, FILLED)
return rd.detectRoadContour(&img)
}
2021-10-12 21:05:10 +00:00
func (rd *RoadDetector) detectRoadContour(imgInversed *gocv.Mat) *gocv.PointVector {
2020-01-04 13:10:36 +00:00
var (
epsilon float64
2021-10-12 21:05:10 +00:00
cntr gocv.PointVector
2020-01-04 13:10:36 +00:00
)
2021-10-12 21:05:10 +00:00
ptsVec := gocv.FindContours(*imgInversed, gocv.RetrievalExternal, gocv.ChainApproxSimple)
defer ptsVec.Close()
2020-01-04 13:10:36 +00:00
2021-10-12 21:05:10 +00:00
if ptsVec.Size() == 0 {
emptyContours := gocv.NewPointVector()
2020-01-04 13:10:36 +00:00
return &emptyContours
2021-10-12 21:05:10 +00:00
} else if ptsVec.Size() == 1 {
epsilon = rd.approxPolyEpsilonFactor * gocv.ArcLength(ptsVec.At(0), true)
cntr = ptsVec.At(0)
2020-01-04 13:10:36 +00:00
} else {
// Search biggest contour
2021-10-12 21:05:10 +00:00
peris := make([]float64, ptsVec.Size())
2020-01-04 13:10:36 +00:00
maxArcIdx := 0
maxArcValue := 0.
2021-10-12 21:05:10 +00:00
//for i, c := range cntrs {
for i := 0; i< ptsVec.Size(); i++ {
c := ptsVec.At(i)
2020-01-04 13:10:36 +00:00
peri := gocv.ArcLength(c, true)
peris[i] = peri
if peri > maxArcValue {
maxArcValue = peri
maxArcIdx = i
}
2021-10-12 21:05:10 +00:00
cntr = ptsVec.At(maxArcIdx)
2020-01-04 13:10:36 +00:00
}
epsilon = rd.approxPolyEpsilonFactor * peris[maxArcIdx]
}
approx := gocv.ApproxPolyDP(cntr, epsilon, true)
return &approx
}
var EllipseNotFound = events.Ellipse{Confidence: 0.}
2021-10-12 21:05:10 +00:00
func (rd *RoadDetector) ComputeEllipsis(road *gocv.PointVector) *events.Ellipse {
if road.Size() < 5 {
2020-01-04 13:10:36 +00:00
return &EllipseNotFound
}
rotatedRect := gocv.FitEllipse(*road)
trust := rd.computeTrustFromCenter(&rotatedRect.Center)
2021-10-12 21:15:05 +00:00
zap.S().Debugf("Trust: %v", trust)
2020-01-04 13:10:36 +00:00
return &events.Ellipse{
Center: &events.Point{
X: int32(rotatedRect.Center.X),
Y: int32(rotatedRect.Center.Y),
},
Width: int32(rotatedRect.Width),
Height: int32(rotatedRect.Height),
Angle: float32(rotatedRect.Angle),
Confidence: rd.computeTrustFromCenter(&rotatedRect.Center),
}
}
func (rd *RoadDetector) computeTrustFromCenter(ellipsisCenter *image.Point) float32 {
safeMinX := 48
safeMaxX := 115
safeMinY := 69
safeMaxY := 119
if safeMinX <= ellipsisCenter.X && ellipsisCenter.X <= safeMaxX && safeMinY <= ellipsisCenter.Y && ellipsisCenter.Y <= safeMaxY {
return 1.0
}
if safeMinX <= ellipsisCenter.X && ellipsisCenter.X <= safeMaxX {
return rd.computeTrustOnAxis(safeMaxY, safeMinY, ellipsisCenter.Y)
}
if safeMinY <= ellipsisCenter.Y && ellipsisCenter.Y <= safeMaxY {
return rd.computeTrustOnAxis(safeMaxX, safeMinX, ellipsisCenter.X)
}
return rd.computeTrustOnAxis(safeMaxY, safeMinY, ellipsisCenter.Y) * rd.computeTrustOnAxis(safeMaxX, safeMinX, ellipsisCenter.X)
}
func (rd *RoadDetector) computeTrustOnAxis(safeMax, safeMin, value int) float32 {
trust := 1.
if value > safeMax {
trust = 1. / float64(value-safeMax)
} else if value < safeMin {
trust = 1. / float64(safeMin-value)
}
trust = trust * 10.
if trust > 0.9 {
trust = 0.9
}
if trust < 0. {
trust = 0.
}
return float32(trust)
}