@pexip/media-processor 18.3.0 → 18.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +26 -28
- package/dist/common/backends/webgl/blender.d.ts +5 -4
- package/dist/common/backends/webgl/blender.js +4 -9
- package/dist/common/backends/webgl/blur.d.ts +3 -0
- package/dist/common/backends/webgl/blur.js +3 -0
- package/dist/common/backends/webgl/canvas.d.ts +3 -0
- package/dist/common/backends/webgl/canvas.js +3 -0
- package/dist/common/backends/webgl/renderer.js +18 -6
- package/dist/common/backends/webgl/shaders/blendFragmentShader.d.ts +1 -1
- package/dist/common/backends/webgl/shaders/blendFragmentShader.js +1 -43
- package/dist/common/backends/webgl/shaders/smoothingMaskFragmentShader.d.ts +1 -0
- package/dist/common/backends/webgl/shaders/smoothingMaskFragmentShader.js +57 -0
- package/dist/common/backends/webgl/smoothingMask.d.ts +12 -0
- package/dist/common/backends/webgl/smoothingMask.js +100 -0
- package/dist/common/backends/webgl/texture.d.ts +3 -0
- package/dist/common/backends/webgl/texture.js +3 -0
- package/dist/common/backends/webgl/textureToTexture.d.ts +8 -0
- package/dist/common/backends/webgl/textureToTexture.js +92 -0
- package/dist/common/tsconfig.tsbuildinfo +1 -1
- package/dist/common/types/segmentation.d.ts +4 -2
- package/dist/main/tsconfig.tsbuildinfo +1 -1
- package/dist/main/video/segmenters/mediapipe.d.ts +19 -0
- package/dist/main/video/segmenters/mediapipe.js +11 -3
- package/dist/main/video/videoStreamTrackProcessor.d.ts +6 -0
- package/dist/main/video/videoStreamTrackProcessor.js +6 -0
- package/dist/workers/mediaWorker.js +36 -22
- package/dist/workers/tsconfig.tsbuildinfo +1 -1
- package/dist/worklets/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 18.4.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 4bfce08: Decouple blender
|
|
8
|
+
- 31e4c17: Update media-processor's Readme
|
|
9
|
+
|
|
10
|
+
### Patch Changes
|
|
11
|
+
|
|
12
|
+
- 36d5672: Remove offscreencanvas types
|
|
13
|
+
- @pexip/utils@16.12.0
|
|
14
|
+
|
|
3
15
|
## 18.3.0
|
|
4
16
|
|
|
5
17
|
## 18.2.0
|
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ An package for media data processing using Web API.
|
|
|
6
6
|
|
|
7
7
|
`npm install @pexip/media-processor`
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## Usages
|
|
10
10
|
|
|
11
11
|
### Use Analyzer to get the MediaStream data
|
|
12
12
|
|
|
@@ -105,38 +105,36 @@ await audioGraph.release();
|
|
|
105
105
|
### Use background blur
|
|
106
106
|
|
|
107
107
|
```typescript
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
108
|
+
import {
|
|
109
|
+
createSegmenter,
|
|
110
|
+
createCanvasTransform,
|
|
111
|
+
createVideoTrackProcessor,
|
|
112
|
+
createVideoTrackProcessorWithFallback,
|
|
113
|
+
} from '@pexip/media-processor';
|
|
114
|
+
|
|
115
|
+
// Grab the user's camera stream
|
|
111
116
|
const stream = await navigator.mediaDevices.getUserMedia({video: true});
|
|
112
117
|
|
|
113
|
-
//
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
height: mediaState.height,
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
const getTrackProcessor = () => {
|
|
129
|
-
// Feature detection if the browser has the `MediaStreamProcessor` API
|
|
130
|
-
if ('MediaStreamTrackProcessor' in window) {
|
|
131
|
-
return processAPI === 'Streams API'
|
|
132
|
-
? createVideoTrackProcessor() // Using the latest Streams API
|
|
133
|
-
: createVideoTrackProcessorWithFallback(); // Using the fallback implementation
|
|
134
|
-
}
|
|
135
|
-
return createVideoTrackProcessorWithFallback(); // Using the fallback implementation
|
|
118
|
+
// Setting the path to that `@mediapipe/tasks-vision` assets
|
|
119
|
+
// It will be passed direct to
|
|
120
|
+
// [FilesetResolver.forVisionTasks()](https://ai.google.dev/edge/api/mediapipe/js/tasks-vision.filesetresolver#filesetresolverforvisiontasks)
|
|
121
|
+
const tasksVisionBasePath =
|
|
122
|
+
'A base path to specify the directory the Wasm files should be loaded from';
|
|
123
|
+
|
|
124
|
+
const modelAsset = {
|
|
125
|
+
/**
|
|
126
|
+
* Path to mediapipe selfie segmentation model asset
|
|
127
|
+
*/
|
|
128
|
+
path: 'A path to selfie segmentation model',
|
|
129
|
+
modelName: 'selfie' as const,
|
|
136
130
|
};
|
|
137
131
|
|
|
138
|
-
const
|
|
132
|
+
const segmenter = createSegmenter(tasksVisionBasePath, {modelAssets});
|
|
133
|
+
// Create a processing transformer and set the effects to `blur`
|
|
134
|
+
const transformer = createCanvasTransform(segmenter, {effects: 'blur'});
|
|
135
|
+
const processor = createVideoProcessor([transformer]);
|
|
139
136
|
|
|
137
|
+
// Start the processor
|
|
140
138
|
await videoProcessor.open();
|
|
141
139
|
|
|
142
140
|
// Passing the raw MediaStream to apply the effects
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import type { RendererOptions } from '../../types/render';
|
|
2
1
|
import type { TextureInfo } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Blend foreground and background according to the mask
|
|
4
|
+
*/
|
|
3
5
|
export declare const createBlendRenderer: (processor: OffscreenCanvas) => {
|
|
4
|
-
render: ({ mask,
|
|
5
|
-
prevMask?: TextureInfo | undefined;
|
|
6
|
+
render: ({ mask, foreground, background, }: {
|
|
6
7
|
mask: TextureInfo;
|
|
7
8
|
foreground: TextureInfo;
|
|
8
9
|
background: TextureInfo;
|
|
9
|
-
}
|
|
10
|
+
}) => TextureInfo;
|
|
10
11
|
release: () => void;
|
|
11
12
|
};
|
|
@@ -3,6 +3,9 @@ import { m3 } from '../../m3';
|
|
|
3
3
|
import { createGLProgram, createAndSetupTexture } from './webglUtils';
|
|
4
4
|
import { vertexShaderSource } from './shaders/vertexShader';
|
|
5
5
|
import { blendFragmentShaderSource } from './shaders/blendFragmentShader';
|
|
6
|
+
/**
|
|
7
|
+
* Blend foreground and background according to the mask
|
|
8
|
+
*/
|
|
6
9
|
export const createBlendRenderer = (processor) => {
|
|
7
10
|
const { gl, program, attribLocations, uniformLocations, release } = createGLProgram(processor, {
|
|
8
11
|
attribs: {
|
|
@@ -14,9 +17,6 @@ export const createBlendRenderer = (processor) => {
|
|
|
14
17
|
frameBackground: 'u_frame_background',
|
|
15
18
|
frameForeground: 'u_frame_foreground',
|
|
16
19
|
mask: 'u_mask',
|
|
17
|
-
maskCombineRatio: 'u_mask_combine_ratio',
|
|
18
|
-
foregroundThreshold: 'u_foreground_threshold',
|
|
19
|
-
prevMask: 'u_mask_prev',
|
|
20
20
|
},
|
|
21
21
|
vertexSrc: vertexShaderSource,
|
|
22
22
|
fragmentSrc: blendFragmentShaderSource,
|
|
@@ -60,7 +60,7 @@ export const createBlendRenderer = (processor) => {
|
|
|
60
60
|
gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
|
|
61
61
|
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, outputTexture, 0);
|
|
62
62
|
return {
|
|
63
|
-
render: ({ mask,
|
|
63
|
+
render: ({ mask, foreground, background, }) => {
|
|
64
64
|
gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
|
|
65
65
|
gl.viewport(0, 0, processor.width, processor.height);
|
|
66
66
|
gl.useProgram(program);
|
|
@@ -75,15 +75,10 @@ export const createBlendRenderer = (processor) => {
|
|
|
75
75
|
gl.activeTexture(gl.TEXTURE2);
|
|
76
76
|
gl.uniform1i(uniformLocations.mask, 2);
|
|
77
77
|
gl.bindTexture(gl.TEXTURE_2D, mask.texture);
|
|
78
|
-
gl.activeTexture(gl.TEXTURE3);
|
|
79
|
-
gl.uniform1i(uniformLocations.prevMask, 3);
|
|
80
|
-
gl.bindTexture(gl.TEXTURE_2D, prevMask?.texture ?? mask.texture);
|
|
81
78
|
// No Flip Y Coordinate for drawing on the frame buffer
|
|
82
79
|
// Project, position and scale to full-size of the canvas
|
|
83
80
|
const matrix = m3.projection(1, 1, 1);
|
|
84
81
|
gl.uniformMatrix3fv(uniformLocations.matrix, false, matrix);
|
|
85
|
-
gl.uniform1f(uniformLocations.maskCombineRatio, options.maskCombineRatio);
|
|
86
|
-
gl.uniform1f(uniformLocations.foregroundThreshold, options.foregroundThreshold);
|
|
87
82
|
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
|
88
83
|
assert(outputTexture);
|
|
89
84
|
return {
|
|
@@ -4,6 +4,9 @@ import { createAndSetupTexture, createGLProgram } from './webglUtils';
|
|
|
4
4
|
import { vertexShaderSource } from './shaders/vertexShader';
|
|
5
5
|
import { dualFilterBlurFragmentShaderSource } from './shaders/dualFilterBlurFragmentShader';
|
|
6
6
|
const clampBlurAmount = clamping(1, 9);
|
|
7
|
+
/**
|
|
8
|
+
* Blur provided frame
|
|
9
|
+
*/
|
|
7
10
|
export const createBlurRenderer = (processor, pass) => {
|
|
8
11
|
const { gl, program, attribLocations, uniformLocations, release } = createGLProgram(processor, {
|
|
9
12
|
attribs: {
|
|
@@ -2,6 +2,9 @@ import { m3 } from '../../m3';
|
|
|
2
2
|
import { createGLProgram } from './webglUtils';
|
|
3
3
|
import { vertexShaderSource } from './shaders/vertexShader';
|
|
4
4
|
import { textureFragmentShaderSource } from './shaders/textureFragmentShader';
|
|
5
|
+
/**
|
|
6
|
+
* Render texture to the processing canvas framebuffer
|
|
7
|
+
*/
|
|
5
8
|
export const createCanvasRenderer = (processor) => {
|
|
6
9
|
const { gl, program, attribLocations, uniformLocations, release } = createGLProgram(processor, {
|
|
7
10
|
attribs: {
|
|
@@ -5,6 +5,8 @@ import { createTextureRenderer } from './texture';
|
|
|
5
5
|
import { createBlurRenderer } from './blur';
|
|
6
6
|
import { createBlendRenderer } from './blender';
|
|
7
7
|
import { createCanvasRenderer } from './canvas';
|
|
8
|
+
import { createTextureToTextureRenderer } from './textureToTexture';
|
|
9
|
+
import { createSmoothingMaskRenderer } from './smoothingMask';
|
|
8
10
|
const createLazyProps = (stateBuilder) => {
|
|
9
11
|
const props = {};
|
|
10
12
|
return new Proxy(props, {
|
|
@@ -25,6 +27,8 @@ export const createRenderer = (canvas = new OffscreenCanvas(PROCESSING_WIDTH, PR
|
|
|
25
27
|
blendRenderer: () => createBlendRenderer(canvas),
|
|
26
28
|
blurRenderer: () => createBlurRenderer(canvas, props.backgroundBlurAmount),
|
|
27
29
|
maskBlurRenderer: () => createBlurRenderer(canvas, props.edgeBlurAmount),
|
|
30
|
+
textureRenderer: () => createTextureToTextureRenderer(canvas),
|
|
31
|
+
smoothingMaskRenderer: () => createSmoothingMaskRenderer(canvas),
|
|
28
32
|
release: (states) => () => Object.keys(states).forEach(key => key !== 'release' &&
|
|
29
33
|
states[key]?.release()),
|
|
30
34
|
};
|
|
@@ -58,6 +62,12 @@ export const createRenderer = (canvas = new OffscreenCanvas(PROCESSING_WIDTH, PR
|
|
|
58
62
|
get maskBlurRenderer() {
|
|
59
63
|
return lazyProps.maskBlurRenderer;
|
|
60
64
|
},
|
|
65
|
+
get textureRenderer() {
|
|
66
|
+
return lazyProps.textureRenderer;
|
|
67
|
+
},
|
|
68
|
+
get smoothingMaskRenderer() {
|
|
69
|
+
return lazyProps.smoothingMaskRenderer;
|
|
70
|
+
},
|
|
61
71
|
};
|
|
62
72
|
const updateProps = createObjectUpdater(props);
|
|
63
73
|
const update = options => {
|
|
@@ -81,6 +91,10 @@ export const createRenderer = (canvas = new OffscreenCanvas(PROCESSING_WIDTH, PR
|
|
|
81
91
|
if (options) {
|
|
82
92
|
updateProps(options);
|
|
83
93
|
}
|
|
94
|
+
const smoothedMask = props.smoothingMaskRenderer.render({
|
|
95
|
+
mask: toTextureInfo(mask),
|
|
96
|
+
prevMask: props.prevMask,
|
|
97
|
+
}, props);
|
|
84
98
|
const frameTexture = props.videoFrameRenderer.render(frame);
|
|
85
99
|
let background = frameTexture;
|
|
86
100
|
if (props.effects === 'blur' && props.backgroundBlurAmount) {
|
|
@@ -106,20 +120,18 @@ export const createRenderer = (canvas = new OffscreenCanvas(PROCESSING_WIDTH, PR
|
|
|
106
120
|
}
|
|
107
121
|
}
|
|
108
122
|
const blendedFrameTexture = props.blendRenderer.render({
|
|
109
|
-
|
|
110
|
-
mask: toTextureInfo(mask),
|
|
123
|
+
mask: smoothedMask,
|
|
111
124
|
foreground: frameTexture,
|
|
112
125
|
background,
|
|
113
|
-
}
|
|
126
|
+
});
|
|
114
127
|
props.canvasRenderer.render(blendedFrameTexture);
|
|
115
|
-
|
|
116
|
-
props.prevMask =
|
|
128
|
+
// Cloned the current smoothedMask so it can be fed back to the next round
|
|
129
|
+
props.prevMask = props.textureRenderer.render(smoothedMask);
|
|
117
130
|
};
|
|
118
131
|
const release = () => {
|
|
119
132
|
lazyProps.release();
|
|
120
133
|
lazyProps = createLazyProps(rendererCreator);
|
|
121
134
|
props.backgroundImages.clear();
|
|
122
|
-
props.prevMask?.close();
|
|
123
135
|
props.prevMask = undefined;
|
|
124
136
|
};
|
|
125
137
|
return { init, render, release, update };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const blendFragmentShaderSource = "#version 300 es\nprecision mediump float;\n\n// our textures\nuniform sampler2D u_mask;\nuniform sampler2D
|
|
1
|
+
export declare const blendFragmentShaderSource = "#version 300 es\nprecision mediump float;\n\n// our textures\nuniform sampler2D u_mask;\nuniform sampler2D u_frame_foreground;\nuniform sampler2D u_frame_background;\n\nin vec2 v_texCoord;\n\n// we need to declare an output for the fragment shader\nout vec4 outColor;\n\nvoid main() {\n vec4 background = texture(u_frame_background, v_texCoord);\n vec4 foreground = texture(u_frame_foreground, v_texCoord);\n float filteredMask = texture(u_mask, v_texCoord).r;\n\n outColor = mix(background, foreground, filteredMask);\n}\n";
|
|
@@ -3,60 +3,18 @@ precision mediump float;
|
|
|
3
3
|
|
|
4
4
|
// our textures
|
|
5
5
|
uniform sampler2D u_mask;
|
|
6
|
-
uniform sampler2D u_mask_prev;
|
|
7
6
|
uniform sampler2D u_frame_foreground;
|
|
8
7
|
uniform sampler2D u_frame_background;
|
|
9
8
|
|
|
10
|
-
// control flags
|
|
11
|
-
uniform float u_mask_combine_ratio;
|
|
12
|
-
uniform float u_foreground_threshold;
|
|
13
|
-
|
|
14
9
|
in vec2 v_texCoord;
|
|
15
10
|
|
|
16
11
|
// we need to declare an output for the fragment shader
|
|
17
12
|
out vec4 outColor;
|
|
18
13
|
|
|
19
|
-
// Source: https://github.com/google/mediapipe/blob/8750c3dca1bc00fbe47acf501b7c9c61f3f3bd3f/mediapipe/calculators/image/segmentation_smoothing_calculator.cc#L377
|
|
20
|
-
float SmoothingMask(sampler2D prevMask, sampler2D currMask, vec2 uv, float ratio) {
|
|
21
|
-
float prevMaskValue = texture(prevMask, uv).r;
|
|
22
|
-
float newMaskValue = texture(currMask, uv).r;
|
|
23
|
-
|
|
24
|
-
/*
|
|
25
|
-
* Assume p := newMaskValue
|
|
26
|
-
* H(p) := 1 + (p * log(p) + (1-p) * log(1-p)) / log(2)
|
|
27
|
-
* uncertainty alpha(p) =
|
|
28
|
-
* Clamp(1 - (1 - H(p)) * (1 - H(p)), 0, 1) [squaring the
|
|
29
|
-
* uncertainty]
|
|
30
|
-
*
|
|
31
|
-
* The following polynomial approximates uncertainty alpha as a
|
|
32
|
-
* function of (p + 0.5):
|
|
33
|
-
*/
|
|
34
|
-
const float c1 = 5.68842;
|
|
35
|
-
const float c2 = -0.748699;
|
|
36
|
-
const float c3 = -57.8051;
|
|
37
|
-
const float c4 = 291.309;
|
|
38
|
-
const float c5 = -624.717;
|
|
39
|
-
float t = newMaskValue - 0.5;
|
|
40
|
-
float x = t * t;
|
|
41
|
-
|
|
42
|
-
float uncertainty =
|
|
43
|
-
1.0 - min(1.0, x * (c1 + x * (c2 + x * (c3 + x * (c4 + x * c5)))));
|
|
44
|
-
|
|
45
|
-
float outputValue = newMaskValue + (prevMaskValue - newMaskValue) *
|
|
46
|
-
(uncertainty * ratio);
|
|
47
|
-
return outputValue;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
14
|
void main() {
|
|
51
|
-
vec2 halfpixel = 0.5 / vec2(textureSize(u_frame_background, 0));
|
|
52
|
-
|
|
53
15
|
vec4 background = texture(u_frame_background, v_texCoord);
|
|
54
16
|
vec4 foreground = texture(u_frame_foreground, v_texCoord);
|
|
55
|
-
float
|
|
56
|
-
float filteredMask = maskAlpha;
|
|
57
|
-
if (maskAlpha < u_foreground_threshold) {
|
|
58
|
-
filteredMask = 0.0;
|
|
59
|
-
}
|
|
17
|
+
float filteredMask = texture(u_mask, v_texCoord).r;
|
|
60
18
|
|
|
61
19
|
outColor = mix(background, foreground, filteredMask);
|
|
62
20
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const smoothingMaskFragmentShaderSource = "#version 300 es\nprecision mediump float;\n\n// our textures\nuniform sampler2D u_mask;\nuniform sampler2D u_mask_prev;\n\n// control flags\nuniform float u_mask_combine_ratio;\nuniform float u_foreground_threshold;\n\nin vec2 v_texCoord;\n\n// we need to declare an output for the fragment shader\nout vec4 outColor;\n\n// Source: https://github.com/google/mediapipe/blob/8750c3dca1bc00fbe47acf501b7c9c61f3f3bd3f/mediapipe/calculators/image/segmentation_smoothing_calculator.cc#L377\nfloat SmoothingMask(sampler2D prevMask, sampler2D currMask, vec2 uv, float ratio) {\n float prevMaskValue = texture(prevMask, uv).r;\n float newMaskValue = texture(currMask, uv).r;\n\n /*\n * Assume p := newMaskValue\n * H(p) := 1 + (p * log(p) + (1-p) * log(1-p)) / log(2)\n * uncertainty alpha(p) =\n * Clamp(1 - (1 - H(p)) * (1 - H(p)), 0, 1) [squaring the\n * uncertainty]\n *\n * The following polynomial approximates uncertainty alpha as a\n * function of (p + 0.5):\n */\n const float c1 = 5.68842;\n const float c2 = -0.748699;\n const float c3 = -57.8051;\n const float c4 = 291.309;\n const float c5 = -624.717;\n float t = newMaskValue - 0.5;\n float x = t * t;\n\n float uncertainty =\n 1.0 - min(1.0, x * (c1 + x * (c2 + x * (c3 + x * (c4 + x * c5)))));\n\n float outputValue = newMaskValue + (prevMaskValue - newMaskValue) *\n (uncertainty * ratio);\n return outputValue;\n}\n\nvoid main() {\n float maskAlpha = SmoothingMask(u_mask_prev, u_mask, v_texCoord, u_mask_combine_ratio);\n float backgroundMask = maskAlpha;\n if (backgroundMask < u_foreground_threshold) {\n backgroundMask = 0.0;\n }\n\n outColor = vec4(backgroundMask, backgroundMask, backgroundMask, backgroundMask);\n}\n";
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export const smoothingMaskFragmentShaderSource = `#version 300 es
|
|
2
|
+
precision mediump float;
|
|
3
|
+
|
|
4
|
+
// our textures
|
|
5
|
+
uniform sampler2D u_mask;
|
|
6
|
+
uniform sampler2D u_mask_prev;
|
|
7
|
+
|
|
8
|
+
// control flags
|
|
9
|
+
uniform float u_mask_combine_ratio;
|
|
10
|
+
uniform float u_foreground_threshold;
|
|
11
|
+
|
|
12
|
+
in vec2 v_texCoord;
|
|
13
|
+
|
|
14
|
+
// we need to declare an output for the fragment shader
|
|
15
|
+
out vec4 outColor;
|
|
16
|
+
|
|
17
|
+
// Source: https://github.com/google/mediapipe/blob/8750c3dca1bc00fbe47acf501b7c9c61f3f3bd3f/mediapipe/calculators/image/segmentation_smoothing_calculator.cc#L377
|
|
18
|
+
float SmoothingMask(sampler2D prevMask, sampler2D currMask, vec2 uv, float ratio) {
|
|
19
|
+
float prevMaskValue = texture(prevMask, uv).r;
|
|
20
|
+
float newMaskValue = texture(currMask, uv).r;
|
|
21
|
+
|
|
22
|
+
/*
|
|
23
|
+
* Assume p := newMaskValue
|
|
24
|
+
* H(p) := 1 + (p * log(p) + (1-p) * log(1-p)) / log(2)
|
|
25
|
+
* uncertainty alpha(p) =
|
|
26
|
+
* Clamp(1 - (1 - H(p)) * (1 - H(p)), 0, 1) [squaring the
|
|
27
|
+
* uncertainty]
|
|
28
|
+
*
|
|
29
|
+
* The following polynomial approximates uncertainty alpha as a
|
|
30
|
+
* function of (p + 0.5):
|
|
31
|
+
*/
|
|
32
|
+
const float c1 = 5.68842;
|
|
33
|
+
const float c2 = -0.748699;
|
|
34
|
+
const float c3 = -57.8051;
|
|
35
|
+
const float c4 = 291.309;
|
|
36
|
+
const float c5 = -624.717;
|
|
37
|
+
float t = newMaskValue - 0.5;
|
|
38
|
+
float x = t * t;
|
|
39
|
+
|
|
40
|
+
float uncertainty =
|
|
41
|
+
1.0 - min(1.0, x * (c1 + x * (c2 + x * (c3 + x * (c4 + x * c5)))));
|
|
42
|
+
|
|
43
|
+
float outputValue = newMaskValue + (prevMaskValue - newMaskValue) *
|
|
44
|
+
(uncertainty * ratio);
|
|
45
|
+
return outputValue;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
void main() {
|
|
49
|
+
float maskAlpha = SmoothingMask(u_mask_prev, u_mask, v_texCoord, u_mask_combine_ratio);
|
|
50
|
+
float backgroundMask = maskAlpha;
|
|
51
|
+
if (backgroundMask < u_foreground_threshold) {
|
|
52
|
+
backgroundMask = 0.0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
outColor = vec4(backgroundMask, backgroundMask, backgroundMask, backgroundMask);
|
|
56
|
+
}
|
|
57
|
+
`;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { RendererOptions } from '../../types/render';
|
|
2
|
+
import type { TextureInfo } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Smooth the mask edge
|
|
5
|
+
*/
|
|
6
|
+
export declare const createSmoothingMaskRenderer: (processor: OffscreenCanvas) => {
|
|
7
|
+
render: ({ mask, prevMask, }: {
|
|
8
|
+
prevMask?: TextureInfo | undefined;
|
|
9
|
+
mask: TextureInfo;
|
|
10
|
+
}, options: RendererOptions) => TextureInfo;
|
|
11
|
+
release: () => void;
|
|
12
|
+
};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { assert } from '../../utils';
|
|
2
|
+
import { m3 } from '../../m3';
|
|
3
|
+
import { createGLProgram, createAndSetupTexture } from './webglUtils';
|
|
4
|
+
import { vertexShaderSource } from './shaders/vertexShader';
|
|
5
|
+
import { smoothingMaskFragmentShaderSource } from './shaders/smoothingMaskFragmentShader';
|
|
6
|
+
/**
|
|
7
|
+
* Smooth the mask edge
|
|
8
|
+
*/
|
|
9
|
+
export const createSmoothingMaskRenderer = (processor) => {
|
|
10
|
+
const { gl, program, attribLocations, uniformLocations, release } = createGLProgram(processor, {
|
|
11
|
+
attribs: {
|
|
12
|
+
position: 'a_position',
|
|
13
|
+
texCoord: 'a_texCoord',
|
|
14
|
+
},
|
|
15
|
+
uniforms: {
|
|
16
|
+
matrix: 'u_matrix',
|
|
17
|
+
mask: 'u_mask',
|
|
18
|
+
maskCombineRatio: 'u_mask_combine_ratio',
|
|
19
|
+
foregroundThreshold: 'u_foreground_threshold',
|
|
20
|
+
prevMask: 'u_mask_prev',
|
|
21
|
+
},
|
|
22
|
+
vertexSrc: vertexShaderSource,
|
|
23
|
+
fragmentSrc: smoothingMaskFragmentShaderSource,
|
|
24
|
+
});
|
|
25
|
+
const vao = gl.createVertexArray();
|
|
26
|
+
gl.bindVertexArray(vao);
|
|
27
|
+
// Position
|
|
28
|
+
const positionBuffer = gl.createBuffer();
|
|
29
|
+
gl.enableVertexAttribArray(attribLocations.position);
|
|
30
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
|
|
31
|
+
gl.vertexAttribPointer(attribLocations.position, 2, gl.FLOAT, false, 0, 0);
|
|
32
|
+
gl.bufferData(gl.ARRAY_BUFFER,
|
|
33
|
+
// prettier-ignore
|
|
34
|
+
new Float32Array([
|
|
35
|
+
0, 0,
|
|
36
|
+
0, 1,
|
|
37
|
+
1, 0,
|
|
38
|
+
1, 0,
|
|
39
|
+
0, 1,
|
|
40
|
+
1, 1,
|
|
41
|
+
]), gl.STATIC_DRAW);
|
|
42
|
+
// Texture coordinates for the rectangle
|
|
43
|
+
const texCoordBuffer = gl.createBuffer();
|
|
44
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
|
|
45
|
+
gl.bufferData(gl.ARRAY_BUFFER,
|
|
46
|
+
// prettier-ignore
|
|
47
|
+
new Float32Array([
|
|
48
|
+
0, 0,
|
|
49
|
+
0, 1,
|
|
50
|
+
1, 0,
|
|
51
|
+
1, 0,
|
|
52
|
+
0, 1,
|
|
53
|
+
1, 1,
|
|
54
|
+
]), gl.STATIC_DRAW);
|
|
55
|
+
gl.enableVertexAttribArray(attribLocations.texCoord);
|
|
56
|
+
gl.vertexAttribPointer(attribLocations.texCoord, 2, gl.FLOAT, false, 0, 0);
|
|
57
|
+
const outputTexture = createAndSetupTexture(gl);
|
|
58
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, processor.width, processor.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
|
|
59
|
+
const frameBuffer = gl.createFramebuffer();
|
|
60
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
|
|
61
|
+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, outputTexture, 0);
|
|
62
|
+
return {
|
|
63
|
+
render: ({ mask, prevMask, }, options) => {
|
|
64
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
|
|
65
|
+
gl.viewport(0, 0, processor.width, processor.height);
|
|
66
|
+
gl.clearColor(0, 0, 0, 0);
|
|
67
|
+
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
68
|
+
gl.useProgram(program);
|
|
69
|
+
gl.bindVertexArray(vao);
|
|
70
|
+
// Textures
|
|
71
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
72
|
+
gl.uniform1i(uniformLocations.mask, 0);
|
|
73
|
+
gl.bindTexture(gl.TEXTURE_2D, mask.texture);
|
|
74
|
+
gl.activeTexture(gl.TEXTURE1);
|
|
75
|
+
gl.uniform1i(uniformLocations.prevMask, 1);
|
|
76
|
+
gl.bindTexture(gl.TEXTURE_2D, prevMask?.texture ?? mask.texture);
|
|
77
|
+
// No Flip Y Coordinate for drawing on the frame buffer
|
|
78
|
+
// Project, position and scale to full-size of the canvas
|
|
79
|
+
const matrix = m3.projection(1, 1, 1);
|
|
80
|
+
gl.uniformMatrix3fv(uniformLocations.matrix, false, matrix);
|
|
81
|
+
gl.uniform1f(uniformLocations.maskCombineRatio, options.maskCombineRatio);
|
|
82
|
+
gl.uniform1f(uniformLocations.foregroundThreshold, options.foregroundThreshold);
|
|
83
|
+
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
|
84
|
+
assert(outputTexture);
|
|
85
|
+
return {
|
|
86
|
+
texture: outputTexture,
|
|
87
|
+
width: processor.width,
|
|
88
|
+
height: processor.height,
|
|
89
|
+
};
|
|
90
|
+
},
|
|
91
|
+
release: () => {
|
|
92
|
+
gl.deleteBuffer(positionBuffer);
|
|
93
|
+
gl.deleteBuffer(texCoordBuffer);
|
|
94
|
+
gl.deleteTexture(outputTexture);
|
|
95
|
+
gl.deleteFramebuffer(frameBuffer);
|
|
96
|
+
gl.deleteVertexArray(vao);
|
|
97
|
+
release();
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
};
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import type { VideoFrameLike } from '../../types/media';
|
|
2
2
|
import type { TextureInfo } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Render Video frame into texture
|
|
5
|
+
*/
|
|
3
6
|
export declare const createTextureRenderer: (processor: OffscreenCanvas) => {
|
|
4
7
|
render: (video: VideoFrameLike) => TextureInfo;
|
|
5
8
|
release: () => void;
|
|
@@ -3,6 +3,9 @@ import { m3 } from '../../m3';
|
|
|
3
3
|
import { createAndSetupTexture, createGLProgram } from './webglUtils';
|
|
4
4
|
import { vertexShaderSource } from './shaders/vertexShader';
|
|
5
5
|
import { textureFragmentShaderSource } from './shaders/textureFragmentShader';
|
|
6
|
+
/**
|
|
7
|
+
* Render Video frame into texture
|
|
8
|
+
*/
|
|
6
9
|
export const createTextureRenderer = (processor) => {
|
|
7
10
|
const { gl, program, attribLocations, uniformLocations, release } = createGLProgram(processor, {
|
|
8
11
|
attribs: {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { TextureInfo } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Render texture into another texture, i.e. clone
|
|
4
|
+
*/
|
|
5
|
+
export declare const createTextureToTextureRenderer: (processor: OffscreenCanvas) => {
|
|
6
|
+
render: (inputTexture: TextureInfo) => TextureInfo;
|
|
7
|
+
release: () => void;
|
|
8
|
+
};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { assert } from '../../utils';
|
|
2
|
+
import { m3 } from '../../m3';
|
|
3
|
+
import { createAndSetupTexture, createGLProgram } from './webglUtils';
|
|
4
|
+
import { vertexShaderSource } from './shaders/vertexShader';
|
|
5
|
+
import { textureFragmentShaderSource } from './shaders/textureFragmentShader';
|
|
6
|
+
/**
|
|
7
|
+
* Render texture into another texture, i.e. clone
|
|
8
|
+
*/
|
|
9
|
+
export const createTextureToTextureRenderer = (processor) => {
|
|
10
|
+
const { gl, program, attribLocations, uniformLocations, release } = createGLProgram(processor, {
|
|
11
|
+
attribs: {
|
|
12
|
+
position: 'a_position',
|
|
13
|
+
texCoord: 'a_texCoord',
|
|
14
|
+
},
|
|
15
|
+
uniforms: {
|
|
16
|
+
matrix: 'u_matrix',
|
|
17
|
+
texture: 'u_texture',
|
|
18
|
+
},
|
|
19
|
+
vertexSrc: vertexShaderSource,
|
|
20
|
+
fragmentSrc: textureFragmentShaderSource,
|
|
21
|
+
});
|
|
22
|
+
const vao = gl.createVertexArray();
|
|
23
|
+
gl.bindVertexArray(vao);
|
|
24
|
+
// Position
|
|
25
|
+
const positionBuffer = gl.createBuffer();
|
|
26
|
+
gl.enableVertexAttribArray(attribLocations.position);
|
|
27
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
|
|
28
|
+
gl.vertexAttribPointer(attribLocations.position, 2, gl.FLOAT, false, 0, 0);
|
|
29
|
+
// Put a unit quad in the buffer
|
|
30
|
+
gl.bufferData(gl.ARRAY_BUFFER,
|
|
31
|
+
// prettier-ignore
|
|
32
|
+
new Float32Array([
|
|
33
|
+
0, 0,
|
|
34
|
+
0, 1,
|
|
35
|
+
1, 0,
|
|
36
|
+
1, 0,
|
|
37
|
+
0, 1,
|
|
38
|
+
1, 1,
|
|
39
|
+
]), gl.STATIC_DRAW);
|
|
40
|
+
// Texture coordinates for the rectangle
|
|
41
|
+
const texCoordBuffer = gl.createBuffer();
|
|
42
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
|
|
43
|
+
gl.bufferData(gl.ARRAY_BUFFER,
|
|
44
|
+
// prettier-ignore
|
|
45
|
+
new Float32Array([
|
|
46
|
+
0, 0,
|
|
47
|
+
0, 1,
|
|
48
|
+
1, 0,
|
|
49
|
+
1, 0,
|
|
50
|
+
0, 1,
|
|
51
|
+
1, 1,
|
|
52
|
+
]), gl.STATIC_DRAW);
|
|
53
|
+
gl.enableVertexAttribArray(attribLocations.texCoord);
|
|
54
|
+
gl.vertexAttribPointer(attribLocations.texCoord, 2, gl.FLOAT, false, 0, 0);
|
|
55
|
+
const outputTexture = createAndSetupTexture(gl);
|
|
56
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, processor.width, processor.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
|
|
57
|
+
const frameBuffer = gl.createFramebuffer();
|
|
58
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
|
|
59
|
+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, outputTexture, 0);
|
|
60
|
+
return {
|
|
61
|
+
render: (inputTexture) => {
|
|
62
|
+
// Draw on the frame buffer
|
|
63
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
|
|
64
|
+
gl.viewport(0, 0, processor.width, processor.height);
|
|
65
|
+
gl.clearColor(0, 0, 0, 1);
|
|
66
|
+
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
67
|
+
gl.useProgram(program);
|
|
68
|
+
gl.bindVertexArray(vao);
|
|
69
|
+
// Texture Unit setup
|
|
70
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
71
|
+
gl.uniform1i(uniformLocations.texture, 0);
|
|
72
|
+
gl.bindTexture(gl.TEXTURE_2D, inputTexture.texture);
|
|
73
|
+
// Project, position and scale to full-size of the canvas
|
|
74
|
+
const matrix = m3.projection(1, 1, 1);
|
|
75
|
+
gl.uniformMatrix3fv(uniformLocations.matrix, false, matrix);
|
|
76
|
+
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
|
77
|
+
assert(outputTexture);
|
|
78
|
+
return {
|
|
79
|
+
texture: outputTexture,
|
|
80
|
+
width: processor.width,
|
|
81
|
+
height: processor.height,
|
|
82
|
+
};
|
|
83
|
+
},
|
|
84
|
+
release: () => {
|
|
85
|
+
gl.deleteBuffer(positionBuffer);
|
|
86
|
+
gl.deleteBuffer(texCoordBuffer);
|
|
87
|
+
gl.deleteTexture(outputTexture);
|
|
88
|
+
gl.deleteVertexArray(vao);
|
|
89
|
+
release();
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
};
|