Utilizing WebWorkers with @mediapipe/tasks-vision (Pose Landmarker): A Step-by-Step Guide

I've been experimenting with using a web worker to detect poses frame by frame, and then displaying the results on the main thread. However, I'm encountering some delays and synchronization issues.

My setup involves Next.js 14.0.4 with @mediapipe/[email protected].

pose-landmarker-worker.ts

import { PoseLandmarker } from "@mediapipe/tasks-vision";
import { createPoseLandmarker } from "@/lib/create-pose-landmarker";

let poseLandmarker: PoseLandmarker;

self.onmessage = async function (event: MessageEvent) {
    const data = event.data;

    if (data.action === "init") {
        if (!data.runningMode) {
            throw new Error("Could not initialize. The runningMode prop is not specified");
        }

        if (!poseLandmarker) {
            const poseLandmarkerInstance = await createPoseLandmarker(data.runningMode);

            if (!poseLandmarkerInstance) {
                throw new Error("Failed to create pose landmarker instance");
            }

            poseLandmarker = poseLandmarkerInstance;
            console.log("Created new pose landmarker instance");
        } else {
            console.warn("pose landmarker already initialized");
        }
    } else if (data.action === "detectForVideo") {
        const imageBitmap = data.frame as ImageBitmap;
        const result = poseLandmarker.detectForVideo(imageBitmap, data.timestamp);
        self.postMessage(result);
    }
};

page.tsx

"use client";
import React, { useEffect, useRef } from "react";
import { PoseLandmarkerResult } from "@mediapipe/tasks-vision";
import { drawConnectors } from "@/lib/draw-utils";

export default function WebWorkers2() {
    const videoRef = useRef<HTMLVideoElement>(null);

    const offscreenCanvasRef = useRef<OffscreenCanvas | null>(null);
    const offscreenCanvasCtxRef = useRef<OffscreenCanvasRenderingContext2D | null>(null);

    const drawCanvasRef = useRef<HTMLCanvasElement>(null);
    const workerRef = useRef<Worker | null>(null);

    const drawVideoToCanvas = () => {
        const video = videoRef.current;
        const worker = workerRef.current;

        if (!video || !worker) {
            console.log("invalid data");
            return;
        }
        const width = video.width;
        const height = video.height;

        if (!offscreenCanvasRef.current) {
            const offscreenCanvasInstance = new OffscreenCanvas(width, height);
            offscreenCanvasRef.current = offscreenCanvasInstance;
            offscreenCanvasCtxRef.current = offscreenCanvasInstance.getContext("2d", { willReadFrequently: true });
        }

        const ctx = offscreenCanvasCtxRef.current;
        const offscreenCanvas = offscreenCanvasRef.current;

        if (!offscreenCanvas || !ctx) {
            return;
        }

        ctx.drawImage(video, 0, 0, width, height);
        const imageBitmap = offscreenCanvas.transferToImageBitmap();
        const timestamp = performance.now();
        worker.postMessage({ action: "detectForVideo", frame: imageBitmap, timestamp }, [imageBitmap]);
        requestAnimationFrame(drawVideoToCanvas);
    };

    useEffect(() => {
        workerRef.current = new Worker(new URL("@/lib/pose-landmarker-worker.ts", import.meta.url));

        const ctx = drawCanvasRef.current?.getContext("2d")!;

        workerRef.current.onmessage = function (event: MessageEvent) {
            const result = event.data as PoseLandmarkerResult;

            if (ctx) {
                ctx.save();
                ctx.clearRect(0, 0, 360, 640);
                drawConnectors(result, ctx);
                ctx.restore();
            }
        };

        return () => {
            const worker = workerRef.current;
            if (worker) {
                worker.terminate();
            }
        };
    }, []);

    return (
        <div className="flex h-screen flex-col">
            <div className="flex ">
                <div className="relative">
                    <video
                        ref={videoRef}
                        onLoadedData={(event) => {
                            workerRef.current?.postMessage({
                                action: "init",
                                runningMode: "VIDEO",
                            });
                            setTimeout(drawVideoToCanvas, 3000);
                        }}
                        src={"/static/videos/body_scan.mp4"}
                        height={640}
                        width={360}
                        playsInline
                        autoPlay
                        muted
                        loop
                    />
                    <canvas ref={drawCanvasRef} className="absolute left-0 top-0" height={640} width={360} />
                </div>
            </div>
        </div>
    );
}

The outcome: https://drive.google.com/file/d/1KBt_VRzuE9zc-sco9d3tHZHj059fTYnf/view?usp=drive_link

Answer №1

I have not had the opportunity to test this out yet, but one thing that stands out is the absence of an await in the call to detectForVideo within the web worker. You might want to consider adding the await keyword:

const result = poseLandmarker.detectForVideo(imageBitmap, data.timestamp);

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Can you explain the purpose and functionality of the following code in Typescript: `export type Replace<T, R> = Omit<T, keyof R> & R;`

Despite my efforts, I am still struggling to grasp the concept of the Replace type. I have thoroughly reviewed the typescript documentation and gained some insight into what is happening in that line, but it remains elusive to me. ...

The Discord Oauth2 API query parameter is returning as empty

While integrating Discord OAuth2 into my Next.js application, I encountered an issue when deploying to Amplify. The functionality works smoothly on localhost but fails in production. On the profile page, there is a setup for users to link their Discord acc ...

Calculate the sum of elements within an array

I've been attempting to calculate the sum of values in an array based on different levels, but so far I haven't had much success. Here are the data I'm working with (stored in a variable called totalByLevel): https://i.stack.imgur.com/rOP9 ...

Creating a proxy using the Next.js API is not compatible with the next-http-proxy-middleware package

I'm attempting to create a proxy using the Next.js API, but it appears to be malfunctioning. This is how my pages/api/index.js file looks: import { NextApiRequest, NextApiResponse } from 'next'; import httpProxyMiddleware from 'next-ht ...

Sending information from service.ts to component

I'm encountering a roadblock with this issue, hopefully I can find some assistance here. Essentially, I am attempting to make a simple get http request in http.service and then pass the json object to the filter.service. From there, I aim to transfer ...

Styling of Bootstrap HTML element not appearing as expected

Recently, I've been trying out a new approach by embedding Bootstrap components in an iframe. However, despite loading the necessary stylesheet and scripts, the elements seem to be missing their styles. Can anyone shed some light on why this might be ...

What is the best method for accessing the properties of a JavaScript object based on input from a textbox?

Just starting out with angular and having trouble generating or updating a table based on text boxes. The schema includes country, sales, and profit fields. There are two text boxes for the x-axis and y-axis inputs. The table should dynamically update when ...

"Slow loading times experienced with Nextjs Image component when integrated with a map

Why do the images load slowly on localhost when using map, but quickly when not using it? I've tried various props with the Image component, but none seem to solve this issue. However, if I refresh the page after all images have rendered once, they ...

Leveraging the power of NestJS in conjunction with Apollo Server version 2

I recently delved into learning nestjs and decided to give this graphql example a try. The issue I encountered is that the example was originally designed for apollo-server version 1, and I'm having difficulty adapting it to work with apollo-server v ...

Can a standard tuple be matched with its corresponding key?

This code snippet showcases a function that can recognize that the key "banana" cannot have the value "red": type Fruits = { banana: 'yellow' | 'green' strawberry: 'red' } const fruit = <K extends keyof Fruits>(modu ...

Extracting PNG file from response (bypassing standard JSON extraction)

Despite my efforts to find a solution, I am still unable to resolve this specific issue: I have implemented an Angular request (localhost:4200) to an API on Spring (localhost:8080). The HttpService successfully handles the requests, except when it comes to ...

Utilizing React Router with the power of useCallback

My route configuration is set up as follows: const defineRoutes = (): React.ReactElement => ( <Switch> <Redirect exact from="/" to="/estimates" /> <Route exact path="/estimates" component={CostingPa ...

What is the process for converting/executing TypeScript into JavaScript?

Having trouble running https://github.com/airgram/airgram Encountering this warning message from the post (node:9374) Warning: To load an ES module, set "type": "module" Have already added {"type": "module"} To pa ...

Encountering a problem with Typescript and eslint while utilizing styled-components and Material UI: "Warning: React does not identify the `showText` prop on a DOM element."

While using a styled component to return a material ui Fab component, an error appears in the console: React does not recognize the `showText` prop on a DOM element. If you intentionally want it to appear in the DOM as a custom attribute, spell it as low ...

Combining server-side and client-side components, Next.js creates a versatile

Struggling with understanding how to handle server and client components in my Nextjs v14 app using the app router. The documentation here suggests wrapping a ServerComponent inside a ClientComponent, which is exactly what I need. <ClientComponent> ...

What is the best way to keep my layout component fixed in the Next13 app directory?

I am struggling to develop a custom layout component that can retrieve its own data. Despite adding 'cache: 'force-cache'' to the fetch function, the updated content from my CMS is still being loaded every time I refresh the page. Below ...

Breaking down large reducer into smaller reducers

I am currently working on a feature reducer (slice reducer) called animals. My goal is to separate these reducers into categories such as mammals, birds, fishes, and more. Initially, I thought this would be a smooth process using the ActionReducerMap. How ...

Unable to swap out string with text box in TypeScript

I am trying to swap __ with a text box in Angular 2/4. Take a look at the example provided in the link below. https://stackblitz.com/edit/angular-ajkvyq?file=app%2Fapp.component.ts ...

Mapbox GL JS stops displaying layers once a specific zoom level or distance threshold is reached

My map is using mapbox-gl and consists of only two layers: a marker and a circle that is centered on a specific point. The distance is dynamic, based on a predefined distance in meters. The issue I'm facing is that as I zoom in and move away from the ...

The type 'number[]' is lacking the properties 0, 1, 2, and 3 found in the type '[number, number, number, number]'

type spacing = [number, number, number, number] interface ISpacingProps { defaultValue?: spacing className?: string disabled?: boolean min?: number max?: number onChange?: (value: number | string) => void } interface IFieldState { value: ...