-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathapp.js
214 lines (183 loc) · 5.47 KB
/
app.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
(function() {
var MODEL_PATH = 'model/mobilenet.json';
var CLASSES_PATH = 'model/mobilenet.classes.json';
var IMAGE_SIZE = 224; // Required by this model
var PREDICT_INTERVAL = 1000; // Delay between predictions in milliseconds
var MAX_CLASSES = 8;
var overlayDialog = document.querySelector('#overlay .dialog');
var camera = document.getElementById('camera');
var notification = new Audio('notification.ogg');
notification.preload = 'auto';
var model, modelClasses;
var lastCheck = null;
/**
* Update UI
* @param {Boolean} isHotdog Is hotdog
*/
function updateUI(isHotdog) {
if (lastCheck == isHotdog) return;
// Update result overlay
var result = document.querySelector('#overlay .result');
if (isHotdog) {
result.classList.remove('not-hotdog');
result.classList.add('hotdog');
result.innerHTML = 'Hotdog!';
// Play sound
notification.play();
} else {
result.classList.remove('hotdog');
result.classList.add('not-hotdog');
result.innerHTML = 'Not Hotdog!';
}
result.style.display = 'block';
// Update last state
lastCheck = isHotdog;
}
/**
* Get camera image
* @return {HTMLCanvasElement} Canvas with image
*/
function getCameraImage() {
var c = document.createElement('canvas');
c.width = IMAGE_SIZE;
c.height = IMAGE_SIZE;
c.getContext('2d').drawImage(camera, 0, 0, c.width, c.height);
return c;
}
/**
* Connect to camera
* @async
* @return {Promise} Callback
*/
function connectToCamera() {
return new Promise(function(resolve, reject) {
navigator.mediaDevices.getUserMedia({
video: {
facingMode: 'environment' // For phones, prefer main camera
}
}).then(function(stream) {
// Start rendering camera
var streamURL = window.URL.createObjectURL(stream);
try {
camera.srcObject = streamURL;
} catch (e) {
camera.src = streamURL;
}
// Resolve promise
resolve();
}).catch(reject);
});
}
/**
* Load model
* @async
* @return {Promise} Callback
*/
function loadModel() {
return new Promise(function(resolve, reject) {
tf.loadModel(MODEL_PATH).then(function(m) {
model = m;
fetch(CLASSES_PATH).then(function(res) {
res.json().then(function(json) {
modelClasses = json;
resolve();
});
});
}).catch(reject);
});
}
/**
* Start predicting
*/
function startPredicting() {
predict().then(function(isHotdog) {
// Update UI
if (isHotdog) console.log('Hotdog found!');
updateUI(isHotdog);
// Schedule next prediction
setTimeout(function() {
startPredicting();
}, PREDICT_INTERVAL);
});
}
/**
* Predict
* @async
* @return {Promise<Boolean>} Is hotdog
*/
function predict() {
return new Promise(function(resolve, reject) {
tf.tidy(function() {
// Get camera pixels and covert them to a tensor
var image = tf.fromPixels(getCameraImage()).toFloat();
// Change coordinates from [0,255] to [-1,1]
var offset = tf.scalar(127.5);
image = image.sub(offset).div(offset);
// Convert linear array to matrix
image = image.reshape([1, IMAGE_SIZE, IMAGE_SIZE, 3]);
// Make a prediction using loaded model
var logits = model.predict(image);
getTopClasses(logits, MAX_CLASSES).then(function(classes) {
console.log('Found classes', classes);
// Validate whether is Hotdog or Not Hotdog
var isHotdog = false;
for (var i=0; i<classes.length; i++) {
if (classes[i].name.indexOf('hotdog') > -1) {
isHotdog = true;
break;
}
}
resolve(isHotdog);
});
});
});
}
/**
* Get top classes
* @param {tf.Tensor} logits Logits returned by model
* @param {Integer} maxClasses Maximum number of classes to return
* @return {Promise<Array>} Top classes
*/
function getTopClasses(logits, maxClasses) {
return new Promise(function(resolve, reject) {
// Get raw data from logits
logits.data().then(function(values) {
// Sort data by value, while keeping index
var sortedData = [];
for (var i=0; i<values.length; i++) {
sortedData.push({value: values[i], index: i});
}
sortedData.sort(function(a, b) {
return b.value - a.value;
});
// Get top entries
var topValues = new Float32Array(maxClasses);
var topIndices = new Int32Array(maxClasses);
for (var i=0; i<maxClasses; i++) {
topValues[i] = sortedData[i].value;
topIndices[i] = sortedData[i].index;
}
// Get top classes name
var topClasses = [];
for (var i=0; i<topIndices.length; i++) {
topClasses.push({
name: modelClasses[topIndices[i]],
probability: topValues[i]
});
}
resolve(topClasses);
});
});
}
/* INITIALIZE */
connectToCamera().then(function() {
overlayDialog.innerHTML = 'Cargando modelo...';
loadModel().then(function() {
overlayDialog.style.display = 'none';
startPredicting();
});
}).catch(function() {
overlayDialog.innerHTML = 'Esta app necesita acceder a la cámara de ' +
'tu dispositivo para funcionar';
});
})();