Inspired by the work of Jason Salavon I wanted to extract all the photos of my face and generate the average of them.
This is the ruby code for doing that.
Install osxphotos
and use it to extract the images of the face you want to average. In my case, that's me, but probably you'll want to use your own name.
I chose to place mine in ~/Pictures/Averages
and to divide them up into folders by year. The ruby code that follows assumes that you have a root folder with sub-folders of jpgs you want to extract faces from by reading XMP metadata files. This is the command I used:
osxphotos export --person "Murray Steele" --sidecar xmp --convert-to-jpeg --jpeg-quality 1.0 --jpeg-ext jpg --directory "{created.year}" --skip-edited --download-missing --verbose --only-photos ~/Pictures/Averages
You can experiment with osxphotos
options to get different results, but the ruby code might not work if you try exif or json metadata, or a different, more nested, destination folder structure.
The macos photos library is a SQLite DB and I could have written some ruby code to execute the relevant SQL to extract the metadata myself. That was my original plan TBH, but the osxphotos
project has done all the heavily lifting already, and I'd probably end up using it as source for tranlisterating bits of the python to ruby for my purposes. This seemed boring so I decided not to.
Install imagemagick
because the minimagick
gem I use is a wrapper to the command line and doesn't install it for you.
Having run the osxphotos
step above, copy the ruby files below into the place you extracted the images to. Then run bundle install
.
Run the ruby script, passing in the folder of images you want to process. In this example we generate averages of all the photos of my face from 2019:
bundle exec ruby average.rb 2019
This will leave 2019-averaged.jpg
in the same folder as the ruby script. It'll also leave a bunch of semi processed images in the 2019
folder. These can all be deleted as the code isn't smart enough to skip bits it's already done. The whole folder can be deleted if you don't want to run it again.
You can comment out various parts of the process to avoid running things twice if you encounter problems.
E.g. if you've extracted the faces, but normalizing them is broken, you can comment out the call to extract_faces
and run the script from there. Or, if normalizing is fine, but orienting is broken, comment out the calls to both extract_faces
and normalize_faces
.
If I put more effort into this it would work via command line options or be smart enough to not repeat itself. I might also have made the filenames less verbose (foo.jpg.face.jpg.normalized.jpg.oriented.jpg
is ... a choice).
Marvel at your digitally average face. It might look something like these images averaging my face from 2024, 2019 and 2016:
In my opinion the output looks better when based on more photos as it becomes more dreamy and blurry. Contrast the 2019 version which was calculated from 321 images, with the 2024 version based on 143 images, or the 2016 version based on only 44 images. An actual specific face is much clearer in the 2016 version, whereas it becomes increasinly abstract in 2024 and 2019 as the image count used increases.
In a future version it might be interesting to extract some more data about the face position and do more than simply scale the faces to the same size to align them. Perhaps rotating the image to align the eyes, and scale the image more accurately based on the detected eye positions.