需要强调的是,`GrayCodePattern` 类实际上实现了 [127] 中描述的 3DUNDERWORLD 算法,该算法基于立体视觉方法:如果我们想重建扫描对象的 3D 模型,我们需要同时从两个不同视图捕获投影的图案。因此,一个采集集由每个相机在图案序列的每个图像中捕获的图像组成。
#include <iostream>
#include <stdio.h>
static const char* keys =
{ "{@path | | 将要保存捕获图案图像的文件夹路径 }"
"{@proj_width | | 投影仪宽度 }"
"{@proj_height | | 投影仪高度 }" };
static void help()
{
cout << "\n此示例演示如何使用“结构光模块”来采集格雷码图案"
"\n调用(连接两个相机):\n"
"./example_structured_light_cap_pattern <path> <proj_width> <proj_height> \n"
<< endl;
}
int main(
int argc,
char** argv )
{
params.width = parser.get<int>( 1 );
params.height = parser.get<int>( 2 );
if( path.empty() || params.width < 1 || params.height < 1 )
{
help();
return -1;
}
vector<Mat> pattern;
graycode->generate( pattern );
cout << pattern.size() << " 个图案图像 + 2 个用于阴影蒙版计算的图像,需要用两个相机采集"
<< endl;
graycode->getImagesForShadowMasks( black, white );
pattern.push_back( white );
pattern.push_back( black );
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 );
if( !cap1.isOpened() )
{
cout << "cam1 not opened!" << endl;
help();
return -1;
}
if( !cap2.isOpened() )
{
cout << "cam2 not opened!" << endl;
help();
return -1;
}
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] );
cap1 >> frame1;
cap2 >> frame2;
if( ( frame1.
data ) && ( frame2.
data ) )
{
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;
resize( frame1, tmp,
Size( 640, 480 ), 0, 0, INTER_LINEAR_EXACT);
resize( frame2, tmp,
Size( 640, 480 ), 0, 0, INTER_LINEAR_EXACT);
bool save1 =
false;
bool save2 =
false;
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;
}
}
if( key == 27 )
{
cout << "Closing program" << endl;
}
}
else
{
cout << "No frame data, waiting for new frame" << endl;
}
}
return 0;
}
专为命令行解析设计。
定义 utility.hpp:890
uchar * data
指向数据的指针
定义 mat.hpp:2206
用于指定图像或矩形大小的模板类。
定义 types.hpp:335
从视频文件、图像序列或摄像头捕获视频的类。
定义 videoio.hpp:786
std::string String
定义 cvstd.hpp:151
std::shared_ptr< _Tp > Ptr
定义 cvstd_wrapper.hpp:23
void imshow(const String &winname, InputArray mat)
在指定窗口中显示图像。
int waitKey(int delay=0)
等待按键操作。
void namedWindow(const String &winname, int flags=WINDOW_AUTOSIZE)
创建窗口。
void moveWindow(const String &winname, int x, int y)
将窗口移动到指定位置。
void resizeWindow(const String &winname, int width, int height)
将窗口调整到指定大小。
bool imwrite(const String &filename, InputArray img, const std::vector< int > ¶ms=std::vector< int >())
将图像保存到指定文件。
void cvtColor(InputArray src, OutputArray dst, int code, int dstCn=0, AlgorithmHint hint=cv::ALGO_HINT_DEFAULT)
将图像从一种颜色空间转换为另一种。
int main(int argc, char *argv[])
定义 highgui_qt.cpp:3
StructuredLightPattern 构造函数的参数。
定义 graycodepattern.hpp:77
说明
首先必须生成要投影的图案图像。由于图像数量是投影仪分辨率的函数,因此必须使用投影仪的宽度和高度来设置 `GrayCodePattern` 类的参数。这样就可以调用 `generate` 方法:它会用计算出的图案图像填充 `Mat` 向量。
....
params.width = parser.get<int>( 1 );
params.height = parser.get<int>( 2 );
....
vector<Mat> pattern;
graycode->generate( pattern );
例如,使用默认投影仪分辨率(1024 x 768),需要投影 40 张图像:20 张用于常规颜色图案(10 张用于列序列,10 张用于行序列),以及 20 张用于颜色反转图案,其中反转图案的图像结构与原始图像相同,但颜色反转。这提供了一种有效的方法,可以在解码步骤中轻松确定每个像素在被照亮时(最高值)和未被照亮时(最低值)的强度值。
随后,为了识别阴影区域(投影仪光线未照亮像素且因此没有编码信息的区域),3DUNDERWORLD 算法从每个相机的白色和黑色图像开始,为两个相机视图计算阴影蒙版。因此,需要用两个相机投影和捕获另外两张图像。
graycode->getImagesForShadowMasks( black, white );
pattern.push_back( white );
pattern.push_back( black );
因此,最终的投影序列如下:首先是列序列及其反转序列,然后是行序列及其反转序列,最后是白色和黑色图像。
一旦生成了图案图像,就必须使用全屏选项进行投影:图像必须填充所有投影区域,否则将无法充分利用投影仪的全分辨率,而 3DUNDERWORLD 实现正是基于这一条件。
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 );
此时,可以使用我们的数码相机捕获图像,使用最近包含在 OpenCV 中的 libgphoto2 库:在编译 OpenCV 时,请记住在 CMake.list 中启用 gPhoto2 选项。
if( !cap1.isOpened() )
{
cout << "cam1 not opened!" << endl;
help();
return -1;
}
if( !cap2.isOpened() )
{
cout << "cam2 not opened!" << endl;
help();
return -1;
}
两个相机必须以相同的分辨率工作,并且必须禁用自动对焦选项,在所有采集过程中保持相同的对焦。投影仪可以放置在两个相机之间。
然而,在进行图案采集之前,必须对相机进行标定。一旦完成标定,相机就不应该移动,否则将需要重新标定。
在将相机和投影仪连接到计算机后,可以启动 cap_pattern 演示,并将保存图像的路径以及投影仪的宽度和高度作为参数提供,同时要注意使用与标定相同的对焦和相机设置。
此时,要用两个相机采集图像,用户可以按任意键。
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] );
cap1 >> frame1;
cap2 >> frame2;
...
}
如果捕获的图像质量良好(用户必须确保从两个相机都能看到投影的图案),用户可以通过按 Enter 键保存它们,否则通过按任意其他键可以重新拍摄。
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;
}
}
采集结束条件是,为两个相机保存了所有图案图像。然后,用户可以使用 `GrayCodePattern` 类的 `decode` 方法重建捕获场景的 3D 模型(参见下一个教程)。