Spaces:
Build error
Build error
Create extensions/scratch3_facemesh2scratch/index.js
Browse files
local-scratch-vm/src/extensions/scratch3_facemesh2scratch/index.js
ADDED
@@ -0,0 +1,354 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
const ArgumentType = require('../../extension-support/argument-type');
|
2 |
+
const BlockType = require('../../extension-support/block-type');
|
3 |
+
const Cast = require('../../util/cast');
|
4 |
+
const formatMessage = require('format-message');
|
5 |
+
const ml5 = require('ml5');
|
6 |
+
|
7 |
+
const blockIconURI = 'data:image/png;base64,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';
|
8 |
+
|
9 |
+
const Message = {
|
10 |
+
getX: {
|
11 |
+
'ja': '[PERSON_NUMBER] 人目の [KEYPOINT] 番目の部位のx座標',
|
12 |
+
'ja-Hira': '[PERSON_NUMBER] にんめの [KEYPOINT] ばんめのぶいのxざひょう',
|
13 |
+
'en': 'x of person no: [PERSON_NUMBER] , keypoint no: [KEYPOINT]'
|
14 |
+
},
|
15 |
+
getY: {
|
16 |
+
'ja': '[PERSON_NUMBER] 人目の [KEYPOINT] 番目の部位のy座標',
|
17 |
+
'ja-Hira': '[PERSON_NUMBER] にんめの [KEYPOINT] ばんめのぶいのyざひょう',
|
18 |
+
'en': 'y of person no: [PERSON_NUMBER] , keypoint no: [KEYPOINT]'
|
19 |
+
},
|
20 |
+
peopleCount: {
|
21 |
+
'ja': '人数',
|
22 |
+
'ja-Hira': 'にんずう',
|
23 |
+
'en': 'people count'
|
24 |
+
},
|
25 |
+
videoToggle: {
|
26 |
+
'ja': 'ビデオを [VIDEO_STATE] にする',
|
27 |
+
'ja-Hira': 'ビデオを [VIDEO_STATE] にする',
|
28 |
+
'en': 'turn video [VIDEO_STATE]'
|
29 |
+
},
|
30 |
+
setRatio: {
|
31 |
+
'ja': '倍率を [RATIO] にする',
|
32 |
+
'ja-Hira': 'ばいりつを [RATIO] にする',
|
33 |
+
'en': 'set ratio to [RATIO]'
|
34 |
+
},
|
35 |
+
setInterval: {
|
36 |
+
'ja': '認識を [INTERVAL] 秒ごとに行う',
|
37 |
+
'ja-Hira': 'にんしきを [INTERVAL] びょうごとにおこなう',
|
38 |
+
'en': 'Label once every [INTERVAL] seconds'
|
39 |
+
},
|
40 |
+
on: {
|
41 |
+
'ja': '入',
|
42 |
+
'ja-Hira': 'いり',
|
43 |
+
'en': 'on'
|
44 |
+
},
|
45 |
+
off: {
|
46 |
+
'ja': '切',
|
47 |
+
'ja-Hira': 'きり',
|
48 |
+
'en': 'off'
|
49 |
+
},
|
50 |
+
video_on_flipped: {
|
51 |
+
'ja': '左右反転',
|
52 |
+
'ja-Hira': 'さゆうはんてん',
|
53 |
+
'en': 'on flipped',
|
54 |
+
},
|
55 |
+
please_wait: {
|
56 |
+
'ja': '準備に時間がかかります。少しの間、操作ができなくなりますがお待ち下さい。',
|
57 |
+
'ja-Hira': 'じゅんびにじかんがかかります。すこしのあいだ、そうさができなくなりますがおまちください。',
|
58 |
+
'en': 'Setup takes a while. The browser will get stuck, but please wait.'
|
59 |
+
}
|
60 |
+
}
|
61 |
+
const AvailableLocales = ['en', 'ja', 'ja-Hira'];
|
62 |
+
|
63 |
+
class Scratch3Facemesh2ScratchBlocks {
|
64 |
+
get PERSON_NUMBER_MENU () {
|
65 |
+
let person_number_menu = []
|
66 |
+
for (let i = 1; i <= 10; i++) {
|
67 |
+
person_number_menu.push({text: String(i), value: String(i)})
|
68 |
+
}
|
69 |
+
return person_number_menu;
|
70 |
+
}
|
71 |
+
|
72 |
+
get KEYPOINT_MENU () {
|
73 |
+
let keypoint_menu = [];
|
74 |
+
for (let i = 1; i <= 468; i++) {
|
75 |
+
keypoint_menu.push({text: String(i), value: String(i)})
|
76 |
+
}
|
77 |
+
return keypoint_menu;
|
78 |
+
}
|
79 |
+
|
80 |
+
get VIDEO_MENU () {
|
81 |
+
return [
|
82 |
+
{
|
83 |
+
text: Message.off[this._locale],
|
84 |
+
value: 'off'
|
85 |
+
},
|
86 |
+
{
|
87 |
+
text: Message.on[this._locale],
|
88 |
+
value: 'on'
|
89 |
+
},
|
90 |
+
{
|
91 |
+
text: Message.video_on_flipped[this._locale],
|
92 |
+
value: 'on-flipped'
|
93 |
+
}
|
94 |
+
]
|
95 |
+
}
|
96 |
+
|
97 |
+
get INTERVAL_MENU () {
|
98 |
+
return [
|
99 |
+
{
|
100 |
+
text: '0.1',
|
101 |
+
value: '0.1'
|
102 |
+
},
|
103 |
+
{
|
104 |
+
text: '0.2',
|
105 |
+
value: '0.2'
|
106 |
+
},
|
107 |
+
{
|
108 |
+
text: '0.5',
|
109 |
+
value: '0.5'
|
110 |
+
},
|
111 |
+
{
|
112 |
+
text: '1.0',
|
113 |
+
value: '1.0'
|
114 |
+
}
|
115 |
+
]
|
116 |
+
}
|
117 |
+
|
118 |
+
get RATIO_MENU () {
|
119 |
+
return [
|
120 |
+
{
|
121 |
+
text: '0.5',
|
122 |
+
value: '0.5'
|
123 |
+
},
|
124 |
+
{
|
125 |
+
text: '0.75',
|
126 |
+
value: '0.75'
|
127 |
+
},
|
128 |
+
{
|
129 |
+
text: '1',
|
130 |
+
value: '1'
|
131 |
+
},
|
132 |
+
{
|
133 |
+
text: '1.5',
|
134 |
+
value: '1.5'
|
135 |
+
},
|
136 |
+
{
|
137 |
+
text: '2.0',
|
138 |
+
value: '2.0'
|
139 |
+
}
|
140 |
+
]
|
141 |
+
}
|
142 |
+
|
143 |
+
constructor (runtime) {
|
144 |
+
this.runtime = runtime;
|
145 |
+
|
146 |
+
this.faces = [];
|
147 |
+
this.ratio = 0.75;
|
148 |
+
|
149 |
+
this.detectFace = () => {
|
150 |
+
// We should reuse the video element created by videoProvider instead of creating a new video element
|
151 |
+
// This is because iOS or iPad does not allow camera attached to two video elements
|
152 |
+
this.video = this.runtime.ioDevices.video.provider.video
|
153 |
+
|
154 |
+
alert(Message.please_wait[this._locale]);
|
155 |
+
|
156 |
+
|
157 |
+
this.facemesh = ml5.facemesh(this.video, function() {
|
158 |
+
console.log("Model loaded!")
|
159 |
+
});
|
160 |
+
|
161 |
+
this.facemesh.on('predict', faces => {
|
162 |
+
if (faces.length < this.faces.length) {
|
163 |
+
this.faces.splice(faces.length);
|
164 |
+
}
|
165 |
+
faces.forEach((face, index) => {
|
166 |
+
this.faces[index] = {keypoints: face.scaledMesh};
|
167 |
+
});
|
168 |
+
});
|
169 |
+
}
|
170 |
+
|
171 |
+
this.runtime.ioDevices.video.enableVideo().then(this.detectFace)
|
172 |
+
}
|
173 |
+
|
174 |
+
getInfo () {
|
175 |
+
this._locale = this.setLocale();
|
176 |
+
|
177 |
+
return {
|
178 |
+
id: 'facemesh2scratch',
|
179 |
+
name: 'Facemesh2Scratch',
|
180 |
+
blockIconURI: blockIconURI,
|
181 |
+
blocks: [
|
182 |
+
{
|
183 |
+
opcode: 'getX',
|
184 |
+
blockType: BlockType.REPORTER,
|
185 |
+
text: Message.getX[this._locale],
|
186 |
+
arguments: {
|
187 |
+
PERSON_NUMBER: {
|
188 |
+
type: ArgumentType.STRING,
|
189 |
+
menu: 'personNumberMenu',
|
190 |
+
defaultValue: '1'
|
191 |
+
},
|
192 |
+
KEYPOINT: {
|
193 |
+
type: ArgumentType.STRING,
|
194 |
+
menu: 'keypointMenu',
|
195 |
+
defaultValue: '1'
|
196 |
+
}
|
197 |
+
}
|
198 |
+
},
|
199 |
+
{
|
200 |
+
opcode: 'getY',
|
201 |
+
blockType: BlockType.REPORTER,
|
202 |
+
text: Message.getY[this._locale],
|
203 |
+
arguments: {
|
204 |
+
PERSON_NUMBER: {
|
205 |
+
type: ArgumentType.STRING,
|
206 |
+
menu: 'personNumberMenu',
|
207 |
+
defaultValue: '1'
|
208 |
+
},
|
209 |
+
KEYPOINT: {
|
210 |
+
type: ArgumentType.STRING,
|
211 |
+
menu: 'keypointMenu',
|
212 |
+
defaultValue: '1'
|
213 |
+
}
|
214 |
+
}
|
215 |
+
},
|
216 |
+
{
|
217 |
+
opcode: 'getPeopleCount',
|
218 |
+
blockType: BlockType.REPORTER,
|
219 |
+
text: Message.peopleCount[this._locale]
|
220 |
+
},
|
221 |
+
{
|
222 |
+
opcode: 'videoToggle',
|
223 |
+
blockType: BlockType.COMMAND,
|
224 |
+
text: Message.videoToggle[this._locale],
|
225 |
+
arguments: {
|
226 |
+
VIDEO_STATE: {
|
227 |
+
type: ArgumentType.STRING,
|
228 |
+
menu: 'videoMenu',
|
229 |
+
defaultValue: 'off'
|
230 |
+
}
|
231 |
+
}
|
232 |
+
},
|
233 |
+
{
|
234 |
+
opcode: 'setVideoTransparency',
|
235 |
+
text: formatMessage({
|
236 |
+
id: 'videoSensing.setVideoTransparency',
|
237 |
+
default: 'set video transparency to [TRANSPARENCY]',
|
238 |
+
description: 'Controls transparency of the video preview layer'
|
239 |
+
}),
|
240 |
+
arguments: {
|
241 |
+
TRANSPARENCY: {
|
242 |
+
type: ArgumentType.NUMBER,
|
243 |
+
defaultValue: 50
|
244 |
+
}
|
245 |
+
}
|
246 |
+
},
|
247 |
+
{
|
248 |
+
opcode: 'setRatio',
|
249 |
+
blockType: BlockType.COMMAND,
|
250 |
+
text: Message.setRatio[this._locale],
|
251 |
+
arguments: {
|
252 |
+
RATIO: {
|
253 |
+
type: ArgumentType.STRING,
|
254 |
+
menu: 'ratioMenu',
|
255 |
+
defaultValue: '0.75'
|
256 |
+
}
|
257 |
+
}
|
258 |
+
}
|
259 |
+
],
|
260 |
+
menus: {
|
261 |
+
personNumberMenu: {
|
262 |
+
acceptReporters: true,
|
263 |
+
items: this.PERSON_NUMBER_MENU
|
264 |
+
},
|
265 |
+
keypointMenu: {
|
266 |
+
acceptReporters: true,
|
267 |
+
items: this.KEYPOINT_MENU
|
268 |
+
},
|
269 |
+
videoMenu: {
|
270 |
+
acceptReporters: true,
|
271 |
+
items: this.VIDEO_MENU
|
272 |
+
},
|
273 |
+
ratioMenu: {
|
274 |
+
acceptReporters: true,
|
275 |
+
items: this.RATIO_MENU
|
276 |
+
},
|
277 |
+
intervalMenu: {
|
278 |
+
acceptReporters: true,
|
279 |
+
items: this.INTERVAL_MENU
|
280 |
+
}
|
281 |
+
}
|
282 |
+
};
|
283 |
+
}
|
284 |
+
|
285 |
+
getX (args) {
|
286 |
+
let personNumber = parseInt(args.PERSON_NUMBER, 10) - 1;
|
287 |
+
let keypoint = parseInt(args.KEYPOINT, 10) - 1;
|
288 |
+
|
289 |
+
if (this.faces[personNumber].keypoints && this.faces[personNumber].keypoints[keypoint]) {
|
290 |
+
if (this.runtime.ioDevices.video.mirror === false) {
|
291 |
+
return -1 * (240 - this.faces[personNumber].keypoints[keypoint][0] * this.ratio);
|
292 |
+
} else {
|
293 |
+
return 240 - this.faces[personNumber].keypoints[keypoint][0] * this.ratio;
|
294 |
+
}
|
295 |
+
} else {
|
296 |
+
return "";
|
297 |
+
}
|
298 |
+
}
|
299 |
+
|
300 |
+
getY (args) {
|
301 |
+
let personNumber = parseInt(args.PERSON_NUMBER, 10) - 1;
|
302 |
+
let keypoint = parseInt(args.KEYPOINT, 10) - 1;
|
303 |
+
|
304 |
+
if (this.faces[personNumber].keypoints && this.faces[personNumber].keypoints[keypoint]) {
|
305 |
+
return 180 - this.faces[personNumber].keypoints[keypoint][1] * this.ratio;
|
306 |
+
} else {
|
307 |
+
return "";
|
308 |
+
}
|
309 |
+
}
|
310 |
+
|
311 |
+
getPeopleCount () {
|
312 |
+
return this.faces.length;
|
313 |
+
}
|
314 |
+
|
315 |
+
videoToggle (args) {
|
316 |
+
let state = args.VIDEO_STATE;
|
317 |
+
if (state === 'off') {
|
318 |
+
this.runtime.ioDevices.video.disableVideo();
|
319 |
+
this.facemesh.video = null; // Stop the model prediction if video is off
|
320 |
+
} else {
|
321 |
+
this.facemesh.removeAllListeners('predict');
|
322 |
+
this.runtime.ioDevices.video.enableVideo().then(this.detectFace);
|
323 |
+
this.runtime.ioDevices.video.mirror = state === "on";
|
324 |
+
}
|
325 |
+
}
|
326 |
+
|
327 |
+
/**
|
328 |
+
* A scratch command block handle that configures the video preview's
|
329 |
+
* transparency from passed arguments.
|
330 |
+
* @param {object} args - the block arguments
|
331 |
+
* @param {number} args.TRANSPARENCY - the transparency to set the video
|
332 |
+
* preview to
|
333 |
+
*/
|
334 |
+
setVideoTransparency (args) {
|
335 |
+
const transparency = Cast.toNumber(args.TRANSPARENCY);
|
336 |
+
this.globalVideoTransparency = transparency;
|
337 |
+
this.runtime.ioDevices.video.setPreviewGhost(transparency);
|
338 |
+
}
|
339 |
+
|
340 |
+
setRatio (args) {
|
341 |
+
this.ratio = parseFloat(args.RATIO);
|
342 |
+
}
|
343 |
+
|
344 |
+
setLocale() {
|
345 |
+
let locale = formatMessage.setup().locale;
|
346 |
+
if (AvailableLocales.includes(locale)) {
|
347 |
+
return locale;
|
348 |
+
} else {
|
349 |
+
return 'en';
|
350 |
+
}
|
351 |
+
}
|
352 |
+
}
|
353 |
+
|
354 |
+
module.exports = Scratch3Facemesh2ScratchBlocks;
|