@stream-io/video-filters-web 0.0.1
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 +10 -0
- package/LICENSE +202 -0
- package/README.md +50 -0
- package/dist/index.cjs.js +1558 -0
- package/dist/index.cjs.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.es.js +1554 -0
- package/dist/index.es.js.map +1 -0
- package/dist/src/compatibility.d.ts +5 -0
- package/dist/src/createRenderer.d.ts +17 -0
- package/dist/src/helpers/webglHelper.d.ts +17 -0
- package/dist/src/segmentation.d.ts +9 -0
- package/dist/src/tflite.d.ts +19 -0
- package/dist/src/version.d.ts +2 -0
- package/dist/src/webgl2/backgroundBlurStage.d.ts +7 -0
- package/dist/src/webgl2/backgroundImageStage.d.ts +8 -0
- package/dist/src/webgl2/jointBilateralFilterStage.d.ts +7 -0
- package/dist/src/webgl2/resizingStage.d.ts +6 -0
- package/dist/src/webgl2/softmaxStage.d.ts +6 -0
- package/dist/src/webgl2/webgl2Pipeline.d.ts +8 -0
- package/index.ts +4 -0
- package/package.json +40 -0
- package/src/compatibility.ts +23 -0
- package/src/createRenderer.ts +63 -0
- package/src/helpers/webglHelper.ts +142 -0
- package/src/segmentation.ts +18 -0
- package/src/tflite-simd.js +874 -0
- package/src/tflite.ts +59 -0
- package/src/version.ts +3 -0
- package/src/webgl2/backgroundBlurStage.ts +307 -0
- package/src/webgl2/backgroundImageStage.ts +222 -0
- package/src/webgl2/jointBilateralFilterStage.ts +159 -0
- package/src/webgl2/resizingStage.ts +103 -0
- package/src/webgl2/softmaxStage.ts +103 -0
- package/src/webgl2/webgl2Pipeline.ts +200 -0
- package/tf/models/segm_full_v679.tflite +0 -0
- package/tf/tflite/tflite-simd.wasm +0 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import {
|
|
2
|
+
compileShader,
|
|
3
|
+
createPipelineStageProgram,
|
|
4
|
+
glsl,
|
|
5
|
+
} from '../helpers/webglHelper';
|
|
6
|
+
import { SegmentationParams } from '../segmentation';
|
|
7
|
+
|
|
8
|
+
export function buildJointBilateralFilterStage(
|
|
9
|
+
gl: WebGL2RenderingContext,
|
|
10
|
+
vertexShader: WebGLShader,
|
|
11
|
+
positionBuffer: WebGLBuffer,
|
|
12
|
+
texCoordBuffer: WebGLBuffer,
|
|
13
|
+
inputTexture: WebGLTexture,
|
|
14
|
+
outputTexture: WebGLTexture,
|
|
15
|
+
canvas: HTMLCanvasElement,
|
|
16
|
+
segmentationConfig: SegmentationParams,
|
|
17
|
+
) {
|
|
18
|
+
const fragmentShaderSource = glsl`#version 300 es
|
|
19
|
+
|
|
20
|
+
precision highp float;
|
|
21
|
+
|
|
22
|
+
uniform sampler2D u_inputFrame;
|
|
23
|
+
uniform sampler2D u_segmentationMask;
|
|
24
|
+
uniform vec2 u_texelSize;
|
|
25
|
+
uniform float u_step;
|
|
26
|
+
uniform float u_radius;
|
|
27
|
+
uniform float u_offset;
|
|
28
|
+
uniform float u_sigmaTexel;
|
|
29
|
+
uniform float u_sigmaColor;
|
|
30
|
+
|
|
31
|
+
in vec2 v_texCoord;
|
|
32
|
+
out vec4 outColor;
|
|
33
|
+
|
|
34
|
+
float gaussian(float x, float sigma) {
|
|
35
|
+
float coeff = -0.5 / (sigma * sigma * 4.0 + 1.0e-6);
|
|
36
|
+
return exp((x * x) * coeff);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
void main() {
|
|
40
|
+
vec2 centerCoord = v_texCoord;
|
|
41
|
+
vec3 centerColor = texture(u_inputFrame, centerCoord).rgb;
|
|
42
|
+
float newVal = 0.0;
|
|
43
|
+
|
|
44
|
+
float spaceWeight = 0.0;
|
|
45
|
+
float colorWeight = 0.0;
|
|
46
|
+
float totalWeight = 0.0;
|
|
47
|
+
|
|
48
|
+
// Subsample kernel space.
|
|
49
|
+
for (float i = -u_radius + u_offset; i <= u_radius; i += u_step) {
|
|
50
|
+
for (float j = -u_radius + u_offset; j <= u_radius; j += u_step) {
|
|
51
|
+
vec2 shift = vec2(j, i) * u_texelSize;
|
|
52
|
+
vec2 coord = vec2(centerCoord + shift);
|
|
53
|
+
vec3 frameColor = texture(u_inputFrame, coord).rgb;
|
|
54
|
+
float outVal = texture(u_segmentationMask, coord).a;
|
|
55
|
+
|
|
56
|
+
spaceWeight = gaussian(distance(centerCoord, coord), u_sigmaTexel);
|
|
57
|
+
colorWeight = gaussian(distance(centerColor, frameColor), u_sigmaColor);
|
|
58
|
+
totalWeight += spaceWeight * colorWeight;
|
|
59
|
+
|
|
60
|
+
newVal += spaceWeight * colorWeight * outVal;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
newVal /= totalWeight;
|
|
64
|
+
|
|
65
|
+
outColor = vec4(vec3(0.0), newVal);
|
|
66
|
+
}
|
|
67
|
+
`;
|
|
68
|
+
|
|
69
|
+
const { width: segmentationWidth, height: segmentationHeight } =
|
|
70
|
+
segmentationConfig;
|
|
71
|
+
const { width: outputWidth, height: outputHeight } = canvas;
|
|
72
|
+
const texelWidth = 1 / outputWidth;
|
|
73
|
+
const texelHeight = 1 / outputHeight;
|
|
74
|
+
|
|
75
|
+
const fragmentShader = compileShader(
|
|
76
|
+
gl,
|
|
77
|
+
gl.FRAGMENT_SHADER,
|
|
78
|
+
fragmentShaderSource,
|
|
79
|
+
);
|
|
80
|
+
const program = createPipelineStageProgram(
|
|
81
|
+
gl,
|
|
82
|
+
vertexShader,
|
|
83
|
+
fragmentShader,
|
|
84
|
+
positionBuffer,
|
|
85
|
+
texCoordBuffer,
|
|
86
|
+
);
|
|
87
|
+
const inputFrameLocation = gl.getUniformLocation(program, 'u_inputFrame');
|
|
88
|
+
const segmentationMaskLocation = gl.getUniformLocation(
|
|
89
|
+
program,
|
|
90
|
+
'u_segmentationMask',
|
|
91
|
+
);
|
|
92
|
+
const texelSizeLocation = gl.getUniformLocation(program, 'u_texelSize');
|
|
93
|
+
const stepLocation = gl.getUniformLocation(program, 'u_step');
|
|
94
|
+
const radiusLocation = gl.getUniformLocation(program, 'u_radius');
|
|
95
|
+
const offsetLocation = gl.getUniformLocation(program, 'u_offset');
|
|
96
|
+
const sigmaTexelLocation = gl.getUniformLocation(program, 'u_sigmaTexel');
|
|
97
|
+
const sigmaColorLocation = gl.getUniformLocation(program, 'u_sigmaColor');
|
|
98
|
+
|
|
99
|
+
const frameBuffer = gl.createFramebuffer();
|
|
100
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
|
|
101
|
+
gl.framebufferTexture2D(
|
|
102
|
+
gl.FRAMEBUFFER,
|
|
103
|
+
gl.COLOR_ATTACHMENT0,
|
|
104
|
+
gl.TEXTURE_2D,
|
|
105
|
+
outputTexture,
|
|
106
|
+
0,
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
gl.useProgram(program);
|
|
110
|
+
gl.uniform1i(inputFrameLocation, 0);
|
|
111
|
+
gl.uniform1i(segmentationMaskLocation, 1);
|
|
112
|
+
gl.uniform2f(texelSizeLocation, texelWidth, texelHeight);
|
|
113
|
+
|
|
114
|
+
// Ensures default values are configured to prevent infinite
|
|
115
|
+
// loop in fragment shader
|
|
116
|
+
updateSigmaSpace(0);
|
|
117
|
+
updateSigmaColor(0);
|
|
118
|
+
|
|
119
|
+
function render() {
|
|
120
|
+
gl.viewport(0, 0, outputWidth, outputHeight);
|
|
121
|
+
gl.useProgram(program);
|
|
122
|
+
gl.activeTexture(gl.TEXTURE1);
|
|
123
|
+
gl.bindTexture(gl.TEXTURE_2D, inputTexture);
|
|
124
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
|
|
125
|
+
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function updateSigmaSpace(sigmaSpace: number) {
|
|
129
|
+
sigmaSpace *= Math.max(
|
|
130
|
+
outputWidth / segmentationWidth,
|
|
131
|
+
outputHeight / segmentationHeight,
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
const kSparsityFactor = 0.66; // Higher is sparser.
|
|
135
|
+
const step = Math.max(1, Math.sqrt(sigmaSpace) * kSparsityFactor);
|
|
136
|
+
const radius = sigmaSpace;
|
|
137
|
+
const offset = step > 1 ? step * 0.5 : 0;
|
|
138
|
+
const sigmaTexel = Math.max(texelWidth, texelHeight) * sigmaSpace;
|
|
139
|
+
|
|
140
|
+
gl.useProgram(program);
|
|
141
|
+
gl.uniform1f(stepLocation, step);
|
|
142
|
+
gl.uniform1f(radiusLocation, radius);
|
|
143
|
+
gl.uniform1f(offsetLocation, offset);
|
|
144
|
+
gl.uniform1f(sigmaTexelLocation, sigmaTexel);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function updateSigmaColor(sigmaColor: number) {
|
|
148
|
+
gl.useProgram(program);
|
|
149
|
+
gl.uniform1f(sigmaColorLocation, sigmaColor);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function cleanUp() {
|
|
153
|
+
gl.deleteFramebuffer(frameBuffer);
|
|
154
|
+
gl.deleteProgram(program);
|
|
155
|
+
gl.deleteShader(fragmentShader);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return { render, updateSigmaSpace, updateSigmaColor, cleanUp };
|
|
159
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { TFLite } from '../tflite';
|
|
2
|
+
import {
|
|
3
|
+
compileShader,
|
|
4
|
+
createPipelineStageProgram,
|
|
5
|
+
createTexture,
|
|
6
|
+
glsl,
|
|
7
|
+
readPixelsAsync,
|
|
8
|
+
} from '../helpers/webglHelper';
|
|
9
|
+
import { SegmentationParams } from '../segmentation';
|
|
10
|
+
|
|
11
|
+
export function buildResizingStage(
|
|
12
|
+
gl: WebGL2RenderingContext,
|
|
13
|
+
vertexShader: WebGLShader,
|
|
14
|
+
positionBuffer: WebGLBuffer,
|
|
15
|
+
texCoordBuffer: WebGLBuffer,
|
|
16
|
+
tflite: TFLite,
|
|
17
|
+
segmentationConfig: SegmentationParams,
|
|
18
|
+
) {
|
|
19
|
+
const fragmentShaderSource = glsl`#version 300 es
|
|
20
|
+
|
|
21
|
+
precision highp float;
|
|
22
|
+
uniform sampler2D u_inputFrame;
|
|
23
|
+
in vec2 v_texCoord;
|
|
24
|
+
out vec4 outColor;
|
|
25
|
+
|
|
26
|
+
void main() {
|
|
27
|
+
outColor = texture(u_inputFrame, v_texCoord);
|
|
28
|
+
}
|
|
29
|
+
`;
|
|
30
|
+
|
|
31
|
+
// TFLite memory will be accessed as float32
|
|
32
|
+
const tfliteInputMemoryOffset = tflite._getInputMemoryOffset() / 4;
|
|
33
|
+
|
|
34
|
+
const { width: outputWidth, height: outputHeight } = segmentationConfig;
|
|
35
|
+
const outputPixelCount = outputWidth * outputHeight;
|
|
36
|
+
|
|
37
|
+
const fragmentShader = compileShader(
|
|
38
|
+
gl,
|
|
39
|
+
gl.FRAGMENT_SHADER,
|
|
40
|
+
fragmentShaderSource,
|
|
41
|
+
);
|
|
42
|
+
const program = createPipelineStageProgram(
|
|
43
|
+
gl,
|
|
44
|
+
vertexShader,
|
|
45
|
+
fragmentShader,
|
|
46
|
+
positionBuffer,
|
|
47
|
+
texCoordBuffer,
|
|
48
|
+
);
|
|
49
|
+
const inputFrameLocation = gl.getUniformLocation(program, 'u_inputFrame');
|
|
50
|
+
const outputTexture = createTexture(gl, gl.RGBA8, outputWidth, outputHeight);
|
|
51
|
+
|
|
52
|
+
const frameBuffer = gl.createFramebuffer();
|
|
53
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
|
|
54
|
+
gl.framebufferTexture2D(
|
|
55
|
+
gl.FRAMEBUFFER,
|
|
56
|
+
gl.COLOR_ATTACHMENT0,
|
|
57
|
+
gl.TEXTURE_2D,
|
|
58
|
+
outputTexture,
|
|
59
|
+
0,
|
|
60
|
+
);
|
|
61
|
+
const outputPixels = new Uint8Array(outputPixelCount * 4);
|
|
62
|
+
|
|
63
|
+
gl.useProgram(program);
|
|
64
|
+
gl.uniform1i(inputFrameLocation, 0);
|
|
65
|
+
|
|
66
|
+
function render() {
|
|
67
|
+
gl.viewport(0, 0, outputWidth, outputHeight);
|
|
68
|
+
gl.useProgram(program);
|
|
69
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
|
|
70
|
+
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
|
71
|
+
|
|
72
|
+
// Downloads pixels asynchronously from GPU while rendering the current frame.
|
|
73
|
+
// The pixels will be available in the next frame render which results
|
|
74
|
+
// in offsets in the segmentation output but increases the frame rate.
|
|
75
|
+
readPixelsAsync(
|
|
76
|
+
gl,
|
|
77
|
+
0,
|
|
78
|
+
0,
|
|
79
|
+
outputWidth,
|
|
80
|
+
outputHeight,
|
|
81
|
+
gl.RGBA,
|
|
82
|
+
gl.UNSIGNED_BYTE,
|
|
83
|
+
outputPixels,
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
for (let i = 0; i < outputPixelCount; i++) {
|
|
87
|
+
const tfliteIndex = tfliteInputMemoryOffset + i * 3;
|
|
88
|
+
const outputIndex = i * 4;
|
|
89
|
+
tflite.HEAPF32[tfliteIndex] = outputPixels[outputIndex] / 255;
|
|
90
|
+
tflite.HEAPF32[tfliteIndex + 1] = outputPixels[outputIndex + 1] / 255;
|
|
91
|
+
tflite.HEAPF32[tfliteIndex + 2] = outputPixels[outputIndex + 2] / 255;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function cleanUp() {
|
|
96
|
+
gl.deleteFramebuffer(frameBuffer);
|
|
97
|
+
gl.deleteTexture(outputTexture);
|
|
98
|
+
gl.deleteProgram(program);
|
|
99
|
+
gl.deleteShader(fragmentShader);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return { render, cleanUp };
|
|
103
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { TFLite } from '../tflite';
|
|
2
|
+
import {
|
|
3
|
+
compileShader,
|
|
4
|
+
createPipelineStageProgram,
|
|
5
|
+
createTexture,
|
|
6
|
+
glsl,
|
|
7
|
+
} from '../helpers/webglHelper';
|
|
8
|
+
import { SegmentationParams } from '../segmentation';
|
|
9
|
+
|
|
10
|
+
export function buildSoftmaxStage(
|
|
11
|
+
gl: WebGL2RenderingContext,
|
|
12
|
+
vertexShader: WebGLShader,
|
|
13
|
+
positionBuffer: WebGLBuffer,
|
|
14
|
+
texCoordBuffer: WebGLBuffer,
|
|
15
|
+
tflite: TFLite,
|
|
16
|
+
outputTexture: WebGLTexture,
|
|
17
|
+
segmentationConfig: SegmentationParams,
|
|
18
|
+
) {
|
|
19
|
+
const fragmentShaderSource = glsl`#version 300 es
|
|
20
|
+
|
|
21
|
+
precision highp float;
|
|
22
|
+
|
|
23
|
+
uniform sampler2D u_inputSegmentation;
|
|
24
|
+
in vec2 v_texCoord;
|
|
25
|
+
out vec4 outColor;
|
|
26
|
+
|
|
27
|
+
void main() {
|
|
28
|
+
vec2 segmentation = texture(u_inputSegmentation, v_texCoord).rg;
|
|
29
|
+
float shift = max(segmentation.r, segmentation.g);
|
|
30
|
+
float backgroundExp = exp(segmentation.r - shift);
|
|
31
|
+
float personExp = exp(segmentation.g - shift);
|
|
32
|
+
outColor = vec4(vec3(0.0), personExp / (backgroundExp + personExp));
|
|
33
|
+
}
|
|
34
|
+
`;
|
|
35
|
+
|
|
36
|
+
// TFLite memory will be accessed as float32
|
|
37
|
+
const tfliteOutputMemoryOffset = tflite._getOutputMemoryOffset() / 4;
|
|
38
|
+
const { width: segmentationWidth, height: segmentationHeight } =
|
|
39
|
+
segmentationConfig;
|
|
40
|
+
|
|
41
|
+
const fragmentShader = compileShader(
|
|
42
|
+
gl,
|
|
43
|
+
gl.FRAGMENT_SHADER,
|
|
44
|
+
fragmentShaderSource,
|
|
45
|
+
);
|
|
46
|
+
const program = createPipelineStageProgram(
|
|
47
|
+
gl,
|
|
48
|
+
vertexShader,
|
|
49
|
+
fragmentShader,
|
|
50
|
+
positionBuffer,
|
|
51
|
+
texCoordBuffer,
|
|
52
|
+
);
|
|
53
|
+
const inputLocation = gl.getUniformLocation(program, 'u_inputSegmentation');
|
|
54
|
+
const inputTexture = createTexture(
|
|
55
|
+
gl,
|
|
56
|
+
gl.RG32F,
|
|
57
|
+
segmentationWidth,
|
|
58
|
+
segmentationHeight,
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
const frameBuffer = gl.createFramebuffer();
|
|
62
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
|
|
63
|
+
gl.framebufferTexture2D(
|
|
64
|
+
gl.FRAMEBUFFER,
|
|
65
|
+
gl.COLOR_ATTACHMENT0,
|
|
66
|
+
gl.TEXTURE_2D,
|
|
67
|
+
outputTexture,
|
|
68
|
+
0,
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
gl.useProgram(program);
|
|
72
|
+
gl.uniform1i(inputLocation, 1);
|
|
73
|
+
|
|
74
|
+
function render() {
|
|
75
|
+
gl.viewport(0, 0, segmentationWidth, segmentationHeight);
|
|
76
|
+
gl.useProgram(program);
|
|
77
|
+
gl.activeTexture(gl.TEXTURE1);
|
|
78
|
+
gl.bindTexture(gl.TEXTURE_2D, inputTexture);
|
|
79
|
+
gl.texSubImage2D(
|
|
80
|
+
gl.TEXTURE_2D,
|
|
81
|
+
0,
|
|
82
|
+
0,
|
|
83
|
+
0,
|
|
84
|
+
segmentationWidth,
|
|
85
|
+
segmentationHeight,
|
|
86
|
+
gl.RG,
|
|
87
|
+
gl.FLOAT,
|
|
88
|
+
tflite.HEAPF32,
|
|
89
|
+
tfliteOutputMemoryOffset,
|
|
90
|
+
);
|
|
91
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
|
|
92
|
+
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function cleanUp() {
|
|
96
|
+
gl.deleteFramebuffer(frameBuffer);
|
|
97
|
+
gl.deleteTexture(inputTexture);
|
|
98
|
+
gl.deleteProgram(program);
|
|
99
|
+
gl.deleteShader(fragmentShader);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return { render, cleanUp };
|
|
103
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { TFLite } from '../tflite';
|
|
2
|
+
import { compileShader, createTexture, glsl } from '../helpers/webglHelper';
|
|
3
|
+
import {
|
|
4
|
+
BackgroundBlurStage,
|
|
5
|
+
buildBackgroundBlurStage,
|
|
6
|
+
} from './backgroundBlurStage';
|
|
7
|
+
import {
|
|
8
|
+
BackgroundImageStage,
|
|
9
|
+
buildBackgroundImageStage,
|
|
10
|
+
} from './backgroundImageStage';
|
|
11
|
+
import { buildJointBilateralFilterStage } from './jointBilateralFilterStage';
|
|
12
|
+
import { buildResizingStage } from './resizingStage';
|
|
13
|
+
import { buildSoftmaxStage } from './softmaxStage';
|
|
14
|
+
import { BackgroundBlurLevel, BackgroundFilter } from '../createRenderer';
|
|
15
|
+
import { SegmentationParams } from '../segmentation';
|
|
16
|
+
|
|
17
|
+
export function buildWebGL2Pipeline(
|
|
18
|
+
videoSource: HTMLVideoElement,
|
|
19
|
+
backgroundImage: HTMLImageElement | undefined,
|
|
20
|
+
blurLevel: BackgroundBlurLevel | undefined,
|
|
21
|
+
backgroundFilter: BackgroundFilter,
|
|
22
|
+
canvas: HTMLCanvasElement,
|
|
23
|
+
tflite: TFLite,
|
|
24
|
+
segmentationConfig: SegmentationParams,
|
|
25
|
+
) {
|
|
26
|
+
const gl = canvas.getContext('webgl2')!;
|
|
27
|
+
if (!gl) throw new Error('WebGL2 is not supported');
|
|
28
|
+
|
|
29
|
+
const { width: frameWidth, height: frameHeight } = videoSource;
|
|
30
|
+
const { width: segmentationWidth, height: segmentationHeight } =
|
|
31
|
+
segmentationConfig;
|
|
32
|
+
|
|
33
|
+
const vertexShaderSource = glsl`#version 300 es
|
|
34
|
+
|
|
35
|
+
in vec2 a_position;
|
|
36
|
+
in vec2 a_texCoord;
|
|
37
|
+
out vec2 v_texCoord;
|
|
38
|
+
|
|
39
|
+
void main() {
|
|
40
|
+
gl_Position = vec4(a_position, 0.0, 1.0);
|
|
41
|
+
v_texCoord = a_texCoord;
|
|
42
|
+
}
|
|
43
|
+
`;
|
|
44
|
+
const vertexShader = compileShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
|
|
45
|
+
|
|
46
|
+
const vertexArray = gl.createVertexArray();
|
|
47
|
+
gl.bindVertexArray(vertexArray);
|
|
48
|
+
|
|
49
|
+
const positionBuffer = gl.createBuffer()!;
|
|
50
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
|
|
51
|
+
gl.bufferData(
|
|
52
|
+
gl.ARRAY_BUFFER,
|
|
53
|
+
new Float32Array([-1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, 1.0]),
|
|
54
|
+
gl.STATIC_DRAW,
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const texCoordBuffer = gl.createBuffer()!;
|
|
58
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
|
|
59
|
+
gl.bufferData(
|
|
60
|
+
gl.ARRAY_BUFFER,
|
|
61
|
+
new Float32Array([0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0]),
|
|
62
|
+
gl.STATIC_DRAW,
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
// We don't use texStorage2D here because texImage2D seems faster
|
|
66
|
+
// to upload video texture than texSubImage2D even though the latter
|
|
67
|
+
// is supposed to be the recommended way:
|
|
68
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/WebGL_best_practices#use_texstorage_to_create_textures
|
|
69
|
+
const inputFrameTexture = gl.createTexture();
|
|
70
|
+
gl.bindTexture(gl.TEXTURE_2D, inputFrameTexture);
|
|
71
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
72
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
73
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
74
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
75
|
+
|
|
76
|
+
// TODO Rename segmentation and person mask to be more specific
|
|
77
|
+
const segmentationTexture = createTexture(
|
|
78
|
+
gl,
|
|
79
|
+
gl.RGBA8,
|
|
80
|
+
segmentationWidth,
|
|
81
|
+
segmentationHeight,
|
|
82
|
+
)!;
|
|
83
|
+
const personMaskTexture = createTexture(
|
|
84
|
+
gl,
|
|
85
|
+
gl.RGBA8,
|
|
86
|
+
frameWidth,
|
|
87
|
+
frameHeight,
|
|
88
|
+
)!;
|
|
89
|
+
|
|
90
|
+
const resizingStage = buildResizingStage(
|
|
91
|
+
gl,
|
|
92
|
+
vertexShader,
|
|
93
|
+
positionBuffer,
|
|
94
|
+
texCoordBuffer,
|
|
95
|
+
tflite,
|
|
96
|
+
segmentationConfig,
|
|
97
|
+
);
|
|
98
|
+
const loadSegmentationStage = buildSoftmaxStage(
|
|
99
|
+
gl,
|
|
100
|
+
vertexShader,
|
|
101
|
+
positionBuffer,
|
|
102
|
+
texCoordBuffer,
|
|
103
|
+
tflite,
|
|
104
|
+
segmentationTexture,
|
|
105
|
+
segmentationConfig,
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
const jointBilateralFilterStage = buildJointBilateralFilterStage(
|
|
109
|
+
gl,
|
|
110
|
+
vertexShader,
|
|
111
|
+
positionBuffer,
|
|
112
|
+
texCoordBuffer,
|
|
113
|
+
segmentationTexture,
|
|
114
|
+
personMaskTexture,
|
|
115
|
+
canvas,
|
|
116
|
+
segmentationConfig,
|
|
117
|
+
);
|
|
118
|
+
const backgroundStage =
|
|
119
|
+
backgroundFilter === 'blur'
|
|
120
|
+
? buildBackgroundBlurStage(
|
|
121
|
+
gl,
|
|
122
|
+
vertexShader,
|
|
123
|
+
positionBuffer,
|
|
124
|
+
texCoordBuffer,
|
|
125
|
+
personMaskTexture,
|
|
126
|
+
canvas,
|
|
127
|
+
blurLevel || 'high',
|
|
128
|
+
)
|
|
129
|
+
: buildBackgroundImageStage(
|
|
130
|
+
gl,
|
|
131
|
+
positionBuffer,
|
|
132
|
+
texCoordBuffer,
|
|
133
|
+
personMaskTexture,
|
|
134
|
+
backgroundImage,
|
|
135
|
+
canvas,
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
function render() {
|
|
139
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
140
|
+
gl.bindTexture(gl.TEXTURE_2D, inputFrameTexture);
|
|
141
|
+
|
|
142
|
+
// texImage2D seems faster than texSubImage2D to upload
|
|
143
|
+
// video texture
|
|
144
|
+
gl.texImage2D(
|
|
145
|
+
gl.TEXTURE_2D,
|
|
146
|
+
0,
|
|
147
|
+
gl.RGBA,
|
|
148
|
+
gl.RGBA,
|
|
149
|
+
gl.UNSIGNED_BYTE,
|
|
150
|
+
videoSource,
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
gl.bindVertexArray(vertexArray);
|
|
154
|
+
|
|
155
|
+
resizingStage.render();
|
|
156
|
+
|
|
157
|
+
tflite._runInference();
|
|
158
|
+
|
|
159
|
+
loadSegmentationStage.render();
|
|
160
|
+
jointBilateralFilterStage.render();
|
|
161
|
+
backgroundStage.render();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function updatePostProcessingConfig() {
|
|
165
|
+
jointBilateralFilterStage.updateSigmaSpace(1);
|
|
166
|
+
jointBilateralFilterStage.updateSigmaColor(0.1);
|
|
167
|
+
|
|
168
|
+
if (backgroundFilter === 'image') {
|
|
169
|
+
const backgroundImageStage = backgroundStage as BackgroundImageStage;
|
|
170
|
+
backgroundImageStage.updateCoverage([0.5, 0.75]);
|
|
171
|
+
backgroundImageStage.updateLightWrapping(0.3);
|
|
172
|
+
backgroundImageStage.updateBlendMode('screen');
|
|
173
|
+
} else if (backgroundFilter === 'blur') {
|
|
174
|
+
const backgroundBlurStage = backgroundStage as BackgroundBlurStage;
|
|
175
|
+
backgroundBlurStage.updateCoverage([0.5, 0.75]);
|
|
176
|
+
} else {
|
|
177
|
+
// TODO Handle no background in a separate pipeline path
|
|
178
|
+
const backgroundImageStage = backgroundStage as BackgroundImageStage;
|
|
179
|
+
backgroundImageStage.updateCoverage([0, 0.9999]);
|
|
180
|
+
backgroundImageStage.updateLightWrapping(0);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function cleanUp() {
|
|
185
|
+
backgroundStage.cleanUp();
|
|
186
|
+
jointBilateralFilterStage.cleanUp();
|
|
187
|
+
loadSegmentationStage.cleanUp();
|
|
188
|
+
resizingStage.cleanUp();
|
|
189
|
+
|
|
190
|
+
gl.deleteTexture(personMaskTexture);
|
|
191
|
+
gl.deleteTexture(segmentationTexture);
|
|
192
|
+
gl.deleteTexture(inputFrameTexture);
|
|
193
|
+
gl.deleteBuffer(texCoordBuffer);
|
|
194
|
+
gl.deleteBuffer(positionBuffer);
|
|
195
|
+
gl.deleteVertexArray(vertexArray);
|
|
196
|
+
gl.deleteShader(vertexShader);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return { render, updatePostProcessingConfig, cleanUp };
|
|
200
|
+
}
|
|
Binary file
|
|
Binary file
|