657 lines
18 KiB
C++
657 lines
18 KiB
C++
#include <iostream>
|
|
#include <opencv2/highgui.hpp>
|
|
#include <opencv2/imgproc.hpp>
|
|
#include <opencv2/bgsegm.hpp>
|
|
#include <opencv2/ccalib.hpp>
|
|
#include <opencv2/stitching/detail/autocalib.hpp>
|
|
#include <opencv2/stitching/detail/blenders.hpp>
|
|
#include <opencv2/stitching/detail/timelapsers.hpp>
|
|
#include <opencv2/stitching/detail/camera.hpp>
|
|
#include <opencv2/stitching/detail/exposure_compensate.hpp>
|
|
#include <opencv2/stitching/detail/matchers.hpp>
|
|
#include <opencv2/stitching/detail/motion_estimators.hpp>
|
|
#include <opencv2/stitching/detail/seam_finders.hpp>
|
|
#include <opencv2/stitching/detail/warpers.hpp>
|
|
#include <opencv2/stitching/warpers.hpp>
|
|
#include <opencv2/stitching/detail/seam_finders.hpp>
|
|
#include <opencv2/core/ocl.hpp>
|
|
#include <math.h>
|
|
#include <unistd.h>
|
|
#include <vector>
|
|
#include <string>
|
|
#include <sstream>
|
|
#include <sys/stat.h>
|
|
#include <dirent.h>
|
|
#include <algorithm>
|
|
|
|
#include "strnatcmp.h"
|
|
|
|
bool verbose = false;
|
|
|
|
class Config
|
|
{
|
|
public:
|
|
std::vector<std::string> inputImages;
|
|
std::string outputImage = "out.png";
|
|
int gridSizeX = 11;
|
|
int gridSizeY = 10;
|
|
int sampleSize = 2;
|
|
float confidence = 1.5f;
|
|
int startSearch = 0;
|
|
bool warp = false;
|
|
float deltaX = -1;
|
|
float deltaY = -1;
|
|
bool illum = true;
|
|
bool seamAdjust = true;
|
|
};
|
|
|
|
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() );
|
|
}
|
|
|
|
static void printUsage()
|
|
{
|
|
std::cout<<"Usage: scannassembler [OPTION]... [FILES/DIRECTORY]...\n"
|
|
<<"This program assembles a regular grid of overlapping images into one large image. [FILES/DIRECTORY] is either a list of at least 4 files or a directory containing only images, with naturaly sorted file names.\n\n"
|
|
<<"Options:\n"
|
|
<<" -h, --help Display this help.\n"
|
|
<<" -o, --output Output image file name.\n"
|
|
<<" -v, --verbose Make application verbose.\n"
|
|
<<" -x, --xSize X size of grid.\n"
|
|
<<" -y, --ySize Y size of grid.\n"
|
|
<<" -c, --confidence Confidence of image feature matiching, default 1.2.\n"
|
|
<<" --start Image number where to start matching search\n"
|
|
<<" --warp Attempt to warp the image to correct for camera intrinsics. Defaults to off\n"
|
|
<<" --no-warp Disable warp.\n"
|
|
<<" -d, --delta [x] [y] Presett distance between images in pixels instead of automatic recognition\n"
|
|
<<" --no-seam blend only mode\n"
|
|
<<" --illum correct for differences in image brigeness. Defaults to on\n"
|
|
<<" --no-illum Disable image brigeness correction\n";
|
|
}
|
|
|
|
static int parseCmdArgs(int argc, char** argv, Config *config)
|
|
{
|
|
if (argc == 1)
|
|
{
|
|
printUsage();
|
|
return -1;
|
|
}
|
|
for (int i = 1; i < argc; i++)
|
|
{
|
|
std::string currentToken(argv[i]);
|
|
if (currentToken == "--help" || currentToken == "-h")
|
|
{
|
|
printUsage();
|
|
return -1;
|
|
}
|
|
else if (currentToken == "--output" || currentToken == "-o")
|
|
{
|
|
i++;
|
|
if(argc > i) config->outputImage = argv[i];
|
|
else return -1;
|
|
}
|
|
else if (currentToken == "--verbose" || currentToken == "-v")
|
|
{
|
|
verbose = true;
|
|
std::cout<<"Verbose mode on\n";
|
|
}
|
|
else if (currentToken == "--xSize" || currentToken == "-x")
|
|
{
|
|
i++;
|
|
if(argc > i) config->gridSizeX = atoi(argv[i]);
|
|
else return -1;
|
|
}
|
|
else if (currentToken == "--ySize" || currentToken == "-y")
|
|
{
|
|
i++;
|
|
if(argc > i) config->gridSizeY = atoi(argv[i]);
|
|
else return -1;
|
|
}
|
|
else if (currentToken == "--confidence" || currentToken == "-c")
|
|
{
|
|
i++;
|
|
if(argc > i) config->confidence = atof(argv[i]);
|
|
else return -1;
|
|
}
|
|
else if (currentToken == "--start")
|
|
{
|
|
i++;
|
|
if(argc > i) config->startSearch = atoi(argv[i]);
|
|
else return -1;
|
|
}
|
|
else if (currentToken == "--delta" || currentToken == "-d")
|
|
{
|
|
if(argc > i+2)
|
|
{
|
|
config->deltaX = atof(argv[i+1]);
|
|
config->deltaY = atof(argv[i+2]);
|
|
i+=2;
|
|
}
|
|
else return -1;
|
|
}
|
|
else if (currentToken == "--warp")
|
|
{
|
|
config->warp = true;
|
|
}
|
|
else if (currentToken == "--no-warp")
|
|
{
|
|
config->warp = false;
|
|
}
|
|
else if (currentToken == "--illum")
|
|
{
|
|
config->illum = true;
|
|
}
|
|
else if (currentToken == "--no-illum")
|
|
{
|
|
config->illum = false;
|
|
}
|
|
else if (currentToken == "--no-seam")
|
|
{
|
|
config->seamAdjust = false;
|
|
}
|
|
else config->inputImages.push_back(argv[i]);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void showImages(std::vector<cv::Mat> images)
|
|
{
|
|
cv::namedWindow( "Viewer", cv::WINDOW_AUTOSIZE );
|
|
for(uint i = 0; i< images.size(); i++)
|
|
{
|
|
cv::imshow( "Viewer", images[i] );
|
|
cv::waitKey(0);
|
|
}
|
|
cv::destroyWindow("Viewer");
|
|
}
|
|
|
|
void showImages(std::vector<cv::UMat> images)
|
|
{
|
|
cv::namedWindow( "Viewer", cv::WINDOW_AUTOSIZE );
|
|
for(uint i = 0; i< images.size(); i++)
|
|
{
|
|
cv::imshow( "Viewer", images[i] );
|
|
cv::waitKey(0);
|
|
}
|
|
cv::destroyWindow("Viewer");
|
|
}
|
|
|
|
void showImages(cv::Mat image)
|
|
{
|
|
cv::namedWindow( "Viewer", cv::WINDOW_AUTOSIZE );
|
|
cv::imshow( "Viewer", image );
|
|
cv::waitKey(0);
|
|
cv::destroyWindow("Viewer");
|
|
}
|
|
|
|
std::vector<cv::Mat> getSample(Config config, std::vector<cv::Mat> images, const int centralIndex, const int gridSizeX = 2, const int gridSizeY = 2)
|
|
{
|
|
std::vector<cv::Mat> sampleImages;
|
|
if(images.size() < (uint)centralIndex+gridSizeX+gridSizeY*(config.gridSizeX)) return sampleImages;
|
|
|
|
for(int y = 0; y < gridSizeY; y++)
|
|
{
|
|
for(int x = 0; x < gridSizeX; x++)
|
|
{
|
|
sampleImages.push_back(images[centralIndex+x+y*(config.gridSizeX)]);
|
|
}
|
|
}
|
|
return sampleImages;
|
|
}
|
|
|
|
bool calculateCameraIntrinsics(std::vector<cv::Mat> sampleImages, std::vector<cv::detail::CameraParams>* cameras, float confidence = 1.2f)
|
|
{
|
|
std::vector<cv::detail::ImageFeatures> features(sampleImages.size());
|
|
|
|
cv::Ptr<cv::Feature2D> featurefinder = cv::KAZE::create();
|
|
for(uint i = 0; i < sampleImages.size(); i++) featurefinder->detectAndCompute(sampleImages[i], cv::Mat(), features[i].keypoints, features[i].descriptors);
|
|
|
|
for(uint i = 0; i<sampleImages.size(); i++)std::cout<<"Image "<<i<<" features: " << features[i].keypoints.size()<<'\n';
|
|
|
|
cv::detail::BestOf2NearestMatcher matcher;
|
|
std::vector<cv::detail::MatchesInfo> pairwiseMatches;
|
|
matcher(features, pairwiseMatches);
|
|
|
|
std::vector<int> ids = cv::detail::leaveBiggestComponent(features, pairwiseMatches, confidence);
|
|
if(ids.size() != sampleImages.size())
|
|
{
|
|
std::cout<<"Match failure only "<<ids.size()<<" matches\n";
|
|
return false;
|
|
}
|
|
std::cout<<"Matched!\nCaluclating Camera Matrixes\n";
|
|
|
|
cv::detail::HomographyBasedEstimator estimator;
|
|
if(!(estimator(features, pairwiseMatches, *cameras)))
|
|
{
|
|
std::cout << "Homography estimation failed.\n";
|
|
return false;
|
|
}
|
|
|
|
for (uint i = 0; i < cameras->size(); i++)
|
|
{
|
|
cv::Mat R;
|
|
cameras->at(i).R.convertTo(R, CV_32F);
|
|
cameras->at(i).R = R;
|
|
}
|
|
|
|
|
|
cv::detail::BundleAdjusterRay adjuster;
|
|
adjuster.setRefinementMask(cv::Mat::ones(3, 3, CV_8U));
|
|
if(!adjuster(features, pairwiseMatches, *cameras))
|
|
{
|
|
std::cout << "Camera parameters adjusting failed.\n";
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
std::vector<std::string> processFileNames(std::vector<std::string> fileNames, bool sort = true)
|
|
{
|
|
if(fileNames.size() < 4)
|
|
{
|
|
if(verbose)std::cout<<"assuming directory\n";
|
|
DIR *directory;
|
|
directory = opendir(fileNames[0].c_str());
|
|
if(directory == NULL)
|
|
{
|
|
std::cout<<"Cant open directory "<<fileNames[0]<<'\n';
|
|
return fileNames;
|
|
}
|
|
std::string dirName = fileNames[0];
|
|
fileNames.clear();
|
|
struct dirent *dirEntry;
|
|
while ((dirEntry = readdir(directory)) != NULL)
|
|
{
|
|
if(dirEntry->d_type == DT_REG) fileNames.push_back(dirName + std::string(dirEntry->d_name));
|
|
}
|
|
}
|
|
if(sort) std::sort(fileNames.begin(),fileNames.end(), compareNat);
|
|
if(verbose)for(unsigned i = 0; i < fileNames.size(); i++) std::cout<<fileNames[i]<<'\n';
|
|
return fileNames;
|
|
}
|
|
|
|
std::vector<cv::Mat> loadImages(std::vector<std::string> fileNames)
|
|
{
|
|
std::vector<cv::Mat> images;
|
|
for(uint i = 0; i < fileNames.size(); 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;
|
|
}
|
|
|
|
cv::Mat illumCorrection(cv::Mat bgr_image, int clipLmt = 2)
|
|
{
|
|
// READ RGB color image and convert it to Lab
|
|
cv::Mat lab_image;
|
|
cv::cvtColor(bgr_image, lab_image, cv::COLOR_BGR2Lab);
|
|
|
|
// Extract the L channel
|
|
std::vector<cv::Mat> lab_planes(3);
|
|
cv::split(lab_image, lab_planes); // now we have the L image in lab_planes[0]
|
|
|
|
|
|
|
|
// apply the CLAHE algorithm to the L channel
|
|
cv::Ptr<cv::CLAHE> clahe = cv::createCLAHE();
|
|
clahe->setClipLimit(clipLmt);
|
|
cv::Mat dst;
|
|
|
|
clahe->apply(lab_planes[0], dst);
|
|
|
|
// Merge the the color planes back into an Lab image
|
|
dst.copyTo(lab_planes[0]);
|
|
cv::merge(lab_planes, lab_image);
|
|
|
|
// convert back to RGB
|
|
cv::Mat result;
|
|
cv::cvtColor(lab_image, result, cv::COLOR_Lab2BGR);
|
|
return result;
|
|
}
|
|
|
|
cv::Mat accumulate( std::vector< cv::Mat > images, int type = -1 )
|
|
{
|
|
cv::Mat accBuffer = cv::Mat::zeros(images.at(0).size(), CV_64FC3);
|
|
for(uint i = 0; i< images.size(); i++)
|
|
{
|
|
cv::Mat tmp;
|
|
images[i].convertTo(tmp, CV_64FC3);
|
|
cv::add(tmp, accBuffer, accBuffer);
|
|
}
|
|
accBuffer = accBuffer / images.size();
|
|
(type == -1) ? accBuffer.convertTo(accBuffer, images.at(0).type()) : accBuffer.convertTo(accBuffer, type);
|
|
return accBuffer;
|
|
}
|
|
|
|
void removeMean(std::vector< cv::Mat >* images)
|
|
{
|
|
cv::Mat acuumulated = accumulate(*images, CV_64FC3);
|
|
acuumulated = acuumulated - cv::mean(acuumulated);
|
|
for(uint i = 0; i< images->size(); i++)
|
|
{
|
|
cv::Mat tmp;
|
|
images->at(i).convertTo(tmp, CV_64FC3);
|
|
tmp = tmp - acuumulated;
|
|
tmp.convertTo(images->at(i), images->at(i).type());
|
|
}
|
|
}
|
|
|
|
cv::Point2f projectTopLeftPoint(cv::Size src_size, cv::InputArray K, cv::InputArray R, float flocal)
|
|
{
|
|
float tl_uf = (std::numeric_limits<float>::max)();
|
|
float tl_vf = (std::numeric_limits<float>::max)();
|
|
|
|
cv::detail::SphericalProjector projector;
|
|
|
|
projector.scale = flocal;
|
|
|
|
projector.setCameraParams(K,R);
|
|
|
|
float u, v;
|
|
for (int y = 0; y < src_size.height; ++y)
|
|
{
|
|
for (int x = 0; x < src_size.width; ++x)
|
|
{
|
|
projector.mapForward(static_cast<float>(x), static_cast<float>(y), u, v);
|
|
tl_uf = (std::min)(tl_uf, u);
|
|
tl_vf = (std::min)(tl_vf, v);
|
|
}
|
|
}
|
|
|
|
cv::Point2f result;
|
|
result.x = tl_uf;
|
|
result.y = tl_vf;
|
|
return result;
|
|
}
|
|
|
|
|
|
|
|
cv::Point2f wheigtedMean( std::vector< std::vector<cv::Point2f> > input )
|
|
{
|
|
//findDeltas
|
|
std::vector<cv::Point2f> delta;
|
|
for(uint i = 0; i < input.size(); i++)
|
|
{
|
|
cv::Point2f tmp;
|
|
tmp.x = ((input[i][1].x-input[i][0].x)+(input[i][3].x-input[i][2].x))/2;
|
|
tmp.y = ((input[i][2].y-input[i][0].y)+(input[i][3].y-input[i][1].y))/2;
|
|
delta.push_back(tmp);
|
|
|
|
}
|
|
|
|
//compute Mean
|
|
cv::Point2f avDelta(0,0);
|
|
for(uint i = 0; i < delta.size(); i++)
|
|
{
|
|
avDelta.x = avDelta.x + delta[i].x;
|
|
avDelta.y = avDelta.y + delta[i].y;
|
|
}
|
|
avDelta.x = avDelta.x/delta.size();
|
|
avDelta.y = avDelta.y/delta.size();
|
|
std::cout<<"Mean Delta x: "<<avDelta.x<<" y: "<<avDelta.y<<std::endl;
|
|
|
|
//refine mean
|
|
for(uint i = 0; i < delta.size(); i++)
|
|
{
|
|
if( abs(delta[i].x-avDelta.x)/avDelta.x > 0.2 || abs(delta[i].y-avDelta.y)/avDelta.y > 0.2 )
|
|
{
|
|
std::cout<<"removing x: "<<delta[i].x<<" y: "<<delta[i].y<<std::endl;
|
|
delta.erase(delta.begin()+i);
|
|
i--;
|
|
}
|
|
}
|
|
|
|
//recompute
|
|
avDelta.x=0;
|
|
avDelta.y=0;
|
|
for(uint i = 0; i < delta.size(); i++)
|
|
{
|
|
avDelta.x = avDelta.x + delta[i].x;
|
|
avDelta.y = avDelta.y + delta[i].y;
|
|
}
|
|
avDelta.x = avDelta.x/delta.size();
|
|
avDelta.y = avDelta.y/delta.size();
|
|
std::cout<<"refined Mean Delta x: "<<avDelta.x<<" y: "<<avDelta.y<<std::endl;
|
|
return avDelta;
|
|
}
|
|
|
|
int main(int argc, char* argv[])
|
|
{
|
|
cv::ocl::setUseOpenCL(false);
|
|
std::cout<<"UVOS scann sticher v2.1\n";
|
|
cd_to_exe_dir( argv );
|
|
|
|
Config config;
|
|
|
|
int parseReturn = parseCmdArgs(argc, argv, &config);
|
|
if(parseReturn != 0) return parseReturn;
|
|
|
|
std::vector<std::string> fileNames = processFileNames(config.inputImages);
|
|
if(fileNames.size() != (uint)config.gridSizeX*config.gridSizeY)
|
|
{
|
|
std::cout<<"This grid needs "<<config.gridSizeX*config.gridSizeY<<" images. "<<config.inputImages.size()<<" given.\n";
|
|
return -1;
|
|
}
|
|
|
|
std::vector<cv::Mat> images = loadImages(fileNames);
|
|
if(images.size() != fileNames.size())
|
|
{
|
|
images.size() == 0 ? std::cout<<"Could not read any images.\n" : std::cout<<"Could not read all images.\n";
|
|
return -1;
|
|
}
|
|
|
|
std::vector<cv::Size> sizes(images.size());
|
|
for(uint i = 0; i < images.size(); i++) sizes[i] = images[i].size();
|
|
|
|
cv::Point2f avDelta;
|
|
std::vector<cv::Mat> masks(images.size());
|
|
std::vector<cv::Mat> imagesWarped(images.size());
|
|
if(config.deltaX == -1 || config.deltaY == -1)
|
|
{
|
|
double avFocal = 1;
|
|
|
|
//Caluclate camera Intrinsics
|
|
std::vector< std::vector<cv::detail::CameraParams> > cameras;
|
|
{
|
|
std::vector<cv::Mat> samples;
|
|
int bottomLeftSampleImageIndex = config.startSearch;
|
|
int subsequentMatches = 0;
|
|
do
|
|
{
|
|
std::cout<<"trying set "<<bottomLeftSampleImageIndex<<'\n';
|
|
|
|
samples = getSample(config, images, bottomLeftSampleImageIndex,2,2);
|
|
bottomLeftSampleImageIndex+=(subsequentMatches*subsequentMatches+1)*2;
|
|
|
|
std::vector<cv::detail::CameraParams> tmpCameras;
|
|
if(samples.size() != 0 && calculateCameraIntrinsics(samples, &tmpCameras, config.confidence))
|
|
{
|
|
cameras.push_back(tmpCameras);
|
|
subsequentMatches++;
|
|
}
|
|
else subsequentMatches = 0;
|
|
|
|
}while( samples.size() != 0 );
|
|
}
|
|
if(cameras.size() == 0 )
|
|
{
|
|
std::cout<<"Can not find match.";
|
|
return -1;
|
|
}
|
|
|
|
//Caluclate mean focal length
|
|
for(uint i = 0; i < cameras.size(); i++)
|
|
{
|
|
for(uint u = 0; u < cameras[i].size(); u++)
|
|
{
|
|
avFocal += cameras[i][u].focal;
|
|
}
|
|
}
|
|
avFocal = avFocal/(cameras.size()*cameras[0].size());
|
|
|
|
//Caluclate Master Matrixes
|
|
cv::Mat_<double> avCameraMatrix = cv::Mat::zeros(3,3,CV_64F);
|
|
cv::Mat_<double> avRotationMatrix = cv::Mat::zeros(3,3,CV_64F);
|
|
for(uint i = 0; i< cameras.size(); i++)
|
|
{
|
|
for(uint u = 0; u < cameras[i].size(); u++)
|
|
{
|
|
cv::add(avCameraMatrix, cameras[i][u].K(), avCameraMatrix);
|
|
cv::add(avRotationMatrix, cameras[i][u].R, avRotationMatrix);
|
|
}
|
|
}
|
|
avCameraMatrix = avCameraMatrix/(cameras.size()*cameras[0].size());
|
|
avRotationMatrix = avRotationMatrix/(cameras.size()*cameras[0].size());
|
|
std::cout<<"Master Camera Intrinsics :\nK:\n" << avCameraMatrix << "\nR:\n" <<avRotationMatrix<<'\n'<<"Focal: "<<avFocal<<'\n';
|
|
|
|
// Caluclate smaple corners
|
|
std::vector< std::vector<cv::Point2f> > sampleCorners(cameras.size());
|
|
for(uint i = 0; i < cameras.size(); i++)
|
|
{
|
|
sampleCorners[i] = std::vector<cv::Point2f>(cameras[i].size());
|
|
for(uint u = 0; u < cameras[i].size(); u++)
|
|
{
|
|
cv::Mat_<float> K;
|
|
cameras[i][u].K().convertTo(K, CV_32F);
|
|
cv::Mat_<float> R;
|
|
cameras[i][u].R.convertTo(R, CV_32F);
|
|
cv::Mat uxmap, uymap;
|
|
|
|
sampleCorners[i][u] = projectTopLeftPoint(images[0].size(), K, R, avFocal);
|
|
std::cout<<"corner of set "<<i<<" sample "<<u<<" : x: "<<sampleCorners[i][u].x<<" y: "<<sampleCorners[i][u].y<<'\n';
|
|
}
|
|
}
|
|
|
|
avDelta = wheigtedMean(sampleCorners);
|
|
if(isnan(avDelta.x) || isnan(avDelta.y))
|
|
{
|
|
std::cout<<"Deltas not convergant.\n";
|
|
return 1;
|
|
}
|
|
if(config.warp)
|
|
{
|
|
std::cout<<"Warping Images\n";
|
|
cv::Ptr<cv::WarperCreator> warperCreator = cv::makePtr<cv::SphericalWarper>();
|
|
cv::Ptr<cv::detail::RotationWarper> warper = warperCreator->create(avFocal);
|
|
cv::Mat_<float> avCameraMatrix32f;
|
|
cv::Mat_<float> avRotationMatrix32f;
|
|
avCameraMatrix.convertTo(avCameraMatrix32f, CV_32F);
|
|
avRotationMatrix.convertTo(avRotationMatrix32f, CV_32F);
|
|
for (uint i = 0; i < images.size(); i++)
|
|
{
|
|
if(config.warp) warper->warp(images[i], avCameraMatrix32f, avRotationMatrix32f, cv::INTER_LINEAR, cv::BORDER_REFLECT, imagesWarped[i]);
|
|
images[i].release();
|
|
}
|
|
//warp masks
|
|
for (uint i = 0; i < imagesWarped.size(); i++)
|
|
{
|
|
masks[i].create(sizes[i], CV_8U);
|
|
masks[i].setTo(cv::Scalar::all(255));
|
|
warper->warp(masks[i], avCameraMatrix32f, avRotationMatrix32f, cv::INTER_LINEAR, cv::BORDER_REFLECT, masks[i]);
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
for (uint i = 0; i < images.size(); i++)
|
|
{
|
|
masks[i].create(sizes[i], CV_8U);
|
|
masks[i].setTo(cv::Scalar::all(255));
|
|
imagesWarped[i] = images[i];
|
|
images[i].release();
|
|
}
|
|
|
|
}
|
|
}
|
|
else
|
|
{
|
|
avDelta.x = config.deltaX;
|
|
avDelta.y = config.deltaY;
|
|
for (uint i = 0; i < images.size(); i++)
|
|
{
|
|
imagesWarped[i] = images[i];
|
|
masks[i].create(images[i].size(), CV_8U);
|
|
masks[i].setTo(cv::Scalar::all(255));
|
|
images[i].release();
|
|
}
|
|
}
|
|
|
|
|
|
//calculate Corners of all images
|
|
std::vector<cv::Point> corners(imagesWarped.size());
|
|
for(uint i = 0; i < imagesWarped.size(); i++)
|
|
{
|
|
cv::Point2i postionOfCurrentImage;
|
|
postionOfCurrentImage.x = i % (config.gridSizeX);
|
|
postionOfCurrentImage.y = (i - i % (config.gridSizeX))/config.gridSizeX;
|
|
if(verbose)std::cout<<"postition of image "<<i<<" file name: "<<config.inputImages[i]<<" x: "<<postionOfCurrentImage.x<<" y: "<<postionOfCurrentImage.y<<'\n';
|
|
corners[i].x = postionOfCurrentImage.x*avDelta.x;
|
|
corners[i].y = -avDelta.y*config.gridSizeY + postionOfCurrentImage.y*avDelta.y;
|
|
}
|
|
|
|
if(config.seamAdjust)
|
|
{
|
|
std::cout<<"Adjusting seams\n";
|
|
std::vector<cv::UMat> imagesWarped32f(imagesWarped.size());
|
|
std::vector<cv::UMat> masksUmat(imagesWarped.size());
|
|
for (uint i = 0; i < imagesWarped.size(); i++)
|
|
{
|
|
imagesWarped[i].convertTo(imagesWarped32f[i], CV_32F);
|
|
masks[i].copyTo(masksUmat[i]);
|
|
}
|
|
cv::detail::VoronoiSeamFinder seamFinder;
|
|
seamFinder.find(imagesWarped32f, corners, masksUmat);
|
|
for (uint i = 0; i < imagesWarped.size(); i++)
|
|
{
|
|
masksUmat[i].copyTo(masks[i]);
|
|
masksUmat[i].release();
|
|
imagesWarped32f[i].release();
|
|
}
|
|
imagesWarped32f.clear();
|
|
masksUmat.clear();
|
|
}
|
|
|
|
|
|
if(config.illum)
|
|
{
|
|
std::cout<<"Adjusting Illumination\n";
|
|
for(uint i = 0; i < imagesWarped.size(); i++) imagesWarped[i] = illumCorrection(imagesWarped[i], 3);
|
|
}
|
|
std::cout<<"Removeing Mean\n";
|
|
removeMean(&imagesWarped);
|
|
//showImages(accumulate(images));
|
|
|
|
std::cout<<"Composing\n";
|
|
//compose
|
|
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 (uint i = 0; i < imagesWarped.size(); i++)
|
|
{
|
|
blender->feed(imagesWarped[i], masks[i], corners[i]);
|
|
}
|
|
cv::Mat result;
|
|
cv::Mat result_mask;
|
|
blender->blend(result, result_mask);
|
|
|
|
result.convertTo(result, CV_8UC3);
|
|
cv::imwrite(config.outputImage, result);
|
|
|
|
return 0;
|
|
}
|