Compare commits

..

No commits in common. "picamera_support" and "master" have entirely different histories.

4 changed files with 68 additions and 204 deletions

View File

@ -1,23 +1,21 @@
[DEFAULT]
cam_is_picam = true
cam_is_showmewebcam = false
cam_is_showmewebcam = true
use_date_for_folder = false
file_extension = 'jpg'
jpg_quality = 88
file_extension = 'png'
projects_folder = ''
onion_skin_onstartup = true
onionskin_alpha_default = 0.5
fullscreen_bool = true
screen_w = 1440
screen_h = 900
screen_w = 1920
screen_h = 1080
framerate = 16
ffmpeg_path = '/usr/bin/ffmpeg'
export_options = 'scale=1920:-1,crop=1920:1080'
[CAMERA]
cam_w = 1920
cam_h = 1080
vflip = 0
hflip = 0
cam_w = 1600
cam_h = 900
vflip = 1
hflip = 1
auto_exposure = 1
white_balance_auto_preset = 1
white_balance_auto_preset = 2
video_bitrate=25000000

View File

@ -1,4 +1,3 @@
#!/bin/env python
import cv2
import gettext
from itertools import count
@ -15,10 +14,6 @@ import tomllib
import numpy as np
import serialutils
# Run from SSH
if not os.getenv('DISPLAY'):
os.putenv('DISPLAY', ':0')
running_from_folder = os.path.realpath(__file__)
alphabet = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z']
@ -30,11 +25,9 @@ _ = gettext.translation('template', localedir='locales', languages=[LOCALE]).get
# Config
# defaults
project_settings_defaults = {
'cam_is_picam': True,
'cam_is_showmewebcam': False,
'use_date_for_folder': False,
'file_extension':'png',
'jpg_quality':90,
'projects_folder': '',
'onion_skin_onstartup' : False,
'onionskin_alpha_default' : 0.4,
@ -72,22 +65,13 @@ for location in config_locations:
config_found_msg = _("Found configuration file in {}").format(os.path.expanduser(location))
print(config_found_msg)
if project_settings['cam_is_showmewebcam']:
camera_current_settings = {
camera_current_settings = {
'auto_exposure': dict(min=0, max=1, default=camera_settings['auto_exposure'], value=camera_settings['auto_exposure']),
'white_balance_auto_preset': dict(min=0, max=9, default=camera_settings['white_balance_auto_preset'], value=camera_settings['white_balance_auto_preset']),
'horizontal_flip': dict(min=0, max=1, default=camera_settings['hflip'], value=camera_settings['hflip']),
'vertical_flip': dict(min=0, max=1, default=camera_settings['vflip'], value=camera_settings['vflip']),
'video_bitrate': dict(min=25000000, max=25000000, default=camera_settings['video_bitrate'], value=camera_settings['video_bitrate']),
}
else: # cam is picam
camera_current_settings = {
'auto_exposure': dict(min=0, max=4, default=camera_settings['auto_exposure'], value=camera_settings['auto_exposure']),
'white_balance_auto_preset': dict(min=0, max=7, default=camera_settings['white_balance_auto_preset'], value=camera_settings['white_balance_auto_preset']),
'horizontal_flip': dict(min=0, max=1, default=camera_settings['hflip'], value=camera_settings['hflip']),
'vertical_flip': dict(min=0, max=1, default=camera_settings['vflip'], value=camera_settings['vflip']),
'anti_flicker': dict(min=0, max=2, default=1, value=1),
}
def apply_cam_setting(cam_settings:dict, to_set:list=None):
@ -129,7 +113,7 @@ def generate_text_image(text:str, screen_w, screen_h, bullets=False):
)
text_image_draw = ImageDraw.Draw(text_image)
if text is not None:
font = ImageFont.truetype("Tuffy_Bold.ttf", int(screen_w/32))
font = ImageFont.truetype("Tuffy_Bold.ttf", (screen_w/32))
lines = text.split('\n')
longest_line = lines[0]
for line in lines:
@ -451,45 +435,12 @@ def main(args):
global onionskin, liveview_only, playback, loop_playback, playhead, index, img_list, first_playback, camera_current_settings
if not project_settings['cam_is_picam']:
if not testDevice(0):
print(_("No camera device found. Exiting..."))
return 1
cam = cv2.VideoCapture(0)
cam.set(cv2.CAP_PROP_FRAME_WIDTH, camera_settings['cam_w'])
cam.set(cv2.CAP_PROP_FRAME_HEIGHT, camera_settings['cam_h'])
else:
# Pi Cam V3 setup
from picamera2 import Picamera2
from libcamera import Transform
cam = Picamera2()
picam_config = cam.create_video_configuration(main={"format": 'RGB888',"size": (camera_settings['cam_w'], camera_settings['cam_h'])})
# ~ picam_config["transform"] = Transform(hflip=camera_settings['hflip'], vflip=camera_settings['vflip'])
picam_config["transform"] = Transform(vflip=camera_current_settings['vertical_flip']['value'],hflip=camera_current_settings['horizontal_flip']['value'])
cam.configure(picam_config)
# Autofocus, get lens position and switch to manual mode
# Set Af mode to Auto then Manual (0). Default is Continuous (2), Auto is 1
cam.set_controls({'AfMode':1})
cam.start()
cam.autofocus_cycle()
cam_lenspos = cam.capture_metadata()['LensPosition']
# Set focus, wb, exp to manual
cam.set_controls({'AfMode': 0,
'AwbEnable': 1,
'AwbMode': camera_current_settings['white_balance_auto_preset']['default'],
'AeEnable': 1,
'AeExposureMode': camera_current_settings['auto_exposure']['default'],
# Enable flicker avoidance due to mains
'AeFlickerMode': 1,
# Mains 50hz = 10000, 60hz = 8333
# ~ 'AeFlickerPeriod': 8333,
'AeFlickerPeriod': 10000,
# Format is (min, max, default) in ms
# here: (60fps, 12fps, None)
# ~ 'FrameDurationLimits':(16666,83333,None)
})
# ~ cam.stop()
if not testDevice(0):
print(_("No camera device found. Exiting..."))
return 1
cam = cv2.VideoCapture(0)
cam.set(cv2.CAP_PROP_FRAME_WIDTH, camera_settings['cam_w'])
cam.set(cv2.CAP_PROP_FRAME_HEIGHT, camera_settings['cam_h'])
frame = get_onionskin_frame(savepath, index)
@ -529,13 +480,10 @@ def main(args):
if liveview_only:
# ~ onionskin = False
if not project_settings['cam_is_picam']:
ret, overlay = cam.read()
if not ret:
print(_("Failed to grab frame."))
break
else:
overlay = cam.capture_array("main")
ret, overlay = cam.read()
if not ret:
print(_("Failed to grab frame."))
break
# Resize preview
overlay = cv2.resize(overlay, (project_settings['screen_w'], project_settings['screen_h']))
cv2.imshow("StopiCV", overlay)
@ -543,13 +491,7 @@ def main(args):
# ~ onionskin = True
if onionskin:
if not project_settings['cam_is_picam']:
ret, overlay = cam.read()
if not ret:
print(_("Failed to grab frame."))
break
else:
overlay = cam.capture_array("main")
ret, overlay = cam.read()
og_frame = overlay.copy()
# Resize preview
overlay = cv2.resize(overlay, (project_settings['screen_w'], project_settings['screen_h']))
@ -557,153 +499,81 @@ def main(args):
alpha = project_settings['onionskin_alpha_default']
beta = (1.0 - alpha)
overlay = cv2.addWeighted(frame, alpha, overlay, beta, 0)
if not ret:
print(_("Failed to grab frame."))
break
cv2.imshow("StopiCV", overlay)
if not playback and not onionskin and not liveview_only:
cv2.imshow("StopiCV", frame)
k = cv2.waitKey(1)
# Key l / kp 5
if (k%256 == 108) or (k%256 == 53) or (k%256 == 181):
print(_("Liveview only"))
# TODO : Show liveview only
# Key l / 5
if (k%256 == 108) or (k%256 == 53):
# Toggle liveview
liveview_only = not liveview_only
onionskin = not onionskin
# Key o / kp slash
elif (k%256 == 111) or (k%256 == 47) or (k%256 == 175):
print(_("Onionskin toggle"))
# Key o / 9
if (k%256 == 111) or (k%256 == 47):
# Toggle onionskin
onionskin = not onionskin
liveview_only = False
# Key w / 7 - cycle wb
elif (k%256 == 119) or (k%256 == 55) or (k%256 == 183):
print(_("White balance mode"))
if (k%256 == 119) or (k%256 == 55):
camera_current_settings = apply_cam_setting(camera_current_settings, ['white_balance_auto_preset'])
if project_settings['cam_is_picam']:
cam.set_controls({'AwbMode': camera_current_settings['white_balance_auto_preset']['value']})
# Key x / 1 - cycle exposure
elif (k%256 == 120) or (k%256 == 49) or (k%256 == 177):
print(_("Exp. mode"))
if (k%256 == 120) or (k%256 == 49):
camera_current_settings = apply_cam_setting(camera_current_settings, ['auto_exposure'])
if project_settings['cam_is_picam']:
print(camera_current_settings['auto_exposure']['value'])
if camera_current_settings['auto_exposure']['value'] == 4:
cam.set_controls({'AeEnable': 1})
else:
cam.set_controls({'AeEnable': 0})
cam.set_controls({"AeExposureMode": camera_current_settings['auto_exposure']['value']})
# Key f / 3 - flip image
elif (k%256 == 102) or (k%256 == 51) or (k%256 == 179):
print(_("Flip image"))
if (k%256 == 102) or (k%256 == 51):
camera_current_settings = apply_cam_setting(camera_current_settings, ['vertical_flip','horizontal_flip'])
if project_settings['cam_is_picam']:
cam.stop()
picam_config["transform"] = Transform(vflip=camera_current_settings['vertical_flip']['value'],hflip=camera_current_settings['horizontal_flip']['value'])
cam.configure(picam_config)
cam.start()
# Key up, kp 8
elif (k%256 == 82) or (k%256 == 56) or (k%256 == 184):
print(_("Last frame"))
# Key up
elif (k%256 == 82) or (k%256 == 56):
if len(img_list):
if playback:
playback = False
index, frame = last_frame(index)
# Key down , kp 2
elif (k%256 == 84) or (k%256 == 50) or (k%256 == 178):
print(_("First frame"))
# Key down
elif (k%256 == 84) or (k%256 == 50):
if len(img_list):
if playback:
playback = False
index, frame = first_frame(index)
# Key left, kp 4
elif (k%256 == 81) or (k%256 == 52) or (k%256 == 180):
print(_("Prev. frame"))
# Key left
elif (k%256 == 81) or (k%256 == 52):
# Displau previous frame
if len(img_list):
if playback:
playback = False
index, frame = previous_frame(index)
# Key right, kp 6
elif (k%256 == 83) or (k%256 == 54) or (k%256 == 182):
print(_("Next frame"))
# Key right
elif (k%256 == 83) or (k%256 == 54):
# Displau next frame
if len(img_list):
if playback:
playback = False
index, frame = next_frame(index)
# Key r / keypad 9 - reset wb,exp
elif (k%256 == 114) or (k%256 == 57) or (k%256 == 185) :
elif (k%256 == 114) or (k%256 == 57):
print(_("Reset camera settings"))
camera_current_settings = apply_cam_setting(camera_current_settings)
if project_settings['cam_is_picam']:
if camera_current_settings['auto_exposure']['default'] == 4:
cam.set_controls({'AeEnable': 0})
else:
cam.set_controls({'AeEnable': 1})
cam.set_controls({"AeExposureMode": camera_current_settings['auto_exposure']['default']})
cam.set_controls({'AwbMode': camera_current_settings['white_balance_auto_preset']['default']})
cam.stop()
picam_config["transform"] = Transform(vflip=camera_current_settings['vertical_flip']['default'],hflip=camera_current_settings['horizontal_flip']['default'])
cam.configure(picam_config)
cam.start()
# Key e / keypad *
elif (k%256 == 101) or (k%256 == 42) or (k%256 == 170) :
elif (k%256 == 101) or (k%256 == 42):
print(_("Export"))
ffmpeg_process = export_animation(input_filename, export_filename)
# Key Return, kp return
elif (k%256 == 13) or (k%256 == 141) :
# Key Return
elif (k%256 == 13):
print(_("Playback"))
playhead = index
loop_playback = True
playback = not playback
# Key remove frame - backspace, del, numpad_minus
elif (k%256 == 8) or (k%256 == 45) or (k == 255) or (k%256 == 173) :
elif (k%256 == 8) or (k%256 == 45) or (k == 255):
# Remove frame
print(_("Remove frame"))
img_list, index, frame = remove_frame(img_list, index)
# TODO: replace keys with rotary encoder
# Focus +/- with a,z
elif (k%256 == 97) and project_settings['cam_is_picam']:
cam_lenspos += 0.2
# Set AfMode to Manual
cam.set_controls({'AfMode': 0, 'LensPosition': cam_lenspos})
print(_("+Lens pos: {}".format(cam_lenspos)))
elif (k%256 == 122) and project_settings['cam_is_picam']:
cam_lenspos -= 0.2
# Set AfMode to Manual
cam.set_controls({'AfMode': 0, 'LensPosition': cam_lenspos})
print(_("-Lens pos: {}".format(cam_lenspos)))
# Set anti-flicker mode with q
elif (k%256 == 113) and project_settings['cam_is_picam']:
# Set AfMode to Manual
camera_current_settings = apply_cam_setting(camera_current_settings, ['anti_flicker'])
if camera_current_settings['anti_flicker']['value'] == 0:
cam.set_controls({'AeFlickerMode': 0})
elif camera_current_settings['anti_flicker']['value'] == 1:
cam.set_controls({'AeFlickerMode': 1, 'AeFlickerPeriod':8333})
else:
cam.set_controls({'AeFlickerMode': 1, 'AeFlickerPeriod':10000})
print(camera_current_settings['anti_flicker']['value'])
# ~ elif (k%256 == 115) and project_settings['cam_is_picam']:
# ~ # Set AfMode to Manual
# ~ cam.set_controls({'AeFlickerMode': 0, 'AeFlickerPeriod': 8333})
# Take pic
# SPACE or numpad 0 pressed
elif (k%256 == 32) or (k%256 == 48) or (k%256 == 176):
print(_("Capture frame"))
img_name = return_next_frame_number(get_last_frame(savepath))
img_path = os.path.join(savepath, img_name)
if project_settings['file_extension'] == 'jpg':
cv2.imwrite(img_path, og_frame, [int(cv2.IMWRITE_JPEG_QUALITY), project_settings['jpg_quality']])
else:
cv2.imwrite(img_path, og_frame)
print(_("File {} written.").format(img_path))
if len(img_list) and (img_list[index] == '{letter}.-001.{ext}'.format(letter=project_letter, ext=project_settings['file_extension'])):
img_list[index] = img_name
else:
index += 1
frame = get_onionskin_frame(savepath, index)
# Quit app
elif k%256 == 27:
# ESC pressed
@ -712,10 +582,21 @@ def main(args):
elif ctrlc_pressed:
print(_("Ctrl-C hit, exiting..."))
break
elif cv2.getWindowProperty("StopiCV", cv2.WND_PROP_AUTOSIZE) == -1:
elif cv2.getWindowProperty("StopiCV", cv2.WND_PROP_VISIBLE) < 1:
print(_("Window was closed, exiting..."))
# ~ pass
break
# Take pic
# SPACE or numpad 0 pressed
elif (k%256 == 32) or (k%256 == 48):
img_name = return_next_frame_number(get_last_frame(savepath))
img_path = os.path.join(savepath, img_name)
cv2.imwrite(img_path, og_frame)
print(_("File {} written.").format(img_path))
if len(img_list) and (img_list[index] == '{letter}.-001.{ext}'.format(letter=project_letter, ext=project_settings['file_extension'])):
img_list[index] = img_name
else:
index += 1
frame = get_onionskin_frame(savepath, index)
# REMOVE : Debug print keycode
elif k==-1: # normally -1 returned,so don't print it
continue
@ -738,10 +619,7 @@ def main(args):
except:
print(_("Terminating running process..."))
ffmpeg_process.terminate()
if not project_settings["cam_is_picam"]:
cam.release()
else:
cam.close()
cam.release()
cv2.destroyAllWindows()
cv2.namedWindow("StopiCV", cv2.WINDOW_GUI_NORMAL)

View File

@ -1,15 +1,10 @@
# Stopi2
## Branche libcamera
**Ceci est la branche qui restaure la possibilité d'utiliser des périphériques compatibles rpi-libcamera (Modules Raspicam v1,v2 et v3).**
**En utilisant la [branche correspondante pour la télécommande picote](/arthus/picote/src/branch/picamera), vous pouvez régler la mise au point du module caméra avec un [codeur rotatif](https://fr.wikipedia.org/wiki/Codeur_rotatif).**
<a style="max-height: 300px;display: inline-block;" href="./stopi2/raw/branch/master/stopi_station.jpg"><img src="./stopi_station.jpg"/><a/>
Seconde version du script python [stopi](https://git.arthus.net/arthus/stopi) destiné à être utilisé avec une télécommande [picote](/arthus/picote/src/branch/picamera).
Seconde version du script python [stopi](https://git.arthus.net/arthus/stopi) destiné à être utilisé avec une télécommande [picote](/arthus/picote).
Cette version utilise opencv et libcamera.Elle fonctionne avec une webcam ou un module vidéo Picamera (v1,v2 ou v3).
Cette version utilise opencv et ne fonctionne pour le moment qu'avec une webcam.
Encore une fois, l'objectif est de créer un logiciel simple et minimaliste dans son interface, dont les caractéristiques sont les suivantes :
* Affichage des images en plein écran sans interface : toutes les fonctions utilisent quelques touches du clavier.
@ -22,7 +17,7 @@ Encore une fois, l'objectif est de créer un logiciel simple et minimaliste dans
## Banc de test
Ce script a été testé avec une webcam compatible V4L2, une ["showmewebcam"](https://github.com/showmewebcam/showmewebcam) à base de rpi 0 et d'un module caméra v2 (8Mp), et un ordinateur classique sous [Debian](https://debian.org) et un [RPI 4B](https://www.raspberrypi.com/products/raspberry-pi-4-model-b/) munis d'un module [Picamera V3](https://www.raspberrypi.com/products/camera-module-3/).
Ce script a été testé avec une webcam compatible V4L2, et plus précisement une ["showmewebcam"](https://github.com/showmewebcam/showmewebcam) à base de rpi 0 et d'un module caméra v2 (8Mp), et un ordinateur classique sous [Debian](https://debian.org).
Les contributions et rapports de bugs sont les bienvenus !
## Installation
@ -33,8 +28,8 @@ Dans un terminal :
1. Installer les dépendances suivantes :
```
# Avec une distribution basée sur Debian (Ubuntu, Mint...)
sudo apt install --no-install-recommends --no-install-suggests git ffmpeg python3-tk python3-pip python3-venv libtiff5-dev libtopenjp2 libopenjp2-7-dev zlib1g-dev libfreetype6-dev liblcms2-dev libwebp-dev tcl8.6-dev tk8.6-dev libharfbuzz-dev libfribidi-dev libxcb1-dev python3-tk python3-dev libopenblas-dev libatlas-base-dev libhdf5-dev libhdf5-serial-dev libatlas-base-dev libjasper-dev libqtgui4 libqt4-test
```
sudo apt install --no-install-recommends --no-install-suggests git ffmpeg python3-tk python3-pip python3-venv libtiff5-dev libtopenjp2 libopenjp2-7-dev zlib1g-dev libfreetype6-dev liblcms2-dev libwebp-dev tcl8.6-dev tk8.6-dev libharfbuzz-dev libfribidi-dev libxcb1-dev python3-tk python3-dev
```
- (Optionnel) Pour installer un environnement graphique minimal sur une [installation console](https://debian-facile.org/doc:install:installation-minimale) : `sudo apt install --no-install-recommends --no-install-suggests openbox xserver-xorg xinit pcmanfm gmrun lxterminal hsetroot unclutter plymouth plymouth-themes`
2. Cloner le dépôt dans le dossier de votre choix : `git clone https://git.arthus.net/arthus/stopi2.git`
3. Aller dans répertoire du projet : `cd stopi2`
@ -132,11 +127,4 @@ en :
Puis configurer plymouth : `sudo plymouth-set-default-theme`
Appliquer les modifs avec `sudo update-grub`.
## Raspberry Pi OS
Avec Raspberry Pi OS, il suffit d'ajouter les options suivantes dans '/boot/firmware/cmdline.txt':
`loglevel=3 vt.global_cursor_default=0 logo.nologo consoleblank=3 quiet`
``
Appliquer les modifs avec `sudo update-grub`.

View File

@ -1,5 +1,5 @@
Send2Trash
opencv-python
numpy
pyserial
pillow
opencv-python