Files
pigo/core/puploc.go
2022-11-24 11:13:59 +02:00

285 lines
8.4 KiB
Go

package pigo
import (
"encoding/binary"
"math"
"math/rand"
"sort"
"sync"
"unsafe"
)
// Puploc contains all the information resulted from the pupil detection
// needed for accessing from a global scope.
type Puploc struct {
Row int
Col int
Scale float32
Perturbs int
}
// PuplocCascade is a general struct for storing
// the cascade tree values encoded into the binary file.
type PuplocCascade struct {
treeCodes []int8
treePreds []float32
scales float32
stages uint32
trees uint32
treeDepth uint32
}
// NewPuplocCascade initializes the PuplocCascade constructor method.
func NewPuplocCascade() *PuplocCascade {
return &PuplocCascade{}
}
// UnpackCascade unpacks the pupil localization cascade file.
func (plc *PuplocCascade) UnpackCascade(packet []byte) (*PuplocCascade, error) {
var (
stages uint32
scales float32
trees uint32
treeDepth uint32
treeCodes = make([]int8, 0, 409200)
treePreds = make([]float32, 0, 204800)
)
pos := 0
// Get the number of stages as 32-bit unsigned integer.
stages = binary.LittleEndian.Uint32(packet[pos:])
pos += 4
// Obtain the scale multiplier (applied after each stage).
u32scales := binary.LittleEndian.Uint32(packet[pos:])
// Convert uint32 to float32
scales = *(*float32)(unsafe.Pointer(&u32scales))
pos += 4
// Obtain the number of trees per stage.
trees = binary.LittleEndian.Uint32(packet[pos:])
pos += 4
// Obtain the depth of each tree.
treeDepth = binary.LittleEndian.Uint32(packet[pos:])
pos += 4
// Traverse all the stages of the binary tree.
for s := 0; s < int(stages); s++ {
// Traverse the branches of each stage.
for t := 0; t < int(trees); t++ {
depth := int(pow(2, int(treeDepth)))
code := packet[pos : pos+4*depth-4]
// Convert unsigned bytecodes to signed ones.
i8code := *(*[]int8)(unsafe.Pointer(&code))
treeCodes = append(treeCodes, i8code...)
pos += 4*depth - 4
// Read prediction from tree's leaf nodes.
for i := 0; i < depth; i++ {
for l := 0; l < 2; l++ {
u32pred := binary.LittleEndian.Uint32(packet[pos:])
// Convert uint32 to float32
f32pred := *(*float32)(unsafe.Pointer(&u32pred))
treePreds = append(treePreds, f32pred)
pos += 4
}
}
}
}
return &PuplocCascade{
stages: stages,
scales: scales,
trees: trees,
treeDepth: treeDepth,
treeCodes: treeCodes,
treePreds: treePreds,
}, nil
}
// classifyRegion applies the face classification function over an image.
func (plc *PuplocCascade) classifyRegion(r, c, s float32, treeDepth, nrows, ncols int, pixels []uint8, dim int, flipV bool) []float32 {
var (
c1, c2 int
root int
)
for i := 0; i < int(plc.stages); i++ {
var dr, dc float32 = 0.0, 0.0
for j := 0; j < int(plc.trees); j++ {
idx := 0
for k := 0; k < int(plc.treeDepth); k++ {
r1 := min(nrows-1, max(0, (256*int(r)+int(plc.treeCodes[root+4*idx+0])*int(math.Round(float64(s))))>>8))
r2 := min(nrows-1, max(0, (256*int(r)+int(plc.treeCodes[root+4*idx+2])*int(math.Round(float64(s))))>>8))
// flipV means that we wish to flip the column coordinates sign in the tree nodes.
// This is required at running the facial landmark detector over the right side of the detected face.
if flipV {
c1 = min(ncols-1, max(0, (256*int(c)+int(-plc.treeCodes[root+4*idx+1])*int(math.Round(float64(s))))>>8))
c2 = min(ncols-1, max(0, (256*int(c)+int(-plc.treeCodes[root+4*idx+3])*int(math.Round(float64(s))))>>8))
} else {
c1 = min(ncols-1, max(0, (256*int(c)+int(plc.treeCodes[root+4*idx+1])*int(math.Round(float64(s))))>>8))
c2 = min(ncols-1, max(0, (256*int(c)+int(plc.treeCodes[root+4*idx+3])*int(math.Round(float64(s))))>>8))
}
bintest := func(p1, p2 uint8) uint8 {
if p1 > p2 {
return 1
}
return 0
}
idx = 2*idx + 1 + int(bintest(pixels[r1*dim+c1], pixels[r2*dim+c2]))
}
lutIdx := 2 * (int(plc.trees)*treeDepth*i + treeDepth*j + idx - (treeDepth - 1))
dr += plc.treePreds[lutIdx+0]
if flipV {
dc += -plc.treePreds[lutIdx+1]
} else {
dc += plc.treePreds[lutIdx+1]
}
root += 4*treeDepth - 4
}
r += dr * s
c += dc * s
s *= plc.scales
}
return []float32{r, c, s}
}
// classifyRotatedRegion applies the face classification function over a rotated image.
func (plc *PuplocCascade) classifyRotatedRegion(r, c, s float32, a float64, treeDepth, nrows, ncols int, pixels []uint8, dim int, flipV bool) []float32 {
var (
row1, col1, row2, col2 int
root int
)
qCosTable := []float32{256, 251, 236, 212, 181, 142, 97, 49, 0, -49, -97, -142, -181, -212, -236, -251, -256, -251, -236, -212, -181, -142, -97, -49, 0, 49, 97, 142, 181, 212, 236, 251, 256}
qSinTable := []float32{0, 49, 97, 142, 181, 212, 236, 251, 256, 251, 236, 212, 181, 142, 97, 49, 0, -49, -97, -142, -181, -212, -236, -251, -256, -251, -236, -212, -181, -142, -97, -49, 0}
qsin := s * qSinTable[int(32.0*a)] //s*(256.0*math.Sin(2*math.Pi*a))
qcos := s * qCosTable[int(32.0*a)] //s*(256.0*math.Cos(2*math.Pi*a))
for i := 0; i < int(plc.stages); i++ {
var dr, dc float32 = 0.0, 0.0
for j := 0; j < int(plc.trees); j++ {
idx := 0
for k := 0; k < int(plc.treeDepth); k++ {
row1 = int(plc.treeCodes[root+4*idx+0])
row2 = int(plc.treeCodes[root+4*idx+2])
// flipV means that we wish to flip the column coordinates sign in the tree nodes.
// This is required at running the facial landmark detector over the right side of the detected face.
if flipV {
col1 = int(-plc.treeCodes[root+4*idx+1])
col2 = int(-plc.treeCodes[root+4*idx+3])
} else {
col1 = int(plc.treeCodes[root+4*idx+1])
col2 = int(plc.treeCodes[root+4*idx+3])
}
r1 := min(nrows-1, max(0, 65536*int(r)+int(qcos)*row1-int(qsin)*col1)>>16)
c1 := min(ncols-1, max(0, 65536*int(c)+int(qsin)*row1+int(qcos)*col1)>>16)
r2 := min(nrows-1, max(0, 65536*int(r)+int(qcos)*row2-int(qsin)*col2)>>16)
c2 := min(ncols-1, max(0, 65536*int(c)+int(qsin)*row2+int(qcos)*col2)>>16)
bintest := func(px1, px2 uint8) int {
if px1 <= px2 {
return 1
}
return 0
}
idx = 2*idx + 1 + bintest(pixels[r1*dim+c1], pixels[r2*dim+c2])
}
lutIdx := 2 * (int(plc.trees)*treeDepth*i + treeDepth*j + idx - (treeDepth - 1))
dr += plc.treePreds[lutIdx+0]
if flipV {
dc += -plc.treePreds[lutIdx+1]
} else {
dc += plc.treePreds[lutIdx+1]
}
root += 4*treeDepth - 4
}
r += dr * s
c += dc * s
s *= plc.scales
}
return []float32{r, c, s}
}
// puplocPool is a struct for holding the pupil localization values in sync pool.
type puplocPool struct {
rows []float32
cols []float32
scale []float32
}
// Create a sync.Pool for further reusing the allocated memory space
// in order to keep the GC overhead as low as possible.
var plcPool = sync.Pool{
New: func() interface{} {
return &puplocPool{
rows: make([]float32, 63),
cols: make([]float32, 63),
scale: make([]float32, 63),
}
},
}
// RunDetector runs the pupil localization function.
func (plc *PuplocCascade) RunDetector(pl Puploc, img ImageParams, angle float64, flipV bool) *Puploc {
var res []float32
det := plcPool.Get().(*puplocPool)
defer plcPool.Put(det)
treeDepth := int(pow(2, int(plc.treeDepth)))
for i := 0; i < pl.Perturbs; i++ {
row := float32(pl.Row) + float32(pl.Scale)*0.15*(0.5-rand.Float32())
col := float32(pl.Col) + float32(pl.Scale)*0.15*(0.5-rand.Float32())
sc := float32(pl.Scale) * (0.925 + 0.15*rand.Float32())
if angle > 0.0 {
if angle > 1.0 {
angle = 1.0
}
res = plc.classifyRotatedRegion(row, col, sc, angle, treeDepth, img.Rows, img.Cols, img.Pixels, img.Dim, flipV)
} else {
res = plc.classifyRegion(row, col, sc, treeDepth, img.Rows, img.Cols, img.Pixels, img.Dim, flipV)
}
det.rows[i] = res[0]
det.cols[i] = res[1]
det.scale[i] = res[2]
}
// Sorting the perturbations in ascendent order
sort.Sort(plocSort(det.rows))
sort.Sort(plocSort(det.cols))
sort.Sort(plocSort(det.scale))
// Get the median value of the sorted perturbation results
return &Puploc{
Row: int(det.rows[int(math.Round(float64(pl.Perturbs)/2))]),
Col: int(det.cols[int(math.Round(float64(pl.Perturbs)/2))]),
Scale: det.scale[int(math.Round(float64(pl.Perturbs)/2))],
}
}
// Implement custom sorting function on detection values.
type plocSort []float32
func (q plocSort) Len() int { return len(q) }
func (q plocSort) Less(i, j int) bool { return q[i] < q[j] }
func (q plocSort) Swap(i, j int) { q[i], q[j] = q[j], q[i] }