From 33efbdd3e3ef8642bb8d8b2bb3b7d4b2d3071deb Mon Sep 17 00:00:00 2001 From: Christopher Nadler <147471517+cnadler86@users.noreply.github.com> Date: Wed, 6 Nov 2024 14:37:35 +0100 Subject: [PATCH] Develop into master (#11) * Changes: Quality definitions to standard jpeg quality * Added image converters * Added a lot of supported boards * Many bug-fixes * More examples * Additional improvements --- .github/workflows/Create-Release.yml | 32 +++ .github/workflows/ESP32.yml | 61 ++++- README.md | 214 +++++++++++++--- examples/CameraSettings.html | 316 +++++++++++++++++++++++ examples/CameraSettings.py | 109 ++++++++ examples/{WebCam.py => SimpleWebCam.py} | 2 +- examples/benchmark.py | 108 ++++++++ src/camera_pins.h | 321 ++++++++++++++++++++++++ src/micropython.cmake | 15 +- src/micropython.mk | 1 + src/modcamera.c | 253 ++++++++++++++----- src/modcamera.h | 40 +-- src/modcamera_api.c | 57 +++-- tests/esp32_test.py | 26 +- 14 files changed, 1397 insertions(+), 158 deletions(-) create mode 100644 .github/workflows/Create-Release.yml create mode 100644 examples/CameraSettings.html create mode 100644 examples/CameraSettings.py rename examples/{WebCam.py => SimpleWebCam.py} (99%) create mode 100644 examples/benchmark.py create mode 100644 src/camera_pins.h diff --git a/.github/workflows/Create-Release.yml b/.github/workflows/Create-Release.yml new file mode 100644 index 0000000..4b8dccf --- /dev/null +++ b/.github/workflows/Create-Release.yml @@ -0,0 +1,32 @@ +name: Add-Artifacts-to-Draft-Release + +on: + release: + types: + - created + +jobs: + add_artifacts: + if: ${{ github.event.release.draft }} + 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: Download artifacts + uses: actions/download-artifact@v4 + with: + name: firmware-* + path: ./artifacts + + - name: Upload Release Assets + run: | + for file in ./artifacts/*; do + gh release upload "${{ github.event.release.tag_name }}" "$file" --clobber + done diff --git a/.github/workflows/ESP32.yml b/.github/workflows/ESP32.yml index ce5a7d1..9548001 100644 --- a/.github/workflows/ESP32.yml +++ b/.github/workflows/ESP32.yml @@ -5,9 +5,13 @@ on: push: paths: - 'src/**' + - '.github/workflows/*.yml' pull_request: branches: - master + paths: + - 'src/**' + - '.github/workflows/*.yml' concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -68,13 +72,13 @@ 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 ${{ env.IDF_VER }} https://github.com/espressif/esp-idf.git + # git clone --depth 1 --branch release/v5.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 ./install.sh all cd components - git clone https://github.com/espressif/esp32-camera + git clone https://github.com/cnadler86/esp32-camera cd ~/esp-idf/ source ./export.sh @@ -85,12 +89,30 @@ jobs: strategy: fail-fast: false matrix: - board: + board: - ESP32_GENERIC-SPIRAM - 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 + - ESP32_GENERIC-SPIRAM@M5STACK_V2_PSRAM + - ESP32_GENERIC-SPIRAM@M5STACK_WIDE + - ESP32_GENERIC-SPIRAM@M5STACK_ESP32CAM + - ESP32_GENERIC-SPIRAM@M5STACK_UNITCAM + - 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@XIAO_ESP32S3 + - ESP32_GENERIC_S3-SPIRAM_OCT@ESP32S3_CAM_LCD + - ESP32_GENERIC_S3-SPIRAM_OCT@ESP32S3_EYE + - ESP32_GENERIC_S3-SPIRAM_OCT@FREENOVE_ESP32S3_CAM + - ESP32_GENERIC_S3-SPIRAM_OCT@DFRobot_ESP32S3 + - ESP32_GENERIC_S3-SPIRAM_OCT@NEW_ESPS3_RE1_0 + - ESP32_GENERIC_S3-SPIRAM_OCT@XENOIONEX steps: # Get the latest MicroPython release @@ -126,18 +148,33 @@ jobs: cd ~/micropython/ports/esp32 source ~/esp-idf/export.sh - # Check if a variant is defined and adjust the make command - IFS='-' read -r BOARD_NAME BOARD_VARIANT <<< "${{ matrix.board }}" + # Check if a variant is defined and adjust the idf.py command + IFS='@' read -r BUILD_TARGET CAMERA_MODEL <<< "${{ matrix.board }}" + IFS='-' read -r BOARD_NAME BOARD_VARIANT <<< "${BUILD_TARGET}" + if [ -n "${BOARD_VARIANT}" ]; then - make USER_C_MODULES=${{ github.workspace }}/src/micropython.cmake BOARD=$BOARD_NAME BOARD_VARIANT=$BOARD_VARIANT all + 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" + else + IDF_CMD="idf.py -D MICROPY_BOARD=$BOARD_NAME -D USER_C_MODULES=${{ github.workspace }}/src/micropython.cmake -B build-$BUILD_TARGET" + fi + if [ -n "${CAMERA_MODEL}" ]; then + echo "FW_NAME=${CAMERA_MODEL}" >> $GITHUB_ENV + FINAL_CMD="${IDF_CMD} -D MICROPY_CAMERA_MODEL=${CAMERA_MODEL} build" else - make USER_C_MODULES=${{ github.workspace }}/src/micropython.cmake BOARD=$BOARD_NAME all + echo "FW_NAME=${BUILD_TARGET}" >> $GITHUB_ENV + FINAL_CMD="${IDF_CMD} build" fi - mv ~/micropython/ports/esp32/build-${{ matrix.board }}/firmware.bin ~/${{ matrix.board }}.bin + make USER_C_MODULES=${{ github.workspace }}/src/micropython.cmake BOARD=$BOARD_NAME submodules + echo "Running command: $FINAL_CMD" + eval $FINAL_CMD + cd ~/micropython/ports/esp32/build-${BUILD_TARGET} + python ../makeimg.py sdkconfig bootloader/bootloader.bin partition_table/partition-table.bin micropython.bin firmware.bin micropython.uf2 + mkdir -p ~/artifacts + mv ~/micropython/ports/esp32/build-${BUILD_TARGET}/firmware.bin ~/artifacts/firmware.bin - name: Upload firmware artifact uses: actions/upload-artifact@v4 with: - name: firmware-${{ matrix.board }} - path: ~/${{ matrix.board }}.bin - retention-days: 90 \ No newline at end of file + name: firmware-${{ env.FW_NAME }} + path: ~/artifacts/** + retention-days: 5 \ No newline at end of file diff --git a/README.md b/README.md index acb2778..afba7ee 100644 --- a/README.md +++ b/README.md @@ -2,22 +2,45 @@ [![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 cameras in different ports in micropython, 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 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). 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 anounce. +The API is stable, but it might change without previous announce. ## Precomiled FW (the easy way) -If you are not familiar with building a custom firmware, you can go to the [releases](https://github.com/cnadler86/micropython-camera-API/releases) page and download one of the generic FWs that suits your board. +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 +### Importing the Camera Module + ```python from camera 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. + +```python +cam = Camera() +``` + +or with relevant keyword arguments: + +```python +cam = Camera(pixel_format=PixelFormat.JPEG, + frame_size=FrameSize.QVGA, + jpeg_quality=90, + fb_count=2, + grab_mode=GrabMode.WHEN_EMPTY) +``` + +When using a **generic** precompiled firmware, the camera constructor requires specific keyword arguments (namely the camera pins to be used). +These pins are just examples and if used as-is, a error will occur. Adapt them to your board! -# Camera construction and initialization -# These pins are just examples and if you use them just like that will get a watchdog error. Adapt them to your board! -camera = Camera( +```python +cam = Camera( data_pins=[1,2,3,4,5,6,7,8], vsync_pin=9, href_pin=10, @@ -28,48 +51,143 @@ camera = Camera( xclk_freq=20000000, powerdown_pin=-1, reset_pin=-1, - pixel_format=PixelFormat.RGB565, - frame_size=FrameSize.QVGA, - jpeg_quality=15, - fb_count=1, - grab_mode=GrabMode.WHEN_EMPTY ) +``` + +**Keyword arguments for construction:** + +- data_pins: List of data pins +- pclk_pin: Pixel clock pin + -vsync_pin: VSYNC pin +- 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 (default: -1, meaning not used) +- reset_pin: Reset pin (default: -1, meaning 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 capture 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 +- 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) +- fb_count: + - 2 for ESP32S3 boards + - 1 for all other +- grab_mode: + - LATEST for ESP32S3 boards + - WHEN_EMPTY for all other + +### Initializing the Camera + +```python +cam.init() +``` + +### Capture image -#Camera construction using defaults (if you specified them in mpconfigboard.h) -camera = Camera() +```python +img = cam.capture() +``` + +Keyword arguments for capture + +- out_format: Output format as PixelFormat (optional) -# Capture image -img = camera.capture() +### Camera reconfiguration -# Camera reconfiguration -camera.reconfigure(pixel_format=PixelFormat.JPEG,frame_size=FrameSize.QVGA,grab_mode=GrabMode.LATEST, fb_count=2) -camera.set_quality(10) +```python +cam.reconfigure(pixel_format=PixelFormat.JPEG,frame_size=FrameSize.QVGA,grab_mode=GrabMode.LATEST, fb_count=2) ``` -You can get and set sensor properties by the respective methods (e.g. camera.get_brightness() or camera.set_vflip(True). See autocompletitions in Thonny in order to see the list of methods. +Keyword arguments for reconfigure + +- frame_size: Frame size as FrameSize (optional) +- pixel_format: Pixel format as PixelFormat(optional) +- grab_mode: Grab mode as GrabMode (optional) +- fb_count: Frame buffer count (optional) + +### Additional methods + +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). -Notice that for the methods in here you need to prefix a get/set, depending on what you want to do. +Note that each method requires a "get_" or "set_" prefix, depending on the desired action. ## Build your custom FW -### Setup build environment (the DIY way) +### Setting up the build environment (DIY method) -To build the project, follow the following instructions: +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". I used the actual micropython master branch (between v1.23 and before 1.24). -- You will have to add the ESP32-Camera driver (I used v2.0.12). To do this, add the following to the respective idf_component.yml file (e.g. in micropython/ports/esp32/main_esp32s3/idf_component.yml): +- 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): ```yml espressif/esp32-camera: - git: https://github.com/espressif/esp32-camera + git: https://github.com/cnadler86/esp32-camera #At the moment I maintain a fork because of some unsolved bugs and conveniance. +``` + +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). +Example (don't forget to add the empty line at the bottom): + +```c +#define MICROPY_CAMERA_MODEL_WROVER_KIT 1 + ``` -Alternatively, you can clone the repository inside the esp-idf/components folder instead of altering the idf_component.yml file. +Below is a list of supported `MICROPY_CAMERA_MODEL_xxx` definitions: -### Add camera configurations to your board (Optional, but recommended) +- MICROPY_CAMERA_MODEL_WROVER_KIT - [ESP32-WROVER-KIT](https://www.espressif.com/en/products/devkits/esp32-wrover-kit/overview) +- MICROPY_CAMERA_MODEL_ESP_EYE - [ESP-EYE](https://www.espressif.com/en/products/devkits/esp-eye/overview) +- MICROPY_CAMERA_MODEL_M5STACK_PSRAM - [M5Stack PSRAM](https://shop.m5stack.com/collections/m5-cameras) +- MICROPY_CAMERA_MODEL_M5STACK_UNITCAM - [M5Stack UnitCam](https://shop.m5stack.com/collections/m5-cameras) +- MICROPY_CAMERA_MODEL_M5STACK_V2_PSRAM - [M5Stack V2 PSRAM](https://shop.m5stack.com/collections/m5-cameras) +- 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_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] +- MICROPY_CAMERA_MODEL_ESP32S3_CAM_LCD - [ESP32-S3 CAM LCD] +- MICROPY_CAMERA_MODEL_ESP32S3_EYE - [ESP32-S3 EYE](https://www.espressif.com/en/products/devkits/esp32-s3-eye/overview) +- MICROPY_CAMERA_MODEL_FREENOVE_ESP32S3_CAM - [Freenove ESP32-S3 CAM](https://store.freenove.com/products/fnk0085) +- MICROPY_CAMERA_MODEL_DFRobot_ESP32S3 - [DFRobot ESP32-S3](https://www.dfrobot.com/) +- MICROPY_CAMERA_MODEL_TTGO_T_JOURNAL - [TTGO T-Journal](https://www.lilygo.cc/products/) +- MICROPY_CAMERA_MODEL_TTGO_T_CAMERA_PLUS - [TTGO T-Camera Plus](https://www.lilygo.cc/products/) +- MICROPY_CAMERA_MODEL_NEW_ESPS3_RE1_0 - [New ESP32-S3 RE:1.0] +- MICROPY_CAMERA_MODEL_XENOIONEX - [Xenoionex] -To make things easier, 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. +#### 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: @@ -91,8 +209,8 @@ Example for Xiao sense: #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) // Usually the value is between 1 (slow) and 2 (fast, but more load on CPU) -#define MICROPY_CAMERA_JPEG_QUALITY (10) // Quality of JPEG output. 0-63 lower means higher quality. Definition will change in the future +#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) ``` @@ -110,13 +228,39 @@ make USER_C_MODULES=../../../../micropython-camera-API/src/micropython.cmake BOA ``` If you experience problems, visit [MicroPython external C modules](https://docs.micropython.org/en/latest/develop/cmodules.html). + ## Notes +- For ESP32, do not use sizes above QVGA when not JPEG. The performance of the ESP32-S series has significantly improved, but JPEG mode always gives better frame rates. - The OV5640 pinout is compatible with boards designed for the OV2640 but the voltage supply is too high for the internal 1.5V regulator, so the camera overheats unless a heat sink is applied. For recording purposes the OV5640 should only be used with an ESP32S3 board. Frame sizes above FHD framesize should only be used for still images due to memory limitations. - 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. + +## 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 | + +## Future Plans -## Plans for the future -- [ ] imolrment structure in repo to include other boards like xiao sense -- [ ] harmonize properties to standard ones at API level, e.g. jpeg quality to the range 100=very good, 1/0= very bad -- [ ] edge case: enable usage of pins such as i2c for other applications -- [ ] provide examples in binary image with lfs-merge \ No newline at end of file +- 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.html b/examples/CameraSettings.html new file mode 100644 index 0000000..27e7447 --- /dev/null +++ b/examples/CameraSettings.html @@ -0,0 +1,316 @@ + + + + Micropython Camera Stream + + + + +
+

Micropython Camera Stream

+
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ + +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+ + +
+
+
+ Loading stream... +
+
+ + \ No newline at end of file diff --git a/examples/CameraSettings.py b/examples/CameraSettings.py new file mode 100644 index 0000000..26512c5 --- /dev/null +++ b/examples/CameraSettings.py @@ -0,0 +1,109 @@ +import network +import asyncio +import time +from camera import Camera, FrameSize, PixelFormat + +cam = Camera(frame_size=FrameSize.VGA, pixel_format=PixelFormat.JPEG, jpeg_quality=85, init=False) +# WLAN config +ssid = '' +password = '' + +station = network.WLAN(network.STA_IF) +station.active(True) +station.connect(ssid, password) + +while not station.isconnected(): + time.sleep(1) + +print(f'Connected! IP: {station.ifconfig()[0]}. Open this IP in your browser') + +try: + with open("CameraSettings.html", 'r') as file: + html = file.read() +except Exception as e: + print("Error reading CameraSettings.html file. You might forgot to copy it from the examples folder.") + raise e +async def stream_camera(writer): + try: + cam.init() + if not cam.get_bmp_out() and cam.get_pixel_format() != PixelFormat.JPEG: + cam.set_bmp_out(True) + + writer.write(b'HTTP/1.1 200 OK\r\nContent-Type: multipart/x-mixed-replace; boundary=frame\r\n\r\n') + await writer.drain() + + while True: + frame = cam.capture() + if frame: + if cam.get_pixel_format() == PixelFormat.JPEG: + writer.write(b'--frame\r\nContent-Type: image/jpeg\r\n\r\n') + else: + writer.write(b'--frame\r\nContent-Type: image/bmp\r\n\r\n') + writer.write(frame) + await writer.drain() + + finally: + cam.deinit() + writer.close() + await writer.wait_closed() + print("Streaming stopped and camera deinitialized.") + +async def handle_client(reader, writer): + try: + request = await reader.read(1024) + request = request.decode() + + if 'GET /stream' in request: + print("Start streaming...") + await stream_camera(writer) + + elif 'GET /set_' in request: + method_name = request.split('GET /set_')[1].split('?')[0] + value = int(request.split('value=')[1].split(' ')[0]) + set_method = getattr(cam, f'set_{method_name}', None) + if callable(set_method): + print(f"setting {method_name} to {value}") + set_method(value) + response = 'HTTP/1.1 200 OK\r\n\r\n' + writer.write(response.encode()) + await writer.drain() + else: + response = 'HTTP/1.1 404 Not Found\r\n\r\n' + writer.write(response.encode()) + await writer.drain() + + elif 'GET /get_' in request: + method_name = request.split('GET /get_')[1].split(' ')[0] + get_method = getattr(cam, f'get_{method_name}', None) + if callable(get_method): + value = get_method() + print(f"{method_name} is {value}") + response = f'HTTP/1.1 200 OK\r\n\r\n{value}' + writer.write(response.encode()) + await writer.drain() + else: + response = 'HTTP/1.1 404 Not Found\r\n\r\n' + writer.write(response.encode()) + await writer.drain() + + else: + writer.write('HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n'.encode() + html.encode()) + await writer.drain() + except Exception as e: + print(f"Error: {e}") + finally: + writer.close() + await writer.wait_closed() + +async def start_server(): + server = await asyncio.start_server(handle_client, "0.0.0.0", 80) + print(f'Server is running on {station.ifconfig()[0]}:80') + while True: + await asyncio.sleep(3600) + +try: + asyncio.run(start_server()) +except KeyboardInterrupt: + cam.deinit() + print("Server stopped") + diff --git a/examples/WebCam.py b/examples/SimpleWebCam.py similarity index 99% rename from examples/WebCam.py rename to examples/SimpleWebCam.py index bf78488..b4f5180 100644 --- a/examples/WebCam.py +++ b/examples/SimpleWebCam.py @@ -4,7 +4,7 @@ from camera import Camera, FrameSize, PixelFormat # Cam Config -cam = Camera(frame_size = FrameSize.VGA,pixel_format=PixelFormat.JPEG) +cam = Camera(frame_size = FrameSize.VGA,pixel_format=PixelFormat.JPEG,init=False) # WLAN config ssid = '' diff --git a/examples/benchmark.py b/examples/benchmark.py new file mode 100644 index 0000000..806a204 --- /dev/null +++ b/examples/benchmark.py @@ -0,0 +1,108 @@ +from camera import Camera, FrameSize, PixelFormat +import time +import gc +import os +gc.enable() + +def measure_fps(duration=2): + start_time = time.time() + while time.time() - start_time < 0.5: + cam.capture() + + start_time = time.time() + frame_count = 0 + + while time.time() - start_time < duration: + cam.capture() + frame_count += 1 + + end_time = time.time() + fps = frame_count / (end_time - start_time) + return fps + +def print_summary_table(results, cam): + 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() + results = {} + + try: + for fb in [1, 2]: + cam.reconfigure(fb_count=fb) + results[fb] = {} + for p in dir(PixelFormat): + if not p.startswith('_'): + p_value = getattr(PixelFormat, p) + if (p_value == PixelFormat.RGB888 and cam.get_sensor_name() == "OV2640") or (p_value != PixelFormat.JPEG and fb > 1): + continue + try: + cam.reconfigure(pixel_format=p_value) + results[fb][p] = {} + except Exception as e: + print('ERR:', e) + continue + + for f in dir(FrameSize): + if not f.startswith('_'): + f_value = getattr(FrameSize, f) + if f_value > cam.get_max_frame_size(): + continue + gc.collect() + print('Set', p, f,f'fb={fb}',':') + + try: + cam.reconfigure(frame_size=f_value) + time.sleep_ms(10) + img = cam.capture() + + if img: + print('---> Image size:', len(img)) + fps = measure_fps(2) + print(f"---> FPS: {fps}") + results[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/camera_pins.h b/src/camera_pins.h new file mode 100644 index 0000000..203f7f1 --- /dev/null +++ b/src/camera_pins.h @@ -0,0 +1,321 @@ +// Camera pins definitions for different boards +#ifndef MICROPY_CAMERA_MODEL_PINS_H +#define MICROPY_CAMERA_MODEL_PINS_H + +#if defined(MICROPY_CAMERA_MODEL_WROVER_KIT) +#define MICROPY_CAMERA_PIN_PWDN -1 +#define MICROPY_CAMERA_PIN_RESET -1 +#define MICROPY_CAMERA_PIN_XCLK 21 +#define MICROPY_CAMERA_PIN_SIOD 26 +#define MICROPY_CAMERA_PIN_SIOC 27 + +#define MICROPY_CAMERA_PIN_D7 35 +#define MICROPY_CAMERA_PIN_D6 34 +#define MICROPY_CAMERA_PIN_D5 39 +#define MICROPY_CAMERA_PIN_D4 36 +#define MICROPY_CAMERA_PIN_D3 19 +#define MICROPY_CAMERA_PIN_D2 18 +#define MICROPY_CAMERA_PIN_D1 5 +#define MICROPY_CAMERA_PIN_D0 4 +#define MICROPY_CAMERA_PIN_VSYNC 25 +#define MICROPY_CAMERA_PIN_HREF 23 +#define MICROPY_CAMERA_PIN_PCLK 22 + +#elif defined(MICROPY_CAMERA_MODEL_ESP_EYE) +#define MICROPY_CAMERA_PIN_PWDN -1 +#define MICROPY_CAMERA_PIN_RESET -1 +#define MICROPY_CAMERA_PIN_XCLK 4 +#define MICROPY_CAMERA_PIN_SIOD 18 +#define MICROPY_CAMERA_PIN_SIOC 23 + +#define MICROPY_CAMERA_PIN_D7 36 +#define MICROPY_CAMERA_PIN_D6 37 +#define MICROPY_CAMERA_PIN_D5 38 +#define MICROPY_CAMERA_PIN_D4 39 +#define MICROPY_CAMERA_PIN_D3 35 +#define MICROPY_CAMERA_PIN_D2 14 +#define MICROPY_CAMERA_PIN_D1 13 +#define MICROPY_CAMERA_PIN_D0 34 +#define MICROPY_CAMERA_PIN_VSYNC 5 +#define MICROPY_CAMERA_PIN_HREF 27 +#define MICROPY_CAMERA_PIN_PCLK 25 + +#elif defined(MICROPY_CAMERA_MODEL_M5STACK_PSRAM) || defined(MICROPY_CAMERA_MODEL_M5STACK_UNITCAM) +#define MICROPY_CAMERA_PIN_PWDN -1 +#define MICROPY_CAMERA_PIN_RESET 15 +#define MICROPY_CAMERA_PIN_XCLK 27 +#define MICROPY_CAMERA_PIN_SIOD 25 +#define MICROPY_CAMERA_PIN_SIOC 23 + +#define MICROPY_CAMERA_PIN_D7 19 +#define MICROPY_CAMERA_PIN_D6 36 +#define MICROPY_CAMERA_PIN_D5 18 +#define MICROPY_CAMERA_PIN_D4 39 +#define MICROPY_CAMERA_PIN_D3 5 +#define MICROPY_CAMERA_PIN_D2 34 +#define MICROPY_CAMERA_PIN_D1 35 +#define MICROPY_CAMERA_PIN_D0 32 +#define MICROPY_CAMERA_PIN_VSYNC 22 +#define MICROPY_CAMERA_PIN_HREF 26 +#define MICROPY_CAMERA_PIN_PCLK 21 + +#elif defined(MICROPY_CAMERA_MODEL_M5STACK_V2_PSRAM) || defined(MICROPY_CAMERA_MODEL_M5STACK_WIDE) +#define MICROPY_CAMERA_PIN_PWDN -1 +#define MICROPY_CAMERA_PIN_RESET 15 +#define MICROPY_CAMERA_PIN_XCLK 27 +#define MICROPY_CAMERA_PIN_SIOD 22 +#define MICROPY_CAMERA_PIN_SIOC 23 + +#define MICROPY_CAMERA_PIN_D7 19 +#define MICROPY_CAMERA_PIN_D6 36 +#define MICROPY_CAMERA_PIN_D5 18 +#define MICROPY_CAMERA_PIN_D4 39 +#define MICROPY_CAMERA_PIN_D3 5 +#define MICROPY_CAMERA_PIN_D2 34 +#define MICROPY_CAMERA_PIN_D1 35 +#define MICROPY_CAMERA_PIN_D0 32 +#define MICROPY_CAMERA_PIN_VSYNC 25 +#define MICROPY_CAMERA_PIN_HREF 26 +#define MICROPY_CAMERA_PIN_PCLK 21 + +#elif defined(MICROPY_CAMERA_MODEL_M5STACK_ESP32CAM) +#define MICROPY_CAMERA_PIN_PWDN -1 +#define MICROPY_CAMERA_PIN_RESET 15 +#define MICROPY_CAMERA_PIN_XCLK 27 +#define MICROPY_CAMERA_PIN_SIOD 25 +#define MICROPY_CAMERA_PIN_SIOC 23 + +#define MICROPY_CAMERA_PIN_D7 19 +#define MICROPY_CAMERA_PIN_D6 36 +#define MICROPY_CAMERA_PIN_D5 18 +#define MICROPY_CAMERA_PIN_D4 39 +#define MICROPY_CAMERA_PIN_D3 5 +#define MICROPY_CAMERA_PIN_D2 34 +#define MICROPY_CAMERA_PIN_D1 35 +#define MICROPY_CAMERA_PIN_D0 17 +#define MICROPY_CAMERA_PIN_VSYNC 22 +#define MICROPY_CAMERA_PIN_HREF 26 +#define MICROPY_CAMERA_PIN_PCLK 21 + +#elif defined(MICROPY_CAMERA_MODEL_M5STACK_CAMS3_UNIT) +#define MICROPY_CAMERA_PIN_PWDN -1 +#define MICROPY_CAMERA_PIN_RESET 21 +#define MICROPY_CAMERA_PIN_XCLK 11 +#define MICROPY_CAMERA_PIN_SIOD 17 +#define MICROPY_CAMERA_PIN_SIOC 41 + +#define MICROPY_CAMERA_PIN_D7 13 +#define MICROPY_CAMERA_PIN_D6 4 +#define MICROPY_CAMERA_PIN_D5 10 +#define MICROPY_CAMERA_PIN_D4 5 +#define MICROPY_CAMERA_PIN_D3 7 +#define MICROPY_CAMERA_PIN_D2 16 +#define MICROPY_CAMERA_PIN_D1 15 +#define MICROPY_CAMERA_PIN_D0 6 +#define MICROPY_CAMERA_PIN_VSYNC 42 +#define MICROPY_CAMERA_PIN_HREF 18 +#define MICROPY_CAMERA_PIN_PCLK 12 + +#elif defined(MICROPY_CAMERA_MODEL_AI_THINKER) +#define MICROPY_CAMERA_PIN_PWDN 32 +#define MICROPY_CAMERA_PIN_RESET -1 +#define MICROPY_CAMERA_PIN_XCLK 0 +#define MICROPY_CAMERA_PIN_SIOD 26 +#define MICROPY_CAMERA_PIN_SIOC 27 + +#define MICROPY_CAMERA_PIN_D7 35 +#define MICROPY_CAMERA_PIN_D6 34 +#define MICROPY_CAMERA_PIN_D5 39 +#define MICROPY_CAMERA_PIN_D4 36 +#define MICROPY_CAMERA_PIN_D3 21 +#define MICROPY_CAMERA_PIN_D2 19 +#define MICROPY_CAMERA_PIN_D1 18 +#define MICROPY_CAMERA_PIN_D0 5 +#define MICROPY_CAMERA_PIN_VSYNC 25 +#define MICROPY_CAMERA_PIN_HREF 23 +#define MICROPY_CAMERA_PIN_PCLK 22 + +#elif defined(MICROPY_CAMERA_MODEL_XIAO_ESP32S3) +#define MICROPY_CAMERA_PIN_PWDN -1 +#define MICROPY_CAMERA_PIN_RESET -1 +#define MICROPY_CAMERA_PIN_XCLK 10 +#define MICROPY_CAMERA_PIN_SIOD 40 +#define MICROPY_CAMERA_PIN_SIOC 39 + +#define MICROPY_CAMERA_PIN_D7 48 +#define MICROPY_CAMERA_PIN_D6 11 +#define MICROPY_CAMERA_PIN_D5 12 +#define MICROPY_CAMERA_PIN_D4 14 +#define MICROPY_CAMERA_PIN_D3 16 +#define MICROPY_CAMERA_PIN_D2 18 +#define MICROPY_CAMERA_PIN_D1 17 +#define MICROPY_CAMERA_PIN_D0 15 +#define MICROPY_CAMERA_PIN_VSYNC 38 +#define MICROPY_CAMERA_PIN_HREF 47 +#define MICROPY_CAMERA_PIN_PCLK 13 + +#elif defined(MICROPY_CAMERA_MODEL_ESP32_MP_CAMERA_BOARD) +// The 18 pin header on the board has Y5 and Y3 swapped +#define ESP32_MP_CAMERA_BOARD_HEADER 0 +#define MICROPY_CAMERA_PIN_PWDN 32 +#define MICROPY_CAMERA_PIN_RESET 33 +#define MICROPY_CAMERA_PIN_XCLK 4 +#define MICROPY_CAMERA_PIN_SIOD 18 +#define MICROPY_CAMERA_PIN_SIOC 23 + +#define MICROPY_CAMERA_PIN_D7 36 +#define MICROPY_CAMERA_PIN_D6 19 +#define MICROPY_CAMERA_PIN_D5 21 +#define MICROPY_CAMERA_PIN_D4 39 +#if ESP32_MP_CAMERA_BOARD_HEADER +#define MICROPY_CAMERA_PIN_D3 13 +#else +#define MICROPY_CAMERA_PIN_D3 35 +#endif +#define MICROPY_CAMERA_PIN_D2 14 +#if ESP32_MP_CAMERA_BOARD_HEADER +#define MICROPY_CAMERA_PIN_D1 35 +#else +#define MICROPY_CAMERA_PIN_D1 13 +#endif +#define MICROPY_CAMERA_PIN_D0 34 +#define MICROPY_CAMERA_PIN_VSYNC 5 +#define MICROPY_CAMERA_PIN_HREF 27 +#define MICROPY_CAMERA_PIN_PCLK 25 + +#elif defined(MICROPY_CAMERA_MODEL_ESP32S3_CAM_LCD) +#define MICROPY_CAMERA_PIN_PWDN -1 +#define MICROPY_CAMERA_PIN_RESET -1 +#define MICROPY_CAMERA_PIN_XCLK 40 +#define MICROPY_CAMERA_PIN_SIOD 17 +#define MICROPY_CAMERA_PIN_SIOC 18 + +#define MICROPY_CAMERA_PIN_D7 39 +#define MICROPY_CAMERA_PIN_D6 41 +#define MICROPY_CAMERA_PIN_D5 42 +#define MICROPY_CAMERA_PIN_D4 12 +#define MICROPY_CAMERA_PIN_D3 3 +#define MICROPY_CAMERA_PIN_D2 14 +#define MICROPY_CAMERA_PIN_D1 47 +#define MICROPY_CAMERA_PIN_D0 13 +#define MICROPY_CAMERA_PIN_VSYNC 21 +#define MICROPY_CAMERA_PIN_HREF 38 +#define MICROPY_CAMERA_PIN_PCLK 11 + +#elif defined(MICROPY_CAMERA_MODEL_ESP32S3_EYE) || defined(MICROPY_CAMERA_MODEL_FREENOVE_ESP32S3_CAM) +#define MICROPY_CAMERA_PIN_PWDN -1 +#define MICROPY_CAMERA_PIN_RESET -1 +#define MICROPY_CAMERA_PIN_XCLK 15 +#define MICROPY_CAMERA_PIN_SIOD 4 +#define MICROPY_CAMERA_PIN_SIOC 5 + +#define MICROPY_CAMERA_PIN_D0 11 +#define MICROPY_CAMERA_PIN_D1 9 +#define MICROPY_CAMERA_PIN_D2 8 +#define MICROPY_CAMERA_PIN_D3 10 +#define MICROPY_CAMERA_PIN_D4 12 +#define MICROPY_CAMERA_PIN_D5 18 +#define MICROPY_CAMERA_PIN_D6 17 +#define MICROPY_CAMERA_PIN_D7 16 +#define MICROPY_CAMERA_PIN_VSYNC 6 +#define MICROPY_CAMERA_PIN_HREF 7 +#define MICROPY_CAMERA_PIN_PCLK 13 + +#elif defined(MICROPY_CAMERA_MODEL_DFRobot_ESP32S3) +#define MICROPY_CAMERA_PIN_PWDN -1 +#define MICROPY_CAMERA_PIN_RESET -1 +#define MICROPY_CAMERA_PIN_XCLK 45 +#define MICROPY_CAMERA_PIN_SIOD 1 +#define MICROPY_CAMERA_PIN_SIOC 2 + +#define MICROPY_CAMERA_PIN_D7 48 +#define MICROPY_CAMERA_PIN_D6 46 +#define MICROPY_CAMERA_PIN_D5 8 +#define MICROPY_CAMERA_PIN_D4 7 +#define MICROPY_CAMERA_PIN_D3 4 +#define MICROPY_CAMERA_PIN_D2 41 +#define MICROPY_CAMERA_PIN_D1 40 +#define MICROPY_CAMERA_PIN_D0 39 +#define MICROPY_CAMERA_PIN_VSYNC 6 +#define MICROPY_CAMERA_PIN_HREF 42 +#define MICROPY_CAMERA_PIN_PCLK 5 + +#elif defined(MICROPY_CAMERA_MODEL_TTGO_T_JOURNAL) +#define MICROPY_CAMERA_PIN_PWDN 0 +#define MICROPY_CAMERA_PIN_RESET 15 +#define MICROPY_CAMERA_PIN_XCLK 27 +#define MICROPY_CAMERA_PIN_SIOD 25 +#define MICROPY_CAMERA_PIN_SIOC 23 + +#define MICROPY_CAMERA_PIN_D7 19 +#define MICROPY_CAMERA_PIN_D6 36 +#define MICROPY_CAMERA_PIN_D5 18 +#define MICROPY_CAMERA_PIN_D4 39 +#define MICROPY_CAMERA_PIN_D3 5 +#define MICROPY_CAMERA_PIN_D2 34 +#define MICROPY_CAMERA_PIN_D1 35 +#define MICROPY_CAMERA_PIN_D0 17 +#define MICROPY_CAMERA_PIN_VSYNC 22 +#define MICROPY_CAMERA_PIN_HREF 26 +#define MICROPY_CAMERA_PIN_PCLK 21 + +#elif defined(MICROPY_CAMERA_MODEL_TTGO_T_CAMERA_PLUS) +#define MICROPY_CAMERA_PIN_PWDN -1 +#define MICROPY_CAMERA_PIN_RESET -1 +#define MICROPY_CAMERA_PIN_XCLK 4 +#define MICROPY_CAMERA_PIN_SIOD 18 +#define MICROPY_CAMERA_PIN_SIOC 23 + +#define MICROPY_CAMERA_PIN_D7 36 +#define MICROPY_CAMERA_PIN_D6 37 +#define MICROPY_CAMERA_PIN_D5 38 +#define MICROPY_CAMERA_PIN_D4 39 +#define MICROPY_CAMERA_PIN_D3 35 +#define MICROPY_CAMERA_PIN_D2 26 +#define MICROPY_CAMERA_PIN_D1 13 +#define MICROPY_CAMERA_PIN_D0 34 +#define MICROPY_CAMERA_PIN_VSYNC 5 +#define MICROPY_CAMERA_PIN_HREF 27 +#define MICROPY_CAMERA_PIN_PCLK 25 + +#elif defined(MICROPY_CAMERA_MODEL_NEW_ESPS3_RE1_0) +// aliexpress board with label RE:1.0, uses slow 8MB QSPI PSRAM, only 4MB addressable +#define MICROPY_CAMERA_PIN_PWDN -1 +#define MICROPY_CAMERA_PIN_RESET -1 +#define MICROPY_CAMERA_PIN_XCLK 10 +#define MICROPY_CAMERA_PIN_SIOD 21 +#define MICROPY_CAMERA_PIN_SIOC 14 + +#define MICROPY_CAMERA_PIN_D7 11 +#define MICROPY_CAMERA_PIN_D6 9 +#define MICROPY_CAMERA_PIN_D5 8 +#define MICROPY_CAMERA_PIN_D4 6 +#define MICROPY_CAMERA_PIN_D3 4 +#define MICROPY_CAMERA_PIN_D2 2 +#define MICROPY_CAMERA_PIN_D1 3 +#define MICROPY_CAMERA_PIN_D0 5 +#define MICROPY_CAMERA_PIN_VSYNC 13 +#define MICROPY_CAMERA_PIN_HREF 12 +#define MICROPY_CAMERA_PIN_PCLK 7 + +#elif defined(MICROPY_CAMERA_MODEL_XENOIONEX) +#define MICROPY_CAMERA_PIN_PWDN -1 +#define MICROPY_CAMERA_PIN_RESET -1 +#define MICROPY_CAMERA_PIN_XCLK 1 // Can use +#define MICROPY_CAMERA_PIN_SIOD 8 // Can use other i2c SDA pin, set this to -1 | If not using i2c set to 8 or 47 +#define MICROPY_CAMERA_PIN_SIOC 9 // Can use other i2c SCL pin, set this to -1 | If not using i2c set to 9 or 21 + +#define MICROPY_CAMERA_PIN_D7 3 //D7 +#define MICROPY_CAMERA_PIN_D6 18 //D6 +#define MICROPY_CAMERA_PIN_D5 42 //D5 +#define MICROPY_CAMERA_PIN_D4 16 //D4 +#define MICROPY_CAMERA_PIN_D3 41 //D3 +#define MICROPY_CAMERA_PIN_D2 17 //D2 +#define MICROPY_CAMERA_PIN_D1 40 //D1 +#define MICROPY_CAMERA_PIN_D0 39 //D0 +#define MICROPY_CAMERA_PIN_VSYNC 45 +#define MICROPY_CAMERA_PIN_HREF 38 +#define MICROPY_CAMERA_PIN_PCLK 2 + +#endif // definition of camera pins for different boards +#endif // MICROPY_CAMERA_MODEL_PINS_H \ No newline at end of file diff --git a/src/micropython.cmake b/src/micropython.cmake index 9527545..68c68c8 100644 --- a/src/micropython.cmake +++ b/src/micropython.cmake @@ -1,8 +1,13 @@ +# `py.cmake` for `micropy_gather_target_properties` macro usage +include(${MICROPY_DIR}/py/py.cmake) + add_library(usermod_mp_camera INTERFACE) + target_sources(usermod_mp_camera INTERFACE ${CMAKE_CURRENT_LIST_DIR}/modcamera.c ${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 @@ -11,5 +16,11 @@ target_include_directories(usermod_mp_camera INTERFACE ${IDF_PATH}/components/esp32-camera/conversions/private_include ${IDF_PATH}/components/esp32-camera/sensors/private_include ) -target_compile_definitions(usermod_mp_camera INTERFACE) -target_link_libraries(usermod INTERFACE usermod_mp_camera) \ No newline at end of file + +if (MICROPY_CAMERA_MODEL) + target_compile_definitions(usermod_mp_camera INTERFACE MICROPY_CAMERA_MODEL_${MICROPY_CAMERA_MODEL}=1) +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/micropython.mk b/src/micropython.mk index 749223e..7d5012b 100644 --- a/src/micropython.mk +++ b/src/micropython.mk @@ -1,3 +1,4 @@ CAMERA_MOD_DIR := $(USERMOD_DIR) SRC_USERMOD_C += $(addprefix $(CAMERA_MOD_DIR)/, modcamera_api.c) SRC_USERMOD_LIB_C += $(addprefix $(CAMERA_MOD_DIR)/, modcamera.c) +CFLAGS_USERMOD += -I$(CAMERA_MOD_DIR) \ No newline at end of file diff --git a/src/modcamera.c b/src/modcamera.c index bd9f731..041554b 100644 --- a/src/modcamera.c +++ b/src/modcamera.c @@ -28,6 +28,7 @@ #include "modcamera.h" #include "esp_err.h" #include "esp_log.h" +#include "img_converters.h" #define TAG "ESP32_MPY_CAMERA" @@ -35,35 +36,37 @@ #error Camera only works on boards configured with spiram #endif -void raise_micropython_error_from_esp_err(esp_err_t err) { +// #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")); @@ -71,6 +74,18 @@ void raise_micropython_error_from_esp_err(esp_err_t err) { } } +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")); + } + return (int)((int32_t)(value - fromLow) * (toHigh - toLow) / (fromHigh - fromLow) + toLow); +} + +static inline int get_mapped_jpeg_quality(int8_t quality) { + return map(quality, 0, 100, 63, 0); +} + +// Camera HAL Funcitons void mp_camera_hal_construct( mp_camera_obj_t *self, int8_t data_pins[8], @@ -91,7 +106,8 @@ void mp_camera_hal_construct( // configure camera based on arguments self->camera_config.pixel_format = pixel_format; self->camera_config.frame_size = frame_size; - self->camera_config.jpeg_quality = jpeg_quality; //0-63 lower number means higher quality. TODO: Harmonization in API and Validation + // 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]; @@ -108,8 +124,22 @@ 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; - self->camera_config.xclk_freq_hz = xclk_freq_hz; - self->camera_config.fb_count = fb_count; //if more than one, i2s runs in continuous mode. TODO: Test with others than JPEG + 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; // defaul parameters @@ -125,26 +155,31 @@ void mp_camera_hal_init(mp_camera_obj_t *self) { if (self->initialized) { return; } + #ifndef CONFIG_IDF_TARGET_ESP32S3 + if (self->camera_config.fb_count > 1 && self->camera_config.pixel_format != PIXFORMAT_JPEG) { + mp_warning(NULL, "It is recomended to use a frame buffer size of 1 for non-JPEG pixel format"); + } + #endif ESP_LOGI(TAG, "Initializing camera"); - camera_config_t temp_config = self->camera_config; - temp_config.frame_size = FRAMESIZE_QVGA; //use values supported by all cameras - temp_config.pixel_format = PIXFORMAT_RGB565; //use values supported by all cameras - esp_err_t err = esp_camera_init(&temp_config); + // 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 { - self->initialized = true; + return; } - mp_camera_hal_reconfigure(self, self->camera_config.frame_size, self->camera_config.pixel_format, - self->camera_config.grab_mode, self->camera_config.fb_count); + self->initialized = true; ESP_LOGI(TAG, "Camera initialized successfully"); + return; } void mp_camera_hal_deinit(mp_camera_obj_t *self) { if (self->initialized) { if (self->captured_buffer) { - esp_camera_fb_return(self->captured_buffer); + esp_camera_return_all(); self->captured_buffer = NULL; } esp_err_t err = esp_camera_deinit(); @@ -186,16 +221,22 @@ void mp_camera_hal_reconfigure(mp_camera_obj_t *self, mp_camera_framesize_t fram if (fb_count > 2) { self->camera_config.fb_count = 2; mp_warning(NULL, "Frame buffer size limited to 2"); - } else { + } 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()); - // sensor->set_pixformat(sensor, self->camera_config.pixel_format); //seems to be needed because of some bug? - // sensor->set_framesize(sensor, self->camera_config.frame_size); //seems to be needed because of some bug? - + // 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); @@ -205,8 +246,7 @@ void mp_camera_hal_reconfigure(mp_camera_obj_t *self, mp_camera_framesize_t fram } } -mp_obj_t mp_camera_hal_capture(mp_camera_obj_t *self, int timeout_ms) { - // Timeout not used at the moment +mp_obj_t mp_camera_hal_capture(mp_camera_obj_t *self, int8_t out_format) { if (!self->initialized) { mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Failed to capture image: Camera not initialized")); } @@ -214,28 +254,85 @@ mp_obj_t mp_camera_hal_capture(mp_camera_obj_t *self, int timeout_ms) { esp_camera_fb_return(self->captured_buffer); self->captured_buffer = NULL; } + + static size_t out_len = 0; + static uint8_t *out_buf = NULL; + if (out_len > 0 || out_buf) { + free(out_buf); + out_len = 0; + out_buf = NULL; + } + ESP_LOGI(TAG, "Capturing image"); self->captured_buffer = esp_camera_fb_get(); - if (self->captured_buffer) { - if (self->camera_config.pixel_format == PIXFORMAT_JPEG) { - ESP_LOGI(TAG, "Captured image in JPEG format"); - return mp_obj_new_memoryview('b', self->captured_buffer->len, self->captured_buffer->buf); - } else { - ESP_LOGI(TAG, "Captured image in raw format"); - return mp_obj_new_memoryview('b', self->captured_buffer->len, self->captured_buffer->buf); - // TODO: Stub at the moment in order to return raw data, but it sould be implemented to return a Bitmap, see following circuitpython example: - // - // int width = common_hal_espcamera_camera_get_width(self); - // int height = common_hal_espcamera_camera_get_height(self); - // displayio_bitmap_t *bitmap = m_new_obj(displayio_bitmap_t); - // bitmap->base.type = &displayio_bitmap_type; - // common_hal_displayio_bitmap_construct_from_buffer(bitmap, width, height, (format == PIXFORMAT_RGB565) ? 16 : 8, (uint32_t *)(void *)result->buf, true); - // return bitmap; + if (!self->captured_buffer) { + ESP_LOGE(TAG, "Failed to capture image"); + return mp_const_none; + } + + if (out_format >= 0 && (mp_camera_pixformat_t)out_format != self->camera_config.pixel_format) { + switch ((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: + if(self->camera_config.pixel_format == PIXFORMAT_JPEG){ + if (jpg2rgb565(self->captured_buffer->buf, self->captured_buffer->len, out_buf, JPG_SCALE_NONE)) { + esp_camera_fb_return(self->captured_buffer); + mp_obj_t result = mp_obj_new_memoryview('b', out_len, out_buf); + return result; + } else { + return mp_const_none; + } + } else { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Can only convert JPEG to RGB565")); + return mp_const_none; + } + + default: + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Unsupported pixel format for conversion")); + return mp_const_none; + } + } + + if (self->bmp_out == false) { + ESP_LOGI(TAG, "Returning imgae without conversion"); + return mp_obj_new_memoryview('b', self->captured_buffer->len, self->captured_buffer->buf); } else { - esp_camera_fb_return(self->captured_buffer); - self->captured_buffer = NULL; - return mp_const_none; + ESP_LOGI(TAG, "Returning image as bitmap"); + if (frame2bmp(self->captured_buffer, &out_buf, &out_len)) { + esp_camera_fb_return(self->captured_buffer); + mp_obj_t result = mp_obj_new_memoryview('b', out_len, out_buf); + return result; + } else { + free(out_buf); + out_buf = NULL; + out_len = 0; + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Failed to convert image to BMP")); + return mp_const_none; + } } } @@ -248,6 +345,7 @@ const mp_rom_map_elem_t mp_camera_hal_pixel_format_table[] = { { MP_ROM_QSTR(MP_QSTR_YUV422), MP_ROM_INT(PIXFORMAT_YUV422) }, { MP_ROM_QSTR(MP_QSTR_GRAYSCALE), MP_ROM_INT(PIXFORMAT_GRAYSCALE) }, { MP_ROM_QSTR(MP_QSTR_RGB565), MP_ROM_INT(PIXFORMAT_RGB565) }, + { MP_ROM_QSTR(MP_QSTR_RGB888), MP_ROM_INT(PIXFORMAT_RGB888) }, }; const mp_rom_map_elem_t mp_camera_hal_frame_size_table[] = { @@ -290,14 +388,6 @@ const mp_rom_map_elem_t mp_camera_hal_gainceiling_table[] = { { MP_ROM_QSTR(MP_QSTR_128X), MP_ROM_INT(GAINCEILING_128X) }, }; -// Supporting 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")); - } - return (int)((int32_t)(value - fromLow) * (toHigh - toLow) / (fromHigh - fromLow) + toLow); -} - //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) \ @@ -308,22 +398,19 @@ static int map(int value, int fromLow, int fromHigh, int toLow, int toHigh) { // For subsequent modules using this as example, you will probably only need the makros below. #define SENSOR_GETSET(type, name, field_name, setter_function_name) \ - SENSOR_GET(type, name, field_name, setter_function_name) \ + 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, setter_function_name) \ + 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, getter_function_name) \ +#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")); \ } \ sensor_t *sensor = esp_camera_sensor_get(); \ - if (!sensor->getter_function_name) { \ - mp_raise_ValueError(MP_ERROR_TEXT("No attribute " #name)); \ - } \ return sensor->status_field_name; \ } @@ -358,13 +445,13 @@ static int map(int value, int fromLow, int fromHigh, int toLow, int toHigh) { } \ } +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, denoise, denoise, set_denoise); SENSOR_STATUS_GETSET(mp_camera_gainceiling_t, gainceiling, gainceiling, set_gainceiling); -SENSOR_STATUS_GETSET(int, quality, quality, set_quality); //in_Range not needed since driver limits value SENSOR_STATUS_GETSET(bool, colorbar, colorbar, set_colorbar); SENSOR_STATUS_GETSET(bool, whitebal, awb, set_whitebal); SENSOR_STATUS_GETSET(bool, gain_ctrl, agc, set_gain_ctrl); @@ -384,12 +471,52 @@ SENSOR_STATUS_GETSET(bool, wpc, wpc, set_wpc); SENSOR_STATUS_GETSET(bool, raw_gma, raw_gma, set_raw_gma); SENSOR_STATUS_GETSET(bool, lenc, lenc, set_lenc); -mp_camera_pixformat_t mp_camera_hal_get_pixel_format(mp_camera_obj_t *self) { - return self->camera_config.pixel_format; +void mp_camera_hal_set_frame_size(mp_camera_obj_t * self, framesize_t value) { + if (!self->initialized) { + mp_raise_ValueError(MP_ERROR_TEXT("Camera not initialized")); + } + + sensor_t *sensor = esp_camera_sensor_get(); + if (!sensor->set_framesize) { + mp_raise_ValueError(MP_ERROR_TEXT("No attribute frame_size")); + } + + if (self->captured_buffer) { + esp_camera_return_all(); + self->captured_buffer = NULL; + } + + if (sensor->set_framesize(sensor, value) < 0) { + mp_raise_ValueError(MP_ERROR_TEXT("Invalid setting for frame_size")); + } else { + self->camera_config.frame_size = value; + } +} + +int mp_camera_hal_get_quality(mp_camera_obj_t * self) { + if (!self->initialized) { + mp_raise_ValueError(MP_ERROR_TEXT("Camera not initialized")); + } + return self->camera_config.jpeg_quality; } -mp_camera_framesize_t mp_camera_hal_get_frame_size(mp_camera_obj_t *self) { - return self->camera_config.frame_size; +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")); + } + sensor_t *sensor = esp_camera_sensor_get(); + if (!sensor->set_quality) { + mp_raise_ValueError(MP_ERROR_TEXT("No attribute quality")); + } + if (sensor->set_quality(sensor, get_mapped_jpeg_quality(value)) < 0) { + mp_raise_ValueError(MP_ERROR_TEXT("Invalid setting for quality")); + } else { + self->camera_config.jpeg_quality = value; + } +} + +mp_camera_pixformat_t mp_camera_hal_get_pixel_format(mp_camera_obj_t *self) { + return self->camera_config.pixel_format; } camera_grab_mode_t mp_camera_hal_get_grab_mode(mp_camera_obj_t *self) { diff --git a/src/modcamera.h b/src/modcamera.h index 07b9890..1e1305b 100644 --- a/src/modcamera.h +++ b/src/modcamera.h @@ -42,10 +42,11 @@ #include "esp_camera.h" #include "sensor.h" +#include "camera_pins.h" -#if defined(MICROPY_CAMERA_PIN_SIOD) && defined(MICROPY_CAMERA_PIN_SIOC) && defined(MICROPY_CAMERA_PIN_D0) && defined(MICROPY_CAMERA_PIN_D1) && defined(MICROPY_CAMERA_PIN_D2) && \ -defined(MICROPY_CAMERA_PIN_D3) && defined(MICROPY_CAMERA_PIN_D4) && defined(MICROPY_CAMERA_PIN_D5) && defined(MICROPY_CAMERA_PIN_D6) && defined(MICROPY_CAMERA_PIN_D7) && \ -defined(MICROPY_CAMERA_PIN_PCLK) && defined(MICROPY_CAMERA_PIN_VSYNC) && defined(MICROPY_CAMERA_PIN_HREF) && defined(MICROPY_CAMERA_PIN_XCLK) +#if defined (MICROPY_CAMERA_PIN_SIOD) && defined (MICROPY_CAMERA_PIN_SIOC) && defined (MICROPY_CAMERA_PIN_D0) && defined (MICROPY_CAMERA_PIN_D1) && defined (MICROPY_CAMERA_PIN_D2) && \ +defined (MICROPY_CAMERA_PIN_D3) && defined (MICROPY_CAMERA_PIN_D4) && defined (MICROPY_CAMERA_PIN_D5) && defined (MICROPY_CAMERA_PIN_D6) && defined (MICROPY_CAMERA_PIN_D7) && \ +defined (MICROPY_CAMERA_PIN_PCLK) && defined (MICROPY_CAMERA_PIN_VSYNC) && defined (MICROPY_CAMERA_PIN_HREF) && defined (MICROPY_CAMERA_PIN_XCLK) #define MICROPY_CAMERA_ALL_REQ_PINS_DEFINED (1) #endif @@ -58,27 +59,31 @@ defined(MICROPY_CAMERA_PIN_PCLK) && defined(MICROPY_CAMERA_PIN_VSYNC) && defined #endif #ifndef MICROPY_CAMERA_XCLK_FREQ -#define MICROPY_CAMERA_XCLK_FREQ (10) +#define MICROPY_CAMERA_XCLK_FREQ (20) #endif -#ifndef MICROPY_CAMERA_DEFAULT_FRAME_SIZE -#define MICROPY_CAMERA_DEFAULT_FRAME_SIZE FRAMESIZE_QQVGA +#if !defined (MICROPY_CAMERA_GRAB_MODE) && defined (CONFIG_IDF_TARGET_ESP32S3) +#define MICROPY_CAMERA_GRAB_MODE CAMERA_GRAB_LATEST +#elif !defined(MICROPY_CAMERA_GRAB_MODE) +#define MICROPY_CAMERA_GRAB_MODE CAMERA_GRAB_WHEN_EMPTY #endif -#ifndef MICROPY_CAMERA_DEFAULT_PIXEL_FORMAT -#define MICROPY_CAMERA_DEFAULT_PIXEL_FORMAT PIXFORMAT_RGB565 +#if !defined (MICROPY_CAMERA_FB_COUNT) && defined (CONFIG_IDF_TARGET_ESP32S3) +#define MICROPY_CAMERA_FB_COUNT (2) +#elif !defined(MICROPY_CAMERA_FB_COUNT) +#define MICROPY_CAMERA_FB_COUNT (1) #endif -#ifndef MICROPY_CAMERA_GRAB_MODE -#define MICROPY_CAMERA_GRAB_MODE CAMERA_GRAB_WHEN_EMPTY +#ifndef MICROPY_CAMERA_DEFAULT_FRAME_SIZE +#define MICROPY_CAMERA_DEFAULT_FRAME_SIZE FRAMESIZE_QQVGA #endif -#ifndef MICROPY_CAMERA_FB_COUNT -#define MICROPY_CAMERA_FB_COUNT (1) +#ifndef MICROPY_CAMERA_DEFAULT_PIXEL_FORMAT +#define MICROPY_CAMERA_DEFAULT_PIXEL_FORMAT PIXFORMAT_RGB565 #endif #ifndef MICROPY_CAMERA_JPEG_QUALITY -#define MICROPY_CAMERA_JPEG_QUALITY (15) +#define MICROPY_CAMERA_JPEG_QUALITY (85) #endif //Supported Camera sensors @@ -101,6 +106,7 @@ typedef struct hal_camera_obj { camera_config_t camera_config; bool initialized; camera_fb_t *captured_buffer; + bool bmp_out; } hal_camera_obj_t; #endif // CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 @@ -189,16 +195,16 @@ extern void mp_camera_hal_reconfigure(mp_camera_obj_t *self, mp_camera_framesize * @brief Captures an image and returns it as mp_obj_t (e.g. mp_obj_new_memoryview). * * @param self Pointer to the camera object. - * @param timeout_ms Timeout in milliseconds. + * @param out_format Output pixelformat format. * @return Captured image as micropython object. */ -extern mp_obj_t mp_camera_hal_capture(mp_camera_obj_t *self, int timeout_ms); +extern mp_obj_t mp_camera_hal_capture(mp_camera_obj_t *self, int8_t out_format); /** * @brief Table mapping pixel formats API to their corresponding values at HAL. * @details Needs to be defined in the port-specific implementation. */ -extern const mp_rom_map_elem_t mp_camera_hal_pixel_format_table[4]; +extern const mp_rom_map_elem_t mp_camera_hal_pixel_format_table[5]; /** * @brief Table mapping frame sizes API to their corresponding values at HAL. @@ -262,7 +268,7 @@ DECLARE_CAMERA_HAL_GETSET(bool, wpc) DECLARE_CAMERA_HAL_GET(int, address) DECLARE_CAMERA_HAL_GET(int, fb_count) -DECLARE_CAMERA_HAL_GET(mp_camera_framesize_t, frame_size) +DECLARE_CAMERA_HAL_GETSET(mp_camera_framesize_t, frame_size) DECLARE_CAMERA_HAL_GET(camera_grab_mode_t, grab_mode) DECLARE_CAMERA_HAL_GET(mp_camera_framesize_t, max_frame_size) DECLARE_CAMERA_HAL_GET(mp_camera_pixformat_t, pixel_format) diff --git a/src/modcamera_api.c b/src/modcamera_api.c index 79013db..fd52cec 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, 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, ARG_bmp_out, 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 } }, @@ -67,6 +67,8 @@ static mp_obj_t mp_camera_make_new(const mp_obj_type_t *type, size_t n_args, siz { MP_QSTR_jpeg_quality, MP_ARG_INT | MP_ARG_KW_ONLY, { .u_int = MICROPY_CAMERA_JPEG_QUALITY } }, { 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)]; @@ -119,23 +121,29 @@ static mp_obj_t mp_camera_make_new(const mp_obj_type_t *type, size_t n_args, siz mp_camera_pixformat_t pixel_format = args[ARG_pixel_format].u_int; mp_camera_framesize_t frame_size = args[ARG_frame_size].u_int; int8_t jpeg_quality = args[ARG_jpeg_quality].u_int; + if ((jpeg_quality < 0) || (jpeg_quality > 100)) { + mp_raise_ValueError(MP_ERROR_TEXT("jpeg quality must be in range 0-100")); + } int8_t fb_count = args[ARG_fb_count].u_int; mp_camera_grabmode_t grab_mode = args[ARG_grab_mode].u_int; 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, 100) == mp_const_none){ + 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. \ - Run reconfigure method or construct a new object with appropriate configuration (e.g. FrameSize).")); + 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); } } @@ -143,8 +151,8 @@ static mp_obj_t mp_camera_make_new(const mp_obj_type_t *type, size_t n_args, siz // 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]); - mp_float_t timeout = n_args < 2 ? MICROPY_FLOAT_CONST(0.25) : mp_obj_get_float(args[1]); - return mp_camera_hal_capture(self, timeout); + int8_t out_format = n_args < 2 ? -1 : mp_obj_get_int(args[1]); + return mp_camera_hal_capture(self, out_format); } static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(camera_capture_obj, 1, 2, camera_capture); @@ -198,14 +206,28 @@ 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; return mp_camera_deinit(args[0]); } static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_camera___exit___obj, 4, 4, mp_camera_obj___exit__); -// Camera propertiy functions -// Camera sensor propertiy functions +// Camera property functions +// Camera sensor property functions #define CREATE_GETTER(property, get_function) \ static mp_obj_t camera_get_##property(const mp_obj_t self_in) { \ mp_camera_obj_t *self = MP_OBJ_TO_PTR(self_in); \ @@ -233,12 +255,14 @@ static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_camera___exit___obj, 4, 4, mp_came { MP_ROM_QSTR(MP_QSTR_get_##property), MP_ROM_PTR(&camera_get_##property##_obj) }, \ { MP_ROM_QSTR(MP_QSTR_set_##property), MP_ROM_PTR(&camera_set_##property##_obj) } -CREATE_GETTER(frame_size, mp_obj_new_int) -CREATE_GETTER(pixel_format, mp_obj_new_int) -CREATE_GETTER(grab_mode, mp_obj_new_int) -CREATE_GETTER(fb_count, mp_obj_new_int) -CREATE_GETTER(pixel_width, mp_obj_new_int) -CREATE_GETTER(pixel_height, mp_obj_new_int) +CREATE_GETSET_FUNCTIONS(frame_size, MP_OBJ_NEW_SMALL_INT, mp_obj_get_int); +CREATE_GETTER(pixel_format, mp_obj_new_int); +CREATE_GETTER(grab_mode, mp_obj_new_int); +CREATE_GETTER(fb_count, mp_obj_new_int); +CREATE_GETTER(pixel_width, mp_obj_new_int); +CREATE_GETTER(pixel_height, mp_obj_new_int); +CREATE_GETTER(max_frame_size, mp_obj_new_int); +CREATE_GETTER(sensor_name, mp_obj_new_str_from_cstr); CREATE_GETSET_FUNCTIONS(contrast, MP_OBJ_NEW_SMALL_INT, mp_obj_get_int); CREATE_GETSET_FUNCTIONS(brightness, MP_OBJ_NEW_SMALL_INT, mp_obj_get_int); CREATE_GETSET_FUNCTIONS(saturation, MP_OBJ_NEW_SMALL_INT, mp_obj_get_int); @@ -274,12 +298,14 @@ static const mp_rom_map_elem_t camera_camera_locals_table[] = { { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&mp_camera_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&mp_identity_obj) }, { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&mp_camera___exit___obj) }, - { MP_ROM_QSTR(MP_QSTR_get_framesize), MP_ROM_PTR(&camera_get_frame_size_obj) }, { MP_ROM_QSTR(MP_QSTR_get_pixel_format), MP_ROM_PTR(&camera_get_pixel_format_obj) }, { MP_ROM_QSTR(MP_QSTR_get_grab_mode), MP_ROM_PTR(&camera_get_grab_mode_obj) }, { MP_ROM_QSTR(MP_QSTR_get_fb_count), MP_ROM_PTR(&camera_get_fb_count_obj) }, { MP_ROM_QSTR(MP_QSTR_get_pixel_width), MP_ROM_PTR(&camera_get_pixel_width_obj) }, { MP_ROM_QSTR(MP_QSTR_get_pixel_height), MP_ROM_PTR(&camera_get_pixel_height_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_max_frame_size), MP_ROM_PTR(&camera_get_max_frame_size_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_sensor_name), MP_ROM_PTR(&camera_get_sensor_name_obj) }, + ADD_PROPERTY_TO_TABLE(frame_size), ADD_PROPERTY_TO_TABLE(contrast), ADD_PROPERTY_TO_TABLE(brightness), ADD_PROPERTY_TO_TABLE(saturation), @@ -305,6 +331,7 @@ 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); diff --git a/tests/esp32_test.py b/tests/esp32_test.py index 16b65c6..23949fa 100644 --- a/tests/esp32_test.py +++ b/tests/esp32_test.py @@ -1,27 +1,27 @@ from camera import Camera, FrameSize, PixelFormat def test_property_get_frame_size(): - camera = Camera() + cam = Camera() Frame_Size = FrameSize.VGA - camera.reconfigure(frame_size=Frame_Size.VGA) - assert camera.get_frame_size == Frame_Size - assert camera.get_pixel_width == 640 - assert camera.get_pixel_height == 480 + 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 def test_property_get_pixel_format(): - camera = Camera() + cam = Camera() Pixel_Format = PixelFormat.RGB565 - camera.reconfigure(pixel_format=PixelFormat.RGB) - assert camera.get_pixel_format == Pixel_Format + cam.reconfigure(pixel_format=PixelFormat.RGB) + assert cam.get_pixel_format == Pixel_Format def test_camera_properties(): - camera = Camera() - for name in dir(camera): + cam = Camera() + for name in dir(cam): if name.startswith('get_'): prop_name = name[4:] set_method_name = f'set_{prop_name}' - if hasattr(camera, set_method_name): - set_method = getattr(camera, set_method_name) - get_method = getattr(camera, 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