commit ced9e792bf1edf26e24fd3ce5314710698c44531 Author: Faerbit Date: Tue Jan 17 22:39:27 2017 +0100 Initial commit. diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..0e706c6 --- /dev/null +++ b/CMakeLists.txt @@ -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}) diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ea6ff03 --- /dev/null +++ b/Makefile @@ -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 diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..00b0729 --- /dev/null +++ b/Readme.md @@ -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. + diff --git a/colors.h b/colors.h new file mode 100644 index 0000000..5b38398 --- /dev/null +++ b/colors.h @@ -0,0 +1,1032 @@ +#ifndef COLORS_H +#define COLORS_H + +#include +#include + +std::vector colors = { + cv::Vec3b(0, 0, 0), + cv::Vec3b(255, 255, 0), + cv::Vec3b(28, 230, 255), + cv::Vec3b(255, 52, 255), + cv::Vec3b(255, 74, 70), + cv::Vec3b(0, 137, 65), + cv::Vec3b(0, 111, 166), + cv::Vec3b(163, 0, 89), + cv::Vec3b(255, 219, 229), + cv::Vec3b(122, 73, 0), + cv::Vec3b(0, 0, 166), + cv::Vec3b(99, 255, 172), + cv::Vec3b(183, 151, 98), + cv::Vec3b(0, 77, 67), + cv::Vec3b(143, 176, 255), + cv::Vec3b(153, 125, 135), + cv::Vec3b(90, 0, 7), + cv::Vec3b(128, 150, 147), + cv::Vec3b(254, 255, 230), + cv::Vec3b(27, 68, 0), + cv::Vec3b(79, 198, 1), + cv::Vec3b(59, 93, 255), + cv::Vec3b(74, 59, 83), + cv::Vec3b(255, 47, 128), + cv::Vec3b(97, 97, 90), + cv::Vec3b(186, 9, 0), + cv::Vec3b(107, 121, 0), + cv::Vec3b(0, 194, 160), + cv::Vec3b(255, 170, 146), + cv::Vec3b(255, 144, 201), + cv::Vec3b(185, 3, 170), + cv::Vec3b(209, 97, 0), + cv::Vec3b(221, 239, 255), + cv::Vec3b(0, 0, 53), + cv::Vec3b(123, 79, 75), + cv::Vec3b(161, 194, 153), + cv::Vec3b(48, 0, 24), + cv::Vec3b(10, 166, 216), + cv::Vec3b(1, 51, 73), + cv::Vec3b(0, 132, 111), + cv::Vec3b(55, 33, 1), + cv::Vec3b(255, 181, 0), + cv::Vec3b(194, 255, 237), + cv::Vec3b(160, 121, 191), + cv::Vec3b(204, 7, 68), + cv::Vec3b(192, 185, 178), + cv::Vec3b(194, 255, 153), + cv::Vec3b(0, 30, 9), + cv::Vec3b(0, 72, 156), + cv::Vec3b(111, 0, 98), + cv::Vec3b(12, 189, 102), + cv::Vec3b(238, 195, 255), + cv::Vec3b(69, 109, 117), + cv::Vec3b(183, 123, 104), + cv::Vec3b(122, 135, 161), + cv::Vec3b(120, 141, 102), + cv::Vec3b(136, 85, 120), + cv::Vec3b(250, 208, 159), + cv::Vec3b(255, 138, 154), + cv::Vec3b(209, 87, 160), + cv::Vec3b(190, 196, 89), + cv::Vec3b(69, 102, 72), + cv::Vec3b(0, 134, 237), + cv::Vec3b(136, 111, 76), + cv::Vec3b(52, 54, 45), + cv::Vec3b(180, 168, 189), + cv::Vec3b(0, 166, 170), + cv::Vec3b(69, 44, 44), + cv::Vec3b(99, 99, 117), + cv::Vec3b(163, 200, 201), + cv::Vec3b(255, 145, 63), + cv::Vec3b(147, 138, 129), + cv::Vec3b(87, 83, 41), + cv::Vec3b(0, 254, 207), + cv::Vec3b(176, 91, 111), + cv::Vec3b(140, 208, 255), + cv::Vec3b(59, 151, 0), + cv::Vec3b(4, 247, 87), + cv::Vec3b(200, 161, 161), + cv::Vec3b(30, 110, 0), + cv::Vec3b(121, 0, 215), + cv::Vec3b(167, 117, 0), + cv::Vec3b(99, 103, 169), + cv::Vec3b(160, 88, 55), + cv::Vec3b(107, 0, 44), + cv::Vec3b(119, 38, 0), + cv::Vec3b(215, 144, 255), + cv::Vec3b(155, 151, 0), + cv::Vec3b(84, 158, 121), + cv::Vec3b(255, 246, 159), + cv::Vec3b(32, 22, 37), + cv::Vec3b(114, 65, 143), + cv::Vec3b(188, 35, 255), + cv::Vec3b(153, 173, 192), + cv::Vec3b(58, 36, 101), + cv::Vec3b(146, 35, 41), + cv::Vec3b(91, 69, 52), + cv::Vec3b(253, 232, 220), + cv::Vec3b(64, 78, 85), + cv::Vec3b(0, 137, 163), + cv::Vec3b(203, 126, 152), + cv::Vec3b(164, 232, 4), + cv::Vec3b(50, 78, 114), + cv::Vec3b(106, 58, 76), + cv::Vec3b(131, 171, 88), + cv::Vec3b(0, 28, 30), + cv::Vec3b(209, 247, 206), + cv::Vec3b(0, 75, 40), + cv::Vec3b(200, 208, 246), + cv::Vec3b(163, 164, 137), + cv::Vec3b(128, 108, 102), + cv::Vec3b(34, 40, 0), + cv::Vec3b(191, 86, 80), + cv::Vec3b(232, 48, 0), + cv::Vec3b(102, 121, 109), + cv::Vec3b(218, 0, 124), + cv::Vec3b(255, 26, 89), + cv::Vec3b(138, 219, 180), + cv::Vec3b(30, 2, 0), + cv::Vec3b(91, 78, 81), + cv::Vec3b(200, 149, 197), + cv::Vec3b(50, 0, 51), + cv::Vec3b(255, 104, 50), + cv::Vec3b(102, 225, 211), + cv::Vec3b(207, 205, 172), + cv::Vec3b(208, 172, 148), + cv::Vec3b(126, 211, 121), + cv::Vec3b(1, 44, 88), + cv::Vec3b(122, 123, 255), + cv::Vec3b(214, 142, 1), + cv::Vec3b(53, 51, 57), + cv::Vec3b(120, 175, 161), + cv::Vec3b(254, 178, 198), + cv::Vec3b(117, 121, 124), + cv::Vec3b(131, 115, 147), + cv::Vec3b(148, 58, 77), + cv::Vec3b(181, 244, 255), + cv::Vec3b(210, 220, 213), + cv::Vec3b(149, 86, 189), + cv::Vec3b(106, 113, 74), + cv::Vec3b(0, 19, 37), + cv::Vec3b(2, 82, 95), + cv::Vec3b(10, 163, 247), + cv::Vec3b(233, 129, 118), + cv::Vec3b(219, 213, 221), + cv::Vec3b(94, 188, 209), + cv::Vec3b(61, 79, 68), + cv::Vec3b(126, 100, 5), + cv::Vec3b(2, 104, 78), + cv::Vec3b(150, 43, 117), + cv::Vec3b(141, 133, 70), + cv::Vec3b(150, 149, 197), + cv::Vec3b(231, 115, 206), + cv::Vec3b(216, 106, 120), + cv::Vec3b(62, 137, 190), + cv::Vec3b(202, 131, 78), + cv::Vec3b(81, 138, 135), + cv::Vec3b(91, 17, 60), + cv::Vec3b(85, 129, 59), + cv::Vec3b(231, 4, 196), + cv::Vec3b(0, 0, 95), + cv::Vec3b(169, 115, 153), + cv::Vec3b(75, 129, 96), + cv::Vec3b(89, 115, 138), + cv::Vec3b(255, 93, 167), + cv::Vec3b(247, 201, 191), + cv::Vec3b(100, 49, 39), + cv::Vec3b(81, 58, 1), + cv::Vec3b(107, 148, 170), + cv::Vec3b(81, 160, 88), + cv::Vec3b(164, 91, 2), + cv::Vec3b(29, 23, 2), + cv::Vec3b(226, 0, 39), + cv::Vec3b(231, 171, 99), + cv::Vec3b(76, 96, 1), + cv::Vec3b(156, 105, 102), + cv::Vec3b(100, 84, 123), + cv::Vec3b(151, 151, 158), + cv::Vec3b(0, 106, 102), + cv::Vec3b(57, 20, 6), + cv::Vec3b(244, 215, 73), + cv::Vec3b(0, 69, 210), + cv::Vec3b(0, 108, 49), + cv::Vec3b(221, 182, 208), + cv::Vec3b(124, 101, 113), + cv::Vec3b(159, 178, 164), + cv::Vec3b(0, 216, 145), + cv::Vec3b(21, 160, 138), + cv::Vec3b(188, 101, 233), + cv::Vec3b(255, 255, 254), + cv::Vec3b(198, 220, 153), + cv::Vec3b(32, 59, 60), + cv::Vec3b(103, 17, 144), + cv::Vec3b(107, 58, 100), + cv::Vec3b(245, 225, 255), + cv::Vec3b(255, 160, 242), + cv::Vec3b(204, 170, 53), + cv::Vec3b(55, 69, 39), + cv::Vec3b(139, 180, 0), + cv::Vec3b(121, 120, 104), + cv::Vec3b(198, 0, 90), + cv::Vec3b(59, 0, 10), + cv::Vec3b(200, 98, 64), + cv::Vec3b(41, 96, 124), + cv::Vec3b(64, 35, 52), + cv::Vec3b(125, 90, 68), + cv::Vec3b(204, 184, 124), + cv::Vec3b(184, 129, 131), + cv::Vec3b(170, 81, 153), + cv::Vec3b(181, 214, 195), + cv::Vec3b(163, 132, 105), + cv::Vec3b(159, 148, 240), + cv::Vec3b(167, 69, 113), + cv::Vec3b(184, 148, 166), + cv::Vec3b(113, 187, 140), + cv::Vec3b(0, 180, 51), + cv::Vec3b(120, 158, 201), + cv::Vec3b(109, 128, 186), + cv::Vec3b(149, 63, 0), + cv::Vec3b(94, 255, 3), + cv::Vec3b(228, 255, 252), + cv::Vec3b(27, 225, 119), + cv::Vec3b(188, 177, 229), + cv::Vec3b(118, 145, 47), + cv::Vec3b(0, 49, 9), + cv::Vec3b(0, 96, 205), + cv::Vec3b(210, 0, 150), + cv::Vec3b(137, 85, 99), + cv::Vec3b(41, 32, 29), + cv::Vec3b(91, 50, 19), + cv::Vec3b(167, 111, 66), + cv::Vec3b(137, 65, 46), + cv::Vec3b(26, 58, 42), + cv::Vec3b(73, 75, 90), + cv::Vec3b(168, 140, 133), + cv::Vec3b(244, 171, 170), + cv::Vec3b(163, 243, 171), + cv::Vec3b(0, 198, 200), + cv::Vec3b(234, 139, 102), + cv::Vec3b(149, 138, 159), + cv::Vec3b(189, 201, 210), + cv::Vec3b(159, 160, 100), + cv::Vec3b(190, 71, 0), + cv::Vec3b(101, 129, 136), + cv::Vec3b(131, 164, 133), + cv::Vec3b(69, 60, 35), + cv::Vec3b(71, 103, 93), + cv::Vec3b(58, 63, 0), + cv::Vec3b(6, 18, 3), + cv::Vec3b(223, 251, 113), + cv::Vec3b(134, 142, 126), + cv::Vec3b(152, 208, 88), + cv::Vec3b(108, 143, 125), + cv::Vec3b(215, 191, 194), + cv::Vec3b(60, 62, 110), + cv::Vec3b(216, 61, 102), + cv::Vec3b(47, 93, 155), + cv::Vec3b(108, 94, 70), + cv::Vec3b(210, 91, 136), + cv::Vec3b(91, 101, 108), + cv::Vec3b(0, 181, 127), + cv::Vec3b(84, 92, 70), + cv::Vec3b(134, 96, 151), + cv::Vec3b(54, 93, 37), + cv::Vec3b(37, 47, 153), + cv::Vec3b(0, 204, 255), + cv::Vec3b(103, 78, 96), + cv::Vec3b(252, 0, 156), + cv::Vec3b(146, 137, 107), + cv::Vec3b(30, 35, 36), + cv::Vec3b(222, 201, 178), + cv::Vec3b(157, 73, 72), + cv::Vec3b(133, 171, 180), + cv::Vec3b(52, 33, 66), + cv::Vec3b(208, 150, 133), + cv::Vec3b(164, 172, 172), + cv::Vec3b(0, 255, 255), + cv::Vec3b(174, 156, 134), + cv::Vec3b(116, 42, 51), + cv::Vec3b(14, 114, 197), + cv::Vec3b(175, 216, 236), + cv::Vec3b(192, 100, 185), + cv::Vec3b(145, 2, 140), + cv::Vec3b(254, 237, 191), + cv::Vec3b(255, 183, 137), + cv::Vec3b(156, 184, 228), + cv::Vec3b(175, 255, 209), + cv::Vec3b(42, 54, 76), + cv::Vec3b(79, 74, 67), + cv::Vec3b(100, 112, 149), + cv::Vec3b(52, 187, 255), + cv::Vec3b(128, 119, 129), + cv::Vec3b(146, 0, 3), + cv::Vec3b(179, 165, 167), + cv::Vec3b(1, 134, 21), + cv::Vec3b(241, 255, 200), + cv::Vec3b(151, 111, 92), + cv::Vec3b(255, 59, 193), + cv::Vec3b(255, 95, 107), + cv::Vec3b(7, 125, 132), + cv::Vec3b(245, 109, 147), + cv::Vec3b(87, 113, 218), + cv::Vec3b(78, 30, 42), + cv::Vec3b(131, 0, 85), + cv::Vec3b(2, 211, 70), + cv::Vec3b(190, 69, 45), + cv::Vec3b(0, 144, 94), + cv::Vec3b(190, 0, 40), + cv::Vec3b(110, 150, 227), + cv::Vec3b(0, 118, 153), + cv::Vec3b(254, 201, 109), + cv::Vec3b(156, 106, 125), + cv::Vec3b(63, 161, 184), + cv::Vec3b(137, 61, 227), + cv::Vec3b(121, 180, 214), + cv::Vec3b(127, 212, 217), + cv::Vec3b(103, 81, 187), + cv::Vec3b(178, 141, 45), + cv::Vec3b(226, 122, 5), + cv::Vec3b(221, 156, 184), + cv::Vec3b(170, 188, 122), + cv::Vec3b(152, 0, 52), + cv::Vec3b(86, 26, 2), + cv::Vec3b(143, 127, 0), + cv::Vec3b(99, 80, 0), + cv::Vec3b(205, 125, 174), + cv::Vec3b(138, 94, 45), + cv::Vec3b(255, 179, 225), + cv::Vec3b(107, 100, 102), + cv::Vec3b(198, 211, 0), + cv::Vec3b(1, 0, 226), + cv::Vec3b(136, 236, 105), + cv::Vec3b(143, 204, 190), + cv::Vec3b(33, 0, 28), + cv::Vec3b(81, 31, 77), + cv::Vec3b(227, 246, 227), + cv::Vec3b(255, 142, 177), + cv::Vec3b(107, 79, 41), + cv::Vec3b(163, 127, 70), + cv::Vec3b(106, 89, 80), + cv::Vec3b(31, 42, 26), + cv::Vec3b(4, 120, 77), + cv::Vec3b(16, 24, 53), + cv::Vec3b(230, 224, 208), + cv::Vec3b(255, 116, 254), + cv::Vec3b(0, 164, 95), + cv::Vec3b(143, 93, 248), + cv::Vec3b(75, 0, 89), + cv::Vec3b(65, 47, 35), + cv::Vec3b(216, 147, 158), + cv::Vec3b(219, 157, 114), + cv::Vec3b(96, 65, 67), + cv::Vec3b(181, 186, 206), + cv::Vec3b(152, 158, 183), + cv::Vec3b(210, 196, 219), + cv::Vec3b(165, 135, 175), + cv::Vec3b(119, 215, 150), + cv::Vec3b(127, 140, 148), + cv::Vec3b(255, 155, 3), + cv::Vec3b(85, 81, 150), + cv::Vec3b(49, 221, 174), + cv::Vec3b(116, 182, 113), + cv::Vec3b(128, 38, 71), + cv::Vec3b(42, 55, 63), + cv::Vec3b(1, 74, 104), + cv::Vec3b(105, 102, 40), + cv::Vec3b(76, 123, 109), + cv::Vec3b(0, 44, 39), + cv::Vec3b(122, 69, 34), + cv::Vec3b(59, 88, 89), + cv::Vec3b(229, 211, 129), + cv::Vec3b(255, 243, 255), + cv::Vec3b(103, 159, 160), + cv::Vec3b(38, 19, 0), + cv::Vec3b(44, 87, 66), + cv::Vec3b(145, 49, 175), + cv::Vec3b(175, 93, 136), + cv::Vec3b(199, 112, 106), + cv::Vec3b(97, 171, 31), + cv::Vec3b(140, 242, 212), + cv::Vec3b(197, 217, 184), + cv::Vec3b(159, 255, 251), + cv::Vec3b(191, 69, 204), + cv::Vec3b(73, 57, 65), + cv::Vec3b(134, 59, 96), + cv::Vec3b(185, 0, 118), + cv::Vec3b(0, 49, 119), + cv::Vec3b(197, 130, 210), + cv::Vec3b(193, 179, 148), + cv::Vec3b(96, 43, 112), + cv::Vec3b(136, 120, 104), + cv::Vec3b(186, 191, 176), + cv::Vec3b(3, 0, 18), + cv::Vec3b(209, 172, 254), + cv::Vec3b(127, 222, 254), + cv::Vec3b(75, 92, 113), + cv::Vec3b(163, 160, 151), + cv::Vec3b(230, 109, 83), + cv::Vec3b(99, 123, 93), + cv::Vec3b(146, 190, 165), + cv::Vec3b(0, 248, 179), + cv::Vec3b(190, 221, 255), + cv::Vec3b(61, 181, 167), + cv::Vec3b(221, 50, 72), + cv::Vec3b(182, 228, 222), + cv::Vec3b(66, 119, 69), + cv::Vec3b(89, 140, 90), + cv::Vec3b(185, 76, 89), + cv::Vec3b(129, 129, 213), + cv::Vec3b(148, 136, 139), + cv::Vec3b(254, 214, 189), + cv::Vec3b(83, 109, 49), + cv::Vec3b(110, 255, 146), + cv::Vec3b(228, 232, 255), + cv::Vec3b(32, 226, 0), + cv::Vec3b(255, 208, 242), + cv::Vec3b(76, 131, 161), + cv::Vec3b(189, 115, 34), + cv::Vec3b(145, 92, 78), + cv::Vec3b(140, 71, 135), + cv::Vec3b(2, 81, 23), + cv::Vec3b(162, 170, 69), + cv::Vec3b(45, 27, 33), + cv::Vec3b(169, 221, 176), + cv::Vec3b(255, 79, 120), + cv::Vec3b(82, 133, 0), + cv::Vec3b(0, 154, 46), + cv::Vec3b(23, 252, 228), + cv::Vec3b(113, 85, 90), + cv::Vec3b(82, 93, 130), + cv::Vec3b(0, 25, 90), + cv::Vec3b(150, 120, 116), + cv::Vec3b(85, 85, 88), + cv::Vec3b(11, 33, 44), + cv::Vec3b(30, 32, 43), + cv::Vec3b(239, 191, 196), + cv::Vec3b(111, 151, 85), + cv::Vec3b(111, 117, 134), + cv::Vec3b(80, 29, 29), + cv::Vec3b(55, 45, 0), + cv::Vec3b(116, 29, 22), + cv::Vec3b(94, 179, 147), + cv::Vec3b(181, 180, 0), + cv::Vec3b(221, 74, 56), + cv::Vec3b(54, 61, 255), + cv::Vec3b(173, 101, 82), + cv::Vec3b(102, 53, 175), + cv::Vec3b(131, 107, 186), + cv::Vec3b(152, 170, 127), + cv::Vec3b(70, 72, 54), + cv::Vec3b(50, 44, 62), + cv::Vec3b(124, 185, 186), + cv::Vec3b(91, 105, 101), + cv::Vec3b(112, 125, 61), + cv::Vec3b(122, 0, 29), + cv::Vec3b(110, 70, 54), + cv::Vec3b(68, 58, 56), + cv::Vec3b(174, 129, 255), + cv::Vec3b(72, 144, 121), + cv::Vec3b(137, 115, 52), + cv::Vec3b(0, 144, 135), + cv::Vec3b(218, 113, 60), + cv::Vec3b(54, 22, 24), + cv::Vec3b(255, 111, 1), + cv::Vec3b(0, 102, 121), + cv::Vec3b(55, 14, 119), + cv::Vec3b(75, 58, 131), + cv::Vec3b(201, 226, 230), + cv::Vec3b(196, 65, 112), + cv::Vec3b(255, 69, 38), + cv::Vec3b(115, 190, 84), + cv::Vec3b(196, 223, 114), + cv::Vec3b(173, 255, 96), + cv::Vec3b(0, 68, 125), + cv::Vec3b(220, 206, 201), + cv::Vec3b(189, 148, 121), + cv::Vec3b(101, 110, 91), + cv::Vec3b(236, 82, 0), + cv::Vec3b(255, 110, 194), + cv::Vec3b(122, 97, 126), + cv::Vec3b(221, 174, 162), + cv::Vec3b(119, 131, 127), + cv::Vec3b(165, 51, 39), + cv::Vec3b(96, 142, 255), + cv::Vec3b(181, 153, 215), + cv::Vec3b(165, 1, 73), + cv::Vec3b(78, 0, 37), + cv::Vec3b(201, 177, 169), + cv::Vec3b(3, 145, 154), + cv::Vec3b(27, 42, 37), + cv::Vec3b(229, 0, 241), + cv::Vec3b(152, 46, 11), + cv::Vec3b(182, 113, 128), + cv::Vec3b(224, 88, 89), + cv::Vec3b(0, 96, 57), + cv::Vec3b(87, 143, 155), + cv::Vec3b(48, 82, 48), + cv::Vec3b(206, 147, 76), + cv::Vec3b(179, 194, 190), + cv::Vec3b(192, 186, 192), + cv::Vec3b(181, 6, 211), + cv::Vec3b(23, 12, 16), + cv::Vec3b(76, 83, 79), + cv::Vec3b(34, 68, 81), + cv::Vec3b(62, 65, 65), + cv::Vec3b(120, 114, 109), + cv::Vec3b(182, 96, 43), + cv::Vec3b(32, 4, 65), + cv::Vec3b(221, 181, 136), + cv::Vec3b(73, 114, 0), + cv::Vec3b(197, 170, 182), + cv::Vec3b(3, 60, 97), + cv::Vec3b(113, 178, 245), + cv::Vec3b(169, 224, 136), + cv::Vec3b(73, 121, 176), + cv::Vec3b(162, 195, 223), + cv::Vec3b(120, 65, 73), + cv::Vec3b(45, 43, 23), + cv::Vec3b(62, 14, 47), + cv::Vec3b(87, 52, 76), + cv::Vec3b(0, 145, 190), + cv::Vec3b(228, 81, 209), + cv::Vec3b(75, 75, 106), + cv::Vec3b(92, 1, 26), + cv::Vec3b(124, 128, 96), + cv::Vec3b(255, 148, 145), + cv::Vec3b(76, 50, 93), + cv::Vec3b(0, 92, 139), + cv::Vec3b(229, 253, 164), + cv::Vec3b(104, 209, 182), + cv::Vec3b(3, 38, 65), + cv::Vec3b(20, 0, 35), + cv::Vec3b(134, 131, 169), + cv::Vec3b(207, 255, 0), + cv::Vec3b(167, 44, 62), + cv::Vec3b(52, 71, 90), + cv::Vec3b(177, 187, 154), + cv::Vec3b(180, 160, 79), + cv::Vec3b(141, 145, 142), + cv::Vec3b(161, 104, 166), + cv::Vec3b(129, 61, 58), + cv::Vec3b(66, 82, 24), + cv::Vec3b(218, 131, 134), + cv::Vec3b(119, 97, 51), + cv::Vec3b(86, 57, 48), + cv::Vec3b(132, 152, 174), + cv::Vec3b(144, 193, 211), + cv::Vec3b(181, 102, 107), + cv::Vec3b(155, 88, 94), + cv::Vec3b(133, 100, 101), + cv::Vec3b(173, 124, 144), + cv::Vec3b(226, 188, 0), + cv::Vec3b(227, 170, 224), + cv::Vec3b(178, 194, 254), + cv::Vec3b(253, 0, 57), + cv::Vec3b(0, 155, 117), + cv::Vec3b(255, 244, 109), + cv::Vec3b(232, 126, 172), + cv::Vec3b(223, 227, 230), + cv::Vec3b(132, 133, 144), + cv::Vec3b(170, 146, 151), + cv::Vec3b(131, 161, 147), + cv::Vec3b(87, 121, 119), + cv::Vec3b(62, 113, 88), + cv::Vec3b(198, 66, 137), + cv::Vec3b(234, 0, 114), + cv::Vec3b(196, 168, 203), + cv::Vec3b(85, 200, 153), + cv::Vec3b(231, 143, 207), + cv::Vec3b(0, 69, 71), + cv::Vec3b(246, 226, 227), + cv::Vec3b(150, 103, 22), + cv::Vec3b(55, 143, 219), + cv::Vec3b(67, 94, 106), + cv::Vec3b(218, 0, 4), + cv::Vec3b(27, 0, 15), + cv::Vec3b(91, 156, 143), + cv::Vec3b(110, 43, 82), + cv::Vec3b(1, 17, 21), + cv::Vec3b(227, 232, 196), + cv::Vec3b(174, 59, 133), + cv::Vec3b(234, 28, 169), + cv::Vec3b(255, 158, 107), + cv::Vec3b(69, 125, 139), + cv::Vec3b(146, 103, 139), + cv::Vec3b(0, 205, 187), + cv::Vec3b(156, 204, 4), + cv::Vec3b(0, 46, 56), + cv::Vec3b(150, 197, 127), + cv::Vec3b(207, 246, 180), + cv::Vec3b(73, 40, 24), + cv::Vec3b(118, 110, 82), + cv::Vec3b(32, 55, 14), + cv::Vec3b(227, 209, 159), + cv::Vec3b(46, 60, 48), + cv::Vec3b(178, 234, 206), + cv::Vec3b(243, 189, 164), + cv::Vec3b(162, 78, 61), + cv::Vec3b(151, 111, 217), + cv::Vec3b(140, 159, 168), + cv::Vec3b(124, 43, 115), + cv::Vec3b(78, 95, 55), + cv::Vec3b(93, 84, 98), + cv::Vec3b(144, 149, 111), + cv::Vec3b(106, 167, 118), + cv::Vec3b(219, 203, 246), + cv::Vec3b(218, 113, 255), + cv::Vec3b(152, 124, 149), + cv::Vec3b(82, 50, 60), + cv::Vec3b(187, 60, 66), + cv::Vec3b(88, 77, 57), + cv::Vec3b(79, 193, 95), + cv::Vec3b(162, 185, 193), + cv::Vec3b(121, 219, 33), + cv::Vec3b(29, 89, 88), + cv::Vec3b(189, 116, 78), + cv::Vec3b(22, 11, 0), + cv::Vec3b(32, 34, 26), + cv::Vec3b(107, 130, 149), + cv::Vec3b(0, 224, 228), + cv::Vec3b(16, 36, 1), + cv::Vec3b(27, 120, 42), + cv::Vec3b(218, 169, 181), + cv::Vec3b(176, 65, 93), + cv::Vec3b(133, 146, 83), + cv::Vec3b(151, 160, 148), + cv::Vec3b(6, 227, 196), + cv::Vec3b(71, 104, 140), + cv::Vec3b(124, 103, 85), + cv::Vec3b(7, 92, 0), + cv::Vec3b(117, 96, 213), + cv::Vec3b(125, 159, 0), + cv::Vec3b(195, 109, 150), + cv::Vec3b(77, 145, 62), + cv::Vec3b(95, 66, 118), + cv::Vec3b(252, 228, 200), + cv::Vec3b(48, 48, 82), + cv::Vec3b(79, 56, 27), + cv::Vec3b(229, 165, 50), + cv::Vec3b(112, 102, 144), + cv::Vec3b(170, 154, 146), + cv::Vec3b(35, 115, 99), + cv::Vec3b(115, 1, 62), + cv::Vec3b(255, 144, 121), + cv::Vec3b(167, 154, 116), + cv::Vec3b(2, 155, 219), + cv::Vec3b(255, 1, 105), + cv::Vec3b(199, 210, 231), + cv::Vec3b(202, 136, 105), + cv::Vec3b(128, 255, 205), + cv::Vec3b(187, 31, 105), + cv::Vec3b(144, 176, 171), + cv::Vec3b(125, 116, 169), + cv::Vec3b(252, 199, 219), + cv::Vec3b(153, 55, 91), + cv::Vec3b(0, 171, 77), + cv::Vec3b(171, 174, 209), + cv::Vec3b(190, 157, 145), + cv::Vec3b(230, 229, 167), + cv::Vec3b(51, 44, 34), + cv::Vec3b(221, 88, 123), + cv::Vec3b(245, 255, 247), + cv::Vec3b(93, 48, 51), + cv::Vec3b(109, 56, 0), + cv::Vec3b(255, 0, 32), + cv::Vec3b(181, 123, 179), + cv::Vec3b(215, 255, 230), + cv::Vec3b(197, 53, 169), + cv::Vec3b(38, 0, 9), + cv::Vec3b(106, 135, 129), + cv::Vec3b(168, 171, 180), + cv::Vec3b(212, 82, 98), + cv::Vec3b(121, 75, 97), + cv::Vec3b(70, 33, 178), + cv::Vec3b(141, 164, 219), + cv::Vec3b(199, 200, 144), + cv::Vec3b(111, 233, 173), + cv::Vec3b(162, 67, 167), + cv::Vec3b(178, 176, 129), + cv::Vec3b(24, 27, 0), + cv::Vec3b(40, 97, 84), + cv::Vec3b(76, 164, 59), + cv::Vec3b(106, 149, 115), + cv::Vec3b(168, 68, 29), + cv::Vec3b(92, 114, 123), + cv::Vec3b(115, 134, 113), + cv::Vec3b(208, 207, 203), + cv::Vec3b(137, 123, 119), + cv::Vec3b(31, 63, 34), + cv::Vec3b(65, 69, 167), + cv::Vec3b(218, 152, 148), + cv::Vec3b(161, 117, 122), + cv::Vec3b(99, 36, 60), + cv::Vec3b(173, 170, 255), + cv::Vec3b(0, 205, 226), + cv::Vec3b(221, 188, 98), + cv::Vec3b(105, 142, 177), + cv::Vec3b(32, 132, 98), + cv::Vec3b(0, 183, 224), + cv::Vec3b(97, 74, 68), + cv::Vec3b(155, 187, 87), + cv::Vec3b(122, 92, 84), + cv::Vec3b(133, 122, 80), + cv::Vec3b(118, 107, 126), + cv::Vec3b(1, 72, 51), + cv::Vec3b(255, 131, 71), + cv::Vec3b(122, 142, 186), + cv::Vec3b(39, 71, 64), + cv::Vec3b(148, 100, 68), + cv::Vec3b(235, 216, 230), + cv::Vec3b(100, 98, 65), + cv::Vec3b(55, 57, 23), + cv::Vec3b(106, 212, 80), + cv::Vec3b(129, 129, 123), + cv::Vec3b(212, 153, 227), + cv::Vec3b(151, 148, 64), + cv::Vec3b(1, 26, 18), + cv::Vec3b(82, 101, 84), + cv::Vec3b(181, 136, 92), + cv::Vec3b(164, 153, 165), + cv::Vec3b(3, 173, 137), + cv::Vec3b(179, 0, 139), + cv::Vec3b(227, 196, 181), + cv::Vec3b(150, 83, 31), + cv::Vec3b(134, 113, 117), + cv::Vec3b(116, 86, 158), + cv::Vec3b(97, 125, 159), + cv::Vec3b(231, 4, 82), + cv::Vec3b(6, 126, 175), + cv::Vec3b(166, 151, 182), + cv::Vec3b(183, 135, 168), + cv::Vec3b(156, 255, 147), + cv::Vec3b(49, 29, 25), + cv::Vec3b(58, 148, 89), + cv::Vec3b(110, 116, 110), + cv::Vec3b(176, 197, 174), + cv::Vec3b(132, 237, 247), + cv::Vec3b(237, 52, 136), + cv::Vec3b(117, 76, 120), + cv::Vec3b(56, 70, 68), + cv::Vec3b(199, 132, 123), + cv::Vec3b(0, 182, 197), + cv::Vec3b(127, 166, 112), + cv::Vec3b(193, 175, 158), + cv::Vec3b(42, 127, 255), + cv::Vec3b(114, 165, 140), + cv::Vec3b(255, 192, 127), + cv::Vec3b(157, 235, 221), + cv::Vec3b(217, 124, 142), + cv::Vec3b(126, 124, 147), + cv::Vec3b(98, 230, 116), + cv::Vec3b(181, 99, 158), + cv::Vec3b(255, 168, 97), + cv::Vec3b(194, 165, 128), + cv::Vec3b(141, 156, 131), + cv::Vec3b(183, 5, 70), + cv::Vec3b(55, 43, 46), + cv::Vec3b(0, 152, 255), + cv::Vec3b(152, 89, 117), + cv::Vec3b(32, 32, 76), + cv::Vec3b(255, 108, 96), + cv::Vec3b(68, 80, 131), + cv::Vec3b(133, 2, 170), + cv::Vec3b(114, 54, 31), + cv::Vec3b(150, 118, 163), + cv::Vec3b(72, 68, 73), + cv::Vec3b(206, 214, 194), + cv::Vec3b(59, 22, 74), + cv::Vec3b(204, 167, 99), + cv::Vec3b(44, 127, 119), + cv::Vec3b(2, 34, 123), + cv::Vec3b(163, 126, 111), + cv::Vec3b(205, 230, 220), + cv::Vec3b(205, 255, 251), + cv::Vec3b(190, 129, 26), + cv::Vec3b(247, 113, 131), + cv::Vec3b(237, 230, 226), + cv::Vec3b(205, 198, 180), + cv::Vec3b(255, 224, 158), + cv::Vec3b(58, 114, 113), + cv::Vec3b(255, 123, 89), + cv::Vec3b(78, 78, 1), + cv::Vec3b(74, 198, 132), + cv::Vec3b(139, 200, 145), + cv::Vec3b(188, 138, 150), + cv::Vec3b(207, 99, 83), + cv::Vec3b(220, 222, 92), + cv::Vec3b(94, 170, 221), + cv::Vec3b(246, 160, 173), + cv::Vec3b(226, 105, 170), + cv::Vec3b(163, 218, 228), + cv::Vec3b(67, 110, 131), + cv::Vec3b(0, 46, 23), + cv::Vec3b(236, 251, 255), + cv::Vec3b(161, 194, 182), + cv::Vec3b(80, 0, 63), + cv::Vec3b(113, 105, 91), + cv::Vec3b(103, 196, 187), + cv::Vec3b(83, 110, 255), + cv::Vec3b(93, 90, 72), + cv::Vec3b(137, 0, 57), + cv::Vec3b(150, 147, 129), + cv::Vec3b(55, 21, 33), + cv::Vec3b(94, 70, 101), + cv::Vec3b(170, 98, 195), + cv::Vec3b(141, 111, 129), + cv::Vec3b(44, 97, 53), + cv::Vec3b(65, 6, 1), + cv::Vec3b(86, 70, 32), + cv::Vec3b(230, 144, 52), + cv::Vec3b(109, 166, 189), + cv::Vec3b(229, 142, 86), + cv::Vec3b(227, 166, 139), + cv::Vec3b(72, 177, 118), + cv::Vec3b(210, 125, 103), + cv::Vec3b(181, 178, 104), + cv::Vec3b(127, 132, 39), + cv::Vec3b(255, 132, 230), + cv::Vec3b(67, 87, 64), + cv::Vec3b(234, 228, 8), + cv::Vec3b(244, 245, 255), + cv::Vec3b(50, 88, 0), + cv::Vec3b(75, 107, 165), + cv::Vec3b(173, 206, 255), + cv::Vec3b(155, 138, 204), + cv::Vec3b(136, 81, 56), + cv::Vec3b(88, 117, 193), + cv::Vec3b(126, 115, 17), + cv::Vec3b(254, 165, 202), + cv::Vec3b(159, 139, 91), + cv::Vec3b(165, 91, 84), + cv::Vec3b(137, 0, 106), + cv::Vec3b(175, 117, 111), + cv::Vec3b(42, 32, 0), + cv::Vec3b(116, 153, 161), + cv::Vec3b(255, 181, 80), + cv::Vec3b(0, 1, 30), + cv::Vec3b(209, 81, 28), + cv::Vec3b(104, 129, 81), + cv::Vec3b(188, 144, 138), + cv::Vec3b(120, 200, 235), + cv::Vec3b(133, 2, 255), + cv::Vec3b(72, 61, 48), + cv::Vec3b(196, 34, 33), + cv::Vec3b(94, 167, 255), + cv::Vec3b(120, 87, 21), + cv::Vec3b(12, 234, 145), + cv::Vec3b(255, 250, 237), + cv::Vec3b(179, 175, 157), + cv::Vec3b(62, 61, 82), + cv::Vec3b(90, 155, 194), + cv::Vec3b(156, 47, 144), + cv::Vec3b(141, 87, 0), + cv::Vec3b(173, 215, 156), + cv::Vec3b(0, 118, 139), + cv::Vec3b(51, 125, 0), + cv::Vec3b(197, 151, 0), + cv::Vec3b(49, 86, 220), + cv::Vec3b(148, 69, 117), + cv::Vec3b(236, 255, 220), + cv::Vec3b(210, 76, 178), + cv::Vec3b(151, 112, 60), + cv::Vec3b(76, 37, 127), + cv::Vec3b(158, 3, 102), + cv::Vec3b(136, 255, 236), + cv::Vec3b(181, 100, 129), + cv::Vec3b(57, 109, 43), + cv::Vec3b(86, 115, 95), + cv::Vec3b(152, 131, 118), + cv::Vec3b(155, 177, 149), + cv::Vec3b(169, 121, 92), + cv::Vec3b(228, 197, 211), + cv::Vec3b(159, 79, 103), + cv::Vec3b(30, 43, 57), + cv::Vec3b(102, 67, 39), + cv::Vec3b(175, 206, 120), + cv::Vec3b(50, 46, 223), + cv::Vec3b(134, 180, 135), + cv::Vec3b(194, 48, 0), + cv::Vec3b(171, 232, 107), + cv::Vec3b(150, 101, 109), + cv::Vec3b(37, 14, 53), + cv::Vec3b(166, 0, 25), + cv::Vec3b(0, 128, 207), + cv::Vec3b(202, 239, 255), + cv::Vec3b(50, 63, 97), + cv::Vec3b(164, 73, 220), + cv::Vec3b(106, 157, 59), + cv::Vec3b(255, 90, 228), + cv::Vec3b(99, 106, 1), + cv::Vec3b(209, 108, 218), + cv::Vec3b(115, 96, 96), + cv::Vec3b(255, 186, 173), + cv::Vec3b(211, 105, 180), + cv::Vec3b(255, 222, 214), + cv::Vec3b(108, 109, 116), + cv::Vec3b(146, 125, 94), + cv::Vec3b(132, 93, 112), + cv::Vec3b(91, 98, 193), + cv::Vec3b(47, 74, 54), + cv::Vec3b(228, 95, 53), + cv::Vec3b(255, 59, 83), + cv::Vec3b(172, 132, 221), + cv::Vec3b(118, 41, 136), + cv::Vec3b(112, 236, 152), + cv::Vec3b(64, 133, 67), + cv::Vec3b(44, 53, 51), + cv::Vec3b(46, 24, 45), + cv::Vec3b(50, 57, 37), + cv::Vec3b(25, 24, 27), + cv::Vec3b(47, 46, 44), + cv::Vec3b(2, 60, 50), + cv::Vec3b(155, 158, 226), + cv::Vec3b(88, 175, 173), + cv::Vec3b(92, 66, 77), + cv::Vec3b(122, 197, 166), + cv::Vec3b(104, 93, 117), + cv::Vec3b(185, 188, 189), + cv::Vec3b(131, 67, 87), + cv::Vec3b(26, 123, 66), + cv::Vec3b(46, 87, 170), + cv::Vec3b(229, 81, 153), + cv::Vec3b(49, 110, 71), + cv::Vec3b(205, 0, 197), + cv::Vec3b(106, 0, 77), + cv::Vec3b(127, 187, 236), + cv::Vec3b(243, 86, 145), + cv::Vec3b(215, 197, 74), + cv::Vec3b(98, 172, 183), + cv::Vec3b(203, 161, 188), + cv::Vec3b(162, 138, 154), + cv::Vec3b(108, 63, 59), + cv::Vec3b(255, 228, 125), + cv::Vec3b(220, 186, 227), + cv::Vec3b(95, 129, 109), + cv::Vec3b(58, 64, 74), + cv::Vec3b(125, 191, 50), + cv::Vec3b(230, 236, 220), + cv::Vec3b(133, 44, 25), + cv::Vec3b(40, 83, 102), + cv::Vec3b(184, 203, 156), + cv::Vec3b(14, 13, 0), + cv::Vec3b(75, 93, 86), + cv::Vec3b(107, 84, 63), + cv::Vec3b(226, 113, 114), + cv::Vec3b(5, 104, 236), + cv::Vec3b(46, 181, 0), + cv::Vec3b(210, 22, 86), + cv::Vec3b(239, 175, 255), + cv::Vec3b(104, 32, 33), + cv::Vec3b(45, 32, 17), + cv::Vec3b(218, 76, 255), + cv::Vec3b(112, 150, 142), + cv::Vec3b(255, 123, 125), + cv::Vec3b(74, 25, 48), + cv::Vec3b(232, 194, 130), + cv::Vec3b(231, 219, 188), + cv::Vec3b(166, 132, 134), + cv::Vec3b(31, 38, 60), + cv::Vec3b(54, 87, 78), + cv::Vec3b(82, 206, 121), + cv::Vec3b(173, 170, 169), + cv::Vec3b(138, 159, 69), + cv::Vec3b(101, 66, 210), + cv::Vec3b(0, 251, 140), + cv::Vec3b(93, 105, 123), + cv::Vec3b(204, 210, 127), + cv::Vec3b(148, 165, 161), + cv::Vec3b(121, 2, 41), + cv::Vec3b(227, 131, 230), + cv::Vec3b(126, 164, 193), + cv::Vec3b(78, 68, 82), + cv::Vec3b(75, 44, 0), + cv::Vec3b(98, 11, 112), + cv::Vec3b(49, 76, 30), + cv::Vec3b(135, 74, 166), + cv::Vec3b(227, 0, 145), + cv::Vec3b(102, 70, 10), + cv::Vec3b(235, 154, 139), + cv::Vec3b(234, 195, 163), + cv::Vec3b(152, 234, 179), + cv::Vec3b(171, 145, 128), + cv::Vec3b(184, 85, 47), + cv::Vec3b(26, 43, 47), + cv::Vec3b(148, 221, 197), + cv::Vec3b(157, 140, 118), + cv::Vec3b(156, 131, 51), + cv::Vec3b(148, 169, 201), + cv::Vec3b(57, 41, 53), + cv::Vec3b(140, 103, 94), + cv::Vec3b(204, 233, 58), + cv::Vec3b(145, 113, 0), + cv::Vec3b(1, 64, 11), + cv::Vec3b(68, 152, 150), + cv::Vec3b(28, 163, 112), + cv::Vec3b(224, 141, 167), + cv::Vec3b(139, 74, 78), + cv::Vec3b(102, 119, 118), + cv::Vec3b(70, 146, 173), + cv::Vec3b(103, 189, 168), + cv::Vec3b(105, 37, 92), + cv::Vec3b(211, 191, 255), + cv::Vec3b(74, 81, 50), + cv::Vec3b(126, 146, 133), + cv::Vec3b(119, 115, 60), + cv::Vec3b(231, 160, 204), + cv::Vec3b(81, 162, 136), + cv::Vec3b(44, 101, 106), + cv::Vec3b(77, 92, 94), + cv::Vec3b(201, 64, 58), + cv::Vec3b(221, 215, 243), + cv::Vec3b(0, 88, 68), + cv::Vec3b(180, 162, 0), + cv::Vec3b(72, 143, 105), + cv::Vec3b(133, 129, 130), + cv::Vec3b(212, 233, 185), + cv::Vec3b(61, 115, 151), + cv::Vec3b(202, 232, 206), + cv::Vec3b(214, 0, 52), + cv::Vec3b(170, 103, 70), + cv::Vec3b(158, 85, 133), + cv::Vec3b(186, 98, 0) +}; + +#endif // COLORS_H diff --git a/config.json b/config.json new file mode 100644 index 0000000..88f9fc8 --- /dev/null +++ b/config.json @@ -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" + ] + } +} diff --git a/controller.cpp b/controller.cpp new file mode 100644 index 0000000..4ef22aa --- /dev/null +++ b/controller.cpp @@ -0,0 +1,452 @@ +#include "controller.h" +#include +#include +#include +#include +#include +#include +#include +#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 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>& contours = rateProc.getContours(); + vector rects(contours.size()); + for (unsigned int i = 0; i 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(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( + chrono::steady_clock::now() - start); + cout << "Detection took " << (float)duration.count()/1000.0 + << " seconds." << endl; + return success; +} + +bool Controller::matchNuts(const vector& cur_results) { + auto start = chrono::steady_clock::now(); + vector min_dists = vector(nuts.size()); + int count = 0; + for(const auto& nut : nuts) { + vector x_dist = vector(cur_results.size()); + for (unsigned int i = 0; i y_dist = vector(cur_results.size()); + for (unsigned int i = 0; i dist = vector(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(cur_img.cols), 2) + + pow(static_cast(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::steady_clock::now() - start); + cout << "Matching took " << (float)duration.count()/1000.0 + << " seconds." << endl; + return success; +} + +void Controller::extractNuts(vector& 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_count); + for (unsigned int i = 0; i(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(i, j)-2; + if (index >= 0 && index < nut_count) { + result_nuts[index].img.at(i - result_nuts[index].min_x, + j - result_nuts[index].min_y) = cur_img.at(i, j); + } + } + } + auto duration = chrono::duration_cast( + 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 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; +} diff --git a/controller.h b/controller.h new file mode 100644 index 0000000..fd00bd5 --- /dev/null +++ b/controller.h @@ -0,0 +1,103 @@ +#ifndef CONTROLLER_H +#define CONTROLLER_H + +#include "detectionprocessing.h" +#include "rateprocessing.h" +#include "neuralnet.h" +#include +#include +#include +#include +#include +#include + +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& 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& 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 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 diff --git a/cvimagewidget.h b/cvimagewidget.h new file mode 100644 index 0000000..7d27f09 --- /dev/null +++ b/cvimagewidget.h @@ -0,0 +1,123 @@ +#ifndef CVIMAGEWIDGET_H +#define CVIMAGEWIDGET_H +#include +#include +#include +#include +#include +#include + +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(event->size().height())/ + static_cast(_tmp.rows); + } + else { + fac = static_cast(event->size().width())/ + static_cast(_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 diff --git a/data/imgs/.gitignore b/data/imgs/.gitignore new file mode 100644 index 0000000..b5a379d --- /dev/null +++ b/data/imgs/.gitignore @@ -0,0 +1 @@ +!*.jpg diff --git a/data/imgs/nut_0.jpg b/data/imgs/nut_0.jpg new file mode 100644 index 0000000..d412a0d Binary files /dev/null and b/data/imgs/nut_0.jpg differ diff --git a/data/imgs/nut_1.jpg b/data/imgs/nut_1.jpg new file mode 100644 index 0000000..21870b0 Binary files /dev/null and b/data/imgs/nut_1.jpg differ diff --git a/data/imgs/nut_10.jpg b/data/imgs/nut_10.jpg new file mode 100644 index 0000000..39e8310 Binary files /dev/null and b/data/imgs/nut_10.jpg differ diff --git a/data/imgs/nut_11.jpg b/data/imgs/nut_11.jpg new file mode 100644 index 0000000..70bf1b9 Binary files /dev/null and b/data/imgs/nut_11.jpg differ diff --git a/data/imgs/nut_12.jpg b/data/imgs/nut_12.jpg new file mode 100644 index 0000000..a5f27a0 Binary files /dev/null and b/data/imgs/nut_12.jpg differ diff --git a/data/imgs/nut_13.jpg b/data/imgs/nut_13.jpg new file mode 100644 index 0000000..fc460c7 Binary files /dev/null and b/data/imgs/nut_13.jpg differ diff --git a/data/imgs/nut_14.jpg b/data/imgs/nut_14.jpg new file mode 100644 index 0000000..073083b Binary files /dev/null and b/data/imgs/nut_14.jpg differ diff --git a/data/imgs/nut_15.jpg b/data/imgs/nut_15.jpg new file mode 100644 index 0000000..8243347 Binary files /dev/null and b/data/imgs/nut_15.jpg differ diff --git a/data/imgs/nut_16.jpg b/data/imgs/nut_16.jpg new file mode 100644 index 0000000..df864f1 Binary files /dev/null and b/data/imgs/nut_16.jpg differ diff --git a/data/imgs/nut_17.jpg b/data/imgs/nut_17.jpg new file mode 100644 index 0000000..55586ee Binary files /dev/null and b/data/imgs/nut_17.jpg differ diff --git a/data/imgs/nut_18.jpg b/data/imgs/nut_18.jpg new file mode 100644 index 0000000..b7a651e Binary files /dev/null and b/data/imgs/nut_18.jpg differ diff --git a/data/imgs/nut_19.jpg b/data/imgs/nut_19.jpg new file mode 100644 index 0000000..33061e1 Binary files /dev/null and b/data/imgs/nut_19.jpg differ diff --git a/data/imgs/nut_2.jpg b/data/imgs/nut_2.jpg new file mode 100644 index 0000000..6c4b866 Binary files /dev/null and b/data/imgs/nut_2.jpg differ diff --git a/data/imgs/nut_20.jpg b/data/imgs/nut_20.jpg new file mode 100644 index 0000000..b6ee186 Binary files /dev/null and b/data/imgs/nut_20.jpg differ diff --git a/data/imgs/nut_21.jpg b/data/imgs/nut_21.jpg new file mode 100644 index 0000000..a9f0549 Binary files /dev/null and b/data/imgs/nut_21.jpg differ diff --git a/data/imgs/nut_22.jpg b/data/imgs/nut_22.jpg new file mode 100644 index 0000000..14e747e Binary files /dev/null and b/data/imgs/nut_22.jpg differ diff --git a/data/imgs/nut_23.jpg b/data/imgs/nut_23.jpg new file mode 100644 index 0000000..b43dcc9 Binary files /dev/null and b/data/imgs/nut_23.jpg differ diff --git a/data/imgs/nut_24.jpg b/data/imgs/nut_24.jpg new file mode 100644 index 0000000..93a2a76 Binary files /dev/null and b/data/imgs/nut_24.jpg differ diff --git a/data/imgs/nut_25.jpg b/data/imgs/nut_25.jpg new file mode 100644 index 0000000..04c4898 Binary files /dev/null and b/data/imgs/nut_25.jpg differ diff --git a/data/imgs/nut_26.jpg b/data/imgs/nut_26.jpg new file mode 100644 index 0000000..7558ea1 Binary files /dev/null and b/data/imgs/nut_26.jpg differ diff --git a/data/imgs/nut_3.jpg b/data/imgs/nut_3.jpg new file mode 100644 index 0000000..e9df446 Binary files /dev/null and b/data/imgs/nut_3.jpg differ diff --git a/data/imgs/nut_4.jpg b/data/imgs/nut_4.jpg new file mode 100644 index 0000000..2877062 Binary files /dev/null and b/data/imgs/nut_4.jpg differ diff --git a/data/imgs/nut_5.jpg b/data/imgs/nut_5.jpg new file mode 100644 index 0000000..d77d4cd Binary files /dev/null and b/data/imgs/nut_5.jpg differ diff --git a/data/imgs/nut_6.jpg b/data/imgs/nut_6.jpg new file mode 100644 index 0000000..66e5f57 Binary files /dev/null and b/data/imgs/nut_6.jpg differ diff --git a/data/imgs/nut_7.jpg b/data/imgs/nut_7.jpg new file mode 100644 index 0000000..52dfe72 Binary files /dev/null and b/data/imgs/nut_7.jpg differ diff --git a/data/imgs/nut_8.jpg b/data/imgs/nut_8.jpg new file mode 100644 index 0000000..732e3d7 Binary files /dev/null and b/data/imgs/nut_8.jpg differ diff --git a/data/imgs/nut_9.jpg b/data/imgs/nut_9.jpg new file mode 100644 index 0000000..9a1d785 Binary files /dev/null and b/data/imgs/nut_9.jpg differ diff --git a/data/input.csv b/data/input.csv new file mode 100644 index 0000000..8a2279a --- /dev/null +++ b/data/input.csv @@ -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; diff --git a/data/nuts.net b/data/nuts.net new file mode 100644 index 0000000..8339e01 --- /dev/null +++ b/data/nuts.net @@ -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) diff --git a/data/output.csv b/data/output.csv new file mode 100644 index 0000000..8b33fbc --- /dev/null +++ b/data/output.csv @@ -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; diff --git a/detectionprocessing.cpp b/detectionprocessing.cpp new file mode 100644 index 0000000..d0a27c7 --- /dev/null +++ b/detectionprocessing.cpp @@ -0,0 +1,162 @@ +#include "detectionprocessing.h" +#include "controller.h" +#include "colors.h" +#include +#include +#include +#include +#include +#include + +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> contours; + findContours(in, contours, RETR_LIST, CHAIN_APPROX_NONE); + for(int i = 0; i> contours; + findContours(in, contours, RETR_LIST, CHAIN_APPROX_SIMPLE); + nut_count = 0; + for(int i = 0; i(i,j); + if( index == -1 ) + wshed.at(i,j) = Vec3b(255,255,255); + else if( index <= 0 || index > nut_count ) + wshed.at(i,j) = Vec3b(0,0,0); + else + wshed.at(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; +} diff --git a/detectionprocessing.h b/detectionprocessing.h new file mode 100644 index 0000000..d3941b0 --- /dev/null +++ b/detectionprocessing.h @@ -0,0 +1,43 @@ +#ifndef DETECTIONPROCESSING_H +#define DETECTIONPROCESSING_H + +#include +#include "processing.h" +#include + +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 diff --git a/filenames.cpp b/filenames.cpp new file mode 100644 index 0000000..4dd9844 --- /dev/null +++ b/filenames.cpp @@ -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"; diff --git a/filenames.h b/filenames.h new file mode 100644 index 0000000..6d51cfa --- /dev/null +++ b/filenames.h @@ -0,0 +1,14 @@ +#ifndef FILENAMES_H +#define FILENAMES_H + +#include + +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 diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..ae3bcae --- /dev/null +++ b/main.cpp @@ -0,0 +1,14 @@ +#include + +#include +#include +#include + +int main(int argc, char** argv) { + QApplication app(argc, argv); + MainWindow window; + + window.show(); + + return app.exec(); +} diff --git a/mainwindow.cpp b/mainwindow.cpp new file mode 100644 index 0000000..be23185 --- /dev/null +++ b/mainwindow.cpp @@ -0,0 +1,264 @@ +#include "mainwindow.h" +#include "ui_mainwindow.h" +#include +#include +#include + +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 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 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; +} diff --git a/mainwindow.h b/mainwindow.h new file mode 100644 index 0000000..2317b93 --- /dev/null +++ b/mainwindow.h @@ -0,0 +1,53 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include +#include +#include +#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 diff --git a/mainwindow.ui b/mainwindow.ui new file mode 100644 index 0000000..19a011b --- /dev/null +++ b/mainwindow.ui @@ -0,0 +1,691 @@ + + + MainWindow + + + + 0 + 0 + 1097 + 807 + + + + + 0 + 0 + + + + + 1035 + 560 + + + + Haselnüsse + + + + + + + + + + 0 + + + + Live + + + + + + + + + 0 + 0 + + + + + + + + + + + 0 + 0 + + + + + + + + + 3 + 0 + + + + Geschmack + + + + + + &1 + + + 1 + + + + + + + &2 + + + 2 + + + + + + + &3 + + + 3 + + + + + + + &4 + + + 4 + + + + + + + &5 + + + 5 + + + + + + + + + + + + + + + + + 0 + 0 + + + + Status: + + + + + + + + 9 + 0 + + + + ... lade ... + + + + + + + N = 0 + + + + + + + + + + + Bild laden + + + + + + + Bild aufnehmen + + + + + + + + + + Zurücksetzen + + + + + + + + 0 + 0 + + + + Benutze Neurales Netz + + + true + + + + + + + + + + Konfiguration + + + + + + 0 + + + + Nüsse erkennen + + + + + + + + Vorheriger Schritt + + + Ctrl+S + + + + + + + Nächster Schritt + + + Right + + + + + + + Speichern + + + + + + + + + true + + + 24 + + + + + + + Aktuelle Operation: + + + + + + + + + Input: + + + + + + + Output: + + + + + + + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + + + + + + + + Parameter 1: + + + + + + + 255 + + + 10 + + + Qt::Horizontal + + + + + + + 1 + + + 255 + + + 1 + + + 10 + + + + + + + Parameter 2: + + + + + + + 255 + + + 3 + + + Qt::Horizontal + + + + + + + 1 + + + 255 + + + 3 + + + + + + + Konfiguration speichern + + + + + + + + + + Nüsse bewerten + + + + + + + + vorherige Nuss + + + + + + + nächste Nuss + + + + + + + Vorheriger Schritt + + + Ctrl+S + + + + + + + Nächster Schritt + + + Right + + + + + + + true + + + Speichern + + + + + + + + + true + + + 24 + + + + + + + Aktuelle Operation: + + + + + + + + + Input: + + + + + + + Output: + + + + + + + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + + + + + + + + Parameter 1: + + + + + + + 255 + + + 10 + + + Qt::Horizontal + + + + + + + 1 + + + 255 + + + 1 + + + 10 + + + + + + + Parameter 2: + + + + + + + 255 + + + 3 + + + Qt::Horizontal + + + + + + + 1 + + + 255 + + + 3 + + + + + + + Konfiguration speichern + + + + + + + + + Ergebnis: + + + + + + + + + + + + + + + + + CVImageWidget + QWidget +
cvimagewidget.h
+ 1 +
+
+ + + + rate_param1Slider + valueChanged(int) + rate_param1SpinBox + setValue(int) + + + 272 + 729 + + + 408 + 733 + + + + + rate_param1SpinBox + valueChanged(int) + rate_param1Slider + setValue(int) + + + 411 + 718 + + + 226 + 728 + + + + + rate_param2SpinBox + valueChanged(int) + rate_param2Slider + setValue(int) + + + 845 + 726 + + + 797 + 732 + + + + + rate_param2Slider + valueChanged(int) + rate_param2SpinBox + setValue(int) + + + 704 + 726 + + + 840 + 719 + + + + +
diff --git a/neuralnet.cpp b/neuralnet.cpp new file mode 100644 index 0000000..8af6bdb --- /dev/null +++ b/neuralnet.cpp @@ -0,0 +1,273 @@ +#include "neuralnet.h" +#include "controller.h" +#include "filenames.h" +#include +#include +#include + +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::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(inputTrainData.size()); + for(int i = 0; i(scaling_input_data[i])) * + get <1>(scaling_input_data[i]); + } + } + scaledOutputTrainData = vector(outputTrainData.size()); + for(int i = 0; i>(result_names.size()); + for(int i = 0; i& input) { + float input_arr[input.size()]; + for (unsigned int i = 0; i 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 +#include +#include +#include +#include + +class NeuralNet { +public: + void saveNut(); + void init(Json::Value jsonResults); + int getN(); + float run(const std::map& input); + void neuralNetEnabled(bool val); + +private: + void createHeaders(); + bool filesExist(); + void trainAndSave(); + void getScalingFactors(); + void scaleData(); + +private: + int current_index = 0; + std::vector inputTrainData = {}; + std::vector scaledInputTrainData = {}; + std::vector outputTrainData = {}; + std::vector scaledOutputTrainData = {}; + std::vector result_names = {}; + FANN::neural_net net; + // tuple of min and scaling factor for each input + std::vector> scaling_input_data = {}; + bool use_neural_net = true; +}; + +#endif // NEURALNET_H diff --git a/processing.cpp b/processing.cpp new file mode 100644 index 0000000..003d6e6 --- /dev/null +++ b/processing.cpp @@ -0,0 +1,183 @@ +#include "processing.h" +#include "controller.h" +#include +#include +#include +#include +#include +#include + +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; imethodCount(); 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 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 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 +#include +#include + +#include "processingstep.h" + +class Processing : public QObject { + + Q_OBJECT + +public: + Processing(QObject *parent = 0); + std::vector 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 steps {}; + int current_step = 0; + Json::Value config; + void init(const QMetaObject* metaObj, Json::Value json_steps); + bool interactive = false; +}; + +#endif // PROCESSING_H diff --git a/processingstep.h b/processingstep.h new file mode 100644 index 0000000..a5a2a97 --- /dev/null +++ b/processingstep.h @@ -0,0 +1,100 @@ +#ifndef PROCESSINGSTEP_H +#define PROCESSINGSTEP_H + +#include +#include +#include +#include + +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 params, + InputType inputType + ) { + this->processing = processing; + this->stepName = stepName; + this->operation = operation; + this->params = params; + this->inputType = inputType; + } + + std::vector 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 params; + InputType inputType; + QObject* processing; +}; + +#endif diff --git a/rateprocessing.cpp b/rateprocessing.cpp new file mode 100644 index 0000000..2a9855d --- /dev/null +++ b/rateprocessing.cpp @@ -0,0 +1,311 @@ +#include "detectionprocessing.h" +#include "controller.h" +#include +#include +#include +#include +#include +#include +#include +#include + +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 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> 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> 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> contours; + contours = {}; + Mat out; + findContours(in, contours, RETR_LIST, CHAIN_APPROX_NONE); + if (isInteractive()) { + out = Mat::zeros(input.size(), CV_8UC3); + } + vector 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(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> contours; + Mat out; + findContours(in, contours, RETR_LIST, CHAIN_APPROX_NONE); + if (isInteractive()) { + out = Mat::zeros(input.size(), CV_8UC3); + } + vector boxes = {}; + // image and mask of spots + vector> 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 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 1) { + box_avg_size /= static_cast(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[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(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 RateProcessing::getResults() { + return results; +} + +const cv::Mat* RateProcessing::getInput() { + if (rateCurNut) { + return Controller::inst().getCurNut(); + } + else { + return Controller::inst().getNutToRate(); + } +} + +const vector>& RateProcessing::getContours() { + return contours; +} + +void RateProcessing::setRateCurNut(bool val) { + rateCurNut = val; +} diff --git a/rateprocessing.h b/rateprocessing.h new file mode 100644 index 0000000..2311b50 --- /dev/null +++ b/rateprocessing.h @@ -0,0 +1,46 @@ +#ifndef RATEPROCESSING_H +#define RATEPROCESSING_H + +#include +#include "processing.h" +#include +#include + +class RateProcessing : public Processing { + + Q_OBJECT + +public: + RateProcessing(QObject *parent = 0); + void init(Json::Value json_steps); + void reset(); + std::map getResults(); + const std::vector>& 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 results; + std::vector> contours; +}; + +#endif // RATEPROCESSING_H diff --git a/test_data/.directory b/test_data/.directory new file mode 100644 index 0000000..7e8a08f --- /dev/null +++ b/test_data/.directory @@ -0,0 +1,4 @@ +[Dolphin] +PreviewsShown=true +Timestamp=2016,11,29,0,38,21 +Version=3 diff --git a/test_data/IMG_20170117_012527.jpg b/test_data/IMG_20170117_012527.jpg new file mode 100644 index 0000000..2e620cd Binary files /dev/null and b/test_data/IMG_20170117_012527.jpg differ