Commit f077f58e authored by Maksim Shabunin's avatar Maksim Shabunin

Merge pull request #711 from AmbroiseMoreau:gsoc2016_StructuredLight

parents b7dcf141 e439f26d
set(the_description "Phase Unwrapping API")
ocv_define_module(phase_unwrapping opencv_core opencv_calib3d opencv_imgproc opencv_highgui opencv_features2d opencv_rgbd WRAP python java)
Phase Unwrapping
================
OpenCV module that can be used to unwrap two-dimensional phase map.
@article{histogramUnwrapping,
title={A novel algorithm based on histogram processing of reliability for two-dimensional phase unwrapping},
author={Lei, Hai and Chang, Xin-yu and Wang, Fei and Hu, Xiao-Tang and Hu, Xiao-Dong},
journal={Optik-International Journal for Light and Electron Optics},
volume={126},
number={18},
pages={1640--1644},
year={2015},
}
/*M///////////////////////////////////////////////////////////////////////////////////////
//
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
//
// By downloading, copying, installing or using the software you agree to this license.
// If you do not agree to this license, do not download, install,
// copy or use the software.
//
//
// License Agreement
// For Open Source Computer Vision Library
//
// Copyright (C) 2015, OpenCV Foundation, all rights reserved.
// Third party copyrights are property of their respective owners.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// * Redistribution's of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistribution's in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// * The name of the copyright holders may not be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// This software is provided by the copyright holders and contributors "as is" and
// any express or implied warranties, including, but not limited to, the implied
// warranties of merchantability and fitness for a particular purpose are disclaimed.
// In no event shall the Intel Corporation or contributors be liable for any direct,
// indirect, incidental, special, exemplary, or consequential damages
// (including, but not limited to, procurement of substitute goods or services;
// loss of use, data, or profits; or business interruption) however caused
// and on any theory of liability, whether in contract, strict liability,
// or tort (including negligence or otherwise) arising in any way out of
// the use of this software, even if advised of the possibility of such damage.
//
//M*/
#include "opencv2/phase_unwrapping/phase_unwrapping.hpp"
#include "opencv2/phase_unwrapping/histogramphaseunwrapping.hpp"
/** @defgroup phase_unwrapping Phase Unwrapping API
Two-dimensional phase unwrapping is found in different applications like terrain elevation estimation
in synthetic aperture radar (SAR), field mapping in magnetic resonance imaging or as a way of finding
corresponding pixels in structured light reconstruction with sinusoidal patterns.
Given a phase map, wrapped between [-pi; pi], phase unwrapping aims at finding the "true" phase map
by adding the right number of 2*pi to each pixel.
The problem is straightforward for perfect wrapped phase map, but real data are usually not noise-free.
Among the different algorithms that were developed, quality-guided phase unwrapping methods are fast
and efficient. They follow a path that unwraps high quality pixels first,
avoiding error propagation from the start.
In this module, a quality-guided phase unwrapping is implemented following the approach described in @cite histogramUnwrapping .
*/
\ No newline at end of file
/*M///////////////////////////////////////////////////////////////////////////////////////
//
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
//
// By downloading, copying, installing or using the software you agree to this license.
// If you do not agree to this license, do not download, install,
// copy or use the software.
//
//
// License Agreement
// For Open Source Computer Vision Library
//
// Copyright (C) 2015, OpenCV Foundation, all rights reserved.
// Third party copyrights are property of their respective owners.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// * Redistribution's of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistribution's in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// * The name of the copyright holders may not be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// This software is provided by the copyright holders and contributors "as is" and
// any express or implied warranties, including, but not limited to, the implied
// warranties of merchantability and fitness for a particular purpose are disclaimed.
// In no event shall the Intel Corporation or contributors be liable for any direct,
// indirect, incidental, special, exemplary, or consequential damages
// (including, but not limited to, procurement of substitute goods or services;
// loss of use, data, or profits; or business interruption) however caused
// and on any theory of liability, whether in contract, strict liability,
// or tort (including negligence or otherwise) arising in any way out of
// the use of this software, even if advised of the possibility of such damage.
//
//M*/
#ifndef __OPENCV_HISTOGRAM_PHASE_UNWRAPPING_HPP__
#define __OPENCV_HISTOGRAM_PHASE_UNWRAPPING_HPP__
#include "opencv2/core.hpp"
#include <opencv2/imgproc.hpp>
#include "opencv2/phase_unwrapping/phase_unwrapping.hpp"
namespace cv {
namespace phase_unwrapping {
//! @addtogroup phase_unwrapping
//! @{
/** @brief Class implementing two-dimensional phase unwrapping based on @cite histogramUnwrapping
* This algorithm belongs to the quality-guided phase unwrapping methods.
* First, it computes a reliability map from second differences between a pixel and its eight neighbours.
* Reliability values lie between 0 and 16*pi*pi. Then, this reliability map is used to compute
* the reliabilities of "edges". An edge is an entity defined by two pixels that are connected
* horizontally or vertically. Its reliability is found by adding the the reliabilities of the
* two pixels connected through it. Edges are sorted in a histogram based on their reliability values.
* This histogram is then used to unwrap pixels, starting from the highest quality pixel.
* The wrapped phase map and the unwrapped result are stored in CV_32FC1 Mat.
*/
class CV_EXPORTS_W HistogramPhaseUnwrapping : public PhaseUnwrapping
{
public:
/**
* @brief Parameters of phaseUnwrapping constructor.
* @param width Phase map width.
* @param height Phase map height.
* @param histThresh Bins in the histogram are not of equal size. Default value is 3*pi*pi. The one before "histThresh" value are smaller.
* @param nbrOfSmallBins Number of bins between 0 and "histThresh". Default value is 10.
* @param nbrOfLargeBins Number of bins between "histThresh" and 32*pi*pi (highest edge reliability value). Default value is 5.
*/
struct CV_EXPORTS Params
{
Params();
int width;
int height;
float histThresh;
int nbrOfSmallBins;
int nbrOfLargeBins;
};
/**
* @brief Constructor
* @param parameters HistogramPhaseUnwrapping parameters HistogramPhaseUnwrapping::Params: width,height of the phase map and histogram characteristics.
*/
static Ptr<HistogramPhaseUnwrapping> create( const HistogramPhaseUnwrapping::Params &parameters =
HistogramPhaseUnwrapping::Params() );
/**
* @brief Get the reliability map computed from the wrapped phase map.
* @param reliabilityMap Image where the reliability map is stored.
*/
CV_WRAP
virtual void getInverseReliabilityMap( OutputArray reliabilityMap ) = 0;
};
//! @}
}
}
#endif
\ No newline at end of file
/*M///////////////////////////////////////////////////////////////////////////////////////
//
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
//
// By downloading, copying, installing or using the software you agree to this license.
// If you do not agree to this license, do not download, install,
// copy or use the software.
//
//
// License Agreement
// For Open Source Computer Vision Library
//
// Copyright (C) 2015, OpenCV Foundation, all rights reserved.
// Third party copyrights are property of their respective owners.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// * Redistribution's of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistribution's in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// * The name of the copyright holders may not be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// This software is provided by the copyright holders and contributors "as is" and
// any express or implied warranties, including, but not limited to, the implied
// warranties of merchantability and fitness for a particular purpose are disclaimed.
// In no event shall the Intel Corporation or contributors be liable for any direct,
// indirect, incidental, special, exemplary, or consequential damages
// (including, but not limited to, procurement of substitute goods or services;
// loss of use, data, or profits; or business interruption) however caused
// and on any theory of liability, whether in contract, strict liability,
// or tort (including negligence or otherwise) arising in any way out of
// the use of this software, even if advised of the possibility of such damage.
//
//M*/
#ifndef __OPENCV_PHASE_UNWRAPPING_HPP__
#define __OPENCV_PHASE_UNWRAPPING_HPP__
#include "opencv2/core.hpp"
namespace cv {
namespace phase_unwrapping {
//! @addtogroup phase_unwrapping
//! @{
/**
@brief Abstract base class for phase unwrapping.
*/
class CV_EXPORTS_W PhaseUnwrapping : public virtual Algorithm
{
public:
/**
* @brief Unwraps a 2D phase map.
* @param wrappedPhaseMap The wrapped phase map that needs to be unwrapped.
* @param unwrappedPhaseMap The unwrapped phase map.
* @param shadowMask Optional parameter used when some pixels do not hold any phase information in the wrapped phase map.
*/
CV_WRAP
virtual void unwrapPhaseMap( InputArray wrappedPhaseMap, OutputArray unwrappedPhaseMap,
InputArray shadowMask = noArray() ) = 0;
};
//! @}
}
}
#endif
\ No newline at end of file
/*M///////////////////////////////////////////////////////////////////////////////////////
//
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
//
// By downloading, copying, installing or using the software you agree to this license.
// If you do not agree to this license, do not download, install,
// copy or use the software.
//
//
// License Agreement
// For Open Source Computer Vision Library
//
// Copyright (C) 2015, OpenCV Foundation, all rights reserved.
// Third party copyrights are property of their respective owners.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// * Redistribution's of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistribution's in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// * The name of the copyright holders may not be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// This software is provided by the copyright holders and contributors "as is" and
// any express or implied warranties, including, but not limited to, the implied
// warranties of merchantability and fitness for a particular purpose are disclaimed.
// In no event shall the Intel Corporation or contributors be liable for any direct,
// indirect, incidental, special, exemplary, or consequential damages
// (including, but not limited to, procurement of substitute goods or services;
// loss of use, data, or profits; or business interruption) however caused
// and on any theory of liability, whether in contract, strict liability,
// or tort (including negligence or otherwise) arising in any way out of
// the use of this software, even if advised of the possibility of such damage.
* //
//M*/
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/phase_unwrapping.hpp>
#include <iostream>
#include <fstream>
#include <stdio.h>
using namespace cv;
using namespace std;
static const char* keys =
{
"{@inputPath | | Path of the wrapped phase map saved in a yaml file }"
"{@outputUnwrappedName | | Path of the unwrapped phase map to be saved in a yaml file and as an 8 bit png}"
};
static void help()
{
cout << "\nThis example shows how to use the \"Phase unwrapping module\" to unwrap a phase map"
" saved in a yaml file (see extra_data\\phase_unwrapping\\data\\wrappedpeaks.yml)."
" The mat name in the file should be \"phaseValue\". The result is saved in a yaml file"
" too. Two images (wrapped.png and output_name.png) are also created"
" for visualization purpose."
"\nTo call: ./example_phase_unwrapping_unwrap <input_path> <output_unwrapped_name> \n"
<< endl;
}
int main(int argc, char **argv)
{
phase_unwrapping::HistogramPhaseUnwrapping::Params params;
CommandLineParser parser(argc, argv, keys);
String inputPath = parser.get<String>(0);
String outputUnwrappedName = parser.get<String>(1);
if( inputPath.empty() || outputUnwrappedName.empty() )
{
help();
return -1;
}
FileStorage fsInput(inputPath, FileStorage::READ);
FileStorage fsOutput(outputUnwrappedName + ".yml", FileStorage::WRITE);
Mat wPhaseMap;
Mat uPhaseMap;
Mat reliabilities;
fsInput["phaseValues"] >> wPhaseMap;
fsInput.release();
params.width = wPhaseMap.cols;
params.height = wPhaseMap.rows;
Ptr<phase_unwrapping::HistogramPhaseUnwrapping> phaseUnwrapping = phase_unwrapping::HistogramPhaseUnwrapping::create(params);
phaseUnwrapping->unwrapPhaseMap(wPhaseMap, uPhaseMap);
fsOutput << "phaseValues" << uPhaseMap;
fsOutput.release();
phaseUnwrapping->getInverseReliabilityMap(reliabilities);
Mat uPhaseMap8, wPhaseMap8, reliabilities8;
wPhaseMap.convertTo(wPhaseMap8, CV_8U, 255, 128);
uPhaseMap.convertTo(uPhaseMap8, CV_8U, 1, 128);
reliabilities.convertTo(reliabilities8, CV_8U, 255,128);
imshow("reliabilities", reliabilities);
imshow("wrapped phase map", wPhaseMap8);
imshow("unwrapped phase map", uPhaseMap8);
imwrite(outputUnwrappedName + ".png", uPhaseMap8);
imwrite("reliabilities.png", reliabilities8);
bool loop = true;
while( loop )
{
char key = (char)waitKey(0);
if( key == 27 )
{
loop = false;
}
}
return 0;
}
\ No newline at end of file
/*M///////////////////////////////////////////////////////////////////////////////////////
//
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
//
// By downloading, copying, installing or using the software you agree to this license.
// If you do not agree to this license, do not download, install,
// copy or use the software.
//
//
// License Agreement
// For Open Source Computer Vision Library
//
// Copyright (C) 2015, OpenCV Foundation, all rights reserved.
// Third party copyrights are property of their respective owners.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// * Redistribution's of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistribution's in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// * The name of the copyright holders may not be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// This software is provided by the copyright holders and contributors "as is" and
// any express or implied warranties, including, but not limited to, the implied
// warranties of merchantability and fitness for a particular purpose are disclaimed.
// In no event shall the Intel Corporation or contributors be liable for any direct,
// indirect, incidental, special, exemplary, or consequential damages
// (including, but not limited to, procurement of substitute goods or services;
// loss of use, data, or profits; or business interruption) however caused
// and on any theory of liability, whether in contract, strict liability,
// or tort (including negligence or otherwise) arising in any way out of
// the use of this software, even if advised of the possibility of such damage.
//
//M*/
#include "precomp.hpp"
namespace cv {
namespace phase_unwrapping {
class CV_EXPORTS_W HistogramPhaseUnwrapping_Impl : public HistogramPhaseUnwrapping
{
public:
// Constructor
explicit HistogramPhaseUnwrapping_Impl( const HistogramPhaseUnwrapping::Params &parameters =
HistogramPhaseUnwrapping::Params() );
// Destructor
virtual ~HistogramPhaseUnwrapping_Impl(){};
// Unwrap phase map
void unwrapPhaseMap( InputArray wrappedPhaseMap, OutputArray unwrappedPhaseMap,
InputArray shadowMask = noArray() );
// Get reliability map computed from the wrapped phase map
void getInverseReliabilityMap( OutputArray reliabilityMap );
private:
// Class describing a pixel
class Pixel
{
private:
// Value from the wrapped phase map
float phaseValue;
// Id of a pixel. Computed from its position in the Mat
int idx;
// Pixel is valid if it's not in a shadow region
bool valid;
// "Quality" parameter. See reference paper
float inverseReliability;
// Number of 2pi that needs to be added to the pixel to unwrap the phase map
int increment;
// Number of pixels that are in the same group as the current pixel
int nbrOfPixelsInGroup;
// Group id. At first, group id is the same value as idx
int groupId;
// Pixel is alone in its group
bool singlePixelGroup;
public:
Pixel();
Pixel( float pV, int id, bool v, float iR, int inc );
float getPhaseValue();
int getIndex();
bool getValidity();
float getInverseReliability();
int getIncrement();
int getNbrOfPixelsInGroup();
int getGroupId();
bool getSinglePixelGroup();
void setIncrement( int inc );
// When a pixel which is not in a single group is added to a new group, we need to keep the previous increment and add "inc" to it.
void changeIncrement( int inc );
void setNbrOfPixelsInGroup( int nbr );
void setGroupId( int gId );
void setSinglePixelGroup( bool s );
};
// Class describing an Edge as presented in the reference paper
class Edge
{
private:
// Id of the first pixel that forms the edge
int pixOneId;
// Id of the second pixel that forms the edge
int pixTwoId;
// Number of 2pi that needs to be added to the second pixel to remove discontinuities
int increment;
public:
Edge();
Edge( int p1, int p2, int inc );
int getPixOneId();
int getPixTwoId();
int getIncrement();
};
// Class describing a bin from the histogram
class HistogramBin
{
private:
float start;
float end;
std::vector<Edge> edges;
public:
HistogramBin();
HistogramBin( float s, float e );
void addEdge( Edge e );
std::vector<Edge> getEdges();
};
// Class describing the histogram. Bins before "thresh" are smaller than the one after "thresh" value
class Histogram
{
private:
std::vector<HistogramBin> bins;
float thresh;
float smallWidth;
float largeWidth;
int nbrOfSmallBins;
int nbrOfLargeBins;
int nbrOfBins;
public:
Histogram();
void createBins( float t, int nbrOfBinsBeforeThresh, int nbrOfBinsAfterThresh );
void addBin( HistogramBin b );
void addEdgeInBin( Edge e, int binIndex);
float getThresh();
float getSmallWidth();
float getLargeWidth();
int getNbrOfBins();
std::vector<Edge> getEdgesFromBin( int binIndex );
};
// Params for phase unwrapping
Params params;
// Pixels from the wrapped phase map
std::vector<Pixel> pixels;
// Histogram used to unwrap
Histogram histogram;
// Compute pixel reliability.
void computePixelsReliability( InputArray wrappedPhaseMap, InputArray shadowMask = noArray() );
// Compute edges reliability and sort them in the histogram
void computeEdgesReliabilityAndCreateHistogram();
// Methods that is used in the previous one
void createAndSortEdge( int idx1, int idx2 );
// Unwrap the phase map thanks to the histogram
void unwrapHistogram();
// add right number of 2*pi to the pixels
void addIncrement( OutputArray unwrappedPhaseMap );
// Gamma function from the paper
float wrap( float a, float b );
// Similar to the previous one but returns the number of 2pi that needs to be added
int findInc( float a, float b );
};
// Default parameters
HistogramPhaseUnwrapping::Params::Params(){
width = 800;
height = 600;
histThresh = static_cast<float>(3 * CV_PI * CV_PI);
nbrOfSmallBins = 10;
nbrOfLargeBins = 5;
}
HistogramPhaseUnwrapping_Impl::HistogramPhaseUnwrapping_Impl(
const HistogramPhaseUnwrapping::Params &parameters ) : params(parameters)
{
}
HistogramPhaseUnwrapping_Impl::Pixel::Pixel()
{
}
// Constructor
HistogramPhaseUnwrapping_Impl::Pixel::Pixel( float pV, int id, bool v, float iR, int inc )
{
phaseValue = pV;
idx = id;
valid = v;
inverseReliability = iR;
increment = inc;
nbrOfPixelsInGroup = 1;
groupId = id;
singlePixelGroup = true;
}
float HistogramPhaseUnwrapping_Impl::Pixel::getPhaseValue()
{
return phaseValue;
}
int HistogramPhaseUnwrapping_Impl::Pixel::getIndex()
{
return idx;
}
bool HistogramPhaseUnwrapping_Impl::Pixel::getValidity()
{
return valid;
}
float HistogramPhaseUnwrapping_Impl::Pixel::getInverseReliability()
{
return inverseReliability;
}
int HistogramPhaseUnwrapping_Impl::Pixel::getIncrement()
{
return increment;
}
int HistogramPhaseUnwrapping_Impl::Pixel::getNbrOfPixelsInGroup()
{
return nbrOfPixelsInGroup;
}
int HistogramPhaseUnwrapping_Impl::Pixel::getGroupId()
{
return groupId;
}
bool HistogramPhaseUnwrapping_Impl::Pixel::getSinglePixelGroup()
{
return singlePixelGroup;
}
void HistogramPhaseUnwrapping_Impl::Pixel::setIncrement( int inc )
{
increment = inc;
}
/* When a pixel of a non-single group is added to an other non-single group, we need to add a new
increment to the one that was there previously and that was already removing some wraps.
*/
void HistogramPhaseUnwrapping_Impl::Pixel::changeIncrement( int inc )
{
increment += inc;
}
void HistogramPhaseUnwrapping_Impl::Pixel::setNbrOfPixelsInGroup( int nbr )
{
nbrOfPixelsInGroup = nbr;
}
void HistogramPhaseUnwrapping_Impl::Pixel::setGroupId( int gId )
{
groupId = gId;
}
void HistogramPhaseUnwrapping_Impl::Pixel::setSinglePixelGroup( bool s )
{
singlePixelGroup = s;
}
HistogramPhaseUnwrapping_Impl::Edge::Edge()
{
}
// Constructor
HistogramPhaseUnwrapping_Impl::Edge::Edge( int p1, int p2, int inc )
{
pixOneId = p1;
pixTwoId = p2;
increment = inc;
}
int HistogramPhaseUnwrapping_Impl::Edge::getPixOneId()
{
return pixOneId;
}
int HistogramPhaseUnwrapping_Impl::Edge::getPixTwoId()
{
return pixTwoId;
}
int HistogramPhaseUnwrapping_Impl::Edge::getIncrement()
{
return increment;
}
HistogramPhaseUnwrapping_Impl::HistogramBin::HistogramBin()
{
}
HistogramPhaseUnwrapping_Impl::HistogramBin::HistogramBin( float s, float e )
{
start = s;
end = e;
}
void HistogramPhaseUnwrapping_Impl::HistogramBin::addEdge( Edge e )
{
edges.push_back(e);
}
std::vector<HistogramPhaseUnwrapping_Impl::Edge> HistogramPhaseUnwrapping_Impl::HistogramBin::getEdges()
{
return edges;
}
HistogramPhaseUnwrapping_Impl::Histogram::Histogram()
{
}
/*
* create histogram bins. Bins size is not uniform, as in the reference paper
*
*/
void HistogramPhaseUnwrapping_Impl::Histogram::createBins( float t, int nbrOfBinsBeforeThresh,
int nbrOfBinsAfterThresh )
{
thresh = t;
nbrOfSmallBins = nbrOfBinsBeforeThresh;
nbrOfLargeBins = nbrOfBinsAfterThresh;
nbrOfBins = nbrOfBinsBeforeThresh + nbrOfBinsAfterThresh;
smallWidth = thresh / nbrOfSmallBins;
largeWidth = static_cast<float>(32 * CV_PI * CV_PI - thresh) / static_cast<float>(nbrOfLargeBins);
for( int i = 0; i < nbrOfSmallBins; ++i )
{
addBin(HistogramBin(i * smallWidth, ( i + 1 ) * smallWidth));
}
for( int i = 0; i < nbrOfLargeBins; ++i )
{
addBin(HistogramBin(thresh + i * largeWidth, thresh + ( i + 1 ) * largeWidth));
}
}
// Add a bin b to the histogram
void HistogramPhaseUnwrapping_Impl::Histogram::addBin( HistogramBin b )
{
bins.push_back(b);
}
// Add edge E in bin binIndex
void HistogramPhaseUnwrapping_Impl::Histogram::addEdgeInBin( Edge e, int binIndex )
{
bins[binIndex].addEdge(e);
}
float HistogramPhaseUnwrapping_Impl::Histogram::getThresh()
{
return thresh;
}
float HistogramPhaseUnwrapping_Impl::Histogram::getSmallWidth()
{
return smallWidth;
}
float HistogramPhaseUnwrapping_Impl::Histogram::getLargeWidth()
{
return largeWidth;
}
int HistogramPhaseUnwrapping_Impl::Histogram::getNbrOfBins()
{
return nbrOfBins;
}
std::vector<HistogramPhaseUnwrapping_Impl::Edge> HistogramPhaseUnwrapping_Impl::
Histogram::getEdgesFromBin( int binIndex )
{
std::vector<HistogramPhaseUnwrapping_Impl::Edge> temp;
temp = bins[binIndex].getEdges();
return temp;
}
/* Method in which reliabilities are computed and edges are sorted in the histogram.
Increments are computed for each pixels.
*/
void HistogramPhaseUnwrapping_Impl::unwrapPhaseMap( InputArray wrappedPhaseMap,
OutputArray unwrappedPhaseMap,
InputArray shadowMask )
{
Mat &wPhaseMap = *(Mat*) wrappedPhaseMap.getObj();
Mat mask;
int rows = params.height;
int cols = params.width;
if( shadowMask.empty() )
{
mask.create(rows, cols, CV_8UC1);
mask = Scalar::all(255);
}
else
{
Mat &temp = *(Mat*) shadowMask.getObj();
temp.copyTo(mask);
}
computePixelsReliability(wPhaseMap, mask);
computeEdgesReliabilityAndCreateHistogram();
unwrapHistogram();
addIncrement(unwrappedPhaseMap);
}
//compute pixels reliabilities according to "A novel algorithm based on histogram processing of reliability for two-dimensional phase unwrapping"
void HistogramPhaseUnwrapping_Impl::computePixelsReliability( InputArray wrappedPhaseMap,
InputArray shadowMask )
{
int rows = params.height;
int cols = params.width;
Mat &wPhaseMap = *(Mat*) wrappedPhaseMap.getObj();
Mat &mask = *(Mat*) shadowMask.getObj();
int idx; //idx is used to store pixel position (idx = i*cols + j)
bool valid;//tells if a pixel is in the valid mask region
// H, V, D1, D2 are from the paper
float H, V, D1, D2, D;
/* used to store neighbours coordinates
* ul = upper left, um = upper middle, ur = upper right
* ml = middle left, mr = middle right
* ll = lower left, lm = lower middle, lr = lower right
*/
Point ul, um, ur, ml, mr, ll, lm, lr;
for( int i = 0; i < rows; ++i )
{
for( int j = 0; j < cols; ++j )
{
if( mask.at<uchar>( i, j ) != 0 ) //if pixel is in a valid region
{
if( i == 0 || i == rows - 1 || j == 0 || j == cols - 1 )
{
idx = i * cols + j;
valid = true;
Pixel p(wPhaseMap.at<float>(i, j), idx, valid,
static_cast<float>(16 * CV_PI * CV_PI), 0);
pixels.push_back(p);
}
else
{
ul = Point(j-1, i-1);
um = Point(j, i-1);
ur = Point(j+1, i-1);
ml = Point(j-1, i);
mr = Point(j+1, i);
ll = Point(j-1, i+1);
lm = Point(j, i+1);
lr = Point(j+1, i+1);
Mat neighbourhood = mask( Rect( j-1, i-1, 3, 3 ) );
Scalar meanValue = mean(neighbourhood);
/* if mean value is different from 255, it means that one of the neighbouring
* pixel is not valid -> pixel (i,j) is considered as being on the border.
*/
if( meanValue[0] != 255 )
{
idx = i * cols + j;
valid = true;
Pixel p(wPhaseMap.at<float>(i, j), idx, valid,
static_cast<float>(16 * CV_PI * CV_PI), 0);
pixels.push_back(p);
}
else
{
H = wrap(wPhaseMap.at<float>(ml.y, ml.x), wPhaseMap.at<float>(i, j))
- wrap(wPhaseMap.at<float>(i, j), wPhaseMap.at<float>(mr.y, mr.x));
V = wrap(wPhaseMap.at<float>(um.y, um.x), wPhaseMap.at<float>(i, j))
- wrap(wPhaseMap.at<float>(i, j), wPhaseMap.at<float>(lm.y, lm.x));
D1 = wrap(wPhaseMap.at<float>(ul.y, ul.x), wPhaseMap.at<float>(i, j))
- wrap(wPhaseMap.at<float>(i, j), wPhaseMap.at<float>(lr.y, lr.x));
D2 = wrap(wPhaseMap.at<float>(ur.y, ur.x), wPhaseMap.at<float>(i, j))
- wrap(wPhaseMap.at<float>(i, j), wPhaseMap.at<float>(ll.y, ll.x));
D = H * H + V * V + D1 * D1 + D2 * D2;
idx = i * cols + j;
valid = true;
Pixel p(wPhaseMap.at<float>(i, j), idx, valid, D, 0);
pixels.push_back(p);
}
}
}
else // pixel is not in a valid region. It's inverse reliability is set to the maximum
{
idx = i * cols + j;
valid = false;
Pixel p(wPhaseMap.at<float>(i, j), idx, valid,
static_cast<float>(16 * CV_PI * CV_PI), 0);
pixels.push_back(p);
}
}
}
}
/* Edges are created from the vector of pixels. We loop on the vector and create the edges
* that link the current pixel to his right neighbour (first edge) and the one that is under it (second edge)
*/
void HistogramPhaseUnwrapping_Impl::computeEdgesReliabilityAndCreateHistogram()
{
int row;
int col;
histogram.createBins(params.histThresh, params.nbrOfSmallBins, params.nbrOfLargeBins);
int nbrOfPixels = static_cast<int>(pixels.size());
/* Edges are built by considering a pixel and it's right-neighbour and lower-neighbour.
We discard non-valid pixels here.
*/
for( int i = 0; i < nbrOfPixels; ++i )
{
if( pixels[i].getValidity() )
{
row = pixels[i].getIndex() / params.width;
col = pixels[i].getIndex() % params.width;
if( row != params.height - 1 && col != params.width -1 )
{
int idxRight, idxDown;
idxRight = row * params.width + col + 1; // Pixel to the right
idxDown = ( row + 1 ) * params.width + col; // Pixel under pixel i.
createAndSortEdge(i, idxRight);
createAndSortEdge(i, idxDown);
}
else if( row != params.height - 1 && col == params.width - 1 )
{
int idxDown = ( row + 1 ) * params.width + col;
createAndSortEdge(i, idxDown);
}
else if( row == params.height - 1 && col != params.width - 1 )
{
int idxRight = row * params.width + col + 1;
createAndSortEdge(i, idxRight);
}
}
}
}
/*used along the previous method to sort edges in the histogram*/
void HistogramPhaseUnwrapping_Impl::createAndSortEdge( int idx1, int idx2 )
{
if( pixels[idx2].getValidity() )
{
float edgeReliability = pixels[idx1].getInverseReliability() +
pixels[idx2].getInverseReliability();
int inc = findInc(pixels[idx2].getPhaseValue(), pixels[idx1].getPhaseValue());
Edge e(idx1, idx2, inc);
if( edgeReliability < histogram.getThresh() )
{
int binIndex = static_cast<int> (ceil(edgeReliability / histogram.getSmallWidth()) - 1);
if( binIndex == -1 )
{
binIndex = 0;
}
histogram.addEdgeInBin(e, binIndex);
}
else
{
int binIndex = params.nbrOfSmallBins +
static_cast<int> (ceil((edgeReliability - histogram.getThresh()) /
histogram.getLargeWidth()) - 1);
histogram.addEdgeInBin(e, binIndex);
}
}
}
void HistogramPhaseUnwrapping_Impl::unwrapHistogram()
{
int nbrOfPixels = static_cast<int>(pixels.size());
int nbrOfBins = histogram.getNbrOfBins();
/* This vector is used to keep track of the number of pixels in each group and avoid useless group.
For example, if lastPixelAddedToGroup[10] is equal to 5, it means that pixel "5" was the last one
to be added to group 10. So, pixel "5" is the only one that has the correct value for parameter
"numberOfPixelsInGroup" in order to avoid a loop on all the pixels to update this number*/
std::vector<int> lastPixelAddedToGroup(nbrOfPixels, 0);
for( int i = 0; i < nbrOfBins; ++i )
{
std::vector<Edge> currentEdges = histogram.getEdgesFromBin(i);
int nbrOfEdgesInBin = static_cast<int>(currentEdges.size());
for( int j = 0; j < nbrOfEdgesInBin; ++j )
{
int pOneId = currentEdges[j].getPixOneId();
int pTwoId = currentEdges[j].getPixTwoId();
// Both pixels are in a single group.
if( pixels[pOneId].getSinglePixelGroup() && pixels[pTwoId].getSinglePixelGroup() )
{
float invRel1 = pixels[pOneId].getInverseReliability();
float invRel2 = pixels[pTwoId].getInverseReliability();
// Quality of pixel 2 is better than that of pixel 1 -> pixel 1 is added to group 2
if( invRel1 > invRel2 )
{
int newGroupId = pixels[pTwoId].getGroupId();
int newInc = pixels[pTwoId].getIncrement() + currentEdges[j].getIncrement();
pixels[pOneId].setGroupId(newGroupId);
pixels[pOneId].setIncrement(newInc);
lastPixelAddedToGroup[newGroupId] = pOneId; // Pixel 1 is the last one to be added to group 2
}
else
{
int newGroupId = pixels[pOneId].getGroupId();
int newInc = pixels[pOneId].getIncrement() - currentEdges[j].getIncrement();
pixels[pTwoId].setGroupId(newGroupId);
pixels[pTwoId].setIncrement(newInc);
lastPixelAddedToGroup[newGroupId] = pTwoId;
}
pixels[pOneId].setNbrOfPixelsInGroup(2);
pixels[pTwoId].setNbrOfPixelsInGroup(2);
pixels[pOneId].setSinglePixelGroup(false);
pixels[pTwoId].setSinglePixelGroup(false);
}
//p1 is in a single group, p2 is not -> p1 added to p2
else if( pixels[pOneId].getSinglePixelGroup() && !pixels[pTwoId].getSinglePixelGroup() )
{
int newGroupId = pixels[pTwoId].getGroupId();
int lastPix = lastPixelAddedToGroup[newGroupId];
int newNbrOfPixelsInGroup = pixels[lastPix].getNbrOfPixelsInGroup() + 1;
int newInc = pixels[pTwoId].getIncrement() + currentEdges[j].getIncrement();
pixels[pOneId].setGroupId(newGroupId);
pixels[pOneId].setNbrOfPixelsInGroup(newNbrOfPixelsInGroup);
pixels[pTwoId].setNbrOfPixelsInGroup(newNbrOfPixelsInGroup);
pixels[pOneId].setIncrement(newInc);
pixels[pOneId].setSinglePixelGroup(false);
lastPixelAddedToGroup[newGroupId] = pOneId;
}
//p2 is in a single group, p1 is not -> p2 added to p1
else if( !pixels[pOneId].getSinglePixelGroup() && pixels[pTwoId].getSinglePixelGroup() )
{
int newGroupId = pixels[pOneId].getGroupId();
int lastPix = lastPixelAddedToGroup[newGroupId];
int newNbrOfPixelsInGroup = pixels[lastPix].getNbrOfPixelsInGroup() + 1;
int newInc = pixels[pOneId].getIncrement() - currentEdges[j].getIncrement();
pixels[pTwoId].setGroupId(newGroupId);
pixels[pTwoId].setNbrOfPixelsInGroup(newNbrOfPixelsInGroup);
pixels[pOneId].setNbrOfPixelsInGroup(newNbrOfPixelsInGroup);
pixels[pTwoId].setIncrement(newInc);
pixels[pTwoId].setSinglePixelGroup(false);
lastPixelAddedToGroup[newGroupId] = pTwoId;
}
//p1 and p2 are in two different groups
else if( pixels[pOneId].getGroupId() != pixels[pTwoId].getGroupId() )
{
int pOneGroupId = pixels[pOneId].getGroupId();
int pTwoGroupId = pixels[pTwoId].getGroupId();
float invRel1 = pixels[pOneId].getInverseReliability();
float invRel2 = pixels[pTwoId].getInverseReliability();
int lastAddedToGroupOne = lastPixelAddedToGroup[pOneGroupId];
int lastAddedToGroupTwo = lastPixelAddedToGroup[pTwoGroupId];
int nbrOfPixelsInGroupOne = pixels[lastAddedToGroupOne].getNbrOfPixelsInGroup();
int nbrOfPixelsInGroupTwo = pixels[lastAddedToGroupTwo].getNbrOfPixelsInGroup();
int totalNbrOfPixels = nbrOfPixelsInGroupOne + nbrOfPixelsInGroupTwo;
if( nbrOfPixelsInGroupOne < nbrOfPixelsInGroupTwo ||
(nbrOfPixelsInGroupOne == nbrOfPixelsInGroupTwo && invRel1 >= invRel2) ) //group p1 added to group p2
{
pixels[pTwoId].setNbrOfPixelsInGroup(totalNbrOfPixels);
pixels[pOneId].setNbrOfPixelsInGroup(totalNbrOfPixels);
int inc = pixels[pTwoId].getIncrement() + currentEdges[j].getIncrement() -
pixels[pOneId].getIncrement();
lastPixelAddedToGroup[pTwoGroupId] = pOneId;
for( int k = 0; k < nbrOfPixels; ++k )
{
if( pixels[k].getGroupId() == pOneGroupId )
{
pixels[k].setGroupId(pTwoGroupId);
pixels[k].changeIncrement(inc);
}
}
}
else if( nbrOfPixelsInGroupOne > nbrOfPixelsInGroupTwo ||
(nbrOfPixelsInGroupOne == nbrOfPixelsInGroupTwo && invRel2 > invRel1) ) //group p2 added to group p1
{
int oldGroupId = pTwoGroupId;
pixels[pOneId].setNbrOfPixelsInGroup(totalNbrOfPixels);
pixels[pTwoId].setNbrOfPixelsInGroup(totalNbrOfPixels);
int inc = pixels[pOneId].getIncrement() - currentEdges[j].getIncrement() -
pixels[pTwoId].getIncrement();
lastPixelAddedToGroup[pOneGroupId] = pTwoId;
for( int k = 0; k < nbrOfPixels; ++k )
{
if( pixels[k].getGroupId() == oldGroupId )
{
pixels[k].setGroupId(pOneGroupId);
pixels[k].changeIncrement(inc);
}
}
}
}
}
}
}
void HistogramPhaseUnwrapping_Impl::addIncrement( OutputArray unwrappedPhaseMap )
{
Mat &uPhaseMap = *(Mat*) unwrappedPhaseMap.getObj();
int rows = params.height;
int cols = params.width;
if( uPhaseMap.empty() )
uPhaseMap.create(rows, cols, CV_32FC1);
int nbrOfPixels = static_cast<int>(pixels.size());
for( int i = 0; i < nbrOfPixels; ++i )
{
int row = pixels[i].getIndex() / params.width;
int col = pixels[i].getIndex() % params.width;
if( pixels[i].getValidity() )
{
uPhaseMap.at<float>(row, col) = pixels[i].getPhaseValue() +
static_cast<float>(2 * CV_PI * pixels[i].getIncrement());
}
}
}
float HistogramPhaseUnwrapping_Impl::wrap( float a, float b )
{
float result;
float difference = a - b;
float pi = static_cast<float>(CV_PI);
if( difference > pi )
result = ( difference - 2 * pi );
else if( difference < -pi )
result = ( difference + 2 * pi );
else
result = difference;
return result;
}
int HistogramPhaseUnwrapping_Impl::findInc( float a, float b )
{
float difference;
int wrapValue;
difference = b - a;
float pi = static_cast<float>(CV_PI);
if( difference > pi )
wrapValue = -1;
else if( difference < -pi )
wrapValue = 1;
else
wrapValue = 0;
return wrapValue;
}
//create a Mat that shows pixel inverse reliabilities
void HistogramPhaseUnwrapping_Impl::getInverseReliabilityMap( OutputArray inverseReliabilityMap )
{
int rows = params.height;
int cols = params.width;
Mat &reliabilityMap_ = *(Mat*) inverseReliabilityMap.getObj();
if( reliabilityMap_.empty() )
reliabilityMap_.create(rows, cols, CV_32FC1);
for( int i = 0; i < rows; ++i )
{
for( int j = 0; j < cols; ++j )
{
int idx = i * cols + j;
reliabilityMap_.at<float>(i, j) = pixels[idx].getInverseReliability();
}
}
}
Ptr<HistogramPhaseUnwrapping> HistogramPhaseUnwrapping::create( const HistogramPhaseUnwrapping::Params
&params )
{
return makePtr<HistogramPhaseUnwrapping_Impl>(params);
}
}
}
\ No newline at end of file
/*M///////////////////////////////////////////////////////////////////////////////////////
//
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
//
// By downloading, copying, installing or using the software you agree to this license.
// If you do not agree to this license, do not download, install,
// copy or use the software.
//
//
// License Agreement
// For Open Source Computer Vision Library
//
// Copyright (C) 2015, OpenCV Foundation, all rights reserved.
// Third party copyrights are property of their respective owners.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// * Redistribution's of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistribution's in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// * The name of the copyright holders may not be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// This software is provided by the copyright holders and contributors "as is" and
// any express or implied warranties, including, but not limited to, the implied
// warranties of merchantability and fitness for a particular purpose are disclaimed.
// In no event shall the Intel Corporation or contributors be liable for any direct,
// indirect, incidental, special, exemplary, or consequential damages
// (including, but not limited to, procurement of substitute goods or services;
// loss of use, data, or profits; or business interruption) however caused
// and on any theory of liability, whether in contract, strict liability,
// or tort (including negligence or otherwise) arising in any way out of
// the use of this software, even if advised of the possibility of such damage.
//
//M*/
#ifndef __OPENCV_PRECOMP_H__
#define __OPENCV_PRECOMP_H__
#include "opencv2/phase_unwrapping.hpp"
#include "opencv2/core/utility.hpp"
#include "opencv2/core/private.hpp"
#endif
\ No newline at end of file
#include "test_precomp.hpp"
CV_TEST_MAIN("cv")
\ No newline at end of file
#ifdef __GNUC__
# pragma GCC diagnostic ignored "-Wmissing-declarations"
# if defined __clang__ || defined __APPLE__
# pragma GCC diagnostic ignored "-Wmissing-prototypes"
# pragma GCC diagnostic ignored "-Wextra"
# endif
#endif
#ifndef __OPENCV_TEST_PRECOMP_HPP__
#define __OPENCV_TEST_PRECOMP_HPP__
#include "opencv2/ts.hpp"
#include "opencv2/phase_unwrapping.hpp"
#include <opencv2/rgbd.hpp>
#include <iostream>
#endif
/*M///////////////////////////////////////////////////////////////////////////////////////
//
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
//
// By downloading, copying, installing or using the software you agree to this license.
// If you do not agree to this license, do not download, install,
// copy or use the software.
//
//
// License Agreement
// For Open Source Computer Vision Library
//
// Copyright (C) 2015, OpenCV Foundation, all rights reserved.
// Third party copyrights are property of their respective owners.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// * Redistribution's of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistribution's in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// * The name of the copyright holders may not be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// This software is provided by the copyright holders and contributors "as is" and
// any express or implied warranties, including, but not limited to, the implied
// warranties of merchantability and fitness for a particular purpose are disclaimed.
// In no event shall the Intel Corporation or contributors be liable for any direct,
// indirect, incidental, special, exemplary, or consequential damages
// (including, but not limited to, procurement of substitute goods or services;
// loss of use, data, or profits; or business interruption) however caused
// and on any theory of liability, whether in contract, strict liability,
// or tort (including negligence or otherwise) arising in any way out of
// the use of this software, even if advised of the possibility of such damage.
//
//M*/
#include "test_precomp.hpp"
using namespace std;
using namespace cv;
class CV_Unwrapping : public cvtest::BaseTest
{
public:
CV_Unwrapping();
~CV_Unwrapping();
protected:
void run(int);
};
CV_Unwrapping::CV_Unwrapping(){}
CV_Unwrapping::~CV_Unwrapping(){}
void CV_Unwrapping::run( int )
{
int rows = 600;
int cols = 800;
int max = 50;
Mat ramp(rows, cols, CV_32FC1);
Mat wrappedRamp(rows, cols, CV_32FC1);
Mat unwrappedRamp;
Mat rowValues(1, cols, CV_32FC1);
Mat wrappedRowValues(1, cols, CV_32FC1);
for( int i = 0; i < cols; ++i )
{
float v = (float)i*(float)max/(float)cols;
rowValues.at<float>(0, i) = v;
wrappedRowValues.at<float>(0, i) = atan2(sin(v), cos(v));
}
for( int i = 0; i < rows; ++i )
{
rowValues.row(0).copyTo(ramp.row(i));
wrappedRowValues.row(0).copyTo(wrappedRamp.row(i));
}
phase_unwrapping::HistogramPhaseUnwrapping::Params params;
params.width = cols;
params.height = rows;
Ptr<phase_unwrapping::HistogramPhaseUnwrapping> phaseUnwrapping = phase_unwrapping::HistogramPhaseUnwrapping::create(params);
phaseUnwrapping->unwrapPhaseMap(wrappedRamp, unwrappedRamp);
for(int i = 0; i < rows; ++i )
{
for( int j = 0; j < cols; ++ j )
{
EXPECT_NEAR(ramp.at<float>(i, j), unwrappedRamp.at<float>(i, j), 0.001);
}
}
}
TEST( HistogramPhaseUnwrapping, unwrapPhaseMap )
{
CV_Unwrapping test;
test.safe_run();
}
\ No newline at end of file
Phase Unwrapping tutorial {#tutorial_unwrap_phase_map}
=======================================================
- @subpage tutorial_unwrap
_Compatibility:_ \> OpenCV 3.0.0
_Author:_ Ambroise Moreau
You will learn how to use the phase unwrapping module.
Unwrap two-dimensional phase maps {#tutorial_unwrap}
==============
Goal
----
In this tutorial, you will learn how to use the phase unwrapping module to unwrap two-dimensional phase maps. The implementation is based on @cite histogramUnwrapping.
Code
----
@include phase_unwrapping/samples/unwrap.cpp
Explanation
-----------
To use this example, wrapped phase map values should be stored in a yml file as CV_32FC1 Mat, under the name "phaseValues". Path to the data and a name to save the unwrapped phase map must be set in the command line. The results are saved with floating point precision in a yml file and as an 8-bit image for visualization purpose.
Some parameters can be chosen by the user:
- histThresh is a parameter used to divide the histogram in two parts. Bins before histThresh are smaller than the ones after histThresh. (Default value is 3*pi*pi).
- nbrOfSmallBins is the number of bins between 0 and histThresh. (Default value is 10).
- nbrOfLargeBins is the number of bins between histThresh and 32*pi*pi. (Default value is 5).
@code{.cpp}
phase_unwrapping::HistogramPhaseUnwrapping::Params params;
CommandLineParser parser(argc, argv, keys);
String inputPath = parser.get<String>(0);
String outputUnwrappedName = parser.get<String>(1);
String outputWrappedName = parser.get<String>(2);
if( inputPath.empty() || outputUnwrappedName.empty() )
{
help();
return -1;
}
FileStorage fsInput(inputPath, FileStorage::READ);
FileStorage fsOutput(outputUnwrappedName + ".yml", FileStorage::WRITE);
Mat wPhaseMap;
Mat uPhaseMap;
Mat reliabilities;
fsInput["phaseValues"] >> wPhaseMap;
fsInput.release();
params.width = wPhaseMap.cols;
params.height = wPhaseMap.rows;
Ptr<phase_unwrapping::HistogramPhaseUnwrapping> phaseUnwrapping = phase_unwrapping::HistogramPhaseUnwrapping::create(params);
@endcode
The wrapped phase map is unwrapped and the result is saved in a yml file. We can also get the reliabilities map for visualization purpose. The unwrapped phase map and the reliabilities map are converted to 8-bit images in order to be saved as png files.
@code{.cpp}
phaseUnwrapping->unwrapPhaseMap(wPhaseMap, uPhaseMap);
fsOutput << "phaseValues" << uPhaseMap;
fsOutput.release();
phaseUnwrapping->getInverseReliabilityMap(reliabilities);
Mat uPhaseMap8, wPhaseMap8, reliabilities8;
wPhaseMap.convertTo(wPhaseMap8, CV_8U, 255, 128);
uPhaseMap.convertTo(uPhaseMap8, CV_8U, 1, 128);
reliabilities.convertTo(reliabilities8, CV_8U, 255,128);
imshow("reliabilities", reliabilities);
imshow("wrapped phase map", wPhaseMap8);
imshow("unwrapped phase map", uPhaseMap8);
imwrite(outputUnwrappedName + ".png", uPhaseMap8);
imwrite("reliabilities.png", reliabilities8);
@endcode
set(the_description "Structured Light API")
ocv_define_module(structured_light opencv_core opencv_calib3d opencv_imgproc opencv_highgui opencv_features2d opencv_rgbd OPTIONAL opencv_viz WRAP python java)
ocv_define_module(structured_light opencv_core opencv_calib3d opencv_imgproc opencv_highgui opencv_features2d opencv_rgbd opencv_phase_unwrapping OPTIONAL opencv_viz WRAP python java)
......@@ -14,3 +14,13 @@
pages = {827-849},
year = {April 2004},
}
@article{faps,
title={Accurate dynamic 3D sensing with Fourier-assisted phase shifting},
author={Cong, Pengyu and Xiong, Zhiwei and Zhang, Yueyi and Zhao, Shenghui and Wu, Feng},
journal={IEEE Journal of Selected Topics in Signal Processing},
volume={9},
number={3},
pages={396--408},
year={2015},
}
......@@ -45,6 +45,7 @@
#include "opencv2/structured_light/structured_light.hpp"
#include "opencv2/structured_light/graycodepattern.hpp"
#include "opencv2/structured_light/sinusoidalpattern.hpp"
/** @defgroup structured_light Structured Light API
......
/*M///////////////////////////////////////////////////////////////////////////////////////
//
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
//
// By downloading, copying, installing or using the software you agree to this license.
// If you do not agree to this license, do not download, install,
// copy or use the software.
//
//
// License Agreement
// For Open Source Computer Vision Library
//
// Copyright (C) 2015, OpenCV Foundation, all rights reserved.
// Third party copyrights are property of their respective owners.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// * Redistribution's of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistribution's in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// * The name of the copyright holders may not be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// This software is provided by the copyright holders and contributors "as is" and
// any express or implied warranties, including, but not limited to, the implied
// warranties of merchantability and fitness for a particular purpose are disclaimed.
// In no event shall the Intel Corporation or contributors be liable for any direct,
// indirect, incidental, special, exemplary, or consequential damages
// (including, but not limited to, procurement of substitute goods or services;
// loss of use, data, or profits; or business interruption) however caused
// and on any theory of liability, whether in contract, strict liability,
// or tort (including negligence or otherwise) arising in any way out of
// the use of this software, even if advised of the possibility of such damage.
//
//M*/
#ifndef __OPENCV_SINUSOIDAL_PATTERN_HPP__
#define __OPENCV_SINUSOIDAL_PATTERN_HPP__
#include "opencv2/core.hpp"
#include "opencv2/imgproc.hpp"
#include "opencv2/structured_light/structured_light.hpp"
#include <opencv2/phase_unwrapping.hpp>
#include <opencv2/calib3d.hpp>
namespace cv {
namespace structured_light {
//! @addtogroup structured_light
//! @{
//! Type of sinusoidal pattern profilometry methods.
enum{
FTP = 0,
PSP = 1,
FAPS = 2
};
/**
* @brief Class implementing Fourier transform profilometry (FTP) , phase-shifting profilometry (PSP)
* and Fourier-assisted phase-shifting profilometry (FAPS) based on @cite faps.
* This class generates sinusoidal patterns that can be used with FTP, PSP and FAPS.
*/
class CV_EXPORTS_W SinusoidalPattern : public StructuredLightPattern
{
public:
/**
* @brief Parameters of SinusoidalPattern constructor
* @param width Projector's width.
* @param height Projector's height.
* @param nbrOfPeriods Number of period along the patterns direction.
* @param shiftValue Phase shift between two consecutive patterns.
* @param methodId Allow to choose between FTP, PSP and FAPS.
* @param nbrOfPixelsBetweenMarkers Number of pixels between two consecutive markers on the same row.
* @param setMarkers Allow to set markers on the patterns.
* @param markersLocation vector used to store markers location on the patterns.
*/
struct CV_EXPORTS Params
{
Params();
int width;
int height;
int nbrOfPeriods;
float shiftValue;
int methodId;
int nbrOfPixelsBetweenMarkers;
bool horizontal;
bool setMarkers;
std::vector<Point2f> markersLocation;
};
/**
* @brief Constructor.
* @param parameters SinusoidalPattern parameters SinusoidalPattern::Params: width, height of the projector and patterns parameters.
*
*/
static Ptr<SinusoidalPattern> create( const SinusoidalPattern::Params &parameters =
SinusoidalPattern::Params() );
/**
* @brief Compute a wrapped phase map from sinusoidal patterns.
* @param patternImages Input data to compute the wrapped phase map.
* @param wrappedPhaseMap Wrapped phase map obtained through one of the three methods.
* @param shadowMask Mask used to discard shadow regions.
* @param fundamental Fundamental matrix used to compute epipolar lines and ease the matching step.
*/
CV_WRAP
virtual void computePhaseMap( InputArrayOfArrays patternImages,
OutputArray wrappedPhaseMap,
OutputArray shadowMask = noArray(),
InputArray fundamental = noArray()) = 0;
/**
* @brief Unwrap the wrapped phase map to remove phase ambiguities.
* @param wrappedPhaseMap The wrapped phase map computed from the pattern.
* @param unwrappedPhaseMap The unwrapped phase map used to find correspondences between the two devices.
* @param camSize Resolution of the camera.
* @param shadowMask Mask used to discard shadow regions.
*/
CV_WRAP
virtual void unwrapPhaseMap( InputArrayOfArrays wrappedPhaseMap,
OutputArray unwrappedPhaseMap,
cv::Size camSize,
InputArray shadowMask = noArray() ) = 0;
/**
* @brief Find correspondences between the two devices thanks to unwrapped phase maps.
* @param projUnwrappedPhaseMap Projector's unwrapped phase map.
* @param camUnwrappedPhaseMap Camera's unwrapped phase map.
* @param matches Images used to display correspondences map.
*/
CV_WRAP
virtual void findProCamMatches( InputArray projUnwrappedPhaseMap, InputArray camUnwrappedPhaseMap,
OutputArrayOfArrays matches ) = 0;
/**
* @brief compute the data modulation term.
* @param patternImages captured images with projected patterns.
* @param dataModulationTerm Mat where the data modulation term is saved.
* @param shadowMask Mask used to discard shadow regions.
*/
CV_WRAP
virtual void computeDataModulationTerm( InputArrayOfArrays patternImages,
OutputArray dataModulationTerm,
InputArray shadowMask ) = 0;
};
//! @}
}
}
#endif
\ No newline at end of file
......@@ -78,9 +78,10 @@ class CV_EXPORTS_W StructuredLightPattern : public virtual Algorithm
@note All the images must be at the same resolution.
*/
CV_WRAP
virtual bool decode( InputArrayOfArrays patternImages, OutputArray disparityMap, InputArrayOfArrays blackImages =
noArray(),
InputArrayOfArrays whiteImages = noArray(), int flags = DECODE_3D_UNDERWORLD ) const = 0;
virtual bool decode( InputArrayOfArrays patternImages, OutputArray disparityMap,
InputArrayOfArrays blackImages = noArray(),
InputArrayOfArrays whiteImages = noArray(),
int flags = DECODE_3D_UNDERWORLD ) const = 0;
};
//! @}
......
/*M///////////////////////////////////////////////////////////////////////////////////////
//
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
//
// By downloading, copying, installing or using the software you agree to this license.
// If you do not agree to this license, do not download, install,
// copy or use the software.
//
//
// License Agreement
// For Open Source Computer Vision Library
//
// Copyright (C) 2015, OpenCV Foundation, all rights reserved.
// Third party copyrights are property of their respective owners.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// * Redistribution's of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistribution's in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// * The name of the copyright holders may not be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// This software is provided by the copyright holders and contributors "as is" and
// any express or implied warranties, including, but not limited to, the implied
// warranties of merchantability and fitness for a particular purpose are disclaimed.
// In no event shall the Intel Corporation or contributors be liable for any direct,
// indirect, incidental, special, exemplary, or consequential damages
// (including, but not limited to, procurement of substitute goods or services;
// loss of use, data, or profits; or business interruption) however caused
// and on any theory of liability, whether in contract, strict liability,
// or tort (including negligence or otherwise) arising in any way out of
// the use of this software, even if advised of the possibility of such damage.
//
//M*/
#include <opencv2/highgui.hpp>
#include <vector>
#include <iostream>
#include <fstream>
#include <opencv2/core.hpp>
#include <opencv2/core/utility.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/calib3d.hpp>
#include <opencv2/structured_light.hpp>
#include <opencv2/phase_unwrapping.hpp>
using namespace cv;
using namespace std;
static const char* keys =
{
"{@width | | Projector width}"
"{@height | | Projector height}"
"{@periods | | Number of periods}"
"{@setMarkers | | Patterns with or without markers}"
"{@horizontal | | Patterns are horizontal}"
"{@methodId | | Method to be used}"
"{@outputPatternPath | | Path to save patterns}"
"{@outputWrappedPhasePath | | Path to save wrapped phase map}"
"{@outputUnwrappedPhasePath | | Path to save unwrapped phase map}"
"{@outputCapturePath | | Path to save the captures}"
"{@reliabilitiesPath | | Path to save reliabilities}"
};
static void help()
{
cout << "\nThis example generates sinusoidal patterns" << endl;
cout << "To call: ./example_structured_light_createsinuspattern <width> <height>"
" <number_of_period> <set_marker>(bool) <horizontal_patterns>(bool) <method_id>"
" <output_captures_path> <output_pattern_path>(optional) <output_wrapped_phase_path> (optional)"
" <output_unwrapped_phase_path>" << endl;
}
int main(int argc, char **argv)
{
if( argc < 2 )
{
help();
return -1;
}
structured_light::SinusoidalPattern::Params params;
phase_unwrapping::HistogramPhaseUnwrapping::Params paramsUnwrapping;
// Retrieve parameters written in the command line
CommandLineParser parser(argc, argv, keys);
params.width = parser.get<int>(0);
params.height = parser.get<int>(1);
params.nbrOfPeriods = parser.get<int>(2);
params.setMarkers = parser.get<bool>(3);
params.horizontal = parser.get<bool>(4);
params.methodId = parser.get<int>(5);
String outputCapturePath = parser.get<String>(6);
params.shiftValue = static_cast<float>(2 * CV_PI / 3);
params.nbrOfPixelsBetweenMarkers = 70;
String outputPatternPath = parser.get<String>(7);
String outputWrappedPhasePath = parser.get<String>(8);
String outputUnwrappedPhasePath = parser.get<String>(9);
String reliabilitiesPath = parser.get<String>(10);
Ptr<structured_light::SinusoidalPattern> sinus = structured_light::SinusoidalPattern::create(params);
Ptr<phase_unwrapping::HistogramPhaseUnwrapping> phaseUnwrapping;
vector<Mat> patterns;
Mat shadowMask;
Mat unwrappedPhaseMap, unwrappedPhaseMap8;
Mat wrappedPhaseMap, wrappedPhaseMap8;
//Generate sinusoidal patterns
sinus->generate(patterns);
VideoCapture cap(CAP_PVAPI);
if( !cap.isOpened() )
{
cout << "Camera could not be opened" << endl;
return -1;
}
cap.set(CAP_PROP_PVAPI_PIXELFORMAT, CAP_PVAPI_PIXELFORMAT_MONO8);
namedWindow("pattern", WINDOW_NORMAL);
setWindowProperty("pattern", WND_PROP_FULLSCREEN, WINDOW_FULLSCREEN);
imshow("pattern", patterns[0]);
cout << "Press any key when ready" << endl;
waitKey(0);
int nbrOfImages = 30;
int count = 0;
vector<Mat> img(nbrOfImages);
Size camSize(-1, -1);
while( count < nbrOfImages )
{
for(int i = 0; i < (int)patterns.size(); ++i )
{
imshow("pattern", patterns[i]);
waitKey(300);
cap >> img[count];
count += 1;
}
}
cout << "press enter when ready" << endl;
bool loop = true;
while ( loop )
{
char c = (char) waitKey(0);
if( c == 10 )
{
loop = false;
}
}
switch(params.methodId)
{
case structured_light::FTP:
for( int i = 0; i < nbrOfImages; ++i )
{
/*We need three images to compute the shadow mask, as described in the reference paper
* even if the phase map is computed from one pattern only
*/
vector<Mat> captures;
if( i == nbrOfImages - 2 )
{
captures.push_back(img[i]);
captures.push_back(img[i-1]);
captures.push_back(img[i+1]);
}
else if( i == nbrOfImages - 1 )
{
captures.push_back(img[i]);
captures.push_back(img[i-1]);
captures.push_back(img[i-2]);
}
else
{
captures.push_back(img[i]);
captures.push_back(img[i+1]);
captures.push_back(img[i+2]);
}
sinus->computePhaseMap(captures, wrappedPhaseMap, shadowMask);
if( camSize.height == -1 )
{
camSize.height = img[i].rows;
camSize.width = img[i].cols;
paramsUnwrapping.height = camSize.height;
paramsUnwrapping.width = camSize.width;
phaseUnwrapping =
phase_unwrapping::HistogramPhaseUnwrapping::create(paramsUnwrapping);
}
sinus->unwrapPhaseMap(wrappedPhaseMap, unwrappedPhaseMap, camSize, shadowMask);
phaseUnwrapping->unwrapPhaseMap(wrappedPhaseMap, unwrappedPhaseMap, shadowMask);
Mat reliabilities, reliabilities8;
phaseUnwrapping->getInverseReliabilityMap(reliabilities);
reliabilities.convertTo(reliabilities8, CV_8U, 255,128);
ostringstream tt;
tt << i;
imwrite(reliabilitiesPath + tt.str() + ".png", reliabilities8);
unwrappedPhaseMap.convertTo(unwrappedPhaseMap8, CV_8U, 1, 128);
wrappedPhaseMap.convertTo(wrappedPhaseMap8, CV_8U, 255, 128);
if( !outputUnwrappedPhasePath.empty() )
{
ostringstream name;
name << i;
imwrite(outputUnwrappedPhasePath + "_FTP_" + name.str() + ".png", unwrappedPhaseMap8);
}
if( !outputWrappedPhasePath.empty() )
{
ostringstream name;
name << i;
imwrite(outputWrappedPhasePath + "_FTP_" + name.str() + ".png", wrappedPhaseMap8);
}
}
break;
case structured_light::PSP:
case structured_light::FAPS:
for( int i = 0; i < nbrOfImages - 2; ++i )
{
vector<Mat> captures;
captures.push_back(img[i]);
captures.push_back(img[i+1]);
captures.push_back(img[i+2]);
sinus->computePhaseMap(captures, wrappedPhaseMap, shadowMask);
if( camSize.height == -1 )
{
camSize.height = img[i].rows;
camSize.width = img[i].cols;
paramsUnwrapping.height = camSize.height;
paramsUnwrapping.width = camSize.width;
phaseUnwrapping =
phase_unwrapping::HistogramPhaseUnwrapping::create(paramsUnwrapping);
}
sinus->unwrapPhaseMap(wrappedPhaseMap, unwrappedPhaseMap, camSize, shadowMask);
unwrappedPhaseMap.convertTo(unwrappedPhaseMap8, CV_8U, 1, 128);
wrappedPhaseMap.convertTo(wrappedPhaseMap8, CV_8U, 255, 128);
phaseUnwrapping->unwrapPhaseMap(wrappedPhaseMap, unwrappedPhaseMap, shadowMask);
Mat reliabilities, reliabilities8;
phaseUnwrapping->getInverseReliabilityMap(reliabilities);
reliabilities.convertTo(reliabilities8, CV_8U, 255,128);
ostringstream tt;
tt << i;
imwrite(reliabilitiesPath + tt.str() + ".png", reliabilities8);
if( !outputUnwrappedPhasePath.empty() )
{
ostringstream name;
name << i;
if( params.methodId == structured_light::PSP )
imwrite(outputUnwrappedPhasePath + "_PSP_" + name.str() + ".png", unwrappedPhaseMap8);
else
imwrite(outputUnwrappedPhasePath + "_FAPS_" + name.str() + ".png", unwrappedPhaseMap8);
}
if( !outputWrappedPhasePath.empty() )
{
ostringstream name;
name << i;
if( params.methodId == structured_light::PSP )
imwrite(outputWrappedPhasePath + "_PSP_" + name.str() + ".png", wrappedPhaseMap8);
else
imwrite(outputWrappedPhasePath + "_FAPS_" + name.str() + ".png", wrappedPhaseMap8);
}
if( !outputCapturePath.empty() )
{
ostringstream name;
name << i;
if( params.methodId == structured_light::PSP )
imwrite(outputCapturePath + "_PSP_" + name.str() + ".png", img[i]);
else
imwrite(outputCapturePath + "_FAPS_" + name.str() + ".png", img[i]);
if( i == nbrOfImages - 3 )
{
if( params.methodId == structured_light::PSP )
{
ostringstream nameBis;
nameBis << i+1;
ostringstream nameTer;
nameTer << i+2;
imwrite(outputCapturePath + "_PSP_" + nameBis.str() + ".png", img[i+1]);
imwrite(outputCapturePath + "_PSP_" + nameTer.str() + ".png", img[i+2]);
}
else
{
ostringstream nameBis;
nameBis << i+1;
ostringstream nameTer;
nameTer << i+2;
imwrite(outputCapturePath + "_FAPS_" + nameBis.str() + ".png", img[i+1]);
imwrite(outputCapturePath + "_FAPS_" + nameTer.str() + ".png", img[i+2]);
}
}
}
}
break;
default:
cout << "error" << endl;
}
cout << "done" << endl;
if( !outputPatternPath.empty() )
{
for( int i = 0; i < 3; ++ i )
{
ostringstream name;
name << i + 1;
imwrite(outputPatternPath + name.str() + ".png", patterns[i]);
}
}
loop = true;
while( loop )
{
char key = (char) waitKey(0);
if( key == 27 )
{
loop = false;
}
}
return 0;
}
\ No newline at end of file
/*M///////////////////////////////////////////////////////////////////////////////////////
//
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
//
// By downloading, copying, installing or using the software you agree to this license.
// If you do not agree to this license, do not download, install,
// copy or use the software.
//
//
// License Agreement
// For Open Source Computer Vision Library
//
// Copyright (C) 2015, OpenCV Foundation, all rights reserved.
// Third party copyrights are property of their respective owners.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// * Redistribution's of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistribution's in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// * The name of the copyright holders may not be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// This software is provided by the copyright holders and contributors "as is" and
// any express or implied warranties, including, but not limited to, the implied
// warranties of merchantability and fitness for a particular purpose are disclaimed.
// In no event shall the Intel Corporation or contributors be liable for any direct,
// indirect, incidental, special, exemplary, or consequential damages
// (including, but not limited to, procurement of substitute goods or services;
// loss of use, data, or profits; or business interruption) however caused
// and on any theory of liability, whether in contract, strict liability,
// or tort (including negligence or otherwise) arising in any way out of
// the use of this software, even if advised of the possibility of such damage.
//
//M*/
#include <opencv2/highgui.hpp>
#include <vector>
#include <iostream>
#include <fstream>
#include <opencv2/core.hpp>
#include <opencv2/core/utility.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/calib3d.hpp>
using namespace std;
using namespace cv;
static const char* keys =
{
"{@camSettingsPath | | Path of camera calibration file}"
"{@projSettingsPath | | Path of projector settings}"
"{@patternPath | | Path to checkerboard pattern}"
"{@outputName | | Base name for the calibration data}"
};
static void help()
{
cout << "\nThis example calibrates a camera and a projector" << endl;
cout << "To call: ./example_structured_light_projectorcalibration <cam_settings_path> "
" <proj_settings_path> <chessboard_path> <calibration_basename>"
" cam settings are parameters about the chessboard that needs to be detected to"
" calibrate the camera and proj setting are the same kind of parameters about the chessboard"
" that needs to be detected to calibrate the projector" << endl;
}
enum calibrationPattern{ CHESSBOARD, CIRCLES_GRID, ASYMETRIC_CIRCLES_GRID };
struct Settings
{
Settings();
int patternType;
Size patternSize;
Size subpixelSize;
Size imageSize;
float squareSize;
int nbrOfFrames;
};
void loadSettings( String path, Settings &sttngs );
void createObjectPoints( vector<Point3f> &patternCorners, Size patternSize, float squareSize,
int patternType );
void createProjectorObjectPoints( vector<Point2f> &patternCorners, Size patternSize, float squareSize,
int patternType );
double calibrate( vector< vector<Point3f> > objPoints, vector< vector<Point2f> > imgPoints,
Mat &cameraMatrix, Mat &distCoeffs, vector<Mat> &r, vector<Mat> &t, Size imgSize );
void fromCamToWorld( Mat cameraMatrix, vector<Mat> rV, vector<Mat> tV,
vector< vector<Point2f> > imgPoints, vector< vector<Point3f> > &worldPoints );
void saveCalibrationResults( String path, Mat camK, Mat camDistCoeffs, Mat projK, Mat projDistCoeffs,
Mat fundamental );
void saveCalibrationData( String path, vector<Mat> T1, vector<Mat> T2, vector<Mat> ptsProjCam, vector<Mat> ptsProjProj, vector<Mat> ptsProjCamN, vector<Mat> ptsProjProjN);
void normalize(const Mat &pts, const int& dim, Mat& normpts, Mat &T);
void fromVectorToMat( vector<Point2f> v, Mat &pts);
void fromMatToVector( Mat pts, vector<Point2f> &v );
int main( int argc, char **argv )
{
VideoCapture cap(CAP_PVAPI);
Mat frame;
int nbrOfValidFrames = 0;
vector< vector<Point2f> > imagePointsCam, imagePointsProj, PointsInProj, imagePointsProjN, pointsInProjN;
vector< vector<Point3f> > objectPointsCam, worldPointsProj;
vector<Point3f> tempCam;
vector<Point2f> tempProj;
vector<Mat> T1, T2;
vector<Mat> projInProj, projInCam;
vector<Mat> projInProjN, projInCamN;
vector<Mat> rVecs, tVecs, projectorRVecs, projectorTVecs;
Mat cameraMatrix, distCoeffs, projectorMatrix, projectorDistCoeffs;
Mat pattern;
vector<Mat> images;
Settings camSettings, projSettings;
CommandLineParser parser(argc, argv, keys);
String camSettingsPath = parser.get<String>(0);
String projSettingsPath = parser.get<String>(1);
String patternPath = parser.get<String>(2);
String outputName = parser.get<String>(3);
if( camSettingsPath.empty() || projSettingsPath.empty() || patternPath.empty() || outputName.empty() ){
help();
return -1;
}
pattern = imread(patternPath);
loadSettings(camSettingsPath, camSettings);
loadSettings(projSettingsPath, projSettings);
projSettings.imageSize = Size(pattern.rows, pattern.cols);
createObjectPoints(tempCam, camSettings.patternSize,
camSettings.squareSize, camSettings.patternType);
createProjectorObjectPoints(tempProj, projSettings.patternSize,
projSettings.squareSize, projSettings.patternType);
if(!cap.isOpened())
{
cout << "Camera could not be opened" << endl;
return -1;
}
cap.set(CAP_PROP_PVAPI_PIXELFORMAT, CAP_PVAPI_PIXELFORMAT_BAYER8);
namedWindow("pattern", WINDOW_NORMAL);
setWindowProperty("pattern", WND_PROP_FULLSCREEN, WINDOW_FULLSCREEN);
namedWindow("camera view", WINDOW_NORMAL);
imshow("pattern", pattern);
cout << "Press any key when ready" << endl;
waitKey(0);
while( nbrOfValidFrames < camSettings.nbrOfFrames )
{
cap >> frame;
if( frame.data )
{
Mat color;
cvtColor(frame, color, COLOR_BayerBG2BGR);
if( camSettings.imageSize.height == 0 || camSettings.imageSize.width == 0 )
{
camSettings.imageSize = Size(frame.rows, frame.cols);
}
bool foundProj, foundCam;
vector<Point2f> projPointBuf;
vector<Point2f> camPointBuf;
imshow("camera view", color);
if( camSettings.patternType == CHESSBOARD && projSettings.patternType == CHESSBOARD )
{
int calibFlags = CALIB_CB_ADAPTIVE_THRESH;
foundCam = findChessboardCorners(color, camSettings.patternSize,
camPointBuf, calibFlags);
foundProj = findChessboardCorners(color, projSettings.patternSize,
projPointBuf, calibFlags);
if( foundCam && foundProj )
{
Mat gray;
cvtColor(color, gray, COLOR_BGR2GRAY);
cout << "found pattern" << endl;
Mat projCorners, camCorners;
cornerSubPix(gray, camPointBuf, camSettings.subpixelSize, Size(-1, -1),
TermCriteria(TermCriteria::COUNT + TermCriteria::EPS, 30, 0.1));
cornerSubPix(gray, projPointBuf, projSettings.subpixelSize, Size(-1, -1),
TermCriteria(TermCriteria::COUNT + TermCriteria::EPS, 30, 0.1));
drawChessboardCorners(gray, camSettings.patternSize, camPointBuf, foundCam);
drawChessboardCorners(gray, projSettings.patternSize, projPointBuf, foundProj);
imshow("camera view", gray);
char c = (char)waitKey(0);
if( c == 10 )
{
cout << "saving pattern #" << nbrOfValidFrames << " for calibration" << endl;
ostringstream name;
name << nbrOfValidFrames;
nbrOfValidFrames += 1;
imagePointsCam.push_back(camPointBuf);
imagePointsProj.push_back(projPointBuf);
objectPointsCam.push_back(tempCam);
PointsInProj.push_back(tempProj);
images.push_back(frame);
Mat ptsProjProj, ptsProjCam;
Mat ptsProjProjN, ptsProjCamN;
Mat TProjProj, TProjCam;
vector<Point2f> ptsProjProjVec;
vector<Point2f> ptsProjCamVec;
fromVectorToMat(tempProj, ptsProjProj);
normalize(ptsProjProj, 2, ptsProjProjN, TProjProj);
fromMatToVector(ptsProjProjN, ptsProjProjVec);
pointsInProjN.push_back(ptsProjProjVec);
T2.push_back(TProjProj);
projInProj.push_back(ptsProjProj);
projInProjN.push_back(ptsProjProjN);
fromVectorToMat(projPointBuf, ptsProjCam);
normalize(ptsProjCam, 2, ptsProjCamN, TProjCam);
fromMatToVector(ptsProjCamN, ptsProjCamVec);
imagePointsProjN.push_back(ptsProjCamVec);
T1.push_back(TProjCam);
projInCam.push_back(ptsProjCam);
projInCamN.push_back(ptsProjCamN);
}
else if( c == 32 )
{
cout << "capture discarded" << endl;
}
else if( c == 27 )
{
cout << "closing program" << endl;
return -1;
}
}
else
{
cout << "no pattern found, move board and press any key" << endl;
imshow("camera view", frame);
waitKey(0);
}
}
}
}
saveCalibrationData(outputName + "_points.yml", T1, T2, projInCam, projInProj, projInCamN, projInProjN);
double rms = calibrate(objectPointsCam, imagePointsCam, cameraMatrix, distCoeffs,
rVecs, tVecs, camSettings.imageSize);
cout << "rms = " << rms << endl;
cout << "camera matrix = \n" << cameraMatrix << endl;
cout << "dist coeffs = \n" << distCoeffs << endl;
fromCamToWorld(cameraMatrix, rVecs, tVecs, imagePointsProj, worldPointsProj);
rms = calibrate(worldPointsProj, PointsInProj, projectorMatrix, projectorDistCoeffs,
projectorRVecs, projectorTVecs, projSettings.imageSize);
cout << "rms = " << rms << endl;
cout << "projector matrix = \n" << projectorMatrix << endl;
cout << "projector dist coeffs = \n" << distCoeffs << endl;
Mat stereoR, stereoT, essential, fundamental;
Mat RCam, RProj, PCam, PProj, Q;
rms = stereoCalibrate(worldPointsProj, imagePointsProj, PointsInProj, cameraMatrix, distCoeffs,
projectorMatrix, projectorDistCoeffs, camSettings.imageSize, stereoR, stereoT,
essential, fundamental);
cout << "stereo calibrate: \n" << fundamental << endl;
saveCalibrationResults(outputName, cameraMatrix, distCoeffs, projectorMatrix, projectorDistCoeffs, fundamental );
return 0;
}
Settings::Settings(){
patternType = CHESSBOARD;
patternSize = Size(13, 9);
subpixelSize = Size(11, 11);
squareSize = 50;
nbrOfFrames = 25;
}
void loadSettings( String path, Settings &sttngs )
{
FileStorage fsInput(path, FileStorage::READ);
fsInput["PatternWidth"] >> sttngs.patternSize.width;
fsInput["PatternHeight"] >> sttngs.patternSize.height;
fsInput["SubPixelWidth"] >> sttngs.subpixelSize.width;
fsInput["SubPixelHeight"] >> sttngs.subpixelSize.height;
fsInput["SquareSize"] >> sttngs.squareSize;
fsInput["NbrOfFrames"] >> sttngs.nbrOfFrames;
fsInput["PatternType"] >> sttngs.patternType;
fsInput.release();
}
double calibrate( vector< vector<Point3f> > objPoints, vector< vector<Point2f> > imgPoints,
Mat &cameraMatrix, Mat &distCoeffs, vector<Mat> &r, vector<Mat> &t, Size imgSize )
{
int calibFlags = 0;
double rms = calibrateCamera(objPoints, imgPoints, imgSize, cameraMatrix,
distCoeffs, r, t, calibFlags);
return rms;
}
void createObjectPoints( vector<Point3f> &patternCorners, Size patternSize, float squareSize,
int patternType )
{
switch( patternType )
{
case CHESSBOARD:
case CIRCLES_GRID:
for( int i = 0; i < patternSize.height; ++i )
{
for( int j = 0; j < patternSize.width; ++j )
{
patternCorners.push_back(Point3f(float(i*squareSize), float(j*squareSize), 0));
}
}
break;
case ASYMETRIC_CIRCLES_GRID:
break;
}
}
void createProjectorObjectPoints( vector<Point2f> &patternCorners, Size patternSize, float squareSize,
int patternType )
{
switch( patternType )
{
case CHESSBOARD:
case CIRCLES_GRID:
for( int i = 1; i <= patternSize.height; ++i )
{
for( int j = 1; j <= patternSize.width; ++j )
{
patternCorners.push_back(Point2f(float(j*squareSize), float(i*squareSize)));
}
}
break;
case ASYMETRIC_CIRCLES_GRID:
break;
}
}
void fromCamToWorld( Mat cameraMatrix, vector<Mat> rV, vector<Mat> tV,
vector< vector<Point2f> > imgPoints, vector< vector<Point3f> > &worldPoints )
{
int s = (int) rV.size();
Mat invK64, invK;
invK64 = cameraMatrix.inv();
invK64.convertTo(invK, CV_32F);
for(int i = 0; i < s; ++i)
{
Mat r, t, rMat;
rV[i].convertTo(r, CV_32F);
tV[i].convertTo(t, CV_32F);
Rodrigues(r, rMat);
Mat transPlaneToCam = rMat.inv()*t;
vector<Point3f> wpTemp;
int s2 = (int) imgPoints[i].size();
for(int j = 0; j < s2; ++j){
Mat coords(3, 1, CV_32F);
coords.at<float>(0, 0) = imgPoints[i][j].x;
coords.at<float>(1, 0) = imgPoints[i][j].y;
coords.at<float>(2, 0) = 1.0f;
Mat worldPtCam = invK*coords;
Mat worldPtPlane = rMat.inv()*worldPtCam;
float scale = transPlaneToCam.at<float>(2)/worldPtPlane.at<float>(2);
Mat worldPtPlaneReproject = scale*worldPtPlane - transPlaneToCam;
Point3f pt;
pt.x = worldPtPlaneReproject.at<float>(0);
pt.y = worldPtPlaneReproject.at<float>(1);
pt.z = 0;
wpTemp.push_back(pt);
}
worldPoints.push_back(wpTemp);
}
}
void saveCalibrationResults( String path, Mat camK, Mat camDistCoeffs, Mat projK, Mat projDistCoeffs,
Mat fundamental )
{
FileStorage fs(path + ".yml", FileStorage::WRITE);
fs << "camIntrinsics" << camK;
fs << "camDistCoeffs" << camDistCoeffs;
fs << "projIntrinsics" << projK;
fs << "projDistCoeffs" << projDistCoeffs;
fs << "fundamental" << fundamental;
fs.release();
}
void saveCalibrationData( String path, vector<Mat> T1, vector<Mat> T2, vector<Mat> ptsProjCam, vector<Mat> ptsProjProj, vector<Mat> ptsProjCamN, vector<Mat> ptsProjProjN )
{
FileStorage fs(path + ".yml", FileStorage::WRITE);
int size = (int) T1.size();
fs << "size" << size;
for( int i = 0; i < (int)T1.size(); ++i )
{
ostringstream nbr;
nbr << i;
fs << "TprojCam" + nbr.str() << T1[i];
fs << "TProjProj" + nbr.str() << T2[i];
fs << "ptsProjCam" + nbr.str() << ptsProjCam[i];
fs << "ptsProjProj" + nbr.str() << ptsProjProj[i];
fs << "ptsProjCamN" + nbr.str() << ptsProjCamN[i];
fs << "ptsProjProjN" + nbr.str() << ptsProjProjN[i];
}
fs.release();
}
void normalize( const Mat &pts, const int& dim, Mat& normpts, Mat &T )
{
float averagedist = 0;
float scale = 0;
//centroid
Mat centroid(dim,1,CV_32F);
Scalar tmp;
if( normpts.empty() )
{
normpts= Mat(pts.rows,pts.cols,CV_32F);
}
for( int i = 0 ; i < dim ; ++i )
{
tmp = mean(pts.row(i));
centroid.at<float>(i,0) = (float)tmp[0];
subtract(pts.row(i), centroid.at<float>(i, 0), normpts.row(i));
}
//average distance
Mat ptstmp;
for( int i = 0 ; i < normpts.cols; ++i )
{
ptstmp = normpts.col(i);
averagedist = averagedist+(float)norm(ptstmp);
}
averagedist = averagedist / normpts.cols;
scale = (float)(sqrt(dim) / averagedist);
normpts = normpts * scale;
T=cv::Mat::eye(dim+1,dim+1,CV_32F);
for( int i = 0; i < dim; ++i )
{
T.at<float>(i, i) = scale;
T.at<float>(i, dim) = -scale*centroid.at<float>(i, 0);
}
}
void fromVectorToMat( vector<Point2f> v, Mat &pts )
{
int nbrOfPoints = (int) v.size();
if( pts.empty() )
pts.create(2, nbrOfPoints, CV_32F);
for( int i = 0; i < nbrOfPoints; ++i )
{
pts.at<float>(0, i) = v[i].x;
pts.at<float>(1, i) = v[i].y;
}
}
void fromMatToVector( Mat pts, vector<Point2f> &v )
{
int nbrOfPoints = pts.cols;
for( int i = 0; i < nbrOfPoints; ++i )
{
Point2f temp;
temp.x = pts.at<float>(0, i);
temp.y = pts.at<float>(1, i);
v.push_back(temp);
}
}
\ No newline at end of file
/*M///////////////////////////////////////////////////////////////////////////////////////
//
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
//
// By downloading, copying, installing or using the software you agree to this license.
// If you do not agree to this license, do not download, install,
// copy or use the software.
//
//
// License Agreement
// For Open Source Computer Vision Library
//
// Copyright (C) 2015, OpenCV Foundation, all rights reserved.
// Third party copyrights are property of their respective owners.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// * Redistribution's of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistribution's in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// * The name of the copyright holders may not be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// This software is provided by the copyright holders and contributors "as is" and
// any express or implied warranties, including, but not limited to, the implied
// warranties of merchantability and fitness for a particular purpose are disclaimed.
// In no event shall the Intel Corporation or contributors be liable for any direct,
// indirect, incidental, special, exemplary, or consequential damages
// (including, but not limited to, procurement of substitute goods or services;
// loss of use, data, or profits; or business interruption) however caused
// and on any theory of liability, whether in contract, strict liability,
// or tort (including negligence or otherwise) arising in any way out of
// the use of this software, even if advised of the possibility of such damage.
//
//M*/
#include "precomp.hpp"
namespace cv {
namespace structured_light {
class CV_EXPORTS_W SinusoidalPatternProfilometry_Impl : public SinusoidalPattern
{
public:
// Constructor
explicit SinusoidalPatternProfilometry_Impl( const SinusoidalPattern::Params &parameters =
SinusoidalPattern::Params() );
// Destructor
virtual ~SinusoidalPatternProfilometry_Impl(){};
// Generate sinusoidal patterns
bool generate( OutputArrayOfArrays patternImages );
bool decode( InputArrayOfArrays patternImages, OutputArray disparityMap,
InputArrayOfArrays blackImages = noArray(), InputArrayOfArrays whiteImages =
noArray(), int flags = 0 ) const;
// Compute a wrapped phase map from the sinusoidal patterns
void computePhaseMap( InputArrayOfArrays patternImages, OutputArray wrappedPhaseMap,
OutputArray shadowMask = noArray(), InputArray fundamental = noArray());
// Unwrap the wrapped phase map to retrieve correspondences
void unwrapPhaseMap( InputArray wrappedPhaseMap,
OutputArray unwrappedPhaseMap,
cv::Size camSize,
InputArray shadowMask = noArray() );
// Find correspondences between the devices
void findProCamMatches( InputArray projUnwrappedPhaseMap, InputArray camUnwrappedPhaseMap,
OutputArrayOfArrays matches );
void computeDataModulationTerm( InputArrayOfArrays patternImages,
OutputArray dataModulationTerm,
InputArray shadowMask );
private:
// Compute The Fourier transform of a pattern. Output is complex. Taken from the DFT example in OpenCV
void computeDft( InputArray patternImage, OutputArray FourierTransform );
// Compute the inverse Fourier transform. Output can be complex or real
void computeInverseDft( InputArray FourierTransform, OutputArray inverseFourierTransform,
bool realOutput );
// Compute the DFT magnitude which is used to find maxima in the spectrum
void computeDftMagnitude( InputArray FourierTransform, OutputArray FourierTransformMagnitude );
// Compute phase map from the complex signal given by non-symmetrical filtering of DFT
void computeFtPhaseMap( InputArray inverseFourierTransform,
InputArray shadowMask,
OutputArray wrappedPhaseMap );
// Swap DFT quadrants. Come from opencv example
void swapQuadrants( InputOutputArray image, int centerX, int centerY );
// Filter (non)-symmetrically the DFT.
void frequencyFiltering( InputOutputArray FourierTransform, int centerX1, int centerY1,
int halfRegionWidth, int halfRegionHeight, bool keepInsideRegion,
int centerX2 = -1, int centerY2 = -1 );
// Find maxima in the spectrum so that we know how it should be filtered
bool findMaxInHalvesTransform( InputArray FourierTransformMag, Point &maxPosition1,
Point &maxPosition2 );
// Compute phase map from the three sinusoidal patterns
void computePsPhaseMap( InputArrayOfArrays patternImages,
InputArray shadowMask,
OutputArray wrappedPhaseMap );
void computeFapsPhaseMap( InputArray a, InputArray b, InputArray theta1, InputArray theta2,
InputArray shadowMask, OutputArray wrappedPhaseMap );
// Compute a shadow mask to discard shadow regions
void computeShadowMask( InputArrayOfArrays patternImages, OutputArray shadowMask );
// Data modulation term is used to isolate cross markers
void extractMarkersLocation( InputArray dataModulationTerm,
std::vector<Point> &markersLocation );
void convertToAbsolutePhaseMap( InputArrayOfArrays camPatterns,
InputArray unwrappedProjPhaseMap,
InputArray unwrappedCamPhaseMap,
InputArray shadowMask,
InputArray fundamentalMatrix );
Params params;
phase_unwrapping::HistogramPhaseUnwrapping::Params unwrappingParams;
// Class describing markers that are added to the patterns
class Marker{
private:
Point center, up, right, left, down;
public:
Marker();
Marker( Point c );
void drawMarker( OutputArray pattern );
};
};
// Default parameters value
SinusoidalPattern::Params::Params()
{
width = 800;
height = 600;
nbrOfPeriods = 20;
shiftValue = (float)(2 * CV_PI / 3);
methodId = FAPS;
nbrOfPixelsBetweenMarkers = 56;
horizontal = false;
setMarkers = false;
}
SinusoidalPatternProfilometry_Impl::Marker::Marker(){};
SinusoidalPatternProfilometry_Impl::Marker::Marker( Point c )
{
center = c;
up.x = c.x;
up.y = c.y - 1;
left.x = c.x - 1;
left.y = c.y;
down.x = c.x;
down.y = c.y + 1;
right.x = c.x + 1;
right.y = c.y;
}
// Draw marker on a pattern
void SinusoidalPatternProfilometry_Impl::Marker::drawMarker( OutputArray pattern )
{
Mat &pattern_ = *(Mat*) pattern.getObj();
pattern_.at<uchar>(center.x, center.y) = 255;
pattern_.at<uchar>(up.x, up.y) = 255;
pattern_.at<uchar>(right.x, right.y) = 255;
pattern_.at<uchar>(left.x, left.y) = 255;
pattern_.at<uchar>(down.x, down.y) = 255;
}
SinusoidalPatternProfilometry_Impl::SinusoidalPatternProfilometry_Impl(
const SinusoidalPattern::Params &parameters ) : params(parameters)
{
}
// Generate sinusoidal patterns. Markers are optional
bool SinusoidalPatternProfilometry_Impl::generate( OutputArrayOfArrays pattern )
{
// Three patterns are used in the reference paper.
int nbrOfPatterns = 3;
float meanAmpl = 127.5;
float sinAmpl = 127.5;
// Period in number of pixels
int period;
float frequency;
// m and n are parameters described in the reference paper
int m = params.nbrOfPixelsBetweenMarkers;
int n;
// Offset for the first marker of the first row.
int firstMarkerOffset = 10;
int mnRatio;
int nbrOfMarkersOnOneRow;
std::vector<Mat> &pattern_ = *(std::vector<Mat>*) pattern.getObj();
n = params.nbrOfPeriods / nbrOfPatterns;
mnRatio = m / n;
pattern_.resize(nbrOfPatterns);
if( params.horizontal )
{
period = params.height / params.nbrOfPeriods;
nbrOfMarkersOnOneRow = (int)floor((params.width - firstMarkerOffset) / m);
}
else
{
period = params.width / params.nbrOfPeriods;
nbrOfMarkersOnOneRow = (int)floor((params.height - firstMarkerOffset) / m);
}
frequency = (float) 1 / period;
for( int i = 0; i < nbrOfPatterns; ++i )
{
pattern_[i] = Mat(params.height, params.width, CV_8UC1);
if( params.horizontal )
pattern_[i] = pattern_[i].t();
}
// Patterns vary along one direction only so, a row Mat can be created and copied to the pattern's rows
for( int i = 0; i < nbrOfPatterns; ++i )
{
Mat rowValues(1, pattern_[i].cols, CV_8UC1);
for( int j = 0; j < pattern_[i].cols; ++j )
{
rowValues.at<uchar>(0, j) = saturate_cast<uchar>(
meanAmpl + sinAmpl * sin(2 * CV_PI * frequency * j + i * params.shiftValue));
}
for( int j = 0; j < pattern_[i].rows; ++j )
{
rowValues.row(0).copyTo(pattern_[i].row(j));
}
}
// Add cross markers to the patterns.
if( params.setMarkers )
{
for( int i = 0; i < nbrOfPatterns; ++i )
{
for( int j = 0; j < n; ++j )
{
for( int k = 0; k < nbrOfMarkersOnOneRow; ++k )
{
Marker mark(Point(firstMarkerOffset + k * m + j * mnRatio,
3 * period / 4 + j * period + i * period * n - i * period / 3));
mark.drawMarker(pattern_[i]);
params.markersLocation.push_back(Point2f((float)(firstMarkerOffset + k * m + j * mnRatio),
(float) (3 * period / 4 + j * period + i * period * n - i * period / 3)));
}
}
}
}
if( params.horizontal )
for( int i = 0; i < nbrOfPatterns; ++i )
{
pattern_[i] = pattern_[i].t();
}
return true;
}
bool SinusoidalPatternProfilometry_Impl::decode( InputArrayOfArrays patternImages,
OutputArray disparityMap,
InputArrayOfArrays blackImages,
InputArrayOfArrays whiteImages, int flags ) const
{
(void) patternImages;
(void) disparityMap;
(void) blackImages;
(void) whiteImages;
(void) flags;
return true;
}
// Most of the steps described in the paper to get the wrapped phase map take place here
void SinusoidalPatternProfilometry_Impl::computePhaseMap( InputArrayOfArrays patternImages,
OutputArray wrappedPhaseMap,
OutputArray shadowMask,
InputArray fundamental )
{
std::vector<Mat> &pattern_ = *(std::vector<Mat>*) patternImages.getObj();
Mat &wrappedPhaseMap_ = *(Mat*) wrappedPhaseMap.getObj();
int rows = pattern_[0].rows;
int cols = pattern_[0].cols;
int dcWidth = 5;
int dcHeight = 5;
int bpWidth = 21;
int bpHeight = 21;
// Compute wrapped phase map for FTP
if( params.methodId == FTP )
{
Mat &shadowMask_ = *(Mat*) shadowMask.getObj();
Mat dftImage, complexInverseDft;
Mat dftMag;
int halfWidth = cols/2;
int halfHeight = rows/2;
Point m1, m2;
computeShadowMask(pattern_, shadowMask_);
computeDft(pattern_[0], dftImage); //compute the complex pattern DFT
swapQuadrants(dftImage, halfWidth, halfHeight); //swap quadrants to get 0 frequency in (halfWidth, halfHeight)
frequencyFiltering(dftImage, halfHeight, halfWidth, dcHeight, dcWidth, false); //get rid of 0 frequency
computeDftMagnitude(dftImage, dftMag); //compute magnitude to find maxima
findMaxInHalvesTransform(dftMag, m1, m2); //look for maxima in the magnitude. Useful information is located around maxima
frequencyFiltering(dftImage, m2.y, m2.x, bpHeight, bpWidth, true); //keep useful information only
swapQuadrants(dftImage,halfWidth, halfHeight); //swap quadrants again to compute inverse dft
computeInverseDft(dftImage, complexInverseDft, false); //compute inverse dft. Result is complex since we only keep half of the spectrum
computeFtPhaseMap(complexInverseDft, shadowMask_, wrappedPhaseMap_); //compute phaseMap from the complex image.
}
// Compute wrapped pahse map for PSP
else if( params.methodId == PSP )
{
Mat &shadowMask_ = *(Mat*) shadowMask.getObj();
//Mat &fundamental_ = *(Mat*) fundamental.getObj();
(void) fundamental;
Mat dmt;
int nbrOfPatterns = static_cast<int>(pattern_.size());
std::vector<Mat> filteredPatterns(nbrOfPatterns);
std::vector<Mat> dftImages(nbrOfPatterns);
std::vector<Mat> dftMags(nbrOfPatterns);
int halfWidth = cols/2;
int halfHeight = rows/2;
Point m1, m2;
computeShadowMask(pattern_, shadowMask_);
//this loop symmetrically filters pattern to remove cross markers.
for( int i = 0; i < nbrOfPatterns; ++i )
{
computeDft(pattern_[i], dftImages[i]);
swapQuadrants(dftImages[i], halfWidth, halfHeight);
frequencyFiltering(dftImages[i], halfHeight, halfWidth, dcHeight, dcWidth, false);
computeDftMagnitude(dftImages[i], dftMags[i]);
findMaxInHalvesTransform(dftMags[i], m1, m2);
frequencyFiltering(dftImages[i], m1.y, m1.x, bpHeight, bpWidth, true, m2.y, m2.x);//symmetrical filtering
swapQuadrants(dftImages[i], halfWidth, halfHeight);
computeInverseDft(dftImages[i], filteredPatterns[i], true);
}
computePsPhaseMap(filteredPatterns, shadowMask_, wrappedPhaseMap_);
}
else if( params.methodId == FAPS )
{
Mat &shadowMask_ = *(Mat*) shadowMask.getObj();
int nbrOfPatterns = static_cast<int>(pattern_.size());
std::vector<Mat> unwrappedFTPhaseMaps;
std::vector<Mat> filteredPatterns(nbrOfPatterns);
Mat dmt;
Mat theta1, theta2, a, b;
std::vector<Point> markersLoc;
cv::Size camSize;
camSize.height = pattern_[0].rows;
camSize.width = pattern_[0].cols;
computeShadowMask(pattern_, shadowMask_);
for( int i = 0; i < nbrOfPatterns; ++i )
{
Mat dftImage, complexInverseDft;
Mat dftMag;
Mat tempWrappedPhaseMap;
Mat tempUnwrappedPhaseMap;
int halfWidth = cols/2;
int halfHeight = rows/2;
Point m1, m2;
computeDft(pattern_[i], dftImage); //compute the complex pattern DFT
swapQuadrants(dftImage, halfWidth, halfHeight); //swap quadrants to get 0 frequency in (halfWidth, halfHeight)
frequencyFiltering(dftImage, halfHeight, halfWidth, dcHeight, dcWidth, false); //get rid of 0 frequency
computeDftMagnitude(dftImage, dftMag); //compute magnitude to find maxima
findMaxInHalvesTransform(dftMag, m1, m2); //look for maxima in the magnitude. Useful information is located around maxima
frequencyFiltering(dftImage, m2.y, m2.x, bpHeight, bpWidth, true); //keep useful information only
swapQuadrants(dftImage,halfWidth, halfHeight); //swap quadrants again to compute inverse dft
computeInverseDft(dftImage, complexInverseDft, false); //compute inverse dft. Result is complex since we only keep half of the spectrum
computeFtPhaseMap(complexInverseDft, shadowMask_, tempWrappedPhaseMap); //compute phaseMap from the complex image.
unwrapPhaseMap(tempWrappedPhaseMap, tempUnwrappedPhaseMap, camSize, shadowMask);
unwrappedFTPhaseMaps.push_back(tempUnwrappedPhaseMap);
computeInverseDft(dftImage, filteredPatterns[i], true);
}
theta1.create(camSize.height, camSize.width, unwrappedFTPhaseMaps[0].type());
theta2.create(camSize.height, camSize.width, unwrappedFTPhaseMaps[0].type());
a.create(camSize.height, camSize.width, CV_32FC1);
b.create(camSize.height, camSize.width, CV_32FC1);
a = filteredPatterns[0] - filteredPatterns[1];
b = filteredPatterns[1] - filteredPatterns[2];
theta1 = unwrappedFTPhaseMaps[1] - unwrappedFTPhaseMaps[0];
theta2 = unwrappedFTPhaseMaps[2] - unwrappedFTPhaseMaps[1];
computeFapsPhaseMap(a, b, theta1, theta2, shadowMask_, wrappedPhaseMap_);
}
}
void SinusoidalPatternProfilometry_Impl::unwrapPhaseMap( InputArray wrappedPhaseMap,
OutputArray unwrappedPhaseMap,
cv::Size camSize,
InputArray shadowMask )
{
int rows = params.height;
int cols = params.width;
unwrappingParams.width = camSize.width;
unwrappingParams.height = camSize.height;
Mat &wPhaseMap = *(Mat*) wrappedPhaseMap.getObj();
Mat &uPhaseMap = *(Mat*) unwrappedPhaseMap.getObj();
Mat mask;
if( shadowMask.empty() )
{
mask.create(rows, cols, CV_8UC1);
mask = Scalar::all(255);
}
else
{
Mat &temp = *(Mat*) shadowMask.getObj();
temp.copyTo(mask);
}
Ptr<phase_unwrapping::HistogramPhaseUnwrapping> phaseUnwrapping =
phase_unwrapping::HistogramPhaseUnwrapping::create(unwrappingParams);
phaseUnwrapping->unwrapPhaseMap(wPhaseMap, uPhaseMap, mask);
}
void SinusoidalPatternProfilometry_Impl::findProCamMatches( InputArray projUnwrappedPhaseMap,
InputArray camUnwrappedPhaseMap,
OutputArrayOfArrays matches )
{
(void) projUnwrappedPhaseMap;
(void) camUnwrappedPhaseMap;
(void) matches;
}
void SinusoidalPatternProfilometry_Impl::computeDft( InputArray patternImage,
OutputArray FourierTransform )
{
Mat &pattern_ = *(Mat*) patternImage.getObj();
Mat &FourierTransform_ = *(Mat*) FourierTransform.getObj();
Mat padded;
int m = getOptimalDFTSize(pattern_.rows);
int n = getOptimalDFTSize(pattern_.cols);
copyMakeBorder(pattern_, padded, 0, m - pattern_.rows, 0, n - pattern_.cols, BORDER_CONSTANT,
Scalar::all(0));
Mat planes[] = {Mat_<float>(padded), Mat::zeros(padded.size(), CV_32F)};
merge(planes, 2, FourierTransform_);
dft(FourierTransform_, FourierTransform_);
}
void SinusoidalPatternProfilometry_Impl::computeInverseDft( InputArray FourierTransform,
OutputArray inverseFourierTransform,
bool realOutput )
{
Mat &FourierTransform_ = *(Mat*) FourierTransform.getObj();
Mat &inverseFourierTransform_ = *(Mat*) inverseFourierTransform.getObj();
if( realOutput )
idft(FourierTransform_, inverseFourierTransform_, DFT_SCALE | DFT_REAL_OUTPUT);
else
idft(FourierTransform_, inverseFourierTransform_, DFT_SCALE);
}
void SinusoidalPatternProfilometry_Impl::computeDftMagnitude( InputArray FourierTransform,
OutputArray FourierTransformMagnitude )
{
Mat &FourierTransform_ = *(Mat*) FourierTransform.getObj();
Mat &FourierTransformMagnitude_ = *(Mat*) FourierTransformMagnitude.getObj();
Mat planes[2];
split(FourierTransform_, planes);
magnitude(planes[0], planes[1], planes[0]);
FourierTransformMagnitude_ = planes[0];
FourierTransformMagnitude_ += Scalar::all(1);
log(FourierTransformMagnitude_, FourierTransformMagnitude_);
FourierTransformMagnitude_ = FourierTransformMagnitude_(
Rect(0, 0, FourierTransformMagnitude_.cols & -2, FourierTransformMagnitude_.rows & - 2));
normalize(FourierTransformMagnitude_, FourierTransformMagnitude_, 0, 1, NORM_MINMAX);
}
void SinusoidalPatternProfilometry_Impl::computeFtPhaseMap( InputArray inverseFourierTransform,
InputArray shadowMask,
OutputArray wrappedPhaseMap )
{
Mat &inverseFourierTransform_ = *(Mat*) inverseFourierTransform.getObj();
Mat &wrappedPhaseMap_ = *(Mat*) wrappedPhaseMap.getObj();
Mat &shadowMask_ = *(Mat*) shadowMask.getObj();
Mat planes[2];
int rows = inverseFourierTransform_.rows;
int cols = inverseFourierTransform_.cols;
if( wrappedPhaseMap_.empty () )
wrappedPhaseMap_.create(rows, cols, CV_32FC1);
split(inverseFourierTransform_, planes);
for( int i = 0; i < rows; ++i )
{
for( int j = 0; j < cols; ++j )
{
if( shadowMask_.at<uchar>(i, j) != 0 )
{
float im = planes[1].at<float>(i, j);
float re = planes[0].at<float>(i, j);
wrappedPhaseMap_.at<float>(i, j) = atan2(re, im);
}
else
{
wrappedPhaseMap_.at<float>(i, j) = 0;
}
}
}
}
void SinusoidalPatternProfilometry_Impl::swapQuadrants( InputOutputArray image,
int centerX, int centerY )
{
Mat &image_ = *(Mat*) image.getObj();
Mat q0(image_, Rect(0, 0, centerX, centerY));
Mat q1(image_, Rect(centerX, 0, centerX, centerY));
Mat q2(image_, Rect(0, centerY, centerX, centerY));
Mat q3(image_, Rect(centerX, centerY, centerX, centerY));
Mat tmp;
q0.copyTo(tmp);
q3.copyTo(q0);
tmp.copyTo(q3);
q1.copyTo(tmp);
q2.copyTo(q1);
tmp.copyTo(q2);
}
void SinusoidalPatternProfilometry_Impl::frequencyFiltering( InputOutputArray FourierTransform,
int centerX1, int centerY1,
int halfRegionWidth, int halfRegionHeight,
bool keepInsideRegion, int centerX2,
int centerY2 )
{
Mat &FourierTransform_ = *(Mat*) FourierTransform.getObj();
int rows = FourierTransform_.rows;
int cols = FourierTransform_.cols;
int type = FourierTransform_.type();
if( keepInsideRegion )
{
Mat maskedTransform(rows, cols, type);
maskedTransform = Scalar::all(0);
Mat roi1 = FourierTransform_(
Rect(centerY1 - halfRegionHeight, centerX1 - halfRegionWidth,
2 * halfRegionHeight, 2 * halfRegionWidth));
Mat dstRoi1 = maskedTransform(
Rect(centerY1 - halfRegionHeight, centerX1 - halfRegionWidth,
2 * halfRegionHeight, 2 * halfRegionWidth));
roi1.copyTo(dstRoi1);
if( centerY2 != -1 || centerX2 != -1 )
{
Mat roi2 = FourierTransform_(
Rect(centerY2 - halfRegionHeight, centerX2 - halfRegionWidth,
2 * halfRegionHeight, 2 * halfRegionWidth));
Mat dstRoi2 = maskedTransform(
Rect(centerY2 - halfRegionHeight, centerX2 - halfRegionWidth,
2 * halfRegionHeight, 2 * halfRegionWidth));
roi2.copyTo(dstRoi2);
}
FourierTransform_ = maskedTransform;
}
else
{
Mat roi(2 * halfRegionHeight, 2 * halfRegionWidth, type);
roi = Scalar::all(0);
Mat dstRoi1 = FourierTransform_(
Rect(centerY1 - halfRegionHeight, centerX1 - halfRegionWidth,
2 * halfRegionHeight, 2 * halfRegionWidth));
roi.copyTo(dstRoi1);
if( centerY2 != -1 || centerX2 != -1 )
{
Mat dstRoi2 = FourierTransform_(
Rect(centerY2 - halfRegionHeight, centerX2 - halfRegionWidth,
2 * halfRegionHeight, 2 * halfRegionWidth));
roi.copyTo(dstRoi2);
}
}
}
bool SinusoidalPatternProfilometry_Impl::findMaxInHalvesTransform( InputArray FourierTransformMag,
Point &maxPosition1,
Point &maxPosition2 )
{
Mat &FourierTransformMag_ = *(Mat*) FourierTransformMag.getObj();
int centerX = FourierTransformMag_.cols / 2;
int centerY = FourierTransformMag_.rows / 2;
Mat h0, h1;
double maxV1 = -1;
double maxV2 = -1;
int margin = 5;
if( params.horizontal )
{
h0 = FourierTransformMag_(Rect(0, 0, FourierTransformMag_.cols, centerY - margin));
h1 = FourierTransformMag_(
Rect(0, centerY + margin, FourierTransformMag_.cols, centerY - margin));
}
else
{
h0 = FourierTransformMag_(Rect(0, 0, centerX - margin, FourierTransformMag_.rows));
h1 = FourierTransformMag_(
Rect(centerX + margin, 0, centerX - margin, FourierTransformMag_.rows));
}
minMaxLoc(h0, NULL, &maxV1, NULL, &maxPosition1);
minMaxLoc(h1, NULL, &maxV2, NULL, &maxPosition2);
if( params.horizontal )
{
maxPosition2.y = maxPosition2.y + centerY + margin;
}
else
{
maxPosition2.x = maxPosition2.x + centerX + margin;
}
if( maxV1 == -1 || maxV2 == -1 )
{
return false;
}
return true;
}
void SinusoidalPatternProfilometry_Impl::computePsPhaseMap( InputArrayOfArrays patternImages,
InputArray shadowMask,
OutputArray wrappedPhaseMap )
{
std::vector<Mat> &pattern_ = *(std::vector<Mat>*) patternImages.getObj();
Mat &wrappedPhaseMap_ = *(Mat*) wrappedPhaseMap.getObj();
Mat &shadowMask_ = *(Mat*) shadowMask.getObj();
int rows = pattern_[0].rows;
int cols = pattern_[0].cols;
float i1 = 0;
float i2 = 0;
float i3 = 0;
if( wrappedPhaseMap_.empty() )
wrappedPhaseMap_.create(rows, cols, CV_32FC1);
for( int i = 0; i < rows; ++i )
{
for( int j = 0; j < cols; ++j )
{
if( shadowMask_.at<uchar>(i, j) != 0 )
{
if( pattern_[0].type() == CV_8UC1 )
{
i1 = pattern_[0].at<uchar>(i, j);
i2 = pattern_[1].at<uchar>(i, j);
i3 = pattern_[2].at<uchar>(i, j);
}
else if( pattern_[0].type() == CV_32FC1 )
{
i1 = pattern_[0].at<float>(i, j);
i2 = pattern_[1].at<float>(i, j);
i3 = pattern_[2].at<float>(i, j);
}
float num = (1- cos(params.shiftValue)) * (i3 - i2);
float den = sin(params.shiftValue) * (2 * i1 - i2 - i3);
wrappedPhaseMap_.at<float>(i,j) = atan2(num, den);
}
else
{
wrappedPhaseMap_.at<float>(i,j) = 0;
}
}
}
}
void SinusoidalPatternProfilometry_Impl::computeFapsPhaseMap( InputArray a,
InputArray b,
InputArray theta1,
InputArray theta2,
InputArray shadowMask,
OutputArray wrappedPhaseMap )
{
Mat &a_ = *(Mat*) a.getObj();
Mat &b_ = *(Mat*) b.getObj();
Mat &theta1_ = *(Mat*) theta1.getObj();
Mat &theta2_ = *(Mat*) theta2.getObj();
Mat &wrappedPhaseMap_ = *(Mat*) wrappedPhaseMap.getObj();
Mat &shadowMask_ = *(Mat*) shadowMask.getObj();
int rows = a_.rows;
int cols = a_.cols;
if( wrappedPhaseMap_.empty() )
wrappedPhaseMap_.create(rows, cols, CV_32FC1);
for( int i = 0; i < rows; ++i )
{
for( int j = 0; j < cols; ++j )
{
if( shadowMask_.at<uchar>(i, j ) != 0 )
{
float num = (1 - cos(theta2_.at<float>(i, j))) * a_.at<float>(i, j) +
(1 - cos(theta1_.at<float>(i, j))) * b_.at<float>(i, j);
float den = sin(theta1_.at<float>(i, j)) * b_.at<float>(i, j) -
sin(theta2_.at<float>(i, j)) * a_.at<float>(i, j);
wrappedPhaseMap_.at<float>(i, j) = atan2(num, den);
}
else
{
wrappedPhaseMap_.at<float>(i, j) = 0;
}
}
}
}
//compute shadow mask from three patterns. Valid pixels are lit at least by one pattern
void SinusoidalPatternProfilometry_Impl::computeShadowMask( InputArrayOfArrays patternImages,
OutputArray shadowMask )
{
std::vector<Mat> &patternImages_ = *(std::vector<Mat>*) patternImages.getObj();
Mat &shadowMask_ = *(Mat*) shadowMask.getObj();
Mat mean;
int rows = patternImages_[0].rows;
int cols = patternImages_[0].cols;
float i1, i2, i3;
mean.create(rows, cols, CV_32FC1);
for( int i = 0; i < rows; ++i )
{
for( int j = 0; j < cols; ++j )
{
i1 = (float) patternImages_[0].at<uchar>(i, j);
i2 = (float) patternImages_[1].at<uchar>(i, j);
i3 = (float) patternImages_[2].at<uchar>(i, j);
mean.at<float>(i, j) = (i1 + i2 + i3) / 3;
}
}
mean.convertTo(mean, CV_8UC1);
threshold(mean, shadowMask_, 10, 255, 0);
}
// Compute the data modulation term according to the formula given in the reference paper
void SinusoidalPatternProfilometry_Impl::computeDataModulationTerm( InputArrayOfArrays patternImages,
OutputArray dataModulationTerm,
InputArray shadowMask )
{
std::vector<Mat> &patternImages_ = *(std::vector<Mat>*) patternImages.getObj();
Mat &dataModulationTerm_ = *(Mat*) dataModulationTerm.getObj();
Mat &shadowMask_ = *(Mat*) shadowMask.getObj();
int rows = patternImages_[0].rows;
int cols = patternImages_[0].cols;
float num = 0;
float den = 0;
float i1 = 0;
float i2 = 0;
float i3 = 0;
int iOffset, jOffset;
Mat dmt(rows, cols, CV_32FC1);
Mat threshedDmt;
if( dataModulationTerm_.empty() )
{
dataModulationTerm_.create(rows, cols, CV_8UC1);
}
if( shadowMask_.empty() )
{
shadowMask_.create(rows, cols, CV_8U);
shadowMask_ = Scalar::all(255);
}
for( int i = 0; i < rows; ++i )
{
for( int j = 0; j < cols; ++j )
{
if( shadowMask_.at<uchar>(i, j) != 0 ){
if( i - 2 == - 2 )
{
iOffset = 0;
}
else if( i - 2 == - 1 )
{
iOffset = -1;
}
else if( i - 2 + 4 == rows + 1 )
{
iOffset = -3;
}
else
{
iOffset = -2;
}
if( j - 2 == -2 )
{
jOffset = 0;
}
else if( j - 2 == -1 )
{
jOffset = -1;
}
else if( j - 2 + 4 == cols + 1 )
{
jOffset = -3;
}
else
{
jOffset = -2;
}
Mat roi = shadowMask_(Rect(j + jOffset, i + iOffset, 4, 4));
Scalar nbrOfValidPixels = sum(roi);
if( nbrOfValidPixels[0] < 14*255 )
{
dmt.at<float>(i, j) = 0;
}
else
{
i1 = patternImages_[0].at<uchar>(i, j);
i2 = patternImages_[1].at<uchar>(i, j);
i3 = patternImages_[2].at<uchar>(i, j);
num = sqrt(3 * ( i1 - i3 ) * ( i1 - i3 ) + ( 2 * i2 - i1 - i3 ) * ( 2 * i2 - i1 - i3 ));
den = i1 + i2 + i3;
dmt.at<float>(i, j) = 1 - num / den;
}
}
else
{
dmt.at<float>(i, j) = 0;
}
}
}
Mat kernel(3, 3, CV_32F);
kernel.at<float>(0, 0) = 1.f/16.f;
kernel.at<float>(1, 0) = 2.f/16.f;
kernel.at<float>(2, 0) = 1.f/16.f;
kernel.at<float>(0, 1) = 2.f/16.f;
kernel.at<float>(1, 1) = 4.f/16.f;
kernel.at<float>(2, 1) = 2.f/16.f;
kernel.at<float>(0, 2) = 1.f/16.f;
kernel.at<float>(1, 2) = 2.f/16.f;
kernel.at<float>(2, 2) = 1.f/16.f;
Point anchor = Point(-1, -1);
double delta = 0;
int ddepth = -1;
filter2D(dmt, dmt, ddepth, kernel, anchor, delta, BORDER_DEFAULT);
threshold(dmt, threshedDmt, 0.4, 1, THRESH_BINARY);
threshedDmt.convertTo(dataModulationTerm_, CV_8UC1, 255, 0);
}
//Extract marker location on the DMT. Duplicates are removed
void SinusoidalPatternProfilometry_Impl::extractMarkersLocation( InputArray dataModulationTerm,
std::vector<Point> &markersLocation )
{
Mat &dmt = *(Mat*) dataModulationTerm.getObj();
int rows = dmt.rows;
int cols = dmt.cols;
int halfRegionSize = 6;
for( int i = 0; i < rows; ++i )
{
for( int j = 0; j < cols; ++j )
{
if( dmt.at<uchar>(i,j) != 0 )
{
bool addToVector = true;
for(int k = 0; k < (int)markersLocation.size(); ++k)
{
if( markersLocation[k].x - halfRegionSize < i &&
markersLocation[k].x + halfRegionSize > i &&
markersLocation[k].y - halfRegionSize < j &&
markersLocation[k].y + halfRegionSize > j ){
addToVector = false;
}
}
if(addToVector)
{
Point temp(i,j);
markersLocation.push_back(temp);
}
}
}
}
}
void SinusoidalPatternProfilometry_Impl::convertToAbsolutePhaseMap( InputArrayOfArrays camPatterns,
InputArray unwrappedProjPhaseMap,
InputArray unwrappedCamPhaseMap,
InputArray shadowMask,
InputArray fundamentalMatrix )
{
std::vector<Mat> &camPatterns_ = *(std::vector<Mat>*) camPatterns.getObj();
(void) unwrappedCamPhaseMap;
(void) unwrappedProjPhaseMap;
Mat &fundamental = *(Mat*) fundamentalMatrix.getObj();
Mat camDmt;
std::vector<Point> markersLocation;
computeDataModulationTerm(camPatterns_, camDmt, shadowMask);
std::vector<Vec3f> epilines;
computeCorrespondEpilines(params.markersLocation, 2, fundamental, epilines);
}
Ptr<SinusoidalPattern> SinusoidalPattern::create( const SinusoidalPattern::Params &params )
{
return makePtr<SinusoidalPatternProfilometry_Impl>(params);
}
}
}
\ No newline at end of file
/*M///////////////////////////////////////////////////////////////////////////////////////
//
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
//
// By downloading, copying, installing or using the software you agree to this license.
// If you do not agree to this license, do not download, install,
// copy or use the software.
//
//
// License Agreement
// For Open Source Computer Vision Library
//
// Copyright (C) 2015, OpenCV Foundation, all rights reserved.
// Third party copyrights are property of their respective owners.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// * Redistribution's of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistribution's in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// * The name of the copyright holders may not be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// This software is provided by the copyright holders and contributors "as is" and
// any express or implied warranties, including, but not limited to, the implied
// warranties of merchantability and fitness for a particular purpose are disclaimed.
// In no event shall the Intel Corporation or contributors be liable for any direct,
// indirect, incidental, special, exemplary, or consequential damages
// (including, but not limited to, procurement of substitute goods or services;
// loss of use, data, or profits; or business interruption) however caused
// and on any theory of liability, whether in contract, strict liability,
// or tort (including negligence or otherwise) arising in any way out of
// the use of this software, even if advised of the possibility of such damage.
//
//M*/
#include <opencv2/structured_light/graycodepattern.hpp>
#include <opencv2/structured_light/sinusoidalpattern.hpp>
#include <opencv2/imgcodecs.hpp>
#include "test_precomp.hpp"
using namespace std;
using namespace cv;
const string STRUCTURED_LIGHT_DIR = "structured_light";
const string FOLDER_DATA = "data";
TEST( SinusoidalPattern, unwrapPhaseMap )
{
string folder = cvtest::TS::ptr()->get_data_path() + "/" + STRUCTURED_LIGHT_DIR + "/" + FOLDER_DATA + "/";
structured_light::SinusoidalPattern::Params paramsPsp, paramsFtp, paramsFaps;
paramsFtp.methodId = 0;
paramsPsp.methodId = 1;
paramsFaps.methodId = 2;
Ptr<structured_light::SinusoidalPattern> sinusPsp = structured_light::SinusoidalPattern::create(paramsPsp);
Ptr<structured_light::SinusoidalPattern> sinusFtp = structured_light::SinusoidalPattern::create(paramsFtp);
Ptr<structured_light::SinusoidalPattern> sinusFaps = structured_light::SinusoidalPattern::create(paramsFaps);
vector<Mat> captures(3);
Mat unwrappedPhaseMapPspRef, unwrappedPhaseMapFtpRef, unwrappedPhaseMapFapsRef;
Mat shadowMask;
Mat wrappedPhaseMap, unwrappedPhaseMap, unwrappedPhaseMap8;
captures[0] = imread(folder + "capture_sin_0.jpg", IMREAD_GRAYSCALE);
captures[1] = imread(folder + "capture_sin_1.jpg", IMREAD_GRAYSCALE);
captures[2] = imread(folder + "capture_sin_2.jpg", IMREAD_GRAYSCALE);
unwrappedPhaseMapPspRef = imread(folder + "unwrappedPspTest.jpg", IMREAD_GRAYSCALE);
unwrappedPhaseMapFtpRef = imread(folder + "unwrappedFtpTest.jpg", IMREAD_GRAYSCALE);
unwrappedPhaseMapFapsRef = imread(folder + "unwrappedFapsTest.jpg", IMREAD_GRAYSCALE);
if( !captures[0].data || !captures[1].data || !captures[2].data || !unwrappedPhaseMapFapsRef.data
|| !unwrappedPhaseMapFtpRef.data || !unwrappedPhaseMapPspRef.data )
{
cerr << "invalid test data" << endl;
}
sinusPsp->computePhaseMap(captures, wrappedPhaseMap, shadowMask);
sinusPsp->unwrapPhaseMap(wrappedPhaseMap, unwrappedPhaseMap, Size(captures[0].cols, captures[1].rows), shadowMask);
unwrappedPhaseMap.convertTo(unwrappedPhaseMap8, CV_8U, 1, 128);
int sumOfDiff = 0;
int count = 0;
float ratio = 0;
for( int i = 0; i < unwrappedPhaseMap8.rows; ++i )
{
for( int j = 0; j < unwrappedPhaseMap8.cols; ++j )
{
int ref = unwrappedPhaseMapPspRef.at<uchar>(i, j);
int comp = unwrappedPhaseMap8.at<uchar>(i, j);
sumOfDiff += (ref - comp);
count ++;
}
}
ratio = (float)(sumOfDiff / count);
EXPECT_LE( ratio, 0.003 );
sinusFtp->computePhaseMap(captures, wrappedPhaseMap, shadowMask);
sinusFtp->unwrapPhaseMap(wrappedPhaseMap, unwrappedPhaseMap, Size(captures[0].cols, captures[1].rows), shadowMask);
unwrappedPhaseMap.convertTo(unwrappedPhaseMap8, CV_8U, 1, 128);
sumOfDiff = 0;
count = 0;
ratio = 0;
for( int i = 0; i < unwrappedPhaseMap8.rows; ++i )
{
for( int j = 0; j < unwrappedPhaseMap8.cols; ++j )
{
int ref = unwrappedPhaseMapFtpRef.at<uchar>(i, j);
int comp = unwrappedPhaseMap8.at<uchar>(i, j);
sumOfDiff += (ref - comp);
count ++;
}
}
ratio = (float)(sumOfDiff / count);
EXPECT_LE( ratio, 0.003 );
sinusFaps->computePhaseMap(captures, wrappedPhaseMap, shadowMask);
sinusFaps->unwrapPhaseMap(wrappedPhaseMap, unwrappedPhaseMap, Size(captures[0].cols, captures[1].rows), shadowMask);
unwrappedPhaseMap.convertTo(unwrappedPhaseMap8, CV_8U, 1, 128);
sumOfDiff = 0;
count = 0;
ratio = 0;
for( int i = 0; i < unwrappedPhaseMap8.rows; ++i )
{
for( int j = 0; j < unwrappedPhaseMap8.cols; ++j )
{
int ref = unwrappedPhaseMapFapsRef.at<uchar>(i, j);
int comp = unwrappedPhaseMap8.at<uchar>(i, j);
sumOfDiff += (ref - comp);
count ++;
}
}
ratio = (float)(sumOfDiff / count);
EXPECT_LE( ratio, 0.003 );
}
Capture Sinusoidal pattern tutorial {#tutorial_capture_sinusoidal_pattern}
=============
Goal
----
In this tutorial, you will learn how to use the sinusoidal pattern class to:
- Generate sinusoidal patterns.
- Project the generated patterns.
- Capture the projected patterns.
- Compute a wrapped phase map from these patterns using three different algorithms (Fourier Transform Profilometry, Phase Shifting Profilometry, Fourier-assisted Phase Shifting Profilometry)
- Unwrap the previous phase map.
Code
----
@include structured_light/samples/capsinpattern.cpp
Expalantion
-----------
First, the sinusoidal patterns must be generated. *SinusoidalPattern* class parameters have to be set by the user:
- projector width and height
- number of periods in the patterns
- set cross markers in the patterns (used to convert relative phase map to absolute phase map)
- patterns direction (horizontal or vertical)
- phase shift value (usually set to 2pi/3 to enable a cyclical system)
- number of pixels between two consecutive markers on the same row/column
- id of the method used to compute the phase map (FTP = 0, PSP = 1, FAPS = 2)
The user can also choose to save the patterns and the phase map.
@code{.cpp}
structured_light::SinusoidalPattern::Params params;
params.width = parser.get<int>(0);
params.height = parser.get<int>(1);
params.nbrOfPeriods = parser.get<int>(2);
params.setMarkers = parser.get<bool>(3);
params.horizontal = parser.get<bool>(4);
params.methodId = parser.get<int>(5);
params.shiftValue = static_cast<float>(2 * CV_PI / 3);
params.nbrOfPixelsBetweenMarkers = 70;
String outputPatternPath = parser.get<String>(6);
String outputWrappedPhasePath = parser.get<String>(7);
String outputUnwrappedPhasePath = parser.get<String>(8);
Ptr<structured_light::SinusoidalPattern> sinus = structured_light::SinusoidalPattern::create(params);
// Storage for patterns
vector<Mat> patterns;
//Generate sinusoidal patterns
sinus->generate(patterns);
@endcode
The number of patterns is always equal to three, no matter the method used to compute the phase map. Those three patterns are projected in a loop which is fine since the system is cyclical.
Once the patterns have been generated, the camera is opened and the patterns are projected, using fullscreen resolution. In this tutorial, a prosilica camera is used to capture gray images. When the first pattern is displayed by the projector, the user can press any key to start the projection sequence.
@code{.cpp}
VideoCapture cap(CAP_PVAPI);
if( !cap.isOpened() )
{
cout << "Camera could not be opened" << endl;
return -1;
}
cap.set(CAP_PROP_PVAPI_PIXELFORMAT, CAP_PVAPI_PIXELFORMAT_MONO8);
namedWindow("pattern", WINDOW_NORMAL);
setWindowProperty("pattern", WND_PROP_FULLSCREEN, WINDOW_FULLSCREEN);
imshow("pattern", patterns[0]);
cout << "Press any key when ready" << endl;
waitKey(0);
@endcode
In this tutorial, 30 images are projected so, each of the three patterns is projected ten times.
The "while" loop takes care of the projection process. The captured images are stored in a vector of Mat. There is a 30 ms delay between two successive captures.
When the projection is done, the user has to press "Enter" to start computing the phase maps.
@code{.cpp}
int nbrOfImages = 30;
int count = 0;
vector<Mat> img(nbrOfImages);
Size camSize(-1, -1);
while( count < nbrOfImages )
{
for(int i = 0; i < (int)patterns.size(); ++i )
{
imshow("pattern", patterns[i]);
waitKey(30);
cap >> img[count];
count += 1;
}
}
cout << "press enter when ready" << endl;
bool loop = true;
while ( loop )
{
char c = waitKey(0);
if( c == 10 )
{
loop = false;
}
}
@endcode
The phase maps are ready to be computed according to the selected method.
For FTP, a phase map is computed for each projected pattern, but we need to compute the shadow mask from three successive patterns, as explained in @cite faps. Therefore, three patterns are set in a vector called captures. Care is taken to fill this vector with three patterns, especially when we reach the last captures. The unwrapping algorithm needs to know the size of the captured images so, we make sure to give it to the "unwrapPhaseMap" method.
The phase maps are converted to 8-bit images in order to save them as png.
@code{.cpp}
switch(params.methodId)
{
case structured_light::FTP:
for( int i = 0; i < nbrOfImages; ++i )
{
/*We need three images to compute the shadow mask, as described in the reference paper
* even if the phase map is computed from one pattern only
*/
vector<Mat> captures;
if( i == nbrOfImages - 2 )
{
captures.push_back(img[i]);
captures.push_back(img[i-1]);
captures.push_back(img[i+1]);
}
else if( i == nbrOfImages - 1 )
{
captures.push_back(img[i]);
captures.push_back(img[i-1]);
captures.push_back(img[i-2]);
}
else
{
captures.push_back(img[i]);
captures.push_back(img[i+1]);
captures.push_back(img[i+2]);
}
sinus->computePhaseMap(captures, wrappedPhaseMap, shadowMask);
if( camSize.height == -1 )
{
camSize.height = img[i].rows;
camSize.width = img[i].cols;
}
sinus->unwrapPhaseMap(wrappedPhaseMap, unwrappedPhaseMap, camSize, shadowMask);
unwrappedPhaseMap.convertTo(unwrappedPhaseMap8, CV_8U, 1, 128);
wrappedPhaseMap.convertTo(wrappedPhaseMap8, CV_8U, 255, 128);
if( !outputUnwrappedPhasePath.empty() )
{
ostringstream name;
name << i;
imwrite(outputUnwrappedPhasePath + "_FTP_" + name.str() + ".png", unwrappedPhaseMap8);
}
if( !outputWrappedPhasePath.empty() )
{
ostringstream name;
name << i;
imwrite(outputWrappedPhasePath + "_FTP_" + name.str() + ".png", wrappedPhaseMap8);
}
}
break;
@endcode
For PSP and FAPS, three projected images are used to compute a single phase map. These three images are set in "captures", a vector working as a FIFO.Here again, phase maps are converted to 8-bit images in order to save them as png.
@code{.cpp}
case structured_light::PSP:
case structured_light::FAPS:
for( int i = 0; i < nbrOfImages - 2; ++i )
{
vector<Mat> captures;
captures.push_back(img[i]);
captures.push_back(img[i+1]);
captures.push_back(img[i+2]);
sinus->computePhaseMap(captures, wrappedPhaseMap, shadowMask);
if( camSize.height == -1 )
{
camSize.height = img[i].rows;
camSize.width = img[i].cols;
}
sinus->unwrapPhaseMap(wrappedPhaseMap, unwrappedPhaseMap, camSize, shadowMask);
unwrappedPhaseMap.convertTo(unwrappedPhaseMap8, CV_8U, 1, 128);
wrappedPhaseMap.convertTo(wrappedPhaseMap8, CV_8U, 255, 128);
if( !outputUnwrappedPhasePath.empty() )
{
ostringstream name;
name << i;
if( params.methodId == structured_light::PSP )
imwrite(outputUnwrappedPhasePath + "_PSP_" + name.str() + ".png", unwrappedPhaseMap8);
else
imwrite(outputUnwrappedPhasePath + "_FAPS_" + name.str() + ".png", unwrappedPhaseMap8);
}
if( !outputWrappedPhasePath.empty() )
{
ostringstream name;
name << i;
if( params.methodId == structured_light::PSP )
imwrite(outputWrappedPhasePath + "_PSP_" + name.str() + ".png", wrappedPhaseMap8);
else
imwrite(outputWrappedPhasePath + "_FAPS_" + name.str() + ".png", wrappedPhaseMap8);
}
}
break;
@endcode
......@@ -15,4 +15,12 @@ Structured Light tutorials {#tutorial_structured_light}
_Author:_ Roberta Ravanelli
You will learn how to decode a previously acquired Gray code pattern, generating a pointcloud.
\ No newline at end of file
You will learn how to decode a previously acquired Gray code pattern, generating a pointcloud.
- @subpage tutorial_capture_sinusoidal_pattern
_Compatibility:_ \> OpenCV 3.0.0
_Author:_ Ambroise Moreau
You will learn how to compute phase maps using *SinusoidalPattern* class.
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