Add pi camera v3 support
This commit is contained in:
parent
3c00380b0d
commit
0bf7fb9d98
185
frame_opencv.py
185
frame_opencv.py
|
@ -1,3 +1,4 @@
|
||||||
|
#!/bin/env python
|
||||||
import cv2
|
import cv2
|
||||||
import gettext
|
import gettext
|
||||||
from itertools import count
|
from itertools import count
|
||||||
|
@ -14,6 +15,10 @@ import tomllib
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import serialutils
|
import serialutils
|
||||||
|
|
||||||
|
# Run from SSH
|
||||||
|
if not os.getenv('DISPLAY'):
|
||||||
|
os.putenv('DISPLAY', ':0')
|
||||||
|
|
||||||
running_from_folder = os.path.realpath(__file__)
|
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']
|
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
|
# Config
|
||||||
# defaults
|
# defaults
|
||||||
project_settings_defaults = {
|
project_settings_defaults = {
|
||||||
|
'cam_is_picam': True,
|
||||||
'cam_is_showmewebcam': False,
|
'cam_is_showmewebcam': False,
|
||||||
'use_date_for_folder': False,
|
'use_date_for_folder': False,
|
||||||
'file_extension':'png',
|
'file_extension':'png',
|
||||||
|
@ -65,13 +71,21 @@ for location in config_locations:
|
||||||
config_found_msg = _("Found configuration file in {}").format(os.path.expanduser(location))
|
config_found_msg = _("Found configuration file in {}").format(os.path.expanduser(location))
|
||||||
print(config_found_msg)
|
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']),
|
'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']),
|
'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']),
|
'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']),
|
'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']),
|
'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):
|
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)
|
text_image_draw = ImageDraw.Draw(text_image)
|
||||||
if text is not None:
|
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')
|
lines = text.split('\n')
|
||||||
longest_line = lines[0]
|
longest_line = lines[0]
|
||||||
for line in lines:
|
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
|
global onionskin, liveview_only, playback, loop_playback, playhead, index, img_list, first_playback, camera_current_settings
|
||||||
|
|
||||||
if not testDevice(0):
|
if not project_settings['cam_is_picam']:
|
||||||
print(_("No camera device found. Exiting..."))
|
if not testDevice(0):
|
||||||
return 1
|
print(_("No camera device found. Exiting..."))
|
||||||
cam = cv2.VideoCapture(0)
|
return 1
|
||||||
cam.set(cv2.CAP_PROP_FRAME_WIDTH, camera_settings['cam_w'])
|
cam = cv2.VideoCapture(0)
|
||||||
cam.set(cv2.CAP_PROP_FRAME_HEIGHT, camera_settings['cam_h'])
|
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)
|
frame = get_onionskin_frame(savepath, index)
|
||||||
|
|
||||||
|
@ -480,10 +519,13 @@ def main(args):
|
||||||
|
|
||||||
if liveview_only:
|
if liveview_only:
|
||||||
# ~ onionskin = False
|
# ~ onionskin = False
|
||||||
ret, overlay = cam.read()
|
if not project_settings['cam_is_picam']:
|
||||||
if not ret:
|
ret, overlay = cam.read()
|
||||||
print(_("Failed to grab frame."))
|
if not ret:
|
||||||
break
|
print(_("Failed to grab frame."))
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
overlay = cam.capture_array("main")
|
||||||
# Resize preview
|
# Resize preview
|
||||||
overlay = cv2.resize(overlay, (project_settings['screen_w'], project_settings['screen_h']))
|
overlay = cv2.resize(overlay, (project_settings['screen_w'], project_settings['screen_h']))
|
||||||
cv2.imshow("StopiCV", overlay)
|
cv2.imshow("StopiCV", overlay)
|
||||||
|
@ -491,7 +533,13 @@ def main(args):
|
||||||
# ~ onionskin = True
|
# ~ onionskin = True
|
||||||
|
|
||||||
if onionskin:
|
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()
|
og_frame = overlay.copy()
|
||||||
# Resize preview
|
# Resize preview
|
||||||
overlay = cv2.resize(overlay, (project_settings['screen_w'], project_settings['screen_h']))
|
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']
|
alpha = project_settings['onionskin_alpha_default']
|
||||||
beta = (1.0 - alpha)
|
beta = (1.0 - alpha)
|
||||||
overlay = cv2.addWeighted(frame, alpha, overlay, beta, 0)
|
overlay = cv2.addWeighted(frame, alpha, overlay, beta, 0)
|
||||||
|
|
||||||
if not ret:
|
|
||||||
print(_("Failed to grab frame."))
|
|
||||||
break
|
|
||||||
cv2.imshow("StopiCV", overlay)
|
cv2.imshow("StopiCV", overlay)
|
||||||
|
|
||||||
if not playback and not onionskin and not liveview_only:
|
if not playback and not onionskin and not liveview_only:
|
||||||
cv2.imshow("StopiCV", frame)
|
cv2.imshow("StopiCV", frame)
|
||||||
|
|
||||||
k = cv2.waitKey(1)
|
k = cv2.waitKey(1)
|
||||||
# TODO : Show liveview only
|
# Key l / kp 5
|
||||||
# Key l / 5
|
if (k%256 == 108) or (k%256 == 53) or (k%256 == 181):
|
||||||
if (k%256 == 108) or (k%256 == 53):
|
print(_("Liveview only"))
|
||||||
# Toggle liveview
|
# Toggle liveview
|
||||||
liveview_only = not liveview_only
|
liveview_only = not liveview_only
|
||||||
onionskin = not onionskin
|
onionskin = not onionskin
|
||||||
# Key o / 9
|
# Key o / kp slash
|
||||||
if (k%256 == 111) or (k%256 == 47):
|
elif (k%256 == 111) or (k%256 == 47) or (k%256 == 175):
|
||||||
|
print(_("Onionskin toggle"))
|
||||||
# Toggle onionskin
|
# Toggle onionskin
|
||||||
onionskin = not onionskin
|
onionskin = not onionskin
|
||||||
liveview_only = False
|
liveview_only = False
|
||||||
# Key w / 7 - cycle wb
|
# 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'])
|
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
|
# 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'])
|
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
|
# 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'])
|
camera_current_settings = apply_cam_setting(camera_current_settings, ['vertical_flip','horizontal_flip'])
|
||||||
# Key up
|
if project_settings['cam_is_picam']:
|
||||||
elif (k%256 == 82) or (k%256 == 56):
|
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 len(img_list):
|
||||||
if playback:
|
if playback:
|
||||||
playback = False
|
playback = False
|
||||||
index, frame = last_frame(index)
|
index, frame = last_frame(index)
|
||||||
# Key down
|
# Key down , kp 2
|
||||||
elif (k%256 == 84) or (k%256 == 50):
|
elif (k%256 == 84) or (k%256 == 50) or (k%256 == 178):
|
||||||
|
print(_("First frame"))
|
||||||
if len(img_list):
|
if len(img_list):
|
||||||
if playback:
|
if playback:
|
||||||
playback = False
|
playback = False
|
||||||
index, frame = first_frame(index)
|
index, frame = first_frame(index)
|
||||||
# Key left
|
# Key left, kp 4
|
||||||
elif (k%256 == 81) or (k%256 == 52):
|
elif (k%256 == 81) or (k%256 == 52) or (k%256 == 180):
|
||||||
|
print(_("Prev. frame"))
|
||||||
# Displau previous frame
|
# Displau previous frame
|
||||||
if len(img_list):
|
if len(img_list):
|
||||||
if playback:
|
if playback:
|
||||||
playback = False
|
playback = False
|
||||||
index, frame = previous_frame(index)
|
index, frame = previous_frame(index)
|
||||||
# Key right
|
# Key right, kp 6
|
||||||
elif (k%256 == 83) or (k%256 == 54):
|
elif (k%256 == 83) or (k%256 == 54) or (k%256 == 182):
|
||||||
|
print(_("Next frame"))
|
||||||
# Displau next frame
|
# Displau next frame
|
||||||
if len(img_list):
|
if len(img_list):
|
||||||
if playback:
|
if playback:
|
||||||
playback = False
|
playback = False
|
||||||
index, frame = next_frame(index)
|
index, frame = next_frame(index)
|
||||||
# Key r / keypad 9 - reset wb,exp
|
# 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"))
|
print(_("Reset camera settings"))
|
||||||
camera_current_settings = apply_cam_setting(camera_current_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 *
|
# 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"))
|
print(_("Export"))
|
||||||
ffmpeg_process = export_animation(input_filename, export_filename)
|
ffmpeg_process = export_animation(input_filename, export_filename)
|
||||||
# Key Return
|
# Key Return, kp return
|
||||||
elif (k%256 == 13):
|
elif (k%256 == 13) or (k%256 == 141) :
|
||||||
print(_("Playback"))
|
print(_("Playback"))
|
||||||
playhead = index
|
playhead = index
|
||||||
loop_playback = True
|
loop_playback = True
|
||||||
playback = not playback
|
playback = not playback
|
||||||
# Key remove frame - backspace, del, numpad_minus
|
# 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
|
# Remove frame
|
||||||
print(_("Remove frame"))
|
print(_("Remove frame"))
|
||||||
img_list, index, frame = remove_frame(img_list, index)
|
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
|
# Take pic
|
||||||
# SPACE or numpad 0 pressed
|
# 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_name = return_next_frame_number(get_last_frame(savepath))
|
||||||
img_path = os.path.join(savepath, img_name)
|
img_path = os.path.join(savepath, img_name)
|
||||||
cv2.imwrite(img_path, og_frame)
|
cv2.imwrite(img_path, og_frame)
|
||||||
|
@ -597,6 +665,18 @@ def main(args):
|
||||||
else:
|
else:
|
||||||
index += 1
|
index += 1
|
||||||
frame = get_onionskin_frame(savepath, index)
|
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
|
# REMOVE : Debug print keycode
|
||||||
elif k==-1: # normally -1 returned,so don't print it
|
elif k==-1: # normally -1 returned,so don't print it
|
||||||
continue
|
continue
|
||||||
|
@ -619,7 +699,10 @@ def main(args):
|
||||||
except:
|
except:
|
||||||
print(_("Terminating running process..."))
|
print(_("Terminating running process..."))
|
||||||
ffmpeg_process.terminate()
|
ffmpeg_process.terminate()
|
||||||
cam.release()
|
if not project_settings["cam_is_picam"]:
|
||||||
|
cam.release()
|
||||||
|
else:
|
||||||
|
cam.close()
|
||||||
cv2.destroyAllWindows()
|
cv2.destroyAllWindows()
|
||||||
|
|
||||||
cv2.namedWindow("StopiCV", cv2.WINDOW_GUI_NORMAL)
|
cv2.namedWindow("StopiCV", cv2.WINDOW_GUI_NORMAL)
|
||||||
|
|
|
@ -28,8 +28,8 @@ Dans un terminal :
|
||||||
1. Installer les dépendances suivantes :
|
1. Installer les dépendances suivantes :
|
||||||
```
|
```
|
||||||
# Avec une distribution basée sur Debian (Ubuntu, Mint...)
|
# 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`
|
- (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`
|
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`
|
3. Aller dans répertoire du projet : `cd stopi2`
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
Send2Trash
|
Send2Trash
|
||||||
opencv-python
|
|
||||||
numpy
|
numpy
|
||||||
pyserial
|
pyserial
|
||||||
pillow
|
pillow
|
||||||
|
opencv-python
|
||||||
|
|
Loading…
Reference in New Issue