vendor: OpenCV 5.0.0 snapshot at 755e50675d97db9b7d449d8bd6b09888646f6c6e

This commit is contained in:
Gitea Mirror Bot
2026-08-22 00:11:13 +08:00
commit 12022378a3
3872 changed files with 2513409 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
set(the_description "Structured Light API")
ocv_define_module(structured_light opencv_core opencv_imgproc opencv_geometry opencv_stereo opencv_calib opencv_phase_unwrapping OPTIONAL opencv_viz WRAP python java objc)
+4
View File
@@ -0,0 +1,4 @@
Structured Light Use
====================
How to generate and project gray code patterns and use them to find dense depth in a scene.
Binary file not shown.

After

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 260 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

@@ -0,0 +1,26 @@
@article{UNDERWORLD,
title={{3DUNDERWORLD-SLS}: {A}n {O}pen-{S}ource {S}tructured-{L}ight {S}canning {S}ystem for {R}apid {G}eometry {A}cquisition},
author={Herakleous, Kyriakos and Poullis, Charalambos},
journal={arXiv preprint arXiv:1406.6595},
year={2014}
}
@Article{pattern,
author = {Salvi, Joaquim and Pag\'es, Jordi and Batlle, Joan},
title = {Pattern codification strategies in structured light systems},
journal = {Pattern Recognition},
volume = {37},
number = {4},
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},
}
@@ -0,0 +1,68 @@
/*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*/
/*#ifdef __OPENCV_BUILD
#error this is a compatibility header which should not be used inside the OpenCV library
#endif*/
#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
Structured light is considered one of the most effective techniques to acquire 3D models.
This technique is based on projecting a light pattern and capturing the illuminated scene
from one or more points of view. Since the pattern is coded, correspondences between image
points and points of the projected pattern can be quickly found and 3D information easily
retrieved.
One of the most commonly exploited coding strategies is based on trmatime-multiplexing. In this
case, a set of patterns are successively projected onto the measuring surface.
The codeword for a given pixel is usually formed by the sequence of illuminance values for that
pixel across the projected patterns. Thus, the codification is called temporal because the bits
of the codewords are multiplexed in time @cite pattern .
In this module a time-multiplexing coding strategy based on Gray encoding is implemented following the
(stereo) approach described in 3DUNDERWORLD algorithm @cite UNDERWORLD .
For more details, see @ref tutorial_structured_light.
*/
@@ -0,0 +1,149 @@
/*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_GRAY_CODE_PATTERN_HPP__
#define __OPENCV_GRAY_CODE_PATTERN_HPP__
#include "opencv2/core.hpp"
#include "opencv2/structured_light/structured_light.hpp"
namespace cv {
namespace structured_light {
//! @addtogroup structured_light
//! @{
/** @brief Class implementing the Gray-code pattern, based on @cite UNDERWORLD.
*
* The generation of the pattern images is performed with Gray encoding using the traditional white and black colors.
*
* The information about the two image axes x, y is encoded separately into two different pattern sequences.
* A projector P with resolution (P_res_x, P_res_y) will result in Ncols = log 2 (P_res_x) encoded pattern images representing the columns, and
* in Nrows = log 2 (P_res_y) encoded pattern images representing the rows.
* For example a projector with resolution 1024x768 will result in Ncols = 10 and Nrows = 10.
* However, the generated pattern sequence consists of both regular color and color-inverted images: inverted pattern images are images
* with the same structure as the original but with inverted colors.
* This provides an effective method for easily determining the intensity value of each pixel when it is lit (highest value) and
* when it is not lit (lowest value). So for a a projector with resolution 1024x768, the number of pattern images will be Ncols * 2 + Nrows * 2 = 40.
*
*/
class CV_EXPORTS_W GrayCodePattern : public StructuredLightPattern
{
public:
/** @brief Parameters of StructuredLightPattern constructor.
* @param width Projector's width. Default value is 1024.
* @param height Projector's height. Default value is 768.
*/
struct CV_EXPORTS Params
{
Params();
int width;
int height;
};
/** @brief Constructor
@param parameters GrayCodePattern parameters GrayCodePattern::Params: the width and the height of the projector.
*/
static Ptr<GrayCodePattern> create( const GrayCodePattern::Params &parameters = GrayCodePattern::Params() );
// alias for scripting
CV_WRAP
static Ptr<GrayCodePattern> create( int width, int height );
/** @brief Get the number of pattern images needed for the graycode pattern.
*
* @return The number of pattern images needed for the graycode pattern.
*
*/
CV_WRAP
virtual size_t getNumberOfPatternImages() const = 0;
/** @brief Sets the value for white threshold, needed for decoding.
*
* White threshold is a number between 0-255 that represents the minimum brightness difference required for valid pixels, between the graycode pattern and its inverse images; used in getProjPixel method.
*
* @param value The desired white threshold value.
*
*/
CV_WRAP
virtual void setWhiteThreshold( size_t value ) = 0;
/** @brief Sets the value for black threshold, needed for decoding (shadowsmasks computation).
*
* Black threshold is a number between 0-255 that represents the minimum brightness difference required for valid pixels, between the fully illuminated (white) and the not illuminated images (black); used in computeShadowMasks method.
*
* @param value The desired black threshold value.
*
*/
CV_WRAP
virtual void setBlackThreshold( size_t value ) = 0;
/** @brief Generates the all-black and all-white images needed for shadowMasks computation.
*
* To identify shadow regions, the regions of two images where the pixels are not lit by projector's light and thus where there is not coded information,
* the 3DUNDERWORLD algorithm computes a shadow mask for the two cameras views, starting from a white and a black images captured by each camera.
* This method generates these two additional images to project.
*
* @param blackImage The generated all-black CV_8U image, at projector's resolution.
* @param whiteImage The generated all-white CV_8U image, at projector's resolution.
*/
CV_WRAP
virtual void getImagesForShadowMasks( InputOutputArray blackImage, InputOutputArray whiteImage ) const = 0;
/** @brief For a (x,y) pixel of a camera returns the corresponding projector pixel.
*
* The function decodes each pixel in the pattern images acquired by a camera into their corresponding decimal numbers representing the projector's column and row,
* providing a mapping between camera's and projector's pixel.
*
* @param patternImages The pattern images acquired by the camera, stored in a grayscale vector < Mat >.
* @param x x coordinate of the image pixel.
* @param y y coordinate of the image pixel.
* @param projPix Projector's pixel corresponding to the camera's pixel: projPix.x and projPix.y are the image coordinates of the projector's pixel corresponding to the pixel being decoded in a camera.
*/
CV_WRAP
virtual bool getProjPixel( InputArrayOfArrays patternImages, int x, int y, CV_OUT Point &projPix ) const = 0;
};
//! @}
}
}
#endif
@@ -0,0 +1,151 @@
/*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/geometry.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_W Params
{
CV_WRAP Params();
CV_PROP_RW int width;
CV_PROP_RW int height;
CV_PROP_RW int nbrOfPeriods;
CV_PROP_RW float shiftValue;
CV_PROP_RW int methodId;
CV_PROP_RW int nbrOfPixelsBetweenMarkers;
CV_PROP_RW bool horizontal;
CV_PROP_RW bool setMarkers;
std::vector<Point2f> markersLocation;
};
/**
* @brief Constructor.
* @param parameters SinusoidalPattern parameters SinusoidalPattern::Params: width, height of the projector and patterns parameters.
*
*/
CV_WRAP static Ptr<SinusoidalPattern> create( Ptr<SinusoidalPattern::Params> parameters =
makePtr<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( InputArray 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
@@ -0,0 +1,91 @@
/*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_STRUCTURED_LIGHT_HPP__
#define __OPENCV_STRUCTURED_LIGHT_HPP__
#include "opencv2/core.hpp"
namespace cv {
namespace structured_light {
//! @addtogroup structured_light
//! @{
//! Type of the decoding algorithm
// other algorithms can be implemented
enum
{
DECODE_3D_UNDERWORLD = 0 //!< Kyriakos Herakleous, Charalambos Poullis. "3DUNDERWORLD-SLS: An Open-Source Structured-Light Scanning System for Rapid Geometry Acquisition", arXiv preprint arXiv:1406.6595 (2014).
};
/** @brief Abstract base class for generating and decoding structured light patterns.
*/
class CV_EXPORTS_W StructuredLightPattern : public virtual Algorithm
{
public:
/** @brief Generates the structured light pattern to project.
@param patternImages The generated pattern: a vector<Mat>, in which each image is a CV_8U Mat at projector's resolution.
*/
CV_WRAP
virtual bool generate( OutputArrayOfArrays patternImages ) = 0;
/** @brief Decodes the structured light pattern, generating a disparity map
@param patternImages The acquired pattern images to decode (vector<vector<Mat>>), loaded as grayscale and previously rectified.
@param disparityMap The decoding result: a CV_64F Mat at image resolution, storing the computed disparity map.
@param blackImages The all-black images needed for shadowMasks computation.
@param whiteImages The all-white images needed for shadowMasks computation.
@param flags Flags setting decoding algorithms. Default: DECODE_3D_UNDERWORLD.
@note All the images must be at the same resolution.
*/
CV_WRAP
virtual bool decode( const std::vector< std::vector<Mat> >& patternImages, OutputArray disparityMap,
InputArrayOfArrays blackImages = noArray(),
InputArrayOfArrays whiteImages = noArray(),
int flags = DECODE_3D_UNDERWORLD ) const = 0;
};
//! @}
}
}
#endif
@@ -0,0 +1,5 @@
{
"AdditionalImports" : {
"*" : [ "\"structured_light.hpp\"" ]
}
}
@@ -0,0 +1,94 @@
#!/usr/bin/env python
# Python 2/3 compatibility
from __future__ import print_function
import os, numpy
import cv2 as cv
from tests_common import NewOpenCVTests
class structured_light_test(NewOpenCVTests):
def test_unwrap(self):
paramsPsp = cv.structured_light_SinusoidalPattern_Params();
paramsFtp = cv.structured_light_SinusoidalPattern_Params();
paramsFaps = cv.structured_light_SinusoidalPattern_Params();
paramsPsp.methodId = cv.structured_light.PSP;
paramsFtp.methodId = cv.structured_light.FTP;
paramsFaps.methodId = cv.structured_light.FAPS;
sinusPsp = cv.structured_light.SinusoidalPattern_create(paramsPsp)
sinusFtp = cv.structured_light.SinusoidalPattern_create(paramsFtp)
sinusFaps = cv.structured_light.SinusoidalPattern_create(paramsFaps)
captures = []
for i in range(0,3):
capture = self.get_sample('/cv/structured_light/data/capture_sin_%d.jpg'%i, cv.IMREAD_GRAYSCALE)
if capture is None:
raise unittest.SkipTest("Missing files with test data")
captures.append(capture)
rows,cols = captures[0].shape
unwrappedPhaseMapPspRef = self.get_sample('/cv/structured_light/data/unwrappedPspTest.jpg',
cv.IMREAD_GRAYSCALE)
unwrappedPhaseMapFtpRef = self.get_sample('/cv/structured_light/data/unwrappedFtpTest.jpg',
cv.IMREAD_GRAYSCALE)
unwrappedPhaseMapFapsRef = self.get_sample('/cv/structured_light/data/unwrappedFapsTest.jpg',
cv.IMREAD_GRAYSCALE)
wrappedPhaseMap,shadowMask = sinusPsp.computePhaseMap(captures);
unwrappedPhaseMap = sinusPsp.unwrapPhaseMap(wrappedPhaseMap, (cols, rows), shadowMask=shadowMask)
unwrappedPhaseMap8 = unwrappedPhaseMap*1 + 128
unwrappedPhaseMap8 = numpy.uint8(unwrappedPhaseMap8)
sumOfDiff = 0
count = 0
for i in range(rows):
for j in range(cols):
ref = int(unwrappedPhaseMapPspRef[i, j])
comp = int(unwrappedPhaseMap8[i, j])
sumOfDiff += (ref - comp)
count += 1
ratio = sumOfDiff/float(count)
self.assertLessEqual(ratio, 0.2)
wrappedPhaseMap,shadowMask = sinusFtp.computePhaseMap(captures);
unwrappedPhaseMap = sinusFtp.unwrapPhaseMap(wrappedPhaseMap, (cols, rows), shadowMask=shadowMask)
unwrappedPhaseMap8 = unwrappedPhaseMap*1 + 128
unwrappedPhaseMap8 = numpy.uint8(unwrappedPhaseMap8)
sumOfDiff = 0
count = 0
for i in range(rows):
for j in range(cols):
ref = int(unwrappedPhaseMapFtpRef[i, j])
comp = int(unwrappedPhaseMap8[i, j])
sumOfDiff += (ref - comp)
count += 1
ratio = sumOfDiff/float(count)
self.assertLessEqual(ratio, 0.2)
wrappedPhaseMap,shadowMask2 = sinusFaps.computePhaseMap(captures);
unwrappedPhaseMap = sinusFaps.unwrapPhaseMap(wrappedPhaseMap, (cols, rows), shadowMask=shadowMask)
unwrappedPhaseMap8 = unwrappedPhaseMap*1 + 128
unwrappedPhaseMap8 = numpy.uint8(unwrappedPhaseMap8)
sumOfDiff = 0
count = 0
for i in range(rows):
for j in range(cols):
ref = int(unwrappedPhaseMapFapsRef[i, j])
comp = int(unwrappedPhaseMap8[i, j])
sumOfDiff += (ref - comp)
count += 1
ratio = sumOfDiff/float(count)
self.assertLessEqual(ratio, 0.2)
if __name__ == '__main__':
NewOpenCVTests.bootstrap()
@@ -0,0 +1,215 @@
/*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/structured_light.hpp>
#include <iostream>
#include <stdio.h>
using namespace cv;
using namespace std;
static const char* keys =
{ "{@path | | Path of the folder where the captured pattern images will be save }"
"{@proj_width | | Projector width }"
"{@proj_height | | Projector height }" };
static void help()
{
cout << "\nThis example shows how to use the \"Structured Light module\" to acquire a graycode pattern"
"\nCall (with the two cams connected):\n"
"./example_structured_light_cap_pattern <path> <proj_width> <proj_height> \n"
<< endl;
}
int main( int argc, char** argv )
{
structured_light::GrayCodePattern::Params params;
CommandLineParser parser( argc, argv, keys );
String path = parser.get<String>( 0 );
params.width = parser.get<int>( 1 );
params.height = parser.get<int>( 2 );
if( path.empty() || params.width < 1 || params.height < 1 )
{
help();
return -1;
}
// Set up GraycodePattern with params
Ptr<structured_light::GrayCodePattern> graycode = structured_light::GrayCodePattern::create( params );
// Storage for pattern
vector<Mat> pattern;
graycode->generate( pattern );
cout << pattern.size() << " pattern images + 2 images for shadows mask computation to acquire with both cameras"
<< endl;
// Generate the all-white and all-black images needed for shadows mask computation
Mat white;
Mat black;
graycode->getImagesForShadowMasks( black, white );
pattern.push_back( white );
pattern.push_back( black );
// Setting pattern window on second monitor (the projector's one)
namedWindow( "Pattern Window", WINDOW_NORMAL );
resizeWindow( "Pattern Window", params.width, params.height );
moveWindow( "Pattern Window", params.width + 316, -20 );
setWindowProperty( "Pattern Window", WND_PROP_FULLSCREEN, WINDOW_FULLSCREEN );
// Open camera number 1, using libgphoto2
VideoCapture cap1( CAP_GPHOTO2 );
if( !cap1.isOpened() )
{
// check if cam1 opened
cout << "cam1 not opened!" << endl;
help();
return -1;
}
// Open camera number 2
VideoCapture cap2( 1 );
if( !cap2.isOpened() )
{
// check if cam2 opened
cout << "cam2 not opened!" << endl;
help();
return -1;
}
// Turning off autofocus
cap1.set( CAP_PROP_SETTINGS, 1 );
cap2.set( CAP_PROP_SETTINGS, 1 );
int i = 0;
while( i < (int) pattern.size() )
{
cout << "Waiting to save image number " << i + 1 << endl << "Press any key to acquire the photo" << endl;
imshow( "Pattern Window", pattern[i] );
Mat frame1;
Mat frame2;
cap1 >> frame1; // get a new frame from camera 1
cap2 >> frame2; // get a new frame from camera 2
if( ( frame1.data ) && ( frame2.data ) )
{
Mat tmp;
cout << "cam 1 size: " << Size( ( int ) cap1.get( CAP_PROP_FRAME_WIDTH ), ( int ) cap1.get( CAP_PROP_FRAME_HEIGHT ) )
<< endl;
cout << "cam 2 size: " << Size( ( int ) cap2.get( CAP_PROP_FRAME_WIDTH ), ( int ) cap2.get( CAP_PROP_FRAME_HEIGHT ) )
<< endl;
cout << "zoom cam 1: " << cap1.get( CAP_PROP_ZOOM ) << endl << "zoom cam 2: " << cap2.get( CAP_PROP_ZOOM )
<< endl;
cout << "focus cam 1: " << cap1.get( CAP_PROP_FOCUS ) << endl << "focus cam 2: " << cap2.get( CAP_PROP_FOCUS )
<< endl;
cout << "Press enter to save the photo or an other key to re-acquire the photo" << endl;
namedWindow( "cam1", WINDOW_NORMAL );
resizeWindow( "cam1", 640, 480 );
namedWindow( "cam2", WINDOW_NORMAL );
resizeWindow( "cam2", 640, 480 );
// Moving window of cam2 to see the image at the same time with cam1
moveWindow( "cam2", 640 + 75, 0 );
// Resizing images to avoid issues for high resolution images, visualizing them as grayscale
resize( frame1, tmp, Size( 640, 480 ), 0, 0, INTER_LINEAR_EXACT);
cvtColor( tmp, tmp, COLOR_RGB2GRAY );
imshow( "cam1", tmp );
resize( frame2, tmp, Size( 640, 480 ), 0, 0, INTER_LINEAR_EXACT);
cvtColor( tmp, tmp, COLOR_RGB2GRAY );
imshow( "cam2", tmp );
bool save1 = false;
bool save2 = false;
int key = waitKey( 0 );
// Pressing enter, it saves the output
if( key == 13 )
{
ostringstream name;
name << i + 1;
save1 = imwrite( path + "pattern_cam1_im" + name.str() + ".png", frame1 );
save2 = imwrite( path + "pattern_cam2_im" + name.str() + ".png", frame2 );
if( ( save1 ) && ( save2 ) )
{
cout << "pattern cam1 and cam2 images number " << i + 1 << " saved" << endl << endl;
i++;
}
else
{
cout << "pattern cam1 and cam2 images number " << i + 1 << " NOT saved" << endl << endl << "Retry, check the path"<< endl << endl;
}
}
// Pressing escape, the program closes
if( key == 27 )
{
cout << "Closing program" << endl;
}
}
else
{
cout << "No frame data, waiting for new frame" << endl;
}
}
// the camera will be deinitialized automatically in VideoCapture destructor
return 0;
}
@@ -0,0 +1,336 @@
/*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/geometry.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(makePtr<structured_light::SinusoidalPattern::Params>(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;
}
@@ -0,0 +1,299 @@
/*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 <iostream>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/geometry.hpp>
#include <opencv2/stereo.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/structured_light.hpp>
#include <opencv2/opencv_modules.hpp>
// (if you did not build the opencv_viz module, you will only see the disparity images)
#ifdef HAVE_OPENCV_VIZ
#include <opencv2/viz.hpp>
#endif
using namespace std;
using namespace cv;
static const char* keys =
{ "{@images_list | | Image list where the captured pattern images are saved}"
"{@calib_param_path | | Calibration_parameters }"
"{@proj_width | | The projector width used to acquire the pattern }"
"{@proj_height | | The projector height used to acquire the pattern}"
"{@white_thresh | | The white threshold height (optional)}"
"{@black_thresh | | The black threshold (optional)}" };
static void help()
{
cout << "\nThis example shows how to use the \"Structured Light module\" to decode a previously acquired gray code pattern, generating a pointcloud"
"\nCall:\n"
"./example_structured_light_pointcloud <images_list> <calib_param_path> <proj_width> <proj_height> <white_thresh> <black_thresh>\n"
<< endl;
}
static bool readStringList( const string& filename, vector<string>& l )
{
l.resize( 0 );
FileStorage fs( filename, FileStorage::READ );
if( !fs.isOpened() )
{
cerr << "failed to open " << filename << endl;
return false;
}
FileNode n = fs.getFirstTopLevelNode();
if( n.type() != FileNode::SEQ )
{
cerr << "cam 1 images are not a sequence! FAIL" << endl;
return false;
}
FileNodeIterator it = n.begin(), it_end = n.end();
for( ; it != it_end; ++it )
{
l.push_back( ( string ) *it );
}
n = fs["cam2"];
if( n.type() != FileNode::SEQ )
{
cerr << "cam 2 images are not a sequence! FAIL" << endl;
return false;
}
it = n.begin(), it_end = n.end();
for( ; it != it_end; ++it )
{
l.push_back( ( string ) *it );
}
if( l.size() % 2 != 0 )
{
cout << "Error: the image list contains odd (non-even) number of elements\n";
return false;
}
return true;
}
int main( int argc, char** argv )
{
structured_light::GrayCodePattern::Params params;
CommandLineParser parser( argc, argv, keys );
String images_file = parser.get<String>( 0 );
String calib_file = parser.get<String>( 1 );
params.width = parser.get<int>( 2 );
params.height = parser.get<int>( 3 );
if( images_file.empty() || calib_file.empty() || params.width < 1 || params.height < 1 || argc < 5 || argc > 7 )
{
help();
return -1;
}
// Set up GraycodePattern with params
Ptr<structured_light::GrayCodePattern> graycode = structured_light::GrayCodePattern::create( params );
size_t white_thresh = 0;
size_t black_thresh = 0;
if( argc == 7 )
{
// If passed, setting the white and black threshold, otherwise using default values
white_thresh = parser.get<unsigned>( 4 );
black_thresh = parser.get<unsigned>( 5 );
graycode->setWhiteThreshold( white_thresh );
graycode->setBlackThreshold( black_thresh );
}
vector<string> imagelist;
bool ok = readStringList( images_file, imagelist );
if( !ok || imagelist.empty() )
{
cout << "can not open " << images_file << " or the string list is empty" << endl;
help();
return -1;
}
FileStorage fs( calib_file, FileStorage::READ );
if( !fs.isOpened() )
{
cout << "Failed to open Calibration Data File." << endl;
help();
return -1;
}
// Loading calibration parameters
Mat cam1intrinsics, cam1distCoeffs, cam2intrinsics, cam2distCoeffs, R, T;
fs["cam1_intrinsics"] >> cam1intrinsics;
fs["cam2_intrinsics"] >> cam2intrinsics;
fs["cam1_distorsion"] >> cam1distCoeffs;
fs["cam2_distorsion"] >> cam2distCoeffs;
fs["R"] >> R;
fs["T"] >> T;
cout << "cam1intrinsics" << endl << cam1intrinsics << endl;
cout << "cam1distCoeffs" << endl << cam1distCoeffs << endl;
cout << "cam2intrinsics" << endl << cam2intrinsics << endl;
cout << "cam2distCoeffs" << endl << cam2distCoeffs << endl;
cout << "T" << endl << T << endl << "R" << endl << R << endl;
if( (!R.data) || (!T.data) || (!cam1intrinsics.data) || (!cam2intrinsics.data) || (!cam1distCoeffs.data) || (!cam2distCoeffs.data) )
{
cout << "Failed to load cameras calibration parameters" << endl;
help();
return -1;
}
size_t numberOfPatternImages = graycode->getNumberOfPatternImages();
vector<vector<Mat> > captured_pattern;
captured_pattern.resize( 2 );
captured_pattern[0].resize( numberOfPatternImages );
captured_pattern[1].resize( numberOfPatternImages );
Mat color = imread( imagelist[numberOfPatternImages], IMREAD_COLOR );
Size imagesSize = color.size();
// Stereo rectify
cout << "Rectifying images..." << endl;
Mat R1, R2, P1, P2, Q;
Rect validRoi[2];
stereoRectify( cam1intrinsics, cam1distCoeffs, cam2intrinsics, cam2distCoeffs, imagesSize, R, T, R1, R2, P1, P2, Q, 0,
-1, imagesSize, &validRoi[0], &validRoi[1] );
Mat map1x, map1y, map2x, map2y;
initUndistortRectifyMap( cam1intrinsics, cam1distCoeffs, R1, P1, imagesSize, CV_32FC1, map1x, map1y );
initUndistortRectifyMap( cam2intrinsics, cam2distCoeffs, R2, P2, imagesSize, CV_32FC1, map2x, map2y );
// Loading pattern images
for( size_t i = 0; i < numberOfPatternImages; i++ )
{
captured_pattern[0][i] = imread( imagelist[i], IMREAD_GRAYSCALE );
captured_pattern[1][i] = imread( imagelist[i + numberOfPatternImages + 2], IMREAD_GRAYSCALE );
if( (!captured_pattern[0][i].data) || (!captured_pattern[1][i].data) )
{
cout << "Empty images" << endl;
help();
return -1;
}
remap( captured_pattern[1][i], captured_pattern[1][i], map1x, map1y, INTER_NEAREST, BORDER_CONSTANT, Scalar() );
remap( captured_pattern[0][i], captured_pattern[0][i], map2x, map2y, INTER_NEAREST, BORDER_CONSTANT, Scalar() );
}
cout << "done" << endl;
vector<Mat> blackImages;
vector<Mat> whiteImages;
blackImages.resize( 2 );
whiteImages.resize( 2 );
// Loading images (all white + all black) needed for shadows computation
cvtColor( color, whiteImages[0], COLOR_RGB2GRAY );
whiteImages[1] = imread( imagelist[2 * numberOfPatternImages + 2], IMREAD_GRAYSCALE );
blackImages[0] = imread( imagelist[numberOfPatternImages + 1], IMREAD_GRAYSCALE );
blackImages[1] = imread( imagelist[2 * numberOfPatternImages + 2 + 1], IMREAD_GRAYSCALE );
remap( color, color, map2x, map2y, INTER_NEAREST, BORDER_CONSTANT, Scalar() );
remap( whiteImages[0], whiteImages[0], map2x, map2y, INTER_NEAREST, BORDER_CONSTANT, Scalar() );
remap( whiteImages[1], whiteImages[1], map1x, map1y, INTER_NEAREST, BORDER_CONSTANT, Scalar() );
remap( blackImages[0], blackImages[0], map2x, map2y, INTER_NEAREST, BORDER_CONSTANT, Scalar() );
remap( blackImages[1], blackImages[1], map1x, map1y, INTER_NEAREST, BORDER_CONSTANT, Scalar() );
cout << endl << "Decoding pattern ..." << endl;
Mat disparityMap;
bool decoded = graycode->decode( captured_pattern, disparityMap, blackImages, whiteImages,
structured_light::DECODE_3D_UNDERWORLD );
if( decoded )
{
cout << endl << "pattern decoded" << endl;
// To better visualize the result, apply a colormap to the computed disparity
double min;
double max;
minMaxIdx(disparityMap, &min, &max);
Mat cm_disp, scaledDisparityMap;
cout << "disp min " << min << endl << "disp max " << max << endl;
convertScaleAbs( disparityMap, scaledDisparityMap, 255 / ( max - min ) );
applyColorMap( scaledDisparityMap, cm_disp, COLORMAP_JET );
// Show the result
resize( cm_disp, cm_disp, Size( 640, 480 ), 0, 0, INTER_LINEAR_EXACT );
imshow( "cm disparity m", cm_disp );
// Compute the point cloud
Mat pointcloud;
disparityMap.convertTo( disparityMap, CV_32FC1 );
reprojectImageTo3D( disparityMap, pointcloud, Q, true, -1 );
// Compute a mask to remove background
Mat dst, thresholded_disp;
threshold( scaledDisparityMap, thresholded_disp, 0, 255, THRESH_OTSU + THRESH_BINARY );
resize( thresholded_disp, dst, Size( 640, 480 ), 0, 0, INTER_LINEAR_EXACT );
imshow( "threshold disp otsu", dst );
#ifdef HAVE_OPENCV_VIZ
// Apply the mask to the point cloud
Mat pointcloud_tresh, color_tresh;
pointcloud.copyTo( pointcloud_tresh, thresholded_disp );
color.copyTo( color_tresh, thresholded_disp );
// Show the point cloud on viz
viz::Viz3d myWindow( "Point cloud with color" );
myWindow.setBackgroundMeshLab();
myWindow.showWidget( "coosys", viz::WCoordinateSystem() );
myWindow.showWidget( "pointcloud", viz::WCloud( pointcloud_tresh, color_tresh ) );
myWindow.showWidget( "text2d", viz::WText( "Point cloud", Point(20, 20), 20, viz::Color::green() ) );
myWindow.spin();
#endif // HAVE_OPENCV_VIZ
}
waitKey();
return 0;
}
@@ -0,0 +1,519 @@
/*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/geometry.hpp>
#include <opencv2/objdetect.hpp>
#include <opencv2/calib.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(static_cast<float>(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);
}
}
@@ -0,0 +1,480 @@
/*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 GrayCodePattern_Impl CV_FINAL : public GrayCodePattern
{
public:
// Constructor
explicit GrayCodePattern_Impl( const GrayCodePattern::Params &parameters = GrayCodePattern::Params() );
// Destructor
virtual ~GrayCodePattern_Impl() CV_OVERRIDE {};
// Generates the gray code pattern as a std::vector<Mat>
bool generate( OutputArrayOfArrays patternImages ) CV_OVERRIDE;
// Decodes the gray code pattern, computing the disparity map
bool decode( const std::vector< std::vector<Mat> >& patternImages, OutputArray disparityMap, InputArrayOfArrays blackImages = noArray(),
InputArrayOfArrays whiteImages = noArray(), int flags = DECODE_3D_UNDERWORLD ) const CV_OVERRIDE;
// Returns the number of pattern images for the graycode pattern
size_t getNumberOfPatternImages() const CV_OVERRIDE;
// Sets the value for black threshold
void setBlackThreshold( size_t val ) CV_OVERRIDE;
// Sets the value for set the value for white threshold
void setWhiteThreshold( size_t val ) CV_OVERRIDE;
// Generates the images needed for shadowMasks computation
void getImagesForShadowMasks( InputOutputArray blackImage, InputOutputArray whiteImage ) const CV_OVERRIDE;
// For a (x,y) pixel of the camera returns the corresponding projector pixel
bool getProjPixel(InputArrayOfArrays patternImages, int x, int y, CV_OUT Point &projPix) const CV_OVERRIDE;
private:
// Parameters
Params params;
// The number of images of the pattern
size_t numOfPatternImages;
// The number of row images of the pattern
size_t numOfRowImgs;
// The number of column images of the pattern
size_t numOfColImgs;
// Number between 0-255 that represents the minimum brightness difference
// between the fully illuminated (white) and the non - illuminated images (black)
size_t blackThreshold;
// Number between 0-255 that represents the minimum brightness difference
// between the gray-code pattern and its inverse images
size_t whiteThreshold;
// Computes the required number of pattern images, allocating the pattern vector
void computeNumberOfPatternImages();
// Computes the shadows occlusion where we cannot reconstruct the model
void computeShadowMasks( InputArrayOfArrays blackImages, InputArrayOfArrays whiteImages,
OutputArrayOfArrays shadowMasks ) const;
// Converts a gray code sequence (~ binary number) to a decimal number
int grayToDec( const std::vector<uchar>& gray ) const;
};
/*
* GrayCodePattern
*/
GrayCodePattern::Params::Params()
{
width = 1024;
height = 768;
}
GrayCodePattern_Impl::GrayCodePattern_Impl( const GrayCodePattern::Params &parameters ) :
params( parameters )
{
computeNumberOfPatternImages();
blackThreshold = 40; // 3D_underworld default value
whiteThreshold = 5; // 3D_underworld default value
}
bool GrayCodePattern_Impl::generate( OutputArrayOfArrays pattern )
{
std::vector<Mat>& pattern_ = *( std::vector<Mat>* ) pattern.getObj();
pattern_.resize( numOfPatternImages );
for( size_t i = 0; i < numOfPatternImages; i++ )
{
pattern_[i] = Mat( params.height, params.width, CV_8U );
}
uchar flag = 0;
for( int j = 0; j < params.width; j++ ) // rows loop
{
int rem = 0, num = j, prevRem = j % 2;
for( size_t k = 0; k < numOfColImgs; k++ ) // images loop
{
num = num / 2;
rem = num % 2;
if( ( rem == 0 && prevRem == 1 ) || ( rem == 1 && prevRem == 0) )
{
flag = 1;
}
else
{
flag = 0;
}
for( int i = 0; i < params.height; i++ ) // rows loop
{
uchar pixel_color = ( uchar ) flag * 255;
pattern_[2 * numOfColImgs - 2 * k - 2].at<uchar>( i, j ) = pixel_color;
if( pixel_color > 0 )
pixel_color = ( uchar ) 0;
else
pixel_color = ( uchar ) 255;
pattern_[2 * numOfColImgs - 2 * k - 1].at<uchar>( i, j ) = pixel_color; // inverse
}
prevRem = rem;
}
}
for( int i = 0; i < params.height; i++ ) // rows loop
{
int rem = 0, num = i, prevRem = i % 2;
for( size_t k = 0; k < numOfRowImgs; k++ )
{
num = num / 2;
rem = num % 2;
if( (rem == 0 && prevRem == 1) || (rem == 1 && prevRem == 0) )
{
flag = 1;
}
else
{
flag = 0;
}
for( int j = 0; j < params.width; j++ )
{
uchar pixel_color = ( uchar ) flag * 255;
pattern_[2 * numOfRowImgs - 2 * k + 2 * numOfColImgs - 2].at<uchar>( i, j ) = pixel_color;
if( pixel_color > 0 )
pixel_color = ( uchar ) 0;
else
pixel_color = ( uchar ) 255;
pattern_[2 * numOfRowImgs - 2 * k + 2 * numOfColImgs - 1].at<uchar>( i, j ) = pixel_color;
}
prevRem = rem;
}
}
return true;
}
bool GrayCodePattern_Impl::decode( const std::vector< std::vector<Mat> >& patternImages, OutputArray disparityMap,
InputArrayOfArrays blackImages, InputArrayOfArrays whitheImages, int flags ) const
{
const std::vector<std::vector<Mat> >& acquired_pattern = patternImages;
if( flags == DECODE_3D_UNDERWORLD )
{
// Computing shadows mask
std::vector<Mat> shadowMasks;
computeShadowMasks( blackImages, whitheImages, shadowMasks );
int cam_width = acquired_pattern[0][0].cols;
int cam_height = acquired_pattern[0][0].rows;
Point projPixel;
// Storage for the pixels of the two cams that correspond to the same pixel of the projector
std::vector<std::vector<std::vector<Point> > > camsPixels;
camsPixels.resize( acquired_pattern.size() );
// TODO: parallelize for (k and j)
for( size_t k = 0; k < acquired_pattern.size(); k++ )
{
camsPixels[k].resize( params.height * params.width );
for( int i = 0; i < cam_width; i++ )
{
for( int j = 0; j < cam_height; j++ )
{
//if the pixel is not shadowed, reconstruct
if( shadowMasks[k].at<uchar>( j, i ) )
{
//for a (x,y) pixel of the camera returns the corresponding projector pixel by calculating the decimal number
bool error = getProjPixel( acquired_pattern[k], i, j, projPixel );
if( error )
{
continue;
}
camsPixels[k][projPixel.x * params.height + projPixel.y].push_back( Point( i, j ) );
}
}
}
}
std::vector<Point> cam1Pixs, cam2Pixs;
Mat& disparityMap_ = *( Mat* ) disparityMap.getObj();
disparityMap_ = Mat( cam_height, cam_width, CV_64F, double( 0 ) );
for( int i = 0; i < params.width; i++ )
{
for( int j = 0; j < params.height; j++ )
{
cam1Pixs = camsPixels[0][i * params.height + j];
cam2Pixs = camsPixels[1][i * params.height + j];
if( cam1Pixs.size() == 0 || cam2Pixs.size() == 0 )
continue;
Point p1;
Point p2;
double sump1x = 0;
double sump2x = 0;
for( int c1 = 0; c1 < (int) cam1Pixs.size(); c1++ )
{
p1 = cam1Pixs[c1];
sump1x += p1.x;
}
for( int c2 = 0; c2 < (int) cam2Pixs.size(); c2++ )
{
p2 = cam2Pixs[c2];
sump2x += p2.x;
}
sump2x /= cam2Pixs.size();
sump1x /= cam1Pixs.size();
for( int c1 = 0; c1 < (int) cam1Pixs.size(); c1++ )
{
p1 = cam1Pixs[c1];
disparityMap_.at<double>( p1.y, p1.x ) = ( double ) (sump2x - sump1x);
}
sump2x = 0;
sump1x = 0;
}
}
return true;
} // end if flags
return false;
}
// Computes the required number of pattern images
void GrayCodePattern_Impl::computeNumberOfPatternImages()
{
numOfColImgs = ( size_t ) ceil( log( double( params.width ) ) / log( 2.0 ) );
numOfRowImgs = ( size_t ) ceil( log( double( params.height ) ) / log( 2.0 ) );
numOfPatternImages = 2 * numOfColImgs + 2 * numOfRowImgs;
}
// Returns the number of pattern images to project / decode
size_t GrayCodePattern_Impl::getNumberOfPatternImages() const
{
return numOfPatternImages;
}
// Computes the shadows occlusion where we cannot reconstruct the model
void GrayCodePattern_Impl::computeShadowMasks( InputArrayOfArrays blackImages, InputArrayOfArrays whiteImages,
OutputArrayOfArrays shadowMasks ) const
{
std::vector<Mat>& whiteImages_ = *( std::vector<Mat>* ) whiteImages.getObj();
std::vector<Mat>& blackImages_ = *( std::vector<Mat>* ) blackImages.getObj();
std::vector<Mat>& shadowMasks_ = *( std::vector<Mat>* ) shadowMasks.getObj();
shadowMasks_.resize( whiteImages_.size() );
int cam_width = whiteImages_[0].cols;
int cam_height = whiteImages_[0].rows;
// TODO: parallelize for
for( int k = 0; k < (int) shadowMasks_.size(); k++ )
{
shadowMasks_[k] = Mat( cam_height, cam_width, CV_8U );
for( int i = 0; i < cam_width; i++ )
{
for( int j = 0; j < cam_height; j++ )
{
double white = whiteImages_[k].at<uchar>( Point( i, j ) );
double black = blackImages_[k].at<uchar>( Point( i, j ) );
if( abs(white - black) > blackThreshold )
{
shadowMasks_[k].at<uchar>( Point( i, j ) ) = ( uchar ) 1;
}
else
{
shadowMasks_[k].at<uchar>( Point( i, j ) ) = ( uchar ) 0;
}
}
}
}
}
// Generates the images needed for shadowMasks computation
void GrayCodePattern_Impl::getImagesForShadowMasks( InputOutputArray blackImage, InputOutputArray whiteImage ) const
{
Mat& blackImage_ = *( Mat* ) blackImage.getObj();
Mat& whiteImage_ = *( Mat* ) whiteImage.getObj();
blackImage_ = Mat( params.height, params.width, CV_8U, Scalar( 0 ) );
whiteImage_ = Mat( params.height, params.width, CV_8U, Scalar( 255 ) );
}
// For a (x,y) pixel of the camera returns the corresponding projector's pixel
bool GrayCodePattern_Impl::getProjPixel( InputArrayOfArrays patternImages, int x, int y, Point &projPix ) const
{
std::vector<Mat>& _patternImages = *( std::vector<Mat>* ) patternImages.getObj();
std::vector<uchar> grayCol;
std::vector<uchar> grayRow;
bool error = false;
int xDec, yDec;
// process column images
for( size_t count = 0; count < numOfColImgs; count++ )
{
// get pixel intensity for regular pattern projection and its inverse
double val1 = _patternImages[count * 2].at<uchar>( Point( x, y ) );
double val2 = _patternImages[count * 2 + 1].at<uchar>( Point( x, y ) );
// check if the intensity difference between the values of the normal and its inverse projection image is in a valid range
if( abs(val1 - val2) < whiteThreshold )
error = true;
// determine if projection pixel is on or off
if( val1 > val2 )
grayCol.push_back( 1 );
else
grayCol.push_back( 0 );
}
xDec = grayToDec( grayCol );
// process row images
for( size_t count = 0; count < numOfRowImgs; count++ )
{
// get pixel intensity for regular pattern projection and its inverse
double val1 = _patternImages[count * 2 + numOfColImgs * 2].at<uchar>( Point( x, y ) );
double val2 = _patternImages[count * 2 + numOfColImgs * 2 + 1].at<uchar>( Point( x, y ) );
// check if the intensity difference between the values of the normal and its inverse projection image is in a valid range
if( abs(val1 - val2) < whiteThreshold )
error = true;
// determine if projection pixel is on or off
if( val1 > val2 )
grayRow.push_back( 1 );
else
grayRow.push_back( 0 );
}
yDec = grayToDec( grayRow );
if( (yDec >= params.height || xDec >= params.width) )
{
error = true;
}
projPix.x = xDec;
projPix.y = yDec;
return error;
}
// Converts a gray code sequence (~ binary number) to a decimal number
int GrayCodePattern_Impl::grayToDec( const std::vector<uchar>& gray ) const
{
int dec = 0;
uchar tmp = gray[0];
if( tmp )
dec += ( int ) pow( ( float ) 2, int( gray.size() - 1 ) );
for( int i = 1; i < (int) gray.size(); i++ )
{
// XOR operation
tmp = tmp ^ gray[i];
if( tmp )
dec += (int) pow( ( float ) 2, int( gray.size() - i - 1 ) );
}
return dec;
}
// Sets the value for black threshold
void GrayCodePattern_Impl::setBlackThreshold( size_t val )
{
blackThreshold = val;
}
// Sets the value for white threshold
void GrayCodePattern_Impl::setWhiteThreshold( size_t val )
{
whiteThreshold = val;
}
// Creates the GrayCodePattern instance
Ptr<GrayCodePattern> GrayCodePattern::create( const GrayCodePattern::Params& params )
{
return makePtr<GrayCodePattern_Impl>( params );
}
// Creates the GrayCodePattern instance
// alias for scripting
Ptr<GrayCodePattern> GrayCodePattern::create( int width, int height )
{
Params params;
params.width = width;
params.height = height;
return makePtr<GrayCodePattern_Impl>( params );
}
}
}
+49
View File
@@ -0,0 +1,49 @@
/*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/structured_light.hpp"
#include "opencv2/core/utility.hpp"
#include "opencv2/core/private.hpp"
#endif
@@ -0,0 +1,919 @@
/*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 CV_FINAL : public SinusoidalPattern
{
public:
// Constructor
explicit SinusoidalPatternProfilometry_Impl( const SinusoidalPattern::Params &parameters =
SinusoidalPattern::Params() );
// Destructor
virtual ~SinusoidalPatternProfilometry_Impl() CV_OVERRIDE {};
// Generate sinusoidal patterns
bool generate( OutputArrayOfArrays patternImages ) CV_OVERRIDE;
bool decode( const std::vector< std::vector<Mat> >& patternImages, OutputArray disparityMap,
InputArrayOfArrays blackImages = noArray(), InputArrayOfArrays whiteImages =
noArray(), int flags = 0 ) const CV_OVERRIDE;
// Compute a wrapped phase map from the sinusoidal patterns
void computePhaseMap( InputArrayOfArrays patternImages, OutputArray wrappedPhaseMap,
OutputArray shadowMask = noArray(), InputArray fundamental = noArray()) CV_OVERRIDE;
// Unwrap the wrapped phase map to retrieve correspondences
void unwrapPhaseMap( InputArray wrappedPhaseMap,
OutputArray unwrappedPhaseMap,
cv::Size camSize,
InputArray shadowMask = noArray() ) CV_OVERRIDE;
// Find correspondences between the devices
void findProCamMatches( InputArray projUnwrappedPhaseMap, InputArray camUnwrappedPhaseMap,
OutputArrayOfArrays matches ) CV_OVERRIDE;
void computeDataModulationTerm( InputArrayOfArrays patternImages,
OutputArray dataModulationTerm,
InputArray shadowMask ) CV_OVERRIDE;
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(static_cast<float>((params.width - firstMarkerOffset) / m));
}
else
{
period = params.width / params.nbrOfPeriods;
nbrOfMarkersOnOneRow = (int)floor(static_cast<float>((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 * std::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(const std::vector< std::vector<Mat> >& patternImages,
OutputArray disparityMap,
InputArrayOfArrays blackImages,
InputArrayOfArrays whiteImages, int flags ) const
{
CV_UNUSED(patternImages);
CV_UNUSED(disparityMap);
CV_UNUSED(blackImages);
CV_UNUSED(whiteImages);
CV_UNUSED(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();
CV_UNUSED(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 )
{
CV_UNUSED(projUnwrappedPhaseMap);
CV_UNUSED(camUnwrappedPhaseMap);
CV_UNUSED(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- std::cos(params.shiftValue)) * (i3 - i2);
float den = std::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 - std::cos(theta2_.at<float>(i, j))) * a_.at<float>(i, j) +
(1 - std::cos(theta1_.at<float>(i, j))) * b_.at<float>(i, j);
float den = std::sin(theta1_.at<float>(i, j)) * b_.at<float>(i, j) -
std::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();
CV_UNUSED(unwrappedCamPhaseMap);
CV_UNUSED(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( Ptr<SinusoidalPattern::Params> params )
{
return makePtr<SinusoidalPatternProfilometry_Impl>(*params);
}
}
}
+149
View File
@@ -0,0 +1,149 @@
/*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"
#include <opencv2/structured_light/graycodepattern.hpp>
#include <opencv2/structured_light/sinusoidalpattern.hpp>
namespace opencv_test { namespace {
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 + "/";
Ptr<structured_light::SinusoidalPattern::Params> paramsPsp, paramsFtp, paramsFaps;
paramsPsp = makePtr<structured_light::SinusoidalPattern::Params>();
paramsFtp = makePtr<structured_light::SinusoidalPattern::Params>();
paramsFaps = makePtr<structured_light::SinusoidalPattern::Params>();
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 );
}
}} // namespace
@@ -0,0 +1,101 @@
/*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"
namespace opencv_test { namespace {
/****************************************************************************************\
* GetProjPixel test *
\****************************************************************************************/
class CV_GetProjPixelTest : public cvtest::BaseTest
{
public:
CV_GetProjPixelTest();
~CV_GetProjPixelTest();
protected:
void run(int);
};
CV_GetProjPixelTest::CV_GetProjPixelTest(){}
CV_GetProjPixelTest::~CV_GetProjPixelTest(){}
void CV_GetProjPixelTest::run( int )
{
// Using default projector resolution (1024 x 768)
Ptr<structured_light::GrayCodePattern> graycode = structured_light::GrayCodePattern::create();
// Storage for pattern
vector<Mat> pattern;
// Generate the pattern
graycode->generate( pattern );
Point projPixel;
int image_width = pattern[0].cols;
int image_height = pattern[0].rows;
for( int i = 0; i < image_width; i++ )
{
for( int j = 0; j < image_height; j++ )
{
//for a (x,y) pixel of the camera returns the corresponding projector pixel
bool error = graycode->getProjPixel( pattern, i, j, projPixel );
EXPECT_FALSE( error );
EXPECT_EQ( projPixel.y, j );
EXPECT_EQ( projPixel.x, i );
}
}
}
/****************************************************************************************\
* Test registration *
\****************************************************************************************/
TEST( GrayCodePattern, getProjPixel )
{
CV_GetProjPixelTest test;
test.safe_run();
}
}} // namespace
@@ -0,0 +1,6 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
#include "test_precomp.hpp"
CV_TEST_MAIN("cv")
@@ -0,0 +1,363 @@
/*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"
#include "opencv2/stereo.hpp"
namespace opencv_test { namespace {
const string STRUCTURED_LIGHT_DIR = "structured_light";
const string FOLDER_DATA = "data";
/****************************************************************************************\
* Plane test *
\****************************************************************************************/
class CV_PlaneTest : public cvtest::BaseTest
{
public:
CV_PlaneTest();
~CV_PlaneTest();
//////////////////////////////////////////////////////////////////////////////////////////////////
// From rgbd module: since I needed the distance method of plane class, I copied the class from rgb module
// it will be made a pull request to make Plane class public
/** Structure defining a plane. The notations are from the second paper */
class PlaneBase
{
public:
PlaneBase(const Vec3f & m, const Vec3f &n_in, int index) :
index_(index),
n_(n_in),
m_sum_(Vec3f(0, 0, 0)),
m_(m),
Q_(Matx33f::zeros()),
mse_(0),
K_(0)
{
UpdateD();
}
virtual ~PlaneBase()
{
}
/** Compute the distance to the plane. This will be implemented by the children to take into account different
* sensor models
* @param p_j
* @return
*/
virtual
float
distance(const Vec3f& p_j) const = 0;
/** The d coefficient in the plane equation ax+by+cz+d = 0
* @return
*/
inline float d() const
{
return d_;
}
/** The normal to the plane
* @return the normal to the plane
*/
const Vec3f &
n() const
{
return n_;
}
/** Update the different coefficients of the plane, based on the new statistics
*/
void UpdateParameters()
{
if( empty() )
return;
m_ = m_sum_ / K_;
// Compute C
Matx33f C = Q_ - m_sum_ * m_.t();
// Compute n
SVD svd(C);
n_ = Vec3f(svd.vt.at<float>(2, 0), svd.vt.at<float>(2, 1), svd.vt.at<float>(2, 2));
mse_ = svd.w.at<float>(2) / K_;
UpdateD();
}
/** Update the different sum of point and sum of point*point.t()
*/
void UpdateStatistics(const Vec3f & point, const Matx33f & Q_local)
{
m_sum_ += point;
Q_ += Q_local;
++K_;
}
inline size_t empty() const
{
return K_ == 0;
}
inline int K() const
{
return K_;
}
/** The index of the plane */
int index_;
protected:
/** The 4th coefficient in the plane equation ax+by+cz+d = 0 */
float d_;
/** Normal of the plane */
Vec3f n_;
private:
inline void UpdateD()
{
// Hessian form (d = nc . p_plane (centroid here) + p)
//d = -1 * n.dot (xyz_centroid);//d =-axP+byP+czP
d_ = -m_.dot(n_);
}
/** The sum of the points */
Vec3f m_sum_;
/** The mean of the points */
Vec3f m_;
/** The sum of pi * pi^\top */
Matx33f Q_;
/** The different matrices we need to update */
Matx33f C_;
float mse_;
/** the number of points that form the plane */
int K_;
};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/** Basic planar child, with no sensor error model
*/
class Plane : public PlaneBase
{
public:
Plane(const Vec3f & m, const Vec3f &n_in, int index) :
PlaneBase(m, n_in, index)
{
}
/** The computed distance is perfect in that case
* @param p_j the point to compute its distance to
* @return
*/
float distance(const Vec3f& p_j) const
{
return std::abs(float(p_j.dot(n_) + d_));
}
float distance(const Vec4f& p_j) const
{
return std::abs(float(Vec3f(p_j[0],p_j[1],p_j[2]).dot(n_) + d_));
}
};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
protected:
void run( int );
};
CV_PlaneTest::CV_PlaneTest(){}
CV_PlaneTest::~CV_PlaneTest(){}
void CV_PlaneTest::run( int )
{
string folder = cvtest::TS::ptr()->get_data_path() + "/" + STRUCTURED_LIGHT_DIR + "/" + FOLDER_DATA + "/";
structured_light::GrayCodePattern::Params params;
params.width = 1280;
params.height = 800;
// Set up GraycodePattern with params
Ptr<structured_light::GrayCodePattern> graycode = structured_light::GrayCodePattern::create( params );
size_t numberOfPatternImages = graycode->getNumberOfPatternImages();
FileStorage fs( folder + "calibrationParameters.yml", FileStorage::READ );
if( !fs.isOpened() )
{
ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_TEST_DATA );
}
FileStorage fs2( folder + "gt_plane.yml", FileStorage::READ );
if( !fs.isOpened() )
{
ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_TEST_DATA );
}
// Loading ground truth plane parameters
Vec4f plane_coefficients;
Vec3f m;
fs2["plane_coefficients"] >> plane_coefficients;
fs2["m"] >> m;
// Loading calibration parameters
Mat cam1intrinsics, cam1distCoeffs, cam2intrinsics, cam2distCoeffs, R, T;
fs["cam1_intrinsics"] >> cam1intrinsics;
fs["cam2_intrinsics"] >> cam2intrinsics;
fs["cam1_distorsion"] >> cam1distCoeffs;
fs["cam2_distorsion"] >> cam2distCoeffs;
fs["R"] >> R;
fs["T"] >> T;
// Loading white and black images
vector<Mat> blackImages;
vector<Mat> whiteImages;
blackImages.resize( 2 );
whiteImages.resize( 2 );
whiteImages[0] = imread( folder + "pattern_cam1_im43.jpg", 0 );
whiteImages[1] = imread( folder + "pattern_cam2_im43.jpg", 0 );
blackImages[0] = imread( folder + "pattern_cam1_im44.jpg", 0 );
blackImages[1] = imread( folder + "pattern_cam2_im44.jpg", 0 );
Size imagesSize = whiteImages[0].size();
if( ( !cam1intrinsics.data ) || ( !cam2intrinsics.data ) || ( !cam1distCoeffs.data ) || ( !cam2distCoeffs.data ) || ( !R.data )
|| ( !T.data ) || ( !whiteImages[0].data ) || ( !whiteImages[1].data ) || ( !blackImages[0].data )
|| ( !blackImages[1].data ) )
{
ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_TEST_DATA );
}
// Computing stereo rectify parameters
Mat R1, R2, P1, P2, Q;
Rect validRoi[2];
stereoRectify( cam1intrinsics, cam1distCoeffs, cam2intrinsics, cam2distCoeffs, imagesSize, R, T, R1, R2, P1, P2, Q, 0,
-1, imagesSize, &validRoi[0], &validRoi[1] );
Mat map1x, map1y, map2x, map2y;
initUndistortRectifyMap( cam1intrinsics, cam1distCoeffs, R1, P1, imagesSize, CV_32FC1, map1x, map1y );
initUndistortRectifyMap( cam2intrinsics, cam2distCoeffs, R2, P2, imagesSize, CV_32FC1, map2x, map2y );
vector<vector<Mat> > captured_pattern;
captured_pattern.resize( 2 );
captured_pattern[0].resize( numberOfPatternImages );
captured_pattern[1].resize( numberOfPatternImages );
// Loading and rectifying pattern images
for( size_t i = 0; i < numberOfPatternImages; i++ )
{
std::ostringstream name1;
name1 << "pattern_cam1_im" << i + 1 << ".jpg";
captured_pattern[0][i] = imread( folder + name1.str(), 0 );
std::ostringstream name2;
name2 << "pattern_cam2_im" << i + 1 << ".jpg";
captured_pattern[1][i] = imread( folder + name2.str(), 0 );
if( (!captured_pattern[0][i].data) || (!captured_pattern[1][i].data) )
{
ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_TEST_DATA );
}
remap( captured_pattern[0][i], captured_pattern[0][i], map2x, map2y, INTER_NEAREST, BORDER_CONSTANT, Scalar() );
remap( captured_pattern[1][i], captured_pattern[1][i], map1x, map1y, INTER_NEAREST, BORDER_CONSTANT, Scalar() );
}
// Rectifying white and black images
remap( whiteImages[0], whiteImages[0], map2x, map2y, INTER_NEAREST, BORDER_CONSTANT, Scalar() );
remap( whiteImages[1], whiteImages[1], map1x, map1y, INTER_NEAREST, BORDER_CONSTANT, Scalar() );
remap( blackImages[0], blackImages[0], map2x, map2y, INTER_NEAREST, BORDER_CONSTANT, Scalar() );
remap( blackImages[1], blackImages[1], map1x, map1y, INTER_NEAREST, BORDER_CONSTANT, Scalar() );
// Setting up threshold parameters to reconstruct only the plane in foreground
graycode->setBlackThreshold( 55 );
graycode->setWhiteThreshold( 10 );
// Computing the disparity map
Mat disparityMap;
bool decoded = graycode->decode( captured_pattern, disparityMap, blackImages, whiteImages,
structured_light::DECODE_3D_UNDERWORLD );
EXPECT_TRUE( decoded );
// Computing the point cloud
Mat pointcloud;
disparityMap.convertTo( disparityMap, CV_32FC1 );
reprojectImageTo3D( disparityMap, pointcloud, Q, true, -1 );
// from mm (unit of calibration) to m
pointcloud = pointcloud / 1000;
// Setting up plane with ground truth plane values
Vec3f normal( plane_coefficients.val[0], plane_coefficients.val[1], plane_coefficients.val[2] );
Ptr<PlaneBase> plane = Ptr<PlaneBase>( new Plane( m, normal, 0 ) );
// Computing the distance of every point of the pointcloud from ground truth plane
float sum_d = 0;
int cont = 0;
for( int i = 0; i < disparityMap.rows; i++ )
{
for( int j = 0; j < disparityMap.cols; j++ )
{
float value = disparityMap.at<float>( i, j );
if( value != 0 )
{
Vec3f point = pointcloud.at<Vec3f>( i, j );
sum_d += plane->distance( point );
cont++;
}
}
}
sum_d /= cont;
// test pass if the mean of points distance from ground truth plane is lower than 3 mm
EXPECT_LE( sum_d, 0.003 );
}
/****************************************************************************************\
* Test registration *
\****************************************************************************************/
TEST( GrayCodePattern, plane_reconstruction )
{
CV_PlaneTest test;
test.safe_run();
}
}} // namespace
@@ -0,0 +1,10 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
#ifndef __OPENCV_TEST_PRECOMP_HPP__
#define __OPENCV_TEST_PRECOMP_HPP__
#include "opencv2/ts.hpp"
#include "opencv2/structured_light.hpp"
#endif
@@ -0,0 +1,130 @@
Capture Gray code pattern tutorial {#tutorial_capture_graycode_pattern}
=============
Goal
----
In this tutorial you will learn how to use the *GrayCodePattern* class to:
- Generate a Gray code pattern.
- Project the Gray code pattern.
- Capture the projected Gray code pattern.
It is important to underline that *GrayCodePattern* class actually implements the 3DUNDERWORLD algorithm described in @cite UNDERWORLD , which is based on a stereo approach: we need to capture the projected pattern at the same time from two different views if we want to reconstruct the 3D model of the scanned object. Thus, an acquisition set consists of the images captured by each camera for each image in the pattern sequence.
Code
----
@include structured_light/samples/cap_pattern.cpp
Explanation
-----------
First of all the pattern images to project must be generated. Since the number of images is a function of the projector's resolution, *GrayCodePattern* class parameters must be set with our projector's width and height. In this way the *generate* method can be called: it fills a vector of Mat with the computed pattern images:
@code{.cpp}
structured_light::GrayCodePattern::Params params;
....
params.width = parser.get<int>( 1 );
params.height = parser.get<int>( 2 );
....
// Set up GraycodePattern with params
Ptr<structured_light::GrayCodePattern> graycode = structured_light::GrayCodePattern::create( params );
// Storage for pattern
vector<Mat> pattern;
graycode->generate( pattern );
@endcode
For example, using the default projector resolution (1024 x 768), 40 images have to be projected: 20 for regular color pattern (10 images for the columns sequence and 10 for the rows one) and 20 for the color-inverted pattern, where the inverted pattern images are images with the same structure as the original but with inverted colors. This provides an effective method for easily determining the intensity value of each pixel when it is lit (highest value) and when it is not lit (lowest value) during the decoding step.
Subsequently, to identify shadow regions, the regions of two images where the pixels are not lit by projector's light and thus where there is not code information, the 3DUNDERWORLD algorithm computes a shadow mask for the two cameras views, starting from a white and a black images captured by each camera. So two additional images need to be projected and captured with both cameras:
@code{.cpp}
// Generate the all-white and all-black images needed for shadows mask computation
Mat white;
Mat black;
graycode->getImagesForShadowMasks( black, white );
pattern.push_back( white );
pattern.push_back( black );
@endcode
Thus, the final projection sequence is projected as follows: first the column and its inverted sequence, then the row and its inverted sequence and finally the white and black images.
Once the pattern images have been generated, they must be projected using the full screen option: the images must fill all the projection area, otherwise the projector full resolution is not exploited, a condition on which is based 3DUNDERWORLD implementation.
@code{.cpp}
// Setting pattern window on second monitor (the projector's one)
namedWindow( "Pattern Window", WINDOW_NORMAL );
resizeWindow( "Pattern Window", params.width, params.height );
moveWindow( "Pattern Window", params.width + 316, -20 );
setWindowProperty( "Pattern Window", WND_PROP_FULLSCREEN, WINDOW_FULLSCREEN );
@endcode
At this point the images can be captured with our digital cameras, using libgphoto2 library, recently included in OpenCV: remember to turn on gPhoto2 option in Cmake.list when building OpenCV.
@code{.cpp}
// Open camera number 1, using libgphoto2
VideoCapture cap1( CAP_GPHOTO2 );
if( !cap1.isOpened() )
{
// check if cam1 opened
cout << "cam1 not opened!" << endl;
help();
return -1;
}
// Open camera number 2
VideoCapture cap2( 1 );
if( !cap2.isOpened() )
{
// check if cam2 opened
cout << "cam2 not opened!" << endl;
help();
return -1;
}
@endcode
The two cameras must work at the same resolution and must have autofocus option disabled, maintaining the same focus during all acquisition. The projector can be positioned in the middle of the cameras.
However, before to proceed with pattern acquisition, the cameras must be calibrated. Once the calibration is performed, there should be no movement of the cameras, otherwise a new calibration will be needed.
After having connected the cameras and the projector to the computer, cap_pattern demo can be launched giving as parameters the path where to save the images, and the projector's width and height, taking care to use the same focus and cameras settings of calibration.
At this point, to acquire the images with both cameras, the user can press any key.
@code{.cpp}
// Turning off autofocus
cap1.set( CAP_PROP_SETTINGS, 1 );
cap2.set( CAP_PROP_SETTINGS, 1 );
int i = 0;
while( i < (int) pattern.size() )
{
cout << "Waiting to save image number " << i + 1 << endl << "Press any key to acquire the photo" << endl;
imshow( "Pattern Window", pattern[i] );
Mat frame1;
Mat frame2;
cap1 >> frame1; // get a new frame from camera 1
cap2 >> frame2; // get a new frame from camera 2
...
}
@endcode
If the captured images are good (the user must take care that the projected pattern is viewed from the two cameras), the user can save them pressing the enter key, otherwise pressing any other key he can take another shot.
@code{.cpp}
// Pressing enter, it saves the output
if( key == 13 )
{
ostringstream name;
name << i + 1;
save1 = imwrite( path + "pattern_cam1_im" + name.str() + ".png", frame1 );
save2 = imwrite( path + "pattern_cam2_im" + name.str() + ".png", frame2 );
if( ( save1 ) && ( save2 ) )
{
cout << "pattern cam1 and cam2 images number " << i + 1 << " saved" << endl << endl;
i++;
}
else
{
cout << "pattern cam1 and cam2 images number " << i + 1 << " NOT saved" << endl << endl << "Retry, check the path"<< endl << endl;
}
}
@endcode
The acquistion ends when all the pattern images have saved for both cameras. Then the user can reconstruct the 3D model of the captured scene using the *decode* method of *GrayCodePattern* class (see next tutorial).
@@ -0,0 +1,207 @@
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
@@ -0,0 +1,196 @@
Decode Gray code pattern tutorial {#tutorial_decode_graycode_pattern}
=============
Goal
----
In this tutorial you will learn how to use the *GrayCodePattern* class to:
- Decode a previously acquired Gray code pattern.
- Generate a disparity map.
- Generate a pointcloud.
Code
----
@include structured_light/samples/pointcloud.cpp
Explanation
-----------
First of all the needed parameters must be passed to the program.
The first is the name list of previously acquired pattern images, stored in a .yaml file organized as below:
@code{.cpp}
%YAML:1.0
cam1:
- "/data/pattern_cam1_im1.png"
- "/data/pattern_cam1_im2.png"
..............
- "/data/pattern_cam1_im42.png"
- "/data/pattern_cam1_im43.png"
- "/data/pattern_cam1_im44.png"
cam2:
- "/data/pattern_cam2_im1.png"
- "/data/pattern_cam2_im2.png"
..............
- "/data/pattern_cam2_im42.png"
- "/data/pattern_cam2_im43.png"
- "/data/pattern_cam2_im44.png"
@endcode
For example, the dataset used for this tutorial has been acquired using a projector with a resolution of 1280x800, so 42 pattern images (from number 1 to 42) + 1 white (number 43) and 1 black (number 44) were captured with both the two cameras.
Then the cameras calibration parameters, stored in another .yml file, together with the width and the height of the projector used to project the pattern, and, optionally, the values of white and black tresholds, must be passed to the tutorial program.
In this way, *GrayCodePattern* class parameters can be set up with the width and the height of the projector used during the pattern acquisition and a pointer to a GrayCodePattern object can be created:
@code{.cpp}
structured_light::GrayCodePattern::Params params;
....
params.width = parser.get<int>( 2 );
params.height = parser.get<int>( 3 );
....
// Set up GraycodePattern with params
Ptr<structured_light::GrayCodePattern> graycode = structured_light::GrayCodePattern::create( params );
@endcode
If the white and black thresholds are passed as parameters (these thresholds influence the number of decoded pixels), their values can be set, otherwise the algorithm will use the default values.
@code{.cpp}
size_t white_thresh = 0;
size_t black_thresh = 0;
if( argc == 7 )
{
// If passed, setting the white and black threshold, otherwise using default values
white_thresh = parser.get<size_t>( 4 );
black_thresh = parser.get<size_t>( 5 );
graycode->setWhiteThreshold( white_thresh );
graycode->setBlackThreshold( black_thresh );
}
@endcode
At this point, to use the *decode* method of *GrayCodePattern* class, the acquired pattern images must be stored in a vector of vector of Mat.
The external vector has a size of two because two are the cameras: the first vector stores the pattern images captured from the left camera, the second those acquired from the right one. The number of pattern images is obviously the same for both cameras and can be retrieved using the getNumberOfPatternImages() method:
@code{.cpp}
size_t numberOfPatternImages = graycode->getNumberOfPatternImages();
vector<vector<Mat> > captured_pattern;
captured_pattern.resize( 2 );
captured_pattern[0].resize( numberOfPatternImages );
captured_pattern[1].resize( numberOfPatternImages );
.....
for( size_t i = 0; i < numberOfPatternImages; i++ )
{
captured_pattern[0][i] = imread( imagelist[i], IMREAD_GRAYSCALE );
captured_pattern[1][i] = imread( imagelist[i + numberOfPatternImages + 2], IMREAD_GRAYSCALE );
......
}
@endcode
As regards the black and white images, they must be stored in two different vectors of Mat:
@code{.cpp}
vector<Mat> blackImages;
vector<Mat> whiteImages;
blackImages.resize( 2 );
whiteImages.resize( 2 );
// Loading images (all white + all black) needed for shadows computation
cvtColor( color, whiteImages[0], COLOR_RGB2GRAY );
whiteImages[1] = imread( imagelist[2 * numberOfPatternImages + 2], IMREAD_GRAYSCALE );
blackImages[0] = imread( imagelist[numberOfPatternImages + 1], IMREAD_GRAYSCALE );
blackImages[1] = imread( imagelist[2 * numberOfPatternImages + 2 + 1], IMREAD_GRAYSCALE );
@endcode
It is important to underline that all the images, the pattern ones, black and white, must be loaded as grayscale images and rectified before being passed to decode method:
@code{.cpp}
// Stereo rectify
cout << "Rectifying images..." << endl;
Mat R1, R2, P1, P2, Q;
Rect validRoi[2];
stereoRectify( cam1intrinsics, cam1distCoeffs, cam2intrinsics, cam2distCoeffs, imagesSize, R, T, R1, R2, P1, P2, Q, 0,
-1, imagesSize, &validRoi[0], &validRoi[1] );
Mat map1x, map1y, map2x, map2y;
initUndistortRectifyMap( cam1intrinsics, cam1distCoeffs, R1, P1, imagesSize, CV_32FC1, map1x, map1y );
initUndistortRectifyMap( cam2intrinsics, cam2distCoeffs, R2, P2, imagesSize, CV_32FC1, map2x, map2y );
........
for( size_t i = 0; i < numberOfPatternImages; i++ )
{
........
remap( captured_pattern[1][i], captured_pattern[1][i], map1x, map1y, INTER_NEAREST, BORDER_CONSTANT, Scalar() );
remap( captured_pattern[0][i], captured_pattern[0][i], map2x, map2y, INTER_NEAREST, BORDER_CONSTANT, Scalar() );
}
........
remap( color, color, map2x, map2y, INTER_NEAREST, BORDER_CONSTANT, Scalar() );
remap( whiteImages[0], whiteImages[0], map2x, map2y, INTER_NEAREST, BORDER_CONSTANT, Scalar() );
remap( whiteImages[1], whiteImages[1], map1x, map1y, INTER_NEAREST, BORDER_CONSTANT, Scalar() );
remap( blackImages[0], blackImages[0], map2x, map2y, INTER_NEAREST, BORDER_CONSTANT, Scalar() );
remap( blackImages[1], blackImages[1], map1x, map1y, INTER_NEAREST, BORDER_CONSTANT, Scalar() );
@endcode
In this way the *decode* method can be called to decode the pattern and to generate the corresponding disparity map, computed on the first camera (left):
@code{.cpp}
Mat disparityMap;
bool decoded = graycode->decode(captured_pattern, disparityMap, blackImages, whiteImages,
structured_light::DECODE_3D_UNDERWORLD);
@endcode
To better visualize the result, a colormap is applied to the computed disparity:
@code{.cpp}
double min;
double max;
minMaxIdx(disparityMap, &min, &max);
Mat cm_disp, scaledDisparityMap;
cout << "disp min " << min << endl << "disp max " << max << endl;
convertScaleAbs( disparityMap, scaledDisparityMap, 255 / ( max - min ) );
applyColorMap( scaledDisparityMap, cm_disp, COLORMAP_JET );
// Show the result
resize( cm_disp, cm_disp, Size( 640, 480 ) );
imshow( "cm disparity m", cm_disp )
@endcode
![](pics/cm_disparity.png)
At this point the point cloud can be generated using the reprojectImageTo3D method, taking care to convert the computed disparity in a CV_32FC1 Mat (decode method computes a CV_64FC1 disparity map):
@code{.cpp}
Mat pointcloud;
disparityMap.convertTo( disparityMap, CV_32FC1 );
reprojectImageTo3D( disparityMap, pointcloud, Q, true, -1 );
@endcode
Then a mask to remove the unwanted background is computed:
@code{.cpp}
Mat dst, thresholded_disp;
threshold( scaledDisparityMap, thresholded_disp, 0, 255, THRESH_OTSU + THRESH_BINARY );
resize( thresholded_disp, dst, Size( 640, 480 ) );
imshow( "threshold disp otsu", dst );
@endcode
![](pics/threshold_disp.png)
The white image of cam1 was previously loaded also as a color image, in order to map the color of the object on its reconstructed pointcloud:
@code{.cpp}
Mat color = imread( imagelist[numberOfPatternImages], IMREAD_COLOR );
@endcode
The background renoval mask is thus applied to the point cloud and to the color image:
@code{.cpp}
Mat pointcloud_tresh, color_tresh;
pointcloud.copyTo(pointcloud_tresh, thresholded_disp);
color.copyTo(color_tresh, thresholded_disp);
@endcode
Finally the computed point cloud of the scanned object can be visualized on viz:
@code{.cpp}
viz::Viz3d myWindow( "Point cloud with color");
myWindow.setBackgroundMeshLab();
myWindow.showWidget( "coosys", viz::WCoordinateSystem());
myWindow.showWidget( "pointcloud", viz::WCloud( pointcloud_tresh, color_tresh ) );
myWindow.showWidget( "text2d", viz::WText( "Point cloud", Point(20, 20), 20, viz::Color::green() ) );
myWindow.spin();
@endcode
![](pics/plane_viz.png)
@@ -0,0 +1,26 @@
Structured Light tutorials {#tutorial_structured_light}
=============================================================
- @subpage tutorial_capture_graycode_pattern
_Compatibility:_ \> OpenCV 3.0.0
_Author:_ Roberta Ravanelli
You will learn how to acquire a dataset using *GrayCodePattern* class.
- @subpage tutorial_decode_graycode_pattern
_Compatibility:_ \> OpenCV 3.0.0
_Author:_ Roberta Ravanelli
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.