Skip to content

Commit 176b0a3

Browse files
committed
- Fix: image convertes
- Add: Camera version driver - Performance improvements - Minor imprvements
1 parent eae7247 commit 176b0a3

File tree

10 files changed

+384
-229
lines changed

10 files changed

+384
-229
lines changed

.github/workflows/ESP32.yml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,9 @@ jobs:
7878
cd esp-idf
7979
./install.sh all
8080
cd components
81-
git clone https://github.com/cnadler86/esp32-camera
81+
latest_cam_driver=$(curl -s https://api.github.com/repos/espressif/esp32-camera/releases/latest | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
82+
git clone --depth 1 --branch $latest_cam_driver https://github.com/espressif/esp32-camera.git
83+
# git clone https://github.com/cnadler86/esp32-camera.git
8284
cd ~/esp-idf/
8385
source ./export.sh
8486
@@ -145,6 +147,8 @@ jobs:
145147
# Build MicroPython for each board
146148
- name: Build MicroPython
147149
run: |
150+
cd ~/esp-idf/components/esp32-camera
151+
CAM_DRIVER=$(git describe --tags --always --dirty)
148152
cd ~/micropython/ports/esp32
149153
source ~/esp-idf/export.sh
150154
@@ -153,9 +157,9 @@ jobs:
153157
IFS='-' read -r BOARD_NAME BOARD_VARIANT <<< "${BUILD_TARGET}"
154158
155159
if [ -n "${BOARD_VARIANT}" ]; then
156-
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"
160+
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"
157161
else
158-
IDF_CMD="idf.py -D MICROPY_BOARD=$BOARD_NAME -D USER_C_MODULES=${{ github.workspace }}/src/micropython.cmake -B build-$BUILD_TARGET"
162+
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"
159163
fi
160164
if [ -n "${CAMERA_MODEL}" ]; then
161165
echo "FW_NAME=${CAMERA_MODEL}" >> $GITHUB_ENV

README.md

Lines changed: 48 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
[![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)
44

5-
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).
5+
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.
66
At the moment, this is a micropython user module, but it might get in the micropython repo in the future.
77
The API is stable, but it might change without previous announce.
88

@@ -134,6 +134,17 @@ See autocompletions in Thonny in order to see the list of methods.
134134
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).
135135
Note that each method requires a "get_" or "set_" prefix, depending on the desired action.
136136

137+
To get the version of the camera driver used:
138+
139+
```python
140+
import camera
141+
vers = camera.Version()
142+
```
143+
144+
### Additional information
145+
146+
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
147+
137148
## Build your custom FW
138149

139150
### Setting up the build environment (DIY method)
@@ -142,20 +153,20 @@ To build the project, follow these instructions:
142153

143154
- [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).
144155
- 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).
145-
- 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):
156+
- 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):
146157

147158
```yml
148159
espressif/esp32-camera:
149-
git: https://github.com/cnadler86/esp32-camera #At the moment I maintain a fork because of some unsolved bugs and conveniance.
160+
git: https://github.com/espressif/esp32-camera.git
150161
```
151162
152-
Alternatively, you can clone the <https://github.com/cnadler86/esp32-camera> repository inside the esp-idf/components folder instead of altering the idf_component.yml file.
163+
Alternatively, you can clone the <https://github.com/espressif/esp32-camera> repository inside the esp-idf/components folder instead of altering the idf_component.yml file.
153164
154165
### Add camera configurations to your board (optional, but recommended)
155166
156167
#### Supported Camera Models
157168
158-
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).
169+
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).
159170
Example (don't forget to add the empty line at the bottom):
160171
161172
```c
@@ -188,7 +199,6 @@ Below is a list of supported `MICROPY_CAMERA_MODEL_xxx` definitions:
188199
#### For unsupported camera models
189200

190201
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.
191-
Don't forget the empty line at the bottom.
192202
Example for Xiao sense:
193203

194204
```c
@@ -214,6 +224,9 @@ Example for Xiao sense:
214224
#define MICROPY_CAMERA_GRAB_MODE (1) // 0=WHEN_EMPTY (might have old data, but less resources), 1=LATEST (best, but more resources)
215225

216226
```
227+
#### Customize additional camera settings
228+
229+
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.
217230

218231
### Build the API
219232

@@ -239,28 +252,37 @@ If you experience problems, visit [MicroPython external C modules](https://docs.
239252

240253
## FPS benchmark
241254

242-
I didn't use a calibrated osziloscope, but here is a benchmark with my ESP32S3 (GrabMode=LATEST).
243-
Using fb_count=2 doubles the FPS for JPEG. This might also aplly for other PixelFormats.
244-
245-
| Frame Size | GRAYSCALE | RGB565 | YUV422 | JPEG | JPEG (fb = 2) |
246-
|------------|-----------|--------|--------|--------|---------------|
247-
| R96X96 | 12.5 | 12.5 | 12.5 | No img | No img |
248-
| QQVGA | 12.5 | 12.5 | 12.5 | 25 | 50 |
249-
| QCIF | 11 | 11 | 11.5 | 25 | 50 |
250-
| HQVGA | 12.5 | 12.5 | 12.5 | 25 | 50 |
251-
| R240X240 | 12 | 12.5 | 11.5 | 25 | 50 |
252-
| QVGA | 12 | 11 | 12 | 25 | 50 |
253-
| CIF | 12.5 | No img | No img | 6 | 12.5 |
254-
| HVGA | 2.5 | 3 | 2.5 | 12.5 | 25 |
255-
| VGA | 3 | 3 | 3 | 12.5 | 25 |
256-
| SVGA | 3 | 3 | 3 | 12.5 | 25 |
257-
| XGA | No img | No img | No img | 6 | 12.5 |
258-
| HD | No img | No img | No img | 6 | 12.5 |
259-
| SXGA | 2 | 2 | 2 | 6 | 12.5 |
260-
| UXGA | No img | No img | No img | 6 | 12.5 |
255+
I didn't use a calibrated osziloscope, but here is a benchmark with my ESP32S3 (GrabMode=LATEST, fb_count = 1, jpeg_quality=85%).
256+
Using fb_count=2 theoretically can double the FPS (see JPEG with fb_count=2). This might also aplly for other PixelFormats.
257+
258+
| Frame Size | GRAYSCALE | RGB565 | YUV422 | JPEG | JPEG -> RGB565 | JPEG -> RGB888 | JPEG (fb=2) |
259+
|------------|-----------|--------|--------|--------|----------------|----------------|-------------|
260+
| R96X96 | 12.5 | 12.5 | 12.5 | No img | No img | No img | No img |
261+
| QQVGA | 12.5 | 12.5 | 12.5 | 25 | 25 | 25 | 50 |
262+
| QCIF | 11 | 11 | 11.5 | 25 | 25 | 25 | 50 |
263+
| HQVGA | 12.5 | 12.5 | 12.5 | 25 | 16.7 | 16.7 | 50 |
264+
| R240X240 | 12.5 | 12.5 | 11.5 | 25 | 16.7 | 12.5 | 50 |
265+
| QVGA | 12 | 11 | 12 | 25 | 12.5 | 12.5 | 50 |
266+
| CIF | 12.5 | No img | No img | 6.3 | 1.6 | 1.6 | 12.5 |
267+
| HVGA | 3 | 3 | 2.5 | 12.5 | 6.3 | 6.3 | 25 |
268+
| VGA | 3 | 3 | 3 | 12.5 | 3.6 | 3.6 | 25 |
269+
| SVGA | 3 | 3 | 3 | 12.5 | 2.8 | 2.5 | 25 |
270+
| XGA | No img | No img | No img | 6.3 | 1.6 | 1.6 | 12.5 |
271+
| HD | No img | No img | No img | 6.3 | 1.4 | 1.3 | 12.5 |
272+
| SXGA | 2 | 2 | 2 | 6.3 | 1 | 1 | 12.5 |
273+
| UXGA | No img | No img | No img | 6.3 | 0.7 | 0.7 | 12.5 |
274+
275+
276+
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.
277+
278+
## Troubleshoot
279+
280+
You can find information on the following sites:
281+
- [ESP-FAQ](https://docs.espressif.com/projects/esp-faq/en/latest/application-solution/camera-application.html)
282+
- [ChatGPT](https://chatgpt.com/)
283+
- [Issues in here](https://github.com/cnadler86/micropython-camera-API/issues?q=is%3Aissue)
261284

262285
## Future Plans
263286

264287
- Edge case: enable usage of pins such as i2c for other applications
265288
- Provide examples in binary image
266-
- Include camera driver version in API

examples/CameraSettings.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ async def stream_camera(writer):
2828
cam.init()
2929
if not cam.get_bmp_out() and cam.get_pixel_format() != PixelFormat.JPEG:
3030
cam.set_bmp_out(True)
31-
31+
await asyncio.sleep(1)
32+
3233
writer.write(b'HTTP/1.1 200 OK\r\nContent-Type: multipart/x-mixed-replace; boundary=frame\r\n\r\n')
3334
await writer.drain()
3435

examples/benchmark.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,21 @@
55
gc.enable()
66

77
def measure_fps(duration=2):
8-
start_time = time.time()
9-
while time.time() - start_time < 0.5:
8+
start_time = time.ticks_ms()
9+
while time.ticks_ms() - start_time < 500:
1010
cam.capture()
1111

12-
start_time = time.time()
12+
start_time = time.ticks_ms()
1313
frame_count = 0
1414

15-
while time.time() - start_time < duration:
16-
cam.capture()
17-
frame_count += 1
15+
while time.ticks_ms() - start_time < duration*1000:
16+
img = cam.capture()
17+
if img:
18+
frame_count += 1
1819

19-
end_time = time.time()
20-
fps = frame_count / (end_time - start_time)
21-
return fps
20+
end_time = time.ticks_ms()
21+
fps = frame_count / (end_time - start_time) * 1000
22+
return round(fps,1)
2223

2324
def print_summary_table(results, cam):
2425
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):
7879
print('Set', p, f,f'fb={fb}',':')
7980

8081
try:
81-
cam.reconfigure(frame_size=f_value)
82+
cam.reconfigure(frame_size=f_value) #set_frame_size fails for YUV422
8283
time.sleep_ms(10)
8384
img = cam.capture()
8485

examples/benchmark_img_conv.py

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

src/micropython.cmake

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,28 @@ target_sources(usermod_mp_camera INTERFACE
88
${CMAKE_CURRENT_LIST_DIR}/modcamera_api.c
99
)
1010

11-
target_include_directories(usermod_mp_camera INTERFACE
12-
${CMAKE_CURRENT_LIST_DIR}
13-
${IDF_PATH}/components/esp32-camera/driver/include
14-
${IDF_PATH}/components/esp32-camera/driver/private_include
15-
${IDF_PATH}/components/esp32-camera/conversions/include
16-
${IDF_PATH}/components/esp32-camera/conversions/private_include
17-
${IDF_PATH}/components/esp32-camera/sensors/private_include
18-
)
11+
if(EXISTS "${IDF_PATH}/components/esp32-camera")
12+
target_include_directories(usermod_mp_camera INTERFACE
13+
${CMAKE_CURRENT_LIST_DIR}
14+
${IDF_PATH}/components/esp32-camera/driver/include
15+
${IDF_PATH}/components/esp32-camera/driver/private_include
16+
${IDF_PATH}/components/esp32-camera/conversions/include
17+
${IDF_PATH}/components/esp32-camera/conversions/private_include
18+
${IDF_PATH}/components/esp32-camera/sensors/private_include
19+
)
20+
else()
21+
target_include_directories(usermod_mp_camera INTERFACE
22+
${CMAKE_CURRENT_LIST_DIR})
23+
endif()
1924

2025
if (MICROPY_CAMERA_MODEL)
2126
target_compile_definitions(usermod_mp_camera INTERFACE MICROPY_CAMERA_MODEL_${MICROPY_CAMERA_MODEL}=1)
2227
endif()
2328

29+
if (MP_CAMERA_DRIVER_VERSION)
30+
target_compile_definitions(usermod_mp_camera INTERFACE MP_CAMERA_DRIVER_VERSION=\"${MP_CAMERA_DRIVER_VERSION}\")
31+
endif()
32+
2433
target_link_libraries(usermod INTERFACE usermod_mp_camera)
2534

2635
micropy_gather_target_properties(usermod_mp_camera)

0 commit comments

Comments
 (0)