Skip to content

Commit e462cac

Browse files
authored
Img conv fun (#21)
Adds more functionality and improves the performance in image conversion. Fix #18
1 parent 86499f6 commit e462cac

File tree

5 files changed

+122
-76
lines changed

5 files changed

+122
-76
lines changed

.github/workflows/ESP32.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ on:
77
- 'src/**'
88
- '.github/workflows/*.yml'
99
tags-ignore:
10-
- '**'
10+
- 'v*'
1111
pull_request:
1212
branches:
1313
- master

README.md

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -99,14 +99,31 @@ cam.init()
9999
### Capture image
100100

101101
```python
102-
img = cam.capture() #capture image as configured
103-
img_rgb888 = cam.capture(PixelFormat.RGB888) #capture image and convert it to RGB888
102+
img = cam.capture()
104103
```
105104

106105
Arguments for capture
107106

108107
- out_format: Output format as PixelFormat (optional)
109108

109+
### Convert image to another format
110+
111+
You can either convert the image with the `capture` method directly passing the desired output format:
112+
```python
113+
img_rgb888 = cam.capture(PixelFormat.RGB888) #capture image as configured (e.g. JPEG), convert it to RGB888 and return the converted image
114+
```
115+
Or you can first capture the image and then convert it to the desired PixelFormat with the `convert` method.
116+
Doing so you can have both, the captured and the converted image. Note that more memory will be used.
117+
```python
118+
img = cam.capture()
119+
img_rgb888 = cam.convert(PixelFormat.RGB888) #converts the last captured image to RGB888 and returns the converted image
120+
```
121+
122+
Convertion supported
123+
- from JPEG to RGB565
124+
- to RGB888 in general
125+
- to JPEG in gerenal (use the `set_quality` method to set the desired JPEG quality)
126+
110127
### Camera reconfiguration
111128

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

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

254271
## FPS benchmark
255272

256-
I didn't use a calibrated osziloscope, but here is a benchmark with my ESP32S3 (GrabMode=LATEST, fb_count = 1, jpeg_quality=85%).
273+
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.
257274
Using fb_count=2 theoretically can double the FPS (see JPEG with fb_count=2). This might also aplly for other PixelFormats.
258275

259276
| Frame Size | GRAYSCALE | RGB565 | YUV422 | JPEG | JPEG -> RGB565 | JPEG -> RGB888 | JPEG (fb=2) |
@@ -263,8 +280,8 @@ Using fb_count=2 theoretically can double the FPS (see JPEG with fb_count=2). Th
263280
| QCIF | 11 | 11 | 11.5 | 25 | 25 | 25 | 50 |
264281
| HQVGA | 12.5 | 12.5 | 12.5 | 25 | 16.7 | 16.7 | 50 |
265282
| R240X240 | 12.5 | 12.5 | 11.5 | 25 | 16.7 | 12.5 | 50 |
266-
| QVGA | 12 | 11 | 12 | 25 | 12.5 | 12.5 | 50 |
267-
| CIF | 12.5 | No img | No img | 6.3 | 1.6 | 1.6 | 12.5 |
283+
| QVGA | 12 | 11 | 12 | 25 | 25 | 25 | 50 |
284+
| CIF | 12.5 | No img | No img | 6.3 | 8.3 | 8.3 | 12.5 |
268285
| HVGA | 3 | 3 | 2.5 | 12.5 | 6.3 | 6.3 | 25 |
269286
| VGA | 3 | 3 | 3 | 12.5 | 3.6 | 3.6 | 25 |
270287
| SVGA | 3 | 3 | 3 | 12.5 | 2.8 | 2.5 | 25 |

src/modcamera.c

Lines changed: 76 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -148,11 +148,15 @@ void mp_camera_hal_construct(
148148

149149
// defaul parameters
150150
self->camera_config.fb_location = CAMERA_FB_IN_PSRAM;
151-
self->camera_config.ledc_timer = LEDC_TIMER_0;
151+
self->camera_config.ledc_timer = LEDC_TIMER_3;
152152
self->camera_config.ledc_channel = LEDC_CHANNEL_0;
153153

154154
self->initialized = false;
155155
self->captured_buffer = NULL;
156+
self->converted_buffer.len = 0;
157+
self->converted_buffer.buf = NULL;
158+
self->converted_buffer.typecode = 'B';
159+
self->bmp_out = false;
156160
}
157161

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

172176
void mp_camera_hal_deinit(mp_camera_obj_t *self) {
173177
if (self->initialized) {
178+
if (self->converted_buffer.buf) {
179+
free(self->converted_buffer.buf);
180+
self->converted_buffer.buf = NULL;
181+
self->converted_buffer.len = 0;
182+
}
174183
if (self->captured_buffer) {
175184
esp_camera_return_all();
176185
self->captured_buffer = NULL;
@@ -205,21 +214,68 @@ void mp_camera_hal_reconfigure(mp_camera_obj_t *self, mp_camera_framesize_t fram
205214
ESP_LOGI(TAG, "Camera reconfigured successfully");
206215
}
207216

217+
static bool ensure_buffer(mp_camera_obj_t *self, size_t req_len) {
218+
if (self->converted_buffer.len == req_len) {
219+
return true;
220+
}
221+
if (self->converted_buffer.len > 0 || self->converted_buffer.buf) {
222+
free(self->converted_buffer.buf);
223+
}
224+
self->converted_buffer.buf = (uint8_t *)malloc(req_len);
225+
if (!self->converted_buffer.buf) {
226+
self->converted_buffer.len = 0;
227+
ESP_LOGE(TAG, "converted_buffer malloc failed");
228+
return false;
229+
}
230+
self->converted_buffer.len = req_len;
231+
return true;
232+
}
233+
234+
static bool mp_camera_convert(mp_camera_obj_t *self, mp_camera_pixformat_t out_format) {
235+
ESP_LOGI(TAG, "Converting image to pixel format: %d", out_format);
236+
237+
switch (out_format) {
238+
case PIXFORMAT_JPEG:
239+
return frame2jpg(self->captured_buffer, self->camera_config.jpeg_quality, self->converted_buffer.buf, &self->converted_buffer.len);
240+
241+
case PIXFORMAT_RGB888:
242+
if (ensure_buffer(self, self->captured_buffer->width * self->captured_buffer->height * 3)) {
243+
return fmt2rgb888(self->captured_buffer->buf, self->captured_buffer->len, self->captured_buffer->format, self->converted_buffer.buf);
244+
} else {
245+
return false;
246+
}
247+
248+
case PIXFORMAT_RGB565:
249+
if (self->camera_config.pixel_format == PIXFORMAT_JPEG) {
250+
if (ensure_buffer(self, self->captured_buffer->width * self->captured_buffer->height * 2)) {
251+
return jpg2rgb565(self->captured_buffer->buf, self->captured_buffer->len, self->converted_buffer.buf, JPG_SCALE_NONE);
252+
} else {
253+
return false;
254+
}
255+
} else {
256+
mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Can only convert JPEG to RGB565"));
257+
}
258+
259+
default:
260+
mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Unsupported pixel format for conversion"));
261+
}
262+
}
263+
mp_obj_t mp_camera_hal_convert(mp_camera_obj_t *self, int8_t out_format) {
264+
check_init(self);
265+
if (mp_camera_convert(self, (mp_camera_pixformat_t)out_format)) {
266+
return mp_obj_new_memoryview('b', self->converted_buffer.len, self->converted_buffer.buf);
267+
} else {
268+
return mp_const_none;
269+
}
270+
}
271+
208272
mp_obj_t mp_camera_hal_capture(mp_camera_obj_t *self, int8_t out_format) {
209273
check_init(self);
210274
if (self->captured_buffer) {
211275
esp_camera_fb_return(self->captured_buffer);
212276
self->captured_buffer = NULL;
213277
}
214278

215-
static size_t out_len = 0;
216-
static uint8_t *out_buf = NULL;
217-
if (out_len > 0 || out_buf) {
218-
free(out_buf);
219-
out_len = 0;
220-
out_buf = NULL;
221-
}
222-
223279
ESP_LOGI(TAG, "Capturing image");
224280
self->captured_buffer = esp_camera_fb_get();
225281
if (!self->captured_buffer) {
@@ -228,56 +284,12 @@ mp_obj_t mp_camera_hal_capture(mp_camera_obj_t *self, int8_t out_format) {
228284
}
229285

230286
if (out_format >= 0 && (mp_camera_pixformat_t)out_format != self->camera_config.pixel_format) {
231-
ESP_LOGI(TAG, "Converting image to pixel format: %d", out_format);
232-
switch ((mp_camera_pixformat_t)out_format) {
233-
case PIXFORMAT_JPEG:
234-
if (frame2jpg(self->captured_buffer, self->camera_config.jpeg_quality, &out_buf, &out_len)) {
235-
esp_camera_fb_return(self->captured_buffer);
236-
mp_obj_t result = mp_obj_new_memoryview('b', out_len, out_buf);
237-
return result;
238-
} else {
239-
return mp_const_none;
240-
}
241-
242-
case PIXFORMAT_RGB888:
243-
out_len = self->captured_buffer->width * self->captured_buffer->height * 3;
244-
out_buf = (uint8_t *)malloc(out_len);
245-
if (!out_buf) {
246-
ESP_LOGE(TAG, "out_buf malloc failed");
247-
return mp_const_none;
248-
}
249-
if (fmt2rgb888(self->captured_buffer->buf, self->captured_buffer->len, self->captured_buffer->format, out_buf)) {
250-
esp_camera_fb_return(self->captured_buffer);
251-
mp_obj_t result = mp_obj_new_memoryview('b', out_len, out_buf);
252-
return result;
253-
} else {
254-
return mp_const_none;
255-
}
256-
257-
case PIXFORMAT_RGB565:
258-
out_len = self->captured_buffer->width * self->captured_buffer->height * 2;
259-
out_buf = (uint8_t *)malloc(out_len);
260-
if (!out_buf) {
261-
ESP_LOGE(TAG, "out_buf malloc failed");
262-
return mp_const_none;
263-
}
264-
if(self->camera_config.pixel_format == PIXFORMAT_JPEG){
265-
if (jpg2rgb565(self->captured_buffer->buf, self->captured_buffer->len, out_buf, JPG_SCALE_NONE)) {
266-
esp_camera_fb_return(self->captured_buffer);
267-
mp_obj_t result = mp_obj_new_memoryview('b', out_len, out_buf);
268-
return result;
269-
} else {
270-
return mp_const_none;
271-
}
272-
} else {
273-
mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Can only convert JPEG to RGB565"));
274-
return mp_const_none;
275-
}
276-
277-
default:
278-
mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Unsupported pixel format for conversion"));
279-
return mp_const_none;
280-
287+
if (mp_camera_convert(self, (mp_camera_pixformat_t)out_format)) {
288+
esp_camera_fb_return(self->captured_buffer);
289+
mp_obj_t result = mp_obj_new_memoryview('b', self->converted_buffer.len, self->converted_buffer.buf);
290+
return result;
291+
} else {
292+
return mp_const_none;
281293
}
282294
}
283295

@@ -286,16 +298,15 @@ mp_obj_t mp_camera_hal_capture(mp_camera_obj_t *self, int8_t out_format) {
286298
return mp_obj_new_memoryview('b', self->captured_buffer->len, self->captured_buffer->buf);
287299
} else {
288300
ESP_LOGI(TAG, "Returning image as bitmap");
289-
if (frame2bmp(self->captured_buffer, &out_buf, &out_len)) {
301+
if (frame2bmp(self->captured_buffer, self->converted_buffer.buf, &self->converted_buffer.len)) {
290302
esp_camera_fb_return(self->captured_buffer);
291-
mp_obj_t result = mp_obj_new_memoryview('b', out_len, out_buf);
303+
mp_obj_t result = mp_obj_new_memoryview('b', self->converted_buffer.len, self->converted_buffer.buf);
292304
return result;
293305
} else {
294-
free(out_buf);
295-
out_buf = NULL;
296-
out_len = 0;
306+
free(self->converted_buffer.buf);
307+
self->converted_buffer.buf = NULL;
308+
self->converted_buffer.len = 0;
297309
mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Failed to convert image to BMP"));
298-
return mp_const_none;
299310
}
300311
}
301312
} // mp_camera_hal_capture

src/modcamera.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ typedef struct hal_camera_obj {
9898
bool initialized;
9999
camera_fb_t *captured_buffer;
100100
bool bmp_out;
101+
mp_buffer_info_t converted_buffer;
101102
} hal_camera_obj_t;
102103

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

195+
/**
196+
* @brief Converts an image from one pixelformat to another.
197+
*
198+
* @param self Pointer to the camera object.
199+
* @param out_format Output pixelformat format.
200+
* @return Converted image as micropython object.
201+
*/
202+
extern mp_obj_t mp_camera_hal_convert(mp_camera_obj_t *self, int8_t out_format);
203+
194204
/**
195205
* @brief Table mapping pixel formats API to their corresponding values at HAL.
196206
* @details Needs to be defined in the port-specific implementation.

src/modcamera_api.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,13 @@ static mp_obj_t camera_capture(size_t n_args, const mp_obj_t *args){
154154
}
155155
static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(camera_capture_obj, 1, 2, camera_capture);
156156

157+
static mp_obj_t camera_convert(mp_obj_t self_in, mp_obj_t arg) {
158+
mp_camera_obj_t *self = MP_OBJ_TO_PTR(self_in);
159+
int8_t out_format = mp_obj_get_int(arg);
160+
return mp_camera_hal_convert(self, out_format);
161+
}
162+
static MP_DEFINE_CONST_FUN_OBJ_2(camera_convert_obj, camera_convert);
163+
157164
static mp_obj_t camera_reconfigure(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args){
158165
//OPEN: Validate inputs
159166
mp_camera_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]);
@@ -291,6 +298,7 @@ CREATE_GETSET_FUNCTIONS(lenc, mp_obj_new_bool, mp_obj_is_true);
291298
static const mp_rom_map_elem_t camera_camera_locals_table[] = {
292299
{ MP_ROM_QSTR(MP_QSTR_reconfigure), MP_ROM_PTR(&camera_reconfigure_obj) },
293300
{ MP_ROM_QSTR(MP_QSTR_capture), MP_ROM_PTR(&camera_capture_obj) },
301+
{ MP_ROM_QSTR(MP_QSTR_convert), MP_ROM_PTR(&camera_convert_obj) },
294302
{ MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&camera_init_obj) },
295303
{ MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&mp_camera_deinit_obj) },
296304
{ MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&mp_camera_deinit_obj) },

0 commit comments

Comments
 (0)