js_object_detection.html 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="utf-8">
  5. <title>Object Detection Example</title>
  6. <link href="js_example_style.css" rel="stylesheet" type="text/css" />
  7. </head>
  8. <body>
  9. <h2>Object Detection Example</h2>
  10. <p>
  11. This tutorial shows you how to write an object detection example with OpenCV.js.<br>
  12. To try the example you should click the <b>modelFile</b> button(and <b>configFile</b> button if needed) to upload inference model.
  13. You can find the model URLs and parameters in the <a href="#appendix">model info</a> section.
  14. Then You should change the parameters in the first code snippet according to the uploaded model.
  15. Finally click <b>Try it</b> button to see the result. You can choose any other images.<br>
  16. </p>
  17. <div class="control"><button id="tryIt" disabled>Try it</button></div>
  18. <div>
  19. <table cellpadding="0" cellspacing="0" width="0" border="0">
  20. <tr>
  21. <td>
  22. <canvas id="canvasInput" width="400" height="400"></canvas>
  23. </td>
  24. <td>
  25. <canvas id="canvasOutput" style="visibility: hidden;" width="400" height="400"></canvas>
  26. </td>
  27. </tr>
  28. <tr>
  29. <td>
  30. <div class="caption">
  31. canvasInput <input type="file" id="fileInput" name="file" accept="image/*">
  32. </div>
  33. </td>
  34. <td>
  35. <p id='status' align="left"></p>
  36. </td>
  37. </tr>
  38. <tr>
  39. <td>
  40. <div class="caption">
  41. modelFile <input type="file" id="modelFile" name="file">
  42. </div>
  43. </td>
  44. </tr>
  45. <tr>
  46. <td>
  47. <div class="caption">
  48. configFile <input type="file" id="configFile">
  49. </div>
  50. </td>
  51. </tr>
  52. </table>
  53. </div>
  54. <div>
  55. <p class="err" id="errorMessage"></p>
  56. </div>
  57. <div>
  58. <h3>Help function</h3>
  59. <p>1.The parameters for model inference which you can modify to investigate more models.</p>
  60. <textarea class="code" rows="15" cols="100" id="codeEditor" spellcheck="false"></textarea>
  61. <p>2.Main loop in which will read the image from canvas and do inference once.</p>
  62. <textarea class="code" rows="16" cols="100" id="codeEditor1" spellcheck="false"></textarea>
  63. <p>3.Load labels from txt file and process it into an array.</p>
  64. <textarea class="code" rows="7" cols="100" id="codeEditor2" spellcheck="false"></textarea>
  65. <p>4.Get blob from image as input for net, and standardize it with <b>mean</b> and <b>std</b>.</p>
  66. <textarea class="code" rows="17" cols="100" id="codeEditor3" spellcheck="false"></textarea>
  67. <p>5.Fetch model file and save to emscripten file system once click the input button.</p>
  68. <textarea class="code" rows="17" cols="100" id="codeEditor4" spellcheck="false"></textarea>
  69. <p>6.The post-processing, including get boxes from output and draw boxes into the image.</p>
  70. <textarea class="code" rows="35" cols="100" id="codeEditor5" spellcheck="false"></textarea>
  71. </div>
  72. <div id="appendix">
  73. <h2>Model Info:</h2>
  74. </div>
  75. <script src="utils.js" type="text/javascript"></script>
  76. <script src="js_dnn_example_helper.js" type="text/javascript"></script>
  77. <script id="codeSnippet" type="text/code-snippet">
  78. inputSize = [300, 300];
  79. mean = [127.5, 127.5, 127.5];
  80. std = 0.007843;
  81. swapRB = false;
  82. confThreshold = 0.5;
  83. nmsThreshold = 0.4;
  84. // The type of output, can be YOLO or SSD
  85. outType = "SSD";
  86. // url for label file, can from local or Internet
  87. labelsUrl = "https://raw.githubusercontent.com/opencv/opencv/4.x/samples/data/dnn/object_detection_classes_pascal_voc.txt";
  88. </script>
  89. <script id="codeSnippet1" type="text/code-snippet">
  90. main = async function() {
  91. const labels = await loadLables(labelsUrl);
  92. const input = getBlobFromImage(inputSize, mean, std, swapRB, 'canvasInput');
  93. let net = cv.readNet(configPath, modelPath);
  94. net.setInput(input);
  95. const start = performance.now();
  96. const result = net.forward();
  97. const time = performance.now()-start;
  98. const output = postProcess(result, labels);
  99. updateResult(output, time);
  100. input.delete();
  101. net.delete();
  102. result.delete();
  103. }
  104. </script>
  105. <script id="codeSnippet5" type="text/code-snippet">
  106. postProcess = function(result, labels) {
  107. let canvasOutput = document.getElementById('canvasOutput');
  108. const outputWidth = canvasOutput.width;
  109. const outputHeight = canvasOutput.height;
  110. const resultData = result.data32F;
  111. // Get the boxes(with class and confidence) from the output
  112. let boxes = [];
  113. switch(outType) {
  114. case "YOLO": {
  115. const vecNum = result.matSize[0];
  116. const vecLength = result.matSize[1];
  117. const classNum = vecLength - 5;
  118. for (let i = 0; i < vecNum; ++i) {
  119. let vector = resultData.slice(i*vecLength, (i+1)*vecLength);
  120. let scores = vector.slice(5, vecLength);
  121. let classId = scores.indexOf(Math.max(...scores));
  122. let confidence = scores[classId];
  123. if (confidence > confThreshold) {
  124. let center_x = Math.round(vector[0] * outputWidth);
  125. let center_y = Math.round(vector[1] * outputHeight);
  126. let width = Math.round(vector[2] * outputWidth);
  127. let height = Math.round(vector[3] * outputHeight);
  128. let left = Math.round(center_x - width / 2);
  129. let top = Math.round(center_y - height / 2);
  130. let box = {
  131. scores: scores,
  132. classId: classId,
  133. confidence: confidence,
  134. bounding: [left, top, width, height],
  135. toDraw: true
  136. }
  137. boxes.push(box);
  138. }
  139. }
  140. // NMS(Non Maximum Suppression) algorithm
  141. let boxNum = boxes.length;
  142. let tmp_boxes = [];
  143. let sorted_boxes = [];
  144. for (let c = 0; c < classNum; ++c) {
  145. for (let i = 0; i < boxes.length; ++i) {
  146. tmp_boxes[i] = [boxes[i], i];
  147. }
  148. sorted_boxes = tmp_boxes.sort((a, b) => { return (b[0].scores[c] - a[0].scores[c]); });
  149. for (let i = 0; i < boxNum; ++i) {
  150. if (sorted_boxes[i][0].scores[c] === 0) continue;
  151. else {
  152. for (let j = i + 1; j < boxNum; ++j) {
  153. if (IOU(sorted_boxes[i][0], sorted_boxes[j][0]) >= nmsThreshold) {
  154. boxes[sorted_boxes[j][1]].toDraw = false;
  155. }
  156. }
  157. }
  158. }
  159. }
  160. } break;
  161. case "SSD": {
  162. const vecNum = result.matSize[2];
  163. const vecLength = 7;
  164. for (let i = 0; i < vecNum; ++i) {
  165. let vector = resultData.slice(i*vecLength, (i+1)*vecLength);
  166. let confidence = vector[2];
  167. if (confidence > confThreshold) {
  168. let left, top, right, bottom, width, height;
  169. left = Math.round(vector[3]);
  170. top = Math.round(vector[4]);
  171. right = Math.round(vector[5]);
  172. bottom = Math.round(vector[6]);
  173. width = right - left + 1;
  174. height = bottom - top + 1;
  175. if (width <= 2 || height <= 2) {
  176. left = Math.round(vector[3] * outputWidth);
  177. top = Math.round(vector[4] * outputHeight);
  178. right = Math.round(vector[5] * outputWidth);
  179. bottom = Math.round(vector[6] * outputHeight);
  180. width = right - left + 1;
  181. height = bottom - top + 1;
  182. }
  183. let box = {
  184. classId: vector[1] - 1,
  185. confidence: confidence,
  186. bounding: [left, top, width, height],
  187. toDraw: true
  188. }
  189. boxes.push(box);
  190. }
  191. }
  192. } break;
  193. default:
  194. console.error(`Unsupported output type ${outType}`)
  195. }
  196. // Draw the saved box into the image
  197. let image = cv.imread("canvasInput");
  198. let output = new cv.Mat(outputWidth, outputHeight, cv.CV_8UC3);
  199. cv.cvtColor(image, output, cv.COLOR_RGBA2RGB);
  200. let boxNum = boxes.length;
  201. for (let i = 0; i < boxNum; ++i) {
  202. if (boxes[i].toDraw) {
  203. drawBox(boxes[i]);
  204. }
  205. }
  206. return output;
  207. // Calculate the IOU(Intersection over Union) of two boxes
  208. function IOU(box1, box2) {
  209. let bounding1 = box1.bounding;
  210. let bounding2 = box2.bounding;
  211. let s1 = bounding1[2] * bounding1[3];
  212. let s2 = bounding2[2] * bounding2[3];
  213. let left1 = bounding1[0];
  214. let right1 = left1 + bounding1[2];
  215. let left2 = bounding2[0];
  216. let right2 = left2 + bounding2[2];
  217. let overlapW = calOverlap([left1, right1], [left2, right2]);
  218. let top1 = bounding2[1];
  219. let bottom1 = top1 + bounding1[3];
  220. let top2 = bounding2[1];
  221. let bottom2 = top2 + bounding2[3];
  222. let overlapH = calOverlap([top1, bottom1], [top2, bottom2]);
  223. let overlapS = overlapW * overlapH;
  224. return overlapS / (s1 + s2 + overlapS);
  225. }
  226. // Calculate the overlap range of two vector
  227. function calOverlap(range1, range2) {
  228. let min1 = range1[0];
  229. let max1 = range1[1];
  230. let min2 = range2[0];
  231. let max2 = range2[1];
  232. if (min2 > min1 && min2 < max1) {
  233. return max1 - min2;
  234. } else if (max2 > min1 && max2 < max1) {
  235. return max2 - min1;
  236. } else {
  237. return 0;
  238. }
  239. }
  240. // Draw one predict box into the origin image
  241. function drawBox(box) {
  242. let bounding = box.bounding;
  243. let left = bounding[0];
  244. let top = bounding[1];
  245. let width = bounding[2];
  246. let height = bounding[3];
  247. cv.rectangle(output, new cv.Point(left, top), new cv.Point(left + width, top + height),
  248. new cv.Scalar(0, 255, 0));
  249. cv.rectangle(output, new cv.Point(left, top), new cv.Point(left + width, top + 15),
  250. new cv.Scalar(255, 255, 255), cv.FILLED);
  251. let text = `${labels[box.classId]}: ${box.confidence.toFixed(4)}`;
  252. cv.putText(output, text, new cv.Point(left, top + 10), cv.FONT_HERSHEY_SIMPLEX, 0.3,
  253. new cv.Scalar(0, 0, 0));
  254. }
  255. }
  256. </script>
  257. <script type="text/javascript">
  258. let jsonUrl = "js_object_detection_model_info.json";
  259. drawInfoTable(jsonUrl, 'appendix');
  260. let utils = new Utils('errorMessage');
  261. utils.loadCode('codeSnippet', 'codeEditor');
  262. utils.loadCode('codeSnippet1', 'codeEditor1');
  263. let loadLablesCode = 'loadLables = ' + loadLables.toString();
  264. document.getElementById('codeEditor2').value = loadLablesCode;
  265. let getBlobFromImageCode = 'getBlobFromImage = ' + getBlobFromImage.toString();
  266. document.getElementById('codeEditor3').value = getBlobFromImageCode;
  267. let loadModelCode = 'loadModel = ' + loadModel.toString();
  268. document.getElementById('codeEditor4').value = loadModelCode;
  269. utils.loadCode('codeSnippet5', 'codeEditor5');
  270. let canvas = document.getElementById('canvasInput');
  271. let ctx = canvas.getContext('2d');
  272. let img = new Image();
  273. img.crossOrigin = 'anonymous';
  274. img.src = 'lena.png';
  275. img.onload = function() {
  276. ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
  277. };
  278. let tryIt = document.getElementById('tryIt');
  279. tryIt.addEventListener('click', () => {
  280. initStatus();
  281. document.getElementById('status').innerHTML = 'Running function main()...';
  282. utils.executeCode('codeEditor');
  283. utils.executeCode('codeEditor1');
  284. if (modelPath === "") {
  285. document.getElementById('status').innerHTML = 'Runing failed.';
  286. utils.printError('Please upload model file by clicking the button first.');
  287. } else {
  288. setTimeout(main, 1);
  289. }
  290. });
  291. let fileInput = document.getElementById('fileInput');
  292. fileInput.addEventListener('change', (e) => {
  293. initStatus();
  294. loadImageToCanvas(e, 'canvasInput');
  295. });
  296. let configPath = "";
  297. let configFile = document.getElementById('configFile');
  298. configFile.addEventListener('change', async (e) => {
  299. initStatus();
  300. configPath = await loadModel(e);
  301. document.getElementById('status').innerHTML = `The config file '${configPath}' is created successfully.`;
  302. });
  303. let modelPath = "";
  304. let modelFile = document.getElementById('modelFile');
  305. modelFile.addEventListener('change', async (e) => {
  306. initStatus();
  307. modelPath = await loadModel(e);
  308. document.getElementById('status').innerHTML = `The model file '${modelPath}' is created successfully.`;
  309. configPath = "";
  310. configFile.value = "";
  311. });
  312. utils.loadOpenCv(() => {
  313. tryIt.removeAttribute('disabled');
  314. });
  315. var main = async function() {};
  316. var postProcess = function(result, labels) {};
  317. utils.executeCode('codeEditor1');
  318. utils.executeCode('codeEditor2');
  319. utils.executeCode('codeEditor3');
  320. utils.executeCode('codeEditor4');
  321. utils.executeCode('codeEditor5');
  322. function updateResult(output, time) {
  323. try{
  324. let canvasOutput = document.getElementById('canvasOutput');
  325. canvasOutput.style.visibility = "visible";
  326. cv.imshow('canvasOutput', output);
  327. document.getElementById('status').innerHTML = `<b>Model:</b> ${modelPath}<br>
  328. <b>Inference time:</b> ${time.toFixed(2)} ms`;
  329. } catch(e) {
  330. console.log(e);
  331. }
  332. }
  333. function initStatus() {
  334. document.getElementById('status').innerHTML = '';
  335. document.getElementById('canvasOutput').style.visibility = "hidden";
  336. utils.clearError();
  337. }
  338. </script>
  339. </body>
  340. </html>