Files
face/model/util.go
2024-07-06 09:33:36 +00:00

290 lines
9.1 KiB
Go

package model
import (
"bytes"
"encoding/binary"
"image"
"image/color"
"io"
"math"
"gocv.io/x/gocv"
)
func BytesToFloat32Slice(data []byte) ([]float32, error) {
t := []float32{}
// Create a buffer from the input data
buffer := bytes.NewReader(data)
for {
// Read the binary data from the buffer
var binaryValue uint32
err := binary.Read(buffer, binary.LittleEndian, &binaryValue)
if err != nil {
if err == io.EOF {
break
}
return nil, err
}
t = append(t, math.Float32frombits(binaryValue))
}
return t, nil
}
// Argmax return the index of the maximum value in a slice
func Argmax(slice []float32) int {
maxIndex := 0
maxValue := slice[0]
for i, value := range slice {
if value > maxValue {
maxIndex = i
maxValue = value
}
}
return maxIndex
}
// Translation represents the translation vector.
type Translation [2]float64
func WarpFaceByTranslation(visionFrame gocv.Mat, translation Translation, scale float64, cropSize image.Point) (gocv.Mat, gocv.Mat) {
affineMatrix := gocv.NewMatWithSize(2, 3, gocv.MatTypeCV64F)
affineMatrix.SetDoubleAt(0, 0, scale)
affineMatrix.SetDoubleAt(0, 1, 0.0)
affineMatrix.SetDoubleAt(0, 2, translation[0])
affineMatrix.SetDoubleAt(1, 0, 0.0)
affineMatrix.SetDoubleAt(1, 1, scale)
affineMatrix.SetDoubleAt(1, 2, translation[1])
cropVisionFrame := gocv.NewMat()
gocv.WarpAffine(visionFrame, &cropVisionFrame, affineMatrix, cropSize)
return cropVisionFrame, affineMatrix
}
func InvertAffineMatrix(affineMatrix gocv.Mat) gocv.Mat {
invertAffineMatrix := gocv.NewMatWithSize(2, 3, gocv.MatTypeCV64F)
gocv.InvertAffineTransform(affineMatrix, &invertAffineMatrix)
return invertAffineMatrix
}
// CalculateMean2f calculates the mean of a slice of gocv.Point2f
func CalculateMean2f(points []gocv.Point2f) gocv.Point2f {
if len(points) == 0 {
return gocv.Point2f{}
}
var sumX, sumY float32
for _, point := range points {
sumX += point.X
sumY += point.Y
}
meanX := sumX / float32(len(points))
meanY := sumY / float32(len(points))
return gocv.Point2f{X: meanX, Y: meanY}
}
// Size represents the width and height dimensions.
type Size struct {
Width, Height int
}
func WarpFaceByFaceLandmark5(visionFrame gocv.Mat, faceLandmark5 []gocv.Point2f, warpTemplate []gocv.Point2f, cropSize Size) (gocv.Mat, gocv.Mat) {
affineMatrix := estimateMatrixByFaceLandmark5(faceLandmark5, warpTemplate, cropSize)
cropVisionFrame := gocv.NewMat()
gocv.WarpAffineWithParams(
visionFrame,
&cropVisionFrame,
affineMatrix,
image.Pt(cropSize.Width, cropSize.Height),
gocv.InterpolationArea,
gocv.BorderReplicate,
color.RGBA{},
)
return cropVisionFrame, affineMatrix
}
func estimateMatrixByFaceLandmark5(faceLandmark5 []gocv.Point2f, warpTemplate []gocv.Point2f, cropSize Size) gocv.Mat {
normedWarpTemplate := normalizeWarpTemplate(warpTemplate, cropSize)
pvsrc := gocv.NewPoint2fVectorFromPoints(faceLandmark5)
pvdst := gocv.NewPoint2fVectorFromPoints(normedWarpTemplate)
inliers := gocv.NewMat()
defer inliers.Close()
method := 8
ransacProjThreshold := 100.0
maxiters := uint(2000)
confidence := 0.99
refineIters := uint(10)
// https://docs.opencv.org/4.x/d9/d0c/group__calib3d.html#gad767faff73e9cbd8b9d92b955b50062d
affineMatrix := gocv.EstimateAffinePartial2DWithParams(pvsrc, pvdst, inliers, method, ransacProjThreshold, maxiters, confidence, refineIters)
return affineMatrix
}
// normalizeWarpTemplate scales the warp template according to the crop size.
func normalizeWarpTemplate(warpTemplate []gocv.Point2f, cropSize Size) []gocv.Point2f {
normedWarpTemplate := make([]gocv.Point2f, len(warpTemplate))
for i, pt := range warpTemplate {
normedWarpTemplate[i] = gocv.Point2f{
X: pt.X * float32(cropSize.Width),
Y: pt.Y * float32(cropSize.Height),
}
}
return normedWarpTemplate
}
// MatSubtract subtract value v fom mat
func MatSubtract(mat gocv.Mat, v float64) {
constantValue := float64(v)
constantMat := gocv.NewMatWithSizeFromScalar(gocv.NewScalar(constantValue, constantValue, constantValue, 0), mat.Rows(), mat.Cols(), mat.Type())
defer constantMat.Close()
gocv.Subtract(mat, constantMat, &mat)
}
// ClipMat clips the values of a gocv.Mat within a specified range.
// mat need to be float32 type
func ClipMat(mat gocv.Mat, minVal, maxVal float32) {
for row := 0; row < mat.Rows(); row++ {
for col := 0; col < mat.Cols(); col++ {
value := mat.GetFloatAt(row, col)
if value < minVal {
value = minVal
} else if value > maxVal {
value = maxVal
}
mat.SetFloatAt(row, col, value)
}
}
}
// Padding represents the padding values for the mask.
type Padding struct {
Top, Right, Bottom, Left float64
}
// CreateStaticBoxMask create a static box mask with specified size, blur, and padding.
func CreateStaticBoxMask(cropSize Size, faceMaskBlur float64, faceMaskPadding Padding) gocv.Mat {
blurAmount := int(float64(cropSize.Width) * 0.5 * faceMaskBlur)
blurArea := int(math.Max(float64(blurAmount/2), 1))
// Create a box mask initialized to ones.
boxMask := gocv.NewMatWithSize(cropSize.Height, cropSize.Width, gocv.MatTypeCV32F)
boxMask.SetTo(gocv.NewScalar(1.0, 1.0, 1.0, 1.0)) // Fill the entire matrix with ones.
// Calculate padding values.
padTop := int(math.Max(float64(blurArea), float64(cropSize.Height)*faceMaskPadding.Top/100))
padBottom := int(math.Max(float64(blurArea), float64(cropSize.Height)*faceMaskPadding.Bottom/100))
padLeft := int(math.Max(float64(blurArea), float64(cropSize.Width)*faceMaskPadding.Left/100))
padRight := int(math.Max(float64(blurArea), float64(cropSize.Width)*faceMaskPadding.Right/100))
// Set padding areas to zero.
topRegion := boxMask.Region(image.Rect(0, 0, cropSize.Width, padTop))
defer topRegion.Close()
topRegion.SetTo(gocv.NewScalar(0, 0, 0, 0))
bottomRegion := boxMask.Region(image.Rect(0, cropSize.Height-padBottom, cropSize.Width, cropSize.Height))
defer bottomRegion.Close()
bottomRegion.SetTo(gocv.NewScalar(0, 0, 0, 0))
leftRegion := boxMask.Region(image.Rect(0, 0, padLeft, cropSize.Height))
defer leftRegion.Close()
leftRegion.SetTo(gocv.NewScalar(0, 0, 0, 0))
rightRegion := boxMask.Region(image.Rect(cropSize.Width-padRight, 0, cropSize.Width, cropSize.Height))
defer rightRegion.Close()
rightRegion.SetTo(gocv.NewScalar(0, 0, 0, 0))
// Apply Gaussian blur if required.
if blurAmount > 0 {
gocv.GaussianBlur(boxMask, &boxMask, image.Point{0, 0}, float64(blurAmount)*0.25, 0, gocv.BorderDefault)
}
return boxMask
}
// ReduceMinimum finds the element-wise minimum of a list of gocv.Mat
func ReduceMinimum(mats []gocv.Mat) gocv.Mat {
if len(mats) == 0 {
return gocv.NewMat()
}
// Start with the first matrix as the initial minimum
minMat := mats[0].Clone()
rows, cols := minMat.Rows(), minMat.Cols()
// Iterate over the remaining matrices
for i := 1; i < len(mats); i++ {
for row := 0; row < rows; row++ {
for col := 0; col < cols; col++ {
currentMin := minMat.GetFloatAt(row, col)
newValue := mats[i].GetFloatAt(row, col)
// Update the minimum value
if newValue < currentMin {
minMat.SetFloatAt(row, col, newValue)
}
}
}
}
return minMat
}
// PasteBack paste cropVisionFrame back to targetVisionFrame
func PasteBack(targetVisionFrame gocv.Mat, cropVisionFrame gocv.Mat, cropMask gocv.Mat, affineMatrix gocv.Mat) gocv.Mat {
inverseMatrix := InvertAffineMatrix(affineMatrix)
defer inverseMatrix.Close()
tempSize := image.Pt(targetVisionFrame.Cols(), targetVisionFrame.Rows())
cropVisionFrame.ConvertTo(&cropVisionFrame, gocv.MatTypeCV64F)
inverseVisionFrame := getInverseVisionFrame(cropVisionFrame, inverseMatrix, tempSize)
defer inverseVisionFrame.Close()
inverseMask := getInverseMask(inverseMatrix, cropMask, tempSize)
defer inverseMask.Close()
inverseMaskData, _ := inverseMask.DataPtrFloat64()
inverseVisionFrameData, _ := inverseVisionFrame.DataPtrFloat64()
data, _ := targetVisionFrame.DataPtrUint8()
d := make([]uint8, len(data))
j := 0
for i := 0; i < len(data); i += 3 {
inverseM := inverseMaskData[j]
d[i] = uint8(inverseM*inverseVisionFrameData[i] + (1-inverseM)*float64(data[i]))
d[i+1] = uint8(inverseM*inverseVisionFrameData[i+1] + (1-inverseM)*float64(data[i+1]))
d[i+2] = uint8(inverseM*inverseVisionFrameData[i+2] + (1-inverseM)*float64(data[i+2]))
j++
}
mat, _ := gocv.NewMatFromBytes(targetVisionFrame.Rows(), targetVisionFrame.Cols(), gocv.MatTypeCV8UC3, d)
return mat
}
func getInverseMask(inverseMatrix gocv.Mat, cropMask gocv.Mat, tempSize image.Point) gocv.Mat {
inverseMask := gocv.NewMat()
gocv.WarpAffine(
cropMask,
&inverseMask,
inverseMatrix,
tempSize,
)
inverseMask.ConvertTo(&inverseMask, gocv.MatTypeCV32F)
ClipMat(inverseMask, 0, 1)
inverseMask.ConvertTo(&inverseMask, gocv.MatTypeCV64F)
return inverseMask
}
func getInverseVisionFrame(cropVisionFrame gocv.Mat, inverseMatrix gocv.Mat, tempSize image.Point) gocv.Mat {
inverseVisionFrame := gocv.NewMat()
cropVisionFrame.ConvertTo(&cropVisionFrame, gocv.MatTypeCV64F)
gocv.WarpAffineWithParams(cropVisionFrame, &inverseVisionFrame, inverseMatrix, tempSize, gocv.InterpolationLinear, gocv.BorderReplicate, color.RGBA{})
inverseVisionFrame.ConvertTo(&inverseVisionFrame, gocv.MatTypeCV64F)
return inverseVisionFrame
}