Recover git state after .git was lost
This commit is contained in:
@ -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(SRC_FILES src/main.cpp src/unwrap.cpp src/drawing.cpp src/matutils.cpp src/bgremoval.cpp src/charuco.cpp src/harris.cpp)
|
||||||
set(LIBS -lX11 -lrt)
|
|
||||||
|
|
||||||
find_package( OpenCV REQUIRED )
|
find_package( OpenCV REQUIRED )
|
||||||
|
|
||||||
add_executable(${PROJECT_NAME} ${SRC_FILES})
|
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")
|
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")
|
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -s")
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
#include "bgremoval.h"
|
|
||||||
#include <iostream>
|
|
||||||
#include <opencv2/highgui.hpp>
|
|
||||||
#include <opencv2/imgproc.hpp>
|
|
||||||
#include <opencv2/bgsegm.hpp>
|
|
||||||
#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<cv::BackgroundSubtractorMOG2> 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;
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <opencv2/core/ocl.hpp>
|
|
||||||
|
|
||||||
bool createMask(const cv::Mat& in, cv::Mat& mask, const cv::Mat& bg);
|
|
@ -1,8 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <opencv2/core/ocl.hpp>
|
|
||||||
|
|
||||||
void createCharucoBoard(unsigned int size, const std::string& fileName);
|
|
||||||
|
|
||||||
std::vector<cv::Point2f> detectCharucoPoints(cv::Mat image, std::vector<cv::Point2i>* coordiantes = nullptr, bool verbose = true);
|
|
19
drawing.cpp
19
drawing.cpp
@ -1,19 +0,0 @@
|
|||||||
#include "drawing.h"
|
|
||||||
#include <opencv2/viz/types.hpp>
|
|
||||||
#include <opencv2/imgproc.hpp>
|
|
||||||
|
|
||||||
void drawRows(cv::Mat& image, const std::vector< std::vector<cv::Point2f > >& 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<cv::RotatedRect>& ellipses )
|
|
||||||
{
|
|
||||||
for(const auto& ellipse : ellipses)cv::ellipse(image, ellipse, cv::viz::Color(128,128,128));
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include <opencv2/core/ocl.hpp>
|
|
||||||
|
|
||||||
void drawRows(cv::Mat& image, const std::vector< std::vector<cv::Point2f > >& rows);
|
|
||||||
|
|
||||||
void drawEllipses(cv::Mat& image, const std::vector<cv::RotatedRect>& ellipses );
|
|
44
harris.h
44
harris.h
@ -1,44 +0,0 @@
|
|||||||
#include <opencv2/imgproc.hpp>
|
|
||||||
#include <opencv2/features2d.hpp>
|
|
||||||
|
|
||||||
static std::vector<cv::Point2f> 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<cv::SimpleBlobDetector> blobDetector = cv::SimpleBlobDetector::create(blobParams);
|
|
||||||
std::vector<cv::KeyPoint> keypoints;
|
|
||||||
blobDetector->detect(cornersMasked, keypoints);
|
|
||||||
|
|
||||||
std::vector<cv::Point2f> points;
|
|
||||||
cv::KeyPoint::convert(keypoints, points);
|
|
||||||
|
|
||||||
return points;
|
|
||||||
}
|
|
171
main.cpp
171
main.cpp
@ -1,171 +0,0 @@
|
|||||||
#include <iostream>
|
|
||||||
#include <opencv2/highgui.hpp>
|
|
||||||
#include <opencv2/core/ocl.hpp>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <vector>
|
|
||||||
#include <string>
|
|
||||||
#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<cv::Mat> loadImages(char** fileNames)
|
|
||||||
{
|
|
||||||
std::vector<cv::Mat> 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 "<<i<<" from "<<fileNames[i]<<'\n';
|
|
||||||
}
|
|
||||||
return images;
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char* argv[])
|
|
||||||
{
|
|
||||||
cv::ocl::setUseOpenCL(false);
|
|
||||||
|
|
||||||
cd_to_exe_dir(argv);
|
|
||||||
|
|
||||||
Config config;
|
|
||||||
argp_parse(&argp, argc, argv, 0, 0, &config);
|
|
||||||
|
|
||||||
Log::level = config.quiet ? Log::WARN : config.verbose ? Log::DEBUG : Log::INFO;
|
|
||||||
|
|
||||||
Log(Log::INFO)<<"UVOS Optical lubricant thikness mapper "<<argp_program_version;
|
|
||||||
|
|
||||||
if(!config.charuco.empty())
|
|
||||||
{
|
|
||||||
createCharucoBoard(config.size, config.charuco);
|
|
||||||
Log(Log::INFO)<<"exporting charuco map";
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<cv::Mat> 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 "<<config.bg;
|
|
||||||
}
|
|
||||||
std::vector<cv::Point2f > points;
|
|
||||||
std::vector<cv::Point2i> coordiantes;
|
|
||||||
|
|
||||||
if(config.harris)
|
|
||||||
{
|
|
||||||
points = harrisDetectPoints(inImages[0], mask);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
points = detectCharucoPoints(inImages[0], &coordiantes);
|
|
||||||
}
|
|
||||||
|
|
||||||
Log(Log::INFO)<<"Found "<<points.size()<<" points";
|
|
||||||
if(points.size() < 8)
|
|
||||||
{
|
|
||||||
Log(Log::ERROR)<<"Error creating map, insufficant points detected";
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
createRemapMap(inImages[0], points, coordiantes, config.inFileNames[0], config.verbose);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
cv::FileStorage fs(config.maps, cv::FileStorage::READ);
|
|
||||||
if (!fs.isOpened())
|
|
||||||
{
|
|
||||||
Log(Log::ERROR)<<"could not open maps file " <<config.maps;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
cv::Mat xMat;
|
|
||||||
cv::Mat yMat;
|
|
||||||
fs["xmat"]>>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 " <<config.norm;
|
|
||||||
}
|
|
||||||
applyRemap(tmp, norm, xMat, yMat, config.size);
|
|
||||||
if(config.verbose)
|
|
||||||
{
|
|
||||||
cv::imshow("Viewer", norm );
|
|
||||||
cv::waitKey(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for(auto& image : inImages)
|
|
||||||
{
|
|
||||||
if(config.verbose)
|
|
||||||
{
|
|
||||||
cv::imshow( "Viewer", image );
|
|
||||||
cv::waitKey(0);
|
|
||||||
}
|
|
||||||
cv::Mat remaped;
|
|
||||||
applyRemap(image, remaped, xMat, yMat, config.size);
|
|
||||||
if(norm.data) normalize(remaped, norm);
|
|
||||||
cv::imshow( "Viewer", remaped );
|
|
||||||
cv::waitKey(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(config.verbose)
|
|
||||||
{
|
|
||||||
cv::destroyWindow("Viewer");
|
|
||||||
cv::waitKey(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
166
matutils.cpp
166
matutils.cpp
@ -1,166 +0,0 @@
|
|||||||
#include "matutils.h"
|
|
||||||
#include <opencv2/core/ocl.hpp>
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
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<float>(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<cv::Point2f>::iterator getTopLeft(std::vector<cv::Point2f>& 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<cv::Point2f>::iterator getBottomRight(std::vector<cv::Point2f>& 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<cv::Point2f> >& 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<float>::max();
|
|
||||||
float distRA = std::numeric_limits<float>::max();
|
|
||||||
float distLA = std::numeric_limits<float>::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<float>(y);
|
|
||||||
for(int x = 0; x < mat.cols; ++x)
|
|
||||||
{
|
|
||||||
if(col[x] < 0)
|
|
||||||
{
|
|
||||||
int closestA = -1;
|
|
||||||
int closestB = -1;
|
|
||||||
int dist = std::numeric_limits<int>::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<int>::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<float>(y);
|
|
||||||
for(int x = 0; x < mat.cols; ++x)
|
|
||||||
{
|
|
||||||
if(col[x] < 0 && col[x] > -2)
|
|
||||||
{
|
|
||||||
if(y > 0 && mat.at<float>(y-1,x) >= 0)
|
|
||||||
{
|
|
||||||
col[x] = mat.at<float>(y-1,x);
|
|
||||||
finished = false;
|
|
||||||
}
|
|
||||||
else if(y < mat.rows-1 && mat.at<float>(y+1,x) >= 0)
|
|
||||||
{
|
|
||||||
col[x] = mat.at<float>(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);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
22
matutils.h
22
matutils.h
@ -1,22 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include <opencv2/core/ocl.hpp>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
void sanityCheckMap(cv::Mat& mat, const float min, const float max, const float minValue, const float maxValue);
|
|
||||||
|
|
||||||
std::vector<cv::Point2f>::iterator getTopLeft(std::vector<cv::Point2f>& points);
|
|
||||||
|
|
||||||
std::vector<cv::Point2f>::iterator getBottomRight(std::vector<cv::Point2f>& 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<cv::Point2f> >& array,
|
|
||||||
float xTolerance, float yTolerance);
|
|
||||||
|
|
||||||
|
|
||||||
void interpolateMissing(cv::Mat& mat);
|
|
||||||
|
|
||||||
void fillMissing(cv::Mat& mat);
|
|
||||||
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* Sigstoped
|
* Lubricant Detecter
|
||||||
* Copyright (C) 2020 Carl Klemm
|
* Copyright (C) 2021 Carl Klemm
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
* modify it under the terms of the GNU General Public License
|
* modify it under the terms of the GNU General Public License
|
||||||
@ -23,16 +23,17 @@
|
|||||||
|
|
||||||
struct Config
|
struct Config
|
||||||
{
|
{
|
||||||
char** inFileNames = NULL;
|
char** commandsFiles = NULL;
|
||||||
|
std::string output = "";
|
||||||
std::string norm = "";
|
std::string norm = "";
|
||||||
std::string maps = "";
|
|
||||||
std::string bg = "";
|
std::string bg = "";
|
||||||
std::string charuco = "";
|
|
||||||
bool harris = false;
|
bool harris = false;
|
||||||
float minSize = 7;
|
float minSize = 7;
|
||||||
unsigned int size = 600;
|
unsigned int size = 50;
|
||||||
bool verbose = false;
|
bool verbose = false;
|
||||||
bool quiet = false;
|
bool quiet = false;
|
||||||
|
bool interactive = false;
|
||||||
|
bool simpleStich = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const char *argp_program_version = "0.2";
|
const char *argp_program_version = "0.2";
|
||||||
@ -44,13 +45,14 @@ static struct argp_option options[] =
|
|||||||
{
|
{
|
||||||
{"verbose", 'v', 0, 0, "Enable verbose logging" },
|
{"verbose", 'v', 0, 0, "Enable verbose logging" },
|
||||||
{"quiet", 'q', 0, 0, "Disable info messages" },
|
{"quiet", 'q', 0, 0, "Disable info messages" },
|
||||||
{"map", 'm', "File Name", 0, "remap maps file" },
|
|
||||||
{"bg", 'b', "File Name", 0, "background image file" },
|
{"bg", 'b', "File Name", 0, "background image file" },
|
||||||
{"normalize", 'n', "File Name", 0, "image to use as a normalization source" },
|
{"normalize", 'n', "File Name", 0, "image to use as a normalization source" },
|
||||||
{"min-size", 's', "Value", 0, "Smallest feature accepted as a corner" },
|
{"min-size", 's', "Value", 0, "Smallest feature accepted as a corner" },
|
||||||
{"create", 'c', "File Name", 0, "Create charuco board" },
|
{"size", 'x', "Value", 0, "Output cell size" },
|
||||||
{"x-size", 'x', "Value", 0, "Output image width" },
|
|
||||||
{"harris", 'r', 0, 0, "Use harris to detect points" },
|
{"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 }
|
{ 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -66,9 +68,6 @@ error_t parse_opt (int key, char *arg, struct argp_state *state)
|
|||||||
case 'q':
|
case 'q':
|
||||||
config->quiet = true;
|
config->quiet = true;
|
||||||
break;
|
break;
|
||||||
case 'm':
|
|
||||||
config->maps.assign(arg);
|
|
||||||
break;
|
|
||||||
case 'b':
|
case 'b':
|
||||||
config->bg.assign(arg);
|
config->bg.assign(arg);
|
||||||
break;
|
break;
|
||||||
@ -81,14 +80,20 @@ error_t parse_opt (int key, char *arg, struct argp_state *state)
|
|||||||
case 'r':
|
case 'r':
|
||||||
config->harris = true;
|
config->harris = true;
|
||||||
break;
|
break;
|
||||||
case 'c':
|
|
||||||
config->charuco.assign(arg);
|
|
||||||
break;
|
|
||||||
case 'x':
|
case 'x':
|
||||||
config->size=atol(arg);
|
config->size=atol(arg);
|
||||||
break;
|
break;
|
||||||
|
case 'a':
|
||||||
|
config->simpleStich = true;
|
||||||
|
break;
|
||||||
|
case 'i':
|
||||||
|
config->interactive = true;
|
||||||
|
break;
|
||||||
|
case 'o':
|
||||||
|
config->output.assign(arg);
|
||||||
|
break;
|
||||||
case ARGP_KEY_ARG:
|
case ARGP_KEY_ARG:
|
||||||
config->inFileNames = &state->argv[state->next-1];
|
config->commandsFiles = &state->argv[state->next-1];
|
||||||
state->next = state->argc;
|
state->next = state->argc;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
41
src/bgremoval.cpp
Normal file
41
src/bgremoval.cpp
Normal file
@ -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 <iostream>
|
||||||
|
#include <opencv2/highgui.hpp>
|
||||||
|
#include <opencv2/imgproc.hpp>
|
||||||
|
#include <opencv2/bgsegm.hpp>
|
||||||
|
#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<cv::BackgroundSubtractorMOG2> 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;
|
||||||
|
}
|
24
src/bgremoval.h
Normal file
24
src/bgremoval.h
Normal file
@ -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 <opencv2/core/ocl.hpp>
|
||||||
|
|
||||||
|
bool createMask(const cv::Mat& in, cv::Mat& mask, const cv::Mat& bg);
|
@ -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 "charuco.h"
|
||||||
#include <opencv2/aruco/charuco.hpp>
|
#include <opencv2/aruco/charuco.hpp>
|
||||||
#include <opencv2/highgui.hpp>
|
#include <opencv2/highgui.hpp>
|
||||||
@ -5,6 +24,7 @@
|
|||||||
static constexpr unsigned int X_BOARD_SIZE = 18;
|
static constexpr unsigned int X_BOARD_SIZE = 18;
|
||||||
static constexpr unsigned int Y_BOARD_SIZE = 10;
|
static constexpr unsigned int Y_BOARD_SIZE = 10;
|
||||||
|
|
||||||
|
|
||||||
void createCharucoBoard(unsigned int size, const std::string& fileName)
|
void createCharucoBoard(unsigned int size, const std::string& fileName)
|
||||||
{
|
{
|
||||||
cv::Ptr<cv::aruco::CharucoBoard> board =
|
cv::Ptr<cv::aruco::CharucoBoard> board =
|
||||||
@ -15,7 +35,7 @@ void createCharucoBoard(unsigned int size, const std::string& fileName)
|
|||||||
cv::imwrite(fileName, charucoImage);
|
cv::imwrite(fileName, charucoImage);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<cv::Point2f> detectCharucoPoints(cv::Mat image, std::vector<cv::Point2i>* coordiantes, bool verbose)
|
std::vector<DetectedPoint> detectCharucoPoints(cv::Mat image, bool verbose)
|
||||||
{
|
{
|
||||||
cv::Ptr<cv::aruco::CharucoBoard> board = cv::aruco::CharucoBoard::create(X_BOARD_SIZE, Y_BOARD_SIZE, 0.03f, 0.02f,
|
cv::Ptr<cv::aruco::CharucoBoard> board = cv::aruco::CharucoBoard::create(X_BOARD_SIZE, Y_BOARD_SIZE, 0.03f, 0.02f,
|
||||||
cv::aruco::getPredefinedDictionary(cv::aruco::DICT_4X4_250 ));
|
cv::aruco::getPredefinedDictionary(cv::aruco::DICT_4X4_250 ));
|
||||||
@ -58,10 +78,15 @@ std::vector<cv::Point2f> detectCharucoPoints(cv::Mat image, std::vector<cv::Poin
|
|||||||
cv::waitKey(0);
|
cv::waitKey(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(coordiantes) for( auto id : charucoIds)
|
std::vector<DetectedPoint> detections;
|
||||||
coordiantes->push_back(cv::Point2i(id % (X_BOARD_SIZE-1), (Y_BOARD_SIZE-2) - id/(X_BOARD_SIZE-1)));
|
|
||||||
|
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<cv::Point2f>();
|
return std::vector<DetectedPoint>();
|
||||||
}
|
}
|
28
src/charuco.h
Normal file
28
src/charuco.h
Normal file
@ -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 <string>
|
||||||
|
#include <opencv2/core/ocl.hpp>
|
||||||
|
#include "detectedpoint.h"
|
||||||
|
|
||||||
|
void createCharucoBoard(unsigned int size, const std::string& fileName);
|
||||||
|
|
||||||
|
std::vector<DetectedPoint> detectCharucoPoints(cv::Mat image, bool verbose = true);
|
31
src/detectedpoint.h
Normal file
31
src/detectedpoint.h
Normal file
@ -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)
|
||||||
|
{}
|
||||||
|
};
|
38
src/drawing.cpp
Normal file
38
src/drawing.cpp
Normal file
@ -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 <opencv2/viz/types.hpp>
|
||||||
|
#include <opencv2/imgproc.hpp>
|
||||||
|
|
||||||
|
void drawRows(cv::Mat& image, const std::vector< std::vector<cv::Point2f > >& 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<cv::RotatedRect>& ellipses )
|
||||||
|
{
|
||||||
|
for(const auto& ellipse : ellipses)cv::ellipse(image, ellipse, cv::viz::Color(128,128,128));
|
||||||
|
}
|
25
src/drawing.h
Normal file
25
src/drawing.h
Normal file
@ -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 <opencv2/core/ocl.hpp>
|
||||||
|
|
||||||
|
void drawRows(cv::Mat& image, const std::vector< std::vector<cv::Point2f > >& rows);
|
||||||
|
|
||||||
|
void drawEllipses(cv::Mat& image, const std::vector<cv::RotatedRect>& ellipses );
|
294
src/harris.cpp
Normal file
294
src/harris.cpp
Normal file
@ -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 <opencv2/imgproc.hpp>
|
||||||
|
#include <opencv2/features2d.hpp>
|
||||||
|
#include <opencv2/highgui.hpp>
|
||||||
|
#include "harris.h"
|
||||||
|
#include "log.h"
|
||||||
|
#include "matutils.h"
|
||||||
|
#include "drawing.h"
|
||||||
|
|
||||||
|
static float detimineXPitch(const std::vector<cv::Point2f>& row, float fuzz = 1.3f)
|
||||||
|
{
|
||||||
|
std::vector<float> 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 "<<xMinDist;
|
||||||
|
|
||||||
|
float meanXDistAccum = 0;
|
||||||
|
size_t validCount = 0;
|
||||||
|
for(size_t i = 0; i < xRowDists.size(); ++i)
|
||||||
|
{
|
||||||
|
if(xRowDists[i] < xMinDist*fuzz)
|
||||||
|
{
|
||||||
|
++validCount;
|
||||||
|
meanXDistAccum+=xRowDists[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return meanXDistAccum/validCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<cv::RotatedRect> fitEllipses(std::vector< std::vector<cv::Point2f > >& rows, bool remove = true)
|
||||||
|
{
|
||||||
|
if(rows.empty()) return std::vector<cv::RotatedRect>();
|
||||||
|
|
||||||
|
std::vector<cv::RotatedRect> 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<cv::Point2f > >& rows, std::vector<cv::RotatedRect>& 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<float> widths;
|
||||||
|
std::vector<float> heights;
|
||||||
|
|
||||||
|
for(auto& elipse : elipses)
|
||||||
|
{
|
||||||
|
widths.push_back(elipse.size.width);
|
||||||
|
heights.push_back(elipse.size.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<size_t> outliersW;
|
||||||
|
std::vector<size_t> outliersH;
|
||||||
|
thompsonTauTest(widths, outliersW, 2);
|
||||||
|
thompsonTauTest(heights, outliersH, 2);
|
||||||
|
|
||||||
|
std::vector< std::vector<cv::Point2f > > rowsReduced;
|
||||||
|
std::vector<cv::RotatedRect> 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<cv::Point2f > > sortPointsIntoRows(std::vector<cv::Point2f>& points)
|
||||||
|
{
|
||||||
|
|
||||||
|
if(points.size() < 6) return std::vector< std::vector<cv::Point2f> >();
|
||||||
|
|
||||||
|
cv::Point2f topLeft(*getTopLeft(points));
|
||||||
|
cv::Point2f bottomRight(*getBottomRight(points));
|
||||||
|
|
||||||
|
Log(Log::DEBUG)<<"topLeft "<<topLeft.x<<' '<<topLeft.y;
|
||||||
|
Log(Log::DEBUG)<<"bottomRight "<<bottomRight.x<<' '<<bottomRight.y;
|
||||||
|
|
||||||
|
float fuzz = (bottomRight.x-topLeft.x)*0.05f;
|
||||||
|
|
||||||
|
for(size_t i = 0; i < points.size(); ++i)
|
||||||
|
{
|
||||||
|
if(points[i].x < topLeft.x-fuzz || points[i].y < topLeft.y-fuzz ||
|
||||||
|
points[i].x > 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<cv::Point2f> > 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<cv::Point2f>());
|
||||||
|
}
|
||||||
|
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<DetectedPoint> harrisGenerateCoordinates(std::vector<cv::Point2f>& points, bool verbose)
|
||||||
|
{
|
||||||
|
std::vector< std::vector<cv::Point2f > > rows = sortPointsIntoRows(points);
|
||||||
|
Log(Log::INFO)<<"Found "<<rows.size()<<" rows";
|
||||||
|
if(rows.size() < 2)
|
||||||
|
{
|
||||||
|
Log(Log::ERROR)<<"Error creating map, insufficant rows detected";
|
||||||
|
return std::vector<DetectedPoint>();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector< cv::RotatedRect > ellipses = fitEllipses(rows);
|
||||||
|
Log(Log::INFO)<<"Found "<<ellipses.size()<<" ellipses. rows reduced to "<<rows.size();
|
||||||
|
if(ellipses.size() < 3)
|
||||||
|
{
|
||||||
|
Log(Log::ERROR)<<"Error creating map, insufficant ellipses detected";
|
||||||
|
return std::vector<DetectedPoint>();
|
||||||
|
}
|
||||||
|
|
||||||
|
sanityCheckElipses(rows, ellipses);
|
||||||
|
|
||||||
|
if(rows.size() < 2 || rows.size() != ellipses.size()) {
|
||||||
|
Log(Log::ERROR)<<__func__<<": rows < 2 or rows != ellipses";
|
||||||
|
return std::vector<DetectedPoint>();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<cv::Point2i> 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<float>::max();
|
||||||
|
float xMeanMin = 0;
|
||||||
|
float xMax = std::numeric_limits<float>::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 "<<meanXdist;
|
||||||
|
Log(Log::DEBUG)<<__func__<<": meanYdist "<<meanYdist;
|
||||||
|
xMeanMin = xMeanMin / rows.size();
|
||||||
|
|
||||||
|
Log(Log::DEBUG)<<__func__<<": Grid: xMin "<<xMin;
|
||||||
|
Log(Log::DEBUG)<<__func__<<": Grid: grid xMax "<<xMax;
|
||||||
|
Log(Log::DEBUG)<<__func__<<": Grid: grid xMeanMin "<<xMeanMin;
|
||||||
|
|
||||||
|
size_t xGridSize = static_cast<size_t>(std::lround(abs((xMax-xMin)/meanXdist))+1);
|
||||||
|
|
||||||
|
std::vector<DetectedPoint> dpoints;
|
||||||
|
|
||||||
|
for(size_t y = 0; y < rows.size(); ++y)
|
||||||
|
{
|
||||||
|
Log(Log::DEBUG)<<__func__<<": Proc row "<<y;
|
||||||
|
|
||||||
|
for(size_t x = 0; x < xGridSize; ++x)
|
||||||
|
{
|
||||||
|
cv::Point2f tmp(x*meanXdist+xMin, y*meanYdist);
|
||||||
|
size_t closest;
|
||||||
|
bool found = findClosest(closest, tmp,
|
||||||
|
points,
|
||||||
|
(2*meanXdist)/5, (2*meanYdist)/5);
|
||||||
|
|
||||||
|
Log(Log::DEBUG)<<__func__<<": found: "<<found<<' '<<x<<'x'<<y;
|
||||||
|
|
||||||
|
if(found)
|
||||||
|
dpoints.push_back(DetectedPoint(points[closest], cv::Point2i(x,y)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dpoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<DetectedPoint> 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<cv::SimpleBlobDetector> blobDetector = cv::SimpleBlobDetector::create(blobParams);
|
||||||
|
std::vector<cv::KeyPoint> keypoints;
|
||||||
|
blobDetector->detect(cornersMasked, keypoints);
|
||||||
|
|
||||||
|
std::vector<cv::Point2f> points;
|
||||||
|
cv::KeyPoint::convert(keypoints, points);
|
||||||
|
|
||||||
|
return harrisGenerateCoordinates(points, verbose);
|
||||||
|
}
|
25
src/harris.h
Normal file
25
src/harris.h
Normal file
@ -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 <opencv2/core/ocl.hpp>
|
||||||
|
#include "detectedpoint.h"
|
||||||
|
|
||||||
|
std::vector<DetectedPoint> harrisDetectPoints(cv::Mat& image, const cv::Mat& mask,
|
||||||
|
int blockSize = 5, int apature = 5, float detectorParameter = 0.01,
|
||||||
|
float minSize = 7, bool verbose = false);
|
@ -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
|
#pragma once
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <string>
|
#include <string>
|
296
src/main.cpp
Normal file
296
src/main.cpp
Normal file
@ -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 <iostream>
|
||||||
|
#include <opencv2/highgui.hpp>
|
||||||
|
#include <opencv2/core/ocl.hpp>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#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<cv::Mat> loadImages(char** fileNames)
|
||||||
|
{
|
||||||
|
std::vector<cv::Mat> 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 "<<fileNames[i]<< "ignoreing";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t pos = 0;
|
||||||
|
std::vector<char> 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)<<pos<<" "<<IMREAD_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
cv::Mat tmpImage = cv::imdecode(buffer, cv::IMREAD_UNCHANGED);
|
||||||
|
if(tmpImage.data)
|
||||||
|
images.push_back(tmpImage);
|
||||||
|
else
|
||||||
|
Log(Log::WARN)<<"can not read image "<<i<<" from "<<fileNames[i]<<'\n';
|
||||||
|
}
|
||||||
|
return images;
|
||||||
|
}
|
||||||
|
|
||||||
|
int perfromOperation(int operation, char** fileNames, const Config& config)
|
||||||
|
{
|
||||||
|
std::vector<cv::Mat> 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 "<<config.size*14<<" to "<<fileName;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else if(operation == CREATE_MAP)
|
||||||
|
{
|
||||||
|
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 "<<config.bg;
|
||||||
|
}
|
||||||
|
std::vector<DetectedPoint> points;
|
||||||
|
|
||||||
|
if(config.harris)
|
||||||
|
points = harrisDetectPoints(inImages[0], mask);
|
||||||
|
else
|
||||||
|
points = detectCharucoPoints(inImages[0], config.verbose);
|
||||||
|
|
||||||
|
Log(Log::INFO)<<"Found "<<points.size()<<" points";
|
||||||
|
if(points.size() < 8)
|
||||||
|
{
|
||||||
|
Log(Log::ERROR)<<"Error creating map, insufficant points detected";
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
RemapMap map;
|
||||||
|
map.outputCellSize = 50;
|
||||||
|
createRemapMap(inImages[0], map, points, config.verbose);
|
||||||
|
saveRemapMap(map, fileNames[0]);
|
||||||
|
}
|
||||||
|
else if(operation == APPLY_MAP)
|
||||||
|
{
|
||||||
|
std::vector<RemapedImage> remapedImages;
|
||||||
|
|
||||||
|
for(size_t i = 0; i<inImages.size(); ++i)
|
||||||
|
{
|
||||||
|
Log(Log::INFO)<<"Processing image "<<i;
|
||||||
|
RemapMap map = loadRemapMap(std::string(fileNames[i])+".mat");
|
||||||
|
map.outputCellSize = config.size;
|
||||||
|
|
||||||
|
if(!map.xMat.data)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
RemapedImage norm;
|
||||||
|
if(!config.norm.empty())
|
||||||
|
{
|
||||||
|
cv::Mat tmp = cv::imread(config.norm);
|
||||||
|
if(!tmp.data)
|
||||||
|
{
|
||||||
|
Log(Log::WARN)<<"could not open normalize file " <<config.norm;
|
||||||
|
}
|
||||||
|
norm = applyRemap(tmp, map);
|
||||||
|
if(config.verbose)
|
||||||
|
{
|
||||||
|
cv::imshow("Viewer", norm.image);
|
||||||
|
cv::waitKey(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Log(Log::INFO)<<"Remaping image";
|
||||||
|
RemapedImage remaped = applyRemap(inImages[i], map);
|
||||||
|
|
||||||
|
Log(Log::INFO)<<"Normalizeing image";
|
||||||
|
if(norm.image.data) normalize(remaped.image, norm.image);
|
||||||
|
|
||||||
|
if(config.verbose)
|
||||||
|
{
|
||||||
|
cv::imshow( "Viewer", remaped.image );
|
||||||
|
cv::waitKey(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
remapedImages.push_back(remaped);
|
||||||
|
}
|
||||||
|
|
||||||
|
cv::Mat out;
|
||||||
|
|
||||||
|
Log(Log::INFO)<<"Stiching images";
|
||||||
|
if(config.simpleStich)
|
||||||
|
out = simpleStich(remapedImages);
|
||||||
|
else
|
||||||
|
out = stich(remapedImages);
|
||||||
|
|
||||||
|
if(config.verbose)
|
||||||
|
{
|
||||||
|
cv::imshow( "Viewer", out );
|
||||||
|
cv::waitKey(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
cv::imwrite(!config.output.empty() ? config.output : "out.png", out);
|
||||||
|
}
|
||||||
|
else if(operation == EXIT)
|
||||||
|
return -1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char* argv[])
|
||||||
|
{
|
||||||
|
cv::ocl::setUseOpenCL(false);
|
||||||
|
|
||||||
|
Config config;
|
||||||
|
argp_parse(&argp, argc, argv, 0, 0, &config);
|
||||||
|
|
||||||
|
Log::level = config.quiet ? Log::WARN : config.verbose ? Log::DEBUG : Log::INFO;
|
||||||
|
|
||||||
|
Log(Log::INFO)<<"UVOS Optical lubricant thikness mapper "<<argp_program_version;
|
||||||
|
|
||||||
|
if(config.verbose)
|
||||||
|
{
|
||||||
|
cv::namedWindow( "Viewer", cv::WINDOW_NORMAL );
|
||||||
|
cv::resizeWindow("Viewer", 960, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
int operation = selectOperation(config.commandsFiles);
|
||||||
|
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
if(!config.interactive)
|
||||||
|
{
|
||||||
|
if(operation < 0)
|
||||||
|
{
|
||||||
|
Log(Log::ERROR)<<"An operation must be selected";
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
ret = perfromOperation(operation, config.commandsFiles+1, config);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
while(true)
|
||||||
|
{
|
||||||
|
std::cout<<"> ";
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
353
src/matutils.cpp
Normal file
353
src/matutils.cpp
Normal file
@ -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 <opencv2/core/ocl.hpp>
|
||||||
|
#include <iostream>
|
||||||
|
#include <math.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#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<float>(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<float>& in, std::vector<size_t>& 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__<<": "<<i<<" is outlier mean: "<<mean<<" sd: "<<sd<<" n: "<<n<<'\n';
|
||||||
|
outliers.push_back(i);
|
||||||
|
removed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(removed) thompsonTauTest(in, outliers, criticalValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<cv::Point2f>::iterator getTopLeft(std::vector<cv::Point2f>& 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<cv::Point2f>::iterator getBottomRight(std::vector<cv::Point2f>& 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<cv::Point2f> >& 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<float>::max();
|
||||||
|
float distRA = std::numeric_limits<float>::max();
|
||||||
|
float distLA = std::numeric_limits<float>::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<cv::Point2f>& array, float xTolerance, float yTolerance)
|
||||||
|
{
|
||||||
|
float minDistance = std::numeric_limits<float>::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<float>(y);
|
||||||
|
for(int x = 0; x < mat.cols; ++x)
|
||||||
|
{
|
||||||
|
if(col[x] < 0)
|
||||||
|
{
|
||||||
|
int closestA = -1;
|
||||||
|
int closestB = -1;
|
||||||
|
int dist = std::numeric_limits<int>::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<int>::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<float>(y);
|
||||||
|
for(int x = 0; x < mat.cols; ++x)
|
||||||
|
{
|
||||||
|
if(col[x] < 0 && col[x] > -2)
|
||||||
|
{
|
||||||
|
if(y > 0 && mat.at<float>(y-1,x) >= 0)
|
||||||
|
{
|
||||||
|
col[x] = mat.at<float>(y-1,x);
|
||||||
|
finished = false;
|
||||||
|
}
|
||||||
|
else if(y < mat.rows-1 && mat.at<float>(y+1,x) >= 0)
|
||||||
|
{
|
||||||
|
col[x] = mat.at<float>(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<float>(0,mat.cols/2);
|
||||||
|
float centerLeft = mat.at<float>(mat.rows/2,0);
|
||||||
|
float centerBottom = mat.at<float>(mat.rows-1,mat.cols/2);
|
||||||
|
float centerRight = mat.at<float>(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<float>(mat.rows/2,left) == centerLeft; ++left);
|
||||||
|
for(; top < mat.rows && mat.at<float>(top,mat.cols/2) == centerTop; ++top);
|
||||||
|
for(; right > left && mat.at<float>(mat.rows/2,right) == centerRight; --right);
|
||||||
|
for(; bottom > top && mat.at<float>(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: "<<roi;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool deleteEmptyCols(cv::Mat& mat)
|
||||||
|
{
|
||||||
|
assert(mat.type() == CV_32FC1);
|
||||||
|
|
||||||
|
std::vector<size_t> 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<float>(x,y) > 0)
|
||||||
|
{
|
||||||
|
empty = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!empty)
|
||||||
|
cols.push_back(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mat.cols < static_cast<long int>(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<float>(y,0) >= 0) ++front;
|
||||||
|
if(mat.at<float>(y,mat.cols-1) >= 0) ++back;
|
||||||
|
}
|
||||||
|
Log(Log::DEBUG)<<__func__<<" front: "<<front<<" back "<<back;
|
||||||
|
cv::Rect roi;
|
||||||
|
bool frontRej = (front < mat.rows/2);
|
||||||
|
bool backRej = (back < mat.rows/2);
|
||||||
|
roi.x=frontRej;
|
||||||
|
roi.y=0;
|
||||||
|
roi.width = mat.cols - backRej - frontRej;
|
||||||
|
roi.height = mat.rows;
|
||||||
|
mat = mat(roi);
|
||||||
|
if(frontRej || backRej) removeEmptyFrontBackCols(mat);
|
||||||
|
}
|
||||||
|
|
50
src/matutils.h
Normal file
50
src/matutils.h
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
/**
|
||||||
|
* 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 <opencv2/core/ocl.hpp>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
void sanityCheckMap(cv::Mat& mat, const float min, const float max, const float minValue, const float maxValue);
|
||||||
|
|
||||||
|
std::vector<cv::Point2f>::iterator getTopLeft(std::vector<cv::Point2f>& points);
|
||||||
|
|
||||||
|
std::vector<cv::Point2f>::iterator getBottomRight(std::vector<cv::Point2f>& 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<cv::Point2f> >& array,
|
||||||
|
float xTolerance, float yTolerance);
|
||||||
|
|
||||||
|
bool findClosest(size_t& index, const cv::Point2f point, const std::vector<cv::Point2f>& array, float xTolerance, float yTolerance);
|
||||||
|
|
||||||
|
void thompsonTauTest(const std::vector<float>& in, std::vector<size_t>& 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);
|
||||||
|
|
||||||
|
|
@ -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
|
#pragma once
|
||||||
#include <opencv2/core/ocl.hpp>
|
#include <opencv2/core/ocl.hpp>
|
||||||
#include <opencv2/imgproc.hpp>
|
#include <opencv2/imgproc.hpp>
|
263
src/unwrap.cpp
Normal file
263
src/unwrap.cpp
Normal file
@ -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 <opencv2/highgui.hpp>
|
||||||
|
#include <opencv2/stitching/detail/seam_finders.hpp>
|
||||||
|
#include <opencv2/stitching/detail/blenders.hpp>
|
||||||
|
#include <opencv2/imgproc.hpp>
|
||||||
|
#include <math.h>
|
||||||
|
#include <vector>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "matutils.h"
|
||||||
|
#include "drawing.h"
|
||||||
|
#include "log.h"
|
||||||
|
|
||||||
|
static void sortIntoRemapMaps(const std::vector<DetectedPoint>& 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<int>::max();
|
||||||
|
int xMax = std::numeric_limits<int>::min();
|
||||||
|
int yMin = std::numeric_limits<int>::max();
|
||||||
|
int yMax = std::numeric_limits<int>::min();
|
||||||
|
|
||||||
|
for(auto& point : points)
|
||||||
|
{
|
||||||
|
Log(Log::DEBUG)<<"point: "<<point.coordinate.x<<'x'<<point.coordinate.y;
|
||||||
|
|
||||||
|
if(point.coordinate.x > 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: "<<xGridSize<<'x'<<yGridSize;
|
||||||
|
|
||||||
|
for(int y = 0; y < xMat.rows; y++)
|
||||||
|
{
|
||||||
|
float* colx = xMat.ptr<float>(y);
|
||||||
|
float* coly = yMat.ptr<float>(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<DetectedPoint>& points, bool verbose)
|
||||||
|
{
|
||||||
|
sortIntoRemapMaps(points, out.xMat, out.yMat, out.topLeftCoordinate);
|
||||||
|
|
||||||
|
Log(Log::DEBUG)<<__func__<<": xMat raw\n"<<out.xMat;
|
||||||
|
removeEmptyFrontBackCols(out.xMat);
|
||||||
|
removeEmptyFrontBackCols(out.yMat);
|
||||||
|
deleteEmptyCols(out.xMat);
|
||||||
|
deleteEmptyCols(out.yMat);
|
||||||
|
Log(Log::DEBUG)<<__func__<<": xMat rejcted\n"<<out.xMat;
|
||||||
|
fillMissing(out.xMat);
|
||||||
|
Log(Log::DEBUG)<<__func__<<": xMat filled\n"<<out.xMat;
|
||||||
|
interpolateMissing(out.xMat);
|
||||||
|
interpolateMissing(out.yMat);
|
||||||
|
|
||||||
|
sanityCheckMap(out.xMat, 0, image.cols-1, -1, -1);
|
||||||
|
sanityCheckMap(out.yMat, 0, image.rows-1, -1, -1);
|
||||||
|
fillMissing(out.xMat);
|
||||||
|
interpolateMissing(out.xMat);
|
||||||
|
interpolateMissing(out.yMat);
|
||||||
|
sanityCheckMap(out.xMat, 0, image.cols-1, 0, image.cols-1);
|
||||||
|
sanityCheckMap(out.yMat, 0, image.rows-1, 0, image.rows-1);
|
||||||
|
|
||||||
|
Log(Log::INFO)<<__func__<<": xMat \n"<<out.xMat;
|
||||||
|
Log(Log::INFO)<<__func__<<": yMat \n"<<out.yMat;
|
||||||
|
|
||||||
|
if(out.xMat.cols < 3 || out.xMat.rows < 3)
|
||||||
|
{
|
||||||
|
Log(Log::ERROR)<<"Error creating map, to few points with high confidence";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(verbose)
|
||||||
|
{
|
||||||
|
RemapedImage remaped = applyRemap(image, out);
|
||||||
|
cv::imshow( "Viewer", remaped.image );
|
||||||
|
cv::waitKey(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool saveRemapMap(const RemapMap& map, const std::string& fileName)
|
||||||
|
{
|
||||||
|
cv::FileStorage matf(fileName+".mat", cv::FileStorage::WRITE );
|
||||||
|
matf<<"xmat"<<map.xMat<<"ymat"<<map.yMat<<"origin"<<map.topLeftCoordinate;
|
||||||
|
matf.release();
|
||||||
|
Log(Log::INFO)<<"Unwrap maps saved to "<<fileName<<".mat";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
RemapMap loadRemapMap(const std::string& fileName)
|
||||||
|
{
|
||||||
|
cv::FileStorage fs(fileName, cv::FileStorage::READ);
|
||||||
|
if (!fs.isOpened())
|
||||||
|
{
|
||||||
|
Log(Log::ERROR)<<"could not open maps file "<<fileName;
|
||||||
|
return RemapMap();
|
||||||
|
}
|
||||||
|
RemapMap map;
|
||||||
|
fs["xmat"]>>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<RemapedImage>& 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: "<<image.image.rows<<'x'<<image.image.cols<<" at "<<image.origin.x<<'x'<<image.origin.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log(Log::DEBUG)<<"outputSize: "<<outputSize;
|
||||||
|
|
||||||
|
cv::Mat out(outputSize, images[0].image.type(), cv::Scalar::all(0));
|
||||||
|
|
||||||
|
for(auto& image : images)
|
||||||
|
{
|
||||||
|
cv::Rect roi(image.origin, image.image.size());
|
||||||
|
image.image.copyTo(out(roi));
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
cv::Mat stich(const std::vector<RemapedImage>& images, bool seamAdjust)
|
||||||
|
{
|
||||||
|
std::vector<cv::Mat> masks(images.size());
|
||||||
|
std::vector<cv::Point> corners(images.size());
|
||||||
|
std::vector<cv::Size> 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<cv::UMat> images32f(images.size());
|
||||||
|
std::vector<cv::UMat> 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<cv::detail::Blender> blender;
|
||||||
|
blender = cv::detail::Blender::createDefault(cv::detail::Blender::Blender::MULTI_BAND, false);
|
||||||
|
cv::detail::MultiBandBlender* mb = dynamic_cast<cv::detail::MultiBandBlender*>(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;
|
||||||
|
}
|
48
src/unwrap.h
Normal file
48
src/unwrap.h
Normal file
@ -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 <opencv2/core/ocl.hpp>
|
||||||
|
#include <string>
|
||||||
|
#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<DetectedPoint>& points, bool verbose = false);
|
||||||
|
|
||||||
|
RemapedImage applyRemap(const cv::Mat& image, const RemapMap &map);
|
||||||
|
|
||||||
|
cv::Mat stich(const std::vector<RemapedImage>& images, bool seamAdjust = false);
|
||||||
|
cv::Mat simpleStich(const std::vector<RemapedImage>& images);
|
568
unwrap.cpp
568
unwrap.cpp
@ -1,568 +0,0 @@
|
|||||||
#include "unwrap.h"
|
|
||||||
|
|
||||||
#include <opencv2/highgui.hpp>
|
|
||||||
#include <opencv2/imgproc.hpp>
|
|
||||||
#include <math.h>
|
|
||||||
#include <vector>
|
|
||||||
#include <algorithm>
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
#include "matutils.h"
|
|
||||||
#include "drawing.h"
|
|
||||||
#include "log.h"
|
|
||||||
|
|
||||||
struct DisplacmentMap
|
|
||||||
{
|
|
||||||
std::vector< std::vector<cv::Point2f> > destination;
|
|
||||||
std::vector< std::vector<cv::Point2f> > source;
|
|
||||||
};
|
|
||||||
|
|
||||||
static std::vector< std::vector<cv::Point2f > > sortPointsIntoRows(std::vector<cv::Point2f>& points)
|
|
||||||
{
|
|
||||||
|
|
||||||
if(points.size() < 6) return std::vector< std::vector<cv::Point2f> >();
|
|
||||||
|
|
||||||
cv::Point2f topLeft(*getTopLeft(points));
|
|
||||||
cv::Point2f bottomRight(*getBottomRight(points));
|
|
||||||
|
|
||||||
Log(Log::DEBUG)<<"topLeft "<<topLeft.x<<' '<<topLeft.y;
|
|
||||||
Log(Log::DEBUG)<<"bottomRight "<<bottomRight.x<<' '<<bottomRight.y;
|
|
||||||
|
|
||||||
float fuzz = (bottomRight.x-topLeft.x)*0.05f;
|
|
||||||
|
|
||||||
for(size_t i = 0; i < points.size(); ++i)
|
|
||||||
{
|
|
||||||
if(points[i].x < topLeft.x-fuzz || points[i].y < topLeft.y-fuzz ||
|
|
||||||
points[i].x > 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<cv::Point2f> > 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<cv::Point2f>());
|
|
||||||
}
|
|
||||||
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<cv::Point2f>& row, float fuzz = 1.3f)
|
|
||||||
{
|
|
||||||
std::vector<float> 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 "<<xMinDist;
|
|
||||||
|
|
||||||
float meanXDistAccum = 0;
|
|
||||||
size_t validCount = 0;
|
|
||||||
for(size_t i = 0; i < xRowDists.size(); ++i)
|
|
||||||
{
|
|
||||||
if(xRowDists[i] < xMinDist*fuzz)
|
|
||||||
{
|
|
||||||
++validCount;
|
|
||||||
meanXDistAccum+=xRowDists[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return meanXDistAccum/validCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static std::vector<cv::RotatedRect> fitEllipses(std::vector< std::vector<cv::Point2f > >& rows, bool remove = true)
|
|
||||||
{
|
|
||||||
if(rows.empty()) return std::vector<cv::RotatedRect>();
|
|
||||||
|
|
||||||
std::vector<cv::RotatedRect> 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<float>& in, std::vector<size_t>& 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__<<": "<<i<<" is outlier mean: "<<mean<<" sd: "<<sd<<" n: "<<n<<'\n';
|
|
||||||
outliers.push_back(i);
|
|
||||||
removed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(removed) thompsonTauTest(in, outliers, criticalValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool sanityCheckElipses(std::vector< std::vector<cv::Point2f > >& rows, std::vector<cv::RotatedRect>& 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<float> widths;
|
|
||||||
std::vector<float> heights;
|
|
||||||
|
|
||||||
for(auto& elipse : elipses)
|
|
||||||
{
|
|
||||||
widths.push_back(elipse.size.width);
|
|
||||||
heights.push_back(elipse.size.height);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<size_t> outliersW;
|
|
||||||
std::vector<size_t> outliersH;
|
|
||||||
thompsonTauTest(widths, outliersW, 2);
|
|
||||||
thompsonTauTest(heights, outliersH, 2);
|
|
||||||
|
|
||||||
std::vector< std::vector<cv::Point2f > > rowsReduced;
|
|
||||||
std::vector<cv::RotatedRect> 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<cv::Point2f > >& rows,
|
|
||||||
const std::vector<cv::RotatedRect>& 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<cv::Point2f>());
|
|
||||||
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 "<<meanYdist;
|
|
||||||
|
|
||||||
for(size_t rowCnt = 0; rowCnt < rows.size(); ++rowCnt)
|
|
||||||
{
|
|
||||||
const std::vector<cv::Point2f>& row = rows[rowCnt];
|
|
||||||
const cv::RotatedRect& elipse = elipses[rowCnt];
|
|
||||||
|
|
||||||
cv::Rect_<float> boundingRect = elipse.boundingRect2f();
|
|
||||||
|
|
||||||
Log(Log::DEBUG)<<__func__<<": Proc row "<<rowCnt;
|
|
||||||
|
|
||||||
for(size_t i = 0; i < row.size(); ++i)
|
|
||||||
{
|
|
||||||
float yDest = rowCnt*meanYdist;
|
|
||||||
|
|
||||||
double normDist = ((row[i].x - boundingRect.x)/boundingRect.width-0.5)*2;
|
|
||||||
double tau = asin(normDist);
|
|
||||||
float xDest = (((2*tau)/M_PI)*500)+500;
|
|
||||||
Log(Log::DEBUG)<<__func__<<": normDist "<<normDist<<" tau "<<tau<<" xDest "<<xDest;
|
|
||||||
displacmentmap.destination[rowCnt].push_back(cv::Point2f(xDest,yDest));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return displacmentmap;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void removeSparseCollums(cv::Mat& mat)
|
|
||||||
{
|
|
||||||
int front = 0;
|
|
||||||
int back = 0;
|
|
||||||
for(int y = 0; y < mat.rows; ++y)
|
|
||||||
{
|
|
||||||
if(mat.at<float>(y,0) >= 0) ++front;
|
|
||||||
if(mat.at<float>(y,mat.cols-1) >= 0) ++back;
|
|
||||||
}
|
|
||||||
Log(Log::DEBUG)<<__func__<<" front: "<<front<<" back "<<back;
|
|
||||||
cv::Rect roi;
|
|
||||||
bool frontRej = (front < mat.rows/2);
|
|
||||||
bool backRej = (back < mat.rows/2);
|
|
||||||
roi.x=frontRej;
|
|
||||||
roi.y=0;
|
|
||||||
roi.width = mat.cols - backRej - frontRej;
|
|
||||||
roi.height = mat.rows;
|
|
||||||
mat = mat(roi);
|
|
||||||
if(frontRej || backRej) removeSparseCollums(mat);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static void generateRemapMaps(const DisplacmentMap& map, cv::Mat& xMat, cv::Mat& yMat, float fuzz = 1.3f)
|
|
||||||
{
|
|
||||||
if(map.destination.size() < 2)
|
|
||||||
{
|
|
||||||
Log(Log::ERROR)<<__func__<<": at least 2 rows are needed";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
float yMeanDist = map.destination[1][0].y-map.destination[0][0].y;
|
|
||||||
float xMeanDist = 0;
|
|
||||||
for(size_t i = 0; i < map.destination.size(); ++i)
|
|
||||||
{
|
|
||||||
xMeanDist+=detimineXPitch(map.destination[i]);
|
|
||||||
}
|
|
||||||
xMeanDist/=map.destination.size();
|
|
||||||
Log(Log::DEBUG)<<__func__<<": xMeanDist "<<xMeanDist;
|
|
||||||
|
|
||||||
float xMin = std::numeric_limits<float>::max();
|
|
||||||
float xMeanMin = 0;
|
|
||||||
float xMax = std::numeric_limits<float>::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 "<<xMin;
|
|
||||||
Log(Log::DEBUG)<<__func__<<": Grid: grid xMax "<<xMax;
|
|
||||||
Log(Log::DEBUG)<<__func__<<": Grid: grid xMeanMin "<<xMeanMin;
|
|
||||||
|
|
||||||
size_t xGridSize = static_cast<size_t>(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: "<<xGridSize<<'x'<<map.destination.size();
|
|
||||||
|
|
||||||
for(int y = 0; y < xMat.rows; y++)
|
|
||||||
{
|
|
||||||
float* colx = xMat.ptr<float>(y);
|
|
||||||
float* coly = yMat.ptr<float>(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: "<<found<<' '<<xIndex<<'x'<<yIndex<<" at: "<<(x)*xMeanDist+xMin<<'x'<<(y)*yMeanDist;
|
|
||||||
colx[x] = found ? map.source[yIndex][xIndex].x : -1;
|
|
||||||
coly[x] = found ? map.source[yIndex][xIndex].y : -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Log(Log::DEBUG)<<__func__<<": xMat raw\n"<<xMat;
|
|
||||||
removeSparseCollums(xMat);
|
|
||||||
removeSparseCollums(yMat);
|
|
||||||
Log(Log::DEBUG)<<__func__<<": xMat rejcted\n"<<xMat;
|
|
||||||
fillMissing(xMat);
|
|
||||||
Log(Log::DEBUG)<<__func__<<": xMat filled\n"<<xMat;
|
|
||||||
interpolateMissing(xMat);
|
|
||||||
Log(Log::DEBUG)<<__func__<<": yMat raw \n"<<yMat;
|
|
||||||
interpolateMissing(yMat);
|
|
||||||
Log(Log::INFO)<<__func__<<": xMat \n"<<xMat;
|
|
||||||
Log(Log::INFO)<<__func__<<": yMat \n"<<yMat;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void generateRemapMaps(const std::vector<cv::Point2f>& points, const std::vector<cv::Point2i>& 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<int>::max();
|
|
||||||
int xMax = std::numeric_limits<int>::min();
|
|
||||||
int yMin = std::numeric_limits<int>::max();
|
|
||||||
int yMax = std::numeric_limits<int>::min();
|
|
||||||
|
|
||||||
for(auto& point : coordinates)
|
|
||||||
{
|
|
||||||
Log(Log::DEBUG)<<"point: "<<point.x<<'x'<<point.y;
|
|
||||||
|
|
||||||
if(point.x > 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: "<<xGridSize<<'x'<<yGridSize;
|
|
||||||
|
|
||||||
for(int y = 0; y < xMat.rows; y++)
|
|
||||||
{
|
|
||||||
float* colx = xMat.ptr<float>(y);
|
|
||||||
float* coly = yMat.ptr<float>(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"<<xMat;
|
|
||||||
removeSparseCollums(xMat);
|
|
||||||
removeSparseCollums(yMat);
|
|
||||||
Log(Log::DEBUG)<<__func__<<": xMat rejcted\n"<<xMat;
|
|
||||||
fillMissing(xMat);
|
|
||||||
Log(Log::DEBUG)<<__func__<<": xMat filled\n"<<xMat;
|
|
||||||
interpolateMissing(xMat);
|
|
||||||
Log(Log::DEBUG)<<__func__<<": yMat raw \n"<<yMat;
|
|
||||||
interpolateMissing(yMat);
|
|
||||||
Log(Log::INFO)<<__func__<<": xMat \n"<<xMat;
|
|
||||||
Log(Log::INFO)<<__func__<<": yMat \n"<<yMat;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool createRemapMap(cv::Mat& image, std::vector<cv::Point2f> points, const std::vector<cv::Point2i>& coordinates,
|
|
||||||
const std::string& fileName, bool verbose)
|
|
||||||
{
|
|
||||||
|
|
||||||
cv::Mat xMat;
|
|
||||||
cv::Mat yMat;
|
|
||||||
|
|
||||||
if(coordinates.empty())
|
|
||||||
{
|
|
||||||
std::vector< std::vector<cv::Point2f > > rows = sortPointsIntoRows(points);
|
|
||||||
Log(Log::INFO)<<"Found "<<rows.size()<<" rows";
|
|
||||||
if(rows.size() < 2)
|
|
||||||
{
|
|
||||||
Log(Log::ERROR)<<"Error creating map, insufficant rows detected";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(verbose)
|
|
||||||
{
|
|
||||||
cv::Mat pointsMat = cv::Mat::zeros(image.size(), CV_8UC3);
|
|
||||||
drawRows(pointsMat, rows);
|
|
||||||
cv::imshow( "Viewer", pointsMat );
|
|
||||||
cv::waitKey(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector< cv::RotatedRect > ellipses = fitEllipses(rows);
|
|
||||||
Log(Log::INFO)<<"Found "<<ellipses.size()<<" ellipses. rows reduced to "<<rows.size();
|
|
||||||
if(ellipses.size() < 3)
|
|
||||||
{
|
|
||||||
Log(Log::ERROR)<<"Error creating map, insufficant ellipses detected";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(verbose)
|
|
||||||
{
|
|
||||||
cv::Mat pointsMat = cv::Mat::zeros(image.size(), CV_8UC3);
|
|
||||||
drawRows(pointsMat, rows);
|
|
||||||
drawEllipses(pointsMat, ellipses);
|
|
||||||
cv::imshow( "Viewer", pointsMat );
|
|
||||||
cv::waitKey(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
sanityCheckElipses(rows, ellipses);
|
|
||||||
|
|
||||||
if(verbose)
|
|
||||||
{
|
|
||||||
cv::Mat pointsMat = cv::Mat::zeros(image.size(), CV_8UC3);
|
|
||||||
drawRows(pointsMat, rows);
|
|
||||||
drawEllipses(pointsMat, ellipses);
|
|
||||||
cv::imshow( "Viewer", pointsMat );
|
|
||||||
cv::waitKey(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
DisplacmentMap dispMap = calcDisplacementMap(rows, ellipses);
|
|
||||||
if(dispMap.destination.size() < 2)
|
|
||||||
{
|
|
||||||
Log(Log::ERROR)<<"Error creating map, unable to calculate destination points";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
cv::Mat dispPointsDest = cv::Mat::zeros(cv::Size(cv::Size(1000,1000)), CV_8UC3);
|
|
||||||
drawRows(dispPointsDest, dispMap.destination);
|
|
||||||
|
|
||||||
if(verbose)
|
|
||||||
{
|
|
||||||
cv::imshow( "Viewer", dispPointsDest );
|
|
||||||
cv::waitKey(0);
|
|
||||||
}
|
|
||||||
generateRemapMaps(dispMap, xMat, yMat);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
generateRemapMaps(points, coordinates, xMat, yMat);
|
|
||||||
}
|
|
||||||
|
|
||||||
sanityCheckMap(xMat, 0, image.cols-1, -1, -1);
|
|
||||||
sanityCheckMap(yMat, 0, image.rows-1, -1, -1);
|
|
||||||
fillMissing(xMat);
|
|
||||||
interpolateMissing(xMat);
|
|
||||||
interpolateMissing(yMat);
|
|
||||||
sanityCheckMap(xMat, 0, image.cols-1, 0, image.cols-1);
|
|
||||||
sanityCheckMap(yMat, 0, image.rows-1, 0, image.rows-1);
|
|
||||||
|
|
||||||
if(xMat.cols < 3 || xMat.rows < 3)
|
|
||||||
{
|
|
||||||
Log(Log::ERROR)<<"Error creating map, to few points with high confidence";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
cv::FileStorage matf(fileName+".mat", cv::FileStorage::WRITE );
|
|
||||||
matf<<"xmat"<<xMat<<"ymat"<<yMat;
|
|
||||||
matf.release();
|
|
||||||
|
|
||||||
Log(Log::INFO)<<"Unwrap maps saved to "<<fileName<<".mat";
|
|
||||||
|
|
||||||
if(verbose)
|
|
||||||
{
|
|
||||||
cv::Mat remaped;
|
|
||||||
applyRemap(image, remaped, xMat, yMat, 800);
|
|
||||||
cv::imshow( "Viewer", remaped );
|
|
||||||
cv::waitKey(0);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool findDeadSpace(const cv::Mat& mat, cv::Rect& roi)
|
|
||||||
{
|
|
||||||
if(mat.cols < 2 || mat.rows < 2) return false;
|
|
||||||
|
|
||||||
float centerTop = mat.at<float>(0,mat.cols/2);
|
|
||||||
float centerLeft = mat.at<float>(mat.rows/2,0);
|
|
||||||
float centerBottom = mat.at<float>(mat.rows-1,mat.cols/2);
|
|
||||||
float centerRight = mat.at<float>(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<float>(mat.rows/2,left) == centerLeft; ++left);
|
|
||||||
for(; top < mat.rows && mat.at<float>(top,mat.cols/2) == centerTop; ++top);
|
|
||||||
for(; right > left && mat.at<float>(mat.rows/2,right) == centerRight; --right);
|
|
||||||
for(; bottom > top && mat.at<float>(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);
|
|
||||||
}
|
|
7
unwrap.h
7
unwrap.h
@ -1,7 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include <opencv2/core/ocl.hpp>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
bool createRemapMap(cv::Mat& image, std::vector<cv::Point2f> points, const std::vector<cv::Point2i>& 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);
|
|
Reference in New Issue
Block a user