Skip to content

Commit

Permalink
Add 'sessie 1' (#4)
Browse files Browse the repository at this point in the history
* Init Travis CI

* Fixed build path

* Reduce CMake minimum version to 3.9

* Travis CI doesn't have OpenCV installed by default

* Use PPA to retrieve OpenCV

* Add PPA silently

* Install OpenCV dev package too

* Building OpenCV manually

* Updated assignments + init sessie_1

* Setup CMake project

* Sessie 1 opdracht 1

* Finished task 1

* WIP task 2

* Fixed build

* Finished sessie 1

* Updated CI
  • Loading branch information
DylanVanAssche authored Oct 26, 2018
1 parent 1a7b33f commit 7a2c295
Show file tree
Hide file tree
Showing 11 changed files with 373 additions and 1 deletion.
11 changes: 11 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,14 @@ script:
- cd sessie_0
- cmake CMakeLists.txt
- make
- cd ..
# Build sessie_1
- cd sessie_1
- cd opdracht_1
- cmake CMakeLists.txt
- make
- cd ..
- cd opdracht_2
- cmake CMakeLists.txt
- make
- cd ../..
2 changes: 1 addition & 1 deletion docs
Submodule docs updated from 4483bc to 0e2100
23 changes: 23 additions & 0 deletions sessie_1/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Sessie 1 - pixel manipulatie

## Opdracht 1.1: Thresholding

* Lees afbeelding `imageColor.png` en segmenteer de skin pixels
* Gebruik hiervoor een filter uit de literatuur in het RGB domein: `(RED>95) && (GREEN>40) && (BLUE>20) && ((max(RED,max(GREEN,BLUE)) - min(RED,min(GREEN,BLUE)))>15) && (abs(RED-GREEN)>15) && (RED>GREEN) && (RED>BLUE);`
* Visualiseer naast het masker (binaire pixels die tot huid behoren) dat je bekomt ook de resulterende pixelwaarden.
* Lees afbeelding `imageBimodal.png` in en segmenteer de tekst
* Start met OTSU thresholding - wat gaat er mis?
* Hoe kunnen we het originele beeld verbeteren om dit tegen te gaan?
* Histogram equalization
* CLAHE: Contrast Limited Adaptive Histogram Equalization
* Merk je bepaalde problemen bij beide technieken?

## Opdracht 1.2: Erosie en dilatie

* Haal afbeelding `imageColorAdapted` door je thresholding pipeline en ga na wat er mis gaat.
* Gebruik `opening`, `closing`, `dilatie`, `erosie` om het binaire masker op te schonen en ruis te onderdrukken.
* Kan je gescheiden ledematen terug met elkaar verbinden?
* Kun je niet volledige binaire regio's terug vullen?
* Probeer in resulterende blobs regios weg te werken

Bekijk zeker `findContours()`, `convexHull` en `drawContours`!
12 changes: 12 additions & 0 deletions sessie_1/opdracht_1/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# CMake setup
cmake_minimum_required(VERSION 3.9)
project(sessie_1-opdracht_1)
set(CMAKE_CXX_STANDARD 98)

# OpenCV libraries
find_package(OpenCV REQUIRED)
include_directories(${OpenCV_INCLUDE_DIRS})

# Output executable
add_executable(sessie_1-opdracht_1 main.cpp)
target_link_libraries(sessie_1-opdracht_1 ${OpenCV_LIBS})
Binary file added sessie_1/opdracht_1/imageBimodal.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added sessie_1/opdracht_1/imageColor.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added sessie_1/opdracht_1/imageColorAdapted.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
136 changes: 136 additions & 0 deletions sessie_1/opdracht_1/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;
using namespace cv;

int main(int argc, const char** argv) {
CommandLineParser parser(argc, argv,
"{ help h usage ? | | Shows this message.}"
"{ bimodal b | | Loads a bimodal image <REQUIRED> }"
"{ color c | | Loads a color image <REQUIRED> }"
);

// Help printing
if(parser.has("help") || argc <= 1) {
cerr << "Please use absolute paths when supplying your images." << endl;
parser.printMessage();
return 0;
}

// Parser fail
if (!parser.check())
{
parser.printErrors();
return -1;
}

// Required arguments supplied?
string bimodal(parser.get<string>("bimodal"));
string color(parser.get<string>("color"));
if(bimodal.empty() || color.empty())
{
cerr << "Please supply your images using command line arguments: --bimodal=imageBimodal.jpg and --color=imageColor.jpg or --color=imageColorAdapted.jpg" << endl;
return -1;
}

// Try to load images
Mat bimodalImg;
Mat colorImg; // BGR
bimodalImg = imread(bimodal, IMREAD_GRAYSCALE);
colorImg = imread(color, IMREAD_COLOR);

if(bimodalImg.empty() || colorImg.empty()) {
cerr << "Loading images failed, please verify the paths to the images." << endl;
return -1;
}

// Thresholding skin pixels
// Approach 1: looping through all the pixels using a double for lus
// Print all pixels of our colorImg image to the command line;
Mat maskerLoop(colorImg.size(), CV_8UC1);
Mat segmentationLoop(colorImg.size(), CV_8UC3);
for(int rowIndex = 0; rowIndex < colorImg.rows; rowIndex++)
{
for(int columnIndex = 0; columnIndex < colorImg.cols; columnIndex++) {
cout << "(" << (int)colorImg.at<Vec3b>(rowIndex, columnIndex)[0] << "," << (int)colorImg.at<Vec3b>(rowIndex, columnIndex)[1] << "," << (int)colorImg.at<Vec3b>(rowIndex, columnIndex)[2] << ")"; // typecasting to show it properly in the terminal
cout << " ";
int blue = (int)colorImg.at<Vec3b>(rowIndex, columnIndex)[0];
int green = (int)colorImg.at<Vec3b>(rowIndex, columnIndex)[1];
int red = (int)colorImg.at<Vec3b>(rowIndex, columnIndex)[2];
// Filter formula from the assignment
if((red > 95) && (green > 40) && (blue > 20) && ((max(red, max(green, blue)) - min(red, min(green, blue))) > 15) && (abs(red - green) > 15) && (red > green) && (red > blue))
{
maskerLoop.at<uchar>(rowIndex, columnIndex) = 255;
}
else {
maskerLoop.at<uchar>(rowIndex, columnIndex) = 0;
}
}
cout << endl; // new line when row++
}
cout << endl;

// Combine masker and image
colorImg.copyTo(segmentationLoop, maskerLoop);

namedWindow("Skin extraction pixel loop", WINDOW_AUTOSIZE);
imshow("Skin extraction pixel loop", maskerLoop);
namedWindow("Segmentation pixel loop", WINDOW_AUTOSIZE);
imshow("Segmentation pixel loop", segmentationLoop);
waitKey(0); // Wait until the user presses a key

// Approach 2: using OpenCV matrix operations to retrieve all the pixels (binary image) that are valid for the filter
Mat splitted[3];
Mat maskerMatrixOperation(colorImg.size(), CV_8UC1);
Mat segmentationMatrixOperation(colorImg.size(), CV_8UC3);
split(colorImg, splitted);
Mat blue = splitted[0]; // merge() replaces this boiler plate code
Mat green = splitted[1];
Mat red = splitted[2];
maskerMatrixOperation = (red > 95) & (green > 40) & (blue > 20) & ((max(red, max(green, blue)) - min(red, min(green, blue))) > 15) & (abs(red - green) > 15) & (red > green) & (red > blue);
// Combine masker and image
colorImg.copyTo(segmentationMatrixOperation, maskerMatrixOperation);

namedWindow("Skin extraction matrix operations", WINDOW_AUTOSIZE);
imshow("Skin extraction matrix operations", maskerMatrixOperation);
namedWindow("Segmentation matrix operations", WINDOW_AUTOSIZE);
imshow("Segmentation matrix operations", segmentationMatrixOperation);
waitKey(0); // Wait until the user presses a key

// Bimodal OTSU
namedWindow("OTSU original", WINDOW_AUTOSIZE);
imshow("OTSU original", bimodalImg);

Mat threshedImg;
threshold(bimodalImg, threshedImg, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU);
namedWindow("OTSU thresholding", WINDOW_AUTOSIZE);
imshow("OTSU thresholding", threshedImg);
waitKey(0); // Wait until the user presses a key
// A part of the ticket is completely black (left, bottom), OTSU depends on a bimodal image -> no uniform background color
// We can use histogram equalisation and CLAHE to optimize the image before applying OTSU
// 1. histogram equalisation: isn't that great either
Mat bimodalImgEqualized;
Mat threshedImgEqualized;
equalizeHist(bimodalImg, bimodalImgEqualized); // previously: .clone() to avoid losing original matrix (obselete by now)
threshold(bimodalImgEqualized, threshedImgEqualized, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU);
namedWindow("Equalized OTSU", WINDOW_AUTOSIZE);
imshow("Equalized OTSU", threshedImgEqualized);
waitKey(0);

// 2. CLAHE: advanced locally equalisation using a window, a bit better than normal equalisation since the
// thresholding is applied locally, non equal backgrounds are difficult for OTSU, CLAHE makes the background more
// equally then histogram thresholding
Mat bimodalImgCLAHE;
Mat threshedImgCLAHE;
Ptr<CLAHE> clahe_p = createCLAHE();
clahe_p->setTilesGridSize(Size(15, 15)); // window size
clahe_p->setClipLimit(1); // 1 - 10 contrast
clahe_p->apply(bimodalImg.clone(), bimodalImgCLAHE); // still old C function, use .clone() to be sure
threshold(bimodalImgCLAHE, threshedImgCLAHE, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU);
namedWindow("OTSU CLAHE", WINDOW_AUTOSIZE);
imshow("OTSU CLAHE", threshedImgCLAHE);
waitKey(0);

return 0;
}
12 changes: 12 additions & 0 deletions sessie_1/opdracht_2/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# CMake setup
cmake_minimum_required(VERSION 3.9)
project(sessie_1-opdracht_2)
set(CMAKE_CXX_STANDARD 98)

# OpenCV libraries
find_package(OpenCV REQUIRED)
include_directories(${OpenCV_INCLUDE_DIRS})

# Output executable
add_executable(sessie_1-opdracht_2 main.cpp)
target_link_libraries(sessie_1-opdracht_2 ${OpenCV_LIBS})
Binary file added sessie_1/opdracht_2/imageColorAdapted.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
178 changes: 178 additions & 0 deletions sessie_1/opdracht_2/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;
using namespace cv;

int main(int argc, const char** argv) {
CommandLineParser parser(argc, argv,
"{ help h usage ? | | Shows this message.}"
"{ color c | | Loads a color image <REQUIRED> }"
);

// Help printing
if(parser.has("help") || argc <= 1) {
cerr << "Please use absolute paths when supplying your images." << endl;
parser.printMessage();
return 0;
}

// Parser fail
if (!parser.check())
{
parser.printErrors();
return -1;
}

// Required arguments supplied?
string color(parser.get<string>("color"));
if(color.empty())
{
cerr << "Please supply your images using command line arguments: --color=imageColorAdapted.jpg" << endl;
return -1;
}

// Try to load images
Mat colorImg; // BGR
colorImg = imread(color, IMREAD_COLOR);

if(colorImg.empty()) {
cerr << "Loading images failed, please verify the paths to the images." << endl;
return -1;
}

// Thresholding skin pixels
// Approach 1: looping through all the pixels using a double for lus
// Print all pixels of our colorImg image to the command line;
Mat maskerLoop(colorImg.size(), CV_8UC1);
Mat segmentationLoop(colorImg.size(), CV_8UC3);
for(int rowIndex = 0; rowIndex < colorImg.rows; rowIndex++)
{
for(int columnIndex = 0; columnIndex < colorImg.cols; columnIndex++) {
cout << "(" << (int)colorImg.at<Vec3b>(rowIndex, columnIndex)[0] << "," << (int)colorImg.at<Vec3b>(rowIndex, columnIndex)[1] << "," << (int)colorImg.at<Vec3b>(rowIndex, columnIndex)[2] << ")"; // typecasting to show it properly in the terminal
cout << " ";
int blue = (int)colorImg.at<Vec3b>(rowIndex, columnIndex)[0];
int green = (int)colorImg.at<Vec3b>(rowIndex, columnIndex)[1];
int red = (int)colorImg.at<Vec3b>(rowIndex, columnIndex)[2];
// Filter formula from the assignment
if((red > 95) && (green > 40) && (blue > 20) && ((max(red, max(green, blue)) - min(red, min(green, blue))) > 15) && (abs(red - green) > 15) && (red > green) && (red > blue))
{
maskerLoop.at<uchar>(rowIndex, columnIndex) = 255;
}
else {
maskerLoop.at<uchar>(rowIndex, columnIndex) = 0;
}
}
cout << endl; // new line when row++
}
cout << endl;

// Combine masker and image
colorImg.copyTo(segmentationLoop, maskerLoop);

namedWindow("Skin extraction pixel loop", WINDOW_AUTOSIZE);
imshow("Skin extraction pixel loop", maskerLoop);
namedWindow("Segmentation pixel loop", WINDOW_AUTOSIZE);
imshow("Segmentation pixel loop", segmentationLoop);
waitKey(0); // Wait until the user presses a key

// Approach 2: using OpenCV matrix operations to retrieve all the pixels (binary image) that are valid for the filter
Mat splitted[3];
Mat maskerMatrixOperation(colorImg.size(), CV_8UC1);
Mat segmentationMatrixOperation(colorImg.size(), CV_8UC3);
split(colorImg, splitted);
Mat blue = splitted[0]; // merge() replaces this boiler plate code
Mat green = splitted[1];
Mat red = splitted[2];
maskerMatrixOperation = (red > 95) & (green > 40) & (blue > 20) & ((max(red, max(green, blue)) - min(red, min(green, blue))) > 15) & (abs(red - green) > 15) & (red > green) & (red > blue);
// Combine masker and image
colorImg.copyTo(segmentationMatrixOperation, maskerMatrixOperation);

namedWindow("Skin extraction matrix operations", WINDOW_AUTOSIZE);
imshow("Skin extraction matrix operations", maskerMatrixOperation);
namedWindow("Segmentation matrix operations", WINDOW_AUTOSIZE);
imshow("Segmentation matrix operations", segmentationMatrixOperation);
waitKey(0); // Wait until the user presses a key

// Optimize mask with opening, closing; dilation and erosion
cerr << "Optimizing mask" << endl;
erode(maskerMatrixOperation, maskerMatrixOperation, Mat(), Point(-1, -1), 2); // noise suppression x2 due huge pixels
dilate(maskerMatrixOperation, maskerMatrixOperation, Mat(), Point(-1, -1), 2); // fix erode data loss
namedWindow("Remove noise", WINDOW_AUTOSIZE);
imshow("Remove noise", maskerMatrixOperation);
waitKey(0);

// Connect blobs
dilate(maskerMatrixOperation, maskerMatrixOperation, Mat(), Point(-1, -1), 2); // connect blobs
erode(maskerMatrixOperation, maskerMatrixOperation, Mat(), Point(-1, -1), 2); // fix dilate data loss
namedWindow("Connect blobs", WINDOW_AUTOSIZE);
imshow("Connect blobs", maskerMatrixOperation);
waitKey(0);

// Convex hull approach -> contours
vector< vector<Point> > contours;
// Find contours of a binary image.
findContours(maskerMatrixOperation.clone(), contours, RETR_EXTERNAL, CHAIN_APPROX_NONE); // explain defines
vector< vector<Point> > hulls;
for(size_t i=0; i < contours.size(); i++) {
vector<Point> hull;
// contour, output hull, clockwise, returnPoints -> combines contours
convexHull(contours[i], hull);
hulls.push_back(hull);
}
// input image, contours, contourIdx (-1 = draw all contours), color, thickness (< 0, draw contour interiors), lineType, hierarchy, maxLevel, offset
drawContours(maskerMatrixOperation, hulls, -1, 255, -1); // check docs -1

Mat contouredImg(colorImg.size(), CV_8UC3);
colorImg.copyTo(contouredImg, maskerMatrixOperation);
namedWindow("Draw contours", WINDOW_AUTOSIZE);
imshow("Draw contours", contouredImg);
waitKey(0);

Mat threshedImg;
Mat colorImgGreyed;
Mat colorImgGreyedOriginal;

// threshold() expects a grey image
cvtColor(contouredImg, colorImgGreyed, COLOR_BGR2GRAY);
cvtColor(colorImg, colorImgGreyedOriginal, COLOR_BGR2GRAY);

// Original OTSU
// Fails completely due a non equal background
// By applying our previous optimisations to the mask, OTSU can do it's job much better
threshold(colorImgGreyedOriginal, threshedImg, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU);
namedWindow("OTSU original thresholding", WINDOW_AUTOSIZE);
imshow("OTSU original thresholding", threshedImg);
waitKey(0);

threshold(colorImgGreyed, threshedImg, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU);
namedWindow("OTSU thresholding", WINDOW_AUTOSIZE);
imshow("OTSU thresholding", threshedImg);
waitKey(0); // Wait until the user presses a key
// A part of the ticket is completely black (left, bottom), OTSU depends on a bimodal image -> no uniform background color
// We can use histogram equalisation and CLAHE to optimize the image before applying OTSU
// 1. histogram equalisation: isn't that great either
Mat bimodalImgEqualized;
Mat threshedImgEqualized;
equalizeHist(colorImgGreyed, bimodalImgEqualized); // previously: .clone() to avoid losing original matrix (obselete by now)
threshold(bimodalImgEqualized, threshedImgEqualized, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU);
namedWindow("Equalized OTSU", WINDOW_AUTOSIZE);
imshow("Equalized OTSU", threshedImgEqualized);
waitKey(0);

// 2. CLAHE: advanced locally equalisation using a window, a bit better than normal equalisation since the
// thresholding is applied locally, non equal backgrounds are difficult for OTSU, CLAHE makes the background more
// equally then histogram thresholding
Mat bimodalImgCLAHE;
Mat threshedImgCLAHE;
Ptr<CLAHE> clahe_p = createCLAHE();
clahe_p->setTilesGridSize(Size(15, 15)); // window size
clahe_p->setClipLimit(1); // 1 - 10 contrast
clahe_p->apply(colorImgGreyed.clone(), bimodalImgCLAHE); // still old C function, use .clone() to be sure
threshold(bimodalImgCLAHE, threshedImgCLAHE, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU);
namedWindow("OTSU CLAHE", WINDOW_AUTOSIZE);
imshow("OTSU CLAHE", threshedImgCLAHE);
waitKey(0);

return 0;
}

0 comments on commit 7a2c295

Please sign in to comment.