diff --git a/CMakeLists.txt b/CMakeLists.txt index a43c95e..dba063b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,14 +2,14 @@ cmake_minimum_required(VERSION 2.4) project(unwap) -set(SRC_FILES main.cpp unwrap.cpp drawing.cpp matutils.cpp bgremoval.cpp) +set(SRC_FILES main.cpp unwrap.cpp drawing.cpp matutils.cpp bgremoval.cpp charuco.cpp) set(LIBS -lX11 -lrt) find_package( OpenCV REQUIRED ) add_executable(${PROJECT_NAME} ${SRC_FILES}) -target_link_libraries( ${PROJECT_NAME} ${LIBS} -lopencv_core -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) target_include_directories(${PROJECT_NAME} PRIVATE "/usr/include/opencv4") add_definitions(" -std=c++17 -Wall -O2 -flto -fno-strict-aliasing") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -s") diff --git a/argpopt.h b/argpopt.h index a11ad1d..f379d8c 100644 --- a/argpopt.h +++ b/argpopt.h @@ -27,10 +27,10 @@ struct Config std::string norm = ""; std::string maps = ""; std::string bg = ""; - int blockSize = 5; - int apature = 5; - float detectorParameter = 0.01; - float minSize = 7; + std::string charuco = ""; + bool harris = false; + float minSize = 7; + unsigned int size = 600; bool verbose = false; bool quiet = false; }; @@ -47,10 +47,10 @@ static struct argp_option options[] = {"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" }, -{"apature", 'a', "Value", 0, "Sobel size" }, -{"block-size", 't', "Value", 0, "Harris neighborhood size " }, -{"detector-arameter", 'd', "Value", 0, "Harris detector parameter" }, {"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" }, +{"harris", 'r', 0, 0, "Use harris to detect points" }, { 0 } }; @@ -75,22 +75,18 @@ error_t parse_opt (int key, char *arg, struct argp_state *state) case 'n': config->norm.assign(arg); break; - case 'a': - config->apature=atol(arg); - if(config->apature % 2 == 0) ++config->apature; - break; - case 't': - config->blockSize=atol(arg); - if(config->blockSize % 2 == 0) ++config->blockSize; - break; - case 'd': - config->detectorParameter=atol(arg); - break; case 's': config->minSize=atol(arg); break; - case ARGP_KEY_NO_ARGS: - argp_usage(state); + case 'r': + config->harris = true; + break; + case 'c': + config->charuco.assign(arg); + break; + case 'x': + config->size=atol(arg); + break; case ARGP_KEY_ARG: config->inFileNames = &state->argv[state->next-1]; state->next = state->argc; diff --git a/charuco.cpp b/charuco.cpp new file mode 100644 index 0000000..c5d50af --- /dev/null +++ b/charuco.cpp @@ -0,0 +1,67 @@ +#include "charuco.h" +#include +#include + +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 = + cv::aruco::CharucoBoard::create(X_BOARD_SIZE, Y_BOARD_SIZE, 0.03f, 0.02f, + cv::aruco::getPredefinedDictionary(cv::aruco::DICT_4X4_250 )); + cv::Mat charucoImage; + board->draw(cv::Size((size*18)/10, size), charucoImage, 0, 1); + cv::imwrite(fileName, charucoImage); +} + +std::vector detectCharucoPoints(cv::Mat image, std::vector* coordiantes, 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 )); + cv::Ptr params = cv::aruco::DetectorParameters::create(); + params->adaptiveThreshWinSizeMin = 3; + params->adaptiveThreshWinSizeMax = 41; + params->adaptiveThreshWinSizeStep = 4; + params->perspectiveRemovePixelPerCell = 16; + params->perspectiveRemoveIgnoredMarginPerCell = 0.1; + params->polygonalApproxAccuracyRate = 0.05; + params->perspectiveRemoveIgnoredMarginPerCell = 0.1; + params->aprilTagMinClusterPixels = 3; + + std::vector arucoIds; + std::vector > arucoCorners; + cv::aruco::detectMarkers(image, board->dictionary, arucoCorners, arucoIds, params); + + + if(verbose) + { + cv::Mat debugImage; + image.copyTo(debugImage); + cv::aruco::drawDetectedMarkers(debugImage, arucoCorners, arucoIds); + cv::imshow( "Viewer", debugImage ); + cv::waitKey(0); + } + + if (arucoIds.size() > 0) + { + std::vector charucoCorners; + std::vector charucoIds; + cv::aruco::interpolateCornersCharuco(arucoCorners, arucoIds, image, board, charucoCorners, + charucoIds, cv::noArray(), cv::noArray(), 0); + if(verbose) + { + cv::Mat debugImage; + image.copyTo(debugImage); + cv::aruco::drawDetectedCornersCharuco(debugImage, charucoCorners, charucoIds, cv::Scalar(0, 255, 0)); + cv::imshow("Viewer", debugImage); + cv::waitKey(0); + } + + if(coordiantes) for( auto id : charucoIds) + coordiantes->push_back(cv::Point2i(id % (X_BOARD_SIZE-1), (Y_BOARD_SIZE-2) - id/(X_BOARD_SIZE-1))); + + return charucoCorners; + } + return std::vector(); +} diff --git a/charuco.h b/charuco.h new file mode 100644 index 0000000..a7017ab --- /dev/null +++ b/charuco.h @@ -0,0 +1,8 @@ +#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/harris.h b/harris.h new file mode 100644 index 0000000..fb91960 --- /dev/null +++ b/harris.h @@ -0,0 +1,44 @@ +#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 index 0e90469..0d6423b 100644 --- a/main.cpp +++ b/main.cpp @@ -9,6 +9,8 @@ #include "bgremoval.h" #include "normalize.h" #include "log.h" +#include "charuco.h" +#include "harris.h" void cd_to_exe_dir( char *argv[] ) { @@ -37,7 +39,7 @@ std::vector loadImages(char** fileNames) int main(int argc, char* argv[]) { cv::ocl::setUseOpenCL(false); - std::cout<<"UVOS Optical lubricant thikness mapper "< inImages = loadImages(config.inFileNames); if(inImages.empty()) @@ -59,7 +70,6 @@ int main(int argc, char* argv[]) cv::resizeWindow("Viewer", 960, 500); } - if(config.maps.empty()) { cv::Mat mask; @@ -85,9 +95,26 @@ int main(int argc, char* argv[]) } 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 "< #include #include -#include #include #include "matutils.h" @@ -29,7 +28,7 @@ static std::vector< std::vector > sortPointsIntoRows(std::vector detectPoints(cv::Mat& image, const cv::Mat& mask, - int blockSize = 5, int apature = 5, float detectorParameter = 0.01, - float minSize = 7, bool verbose = false) +static void generateRemapMaps(const std::vector& points, const std::vector& coordinates, cv::Mat& xMat, cv::Mat& yMat) { - 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.size == corners.size) corners.copyTo(cornersMasked, mask); - else corners.copyTo(cornersMasked); - - if(verbose) + if(points.size() < 6 || coordinates.size() != points.size()) { - cv::imshow( "Viewer", cornersMasked ); - cv::waitKey(0); + Log(Log::ERROR)<<__func__<<": at least 6 points are needed"; + return; } - //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); + int xMin = std::numeric_limits::max(); + int xMax = std::numeric_limits::min(); + int yMin = std::numeric_limits::max(); + int yMax = std::numeric_limits::min(); - std::vector points; - cv::KeyPoint::convert(keypoints, points); + 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; + } - return points; + 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) { - std::vector points = detectPoints(image, mask, blockSize, apature, detectorParameter, minSize, verbose); - if(verbose) std::cout<<"Found "< > rows = sortPointsIntoRows(points); - if(verbose) std::cout<<"Found "< ellipses = fitEllipses(rows); - if(verbose) Log(Log::INFO)<<"Found "< > rows = sortPointsIntoRows(points); + Log(Log::INFO)<<"Found "< ellipses = fitEllipses(rows); + Log(Log::INFO)<<"Found "< #include -bool createRemapMap(cv::Mat& image, const std::string& fileName, const cv::Mat& mask, - int blockSize = 5, int apature = 5, float detectorParameter = 0.01, float minSize = 7, - bool verbose = false); +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);