Created
April 1, 2025 18:57
-
-
Save cyrusmeh/309dfb2014ac1c5b3d29dd23c73ab21a to your computer and use it in GitHub Desktop.
In this comprehensive tutorial, we’ll guide you through building a License Plate Recognition System using the ESP32-CAM. You’ll receive step-by-step instructions, complete with a circuit diagram and code, to help you capture images of cars and bikes. We’ll show you how to send these images to a cloud server powered by AI for license plate recogn…
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Libraries for WiFi, Secure Client, and Camera functionalities | |
| #include <Arduino.h> | |
| #include <WiFi.h> | |
| #include <WiFiClientSecure.h> | |
| #include "soc/soc.h" | |
| #include "soc/rtc_cntl_reg.h" | |
| #include "esp_camera.h" | |
| /* I2C and OLED Display Includes ------------------------------------------- */ | |
| #include <Wire.h> | |
| #include <Adafruit_GFX.h> | |
| #include <Adafruit_SSD1306.h> | |
| // ESP32-CAM doesn't have dedicated I2C pins, so we define our own | |
| #define I2C_SDA 15 | |
| #define I2C_SCL 14 | |
| TwoWire I2Cbus = TwoWire(0); | |
| // Display defines | |
| #define SCREEN_WIDTH 128 | |
| #define SCREEN_HEIGHT 64 | |
| #define OLED_RESET -1 | |
| #define SCREEN_ADDRESS 0x3C | |
| Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &I2Cbus, OLED_RESET); | |
| const char* ssid = "xxx"; // Replace "xxx" with your WiFi SSID | |
| const char* password = "xxx"; // Replace "xxx" with your WiFi Password | |
| String serverName = "www.circuitdigest.cloud"; // Replace with your server domain | |
| String serverPath = "/readnumberplate"; // API endpoint path "/readqrcode" "/readnumberplate" | |
| const int serverPort = 443; // HTTPS port | |
| String apiKey = "xxx"; // Replace "xxx" with your API key | |
| #define triggerButton 13 // GPIO pin for the trigger button | |
| #define flashLight 4 // GPIO pin for the flashlight | |
| int count = 0; // Counter for image uploads | |
| WiFiClientSecure client; // Secure client for HTTPS communication | |
| // Camera GPIO pins - adjust based on your ESP32-CAM board | |
| #define PWDN_GPIO_NUM 32 | |
| #define RESET_GPIO_NUM -1 | |
| #define XCLK_GPIO_NUM 0 | |
| #define SIOD_GPIO_NUM 26 | |
| #define SIOC_GPIO_NUM 27 | |
| #define Y9_GPIO_NUM 35 | |
| #define Y8_GPIO_NUM 34 | |
| #define Y7_GPIO_NUM 39 | |
| #define Y6_GPIO_NUM 36 | |
| #define Y5_GPIO_NUM 21 | |
| #define Y4_GPIO_NUM 19 | |
| #define Y3_GPIO_NUM 18 | |
| #define Y2_GPIO_NUM 5 | |
| #define VSYNC_GPIO_NUM 25 | |
| #define HREF_GPIO_NUM 23 | |
| #define PCLK_GPIO_NUM 22 | |
| // Function to extract a JSON string value by key | |
| String extractJsonStringValue(const String& jsonString, const String& key) { | |
| int keyIndex = jsonString.indexOf(key); | |
| if (keyIndex == -1) { | |
| return ""; | |
| } | |
| int startIndex = jsonString.indexOf(':', keyIndex) + 2; | |
| int endIndex = jsonString.indexOf('"', startIndex); | |
| if (startIndex == -1 || endIndex == -1) { | |
| return ""; | |
| } | |
| return jsonString.substring(startIndex, endIndex); | |
| } | |
| // Function to display text on OLED | |
| void displayText(String text) { | |
| display.clearDisplay(); | |
| display.setCursor(0, 10); | |
| display.setTextSize(1); | |
| display.setTextColor(SSD1306_WHITE); | |
| display.print(text); | |
| display.display(); | |
| } | |
| void setup() { | |
| // Disable brownout detector | |
| WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); | |
| Serial.begin(115200); | |
| pinMode(flashLight, OUTPUT); | |
| pinMode(triggerButton, INPUT); | |
| digitalWrite(flashLight, LOW); | |
| // Connect to WiFi | |
| WiFi.mode(WIFI_STA); | |
| Serial.println(); | |
| Serial.print("Connecting to "); | |
| Serial.println(ssid); | |
| WiFi.begin(ssid, password); | |
| while (WiFi.status() != WL_CONNECTED) { | |
| Serial.print("."); | |
| delay(500); | |
| } | |
| Serial.println(); | |
| Serial.print("ESP32-CAM IP Address: "); | |
| Serial.println(WiFi.localIP()); | |
| // Configure camera | |
| camera_config_t config; | |
| config.ledc_channel = LEDC_CHANNEL_0; | |
| config.ledc_timer = LEDC_TIMER_0; | |
| config.pin_d0 = Y2_GPIO_NUM; | |
| config.pin_d1 = Y3_GPIO_NUM; | |
| config.pin_d2 = Y4_GPIO_NUM; | |
| config.pin_d3 = Y5_GPIO_NUM; | |
| config.pin_d4 = Y6_GPIO_NUM; | |
| config.pin_d5 = Y7_GPIO_NUM; | |
| config.pin_d6 = Y8_GPIO_NUM; | |
| config.pin_d7 = Y9_GPIO_NUM; | |
| config.pin_xclk = XCLK_GPIO_NUM; | |
| config.pin_pclk = PCLK_GPIO_NUM; | |
| config.pin_vsync = VSYNC_GPIO_NUM; | |
| config.pin_href = HREF_GPIO_NUM; | |
| config.pin_sscb_sda = SIOD_GPIO_NUM; | |
| config.pin_sscb_scl = SIOC_GPIO_NUM; | |
| config.pin_pwdn = PWDN_GPIO_NUM; | |
| config.pin_reset = RESET_GPIO_NUM; | |
| config.xclk_freq_hz = 20000000; | |
| config.pixel_format = PIXFORMAT_JPEG; | |
| // Adjust frame size and quality based on PSRAM availability | |
| if (psramFound()) { | |
| config.frame_size = FRAMESIZE_SVGA; | |
| config.jpeg_quality = 5; // Lower number means higher quality (0-63) | |
| config.fb_count = 2; | |
| Serial.println("PSRAM found"); | |
| } else { | |
| config.frame_size = FRAMESIZE_CIF; | |
| config.jpeg_quality = 12; // Lower number means higher quality (0-63) | |
| config.fb_count = 1; | |
| } | |
| // Initialize camera | |
| esp_err_t err = esp_camera_init(&config); | |
| if (err != ESP_OK) { | |
| Serial.printf("Camera init failed with error 0x%x", err); | |
| delay(1000); | |
| ESP.restart(); | |
| } | |
| // Initialize I2C with our defined pins | |
| I2Cbus.begin(I2C_SDA, I2C_SCL, 100000); | |
| // Initialize OLED display | |
| //if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) { | |
| if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { | |
| Serial.printf("SSD1306 OLED display failed to initialize.\nCheck that display SDA is connected to pin %d and SCL connected to pin %d\n", I2C_SDA, I2C_SCL); | |
| while (true); | |
| } | |
| // Display initialization messages | |
| displayText("System Initialization Successful"); | |
| delay(1000); | |
| displayText("Press Trigger Button \n\nto Start Capturing"); | |
| } | |
| void loop() { | |
| // Check if trigger button is pressed | |
| if (digitalRead(triggerButton) == HIGH) { | |
| int status = sendPhoto(); | |
| if (status == -1) { | |
| displayText("Image Capture Failed"); | |
| } else if (status == -2) { | |
| displayText("Server Connection Failed"); | |
| } | |
| } | |
| } | |
| // Function to capture and send photo to the server | |
| int sendPhoto() { | |
| camera_fb_t* fb = NULL; | |
| // Turn on flash light and capture image | |
| // digitalWrite(flashLight, HIGH); | |
| delay(100); | |
| fb = esp_camera_fb_get(); | |
| delay(100); | |
| if (!fb) { | |
| Serial.println("Camera capture failed"); | |
| return -1; | |
| } | |
| // Display success message | |
| displayText("Image Capture Success"); | |
| delay(300); | |
| // digitalWrite(flashLight, LOW); | |
| // Connect to server | |
| Serial.println("Connecting to server:" + serverName); | |
| displayText("Connecting to server:\n\n" + serverName); | |
| client.setInsecure(); // Skip certificate validation for simplicity | |
| if (client.connect(serverName.c_str(), serverPort)) { | |
| Serial.println("Connection successful!"); | |
| displayText("Connection successful!"); | |
| delay(300); | |
| displayText("Data Uploading !"); | |
| // Increment count and prepare file name | |
| count++; | |
| Serial.println(count); | |
| String filename = apiKey + ".jpeg"; | |
| // Prepare HTTP POST request | |
| String head = "--CircuitDigest\r\nContent-Disposition: form-data; name=\"imageFile\"; filename=\"" + filename + "\"\r\nContent-Type: image/jpeg\r\n\r\n"; | |
| String tail = "\r\n--CircuitDigest--\r\n"; | |
| uint32_t imageLen = fb->len; | |
| uint32_t extraLen = head.length() + tail.length(); | |
| uint32_t totalLen = imageLen + extraLen; | |
| client.println("POST " + serverPath + " HTTP/1.1"); | |
| client.println("Host: " + serverName); | |
| client.println("Content-Length: " + String(totalLen)); | |
| client.println("Content-Type: multipart/form-data; boundary=CircuitDigest"); | |
| client.println("Authorization:" + apiKey); | |
| client.println(); | |
| client.print(head); | |
| // Send image data in chunks | |
| uint8_t* fbBuf = fb->buf; | |
| size_t fbLen = fb->len; | |
| for (size_t n = 0; n < fbLen; n += 1024) { | |
| if (n + 1024 < fbLen) { | |
| client.write(fbBuf, 1024); | |
| fbBuf += 1024; | |
| } else { | |
| size_t remainder = fbLen % 1024; | |
| client.write(fbBuf, remainder); | |
| } | |
| } | |
| client.print(tail); | |
| // Clean up | |
| esp_camera_fb_return(fb); | |
| displayText("Waiting For Response!"); | |
| // Wait for server response | |
| String response; | |
| long startTime = millis(); | |
| while (client.connected() && millis() - startTime < 5000) { // Modifify the Waiting time as per the response time | |
| if (client.available()) { | |
| char c = client.read(); | |
| response += c; | |
| } | |
| } | |
| // Extract and display NPR data from response | |
| String NPRData = extractJsonStringValue(response, "\"number_plate\""); | |
| String imageLink = extractJsonStringValue(response, "\"view_image\""); | |
| // Serial.print("NPR DATA: "); | |
| // Serial.println(NPRData); | |
| // Serial.print("ImageLink: "); | |
| // Serial.println(imageLink); | |
| Serial.print("Response: "); | |
| Serial.println(response); | |
| displayText("NPR Data:\n\n" + NPRData); | |
| client.stop(); | |
| esp_camera_fb_return(fb); | |
| return 0; | |
| } else { | |
| Serial.println("Connection to server failed"); | |
| esp_camera_fb_return(fb); | |
| return -2; | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment