OpenCV 4.11.0
开源计算机视觉库
加载中…
搜索中…
无匹配项
捕获正弦图案教程

目标

本教程将指导您如何使用正弦图案类:

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

代码

/*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 <宽度> <高度>"
" <周期数> <设置标记>(布尔值) <水平图案>(布尔值) <算法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 )
{
/*根据参考文献,我们需要三张图像来计算阴影掩码,*/
/*即使相位图仅从一种图案计算而来*/
*/
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 << "错误" << endl;
}
cout << "完成" << 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:829
void convertTo(OutputArray m, int rtype, double alpha=1, double beta=0) const
将数组转换为另一种数据类型,并可以选择缩放。
用于指定图像或矩形大小的模板类。
定义 types.hpp:335
用于从视频文件、图像序列或摄像头捕获视频的类。
定义 videoio.hpp:766
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:380
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)
动态更改窗口参数。
CV_EXPORTS_W 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,为每个投射的图案计算一个相位图,但我们需要根据[62] 中的说明,从三个连续的图案计算阴影掩码。因此,三个图案设置在一个名为 captures 的向量中。特别是在达到最后捕获时,需要小心地用三个图案填充此向量。解包裹算法需要知道捕获图像的大小,因此我们确保将其提供给“unwrapPhaseMap”方法。为了将相位图保存为 png,将其转换为 8 位图像。

switch(params.methodId)
{
case structured_light::FTP
for( int i = 0; i < nbrOfImages; ++i )
{
/*根据参考文献,我们需要三张图像来计算阴影掩码,*/
/*即使相位图仅从一种图案计算而来*/
*/
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,三个投影图像用于计算单个相位图。这三个图像设置在充当 FIFO 的向量“captures”中。在这里,为了将相位图保存为 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;