mirror of
https://github.com/esimov/pigo.git
synced 2025-09-26 20:21:28 +08:00
285 lines
8.4 KiB
Go
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] }
|