diff --git a/CMakeLists.txt b/CMakeLists.txt index dba063b..44d4be9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,17 +1,16 @@ -cmake_minimum_required(VERSION 2.4) +cmake_minimum_required(VERSION 3.0) -project(unwap) +project(unwrap) -set(SRC_FILES main.cpp unwrap.cpp drawing.cpp matutils.cpp bgremoval.cpp charuco.cpp) -set(LIBS -lX11 -lrt) +set(SRC_FILES src/main.cpp src/unwrap.cpp src/drawing.cpp src/matutils.cpp src/bgremoval.cpp src/charuco.cpp src/harris.cpp) find_package( OpenCV REQUIRED ) add_executable(${PROJECT_NAME} ${SRC_FILES}) -target_link_libraries( ${PROJECT_NAME} ${LIBS} -lopencv_core -lopencv_aruco -lopencv_imgcodecs -lopencv_highgui -lopencv_features2d -lopencv_imgcodecs -lopencv_imgproc -lopencv_video) +target_link_libraries( ${PROJECT_NAME} ${LIBS} -lopencv_core -lopencv_aruco -lopencv_imgcodecs -lopencv_highgui -lopencv_features2d -lopencv_imgcodecs -lopencv_imgproc -lopencv_video -lopencv_stitching) target_include_directories(${PROJECT_NAME} PRIVATE "/usr/include/opencv4") -add_definitions(" -std=c++17 -Wall -O2 -flto -fno-strict-aliasing") +add_definitions(" -std=c++17 -Wall -O2 -fno-strict-aliasing") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -s") diff --git a/bgremoval.cpp b/bgremoval.cpp deleted file mode 100644 index 30e92e7..0000000 --- a/bgremoval.cpp +++ /dev/null @@ -1,22 +0,0 @@ -#include "bgremoval.h" -#include -#include -#include -#include -#include "log.h" - -bool createMask(const cv::Mat& in, cv::Mat& mask, const cv::Mat& bg) -{ - if(in.size != bg.size || in.type() != bg.type()) - { - Log(Log::ERROR)<<"input image and backgournd image size and type needs to be the same"; - return false; - } - - cv::Ptr bgremv = cv::createBackgroundSubtractorMOG2(2,10,false); - bgremv->apply(bg, mask, 1); - bgremv->apply(in, mask, 0); - cv::GaussianBlur(mask,mask,cv::Size(49,49), 15); - cv::threshold(mask, mask, 70, 255, cv::THRESH_BINARY); - return true; -} diff --git a/bgremoval.h b/bgremoval.h deleted file mode 100644 index 516791e..0000000 --- a/bgremoval.h +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - -#include - -bool createMask(const cv::Mat& in, cv::Mat& mask, const cv::Mat& bg); diff --git a/charuco.h b/charuco.h deleted file mode 100644 index a7017ab..0000000 --- a/charuco.h +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once - -#include -#include - -void createCharucoBoard(unsigned int size, const std::string& fileName); - -std::vector detectCharucoPoints(cv::Mat image, std::vector* coordiantes = nullptr, bool verbose = true); diff --git a/drawing.cpp b/drawing.cpp deleted file mode 100644 index b8b0090..0000000 --- a/drawing.cpp +++ /dev/null @@ -1,19 +0,0 @@ -#include "drawing.h" -#include -#include - -void drawRows(cv::Mat& image, const std::vector< std::vector >& rows) -{ - for(size_t i = 0; i < rows.size(); ++i) - { - for(size_t y = 0; y < rows[i].size(); ++y) - { - cv::circle(image, rows[i][y], 5, cv::viz::Color(128 * (i%3), 128 * ((i+1)%3), 128 * ((i+2)%3))); - } - } -} - -void drawEllipses(cv::Mat& image, const std::vector& ellipses ) -{ - for(const auto& ellipse : ellipses)cv::ellipse(image, ellipse, cv::viz::Color(128,128,128)); -} diff --git a/drawing.h b/drawing.h deleted file mode 100644 index b06e0d0..0000000 --- a/drawing.h +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once -#include - -void drawRows(cv::Mat& image, const std::vector< std::vector >& rows); - -void drawEllipses(cv::Mat& image, const std::vector& ellipses ); diff --git a/harris.h b/harris.h deleted file mode 100644 index fb91960..0000000 --- a/harris.h +++ /dev/null @@ -1,44 +0,0 @@ -#include -#include - -static std::vector harrisDetectPoints(cv::Mat& image, const cv::Mat& mask, - int blockSize = 5, int apature = 5, float detectorParameter = 0.01, - float minSize = 7, bool verbose = false) -{ - cv::Mat gray; - cv::cvtColor(image, gray, cv::COLOR_BGR2GRAY); - - //detect corners - cv::Mat corners; - cv::cornerHarris(gray, corners, blockSize, apature, detectorParameter); - cv::normalize(corners, corners, 0, 255, cv::NORM_MINMAX, CV_32FC1, cv::Mat()); - cv::convertScaleAbs( corners, corners ); - cv::threshold(corners, corners, 50, 255, cv::THRESH_BINARY); - cv::Mat cornersMasked; - if(mask.data && mask.size == corners.size) corners.copyTo(cornersMasked, mask); - else corners.copyTo(cornersMasked); - - if(verbose) - { - cv::imshow( "Viewer", cornersMasked ); - cv::waitKey(0); - } - - //get middle of corners - cv::SimpleBlobDetector::Params blobParams; - blobParams.filterByArea = true; - blobParams.minArea = minSize; - blobParams.maxArea = 500; - blobParams.filterByColor = false; - blobParams.blobColor = 255; - blobParams.filterByInertia = false; - blobParams.filterByConvexity = false; - cv::Ptr blobDetector = cv::SimpleBlobDetector::create(blobParams); - std::vector keypoints; - blobDetector->detect(cornersMasked, keypoints); - - std::vector points; - cv::KeyPoint::convert(keypoints, points); - - return points; -} diff --git a/main.cpp b/main.cpp deleted file mode 100644 index 0d6423b..0000000 --- a/main.cpp +++ /dev/null @@ -1,171 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include "argpopt.h" -#include "unwrap.h" -#include "bgremoval.h" -#include "normalize.h" -#include "log.h" -#include "charuco.h" -#include "harris.h" - -void cd_to_exe_dir( char *argv[] ) -{ - std::string path = argv[0]; - int ii = path.length(); - while ( !( path[ii] == '/' || path[ii] == '\\' ) && ii > 0 ) - { - ii--; - } - path.erase( ii, 100 ); - chdir( path.c_str() ); -} - -std::vector loadImages(char** fileNames) -{ - std::vector images; - for(size_t i = 0; fileNames[i]; ++i) - { - cv::Mat tmpImage = cv::imread(fileNames[i]); - if(tmpImage.data)images.push_back(tmpImage); - else std::cout<<"can not read image "< inImages = loadImages(config.inFileNames); - - if(inImages.empty()) - { - Log(Log::ERROR)<<"Input images must be provided!"; - return -1; - } - - if(config.verbose) - { - cv::namedWindow( "Viewer", cv::WINDOW_NORMAL ); - cv::resizeWindow("Viewer", 960, 500); - } - - if(config.maps.empty()) - { - cv::Mat mask; - if(config.verbose) - { - cv::imshow( "Viewer", inImages[0] ); - cv::waitKey(0); - } - if(!config.bg.empty()) - { - cv::Mat bg = cv::imread(config.bg); - - if(bg.data) - { - createMask(inImages[0], mask, bg); - if(config.verbose) - { - cv::Mat masked; - inImages[0].copyTo(masked, mask); - cv::imshow( "Viewer", masked ); - cv::waitKey(0); - } - } - else Log(Log::WARN)<<"can not read background image from "< points; - std::vector coordiantes; - - if(config.harris) - { - points = harrisDetectPoints(inImages[0], mask); - } - else - { - points = detectCharucoPoints(inImages[0], &coordiantes); - } - - Log(Log::INFO)<<"Found "<>xMat; - fs["ymat"]>>yMat; - - cv::Mat norm; - if(!config.norm.empty()) - { - cv::Mat tmp = cv::imread(config.norm); - if(!tmp.data) - { - Log(Log::WARN)<<"could not open normalize file " < -#include - -void sanityCheckMap(cv::Mat& mat, const float min, const float max, float minValue, float maxValue) -{ - for(int y = 0; y < mat.rows; y++) - { - float* col = mat.ptr(y); - for(int x = 0; x < mat.cols; ++x) - { - if(col[x] < min) - col[x] = minValue; - else if(col[x] > max) - col[x] = maxValue; - } - } -} - -std::vector::iterator getTopLeft(std::vector& points) -{ - return std::min_element(points.begin(), points.end(), [](const cv::Point2f& a, const cv::Point2f& b){ - return sqrt(a.y*a.y+a.x*a.x) < sqrt(b.y*b.y+b.x*b.x); - }); -} - -std::vector::iterator getBottomRight(std::vector& points) -{ - return std::max_element(points.begin(), points.end(), [](const cv::Point2f& a, const cv::Point2f& b){ - return sqrt(a.y*a.y+a.x*a.x) < sqrt(b.y*b.y+b.x*b.x); - }); -} - -double distance(const cv::Point2f& a, const cv::Point2f& b) -{ - return sqrt((a.y-b.y)*(a.y-b.y)+(a.x-b.x)*(a.x-b.x)); -} - -bool findClosest(size_t& xIndex, size_t& yIndex, - cv::Point2f point, const std::vector< std::vector >& array, - float xTolerance, float yTolerance) -{ - size_t rowBelow = 0; - while(rowBelow < array.size() && !array[rowBelow].empty() && array[rowBelow][0].y < point.y) ++rowBelow; - - if(rowBelow == array.size()) rowBelow = array.size()-1; - - size_t rightAbove = 0; - size_t rightBelow = 0; - - while(rightBelow < array[rowBelow].size() && array[rowBelow][rightBelow].x < point.x ) ++rightBelow; - - float distRB = distance(point, array[rowBelow][rightBelow]); - float distLB = rightBelow > 0 ? distance(point, array[rowBelow][rightBelow-1]) : std::numeric_limits::max(); - float distRA = std::numeric_limits::max(); - float distLA = std::numeric_limits::max(); - - if(rowBelow > 0) - { - while(rightAbove < array[rowBelow-1].size() && array[rowBelow-1][rightAbove].x < point.x ) ++rightAbove; - distRA = distance(point, array[rowBelow-1][rightAbove]); - if(rightAbove > 0) distLA = distance(point, array[rowBelow-1][rightAbove-1]); - } - - float* min = &distRB; - if(distLB < *min) min = &distLB; - if(distRA < *min) min = &distRA; - if(distLA < *min) min = &distLA; - - if(min == &distRB) - { - yIndex = rowBelow; - xIndex = rightBelow; - } - else if(min == &distLB) - { - yIndex = rowBelow; - xIndex = rightBelow-1; - } - else if(min == &distRA) - { - yIndex = rowBelow-1; - xIndex = rightBelow; - } - else if(min == &distLA) - { - yIndex = rowBelow-1; - xIndex = rightBelow-1; - } - else return false; - return abs(array[yIndex][xIndex].x - point.x) < xTolerance && abs(array[yIndex][xIndex].y - point.y) < yTolerance; -} - - -void interpolateMissing(cv::Mat& mat) -{ - for(int y = 0; y < mat.rows; y++) - { - float* col = mat.ptr(y); - for(int x = 0; x < mat.cols; ++x) - { - if(col[x] < 0) - { - int closestA = -1; - int closestB = -1; - int dist = std::numeric_limits::max(); - for(int i = 0; i < mat.cols; ++i) - { - if(i != closestA && col[i] >= 0 && abs(i-x) <= dist) - { - closestB = closestA; - closestA = i; - dist = abs(i-x); - } - } - if(closestA < 0 || closestB < 0) - { - closestA = -1; - closestB = -1; - dist = std::numeric_limits::max(); - for(int i = mat.cols-1; i >= 0; --i) - { - if(i != closestA && col[i] >= 0 && abs(i-x) <= dist) - { - closestB = closestA; - closestA = i; - dist = abs(i-x); - } - } - } - float slope = (col[closestB] - col[closestA])/(closestB-closestA); - col[x] = col[closestA] - (closestA-x)*slope; - } - } - } -} - -void fillMissing(cv::Mat& mat) -{ - bool finished = true; - for(int y = 0; y < mat.rows; y++) - { - float* col = mat.ptr(y); - for(int x = 0; x < mat.cols; ++x) - { - if(col[x] < 0 && col[x] > -2) - { - if(y > 0 && mat.at(y-1,x) >= 0) - { - col[x] = mat.at(y-1,x); - finished = false; - } - else if(y < mat.rows-1 && mat.at(y+1,x) >= 0) - { - col[x] = mat.at(y+1,x); - finished = false; - } - if(col[x] > 0 && ((x+1 < mat.cols && col[x] > col[x+1]) || (x > 0 && col[x] < col[x-1]))) - col[x] = -2; - } - } - } - if(!finished) fillMissing(mat); -} - - diff --git a/matutils.h b/matutils.h deleted file mode 100644 index 200a79e..0000000 --- a/matutils.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once -#include -#include - -void sanityCheckMap(cv::Mat& mat, const float min, const float max, const float minValue, const float maxValue); - -std::vector::iterator getTopLeft(std::vector& points); - -std::vector::iterator getBottomRight(std::vector& points); - -double distance(const cv::Point2f& a, const cv::Point2f& b); - -bool findClosest(size_t& xIndex, size_t& yIndex, - cv::Point2f point, const std::vector< std::vector >& array, - float xTolerance, float yTolerance); - - -void interpolateMissing(cv::Mat& mat); - -void fillMissing(cv::Mat& mat); - - diff --git a/argpopt.h b/src/argpopt.h similarity index 77% rename from argpopt.h rename to src/argpopt.h index f379d8c..963b1c5 100644 --- a/argpopt.h +++ b/src/argpopt.h @@ -1,6 +1,6 @@ /** -* Sigstoped -* Copyright (C) 2020 Carl Klemm +* Lubricant Detecter +* Copyright (C) 2021 Carl Klemm * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -23,16 +23,17 @@ struct Config { - char** inFileNames = NULL; + char** commandsFiles = NULL; + std::string output = ""; std::string norm = ""; - std::string maps = ""; std::string bg = ""; - std::string charuco = ""; bool harris = false; float minSize = 7; - unsigned int size = 600; + unsigned int size = 50; bool verbose = false; bool quiet = false; + bool interactive = false; + bool simpleStich = false; }; const char *argp_program_version = "0.2"; @@ -44,13 +45,14 @@ static struct argp_option options[] = { {"verbose", 'v', 0, 0, "Enable verbose logging" }, {"quiet", 'q', 0, 0, "Disable info messages" }, -{"map", 'm', "File Name", 0, "remap maps file" }, {"bg", 'b', "File Name", 0, "background image file" }, {"normalize", 'n', "File Name", 0, "image to use as a normalization source" }, {"min-size", 's', "Value", 0, "Smallest feature accepted as a corner" }, -{"create", 'c', "File Name", 0, "Create charuco board" }, -{"x-size", 'x', "Value", 0, "Output image width" }, +{"size", 'x', "Value", 0, "Output cell size" }, {"harris", 'r', 0, 0, "Use harris to detect points" }, +{"output", 'o', "File Name", 0, "output file name" }, +{"interactive", 'i', 0, 0, "interactivly process multiple commands" }, +{"simpe-stich", 'a', 0, 0, "Use non blending sticher" }, { 0 } }; @@ -66,9 +68,6 @@ error_t parse_opt (int key, char *arg, struct argp_state *state) case 'q': config->quiet = true; break; - case 'm': - config->maps.assign(arg); - break; case 'b': config->bg.assign(arg); break; @@ -81,14 +80,20 @@ error_t parse_opt (int key, char *arg, struct argp_state *state) case 'r': config->harris = true; break; - case 'c': - config->charuco.assign(arg); - break; case 'x': config->size=atol(arg); break; + case 'a': + config->simpleStich = true; + break; + case 'i': + config->interactive = true; + break; + case 'o': + config->output.assign(arg); + break; case ARGP_KEY_ARG: - config->inFileNames = &state->argv[state->next-1]; + config->commandsFiles = &state->argv[state->next-1]; state->next = state->argc; break; default: diff --git a/src/bgremoval.cpp b/src/bgremoval.cpp new file mode 100644 index 0000000..cf87d2c --- /dev/null +++ b/src/bgremoval.cpp @@ -0,0 +1,41 @@ +/** +* Lubricant Detecter +* Copyright (C) 2021 Carl Klemm +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* version 3 as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the +* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +* Boston, MA 02110-1301, USA. +*/ + +#include "bgremoval.h" +#include +#include +#include +#include +#include "log.h" + +bool createMask(const cv::Mat& in, cv::Mat& mask, const cv::Mat& bg) +{ + if(in.size != bg.size || in.type() != bg.type()) + { + Log(Log::ERROR)<<"input image and backgournd image size and type needs to be the same"; + return false; + } + + cv::Ptr bgremv = cv::createBackgroundSubtractorMOG2(2,10,false); + bgremv->apply(bg, mask, 1); + bgremv->apply(in, mask, 0); + cv::GaussianBlur(mask,mask,cv::Size(49,49), 15); + cv::threshold(mask, mask, 70, 255, cv::THRESH_BINARY); + return true; +} diff --git a/src/bgremoval.h b/src/bgremoval.h new file mode 100644 index 0000000..69fca22 --- /dev/null +++ b/src/bgremoval.h @@ -0,0 +1,24 @@ +/** +* Lubricant Detecter +* Copyright (C) 2021 Carl Klemm +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* version 3 as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the +* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +* Boston, MA 02110-1301, USA. +*/ + +#pragma once + +#include + +bool createMask(const cv::Mat& in, cv::Mat& mask, const cv::Mat& bg); diff --git a/charuco.cpp b/src/charuco.cpp similarity index 65% rename from charuco.cpp rename to src/charuco.cpp index c5d50af..54f167f 100644 --- a/charuco.cpp +++ b/src/charuco.cpp @@ -1,3 +1,22 @@ +/** +* Lubricant Detecter +* Copyright (C) 2021 Carl Klemm +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* version 3 as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the +* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +* Boston, MA 02110-1301, USA. +*/ + #include "charuco.h" #include #include @@ -5,6 +24,7 @@ static constexpr unsigned int X_BOARD_SIZE = 18; static constexpr unsigned int Y_BOARD_SIZE = 10; + void createCharucoBoard(unsigned int size, const std::string& fileName) { cv::Ptr board = @@ -15,7 +35,7 @@ void createCharucoBoard(unsigned int size, const std::string& fileName) cv::imwrite(fileName, charucoImage); } -std::vector detectCharucoPoints(cv::Mat image, std::vector* coordiantes, bool verbose) +std::vector detectCharucoPoints(cv::Mat image, bool verbose) { cv::Ptr board = cv::aruco::CharucoBoard::create(X_BOARD_SIZE, Y_BOARD_SIZE, 0.03f, 0.02f, cv::aruco::getPredefinedDictionary(cv::aruco::DICT_4X4_250 )); @@ -58,10 +78,15 @@ std::vector detectCharucoPoints(cv::Mat image, std::vectorpush_back(cv::Point2i(id % (X_BOARD_SIZE-1), (Y_BOARD_SIZE-2) - id/(X_BOARD_SIZE-1))); + std::vector detections; + + for(size_t i = 0; i < charucoIds.size(); ++i) + { + cv::Point2i coordiante(charucoIds[i] % (X_BOARD_SIZE-1), (Y_BOARD_SIZE-2) - charucoIds[i]/(X_BOARD_SIZE-1)); + detections.push_back(DetectedPoint(charucoCorners[i], coordiante)); + } - return charucoCorners; + return detections; } - return std::vector(); + return std::vector(); } diff --git a/src/charuco.h b/src/charuco.h new file mode 100644 index 0000000..e67c916 --- /dev/null +++ b/src/charuco.h @@ -0,0 +1,28 @@ +/** +* Lubricant Detecter +* Copyright (C) 2021 Carl Klemm +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* version 3 as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the +* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +* Boston, MA 02110-1301, USA. +*/ + +#pragma once + +#include +#include +#include "detectedpoint.h" + +void createCharucoBoard(unsigned int size, const std::string& fileName); + +std::vector detectCharucoPoints(cv::Mat image, bool verbose = true); diff --git a/src/detectedpoint.h b/src/detectedpoint.h new file mode 100644 index 0000000..625bf55 --- /dev/null +++ b/src/detectedpoint.h @@ -0,0 +1,31 @@ +/** +* Lubricant Detecter +* Copyright (C) 2021 Carl Klemm +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* version 3 as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the +* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +* Boston, MA 02110-1301, USA. +*/ + +#pragma once + +class DetectedPoint +{ +public: + cv::Point2f point; + cv::Point2i coordinate; + + DetectedPoint(const cv::Point2f& pointI, const cv::Point2f& coordinateI): + point(pointI), coordinate(coordinateI) + {} +}; diff --git a/src/drawing.cpp b/src/drawing.cpp new file mode 100644 index 0000000..c25c446 --- /dev/null +++ b/src/drawing.cpp @@ -0,0 +1,38 @@ +/** +* Lubricant Detecter +* Copyright (C) 2021 Carl Klemm +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* version 3 as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the +* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +* Boston, MA 02110-1301, USA. +*/ + +#include "drawing.h" +#include +#include + +void drawRows(cv::Mat& image, const std::vector< std::vector >& rows) +{ + for(size_t i = 0; i < rows.size(); ++i) + { + for(size_t y = 0; y < rows[i].size(); ++y) + { + cv::circle(image, rows[i][y], 5, cv::viz::Color(128 * (i%3), 128 * ((i+1)%3), 128 * ((i+2)%3))); + } + } +} + +void drawEllipses(cv::Mat& image, const std::vector& ellipses ) +{ + for(const auto& ellipse : ellipses)cv::ellipse(image, ellipse, cv::viz::Color(128,128,128)); +} diff --git a/src/drawing.h b/src/drawing.h new file mode 100644 index 0000000..3e995aa --- /dev/null +++ b/src/drawing.h @@ -0,0 +1,25 @@ +/** +* Lubricant Detecter +* Copyright (C) 2021 Carl Klemm +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* version 3 as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the +* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +* Boston, MA 02110-1301, USA. +*/ + +#pragma once +#include + +void drawRows(cv::Mat& image, const std::vector< std::vector >& rows); + +void drawEllipses(cv::Mat& image, const std::vector& ellipses ); diff --git a/src/harris.cpp b/src/harris.cpp new file mode 100644 index 0000000..4ef2503 --- /dev/null +++ b/src/harris.cpp @@ -0,0 +1,294 @@ +/** +* Lubricant Detecter +* Copyright (C) 2021 Carl Klemm +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* version 3 as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the +* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +* Boston, MA 02110-1301, USA. +*/ + +#include +#include +#include +#include "harris.h" +#include "log.h" +#include "matutils.h" +#include "drawing.h" + +static float detimineXPitch(const std::vector& row, float fuzz = 1.3f) +{ + std::vector xRowDists; + for(size_t i = 0; i < row.size()-1; ++i) + xRowDists.push_back(abs(row[i+1].x-row[i].x)); + float xMinDist = *std::min(xRowDists.begin(), xRowDists.end()); + + Log(Log::DEBUG)<<__func__<<": xMinDist "< fitEllipses(std::vector< std::vector >& rows, bool remove = true) +{ + if(rows.empty()) return std::vector(); + + std::vector ellipses; + ellipses.reserve(rows.size()); + for(size_t i = 0; i < rows.size(); ++i) + { + if(rows[i].size() > 4) ellipses.push_back(cv::fitEllipse(rows[i])); + else + { + rows.erase(rows.begin()+i); + i--; + } + } + return ellipses; +} + +static bool sanityCheckElipses(std::vector< std::vector >& rows, std::vector& elipses) +{ + if(rows.size() != elipses.size() && elipses.size() > 1) return false; + + for(size_t i = 0; i < elipses.size(); ++i) + { + float angDiff = fmod(elipses[i].angle,90); + if(angDiff < 90-5 && angDiff > 5) + { + elipses.erase(elipses.begin()+i); + rows.erase(rows.begin()+i); + --i; + } + } + + std::vector widths; + std::vector heights; + + for(auto& elipse : elipses) + { + widths.push_back(elipse.size.width); + heights.push_back(elipse.size.height); + } + + std::vector outliersW; + std::vector outliersH; + thompsonTauTest(widths, outliersW, 2); + thompsonTauTest(heights, outliersH, 2); + + std::vector< std::vector > rowsReduced; + std::vector elipsesReduced; + + for(size_t i = 0; i < elipses.size(); ++i) + { + bool found = false; + for(size_t j = 0; j < outliersW.size() && !found; ++j) + if(outliersW[j]==i) found = true; + for(size_t j = 0; j < outliersH.size() && !found; ++j) + if(outliersH[j]==i) found = true; + if(!found) + { + rowsReduced.push_back(rows[i]); + elipsesReduced.push_back(elipses[i]); + } + } + elipses = elipsesReduced; + rows = rowsReduced; + return true; +} + +static std::vector< std::vector > sortPointsIntoRows(std::vector& points) +{ + + if(points.size() < 6) return std::vector< std::vector >(); + + cv::Point2f topLeft(*getTopLeft(points)); + cv::Point2f bottomRight(*getBottomRight(points)); + + Log(Log::DEBUG)<<"topLeft "< bottomRight.x+fuzz || points[i].y > bottomRight.y+fuzz) + points.erase(points.begin()+i); + } + + std::sort(points.begin(), points.end(), [](const cv::Point2f& a, const cv::Point2f& b){return a.y < b.y;}); + + double accumulator = points[0].y+points[1].y; + size_t accuCount = 2; + float sigDist = (bottomRight.y-topLeft.y) / 20; + + std::vector< std::vector > result(1); + result.back().push_back(points[0]); + result.back().push_back(points[1]); + + for(size_t i = 2; i < points.size(); ++i) + { + if( points[i].y - accumulator/accuCount > sigDist ) + { + if(points.size() - i - 3 < 0) break; + else + { + accumulator = points[i+1].y+points[i+2].y; + accuCount = 2; + } + result.push_back(std::vector()); + } + result.back().push_back(points[i]); + accumulator += points[i].y; + ++accuCount; + } + + for(auto& row : result) + std::sort(row.begin(), row.end(), [](const cv::Point2f& a, const cv::Point2f& b){return a.x < b.x;}); + return result; +} + +static std::vector harrisGenerateCoordinates(std::vector& points, bool verbose) +{ + std::vector< std::vector > rows = sortPointsIntoRows(points); + Log(Log::INFO)<<"Found "<(); + } + + std::vector< cv::RotatedRect > ellipses = fitEllipses(rows); + Log(Log::INFO)<<"Found "<(); + } + + sanityCheckElipses(rows, ellipses); + + if(rows.size() < 2 || rows.size() != ellipses.size()) { + Log(Log::ERROR)<<__func__<<": rows < 2 or rows != ellipses"; + return std::vector(); + } + + std::vector coordinates; + + coordinates.assign(points.size(), cv::Point2i()); + + float meanYdist = 0; + for(size_t i = 0; i < ellipses.size()-1; ++i) + { + meanYdist += ellipses[i+1].center.y-ellipses[i].center.y; + } + meanYdist = meanYdist/ellipses.size(); + + float meanXdist = 0; + float xMin = std::numeric_limits::max(); + float xMeanMin = 0; + float xMax = std::numeric_limits::min(); + + for(auto& row : rows) + { + meanXdist+=detimineXPitch(row); + xMeanMin+=row.front().x; + if(xMin > row.front().x ) + xMin = row.front().x; + if(xMax < row.back().x) + xMax = row.back().x; + } + meanXdist/=rows.size(); + Log(Log::DEBUG)<<__func__<<": meanXdist "<(std::lround(abs((xMax-xMin)/meanXdist))+1); + + std::vector dpoints; + + for(size_t y = 0; y < rows.size(); ++y) + { + Log(Log::DEBUG)<<__func__<<": Proc row "< harrisDetectPoints(cv::Mat& image, const cv::Mat& mask, + int blockSize, int apature, float detectorParameter, + float minSize, bool verbose) +{ + cv::Mat gray; + cv::cvtColor(image, gray, cv::COLOR_BGR2GRAY); + + //detect corners + cv::Mat corners; + cv::cornerHarris(gray, corners, blockSize, apature, detectorParameter); + cv::normalize(corners, corners, 0, 255, cv::NORM_MINMAX, CV_32FC1, cv::Mat()); + cv::convertScaleAbs( corners, corners ); + cv::threshold(corners, corners, 50, 255, cv::THRESH_BINARY); + cv::Mat cornersMasked; + if(mask.data && mask.size == corners.size) corners.copyTo(cornersMasked, mask); + else corners.copyTo(cornersMasked); + + if(verbose) + { + cv::imshow( "Viewer", cornersMasked ); + cv::waitKey(0); + } + + //get middle of corners + cv::SimpleBlobDetector::Params blobParams; + blobParams.filterByArea = true; + blobParams.minArea = minSize; + blobParams.maxArea = 500; + blobParams.filterByColor = false; + blobParams.blobColor = 255; + blobParams.filterByInertia = false; + blobParams.filterByConvexity = false; + cv::Ptr blobDetector = cv::SimpleBlobDetector::create(blobParams); + std::vector keypoints; + blobDetector->detect(cornersMasked, keypoints); + + std::vector points; + cv::KeyPoint::convert(keypoints, points); + + return harrisGenerateCoordinates(points, verbose); +} diff --git a/src/harris.h b/src/harris.h new file mode 100644 index 0000000..cb551e2 --- /dev/null +++ b/src/harris.h @@ -0,0 +1,25 @@ +/** +* Lubricant Detecter +* Copyright (C) 2021 Carl Klemm +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* version 3 as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the +* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +* Boston, MA 02110-1301, USA. +*/ + +#include +#include "detectedpoint.h" + +std::vector harrisDetectPoints(cv::Mat& image, const cv::Mat& mask, + int blockSize = 5, int apature = 5, float detectorParameter = 0.01, + float minSize = 7, bool verbose = false); diff --git a/log.h b/src/log.h similarity index 56% rename from log.h rename to src/log.h index 5f3a57d..5052a26 100644 --- a/log.h +++ b/src/log.h @@ -1,3 +1,22 @@ +/** +* Lubricant Detecter +* Copyright (C) 2021 Carl Klemm +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* version 3 as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the +* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +* Boston, MA 02110-1301, USA. +*/ + #pragma once #include #include diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..58f0cf7 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,296 @@ +/** +* Lubricant Detecter +* Copyright (C) 2021 Carl Klemm +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* version 3 as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the +* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +* Boston, MA 02110-1301, USA. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "argpopt.h" +#include "unwrap.h" +#include "bgremoval.h" +#include "normalize.h" +#include "log.h" +#include "charuco.h" +#include "harris.h" + +#define IMREAD_SIZE pow(2, 20) + +enum { + CREATE_CHARUCO, + CREATE_MAP, + APPLY_MAP, + EXIT +}; + +int selectOperation(char** opt) +{ + if(!opt || !opt[0]) + return -1; + else if(strcmp(opt[0], "create" ) == 0) + return CREATE_MAP; + else if(strcmp(opt[0], "apply" ) == 0) + return APPLY_MAP; + else if(strcmp(opt[0], "makeboard" ) == 0) + return CREATE_CHARUCO; + else if(strcmp(opt[0], "exit" ) == 0) + return EXIT; + return -1; +} + +std::vector loadImages(char** fileNames) +{ + std::vector images; + for(size_t i = 0; fileNames[i]; ++i) + { + int fd = open(fileNames[i], O_RDONLY); + if(fd < 0) + { + Log(Log::WARN)<<"could not open "< buffer(IMREAD_SIZE); + size_t count; + while((count = read(fd, buffer.data()+pos, IMREAD_SIZE)) > 0) + { + pos+=count; + buffer.resize(pos+IMREAD_SIZE); + Log(Log::WARN)< inImages; + if(operation == CREATE_MAP || operation == APPLY_MAP) + { + inImages = loadImages(fileNames); + + if(inImages.empty()) + { + Log(Log::ERROR)<<"Input images must be provided"; + return -1; + } + } + + if(operation == CREATE_CHARUCO) + { + std::string fileName = config.output.empty() ? "out.png" : config.output; + createCharucoBoard(config.size*14, fileName); + Log(Log::INFO)<<"Exported charuco map of size "< points; + + if(config.harris) + points = harrisDetectPoints(inImages[0], mask); + else + points = detectCharucoPoints(inImages[0], config.verbose); + + Log(Log::INFO)<<"Found "< remapedImages; + + for(size_t i = 0; i "; + char cmdline[1024]; + char** tokens = new char*[256]; + char *token; + char *r = cmdline; + + for(size_t i = 0; i < 256/sizeof(char*); ++i) + tokens[i] = nullptr; + + std::cin.getline(cmdline, sizeof(cmdline), '\n'); + + for(size_t i = 0; (token = strtok_r(r, " ", &r)) && i < 256; ++i) + { + tokens[i] = new char[strlen(token)+1]; + strcpy(tokens[i], token); + } + + int operation = selectOperation(tokens); + if(operation < 0) + { + Log(Log::ERROR)<<"A operation must be selected"; + continue; + } + + if(perfromOperation(operation, tokens+1, config) < 0) + break; + + for(size_t i = 0; i < 256/sizeof(char*) && tokens[i]; ++i) + delete[] tokens[i]; + delete[] tokens; + } + } + + if(config.verbose) + { + cv::destroyWindow("Viewer"); + cv::waitKey(0); + } + + return ret; +} + diff --git a/src/matutils.cpp b/src/matutils.cpp new file mode 100644 index 0000000..455b7ec --- /dev/null +++ b/src/matutils.cpp @@ -0,0 +1,353 @@ +/** +* Lubricant Detecter +* Copyright (C) 2021 Carl Klemm +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* version 3 as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the +* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +* Boston, MA 02110-1301, USA. +*/ + +#include "matutils.h" +#include +#include +#include +#include +#include "log.h" + +void sanityCheckMap(cv::Mat& mat, const float min, const float max, float minValue, float maxValue) +{ + for(int y = 0; y < mat.rows; y++) + { + float* col = mat.ptr(y); + for(int x = 0; x < mat.cols; ++x) + { + if(col[x] < min) + col[x] = minValue; + else if(col[x] > max) + col[x] = maxValue; + } + } +} + +void thompsonTauTest(const std::vector& in, std::vector& outliers, float criticalValue) +{ + float mean = 0; + size_t n = 0; + for(size_t i = 0; i < in.size(); ++i) + { + bool found = false; + for(size_t j = 0; j < outliers.size() && !found; ++j) + if(outliers[j]==i) found = true; + if(!found) + { + mean+=in[i]; + ++n; + } + } + mean/=n; + + float sd = 0; + for(size_t i = 0; i < in.size(); ++i) + { + bool found = false; + for(size_t j = 0; j < outliers.size() && !found; ++j) + if(outliers[j]==i) found = true; + if(!found) sd+=pow(in[i]-mean,2); + } + sd = sqrt(sd/(n-1)); + float rej = (criticalValue*(n-1))/(sqrt(n)*sqrt(n-2+criticalValue)); + bool removed = false; + for(size_t i = 0; i < in.size() && !removed; ++i) + { + bool found = false; + for(size_t j = 0; j < outliers.size() && !found; ++j) + if(outliers[j]==i) found = true; + if(!found) + { + if(abs((in[i]-mean)/sd) > rej) + { + Log(Log::DEBUG)<<__func__<<": "<::iterator getTopLeft(std::vector& points) +{ + return std::min_element(points.begin(), points.end(), [](const cv::Point2f& a, const cv::Point2f& b){ + return sqrt(a.y*a.y+a.x*a.x) < sqrt(b.y*b.y+b.x*b.x); + }); +} + +std::vector::iterator getBottomRight(std::vector& points) +{ + return std::max_element(points.begin(), points.end(), [](const cv::Point2f& a, const cv::Point2f& b){ + return sqrt(a.y*a.y+a.x*a.x) < sqrt(b.y*b.y+b.x*b.x); + }); +} + +double distance(const cv::Point2f& a, const cv::Point2f& b) +{ + return sqrt((a.y-b.y)*(a.y-b.y)+(a.x-b.x)*(a.x-b.x)); +} + +bool findClosest(size_t& xIndex, size_t& yIndex, + const cv::Point2f point, const std::vector< std::vector >& array, + float xTolerance, float yTolerance) +{ + size_t rowBelow = 0; + while(rowBelow < array.size() && !array[rowBelow].empty() && array[rowBelow][0].y < point.y) ++rowBelow; + + if(rowBelow == array.size()) rowBelow = array.size()-1; + + size_t rightAbove = 0; + size_t rightBelow = 0; + + while(rightBelow < array[rowBelow].size() && array[rowBelow][rightBelow].x < point.x ) ++rightBelow; + + float distRB = distance(point, array[rowBelow][rightBelow]); + float distLB = rightBelow > 0 ? distance(point, array[rowBelow][rightBelow-1]) : std::numeric_limits::max(); + float distRA = std::numeric_limits::max(); + float distLA = std::numeric_limits::max(); + + if(rowBelow > 0) + { + while(rightAbove < array[rowBelow-1].size() && array[rowBelow-1][rightAbove].x < point.x ) ++rightAbove; + distRA = distance(point, array[rowBelow-1][rightAbove]); + if(rightAbove > 0) distLA = distance(point, array[rowBelow-1][rightAbove-1]); + } + + float* min = &distRB; + if(distLB < *min) min = &distLB; + if(distRA < *min) min = &distRA; + if(distLA < *min) min = &distLA; + + if(min == &distRB) + { + yIndex = rowBelow; + xIndex = rightBelow; + } + else if(min == &distLB) + { + yIndex = rowBelow; + xIndex = rightBelow-1; + } + else if(min == &distRA) + { + yIndex = rowBelow-1; + xIndex = rightBelow; + } + else if(min == &distLA) + { + yIndex = rowBelow-1; + xIndex = rightBelow-1; + } + else return false; + return abs(array[yIndex][xIndex].x - point.x) < xTolerance && abs(array[yIndex][xIndex].y - point.y) < yTolerance; +} + +bool findClosest(size_t& index, const cv::Point2f point, const std::vector& array, float xTolerance, float yTolerance) +{ + float minDistance = std::numeric_limits::max(); + + bool found; + for(size_t i = 0; i < array.size(); ++i) + { + const auto& arrayPoint = array[i]; + if(abs(arrayPoint.x - point.x < xTolerance) && + abs(arrayPoint.y - point.y < yTolerance) && + distance(point, arrayPoint) < minDistance) + { + index = i; + found = true; + } + } + return found; +} + +void interpolateMissing(cv::Mat& mat) +{ + assert(mat.type() == CV_32FC1); + + for(int y = 0; y < mat.rows; y++) + { + float* col = mat.ptr(y); + for(int x = 0; x < mat.cols; ++x) + { + if(col[x] < 0) + { + int closestA = -1; + int closestB = -1; + int dist = std::numeric_limits::max(); + for(int i = 0; i < mat.cols; ++i) + { + if(i != closestA && col[i] >= 0 && abs(i-x) <= dist) + { + closestB = closestA; + closestA = i; + dist = abs(i-x); + } + } + if(closestA < 0 || closestB < 0) + { + closestA = -1; + closestB = -1; + dist = std::numeric_limits::max(); + for(int i = mat.cols-1; i >= 0; --i) + { + if(i != closestA && col[i] >= 0 && abs(i-x) <= dist) + { + closestB = closestA; + closestA = i; + dist = abs(i-x); + } + } + } + float slope = (col[closestB] - col[closestA])/(closestB-closestA); + col[x] = col[closestA] - (closestA-x)*slope; + } + } + } +} + +void fillMissing(cv::Mat& mat) +{ + assert(mat.type() == CV_32FC1); + + bool finished = true; + for(int y = 0; y < mat.rows; y++) + { + float* col = mat.ptr(y); + for(int x = 0; x < mat.cols; ++x) + { + if(col[x] < 0 && col[x] > -2) + { + if(y > 0 && mat.at(y-1,x) >= 0) + { + col[x] = mat.at(y-1,x); + finished = false; + } + else if(y < mat.rows-1 && mat.at(y+1,x) >= 0) + { + col[x] = mat.at(y+1,x); + finished = false; + } + if(col[x] > 0 && ((x+1 < mat.cols && col[x] > col[x+1]) || (x > 0 && col[x] < col[x-1]))) + col[x] = -2; + } + } + } + if(!finished) fillMissing(mat); +} + +bool findDeadSpace(const cv::Mat& mat, cv::Rect& roi) +{ + assert(mat.type() == CV_32FC1); + + if(mat.cols < 2 || mat.rows < 2) + return false; + + float centerTop = mat.at(0,mat.cols/2); + float centerLeft = mat.at(mat.rows/2,0); + float centerBottom = mat.at(mat.rows-1,mat.cols/2); + float centerRight = mat.at(mat.rows/2,mat.cols-1); + + int left = 0; + int right = mat.cols-1; + int top = 0; + int bottom = mat.rows-1; + for(; left < mat.cols && mat.at(mat.rows/2,left) == centerLeft; ++left); + for(; top < mat.rows && mat.at(top,mat.cols/2) == centerTop; ++top); + for(; right > left && mat.at(mat.rows/2,right) == centerRight; --right); + for(; bottom > top && mat.at(bottom,mat.cols/2) == centerBottom; --bottom); + + + roi.x=left > 0 ? left : 0; + roi.y=top > 0 ? top : 0; + roi.width = right-roi.x; + roi.height = bottom-roi.y; + + if(roi.width < 0 || roi.height < 0) + return false; + + Log(Log::DEBUG)<<"Reduced Roi: "< cols; + cols.reserve(mat.cols); + + for(int x = 0; x < mat.cols; x++) + { + bool empty = true; + for(int y = 0; y < mat.rows; y++) + { + if(mat.at(x,y) > 0) + { + empty = false; + break; + } + } + + if(!empty) + cols.push_back(x); + } + + if(mat.cols < static_cast(cols.size())) + { + cv::Mat tmp(cv::Size(cols.size(), mat.rows), CV_32FC1); + + for(auto& col : cols) + { + cv::Rect roi(cv::Point2i(col, 0), cv::Size(1, mat.rows)); + mat.copyTo(tmp(roi)); + } + mat.release(); + mat = tmp; + + return true; + } + + return false; +} + +void removeEmptyFrontBackCols(cv::Mat& mat) +{ + assert(mat.type() == CV_32FC1); + + int front = 0; + int back = 0; + for(int y = 0; y < mat.rows; ++y) + { + if(mat.at(y,0) >= 0) ++front; + if(mat.at(y,mat.cols-1) >= 0) ++back; + } + Log(Log::DEBUG)<<__func__<<" front: "< +#include + +void sanityCheckMap(cv::Mat& mat, const float min, const float max, const float minValue, const float maxValue); + +std::vector::iterator getTopLeft(std::vector& points); + +std::vector::iterator getBottomRight(std::vector& points); + +double distance(const cv::Point2f& a, const cv::Point2f& b); + +bool findClosest(size_t& xIndex, size_t& yIndex, + const cv::Point2f point, const std::vector< std::vector >& array, + float xTolerance, float yTolerance); + +bool findClosest(size_t& index, const cv::Point2f point, const std::vector& array, float xTolerance, float yTolerance); + +void thompsonTauTest(const std::vector& in, std::vector& outliers, float criticalValue); + +void interpolateMissing(cv::Mat& mat); + +void fillMissing(cv::Mat& mat); + +bool findDeadSpace(const cv::Mat& mat, cv::Rect& roi); + +bool deleteEmptyCols(cv::Mat& mat); + +void removeEmptyFrontBackCols(cv::Mat& mat); + + diff --git a/normalize.h b/src/normalize.h similarity index 53% rename from normalize.h rename to src/normalize.h index 635c7ac..1526af6 100644 --- a/normalize.h +++ b/src/normalize.h @@ -1,3 +1,22 @@ +/** +* Lubricant Detecter +* Copyright (C) 2021 Carl Klemm +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* version 3 as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the +* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +* Boston, MA 02110-1301, USA. +*/ + #pragma once #include #include diff --git a/src/unwrap.cpp b/src/unwrap.cpp new file mode 100644 index 0000000..a7def43 --- /dev/null +++ b/src/unwrap.cpp @@ -0,0 +1,263 @@ +/** +* Lubricant Detecter +* Copyright (C) 2021 Carl Klemm +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* version 3 as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the +* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +* Boston, MA 02110-1301, USA. +*/ + +#include "unwrap.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "matutils.h" +#include "drawing.h" +#include "log.h" + +static void sortIntoRemapMaps(const std::vector& points, cv::Mat& xMat, cv::Mat& yMat, cv::Point2i& topLeftCoordinate) +{ + if(points.size() < 6) + { + Log(Log::ERROR)<<__func__<<": at least 6 points are needed"; + return; + } + + int xMin = std::numeric_limits::max(); + int xMax = std::numeric_limits::min(); + int yMin = std::numeric_limits::max(); + int yMax = std::numeric_limits::min(); + + for(auto& point : points) + { + Log(Log::DEBUG)<<"point: "< xMax) xMax = point.coordinate.x; + else if(point.coordinate.x < xMin) xMin = point.coordinate.x; + if(point.coordinate.y > yMax) yMax = point.coordinate.y; + else if(point.coordinate.y < yMin) yMin = point.coordinate.y; + } + + topLeftCoordinate.x = xMin; + topLeftCoordinate.y = yMin; + + size_t xGridSize = xMax-xMin+1; + size_t yGridSize = yMax-yMin+1; + xMat.create(cv::Size(xGridSize, yGridSize), CV_32FC1); + yMat.create(cv::Size(xGridSize, yGridSize), CV_32FC1); + xMat = -1; + yMat = -1; + + Log(Log::DEBUG)<<"Grid: "<(y); + float* coly = yMat.ptr(y); + for(int x = 0; x < xMat.cols; x++) + { + for(size_t i = 0; i < points.size(); ++i) + { + if(points[i].coordinate == cv::Point2i(x+xMin,y+yMin)) + { + colx[x] = points[i].point.x; + coly[x] = points[i].point.y; + break; + } + } + + + } + } + +} + +bool createRemapMap(const cv::Mat& image, RemapMap& out, const std::vector& points, bool verbose) +{ + sortIntoRemapMaps(points, out.xMat, out.yMat, out.topLeftCoordinate); + + Log(Log::DEBUG)<<__func__<<": xMat raw\n"<>map.xMat; + fs["ymat"]>>map.yMat; + fs["origin"]>>map.topLeftCoordinate; + return map; +} + +RemapedImage applyRemap(const cv::Mat& image, const RemapMap &map) +{ + RemapedImage out; + cv::Mat xMapResized; + cv::Mat yMapResized; + const cv::Size outputSize(map.outputCellSize*map.xMat.cols,map.outputCellSize*map.xMat.rows); + + cv::resize(map.xMat, xMapResized, outputSize, cv::INTER_LINEAR); + cv::resize(map.yMat, yMapResized, outputSize, cv::INTER_LINEAR); + cv::Rect roi; + cv::Mat xMapRed; + cv::Mat yMapRed; + if(findDeadSpace(xMapResized, roi)) + { + xMapRed = xMapResized(roi); + yMapRed = yMapResized(roi); + } + else + { + xMapRed = xMapResized; + yMapRed = yMapResized; + } + cv::remap(image, out.image, xMapRed, yMapRed, cv::INTER_LINEAR); + out.origin = cv::Point2i(map.topLeftCoordinate.x*map.outputCellSize, map.topLeftCoordinate.y*map.outputCellSize); + return out; +} + +cv::Mat simpleStich(const std::vector& images) +{ + if(images.size() < 1) + return cv::Mat(); + + cv::Size outputSize(0,0); + for(auto& image : images) + { + if(outputSize.width < image.image.cols+image.origin.x) + outputSize.width = image.image.cols+image.origin.x; + if(outputSize.height < image.image.rows+image.origin.y) + outputSize.height = image.image.rows+image.origin.y; + + Log(Log::DEBUG)<<"image: "<& images, bool seamAdjust) +{ + std::vector masks(images.size()); + std::vector corners(images.size()); + std::vector sizes(images.size()); + for (size_t i = 0; i < images.size(); i++) + { + masks[i].create(images[i].image.size(), CV_8U); + masks[i].setTo(cv::Scalar::all(255)); + corners[i] = images[i].origin; + sizes[i] = images[i].image.size(); + } + + + if(seamAdjust) + { + std::vector images32f(images.size()); + std::vector masksUmat(images.size()); + for (size_t i = 0; i < images.size(); i++) + { + images[i].image.convertTo(images32f[i], CV_32F); + masks[i].copyTo(masksUmat[i]); + } + cv::detail::VoronoiSeamFinder seamFinder; + seamFinder.find(images32f, corners, masksUmat); + for (size_t i = 0; i < images.size(); i++) + { + masksUmat[i].copyTo(masks[i]); + masksUmat[i].release(); + images32f[i].release(); + } + images32f.clear(); + masksUmat.clear(); + } + + cv::Ptr blender; + blender = cv::detail::Blender::createDefault(cv::detail::Blender::Blender::MULTI_BAND, false); + cv::detail::MultiBandBlender* mb = dynamic_cast(blender.get()); + mb->setNumBands(5); + blender->prepare(corners, sizes); + for (size_t i = 0; i < images.size(); i++) + { + blender->feed(images[i].image, masks[i], corners[i]); + } + cv::Mat result; + cv::Mat result_mask; + blender->blend(result, result_mask); + + return result; +} diff --git a/src/unwrap.h b/src/unwrap.h new file mode 100644 index 0000000..f9bacda --- /dev/null +++ b/src/unwrap.h @@ -0,0 +1,48 @@ +/** +* Lubricant Detecter +* Copyright (C) 2021 Carl Klemm +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* version 3 as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the +* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +* Boston, MA 02110-1301, USA. +*/ + +#pragma once +#include +#include +#include "detectedpoint.h" + +struct RemapedImage +{ + cv::Mat image; + cv::Point2i origin; +}; + +struct RemapMap +{ + cv::Mat xMat; + cv::Mat yMat; + cv::Point2i topLeftCoordinate; + unsigned int outputCellSize; +}; + +bool saveRemapMap(const RemapMap& map, const std::string& fileName); + +RemapMap loadRemapMap(const std::string& fileName); + +bool createRemapMap(const cv::Mat& image, RemapMap& out, const std::vector& points, bool verbose = false); + +RemapedImage applyRemap(const cv::Mat& image, const RemapMap &map); + +cv::Mat stich(const std::vector& images, bool seamAdjust = false); +cv::Mat simpleStich(const std::vector& images); diff --git a/unwrap.cpp b/unwrap.cpp deleted file mode 100644 index f2aea31..0000000 --- a/unwrap.cpp +++ /dev/null @@ -1,568 +0,0 @@ -#include "unwrap.h" - -#include -#include -#include -#include -#include -#include - -#include "matutils.h" -#include "drawing.h" -#include "log.h" - -struct DisplacmentMap -{ - std::vector< std::vector > destination; - std::vector< std::vector > source; -}; - -static std::vector< std::vector > sortPointsIntoRows(std::vector& points) -{ - - if(points.size() < 6) return std::vector< std::vector >(); - - cv::Point2f topLeft(*getTopLeft(points)); - cv::Point2f bottomRight(*getBottomRight(points)); - - Log(Log::DEBUG)<<"topLeft "< bottomRight.x+fuzz || points[i].y > bottomRight.y+fuzz) - points.erase(points.begin()+i); - } - - std::sort(points.begin(), points.end(), [](const cv::Point2f& a, const cv::Point2f& b){return a.y < b.y;}); - - double accumulator = points[0].y+points[1].y; - size_t accuCount = 2; - float sigDist = (bottomRight.y-topLeft.y) / 20; - - std::vector< std::vector > result(1); - result.back().push_back(points[0]); - result.back().push_back(points[1]); - - for(size_t i = 2; i < points.size(); ++i) - { - if( points[i].y - accumulator/accuCount > sigDist ) - { - if(points.size() - i - 3 < 0) break; - else - { - accumulator = points[i+1].y+points[i+2].y; - accuCount = 2; - } - result.push_back(std::vector()); - } - result.back().push_back(points[i]); - accumulator += points[i].y; - ++accuCount; - } - - for(auto& row : result) std::sort(row.begin(), row.end(), [](const cv::Point2f& a, const cv::Point2f& b){return a.x < b.x;}); - - return result; -} - - -static float detimineXPitch(const std::vector& row, float fuzz = 1.3f) -{ - std::vector xRowDists; - for(size_t i = 0; i < row.size()-1; ++i) - xRowDists.push_back(abs(row[i+1].x-row[i].x)); - float xMinDist = *std::min(xRowDists.begin(), xRowDists.end()); - - Log(Log::DEBUG)<<__func__<<": xMinDist "< fitEllipses(std::vector< std::vector >& rows, bool remove = true) -{ - if(rows.empty()) return std::vector(); - - std::vector ellipses; - ellipses.reserve(rows.size()); - for(size_t i = 0; i < rows.size(); ++i) - { - if(rows[i].size() > 4) ellipses.push_back(cv::fitEllipse(rows[i])); - else - { - rows.erase(rows.begin()+i); - i--; - } - } - return ellipses; -} - -static void thompsonTauTest(const std::vector& in, std::vector& outliers, float criticalValue) -{ - float mean = 0; - size_t n = 0; - for(size_t i = 0; i < in.size(); ++i) - { - bool found = false; - for(size_t j = 0; j < outliers.size() && !found; ++j) - if(outliers[j]==i) found = true; - if(!found) - { - mean+=in[i]; - ++n; - } - } - mean/=n; - - float sd = 0; - for(size_t i = 0; i < in.size(); ++i) - { - bool found = false; - for(size_t j = 0; j < outliers.size() && !found; ++j) - if(outliers[j]==i) found = true; - if(!found) sd+=pow(in[i]-mean,2); - } - sd = sqrt(sd/(n-1)); - float rej = (criticalValue*(n-1))/(sqrt(n)*sqrt(n-2+criticalValue)); - bool removed = false; - for(size_t i = 0; i < in.size() && !removed; ++i) - { - bool found = false; - for(size_t j = 0; j < outliers.size() && !found; ++j) - if(outliers[j]==i) found = true; - if(!found) - { - if(abs((in[i]-mean)/sd) > rej) - { - Log(Log::DEBUG)<<__func__<<": "< >& rows, std::vector& elipses) -{ - if(rows.size() != elipses.size() && elipses.size() > 1) return false; - - for(size_t i = 0; i < elipses.size(); ++i) - { - float angDiff = fmod(elipses[i].angle,90); - if(angDiff < 90-5 && angDiff > 5) - { - elipses.erase(elipses.begin()+i); - rows.erase(rows.begin()+i); - --i; - } - } - - std::vector widths; - std::vector heights; - - for(auto& elipse : elipses) - { - widths.push_back(elipse.size.width); - heights.push_back(elipse.size.height); - } - - std::vector outliersW; - std::vector outliersH; - thompsonTauTest(widths, outliersW, 2); - thompsonTauTest(heights, outliersH, 2); - - std::vector< std::vector > rowsReduced; - std::vector elipsesReduced; - - for(size_t i = 0; i < elipses.size(); ++i) - { - bool found = false; - for(size_t j = 0; j < outliersW.size() && !found; ++j) - if(outliersW[j]==i) found = true; - for(size_t j = 0; j < outliersH.size() && !found; ++j) - if(outliersH[j]==i) found = true; - if(!found) - { - rowsReduced.push_back(rows[i]); - elipsesReduced.push_back(elipses[i]); - } - } - elipses = elipsesReduced; - rows = rowsReduced; - return true; -} - -static DisplacmentMap calcDisplacementMap(const std::vector< std::vector >& rows, - const std::vector& elipses) -{ - if(rows.size() < 2 || rows.size() != elipses.size()) { - Log(Log::ERROR)<<__func__<<": rows < 2 or rows != elipses"; - return DisplacmentMap(); - } - - DisplacmentMap displacmentmap; - - displacmentmap.destination.assign(rows.size(), std::vector()); - displacmentmap.source = rows; - - for(size_t i = 0; i < displacmentmap.destination.size(); ++i) - { - displacmentmap.destination[i].reserve(rows[i].size()); - } - - float meanYdist = 0; - size_t j = 0; - while(j < elipses.size()-1) - { - meanYdist += elipses[j+1].center.y-elipses[j].center.y; - ++j; - } - meanYdist = meanYdist/elipses.size(); - - Log(Log::DEBUG)<<__func__<<": meanYdist "<& row = rows[rowCnt]; - const cv::RotatedRect& elipse = elipses[rowCnt]; - - cv::Rect_ boundingRect = elipse.boundingRect2f(); - - Log(Log::DEBUG)<<__func__<<": Proc row "<(y,0) >= 0) ++front; - if(mat.at(y,mat.cols-1) >= 0) ++back; - } - Log(Log::DEBUG)<<__func__<<" front: "<::max(); - float xMeanMin = 0; - float xMax = std::numeric_limits::min(); - - for(auto& row: map.destination ) - { - xMeanMin+=row.front().x; - if(xMin > row.front().x ) - xMin = row.front().x; - if(xMax < row.back().x) - xMax = row.back().x; - } - xMeanMin = xMeanMin / map.destination.size(); - - Log(Log::DEBUG)<<__func__<<": Grid: xMin "<(std::lround(abs((xMax-xMin)/xMeanDist))+1); - xMat = cv::Mat::zeros(cv::Size(xGridSize, map.destination.size()), CV_32FC1); - yMat = cv::Mat::zeros(cv::Size(xGridSize, map.destination.size()), CV_32FC1); - - Log(Log::DEBUG)<<"Grid: "<(y); - float* coly = yMat.ptr(y); - for(int x = 0; x < xMat.cols; x++) - { - size_t xIndex = 0; - size_t yIndex = 0; - bool found = findClosest(xIndex, yIndex, - cv::Point2f((x)*xMeanDist+xMin, (y)*yMeanDist), - map.destination, (2*xMeanDist)/5, (2*yMeanDist)/5); - Log(Log::DEBUG)<<__func__<<": found: "<& points, const std::vector& coordinates, cv::Mat& xMat, cv::Mat& yMat) -{ - if(points.size() < 6 || coordinates.size() != points.size()) - { - Log(Log::ERROR)<<__func__<<": at least 6 points are needed"; - return; - } - - int xMin = std::numeric_limits::max(); - int xMax = std::numeric_limits::min(); - int yMin = std::numeric_limits::max(); - int yMax = std::numeric_limits::min(); - - for(auto& point : coordinates) - { - Log(Log::DEBUG)<<"point: "< xMax) xMax = point.x; - else if(point.x < xMin) xMin = point.x; - if(point.y > yMax) yMax = point.y; - else if(point.y < yMin) yMin = point.y; - } - - size_t xGridSize = xMax-xMin+1; - size_t yGridSize = yMax-yMin+1; - xMat = cv::Mat::zeros(cv::Size(xGridSize, yGridSize), CV_32FC1); - yMat = cv::Mat::zeros(cv::Size(xGridSize, yGridSize), CV_32FC1); - - Log(Log::DEBUG)<<"Grid: "<(y); - float* coly = yMat.ptr(y); - for(int x = 0; x < xMat.cols; x++) - { - colx[x] = -1; - coly[x] = -1; - for(size_t i = 0; i < coordinates.size(); ++i) - { - if(coordinates[i] == cv::Point2i(x+xMin,y+yMin)) - { - colx[x] = points[i].x; - coly[x] = points[i].y; - break; - } - } - - - } - } - Log(Log::DEBUG)<<__func__<<": xMat raw\n"< points, const std::vector& coordinates, - const std::string& fileName, bool verbose) -{ - - cv::Mat xMat; - cv::Mat yMat; - - if(coordinates.empty()) - { - std::vector< std::vector > rows = sortPointsIntoRows(points); - Log(Log::INFO)<<"Found "< ellipses = fitEllipses(rows); - Log(Log::INFO)<<"Found "<(0,mat.cols/2); - float centerLeft = mat.at(mat.rows/2,0); - float centerBottom = mat.at(mat.rows-1,mat.cols/2); - float centerRight = mat.at(mat.rows/2,mat.cols-1); - - int left = 0; - int right = mat.cols-1; - int top = 0; - int bottom = mat.rows-1; - for(; left < mat.cols && mat.at(mat.rows/2,left) == centerLeft; ++left); - for(; top < mat.rows && mat.at(top,mat.cols/2) == centerTop; ++top); - for(; right > left && mat.at(mat.rows/2,right) == centerRight; --right); - for(; bottom > top && mat.at(bottom,mat.cols/2) == centerBottom; --bottom); - - roi.x=left; - roi.y=top; - roi.width = right-left; - roi.height = bottom-top; - return true; -} - -void applyRemap(cv::Mat& image, cv::Mat& out, const cv::Mat& xmap, const cv::Mat& ymap, unsigned int outputSize) -{ - cv::Mat xMapResized; - cv::Mat yMapResized; - cv::resize(xmap, xMapResized, cv::Size(outputSize,outputSize*(xmap.rows/(double)xmap.cols)), cv::INTER_CUBIC); - cv::resize(ymap, yMapResized, cv::Size(outputSize,outputSize*(xmap.rows/(double)xmap.cols)), cv::INTER_CUBIC); - cv::Rect roi; - cv::Mat xMapRed; - cv::Mat yMapRed; - if(!findDeadSpace(xMapResized, roi)) - { - xMapRed = xMapResized(roi); - yMapRed = yMapResized(roi); - } - else - { - xMapRed = xMapResized; - yMapRed = yMapResized; - } - cv::remap(image, out, xMapRed, yMapRed, cv::INTER_LINEAR); -} diff --git a/unwrap.h b/unwrap.h deleted file mode 100644 index 179ac90..0000000 --- a/unwrap.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once -#include -#include - -bool createRemapMap(cv::Mat& image, std::vector points, const std::vector& coordinates, const std::string& fileName, bool verbose = false); - -void applyRemap(cv::Mat& image, cv::Mat& out, const cv::Mat& xmap, const cv::Mat& ymap, unsigned int size);