mirror of
https://github.com/dev6699/face.git
synced 2025-09-27 05:26:01 +08:00
290 lines
9.1 KiB
Go
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
|
|
}
|