HuggingFace-SK commited on
Commit
e60eb80
·
1 Parent(s): 5638c14

browser based local prediections!

Browse files
Files changed (4) hide show
  1. README.md +1 -1
  2. main.py +14 -0
  3. static/style.css +65 -51
  4. templates/browser-detect.html +306 -0
README.md CHANGED
@@ -6,7 +6,7 @@ colorTo: gray
6
  sdk: gradio
7
  sdk_version: 4.36.0
8
  python_version: 3.10.4
9
- app_file: app.py
10
  license: gpl-3.0
11
  pinned: false
12
  ---
 
6
  sdk: gradio
7
  sdk_version: 4.36.0
8
  python_version: 3.10.4
9
+ app_file: main.py
10
  license: gpl-3.0
11
  pinned: false
12
  ---
main.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, render_template, Response, request, jsonify, send_from_directory
2
+
3
+ app = Flask(__name__)
4
+
5
+ @app.route('/')
6
+ def index():
7
+ return render_template('browser-detect.html')
8
+
9
+ @app.route('/exported')
10
+ def send_report():
11
+ return send_from_directory("exported", "model.tflite")
12
+
13
+ if (__name__ == '__main__'):
14
+ app.run( host='0.0.0.0', port=7860)
static/style.css CHANGED
@@ -1,79 +1,93 @@
1
- body {
2
- --text: hsl(0 0% 15%);
3
- padding: 2.5rem;
4
- font-family: sans-serif;
5
- color: var(--text);
6
- }
7
- body.dark-theme {
8
- --text: hsl(0 0% 90%);
9
- background-color: hsl(223 39% 7%);
10
- }
11
 
12
- main {
13
- max-width: 80rem;
14
- text-align: center;
15
- }
16
 
17
- section {
18
- display: flex;
19
- flex-direction: column;
20
- align-items: center;
21
- }
22
 
23
- a {
24
- color: var(--text);
 
 
 
 
 
 
 
 
 
 
 
25
  }
26
 
27
- select, input, button, .text-gen-output {
28
- padding: 0.5rem 1rem;
 
29
  }
30
 
31
- select, img, input {
32
- margin: 0.5rem auto 1rem;
 
33
  }
34
 
35
- form {
36
- width: 25rem;
37
- margin: 0 auto;
38
  }
39
 
40
- input {
41
- width: 70%;
42
  }
43
 
44
- button {
45
- cursor: pointer;
 
46
  }
47
 
48
- .text-gen-output {
49
- min-height: 1.2rem;
50
- margin: 1rem;
51
- border: 0.5px solid grey;
 
 
 
52
  }
53
 
54
- #dataset button {
55
- width: 6rem;
56
- margin: 0.5rem;
 
 
 
 
 
 
 
57
  }
58
 
59
- #dataset button.hidden {
60
- visibility: hidden;
 
 
 
61
  }
62
 
63
- table {
64
- max-width: 40rem;
65
- text-align: left;
66
- border-collapse: collapse;
67
  }
68
 
69
- thead {
70
- font-weight: bold;
 
 
71
  }
72
 
73
- td {
74
- padding: 0.5rem;
75
  }
76
 
77
- td:not(thead td) {
78
- border: 0.5px solid grey;
79
  }
 
1
+ /* Copyright 2023 The MediaPipe Authors.
 
 
 
 
 
 
 
 
 
2
 
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
 
6
 
7
+ http://www.apache.org/licenses/LICENSE-2.0
 
 
 
 
8
 
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License. */
14
+
15
+
16
+ video {
17
+ clear: both;
18
+ display: block;
19
+ transform: rotateY(180deg);
20
+ -webkit-transform: rotateY(180deg);
21
+ -moz-transform: rotateY(180deg);
22
  }
23
 
24
+ section {
25
+ opacity: 1;
26
+ transition: opacity 500ms ease-in-out;
27
  }
28
 
29
+ header,
30
+ footer {
31
+ clear: both;
32
  }
33
 
34
+ .removed {
35
+ display: none;
 
36
  }
37
 
38
+ .invisible {
39
+ opacity: 0.2;
40
  }
41
 
42
+ .note {
43
+ font-style: italic;
44
+ font-size: 130%;
45
  }
46
 
47
+ .videoView,
48
+ .detectOnClick {
49
+ position: relative;
50
+ float: left;
51
+ width: 48%;
52
+ margin: 2% 1%;
53
+ cursor: pointer;
54
  }
55
 
56
+ .videoView p,
57
+ .detectOnClick p {
58
+ position: absolute;
59
+ padding: 5px;
60
+ background-color: #007f8b;
61
+ color: #fff;
62
+ border: 1px dashed rgba(255, 255, 255, 0.7);
63
+ z-index: 2;
64
+ font-size: 12px;
65
+ margin: 0;
66
  }
67
 
68
+ .highlighter {
69
+ background: rgba(0, 255, 0, 0.25);
70
+ border: 1px dashed #fff;
71
+ z-index: 1;
72
+ position: absolute;
73
  }
74
 
75
+ .canvas {
76
+ z-index: 1;
77
+ position: absolute;
78
+ pointer-events: none;
79
  }
80
 
81
+ .output_canvas {
82
+ transform: rotateY(180deg);
83
+ -webkit-transform: rotateY(180deg);
84
+ -moz-transform: rotateY(180deg);
85
  }
86
 
87
+ .detectOnClick {
88
+ z-index: 0;
89
  }
90
 
91
+ .detectOnClick img {
92
+ width: 100%;
93
  }
templates/browser-detect.html ADDED
@@ -0,0 +1,306 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en"><head></head>
3
+ <meta charset="UTF-8">
4
+
5
+
6
+ <link rel="apple-touch-icon" type="image/png" href="https://cpwebassets.codepen.io/assets/favicon/apple-touch-icon-5ae1a0698dcc2402e9712f7d01ed509a57814f994c660df9f7a952f3060705ee.png">
7
+
8
+ <meta name="apple-mobile-web-app-title" content="CodePen">
9
+
10
+ <link rel="shortcut icon" type="image/x-icon" href="https://cpwebassets.codepen.io/assets/favicon/favicon-aec34940fbc1a6e787974dcd360f2c6b63348d4b1f4e06c77743096d55480f33.ico">
11
+
12
+ <link rel="mask-icon" type="image/x-icon" href="https://cpwebassets.codepen.io/assets/favicon/logo-pin-b4b4269c16397ad2f0f7a01bcdf513a1994f4c94b8af2f191c09eb0d601762b1.svg" color="#111">
13
+
14
+
15
+
16
+
17
+ <script src="https://cpwebassets.codepen.io/assets/common/stopExecutionOnTimeout-2c7831bb44f98c1391d6a4ffda0e1fd302503391ca806e7fcc7b9b87197aec26.js"></script>
18
+
19
+
20
+ <title>CodePen - MediaPipe HandLandmarker Task for web</title>
21
+
22
+ <link rel="canonical" href="https://codepen.io/mediapipe-preview/pen/gOKBGPN">
23
+
24
+
25
+
26
+
27
+ <style>
28
+ /* Copyright 2023 The MediaPipe Authors.
29
+
30
+ Licensed under the Apache License, Version 2.0 (the "License");
31
+ you may not use this file except in compliance with the License.
32
+ You may obtain a copy of the License at
33
+
34
+ http://www.apache.org/licenses/LICENSE-2.0
35
+
36
+ Unless required by applicable law or agreed to in writing, software
37
+ distributed under the License is distributed on an "AS IS" BASIS,
38
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
39
+ See the License for the specific language governing permissions and
40
+ limitations under the License. */
41
+
42
+ body {
43
+ font-family: roboto;
44
+ margin: 2em;
45
+ color: #3d3d3d;
46
+ --mdc-theme-primary: #007f8b;
47
+ --mdc-theme-on-primary: #f1f3f4;
48
+ }
49
+ </style>
50
+
51
+ <script>
52
+ window.console = window.console || function(t) {};
53
+ </script>
54
+
55
+
56
+
57
+ </head>
58
+
59
+ <body translate="no">
60
+ <!-- Copyright 2023 The MediaPipe Authors.
61
+
62
+ Licensed under the Apache License, Version 2.0 (the "License");
63
+ you may not use this file except in compliance with the License.
64
+ You may obtain a copy of the License at
65
+
66
+ http://www.apache.org/licenses/LICENSE-2.0
67
+
68
+ Unless required by applicable law or agreed to in writing, software
69
+ distributed under the License is distributed on an "AS IS" BASIS,
70
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
71
+ See the License for the specific language governing permissions and
72
+ limitations under the License. -->
73
+ <script src="https://cdn.jsdelivr.net/npm/@mediapipe/drawing_utils/drawing_utils.js" crossorigin="anonymous"></script>
74
+ <script src="https://cdn.jsdelivr.net/npm/@mediapipe/hands/hands.js" crossorigin="anonymous"></script>
75
+ <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs-core"></script>
76
+ <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs-backend-cpu"></script>
77
+ <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs-tflite/dist/tf-tflite.min.js"></script>
78
+
79
+
80
+ <div id="liveView" class="videoView">
81
+ <img id="output_image"></img>
82
+ <button id="webcamButton">
83
+ <span>ENABLE WEBCAM</span>
84
+ </button>
85
+ <div style="position: relative;">
86
+ <video id="webcam" style="position: absolute" autoplay="" playsinline=""></video>
87
+ <canvas class="output_canvas" id="output_canvas" style="position: absolute; left: 0px; top: 0px; display:none"></canvas>
88
+ </div>
89
+ </div>
90
+
91
+
92
+
93
+ <script type="module">
94
+
95
+ import { HandLandmarker, FilesetResolver } from "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.0";
96
+ let handLandmarker = undefined;
97
+ let runningMode = "IMAGE";
98
+ let enableWebcamButton;
99
+ let webcamRunning = false;
100
+ // Before we can use HandLandmarker class we must wait for it to finish
101
+ // loading. Machine Learning models can be large and take a moment to
102
+ // get everything needed to run.
103
+ const createHandLandmarker = async () => {
104
+ const vision = await FilesetResolver.forVisionTasks("https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.0/wasm");
105
+ handLandmarker = await HandLandmarker.createFromOptions(vision, {
106
+ baseOptions: {
107
+ modelAssetPath: `https://storage.googleapis.com/mediapipe-models/hand_landmarker/hand_landmarker/float16/1/hand_landmarker.task`,
108
+ delegate: "GPU"
109
+ },
110
+ runningMode: runningMode,
111
+ numHands: 1
112
+ });
113
+ };
114
+ createHandLandmarker();
115
+
116
+ const MODEL_PATH = "/exported"
117
+ var objectDetector = tflite.loadTFLiteModel(MODEL_PATH);
118
+
119
+ /********************************************************************
120
+ // Demo 2: Continuously grab image from webcam stream and detect it.
121
+ ********************************************************************/
122
+ var global_res = 0;
123
+ const video = document.getElementById("webcam");
124
+ const canvasElement = document.getElementById("output_canvas");
125
+ const canvasCtx = canvasElement.getContext("2d");
126
+ var x_array=[]
127
+ var y_array=[]
128
+ // Check if webcam access is supported.
129
+ const hasGetUserMedia = () => { var _a; return !!((_a = navigator.mediaDevices) === null || _a === void 0 ? void 0 : _a.getUserMedia); };
130
+ // If webcam supported, add event listener to button for when user
131
+ // wants to activate it.
132
+ if (hasGetUserMedia()) {
133
+ enableWebcamButton = document.getElementById("webcamButton");
134
+ enableWebcamButton.addEventListener("click", enableCam);
135
+ }
136
+ else {
137
+ console.warn("getUserMedia() is not supported by your browser");
138
+ }
139
+ // Enable the live webcam view and start detection.
140
+ function enableCam(event) {
141
+ if (!handLandmarker) {
142
+ console.log("Wait! objectDetector not loaded yet.");
143
+ return;
144
+ }
145
+ if (webcamRunning === true) {
146
+ webcamRunning = false;
147
+ enableWebcamButton.innerText = "ENABLE PREDICTIONS";
148
+ }
149
+ else {
150
+ webcamRunning = true;
151
+ enableWebcamButton.innerText = "DISABLE PREDICTIONS";
152
+ }
153
+ // getUsermedia parameters.
154
+ const constraints = {
155
+ video: true
156
+ };
157
+ // Activate the webcam stream.
158
+ navigator.mediaDevices.getUserMedia(constraints).then((stream) => {
159
+ video.srcObject = stream;
160
+ video.addEventListener("loadeddata", predictWebcam);
161
+ });
162
+ }
163
+ let lastVideoTime = -1;
164
+ let results = undefined;
165
+ console.log(video);
166
+ async function predictWebcam() {
167
+ canvasElement.style.width = video.videoWidth;
168
+ ;
169
+ canvasElement.style.height = video.videoHeight;
170
+ canvasElement.width = video.videoWidth;
171
+ canvasElement.height = video.videoHeight;
172
+ // Now let's start detecting the stream.
173
+ if (runningMode === "IMAGE") {
174
+ runningMode = "VIDEO";
175
+ await handLandmarker.setOptions({ runningMode: "VIDEO" });
176
+ }
177
+ let startTimeMs = performance.now();
178
+ if (lastVideoTime !== video.currentTime) {
179
+ lastVideoTime = video.currentTime;
180
+ results = handLandmarker.detectForVideo(video, startTimeMs);
181
+ }
182
+ canvasCtx.save();
183
+ canvasCtx.clearRect(0, 0, canvasElement.width, canvasElement.height);
184
+ canvasCtx.drawImage(video, 0, 0, canvasElement.width, canvasElement.height)
185
+ if (results.landmarks) {
186
+ annotateImage()
187
+ //detectSign()
188
+ }
189
+ canvasCtx.restore();
190
+ // Call this function again to keep predicting when the browser is ready.
191
+ if (webcamRunning === true) {
192
+ window.requestAnimationFrame(predictWebcam);
193
+ }
194
+ }
195
+ function annotateImage(){
196
+
197
+ //console.log(results.landmarks)
198
+ if(results.landmarks[0]){
199
+ x_array=[]
200
+ y_array=[]
201
+ results.landmarks[0].forEach(iterate)
202
+ //console.log(x_array)
203
+ var image_height = video.videoHeight
204
+ var image_width= video.videoWidth
205
+ var min_x = Math.min(...x_array)*image_width
206
+ var min_y = Math.min(...y_array)*image_height
207
+ var max_x = Math.max(...x_array)*image_width
208
+ var max_y = Math.max(...y_array)*image_height
209
+
210
+ var sect_height = max_y-(min_y)
211
+ var sect_width = max_x-(min_x)
212
+ var center_x=(min_x+max_x)/2
213
+ var center_y=(min_y+max_y)/2
214
+
215
+ var sect_diameter=50
216
+ if(sect_height>sect_width){
217
+ sect_diameter = sect_height
218
+ //console.log("sect_height", sect_diameter)
219
+ }
220
+ if(sect_height<sect_width){
221
+ sect_diameter = sect_width
222
+ // console.log("sect_width", sect_diameter)
223
+ }
224
+
225
+ sect_diameter=sect_diameter+50
226
+ var sect_radius=sect_diameter/2
227
+ var crop_top=center_y-sect_radius
228
+ var crop_bottom=center_y+sect_radius
229
+ var crop_left=center_x-sect_radius
230
+ var crop_right=center_x+sect_radius
231
+ if(crop_top<0){
232
+ crop_top=0
233
+ }
234
+ if(crop_left<0){
235
+ crop_left=0
236
+ }
237
+ if(crop_right>image_width){
238
+ crop_right=image_width
239
+ }
240
+ if(crop_bottom>image_height){
241
+ crop_bottom=image_height
242
+ }
243
+
244
+ canvasCtx.beginPath();
245
+ canvasCtx.rect(crop_left, crop_top, crop_right-crop_left, crop_bottom-crop_top);
246
+ canvasCtx.stroke();
247
+
248
+
249
+ }
250
+ for (const landmarks of results.landmarks) {
251
+ drawConnectors(canvasCtx, landmarks, HAND_CONNECTIONS, {
252
+ color: "#00FF00",
253
+ lineWidth: 5
254
+ });
255
+ drawLandmarks(canvasCtx, landmarks, { color: "#FF0000", lineWidth: 2 });
256
+ }
257
+ var dataurl=cropCanvas(canvasElement,crop_left,crop_top,244,244).toDataURL("image/jpeg", 2);
258
+ document.getElementById("output_image").src=dataurl
259
+
260
+ //# sourceURL=pen.js
261
+ }
262
+
263
+
264
+ function iterate(x,y){
265
+ x_array.push(x.x)
266
+ y_array.push(x.y)
267
+ }
268
+
269
+ const cropCanvas = (sourceCanvas,left,top,width,height) => {
270
+ let destCanvas = document.createElement('canvas');
271
+ destCanvas.width = width;
272
+ destCanvas.height = height;
273
+ destCanvas.getContext("2d").drawImage(
274
+ sourceCanvas,
275
+ left,top,width,height, // source rect with content to crop
276
+ 0,0,width,height); // newCanvas, same size as source
277
+ var predictionInput=tf.browser.fromPixels(destCanvas.getContext("2d").getImageData(0, 0, 224, 224))
278
+
279
+ predict(tf.expandDims(predictionInput,0))
280
+ return destCanvas;
281
+ }
282
+ async function predict(inputTensor){
283
+ //console.log("in predict")
284
+ var letter_list=["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","#"]
285
+ objectDetector.then(function (res) {
286
+ var prediction = res.predict(inputTensor);
287
+ var outputArray = prediction.dataSync(); // Get the output as an array
288
+ var predictedClass = outputArray.indexOf(Math.max(...outputArray)); // Get the index
289
+
290
+ console.log(letter_list[predictedClass]);
291
+ }, function (err) {
292
+ console.log(err);
293
+ });
294
+
295
+ }
296
+
297
+
298
+ </script>
299
+
300
+
301
+
302
+
303
+
304
+ <script src="https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.0/wasm/vision_wasm_internal.js" crossorigin="anonymous"></script>
305
+ </body>
306
+ </html>