Category Archives: Photography

Automatic photo sorting with Gps location lookup

We take a lot of pictures, with our Nikon camera and our mobile phones.

(Apparently in 2019 5544 pictures)

Some stats

757 20190803 - on a single day (Holiday)

Average pictures per month
locate "/2019/" | egrep -i "photoalbum|gsm" | egrep -i "mp4$|jpg$" | grep -Eo '2[[:digit:]]{3}[[:digit:]]{2}[[:digit:]]{2}' | cut -c-6 |sort | uniq -c | sort -n | awk '{ sum += $1; n++ } END { if (n > 0) print sum / n; }'
461

Besides android pictures being automatically uploaded to our nextcloud, I’m using some apps and scripts to get pictures and movies stored on my fileserver. (bash scripts/andftp)

For sorting those media files, i made a sorting script.
(Today I added a location sorting addition using GPS information stored in the exif information.

  • jpg and jpeg (add your own extentions)
  • mp4 and mov (for mobile and nikon)
  • Sorts by camera model/year/date/location
  • tries to extract date from filename when not found in exifinfo
  • Sorts whatsapp media
  • Sorts Raw

INSTALLING

pip3 install reverse_geocoder
You need python3, exiftool, exiftime and mediainfo

copy below python script in ~/bin/reverse2.py

( need more info? change last print entry admin1/admin2)
[{‘lat’: ‘-39.45556’, ‘lon’: ‘173.85833’, ‘name’: ‘Opunake’, ‘admin1’: ‘Taranaki’, ‘admin2’: ‘South Taranaki District’, ‘cc’: ‘NZ’}]

import reverse_geocoder as rg
import sys

lat=sys.argv[1]
lon=sys.argv[2]

coordinates = (lat,lon)

results = rg.search(coordinates) # default mode = 2


#print (results)

for entry in results:
    print(entry['name'] + "(" +  entry['cc'] + ")")

And a bash script /usr/local/bin/exifsort.sh

#!/bin/bash
#set -x

reversepath=/home/henri/projects/reversegeo/reverse2.py

#RAW
rawcnt=`ls | grep -i  nef$ | wc -l`
if [ "$rawcnt" = "0" ] ; then
echo "no raw"
else
mkdir raw 2>/dev/null
ls | grep -i nef$ | while read ; do mv $REPLY raw ; done
fi


ls | egrep -i "jpg$|jpeg" | while read ; do 
 	location=""	
	getmodel=$(exiftool "$REPLY" |grep "Make " | awk '{ print $3 }')
	if [ "$getmodel" != "" ] ; then 
		getmodel=$getmodel/
	fi
	echo "$REPLY" | grep WA0 >/dev/null && getmodel=whatsapp/

	gpsinfo=$(exiftool -c "%+.6f" "$REPLY" |grep "GPS Position" | cut -d":" -f2 | tr -d ' ' | sed s/,/\ /g)
	if [ "$gpsinfo" != "" ] ; then 
		location=$(python3 $reversepath $gpsinfo | grep -vi load | sed s/\(NL\)//g)
	fi
	dater=$(exiftime "$REPLY" 2>/dev/null | egrep "Created|Digitized" | sed s/Digitized/Created/g | tail -1  | cut -c 16-19,21,22,24,25)
	if [ "$dater" = "" ] ; then 
#		echo "Trying from filename"
		dater=$(echo $REPLY | grep -Eo '2[[:digit:]]{3}-[[:digit:]]{2}-[[:digit:]]{2}')

		if [ "$dater" = "" ] ; then 
#			echo "Trying from filename - maybe without dashes"
			dater=$(echo $REPLY | grep -Eo '2[[:digit:]]{3}[[:digit:]]{2}[[:digit:]]{2}')

		fi
	fi
	if [ "$dater" != "" ] ; then 
		year=$(echo $dater | cut -c-4)
		mkdir -p "${getmodel}$year/${dater}/$location"
		mv "$REPLY" "${getmodel}${year}/${dater}/$location"
	else
		mkdir -p "${getmodel}unknowndate/$location"
		mv "$REPLY" "${getmodel}unknowndate/$location"
      	fi
done

ls | egrep -i "mov$|mp4$" | while read ; do 
	
 	location=""	
	getmodel=$(exiftool "$REPLY" |grep "Make " | awk '{ print $3 }')
	if [ "$getmodel" != "" ] ; then 
		getmodel=$getmodel/
	fi

	echo "$REPLY" | grep WA0 >/dev/null && getmodel=whatsapp/
	gpsinfo=$(exiftool -c "%+.6f" "$REPLY" |grep "GPS Position" | cut -d":" -f2 | tr -d ' ' | sed s/,/\ /g)
	if [ "$gpsinfo" != "" ] ; then 
		location=$(python3 $reversepath $gpsinfo | grep -vi load | sed s/\(NL\)//g)
	fi
	dater=$(mediainfo "$REPLY" | grep Encode | tail -1 | cut -f2- -d:  | cut -f3 -d" " | sed s/-//g)
	if [ "$dater" = "" ] ; then 
#		echo "Trying from filename"
		dater=$(echo $REPLY | grep -Eo '2[[:digit:]]{3}-[[:digit:]]{2}-[[:digit:]]{2}')

		if [ "$dater" = "" ] ; then 
#			echo "Trying from filename - maybe without dashes"
			dater=$(echo $REPLY | grep -Eo '2[[:digit:]]{3}[[:digit:]]{2}[[:digit:]]{2}')

		fi
	fi
	if [ "$dater" != "" ] ; then 
		year=$(echo $dater | cut -c-4)
		mkdir -p "${getmodel}$year/${dater}/$location"
		mv "$REPLY" "${getmodel}${year}/${dater}/$location"
	else
		mkdir -p "${getmodel}unknowndate/$location"
		mv "$REPLY" "${getmodel}unknowndate/$location"
      	fi
done

Example running in a directory with mixed media

# Raw images get moved into a RAW directory
no raw

# Samsung phone detected with date and GPS location
mkdir -p samsung/20220717/Hilversum
mv 20220717_133453.jpg samsung/20220717/Hilversum

# OnePlus phone
mkdir -p OnePlus/20021208/Voorburg
mv IMG_20190109_091825.jpg OnePlus/20021208/Voorburg

# Realme (Added country when not NL)
mkdir -p realme/20220607/Isle of Islay(GB)
mv IMG20220607213630.jpg realme/20220607/Isle of Islay(GB)

# Whatsapp has no date embedded so it get it from filename
Trying from filename
Trying from filename - maybe without dashes
mkdir -p whatsapp/20221021/
mv IMG-20221021-WA0000.jpg whatsapp/20221021/

# Nikon without GPS
mkdir -p NIKON/20220613/
mv DSC_1423.MOV NIKON/20220613/

# Whatsapp video without exif
mkdir -p whatsapp/20170528/
mv VID-20170528-WA0006.mp4 whatsapp/20170528/

# No camera name detected in exif from mobile movie
mkdir -p 20190114/Maarssen
mv VID_20190114_142455.mp4 20190114/Maarssen

# Location in mp4
mkdir -p 20220607/Lamlash(GB)
mv VID20220607155044.mp4 20220607/Lamlash(GB)

Result

./NIKON/2022/20220613/DSC_1423.MOV
./NIKON/2022/20220610/750_1101.JPG
./realme/2022/20220818/Hilversum/IMG20220818203825.jpg
./realme/2022/20220607/Isle of Islay(GB)/IMG20220607213630.jpg
./2019/20190114/Maarssen/VID_20190114_142455.mp4
./whatsapp/2017/20170528/VID-20170528-WA0006.mp4
./whatsapp/2022/20221021/IMG-20221021-WA0000.jpg
./2022/20220607/Lamlash(GB)/VID20220607155044.mp4
./2022/20220516/Hilversum/VID20220516125913.mp4
./OnePlus/2002/20021208/Voorburg/IMG_20190109_091825.jpg
./samsung/2022/20220717/Hilversum/20220717_133453.jpg

You could automate this using incrond
apt-get install incron
add your user to /etc/incron.allow
incrontab -e
add

/fileserver/mediain/ IN_CREATE /usr/local/bin/sortmymedia.sh
Coping a file in the directory, auto sort and move to correct location

sortmymedia.sh

#!/bin/bash
cd /home/user/media
/usr/local/bin/exifsort.sh

Workshop Cyanotype

The cyanotype (from Ancient Greek kuáneos, “dark blue” and túpos, “mark, impression, type”) is a slow-reacting, photographic printing technique. It produces a cyan-blue print used for art as monochrome imagery applicable on a range of supports, and for reprography in the form of blueprints. For any purpose, the process usually uses two chemicals: ferric ammonium citrate or ferric ammonium oxalate, and potassium ferricyanide, and only water to develop and fix. Announced in 1842, it is still in use.

This technique was also used as a method of copying drawings.
For example buildings and schematics. While making copies of drawings with the exact dimensions of the original, making the result untemperable was another big plus. ( You could not move/redraw walls for example on the copy)

I’ve printed a photo on transparant sheets to experiment with.
(Next time, i’ll take a larger size, and fix the contrast.

The most interesting ones i’ve made today:

Challenged myself into programming a photo quiz

Got bored, and challenged myself …

How about a quess the picture from our photo collection??
(123580 photos .. )
So i show a random picture, and when i press ESC it will show some information about the picture …
Quess the year and the event

Well i gave myself 15 minutes to program something ..

I was watching a tv show meanwhile .. but i managed to come up with this …

This script is showing a picture, when you press ESC it wil show some details.After that it will select another random picture.

Improvements : reading tags and other metadata from my photo database, to give more information.

#!/bin/bash
while true ; do
shuf -n 1 allpix > /tmp/randompix
pix=$(cat /tmp/randompix | cut -f2 -d\> | cut -f1 -d\<)
dir=$(cat /tmp/randompix | cut -f4 -d\> | cut -f1 -d\< |cut -f3 -d\; | sed 's#\\#/#g')
displaypath=/mnt/${dir}/${pix}
info=$(cat /tmp/randompix | rev | cut -f-4 -d\\ | rev | cut -f1 -d \<)
convert -background black -fill white  -size 1920x1080 -pointsize 24 label:"$info" info.jpg
pqiv -i --fullscreen "$displaypath"
pqiv -i --fullscreen info.jpg
done

Which gave me … this

Welllll .. it toke 20 minutes .. so i lost
(Must have been the wine)

Acdsee quick negative converting

Using a setup like this, i could digitize these negatives quite quickly.
But they’re are all beige. (And negative ofcourse)
I made a tutorial for people who need to digitize and convert non-raw black-white, color negatives, and negatives on these kind of beige film using windows.

I’m using a D7100 Nikon with a 70mm lens. The lightbox is a Haiser Slimlite Plano.

Setup your camera, focus one time, and put it on manual.
Cut from a piece of black paper a frame in which you keep the negatives.
(Taking a picture without, can result in weird inconsistent lighting, and will have effect on auto-leveling in Acdsee)

Take the pictures and move them to your machine.

Openup acdsee, select a picture and press CTRL-E

Crop and rotate first, else it wil have a effect on leveling!

Next press White Balance and select auto

After that press Special Effect and Negative

Still not okay

Next press Auto Levels .. and presto (Move the slider to your liking ..)
Note! .. changing the order of Cropping, White Balance, Effect and Auto Levels will affect the result!

This is probably all scriptable in Linux, but this is a Windows howto.

For conversions on linux i use Freds ImageMagick scripts:
( http://www.fmwconcepts.com/imagemagick/index.php )
autolevel autowhite neg2pos negative2positive and autotone

Photoshoots again

Today we did a photoshoot again. (Arja needed some pictures again). Nice to have some practice again.
I’ve been setting up a little studio in our shed. i’ve got a nice interchangeable background with multiple colors/fabrics. (White/Black/Chromagreen/Blue cloth)
I also installed the semi professional lights below.

Nice lightweight stands, with a lightbox, and a remote trigger system.
It has two modes. Flash and permanent lighting. It’s a nice piece of equipment, don’t get me wrong. But lately i’ve been using this!

These are led panels, meant for installing in suspended ceilings. I’ve got these from a friend.

I like these because they are white, and the other one a little yellow. And produce a lot of light!
Unlike the permanent lighting mode of the FalconEyes. (When using the permanent lighting mode you see what the lighting wil be. Unlike using a flash. (Which is also great, when going for other photographs)

When using a reflector like below, you can see the effect instantly.

We used the golden part to get a little warmth into the pictures.

I’m using my big painting-easel to hold the led panels. I just placed the picture holder upside down.

Those led panels are cheap, and you can get them in 3000k,4000k and 6000k (Kelvin) those three i’ve seen in online shops doing a quick search.

The rest of my equipment is for another post.

  • Nikon D750
  • Nikon D7100
  • Lenses: 60mm/35mm/200mm and 300mm

VR 360 180 3D

First tests with a Vuze Xr. (unedited unoptimized)

It can record 360 movies or take 360 degrees pictures.
Besides that it can do stereoscopic recording for using in VR glasses. (2D 180 tested in a oculus vr set)
In the past i made those 360 degrees pictures manually using Hugin for example. Also stereoscopic images, using two pictures.
But it was a lot of work, and most of the times the quality wasn’t worth it.

Another test i did recently was
https://www.henriaanstoot.nl/2022/01/02/ipcam-sphere-panorama/

Soon more

Photo timelapse slider fixed

I got my DIY timelapse slider out of storage, and notished it wasn’t working any more.
I’t was a quick and dirty build, using minimal components and could be build with minimal effort.
We could not take a lot of stuff with us to New Zealand.
Camera and powerbanks, those we always take with us. So i only needed:

  • Raspberry Pi
  • Steppermotor and a plastic sheet where it was mounted on (using tiewraps you can undo)
  • Timingbeld
  • Two metal feet it was mounted on

It had to be build strong enough to hold a Nikon 750, and didn’t get out of balance when moving the camera on two metal tubes i bought in NZ.

The RPI would not start anymore, just a red power smd led. SO it didn’t boot.
Taking the mini sdcard out of the raspberry trying to put it in my cardreader .. note the trying part.
The damn thing broke into two parts, never seen anything like it.
Damn, did i backup the latest version? No, i used my mobile and wifi in NZ to modify the scripts.
Well .. “We can rebuild him, we have the technology”

How does it work?

Stepper motors move my camera over two metal rods, with ball bearing wheels.
The raspberry controls my nikon using a usb cable. Mounted on the raspberry is a steppermotor hat (adafruit) which can control DC motors and stepper motors. ( In this project i used only one stepper )
The stepper motor carries the platform containing itself, a raspberry and my nikon over the “rails”
Two switches on each side sends a signal to the program to stop.
All timing are set via the Webgui.

Steps

  • At reboot, python script wil be started
  • Moving platform to the left, until switch detects the edge
  • Waiting for in structions
  • Entering for example timer 30, speed 10 and r
  • Platform wil move distance 10 to the right
  • Wait 30 seconds
  • Grab a picture
  • And loops until end of rod reached, then it wil move to left again.

Notes:

  • Gphoto works with other camera’s also
  • When placing the camera on the platform, focus once. Disable autofocus, also put your camera in manual mode, setting Apeture, ISO and shutterspeed same as your test photo. (bear in mind: when doing sundown shots maybe start with a little light over compensation)

Below old 2018 version

New Sdcard. Format, put lite on this

# install gphoto2
apt-get install photo2
# connect nikon with usb, capture test with
gphoto2 --capture-image-and-download --interval 5
# Next steppers
apt-get install python3-pip
pip3 install adafruit-circuitpython-motorkit
# enable I2C
raspi-config -> enable i2c
reboot

# Test python script
import time
import board
from adafruit_motorkit import MotorKit

kit = MotorKit(i2c=board.I2C())

for i in range(100):
    kit.stepper1.onestep()
    time.sleep(0.01)

# Create a API
apt-get install python3-flask python3-flaskext.wtf

I’m using my phone in Hotspot mode, Timelapser will connect to my phone.
Open a browser and enter : http://<ip of timelapser>:8080/form

timer in seconds to shoot pictures
speed is the movement on the rail
go = r(ight) or l(eft)
go + timer 0, move until you reach the end (switch detect)

Todo: Need to change CSS to mobile responsive gui .. like my quizzer

import time
import subprocess
from flask import Flask, jsonify
from multiprocessing import Process, Value
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, BooleanField, SubmitField, TextAreaField
from wtforms.validators import DataRequired
from flask import Flask, render_template, request
from flask import render_template
import board
from adafruit_motor import stepper
from adafruit_motorkit import MotorKit
import RPi.GPIO as GPIO
import os; myenv = os.environ.copy(); myenv["LANG"] = "C"


# NOTE: 
# timer = seconds between shots
# speed = distance stepper travel

# Using gpio pins to detect max left/right with switches
# BCM Numbering
GPIO.setmode(GPIO.BCM)
# pullup to 17 &amp; 18
GPIO.setup(17, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(18, GPIO.IN, pull_up_down=GPIO.PUD_UP)

# Stepper HAT is i2c
kit = MotorKit(i2c=board.I2C())

kit.stepper1.release()

global timer
global speed
timer=0
speed=0
go="nix"

app = Flask(__name__)
app.config['SECRET_KEY'] = 'you-will-never-guess'

class FormForm(FlaskForm):
    timer = StringField('timer', validators=[DataRequired()])
    speed = StringField('speed', validators=[DataRequired()])
    go = StringField('go', validators=[DataRequired()])
    submit = SubmitField('Send control')

# Make below in something like : nikon record .. slowly 10s to the right and recording stop?
# Or bounch left/right using gpio sensors
@app.route("/control/&lt;time&gt;/&lt;speed&gt;")
def action(number, message):
 
    time.sleep(1)

# Print form on: http://&lt;IP&gt;:8080/form = start page 
@app.route("/form")
def form():
    form = FormForm()
    return render_template('web.html', title='Web slide control', form=form)


# process form
@app.route('/data', methods = ['POST', 'GET'])
def data():
    if request.method == 'GET':
        return "The URL /data is accessed directly. Try going to '/form' to submit form"
    if request.method == 'POST':
         
        timer = request.form['timer']
        speed = request.form['speed']
        go = request.form['go']
        timer = int(timer)
        speed = int(speed)
        if timer == 0:
            print("Turn off")
            p = Process(target=record_loop, args=(False,speed,timer,go))
            p.start() 
            if str(go) == "l":
               while GPIO.input(17) == True:
                kit.stepper1.onestep(direction=stepper.FORWARD)
                time.sleep(0.01)
            if str(go) == "r":
               while GPIO.input(18) == True:
                kit.stepper1.onestep(direction=stepper.BACKWARD, style=stepper.DOUBLE)
                time.sleep(0.01)

        else: 
            print("Turn on")
            p = Process(target=record_loop, args=(True,speed,timer,go))
            p.start()  
# print form again
        form = FormForm()
        return render_template('web.html', title='Web slide control', form=form)


# main loop, controls stepper and camera
def record_loop(loop_on,myspeed,mytimer,mygo):
   while True:
      if loop_on == True:
# test if switch hit yet, else move
         print('timer' + str(mytimer))
         print('speed' + str(myspeed))
         time.sleep(2)
         if str(mygo) == "l":
             if GPIO.input(17):
                print("Pin 17 is HIGH")
                for i in range(myspeed):
                        kit.stepper1.onestep(direction=stepper.FORWARD, style=stepper.DOUBLE)
                        time.sleep(0.01)
                kit.stepper1.release()
             else:
                print("Pin 17 is LOW")
         if str(mygo) == "r":
             if GPIO.input(18):
                print("Pin 18 is HIGH")
                for i in range(myspeed):
                        kit.stepper1.onestep(direction=stepper.BACKWARD, style=stepper.DOUBLE)
                        time.sleep(0.01)
                kit.stepper1.release()
             else:
                print("Pin 18 is LOW")
         time.sleep(mytimer)
         subprocess.run(['/root/mycapture'])
         subprocess.Popen([
        "gphoto2",
        "--capture-image"],stdout=subprocess.PIPE)

# Main loop
if __name__ == "__main__":
   while GPIO.input(17) == True:
         kit.stepper1.onestep(direction=stepper.FORWARD, style=stepper.DOUBLE)
         time.sleep(0.01)
   kit.stepper1.release()
   p = Process(target=record_loop, args=(False,0,0,go))
   p.start()  
   app.run(host='0.0.0.0', port=8080, debug=False)
   p.join()
Other files
cat templates/web.html 
{% block content %}
    <h1>Slide control</h1>
	    <form action="/data" method = "POST">

        {{ form.hidden_tag() }}
        <p>
            {{ form.timer.label }}<br>
            {{ form.timer(size=32) }}
        </p>
        <p>
            {{ form.speed.label }}<br>
            {{ form.speed(size=32) }}
        </p>
        <p>
            {{ form.speed.go }}<br>
            {{ form.go(size=32) }}
        </p>
        <p>{{ form.submit() }}</p>
    </form>
{% endblock %}

gphoto running from cron/python is a b*tch, had to rewrite subprocess and running from screen

Start screen @reboot, just crontab -e
# crontab entry
@reboot screen -dmS slide /usr/bin/python3 /root/control.py
RPI with Stepper Hat and two Limit switches and a stepper motor
Datasheet showing me which colors are what


Photo manager addition using ML!

A few years ago i wrote a photo manager .. again .. ( see post about my first previous photo manager )
It is a web gui to find photos in my huge photo archive.
I manually added 190k tags to 120k photos in 20+ years.

I thought wouldn’t it be nice if i can generate additional metadata using Machine Learning. A few years ago i did some testing and followed a podcast and free course about machine learning.

So today i started to implement a addition to my gui. Machine recognition tags!

It already kinda works.

Things to do :

  • Make it a background job, my fileserver doesn’t run Tensorflow on a GPU, so it is slooow
  • Embed in existing GUI and stats
  • Design a editor to remove wrong tags

Below a part of ML images

Command to get a thumbnail sheet with only directory names:

montage -verbose -units PixelsPerInch -density 300 -tile 7x6 -label "%d" -font Arial -pointsize 6 -background "#FFFFFF" -fill "black" -define jpeg:size=253x154 -geometry 253x154+2+2 -auto-orient */*.JPG -title "ML Thumbs" thumbsheet.jpg

Maybe, i can use debug output like below.

['lakeside, lakeshore (score = 0.47934)', 'seashore, coast, seacoast, sea-coast (score = 0.11385)', 'sandbar, sand bar (score = 0.08822)', 'breakwater, groin, groyne, mole, bulwark, seawall, jetty (score = 0.06281)', 'valley, vale (score = 0.01790)', '']