av/turbidity/turbidity.go

172 lines
4.6 KiB
Go
Raw Normal View History

2021-12-15 06:55:14 +03:00
package main
import (
"errors"
2021-12-15 06:55:14 +03:00
"fmt"
2021-12-21 04:43:05 +03:00
"image"
"math"
2021-12-15 06:55:14 +03:00
"gocv.io/x/gocv"
)
// Turbidity Sensor.
2021-12-21 04:43:05 +03:00
type TurbiditySensor struct {
template, standard gocv.Mat
k1, k2 int //block size
alpha, scale float64
}
// Given a slice of test images, return the sharpness and contrast scores.
func (ts TurbiditySensor) Evaluate(imgs []gocv.Mat) (Results, error) {
2021-12-21 04:43:05 +03:00
var result Results
2021-12-23 09:10:35 +03:00
result.new(len(imgs))
2021-12-21 04:43:05 +03:00
for i := range imgs {
2021-12-21 04:43:05 +03:00
// Transform image.
marker, err := ts.Transform(imgs[i])
2021-12-21 04:43:05 +03:00
if err != nil {
return result, fmt.Errorf("Image %v: %w", i, err)
2021-12-21 04:43:05 +03:00
}
// Apply sobel filter.
2021-12-21 04:43:05 +03:00
edge := ts.Sobel(marker, ts.scale)
2021-12-23 09:10:35 +03:00
gocv.IMWrite("marker.jpg", marker)
gocv.IMWrite("edge.jpg", edge)
// Evaluate image.
2021-12-21 04:43:05 +03:00
out, err := ts.EvaluateImage(marker, edge, ts.k1, ts.k2, ts.alpha)
if err != nil {
return result, err
}
2021-12-23 09:10:35 +03:00
result.update(out[0], out[1], float64(i*10), i)
2021-12-21 04:43:05 +03:00
}
return result, nil
}
// Evaluate image sharpness and contrast using blocks of size k1 by k2. Return a slice of the respective scores.
2021-12-21 04:43:05 +03:00
func (ts TurbiditySensor) EvaluateImage(img, edge gocv.Mat, k1, k2 int, alpha float64) ([]float64, error) {
2021-12-15 06:55:14 +03:00
2021-12-21 04:43:05 +03:00
result := make([]float64, 2) // [0.0, 0.0]
2021-12-15 06:55:14 +03:00
2021-12-21 04:43:05 +03:00
if img.Rows()%k1 != 0 || img.Cols()%k2 != 0 {
return nil, fmt.Errorf("Dimensions not compatible (%v, %v)", k1, k2)
2021-12-21 04:43:05 +03:00
}
lStep := int(img.Rows() / k1)
kStep := int(img.Cols() / k2)
2021-12-21 04:43:05 +03:00
for l := 0; l < img.Rows(); l = l + lStep {
for k := 0; k < img.Cols(); k = k + kStep {
2021-12-15 06:55:14 +03:00
// Enhancement Measure Estimation (EME), provides a measure of the sharpness.
err := ts.EvaluateBlock(edge, l, k, l+lStep, k+kStep, result, "EME", alpha)
2021-12-21 04:43:05 +03:00
if err != nil {
return nil, err
2021-12-21 04:43:05 +03:00
}
// AMEE, provides a measure of the contrast.
err = ts.EvaluateBlock(img, l, k, l+lStep, k+kStep, result, "AMEE", alpha)
2021-12-21 04:43:05 +03:00
if err != nil {
return nil, err
2021-12-21 04:43:05 +03:00
}
}
2021-12-15 06:55:14 +03:00
}
2021-12-21 04:43:05 +03:00
// EME.
result[0] = 2.0 / (float64(k1) * float64(k2)) * result[0]
2021-12-21 04:43:05 +03:00
// AMEE.
result[1] = -1.0 / (float64(k1) * float64(k2)) * result[1]
2021-12-21 04:43:05 +03:00
return result, nil
}
// Evaluate a block within an image and add to to the result slice.
2021-12-21 04:43:05 +03:00
func (ts TurbiditySensor) EvaluateBlock(img gocv.Mat, l1, k1, l2, k2 int, result []float64, operation string, alpha float64) error {
max := -math.MaxFloat64
min := math.MaxFloat64
2021-12-21 04:43:05 +03:00
for i := l1; i < l2; i++ {
for j := k1; j < k2; j++ {
value := float64(img.GetUCharAt(i, j))
// Check max/min conditions, zero values are ignored.
2021-12-21 04:43:05 +03:00
if value > max && value != 0.0 {
max = value
}
if value < min && value != 0.0 {
min = value
}
}
}
// Blocks which have no information are ignored.
if max != -math.MaxFloat64 && min != math.MaxFloat64 && max != min {
2021-12-21 04:43:05 +03:00
if operation == "EME" {
result[0] += math.Log(max / min)
} else if operation == "AMEE" {
contrast := (max + min) / (max - min)
result[1] += math.Pow(alpha*(contrast), alpha) * math.Log(contrast)
} else {
return fmt.Errorf("Invalid operation: %v", operation)
}
}
return nil
}
// Search image for matching template. Returns the transformed image which best match the template.
2021-12-21 04:43:05 +03:00
func (ts TurbiditySensor) Transform(img gocv.Mat) (gocv.Mat, error) {
out := gocv.NewMat()
mask := gocv.NewMat()
corners_img := gocv.NewMat()
corners_template := gocv.NewMat()
// Find corners in image.
2021-12-23 09:10:35 +03:00
if !gocv.FindChessboardCorners(ts.standard, image.Pt(3, 3), &corners_img, gocv.CalibCBNormalizeImage) {
// Apply default if transformation fails.
2021-12-23 09:10:35 +03:00
fmt.Println("Corner detection failed applying standard transformation")
2021-12-21 04:43:05 +03:00
if !gocv.FindChessboardCorners(ts.standard, image.Pt(3, 3), &corners_img, gocv.CalibCBNormalizeImage) {
return out, errors.New("Could not find corners in default image")
2021-12-21 04:43:05 +03:00
}
}
// Find corners in template.
2021-12-21 04:43:05 +03:00
if !gocv.FindChessboardCorners(ts.template, image.Pt(3, 3), &corners_template, gocv.CalibCBNormalizeImage) {
return out, errors.New("Could not find corners in template")
2021-12-21 04:43:05 +03:00
}
// Find and apply transformation.
2021-12-21 04:43:05 +03:00
H := gocv.FindHomography(corners_img, &corners_template, gocv.HomograpyMethodRANSAC, 3.0, &mask, 2000, 0.995)
gocv.WarpPerspective(img, &out, H, image.Pt(ts.template.Rows(), ts.template.Cols()))
return out, nil
}
// Apply sobel filter to an image with a given scale and return the result.
2021-12-21 04:43:05 +03:00
func (ts TurbiditySensor) Sobel(img gocv.Mat, scale float64) gocv.Mat {
dx := gocv.NewMat()
dy := gocv.NewMat()
sobel := gocv.NewMat()
// Apply filter.
2021-12-21 04:43:05 +03:00
gocv.Sobel(img, &dx, gocv.MatTypeCV64F, 0, 1, 3, scale, 0.0, gocv.BorderConstant)
gocv.Sobel(img, &dy, gocv.MatTypeCV64F, 1, 0, 3, scale, 0.0, gocv.BorderConstant)
// Convert to unsigned.
2021-12-21 04:43:05 +03:00
gocv.ConvertScaleAbs(dx, &dx, 1.0, 0.0)
gocv.ConvertScaleAbs(dy, &dy, 1.0, 0.0)
// Add x and y components.
2021-12-21 04:43:05 +03:00
gocv.AddWeighted(dx, 0.5, dy, 0.5, 0, &sobel)
return sobel
2021-12-15 06:55:14 +03:00
}