Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
917ffd276c | ||
|
|
647716ff66 | ||
|
|
c7937d6389 | ||
|
|
171dbee748 | ||
|
|
6dad1d2df2 | ||
|
|
6963e15557 |
No files matched your search
+132
-1
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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,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);
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
Executable
BIN
Binary file not shown.
Reference in new issue
Block a user