{"id":100642,"date":"2020-12-16T15:10:20","date_gmt":"2020-12-16T15:10:20","guid":{"rendered":"https:\/\/randomnerdtutorials.com\/?p=100642"},"modified":"2024-06-16T15:08:15","modified_gmt":"2024-06-16T15:08:15","slug":"esp32-cam-opencv-js-color-detection-tracking","status":"publish","type":"post","link":"https:\/\/randomnerdtutorials.com\/esp32-cam-opencv-js-color-detection-tracking\/","title":{"rendered":"ESP32-CAM Web Server with OpenCV.js: Color Detection and Tracking"},"content":{"rendered":"\n<p>This guide introduces OpenCV.js and OpenCV tools for the ESP32 Camera Web Server environment. As an example, we&#8217;ll build a simple ESP32 Camera Web Server that includes color detection and tracking of a moving object.<\/p>\n\n\n<div class=\"wp-block-image is-style-default\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" fetchpriority=\"high\" decoding=\"async\" width=\"1200\" height=\"675\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/12\/ESP32-CAM-Web-Server-with-OpenCVjs-Color-Detection-Tracking.jpg?resize=1200%2C675&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"ESP32-CAM Web Server with OpenCV.js: Color Detection and Tracking\" class=\"wp-image-100812\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/12\/ESP32-CAM-Web-Server-with-OpenCVjs-Color-Detection-Tracking.jpg?w=1280&amp;quality=100&amp;strip=all&amp;ssl=1 1280w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/12\/ESP32-CAM-Web-Server-with-OpenCVjs-Color-Detection-Tracking.jpg?resize=300%2C169&amp;quality=100&amp;strip=all&amp;ssl=1 300w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/12\/ESP32-CAM-Web-Server-with-OpenCVjs-Color-Detection-Tracking.jpg?resize=1024%2C576&amp;quality=100&amp;strip=all&amp;ssl=1 1024w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/12\/ESP32-CAM-Web-Server-with-OpenCVjs-Color-Detection-Tracking.jpg?resize=768%2C432&amp;quality=100&amp;strip=all&amp;ssl=1 768w\" sizes=\"(max-width: 1200px) 100vw, 1200px\" \/><\/figure><\/div>\n\n\n<p>This tutorial is by no means an exhaustive treatment of all that OpenCV can offer to ESP32 camera web servers. It is expected that this introduction will inspire additional OpenCV work with the ESP32 cameras.<\/p>\n\n\n\n<p class=\"has-text-align-center rntbox rntclgray\">This project\/tutorial was created based on <a href=\"#andrew\">Andrew R. Sass<\/a> project and edited by Sara Santos.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Introduction<\/h2>\n\n\n\n<p>The ESP32 can act as a server for a browser client and some models include a camera (for example, <a href=\"https:\/\/makeradvisor.com\/esp32-cam-ov2640-camera\/\" target=\"_blank\" rel=\"noreferrer noopener\">ESP32-CAM<\/a>) which allows the client to view still or video pictures in the browser. HTML, JavaScript, and other browser languages can take advantage of the extensive capabilities of ESP32 and its camera.<\/p>\n\n\n\n<p>For those who have little or no experience with the ESP32 camera development boards can start with the following tutorial.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/randomnerdtutorials.com\/esp32-cam-video-streaming-face-recognition-arduino-ide\/\">ESP32-CAM Video Streaming and Face Recognition with Arduino IDE<\/a><\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">OpenCV.js<\/h3>\n\n\n\n<p>As described in <a href=\"https:\/\/docs.opencv.org\/master\/\" target=\"_blank\" rel=\"noreferrer noopener nofollow\">docs.opencv.org<\/a>, \u201c<em>OpenCV.js is a JavaScript binding for a selected subset of OpenCV functions for the web platform<\/em>\u201d. OpenCV.js uses Emscripten, a LLVM-to-JavaScript compiler, to compile OpenCV functions for an API library which continues to grow.<\/p>\n\n\n<div class=\"wp-block-image is-style-default\">\n<figure class=\"aligncenter size-full is-resized\"><img data-recalc-dims=\"1\" decoding=\"async\" width=\"500\" height=\"616\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/12\/OpenCV-logo.png?resize=500%2C616&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"OpenCV.js\" class=\"wp-image-100644\" style=\"width:125px;height:154px\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/12\/OpenCV-logo.png?w=500&amp;quality=100&amp;strip=all&amp;ssl=1 500w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/12\/OpenCV-logo.png?resize=244%2C300&amp;quality=100&amp;strip=all&amp;ssl=1 244w\" sizes=\"(max-width: 500px) 100vw, 500px\" \/><\/figure><\/div>\n\n\n<p>OpenCV.js runs in a browser which allows rapid trial of OpenCV functions by someone with only a modest background in HTML and JavaScript. Those with a background in Esp32 Camera applications have this background already.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Project Overview<\/h2>\n\n\n\n<p>The project we&#8217;ll build throughout this tutorial creates a web server that allows color tracking of a moving object. On the web server interface, you play with several configurations to properly select the color you want to track. Then, the browser sends the real time x and y coordinates of the center of mass of the moving object to the ESP32 board.<\/p>\n\n\n<div class=\"wp-block-image is-style-default\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" decoding=\"async\" width=\"1200\" height=\"691\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/12\/ESP32-CAM-Color-Tracking-Project-Overview.jpg?resize=1200%2C691&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"ESP32-CAm Color Tracking OpenCVJS Project Overview\" class=\"wp-image-100797\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/12\/ESP32-CAM-Color-Tracking-Project-Overview.jpg?w=1267&amp;quality=100&amp;strip=all&amp;ssl=1 1267w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/12\/ESP32-CAM-Color-Tracking-Project-Overview.jpg?resize=300%2C173&amp;quality=100&amp;strip=all&amp;ssl=1 300w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/12\/ESP32-CAM-Color-Tracking-Project-Overview.jpg?resize=1024%2C590&amp;quality=100&amp;strip=all&amp;ssl=1 1024w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/12\/ESP32-CAM-Color-Tracking-Project-Overview.jpg?resize=768%2C442&amp;quality=100&amp;strip=all&amp;ssl=1 768w\" sizes=\"(max-width: 1200px) 100vw, 1200px\" \/><\/figure><\/div>\n\n\n<p>Here&#8217;s a preview of the web server.<\/p>\n\n\n<div class=\"wp-block-image is-style-default\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1117\" height=\"1012\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/12\/ESP32-CAM-Color-Tracking-Web-Server-Preview.jpg?resize=1117%2C1012&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"ESP32-CAM Color Tracking Web Server Preview\" class=\"wp-image-100798\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/12\/ESP32-CAM-Color-Tracking-Web-Server-Preview.jpg?w=1117&amp;quality=100&amp;strip=all&amp;ssl=1 1117w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/12\/ESP32-CAM-Color-Tracking-Web-Server-Preview.jpg?resize=300%2C272&amp;quality=100&amp;strip=all&amp;ssl=1 300w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/12\/ESP32-CAM-Color-Tracking-Web-Server-Preview.jpg?resize=1024%2C928&amp;quality=100&amp;strip=all&amp;ssl=1 1024w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/12\/ESP32-CAM-Color-Tracking-Web-Server-Preview.jpg?resize=768%2C696&amp;quality=100&amp;strip=all&amp;ssl=1 768w\" sizes=\"(max-width: 1117px) 100vw, 1117px\" \/><\/figure><\/div>\n\n\n<h2 class=\"wp-block-heading\">Prerequisites<\/h2>\n\n\n\n<p>Before proceeding with this project, make sure you follow the next pre-requisites.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Arduino IDE<\/h3>\n\n\n\n<p>We\u2019ll program the ESP32 board using Arduino IDE. So, you need the Arduino IDE installed as well as the ESP32 add-on:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/randomnerdtutorials.com\/installing-esp32-arduino-ide-2-0\/\" title=\"\">Installing the ESP32 Board in Arduino IDE (Windows, Mac OS X, Linux)<\/a><\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">VS Code (optional)<\/h3>\n\n\n\n<p>If you prefer to use VS Code + PlatformIO to program your board, you can follow the next tutorial to learn how to set up VS Code to work with the ESP32 boards.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/randomnerdtutorials.com\/vs-code-platformio-ide-esp32-esp8266-arduino\/\">Getting Started with VS Code and PlatformIO IDE for ESP32<\/a><\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Getting an ESP32 Camera<\/h3>\n\n\n\n<p>This project is compatible with any ESP32 camera board that features an OV2640 camera. There are several ESP32 camera models out there. For a comparison of the most popular cameras, you can refer to the next article:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/makeradvisor.com\/esp32-camera-cam-boards-review-comparison\/\" target=\"_blank\" rel=\"noreferrer noopener\">ESP32 Camera Dev Boards Review and Comparison (Best ESP32-CAM)<\/a><\/li>\n<\/ul>\n\n\n\n<p>Make sure you know the pin assignment for the camera board you&#8217;re using. For the pin assignment of the most popular boards, check this article:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/randomnerdtutorials.com\/projects-esp32-cam\/\">ESP32-CAM Camera Boards: Pin and GPIOs Assignment Guide<\/a><\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Code &#8211; ESP32-CAM with OpenCV.js<\/h2>\n\n\n\n<p>The program consists of two parts:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>the server program which runs on the ESP32 Camera <\/li>\n\n\n\n<li>the client program which runs on the Chrome browser<\/li>\n<\/ul>\n\n\n\n<p>The program is split into two files: the <span class=\"rnthl rntliteral\">OCV_ColorTrack_P.ino<\/span> file containing the server program and the <span class=\"rnthl rntliteral\">index_OCV_ColorTrack.h<\/span> header file containing the client program (HTML, CSS and JavaScript with OpenCV.js).<\/p>\n\n\n\n<p>Create a new Arduino sketch called <span class=\"rnthl rntliteral\">OCV_ColorTrack_P<\/span> and copy the following code.<\/p>\n\n\n<pre style=\"max-height: 40em; margin-bottom: 20px;\"><code class=\"language-c\">\/*********\n  The include file, index_OCV_ColorTrack.h, the Client, is an intoduction of OpenCV.js to the ESP32 Camera environment. The Client was\n  developed and written by Andrew R. Sass. Permission to reproduce the index_OCV_ColorTrack.h file is granted free of charge if this\n  entire copyright notice is included in all copies of the index_OCV_ColorTrack.h file.\n  \n  Complete instructions at https:\/\/RandomNerdTutorials.com\/esp32-cam-opencv-js-color-detection-tracking\/\n  \n  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.\n  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n*********\/\n\n#include &lt;WiFi.h&gt;\n#include &lt;WiFiClientSecure.h&gt;\n#include &quot;esp_camera.h&quot;\n#include &quot;soc\/soc.h&quot;\n#include &quot;soc\/rtc_cntl_reg.h&quot;\n#include &quot;index_OCV_ColorTrack.h&quot;\n\n\/\/ Replace with your network credentials\nconst char* ssid = &quot;REPLACE_WITH_YOUR_SSID&quot;;\nconst char* password = &quot;REPLACE_WITH_YOUR_PASSWORD&quot;;\n \nString Feedback=&quot;&quot;;\nString Command=&quot;&quot;,cmd=&quot;&quot;,P1=&quot;&quot;,P2=&quot;&quot;,P3=&quot;&quot;,P4=&quot;&quot;,P5=&quot;&quot;,P6=&quot;&quot;,P7=&quot;&quot;,P8=&quot;&quot;,P9=&quot;&quot;;\nbyte ReceiveState=0,cmdState=1,strState=1,questionstate=0,equalstate=0,semicolonstate=0;\n\/\/ANN:0\n\/\/       AI-Thinker                    \n#define PWDN_GPIO_NUM     32\n#define RESET_GPIO_NUM    -1\n#define XCLK_GPIO_NUM      0\n#define SIOD_GPIO_NUM     26\n#define SIOC_GPIO_NUM     27\n#define Y9_GPIO_NUM       35\n#define Y8_GPIO_NUM       34\n#define Y7_GPIO_NUM       39\n#define Y6_GPIO_NUM       36\n#define Y5_GPIO_NUM       21\n#define Y4_GPIO_NUM       19\n#define Y3_GPIO_NUM       18\n#define Y2_GPIO_NUM        5\n#define VSYNC_GPIO_NUM    25\n#define HREF_GPIO_NUM     23\n#define PCLK_GPIO_NUM     22\n\nWiFiServer server(80);\n\/\/ANN:2\nvoid ExecuteCommand() {\n  if (cmd!=&quot;colorDetect&quot;) {  \/\/Omit printout\n    \/\/Serial.println(&quot;cmd= &quot;+cmd+&quot; ,P1= &quot;+P1+&quot; ,P2= &quot;+P2+&quot; ,P3= &quot;+P3+&quot; ,P4= &quot;+P4+&quot; ,P5= &quot;+P5+&quot; ,P6= &quot;+P6+&quot; ,P7= &quot;+P7+&quot; ,P8= &quot;+P8+&quot; ,P9= &quot;+P9);\n    \/\/Serial.println(&quot;&quot;);\n  }\n  \n  if (cmd==&quot;resetwifi&quot;) {\n    WiFi.begin(P1.c_str(), P2.c_str());\n    Serial.print(&quot;Connecting to &quot;);\n    Serial.println(P1);\n    long int StartTime=millis();\n    while (WiFi.status() != WL_CONNECTED) \n    {\n        delay(500);\n        if ((StartTime+5000) &lt; millis()) break;\n    } \n    Serial.println(&quot;&quot;);\n    Serial.println(&quot;STAIP: &quot;+WiFi.localIP().toString());\n    Feedback=&quot;STAIP: &quot;+WiFi.localIP().toString();\n  }    \n  else if (cmd==&quot;restart&quot;) {\n    ESP.restart();\n  }\n  else if (cmd==&quot;cm&quot;){\n    int XcmVal = P1.toInt();\n    int YcmVal = P2.toInt();\n    Serial.println(&quot;cmd= &quot;+cmd+&quot; ,VALXCM= &quot;+XcmVal);\n    Serial.println(&quot;cmd= &quot;+cmd+&quot; ,VALYCM= &quot;+YcmVal);   \n  }\n  else if (cmd==&quot;quality&quot;) { \n    sensor_t * s = esp_camera_sensor_get();\n    int val = P1.toInt(); \n    s-&gt;set_quality(s, val);\n  }\n  else if (cmd==&quot;contrast&quot;) {\n    sensor_t * s = esp_camera_sensor_get();\n    int val = P1.toInt(); \n    s-&gt;set_contrast(s, val);\n  }\n  else if (cmd==&quot;brightness&quot;) {\n    sensor_t * s = esp_camera_sensor_get();\n    int val = P1.toInt();  \n    s-&gt;set_brightness(s, val);  \n  }   \n  else {\n    Feedback=&quot;Command is not defined.&quot;;\n  }\n  if (Feedback==&quot;&quot;) {\n    Feedback=Command;\n  }\n}\n\nvoid setup() {\n  WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0);\n  \n  Serial.begin(115200);\n  Serial.setDebugOutput(true);\n  Serial.println();\n\n  camera_config_t config;\n  config.ledc_channel = LEDC_CHANNEL_0;\n  config.ledc_timer = LEDC_TIMER_0;\n  config.pin_d0 = Y2_GPIO_NUM;\n  config.pin_d1 = Y3_GPIO_NUM;\n  config.pin_d2 = Y4_GPIO_NUM;\n  config.pin_d3 = Y5_GPIO_NUM;\n  config.pin_d4 = Y6_GPIO_NUM;\n  config.pin_d5 = Y7_GPIO_NUM;\n  config.pin_d6 = Y8_GPIO_NUM;\n  config.pin_d7 = Y9_GPIO_NUM;\n  config.pin_xclk = XCLK_GPIO_NUM;\n  config.pin_pclk = PCLK_GPIO_NUM;\n  config.pin_vsync = VSYNC_GPIO_NUM;\n  config.pin_href = HREF_GPIO_NUM;\n  config.pin_sccb_sda = SIOD_GPIO_NUM;\n  config.pin_sccb_scl = SIOC_GPIO_NUM;\n  config.pin_pwdn = PWDN_GPIO_NUM;\n  config.pin_reset = RESET_GPIO_NUM;\n  config.xclk_freq_hz = 20000000;\n  config.pixel_format = PIXFORMAT_JPEG;\n  \/\/init with high specs to pre-allocate larger buffers\n  if(psramFound()){\n    config.frame_size = FRAMESIZE_UXGA;\n    config.jpeg_quality = 10;  \/\/0-63 lower number means higher quality\n    config.fb_count = 2;\n  } else {\n    config.frame_size = FRAMESIZE_SVGA;\n    config.jpeg_quality = 12;  \/\/0-63 lower number means higher quality\n    config.fb_count = 1;\n  }\n  \n  \/\/ camera init\n  esp_err_t err = esp_camera_init(&amp;config);\n  if (err != ESP_OK) {\n    Serial.printf(&quot;Camera init failed with error 0x%x&quot;, err);\n    delay(1000);\n    ESP.restart();\n  }\n\n  \/\/drop down frame size for higher initial frame rate\n  sensor_t * s = esp_camera_sensor_get();\n  s-&gt;set_framesize(s, FRAMESIZE_CIF);  \/\/UXGA|SXGA|XGA|SVGA|VGA|CIF|QVGA|HQVGA|QQVGA  \u8a2d\u5b9a\u521d\u59cb\u5316\u5f71\u50cf\u89e3\u6790\u5ea6\n     \n  WiFi.mode(WIFI_AP_STA);\n  WiFi.begin(ssid, password);   \n\n  delay(1000);\n\n  long int StartTime=millis();\n  while (WiFi.status() != WL_CONNECTED) {\n    delay(500);\n    if ((StartTime+10000) &lt; millis()) \n      break;   \n  } \n\n  if (WiFi.status() == WL_CONNECTED) {   \n    Serial.print(&quot;ESP IP Address: http:\/\/&quot;);\n    Serial.println(WiFi.localIP());  \n  }\n  server.begin();          \n}\n\n\n\nvoid loop() {\n  Feedback=&quot;&quot;;Command=&quot;&quot;;cmd=&quot;&quot;;P1=&quot;&quot;;P2=&quot;&quot;;P3=&quot;&quot;;P4=&quot;&quot;;P5=&quot;&quot;;P6=&quot;&quot;;P7=&quot;&quot;;P8=&quot;&quot;;P9=&quot;&quot;;\n  ReceiveState=0,cmdState=1,strState=1,questionstate=0,equalstate=0,semicolonstate=0;\n  \n  WiFiClient client = server.available();\n\n  if (client) { \n    String currentLine = &quot;&quot;;\n\n    while (client.connected()) {\n      if (client.available()) {\n        char c = client.read();             \n        \n        getCommand(c);\n                \n        if (c == '\\n') {\n          if (currentLine.length() == 0) {    \n            \n            if (cmd==&quot;colorDetect&quot;) {\n              camera_fb_t * fb = NULL;\n              fb = esp_camera_fb_get();  \n              if(!fb) {\n                Serial.println(&quot;Camera capture failed&quot;);\n                delay(1000);\n                ESP.restart();\n              }\n              \/\/ANN:1\n              client.println(&quot;HTTP\/1.1 200 OK&quot;);\n              client.println(&quot;Access-Control-Allow-Origin: *&quot;);              \n              client.println(&quot;Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept&quot;);\n              client.println(&quot;Access-Control-Allow-Methods: GET,POST,PUT,DELETE,OPTIONS&quot;);\n              client.println(&quot;Content-Type: image\/jpeg&quot;);\n              client.println(&quot;Content-Disposition: form-data; name=\\&quot;imageFile\\&quot;; filename=\\&quot;picture.jpg\\&quot;&quot;); \n              client.println(&quot;Content-Length: &quot; + String(fb-&gt;len));             \n              client.println(&quot;Connection: close&quot;);\n              client.println();\n              \n              uint8_t *fbBuf = fb-&gt;buf;\n              size_t fbLen = fb-&gt;len;\n              for (size_t n=0;n&lt;fbLen;n=n+1024) {\n                if (n+1024&lt;fbLen) {\n                  client.write(fbBuf, 1024);\n                  fbBuf += 1024;\n                }\n                else if (fbLen%1024&gt;0) {\n                  size_t remainder = fbLen%1024;\n                  client.write(fbBuf, remainder);\n                }\n              }    \n              esp_camera_fb_return(fb);                        \n            }\n            else {\n              \/\/ANN:1\n              client.println(&quot;HTTP\/1.1 200 OK&quot;);\n              client.println(&quot;Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept&quot;);\n              client.println(&quot;Access-Control-Allow-Methods: GET,POST,PUT,DELETE,OPTIONS&quot;);\n              client.println(&quot;Content-Type: text\/html; charset=utf-8&quot;);\n              client.println(&quot;Access-Control-Allow-Origin: *&quot;);\n              client.println(&quot;Connection: close&quot;);\n              client.println();           \n              String Data=&quot;&quot;;\n              if (cmd!=&quot;&quot;)\n                Data = Feedback;\n              else {\n                Data = String((const char *)INDEX_HTML);\n              }\n              int Index;\n              for (Index = 0; Index &lt; Data.length(); Index = Index+1000) {\n                client.print(Data.substring(Index, Index+1000));\n              }        \n              client.println();\n            }\n                        \n            Feedback=&quot;&quot;;\n            break;\n          } else {\n            currentLine = &quot;&quot;;\n          }\n        } \n        else if (c != '\\r') {\n          currentLine += c;\n        }\n        if ((currentLine.indexOf(&quot;\/?&quot;)!=-1)&amp;&amp;(currentLine.indexOf(&quot; HTTP&quot;)!=-1)) {\n          if (Command.indexOf(&quot;stop&quot;)!=-1) {  \n            client.println();\n            client.println();\n            client.stop();\n          }\n          currentLine=&quot;&quot;;\n          Feedback=&quot;&quot;;\n          ExecuteCommand();\n        }\n      }\n    }\n    delay(1);\n    client.stop();\n  }\n}\n\nvoid getCommand(char c){\n  if (c=='?') ReceiveState=1;\n  if ((c==' ')||(c=='\\r')||(c=='\\n')) ReceiveState=0;\n  \n  if (ReceiveState==1) {\n    Command=Command+String(c);    \n    if (c=='=') cmdState=0;\n    if (c==';') strState++;\n    if ((cmdState==1)&amp;&amp;((c!='?')||(questionstate==1))) cmd=cmd+String(c);\n    if ((cmdState==0)&amp;&amp;(strState==1)&amp;&amp;((c!='=')||(equalstate==1))) P1=P1+String(c);\n    if ((cmdState==0)&amp;&amp;(strState==2)&amp;&amp;(c!=';')) P2=P2+String(c);\n    if ((cmdState==0)&amp;&amp;(strState==3)&amp;&amp;(c!=';')) P3=P3+String(c);\n    if ((cmdState==0)&amp;&amp;(strState==4)&amp;&amp;(c!=';')) P4=P4+String(c);\n    if ((cmdState==0)&amp;&amp;(strState==5)&amp;&amp;(c!=';')) P5=P5+String(c);\n    if ((cmdState==0)&amp;&amp;(strState==6)&amp;&amp;(c!=';')) P6=P6+String(c);\n    if ((cmdState==0)&amp;&amp;(strState==7)&amp;&amp;(c!=';')) P7=P7+String(c);\n    if ((cmdState==0)&amp;&amp;(strState==8)&amp;&amp;(c!=';')) P8=P8+String(c);\n    if ((cmdState==0)&amp;&amp;(strState&gt;=9)&amp;&amp;((c!=';')||(semicolonstate==1))) P9=P9+String(c);   \n    if (c=='?') questionstate=1;\n    if (c=='=') equalstate=1;\n    if ((strState&gt;=9)&amp;&amp;(c==';')) semicolonstate=1;\n  }\n}\n<\/code><\/pre>\n\t<p style=\"text-align:center\"><a class=\"rntwhite\" href=\"https:\/\/github.com\/RuiSantosdotme\/ESP32-CAM-Arduino-IDE\/raw\/master\/ESP32-CAM-OpenCV-js\/ESP32-CAM-OpenCV-js.ino\" target=\"_blank\">View raw code<\/a><\/p>\n\n\n\n<p>Save that file.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">index_OCV_ColorTrack.h<\/h3>\n\n\n\n<p>Then, open a new tab in the Arduino IDE as shown in the following image.<\/p>\n\n\n<div class=\"wp-block-image is-style-default\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"904\" height=\"299\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/12\/Arduino-IDE-Create-New-Tab.png?resize=904%2C299&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"Arduino IDE Create a New Tab\" class=\"wp-image-100680\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/12\/Arduino-IDE-Create-New-Tab.png?w=904&amp;quality=100&amp;strip=all&amp;ssl=1 904w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/12\/Arduino-IDE-Create-New-Tab.png?resize=300%2C99&amp;quality=100&amp;strip=all&amp;ssl=1 300w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/12\/Arduino-IDE-Create-New-Tab.png?resize=768%2C254&amp;quality=100&amp;strip=all&amp;ssl=1 768w\" sizes=\"(max-width: 904px) 100vw, 904px\" \/><\/figure><\/div>\n\n\n<p>Name it <span class=\"rnthl rntliteral\">index_OCV_ColorTrack.h<\/span>.<\/p>\n\n\n<div class=\"wp-block-image is-style-default\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"705\" height=\"171\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/12\/Arduino-IDE-name-new-file-tab.png?resize=705%2C171&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"Arduino IDE Name a New Tab\" class=\"wp-image-100681\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/12\/Arduino-IDE-name-new-file-tab.png?w=705&amp;quality=100&amp;strip=all&amp;ssl=1 705w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/12\/Arduino-IDE-name-new-file-tab.png?resize=300%2C73&amp;quality=100&amp;strip=all&amp;ssl=1 300w\" sizes=\"(max-width: 705px) 100vw, 705px\" \/><\/figure><\/div>\n\n\n<p>Copy the following into that file. <\/p>\n\n\n<pre style=\"max-height: 40em; margin-bottom: 20px;\"><code class=\"language-c\">\/****************************\n  This include file, index_OCV_ColorTrack.h, the Client, is an intoduction of OpenCV.js to the ESP32 Camera environment. The Client was\n  developed and written by Andrew R. Sass. Permission to reproduce the index_OCV_ColorTrack.h file is granted free of charge if this\n  entire copyright notice is included in all copies of the index_OCV_ColorTrack.h file. \n  \n  Complete instructions at https:\/\/RandomNerdTutorials.com\/esp32-cam-opencv-js-color-detection-tracking\/\n*******************************\/\nstatic const char PROGMEM INDEX_HTML[] = R&quot;rawliteral(\n&lt;!DOCTYPE html&gt;\n&lt;html&gt;\n&lt;head&gt;\n   &lt;title&gt;ESP32-CAMERA COLOR DETECTION&lt;\/title&gt;\n   &lt;meta charset=&quot;utf-8&quot;&gt;\n   &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width,initial-scale=1&quot;&gt;\n   &lt;!----ANN:3---&gt;\n   &lt;script async src=&quot; https:\/\/docs.opencv.org\/master\/opencv.js&quot; type=&quot;text\/javascript&quot;&gt;&lt;\/script&gt;\n&lt;\/head&gt;\n&lt;style&gt;\nhtml {\n    font-family: Arial, Helvetica, sans-serif;\n    }\nbody { \n    background-color: #F7F7F2;\n    margin: 0px;\n}\nh1 {\n    font-size: 1.6rem;\n    color:white;\n    text-align: center;\n}\n.topnav {\n    overflow: hidden;\n    background-color: #0A1128;\n}\n.main-controls{\n  padding-top: 5px;\n}\nh2 {\n    color: #0A1128;\n    font-size: 1rem;\n}    \n.section {\n    margin: 2px;\n    padding: 10px;\n}\n.column{\n    float: left;\n    width: 50%\n}\ntable {\n    margin: 0;\n    width: 90%;\n    border-collapse: collapse;\n}\nth{\n    text-align: center;\n}\n.row{\n    margin-right:50px;\n    margin-left:50px;\n}\n\n#colorDetect{ \n    border: none;\n    color: #FEFCFB;\n    background-color: #0A1128;\n    padding: 15px;\n    text-align: center;\n    display: inline-block;\n    font-size: 16px;\n    border-radius: 4px;\n}\n#restart{\n    border: none;\n    color: #FEFCFB;\n    background-color: #7B0828;\n    padding: 15px;\n    text-align: center;\n    display: inline-block;\n    font-size: 16px;\n    border-radius: 4px;  \n}\nbutton{\n    border: none;\n    color: #FEFCFB;\n    background-color: #0A1128;\n    padding: 10px;\n    text-align: center;\n    display: inline-block;\n    border-radius: 4px;    \n}\n\n&lt;\/style&gt;\n&lt;body&gt;\n    &lt;div class=&quot;topnav&quot;&gt;\n        &lt;h1&gt;ESP32-CAM Color Detection and Tracking&lt;\/h1&gt;\n    &lt;\/div&gt;\n    &lt;div class=&quot;main-controls&quot;&gt;\n        &lt;table&gt;\n            &lt;tr&gt;\n                &lt;td&gt;&lt;center&gt;&lt;input type=&quot;button&quot; id=&quot;colorDetect&quot; value=&quot;COLOR DETECTION&quot;&gt;&lt;\/center&gt;&lt;\/td&gt; \n                &lt;td&gt;&lt;center&gt;&lt;input type=&quot;button&quot; id=&quot;restart&quot; value=&quot;RESET BOARD&quot;&gt;&lt;\/center&gt;&lt;\/td&gt; \n            &lt;\/tr&gt;      \n        &lt;\/table&gt;\n    &lt;\/div&gt;\n&lt;div class=&quot;container&quot;&gt;\n  &lt;div class = &quot;row&quot;&gt; \n    &lt;div class = &quot;column&quot;&gt; \n        &lt;div class=&quot;section&quot;&gt;\n            &lt;div class =&quot;video-container&quot;&gt;\n                &lt;h2&gt;Video Streaming&lt;\/h2&gt;   \n                &lt;center&gt;&lt;img id=&quot;ShowImage&quot; src=&quot;&quot; style=&quot;display:none&quot;&gt;&lt;\/center&gt;\n                &lt;center&gt;&lt;canvas id=&quot;canvas&quot; style=&quot;display:none&quot;&gt;&lt;\/canvas&gt;&lt;\/center&gt;\n            &lt;\/div&gt;\n        &lt;\/div&gt;\n        &lt;div class=&quot;section&quot;&gt;\n            &lt;table&gt;\n              &lt;tr&gt;\n                  &lt;td&gt;Quality&lt;\/td&gt;\n                  &lt;td&gt;&lt;input type=&quot;range&quot; id=&quot;quality&quot; min=&quot;10&quot; max=&quot;63&quot; value=&quot;10&quot;&gt;&lt;\/td&gt;\n              &lt;\/tr&gt;\n              &lt;tr&gt;\n                  &lt;td&gt;Brightness&lt;\/td&gt;\n                  &lt;td&gt;&lt;input type=&quot;range&quot; id=&quot;brightness&quot; min=&quot;-2&quot; max=&quot;2&quot; value=&quot;0&quot;&gt;&lt;\/td&gt;\n              &lt;\/tr&gt;\n              &lt;tr&gt;\n                  &lt;td&gt;Contrast&lt;\/td&gt;\n                  &lt;td&gt;&lt;input type=&quot;range&quot; id=&quot;contrast&quot; min=&quot;-2&quot; max=&quot;2&quot; value=&quot;0&quot;&gt;&lt;\/td&gt;\n              &lt;\/tr&gt;\n            &lt;\/table&gt;\n        &lt;\/div&gt;\n \n      &lt;!-----ANN:5----&gt;\n      &lt;div class=&quot;section&quot;&gt;\n        &lt;h2&gt;RGB Color Trackbars&lt;\/h2&gt;\n        &lt;table&gt;\n            &lt;tr&gt;\n                &lt;td&gt;R min:&amp;#160;&amp;#160;&amp;#160;&lt;span id=&quot;RMINdemo&quot;&gt;&lt;\/span&gt;&lt;\/td&gt;\n                &lt;td&gt;&lt;input type=&quot;range&quot; id=&quot;rmin&quot; min=&quot;0&quot; max=&quot;255&quot; value=&quot;0&quot; class = &quot;slider&quot;&gt;&lt;\/td&gt;\n                &lt;td&gt;R max:&amp;#160;&amp;#160;&amp;#160;&lt;span id=&quot;RMAXdemo&quot;&gt;&lt;\/span&gt;&lt;\/td&gt;\n                &lt;td&gt;&lt;input type=&quot;range&quot; id=&quot;rmax&quot; min=&quot;0&quot; max=&quot;255&quot; value=&quot;50&quot; class = &quot;slider&quot;&gt;&lt;\/td&gt;\n            &lt;\/tr&gt;\n            &lt;tr&gt;\n                &lt;td&gt;G min:&amp;#160;&amp;#160;&amp;#160;&lt;span id=&quot;GMINdemo&quot;&gt;&lt;\/span&gt;&lt;\/td&gt;\n                &lt;td&gt;&lt;input type=&quot;range&quot; id=&quot;gmin&quot; min=&quot;0&quot; max=&quot;255&quot; value=&quot;0&quot; class = &quot;slider&quot;&gt;&lt;\/td&gt;\n                &lt;td&gt;G max:&amp;#160;&amp;#160;&amp;#160;&lt;span id=&quot;GMAXdemo&quot;&gt;&lt;\/span&gt;&lt;\/td&gt;\n                &lt;td&gt;&lt;input type=&quot;range&quot; id=&quot;gmax&quot; min=&quot;0&quot; max=&quot;255&quot; value=&quot;50&quot; class = &quot;slider&quot;&gt;&lt;\/td&gt;\n            &lt;\/tr&gt;\n            &lt;tr&gt;\n                &lt;td&gt;B min:&amp;#160;&amp;#160;&amp;#160;&lt;span id =&quot;BMINdemo&quot;&gt;&lt;\/span&gt;&lt;\/td&gt;\n                &lt;td&gt;&lt;input type=&quot;range&quot; id=&quot;bmin&quot; min=&quot;0&quot; max=&quot;255&quot; value=&quot;0&quot; class = &quot;slider&quot;&gt;  &lt;\/td&gt;\n                &lt;td&gt;B max:&amp;#160;&amp;#160;&amp;#160;&lt;span id=&quot;BMAXdemo&quot;&gt;&lt;\/span&gt;&lt;\/td&gt;\n                &lt;td&gt; &lt;input type=&quot;range&quot; id=&quot;bmax&quot; min=&quot;0&quot; max=&quot;255&quot; value=&quot;50&quot; class = &quot;slider&quot;&gt;   &lt;\/td&gt;\n            &lt;\/tr&gt;\n        &lt;\/table&gt;\n      &lt;\/div&gt;\n\n      &lt;div class=&quot;section&quot;&gt;\n        &lt;h2&gt;Threshold Minimum-Binary Image&lt;\/h2&gt;\n        &lt;table&gt;\n            &lt;tr&gt;\n                &lt;td&gt;Minimum Threshold:&amp;#160;&amp;#160;&amp;#160;&lt;span id=&quot;THRESH_MINdemo&quot;&gt;&lt;\/span&gt;&lt;\/td&gt;\n                &lt;td&gt;&lt;input type=&quot;range&quot; id=&quot;thresh_min&quot; min=&quot;0&quot; max=&quot;255&quot; value=&quot;120&quot; class = &quot;slider&quot;&gt;  &lt;\/td&gt;\n            &lt;\/tr&gt;\n        &lt;\/table&gt;\n    &lt;\/div&gt;\n     &lt;!----ANN:9---&gt; \n     &lt;div class=&quot;section&quot;&gt;\n        &lt;h2&gt;Color Probe&lt;\/h2&gt;\n        &lt;table&gt;\n            &lt;tr&gt;\n                &lt;td&gt;X probe:&amp;#160;&amp;#160;&amp;#160;&lt;span id=&quot;X_PROBEdemo&quot;&gt;&lt;\/span&gt;&lt;\/td&gt;\n                &lt;td&gt;&lt;input type=&quot;range&quot; id=&quot;x_probe&quot; min=&quot;0&quot; max=&quot;400&quot; value=&quot;200&quot; class = &quot;slider&quot;&gt;&lt;\/td&gt;\n                &lt;td&gt;Y probe:&amp;#160;&amp;#160;&amp;#160;&lt;span id=&quot;Y_PROBEdemo&quot;&gt;&lt;\/span&gt;&lt;\/td&gt;\n                &lt;td&gt; &lt;input type=&quot;range&quot; id=&quot;y_probe&quot; min=&quot;0&quot; max=&quot;296&quot; value=&quot;148&quot; class = &quot;slider&quot;&gt;&lt;\/td&gt;\n            &lt;\/tr&gt;\n        &lt;\/table&gt;\n      &lt;\/div&gt;\n            \n    &lt;\/div&gt;   &lt;!------endfirstcolumn----------------&gt;   \n    \n    &lt;div class = &quot;column&quot;&gt;      \n        &lt;div class=&quot;section&quot;&gt;\n            &lt;h2&gt;Image Mask&lt;\/h2&gt;\n            &lt;canvas id=&quot;imageMask&quot;&gt;&lt;\/canvas&gt;\n        &lt;\/div&gt;\n        &lt;div class=&quot;section&quot;&gt;\n            &lt;h2&gt;Image Canvas&lt;\/h2&gt;\n            &lt;canvas id=&quot;imageCanvas&quot;&gt;&lt;\/canvas&gt;\n        &lt;\/div&gt;\n        &lt;div class=&quot;section&quot;&gt;\n            &lt;table&gt;\n                &lt;tr&gt;\n                    &lt;td&gt;&lt;button type=&quot;button&quot; id=&quot;invertButton&quot; class=&quot;btn btn-primary&quot;&gt;INVERT&lt;\/button&gt;&lt;\/td&gt;\n                    &lt;td&gt;&lt;button type=&quot;button&quot; id=&quot;contourButton&quot; class=&quot;btn btn-primary&quot;&gt;SHOW CONTOUR&lt;\/button&gt;&lt;\/td&gt;\n                    &lt;td&gt;&lt;button type=&quot;button&quot; id=&quot;trackButton&quot; class=&quot;btn btn-primary&quot;&gt;TRACKING&lt;\/button&gt;&lt;\/td&gt;\n                &lt;\/tr&gt;\n                &lt;tr&gt;\n                    &lt;td&gt;Invert: &lt;span id=&quot;INVERTdemo&quot;&gt;&lt;\/span&gt;&lt;\/td&gt;\n                    &lt;td&gt;Contour: &lt;span id=&quot;CONTOURdemo&quot;&gt;&lt;\/span&gt;&lt;\/td&gt;\n                    &lt;td&gt;Track: &lt;span id=&quot;TRACKdemo&quot;&gt;&lt;\/span&gt;\n                    &lt;\/td&gt;\n                &lt;\/tr&gt;\n            &lt;\/table&gt;\n        &lt;\/div&gt;\n        &lt;div class=&quot;section&quot;&gt;\n            &lt;table&gt;\n                &lt;tr&gt;\n                    &lt;td&gt;&lt;strong&gt;XCM:&lt;\/strong&gt; &lt;span id=&quot;XCMdemo&quot;&gt;&lt;\/span&gt;&lt;\/td&gt;\n                    &lt;td&gt;&lt;strong&gt;YCM:&lt;\/strong&gt; &lt;span id=&quot;YCMdemo&quot;&gt;&lt;\/span&gt;&lt;\/td&gt;\n                &lt;\/tr&gt;\n            &lt;\/table&gt;\n        &lt;\/div&gt;\n        \n        &lt;div class=&quot;section&quot;&gt;\n            &lt;canvas id=&quot;textCanvas&quot; width=&quot;480&quot; height=&quot;180&quot; style= &quot;border: 1px solid #black;&quot;&gt;&lt;\/canvas&gt;\n            &lt;iframe id=&quot;ifr&quot; style=&quot;display:none&quot;&gt;&lt;\/iframe&gt;\n            &lt;div id=&quot;message&quot;&gt;&lt;\/div&gt;  \n        &lt;\/div&gt;             \n        &lt;\/div&gt;  &lt;!------end2ndcolumn------------------------&gt;\n  &lt;\/div&gt;   &lt;!-----endrow----------------------&gt;   \n&lt;\/div&gt;   &lt;!------endcontainer--------------&gt;\n &lt;!--------------- &lt;\/body&gt;-----------------&gt;\n &lt;!----------------&lt;\/html&gt;-----------------&gt;\n&lt;div class=&quot;modal&quot;&gt;&lt;\/div&gt;\n&lt;script&gt;\nvar colorDetect = document.getElementById('colorDetect');\nvar ShowImage = document.getElementById('ShowImage');\nvar canvas = document.getElementById(&quot;canvas&quot;);\nvar context = canvas.getContext(&quot;2d&quot;);\nvar imageMask = document.getElementById(&quot;imageMask&quot;);\nvar imageMaskContext = imageMask.getContext(&quot;2d&quot;); \nvar imageCanvas = document.getElementById(&quot;imageCanvas&quot;);\nvar imageContext = imageCanvas.getContext(&quot;2d&quot;); \nvar txtcanvas = document.getElementById(&quot;textCanvas&quot;);\nvar ctx = txtcanvas.getContext(&quot;2d&quot;);  \nvar message = document.getElementById('message');\nvar ifr = document.getElementById('ifr');\nvar myTimer;\nvar restartCount=0;\nconst modelPath = 'https:\/\/ruisantosdotme.github.io\/face-api.js\/weights\/';\nlet currentStream;\nlet displaySize = { width:400, height: 296 }\nlet faceDetection;\n\nlet b_tracker = false;\nlet x_cm = 0;\nlet y_cm = 0;\n\nlet b_invert = false;\n\nlet b_contour = false;\n\nvar RMAX=50;\nvar RMIN=0;\nvar GMAX=50;\nvar GMIN=0;\nvar BMAX=50;\nvar BMIN=0;\nvar THRESH_MIN=120;\nvar X_PROBE=200;\nvar Y_PROBE=196;\nvar R=0;\nvar G=0;\nvar B=0;\nvar A=0;\n\n\ncolorDetect.onclick = function (event) {\n  clearInterval(myTimer);  \n  myTimer = setInterval(function(){error_handle();},5000);\n  ShowImage.src=location.origin+'\/?colorDetect='+Math.random();\n}\n\n\/\/ANN:READY\nvar Module = {\n  onRuntimeInitialized(){onOpenCvReady();}\n}\n\nfunction onOpenCvReady(){\n  \/\/alert(&quot;onOpenCvReady&quot;);\n  console.log(&quot;OpenCV IS READY!!!&quot;);\n  drawReadyText();  \n  document.body.classList.remove(&quot;loading&quot;);\n}\n\n    \nfunction error_handle() {\n  restartCount++;\n  clearInterval(myTimer);\n  if (restartCount&lt;=2) {\n    message.innerHTML = &quot;Get still error. &lt;br&gt;Restart ESP32-CAM &quot;+restartCount+&quot; times.&quot;;\n    myTimer = setInterval(function(){colorDetect.click();},10000);\n    ifr.src = document.location.origin+'?restart';\n  }\n  else\n    message.innerHTML = &quot;Get still error. &lt;br&gt;Please close the page and check ESP32-CAM.&quot;;\n}    \ncolorDetect.style.display = &quot;block&quot;;\nShowImage.onload = function (event) {\n  \/\/alert(&quot;SHOW IMAGE&quot;);\n  console.log(&quot;SHOW iMAGE&quot;);\n  clearInterval(myTimer);\n  restartCount=0;      \n  canvas.setAttribute(&quot;width&quot;, ShowImage.width);\n  canvas.setAttribute(&quot;height&quot;, ShowImage.height);\n  canvas.style.display = &quot;block&quot;;\n  imageCanvas.setAttribute(&quot;width&quot;, ShowImage.width);\n  imageCanvas.setAttribute(&quot;height&quot;, ShowImage.height);\n  imageCanvas.style.display = &quot;block&quot;;\n\n  imageMask.setAttribute(&quot;width&quot;, ShowImage.width);\n  imageMask.setAttribute(&quot;height&quot;, ShowImage.height);\n  imageMask.style.display = &quot;block&quot;;      \n      \n  context.drawImage(ShowImage,0,0,ShowImage.width,ShowImage.height);\n  \n  DetectImage();        \n}\nrestart.onclick = function (event) {\n  fetch(location.origin+'\/?restart=stop');\n}\nquality.onclick = function (event) {\n  fetch(document.location.origin+'\/?quality='+this.value+';stop');\n} \nbrightness.onclick = function (event) {\n  fetch(document.location.origin+'\/?brightness='+this.value+';stop');\n} \ncontrast.onclick = function (event) {\n  fetch(document.location.origin+'\/?contrast='+this.value+';stop');\n}                             \nasync function DetectImage() {\n  \/\/alert(&quot;DETECT IMAGE&quot;);\n  console.log(&quot;DETECT IMAGE&quot;);\n\n  \/***************opencv********************************\/\n  \/\/ANN:4\n  let src = cv.imread(ShowImage);\n  arows = src.rows;\n  acols = src.cols;\n  aarea = arows*acols;\n  adepth = src.depth();\n  atype = src.type();\n  achannels = src.channels();\n  console.log(&quot;rows = &quot; + arows);\n  console.log(&quot;cols = &quot; + acols);\n  console.log(&quot;pic area = &quot; + aarea);\n  console.log(&quot;depth = &quot; + adepth); \n  console.log(&quot;type = &quot; + atype); \n  console.log(&quot;channels = &quot; + achannels);\n  \n  \/******************COLOR DETECT******************************\/\n\n  \/\/ANN:6\n  var RMAXslider = document.getElementById(&quot;rmax&quot;);\n  var RMAXoutput = document.getElementById(&quot;RMAXdemo&quot;);\n  RMAXoutput.innerHTML = RMAXslider.value;\n  RMAXslider.oninput = function(){\n  RMAXoutput.innerHTML = this.value;\n  RMAX = parseInt(RMAXoutput.innerHTML,10);\n  console.log(&quot;RMAX=&quot; + RMAX);\n  }\n\n  console.log(&quot;RMAX=&quot; + RMAX);\n\n  var RMINslider = document.getElementById(&quot;rmin&quot;);\n  var RMINoutput = document.getElementById(&quot;RMINdemo&quot;);\n  RMINoutput.innerHTML = RMINslider.value;\n  RMINslider.oninput = function(){\n    RMINoutput.innerHTML = this.value;\n    RMIN = parseInt(RMINoutput.innerHTML,10);\n    console.log(&quot;RMIN=&quot; + RMIN);\n  }\n  console.log(&quot;RMIN=&quot; + RMIN);\n\n  var GMAXslider = document.getElementById(&quot;gmax&quot;);\n  var GMAXoutput = document.getElementById(&quot;GMAXdemo&quot;);\n  GMAXoutput.innerHTML = GMAXslider.value;\n  GMAXslider.oninput = function(){\n    GMAXoutput.innerHTML = this.value;\n    GMAX = parseInt(GMAXoutput.innerHTML,10);\n  }\n  console.log(&quot;GMAX=&quot; + GMAX);\n\n  var GMINslider = document.getElementById(&quot;gmin&quot;);\n  var GMINoutput = document.getElementById(&quot;GMINdemo&quot;);\n  GMINoutput.innerHTML = GMINslider.value;\n  GMINslider.oninput = function(){\n    GMINoutput.innerHTML = this.value;\n    GMIN = parseInt(GMINoutput.innerHTML,10);\n  }\n  console.log(&quot;GMIN=&quot; + GMIN);\n\n  var BMAXslider = document.getElementById(&quot;bmax&quot;);\n  var BMAXoutput = document.getElementById(&quot;BMAXdemo&quot;);\n  BMAXoutput.innerHTML = BMAXslider.value;\n  BMAXslider.oninput = function(){\n    BMAXoutput.innerHTML = this.value;\n    BMAX = parseInt(BMAXoutput.innerHTML,10);\n  }\n  console.log(&quot;BMAX=&quot; + BMAX);\n\n  var BMINslider = document.getElementById(&quot;bmin&quot;);\n  var BMINoutput = document.getElementById(&quot;BMINdemo&quot;);\n  BMINoutput.innerHTML = BMINslider.value;\n  BMINslider.oninput = function(){\n  BMINoutput.innerHTML = this.value;\n  BMIN = parseInt(BMINoutput.innerHTML,10);\n  }\n  console.log(&quot;BMIN=&quot; + BMIN);\n\n\n\n  var THRESH_MINslider = document.getElementById(&quot;thresh_min&quot;);\n  var THRESH_MINoutput = document.getElementById(&quot;THRESH_MINdemo&quot;);\n  THRESH_MINoutput.innerHTML = THRESH_MINslider.value;\n  THRESH_MINslider.oninput = function(){\n  THRESH_MINoutput.innerHTML = this.value;\n  THRESH_MIN = parseInt(THRESH_MINoutput.innerHTML,10);\n  }\n  console.log(&quot;THRESHOLD MIN=&quot; + THRESH_MIN);\n\n  \/\/ANN:9A\n  var X_PROBEslider = document.getElementById(&quot;x_probe&quot;);\n  var X_PROBEoutput = document.getElementById(&quot;X_PROBEdemo&quot;);\n  X_PROBEoutput.innerHTML = X_PROBEslider.value;\n  X_PROBEslider.oninput = function(){\n  X_PROBEoutput.innerHTML = this.value;\n  X_PROBE = parseInt(X_PROBEoutput.innerHTML,10);\n  }\n  console.log(&quot;X_PROBE=&quot; + X_PROBE); \n\n  var Y_PROBEslider = document.getElementById(&quot;y_probe&quot;);\n  var Y_PROBEoutput = document.getElementById(&quot;Y_PROBEdemo&quot;);\n  Y_PROBEoutput.innerHTML = Y_PROBEslider.value;\n  Y_PROBEslider.oninput = function(){\n  Y_PROBEoutput.innerHTML = this.value;\n  Y_PROBE = parseInt(Y_PROBEoutput.innerHTML,10);\n  }\n  console.log(&quot;Y_PROBE=&quot; + Y_PROBE); \n\n\n  document.getElementById('trackButton').onclick = function(){\n    b_tracker = (true &amp;&amp; !b_tracker)  \n    console.log(&quot;TRACKER = &quot; + b_tracker );\n    var TRACKoutput = document.getElementById(&quot;TRACKdemo&quot;);\n    TRACKoutput.innerHTML = b_tracker;\n    \/\/var XCMoutput = document.getElementById(&quot;XCMdemo&quot;);\n    \/\/XCMoutput.innerHTML = x_cm;\n \n  }  \n\n  document.getElementById('invertButton').onclick = function(){\n    b_invert = (true &amp;&amp; !b_invert)  \n    console.log(&quot;TRACKER = &quot; + b_invert );\n    var INVERToutput = document.getElementById(&quot;INVERTdemo&quot;);\n    INVERToutput.innerHTML = b_invert;\n  }  \n\/**\/\n  document.getElementById('contourButton').onclick = function(){\n    b_contour = (true &amp;&amp; !b_contour)  \n    console.log(&quot;TRACKER = &quot; + b_contour );\n    var CONTOURoutput = document.getElementById(&quot;CONTOURdemo&quot;);\n    CONTOURoutput.innerHTML = b_contour;\n  } \n\/**\/ \n\n  let tracker = 0;\n  \n  var TRACKoutput = document.getElementById(&quot;TRACKdemo&quot;);\n  TRACKoutput.innerHTML = b_tracker;\n  var XCMoutput = document.getElementById(&quot;XCMdemo&quot;);\n  var YCMoutput = document.getElementById(&quot;YCMdemo&quot;);\n\n  XCMoutput.innerHTML = 0;\n  YCMoutput.innerHTML = 0; \n\n  var INVERToutput = document.getElementById(&quot;INVERTdemo&quot;);\n  INVERToutput.innerHTML = b_invert;  \n\n  var CONTOURoutput = document.getElementById(&quot;CONTOURdemo&quot;);\n  CONTOURoutput.innerHTML = b_contour;   \n\n  \/\/ANN:8\n  let M00Array = [0,];\n  let orig = new cv.Mat();\n  let mask = new cv.Mat();\n  let mask1 = new cv.Mat();\n  let mask2 = new cv.Mat();\n  let contours = new cv.MatVector();\n  let hierarchy = new cv.Mat();\n  let rgbaPlanes = new cv.MatVector();\n    \n  let color = new cv.Scalar(0,0,0);\n\n  clear_canvas();\n\n\n    \n  orig = cv.imread(ShowImage);\n  cv.split(orig,rgbaPlanes);  \/\/SPLIT\n  let BP = rgbaPlanes.get(2);  \/\/ SELECTED COLOR PLANE\n  let GP = rgbaPlanes.get(1);\n  let RP = rgbaPlanes.get(0);\n  cv.merge(rgbaPlanes,orig);\n   \n    \n              \/\/   BLK    BLU   GRN   RED\n  let row = Y_PROBE \/\/180\/\/275 \/\/225 \/\/150 \/\/130    \n  let col = X_PROBE \/\/100\/\/10 \/\/100 \/\/200 \/\/300\n  drawColRowText(acols,arows);\n\n\n  console.log(&quot;ISCONTINUOUS = &quot; + orig.isContinuous());\n\n  \/\/ANN:9C\n  R = src.data[row * src.cols * src.channels() + col * src.channels()];\n  G = src.data[row * src.cols * src.channels() + col * src.channels() + 1];\n  B = src.data[row * src.cols * src.channels() + col * src.channels() + 2];\n  A = src.data[row * src.cols * src.channels() + col * src.channels() + 3];\n  console.log(&quot;RDATA = &quot; + R);\n  console.log(&quot;GDATA = &quot; + G);\n  console.log(&quot;BDATA = &quot; + B);\n  console.log(&quot;ADATA = &quot; + A);\n\n  drawRGB_PROBE_Text();\n  \n   \n    \n  \/\/ANN:9b\n  \/\/*************draw probe point*********************\n  let point4 = new cv.Point(col,row);\n  cv.circle(src,point4,5,[255,255,255,255],2,cv.LINE_AA,0);\n  \/\/***********end draw probe point*********************\n\n  \/\/ANN:7\n  let high = new cv.Mat(src.rows,src.cols,src.type(),[RMAX,GMAX,BMAX,255]);\n  let low = new cv.Mat(src.rows,src.cols,src.type(),[RMIN,GMIN,BMIN,0]);\n\n  cv.inRange(src,low,high,mask1);\n  \/\/inRange(source image, lower limit, higher limit, destination image)\n    \n  cv.threshold(mask1,mask,THRESH_MIN,255,cv.THRESH_BINARY);\n  \/\/threshold(source image,destination image,threshold,255,threshold method);\n\n  \/\/ANN:9\n  if(b_invert==true){\n     cv.bitwise_not(mask,mask2);\n  }\n\/********************start contours******************************************\/\n  \/\/ANN:10\n  if(b_tracker == true){\n  try{\n   if(b_invert==false){\n    \/\/ANN:11   \n    cv.findContours(mask,contours,hierarchy,cv.RETR_CCOMP,cv.CHAIN_APPROX_SIMPLE);\n    \/\/findContours(source image, array of contours found, hierarchy of contours\n        \/\/ if contours are inside other contours, method of contour data retrieval,\n        \/\/algorithm method)\n   }\n   else{\n    cv.findContours(mask2,contours,hierarchy,cv.RETR_CCOMP,cv.CHAIN_APPROX_SIMPLE);\n   }\n    console.log(&quot;CONTOUR_SIZE = &quot; + contours.size());\n\n    \/\/draw contours\n    if(b_contour==true){\n     for(let i = 0; i &lt; contours.size(); i++){\n        cv.drawContours(src,contours,i,[0,0,0,255],2,cv.LINE_8,hierarchy,100)\n     }\n    }\n\n    \/\/ANN:12\n    let cnt;\n    let Moments;\n    let M00;\n    let M10;\n    \/\/let x_cm;\n    \/\/let y_cm;\n    \n    \/\/ANN:13\n    for(let k = 0; k &lt; contours.size(); k++){\n        cnt = contours.get(k); \n        Moments = cv.moments(cnt,false);\n        M00Array[k] = Moments.m00;\n       \/\/ cnt.delete();\n    }\n\n    \/\/ANN13A\n    let max_area_arg = MaxAreaArg(M00Array);\n    console.log(&quot;MAXAREAARG = &quot;+max_area_arg);\n\n    \/\/let TestArray = [0,0,0,15,4,15,2];\n    \/\/let TestArray0 = [];\n    \/\/let max_test_area_arg = MaxAreaArg(TestArray0);\n    \/\/console.log(&quot;MAXTESTAREAARG = &quot;+max_test_area_arg);\n\n\n\n    let ArgMaxArea = MaxAreaArg(M00Array);\n    if(ArgMaxArea &gt;= 0){\n    cnt = contours.get(MaxAreaArg(M00Array));  \/\/use the contour with biggest MOO\n    \/\/cnt = contours.get(54);\n    Moments = cv.moments(cnt,false);\n    M00 = Moments.m00;\n    M10 = Moments.m10;\n    M01 = Moments.m01;\n    x_cm = M10\/M00;    \/\/ 75 for circle_9.jpg\n    y_cm = M01\/M00;    \/\/ 41 for circle_9.jpg\n\n    XCMoutput.innerHTML = Math.round(x_cm);\n    YCMoutput.innerHTML = Math.round(y_cm);\n\n    console.log(&quot;M00 = &quot;+M00);  \n    console.log(&quot;XCM = &quot;+Math.round(x_cm));\n    console.log(&quot;YCM = &quot;+Math.round(y_cm)); \n\n    \/\/fetch(document.location.origin+'\/?xcm='+Math.round(x_cm)+';stop');\n    fetch(document.location.origin+'\/?cm='+Math.round(x_cm)+';'+Math.round(y_cm)+';stop');\n\n    console.log(&quot;M00ARRAY = &quot; + M00Array);\n\n    \/\/ANN:14   \n    \n    \/\/**************min area bounding rect********************\n    \/\/let rotatedRect=cv.minAreaRect(cnt);\n    \/\/let vertices = cv.RotatedRect.points(rotatedRect);\n\n    \/\/for(let j=0;j&lt;4;j++){\n    \/\/    cv.line(src,vertices[j],\n    \/\/        vertices[(j+1)%4],[0,0,255,255],2,cv.LINE_AA,0);\n    \/\/}\n    \/\/***************end min area bounding rect*************************************\n\n\n    \/\/***************bounding rect***************************\n    let rect = cv.boundingRect(cnt);\n    let point1 = new cv.Point(rect.x,rect.y);\n    let point2 = new cv.Point(rect.x+rect.width,rect.y+rect.height);\n\n    cv.rectangle(src,point1,point2,[0,0,255,255],2,cv.LINE_AA,0);\n    \/\/*************end bounding rect***************************\n\n\n    \/\/*************draw center point*********************\n    let point3 = new cv.Point(x_cm,y_cm);\n    cv.circle(src,point3,2,[0,0,255,255],2,cv.LINE_AA,0);\n    \/\/***********end draw center point*********************\n\n    }\/\/end if(ArgMaxArea &gt;= 0)\n    else{\n      if(ArgMaxArea==-1){ \n        console.log(&quot;ZERO ARRAY LENGTH&quot;);\n      }\n      else{              \/\/ArgMaxArea=-2\n        console.log(&quot;DUPLICATE MAX ARRAY-ELEMENT&quot;);\n      }\n    }\n\n\n\n\n    cnt.delete();\n\/******************end contours  note cnt line one up*******************************************\/\n   drawXCM_YCM_Text();\n\n  }\/\/end try\n  catch{\n    console.log(&quot;ERROR TRACKER NO CONTOUR&quot;);\n    clear_canvas();\n    drawErrorTracking_Text();\n  }\n    \n  }\/\/end b_tracking if statement\n  else{\n      XCMoutput.innerHTML = 0;\n      YCMoutput.innerHTML = 0;\n  }    \n\n  if(b_invert==false){\n     cv.imshow('imageMask', mask);\n  }\n  else{\n     cv.imshow('imageMask', mask2);\n  }\n  \/\/cv.imshow('imageMask', R);\n  cv.imshow('imageCanvas', src);\n\n  \/\/ANN:8A\n  src.delete();\n  high.delete();\n  low.delete();\n  orig.delete();\n  mask1.delete();\n  mask2.delete();\n  mask.delete();\n  contours.delete();\n  hierarchy.delete();\n  \/\/cnt.delete();\n  RP.delete();\n    \n  \n\n\n \/********************END COLOR DETECT****************************\/\n  \n\/***************end opencv******************************\/\n      \n\n setTimeout(function(){colorDetect.click();},500);\n  \n}\/\/end detectimage \n\n\nfunction MaxAreaArg(arr){\n    if (arr.length == 0) {\n        return -1;\n    }\n\n    var max = arr[0];\n    var maxIndex = 0;\n    var dupIndexCount = 0; \/\/duplicate max elements?\n\n    if(arr[0] &gt;= .90*aarea){\n        max = 0;\n    }\n\n    for (var i = 1; i &lt; arr.length; i++) {\n        if (arr[i] &gt; max &amp;&amp; arr[i] &lt; .99*aarea) {\n            maxIndex = i;\n            max = arr[i];\n            dupIndexCount = 0;\n        }\n        else if(arr[i]==max &amp;&amp; arr[i]!=0){\n            dupIndexCount++;\n        }\n    }\n\n    if(dupIndexCount==0){\n        return maxIndex;\n    }\n\n    else{\n        return -2;\n    }        \n}\/\/end MaxAreaArg    \n\n\n\nfunction clear_canvas(){\n    ctx.clearRect(0,0,txtcanvas.width,txtcanvas.height);\n    ctx.rect(0,0,txtcanvas.width,txtcanvas.height);\n    ctx.fillStyle=&quot;red&quot;;\n    ctx.fill();\n}\n\nfunction drawReadyText(){\n    ctx.fillStyle = 'black';\n    ctx.font = '20px serif';\n    ctx.fillText('OpenCV.JS READY',txtcanvas.width\/4,txtcanvas.height\/10);\n}          \n\nfunction drawColRowText(x,y){\n    ctx.fillStyle = 'black';\n    ctx.font = '20px serif';\n    ctx.fillText('ImageCols='+x,0,txtcanvas.height\/10);\n    ctx.fillText('ImageRows='+y,txtcanvas.width\/2,txtcanvas.height\/10);\n} \n\nfunction drawRGB_PROBE_Text(){\n    ctx.fillStyle = 'black';\n    ctx.font = '20px serif';\n    ctx.fillText('Rp='+R,0,2*txtcanvas.height\/10);\n    ctx.fillText('Gp='+G,txtcanvas.width\/4,2*txtcanvas.height\/10);\n    ctx.fillText('Bp='+B,txtcanvas.width\/2,2*txtcanvas.height\/10);\n    ctx.fillText('Ap='+A,3*txtcanvas.width\/4,2*txtcanvas.height\/10);\n}\n\nfunction drawXCM_YCM_Text(){\n    ctx.fillStyle = 'black';\n    ctx.font = '20px serif';\n    ctx.fillText('XCM='+Math.round(x_cm),0,3*txtcanvas.height\/10); \n    ctx.fillText('YCM='+Math.round(y_cm),txtcanvas.width\/4,3*txtcanvas.height\/10);    \n}\n\nfunction drawErrorTracking_Text(){\n    ctx.fillStyle = 'black';\n    ctx.font = '20px serif';\n    ctx.fillText('ERROR TRACKING-NO CONTOUR',0,3*txtcanvas.height\/10);\n}          \n         \n  &lt;\/script&gt; \n&lt;\/body&gt;\n&lt;\/html&gt;  \n)rawliteral&quot;;\n<\/code><\/pre>\n\t<p style=\"text-align:center\"><a class=\"rntwhite\" href=\"https:\/\/github.com\/RuiSantosdotme\/ESP32-CAM-Arduino-IDE\/raw\/master\/ESP32-CAM-OpenCV-js\/index_OCV_ColorTrack.h\" target=\"_blank\">View raw code<\/a><\/p>\n\n\n\n<p>Save the file.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Network Credentials<\/h3>\n\n\n\n<p>For the program to work properly, you need to insert your network credentials in the following variables in the <span class=\"rnthl rntliteral\">OCV_ColorTrack_P.ino<\/span> file:<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>const char* ssid = \"REPLACE_WITH_YOUR_SSID\";\nconst char* password = \"REPLACE_WITH_YOUR_PASSWORD\";<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Camera Pin Assignment<\/h3>\n\n\n\n<p>By default, the code uses the pin assignment for the ESP32-CAM AI-Thinker module.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>#define PWDN_GPIO_NUM     32\n#define RESET_GPIO_NUM    -1\n#define XCLK_GPIO_NUM      0\n#define SIOD_GPIO_NUM     26\n#define SIOC_GPIO_NUM     27\n#define Y9_GPIO_NUM       35\n#define Y8_GPIO_NUM       34\n#define Y7_GPIO_NUM       39\n#define Y6_GPIO_NUM       36\n#define Y5_GPIO_NUM       21\n#define Y4_GPIO_NUM       19\n#define Y3_GPIO_NUM       18\n#define Y2_GPIO_NUM        5\n#define VSYNC_GPIO_NUM    25\n#define HREF_GPIO_NUM     23\n#define PCLK_GPIO_NUM     22<\/code><\/pre>\n\n\n\n<p class=\"rntbox rntcred\">If you&#8217;re using a different camera board, don&#8217;t forget to insert the right pin assignment, you can go to the following article to find the pinout for your board:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/randomnerdtutorials.com\/esp32-cam-camera-pin-gpios\/\">ESP32-CAM Camera Boards: Pin and GPIOs Assignment Guide<\/a><\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">How the Code Works<\/h2>\n\n\n\n<p>Continue reading to learn how the code works, or skip to the next section.<\/p>\n\n\n\n<p>In order to facilitate reading and understanding the program, <strong><em>ANNOTATIONS<\/em><\/strong> have been added as comments in the program.<\/p>\n\n\n\n<p>For example, wiring for ESP32-CAM is listed below the <strong><em>ANN<\/em>:0<\/strong> annotation, located in the <span class=\"rnthl rntliteral\">.ino<\/span> file. <strong><em>ANN<\/em>:0<\/strong> is found with the Edit\/Find command of the Arduino IDE.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>#define PWDN_GPIO_NUM     32\n#define RESET_GPIO_NUM    -1\n#define XCLK_GPIO_NUM      0\n#define SIOD_GPIO_NUM     26\n#define SIOC_GPIO_NUM     27\n#define Y9_GPIO_NUM       35\n#define Y8_GPIO_NUM       34\n#define Y7_GPIO_NUM       39\n#define Y6_GPIO_NUM       36\n#define Y5_GPIO_NUM       21\n#define Y4_GPIO_NUM       19\n#define Y3_GPIO_NUM       18\n#define Y2_GPIO_NUM        5\n#define VSYNC_GPIO_NUM    25\n#define HREF_GPIO_NUM     23\n#define PCLK_GPIO_NUM     22<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Server Sketch<\/h3>\n\n\n\n<p>The server program, <span class=\"rnthl rntliteral\">OCV_ColorTrack.ino<\/span>, is taken from <a href=\"https:\/\/randomnerdtutorials.com\/esp32-cam-projects-ebook\/\">ESP32-CAM Projects<\/a>, Module 5 by Rui Santos and Sara Santos. It has a standard ESP32 Camera <span class=\"rnthl rntliteral\">setup()<\/span> which configures the Camera and server IP address and password.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Annotation 1 (ANN:1)<\/h4>\n\n\n\n<p>However, what is not standard, in this server program are instructions of vital importance, which allow Access-Control. See code at <strong><em>ANN<\/em>:1<\/strong>. <\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>\/\/ANN:1\nclient.println(\"HTTP\/1.1 200 OK\");\nclient.println(\"Access-Control-Allow-Origin: *\");              \nclient.println(\"Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept\");\nclient.println(\"Access-Control-Allow-Methods: GET,POST,PUT,DELETE,OPTIONS\");\nclient.println(\"Content-Type: image\/jpeg\");\nclient.println(\"Content-Disposition: form-data; name=\\\"imageFile\\\"; filename=\\\"picture.jpg\\\"\"); \nclient.println(\"Content-Length: \" + String(fb-&gt;len));             \nclient.println(\"Connection: close\");\nclient.println();<\/code><\/pre>\n\n\n\n<p>This instructs the browser to allow the Camera image and OpenCV.js, which have different origins to work together in the program. Without these instructions, the Chrome browser throws errors.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Annotation 2 (ANN:2)<\/h4>\n\n\n\n<p>The server <span class=\"rnthl rntliteral\">loop()<\/span> monitors client messages and decodes them via an <span class=\"rnthl rntliteral\">ExecuteCommand()<\/span> found at <strong><em>ANN<\/em><\/strong>:<strong>2<\/strong>. <\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>\/\/ANN:2\nvoid ExecuteCommand() {\n  if (cmd!=\"colorDetect\") {  \/\/Omit printout\n    \/\/Serial.println(\"cmd= \"+cmd+\" ,P1= \"+P1+\" ,P2= \"+P2+\" ,P3= \"+P3+\" ,P4= \"+P4+\" ,P5= \"+P5+\" ,P6= \"+P6+\" ,P7= \"+P7+\" ,P8= \"+P8+\" ,P9= \"+P9);\n    \/\/Serial.println(\"\");\n  }\n  \n  if (cmd==\"resetwifi\") {\n    WiFi.begin(P1.c_str(), P2.c_str());\n    Serial.print(\"Connecting to \");\n    Serial.println(P1);\n    long int StartTime=millis();\n    while (WiFi.status() != WL_CONNECTED) \n    {\n        delay(500);\n        if ((StartTime+5000) &lt; millis()) break;\n    } \n    Serial.println(\"\");\n    Serial.println(\"STAIP: \"+WiFi.localIP().toString());\n    Feedback=\"STAIP: \"+WiFi.localIP().toString();\n  }    \n  else if (cmd==\"restart\") {\n    ESP.restart();\n  }\n  else if (cmd==\"cm\"){\n    int XcmVal = P1.toInt();\n    int YcmVal = P2.toInt();\n    Serial.println(\"cmd= \"+cmd+\" ,VALXCM= \"+XcmVal);\n    Serial.println(\"cmd= \"+cmd+\" ,VALYCM= \"+YcmVal);   \n  }\n  else if (cmd==\"quality\") { \n    sensor_t * s = esp_camera_sensor_get();\n    int val = P1.toInt(); \n    s-&gt;set_quality(s, val);\n  }\n  else if (cmd==\"contrast\") {\n    sensor_t * s = esp_camera_sensor_get();\n    int val = P1.toInt(); \n    s-&gt;set_contrast(s, val);\n  }\n  else if (cmd==\"brightness\") {\n    sensor_t * s = esp_camera_sensor_get();\n    int val = P1.toInt();  \n    s-&gt;set_brightness(s, val);  \n  }   \n  else {\n    Feedback=\"Command is not defined.\";\n  }\n  if (Feedback==\"\") {\n    Feedback=Command;\n  }\n}<\/code><\/pre>\n\n\n\n<p>The original program uses this function to receive and execute sliders in the client which controls image characteristics and which are transmitted by the client via a \u201cfetch\u201d instruction. <\/p>\n\n\n\n<p>In our current program, this feature, to be described, is used to communicate the \u201ccenter-of-mass\u201d of a color target detected by the client to the ESP32 server; a feature vital to a robotics application.<\/p>\n\n\n\n<p>Other than the change of extracting the x and y center-of-mass and printing it, there are no other changes to the server program. <\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Client Sketch (OpenCV.js)<\/h3>\n\n\n\n<p>Other that the image-characteristics-sliders-routines and their data transmission to the server via \u201cfetch\u201d and the error routine in the original client program eBook refenced above, the client program here is new and contains code devoted to the application of OpenCV.js to the ESP32 Camera image transmitted to the browser (as mentioned previously, a \u201cfetch\u201d is used to transmit color target data to the server).<\/p>\n\n\n\n<p>The client code is sprinkled liberally with <span class=\"rnthl rntliteral\">console.log<\/span> instructions which allow the user to see the results of the code. Chrome <span class=\"rnthl rntliteral\">console.log<\/span> is accessed by pressing <strong>CTRL<\/strong>+<strong>SHIFT<\/strong>+<strong>J<\/strong> simultaneously.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Annotation 3 (ANN:3)<\/h4>\n\n\n\n<p><strong><em>ANN<\/em><\/strong>:<strong>3<\/strong> includes the latest version of OpenCV.js in our web page. <a href=\"https:\/\/docs.opencv.org\/3.4\/d0\/d84\/tutorial_js_usage.html\" target=\"_blank\" rel=\"noreferrer noopener\">Click here to learn more<\/a>.<\/p>\n\n\n\n<pre class=\"wp-block-code language-html\"><code>&lt;script async src=\" https:\/\/docs.opencv.org\/master\/opencv.js\" type=\"text\/javascript\"&gt;&lt;\/script&gt;<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">ANN:READY<\/h4>\n\n\n\n<p><strong><em>ANN<\/em><\/strong>:<strong>READY<\/strong> marks the module which signals that OpenCV.js has been initialized. Once initialization is complete, the <strong>Color Detection<\/strong> button can be clicked. While faster computers do not require this capability, it is included for the sake of completeness.<\/p>\n\n\n<div class=\"wp-block-image is-style-default\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"702\" height=\"249\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/12\/ESP32-CAM-Web-Server-OpenCVJS-Ready.jpg?resize=702%2C249&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"OpenCVJS ready ESP32-CAM Web Server\" class=\"wp-image-100801\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/12\/ESP32-CAM-Web-Server-OpenCVJS-Ready.jpg?w=702&amp;quality=100&amp;strip=all&amp;ssl=1 702w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/12\/ESP32-CAM-Web-Server-OpenCVJS-Ready.jpg?resize=300%2C106&amp;quality=100&amp;strip=all&amp;ssl=1 300w\" sizes=\"(max-width: 702px) 100vw, 702px\" \/><\/figure><\/div>\n\n\n<h4 class=\"wp-block-heading\">Annotation 4 (ANN:4)<\/h4>\n\n\n\n<p>The screenshot of the client program, running on Chrome shows two columns as created by the HTML section of the code. The left column shows the original image of the camera which is transmitted at approximately 1 fps. This image, with an ID of <span class=\"rnthl rntliteral\">ShowImage<\/span> is the source image of the OpenCV code routine in the program.<\/p>\n\n\n\n<p><strong><em>ANN<\/em><\/strong>:<strong>4<\/strong> marks the creation of the src and its characteristics; rows, cols, etc.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>\/\/ANN:4\nlet src = cv.imread(ShowImage);\narows = src.rows;\nacols = src.cols;\naarea = arows*acols;\nadepth = src.depth();\natype = src.type();\nachannels = src.channels();\nconsole.log(\"rows = \" + arows);\nconsole.log(\"cols = \" + acols);\nconsole.log(\"pic area = \" + aarea);\nconsole.log(\"depth = \" + adepth); \nconsole.log(\"type = \" + atype); \nconsole.log(\"channels = \" + achannels);<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">RGB Color Trackbars<\/h4>\n\n\n\n<p>Below the source image are the original three image-characteristics sliders (Quality, Brightness and Contrast), there are <strong>RGB Color Trackbars<\/strong>. <\/p>\n\n\n<div class=\"wp-block-image is-style-default\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"538\" height=\"730\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/12\/ESP32-CAM-Color-Tracking-RGB-Trackbars.jpg?resize=538%2C730&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"ESP32-CAM Web Server Color Tracking RGB Trackbars OpenCVJS\" class=\"wp-image-100802\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/12\/ESP32-CAM-Color-Tracking-RGB-Trackbars.jpg?w=538&amp;quality=100&amp;strip=all&amp;ssl=1 538w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/12\/ESP32-CAM-Color-Tracking-RGB-Trackbars.jpg?resize=221%2C300&amp;quality=100&amp;strip=all&amp;ssl=1 221w\" sizes=\"(max-width: 538px) 100vw, 538px\" \/><\/figure><\/div>\n\n\n<p>These are used to set limits to the color range of colors allowed in the \u201cprocessed\u201d image in the CV application. Code for the trackbars are found at <strong><em>ANN<\/em><\/strong>:<strong>5<\/strong>, <strong><em>ANN<\/em><\/strong>:<strong>6<\/strong>. <\/p>\n\n\n\n<pre class=\"wp-block-code language-html\"><code>&lt;!-----ANN:5----&gt;\n&lt;div class=\"section\"&gt;\n&lt;h2&gt;RGB Color Trackbars&lt;\/h2&gt;\n&lt;table&gt;\n  &lt;tr&gt;\n    &lt;td&gt;R min:&amp;#160;&amp;#160;&amp;#160;&lt;span id=\"RMINdemo\"&gt;&lt;\/span&gt;&lt;\/td&gt;\n    &lt;td&gt;&lt;input type=\"range\" id=\"rmin\" min=\"0\" max=\"255\" value=\"0\" class = \"slider\"&gt;&lt;\/td&gt;\n    &lt;td&gt;R max:&amp;#160;&amp;#160;&amp;#160;&lt;span id=\"RMAXdemo\"&gt;&lt;\/span&gt;&lt;\/td&gt;\n    &lt;td&gt;&lt;input type=\"range\" id=\"rmax\" min=\"0\" max=\"255\" value=\"50\" class = \"slider\"&gt;&lt;\/td&gt;\n  &lt;\/tr&gt;\n  &lt;tr&gt;\n    &lt;td&gt;G min:&amp;#160;&amp;#160;&amp;#160;&lt;span id=\"GMINdemo\"&gt;&lt;\/span&gt;&lt;\/td&gt;\n    &lt;td&gt;&lt;input type=\"range\" id=\"gmin\" min=\"0\" max=\"255\" value=\"0\" class = \"slider\"&gt;&lt;\/td&gt;\n    &lt;td&gt;G max:&amp;#160;&amp;#160;&amp;#160;&lt;span id=\"GMAXdemo\"&gt;&lt;\/span&gt;&lt;\/td&gt;\n    &lt;td&gt;&lt;input type=\"range\" id=\"gmax\" min=\"0\" max=\"255\" value=\"50\" class = \"slider\"&gt;&lt;\/td&gt;\n  &lt;\/tr&gt;\n  &lt;tr&gt;\n    &lt;td&gt;B min:&amp;#160;&amp;#160;&amp;#160;&lt;span id =\"BMINdemo\"&gt;&lt;\/span&gt;&lt;\/td&gt;\n    &lt;td&gt;&lt;input type=\"range\" id=\"bmin\" min=\"0\" max=\"255\" value=\"0\" class = \"slider\"&gt;&lt;\/td&gt;\n    &lt;td&gt;B max:&lt;span id=\"BMAXdemo\"&gt;&lt;\/span&gt;&lt;\/td&gt;\n    &lt;td&gt; &lt;input type=\"range\" id=\"bmax\" min=\"0\" max=\"255\" value=\"50\" class = \"slider\"&gt;&lt;\/td&gt;\n  &lt;\/tr&gt;\n&lt;\/table&gt;\n&lt;\/div&gt;<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>\/\/ANN:6\nvar RMAXslider = document.getElementById(\"rmax\");\nvar RMAXoutput = document.getElementById(\"RMAXdemo\");\nRMAXoutput.innerHTML = RMAXslider.value;\nRMAXslider.oninput = function() {\n  RMAXoutput.innerHTML = this.value;\n  RMAX = parseInt(RMAXoutput.innerHTML,10);\n  console.log(\"RMAX=\" + RMAX);\n}\nconsole.log(\"RMAX=\" + RMAX);\n\nvar RMINslider = document.getElementById(\"rmin\");\nvar RMINoutput = document.getElementById(\"RMINdemo\");\nRMINoutput.innerHTML = RMINslider.value;\nRMINslider.oninput = function(){\nRMINoutput.innerHTML = this.value;\n  RMIN = parseInt(RMINoutput.innerHTML,10);\n  console.log(\"RMIN=\" + RMIN);\n}\nconsole.log(\"RMIN=\" + RMIN);\n\nvar GMAXslider = document.getElementById(\"gmax\");\nvar GMAXoutput = document.getElementById(\"GMAXdemo\");\nGMAXoutput.innerHTML = GMAXslider.value;\nGMAXslider.oninput = function(){\n  GMAXoutput.innerHTML = this.value;\n  GMAX = parseInt(GMAXoutput.innerHTML,10);\n}\nconsole.log(\"GMAX=\" + GMAX);\n\nvar GMINslider = document.getElementById(\"gmin\");\nvar GMINoutput = document.getElementById(\"GMINdemo\");\nGMINoutput.innerHTML = GMINslider.value;\nGMINslider.oninput = function(){\n  GMINoutput.innerHTML = this.value;\n  GMIN = parseInt(GMINoutput.innerHTML,10);\n}\nconsole.log(\"GMIN=\" + GMIN);\n\nvar BMAXslider = document.getElementById(\"bmax\");\nvar BMAXoutput = document.getElementById(\"BMAXdemo\");\nBMAXoutput.innerHTML = BMAXslider.value;\nBMAXslider.oninput = function(){\n  BMAXoutput.innerHTML = this.value;\n  BMAX = parseInt(BMAXoutput.innerHTML,10);\n}\nconsole.log(\"BMAX=\" + BMAX);\n\nvar BMINslider = document.getElementById(\"bmin\");\nvar BMINoutput = document.getElementById(\"BMINdemo\");\nBMINoutput.innerHTML = BMINslider.value;\nBMINslider.oninput = function(){\n  BMINoutput.innerHTML = this.value;\n  BMIN = parseInt(BMINoutput.innerHTML,10);\n}\nconsole.log(\"BMIN=\" + BMIN);<\/code><\/pre>\n\n\n\n<p>The maximum and minimum values of red, green, and blue (RGB)&nbsp; are applied to the OpenCV function, <span class=\"rnthl rntliteral\">inRange()<\/span>, at <strong><em>ANN<\/em><\/strong>:<strong>7<\/strong>.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>let high = new cv.Mat(src.rows,src.cols,src.type(),&#091;RMAX,GMAX,BMAX,255]);\nlet low = new cv.Mat(src.rows,src.cols,src.type(),&#091;RMIN,GMIN,BMIN,0]);\n\ncv.inRange(src,low,high,mask1);\n\/\/inRange(source image, lower limit, higher limit, destination image)\n    \ncv.threshold(mask1,mask,THRESH_MIN,255,cv.THRESH_BINARY);\n\/\/threshold(source image,destination image,threshold,255,threshold method);<\/code><\/pre>\n\n\n\n<p>The&nbsp; image is 4channel; RGBA where A is the level of transparency. In this tutorial, A will set set at 100% opacity, namely 255. The code is based on the fact that, besides the A plane, the image has 3 color planes, RGB, each pixel in each plane having a value between 0 and 255. The high\/low limits are applied to the corresponding color planes for each pixel. <\/p>\n\n\n\n<p>Note that <span class=\"rnthl rntliteral\">inRange()<\/span> has a destination image which has been created previously in the program (<strong><em>ANN<\/em><\/strong>:<strong>8<\/strong>). <\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>let M00Array = &#091;0,];\nlet orig = new cv.Mat();\nlet mask = new cv.Mat();\nlet mask1 = new cv.Mat();\nlet mask2 = new cv.Mat();\nlet contours = new cv.MatVector();\nlet hierarchy = new cv.Mat();\nlet rgbaPlanes = new cv.MatVector();\n    \nlet color = new cv.Scalar(0,0,0);\n\nclear_canvas();\n\norig = cv.imread(ShowImage);\ncv.split(orig,rgbaPlanes);  \/\/SPLIT\nlet BP = rgbaPlanes.get(2);  \/\/ SELECTED COLOR PLANE\nlet GP = rgbaPlanes.get(1);\nlet RP = rgbaPlanes.get(0);\ncv.merge(rgbaPlanes,orig);<\/code><\/pre>\n\n\n\n<p><strong>Important: <\/strong> every image created in an OpenCV program has to be deleted to avoid computer memory leakage (<strong><em>ANN<\/em><\/strong>:<strong>8A<\/strong>).<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>src.delete();\nhigh.delete();\nlow.delete();\norig.delete();\nmask1.delete();\nmask2.delete();\nmask.delete();\ncontours.delete();\nhierarchy.delete();\n\/\/cnt.delete();\nRP.delete();<\/code><\/pre>\n\n\n\n<p>The destination image Mask1 is not shown in the program although it could be. However, it is used by the <span class=\"rnthl rntliteral\">threshold()<\/span> function immediately following <span class=\"rnthl rntliteral\">inRange()<\/span>. <\/p>\n\n\n\n<p>The <span class=\"rnthl rntliteral\">threshold()<\/span> function examines the composite source image pixel value and sets the corresponding destination value at either 0 or 255 depending on whether the source value is less or greater than the threshold value. The top image in the right hand column shows this binary image.<\/p>\n\n\n\n<p>For the sake of completeness, an invert capability has been added to the binary image. When the <strong>INVERT <\/strong>button in the web page is clicked, the binary image is inverted (black becomes white, white becomes black) and subsequent processing is performed on the new image. The button is bistable, so that a second push returns the binary image to its original state.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Target-color Probe<\/h4>\n\n\n<div class=\"wp-block-image is-style-default\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"977\" height=\"387\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/12\/ESP32-CAM-Color-Tracking-Color-Probe.jpg?resize=977%2C387&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"ESP32-CAM Color Tracking Color Probe OpenCVJS\" class=\"wp-image-100803\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/12\/ESP32-CAM-Color-Tracking-Color-Probe.jpg?w=977&amp;quality=100&amp;strip=all&amp;ssl=1 977w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/12\/ESP32-CAM-Color-Tracking-Color-Probe.jpg?resize=300%2C119&amp;quality=100&amp;strip=all&amp;ssl=1 300w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/12\/ESP32-CAM-Color-Tracking-Color-Probe.jpg?resize=768%2C304&amp;quality=100&amp;strip=all&amp;ssl=1 768w\" sizes=\"(max-width: 977px) 100vw, 977px\" \/><\/figure><\/div>\n\n\n<p>In the screenshot, a red cap is the target in an ordinary room environment with an ordinary 60W fluorescent lamp. The lamp emits red, green, and blue. The red cap reflects red, green, and blue but principally red. The method of detecting the amount of each reflected color will be described now. This method allows the RGB trackbars to be set with minimal effort. Its use is strongly advised.<\/p>\n\n\n<div class=\"wp-block-image is-style-default\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"941\" height=\"265\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/12\/ESP32-CAM-Web-Server-Color-Tracking-Example-2.png?resize=941%2C265&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"ESP32-CAM OpenCVJS Original Image Mask and Color Tracking\" class=\"wp-image-100805\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/12\/ESP32-CAM-Web-Server-Color-Tracking-Example-2.png?w=941&amp;quality=100&amp;strip=all&amp;ssl=1 941w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/12\/ESP32-CAM-Web-Server-Color-Tracking-Example-2.png?resize=300%2C84&amp;quality=100&amp;strip=all&amp;ssl=1 300w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/12\/ESP32-CAM-Web-Server-Color-Tracking-Example-2.png?resize=768%2C216&amp;quality=100&amp;strip=all&amp;ssl=1 768w\" sizes=\"(max-width: 941px) 100vw, 941px\" \/><figcaption class=\"wp-element-caption\"><center>Image courtesy of <a href=\"https:\/\/randomnerdtutorials.com\/wp-admin\/post.php?post=100642&amp;action=edit#andrew\">Andrew R. Sass<\/a><\/center><\/figcaption><\/figure><\/div>\n\n\n<p>The method involves using the Color Probe sliders. These two sliders, X and Y Probe are used to place a small white circle probe in a desired position in the bottom image of the right-hand column. The RGB values in this probe position are measured and used to set the <span class=\"rnthl rntliteral\">inRange()<\/span> RGB maximums and minimums described previously. <\/p>\n\n\n\n<p>See <strong><em>ANN<\/em><\/strong>:<strong>9<\/strong>,<strong>9A,9B,9C<\/strong> for the code associated with this probe.<\/p>\n\n\n\n<p>When the optimum values for a desired target are found using the X, Y probe and set by the trackbar, the target in the binary image is white and the remainder of the image is black, ideally, as shown in the screenshot. <\/p>\n\n\n\n<p>This ideal typically can be&nbsp; realized only when lighting conditions can be closely controlled. Indoor, standard room lighting is acceptable. Filters can be used for optimal results but were not used here.<\/p>\n\n\n\n<p>Here&#8217;s another example:<\/p>\n\n\n<div class=\"wp-block-image is-style-default\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"942\" height=\"272\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/12\/ESP32-CAM-Web-Server-Color-Tracking-Example.png?resize=942%2C272&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"ESP32-CAM OpenCVJS Original Image Mask and Color Tracking Example\" class=\"wp-image-100806\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/12\/ESP32-CAM-Web-Server-Color-Tracking-Example.png?w=942&amp;quality=100&amp;strip=all&amp;ssl=1 942w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/12\/ESP32-CAM-Web-Server-Color-Tracking-Example.png?resize=300%2C87&amp;quality=100&amp;strip=all&amp;ssl=1 300w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/12\/ESP32-CAM-Web-Server-Color-Tracking-Example.png?resize=768%2C222&amp;quality=100&amp;strip=all&amp;ssl=1 768w\" sizes=\"(max-width: 942px) 100vw, 942px\" \/><\/figure><\/div>\n\n\n<h4 class=\"wp-block-heading\">Tracking<\/h4>\n\n\n\n<p>Once the binary image is deemed acceptable, the <strong>TRACKING <\/strong>button, which is bistable, can be clicked. <strong><em>ANN<\/em><\/strong>:<strong>10<\/strong> marks the beginning of the tracking routine. <\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>\/\/ANN:10\nif(b_tracker == true){\ntry{\n if(b_invert==false){<\/code><\/pre>\n\n\n\n<p>Since, as mentioned above, this article is not concerned with the INVERT capability, only the <span class=\"rnthl rntliteral\">b_invert<\/span> equal to false is of interest<\/p>\n\n\n\n<p><strong><em>ANN<\/em><\/strong>:<strong>11<\/strong> The first step in the tracking is <span class=\"rnthl rntliteral\">findContours<\/span>, which is the OpenCV algorithm which finds the contours of all the white objects in the binary image.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>\/\/ANN:11   \n    cv.findContours(mask,contours,hierarchy,cv.RETR_CCOMP,cv.CHAIN_APPROX_SIMPLE);\n\/\/findContours(source image, array of contours found, hierarchy of contours\n\/\/ if contours are inside other contours, method of contour data retrieval,\n\/\/algorithm method)\n}\nelse{\n  cv.findContours(mask2,contours,hierarchy,cv.RETR_CCOMP,cv.CHAIN_APPROX_SIMPLE);\n}\nconsole.log(\"CONTOUR_SIZE = \" + contours.size());\n\n\/\/draw contours\nif(b_contour==true){\n  for(let i = 0; i &lt; contours.size(); i++){\n    cv.drawContours(src,contours,i,&#091;0,0,0,255],2,cv.LINE_8,hierarchy,100)\n  }\n}<\/code><\/pre>\n\n\n\n<p>If the tracking button is pressed when the binary image is fully black,&nbsp; the instructions&nbsp; depending on <span class=\"rnthl rntliteral\">findContours<\/span> output will throw exceptions; the try-catch allows the program to continue safely, posting an output in the console log and the text box.<\/p>\n\n\n\n<p><span class=\"rnthl rntliteral\">Contours.sizecontours<\/span> is the output of <span class=\"rnthl rntliteral\">findContours<\/span> and is an array of contours of the white object(s) found in the binary image. <span class=\"rnthl rntliteral\">Contours.size()<\/span> finds the number of elements in the array. The hierarchy (contours inside other contours) output is not of concern here as there will be no white objects (outlined in black) inside other white objects.<\/p>\n\n\n\n<p><strong><em>ANN<\/em><\/strong>:<strong>12<\/strong> Marks the beginning of finding the moments of the contours found. <\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>\/ANN:12\nlet cnt;\nlet Moments;\nlet M00;\nlet M10;<\/code><\/pre>\n\n\n\n<p><span class=\"rnthl rntliteral\">M00<\/span> is the zeroth moment-the \u201carea\u201d enclosed by a contour. In OpenCv it is actually the number of pixels enclosed by the contour. <span class=\"rnthl rntliteral\">M10<\/span> and <span class=\"rnthl rntliteral\">M01<\/span> are the x and y coordinate-weighted number of pixels enclosed. <\/p>\n\n\n\n<p>As usual, the origin of the x,y coordinate system is at the upper left corner of the image. X is positive horizontal to the right and Y is positive vertical down. Therefore <span class=\"rnthl rntliteral\">M10\/M00<\/span> and <span class=\"rnthl rntliteral\">M01\/M00<\/span> are the x,y coordinates of the centroid of a contour in the array.<\/p>\n\n\n\n<p><strong><em>ANN<\/em><\/strong>:<strong>13,13A<\/strong> marks finding the largest area contour in the array of contours using the <span class=\"rnthl rntliteral\">MaxAreaArg<\/span> function and transmitting the centroid, <span class=\"rnthl rntliteral\">x_cm<\/span>, <span class=\"rnthl rntliteral\">y_cm<\/span> to the ESP32 via a fetch instruction. <\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>\/\/ANN:13\nfor(let k = 0; k &lt; contours.size(); k++){\n  cnt = contours.get(k); \n  Moments = cv.moments(cnt,false);\n  M00Array&#091;k] = Moments.m00;\n  \/\/ cnt.delete();\n}\n\n\/\/ANN13A\nlet max_area_arg = MaxAreaArg(M00Array);\nconsole.log(\"MAXAREAARG = \"+max_area_arg);\n\n\/\/let TestArray = &#091;0,0,0,15,4,15,2];\n\/\/let TestArray0 = &#091;];\n\/\/let max_test_area_arg = MaxAreaArg(TestArray0);\n\/\/console.log(\"MAXTESTAREAARG = \"+max_test_area_arg);\n\nlet ArgMaxArea = MaxAreaArg(M00Array);\nif(ArgMaxArea &gt;= 0){\ncnt = contours.get(MaxAreaArg(M00Array));  \/\/use the contour with biggest MOO\n\/\/cnt = contours.get(54);\nMoments = cv.moments(cnt,false);\nM00 = Moments.m00;\nM10 = Moments.m10;\nM01 = Moments.m01;\nx_cm = M10\/M00;    \/\/ 75 for circle_9.jpg\ny_cm = M01\/M00;    \/\/ 41 for circle_9.jpg\n\nXCMoutput.innerHTML = Math.round(x_cm);\nYCMoutput.innerHTML = Math.round(y_cm);\n\nconsole.log(\"M00 = \"+M00);  \nconsole.log(\"XCM = \"+Math.round(x_cm));\nconsole.log(\"YCM = \"+Math.round(y_cm)); \n\n\/\/fetch(document.location.origin+'\/?xcm='+Math.round(x_cm)+';stop');\nfetch(document.location.origin+'\/?cm='+Math.round(x_cm)+';'+Math.round(y_cm)+';stop');\n\nconsole.log(\"M00ARRAY = \" + M00Array);<\/code><\/pre>\n\n\n\n<p>During the running of the program, the centroid coordinates are seen printed in the serial monitor as well as in the <span class=\"rnthl rntliteral\">console.log<\/span> and in the text box in the browser screen. The ESP32 can use the centroid data for tracking purposes in robotic applications.<\/p>\n\n\n\n<p><strong><em>ANN<\/em><\/strong>:<strong>14<\/strong> Marks code for a blue bounding rectangle which bounds the largest area contour and the centroid of that contour. These can be seen in the lower image in the right hand column of the browser screen.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>\/\/ANN:14   \n    \n\/\/**************min area bounding rect********************\n\/\/let rotatedRect=cv.minAreaRect(cnt);\n\/\/let vertices = cv.RotatedRect.points(rotatedRect);\n\n\/\/for(let j=0;j&lt;4;j++){\n\/\/    cv.line(src,vertices&#091;j],\n\/\/        vertices&#091;(j+1)%4],&#091;0,0,255,255],2,cv.LINE_AA,0);\n\/\/}\n\/\/***************end min area bounding rect*************************************\n\n\n\/\/***************bounding rect***************************\nlet rect = cv.boundingRect(cnt);\nlet point1 = new cv.Point(rect.x,rect.y);\nlet point2 = new cv.Point(rect.x+rect.width,rect.y+rect.height);\n\ncv.rectangle(src,point1,point2,&#091;0,0,255,255],2,cv.LINE_AA,0);\n\/\/*************end bounding rect***************************\n\n\/\/*************draw center point*********************\nlet point3 = new cv.Point(x_cm,y_cm);\ncv.circle(src,point3,2,&#091;0,0,255,255],2,cv.LINE_AA,0);\n\/\/***********end draw center point*********************\n\n}\/\/end if(ArgMaxArea &gt;= 0)\nelse{\n  if(ArgMaxArea==-1){ \n    console.log(\"ZERO ARRAY LENGTH\");\n  }\n  else{              \/\/ArgMaxArea=-2\n    console.log(\"DUPLICATE MAX ARRAY-ELEMENT\");\n  }\n}\n\ncnt.delete();<\/code><\/pre>\n\n\n\n<p>Below the lower image in the right-hand column, a text box contains selected outputs of the program including the X, Y Probe data, the centroid coordinates, and a catch output if an exception is generated as mentioned above.<\/p>\n\n\n<div class=\"wp-block-image is-style-default\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1025\" height=\"535\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/12\/ESP32-CAM-Color-Tracking-X-Y-Coordinates-messages.jpg?resize=1025%2C535&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"ESP32-CAM Color Tracking Output Messages X Y coordinates\" class=\"wp-image-100808\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/12\/ESP32-CAM-Color-Tracking-X-Y-Coordinates-messages.jpg?w=1025&amp;quality=100&amp;strip=all&amp;ssl=1 1025w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/12\/ESP32-CAM-Color-Tracking-X-Y-Coordinates-messages.jpg?resize=300%2C157&amp;quality=100&amp;strip=all&amp;ssl=1 300w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/12\/ESP32-CAM-Color-Tracking-X-Y-Coordinates-messages.jpg?resize=768%2C401&amp;quality=100&amp;strip=all&amp;ssl=1 768w\" sizes=\"(max-width: 1025px) 100vw, 1025px\" \/><\/figure><\/div>\n\n\n<h2 class=\"wp-block-heading\">Uploading the Code<\/h2>\n\n\n\n<p>After inserting your network credentials and the pinout for the camera you&#8217;re using you can upload the code.<\/p>\n\n\n\n<p>In the <strong>Tools <\/strong>menu select the following settings before uploading the code to your board.<\/p>\n\n\n<div class=\"wp-block-image is-style-default\">\n<figure class=\"aligncenter size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"672\" height=\"538\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/12\/ESP32-CAM-Wrover-Upload-Options.png?resize=672%2C538&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"ESP32-CAM Wrover Upload Options\" class=\"wp-image-100809\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/12\/ESP32-CAM-Wrover-Upload-Options.png?w=672&amp;quality=100&amp;strip=all&amp;ssl=1 672w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/12\/ESP32-CAM-Wrover-Upload-Options.png?resize=300%2C240&amp;quality=100&amp;strip=all&amp;ssl=1 300w\" sizes=\"(max-width: 672px) 100vw, 672px\" \/><\/figure><\/div>\n\n\n<ul class=\"wp-block-list\">\n<li>BOARD: ESP32 Wrover Module<\/li>\n\n\n\n<li>Flash Mode: \u201cQIO\u201d<\/li>\n\n\n\n<li>PARTITION SCHEME: \u201cHuge App (3Mb No OTA\/1MB SPIFFS)\u201d<\/li>\n\n\n\n<li>Flash Frequency: \u201c80 Mhz\u201d<\/li>\n\n\n\n<li>Upload Speed: \u201c115200\u201d<\/li>\n\n\n\n<li>Core Debug Level: \u201cNone\u201d<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Testing the Program<\/h2>\n\n\n\n<p>After uploading the code, open the Serial Monitor at a baud rate of 115200. Press the on-board RST button, and the ESP IP address should be printed. In this case, the IP address is <span class=\"rnthl rntliteral\">192.168.1.95<\/span>.<\/p>\n\n\n<div class=\"wp-block-image is-style-default\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"739\" height=\"445\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/12\/ESP32-CAM-IP-Address-Serial-Monitor.jpg?resize=739%2C445&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"ESP32-CAM Web Server Color Tracking Demonstration OpenCV.js IP Address\" class=\"wp-image-100689\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/12\/ESP32-CAM-IP-Address-Serial-Monitor.jpg?w=739&amp;quality=100&amp;strip=all&amp;ssl=1 739w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/12\/ESP32-CAM-IP-Address-Serial-Monitor.jpg?resize=300%2C181&amp;quality=100&amp;strip=all&amp;ssl=1 300w\" sizes=\"(max-width: 739px) 100vw, 739px\" \/><\/figure><\/div>\n\n\n<p>Open a browser on your local network and type de ESP32-CAM IP address. <\/p>\n\n\n\n<p>Open the console log when the browser opens. Check if OpenCV.js is loading properly. At the bottom right corner of the web page, it should display &#8220;OpenCV.JS READY&#8221;.<\/p>\n\n\n\n<p>Then left-click the <strong>Color Detection<\/strong> button in the upper left column of the browser window. <\/p>\n\n\n\n<p>You should see a similar window and no error messages.<\/p>\n\n\n<div class=\"wp-block-image is-style-default\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"542\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/12\/ESP32-CAM-Color-Detection-Tracking-Web-Server-Preview.jpg?resize=800%2C542&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"ESP32-CAM Web Server Color Tracking Demonstration OpenCV.js\" class=\"wp-image-100810\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/12\/ESP32-CAM-Color-Detection-Tracking-Web-Server-Preview.jpg?w=800&amp;quality=100&amp;strip=all&amp;ssl=1 800w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/12\/ESP32-CAM-Color-Detection-Tracking-Web-Server-Preview.jpg?resize=300%2C203&amp;quality=100&amp;strip=all&amp;ssl=1 300w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/12\/ESP32-CAM-Color-Detection-Tracking-Web-Server-Preview.jpg?resize=768%2C520&amp;quality=100&amp;strip=all&amp;ssl=1 768w\" sizes=\"(max-width: 800px) 100vw, 800px\" \/><\/figure><\/div>\n\n\n<p>After setting the right settings to target a color in the Target-color Probe (as explained previously), click the <strong>Tracking <\/strong>button.<\/p>\n\n\n\n<p>At the same time, the centroid coordinates of the target should be displayed on the web page as well as on the ESP32-CAM Serial Monitor.<\/p>\n\n\n<div class=\"wp-block-image is-style-default\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"739\" height=\"445\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/12\/Serial-Monitor-Color-Tracking-Results-ESP32-CAM-OpenCV-JS.jpg?resize=739%2C445&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"ESP32-CAM Web Server Color Tracking Demonstration OpenCV.js Arduino IDE Serial Monitor\" class=\"wp-image-100690\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/12\/Serial-Monitor-Color-Tracking-Results-ESP32-CAM-OpenCV-JS.jpg?w=739&amp;quality=100&amp;strip=all&amp;ssl=1 739w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/12\/Serial-Monitor-Color-Tracking-Results-ESP32-CAM-OpenCV-JS.jpg?resize=300%2C181&amp;quality=100&amp;strip=all&amp;ssl=1 300w\" sizes=\"(max-width: 739px) 100vw, 739px\" \/><\/figure><\/div>\n\n\n<h2 class=\"wp-block-heading\">Wrapping Up<\/h2>\n\n\n\n<p>None of the elements of the project described in this tutorial are new. The ESP32 Camera Web Server and OpenCV have each been described extensively and in detail in the literature. <\/p>\n\n\n\n<p>The novelty here has been the combining of these two technologies via OpenCV.js. ESP32 Camera, with its small size, wi-fi, high tech and low-cost capability promises to be an interesting new front-end image-capture capability for OpenCV web server applications.<\/p>\n\n\n\n<p><strong>Learn more about the ESP32-CAM<\/strong><\/p>\n\n\n\n<p>We hope you liked this project. Learn more about the ESP32-CAM with our tutorials:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/randomnerdtutorials.com\/projects-esp32-cam\/\">ESP32-CAM Projects and Tutorials<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/randomnerdtutorials.com\/esp32-cam-projects-ebook\/\">Build ESP32-CAM Project eBook<\/a><\/li>\n<\/ul>\n\n\n\n<h3 id=\"andrew\">About Andrew R. Sass<\/h3>\n\n\n\n<p>This project\/tutorial was developed by Andrew R. Sass. We&#8217;ve edited the tutorial to match our tutorials&#8217; style. Apart from some CSS, the code is the original provided by Andrew.<\/p>\n\n\n\n<p><strong>Author background<\/strong>: Andrew (\u201cDOC\u201d) R. Sass holds a BSEE(MIT), MSEE &amp; PhD EE (PURDUE). He is a retired research engineer (integrated circuit components), a second-career retired teacher (AP Physics, Physics, Robotics), and has been a mentor of a local FIRST robotics team.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This guide introduces OpenCV.js and OpenCV tools for the ESP32 Camera Web Server environment. As an example, we&#8217;ll build a simple ESP32 Camera Web Server that includes color detection and &#8230; <\/p>\n<p class=\"read-more-container\"><a title=\"ESP32-CAM Web Server with OpenCV.js: Color Detection and Tracking\" class=\"read-more button\" href=\"https:\/\/randomnerdtutorials.com\/esp32-cam-opencv-js-color-detection-tracking\/#more-100642\" aria-label=\"Read more about ESP32-CAM Web Server with OpenCV.js: Color Detection and Tracking\">CONTINUE READING \u00bb<\/a><\/p>\n","protected":false},"author":5,"featured_media":100812,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[319],"tags":[],"class_list":["post-100642","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-esp32-cam"],"aioseo_notices":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/12\/ESP32-CAM-Web-Server-with-OpenCVjs-Color-Detection-Tracking.jpg?fit=1280%2C720&quality=100&strip=all&ssl=1","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/posts\/100642","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/users\/5"}],"replies":[{"embeddable":true,"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/comments?post=100642"}],"version-history":[{"count":1,"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/posts\/100642\/revisions"}],"predecessor-version":[{"id":159114,"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/posts\/100642\/revisions\/159114"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/media\/100812"}],"wp:attachment":[{"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/media?parent=100642"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/categories?post=100642"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/tags?post=100642"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}