#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "strnatcmp.h" bool verbose = false; class Config { public: std::vector 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 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 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 getSample(Config config, std::vector images, const int centralIndex, const int gridSizeX = 2, const int gridSizeY = 2) { std::vector 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 sampleImages, std::vector* cameras, float confidence = 1.2f) { std::vector features(sampleImages.size()); cv::Ptr 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 pairwiseMatches; matcher(features, pairwiseMatches); std::vector ids = cv::detail::leaveBiggestComponent(features, pairwiseMatches, confidence); if(ids.size() != sampleImages.size()) { std::cout<<"Match failure only "<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 processFileNames(std::vector 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 "<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< loadImages(std::vector fileNames) { std::vector 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 "< 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 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::max)(); float tl_vf = (std::numeric_limits::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(x), static_cast(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 > input ) { //findDeltas std::vector 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: "< 0.2 || abs(delta[i].y-avDelta.y)/avDelta.y > 0.2 ) { std::cout<<"removing x: "< fileNames = processFileNames(config.inputImages); if(fileNames.size() != (uint)config.gridSizeX*config.gridSizeY) { std::cout<<"This grid needs "< 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 sizes(images.size()); for(uint i = 0; i < images.size(); i++) sizes[i] = images[i].size(); cv::Point2f avDelta; std::vector masks(images.size()); std::vector imagesWarped(images.size()); if(config.deltaX == -1 || config.deltaY == -1) { double avFocal = 1; //Caluclate camera Intrinsics std::vector< std::vector > cameras; { std::vector samples; int bottomLeftSampleImageIndex = config.startSearch; int subsequentMatches = 0; do { std::cout<<"trying set "< 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_ avCameraMatrix = cv::Mat::zeros(3,3,CV_64F); cv::Mat_ 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" < > sampleCorners(cameras.size()); for(uint i = 0; i < cameras.size(); i++) { sampleCorners[i] = std::vector(cameras[i].size()); for(uint u = 0; u < cameras[i].size(); u++) { cv::Mat_ K; cameras[i][u].K().convertTo(K, CV_32F); cv::Mat_ 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 "< warperCreator = cv::makePtr(); cv::Ptr warper = warperCreator->create(avFocal); cv::Mat_ avCameraMatrix32f; cv::Mat_ 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 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 "< imagesWarped32f(imagesWarped.size()); std::vector 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 blender; blender = cv::detail::Blender::createDefault(cv::detail::Blender::Blender::MULTI_BAND, false); cv::detail::MultiBandBlender* mb = dynamic_cast(blender.get()); mb->setNumBands(5); blender->prepare(corners, sizes); for (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; }