Data from ESP32-CAM do not make sense

I have been reading this forum trying to understand how to apply it to my project. However, I still am not getting data that makes sense. Here is what I would like to accomplish:

  1. Take a picture with ESP32-CAM
  2. Save the picture to an SD Card as a .jpg
  3. Output the frame buffer to the Serial Monitor
  4. Clear the frame buffer

I have been using config.pixformat_grayscale so that I would get 1 decimal number per pixel (ranging from 0-255 with 255 being white and 0 being black). However, the decimal values output to the serial monitor do not make sense. Can you help?

I have copied my code below for reference:

#include "esp_camera.h"
#include "Arduino.h"
#include "FS.h"                // SD Card ESP32
#include "SD_MMC.h"            // SD Card ESP32
#include "soc/soc.h"           // Disable brownour problems
#include "soc/rtc_cntl_reg.h"  // Disable brownour problems
#include "driver/rtc_io.h"
#include <EEPROM.h>            // read and write from flash memory

// define the number of bytes you want to access
#define EEPROM_SIZE 1

// Pin definition for CAMERA_MODEL_AI_THINKER
#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

int pictureNumber = 0;

void setup() {
  WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable brownout detector
 
  Serial.begin(115200);
  //Serial.setDebugOutput(true);
  //Serial.println();
  
  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_GRAYSCALE; 
  
  if(psramFound()){
    config.frame_size = FRAMESIZE_QVGA; // FRAMESIZE_ + QVGA|CIF|VGA|SVGA|XGA|SXGA|UXGA
    config.jpeg_quality = 10;
    config.fb_count = 2;
  } else {
    config.frame_size = FRAMESIZE_QVGA;
    config.jpeg_quality = 12;
    config.fb_count = 1;
  }
  
  // Init Camera
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("Camera init failed with error 0x%x", err);
    return;
  }

  sensor_t * s = esp_camera_sensor_get();
s->set_brightness(s, 0);     // -2 to 2
s->set_contrast(s, 0);       // -2 to 2
s->set_saturation(s, 0);     // -2 to 2
s->set_special_effect(s, 2); // 0 to 6 (0 - No Effect, 1 - Negative, 2 - Grayscale, 3 - Red Tint, 4 - Green Tint, 5 - Blue Tint, 6 - Sepia)
s->set_whitebal(s, 1);       // 0 = disable , 1 = enable
s->set_awb_gain(s, 1);       // 0 = disable , 1 = enable
s->set_wb_mode(s, 0);        // 0 to 4 - if awb_gain enabled (0 - Auto, 1 - Sunny, 2 - Cloudy, 3 - Office, 4 - Home)
s->set_exposure_ctrl(s, 1);  // 0 = disable , 1 = enable
s->set_aec2(s, 0);           // 0 = disable , 1 = enable
s->set_ae_level(s, 0);       // -2 to 2
s->set_aec_value(s, 300);    // 0 to 1200
s->set_gain_ctrl(s, 1);      // 0 = disable , 1 = enable
s->set_agc_gain(s, 0);       // 0 to 30
s->set_gainceiling(s, (gainceiling_t)0);  // 0 to 6
s->set_bpc(s, 0);            // 0 = disable , 1 = enable
s->set_wpc(s, 1);            // 0 = disable , 1 = enable
s->set_raw_gma(s, 1);        // 0 = disable , 1 = enable
s->set_lenc(s, 1);           // 0 = disable , 1 = enable
s->set_hmirror(s, 0);        // 0 = disable , 1 = enable
s->set_vflip(s, 0);          // 0 = disable , 1 = enable
s->set_dcw(s, 1);            // 0 = disable , 1 = enable
s->set_colorbar(s, 0);       // 0 = disable , 1 = enable
  
  //Serial.println("Starting SD Card");
  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;
  }
    
  camera_fb_t * fb = NULL;
  
  // Take Picture with Camera
  fb = esp_camera_fb_get();  
  if(!fb) {
    Serial.println("Camera capture failed");
    return;
  }
  // initialize EEPROM with predefined size
  EEPROM.begin(EEPROM_SIZE);
  pictureNumber = EEPROM.read(0) + 1;

  // Path where new picture will be saved in SD Card
  String path = "/picture" + String(pictureNumber) +".jpg";

  fs::FS &fs = SD_MMC; 
  Serial.printf("Picture file name: %s\n", path.c_str());
  
  File file = fs.open(path.c_str(), FILE_WRITE);
  if(!file){
    Serial.println("Failed to open file in writing mode");
  } 
  else {
    file.write(fb->buf, fb->len); // payload (image), payload length
    Serial.printf("Saved file to path: %s\n", path.c_str());
    EEPROM.write(0, pictureNumber);
    EEPROM.commit();
  }
  file.close();

    for (int i = 0; i < fb->len; i++) {
        Serial.print (fb->buf[i], DEC);
        Serial.write (',');
    }
    Serial.println ();
  
  esp_camera_fb_return(fb); 
  
  // Turns off the ESP32-CAM white on-board LED (flash) connected to GPIO 4
  pinMode(4, OUTPUT);
  digitalWrite(4, LOW);
  rtc_gpio_hold_en(GPIO_NUM_4);
  
  delay(2000);
  Serial.println("Going to sleep now");
  delay(2000);
  esp_deep_sleep_start();
  Serial.println("This will never be printed");
}

void loop() {
  
}

Why exactly ?

The picture array can be quite large and is binary data, so what are you going to use to receive it ?

It is mostly for debugging purposes. I would like to output it so I can visually see the pixel values and then correlate them to the image to ensure they are correct. I am using a low resolution image to ensure a lot of data will not be written to the serial monitor. (320x240)

Why not? Give examples.

I took an image of a black and white checkerboard. The decimal values for each pixel did not correspond correctly with the color of the image. Black pixels on the image were corresponding to a 255 pixel value.

If white pixels are about zero, no problem.

There was no consistency. Black pixels were not always 255 and white pixels were not always 0.

Here is the pixel data that was output (sorry it is so long):
107,110,122,140,146,203,223,243,255,255,253,254,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,237,231,141,136,131,123,123,121,118,117,115,115,115,106,106,104,107,109,103,100,97,100,100,100,100,99,91,92,93,89,89,89,90,91,89,89,88,87,87,86,83,81,80,76,72,78,78,77,74,72,78,74,70,65,67,74,72,71,70,68,65,113,113,110,121,184,230,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,157,106,123,114,110,113,108,105,101,107,109,98,94,96,97,94,84,85,82,75,79,77,73,75,84,75,80,75,74,71,67,65,66,66,71,70,59,54,51,48,42,59,51,46,60,63,62,64,65,64,64,66,68,68,63,71,73,67,70,69,117,112,101,130,221,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,238,248,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,221,215,216,217,215,211,209,207,207,199,200,197,198,201,198,188,184,177,175,171,163,158,155,155,157,146,143,140,143,139,128,118,114,112,112,116,101,106,109,102,104,104,93,89,89,90,85,69,58,54,58,56,56,55,54,54,56,48,64,77,114,110,101,143,253,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,137,143,212,231,234,235,234,234,236,236,236,234,235,237,238,239,236,236,235,236,236,236,238,239,241,244,249,252,252,252,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,253,248,248,245,246,242,239,237,236,235,233,230,227,225,227,228,225,224,217,213,202,195,186,186,181,172,163,160,153,151,144,138,136,135,132,129,129,113,101,102,97,93,93,92,107,110,111,98,90,143,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,102,42,104,154,180,181,177,172,180,180,176,165,163,167,171,171,165,168,169,169,168,170,177,179,176,174,172,174,173,165,163,160,160,162,162,160,163,160,162,159,160,162,165,169,168,166,168,171,179,182,185,182,181,178,178,185,185,185,185,209,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,254,254,254,253,252,250,249,248,247,246,244,245,244,243,242,241,237,228,224,214,211,206,191,188,182,176,172,179,166,155,113,95,88,142,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,117,101,120,127,127,127,131,133,129,139,142,151,149,149,144,142,148,151,153,155,153,150,144,144,149,151,156,155,156,159,159,160,161,161,162,164,166,169,168,164,163,162,162,162,163,168,169,172,169,170,171,174,169,167,161,164,165,166,139,188,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,230,142,190,194,201,199,195,193,200,195,197,198,204,209,208,207,206,209,213,214,214,216,218,221,220,221,222,230,228,227,225,230,242,245,245,245,244,244,245,245,245,245,246,246,246,246,246,245,246,246,246,246,246,247,247,247,248,248,247,242,245,255,255,255,255

The other issue is that the image saved to the SD card is unreadable by my computer.

Sounds like you have more than one fundamental problem. I've asked the moderator to split this off into a new thread, where it should have been in the first place.

Thanks, I need all the help I can get

TOPIC SPLIT
PLEASE DO NOT HIJACK / NECRO POST !

Could you also take a few moments to Learn How To Use The Forum.

Other general help and troubleshooting advice can be found here.

It will help you get the best out of the forum in the future.

I've used this simple hack to take a grey scale image from the ESP32-CAM, dump it to a PC via serial and reconstruct it on the PC as a .BMP image file, so I know the entire process works as expected.

The first line of the code below shows where to find the original source code.

// from https://github.com/alanesq/esp32cam-demo/blob/master/Misc/esp32camdemo-greyscale.ino
#if !defined ESP32
#error This sketch is only for an ESP32Cam module
#endif

#include "esp_camera.h"       // https://github.com/espressif/esp32-camera
// #include "camera_pins.h"

// ---------------------------------------------------------------
//                           -SETTINGS
// ---------------------------------------------------------------

const char* stitle = "ESP32Cam-demo-gs";               // title of this sketch
const char* sversion = "10Jul21";                      // Sketch version

// Camera related
const bool flashRequired = 1;                        // If flash to be used when capturing image (1 = yes)
const framesize_t FRAME_SIZE_IMAGE = FRAMESIZE_QQVGA;// Image resolution:
//               default = "const framesize_t FRAME_SIZE_IMAGE = FRAMESIZE_VGA"
//               160x120 (QQVGA), 128x160 (QQVGA2), 176x144 (QCIF), 240x176 (HQVGA),
//               320x240 (QVGA), 400x296 (CIF), 640x480 (VGA, default), 800x600 (SVGA),
//               1024x768 (XGA), 1280x1024 (SXGA), 1600x1200 (UXGA)
#define PIXFORMAT PIXFORMAT_GRAYSCALE;               // image format, Options =  YUV422, GRAYSCALE, RGB565, JPEG, RGB888                                                         
#define WIDTH 160                                    // image size
#define HEIGHT 120

int cameraImageExposure = 0;                         // Camera exposure (0 - 1200)   If gain and exposure both set to zero then auto adjust is enabled
int cameraImageGain = 0;                             // Image gain (0 - 30)

const int TimeBetweenStatus = 600;                     // speed of flashing system running ok status light (milliseconds)

const int indicatorLED = 33;                           // onboard small LED pin (33)

const int brightLED = 4;                               // onboard Illumination/flash LED pin (4)

const int iopinA = 13;                                 // general io pin 13
const int iopinB = 12;                                 // general io pin 12 (must not be high at boot)
const int iopinC = 16;                                 // input only pin 16 (used by PSRam but you may get away with using it for a button)

const int serialSpeed = 115200;                        // Serial data speed to use


// camera settings (for the standard - OV2640 - CAMERA_MODEL_AI_THINKER)
// see: https://randomnerdtutorials.com/esp32-cam-camera-pin-gpios/
// set camera resolution etc. in 'initialiseCamera()' and 'cameraImageSettings()'
#define CAMERA_MODEL_AI_THINKER
#define PWDN_GPIO_NUM     32      // power to camera (on/off)
#define RESET_GPIO_NUM    -1      // -1 = not used
#define XCLK_GPIO_NUM      0
#define SIOD_GPIO_NUM     26      // i2c sda
#define SIOC_GPIO_NUM     27      // i2c scl
#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      // vsync_pin
#define HREF_GPIO_NUM     23      // href_pin
#define PCLK_GPIO_NUM     22      // pixel_clock_pin



// ******************************************************************************************************************

#include "driver/ledc.h"      // used to configure pwm on illumination led

// Used to disable brownout detection
#include "soc/soc.h"
#include "soc/rtc_cntl_reg.h"

// sd-card
#include "SD_MMC.h"                         // sd card - see https://randomnerdtutorials.com/esp32-cam-take-photo-save-microsd-card/
#include <SPI.h>
#include <FS.h>                             // gives file access 
#define SD_CS 5                             // sd chip select pin = 5

// Define some global variables:
uint32_t lastStatus = millis();           // last time status light changed status (to flash all ok led)
uint32_t lastCamera = millis();           // timer for periodic image capture
bool sdcardPresent;                       // flag if an sd card is detected
int imageCounter;                         // image file name on sd card counter
uint32_t illuminationLEDstatus;           // current brightness setting of the illumination led


void setup() {

  Serial.begin(serialSpeed);                     // Start serial communication

  Serial.println("\n");                      // line feeds
  Serial.printf("Starting - %s - %s \n", stitle, sversion);
  // Serial.print("Reset reason: " + ESP.getResetReason());


  WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0);     // Turn-off the 'brownout detector'

  // small indicator led on rear of esp32cam board
  pinMode(indicatorLED, OUTPUT);

  digitalWrite(indicatorLED, LOW);              // small indicator led on
  delay(1000);
  digitalWrite(indicatorLED, HIGH);             // small indicator led off

  // set up camera
  Serial.print(("\nInitialising camera: "));
  if (initialiseCamera()) {
    Serial.println("OK");
  }
  else {
    Serial.println("Error!");
  }

  // define i/o pins
  pinMode(indicatorLED, OUTPUT);            // defined again as sd card config can reset it
  digitalWrite(indicatorLED, HIGH);         // led off = High
  pinMode(iopinA, OUTPUT);                  // pin 13 - free io pin, can be used for input or output
  pinMode(iopinB, OUTPUT);                  // pin 12 - free io pin, can be used for input or output (must not be high at boot)
  pinMode(iopinC, INPUT);                   // pin 16 - free input only pin

  // startup complete
  Serial.println("\nSetup complete...");

}  // setup

void loop() {

  //  //  demo to Capture an image 
  if ( (unsigned long)(millis() - lastCamera) >= 10000UL) { // ever 10 sec
    lastCamera = millis();     // reset timer
    capture_still();              // collect and send image out serial port
  }
  // flash status LED to show sketch is running ok
  if ((unsigned long)(millis() - lastStatus) >= TimeBetweenStatus) {
    lastStatus = millis();                                               // reset timer
    digitalWrite(indicatorLED, !digitalRead(indicatorLED));              // flip indicator led status
  }

}  // loop

// ----------------------------------------------------------------
//                        Initialise the camera
// ----------------------------------------------------------------
// returns TRUE if successful

bool initialiseCamera() {

  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;               // XCLK 20MHz or 10MHz for OV2640 double FPS (Experimental)
  config.pixel_format = PIXFORMAT;              // Options =  YUV422, GRAYSCALE, RGB565, JPEG, RGB888
  config.frame_size = FRAME_SIZE_IMAGE;         // Image sizes: 160x120 (QQVGA), 128x160 (QQVGA2), 176x144 (QCIF), 240x176 (HQVGA), 320x240 (QVGA),
  //              400x296 (CIF), 640x480 (VGA, default), 800x600 (SVGA), 1024x768 (XGA), 1280x1024 (SXGA),
  //              1600x1200 (UXGA)
  config.jpeg_quality = 10;                     // 0-63 lower number means higher quality
  config.fb_count = 1;                          // if more than one, i2s runs in continuous mode. Use only with JPEG

  // check the esp32cam board has a psram chip installed (extra memory used for storing captured images)
  //    Note: if not using "AI thinker esp32 cam" in the Arduino IDE, PSRAM must be enabled
  if (!psramFound()) {
    Serial.println("Warning: No PSRam found so defaulting to image size 'CIF'");
    config.frame_size = FRAMESIZE_CIF;
  }

  //#if defined(CAMERA_MODEL_ESP_EYE)
  //  pinMode(13, INPUT_PULLUP);
  //  pinMode(14, INPUT_PULLUP);
  //#endif

  esp_err_t camerr = esp_camera_init(&config);  // initialise the camera
  if (camerr != ESP_OK) {
    Serial.printf("ERROR: Camera init failed with error 0x%x", camerr);
  }

  cameraImageSettings();                        // apply custom camera settings

  return (camerr == ESP_OK);                    // return boolean result of camera initialisation
}

// ----------------------------------------------------------------
//                   -Change camera image settings
// ----------------------------------------------------------------
// Adjust image properties (brightness etc.)
// Defaults to auto adjustments if exposure and gain are both set to zero
// - Returns TRUE if successful
// BTW - some interesting info on exposure times here: https://github.com/raduprv/esp32-cam_ov2640-timelapse

bool cameraImageSettings() {

  sensor_t *s = esp_camera_sensor_get();
  // something to try?:     if (s->id.PID == OV3660_PID)
  if (s == NULL) {
    Serial.println("Error: problem reading camera sensor settings");
    return 0;
  }

  // if both set to zero enable auto adjust
  if (cameraImageExposure == 0 && cameraImageGain == 0) {
    // enable auto adjust
    s->set_gain_ctrl(s, 1);                       // auto gain on
    s->set_exposure_ctrl(s, 1);                   // auto exposure on
    s->set_awb_gain(s, 1);                        // Auto White Balance enable (0 or 1)
  } else {
    // Apply manual settings
    s->set_gain_ctrl(s, 0);                       // auto gain off
    s->set_awb_gain(s, 1);                        // Auto White Balance enable (0 or 1)
    s->set_exposure_ctrl(s, 0);                   // auto exposure off
    s->set_agc_gain(s, cameraImageGain);          // set gain manually (0 - 30)
    s->set_aec_value(s, cameraImageExposure);     // set exposure manually  (0-1200)
  }

  return 1;
}  // cameraImageSettings


//    // More camera settings available:
//    // If you enable gain_ctrl or exposure_ctrl it will prevent a lot of the other settings having any effect
//    // more info on settings here: https://randomnerdtutorials.com/esp32-cam-ov2640-camera-settings/
//    s->set_gain_ctrl(s, 0);                       // auto gain off (1 or 0)
//    s->set_exposure_ctrl(s, 0);                   // auto exposure off (1 or 0)
//    s->set_agc_gain(s, cameraImageGain);          // set gain manually (0 - 30)
//    s->set_aec_value(s, cameraImageExposure);     // set exposure manually  (0-1200)
//    s->set_vflip(s, cameraImageInvert);           // Invert image (0 or 1)
//    s->set_quality(s, 10);                        // (0 - 63)
//    s->set_gainceiling(s, GAINCEILING_32X);       // Image gain (GAINCEILING_x2, x4, x8, x16, x32, x64 or x128)
//    s->set_brightness(s, cameraImageBrightness);  // (-2 to 2) - set brightness
//    s->set_lenc(s, 1);                            // lens correction? (1 or 0)
//    s->set_saturation(s, 0);                      // (-2 to 2)
//    s->set_contrast(s, cameraImageContrast);      // (-2 to 2)
//    s->set_sharpness(s, 0);                       // (-2 to 2)
//    s->set_hmirror(s, 0);                         // (0 or 1) flip horizontally
//    s->set_colorbar(s, 0);                        // (0 or 1) - show a testcard
//    s->set_special_effect(s, 0);                  // (0 to 6?) apply special effect
//    s->set_whitebal(s, 0);                        // white balance enable (0 or 1)
//    s->set_awb_gain(s, 1);                        // Auto White Balance enable (0 or 1)
//    s->set_wb_mode(s, 0);                         // 0 to 4 - if awb_gain enabled (0 - Auto, 1 - Sunny, 2 - Cloudy, 3 - Office, 4 - Home)
//    s->set_dcw(s, 0);                             // downsize enable? (1 or 0)?
//    s->set_raw_gma(s, 1);                         // (1 or 0)
//    s->set_aec2(s, 0);                            // automatic exposure sensor?  (0 or 1)
//    s->set_ae_level(s, 0);                        // auto exposure levels (-2 to 2)
//    s->set_bpc(s, 0);                             // black pixel correction
//    s->set_wpc(s, 0);                             // white pixel correction



// ----------------------------------------------------------------
//      -access image as greyscale data - i.e. http://x.x.x.x/
// ----------------------------------------------------------------

bool capture_still() {
  uint16_t x, y;

  Serial.print("***** greyscale image dump\n");
  camera_fb_t *frame = esp_camera_fb_get();

  if (!frame)
    return false;

  int npix=0;
  // for each pixel in image
    
  for (size_t i = 0; i < frame->len; i++) {

    x = i % WIDTH;                   // x position in image
    y = floor(i / WIDTH);            // y position in image
    byte pixel = frame->buf[i];                     // pixel value

    // show data
    Serial.print((unsigned int)pixel);
    Serial.print("\n");
    npix++;
  }
  Serial.print("***** ");  //separator
  Serial.print(npix);
  Serial.print(" data lines\n");
  
  esp_camera_fb_return(frame);                      // return storage space
  return true;
}

Here is a snapshot produced on the PC in this way:
1-image

PSRAM variable declaration uses a 32bit memory address. If Bool is declared in PSRAM, then the entire 32 bit address is used to store the one bit.

API Reference - ESP32 - — ESP-IDF Programming Guide latest documentation (espressif.com)

Thank you so much! The code worked. Help me understand what is going on, please.

  1. What is the purpose of the x and y variables? I do not see them referenced anywhere else.
  2. Why do you declare "(unsigned int)" before pixel in the serial.print function?
    x = i % WIDTH;                   // x position in image
    y = floor(i / WIDTH);            // y position in image
    byte pixel = frame->buf[i];                     // pixel value

    // show data
    Serial.print((unsigned int)pixel);

I am not sure how this applies to the project. Can you explain a little more?

They are not used. Left over from someone else's project. This is just a crude hack.

The "(unsigned int)" cast makes sure that the byte value is treated as an unsigned integer. I don't recall whether the cast is required, but in some cases a value like "255" will be sign-extended and interpreted as -1.

That makes sense. Thanks for the help!