惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

F
Full Disclosure
WordPress大学
WordPress大学
小众软件
小众软件
Cloudbric
Cloudbric
AWS News Blog
AWS News Blog
腾讯CDC
量子位
人人都是产品经理
人人都是产品经理
大猫的无限游戏
大猫的无限游戏
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
V
Vulnerabilities – Threatpost
Scott Helme
Scott Helme
Hugging Face - Blog
Hugging Face - Blog
博客园_首页
C
CXSECURITY Database RSS Feed - CXSecurity.com
The Hacker News
The Hacker News
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
IT之家
IT之家
Jina AI
Jina AI
Attack and Defense Labs
Attack and Defense Labs
S
SegmentFault 最新的问题
Simon Willison's Weblog
Simon Willison's Weblog
The Cloudflare Blog
阮一峰的网络日志
阮一峰的网络日志
T
Tailwind CSS Blog
Last Week in AI
Last Week in AI
博客园 - 【当耐特】
Google Online Security Blog
Google Online Security Blog
美团技术团队
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
V
Visual Studio Blog
罗磊的独立博客
L
LINUX DO - 最新话题
博客园 - Franky
博客园 - 叶小钗
Apple Machine Learning Research
Apple Machine Learning Research
The Last Watchdog
The Last Watchdog
J
Java Code Geeks
AI
AI
C
Cisco Blogs
酷 壳 – CoolShell
酷 壳 – CoolShell
C
Cyber Attacks, Cyber Crime and Cyber Security
Cisco Talos Blog
Cisco Talos Blog
博客园 - 三生石上(FineUI控件)
雷峰网
雷峰网
Help Net Security
Help Net Security
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
云风的 BLOG
云风的 BLOG
I
Intezer
S
Securelist

Simply Explained

Converting a Tuya Thermostat to ESPHome Bringing Foam Monsters to Life: How I Wrote and Illustrated a Children's Book Using AI How I Built an NFC Movie Library for my Kids Analyzing Link Rot in My Newsletter (After 31 Editions) How I Use Alfred to Search My Obsidian Notes Faster (with Spotlight!) Year in review: 2022 Smart lights behind a wall switch (Shelly, Z-Wave, ESPHome) Serverless Anagram Solver with Cloudflare R2 and Pages Integrate Home Assistant with Apple Reminders How WebP Images Reduced My Bandwidth Usage by 50% Tracking gas usage with ESPHome, Home Assistant, and TCRT5000 My Sixth Year as YouTube Creator (statistics + retrospective) EZStore: a tiny serverless datastore for IoT data (DynamoDB + Lambda) ESP-IDF: Storing AWS IoT certificates in the NVS partition (for OTA) How to securely access your home network with Cloudflare Tunnel and WARP I Built a CO2 Sensor and It Terrifies Me Filtering spam on YouTube with TensorFlow & AI Building a killer NAS with an old Rackable Server How I Structure My ESPHome Config Files Howto Virtualize Unraid on a Proxmox host MAX17043: Battery Monitoring Done Right (Arduino & ESP32) Preventing Cumulative Layout Shifts with lazy loaded images (Eleventy + markdown-it) Migrating This Blog From Jekyll to Eleventy Good Home Automation Should be Boring Retrospective: My Fifth Year on YouTube Secure Home Assistant Access with Cloudflare and Ubiquiti Dream Machine Shelly 2.5 + ESPHome: potential fire hazard + fix Impact of Adblockers on Google Analytics (vs. Plausible) Shelly 2.5: Flash ESPHome Over The Air! Tuya IR Hub: control Daikin AC (Home Assistant + ESPHome) Building Air Quality Sensor: Luftdaten + Home Assistant HEIC to JPG: Build a Quick Action with Automator Make Your Garage Door Opener Smart: Shelly 1, ESPHome and Home Assistant Static webhosting benchmark: AWS, Google, Firebase, Netlify, GitHub & Cloudflare Why I don't take sponsorships Monitoring my 3D printer with a Pi Zero, Home Assistant and TinyCore Linux ESP32: Keep WiFi connection alive with a FreeRTOS task Home Energy Monitor: V2 Retrospective: 4 years on YouTube
ESP32 Cam: cropping images on device
Xavier Decuy · 2021-02-23 · via Simply Explained

The ESP32 camera is the cheapest microcontroller with a built-in camera that you can buy. The OV2640 sensor has a max resolution of 1600x1200, but sometimes you don't need the entire image.

In this post I'll show how to crop the images on the ESP32 itself, before sending it of to your preferred (cloud) IoT service.

Step 1: Configuring the camera

Most code examples for the ESP32 cam use JPG encoding when taking pictures. This makes sense as it compresses the image.

However, for cropping, we need to capture in a raw format. I changed the camera configuration so that the pixel_format is set to RGB565:

static camera_config_t camera_config = {
    // ... other parameters

    // Set the pixel format to RGB656. This is a RAW format where each pixel
    // is represented by 2 bytes. Be aware of high memory usage!
    .pixel_format = PIXFORMAT_RGB565,
    .frame_size = FRAMESIZE_SXGA,
    .fb_count = 1 
};

Be aware: RGB565 uses 2 bytes for each pixel. At SXGA resolution, you'll be using 2.6MB of PSRAM (1280x1024x2).

Most ESP32 cam boards have 4MB of PSRAM, which should be sufficient for this resolution. If you need higher resolutions, you'll need a board with more PSRAM.

Step 2: create the crop function

I then created a crop function that takes your frame buffer as input, along with your cropping parameters.

Visualization of the crop parameters.

It will then crop your image, store the resulting image back in your frame buffer fb, and change the width, height, and len of it.

void crop_image(camera_fb_t *fb, unsigned short cropLeft, unsigned short cropRight, unsigned short cropTop, unsigned short cropBottom)
{
    unsigned int maxTopIndex = cropTop * fb->width * 2;
    unsigned int minBottomIndex = ((fb->width*fb->height) - (cropBottom * fb->width)) * 2;
    unsigned short maxX = fb->width - cropRight; // In pixels
    unsigned short newWidth = fb->width - cropLeft - cropRight;
    unsigned short newHeight = fb->height - cropTop - cropBottom;
    size_t newJpgSize = newWidth * newHeight *2;

    unsigned int writeIndex = 0;
    // Loop over all bytes
    for(int i = 0; i < fb->len; i+=2){
        // Calculate current X, Y pixel position
        int x = (i/2) % fb->width;

        // Crop from the top
        if(i < maxTopIndex){ continue; }

        // Crop from the bottom
        if(i > minBottomIndex){ continue; }

        // Crop from the left
        if(x <= cropLeft){ continue; }

        // Crop from the right
        if(x > maxX){ continue; }

        // If we get here, keep the pixels
        fb->buf[writeIndex++] = fb->buf[i];
        fb->buf[writeIndex++] = fb->buf[i+1];
    }

    // Set the new dimensions of the framebuffer for further use.
    fb->width = newWidth;
    fb->height = newHeight;
    fb->len = newJpgSize;
}

Step 3: Take a picture, crop it and convert it to JPG

To use the crop function, you must first take a picture. This is the standard ESP32 camera code:

// Take picture
printf("Taking picture...\n");
camera_fb_t *fb = esp_camera_fb_get();
if(!fb) {
    printf("Could not take picture \n");
    // Handle error
}

printf("Picture taken: %d bytes \n", fb->len);

When successful, you can pass the frame buffer fb to the crop function and give it the desired dimensions:

// Crop image (frame buffer, cropLeft, cropRight, cropTop, cropBottom)
crop_image(fb, 550, 450, 100, 190);

At this stage, the frame buffer will contain the cropped image in RGB565 format. To convert it back to JPG, we need to allocate a new buffer in PSRAM. In this case, I allocate 200k. Then, you can use the fmt2jpg function to convert the RAW format into JPG.

// Create a buffer for the JPG in psram
uint8_t * jpg_buf = (uint8_t *) heap_caps_malloc(200000, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);

if(jpg_buf == NULL){
    printf("Malloc failed to allocate buffer for JPG.\n");
}else{
    size_t jpg_size = 0;

    // Convert the RAW image into JPG
    // The parameter "31" is the JPG quality. Higher is better.
    fmt2jpg(fb->buf, fb->len, fb->width, fb->height, fb->format, 31, &jpg_buf, &jpg_size);
    printf("Converted JPG size: %d bytes \n", jpg_size);
}

At this point, the cropped JPG is available in jpg_buf, and its size is stored in jpg_size (bytes). If you want, you can now release the original frame buffer to free up some memory:

esp_camera_fb_return(fb);

How images are represented

You might be interested how the cropping algorithm works. First, you must understand how images are represented in the ESP32's memory.

Visually, we see an image like this:

Each pixel of the image is laid on a grid that's determined by the width and height of the image.

However, inside the microcontroller's memory, an image is nothing more than a single-dimension array. All pixels are in a single list, one after the other:

To crop the image, we need to figure out which pixels to keep and which to skip. You can determine this by using the image's width, height, and a series of modulo operations (see the algorithm).

So this cropping area:

translates to these pixels in memory:

Now that we figured out which pixels we want to keep, we need a place to store them. You could allocate a new buffer for this, but that's wasteful and might not be possible. In this case, my RGB565 photo takes up 2.3MB of memory. If I want to crop out just a few pixels from each side, I would need another buffer of 2.3MB. That would exhaust all of the PSRAM that most ESP32's have (4MB).

Luckily, we can re-use our existing frame buffer! While looping over all pixel data, you check if you want to keep the pixel or not. If you want to keep it, insert it back into the beginning of our frame buffer, overwriting the original pixel that was in there.

This solution requires no additional PSRAM memory. Yay!

Just remember to count how many bytes you've carried over and change fb->len accordingly. Otherwise, part of the old image would still be there.

VSYNC errors

Capturing RAW images with high resolution could lead to a VSYNC error:

Timeout waiting for VSYNC

The esp32-camera component is to blame. It has a fixed timeout for moving image data from the camera sensor to the PSRAM. You'll run into this timeout when taking large, RAW photos.

If you're using ESP-IDF (with or without the Arduino component), you can change this timeout.

Edit the file esp32-camera/driver/camera.c and locate line 198. You should see three timeout blocks like this:

if((esp_timer_get_time() - st_t) > 1000000LL){
   goto timeout;
}

Change all 3 timeouts to a bigger value. For SXGA resolution, a 50% increase will do:

if((esp_timer_get_time() - st_t) > 1500000LL){
   goto timeout;
}

Flash the program to your board, and the VSYNC error will be gone!