Smooth foreground extraction - javascript

I am trying to do smooth foreground extraction couple of days.I tried many things but nothing seems to work ;( ;(
I just want smooth human body extraction just like this:https://www.youtube.com/watch?v=rGMqXBvYxog
1-)I use Morphological Transformations and BackgroundSubstraction to do this.
Documentation says
"Opening for removing background noise and closing image for closing small holes inside the foreground objects" But It didn't work ;(
Without Closing and Opening : https://streamable.com/xh368
With Closing and Opening : https://streamable.com/bixmm
Closing and Opening Javascript Code :
let video = document.getElementById('videoInput');
let cap = new cv.VideoCapture(video);
let frame = new cv.Mat(video.height, video.width, cv.CV_8UC4);
let fgmask = new cv.Mat(video.height, video.width, cv.CV_8UC1);
let fgbg = new cv.BackgroundSubtractorMOG2(500, 16,false);
const FPS = 30;
function processVideo() {
try {
if (!streaming) {
// clean and stop.
frame.delete(); fgmask.delete(); fgbg.delete();
return;
}
let begin = Date.now();
// start processing.
cap.read(frame);
fgbg.apply(frame, fgmask); //Apply Background Substraction
cv.bitwise_not(fgmask,fgmask);//Set background color black and foreground color white for Morphological Transformations
let M = cv.Mat.ones(5,5, cv.CV_8U);
let anchor = new cv.Point(-1, -1);
cv.morphologyEx(fgmask, fgmask, cv.MORPH_OPEN, M, anchor, 1,
cv.BORDER_CONSTANT, cv.morphologyDefaultBorderValue());
cv.morphologyEx(fgmask,fgmask, cv.MORPH_CLOSE, M);
frame.copyTo(fgmask, fgmask); //Copy original colors
cv.imshow('canvasOutput', fgmask);
// schedule the next one.
let delay = 100/FPS - (Date.now() - begin);
setTimeout(processVideo, delay);
} catch (err) {
utils.printError(err);
}
};
// schedule the first one.
setTimeout(processVideo, 0);
Full HTML:https://anotepad.com/notes/ne7n4w
All files:https://files.fm/u/c9egsgqe
2-))I am using haarcascades to extract human bodies in this technique , just like this:https://www.bytefish.de/blog/extracting_contours_with_opencv/
This algorithm run on following 5 steps.These algorithm not work very well because sometimes haar cascades cannot detect objects
Javascript Code
let src = cv.imread('canvasInput');
let gray = new cv.Mat();
cv.cvtColor(src, gray, cv.COLOR_RGBA2GRAY, 0);
let faces = new cv.RectVector();
let poly=new cv.MatVector();
let faceCascade = new cv.CascadeClassifier();
faceCascade.load('haarcascade_frontalface_default.xml');
let msize = new cv.Size(0, 0);
faceCascade.detectMultiScale(gray, faces, 1.1, 3, 0, msize, msize);
//1-Create a mask with the rectangular coordinates
let rect = new cv.Rect(faces.get(0).x, faces.get(0).y,faces.get(0).width,faces.get(0).height);
//2-Mask out
dst = src.roi(rect);
//3-Edge detection using canny
let cannyoutput=new cv.Mat();
cv.Canny(dst, cannyoutput, 0, 100, 3, true);
//4-Find contours
let contours = new cv.MatVector();
let hierarchy = new cv.Mat();
cv.findContours(cannyoutput,contours, hierarchy, cv.RETR_CCOMP, cv.CHAIN_APPROX_NONE);
//cv.drawContours();
cv.imshow('canvasOutput', cannyoutput);
src.delete(); gray.delete(); faceCascade.delete();
faces.delete();
contours.delete(); hierarchy.delete();
3-)I tried to apply erosion then dialatios ,it didn't work.This Answer
Without Erosion And Dilation : https://streamable.com/xh368
With Erosion And Dilation : https://streamable.com/fffsn
My javascript code:
let video = document.getElementById('videoInput');
let cap = new cv.VideoCapture(video);
let frame = new cv.Mat(video.height, video.width, cv.CV_8UC4);
let fgmask = new cv.Mat(video.height, video.width, cv.CV_8UC1);
let fgbg = new cv.BackgroundSubtractorMOG2(500, 16,false);
const FPS = 30;
function processVideo() {
try {
if (!streaming) {
// clean and stop.
frame.delete(); fgmask.delete(); fgbg.delete();
return;
}
let begin = Date.now();
// start processing.
cap.read(frame);
fgbg.apply(frame, fgmask);
cv.bitwise_not(fgmask,fgmask);
let M = cv.Mat.ones(5,5, cv.CV_8U);
let anchor = new cv.Point(-1, -1);
cv.erode(fgmask, fgmask, M, anchor, 1,
cv.BORDER_CONSTANT, cv.morphologyDefaultBorderValue());
cv.dilate(fgmask, fgmask, M, anchor, 1, cv.BORDER_CONSTANT,
cv.morphologyDefaultBorderValue());
frame.copyTo(fgmask, fgmask);
cv.imshow('canvasOutput', fgmask);
// schedule the next one.
let delay = 1000/FPS - (Date.now() - begin);
setTimeout(processVideo, delay);
} catch (err) {
utils.printError(err);
}
};
// schedule the first one.
setTimeout(processVideo, 0);
My full html:https://anotepad.com/notes/gg5esk

Related

MediaRecorder ignoring VideoFrame.timestamp

I would like to generate a video. I am using MediaRecorder to record a track generated by MediaStreamTrackGenerator.
Generating each frame takes some time, let's say 1 second, and I would like to generate the video at 10 fps.
Therefore, when I create a frame, I use timestamp and duration to indicate the real time of the frame.
const ms = 1_000_000; // 1µs
const fps = 10;
const frame = new VideoFrame(await createImageBitmap(canvas), {
timestamp: (ms * 1) / fps,
duration: ms / fps,
});
Unfortunately, if generating each frame takes 1 second, despite indicating timestamp and duration, the video is played at 1frame/sec, not 10fps.
How can I encode the video frames at the desired frame rate?
Bonus: Downloading the generated video in VLC, the video has no duration. Can this be set?
CodePen for reproduction: https://codepen.io/AmitMY/pen/OJxgPoG
(this example works in Chrome. If you use Safari, change video/webm to video/mp4.)
Things I tried and aren't a good solution for me:
Storing all frames in some cache, then playing them back at the desired speed and recording that playback. It is unreliable, inconsistent, and memory intensive.
Foreword
So... I've been investigating this for two days now and it's a complete mess. I don't have a full answer, but here's what I've tried and figured out so far.
The situation
First up I scrapped up this diagram of Web Codecs / Insertable Streams API to better understand how everything links together:
MediaStream, StreamTrack, VideoFrame, TrackProcessor, TrackGenerator, ...
The most common use case / flow is that you have a MediaStream, such as a video camera feed or an existing video (playing on canvas), which you'd then "break into" different MediaStreamTracks - usually audio- and video track, though the API actually supports subtitle-, image- and shared screen tracks as well.
So you break a MediaStream into a MediaStreamTrack of "video" kind, which you then feed to MediaStreamTrackProcessor to actually break the video track into individual VideoFrames. You can then do frame-by-frame manipulation and when you're done, you're supposed to stream those VideoFrames into MediaStreamTrackGenerator, which in turn turns those VideoFrames into a MediaStreamTrack, which in turn you can stuff into a MediaStream to make a sort of "Full Media Object" aka. something that contains Video and Audio tracks.
Interestingly enough, I couldn't get a MediaStream to play on a <video> element directly, but I think that this is a hard requirement if we want to accomplish what OP wants.
As it currently stands, even when we have all the VideoFrames ready to go and turned into a MediaStream, we still have to, for some reason, record it twice to create a proper Blob which <video> accepts - think of this step pretty much as a "rendering" step of a professional video editing software, the only difference being that we already have the final frames, so why can't we just create a video out of those?
As far as I know, everything here that works for Video, also works for Audio. So there actually exist something called AudioFrame for example, though the documentation page is missing as I am writing this.
Encoding and Decoding
Furthermore, regarding VideoFrames and AudioFrames, there's also API support for encoding and decoding of those, which I actually tried in the hopes that encoding a VideoFrame with VP8 would somehow "bake" that duration and timestamp into it, as at least the duration of VideoFrame does not seem to do anything.
Here's my encoding / decoding code when I tried playing around with it. Note that this whole encoding and decoding business + codecs is one hell of a deep rabbit hole. I have no idea how I found this for example, but it did tell me that Chromium doesn't support hardware accelerated VP8 on Windows (no thanks to the codec error messages, which just babbled something about "cannot used closed codec"):
const createFrames = async (ctx, fps, streamWriter, width, height) => {
const getRandomRgb = () => {
var num = Math.round(0xffffff * Math.random());
var r = num >> 16;
var g = num >> 8 & 255;
var b = num & 255;
return 'rgb(' + r + ', ' + g + ', ' + b + ')';
}
const encodedChunks = [];
const videoFrames = [];
const encoderOutput = (encodedChunk) => {
encodedChunks.push(encodedChunk);
}
const encoderError = (err) => {
//console.error(err);
}
const encoder = new VideoEncoder({
output: encoderOutput,
error: encoderError
})
encoder.configure({
//codec: "avc1.64001E",
//avc:{format:"annexb"},
codec: "vp8",
hardwareAcceleration: "prefer-software", // VP8 with hardware acceleration not supported
width: width,
height: height,
displayWidth: width,
displayHeight: height,
bitrate: 3_000_000,
framerate: fps,
bitrateMode: "constant",
latencyMode: "quality"
});
const ft = 1 / fps;
const micro = 1_000_000;
const ft_us = Math.floor(ft * micro);
for(let i = 0; i < 10; i++) {
console.log(`Writing frames ${i * fps}-${(i + 1) * fps}`);
ctx.fillStyle = getRandomRgb();
ctx.fillRect(0,0, width, height);
ctx.fillStyle = "white";
ctx.textAlign = "center";
ctx.font = "80px Arial";
ctx.fillText(`${i}`, width / 2, height / 2);
for(let j = 0; j < fps; j++) {
//console.log(`Writing frame ${i}.${j}`);
const offset = i > 0 ? 1 : 0;
const timestamp = i * ft_us * fps + j * ft_us;
const duration = ft_us;
var frameData = ctx.getImageData(0, 0, width, height);
var buffer = frameData.data.buffer;
const frame = new VideoFrame(buffer,
{
format: "RGBA",
codedWidth: width,
codedHeight: height,
colorSpace: {
primaries: "bt709",
transfer: "bt709",
matrix: "bt709",
fullRange: true
},
timestamp: timestamp,
duration: ft_us
});
encoder.encode(frame, { keyFrame: false });
videoFrames.push(frame);
}
}
//return videoFrames;
await encoder.flush();
//return encodedChunks;
const decodedChunks = [];
const decoder = new VideoDecoder({
output: (frame) => {
decodedChunks.push(frame);
},
error: (e) => {
console.log(e.message);
}
});
decoder.configure({
codec: 'vp8',
codedWidth: width,
codedHeight: height
});
encodedChunks.forEach((chunk) => {
decoder.decode(chunk);
});
await decoder.flush();
return decodedChunks;
}
Frame calculations
Regarding your frame calculations, I did things a bit differently. Consider the following image and code:
const fps = 30;
const ft = 1 / fps;
const micro = 1_000_000;
const ft_us = Math.floor(ft * micro);
Ignoring the fact how long it takes to create 1 frame (as it should be irrelevant here, if we can set the frame duration), here's what I figured.
We want to play the video at 30 frames per second (fps). We generate 10 colored rectangles which we want to show on the screen for 1 second each, resulting in a video length of 10 seconds. This means that, in order to actually play the video at 30fps, we need to generate 30 frames for each rectangle. If we could set a frame duration, we could technically have only 10 frames with a duration of 1 second each, but then the fps would actually be 1 frame per second. We're doing 30fps though.
An fps of 30 gives us a frametime (ft) of 1 / 30 seconds, aka. the time that each frame is shown on the screen. We generate 30 frames for 1 rectangle -> 30 * (1 / 30) = 1 second checks out. The other thing here is that VideoFrame duration and timestamp do not accept seconds or milliseconds, but microseconds, so we need to turn that frametime (ft) to frametime in microseconds (ft_us), which is just (1 / 30) * 1 000 000 = ~33 333us.
Calculating the final duration and timestamp for each frame is a bit tricky as we are now looping twice, one loop for each rectangle and one loop for each frame of a rectangle at 30fps.
The timestamp for a frame j of rectangle i is (in english):
<i> * <frametime in us> * <fps> + <j> * <frametime in us> (+ <offset 0 or 1>
Where <i> * <frametime in us> * <fps> gets us many microseconds each previous rectangle takes and <j> * <frametime in us> gets us how many microseconds each previous frame of the current rectangle takes. We also supply and optional offset of 0, when we're making our very first frame of the very first rectangle and an offset of 1 otherwise, so that we avoid overlapping.
const fps = 30;
const ft = 1 / fps;
const micro = 1_000_000;
const ft_us = Math.floor(ft * micro);
// For each colored rectangle
for(let i = 0; i < 10; i++) {
// For each frame of colored rectangle at 30fps
for(let j = 0; j < fps; j++) {
const offset = i > 0 ? 1 : 0;
const timestamp = i * ft_us * fps + j * ft_us /* + offset */;
const duration = ft_us * 10;
new VideoFrame({ duration, timestamp });
...
}
}
This should get us 10 * 30 = 300 frames in total, for a video length of 10 seconds when played at 30 fps.
My latest try and ReadableStream test
I've refactored everything so many times without luck, but here is my current solution where I try to use ReadableStream to pass the generated VideoFrames to MediaStreamTrackGenerator (skipping the recording step), generate a MediaStream from that and try to give the result to srcObject of a <video> element:
const streamTrackGenerator = new MediaStreamTrackGenerator({ kind: 'video' });
const streamWriter = streamTrackGenerator.writable;
const chunks = await createFrames(ctx, fps, streamWriter, width, height); // array of VideoFrames
let idx = 0;
await streamWriter.ready;
const frameStream = new ReadableStream({
start(controller) {
controller.enqueue(chunks[idx]);
idx++;
},
pull(controller) {
if(idx >= chunks.length) {
controller.close();
}
else {
controller.enqueue(chunks[idx]);
idx++;
}
},
cancel(reason) {
console.log("Cancelled", reason);
}
});
await frameStream.pipeThrough(new TransformStream({
transform (chunk, controller) {
console.log(chunk); // debugging
controller.enqueue(chunk) // passthrough
}
})).pipeTo(streamWriter);
const mediaStreamTrack = streamTrackGenerator.clone();
const mediaStream = new MediaStream([mediaStreamTrack]);
const video = document.createElement('video');
video.style.width = `${width}px`;
video.style.height = `${height}px`;
document.body.appendChild(video);
video.srcObject = mediaStream;
video.setAttribute('controls', 'true')
video.onloadedmetadata = function(e) {
video.play().catch(e => alert(e.message))
};
Try with VP8 encoding + decoding and trying to give VideoFrames to MediaSource via SourceBuffers
More info on MediaSource and SourceBuffers. This one is also me trying to exploit the MediaRecorder.start() function with timeslice parameter in conjuction with MediaRecorder.requestFrame() to try and record frame-by-frame:
const init = async () => {
const width = 256;
const height = 256;
const fps = 30;
const createFrames = async (ctx, fps, streamWriter, width, height) => {
const getRandomRgb = () => {
var num = Math.round(0xffffff * Math.random());
var r = num >> 16;
var g = num >> 8 & 255;
var b = num & 255;
return 'rgb(' + r + ', ' + g + ', ' + b + ')';
}
const encodedChunks = [];
const videoFrames = [];
const encoderOutput = (encodedChunk) => {
encodedChunks.push(encodedChunk);
}
const encoderError = (err) => {
//console.error(err);
}
const encoder = new VideoEncoder({
output: encoderOutput,
error: encoderError
})
encoder.configure({
//codec: "avc1.64001E",
//avc:{format:"annexb"},
codec: "vp8",
hardwareAcceleration: "prefer-software",
width: width,
height: height,
displayWidth: width,
displayHeight: height,
bitrate: 3_000_000,
framerate: fps,
bitrateMode: "constant",
latencyMode: "quality"
});
const ft = 1 / fps;
const micro = 1_000_000;
const ft_us = Math.floor(ft * micro);
for(let i = 0; i < 10; i++) {
console.log(`Writing frames ${i * fps}-${(i + 1) * fps}`);
ctx.fillStyle = getRandomRgb();
ctx.fillRect(0,0, width, height);
ctx.fillStyle = "white";
ctx.textAlign = "center";
ctx.font = "80px Arial";
ctx.fillText(`${i}`, width / 2, height / 2);
for(let j = 0; j < fps; j++) {
//console.log(`Writing frame ${i}.${j}`);
const offset = i > 0 ? 1 : 0;
const timestamp = i * ft_us * fps + j * ft_us;
const duration = ft_us;
var frameData = ctx.getImageData(0, 0, width, height);
var buffer = frameData.data.buffer;
const frame = new VideoFrame(buffer,
{
format: "RGBA",
codedWidth: width,
codedHeight: height,
colorSpace: {
primaries: "bt709",
transfer: "bt709",
matrix: "bt709",
fullRange: true
},
timestamp: timestamp,
duration: ft_us
});
encoder.encode(frame, { keyFrame: false });
videoFrames.push(frame);
}
}
//return videoFrames;
await encoder.flush();
//return encodedChunks;
const decodedChunks = [];
const decoder = new VideoDecoder({
output: (frame) => {
decodedChunks.push(frame);
},
error: (e) => {
console.log(e.message);
}
});
decoder.configure({
codec: 'vp8',
codedWidth: width,
codedHeight: height
});
encodedChunks.forEach((chunk) => {
decoder.decode(chunk);
});
await decoder.flush();
return decodedChunks;
}
const canvas = new OffscreenCanvas(256, 256);
const ctx = canvas.getContext("2d");
const recordedChunks = [];
const streamTrackGenerator = new MediaStreamTrackGenerator({ kind: 'video' });
const streamWriter = streamTrackGenerator.writable.getWriter();
const mediaStream = new MediaStream();
mediaStream.addTrack(streamTrackGenerator);
const mediaRecorder = new MediaRecorder(mediaStream, {
mimeType: "video/webm",
videoBitsPerSecond: 3_000_000
});
mediaRecorder.addEventListener('dataavailable', (event) => {
recordedChunks.push(event.data);
console.log(event)
});
mediaRecorder.addEventListener('stop', (event) => {
console.log("stopped?")
console.log('Frames written');
console.log('Stopping MediaRecorder');
console.log('Closing StreamWriter');
const blob = new Blob(recordedChunks, {type: mediaRecorder.mimeType});
const url = URL.createObjectURL(blob);
const video = document.createElement('video');
video.src = url;
document.body.appendChild(video);
video.setAttribute('controls', 'true')
video.play().catch(e => alert(e.message))
});
console.log('StreamWrite ready');
console.log('Starting mediarecorder');
console.log('Creating frames');
const chunks = await createFrames(ctx, fps, streamWriter, width, height);
mediaRecorder.start(33333);
for(const key in chunks) {
await streamWriter.ready;
const chunk = chunks[key];
//await new Promise(resolve => setTimeout(resolve, 1))
await streamWriter.write(chunk);
mediaRecorder.requestData();
}
//await streamWriter.ready;
//streamWriter.close();
//mediaRecorder.stop();
/*const mediaSource = new MediaSource();
const video = document.createElement('video');
document.body.appendChild(video);
video.setAttribute('controls', 'true')
const url = URL.createObjectURL(mediaSource);
video.src = url;
mediaSource.addEventListener('sourceopen', function() {
var mediaSource = this;
const sourceBuffer = mediaSource.addSourceBuffer('video/webm; codecs="vp8"');
let allocationSize = 0;
chunks.forEach((c) => { allocationSize += c.byteLength});
var buf = new ArrayBuffer(allocationSize);
chunks.forEach((chunk) => {
chunk.copyTo(buf);
});
sourceBuffer.addEventListener('updateend', function() {
//mediaSource.endOfStream();
video.play();
});
sourceBuffer.appendBuffer(buf);
});*/
//video.play().catch(e => alert(e.message))
/*mediaStream.getTracks()[0].stop();
const blob = new Blob(chunks, { type: "video/webm" });
const url = URL.createObjectURL(blob);
const video = document.createElement('video');
video.srcObject = url;
document.body.appendChild(video);
video.setAttribute('controls', 'true')
video.play().catch(e => alert(e.message))*/
//mediaRecorder.stop();
}
Conclusion / Afterwords
After all that I tried, I had the most problems with turning Frames into Tracks and Tracks into Streams etc. There is so much (poorly documentet) converting from one thing to another and half of it is done with streams, which also lacks a lot of documentation. There doesn't even seem to be any meaningful way to create custom ReadableStreams and WritableStreams without the use of NPM packages.
I never got VideoFrame duration working. What surprised me the most is that basically nothing else in the process mattered with regards to video or frame length other than adjusting the hacky await new Promise(resolve => setTimeout(resolve, 1000)) timing, but even with that, the recording was really inconsistent. If there was any lag during recording, it would show on the recording; I had recordings where some rectangles were shown for half a second and other ones for 2 seconds. Interestingly enough, the whole recording process would sometimes break completely, if I removed the arbitrary setTimeout. A program that would break without the timeout, would work with await new Promise(resolve => setTimeout(resolve, 1)). This is usually a clue that this has something to do with JS Event Loops, as setTimeouts with 0ms timings tell JS to "wait for next event loop round".
I'm still going to work on this a bit, but I'm doubtful I'll make any further progress. I'd like to get this to work without the use of MediaRecorder and by utilizing streams to work out resource issues.
One really interesting thing that I bumped into was that MediaStreamTrackGenerator is actually old news. The w3 documentation only really talks about VideoTrackGenerator and there's an interesting take on how to basically build a VideoTrackGenerator from the existing MediaStreamTrackGenerator. Also note this part specifically:
This interestingily enough tells us that MediaStreamTrackGenerator.clone() === MediaStreamTrack which I tried to put in use, but without success.
Anyway, I hope this might give you some new ideas or clarify some things. Maybe you'll figure out something I didn't. Have a good one and do tell if you have questions or figure something out!
Further reading
w3.org VideoFrame and duration
Edit 1
Forgot to mention that I used OffscreenCanvas and it's context, instead of normal Canvas. As we're also talking about performance here, I figured I'd try and see how OffscreenCanvas works.
I also used the second constructor of VideoFrame, that is, I gave it an ArrayBuffer instead of a bitmap image like in your code.
Although you have an accepted Answer, I'll add my two-cents worth of advice...
"Generating each frame takes some time, let's say 1 second, and I would like to generate the video at 10 fps. If generating each frame takes 1 second, despite indicating timestamp and duration, the video is played at 1frame/sec, not 10fps.
How can I encode the video frames at the desired frame rate?"
To encode a 10 frames-per-sec video, from your For loop of 10 bitmaps, would give you a video with a 1 second of duration (but it travels through 10 frames during that 1 second interval).
What you want then is a new frame every 100ms until these 10 frames makes a 1000ms.
To achieve that 10 FPS, you simply...
First pause the recorder with mediaRecorder.pause();
Now generate your bitmap (this process can take any length of time)
When frame/bitmap is ready, then resume the recorder for 100ms with mediaRecorder.resume();
To achieve the 100ms per frame, you can use a Timer that re-pauses the recording.
Think of it as using a camcorder, where you:
press record -> await 100ms of capture -> pause -> new frame ->repeat press record until 10 frames.
Here is a quick-ish example as a starting point (eg: readers should improve upon it):
<!DOCTYPE html>
<html>
<body>
<button onclick="recorder_Setup()">Create Video</button>
<h2 id="demo"></h2>
<script>
//# Create canvas for dummy frames
const canvas = document.createElement('canvas');
canvas.width = 256;
canvas.height = 256;
document.body.appendChild(canvas);
const ctx = canvas.getContext('2d');
const recordedChunks = [];
var mediaRecorder; var generator; var writer
var stream; var frame; var frameCount = 0;
//# needed to pause the function, whilst recorder stores frames at the specified interval
const sleep = ( sleep_time ) => { return new Promise(resolve => setTimeout(resolve, sleep_time) ) }
//# setup recorder
recorder_Setup(); //# create and start recorder here
function getRandomRgb()
{
var num = Math.round(0xffffff * Math.random());
var r = num >> 16;
var g = num >> 8 & 255;
var b = num & 255;
return 'rgb(' + r + ', ' + g + ', ' + b + ')';
}
function recorder_Setup()
{
//# create media generator track
generator = new MediaStreamTrackGenerator({kind: 'video'});
writer = generator.writable.getWriter();
stream = new MediaStream();
stream.addTrack(generator);
var myObj = {
mimeType: "video/webm",
videoBitsPerSecond: 3_000_000 // 3MBps
};
mediaRecorder = new MediaRecorder( stream, myObj );
mediaRecorder.addEventListener('dataavailable', (event) => { onFrameData( event.data ); } );
mediaRecorder.addEventListener("stop", (event) => { recorder_Stop() } );
//# start the recorder... and start adding frames
mediaRecorder.start();
recorder_addFrame();
}
function onFrameData( input )
{
//console.log( "got frame data... frame count v2 : " + frameCount );
recordedChunks.push( input );
}
async function recorder_addFrame ()
{
mediaRecorder.pause();
await new Promise(resolve => setTimeout(resolve, 1000) )
//# add text for frame number
ctx.fillStyle = "#808080";
ctx.fillRect(0, 0, 256, 256);
ctx.font = "30px Arial"; ctx.fillStyle = "#FFFFFF";
ctx.fillText("frame : " + frameCount ,10,50);
//# add color fill for frame pixels
ctx.fillStyle = getRandomRgb();
ctx.fillRect(0, 70, 256, 180);
const ms = 1000; // 1µs
//# note "timestamp" and "duration" don't mean anything here...
frame = new VideoFrame( await createImageBitmap(canvas), {timestamp: 0, duration: 0} );
console.log( "frame count v1 : " + frameCount );
frameCount++;
//# When ready to write a frame, you resume the recoder for the required interval period
//# (eg: a 10 FPS = 1000/10 = 100 ms interval per frame during the 1000 ms (of 1 second)...
mediaRecorder.resume();
await sleep(100);
writer.write(frame);
frame.close();
if( frameCount >= 10 ) { mediaRecorder.stop(); }
else { recorder_addFrame(); }
}
function recorder_Stop()
{
console.log("recorder stopped");
stream.getTracks().forEach(track => track.stop());
const blob = new Blob(recordedChunks, {type: mediaRecorder.mimeType});
const url = URL.createObjectURL(blob);
const video = document.createElement('video');
video.src = url;
document.body.appendChild(video);
video.setAttribute('controls', 'true')
video.setAttribute('muted', 'true')
//video.play().catch(e => alert(e.message))
}
</script>
</body>
</html>

Difference between BABYLON.Animation and scene.registerBeforeRender

I just started to learn more about Babylon.js (I don't know if this is a good choice between p5.js and three.js; throw some suggestions for me).
I came along with this question "which function is used more often between BABYLON.Animation and scene.registerBeforeRender(). I guess I am more used to use render() method, but I guess Animation function is good when I change the frameRates.
Which is better? which is used more often ?
const canvas = document.getElementById("renderCanvas");
const engine = new BABYLON.Engine(canvas, true);
const createScene = () => {
const scene = new BABYLON.Scene(engine);
/**** Set camera and light *****/
const camera = new BABYLON.ArcRotateCamera("camera", -Math.PI / 2, Math.PI / 2.5, 10, new BABYLON.Vector3(0, 0, 0));
camera.attachControl(canvas, true);
const light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(1, 1, 0));
const box = BABYLON.MeshBuilder.CreateBox("box", {});
box.position.y = 0.5;
const ground = BABYLON.MeshBuilder.CreateGround("ground", {width:10, height:10});
// Animations
var alpha = 0;
scene.registerBeforeRender(function () {
box.rotation.y += 0.05;
});
const frameRate = 60;
const xSlide = new BABYLON.Animation("xSlide", "position.x", frameRate, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
const keyFrames = [];
keyFrames.push({
frame: 0,
value: 2
});
keyFrames.push({
frame: frameRate,
value: -2
});
keyFrames.push({
frame: 2 * frameRate,
value: 2
});
xSlide.setKeys(keyFrames);
box.animations.push(xSlide);
scene.beginAnimation(box, 0, 2 * frameRate, true);
return scene;
}
const scene = createScene();
engine.runRenderLoop(() => {
// call render method for our scene
scene.render();
});
scene.registerBeforeRender() is more flexible in changing the values. In your example, you are changing rotation by 0.05 constant value. In some cases, this may be variable, so you can assign a variable instead of constant value.
It is tricky to change this in Animation class methods, because in KeyFrame, your key and values are fixed once you set them. The only way I could change this is remove animations and add new ones. On a positive side, in Animation class methods, you can change frameRate and change what you want to do in a particular frame.

Im trying to detect circle only at real-time webcam, and draw circle on it

I trying to detect circle only on real-time webcam video and trying to draw circle border on my targeted circle, but right now I can draw a circle on output on a random target, please help me to get the circle target. I'm developing this code in javascript and using opencv.js. That random detection also detecting on video bottom only not in any other places.
function processVideo() {
try {
if (!streaming) {
// clean and stop.
src.delete();
dst.delete();
dstC1.delete();
dstC3.delete();
return;
}
let begin = Date.now();
// start processing.
cap.read(src);
cv.cvtColor(src, dst, cv.COLOR_RGBA2GRAY);
cv.blur(dst, dstC1, ksize, anchor, cv.BORDER_DEFAULT); // blur the image to avoids noise
cv.Canny(dstC1, dstC1, 50, 100, 3, false); // black and white border
let contours = new cv.MatVector();
let hierarchy = new cv.Mat();
cv.findContours(dstC1, contours, hierarchy, cv.RETR_CCOMP, cv.CHAIN_APPROX_SIMPLE, offset = new cv.Point(0, 0));
let cnt = contours.get(0);
let contoursColor = new cv.Scalar(255, 255, 255);
let circleColor = new cv.Scalar(255, 0, 0);
let circle = cv.minEnclosingCircle(cnt);// this one for circle
cv.drawContours(dstC1, contours, 0, contoursColor, 1, 8, hierarchy, 100);
for (let i = 0; i < 4; i++) {
cv.circle(dstC1, circle.center, circle.radius, circleColor, 3);
}
cv.imshow('canvasOutput', dstC1);
let delay = 1000/FPS - (Date.now() - begin);
setTimeout(processVideo, delay);
} catch (err) {
utils.printError(err);
}
};

OpenCV.js - detectMultiScale "This Exception cannot be caught"

I'm trying to use facial recognition via OpenCV.js, however when I call on the detectMultiScale() method of the CascadeClassifier object I receive the error:
Uncaught 6446128 - Exception catching is disabled, this exception cannot be caught. Compile with -s DISABLE_EXCEPTION_CATCHING=0 or DISABLE_EXCEPTION_CATCHING=2 to catch.
The problem is I'm leveraging a hosted version of opencv.js directly from opencv.org - it's not a build version because I'm unable to build it myself, and therefore cannot follow the error's instructions.
I've followed an example from their GitHub here and adapted the code to suit my needs, as follows:
<html>
<head>
<script src="https://docs.opencv.org/master/opencv.js"></script>
<script src="https://docs.opencv.org/master/utils.js"></script>
</head>
<body>
<img id="test" src="image/with/face.jpg" alt=""/>
<canvas id="output"></canvas>
<script>
let face_cascade = new cv.CascadeClassifier();
face_cascade.load("https://raw.githubusercontent.com/opencv/opencv/master/data/haarcascades_cuda/haarcascade_frontalface_default.xml");
function face_detector() {
let imgEl = document.getElementById("test");
let img = cv.imread(imgEl);
cv.imshow("output", img);
let src = cv.imread("output");
let gray = new cv.Mat();
let msize = new cv.Size(0,0);
cv.cvtColor(src, gray, cv.COLOR_RGBA2GRAY, 0);
let faces = new cv.RectVector();
face_cascade.detectMultiScale(gray, faces, 1.1, 3, 0, msize, msize); //Error occurs here
}
face_detector();
</script>
</body>
</html>
Anyone with experience with OpenCV.js and facial recognition that could help?
Following this thread:
The xml files are "pre-built" before loading them with the load function. To achieve this it's used the function createFileFromUrl from utils.js. After that we can finally load our classifier from file.
let classifier = new cv.CascadeClassifier(); // initialize classifier
let utils = new Utils('errorMessage'); //use utils class
let faceCascadeFile = 'haarcascade_frontalface_default.xml'; // path to xml
// use createFileFromUrl to "pre-build" the xml
utils.createFileFromUrl(faceCascadeFile, faceCascadeFile, () => {
classifier.load(faceCascadeFile); // in the callback, load the cascade from file
});
Face Detection Other Example
TRY IT :
let src = cv.imread('canvasInput');
let gray = new cv.Mat();
cv.cvtColor(src, gray, cv.COLOR_RGBA2GRAY, 0);
let faces = new cv.RectVector();
let eyes = new cv.RectVector();
let faceCascade = new cv.CascadeClassifier();
// load pre-trained classifiers
faceCascade.load('haarcascade_frontalface_default.xml');
// detect faces
let msize = new cv.Size(0, 0);
// try to change scaleFactor and minNeighbors values
faceCascade.detectMultiScale(gray, faces,1.05,0);
for (let i = 0; i < faces.size(); ++i) {
let roiGray = gray.roi(faces.get(i));
let roiSrc = src.roi(faces.get(i));
let point1 = new cv.Point(faces.get(i).x, faces.get(i).y);
let point2 = new cv.Point(faces.get(i).x + faces.get(i).width,
faces.get(i).y + faces.get(i).height);
cv.rectangle(src, point1, point2, [255, 0, 0, 255]);
roiGray.delete(); roiSrc.delete();
}
cv.imshow('canvasOutput', src);
src.delete(); gray.delete(); faceCascade.delete();
faces.delete(); eyes.delete();
Try to change faceCascade.detectMultiScale parameters like given examples below:
faceCascade.detectMultiScale(gray, faces,1.05,0);
faceCascade.detectMultiScale(gray, faces,1.05,1);
faceCascade.detectMultiScale(gray, faces,2,0);
faceCascade.detectMultiScale(gray, faces,2,1);
faceCascade.detectMultiScale(gray, faces,3,0);
faceCascade.detectMultiScale(gray, faces,3,1);
faceCascade.detectMultiScale(gray, faces,4,0);
faceCascade.detectMultiScale(gray, faces,4,1);
The solution is
let faceCascadeFile = 'haarcascade_frontalface_default.xml';
utils.createFileFromUrl(faceCascadeFile, faceCascadeFile, () => {
console.log('cascade ready to load.');
let src = cv.imread('imageInit');
let gray = new cv.Mat();
cv.cvtColor(src, gray, cv.COLOR_RGBA2GRAY, 0);
let faces = new cv.RectVector();
let faceCascade = new cv.CascadeClassifier();
faceCascade.load(faceCascadeFile);
let msize = new cv.Size(0, 0);
faceCascade.detectMultiScale(gray, faces, 1.1, 3, 0, msize, msize);
});
}
For full code and explanation use this link Face Detection with Javascript and OpenCV
or
Human Eye Detection using Javascript and OpenCV

How to create a mask in OpenCV.js?

I can create a mask in OPENCV C++ using cv::Mat::zeros and Rect.But i cannot find these features on OPENCV.js.How can i create a mask on OPENCV.js?
cv::Mat mask = cv::Mat::zeros(8, 8, CV_8U); // all 0
mask(Rect(2,2,4,4)) = 1;
let src = cv.imread('canvasInput');
let dst = new cv.Mat();
// You can try more different parameters
let rect = new cv.Rect(100, 100, 200, 200);
dst = src.roi(rect);
cv.imshow('canvasOutput', dst);
src.delete();
dst.delete();
Taken from here, specifically the Image ROI section

Categories