From 5e3c26e763da9d8da2638429a022fb6d97ded5a7 Mon Sep 17 00:00:00 2001 From: uvos Date: Tue, 8 Jun 2021 14:59:23 +0200 Subject: [PATCH] Recover git state after .git was lost --- CMakeLists.txt | 11 +- bgremoval.cpp | 22 -- bgremoval.h | 5 - charuco.h | 8 - drawing.cpp | 19 -- drawing.h | 6 - harris.h | 44 --- main.cpp | 171 ---------- matutils.cpp | 166 ---------- matutils.h | 22 -- argpopt.h => src/argpopt.h | 37 ++- src/bgremoval.cpp | 41 +++ src/bgremoval.h | 24 ++ charuco.cpp => src/charuco.cpp | 35 +- src/charuco.h | 28 ++ src/detectedpoint.h | 31 ++ src/drawing.cpp | 38 +++ src/drawing.h | 25 ++ src/harris.cpp | 294 +++++++++++++++++ src/harris.h | 25 ++ log.h => src/log.h | 19 ++ src/main.cpp | 296 +++++++++++++++++ src/matutils.cpp | 353 ++++++++++++++++++++ src/matutils.h | 50 +++ normalize.h => src/normalize.h | 19 ++ src/unwrap.cpp | 263 +++++++++++++++ src/unwrap.h | 48 +++ unwrap.cpp | 568 --------------------------------- unwrap.h | 7 - 29 files changed, 1610 insertions(+), 1065 deletions(-) delete mode 100644 bgremoval.cpp delete mode 100644 bgremoval.h delete mode 100644 charuco.h delete mode 100644 drawing.cpp delete mode 100644 drawing.h delete mode 100644 harris.h delete mode 100644 main.cpp delete mode 100644 matutils.cpp delete mode 100644 matutils.h rename argpopt.h => src/argpopt.h (77%) create mode 100644 src/bgremoval.cpp create mode 100644 src/bgremoval.h rename charuco.cpp => src/charuco.cpp (65%) create mode 100644 src/charuco.h create mode 100644 src/detectedpoint.h create mode 100644 src/drawing.cpp create mode 100644 src/drawing.h create mode 100644 src/harris.cpp create mode 100644 src/harris.h rename log.h => src/log.h (56%) create mode 100644 src/main.cpp create mode 100644 src/matutils.cpp create mode 100644 src/matutils.h rename normalize.h => src/normalize.h (53%) create mode 100644 src/unwrap.cpp create mode 100644 src/unwrap.h delete mode 100644 unwrap.cpp delete mode 100644 unwrap.h 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);