Skip to content

Commit

Permalink
Img converter to develop (#10)
Browse files Browse the repository at this point in the history
Add image conversion feature to capture method (experimental feature)
  • Loading branch information
cnadler86 authored Oct 29, 2024
1 parent 76a89ea commit 9f715c0
Show file tree
Hide file tree
Showing 7 changed files with 231 additions and 37 deletions.
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

0 comments on commit 9f715c0

Please sign in to comment.