diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..7872745 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 2.8.4) +project(RSlic) + +include(CheckCXXCompilerFlag) +CHECK_CXX_COMPILER_FLAG("-std=c++11" COMPILER_SUPPORTS_CXX11) +CHECK_CXX_COMPILER_FLAG("-std=c++0x" COMPILER_SUPPORTS_CXX0X) + +if(COMPILER_SUPPORTS_CXX11) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +elseif(COMPILER_SUPPORTS_CXX0X) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x") +else() + message(STATUS "The compiler ${CMAKE_CXX_COMPILER} has no C++11 support. Please use a different C++ compiler.") +endif() + +add_subdirectory(apps) +add_subdirectory(lib) + +configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/cmake_uninstall.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" + IMMEDIATE @ONLY) + +add_custom_target(uninstall + COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake) diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..4aae62a --- /dev/null +++ b/Readme.md @@ -0,0 +1,115 @@ +# Overview +RSlic is a library written in C++ for computing Superpixel and Supervoxel using OpenCV. It based on the Slic-Algorithmus (http://ivrg.epfl.ch/research/superpixels). + +# Features +- Compute Superpixel and Supervoxel +- Support for the zero parameter version of the SLIC algorithm (SLICO) +- Support for custom metrics +- Ability to save any iteration while computing (e.g. SuperPixelGUI makes use of it) + +# Dependency +To build RSlic you need: +- OpenCV +- CMake +- optional: ccmake or cmake-gui for a configuration interface +- C++11 Compiler (e.g. gcc or clang) +- Qt4 or Qt5 for GUI-Apps + +# Installing +Go into the source directory and run: + +``` +$ mkdir build && cd build +$ cmake .. +$ make +$ make install +``` + +Or run *ccmake* if you want to configure somethings before building. (E.g. you can disable thread usage. Or prefer Qt4 over Qt5) + +# Create a project +## CMakeLists +Create a CMakeLists.txt for your project. If your project has the name myproj, the CMakeLists.txt should contains something like: + +``` + find_package(RSlic REQUIRED) +include_directories(${RSLIC_INCLUDE_DIRS}) +add_executable(myproj main.cpp ...) +target_link_libraries(myproj ${RSLIC_LIB}) +``` + +By the way: Your project will (and have to) use C++11 now. + +## Including the files +If you would like to use Superpixel the simplest way would be to import RSlic/RSlic2H.h: + +```cpp +#include +``` + +and if you want to use Supervoxel: + +```cpp +#include +``` +Of course you can use Superpixel and Supervoxel at the same time. + +## Simple introduction RSlic +Executing the Slic-Algorithmus is very simple. Let's say you have a gray image img, want n Superpixel with a special stiffness. Then: + +```cpp +#include +using namespace RSlic::Pixel; +.... +Mat grad = buildGrad(img); //Build the gradient for the algorithm +int w = m.cols; +int h = m.rows; +int step = sqrt(w * h * 1.0 / n); //Calc the step-width +Slic2P slic = Slic2::initialize(img,grad,step,stiffness); //Initialize with the parameter +for (int i=0; i < 10; i++){ //Do 10 Iterations + slic = slic->iterate(); + //slic = slic->iterateZero(); //For the zero parameter version +} +slic = slic->finalize(); +``` + +Now you can work with *slic*. Mainly you want to use +*slic->getClusters()*. See the documentation for more details. + +You're not sure if you have a gray image or a colorful one? + +```cpp +#include +using namespace RSlic::Pixel; +.... +Mat grad = buildGrad(img); //Build the gradient for the algorithms +int w = m.cols; +int h = m.rows; +int step = sqrt(w * h * 1.0 / n); //Calc the step-width +Slic2P slic = Slic2::initialize(img,grad,step,stiffness); //Initialize with the parameter +for (int i=0; i < 10; i++){ //Do 10 Iterations + slic = iteratingHelper(slic, img.type()); + // slic = iteratingHelper(slic, img.type(), true); //For the zero parameter version +} +slic = slic->finalize(); + ``` + +You think that's not simple enough? OK, an easier version: +```cpp +#include +using namespace RSlic::Pixel; +.... +//10-Iterations +Slic2P slic = shutUpAndTakeMyMoney(img, n, hardness, false, 10); +//10-Iterations with Slico +//Slic2P slic = shutUpAndTakeMyMoney(img, n, hardness, true, 10); +``` + +Note that Slic is often intended to be applied on top of LAB images. So you may want to convert your image before using the functions above. (Use OpenCV for this) + +# 3rd Party Code +This library uses the ThreaPool file from https://github.com/progschj/ThreadPool. +The RSlic library uses threads if 'PARALLEL' is enabled in CMakeCache. + +# License +BSD license diff --git a/apps/3DTest/CMakeLists.txt b/apps/3DTest/CMakeLists.txt new file mode 100644 index 0000000..b9c0757 --- /dev/null +++ b/apps/3DTest/CMakeLists.txt @@ -0,0 +1,10 @@ + project(3DTest) + +find_package( OpenCV REQUIRED ) +include_directories ("${3DTest_SOURCE_DIR}/../../lib") +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCE_FILES main.cpp parser.cpp exporter.cpp) +add_executable(3DTest ${SOURCE_FILES}) + +target_link_libraries(3DTest rslic ${OpenCV_LIBS}) diff --git a/apps/3DTest/Readme.md b/apps/3DTest/Readme.md new file mode 100644 index 0000000..d0dac60 --- /dev/null +++ b/apps/3DTest/Readme.md @@ -0,0 +1,39 @@ +# 3DTest + +3DTest is a program which creates a point cloud from several images with Supervoxel. +The export format is ply. + +Additionally, a window appears which shows the result in pictures. +The following keys help navigate through these pictures: + +- a Go a picture backward +- s Go a picture forward +- t Toggle between showing contour in z-direction +- d Show some debug information +- q Close the window + +The following arguments are allowed on the command line: + +- -c Number of Supervoxel (optional) +- -m Stiffness (optional) +- -t Number of threads to be used (optional) +- -0 Use Slico (optional) +- -h Show some help and exit +- -o Output filename for the point cloud. (optional). The points cloud will only exported if a filename is set. +- pictures + +Some command can like this: + +` +./3DTest -c 39000 -m 40 -i 10 -o out.ply pic001.tif pic002.tif +` + +` +./3DTest -0 -o out.ply img/*.tif +` + +or without exporting: + +` +./3DTest img/*.tif +` diff --git a/apps/3DTest/exporter.cpp b/apps/3DTest/exporter.cpp new file mode 100644 index 0000000..eaa73a0 --- /dev/null +++ b/apps/3DTest/exporter.cpp @@ -0,0 +1,142 @@ +#include "exporter.h" +#include +#include +using namespace std; +using namespace RSlic::Voxel; + +// Return coordinates of points which are between clusters +// mask.empty => ignore mask +vector contourVertex(Slic3P p, const Mat &mask) { + Mat_ clusters = p->getClusters().getClusterLabel(); + int w = clusters.size[1]; + int h = clusters.size[0]; + int d = clusters.size[2]; + vector res; + for (int x = 0; x < w - 1; x++) { + for (int y = 0; y < h - 1; y++) { + for (int t = 0; t < d - 1; t++) { + if (clusters.at(y, x, t) != clusters.at(y, x, t + 1) + || clusters.at(y, x, t) != clusters.at(y, x + 1, t) + || clusters.at(y, x, t) != clusters.at(y + 1, x, t)){ + if (!mask.empty() && mask.at(y,x,t) == 0) continue; + res.push_back(Vec3i(x, y, t)); + } + } + } + } + return res; +} + +void exportPLY(const vector &vertex, const string &filename,RSlic::Voxel::MovieCacheP img, const string & comments) { + ofstream out; + out.open(filename); + if (out.fail()) { + cout << "[ERROR] Opening " << filename << " failed" << endl; + return; + } + out << "ply" << endl; + if (!comments.empty()) + out << "comment " <type() == CV_8UC1) color = Vec3b::all(img->at(y,x,z)); + else if (img->type()==CV_8UC3) color = img->at(y,x,z); + int r = color[1] , g = color[0],b = color[2]; + out << x << ' ' << y << ' ' << z + << ' ' << r <<' ' << g << ' ' << b < +template +void showNrIntern(cv::Mat labels, int i, bool tIgnore, Mat &img, T value){ + for (int x = 1; x < img.cols - 1; x++) { + for (int y = 1; y < img.rows - 1; y++) { + if (labels.at(y, x, i) < 0){ + std::cout<<"Fail"; + continue; + } + if ((labels.at(y, x, i) != labels.at(y, x + 1, i) + || labels.at(y, x, i) != labels.at(y, x - 1, i) + || labels.at(y, x, i) != labels.at(y + 1, x, i) + || labels.at(y, x, i) != labels.at(y - 1, x, i)) + && (tIgnore || (labels.at(y, x, i) == labels.at(y, x, i-1) + && labels.at(y, x, i) == labels.at(y, x, i+1) + )) + ) + img.at(y, x) = value; + } + } +} + +// Get a 2-D image visualization +// tIgnore: ignore time-dimension for cluster contour +void showNr(Slic3P p, int i, bool tIgnore) { + auto img = p->getImg()->matAt(i).clone(); + auto labels = p->getClusters().getClusterLabel(); + if (img.type()==CV_8UC3){ + cv::cvtColor(img, img, cv::COLOR_Lab2BGR); + showNrIntern(labels,i,tIgnore,img,Vec3b(255,255,255)); + }else{ + showNrIntern(labels,i,tIgnore,img,255); + } + + imshow("Hi", img); +} +#include + +// Show first image +// and handling for key events +void showFirst(Slic3P p) { + int i = 0; + const int d = p->getImg()->duration(); + bool tIgnore(false); + showNr(p, i, tIgnore); + char c; + do { + c = waitKey(-1); + if (c == 's') { + i = min(i + 1, d - 1); + showNr(p, i, tIgnore); + } + else if (c == 't'){ + tIgnore = !tIgnore; + showNr(p,i,tIgnore); + } + else if (c == 'a') { + i = max(i - 1, 0); + showNr(p, i, tIgnore); + }else if (c=='d'){ + auto labels = p->getClusters().getClusterLabel(); + std::cout<<"Nr. "< set; + auto img = p->getImg()->matAt(i); + for (int x = 1; x < img.cols - 1; x++) { + for (int y = 1; y < img.rows - 1; y++) { + set.insert(labels.at(y,x,i)); + } + } + for (ClusterInt c : set){ + std::cout< +#include + +void exportPLY(const std::vector &vertex, const std::string &filename, RSlic::Voxel::MovieCacheP img, const string &comments = string()); + +std::vector contourVertex(RSlic::Voxel::Slic3P p, const Mat & mask = Mat()); + + +void showNr(RSlic::Voxel::Slic3P p, int i, bool tIgnore); + +void showFirst(RSlic::Voxel::Slic3P p); + +#endif // EXPORTER_H diff --git a/apps/3DTest/main.cpp b/apps/3DTest/main.cpp new file mode 100644 index 0000000..a6e0026 --- /dev/null +++ b/apps/3DTest/main.cpp @@ -0,0 +1,84 @@ +#include +#include +#include <3rd/ThreadPool.h> + +#include "parser.h" +#include "exporter.h" + +using namespace RSlic::Voxel; +using namespace std; + +template +auto measureTime(std::function function) -> std::tuple> { + std::chrono::time_point start, end; + start = std::chrono::system_clock::now(); + T res = function(); + end = std::chrono::system_clock::now(); + return std::make_tuple(res, end - start); +} + +template +auto measureTimeV(std::function function) -> std::chrono::duration { + std::chrono::time_point start, end; + start = std::chrono::system_clock::now(); + function(); + end = std::chrono::system_clock::now(); + return end - start; +} + +MovieCacheP loadFiles(MainSetting *s) { + return std::make_shared(s->filenames); +} + +int main(int argc, char **argv) { + MainSetting *settings = parseSetting(argc, argv); + if (settings == nullptr) return -1; + + MovieCacheP img = loadFiles(settings); + cout << "Width: " << img->width() << ", Height: " << img->height() + << " Duration: " << img->duration() << endl; + auto s = pow(img->width() * img->height() * img->duration() *1.0 / settings->count,0.333); + if (std::min(img->duration(), std::min(img->width(), img->height())) <= s / 2) { + cout << "[Warning] Not enough pictures. At least " << s << " pictures are recommended" << endl; + } + Slic3P slic; + if (settings->threadcount <=0) settings->threadcount=std::thread::hardware_concurrency(); + ThreadPoolP pool = std::make_shared(settings->threadcount); + slic = Slic3::initialize(img, buildGradColor, s, settings->stiffness, pool); + + if (slic.get() == nullptr) { + cout << "[ERROR] Not able to perform the algorithm. May you should play with the parameters." << endl; + return -1; + } + //showFirst(slic); + + // Iteration + std::cout << "* Process: 0 of " << settings->iterations << std::flush; + auto res = measureTimeV([&]() { + for (int i = 0; i < settings->iterations; i++) { + std::cout << '\r' << "* Process: " << i + 1 << " of " << settings->iterations << std::flush; + slic = RSlic::Voxel::iterateHelper(slic,img->type(),settings->slico); + if (slic == nullptr){ + cout << "This image type is not currently supported " << endl; + exit(EXIT_FAILURE); + } + //showFirst(slic); + } + }); + cout<<" - Needed "<type(),slic->finalize); //Cluster finalizing + showFirst(slic); + std::cout << " Finish" << std::endl; + if (!settings->outputfile.empty()) { + cout << "* Exporting " << std::flush; + Mat mask; + stringstream str; + str << "iterations: "<iterations << ", stiffness: "<stiffness <<", count: "<count; + str << ", Slico?: "<< (settings->slico?"true":"false") ; + exportPLY(contourVertex(slic,mask), settings->outputfile,img,str.str()); + std::cout << " Finish" << std::endl; + } + delete settings; +} diff --git a/apps/3DTest/parser.cpp b/apps/3DTest/parser.cpp new file mode 100644 index 0000000..746f049 --- /dev/null +++ b/apps/3DTest/parser.cpp @@ -0,0 +1,81 @@ +#include "parser.h" +#include + +using namespace std; + +bool file_exist(const char *filename) { + struct stat buffer; + return (stat(filename, &buffer) == 0); +} + +ostream &operator<<(ostream &str, const vector &list) { + str << "["; + for (const string s: list) { + str << s << ","; + } + str << "]"; + return str; +} + + +void printHelp(char *name) { + MainSetting *tmp = new MainSetting; + cout << "Program to create Supervoxel from images" << endl; + cout << name << " [-c ...] [-m ...] [-i ...] [-h] [-0] [-t ...] filename1 filename2 ... filename n [-o ...] " << endl; + cout << "-c a: Set the number of Supervoxels to a (a is a number, default " << tmp->count << ")" << endl; + cout << "-m a: Set stiffness to a (a is a number, default " << tmp->stiffness << ")" << endl; + cout << "-i a: Set iteration count to a (a is a number, default " << tmp->iterations << ")" << endl; + cout << "-o a: Set the output ply file to a. If not set, there will be no export." << endl; + cout << "-t a: Set the number of thread to be used. -1 does automatically detecting. (a is a number, default " << tmp->threadcount << ")" << endl; + cout << "-0: Use Slico algorithms. -m will be ignored (default "<< (tmp->slico?"true":"false")<<")" << endl; + cout << "-h: Print this help" << endl; + cout << "filenames: Set the filename of the image to convert (filename is a list the path, required)" << endl; + delete tmp; +} + +MainSetting *parseSetting(int argc, char **argv) { + if (argc < 2) { + std::cout << "[Error] Not enough arguments" << std::endl; + printHelp(argv[0]); + return nullptr; + } + MainSetting *res = new MainSetting(); + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "-c") == 0 && i + 1 < argc) { + res->count = atoi(argv[i + 1]); + i++; + } else if (strcmp(argv[i], "-m") == 0 && i + 1 < argc) { + res->stiffness = atoi(argv[i + 1]); + i++; + } else if (strcmp(argv[i], "-i") == 0 && i + 1 < argc) { + res->iterations = atoi(argv[i + 1]); + i++; + } else if (strcmp(argv[i], "-h") == 0) { + delete res; + printHelp(argv[0]); + return nullptr; + } else if (strcmp(argv[i], "-o") == 0 && i + 1 < argc) { + res->outputfile = argv[i + 1]; + i++; + } else if (strcmp(argv[i], "-t") == 0 && i + 1 < argc) { + res->threadcount = atoi(argv[i + 1]); + i++; + } else if (strcmp(argv[i], "-0") == 0) { + res->slico = true; + }else { + char *filename = argv[i]; + if (!file_exist(filename)) { + cout << "[Error] File " << filename << " does not exists" << std::endl; + delete res; + return nullptr; + } + res->filenames.push_back(filename); + } + } + if (res->filenames.empty()){ //FIXME: Check for whitespace and similar + printHelp(argv[0]); + delete res; + return nullptr; + } + return res; +} diff --git a/apps/3DTest/parser.h b/apps/3DTest/parser.h new file mode 100644 index 0000000..931fd55 --- /dev/null +++ b/apps/3DTest/parser.h @@ -0,0 +1,36 @@ +#ifndef PARSER_H +#define PARSER_H + +#include +#include +#include + +bool file_exist(const char *filename); + +std::ostream &operator<<(std::ostream &str, const std::vector &list); + +struct MainSetting { + MainSetting() : count(32400), stiffness(40), + threadcount(-1), iterations(10), slico(false) {} + + std::vector filenames; + std::string outputfile; + int count; + int stiffness; + int iterations; + bool slico; + int threadcount; + + void print() { + std::cout << "[] Iterations " << iterations << std::endl; + std::cout << "[] Count " << count << std::endl; + std::cout << "[] stiffness " << stiffness << std::endl; + std::cout << "[] filenames " << filenames << std::endl; + } +}; + +void printHelp(char *name); + +MainSetting *parseSetting(int argc, char **argv); + +#endif // PARSER_H diff --git a/apps/CMakeLists.txt b/apps/CMakeLists.txt new file mode 100644 index 0000000..7e48e7b --- /dev/null +++ b/apps/CMakeLists.txt @@ -0,0 +1,6 @@ +add_subdirectory(SimpleTest) +add_subdirectory(3DTest) +option(GUI "Compile GUI" ON) +IF(${GUI}) + add_subdirectory(SuperPixelGui) +ENDIF() diff --git a/apps/SimpleTest/CMakeLists.txt b/apps/SimpleTest/CMakeLists.txt new file mode 100644 index 0000000..b4b48b5 --- /dev/null +++ b/apps/SimpleTest/CMakeLists.txt @@ -0,0 +1,10 @@ +project(SimpleTest) + +find_package( OpenCV REQUIRED ) +include_directories ("${SimpleTest_SOURCE_DIR}/../../lib") +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCE_FILES main.cpp) +add_executable(SimpleTest ${SOURCE_FILES}) + +target_link_libraries(SimpleTest rslic ${OpenCV_LIBS}) diff --git a/apps/SimpleTest/Readme.md b/apps/SimpleTest/Readme.md new file mode 100644 index 0000000..5e55562 --- /dev/null +++ b/apps/SimpleTest/Readme.md @@ -0,0 +1,25 @@ +#SimpleTest + +Little application for showing pictures with Superpixel. + +Parameters: + +- -c Number of Superpixel (optional) +- -m Stiffness (optional) +- -i Number of iterations (optional) +- -0 Use Slico. Ignores -i (optional) +- -t Number of threads to be used. +- -h Show help +- Filename of the picture + +For the window following keys are accepted: + +- q Close the Window +- -b Set contour black +- -w Set contour white + +For example: + +- `./SimpleTest -c 400 -m 40 -i 10 pic.png` +- `./SimpleTest -0 pic.png` +- `./SimpleTest pic.png` diff --git a/apps/SimpleTest/main.cpp b/apps/SimpleTest/main.cpp new file mode 100644 index 0000000..1a56dff --- /dev/null +++ b/apps/SimpleTest/main.cpp @@ -0,0 +1,186 @@ +#include +#include +#include +#include <3rd/ThreadPool.h> + +using namespace RSlic; + +inline void showImg(const ClusterSet &cl, const cv::Mat &img, const string &name, bool white) { + int gray = white ? 255 : 0; + Vec3b color = white ? Vec3b(255, 255, 255) : Vec3b(0, 0, 0); + switch (img.type()) { + case CV_8UC3: + //cvtColor(img,res,CV_Lab2BGR); + cv::imshow(name, contourCluster(drawCluster(img, cl), cl, color)); + cv::imshow(name + "orig", contourCluster(img, cl, color)); + break; + case CV_8UC1: + cv::imshow(name, contourCluster(drawCluster(img, cl), cl, gray)); + cv::imshow(name + "orig", contourCluster(img, cl, gray)); + break; + } +} + +inline void showClusters(const ClusterSet &cl, const cv::Mat &img, const std::string &name) { + //cv::Mat res; + showImg(cl, img, name, false); + char key = -1; + while (key != 'q') { + key = cv::waitKey(-1); + if (key == 'b') { + showImg(cl, img, name, false); + } else if (key == 'w') { + showImg(cl, img, name, true); + } + } +} + +#include + +bool file_exist(const char *filename) { + struct stat buffer; + return (stat(filename, &buffer) == 0); +} + +struct MainSetting { + MainSetting() : count(400), stiffness(40), iterations(10), slico(false), threadcount(-1) { + } + + string filename; + int count; + int stiffness; + int iterations; + bool slico; + int threadcount; + + int guessthreadcount() { + if (threadcount <= 0) + return std::thread::hardware_concurrency(); + return threadcount; + } + + void print() { + std::cout << "[] Iterations " << iterations << endl; + std::cout << "[] Count " << count << endl; + std::cout << "[] stiffness " << stiffness << endl; + std::cout << "[] filename " << filename << endl; + std::cout << "[] slico " << slico << endl; + std::cout << "[] threadcount " << threadcount << endl; + } +}; + +void printHelp(char *name) { + MainSetting *tmp = new MainSetting; + cout << "Create Superpixel from an image" << endl; + cout << name << " [-c ...] [-m ...] [-i ...] [-o] [-h] [-t ...] filename " << endl; + cout << "-c a: Set the number of superpixel to a (a is a number, default " << tmp->count << ")" << endl; + cout << "-m a: Set stiffness to a (a is a number, default " << tmp->stiffness << ")" << endl; + cout << "-i a: Set iteration count to a (a is a number, default " << tmp->iterations << ")" << endl; + cout << "-0: Use Slico (zero-parameter variant of Slic, default " << tmp->slico << ", -m will be ignored)" << endl; + cout << "-t a: Set the number of thread to be used to a (a is a number, default " << tmp->threadcount << ")" << endl; + cout << "-h: Print this help" << endl; + cout << "filename: Set the filename of the image to convert (filename is the path)" << endl; + delete tmp; +} + +MainSetting *parseSetting(int argc, char **argv) { + if (argc < 2) { + std::cout << "[Error] Not enough arguments" << std::endl; + printHelp(argv[0]); + return nullptr; + } + MainSetting *res = new MainSetting(); + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "-c") == 0 && i + 1 < argc) { + res->count = atoi(argv[i + 1]); + i++; + } else if (strcmp(argv[i], "-m") == 0 && i + 1 < argc) { + res->stiffness = atoi(argv[i + 1]); + i++; + } else if (strcmp(argv[i], "-i") == 0 && i + 1 < argc) { + res->iterations = atoi(argv[i + 1]); + i++; + } else if (strcmp(argv[i], "-0") == 0) { + res->slico = true; + } else if (strcmp(argv[i], "-h") == 0) { + delete res; + printHelp(argv[0]); + return nullptr; + } else + res->filename = argv[i]; + } + if (res->filename.empty()) { //FIXME: Check for whitespace and similar + printHelp(argv[0]); + delete res; + return nullptr; + } + if (!file_exist(res->filename.c_str())) { + cout << "[Error] File " << res->filename << " does not exists" << std::endl; + delete res; + return nullptr; + } + return res; +} + +int main(int argc, char **argv) { + MainSetting *settings = parseSetting(argc, argv); + if (settings == nullptr) return -1; + if (settings->slico) settings->stiffness = 1; +#ifdef DEBUG_ME + settings->print(); +#endif + + auto img = cv::imread(settings->filename, cv::IMREAD_UNCHANGED); + if (img.type() == CV_8UC4) { + cv::cvtColor(img, img, CV_BGRA2BGR); + } + auto grad = buildGrad(img); + + cout << "Image Type: " << getType(img) << ", Gradient: " << getType(grad) << endl; + + auto s = sqrt(img.cols * img.rows / settings->count); + + Slic2P slic; + ThreadPoolP pool = std::make_shared(settings->guessthreadcount()); + Mat img_lab; + + switch (img.type()) { + case CV_8UC3: + cvtColor(img, img_lab, CV_BGR2Lab); // See paper + slic = Slic2::initialize(img_lab, grad, s, settings->stiffness, pool); + break; + case CV_8UC1: + slic = Slic2::initialize(img, grad, s, settings->stiffness, pool); + break; + default: + cout << "This image type is not currently supported " << endl; + return -1; + } + cv::imshow("orig", img); + +#ifdef DEBUG_ME + showClusters(slic->getClusters(), img, "res"); +#endif + // Iteration + std::cout << "* Process: 0 from " << settings->iterations << std::flush; + for (int i = 0; i < settings->iterations; i++) { + std::cout << '\r' << "* Process: " << i + 1 << " from " << settings->iterations << std::flush; + slic = RSlic::Pixel::iteratingHelper(slic,img.type(),settings->slico); +#ifdef DEBUG_ME + showClusters(slic->getClusters(), img, std::string("res_")+std::to_string(i)); +#endif + } + + std::cout << std::endl << "* Finalize Clusters..." << std::flush; + if (img.type() == CV_8UC1){ + distanceGray g; + slic = slic->finalize(g); + }else{ + distanceColor c; + slic = slic->finalize(c); + } + std::cout << " Finish" << std::endl << "* Drawing Clusters..." << std::flush; + std::cout << " Finish" << std::endl << "Press q to quit" << std::endl << std::flush; + delete settings; + showClusters(slic->getClusters(), img, "result"); +} diff --git a/apps/SuperPixelGui/CMakeLists.txt b/apps/SuperPixelGui/CMakeLists.txt new file mode 100644 index 0000000..35bc564 --- /dev/null +++ b/apps/SuperPixelGui/CMakeLists.txt @@ -0,0 +1,54 @@ +project(SuperPixelGui) +cmake_minimum_required(VERSION 2.8.11) + +find_package( OpenCV REQUIRED ) +option(Qt5 "Use Qt5 instead of Qt4 if available" ON) + +set(CMAKE_MODULE_PATH ${SuperPixelGui_SOURCE_DIR}/cmake) + +include_directories ("${SuperPixelGui_SOURCE_DIR}/../../lib") +set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(SOURCE_FILES main.cpp mainwindow.cpp utils.cpp imgwdg.cpp ) + +# OSX-Bundle Settings +IF(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + set(MACOSX_BUNDLE_ICON_FILE superpi.icns) + SET(MACOSX_BUNDLE_BUNDLE_NAME SuperPixelGUI) + set(app_icon ${SuperPixelGui_SOURCE_DIR}/superpi.icns) + set_source_files_properties(${app_icon} PROPERTIES MACOSX_PACKAGE_LOCATION "Resources") +ENDIF() + +# Allowed to use Qt5? +IF(${Qt5}) + find_package(Qt5Widgets) + Set(Qt5_FOUND ${Qt5Widgets_FOUND}) +ELSE (${Qt5}) + Set(Qt5_FOUND false) +ENDIF() + +IF (Qt5_FOUND) + set(CMAKE_AUTOMOC ON) + QT5_ADD_RESOURCES( RES ${SuperPixelGui_SOURCE_DIR}/superpixelgui.qrc) + IF(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + add_executable(SuperPixelGui MACOSX_BUNDLE ${app_icon} ${SOURCE_FILES}) + ELSE() + add_executable(SuperPixelGui ${SOURCE_FILES} ${RES}) + ENDIF() + target_link_libraries(SuperPixelGui rslic ${OpenCV_LIBS} Qt5::Core Qt5::Widgets ) +ELSE (Qt5_FOUND) + find_package(Qt4 COMPONENTS QTCORE QTGUI) + IF (Qt4_FOUND) + INCLUDE(${QT_USE_FILE}) + ADD_DEFINITIONS(${QT_DEFINITIONS}) + QT4_WRAP_CPP(SOURCE_FILES_HEADERS_MOC imgwdg.h mainwindow.h) + QT4_ADD_RESOURCES( RES ${SuperPixelGui_SOURCE_DIR}/superpixelgui.qrc) + IF(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + ADD_EXECUTABLE(SuperPixelGui MACOSX_BUNDLE ${app_icon} ${SOURCE_FILES} ${SOURCE_FILES_HEADERS_MOC}) + ELSE() + ADD_EXECUTABLE(SuperPixelGui ${SOURCE_FILES} ${SOURCE_FILES_HEADERS_MOC} ${RES}) + ENDIF() + TARGET_LINK_LIBRARIES(SuperPixelGui rslic ${OpenCV_LIBS} ${QT_LIBRARIES}) + ENDIF(Qt4_FOUND) +ENDIF(Qt5_FOUND) + +install(TARGETS SuperPixelGui DESTINATION bin) diff --git a/apps/SuperPixelGui/UndoManager.h b/apps/SuperPixelGui/UndoManager.h new file mode 100644 index 0000000..194fe22 --- /dev/null +++ b/apps/SuperPixelGui/UndoManager.h @@ -0,0 +1,58 @@ +#ifndef UNDOMANAGER_H +#define UNDOMANAGER_H + +#include + +// T -> What type +// N -> Length of history +template +class UndoManager { +public: + constexpr UndoManager() noexcept: beg(0),end(0),pos(-1){} + UndoManager(const UndoManager & other): beg(other.beg), end(other.end), pos(other.pos){ + for (int i = 0; i < N; i++) { + array[i] = other.array[i]; + } + } + void pushItem(){ + pos = (pos+1)%N; + end = (pos+1)%N; //Always: pos < end ! + if (beg == end) beg = (beg+1)%N; + } + const T* getItem() const{ + if (validIdx(pos)) + return &array[pos]; + return nullptr; + } + T* getItem(){ + if (validIdx(pos)) + return &array[pos]; + return nullptr; + } + void undo(){ + if (undoAvailable()) pos=(pos-1)%N; + } + void redo(){ + if (redoAvailable()) pos=(pos+1)%N; + } + bool undoAvailable(){ + return validIdx((pos-1)%N); + } + bool redoAvailable(){ + return validIdx((pos+1)%N); + } +protected: + bool validIdx(int idx) const{ + if (idx >= N || idx <0) return false; + if (beg < end){ + return beg<= idx && idx < end; + }else if (end < beg){ + return beg <= idx || idx < end; + } + return false; + } +private: + std::array array; + int beg, end, pos; //beg <= pos + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ${MACOSX_BUNDLE_EXECUTABLE_NAME} + CFBundleGetInfoString + ${MACOSX_BUNDLE_INFO_STRING} + CFBundleIconFile + ${MACOSX_BUNDLE_ICON_FILE} + CFBundleIdentifier + ${MACOSX_BUNDLE_GUI_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleLongVersionString + ${MACOSX_BUNDLE_LONG_VERSION_STRING} + CFBundleName + ${MACOSX_BUNDLE_BUNDLE_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + ${MACOSX_BUNDLE_SHORT_VERSION_STRING} + CFBundleSignature + ???? + CFBundleVersion + ${MACOSX_BUNDLE_BUNDLE_VERSION} + CSResourcesFileMapped + + LSRequiresCarbon + + NSHumanReadableCopyright + ${MACOSX_BUNDLE_COPYRIGHT} + NSPrincipalClass + NSApplication + NSHighResolutionCapable + + + diff --git a/apps/SuperPixelGui/genIcns.sh b/apps/SuperPixelGui/genIcns.sh new file mode 100644 index 0000000..4b5ad87 --- /dev/null +++ b/apps/SuperPixelGui/genIcns.sh @@ -0,0 +1,13 @@ +mkdir superpi.iconset +sips -z 16 16 superpi.png --out superpi.iconset/icon_16x16.png +sips -z 32 32 superpi.png --out superpi.iconset/icon_16x16@2x.png +sips -z 32 32 superpi.png --out superpi.iconset/icon_32x32.png +sips -z 64 64 superpi.png --out superpi.iconset/icon_32x32@2x.png +sips -z 128 128 superpi.png --out superpi.iconset/icon_128x128.png +sips -z 256 256 superpi.png --out superpi.iconset/icon_128x128@2x.png +sips -z 256 256 superpi.png --out superpi.iconset/icon_256x256.png +sips -z 512 512 superpi.png --out superpi.iconset/icon_256x256@2x.png +sips -z 512 512 superpi.png --out superpi.iconset/icon_512x512.png +sips -z 1024 1024 superpi.png --out superpi.iconset/icon_512x512.png +iconutil -c icns superpi.iconset +rm -r superpi.iconset diff --git a/apps/SuperPixelGui/imgwdg.cpp b/apps/SuperPixelGui/imgwdg.cpp new file mode 100644 index 0000000..43791e5 --- /dev/null +++ b/apps/SuperPixelGui/imgwdg.cpp @@ -0,0 +1,196 @@ +#include "imgwdg.h" +#include "utils.h" +#include + +TripleImageConversion::TripleImageConversion() : img(nullptr), mat(nullptr), data(nullptr) { +} + +void TripleImageConversion::setImg(const QImage &img) { + auto tmp = new QImage(img); + setImg(QPixmap::fromImage(img)); + this->img = tmp; +} + + +void TripleImageConversion::setImg(const QPixmap &px) { + this->pxm = px; + if (img != nullptr) delete img; + img = nullptr; + if (mat != nullptr) delete mat; + mat = nullptr; +} + +void TripleImageConversion::setImg(const cv::Mat &mat) { + auto tmp = new cv::Mat(mat); + setImg(utils::mat2QImage(mat)); + this->mat = tmp; +} + + +QImage TripleImageConversion::getImg() { + if (img == nullptr) { + img = new QImage(pxm.toImage()); + } + return *img; +} + +cv::Mat TripleImageConversion::getMat() { + if (mat == nullptr) { + mat = new cv::Mat(utils::qImage2Mat(getImg())); + } + return *mat; +} + +QPixmap TripleImageConversion::getPixmap() const { + return pxm; +} + +bool TripleImageConversion::isSet() const { + return !pxm.isNull(); +} + + +void TripleImageConversion::clean() { + if (data != nullptr) { + data->decrement(); + if (data->zeroReference()) delete data; + } + if (img != nullptr) delete img; + if (mat != nullptr) delete mat; + img = nullptr; + pxm = QPixmap(); + mat = nullptr; + data = nullptr; +} + +TripleImageConversion::~TripleImageConversion() { + clean(); +} + +void ImgWdg::init() { + this->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + this->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + pitem = new QGraphicsPixmapItem(); + scene = new QGraphicsScene(); + scene->addItem(pitem); + setScene(scene); +} + +ImgWdg::ImgWdg(QWidget *parent) : QGraphicsView(parent) { + init(); +} + +ImgWdg::ImgWdg(ImgWdg *other, QWidget *parent) : QGraphicsView(parent), history(other->history) { + init(); + reloadPixmap(); +} + +void ImgWdg::resizeEvent(QResizeEvent *event) { + QGraphicsView::resizeEvent(event); + this->fitInView(pitem, Qt::KeepAspectRatio); +} + +TripleImageConversion *ImgWdg::currentImage() { + return history.getItem(); +} + +const TripleImageConversion *ImgWdg::currentImage() const { + return history.getItem(); +} + +TripleImageConversion *ImgWdg::createOne() { + history.pushItem(); + history.getItem()->clean(); + return history.getItem(); +} + + +void ImgWdg::reloadPixmap() { + auto current = currentImage(); + if (current == nullptr) return; + pitem->setPixmap(current->getPixmap()); + this->fitInView(pitem, Qt::KeepAspectRatio); +} + +void ImgWdg::showImg(const QImage &img) { + createOne()->setImg(img); + reloadPixmap(); +} + +void ImgWdg::showImg(const QPixmap &px) { + createOne()->setImg(px); + reloadPixmap(); +} + +void ImgWdg::showImg(const cv::Mat &mat) { + createOne()->setImg(mat); + reloadPixmap(); +} + +void ImgWdg::replaceImg(const QImage & img){ + auto item = history.getItem(); + item->clean(); + item->setImg(img); + reloadPixmap(); +} + +void ImgWdg::replaceImg(const QPixmap & img){ + auto item = history.getItem(); + item->clean(); + item->setImg(img); + reloadPixmap(); +} + +void ImgWdg::replaceImg(const cv::Mat & img){ + auto item = history.getItem(); + item->clean(); + item->setImg(img); + reloadPixmap(); +} + + +QImage ImgWdg::getImg() { + auto current = currentImage(); + if (current != nullptr) + return current->getImg(); + return QImage(); +} + +cv::Mat ImgWdg::getMat() { + auto current = currentImage(); + if (current != nullptr) + return current->getMat(); + return cv::Mat(); +} + +QPixmap ImgWdg::getPixmap() const { + auto current = currentImage(); + if (current != nullptr) + return current->getPixmap(); + return QPixmap(); +} + +void ImgWdg::goNext() { + history.redo(); + reloadPixmap(); +} + +void ImgWdg::goPrev() { + history.undo(); + reloadPixmap(); +} + + +bool ImgWdg::hasNext() { + return history.redoAvailable(); +} + +bool ImgWdg::hasPrev() { + return history.undoAvailable(); +} + + +ImgWdg::~ImgWdg() { +} + + diff --git a/apps/SuperPixelGui/imgwdg.h b/apps/SuperPixelGui/imgwdg.h new file mode 100644 index 0000000..96dc71b --- /dev/null +++ b/apps/SuperPixelGui/imgwdg.h @@ -0,0 +1,243 @@ +#ifndef IMGWDG_H +#define IMGWDG_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "UndoManager.h" + +namespace priv { + template + class TripleImageConversionData; + + class TripleImageData { + public: + TripleImageData() : reference(0) { + } + + virtual ~TripleImageData() { + } + + virtual const std::type_info &type_info() const = 0; + + void increment() { + reference++; + } + + void decrement() { + reference--; + } + + bool zeroReference() { + return reference == 0; + } + + int refCount() const{return reference;} + + template + std::unique_ptr & getData(); + + template + const std::unique_ptr & getData() const; + + private: + int reference; + }; + + template + class TripleImageConversionData : public TripleImageData { + public: + TripleImageConversionData(std::unique_ptr && d) { + data = std::move(d); + } + + ~TripleImageConversionData(){ + } + + std::unique_ptr & getData() { + return data; + } + + const std::unique_ptr & getData() const{ + return data; + } + + virtual const std::type_info &type_info() const { + return typeid(T); + } + + private: + std::unique_ptr data; + }; + + + template + std::unique_ptr &TripleImageData::getData() { + static std::unique_ptr nullp; + if (type_info() == typeid(T)) + return static_cast *>(this)->getData(); + return nullp; + } + template + const std::unique_ptr& TripleImageData::getData() const{ + const static std::unique_ptr nullp; + if (type_info() == typeid(T)) + return static_cast *>(this)->getData(); + return nullp; + } +} + +// Automatically convert between QImage, QPixmap and cv::Mat +class TripleImageConversion { + public: + TripleImageConversion(); + + TripleImageConversion(const TripleImageConversion &other) : img(nullptr), mat(nullptr), pxm(other.pxm) { + if (other.img != nullptr) img = new QImage(*other.img); + if (other.mat != nullptr) mat = new cv::Mat(*other.mat); + if (other.data != nullptr) { + data = other.data; + data->increment(); + } + } + + TripleImageConversion &operator=(const TripleImageConversion &other) { + clean(); + pxm = other.pxm.copy(); + if (other.img != nullptr) { + img = new QImage(*other.img); + } + if (other.mat != nullptr) { + mat = new cv::Mat(*other.mat); + } + if (other.data != nullptr) { + data = other.data; + data->increment(); + } + return *this; + } + + void setImg(const QImage &img); + + void setImg(const QPixmap &px); + + void setImg(const cv::Mat &mat); + + bool isSet() const; + + void clean(); + + template + void setMoreData(std::unique_ptr && p) { + if (data != nullptr) { + data->decrement(); + if (data->zeroReference()) delete data; + } + data = new priv::TripleImageConversionData(std::move(p)); + data->increment(); + }; + + template + const std::unique_ptr & moreData() const{ + static std::unique_ptr nullp; + if (data != nullptr) { + return data->getData(); + } + return nullp; + } + + template + std::unique_ptr & moreData() { + static std::unique_ptr nullp; + if (data != nullptr) { + if (data->refCount()>1){ + data->decrement(); + data = new priv::TripleImageConversionData( + std::unique_ptr(new T(*data->getData())) + ); + data->increment(); + } + return data->getData(); + } + return nullp; + } + + QImage getImg(); + + cv::Mat getMat(); + + QPixmap getPixmap() const; + + virtual ~TripleImageConversion(); + + private: + QPixmap pxm; + QImage *img; + cv::Mat *mat; + priv::TripleImageData *data; +}; + +// Show images with history (5 images in history) +class ImgWdg : public QGraphicsView { + Q_OBJECT + public: + explicit ImgWdg(QWidget *parent = nullptr); + + ImgWdg(ImgWdg *other, QWidget *parent = nullptr); + + void showImg(const QImage &img); + + void showImg(const QPixmap &px); + + void showImg(const cv::Mat &mat); + + void replaceImg(const QImage & img); + + void replaceImg(const QPixmap & px); + + void replaceImg(const cv::Mat & mat); + + void reloadPixmap(); + + QImage getImg(); + + cv::Mat getMat(); + + QPixmap getPixmap() const; + + virtual ~ImgWdg(); + + TripleImageConversion *currentImage(); + + const TripleImageConversion *currentImage() const; + + bool hasNext(); + + bool hasPrev(); + + public slots: + void goNext(); + + void goPrev(); + + protected: + TripleImageConversion *createOne(); + + void resizeEvent(QResizeEvent *event); + + private: + void init(); + QGraphicsScene *scene; + QGraphicsPixmapItem *pitem; + UndoManager history; +}; + +#endif // IMGWDG_H diff --git a/apps/SuperPixelGui/main.cpp b/apps/SuperPixelGui/main.cpp new file mode 100644 index 0000000..83a9b99 --- /dev/null +++ b/apps/SuperPixelGui/main.cpp @@ -0,0 +1,13 @@ +#include +#include "mainwindow.h" + +int main(int argc, char **argv) { + QApplication app(argc, argv); + MainWindow w; +#ifndef Q_OS_OSX + QIcon icon(":/superpi.png"); + w.setWindowIcon(icon); +#endif + w.show(); + return app.exec(); +} diff --git a/apps/SuperPixelGui/mainwindow.cpp b/apps/SuperPixelGui/mainwindow.cpp new file mode 100644 index 0000000..5d4a115 --- /dev/null +++ b/apps/SuperPixelGui/mainwindow.cpp @@ -0,0 +1,487 @@ +#include "mainwindow.h" +#include "utils.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0)) +#define QStringLiteral QString +#endif + +struct superpixeldata { + vector slicv; + cv::Mat mat; + bool drawContour = true; + bool fillCluster = false; + int currentIdx = 0; + Vec3b color = Vec3b(0,0,0); + + RSlic::Pixel::Slic2P currentItem(){ + assert(0<= currentIdx && currentIdx <= slicv.size()); + return slicv[currentIdx]; + } +}; + +WorkerObject::WorkerObject(ThreadPoolP p, QObject *parent) : QObject(parent), pool(p) { +} + +void WorkerObject::work(cv::Mat m, SlicSetting *settings, QWidget* tabwdg) { + std::chrono::time_point start, end; + start = std::chrono::system_clock::now(); + + int step = sqrt(m.cols * m.rows / settings->count); + auto grad = RSlic::Pixel::buildGrad(m); + emit message(tr("Initializing Slic ...")); + auto slic = RSlic::Pixel::Slic2::initialize(utils::makeLabIfNecessary(m), grad, step, settings->stiffness, pool); + if (slic.get() == nullptr) { + emit failed(tr("Wrong Parameter"), tr("No Superpixel can be build. May you should play with the parameters")); + return; + } + emit message(tr("Iterating")); + vector iterators; + if (settings->saveIterations) + iterators.reserve(settings->iteration); + for (int i = 0; i < settings->iteration; i++) { + if (settings->slico) + slic = slic->iterateZero(RSlic::Pixel::distanceColor()); + else + slic = slic->iterate(RSlic::Pixel::distanceColor()); + if (settings->saveIterations) + iterators.push_back(slic); + emit message(tr("Iterating %1 of %2").arg(i + 1).arg(settings->iteration)); + emit progress((i + 1) * 100 / (settings->iteration + 1)); + } + emit message(tr("Finalizing ...")); + slic = slic->finalize(RSlic::Pixel::distanceColor()); + + end = std::chrono::system_clock::now(); + std::chrono::duration elapsed_seconds = end-start; + + iterators.push_back(slic); + emit finished(std::move(iterators),tabwdg); + emit message(tr("Needed %1 sec").arg(elapsed_seconds.count())); +} + +MainWindow::MainWindow(QWidget *parent) : + QMainWindow(parent), settings(nullptr) { + qRegisterMetaType(); + qRegisterMetaType("cv::Mat const&"); + qRegisterMetaType("SlicSetting*"); + qRegisterMetaType("shared_ptrconst&"); + qRegisterMetaType>("vectorconst&"); + + this->setWindowTitle(QStringLiteral("SuperPixelGui")); + this->setUnifiedTitleAndToolBarOnMac(true); + this->setAcceptDrops(true); + tabs = new QTabWidget(this); + tabs->setMovable(true); + tabs->setDocumentMode(true); + tabs->setTabsClosable(true); + connect(tabs, SIGNAL(currentChanged(int)), SLOT(updateTitleForIndex(int))); + connect(tabs, SIGNAL(currentChanged(int)), SLOT(updateActions())); + connect(tabs, SIGNAL(tabCloseRequested(int)),SLOT(closeTab(int))); + setCentralWidget(tabs); + + worker = new WorkerObject(std::make_shared(std::thread::hardware_concurrency())); + worker->moveToThread(&workerThread); + workerThread.start(); + + connect(worker, SIGNAL(failed( + const QString &, const QString &)), this, SLOT(onWorkerFailed( + const QString &, const QString &)), Qt::QueuedConnection); + connect(worker, SIGNAL(finished(vector,QWidget*)), this, SLOT(onWorkerFinished(vector,QWidget*)), Qt::QueuedConnection); + connect(worker, SIGNAL(progress(int)), this, SLOT(onWorkerProgress(int)), Qt::QueuedConnection); + connect(worker, SIGNAL(message( + const QString&)), this, SLOT(onWorkerMessage( + const QString &))); + + colorDlg = new QColorDialog(this); + colorDlg->setCurrentColor(QColor(0,0,0)); + connect(colorDlg,SIGNAL(colorSelected(QColor)),SLOT(setDrawColor(QColor))); + + auto style = QApplication::style(); + + QToolBar *bar = this->addToolBar(tr("File")); + QToolBar* viewbar = this->addToolBar(tr("View")); + bar->addAction(style->standardIcon(QStyle::SP_DialogOpenButton), "Load...", this, SLOT(loadImage())); + bar->addAction(style->standardIcon(QStyle::SP_DialogSaveButton), "Save...", this, SLOT(saveImage())); + colorClusterAction = new QAction("Color", this); + colorClusterAction->setCheckable(true); + colorClusterAction->setChecked(true); + connect(colorClusterAction, SIGNAL(triggered(bool)), SLOT(setClusterFill(bool))); + viewbar->addAction(colorClusterAction); + drawContourAction = new QAction("Draw", this); + drawContourAction->setCheckable(true); + colorClusterAction->setChecked(true); + connect(drawContourAction, SIGNAL(triggered(bool)), SLOT(setClusterContour(bool))); + viewbar->addAction(drawContourAction); + viewbar->addAction("Set Color",colorDlg,SLOT(open())); + bar->addSeparator(); + goPrevAction = bar->addAction(style->standardIcon(QStyle::SP_ArrowLeft), "Prev", this, SLOT(goPrev())); + goNextAction = bar->addAction(style->standardIcon(QStyle::SP_ArrowRight), "Next", this, SLOT(goNext())); + bar->addSeparator(); + bar->addAction("Dupl", this, SLOT(duplicate())); + bar->addAction("Close", this, SLOT(closeCurrentTab()));; + slicIdxSlider = new QSlider(Qt::Horizontal); + slicIdxSlider->setTracking(false); + slicIdxSlider->setValue(0); + slicIdxSlider->setMaximum(1); + slicIdxSlider->setEnabled(true); + connect(slicIdxSlider,SIGNAL(valueChanged(int)),this,SLOT(setSlicIdx(int))); + viewbar->addSeparator(); + viewbar->addWidget(slicIdxSlider); + + QDockWidget *dock = new QDockWidget("Slic", this); + settingWdg = new SlicSettingWidget(dock); + connect(settingWdg, SIGNAL(activate()), this, SLOT(doSuperpixel())); + dock->setWidget(settingWdg); + this->addDockWidget(Qt::LeftDockWidgetArea, dock); + + progressbar = new QProgressBar(this); + progressbar->setRange(0, 100); + this->statusBar()->addPermanentWidget(progressbar); + } + +void MainWindow::setSlicIdx(int i){ + auto& data = currentData(); + if (data == nullptr) return; + data->currentIdx = i; + repaintImg(); +} + +void MainWindow::setDrawColor(const QColor & c){ + auto & data = currentDataConst(); + auto oldColor = data->color; + data->color = Vec3b(c.blue(),c.green(),c.red()); + if (data->drawContour && data->color != oldColor) + repaintImg(); +} + +void MainWindow::goNext() { + ImgWdg *imgwdg = qobject_cast(tabs->currentWidget()); + if (imgwdg == nullptr) return; + imgwdg->goNext(); + updateActions(); +} + +void MainWindow::goPrev() { + ImgWdg *imgwdg = qobject_cast(tabs->currentWidget()); + if (imgwdg == nullptr) return; + imgwdg->goPrev(); + updateActions(); +} + +void MainWindow::onWorkerProgress(int i) { + progressbar->setValue(i); +} + +void MainWindow::onWorkerFinished(vector res,QWidget* wdg) { + settingWdg->setEnabled(true); + this->statusBar()->clearMessage(); + progressbar->setValue(0); + + int idx = tabs->indexOf(wdg); + if (idx < 0) return; // Tab was closed while processing + ImgWdg *imgwdg = qobject_cast(wdg); + if (imgwdg == nullptr) return; + tabs->setCurrentIndex(idx); + + cv::Mat m(imgwdg->getMat()); + imgwdg->showImg(m); + auto more = imgwdg->currentImage(); + if (more != nullptr) { + superpixeldata *data = new superpixeldata; + data->mat = m; + data->slicv = std::move(res); + data->currentIdx = data->slicv.size()-1; + data->drawContour = drawContourAction->isChecked(); + data->fillCluster = colorClusterAction->isChecked(); + auto color = colorDlg->currentColor(); + data->color = Vec3b(color.blue(),color.red(),color.green()); + more->setMoreData(std::unique_ptr(data)); + } + repaintImg(); + updateActions(); +} + +void MainWindow::updateActions() { + ImgWdg *imgwdg; + auto & data = currentDataConst(&imgwdg); + if (imgwdg == nullptr) return ; + goNextAction->setEnabled(imgwdg->hasNext()); + goPrevAction->setEnabled(imgwdg->hasPrev()); + slicIdxSlider->setEnabled(false); + + if (data == nullptr) return; + drawContourAction->setChecked(data->drawContour); + colorClusterAction->setChecked(data->fillCluster); + colorDlg->setCurrentColor(QColor(data->color[2],data->color[1],data->color[0])); + if (data->slicv.size() > 1){ + slicIdxSlider->setEnabled(true); + slicIdxSlider->setMaximum(data->slicv.size()-1); + slicIdxSlider->setValue(data->currentIdx); + } +} + +void MainWindow::setClusterFill(bool b) { + auto &data = currentDataConst(); + if (data == nullptr) return; + if (b!=data->fillCluster){ + auto& data = currentData(); + data->fillCluster = b; + repaintImg(); + } +} + +void MainWindow::setClusterContour(bool b) { + auto& data = currentDataConst(); + if (data == nullptr) return; + if (b!=data->drawContour){ + auto &data = currentData(); + data->drawContour = b; + repaintImg(); + } +} + +void MainWindow::onWorkerMessage(const QString &msg) { + this->statusBar()->showMessage(msg); +} + +void MainWindow::onWorkerFailed(const QString &title, const QString &msg) { + QMessageBox::critical(this, title, msg); + progressbar->setValue(0); + settingWdg->setEnabled(true); + this->statusBar()->clearMessage(); +} + +void MainWindow::doSuperpixel() { + ImgWdg *imgwdg = qobject_cast(tabs->currentWidget()); + if (imgwdg == nullptr) { + loadImage(); + return; + } + + if (settings != nullptr) delete settings; + settings = settingWdg->buildSetting(); + auto pixmap = imgwdg->getPixmap(); + if (pixmap.isNull()) { + loadImage(); + return; + } + auto mat = imgwdg->getMat(); + settingWdg->setEnabled(false); + QMetaObject::invokeMethod(worker, "work", Qt::QueuedConnection, Q_ARG(cv::Mat, mat), Q_ARG(SlicSetting *, settings),Q_ARG(QWidget*,imgwdg)); +} + +Vec3b operator+(const Vec3d& a,const Vec3b &b){ + return Vec3b(a[0]+b[0],a[1]+b[1],a[2]+b[2]); +} + +void MainWindow::repaintImg() { + ImgWdg* imgwdg; + auto & data = currentDataConst(&imgwdg); + if (data == nullptr) return; + auto more = imgwdg->currentImage(); + cv::Mat m(data->mat); + Mat grad = RSlic::Pixel::buildGrad(m); + if (data->fillCluster) { + m = RSlic::Pixel::drawCluster(m, data->currentItem()->getClusters()); + } + if (data->drawContour) { + m = RSlic::Pixel::contourCluster(m, data->currentItem()->getClusters(), data->color); + } + more->setImg(m); + imgwdg->reloadPixmap(); +} + +void MainWindow::duplicate() { + ImgWdg *imgwdg = qobject_cast(tabs->currentWidget()); + QString title = tabs->tabText(tabs->currentIndex()); + if (imgwdg == nullptr) return; + ImgWdg *newone = new ImgWdg(imgwdg, this); + tabs->addTab(newone, QStringLiteral("%1+").arg(title)); +} + +void MainWindow::closeCurrentTab() { + int idx = tabs->currentIndex(); + if (idx < 0) return; + auto wdg = this->tabs->widget(idx); + this->tabs->removeTab(idx); + wdg->deleteLater(); +} + + +MainWindow::~MainWindow() { + if (settings != nullptr) delete settings; + workerThread.quit(); + workerThread.wait(); +} + +#include + +void MainWindow::loadImage() { + QString file = QFileDialog::getOpenFileName(this); + if (file.isNull()) return; + addImage(file); +} + +void MainWindow::addImage(const QString &file) { + auto imgwdg = addImgWdg(file); +#define USE_CV +#ifdef USE_CV + cv::Mat m = cv::imread(file.toStdString()); + imgwdg->showImg(m); +#else + QImage img(file); + imgwdg->showImg(img); +#endif +} + +void MainWindow::saveImage() { + QString file = QFileDialog::getSaveFileName(this); + if (file.isNull()) return; + ImgWdg *imgwdg = (ImgWdg *) tabs->currentWidget(); + if (imgwdg == nullptr) return; + imgwdg->getImg().save(file); +} + +ImgWdg *MainWindow::addImgWdg(const QString &title) { + auto imgwdg = new ImgWdg(this); + tabs->addTab(imgwdg, title); + return imgwdg; +} + +void MainWindow::dragEnterEvent(QDragEnterEvent *event) { + if (event->mimeData()->hasImage() || event->mimeData()->hasUrls() || event->mimeData()->hasText()) { + event->acceptProposedAction(); + } +} + +void MainWindow::dropEvent(QDropEvent *event) { + if (event->mimeData()->hasImage()) { + QImage img = qvariant_cast(event->mimeData()->imageData()); + ImgWdg *w = addImgWdg("drop content"); + w->showImg(img); + event->acceptProposedAction(); + } else if (event->mimeData()->hasUrls()) { + auto urls = event->mimeData()->urls(); + for (auto url : urls) { + if (!url.isLocalFile()) continue; + auto filename = url.toLocalFile(); + if (!QFile::exists(filename)) continue; + addImage(filename); + } + event->acceptProposedAction(); + } else if (event->mimeData()->hasText()) { + auto text = event->mimeData()->text(); + if (!QFile::exists(text)) return; + addImage(text); + event->acceptProposedAction(); + } +} + +namespace { + inline QSpinBox *createBox(int min, int max, int def, QWidget *parent = nullptr) { + auto res = new QSpinBox(parent); + res->setMinimum(min); + res->setMaximum(max); + res->setValue(def); + return res; + } + + inline QHBoxLayout *titleMe(QWidget *w, const QString &title, QWidget *parent = nullptr) { + QHBoxLayout *res = new QHBoxLayout(parent); + res->addWidget(new QLabel(title)); + res->addWidget(w); + return res; + } +} + +SlicSettingWidget::SlicSettingWidget(QWidget *parent) : QWidget(parent) { + QPushButton *b = new QPushButton(tr("Do")); + connect(b, SIGNAL(pressed()), this, SIGNAL(activate())); + iterationBox = createBox(1, 1000, 10); + stiffnessBox = createBox(1, 250, 40); + countBox = createBox(10, 1000, 400); + slicoBox = new QGroupBox(tr("No Slico"), this); + slicoBox->setCheckable(true); + saveBox = new QCheckBox(tr("Save Iterations"), this); + saveBox->setChecked(true); + + slicoBox->setLayout(titleMe(stiffnessBox, tr("Stiffness"))); + + QVBoxLayout *lay = new QVBoxLayout(); + lay->addWidget(b); + lay->addLayout(titleMe(iterationBox, tr("Iterations"))); + lay->addLayout(titleMe(countBox, tr("Number of Superpixels"))); + lay->addWidget(slicoBox); + QFrame * line = new QFrame(); + line->setFrameShape(QFrame::HLine); + line->setFrameShadow(QFrame::Sunken); + lay->addWidget(line); + lay->addWidget(saveBox); + lay->addStretch(); + + setLayout(lay); +} + +SlicSetting *SlicSettingWidget::buildSetting() { + SlicSetting *res = new SlicSetting; + res->slico = !slicoBox->isChecked(); + res->iteration = iterationBox->value(); + res->stiffness = stiffnessBox->value(); + res->count = countBox->value(); + res->saveIterations = saveBox->isChecked(); + return res; +} + +void MainWindow::updateTitleForIndex(int idx) { + if (idx < 0) return; + auto name = tabs->tabText(idx); + setWindowTitle(QStringLiteral("%1 - SuperPixelGui").arg(name)); +} + +void MainWindow::closeTab(int idx) { + if (idx < 0) return; + auto wdg = this->tabs->widget(idx); + this->tabs->removeTab(idx); + wdg->deleteLater(); +} + +const std::unique_ptr &MainWindow::currentDataConst(ImgWdg** currentWdg) const{ + if (currentWdg != nullptr) + *currentWdg = nullptr; + ImgWdg *imgwdg = qobject_cast(tabs->currentWidget()); + if (imgwdg == nullptr) return nullp; + if (currentWdg != nullptr) + *currentWdg = imgwdg; + const TripleImageConversion* const more = imgwdg->currentImage(); + if (more == nullptr) return nullp; + return more->moreData(); +} + +std::unique_ptr &MainWindow::currentData(ImgWdg** currentWdg){ + if (currentWdg != nullptr) + *currentWdg = nullptr; + ImgWdg *imgwdg = qobject_cast(tabs->currentWidget()); + if (imgwdg == nullptr) return nullp; + if (currentWdg != nullptr) + *currentWdg = imgwdg; + auto more = imgwdg->currentImage(); + if (more == nullptr) return nullp; + return more->moreData(); +} diff --git a/apps/SuperPixelGui/mainwindow.h b/apps/SuperPixelGui/mainwindow.h new file mode 100644 index 0000000..8850e04 --- /dev/null +++ b/apps/SuperPixelGui/mainwindow.h @@ -0,0 +1,155 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include +#include +#include +#include <3rd/ThreadPool.h> +#include +#include +#include +#include "imgwdg.h" +#include +#include +#include +#include +#include +#include +#include +#include + +class ImgWdg; +class SlicSettingWidget; +struct SlicSetting; + +Q_DECLARE_METATYPE(RSlic::Pixel::Slic2P) +Q_DECLARE_METATYPE(vector) +Q_DECLARE_METATYPE(cv::Mat) +Q_DECLARE_METATYPE(SlicSetting*) + +// Computes Superpixel in different Thread. +// But communicates with Gui-Thread +class WorkerObject : public QObject { + Q_OBJECT +public: + WorkerObject(ThreadPoolP p, QObject *parent = nullptr); + +public slots: + void work(cv::Mat m, SlicSetting *setting, QWidget* wdg); +signals: + void message(const QString &); + + void progress(int i); + + void finished(vector res, QWidget* wdg); + + void failed(const QString &title, const QString &msg); + +private: + ThreadPoolP pool; +}; + +struct superpixeldata; +class MainWindow : public QMainWindow { + Q_OBJECT +public: + explicit MainWindow(QWidget *parent = nullptr); + + virtual ~MainWindow(); + + signals: + +public slots: + void loadImage(); + + void doSuperpixel(); + + void repaintImg(); + + void goPrev(); + + void goNext(); + + void duplicate(); + + void closeCurrentTab(); + + void addImage(const QString &filename); + + void saveImage(); + +protected: + void dragEnterEvent(QDragEnterEvent *event); + + void dropEvent(QDropEvent *event); + + ImgWdg *addImgWdg(const QString &title); + + std::unique_ptr ¤tData(ImgWdg** currentWdg = nullptr); + const std::unique_ptr ¤tDataConst(ImgWdg** currentWdg = nullptr) const; + +private slots: + void onWorkerMessage(const QString &msg); + + void onWorkerProgress(int i); + + void onWorkerFinished(vector res, QWidget*wdg); + + void onWorkerFailed(const QString &title, const QString &msg); + + void updateTitleForIndex(int idx); + + void closeTab(int idx); + + void updateActions(); + + void setClusterFill(bool); + + void setClusterContour(bool); + + void setSlicIdx(int idx); + + void setDrawColor(const QColor &); + +private: + QTabWidget *tabs; + WorkerObject *worker; + QThread workerThread; + QProgressBar *progressbar; + SlicSetting *settings; + SlicSettingWidget *settingWdg; + QSlider * slicIdxSlider; + QAction *colorClusterAction, *drawContourAction, *goNextAction, *goPrevAction; + std::unique_ptr nullp; + QColorDialog * colorDlg; +}; + +struct SlicSetting { + constexpr SlicSetting() noexcept: slico(false), iteration(10), stiffness(40), count(400), saveIterations(true) {} + + bool slico; + int iteration; + int stiffness; + int count; + + bool saveIterations; +}; + +class SlicSettingWidget : public QWidget { + Q_OBJECT +public: + explicit SlicSettingWidget(QWidget *parent = nullptr); + + SlicSetting *buildSetting(); // Result have to be removed by hand +signals: + void activate(); + +private: + QSpinBox *iterationBox, *stiffnessBox, *countBox; + QGroupBox *slicoBox; + QCheckBox *saveBox; +}; + + +#endif // MAINWINDOW_H diff --git a/apps/SuperPixelGui/superpi.icns b/apps/SuperPixelGui/superpi.icns new file mode 100644 index 0000000..4f8309d Binary files /dev/null and b/apps/SuperPixelGui/superpi.icns differ diff --git a/apps/SuperPixelGui/superpi.png b/apps/SuperPixelGui/superpi.png new file mode 100644 index 0000000..0ba3d67 Binary files /dev/null and b/apps/SuperPixelGui/superpi.png differ diff --git a/apps/SuperPixelGui/superpixelgui.qrc b/apps/SuperPixelGui/superpixelgui.qrc new file mode 100644 index 0000000..794a47b --- /dev/null +++ b/apps/SuperPixelGui/superpixelgui.qrc @@ -0,0 +1,5 @@ + + + superpi.png + + diff --git a/apps/SuperPixelGui/utils.cpp b/apps/SuperPixelGui/utils.cpp new file mode 100644 index 0000000..2bcd2cb --- /dev/null +++ b/apps/SuperPixelGui/utils.cpp @@ -0,0 +1,206 @@ +#include "utils.h" +#include +/*cv::Mat utils::qImage2Mat(const QImage & src){ + cv::Mat tmp(src.height(),src.width(),CV_8UC3,(uchar*)src.bits(),src.bytesPerLine()); + cv::Mat result; + cv::cvtColor(tmp, result,CV_RGB2BGR); + return result; +}*/ +//Copied from https://github.com/stereomatchingkiss/blogCodes2/blob/master/libs/openCVToQt.cpp +#include +#include + +#include + + +namespace { + + inline QImage mat_to_qimage_ref_policy(cv::Mat &mat, QImage::Format format) { + return QImage(mat.data, mat.cols, mat.rows, mat.step, format); + } + + inline QImage mat_to_qimage_cpy_policy(cv::Mat const &mat, QImage::Format format) { + return QImage(mat.data, mat.cols, mat.rows, mat.step, format).copy(); + } + +/** +* @brief copy QImage into cv::Mat +*/ + struct qimage_to_mat_cpy_policy { + static cv::Mat start(QImage const &img, int format) { + return cv::Mat(img.height(), img.width(), format, const_cast(img.bits()), img.bytesPerLine()).clone(); + } + }; + +/** +* @brief make Qimage and cv::Mat share the same buffer, the resource +* of the cv::Mat must not deleted before the QImage finish +* the jobs. +*/ + struct qimage_to_mat_ref_policy { + static cv::Mat start(QImage &img, int format) { + return cv::Mat(img.height(), img.width(), format, img.bits(), img.bytesPerLine()); + } + }; + +/** +* @brief generic class for reducing duplicate codes +*/ + template + struct qimage_to_mat { + template + static cv::Mat run(Image &&img, bool swap); + }; + +/** +*@brief transform QImage to cv::Mat +*@param img : input image +*@param swap : true : swap RGB to BGR; false, do nothing +*/ + template + template + cv::Mat qimage_to_mat::run(Image &&img, bool swap) { + if (img.isNull()) { + return cv::Mat(); + } + + switch (img.format()) { + case QImage::Format_RGB888: { + cv::Mat result = Policy::start(img, CV_8UC3); + if (swap) { + cv::cvtColor(result, result, CV_RGB2BGR); + } + return result; + } + case QImage::Format_Indexed8: { + return Policy::start(img, CV_8U); + } + case QImage::Format_RGB32: + case QImage::Format_ARGB32: + case QImage::Format_ARGB32_Premultiplied: { + return Policy::start(img, CV_8UC4); + } + default: + break; + } + + return cv::Mat(); + } + +/** +*@brief copy cv::Mat into QImage +* +*@param mat : input mat +*@param swap : true : swap RGB to BGR; false, do nothing +*/ + QImage mat_to_qimage_cpy(cv::Mat const &mat, bool swap) { + if (!mat.empty()) { + switch (mat.type()) { + + case CV_8UC3 : { + if (swap) { + return mat_to_qimage_cpy_policy(mat, QImage::Format_RGB888).rgbSwapped(); + } else { + return mat_to_qimage_cpy_policy(mat, QImage::Format_RGB888); + } + } + + case CV_8U : { + return mat_to_qimage_cpy_policy(mat, QImage::Format_Indexed8); + } + + case CV_8UC4 : { + return mat_to_qimage_cpy_policy(mat, QImage::Format_ARGB32); + } + + } + } + + return QImage(); + } + +/** +*@brief make Qimage and cv::Mat share the same buffer, the resource +* of the cv::Mat must not deleted before the QImage finish +* the jobs. +* +*@param mat : input mat +*@param swap : true : swap RGB to BGR; false, do nothing +*/ + QImage mat_to_qimage_ref(cv::Mat &mat, bool swap) { + if (!mat.empty()) { + switch (mat.type()) { + + case CV_8UC3 : { + if (swap) { + cv::cvtColor(mat, mat, CV_BGR2RGB); + } + + return mat_to_qimage_ref_policy(mat, QImage::Format_RGB888); + } + + case CV_8U : { + return mat_to_qimage_ref_policy(mat, QImage::Format_Indexed8); + } + + case CV_8UC4 : { + return mat_to_qimage_ref_policy(mat, QImage::Format_ARGB32); + } + + } + } + + return QImage(); + } + +/** +*@brief transform QImage to cv::Mat by copy QImage to cv::Mat +*@param img : input image +*@param swap : true : swap RGB to BGR; false, do nothing +*/ + cv::Mat qimage_to_mat_cpy(QImage const &img, bool swap) { + return qimage_to_mat::run(img, swap); + } + +/** +*@brief transform QImage to cv::Mat by sharing the buffer +*@param img : input image +*@param swap : true : swap RGB to BGR; false, do nothing +*/ + cv::Mat qimage_to_mat_ref(QImage &img, bool swap) { + return qimage_to_mat::run(img, swap); + } +} + +cv::Mat utils::qImage2Mat(const QImage &img) { + return qimage_to_mat_cpy(img, true); +} + +QImage utils::mat2QImage(const cv::Mat &src) { + return mat_to_qimage_cpy(src, true); +} + +/* +cv::Mat utils::qImage2Mat(const QImage &img) +{ + return cv::Mat(img.height(), img.width(), CV_8UC3, + const_cast(img.bits()), + img.bytesPerLine()).clone(); +} + +QImage utils::mat2QImage(const cv::Mat & src){ + //cv::Mat temp; + //cv::cvtColor(src, temp,CV_BGR2RGB); + QImage dest((const uchar *) src.data, src.cols, src.rows, src.step, QImage::Format_RGB888); + dest.bits(); + return dest.rgbSwapped(); +} +*/ +cv::Mat utils::makeLabIfNecessary(const cv::Mat &m) { + cv::Mat res; + if (m.type() == CV_8UC3) { + cv::cvtColor(m, res, CV_BGR2Lab); + } else + res = m; + return res; +} diff --git a/apps/SuperPixelGui/utils.h b/apps/SuperPixelGui/utils.h new file mode 100644 index 0000000..c300e88 --- /dev/null +++ b/apps/SuperPixelGui/utils.h @@ -0,0 +1,14 @@ +#ifndef UTILS_H +#define UTILS_H + +#include +#include + +namespace utils { + cv::Mat qImage2Mat(const QImage &img); + + QImage mat2QImage(const cv::Mat &m); + + cv::Mat makeLabIfNecessary(const cv::Mat &m); +} +#endif // UTILS_H diff --git a/cmake_uninstall.cmake.in b/cmake_uninstall.cmake.in new file mode 100644 index 0000000..491624d --- /dev/null +++ b/cmake_uninstall.cmake.in @@ -0,0 +1,22 @@ +#See http://www.cmake.org/Wiki/CMake_FAQ#Can_I_do_.22make_uninstall.22_with_CMake.3F +if(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") + message(FATAL_ERROR "Cannot find install manifest: @CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") +endif(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") + +file(READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" files) +string(REGEX REPLACE "\n" ";" files "${files}") +foreach(file ${files}) + message(STATUS "Uninstalling $ENV{DESTDIR}${file}") + if(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") + exec_program( + "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\"" + OUTPUT_VARIABLE rm_out + RETURN_VALUE rm_retval + ) + if(NOT "${rm_retval}" STREQUAL 0) + message(FATAL_ERROR "Problem when removing $ENV{DESTDIR}${file}") + endif(NOT "${rm_retval}" STREQUAL 0) + else(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") + message(STATUS "File $ENV{DESTDIR}${file} does not exist.") + endif(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") +endforeach(file) diff --git a/lib/3rd/ThreadPool.h b/lib/3rd/ThreadPool.h new file mode 100644 index 0000000..a0c6d0a --- /dev/null +++ b/lib/3rd/ThreadPool.h @@ -0,0 +1,102 @@ +#ifndef THREAD_POOL_H +#define THREAD_POOL_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//https://github.com/progschj/ThreadPool +class ThreadPool { + public: + ThreadPool(size_t); + template + auto enqueue(F&& f, Args&&... args) + -> std::future::type>; + ~ThreadPool(); + size_t threadcount(){return workers.size();} + private: + // need to keep track of threads so we can join them + std::vector< std::thread > workers; + // the task queue + std::queue< std::function > tasks; + + // synchronization + std::mutex queue_mutex; + std::condition_variable condition; + bool stop; +}; + +// the constructor just launches some amount of workers + inline ThreadPool::ThreadPool(size_t threads) +: stop(false) +{ + for(size_t i = 0;i task; + + { + std::unique_lock lock(this->queue_mutex); + this->condition.wait(lock, + [this]{ return this->stop || !this->tasks.empty(); }); + if(this->stop && this->tasks.empty()) + return; + task = std::move(this->tasks.front()); + this->tasks.pop(); + } + + task(); + } + } + ); +} + +// add new work item to the pool + template +auto ThreadPool::enqueue(F&& f, Args&&... args) + -> std::future::type> +{ + using return_type = typename std::result_of::type; + + auto task = std::make_shared< std::packaged_task >( + std::bind(std::forward(f), std::forward(args)...) + ); + + std::future res = task->get_future(); + { + std::unique_lock lock(queue_mutex); + + // don't allow enqueueing after stopping the pool + if(stop) + throw std::runtime_error("enqueue on stopped ThreadPool"); + + tasks.emplace([task](){ (*task)(); }); + } + condition.notify_one(); + return res; +} + +// the destructor joins all threads +inline ThreadPool::~ThreadPool() +{ + { + std::unique_lock lock(queue_mutex); + stop = true; + } + condition.notify_all(); + for(std::thread &worker: workers) + worker.join(); +} + +typedef std::shared_ptr ThreadPoolP; + +#endif diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt new file mode 100644 index 0000000..634bd7b --- /dev/null +++ b/lib/CMakeLists.txt @@ -0,0 +1,25 @@ +project(RSlic) + + +find_package( OpenCV REQUIRED ) +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +option(PARALLEL "Use Threads" OFF) # Solange Ergebnisse sich unterscheiden pro Durchgang +IF(${PARALLEL}) + add_definitions( -DPARALLEL) +ENDIF() + +set(SOURCE_FILES + Pixel/RSlic2.cpp Pixel/ClusterSet.cpp Pixel/RSlic2Draw.cpp Pixel/RSlic2Util.cpp + Voxel/RSlic3.cpp Voxel/ClusterSet.cpp Voxel/RSlic3Utils.cpp + ) +add_library(rslic STATIC ${SOURCE_FILES}) + +target_link_libraries(rslic ${OpenCV_LIBS}) + +install(TARGETS rslic DESTINATION lib/rslic EXPORT rslic-target) +install(DIRECTORY . DESTINATION include/RSlic + FILES_MATCHING PATTERN "*.h") +install(EXPORT rslic-target DESTINATION lib/rslic) +install(FILES rslic-config.cmake DESTINATION lib/rslic) +# another package example https://github.com/forexample/package-example diff --git a/lib/Pixel/ClusterSet.cpp b/lib/Pixel/ClusterSet.cpp new file mode 100644 index 0000000..1bf5a29 --- /dev/null +++ b/lib/Pixel/ClusterSet.cpp @@ -0,0 +1,109 @@ +#include "clusterset.h" +#include "../priv/Useful.h" + +using namespace RSlic::Pixel; +using RSlic::priv::aSize; + +RSlic::Pixel::ClusterSet::ClusterSet(cv::Mat_ clusters, int clusterCount) + : data{clusters, std::vector(), clusterCount, false} { +} + +Mat_ RSlic::Pixel::ClusterSet::getClusterLabel() const { + return data.clusterLabel; +} + +const vector &RSlic::Pixel::ClusterSet::getCenters() const { + if (!data.centers_calculated) { + refindCenters(); + } + return data.centers; +} + +int RSlic::Pixel::ClusterSet::clusterCount() const noexcept { + return data._clusterCount; +} + +namespace { + inline void setClusterAdjacent(Mat &m, int p1, int p2) { + m.at(p1, p2) = 1; + m.at(p2, p1) = 1; + } +} + +const cv::Mat RSlic::Pixel::ClusterSet::adjacentMatrix() const { + if (!data.adj_calculated) { + refindAdjacent(); + } + return data.adjMatrix; +} + +void RSlic::Pixel::ClusterSet::refindAdjacent() const { + std::lock_guard guard(adjMutex); + if (data.adj_calculated) return; + int size = clusterCount(); + Mat res = Mat::eye(size, size, CV_8UC1); + auto clusterMat = getClusterLabel(); + int w = clusterMat.cols; + int h = clusterMat.rows; + static const int xNeighbour[] = {1, 0, 1}; + static const int yNeighbour[aSize(xNeighbour)] = {0, 1, 1}; + for (int x = 0; x < w - 1; x++) { + for (int y = 0; y < h - 1; y++) { + ClusterInt currentCluster = clusterMat.at(y, x); + for (int i = 0; i < aSize(xNeighbour); i++) { + ClusterInt otherCluster = clusterMat.at(y + yNeighbour[i], x + xNeighbour[i]); + if (currentCluster != otherCluster) { + setClusterAdjacent(res, currentCluster, otherCluster); + } + } + } + } + data.adjMatrix = res; +} + +inline tuple operator+(const tuple &a, const tuple &b) { + return make_tuple(std::get<0>(a) + std::get<0>(b), std::get<1>(a) + std::get<1>(b)); +} + +void RSlic::Pixel::ClusterSet::refindCenters() const { + std::lock_guard guard(centerMutex); + if (data.centers_calculated) return; + + vector centersCounts(data._clusterCount, 0); + vector> centerCoord(data._clusterCount, make_tuple(0l, 0l));//Da Werte länger als sizeof(int) sein kann + + + for (int x = 0; x < data.clusterLabel.cols; x++) { + for (int y = 0; y < data.clusterLabel.rows; y++) { + ClusterInt idx = data.clusterLabel.at(y, x); + if (idx < 0) continue; + centersCounts[idx]++; + centerCoord[idx] = centerCoord[idx] + make_tuple(static_cast(x), static_cast(y)); + } + } + + + for (int i = 0; i < data._clusterCount; i++) { + auto counts = centersCounts[i]; + if (counts == 0) { + data.centers.emplace_back(0, 0); + continue; + } + data.centers.emplace_back(std::get<0>(centerCoord[i]) * 1.0 / counts, std::get<1>(centerCoord[i]) * 1.0 / counts); + } + data.centers_calculated = true; +} + +Mat RSlic::Pixel::ClusterSet::maskOfCluster(ClusterInt idx) const { + int h = data.clusterLabel.rows; + int w = data.clusterLabel.cols; + cv::Mat res = cv::Mat::zeros(h, w, CV_8U); + for (int x = 0; x < w; x++) { + for (int y = 0; y < h; y++) { + if (data.clusterLabel.at(y, x) == idx) + res.at(y, x) = 1; + } + } + return res; +} + diff --git a/lib/Pixel/ClusterSet.h b/lib/Pixel/ClusterSet.h new file mode 100644 index 0000000..6b68963 --- /dev/null +++ b/lib/Pixel/ClusterSet.h @@ -0,0 +1,130 @@ +#ifndef CLUSTERSET2_H +#define CLUSTERSET2_H +#include +#include + +using namespace std; +using namespace cv; + +namespace RSlic{ +namespace Pixel{ +using ClusterInt = int16_t; + +/** +* @brief Representing cluster (also called "Superpixel") with some functionality +*/ + class ClusterSet { + public: + /** + * Constructor for an empty ClusterSet + */ + ClusterSet() : data{cv::Mat(), std::vector(), 0, true, false, cv::Mat()} { + } + + ClusterSet(const ClusterSet &other) : data(other.data) { + } + + ClusterSet(ClusterSet &&other) : data(std::move(other.data)) { + } + + RSlic::Pixel::ClusterSet &operator=(ClusterSet &&other) { + data = std::move(other.data); + return *this; + } + + RSlic::Pixel::ClusterSet &operator=(const ClusterSet &other) { + data = other.data; + return *this; + } + + /** + * Initialize with given centers and cluster-label mat + * @param _centers List with the central points of the clusters + * @param _clusters Mat where _clusters[i,j]=x means that the point i,j belongs to the cluster number x + */ + template, typename std::decay::type>::value + >::type> + ClusterSet(T &&_centers, cv::Mat_ _clusters) + : data{_clusters, std::forward(_centers), 0, true, false, cv::Mat()} { + data._clusterCount = _centers.size(); + } + + /** + * Initialize with given clusters and the cluster's amount. + * The central points will be calculating if necessary. + * @param clusters Mat where clusters[i,j]=x means that the point i,j belongs to the cluster number x + * @param clusterCount the amount of the clusters + * @see getCenters() + */ + ClusterSet(cv::Mat_ clusters, int clusterCount); + + /** + * Returns a mask of the cluster with the number idx. + * @param idx Clusters index + * @return binary mask. + */ + Mat maskOfCluster(ClusterInt idx) const; + + /** + * Returns a Mat m where where m[x,y]=i means that the point x,y belongs to the cluster with the number i + * @return Mat with the cluster label + */ + Mat_ getClusterLabel() const; + + /** + * Returns the central points of the clusters. + * The index indicates the cluster number the central point belongs to. + * This is a lazy-evaluation. + * @return List of central points + */ + const vector &getCenters() const; + + /** + * Returns the amount of clusters. + * @return amount of clusters + */ + int clusterCount() const noexcept; + + /** + * Returns the index of the cluster the point x,y belongs to. + * @param y y-coordinate + * @param x x-coordinate + * @return cluster index + */ + inline ClusterInt at(int y, int x) const { + return data.clusterLabel.at(y, x); + } + + /** + * Returns the adjacent matrix m (type CV_8UC1). + * If m[x,y] == 1 then the cluster with number x + * and the cluster with number y are neighbour. + * It is always m[x,x] == 1 and if m[x,y]==1 + * it means that m[y,x] == 1. + * @return adjacent matrix. + */ + const cv::Mat adjacentMatrix() const; + + private: + struct nonspecial { //for default Copy & Move Construcors + nonspecial(nonspecial &&other) = default; + nonspecial(const nonspecial &other) = default; + nonspecial& operator=(const nonspecial & other) = default; + nonspecial& operator=(nonspecial && other) = default; + + Mat_ clusterLabel; + + mutable vector centers; + int _clusterCount; + mutable bool centers_calculated, adj_calculated; + mutable Mat_ adjMatrix; + } data; + mutable std::mutex centerMutex, adjMutex; + + void refindCenters() const; //computes central points -> data.centers + void refindAdjacent() const; //computes adjacent matrix -> data.adjMatrix + }; +} +} +#endif // CLUSTERSET_H diff --git a/lib/Pixel/RSlic2.cpp b/lib/Pixel/RSlic2.cpp new file mode 100644 index 0000000..fd0a83c --- /dev/null +++ b/lib/Pixel/RSlic2.cpp @@ -0,0 +1,130 @@ +#include "RSlic2.h" + +#include <3rd/ThreadPool.h> +#include +#include +#include +#include + +#include "RSlic2_impl.h" + +#ifndef u_long +#define u_long unsigned long +#endif +#ifndef uint +#define uint unsigned int +#endif + +using RSlic::priv::aSize; +using namespace RSlic; + +Slic2P RSlic::Pixel::Slic2::initialize(const Mat &img, const Mat &grad, int step, int stiffness, ThreadPoolP pool) { + Slic2::Settings *setting = new Slic2::Settings(); + setting->img = img; + setting->step = step; + setting->stiffness = stiffness; + + if (pool.get() == nullptr) + setting->initThreadPool(); + else + setting->pool = pool; + + Slic2 *res = new Slic2(setting); + res->init(grad); + if (res->getClusters().clusterCount() == 0) + return Slic2P(); + return Slic2P(res); +} + +ThreadPoolP RSlic::Pixel::Slic2::threadpool() const { + return setting->pool; +} + +RSlic::Pixel::Slic2::Slic2(Slic2::Settings *s, ClusterSet &&c, const Mat &d) : clusters(std::move(c)), distance(d) { + assert(s != nullptr); + setting = s; + s->__refcount++; +} + +RSlic::Pixel::Slic2::~Slic2() { + int count = --setting->__refcount; + if (count == 0) { + delete setting; + } +} + +namespace { + + template + Vec2i find_local_minimum(const Mat &grad, const Vec2i ¢er) { + auto px = center[0]; + auto py = center[1]; + gtp min_value = std::numeric_limits::infinity(); + cv::Vec2i minPos = center; + for (int x = std::max(px - 1, 0); x < std::min(grad.cols, px + 2); x++) { + for (int y = std::max(0, py - 1); y < std::min(grad.rows, py + 2); y++) { + const gtp currentVal = grad.at(y, x); + if (currentVal < min_value) { + min_value = currentVal; + minPos = Vec2i(x, y); + } + } + } + return minPos; + } + + //find_local_minimum needs a type as template argument. + //find_local_minimum_helper will call find_local_minimum with the right template. + declareCVF_D(find_local_minimum, find_local_minimum_helper, return Vec2i(-1, -1)) + + /** + * Finds the local minimum in a 3x3-neighbourhood. + * @param grad the gradient of the image + * @param p the point + * @return the point with the lowest gradient value. + */ + Vec2i find_local_minimum_(const cv::Mat &grad, const Vec2i &p) { + auto res = ::find_local_minimum_helper(grad.type(), grad, p); + if (res == Vec2i(-1, -1)) return p; + return res; + } +} + +void RSlic::Pixel::Slic2::init(const Mat &grad) { + int s = setting->step; + std::vector centerGrid; + centerGrid.reserve(setting->img.cols / s * setting->img.rows / s); + // initialise the grid and set the its points to the lowest gradient at once + for (int x = s; x < setting->img.cols - s / 2; x += s) { + for (int y = s; y < setting->img.rows - s / 2; y += s) { + Vec2i p(x, y); + centerGrid.push_back(find_local_minimum_(grad, p)); + } + } + + + Mat_ label(setting->img.rows, setting->img.cols, -1); + + distance = Mat_(setting->img.rows, setting->img.cols, DINF); + + clusters = ClusterSet(centerGrid, label); + + max_dist_color = vector(centerGrid.size(), 1); //for slico +} + +int RSlic::Pixel::Slic2::getStep() const { + return setting->step; +} + +int RSlic::Pixel::Slic2::getStiffness() const { + return setting->stiffness; +} + +Mat RSlic::Pixel::Slic2::getImg() const { + return setting->img; +} + + +const RSlic::Pixel::ClusterSet &RSlic::Pixel::Slic2::getClusters() const { + return clusters; +} diff --git a/lib/Pixel/RSlic2.h b/lib/Pixel/RSlic2.h new file mode 100644 index 0000000..93dbcc1 --- /dev/null +++ b/lib/Pixel/RSlic2.h @@ -0,0 +1,137 @@ +#ifndef RSlic2_H +#define RSlic2_H + +#include +#include +#include +#include +#include +#include + +#include "ClusterSet.h" + +using namespace std; +using namespace cv; + +class ThreadPool; + +using ThreadPoolP=shared_ptr; + + +#define DINF std::numeric_limits::infinity() + +namespace RSlic { + namespace Pixel { + + using DistanceFunc=function; + + class Slic2; + + using Slic2P=shared_ptr; + + class Slic2 { + private: + struct Settings; + + public: + + Slic2(const Slic2 &other) = delete; + + Slic2(Slic2 &&other) = default; + + /** + * initialize the algorithm. It build the gradient and set the needed values + * @param img the picture + * @param grad the gradient of the picture. Should be positive. + * @param step how many pixel should belongs (approximately) to a clusters + * @param stiffness the stiffness value + * @param pool ThreadPool for computing parallel. + * @return SharedPointer of the Slic2-Object. (Error -> nullptr) + * @see iterate + */ + static Slic2P initialize(const Mat &img, const Mat &grad, int step, int stiffness, ThreadPoolP pool = ThreadPoolP()); + + ThreadPoolP threadpool() const; + + /** + * Iterating the algorithm. + * @param f the functor with the metrics for the iteration. + * Has to be something like struct Example{..;inline double operator()(const cv::Vec2i &point, const cv::Vec2i &clusterCenter, const cv::Mat &mat, int stiffness, int step){...} ...} + * (stiffness will be passed squared) + * @return a new instance of Slic2 with the results of the iteration. + */ + template + Slic2P iterate(F f) const; + + /** + * Iterating the algorithm with another stiffness. + * @param stiffness the stiffness factor. + * @param f the functor with the metrics for the iteration. + * @return a new instance of Slic2 with the results of the iteration. + * @see iterate + */ + template + Slic2P iterate(int stiffness, F f) const; + + /** + * Iterating the algorithm + * using the zero parameter version of the SLIC algorithm (SLICO) + * @param f the functor with the metrics for the iteration + * @return a new instance of Slic2 with the results of the iteration. + */ + template + Slic2P iterateZero(F f) const; + + + /** + * Enforce connectivity. + * Often this is the last step you want to do. + * But you can do another iteration after here. + * Even if it does not make any sense. + * @param f the functor with the metrics for the iteration + * @return a new instance of Slic2 with the results of the iteration. + */ + template + Slic2P finalize(F f) const; + + /** + * Returns the step value + * @return step value + */ + int getStep() const; + + /** + * Returns the stiffness value + * @return stiffness value + */ + int getStiffness() const; + + /** + * Returns the image + * @return the image + */ + Mat getImg() const; + + /** + * Returns the computed clusters. + * @return computed clusters + */ + const ClusterSet &getClusters() const; + + virtual ~Slic2(); + + private: + ClusterSet clusters; + Settings *setting; + Mat distance; + + vector max_dist_color; // For Slico (square values) + protected: + Slic2(Settings *s, ClusterSet &&clusters = ClusterSet(), const Mat &distance = cv::Mat()); + + void init(const Mat &grad); + }; + + } +} +#endif // RSlic2_H diff --git a/lib/Pixel/RSlic2Draw.cpp b/lib/Pixel/RSlic2Draw.cpp new file mode 100644 index 0000000..36df194 --- /dev/null +++ b/lib/Pixel/RSlic2Draw.cpp @@ -0,0 +1,112 @@ +#include "RSlic2Draw.h" +#include "../priv/Useful.h" + +#ifdef DEBUG_ME +#include +#endif + +#include +#include + +//To compute mean values there have to be a larger type (e.g. int -> long, vec of int -> vec of long) +//To find this type there are this struct. +//To use: LongVariant::type -> long version of T. +namespace priv { + //Default + template::value> + struct LongVariant { + using type = T; + }; + + //To distinguish signed form unsigned + namespace SignedLongTrait { + template::value> + struct SignedTrait { + }; + template + struct SignedTrait { // signed + using type = long; + using cvtype = int16_t; + }; + template + struct SignedTrait { // unsigned + using type = unsigned long; + using cvtype = uint32_t; + }; + } + + //char, short, int, long + template + struct LongVariant { + using type = typename priv::SignedLongTrait::SignedTrait::type; + }; + + namespace VecLongTrait { + template class VT, typename T, int N, bool = std::is_integral::value, bool = std::is_same, Vec>::value> + struct VecLongVariant { + }; + //vec of char, short, int, long + template class VT, typename T, int N> + struct VecLongVariant { + using type = Vec::cvtype, N>; + }; + //vec of double, float + template class VT, typename T, int N> + struct VecLongVariant { + using type = Vec; + }; + } + // Vec<..,..> + template class VT, typename T, int N> + struct LongVariant, false> { + using type = typename VecLongTrait::VecLongVariant::type; + }; + + template + using LongVector = vector::type>; +} + +namespace { + using namespace RSlic::Pixel; + + template + inline tuple, vector> calcMean(const Mat &m, const ClusterSet &set) { + auto n = set.clusterCount(); + priv::LongVector meanValue(n, 0); + vector pixelCount(n, 0); + auto label = set.getClusterLabel(); + for (int x = 0; x < m.cols; x++) { + for (int y = 0; y < m.rows; y++) { + auto idx = label.at(y, x); + if (idx < 0) continue; + meanValue[idx] += m.at(y, x); + pixelCount[idx]++; + } + } + return make_tuple(std::move(meanValue), std::move(pixelCount)); + } + + + template + Mat drawClusterType(const Mat &m, const ClusterSet &set) { + Mat res(m.rows, m.cols, m.type()); + auto label = set.getClusterLabel(); + auto meantuple = calcMean(m, set); + auto &&meanValue = get<0>(meantuple); + auto &&pixelCount = get<1>(meantuple); + for (int x = 0; x < m.cols; x++) { + for (int y = 0; y < m.rows; y++) { + auto idx = label.at(y, x); + res.at(y, x) = meanValue[idx] / pixelCount[idx]; + } + } + return res; + } + + declareCVF_T(drawClusterType, drawClusterType_, return cv::Mat()) +} + +Mat RSlic::Pixel::drawCluster(const Mat &m, const ClusterSet &set) { + return ::drawClusterType_(m.type(), m, set); +} + diff --git a/lib/Pixel/RSlic2Draw.h b/lib/Pixel/RSlic2Draw.h new file mode 100644 index 0000000..2d94bc9 --- /dev/null +++ b/lib/Pixel/RSlic2Draw.h @@ -0,0 +1,45 @@ +#ifndef RSlic2DRAW_H +#define RSlic2DRAW_H + +#include "RSlic2.h" +/* + * Functions for drawing cluster + */ + +namespace RSlic { + namespace Pixel { + /** + * Colorize the cluster by using the mean color. + * @param m the picture + * @param set the ClusterSet + * @return new picutre with colorized cluster + */ + Mat drawCluster(const Mat &m, const ClusterSet &set); + + /** + * Draw the lines around the cluster. color_t have to be right type for the image. + * @param m the picture + * @param set the ClusterSet + * @param color the color for drawing + * @return a new image + */ + template + Mat contourCluster(const Mat &m, const ClusterSet &set, color_t color) { + Mat res = m.clone(); + for (int x = 1; x < m.cols - 1; x++) { + for (int y = 1; y < m.rows - 1; y++) { + int currentLabel = set.at(y, x); + if (set.at(y + 1, x) != currentLabel + || set.at(y - 1, x) != currentLabel + || set.at(y, x + 1) != currentLabel + || set.at(y, x - 1) != currentLabel) { + res.at(y, x) = color; + } + } + } + return res; + } + } +} + +#endif // RSlic2DRAW_H diff --git a/lib/Pixel/RSlic2Util.cpp b/lib/Pixel/RSlic2Util.cpp new file mode 100644 index 0000000..4958573 --- /dev/null +++ b/lib/Pixel/RSlic2Util.cpp @@ -0,0 +1,129 @@ +#include "RSlic2Util.h" + + +string RSlic::Pixel::getType(const Mat &m) { + switch (m.type()) { + case CV_8UC1: + return "CV_8UC1"; + case CV_8UC2: + return "CV_8UC2"; + case CV_8UC3: + return "CV_8UC3"; + case CV_8UC4: + return "CV_8UC4"; + + case CV_8SC1: + return "CV_8SC1"; + case CV_8SC2: + return "CV_8SC2"; + case CV_8SC3: + return "CV_8SC3"; + case CV_8SC4: + return "CV_8SC4"; + + case CV_16UC1: + return "CV_16UC1"; + case CV_16UC2: + return "CV_16UC2"; + case CV_16UC3: + return "CV_16UC3"; + case CV_16UC4: + return "CV_16WUC4"; + + case CV_16SC1: + return "CV_16SC1"; + case CV_16SC2: + return "CV_16SC2"; + case CV_16SC3: + return "CV_16SC3"; + case CV_16SC4: + return "CV_16SC4"; + + case CV_32SC1: + return "CV_32SC1"; + case CV_32SC2: + return "CV_32SC2"; + case CV_32SC3: + return "CV_32SC3"; + case CV_32SC4: + return "CV_32SC4"; + + case CV_32FC1: + return "CV_32FC1"; + case CV_32FC2: + return "CV_32FC2"; + case CV_32FC3: + return "CV_32FC3"; + case CV_32FC4: + return "CV_32FC4"; + + case CV_64FC1: + return "CV_64FC1"; + case CV_64FC2: + return "CV_64FC2"; + case CV_64FC3: + return "CV_64FC3"; + case CV_64FC4: + return "CV_64FC4"; + + default: + return "Unknown"; + } +} + + +Mat RSlic::Pixel::buildGrad(const Mat &mat) { + cv::Mat dx, dy, res; + cv::Mat mat_gray; + switch (mat.type()) { + case CV_8UC3: + cv::cvtColor(mat, mat_gray, cv::COLOR_BGR2GRAY); + break; + case CV_8UC4: + cv::cvtColor(mat, mat_gray, cv::COLOR_BGRA2GRAY); + break; + default: + mat_gray = mat; + } + mat_gray.convertTo(mat_gray, CV_32FC1); + cv::Sobel(mat_gray, dx, -1, 1, 0); + cv::pow(dx, 2, dx); + cv::Sobel(mat_gray, dy, -1, 0, 1); + cv::pow(dy, 2, dy); + cv::sqrt(dx + dy, res); + return res; +} + +template +static RSlic::Pixel::Slic2P shutUpAndTakeMyMoneyType(const Mat &m, int step, int stiffness, bool slico, int iterations) { + F f; + Mat grad = RSlic::Pixel::buildGrad(m); + auto res = RSlic::Pixel::Slic2::initialize(m,grad,step, stiffness); + if (res.get() == nullptr) return res; //error + for (int i=0; i< iterations; i++){ + if (slico) res = res->iterateZero(f); + else res = res->iterate(f); + } + res = res->finalize(f); + return res; +} + + +RSlic::Pixel::Slic2P RSlic::Pixel::shutUpAndTakeMyMoney(const Mat &m, int count, int stiffness, bool slico, int iterations) { + int w = m.cols; + int h = m.rows; + int step = sqrt(w * h * 1.0 / count); + + if (m.type() == CV_8UC3) { + return shutUpAndTakeMyMoneyType(m,step, stiffness,slico,iterations); + } + if (m.type() == CV_8UC4) { + Mat other; + cv::cvtColor(m, other, cv::COLOR_BGRA2BGR); + return shutUpAndTakeMyMoneyType(other,step, stiffness,slico,iterations); + }//TODO: More Types + if (m.type() == CV_8UC1) { + return shutUpAndTakeMyMoneyType(m,step, stiffness,slico,iterations); + } + return nullptr; +} diff --git a/lib/Pixel/RSlic2Util.h b/lib/Pixel/RSlic2Util.h new file mode 100644 index 0000000..d03092f --- /dev/null +++ b/lib/Pixel/RSlic2Util.h @@ -0,0 +1,100 @@ +#ifndef RSlic2UTIL_H +#define RSlic2UTIL_H + +#include "RSlic2.h" +#include "RSlic2_impl.h" + +/* + * Helpful functions for Slic and OpenCV + */ +namespace RSlic { + namespace Pixel { + +/** +* Returns the type of the image as a human readable string (e.g. CV_U8C1, CV_16S3, ...) +* @param m the picture +* @return the type as string +*/ + string getType(const Mat &m); + + + /** + * Builds the gradient needed for Slic2 using Sobel. + * Contains the absolute sum of the derivation of both direction. + * (If needed mat will be convert into a gray value image) + * @param mat the picture + * @return the gradient + */ + Mat buildGrad(const Mat &mat); + + /** + * Function that is described in the paper for computing the metrics (of LAB images) for the algorithm. + */ + struct distanceColor { + + inline double operator()(const cv::Vec2i &point, const cv::Vec2i &clusterCenter, const cv::Mat &mat, int stiffness, int step) { + if (clusterCenter[1] < 0 || clusterCenter[0] < 0) { + return DINF; + } + cv::Vec3b pixel = mat.at(point[1], point[0]); + cv::Vec3b clust_pixel = mat.at(clusterCenter[1], clusterCenter[0]); + int pl = pixel[0], pa = pixel[1], pb = pixel[2]; + int cl = clust_pixel[0], ca = clust_pixel[1], cb = clust_pixel[2]; + double dc = pow(pl - cl, 2) + pow(pa - ca, 2) + pow(pb - cb, 2); + double ds = pow(point[0] - clusterCenter[0], 2) + pow(point[1] - clusterCenter[1], 2); + + return dc / stiffness + ds / (step * step); + } + + }; + + /** + * Function that is described in the paper for computing the metrics (of gray images) for the algorithm. + */ + struct distanceGray { + inline double operator()(const cv::Vec2i &point, const cv::Vec2i &clusterCenter, const cv::Mat &mat, int stiffness, int step) { + if (clusterCenter[1] < 0 || clusterCenter[0] < 0) { + return DINF; + } + int8_t pixel = mat.at(point[1], point[0]); + int8_t clust_pixel = mat.at(clusterCenter[1], clusterCenter[0]); + double dc = abs(pixel - clust_pixel); + double ds = sqrt(pow(point[0] - clusterCenter[0], 2) + pow(point[1] - clusterCenter[1], 2)); + + return pow(dc, 2) / stiffness + pow(ds / step, 2); + } + }; + + /** + * Returns Slic2P without any "complicated" parameter. + * @param m the picture + * @param the amount of Superpixel (approximately) + * @param slico use the slico version? + * @param iterations how many iterations + * @return instance of Slic2 (shared_ptr) where no iterating or something similar is needed. (error -> nullptr) + */ + Slic2P shutUpAndTakeMyMoney(const Mat &m, int count = 400, int stiffness = 40, bool slico = false, int iterations = 10); + + /** + * Heelping for do an iteration by selecting the metrics autmaticly. + * @param slic the Slic2-Object to iterate + * @param type the type of the image (img.type() in OpenCV) + * @param slico using Slico + * @return the result of slic->iterate or slic->iterateZero with the right metrics. + * (May nullptr if type is not supported or any other error occurs) + */ + inline Slic2P iteratingHelper(Slic2P slic, int type, bool slico = false) { + if (type == CV_8UC1) { + distanceGray g; + if (slico) return slic->iterateZero(g); + return slic->iterate(g); + } else if (type == CV_8UC3) { + distanceColor c; + if (slico) return slic->iterateZero(c); + return slic->iterate(c); + } + return Slic2P(); //unsupported type + } + } +} +#endif // RSlic2UTIL_H diff --git a/lib/Pixel/RSlic2_impl.h b/lib/Pixel/RSlic2_impl.h new file mode 100644 index 0000000..d16ba5e --- /dev/null +++ b/lib/Pixel/RSlic2_impl.h @@ -0,0 +1,428 @@ +#ifndef RSlic2_IMPL_H +#define RSlic2_IMPL_H + +#include "RSlic2.h" +#include +#include +#include <3rd/ThreadPool.h> + +#ifndef u_long +#define u_long unsigned long +#endif +#ifndef uint +#define uint unsigned int +#endif + +using namespace RSlic::Pixel; +using RSlic::priv::aSize; + +//Settings that are shared over several instances. +struct RSlic::Pixel::Slic2::Settings { + Settings() : __refcount(0), step(0), stiffness(0) { + } + + Settings(const Settings *other) : + __refcount(0), step(other->step), + img(other->img), stiffness(other->stiffness), pool(other->pool) { + } + + void initThreadPool(int threadcount = -1) { + if (threadcount <= 0) + threadcount = std::thread::hardware_concurrency(); + pool = std::make_shared(threadcount); + } + + Mat img; + int step; + int stiffness; + shared_ptr pool; + + std::atomic __refcount; +}; + + +template +RSlic::Pixel::Slic2P RSlic::Pixel::Slic2::iterate(F f) const { + return iterate(setting->stiffness, f); +} + +namespace RSlic { + namespace Pixel { + namespace priv { + + /** + * In order to share code between iterate and iterateZero we need + * to take out the different parts. + * This is the part from iterate. + */ + template + struct DistNormal { + inline double operator()(const Vec2i &point, const Vec2i ¢er, int clusterIdx) { + return f(point, center, img, stiffness * stiffness, step); + } + + const cv::Mat &img; + F &f; + int stiffness; + int step; + }; + } + } +} +namespace { +#ifdef PARALLEL + + /** + * For doing some parallel computing we have to reduce the results of the threads. + * Instead of iterating over all pixels, we just calculating the computed parts with + * BRect. + */ + struct BRect { + static constexpr uint imax = std::numeric_limits::max(); + struct point { + uint x; + uint y; + }; + point topLeft, bottomRight; + + BRect(uint tx, uint ty, uint bx, uint by) : topLeft{tx, ty}, bottomRight{bx, by} { + } + + BRect() : topLeft{imax, imax}, bottomRight{0, 0} { + } + + void combineWith(const BRect &other) { + topLeft.x = std::min(topLeft.x, other.topLeft.x); + topLeft.y = std::min(topLeft.y, other.topLeft.y); + bottomRight.x = std::max(bottomRight.x, other.bottomRight.x); + bottomRight.y = std::max(bottomRight.y, other.bottomRight.y); + } + }; + +#endif +} +namespace RSlic { + namespace Pixel { + namespace priv { + + /** + * Results of the common iteration algorithm + */ + struct iterateCommonRes { + Mat_ label; + Mat_ dist; + + iterateCommonRes(int w, int h) : label(h, w, -1), dist(h, w, DINF) { + } + + inline double &distAt(int y, int x) { + return dist.at(y, x); + } + + inline ClusterInt &labelAt(int y, int x) { + return label.at(y, x); + } + +#ifdef PARALLEL + BRect calcRect; +#endif + }; + + using iterateCommonResP = unique_ptr; + } + } +} +namespace { + /** + * Executes the Slic-Algorithm. Depending on the functor it computes the Slic or Slico version (or some unknown one ;). + * @param f the functor. Have to be something like struct ExampleF{...; double operator()(const Vec2i& point, const Vec2i & center, int clusterIdx){...} ....} + * @param beg the first cluster for computing + * @param end the last cluster for computing + * @param w the width of the picture + * @param h the height of the picture + * @param centers the central points of the clusters + * @param s the step + * @param pool the threadpool for parallel computing + * @result the results composed of the label Mat, distance Mat and may the rect of calculation + * @see iterate + * @see iterateZero + * @see priv::DistNormal + */ + template + inline RSlic::Pixel::priv::iterateCommonResP iterateCommonIteration(F f, int beg, int end, int w, int h, const vector ¢ers, int s, ThreadPoolP pool) { + RSlic::Pixel::priv::iterateCommonResP result(new RSlic::Pixel::priv::iterateCommonRes(w, h)); + for (int k = beg; k < end; k++) { + auto center = centers[k]; + int px = center[0]; + int py = center[1]; +#ifdef PARALLEL + BRect currentRect(std::max(0, px - s), std::max(0, py - s), std::min(w, px + s + 1), std::min(h, py + s + 1)); + result->calcRect.combineWith(currentRect); +#endif + for (int x = std::max(0, px - s); x < std::min(w, px + s + 1); x++) { + for (int y = std::max(0, py - s); y < std::min(h, py + s + 1); y++) { + Vec2i point(x, y); + double &d = result->distAt(y, x); + double D = f(point, center, k); + if (D < d) { + d = D; + result->labelAt(y, x) = k; + } + } + } + } + return result; + } + +#ifndef PARALLEL + + /** + * Executes the Slic-Algorithm. Depending on the functor it computes the Slic or Slico version (or some unknown one ;). + * @param f the functor. Have to be something like struct ExampleF{...; double operator()(const Vec2i& point, const Vec2i & center, int clusterIdx){...} ....} + * @param clusters the ClusterSet + * @param s the step + * @param pool the threadpool for parallel computing + * @result the results composed of the label Mat and distance Mat + * @see iterate + * @see iterateZero + * @see priv::DistNormal + */ + template + RSlic::Pixel::priv::iterateCommonResP iterateCommon(F f, const ClusterSet &clusters, int s, ThreadPoolP pool) { + auto centers = clusters.getCenters(); + int h = clusters.getClusterLabel().rows; + int w = clusters.getClusterLabel().cols; + int N = centers.size(); + + return iterateCommonIteration(f, 0, N, w, h, centers, s, pool); + } + +#else + +/** + * Executes the Slic-Algorithm. Depending on the functor it computes the Slic or Slico version (or some unknown one ;). + * (Do the parallel computing version) + * @param f the functor. Have to be something like struct ExampleF{...; double operator()(const Vec2i& point, const Vec2i & center, int clusterIdx){...} ....} + * @param clusters the ClusterSet + * @param s the step + * @param pool the threadpool for parallel computing + * @result the results composed of the label Mat and distance Mat + * @see iterate + * @see iterateZero + * @see priv::DistNormal + */ + template + RSlic::Pixel::priv::iterateCommonResP iterateCommon(F f, const ClusterSet &clusters, int s, ThreadPoolP pool) { + auto centers = clusters.getCenters(); + int h = clusters.getClusterLabel().rows; + int w = clusters.getClusterLabel().cols; + RSlic::Pixel::priv::iterateCommonResP result(new RSlic::Pixel::priv::iterateCommonRes(w, h)); + + int N = centers.size(); + int thread_step = N / pool->threadcount(); + std::vector> futures; + futures.reserve(pool->threadcount()); + //Map + + for (int thread_start = 0; thread_start < N; thread_start += thread_step) { + futures.push_back(pool->enqueue([&](int start) { + return iterateCommonIteration(f, start, std::min(start + thread_step, N), w, h, centers, s, pool); + }, thread_start)); + } + //Reduce + for (auto &&fut: futures) { + auto &&thread_result = fut.get(); + //Only use changed parts for reducing + for (int x = thread_result->calcRect.topLeft.x; x < thread_result->calcRect.bottomRight.x; x++) { + for (int y = thread_result->calcRect.topLeft.y; y < thread_result->calcRect.bottomRight.y; y++) { + double dist_thread = thread_result->distAt(y, x); + double &dist_result = result->distAt(y, x); + if (dist_thread < dist_result) { + dist_result = dist_thread; + result->labelAt(y, x) = thread_result->labelAt(y, x); + } + } + } + } + return result; + } + +#endif +} + +template +RSlic::Pixel::Slic2P RSlic::Pixel::Slic2::iterate(int stiffness, F f) const { + int s = setting->step; + int w = setting->img.cols; + int h = setting->img.rows; + + // Setting up the normal Slic + RSlic::Pixel::priv::DistNormal distF{setting->img, f, stiffness, s}; + auto res = ::iterateCommon>(distF, clusters, s, setting->pool); + + // Creating the new instace + Settings *newSetting = setting; + if (setting->stiffness != stiffness) { + newSetting = new Settings(setting); + newSetting->stiffness = stiffness; + } + Slic2 *result = new Slic2(newSetting, ClusterSet(res->label, clusters.getCenters().size()), res->dist); + + return shared_ptr(result); +} + +namespace { + //Update the color-distance-maxima-matrix for slico that will be used for the next iteration. + template + inline void iterateZeroUpdate( + const Mat &img, const Mat &label, + const vector ¢ers, vector &max_dist_color, shared_ptr pool) { + int w = img.cols; + int h = img.rows; + //Update Slico distance maxima +#ifdef PARALLEL + int step = w / pool->threadcount(); + std::vector> results; + results.reserve(pool->threadcount()); + for (int xx = 0; xx < w; xx += step) { + results.push_back(pool->enqueue([&](int xx) { + for (int x = xx; x < std::min(xx + step, w); x++) { +#else + for (int x = 0; x < w; x++) { +#endif + for (int y = 0; y < h; y++) { + ClusterInt nearest_segment = label.at(y, x); + if (nearest_segment == -1) continue; + auto point = centers[nearest_segment]; + int py = point[1]; + int px = point[0]; + auto distColor = RSlic::priv::zero::zeroMetrik(img.at(y, x), img.at(py, px)); + if (max_dist_color.at(nearest_segment) < distColor) { + max_dist_color.at(nearest_segment) = distColor; + } + } + } +#ifdef PARALLEL + }, xx)); + } + for (auto &res: results) {res.get();} +#endif + } + + //For calling without template + declareCVF_T(iterateZeroUpdate, iterateZeroUpdateHelper, + return) +} +namespace RSlic { + namespace Pixel { + namespace priv { + + //Computing with zero + template + struct DistZero { + inline double operator()(const Vec2i &point, const Vec2i ¢er, int clusterIdx) { + return f(point, center, img, max_distance[clusterIdx], step); + } + + const cv::Mat &img; + F f; + const vector &max_distance; + int step; + }; + } + } +} + +//Slico +template +RSlic::Pixel::Slic2P RSlic::Pixel::Slic2::iterateZero(F f) const { + int s = setting->step; + int w = setting->img.cols; + int h = setting->img.rows; + + //Setting up Slico + Pixel::priv::DistZero distF{setting->img, f, max_dist_color, s}; + auto res = ::iterateCommon>(distF, clusters, s, setting->pool); + + auto newClusters = ClusterSet(res->label, clusters.getCenters().size()); + vector new_max_dist_color(max_dist_color); + // + //Update values + ::iterateZeroUpdateHelper(setting->img.type(), setting->img, res->label, newClusters.getCenters(), new_max_dist_color, setting->pool); + + //Creating a new instance + Slic2 *result = new Slic2(setting, std::move(newClusters), res->dist); + result->max_dist_color = std::move(new_max_dist_color); + return shared_ptr(result); +} + +template +RSlic::Pixel::Slic2P RSlic::Pixel::Slic2::finalize(F f) const { + int w = setting->img.cols; + int h = setting->img.rows; + Mat_ finalClusters(h, w, -1); + int currentLabel = 0; + const int lims = (h * w) / (clusters.clusterCount()); + static const int neighboursX[] = {1, 0, -1, 0}; + static const int neighboursY[aSize(neighboursX)] = {0, 1, 0, -1}; + + for (int x = 0; x < w; x++) { + for (int y = 0; y < h; y++) { + //Some unassigned pixel? + if (finalClusters.at(y, x) == -1) { + vector current_points; + current_points.emplace_back(x, y); + finalClusters.at(y, x) = currentLabel; + + //Look transitively for all unassigned neighbors + //set them in current_points and finalCluster + for (int i = 0; i < current_points.size(); i++) { + Vec2i point = current_points[i]; + + for (int neighbour = 0; neighbour < aSize(neighboursX); neighbour++) { + int px = point[0] + neighboursX[neighbour]; + int py = point[1] + neighboursY[neighbour]; + if (px < 0 || px >= w || py < 0 || py >= h) continue; // not in the picture anymore + if (finalClusters.at(py, px) == -1 + && clusters.at(y, x) == clusters.at(py, px)) { + current_points.emplace_back(px, py); + finalClusters.at(py, px) = currentLabel; + } + } + } + + //If there are not enough pixel in the cluster + //look for the best in the environment and conjoin both + if (current_points.size() <= lims >> 2) { + int adjlabel = currentLabel; //best neighbor + double topdist = DINF; //best neighbor value + //finding best neighbor + for (int neighbour = 0; neighbour < 4; neighbour++) { + int px = x + neighboursX[neighbour]; + int py = y + neighboursY[neighbour]; + if (px < 0 || px >= w || py < 0 || py >= h) continue; + ClusterInt label = finalClusters.at(py, px); + if (label >= 0 && label != currentLabel) { + double dist = f(Vec2i(x, y), Vec2i(px, py), setting->img, 1, setting->step); + if (dist < topdist) { + topdist = dist; + adjlabel = label; + } + } + } + //Set pixel to this neighbor + for (const Vec2i point: current_points) { + finalClusters.at(point[1], point[0]) = adjlabel; + } + } else currentLabel++; //Else I've created a new cluster + } + } + } + + Slic2 *result = new Slic2(setting, ClusterSet(finalClusters, currentLabel), distance); + return std::shared_ptr(result); +} + + +#endif // RSlic2_IMPL_H + diff --git a/lib/RSlic.h b/lib/RSlic.h new file mode 100644 index 0000000..ee00205 --- /dev/null +++ b/lib/RSlic.h @@ -0,0 +1,5 @@ +#ifndef RSlicH_H +#define RSlicH_H +#include "RSlic2H.h" +#include "RSlic3H.h" +#endif diff --git a/lib/RSlic2H.h b/lib/RSlic2H.h new file mode 100644 index 0000000..7942b79 --- /dev/null +++ b/lib/RSlic2H.h @@ -0,0 +1,12 @@ +#ifndef RSlic2H_H +#define RSlic2H_H + +#include +#include +#include +#include +#include + +#include <3rd/ThreadPool.h> + +#endif diff --git a/lib/RSlic3H.h b/lib/RSlic3H.h new file mode 100644 index 0000000..84d27be --- /dev/null +++ b/lib/RSlic3H.h @@ -0,0 +1,10 @@ +#ifndef RSlic3H_H +#define RSlic3H_H + +#include <3rd/ThreadPool.h> +#include +#include +#include +#include + +#endif diff --git a/lib/Voxel/ClusterSet.cpp b/lib/Voxel/ClusterSet.cpp new file mode 100644 index 0000000..2f3970c --- /dev/null +++ b/lib/Voxel/ClusterSet.cpp @@ -0,0 +1,115 @@ +#include "ClusterSet.h" +#include "../priv/Useful.h" + +using namespace RSlic::Voxel; +using RSlic::priv::aSize; + +inline tuple operator+(const tuple &a, const tuple &b) { + return make_tuple( + std::get<0>(a) + std::get<0>(b), + std::get<1>(a) + std::get<1>(b), + std::get<2>(a) + std::get<2>(b) + ); +} + +RSlic::Voxel::ClusterSet3::ClusterSet3(cv::Mat_ clusters, int clusterCount) + : data{clusters, vector(), clusterCount, false} { +} + +Mat_ RSlic::Voxel::ClusterSet3::getClusterLabel() const { + return data.clusterLabel; +} + +const vector &RSlic::Voxel::ClusterSet3::getCenters() const { + if (!data.centers_calculated) refindCenters(); + return data.centers; +} + +int RSlic::Voxel::ClusterSet3::clusterCount() const { + return data._clusterCount; +} + +void RSlic::Voxel::ClusterSet3::refindCenters() const { + std::lock_guard guard(mutex); + if (data.centers_calculated) return; + int w = data.clusterLabel.size[1]; + int h = data.clusterLabel.size[0]; + int d = data.clusterLabel.size[2]; + vector centersCounts(data._clusterCount, 0); + vector> centerCoord(data._clusterCount, make_tuple(0l, 0l, 0l)); + + for (int x = 0; x < w; x++) { + for (int y = 0; y < h; y++) { + for (int t = 0; t < d; t++) { + ClusterInt idx = data.clusterLabel.at(y, x, t); + if (idx < 0) continue; + centersCounts[idx] = centersCounts[idx] + 1; + centerCoord[idx] = centerCoord[idx] + make_tuple(static_cast(x), static_cast(y), static_cast(t)); + } + } + } + + for (int i = 0; i < data._clusterCount; i++) { + auto counts = centersCounts[i]; + assert(counts > 0); + /*if (counts == 0) { + centers.push_back(Vec3i(0, 0, 0)); + continue; + }*/ + data.centers.emplace_back( + std::get<0>(centerCoord[i]) / counts, + std::get<1>(centerCoord[i]) / counts, + std::get<2>(centerCoord[i]) / counts); + } + data.centers_calculated = true; +} + +Mat RSlic::Voxel::ClusterSet3::maskOfCluster(ClusterInt idx) const { + int w = data.clusterLabel.size[1]; + int h = data.clusterLabel.size[0]; + int d = data.clusterLabel.size[2]; + cv::Mat res = cv::Mat(3, data.clusterLabel.size, CV_8U); + for (int x = 0; x < w; x++) { + for (int y = 0; y < h; y++) { + for (int t = 0; t < d; t++) { + if (data.clusterLabel.at(y, x, t) == idx) + res.at(y, x, t) = 1; + } + } + } + return res; +} + + +namespace { + inline void setClusterAdjacent(Mat &m, int p1, int p2) { + m.at(p1, p2) = 1; + m.at(p2, p1) = 1; + } +} + +cv::Mat RSlic::Voxel::ClusterSet3::adjacentMatrix() const { + int size = clusterCount(); + Mat res = Mat::eye(size, size, CV_8UC1); + Mat_ clusterMat = getClusterLabel(); + int w = clusterMat.size[1]; + int h = clusterMat.size[0]; + int d = clusterMat.size[2]; + static const int xNeighbour[] = {1, 0, 0, 1, 0, 1, 1}; + static const int yNeighbour[aSize(xNeighbour)] = {0, 1, 0, 1, 1, 0, 1}; + static const int zNeighbour[aSize(xNeighbour)] = {0, 0, 1, 0, 1, 1, 1}; + for (int x = 0; x < w - 1; x++) { + for (int y = 0; y < h - 1; y++) { + for (int t = 0; t < d - 1; t++) { + ClusterInt currentCluster = clusterMat.at(y, x, t); + for (int i = 0; i < aSize(xNeighbour); i++) { + ClusterInt otherCluster = clusterMat.at(y + yNeighbour[i], x + xNeighbour[i], t + zNeighbour[i]); + if (currentCluster != otherCluster) { + setClusterAdjacent(res, currentCluster, otherCluster); + } + } + } + } + } + return res; +} diff --git a/lib/Voxel/ClusterSet.h b/lib/Voxel/ClusterSet.h new file mode 100644 index 0000000..025b927 --- /dev/null +++ b/lib/Voxel/ClusterSet.h @@ -0,0 +1,133 @@ +#ifndef ClUSTERSET3_H +#define ClUSTERSET3_H + +#include +#include + +using namespace std; +using namespace cv; + +namespace RSlic { + namespace Voxel { + using ClusterInt=int; //int32_t + +/** +* @brief Representing cluster (also called "Supervoxel") with some functionality +*/ + class ClusterSet3 { + public: + + ClusterSet3() : data{Mat_(), vector(), 0, false} { + } + + ClusterSet3(ClusterSet3 &&other) : data(std::move(other.data)) { + } + + ClusterSet3(const ClusterSet3 &other) : data(other.data) { + } + + ClusterSet3 &operator=(ClusterSet3 &&other) { + data = std::move(other.data); + return *this; + } + + ClusterSet3 &operator=(const ClusterSet3 &other) { + data = other.data; + return *this; + } + + /** + * Initialize with given centers and cluster-label mat + * @param _centers List with the central points of the clusters + * @param _clusters Mat where _clusters[i,j]=x means that the point i,j belongs to the cluster number x + */ + template, typename std::decay::type>::value + >::type> + ClusterSet3(T &&_centers, cv::Mat_ _clusters) : data{_clusters, std::forward(_centers), 0, true} { + data._clusterCount = data.centers.size(); + } + + /** + * Initialize with given clusters and the cluster's amount. + * The central points will be calculating if necessary. + * @param clusters Mat where clusters[i,j]=x means that the point i,j belongs to the cluster number x + * @param clusterCount the amount of the clusters + * @see getCenters() + */ + ClusterSet3(cv::Mat_ clusters, int clusterCount); + + + /** + * Returns a mask of the cluster with the number idx. + * @param idx Clusters index + * @return binary mask. + */ + Mat maskOfCluster(ClusterInt idx) const; // Binäres Bild. 0 => gehört nicht dazu, 1 => gehört dazu + + + /** + * Returns a Mat m where where m[x,y]=i means that the point x,y belongs to the cluster with the number i + * @return Mat with the cluster label + */ + Mat_ getClusterLabel() const; + + /** + * Returns the central points of the clusters. + * The index indicates the cluster number the central point belongs to. + * This is a lazy-evaluation. + * @return List of central points + */ + const vector &getCenters() const; + + /** + * Returns the amount of clusters. + * @return amount of clusters + */ + int clusterCount() const; + + /** + * Returns the index of the cluster the point x,y belongs to. + * @param y y-coordinate + * @param x x-coordinate + * @return cluster index + */ + inline ClusterInt at(int y, int x, int t) const { + return data.clusterLabel.at(y, x, t); + } + + /** + * Returns the adjacent matrix m (type CV_8UC1). + * If m[x,y] == 1 then the cluster with number x + * and the cluster with number y are neighbor. + * It is always m[x,x] == 1 and if m[x,y]==1 + * it means that m[y,x] == 1. + * @return adjacent matrix. + */ + cv::Mat adjacentMatrix() const; + + private: + struct nonspecial { //For default copy & move constructor + nonspecial(nonspecial &&other) = default; + + nonspecial(const nonspecial &other) = default; + + nonspecial &operator=(const nonspecial &other) = default; + + nonspecial &operator=(nonspecial &&other) = default; + + Mat_ clusterLabel; //3-Dim + + mutable vector centers; + int _clusterCount; + mutable bool centers_calculated; + } data; + + mutable std::mutex mutex; + + void refindCenters() const; + }; + + } +} +#endif diff --git a/lib/Voxel/RSlic3.cpp b/lib/Voxel/RSlic3.cpp new file mode 100644 index 0000000..3b49753 --- /dev/null +++ b/lib/Voxel/RSlic3.cpp @@ -0,0 +1,126 @@ +#include "RSlic3.h" +#include +#include <3rd/ThreadPool.h> +#include +#include "RSlic3_impl.h" +#include + +#ifndef u_long +#define u_long unsigned long +#endif +#ifndef uint +#define uint unsigned int +#endif + +using RSlic::priv::aSize; +using namespace RSlic::Voxel; + + +ThreadPoolP Slic3::threadpool() const { + return setting->pool; +} + +Slic3P Slic3::initialize(const MovieCacheP &img, const GradFunc &grad, int step, int stiffness, ThreadPoolP pool) { + Slic3::Settings *setting = new Slic3::Settings(); + setting->img = img; + setting->step = step; + setting->stiffness = stiffness; + setting->gradFunc = grad; + if (pool.get() == nullptr) + setting->initThread(); + else + setting->pool = pool; + + Slic3 *res = new Slic3(setting); + res->init(); + if (res->clusters.clusterCount() == 0) { + delete res; + return Slic3P(); + } + return Slic3P(res); +} + +RSlic::Voxel::Slic3::Slic3(Settings *s, ClusterSet3 &&set, const Mat &d) : clusters(std::move(set)), distance(d) { + assert(s != nullptr); + setting = s; + s->__refcount++; +} + +RSlic::Voxel::Slic3::~Slic3() { + int count = --setting->__refcount; + if (count == 0) { + delete setting; + } +} + +namespace { + Vec3i find_local_minimum(const MovieCacheP &p, const GradFunc &f, const Vec3i ¢er) { + auto px = center[0]; + auto py = center[1]; + auto pt = center[2]; + int w = p->width(); + int h = p->height(); + int duration = p->duration(); + double min_value = DINF; + cv::Vec3i minPos = center; + for (int x = std::max(px - 1, 0); x < std::min(w, px + 2); x++) { + for (int y = std::max(0, py - 1); y < std::min(h, py + 2); y++) { + for (int t = std::max(0, pt - 1); t < std::min(duration, pt + 2); t++) { + Vec3i point(x, y, t); + auto currentVal = f(p, point); + if (currentVal < min_value) { + min_value = currentVal; + minPos = point; + } + } + } + } + return minPos; + } + +} + +void RSlic::Voxel::Slic3::init() { + int s = setting->step; + int w = setting->img->width(); + int h = setting->img->height(); + int d = setting->img->duration(); + std::vector centerGrid; + centerGrid.reserve(w * h * d / s / s / s); + // initialize the grid and set the minimum of the gradient at once + for (int x = s; x < w - s / 2; x += s) { + for (int y = s; y < h - s / 2; y += s) { + for (int t = s; t < d - s / 2; t += s) { + Vec3i p(x, y, t); + centerGrid.push_back(find_local_minimum(setting->img, setting->gradFunc, p)); + } + } + } + + + int *size = setting->img->sizeArray(); + Mat_ label(3, size, -1); +; + delete size; + clusters = ClusterSet3(centerGrid, label); + + max_dist_color = std::vector(clusters.clusterCount(), 1); //for slico +} + + +int RSlic::Voxel::Slic3::getStep() const { + return setting->step; +} + +int RSlic::Voxel::Slic3::getStiffness() const { + return setting->stiffness; +} + +MovieCacheP RSlic::Voxel::Slic3::getImg() const { + return setting->img; +} + + +const ClusterSet3 &RSlic::Voxel::Slic3::getClusters() const { + return clusters; +} diff --git a/lib/Voxel/RSlic3.h b/lib/Voxel/RSlic3.h new file mode 100644 index 0000000..6fefdd3 --- /dev/null +++ b/lib/Voxel/RSlic3.h @@ -0,0 +1,237 @@ +#ifndef RSlic3_H +#define RSlic3_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ClusterSet.h" + + + +using namespace std; +using namespace cv; + +class ThreadPool; + +using ThreadPoolP=shared_ptr; + +#define DINF std::numeric_limits::infinity() + +namespace RSlic { + namespace Voxel { + + /** + * Abstract class for managing the pictures of a sequence for the Supervoxel algorithm. + * (E.g. load and destroy images if memory is low) + * By subclassing it you have to make sure that are pictures have the same type and size. + */ + class MovieCache { + public: + + /** + * Returns the image at the position t + * @param t the position of the image + * @return the image as a Mat. (Empty Mat for an invalid t) + */ + virtual Mat matAt(int t) const = 0; + + /** + * Returns the value of the point x,y from the picture at position t + * (Similar to Mat::at) + * @param y y coordinate + * @param x x coordinate + * @param t position of the image + * @return value of the point + */ + template + inline T at(int y, int x, int t) const { + return matAt(t).at(y, x); + } + + /** + * Returns the value of the point v + * (Equally to MovieCacheP::at(v[1],v[0],v[3])= + * @param v the point + * @return value of the point + */ + template + inline T at(const Vec3i &v) const { + return matAt(v[2]).at(v[1], v[0]); + } + + /** + * Returns the duration (number of images of the sequence) + * @return the duration + */ + virtual int duration() const = 0; + + /** + * Returns the width of *all* images + * @return the width + */ + inline int width() const { + return matAt(0).size[1]; + } + + /** + * Returns the width of *all* images + * @return the height + */ + inline int height() const { + return matAt(0).size[0]; + } + + /** + * Returns the height, width and durations as a tuple + * @returns size tuple + */ + inline tuple sizeTuple() const { + return std::make_tuple(matAt(0).size[0], matAt(0).size[1], duration()); + } + + + /** + * Returns the height, width and durations as an array. + * The array have to be deleted. + * @returns size array + */ + inline int *sizeArray() const { + int *res = new int[3]; + res[0] = matAt(0).size[0]; + res[1] = matAt(0).size[1]; + res[2] = duration(); + return res; + } + + /** + * Returns the type of *all* images + * @returns image type + */ + inline int type() const { + return matAt(0).type(); + } + }; + + using MovieCacheP=shared_ptr; + + + + using DistanceFunc = function; + using GradFunc = function; + + class Slic3; + + using Slic3P = shared_ptr; + + class Slic3 { + private: + struct Settings; + + public: + Slic3(const Slic3 &other) = delete; + + Slic3(Slic3 &&other) = default; + + /** + * initialize the algorithm. It build the gradient and set the needed values + * @param img the MoveCache + * @param a function to calculate the gradient + * @param step how many pixel should belongs (approximately) to a clusters + * @param stiffness the stiffness value + * @param pool ThreadPool for computing parallel. + * @return SharedPointer of the Slic3-Object. (Error -> nullptr) + * @see iterate + */ + static Slic3P initialize(const MovieCacheP &img, const GradFunc &grad, int step, int stiffness, ThreadPoolP pool = ThreadPoolP()); + + + /** + * Iterating the algorithm. + * @param f the functor with the metrics for the iteration. + * Has to be something like struct Example{..; inline double operator()(const cv::Vec3i &point, const cv::Vec3i &clusterCenter, const MovieCacheP &mat, int stiffness, int step){...} ...} + * (stiffness will be passed squared) + * @return a new instance of Slic3 with the results of the iteration. + */ + template + Slic3P iterate(F f) const; + + /** + * Iterating the algorithm with another stiffness. + * @param stiffness the stiffness factor. + * @param f the functor with the metrics for the iteration. + * @return a new instance of Slic3 with the results of the iteration. + * @see iterate + */ + template + Slic3P iterate(int stiffness, F f) const; + + ThreadPoolP threadpool() const; + + /** + * Iterating the algorithm + * using the zero parameter version of the SLIC algorithm (SLICO) + * @param f the functor with the metrics for the iteration + * @return a new instance of Slic3 with the results of the iteration. + */ + template + Slic3P iterateZero(F f) const; + + /** + * Enforce connectivity. + * Often this is the last step you want to do. + * But you can do another iteration after here. + * Even if it does not make any sense. + * @param f the functor with the metrics for the iteration + * @return a new instance of Slic3 with the results of the iteration. + */ + template + Slic3P finalize(F f) const; + + /** + * Returns the step value + * @return step value + */ + int getStep() const; + + /** + * Returns the stiffness value + * @return stiffness value + */ + int getStiffness() const; + + /** + * Returns the MovieCache + * @return the MovieCache + */ + MovieCacheP getImg() const; + + /** + * Returns the computed clusters. + * @return computed clusters + */ + const ClusterSet3 &getClusters() const; + + virtual ~Slic3(); + + + private: + ClusterSet3 clusters; + Settings *setting; + Mat distance; //3-dim + double error; + + vector max_dist_color; + protected: + Slic3(Settings *s, ClusterSet3 &&set = ClusterSet3(), const Mat &distance = Mat()); + + void init(); + }; + } +} +#endif // RSlic3_H diff --git a/lib/Voxel/RSlic3Utils.cpp b/lib/Voxel/RSlic3Utils.cpp new file mode 100644 index 0000000..b429280 --- /dev/null +++ b/lib/Voxel/RSlic3Utils.cpp @@ -0,0 +1,107 @@ +#include +#include +#include "RSlic3Utils.h" +#include "RSlic3_impl.h" + +cv::Mat RSlic::Voxel::SimpleMovieCache::matAt(int t) const { + if (t >= pictures.size()) return Mat(); + return pictures[t]; +} + +int RSlic::Voxel::SimpleMovieCache::duration() const { + return pictures.size(); +} + +RSlic::Voxel::SimpleMovieCache::SimpleMovieCache(const std::vector &_pictures) : pictures(_pictures) { + +} + + +RSlic::Voxel::SimpleMovieCache::SimpleMovieCache(std::vector &&_pictures) : pictures(std::move(_pictures)) { + +} + +RSlic::Voxel::SimpleMovieCache::SimpleMovieCache(const std::vector &filenames) { + for (const string &f: filenames) { + Mat mat = cv::imread(f, cv::IMREAD_COLOR); //TODO: Don't ignore cases where images have not the same size or type + switch (mat.type()) { + case CV_8UC3: + cv::cvtColor(mat, mat, cv::COLOR_BGR2Lab); + break; + } + pictures.push_back(mat); + } +} + +namespace { + template + inline double decolor(const T &color) { + return (double) color; + } + + template<> + inline double decolor(const Vec3b &color) { + return (color[0] * 1.0 + color[1] * 1.0 + color[2] * 1.0 ) / 3.0; + } + + template<> + inline double decolor(const uint8_t &color) { + return (double) color; + } + + template + inline double buildGradIntern(RSlic::Voxel::MovieCacheP const &img, Vec3i const &vec) { + int y = vec[1]; + int x = vec[0]; + int t = vec[2]; + + double dx = decolor(img->at(y, x - 1, t) + img->at(y, x + 1, t)); + dx = dx / 2; + + double dy = decolor(img->at(y - 1, x, t) + img->at(y + 1, x, t)); + dy = dy / 2; + + double dt = decolor(img->at(y, x, t - 1) + img->at(y, x, t + 1)); + dt = dt / 2; + + return sqrt(dx * dx + dy * dy + dt * dt); + } +} + + +double ::RSlic::Voxel::buildGradGray(RSlic::Voxel::MovieCacheP const &img, Vec3i const &vec) { + return ::buildGradIntern(img, vec); +} + +double ::RSlic::Voxel::buildGradColor(RSlic::Voxel::MovieCacheP const &img, const Vec3i &vec) { + return ::buildGradIntern(img, vec); +} + +template +static RSlic::Voxel::Slic3P shutUpAndTakeMyMoneyType(const RSlic::Voxel::MovieCacheP &m, int step, int stiffness, bool slico, int iterations, function grad) { + F f; + auto res = RSlic::Voxel::Slic3::initialize(m,grad,step, stiffness); + if (res.get() == nullptr) return res; //error + for (int i=0; i< iterations; i++){ + if (slico) res = res->iterateZero(f); + else res = res->iterate(f); + } + res = res->finalize(f); + return res; +} + + +RSlic::Voxel::Slic3P RSlic::Voxel::shutUpAndTakeMyMoney(const RSlic::Voxel::MovieCacheP &m, int count, int stiffness, bool slico, int iterations) { + int w = m->width(); + int h = m->height(); + int t = m->duration(); + int step = pow(w * h * t * 1.0 / count, 1.0/3); + + if (m->type() == CV_8UC3) { + return shutUpAndTakeMyMoneyType(m,step, stiffness,slico,iterations, buildGradColor); + } + else if (m->type() == CV_8UC1){ + return shutUpAndTakeMyMoneyType(m,step, stiffness, slico,iterations, buildGradGray); + } + return nullptr; +} diff --git a/lib/Voxel/RSlic3Utils.h b/lib/Voxel/RSlic3Utils.h new file mode 100644 index 0000000..e991b7a --- /dev/null +++ b/lib/Voxel/RSlic3Utils.h @@ -0,0 +1,131 @@ +#ifndef RSlic3UTILS_H +#define RSlic3UTILS_H + +#include "RSlic3.h" + +namespace RSlic { + namespace Voxel { + /** + * Simple MovieCache that just load all images into memory. + */ + class SimpleMovieCache : public MovieCache { + public: + + /** + * Initialize with filenames of all images. + * The images will be converted from BGR to LAB if needed. + * @param filenames list of files + */ + SimpleMovieCache(const std::vector &filenames); + + /** + * Initialize with list of all images. + * @param pictures list of images + */ + SimpleMovieCache(const std::vector &pictures); + + /** + * Initialize with list of all images. (rvalue) + * @param pictures list of images + */ + SimpleMovieCache(std::vector &&pictures); + + virtual Mat matAt(int t) const override; + + virtual int duration() const override; + + /** + * Returns list with all images + * @return list of images + */ + const std::vector &getPictures() const; + + private: + std::vector pictures; + }; + + /** + * Function for computing the gradient of a gray image. + * @param img MovieCacheP + * @param vec the point to compute the gradient + */ + double buildGradGray(const MovieCacheP &img, const Vec3i &vec); + + /** + * Function for computing the gradient of a color image. + * @param img MovieCacheP + * @param vec the point to compute the gradient + */ + double buildGradColor(const MovieCacheP &img, const Vec3i &vec); + + /** + * Function that is described in the paper for computing the metrics (of LAB images) for the algorithm. + */ + struct distanceColor{ + inline double operator()(const cv::Vec3i &point, const cv::Vec3i &clusterCenter, const MovieCacheP &mat, int stiffness, int step){ + if (clusterCenter[1] < 0 || clusterCenter[0] < 0 || clusterCenter[2] < 0) { + return DINF; + } + cv::Vec3b pixel = mat->at(point); + cv::Vec3b clust_pixel = mat->at(clusterCenter); + int pl = pixel[0], pa = pixel[1], pb = pixel[2]; + int cl = clust_pixel[0], ca = clust_pixel[1], cb = clust_pixel[2]; + double dc = sqrt(pow(pl - cl, 2) + pow(pb - cb, 2) + pow(pa - ca, 2)); + double ds = sqrt(pow(point[0] - clusterCenter[0], 2) + pow(point[1] - clusterCenter[1], 2) + pow(point[2] - clusterCenter[2], 2)); + + return sqrt(pow(dc, 2) / stiffness + pow(ds / step, 2)); + } + }; + + /** + * Function which is described in the paper for computing the metrics (of gray images) for the algorithm. + */ + struct distanceGray{ + inline double operator()(const cv::Vec3i &point, const cv::Vec3i &clusterCenter, const MovieCacheP &mat, int stiffness, int step){ + if (clusterCenter[1] < 0 || clusterCenter[0] < 0 || clusterCenter[2] < 0) { + return DINF; + } + int8_t pixel = mat->at(point); + int8_t clust_pixel = mat->at(clusterCenter); + double dc = abs(pixel - clust_pixel); + double ds = sqrt(pow(point[0] - clusterCenter[0], 2) + + pow(point[1] - clusterCenter[1], 2) + + pow(point[2] - clusterCenter[2], 2)); + return /*sqrt(*/pow(dc, 2) / stiffness + pow(ds / step, 2)/*)*/; + + } + }; + +#define slicFunHelper(type, fun) \ + type == CV_8UC3? fun(RSlic::Voxel::distanceColor())\ + : (type == CV_8UC1 ? fun(RSlic::Voxel::distanceGray())\ + : nullptr) + + /** + * Helping for doing an iteration by selecting the metrics automatically. + * @param slic the Slic2-Object to iterate + * @param type the type of the image (img.type() in OpenCV) + * @param slico using Slico + * @return the result of slic->iterate or slic->iterateZero with the right metrics. + * (May nullptr if type is not supported or any other error occurs) + */ + inline Slic3P iterateHelper(Slic3P p, int type, bool slico){ + if (slico){ + return slicFunHelper(type,p->iterateZero); + } + return slicFunHelper(type,p->iterate); + } + + /** + * Returns Slic3P without any "complicated" parameter. + * @param m the moviecache + * @param the amount of Superpixel (approximately) + * @param slico use the slico version? + * @param iterations how many iterations + * @return instance of Slic3 (shared_ptr) where no iterating or something similar is needed. (error -> nullptr) + */ + Slic3P shutUpAndTakeMyMoney(const RSlic::Voxel::MovieCacheP &m, int count = 4000, int stiffness = 40, bool slico = false, int iterations = 10); + } +} + +#endif diff --git a/lib/Voxel/RSlic3_impl.h b/lib/Voxel/RSlic3_impl.h new file mode 100644 index 0000000..06386f2 --- /dev/null +++ b/lib/Voxel/RSlic3_impl.h @@ -0,0 +1,387 @@ +#ifndef RSlic3_IMPL_H +#define RSlic3_IMPL_H + +#include "RSlic3.h" +#include +#include +#include <3rd/ThreadPool.h> + +#ifndef u_long +#define u_long unsigned long +#endif +#ifndef uint +#define uint unsigned int +#endif + +using RSlic::priv::aSize; +using namespace RSlic::Voxel; + + +struct RSlic::Voxel::Slic3::Settings { + Settings() : __refcount(0), step(0), stiffness(0) { + } + + Settings(const Settings *other) : + __refcount(0), step(other->step), distFunc(other->distFunc), pool(other->pool), + img(other->img), stiffness(other->stiffness), gradFunc(other->gradFunc) { + } + + void initThread(int threadcount = -1) { + if (threadcount <= 0) + threadcount = std::thread::hardware_concurrency(); + pool = std::make_shared(threadcount); + } + + ~Settings() { + } + + MovieCacheP img; + int step; + int stiffness; + shared_ptr pool; + DistanceFunc distFunc; + GradFunc gradFunc; + + std::atomic __refcount; +}; + +template +Slic3P RSlic::Voxel::Slic3::iterate(F f) const { + return iterate(setting->stiffness, f); +} + +namespace RSlic { + namespace Voxel { + namespace priv { +/* See RSlic2_impl.h for more information + */ + template + struct DistNormal { + inline double operator()(const Vec3i &point, const Vec3i ¢er, int clusterIdx) { + return f(point, center, img, stiffness * stiffness, step); + } + + const RSlic::Voxel::MovieCacheP img; + F f; + int stiffness; + int step; + }; + } + } +} +namespace { + /* + See RSlic2_impl.h for more information + */ + struct BRect { + static constexpr uint imax = std::numeric_limits::max(); + struct point { + uint x; + uint y; + uint z; + }; + point topFrontLeft, bottomBackRight; + + BRect(uint tx, uint ty, uint tz, uint bx, uint by, uint bz) + : topFrontLeft{tx, ty, tz}, bottomBackRight{bx, by, bz} { + } + + BRect() : topFrontLeft{imax, imax, imax}, bottomBackRight{0, 0, 0} { + } + + void combineWith(const BRect &other) { + topFrontLeft.x = std::min(topFrontLeft.x, other.topFrontLeft.x); + topFrontLeft.y = std::min(topFrontLeft.y, other.topFrontLeft.y); + topFrontLeft.z = std::min(topFrontLeft.z, other.topFrontLeft.z); + bottomBackRight.x = std::max(bottomBackRight.x, other.bottomBackRight.x); + bottomBackRight.y = std::max(bottomBackRight.y, other.bottomBackRight.y); + bottomBackRight.z = std::max(bottomBackRight.z, other.bottomBackRight.z); + } + }; +} +namespace RSlic { + namespace Voxel { + namespace priv { + struct iterateCommonRes { + Mat_ label; + Mat_ dist; + + iterateCommonRes(const cv::Mat::MSize &size) : label(3, size, -1), dist(3, size, DINF) { + } + + inline double &distAt(int y, int x, int t) { + return dist.at(y, x, t); + } + + inline ClusterInt &labelAt(int y, int x, int t) { + return label.at(y, x, t); + } + +#ifdef PARALLEL + BRect calcRect; +#endif + }; + + using iterateCommonResP = unique_ptr; + } + } +} +namespace { + template + inline RSlic::Voxel::priv::iterateCommonResP iterateCommonIteration(F f, int beg, int end, const cv::Mat::MSize &size, const vector ¢ers, int s, ThreadPoolP pool) { + RSlic::Voxel::priv::iterateCommonResP result(new RSlic::Voxel::priv::iterateCommonRes(size)); + const int w = size[1]; + const int h = size[0]; + const int duration = size[2]; + for (int k = beg; k < end; k++) { + auto center = centers[k]; + int px = center[0]; + int py = center[1]; + int pt = center[2]; +#ifdef PARALLEL + BRect currentRect(std::max(0, px - s), std::max(0, py - s), std::max(0, pt - s), + std::min(w, px + s + 1), std::min(h, py + s + 1), std::min(duration, pt + s + 1)); + result->calcRect.combineWith(currentRect); +#endif + for (int x = std::max(0, px - s); x < std::min(w, px + s + 1); x++) { + for (int y = std::max(0, py - s); y < std::min(h, py + s + 1); y++) { + for (int t = std::max(0, pt - s); t < std::min(duration, pt + s + 1); t++) { + Vec3i point(x, y, t); + double &d = result->distAt(y, x, t); + double D = f(point, center, k); + if (D < d) { + d = D; + result->labelAt(y, x, t) = k; + } + } + } + } + } + return result; + } + +#ifndef PARALLEL + +//See RSlic2_impl.cpp + template + RSlic::Voxel::priv::iterateCommonResP iterateCommon(F f, const ClusterSet3 &clusters, int s, ThreadPoolP pool) { + auto &&size = clusters.getClusterLabel().size; + auto centers = clusters.getCenters(); + int N = centers.size(); + return iterateCommonIteration(f, 0, N, size, centers, s, pool); + } + +#else + + //See RSlic2_impl.cpp + template + RSlic::Voxel::priv::iterateCommonResP iterateCommon(F f, const ClusterSet3 &clusters, int s, ThreadPoolP pool) { + auto &&size = clusters.getClusterLabel().size; + auto centers = clusters.getCenters(); + RSlic::Voxel::priv::iterateCommonResP result(new RSlic::Voxel::priv::iterateCommonRes(size)); + int N = centers.size(); + int thread_step = N / pool->threadcount(); + std::vector> futures; + futures.reserve(pool->threadcount()); + //Map + for (int thread_start = 0; thread_start < N; thread_start += thread_step) { + futures.push_back(pool->enqueue([&](int start) { + return iterateCommonIteration(f, start, std::min(start + thread_step, N), size, centers, s, pool); + }, thread_start)); + } + //Reduce + for (auto &&fut: futures) { + auto &&thread_result = fut.get(); + for (int x = thread_result->calcRect.topFrontLeft.x; x < thread_result->calcRect.bottomBackRight.x; x++) { + for (int y = thread_result->calcRect.topFrontLeft.y; y < thread_result->calcRect.bottomBackRight.y; y++) { + for (int t = thread_result->calcRect.topFrontLeft.z; t < thread_result->calcRect.bottomBackRight.z; t++) { + double dist_thread = thread_result->distAt(y, x, t); + double &dist_result = result->distAt(y, x, t); + if (dist_thread < dist_result) { + dist_result = dist_thread; + result->labelAt(y, x, t) = thread_result->labelAt(y, x, t); + } + } + } + } + } + return result; + } + +#endif +} + +template +Slic3P RSlic::Voxel::Slic3::iterate(int stiffness, F f) const { + const int s = setting->step; + + //Set up the normal Slic version + RSlic::Voxel::priv::DistNormal distF{setting->img, f, setting->stiffness, s}; + auto res = ::iterateCommon>(distF, clusters, s, setting->pool); + + //create a new instance + Settings *newSetting = setting; + if (setting->stiffness != stiffness) { + newSetting = new Settings(setting); + newSetting->stiffness = stiffness; + } + Slic3 *result = new Slic3(newSetting, ClusterSet3(res->label, clusters.getCenters().size()), res->dist); + return shared_ptr(result); +} + +namespace { + template + inline void iterateZeroUpdate3( + const MovieCacheP &img, const Mat &label, + const vector ¢ers, vector &max_dist_color, std::shared_ptr pool) { + int w = img->width(); + int h = img->height(); + int d = img->duration(); + //Update Slico distance maxima +#ifdef PARALLEL + std::vector> results; + results.reserve(pool->threadcount()); + int step = w / pool->threadcount(); + for (int x = 0; x < w; x += step) { + results.push_back(pool->enqueue([&](int xx) { + for (int x = xx; x < std::min(xx + step, w); x++) { +#else + for (int x = 0; x < w; x++) { +#endif + for (int y = 0; y < h; y++) { + for (int t = 0; t < d; t++) { + RSlic::Voxel::ClusterInt nearest_segment = label.at(y, x, t); + if (nearest_segment == -1) continue; + auto point = centers[nearest_segment]; + int py = point[1]; + int px = point[0]; + int pt = point[2]; + auto distColor = RSlic::priv::zero::zeroMetrik(img->at(y, x, t), img->at(py, px, pt)); + if (max_dist_color.at(nearest_segment) < distColor) { + max_dist_color.at(nearest_segment) = distColor; + } + } + } +#ifdef PARALLEL + } + }, x)); +#endif + } +#ifdef PARALLEL + for (auto &res: results) {res.get();} +#endif + } + + declareCVF_T(iterateZeroUpdate3, iterateZeroUpdate3Helper, return) +} +namespace RSlic { + namespace Voxel { + namespace priv { + //See RSlic2_impl.cpp + template + struct DistZero { + inline double operator()(const Vec3i &point, const Vec3i ¢er, int clusterIdx) { + return f(point, center, img, max_distance[clusterIdx], step); + } + + const RSlic::Voxel::MovieCacheP img; + F f; + const vector &max_distance; + int step; + }; + } + } +} + +template +Slic3P RSlic::Voxel::Slic3::iterateZero(F f) const { + const int s = setting->step; + //Set up Slico + Voxel::priv::DistZero distF{setting->img, f, max_dist_color, s}; + auto res = ::iterateCommon>(distF, clusters, s, setting->pool); + + //update max_dist_color + ClusterSet3 newClusters(res->label, clusters.getCenters().size()); + vector new_max_dist_color(max_dist_color); + ::iterateZeroUpdate3Helper(setting->img->type(), setting->img, res->label, newClusters.getCenters(), new_max_dist_color, setting->pool); + //creating a new instance + Slic3 *result = new Slic3(setting, std::move(newClusters), res->dist); + result->max_dist_color = std::move(new_max_dist_color); + return shared_ptr(result); +} + +template +Slic3P RSlic::Voxel::Slic3::finalize(F f) const { + int w = setting->img->width(); + int h = setting->img->height(); + int d = setting->img->duration(); + int *size = setting->img->sizeArray(); + Mat_ finalClusters(3, size, -1); + delete size; + int currentLabel = 0; + const int lims = setting->step * setting->step * setting->step;// (h * w * d) / (clusters.clusterCount()); + static const int neighboursX[] = {-1, 0, 1, 0, -1, 1, 1, -1, 0, 0};//{1, 0, 0, -1, 0, 0}; + static const int neighboursY[aSize(neighboursX)] = {0, -1, 0, 1, -1, -1, 1, 1, 0, 0};//{0, 1, 0, 0, -1, 0}; + static const int neighboursZ[aSize(neighboursX)] = {0, 0, 0, 0, 0, 0, 0, 0, -1, 1};//{0, 0, 1, 0, 0, -1}; + + for (int x = 0; x < w; x++) { + for (int y = 0; y < h; y++) { + for (int t = 0; t < d; t++) { + //Some unassigned pixel? + if (finalClusters.at(y, x, t) == -1) { + vector current_points; + current_points.emplace_back(x, y, t); + finalClusters.at(y, x, t) = currentLabel; + + //Look transitive for all unassigned neighbors + //set them in current_points and finalCluster + for (int i = 0; i < current_points.size(); i++) { + Vec3i point = current_points[i]; + for (int neighbour = 0; neighbour < aSize(neighboursX); neighbour++) { + int px = point[0] + neighboursX[neighbour]; + int py = point[1] + neighboursY[neighbour]; + int pt = point[2] + neighboursZ[neighbour]; + if (px < 0 || px >= w || py < 0 || py >= h || pt < 0 || pt >= d) continue; + if (finalClusters.at(py, px, pt) == -1 + && clusters.at(y, x, t) == clusters.at(py, px, pt)) { + current_points.emplace_back(px, py, pt); + finalClusters.at(py, px, pt) = currentLabel; + } + } + } + + //If there are not enough pixel in the cluster + //look for the best in the environment and conjoin both + if (current_points.size() <= lims >> 2) { + int adjlabel = currentLabel; + double topdist = DINF; + + for (int neighbour = 0; neighbour < aSize(neighboursX); neighbour++) { + int px = x + neighboursX[neighbour]; + int py = y + neighboursY[neighbour]; + int pt = t + neighboursZ[neighbour]; + if (px < 0 || px >= w || py < 0 || py >= h || pt < 0 || pt >= d) continue; + ClusterInt label = finalClusters.at(py, px, pt); + if (label >= 0 && label != currentLabel) { + double dist = f(Vec3i(x, y, t), Vec3i(px, py, pt), setting->img, 1, setting->step); + if (dist < topdist) { + adjlabel = label; + topdist = dist; + } + } + } + //Set pixel to this neighbor + for (const Vec3i point: current_points) { + finalClusters.at(point[1], point[0], point[2]) = adjlabel; + } + } else currentLabel++; + } + } + } + } + + Slic3 *result = new Slic3(setting, ClusterSet3(finalClusters, currentLabel), distance); + return std::shared_ptr(result); +} + +#endif // RSlic3_IMPL_H diff --git a/lib/priv/Useful.h b/lib/priv/Useful.h new file mode 100644 index 0000000..448c87e --- /dev/null +++ b/lib/priv/Useful.h @@ -0,0 +1,133 @@ +#ifndef USEFUL_H +#define USEFUL_H +#include +namespace RSlic{ +namespace priv{ + + /** + * Returns the size of an array (compile-time) + */ + template + constexpr std::size_t aSize(T(&)[N]) noexcept { return N;} + + + template + struct cvtp{using type= int;}; //Default + template <> + struct cvtp{using type= int8_t;}; + template <> + struct cvtp{using type= cv::Vec;}; + template <> + struct cvtp{using type= cv::Vec;}; + template <> + struct cvtp{using type= cv::Vec;}; + template <> + struct cvtp{using type= uint8_t;}; + template <> + struct cvtp{using type= cv::Vec2b;}; + template <> + struct cvtp{using type= cv::Vec3b;}; + template <> + struct cvtp{using type= cv::Vec4b;}; + template <> + struct cvtp{using type=uint16_t;}; + template <> + struct cvtp{using type=cv::Vec2w;}; + template <> + struct cvtp{using type=cv::Vec3w;}; + template <> + struct cvtp{using type=cv::Vec4w;}; + template <> + struct cvtp{using type=int16_t;}; + template <> + struct cvtp{using type=cv::Vec2s;}; + template <> + struct cvtp{using type=cv::Vec3s;}; + template <> + struct cvtp{using type=cv::Vec4s;}; + template <> + struct cvtp{using type=int32_t;}; + template <> + struct cvtp{using type=cv::Vec;}; + template <> + struct cvtp{using type=cv::Vec;}; + template <> + struct cvtp{using type=cv::Vec;}; + template <> + struct cvtp{using type=float;}; + template <> + struct cvtp{using type=cv::Vec2f;}; + template <> + struct cvtp{using type=cv::Vec3f;}; + template <> + struct cvtp{using type=cv::Vec4f;}; + template <> + struct cvtp{using type=double;}; + template <> + struct cvtp{using type=cv::Vec2d;}; + template <> + struct cvtp{using type=cv::Vec3d;}; + template <> + struct cvtp{using type=cv::Vec4d;}; +} +} + +// Creates function which calls f with the right template argument based on the type +// f -> template Function, ff -> name of new function, def -> comes after default in switch(type){....; default: def} +#define declareCVF_T(f,ff,def)\ + template \ + inline auto ff(int type, T&&... params) -> typename std::result_of)&(T...)>::type{\ + using namespace RSlic::priv;\ + static_assert(sizeof(float) * CHAR_BIT == 32,"Float Size != 32 Bit");\ + static_assert(sizeof(double) * CHAR_BIT == 64,"Double Size != 64 Bit");\ + switch(type){\ + case CV_8UC1: return f::type>(std::forward(params)...);\ + case CV_8UC2: return f::type>(std::forward(params)...);\ + case CV_8UC3: return f::type>(std::forward(params)...);\ + case CV_8UC4: return f::type>(std::forward(params)...);\ + case CV_8SC1: return f::type>(std::forward(params)...);\ + case CV_8SC2: return f::type>(std::forward(params)...);\ + case CV_8SC3: return f::type>(std::forward(params)...);\ + case CV_8SC4: return f::type>(std::forward(params)...);\ + case CV_16UC1: return f::type>(std::forward(params)...);\ + case CV_16UC2: return f::type>(std::forward(params)...);\ + case CV_16UC3: return f::type>(std::forward(params)...);\ + case CV_16UC4: return f::type>(std::forward(params)...);\ + case CV_16SC1: return f::type>(std::forward(params)...);\ + case CV_16SC2: return f::type>(std::forward(params)...);\ + case CV_16SC3: return f::type>(std::forward(params)...);\ + case CV_16SC4: return f::type>(std::forward(params)...);\ + case CV_32SC1: return f::type>(std::forward(params)...);\ + case CV_32SC2: return f::type>(std::forward(params)...);\ + case CV_32SC3: return f::type>(std::forward(params)...);\ + case CV_32SC4: return f::type>(std::forward(params)...);\ + case CV_32FC1: return f::type>(std::forward(params)...);\ + case CV_32FC2: return f::type>(std::forward(params)...);\ + case CV_32FC3: return f::type>(std::forward(params)...);\ + case CV_32FC4: return f::type>(std::forward(params)...);\ + case CV_64FC1: return f::type>(std::forward(params)...);\ + case CV_64FC2: return f::type>(std::forward(params)...);\ + case CV_64FC3: return f::type>(std::forward(params)...);\ + case CV_64FC4: return f::type>(std::forward(params)...);\ + default: def;\ + }} + +// Same as declareCVF_T, but does not use the depth. +#define declareCVF_D(f,ff,d)\ + template \ + inline auto ff(int type, T&&... params) -> typename std::result_of)&(T...)>::type{\ + using namespace RSlic::priv;\ + static_assert(sizeof(float) * CHAR_BIT == 32,"Float Size != 32 Bit");\ + static_assert(sizeof(double) * CHAR_BIT == 64,"Double Size != 64 Bit");\ + switch(type){\ + case CV_8U: return f::type>(std::forward(params)...);\ + case CV_8S: return f::type>(std::forward(params)...);\ + case CV_16U: return f::type>(std::forward(params)...);\ + case CV_16S: return f::type>(std::forward(params)...);\ + case CV_32S: return f::type>(std::forward(params)...);\ + case CV_32F: return f::type>(std::forward(params)...);\ + case CV_64F: return f::type>(std::forward(params)...);\ + default: d;\ + }} + +#endif // USEFUL_H diff --git a/lib/priv/ZeroSlico_p.h b/lib/priv/ZeroSlico_p.h new file mode 100644 index 0000000..8796094 --- /dev/null +++ b/lib/priv/ZeroSlico_p.h @@ -0,0 +1,27 @@ +#ifndef ZEROSLICO_P_H +#define ZEROSLICO_P_H +#include +#include +namespace RSlic{ +namespace priv { + namespace zero { + + template + inline uint64_t zeroMetrik(T first, T second) { + static_assert(std::is_arithmetic::value==true,"Need arithmetic type in zeroMetrik"); + return pow(first - second, 2); + } + + template + inline uint64_t zeroMetrik(const Vec &first, const Vec &second) { + static_assert(std::is_arithmetic::value==true,"Need arithmetic type in zeroMetrik"); + uint64_t res = 0; + for (int i = 0; i < n; i++) { + res += pow(first[i] - second[i], 2); + } + return res; + } + } +} +} +#endif diff --git a/lib/rslic-config.cmake b/lib/rslic-config.cmake new file mode 100644 index 0000000..7c5d538 --- /dev/null +++ b/lib/rslic-config.cmake @@ -0,0 +1,17 @@ +include(CheckCXXCompilerFlag) +CHECK_CXX_COMPILER_FLAG("-std=c++11" COMPILER_SUPPORTS_CXX11) +CHECK_CXX_COMPILER_FLAG("-std=c++0x" COMPILER_SUPPORTS_CXX0X) + +if(COMPILER_SUPPORTS_CXX11) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +elseif(COMPILER_SUPPORTS_CXX0X) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x") +else() + message(STATUS "The compiler ${CMAKE_CXX_COMPILER} has no C++11 support. Please use a different C++ compiler.") +endif() + +find_package( OpenCV REQUIRED ) +get_filename_component(SELF_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH) +include(${SELF_DIR}/rslic-target.cmake) +get_filename_component(RSLIC_INCLUDE_DIRS "${SELF_DIR}/../../include/RSlic" ABSOLUTE) +SET(RSLIC_LIB rslic ${OpenCV_LIBS})