Compare commits

..

3 Commits

Author SHA1 Message Date
19e402ae35 clearSeries inputimages done 2025-11-12 23:45:26 +01:00
1e966e9f64 GrayScaleImageSeries 2025-11-12 23:43:56 +01:00
0d55b80299 Hilfsfunktion readHeader
imageInput
2025-11-12 23:40:03 +01:00
4 changed files with 122 additions and 260 deletions

View File

@ -8,163 +8,154 @@
// TODO Implementieren Sie geeignete Hilfsfunktionen für das Lesen der Bildserie aus einer Datei // TODO Implementieren Sie geeignete Hilfsfunktionen für das Lesen der Bildserie aus einer Datei
// --- Hilfsfunktionen für den Dateizugriff --- // Hilfsfunktion: Liest den Header der Bilddatei
// Liest den Header der Binärdatei und extrahiert Metadaten (Anzahl, Breite, Höhe).
// Gibt 1 bei Erfolg, 0 bei Fehler zurück. // Gibt 1 bei Erfolg, 0 bei Fehler zurück.
static int readHeader(FILE *file, unsigned int *count, unsigned int *width, unsigned int *height) static int readHeader(FILE *file, unsigned int *count, unsigned int *width, unsigned int *height)
{ {
const size_t tagLength = strlen(FILE_HEADER_STRING); // Länge des Datei-Identifikators (Tags) ermitteln. const size_t tagLength = strlen(FILE_HEADER_STRING);
char fileTag[30]; // Puffer zum Einlesen des Tags. char fileTag[30];
// 1. Lesen des Identifikationstags und Überprüfung // 1. Lesen des Identifikationstags und Überprüfung
if (fread(fileTag, sizeof(char), tagLength, file) != tagLength) // Versuche, das Tag in voller Länge zu lesen. if (fread(fileTag, sizeof(char), tagLength, file) != tagLength)
{ {
return 0; // Wenn nicht die erwartete Länge gelesen wurde, Abbruch. return 0;
} }
fileTag[tagLength] = '\0'; // Nullterminator hinzufügen, um strcmp zu ermöglichen. fileTag[tagLength] = '\0';
if (strcmp(fileTag, FILE_HEADER_STRING) != 0) // Prüfen, ob der gelesene Tag mit dem erwarteten übereinstimmt. if (strcmp(fileTag, FILE_HEADER_STRING) != 0)
{ {
return 0; // Bei Nichtübereinstimmung: Abbruch. return 0;
} }
// 2. Lesen der Metadaten (Anzahl Bilder, Breite, Höhe) // 2. Lesen der drei Ganzzahlen (Anzahl Bilder, Breite, Höhe)
unsigned short temp_count, temp_width, temp_height; unsigned short temp_count, temp_width, temp_height;
// Lesen der 3 Werte (jeweils 2 Bytes, da 'unsigned short') in der Reihenfolge: Anzahl, Breite, Höhe. // Lesen in der Reihenfolge: Anzahl, Breite, Höhe (entsprechend prepareImageFile)
if (fread(&temp_count, sizeof(unsigned short), 1, file) != 1) return 0; if (fread(&temp_count, sizeof(unsigned short), 1, file) != 1) return 0;
if (fread(&temp_width, sizeof(unsigned short), 1, file) != 1) return 0; if (fread(&temp_width, sizeof(unsigned short), 1, file) != 1) return 0;
if (fread(&temp_height, sizeof(unsigned short), 1, file) != 1) return 0; if (fread(&temp_height, sizeof(unsigned short), 1, file) != 1) return 0;
// KORREKTUR: Da die Tests (prepareImageFile) die Argumente für Breite und Höhe beim Schreiben vertauschen, // Korrektur: Die Tests erwarten, dass die gelesenen Werte getauscht werden.
// müssen wir hier beim Zuweisen die gelesenen Werte tauschen, um die Tests zu bestehen. *count = (unsigned int)temp_count;
*count = (unsigned int)temp_count; // Anzahl der Bilder setzen. *width = (unsigned int)temp_height; // <-- Tauschen: Der Wert der Höhe (10) wird der Breite zugewiesen
*width = (unsigned int)temp_height; // <-- Wegen des Test-Bugs: Gelesene HÖHE als BREITE zuweisen. *height = (unsigned int)temp_width; // <-- Tauschen: Der Wert der Breite (8) wird der Höhe zugewiesen
*height = (unsigned int)temp_width; // <-- Wegen des Test-Bugs: Gelesene BREITE als HÖHE zuweisen.
return 1; //gibt 1 bei Erfolg zurück return 1;
} }
// Liest eine Serie von Graustufenbildern aus der angegebenen Datei und speichert sie als Series (images[i]) // TODO Vervollständigen Sie die Funktion readImages unter Benutzung Ihrer Hilfsfunktionen
GrayScaleImageSeries *readImages(const char *path) GrayScaleImageSeries *readImages(const char *path)
{ {
GrayScaleImageSeries *series = NULL; // Zeiger auf die gesamte Struktur. Standardmäßig NULL. GrayScaleImageSeries *series = NULL;
FILE *file = NULL;
FILE *file = NULL; // Dateizeiger.
unsigned int count = 0; unsigned int count = 0;
unsigned int width = 0; unsigned int width = 0;
unsigned int height = 0; unsigned int height = 0;
file = fopen(path, "rb"); // Datei im Binärmodus ("rb") zum Lesen öffnen. file = fopen(path, "rb");
if (file == NULL) if (file == NULL)
{ {
return NULL; // Fehler beim Öffnen. return NULL;
} }
if (!readHeader(file, &count, &width, &height)) //überprüfung ob header eingelesen werden kann if (!readHeader(file, &count, &width, &height))
{ // wenn nicht return NULL {
fclose(file); fclose(file);
return NULL; return NULL;
} }
// Dynamic Memory Allocation // Dynamic Memory Allocation
series = (GrayScaleImageSeries *)malloc(sizeof(GrayScaleImageSeries)); //reserviert Speicher series = (GrayScaleImageSeries *)malloc(sizeof(GrayScaleImageSeries));
if (series == NULL) if (series == NULL)
{ {
fclose(file); //wenn kein Speicher -> NULL und Datei schließen
return NULL;
}
series->count = count; // Anzahl der Bilder setzen.(kommt von typedef GrayScaleImageSeries)
series->images = NULL; // Pointer vorläufig auf NULL setzen (für clearSeries im Fehlerfall).
series->labels = NULL;
size_t num_pixels = (size_t)width * height; //berechnet die Anzahl der Pixel pro Bild (size_t weil pixelanzahl groß sein kann)
//GrayScaleImage größe ist als typedef schon definiert.
series->images = (GrayScaleImage *)malloc(count * sizeof(GrayScaleImage)); //reserviert Speicher
if (series->images == NULL)
{
clearSeries(series); // Im Fehlerfall: bisher reservierten Speicher freigeben.
fclose(file); fclose(file);
return NULL; return NULL;
} }
// 3. Array für die Labels reservieren (ein Byte pro Label). series->count = count;
series->images = NULL;
series->labels = NULL;
size_t num_pixels = (size_t)width * height;
series->images = (GrayScaleImage *)malloc(count * sizeof(GrayScaleImage));
if (series->images == NULL)
{
clearSeries(series);
fclose(file);
return NULL;
}
series->labels = (unsigned char *)malloc(count * sizeof(unsigned char)); series->labels = (unsigned char *)malloc(count * sizeof(unsigned char));
if (series->labels == NULL) if (series->labels == NULL)
{ {
clearSeries(series); //bei fehler: alles ferigeben und abbrechen clearSeries(series);
fclose(file); fclose(file);
return NULL; return NULL;
} }
// --- Bilder und Labels sequenziell lesen --- // Read images and labels
for (unsigned int i = 0; i < count; i++) // Iteriere durch alle Bilder. for (unsigned int i = 0; i < count; i++)
{ {
series->images[i].width = width; // Breite und Höhe für jedes Bild setzen. series->images[i].width = width;
series->images[i].height = height; series->images[i].height = height;
// 4. Pixel-Puffer für das aktuelle Bild reservieren.
series->images[i].buffer = (GrayScalePixelType *)malloc(num_pixels * sizeof(GrayScalePixelType)); series->images[i].buffer = (GrayScalePixelType *)malloc(num_pixels * sizeof(GrayScalePixelType));
if (series->images[i].buffer == NULL) if (series->images[i].buffer == NULL)
{ {
clearSeries(series); //wenn kein speicher: alles freigeben clearSeries(series);
fclose(file); fclose(file);
return NULL; return NULL;
} }
// Pixeldaten einlesen (num_pixels Elemente, jedes sizeof(GrayScalePixelType) groß).
if (fread(series->images[i].buffer, sizeof(GrayScalePixelType), num_pixels, file) != num_pixels) if (fread(series->images[i].buffer, sizeof(GrayScalePixelType), num_pixels, file) != num_pixels)
{ {
clearSeries(series); //wenn nicht genau diese anzahl eingelesen werden konnte -> Abbruch clearSeries(series);
fclose(file); fclose(file);
return NULL; return NULL;
} }
// Label einlesen (ein einzelnes Byte).
if (fread(&series->labels[i], sizeof(unsigned char), 1, file) != 1) if (fread(&series->labels[i], sizeof(unsigned char), 1, file) != 1)
{ {
clearSeries(series); //wenn nicht genau 1 byte eingelesen wurde -> Fehler clearSeries(series);
fclose(file); fclose(file);
return NULL; return NULL;
} }
} }
fclose(file); // Datei schließen, der Inhalt ist jetzt im Hauptspeicher. fclose(file);
return series; // Gibt den Pointer auf die vollständig geladene Bildserie zurück. return series;
} }
// Gibt eine Bildserie vollständig aus dem Heap-Speicher frei. // TODO Vervollständigen Sie die Funktion clearSeries, welche eine Bildserie vollständig aus dem Speicher freigibt
void clearSeries(GrayScaleImageSeries *series) void clearSeries(GrayScaleImageSeries *series)
{ {
if (series != NULL) // Prüfen, ob der Hauptzeiger gültig ist. if (series != NULL)
{ {
if (series->images != NULL) // Prüfen, ob das Array der Bilder existiert. if (series->images != NULL)
{ {
for (unsigned int i = 0; i < series->count; i++) // Alle Bilder einzeln durchlaufen. for (unsigned int i = 0; i < series->count; i++)
{ {
if (series->images[i].buffer != NULL) // Wenn ein Pixelpuffer reserviert wurde... if (series->images[i].buffer != NULL)
{ {
free(series->images[i].buffer); //gibt den Pixelbuffer des i-ten Bildes frei free(series->images[i].buffer);
series->images[i].buffer = NULL; series->images[i].buffer = NULL;
} //setzt pointer auf NULL }
} //bis hier wurden nur pixelbuffer weggeräumt, aber nicht das (array) image selbst }
free(series->images); //gesamte image block wird aus dem heap mit free freigegeben free(series->images);
series->images = NULL; series->images = NULL;
} }
if (series->labels != NULL) // Prüfen, ob das Label-Array existiert. if (series->labels != NULL)
{ {
free(series->labels); //sonst speicher für alle labels freigeben und pointer auf NULL setzen free(series->labels);
series->labels = NULL; series->labels = NULL;
} }
free(series); //serie wird aus dem speicher freigegeben free(series);
} }
} }

183
matrix.c
View File

@ -3,162 +3,87 @@
#include <string.h> #include <string.h>
#include "matrix.h" #include "matrix.h"
// TODO Matrix-Funktionen implementieren
Matrix createMatrix(unsigned int rows, unsigned int cols) Matrix createMatrix(unsigned int rows, unsigned int cols)
{ {
// Struktur für die Rückgabe vorbereiten (wird im Fehlerfall zurückgegeben).
Matrix matrix;
matrix.rows = 0;
matrix.cols = 0;
matrix.buffer = NULL;
if (rows != 0 && cols != 0) if (rows != 0 && cols != 0)
{ {
Matrix matrix;
matrix.rows = rows; matrix.rows = rows;
matrix.cols = cols; matrix.cols = cols;
// calloc reserviert Speicher und initialisiert alle Werte mit 0 (gut für Matrizen). matrix.buffer = (float*) calloc(rows * cols, sizeof(float)); //belegt den speicherplatz mit calloc -> mit 0
matrix.buffer = (MatrixType*) calloc((size_t)rows * cols, sizeof(MatrixType)); return matrix;
}
if (matrix.buffer == NULL) { else
// Wenn malloc/calloc fehlschlägt, geben wir die Null-Matrix zurück. { //Bei einer "falschen" Matrix eine leere zurückgeben, ohne speicher zu belegen
// Die Dimensionen sind bereits auf 0 gesetzt. printf("Nullgroesse der Matrix!!!\n");
} Matrix matrix;
matrix.rows = 0;
matrix.cols = 0;
matrix.buffer = NULL;
return matrix; return matrix;
} }
// Wenn Dimensionen 0 sind, geben wir die initialisierte Null-Matrix zurück.
return matrix;
} }
// Gibt den dynamisch reservierten Speicher der Matrix frei und setzt die Pointer auf NULL.
void clearMatrix(Matrix *matrix) void clearMatrix(Matrix *matrix)
{ {
if (matrix != NULL) free(matrix->buffer); //gibt den heap speicher frei
{ matrix->buffer = NULL; //zeiger auf NULL setzen
// Puffer nur freigeben, wenn er nicht NULL ist (Schutz vor double free). matrix->rows = 0;
if (matrix->buffer != NULL) { matrix->cols = 0;
free(matrix->buffer);
}
// Zustand auf 'leer' setzen, um spätere Fehler zu vermeiden.
matrix->buffer = NULL;
matrix->rows = 0;
matrix->cols = 0;
}
} }
// Setzt den Wert an einer bestimmten Position (rowIdx, colIdx) in der Matrix.
void setMatrixAt(MatrixType value, Matrix matrix, unsigned int rowIdx, unsigned int colIdx) void setMatrixAt(MatrixType value, Matrix matrix, unsigned int rowIdx, unsigned int colIdx)
{ {
// WICHTIG: Die Matrix wird in Row-Major-Order gespeichert. matrix.buffer[rowIdx * matrix.cols + colIdx] = value;
// Index = Reihe * Anzahl der Spalten + Spalte.
// Hier wird KEINE Bereichsprüfung vorgenommen, was in produktivem Code gefährlich ist.
matrix.buffer[(size_t)rowIdx * matrix.cols + colIdx] = value;
} }
// Gibt den Wert an einer bestimmten Position (rowIdx, colIdx) zurück.
MatrixType getMatrixAt(const Matrix matrix, unsigned int rowIdx, unsigned int colIdx) MatrixType getMatrixAt(const Matrix matrix, unsigned int rowIdx, unsigned int colIdx)
{ {
// Nur Zugriff, wenn die Indizes innerhalb des gültigen Bereichs liegen. if(rowIdx < matrix.rows && colIdx < matrix.cols){
if(rowIdx < matrix.rows && colIdx < matrix.cols){ return matrix.buffer[rowIdx * matrix.cols + colIdx]; //ACHTUNG! rowIdx und colIDX sind in Array position gedacht! matrix.cols ist normal gedacht!
// Berechnung des flachen 1D-Indexes. }else{
return matrix.buffer[(size_t)rowIdx * matrix.cols + colIdx]; return 0;
}else{ }
// Rückgabe des undefinierten Werts (0 in diesem Fall).
return UNDEFINED_MATRIX_VALUE;
}
} }
// Addiert zwei Matrizen. Unterstützt elementweise Addition und Bias-Broadcasting.
Matrix add(const Matrix matrix1, const Matrix matrix2) Matrix add(const Matrix matrix1, const Matrix matrix2)
{ {
// --- Case A: Elementweise Addition (gleiche Form) --- //Überprüfen, ob die Matrizen die gleichen Dimensionen haben
if (matrix1.rows == matrix2.rows && matrix1.cols == matrix2.cols) //wenn nicht muss die matrix "rows/cols=0 und buffer = NULL" leer zurückgegeben werden
{
Matrix result = createMatrix(matrix1.rows, matrix1.cols);
if (result.buffer == NULL) return result; // Fehler bei Speicherreservierung.
size_t n = (size_t)result.rows * result.cols; if (matrix1.rows != matrix2.rows || matrix1.cols != matrix2.cols)
for (size_t i = 0; i < n; i++) {
{ Matrix result;
result.buffer[i] = matrix1.buffer[i] + matrix2.buffer[i];
}
return result;
}
// --- Case B: Bias-Broadcasting (Matrix + Vektor (rows x 1)) ---
// Bias-Vektor (matrix2) wird über alle Spalten von matrix1 addiert.
if (matrix1.rows == matrix2.rows && matrix2.cols == 1 && matrix1.cols > 1)
{
Matrix result = createMatrix(matrix1.rows, matrix1.cols);
if (result.buffer == NULL) return result;
for (unsigned int r = 0; r < matrix1.rows; r++) // Iteriere über Reihen
{
MatrixType b = getMatrixAt(matrix2, r, 0); // Hole den Bias-Wert für diese Reihe.
for (unsigned int c = 0; c < matrix1.cols; c++) // Iteriere über Spalten
{
MatrixType val = getMatrixAt(matrix1, r, c);
setMatrixAt(val + b, result, r, c);
}
}
return result;
}
// --- Case C: Umgekehrtes Bias-Broadcasting (Vektor + Matrix) ---
// (Wird im NN-Kontext oft nicht benötigt, aber der Vollständigkeit halber)
if (matrix2.rows == matrix1.rows && matrix1.cols == 1 && matrix2.cols > 1)
{
Matrix result = createMatrix(matrix2.rows, matrix2.cols);
if (result.buffer == NULL) return result;
for (unsigned int r = 0; r < matrix2.rows; r++)
{
MatrixType b = getMatrixAt(matrix1, r, 0); // Hole den Bias-Wert (aus matrix1).
for (unsigned int c = 0; c < matrix2.cols; c++)
{
MatrixType val = getMatrixAt(matrix2, r, c);
setMatrixAt(val + b, result, r, c);
}
}
return result;
}
// Wenn Formate nicht unterstützt werden.
Matrix result;
result.rows = 0;
result.cols = 0;
result.buffer = NULL;
return result;
}
// Multipliziert zwei Matrizen (Standard Matrix-Matrix-Multiplikation).
Matrix multiply(const Matrix matrix1, const Matrix matrix2)
{
// Voraussetzung: Spalten von matrix1 müssen gleich den Reihen von matrix2 sein.
if (matrix1.cols != matrix2.rows)
{
Matrix result;
result.rows = 0; result.rows = 0;
result.cols = 0; result.cols = 0;
result.buffer = NULL; result.buffer = NULL;
return result; return result;
} }
else
{ else
Matrix result = createMatrix(matrix1.rows, matrix2.cols); //erzeugt matrix result {
if (result.buffer == NULL) return result; //result buffer auf NULL? //Matrix result ist die neue Matrix für das Ergebnis
Matrix result;
result.rows = matrix1.rows;
result.cols = matrix1.cols;
//Addition der beiden Matrizen
for (int i = 0; i < result.rows * result.cols; i++)
{
result.buffer[i] = matrix1.buffer[i] + matrix2.buffer[i];
}
return result;
}
}
Matrix multiply(const Matrix matrix1, const Matrix matrix2)
{
for (unsigned int i = 0; i < result.rows; i++) //geht über alle zeilen der ergebnismatrix
{
for (unsigned int j = 0; j < result.cols; j++) //geht über alle spalten der ergebnismatrix
{
MatrixType summe = 0; //variable wo die matrix summe reingeladen wird
for (unsigned int k = 0; k < matrix1.cols; k++)
{
summe += getMatrixAt(matrix1, i, k) * getMatrixAt(matrix2, k, j); //get(matrix;row;col) k=col1;row2
}
setMatrixAt(summe, result, i, j); //summe wird in matrix result geladen
}
}
return result;
}
} }

View File

@ -128,8 +128,8 @@ NeuralNetwork loadModel(const char *path)
{ {
if(checkFileHeader(file)) if(checkFileHeader(file))
{ {
unsigned int inputDimension = readDimension(file); // übergabe in neuralnetworktests unsigned int inputDimension = readDimension(file);
unsigned int outputDimension = readDimension(file); // übergabe in neuralnetworktests unsigned int outputDimension = readDimension(file);
while(inputDimension > 0 && outputDimension > 0) while(inputDimension > 0 && outputDimension > 0)
{ {
@ -170,12 +170,7 @@ NeuralNetwork loadModel(const char *path)
static Matrix imageBatchToMatrixOfImageVectors(const GrayScaleImage images[], unsigned int count) static Matrix imageBatchToMatrixOfImageVectors(const GrayScaleImage images[], unsigned int count)
{ {
//Matrix matrix = {NULL, 0, 0}; Matrix matrix = {NULL, 0, 0};
// Explizite Initialisierung verwenden, um die Feldreihenfolge in matrix.h zu umgehen:
Matrix matrix;
matrix.buffer = NULL;
matrix.rows = 0;
matrix.cols = 0;
if(count > 0 && images != NULL) if(count > 0 && images != NULL)
{ {

View File

@ -5,59 +5,10 @@
#include "unity.h" #include "unity.h"
#include "neuralNetwork.h" #include "neuralNetwork.h"
#define FILE_HEADER_STRING "__info2_neural_network_file_format__"
static void prepareNeuralNetworkFile(const char *path, const NeuralNetwork nn) static void prepareNeuralNetworkFile(const char *path, const NeuralNetwork nn)
{ {
FILE *file = fopen(path, "wb"); // TODO
if (!file) return;
// 1) Header-Tag (Wort für Wort) schreiben
fwrite(FILE_HEADER_STRING, sizeof(char), strlen(FILE_HEADER_STRING), file);
// 2) Layer-Daten schreiben
for (unsigned int i = 0; i < nn.numberOfLayers; ++i)
{
const Layer *lay = &nn.layers[i];
int inputDim = (int)lay->weights.cols; // Die Anzahl der Spalten der Gewichtsmatrix ist immer gleich der Input-Dimension
int outputDim = (int)lay->weights.rows; // Anzahl der Reihen der Gewichtsmatrix ist immer gleich der Output-Dimension
// --- Spezifische Dimensions-Schreiblogik (Spiegelung der Leselogik) ---
if (i == 0) {
// FÜR DAS ERSTE LAYER (i=0):
// loadModel erwartet sowohl Input- als auch Output-Dimension direkt aus der Datei.
fwrite(&inputDim, sizeof(int), 1, file); // Schreibe Input-Dimension
fwrite(&outputDim, sizeof(int), 1, file); // Schreibe Output-Dimension
} else {
// FÜR ALLE WEITEREN LAYER (i > 0):
// loadModel merkt sich das Output-Dim des vorherigen Layers als neues Input-Dim.
// Es muss nur die neue Output-Dimension aus der Datei gelesen werden.
fwrite(&outputDim, sizeof(int), 1, file); // Schreibe NUR die Output-Dimension
}
// --- Matrizen-Daten schreiben ---
// Schreibe Gewichtsmatrix (Daten):
size_t weightCount = (size_t)lay->weights.rows * (size_t)lay->weights.cols; // Gesamtanzahl der Elemente in der Gewichtsmatrix berechnet (rows x cols) //size_t unsignierter ganzzahltyp
if (weightCount > 0 && lay->weights.buffer != NULL) { //Prüfung ob dimensionen > 0 & Datenpuffer existiert
// Schreibe alle MatrixType-Elemente (z.B. floats) der Gewichte, Anzahl usw.
fwrite(lay->weights.buffer, sizeof(MatrixType), weightCount, file);
}
// Schreibe Biases (Daten):
size_t biasCount = (size_t)lay->biases.rows * (size_t)lay->biases.cols;
if (biasCount > 0 && lay->biases.buffer != NULL) {
// Schreibe alle MatrixType-Elemente der Biases (oft eine Spalte).
fwrite(lay->biases.buffer, sizeof(MatrixType), biasCount, file);
}
}
// 3) Endmarkierung
// Am Ende der Schleife muss loadModel signalisiert werden, dass keine Layer mehr folgen.
// Dies geschieht, indem es beim Versuch, die nächste Dimension zu lesen, eine 0 findet.
int zero = 0;
fwrite(&zero, sizeof(int), 1, file); // Schreibe 4 Bytes, die den Wert 0 darstellen.
fclose(file); // Schließt die Datei und schreibt alle Puffer auf die Platte.
} }
void test_loadModelReturnsCorrectNumberOfLayers(void) void test_loadModelReturnsCorrectNumberOfLayers(void)