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
14 changes: 12 additions & 2 deletions examples/ESP32cam.html → examples/CameraSettings.html
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,16 @@
.catch(error => {
console.error('Error fetching sensor name:', error);
});

fetch('/get_pixel_format')
.then(response => response.text())
.then(pixelFormat => {
const showJpegQuality = (pixelFormat === '4');
document.getElementById('quality').parentElement.classList.toggle('hidden', !showJpegQuality);
})
.catch(error => {
console.error('Error fetching pixel format:', error);
});
}

document.addEventListener("DOMContentLoaded", () => {
Expand All @@ -151,7 +161,7 @@
</head>
<body>
<div class="title-container">
<h1>ESP32 Camera Stream</h1>
<h1>Micropython Camera Stream</h1>
</div>
<div class="container">
<div class="settings-container">
Expand Down Expand Up @@ -299,7 +309,7 @@ <h1>ESP32 Camera Stream</h1>
</div>
</div>
<div class="video-container">
<img src="/stream">
<img src="/stream" alt="Loading stream...">
</div>
</div>
</body>
Expand Down
9 changes: 7 additions & 2 deletions examples/CameraSettings.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,25 @@

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

with open("ESP32cam.html", 'r') as file:
with open("CameraSettings.html", 'r') as file:
html = file.read()

async def stream_camera(writer):
try:
cam.init()
if not cam.get_bmp_out() and cam.get_pixel_format() != PixelFormat.JPEG:
cam.set_bmp_out(True)

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

while True:
frame = cam.capture()
if frame:
writer.write(b'--frame\r\nContent-Type: image/jpeg\r\n\r\n')
if cam.get_pixel_format() == PixelFormat.JPEG:
writer.write(b'--frame\r\nContent-Type: image/jpeg\r\n\r\n')
else:
writer.write(b'--frame\r\nContent-Type: image/bmp\r\n\r\n')
writer.write(frame)
await writer.drain()

Expand Down
File renamed without changes.
98 changes: 98 additions & 0 deletions examples/benchmark.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
from camera import Camera, FrameSize, PixelFormat
import time
import gc
import os
gc.enable()

def measure_fps(duration=2):
start_time = time.time()
while time.time() - start_time < 0.5:
cam.capture()

start_time = time.time()
frame_count = 0

while time.time() - start_time < duration:
cam.capture()
frame_count += 1

end_time = time.time()
fps = frame_count / (end_time - start_time)
return fps

def print_summary_table(results, cam):
(_,_,_,_,target) = os.uname()
print(f"\nBenchmark {target} with {cam.get_sensor_name()}, fb_count: {cam.get_fb_count()}, GrabMode: {cam.get_grab_mode()}:")

pixel_formats = list(results.keys())
print(f"{'Frame Size':<15}", end="")
for p in pixel_formats:
print(f"{p:<15}", end="")
print()

frame_size_names = {getattr(FrameSize, f): f for f in dir(FrameSize) if not f.startswith('_')}
frame_sizes = list(next(iter(results.values())).keys())

for f in frame_sizes:
frame_size_name = frame_size_names.get(f, str(f))
print(f"{frame_size_name:<15}", end="")
for p in pixel_formats:
fps = results[p].get(f, "N/A")
print(f"{fps:<15}", end="")
print()

if __name__ == "__main__":
cam = Camera()
results = {}

try:
for p in dir(PixelFormat):
if not p.startswith('_'):
p_value = getattr(PixelFormat, p)
if p_value == PixelFormat.RGB888 and cam.get_sensor_name() == "OV2640":
continue
try:
cam.reconfigure(pixel_format=p_value)
results[p] = {}
except Exception as e:
print('ERR:', e)
continue

for f in dir(FrameSize):
if not f.startswith('_'):
f_value = getattr(FrameSize, f)
if f_value > cam.get_max_frame_size():
continue
gc.collect()
print('Set', p, f, ':')

try:
cam.reconfigure(frame_size=f_value)
time.sleep_ms(10)
img = cam.capture()

if img:
print('---> Image size:', len(img))
fps = measure_fps(2)
print(f"---> FPS: {fps}")
results[p][f_value] = fps # FPS in Dictionary speichern
else:
print('No image captured')
results[p][f_value] = 'No image'

print(f"---> Free Memory: {gc.mem_free()}")
except Exception as e:
print('ERR:', e)
results[p][f_value] = 'ERR'
finally:
time.sleep_ms(250)
gc.collect()
print('')

except KeyboardInterrupt:
print("\nScript interrupted by user.")

finally:
cam.deinit()
print_summary_table(results) # Tabelle am Ende ausgeben

111 changes: 87 additions & 24 deletions src/modcamera.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include "modcamera.h"
#include "esp_err.h"
#include "esp_log.h"
#include "img_converters.h"

#define TAG "ESP32_MPY_CAMERA"

Expand All @@ -42,7 +43,7 @@
#endif

// Supporting functions
void raise_micropython_error_from_esp_err(esp_err_t err) {
static void raise_micropython_error_from_esp_err(esp_err_t err) {
switch (err) {
case ESP_OK:
return;
Expand Down Expand Up @@ -72,8 +73,8 @@ void raise_micropython_error_from_esp_err(esp_err_t err) {
break;

default:
mp_raise_msg_varg(&mp_type_RuntimeError, MP_ERROR_TEXT("Unknown error 0x%04x"), err);
// mp_raise_msg_varg(&mp_type_RuntimeError, MP_ERROR_TEXT("Unknown error"));
// mp_raise_msg_varg(&mp_type_RuntimeError, MP_ERROR_TEXT("Unknown error 0x%04x"), err);
mp_raise_msg_varg(&mp_type_RuntimeError, MP_ERROR_TEXT("Unknown error"));
break;
}
}
Expand All @@ -85,7 +86,7 @@ static int map(int value, int fromLow, int fromHigh, int toLow, int toHigh) {
return (int)((int32_t)(value - fromLow) * (toHigh - toLow) / (fromHigh - fromLow) + toLow);
}

static int get_mapped_jpeg_quality(int8_t quality) {
static inline int get_mapped_jpeg_quality(int8_t quality) {
return map(quality, 0, 100, 63, 0);
}

Expand Down Expand Up @@ -246,37 +247,90 @@ void mp_camera_hal_reconfigure(mp_camera_obj_t *self, mp_camera_framesize_t fram
}
}

mp_obj_t mp_camera_hal_capture(mp_camera_obj_t *self, int timeout_ms) {
// Timeout not used at the moment
mp_obj_t mp_camera_hal_capture(mp_camera_obj_t *self, int8_t out_format) {
if (!self->initialized) {
mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Failed to capture image: Camera not initialized"));
}
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) {
if (self->camera_config.pixel_format == PIXFORMAT_JPEG) {
ESP_LOGI(TAG, "Captured image in JPEG format");
return mp_obj_new_memoryview('b', self->captured_buffer->len, self->captured_buffer->buf);
} else {
ESP_LOGI(TAG, "Captured image in raw format");
return mp_obj_new_memoryview('b', self->captured_buffer->len, self->captured_buffer->buf);
// TODO: Stub at the moment in order to return raw data, but it sould be implemented to return a Bitmap, see following circuitpython example:
//
// int width = common_hal_espcamera_camera_get_width(self);
// int height = common_hal_espcamera_camera_get_height(self);
// displayio_bitmap_t *bitmap = m_new_obj(displayio_bitmap_t);
// bitmap->base.type = &displayio_bitmap_type;
// common_hal_displayio_bitmap_construct_from_buffer(bitmap, width, height, (format == PIXFORMAT_RGB565) ? 16 : 8, (uint32_t *)(void *)result->buf, true);
// return bitmap;
if (!self->captured_buffer) {
ESP_LOGE(TAG, "Failed to capture image");
return mp_const_none;
}

if (out_format >= 0 && (mp_camera_pixformat_t)out_format != self->camera_config.pixel_format) {
switch (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:
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 (self->bmp_out == false) {
ESP_LOGI(TAG, "Returning imgae without conversion");
return mp_obj_new_memoryview('b', self->captured_buffer->len, self->captured_buffer->buf);
} else {
esp_camera_fb_return(self->captured_buffer);
self->captured_buffer = NULL;
return mp_const_none;
ESP_LOGI(TAG, "Returning image as bitmap");
if (frame2bmp(self->captured_buffer, &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 {
mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Failed to convert image to BMP"));
return mp_const_none;
}
}
}

Expand All @@ -289,6 +343,7 @@ const mp_rom_map_elem_t mp_camera_hal_pixel_format_table[] = {
{ MP_ROM_QSTR(MP_QSTR_YUV422), MP_ROM_INT(PIXFORMAT_YUV422) },
{ MP_ROM_QSTR(MP_QSTR_GRAYSCALE), MP_ROM_INT(PIXFORMAT_GRAYSCALE) },
{ MP_ROM_QSTR(MP_QSTR_RGB565), MP_ROM_INT(PIXFORMAT_RGB565) },
{ MP_ROM_QSTR(MP_QSTR_RGB888), MP_ROM_INT(PIXFORMAT_RGB888) },
};

const mp_rom_map_elem_t mp_camera_hal_frame_size_table[] = {
Expand Down Expand Up @@ -418,16 +473,24 @@ void mp_camera_hal_set_frame_size(mp_camera_obj_t * self, framesize_t value) {
if (!self->initialized) {
mp_raise_ValueError(MP_ERROR_TEXT("Camera not initialized"));
}

sensor_t *sensor = esp_camera_sensor_get();
if (!sensor->set_framesize) {
mp_raise_ValueError(MP_ERROR_TEXT("No attribute frame_size"));
}

if (self->captured_buffer) {
esp_camera_return_all();
self->captured_buffer = NULL;
}

if (sensor->set_framesize(sensor, value) < 0) {
mp_raise_ValueError(MP_ERROR_TEXT("Invalid setting for frame_size"));
} else {
self->camera_config.frame_size = value;
}
}

int mp_camera_hal_get_quality(mp_camera_obj_t * self) {
if (!self->initialized) {
mp_raise_ValueError(MP_ERROR_TEXT("Camera not initialized"));
Expand Down
7 changes: 4 additions & 3 deletions src/modcamera.h
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ typedef struct hal_camera_obj {
camera_config_t camera_config;
bool initialized;
camera_fb_t *captured_buffer;
bool bmp_out;
} hal_camera_obj_t;

#endif // CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
Expand Down Expand Up @@ -194,16 +195,16 @@ extern void mp_camera_hal_reconfigure(mp_camera_obj_t *self, mp_camera_framesize
* @brief Captures an image and returns it as mp_obj_t (e.g. mp_obj_new_memoryview).
*
* @param self Pointer to the camera object.
* @param timeout_ms Timeout in milliseconds.
* @param out_format Output pixelformat format.
* @return Captured image as micropython object.
*/
extern mp_obj_t mp_camera_hal_capture(mp_camera_obj_t *self, int timeout_ms);
extern mp_obj_t mp_camera_hal_capture(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.
*/
extern const mp_rom_map_elem_t mp_camera_hal_pixel_format_table[4];
extern const mp_rom_map_elem_t mp_camera_hal_pixel_format_table[5];

/**
* @brief Table mapping frame sizes API to their corresponding values at HAL.
Expand Down
Loading