6 Commits
Author SHA1 Message Date
lechselle103415 917ffd276c added Unit Tests 2026-11-09 09:11:31 +01:00
lechselle103415 647716ff66 added ImageInput 2026-11-09 08:35:10 +01:00
kuehnemundli103965 c7937d6389 Fehler ausbessern 2026-11-06 21:56:34 +01:00
kuehnemundli103965 171dbee748 Merge remote-tracking branch 'origin' into Lilli 2026-11-06 21:47:25 +01:00
leimenstollsa105862 6dad1d2df2 Matrix 2026-11-06 11:25:50 +01:00
kuehnemundli103965 6963e15557 Implement neural network file preparation 2026-11-04 09:31:59 +01:00
7 changed files with 360 additions and 12 deletions

No files matched your search

+132 -1
View File
@@ -7,16 +7,147 @@
#define FILE_HEADER_STRING "__info2_image_file_format__"
// TODO Implementieren Sie geeignete Hilfsfunktionen für das Lesen der Bildserie aus einer Datei
//Hilfsfunktion zum Einlesen eines einzigen Bildes
static int read_single_image(FILE *file, unsigned short width, unsigned short height, GrayScaleImage *img, unsigned char *out_label)
{
size_t pixels_to_read = (size_t)width * height;
img->width = width; //save width and height read from file in struct
img->height = height;
img->buffer = malloc(pixels_to_read * sizeof(GrayScalePixelType)); //allocating needed memory for pixel-buffer
if(img->buffer == NULL) //Error case
{
printf("%s", "Fehler bei der Speicherreservierung!\n");
return 0;
}
if(fread(img->buffer, sizeof(GrayScalePixelType), pixels_to_read, file) != pixels_to_read) //read all pixels one after another
{
free(img->buffer); //Error case: free allocated memory
return 0;
}
if(fread(out_label, sizeof(unsigned char), 1, file) != 1) //read label
{
free(img->buffer); //Error case: free allocated memory
return 0;
}
return 1;
}
// TODO Vervollständigen Sie die Funktion readImages unter Benutzung Ihrer Hilfsfunktionen
GrayScaleImageSeries *readImages(const char *path)
{
GrayScaleImageSeries *series = NULL;
char buffer[BUFFER_SIZE];
unsigned short num_images;
unsigned short width;
unsigned short height;
FILE *file = fopen(path, "rb");
if(file == NULL) //file can not be opened
{
return NULL;
}
/*********************************************************************************
* VERIFY IF HEADER FILE IS INCLUDED
********************************************************************************/
fread(buffer, sizeof(buffer[0]), strlen(FILE_HEADER_STRING), file);
buffer[strlen(FILE_HEADER_STRING)] = '\0';
if(strcmp(buffer, FILE_HEADER_STRING) != 0) //file does not include file header
{
fclose(file);
return NULL;
}
/*********************************************************************************
* READ AMOUNT, WIDTH, HEIGHT
********************************************************************************/
fread(&num_images, sizeof(num_images), 1, file);
fread(&width, sizeof(width), 1, file);
fread(&height, sizeof(height), 1, file);
/*********************************************************************************
* MEMORY ALLOCATION FOR SERIES
********************************************************************************/
GrayScaleImageSeries *series = malloc(sizeof(GrayScaleImageSeries));
if(series == NULL) //Error: memory cannot be allocated; close file
{
fclose(file);
return NULL;
}
series->count = num_images; //save number of images in struct
series->images = malloc(num_images * sizeof(GrayScaleImage)); //allocate memory for all images
series->labels = malloc(num_images * sizeof(unsigned char)); //allocate memory for all labels
if(series->images == NULL || series->labels == NULL) //Error: memory cannot be allocated: free every memory
{
free(series->images);
free(series->labels);
free(series);
fclose(file);
return NULL;
}
/*********************************************************************************
* READ IMAGES
********************************************************************************/
for(int i = 0; i < num_images; i++)
{
if(read_single_image(file, width, height, &series->images[i], &series->labels[i]) != 1)
{
series->count = i;
clearSeries(series); //free all allocated memory
fclose(file); //Error case: close file
return NULL;
}
}
fclose(file);
return series;
}
// TODO Vervollständigen Sie die Funktion clearSeries, welche eine Bildserie vollständig aus dem Speicher freigibt
void clearSeries(GrayScaleImageSeries *series)
{
if(series == NULL) //pointer points to nothing; nothing to clear
{
return;
}
if(series->images != NULL) //check if image pointer is valid
{
for(int i = 0; i < series->count; i++) //free memory allocated for all images until error occured
{
free(series->images[i].buffer);
}
free(series->images); //free memory allocated for image array
}
if(series->labels != NULL) //check if label pointer is valid
{
free(series->labels); //free memory allocated for label array
}
free(series); //free memory allocated for series
}
+20
View File
@@ -49,6 +49,23 @@ void test_readImagesReturnsCorrectNumberOfImages(void)
remove(path);
}
void test_mytest_clear_function_NULL(void)
{
clearSeries(NULL); //check if function works with NULL-pointer
}
void test_mytest_clear_function_series_without_images_or_labels(void)
{
GrayScaleImageSeries *series = malloc(sizeof(GrayScaleImageSeries));
TEST_ASSERT_NOT_NULL(series);
series->images = NULL;
series->labels = NULL;
series->count = 0;
clearSeries(series);
}
void test_readImagesReturnsCorrectImageWidth(void)
{
GrayScaleImageSeries *series = NULL;
@@ -139,5 +156,8 @@ int main()
RUN_TEST(test_readImagesReturnsNullOnNotExistingPath);
RUN_TEST(test_readImagesFailsOnWrongFileTag);
RUN_TEST(test_mytest_clear_function_NULL);
RUN_TEST(test_mytest_clear_function_series_without_images_or_labels);
return UNITY_END();
}
+145 -10
View File
@@ -4,32 +4,167 @@
// TODO Matrix-Funktionen implementieren
Matrix createMatrix(unsigned int rows, unsigned int cols)
{
Matrix createMatrix(unsigned int rows, unsigned int cols) // eine Matrix mit ner bestimmten Anzahl an Zeilen und Spalten erstellen und benötigten Speicher reservieren
{
// leere Matrix erstellen:
Matrix matrix = {0, 0, NULL};
if (rows == 0 || cols == 0) // Matrizen mit null Zeilen oder Spalten sind nicht erlaubt
{
return matrix;
}
// Speicher für alle Matrixelemente reservieren:
matrix.buffer = (MatrixType *)calloc(rows * cols, sizeof(MatrixType));
// prüfen, ob die Speicherreservierung erfolgreich war:
if (matrix.buffer != NULL)
{
matrix.rows = rows;
matrix.cols = cols;
}
return matrix;
}
void clearMatrix(Matrix *matrix)
void clearMatrix(Matrix *matrix) // den dynamisch reservierten Speicher einer Matrix freigeben und die Matrix zurücksetzen
{
if (matrix != NULL) // prüfen, ob ein gültiger Zeiger übergeben wurde
{
free(matrix->buffer); // reservierten Speicher freigeben
// Matrix auf Ausgangszustand zurücksetzen:(kann sonst zu Speicherlecks (Memory Leaks) führen, da dynamische Speicher weiterhin belegt bleibt)
matrix->buffer = NULL;
matrix->rows = 0;
matrix->cols = 0;
}
}
void setMatrixAt(MatrixType value, Matrix matrix, unsigned int rowIdx, unsigned int colIdx)
void setMatrixAt(MatrixType value, Matrix matrix, unsigned int rowIdx, unsigned int colIdx) // einen bestimmten Wert an einer bestimmten Position der Matrix speichern
{
// prüfen, ob die Matrix gültig ist und die Indizes innerhalb der Matrixgrenzen liegen:
if (matrix.buffer == NULL || rowIdx >= matrix.rows || colIdx >= matrix.cols)
{
return;
}
unsigned int index = rowIdx * matrix.cols + colIdx; // Position im eindimensionalen Speicher berechnen
matrix.buffer[index] = value; // Wert an der gewünschten Position speichern
}
MatrixType getMatrixAt(const Matrix matrix, unsigned int rowIdx, unsigned int colIdx)
MatrixType getMatrixAt(const Matrix matrix, unsigned int rowIdx, unsigned int colIdx) // einen Wert an einer bestimmten Position auslesen
{
// prüfen, ob die Matrix gültig ist und die Indizes innerhalb der Matrixgrenzen liegen:
if (matrix.buffer == NULL || rowIdx >= matrix.rows || colIdx >= matrix.cols)
{
return UNDEFINED_MATRIX_VALUE; // in matrix.h def.
}
unsigned int index = rowIdx * matrix.cols + colIdx; // eindimensionalen Index berechnen
return matrix.buffer[index]; // Wert an der gewünschten Position zurückgeben
}
Matrix add(const Matrix matrix1, const Matrix matrix2)
Matrix add(const Matrix matrix1, const Matrix matrix2) // zwei Matrizen addieren
{
Matrix result = {0, 0, NULL}; // zunächst eine leere Ergebnismatrix erstellen
// ungültige Eingabematrizen überprüfen:
if (matrix1.buffer == NULL || matrix2.buffer == NULL || matrix1.rows == 0 || matrix1.cols == 0 || matrix2.rows == 0 || matrix2.cols == 0)
{
return result;
}
// Fall 1: Beide Matrizen haben dieselben Dimensionen:
if (matrix1.rows == matrix2.rows &&
matrix1.cols == matrix2.cols)
{
result = createMatrix(matrix1.rows, matrix1.cols);
if (result.buffer == NULL)
{
return result;
}
for (unsigned int i = 0; i < matrix1.rows * matrix1.cols; i++) // alle Elemente paarweise addieren
{
result.buffer[i] = matrix1.buffer[i] + matrix2.buffer[i];
}
}
// Fall 2: Broadcasting mit einem Spaltenvektor:
else if (matrix1.rows == matrix2.rows && (matrix1.cols == 1 || matrix2.cols == 1))
{
// die Matrix mit mehr Spalten bestimmt die Ergebnisgröße:
const Matrix *matrix = &matrix1;
const Matrix *vector = &matrix2;
if (matrix1.cols == 1) // falls matrix1 der Spaltenvektor ist, werden die Rollen der beiden Eingabematrizen vertauscht (Broadcasting soll in beiden Reihenfolgen funktionieren)
{
matrix = &matrix2;
vector = &matrix1;
}
result = createMatrix(matrix->rows, matrix->cols);
if (result.buffer == NULL)
{
return result;
}
for (unsigned int row = 0; row < matrix->rows; row++) // jeden Matrixwert mit dem Vektorwert seiner Zeile addieren
{
for (unsigned int col = 0; col < matrix->cols; col++)
{
result.buffer[row * matrix->cols + col] =
matrix->buffer[row * matrix->cols + col]
+ vector->buffer[row];
}
}
}
return result; // wenn keine der beiden Varianten möglich ist, bleibt Ergebnismatrix leer
}
Matrix multiply(const Matrix matrix1, const Matrix matrix2)
Matrix multiply(const Matrix matrix1, const Matrix matrix2) // zwei Matrizen multiplizieren
{
Matrix result = {0, 0, NULL}; // leere Ergebnismatrix
// prüfen, ob beide Eingabematrizen gültig sind:
if (matrix1.buffer == NULL || matrix2.buffer == NULL || matrix1.rows == 0 || matrix1.cols == 0 || matrix2.rows == 0 || matrix2.cols == 0)
{
return result;
}
if (matrix1.cols != matrix2.rows) // Spaltenanzahl der ersten Matrix muss der Zeilenanzahl der zweiten Matrix entsprechen
{
return result;
}
result = createMatrix(matrix1.rows, matrix2.cols); // Ergebnis hat so viele Zeilen wie matrix1 und so viele Spalten wie matrix2
if (result.buffer == NULL)
{
return result;
}
for (unsigned int row = 0; row < matrix1.rows; row++) // Durchführung Matrixmultiplikation...
{
for (unsigned int col = 0; col < matrix2.cols; col++)
{
MatrixType sum = 0; // Summe für das aktuelle Ergebniselement
for (unsigned int k = 0; k < matrix1.cols; k++)
{
sum += matrix1.buffer[row * matrix1.cols + k] * matrix2.buffer[k * matrix2.cols + col];
// "row" durchläuft Zeilen d. ersten Matrix; "col" durchläuft Zeilen d. zweiten Matrix; "k" berechnet Summe d. Produkte
}
result.buffer[row * result.cols + col] = sum; // berechnetes Ergebnis speichern
}
}
return result;
}
+6
View File
@@ -6,6 +6,12 @@
typedef float MatrixType;
// TODO Matrixtyp definieren
typedef struct
{
unsigned int rows; // Anzahl der Zeilen
unsigned int cols; // Anzahl der Spalten
MatrixType *buffer; // Zeiger auf den Speicher mit den Matrixwerten
} Matrix;
Matrix createMatrix(unsigned int rows, unsigned int cols);
+1 -1
View File
@@ -170,7 +170,7 @@ NeuralNetwork loadModel(const char *path)
static Matrix imageBatchToMatrixOfImageVectors(const GrayScaleImage images[], unsigned int count)
{
Matrix matrix = {NULL, 0, 0};
Matrix matrix = {0, 0, NULL};
if(count > 0 && images != NULL)
{
+56
View File
@@ -9,6 +9,62 @@
static void prepareNeuralNetworkFile(const char *path, const NeuralNetwork nn)
{
// TODO
// 1. Datei Öffnen
FILE *file = fopen(path, "wb");
// überprüfen ob das öffnen funktioniert hat
if (file == NULL)
{
return;
}
// 2. Datei-Header schreiben (Überschrifft bzw. Stempel was in der Datei steht)
const char *fileTag = "__info2_neural_network_file_format__";
fwrite(fileTag, sizeof(char), strlen(fileTag), file);
// 3. Eingabe- und Ausgabedimension bestimmen
int inputDimension = nn.layers[0].weights.cols;
int outputDimension = nn.layers[0].weights.rows;
// in unser File speichern
fwrite(&inputDimension, sizeof(int), 1, file);
fwrite(&outputDimension, sizeof(int), 1, file);
for(unsigned int i = 0; i < nn.numberOfLayers; i++)
{
// Speichern der aktuellen Speicheradresse
const Layer *layer = &nn.layers[i];
// Gewichtsmatrix des aktuellen Layers in die Datei speichern
fwrite
(
layer->weights.buffer,
sizeof(MatrixType),
layer->weights.rows * layer->weights.cols,
file
);
//Bias-Matrix des aktuellen Layers in die Datei speichern
fwrite
(
layer->biases.buffer,
sizeof(MatrixType),
layer->biases.rows * layer->biases.cols,
file
);
//ermitteln wie viele Ausgabewerte der nächste Layer haben wird
if (i + 1 < nn.numberOfLayers)
{
outputDimension = nn.layers[i + 1].weights.rows;
}
else
{
outputDimension = 0;
}
fwrite(&outputDimension, sizeof(int), 1, file);
}
fclose(file);
}
void test_loadModelReturnsCorrectNumberOfLayers(void)
BIN
View File
Binary file not shown.