diff --git a/frame_opencv.py b/frame_opencv.py index 16ef639..85441f3 100644 --- a/frame_opencv.py +++ b/frame_opencv.py @@ -9,14 +9,10 @@ import signal import sys import subprocess import time +from timeit import default_timer as timer import tomllib import numpy as np - -# TODO : find camera module version from image size -# https://www.raspberrypi.com/documentation/accessories/camera.html#hardware-specification -# v4l2-ctl --list-framesizes=YU12 -# 2592 x 1944 == v1 -# 3280 × 2464 == v2 +import serialutils 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'] @@ -65,6 +61,15 @@ for location in config_locations: config_found_msg = _("Found configuration file in {}").format(os.path.expanduser(location)) print(config_found_msg) +def reset_wb_exp(): + # Flip auto-exposure, white-balance on to adapt to light environment + serialutils.send_serial_cmd(serialutils.find_cam_port(), 'v4l2-ctl -d /dev/video0 -c auto_exposure=0 -c white_balance_auto_preset=1') + # Give some time to the captor to adapt + time.sleep(1) + # Flip back, wb 3 = fluorescent + serialutils.send_serial_cmd(serialutils.find_cam_port(), 'v4l2-ctl -d /dev/video0 -c auto_exposure=1 -c white_balance_auto_preset=2') + + def generate_text_image(text:str, screen_w, screen_h, bullets=False): text_image = Image.new('RGB', (screen_w, screen_h), @@ -352,26 +357,26 @@ def remove_frame(img_list, img_index): else: return img_list, 0, blank_image -def playback_animation(img_list, img_index): - # save onionskin state - if onionskin: - onionskin = False - onionskin_was_on = True +# ~ def playback_animation(img_list, img_index): + # ~ # save onionskin state + # ~ if onionskin: + # ~ onionskin = False + # ~ onionskin_was_on = True - for file in img_list: - img = update_image(img_list, img_list.index(file)) - print(str(img_list.index(file)) + " : " + file ) - cv2.imshow("StopiCV", img) - time.sleep(.5) - # Restore previous frame - print(img_index) - frame_before_playback = update_image(img_list, img_index) + # ~ for file in img_list: + # ~ img = update_image(img_list, img_list.index(file)) + # ~ print(str(img_list.index(file)) + " : " + file ) + # ~ cv2.imshow("StopiCV", img) + # ~ time.sleep(.5) + # ~ # Restore previous frame + # ~ print(img_index) + # ~ frame_before_playback = update_image(img_list, img_index) # ~ cv2.imshow("StopiCV", img) - # Restore onionskin - if 'onionskin_was_on' in locals(): - onionskin = True - # Restore index - return img_index, frame_before_playback + # ~ # Restore onionskin + # ~ if 'onionskin_was_on' in locals(): + # ~ onionskin = True + # ~ # Restore index + # ~ return img_index, frame_before_playback def testDevice(source): cap = cv2.VideoCapture(source) @@ -424,7 +429,83 @@ def main(args): cam.set(cv2.CAP_PROP_FRAME_HEIGHT, 900) frame = get_onionskin_frame(savepath, index) + + # TODO : (re)set camera settings on startup + # Using serial to send v4l2-ctl cmd to the camera + # Use v4l-ctl to flip frame + # v4l2-ctl -d /dev/video0 -c vertical_flip=1 -c control=value + # DEPRECATED : Using config file and loading with camera-ctl + # Write config file to /tmp/blah + # ~ write_config_file = '''rm /tmp/blah && echo "power_line_frequency=2 + # ~ sharpness=50 + # ~ video_bitrate=25000000 + # ~ auto_exposure=1 + # ~ auto_exposure_bias=2 + # ~ white_balance_auto_preset=3 + # ~ exposure_metering_mode=1 + # ~ " > /tmp/blah''' + # Apply config + # ~ apply_config = '/opt/camera-control/camera-ctl -c /tmp/blah -v /dev/video0 -i repeat_sequence_header -i h264_i_frame_period -i h264_level -i h264_profile -i compression_quality\rl\rq\r' + # ~ serialutils.send_serial_cmd(serialutils.find_cam_port(), write_config_file) + # ~ serialutils.send_serial_cmd(serialutils.find_cam_port(), apply_config) + + # ~ User Controls + + # ~ brightness 0x00980900 (int) : min=0 max=100 step=1 default=50 value=50 flags=slider + # ~ contrast 0x00980901 (int) : min=-100 max=100 step=1 default=0 value=-2 flags=slider + # ~ saturation 0x00980902 (int) : min=-100 max=100 step=1 default=0 value=5 flags=slider + # ~ red_balance 0x0098090e (int) : min=1 max=7999 step=1 default=1000 value=1000 flags=slider + # ~ blue_balance 0x0098090f (int) : min=1 max=7999 step=1 default=1000 value=1000 flags=slider + # ~ horizontal_flip 0x00980914 (bool) : default=0 value=0 + # ~ vertical_flip 0x00980915 (bool) : default=0 value=0 + # ~ power_line_frequency 0x00980918 (menu) : min=0 max=3 default=1 value=1 + # ~ sharpness 0x0098091b (int) : min=-100 max=100 step=1 default=0 value=10 flags=slider + # ~ color_effects 0x0098091f (menu) : min=0 max=15 default=0 value=0 + # ~ rotate 0x00980922 (int) : min=0 max=360 step=90 default=0 value=0 flags=modify-layout + # ~ color_effects_cbcr 0x0098092a (int) : min=0 max=65535 step=1 default=32896 value=32896 + + # ~ Codec Controls + + # ~ video_bitrate_mode 0x009909ce (menu) : min=0 max=1 default=0 value=0 flags=update + # ~ video_bitrate 0x009909cf (int) : min=25000 max=25000000 step=25000 default=10000000 value=25000000 + # ~ repeat_sequence_header 0x009909e2 (bool) : default=0 value=0 + # ~ h264_i_frame_period 0x00990a66 (int) : min=0 max=2147483647 step=1 default=60 value=60 + # ~ h264_level 0x00990a67 (menu) : min=0 max=11 default=11 value=11 + # ~ h264_profile 0x00990a6b (menu) : min=0 max=4 default=4 value=4 + + # ~ Camera Controls + + # ~ auto_exposure 0x009a0901 (menu) : min=0 max=3 default=0 value=0 + # ~ exposure_time_absolute 0x009a0902 (int) : min=1 max=10000 step=1 default=1000 value=1000 + # ~ exposure_dynamic_framerate 0x009a0903 (bool) : default=0 value=0 + # ~ auto_exposure_bias 0x009a0913 (intmenu): min=0 max=24 default=12 value=2 + # ~ white_balance_auto_preset 0x009a0914 (menu) : min=0 max=9 default=1 value=1 + # ~ image_stabilization 0x009a0916 (bool) : default=0 value=0 + # ~ iso_sensitivity 0x009a0917 (intmenu): min=0 max=4 default=0 value=0 + # ~ iso_sensitivity_auto 0x009a0918 (menu) : min=0 max=1 default=1 value=1 + # ~ exposure_metering_mode 0x009a0919 (menu) : min=0 max=3 default=0 value=0 + # ~ scene_mode 0x009a091a (menu) : min=0 max=13 default=0 value=0 + + # ~ JPEG Compression Controls + # ~ compression_quality 0x009d0903 (int) : min=1 max=100 step=1 default=30 value=30 + + # Make sure we're using max bitrate + serialutils.send_serial_cmd(serialutils.find_cam_port(), 'v4l2-ctl -d /dev/video0 -c video_bitrate=25000000') + # Flip auto-exposure, white-balance on and off to adapt to light environment + reset_wb_exp() + # Flip preview (0 = vert; 1 = hor) + if project_settings['vflip']: + serialutils.send_serial_cmd(serialutils.find_cam_port(), 'v4l2-ctl -d /dev/video0 -c vertical_flip=1') + else: + serialutils.send_serial_cmd(serialutils.find_cam_port(), 'v4l2-ctl -d /dev/video0 -c vertical_flip=0') + if project_settings['hflip']: + serialutils.send_serial_cmd(serialutils.find_cam_port(), 'v4l2-ctl -d /dev/video0 -c horizontal_flip=1') + else: + serialutils.send_serial_cmd(serialutils.find_cam_port(), 'v4l2-ctl -d /dev/video0 -c horizontal_flip=0') + time.sleep(.5) + loop_delta = 0 while True: + start = timer() if playback: if onionskin: onionskin = False @@ -433,7 +514,11 @@ def main(args): if playhead < len(img_list)-1: playhead, img = next_frame(playhead, loop_playback) cv2.imshow("StopiCV", img) - time.sleep(1.0/project_settings['framerate']) + # Calculate framerate according to loop execution time + frame_interval = 1.0/project_settings['framerate']-loop_delta + if frame_interval < 0: + frame_interval = 0 + time.sleep(frame_interval) else: playhead = index img = update_image(img_list, index) @@ -445,24 +530,14 @@ def main(args): if onionskin: ret, overlay = cam.read() - og_frame = overlay + og_frame = overlay.copy() # Resize preview overlay = cv2.resize(overlay, (project_settings['screen_w'], project_settings['screen_h'])) # Apply onionskin alpha = project_settings['onionskin_alpha_default'] beta = (1.0 - alpha) overlay = cv2.addWeighted(frame, alpha, overlay, beta, 0) - # TODO : use v4l-ctl to flip frame - # v4l2-ctl -d /dev/video0 -c vertical_flip=1 - # Flip preview (0 = vert; 1 = hor) - if project_settings['vflip'] or project_settings['hflip']: - if project_settings['vflip'] and project_settings['hflip']: - flip_dir = -1 - elif project_settings['vflip']: - flip_dir = 0 - elif project_settings['hflip']: - flip_dir = 1 - overlay = cv2.flip(overlay, flip_dir) + if not ret: print(_("Failed to grab frame.")) break @@ -502,6 +577,9 @@ def main(args): if playback: playback = False index, frame = next_frame(index) + # Key r / keypad 9 - reset wb,exp + elif (k%256 == 114) or (k%256 == 57): + reset_wb_exp() # Key e / keypad * elif (k%256 == 101) or (k%256 == 42): print(_("Export")) @@ -509,7 +587,6 @@ def main(args): # Key Return elif (k%256 == 13): print(_("Playback")) - print(img_list) playhead = index loop_playback = True playback = not playback @@ -546,6 +623,10 @@ def main(args): continue else: print(k) # else print its value + end = timer() + loop_delta = end - start + print(loop_playback) + if 'ffmpeg_process' in locals(): if ffmpeg_process.poll() is None: print(_("Ffmpeg is still running.\n Waiting for task to complete.")) diff --git a/serialutils.py b/serialutils.py new file mode 100644 index 0000000..f51d4a5 --- /dev/null +++ b/serialutils.py @@ -0,0 +1,18 @@ +import serial +import serial.tools.list_ports + +def find_cam_port(): + serial_devices = serial.tools.list_ports.comports() + for dev in serial_devices: + if str(dev).find('Piwebcam') != -1: + return str(dev).split(' ')[0] + return None + +def send_serial_cmd(cam_port, cmd:str, clear=True): + con = serial.Serial(cam_port, baudrate=115200) + if clear: + append = b'\rclear\r' + else: + append = b'\r' + con.write(str.encode(cmd) + append) + con.close() \ No newline at end of file