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
       "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz",
15234
       "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz",
15235
       "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA=="
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
     "stacktrace-parser": {
15242
     "stacktrace-parser": {
15238
       "version": "0.1.8",
15243
       "version": "0.1.8",
15239
       "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.8.tgz",
15244
       "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.8.tgz",

+ 1
- 0
package.json View File

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

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

1
 // @flow
1
 // @flow
2
 
2
 
3
-import * as bodyPix from '@tensorflow-models/body-pix';
3
+import * as StackBlur from 'stackblur-canvas';
4
 
4
 
5
 import {
5
 import {
6
-    CLEAR_INTERVAL,
7
-    INTERVAL_TIMEOUT,
8
-    SET_INTERVAL,
6
+    CLEAR_TIMEOUT,
7
+    TIMEOUT_TICK,
8
+    SET_TIMEOUT,
9
     timerWorkerScript
9
     timerWorkerScript
10
 } from './TimerWorker';
10
 } from './TimerWorker';
11
 
11
 
17
 export default class JitsiStreamBlurEffect {
17
 export default class JitsiStreamBlurEffect {
18
     _bpModel: Object;
18
     _bpModel: Object;
19
     _inputVideoElement: HTMLVideoElement;
19
     _inputVideoElement: HTMLVideoElement;
20
+    _inputVideoCanvasElement: HTMLCanvasElement;
20
     _onMaskFrameTimer: Function;
21
     _onMaskFrameTimer: Function;
21
     _maskFrameTimerWorker: Worker;
22
     _maskFrameTimerWorker: Worker;
22
     _maskInProgress: boolean;
23
     _maskInProgress: boolean;
43
         this._outputCanvasElement = document.createElement('canvas');
44
         this._outputCanvasElement = document.createElement('canvas');
44
         this._outputCanvasElement.getContext('2d');
45
         this._outputCanvasElement.getContext('2d');
45
         this._inputVideoElement = document.createElement('video');
46
         this._inputVideoElement = document.createElement('video');
47
+        this._inputVideoCanvasElement = document.createElement('canvas');
46
     }
48
     }
47
 
49
 
48
     /**
50
     /**
53
      * @returns {void}
55
      * @returns {void}
54
      */
56
      */
55
     async _onMaskFrameTimer(response: Object) {
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
      * @returns {void}
67
      * @returns {void}
68
      */
68
      */
69
     async _renderMask() {
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
 
143
 
111
         this._outputCanvasElement.width = parseInt(width, 10);
144
         this._outputCanvasElement.width = parseInt(width, 10);
112
         this._outputCanvasElement.height = parseInt(height, 10);
145
         this._outputCanvasElement.height = parseInt(height, 10);
146
+        this._inputVideoCanvasElement.width = parseInt(width, 10);
147
+        this._inputVideoCanvasElement.height = parseInt(height, 10);
113
         this._inputVideoElement.width = parseInt(width, 10);
148
         this._inputVideoElement.width = parseInt(width, 10);
114
         this._inputVideoElement.height = parseInt(height, 10);
149
         this._inputVideoElement.height = parseInt(height, 10);
115
         this._inputVideoElement.autoplay = true;
150
         this._inputVideoElement.autoplay = true;
116
         this._inputVideoElement.srcObject = stream;
151
         this._inputVideoElement.srcObject = stream;
117
         this._inputVideoElement.onloadeddata = () => {
152
         this._inputVideoElement.onloadeddata = () => {
118
             this._maskFrameTimerWorker.postMessage({
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
      */
166
      */
132
     stopEffect() {
167
     stopEffect() {
133
         this._maskFrameTimerWorker.postMessage({
168
         this._maskFrameTimerWorker.postMessage({
134
-            id: CLEAR_INTERVAL
169
+            id: CLEAR_TIMEOUT
135
         });
170
         });
136
 
171
 
137
         this._maskFrameTimerWorker.terminate();
172
         this._maskFrameTimerWorker.terminate();

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

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
  * the id property of the request.data property. timeMs property must
4
  * the id property of the request.data property. timeMs property must
5
  * also be set. request.data example:
5
  * also be set. request.data example:
6
  *
6
  *
7
  * {
7
  * {
8
- *      id: SET_INTERVAL,
8
+ *      id: SET_TIMEOUT,
9
  *      timeMs: 33
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
  * the id property of the request.data property.
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
  * The following code is needed as string to create a URL from a Blob.
34
  * The following code is needed as string to create a URL from a Blob.
40
 
40
 
41
     onmessage = function(request) {
41
     onmessage = function(request) {
42
         switch (request.data.id) {
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
             }, request.data.timeMs);
46
             }, request.data.timeMs);
47
             break;
47
             break;
48
         }
48
         }
49
-        case ${CLEAR_INTERVAL}: {
49
+        case ${CLEAR_TIMEOUT}: {
50
             if (timer) {
50
             if (timer) {
51
-                clearInterval(timer);
51
+                clearTimeout(timer);
52
             }
52
             }
53
             break;
53
             break;
54
         }
54
         }

Loading…
Cancel
Save