fotobox_SPRT/Hauptprogramm.py
2024-10-07 19:55:43 +02:00

354 lines
12 KiB
Python

#Hauptprogramm; Nimmt kontinuierlich Bilder auf wenn externer Trigger ausgelöst wird und speichert diese bei Bedarf
import cv2
import av
import time
import subprocess
import multiprocessing
from multiprocessing import shared_memory
import sys
import signal
import RPi.GPIO as GPIO
import numpy as np
import psutil
import os
#Region of Interest definieren:
roi_start = 0 #min: 0
roi_end = 800 #max: 720
roi_breite = roi_end - roi_start
#Warnungen von av deaktivieren (Ansonsten kommt dauerhaft eine Warnung über ein mögliches falsches Pixelformat, ist aber iO)
av.logging.set_level(av.logging.ERROR)
#GPIO Pin für Speicherimpuls einstellen
ausloeser = 17
GPIO.setmode(GPIO.BCM)
GPIO.setup(ausloeser, GPIO.IN, pull_up_down = GPIO.PUD_UP)
#Kameraeinstellungen; Soll später aus einer Textdatei ausgelesen werden, die über ein GUI eingestellt wird (Fast fertig)
camera_ids = [0, 2, 4, 6]
triggermodus_on = "v4l2-ctl -d %d -c exposure_dynamic_framerate=1"
triggermodus_off = "v4l2-ctl -d %d -c exposure_dynamic_framerate=0"
belichtung = "v4l2-ctl -d %d -c exposure_time_absolute=1"
auto_exposure = "v4l2-ctl -d %d -c auto_exposure=1"
brightness = "v4l2-ctl -d %d -c brightness=64"
contrast = "v4l2-ctl -d %d -c contrast=64"
#Funktion um die Kameraeinstellungen an das Terminal zu senden
def run_cmd(cmd):
try:
subprocess.run(cmd, shell=True, check=True)
except subprocess.CalledProcessError as e:
print("Fehler", e)
#Ctrl+C zum Programmabbruch definieren
def signal_handler(sig, frame):
print('Abbruch')
sys.exit(0)
#Evtl. noch Kameras schließen, Python Prozesse beenden, Bildspeicher komplett leeren, usw..)
signal.signal(signal.SIGINT, signal_handler)
#Klasse definieren, um Bilder von den Kameras aufzunehmen
class CameraModule():
def run(self, camera_id, camera_values, shm_np_block, shm_camera_id_block, shm_k_block):
shm_np_array = np.ndarray(shm_shape, dtype='uint8', buffer=shm_np_block.buf)
shm_camera_id_array = np.frombuffer(shm_camera_id_block.buf, dtype='int32')
shm_k_array = np.frombuffer(shm_k_block.buf, dtype='int32')
shm_cam_id = np.frombuffer(camera_values.buf, dtype='int32')
ring_counter = 0
#Cv2-Stream öffnen (Ohne den Cv2 Warmup werden die Kameras komischerweise nicht korrekt gestartet, hab da vieles probiert..)
cap = cv2.VideoCapture(f"/dev/video{camera_id}", cv2.CAP_V4L2)
cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc('M', 'J', 'P', 'G'))
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 800)
for _ in range(50): #50 Bilder aufnehmen um Kamera zu stabilisieren
ret, frame = cap.read()
cap.release()
cv2.destroyAllWindows()
time.sleep(5)
#av-Kameraeinstellungen
options = {
'video_size': '1280x800',
'framerate': '60',
'input_format': 'mjpeg'
}
input_stream = av.open('/dev/video{}'.format(camera_id), format='video4linux2', options=options)
time.sleep(1)
video_stream = next(s for s in input_stream.streams if s.type == 'video')
framerate = video_stream.average_rate
print(f"Kamera {camera_id} Framerate: {framerate}") #Zur Kontrolle
#50 Bilder aufnehmen; Dient dazu, die Kamera zu stabilisieren (Weißabgleich usw.)
for _ in range(50):
frame = next(input_stream.decode(video_stream))
time.sleep(0.1)
#Triggermodus der Kamera auf externen Trigger umstellen
run_cmd(triggermodus_on % camera_id)
k = 1
frame = None
ring_counter = 0
#Buffer der Kameras (~5 Bilder) füllen und Trigger synchronisieren; Wird später auch noch automatisiert durch Verbindung mit dem Arduino
print("Trigger auslösen bis Freigabe kommt")
for _ in range(50):
frame = next(input_stream.decode(video_stream))
print("Freigabe erteilt")
time.sleep(5)
print("Bereit")
#Kontinuierlich Bilder aufnehmen und in die image_queue schreiben
while True:
slot = (camera_id // 2 + ring_counter * 4) % 200
frame = next(input_stream.decode(video_stream))
numpy_frame = frame.to_ndarray(format='bgr24')[roi_start:roi_end, :]
freigabe = shm_k_array[slot]
if freigabe == 0:
shm_np_array[slot] = numpy_frame
shm_camera_id_array[slot] = camera_id
shm_k_array[slot] = k
shm_cam_id[camera_id//2] = k
k += 1
ring_counter = (ring_counter + 1)
else:
print("Speicher wird überschrieben")
print(shm_k_array[slot])
print(slot)
print(camera_id)
#Noch eine Fehlererkennung einbauen, falls Frame nicht ausgelesen werden kann?
#Kameraworker definieren
def camera_worker(camera_id, camera_values, shm_np_block, shm_camera_id_block, shm_k_block, cpu_core):
p = psutil.Process(os.getpid())
p.cpu_affinity([cpu_core]) #Jede Instanz wird auf einem seperaten CPU Kern ausgeführt
camera_module = CameraModule()
camera_module.run(camera_id, camera_values, shm_np_block, shm_camera_id_block, shm_k_block)
#Klasse definieren, um GPIO/Speicherimpuls zu überwachen
class SaveHandler():
def __init__(self, camera_values, trigger_liste):
self.camera_values = camera_values
self.trigger_liste = trigger_liste
GPIO.add_event_detect(ausloeser, GPIO.FALLING, callback=self.ausloeser_callback, bouncetime=200)
def ausloeser_callback(self, channel):
shm_cam_id = np.frombuffer(camera_values.buf, dtype='int32')
while True:
if not GPIO.input(channel):
cam_value = shm_cam_id
self.trigger_liste.append(cam_value)
print("Trigger empfangen")
time.sleep(1) #Ist nur Testweise drin, damit nicht zu oft getriggert wird durch betätigen eines Tasters
def handler_worker(camera_values, trigger_liste):
handler = SaveHandler(camera_values, trigger_liste)
handler.ausloeser_callback(ausloeser)
#Klasse definieren, um Bilder von der image_queue zu holen und an den Saver zu senden, falls gewünscht
class SaveImageProcess():
def __init__(self, shm_np_block, shm_camera_id_block, shm_k_block, trigger_liste, shm_save_frame, shm_save_k, shm_save_camera_id, shm_save_ts, shm_save_t):
self.trigger_liste = trigger_liste
self.shm_np_block = shm_np_block
self.shm_camera_id_block = shm_camera_id_block
self.shm_k_block = shm_k_block
self.shm_save_frame = shm_save_frame
self.shm_save_k = shm_save_k
self.shm_save_camera_id = shm_save_camera_id
self.shm_save_ts = shm_save_ts
self.shm_save_t = shm_save_t
def run(self, shm_np_block, shm_camera_id_block, shm_k_block, trigger_liste, shm_save_frame, shm_save_k, shm_save_camera_id, shm_save_ts, shm_save_t):
shm_np_array = np.ndarray(shm_shape, dtype='uint8', buffer=shm_np_block.buf)
shm_camera_id_array = np.frombuffer(shm_camera_id_block.buf, dtype='int32')
shm_k_array = np.frombuffer(shm_k_block.buf, dtype='int32')
shm_save_frame_array = np.ndarray(save_shape, dtype='uint8', buffer=shm_save_frame.buf)
shm_save_k_array = np.frombuffer(shm_save_k.buf, dtype='int32')
shm_save_camera_id_array = np.frombuffer(shm_save_camera_id.buf, dtype='int32')
shm_save_ts_array = np.frombuffer(shm_save_ts.buf, dtype='int32')
shm_save_t_array = np.frombuffer(shm_save_t.buf, dtype='int32')
ring_counter = 0
save_counter = 0
j = 0
l = 0
print("SaveImageProcess gestartet")
while True:
non_zero_indices = [index for index, value in enumerate(shm_k_array[:]) if value != 0]
if len(non_zero_indices) > 20:
print(non_zero_indices)
for index in non_zero_indices:
camera_id = shm_camera_id_array[index]
k = shm_k_array[index]
shm_k_array[index] = 0
trigger_test = np.array(trigger_liste)
for i in range(j, trigger_test.shape[0]):
if k >= (trigger_test[i][camera_id//2]-5+1) and k <= (trigger_test[i][camera_id//2]+5+1):
shm_save_frame_array[save_counter] = shm_np_array[index]
shm_save_camera_id_array[save_counter] = camera_id
shm_save_k_array[save_counter] = k
shm_save_ts_array[save_counter] = trigger_test[i][camera_id//2]
shm_save_t_array[save_counter] = i
save_counter = (save_counter + 1) % 200
if k > (trigger_test[i][camera_id//2]+200):
j += 1
def save_worker(shm_np_block, shm_camera_id_block, shm_k_block, trigger_liste, shm_save_frame, shm_save_k, shm_save_camera_id, shm_save_ts, shm_save_t):
save_worker = SaveImageProcess(shm_np_block, shm_camera_id_block, shm_k_block, trigger_liste, shm_save_frame, shm_save_k, shm_save_camera_id, shm_save_ts, shm_save_t)
save_worker.run(shm_np_block, shm_camera_id_block, shm_k_block, trigger_liste, shm_save_frame, shm_save_k, shm_save_camera_id, shm_save_ts, shm_save_t)
#Klasse um die Bilder zu speichern (Ist aufgeteilt, da das Speichern der Bilder deutlich der Aufnahme der Bilder hinterherhängt und ohne diesen Frames verworfen werden)
class Saver():
def __init__(self, shm_save_frame, shm_save_k, shm_save_camera_id, shm_save_ts, shm_save_t):
self.shm_save_frame = shm_save_frame
self.shm_save_k = shm_save_k
self.shm_save_camera_id = shm_save_camera_id
self.shm_save_ts = shm_save_ts
self.shm_save_t = shm_save_t
def run(self, shm_save_frame, shm_save_k, shm_save_camera_id, shm_save_ts, shm_save_t):
shm_save_frame_array = np.ndarray(save_shape, dtype='uint8', buffer=shm_save_frame.buf)
shm_save_k_array = np.frombuffer(shm_save_k.buf, dtype='int32')
shm_save_camera_id_array = np.frombuffer(shm_save_camera_id.buf, dtype='int32')
shm_save_ts_array = np.frombuffer(shm_save_ts.buf, dtype='int32')
shm_save_t_array = np.frombuffer(shm_save_t.buf, dtype='int32')
ring_counter = 0
while True:
if shm_save_k_array[ring_counter] != 0:
filename = "/mnt/ramdisk/Cam{}_T{}_TS{}_B{}.jpg".format(shm_save_camera_id_array[ring_counter], shm_save_t_array[ring_counter], shm_save_ts_array[ring_counter], shm_save_k_array[ring_counter])
cv2.imwrite(filename, shm_save_frame_array[ring_counter])
shm_save_k_array[ring_counter] = 0
ring_counter = (ring_counter + 1) % 200
def saver_worker(shm_save_frame, shm_save_k, shm_save_camera_id, shm_save_ts, shm_save_t):
saver = Saver(shm_save_frame, shm_save_k, shm_save_camera_id, shm_save_ts, shm_save_t)
saver.run(shm_save_frame, shm_save_k, shm_save_camera_id, shm_save_ts, shm_save_t)
if __name__ == "__main__":
#Einstellungen der Shared Memory Blöcke
shm_shape = (200, roi_breite, 1280, 3)
save_shape = (200, roi_breite, 1280, 3)
cv_shape = (1, 4, 1, 1)
shm_np_block = shared_memory.SharedMemory(create=True, size=shm_shape[0]*shm_shape[1]*shm_shape[2]*shm_shape[3]*np.dtype('uint8').itemsize)
shm_camera_id_block = shared_memory.SharedMemory(create=True, size=200 * np.dtype('int32').itemsize)
shm_k_block = shared_memory.SharedMemory(create=True, size=200 * np.dtype('int32').itemsize)
shm_save_frame = shared_memory.SharedMemory(create=True, size=save_shape[0]*save_shape[1]*save_shape[2]*save_shape[3]*np.dtype('uint8').itemsize)
shm_save_k = shared_memory.SharedMemory(create=True, size=200 * np.dtype('int32').itemsize)
shm_save_camera_id = shared_memory.SharedMemory(create=True, size=200 * np.dtype('int32').itemsize)
shm_save_ts = shared_memory.SharedMemory(create=True, size=200 * np.dtype('int32').itemsize)
shm_save_t = shared_memory.SharedMemory(create=True, size=200 * np.dtype('int32').itemsize)
camera_values = shared_memory.SharedMemory(create=True, size= 4*np.dtype('int32').itemsize)
#Kameraeinstellungen vornehmen/Wird später durch GUI ersetzt
print("Kameras einstellen")
for i in [0, 2, 4, 6]:
run_cmd(triggermodus_off % i)
time.sleep(0.5)
run_cmd(auto_exposure % i)
time.sleep(0.5)
run_cmd(belichtung % i)
time.sleep(0.5)
run_cmd(brightness % i)
time.sleep(0.5)
run_cmd(contrast % i)
time.sleep(0.5)
print("Kamera %d eingestellt" % i)
#Trigger_liste als Multiprocessing-Liste erstellen
manager = multiprocessing.Manager()
trigger_liste = manager.list()
processes = []
i = 0
cpu_cores = [0, 1, 2, 3]
for camera_id in camera_ids:
process = multiprocessing.Process(target=camera_worker, args=(camera_id, camera_values, shm_np_block, shm_camera_id_block, shm_k_block, cpu_cores[i]))
i += 1
process.start()
processes.append(process)
handler = SaveHandler(camera_values, trigger_liste)
save_process = multiprocessing.Process(target=save_worker, args=(shm_np_block, shm_camera_id_block, shm_k_block, trigger_liste, shm_save_frame, shm_save_k, shm_save_camera_id, shm_save_ts, shm_save_t))
processes.append(save_process)
save_process.start()
saver2 = Saver(shm_save_frame, shm_save_k, shm_save_camera_id, shm_save_ts, shm_save_t)
saver2.run(shm_save_frame, shm_save_k, shm_save_camera_id, shm_save_ts, shm_save_t)
process.join()
save_process.join()
saver2.join()