Skip to content

Commit 9f715c0

Browse files
authored
Img converter to develop (#10)
Add image conversion feature to capture method (experimental feature)
1 parent 76a89ea commit 9f715c0

File tree

7 files changed

+231
-37
lines changed

7 files changed

+231
-37
lines changed

examples/ESP32cam.html renamed to examples/CameraSettings.html

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,16 @@
140140
.catch(error => {
141141
console.error('Error fetching sensor name:', error);
142142
});
143+
144+
fetch('/get_pixel_format')
145+
.then(response => response.text())
146+
.then(pixelFormat => {
147+
const showJpegQuality = (pixelFormat === '4');
148+
document.getElementById('quality').parentElement.classList.toggle('hidden', !showJpegQuality);
149+
})
150+
.catch(error => {
151+
console.error('Error fetching pixel format:', error);
152+
});
143153
}
144154

145155
document.addEventListener("DOMContentLoaded", () => {
@@ -151,7 +161,7 @@
151161
</head>
152162
<body>
153163
<div class="title-container">
154-
<h1>ESP32 Camera Stream</h1>
164+
<h1>Micropython Camera Stream</h1>
155165
</div>
156166
<div class="container">
157167
<div class="settings-container">
@@ -299,7 +309,7 @@ <h1>ESP32 Camera Stream</h1>
299309
</div>
300310
</div>
301311
<div class="video-container">
302-
<img src="/stream">
312+
<img src="/stream" alt="Loading stream...">
303313
</div>
304314
</div>
305315
</body>

examples/CameraSettings.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,25 @@
1717

1818
print(f'Connected! IP: {station.ifconfig()[0]}. Open this IP in your browser')
1919

20-
with open("ESP32cam.html", 'r') as file:
20+
with open("CameraSettings.html", 'r') as file:
2121
html = file.read()
2222

2323
async def stream_camera(writer):
2424
try:
2525
cam.init()
26+
if not cam.get_bmp_out() and cam.get_pixel_format() != PixelFormat.JPEG:
27+
cam.set_bmp_out(True)
2628

2729
writer.write(b'HTTP/1.1 200 OK\r\nContent-Type: multipart/x-mixed-replace; boundary=frame\r\n\r\n')
2830
await writer.drain()
2931

3032
while True:
3133
frame = cam.capture()
3234
if frame:
33-
writer.write(b'--frame\r\nContent-Type: image/jpeg\r\n\r\n')
35+
if cam.get_pixel_format() == PixelFormat.JPEG:
36+
writer.write(b'--frame\r\nContent-Type: image/jpeg\r\n\r\n')
37+
else:
38+
writer.write(b'--frame\r\nContent-Type: image/bmp\r\n\r\n')
3439
writer.write(frame)
3540
await writer.drain()
3641

File renamed without changes.

examples/benchmark.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
from camera import Camera, FrameSize, PixelFormat
2+
import time
3+
import gc
4+
import os
5+
gc.enable()
6+
7+
def measure_fps(duration=2):
8+
start_time = time.time()
9+
while time.time() - start_time < 0.5:
10+
cam.capture()
11+
12+
start_time = time.time()
13+
frame_count = 0
14+
15+
while time.time() - start_time < duration:
16+
cam.capture()
17+
frame_count += 1
18+
19+
end_time = time.time()
20+
fps = frame_count / (end_time - start_time)
21+
return fps
22+
23+
def print_summary_table(results, cam):
24+
(_,_,_,_,target) = os.uname()
25+
print(f"\nBenchmark {target} with {cam.get_sensor_name()}, fb_count: {cam.get_fb_count()}, GrabMode: {cam.get_grab_mode()}:")
26+
27+
pixel_formats = list(results.keys())
28+
print(f"{'Frame Size':<15}", end="")
29+
for p in pixel_formats:
30+
print(f"{p:<15}", end="")
31+
print()
32+
33+
frame_size_names = {getattr(FrameSize, f): f for f in dir(FrameSize) if not f.startswith('_')}
34+
frame_sizes = list(next(iter(results.values())).keys())
35+
36+
for f in frame_sizes:
37+
frame_size_name = frame_size_names.get(f, str(f))
38+
print(f"{frame_size_name:<15}", end="")
39+
for p in pixel_formats:
40+
fps = results[p].get(f, "N/A")
41+
print(f"{fps:<15}", end="")
42+
print()
43+
44+
if __name__ == "__main__":
45+
cam = Camera()
46+
results = {}
47+
48+
try:
49+
for p in dir(PixelFormat):
50+
if not p.startswith('_'):
51+
p_value = getattr(PixelFormat, p)
52+
if p_value == PixelFormat.RGB888 and cam.get_sensor_name() == "OV2640":
53+
continue
54+
try:
55+
cam.reconfigure(pixel_format=p_value)
56+
results[p] = {}
57+
except Exception as e:
58+
print('ERR:', e)
59+
continue
60+
61+
for f in dir(FrameSize):
62+
if not f.startswith('_'):
63+
f_value = getattr(FrameSize, f)
64+
if f_value > cam.get_max_frame_size():
65+
continue
66+
gc.collect()
67+
print('Set', p, f, ':')
68+
69+
try:
70+
cam.reconfigure(frame_size=f_value)
71+
time.sleep_ms(10)
72+
img = cam.capture()
73+
74+
if img:
75+
print('---> Image size:', len(img))
76+
fps = measure_fps(2)
77+
print(f"---> FPS: {fps}")
78+
results[p][f_value] = fps # FPS in Dictionary speichern
79+
else:
80+
print('No image captured')
81+
results[p][f_value] = 'No image'
82+
83+
print(f"---> Free Memory: {gc.mem_free()}")
84+
except Exception as e:
85+
print('ERR:', e)
86+
results[p][f_value] = 'ERR'
87+
finally:
88+
time.sleep_ms(250)
89+
gc.collect()
90+
print('')
91+
92+
except KeyboardInterrupt:
93+
print("\nScript interrupted by user.")
94+
95+
finally:
96+
cam.deinit()
97+
print_summary_table(results) # Tabelle am Ende ausgeben
98+

src/modcamera.c

Lines changed: 87 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include "modcamera.h"
2929
#include "esp_err.h"
3030
#include "esp_log.h"
31+
#include "img_converters.h"
3132

3233
#define TAG "ESP32_MPY_CAMERA"
3334

@@ -42,7 +43,7 @@
4243
#endif
4344

4445
// Supporting functions
45-
void raise_micropython_error_from_esp_err(esp_err_t err) {
46+
static void raise_micropython_error_from_esp_err(esp_err_t err) {
4647
switch (err) {
4748
case ESP_OK:
4849
return;
@@ -72,8 +73,8 @@ void raise_micropython_error_from_esp_err(esp_err_t err) {
7273
break;
7374

7475
default:
75-
mp_raise_msg_varg(&mp_type_RuntimeError, MP_ERROR_TEXT("Unknown error 0x%04x"), err);
76-
// mp_raise_msg_varg(&mp_type_RuntimeError, MP_ERROR_TEXT("Unknown error"));
76+
// mp_raise_msg_varg(&mp_type_RuntimeError, MP_ERROR_TEXT("Unknown error 0x%04x"), err);
77+
mp_raise_msg_varg(&mp_type_RuntimeError, MP_ERROR_TEXT("Unknown error"));
7778
break;
7879
}
7980
}
@@ -85,7 +86,7 @@ static int map(int value, int fromLow, int fromHigh, int toLow, int toHigh) {
8586
return (int)((int32_t)(value - fromLow) * (toHigh - toLow) / (fromHigh - fromLow) + toLow);
8687
}
8788

88-
static int get_mapped_jpeg_quality(int8_t quality) {
89+
static inline int get_mapped_jpeg_quality(int8_t quality) {
8990
return map(quality, 0, 100, 63, 0);
9091
}
9192

@@ -246,37 +247,90 @@ void mp_camera_hal_reconfigure(mp_camera_obj_t *self, mp_camera_framesize_t fram
246247
}
247248
}
248249

249-
mp_obj_t mp_camera_hal_capture(mp_camera_obj_t *self, int timeout_ms) {
250-
// Timeout not used at the moment
250+
mp_obj_t mp_camera_hal_capture(mp_camera_obj_t *self, int8_t out_format) {
251251
if (!self->initialized) {
252252
mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Failed to capture image: Camera not initialized"));
253253
}
254254
if (self->captured_buffer) {
255255
esp_camera_fb_return(self->captured_buffer);
256256
self->captured_buffer = NULL;
257257
}
258+
259+
static size_t out_len = 0;
260+
static uint8_t *out_buf = NULL;
261+
if (out_len>0 || out_buf) {
262+
free(out_buf);
263+
out_len = 0;
264+
out_buf = NULL;
265+
}
266+
258267
ESP_LOGI(TAG, "Capturing image");
259268
self->captured_buffer = esp_camera_fb_get();
260-
if (self->captured_buffer) {
261-
if (self->camera_config.pixel_format == PIXFORMAT_JPEG) {
262-
ESP_LOGI(TAG, "Captured image in JPEG format");
263-
return mp_obj_new_memoryview('b', self->captured_buffer->len, self->captured_buffer->buf);
264-
} else {
265-
ESP_LOGI(TAG, "Captured image in raw format");
266-
return mp_obj_new_memoryview('b', self->captured_buffer->len, self->captured_buffer->buf);
267-
// TODO: Stub at the moment in order to return raw data, but it sould be implemented to return a Bitmap, see following circuitpython example:
268-
//
269-
// int width = common_hal_espcamera_camera_get_width(self);
270-
// int height = common_hal_espcamera_camera_get_height(self);
271-
// displayio_bitmap_t *bitmap = m_new_obj(displayio_bitmap_t);
272-
// bitmap->base.type = &displayio_bitmap_type;
273-
// common_hal_displayio_bitmap_construct_from_buffer(bitmap, width, height, (format == PIXFORMAT_RGB565) ? 16 : 8, (uint32_t *)(void *)result->buf, true);
274-
// return bitmap;
269+
if (!self->captured_buffer) {
270+
ESP_LOGE(TAG, "Failed to capture image");
271+
return mp_const_none;
272+
}
273+
274+
if (out_format >= 0 && (mp_camera_pixformat_t)out_format != self->camera_config.pixel_format) {
275+
switch (out_format) {
276+
case PIXFORMAT_JPEG:
277+
if (frame2jpg(self->captured_buffer, self->camera_config.jpeg_quality, &out_buf, &out_len)) {
278+
esp_camera_fb_return(self->captured_buffer);
279+
mp_obj_t result = mp_obj_new_memoryview('b', out_len, out_buf);
280+
return result;
281+
} else {
282+
return mp_const_none;
283+
}
284+
285+
case PIXFORMAT_RGB888:
286+
out_len = self->captured_buffer->width * self->captured_buffer->height * 3;
287+
out_buf = (uint8_t *)malloc(out_len);
288+
if (!out_buf) {
289+
ESP_LOGE(TAG, "out_buf malloc failed");
290+
return mp_const_none;
291+
}
292+
if (fmt2rgb888(self->captured_buffer->buf, self->captured_buffer->len, self->captured_buffer->format, out_buf)) {
293+
esp_camera_fb_return(self->captured_buffer);
294+
mp_obj_t result = mp_obj_new_memoryview('b', out_len, out_buf);
295+
return result;
296+
} else {
297+
return mp_const_none;
298+
}
299+
300+
case PIXFORMAT_RGB565:
301+
if(self->camera_config.pixel_format == PIXFORMAT_JPEG){
302+
if (jpg2rgb565(self->captured_buffer->buf, self->captured_buffer->len, out_buf, JPG_SCALE_NONE)) {
303+
esp_camera_fb_return(self->captured_buffer);
304+
mp_obj_t result = mp_obj_new_memoryview('b', out_len, out_buf);
305+
return result;
306+
} else {
307+
return mp_const_none;
308+
}
309+
} else {
310+
mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Can only convert JPEG to RGB565"));
311+
return mp_const_none;
312+
}
313+
314+
default:
315+
mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Unsupported pixel format for conversion"));
316+
return mp_const_none;
317+
275318
}
319+
}
320+
321+
if (self->bmp_out == false) {
322+
ESP_LOGI(TAG, "Returning imgae without conversion");
323+
return mp_obj_new_memoryview('b', self->captured_buffer->len, self->captured_buffer->buf);
276324
} else {
277-
esp_camera_fb_return(self->captured_buffer);
278-
self->captured_buffer = NULL;
279-
return mp_const_none;
325+
ESP_LOGI(TAG, "Returning image as bitmap");
326+
if (frame2bmp(self->captured_buffer, &out_buf, &out_len)) {
327+
esp_camera_fb_return(self->captured_buffer);
328+
mp_obj_t result = mp_obj_new_memoryview('b', out_len, out_buf);
329+
return result;
330+
} else {
331+
mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Failed to convert image to BMP"));
332+
return mp_const_none;
333+
}
280334
}
281335
}
282336

@@ -289,6 +343,7 @@ const mp_rom_map_elem_t mp_camera_hal_pixel_format_table[] = {
289343
{ MP_ROM_QSTR(MP_QSTR_YUV422), MP_ROM_INT(PIXFORMAT_YUV422) },
290344
{ MP_ROM_QSTR(MP_QSTR_GRAYSCALE), MP_ROM_INT(PIXFORMAT_GRAYSCALE) },
291345
{ MP_ROM_QSTR(MP_QSTR_RGB565), MP_ROM_INT(PIXFORMAT_RGB565) },
346+
{ MP_ROM_QSTR(MP_QSTR_RGB888), MP_ROM_INT(PIXFORMAT_RGB888) },
292347
};
293348

294349
const mp_rom_map_elem_t mp_camera_hal_frame_size_table[] = {
@@ -418,16 +473,24 @@ void mp_camera_hal_set_frame_size(mp_camera_obj_t * self, framesize_t value) {
418473
if (!self->initialized) {
419474
mp_raise_ValueError(MP_ERROR_TEXT("Camera not initialized"));
420475
}
476+
421477
sensor_t *sensor = esp_camera_sensor_get();
422478
if (!sensor->set_framesize) {
423479
mp_raise_ValueError(MP_ERROR_TEXT("No attribute frame_size"));
424480
}
481+
482+
if (self->captured_buffer) {
483+
esp_camera_return_all();
484+
self->captured_buffer = NULL;
485+
}
486+
425487
if (sensor->set_framesize(sensor, value) < 0) {
426488
mp_raise_ValueError(MP_ERROR_TEXT("Invalid setting for frame_size"));
427489
} else {
428490
self->camera_config.frame_size = value;
429491
}
430492
}
493+
431494
int mp_camera_hal_get_quality(mp_camera_obj_t * self) {
432495
if (!self->initialized) {
433496
mp_raise_ValueError(MP_ERROR_TEXT("Camera not initialized"));

src/modcamera.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ typedef struct hal_camera_obj {
106106
camera_config_t camera_config;
107107
bool initialized;
108108
camera_fb_t *captured_buffer;
109+
bool bmp_out;
109110
} hal_camera_obj_t;
110111

111112
#endif // CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
@@ -194,16 +195,16 @@ extern void mp_camera_hal_reconfigure(mp_camera_obj_t *self, mp_camera_framesize
194195
* @brief Captures an image and returns it as mp_obj_t (e.g. mp_obj_new_memoryview).
195196
*
196197
* @param self Pointer to the camera object.
197-
* @param timeout_ms Timeout in milliseconds.
198+
* @param out_format Output pixelformat format.
198199
* @return Captured image as micropython object.
199200
*/
200-
extern mp_obj_t mp_camera_hal_capture(mp_camera_obj_t *self, int timeout_ms);
201+
extern mp_obj_t mp_camera_hal_capture(mp_camera_obj_t *self, int8_t out_format);
201202

202203
/**
203204
* @brief Table mapping pixel formats API to their corresponding values at HAL.
204205
* @details Needs to be defined in the port-specific implementation.
205206
*/
206-
extern const mp_rom_map_elem_t mp_camera_hal_pixel_format_table[4];
207+
extern const mp_rom_map_elem_t mp_camera_hal_pixel_format_table[5];
207208

208209
/**
209210
* @brief Table mapping frame sizes API to their corresponding values at HAL.

0 commit comments

Comments
 (0)