From 0e26528bddaced8fa3d4056f4328a5d4c9d01e47 Mon Sep 17 00:00:00 2001 From: Christopher Nadler <147471517+cnadler86@users.noreply.github.com> Date: Wed, 6 Nov 2024 18:47:23 +0100 Subject: [PATCH 01/43] README.md aktualisieren --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index afba7ee..d6a975c 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ cam = Camera( - data_pins: List of data pins - pclk_pin: Pixel clock pin - -vsync_pin: VSYNC pin +- vsync_pin: VSYNC pin - href_pin: HREF pin - sda_pin: SDA pin - scl_pin: SCL pin @@ -72,7 +72,7 @@ cam = Camera( - fb_count: Frame buffer count - grab_mode: Grab mode as GrabMode - init: Initialize camera at construction time (default: True) -- bmp_out: Image capture output converted to bitmap (default: False) +- bmp_out: Image captured output converted to bitmap (default: False) **Default values:** From 12708fef5e6aad3452fd374e706477a9863ebeb9 Mon Sep 17 00:00:00 2001 From: Christopher Nadler <147471517+cnadler86@users.noreply.github.com> Date: Wed, 6 Nov 2024 18:49:10 +0100 Subject: [PATCH 02/43] README.md aktualisieren --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d6a975c..d2ba530 100644 --- a/README.md +++ b/README.md @@ -64,8 +64,8 @@ cam = Camera( - scl_pin: SCL pin - xclk_pin: XCLK pin - xclk_freq: XCLK frequency in Hz -- powerdown_pin: Powerdown pin (default: -1, meaning not used) -- reset_pin: Reset pin (default: -1, meaning not used) +- powerdown_pin: Powerdown pin +- reset_pin: Reset pin - pixel_format: Pixel format as PixelFormat - frame_size: Frame size as FrameSize - jpeg_quality: JPEG quality @@ -82,7 +82,7 @@ The following keyword arguments have default values: - frame_size: QQVGA - pixel_format: RGB565 - jpeg_quality: 85 // Quality of JPEG output in percent. Higher means higher quality. -- powerdown_pin and reset_pin: -1 (not used/available/needed) +- powerdown_pin and reset_pin: -1 ( = not used/available/needed) - fb_count: - 2 for ESP32S3 boards - 1 for all other From eae724742e16cf63dd588a7c30a8cd7cb9623d9f Mon Sep 17 00:00:00 2001 From: Christopher Nadler <147471517+cnadler86@users.noreply.github.com> Date: Tue, 19 Nov 2024 06:28:09 +0100 Subject: [PATCH 03/43] Update issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 24 ++++++++++++++++++++++++ .github/ISSUE_TEMPLATE/general-issue.md | 10 ++++++++++ 2 files changed, 34 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/general-issue.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..d612a9d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,24 @@ +--- +name: Bug report +about: Report a bug and help improving the API +title: '' +labels: bug +assignees: cnadler86 + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Code to reproduce + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Environment:** + - Board: e.g. ESP32S3 XIAO + - Camera drivers version: e.g. 2.0.15 + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/general-issue.md b/.github/ISSUE_TEMPLATE/general-issue.md new file mode 100644 index 0000000..5460346 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/general-issue.md @@ -0,0 +1,10 @@ +--- +name: General issue +about: General issue +title: '' +labels: '' +assignees: '' + +--- + +PLEASE, read the documentation, search [previous issues](https://github.com/cnadler86/micropython-camera-API/issues?q=is%3Aissue) and/or ask ChatGPT before! From 176b0a332cedf6a5f39c831fae06798ae781d9d4 Mon Sep 17 00:00:00 2001 From: Christopher Nadler Date: Mon, 2 Dec 2024 05:51:00 +0100 Subject: [PATCH 04/43] - Fix: image convertes - Add: Camera version driver - Performance improvements - Minor imprvements --- .github/workflows/ESP32.yml | 10 +- README.md | 74 ++++++---- examples/CameraSettings.py | 3 +- examples/benchmark.py | 21 +-- examples/benchmark_img_conv.py | 107 ++++++++++++++ src/micropython.cmake | 25 ++-- src/modcamera.c | 253 ++++++++++++++------------------- src/modcamera.h | 9 -- src/modcamera_api.c | 16 ++- tests/esp32_test.py | 95 ++++++++++--- 10 files changed, 384 insertions(+), 229 deletions(-) create mode 100644 examples/benchmark_img_conv.py diff --git a/.github/workflows/ESP32.yml b/.github/workflows/ESP32.yml index 9548001..65a89cc 100644 --- a/.github/workflows/ESP32.yml +++ b/.github/workflows/ESP32.yml @@ -78,7 +78,9 @@ jobs: cd esp-idf ./install.sh all cd components - git clone https://github.com/cnadler86/esp32-camera + latest_cam_driver=$(curl -s https://api.github.com/repos/espressif/esp32-camera/releases/latest | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') + git clone --depth 1 --branch $latest_cam_driver https://github.com/espressif/esp32-camera.git + # git clone https://github.com/cnadler86/esp32-camera.git cd ~/esp-idf/ source ./export.sh @@ -145,6 +147,8 @@ jobs: # Build MicroPython for each board - name: Build MicroPython run: | + cd ~/esp-idf/components/esp32-camera + CAM_DRIVER=$(git describe --tags --always --dirty) cd ~/micropython/ports/esp32 source ~/esp-idf/export.sh @@ -153,9 +157,9 @@ jobs: IFS='-' read -r BOARD_NAME BOARD_VARIANT <<< "${BUILD_TARGET}" if [ -n "${BOARD_VARIANT}" ]; then - IDF_CMD="idf.py -D MICROPY_BOARD=$BOARD_NAME -D USER_C_MODULES=${{ github.workspace }}/src/micropython.cmake -D MICROPY_BOARD_VARIANT=$BOARD_VARIANT -B build-$BUILD_TARGET" + IDF_CMD="idf.py -D MICROPY_BOARD=$BOARD_NAME -D USER_C_MODULES=${{ github.workspace }}/src/micropython.cmake -D MICROPY_BOARD_VARIANT=$BOARD_VARIANT -B build-$BUILD_TARGET -D MP_CAMERA_DRIVER_VERSION=$CAM_DRIVER" else - IDF_CMD="idf.py -D MICROPY_BOARD=$BOARD_NAME -D USER_C_MODULES=${{ github.workspace }}/src/micropython.cmake -B build-$BUILD_TARGET" + IDF_CMD="idf.py -D MICROPY_BOARD=$BOARD_NAME -D USER_C_MODULES=${{ github.workspace }}/src/micropython.cmake -B build-$BUILD_TARGET -D MP_CAMERA_DRIVER_VERSION=$CAM_DRIVER" fi if [ -n "${CAMERA_MODEL}" ]; then echo "FW_NAME=${CAMERA_MODEL}" >> $GITHUB_ENV diff --git a/README.md b/README.md index d2ba530..6df5c3e 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![ESP32 Port](https://github.com/cnadler86/micropython-camera-API/actions/workflows/ESP32.yml/badge.svg)](https://github.com/cnadler86/micropython-camera-API/actions/workflows/ESP32.yml) -This project aims to support various cameras on different MicroPython ports, starting with the ESP32 port and Omnivision (OV2640 & OV5640) cameras. The project implements a general API for cameras in micropython (such as circuitpython have done). +This project aims to support various cameras (e.g. OV2640, OV5640) on different MicroPython ports, starting with the ESP32 port. The project implements a general API, has precompiled FW images and supports a lot of cameras out of the box. At the moment, this is a micropython user module, but it might get in the micropython repo in the future. The API is stable, but it might change without previous announce. @@ -134,6 +134,17 @@ See autocompletions in Thonny in order to see the list of methods. If you want more insides in the methods and what they actually do, you can find a very good documentation [here](https://docs.circuitpython.org/en/latest/shared-bindings/espcamera/index.html). Note that each method requires a "get_" or "set_" prefix, depending on the desired action. +To get the version of the camera driver used: + +```python +import camera +vers = camera.Version() +``` + +### Additional information + +The FW images support the following cameras out of the box, but is therefore big: OV7670, OV7725, OV2640, OV3660, OV5640, NT99141, GC2145, GC032A, GC0308, BF3005, BF20A6, SC030IOT + ## Build your custom FW ### Setting up the build environment (DIY method) @@ -142,20 +153,20 @@ To build the project, follow these instructions: - [ESP-IDF](https://docs.espressif.com/projects/esp-idf/en/v5.2.3/esp32/get-started/index.html): I used version 5.2.3, but it might work with other versions (see notes). - Clone the micropython repo and this repo in a folder, e.g. "MyESPCam". MicroPython version 1.24 or higher is required (at least commit 92484d8). -- You will have to add the ESP32-Camera driver (I used v2.0.13). To do this, add the following to the respective idf_component.yml file (e.g. in micropython/ports/esp32/main_esp32s3/idf_component.yml): +- You will have to add the ESP32-Camera driver (I used v2.0.15). To do this, add the following to the respective idf_component.yml file (e.g. in micropython/ports/esp32/main_esp32s3/idf_component.yml): ```yml espressif/esp32-camera: - git: https://github.com/cnadler86/esp32-camera #At the moment I maintain a fork because of some unsolved bugs and conveniance. + git: https://github.com/espressif/esp32-camera.git ``` -Alternatively, you can clone the repository inside the esp-idf/components folder instead of altering the idf_component.yml file. +Alternatively, you can clone the repository inside the esp-idf/components folder instead of altering the idf_component.yml file. ### Add camera configurations to your board (optional, but recommended) #### Supported Camera Models -This project supports various camera models out of the box. You typically only need to add a single line to your board config file ("mpconfigboard.h). +This project supports various boards with camera interface out of the box. You typically only need to add a single line to your board config file ("mpconfigboard.h). Example (don't forget to add the empty line at the bottom): ```c @@ -188,7 +199,6 @@ Below is a list of supported `MICROPY_CAMERA_MODEL_xxx` definitions: #### For unsupported camera models If your board is not yet supported, add the following lines to your board config-file "mpconfigboard.h" with the respective pins and camera parameters. Otherwise, you will need to pass all parameters during construction. -Don't forget the empty line at the bottom. Example for Xiao sense: ```c @@ -214,6 +224,9 @@ Example for Xiao sense: #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 + +If you want to customize additional camera setting or reduce the FW size by removing support for unused camera sensors, then take a look at the kconfig file of the esp32-camera driver and specify these on the sdkconfig file of your board. ### Build the API @@ -239,28 +252,37 @@ 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). -Using fb_count=2 doubles the FPS for JPEG. This might also aplly for other PixelFormats. - -| Frame Size | GRAYSCALE | RGB565 | YUV422 | JPEG | JPEG (fb = 2) | -|------------|-----------|--------|--------|--------|---------------| -| R96X96 | 12.5 | 12.5 | 12.5 | No img | No img | -| QQVGA | 12.5 | 12.5 | 12.5 | 25 | 50 | -| QCIF | 11 | 11 | 11.5 | 25 | 50 | -| HQVGA | 12.5 | 12.5 | 12.5 | 25 | 50 | -| R240X240 | 12 | 12.5 | 11.5 | 25 | 50 | -| QVGA | 12 | 11 | 12 | 25 | 50 | -| CIF | 12.5 | No img | No img | 6 | 12.5 | -| HVGA | 2.5 | 3 | 2.5 | 12.5 | 25 | -| VGA | 3 | 3 | 3 | 12.5 | 25 | -| SVGA | 3 | 3 | 3 | 12.5 | 25 | -| XGA | No img | No img | No img | 6 | 12.5 | -| HD | No img | No img | No img | 6 | 12.5 | -| SXGA | 2 | 2 | 2 | 6 | 12.5 | -| UXGA | No img | No img | No img | 6 | 12.5 | +I didn't use a calibrated osziloscope, but here is a benchmark with my ESP32S3 (GrabMode=LATEST, fb_count = 1, jpeg_quality=85%). +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) | +|------------|-----------|--------|--------|--------|----------------|----------------|-------------| +| R96X96 | 12.5 | 12.5 | 12.5 | No img | No img | No img | No img | +| QQVGA | 12.5 | 12.5 | 12.5 | 25 | 25 | 25 | 50 | +| 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 | +| 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 | +| XGA | No img | No img | No img | 6.3 | 1.6 | 1.6 | 12.5 | +| HD | No img | No img | No img | 6.3 | 1.4 | 1.3 | 12.5 | +| SXGA | 2 | 2 | 2 | 6.3 | 1 | 1 | 12.5 | +| UXGA | No img | No img | No img | 6.3 | 0.7 | 0.7 | 12.5 | + + +Looking at the results: image conversion make only sense for frame sized below QVGA or if capturing the image in the intended pixelformat and frame size combination fails. + +## Troubleshoot + +You can find information on the following sites: +- [ESP-FAQ](https://docs.espressif.com/projects/esp-faq/en/latest/application-solution/camera-application.html) +- [ChatGPT](https://chatgpt.com/) +- [Issues in here](https://github.com/cnadler86/micropython-camera-API/issues?q=is%3Aissue) ## Future Plans - Edge case: enable usage of pins such as i2c for other applications - Provide examples in binary image -- Include camera driver version in API diff --git a/examples/CameraSettings.py b/examples/CameraSettings.py index 26512c5..21f50c4 100644 --- a/examples/CameraSettings.py +++ b/examples/CameraSettings.py @@ -28,7 +28,8 @@ async def stream_camera(writer): cam.init() if not cam.get_bmp_out() and cam.get_pixel_format() != PixelFormat.JPEG: cam.set_bmp_out(True) - + await asyncio.sleep(1) + writer.write(b'HTTP/1.1 200 OK\r\nContent-Type: multipart/x-mixed-replace; boundary=frame\r\n\r\n') await writer.drain() diff --git a/examples/benchmark.py b/examples/benchmark.py index 806a204..1ba7aac 100644 --- a/examples/benchmark.py +++ b/examples/benchmark.py @@ -5,20 +5,21 @@ gc.enable() def measure_fps(duration=2): - start_time = time.time() - while time.time() - start_time < 0.5: + start_time = time.ticks_ms() + while time.ticks_ms() - start_time < 500: cam.capture() - start_time = time.time() + start_time = time.ticks_ms() frame_count = 0 - while time.time() - start_time < duration: - cam.capture() - frame_count += 1 + while time.ticks_ms() - start_time < duration*1000: + img = cam.capture() + if img: + frame_count += 1 - end_time = time.time() - fps = frame_count / (end_time - start_time) - return fps + end_time = time.ticks_ms() + fps = frame_count / (end_time - start_time) * 1000 + return round(fps,1) def print_summary_table(results, cam): print(f"\nBenchmark {os.uname().machine} with {cam.get_sensor_name()}, GrabMode: {cam.get_grab_mode()}:") @@ -78,7 +79,7 @@ def print_summary_table(results, cam): print('Set', p, f,f'fb={fb}',':') try: - cam.reconfigure(frame_size=f_value) + cam.reconfigure(frame_size=f_value) #set_frame_size fails for YUV422 time.sleep_ms(10) img = cam.capture() diff --git a/examples/benchmark_img_conv.py b/examples/benchmark_img_conv.py new file mode 100644 index 0000000..9a34b64 --- /dev/null +++ b/examples/benchmark_img_conv.py @@ -0,0 +1,107 @@ +from camera import Camera, FrameSize, PixelFormat +import time +import gc +import os +gc.enable() + +def measure_fps(cam,out_fmt,duration=2): + start_time = time.ticks_ms() + while time.ticks_ms() - start_time < 500: + cam.capture(out_fmt) + + start_time = time.ticks_ms() + frame_count = 0 + + while time.ticks_ms() - start_time < duration*1000: + img = cam.capture(out_fmt) + if img: + frame_count += 1 + + end_time = time.ticks_ms() + fps = frame_count / (end_time - start_time) * 1000 + return round(fps,1) + +def print_summary_table(results, cam): + print(f"\nBenchmark {os.uname().machine} with {cam.get_sensor_name()}, GrabMode: {cam.get_grab_mode()}:") + + fb_counts = sorted(results.keys()) + frame_size_names = {getattr(FrameSize, f): f for f in dir(FrameSize) if not f.startswith('_')} + + header_row = f"{'Frame Size':<15}" + sub_header_row = " " * 15 + + for fb in fb_counts: + for p in results[fb].keys(): + header_row += f"{'fb_count ' + str(fb):<15}" + sub_header_row += f"{p:<15}" + + print(header_row) + print(sub_header_row) + + frame_sizes = list(next(iter(next(iter(results.values())).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 fb in fb_counts: + for p in results[fb].keys(): + fps = results[fb][p].get(f, "N/A") + print(f"{fps:<15}", end="") + print() + +if __name__ == "__main__": + cam = Camera(pixel_format=PixelFormat.JPEG) + results = {} + + try: + for fb in [1, 2]: + cam.reconfigure(fb_count=fb, frame_size=FrameSize.QQVGA) + results[fb] = {} + for p in dir(PixelFormat): + if not p.startswith('_'): + p_value = getattr(PixelFormat, p) + try: + if p_value == PixelFormat.JPEG: + continue + cam.capture(p_value) + results[fb][p] = {} + gc.collect() + except: + 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,f'fb={fb}',':') + + try: + cam.set_frame_size(f_value) + time.sleep_ms(10) + img = cam.capture(p_value) + if img: + print('---> Image size:', len(img)) + fps = measure_fps(cam,p_value,2) + print(f"---> FPS: {fps}") + results[fb][p][f_value] = fps + else: + print('No image captured') + results[fb][p][f_value] = 'No img' + + print(f"---> Free Memory: {gc.mem_free()}") + except Exception as e: + print('ERR:', e) + results[fb][p][f_value] = 'ERR' + finally: + time.sleep_ms(250) + gc.collect() + print('') + + except KeyboardInterrupt: + print("\nScript interrupted by user.") + + finally: + print_summary_table(results, cam) + cam.deinit() diff --git a/src/micropython.cmake b/src/micropython.cmake index 68c68c8..66fd308 100644 --- a/src/micropython.cmake +++ b/src/micropython.cmake @@ -8,19 +8,28 @@ target_sources(usermod_mp_camera INTERFACE ${CMAKE_CURRENT_LIST_DIR}/modcamera_api.c ) -target_include_directories(usermod_mp_camera INTERFACE - ${CMAKE_CURRENT_LIST_DIR} - ${IDF_PATH}/components/esp32-camera/driver/include - ${IDF_PATH}/components/esp32-camera/driver/private_include - ${IDF_PATH}/components/esp32-camera/conversions/include - ${IDF_PATH}/components/esp32-camera/conversions/private_include - ${IDF_PATH}/components/esp32-camera/sensors/private_include -) +if(EXISTS "${IDF_PATH}/components/esp32-camera") + target_include_directories(usermod_mp_camera INTERFACE + ${CMAKE_CURRENT_LIST_DIR} + ${IDF_PATH}/components/esp32-camera/driver/include + ${IDF_PATH}/components/esp32-camera/driver/private_include + ${IDF_PATH}/components/esp32-camera/conversions/include + ${IDF_PATH}/components/esp32-camera/conversions/private_include + ${IDF_PATH}/components/esp32-camera/sensors/private_include + ) +else() + target_include_directories(usermod_mp_camera INTERFACE + ${CMAKE_CURRENT_LIST_DIR}) +endif() if (MICROPY_CAMERA_MODEL) target_compile_definitions(usermod_mp_camera INTERFACE MICROPY_CAMERA_MODEL_${MICROPY_CAMERA_MODEL}=1) endif() +if (MP_CAMERA_DRIVER_VERSION) + target_compile_definitions(usermod_mp_camera INTERFACE MP_CAMERA_DRIVER_VERSION=\"${MP_CAMERA_DRIVER_VERSION}\") +endif() + target_link_libraries(usermod INTERFACE usermod_mp_camera) micropy_gather_target_properties(usermod_mp_camera) \ No newline at end of file diff --git a/src/modcamera.c b/src/modcamera.c index 041554b..63611e0 100644 --- a/src/modcamera.c +++ b/src/modcamera.c @@ -29,51 +29,15 @@ #include "esp_err.h" #include "esp_log.h" #include "img_converters.h" +#include "mphalport.h" -#define TAG "ESP32_MPY_CAMERA" +#define TAG "MPY_CAMERA" #if !CONFIG_SPIRAM #error Camera only works on boards configured with spiram #endif -// #if !defined(CONFIG_CAMERA_CORE0) && !defined(CONFIG_CAMERA_CORE1) -// #if MP_TASK_COREID == 0 -// #define CONFIG_CAMERA_CORE0 1 -// #elif MP_TASK_COREID == 1 -// #define CONFIG_CAMERA_CORE1 1 -// #endif -// #endif // CONFIG_CAMERA_COREx - -// Supporting functions -static void raise_micropython_error_from_esp_err(esp_err_t err) { - switch (err) { - case ESP_OK: - return; - case ESP_ERR_NO_MEM: - mp_raise_msg(&mp_type_MemoryError, MP_ERROR_TEXT("Out of memory")); - break; - case ESP_ERR_INVALID_ARG: - mp_raise_ValueError(MP_ERROR_TEXT("Invalid argument")); - break; - case ESP_ERR_INVALID_STATE: - mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Invalid state")); - break; - case ESP_ERR_NOT_FOUND: - mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Camera not found")); - break; - case ESP_ERR_NOT_SUPPORTED: - mp_raise_NotImplementedError(MP_ERROR_TEXT("Operation/Function not supported/implemented")); - break; - case ESP_ERR_TIMEOUT: - mp_raise_OSError(MP_ETIMEDOUT); - 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")); - break; - } -} - +// Helper functions static int map(int value, int fromLow, int fromHigh, int toLow, int toHigh) { if (fromHigh == fromLow) { mp_raise_ValueError(MP_ERROR_TEXT("fromLow und fromHigh shall not be equal")); @@ -85,6 +49,59 @@ static inline int get_mapped_jpeg_quality(int8_t quality) { return map(quality, 0, 100, 63, 0); } +static inline void check_init(mp_camera_obj_t *self) { + if (!self->initialized) { + mp_raise_OSError(ENOENT); + } +} + +static void set_check_xclk_freq(mp_camera_obj_t *self, int32_t xclk_freq_hz) { + if ( xclk_freq_hz > 20000000) { + mp_raise_ValueError(MP_ERROR_TEXT("xclk frequency cannot be grather than 20MHz")); + } else { + self->camera_config.xclk_freq_hz = xclk_freq_hz; + } +} + +static void set_check_fb_count(mp_camera_obj_t *self, mp_int_t fb_count) { + if (fb_count > 2) { + self->camera_config.fb_count = 2; + mp_warning(NULL, "Frame buffer size limited to 2"); + } else if (fb_count < 1) { + self->camera_config.fb_count = 1; + mp_warning(NULL, "Frame buffer size must be >0. Setting it to 1"); + } + else { + self->camera_config.fb_count = fb_count; + } +} + +static void set_check_grab_mode(mp_camera_obj_t *self, mp_camera_grabmode_t grab_mode) { + if (grab_mode != CAMERA_GRAB_WHEN_EMPTY && grab_mode != CAMERA_GRAB_LATEST) { + mp_raise_ValueError(MP_ERROR_TEXT("Invalid grab_mode")); + } else { + self->camera_config.grab_mode = grab_mode; + } +} + +static void set_check_pixel_format(mp_camera_obj_t *self, mp_camera_pixformat_t pixel_format) { + if ( pixel_format > PIXFORMAT_RGB555) { //Maximal enum value, but validation should be better since wrong pixelformat leads to reboot. + mp_raise_ValueError(MP_ERROR_TEXT("Invalid pixel_format")); + } else { + self->camera_config.pixel_format = pixel_format; + } +} + +static bool init_camera(mp_camera_obj_t *self) { + // Correct the quality before it is passed to esp32 driver and then "undo" the correction in the camera_config + int8_t api_jpeg_quality = self->camera_config.jpeg_quality; + self->camera_config.jpeg_quality = get_mapped_jpeg_quality(api_jpeg_quality); + esp_err_t err = esp_camera_init(&self->camera_config); + self->camera_config.jpeg_quality = api_jpeg_quality; + check_esp_err_(err); + return true; +} + // Camera HAL Funcitons void mp_camera_hal_construct( mp_camera_obj_t *self, @@ -104,10 +121,6 @@ void mp_camera_hal_construct( int8_t fb_count, mp_camera_grabmode_t grab_mode) { // configure camera based on arguments - self->camera_config.pixel_format = pixel_format; - self->camera_config.frame_size = frame_size; - // self->camera_config.jpeg_quality = (int8_t)map(jpeg_quality,0,100,63,0); //0-63 lower number means higher quality. - self->camera_config.jpeg_quality = jpeg_quality; //save value in here, but will be corrected (with map) before passing it to the esp32-driver self->camera_config.pin_d0 = data_pins[0]; self->camera_config.pin_d1 = data_pins[1]; self->camera_config.pin_d2 = data_pins[2]; @@ -124,23 +137,14 @@ void mp_camera_hal_construct( self->camera_config.pin_xclk = external_clock_pin; self->camera_config.pin_sscb_sda = sccb_sda_pin; self->camera_config.pin_sscb_scl = sccb_scl_pin; - if ( xclk_freq_hz > 20000000) { - mp_raise_ValueError(MP_ERROR_TEXT("xclk frequency cannot be grather than 20MHz")); - } else { - self->camera_config.xclk_freq_hz = xclk_freq_hz; - } - if (fb_count > 3) { - self->camera_config.fb_count = 3; - mp_warning(NULL, "Frame buffer size limited to 3"); - } else if (fb_count < 1) { - self->camera_config.fb_count = 1; - mp_warning(NULL, "Frame buffer size must be >0. Setting it to 1"); - } - else { - self->camera_config.fb_count = fb_count; //if more than one, i2s runs in continuous mode. TODO: Test with others than JPEG - } - self->camera_config.grab_mode = grab_mode; + self->camera_config.frame_size = frame_size; + self->camera_config.jpeg_quality = jpeg_quality; //save value in here, but will be corrected (with map) before passing it to the esp32-driver + + set_check_pixel_format(self, pixel_format); + set_check_xclk_freq(self, xclk_freq_hz); + set_check_fb_count(self, fb_count); + set_check_grab_mode(self, grab_mode); // defaul parameters self->camera_config.fb_location = CAMERA_FB_IN_PSRAM; @@ -161,19 +165,8 @@ void mp_camera_hal_init(mp_camera_obj_t *self) { } #endif ESP_LOGI(TAG, "Initializing camera"); - // Correct the quality before it is passed to esp32 driver and then "undo" the correction in the camera_config - int8_t api_jpeg_quality = self->camera_config.jpeg_quality; - self->camera_config.jpeg_quality = get_mapped_jpeg_quality(api_jpeg_quality); - esp_err_t err = esp_camera_init(&self->camera_config); - self->camera_config.jpeg_quality = api_jpeg_quality; - if (err != ESP_OK) { - self->initialized = false; - raise_micropython_error_from_esp_err(err); - return; - } - self->initialized = true; + self->initialized = init_camera(self); ESP_LOGI(TAG, "Camera initialized successfully"); - return; } void mp_camera_hal_deinit(mp_camera_obj_t *self) { @@ -183,73 +176,37 @@ void mp_camera_hal_deinit(mp_camera_obj_t *self) { self->captured_buffer = NULL; } esp_err_t err = esp_camera_deinit(); - raise_micropython_error_from_esp_err(err); + check_esp_err_(err); self->initialized = false; ESP_LOGI(TAG, "Camera deinitialized"); } } void mp_camera_hal_reconfigure(mp_camera_obj_t *self, mp_camera_framesize_t frame_size, mp_camera_pixformat_t pixel_format, mp_camera_grabmode_t grab_mode, mp_int_t fb_count) { - if (self->initialized) { - ESP_LOGI(TAG, "Reconfiguring camera"); - sensor_t *sensor = esp_camera_sensor_get(); - camera_sensor_info_t *sensor_info = esp_camera_sensor_get_info(&sensor->id); - - if (PIXFORMAT_JPEG == self->camera_config.pixel_format && (!sensor_info->support_jpeg)) { - mp_raise_NotImplementedError(MP_ERROR_TEXT("Sensor does not support JPEG")); - } + check_init(self); + ESP_LOGI(TAG, "Reconfiguring camera with frame size: %d, pixel format: %d, grab mode: %d, fb count: %d", (int)frame_size, (int)pixel_format, (int)grab_mode, (int)fb_count); + + sensor_t *sensor = esp_camera_sensor_get(); + camera_sensor_info_t *sensor_info = esp_camera_sensor_get_info(&sensor->id); + if (frame_size > sensor_info->max_size) { + mp_warning(NULL, "Frame size will be scaled down to maximal frame size supported by the camera sensor"); + self->camera_config.frame_size = sensor_info->max_size; + } else { + self->camera_config.frame_size = frame_size; + } - if (frame_size > sensor_info->max_size) { - mp_warning(NULL, "Frame size will be scaled down to maximal frame size supported by the camera sensor"); - self->camera_config.frame_size = sensor_info->max_size; - } else { - self->camera_config.frame_size = frame_size; - } + set_check_pixel_format(self, pixel_format); + set_check_grab_mode(self, grab_mode); + set_check_fb_count(self, fb_count); - if ( pixel_format > PIXFORMAT_RGB555) { //Maximal enum value, but validation should be better since wrong pixelformat leads to reboot. - mp_raise_ValueError(MP_ERROR_TEXT("Invalid pixel_format")); - } else { - self->camera_config.pixel_format = pixel_format; - } - - if (grab_mode != CAMERA_GRAB_WHEN_EMPTY && grab_mode != CAMERA_GRAB_LATEST) { - mp_raise_ValueError(MP_ERROR_TEXT("Invalid grab_mode")); - } else { - self->camera_config.grab_mode = grab_mode; - } - - if (fb_count > 2) { - self->camera_config.fb_count = 2; - mp_warning(NULL, "Frame buffer size limited to 2"); - } else if (fb_count < 1) { - self->camera_config.fb_count = 1; - mp_warning(NULL, "Set to min frame buffer size of 1"); - } - else { - self->camera_config.fb_count = fb_count; - } - - raise_micropython_error_from_esp_err(esp_camera_deinit()); - - // Correct the quality before it is passed to esp32 driver and then "undo" the correction in the camera_config - int8_t api_jpeg_quality = self->camera_config.jpeg_quality; - self->camera_config.jpeg_quality = get_mapped_jpeg_quality(api_jpeg_quality); - esp_err_t err = esp_camera_init(&self->camera_config); - self->camera_config.jpeg_quality = api_jpeg_quality; - - if (err != ESP_OK) { - self->initialized = false; - raise_micropython_error_from_esp_err(err); - } else { - ESP_LOGI(TAG, "Camera reconfigured successfully"); - } - } + check_esp_err_(esp_camera_deinit()); + self->initialized = false; + self->initialized = init_camera(self); + ESP_LOGI(TAG, "Camera reconfigured successfully"); } 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")); - } + check_init(self); if (self->captured_buffer) { esp_camera_fb_return(self->captured_buffer); self->captured_buffer = NULL; @@ -271,6 +228,7 @@ 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)) { @@ -297,6 +255,12 @@ mp_obj_t mp_camera_hal_capture(mp_camera_obj_t *self, int8_t out_format) { } 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); @@ -318,7 +282,7 @@ mp_obj_t mp_camera_hal_capture(mp_camera_obj_t *self, int8_t out_format) { } if (self->bmp_out == false) { - ESP_LOGI(TAG, "Returning imgae without conversion"); + ESP_LOGI(TAG, "Returning image without conversion"); return mp_obj_new_memoryview('b', self->captured_buffer->len, self->captured_buffer->buf); } else { ESP_LOGI(TAG, "Returning image as bitmap"); @@ -334,7 +298,7 @@ mp_obj_t mp_camera_hal_capture(mp_camera_obj_t *self, int8_t out_format) { return mp_const_none; } } -} +} // mp_camera_hal_capture bool mp_camera_hal_initialized(mp_camera_obj_t *self){ return self->initialized; @@ -407,18 +371,14 @@ const mp_rom_map_elem_t mp_camera_hal_gainceiling_table[] = { #define SENSOR_GET(type, name, status_field_name) \ type mp_camera_hal_get_##name(mp_camera_obj_t * self) { \ - if (!self->initialized) { \ - mp_raise_ValueError(MP_ERROR_TEXT("Camera not initialized")); \ - } \ + check_init(self); \ sensor_t *sensor = esp_camera_sensor_get(); \ return sensor->status_field_name; \ } #define SENSOR_SET(type, name, setter_function_name) \ void mp_camera_hal_set_##name(mp_camera_obj_t * self, type value) { \ - if (!self->initialized) { \ - mp_raise_ValueError(MP_ERROR_TEXT("Camera not initialized")); \ - } \ + check_init(self); \ sensor_t *sensor = esp_camera_sensor_get(); \ if (!sensor->setter_function_name) { \ mp_raise_ValueError(MP_ERROR_TEXT("No attribute " #name)); \ @@ -431,9 +391,7 @@ const mp_rom_map_elem_t mp_camera_hal_gainceiling_table[] = { #define SENSOR_SET_IN_RANGE(type, name, setter_function_name, min_val, max_val) \ void mp_camera_hal_set_##name(mp_camera_obj_t * self, type value) { \ sensor_t *sensor = esp_camera_sensor_get(); \ - if (!self->initialized) { \ - mp_raise_ValueError(MP_ERROR_TEXT("Camera not initialized")); \ - } \ + check_init(self); \ if (value < min_val || value > max_val) { \ mp_raise_ValueError(MP_ERROR_TEXT(#name " value must be between " #min_val " and " #max_val)); \ } \ @@ -472,10 +430,7 @@ SENSOR_STATUS_GETSET(bool, raw_gma, raw_gma, set_raw_gma); SENSOR_STATUS_GETSET(bool, lenc, lenc, set_lenc); 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")); - } - + check_init(self); sensor_t *sensor = esp_camera_sensor_get(); if (!sensor->set_framesize) { mp_raise_ValueError(MP_ERROR_TEXT("No attribute frame_size")); @@ -494,16 +449,12 @@ void mp_camera_hal_set_frame_size(mp_camera_obj_t * self, framesize_t value) { } int mp_camera_hal_get_quality(mp_camera_obj_t * self) { - if (!self->initialized) { - mp_raise_ValueError(MP_ERROR_TEXT("Camera not initialized")); - } + check_init(self); return self->camera_config.jpeg_quality; } void mp_camera_hal_set_quality(mp_camera_obj_t * self, int value) { - if (!self->initialized) { - mp_raise_ValueError(MP_ERROR_TEXT("Camera not initialized")); - } + check_init(self); sensor_t *sensor = esp_camera_sensor_get(); if (!sensor->set_quality) { mp_raise_ValueError(MP_ERROR_TEXT("No attribute quality")); @@ -528,36 +479,42 @@ int mp_camera_hal_get_fb_count(mp_camera_obj_t *self) { } const char *mp_camera_hal_get_sensor_name(mp_camera_obj_t *self) { + check_init(self); sensor_t *sensor = esp_camera_sensor_get(); camera_sensor_info_t *sensor_info = esp_camera_sensor_get_info(&sensor->id); return sensor_info->name; } bool mp_camera_hal_get_supports_jpeg(mp_camera_obj_t *self) { + check_init(self); sensor_t *sensor = esp_camera_sensor_get(); camera_sensor_info_t *sensor_info = esp_camera_sensor_get_info(&sensor->id); return sensor_info->support_jpeg; } mp_camera_framesize_t mp_camera_hal_get_max_frame_size(mp_camera_obj_t *self) { + check_init(self); sensor_t *sensor = esp_camera_sensor_get(); camera_sensor_info_t *sensor_info = esp_camera_sensor_get_info(&sensor->id); return sensor_info->max_size; } int mp_camera_hal_get_address(mp_camera_obj_t *self) { + check_init(self); sensor_t *sensor = esp_camera_sensor_get(); camera_sensor_info_t *sensor_info = esp_camera_sensor_get_info(&sensor->id); return sensor_info->sccb_addr; } int mp_camera_hal_get_pixel_width(mp_camera_obj_t *self) { + check_init(self); sensor_t *sensor = esp_camera_sensor_get(); framesize_t framesize = sensor->status.framesize; return resolution[framesize].width; } int mp_camera_hal_get_pixel_height(mp_camera_obj_t *self) { + check_init(self); sensor_t *sensor = esp_camera_sensor_get(); framesize_t framesize = sensor->status.framesize; return resolution[framesize].height; diff --git a/src/modcamera.h b/src/modcamera.h index 1e1305b..e7d557b 100644 --- a/src/modcamera.h +++ b/src/modcamera.h @@ -86,15 +86,6 @@ defined (MICROPY_CAMERA_PIN_PCLK) && defined (MICROPY_CAMERA_PIN_VSYNC) && defin #define MICROPY_CAMERA_JPEG_QUALITY (85) #endif -//Supported Camera sensors -#ifndef CONFIG_OV2640_SUPPORT -#define CONFIG_OV2640_SUPPORT 1 -#endif - -#if !defined(CONFIG_OV5640_SUPPORT) && defined(CONFIG_IDF_TARGET_ESP32S3) -#define CONFIG_OV5640_SUPPORT 1 -#endif - typedef pixformat_t hal_camera_pixformat_t; typedef framesize_t hal_camera_framesize_t; typedef camera_fb_location_t hal_camera_fb_location_t; diff --git a/src/modcamera_api.c b/src/modcamera_api.c index fd52cec..71cc2b0 100644 --- a/src/modcamera_api.c +++ b/src/modcamera_api.c @@ -135,18 +135,16 @@ static mp_obj_t mp_camera_make_new(const mp_obj_type_t *type, size_t n_args, siz sda_pin, scl_pin, xclock_frequency, pixel_format, frame_size, jpeg_quality, fb_count, grab_mode); mp_camera_hal_init(self); - if (mp_camera_hal_capture(self, -1) == mp_const_none){ mp_camera_hal_deinit(self); mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Failed to capture initial frame. Construct a new object with appropriate configuration.")); - return MP_OBJ_FROM_PTR(self); } else { if ( !args[ARG_init].u_bool ){ mp_camera_hal_deinit(self); } return MP_OBJ_FROM_PTR(self); } -} +} // camera_construct // Main methods static mp_obj_t camera_capture(size_t n_args, const mp_obj_t *args){ @@ -182,7 +180,7 @@ static mp_obj_t camera_reconfigure(size_t n_args, const mp_obj_t *pos_args, mp_m args[ARG_grab_mode].u_obj != MP_ROM_NONE ? args[ARG_grab_mode].u_int : mp_camera_hal_get_grab_mode(self); - uint8_t fb_count = + mp_int_t fb_count = args[ARG_fb_count].u_obj != MP_ROM_NONE ? args[ARG_fb_count].u_int : mp_camera_hal_get_fb_count(self); @@ -366,6 +364,13 @@ MP_CREATE_CONST_TYPE(GainCeiling, mp_camera_gainceiling); static MP_DEFINE_CONST_DICT(mp_camera_grab_mode_locals_dict,mp_camera_hal_grab_mode_table); MP_CREATE_CONST_TYPE(GrabMode, mp_camera_grab_mode); +#ifdef MP_CAMERA_DRIVER_VERSION + static mp_obj_t mp_camera_driver_version(void) { + return mp_obj_new_str(MP_CAMERA_DRIVER_VERSION, strlen(MP_CAMERA_DRIVER_VERSION)); + } + static MP_DEFINE_CONST_FUN_OBJ_0(mp_camera_driver_version_obj, mp_camera_driver_version); +#endif + static const mp_rom_map_elem_t camera_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_camera) }, { MP_ROM_QSTR(MP_QSTR_Camera), MP_ROM_PTR(&camera_type) }, @@ -373,6 +378,9 @@ static const mp_rom_map_elem_t camera_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_FrameSize), MP_ROM_PTR(&mp_camera_frame_size_type) }, { MP_ROM_QSTR(MP_QSTR_GainCeiling), MP_ROM_PTR(&mp_camera_gainceiling_type) }, { MP_ROM_QSTR(MP_QSTR_GrabMode), MP_ROM_PTR(&mp_camera_grab_mode_type) }, + #ifdef MP_CAMERA_DRIVER_VERSION + { MP_ROM_QSTR(MP_QSTR_Version), MP_ROM_PTR(&mp_camera_driver_version_obj) }, + #endif }; static MP_DEFINE_CONST_DICT(camera_module_globals, camera_module_globals_table); diff --git a/tests/esp32_test.py b/tests/esp32_test.py index 23949fa..e29c79f 100644 --- a/tests/esp32_test.py +++ b/tests/esp32_test.py @@ -1,27 +1,82 @@ +import time from camera import Camera, FrameSize, PixelFormat def test_property_get_frame_size(): - cam = Camera() - Frame_Size = FrameSize.VGA - cam.reconfigure(frame_size=Frame_Size.VGA) - assert cam.get_frame_size == Frame_Size - assert cam.get_pixel_width == 640 - assert cam.get_pixel_height == 480 + with Camera() as cam: + print("Test frame size") + Frame_Size = FrameSize.VGA + cam.reconfigure(frame_size=Frame_Size) + assert cam.get_frame_size() == Frame_Size + assert cam.get_pixel_width() == 640 + assert cam.get_pixel_height() == 480 def test_property_get_pixel_format(): - cam = Camera() - Pixel_Format = PixelFormat.RGB565 - cam.reconfigure(pixel_format=PixelFormat.RGB) - assert cam.get_pixel_format == Pixel_Format + with Camera() as cam: + print("Test pixel format") + for Pixel_Format_Name in dir(PixelFormat): + Pixel_Format = getattr(PixelFormat, Pixel_Format_Name) + try: + if Pixel_Format_Name.startswith("_") or Pixel_Format_Name.startswith("RGB888"): + continue + cam.reconfigure(pixel_format=Pixel_Format) + assert cam.get_pixel_format() == Pixel_Format + except Exception: + print("\tFailed test for pixel format", Pixel_Format) +def test_must_be_initialized(): + with Camera(init=False) as cam: + print(f"Testing capture without initalization") + try: + cam.capture() + assert False, "Capture should have failed" + except Exception as e: + if e == "Camera not initialized": + assert False, "Capture should have failed" + else: + assert True + def test_camera_properties(): - cam = Camera() - for name in dir(cam): - if name.startswith('get_'): - prop_name = name[4:] - set_method_name = f'set_{prop_name}' - if hasattr(cam, set_method_name): - set_method = getattr(cam, set_method_name) - get_method = getattr(cam, name) - set_method(1) - assert get_method() == 1, f"Failed for property {prop_name}" \ No newline at end of file + with Camera() as cam: + print(f"Testing get/set methods") + for name in dir(cam): + if name.startswith('get_'): + prop_name = name[4:] + set_method_name = f'set_{prop_name}' + if hasattr(cam, set_method_name): + set_method = getattr(cam, set_method_name) + get_method = getattr(cam, name) + try: + set_method(1) + assert get_method() == 1 + except Exception: + print("\tFailed test for property", prop_name) + +def test_invalid_settings(): + print(f"Testing invalid settings") + invalid_settings = [ + {"xclk_freq": 21000000}, + {"frame_size": 23}, + {"pixel_format": 7}, + {"jpeg_quality": 101}, + {"jpeg_quality": -1}, + {"grab_mode": 3}, + ] + Delay= 10 + + for settings in invalid_settings: + param, invalid_value = next(iter(settings.items())) + try: + print("testing",param,"=",invalid_value) + time.sleep_ms(Delay) + with Camera(**{param: invalid_value}) as cam: + print(f"\tFailed test for {param} with value {invalid_value}") + except Exception as e: + time.sleep_ms(Delay) + +if __name__ == "__main__": + test_property_get_frame_size() + test_property_get_pixel_format() + test_must_be_initialized() + test_camera_properties() + test_invalid_settings() + From 7c2f524cd788847fd7085dbd7222811392f56a05 Mon Sep 17 00:00:00 2001 From: Christopher Nadler Date: Thu, 5 Dec 2024 05:58:35 +0100 Subject: [PATCH 05/43] Update Example and adding new frame sizes --- examples/CameraSettings.html | 49 +++++++++++++++++++----------------- src/modcamera.c | 2 ++ src/modcamera.h | 2 +- 3 files changed, 29 insertions(+), 24 deletions(-) diff --git a/examples/CameraSettings.html b/examples/CameraSettings.html index 27e7447..b5fde3d 100644 --- a/examples/CameraSettings.html +++ b/examples/CameraSettings.html @@ -102,8 +102,8 @@ function populateFrameSizeDropdown() { const frameSizes = [ - "R96x96", "QQVGA", "CIF", "HQVGA", "R240x240", "QVGA", - "CIF", "HVGA", "VGA", "SVGA", "XGA", "HD", "SXGA", + "R96x96", "QQVGA", "128X128", "CIF", "HQVGA", "R240x240", "QVGA", + "320X320","CIF", "HVGA", "VGA", "SVGA", "XGA", "HD", "SXGA", "UXGA", "FHD", "P_HD", "P_3MP", "QXGA", "QHD", "WQXGA", "P_FHD", "QSXGA" ]; @@ -133,7 +133,7 @@ fetch('/get_sensor_name') .then(response => response.text()) .then(sensorName => { - const showSharpnessAndDenoise = (sensorName === 'OV3640' || sensorName === 'OV5640'); + const showSharpnessAndDenoise = (sensorName === 'OV3660' || sensorName === 'OV5640'); document.getElementById('sharpness-container').classList.toggle('hidden', !showSharpnessAndDenoise); document.getElementById('denoise-container').classList.toggle('hidden', !showSharpnessAndDenoise); }) @@ -170,26 +170,29 @@

Micropython Camera Stream

diff --git a/src/modcamera.c b/src/modcamera.c index 63611e0..fd3c008 100644 --- a/src/modcamera.c +++ b/src/modcamera.c @@ -315,10 +315,12 @@ const mp_rom_map_elem_t mp_camera_hal_pixel_format_table[] = { const mp_rom_map_elem_t mp_camera_hal_frame_size_table[] = { { MP_ROM_QSTR(MP_QSTR_R96X96), MP_ROM_INT(FRAMESIZE_96X96) }, { MP_ROM_QSTR(MP_QSTR_QQVGA), MP_ROM_INT(FRAMESIZE_QQVGA) }, + { MP_ROM_QSTR(MP_QSTR_R128x128), MP_ROM_INT(FRAMESIZE_128X128) }, { MP_ROM_QSTR(MP_QSTR_QCIF), MP_ROM_INT(FRAMESIZE_QCIF) }, { MP_ROM_QSTR(MP_QSTR_HQVGA), MP_ROM_INT(FRAMESIZE_HQVGA) }, { MP_ROM_QSTR(MP_QSTR_R240X240), MP_ROM_INT(FRAMESIZE_240X240) }, { MP_ROM_QSTR(MP_QSTR_QVGA), MP_ROM_INT(FRAMESIZE_QVGA) }, + { MP_ROM_QSTR(MP_QSTR_R320X320), MP_ROM_INT(FRAMESIZE_320X320) }, { MP_ROM_QSTR(MP_QSTR_CIF), MP_ROM_INT(FRAMESIZE_CIF) }, { MP_ROM_QSTR(MP_QSTR_HVGA), MP_ROM_INT(FRAMESIZE_HVGA) }, { MP_ROM_QSTR(MP_QSTR_VGA), MP_ROM_INT(FRAMESIZE_VGA) }, diff --git a/src/modcamera.h b/src/modcamera.h index e7d557b..1c36751 100644 --- a/src/modcamera.h +++ b/src/modcamera.h @@ -201,7 +201,7 @@ 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. * @details Needs to be defined in the port-specific implementation. */ -extern const mp_rom_map_elem_t mp_camera_hal_frame_size_table[22]; +extern const mp_rom_map_elem_t mp_camera_hal_frame_size_table[24]; /** * @brief Table mapping gainceiling API to their corresponding values at HAL. From 0c916a5d8c3b654d8011ed2de5b9e0fc4a826216 Mon Sep 17 00:00:00 2001 From: Christopher Nadler <147471517+cnadler86@users.noreply.github.com> Date: Thu, 5 Dec 2024 20:55:08 +0100 Subject: [PATCH 06/43] ESP32.yml aktualisieren --- .github/workflows/ESP32.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ESP32.yml b/.github/workflows/ESP32.yml index 65a89cc..ac88247 100644 --- a/.github/workflows/ESP32.yml +++ b/.github/workflows/ESP32.yml @@ -5,7 +5,9 @@ on: push: paths: - 'src/**' - - '.github/workflows/*.yml' + - '.github/workflows/ESP32.yml' + tags-ignore: + - '**' pull_request: branches: - master From d1d1d474c73d2cc99f9f3e807dfe9c43d87d08b0 Mon Sep 17 00:00:00 2001 From: Christopher Nadler <147471517+cnadler86@users.noreply.github.com> Date: Thu, 5 Dec 2024 21:01:03 +0100 Subject: [PATCH 07/43] Create-Release.yml aktualisieren --- .github/workflows/Create-Release.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/Create-Release.yml b/.github/workflows/Create-Release.yml index 4b8dccf..aec7e05 100644 --- a/.github/workflows/Create-Release.yml +++ b/.github/workflows/Create-Release.yml @@ -7,7 +7,6 @@ on: jobs: add_artifacts: - if: ${{ github.event.release.draft }} runs-on: ubuntu-latest steps: - name: Checkout repository @@ -19,14 +18,20 @@ jobs: - name: Authenticate GitHub CLI run: echo "${{ secrets.GITHUB_TOKEN }}" | gh auth login --with-token + - name: Check if release is a draft + id: check_draft + run: echo "is_draft=${{ github.event.release.draft }}" >> $GITHUB_ENV + - name: Download artifacts + if: ${{ env.is_draft == 'true' }} uses: actions/download-artifact@v4 with: name: firmware-* path: ./artifacts - name: Upload Release Assets + if: ${{ env.is_draft == 'true' }} run: | for file in ./artifacts/*; do gh release upload "${{ github.event.release.tag_name }}" "$file" --clobber - done + done \ No newline at end of file From 00e4968af59af80e44f17db355b93c4139e8d46e Mon Sep 17 00:00:00 2001 From: Christopher Nadler <147471517+cnadler86@users.noreply.github.com> Date: Thu, 5 Dec 2024 21:04:02 +0100 Subject: [PATCH 08/43] ESP32.yml aktualisieren --- .github/workflows/ESP32.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ESP32.yml b/.github/workflows/ESP32.yml index ac88247..7314a49 100644 --- a/.github/workflows/ESP32.yml +++ b/.github/workflows/ESP32.yml @@ -5,7 +5,7 @@ on: push: paths: - 'src/**' - - '.github/workflows/ESP32.yml' + - '.github/workflows/*.yml' tags-ignore: - '**' pull_request: From 86499f62e226c22d01277e438f70906d9b2786f4 Mon Sep 17 00:00:00 2001 From: Christopher Nadler <147471517+cnadler86@users.noreply.github.com> Date: Wed, 11 Dec 2024 08:00:50 +0100 Subject: [PATCH 09/43] README.md aktualisieren --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6df5c3e..4b6593b 100644 --- a/README.md +++ b/README.md @@ -99,10 +99,11 @@ cam.init() ### Capture image ```python -img = cam.capture() +img = cam.capture() #capture image as configured +img_rgb888 = cam.capture(PixelFormat.RGB888) #capture image and convert it to RGB888 ``` -Keyword arguments for capture +Arguments for capture - out_format: Output format as PixelFormat (optional) From e462cac2b720f192be7b541517a087878cb875c9 Mon Sep 17 00:00:00 2001 From: Christopher Nadler <147471517+cnadler86@users.noreply.github.com> Date: Tue, 17 Dec 2024 05:57:51 +0100 Subject: [PATCH 10/43] Img conv fun (#21) Adds more functionality and improves the performance in image conversion. Fix #18 --- .github/workflows/ESP32.yml | 2 +- README.md | 37 +++++++--- src/modcamera.c | 141 +++++++++++++++++++----------------- src/modcamera.h | 10 +++ src/modcamera_api.c | 8 ++ 5 files changed, 122 insertions(+), 76 deletions(-) diff --git a/.github/workflows/ESP32.yml b/.github/workflows/ESP32.yml index 7314a49..e124903 100644 --- a/.github/workflows/ESP32.yml +++ b/.github/workflows/ESP32.yml @@ -7,7 +7,7 @@ on: - 'src/**' - '.github/workflows/*.yml' tags-ignore: - - '**' + - 'v*' pull_request: branches: - master diff --git a/README.md b/README.md index 4b6593b..8fc9b9f 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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 @@ -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) | @@ -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 | diff --git a/src/modcamera.c b/src/modcamera.c index fd3c008..ade8386 100644 --- a/src/modcamera.c +++ b/src/modcamera.c @@ -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) { @@ -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; @@ -205,6 +214,61 @@ 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) { @@ -212,14 +276,6 @@ mp_obj_t mp_camera_hal_capture(mp_camera_obj_t *self, int8_t out_format) { 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) { @@ -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; } } @@ -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 diff --git a/src/modcamera.h b/src/modcamera.h index 1c36751..7e68be8 100644 --- a/src/modcamera.h +++ b/src/modcamera.h @@ -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 @@ -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. diff --git a/src/modcamera_api.c b/src/modcamera_api.c index 71cc2b0..0ea8e9d 100644 --- a/src/modcamera_api.c +++ b/src/modcamera_api.c @@ -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]); @@ -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) }, From bcdfd5e1e12c2edd6696dc60f5c2dc41c58cc0ba Mon Sep 17 00:00:00 2001 From: Christopher Nadler <147471517+cnadler86@users.noreply.github.com> Date: Tue, 17 Dec 2024 13:59:21 +0100 Subject: [PATCH 11/43] Create FUNDING.yml Signed-off-by: Christopher Nadler <147471517+cnadler86@users.noreply.github.com> --- .github/FUNDING.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..293eee5 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,15 @@ +# These are supported funding model platforms + +github: cnadler86 +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry +polar: # Replace with a single Polar username +buy_me_a_coffee: # Replace with a single Buy Me a Coffee username +thanks_dev: # Replace with a single thanks.dev username +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] From 2d89e6866569fafa3201bd633d2f8fd43757a625 Mon Sep 17 00:00:00 2001 From: Christopher Nadler <147471517+cnadler86@users.noreply.github.com> Date: Tue, 17 Dec 2024 14:00:54 +0100 Subject: [PATCH 12/43] FUNDING.yml aktualisieren --- .github/FUNDING.yml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 293eee5..1eb8b54 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,15 +1,15 @@ # These are supported funding model platforms github: cnadler86 -patreon: # Replace with a single Patreon username -open_collective: # Replace with a single Open Collective username -ko_fi: # Replace with a single Ko-fi username -tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel -community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry -liberapay: # Replace with a single Liberapay username -issuehunt: # Replace with a single IssueHunt username -lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry -polar: # Replace with a single Polar username -buy_me_a_coffee: # Replace with a single Buy Me a Coffee username -thanks_dev: # Replace with a single thanks.dev username -custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] +#patreon: # Replace with a single Patreon username +#open_collective: # Replace with a single Open Collective username +#ko_fi: # Replace with a single Ko-fi username +#tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +#community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +#liberapay: # Replace with a single Liberapay username +#issuehunt: # Replace with a single IssueHunt username +#lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry +#polar: # Replace with a single Polar username +#buy_me_a_coffee: # Replace with a single Buy Me a Coffee username +#thanks_dev: # Replace with a single thanks.dev username +#custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] From bd8ab312fa650b6702ba7fa0bf11c641c9ee2d14 Mon Sep 17 00:00:00 2001 From: Christopher Nadler <147471517+cnadler86@users.noreply.github.com> Date: Wed, 18 Dec 2024 18:53:22 +0100 Subject: [PATCH 13/43] modcamera.c 40mhz Max --- src/modcamera.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modcamera.c b/src/modcamera.c index ade8386..f8a630e 100644 --- a/src/modcamera.c +++ b/src/modcamera.c @@ -56,8 +56,8 @@ static inline void check_init(mp_camera_obj_t *self) { } static void set_check_xclk_freq(mp_camera_obj_t *self, int32_t xclk_freq_hz) { - if ( xclk_freq_hz > 20000000) { - mp_raise_ValueError(MP_ERROR_TEXT("xclk frequency cannot be grather than 20MHz")); + if ( xclk_freq_hz > 40000000) { + mp_raise_ValueError(MP_ERROR_TEXT("xclk frequency cannot be grather than 40MHz")); } else { self->camera_config.xclk_freq_hz = xclk_freq_hz; } From 5bbb25c2f7c2b1abefb3f79cb7adac06890a648c Mon Sep 17 00:00:00 2001 From: Christopher Nadler Date: Sat, 21 Dec 2024 05:26:10 +0100 Subject: [PATCH 14/43] Minor improvements and missing pixel_formats added --- README.md | 24 ++++++++++++-------- examples/CameraSettings.html | 4 ++-- src/modcamera.c | 44 +++++++++++------------------------- src/modcamera.h | 2 +- 4 files changed, 31 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index 8fc9b9f..1a3d3ac 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,18 @@ This project aims to support various cameras (e.g. OV2640, OV5640) on different At the moment, this is a micropython user module, but it might get in the micropython repo in the future. The API is stable, but it might change without previous announce. -## Precomiled FW (the easy way) +## Content + +[Precomiled FW](#precomiled-fw-the-easy-way) +[Using the API](#using-the-api) +[Build it yourself](#Build) + + +## Precomiled FW (the easy way) If you are not familiar with building custom firmware, visit the [releases](https://github.com/cnadler86/micropython-camera-API/releases) page to download firmware that suits your board. **There are over 20 precompiled board images with the latest micropython!** -## Using the API +## Using the API ### Importing the Camera Module @@ -163,7 +170,7 @@ vers = camera.Version() The FW images support the following cameras out of the box, but is therefore big: OV7670, OV7725, OV2640, OV3660, OV5640, NT99141, GC2145, GC032A, GC0308, BF3005, BF20A6, SC030IOT -## Build your custom FW +## Build your custom FW ### Setting up the build environment (DIY method) @@ -268,9 +275,9 @@ If you experience problems, visit [MicroPython external C modules](https://docs. - The driver requires PSRAM to be installed and activated. - Most of the precompiled firmware images are untested, but the only difference between them are the target architecture and pin definitions, so they should work out of the box. If not, please raise an issue. -## FPS benchmark +## Benchmark -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. +I didn't use a calibrated osziloscope, but here is a FPS benchmark with my ESP32S3 (xclck_freq = 20MHz, 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) | @@ -293,14 +300,13 @@ Using fb_count=2 theoretically can double the FPS (see JPEG with fb_count=2). Th Looking at the results: image conversion make only sense for frame sized below QVGA or if capturing the image in the intended pixelformat and frame size combination fails. -## Troubleshoot +## Troubleshooting You can find information on the following sites: - [ESP-FAQ](https://docs.espressif.com/projects/esp-faq/en/latest/application-solution/camera-application.html) - [ChatGPT](https://chatgpt.com/) - [Issues in here](https://github.com/cnadler86/micropython-camera-API/issues?q=is%3Aissue) -## Future Plans +## Donate -- Edge case: enable usage of pins such as i2c for other applications -- Provide examples in binary image +If you enjoy this work and would like to share your enjoyment, please feel free to [donate](https://github.com/sponsors/cnadler86?frequency=one-time) and contribute to the project. Thanks! :blush: diff --git a/examples/CameraSettings.html b/examples/CameraSettings.html index b5fde3d..63d0055 100644 --- a/examples/CameraSettings.html +++ b/examples/CameraSettings.html @@ -221,11 +221,11 @@

Micropython Camera Stream

diff --git a/src/modcamera.c b/src/modcamera.c index f8a630e..23b256d 100644 --- a/src/modcamera.c +++ b/src/modcamera.c @@ -318,9 +318,13 @@ bool mp_camera_hal_initialized(mp_camera_obj_t *self){ const mp_rom_map_elem_t mp_camera_hal_pixel_format_table[] = { { MP_ROM_QSTR(MP_QSTR_JPEG), MP_ROM_INT(PIXFORMAT_JPEG) }, { MP_ROM_QSTR(MP_QSTR_YUV422), MP_ROM_INT(PIXFORMAT_YUV422) }, + { MP_ROM_QSTR(MP_QSTR_YUV420), MP_ROM_INT(PIXFORMAT_YUV420) }, { 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) }, + { MP_ROM_QSTR(MP_QSTR_RAW), MP_ROM_INT(PIXFORMAT_RAW) }, + { MP_ROM_QSTR(MP_QSTR_RGB444), MP_ROM_INT(PIXFORMAT_RGB444) }, + { MP_ROM_QSTR(MP_QSTR_RGB555), MP_ROM_INT(PIXFORMAT_RGB555) }, }; const mp_rom_map_elem_t mp_camera_hal_frame_size_table[] = { @@ -367,9 +371,6 @@ const mp_rom_map_elem_t mp_camera_hal_gainceiling_table[] = { //TODO: Makros with convertion function, since the API will use standarized values. // Helper functions to get and set camera and sensor information -#define SENSOR_STATUS_GETSET_IN_RANGE(type, name, status_field_name, setter_function_name, min_val, max_val) \ - SENSOR_GETSET_IN_RANGE(type, name, status.status_field_name, setter_function_name, min_val, max_val) - #define SENSOR_STATUS_GETSET(type, name, status_field_name, setter_function_name) \ SENSOR_GETSET(type, name, status.status_field_name, setter_function_name) @@ -378,10 +379,6 @@ const mp_rom_map_elem_t mp_camera_hal_gainceiling_table[] = { SENSOR_GET(type, name, field_name) \ SENSOR_SET(type, name, setter_function_name) -#define SENSOR_GETSET_IN_RANGE(type, name, field_name, setter_function_name, min_val, max_val) \ - SENSOR_GET(type, name, field_name) \ - SENSOR_SET_IN_RANGE(type, name, setter_function_name, min_val, max_val) - #define SENSOR_GET(type, name, status_field_name) \ type mp_camera_hal_get_##name(mp_camera_obj_t * self) { \ check_init(self); \ @@ -401,26 +398,11 @@ const mp_rom_map_elem_t mp_camera_hal_gainceiling_table[] = { } \ } -#define SENSOR_SET_IN_RANGE(type, name, setter_function_name, min_val, max_val) \ - void mp_camera_hal_set_##name(mp_camera_obj_t * self, type value) { \ - sensor_t *sensor = esp_camera_sensor_get(); \ - check_init(self); \ - if (value < min_val || value > max_val) { \ - mp_raise_ValueError(MP_ERROR_TEXT(#name " value must be between " #min_val " and " #max_val)); \ - } \ - if (!sensor->setter_function_name) { \ - mp_raise_ValueError(MP_ERROR_TEXT("No attribute " #name)); \ - } \ - if (sensor->setter_function_name(sensor, value) < 0) { \ - mp_raise_ValueError(MP_ERROR_TEXT("Invalid setting for " #name)); \ - } \ - } - SENSOR_GET(framesize_t, frame_size, status.framesize); -SENSOR_STATUS_GETSET_IN_RANGE(int, contrast, contrast, set_contrast, -2, 2); -SENSOR_STATUS_GETSET_IN_RANGE(int, brightness, brightness, set_brightness, -2, 2); -SENSOR_STATUS_GETSET_IN_RANGE(int, saturation, saturation, set_saturation, -2, 2); -SENSOR_STATUS_GETSET_IN_RANGE(int, sharpness, sharpness, set_sharpness, -2, 2); +SENSOR_STATUS_GETSET(int, contrast, contrast, set_contrast); +SENSOR_STATUS_GETSET(int, brightness, brightness, set_brightness); +SENSOR_STATUS_GETSET(int, saturation, saturation, set_saturation); +SENSOR_STATUS_GETSET(int, sharpness, sharpness, set_sharpness); SENSOR_STATUS_GETSET(int, denoise, denoise, set_denoise); SENSOR_STATUS_GETSET(mp_camera_gainceiling_t, gainceiling, gainceiling, set_gainceiling); SENSOR_STATUS_GETSET(bool, colorbar, colorbar, set_colorbar); @@ -431,11 +413,11 @@ SENSOR_STATUS_GETSET(bool, hmirror, hmirror, set_hmirror); SENSOR_STATUS_GETSET(bool, vflip, vflip, set_vflip); SENSOR_STATUS_GETSET(bool, aec2, aec2, set_aec2); SENSOR_STATUS_GETSET(bool, awb_gain, awb_gain, set_awb_gain); -SENSOR_STATUS_GETSET(int, agc_gain, agc_gain, set_agc_gain); //in_Range not needed since driver limits value -SENSOR_STATUS_GETSET(int, aec_value, aec_value, set_aec_value); //in_Range not needed since driver limits value -SENSOR_STATUS_GETSET_IN_RANGE(int, special_effect, special_effect, set_special_effect, 0, 6); -SENSOR_STATUS_GETSET_IN_RANGE(int, wb_mode, wb_mode, set_wb_mode, 0, 4); -SENSOR_STATUS_GETSET_IN_RANGE(int, ae_level, ae_level, set_ae_level, -2, 2); +SENSOR_STATUS_GETSET(int, agc_gain, agc_gain, set_agc_gain); +SENSOR_STATUS_GETSET(int, aec_value, aec_value, set_aec_value); +SENSOR_STATUS_GETSET(int, special_effect, special_effect, set_special_effect); +SENSOR_STATUS_GETSET(int, wb_mode, wb_mode, set_wb_mode); +SENSOR_STATUS_GETSET(int, ae_level, ae_level, set_ae_level); SENSOR_STATUS_GETSET(bool, dcw, dcw, set_dcw); SENSOR_STATUS_GETSET(bool, bpc, bpc, set_bpc); SENSOR_STATUS_GETSET(bool, wpc, wpc, set_wpc); diff --git a/src/modcamera.h b/src/modcamera.h index 7e68be8..056bf75 100644 --- a/src/modcamera.h +++ b/src/modcamera.h @@ -205,7 +205,7 @@ 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. */ -extern const mp_rom_map_elem_t mp_camera_hal_pixel_format_table[5]; +extern const mp_rom_map_elem_t mp_camera_hal_pixel_format_table[9]; /** * @brief Table mapping frame sizes API to their corresponding values at HAL. From 400afec5ab05324e88cf42c85a3d5e68bb8bc6ac Mon Sep 17 00:00:00 2001 From: Christopher Nadler Date: Sat, 21 Dec 2024 05:28:38 +0100 Subject: [PATCH 15/43] Update readme --- README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 1a3d3ac..31b8449 100644 --- a/README.md +++ b/README.md @@ -8,10 +8,12 @@ The API is stable, but it might change without previous announce. ## Content -[Precomiled FW](#precomiled-fw-the-easy-way) -[Using the API](#using-the-api) -[Build it yourself](#Build) - +- [Precomiled FW](#Precompiled) +- [Using the API](#Usage) +- [Build it yourself](#Build) +- [Notes](#notes) +- [Benchmark](#benchmark) +- [Donate](#donate) ## Precomiled FW (the easy way) From 3e5c84d501547cf8d62fb0be8edcbd2ced683458 Mon Sep 17 00:00:00 2001 From: Christopher Nadler Date: Sat, 21 Dec 2024 05:41:06 +0100 Subject: [PATCH 16/43] Update readme --- README.md | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 31b8449..9be14b7 100644 --- a/README.md +++ b/README.md @@ -8,26 +8,41 @@ The API is stable, but it might change without previous announce. ## Content -- [Precomiled FW](#Precompiled) -- [Using the API](#Usage) -- [Build it yourself](#Build) +- [Precomiled FW (the easy way)](#precomiled-fw-the-easy-way) +- [Using the API](#using-the-api) + - [Importing the camera module](#importing-the-camera-module) + - [Creating a camera object](#creating-a-camera-object) + - [Initializing the camera](#initializing-the-camera) + - [Capture image](#capture-image) + - [Convert image to another format](#convert-image-to-another-format) + - [Camera reconfiguration](#camera-reconfiguration) + - [Additional methods](#additional-methods) + - [Additional information](#additional-information) +- [Build your custom FW](#build-your-custom-fw) + - [Setting up the build environment (DIY method)](#setting-up-the-build-environment-diy-method) + - [Add camera configurations to your board (optional, but recommended)](#add-camera-configurations-to-your-board-optional-but-recommended) + - [Supported camera models](#supported-camera-models) + - [For unsupported camera models](#for-unsupported-camera-models) + - [Customize additional camera settings](#customize-additional-camera-settings) + - [Build the API](#build-the-api) - [Notes](#notes) - [Benchmark](#benchmark) +- [Troubleshooting](#troubleshooting) - [Donate](#donate) -## Precomiled FW (the easy way) +## Precomiled FW (the easy way) If you are not familiar with building custom firmware, visit the [releases](https://github.com/cnadler86/micropython-camera-API/releases) page to download firmware that suits your board. **There are over 20 precompiled board images with the latest micropython!** -## Using the API +## Using the API -### Importing the Camera Module +### Importing the camera module ```python from camera import Camera, GrabMode, PixelFormat, FrameSize, GainCeiling ``` -### Creating a Camera Object +### Creating a camera object Camera construction using defaults. This is the case if you are using a **non-generic** precompiled firmware or if you specified the camera model or pins in mpconfigboard.h during your build. Then you can just call the construction without any keyword arguments. @@ -99,7 +114,7 @@ The following keyword arguments have default values: - LATEST for ESP32S3 boards - WHEN_EMPTY for all other -### Initializing the Camera +### Initializing the camera ```python cam.init() @@ -172,7 +187,7 @@ vers = camera.Version() The FW images support the following cameras out of the box, but is therefore big: OV7670, OV7725, OV2640, OV3660, OV5640, NT99141, GC2145, GC032A, GC0308, BF3005, BF20A6, SC030IOT -## Build your custom FW +## Build your custom FW ### Setting up the build environment (DIY method) @@ -191,7 +206,7 @@ Alternatively, you can clone the rep ### Add camera configurations to your board (optional, but recommended) -#### Supported Camera Models +#### Supported camera models This project supports various boards with camera interface out of the box. You typically only need to add a single line to your board config file ("mpconfigboard.h). Example (don't forget to add the empty line at the bottom): From c515e306e85df454de00339ce56b17e192e61270 Mon Sep 17 00:00:00 2001 From: Christopher Nadler Date: Sat, 21 Dec 2024 05:53:50 +0100 Subject: [PATCH 17/43] Update readme and fix pipeline --- .github/workflows/ESP32.yml | 2 ++ README.md | 6 ++---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ESP32.yml b/.github/workflows/ESP32.yml index e124903..01e422d 100644 --- a/.github/workflows/ESP32.yml +++ b/.github/workflows/ESP32.yml @@ -3,6 +3,8 @@ name: ESP32 on: workflow_dispatch: push: + branches: + - '**' paths: - 'src/**' - '.github/workflows/*.yml' diff --git a/README.md b/README.md index 9be14b7..c733b47 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![ESP32 Port](https://github.com/cnadler86/micropython-camera-API/actions/workflows/ESP32.yml/badge.svg)](https://github.com/cnadler86/micropython-camera-API/actions/workflows/ESP32.yml) -This project aims to support various cameras (e.g. OV2640, OV5640) on different MicroPython ports, starting with the ESP32 port. The project implements a general API, has precompiled FW images and supports a lot of cameras out of the box. +This project aims to support various cameras (e.g. OV2640, OV5640) on different MicroPython ports, starting with the ESP32 port. The project implements a general API, has precompiled FW images and supports a lot of cameras out of the box. Defaults are set to work with the OV2640. At the moment, this is a micropython user module, but it might get in the micropython repo in the future. The API is stable, but it might change without previous announce. @@ -21,9 +21,6 @@ The API is stable, but it might change without previous announce. - [Build your custom FW](#build-your-custom-fw) - [Setting up the build environment (DIY method)](#setting-up-the-build-environment-diy-method) - [Add camera configurations to your board (optional, but recommended)](#add-camera-configurations-to-your-board-optional-but-recommended) - - [Supported camera models](#supported-camera-models) - - [For unsupported camera models](#for-unsupported-camera-models) - - [Customize additional camera settings](#customize-additional-camera-settings) - [Build the API](#build-the-api) - [Notes](#notes) - [Benchmark](#benchmark) @@ -291,6 +288,7 @@ If you experience problems, visit [MicroPython external C modules](https://docs. - If your target board is a ESP32, I recommend using IDF >= 5.2, since older versions may lead to IRAM overflow during build. Alternatively you can modify your sdkconfig-file (see [issue #1](https://github.com/cnadler86/micropython-camera-API/issues/1)). - The driver requires PSRAM to be installed and activated. - Most of the precompiled firmware images are untested, but the only difference between them are the target architecture and pin definitions, so they should work out of the box. If not, please raise an issue. +- Every sensor has its own ranges and settings, please consult the respective specifications. ## Benchmark From c979972df4313d2f06d160e893ba10fa708156f2 Mon Sep 17 00:00:00 2001 From: Christopher Nadler Date: Sun, 22 Dec 2024 06:31:02 +0100 Subject: [PATCH 18/43] Cleanup --- src/modcamera.c | 1 - src/modcamera.h | 3 --- src/modcamera_api.c | 1 - 3 files changed, 5 deletions(-) diff --git a/src/modcamera.c b/src/modcamera.c index 23b256d..6c1cc9a 100644 --- a/src/modcamera.c +++ b/src/modcamera.c @@ -369,7 +369,6 @@ const mp_rom_map_elem_t mp_camera_hal_gainceiling_table[] = { { MP_ROM_QSTR(MP_QSTR_128X), MP_ROM_INT(GAINCEILING_128X) }, }; -//TODO: Makros with convertion function, since the API will use standarized values. // Helper functions to get and set camera and sensor information #define SENSOR_STATUS_GETSET(type, name, status_field_name, setter_function_name) \ SENSOR_GETSET(type, name, status.status_field_name, setter_function_name) diff --git a/src/modcamera.h b/src/modcamera.h index 056bf75..4d86ce8 100644 --- a/src/modcamera.h +++ b/src/modcamera.h @@ -88,7 +88,6 @@ defined (MICROPY_CAMERA_PIN_PCLK) && defined (MICROPY_CAMERA_PIN_VSYNC) && defin typedef pixformat_t hal_camera_pixformat_t; typedef framesize_t hal_camera_framesize_t; -typedef camera_fb_location_t hal_camera_fb_location_t; typedef camera_grab_mode_t hal_camera_grabmode_t; typedef gainceiling_t hal_camera_gainceiling_t; @@ -109,9 +108,7 @@ typedef hal_camera_pixformat_t mp_camera_pixformat_t; typedef hal_camera_framesize_t mp_camera_framesize_t; typedef hal_camera_grabmode_t mp_camera_grabmode_t; typedef hal_camera_gainceiling_t mp_camera_gainceiling_t; -// typedef hal_camera_fb_location_t mp_camera_fb_location_t; //not used at the moment, but might be used in the future -// TODO: Define how to integrate external time source in constructor (e.g. in ESP is LED-Timer). /** * @brief Constructs the camera hardware abstraction layer. * @details The Port-plattform shall define a default pwm-time source and also frame buffer location (no input) diff --git a/src/modcamera_api.c b/src/modcamera_api.c index 0ea8e9d..4799ce3 100644 --- a/src/modcamera_api.c +++ b/src/modcamera_api.c @@ -162,7 +162,6 @@ static mp_obj_t camera_convert(mp_obj_t self_in, mp_obj_t arg) { 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]); enum { ARG_frame_size, ARG_pixel_format, ARG_grab_mode, ARG_fb_count }; static const mp_arg_t allowed_args[] = { From 1f030d953bb870fb91026b2a2d13510ef1bb0e7c Mon Sep 17 00:00:00 2001 From: John Gentilin Date: Tue, 21 Jan 2025 21:19:41 -0800 Subject: [PATCH 19/43] Add esp32 camera dir (#23) minor change that allows to override the directories where the includes exist for the esp32-camera component. It should function the same as before, if you do not provide an override. --------- Signed-off-by: John Gentilin --- src/micropython.cmake | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/micropython.cmake b/src/micropython.cmake index 66fd308..369f867 100644 --- a/src/micropython.cmake +++ b/src/micropython.cmake @@ -8,14 +8,20 @@ target_sources(usermod_mp_camera INTERFACE ${CMAKE_CURRENT_LIST_DIR}/modcamera_api.c ) -if(EXISTS "${IDF_PATH}/components/esp32-camera") +if(NOT ESP32_CAMERA_DIR) + set(ESP32_CAMERA_DIR "${IDF_PATH}/components/esp32-camera") +endif() + +message("Camera Dir: ${ESP32_CAMERA_DIR}") + +if(EXISTS "${ESP32_CAMERA_DIR}") target_include_directories(usermod_mp_camera INTERFACE ${CMAKE_CURRENT_LIST_DIR} - ${IDF_PATH}/components/esp32-camera/driver/include - ${IDF_PATH}/components/esp32-camera/driver/private_include - ${IDF_PATH}/components/esp32-camera/conversions/include - ${IDF_PATH}/components/esp32-camera/conversions/private_include - ${IDF_PATH}/components/esp32-camera/sensors/private_include + ${ESP32_CAMERA_DIR}/driver/include + ${ESP32_CAMERA_DIR}/driver/private_include + ${ESP32_CAMERA_DIR}/conversions/include + ${ESP32_CAMERA_DIR}/conversions/private_include + ${ESP32_CAMERA_DIR}/sensors/private_include ) else() target_include_directories(usermod_mp_camera INTERFACE @@ -32,4 +38,4 @@ endif() target_link_libraries(usermod INTERFACE usermod_mp_camera) -micropy_gather_target_properties(usermod_mp_camera) \ No newline at end of file +micropy_gather_target_properties(usermod_mp_camera) From eaca8eeb722c98dbd23c30045e0fd6f70d5ef84f Mon Sep 17 00:00:00 2001 From: Christopher Nadler <147471517+cnadler86@users.noreply.github.com> Date: Mon, 24 Feb 2025 07:09:53 +0100 Subject: [PATCH 20/43] README.md aktualisieren --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c733b47..854965e 100644 --- a/README.md +++ b/README.md @@ -269,7 +269,7 @@ If you want to customize additional camera setting or reduce the FW size by remo ### Build the API -To build the project, you could do it the following way: +To build the project, you could do it the following way (Micropython and camera-api folders are at the same level): ```bash . /esp-idf/export.sh From d42addac428f0c8fef17e932ef72bdb9ed14f2b3 Mon Sep 17 00:00:00 2001 From: Christopher Nadler <147471517+cnadler86@users.noreply.github.com> Date: Mon, 24 Feb 2025 17:02:03 +0100 Subject: [PATCH 21/43] README.md aktualisieren --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 854965e..940c64c 100644 --- a/README.md +++ b/README.md @@ -269,7 +269,7 @@ If you want to customize additional camera setting or reduce the FW size by remo ### Build the API -To build the project, you could do it the following way (Micropython and camera-api folders are at the same level): +To build the project, you could do it the following way: ```bash . /esp-idf/export.sh @@ -279,6 +279,7 @@ make USER_C_MODULES=../../../../micropython-camera-API/src/micropython.cmake BOA make USER_C_MODULES=../../../../micropython-camera-API/src/micropython.cmake BOARD= all ``` +Micropython and camera-api folders are at the same level. Note that you need those extra "/../"s while been inside the esp32 port folder. If you experience problems, visit [MicroPython external C modules](https://docs.micropython.org/en/latest/develop/cmodules.html). ## Notes From 7fbb4143abaf4988bd603992a8ecb94077b9ad2e Mon Sep 17 00:00:00 2001 From: Christopher Nadler <147471517+cnadler86@users.noreply.github.com> Date: Fri, 21 Mar 2025 20:59:59 +0100 Subject: [PATCH 22/43] asyncio support for capture method and get rid of convert methods (#28) Adds `acamera `instance supporting async capture method Use [mp_jpeg](https://github.com/cnadler86/mp_jpeg) instead of convert methods. Fix #25 Fix #26 --- .github/workflows/Create-Release.yml | 37 ------ .github/workflows/ESP32.yml | 19 ++- README.md | 140 +++++++++++-------- examples/CameraSettings.py | 4 +- src/acamera.py | 12 ++ src/manifest.py | 4 + src/micropython.cmake | 68 ++++++++-- src/modcamera.c | 192 ++++++++------------------- src/modcamera.h | 19 +-- src/modcamera_api.c | 48 +++---- 10 files changed, 256 insertions(+), 287 deletions(-) delete mode 100644 .github/workflows/Create-Release.yml create mode 100644 src/acamera.py create mode 100644 src/manifest.py diff --git a/.github/workflows/Create-Release.yml b/.github/workflows/Create-Release.yml deleted file mode 100644 index aec7e05..0000000 --- a/.github/workflows/Create-Release.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: Add-Artifacts-to-Draft-Release - -on: - release: - types: - - created - -jobs: - add_artifacts: - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Set up GitHub CLI - uses: actions/setup-gh-cli@v2 - - - name: Authenticate GitHub CLI - run: echo "${{ secrets.GITHUB_TOKEN }}" | gh auth login --with-token - - - name: Check if release is a draft - id: check_draft - run: echo "is_draft=${{ github.event.release.draft }}" >> $GITHUB_ENV - - - name: Download artifacts - if: ${{ env.is_draft == 'true' }} - uses: actions/download-artifact@v4 - with: - name: firmware-* - path: ./artifacts - - - name: Upload Release Assets - if: ${{ env.is_draft == 'true' }} - run: | - for file in ./artifacts/*; do - gh release upload "${{ github.event.release.tag_name }}" "$file" --clobber - done \ No newline at end of file diff --git a/.github/workflows/ESP32.yml b/.github/workflows/ESP32.yml index 01e422d..ea142af 100644 --- a/.github/workflows/ESP32.yml +++ b/.github/workflows/ESP32.yml @@ -76,17 +76,20 @@ jobs: if: steps.cache_esp_idf.outputs.cache-hit != 'true' run: | cd ~ - # git clone --depth 1 --branch release/v5.2 https://github.com/espressif/esp-idf.git + # git clone --depth 1 --branch release/v5.3 https://github.com/espressif/esp-idf.git git clone --depth 1 --branch ${{ env.IDF_VER }} https://github.com/espressif/esp-idf.git git -C esp-idf submodule update --init --recursive --filter=tree:0 cd esp-idf ./install.sh all cd components - latest_cam_driver=$(curl -s https://api.github.com/repos/espressif/esp32-camera/releases/latest | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') - git clone --depth 1 --branch $latest_cam_driver https://github.com/espressif/esp32-camera.git - # git clone https://github.com/cnadler86/esp32-camera.git + # latest_cam_driver=$(curl -s https://api.github.com/repos/espressif/esp32-camera/releases/latest | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') + # git clone --depth 1 --branch $latest_cam_driver https://github.com/espressif/esp32-camera.git + git clone https://github.com/cnadler86/esp32-camera.git cd ~/esp-idf/ source ./export.sh + cd ~ + git clone https://github.com/espressif/esp-adf-libs.git + cp -r ~/esp-adf-libs/esp_new_jpeg ~/esp-idf/components/ # Dynamically create jobs for each board build: @@ -151,6 +154,8 @@ jobs: # Build MicroPython for each board - name: Build MicroPython run: | + cd ${{ github.workspace }} + git clone https://github.com/cnadler86/mp_jpeg.git cd ~/esp-idf/components/esp32-camera CAM_DRIVER=$(git describe --tags --always --dirty) cd ~/micropython/ports/esp32 @@ -161,9 +166,9 @@ jobs: IFS='-' read -r BOARD_NAME BOARD_VARIANT <<< "${BUILD_TARGET}" if [ -n "${BOARD_VARIANT}" ]; then - IDF_CMD="idf.py -D MICROPY_BOARD=$BOARD_NAME -D USER_C_MODULES=${{ github.workspace }}/src/micropython.cmake -D MICROPY_BOARD_VARIANT=$BOARD_VARIANT -B build-$BUILD_TARGET -D MP_CAMERA_DRIVER_VERSION=$CAM_DRIVER" + IDF_CMD="idf.py -D MICROPY_BOARD=$BOARD_NAME -D USER_C_MODULES=${{ github.workspace }}/src/micropython.cmake -D MICROPY_BOARD_VARIANT=$BOARD_VARIANT -B build-$BUILD_TARGET -D MP_CAMERA_DRIVER_VERSION=$CAM_DRIVER -D MP_JPEG_DIR=${{ github.workspace }}/mp_jpeg" else - IDF_CMD="idf.py -D MICROPY_BOARD=$BOARD_NAME -D USER_C_MODULES=${{ github.workspace }}/src/micropython.cmake -B build-$BUILD_TARGET -D MP_CAMERA_DRIVER_VERSION=$CAM_DRIVER" + IDF_CMD="idf.py -D MICROPY_BOARD=$BOARD_NAME -D USER_C_MODULES=${{ github.workspace }}/src/micropython.cmake -B build-$BUILD_TARGET -D MP_CAMERA_DRIVER_VERSION=$CAM_DRIVER -D MP_JPEG_DIR=${{ github.workspace }}/mp_jpeg" fi if [ -n "${CAMERA_MODEL}" ]; then echo "FW_NAME=${CAMERA_MODEL}" >> $GITHUB_ENV @@ -183,6 +188,6 @@ jobs: - name: Upload firmware artifact uses: actions/upload-artifact@v4 with: - name: firmware-${{ env.FW_NAME }} + name: mpy_cam-${{ env.MPY_RELEASE }}-${{ env.FW_NAME }} path: ~/artifacts/** retention-days: 5 \ No newline at end of file diff --git a/README.md b/README.md index 940c64c..da880cd 100644 --- a/README.md +++ b/README.md @@ -2,23 +2,24 @@ [![ESP32 Port](https://github.com/cnadler86/micropython-camera-API/actions/workflows/ESP32.yml/badge.svg)](https://github.com/cnadler86/micropython-camera-API/actions/workflows/ESP32.yml) -This project aims to support various cameras (e.g. OV2640, OV5640) on different MicroPython ports, starting with the ESP32 port. The project implements a general API, has precompiled FW images and supports a lot of cameras out of the box. Defaults are set to work with the OV2640. +This project aims to support various cameras (e.g. OV2640, OV5640) on different MicroPython ports, starting with the ESP32 port. The project implements a general API, has precompiled firmware images and supports a lot of cameras out of the box. Defaults are set to work with the OV2640. At the moment, this is a micropython user module, but it might get in the micropython repo in the future. The API is stable, but it might change without previous announce. ## Content -- [Precomiled FW (the easy way)](#precomiled-fw-the-easy-way) +- [Precompiled firmware (the easy way)](#Precompiled-firmware-the-easy-way) - [Using the API](#using-the-api) - [Importing the camera module](#importing-the-camera-module) - [Creating a camera object](#creating-a-camera-object) - [Initializing the camera](#initializing-the-camera) - [Capture image](#capture-image) - - [Convert image to another format](#convert-image-to-another-format) - [Camera reconfiguration](#camera-reconfiguration) + - [Freeing the buffer](#freeing-the-buffer) + - [Is a frame available](#is-frame-available) - [Additional methods](#additional-methods) - [Additional information](#additional-information) -- [Build your custom FW](#build-your-custom-fw) +- [Build your custom firmware](#build-your-custom-firmware) - [Setting up the build environment (DIY method)](#setting-up-the-build-environment-diy-method) - [Add camera configurations to your board (optional, but recommended)](#add-camera-configurations-to-your-board-optional-but-recommended) - [Build the API](#build-the-api) @@ -27,18 +28,28 @@ The API is stable, but it might change without previous announce. - [Troubleshooting](#troubleshooting) - [Donate](#donate) -## Precomiled FW (the easy way) +## Precompiled firmware (the easy way) If you are not familiar with building custom firmware, visit the [releases](https://github.com/cnadler86/micropython-camera-API/releases) page to download firmware that suits your board. **There are over 20 precompiled board images with the latest micropython!** +This firmware binaries also include the [mp_jpeg modul](https://github.com/cnadler86/mp_jpe) to encode/decode JPEGs. + ## Using the API ### Importing the camera module +There general way of using the api is as follow: + ```python from camera import Camera, GrabMode, PixelFormat, FrameSize, GainCeiling ``` +There is also a camera class with asyncio support! To use it, you need to import from the acamera package: + +```python +from acamera import Camera, GrabMode, PixelFormat, FrameSize, GainCeiling +``` + ### Creating a camera object Camera construction using defaults. This is the case if you are using a **non-generic** precompiled firmware or if you specified the camera model or pins in mpconfigboard.h during your build. Then you can just call the construction without any keyword arguments. @@ -83,23 +94,22 @@ cam = Camera( - href_pin: HREF pin - sda_pin: SDA pin - scl_pin: SCL pin -- xclk_pin: XCLK pin -- xclk_freq: XCLK frequency in Hz -- powerdown_pin: Powerdown pin -- reset_pin: Reset pin +- xclk_pin: XCLK pin ( set to -1, if you have an external clock source) +- xclk_freq: XCLK frequency in Hz (consult the camera sensor specification) +- powerdown_pin: Powerdown pin (set to -1 if not used) +- reset_pin: Reset pin (set to -1 if not used) - pixel_format: Pixel format as PixelFormat - frame_size: Frame size as FrameSize - jpeg_quality: JPEG quality - fb_count: Frame buffer count - grab_mode: Grab mode as GrabMode - init: Initialize camera at construction time (default: True) -- bmp_out: Image captured output converted to bitmap (default: False) **Default values:** The following keyword arguments have default values: -- xclk_freq: 20MHz // Frequencies are normally either 10 MHz or 20 MHz +- xclk_freq: 20MHz // Default for OV2640 (normally either 10 MHz or 20 MHz). Plase adapt it to your camera sensor. - frame_size: QQVGA - pixel_format: RGB565 - jpeg_quality: 85 // Quality of JPEG output in percent. Higher means higher quality. @@ -119,31 +129,22 @@ cam.init() ### Capture image +The general way of capturing an image is calling the `capture` method: + ```python img = cam.capture() ``` -Arguments for capture - -- out_format: Output format as PixelFormat (optional) +Each time you call the method, you will receive a new frame as memoryview. +You can convert it to bytes and free the memoryview buffer, so a new frame can be pushed to it. This will reduce the image latency but need more RAM. (see [freeing the buffer](#freeing-the-buffer)) -### Convert image to another format +The probably better way of capturing an image would be in an asyncio-loop: -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 +img = await cam.acapture() #To access this method, you need to import from acamera ``` -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) +Please consult the [asyncio documentation](https://docs.micropython.org/en/latest/library/asyncio.html), if you have questions on this. ### Camera reconfiguration @@ -158,21 +159,43 @@ Keyword arguments for reconfigure - grab_mode: Grab mode as GrabMode (optional) - fb_count: Frame buffer count (optional) -### Additional methods +### Freeing the buffer + +This is optional, but can reduce the latency of capturing an image in some cases (especially with fb_count = 1).. + +```python +Img = bytes(cam.capture()) #Create a new bytes object from the memoryview (because we want to free it afterwards) +cam.free_buffer() # This will free the captured image or in other words "deleting"" the memoryview +``` + +### Is frame available + +```python +Img = bytes(cam.capture()) +cam.free_buffer() +while not cam.frame_available(): + +print('The frame is available now. You can grab the image by the capture method =)') +``` + +This gives you the possibility of creating an asynchronous application without using asyncio. + +### Additional methods and examples Here are just a few examples: ```python cam.set_quality(90) # The quality goes from 0% to 100%, meaning 100% is the highest but has probably no compression -cam.set_bmp_out(True) # Enables convertion to bmp when capturing image camera.get_brightness() camera.set_vflip(True) #Enable vertical flip ``` See autocompletions in Thonny in order to see the list of methods. -If you want more insides in the methods and what they actually do, you can find a very good documentation [here](https://docs.circuitpython.org/en/latest/shared-bindings/espcamera/index.html). +If you want more insights in the methods and what they actually do, you can find a very good documentation [here](https://docs.circuitpython.org/en/latest/shared-bindings/espcamera/index.html). Note that each method requires a "get_" or "set_" prefix, depending on the desired action. +Take also a look in the examples folder. + To get the version of the camera driver used: ```python @@ -182,9 +205,9 @@ vers = camera.Version() ### Additional information -The FW images support the following cameras out of the box, but is therefore big: OV7670, OV7725, OV2640, OV3660, OV5640, NT99141, GC2145, GC032A, GC0308, BF3005, BF20A6, SC030IOT +The firmware images support the following cameras out of the box, but is therefore big: OV7670, OV7725, OV2640, OV3660, OV5640, NT99141, GC2145, GC032A, GC0308, BF3005, BF20A6, SC030IOT -## Build your custom FW +## Build your custom firmware ### Setting up the build environment (DIY method) @@ -192,14 +215,14 @@ To build the project, follow these instructions: - [ESP-IDF](https://docs.espressif.com/projects/esp-idf/en/v5.2.3/esp32/get-started/index.html): I used version 5.2.3, but it might work with other versions (see notes). - Clone the micropython repo and this repo in a folder, e.g. "MyESPCam". MicroPython version 1.24 or higher is required (at least commit 92484d8). -- You will have to add the ESP32-Camera driver (I used v2.0.15). To do this, add the following to the respective idf_component.yml file (e.g. in micropython/ports/esp32/main_esp32s3/idf_component.yml): +- You will have to add the ESP32-Camera driver from my fork. To do this, add the following to the respective idf_component.yml file (e.g. in micropython/ports/esp32/main_esp32s3/idf_component.yml): ```yml espressif/esp32-camera: - git: https://github.com/espressif/esp32-camera.git + git: https://github.com/cnadler86/esp32-camera.git ``` -Alternatively, you can clone the repository inside the esp-idf/components folder instead of altering the idf_component.yml file. +Alternatively, you can clone the repository inside the esp-idf/components folder instead of altering the idf_component.yml file. ### Add camera configurations to your board (optional, but recommended) @@ -265,7 +288,11 @@ Example for Xiao sense: ``` #### Customize additional camera settings -If you want to customize additional camera setting or reduce the FW size by removing support for unused camera sensors, then take a look at the kconfig file of the esp32-camera driver and specify these on the sdkconfig file of your board. +If you want to customize additional camera setting or reduce the firmware size by removing support for unused camera sensors, then take a look at the kconfig file of the esp32-camera driver and specify these on the sdkconfig file of your board. + +#### (Optional) Add the mp_jpeg module + +If you also want to include the [mp_jpeg module](https://github.com/cnadler86/mp_jpeg) in your build, clone the mp_jpeg repo at the same level and folder as the mp_camera_api repo and meet the requirements from the mp_jpeg repo. ### Build the API @@ -293,28 +320,25 @@ If you experience problems, visit [MicroPython external C modules](https://docs. ## Benchmark -I didn't use a calibrated osziloscope, but here is a FPS benchmark with my ESP32S3 (xclck_freq = 20MHz, 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) | -|------------|-----------|--------|--------|--------|----------------|----------------|-------------| -| R96X96 | 12.5 | 12.5 | 12.5 | No img | No img | No img | No img | -| QQVGA | 12.5 | 12.5 | 12.5 | 25 | 25 | 25 | 50 | -| 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 | 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 | -| XGA | No img | No img | No img | 6.3 | 1.6 | 1.6 | 12.5 | -| HD | No img | No img | No img | 6.3 | 1.4 | 1.3 | 12.5 | -| SXGA | 2 | 2 | 2 | 6.3 | 1 | 1 | 12.5 | -| UXGA | No img | No img | No img | 6.3 | 0.7 | 0.7 | 12.5 | - - -Looking at the results: image conversion make only sense for frame sized below QVGA or if capturing the image in the intended pixelformat and frame size combination fails. +I didn't use a calibrated oscilloscope, but here is a FPS benchmark with my ESP32S3 (xclk_freq = 20MHz, 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 apply for other PixelFormats. + +| Frame Size | GRAYSCALE | RGB565 | YUV422 | JPEG | JPEG (fb=2) | +|------------|-----------|--------|--------|--------|-------------| +| R96X96 | 12.5 | 12.5 | 12.5 | No img | No img | +| QQVGA | 12.5 | 12.5 | 12.5 | 25 | 50 | +| QCIF | 11 | 11 | 11.5 | 25 | 50 | +| HQVGA | 12.5 | 12.5 | 12.5 | 25 | 50 | +| R240X240 | 12.5 | 12.5 | 11.5 | 25 | 50 | +| QVGA | 12 | 11 | 12 | 25 | 50 | +| CIF | 12.5 | No img | No img | 6.3 | 12.5 | +| HVGA | 3 | 3 | 2.5 | 12.5 | 25 | +| VGA | 3 | 3 | 3 | 12.5 | 25 | +| SVGA | 3 | 3 | 3 | 12.5 | 25 | +| XGA | No img | No img | No img | 6.3 | 12.5 | +| HD | No img | No img | No img | 6.3 | 12.5 | +| SXGA | 2 | 2 | 2 | 6.3 | 12.5 | +| UXGA | No img | No img | No img | 6.3 | 12.5 | ## Troubleshooting diff --git a/examples/CameraSettings.py b/examples/CameraSettings.py index 21f50c4..3a45772 100644 --- a/examples/CameraSettings.py +++ b/examples/CameraSettings.py @@ -1,7 +1,7 @@ import network import asyncio import time -from camera import Camera, FrameSize, PixelFormat +from acamera import Camera, FrameSize, PixelFormat # Import the async version of the Camera class, you can also use the sync version (camera.Camera) cam = Camera(frame_size=FrameSize.VGA, pixel_format=PixelFormat.JPEG, jpeg_quality=85, init=False) # WLAN config @@ -34,7 +34,7 @@ async def stream_camera(writer): await writer.drain() while True: - frame = cam.capture() + frame = await cam.acapture() # This is the async version of capture, you can also use frame = cam.capture() instead if frame: if cam.get_pixel_format() == PixelFormat.JPEG: writer.write(b'--frame\r\nContent-Type: image/jpeg\r\n\r\n') diff --git a/src/acamera.py b/src/acamera.py new file mode 100644 index 0000000..46c2a6f --- /dev/null +++ b/src/acamera.py @@ -0,0 +1,12 @@ +import asyncio +from camera import Camera as _Camera +from camera import FrameSize, PixelFormat, GainCeiling, GrabMode + +class Camera(_Camera): + async def acapture(self): + self.free_buffer() # Free the buffer so the camera task grabs a new frame + while not self.frame_available(): + await asyncio.sleep(0) # Yield control to the event loop + return self.capture() + +__all__ = ['Camera', 'FrameSize', 'PixelFormat', 'GainCeiling', 'GrabMode'] \ No newline at end of file diff --git a/src/manifest.py b/src/manifest.py new file mode 100644 index 0000000..ff69f76 --- /dev/null +++ b/src/manifest.py @@ -0,0 +1,4 @@ +# Include the board's default manifest. +include("$(PORT_DIR)/boards/manifest.py") +# Add custom driver +module("acamera.py") \ No newline at end of file diff --git a/src/micropython.cmake b/src/micropython.cmake index 369f867..276c25f 100644 --- a/src/micropython.cmake +++ b/src/micropython.cmake @@ -1,6 +1,7 @@ -# `py.cmake` for `micropy_gather_target_properties` macro usage include(${MICROPY_DIR}/py/py.cmake) +set(MICROPY_FROZEN_MANIFEST ${CMAKE_CURRENT_LIST_DIR}/manifest.py) + add_library(usermod_mp_camera INTERFACE) target_sources(usermod_mp_camera INTERFACE @@ -8,34 +9,77 @@ target_sources(usermod_mp_camera INTERFACE ${CMAKE_CURRENT_LIST_DIR}/modcamera_api.c ) -if(NOT ESP32_CAMERA_DIR) - set(ESP32_CAMERA_DIR "${IDF_PATH}/components/esp32-camera") -endif() - -message("Camera Dir: ${ESP32_CAMERA_DIR}") +# Prefer user-defined ESP32_CAMERA_DIR if provided +if(DEFINED ESP32_CAMERA_DIR AND EXISTS "${ESP32_CAMERA_DIR}") + message(STATUS "Using user-defined ESP32 Camera directory: ${ESP32_CAMERA_DIR}") -if(EXISTS "${ESP32_CAMERA_DIR}") target_include_directories(usermod_mp_camera INTERFACE ${CMAKE_CURRENT_LIST_DIR} ${ESP32_CAMERA_DIR}/driver/include - ${ESP32_CAMERA_DIR}/driver/private_include ${ESP32_CAMERA_DIR}/conversions/include + ${ESP32_CAMERA_DIR}/driver/private_include ${ESP32_CAMERA_DIR}/conversions/private_include ${ESP32_CAMERA_DIR}/sensors/private_include ) + +# If no manual directory is provided, try to fetch it from ESP-IDF else() - target_include_directories(usermod_mp_camera INTERFACE - ${CMAKE_CURRENT_LIST_DIR}) + idf_component_get_property(CAMERA_INCLUDES esp32-camera INCLUDE_DIRS) + idf_component_get_property(CAMERA_PRIV_INCLUDES esp32-camera PRIV_INCLUDE_DIRS) + idf_component_get_property(CAMERA_DIR esp32-camera COMPONENT_DIR) + + if(CAMERA_DIR) + message(STATUS "Using ESP32 Camera component from ESP-IDF: ${CAMERA_DIR}") + + # Add public include directories from ESP-IDF + if(CAMERA_INCLUDES) + list(TRANSFORM CAMERA_INCLUDES PREPEND ${CAMERA_DIR}/) + target_include_directories(usermod_mp_camera INTERFACE ${CAMERA_INCLUDES}) + endif() + + # Add private include directories from ESP-IDF + if(CAMERA_PRIV_INCLUDES) + list(TRANSFORM CAMERA_PRIV_INCLUDES PREPEND ${CAMERA_DIR}/) + target_include_directories(usermod_mp_camera INTERFACE ${CAMERA_PRIV_INCLUDES}) + endif() + else() + message(WARNING "ESP32 Camera component not found in ESP-IDF!") + target_include_directories(usermod_mp_camera PUBLIC ${CMAKE_CURRENT_LIST_DIR}) + endif() +endif() + +# Check if MP_JPEG_DIR is set or if mp_jpeg directory exists two levels up +if(DEFINED MP_JPEG_DIR AND EXISTS "${MP_JPEG_DIR}") + message(STATUS "Using user-defined MP_JPEG_DIR: ${MP_JPEG_DIR}") + set(MP_JPEG_SRC "${MP_JPEG_DIR}/src/micropython.cmake") +elseif(EXISTS "${CMAKE_CURRENT_LIST_DIR}/../../mp_jpeg") + message(STATUS "Found mp_jpeg directory two levels up") + set(MP_JPEG_SRC "${CMAKE_CURRENT_LIST_DIR}/../../mp_jpeg/src/micropython.cmake") +endif() + +# Add MP_JPEG_SRC cmake file to target_sources if it is defined +if(DEFINED MP_JPEG_SRC AND EXISTS "${MP_JPEG_SRC}") + include(${MP_JPEG_SRC}) +else() + message(WARNING "MP_JPEG_SRC not found or not defined!") endif() +# Define MICROPY_CAMERA_MODEL if specified if (MICROPY_CAMERA_MODEL) - target_compile_definitions(usermod_mp_camera INTERFACE MICROPY_CAMERA_MODEL_${MICROPY_CAMERA_MODEL}=1) + target_compile_definitions(usermod_mp_camera INTERFACE + MICROPY_CAMERA_MODEL_${MICROPY_CAMERA_MODEL}=1 + ) endif() +# Define MP_CAMERA_DRIVER_VERSION if specified if (MP_CAMERA_DRIVER_VERSION) - target_compile_definitions(usermod_mp_camera INTERFACE MP_CAMERA_DRIVER_VERSION=\"${MP_CAMERA_DRIVER_VERSION}\") + target_compile_definitions(usermod_mp_camera INTERFACE + MP_CAMERA_DRIVER_VERSION=\"${MP_CAMERA_DRIVER_VERSION}\" + ) endif() +# Link the camera module with the main usermod target target_link_libraries(usermod INTERFACE usermod_mp_camera) +# Gather target properties for MicroPython build system micropy_gather_target_properties(usermod_mp_camera) diff --git a/src/modcamera.c b/src/modcamera.c index 6c1cc9a..5867b16 100644 --- a/src/modcamera.c +++ b/src/modcamera.c @@ -28,7 +28,6 @@ #include "modcamera.h" #include "esp_err.h" #include "esp_log.h" -#include "img_converters.h" #include "mphalport.h" #define TAG "MPY_CAMERA" @@ -153,10 +152,6 @@ void mp_camera_hal_construct( 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) { @@ -175,11 +170,6 @@ 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; @@ -214,62 +204,7 @@ 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) { +mp_obj_t mp_camera_hal_capture(mp_camera_obj_t *self) { check_init(self); if (self->captured_buffer) { esp_camera_fb_return(self->captured_buffer); @@ -282,91 +217,78 @@ mp_obj_t mp_camera_hal_capture(mp_camera_obj_t *self, int8_t out_format) { 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) { - 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; - } - } + return mp_obj_new_memoryview('b', self->captured_buffer->len, self->captured_buffer->buf); - if (self->bmp_out == false) { - ESP_LOGI(TAG, "Returning image without conversion"); - 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, self->converted_buffer.buf, &self->converted_buffer.len)) { - 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 { - 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")); - } - } -} // mp_camera_hal_capture +} + +mp_obj_t mp_camera_hal_frame_available(mp_camera_obj_t *self) { + check_init(self); + return mp_obj_new_bool(esp_camera_available_frames()); +} bool mp_camera_hal_initialized(mp_camera_obj_t *self){ return self->initialized; } +void mp_camera_hal_free_buffer(mp_camera_obj_t *self) { + if (self->captured_buffer) { + esp_camera_fb_return(self->captured_buffer); + self->captured_buffer = NULL; + } +} + const mp_rom_map_elem_t mp_camera_hal_pixel_format_table[] = { - { MP_ROM_QSTR(MP_QSTR_JPEG), MP_ROM_INT(PIXFORMAT_JPEG) }, - { MP_ROM_QSTR(MP_QSTR_YUV422), MP_ROM_INT(PIXFORMAT_YUV422) }, - { MP_ROM_QSTR(MP_QSTR_YUV420), MP_ROM_INT(PIXFORMAT_YUV420) }, - { 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) }, - { MP_ROM_QSTR(MP_QSTR_RAW), MP_ROM_INT(PIXFORMAT_RAW) }, - { MP_ROM_QSTR(MP_QSTR_RGB444), MP_ROM_INT(PIXFORMAT_RGB444) }, - { MP_ROM_QSTR(MP_QSTR_RGB555), MP_ROM_INT(PIXFORMAT_RGB555) }, + { MP_ROM_QSTR(MP_QSTR_JPEG), MP_ROM_INT((mp_uint_t)PIXFORMAT_JPEG) }, + { MP_ROM_QSTR(MP_QSTR_YUV422), MP_ROM_INT((mp_uint_t)PIXFORMAT_YUV422) }, + { MP_ROM_QSTR(MP_QSTR_YUV420), MP_ROM_INT((mp_uint_t)PIXFORMAT_YUV420) }, + { MP_ROM_QSTR(MP_QSTR_GRAYSCALE), MP_ROM_INT((mp_uint_t)PIXFORMAT_GRAYSCALE) }, + { MP_ROM_QSTR(MP_QSTR_RGB565), MP_ROM_INT((mp_uint_t)PIXFORMAT_RGB565) }, + { MP_ROM_QSTR(MP_QSTR_RGB888), MP_ROM_INT((mp_uint_t)PIXFORMAT_RGB888) }, + { MP_ROM_QSTR(MP_QSTR_RAW), MP_ROM_INT((mp_uint_t)PIXFORMAT_RAW) }, + { MP_ROM_QSTR(MP_QSTR_RGB444), MP_ROM_INT((mp_uint_t)PIXFORMAT_RGB444) }, + { MP_ROM_QSTR(MP_QSTR_RGB555), MP_ROM_INT((mp_uint_t)PIXFORMAT_RGB555) }, }; const mp_rom_map_elem_t mp_camera_hal_frame_size_table[] = { - { MP_ROM_QSTR(MP_QSTR_R96X96), MP_ROM_INT(FRAMESIZE_96X96) }, - { MP_ROM_QSTR(MP_QSTR_QQVGA), MP_ROM_INT(FRAMESIZE_QQVGA) }, - { MP_ROM_QSTR(MP_QSTR_R128x128), MP_ROM_INT(FRAMESIZE_128X128) }, - { MP_ROM_QSTR(MP_QSTR_QCIF), MP_ROM_INT(FRAMESIZE_QCIF) }, - { MP_ROM_QSTR(MP_QSTR_HQVGA), MP_ROM_INT(FRAMESIZE_HQVGA) }, - { MP_ROM_QSTR(MP_QSTR_R240X240), MP_ROM_INT(FRAMESIZE_240X240) }, - { MP_ROM_QSTR(MP_QSTR_QVGA), MP_ROM_INT(FRAMESIZE_QVGA) }, - { MP_ROM_QSTR(MP_QSTR_R320X320), MP_ROM_INT(FRAMESIZE_320X320) }, - { MP_ROM_QSTR(MP_QSTR_CIF), MP_ROM_INT(FRAMESIZE_CIF) }, - { MP_ROM_QSTR(MP_QSTR_HVGA), MP_ROM_INT(FRAMESIZE_HVGA) }, - { MP_ROM_QSTR(MP_QSTR_VGA), MP_ROM_INT(FRAMESIZE_VGA) }, - { MP_ROM_QSTR(MP_QSTR_SVGA), MP_ROM_INT(FRAMESIZE_SVGA) }, - { MP_ROM_QSTR(MP_QSTR_XGA), MP_ROM_INT(FRAMESIZE_XGA) }, - { MP_ROM_QSTR(MP_QSTR_HD), MP_ROM_INT(FRAMESIZE_HD) }, - { MP_ROM_QSTR(MP_QSTR_SXGA), MP_ROM_INT(FRAMESIZE_SXGA) }, - { MP_ROM_QSTR(MP_QSTR_UXGA), MP_ROM_INT(FRAMESIZE_UXGA) }, - { MP_ROM_QSTR(MP_QSTR_FHD), MP_ROM_INT(FRAMESIZE_FHD) }, - { MP_ROM_QSTR(MP_QSTR_P_HD), MP_ROM_INT(FRAMESIZE_P_HD) }, - { MP_ROM_QSTR(MP_QSTR_P_3MP), MP_ROM_INT(FRAMESIZE_P_3MP) }, - { MP_ROM_QSTR(MP_QSTR_QXGA), MP_ROM_INT(FRAMESIZE_QXGA) }, - { MP_ROM_QSTR(MP_QSTR_QHD), MP_ROM_INT(FRAMESIZE_QHD) }, - { MP_ROM_QSTR(MP_QSTR_WQXGA), MP_ROM_INT(FRAMESIZE_WQXGA) }, - { MP_ROM_QSTR(MP_QSTR_P_FHD), MP_ROM_INT(FRAMESIZE_P_FHD) }, - { MP_ROM_QSTR(MP_QSTR_QSXGA), MP_ROM_INT(FRAMESIZE_QSXGA) }, + { MP_ROM_QSTR(MP_QSTR_R96X96), MP_ROM_INT((mp_uint_t)FRAMESIZE_96X96) }, + { MP_ROM_QSTR(MP_QSTR_QQVGA), MP_ROM_INT((mp_uint_t)FRAMESIZE_QQVGA) }, + { MP_ROM_QSTR(MP_QSTR_R128x128), MP_ROM_INT((mp_uint_t)FRAMESIZE_128X128) }, + { MP_ROM_QSTR(MP_QSTR_QCIF), MP_ROM_INT((mp_uint_t)FRAMESIZE_QCIF) }, + { MP_ROM_QSTR(MP_QSTR_HQVGA), MP_ROM_INT((mp_uint_t)FRAMESIZE_HQVGA) }, + { MP_ROM_QSTR(MP_QSTR_R240X240), MP_ROM_INT((mp_uint_t)FRAMESIZE_240X240) }, + { MP_ROM_QSTR(MP_QSTR_QVGA), MP_ROM_INT((mp_uint_t)FRAMESIZE_QVGA) }, + { MP_ROM_QSTR(MP_QSTR_R320X320), MP_ROM_INT((mp_uint_t)FRAMESIZE_320X320) }, + { MP_ROM_QSTR(MP_QSTR_CIF), MP_ROM_INT((mp_uint_t)FRAMESIZE_CIF) }, + { MP_ROM_QSTR(MP_QSTR_HVGA), MP_ROM_INT((mp_uint_t)FRAMESIZE_HVGA) }, + { MP_ROM_QSTR(MP_QSTR_VGA), MP_ROM_INT((mp_uint_t)FRAMESIZE_VGA) }, + { MP_ROM_QSTR(MP_QSTR_SVGA), MP_ROM_INT((mp_uint_t)FRAMESIZE_SVGA) }, + { MP_ROM_QSTR(MP_QSTR_XGA), MP_ROM_INT((mp_uint_t)FRAMESIZE_XGA) }, + { MP_ROM_QSTR(MP_QSTR_HD), MP_ROM_INT((mp_uint_t)FRAMESIZE_HD) }, + { MP_ROM_QSTR(MP_QSTR_SXGA), MP_ROM_INT((mp_uint_t)FRAMESIZE_SXGA) }, + { MP_ROM_QSTR(MP_QSTR_UXGA), MP_ROM_INT((mp_uint_t)FRAMESIZE_UXGA) }, + { MP_ROM_QSTR(MP_QSTR_FHD), MP_ROM_INT((mp_uint_t)FRAMESIZE_FHD) }, + { MP_ROM_QSTR(MP_QSTR_P_HD), MP_ROM_INT((mp_uint_t)FRAMESIZE_P_HD) }, + { MP_ROM_QSTR(MP_QSTR_P_3MP), MP_ROM_INT((mp_uint_t)FRAMESIZE_P_3MP) }, + { MP_ROM_QSTR(MP_QSTR_QXGA), MP_ROM_INT((mp_uint_t)FRAMESIZE_QXGA) }, + { MP_ROM_QSTR(MP_QSTR_QHD), MP_ROM_INT((mp_uint_t)FRAMESIZE_QHD) }, + { MP_ROM_QSTR(MP_QSTR_WQXGA), MP_ROM_INT((mp_uint_t)FRAMESIZE_WQXGA) }, + { MP_ROM_QSTR(MP_QSTR_P_FHD), MP_ROM_INT((mp_uint_t)FRAMESIZE_P_FHD) }, + { MP_ROM_QSTR(MP_QSTR_QSXGA), MP_ROM_INT((mp_uint_t)FRAMESIZE_QSXGA) }, }; const mp_rom_map_elem_t mp_camera_hal_grab_mode_table[] = { - { MP_ROM_QSTR(MP_QSTR_WHEN_EMPTY), MP_ROM_INT(CAMERA_GRAB_WHEN_EMPTY) }, - { MP_ROM_QSTR(MP_QSTR_LATEST), MP_ROM_INT(CAMERA_GRAB_LATEST) }, + { MP_ROM_QSTR(MP_QSTR_WHEN_EMPTY), MP_ROM_INT((mp_uint_t)CAMERA_GRAB_WHEN_EMPTY) }, + { MP_ROM_QSTR(MP_QSTR_LATEST), MP_ROM_INT((mp_uint_t)CAMERA_GRAB_LATEST) }, }; const mp_rom_map_elem_t mp_camera_hal_gainceiling_table[] = { - { MP_ROM_QSTR(MP_QSTR_2X), MP_ROM_INT(GAINCEILING_2X) }, - { MP_ROM_QSTR(MP_QSTR_4X), MP_ROM_INT(GAINCEILING_4X) }, - { MP_ROM_QSTR(MP_QSTR_8X), MP_ROM_INT(GAINCEILING_8X) }, - { MP_ROM_QSTR(MP_QSTR_16X), MP_ROM_INT(GAINCEILING_16X) }, - { MP_ROM_QSTR(MP_QSTR_32X), MP_ROM_INT(GAINCEILING_32X) }, - { MP_ROM_QSTR(MP_QSTR_64X), MP_ROM_INT(GAINCEILING_64X) }, - { MP_ROM_QSTR(MP_QSTR_128X), MP_ROM_INT(GAINCEILING_128X) }, + { MP_ROM_QSTR(MP_QSTR_2X), MP_ROM_INT((mp_uint_t)GAINCEILING_2X) }, + { MP_ROM_QSTR(MP_QSTR_4X), MP_ROM_INT((mp_uint_t)GAINCEILING_4X) }, + { MP_ROM_QSTR(MP_QSTR_8X), MP_ROM_INT((mp_uint_t)GAINCEILING_8X) }, + { MP_ROM_QSTR(MP_QSTR_16X), MP_ROM_INT((mp_uint_t)GAINCEILING_16X) }, + { MP_ROM_QSTR(MP_QSTR_32X), MP_ROM_INT((mp_uint_t)GAINCEILING_32X) }, + { MP_ROM_QSTR(MP_QSTR_64X), MP_ROM_INT((mp_uint_t)GAINCEILING_64X) }, + { MP_ROM_QSTR(MP_QSTR_128X), MP_ROM_INT((mp_uint_t)GAINCEILING_128X) }, }; // Helper functions to get and set camera and sensor information diff --git a/src/modcamera.h b/src/modcamera.h index 4d86ce8..a3ce749 100644 --- a/src/modcamera.h +++ b/src/modcamera.h @@ -96,8 +96,6 @@ typedef struct hal_camera_obj { camera_config_t camera_config; 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 @@ -184,19 +182,24 @@ 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 out_format Output pixelformat format. * @return Captured image as micropython object. */ -extern mp_obj_t mp_camera_hal_capture(mp_camera_obj_t *self, int8_t out_format); +extern mp_obj_t mp_camera_hal_capture(mp_camera_obj_t *self); /** - * @brief Converts an image from one pixelformat to another. + * @brief Returns true, if a frame is available. * * @param self Pointer to the camera object. - * @param out_format Output pixelformat format. - * @return Converted image as micropython object. + * @return True, if a frame is available. */ -extern mp_obj_t mp_camera_hal_convert(mp_camera_obj_t *self, int8_t out_format); +extern mp_obj_t mp_camera_hal_frame_available(mp_camera_obj_t *self); + +/** + * @brief Frees the buffer of the camera object. + * + * @param self Pointer to the camera object. + */ +extern void mp_camera_hal_free_buffer(mp_camera_obj_t *self); /** * @brief Table mapping pixel formats API to their corresponding values at HAL. diff --git a/src/modcamera_api.c b/src/modcamera_api.c index 4799ce3..39afa71 100644 --- a/src/modcamera_api.c +++ b/src/modcamera_api.c @@ -40,7 +40,7 @@ const mp_obj_type_t camera_type; //Constructor static mp_obj_t mp_camera_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { - enum { ARG_data_pins, ARG_pixel_clock_pin, ARG_vsync_pin, ARG_href_pin, ARG_sda_pin, ARG_scl_pin, ARG_xclock_pin, ARG_xclock_frequency, ARG_powerdown_pin, ARG_reset_pin, ARG_pixel_format, ARG_frame_size, ARG_jpeg_quality, ARG_fb_count, ARG_grab_mode, ARG_init, ARG_bmp_out, NUM_ARGS }; + enum { ARG_data_pins, ARG_pixel_clock_pin, ARG_vsync_pin, ARG_href_pin, ARG_sda_pin, ARG_scl_pin, ARG_xclock_pin, ARG_xclock_frequency, ARG_powerdown_pin, ARG_reset_pin, ARG_pixel_format, ARG_frame_size, ARG_jpeg_quality, ARG_fb_count, ARG_grab_mode, ARG_init, NUM_ARGS }; static const mp_arg_t allowed_args[] = { #ifdef MICROPY_CAMERA_ALL_REQ_PINS_DEFINED { MP_QSTR_data_pins, MP_ARG_OBJ | MP_ARG_KW_ONLY , { .u_obj = MP_ROM_NONE } }, @@ -68,7 +68,6 @@ static mp_obj_t mp_camera_make_new(const mp_obj_type_t *type, size_t n_args, siz { MP_QSTR_fb_count, MP_ARG_INT | MP_ARG_KW_ONLY, { .u_int = MICROPY_CAMERA_FB_COUNT } }, { MP_QSTR_grab_mode, MP_ARG_INT | MP_ARG_KW_ONLY, { .u_int = MICROPY_CAMERA_GRAB_MODE } }, { MP_QSTR_init, MP_ARG_BOOL | MP_ARG_KW_ONLY, { .u_bool = true } }, - { MP_QSTR_bmp_out, MP_ARG_BOOL | MP_ARG_KW_ONLY, { .u_bool = false } }, }; mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; @@ -129,37 +128,43 @@ static mp_obj_t mp_camera_make_new(const mp_obj_type_t *type, size_t n_args, siz mp_camera_obj_t *self = mp_obj_malloc_with_finaliser(mp_camera_obj_t, &camera_type); self->base.type = &camera_type; - self->bmp_out = args[ARG_bmp_out].u_bool; mp_camera_hal_construct(self, data_pins, xclock_pin, pixel_clock_pin, vsync_pin, href_pin, powerdown_pin, reset_pin, sda_pin, scl_pin, xclock_frequency, pixel_format, frame_size, jpeg_quality, fb_count, grab_mode); mp_camera_hal_init(self); - if (mp_camera_hal_capture(self, -1) == mp_const_none){ + if (mp_camera_hal_capture(self) == mp_const_none){ mp_camera_hal_deinit(self); mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Failed to capture initial frame. Construct a new object with appropriate configuration.")); } else { if ( !args[ARG_init].u_bool ){ mp_camera_hal_deinit(self); + } else { + mp_camera_hal_free_buffer(self); } return MP_OBJ_FROM_PTR(self); } } // camera_construct // Main methods -static mp_obj_t camera_capture(size_t n_args, const mp_obj_t *args){ - mp_camera_obj_t *self = MP_OBJ_TO_PTR(args[0]); - int8_t out_format = n_args < 2 ? -1 : mp_obj_get_int(args[1]); - return mp_camera_hal_capture(self, out_format); +static mp_obj_t camera_capture(mp_obj_t self_in){ + mp_camera_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_camera_hal_capture(self); +} +static MP_DEFINE_CONST_FUN_OBJ_1(camera_capture_obj, camera_capture); + +static mp_obj_t camera_frame_available(mp_obj_t self_in){ + mp_camera_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_camera_hal_frame_available(self); } -static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(camera_capture_obj, 1, 2, camera_capture); +static MP_DEFINE_CONST_FUN_OBJ_1(camera_frame_available_obj, camera_frame_available); -static mp_obj_t camera_convert(mp_obj_t self_in, mp_obj_t arg) { +static mp_obj_t camera_free_buf(mp_obj_t self_in) { 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); + mp_camera_hal_free_buffer(self); + return mp_const_none; } -static MP_DEFINE_CONST_FUN_OBJ_2(camera_convert_obj, camera_convert); +static MP_DEFINE_CONST_FUN_OBJ_1(camera_free_buf_obj, camera_free_buf); static mp_obj_t camera_reconfigure(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args){ mp_camera_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); @@ -210,19 +215,6 @@ static mp_obj_t mp_camera_deinit(mp_obj_t self_in) { } static MP_DEFINE_CONST_FUN_OBJ_1(mp_camera_deinit_obj, mp_camera_deinit); -static mp_obj_t camera_get_bmp_out(mp_obj_t self_in) { - mp_camera_obj_t *self = MP_OBJ_TO_PTR(self_in); - return mp_obj_new_bool(self->bmp_out); -} -static MP_DEFINE_CONST_FUN_OBJ_1(camera_get_bmp_out_obj, camera_get_bmp_out); - -static mp_obj_t camera_set_bmp_out(mp_obj_t self_in, mp_obj_t arg) { - mp_camera_obj_t *self = MP_OBJ_TO_PTR(self_in); - self->bmp_out = mp_obj_is_true(arg); - return mp_const_none; -} -static MP_DEFINE_CONST_FUN_OBJ_2(camera_set_bmp_out_obj, camera_set_bmp_out); - // Destructor static mp_obj_t mp_camera_obj___exit__(size_t n_args, const mp_obj_t *args) { (void)n_args; @@ -297,7 +289,8 @@ 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_frame_available), MP_ROM_PTR(&camera_frame_available_obj) }, + { MP_ROM_QSTR(MP_QSTR_free_buffer), MP_ROM_PTR(&camera_free_buf_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) }, @@ -336,7 +329,6 @@ static const mp_rom_map_elem_t camera_camera_locals_table[] = { ADD_PROPERTY_TO_TABLE(wpc), ADD_PROPERTY_TO_TABLE(raw_gma), ADD_PROPERTY_TO_TABLE(lenc), - ADD_PROPERTY_TO_TABLE(bmp_out), }; static MP_DEFINE_CONST_DICT(camera_camera_locals_dict, camera_camera_locals_table); From 6a03743cb7c32093c16324bf90d97fdb6def525f Mon Sep 17 00:00:00 2001 From: Christopher Nadler Date: Sat, 22 Mar 2025 05:20:42 +0100 Subject: [PATCH 23/43] correct bug in micropython.cmake when using esp32-camera component from esp registry --- src/micropython.cmake | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/micropython.cmake b/src/micropython.cmake index 276c25f..914db83 100644 --- a/src/micropython.cmake +++ b/src/micropython.cmake @@ -4,6 +4,8 @@ set(MICROPY_FROZEN_MANIFEST ${CMAKE_CURRENT_LIST_DIR}/manifest.py) add_library(usermod_mp_camera INTERFACE) +add_dependencies(usermod_mp_camera esp32_camera) + target_sources(usermod_mp_camera INTERFACE ${CMAKE_CURRENT_LIST_DIR}/modcamera.c ${CMAKE_CURRENT_LIST_DIR}/modcamera_api.c @@ -23,7 +25,7 @@ if(DEFINED ESP32_CAMERA_DIR AND EXISTS "${ESP32_CAMERA_DIR}") ) # If no manual directory is provided, try to fetch it from ESP-IDF -else() +elseif(EXISTS ${IDF_PATH}/components/esp32-camera) idf_component_get_property(CAMERA_INCLUDES esp32-camera INCLUDE_DIRS) idf_component_get_property(CAMERA_PRIV_INCLUDES esp32-camera PRIV_INCLUDE_DIRS) idf_component_get_property(CAMERA_DIR esp32-camera COMPONENT_DIR) From 84d3b0f07cd4d1d8d2d93324fc19e6ee3b993cf7 Mon Sep 17 00:00:00 2001 From: Christopher Nadler Date: Sat, 22 Mar 2025 05:31:41 +0100 Subject: [PATCH 24/43] Update example --- examples/CameraSettings.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/examples/CameraSettings.py b/examples/CameraSettings.py index 3a45772..9515a19 100644 --- a/examples/CameraSettings.py +++ b/examples/CameraSettings.py @@ -26,8 +26,6 @@ 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) await asyncio.sleep(1) writer.write(b'HTTP/1.1 200 OK\r\nContent-Type: multipart/x-mixed-replace; boundary=frame\r\n\r\n') @@ -36,10 +34,7 @@ async def stream_camera(writer): while True: frame = await cam.acapture() # This is the async version of capture, you can also use frame = cam.capture() instead if frame: - 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(b'--frame\r\nContent-Type: image/jpeg\r\n\r\n') writer.write(frame) await writer.drain() From 0cc4247af20f05e96c9fcbaf28149c9139e3fc17 Mon Sep 17 00:00:00 2001 From: Christopher Nadler <147471517+cnadler86@users.noreply.github.com> Date: Sat, 22 Mar 2025 14:22:21 +0100 Subject: [PATCH 25/43] README.md aktualisieren --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index da880cd..251430a 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ The API is stable, but it might change without previous announce. If you are not familiar with building custom firmware, visit the [releases](https://github.com/cnadler86/micropython-camera-API/releases) page to download firmware that suits your board. **There are over 20 precompiled board images with the latest micropython!** -This firmware binaries also include the [mp_jpeg modul](https://github.com/cnadler86/mp_jpe) to encode/decode JPEGs. +This firmware binaries also include the [mp_jpeg modul](https://github.com/cnadler86/mp_jpeg) to encode/decode JPEGs. ## Using the API From 5befb52c9225b674798d0611a61501051a8fbf0a Mon Sep 17 00:00:00 2001 From: Christopher Nadler <147471517+cnadler86@users.noreply.github.com> Date: Sat, 22 Mar 2025 14:23:53 +0100 Subject: [PATCH 26/43] README.md aktualisieren --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 251430a..d8552b7 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ The API is stable, but it might change without previous announce. If you are not familiar with building custom firmware, visit the [releases](https://github.com/cnadler86/micropython-camera-API/releases) page to download firmware that suits your board. **There are over 20 precompiled board images with the latest micropython!** -This firmware binaries also include the [mp_jpeg modul](https://github.com/cnadler86/mp_jpeg) to encode/decode JPEGs. +These firmware binaries also include the [mp_jpeg modul](https://github.com/cnadler86/mp_jpeg) to encode/decode JPEGs. ## Using the API From 7567703bd5ad39ff4bdc6bda726d8959acdf842a Mon Sep 17 00:00:00 2001 From: Christopher Nadler <147471517+cnadler86@users.noreply.github.com> Date: Sat, 22 Mar 2025 17:17:50 +0100 Subject: [PATCH 27/43] micropython.cmake aktualisieren Add new function to register usermodule only once --- src/micropython.cmake | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/micropython.cmake b/src/micropython.cmake index 914db83..7398d9f 100644 --- a/src/micropython.cmake +++ b/src/micropython.cmake @@ -1,5 +1,12 @@ include(${MICROPY_DIR}/py/py.cmake) +function(check_and_register_component target component) + get_target_property(LIBRARIES ${target} INTERFACE_LINK_LIBRARIES) + if(NOT "${LIBRARIES}" MATCHES "${component}") + target_link_libraries(${target} INTERFACE ${component}) + endif() +endfunction() + set(MICROPY_FROZEN_MANIFEST ${CMAKE_CURRENT_LIST_DIR}/manifest.py) add_library(usermod_mp_camera INTERFACE) @@ -81,7 +88,7 @@ if (MP_CAMERA_DRIVER_VERSION) endif() # Link the camera module with the main usermod target -target_link_libraries(usermod INTERFACE usermod_mp_camera) +check_and_register_component(usermod usermod_mp_camera) # Gather target properties for MicroPython build system micropy_gather_target_properties(usermod_mp_camera) From 2b63e5158e9be96c565d7f5e6bfb70be077085f0 Mon Sep 17 00:00:00 2001 From: Christopher Nadler Date: Wed, 2 Apr 2025 15:29:07 +0200 Subject: [PATCH 28/43] small improvements (still poor performance) --- src/micropython.cmake | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/micropython.cmake b/src/micropython.cmake index 7398d9f..d08205b 100644 --- a/src/micropython.cmake +++ b/src/micropython.cmake @@ -1,12 +1,5 @@ include(${MICROPY_DIR}/py/py.cmake) -function(check_and_register_component target component) - get_target_property(LIBRARIES ${target} INTERFACE_LINK_LIBRARIES) - if(NOT "${LIBRARIES}" MATCHES "${component}") - target_link_libraries(${target} INTERFACE ${component}) - endif() -endfunction() - set(MICROPY_FROZEN_MANIFEST ${CMAKE_CURRENT_LIST_DIR}/manifest.py) add_library(usermod_mp_camera INTERFACE) @@ -75,6 +68,7 @@ endif() # Define MICROPY_CAMERA_MODEL if specified if (MICROPY_CAMERA_MODEL) + message(STATUS "Using user-defined camera model: ${MICROPY_CAMERA_MODEL}") target_compile_definitions(usermod_mp_camera INTERFACE MICROPY_CAMERA_MODEL_${MICROPY_CAMERA_MODEL}=1 ) @@ -88,7 +82,7 @@ if (MP_CAMERA_DRIVER_VERSION) endif() # Link the camera module with the main usermod target -check_and_register_component(usermod usermod_mp_camera) +target_link_libraries(usermod INTERFACE usermod_mp_camera) # Gather target properties for MicroPython build system micropy_gather_target_properties(usermod_mp_camera) From f34fcc93ed316e34bcaf6c2648e9f6f693394991 Mon Sep 17 00:00:00 2001 From: Christopher Nadler <147471517+cnadler86@users.noreply.github.com> Date: Sat, 5 Apr 2025 16:05:27 +0200 Subject: [PATCH 29/43] README.md aktualisieren --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index d8552b7..a3c9404 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ The API is stable, but it might change without previous announce. ## Precompiled firmware (the easy way) If you are not familiar with building custom firmware, visit the [releases](https://github.com/cnadler86/micropython-camera-API/releases) page to download firmware that suits your board. **There are over 20 precompiled board images with the latest micropython!** +To flash the firmware, you can use for example https://esp.huhn.me/](https://esp.huhn.me/). These firmware binaries also include the [mp_jpeg modul](https://github.com/cnadler86/mp_jpeg) to encode/decode JPEGs. From 9c6cd1248726a7a14ecb64c0c4b45d703f820ddd Mon Sep 17 00:00:00 2001 From: Christopher Nadler <147471517+cnadler86@users.noreply.github.com> Date: Sat, 5 Apr 2025 16:05:58 +0200 Subject: [PATCH 30/43] README.md aktualisieren --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a3c9404..d0f5834 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ The API is stable, but it might change without previous announce. ## Precompiled firmware (the easy way) If you are not familiar with building custom firmware, visit the [releases](https://github.com/cnadler86/micropython-camera-API/releases) page to download firmware that suits your board. **There are over 20 precompiled board images with the latest micropython!** -To flash the firmware, you can use for example https://esp.huhn.me/](https://esp.huhn.me/). +To flash the firmware, you can use for example [https://esp.huhn.me/](https://esp.huhn.me/). These firmware binaries also include the [mp_jpeg modul](https://github.com/cnadler86/mp_jpeg) to encode/decode JPEGs. From 71c868e4e122ddc1d4c9537ea0abd6ac4e1b02ec Mon Sep 17 00:00:00 2001 From: Christopher Nadler Date: Fri, 11 Apr 2025 15:51:19 +0200 Subject: [PATCH 31/43] Update example --- examples/CameraSettings.html | 4 ++++ examples/CameraSettings.py | 9 ++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/examples/CameraSettings.html b/examples/CameraSettings.html index 63d0055..e392005 100644 --- a/examples/CameraSettings.html +++ b/examples/CameraSettings.html @@ -199,6 +199,10 @@

Micropython Camera Stream

+
+ + +
diff --git a/examples/CameraSettings.py b/examples/CameraSettings.py index 9515a19..77d6caa 100644 --- a/examples/CameraSettings.py +++ b/examples/CameraSettings.py @@ -64,7 +64,14 @@ async def handle_client(reader, writer): writer.write(response.encode()) await writer.drain() else: - response = 'HTTP/1.1 404 Not Found\r\n\r\n' + try: + cam.reconfigure(**{method_name: value}) + print(f"Camera reconfigured with {method_name}={value}") + print("This action restores all previous configuration!") + response = 'HTTP/1.1 200 OK\r\n\r\n' + except Exception as e: + print(f"Error with {method_name}: {e}") + response = 'HTTP/1.1 404 Not Found\r\n\r\n' writer.write(response.encode()) await writer.drain() From 4334c80334cddff72a2db9f7e202fb013cd695df Mon Sep 17 00:00:00 2001 From: Christopher Nadler Date: Tue, 15 Apr 2025 11:57:51 +0200 Subject: [PATCH 32/43] Update readme --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d0f5834..6380fe9 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,11 @@ [![ESP32 Port](https://github.com/cnadler86/micropython-camera-API/actions/workflows/ESP32.yml/badge.svg)](https://github.com/cnadler86/micropython-camera-API/actions/workflows/ESP32.yml) This project aims to support various cameras (e.g. OV2640, OV5640) on different MicroPython ports, starting with the ESP32 port. The project implements a general API, has precompiled firmware images and supports a lot of cameras out of the box. Defaults are set to work with the OV2640. -At the moment, this is a micropython user module, but it might get in the micropython repo in the future. -The API is stable, but it might change without previous announce. + +The API is stable, but it might change. Please look into the release section for the latest changes. + +I tied to make things as easy as possible. If you find this project useful, please consider donating to support my work. Thanks! +[![Donate](https://img.shields.io/badge/Donate-PayPal-blue.svg)](https://www.paypal.me/cnadler) ## Content From 33328e888ddc04e4d5730a840eb9f974f28d71f2 Mon Sep 17 00:00:00 2001 From: Christopher Nadler Date: Wed, 16 Apr 2025 05:26:37 +0200 Subject: [PATCH 33/43] Release version --- .github/workflows/ESP32.yml | 4 ++-- README.md | 2 ++ src/modcamera.c | 6 +++--- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ESP32.yml b/.github/workflows/ESP32.yml index ea142af..c0ec164 100644 --- a/.github/workflows/ESP32.yml +++ b/.github/workflows/ESP32.yml @@ -76,8 +76,8 @@ jobs: if: steps.cache_esp_idf.outputs.cache-hit != 'true' run: | cd ~ - # git clone --depth 1 --branch release/v5.3 https://github.com/espressif/esp-idf.git - git clone --depth 1 --branch ${{ env.IDF_VER }} https://github.com/espressif/esp-idf.git + git clone --depth 1 --branch v5.4 https://github.com/espressif/esp-idf.git + # git clone --depth 1 --branch ${{ env.IDF_VER }} https://github.com/espressif/esp-idf.git git -C esp-idf submodule update --init --recursive --filter=tree:0 cd esp-idf ./install.sh all diff --git a/README.md b/README.md index 6380fe9..c46598e 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,8 @@ The API is stable, but it might change. Please look into the release section for I tied to make things as easy as possible. If you find this project useful, please consider donating to support my work. Thanks! [![Donate](https://img.shields.io/badge/Donate-PayPal-blue.svg)](https://www.paypal.me/cnadler) +If you want to play arround with AI, take a look at the [micropython binding for esp-dl](https://github.com/cnadler86/mp_esp_dl_models). + ## Content - [Precompiled firmware (the easy way)](#Precompiled-firmware-the-easy-way) diff --git a/src/modcamera.c b/src/modcamera.c index 5867b16..5a0bd05 100644 --- a/src/modcamera.c +++ b/src/modcamera.c @@ -97,7 +97,7 @@ static bool init_camera(mp_camera_obj_t *self) { self->camera_config.jpeg_quality = get_mapped_jpeg_quality(api_jpeg_quality); esp_err_t err = esp_camera_init(&self->camera_config); self->camera_config.jpeg_quality = api_jpeg_quality; - check_esp_err_(err); + check_esp_err(err); return true; } @@ -175,7 +175,7 @@ void mp_camera_hal_deinit(mp_camera_obj_t *self) { self->captured_buffer = NULL; } esp_err_t err = esp_camera_deinit(); - check_esp_err_(err); + check_esp_err(err); self->initialized = false; ESP_LOGI(TAG, "Camera deinitialized"); } @@ -198,7 +198,7 @@ void mp_camera_hal_reconfigure(mp_camera_obj_t *self, mp_camera_framesize_t fram set_check_grab_mode(self, grab_mode); set_check_fb_count(self, fb_count); - check_esp_err_(esp_camera_deinit()); + check_esp_err(esp_camera_deinit()); self->initialized = false; self->initialized = init_camera(self); ESP_LOGI(TAG, "Camera reconfigured successfully"); From 2dd97117359d00729d50448df19404d18f67ac30 Mon Sep 17 00:00:00 2001 From: Christopher Nadler Date: Wed, 16 Apr 2025 05:38:04 +0200 Subject: [PATCH 34/43] Fix pipeline --- .github/workflows/ESP32.yml | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/.github/workflows/ESP32.yml b/.github/workflows/ESP32.yml index c0ec164..ad39378 100644 --- a/.github/workflows/ESP32.yml +++ b/.github/workflows/ESP32.yml @@ -53,23 +53,6 @@ jobs: sudo apt-get update sudo apt-get install -y git wget flex bison gperf python3 python3-pip python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0 - # Clone the latest MicroPython release (if not cached) - - name: Clone MicroPython latest release - id: clone-micropython - if: steps.cache_esp_idf.outputs.cache-hit != 'true' - run: | - echo "Cloning MicroPython release: $MPY_RELEASE" - cd ~ - git clone --depth 1 --branch ${{ env.MPY_RELEASE }} https://github.com/micropython/micropython.git - cd micropython - git submodule update --init --depth 1 - cd mpy-cross - make - cd ~/micropython/ports/esp32 - make submodules - echo "Micropython setup successfully" - source ~/micropython/tools/ci.sh && echo "IDF_VER=$IDF_VER" >> $GITHUB_ENV - # Download and set up ESP-IDF (if not cached) - name: Set up ESP-IDF id: export-idf @@ -91,6 +74,25 @@ jobs: git clone https://github.com/espressif/esp-adf-libs.git cp -r ~/esp-adf-libs/esp_new_jpeg ~/esp-idf/components/ + # Clone the latest MicroPython release (if not cached) + - name: Clone MicroPython latest release + id: clone-micropython + if: steps.cache_esp_idf.outputs.cache-hit != 'true' + run: | + echo "Cloning MicroPython release: $MPY_RELEASE" + cd ~/esp-idf/ + source ./export.sh + cd ~ + git clone --depth 1 --branch ${{ env.MPY_RELEASE }} https://github.com/micropython/micropython.git + cd micropython + # git submodule update --init --depth 1 + cd mpy-cross + make + cd ~/micropython/ports/esp32 + make submodules + echo "Micropython setup successfully" + source ~/micropython/tools/ci.sh && echo "IDF_VER=$IDF_VER" >> $GITHUB_ENV + # Dynamically create jobs for each board build: needs: setup-environment From 70a68f04c5aed2e9179c6228833177050743683c Mon Sep 17 00:00:00 2001 From: Christopher Nadler <147471517+cnadler86@users.noreply.github.com> Date: Thu, 22 May 2025 06:53:33 +0200 Subject: [PATCH 35/43] general-issue.md aktualisieren --- .github/ISSUE_TEMPLATE/general-issue.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/ISSUE_TEMPLATE/general-issue.md b/.github/ISSUE_TEMPLATE/general-issue.md index 5460346..3e6d63d 100644 --- a/.github/ISSUE_TEMPLATE/general-issue.md +++ b/.github/ISSUE_TEMPLATE/general-issue.md @@ -8,3 +8,4 @@ assignees: '' --- PLEASE, read the documentation, search [previous issues](https://github.com/cnadler86/micropython-camera-API/issues?q=is%3Aissue) and/or ask ChatGPT before! +Also disconnect any peripherals to the board and try without them before raising an issue. From 91af395f9ca061bdd390e36e886dff762057f4d6 Mon Sep 17 00:00:00 2001 From: Christopher Nadler <147471517+cnadler86@users.noreply.github.com> Date: Thu, 22 May 2025 06:54:55 +0200 Subject: [PATCH 36/43] bug_report.md aktualisieren --- .github/ISSUE_TEMPLATE/bug_report.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index d612a9d..cf131bf 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -22,3 +22,5 @@ A clear and concise description of what you expected to happen. **Additional context** Add any other context about the problem here. + +Note: Also disconnect any peripherals to the board and try without them before raising a bug. \ No newline at end of file From 9ac3ad9f1134cd2ccd8c6cdb0be551adbd565d9d Mon Sep 17 00:00:00 2001 From: Christopher Nadler Date: Fri, 4 Jul 2025 15:51:22 +0200 Subject: [PATCH 37/43] Add M5STACK_ATOM_S3R Pin definitions --- .github/workflows/ESP32.yml | 1 + src/camera_pins.h | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/.github/workflows/ESP32.yml b/.github/workflows/ESP32.yml index ad39378..ae1332d 100644 --- a/.github/workflows/ESP32.yml +++ b/.github/workflows/ESP32.yml @@ -113,6 +113,7 @@ jobs: - ESP32_GENERIC-SPIRAM@M5STACK_WIDE - ESP32_GENERIC-SPIRAM@M5STACK_ESP32CAM - ESP32_GENERIC-SPIRAM@M5STACK_UNITCAM + - MICROPY_CAMERA_MODEL_M5STACK_ATOM_S3R - ESP32_GENERIC-SPIRAM@AI_THINKER - ESP32_GENERIC-SPIRAM@TTGO_T_JOURNAL - ESP32_GENERIC-SPIRAM@TTGO_T_CAMERA_PLUS diff --git a/src/camera_pins.h b/src/camera_pins.h index 203f7f1..fd0f911 100644 --- a/src/camera_pins.h +++ b/src/camera_pins.h @@ -116,6 +116,25 @@ #define MICROPY_CAMERA_PIN_HREF 18 #define MICROPY_CAMERA_PIN_PCLK 12 +#elif defined(MICROPY_CAMERA_MODEL_M5STACK_ATOM_S3R) +#define MICROPY_CAMERA_PIN_PWDN 18 +#define MICROPY_CAMERA_PIN_RESET -1 +#define MICROPY_CAMERA_PIN_XCLK 21 +#define MICROPY_CAMERA_PIN_SIOD 12 +#define MICROPY_CAMERA_PIN_SIOC 9 + +#define MICROPY_CAMERA_PIN_D7 13 +#define MICROPY_CAMERA_PIN_D6 11 +#define MICROPY_CAMERA_PIN_D5 17 +#define MICROPY_CAMERA_PIN_D4 4 +#define MICROPY_CAMERA_PIN_D3 48 +#define MICROPY_CAMERA_PIN_D2 46 +#define MICROPY_CAMERA_PIN_D1 42 +#define MICROPY_CAMERA_PIN_D0 3 +#define MICROPY_CAMERA_PIN_VSYNC 10 +#define MICROPY_CAMERA_PIN_HREF 14 +#define MICROPY_CAMERA_PIN_PCLK 40 + #elif defined(MICROPY_CAMERA_MODEL_AI_THINKER) #define MICROPY_CAMERA_PIN_PWDN 32 #define MICROPY_CAMERA_PIN_RESET -1 From 720a195bf03086d2a03de264d88920d6235248cf Mon Sep 17 00:00:00 2001 From: Christopher Nadler Date: Fri, 4 Jul 2025 15:53:20 +0200 Subject: [PATCH 38/43] Update readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c46598e..5dc3976 100644 --- a/README.md +++ b/README.md @@ -252,6 +252,7 @@ Below is a list of supported `MICROPY_CAMERA_MODEL_xxx` definitions: - MICROPY_CAMERA_MODEL_M5STACK_WIDE - [M5Stack Wide](https://shop.m5stack.com/collections/m5-cameras) - MICROPY_CAMERA_MODEL_M5STACK_ESP32CAM - [M5Stack ESP32CAM](https://shop.m5stack.com/collections/m5-cameras) - MICROPY_CAMERA_MODEL_M5STACK_CAMS3_UNIT - [M5Stack CAMS3 Unit](https://shop.m5stack.com/collections/m5-cameras) +- MICROPY_CAMERA_MODEL_M5STACK_ATOM_S3R - [M5Stack Atom S3R](https://shop.m5stack.com/products/atoms3r-camera-kit-m12-version-ov3660) - MICROPY_CAMERA_MODEL_AI_THINKER - [AI-Thinker ESP32-CAM] - MICROPY_CAMERA_MODEL_XIAO_ESP32S3 - [XIAO ESP32S3](https://www.seeedstudio.com/xiao-series-page) - MICROPY_CAMERA_MODEL_ESP32_MP_CAMERA_BOARD - [ESP32 MP Camera Board] From bd668701d630532889c604852a17b445ec653074 Mon Sep 17 00:00:00 2001 From: Christopher Nadler Date: Fri, 4 Jul 2025 16:47:31 +0200 Subject: [PATCH 39/43] update pipeline --- .github/workflows/ESP32.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ESP32.yml b/.github/workflows/ESP32.yml index ae1332d..fc58d07 100644 --- a/.github/workflows/ESP32.yml +++ b/.github/workflows/ESP32.yml @@ -113,11 +113,11 @@ jobs: - ESP32_GENERIC-SPIRAM@M5STACK_WIDE - ESP32_GENERIC-SPIRAM@M5STACK_ESP32CAM - ESP32_GENERIC-SPIRAM@M5STACK_UNITCAM - - MICROPY_CAMERA_MODEL_M5STACK_ATOM_S3R - ESP32_GENERIC-SPIRAM@AI_THINKER - ESP32_GENERIC-SPIRAM@TTGO_T_JOURNAL - ESP32_GENERIC-SPIRAM@TTGO_T_CAMERA_PLUS - ESP32_GENERIC_S3-SPIRAM_OCT@M5STACK_CAMS3_UNIT + - ESP32_GENERIC_S3-SPIRAM_OCT@M5STACK_ATOM_S3R - ESP32_GENERIC_S3-SPIRAM_OCT@XIAO_ESP32S3 - ESP32_GENERIC_S3-SPIRAM_OCT@ESP32S3_CAM_LCD - ESP32_GENERIC_S3-SPIRAM_OCT@ESP32S3_EYE From 192095a355eba0e8c645c5525eab6a69a9666a00 Mon Sep 17 00:00:00 2001 From: Christopher Nadler Date: Tue, 29 Jul 2025 21:41:33 +0200 Subject: [PATCH 40/43] update M5 stack atom s3 --- README.md | 2 +- src/camera_pins.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5dc3976..3768354 100644 --- a/README.md +++ b/README.md @@ -252,7 +252,7 @@ Below is a list of supported `MICROPY_CAMERA_MODEL_xxx` definitions: - MICROPY_CAMERA_MODEL_M5STACK_WIDE - [M5Stack Wide](https://shop.m5stack.com/collections/m5-cameras) - MICROPY_CAMERA_MODEL_M5STACK_ESP32CAM - [M5Stack ESP32CAM](https://shop.m5stack.com/collections/m5-cameras) - MICROPY_CAMERA_MODEL_M5STACK_CAMS3_UNIT - [M5Stack CAMS3 Unit](https://shop.m5stack.com/collections/m5-cameras) -- MICROPY_CAMERA_MODEL_M5STACK_ATOM_S3R - [M5Stack Atom S3R](https://shop.m5stack.com/products/atoms3r-camera-kit-m12-version-ov3660) +- MICROPY_CAMERA_MODEL_M5STACK_ATOM_S3R - [M5Stack Atom S3R](https://docs.m5stack.com/en/core/AtomS3R-M12) - MICROPY_CAMERA_MODEL_AI_THINKER - [AI-Thinker ESP32-CAM] - MICROPY_CAMERA_MODEL_XIAO_ESP32S3 - [XIAO ESP32S3](https://www.seeedstudio.com/xiao-series-page) - MICROPY_CAMERA_MODEL_ESP32_MP_CAMERA_BOARD - [ESP32 MP Camera Board] diff --git a/src/camera_pins.h b/src/camera_pins.h index fd0f911..6041cae 100644 --- a/src/camera_pins.h +++ b/src/camera_pins.h @@ -117,7 +117,7 @@ #define MICROPY_CAMERA_PIN_PCLK 12 #elif defined(MICROPY_CAMERA_MODEL_M5STACK_ATOM_S3R) -#define MICROPY_CAMERA_PIN_PWDN 18 +#define MICROPY_CAMERA_PIN_PWDN -1 //needs to be low to run #define MICROPY_CAMERA_PIN_RESET -1 #define MICROPY_CAMERA_PIN_XCLK 21 #define MICROPY_CAMERA_PIN_SIOD 12 From febc944870bae8e437abf2470047758ab67e2b17 Mon Sep 17 00:00:00 2001 From: Christopher Nadler Date: Sat, 2 Aug 2025 14:44:02 +0200 Subject: [PATCH 41/43] add typing stub --- typings/acamera.pyi | 9 ++ typings/camera.pyi | 348 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 357 insertions(+) create mode 100644 typings/acamera.pyi create mode 100644 typings/camera.pyi diff --git a/typings/acamera.pyi b/typings/acamera.pyi new file mode 100644 index 0000000..88ede9f --- /dev/null +++ b/typings/acamera.pyi @@ -0,0 +1,9 @@ +from camera import Camera as _Camera + +class Camera(_Camera): + async def acapture(self) -> memoryview: + """Asynchronously capture a frame and return it as a memoryview. + + Yields control back to the event loop while waiting for a frame to become available. + """ + ... \ No newline at end of file diff --git a/typings/camera.pyi b/typings/camera.pyi new file mode 100644 index 0000000..679d9be --- /dev/null +++ b/typings/camera.pyi @@ -0,0 +1,348 @@ +from __future__ import annotations +from typing import Final + +class GainCeiling(): + X2: Final[int] = 0 # 2X gain + X4: Final[int] = 1 # 4X gain + X8: Final[int] = 2 # 8X gain + X16: Final[int] = 3 # 16X gain + X32: Final[int] = 4 # 32X gain + X64: Final[int] = 5 # 64X gain + X128: Final[int] = 6 # 128X gain + def __init__(self, *argv, **kwargs) -> None: + ... + + +class GrabMode(): + WHEN_EMPTY: Final[int] = 0 + LATEST: Final[int] = 1 + def __init__(self, *argv, **kwargs) -> None: + ... + + +class PixelFormat(): + RGB555: Final[int] = 8 + RGB565: Final[int] = 0 + RGB888: Final[int] = 5 + YUV420: Final[int] = 2 + YUV422: Final[int] = 1 + RGB444: Final[int] = 7 + GRAYSCALE: Final[int] = 3 + JPEG: Final[int] = 4 + RAW: Final[int] = 6 + def __init__(self, *argv, **kwargs) -> None: + ... + + +class FrameSize(): + R240X240: Final[int] = 5 + XGA: Final[int] = 12 + R320X320: Final[int] = 7 + QVGA: Final[int] = 6 + R128x128: int = 2 + QXGA: Final[int] = 19 + VGA: Final[int] = 10 + R96X96: Final[int] = 0 + WQXGA: Final[int] = 21 + SVGA: Final[int] = 11 + UXGA: Final[int] = 15 + SXGA: Final[int] = 14 + HQVGA: Final[int] = 4 + QSXGA: Final[int] = 23 + HVGA: Final[int] = 9 + CIF: Final[int] = 8 + HD: Final[int] = 13 + FHD: Final[int] = 16 + QHD: Final[int] = 20 + P_3MP: Final[int] = 18 + QQVGA: Final[int] = 1 + P_FHD: Final[int] = 22 + QCIF: Final[int] = 3 + P_HD: Final[int] = 17 + def __init__(self, *argv, **kwargs) -> None: + ... + + +class Camera(): + def __init__(self, *, + data_pins: list[int] | None = None, + pclk_pin: int | None = None, + vsync_pin: int | None = None, + href_pin: int | None = None, + sda_pin: int | None = None, + scl_pin: int | None = None, + xclk_pin: int | None = None, + xclk_freq: int = 20000000, + powerdown_pin: int = -1, + reset_pin: int = -1, + pixel_format: int = PixelFormat.RGB565, + frame_size: int = FrameSize.QVGA, + jpeg_quality: int = 85, + fb_count: int = 1, + grab_mode: int = GrabMode.WHEN_EMPTY, + init: bool = True) -> None: + ... + + def get_special_effect(self) -> int: + """Get the current special effect setting.""" + ... + + def set_special_effect(self, value: int) -> None: + """Set the special effect.""" + ... + + def set_awb_gain(self, value: bool) -> None: + """Enable/disable Auto White Balance Gain.""" + ... + + def get_awb_gain(self) -> bool: + """Get Auto White Balance Gain state.""" + ... + + def set_agc_gain(self, value: int) -> None: + """Set the Auto Gain Control gain value.""" + ... + + def get_agc_gain(self) -> int: + """Get the Auto Gain Control gain value.""" + ... + + def set_aec_value(self, value: int) -> None: + """Set the Auto Exposure Control value.""" + ... + + def get_aec_value(self) -> int: + """Get the Auto Exposure Control value.""" + ... + + def set_bpc(self, value: bool) -> None: + """Enable/disable Bad Pixel Correction.""" + ... + + def get_bpc(self) -> bool: + """Get Bad Pixel Correction state.""" + ... + + def set_contrast(self, value: int) -> None: + """Set the contrast level (-2 to 2).""" + ... + + def get_contrast(self) -> int: + """Get the contrast level.""" + ... + + def set_colorbar(self, value: bool) -> None: + """Enable/disable color bar test pattern.""" + ... + + def get_colorbar(self) -> bool: + """Get color bar test pattern state.""" + ... + + def set_brightness(self, value: int) -> None: + """Set the brightness level (-2 to 2).""" + ... + + def get_brightness(self) -> int: + """Get the brightness level.""" + ... + + def set_aec2(self, value: bool) -> None: + """Enable/disable AEC DSP.""" + ... + + def get_aec2(self) -> bool: + """Get AEC DSP state.""" + ... + + def set_whitebal(self, value: bool) -> None: + """Enable/disable white balance.""" + ... + + def get_whitebal(self) -> bool: + """Get white balance state.""" + ... + + def set_wb_mode(self, value: int) -> None: + """Set white balance mode.""" + ... + + def get_wb_mode(self) -> int: + """Get white balance mode.""" + ... + + def set_vflip(self, value: bool) -> None: + """Enable/disable vertical flip.""" + ... + + def get_vflip(self) -> bool: + """Get vertical flip state.""" + ... + + def set_wpc(self, value: bool) -> None: + """Enable/disable White Pixel Correction.""" + ... + + def get_wpc(self) -> bool: + """Get White Pixel Correction state.""" + ... + + def set_ae_level(self, value: int) -> None: + """Set Auto Exposure level (-2 to 2).""" + ... + + def get_ae_level(self) -> int: + """Get Auto Exposure level.""" + ... + + def reconfigure(self, *, frame_size: int | None = None, + pixel_format: int | None = None, + grab_mode: int | None = None, + fb_count: int | None = None) -> None: + """Reconfigure camera with new settings.""" + ... + + def init(self) -> None: + """Initialize the camera.""" + ... + + def deinit(self) -> None: + """Deinitialize the camera.""" + ... + + def set_dcw(self, value: bool) -> None: + """Enable/disable DCW (Downsize EN).""" + ... + + def get_dcw(self) -> bool: + """Get DCW (Downsize EN) state.""" + ... + + def set_sharpness(self, value: int) -> None: + """Set the sharpness level (-2 to 2).""" + ... + + def get_sharpness(self) -> int: + """Get the sharpness level.""" + ... + + def set_saturation(self, value: int) -> None: + """Set the saturation level (-2 to 2).""" + ... + + def get_saturation(self) -> int: + """Get the saturation level.""" + ... + + def set_raw_gma(self, value: bool) -> None: + """Enable/disable GMA (Gamma) Correction.""" + ... + + def get_raw_gma(self) -> bool: + """Get GMA (Gamma) Correction state.""" + ... + + def set_quality(self, value: int) -> None: + """Set JPEG quality (0-63).""" + ... + + def get_quality(self) -> int: + """Get JPEG quality.""" + ... + + def set_frame_size(self, value: int) -> None: + """Set frame size.""" + ... + + def get_frame_size(self) -> int: + """Get frame size.""" + ... + + def set_exposure_ctrl(self, value: bool) -> None: + """Enable/disable Auto Exposure Control.""" + ... + + def get_exposure_ctrl(self) -> bool: + """Get Auto Exposure Control state.""" + ... + + def set_denoise(self, value: int) -> None: + """Set denoise level.""" + ... + + def get_denoise(self) -> int: + """Get denoise level.""" + ... + + def set_gain_ctrl(self, value: bool) -> None: + """Enable/disable Auto Gain Control.""" + ... + + def get_gain_ctrl(self) -> bool: + """Get Auto Gain Control state.""" + ... + + def set_lenc(self, value: bool) -> None: + """Enable/disable Lens Correction.""" + ... + + def get_lenc(self) -> bool: + """Get Lens Correction state.""" + ... + + def set_hmirror(self, value: bool) -> None: + """Enable/disable horizontal mirror.""" + ... + + def get_hmirror(self) -> bool: + """Get horizontal mirror state.""" + ... + + def set_gainceiling(self, value: int) -> None: + """Set gain ceiling.""" + ... + + def get_gainceiling(self) -> int: + """Get gain ceiling.""" + ... + + def frame_available(self) -> bool: + """Check if a frame is available.""" + ... + + def capture(self) -> memoryview: + """Capture a frame and return it as a memoryview.""" + ... + + def free_buffer(self) -> None: + """Free the frame buffer.""" + ... + + def get_pixel_width(self) -> int: + """Get frame width in pixels.""" + ... + + def get_pixel_height(self) -> int: + """Get frame height in pixels.""" + ... + + def get_pixel_format(self) -> int: + """Get current pixel format.""" + ... + + def get_sensor_name(self) -> str: + """Get camera sensor name.""" + ... + + def get_max_frame_size(self) -> int: + """Get maximum supported frame size.""" + ... + + def get_fb_count(self) -> int: + """Get frame buffer count.""" + ... + + def get_grab_mode(self) -> int: + """Get current grab mode.""" + ... + From e2e800a4addf0fbcf34444bac3d65fd96f29d09f Mon Sep 17 00:00:00 2001 From: Christopher Nadler Date: Thu, 14 Aug 2025 12:41:01 +0200 Subject: [PATCH 42/43] Add MICROPY_ROM_TEXT_COMPRESSION as target compile definition and coorect pipeline to include mp_jpeg --- .github/workflows/ESP32.yml | 6 +++--- src/micropython.cmake | 5 +++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ESP32.yml b/.github/workflows/ESP32.yml index fc58d07..eeeaa0c 100644 --- a/.github/workflows/ESP32.yml +++ b/.github/workflows/ESP32.yml @@ -105,7 +105,6 @@ jobs: - ESP32_GENERIC_S2 - ESP32_GENERIC_S3 - ESP32_GENERIC_S3-SPIRAM_OCT - - ESP32_GENERIC_S3-FLASH_4M - ESP32_GENERIC-SPIRAM@WROVER_KIT - ESP32_GENERIC-SPIRAM@ESP_EYE - ESP32_GENERIC-SPIRAM@M5STACK_PSRAM @@ -158,6 +157,7 @@ jobs: - name: Build MicroPython run: | cd ${{ github.workspace }} + cd .. git clone https://github.com/cnadler86/mp_jpeg.git cd ~/esp-idf/components/esp32-camera CAM_DRIVER=$(git describe --tags --always --dirty) @@ -169,9 +169,9 @@ jobs: IFS='-' read -r BOARD_NAME BOARD_VARIANT <<< "${BUILD_TARGET}" if [ -n "${BOARD_VARIANT}" ]; then - IDF_CMD="idf.py -D MICROPY_BOARD=$BOARD_NAME -D USER_C_MODULES=${{ github.workspace }}/src/micropython.cmake -D MICROPY_BOARD_VARIANT=$BOARD_VARIANT -B build-$BUILD_TARGET -D MP_CAMERA_DRIVER_VERSION=$CAM_DRIVER -D MP_JPEG_DIR=${{ github.workspace }}/mp_jpeg" + IDF_CMD="idf.py -D MICROPY_BOARD=$BOARD_NAME -D USER_C_MODULES=${{ github.workspace }}/src/micropython.cmake -D MICROPY_BOARD_VARIANT=$BOARD_VARIANT -B build-$BUILD_TARGET -D MP_CAMERA_DRIVER_VERSION=$CAM_DRIVER" else - IDF_CMD="idf.py -D MICROPY_BOARD=$BOARD_NAME -D USER_C_MODULES=${{ github.workspace }}/src/micropython.cmake -B build-$BUILD_TARGET -D MP_CAMERA_DRIVER_VERSION=$CAM_DRIVER -D MP_JPEG_DIR=${{ github.workspace }}/mp_jpeg" + IDF_CMD="idf.py -D MICROPY_BOARD=$BOARD_NAME -D USER_C_MODULES=${{ github.workspace }}/src/micropython.cmake -B build-$BUILD_TARGET -D MP_CAMERA_DRIVER_VERSION=$CAM_DRIVER" fi if [ -n "${CAMERA_MODEL}" ]; then echo "FW_NAME=${CAMERA_MODEL}" >> $GITHUB_ENV diff --git a/src/micropython.cmake b/src/micropython.cmake index d08205b..178d39f 100644 --- a/src/micropython.cmake +++ b/src/micropython.cmake @@ -81,6 +81,11 @@ if (MP_CAMERA_DRIVER_VERSION) ) endif() +# Camera module strings are not suitable for compression and cause size increase +target_compile_definitions(usermod_mp_camera INTERFACE + MICROPY_ROM_TEXT_COMPRESSION=0 +) + # Link the camera module with the main usermod target target_link_libraries(usermod INTERFACE usermod_mp_camera) From ab114facc4d674b675c5a44e3a010ef987a51afa Mon Sep 17 00:00:00 2001 From: Christopher Nadler <147471517+cnadler86@users.noreply.github.com> Date: Thu, 14 Aug 2025 21:22:27 +0200 Subject: [PATCH 43/43] ESP32.yml aktualisieren --- .github/workflows/ESP32.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ESP32.yml b/.github/workflows/ESP32.yml index eeeaa0c..3ad8407 100644 --- a/.github/workflows/ESP32.yml +++ b/.github/workflows/ESP32.yml @@ -59,7 +59,7 @@ jobs: if: steps.cache_esp_idf.outputs.cache-hit != 'true' run: | cd ~ - git clone --depth 1 --branch v5.4 https://github.com/espressif/esp-idf.git + git clone --depth 1 --branch v5.4.2 https://github.com/espressif/esp-idf.git # git clone --depth 1 --branch ${{ env.IDF_VER }} https://github.com/espressif/esp-idf.git git -C esp-idf submodule update --init --recursive --filter=tree:0 cd esp-idf