Projektaufbau – Entrauschungs-KI: Unterschied zwischen den Versionen

Aus HSHL Mechatronik
Zur Navigation springen Zur Suche springen
Adrian.klinspon@stud.hshl.de (Diskussion | Beiträge)
Adrian.klinspon@stud.hshl.de (Diskussion | Beiträge)
Keine Bearbeitungszusammenfassung
 
(7 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt)
Zeile 1: Zeile 1:
{| class="wikitable"
|-
| '''Autoren:''' || Fabian Babik, [[Benutzer:Adrian.klinspon@stud.hshl.de| Adrian Klinspon]]
|-
| '''Art:''' || [[Signalverarbeitung mit MATLAB und künstlicher Intelligenz (KI) - Praxissemester|Projekt im Praxissemester]]
|-
| '''Dauer:''' || 29.09.2025 bis 09.01.2026
|-
| '''Betreuer:''' || [[Benutzer:Ulrich_Schneider|Prof. Dr.-Ing. Schneider]]
|}
= Aufbau =
= Aufbau =
Dieses Projekt implementiert ein Denoising Convolutional Neural Network (DnCNN) in MATLAB unter Verwendung von Residual Learning. Es dient dazu, Bildrauschen aus digitalen Bildern zu entfernen. Im Gegensatz zu klassischen Ansätzen lernt das Netz hierbei das Rauschen selbst (Residual) und subtrahiert es vom Eingangsbild.
Dieses Projekt implementiert ein Denoising Convolutional Neural Network (DnCNN) in MATLAB unter Verwendung von Residual Learning. Es dient dazu, Bildrauschen aus digitalen Bildern zu entfernen. Im Gegensatz zu klassischen Ansätzen lernt das Netz hierbei das Rauschen selbst (Residual) und subtrahiert es vom Eingangsbild.
Zeile 9: Zeile 29:
* Parallel Computing Toolbox
* Parallel Computing Toolbox


= Training (<code>DnCnnTrain.m</code>)=
= Training (<code>TrainDnCNN.m</code>)=
Trainiert das neuronale Netz mit Bildern aus dem Ordner <code>./ImagesForTraining</code>. Dieser Ordner enthält die Trainingsdaten, zusammengestellt aus den Datensätzen BSDS500 und DIV2K.
Trainiert das neuronale Netz mit Bildern aus dem Ordner <code>./ImagesForTraining</code>. Dieser Ordner enthält die Trainingsdaten, zusammengestellt aus den Datensätzen BSDS500 und DIV2K.


== Funktionsweise ==
== Funktionsweise ==
Das Skript prüft zunächst, ob bereits generierte Bild-Patches existieren und fragt den Nutzer via Dialog, ob diese neu erstellt werden sollen oder ob ein bereits existierendes KI-Modell weiter trainiert werden soll.
Das Skript prüft, ob Patches existieren, und bietet via Dialog an, neue zu erstellen.
* '''Daten-Verarbeitung'''
* '''Daten-Verarbeitung'''
** '''Quellen:''' Bilder werden aus <code>./ImagesForTraining</code> geladen.
** '''Quellen:''' Bilder werden aus <code>./ImagesForTraining</code> (und Unterordnern) geladen.
** '''Patch-Extraktion:''' Bilder werden in 50x50 Pixel große Ausschnitte zerlegt (Stride: 30px, Overlap: 20px).
** '''Filterung:''' Nur RGB-Bilder, die größer als die Patches sind, werden verarbeitet.
** '''Filterung:''' Nur RGB-Bilder, die größer als die Patches sind, werden verarbeitet.
** '''Patch-Extraktion:''' Bilder werden in 50x50 Pixel große Quadrate zerlegt (Stride: 30px).
** '''Speicherung:''' Patches werden physisch im Ordner <code>./PatchesCreatedByThisScriptInOrderToProperlyTrainTheAI</code> abgelegt.
** '''Speicherung:''' Patches werden physisch im Ordner <code>./PatchesCreatedByThisScriptInOrderToProperlyTrainTheAI</code> abgelegt.
* '''Datensatz-Split:''' Die erstellten Patches werden im Verhältnis 90:10 in Trainings- und Validierungsdaten unterteilt (<code>trainSplit = 0.9</code>)
* '''Datensatz-Split:''' Die erstellten Patches werden im Verhältnis 90:10 in Trainings- und Validierungsdaten unterteilt (<code>trainSplit = 0.9</code>)
Zeile 26: Zeile 46:
# '''Train new AI:''' Startet mit einer Lernrate von <code>1e-3</code>.
# '''Train new AI:''' Startet mit einer Lernrate von <code>1e-3</code>.
# '''Train old AI:''' Lädt trainierte AI und setzt das Training mit einer feineren Lernrate von <code>1e-4</code> fort.
# '''Train old AI:''' Lädt trainierte AI und setzt das Training mit einer feineren Lernrate von <code>1e-4</code> fort.
* '''Speicherung:''' Das beste Ergebnis (gemessen am Validation-Loss) wird gespeichert. Das finale Netz wird unter <code>.KIs/Neue_KI.mat</code> abgelegt.


== Code-Auszug ==
== Code-Auszug ==
<syntaxhighlight lang="matlab">% --- Pfade & Grundkonfiguration ---
<syntaxhighlight lang="matlab">
pathToImages = './ImagesForTraining';
% --- Grundkonfiguration & Parameter ---
pathToImages = './ImagesForTraining';  
patchDirectory = './PatchesCreatedByThisScriptInOrderToProperlyTrainTheAI';
patchDirectory = './PatchesCreatedByThisScriptInOrderToProperlyTrainTheAI';
pathToSavedAI  = './KIs/AI.mat';     
patchSize = 50;     
stride = 30;         
trainSplit = 0.9;    % 90% Training, 10% Validierung
maxPatchesPerImage = 1500;
% GPU aktivieren
parallel.gpu.enableCUDAForwardCompatibility(true);
% --- Benutzer-Dialog 1: Patches erstellen? ---
areNewPatchesNeeded = false;
if ~isempty(dir(fullfile(patchDirectory, '*.png')))
    recreatePatches = questdlg('Patches already Exist. Do you want do Delete them and Create new ones?', ...
        'Create new Patches?', 'Yes', 'No', 'Yes');


    if strcmp(recreatePatches, 'Yes')
patchSize = 50;          % Patch-Größe: 50x50 Pixel
        areNewPatchesNeeded = true;
stride = 30;              % Versatz (Stride)
         delete(fullfile(patchDirectory, '*.png')); % Löscht alte Patches
trainSplit = 0.9;        % 90% Training, 10% Validierung
    end
maxPatchesPerImage = 600; % Max. 600 Patches pro Bild
else
networkDepth = 15;       % Tiefe des Netzwerks
    areNewPatchesNeeded = true;
end


% (Hier folgt im Skript der Code zur Erstellung der Patches, falls areNewPatchesNeeded == true)
% --- (Code-Abschnitt zur Patch-Erstellung hier ausgeblendet) ---
% ... Bilder werden geladen, geprüft und in Patches zerlegt ...


% --- Datastore & Split Logik ---
% --- Datensatz vorbereiten ---
% Lädt alle Patches
% Lädt alle Patches und konvertiert sie zu Double
allPatches = imageDatastore(patchDirectory, 'ReadFcn', @(f)im2double(imread(f)));
allPatches = imageDatastore(patchDirectory, 'ReadFcn', @(f)im2double(imread(f)));


% Zufällige Aufteilung in Training (90%) und Validierung (10%)
% Zufällige Aufteilung (Split)
numFiles = numel(allPatches.Files);
numFiles = numel(allPatches.Files);
idx = randperm(numFiles); % Erzeugt zufällige Indizes
idx = randperm(numFiles);
trainFiles = allPatches.Files(idx(1:round(trainSplit * numFiles)));
trainFiles = allPatches.Files(idx(1:round(trainSplit * numFiles)));
valFiles = allPatches.Files(idx(round(trainSplit * numFiles) + 1:end));
valFiles   = allPatches.Files(idx(round(trainSplit * numFiles) + 1:end));


% Datastores mit Rauschen (addNoise Funktion)
% Datastores mit dynamischem Rauschen (addNoise Funktion) erstellen
dsTrain = transform(imageDatastore(trainFiles), @(x)addNoise(x, useResidual));
dsTrain = transform(imageDatastore(trainFiles), @(x)addNoise(x, true));
dsVal  = transform(imageDatastore(valFiles),  @(x)addNoise(x, useResidual));
dsVal  = transform(imageDatastore(valFiles),  @(x)addNoise(x, true));


% --- Benutzer-Dialog 2: Training Modus ---
% --- Netzwerk-Architektur & Modus ---
trainNewOrOldAI = questdlg('Would you like to...', 'Training Options', 'Train new AI', 'Train old AI', 'Train new AI');
% Abfrage: Neue KI trainieren oder alte fortsetzen?
initialLRate = 1e-3; % Standard Lernrate
if strcmp(trainNewOrOldAI, 'Neue KI trainieren')
    initialLRate = 1e-3;
   
    % 1. Eingabe-Block (Start)
    layers = [
        imageInputLayer([patchSize patchSize 3], "Normalization", "none")
        convolution2dLayer(3, 64, 'Padding', 'same')
        batchNormalizationLayer
        reluLayer
    ];


if strcmp(trainNewOrOldAI, 'Train old AI')
    % 2. Mittlere Layer (Schleife für Tiefe)
     if exist(pathToSavedAI, 'file')
     for i = 2:(networkDepth-1)
         loadNet = load(pathToSavedAI);
         layers = [
        lGraph = layerGraph(loadNet.net);
            layers
        initialLRate = 1e-4; % Feinere Lernrate für Nachtraining
            convolution2dLayer(3, 64, 'Padding', 'same')
    else
            batchNormalizationLayer
         trainNewOrOldAI = 'Train new AI'; % Fallback
            reluLayer
         ];
     end
     end
    % 3. Ausgabe-Block (Ende)
    layers = [
        layers
        convolution2dLayer(3, 3, "Padding","same")
        batchNormalizationLayer
        regressionLayer
    ];
   
    lGraph = layerGraph(layers);
else
    % Alte KI laden
    loadNet = load('./KIs/Alte_KI.mat');
    lGraph = layerGraph(loadNet.net);
    initialLRate = 1e-4; % Feinere Lernrate
end
end


% --- Training Starten ---
% --- Training Starten ---
options = trainingOptions("adam", ...
options = trainingOptions("adam", ...
     'MaxEpochs', 70, ...
     'MaxEpochs', 50, ...
     'MiniBatchSize', 128, ...
     'MiniBatchSize', 128, ...
     'InitialLearnRate', initialLRate, ...
     'InitialLearnRate', initialLRate, ...
     'LearnRateSchedule', 'piecewise', ...
     'LearnRateSchedule', 'piecewise', ...
    'LearnRateDropFactor', 0.3, ...
    'LearnRateDropPeriod', 12, ...
     'ValidationData', dsVal, ...
     'ValidationData', dsVal, ...
     'CheckpointPath', './Checkpoints/LAB29-06', ...
     'OutputNetwork', 'best-validation-loss', ...
     'OutputNetwork', 'best-validation-loss');
     'Plots', 'training-progress');


net = trainNetwork(dsTrain, lgraph, options);
net = trainNetwork(dsTrain, lGraph, options);
save(pathToSavedAI, 'net');</syntaxhighlight>
save('.KIs/Neue_AI.mat', 'net');
</syntaxhighlight>


= Evaluierung (<code>DnCnnEval.m</code>) =
= Evaluierung (<code>EvalDnCNN.m</code>) =
Wendet das trainierte Modell auf neue Bilder im Ordner <code>./Testbilder</code> an.
Wendet das trainierte Modell auf neue Bilder im Ordner <code>./Testbilder</code> an und speichert die Ergebnisse in einem Unterordner.


== Funktionsweise ==
== Funktionsweise ==
* '''Preprocessing:''' Lädt Bilder und konvertiert Graustufenbilder künstlich in 3-Kanal-Bilder (RGB), um die Kompatibilität mit dem Netz zu gewährleisten.
* '''Dateimanagement:''' Das Skript sucht automatisch nach diversen Bildformaten (.png, .jpg, .tif, etc.) und erstellt einen Ausgabeordner.
* '''Rauschen:''' Falls das Bild als "sauber" markiert ist, fügt das Skript automatisch Gaußsches Rauschen hinzu.
* '''Preprocessing:''' Lädt Bilder, wandelt sie in `double` um und konvertiert Graustufenbilder künstlich in 3-Kanal-Bilder (RGB), da das Netz RGB erwartet.
* '''Visualisierung:''' Erstellt eine "Input vs. Output"-Collage.
* '''Rauschen:''' Über das Flag <code>isImageNoisy</code> wird gesteuert, ob das Bild bereits verrauscht ist. Falls nicht (0), fügt das Skript automatisch Gaußsches Rauschen hinzu.
* '''Entrauschen:''' Das Bild wird durch das Netz geschickt (<code>denoiseImages</code>) und anschließend mittels <code>imresize</code> sicherheitshalber auf die Originalgröße zurückskaliert.
* '''Fehlerbehandlung:''' Durch einen <code>try-catch</code>-Block stürzt das Skript nicht ab, falls eine einzelne Datei beschädigt ist.
* '''Visualisierung:''' Erstellt eine Collage (Original | Weißer Trennstrich | Ergebnis) mit dynamischer Schriftgröße für die Beschriftung ("Input", "Output").


== Code-Auszug ==
== Code-Auszug ==
<syntaxhighlight lang="matlab">% Modell laden
<syntaxhighlight lang="matlab">
clear; clc; close all;
 
% --- Parameter ---
imageFolder = './TestBilder';  % Verzeichnis mit den Testbildern
isImageNoisy = 1;              % 1 = Bilder sind schon verrauscht, 0 = Rauschen hinzufügen
 
% --- Output Konfiguration ---
outputFolder = fullfile(imageFolder, 'KI'); % Zielpfad
if ~exist(outputFolder, 'dir')
    mkdir(outputFolder); % Erstellt Ordner, falls nicht vorhanden
end
 
% --- Modell laden ---
if ~exist('net', 'var')
if ~exist('net', 'var')
     data = load("Path/AI_InitialLearnRate_1e-3.mat");
    % Lädt das trainierte Netzwerk (.mat Datei)
     net = data.net;
     data = load('KIs/KI.mat');  
     net = data.net;  
end
end


% Rauschen hinzufügen (falls nötig)
% --- Bilder suchen (Auszug) ---
if ~isImageNoisy
% (Hier sucht das Skript nach .png, .jpg, .tif, etc. und füllt 'allImages')
    imgDouble = imnoise(imgDouble, "gaussian", 0, 0.03);
% ...
end
 
% --- Verarbeitungs-Schleife ---
fprintf('Verarbeite %d Bilder. Bitte warten...\n', numel(allImages));
 
for k = 1:numel(allImages)
    try
        % Bild laden und Basisnamen extrahieren
        fullFileName = fullfile(allImages(k).folder, allImages(k).name);
        [~, baseName, ~] = fileparts(allImages(k).name);
       
        imgDouble = im2double(imread(fullFileName));
 
        % Graustufen zu RGB konvertieren falls nötig
        if size(imgDouble, 3) == 1
            imgDouble = cat(3, imgDouble, imgDouble, imgDouble);
        end
       
        [h, w, ~] = size(imgDouble); % Original-Dimensionen speichern
 
        % Rauschen simulieren (falls Flag auf 0)
        if ~isImageNoisy
            imgDouble = imnoise(imgDouble, "gaussian", 0, 0.03);
        end
 
        % --- Entrauschen ---
        % Wendet das Netz an
        imgRestored = denoiseImages(net, imgDouble, 50, 20, true);
       
        % Auf Originalgröße zurückskalieren & RGB sicherstellen
        imgRestored = imresize(imgRestored, [h, w]);
        if size(imgRestored, 3) == 1
            imgRestored = cat(3, imgRestored, imgRestored, imgRestored);
        end
 
        % --- Collage erstellen (Input | Separator | Output) ---
        separatorWidth = 20;
        separator = ones(h, separatorWidth, 3); % Weißer Streifen
        collage = [imgDouble, separator, imgRestored];
 
        % Beschriftung und Positionierung
        textLabels = {'Input', 'Output'};
        positions = [10, 10; (w + separatorWidth + 10), 10];


% Entrauschen
        collage = insertText(collage, positions, textLabels, ...
imgRestored = denoiseImages(net, imgDouble, 50, 50/2, true);
            'FontSize', max(20, round(h/25)), ... % Dynamische Schriftgröße
            'BoxColor', 'white', 'BoxOpacity', 0.7, 'TextColor', 'black');


% Collage erstellen (Input | Separator | Output)
        % Speichern
separatorWidth = 20;
        saveName = fullfile(outputFolder, [baseName '_Result.jpg']);
separator = ones(h, separatorWidth, 3);
        imwrite(collage, saveName);
collage = [imgDouble, separator, imgRestored];


% Beschriftung einfügen
    catch ME
textLabels = {'Input', 'Output'};
        % Fehler abfangen und Loop fortsetzen
positions = [10, 10; (w + separatorWidth + 10), 10];
        fprintf('Konnte %s nicht speichern: %s\n', allImages(k).name, ME.message);
    end
end
</syntaxhighlight>


collage = insertText(collage, positions, textLabels, ...
= Rausch-Generator Tool (<code>addNoise.m</code>) =
    'FontSize', max(20, round(h/25)), ...
Diese Funktion dient der dynamischen Datenaufbereitung (Data Augmentation) während des Trainings. Sie wird vom `imageDatastore` aufgerufen, um jedem sauberen Bild Rauschen hinzuzufügen und das Trainingsziel zu definieren.
    'BoxColor', 'white', ...
    'BoxOpacity', 0.7);


imwrite(collage, saveName);</syntaxhighlight>
== Funktionsweise==
* '''Input:''' Erwartet ein sauberes Bild (`clean`) und das Flag für Residual Learning (`useResidual`).
* '''Dynamisches Rauschen:''' Es wird zufällig (50/50 Chance) zwischen '''Gaußschem Rauschen''' und '''Salt & Pepper''' gewählt.
* '''Variable Intensität:''' Die Stärke des Rauschens ist nicht statisch, sondern variiert zufällig zwischen **0.01 und 0.10** (`0.01 + rand()*0.09`).
* '''Residual Learning:'''
** Ist <code>useResidual = true</code>, berechnet die Funktion die Differenz (<code>noisy - clean</code>). Das Netz lernt also nur, das Rauschen vorherzusagen.
** Ist <code>useResidual = false</code>, ist das Ziel das saubere Originalbild.
* '''Output:''' Gibt ein Paar <code>{verrauschtesBild, ZielBild}</code> zurück.


= Rausch-Generator Tool (<code>DnCNNNoise.m</code>) =
== Code ==
Ein Hilfsskript, um einen kompletten Ordner von Bildern permanent zu verrauschen, falls Testdaten benötigt werden.
<syntaxhighlight lang="matlab">
function pair = addNoise(clean, useResidual)
    clean = im2double(clean); % Konvertiert Bild in Double-Format (0 bis 1)


== Funktionsweise & Code ==
    % Zufällige Auswahl des Rausch-Typs (50% Chance)
Nutzt eine grafische Oberfläche (GUI) zur Ordnerauswahl und die Image Processing Toolbox (<code>imnoise</code>), um mathematisch exaktes Rauschen hinzuzufügen.<br>
    if rand() > 0.5
<syntaxhighlight lang="matlab">% User Interface für Auswahl
        % Gaußsches Rauschen mit zufälliger Varianz (0.01 bis 0.10)
noiseChoice = questdlg('Which noise for ALL images?', 'Noise Type', ...
        noisy = imnoise(clean,'gaussian', 0, 0.01 + rand() * 0.09);
     'Salt & Pepper', 'Gaussian', 'Speckle', 'Gaussian');
     else
        % Salt & Pepper Rauschen mit zufälliger Dichte (0.01 bis 0.10)
        noisy = imnoise(clean, 'salt & pepper', 0.01 + rand() * 0.09);
    end


% Rauschen anwenden
    % Berechnung des Trainingsziels (Target)
switch noiseChoice
     % Wenn useResidual == true: Target = (noisy - clean) -> Nur das Rauschen lernen
     case 'Salt & Pepper'
     % Wenn useResidual == false: Target = clean          -> Das saubere Bild lernen
        % Fügt "Salz und Pfeffer"-Rauschen hinzu (Dichte: 0.05)
    target = useResidual * (noisy - clean) + ~useResidual * clean;
        imgNoisy = imnoise(img, 'salt & pepper', 0.05);
 
     case 'Gaussian'
     pair = {noisy, target}; % Rückgabe als Paar für den Datastore
        % Fügt Gaußsches Rauschen hinzu (Mittelwert 0, Varianz 0.01)
end
        imgNoisy = imnoise(img, 'gaussian', 0, 0.01);
</syntaxhighlight>
     case 'Speckle'
        % Fügt multiplikatives Rauschen hinzu (Varianz 0.04)
        imgNoisy = imnoise(img, 'speckle', 0.04);
end</syntaxhighlight>


= Quellen =
= Quellen =
* P. Arbeláez, M. Maire, C. Fowlkes and J. Malik, "Contour Detection and Hierarchical Image Segmentation," in IEEE Transactions on Pattern Analysis and Machine Intelligence, vol. 33, no. 5, pp. 898-916, May 2011, doi: 10.1109/TPAMI.2010.161. keywords: {Image segmentation;Pixel;Detectors;Image edge detection;Humans;Histograms;Benchmark testing;Contour detection;image segmentation;computer vision.}
* P. Arbeláez, M. Maire, C. Fowlkes and J. Malik, "Contour Detection and Hierarchical Image Segmentation," in IEEE Transactions on Pattern Analysis and Machine Intelligence, vol. 33, no. 5, pp. 898-916, May 2011, doi: 10.1109/TPAMI.2010.161. keywords: {Image segmentation;Pixel;Detectors;Image edge detection;Humans;Histograms;Benchmark testing;Contour detection;image segmentation;computer vision.}
* R. Timofte, S. Gu, J. Wu, L. Van Gool, L. Zhang, M.-H. Yang, M. Haris et al., "NTIRE 2018 Challenge on Single Image Super-Resolution: Methods and Results," in The IEEE Conference on Computer Vision and Pattern Recognition (CVPR) Workshops, June 2018.
* R. Timofte, S. Gu, J. Wu, L. Van Gool, L. Zhang, M.-H. Yang, M. Haris et al., "NTIRE 2018 Challenge on Single Image Super-Resolution: Methods and Results," in The IEEE Conference on Computer Vision and Pattern Recognition (CVPR) Workshops, June 2018.

Aktuelle Version vom 4. Februar 2026, 15:12 Uhr

Autoren: Fabian Babik, Adrian Klinspon
Art: Projekt im Praxissemester
Dauer: 29.09.2025 bis 09.01.2026
Betreuer: Prof. Dr.-Ing. Schneider

Aufbau

Dieses Projekt implementiert ein Denoising Convolutional Neural Network (DnCNN) in MATLAB unter Verwendung von Residual Learning. Es dient dazu, Bildrauschen aus digitalen Bildern zu entfernen. Im Gegensatz zu klassischen Ansätzen lernt das Netz hierbei das Rauschen selbst (Residual) und subtrahiert es vom Eingangsbild.

Technische Voraussetzungen

MATLAB mit folgenden Toolboxen:

  • Deep Learning Toolbox
  • Image Processing Toolbox
  • Computer Vision Toolbox
  • Parallel Computing Toolbox

Training (TrainDnCNN.m)

Trainiert das neuronale Netz mit Bildern aus dem Ordner ./ImagesForTraining. Dieser Ordner enthält die Trainingsdaten, zusammengestellt aus den Datensätzen BSDS500 und DIV2K.

Funktionsweise

Das Skript prüft, ob Patches existieren, und bietet via Dialog an, neue zu erstellen.

  • Daten-Verarbeitung
    • Quellen: Bilder werden aus ./ImagesForTraining (und Unterordnern) geladen.
    • Patch-Extraktion: Bilder werden in 50x50 Pixel große Ausschnitte zerlegt (Stride: 30px, Overlap: 20px).
    • Filterung: Nur RGB-Bilder, die größer als die Patches sind, werden verarbeitet.
    • Speicherung: Patches werden physisch im Ordner ./PatchesCreatedByThisScriptInOrderToProperlyTrainTheAI abgelegt.
  • Datensatz-Split: Die erstellten Patches werden im Verhältnis 90:10 in Trainings- und Validierungsdaten unterteilt (trainSplit = 0.9)
  • Noise-Augmentation: Während des Trainings wird dynamisch Rauschen mittels einer Helper-Funktion (addNoise) hinzugefügt.
  • Architektur: Ein 15-Layer tiefes CNN mit Residual Learning.
  • Optimiser: Verwendet ADAM mit dynamischer Anpassung der Lernrate (Piecewise Drop).
  • Training-Modi: Über einen Dialog kann gewählt werden zwischen:
  1. Train new AI: Startet mit einer Lernrate von 1e-3.
  2. Train old AI: Lädt trainierte AI und setzt das Training mit einer feineren Lernrate von 1e-4 fort.
  • Speicherung: Das beste Ergebnis (gemessen am Validation-Loss) wird gespeichert. Das finale Netz wird unter .KIs/Neue_KI.mat abgelegt.

Code-Auszug

% --- Grundkonfiguration & Parameter ---
pathToImages = './ImagesForTraining'; 
patchDirectory = './PatchesCreatedByThisScriptInOrderToProperlyTrainTheAI';

patchSize = 50;           % Patch-Größe: 50x50 Pixel
stride = 30;              % Versatz (Stride)
trainSplit = 0.9;         % 90% Training, 10% Validierung
maxPatchesPerImage = 600; % Max. 600 Patches pro Bild
networkDepth = 15;        % Tiefe des Netzwerks

% --- (Code-Abschnitt zur Patch-Erstellung hier ausgeblendet) ---
% ... Bilder werden geladen, geprüft und in Patches zerlegt ...

% --- Datensatz vorbereiten ---
% Lädt alle Patches und konvertiert sie zu Double
allPatches = imageDatastore(patchDirectory, 'ReadFcn', @(f)im2double(imread(f)));

% Zufällige Aufteilung (Split)
numFiles = numel(allPatches.Files);
idx = randperm(numFiles);
trainFiles = allPatches.Files(idx(1:round(trainSplit * numFiles)));
valFiles   = allPatches.Files(idx(round(trainSplit * numFiles) + 1:end));

% Datastores mit dynamischem Rauschen (addNoise Funktion) erstellen
dsTrain = transform(imageDatastore(trainFiles), @(x)addNoise(x, true));
dsVal   = transform(imageDatastore(valFiles),   @(x)addNoise(x, true));

% --- Netzwerk-Architektur & Modus ---
% Abfrage: Neue KI trainieren oder alte fortsetzen?
if strcmp(trainNewOrOldAI, 'Neue KI trainieren')
    initialLRate = 1e-3;
    
    % 1. Eingabe-Block (Start)
    layers = [
        imageInputLayer([patchSize patchSize 3], "Normalization", "none")
        convolution2dLayer(3, 64, 'Padding', 'same')
        batchNormalizationLayer
        reluLayer
    ];

    % 2. Mittlere Layer (Schleife für Tiefe)
    for i = 2:(networkDepth-1)
        layers = [
            layers
            convolution2dLayer(3, 64, 'Padding', 'same')
            batchNormalizationLayer
            reluLayer
        ];
    end

    % 3. Ausgabe-Block (Ende)
    layers = [
        layers
        convolution2dLayer(3, 3, "Padding","same")
        batchNormalizationLayer
        regressionLayer
    ];
    
    lGraph = layerGraph(layers);
else
    % Alte KI laden
    loadNet = load('./KIs/Alte_KI.mat');
    lGraph = layerGraph(loadNet.net);
    initialLRate = 1e-4; % Feinere Lernrate
end

% --- Training Starten ---
options = trainingOptions("adam", ...
    'MaxEpochs', 50, ...
    'MiniBatchSize', 128, ...
    'InitialLearnRate', initialLRate, ...
    'LearnRateSchedule', 'piecewise', ...
    'LearnRateDropFactor', 0.3, ...
    'LearnRateDropPeriod', 12, ...
    'ValidationData', dsVal, ...
    'OutputNetwork', 'best-validation-loss', ...
    'Plots', 'training-progress');

net = trainNetwork(dsTrain, lGraph, options);
save('.KIs/Neue_AI.mat', 'net');

Evaluierung (EvalDnCNN.m)

Wendet das trainierte Modell auf neue Bilder im Ordner ./Testbilder an und speichert die Ergebnisse in einem Unterordner.

Funktionsweise

  • Dateimanagement: Das Skript sucht automatisch nach diversen Bildformaten (.png, .jpg, .tif, etc.) und erstellt einen Ausgabeordner.
  • Preprocessing: Lädt Bilder, wandelt sie in `double` um und konvertiert Graustufenbilder künstlich in 3-Kanal-Bilder (RGB), da das Netz RGB erwartet.
  • Rauschen: Über das Flag isImageNoisy wird gesteuert, ob das Bild bereits verrauscht ist. Falls nicht (0), fügt das Skript automatisch Gaußsches Rauschen hinzu.
  • Entrauschen: Das Bild wird durch das Netz geschickt (denoiseImages) und anschließend mittels imresize sicherheitshalber auf die Originalgröße zurückskaliert.
  • Fehlerbehandlung: Durch einen try-catch-Block stürzt das Skript nicht ab, falls eine einzelne Datei beschädigt ist.
  • Visualisierung: Erstellt eine Collage (Original | Weißer Trennstrich | Ergebnis) mit dynamischer Schriftgröße für die Beschriftung ("Input", "Output").

Code-Auszug

clear; clc; close all;

% --- Parameter ---
imageFolder = './TestBilder';   % Verzeichnis mit den Testbildern
isImageNoisy = 1;               % 1 = Bilder sind schon verrauscht, 0 = Rauschen hinzufügen

% --- Output Konfiguration ---
outputFolder = fullfile(imageFolder, 'KI'); % Zielpfad
if ~exist(outputFolder, 'dir')
    mkdir(outputFolder); % Erstellt Ordner, falls nicht vorhanden
end

% --- Modell laden ---
if ~exist('net', 'var')
    % Lädt das trainierte Netzwerk (.mat Datei)
    data = load('KIs/KI.mat'); 
    net = data.net; 
end

% --- Bilder suchen (Auszug) ---
% (Hier sucht das Skript nach .png, .jpg, .tif, etc. und füllt 'allImages')
% ...

% --- Verarbeitungs-Schleife ---
fprintf('Verarbeite %d Bilder. Bitte warten...\n', numel(allImages));

for k = 1:numel(allImages)
    try
        % Bild laden und Basisnamen extrahieren
        fullFileName = fullfile(allImages(k).folder, allImages(k).name);
        [~, baseName, ~] = fileparts(allImages(k).name);
        
        imgDouble = im2double(imread(fullFileName)); 

        % Graustufen zu RGB konvertieren falls nötig
        if size(imgDouble, 3) == 1
            imgDouble = cat(3, imgDouble, imgDouble, imgDouble);
        end
        
        [h, w, ~] = size(imgDouble); % Original-Dimensionen speichern

        % Rauschen simulieren (falls Flag auf 0)
        if ~isImageNoisy
            imgDouble = imnoise(imgDouble, "gaussian", 0, 0.03);
        end

        % --- Entrauschen ---
        % Wendet das Netz an
        imgRestored = denoiseImages(net, imgDouble, 50, 20, true);
        
        % Auf Originalgröße zurückskalieren & RGB sicherstellen
        imgRestored = imresize(imgRestored, [h, w]);
        if size(imgRestored, 3) == 1
            imgRestored = cat(3, imgRestored, imgRestored, imgRestored);
        end

        % --- Collage erstellen (Input | Separator | Output) ---
        separatorWidth = 20; 
        separator = ones(h, separatorWidth, 3); % Weißer Streifen
        collage = [imgDouble, separator, imgRestored]; 

        % Beschriftung und Positionierung
        textLabels = {'Input', 'Output'};
        positions = [10, 10; (w + separatorWidth + 10), 10];

        collage = insertText(collage, positions, textLabels, ...
            'FontSize', max(20, round(h/25)), ... % Dynamische Schriftgröße
            'BoxColor', 'white', 'BoxOpacity', 0.7, 'TextColor', 'black');

        % Speichern
        saveName = fullfile(outputFolder, [baseName '_Result.jpg']);
        imwrite(collage, saveName);

    catch ME
        % Fehler abfangen und Loop fortsetzen
        fprintf('Konnte %s nicht speichern: %s\n', allImages(k).name, ME.message);
    end
end

Rausch-Generator Tool (addNoise.m)

Diese Funktion dient der dynamischen Datenaufbereitung (Data Augmentation) während des Trainings. Sie wird vom `imageDatastore` aufgerufen, um jedem sauberen Bild Rauschen hinzuzufügen und das Trainingsziel zu definieren.

Funktionsweise

  • Input: Erwartet ein sauberes Bild (`clean`) und das Flag für Residual Learning (`useResidual`).
  • Dynamisches Rauschen: Es wird zufällig (50/50 Chance) zwischen Gaußschem Rauschen und Salt & Pepper gewählt.
  • Variable Intensität: Die Stärke des Rauschens ist nicht statisch, sondern variiert zufällig zwischen **0.01 und 0.10** (`0.01 + rand()*0.09`).
  • Residual Learning:
    • Ist useResidual = true, berechnet die Funktion die Differenz (noisy - clean). Das Netz lernt also nur, das Rauschen vorherzusagen.
    • Ist useResidual = false, ist das Ziel das saubere Originalbild.
  • Output: Gibt ein Paar {verrauschtesBild, ZielBild} zurück.

Code

function pair = addNoise(clean, useResidual)
    clean = im2double(clean); % Konvertiert Bild in Double-Format (0 bis 1)

    % Zufällige Auswahl des Rausch-Typs (50% Chance)
    if rand() > 0.5
        % Gaußsches Rauschen mit zufälliger Varianz (0.01 bis 0.10)
        noisy = imnoise(clean,'gaussian', 0, 0.01 + rand() * 0.09);
    else
        % Salt & Pepper Rauschen mit zufälliger Dichte (0.01 bis 0.10)
        noisy = imnoise(clean, 'salt & pepper', 0.01 + rand() * 0.09);
    end

    % Berechnung des Trainingsziels (Target)
    % Wenn useResidual == true:  Target = (noisy - clean) -> Nur das Rauschen lernen
    % Wenn useResidual == false: Target = clean           -> Das saubere Bild lernen
    target = useResidual * (noisy - clean) + ~useResidual * clean;

    pair = {noisy, target}; % Rückgabe als Paar für den Datastore
end

Quellen

  • P. Arbeláez, M. Maire, C. Fowlkes and J. Malik, "Contour Detection and Hierarchical Image Segmentation," in IEEE Transactions on Pattern Analysis and Machine Intelligence, vol. 33, no. 5, pp. 898-916, May 2011, doi: 10.1109/TPAMI.2010.161. keywords: {Image segmentation;Pixel;Detectors;Image edge detection;Humans;Histograms;Benchmark testing;Contour detection;image segmentation;computer vision.}
  • R. Timofte, S. Gu, J. Wu, L. Van Gool, L. Zhang, M.-H. Yang, M. Haris et al., "NTIRE 2018 Challenge on Single Image Super-Resolution: Methods and Results," in The IEEE Conference on Computer Vision and Pattern Recognition (CVPR) Workshops, June 2018.