| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351 |
- #include "esp_camera.h"
- #include "FS.h"
- #include "SD_MMC.h"
- // 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(camera_fb_t * fb);
- void initializeCamera();
- void processImage(camera_fb_t * fb);
- void ditherImage(camera_fb_t * fb);
- void saveFrameBufferToSDCard(camera_fb_t * fb);
- // Enumeration to represent the available dithering algorithms.
- enum DitheringAlgorithm {
- 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.
- bool isFlashOn = 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() {
- // Start serial communication at 230400 baud rate.
- Serial.begin(230400);
- initializeCamera();
- }
- void loop() {
- // Capture and process the frame buffer if streaming is enabled.
- camera_fb_t * fb = esp_camera_fb_get();
-
- if (!stopStream) {
-
- if (fb) {
- processImage(fb);
- // Return the frame buffer back to the camera driver.
- esp_camera_fb_return(fb);
- }
- // Delay for 10ms between each frame.
- delay(10);
- }
-
- // Handle any available serial input commands.
- handleSerialInput(fb);
- }
- void handleSerialInput(camera_fb_t * fb) {
- 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.
- if (!isFlashOn) {
- isFlashOn = true;
- // Set up the flash light control pin (number 4) as an "output"
- // so we can turn the torch ON and OFF.
- pinMode(FLASH_GPIO_NUM, OUTPUT);
- // Turn on torch.
- digitalWrite(FLASH_GPIO_NUM, HIGH);
- if (storeJpeg) {
- // Save jpeg image to sd card.
- saveFrameBufferToSDCard(fb);
- // Return the frame buffer back to the camera driver.
- esp_camera_fb_return(fb);
- }
- // Give some time for Flipper to save locally with flash on.
- delay(15);
- // Turn off torch.
- digitalWrite(FLASH_GPIO_NUM, LOW);
- isFlashOn = 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 camera configurations
- 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;
- if (isFlashOn) {
- pinMode(FLASH_GPIO_NUM, OUTPUT);
- // Turn off torch.
- digitalWrite(FLASH_GPIO_NUM, LOW);
- isFlashOn = false;
- }
- // Initialize camera
- esp_err_t err = esp_camera_init( & config);
- if (err != ESP_OK) {
- Serial.printf("Camera init failed with error 0x%x", err);
- return;
- }
- // 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 * frameBuffer) {
- // 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(frameBuffer); // 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 * frameBuffer -> 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 (frameBuffer -> 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 (frameBuffer -> 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 * fb) {
- for (uint8_t y = 0; y < fb -> height; ++y) {
- for (uint8_t x = 0; x < fb -> width; ++x) {
- size_t current = (y * fb -> width) + x;
- uint8_t oldpixel = fb -> buf[current];
- uint8_t newpixel = oldpixel >= 128 ? 255 : 0;
- fb -> buf[current] = newpixel;
- int8_t quant_error = oldpixel - newpixel;
- // Apply error diffusion based on the selected algorithm
- switch (ditherAlgorithm) {
- case JARVIS_JUDICE_NINKE:
- fb -> buf[(y * fb -> width) + x + 1] += quant_error * 7 / 48;
- fb -> buf[(y * fb -> width) + x + 2] += quant_error * 5 / 48;
- fb -> buf[(y + 1) * fb -> width + x - 2] += quant_error * 3 / 48;
- fb -> buf[(y + 1) * fb -> width + x - 1] += quant_error * 5 / 48;
- fb -> buf[(y + 1) * fb -> width + x] += quant_error * 7 / 48;
- fb -> buf[(y + 1) * fb -> width + x + 1] += quant_error * 5 / 48;
- fb -> buf[(y + 1) * fb -> width + x + 2] += quant_error * 3 / 48;
- fb -> buf[(y + 2) * fb -> width + x - 2] += quant_error * 1 / 48;
- fb -> buf[(y + 2) * fb -> width + x - 1] += quant_error * 3 / 48;
- fb -> buf[(y + 2) * fb -> width + x] += quant_error * 5 / 48;
- fb -> buf[(y + 2) * fb -> width + x + 1] += quant_error * 3 / 48;
- fb -> buf[(y + 2) * fb -> width + x + 2] += quant_error * 1 / 48;
- break;
- case STUCKI:
- fb -> buf[(y * fb -> width) + x + 1] += quant_error * 8 / 42;
- fb -> buf[(y * fb -> width) + x + 2] += quant_error * 4 / 42;
- fb -> buf[(y + 1) * fb -> width + x - 2] += quant_error * 2 / 42;
- fb -> buf[(y + 1) * fb -> width + x - 1] += quant_error * 4 / 42;
- fb -> buf[(y + 1) * fb -> width + x] += quant_error * 8 / 42;
- fb -> buf[(y + 1) * fb -> width + x + 1] += quant_error * 4 / 42;
- fb -> buf[(y + 1) * fb -> width + x + 2] += quant_error * 2 / 42;
- fb -> buf[(y + 2) * fb -> width + x - 2] += quant_error * 1 / 42;
- fb -> buf[(y + 2) * fb -> width + x - 1] += quant_error * 2 / 42;
- fb -> buf[(y + 2) * fb -> width + x] += quant_error * 4 / 42;
- fb -> buf[(y + 2) * fb -> width + x + 1] += quant_error * 2 / 42;
- fb -> buf[(y + 2) * fb -> width + x + 2] += quant_error * 1 / 42;
- break;
- case FLOYD_STEINBERG:
- default:
- // Default to Floyd-Steinberg dithering if an invalid algorithm is selected
- fb -> buf[(y * fb -> width) + x + 1] += quant_error * 7 / 16;
- fb -> buf[(y + 1) * fb -> width + x - 1] += quant_error * 3 / 16;
- fb -> buf[(y + 1) * fb -> width + x] += quant_error * 5 / 16;
- fb -> buf[(y + 1) * fb -> width + x + 1] += quant_error * 1 / 16;
- break;
- }
- }
- }
- }
- void saveFrameBufferToSDCard(camera_fb_t * fb) {
- if (!SD_MMC.begin()) {
- // Serial.println("SD Card Mount Failed");
- return;
- }
- uint8_t cardType = SD_MMC.cardType();
- if (cardType == CARD_NONE) {
- // Serial.println("No SD Card attached");
- return;
- }
- // Generate a unique filename
- String path = "/picture";
- path += String(millis());
- path += ".jpg";
- File file = SD_MMC.open(path.c_str(), FILE_WRITE);
- if (!file) {
- // Serial.println("Failed to open file for writing");
- return;
- }
- // Write frame buffer to file
- file.write(fb -> buf, fb -> len);
- // Serial.println("File written to SD card");
- file.close();
- }
|