Skip to content

Commit 4f45297

Browse files
added test script to validate model accuracy on fddb benchmark
1 parent a97cd22 commit 4f45297

File tree

7 files changed

+182
-44
lines changed

7 files changed

+182
-44
lines changed

tools/train/serveTinyYolov2.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,12 @@ app.use(express.static(path.join(__dirname, '../../weights')))
1515
app.use(express.static(path.join(__dirname, '../../dist')))
1616

1717
const trainDataPath = path.resolve(process.env.TRAIN_DATA_PATH)
18+
const testDataPath = path.resolve(process.env.TEST_DATA_PATH)
1819
const imagesPath = path.join(trainDataPath, './final_images')
1920
const detectionsPath = path.join(trainDataPath, './final_detections')
2021
app.use(express.static(imagesPath))
2122
app.use(express.static(detectionsPath))
23+
app.use(express.static(testDataPath))
2224

2325
const detectionFilenames = fs.readdirSync(detectionsPath)
2426
const detectionFilenamesMultibox = JSON.parse(fs.readFileSync(path.join(__dirname, './tinyYolov2/multibox.json')))
@@ -29,5 +31,6 @@ app.get('/detection_filenames', (req, res) => res.status(202).send(detectionFile
2931
app.get('/detection_filenames_multibox', (req, res) => res.status(202).send(detectionFilenamesMultibox))
3032
app.get('/', (req, res) => res.sendFile(path.join(publicDir, 'train.html')))
3133
app.get('/verify', (req, res) => res.sendFile(path.join(publicDir, 'verify.html')))
34+
app.get('/test', (req, res) => res.sendFile(path.join(publicDir, 'test.html')))
3235

3336
app.listen(3000, () => console.log('Listening on port 3000!'))

tools/train/shared/trainUtils.js

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
const log = (str, ...args) => console.log(`[${[(new Date()).toTimeString().substr(0, 8)]}] ${str || ''}`, ...args)
2+
13
async function promiseSequential(promises) {
24
const curr = promises[0]
35
if (!curr) {
@@ -29,4 +31,31 @@ function saveWeights(net, filename = 'train_tmp') {
2931
saveAs(new Blob([binaryWeights]), filename)
3032
}
3133

32-
const log = (str, ...args) => console.log(`[${[(new Date()).toTimeString().substr(0, 8)]}] ${str || ''}`, ...args)
34+
function imageToSquare(img) {
35+
const scale = 608 / Math.max(img.height, img.width)
36+
const width = scale * img.width
37+
const height = scale * img.height
38+
39+
const canvas1 = faceapi.createCanvasFromMedia(img)
40+
const targetCanvas = faceapi.createCanvas({ width: 608, height: 608 })
41+
targetCanvas.getContext('2d').putImageData(canvas1.getContext('2d').getImageData(0, 0, width, height), 0, 0)
42+
return targetCanvas
43+
}
44+
45+
function getPaddingsAndReshapedSize(img, inputSize) {
46+
const [h, w] = [img.height, img.width]
47+
const maxDim = Math.max(h, w)
48+
49+
const f = inputSize / maxDim
50+
const reshapedImgDims = {
51+
height: Math.floor(h * f),
52+
width: Math.floor(w * f)
53+
}
54+
55+
const paddings = new faceapi.Point(
56+
maxDim / img.width,
57+
maxDim / img.height
58+
)
59+
60+
return { paddings, reshapedImgDims }
61+
}

tools/train/tinyYolov2/loss.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ function computeBoxAdjustments(groundTruthBoxes, reshapedImgDims) {
102102

103103
const dx = inverseSigmoid(dCenterX / CELL_SIZE)
104104
const dy = inverseSigmoid(dCenterY / CELL_SIZE)
105+
//const dx = dCenterX / CELL_SIZE
106+
//const dy = dCenterY / CELL_SIZE
105107
const dw = Math.log((width / CELL_SIZE) / getAnchors()[anchor].x)
106108
const dh = Math.log((height / CELL_SIZE) / getAnchors()[anchor].y)
107109

tools/train/tinyYolov2/test.html

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<script src="face-api.js"></script>
5+
<script src="commons.js"></script>
6+
<script src="trainUtils.js"></script>
7+
<script src="loss.js"></script>
8+
<script src="FileSaver.js"></script>
9+
<link rel="stylesheet" href="styles.css">
10+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.100.2/css/materialize.css">
11+
<script type="text/javascript" src="https://code.jquery.com/jquery-2.1.1.min.js"></script>
12+
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.100.2/js/materialize.min.js"></script>
13+
</head>
14+
<body>
15+
<script>
16+
window.tf = faceapi.tf
17+
18+
// hyper parameters
19+
window.objectScale = 5
20+
window.noObjectScale = 1
21+
window.coordScale = 1
22+
23+
const weightsUrl = `/tmp/tmp__224_35060__320_41188__416_31050__608_16520.weights`
24+
//const inputSizes = [160, 224, 320, 416]
25+
const inputSizes = [512, 608]
26+
27+
async function loadNetWeights(uri) {
28+
return new Float32Array(await (await fetch(uri)).arrayBuffer())
29+
}
30+
31+
async function fetchFddbJson() {
32+
return fetch('/fddb-detections.json').then(res => res.json())
33+
}
34+
35+
async function run() {
36+
window.fddbJson = await fetchFddbJson()
37+
38+
const weights = await loadNetWeights(weightsUrl)
39+
window.net = new faceapi.TinyYolov2(true)
40+
await window.net.load(weights)
41+
42+
await promiseSequential(inputSizes.map(inputSize => async () => {
43+
await promiseSequential(window.fddbJson.map(({ filePath, rects }) => () => {
44+
return test(filePath, rects, inputSize)
45+
}))
46+
47+
const losses = Object.keys(window.lossMap[inputSize]).map(k => window.lossMap[inputSize][k])
48+
49+
const totalLoss = losses
50+
.map(l => l.totalLoss)
51+
.reduce((sum, l) => sum + l)
52+
const avgLoss = totalLoss / losses.length
53+
54+
log(`totalLoss (${inputSize}): ${totalLoss}`)
55+
log(`avgLoss (${inputSize}): ${avgLoss}`)
56+
57+
window.losses = window.losses || {}
58+
window.losses[inputSize] = { totalLoss, avgLoss }
59+
}))
60+
61+
console.log(window.losses)
62+
}
63+
64+
async function test(fileUri, rects, inputSize) {
65+
const img = await faceapi.bufferToImage(await fetchImage(fileUri))
66+
67+
const groundTruthBoxes = rects
68+
.map(({ x, y, width, height }) => new faceapi.Rect(x, y, width, height))
69+
.map(rect => rect.clipAtImageBorders(img.width, img.height))
70+
.map(({ x, y, width, height }) => ({
71+
x: x / img.width,
72+
y: y / img.height,
73+
width: width / img.width,
74+
height: height / img.height,
75+
}))
76+
77+
const { reshapedImgDims, paddings } = getPaddingsAndReshapedSize(img, inputSize)
78+
const squareImg = imageToSquare(img)
79+
80+
const netInput = (await faceapi.toNetInput(squareImg)).managed()
81+
82+
const losses = tf.tidy(() => {
83+
const outTensor = window.net.forwardInput(netInput, inputSize)
84+
85+
const {
86+
noObjectLoss,
87+
objectLoss,
88+
coordLoss,
89+
totalLoss
90+
} = computeLoss(
91+
outTensor,
92+
groundTruthBoxes,
93+
reshapedImgDims,
94+
paddings
95+
)
96+
97+
const losses = {
98+
totalLoss: totalLoss.dataSync()[0],
99+
noObjectLoss: noObjectLoss.dataSync()[0],
100+
objectLoss: objectLoss.dataSync()[0],
101+
coordLoss: coordLoss.dataSync()[0]
102+
}
103+
104+
return losses
105+
})
106+
107+
log(`${fileUri}:`)
108+
log(`ground truth boxes: ${groundTruthBoxes.length}`)
109+
log(`noObjectLoss: ${losses.noObjectLoss}`)
110+
log(`objectLoss: ${losses.objectLoss}`)
111+
log(`coordLoss: ${losses.coordLoss}`)
112+
log(`totalLoss: ${losses.totalLoss}`)
113+
114+
if (Object.keys(losses).map(k => losses[k]).some(loss => isNaN(loss) || loss === Infinity)) {
115+
console.log(groundTruthBoxes)
116+
console.log(img)
117+
console.log(losses)
118+
throw new Error('corrupted loss value')
119+
}
120+
window.lossMap = window.lossMap || {}
121+
window.lossMap[inputSize] = window.lossMap[inputSize] || {}
122+
window.lossMap[inputSize][fileUri] = losses
123+
}
124+
125+
$(document).ready(function() {
126+
run()
127+
})
128+
</script>
129+
</body>
130+
</html>

tools/train/tinyYolov2/train.html

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,25 @@
1717
<script>
1818
tf = faceapi.tf
1919

20-
const startIdx224 = 35060
21-
const startIdx320 = 41188
22-
const startIdx416 = 31050
20+
const startIdx160 = 26600
21+
const startIdx224 = 61660
22+
const startIdx320 = 67788
23+
const startIdx416 = 57650
2324
const startIdx608 = 16520
2425

25-
const weightsUrl = `/tmp/tmp__224_${startIdx224}__320_${startIdx320}__416_${startIdx416}__608_${startIdx608}.weights`
26+
//const weightsUrl = `/tmp/tmp__160_${startIdx160}__224_${startIdx224}__320_${startIdx320}__416_${startIdx416}__608_${startIdx608}.weights`
27+
const weightsUrl = `/tmp/tmp_multiscale_count_8700.weights`
2628

2729
const fromEpoch = 0
2830

2931
const trainOnlyMultibox = false
3032

31-
const trainSizes = [160, 224, 320, 416]
33+
const trainSizes = [416, 512, 608]
3234
//const trainSizes = [608]
3335

3436
window.debug = false
3537
window.logTrainSteps = true
38+
window.count = 0
3639

3740

3841
// hyper parameters
@@ -85,7 +88,6 @@
8588
log(`${lossType} : ${faceapi.round(currentLoss[lossType])} (avg: ${faceapi.round(currentLoss[lossType] / detectionFilenames.length)}) (delta: ${currentLoss[lossType] - prevLoss[lossType]})`)
8689
}
8790

88-
window.count = 0
8991

9092
function onBatchProcessed(dataIdx, inputSize) {
9193
window.count++

tools/train/tinyYolov2/train.js

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -47,35 +47,6 @@ function minimize(groundTruthBoxes, batchInput, inputSize, batch, { reshapedImgD
4747
}, true)
4848
}
4949

50-
function imageToSquare(img) {
51-
const scale = 608 / Math.max(img.height, img.width)
52-
const width = scale * img.width
53-
const height = scale * img.height
54-
55-
const canvas1 = faceapi.createCanvasFromMedia(img)
56-
const targetCanvas = faceapi.createCanvas({ width: 608, height: 608 })
57-
targetCanvas.getContext('2d').putImageData(canvas1.getContext('2d').getImageData(0, 0, width, height), 0, 0)
58-
return targetCanvas
59-
}
60-
61-
function getPaddingsAndReshapedSize(img, inputSize) {
62-
const [h, w] = [img.height, img.width]
63-
const maxDim = Math.max(h, w)
64-
65-
const f = inputSize / maxDim
66-
const reshapedImgDims = {
67-
height: Math.floor(h * f),
68-
width: Math.floor(w * f)
69-
}
70-
71-
const paddings = new faceapi.Point(
72-
maxDim / img.width,
73-
maxDim / img.height
74-
)
75-
76-
return { paddings, reshapedImgDims }
77-
}
78-
7950
async function trainStep(batchCreators, inputSizes, rescaleEveryNthBatch, onBatchProcessed = () => {}) {
8051

8152
async function step(currentBatchCreators) {

tools/train/tinyYolov2/verify.html

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,12 @@
3434
<div class="row side-by-side">
3535
<div class="row input-field" style="margin-right: 20px;">
3636
<select id="sizeType">
37-
<option value="" disabled selected>Input Size:</option>
38-
<option value="xs">XS: 224 x 224</option>
39-
<option value="sm">SM: 320 x 320</option>
40-
<option value="md">MD: 416 x 416</option>
41-
<option value="lg">LG: 608 x 608</option>
37+
<option value="128">128 x 128</option>
38+
<option value="160">160 x 160</option>
39+
<option value="224">224 x 224</option>
40+
<option value="320">320 x 320</option>
41+
<option value="416">416 x 416</option>
42+
<option value="608">608 x 608</option>
4243
</select>
4344
<label>Input Size</label>
4445
</div>
@@ -67,7 +68,7 @@
6768

6869
<script>
6970
let scoreThreshold = 0.5
70-
let sizeType = 'sm'
71+
let sizeType = 608
7172

7273
function onKeyDown(e) {
7374
e.target.value = (
@@ -113,7 +114,7 @@
113114
canvas.height = height
114115

115116
const forwardParams = {
116-
inputSize: sizeType,
117+
inputSize: parseInt(sizeType),
117118
scoreThreshold
118119
}
119120

@@ -145,7 +146,7 @@
145146
const startIdx608 = 16520
146147

147148
//const weightsUrl = `/tmp/tmp__224_${startIdx224}__320_${startIdx320}__416_${startIdx416}__608_${startIdx608}.weights`
148-
const weightsUrl = `/tmp/overfit_count_1500.weights`
149+
const weightsUrl = `/tmp/tmp_multiscale_count_4200.weights`
149150

150151
const weights = await loadNetWeights(weightsUrl)
151152
window.net = new faceapi.TinyYolov2(true)

0 commit comments

Comments
 (0)