How would I go about loading thousands of images in p5.js?
The only good way for images to actually load is within the preload() function, but I am making a program that needs to load 3498 images, and loading 10.9 GB of images, assumingly into RAM, with preload(), won't work. The reason I want to do this is to manipulate the frames by merging them and rotating them and then exporting them.
let frameNum = 3498;
let overlapNum = 10;
let overlapAngle;
function setup() {
createCanvas(1080, 1080);
overlapAngle = QUARTER_PI * 0.08;
}
function draw() {
background(0);
push();
translate(width / 2, width / 2);
tint(255, 255 / overlapNum);
let imgSize = width / sin(QUARTER_PI);
let rotAmount = frameCount / 120.0 * TWO_PI;
for (i = 0; i < overlapNum; i++) {
let imgNum = ((frameCount * 10) + i + 3496) % (frameNum) + 1;
push();
rotate(i / float(overlapNum) * overlapAngle + rotAmount);
let img = loadImage("Input/frame-" + str(imgNum).padStart(5, "0") + ".png");
image(img, -imgSize / 2, -imgSize / 2, imgSize, imgSize);
print("Input/frame-" + str(imgNum).padStart(5, "0") + ".png");
print(imgSize);
pop();
}
pop();
}
The result of this is just a blank canvas. Nothing loads. I tried loading an image into preload() and that works.
I hope I didn't word my question badly. Thank you.
In your comment, you have the right idea. The loadImage() function can take up to 3 parameters. If you make a function for displaying the image as you want it, and pass the function name as the second parameter of loadImage(), that function will be called when the image has loaded (with the loaded image as the input). Something like this might work:
function setup() {
//... your setup stuff
frameRate(0.1);
}
function draw() {
//...
for (...) {
loadImage('file path', dispImage);
}
}
function dispImage(image_to_display) {
this.img = image_to_display;
//... do whatever to display the image
}
I put the frameRate() command in because I don't think that the for loop will wait until one image is displayed to load the next image, so in order for this to work, you need the frame rate to be slow enough to allow a bunch of images to load between frames.
I didn't test this out, so I may have made mistakes or misunderstood something.
Related
I am new to the amazing world of creative coding and p5js. I want to create a website for my design studio with a p5js effect that I can't find the solution for. I searched everywhere and I can't find anyone with the same problem as me, that's why I'm posting my very first message here. Here's the idea: in a canvas, I would like that with each mouse click, a different image can appear. Currently, my code allows to display images randomly but I would like to be able to set a cyclic order of appearance : work0.png, work1.png, work2.png... and it starts again.
If someone has seen this problem somewhere or could explain it to me I would be very grateful. Thanks !
let works = []
function preload() {
for (let i = 0; i < 5; i++) {
works[i] = loadImage("work" + i + ".png")
}
}
function setup() {
canvas = createCanvas(windowWidth, windowHeight);
canvas.position(0, 0);
canvas.style('z-index', '1');
}
function windowResized() {
resizeCanvas(windowWidth, windowHeight);
canvas.position(0, 0);
canvas.style('z-index', '1');
}
function draw() {
cursor(CROSS);
}
function mouseClicked() {
imageMode(CENTER);
let r = floor(random(0, 6));
image(works[r], mouseX, mouseY, 500, 600);
}
instead of using random, you can use a counter
let works = []
function preload() {
for (let i = 0; i < 5; i++) {
works[i] = loadImage("work" + i + ".png")
}
}
function setup() {
canvas = createCanvas(windowWidth, windowHeight);
canvas.position(0, 0);
canvas.style('z-index', '1');
}
function windowResized() {
resizeCanvas(windowWidth, windowHeight);
canvas.position(0, 0);
canvas.style('z-index', '1');
}
function draw() {
cursor(CROSS);
}
var counter = 0
function mouseClicked() {
imageMode(CENTER);
counter++ //add one to the counter
image(works[counter%5], mouseX, mouseY, 500, 600); // the % symbols is modulo, which is a fancy word for remainder when divided by that number, so it would cycle from 0 to 4
}
So the counter starts at zero, and every time you click we increase the counter by one. to stop it from increasing constantly, we can use the modulo operator %(in programming this isn't the percent sign). examples: 2%5 is 2, 16%5 is 1, 25%5 is 0.
this essentially makes the value cycle from 0 to 4. the counter is constantly increasing, but the remainder when divided by five will always cycle.
also btw p5.js is a javascript library, and you are loading in code made by other people, which means without the p5.js library the code doesn't run in this code snippet on StackOverflow
I'm making a sketch with the p5.js library and ml5's poseNet. I have the variable noseX — which indicates the x-coordinate position of the nose on the canvas — and a preloaded array of 60 images. I would like to vertically divide the canvas into 60 sections. For each cnvSection noseX is on, I want the image img[i] corresponding to that cnvSection[i] to to be drawn on the canvas.
So basically if noseX is on cnvSection[5], draw img[5] on the canvas etc.
Here's my function, at the moment I have only been able to draw the vertical lines indicating the canvas sections.
let cnvWidth = 1440;
let cnvHeight = 900;
function nosePosition() {
let sectionWidth = cnvWidth / 60;
let cnvSection = [];
for (let i = 0; i < cnvWidth; i = i + sectionWidth) {
line(i, 0, i, cnvHeight);
}
}
Many thanks!
Try the following:
noseX = 50;
function setup() {
createCanvas(700, 400);
}
function draw() {
background(220);
nosePosition();
fill('red');
ellipse(noseX, height / 2, 6);
}
function nosePosition() {
let sectionWidth = width / 60;
for (let i = 0; i < width; i += sectionWidth) {
line(i, 0, i, height);
}
const noseSection = floor(noseX / sectionWidth);
rect(noseSection * sectionWidth, 0, sectionWidth, height);
}
To simplify I use a hardcoded x value for the nose. In your case that would come from ml5 posenet. You don't need the sections in an array. You can simlpy calculate in which section the nose currently is and then draw the image accordingly. I am drawing a rect - again, to simplify the example.
Maybe copy and paste this into the p5 web editor and play around with the noseX value and see if that works for you.
Im working with this part of code which one render simple loading bar
const smallSpinner = document.getElementById('spinner-small').getContext('2d');
let pointToFill = 4.72;
let cw = smallSpinner.canvas.width; //Return canvas width
let ch = smallSpinner.canvas.height; //Return canvas height
let diff;
let = fillSmallSpinner = (startingPointSmall = 0) => {
diff = ((startingPointSmall / 100) * Math.PI * 2 * 10);
smallSpinner.clearRect(0, 0, cw, ch);
smallSpinner.lineWidth = 5;
smallSpinner.strokeStyle = '#d40511';
/* smallSpinner.textAlign = 'center';
smallSpinner.font = "25px monospace";
smallSpinner.fillText(no + '%', 50, 55); */ //uncomment this if you need percent progress inside spinner
smallSpinner.beginPath();
smallSpinner.arc(50, 50, 40, pointToFill, diff / 10 + pointToFill);
smallSpinner.stroke();
if (startingPointSmall >= 100) {
clearTimeout(fill);
}
startingPointSmall++;
}
let small = setInterval(fillSmallSpinner, 50);
The point is that when "startingPointSmall" is defined like normal variable
let startingPointSmall = 0;
it works totaly fine but i want to make this a little bit more usable and pass the starting point as a function parameter. When i do this like this with predefined starting point on 0% it doesnt work. Can someone explain me how to fix this?
Every time that setInterval queues up a call to fillSmallSpinner it will receive its default parameter - over and over!
A more common pattern is to wrap the function in a way that preserves the desired variable's scope:
const startFiller(callback, interval = 50, start = 0) {
let current = startPoint;
let timer = setInterval(() => {
callback(current++);
if (current >= 100) {
clearTimeout(timer);
}
}, interval);
});
startFiller(fillSmallSpinner);
You would then remove any existing timer-related logic from your fillSmallSpinner function. This approach has the added benefit of Separation of Concerns - if you decide you want to use a different function to render your spinner it no longer needs to concern itself with timers.
Ok, so I am working on a sort of detection system where I will be pointing the camera at a screen, and it will have to find the red object. I can successfully do this, with pictures, but the problem is that it takes several seconds to load. I want to be able to do this to live videos, so I need it to find the object immediately. Here is my code:
video.addEventListener('pause', function () {
let reds = [];
for(x=0; x<= canvas.width; x++){
for(y=0; y<= canvas.height; y++){
let data = ctx.getImageData(x, y, 1, 1).data;
let rgb = [ data[0], data[1], data[2] ];
if (rgb[0] >= rgb[1] && rgb[0] >=rgb[2] && !(rgb[0]>100 && rgb[1]>100 && rgb[2]>100) && rgb[1]<100 && rgb[2]<100 && rgb[0]>150){
reds[reds.length] = [x, y]
}
let addedx = 0
let addedy = 0
for(i=0; i<reds.length; i++){
addedx = addedx + reds[i][0]
addedy = addedy + reds[i][1]
}
let center = [addedx/reds.length, addedy/reds.length]
ctx.rect(center[0]-5, center[1]-5, 10, 10)
ctx.stroke()
}, 0);
Ya, I know its messy. Is there something about the for loops that are slow? I know I'm looping through thousands of pixels but that's the only way I can think of to do it.
As it has been said, Javascript is not the most performant for this task. However, here are some things I noticed, which could slow you down.
You grab the image data one pixel at a time. Since this method can return the whole frame, you can do this once.
Optimize your isRed condition:
rgb[0] >= rgb[1] && // \
rgb[0] >= rgb[2] && // >-- This is useless
!(rgb[0] > 100 && rgb[1] > 100 && rgb[2] > 100) && // /
rgb[1] < 100 && // \
rgb[2] < 100 && // >-- These 3 conditions imply the others
rgb[0] > 150 // /
You calculate the center inside your for loop after each pixel, but it would only make sense after processing the whole frame.
Since the video feed is coming from a camera, maybe you don't need to look at every single pixel. Maybe every 5 pixels is enough? That's what the example below does. Tweak this.
Demo including these optimizations
Node: This demo includes an adaptation of the code from this answer, to copy the video onto the canvas.
const video = document.getElementById("video"),
canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d");
let width,
height;
// To make this demo work
video.crossOrigin = "Anonymous";
// Set canvas to video size when known
video.addEventListener("loadedmetadata", function() {
width = canvas.width = video.videoWidth;
height = canvas.height = video.videoHeight;
});
video.addEventListener("play", function() {
const $this = this; // Cache
(function loop() {
if (!$this.paused && !$this.ended) {
ctx.drawImage($this, 0, 0);
const reds = [],
data = ctx.getImageData(0, 0, width, height).data,
len = data.length;
for (let i = 0; i < len; i += 5 * 4) { // 4 because data is made of RGBA values
const rgb = data.slice(i, i + 3);
if (rgb[0] > 150 && rgb[1] < 100 && rgb[2] < 100) {
reds.push([i / 4 % width, Math.floor(i / 4 / width)]); // Get [x,y] from i
}
}
if (reds.length) { // Can't divide by 0
const sums = reds.reduce(function (res, point) {
return [res[0] + point[0], res[1] + point[1]];
}, [0,0]);
const center = [
Math.round(sums[0] / reds.length),
Math.round(sums[1] / reds.length)
];
ctx.strokeStyle = "blue";
ctx.lineWidth = 10;
ctx.beginPath();
ctx.rect(center[0] - 5, center[1] - 5, 10, 10);
ctx.stroke();
}
setTimeout(loop, 1000 / 30); // Drawing at 30fps
}
})();
}, 0);
video, canvas { width: 250px; height: 180px; background: #eee; }
<video id="video" src="https://shrt-statics.s3.eu-west-3.amazonaws.com/redball.mp4" controls></video>
<canvas id="canvas"></canvas>
I would run the detection algorithm in a webassembly module. Since it is just pixel data, thats right up its alley.
You could then pass individual frames to a different instance of the wasm module.
As far as answering your question directly, I would grab the whole frame, not 1 pixel at a time, or you might get pixels sampled from different frames. You can then submit that frame to a worker, you could even divide up the frame and send them to different workers (or as previously mentioned a wasm module)
Also since you have an array you can use Arrray.map and Array.reduce to get you to just the red values, and how big they are by testing for adjacent pixels, instead of all the comparison. Not sure if it will be faster but worth a try.
For the speed, you should consider all your process:
more your language is near the machine language, better your result will be. Saying so, C++ is better for the algorithm.
CPU speed is your friend. Launching your code on an Atom processor or on an i7 processor, is like night and day. Moreover, some type of processor is dedicated for vision like VPU
For your code:
You try to rewrite code that already exists. You can find good examples of detection in the great OpenCV library: https://www.learnopencv.com/invisibility-cloak-using-color-detection-and-segmentation-with-opencv
Hope it help you :)
var video;
var snapshots = [];
var readyCheck = false;
var button;
function setup() {
createCanvas(800, 600);
background(0);
video = createCapture(VIDEO, ready);
video.size(200, 150);
}
function ready() {
readyCheck = true;
console.log('work');
}
function draw() {
var w = 200;
var h = 150;
var x = 0;
var y = 0;
if (readyCheck) {
for (var i = 0; i < 100; i++) {
// use setTimeout() to wait for 2 seconds
setTimeout(function() {
snapshots[i] = video.get();
image(snapshots[i],x, y);
x += w;
if (x >= width) {
x = 0;
y += h;
}
}, 2000);
}
}
}
my purpose is taking pictures from the webcam after specific time. So I use the setTimeout() in JS. I expect pictures will appear on the canvas every 2 seconds in a row.
when entering the for part, the code will wait 2 seconds and capture the image from webcam and display it.
but my situation is that all the picture appear on the canvas at the same time.
You need to take a step back and understand how the draw() function and the setTimeout() functions work.
The draw() function is automatically called 60 times per second. You can adjust this by calling the frameRate() function or the noLoop() function. More info is available in the reference.
The setTimeout() function sets up a callback function that is automatically called after some duration, in your case 2 seconds.
So, what your code is doing is setting up 100 callback functions that will all fire in 2 seconds- and it's doing this 60 times per second! So in 1 second, you'll have 6000 functions that will start firing 2 seconds later! This is almost definitely not what you want.
P5.js already has its own timing mechanism in the draw() function that's called 60 times per second, so it seems a little weird to use the setTimeout() function inside P5.js code. Instead, you should probably set up your own timing using the frameCount variable or the millis() function.
Here's an example that shows a random color every second:
function setup() {
createCanvas(200, 200);
}
function draw() {
if(frameCount % 60 == 0){
background(random(256), random(256), random(256));
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.11/p5.min.js"></script>
This code uses the frameCount variable and the % modulus operator to check whether 60 frames have passed, and if so, it sets the background to a random color. You'll want to do something similar.
Like I said above, more info about all of this can be found in the reference.