Commit 336989f8 authored by Vladislav Vinogradov's avatar Vladislav Vinogradov

added image stitching module

parent d7f04f04
......@@ -36,4 +36,5 @@ add_subdirectory(haartraining)
if(NOT ANDROID)
add_subdirectory(gpu)
add_subdirectory(stitching)
endif()
project(stitching)
include_directories(
"${CMAKE_CURRENT_SOURCE_DIR}"
"${CMAKE_SOURCE_DIR}/modules/core/include"
"${CMAKE_SOURCE_DIR}/modules/imgproc/include"
"${CMAKE_SOURCE_DIR}/modules/objdetect/include"
"${CMAKE_SOURCE_DIR}/modules/ml/include"
"${CMAKE_SOURCE_DIR}/modules/highgui/include"
"${CMAKE_SOURCE_DIR}/modules/video/include"
"${CMAKE_SOURCE_DIR}/modules/features2d/include"
"${CMAKE_SOURCE_DIR}/modules/flann/include"
"${CMAKE_SOURCE_DIR}/modules/calib3d/include"
"${CMAKE_SOURCE_DIR}/modules/legacy/include"
"${CMAKE_SOURCE_DIR}/modules/imgproc/src" # for gcgraph.hpp
"${CMAKE_SOURCE_DIR}/modules/gpu/include"
)
set(stitching_libs opencv_core opencv_imgproc opencv_highgui opencv_features2d opencv_calib3d opencv_gpu)
set(stitching_files blenders.hpp blenders.cpp
focal_estimators.hpp focal_estimators.cpp
motion_estimators.hpp motion_estimators.cpp
seam_finders.hpp seam_finders.cpp
util.hpp util.cpp util_inl.hpp
warpers.hpp warpers.cpp warpers_inl.hpp
main.cpp)
set(the_target opencv_stitching)
add_executable(${the_target} ${stitching_files})
add_dependencies(${the_target} ${stitching_libs})
set_target_properties(${the_target} PROPERTIES
DEBUG_POSTFIX "${OPENCV_DEBUG_POSTFIX}"
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib/"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/"
INSTALL_NAME_DIR "${CMAKE_INSTALL_PREFIX}/lib"
OUTPUT_NAME "opencv_stitching")
target_link_libraries(${the_target} ${stitching_libs})
install(TARGETS ${the_target} RUNTIME DESTINATION bin COMPONENT main)
#include <opencv2/imgproc/imgproc.hpp>
#include "blenders.hpp"
#include "util.hpp"
using namespace std;
using namespace cv;
static const float WEIGHT_EPS = 1e-5f;
Ptr<Blender> Blender::createDefault(int type)
{
if (type == NO)
return new Blender();
if (type == FEATHER)
return new FeatherBlender();
if (type == MULTI_BAND)
return new MultiBandBlender();
CV_Error(CV_StsBadArg, "unsupported blending method");
return NULL;
}
Point Blender::operator ()(const vector<Mat> &src, const vector<Point> &corners, const vector<Mat> &masks,
Mat& dst)
{
Mat dst_mask;
return (*this)(src, corners, masks, dst, dst_mask);
}
Point Blender::operator ()(const vector<Mat> &src, const vector<Point> &corners, const vector<Mat> &masks,
Mat &dst, Mat &dst_mask)
{
Point dst_tl = blend(src, corners, masks, dst, dst_mask);
dst.setTo(Scalar::all(0), dst_mask == 0);
return dst_tl;
}
Point Blender::blend(const vector<Mat> &src, const vector<Point> &corners, const vector<Mat> &masks,
Mat &dst, Mat &dst_mask)
{
for (size_t i = 0; i < src.size(); ++i)
{
CV_Assert(src[i].type() == CV_32FC3);
CV_Assert(masks[i].type() == CV_8U);
}
const int image_type = src[0].type();
Rect dst_roi = resultRoi(src, corners);
dst.create(dst_roi.size(), image_type);
dst.setTo(Scalar::all(0));
dst_mask.create(dst_roi.size(), CV_8U);
dst_mask.setTo(Scalar::all(0));
for (size_t i = 0; i < src.size(); ++i)
{
int dx = corners[i].x - dst_roi.x;
int dy = corners[i].y - dst_roi.y;
for (int y = 0; y < src[i].rows; ++y)
{
const Point3f *src_row = src[i].ptr<Point3f>(y);
Point3f *dst_row = dst.ptr<Point3f>(dy + y);
const uchar *mask_row = masks[i].ptr<uchar>(y);
uchar *dst_mask_row = dst_mask.ptr<uchar>(dy + y);
for (int x = 0; x < src[i].cols; ++x)
{
if (mask_row[x])
dst_row[dx + x] = src_row[x];
dst_mask_row[dx + x] |= mask_row[x];
}
}
}
return dst_roi.tl();
}
Point FeatherBlender::blend(const vector<Mat> &src, const vector<Point> &corners, const vector<Mat> &masks,
Mat &dst, Mat &dst_mask)
{
vector<Mat> weights(masks.size());
for (size_t i = 0; i < weights.size(); ++i)
createWeightMap(masks[i], sharpness_, weights[i]);
Mat dst_weight;
Point dst_tl = blendLinear(src, corners, weights, dst, dst_weight);
dst_mask = dst_weight > WEIGHT_EPS;
return dst_tl;
}
Point MultiBandBlender::blend(const vector<Mat> &src, const vector<Point> &corners, const vector<Mat> &masks,
Mat &dst, Mat &dst_mask)
{
CV_Assert(src.size() == corners.size() && src.size() == masks.size());
const int num_images = src.size();
Rect dst_roi = resultRoi(src, corners);
vector<Mat> src_(num_images);
vector<Point> corners_(num_images);
vector<Mat> masks_(num_images);
// TODO avoid creating extra border
for (int i = 0; i < num_images; ++i)
{
copyMakeBorder(src[i], src_[i],
corners[i].y - dst_roi.y, dst_roi.br().y - corners[i].y - src[i].rows,
corners[i].x - dst_roi.x, dst_roi.br().x - corners[i].x - src[i].cols,
BORDER_REFLECT);
copyMakeBorder(masks[i], masks_[i],
corners[i].y - dst_roi.y, dst_roi.br().y - corners[i].y - src[i].rows,
corners[i].x - dst_roi.x, dst_roi.br().x - corners[i].x - src[i].cols,
BORDER_CONSTANT);
corners_[i] = Point(0, 0);
}
Mat weight_map;
vector<Mat> src_pyr_gauss;
vector< vector<Mat> > src_pyr_laplace(num_images);
vector< vector<Mat> > weight_pyr_gauss(num_images);
// Compute all pyramids
for (int i = 0; i < num_images; ++i)
{
createGaussPyr(src_[i], num_bands_, src_pyr_gauss);
createLaplacePyr(src_pyr_gauss, src_pyr_laplace[i]);
masks_[i].convertTo(weight_map, CV_32F, 1. / 255.);
createGaussPyr(weight_map, num_bands_, weight_pyr_gauss[i]);
}
computeResultMask(masks, corners, dst_mask);
Mat dst_level_weight;
vector<Mat> dst_pyr_laplace(num_bands_ + 1);
vector<Mat> src_pyr_slice(num_images);
vector<Mat> weight_pyr_slice(num_images);
// Blend pyramids
for (int level_id = 0; level_id <= num_bands_; ++level_id)
{
for (int i = 0; i < num_images; ++i)
{
src_pyr_slice[i] = src_pyr_laplace[i][level_id];
weight_pyr_slice[i] = weight_pyr_gauss[i][level_id];
}
blendLinear(src_pyr_slice, corners_, weight_pyr_slice,
dst_pyr_laplace[level_id], dst_level_weight);
}
restoreImageFromLaplacePyr(dst_pyr_laplace);
dst = dst_pyr_laplace[0];
return dst_roi.tl();
}
//////////////////////////////////////////////////////////////////////////////
// Auxiliary functions
Rect resultRoi(const vector<Mat> &src, const vector<Point> &corners)
{
Point tl(numeric_limits<int>::max(), numeric_limits<int>::max());
Point br(numeric_limits<int>::min(), numeric_limits<int>::min());
CV_Assert(src.size() == corners.size());
for (size_t i = 0; i < src.size(); ++i)
{
tl.x = min(tl.x, corners[i].x);
tl.y = min(tl.y, corners[i].y);
br.x = max(br.x, corners[i].x + src[i].cols);
br.y = max(br.y, corners[i].y + src[i].rows);
}
return Rect(tl, br);
}
Point computeResultMask(const vector<Mat> &masks, const vector<Point> &corners, Mat &dst_mask)
{
Rect dst_roi = resultRoi(masks, corners);
dst_mask.create(dst_roi.size(), CV_8U);
dst_mask.setTo(Scalar::all(0));
for (size_t i = 0; i < masks.size(); ++i)
{
int dx = corners[i].x - dst_roi.x;
int dy = corners[i].y - dst_roi.y;
for (int y = 0; y < masks[i].rows; ++y)
{
const uchar *mask_row = masks[i].ptr<uchar>(y);
uchar *dst_mask_row = dst_mask.ptr<uchar>(dy + y);
for (int x = 0; x < masks[i].cols; ++x)
dst_mask_row[dx + x] |= mask_row[x];
}
}
return dst_roi.tl();
}
Point blendLinear(const vector<Mat> &src, const vector<Point> &corners, const vector<Mat> &weights,
Mat &dst, Mat& dst_weight)
{
for (size_t i = 0; i < src.size(); ++i)
{
CV_Assert(src[i].type() == CV_32FC3);
CV_Assert(weights[i].type() == CV_32F);
}
const int image_type = src[0].type();
Rect dst_roi = resultRoi(src, corners);
dst.create(dst_roi.size(), image_type);
dst.setTo(Scalar::all(0));
dst_weight.create(dst_roi.size(), CV_32F);
dst_weight.setTo(Scalar::all(0));
// Compute colors sums and weights
for (size_t i = 0; i < src.size(); ++i)
{
int dx = corners[i].x - dst_roi.x;
int dy = corners[i].y - dst_roi.y;
for (int y = 0; y < src[i].rows; ++y)
{
const Point3f *src_row = src[i].ptr<Point3f>(y);
Point3f *dst_row = dst.ptr<Point3f>(dy + y);
const float *weight_row = weights[i].ptr<float>(y);
float *dst_weight_row = dst_weight.ptr<float>(dy + y);
for (int x = 0; x < src[i].cols; ++x)
{
dst_row[dx + x] += src_row[x] * weight_row[x];
dst_weight_row[dx + x] += weight_row[x];
}
}
}
// Normalize sums
for (int y = 0; y < dst.rows; ++y)
{
Point3f *dst_row = dst.ptr<Point3f>(y);
float *dst_weight_row = dst_weight.ptr<float>(y);
for (int x = 0; x < dst.cols; ++x)
{
dst_weight_row[x] += WEIGHT_EPS;
dst_row[x] *= 1.f / dst_weight_row[x];
}
}
return dst_roi.tl();
}
void createWeightMap(const Mat &mask, float sharpness, Mat &weight)
{
CV_Assert(mask.type() == CV_8U);
distanceTransform(mask, weight, CV_DIST_L1, 3);
threshold(weight * sharpness, weight, 1.f, 1.f, THRESH_TRUNC);
}
void createGaussPyr(const Mat &img, int num_layers, vector<Mat> &pyr)
{
pyr.resize(num_layers + 1);
pyr[0] = img.clone();
for (int i = 0; i < num_layers; ++i)
pyrDown(pyr[i], pyr[i + 1]);
}
void createLaplacePyr(const vector<Mat> &pyr_gauss, vector<Mat> &pyr_laplace)
{
if (pyr_gauss.size() == 0)
return;
pyr_laplace.resize(pyr_gauss.size());
Mat tmp;
for (size_t i = 0; i < pyr_laplace.size() - 1; ++i)
{
pyrUp(pyr_gauss[i + 1], tmp, pyr_gauss[i].size());
pyr_laplace[i] = pyr_gauss[i] - tmp;
}
pyr_laplace[pyr_laplace.size() - 1] = pyr_gauss[pyr_laplace.size() - 1].clone();
}
void restoreImageFromLaplacePyr(vector<Mat> &pyr)
{
if (pyr.size() == 0)
return;
Mat tmp;
for (size_t i = pyr.size() - 1; i > 0; --i)
{
pyrUp(pyr[i], tmp, pyr[i - 1].size());
pyr[i - 1] += tmp;
}
}
#ifndef _OPENCV_BLENDERS_HPP_
#define _OPENCV_BLENDERS_HPP_
#include <vector>
#include <opencv2/core/core.hpp>
// Simple blender which puts one image over another
class Blender
{
public:
enum { NO, FEATHER, MULTI_BAND };
static cv::Ptr<Blender> createDefault(int type);
cv::Point operator ()(const std::vector<cv::Mat> &src, const std::vector<cv::Point> &corners, const std::vector<cv::Mat> &masks,
cv::Mat& dst);
cv::Point operator ()(const std::vector<cv::Mat> &src, const std::vector<cv::Point> &corners, const std::vector<cv::Mat> &masks,
cv::Mat& dst, cv::Mat& dst_mask);
protected:
virtual cv::Point blend(const std::vector<cv::Mat> &src, const std::vector<cv::Point> &corners, const std::vector<cv::Mat> &masks,
cv::Mat& dst, cv::Mat& dst_mask);
};
class FeatherBlender : public Blender
{
public:
FeatherBlender(float sharpness = 0.02f) : sharpness_(sharpness) {}
private:
cv::Point blend(const std::vector<cv::Mat> &src, const std::vector<cv::Point> &corners, const std::vector<cv::Mat> &masks,
cv::Mat &dst, cv::Mat &dst_mask);
float sharpness_;
};
class MultiBandBlender : public Blender
{
public:
MultiBandBlender(int num_bands = 10) : num_bands_(num_bands) {}
private:
cv::Point blend(const std::vector<cv::Mat> &src, const std::vector<cv::Point> &corners, const std::vector<cv::Mat> &masks,
cv::Mat& dst, cv::Mat& dst_mask);
int num_bands_;
};
//////////////////////////////////////////////////////////////////////////////
// Auxiliary functions
cv::Rect resultRoi(const std::vector<cv::Mat> &src, const std::vector<cv::Point> &corners);
cv::Point computeResultMask(const std::vector<cv::Mat> &masks, const std::vector<cv::Point> &corners, cv::Mat &mask);
cv::Point blendLinear(const std::vector<cv::Mat> &src, const std::vector<cv::Point> &corners, const std::vector<cv::Mat> &weights,
cv::Mat& dst, cv::Mat& dst_weight);
void createWeightMap(const cv::Mat& mask, float sharpness, cv::Mat& weight);
void createGaussPyr(const cv::Mat& img, int num_layers, std::vector<cv::Mat>& pyr);
void createLaplacePyr(const std::vector<cv::Mat>& pyr_gauss, std::vector<cv::Mat>& pyr_laplace);
// Restores source image in-place. Result will be in pyr[0].
void restoreImageFromLaplacePyr(std::vector<cv::Mat>& pyr);
#endif // _OPENCV_BLENDERS_HPP_
#include "focal_estimators.hpp"
#include "util.hpp"
using namespace std;
using namespace cv;
void focalsFromHomography(const Mat& H, double &f0, double &f1, bool &f0_ok, bool &f1_ok)
{
CV_Assert(H.type() == CV_64F && H.size() == Size(3, 3));
const double h[9] =
{
H.at<double>(0, 0), H.at<double>(0, 1), H.at<double>(0, 2),
H.at<double>(1, 0), H.at<double>(1, 1), H.at<double>(1, 2),
H.at<double>(2, 0), H.at<double>(2, 1), H.at<double>(2, 2)
};
f1_ok = true;
double denom1 = h[6] * h[7];
double denom2 = (h[7] - h[6]) * (h[7] + h[6]);
if (max(abs(denom1), abs(denom2)) < 1e-5)
f1_ok = false;
else
{
double val1 = -(h[0] * h[1] + h[3] * h[4]) / denom1;
double val2 = (h[0] * h[0] + h[3] * h[3] - h[1] * h[1] - h[4] * h[4]) / denom2;
if (val1 < val2)
swap(val1, val2);
if (val1 > 0 && val2 > 0)
f1 = sqrt(abs(denom1) > abs(denom2) ? val1 : val2);
else if (val1 > 0)
f1 = sqrt(val1);
else
f1_ok = false;
}
f0_ok = true;
denom1 = h[0] * h[3] + h[1] * h[4];
denom2 = h[0] * h[0] + h[1] * h[1] - h[3] * h[3] - h[4] * h[4];
if (max(abs(denom1), abs(denom2)) < 1e-5)
f0_ok = false;
else
{
double val1 = -h[2] * h[5] / denom1;
double val2 = (h[5] * h[5] - h[2] * h[2]) / denom2;
if (val1 < val2)
swap(val1, val2);
if (val1 > 0 && val2 > 0)
f0 = sqrt(abs(denom1) > abs(denom2) ? val1 : val2);
else if (val1 > 0)
f0 = sqrt(val1);
else
f0_ok = false;
}
}
bool focalsFromFundamental(const Mat &F, double &f0, double &f1)
{
CV_Assert(F.type() == CV_64F);
CV_Assert(F.size() == Size(3, 3));
Mat Ft = F.t();
Mat k = Mat::zeros(3, 1, CV_64F);
k.at<double>(2, 0) = 1.f;
// 1. Compute quantities
double a = normL2sq(F*Ft*k) / normL2sq(Ft*k);
double b = normL2sq(Ft*F*k) / normL2sq(F*k);
double c = sqr(k.dot(F*k)) / (normL2sq(Ft*k) * normL2sq(F*k));
double d = k.dot(F*Ft*F*k) / k.dot(F*k);
double A = 1/c + a - 2*d;
double B = 1/c + b - 2*d;
double P = 2*(1/c - 2*d + 0.5*normL2sq(F));
double Q = -(A + B)/c + 0.5*(normL2sq(F*Ft) - 0.5*sqr(normL2sq(F)));
// 2. Solve quadratic equation Z*Z*a_ + Z*b_ + c_ = 0
double a_ = 1 + c*P;
double b_ = -(c*P*P + 2*P + 4*c*Q);
double c_ = P*P + 4*c*P*Q + 12*A*B;
double D = b_*b_ - 4*a_*c_;
if (abs(D) < 1e-5)
D = 0;
else if (D < 0)
return false;
double D_sqrt = sqrt(D);
double Z0 = (-b_ - D_sqrt) / (2*a_);
double Z1 = (-b_ + D_sqrt) / (2*a_);
// 3. Choose solution
double w0 = abs(Z0*Z0*Z0 - 3*P*Z0*Z0 + 2*(P*P + 2*Q)*Z0 - 4*(P*Q + 4*A*B/c));
double w1 = abs(Z1*Z1*Z1 - 3*P*Z1*Z1 + 2*(P*P + 2*Q)*Z1 - 4*(P*Q + 4*A*B/c));
double Z = Z0;
if (w1 < w0)
Z = Z1;
// 4.
double X = -1/c*(1 + 2*B/(Z - P));
double Y = -1/c*(1 + 2*A/(Z - P));
// 5. Compute focal lengths
f0 = 1/sqrt(1 + X/normL2sq(Ft*k));
f1 = 1/sqrt(1 + Y/normL2sq(F*k));
return true;
}
#ifndef _OPENCV_FOCAL_ESTIMATORS_HPP_
#define _OPENCV_FOCAL_ESTIMATORS_HPP_
#include <opencv2/core/core.hpp>
// See "Construction of Panoramic Image Mosaics with Global and Local Alignment"
// by Heung-Yeung Shum and Richard Szeliski.
void focalsFromHomography(const cv::Mat &H, double &f0, double &f1, bool &f0_ok, bool &f1_ok);
bool focalsFromFundamental(const cv::Mat &F, double &f0, double &f1);
#endif // _OPENCV_FOCAL_ESTIMATORS_HPP_
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include "util.hpp"
#include "warpers.hpp"
#include "blenders.hpp"
#include "seam_finders.hpp"
#include "motion_estimators.hpp"
using namespace std;
using namespace cv;
void printHelp()
{
cout << "Rotation model stitcher.\n"
<< "Usage: stitch img1 img2 [...imgN]\n"
<< "\t[--matchconf <0.0-1.0>]\n"
<< "\t[--ba (ray_space|focal_ray_space)]\n"
<< "\t[--warp (plane|cylindrical|spherical)]\n"
<< "\t[--seam (no|voronoi|graphcut)]\n"
<< "\t[--blend (no|feather|multiband)]\n"
<< "\t[--output <result_img>]\n";
}
int main(int argc, char* argv[])
{
cv::setBreakOnError(true);
vector<Mat> images;
string result_name = "result.png";
int ba_space = BundleAdjuster::RAY_SPACE;
int warp_type = Warper::SPHERICAL;
bool user_match_conf = false;
float match_conf;
int seam_find_type = SeamFinder::GRAPH_CUT;
int blend_type = Blender::MULTI_BAND;
if (argc == 1)
{
printHelp();
return 0;
}
for (int i = 1; i < argc; ++i)
{
if (string(argv[i]) == "--result")
{
result_name = argv[i + 1];
i++;
}
else if (string(argv[i]) == "--matchconf")
{
user_match_conf = true;
match_conf = static_cast<float>(atof(string(argv[i + 1]).c_str()));
i++;
}
else if (string(argv[i]) == "--ba")
{
if (string(argv[i + 1]) == "ray_space")
ba_space = BundleAdjuster::RAY_SPACE;
else if (string(argv[i + 1]) == "focal_ray_space")
ba_space = BundleAdjuster::FOCAL_RAY_SPACE;
else
{
cout << "Bad bundle adjustment space\n";
return -1;
}
i++;
}
else if (string(argv[i]) == "--warp")
{
if (string(argv[i + 1]) == "plane")
warp_type = Warper::PLANE;
else if (string(argv[i + 1]) == "cylindrical")
warp_type = Warper::CYLINDRICAL;
else if (string(argv[i + 1]) == "spherical")
warp_type = Warper::SPHERICAL;
else
{
cout << "Bad warping method\n";
return -1;
}
i++;
}
else if (string(argv[i]) == "--seam")
{
if (string(argv[i + 1]) == "no")
seam_find_type = SeamFinder::NO;
else if (string(argv[i + 1]) == "voronoi")
seam_find_type = SeamFinder::VORONOI;
else if (string(argv[i + 1]) == "graphcut")
seam_find_type = SeamFinder::GRAPH_CUT;
else
{
cout << "Bad seam finding method\n";
return -1;
}
i++;
}
else if (string(argv[i]) == "--blend")
{
if (string(argv[i + 1]) == "no")
blend_type = Blender::NO;
else if (string(argv[i + 1]) == "feather")
blend_type = Blender::FEATHER;
else if (string(argv[i + 1]) == "multiband")
blend_type = Blender::MULTI_BAND;
else
{
cout << "Bad blending method\n";
return -1;
}
i++;
}
else if (string(argv[i]) == "--output")
{
result_name = argv[i + 1];
i++;
}
else
{
Mat img = imread(argv[i]);
if (img.empty())
{
cout << "Can't open image " << argv[i] << endl;
return -1;
}
images.push_back(img);
}
}
const int num_images = static_cast<int>(images.size());
if (num_images < 2)
{
cout << "Need more images\n";
return -1;
}
LOGLN("Finding features...");
vector<ImageFeatures> features;
SurfFeaturesFinder finder;
finder(images, features);
LOGLN("Pairwise matching...");
vector<MatchesInfo> pairwise_matches;
BestOf2NearestMatcher matcher;
if (user_match_conf)
matcher = BestOf2NearestMatcher(true, match_conf);
matcher(images, features, pairwise_matches);
LOGLN("Estimating rotations...");
HomographyBasedEstimator estimator;
vector<CameraParams> cameras;
estimator(images, features, pairwise_matches, cameras);
for (size_t i = 0; i < cameras.size(); ++i)
{
Mat R;
cameras[i].M.convertTo(R, CV_32F);
cameras[i].M = R;
LOGLN("Initial focal length " << i << ": " << cameras[i].focal);
}
LOGLN("Bundle adjustment...");
BundleAdjuster adjuster(ba_space);
adjuster(images, features, pairwise_matches, cameras);
// Find median focal length
vector<double> focals;
for (size_t i = 0; i < cameras.size(); ++i)
{
LOGLN("Camera focal length " << i << ": " << cameras[i].focal);
focals.push_back(cameras[i].focal);
}
nth_element(focals.begin(), focals.end(), focals.begin() + focals.size() / 2);
float camera_focal = focals[focals.size() / 2];
vector<Mat> masks(num_images);
for (int i = 0; i < num_images; ++i)
{
masks[i].create(images[i].size(), CV_8U);
masks[i].setTo(Scalar::all(255));
}
vector<Point> corners(num_images);
vector<Mat> masks_warped(num_images);
vector<Mat> images_warped(num_images);
LOGLN("Warping images...");
Ptr<Warper> warper = Warper::createByCameraFocal(camera_focal, warp_type);
for (int i = 0; i < num_images; ++i)
{
corners[i] = (*warper)(images[i], cameras[i].focal, cameras[i].M, images_warped[i]);
(*warper)(masks[i], cameras[i].focal, cameras[i].M, masks_warped[i], INTER_NEAREST, BORDER_CONSTANT);
}
vector<Mat> images_f(num_images);
for (int i = 0; i < num_images; ++i)
images_warped[i].convertTo(images_f[i], CV_32F);
LOGLN("Finding seams...");
Ptr<SeamFinder> seam_finder = SeamFinder::createDefault(seam_find_type);
(*seam_finder)(images_f, corners, masks_warped);
LOGLN("Blending images...");
Mat result, result_mask;
Ptr<Blender> blender = Blender::createDefault(blend_type);
(*blender)(images_f, corners, masks_warped, result, result_mask);
imwrite(result_name, result);
LOGLN("Finished");
return 0;
}
This diff is collapsed.
#ifndef _OPENCV_MOTION_ESTIMATORS_HPP_
#define _OPENCV_MOTION_ESTIMATORS_HPP_
#include <vector>
#include <opencv2/core/core.hpp>
#include <opencv2/features2d/features2d.hpp>
#include "util.hpp"
struct ImageFeatures
{
std::vector<cv::KeyPoint> keypoints;
cv::Mat descriptors;
};
class FeaturesFinder
{
public:
virtual ~FeaturesFinder() {}
void operator ()(const std::vector<cv::Mat> &images, std::vector<ImageFeatures> &features) { find(images, features); }
protected:
virtual void find(const std::vector<cv::Mat> &images, std::vector<ImageFeatures> &features) = 0;
};
class SurfFeaturesFinder : public FeaturesFinder
{
public:
explicit SurfFeaturesFinder(bool gpu_hint = true);
protected:
void find(const std::vector<cv::Mat> &images, std::vector<ImageFeatures> &features);
cv::Ptr<FeaturesFinder> impl_;
};
struct MatchesInfo
{
MatchesInfo();
MatchesInfo(const MatchesInfo &other);
const MatchesInfo& operator =(const MatchesInfo &other);
int src_img_idx, dst_img_idx; // Optional images indices
std::vector<cv::DMatch> matches;
int num_inliers; // Number of geometrically consistent matches
cv::Mat H; // Homography
};
class FeaturesMatcher
{
public:
virtual ~FeaturesMatcher() {}
void operator ()(const cv::Mat &img1, const ImageFeatures &features1, const cv::Mat &img2, const ImageFeatures &features2,
MatchesInfo& matches_info) { match(img1, features1, img2, features2, matches_info); }
void operator ()(const std::vector<cv::Mat> &images, const std::vector<ImageFeatures> &features,
std::vector<MatchesInfo> &pairwise_matches);
protected:
virtual void match(const cv::Mat &img1, const ImageFeatures &features1, const cv::Mat &img2, const ImageFeatures &features2,
MatchesInfo& matches_info) = 0;
};
class BestOf2NearestMatcher : public FeaturesMatcher
{
public:
explicit BestOf2NearestMatcher(bool gpu_hint = true, float match_conf = 0.55f, int num_matches_thresh1 = 5, int num_matches_thresh2 = 5);
protected:
void match(const cv::Mat &img1, const ImageFeatures &features1, const cv::Mat &img2, const ImageFeatures &features2,
MatchesInfo &matches_info);
int num_matches_thresh1_;
int num_matches_thresh2_;
cv::Ptr<FeaturesMatcher> impl_;
};
struct CameraParams
{
CameraParams();
CameraParams(const CameraParams& other);
const CameraParams& operator =(const CameraParams& other);
double focal;
cv::Mat M, t;
};
class Estimator
{
public:
void operator ()(const std::vector<cv::Mat> &images, const std::vector<ImageFeatures> &features,
const std::vector<MatchesInfo> &pairwise_matches, std::vector<CameraParams> &cameras)
{
estimate(images, features, pairwise_matches, cameras);
}
protected:
virtual void estimate(const std::vector<cv::Mat> &images, const std::vector<ImageFeatures> &features,
const std::vector<MatchesInfo> &pairwise_matches, std::vector<CameraParams> &cameras) = 0;
};
class HomographyBasedEstimator : public Estimator
{
public:
HomographyBasedEstimator() : is_focals_estimated_(false) {}
bool isFocalsEstimated() const { return is_focals_estimated_; }
private:
void estimate(const std::vector<cv::Mat> &images, const std::vector<ImageFeatures> &features,
const std::vector<MatchesInfo> &pairwise_matches, std::vector<CameraParams> &cameras);
bool is_focals_estimated_;
};
class BundleAdjuster : public Estimator
{
public:
enum { RAY_SPACE, FOCAL_RAY_SPACE };
BundleAdjuster(int cost_space = FOCAL_RAY_SPACE) : cost_space_(cost_space) {}
private:
void estimate(const std::vector<cv::Mat> &images, const std::vector<ImageFeatures> &features,
const std::vector<MatchesInfo> &pairwise_matches, std::vector<CameraParams> &cameras);
void calcError(cv::Mat &err);
void calcJacobian();
int num_images_;
int total_num_matches_;
const cv::Mat *images_;
const ImageFeatures *features_;
const MatchesInfo *pairwise_matches_;
cv::Mat cameras_;
std::vector<std::pair<int,int> > edges_;
int cost_space_;
cv::Mat err_, err1_, err2_;
cv::Mat J_;
};
//////////////////////////////////////////////////////////////////////////////
// Auxiliary functions
void findMaxSpanningTree(int num_images, const std::vector<MatchesInfo> &pairwise_matches,
Graph &span_tree, std::vector<int> &centers);
#endif // _OPENCV_MOTION_ESTIMATORS_HPP_
#include <opencv2/imgproc/imgproc.hpp>
#include <gcgraph.hpp>
#include "seam_finders.hpp"
#include "util.hpp"
using namespace std;
using namespace cv;
Ptr<SeamFinder> SeamFinder::createDefault(int type)
{
if (type == NO)
return new NoSeamFinder();
if (type == VORONOI)
return new VoronoiSeamFinder();
if (type == GRAPH_CUT)
return new GraphCutSeamFinder();
CV_Error(CV_StsBadArg, "unsupported seam finding method");
return NULL;
}
void SeamFinder::operator ()(const vector<Mat> &src, const vector<Point> &corners,
vector<Mat> &masks)
{
find(src, corners, masks);
}
void PairwiseSeamFinder::find(const vector<Mat> &src, const vector<Point> &corners,
vector<Mat> &masks)
{
if (src.size() == 0)
return;
for (size_t i = 0; i < src.size() - 1; ++i)
{
for (size_t j = i + 1; j < src.size(); ++j)
{
int x_min = max(corners[i].x, corners[j].x);
int x_max = min(corners[i].x + src[i].cols - 1, corners[j].x + src[j].cols - 1);
int y_min = max(corners[i].y, corners[j].y);
int y_max = min(corners[i].y + src[i].rows - 1, corners[j].y + src[j].rows - 1);
if (x_max >= x_min && y_max >= y_min)
findInPair(src[i], src[j], corners[i], corners[j],
Rect(x_min, y_min, x_max - x_min + 1, y_max - y_min + 1),
masks[i], masks[j]);
}
}
}
void VoronoiSeamFinder::findInPair(const Mat &img1, const Mat &img2, Point tl1, Point tl2,
Rect roi, Mat &mask1, Mat &mask2)
{
const int gap = 10;
Mat submask1(roi.height + 2 * gap, roi.width + 2 * gap, CV_8U);
Mat submask2(roi.height + 2 * gap, roi.width + 2 * gap, CV_8U);
// Cut submasks with some gap
for (int y = -gap; y < roi.height + gap; ++y)
{
for (int x = -gap; x < roi.width + gap; ++x)
{
int y1 = roi.y - tl1.y + y;
int x1 = roi.x - tl1.x + x;
if (y1 >= 0 && x1 >= 0 && y1 < img1.rows && x1 < img1.cols)
submask1.at<uchar>(y + gap, x + gap) = mask1.at<uchar>(y1, x1);
else
submask1.at<uchar>(y + gap, x + gap) = 0;
int y2 = roi.y - tl2.y + y;
int x2 = roi.x - tl2.x + x;
if (y2 >= 0 && x2 >= 0 && y2 < img2.rows && x2 < img2.cols)
submask2.at<uchar>(y + gap, x + gap) = mask2.at<uchar>(y2, x2);
else
submask2.at<uchar>(y + gap, x + gap) = 0;
}
}
Mat collision = (submask1 != 0) & (submask2 != 0);
Mat unique1 = submask1.clone(); unique1.setTo(0, collision);
Mat unique2 = submask2.clone(); unique2.setTo(0, collision);
Mat dist1, dist2;
distanceTransform(unique1 == 0, dist1, CV_DIST_L1, 3);
distanceTransform(unique2 == 0, dist2, CV_DIST_L1, 3);
Mat seam = dist1 < dist2;
for (int y = 0; y < roi.height; ++y)
{
for (int x = 0; x < roi.width; ++x)
{
if (seam.at<uchar>(y + gap, x + gap))
mask2.at<uchar>(roi.y - tl2.y + y, roi.x - tl2.x + x) = 0;
else
mask1.at<uchar>(roi.y - tl1.y + y, roi.x - tl1.x + x) = 0;
}
}
}
class GraphCutSeamFinder::Impl
{
public:
Impl(int cost_type, float terminal_cost, float bad_region_penalty)
: cost_type_(cost_type), terminal_cost_(terminal_cost), bad_region_penalty_(bad_region_penalty) {}
void findInPair(const Mat &img1, const Mat &img2, Point tl1, Point tl2,
Rect roi, Mat &mask1, Mat &mask2);
private:
void setGraphWeightsColor(const Mat &img1, const Mat &img2, const Mat &mask1, const Mat &mask2,
GCGraph<float> &graph);
int cost_type_;
float terminal_cost_;
float bad_region_penalty_;
};
void GraphCutSeamFinder::Impl::setGraphWeightsColor(const Mat &img1, const Mat &img2, const Mat &mask1, const Mat &mask2,
GCGraph<float> &graph)
{
const Size img_size = img1.size();
// Set terminal weights
for (int y = 0; y < img_size.height; ++y)
{
for (int x = 0; x < img_size.width; ++x)
{
int v = graph.addVtx();
graph.addTermWeights(v, mask1.at<uchar>(y, x) ? terminal_cost_ : 0.f,
mask2.at<uchar>(y, x) ? terminal_cost_ : 0.f);
}
}
const float weight_eps = 1e-3f;
// Set regular edge weights
for (int y = 0; y < img_size.height; ++y)
{
for (int x = 0; x < img_size.width; ++x)
{
int v = y * img_size.width + x;
if (x < img_size.width - 1)
{
float weight = normL2(img1.at<Point3f>(y, x), img2.at<Point3f>(y, x)) +
normL2(img1.at<Point3f>(y, x + 1), img2.at<Point3f>(y, x + 1)) +
weight_eps;
if (!mask1.at<uchar>(y, x) || !mask1.at<uchar>(y, x + 1) ||
!mask2.at<uchar>(y, x) || !mask2.at<uchar>(y, x + 1))
weight += bad_region_penalty_;
graph.addEdges(v, v + 1, weight, weight);
}
if (y < img_size.height - 1)
{
float weight = normL2(img1.at<Point3f>(y, x), img2.at<Point3f>(y, x)) +
normL2(img1.at<Point3f>(y + 1, x), img2.at<Point3f>(y + 1, x)) +
weight_eps;
if (!mask1.at<uchar>(y, x) || !mask1.at<uchar>(y + 1, x) ||
!mask2.at<uchar>(y, x) || !mask2.at<uchar>(y + 1, x))
weight += bad_region_penalty_;
graph.addEdges(v, v + img_size.width, weight, weight);
}
}
}
}
void GraphCutSeamFinder::Impl::findInPair(const Mat &img1, const Mat &img2, Point tl1, Point tl2,
Rect roi, Mat &mask1, Mat &mask2)
{
const int gap = 10;
Mat subimg1(roi.height + 2 * gap, roi.width + 2 * gap, CV_32FC3);
Mat subimg2(roi.height + 2 * gap, roi.width + 2 * gap, CV_32FC3);
Mat submask1(roi.height + 2 * gap, roi.width + 2 * gap, CV_8U);
Mat submask2(roi.height + 2 * gap, roi.width + 2 * gap, CV_8U);
// Cut subimages and submasks with some gap
for (int y = -gap; y < roi.height + gap; ++y)
{
for (int x = -gap; x < roi.width + gap; ++x)
{
int y1 = roi.y - tl1.y + y;
int x1 = roi.x - tl1.x + x;
if (y1 >= 0 && x1 >= 0 && y1 < img1.rows && x1 < img1.cols)
{
subimg1.at<Point3f>(y + gap, x + gap) = img1.at<Point3f>(y1, x1);
submask1.at<uchar>(y + gap, x + gap) = mask1.at<uchar>(y1, x1);
}
else
{
subimg1.at<Point3f>(y + gap, x + gap) = Point3f(0, 0, 0);
submask1.at<uchar>(y + gap, x + gap) = 0;
}
int y2 = roi.y - tl2.y + y;
int x2 = roi.x - tl2.x + x;
if (y2 >= 0 && x2 >= 0 && y2 < img2.rows && x2 < img2.cols)
{
subimg2.at<Point3f>(y + gap, x + gap) = img2.at<Point3f>(y2, x2);
submask2.at<uchar>(y + gap, x + gap) = mask2.at<uchar>(y2, x2);
}
else
{
subimg2.at<Point3f>(y + gap, x + gap) = Point3f(0, 0, 0);
submask2.at<uchar>(y + gap, x + gap) = 0;
}
}
}
const int vertex_count = (roi.height + 2 * gap) * (roi.width + 2 * gap);
const int edge_count = (roi.height - 1 + 2 * gap) * (roi.width + 2 * gap) +
(roi.width - 1 + 2 * gap) * (roi.height + 2 * gap);
GCGraph<float> graph(vertex_count, edge_count);
switch (cost_type_)
{
case GraphCutSeamFinder::COST_COLOR:
setGraphWeightsColor(subimg1, subimg2, submask1, submask2, graph);
break;
default:
CV_Error(CV_StsBadArg, "unsupported pixel similarity measure");
}
graph.maxFlow();
for (int y = 0; y < roi.height; ++y)
{
for (int x = 0; x < roi.width; ++x)
{
if (graph.inSourceSegment((y + gap) * (roi.width + 2 * gap) + x + gap))
mask2.at<uchar>(roi.y - tl2.y + y, roi.x - tl2.x + x) = 0;
else
mask1.at<uchar>(roi.y - tl1.y + y, roi.x - tl1.x + x) = 0;
}
}
}
GraphCutSeamFinder::GraphCutSeamFinder(int cost_type, float terminal_cost, float bad_region_penalty)
: impl_(new Impl(cost_type, terminal_cost, bad_region_penalty)) {}
void GraphCutSeamFinder::findInPair(const Mat &img1, const Mat &img2, Point tl1, Point tl2,
Rect roi, Mat &mask1, Mat &mask2)
{
impl_->findInPair(img1, img2, tl1, tl2, roi, mask1, mask2);
}
#ifndef _OPENCV_SEAM_FINDERS_HPP_
#define _OPENCV_SEAM_FINDERS_HPP_
#include <vector>
#include <opencv2/core/core.hpp>
class SeamFinder
{
public:
enum { NO, VORONOI, GRAPH_CUT };
static cv::Ptr<SeamFinder> createDefault(int type);
virtual ~SeamFinder() {}
void operator ()(const std::vector<cv::Mat> &src, const std::vector<cv::Point> &corners,
std::vector<cv::Mat> &masks);
protected:
virtual void find(const std::vector<cv::Mat> &src, const std::vector<cv::Point> &corners,
std::vector<cv::Mat> &masks) = 0;
};
class NoSeamFinder : public SeamFinder
{
protected:
void find(const std::vector<cv::Mat>&, const std::vector<cv::Point>&, std::vector<cv::Mat>&) {}
};
class PairwiseSeamFinder : public SeamFinder
{
protected:
void find(const std::vector<cv::Mat> &src, const std::vector<cv::Point> &corners,
std::vector<cv::Mat> &masks);
virtual void findInPair(const cv::Mat &img1, const cv::Mat &img2, cv::Point tl1, cv::Point tl2,
cv::Rect roi, cv::Mat &mask1, cv::Mat &mask2) = 0;
};
class VoronoiSeamFinder : public PairwiseSeamFinder
{
private:
void findInPair(const cv::Mat &img1, const cv::Mat &img2, cv::Point tl1, cv::Point tl2,
cv::Rect roi, cv::Mat &mask1, cv::Mat &mask2);
};
class GraphCutSeamFinder : public PairwiseSeamFinder
{
public:
// TODO add COST_COLOR_GRAD support
enum { COST_COLOR/*, COST_COLOR_GRAD*/ };
GraphCutSeamFinder(int cost_type = COST_COLOR, float terminal_cost = 10000.f,
float bad_region_penalty = 1000.f);
private:
void findInPair(const cv::Mat &img1, const cv::Mat &img2, cv::Point tl1, cv::Point tl2,
cv::Rect roi, cv::Mat &mask1, cv::Mat &mask2);
class Impl;
cv::Ptr<Impl> impl_;
};
#endif // _OPENCV_SEAM_FINDERS_HPP_
#include "util.hpp"
using namespace std;
using namespace cv;
void DjSets::create(int n) {
rank_.assign(n, 0);
size.assign(n, 1);
parent.resize(n);
for (int i = 0; i < n; ++i)
parent[i] = i;
}
int DjSets::find(int elem) {
int set = elem;
while (set != parent[set])
set = parent[set];
int next;
while (elem != parent[elem]) {
next = parent[elem];
parent[elem] = set;
elem = next;
}
return set;
}
int DjSets::merge(int set1, int set2) {
if (rank_[set1] < rank_[set2]) {
parent[set1] = set2;
size[set2] += size[set1];
return set2;
}
if (rank_[set2] < rank_[set1]) {
parent[set2] = set1;
size[set1] += size[set2];
return set1;
}
parent[set1] = set2;
rank_[set2]++;
size[set2] += size[set1];
return set2;
}
void Graph::addEdge(int from, int to, float weight)
{
edges_[from].push_back(GraphEdge(from, to, weight));
}
#ifndef _OPENCV_STITCHING_UTIL_HPP_
#define _OPENCV_STITCHING_UTIL_HPP_
#include <vector>
#include <list>
#include <opencv2/core/core.hpp>
#define ENABLE_LOG 1
#if ENABLE_LOG
#include <iostream>
#define LOG(msg) std::cout << msg;
#else
#define LOG(msg)
#endif
#define LOGLN(msg) LOG(msg << std::endl)
class DjSets
{
public:
DjSets(int n = 0) { create(n); }
void create(int n);
int find(int elem);
int merge(int set1, int set2);
std::vector<int> parent;
std::vector<int> size;
private:
std::vector<int> rank_;
};
struct GraphEdge
{
GraphEdge(int from, int to, float weight)
: from(from), to(to), weight(weight) {}
bool operator <(const GraphEdge& other) const { return weight < other.weight; }
bool operator >(const GraphEdge& other) const { return weight > other.weight; }
int from, to;
float weight;
};
class Graph
{
public:
Graph(int num_vertices = 0) { create(num_vertices); }
void create(int num_vertices) { edges_.assign(num_vertices, std::list<GraphEdge>()); }
int numVertices() const { return static_cast<int>(edges_.size()); }
void addEdge(int from, int to, float weight);
template <typename B>
B forEach(B body) const;
template <typename B>
B walkBreadthFirst(int from, B body) const;
private:
std::vector< std::list<GraphEdge> > edges_;
};
#include "util_inl.hpp"
#endif // _OPENCV_STITCHING_UTIL_HPP_
#ifndef _OPENCV_STITCHING_UTIL_INL_HPP_
#define _OPENCV_STITCHING_UTIL_INL_HPP_
#include <queue>
#include "util.hpp" // Make your IDE see declarations
template <typename B>
B Graph::forEach(B body) const
{
for (int i = 0; i < numVertices(); ++i)
{
std::list<GraphEdge>::const_iterator edge = edges_[i].begin();
for (; edge != edges_[i].end(); ++edge)
body(*edge);
}
return body;
}
template <typename B>
B Graph::walkBreadthFirst(int from, B body) const
{
std::vector<bool> was(numVertices(), false);
std::queue<int> vertices;
was[from] = true;
vertices.push(from);
while (!vertices.empty())
{
int vertex = vertices.front();
vertices.pop();
std::list<GraphEdge>::const_iterator edge = edges_[vertex].begin();
for (; edge != edges_[vertex].end(); ++edge)
{
if (!was[edge->to])
{
body(*edge);
was[edge->to] = true;
vertices.push(edge->to);
}
}
}
return body;
}
//////////////////////////////////////////////////////////////////////////////
// Some auxiliary math functions
static inline
float normL2(const cv::Point3f& a, const cv::Point3f& b)
{
return (a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y) + (a.z - b.z) * (a.z - b.z);
}
static inline
double normL2sq(const cv::Mat &m)
{
return m.dot(m);
}
template <typename T>
static inline
T sqr(T x)
{
return x * x;
}
#endif // _OPENCV_STITCHING_UTIL_INL_HPP_
#include "warpers.hpp"
using namespace std;
using namespace cv;
Ptr<Warper> Warper::createByCameraFocal(int focal, int type)
{
if (type == PLANE)
return new PlaneWarper(focal);
if (type == CYLINDRICAL)
return new CylindricalWarper(focal);
if (type == SPHERICAL)
return new SphericalWarper(focal);
CV_Error(CV_StsBadArg, "unsupported warping type");
return NULL;
}
void ProjectorBase::setCameraMatrix(const Mat &M)
{
CV_Assert(M.size() == Size(3, 3));
CV_Assert(M.type() == CV_32F);
m[0] = M.at<float>(0, 0); m[1] = M.at<float>(0, 1); m[2] = M.at<float>(0, 2);
m[3] = M.at<float>(1, 0); m[4] = M.at<float>(1, 1); m[5] = M.at<float>(1, 2);
m[6] = M.at<float>(2, 0); m[7] = M.at<float>(2, 1); m[8] = M.at<float>(2, 2);
Mat M_inv = M.inv();
minv[0] = M_inv.at<float>(0, 0); minv[1] = M_inv.at<float>(0, 1); minv[2] = M_inv.at<float>(0, 2);
minv[3] = M_inv.at<float>(1, 0); minv[4] = M_inv.at<float>(1, 1); minv[5] = M_inv.at<float>(1, 2);
minv[6] = M_inv.at<float>(2, 0); minv[7] = M_inv.at<float>(2, 1); minv[8] = M_inv.at<float>(2, 2);
}
Point Warper::operator ()(const Mat &src, float focal, const Mat& M, Mat &dst,
int interp_mode, int border_mode)
{
return warp(src, focal, M, dst, interp_mode, border_mode);
}
void PlaneWarper::detectResultRoi(Point &dst_tl, Point &dst_br)
{
float tl_uf = numeric_limits<float>::max();
float tl_vf = numeric_limits<float>::max();
float br_uf = -numeric_limits<float>::max();
float br_vf = -numeric_limits<float>::max();
float u, v;
projector_.mapForward(0, 0, u, v);
tl_uf = min(tl_uf, u); tl_vf = min(tl_vf, v);
br_uf = max(br_uf, u); br_vf = max(br_vf, v);
projector_.mapForward(0, static_cast<float>(src_size_.height - 1), u, v);
tl_uf = min(tl_uf, u); tl_vf = min(tl_vf, v);
br_uf = max(br_uf, u); br_vf = max(br_vf, v);
projector_.mapForward(static_cast<float>(src_size_.width - 1), 0, u, v);
tl_uf = min(tl_uf, u); tl_vf = min(tl_vf, v);
br_uf = max(br_uf, u); br_vf = max(br_vf, v);
projector_.mapForward(static_cast<float>(src_size_.width - 1), static_cast<float>(src_size_.height - 1), u, v);
tl_uf = min(tl_uf, u); tl_vf = min(tl_vf, v);
br_uf = max(br_uf, u); br_vf = max(br_vf, v);
dst_tl.x = static_cast<int>(tl_uf);
dst_tl.y = static_cast<int>(tl_vf);
dst_br.x = static_cast<int>(br_uf);
dst_br.y = static_cast<int>(br_vf);
}
void SphericalWarper::detectResultRoi(Point &dst_tl, Point &dst_br)
{
detectResultRoiByBorder(dst_tl, dst_br);
float tl_uf = static_cast<float>(dst_tl.x);
float tl_vf = static_cast<float>(dst_tl.y);
float br_uf = static_cast<float>(dst_br.x);
float br_vf = static_cast<float>(dst_br.y);
float x = projector_.minv[1];
float y = projector_.minv[4];
float z = projector_.minv[7];
if (y > 0.f)
{
x = projector_.focal * x / z + src_size_.width * 0.5f;
y = projector_.focal * y / z + src_size_.height * 0.5f;
if (x > 0.f && x < src_size_.width && y > 0.f && y < src_size_.height)
{
tl_uf = min(tl_uf, 0.f); tl_vf = min(tl_vf, static_cast<float>(CV_PI * projector_.scale));
br_uf = max(br_uf, 0.f); br_vf = max(br_vf, static_cast<float>(CV_PI * projector_.scale));
}
}
x = projector_.minv[1];
y = -projector_.minv[4];
z = projector_.minv[7];
if (y > 0.f)
{
x = projector_.focal * x / z + src_size_.width * 0.5f;
y = projector_.focal * y / z + src_size_.height * 0.5f;
if (x > 0.f && x < src_size_.width && y > 0.f && y < src_size_.height)
{
tl_uf = min(tl_uf, 0.f); tl_vf = min(tl_vf, static_cast<float>(0));
br_uf = max(br_uf, 0.f); br_vf = max(br_vf, static_cast<float>(0));
}
}
dst_tl.x = static_cast<int>(tl_uf);
dst_tl.y = static_cast<int>(tl_vf);
dst_br.x = static_cast<int>(br_uf);
dst_br.y = static_cast<int>(br_vf);
}
#ifndef _OPENCV_WARPERS_HPP_
#define _OPENCV_WARPERS_HPP_
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
class Warper
{
public:
enum { PLANE, CYLINDRICAL, SPHERICAL };
static cv::Ptr<Warper> createByCameraFocal(int focal, int type);
virtual ~Warper() {}
cv::Point operator ()(const cv::Mat &src, float focal, const cv::Mat& M, cv::Mat &dst,
int interp_mode = cv::INTER_LINEAR, int border_mode = cv::BORDER_REFLECT);
protected:
virtual cv::Point warp(const cv::Mat &src, float focal, const cv::Mat& M, cv::Mat &dst,
int interp_mode, int border_mode) = 0;
};
struct ProjectorBase
{
void setCameraMatrix(const cv::Mat& M);
cv::Size size;
float focal;
float m[9];
float minv[9];
float scale;
};
template <class P>
class WarperBase : public Warper
{
protected:
cv::Point warp(const cv::Mat &src, float focal, const cv::Mat &M, cv::Mat &dst,
int interp_mode, int border_mode);
// Detects ROI of the destination image. It's correct for any projection.
virtual void detectResultRoi(cv::Point &dst_tl, cv::Point &dst_br);
// Detects ROI of the destination image by walking over image border.
// Correctness for any projection isn't guaranteed.
void detectResultRoiByBorder(cv::Point &dst_tl, cv::Point &dst_br);
cv::Size src_size_;
P projector_;
};
struct PlaneProjector : ProjectorBase
{
void mapForward(float x, float y, float &u, float &v);
void mapBackward(float u, float v, float &x, float &y);
float plane_dist;
};
// Projects image onto z = plane_dist plane
class PlaneWarper : public WarperBase<PlaneProjector>
{
public:
PlaneWarper(float plane_dist = 1.f, float scale = 1.f)
{
projector_.plane_dist = plane_dist;
projector_.scale = scale;
}
private:
void detectResultRoi(cv::Point &dst_tl, cv::Point &dst_br);
};
struct SphericalProjector : ProjectorBase
{
void mapForward(float x, float y, float &u, float &v);
void mapBackward(float u, float v, float &x, float &y);
};
// Projects image onto unit sphere with origin at (0, 0, 0).
// Poles are located at (0, -1, 0) and (0, 1, 0) points.
class SphericalWarper : public WarperBase<SphericalProjector>
{
public:
SphericalWarper(float scale = 300.f) { projector_.scale = scale; }
private:
void detectResultRoi(cv::Point &dst_tl, cv::Point &dst_br);
};
struct CylindricalProjector : ProjectorBase
{
void mapForward(float x, float y, float &u, float &v);
void mapBackward(float u, float v, float &x, float &y);
};
// Projects image onto x * x + z * z = 1 cylinder
class CylindricalWarper : public WarperBase<CylindricalProjector>
{
public:
CylindricalWarper(float scale = 300.f) { projector_.scale = scale; }
private:
void detectResultRoi(cv::Point &dst_tl, cv::Point &dst_br)
{
WarperBase<CylindricalProjector>::detectResultRoiByBorder(dst_tl, dst_br);
}
};
#include "warpers_inl.hpp"
#endif // _OPENCV_WARPERS_HPP_
#ifndef _OPENCV_WARPERS_INL_HPP_
#define _OPENCV_WARPERS_INL_HPP_
#include "warpers.hpp" // Make your IDE see declarations
template <class P>
cv::Point WarperBase<P>::warp(const cv::Mat &src, float focal, const cv::Mat &M, cv::Mat &dst,
int interp_mode, int border_mode)
{
src_size_ = src.size();
projector_.size = src.size();
projector_.focal = focal;
projector_.setCameraMatrix(M);
cv::Point dst_tl, dst_br;
detectResultRoi(dst_tl, dst_br);
cv::Mat xmap(dst_br.y - dst_tl.y + 1, dst_br.x - dst_tl.x + 1, CV_32F);
cv::Mat ymap(dst_br.y - dst_tl.y + 1, dst_br.x - dst_tl.x + 1, CV_32F);
float x, y;
for (int v = dst_tl.y; v <= dst_br.y; ++v)
{
for (int u = dst_tl.x; u <= dst_br.x; ++u)
{
projector_.mapBackward(static_cast<float>(u), static_cast<float>(v), x, y);
xmap.at<float>(v - dst_tl.y, u - dst_tl.x) = x;
ymap.at<float>(v - dst_tl.y, u - dst_tl.x) = y;
}
}
dst.create(dst_br.y - dst_tl.y + 1, dst_br.x - dst_tl.x + 1, src.type());
remap(src, dst, xmap, ymap, interp_mode, border_mode);
return dst_tl;
}
template <class P>
void WarperBase<P>::detectResultRoi(cv::Point &dst_tl, cv::Point &dst_br)
{
float tl_uf = std::numeric_limits<float>::max();
float tl_vf = std::numeric_limits<float>::max();
float br_uf = -std::numeric_limits<float>::max();
float br_vf = -std::numeric_limits<float>::max();
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);
br_uf = std::max(br_uf, u); br_vf = std::max(br_vf, v);
}
}
dst_tl.x = static_cast<int>(tl_uf);
dst_tl.y = static_cast<int>(tl_vf);
dst_br.x = static_cast<int>(br_uf);
dst_br.y = static_cast<int>(br_vf);
}
template <class P>
void WarperBase<P>::detectResultRoiByBorder(cv::Point &dst_tl, cv::Point &dst_br)
{
float tl_uf = std::numeric_limits<float>::max();
float tl_vf = std::numeric_limits<float>::max();
float br_uf = -std::numeric_limits<float>::max();
float br_vf = -std::numeric_limits<float>::max();
float u, v;
for (float x = 0; x < src_size_.width; ++x)
{
projector_.mapForward(static_cast<float>(x), 0, u, v);
tl_uf = std::min(tl_uf, u); tl_vf = std::min(tl_vf, v);
br_uf = std::max(br_uf, u); br_vf = std::max(br_vf, v);
projector_.mapForward(static_cast<float>(x), static_cast<float>(src_size_.height - 1), u, v);
tl_uf = std::min(tl_uf, u); tl_vf = std::min(tl_vf, v);
br_uf = std::max(br_uf, u); br_vf = std::max(br_vf, v);
}
for (int y = 0; y < src_size_.height; ++y)
{
projector_.mapForward(0, static_cast<float>(y), u, v);
tl_uf = std::min(tl_uf, u); tl_vf = std::min(tl_vf, v);
br_uf = std::max(br_uf, u); br_vf = std::max(br_vf, v);
projector_.mapForward(static_cast<float>(src_size_.width - 1), static_cast<float>(y), u, v);
tl_uf = std::min(tl_uf, u); tl_vf = std::min(tl_vf, v);
br_uf = std::max(br_uf, u); br_vf = std::max(br_vf, v);
}
dst_tl.x = static_cast<int>(tl_uf);
dst_tl.y = static_cast<int>(tl_vf);
dst_br.x = static_cast<int>(br_uf);
dst_br.y = static_cast<int>(br_vf);
}
inline
void PlaneProjector::mapForward(float x, float y, float &u, float &v)
{
x -= size.width * 0.5f;
y -= size.height * 0.5f;
float x_ = m[0] * x + m[1] * y + m[2] * focal;
float y_ = m[3] * x + m[4] * y + m[5] * focal;
float z_ = m[6] * x + m[7] * y + m[8] * focal;
u = scale * x_ / z_ * plane_dist;
v = scale * y_ / z_ * plane_dist;
}
inline
void PlaneProjector::mapBackward(float u, float v, float &x, float &y)
{
float x_ = u / scale;
float y_ = v / scale;
float z;
x = minv[0] * x_ + minv[1] * y_ + minv[2] * plane_dist;
y = minv[3] * x_ + minv[4] * y_ + minv[5] * plane_dist;
z = minv[6] * x_ + minv[7] * y_ + minv[8] * plane_dist;
x = focal * x / z + size.width * 0.5f;
y = focal * y / z + size.height * 0.5f;
}
inline
void SphericalProjector::mapForward(float x, float y, float &u, float &v)
{
x -= size.width * 0.5f;
y -= size.height * 0.5f;
float x_ = m[0] * x + m[1] * y + m[2] * focal;
float y_ = m[3] * x + m[4] * y + m[5] * focal;
float z_ = m[6] * x + m[7] * y + m[8] * focal;
u = scale * atan2f(x_, z_);
v = scale * (static_cast<float>(CV_PI) - acosf(y_ / sqrtf(x_ * x_ + y_ * y_ + z_ * z_)));
}
inline
void SphericalProjector::mapBackward(float u, float v, float &x, float &y)
{
float sinv = sinf(static_cast<float>(CV_PI) - v / scale);
float x_ = sinv * sinf(u / scale);
float y_ = cosf(static_cast<float>(CV_PI) - v / scale);
float z_ = sinv * cosf(u / scale);
float z;
x = minv[0] * x_ + minv[1] * y_ + minv[2] * z_;
y = minv[3] * x_ + minv[4] * y_ + minv[5] * z_;
z = minv[6] * x_ + minv[7] * y_ + minv[8] * z_;
x = focal * x / z + size.width * 0.5f;
y = focal * y / z + size.height * 0.5f;
}
inline
void CylindricalProjector::mapForward(float x, float y, float &u, float &v)
{
x -= size.width * 0.5f;
y -= size.height * 0.5f;
float x_ = m[0] * x + m[1] * y + m[2] * focal;
float y_ = m[3] * x + m[4] * y + m[5] * focal;
float z_ = m[6] * x + m[7] * y + m[8] * focal;
u = scale * atan2f(x_, z_);
v = scale * y_ / sqrtf(x_ * x_ + z_ * z_);
}
inline
void CylindricalProjector::mapBackward(float u, float v, float &x, float &y)
{
float x_ = sinf(u / scale);
float y_ = v / scale;
float z_ = cosf(u / scale);
float z;
x = minv[0] * x_ + minv[1] * y_ + minv[2] * z_;
y = minv[3] * x_ + minv[4] * y_ + minv[5] * z_;
z = minv[6] * x_ + minv[7] * y_ + minv[8] * z_;
x = focal * x / z + size.width * 0.5f;
y = focal * y / z + size.height * 0.5f;
}
#endif // _OPENCV_WARPERS_INL_HPP_
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment