Przeglądając pudełko z „archiwalnymi sprzętami” trafiłem na 2 „dość archaiczne” (rozdzielczość 640×480) kamerki internetowe (USB), i tak sobie pomyślałem, że jak mają leżeć tak bezproduktywnie, to lepiej je do jakiejś pracy zaprząc, zwłaszcza że w duecie z Raspberry Pi można z tego zrobić prosty system monitorujący (choć patrząc na jakoś tych kamerek to raczej sztuka dla sztuki, niż realna potrzeba, ale może Wy w swoim pudełku znajdziecie macie lepsze ;-)).

Raspberry Pi i (web)kamera USB

Kwestia, nad którą chwilę się zastanawiałem to sposób działania takiego systemu – stały podgląd „na żywo”, za pomocą dedykowanej strony internetowej, lub zapis pojedynczych zdjęć w zdefiniowanych odstępach czasu.

Jeśli interesuje Was stały podgląd „na żywo”, to zachęcam do zainteresowania się programem „motion”. Ja zdecydowałem się na pojedyncze zdjęcia, do czego wykorzystam program „fswebcam”, który jest nie tylko bardzo prosty w obsłudze, ale i pozwala na wykorzystanie go w różnych skryptach.

Zgodna z Raspberry Pi kamerka USB

Po podłączeniu obu kamerek postanowiłem sprawdzić czy i jak zgłaszają się w systemie:

sudo lsusb

Jak widać obie kamerki są widoczne w systemie, a to już połowa sukcesu:

Bus 001 Device 008: ID 05a9:a518 OmniVision Technologies, Inc. D-Link DSB-C310 Webcam
[...]
Bus 009 Device 008: ID 0c45:6128 Microdia PC Camera (SN9C325 + OM6802)

Przy czym szybkie testy wykazały, że tylko druga kamerka działa, i zgłasza się w systemie:

RPi01: ls /dev/video0
/dev/video0

Może się zdarzyć, że choć kamerka będzie widoczna na liście urządzeń USB, to podczas próby skorzystania z niej system zwróci komunikat w stylu:

ls: cannot access /dev/video0: No such file or directory

Zanim zrezygnujecie polecam – z podłączoną kamerką – zrobić aktualizację systemu LINK, po czym zrestartować system.

fswebcam – Small and simple webcam software for *nix

Gdy kamerka jest widoczna w systemie, a do tego mamy dostęp do „urządzenia” (/dev/video0) możemy przystąpić do instalacji programu „fswebcam”:

sudo apt-get install fswebcam

Bezpośrednio po instalacji, bez żadnej konfiguracji możemy przystąpić do zapisywania zdjęć z kamerki:

fswebcam -r image.jpg

Jest to oczywiście podstawowe polecenie, i zdecydowanie warto rozbudować je o dodatkowe parametry, np.:

sudo fswebcam -r 640x480 --no-banner image.jpg

Powyższa komenda zapisze obraz „image.jpg” w aktualnym katalogu, o rozdzielczości 640×480 px (akurat tyle obsługuje podłączona do testów kamerka), bez dolnego paska z datą i godziną.

Webinsider.pl: sudo fswebcam -r 640x480 --no-banner image02.jpg
--- Opening /dev/video0...
Trying source module v4l2...
/dev/video0 opened.
No input was specified, using the first.
--- Capturing frame...
Captured frame in 0.00 seconds.
--- Processing captured image...
Disabling banner.
Writing JPEG image to 'image02.jpg'.

A tutaj pełna lista dostępnych aktualnie parametrów i ustawień:

 -?, --help Display this help page and exit.
 -c, --config <filename> Load configuration from file.
 -q, --quiet Hides all messages except for errors.
 -v, --verbose Displays extra messages while capturing
 --version Displays the version and exits.
 -l, --loop <seconds> Run in loop mode.
 -b, --background Run in the background.
 -o, --output <filename> Output the log to a file.
 -d, --device <name> Sets the source to use.
 -i, --input <number/name> Selects the input to use.
 -t, --tuner <number> Selects the tuner to use.
 -f, --frequency <number> Selects the frequency use.
 -p, --palette <name> Selects the palette format to use.
 -D, --delay <number> Sets the pre-capture delay time. (seconds)
 -r, --resolution <size> Sets the capture resolution.
 --fps <framerate> Sets the capture frame rate.
 -F, --frames <number> Sets the number of frames to capture.
 -S, --skip <number> Sets the number of frames to skip.
 --dumpframe <filename> Dump a raw frame to file.
 -s, --set <name>=<value> Sets a control value.
 --revert Restores original captured image.
 --flip <direction> Flips the image. (h, v)
 --crop <size>[,<offset>] Crop a part of the image.
 --scale <size> Scales the image.
 --rotate <angle> Rotates the image in right angles.
 --deinterlace Reduces interlace artifacts.
 --invert Inverts the images colours.
 --greyscale Removes colour from the image.
 --swapchannels <c1c2> Swap channels c1 and c2.
 --no-banner Hides the banner.
 --top-banner Puts the banner at the top.
 --bottom-banner Puts the banner at the bottom. (Default)
 --banner-colour <colour> Sets the banner colour. (#AARRGGBB)
 --line-colour <colour> Sets the banner line colour.
 --text-colour <colour> Sets the text colour.
 --font <[name][:size]> Sets the font and/or size.
 --no-shadow Disables the text shadow.
 --shadow Enables the text shadow.
 --title <text> Sets the main title. (top left)
 --no-title Clears the main title.
 --subtitle <text> Sets the sub-title. (bottom left)
 --no-subtitle Clears the sub-title.
 --timestamp <format> Sets the timestamp format. (top right)
 --no-timestamp Clears the timestamp.
 --gmt Use GMT instead of local timezone.
 --info <text> Sets the info text. (bottom right)
 --no-info Clears the info text.
 --underlay <PNG image> Sets the underlay image.
 --no-underlay Clears the underlay.
 --overlay <PNG image> Sets the overlay image.
 --no-overlay Clears the overlay.
 --jpeg <factor> Outputs a JPEG image. (-1, 0 - 95)
 --png <factor> Outputs a PNG image. (-1, 0 - 10)
 --save <filename> Save image to file.
 --exec <command> Execute a command and wait for it to complete.

Wykorzystanie w skryptach do stałego monitoringu (timelapse)

Jak wspomniałem na początku, zdecydowałem się na ten program, jak i typ zapisu, by móc zrzucać obraz z kamerki na żądanie, tylko wtedy gdy będzie taka potrzeba.

W tym celu przygotowałem sobie skrypt:

/usr/local/bin/foto

O takiej zawartości:

#!/bin/bash

TIMESTAMP=$(date +"%Y-%m-%d_%H-%M-%S")
sudo fswebcam -r 640x480 --no-banner -q /ścieżka/do/katalogu/webcam/$TIMESTAMP.jpg

exit

Dzięki czemu każdorazowe wpisanie w konsoli polecenia „foto” zapisze obraz z kamerki w zdefiniowanym katalogu, pod aktualną datą (rok-miesiąc-dzień_godz-min-sek.jpg).

W duecie z harmonogramem zadań (CRON)

Oprócz tego w prosty sposób, za pomocą systemowego harmonogramu zadań (CRON) można ustawić, by zdjęcie było wykonywane automatycznie, w wybranych odstępach czasu, np. co 5 minut:

*/5 * * * * root /usr/local/bin/foto

Broken pipe

Nie wiem czy to kwestia konkretnego modelu lub egzemplarza kamerki czy też tego, że do testów została ona podłączona do Raspberry Pi bezpośrednio, z pominięciem aktywnego HUBa USB, który mógłby kamerkę dodatkowo zasilać, ale co jakiś czas pojawiał się błąd, który blokował dalsze zrzuty z kamerki:

Error starting stream.
VIDIOC_STREAMON: Broken pipe

Najprostsze rozwiązanie – restart Raspberry Pi lub chwilowe wypięcie kamerki z portu USB. Działa, ale nie jest to najlepsze rozwiązanie…

Reset (urządzenia) USB

Chwile poszukałem jakiegoś prostego sposobu, jakby móc w takiej sytuacji w prosty i automatyczny sposób za pomocą jakiegoś polecenia zresetować konkretny port USB/konkretne urządzenie w systemie, ale jeśli już na coś trafiłem, to działało to z różną skutecznością, a tu chodzi o dzianie nie tylko skuteczne, ale i pewne.

Z pomocą przyszedł program napisany w języku C, który można pobrać na Malinę, skompilować i w razie potrzeby uruchomić:

sudo wget -c --no-check-certificate https://gist.githubusercontent.com/x2q/5124616/raw/bf21dbda4a67de2c2d15d6c66b1e1bd0b1db7e1b/usbreset.c -O /usr/local/bin/usbreset.c
sudo cc /usr/local/bin/usbreset.c -o /usr/local/bin/usbreset
sudo chmod +x /usr/local/bin/usbreset

Obsługa jest banalnie prosta, wystarczy znać… adres kamerki:

Webinsider: lsusb
Bus 001 Device 007: ID 0c45:6128 Microdia PC Camera (SN9C325 + OM6802)

W moim przypadku jest to „001” i „007”, a więc polecenie resetujące to:

Webinsider: sudo usbreset /dev/bus/usb/001/007
Resetting USB device /dev/bus/usb/001/007
Reset successful

Automatyzujemy całość

Ale skoro zależy nam na pełnej automatyzacji działania, to połączymy całość w jeden skrypt, a do tego dodamy powiadomienia na adres e-mail w przypadku błędu:

#!/bin/bash

DEV_VIDEO=/dev/video0
DEV_VIDEO_CRC=$(sudo ls /dev/video0)
[email protected]
FOTO_DIR=/ścieżka/do/katalogu/webcam

if [ "$DEV_VIDEO_CRC" == "$DEV_VIDEO" ]
then
	echo "OK, robie foto"
	DATE=$(date +"%Y-%m-%d_%H-%M-%S")
	sudo fswebcam -r 640x480 -d $DEV_VIDEO --no-banner -q $FOTO_DIR/$DATE.jpg
	
	echo "Sprawdzam czy istnieje plik foto"
	
	if [ ! -e $FOTO_DIR/$DATE.jpg ]
	then
		echo "Brak pliku foto, wysyłam e-mail"
		echo -e "Subject: FOTO error\r\n\r\nSkrypt wykrył problem z zapisem pliku" | msmtp -t $EMAIL -d
	else
		echo "Plik foto OK"
	fi
else
	echo "Brak "$DEV_VIDEO", wysyłam e-mail"
	echo -e "Subject: FOTO error\r\n\r\nSkrypt wykrył problem ze sprzętem" | msmtp -t $EMAIL -d 
	
	echo "Naprawiam:"
	KEYWORD=Camera
	LSUSB=$(lsusb | grep $KEYWORD)
	DEVICE="/dev/bus/usb/"$(echo $LSUSB | grep "Camera " | awk '{print $2;}')"/"$(echo $LSUSB | grep "Camera " | awk '{print $4;}' | head -c 3)
	echo "Naprawiam: "$LSUSB
	echo "Resetuje: "$DEVICE
	sudo /usr/local/bin/usbreset $DEVICE
fi

exit

Skrypt sprawdza czy w ogóle dostępne jest urządzenie DEV_VIDEO i jeśli wszystko jest OK, to przechodzi do zdjęcia i na tym kończy swoją pracę.

W przypadku gdy wykryje problem z urządzeniem DEV_VIDEO wykona reset urządzenia, co przynajmniej w moim przypadku skutecznie naprawia problem „pękniętej rury”. Dodatkowo skrypt sprawdza, czy po zapisaniu pliku jest on dostępny – np. jakby zasób dyskowy z jakichś przyczyn przestał być dostępny (przynajmniej dla skryptu).

(!) Zgłoś błąd na stronie
Pomogłem? To może postawisz mi wirtualną kawę?
LUTy dla D-Cinelike (DJI Mini 3 Pro, DJI Avata, OSMO Pocket) od MiniFly
Wdrożenie Omnibusa w sklepie na WooCommerce
Jak (legalnie) latać dronem w Kategorii Otwartej
Patryk