Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ESP32.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:
- 'src/**'
- '.github/workflows/*.yml'
tags-ignore:
- '**'
- 'v*'
pull_request:
branches:
- master
Expand Down
37 changes: 27 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,14 +99,31 @@ cam.init()
### Capture image

```python
img = cam.capture() #capture image as configured
img_rgb888 = cam.capture(PixelFormat.RGB888) #capture image and convert it to RGB888
img = cam.capture()
```

Arguments for capture

- out_format: Output format as PixelFormat (optional)

### Convert image to another format

You can either convert the image with the `capture` method directly passing the desired output format:
```python
img_rgb888 = cam.capture(PixelFormat.RGB888) #capture image as configured (e.g. JPEG), convert it to RGB888 and return the converted image
```
Or you can first capture the image and then convert it to the desired PixelFormat with the `convert` method.
Doing so you can have both, the captured and the converted image. Note that more memory will be used.
```python
img = cam.capture()
img_rgb888 = cam.convert(PixelFormat.RGB888) #converts the last captured image to RGB888 and returns the converted image
```

Convertion supported
- from JPEG to RGB565
- to RGB888 in general
- to JPEG in gerenal (use the `set_quality` method to set the desired JPEG quality)

### Camera reconfiguration

```python
Expand Down Expand Up @@ -217,12 +234,12 @@ Example for Xiao sense:
#define MICROPY_CAMERA_PIN_XCLK (10)
#define MICROPY_CAMERA_PIN_PWDN (-1)
#define MICROPY_CAMERA_PIN_RESET (-1)
#define MICROPY_CAMERA_PIN_SIOD (40) // SDA
#define MICROPY_CAMERA_PIN_SIOC (39) // SCL
#define MICROPY_CAMERA_PIN_SIOD (40) // SDA
#define MICROPY_CAMERA_PIN_SIOC (39) // SCL
#define MICROPY_CAMERA_XCLK_FREQ (20000000) // Frequencies are normally either 10 MHz or 20 MHz
#define MICROPY_CAMERA_FB_COUNT (2) // The value is between 1 (slow) and 2 (fast, but more load on CPU and more ram usage)
#define MICROPY_CAMERA_JPEG_QUALITY (85) // Quality of JPEG output in percent. Higher means higher quality.
#define MICROPY_CAMERA_GRAB_MODE (1) // 0=WHEN_EMPTY (might have old data, but less resources), 1=LATEST (best, but more resources)
#define MICROPY_CAMERA_FB_COUNT (2) // The value is between 1 (slow) and 2 (fast, but more load on CPU and more ram usage)
#define MICROPY_CAMERA_JPEG_QUALITY (85) // Quality of JPEG output in percent. Higher means higher quality.
#define MICROPY_CAMERA_GRAB_MODE (1) // 0=WHEN_EMPTY (might have old data, but less resources), 1=LATEST (best, but more resources)

```
#### Customize additional camera settings
Expand Down Expand Up @@ -253,7 +270,7 @@ If you experience problems, visit [MicroPython external C modules](https://docs.

## FPS benchmark

I didn't use a calibrated osziloscope, but here is a benchmark with my ESP32S3 (GrabMode=LATEST, fb_count = 1, jpeg_quality=85%).
I didn't use a calibrated osziloscope, but here is a benchmark with my ESP32S3 (GrabMode=LATEST, fb_count = 1, jpeg_quality=85%) and OV2640.
Using fb_count=2 theoretically can double the FPS (see JPEG with fb_count=2). This might also aplly for other PixelFormats.

| Frame Size | GRAYSCALE | RGB565 | YUV422 | JPEG | JPEG -> RGB565 | JPEG -> RGB888 | JPEG (fb=2) |
Expand All @@ -263,8 +280,8 @@ Using fb_count=2 theoretically can double the FPS (see JPEG with fb_count=2). Th
| QCIF | 11 | 11 | 11.5 | 25 | 25 | 25 | 50 |
| HQVGA | 12.5 | 12.5 | 12.5 | 25 | 16.7 | 16.7 | 50 |
| R240X240 | 12.5 | 12.5 | 11.5 | 25 | 16.7 | 12.5 | 50 |
| QVGA | 12 | 11 | 12 | 25 | 12.5 | 12.5 | 50 |
| CIF | 12.5 | No img | No img | 6.3 | 1.6 | 1.6 | 12.5 |
| QVGA | 12 | 11 | 12 | 25 | 25 | 25 | 50 |
| CIF | 12.5 | No img | No img | 6.3 | 8.3 | 8.3 | 12.5 |
| HVGA | 3 | 3 | 2.5 | 12.5 | 6.3 | 6.3 | 25 |
| VGA | 3 | 3 | 3 | 12.5 | 3.6 | 3.6 | 25 |
| SVGA | 3 | 3 | 3 | 12.5 | 2.8 | 2.5 | 25 |
Expand Down
141 changes: 76 additions & 65 deletions src/modcamera.c
Original file line number Diff line number Diff line change
Expand Up @@ -148,11 +148,15 @@ void mp_camera_hal_construct(

// defaul parameters
self->camera_config.fb_location = CAMERA_FB_IN_PSRAM;
self->camera_config.ledc_timer = LEDC_TIMER_0;
self->camera_config.ledc_timer = LEDC_TIMER_3;
self->camera_config.ledc_channel = LEDC_CHANNEL_0;

self->initialized = false;
self->captured_buffer = NULL;
self->converted_buffer.len = 0;
self->converted_buffer.buf = NULL;
self->converted_buffer.typecode = 'B';
self->bmp_out = false;
}

void mp_camera_hal_init(mp_camera_obj_t *self) {
Expand All @@ -171,6 +175,11 @@ void mp_camera_hal_init(mp_camera_obj_t *self) {

void mp_camera_hal_deinit(mp_camera_obj_t *self) {
if (self->initialized) {
if (self->converted_buffer.buf) {
free(self->converted_buffer.buf);
self->converted_buffer.buf = NULL;
self->converted_buffer.len = 0;
}
if (self->captured_buffer) {
esp_camera_return_all();
self->captured_buffer = NULL;
Expand Down Expand Up @@ -205,21 +214,68 @@ void mp_camera_hal_reconfigure(mp_camera_obj_t *self, mp_camera_framesize_t fram
ESP_LOGI(TAG, "Camera reconfigured successfully");
}

static bool ensure_buffer(mp_camera_obj_t *self, size_t req_len) {
if (self->converted_buffer.len == req_len) {
return true;
}
if (self->converted_buffer.len > 0 || self->converted_buffer.buf) {
free(self->converted_buffer.buf);
}
self->converted_buffer.buf = (uint8_t *)malloc(req_len);
if (!self->converted_buffer.buf) {
self->converted_buffer.len = 0;
ESP_LOGE(TAG, "converted_buffer malloc failed");
return false;
}
self->converted_buffer.len = req_len;
return true;
}

static bool mp_camera_convert(mp_camera_obj_t *self, mp_camera_pixformat_t out_format) {
ESP_LOGI(TAG, "Converting image to pixel format: %d", out_format);

switch (out_format) {
case PIXFORMAT_JPEG:
return frame2jpg(self->captured_buffer, self->camera_config.jpeg_quality, self->converted_buffer.buf, &self->converted_buffer.len);

case PIXFORMAT_RGB888:
if (ensure_buffer(self, self->captured_buffer->width * self->captured_buffer->height * 3)) {
return fmt2rgb888(self->captured_buffer->buf, self->captured_buffer->len, self->captured_buffer->format, self->converted_buffer.buf);
} else {
return false;
}

case PIXFORMAT_RGB565:
if (self->camera_config.pixel_format == PIXFORMAT_JPEG) {
if (ensure_buffer(self, self->captured_buffer->width * self->captured_buffer->height * 2)) {
return jpg2rgb565(self->captured_buffer->buf, self->captured_buffer->len, self->converted_buffer.buf, JPG_SCALE_NONE);
} else {
return false;
}
} else {
mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Can only convert JPEG to RGB565"));
}

default:
mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Unsupported pixel format for conversion"));
}
}
mp_obj_t mp_camera_hal_convert(mp_camera_obj_t *self, int8_t out_format) {
check_init(self);
if (mp_camera_convert(self, (mp_camera_pixformat_t)out_format)) {
return mp_obj_new_memoryview('b', self->converted_buffer.len, self->converted_buffer.buf);
} else {
return mp_const_none;
}
}

mp_obj_t mp_camera_hal_capture(mp_camera_obj_t *self, int8_t out_format) {
check_init(self);
if (self->captured_buffer) {
esp_camera_fb_return(self->captured_buffer);
self->captured_buffer = NULL;
}

static size_t out_len = 0;
static uint8_t *out_buf = NULL;
if (out_len > 0 || out_buf) {
free(out_buf);
out_len = 0;
out_buf = NULL;
}

ESP_LOGI(TAG, "Capturing image");
self->captured_buffer = esp_camera_fb_get();
if (!self->captured_buffer) {
Expand All @@ -228,56 +284,12 @@ mp_obj_t mp_camera_hal_capture(mp_camera_obj_t *self, int8_t out_format) {
}

if (out_format >= 0 && (mp_camera_pixformat_t)out_format != self->camera_config.pixel_format) {
ESP_LOGI(TAG, "Converting image to pixel format: %d", out_format);
switch ((mp_camera_pixformat_t)out_format) {
case PIXFORMAT_JPEG:
if (frame2jpg(self->captured_buffer, self->camera_config.jpeg_quality, &out_buf, &out_len)) {
esp_camera_fb_return(self->captured_buffer);
mp_obj_t result = mp_obj_new_memoryview('b', out_len, out_buf);
return result;
} else {
return mp_const_none;
}

case PIXFORMAT_RGB888:
out_len = self->captured_buffer->width * self->captured_buffer->height * 3;
out_buf = (uint8_t *)malloc(out_len);
if (!out_buf) {
ESP_LOGE(TAG, "out_buf malloc failed");
return mp_const_none;
}
if (fmt2rgb888(self->captured_buffer->buf, self->captured_buffer->len, self->captured_buffer->format, out_buf)) {
esp_camera_fb_return(self->captured_buffer);
mp_obj_t result = mp_obj_new_memoryview('b', out_len, out_buf);
return result;
} else {
return mp_const_none;
}

case PIXFORMAT_RGB565:
out_len = self->captured_buffer->width * self->captured_buffer->height * 2;
out_buf = (uint8_t *)malloc(out_len);
if (!out_buf) {
ESP_LOGE(TAG, "out_buf malloc failed");
return mp_const_none;
}
if(self->camera_config.pixel_format == PIXFORMAT_JPEG){
if (jpg2rgb565(self->captured_buffer->buf, self->captured_buffer->len, out_buf, JPG_SCALE_NONE)) {
esp_camera_fb_return(self->captured_buffer);
mp_obj_t result = mp_obj_new_memoryview('b', out_len, out_buf);
return result;
} else {
return mp_const_none;
}
} else {
mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Can only convert JPEG to RGB565"));
return mp_const_none;
}

default:
mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Unsupported pixel format for conversion"));
return mp_const_none;

if (mp_camera_convert(self, (mp_camera_pixformat_t)out_format)) {
esp_camera_fb_return(self->captured_buffer);
mp_obj_t result = mp_obj_new_memoryview('b', self->converted_buffer.len, self->converted_buffer.buf);
return result;
} else {
return mp_const_none;
}
}

Expand All @@ -286,16 +298,15 @@ mp_obj_t mp_camera_hal_capture(mp_camera_obj_t *self, int8_t out_format) {
return mp_obj_new_memoryview('b', self->captured_buffer->len, self->captured_buffer->buf);
} else {
ESP_LOGI(TAG, "Returning image as bitmap");
if (frame2bmp(self->captured_buffer, &out_buf, &out_len)) {
if (frame2bmp(self->captured_buffer, self->converted_buffer.buf, &self->converted_buffer.len)) {
esp_camera_fb_return(self->captured_buffer);
mp_obj_t result = mp_obj_new_memoryview('b', out_len, out_buf);
mp_obj_t result = mp_obj_new_memoryview('b', self->converted_buffer.len, self->converted_buffer.buf);
return result;
} else {
free(out_buf);
out_buf = NULL;
out_len = 0;
free(self->converted_buffer.buf);
self->converted_buffer.buf = NULL;
self->converted_buffer.len = 0;
mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Failed to convert image to BMP"));
return mp_const_none;
}
}
} // mp_camera_hal_capture
Expand Down
10 changes: 10 additions & 0 deletions src/modcamera.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ typedef struct hal_camera_obj {
bool initialized;
camera_fb_t *captured_buffer;
bool bmp_out;
mp_buffer_info_t converted_buffer;
} hal_camera_obj_t;

#endif // CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
Expand Down Expand Up @@ -191,6 +192,15 @@ extern void mp_camera_hal_reconfigure(mp_camera_obj_t *self, mp_camera_framesize
*/
extern mp_obj_t mp_camera_hal_capture(mp_camera_obj_t *self, int8_t out_format);

/**
* @brief Converts an image from one pixelformat to another.
*
* @param self Pointer to the camera object.
* @param out_format Output pixelformat format.
* @return Converted image as micropython object.
*/
extern mp_obj_t mp_camera_hal_convert(mp_camera_obj_t *self, int8_t out_format);

/**
* @brief Table mapping pixel formats API to their corresponding values at HAL.
* @details Needs to be defined in the port-specific implementation.
Expand Down
8 changes: 8 additions & 0 deletions src/modcamera_api.c
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,13 @@ static mp_obj_t camera_capture(size_t n_args, const mp_obj_t *args){
}
static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(camera_capture_obj, 1, 2, camera_capture);

static mp_obj_t camera_convert(mp_obj_t self_in, mp_obj_t arg) {
mp_camera_obj_t *self = MP_OBJ_TO_PTR(self_in);
int8_t out_format = mp_obj_get_int(arg);
return mp_camera_hal_convert(self, out_format);
}
static MP_DEFINE_CONST_FUN_OBJ_2(camera_convert_obj, camera_convert);

static mp_obj_t camera_reconfigure(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args){
//OPEN: Validate inputs
mp_camera_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]);
Expand Down Expand Up @@ -291,6 +298,7 @@ CREATE_GETSET_FUNCTIONS(lenc, mp_obj_new_bool, mp_obj_is_true);
static const mp_rom_map_elem_t camera_camera_locals_table[] = {
{ MP_ROM_QSTR(MP_QSTR_reconfigure), MP_ROM_PTR(&camera_reconfigure_obj) },
{ MP_ROM_QSTR(MP_QSTR_capture), MP_ROM_PTR(&camera_capture_obj) },
{ MP_ROM_QSTR(MP_QSTR_convert), MP_ROM_PTR(&camera_convert_obj) },
{ MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&camera_init_obj) },
{ MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&mp_camera_deinit_obj) },
{ MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&mp_camera_deinit_obj) },
Expand Down