Spaces:
Runtime error
Runtime error
add pseudo-3d rotation
Browse files- app.py +7 -0
- static/poseEditor.js +118 -11
app.py
CHANGED
@@ -110,6 +110,13 @@ with gr.Blocks() as demo:
|
|
110 |
- "shift + drag" to rotate(move right first, release shift, then up or down)
|
111 |
- "space + drag" to move within range
|
112 |
- "[", "]" or mouse wheel to shrink or expand range
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
113 |
""")
|
114 |
|
115 |
source.change(
|
|
|
110 |
- "shift + drag" to rotate(move right first, release shift, then up or down)
|
111 |
- "space + drag" to move within range
|
112 |
- "[", "]" or mouse wheel to shrink or expand range
|
113 |
+
- "ctrl + z", "shift + ctrl + z" to undo, redo
|
114 |
+
- "a" add new person
|
115 |
+
- "q + click" to delete person
|
116 |
+
- "x + drag" to x-axis pseudo-3D rotation
|
117 |
+
- "c + drag" to y-axis pseudo-3D rotation
|
118 |
+
|
119 |
+
Points to note for pseudo-3D rotation: When performing pseudo-3D rotation on the X and Y axes, the projection is converted to 2D and Z-axis information is lost when the mouse button is released. This means that if you finish dragging while the shape is collapsed, you may not be able to restore it to its original state. In such a case, please use the "undo" function.
|
120 |
""")
|
121 |
|
122 |
source.change(
|
static/poseEditor.js
CHANGED
@@ -10,10 +10,24 @@ function distSq(p0, p1) {
|
|
10 |
return (p0[0] - p1[0]) ** 2 + (p0[1] - p1[1]) ** 2;
|
11 |
}
|
12 |
|
13 |
-
// poseDataの形式:[[[x1, y1], [x2, y2], ...]]
|
14 |
// 各要素が人間
|
15 |
// 人間の各要素が関節
|
16 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
17 |
|
18 |
// サンプルデータ
|
19 |
const sampleCandidateSource = [[235, 158],[234, 220],[193, 222],[138, 263],[89, 308],[276, 220],[325, 264],[375, 309],[207, 347],[203, 433],[199, 523],[261, 347],[262, 430],[261, 522],[227, 148],[245, 148],[208, 158],[258, 154]].map((p) => [p[0], p[1] - 70]);
|
@@ -77,6 +91,70 @@ function resizeCanvas(width, height) {
|
|
77 |
drawBodyPose();
|
78 |
}
|
79 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
80 |
function drawBodyPose() {
|
81 |
const stickWidth = 6;
|
82 |
const limbSeq = [[2, 3], [2, 6], [3, 4], [4, 5], [6, 7], [7, 8], [2, 9], [9, 10],
|
@@ -91,6 +169,7 @@ function drawBodyPose() {
|
|
91 |
|
92 |
for (let i = 0; i < poseData.length; i++) {
|
93 |
const pose = poseData[i];
|
|
|
94 |
|
95 |
// edge
|
96 |
for (let j = 0; j < 17; j++) {
|
@@ -242,7 +321,8 @@ function handleMouseDown(event) {
|
|
242 |
dragStart = p;
|
243 |
dragMarks = poseData.map(pose => pose.map(node => false));
|
244 |
|
245 |
-
if (event.altKey || event.ctrlKey || event.shiftKey
|
|
|
246 |
// dragMarksを設定
|
247 |
dragMarks[personIndex] =
|
248 |
poseData[personIndex].map((node) => node != null);
|
@@ -254,6 +334,10 @@ function handleMouseDown(event) {
|
|
254 |
} else if (event.shiftKey) {
|
255 |
dragMode = "rotate";
|
256 |
rotateBaseVector = [0, 0];
|
|
|
|
|
|
|
|
|
257 |
}
|
258 |
} else if (keyDownFlags["Space"]) {
|
259 |
dragMarks[personIndex] =
|
@@ -302,6 +386,25 @@ function handleMouseMove(event) {
|
|
302 |
node[0] = x * cos - y * sin + dragStart[0];
|
303 |
node[1] = x * sin + y * cos + dragStart[1];
|
304 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
305 |
} else if (dragMode == "move") {
|
306 |
// 移動
|
307 |
forEachMarkedNodes((i, j, node) => {
|
@@ -370,18 +473,22 @@ function initializePose(jsonData,w,h) {
|
|
370 |
canvas.addEventListener('mouseup', handleMouseUp);
|
371 |
|
372 |
resizeCanvas(w, h);
|
|
|
|
|
373 |
}
|
374 |
|
375 |
function savePose() {
|
376 |
-
|
|
|
|
|
|
|
377 |
|
378 |
-
|
379 |
-
|
380 |
|
381 |
-
|
382 |
-
|
383 |
|
384 |
-
|
385 |
-
|
386 |
-
return { "candidate": candidate, "subset": subset };
|
387 |
}
|
|
|
10 |
return (p0[0] - p1[0]) ** 2 + (p0[1] - p1[1]) ** 2;
|
11 |
}
|
12 |
|
13 |
+
// poseDataの形式:[[[x1, y1], [x2, y2], ...],[[x3, y3], [x4, y4], ...], ...]
|
14 |
// 各要素が人間
|
15 |
// 人間の各要素が関節
|
16 |
+
|
17 |
+
function poseDataToCandidateAndSubset(poseData) {
|
18 |
+
let candidate = [];
|
19 |
+
let subset = [];
|
20 |
+
for (let i = 0; i < poseData.length; i++) {
|
21 |
+
let person = poseData[i];
|
22 |
+
let subsetElement = [];
|
23 |
+
for (let j = 0; j < person.length; j++) {
|
24 |
+
candidate.push(person[j]);
|
25 |
+
subsetElement.push(candidate.length - 1);
|
26 |
+
}
|
27 |
+
subset.push(subsetElement);
|
28 |
+
}
|
29 |
+
return [candidate, subset];
|
30 |
+
}
|
31 |
|
32 |
// サンプルデータ
|
33 |
const sampleCandidateSource = [[235, 158],[234, 220],[193, 222],[138, 263],[89, 308],[276, 220],[325, 264],[375, 309],[207, 347],[203, 433],[199, 523],[261, 347],[262, 430],[261, 522],[227, 148],[245, 148],[208, 158],[258, 154]].map((p) => [p[0], p[1] - 70]);
|
|
|
91 |
drawBodyPose();
|
92 |
}
|
93 |
|
94 |
+
function calculateCenter(shape) {
|
95 |
+
var center = shape.reduce(function(acc, point) {
|
96 |
+
acc[0] += point[0];
|
97 |
+
acc[1] += point[1];
|
98 |
+
return acc;
|
99 |
+
}, [0, 0]);
|
100 |
+
center[0] /= shape.length;
|
101 |
+
center[1] /= shape.length;
|
102 |
+
return center;
|
103 |
+
}
|
104 |
+
|
105 |
+
// v2d -> v3d
|
106 |
+
function rotateX(vector, angle) {
|
107 |
+
var x = vector[0];
|
108 |
+
var y = vector[1];
|
109 |
+
var z = 0;
|
110 |
+
|
111 |
+
// X軸に対して回転する
|
112 |
+
var x1 = x;
|
113 |
+
var y1 = y * Math.cos(angle) - z * Math.sin(angle);
|
114 |
+
var z1 = y * Math.sin(angle) + z * Math.cos(angle);
|
115 |
+
|
116 |
+
return [x1, y1, z1];
|
117 |
+
}
|
118 |
+
|
119 |
+
// v2d -> v3d
|
120 |
+
function rotateY(vector, angle) {
|
121 |
+
var x = vector[0];
|
122 |
+
var y = vector[1];
|
123 |
+
var z = 0;
|
124 |
+
|
125 |
+
// Y軸に対して回転する
|
126 |
+
var x1 = x * Math.cos(angle) + z * Math.sin(angle);
|
127 |
+
var y1 = y;
|
128 |
+
var z1 = -x * Math.sin(angle) + z * Math.cos(angle);
|
129 |
+
|
130 |
+
return [x1, y1, z1];
|
131 |
+
}
|
132 |
+
|
133 |
+
// v3d -> v2d
|
134 |
+
function perspectiveProjection(vector, cameraDistance) {
|
135 |
+
var x = vector[0];
|
136 |
+
var y = vector[1];
|
137 |
+
var z = vector[2];
|
138 |
+
|
139 |
+
if (z === 0) {
|
140 |
+
return [x, y];
|
141 |
+
}
|
142 |
+
|
143 |
+
var scale = cameraDistance / (cameraDistance - z);
|
144 |
+
var x1 = x * scale;
|
145 |
+
var y1 = y * scale;
|
146 |
+
|
147 |
+
return [x1, y1];
|
148 |
+
}
|
149 |
+
|
150 |
+
// v2d -> v3d
|
151 |
+
function rotateAndProject(f, p, c, angle) {
|
152 |
+
var v = [p[0] - c[0], p[1] - c[1]];
|
153 |
+
var v1 = f(v, angle);
|
154 |
+
var v2 = perspectiveProjection(v1, 500);
|
155 |
+
return [v2[0] + c[0], v2[1] + c[1]];
|
156 |
+
}
|
157 |
+
|
158 |
function drawBodyPose() {
|
159 |
const stickWidth = 6;
|
160 |
const limbSeq = [[2, 3], [2, 6], [3, 4], [4, 5], [6, 7], [7, 8], [2, 9], [9, 10],
|
|
|
169 |
|
170 |
for (let i = 0; i < poseData.length; i++) {
|
171 |
const pose = poseData[i];
|
172 |
+
const center = calculateCenter(pose);
|
173 |
|
174 |
// edge
|
175 |
for (let j = 0; j < 17; j++) {
|
|
|
321 |
dragStart = p;
|
322 |
dragMarks = poseData.map(pose => pose.map(node => false));
|
323 |
|
324 |
+
if (event.altKey || event.ctrlKey || event.shiftKey ||
|
325 |
+
keyDownFlags["KeyX"] || keyDownFlags["KeyC"]) {
|
326 |
// dragMarksを設定
|
327 |
dragMarks[personIndex] =
|
328 |
poseData[personIndex].map((node) => node != null);
|
|
|
334 |
} else if (event.shiftKey) {
|
335 |
dragMode = "rotate";
|
336 |
rotateBaseVector = [0, 0];
|
337 |
+
} else if (keyDownFlags["KeyX"]) {
|
338 |
+
dragMode = "rotateX";
|
339 |
+
} else if (keyDownFlags["KeyC"]) {
|
340 |
+
dragMode = "rotateY";
|
341 |
}
|
342 |
} else if (keyDownFlags["Space"]) {
|
343 |
dragMarks[personIndex] =
|
|
|
386 |
node[0] = x * cos - y * sin + dragStart[0];
|
387 |
node[1] = x * sin + y * cos + dragStart[1];
|
388 |
});
|
389 |
+
} else if (dragMode == "rotateX") {
|
390 |
+
const center = dragStart;
|
391 |
+
const angle = dragOffset[1] / -40;
|
392 |
+
forEachMarkedNodes((i, j, node) => {
|
393 |
+
const lp = latestPoseData[i][j];
|
394 |
+
const np = rotateAndProject(rotateX, lp, center, angle);
|
395 |
+
console.log(np);
|
396 |
+
node[0] = np[0];
|
397 |
+
node[1] = np[1];
|
398 |
+
});
|
399 |
+
} else if (dragMode == "rotateY") {
|
400 |
+
const center = dragStart;
|
401 |
+
const angle = dragOffset[0] / 40;
|
402 |
+
forEachMarkedNodes((i, j, node) => {
|
403 |
+
const lp = latestPoseData[i][j];
|
404 |
+
const np = rotateAndProject(rotateY, lp, center, angle);
|
405 |
+
node[0] = np[0];
|
406 |
+
node[1] = np[1];
|
407 |
+
});
|
408 |
} else if (dragMode == "move") {
|
409 |
// 移動
|
410 |
forEachMarkedNodes((i, j, node) => {
|
|
|
473 |
canvas.addEventListener('mouseup', handleMouseUp);
|
474 |
|
475 |
resizeCanvas(w, h);
|
476 |
+
|
477 |
+
// setInterval(Redraw, 1000 / 30);
|
478 |
}
|
479 |
|
480 |
function savePose() {
|
481 |
+
const canvasUrl = canvas.toDataURL();
|
482 |
+
|
483 |
+
const createEl = document.createElement('a');
|
484 |
+
createEl.href = canvasUrl;
|
485 |
|
486 |
+
// This is the name of our downloaded file
|
487 |
+
createEl.download = "pose.png";
|
488 |
|
489 |
+
createEl.click();
|
490 |
+
createEl.remove();
|
491 |
|
492 |
+
var [candidate, subset] = poseDataToCandidateAndSubset(poseData);
|
493 |
+
return {candidate: candidate, subset: subset};
|
|
|
494 |
}
|