#ifndef _VIG_QUOTIENT_ESTIMATOR_H_ #define _VIG_QUOTIENT_ESTIMATOR_H_ #include #include "RansacParameterEstimator.h" #include #include #include #include #include #include #include "ransac.h" // uncomment this to use our version of the linear solver //#define HUGIN_VIG_USE_UBLAS #ifdef HUGIN_VIG_USE_UBLAS #include #include #include #include #include #include #include #else #include #endif namespace vigra_ext { /// function to calculate the vignetting correction: 1 + p[0]*r^2 + p[1]*r^4 + p[2]*r^6 template double calcVigCorrPoly(PITER p, double r) { const unsigned n=3; double rsq = r*r; double res = p[n-1] * rsq; for (int j=n-2; j >=0; j--) { res = (res + p[j]) * rsq; } // add constant offset return res + 1; } // structure with various information about the fitting process struct VigQuotientEstimateResult { int nUsedPoints; double brightnessRMSE; }; /** * This class estimates the parameters of the vignetting curve. * * We assume the following function: * I1/I2 = (1 + p_1*r_1^2 + p_2*r_1^4 + p_3*r_1^6) / (1 + a*r_2^2 + b*r_2^4 + c*r_2^6) * * Author: Pablo d'Angelo */ class VigQuotientEstimator : public RansacParameterEstimator { protected: double m_delta; public: #ifdef HUGIN_VIG_USE_UBLAS typedef boost::numeric::ublas::vector Param; #else typedef std::vector Param; // typedef double[3] Param; #endif VigQuotientEstimator (double delta) : RansacParameterEstimator(3), m_delta(delta) { } /** Compute the vignetting curve defined by 3 points. * * This results in the following equations: * z = I_1 / I_2 * * A*p = 1-z; * with A = [ r_1^2-r_2^2 r_1^4-r_2^4 r_1^6-r_2^6 ] * .. .. .. * * @param data A vector containing three 2D points. * @param p This vector is filled with the computed parameters, if fitting * was succesfull * @return fitting was succesfull */ virtual bool estimate(const std::vector &data, Param &p) const { const size_t n=3; if(data.size() < n) { return false; } // estimate point #ifdef HUGIN_VIG_USE_UBLAS p.resize(n); // create linear equation boost::numeric::ublas::permutation_matrix permut(n); boost::numeric::ublas::matrix A(n,n); for (unsigned i = 0; i < n; i++) { double r1sq = data[i]->r1 * data[i]->r1; double r2sq = data[i]->r2 * data[i]->r2; double r1f = r1sq; double r2f = r2sq; for (unsigned j=0;j < n; j++) { A(i,j) = r2f - r1f; r1f*=r1sq; r2f*=r2sq; } double c = (data[i]->i1 / data[i]->i2); p[i] = 1 - c; } try { // std::cout << "before factorize A:" << A << std::endl << "p: " << p << std::endl; lu_factorize(A,permut); // std::cout << "after factorize A:" << A << std::endl << "perm: " << permut << std::endl; lu_substitute(A,permut,p); // std::cout << "after substitute A:" << A << std::endl << "p: " << p << std::endl; } catch (boost::numeric::ublas::singular){ return false; } return true; #else p.resize(n); double matrix[n*(n+1)]; for (unsigned i = 0; i < n; i++) { double r1sq = data[i]->r1 * data[i]->r1; double r2sq = data[i]->r2 * data[i]->r2; double r1f = r1sq; double r2f = r2sq; for (unsigned j=0;j < n; j++) { matrix[i + j*n] = r2f - r1f; r1f*=r1sq; r2f*=r2sq; } double c = (data[i]->i1 / data[i]->i2); matrix[i + n*n] = 1 - c; } double sol[3]; bool ok =math_lu_solve(matrix, sol, n) != 0; p[0] = sol[0]; p[1] = sol[1]; p[2] = sol[2]; return ok; #endif } /** * Compute a least squares estimate. */ bool leastSquaresEstimate(const std::vector &data, Param & p) const { /* { std::ofstream of("hugin_vigquot_least_sq_points"); for (std::vector::const_iterator it = data.begin(); it != data.end(); ++it) { of << (*it)->r1 << " " << (*it)->i1 << " " << (*it)->r2 << " " << (*it)->i2 << std::endl; } } */ const size_t n=3; if(data.size() < n) { return false; } #ifdef HUGIN_VIG_USE_UBLAS // calculate least squares, based on pseudoinverse. // A'A x = A' b. boost::numeric::ublas::permutation_matrix permut(n); boost::numeric::ublas::matrix AtA(n,n); for (unsigned i=0; i< n; i++){ for (unsigned j=0; j< n; j++){ AtA(i,j) = 0; } } // is this the right way to clear a boost matrix? // AtA = boost::numeric::ublas::zero_matrix(n,n); p.resize(n); // is this the right way to clear a boost vector? for (unsigned i=0; i< n; i++){ p[i] = 0; } for(unsigned k=0; k < data.size(); k++) { // calculate one row of A double Arow[n]; double r1sq = data[k]->r1 * data[k]->r1; double r2sq = data[k]->r2 * data[k]->r2; double r1f = r1sq; double r2f = r2sq; for (unsigned j=0;j < n; j++) { Arow[j] = r2f - r1f; r1f*=r1sq; r2f*=r2sq; } double c = (data[k]->i1 / data[k]->i2); double bRow = 1 - c; // add to pseudoinverse for( unsigned i=0; ir1 * data[k]->r1; double r2sq = data[k]->r2 * data[k]->r2; double r1f = r1sq; double r2f = r2sq; for (unsigned j=0;j < n; j++) { Arow[j] = r2f - r1f; r1f*=r1sq; r2f*=r2sq; } double c = (data[k]->i1 / data[k]->i2); double bRow = 1 - c; solver.addRow(&Arow[0], bRow); } return solver.solve(p); #endif } /** * Return true if the distance between the line defined by the parameters and the * given point is smaller than 'delta' (see constructor). * @param p The line parameters [n_x,n_y,a_x,a_y]. * @param data Check that the distance between this point and the line is smaller than 'delta'. */ bool agree(Param &p, const PointPair &data) const { const int n = 3; double r1sq = data.r1 * data.r1; double r2sq = data.r2 * data.r2; double poly1 = p[n-1] * r1sq; double poly2 = p[n-1] * r2sq; for (int j=n-2; j >=0; j--) { poly1 = (poly1 + p[j]) * r1sq; poly2 = (poly2 + p[j]) * r2sq; } poly1 += 1; poly2 += 1; double e; if (data.r1 < data.r2) { // I1 should be bigger than I2 e = data.i2 / data.i1 - poly2/poly1; } else { e = data.i1 / data.i2 - poly1/poly2; } return fabs(e) < m_delta; } }; template void extractRandomPoints(std::vector *> &remapped, std::vector & imgXCoord, std::vector & imgYCoord, const std::vector & imgSize, const std::vector & imgCenter, unsigned nPointsPerOverlap, std::vector & points, unsigned & nBadPoints) { assert(remapped.size() == imgXCoord.size()); assert(remapped.size() == imgYCoord.size()); // init random number generator boost::mt19937 rng; unsigned nImg = remapped.size(); nBadPoints = 0; // extract points from all overlaps. for (unsigned i=0; i < nImg; i++) { for (unsigned j=i+1; j < nImg; j++) { // get unifiying region. vigra::Rect2D roi1 = remapped[i]->boundingBox(); vigra::Rect2D roi2 = remapped[j]->boundingBox(); vigra::Rect2D roi = roi1 & roi2; if (roi.isEmpty()) { continue; } double maxr1 = sqrt(((double)imgSize[i].x)*imgSize[i].x + ((double)imgSize[i].y)*imgSize[i].y) / 2.0; double maxr2 = sqrt(((double)imgSize[i].x)*imgSize[i].x + ((double)imgSize[i].y)*imgSize[i].y) / 2.0; // randomly sample points. boost::uniform_int<> distribx(roi.left(), roi.right()-1); boost::uniform_int<> distriby(roi.top(), roi.bottom()-1); boost::variate_generator > randX(rng, distribx); // glues randomness with mapping boost::variate_generator > randY(rng, distriby); // glues randomness with mapping for (unsigned maxTry = nPointsPerOverlap*2; nPointsPerOverlap > 0 && maxTry > 0; maxTry--) { unsigned x = randX(); unsigned y = randY(); if (remapped[i]->getMask(x,y) && remapped[j]->getMask(x,y)) { // extract gray value pair.. hugin_utils::FDiff2D p1(imgXCoord[i](x-roi1.left(), y-roi1.top()), imgYCoord[i](x-roi1.left(), y-roi1.top())); hugin_utils::FDiff2D p1c = p1 - imgCenter[i]; p1 = p1/maxr1; p1c = p1c/maxr1; hugin_utils::FDiff2D p2(imgXCoord[j](x-roi2.left(), y-roi2.top()), imgYCoord[j](x-roi2.left(), y-roi2.top())); hugin_utils::FDiff2D p2c = p2 - imgCenter[j]; p2 = p2/maxr2; p2c = p2c/maxr2; double r1 = sqrt(p1c.x*p1c.x + p1c.y*p1c.y); double r2 = sqrt(p2c.x*p2c.x + p2c.y*p2c.y); double i1 = remapped[i]->operator()(x,y); double i2 = remapped[j]->operator()(x,y); if (i1 >= i2 && r1 <= r2) { // ok, point is good. i1 is closer to centre, swap point // so that i1 < i2 points.push_back(PointPair(j, i2, p2, r2, i, i1, p1, r1) ); } else if(i1 < i2 && r1 > r2) { points.push_back(PointPair(i, i1, p1, r1, j, i2, p2, r2) ); } else { nBadPoints++; } nPointsPerOverlap--; } } } } DEBUG_DEBUG("random point extraction: consistent: " << points.size() << " inconsistent: " << nBadPoints << " points"); } inline VigQuotientEstimateResult optimizeVignettingQuotient(const std::vector & points, double ransacDelta, std::vector & vigCoeff) { VigQuotientEstimateResult res; // do optimisation.. // the quotient error should be < 0.1 VigQuotientEstimator vqEst(ransacDelta); #ifdef HUGIN_VIG_USE_UBLAS boost::numeric::ublas::vector vigCoeff2(3); for (int i=0; i < 3; i++) vigCoeff2[i] = vigCoeff[i]; res.nUsedPoints = Ransac::compute(vigCoeff2, vqEst, points, 0.99, 0.3); for (int i=0; i < 3; i++) vigCoeff[i] = vigCoeff2[i]; #else std::vector inliers_idx; std::vector inliers = Ransac::compute(vigCoeff, inliers_idx, vqEst, points, 0.99, 0.3); res.nUsedPoints = inliers.size(); #endif double sqerror=0; // calculate brightness RMSE // not a real residual, since the brightness error was not minimized. instead // the quotient error was minimized. However, a brightness difference is observeable // in the final panorama and can be intuitively understood // (well, actually its the RMSE, but thats a detail ;) for (std::vector::const_iterator pnt=points.begin(); pnt != points.end(); pnt++) { #ifdef HUGIN_VIG_USE_UBLAS sqerror += hugin_utils::sqr(calcVigCorrPoly(vigCoeff2, pnt->r1)*pnt->i1 - calcVigCorrPoly(vigCoeff, pnt->r2)*pnt->i2); #else sqerror += hugin_utils::sqr(calcVigCorrPoly(vigCoeff, pnt->r1)*pnt->i1 - calcVigCorrPoly(vigCoeff, pnt->r2)*pnt->i2); #endif } res.brightnessRMSE = sqrt(sqerror/points.size()); return res; } #if 0 template void runPointExtractor(Panorama & pano, ImageSource & imgLoader) { std::vector grayImgs; std::vector maskImgs; std::vector lapImgs; std::vector srcImgs; std::vector srcDescr; for (unsigned i=0; i < m_pano.getNrOfImages(); i++) { SrcPanoImage src = m_pano.getSrcImage(i); RGBFImage * img = imageLoader(src); ImageCache::Entry * cacheEntry = ImageCache::getInstance().getSmallImage(src.getFilename().c_str()); if (!cacheEntry) { throw std::runtime_error("could not retrieve small source image for vignetting optimisation"); } wxImage * srcImgWX = cacheEntry->image; // image view BasicImageView > srcImgInCache((RGBValue *)srcImgWX->GetData(), srcImgWX->GetWidth(), srcImgWX->GetHeight()); src.resize(srcImgInCache.size()); BImage * grayImg = new BImage(srcImgInCache.size()); // change to grayscale // just use green channel vigra::copyImage(vigra::srcImageRange(srcImgInCache, vigra::GreenAccessor >()), vigra::destImage(*grayImg)); BImage *maskImg = new BImage(srcImgInCache.size().x,srcImgInCache.size().y , 255); // TODO need to do gamma correction here! if (src.getGamma() != 1.0) { vigra_ext::applyGammaCorrection(srcImageRange(*grayImg), destImage(*grayImg), src.getGamma(), 255); } // TODO: cut out the masked parts. // applyMask(src, vigra::destImageRange(*maskImg)); vigra_ext::interp_spline36 interp; // create interpolating accessor to images. srcImgs.push_back(InterpolImg(vigra::srcImageRange(*grayImg), vigra::srcImage(*maskImg), interp, false) ); grayImgs.push_back(grayImg); maskImgs.push_back(maskImg); srcDescr.push_back(src); if (uniformRadiusSample) { vigra::FImage * lap = new vigra::FImage(grayImg->size()); laplacianOfGaussian(srcImageRange(*grayImg), destImage(*lap), 1); lapImgs.push_back(lap); } } #endif } #endif //_VIG_QUOTIENT_PARAM_ESTIMATOR_H_