You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: README.md
+9-6Lines changed: 9 additions & 6 deletions
Original file line number
Diff line number
Diff line change
@@ -8,8 +8,6 @@ Here's what the front end looks like when a few images have been captured by the
8
8
9
9

10
10
11
-
(Images are somewhat out of focus as the camera I am using doesn't have auto focus and I didn't adjust its position for these pics, they were just test data!)
12
-
13
11
And here's a Raspberry Pi with a camera attached:
14
12
15
13

@@ -43,6 +41,8 @@ When you're done with the Docker container, stop it like this:
43
41
docker-compose down
44
42
```
45
43
44
+
Your data is saved in a [Redis Append Only File](https://redis.io/docs/management/persistence/) in the `redisdata` folder. Redis Stack will reload the dataset from this file when you restart the container.
45
+
46
46
With the container running, you can access the [Redis CLI](https://redis.io/docs/ui/cli/) using this command:
47
47
48
48
```
@@ -67,6 +67,7 @@ Each key contains a hash with the following name/value pairs:
67
67
68
68
*`mime_type`: The [MIME type](https://en.wikipedia.org/wiki/Media_type) for the captured image data. This will always be `image/jpeg` unless you change it and the image capture format in the `capture.py` script.
69
69
*`timestamp`: The [UNIX timestamp](https://en.wikipedia.org/wiki/Unix_time) that the image was captured at, as recoded from the Raspberry Pi's clock. This will be the same value as the timestamp in the key name.
70
+
*`lux`: The [lux](https://en.wikipedia.org/wiki/Lux) value captured by the camera when the image was taken.
70
71
*`image_data`: A binary representation of the bytes for the image captured by the camera. This will be a [JPEG image](https://en.wikipedia.org/wiki/JPEG) unless you change the capture format in `capture.py`.
71
72
72
73
Here's a complete example, with the image data truncated for brevity:
@@ -76,11 +77,13 @@ Here's a complete example, with the image data truncated for brevity:
With the camera that I used ([Raspberry Pi Camera Module 2.1](https://www.raspberrypi.com/products/camera-module-v2/) capturing at 3280x2464 pixels - configurable in `capture.py`) you can expect each Hash to require around 2Mb of RAM in Redis.
86
+
With the camera that I used ([Raspberry Pi Camera Module 3](https://www.raspberrypi.com/products/camera-module-3/) capturing at 4608x2592 pixels - configurable in `capture.py`) you can expect each Hash to require around 1Mb of RAM in Redis.
Copy file name to clipboardExpand all lines: pi/README.md
+44-23Lines changed: 44 additions & 23 deletions
Original file line number
Diff line number
Diff line change
@@ -6,13 +6,13 @@
6
6
7
7
This folder contains the Python code for the image capture component of this project. It runs on a Raspberry Pi with a Raspberry Pi Camera Module attached.
8
8
9
-
Every 10 or so seconds, the code takes a new picture in JPEG format and stores it in a Redis Hash along with some basic metadata. The key name for each hash is `image:<unix time stamp when image was captured>`.
9
+
Every so many seconds (configurable), the code takes a new picture in JPEG format and stores it in a Redis Hash along with some basic metadata. The key name for each hash is `image:<unix time stamp when image was captured>`.
10
10
11
-
I've tested this on a [Raspberry Pi 3B](https://www.raspberrypi.com/products/raspberry-pi-3-model-b/) using the [Raspberry Pi Camera Module v2.1](https://www.raspberrypi.com/products/camera-module-v2/). Other models of Raspberry Pi that have the camera connector ([3A+](https://www.raspberrypi.com/products/raspberry-pi-3-model-a-plus/), [3B+](https://www.raspberrypi.com/products/raspberry-pi-3-model-b-plus/), [4B](https://www.raspberrypi.com/products/raspberry-pi-4-model-b/) etc) should work too.
11
+
I've tested this on a [Raspberry Pi 3B](https://www.raspberrypi.com/products/raspberry-pi-3-model-b/) using both the [Raspberry Pi Camera Module v2.1](https://www.raspberrypi.com/products/camera-module-v2/) and [Raspberry Pi Camera Module v3](https://www.raspberrypi.com/products/camera-module-3/). Other models of Raspberry Pi that have the camera connector ([3A+](https://www.raspberrypi.com/products/raspberry-pi-3-model-a-plus/), [3B+](https://www.raspberrypi.com/products/raspberry-pi-3-model-b-plus/), [4B](https://www.raspberrypi.com/products/raspberry-pi-4-model-b/) etc) should work too.
12
12
13
-
I haven't tested with the [Raspberry Pi Camera Module v3](https://www.raspberrypi.com/products/camera-module-3/) or the [High Quality Camera](https://www.raspberrypi.com/products/raspberry-pi-high-quality-camera/). I would imagine that the v3 is a good choice for this project as it has auto focus. The pictures from my v2.1 camera can be blurry as there's no auto focus.
13
+
I haven't tested with the [High Quality Camera](https://www.raspberrypi.com/products/raspberry-pi-high-quality-camera/). Of the cheaper models, the v3 is a good choice for this project as it has auto focus and higher resolution than the v2.1 and is easy to find online at a reasonable price. The pictures from my v2.1 camera can be blurry as there's no auto focus.
14
14
15
-
**Note:** As Redis keeps a copy of all the data in memory, you should bear in mind that an 8Mb image file will require at least 8Mb of RAM on the Redis server. As it stands, the code doesn't expire pictures from Redis after a period of time, but you could easily add this using a call to the [`EXPIRE`](https://redis.io/commands/expire/) command whenever you add a new image Hash.
15
+
**Note:** As Redis keeps a copy of all the data in memory, you should bear in mind that an 8Mb image file will require at least 8Mb of RAM on the Redis server. To help manage the amount of memory used, this project automatically expires the image data from Redis after a configurable amount of time (see later for details).
The v3 camera module has autofocus capabilities. These are enabled like so, and only if an environment variable is set to do so (see later for details):
Use the `Picamera2` documentation to adjust the camera configuration in `camera_config` e.g. to capture lower resolution pictures. This configuration assumes we are running on a headless Raspberry Pi so there's no preview window required.
33
40
34
41
Next, a connection to Redis is established, using the value of an environment variable:
@@ -37,7 +44,16 @@ Next, a connection to Redis is established, using the value of an environment va
The code then enters an infinite loop, in which is captures an image plus some metadata from the camera, stores it in Redis and sleeps for 10 seconds before doing it all again.
47
+
The code then enters an infinite loop, in which is captures an image plus some metadata from the camera, stores it in Redis and sleeps for a configurable number of seconds before doing it all again.
48
+
49
+
If the camera module has autofocus and it is enabled... we start an autofocus cycle to make sure that the camera's focus is in the right place:
50
+
51
+
```python
52
+
ifCAMERA_AUTOFOCUS==True:
53
+
picam2.autofocus_cycle()
54
+
```
55
+
56
+
This is synchronous, so may take a short amount of time to complete.
41
57
42
58
We want to capture the image into a file like structure in memory, rather than write it to the filesystem. We use an [in memory binary stream](https://docs.python.org/3/library/io.html#binary-i-o) declared like this:
The return value of `picam2.capture_file` is some metadata from the camera. This isn't currently stored in Redis, but is printed out so you can determine if any of it is useful to you. See later in this file for an example.
56
72
57
-
Now it's time to create a Hash in Redis and store our image plus a couple of other pieces of data there:
73
+
Now it's time to create a Hash in Redis and store our image plus a couple of other pieces of data there, including the Lux value from the metadata:
First, we create the key name we're going to use when storing the Hash. It's `image:<timestamp>`.
@@ -72,20 +89,18 @@ Hashes in Redis are schemaless, so if you add extra fields there's no need to ch
72
89
73
90
We store the bytes of the image, the timestamp and the MIME or media type of the image... so that any front end knows what encoding the data in `image_data` is in.
74
91
75
-
Saving the Hash to Redis is then simply a matter of running the [`HSET` command](https://redis.io/commands/hset/), passing it the key name and dict of name/value pairs to store:
92
+
Saving the Hash to Redis is then simply a matter of running the [`HSET` command](https://redis.io/commands/hset/), passing it the key name and dict of name/value pairs to store. When saving this fata, we also want to set an expiry time for it which we do with the Redis [`EXPIRE` command](https://redis.io/commands/expire/). The time to live for each hash is a configurable number of seconds, read from the `IMAGE_EXPIRY` environment variable (see later for details).
As it stands, the images will stay in Redis until manually deleted. If you want to set a time to live on the image, use the [EXPIRE command](https://redis.io/commands/expire/). Redis will consider the Hash deleted after the number of seconds you specify has passed, freeing up resources associated with it on the Redis server. To implement this with a 1hr expiry time, modify the code as follows:
94
+
This means that we want to send two commands to Redis. To save on network bandwidth, let's use a feature of the Redis protocol called a [pipeline](https://redis.io/docs/manual/pipelining/) and send both in the same network round trip:
redis_client.expire(redis_key, 3600) # 60 secs = 1 min x 60 = 1hr
97
+
pipe = redis_client.pipeline(transaction=False)
98
+
pipe.hset(redis_key, mapping= data_to_save)
99
+
pipe.expire(redis_key, IMAGE_EXPIRY)
100
+
pipe.execute()
86
101
```
87
102
88
-
Redis also has an [EXPIREAT command](https://redis.io/commands/expireat/) if you prefer to specify a time and date for expiry, rather than a number of seconds in the future.
103
+
This sets up the `HSET` and `EXPIRE` commands in a pipeline, which is then sent to Redis using the `execute` function.
Setting up the camera may require some changes to the operating system configuration of the Raspberry Pi. This is what worked for me on the Raspberry Pi 3B using the Camera Module v2.1.
127
+
Setting up the camera may require some changes to the operating system configuration of the Raspberry Pi. This is what worked for me on the Raspberry Pi 3B using either the Camera Module v2.1 or v3 (recommended).
113
128
114
129
First, connect the camera to the Raspberry Pi with the ribbon cable provided. If you are unsure how to do this, follow Raspberry Pi's [instructions here](https://projects.raspberrypi.org/en/projects/getting-started-with-picamera/2).
115
130
@@ -122,13 +137,13 @@ max_framebuffers=10
122
137
dtoverlay=imx219
123
138
```
124
139
125
-
The `imx219` value may differ for your camera. I was using the Raspberry Pi Camera Module v2.1. If you are using something different, you'll need to research appropriate values for your camera.
140
+
The `imx219` value may differ for your camera. Use `imx219` for the Raspberry Pi Camera Module v2.1, or `imx708` for the v3. If you are using something different, you'll need to research appropriate values for your camera. Raspberry Pi provide this information in their [camera documentation](https://www.raspberrypi.com/documentation/accessories/camera.html#preparing-the-software).
126
141
127
-
If you made any changes, save them and reboot the Raspberry Pi.
142
+
If you made any changes, save them and **reboot the Raspberry Pi** (`sudo reboot`).
128
143
129
144
### Python Setup
130
145
131
-
You need Python 3.7 or higher (I've tested this with Python 3.9.2. To check your Python version:
146
+
You need Python 3.7 or higher (I've tested this with Python 3.9.2). To check your Python version:
Be sure to configure both the capture script and the separate server component to talk to the same Redis instance!
180
195
181
-
Alternatively, you can create a file in the `server` folder called `.env` and store your environment variable values there. See `env.example` for an example. Don't commit `.env` to source control, as your Redis credentials should be considered a secret and managed as such!
196
+
You'll also need to set the following environment variables:
197
+
198
+
*`IMAGE_CAPTURE_FREQUENCY` - set this to the number of seconds that you want the code to wait between capturing images, e.g. `30`.
199
+
*`IMAGE_EXPIRY` - set this to the number of seconds that you want the image data to be stored in Redis for before it is expired e.g. `300` for 5 minutes.
200
+
*`CAMERA_AUTOFOCUS` - set this to `1` if your camera module has autofocus (v3) or `0` if it doesn't (v2).
201
+
202
+
Alternatively (recommended), you can create a file in the `server` folder called `.env` and store your environment variable values there. See `env.example` for an example. Don't commit `.env` to source control, as your Redis credentials should be considered a secret and managed as such!
182
203
183
204
### Running the Capture Script
184
205
@@ -188,7 +209,7 @@ With the setup steps completed, start the capture script as follows:
188
209
python3 capture.py
189
210
```
190
211
191
-
You should expect to see output similar to the following on startup:
212
+
You should expect to see output similar to the following on startup (example using camera module v2.1):
192
213
193
214
```
194
215
[0:34:17.749739445] [847] INFO Camera camera_manager.cpp:299 libcamera v0.0.4+22-923f5d70
@@ -204,13 +225,13 @@ Your output may differ if you are using a different camera. It appears that thi
204
225
WARN RPI raspberrypi.cpp:1357 Mismatch between Unicam and CamHelper for embedded data usage!
205
226
```
206
227
207
-
Every 10 seconds or so, the script will capture a new image. Expect to see output similar to the following:
228
+
Every so many seconds, the script will capture a new image. Expect to see output similar to the following:
The camera metadata isn't stored in Redis, it's just output for informational purposes. If any of it is considered useful enough to keep, it should be easy to modify `capture.py` to add it to the Redis Hash that stores the image and associated data.
235
+
With the exception of the `Lux` value, he camera metadata isn't stored in Redis - it's just output for informational purposes. If any of it is considered useful enough to keep, it should be easy to modify `capture.py` to add it to the Redis Hash that stores the image and associated data.
0 commit comments