Skip to content

Commit 6824b4c

Browse files
alanvwwnasif-co
andauthored
Adding tensorflow.js depth-estimation (#248)
* Included tf.js/depth-estimation & added pipeline, examples * Better color map and normalization * indentation clean up * Make grayscale the default colormap Also changed the mention of this in the examples. * Resolve suggestions pertaining to examples Removed console logs, the comments are clear enough without them. Also renamed the examples' <title> tag to match ml5.js format. * Renamed depth images in result Unified the 'visualizationImage' and 'depthMapImage' into a single 'image' property inside the result object, which contains a p5.Image of the depth map. Also modified the examples to use this change and to name the result 'depthMap', following the standard set by other ml5 modules like the result of ml5.bodyPose being called 'poses' or ml5.handPose being called 'hands'. With this change, accessing the depth image can be done with 'depthMap.image'. * Replace const with let in examples Following p5.js style. * Reuse initial segmentation to mask result Code refactor to use bodySegmentation just one time and reuse its result for the final masking of the depth map. * Add missing tensor cleanup Removed the depth estimation tensor from the result object so we could handle disposing of it internally. Also tested ml5.tf.memory() on the current code and found a memory leak, which ended up being due to some segmentation tensors not being disposed. I replaced the disposal code being used here with the one used in the official tensorflow examples, which fixed the leak. * Add mask dilation and make mask available in result Added the dilation algorithm to the library. The level of dilation is controlled by the config option 'dilationFactor' which takes values between 0 and 10, corresponding to the amount of pixels to grow the background into the silouette. Larger dilation factors affect fps because they need longer loops to look for bounds. Also made the mask available as a p5.Image in the result, under the name 'mask'. This mask is compatible with the p5 mask() function, so it is easy to use it to cut out the profile from the background. Lastly, also optimized the helper function that turns imageData into p5.Image, by replacing set() and instead copying the imageData.data array into the pixels array. * Getting started examples The first two examples aimed at being starting points for using the model. One is simply a webcam depth estimation without any interface. The other is the same but using the mask to clear out the background. Also made applying the segmentation mask the default for the model, since the it performs much better with it. * Invert grayscale colormap To match transformers.js: lighter pixels are closer to the camera, darker are farther from it. * Add backdrop image to masked bg example To help visualize what using the mask together with the depthMap does. * Fixed size mismatch bug Bug was due to estimation being done on the source element intrinsic dimensions and not the display dimensions set by the user, leading to an unexpected output. Needed to resize the media given by the user before passing it to the models. After some discussions in the discord, opted for resizing the input media through tensorflow.js own methods. I think this might be more performant than resizing the image in a canvas but didn't test them side by side to corroborate. * Simplified examples Simplified existing examples and aligned them with the changes in config defaults. * Remove console logs Converted console logs to comments. * Apply mask to result.data Realized the mask and dilation was not being applied to the data array and therefore not to the getDepthAt() method. Fixed it for consistency. * Add point cloud example * Refactor point cloud example Make the code a little simpler. * Make HTML video element example Since we already have a webcam video example, it felt redundant to have the depthEstimation-video example also use the webcam. So I modified it to instead showcase how to run depthEstimation with a video file. * Add sourceFrame to result object The depth estimation result now includes the exact frame of the input that was used to generate the returned estimation. This is useful for aligning the image with the estimation, especially if the model is running at a lower fps than the source video (which is most often the case) * Add depthEstimation mesh example Shows how to use the depth estimation result together with p5.js 3D geometry tools to build a live mesh of the webcam video. * Use resizeImageAsTensor for size mismatch bug Replace the code fixing the size mismatch bug by using the new function designed for that: resizeImageAsTensor. * Improve example comments * Create p5 2.0 examples --------- Co-authored-by: nasif-co <[email protected]>
1 parent 48e71df commit 6824b4c

File tree

33 files changed

+2000
-0
lines changed

33 files changed

+2000
-0
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
4+
<head>
5+
<title>ml5.js depthEstimation Webcam Example</title>
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
7+
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/p5.js"></script>
8+
<script src="../../dist/ml5.js"></script>
9+
</head>
10+
11+
<body>
12+
<script src="sketch.js"></script>
13+
</body>
14+
15+
</html>
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* 👋 Hello! This is an ml5.js example made and shared with ❤️.
3+
* Learn more about the ml5.js project: https://ml5js.org/
4+
* ml5.js license and Code of Conduct: https://github.com/ml5js/ml5-next-gen/blob/main/LICENSE.md
5+
*
6+
* This example demonstrates running depth estimation on a webcam with a transparent background.
7+
*/
8+
9+
let depthEstimator;
10+
let webcam;
11+
let depthMap;
12+
let backdrop;
13+
14+
// Video dimensions
15+
let videoWidth = 640;
16+
let videoHeight = 480;
17+
18+
async function setup() {
19+
// Load the model
20+
depthEstimator = await ml5.depthEstimation();
21+
22+
// Load a backdrop image. "Bright Center Star Cluster" by NASA
23+
backdrop = await loadImage('starcluster.jpg');
24+
25+
// Create a canvas the size of the webcam video
26+
createCanvas(videoWidth, videoHeight);
27+
28+
// Create the video capture element
29+
webcam = createCapture(VIDEO);
30+
webcam.size(videoWidth, videoHeight); // Set video size
31+
webcam.hide(); // Hide the default HTML video element
32+
33+
// Start continuous depth estimation on the webcam feed and make "gotResults" the callback function
34+
depthEstimator.estimateStart(webcam, gotResults);
35+
}
36+
37+
function draw() {
38+
//Draw a backdrop image that will be behind the depth map
39+
image(backdrop, 0, -height/2, width);
40+
41+
// If depth estimation results are available
42+
if (depthMap) {
43+
// Apply the silhouette mask to the depth map using the p5 mask function. This will make its background transparent.
44+
depthMap.image.mask(depthMap.mask);
45+
// Draw the depth map
46+
image(depthMap.image, 0, 0);
47+
}
48+
}
49+
50+
// Callback function that receives the depth estimation results
51+
function gotResults(result) {
52+
// Store the latest result in the global variable depthMap
53+
depthMap = result;
54+
}
496 KB
Loading
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
4+
<head>
5+
<title>ml5.js depthEstimation Webcam Example</title>
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
7+
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.9.4/p5.min.js"></script>
8+
<script src="../../dist/ml5.js"></script>
9+
</head>
10+
11+
<body>
12+
<script src="sketch.js"></script>
13+
</body>
14+
15+
</html>
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* 👋 Hello! This is an ml5.js example made and shared with ❤️.
3+
* Learn more about the ml5.js project: https://ml5js.org/
4+
* ml5.js license and Code of Conduct: https://github.com/ml5js/ml5-next-gen/blob/main/LICENSE.md
5+
*
6+
* This example demonstrates running depth estimation on a webcam with a transparent background.
7+
*/
8+
9+
let depthEstimator;
10+
let webcam;
11+
let depthMap;
12+
13+
let backdrop;
14+
15+
// Video dimensions
16+
let videoWidth = 640;
17+
let videoHeight = 480;
18+
19+
function preload() {
20+
// Load and start the depth estimation model
21+
depthEstimator = ml5.depthEstimation();
22+
23+
// Load the backdrop image. "Bright Center Star Cluster" by NASA
24+
backdrop = loadImage('starcluster.jpg');
25+
}
26+
27+
function setup() {
28+
// Create a canvas the size of the webcam video
29+
createCanvas(videoWidth, videoHeight);
30+
31+
// Create the video capture element
32+
webcam = createCapture(VIDEO);
33+
webcam.size(videoWidth, videoHeight); // Set video size
34+
webcam.hide(); // Hide the default HTML video element
35+
36+
// Start continuous depth estimation on the webcam feed and make "gotResults" the callback function
37+
depthEstimator.estimateStart(webcam, gotResults);
38+
}
39+
40+
function draw() {
41+
//Draw a backdrop image that will be behind the depth map
42+
image(backdrop, 0, -height/2, width);
43+
44+
// If depth estimation results are available
45+
if (depthMap) {
46+
// Apply the silhouette mask to the depth map using the p5 mask function. This will make its background transparent.
47+
depthMap.image.mask(depthMap.mask);
48+
// Draw the depth map
49+
image(depthMap.image, 0, 0);
50+
}
51+
}
52+
53+
// Callback function that receives the depth estimation results
54+
function gotResults(result) {
55+
// Store the latest result in the global variable depthMap
56+
depthMap = result;
57+
}
496 KB
Loading
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
4+
<head>
5+
<title>ml5.js depthEstimation Mesh Example</title>
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
7+
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/p5.js"></script>
8+
<script src="../../dist/ml5.js"></script>
9+
</head>
10+
11+
<body>
12+
<script src="sketch.js"></script>
13+
</body>
14+
15+
</html>
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/*
2+
* 👋 Hello! This is an ml5.js example made and shared with ❤️.
3+
* Learn more about the ml5.js project: https://ml5js.org/
4+
* ml5.js license and Code of Conduct: https://github.com/ml5js/ml5-next-gen/blob/main/LICENSE.md
5+
*
6+
* This example demonstrates building and texturing a 3D mesh using depth estimation on the webcam video.
7+
*
8+
* Use your mouse to drag and zoom the 3D mesh in space.
9+
*/
10+
11+
let depthEstimator;
12+
let webcam;
13+
let depthMap;
14+
let mesh;
15+
16+
// Video dimensions,
17+
// making them smaller will increase speed
18+
// but will also reduce the accuracy of the depth map
19+
let videoWidth = 320;
20+
let videoHeight = 240;
21+
22+
// Whether the data in the depthMap is new
23+
let newDataAvailable = false;
24+
25+
let options = {
26+
// Default is 4, but since this image is smaller, we change it to 2 so as to not lose too much detail
27+
dilationFactor: 2,
28+
};
29+
30+
async function setup() {
31+
// Load the depth estimation model
32+
depthEstimator = await ml5.depthEstimation(options);
33+
34+
// Create a canvas larger than the video and turn on WEBGL mode for 3D
35+
createCanvas(videoWidth * 2, videoHeight * 2, WEBGL);
36+
37+
// Create the video capture element
38+
webcam = createCapture(VIDEO);
39+
webcam.size(videoWidth, videoHeight); // Set video size
40+
webcam.hide(); // Hide the default HTML video element
41+
42+
mesh = new p5.Geometry();
43+
44+
// Start continuous depth estimation on the webcam feed and make "gotResults" the callback function
45+
depthEstimator.estimateStart(webcam, gotResults);
46+
47+
noStroke();
48+
}
49+
50+
function draw() {
51+
// Turn on dragging and zooming with the mouse
52+
orbitControl();
53+
54+
// If there is new depth data
55+
if (newDataAvailable) {
56+
background(0);
57+
58+
// Clear the mesh geometry to start fresh
59+
freeGeometry(mesh);
60+
mesh = new p5.Geometry();
61+
62+
// Go through each pixel in the webcam video
63+
for (let y = 0; y < webcam.height; y++) {
64+
for (let x = 0; x < webcam.width; x++) {
65+
// Get the depth value from the model (float, 0 - 1) where 0 is closest and 1 is farthest
66+
let depthAtPixel = depthMap.getDepthAt(x, y);
67+
68+
// Get the index for current pixel in webcam pixels array
69+
let index = (x + y * webcam.width) * 4;
70+
71+
//Get the z depth value for the current pixel and scale it up
72+
let z = map(depthAtPixel, 0, 1, 200, -200);
73+
74+
// Create the vertex as a vector and set its UV coordinates for texturing
75+
// 3D pixels can be called voxels
76+
const voxel = createVector(x, y, z);
77+
mesh.vertices.push(voxel);
78+
mesh.uvs.push(x / webcam.width, y / webcam.height);
79+
80+
// For every pixel up to the edges
81+
if (x < webcam.width - 1 && y < webcam.height - 1) {
82+
// Divide index by 4 to get the voxel number in the list of vertices
83+
let voxelIndex = index / 4;
84+
85+
//Let's get the 4 vertices of this "zone" of the mesh
86+
let a = voxelIndex; // Current pixel
87+
let b = voxelIndex + 1; // x + 1 pixel
88+
let c = voxelIndex + webcam.width; // y + 1 pixel
89+
let d = voxelIndex + webcam.width + 1; // x + 1 and y + 1 pixel
90+
91+
// Lets get the depth values for each of the 4 vertices
92+
const aDepth = depthMap.getDepthAt(x, y);
93+
const bDepth = depthMap.getDepthAt(x + 1, y);
94+
const cDepth = depthMap.getDepthAt(x, y + 1);
95+
const dDepth = depthMap.getDepthAt(x + 1, y + 1);
96+
97+
// Each "zone" with 4 vertices consists of two
98+
// adjacent triangles: abc and bdc
99+
100+
// Only add them if they are not part of
101+
// the background, meaning their depth is not 0.
102+
if (!(aDepth === 0 || bDepth === 0 || cDepth === 0)) {
103+
// First triangle
104+
mesh.faces.push([a, b, c]);
105+
}
106+
107+
if (!(bDepth === 0 || dDepth === 0 || cDepth === 0)) {
108+
// Second triangle
109+
mesh.faces.push([b, d, c]);
110+
}
111+
}
112+
}
113+
}
114+
115+
// Calculate the orientation of the faces in the mesh
116+
mesh.computeNormals();
117+
push();
118+
119+
// Double the size to fill the canvas
120+
scale(2);
121+
122+
// Align the mesh to the center of the canvas
123+
translate(-videoWidth / 2, -videoHeight / 2, 0);
124+
125+
// Set the video frame that was used to create the depth map as the texture of the mesh
126+
texture(depthMap.sourceFrame);
127+
model(mesh);
128+
129+
pop();
130+
131+
// The data is no longer new
132+
newDataAvailable = false;
133+
}
134+
}
135+
136+
// Callback function that receives the depth estimation results
137+
function gotResults(result) {
138+
// Store the latest result in the global variable depthMap
139+
depthMap = result;
140+
newDataAvailable = true;
141+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
4+
<head>
5+
<title>ml5.js depthEstimation Mesh Example</title>
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
7+
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.9.4/p5.min.js"></script>
8+
<script src="../../dist/ml5.js"></script>
9+
</head>
10+
11+
<body>
12+
<script src="sketch.js"></script>
13+
</body>
14+
15+
</html>

0 commit comments

Comments
 (0)