| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389 |
- #include "esp_camera.h"
- #include "Arduino.h"
- #include "FS.h"
- #include "SD_MMC.h"
- #include "soc/soc.h"
- #include "soc/rtc_cntl_reg.h"
- #include "driver/rtc_io.h"
- #include <vector>
- #include <tuple>
- #include <cstdint>
- // Define Pin numbers used by the camera.
- #define FLASH_GPIO_NUM 4
- #define HREF_GPIO_NUM 23
- #define PCLK_GPIO_NUM 22
- #define PWDN_GPIO_NUM 32
- #define RESET_GPIO_NUM - 1
- #define SIOC_GPIO_NUM 27
- #define SIOD_GPIO_NUM 26
- #define XCLK_GPIO_NUM 0
- #define VSYNC_GPIO_NUM 25
- #define Y2_GPIO_NUM 5
- #define Y3_GPIO_NUM 18
- #define Y4_GPIO_NUM 19
- #define Y5_GPIO_NUM 21
- #define Y6_GPIO_NUM 36
- #define Y7_GPIO_NUM 39
- #define Y8_GPIO_NUM 34
- #define Y9_GPIO_NUM 35
- // Structure to hold the camera configuration parameters.
- camera_config_t config;
- // Function prototypes.
- void handleSerialInput();
- void initializeCamera();
- void processImage(camera_fb_t * frame_buffer);
- void ditherImage(camera_fb_t * frame_buffer);
- void savePictureToSDCard(camera_fb_t * frame_buffer);
- void takePicture();
- // Enumeration to represent the available dithering algorithms.
- enum DitheringAlgorithm: uint8_t {
- FLOYD_STEINBERG,
- JARVIS_JUDICE_NINKE,
- STUCKI
- };
- // Holds the currently selected dithering algorithm.
- DitheringAlgorithm ditherAlgorithm = FLOYD_STEINBERG;
- // Flag to enable or disable dithering.
- bool disableDithering = false;
- // Flag to invert pixel colors.
- bool invert = false;
- // Flag to represent the flash state when saving pictures to the Flipper.
- bool isFlashEnabled = false;
- // Flag to represent whether the image is rotated.
- bool rotated = false;
- // Flag to stop or start the stream.
- bool stopStream = false;
- // Flag to store jpeg images to sd card.
- bool storeJpeg = false;
- void setup() {
- // Disable the brownout detector.
- WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0);
- // Start serial communication at 115200 baud rate.
- Serial.begin(115200); // Prev 230400
- initializeCamera();
- }
- void loop() {
- if (!stopStream) {
- // Set pixel format to GRAYSCALE for streaming
- sensor_t * s = esp_camera_sensor_get();
- s -> set_framesize(s, FRAMESIZE_QVGA); // Or your preferred resolution
- s -> set_pixformat(s, PIXFORMAT_GRAYSCALE); // For streaming
- camera_fb_t * frame_buffer = esp_camera_fb_get();
- if (frame_buffer) {
- // Process and Send Grayscale image
- processImage(frame_buffer);
- // Return the frame buffer back to the camera driver
- esp_camera_fb_return(frame_buffer);
- }
- delay(25); // Adjust delay as necessary
- } else {
- // Handle any available serial input commands
- handleSerialInput();
- }
- }
- void handleSerialInput() {
- if (Serial.available() > 0) {
- char input = Serial.read();
- sensor_t * cameraSensor = esp_camera_sensor_get();
- switch (input) {
- case '>': // Toggle dithering.
- disableDithering = !disableDithering;
- break;
- case '<': // Toggle invert.
- invert = !invert;
- break;
- case 'B': // Add brightness.
- cameraSensor -> set_contrast(
- cameraSensor,
- cameraSensor -> status.brightness + 1
- );
- break;
- case 'b': // Remove brightness.
- cameraSensor -> set_contrast(
- cameraSensor,
- cameraSensor -> status.brightness - 1
- );
- break;
- case 'C': // Add contrast.
- cameraSensor -> set_contrast(
- cameraSensor,
- cameraSensor -> status.contrast + 1
- );
- break;
- case 'c': // Remove contrast.
- cameraSensor -> set_contrast(
- cameraSensor,
- cameraSensor -> status.contrast - 1
- );
- break;
- case 'j': // Toggle store jpeg to sd card off.
- storeJpeg = false;
- break;
- case 'J': // Toggle store jpeg to sd card on.
- storeJpeg = true;
- break;
- case 'P': // Picture sequence.
- // Stop the IO stream before taking a picture.
- stopStream = true;
- if (!isFlashEnabled) {
- if (storeJpeg) {
- // Save jpeg image to sd card.
- takePicture();
- } else {
- // Turn on torch.
- pinMode(FLASH_GPIO_NUM, OUTPUT);
- digitalWrite(FLASH_GPIO_NUM, HIGH);
- // Give some time for Flipper to save locally with flash on.
- delay(50);
- }
- // Turn off torch.
- pinMode(FLASH_GPIO_NUM, OUTPUT);
- digitalWrite(FLASH_GPIO_NUM, LOW);
- }
- // Restart the stream after the picture is taken.
- stopStream = false;
- break;
- case 'M': // Toggle Mirror.
- cameraSensor -> set_hmirror(cameraSensor, !cameraSensor -> status.hmirror);
- break;
- case 'S': // Start stream.
- stopStream = false;
- break;
- case 's': // Stop stream.
- stopStream = true;
- break;
- case '0': // Use Floyd Steinberg dithering.
- ditherAlgorithm = FLOYD_STEINBERG;
- break;
- case '1': // Use Jarvis Judice dithering.
- ditherAlgorithm = JARVIS_JUDICE_NINKE;
- break;
- case '2': // Use Stucki dithering.
- ditherAlgorithm = STUCKI;
- break;
- default:
- // Do nothing.
- break;
- }
- }
- }
- void initializeCamera() {
- // Set initial camera configurations for grayscale.
- 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_GRAYSCALE;
- config.frame_size = FRAMESIZE_QQVGA;
- config.fb_count = 1;
- // Initialize camera.
- esp_err_t err = esp_camera_init(&config);
- if (err != ESP_OK) {
- return;
- }
- // Make sure torch starts as off.
- pinMode(FLASH_GPIO_NUM, OUTPUT);
- digitalWrite(FLASH_GPIO_NUM, LOW);
- // Set initial contrast.
- sensor_t * s = esp_camera_sensor_get();
- s -> set_contrast(s, 0);
- // Set rotation
- s -> set_vflip(s, true); // Vertical flip
- s -> set_hmirror(s, true); // Horizontal mirror
- }
- void processImage(camera_fb_t * frame_buffer) {
- // If dithering is not disabled, perform dithering on the image. Dithering is the
- // process of approximating the look of a high-resolution grayscale image in a
- // lower resolution by binary values (black & white), thereby representing
- // different shades of gray.
- if (!disableDithering) {
- ditherImage(frame_buffer); // Invokes the dithering process on the frame buffer
- }
- uint8_t flipper_y = 0;
- // Iterating over specific rows of the frame buffer.
- for (uint8_t y = 28; y < 92; ++y) {
- Serial.print("Y:"); // Print "Y:" for every new row.
- Serial.write(flipper_y); // Send the row identifier as a byte.
- // Calculate the actual y index in the frame buffer 1D array by multiplying the
- // y value with the width of the frame buffer. This gives the starting index
- // of the row in the 1D array.
- size_t true_y = y * frame_buffer -> width;
- // Iterating over specific columns of each row in the frame buffer.
- for (uint8_t x = 16; x < 144; x += 8) { // step by 8 as we're packing 8 pixels per byte
- uint8_t packed_pixels = 0;
- // Packing 8 pixel values into one byte.
- for (uint8_t bit = 0; bit < 8; ++bit) {
- // Check the invert flag and pack the pixels accordingly.
- if (invert) {
- // If invert is true, consider pixel as 1 if it's more than 127.
- if (frame_buffer -> buf[true_y + x + bit] > 127) {
- packed_pixels |= (1 << (7 - bit));
- }
- } else {
- // If invert is false, consider pixel as 1 if it's less than 127.
- if (frame_buffer -> buf[true_y + x + bit] < 127) {
- packed_pixels |= (1 << (7 - bit));
- }
- }
- }
- Serial.write(packed_pixels); // Sending packed pixel byte.
- }
- ++flipper_y; // Move to the next row.
- Serial.flush(); // Ensure all data in the Serial buffer is sent before moving to the next iteration.
- }
- }
- void ditherImage(camera_fb_t* frame_buffer) {
- for (uint8_t y = 0; y < frame_buffer->height; ++y) {
- for (uint8_t x = 0; x < frame_buffer->width; ++x) {
- size_t current = (y * frame_buffer->width) + x;
- uint8_t oldpixel = frame_buffer->buf[current];
- uint8_t newpixel = oldpixel >= 128 ? 255 : 0;
- frame_buffer->buf[current] = newpixel;
- int8_t quant_error = oldpixel - newpixel;
- // Apply error diffusion based on the selected algorithm
- switch (ditherAlgorithm) {
- case JARVIS_JUDICE_NINKE:
- frame_buffer->buf[(y * frame_buffer->width) + x + 1] += quant_error * 7 / 48;
- frame_buffer->buf[(y * frame_buffer->width) + x + 2] += quant_error * 5 / 48;
- frame_buffer->buf[(y + 1) * frame_buffer->width + x - 2] += quant_error * 3 / 48;
- frame_buffer->buf[(y + 1) * frame_buffer->width + x - 1] += quant_error * 5 / 48;
- frame_buffer->buf[(y + 1) * frame_buffer->width + x] += quant_error * 7 / 48;
- frame_buffer->buf[(y + 1) * frame_buffer->width + x + 1] += quant_error * 5 / 48;
- frame_buffer->buf[(y + 1) * frame_buffer->width + x + 2] += quant_error * 3 / 48;
- frame_buffer->buf[(y + 2) * frame_buffer->width + x - 2] += quant_error * 1 / 48;
- frame_buffer->buf[(y + 2) * frame_buffer->width + x - 1] += quant_error * 3 / 48;
- frame_buffer->buf[(y + 2) * frame_buffer->width + x] += quant_error * 5 / 48;
- frame_buffer->buf[(y + 2) * frame_buffer->width + x + 1] += quant_error * 3 / 48;
- frame_buffer->buf[(y + 2) * frame_buffer->width + x + 2] += quant_error * 1 / 48;
- break;
- case STUCKI:
- frame_buffer->buf[(y * frame_buffer->width) + x + 1] += quant_error * 8 / 42;
- frame_buffer->buf[(y * frame_buffer->width) + x + 2] += quant_error * 4 / 42;
- frame_buffer->buf[(y + 1) * frame_buffer->width + x - 2] += quant_error * 2 / 42;
- frame_buffer->buf[(y + 1) * frame_buffer->width + x - 1] += quant_error * 4 / 42;
- frame_buffer->buf[(y + 1) * frame_buffer->width + x] += quant_error * 8 / 42;
- frame_buffer->buf[(y + 1) * frame_buffer->width + x + 1] += quant_error * 4 / 42;
- frame_buffer->buf[(y + 1) * frame_buffer->width + x + 2] += quant_error * 2 / 42;
- frame_buffer->buf[(y + 2) * frame_buffer->width + x - 2] += quant_error * 1 / 42;
- frame_buffer->buf[(y + 2) * frame_buffer->width + x - 1] += quant_error * 2 / 42;
- frame_buffer->buf[(y + 2) * frame_buffer->width + x] += quant_error * 4 / 42;
- frame_buffer->buf[(y + 2) * frame_buffer->width + x + 1] += quant_error * 2 / 42;
- frame_buffer->buf[(y + 2) * frame_buffer->width + x + 2] += quant_error * 1 / 42;
- break;
- case FLOYD_STEINBERG:
- default:
- // Default to Floyd-Steinberg dithering if an invalid algorithm is selected
- frame_buffer->buf[(y * frame_buffer->width) + x + 1] += quant_error * 7 / 16;
- frame_buffer->buf[(y + 1) * frame_buffer->width + x - 1] += quant_error * 3 / 16;
- frame_buffer->buf[(y + 1) * frame_buffer->width + x] += quant_error * 5 / 16;
- frame_buffer->buf[(y + 1) * frame_buffer->width + x + 1] += quant_error * 1 / 16;
- break;
- }
- }
- }
- }
- void takePicture() {
- // Get camera sensor
- sensor_t * s = esp_camera_sensor_get();
-
- // Check if the sensor is valid
- if(!s) {
- Serial.println("Failed to acquire camera sensor");
- return;
- }
- // Set pixel format to JPEG for saving picture
- s->set_pixformat(s, PIXFORMAT_JPEG);
- // Set frame size based on available PSRAM
- if (psramFound()) {
- s->set_framesize(s, FRAMESIZE_UXGA);
- } else {
- s->set_framesize(s, FRAMESIZE_SVGA);
- }
- // Get a frame buffer from camera
- camera_fb_t * frame_buffer = esp_camera_fb_get();
- if (!frame_buffer) {
- Serial.println("Camera capture failed");
- return;
- }
- // Save the picture to SD card
- savePictureToSDCard(frame_buffer);
- }
- void savePictureToSDCard(camera_fb_t * frame_buffer) {
- if (!SD_MMC.begin()) {
- // SD Card Mount Failed.
- Serial.println("SD Card Mount Failed");
- esp_camera_fb_return(frame_buffer);
- return;
- }
- // Generate a unique filename
- String path = "/picture";
- path += String(millis());
- path += ".jpg";
- fs::FS & fs = SD_MMC;
- File file = fs.open(path.c_str(), FILE_WRITE);
- if (!file) {
- Serial.println("Failed to open file in writing mode");
- } else {
- if(file.write(frame_buffer->buf, frame_buffer->len) != frame_buffer->len) {
- Serial.println("Failed to write the image to the file");
- }
- file.close(); // Close the file in any case.
- }
- // Return the frame buffer back to the camera driver.
- esp_camera_fb_return(frame_buffer);
- }
|