This commit is contained in:
parent
9d6b0b5931
commit
0d23348c65
1 changed files with 10 additions and 17 deletions
27
README.md
27
README.md
|
|
@ -10,7 +10,7 @@ Most digital frames either require you to pick and preprocess photos that you pu
|
||||||
|
|
||||||
It's magical to come home from a day out to immediately see photos taken from the day, or see memories from years ago pop up within the living space, unconfined by an app on your phone.
|
It's magical to come home from a day out to immediately see photos taken from the day, or see memories from years ago pop up within the living space, unconfined by an app on your phone.
|
||||||
|
|
||||||
It was a fun afternoon project with Claude Code, a bit of experimenting with different dithering and post-processing, and then fine tuning the photo picking algorithm. Besides powering my frame, I share this repo as a reference and as inspiration for self-hosting, to show that the combination of these services and can produce something that otherwise wouldn't have been possible to hack together so quickly.
|
It was a fun afternoon project with Claude Code, a bit of experimenting with different dithering and post-processing, and then fine tuning the photo picking algorithm. Besides powering my frame, I share this repo as a reference and as inspiration for self-hosting, to show that the combination of these services can produce something that otherwise wouldn't have been possible to hack together so quickly.
|
||||||
|
|
||||||
## How it works
|
## How it works
|
||||||
|
|
||||||
|
|
@ -25,10 +25,9 @@ It was a fun afternoon project with Claude Code, a bit of experimenting with dif
|
||||||
|
|
||||||
The two choices that matter most are `face_aware_crop` and Atkinson dithering.
|
The two choices that matter most are `face_aware_crop` and Atkinson dithering.
|
||||||
|
|
||||||
|
|
||||||
### Cropping
|
### Cropping
|
||||||
|
|
||||||
The frame has one orientation but I didn't want to limit it to only show portrait or landscape photos. So the `face_aware_crop` function resize-crops to fill the frame while keeping all faces within the frame. This can nicely turn a landscape with extra background on a size enjoyable on the portrait frame. For finding faces, it relies on the bounding boxes returned by Immich.
|
The frame has one orientation but I didn't want to limit it to only show portrait or landscape photos. So the `face_aware_crop` function resize-crops to fill the frame while keeping all faces within the frame. This can nicely turn a landscape with extra background into a crop that works well on the portrait frame. For finding faces, it relies on the bounding boxes returned by Immich.
|
||||||
|
|
||||||
See the following example from [crop_compare.ipynb](./notebooks/crop_compare.ipynb) that shows how the head bounding boxes affect the final crop.
|
See the following example from [crop_compare.ipynb](./notebooks/crop_compare.ipynb) that shows how the head bounding boxes affect the final crop.
|
||||||
|
|
||||||
|
|
@ -36,18 +35,17 @@ See the following example from [crop_compare.ipynb](./notebooks/crop_compare.ipy
|
||||||
<img src="photos/crop_compare_portrait.png" alt="Crop comparison showing original photos with face boxes, naive centre crops, and face-aware crops for a portrait frame target" width="760">
|
<img src="photos/crop_compare_portrait.png" alt="Crop comparison showing original photos with face boxes, naive centre crops, and face-aware crops for a portrait frame target" width="760">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
The second issue is the limited 6-colour palette. The intensity of these colours can't be changed like on an LCD panel, they're either shown or not. So to get any legible results, we have to turn to dithering. Turns out, there're many dithering algorithms with wildly different running times. [dither_compare.ipynb](./notebooks/dither_compare.ipynb) shows a comparison between a few.
|
The second issue is the limited 6-colour palette. The intensity of these colours can't be changed like on an LCD panel, they're either shown or not. So to get any legible results, we have to turn to dithering. Turns out, there are many dithering algorithms with wildly different running times. [dither_compare.ipynb](./notebooks/dither_compare.ipynb) shows a comparison between a few.
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="photos/dither_compare_hiker_in_mountains.png" alt="Palette-preserving dither comparison showing several 6-colour algorithms on a mountain hiker photo" width="760">
|
<img src="photos/dither_compare_hiker_in_mountains.png" alt="Palette-preserving dither comparison showing several 6-colour algorithms on a mountain hiker photo" width="760">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
Ultimately, I chose Atkinson dithering which seems to keep the highest contrast without too much artifacting. Of course, its performance on the raspberry pi was abysmal so the final version relies on numba for compiling the array operations resulting in a 100x speed improvement.
|
Ultimately, I chose Atkinson dithering which seems to keep the highest contrast without too much artifacting. Of course, its performance on the Raspberry Pi was abysmal so the final version relies on numba for compiling the array operations, resulting in a 100x speed improvement.
|
||||||
|
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
To run the project, create a file at [src/.env](./src/.env) based on the example of [.env.example](./src/.env.example) file and fill it in:
|
To run the project, copy [src/.env.example](./src/.env.example) to `src/.env` and fill it in:
|
||||||
|
|
||||||
| Variable | Purpose |
|
| Variable | Purpose |
|
||||||
| ---------------- | -------------------------------------------------------------- |
|
| ---------------- | -------------------------------------------------------------- |
|
||||||
|
|
@ -65,25 +63,20 @@ Follow [setup](./setup.md) for the additional setup steps.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
The script takes the following options
|
The script takes the following options:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
python3 src/display.py # uses IMMICH_PEOPLE from the .env file
|
python3 src/display.py # uses IMMICH_PEOPLE from the .env file
|
||||||
python3 src/display.py --album "Holiday 2025"
|
python3 src/display.py --album "Holiday 2025"
|
||||||
python3 src/display.py --people "Alice,Bob"
|
python3 src/display.py --people "Alice,Bob"
|
||||||
python3 src/display.py -o 90 # portrait orientaiton
|
python3 src/display.py -o 90 # portrait orientation
|
||||||
python3 src/display.py --saturation 1.5 --contrast 1.1 --gamma 0.85 # change preprocessing settings
|
python3 src/display.py --saturation 1.5 --contrast 1.1 --gamma 0.85 # change preprocessing settings
|
||||||
```
|
```
|
||||||
|
|
||||||
`display.py` only runs on the Pi (it needs SPI). For off-device experiments
|
`display.py` only runs on the Pi (it needs SPI). For off-device experiments see the notebooks below.
|
||||||
see the notebooks below.
|
|
||||||
|
|
||||||
## Learnings
|
## Learnings
|
||||||
|
|
||||||
Honestly, the Pi Zero 2W is overkill for this. It chews through battery if you
|
Honestly, the Pi Zero 2W is overkill for this. It chews through battery if you try to run untethered, and most of the time it's just sitting idle waiting for the next cron tick. If I were doing this again for a battery-powered build I'd probably reach for an ESP32 with deep sleep. Mine stays plugged in, so I haven't bothered.
|
||||||
try to run untethered, and most of the time it's just sitting idle waiting for
|
|
||||||
the next cron tick. If I were doing this again for a battery-powered build I'd
|
|
||||||
probably reach for an ESP32 with deep sleep. Mine stays plugged in, so I haven't
|
|
||||||
bothered.
|
|
||||||
|
|
||||||
I would also give [Inky Impression](https://shop.pimoroni.com/products/inky-impression?variant=55186435244411) a try with a custom made frame for a larger display and perhaps integrated lights, as the e-ink looks a bit muddled in the evening lights. I think a separate light source would be the gratest improvement by far.
|
I would also give [Inky Impression](https://shop.pimoroni.com/products/inky-impression?variant=55186435244411) a try with a custom-made frame for a larger display and perhaps integrated lights, as the e-ink looks a bit muddled in the evening lights. I think a separate light source would be the greatest improvement by far.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue