Initial commit.

This commit is contained in:
Faerbit 2017-01-17 22:39:27 +01:00
commit ced9e792bf
56 changed files with 4539 additions and 0 deletions

39
CMakeLists.txt Normal file
View File

@ -0,0 +1,39 @@
cmake_minimum_required(VERSION 2.8.11)
project(haselnuss)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
# set qt specific options
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
# set c++ 14
set(CMAKE_CXX_STANDARD 14)
find_package(Qt5Widgets REQUIRED)
find_package(Qt5Core REQUIRED)
find_package(OpenCV REQUIRED)
find_package(fann REQUIRED)
find_package(PkgConfig REQUIRED)
pkg_search_module(JSONCPP REQUIRED jsoncpp)
# Source Files
FILE(GLOB SOURCE_FILES "${CMAKE_SOURCE_DIR}/*.cpp")
# Header files
FILE(GLOB HEADER_FILES "${CMAKE_SOURCE_DIR}/*.h")
set(INCLUDE_DIRS ${INCLUDE_DIRS} "${CMAKE_SOURCE_DIR}")
set(INCLUDE_DIRS ${INCLUDE_DIRS} "${FANN_INCLUDE_DIR}")
INCLUDE_DIRECTORIES("${INCLUDE_DIRS}")
# Libraries
set(LIBRARIES ${LIBRARIES} ${QT_LIBRARIES})
set(LIBRARIES ${LIBRARIES} ${OpenCV_LIBS})
set(LIBRARIES ${LIBRARIES} ${FANN_LIBRARIES})
set(LIBRARIES ${LIBRARIES} ${JSONCPP_LIBRARIES})
add_executable(haselnuss ${SOURCE_FILES} ${HEADER_FILES})
qt5_use_modules(haselnuss Widgets)
target_link_libraries(haselnuss ${LIBRARIES})

12
Makefile Normal file
View File

@ -0,0 +1,12 @@
all: run
.PHONY: build
build:
mkdir -p build
cd build; cmake -G Ninja -DCMAKE_BUILD_TYPE=Debug ..; ninja
run: build
./build/haselnuss
clean:
rm -rf build

57
Readme.md Normal file
View File

@ -0,0 +1,57 @@
#Rate my Haselnuss
Visually guess the taste of a hazelnut
##Disclaimer
This program has been created as part of my image processing practical course
at the FH Aachen. Therefore some parts of this are in german. If you're
interested in this and have trouble understanding it, do not hesitate to
contact me: I'll gladly translate it for you to english.
##Idee
Dieses Programm versucht Haselnüsse visuell nach ihrem Geschmack zu
klassifizieren, insbesondere die Erkennung der schlecht schmeckenden soll
gelingen.
##Technische Vorraussetzungen
Dieses Programm benutzt CMake und kann wie jedes andere CMake-Projekt
kompiliert werden:
```
mkdir build
cd build
cmake ..
make
```
Grundsätzlich sollte dieses Programm platformunabhängig sein. Getestet ist
dieses jedoch nur unter Linux. Insbesondere die direkte Aufnahme der Fotos über
die Android Debug Bridge (ADB) funktioniert nur unter Linux.
Folgende Libraries werden benötigt:
* Qt5
* OpenCV 3.1
* fann 2.2 (Fast Artificial Neural Network Library,
[http://leenissen.dk/fann/wp/]())
* JsonCPP [https://github.com/open-source-parsers/jsoncpp]()
##Aufnahmebedingungen
Aufnahmebedingungen für Eingabedaten:
* Heller, einfarber Hintergrund mit einem H-Wert (im HSV-Farbraum) von über 70
* Lockere Anordnung
* Senkrechte Aufnahme aus ca. 18 cm Entfernung.
* Senkrechte Beleuchtung (z.B. Blitz)
* Auflösung des Bildsensors: 4160x3120
Alle Zahlenwerte können geändert werden, wenn die Konfiguration entsprechend
angepasst wird. Die aktuelle Konfiguration ist allerdings nur unter diesen
Bedingungen getestet. Abweichende Bedingungen können schlechtere Ergebnisse
liefern.
##Bedienung
Die Bedienung des Programmes sollte relativ selbsterklärend sein. Man läd ein
Bild in die Applikation und erhält eine Klassifikation der Nüsse visuell
angezeigt. Unter dem Konfigurationstab können die Erkennungs- und
Bewertungsschritte nachvollzogen und angepasst werden.
Einige Zahlenwerte können hier ebenfalls konfiguriert werden.

1032
colors.h Normal file

File diff suppressed because it is too large Load Diff

375
config.json Normal file
View File

@ -0,0 +1,375 @@
{
"detection" :
[
{
"input" : "color",
"name" : "Verkleinern",
"operation" : "_resize",
"param1" :
{
"default" : 512,
"lowerBound" : 1,
"name" : "Neue Seitenlänge",
"odd" : false,
"upperBound" : 10000
},
"param2" :
{
"default" : 0,
"lowerBound" : 0,
"name" : "Ignoriert",
"odd" : false,
"upperBound" : 0
}
},
{
"input" : "previous",
"name" : "HSV Select",
"operation" : "_thresh_color",
"param1" :
{
"default" : 0,
"lowerBound" : 0,
"name" : "untere Grenze",
"odd" : false,
"upperBound" : 360
},
"param2" :
{
"default" : 70,
"lowerBound" : 0,
"name" : "obere Grenze",
"odd" : false,
"upperBound" : 360
}
},
{
"input" : "previous",
"name" : "Dilatiere",
"operation" : "_dilate",
"param1" :
{
"default" : 11,
"lowerBound" : 1,
"name" : "Kernel Größe",
"odd" : true,
"upperBound" : 100
},
"param2" :
{
"default" : 1,
"lowerBound" : 1,
"name" : "Iterationen",
"odd" : false,
"upperBound" : 50
}
},
{
"input" : "previous",
"name" : "Zurück zur Originalgröße",
"operation" : "_resizeToOriginal",
"param1" :
{
"default" : 0,
"lowerBound" : 0,
"name" : "Ignoriert",
"odd" : false,
"upperBound" : 0
},
"param2" :
{
"default" : 0,
"lowerBound" : 0,
"name" : "Ignoriert",
"odd" : false,
"upperBound" : 0
}
},
{
"input" : "previous",
"name" : "Grenzwert",
"operation" : "_threshold",
"param1" :
{
"default" : 0,
"lowerBound" : 0,
"name" : "Ignoriert",
"odd" : false,
"upperBound" : 0
},
"param2" :
{
"default" : 0,
"lowerBound" : 0,
"name" : "Ignoriert",
"odd" : false,
"upperBound" : 0
}
},
{
"input" : "previous",
"name" : "Markiere Hintergrund",
"operation" : "_markBackground",
"param1" :
{
"default" : 0,
"lowerBound" : 0,
"name" : "Ignoriert",
"odd" : false,
"upperBound" : 0
},
"param2" :
{
"default" : 0,
"lowerBound" : 0,
"name" : "Ignoriert",
"odd" : false,
"upperBound" : 0
}
},
{
"input" : "previous",
"name" : "Invertieren",
"operation" : "_invert_markers",
"param1" :
{
"default" : 0,
"lowerBound" : 0,
"name" : "Ignoriert",
"odd" : false,
"upperBound" : 0
},
"param2" :
{
"default" : 0,
"lowerBound" : 0,
"name" : "Ignoriert",
"odd" : false,
"upperBound" : 0
}
},
{
"input" : "previous",
"name" : "Erodieren",
"operation" : "_erode_intermediate",
"param1" :
{
"default" : 11,
"lowerBound" : 1,
"name" : "Kernel Größe",
"odd" : true,
"upperBound" : 100
},
"param2" :
{
"default" : 2,
"lowerBound" : 1,
"name" : "Iterationen",
"odd" : false,
"upperBound" : 50
}
},
{
"input" : "previous",
"name" : "Zurück zur Originalgröße",
"operation" : "_resizeToOriginal",
"param1" :
{
"default" : 0,
"lowerBound" : 0,
"name" : "Ignoriert",
"odd" : false,
"upperBound" : 0
},
"param2" :
{
"default" : 0,
"lowerBound" : 0,
"name" : "Ignoriert",
"odd" : false,
"upperBound" : 0
}
},
{
"input" : "previous",
"name" : "Grenzwert",
"operation" : "_threshold",
"param1" :
{
"default" : 0,
"lowerBound" : 0,
"name" : "Ignoriert",
"odd" : false,
"upperBound" : 0
},
"param2" :
{
"default" : 0,
"lowerBound" : 0,
"name" : "Ignoriert",
"odd" : false,
"upperBound" : 0
}
},
{
"input" : "previous",
"name" : "Wasserscheide",
"operation" : "_watershed",
"param1" :
{
"default" : 0,
"lowerBound" : 0,
"name" : "Ignoriert",
"odd" : false,
"upperBound" : 0
},
"param2" :
{
"default" : 0,
"lowerBound" : 0,
"name" : "Ignoriert",
"odd" : false,
"upperBound" : 0
}
}
],
"rating" : {
"steps" :
[
{
"input" : "color",
"name" : "Grenzwert",
"operation" : "_threshold",
"param1" :
{
"default" : 0,
"lowerBound" : 0,
"name" : "Ignoriert",
"odd" : false,
"upperBound" : 0
},
"param2" :
{
"default" : 0,
"lowerBound" : 0,
"name" : "Ignoriert",
"odd" : false,
"upperBound" : 0
}
},
{
"input" : "previous",
"name" : "Durschnittlicher H-Wert",
"operation" : "_mean_color",
"param1" :
{
"default" : 0,
"lowerBound" : 0,
"name" : "Ignoriert",
"odd" : false,
"upperBound" : 0
},
"param2" :
{
"default" : 0,
"lowerBound" : 0,
"name" : "Ignoriert",
"odd" : false,
"upperBound" : 0
}
},
{
"input" : "color",
"name" : "Grenzwert",
"operation" : "_threshold",
"param1" :
{
"default" : 0,
"lowerBound" : 0,
"name" : "Ignoriert",
"odd" : false,
"upperBound" : 0
},
"param2" :
{
"default" : 0,
"lowerBound" : 0,
"name" : "Ignoriert",
"odd" : false,
"upperBound" : 0
}
},
{
"input" : "previous",
"name" : "Fit Ellipse",
"operation" : "_fit_ellipse",
"param1" :
{
"default" : 220,
"lowerBound" : 1,
"name" : "Pixel pro Zentimeter",
"odd" : false,
"upperBound" : 10000
},
"param2" :
{
"default" : 0,
"lowerBound" : 0,
"name" : "Ignoriert",
"odd" : false,
"upperBound" : 0
}
},
{
"input" : "color",
"name" : "Grenzwert nach Otsu",
"operation" : "_thresh_otsu",
"param1" :
{
"default" : 0,
"lowerBound" : 0,
"name" : "Ignoriert",
"odd" : false,
"upperBound" : 0
},
"param2" :
{
"default" : 0,
"lowerBound" : 0,
"name" : "Ignoriert",
"odd" : false,
"upperBound" : 0
}
},
{
"input" : "previous",
"name" : "Bewerte kleine Flecken",
"operation" : "_rate_spots",
"param1" :
{
"default" : 220,
"lowerBound" : 1,
"name" : "Pixel pro Zentimeter",
"odd" : false,
"upperBound" : 10000
},
"param2" :
{
"default" : 0,
"lowerBound" : 0,
"name" : "Ignoriert",
"odd" : false,
"upperBound" : 0
}
}
],
"results" :
[
"avg_h_val",
"size_diagonal",
"size_ratio",
"spot_avg_h_val",
"spot_avg_size"
]
}
}

452
controller.cpp Normal file
View File

@ -0,0 +1,452 @@
#include "controller.h"
#include <fstream>
#include <QDebug>
#include <QString>
#include <set>
#include <QFileDialog>
#include <stdio.h>
#include <chrono>
#include "filenames.h"
using namespace std;
using namespace cv;
Controller::Controller(QObject* parent) : QObject(parent),
detProc(), markers(detProc.getMarkers()) {
string errors;
ifstream file;
file.open(FileNames::configName);
bool ok = Json::parseFromStream(Json::CharReaderBuilder(),
file, &config, &errors);
file.close();
if (!ok) {
cerr << "Error while reading " << FileNames::configName
<< ":\n" << errors << "\n";
exit(1);
}
detProc.init(config["detection"]);
rateProc.init(config["rating"]["steps"]);
net.init(config["rating"]["results"]);
}
Controller& Controller::inst() {
static Controller ctrlr;
return ctrlr;
}
const Mat* Controller::getCurImg() {
return &cur_img;
}
const Mat* Controller::getCurNut() {
return &nuts[current_nut].img;
}
void Controller::openImage() {
filePath = QFileDialog::getOpenFileName(nullptr,
tr("Öffnen"), "",
tr("Bilder (*.png *.jpg)"));
if (filePath.toStdString() == "") {
return;
}
loadImage();
}
void Controller::loadImage() {
if ( !cur_img.empty() ){
cur_img.copyTo(prev_img);
}
cur_img = imread(filePath.toStdString(), IMREAD_COLOR);
emit statusMessage("Bild geladen ...");
process();
}
void Controller::process() {
if (valid_nut) {
if (nut_rating > 0) {
emit statusMessage("Bewerte gegessene Nuss...");
rateProc.reset();
rateProc.setRateCurNut(false);
rateProc.process();
rateProc.setRateCurNut(true);
debugResultPrint();
net.saveNut();
}
else {
cout << "No score selected. Not saving Nut." << endl;
}
}
detProc.reset();
emit newInputImage(cur_img);
emit statusMessage("Erkenne Nüsse ...");
if (!detect()) {
return;
}
emit newInputImage(detProc.getResult());
if (!prev_img.empty()) {
vector<Nut> results = {};
emit statusMessage("Extrahiere Nüsse ...");
extractNuts(results);
if (matchNuts(results)) {
nuts = results;
valid_nut = true;
}
else {
valid_nut = false;
nuts = results;
}
}
else {
emit statusMessage("Extrahiere Nüsse ...");
extractNuts();
}
nut_rating = -1;
//debugPaint();
emit statusMessage("Bewerte Nüsse ...");
Mat withScores = Mat::zeros(cur_img.size(), CV_8UC3);
for (int i = 0; i<nuts.size(); i++) {
current_nut = i;
rateProc.reset();
rateProc.process();
float score = net.run(rateProc.getResults());
nuts[current_nut].score = score;
const vector<vector<Point>>& contours = rateProc.getContours();
vector<Rect> rects(contours.size());
for (unsigned int i = 0; i<contours.size(); i++) {
Rect size = boundingRect(contours[i]);
rects[i] = size;
}
int max_rect_index = -1;
int max_rect_size = 0;
for (unsigned int i = 0; i<rects.size(); i++) {
if (rects[i].height * rects[i].width > max_rect_size) {
max_rect_size = rects[i].height * rects[i].width;
max_rect_index = i;
}
}
drawContours(withScores, contours, max_rect_index,
Scalar(static_cast<uchar>(score * 100.0f *0.708333f), 255, 255),
CV_FILLED, LINE_8, noArray(), INT_MAX,
Point(nuts[current_nut].min_y, nuts[current_nut].min_x));
}
emit statusMessage("Zeige Ergebnisse an ...");
cvtColor(withScores, withScores, CV_HSV2BGR);
withScores = 0.5 *withScores + 0.5 * cur_img;
paintScores(withScores);
emit statusMessage("Fertig.");
}
void Controller::debugResultPrint() {
ostringstream sstr;
sstr << "Results: ";
for (const auto& result : rateProc.getResults()) {
sstr << result.first << ": " << result.second << ", ";
}
cout << sstr.str() << endl;
}
void Controller::paintScores(Mat& img) {
for (unsigned int i = 0; i<nuts.size(); i++){
ostringstream sstr;
sstr.precision(1);
sstr << nuts[i].score;
string text = sstr.str();
int font = FONT_HERSHEY_SIMPLEX;
double fontScale = 3.0;
int thickness = 5;
int baseline = 0;
Size textSize = getTextSize(text, font, fontScale, thickness,
&baseline);
baseline += thickness;
Point textOrg(nuts[i].center_y() - 0.5 * textSize.width,
nuts[i].center_x() - 0.5 * textSize.height);
putText(img, text, textOrg,
font, fontScale, Scalar::all(255), thickness);
circle(img, Point(nuts[i].center_y(), nuts[i].center_x()), 5,
Scalar::all(255), 5);
}
emit newInputImage(img);
}
void Controller::debugPaint() {
Mat withText = detProc.getResult().clone();
for (unsigned int i = 0; i<nuts.size(); i++){
string text = to_string(i);
int font = FONT_HERSHEY_SIMPLEX;
double fontScale = 3.0;
int thickness = 5;
int baseline = 0;
Size textSize = getTextSize(text, font, fontScale, thickness,
&baseline);
baseline += thickness;
Point textOrg(nuts[i].center_y() - 0.5 * textSize.width,
nuts[i].center_x() - 0.5 * textSize.height);
putText(withText, text, textOrg,
font, fontScale, Scalar::all(255), thickness);
circle(withText, Point(nuts[i].center_y(), nuts[i].center_x()), 5,
Scalar::all(255), 5);
}
emit newInputImage(withText);
}
void Controller::captureImage() {
system(
"adb shell \"count=\\$(ls /mnt/sdcard/DCIM/CAMERA | wc -l) && "
"am start -a android.media.action.STILL_IMAGE_CAMERA && "
"sleep 1 && "
"input keyevent 27 && "
"while [ \\$count = \\$(ls /mnt/sdcard/DCIM/CAMERA | wc -l) ]; do "
"sleep 0.5; "
"echo 'not done'; done; "
"echo done\"");
string img = runCmd("adb shell \"ls /mnt/sdcard/DCIM/CAMERA | "
"grep -v VID | tail -n 1\"");
// remove white space
img.erase(std::remove_if(img.begin(), img.end(), ::isspace), img.end());
if (!transferFolder.isValid()) {
cerr << "Failed to create temporary folder. Exiting.";
exit(1);
}
string cmd = "adb pull \"/mnt/sdcard/DCIM/CAMERA/" + img + "\" \""
+ transferFolder.path().toStdString() + "/\"";
system(cmd.c_str());
system(("adb shell \"rm /mnt/sdcard/DCIM/CAMERA/" + img + "\"").c_str());
filePath = transferFolder.path() + "/" + QString::fromStdString(img);
loadImage();
remove(filePath.toStdString().c_str());
}
bool Controller::detect() {
auto start = chrono::steady_clock::now();
bool success = detProc.process();
if (!success) {
cerr << "Error during detection." << endl;
return success;
}
auto duration = chrono::duration_cast<chrono::milliseconds>(
chrono::steady_clock::now() - start);
cout << "Detection took " << (float)duration.count()/1000.0
<< " seconds." << endl;
return success;
}
bool Controller::matchNuts(const vector<Nut>& cur_results) {
auto start = chrono::steady_clock::now();
vector<float> min_dists = vector<float>(nuts.size());
int count = 0;
for(const auto& nut : nuts) {
vector<float> x_dist = vector<float>(cur_results.size());
for (unsigned int i = 0; i<cur_results.size(); i++) {
x_dist[i] = abs(cur_results[i].center_x() - nut.center_x());
}
vector<float> y_dist = vector<float>(cur_results.size());
for (unsigned int i = 0; i<cur_results.size(); i++) {
y_dist[i] = abs(cur_results[i].center_y() - nut.center_y());
}
vector<float> dist = vector<float>(cur_results.size());
magnitude(x_dist, y_dist, dist);
auto min = min_element(dist.begin(), dist.end());
min_dists[count] = *min;
cout << "Minimum distance for nut " << count <<
": " << *min
<< " ("
<< (*min/
(sqrt(pow(static_cast<float>(cur_img.cols), 2) +
pow(static_cast<float>(cur_img.rows), 2))))*100.0
<< "%)\n";
count++;
}
bool success = false;
if (cur_results.size() == nuts.size() - 1) {
success = true;
int nut_index;
auto max = max_element(min_dists.begin(), min_dists.end());
nut_index = distance(min_dists.begin(), max);
cout << "Nut " << nut_index << " has been taken away.\n";
nuts[nut_index].img.copyTo(nut_to_rate);
emit newNutToRate(nut_to_rate);
}
else {
emit message("Keine Nuss erkannt");
}
auto duration = chrono::duration_cast<chrono::milliseconds>(
chrono::steady_clock::now() - start);
cout << "Matching took " << (float)duration.count()/1000.0
<< " seconds." << endl;
return success;
}
void Controller::extractNuts(vector<Nut>& result_nuts) {
auto start = chrono::steady_clock::now();
if (result_nuts.size() > 0) {
return;
}
if (markers.empty()) {
if (!detProc.process()) {
return;
}
}
int nut_count = detProc.getNutCount();
result_nuts = vector<Nut>(nut_count);
for (unsigned int i = 0; i<result_nuts.size(); i++) {
result_nuts[i].min_x = markers.rows;
result_nuts[i].min_y = markers.cols;
result_nuts[i].max_x = 0;
result_nuts[i].max_y = 0;
}
for(int i = 0; i<markers.rows; i++) {
for(int j = 0; j<markers.cols; j++) {
int index = markers.at<int>(i, j) -2;
if (index >= 0 && index < nut_count) {
result_nuts[index].min_x = min(result_nuts[index].min_x, i);
result_nuts[index].min_y = min(result_nuts[index].min_y, j);
result_nuts[index].max_x = max(result_nuts[index].max_x, i);
result_nuts[index].max_y = max(result_nuts[index].max_y, j);
}
}
}
for (unsigned int i = 0; i<result_nuts.size(); i++) {
result_nuts[i].img = Mat::zeros(result_nuts[i].max_x - result_nuts[i].min_x,
result_nuts[i].max_y - result_nuts[i].min_y, CV_8UC3);
}
for(int i = 0; i<markers.rows; i++) {
for(int j = 0; j<markers.cols; j++) {
int index = markers.at<int>(i, j)-2;
if (index >= 0 && index < nut_count) {
result_nuts[index].img.at<Vec3b>(i - result_nuts[index].min_x,
j - result_nuts[index].min_y) = cur_img.at<Vec3b>(i, j);
}
}
}
auto duration = chrono::duration_cast<chrono::milliseconds>(
chrono::steady_clock::now() - start);
cout << "Nut extraction took " << (float)duration.count()/1000.0
<< " seconds.\n";
cout << "Detected " << result_nuts.size() << " nuts." << endl;
rateProc.reset();
if (rateProc.isInteractive()) {
emit newNut(result_nuts[current_nut].img);
}
}
void Controller::nextNut() {
if (nuts.size() == 0 || nuts[current_nut].img.empty()) {
return;
}
int size = nuts.size();
current_nut = min(current_nut + 1, size-1);
newNut();
if (rateProc.isInteractive()) {
emit newNut(nuts[current_nut].img);
emit rateProc.requestUpdate();
}
}
void Controller::previousNut() {
if (nuts.size() == 0 || nuts[current_nut].img.empty()) {
return;
}
current_nut = max(current_nut - 1, 0);
newNut();
if (rateProc.isInteractive()) {
emit newNut(nuts[current_nut].img);
}
}
void Controller::newNut() {
rateProc.reset();
if (rateProc.isInteractive()) {
rateProc.start(true);
}
}
string Controller::runCmd(string cmd) {
char buffer[128];
string result = "";
shared_ptr<FILE> pipe(popen(cmd.c_str(), "r"), pclose);
if (!pipe) throw runtime_error("popen() failed!");
while (!feof(pipe.get())) {
if (fgets(buffer, 128, pipe.get()) != NULL)
result += buffer;
}
return result;
}
void Controller::saveDetectionConfig() {
int current_step = detProc.getCurrentStep();
config["detection"][current_step]["param1"]["default"] =
detProc.getCurParams()[0].val;
config["detection"][current_step]["param2"]["default"] =
detProc.getCurParams()[1].val;
ofstream file ("config.json");
file << Json::writeString(Json::StreamWriterBuilder(), config);
file.close();
}
void Controller::saveRatingConfig() {
int current_step = rateProc.getCurrentStep();
config["rating"]["steps"][current_step]["param1"]["default"] =
rateProc.getCurParams()[0].val;
config["rating"]["steps"][current_step]["param2"]["default"] =
rateProc.getCurParams()[1].val;
ofstream file ("config.json");
file << Json::writeString(Json::StreamWriterBuilder(), config);
file.close();
}
void Controller::reset() {
resetNuts();
detProc.reset();
rateProc.reset();
cur_img.release();
prev_img.release();
Mat empty;
emit newInputImage(empty);
emit newNutToRate(empty);
}
void Controller::resetNuts() {
current_nut = 0;
nut_rating = -1;
nuts = {};
}
void Controller::saveData() {
cout << "... saving data ... " << endl;
if (valid_nut) {
if (nut_rating > 0) {
net.saveNut();
}
else {
cout << "No score selected. Not saving Nut." << endl;
}
}
}
void Controller::currentRating(int score) {
nut_rating = score;
}
int Controller::getCurRating() {
return nut_rating;
}
int Controller::getN() {
return net.getN();
}
void Controller::neuralNetChange(int val) {
if (val == Qt::Unchecked) {
net.neuralNetEnabled(false);
process();
}
else if (val == Qt::Checked) {
net.neuralNetEnabled(true);
process();
}
}
const cv::Mat* Controller::getNutToRate() {
return &nut_to_rate;
}

103
controller.h Normal file
View File

@ -0,0 +1,103 @@
#ifndef CONTROLLER_H
#define CONTROLLER_H
#include "detectionprocessing.h"
#include "rateprocessing.h"
#include "neuralnet.h"
#include <json/json.h>
#include <opencv2/opencv.hpp>
#include <QObject>
#include <memory>
#include <QTemporaryDir>
#include <QCloseEvent>
struct Nut {
int min_x;
int min_y;
int max_x;
int max_y;
cv::Mat img;
float score;
int center_x() const {
return min_x + ((max_x - min_x)/2);
}
int center_y() const {
return min_y + ((max_y - min_y)/2);
}
};
class Controller : public QObject {
Q_OBJECT
public:
DetectionProcessing& getDetectionProcessing() { return detProc; }
RateProcessing& getRateProcessing() { return rateProc; }
void extractNuts(std::vector<Nut>& result_nuts = inst().nuts);
const cv::Mat* getCurImg();
const cv::Mat* getCurNut();
const cv::Mat* getNutToRate();
void process();
void resetNuts();
int getCurRating();
int getN();
//Singleton
static Controller& inst();
Controller(Controller const&) = delete;
void operator=(Controller const&) = delete;
signals:
void newNut(const cv::Mat& nut);
void newInputImage(const cv::Mat& img);
void newNutToRate(const cv::Mat& nut);
void message(std::string text);
void statusMessage(QString);
public slots:
void nextNut();
void previousNut();
void openImage();
void captureImage();
void saveDetectionConfig();
void saveRatingConfig();
void reset();
void saveData();
void currentRating(int score);
void neuralNetChange(int val);
// private methods
private:
std::string runCmd(std::string cmd);
void loadImage();
bool detect();
void newNut();
bool matchNuts(const std::vector<Nut>& cur_results);
void debugPaint();
void debugResultPrint();
void paintScores(cv::Mat& img);
// members
private:
// Singleton
Controller(QObject* parent = nullptr);
DetectionProcessing detProc;
RateProcessing rateProc;
NeuralNet net;
Json::Value config;
std::vector<Nut> nuts{};
int current_nut = 0;
const cv::Mat& markers;
cv::Mat prev_img;
cv::Mat cur_img;
static Controller *singleton;
QTemporaryDir transferFolder;
QString filePath;
cv::Mat nut_to_rate;
int nut_rating = -1;
bool valid_nut = false;
};
#endif // CONTROLLER_H

123
cvimagewidget.h Normal file
View File

@ -0,0 +1,123 @@
#ifndef CVIMAGEWIDGET_H
#define CVIMAGEWIDGET_H
#include <QWidget>
#include <QImage>
#include <QPainter>
#include <QResizeEvent>
#include <QDebug>
#include <opencv2/opencv.hpp>
class CVImageWidget : public QWidget
{
Q_OBJECT
public:
explicit CVImageWidget(QWidget *parent = 0) : QWidget(parent) {}
QSize sizeHint() const { return _qimage.size(); }
QSize minimumSizeHint() const { return QSize(400, 400); }
public slots:
void showImage(const cv::Mat& image) {
if (image.empty()) {
_qimage = QImage();
repaint();
return;
}
cv::Mat tmp;
if (image.type() == CV_32S) {
image.convertTo(tmp, CV_8U);
}
else {
tmp = image.clone();
}
// Scale image to 512 pixels
float fac = 1.0;
if (size().width() > size().height()) {
fac = (float) size().height()/image.rows;
}
else {
fac = (float) size().width()/image.cols;
}
cv::Size new_size((int)image.cols*fac, (int) image.rows*fac) ;
cv::resize(tmp, _tmp, new_size);
// Convert the image to the RGB888 format
switch (_tmp.type()) {
case CV_8UC1:
cvtColor(_tmp, _tmp, CV_GRAY2RGB);
break;
case CV_8UC3:
cvtColor(_tmp, _tmp, CV_BGR2RGB);
break;
}
// QImage needs the data to be stored continuously in memory
assert(_tmp.isContinuous());
// Assign OpenCV's image buffer to the QImage. Note that the
// bytesPerLine parameter
// (http://qt-project.org/doc/qt-4.8/qimage.html#QImage-6) is 3*width
// because each pixel has three bytes.
_qimage = QImage(_tmp.data, _tmp.cols, _tmp.rows, _tmp.cols*3,
QImage::Format_RGB888);
repaint();
}
void resizeEvent(QResizeEvent* event) {
if (_tmp.empty()) {
return;
}
float fac = 1.0;
if (event->size().width() > event->size().height()) {
fac = static_cast<float>(event->size().height())/
static_cast<float>(_tmp.rows);
}
else {
fac = static_cast<float>(event->size().width())/
static_cast<float>(_tmp.cols);
}
cv::Size new_size((int)_tmp.cols*fac, (int) _tmp.rows*fac) ;
cv::resize(_tmp, _tmp, new_size);
// Assign OpenCV's image buffer to the QImage. Note that the
// bytesPerLine parameter
// (http://qt-project.org/doc/qt-4.8/qimage.html#QImage-6) is 3*width
// because each pixel has three bytes.
_qimage = QImage(_tmp.data, _tmp.cols, _tmp.rows, _tmp.cols*3,
QImage::Format_RGB888);
repaint();
}
void showTextImage(std::string text) {
int fontFace = cv::FONT_HERSHEY_SIMPLEX;
double fontScale = 2;
int thickness = 3;
cv::Mat img(600, 800, CV_8UC3, cv::Scalar::all(0));
int baseline=0;
cv::Size textSize = cv::getTextSize(text, fontFace,
fontScale, thickness, &baseline);
baseline += thickness;
// center the text
cv::Point textOrg((img.cols - textSize.width)/2,
(img.rows + textSize.height)/2);
// then put the text itself
cv::putText(img, text, textOrg, fontFace, fontScale,
cv::Scalar::all(255), thickness, 8);
showImage(img);
}
protected:
void paintEvent(QPaintEvent* /*event*/) {
// Display the image
QPainter painter(this);
painter.drawImage(QPoint(0,0), _qimage);
painter.end();
}
QImage _qimage;
cv::Mat _tmp;
};
#endif //CVIMAGEWIDGET_H

1
data/imgs/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
!*.jpg

BIN
data/imgs/nut_0.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

BIN
data/imgs/nut_1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
data/imgs/nut_10.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
data/imgs/nut_11.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
data/imgs/nut_12.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
data/imgs/nut_13.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
data/imgs/nut_14.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
data/imgs/nut_15.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
data/imgs/nut_16.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
data/imgs/nut_17.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
data/imgs/nut_18.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
data/imgs/nut_19.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
data/imgs/nut_2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
data/imgs/nut_20.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
data/imgs/nut_21.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
data/imgs/nut_22.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
data/imgs/nut_23.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
data/imgs/nut_24.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
data/imgs/nut_25.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
data/imgs/nut_26.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
data/imgs/nut_3.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
data/imgs/nut_4.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
data/imgs/nut_5.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
data/imgs/nut_6.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
data/imgs/nut_7.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
data/imgs/nut_8.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
data/imgs/nut_9.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

28
data/input.csv Normal file
View File

@ -0,0 +1,28 @@
nut_id;avg_h_val;size_diagonal;size_ratio;spot_avg_h_val;spot_avg_size;
0;2.15543448442352208e+01;2.40840983390808105e+00;8.30784440040588379e-01;4.00582693340368650e+01;2.89089112145802969e-02;
1;2.85995685587164630e+01;2.12009000778198242e+00;8.76522243022918701e-01;7.05022463391572387e+01;3.81656800707181332e-02;
2;2.08126407875761288e+01;2.22104597091674805e+00;9.02630627155303955e-01;3.27760247141309264e+01;4.23160610679326546e-02;
3;2.27827500793902828e+01;2.21160554885864258e+00;8.62763643264770508e-01;6.35754374050604554e+01;4.00986809268770519e-02;
4;2.20254226337891694e+01;2.41725397109985352e+00;8.32525730133056641e-01;8.86535834821747954e+01;3.96018549923069160e-02;
5;2.07578218819002380e+01;2.16546726226806641e+00;8.67750287055969238e-01;7.40295570873757924e+01;3.84405589464939038e-02;
6;2.25812620460063584e+01;2.08592319488525391e+00;9.63612735271453857e-01;5.31947450825757286e+01;4.16234488977769920e-02;
7;2.86359098228663456e+01;2.11695909500122070e+00;9.49448287487030029e-01;5.78029675840922295e+01;4.10201069878011818e-02;
8;2.23608877125114525e+01;2.49507260322570801e+00;8.32136988639831543e-01;3.31328554199994656e+01;5.39600710316402166e-02;
9;2.53518362580978369e+01;2.31170797348022461e+00;8.76543164253234863e-01;6.94130929198291398e+01;4.70851360889505757e-02;
10;2.38376188964527991e+01;2.22326540946960449e+00;9.20266449451446533e-01;6.81529845130824015e+01;4.10076653183280640e-02;
11;2.67171104065323135e+01;2.49273753166198730e+00;7.78337955474853516e-01;8.10785295169221598e+01;9.83759143393793112e-02;
12;2.60709829962772908e+01;2.07745194435119629e+00;9.66803669929504395e-01;6.17339857042509621e+01;4.32154684796301619e-02;
13;1.98233052030320600e+01;2.34185457229614258e+00;8.92356693744659424e-01;4.56226145591685039e+01;6.26236441603117561e-02;
14;2.38743842936802970e+01;2.22323918342590332e+00;9.26075160503387451e-01;4.12464626640129737e+01;3.89743338028589875e-02;
15;2.16857333502065046e+01;2.25483775138854980e+00;9.00114536285400391e-01;1.01177834562342255e+02;3.99716212108672322e-02;
16;2.74209935773427240e+01;2.14553141593933105e+00;9.03680801391601562e-01;5.13987904907524182e+01;6.88065268154826465e-02;
17;2.73438467310758853e+01;2.40109395980834961e+00;7.81638681888580322e-01;4.54371387821523456e+01;4.56836996656475661e-02;
18;2.65054871709301878e+01;2.27910423278808594e+00;7.89494752883911133e-01;7.47637672686023649e+01;6.18897987609845074e-02;
19;2.03020797482668769e+01;2.33886933326721191e+00;7.77873873710632324e-01;5.60728065154369801e+01;4.25227099943926018e-02;
20;2.23449875088078933e+01;2.20789957046508789e+00;8.45886528491973877e-01;7.81261270189904735e+01;5.70639237309947187e-02;
21;2.24425214007782117e+01;2.36169195175170898e+00;9.09197628498077393e-01;3.87564170377607482e+01;6.99308254502036403e-02;
22;2.55443989631836352e+01;2.14459204673767090e+00;9.73132252693176270e-01;5.02639359259216931e+01;5.56839998271064091e-02;
23;2.14019211549139357e+01;2.33309888839721680e+00;8.76630365848541260e-01;3.50631278883680224e+01;5.73160344904119351e-02;
24;2.55478804441722041e+01;2.03121805191040039e+00;8.49647045135498047e-01;3.12149577973704666e+01;3.17756131700512243e-02;
25;2.58053171628290663e+01;1.97867727279663086e+00;9.44334387779235840e-01;5.90922410389039996e+01;3.49965437046893188e-02;
26;1.79281499888120770e+01;2.26088070869445801e+00;8.75540912151336670e-01;3.12600847616190833e+01;3.33142616346284934e-02;
1 nut_id avg_h_val size_diagonal size_ratio spot_avg_h_val spot_avg_size
2 0 2.15543448442352208e+01 2.40840983390808105e+00 8.30784440040588379e-01 4.00582693340368650e+01 2.89089112145802969e-02
3 1 2.85995685587164630e+01 2.12009000778198242e+00 8.76522243022918701e-01 7.05022463391572387e+01 3.81656800707181332e-02
4 2 2.08126407875761288e+01 2.22104597091674805e+00 9.02630627155303955e-01 3.27760247141309264e+01 4.23160610679326546e-02
5 3 2.27827500793902828e+01 2.21160554885864258e+00 8.62763643264770508e-01 6.35754374050604554e+01 4.00986809268770519e-02
6 4 2.20254226337891694e+01 2.41725397109985352e+00 8.32525730133056641e-01 8.86535834821747954e+01 3.96018549923069160e-02
7 5 2.07578218819002380e+01 2.16546726226806641e+00 8.67750287055969238e-01 7.40295570873757924e+01 3.84405589464939038e-02
8 6 2.25812620460063584e+01 2.08592319488525391e+00 9.63612735271453857e-01 5.31947450825757286e+01 4.16234488977769920e-02
9 7 2.86359098228663456e+01 2.11695909500122070e+00 9.49448287487030029e-01 5.78029675840922295e+01 4.10201069878011818e-02
10 8 2.23608877125114525e+01 2.49507260322570801e+00 8.32136988639831543e-01 3.31328554199994656e+01 5.39600710316402166e-02
11 9 2.53518362580978369e+01 2.31170797348022461e+00 8.76543164253234863e-01 6.94130929198291398e+01 4.70851360889505757e-02
12 10 2.38376188964527991e+01 2.22326540946960449e+00 9.20266449451446533e-01 6.81529845130824015e+01 4.10076653183280640e-02
13 11 2.67171104065323135e+01 2.49273753166198730e+00 7.78337955474853516e-01 8.10785295169221598e+01 9.83759143393793112e-02
14 12 2.60709829962772908e+01 2.07745194435119629e+00 9.66803669929504395e-01 6.17339857042509621e+01 4.32154684796301619e-02
15 13 1.98233052030320600e+01 2.34185457229614258e+00 8.92356693744659424e-01 4.56226145591685039e+01 6.26236441603117561e-02
16 14 2.38743842936802970e+01 2.22323918342590332e+00 9.26075160503387451e-01 4.12464626640129737e+01 3.89743338028589875e-02
17 15 2.16857333502065046e+01 2.25483775138854980e+00 9.00114536285400391e-01 1.01177834562342255e+02 3.99716212108672322e-02
18 16 2.74209935773427240e+01 2.14553141593933105e+00 9.03680801391601562e-01 5.13987904907524182e+01 6.88065268154826465e-02
19 17 2.73438467310758853e+01 2.40109395980834961e+00 7.81638681888580322e-01 4.54371387821523456e+01 4.56836996656475661e-02
20 18 2.65054871709301878e+01 2.27910423278808594e+00 7.89494752883911133e-01 7.47637672686023649e+01 6.18897987609845074e-02
21 19 2.03020797482668769e+01 2.33886933326721191e+00 7.77873873710632324e-01 5.60728065154369801e+01 4.25227099943926018e-02
22 20 2.23449875088078933e+01 2.20789957046508789e+00 8.45886528491973877e-01 7.81261270189904735e+01 5.70639237309947187e-02
23 21 2.24425214007782117e+01 2.36169195175170898e+00 9.09197628498077393e-01 3.87564170377607482e+01 6.99308254502036403e-02
24 22 2.55443989631836352e+01 2.14459204673767090e+00 9.73132252693176270e-01 5.02639359259216931e+01 5.56839998271064091e-02
25 23 2.14019211549139357e+01 2.33309888839721680e+00 8.76630365848541260e-01 3.50631278883680224e+01 5.73160344904119351e-02
26 24 2.55478804441722041e+01 2.03121805191040039e+00 8.49647045135498047e-01 3.12149577973704666e+01 3.17756131700512243e-02
27 25 2.58053171628290663e+01 1.97867727279663086e+00 9.44334387779235840e-01 5.90922410389039996e+01 3.49965437046893188e-02
28 26 1.79281499888120770e+01 2.26088070869445801e+00 8.75540912151336670e-01 3.12600847616190833e+01 3.33142616346284934e-02

36
data/nuts.net Normal file
View File

@ -0,0 +1,36 @@
FANN_FLO_2.1
num_layers=3
learning_rate=0.700000
connection_rate=1.000000
network_type=0
learning_momentum=0.000000
training_algorithm=1
train_error_function=1
train_stop_function=0
cascade_output_change_fraction=0.010000
quickprop_decay=-0.000100
quickprop_mu=1.750000
rprop_increase_factor=1.200000
rprop_decrease_factor=0.500000
rprop_delta_min=0.000000
rprop_delta_max=50.000000
rprop_delta_zero=0.100000
cascade_output_stagnation_epochs=12
cascade_candidate_change_fraction=0.010000
cascade_candidate_stagnation_epochs=12
cascade_max_out_epochs=150
cascade_min_out_epochs=50
cascade_max_cand_epochs=150
cascade_min_cand_epochs=50
cascade_num_candidate_groups=2
bit_fail_limit=3.49999994039535522461e-01
cascade_candidate_limit=1.00000000000000000000e+03
cascade_weight_multiplier=4.00000005960464477539e-01
cascade_activation_functions_count=10
cascade_activation_functions=3 5 7 8 10 11 14 15 16 17
cascade_activation_steepnesses_count=4
cascade_activation_steepnesses=2.50000000000000000000e-01 5.00000000000000000000e-01 7.50000000000000000000e-01 1.00000000000000000000e+00
layer_sizes=6 7 2
scale_included=0
neurons (num_inputs, activation_function, activation_steepness)=(0, 0, 0.00000000000000000000e+00) (0, 0, 0.00000000000000000000e+00) (0, 0, 0.00000000000000000000e+00) (0, 0, 0.00000000000000000000e+00) (0, 0, 0.00000000000000000000e+00) (0, 0, 0.00000000000000000000e+00) (6, 3, 5.00000000000000000000e-01) (6, 3, 5.00000000000000000000e-01) (6, 3, 5.00000000000000000000e-01) (6, 3, 5.00000000000000000000e-01) (6, 3, 5.00000000000000000000e-01) (6, 3, 5.00000000000000000000e-01) (0, 3, 0.00000000000000000000e+00) (7, 0, 5.00000000000000000000e-01) (0, 0, 0.00000000000000000000e+00)
connections (connected_to_neuron, weight)=(0, -2.89685893058776855469e+00) (1, -2.21340131759643554688e+00) (2, -6.53899073600769042969e-01) (3, 5.05288600921630859375e-01) (4, -2.12488818168640136719e+00) (5, -3.21228432655334472656e+00) (0, -2.64115977287292480469e+00) (1, 4.38077020645141601562e+00) (2, -2.23532605171203613281e+00) (3, 2.82412099838256835938e+00) (4, -1.78801298141479492188e+00) (5, -2.15510916709899902344e+00) (0, -1.28378415107727050781e+00) (1, -4.66057968139648437500e+00) (2, -3.13344097137451171875e+00) (3, -3.34458440542221069336e-01) (4, 5.57140636444091796875e+00) (5, 4.77392911911010742188e+00) (0, -2.00398230552673339844e+00) (1, 1.77709951996803283691e-01) (2, -4.12612724304199218750e+00) (3, -4.50434893369674682617e-01) (4, 4.67733287811279296875e+00) (5, -7.28608146309852600098e-02) (0, -1.84619712829589843750e+00) (1, -1.72842276096343994141e+00) (2, -3.57149934768676757812e+00) (3, -1.81574237346649169922e+00) (4, 6.00842870771884918213e-02) (5, -2.67083120346069335938e+00) (0, -4.89023780822753906250e+00) (1, -6.48415279388427734375e+00) (2, -3.84839057922363281250e+00) (3, 3.94571590423583984375e+00) (4, -3.12683850526809692383e-01) (5, 3.10192298889160156250e+00) (6, -5.09613215923309326172e-01) (7, 3.94862794876098632812e+00) (8, 6.27435493469238281250e+00) (9, -4.65998220443725585938e+00) (10, -4.07627582550048828125e-01) (11, -2.93442416191101074219e+00) (12, -3.50238943099975585938e+00)

28
data/output.csv Normal file
View File

@ -0,0 +1,28 @@
nut_id;score;
0;2;
1;4;
2;4;
3;4;
4;5;
5;3;
6;3;
7;1;
8;2;
9;3;
10;1;
11;2;
12;3;
13;4;
14;1;
15;4;
16;5;
17;3;
18;3;
19;4;
20;3;
21;3;
22;4;
23;2;
24;4;
25;4;
26;4;
1 nut_id score
2 0 2
3 1 4
4 2 4
5 3 4
6 4 5
7 5 3
8 6 3
9 7 1
10 8 2
11 9 3
12 10 1
13 11 2
14 12 3
15 13 4
16 14 1
17 15 4
18 16 5
19 17 3
20 18 3
21 19 4
22 20 3
23 21 3
24 22 4
25 23 2
26 24 4
27 25 4
28 26 4

162
detectionprocessing.cpp Normal file
View File

@ -0,0 +1,162 @@
#include "detectionprocessing.h"
#include "controller.h"
#include "colors.h"
#include <QDebug>
#include <QFileDialog>
#include <QApplication>
#include <algorithm>
#include <cmath>
#include <fstream>
using namespace std;
using namespace cv;
DetectionProcessing::DetectionProcessing(QObject* parent)
: Processing(parent) {}
void DetectionProcessing::init(Json::Value json_steps) {
Processing::init(this->metaObject(), json_steps);
}
Mat DetectionProcessing::_dilate(const Mat& input, int kernel_size,
int iterations) {
Mat out;
dilate(input, out, getStructuringElement(MORPH_ELLIPSE,
Size(kernel_size, kernel_size)), Point(-1, -1), iterations);
return out;
}
Mat DetectionProcessing::_threshold(const Mat& input, int ignored,
int ignored2) {
Mat out;
threshold(input, out, 127, 255, THRESH_BINARY);
return out;
}
Mat DetectionProcessing::_invert(const Mat& input, int ignored, int ignored2) {
Mat out = Scalar::all(255) - input;
return out;
}
Mat DetectionProcessing::_invert_markers(const Mat& input, int ignored,
int ignored2) {
markers = Scalar::all(1) - markers;
return markers;
}
Mat DetectionProcessing::_thresh_color(const Mat& input, int lowerThreshold, int
upperThreshold) {
Mat hsv;
cvtColor(input, hsv, COLOR_BGR2HSV);
intermediate = Mat (input.size(), CV_8U);
inRange(hsv, Scalar(lowerThreshold, 0, 0), Scalar(upperThreshold,
255, 255), intermediate);
return intermediate;
}
Mat DetectionProcessing::_erode(const Mat& input, int kernel_size, int
iterations) {
Mat out;
erode(input, out, getStructuringElement(MORPH_ELLIPSE,
Size(kernel_size, kernel_size)), Point(-1, -1), iterations);
return out;
}
Mat DetectionProcessing::_erode_intermediate(const Mat& input, int kernel_size,
int iterations) {
Mat out;
erode(intermediate, out, getStructuringElement(MORPH_ELLIPSE,
Size(kernel_size, kernel_size)), Point(-1, -1), iterations);
return out;
}
Mat DetectionProcessing::_markBackground(const Mat& input, int ignored,
int ignored2) {
markers = Mat::zeros(input.size(), CV_32S);
markers = Scalar::all(0);
Mat in = input.clone();
vector<vector<Point>> contours;
findContours(in, contours, RETR_LIST, CHAIN_APPROX_NONE);
for(int i = 0; i<contours.size(); i++) {
drawContours(markers, contours, i, Scalar::all(1),
CV_FILLED, 8);
}
return markers;
}
Mat DetectionProcessing::_watershed(const Mat& input, int ignored,
int ignored2) {
Mat in = input.clone();
vector<vector<Point>> contours;
findContours(in, contours, RETR_LIST, CHAIN_APPROX_SIMPLE);
nut_count = 0;
for(int i = 0; i<contours.size(); i++) {
drawContours(markers, contours, i, Scalar::all(i+2),
CV_FILLED, 8);
nut_count++;
}
const Mat& src = *Controller::inst().getCurImg();
watershed(src, markers);
Mat wshed(markers.size(), CV_8UC3);
// paint the watershed image
for(int i = 0; i < markers.rows; i++ ) {
for(int j = 0; j < markers.cols; j++ )
{
int index = markers.at<int>(i,j);
if( index == -1 )
wshed.at<Vec3b>(i,j) = Vec3b(255,255,255);
else if( index <= 0 || index > nut_count )
wshed.at<Vec3b>(i,j) = Vec3b(0,0,0);
else
wshed.at<Vec3b>(i,j) = colors[(index - 1) % colors.size()];
}
}
wshed = wshed*0.5 + src*0.5;
return wshed;
}
Mat DetectionProcessing::_resize(const Mat& input, int new_length,
int ignored) {
Mat out;
float fac = 1.0;
if (input.rows > input.cols) {
fac = (float) new_length/input.rows;
}
else {
fac = (float) new_length/input.cols;
}
Size new_size((int)input.cols*fac, (int) input.rows*fac) ;
resize(input, out, new_size, INTER_NEAREST);
return out;
}
Mat DetectionProcessing::_resizeToOriginal(const Mat& input, int ignored,
int ignored2) {
Mat out;
float fac = 1.0;
if (input.rows > input.cols) {
fac = (float) Controller::inst().getCurImg()->rows/input.rows;
}
else {
fac = (float) Controller::inst().getCurImg()->cols/input.cols;
}
Size new_size((int)input.cols*fac, (int) input.rows*fac) ;
resize(input, out, new_size, INTER_NEAREST);
return out;
}
const cv::Mat& DetectionProcessing::getResult() {
return steps.back().getOutput();
}
const cv::Mat& DetectionProcessing::getMarkers() {
return markers;
}
const cv::Mat* DetectionProcessing::getInput() {
return Controller::inst().getCurImg();
}
int DetectionProcessing::getNutCount() {
return nut_count;
}

43
detectionprocessing.h Normal file
View File

@ -0,0 +1,43 @@
#ifndef DETECTIONPROCESSING_H
#define DETECTIONPROCESSING_H
#include <QObject>
#include "processing.h"
#include <memory>
class DetectionProcessing : public Processing {
Q_OBJECT
public:
DetectionProcessing(QObject *parent = 0);
void init(Json::Value json_steps);
const cv::Mat& getResult();
const cv::Mat& getMarkers();
int getNutCount();
// Processing methods
public slots:
cv::Mat _dilate(const cv::Mat& input , int kernel_size, int iterations);
cv::Mat _threshold(const cv::Mat& input, int ignored, int ignored2);
cv::Mat _thresh_color(const cv::Mat& input, int ignored, int ignored2);
cv::Mat _erode(const cv::Mat& input , int kernel_size, int iterations);
cv::Mat _erode_intermediate(const cv::Mat& input , int kernel_size, int iterations);
cv::Mat _watershed(const cv::Mat& input, int ignored, int ignored2);
cv::Mat _invert(const cv::Mat& input, int ignored, int ignored2);
cv::Mat _invert_markers(const cv::Mat& input, int ignored, int ignored2);
cv::Mat _resize(const cv::Mat& input, int new_length, int ignored);
cv::Mat _resizeToOriginal(const cv::Mat& input, int ignored, int ignored2);
cv::Mat _markBackground(const cv::Mat& input, int ignored, int ignored2);
private:
const cv::Mat* getInput();
// Members
private:
cv::Mat markers;
int nut_count;
cv::Mat intermediate;
};
#endif // DETECTIONPROCESSING_H

7
filenames.cpp Normal file
View File

@ -0,0 +1,7 @@
#include "filenames.h"
const std::string FileNames::configName = "config.json";
const std::string FileNames::inputName = "data/input.csv";
const std::string FileNames::outputName = "data/output.csv";
const std::string FileNames::imgPath = "data/imgs/";
const std::string FileNames::neuralNetName = "data/nuts.net";

14
filenames.h Normal file
View File

@ -0,0 +1,14 @@
#ifndef FILENAMES_H
#define FILENAMES_H
#include <string>
struct FileNames {
static const std::string configName;
static const std::string inputName;
static const std::string outputName;
static const std::string imgPath;
static const std::string neuralNetName;
};
#endif // FILENAMES_H

14
main.cpp Normal file
View File

@ -0,0 +1,14 @@
#include <mainwindow.h>
#include <QDialog>
#include <QApplication>
#include <QMainWindow>
int main(int argc, char** argv) {
QApplication app(argc, argv);
MainWindow window;
window.show();
return app.exec();
}

264
mainwindow.cpp Normal file
View File

@ -0,0 +1,264 @@
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDir>
#include <QApplication>
#include <QDebug>
using namespace std;
MainWindow::MainWindow(QWidget *parent) : controller(Controller::inst()),
detProc(controller.getDetectionProcessing()),
rateProc(controller.getRateProcessing()),
QMainWindow(parent),
ui(new Ui::MainWindow) {
ui->setupUi(this);
//QDir::setCurrent(qApp->applicationDirPath());
connect(ui->loadButton, SIGNAL( clicked() ),
this, SLOT( load() ));
connect(ui->nextButton, SIGNAL( clicked() ),
&detProc, SLOT( nextStep() ));
connect(ui->previousButton, SIGNAL( clicked() ),
&detProc, SLOT( previousStep() ));
connect(ui->saveButton, SIGNAL( clicked() ),
&detProc, SLOT( saveCur() ));
connect(ui->saveConfigButton, SIGNAL( clicked() ),
&controller, SLOT( saveDetectionConfig() ));
connect(ui->captureButton, SIGNAL( clicked() ),
this, SLOT( capture() ));
connect(ui->resetButton, SIGNAL( clicked() ),
&controller, SLOT( reset() ));
captureShortcut = new QShortcut(QKeySequence("Return"),
ui->liveTab);
connect(captureShortcut, SIGNAL( activated() ),
ui->captureButton, SLOT ( animateClick() ));
captureShortcut2 = new QShortcut(QKeySequence("Space"),
ui->liveTab);
connect(captureShortcut2, SIGNAL( activated() ),
ui->captureButton, SLOT ( animateClick() ));
connect(&detProc, SIGNAL( newInputImage(const cv::Mat&) ),
ui->inputView, SLOT( showImage(const cv::Mat&)));
connect(&detProc, SIGNAL( newOutputImage(const cv::Mat&) ),
ui->outputView, SLOT( showImage(const cv::Mat&)));
connect(ui->param1SpinBox, SIGNAL ( valueChanged(int) ),
&detProc, SLOT( setParam1(int) ));
connect(ui->param2SpinBox, SIGNAL ( valueChanged(int) ),
&detProc, SLOT( setParam2(int) ));
connect(&detProc, SIGNAL( requestUpdate() ),
this, SLOT( updateDetection() ));
connect(&rateProc, SIGNAL( requestUpdate() ),
this, SLOT( updateRating() ));
connect(ui->rate_nextButton, SIGNAL( clicked() ),
&rateProc, SLOT( nextStep() ));
connect(ui->rate_previousButton, SIGNAL( clicked() ),
&rateProc, SLOT( previousStep() ));
connect(ui->rate_saveButton, SIGNAL( clicked() ),
&rateProc, SLOT( saveCur() ));
connect(ui->rate_saveConfigButton, SIGNAL( clicked() ),
&controller, SLOT( saveRatingConfig() ));
connect(&rateProc, SIGNAL( newInputImage(const cv::Mat&) ),
ui->rate_inputView, SLOT( showImage(const cv::Mat&)));
connect(&rateProc, SIGNAL( newOutputImage(const cv::Mat&) ),
ui->rate_outputView, SLOT( showImage(const cv::Mat&)));
connect(ui->rate_param1SpinBox, SIGNAL ( valueChanged(int) ),
&rateProc, SLOT( setParam1(int) ));
connect(ui->rate_param2SpinBox, SIGNAL ( valueChanged(int) ),
&rateProc, SLOT( setParam2(int) ));
connect(ui->configTabWidget, SIGNAL( currentChanged(int) ),
this, SLOT( modeConfig(int) ));
connect(ui->superTabWidget, SIGNAL( currentChanged(int) ),
this, SLOT( mode(int) ));
connect(ui->previousNut, SIGNAL( clicked() ),
&controller, SLOT( previousNut() ));
connect(ui->nextNut, SIGNAL( clicked() ),
&controller, SLOT( nextNut() ));
connect(&controller, SIGNAL( newNut(const cv::Mat&) ),
ui->rate_inputView , SLOT( showImage(const cv::Mat&) ));
connect(&controller, SIGNAL( newInputImage(const cv::Mat&) ),
ui->liveView , SLOT( showImage(const cv::Mat&) ));
connect(&controller, SIGNAL( newNutToRate(const cv::Mat&) ),
ui->liveNut, SLOT( showImage(const cv::Mat&) ));
connect(&controller, SIGNAL( message(std::string ) ),
ui->liveNut, SLOT( showTextImage(std::string) ));
connect(ui->rateButton1, SIGNAL(clicked()),
this, SLOT( select1()));
connect(ui->rateButton2, SIGNAL(clicked()),
this, SLOT( select2()));
connect(ui->rateButton3, SIGNAL(clicked()),
this, SLOT( select3()));
connect(ui->rateButton4, SIGNAL(clicked()),
this, SLOT( select4()));
connect(ui->rateButton5, SIGNAL(clicked()),
this, SLOT( select5()));
connect(&controller, SIGNAL( statusMessage(QString) ),
ui->statusLabel, SLOT( setText(QString) ));
connect(ui->neuralNetCheckBox, SIGNAL( stateChanged(int) ),
&controller, SLOT( neuralNetChange(int) ));
updateDetection();
updateRating();
updateN();
}
void MainWindow::select1() {
controller.currentRating(1);
}
void MainWindow::select2() {
controller.currentRating(2);
}
void MainWindow::select3() {
controller.currentRating(3);
}
void MainWindow::select4() {
controller.currentRating(4);
}
void MainWindow::select5() {
controller.currentRating(5);
}
void MainWindow::load() {
resetRateButtons();
controller.openImage();
updateN();
}
void MainWindow::updateN() {
ostringstream sstr;
sstr << "N = " << to_string(controller.getN());
ui->NLabel->setText(QString::fromStdString(sstr.str()));
}
void MainWindow::capture() {
resetRateButtons();
ui->captureButton->setEnabled(false);
ui->captureButton->blockSignals(true);
captureShortcut->blockSignals(true);
captureShortcut2->blockSignals(true);
qApp->processEvents();
controller.captureImage();
ui->captureButton->setEnabled(true);
captureShortcut->blockSignals(false);
captureShortcut2->blockSignals(false);
ui->captureButton->blockSignals(false);
updateN();
}
void MainWindow::resetRateButtons() {
ui->rateButton1->setAutoExclusive(false);
ui->rateButton2->setAutoExclusive(false);
ui->rateButton3->setAutoExclusive(false);
ui->rateButton4->setAutoExclusive(false);
ui->rateButton5->setAutoExclusive(false);
ui->rateButton1->setChecked(false);
ui->rateButton2->setChecked(false);
ui->rateButton3->setChecked(false);
ui->rateButton4->setChecked(false);
ui->rateButton5->setChecked(false);
ui->rateButton1->setAutoExclusive(true);
ui->rateButton2->setAutoExclusive(true);
ui->rateButton3->setAutoExclusive(true);
ui->rateButton4->setAutoExclusive(true);
ui->rateButton5->setAutoExclusive(true);
}
void MainWindow::updateDetection() {
ui->progressBar->setValue(detProc.getProgress());
ui->operationLabel->setText(QString::fromStdString("Aktuelle Operation: \""
+ detProc.getStepName() + "\""));
vector<Parameter> params = detProc.getCurParams();
ui->param1Label->setText(QString::fromStdString("Parameter 1: \"" +
params[0].name + "\""));
ui->param2Label->setText(QString::fromStdString("Parameter 2: \"" +
params[1].name + "\""));
ui->param1SpinBox->blockSignals(true);
ui->param2SpinBox->blockSignals(true);
ui->param1Slider->setMinimum(params[0].lowerBound);
ui->param1Slider->setMaximum(params[0].upperBound);
ui->param1Slider->setValue(params[0].val);
ui->param2Slider->setMinimum(params[1].lowerBound);
ui->param2Slider->setMaximum(params[1].upperBound);
ui->param2Slider->setValue(params[1].val);
ui->param1SpinBox->setMinimum(params[0].lowerBound);
ui->param1SpinBox->setMaximum(params[0].upperBound);
ui->param1SpinBox->setValue(params[0].val);
ui->param2SpinBox->setMinimum(params[1].lowerBound);
ui->param2SpinBox->setMaximum(params[1].upperBound);
ui->param2SpinBox->setValue(params[1].val);
ui->param1SpinBox->blockSignals(false);
ui->param2SpinBox->blockSignals(false);
}
void MainWindow::updateRating() {
ui->rate_progressBar->setValue(rateProc.getProgress());
ui->rate_operationLabel->setText(QString::fromStdString(
"Aktuelle Operation: \""
+ rateProc.getStepName() + "\""));
vector<Parameter> params = rateProc.getCurParams();
ui->rate_param1Label->setText(QString::fromStdString("Parameter 1: \"" +
params[0].name + "\""));
ui->rate_param2Label->setText(QString::fromStdString("Parameter 2: \"" +
params[1].name + "\""));
ui->rate_param1SpinBox->blockSignals(true);
ui->rate_param2SpinBox->blockSignals(true);
ui->rate_param1Slider->setMinimum(params[0].lowerBound);
ui->rate_param1Slider->setMaximum(params[0].upperBound);
ui->rate_param1Slider->setValue(params[0].val);
ui->rate_param2Slider->setMinimum(params[1].lowerBound);
ui->rate_param2Slider->setMaximum(params[1].upperBound);
ui->rate_param2Slider->setValue(params[1].val);
ui->rate_param1SpinBox->setMinimum(params[0].lowerBound);
ui->rate_param1SpinBox->setMaximum(params[0].upperBound);
ui->rate_param1SpinBox->setValue(params[0].val);
ui->rate_param2SpinBox->setMinimum(params[1].lowerBound);
ui->rate_param2SpinBox->setMaximum(params[1].upperBound);
ui->rate_param2SpinBox->setValue(params[1].val);
ui->rate_param1SpinBox->blockSignals(false);
ui->rate_param2SpinBox->blockSignals(false);
ostringstream sstr;
sstr << "Ergebnisse: ";
for (auto&& result : rateProc.getResults()) {
sstr << result.first << ": " << result.second << ", ";
}
ui->resultLabel->setText(QString::fromStdString(sstr.str()));
}
void MainWindow::mode(int tabIndex) {
if (tabIndex == 0) {
detProc.setInteractive(false);
rateProc.setInteractive(false);
controller.process();
}
else if (tabIndex == 1) {
modeConfig(ui->configTabWidget->currentIndex());
}
}
void MainWindow::modeConfig(int tabIndex) {
if (tabIndex == 0) {
detProc.setInteractive(true);
rateProc.setInteractive(false);
updateDetection();
detProc.start();
}
else if (tabIndex == 1) {
detProc.setInteractive(false);
rateProc.setInteractive(true);
updateRating();
controller.resetNuts();
controller.extractNuts();
rateProc.start(true);
}
}
void MainWindow::closeEvent(QCloseEvent* event) {
controller.saveData();
}
MainWindow::~MainWindow()
{
delete ui;
delete captureShortcut;
delete captureShortcut2;
}

53
mainwindow.h Normal file
View File

@ -0,0 +1,53 @@
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QShortcut>
#include <QCloseEvent>
#include <QTimer>
#include "detectionprocessing.h"
#include "rateprocessing.h"
#include "controller.h"
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private slots:
void updateDetection();
void updateRating();
void modeConfig(int tabIndex);
void mode(int tabIndex);
void capture();
void load();
void select1();
void select2();
void select3();
void select4();
void select5();
private:
void closeEvent(QCloseEvent* event);
void resetRateButtons();
void updateN();
// Members
private:
Ui::MainWindow* ui;
Controller& controller;
DetectionProcessing& detProc;
RateProcessing& rateProc;
QTimer* autoSaveTimer;
QShortcut* captureShortcut;
QShortcut* captureShortcut2;
};
#endif // MAINWINDOW_H

691
mainwindow.ui Normal file
View File

@ -0,0 +1,691 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1097</width>
<height>807</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>1035</width>
<height>560</height>
</size>
</property>
<property name="windowTitle">
<string>Haselnüsse</string>
</property>
<property name="locale">
<locale language="German" country="Germany"/>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QTabWidget" name="superTabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="liveTab">
<attribute name="title">
<string>Live</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_11">
<item>
<widget class="CVImageWidget" name="liveView" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="CVImageWidget" name="liveNut" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="rateGroupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>3</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Geschmack</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_12">
<item>
<widget class="QRadioButton" name="rateButton1">
<property name="text">
<string>&amp;1</string>
</property>
<property name="shortcut">
<string>1</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="rateButton2">
<property name="text">
<string>&amp;2</string>
</property>
<property name="shortcut">
<string>2</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="rateButton3">
<property name="text">
<string>&amp;3</string>
</property>
<property name="shortcut">
<string>3</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="rateButton4">
<property name="text">
<string>&amp;4</string>
</property>
<property name="shortcut">
<string>4</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="rateButton5">
<property name="text">
<string>&amp;5</string>
</property>
<property name="shortcut">
<string>5</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_9">
<item>
<widget class="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Status:</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="statusLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>9</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>... lade ...</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="NLabel">
<property name="text">
<string>N = 0</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_10">
<item>
<widget class="QPushButton" name="loadButton">
<property name="text">
<string>Bild laden</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="captureButton">
<property name="text">
<string>Bild aufnehmen</string>
</property>
<property name="shortcut">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="resetButton">
<property name="text">
<string>Zurücksetzen</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="neuralNetCheckBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Benutze Neurales Netz</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QWidget" name="configTab">
<attribute name="title">
<string>Konfiguration</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<widget class="QTabWidget" name="configTabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="detectTab">
<attribute name="title">
<string>Nüsse erkennen</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QPushButton" name="previousButton">
<property name="text">
<string>Vorheriger Schritt</string>
</property>
<property name="shortcut">
<string>Ctrl+S</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="nextButton">
<property name="text">
<string>Nächster Schritt</string>
</property>
<property name="shortcut">
<string>Right</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="saveButton">
<property name="text">
<string>Speichern</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QProgressBar" name="progressBar">
<property name="enabled">
<bool>true</bool>
</property>
<property name="value">
<number>24</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="operationLabel">
<property name="text">
<string>Aktuelle Operation:</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="label_1">
<property name="text">
<string>Input:</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Output:</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="CVImageWidget" name="inputView" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="CVImageWidget" name="outputView" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="param1Label">
<property name="text">
<string>Parameter 1:</string>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="param1Slider">
<property name="maximum">
<number>255</number>
</property>
<property name="value">
<number>10</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="param1SpinBox">
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>255</number>
</property>
<property name="singleStep">
<number>1</number>
</property>
<property name="value">
<number>10</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="param2Label">
<property name="text">
<string>Parameter 2:</string>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="param2Slider">
<property name="maximum">
<number>255</number>
</property>
<property name="value">
<number>3</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="param2SpinBox">
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>255</number>
</property>
<property name="value">
<number>3</number>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="saveConfigButton">
<property name="text">
<string>Konfiguration speichern</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QWidget" name="rateTab">
<attribute name="title">
<string>Nüsse bewerten</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QPushButton" name="previousNut">
<property name="text">
<string>vorherige Nuss</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="nextNut">
<property name="text">
<string>nächste Nuss</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="rate_previousButton">
<property name="text">
<string>Vorheriger Schritt</string>
</property>
<property name="shortcut">
<string>Ctrl+S</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="rate_nextButton">
<property name="text">
<string>Nächster Schritt</string>
</property>
<property name="shortcut">
<string>Right</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="rate_saveButton">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Speichern</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QProgressBar" name="rate_progressBar">
<property name="enabled">
<bool>true</bool>
</property>
<property name="value">
<number>24</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="rate_operationLabel">
<property name="text">
<string>Aktuelle Operation:</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Input:</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>Output:</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_7">
<item>
<widget class="CVImageWidget" name="rate_inputView" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="CVImageWidget" name="rate_outputView" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_8">
<item>
<widget class="QLabel" name="rate_param1Label">
<property name="text">
<string>Parameter 1:</string>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="rate_param1Slider">
<property name="maximum">
<number>255</number>
</property>
<property name="value">
<number>10</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="rate_param1SpinBox">
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>255</number>
</property>
<property name="singleStep">
<number>1</number>
</property>
<property name="value">
<number>10</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="rate_param2Label">
<property name="text">
<string>Parameter 2:</string>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="rate_param2Slider">
<property name="maximum">
<number>255</number>
</property>
<property name="value">
<number>3</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="rate_param2SpinBox">
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>255</number>
</property>
<property name="value">
<number>3</number>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="rate_saveConfigButton">
<property name="text">
<string>Konfiguration speichern</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="resultLabel">
<property name="text">
<string>Ergebnis:</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
</widget>
<customwidgets>
<customwidget>
<class>CVImageWidget</class>
<extends>QWidget</extends>
<header>cvimagewidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections>
<connection>
<sender>rate_param1Slider</sender>
<signal>valueChanged(int)</signal>
<receiver>rate_param1SpinBox</receiver>
<slot>setValue(int)</slot>
<hints>
<hint type="sourcelabel">
<x>272</x>
<y>729</y>
</hint>
<hint type="destinationlabel">
<x>408</x>
<y>733</y>
</hint>
</hints>
</connection>
<connection>
<sender>rate_param1SpinBox</sender>
<signal>valueChanged(int)</signal>
<receiver>rate_param1Slider</receiver>
<slot>setValue(int)</slot>
<hints>
<hint type="sourcelabel">
<x>411</x>
<y>718</y>
</hint>
<hint type="destinationlabel">
<x>226</x>
<y>728</y>
</hint>
</hints>
</connection>
<connection>
<sender>rate_param2SpinBox</sender>
<signal>valueChanged(int)</signal>
<receiver>rate_param2Slider</receiver>
<slot>setValue(int)</slot>
<hints>
<hint type="sourcelabel">
<x>845</x>
<y>726</y>
</hint>
<hint type="destinationlabel">
<x>797</x>
<y>732</y>
</hint>
</hints>
</connection>
<connection>
<sender>rate_param2Slider</sender>
<signal>valueChanged(int)</signal>
<receiver>rate_param2SpinBox</receiver>
<slot>setValue(int)</slot>
<hints>
<hint type="sourcelabel">
<x>704</x>
<y>726</y>
</hint>
<hint type="destinationlabel">
<x>840</x>
<y>719</y>
</hint>
</hints>
</connection>
</connections>
</ui>

273
neuralnet.cpp Normal file
View File

@ -0,0 +1,273 @@
#include "neuralnet.h"
#include "controller.h"
#include "filenames.h"
#include <fstream>
#include <limits>
#include <clocale>
using namespace std;
using namespace FANN;
void NeuralNet::saveNut() {
if (Controller::inst().getCurRating() == -1) {
cerr << "Error: current rating -1 during saving." << endl;
exit(1);
}
ostringstream input_line;
input_line << current_index << ";";
input_line.precision(numeric_limits<double>::max_digits10);
const auto& results = Controller::inst().getRateProcessing().getResults();
for (const auto& result : result_names) {
auto pos = results.find(result);
if (pos == results.end()) {
cerr << "Could not find " << result << " in results." << endl;
exit(1);
}
inputTrainData.push_back(pos->second);
input_line << scientific << pos->second << ";";
}
ofstream input_file;
input_file.open(FileNames::inputName, ios_base::app);
input_file << input_line.str() << "\n";
outputTrainData.push_back(Controller::inst().getCurRating());
ofstream output_file;
output_file.open(FileNames::outputName, ios_base::app);
output_file << current_index << ";";
output_file << Controller::inst().getCurRating() << ";" << "\n";
ostringstream img_file;
img_file << FileNames::imgPath
<< "nut_" << to_string(current_index)
<< ".jpg";
imwrite(img_file.str(), *Controller::inst().getNutToRate());
current_index++;
if (current_index % 5 == 0) {
trainAndSave();
}
}
void NeuralNet::trainAndSave() {
setlocale(LC_ALL, "en_GB.UTF-8");
getScalingFactors();
scaleData();
training_data data;
data.set_train_data(scaledOutputTrainData.size(), result_names.size(),
&scaledInputTrainData[0], 1, &scaledOutputTrainData[0]);
net.train_on_data(data, 100000, 1000, 0.01);
if (!net.save(FileNames::neuralNetName)) {
cerr << "Error saving neural net:"
<< net.get_errstr() << endl;
exit(1);
}
}
void NeuralNet::scaleData() {
scaledInputTrainData = vector<float>(inputTrainData.size());
for(int i = 0; i<result_names.size(); i++) {
float data_min = inputTrainData[i];
float data_max = inputTrainData[i];
for(int j = 0; j<outputTrainData.size(); j++) {
scaledInputTrainData[(j*result_names.size())+i] =
(inputTrainData[(j*result_names.size())+i] -
get<0>(scaling_input_data[i])) *
get <1>(scaling_input_data[i]);
}
}
scaledOutputTrainData = vector<float>(outputTrainData.size());
for(int i = 0; i<outputTrainData.size(); i++) {
scaledOutputTrainData[i] = (outputTrainData[i] - 1) * 0.25;
}
}
void NeuralNet::getScalingFactors() {
scaling_input_data = vector<tuple<float, float>>(result_names.size());
for(int i = 0; i<result_names.size(); i++) {
float data_min = inputTrainData[i];
float data_max = inputTrainData[i];
for(int j = 0; j<outputTrainData.size(); j++) {
data_min = min(data_min, inputTrainData[(j*result_names.size())+i]);
data_max = max(data_max, inputTrainData[(j*result_names.size())+i]);
}
cout << "Input " << i << ": min, scaling_factor: " << data_min
<< ", " << 1.0f/(data_max - data_min)
<< " min: " << data_min
<< " max: " << data_max << endl;
scaling_input_data[i] = make_tuple(
data_min, 1.0f/(data_max-data_min));
}
}
float NeuralNet::run(const map<string, double>& input) {
float input_arr[input.size()];
for (unsigned int i = 0; i<result_names.size(); i++) {
auto pos = input.find(result_names[i]);
if (pos == input.end()) {
cerr << "Could not find " << result_names[i] << " in results."
<< endl;
exit(1);
}
if (scaling_input_data.size() > 0) {
input_arr[i] =
(pos->second - get<0>(scaling_input_data[i])) *
get<1>(scaling_input_data[i]);
}
else {
input_arr[i] = pos->second;
}
}
float ret = 0.0f;
if (use_neural_net) {
float* output;
output = net.run(input_arr);
ret = output[0];
}
else {
ret =
0.25 * input_arr[0] +
0.125* input_arr[1] +
0.125* (1.0f/input_arr[2]) +
0.25 * input_arr[3] +
0.25 * input_arr[4];
}
return min(1.0f, max(ret, 0.0f));
}
void NeuralNet::init(Json::Value jsonResults) {
for(int i = 0; i<jsonResults.size(); i++) {
result_names.push_back(jsonResults[i].asString());
}
if (filesExist()) {
setlocale(LC_ALL, "en_GB.UTF-8");
string inputLine;
string outputLine;
ifstream input_file(FileNames::inputName);
ifstream output_file(FileNames::outputName);
// check header
getline(input_file, inputLine);
ostringstream inputHeader;
inputHeader << "nut_id" << ";";
for (const auto& result : result_names) {
inputHeader << result << ";";
}
if (inputLine != inputHeader.str()) {
cerr << "Header for output CSV file invalid: " <<
inputHeader.str() << endl;
exit(1);
}
getline(output_file, outputLine);
if (outputLine != "nut_id;score;") {
cerr << "Header for output CSV file invalid: " <<
outputLine << endl;
exit(1);
}
while ( getline(input_file, inputLine)
&& getline(output_file, outputLine)) {
stringstream inputLineStream(inputLine);
string cell;
// read in index
getline(inputLineStream, cell, ';');
try {
current_index = stoi(cell);
}
catch (invalid_argument e) {
cerr << "Could not convert " << cell << " to int." << endl;
exit(1);
}
while(getline(inputLineStream, cell, ';')) {
double val;
try {
val= stof(cell);
}
catch (invalid_argument e) {
cerr << "Could not convert " << cell << " to double."
<< endl;
exit(1);
}
if (val < 0) {
cerr << "Error reading input data. Index " << current_index
<< "has negative data." << endl;
exit(1);
}
inputTrainData.push_back(val);
}
stringstream outputLineStream(outputLine);
getline(outputLineStream, cell, ';');
int output_index;
try {
output_index = stoi(cell);
}
catch (invalid_argument e) {
cerr << "Could not convert " << cell << " to int." << endl;
exit(1);
}
if (current_index != output_index) {
cerr << "Error reading data: Expected index " << current_index
<< " but got index " << output_index << endl;
exit(1);
}
getline(outputLineStream, cell, ';');
double val;
try {
val= stof(cell);
}
catch (invalid_argument e) {
cerr << "Could not convert " << cell << " to double."
<< endl;
exit(1);
}
if (val < 0) {
cerr << "Error reading output data. Index " << current_index
<< "has negative data." << endl;
exit(1);
}
outputTrainData.push_back(val);
}
current_index++;
net.create_from_file(FileNames::neuralNetName);
net.set_training_algorithm(TRAIN_BATCH);
net.set_activation_function_hidden(SIGMOID);
net.set_activation_function_output(LINEAR);
getScalingFactors();
trainAndSave();
}
else {
createHeaders();
int input_size = result_names.size();
net.create_standard(3, input_size, input_size + 1, 1);
net.set_training_algorithm(TRAIN_BATCH);
net.set_activation_function_hidden(SIGMOID);
net.set_activation_function_output(LINEAR);
}
cout << "current_index: " << current_index << endl;
}
void NeuralNet::createHeaders() {
ofstream input_file(FileNames::inputName);
input_file << "nut_id" << ";";
for (const auto& result : result_names) {
input_file << result << ";";
}
input_file << "\n";
ofstream output_file(FileNames::outputName);
output_file << "nut_id" << ";";
output_file << "score" << ";";
output_file << "\n";
}
bool NeuralNet::filesExist() {
ifstream input_file(FileNames::inputName);
ifstream output_file(FileNames::outputName);
ifstream net_file(FileNames::neuralNetName);
return input_file.good() && output_file.good() && net_file.good();
}
int NeuralNet::getN() {
return current_index;
}
void NeuralNet::neuralNetEnabled(bool val) {
use_neural_net = val;
}

38
neuralnet.h Normal file
View File

@ -0,0 +1,38 @@
#ifndef NEURALNET_H
#define NEURALNET_H
#include <vector>
#include <memory>
#include <json/json.h>
#include <fann.h>
#include <fann_cpp.h>
class NeuralNet {
public:
void saveNut();
void init(Json::Value jsonResults);
int getN();
float run(const std::map<std::string, double>& input);
void neuralNetEnabled(bool val);
private:
void createHeaders();
bool filesExist();
void trainAndSave();
void getScalingFactors();
void scaleData();
private:
int current_index = 0;
std::vector<float> inputTrainData = {};
std::vector<float> scaledInputTrainData = {};
std::vector<float> outputTrainData = {};
std::vector<float> scaledOutputTrainData = {};
std::vector<std::string> result_names = {};
FANN::neural_net net;
// tuple of min and scaling factor for each input
std::vector<std::tuple<float, float>> scaling_input_data = {};
bool use_neural_net = true;
};
#endif // NEURALNET_H

183
processing.cpp Normal file
View File

@ -0,0 +1,183 @@
#include "processing.h"
#include "controller.h"
#include <QDebug>
#include <QFileDialog>
#include <QApplication>
#include <algorithm>
#include <cmath>
#include <fstream>
using namespace std;
using namespace cv;
Processing::Processing(QObject *parent) : QObject(parent) {}
void Processing::init(const QMetaObject* metaObj, Json::Value json_steps) {
for(int i = 0; i<json_steps.size(); i++) {
string json_name = json_steps[i].get("name", "").asString();
string json_op_name = json_steps[i].get("operation", "").asString();
QMetaMethod operation;
bool found = false;
for (int i = 0; i<metaObj->methodCount(); i++) {
QMetaMethod method = metaObj->method(i);
if (method.name().toStdString() == json_op_name) {
operation = method;
found = true;
}
}
if (!found) {
cerr << "Processing method " << json_op_name <<
" not found.\n";
exit(1);
}
Json::Value json_param1 = json_steps[i]["param1"];
Parameter param1;
param1.val = json_param1.get("default", 0).asInt();
param1.lowerBound = json_param1.get("lowerBound", 0).asInt();
param1.upperBound = json_param1.get("upperBound", 0).asInt();
param1.name = json_param1.get("name", "").asString();
param1.odd = json_param1.get("odd", false).asBool();
Json::Value json_param2 = json_steps[i]["param2"];
Parameter param2;
param2.val = json_param2.get("default", 0).asInt();
param2.lowerBound = json_param2.get("lowerBound", 0).asInt();
param2.upperBound = json_param2.get("upperBound", 0).asInt();
param2.name = json_param2.get("name", "").asString();
param2.odd = json_param2.get("odd", false).asBool();
vector<Parameter> params = {param1, param2};
InputType type;
if (json_steps[i]["input"] == "previous") {
type = previous;
}
else if (json_steps[i]["input"] == "color") {
type = color;
}
ProcessingStep step(this, json_name, operation, params, type);
steps.push_back(step);
}
}
void Processing::start(bool force) {
if (!steps[current_step].hasInputData()) {
return;
}
if (interactive) {
emit newInputImage(steps[current_step].getInput());
}
if (steps[current_step].getOutput().empty() || force) {
processImage();
}
else {
emit newOutputImage(steps[current_step].getOutput());
}
}
void Processing::reset() {
current_step = 0;
for (auto&& step : steps) {
step.reset();
}
InputType type = steps[0].getInputType();
if (type == color) {
steps[0].setInput(Controller::inst().getCurImg());
}
}
void Processing::processImage() {
steps[current_step].execute();
if (interactive) {
emit newOutputImage(steps[current_step].getOutput());
}
}
void Processing::setParam1(int val) {
steps[current_step].setParam1(val);
processImage();
}
void Processing::setParam2(int val) {
steps[current_step].setParam2(val);
processImage();
}
void Processing::nextStep() {
current_step = min((int)steps.size() - 1, current_step+1);
InputType type = steps[current_step].getInputType();
if (type == previous) {
steps[current_step].setInput(&steps[current_step -
1].getOutput());
if (interactive) {
emit newInputImage(steps[current_step].getInput());
}
}
else if (type == color) {
steps[current_step].setInput(getInput());
if (interactive && steps[current_step].hasInputData()) {
emit newInputImage(steps[current_step].getInput());
}
}
if (interactive) {
emit requestUpdate();
}
if (steps[current_step].hasInputData()) {
processImage();
}
else {
cerr << "No input data for step: "
+ steps[current_step].getStepName() << endl;
}
}
void Processing::previousStep() {
current_step = max(0, current_step-1);
if (interactive) {
emit requestUpdate();
if (steps[current_step].hasData()) {
emit newInputImage(steps[current_step].getInput());
emit newOutputImage(steps[current_step].getOutput());
}
}
}
int Processing::getProgress() {
double prog = (double)(current_step+1)/steps.size();
return ceil(prog*100);
}
string Processing::getStepName() {
return steps[current_step].getStepName();
}
vector<Parameter> Processing::getCurParams() {
return steps[current_step].getParams();
}
void Processing::saveCur() {
steps[current_step].save();
}
void Processing::setInteractive(bool val) {
interactive = val;
}
bool Processing::isInteractive() {
return interactive;
}
int Processing::getCurrentStep() {
return current_step;
}
bool Processing::process() {
if (! steps[0].hasInputData()) {
return false;
}
// also do step 0
current_step = -1;
for (unsigned int i = 0; i<steps.size(); i++) {
nextStep();
}
return true;
}

50
processing.h Normal file
View File

@ -0,0 +1,50 @@
#ifndef PROCESSING_H
#define PROCESSING_H
#include <QObject>
#include <opencv2/opencv.hpp>
#include <json/json.h>
#include "processingstep.h"
class Processing : public QObject {
Q_OBJECT
public:
Processing(QObject *parent = 0);
std::vector<Parameter> getCurParams();
std::string getStepName();
int getProgress();
void setInteractive(bool val);
bool isInteractive();
void start(bool force = false);
void reset();
int getCurrentStep();
bool process();
signals:
void newInputImage(const cv::Mat& newImage);
void newOutputImage(const cv::Mat& newImage);
void requestUpdate();
public slots:
void setParam1(int val);
void setParam2(int val);
void nextStep();
void previousStep();
void saveCur();
void processImage();
protected:
virtual const cv::Mat* getInput() = 0;
// Members
protected:
std::vector<ProcessingStep> steps {};
int current_step = 0;
Json::Value config;
void init(const QMetaObject* metaObj, Json::Value json_steps);
bool interactive = false;
};
#endif // PROCESSING_H

100
processingstep.h Normal file
View File

@ -0,0 +1,100 @@
#ifndef PROCESSINGSTEP_H
#define PROCESSINGSTEP_H
#include <opencv2/opencv.hpp>
#include <functional>
#include <QMetaMethod>
#include <QObject>
struct Parameter {
int val;
int lowerBound;
int upperBound;
std::string name;
bool odd;
};
enum InputType {
previous,
color
};
class ProcessingStep {
public:
ProcessingStep(QObject* processing,
std::string stepName,
QMetaMethod operation,
std::vector<Parameter> params,
InputType inputType
) {
this->processing = processing;
this->stepName = stepName;
this->operation = operation;
this->params = params;
this->inputType = inputType;
}
std::vector<Parameter> getParams() { return params; }
std::string getStepName() { return stepName; }
void setParam1 (int val) {
if (params[0].odd && val % 2 == 0) {
val--;
}
params[0].val = val;
}
int getParam1() { return params[0].val; }
int getParam2() { return params[1].val; }
void setParam2 (int val) {
if (params[1].odd && val % 2 == 0) {
val--;
}
params[1].val = val;
}
void execute() {
if (input->empty()) {
std::cerr << "Input empty for processing step: " << stepName << std::endl;
return;
}
operation.invoke(processing,
Q_RETURN_ARG(cv::Mat, output),
Q_ARG(const cv::Mat&, *input),
Q_ARG(int, params[0].val),
Q_ARG(int, params[1].val)
);
}
void save() {
imwrite(stepName + ".jpg", output);
}
const cv::Mat& getOutput() { return output; }
const cv::Mat& getInput() { return *input; }
void setInput(const cv::Mat* input) { this->input = input; }
InputType getInputType() { return inputType; }
bool hasInputData() { return input != nullptr && !input->empty(); }
bool hasData() { return input != nullptr && !input->empty()
&& !output.empty(); }
void reset() {
input = nullptr;
output.release();
}
private:
const cv::Mat* input = nullptr;
cv::Mat output;
std::string stepName;
QMetaMethod operation;
std::vector<Parameter> params;
InputType inputType;
QObject* processing;
};
#endif

311
rateprocessing.cpp Normal file
View File

@ -0,0 +1,311 @@
#include "detectionprocessing.h"
#include "controller.h"
#include <QDebug>
#include <QFileDialog>
#include <QApplication>
#include <algorithm>
#include <cmath>
#include <fstream>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
using namespace std;
using namespace cv;
RateProcessing::RateProcessing(QObject* parent)
: Processing(parent) {}
void RateProcessing::init(Json::Value json_steps) {
Processing::init(this->metaObject(), json_steps);
}
Mat RateProcessing::_dilate(const Mat& input, int kernel_size,
int iterations) {
Mat out;
dilate(input, out, getStructuringElement(MORPH_ELLIPSE,
Size(kernel_size, kernel_size)), Point(-1, -1), iterations);
return out;
}
Mat RateProcessing::_threshold(const Mat& input, int ignored,
int ignored2) {
Mat gray;
cvtColor(input, gray, COLOR_BGR2GRAY);
Mat out;
threshold(gray, out, 1, 255, THRESH_BINARY);
return out;
}
Mat RateProcessing::_thresh_otsu(const Mat& input, int ignored,
int ignored2) {
Mat gray;
cvtColor(input, gray, COLOR_BGR2GRAY);
Mat out;
threshold(gray, out, 0, 255, THRESH_BINARY | THRESH_OTSU);
return out;
}
Mat RateProcessing::_invert(const Mat& input, int ignored, int ignored2) {
Mat out = Scalar::all(255) - input;
return out;
}
Mat RateProcessing::_thresh_color(const Mat& input, int lowerThreshold, int
upperThreshold) {
Mat hsv;
cvtColor(input, hsv, COLOR_BGR2HSV);
Mat out(input.size(), CV_8U);
inRange(hsv, Scalar(lowerThreshold, 0, 0), Scalar(upperThreshold,
255, 255), out);
return out;
}
Mat RateProcessing::_mean_color(const Mat& input, int ignored, int ignored2) {
Mat hsv_nut;
cvtColor(*getInput(), hsv_nut, CV_BGR2HSV);
Scalar res = mean(hsv_nut, input);
results["avg_h_val"] = res[0];
if (isInteractive()) {
emit requestUpdate();
}
return hsv_nut;
}
Mat RateProcessing::_erode(const Mat& input, int kernel_size, int
iterations) {
Mat out;
erode(input, out, getStructuringElement(MORPH_ELLIPSE,
Size(kernel_size, kernel_size)), Point(-1, -1), iterations);
return out;
}
Mat RateProcessing::_hough(const Mat& input, int canny_1, int canny_2) {
vector<Vec3f> circles;
Mat gray;
cvtColor(input, gray, COLOR_BGR2GRAY);
HoughCircles(gray, circles, HOUGH_GRADIENT, 1, input.rows/4, canny_1,
canny_2);
Mat out;
input.copyTo(out);
for (int i = 0; i<circles.size(); i++) {
Point center(cvRound(circles[i][0]), cvRound(circles[i][1]));
int radius = cvRound(circles[i][2]);
circle(out, center, 3, Scalar(0, 255, 0), -1, 8, 0);
circle(out, center, radius, Scalar(0, 0, 255), 3, 8, 0);
}
return out;
}
Mat RateProcessing::_find_contours_resized(const Mat& input, int max_length,
int ignored2) {
vector<vector<Point>> contours;
Mat in;
float fac = 1.0;
if (input.rows > input.cols) {
fac = (float) max_length/input.rows;
}
else {
fac = (float) max_length/input.cols;
}
Size new_size((int)input.cols*fac, (int) input.rows*fac) ;
resize(input, in, new_size);
findContours(in, contours, RETR_LIST, CHAIN_APPROX_TC89_KCOS);
Mat out;
if (isInteractive()) {
out = Mat::zeros(in.size(), CV_8UC3);
for(int i = 0; i < contours.size(); i++) {
drawContours(out, contours, i, Scalar::all(255), 1, 8);
}
}
return out;
}
Mat RateProcessing::_find_contours(const Mat& input, int ignored,
int ignored2) {
Mat in = input.clone();
vector<vector<Point>> contours;
Mat out;
findContours(in, contours, RETR_LIST, CHAIN_APPROX_NONE);
if (isInteractive()) {
out = Mat::zeros(input.size(), CV_8UC3);
for(int i = 0; i < contours.size(); i++) {
drawContours(out, contours, i, Scalar::all(255), 1, 8);
}
}
return out;
}
Mat RateProcessing::_fit_ellipse(const Mat& input, int ignored, int ignored2) {
Mat in = input.clone();
//vector<vector<Point>> contours;
contours = {};
Mat out;
findContours(in, contours, RETR_LIST, CHAIN_APPROX_NONE);
if (isInteractive()) {
out = Mat::zeros(input.size(), CV_8UC3);
}
vector<RotatedRect> boxes = {};
for(int i = 0; i < contours.size(); i++) {
int count = contours[i].size();
if( count < 6 )
continue;
Mat pointsf;
Mat(contours[i]).convertTo(pointsf, CV_32F);
RotatedRect box = fitEllipse(pointsf);
if( MAX(box.size.width, box.size.height) >
MIN(box.size.width, box.size.height)*30 )
continue;
boxes.push_back(box);
if (isInteractive()) {
drawContours(out, contours, i, Scalar::all(255), 1, 8);
ellipse(out, box, Scalar(0,0,255));
Point2f vtx[4];
box.points(vtx);
for( int j = 0; j < 4; j++ ) {
line(out, vtx[j], vtx[(j+1)%4], Scalar(0,255,0), 1, LINE_AA);
}
}
}
RotatedRect& max_box = boxes[0];
int max_area = 0;
for (auto&& box : boxes) {
if ((box.size.width * box.size.height) > max_area) {
max_box = box;
max_area = box.size.width * box.size.height;
}
}
results["size_ratio"] = max_box.size.width/max_box.size.height;
results["size_diagonal"] =
sqrt(max_box.size.height * max_box.size.height +
max_box.size.height * max_box.size.height)
/ static_cast<float>(steps[current_step].getParam1());
if (isInteractive()) {
emit requestUpdate();
}
return out;
}
Mat RateProcessing::_rate_spots(const Mat& input, int ignored,
int ignored2) {
Mat in = input.clone();
vector<vector<Point>> contours;
Mat out;
findContours(in, contours, RETR_LIST, CHAIN_APPROX_NONE);
if (isInteractive()) {
out = Mat::zeros(input.size(), CV_8UC3);
}
vector<RotatedRect> boxes = {};
// image and mask of spots
vector<tuple<Mat, Mat>> spots = {};
for(int i = 0; i < contours.size(); i++) {
int count = contours[i].size();
if( count < 6 )
continue;
Mat pointsf;
Mat(contours[i]).convertTo(pointsf, CV_32F);
RotatedRect box = fitEllipse(pointsf);
if( MAX(box.size.width, box.size.height) >
MIN(box.size.width, box.size.height)*30 )
continue;
boxes.push_back(box);
if (isInteractive()) {
drawContours(out, contours, i, Scalar::all(255), 1, 8);
ellipse(out, box, Scalar(0,0,255));
Point2f vtx[4];
box.points(vtx);
for( int j = 0; j < 4; j++ ) {
line(out, vtx[j], vtx[(j+1)%4], Scalar(0,255,0), 1, LINE_AA);
}
}
// extract spot from image
Rect roi = boundingRect(contours[i]);
Mat mask = Mat::zeros(input.size(), CV_8UC1);
drawContours(mask, contours, i, Scalar(255), CV_FILLED);
Mat contourRegion;
Mat imageROI;
getInput()->copyTo(imageROI, mask);
contourRegion = imageROI(roi);
Mat maskRegion = mask(roi);
spots.push_back(make_tuple(contourRegion, maskRegion));
}
// find largest ellipse to ignore it, because its no spot but the nut itself
int max_box_index = -1;
int max_area = 0;
for (unsigned int i = 0; i<boxes.size(); i++) {
if ((boxes[i].size.width * boxes[i].size.height) > max_area) {
max_box_index = i;
max_area = boxes[i].size.width * boxes[i].size.height;
}
}
double box_avg_size = 0.0f;
for (unsigned int i = 0; i<boxes.size(); i++) {
if (i == max_box_index) {
continue;
}
box_avg_size +=
sqrt(boxes[i].size.width * boxes[i].size.width +
boxes[i].size.height * boxes[i].size.height);
}
if (boxes.size() > 1) {
box_avg_size /= static_cast<double>(boxes.size() - 1);
}
else {
box_avg_size = 0.0f;
}
results["spot_avg_size"] = box_avg_size/steps[current_step].getParam1();
double spot_avg_h_val = 0.0f;
for (unsigned int i = 0; i<spots.size(); i++) {
if (i == max_box_index) {
continue;
}
Mat hsv_spot;
cvtColor(get<0>(spots[i]), hsv_spot, CV_BGR2HSV);
Scalar res = mean(hsv_spot, get<1>(spots[i]));
spot_avg_h_val += res[0];
}
if (spots.size() > 1) {
spot_avg_h_val /= static_cast<double>(spots.size() - 1);
}
else {
spot_avg_h_val = 0.0f;
}
results["spot_avg_h_val"] = spot_avg_h_val;
if (isInteractive()) {
emit requestUpdate();
}
return out;
}
void RateProcessing::reset() {
current_step = 0;
InputType type = steps[0].getInputType();
if (type == color) {
steps[0].setInput(Controller::inst().getCurNut());
}
results.clear();
rateCurNut = true;
}
map<string, double> RateProcessing::getResults() {
return results;
}
const cv::Mat* RateProcessing::getInput() {
if (rateCurNut) {
return Controller::inst().getCurNut();
}
else {
return Controller::inst().getNutToRate();
}
}
const vector<vector<Point>>& RateProcessing::getContours() {
return contours;
}
void RateProcessing::setRateCurNut(bool val) {
rateCurNut = val;
}

46
rateprocessing.h Normal file
View File

@ -0,0 +1,46 @@
#ifndef RATEPROCESSING_H
#define RATEPROCESSING_H
#include <QObject>
#include "processing.h"
#include <memory>
#include <map>
class RateProcessing : public Processing {
Q_OBJECT
public:
RateProcessing(QObject *parent = 0);
void init(Json::Value json_steps);
void reset();
std::map<std::string, double> getResults();
const std::vector<std::vector<cv::Point>>& getContours();
void setRateCurNut(bool val);
// Processing methods
public slots:
cv::Mat _dilate(const cv::Mat& input , int kernel_size, int iterations);
cv::Mat _threshold(const cv::Mat& input, int ignored, int ignored2);
cv::Mat _thresh_otsu(const cv::Mat& input, int ignored, int ignored2);
cv::Mat _thresh_color(const cv::Mat& input, int ignored, int ignored2);
cv::Mat _erode(const cv::Mat& input , int kernel_size, int iterations);
cv::Mat _invert(const cv::Mat& input, int iter, int ignored);
cv::Mat _hough(const cv::Mat& input, int canny_1, int canny_2);
cv::Mat _fit_ellipse(const cv::Mat& input, int ignored, int ignored2);
cv::Mat _rate_spots(const cv::Mat& input, int ignored, int ignored2);
cv::Mat _find_contours(const cv::Mat& input, int ignored, int ignored2);
cv::Mat _find_contours_resized(const cv::Mat& input, int max_length, int ignored2);
cv::Mat _mean_color(const cv::Mat& input, int ignored, int ignored2);
private:
const cv::Mat* getInput();
// Members
private:
bool rateCurNut = true;
std::map<std::string, double> results;
std::vector<std::vector<cv::Point>> contours;
};
#endif // RATEPROCESSING_H

4
test_data/.directory Normal file
View File

@ -0,0 +1,4 @@
[Dolphin]
PreviewsShown=true
Timestamp=2016,11,29,0,38,21
Version=3

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 MiB