From 0bf7fb9d987eabd814f1e5dfc5bba11700c61ee7 Mon Sep 17 00:00:00 2001 From: ABelliqueux Date: Sun, 15 Dec 2024 12:28:40 +0100 Subject: [PATCH] Add pi camera v3 support --- frame_opencv.py | 185 ++++++++++++++++++++++++++++++++++------------- readme.md | 4 +- requirements.txt | 2 +- 3 files changed, 137 insertions(+), 54 deletions(-) diff --git a/frame_opencv.py b/frame_opencv.py index 43558f3..05309d5 100644 --- a/frame_opencv.py +++ b/frame_opencv.py @@ -1,3 +1,4 @@ +#!/bin/env python import cv2 import gettext from itertools import count @@ -14,6 +15,10 @@ 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'] @@ -25,6 +30,7 @@ _ = 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', @@ -65,13 +71,21 @@ for location in config_locations: config_found_msg = _("Found configuration file in {}").format(os.path.expanduser(location)) print(config_found_msg) -camera_current_settings = { +if project_settings['cam_is_showmewebcam']: + 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: + 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']), + } def apply_cam_setting(cam_settings:dict, to_set:list=None): @@ -113,7 +127,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", (screen_w/32)) + font = ImageFont.truetype("Tuffy_Bold.ttf", int(screen_w/32)) lines = text.split('\n') longest_line = lines[0] for line in lines: @@ -435,12 +449,37 @@ def main(args): global onionskin, liveview_only, playback, loop_playback, playhead, index, img_list, first_playback, camera_current_settings - 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']) + 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 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'] + }) + # ~ cam.stop() frame = get_onionskin_frame(savepath, index) @@ -480,10 +519,13 @@ def main(args): if liveview_only: # ~ onionskin = False - ret, overlay = cam.read() - if not ret: - print(_("Failed to grab frame.")) - break + 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") # Resize preview overlay = cv2.resize(overlay, (project_settings['screen_w'], project_settings['screen_h'])) cv2.imshow("StopiCV", overlay) @@ -491,7 +533,13 @@ def main(args): # ~ onionskin = True if onionskin: - ret, overlay = cam.read() + 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") og_frame = overlay.copy() # Resize preview overlay = cv2.resize(overlay, (project_settings['screen_w'], project_settings['screen_h'])) @@ -499,95 +547,115 @@ 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) - # TODO : Show liveview only - # Key l / 5 - if (k%256 == 108) or (k%256 == 53): + # Key l / kp 5 + if (k%256 == 108) or (k%256 == 53) or (k%256 == 181): + print(_("Liveview only")) # Toggle liveview liveview_only = not liveview_only onionskin = not onionskin - # Key o / 9 - if (k%256 == 111) or (k%256 == 47): + # Key o / kp slash + elif (k%256 == 111) or (k%256 == 47) or (k%256 == 175): + print(_("Onionskin toggle")) # Toggle onionskin onionskin = not onionskin liveview_only = False # Key w / 7 - cycle wb - if (k%256 == 119) or (k%256 == 55): + elif (k%256 == 119) or (k%256 == 55) or (k%256 == 183): + print(_("White balance mode")) 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 - if (k%256 == 120) or (k%256 == 49): + elif (k%256 == 120) or (k%256 == 49) or (k%256 == 177): + print(_("Exp. mode")) 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': 0}) + else: + cam.set_controls({'AeEnable': 1}) + cam.set_controls({"AeExposureMode": camera_current_settings['auto_exposure']['value']}) # Key f / 3 - flip image - if (k%256 == 102) or (k%256 == 51): + elif (k%256 == 102) or (k%256 == 51) or (k%256 == 179): + print(_("Flip image")) camera_current_settings = apply_cam_setting(camera_current_settings, ['vertical_flip','horizontal_flip']) - # Key up - elif (k%256 == 82) or (k%256 == 56): + 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")) if len(img_list): if playback: playback = False index, frame = last_frame(index) - # Key down - elif (k%256 == 84) or (k%256 == 50): + # Key down , kp 2 + elif (k%256 == 84) or (k%256 == 50) or (k%256 == 178): + print(_("First frame")) if len(img_list): if playback: playback = False index, frame = first_frame(index) - # Key left - elif (k%256 == 81) or (k%256 == 52): + # Key left, kp 4 + elif (k%256 == 81) or (k%256 == 52) or (k%256 == 180): + print(_("Prev. frame")) # Displau previous frame if len(img_list): if playback: playback = False index, frame = previous_frame(index) - # Key right - elif (k%256 == 83) or (k%256 == 54): + # Key right, kp 6 + elif (k%256 == 83) or (k%256 == 54) or (k%256 == 182): + print(_("Next frame")) # 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): + elif (k%256 == 114) or (k%256 == 57) or (k%256 == 185) : 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): + elif (k%256 == 101) or (k%256 == 42) or (k%256 == 170) : print(_("Export")) ffmpeg_process = export_animation(input_filename, export_filename) - # Key Return - elif (k%256 == 13): + # Key Return, kp return + elif (k%256 == 13) or (k%256 == 141) : 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): + elif (k%256 == 8) or (k%256 == 45) or (k == 255) or (k%256 == 173) : # Remove frame print(_("Remove frame")) img_list, index, frame = remove_frame(img_list, index) - # Quit app - elif k%256 == 27: - # ESC pressed - print(_("Escape hit, exiting...")) - break - elif ctrlc_pressed: - print(_("Ctrl-C hit, exiting...")) - break - elif cv2.getWindowProperty("StopiCV", cv2.WND_PROP_VISIBLE) < 1: - print(_("Window was closed, exiting...")) - break # Take pic # SPACE or numpad 0 pressed - elif (k%256 == 32) or (k%256 == 48): + 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) cv2.imwrite(img_path, og_frame) @@ -597,6 +665,18 @@ def main(args): else: index += 1 frame = get_onionskin_frame(savepath, index) + # Quit app + elif k%256 == 27: + # ESC pressed + print(_("Escape hit, exiting...")) + break + elif ctrlc_pressed: + print(_("Ctrl-C hit, exiting...")) + break + elif cv2.getWindowProperty("StopiCV", cv2.WND_PROP_AUTOSIZE) == -1: + print(_("Window was closed, exiting...")) + # ~ pass + break # REMOVE : Debug print keycode elif k==-1: # normally -1 returned,so don't print it continue @@ -619,7 +699,10 @@ def main(args): except: print(_("Terminating running process...")) ffmpeg_process.terminate() - cam.release() + if not project_settings["cam_is_picam"]: + cam.release() + else: + cam.close() cv2.destroyAllWindows() cv2.namedWindow("StopiCV", cv2.WINDOW_GUI_NORMAL) diff --git a/readme.md b/readme.md index 575e834..d9367a4 100644 --- a/readme.md +++ b/readme.md @@ -28,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 - ``` + 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 + ``` - (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` diff --git a/requirements.txt b/requirements.txt index 0631529..ec9f528 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ Send2Trash -opencv-python numpy pyserial pillow +opencv-python