OpenCV 4.13.0
开源计算机视觉库 (Open Source Computer Vision)
正在加载...
正在搜索...
未找到匹配项
拍摄正弦图案教程

目标

在本教程中,您将学习如何使用正弦图案类来

  • 生成正弦图案。
  • 投影生成的图案。
  • 捕获投影的图案。
  • 使用三种不同的算法(傅里叶变换轮廓术、相移轮廓术、傅里叶辅助相移轮廓术)从这些图案计算出包裹相位图。
  • 解包裹之前的相位图。

代码

/*M///////////////////////////////////////////////////////////////////////////////////////
//
// 重要提示:下载、复制、安装或使用之前请务必阅读。
//
// 使用软件即表示您同意本许可证。
// 如果您不同意本许可证,请不要下载、安装、
// 复制或使用软件。
//
//
// 许可证协议
// 适用于开源计算机视觉库
//
// 版权所有 (C) 2015,OpenCV基金会,保留所有权利。
// 第三方版权属于其各自所有者。
//
// 允许以源代码和二进制形式重新分发和使用,无论是否修改,
// 均需满足以下条件:
//
// * 源代码的重新分发必须保留上述版权声明、
// 此条件列表和以下免责声明。
//
// * 二进制形式的重新分发必须在文档
// 和/或随分发版提供的其他材料中重现上述版权声明、
// 此条件列表和以下免责声明。
//
// * 未经版权持有者事先明确书面许可,不得使用其名称来认可或推广
// 源自本软件的产品。
//
// 本软件由版权持有者和贡献者“按原样”提供,
// 且不提供任何明示或暗示的保证,包括但不限于对适销性和
// 特定用途适用性的暗示保证。
// 在任何情况下,英特尔公司或贡献者均不对任何直接、
// 间接、附带、特殊、惩戒性或后果性损害
// (包括但不限于采购替代商品或服务;
// 使用损失、数据丢失或利润损失;或业务中断)负责,
// 无论其发生原因和基于何种责任理论,无论是在合同、严格责任
// 还是侵权(包括疏忽或其他)行为中,只要是由于使用本软件而引起的,
// 即使已被告知发生此类损害的可能性。
//
//M*/
#include <vector>
#include <iostream>
#include <fstream>
#include <opencv2/core.hpp>
using namespace cv;
using namespace std;
static const char* keys =
{
"{@width | | 投影仪宽度}"
"{@height | | 投影仪高度}"
"{@periods | | 周期数}"
"{@setMarkers | | 是否带标记的图案}"
"{@horizontal | | 图案是否为水平}"
"{@methodId | | 要使用的方法}"
"{@outputPatternPath | | 保存图案的路径}"
"{@outputWrappedPhasePath | | 保存包裹相位图的路径}"
"{@outputUnwrappedPhasePath | | 保存解包裹相位图的路径}"
"{@outputCapturePath | | 保存捕获图像的路径}"
"{@reliabilitiesPath | | 保存可靠性图的路径}"
};
static void help()
{
cout << "\n此示例生成正弦图案" << endl;
cout << "调用方式:./example_structured_light_createsinuspattern <宽度> <高度>"
" <周期数> <是否设置标记>(bool) <是否为水平图案>(bool) <方法ID>"
" <捕获图像输出路径> <图案输出路径>(可选) <包裹相位图输出路径>(可选)"
" <解包裹相位图输出路径>" << endl;
}
int main(int argc, char **argv)
{
if( argc < 2 )
{
help();
return -1;
}
// 从命令行检索参数
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);
structured_light::SinusoidalPattern::create(makePtr<structured_light::SinusoidalPattern::Params>(params));
vector<Mat> patterns;
Mat shadowMask;
Mat unwrappedPhaseMap, unwrappedPhaseMap8;
Mat wrappedPhaseMap, wrappedPhaseMap8;
//生成正弦图案
sinus->generate(patterns);
VideoCapture cap(CAP_PVAPI);
if( !cap.isOpened() )
{
cout << "无法打开摄像头" << 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 << "准备就绪后请按任意键" << 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 << "准备就绪后请按回车键" << 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 )
{
/*根据参考文献[65],我们需要三张图像来计算阴影掩码,
* 即使相位图仅从一个图案计算得出。
*/
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;
}
专为命令行解析设计。
定义 utility.hpp:890
n 维密集数组类
定义于 mat.hpp:840
void convertTo(OutputArray m, int rtype, double alpha=1, double beta=0) const
将数组转换为另一种数据类型,并可选择缩放。
用于指定图像或矩形大小的模板类。
定义 types.hpp:335
从视频文件、图像序列或摄像头捕获视频的类。
定义 videoio.hpp:786
std::string String
定义 cvstd.hpp:151
std::shared_ptr< _Tp > Ptr
定义 cvstd_wrapper.hpp:23
#define CV_8U
定义于 interface.h:73
#define CV_PI
定义见 cvdef.h:382
void imshow(const String &winname, InputArray mat)
在指定窗口中显示图像。
int waitKey(int delay=0)
等待按键操作。
void namedWindow(const String &winname, int flags=WINDOW_AUTOSIZE)
创建窗口。
void setWindowProperty(const String &winname, int prop_id, double prop_value)
动态更改窗口的参数。
bool imwrite(const String &filename, InputArray img, const std::vector< int > &params=std::vector< int >())
将图像保存到指定文件。
int main(int argc, char *argv[])
定义 highgui_qt.cpp:3
PyParams params(const std::string &tag, const std::string &model, const std::string &weights, const std::string &device)
定义 core.hpp:107
STL 命名空间。
phaseUnwrapping 构造函数的参数。
定义 histogramphaseunwrapping.hpp:79
int width
定义 histogramphaseunwrapping.hpp:81
int height
定义 histogramphaseunwrapping.hpp:82
SinusoidalPattern构造函数的参数。
定义 sinusoidalpattern.hpp:83

解释

首先,必须生成正弦图案。SinusoidalPattern 类的参数必须由用户设置

  • 投影仪宽度和高度
  • 图案中的周期数
  • 在图案中设置十字标记(用于将相对相位图转换为绝对相位图)
  • 图案方向(水平或垂直)
  • 相位移值(通常设置为 2pi/3 以启用循环系统)
  • 同一行/列上两个连续标记之间的像素数
  • 用于计算相位图的方法的 ID(FTP = 0,PSP = 1,FAPS = 2)

用户还可以选择保存图案和相位图。

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);
// 存储图案
vector<Mat> patterns;
//生成正弦图案
sinus->generate(patterns);

图案的数量始终为三个,无论用于计算相位图的方法如何。这三个图案被循环投影,因为系统是循环的,所以这是可以接受的。

一旦图案生成、摄像头打开并投影图案,将使用全屏分辨率。在本教程中,使用 Prosilica 摄像头捕获灰度图像。当投影仪显示第一个图案时,用户可以按任意键开始投影序列。

VideoCapture cap(CAP_PVAPI);
if( !cap.isOpened() )
{
cout << "无法打开摄像头" << 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 << "准备就绪后请按任意键" << endl;
waitKey(0);

在本教程中,投影 30 张图像,因此三个图案中的每一个都投影十次。“while”循环负责投影过程。捕获的图像存储在 Mat 向量中。两次连续捕获之间有 30 毫秒的延迟。投影完成后,用户必须按“Enter”键开始计算相位图。

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 << "准备就绪后请按回车键" << endl;
bool loop = true;
while ( loop )
{
char c = waitKey(0);
if( c == 10 )
{
loop = false;
}
}

相位图已准备好根据所选方法进行计算。对于 FTP,为每个投影的图案计算一个相位图,但我们需要根据参考文献[65]中的说明,从三个连续的图案计算阴影掩码。因此,三个图案被放入一个名为 captures 的向量中。特别是在我们到达最后一个捕获时,我们会注意填充这个向量。解包裹算法需要知道捕获图像的大小,所以我们确保在“unwrapPhaseMap”方法中提供它。相位图被转换为 8 位图像,以便将它们保存为 png。

switch(params.methodId)
{
case structured_light::FTP
for( int i = 0; i < nbrOfImages; ++i )
{
/*根据参考文献[65],我们需要三张图像来计算阴影掩码,
* 即使相位图仅从一个图案计算得出。
*/
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;

对于 PSP 和 FAPS,使用三个投影图像来计算单个相位图。这三个图像被放入“captures”中,这是一个用作 FIFO 的向量。同样,为了将相位图保存为 png,它们被转换为 8 位图像。

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;