Überprüfe dieses skript auf fehler, probleme und ungenutzte optimierungsmöglichkeiten und behebe diese. Sorge auch dafür das auch große anzahlen von bildern im auswähl menü effizient gewählt werden und die anwendung dabei nicht einfriert oder abstürzt. optimiere das ganze skript nocheinmal und nenne mir dann den vollständigen code der gesamten anwendung ohne fehlende zeilen.

import sys
import os
import cv2
import numpy as np
from PyQt5 import QtCore, QtGui, QtWidgets, QtSvg
from PyQt5.QtGui import QImage, QPixmap, QIcon
from PyQt5.QtWidgets import (
    QLabel, QTabWidget, QTextEdit, QLineEdit, QPushButton,
    QListWidget, QVBoxLayout, QFileDialog, QHBoxLayout, QGroupBox,
    QFormLayout, QSlider, QCheckBox, QProgressBar, QMainWindow, QApplication
)
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QEasingCurve, QPropertyAnimation
from tqdm import tqdm
from PIL import Image, ImageFilter
from skimage.metrics import structural_similarity as ssim
import multiprocessing

# Dark mode stylesheet
DARK_STYLE = """
QWidget {
    background-color: #2b2b2b;
    color: #f0f0f0;
    font-family: 'Segoe UI', sans-serif;
    font-size: 10pt;
}

QPushButton {
    background-color: #3c3c3c;
    border: 1px solid #5a5a5a;
    padding: 5px;
    border-radius: 5px;
}

QPushButton:hover {
    background-color: #505050;
}

QPushButton:pressed {
    background-color: #383838;
}

QLineEdit, QTextEdit, QListWidget, QLabel, QSlider, QGroupBox {
    background-color: #3c3c3c;
    border: 1px solid #5a5a5a;
    padding: 5px;
    border-radius: 5px;
    color: #f0f0f0;
}

QSlider::groove:horizontal {
    border: 1px solid #5a5a5a;
    height: 8px;
    background: #4a4a4a;
    border-radius: 4px;
}

QSlider::handle:horizontal {
    background: #8f8f91;
    border: 1px solid #5a5a5a;
    width: 18px;
    margin: -5px 0;
    border-radius: 9px;
}

QProgressBar {
    background-color: #3c3c3c;
    border: 1px solid #5a5a5a;
    border-radius: 5px;
    text-align: center;
}

QProgressBar::chunk {
    background-color: #5a5a5a;
    width: 20px;
    margin: 0.5px;
}

QTabWidget::pane { /* The tab widget frame */
    border-top: 2px solid #444;
}

QTabBar::tab {
    background: #3c3c3c;
    border: 1px solid #444;
    padding: 5px;
    border-bottom: none;
    border-top-left-radius: 4px;
    border-top-right-radius: 4px;
    margin-right: 2px;
}

QTabBar::tab:selected, QTabBar::tab:hover {
    background: #5a5a5a;
}
"""

class FrameExtractor(QtCore.QObject):
    progress = pyqtSignal(int)
    log = pyqtSignal(str)
    finished = pyqtSignal(list)

    def __init__(self, video_path, output_dir, sharpness_threshold, overlap_threshold,
                 brightness_adjustment, shadow_removal_enabled, contrast_adjustment,
                 saturation_adjustment):
        super().__init__()
        self.video_path = video_path
        self.output_dir = output_dir
        self.sharpness_threshold = sharpness_threshold
        self.overlap_threshold = overlap_threshold
        self.brightness_adjustment = brightness_adjustment
        self.shadow_removal_enabled = shadow_removal_enabled
        self.contrast_adjustment = contrast_adjustment
        self.saturation_adjustment = saturation_adjustment

    def log_message(self, message):
        self.log.emit(message)

    def measure_sharpness(self, frame):
        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        lap = cv2.Laplacian(gray, cv2.CV_64F)
        return lap.var()

    def frames_overlap(self, frame1, frame2):
        # Using ORB is computationally expensive; consider using a faster method if necessary
        orb = cv2.ORB_create(nfeatures=500)
        kp1, des1 = orb.detectAndCompute(frame1, None)
        kp2, des2 = orb.detectAndCompute(frame2, None)

        if des1 is None or des2 is None:
            return 0

        bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
        matches = bf.match(des1, des2)

        good_matches = [m for m in matches if m.distance < 50]
        return len(good_matches)

    def adjust_brightness(self, frame):
        hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
        h, s, v = cv2.split(hsv)
        v = np.clip(v + self.brightness_adjustment, 0, 255).astype(np.uint8)
        final_hsv = cv2.merge((h, s, v))
        return cv2.cvtColor(final_hsv, cv2.COLOR_HSV2BGR)

    def adjust_contrast(self, frame):
        # Contrast adjustment scaling factor
        alpha = self.contrast_adjustment
        return cv2.convertScaleAbs(frame, alpha=1 + alpha / 100.0, beta=0)

    def adjust_saturation(self, frame):
        hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
        h, s, v = cv2.split(hsv)
        s = np.clip(s + self.saturation_adjustment, 0, 255).astype(np.uint8)
        final_hsv = cv2.merge((h, s, v))
        return cv2.cvtColor(final_hsv, cv2.COLOR_HSV2BGR)

    def shadow_removal(self, frame):
        if not self.shadow_removal_enabled:
            return frame

        lab = cv2.cvtColor(frame, cv2.COLOR_BGR2LAB)
        l_channel, a_channel, b_channel = cv2.split(lab)
        clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8, 8))
        cl = clahe.apply(l_channel)
        limg = cv2.merge((cl, a_channel, b_channel))
        return cv2.cvtColor(limg, cv2.COLOR_LAB2BGR)

    def sharpen_image(self, frame):
        kernel = np.array([[0, -1, 0],
                           [-1, 5, -1],
                           [0, -1, 0]])
        return cv2.filter2D(frame, -1, kernel)

    def run(self):
        try:
            cap = cv2.VideoCapture(self.video_path)
            if not cap.isOpened():
                self.log_message("Failed to open video file.")
                self.finished.emit([])
                return

            total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
            selected_frames = []
            successful_frame_count = 0

            basename = os.path.splitext(os.path.basename(self.video_path))[0]

            previous_frame = None

            for i in range(total_frames):
                ret, frame = cap.read()
                if not ret:
                    break

                sharpness = self.measure_sharpness(frame)
                if sharpness < self.sharpness_threshold:
                    self.log_message(f"Frame {i}: Sharpness {sharpness:.2f} below threshold.")
                    continue

                if previous_frame is not None:
                    overlap = self.frames_overlap(previous_frame, frame)
                    if overlap < self.overlap_threshold:
                        self.log_message(f"Frame {i}: Overlap {overlap} below threshold.")
                        continue

                # Apply image processing
                frame = self.adjust_brightness(frame)
                frame = self.adjust_contrast(frame)
                frame = self.adjust_saturation(frame)
                frame = self.shadow_removal(frame)
                frame = self.sharpen_image(frame)

                frame_name = f"{basename}_frame_{successful_frame_count:05d}.png"
                frame_path = os.path.join(self.output_dir, frame_name)
                cv2.imwrite(frame_path, frame)
                selected_frames.append(frame_path)
                successful_frame_count += 1
                previous_frame = frame.copy()

                progress_percent = int((i + 1) / total_frames * 100)
                self.progress.emit(progress_percent)
                self.log_message(f"Frame {i}: Successfully extracted.")

            cap.release()
            self.finished.emit(selected_frames)
        except Exception as e:
            self.log_message(f"Error during extraction: {str(e)}")
            self.finished.emit([])

class PreviewLabel(QLabel):
    def __init__(self):
        super().__init__()
        self.original_pixmap = None
        self.setFixedSize(300, 200)
        self.setAlignment(Qt.AlignCenter)
        self.setStyleSheet("background-color: #5a5a5a; border: 1px solid #444; border-radius: 5px;")

    def setPixmap(self, pixmap):
        if pixmap != self.original_pixmap:
            self.original_pixmap = pixmap
        scaled_pixmap = pixmap.scaled(
            self.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation
        )
        super().setPixmap(scaled_pixmap)

    def enterEvent(self, event):
        if self.original_pixmap:
            zoomed_pixmap = self.original_pixmap.scaled(
                1.2 * self.width(),
                1.2 * self.height(),
                Qt.KeepAspectRatio,
                Qt.SmoothTransformation
            )
            super().setPixmap(zoomed_pixmap)

    def leaveEvent(self, event):
        if self.original_pixmap:
            super().setPixmap(
                self.original_pixmap.scaled(
                    self.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation
                )
            )

class FrameExtractorUI(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Video Frame Extractor")
        self.setup_ui()

    def setup_ui(self):
        layout = QVBoxLayout(self)

        # Video Selection
        video_layout = QHBoxLayout()
        self.video_path_edit = QLineEdit()
        browse_button = QPushButton("Browse")
        browse_button.setCursor(Qt.PointingHandCursor)
        browse_button.clicked.connect(self.browse_video)
        video_layout.addWidget(QLabel("Video File:"))
        video_layout.addWidget(self.video_path_edit)
        video_layout.addWidget(browse_button)
        layout.addLayout(video_layout)

        # Output Directory Selection
        output_layout = QHBoxLayout()
        self.output_path_edit = QLineEdit()
        browse_output_button = QPushButton("Browse")
        browse_output_button.setCursor(Qt.PointingHandCursor)
        browse_output_button.clicked.connect(self.browse_output)
        output_layout.addWidget(QLabel("Output Folder:"))
        output_layout.addWidget(self.output_path_edit)
        output_layout.addWidget(browse_output_button)
        layout.addLayout(output_layout)

        # Settings Group
        settings_group = QGroupBox("Settings")
        settings_layout = QFormLayout()

        # Sharpness Threshold
        self.sharpness_slider = QSlider(Qt.Horizontal)
        self.sharpness_slider.setMinimum(100)
        self.sharpness_slider.setMaximum(1000)
        self.sharpness_slider.setValue(300)
        self.sharpness_value = QLabel("300")
        self.sharpness_slider.valueChanged.connect(
            lambda val: self.sharpness_value.setText(str(val))
        )

        # Overlap Threshold
        self.overlap_slider = QSlider(Qt.Horizontal)
        self.overlap_slider.setMinimum(10)
        self.overlap_slider.setMaximum(500)
        self.overlap_slider.setValue(50)
        self.overlap_value = QLabel("50")
        self.overlap_slider.valueChanged.connect(
            lambda val: self.overlap_value.setText(str(val))
        )

        # Brightness Adjustment
        self.brightness_slider = QSlider(Qt.Horizontal)
        self.brightness_slider.setMinimum(-100)
        self.brightness_slider.setMaximum(100)
        self.brightness_slider.setValue(0)
        self.brightness_value = QLabel("0")
        self.brightness_slider.valueChanged.connect(
            lambda val: self.brightness_value.setText(str(val))
        )

        # Contrast Adjustment
        self.contrast_slider = QSlider(Qt.Horizontal)
        self.contrast_slider.setMinimum(-100)
        self.contrast_slider.setMaximum(100)
        self.contrast_slider.setValue(0)
        self.contrast_value = QLabel("0")
        self.contrast_slider.valueChanged.connect(
            lambda val: self.contrast_value.setText(str(val))
        )

        # Saturation Adjustment
        self.saturation_slider = QSlider(Qt.Horizontal)
        self.saturation_slider.setMinimum(-100)
        self.saturation_slider.setMaximum(100)
        self.saturation_slider.setValue(0)
        self.saturation_value = QLabel("0")
        self.saturation_slider.valueChanged.connect(
            lambda val: self.saturation_value.setText(str(val))
        )

        # Shadow Removal
        self.shadow_removal_checkbox = QCheckBox("Enable Shadow Removal")
        self.shadow_removal_checkbox.setChecked(True)

        # Add widgets to settings_layout
        settings_layout.addRow("Sharpness Threshold:", self.sharpness_slider)
        settings_layout.addRow("", self.sharpness_value)
        settings_layout.addRow("Overlap Threshold:", self.overlap_slider)
        settings_layout.addRow("", self.overlap_value)
        settings_layout.addRow("Brightness Adjustment:", self.brightness_slider)
        settings_layout.addRow("", self.brightness_value)
        settings_layout.addRow("Contrast Adjustment:", self.contrast_slider)
        settings_layout.addRow("", self.contrast_value)
        settings_layout.addRow("Saturation Adjustment:", self.saturation_slider)
        settings_layout.addRow("", self.saturation_value)
        settings_layout.addRow(self.shadow_removal_checkbox)

        settings_group.setLayout(settings_layout)
        layout.addWidget(settings_group)

        # Start Button
        self.start_button = QPushButton("Start Extraction")
        self.start_button.setCursor(Qt.PointingHandCursor)
        self.start_button.clicked.connect(self.start_extraction)
        layout.addWidget(self.start_button)

        # Progress Bar and Label
        self.progress_bar = QProgressBar()
        self.progress_label = QLabel("Processing: 0%")
        layout.addWidget(self.progress_label)
        layout.addWidget(self.progress_bar)

        # Log Text
        self.log_text = QTextEdit()
        self.log_text.setReadOnly(True)
        layout.addWidget(QLabel("Log:"))
        layout.addWidget(self.log_text)

        # Selected Frames List
        self.selected_frames_list = QListWidget()
        layout.addWidget(QLabel("Selected Frames:"))
        layout.addWidget(self.selected_frames_list)

        # Remove Frame Button
        remove_button = QPushButton("Remove Selected Frame")
        remove_button.setCursor(Qt.PointingHandCursor)
        remove_button.clicked.connect(self.remove_selected_frame)
        layout.addWidget(remove_button)

        # Preview Section
        self.preview_label = QLabel("Preview:")
        layout.addWidget(self.preview_label)
        self.preview_image = PreviewLabel()
        layout.addWidget(self.preview_image)

    def browse_video(self):
        file_dialog = QFileDialog()
        path, _ = file_dialog.getOpenFileName(
            self, "Select Video File", "", "Videos (*.mp4 *.avi *.mov *.mkv)"
        )
        if path:
            self.video_path_edit.setText(path)

    def browse_output(self):
        dir_dialog = QFileDialog()
        path = dir_dialog.getExistingDirectory(self, "Select Output Folder")
        if path:
            self.output_path_edit.setText(path)

    def start_extraction(self):
        video_path = self.video_path_edit.text()
        output_dir = self.output_path_edit.text()
        sharpness_threshold = self.sharpness_slider.value()
        overlap_threshold = self.overlap_slider.value()
        brightness_adjustment = self.brightness_slider.value()
        contrast_adjustment = self.contrast_slider.value()
        saturation_adjustment = self.saturation_slider.value()
        shadow_removal_enabled = self.shadow_removal_checkbox.isChecked()

        if not os.path.isfile(video_path):
            QtWidgets.QMessageBox.critical(self, "Error", "Please select a valid video file.")
            return
        if not os.path.isdir(output_dir):
            QtWidgets.QMessageBox.critical(self, "Error", "Please select a valid output folder.")
            return

        self.start_button.setEnabled(False)
        self.log_text.clear()
        self.progress_bar.setValue(0)
        self.progress_label.setText("Processing: 0%")
        self.selected_frames_list.clear()

        self.thread = QThread()
        self.extractor = FrameExtractor(
            video_path, output_dir, sharpness_threshold, overlap_threshold,
            brightness_adjustment, shadow_removal_enabled, contrast_adjustment,
            saturation_adjustment
        )
        self.extractor.moveToThread(self.thread)
        self.thread.started.connect(self.extractor.run)
        self.extractor.progress.connect(self.update_progress)
        self.extractor.log.connect(self.update_log)
        self.extractor.finished.connect(self.extraction_finished)
        self.extractor.finished.connect(self.thread.quit)
        self.extractor.finished.connect(self.extractor.deleteLater)
        self.thread.finished.connect(self.thread.deleteLater)
        self.thread.start()

    def update_progress(self, value):
        self.progress_bar.setValue(value)
        self.progress_label.setText(f"Processing: {value}%")

    def update_log(self, message):
        self.log_text.append(message)

    def extraction_finished(self, frames):
        total_extracted = len(frames)
        self.log_text.append(f"Extraction completed. {total_extracted} frames extracted.")
        self.start_button.setEnabled(True)
        self.selected_frames_list.addItems(frames)

    def remove_selected_frame(self):
        selected_items = self.selected_frames_list.selectedItems()
        if not selected_items:
            return
        for item in selected_items:
            self.selected_frames_list.takeItem(self.selected_frames_list.row(item))

    def preview_frame(self, item):
        frame_path = item.text()
        image = QImage(frame_path)
        pixmap = QPixmap.fromImage(image)
        self.preview_image.setPixmap(pixmap)

class ImageQualityChecker(QtCore.QObject):
    log = pyqtSignal(str)
    finished = pyqtSignal(list)

    def __init__(self):
        super().__init__()
        self.image_files = []  # List to hold image file paths
        self.result_files = []  # List for selected files based on quality
        self.min_quality = 0  # Minimum quality threshold

    def load_images(self, files):
        self.image_files = list(files)
        return self.image_files

    def compute_quality(self, image_path) -> int:
        try:
            image = Image.open(image_path).convert('RGB')
            # Calculate brightness
            brightness = self.compute_brightness(image)
            # Calculate sharpness using OpenCV
            cv_image = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
            gray = cv2.cvtColor(cv_image, cv2.COLOR_BGR2GRAY)
            lap_var = cv2.Laplacian(gray, cv2.CV_64F).var()
            # Normalize sharpness to 0-100
            sharpness = min(100, int(lap_var / 100.0))
            # Structural Similarity Index
            if len(self.result_files) > 0:
                reference = cv2.imread(self.result_files[0], cv2.IMREAD_GRAYSCALE)
                current = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
                if reference is not None and current is not None:
                    ssim_index = ssim(reference, current)
                else:
                    ssim_index = 1  # Assume perfect similarity if failed
            else:
                ssim_index = 1  # Perfect similarity for the first image
            ssim_score = int(ssim_index * 100)
            # Combine metrics
            quality = min(100, (brightness + sharpness + ssim_score) // 3)
            return quality
        except Exception as e:
            self.log.emit(f"Error processing {os.path.basename(image_path)}: {str(e)}")
            return 0

    def compute_brightness(self, image: Image) -> int:
        grayscale_image = image.convert('L')
        histogram = grayscale_image.histogram()
        total_pixels = sum(histogram)
        brightness = sum(i * hist for i, hist in enumerate(histogram)) / total_pixels
        return int((brightness / 255) * 100)

    def evaluate_quality(self, min_quality):
        self.result_files.clear()
        if not self.image_files:
            self.log.emit("No images loaded for quality evaluation.")
            self.finished.emit([])
            return

        # Utilize multiprocessing for faster computation
        pool = multiprocessing.Pool(processes=multiprocessing.cpu_count())
        results = pool.map(self.compute_quality, self.image_files)
        pool.close()
        pool.join()

        for file, quality in zip(self.image_files, results):
            if quality >= min_quality:
                self.result_files.append(file)
                self.log.emit(f"{os.path.basename(file)} - Quality: {quality}")

        self.finished.emit(self.result_files)

    def get_results(self):
        return self.result_files

class ImageQualityCheckerUI(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Image Quality Checker")
        self.setup_ui()
        self.image_quality_checker = ImageQualityChecker()
        self.setup_signals()

    def setup_ui(self):
        layout = QVBoxLayout(self)

        # Load Images Button
        self.load_button = QPushButton("Load Images")
        self.load_button.setCursor(Qt.PointingHandCursor)
        self.load_button.clicked.connect(self.load_images)
        layout.addWidget(self.load_button)

        # Min Quality Input
        min_quality_layout = QHBoxLayout()
        self.min_quality_label = QLabel("Min Quality (0-100):")
        self.min_quality_entry = QLineEdit()
        self.min_quality_entry.setPlaceholderText("Enter minimum quality")
        min_quality_layout.addWidget(self.min_quality_label)
        min_quality_layout.addWidget(self.min_quality_entry)
        layout.addLayout(min_quality_layout)

        # Evaluate Button
        self.evaluate_button = QPushButton("Evaluate Quality")
        self.evaluate_button.setCursor(Qt.PointingHandCursor)
        self.evaluate_button.clicked.connect(self.evaluate_quality)
        layout.addWidget(self.evaluate_button)

        # Images List
        self.listbox = QListWidget()
        layout.addWidget(QLabel("Loaded Images:"))
        layout.addWidget(self.listbox)

        # Results Text
        self.result_text = QTextEdit()
        self.result_text.setReadOnly(True)
        layout.addWidget(QLabel("Results:"))
        layout.addWidget(self.result_text)

    def setup_signals(self):
        self.image_quality_checker.log.connect(self.update_log)
        self.image_quality_checker.finished.connect(self.display_results)

    def load_images(self):
        files, _ = QFileDialog.getOpenFileNames(
            self, "Select Images", "", "Image Files (*.png;*.jpg;*.jpeg;*.gif;*.bmp;*.tiff;*.webp)"
        )
        if files:
            self.image_quality_checker.load_images(files)
            self.update_listbox()

    def update_listbox(self):
        self.listbox.clear()
        for file in self.image_quality_checker.image_files:
            self.listbox.addItem(os.path.basename(file))

    def evaluate_quality(self):
        min_quality_text = self.min_quality_entry.text()
        try:
            min_quality = int(min_quality_text)
            if not (0 <= min_quality <= 100):
                raise ValueError
            self.image_quality_checker.min_quality = min_quality
        except ValueError:
            QtWidgets.QMessageBox.critical(
                self, "Invalid Input", "Please enter a valid number between 0 and 100 for Min Quality."
            )
            return

        if not self.image_quality_checker.image_files:
            QtWidgets.QMessageBox.information(
                self, "No Images", "Please load images before evaluating quality."
            )
            return

        self.result_text.clear()
        self.evaluate_button.setEnabled(False)
        self.load_button.setEnabled(False)
        self.result_text.append("Starting quality evaluation...\n")
        QThreadPool.globalInstance().start(self.run_evaluation)

    def run_evaluation(self):
        self.image_quality_checker.evaluate_quality(self.image_quality_checker.min_quality)

    def update_log(self, message):
        self.result_text.append(message)

    def display_results(self, results):
        self.evaluate_button.setEnabled(True)
        self.load_button.setEnabled(True)
        if results:
            self.result_text.append("\nEvaluation completed.")
            self.result_text.append(f"Total images meeting quality criteria: {len(results)}")
        else:
            self.result_text.append("\nNo images meet the minimum quality requirement.")

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Image Processing Application")
        self.setGeometry(100, 100, 900, 700)
        self.setup_ui()

    def setup_ui(self):
        self.tabs = QTabWidget()
        self.setCentralWidget(self.tabs)

        self.frame_extractor_widget = FrameExtractorUI()
        self.image_quality_checker_widget = ImageQualityCheckerUI()

        self.tabs.addTab(self.frame_extractor_widget, "Video Frame Extractor")
        self.tabs.addTab(self.image_quality_checker_widget, "Image Quality Checker")

def main():
    app = QApplication(sys.argv)
    app.setStyle("Fusion")
    app.setStyleSheet(DARK_STYLE)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())

if __name__ == "__main__":
    main()