diff --git a/.gitignore b/.gitignore index f652b45..7b6eba3 100644 --- a/.gitignore +++ b/.gitignore @@ -297,3 +297,36 @@ __pycache__/ *.btm.cs *.odx.cs *.xsd.cs + +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app \ No newline at end of file diff --git a/AdvancedVision/AdvancedVision.sln b/AdvancedVision/AdvancedVision.sln new file mode 100644 index 0000000..32d916b --- /dev/null +++ b/AdvancedVision/AdvancedVision.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26730.12 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AdvancedVision", "AdvancedVision.vcxproj", "{D07114EF-4EC2-45EE-944D-5A269C142C0F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D07114EF-4EC2-45EE-944D-5A269C142C0F}.Debug|x64.ActiveCfg = Debug|x64 + {D07114EF-4EC2-45EE-944D-5A269C142C0F}.Debug|x64.Build.0 = Debug|x64 + {D07114EF-4EC2-45EE-944D-5A269C142C0F}.Debug|x86.ActiveCfg = Debug|Win32 + {D07114EF-4EC2-45EE-944D-5A269C142C0F}.Debug|x86.Build.0 = Debug|Win32 + {D07114EF-4EC2-45EE-944D-5A269C142C0F}.Release|x64.ActiveCfg = Release|x64 + {D07114EF-4EC2-45EE-944D-5A269C142C0F}.Release|x64.Build.0 = Release|x64 + {D07114EF-4EC2-45EE-944D-5A269C142C0F}.Release|x86.ActiveCfg = Release|Win32 + {D07114EF-4EC2-45EE-944D-5A269C142C0F}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {718B94D4-63F0-4DFC-84A7-C7CA7AF5ACA9} + EndGlobalSection +EndGlobal diff --git a/AdvancedVision/AdvancedVision.vcxproj b/AdvancedVision/AdvancedVision.vcxproj new file mode 100644 index 0000000..1447682 --- /dev/null +++ b/AdvancedVision/AdvancedVision.vcxproj @@ -0,0 +1,175 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {d07114ef-4ec2-45ee-944d-5a269c142c0f} + Win32Proj + AdvancedVision + 10.0.15063.0 + + + + Application + true + v141 + Unicode + + + Application + false + v141 + true + Unicode + + + Application + true + v141 + Unicode + + + Application + false + v141 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + + + true + + + false + + + false + + + + NotUsing + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + C:\opencv\build\include;%(AdditionalIncludeDirectories) + C:\opencv\build\include;%(AdditionalUsingDirectories) + + + Console + true + C:\opencv\build\x64\vc14\lib;%(AdditionalLibraryDirectories) + opencv_world330.lib;opencv_world330d.lib;%(AdditionalDependencies) + + + + + NotUsing + Level3 + Disabled + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + C:\opencv\build\include;%(AdditionalIncludeDirectories) + C:\opencv\build\include;%(AdditionalUsingDirectories) + + + Console + true + C:\opencv\build\x64\vc14\lib;%(AdditionalLibraryDirectories) + opencv_world330.lib;opencv_world330d.lib;%(AdditionalDependencies) + + + + + NotUsing + Level3 + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + C:\opencv\build\include;%(AdditionalIncludeDirectories) + C:\opencv\build\include;%(AdditionalUsingDirectories) + + + Console + true + true + true + opencv_world330.lib;opencv_world330d.lib;%(AdditionalDependencies) + C:\opencv\build\x64\vc14\lib;%(AdditionalLibraryDirectories) + + + + + NotUsing + Level3 + MaxSpeed + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + C:\opencv\build\include;%(AdditionalIncludeDirectories) + C:\opencv\build\include;%(AdditionalUsingDirectories) + + + Console + true + true + true + opencv_world330.lib;opencv_world330d.lib;%(AdditionalDependencies) + C:\opencv\build\x64\vc14\lib;%(AdditionalLibraryDirectories) + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/AdvancedVision/AdvancedVision.vcxproj.filters b/AdvancedVision/AdvancedVision.vcxproj.filters new file mode 100644 index 0000000..168e7c1 --- /dev/null +++ b/AdvancedVision/AdvancedVision.vcxproj.filters @@ -0,0 +1,36 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + \ No newline at end of file diff --git a/AdvancedVision/ReadMe.txt b/AdvancedVision/ReadMe.txt new file mode 100644 index 0000000..686284f --- /dev/null +++ b/AdvancedVision/ReadMe.txt @@ -0,0 +1,37 @@ +======================================================================== + CONSOLE APPLICATION : [!output PROJECT_NAME] Project Overview +======================================================================== + +AppWizard has created this [!output PROJECT_NAME] application for you. + +This file contains a summary of what you will find in each of the files that +make up your [!output PROJECT_NAME] application. + + + This is the main project file for VC++ projects generated using an Application Wizard. + It contains information about the version of Visual C++ that generated the file, and + information about the platforms, configurations, and project features selected with the + Application Wizard. + + This is the filters file for VC++ projects generated using an Application Wizard. + It contains information about the association between the files in your project + and the filters. This association is used in the IDE to show grouping of files with + similar extensions under a specific node (for e.g. ".cpp" files are associated with the + "Source Files" filter). + + This is the main application source file. + +///////////////////////////////////////////////////////////////////////////// +Other standard files: + +StdAfx.h, StdAfx.cpp + These files are used to build a precompiled header (PCH) file + named [!output PROJECT_NAME].pch and a precompiled types file named StdAfx.obj. + +///////////////////////////////////////////////////////////////////////////// +Other notes: + +AppWizard uses "TODO:" comments to indicate parts of the source code you +should add to or customize. + +///////////////////////////////////////////////////////////////////////////// diff --git a/AdvancedVision/Source.cpp b/AdvancedVision/Source.cpp new file mode 100644 index 0000000..3e051f8 --- /dev/null +++ b/AdvancedVision/Source.cpp @@ -0,0 +1,33 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace cv; +using namespace std; + +int main(int argc, char** argv) +{ + Mat image = imread("input/Week2/basisfiguren.jpg", CV_LOAD_IMAGE_COLOR); + if (!image.data) + { + cout << "Could not open or find the image" << std::endl; + return -1; + } + + Mat gray_image; + cvtColor(image, gray_image, CV_BGR2GRAY); + + imshow("Source", image); + imshow("Gray image", gray_image); + + waitKey(0); + + return 0; +} \ No newline at end of file diff --git a/AdvancedVision/allContoursTestprogramma.cpp b/AdvancedVision/allContoursTestprogramma.cpp new file mode 100644 index 0000000..99f34c0 --- /dev/null +++ b/AdvancedVision/allContoursTestprogramma.cpp @@ -0,0 +1,106 @@ +// Functionaliteit: Testprogramma voor het testen van de allContours() functie +// Zie module Vision C++ voor Gevorderden Opdracht 2 van Fase 1 +// De demo laat met avansvisionlib en OpenCV de contouren zien. +// +// De algoritmiek wordt in de eerste workshop toegelicht. +// +// Jan Oostindie, dd 8-11-2016 +// +#include "opencv2/imgproc/imgproc.hpp" +#include "opencv2/highgui/highgui.hpp" +#include +#include +#include "avansvisionlib.h" + +using namespace cv; +using namespace std; + + +int main(int argc, char *argv[]) +{ + // zie: project properties - configuration properties - debugging - command arguments + if (argc != 2) + { + cout << "NB! Geef als command argument volledige padnaam van de imagefile mee" << endl; + return -1; + } + else cout << "De imagefile = " << argv[1] << endl; + + // Lees de afbeelding in + Mat image; + image = imread(argv[1], CV_LOAD_IMAGE_COLOR); + if (!image.data) + { + cout << "Could not open or find the image" << std::endl; + return -1; + } + + // De afbeelding converteren naar een grijswaarde afbeelding + Mat gray_image; + cvtColor(image, gray_image, CV_BGR2GRAY); + + // Converteren naar grijswaarde afbeelding + cout << "Imagefile: " << argv[1] << " met succes geconverteerd naar grijswaarde beeld." << endl; + + //////////////////////////////////////////////////////////////////////////////// + + imshow("Original", gray_image); + waitKey(0); + + /** ! Gebruik een region of interest voor de rummikub images **/ + // Mat imageROI = gray_image(Rect(34, 28, 976, 82)); + // imshow("imageROI", imageROI); + // waitKey(0); + + // Grijswaarde afbeelding thresholden + Mat binaryImage; + + /** ! Gebruik een region of interest voor de rummikub figuren **/ + // threshold(imageROI, binaryImage, 65, 1, CV_THRESH_BINARY_INV); + + // Stel een threshold in. Laad evt de figuur eerst in VisionLab om een goede + // threshold te bepalen. + // ! Comment this line out when using rummikub images + threshold(gray_image, binaryImage, 165, 1, CV_THRESH_BINARY_INV); + + // Alvorens bewerkingen uit te voeren op het beeld converteren we deze + // naar een Mat object met grotere diepte (depth), t.w. 16 bits signed + Mat binary16S; + binaryImage.convertTo(binary16S, CV_16S); + + show16SImageStretch(binary16S,"Binary image"); + + // haal de contouren uit het binary image + vector> contours; + cout << "Contouren bepalen..."; + int aantal = allContours(binary16S, contours); // <<<<*** De opdracht is dus om deze functie te maken *** + + // druk alle punten van alle gevonden contouren af + string line; + cout << "Aantal gevonden contouren = " << contours.size() << endl; + getline(cin, line); + for (int i = 0; i < contours.size(); i++) { + cout << "*** Contour " << i + 1 << " ***" << endl; + cout << "Press ENTER to continue...."; + getline(cin, line); + for (int j = 0; j < contours[i].size(); j++) { + cout << "(" << contours[i][j].x << "," << contours[i][j].y << ")" << endl; + } + } + + // Creeer een witte image + Mat contourImage = cvCreateImage(cvSize(binary16S.cols, binary16S.rows), IPL_DEPTH_8U, 3); + contourImage = Scalar(255, 255, 255); + + // teken de contouren op de witte image + drawContours(contourImage, contours, -1, CV_RGB(255, 0, 0)); + + // druk het image met de contouren af + imshow("Found contours", contourImage); + waitKey(0); + + string pipo; + cin >> pipo; + + return 0; +} diff --git a/AdvancedVision/avansvisionlib.cpp b/AdvancedVision/avansvisionlib.cpp new file mode 100644 index 0000000..79ca0b1 --- /dev/null +++ b/AdvancedVision/avansvisionlib.cpp @@ -0,0 +1,763 @@ +// avansvisionlib - Growing Visionlibrary of Avans based on OpenCV 2.4.10 +// Goal: deep understanding of vision algorithms by means of developing own (new) algorithms. +// +// Copyright Jan Oostindie, basic version 1.0 dd 8-11-2016. Contains basic functions to perform calculations on matrices/images of class Mat. +// Including BLOB labeling functions +// +// Note: Students of Avans are free to use this library in projects and for own vision competence development. Others may ask permission to use it by means +// of sending an email to Jan Oostindie, i.e. jac.oostindie@avans.nl + +#include "avansvisionlib.h" +#include +#include /* srand, rand */ +#include /* time */ + +// pre: (i < m.rows) & (j < m.cols) +// Mat is call by reference +void setEntry(Mat m, int i, int j, double value) { + int index = i * m.cols + j; + double * p = m.ptr(0); + p[index] = value; +} // setEntry + +// pre: (i < m.rows) & (j < m.cols) +double getEntry(Mat m, int i, int j) { + int index = i * m.cols + j; + double * p = m.ptr(0); + return *(p + index); +} // getEntry + + +// func: calculate product of a row and column of equal length +// pre: (row.cols == col.rows) +double inproduct(Mat row, Mat col) { + double * p1 = row.ptr(0); + double * p2 = col.ptr(0); + double sumproducts = 0; + for (int i = 0; i < row.cols; i++) + sumproducts += p1[i] * p2[i]; + return sumproducts; +} // inproduct + +void printMatrix(Mat m) { + for (int i = 0; i < m.rows; i++) { + for (int j = 0; j < m.cols; j++) + cout << getEntry(m, i, j) << " "; + cout << endl; + } + cout << endl; +} // printMatrix + + +Mat getRow(Mat m, int rowNr) { + Mat result = Mat_(1, m.cols); + double entry; + for (int colNr = 0; colNr < m.cols; colNr++) { + entry = getEntry(m, rowNr, colNr); + // cout << " ** getRow ** " << endl; + // cout << " entry = " << entry << endl; + setEntry(result, 0, colNr, entry); + } + return result; +} // getRow + +Mat getCol(Mat m, int colNr) { + Mat result = Mat_(m.rows, 1); + double entry; + for (int rowNr = 0; rowNr < m.rows; rowNr++) { + entry = getEntry(m, rowNr, colNr); + // cout << " ** getColumn ** " << endl; + // cout << " entry = " << entry << endl; + setEntry(result, rowNr, 0, entry); + } + return result; +} // getCol + + + +// pre: (a.cols == b.rows) +Mat multiply(Mat a, Mat b) +{ + Mat result = Mat_(a.rows, b.cols); + double inprod; + for (int arow = 0; arow < a.rows; arow++) { + for (int bcol = 0; bcol < b.cols; bcol++) { + inprod = inproduct(getRow(a, arow), getCol(b, bcol)); + setEntry(result, arow, bcol, inprod); + } + } + return result; +} // multiply + +// pre: matrices have equal dimensions i.e. (a.cols == b.cols) & (a.rows == b.rows) +Mat add(Mat a, Mat b) +{ + Mat result = Mat_(a.rows, a.cols); + double entrysum; + for (int row = 0; row < a.rows; row++) { + for (int col = 0; col < a.cols; col++) { + entrysum = getEntry(a, row, col) + getEntry(b, row, col); + setEntry(result, row, col, entrysum); + } + } + return result; +} // add + + +Mat transpose(Mat m) { + Mat result = Mat_(m.cols, m.rows); + for (int row = 0; row < m.rows; row++) + for (int col = 0; col < m.cols; col++) + setEntry(result, col, row, getEntry(m, row, col)); + return result; +} // transpose + + +// func: sets all entries of a matrix to a value +// pre: true +void setValue(Mat m, double value) { + for (int row = 0; row < m.rows; row++) + for (int col = 0; col < m.cols; col++) + setEntry(m, row, col, value); +} // setValue + + +double generateRandomValue(double min, double max) { + int steps = rand() % 100 + 1; + double dx = (max - min) / 100; + return min + dx * steps; +} // generateRandomValue + + +// func: sets all entries of a matrix to a random value in interval [min,max] +// pre: true +void setRandomValue(Mat m, double min, double max) { + srand(time(NULL)); + for (int row = 0; row < m.rows; row++) + for (int col = 0; col < m.cols; col++) + setEntry(m, row, col, generateRandomValue(min, max)); +} // randomValue + + + +/*********************************** Image operaties ****************************************/ +// NB images are supposed to have 1 channel (B/W image) and depth 16 bits signed (CV_16S) +/********************************************************************************************/ + +void setEntryImage(Mat m, int i, int j, _int16 value) { + int index = i * m.cols + j; + _int16 * p = m.ptr<_int16>(0); + p[index] = value; +} // setEntry + +// pre: (i < m.rows) & (j < m.cols) +_int16 getEntryImage(Mat m, int i, int j) { + int index = i * m.cols + j; + _int16 * p = m.ptr<_int16>(0); + return *(p + index); +} // getEntryImage + +// func: calculate product of a row and column of equal length +// pre: (row.cols == col.rows) +_int16 inproductImage(Mat row, Mat col) { + _int16 * p1 = row.ptr<_int16>(0); + _int16 * p2 = col.ptr<_int16>(0); + _int16 sumproducts = 0; + for (int i = 0; i < row.cols; i++) + sumproducts += p1[i] * p2[i]; + return sumproducts; +} // inproductImage + + +Mat getRowImage(Mat m, int rowNr) { + Mat result = Mat_<_int16>(1, m.cols); + _int16 entry; + for (int colNr = 0; colNr < m.cols; colNr++) { + entry = getEntryImage(m, rowNr, colNr); + setEntryImage(result, 0, colNr, entry); + } + return result; +} // getRow + +Mat getColImage(Mat m, int colNr) { + Mat result = Mat_<_int16>(m.rows, 1); + _int16 entry; + for (int rowNr = 0; rowNr < m.rows; rowNr++) { + entry = getEntryImage(m, rowNr, colNr); + setEntryImage(result, rowNr, 0, entry); + } + return result; +} // getColImage + +Mat multiplyImage(Mat a, Mat b) +{ + Mat result = Mat_<_int16>(a.rows, b.cols); + _int16 inprod; + for (int arow = 0; arow < a.rows; arow++) { + for (int bcol = 0; bcol < b.cols; bcol++) { + inprod = inproductImage(getRow(a, arow), getColImage(b, bcol)); + setEntry(result, arow, bcol, inprod); + } + } + return result; +} // multiplyImage + +// pre: matrices have equal dimensions i.e. (a.cols == b.cols) & (a.rows == b.rows) +Mat addImage(Mat a, Mat b) +{ + Mat result = Mat_<_int16>(a.rows, a.cols); + _int16 entrysum; + for (int row = 0; row < a.rows; row++) { + for (int col = 0; col < a.cols; col++) { + entrysum = getEntryImage(a, row, col) + getEntryImage(b, row, col); + setEntryImage(result, row, col, entrysum); + } + } + return result; +} // addImage + + +// func: searches the maximum pixel value in the image +// return: maximum pixel +_int16 maxPixelImage(Mat m) { + _int16 max = getEntryImage(m, 0, 0); + _int16 next; + for (int row = 0; row < m.rows; row++) { + for (int col = 0; col < m.cols; col++) { + next = getEntryImage(m, row, col); + if (next > max) max = next; + } + } + return max; +} // maxPixelImage + +// func: searches the minimum pixel value in the image +// return: minimum pixel value +_int16 minPixelImage(Mat m) { + _int16 min = getEntryImage(m, 0, 0); + _int16 next; + for (int row = 0; row < m.rows; row++) { + for (int col = 0; col < m.cols; col++) { + next = getEntryImage(m, row, col); + if (next < min) min = next; + } + } + return min; +} // minPixelImage + +// func: determines the range of the image, i.e. the minimum +// and maximum pixel value in the image +// post: range = minPixelValue, maxPixelValue +void getPixelRangeImage(Mat m, _int16 &minPixelValue, _int16 &maxPixelValue) { + _int16 max = getEntryImage(m, 0, 0); + _int16 min = getEntryImage(m, 0, 0); + _int16 next; + for (int row = 0; row < m.rows; row++) { + for (int col = 0; col < m.cols; col++) { + next = getEntryImage(m, row, col); + if (next > max) max = next; + else + if (next < min) min = next; + } + } + minPixelValue = min; + maxPixelValue = max; +} // getPixelRangeImage + + +// func: stretches the image to a specified range +void stretchImage(Mat m, _int16 minPixelValue, _int16 maxPixelValue) { + _int16 min, max, oldValue, newValue; + getPixelRangeImage(m, min, max); + double scale = maxPixelValue - minPixelValue; + scale /= (max - min); + for (int row = 0; row < m.rows; row++) { + for (int col = 0; col < m.cols; col++) { + oldValue = getEntryImage(m, row, col); + newValue = scale * (oldValue - min) + minPixelValue; + setEntryImage(m, row, col, newValue); + } + } +} // stretchImage + + +// func: shows a 16S image on the screen +// pre: m is a 16S image (depth 16 bits, signed) +void show16SImageStretch(Mat m, string windowTitle) { + Mat mCopy; + m.copyTo(mCopy); + stretchImage(mCopy, 0, 255); + mCopy.convertTo(mCopy, CV_8U); +// namedWindow(windowTitle, CV_WINDOW_AUTOSIZE); + imshow(windowTitle, mCopy); + waitKey(0); +} // show16SImage + +// func: shows a 16S image on the screen. All values clipped to the interval 0-255 +// i.e. value < 0 => 0; 0 <= value <= 255 => value ; value > 255 => 255 +/// pre: m is a 16S image (depth 16 bits, signed) +void show16SImageClip(Mat m, string windowTitle) { + Mat mCopy; + m.copyTo(mCopy); + mCopy.convertTo(mCopy, CV_8U); +// namedWindow("show16SImageClip", CV_WINDOW_AUTOSIZE); + imshow(windowTitle, mCopy); + waitKey(0); +} // show16SImage + + +// func: histogram gamma correction +// pre: image has depth 8 bits unsigned and 1 or 3 channels +// post: entry(i,j) = 255*power(entry@pre(i,j)/255)^gamma +void gammaCorrection(Mat image, float gamma) { + unsigned char lut[256]; + for (int i = 0; i < 256; i++) { + lut[i] = saturate_cast(pow((float)(i / 255.0), gamma) * 255.0f); + } + // dst = src.clone(); + const int channels = image.channels(); + switch (channels) { + case 1: { + MatIterator_ it, end; + for (it = image.begin(), end = image.end(); it != end; it++) + *it = lut[(*it)]; + break; + } + case 3: { + MatIterator_ it, end; + for (it = image.begin(), end = image.end(); it != end; it++) { + (*it)[0] = lut[((*it)[0])]; + (*it)[1] = lut[((*it)[1])]; + (*it)[2] = lut[((*it)[2])]; + } + break; + } + } // switch +} // gammaCorrection + + +// func: makes a administration used for labeling blobs. +// the function adds a edge of 1 pixel wide tot a binary image, all with value 0. +// All 1's are made -1. The result is returned. +// This function is used by function labelBLOBs +// pre : binaryImage has depth 16 bits signed int. Contains only values 0 and 1. +// return_matrix: All "1" are made "-1" meaning value 1 and unvisited. +Mat makeAdmin(Mat binaryImage) { + Mat result = Mat_<_int16>(binaryImage.rows+2, binaryImage.cols+2); + + // eerste rij 0 maken + for (int col = 0; col < result.cols; col++) + setEntryImage(result,0,col,0); + + // binaryImage copieren naar admin waarbij een 1 steeds wordt omgezet naar -1. + for (int row = 1; row < (result.rows-1); row++) { + + // 0 vooraan de rij zetten + setEntryImage(result,row,0,0); + + // rij binaryImage copieren + _int16 value; + for (int col = 1; col < result.cols - 1; col++) { + value = getEntryImage(binaryImage, row - 1, col - 1); + if (value == 1) value = -1; + setEntryImage(result, row, col, value); + } + + // 0 achteraan de rij zetten + setEntryImage(result, row, result.cols - 1, 0); + } // for + + // laatste rij 0 maken + for (int col = 0; col < result.cols; col++) + setEntryImage(result, result.rows-1, col, 0); + + return result; +} // makeAdmin + + +// func: Searches the next blob after position (row,col) i.e. searches +// the next -1 in admin +// post: if return_value == 1 then (row,col) contains the position +// where the next blob starts. +// return_value: 1 next blob found ; starting position is (row,col) +// 0 no blob found ; (row, col) == (-1, -1) +bool findNextBlob(Mat admin, int & row, int & col) { + + bool found = false; + + // zoeken in de huidige rij + for (int currCol = col; (currCol < (admin.cols - 1)) & !found; currCol++) + if (getEntryImage(admin, row, currCol) == -1) { + found = true; + // row unchanged + col = currCol; + } + + // zoeken vanaf de volgende rij + for (int currRow = row+1; (currRow < (admin.rows-1)) &!found; currRow++) + for (int currCol = 1; (currCol < (admin.cols-1)) & !found; currCol++) + if (getEntryImage(admin, currRow, currCol) == -1) { + found = true; + row = currRow; + col = currCol; + } + + if (!found) { + row = -1; + col = -1; + } + return found; +}; // findNextBlob + +// func: gets the entry of a neighbour pixel with relative position nr. +// Definition of relative positions nr: +// 7 0 1 +// 6 X 2 +// 5 4 3 +_int16 getEntryNeighbour(const Mat & admin , int x, int y, int nr) { + switch (nr) { + case 0: return getEntryImage(admin, x - 1, y ); break; + case 1: return getEntryImage(admin, x - 1, y + 1); break; + case 2: return getEntryImage(admin, x , y + 1); break; + case 3: return getEntryImage(admin, x + 1, y + 1); break; + case 4: return getEntryImage(admin, x + 1, y); break; + case 5: return getEntryImage(admin, x + 1, y - 1); break; + case 6: return getEntryImage(admin, x, y - 1); break; + case 7: return getEntryImage(admin, x - 1, y - 1); break; + default: cout << "ERROR getEntryNeighbour " << endl; + } +} // getEntryNeighbour + +// func: determines if there are more than 1 adjacent 1's +bool moreNext1(const Mat & admin, int x, int y) { + int cnt1 = 0; + bool more = false; + for (int nr = 0; (nr <= 7) & !more; nr++) + if (getEntryNeighbour(admin, x, y, nr) == -1) { + cnt1++; + if (cnt1 > 1) more = true; + } + return more; +} // moreNext1 + +// func: searches the first 1 when rotating around the pixel (currX,currY), +// starting at position 0. Definition of relative positions: +// 7 0 1 +// 6 X 2 +// 5 4 3 +void findNext1(Mat admin, int & currX, int & currY, int & next1) { + int rotX, rotY; + + rotX = currX - 1 ; rotY = currY ; //0 + if (getEntryImage(admin, rotX, rotY) == -1) next1 = 0; + else { + rotX = currX - 1; rotY = currY + 1; //1 + if (getEntryImage(admin, rotX, rotY) == -1) next1 = 1; + else { + rotX = currX; rotY = currY + 1; //2 + if (getEntryImage(admin, rotX, rotY) == -1) next1 = 2; + else { + rotX = currX + 1; rotY = currY + 1; //3 + if (getEntryImage(admin, rotX, rotY) == -1) next1 = 3; + else { + rotX = currX + 1; rotY = currY; //4 + if (getEntryImage(admin, rotX, rotY) == -1) next1 = 4; + else { + rotX = currX + 1; rotY = currY - 1; //5 + if (getEntryImage(admin, rotX, rotY) == -1) next1 = 5; + else { + rotX = currX; rotY = currY - 1; //6 + if (getEntryImage(admin, rotX, rotY) == -1) next1 = 6; + else { + rotX = currX - 1; rotY = currY - 1; //7 + if (getEntryImage(admin, rotX, rotY) == -1) next1 = 7; + else next1 = -99; + } // 6 + } // 5 + } // 4 + } // 3 + } // 2 + } // 1 + } //0 + if (next1 >= 0) { + currX = rotX; + currY = rotY; + } +} // findNext1 + + +// func: labels all pixels of one blob which starts at position (row,col) with blobNr. +// This function is used by function labelBLOB's which labels all blobs. +// return_value: area of the blob +// Evaluation: This function uses a iterative algorithm in which a special labeling technique is +// is used which gives the opportunity to trace all individiual pixels. This makes it +// possible for example to save only these pixels on disk or to translate the object in +// in the image. +// The disadvantagae however is that the algorithm is more complicated an maybe a little bit +// slower than the recursive variant. +int labelIter(Mat & admin, int row, int col, int blobNr) { + // Every visited pixel is labeled with: + // blobNr*10 + + // + // definition of relative positions + // 7 0 1 + // 6 X 2 + // 5 4 3 + // + // The first visited pixel, i.e. (row,col), is labeled with: + // blobNr * 10 + 8 + + int x = row, y = col; + setEntryImage(admin, x, y, blobNr*10 + 8); + + int next1 = -999; + int area = 1; + + // flag more is set when any entry in the path has two or more + // unvisited neighbours because we visit only one at a time. + bool allLabeledFlag = true; + while (allLabeledFlag) { + allLabeledFlag = false; + bool pathLabeled = false; + while (!pathLabeled) { + + if (!allLabeledFlag) allLabeledFlag = moreNext1(admin, x, y); + findNext1(admin, x, y, next1); + + if (next1 >= 0) { + setEntryImage(admin, x, y, blobNr*10 + next1); + area++; + } + else { + //findprevious + switch (getEntryImage(admin, x, y) % 10) { + case 0: x += 1; break; + case 1: x += 1; y -= 1; break; + case 2: y -= 1; break; + case 3: x -= 1; y -= 1; break; + case 4: x -= 1; break; + case 5: x -= 1; y += 1; break; + case 6: y += 1; break; + case 7: x += 1; y += 1; break; + case 8: pathLabeled = true; break; // currIndex should be 0 now + default: cout << "Error func labelIter!"; + } // case + } // else + } // while + } // while (more) + return area; +} // labelIter + + +// func: labels all pixels of one blob which starts at position (topX,topY) with blobNr. +// During the labeling proces the centre of gravity is calculated. +// This function is used by function labelBLOBInfo +// return_value: area of the blob +// Evaluation: This function uses a iterative algorithm in which a special labeling technique is +// is used which gives the opportunity to trace all individiual pixels. This makes it +// possible for example to save only these pixels on disk or to translate the object in +// in the image. +// The disadvantagae however is that the algorithm is more complicated an maybe a little bit +// slower than the recursive variant. +int labelIterInfo(Mat & admin, int topX, int topY, int blobNr, + int & xGravity, int & yGravity) { + + // Every visited pixel is labeled with: + // blobNr*10 + + // + // definition of relative positions + // 7 0 1 + // 6 X 2 + // 5 4 3 + // + // The first visited pixel, i.e. (row,col), is labeled with: + // blobNr * 10 + 8 + + xGravity = topX; + yGravity = topY; + int x = topX, y = topY; + setEntryImage(admin, topX, topY, blobNr * 10 + 8); + + int next1 = -999; + int area = 1; + + // allLabeledFlag is set when any entry in the path has two or more + // unvisited neighbours because we visit only one at a time. + // This algorithm good be speeded up by using a stack (future improvement) + bool allLabeledFlag = true; + while (allLabeledFlag) { + allLabeledFlag = false; + bool pathLabeled = false; + while (!pathLabeled) { + + if (!allLabeledFlag) allLabeledFlag = moreNext1(admin, x, y); + findNext1(admin, x, y, next1); + + if (next1 >= 0) { + setEntryImage(admin, x, y, blobNr * 10 + next1); + area++; + xGravity += x; + yGravity += y; + } + else { + //findprevious + switch (getEntryImage(admin, x, y) % 10) { + case 0: x += 1; break; + case 1: x += 1; y -= 1; break; + case 2: y -= 1; break; + case 3: x -= 1; y -= 1; break; + case 4: x -= 1; break; + case 5: x -= 1; y += 1; break; + case 6: y += 1; break; + case 7: x += 1; y += 1; break; + case 8: pathLabeled = true; break; // currIndex should be 0 now + default: cout << "Error func labelIter!"; + } // case + } // else + } // while + } // while (more) + xGravity /= area; + yGravity /= area; + return area; +} // labelIterInfo + + + +// func: labels all pixels of one blob which starts at position (row,col) with blobNr. +// return_value: area of the blob +// Evaluation: This function uses a recursive algorithm which has the advantage that it is easy and trasparent. +// The disadvantagae however is that it claims a lot of spacee on the stack. I.e. every found +// pixel results in a function call which in case of large blobs causes a stack overflow. +int labelRecursive(Mat & admin, int row, int col, int blobNr) { + int area = 0; + // bij waarde -1 is het pixel nog niet bezocht + if (getEntryImage(admin, row, col) == -1) { + //cout << "(row,col) = " << "(" << row << "," << col << ")" << endl; + // pixel labelen met het volgnummer van de blob + setEntryImage(admin, row, col, blobNr); + area = 1; + + // alle pixels rondom huidige pixel bezoeken + // (row-1,col-1) (row-1,col ) (row-1,col+1) + // (row ,col-1) (row, col ) (row ,col+1) + // (row+1,col-1) (row, col ) (row+1,col+1) + area += labelRecursive(admin, row - 1, col, blobNr); + area += labelRecursive(admin, row - 1, col + 1, blobNr); + area += labelRecursive(admin, row, col + 1, blobNr); + area += labelRecursive(admin, row + 1, col + 1, blobNr); + area += labelRecursive(admin, row, col, blobNr); + area += labelRecursive(admin, row + 1, col - 1, blobNr); + area += labelRecursive(admin, row, col - 1, blobNr); + area += labelRecursive(admin, row - 1, col - 1, blobNr); + } + return area; +} // label + +// func: retrieves a labeledImage from the labeling administration +// pre : admin is contains labeled pixels with neighbour number information. +// post: labeledImage: binary 8-connected pixels with value 1 in binaryImage are +// labeled with the number of the object they belong to. +void retrieveLabeledImage(const Mat & admin, Mat & labeledImage) { + labeledImage = Mat_<_int16>(admin.rows-2,admin.cols-2); + + for (int row = 1; row < admin.rows-1; row++) { + for (int col = 1; col < admin.cols-1; col++) { + setEntryImage(labeledImage, row-1,col-1, + getEntryImage(admin, row, col) / 10); + } + } +} // retrieveLabeledImage + + +// func: labeling of all blobs in a binary image +// pre : binaryImage has depth 16 bits signed int. Contains only values 0 and 1. +// post: labeledImage: binary 8-connected pixels with value 1 in binaryImage are +// labeled with the number of the object they belong to. +// return_value: the total number of objects. +int labelBLOBs(Mat binaryImage, Mat & labeledImage) { + + // admin contains the administration of the recursive process. + // meaning of the entry values: + // -1: a "1" which is not visited yet. Changes to 1 when visited. + // 0: always a "0" + // 1, 2, 3,... : a "1" which is visited and is labeled with the object number. + Mat admin = makeAdmin(binaryImage); + int row = 1; + int col = 1; + + // init volgnummer + int blobNr = 0; + + // label alle BLOBs met een volgnummer + while ((row > 0) & (row < (admin.rows - 1)) & + (col > 0) & (col < (admin.cols - 1))) + if (findNextBlob(admin, row, col)) labelIter(admin, row, col, ++blobNr); + + retrieveLabeledImage(admin, labeledImage); + + // laatste volgnummer is gelijk aan het aantal gevonden blobs + return blobNr; +} // labelBLOBs + +// func: removes a BLOB from the labeling administration +// pre: (posx,posy) is the position of the BLOB, blobNr the number +// of the blob to be removed. +void removeBLOB(Mat & admin, int blobNr) { + _int16 value; + for (int row = 1; row < admin.rows-2; row++) + for (int col = 1; col < admin.cols-2; col++) { + value = getEntryImage(admin, row, col); + while (value > 10) value /= 10; + if (value == blobNr) setEntryImage(admin, row, col, 0); + } +} // removeBLOB + +// func: labeling of all blobs in a binary image with a area in [threhAreaMin,threhAreaMax]. Default +// threshold is [1,INT_MAX]. Alle gathered data during the labeling proces is returned, +// i.e. the positions of the firstpixel of each blob, the position of the blobs (i.e. the +// centres of gravity) and the area's of all blobs. +// pre : binaryImage has depth 16 bits signed int. Contains only values 0 and 1. +// post: labeledImage: binary 8-connected pixels with value 1 in binaryImage are +// labeled with the number of the object they belong to. +// areaVec: contains all area's of the blobs. The index corresponds to the number +// of the blobs. Index 0 has no meaning. +// return_value: the total number of objects. +int labelBLOBsInfo(Mat binaryImage, Mat & labeledImage, + vector & firstpixelVec, vector & posVec, + vector & areaVec, + int threshAreaMin, int threshAreaMax) { + + // admin contains the administration of the recursive process. + // meaning of the entry values: + // -1: a "1" which is not visited yet. + // 0: always a "0" + // 1, 2, 3,... : a "1" which is visited and is labeled with the object number. + Mat admin = makeAdmin(binaryImage); + int row = 1; + int col = 1; + + // init volgnummer + int blobNr = 0; + int area; + int xGravity, yGravity; + // label alle BLOBs met een volgnummer + while ((row > 0) & (row < (admin.rows - 1)) & + (col > 0) & (col < (admin.cols - 1))) + + if (findNextBlob(admin, row, col)) { + + area = labelIterInfo(admin, row, col, ++blobNr, xGravity, yGravity); + + if ((area >= threshAreaMin) & (area <= threshAreaMax)) { + firstpixelVec.push_back(new Point2d(row - 1, col - 1)); + posVec.push_back(new Point2d(xGravity-1, yGravity-1)); + areaVec.push_back(area); + } + else removeBLOB(admin, blobNr--); + } + + retrieveLabeledImage(admin, labeledImage); + + // laatste volgnummer is gelijk aan het aantal gevonden blobs + return blobNr; +} // labelBLOBsInfo diff --git a/AdvancedVision/avansvisionlib.h b/AdvancedVision/avansvisionlib.h new file mode 100644 index 0000000..704376e --- /dev/null +++ b/AdvancedVision/avansvisionlib.h @@ -0,0 +1,233 @@ +// avansvisionlib - Growing Visionlibrary of Avans based on OpenCV 2.4.10 +// Goal: deep understanding of vision algorithms by means of developing own (new) algorithms. +// +// Copyright Jan Oostindie, basic version 0.2 dd 15-9-2016. Contains basic functions to perform calculations on matrices/images of class Mat. +// Including BLOB labeling functions +// +// Note: Students of Avans are free to use this library in projects and for own vision competence development. Others may ask permission to use it by means +// of sending an email to Jan Oostindie, i.e. jac.oostindie@avans.nl + +#include +#include +#include +#include +#include + +using namespace cv; +using namespace std; + +// remark: a functioncall with a Mat-object parameter is a call by reference + +/*********************** PROTOTYPES of the function library ************************/ + +// func: setup a specified entry (i,j) of a matrix m with a specific value +// pre: (i < m.rows) & (j < m.cols) +void setEntry(Mat m, int i, int j, double value); + +// func: get the value of a specified entry (i,j) of a matrix m +// pre: (i < m.rows) & (j < m.cols) +// return: == m(i,j) +double getEntry(Mat m, int i, int j); + +// func: calculate product of a row and column of equal length +// pre: (row.cols == col.rows) & (row.rows == 1) & (col.cols == 1) +double inproduct(Mat row, Mat col); + +// func: prints matrix m in the console +// pre: true +void printMatrix(Mat m); + +// func: select and get a row of a matrix m. rowNr contains the row number +// pre: 0 < rowNr < m.rows +// return: contains the selected row +Mat getRow(Mat m, int rowNr); + +// func: get a column of a matrix m. colNr contains the column number +// pre: 0 < colNr < m.cols +// return: contains the selected column +Mat getCol(Mat m, int colNr); + +// func: multiply two matrices a and b +// pre: (a.cols == b.rows) +// return: .rows == b.rows & .cols == b.cols +Mat multiply(Mat a, Mat b); + +// pre: matrices have equal dimensions i.e. (a.cols == b.cols) & (a.rows == b.rows) +// return: (i,j) == a(i,j) + b(i,j) for all (0,0) <= (i,j) < (a.rows,a.cols) +Mat add(Mat a, Mat b); + +// func: transposes a matrix +// return: (i,j) = m(j,i) & .rows = m.cols & .cols = m.rows +Mat transpose(Mat m); + +// func: sets all entries of a matrix to a certain value +// pre: true +void setValue(Mat m, double value); + +// func: generates a randomvalue between min and max +// pre: true +double generateRandomValue(double min, double max); + + +// func: sets all entries of a matrix to a random value +// pre: true +void setRandomValue(Mat m, double min, double max); + + + +/*********************************** Image operaties ****************************************/ +// NB images are supposed to have 1 channel (B/W image) and depth 16 bits signed (CV_16S) +/********************************************************************************************/ + +// func: setup a specified entry (i,j) of a matrix m with a specific value +// pre: (i < m.rows) & (j < m.cols) +void setEntryImage(Mat m, int i, int j, _int16 value); + +// func: get the value of a specified entry (i,j) of a matrix m +// pre: (i < m.rows) & (j < m.cols) +// return: == m(i,j) +_int16 getEntryImage(Mat m, int i, int j); + +// func: calculate product of a row and column of equal length +// pre: (row.cols == col.rows) & (row.rows == 1) & (col.cols == 1) +_int16 inproductImage(Mat row, Mat col); + +// func: select and get a row of a matrix m. rowNr contains the row number +// pre: 0 < rowNr < m.rows +// return: contains the selected row +Mat getRowImage(Mat m, int rowNr); + +// func: get a column of a matrix m. colNr contains the column number +// pre: 0 < colNr < m.cols +// return: contains the selected column +Mat getColImage(Mat m, int colNr); + +// func: multiply two matrices a and b +// pre: (a.cols == b.rows) +// return: .rows == b.rows & .cols == b.cols +Mat multiplyImage(Mat a, Mat b); + +// pre: matrices have equal dimensions i.e. (a.cols == b.cols) & (a.rows == b.rows) +// return: (i,j) == a(i,j) + b(i,j) for all (0,0) <= (i,j) < (a.rows,a.cols) +Mat addImage(Mat a, Mat b); + + +// func: searches the maximum pixel value in the image +// return: maximum pixel value +_int16 maxPixelImage(Mat m); + +// func: searches the minimum pixel value in the image +// return: minimum pixel value +_int16 minPixelImage(Mat m); + +// func: determines the range of the image, i.e. the minimum +// and maximum pixel value in the image +// post: range = minPixelValue, maxPixelValue +void getPixelRangeImage(Mat m, _int16 &minPixelValue, _int16 &maxPixelValue); + +// func: transform scale the image +// return: maximum pixel value +void stretchImage(Mat m, _int16 minPixelValue, _int16 maxPixelValue); + +// func: shows a 16S image on the screen. All values mapped on the interval 0-255 +/// pre: m is a 16S image (depth 16 bits, signed) +void show16SImageStretch(Mat m, string windowTitle = "show16SImageStretch"); + + +// func: shows a 16S image on the screen. All values clipped to the interval 0-255 +// i.e. value < 0 => 0; 0 <= value <= 255 => value ; value > 255 => 255 +/// pre: m is a 16S image (depth 16 bits, signed) +void show16SImageClip(Mat m, string windowTitle = "show16SImageClip"); + + + +// func: histogram gamma correction +// pre: image has depth 8 bits unsigned and 1 or 3 channels +// post: entry(i,j) = 255*power(entry@pre(i,j)/255)^gamma +void gammaCorrection(Mat image, float gamma); + + +// func: makes a administration used for labeling blobs. +// the function adds a edge of 1 pixel wide tot a binary image, all with value 0. +// All 1's are made -1. The result is returned. +// This function is used by function labelBLOBs +// pre : binaryImage has depth 16 bits signed int. Contains only values 0 and 1. +// return_matrix: All "1" are made "-1" meaning value 1 and unvisited. +Mat makeAdmin(Mat binaryImage); + + +// func: Searches the next blob after position (row,col) +// post: if return_value == 1 then (row,col) contains the position +// where the next blob starts. +// return_value: true => blob found ; starting position is (row,col) +// false => no blob found ; (row, col) == (-1, -1) +bool findNextBlob(Mat admin, int & row, int & col); + + +// func: searches the first 1 when rotating around the pixel (currX,currY), +// starting at position 0. Definition of relative positions: +// 7 0 1 +// 6 X 2 +// 5 4 3 +void findNext1(Mat admin, int & currX, int & currY, int & next1); + +// func: gets the entry of a neighbour pixel with relative position nr. +// Definition of relative positions nr: +// 7 0 1 +// 6 X 2 +// 5 4 3 +_int16 getEntryNeighbour(const Mat & admin, int x, int y, int nr); + + +// func: determines if there are more than 1 adjacent 1's +bool moreNext1(const Mat & admin, int x, int y); + + + +// func: labels all pixels of one blob which starts at position (row,col) with blobNr. +// This function is used by function labelBLOB's which labels all blobs. +// return_value: area of the blob +// Evaluation: This function uses a iterative algorithm in which a special labeling technique is +// is used which gives the opportunity to trace all individiual pixels. This makes it +// possible for example to save only these pixels on disk or to translate the object in +// in the image. +// The disadvantagae however is that the algorithm is more complicated an maybe a little bit +// slower than the recursive variant. +int labelIter(Mat & admin, int row, int col, int blobNr); + + +// func: labels all pixels of one blob which starts at position (row,col) with blobNr. +// return_value: area of the blob +// Evaluation: This function uses a recursive algorithm which has the advantage that it is easy and trasparent. +// The disadvantagae however is that it claims a lot of spacee on the stack. I.e. every found +// pixel results in a function call which in case of large blobs causes a stack overflow. +int labelRecursive(Mat & admin, int row, int col, int blobNr); + +// func: retrieves a labeledImage from the labeling administration +// pre : admin is contains labeled pixels with neighbour number information. +// post: labeledImage: binary 8-connected pixels with value 1 in binaryImage are +// labeled with the number of the object they belong to. +void retrieveLabeledImage(const Mat & admin, Mat & labeledImage); + +// func: labeling of all blobs in a binary image +// pre : binaryImage has depth 16 bits signed int. Contains only values 0 and 1. +// post: labeledImage: binary 8-connected pixels with value 1 in binaryImage are +// labeled with the number of the object they belong to. +// return_value: the total number of objects. +int labelBLOBs(Mat binaryImage, Mat & labeledImage); + + +// func: labeling of all blobs in a binary image with a area in [threshAreaMin,threshAreaMax]. Default +// threshold is [1,INT_MAX]. Alle gathered data during the labeling proces is returned, +// i.e. the positions of the firstpixel of each blob, the position of the blobs (i.e. the +// centres of gravity) and the area's of all blobs. +// pre : binaryImage has depth 16 bits signed int. Contains only values 0 and 1. +// post: labeledImage: binary 8-connected pixels with value 1 in binaryImage are +// labeled with the number of the object they belong to. +// areaVec: contains all area's of the blobs. The index corresponds to the number +// of the blobs. Index 0 has no meaning. +// return_value: the total number of objects. +int labelBLOBsInfo(Mat binaryImage, Mat & labeledImage, + vector & firstpixelVec, vector & posVec, + vector & areaVec, + int threshAreaMin = 1, int threshAreaMax = INT_MAX); diff --git a/AdvancedVision/input/Week 2/basisfiguren.jpg b/AdvancedVision/input/Week 2/basisfiguren.jpg new file mode 100644 index 0000000..8f843a2 Binary files /dev/null and b/AdvancedVision/input/Week 2/basisfiguren.jpg differ diff --git a/AdvancedVision/input/Week 2/bolenpijl.jpg b/AdvancedVision/input/Week 2/bolenpijl.jpg new file mode 100644 index 0000000..422b24f Binary files /dev/null and b/AdvancedVision/input/Week 2/bolenpijl.jpg differ diff --git a/AdvancedVision/input/Week 2/monsters.jpg b/AdvancedVision/input/Week 2/monsters.jpg new file mode 100644 index 0000000..3013a3f Binary files /dev/null and b/AdvancedVision/input/Week 2/monsters.jpg differ diff --git a/AdvancedVision/input/Week 2/rummikub.JPG b/AdvancedVision/input/Week 2/rummikub.JPG new file mode 100644 index 0000000..5b7cca7 Binary files /dev/null and b/AdvancedVision/input/Week 2/rummikub.JPG differ diff --git a/AdvancedVision/input/Week 2/rummikub0.jpg b/AdvancedVision/input/Week 2/rummikub0.jpg new file mode 100644 index 0000000..c329e4c Binary files /dev/null and b/AdvancedVision/input/Week 2/rummikub0.jpg differ