Browse Source

fix(background-blur) refactor to improve performance

master
Josh Brown 4 years ago
parent
commit
ebb1b8d76b
No account linked to committer's email address

+ 5
- 0
package-lock.json View File

@@ -15234,6 +15234,11 @@
15234 15234
       "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz",
15235 15235
       "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA=="
15236 15236
     },
15237
+    "stackblur-canvas": {
15238
+      "version": "2.3.0",
15239
+      "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.3.0.tgz",
15240
+      "integrity": "sha512-3ZHJv+43D8YttgumssIxkfs3hBXW7XaMS5Ux65fOBhKDYMjbG5hF8Ey8a90RiiJ58aQnAhWbGilPzZ9rkIlWgQ=="
15241
+    },
15237 15242
     "stacktrace-parser": {
15238 15243
       "version": "0.1.8",
15239 15244
       "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.8.tgz",

+ 1
- 0
package.json View File

@@ -92,6 +92,7 @@
92 92
     "redux-thunk": "2.2.0",
93 93
     "rnnoise-wasm": "github:jitsi/rnnoise-wasm.git#566a16885897704d6e6d67a1d5ac5d39781db2af",
94 94
     "rtcstats": "github:jitsi/rtcstats#v6.2.0",
95
+    "stackblur-canvas": "2.3.0",
95 96
     "styled-components": "3.4.9",
96 97
     "util": "0.12.1",
97 98
     "uuid": "3.1.0",

+ 59
- 24
react/features/stream-effects/blur/JitsiStreamBlurEffect.js View File

@@ -1,11 +1,11 @@
1 1
 // @flow
2 2
 
3
-import * as bodyPix from '@tensorflow-models/body-pix';
3
+import * as StackBlur from 'stackblur-canvas';
4 4
 
5 5
 import {
6
-    CLEAR_INTERVAL,
7
-    INTERVAL_TIMEOUT,
8
-    SET_INTERVAL,
6
+    CLEAR_TIMEOUT,
7
+    TIMEOUT_TICK,
8
+    SET_TIMEOUT,
9 9
     timerWorkerScript
10 10
 } from './TimerWorker';
11 11
 
@@ -17,6 +17,7 @@ import {
17 17
 export default class JitsiStreamBlurEffect {
18 18
     _bpModel: Object;
19 19
     _inputVideoElement: HTMLVideoElement;
20
+    _inputVideoCanvasElement: HTMLCanvasElement;
20 21
     _onMaskFrameTimer: Function;
21 22
     _maskFrameTimerWorker: Worker;
22 23
     _maskInProgress: boolean;
@@ -43,6 +44,7 @@ export default class JitsiStreamBlurEffect {
43 44
         this._outputCanvasElement = document.createElement('canvas');
44 45
         this._outputCanvasElement.getContext('2d');
45 46
         this._inputVideoElement = document.createElement('video');
47
+        this._inputVideoCanvasElement = document.createElement('canvas');
46 48
     }
47 49
 
48 50
     /**
@@ -53,10 +55,8 @@ export default class JitsiStreamBlurEffect {
53 55
      * @returns {void}
54 56
      */
55 57
     async _onMaskFrameTimer(response: Object) {
56
-        if (response.data.id === INTERVAL_TIMEOUT) {
57
-            if (!this._maskInProgress) {
58
-                await this._renderMask();
59
-            }
58
+        if (response.data.id === TIMEOUT_TICK) {
59
+            await this._renderMask();
60 60
         }
61 61
     }
62 62
 
@@ -67,20 +67,53 @@ export default class JitsiStreamBlurEffect {
67 67
      * @returns {void}
68 68
      */
69 69
     async _renderMask() {
70
-        this._maskInProgress = true;
71
-        this._segmentationData = await this._bpModel.segmentPerson(this._inputVideoElement, {
72
-            internalResolution: 'medium', // resized to 0.5 times of the original resolution before inference
73
-            maxDetections: 1, // max. number of person poses to detect per image
74
-            segmentationThreshold: 0.7 // represents probability that a pixel belongs to a person
75
-        });
76
-        this._maskInProgress = false;
77
-        bodyPix.drawBokehEffect(
78
-            this._outputCanvasElement,
79
-            this._inputVideoElement,
80
-            this._segmentationData,
81
-            12, // Constant for background blur, integer values between 0-20
82
-            7 // Constant for edge blur, integer values between 0-20
70
+        if (!this._maskInProgress) {
71
+            this._maskInProgress = true;
72
+            this._bpModel.segmentPerson(this._inputVideoElement, {
73
+                internalResolution: 'low', // resized to 0.5 times of the original resolution before inference
74
+                maxDetections: 1, // max. number of person poses to detect per image
75
+                segmentationThreshold: 0.7, // represents probability that a pixel belongs to a person
76
+                flipHorizontal: false,
77
+                scoreThreshold: 0.2
78
+            }).then(data => {
79
+                this._segmentationData = data;
80
+                this._maskInProgress = false;
81
+            });
82
+        }
83
+        const inputCanvasCtx = this._inputVideoCanvasElement.getContext('2d');
84
+
85
+        inputCanvasCtx.drawImage(this._inputVideoElement, 0, 0);
86
+
87
+        const currentFrame = inputCanvasCtx.getImageData(
88
+            0,
89
+            0,
90
+            this._inputVideoCanvasElement.width,
91
+            this._inputVideoCanvasElement.height
83 92
         );
93
+
94
+        if (this._segmentationData) {
95
+            const blurData = new ImageData(currentFrame.data.slice(), currentFrame.width, currentFrame.height);
96
+
97
+            StackBlur.imageDataRGB(blurData, 0, 0, currentFrame.width, currentFrame.height, 12);
98
+
99
+            for (let x = 0; x < this._outputCanvasElement.width; x++) {
100
+                for (let y = 0; y < this._outputCanvasElement.height; y++) {
101
+                    const n = (y * this._outputCanvasElement.width) + x;
102
+
103
+                    if (this._segmentationData.data[n] === 0) {
104
+                        currentFrame.data[n * 4] = blurData.data[n * 4];
105
+                        currentFrame.data[(n * 4) + 1] = blurData.data[(n * 4) + 1];
106
+                        currentFrame.data[(n * 4) + 2] = blurData.data[(n * 4) + 2];
107
+                        currentFrame.data[(n * 4) + 3] = blurData.data[(n * 4) + 3];
108
+                    }
109
+                }
110
+            }
111
+        }
112
+        this._outputCanvasElement.getContext('2d').putImageData(currentFrame, 0, 0);
113
+        this._maskFrameTimerWorker.postMessage({
114
+            id: SET_TIMEOUT,
115
+            timeMs: 1000 / 30
116
+        });
84 117
     }
85 118
 
86 119
     /**
@@ -110,14 +143,16 @@ export default class JitsiStreamBlurEffect {
110 143
 
111 144
         this._outputCanvasElement.width = parseInt(width, 10);
112 145
         this._outputCanvasElement.height = parseInt(height, 10);
146
+        this._inputVideoCanvasElement.width = parseInt(width, 10);
147
+        this._inputVideoCanvasElement.height = parseInt(height, 10);
113 148
         this._inputVideoElement.width = parseInt(width, 10);
114 149
         this._inputVideoElement.height = parseInt(height, 10);
115 150
         this._inputVideoElement.autoplay = true;
116 151
         this._inputVideoElement.srcObject = stream;
117 152
         this._inputVideoElement.onloadeddata = () => {
118 153
             this._maskFrameTimerWorker.postMessage({
119
-                id: SET_INTERVAL,
120
-                timeMs: 1000 / parseInt(frameRate, 10)
154
+                id: SET_TIMEOUT,
155
+                timeMs: 1000 / 30
121 156
             });
122 157
         };
123 158
 
@@ -131,7 +166,7 @@ export default class JitsiStreamBlurEffect {
131 166
      */
132 167
     stopEffect() {
133 168
         this._maskFrameTimerWorker.postMessage({
134
-            id: CLEAR_INTERVAL
169
+            id: CLEAR_TIMEOUT
135 170
         });
136 171
 
137 172
         this._maskFrameTimerWorker.terminate();

+ 14
- 14
react/features/stream-effects/blur/TimerWorker.js View File

@@ -1,34 +1,34 @@
1 1
 
2 2
 /**
3
- * SET_INTERVAL constant is used to set interval and it is set in
3
+ * SET_TIMEOUT constant is used to set interval and it is set in
4 4
  * the id property of the request.data property. timeMs property must
5 5
  * also be set. request.data example:
6 6
  *
7 7
  * {
8
- *      id: SET_INTERVAL,
8
+ *      id: SET_TIMEOUT,
9 9
  *      timeMs: 33
10 10
  * }
11 11
  */
12
-export const SET_INTERVAL = 1;
12
+export const SET_TIMEOUT = 1;
13 13
 
14 14
 /**
15
- * CLEAR_INTERVAL constant is used to clear the interval and it is set in
15
+ * CLEAR_TIMEOUT constant is used to clear the interval and it is set in
16 16
  * the id property of the request.data property.
17 17
  *
18 18
  * {
19
- *      id: CLEAR_INTERVAL
19
+ *      id: CLEAR_TIMEOUT
20 20
  * }
21 21
  */
22
-export const CLEAR_INTERVAL = 2;
22
+export const CLEAR_TIMEOUT = 2;
23 23
 
24 24
 /**
25
- * INTERVAL_TIMEOUT constant is used as response and it is set in the id property.
25
+ * TIMEOUT_TICK constant is used as response and it is set in the id property.
26 26
  *
27 27
  * {
28
- *      id: INTERVAL_TIMEOUT
28
+ *      id: TIMEOUT_TICK
29 29
  * }
30 30
  */
31
-export const INTERVAL_TIMEOUT = 3;
31
+export const TIMEOUT_TICK = 3;
32 32
 
33 33
 /**
34 34
  * The following code is needed as string to create a URL from a Blob.
@@ -40,15 +40,15 @@ const code = `
40 40
 
41 41
     onmessage = function(request) {
42 42
         switch (request.data.id) {
43
-        case ${SET_INTERVAL}: {
44
-            timer = setInterval(() => {
45
-                postMessage({ id: ${INTERVAL_TIMEOUT} });
43
+        case ${SET_TIMEOUT}: {
44
+            timer = setTimeout(() => {
45
+                postMessage({ id: ${TIMEOUT_TICK} });
46 46
             }, request.data.timeMs);
47 47
             break;
48 48
         }
49
-        case ${CLEAR_INTERVAL}: {
49
+        case ${CLEAR_TIMEOUT}: {
50 50
             if (timer) {
51
-                clearInterval(timer);
51
+                clearTimeout(timer);
52 52
             }
53 53
             break;
54 54
         }

Loading…
Cancel
Save