OpenCV 4.12.0
开源计算机视觉
加载中...
搜索中...
无匹配项
采集格雷码模式教程

目标

在本教程中,您将学习如何使用 GrayCodePattern 类来

  • 生成格雷码图案。
  • 投射格雷码图案。
  • 捕捉投射的格雷码图案。

需要强调的是,GrayCodePattern 类实际上实现了 [126] 中描述的 3DUNDERWORLD 算法,该算法基于立体视觉方法:如果我们想要重建扫描对象的 3D 模型,我们需要同时从两个不同的视角捕捉投射的图案。因此,一个采集集包括每个相机为图案序列中的每张图像捕捉到的图像。

代码

/*M///////////////////////////////////////////////////////////////////////////////////////
//
// 重要提示:在下载、复制、安装或使用前请阅读。
//
// 下载、复制、安装或使用本软件即表示您同意本许可协议。
// 如果您不同意本许可协议,请勿下载、安装,
// 复制或使用本软件。
//
//
// 许可协议
// 适用于开源计算机视觉库
//
// 版权所有 (C) 2015, OpenCV Foundation, 保留所有权利。
// 第三方版权归其各自所有者所有。
//
// 源代码和二进制形式的再分发和使用,无论是否修改,
// 前提是满足以下条件
//
// * 源代码的再分发必须保留上述版权声明,
// 本条件列表和以下免责声明。
//
// * 二进制形式的再分发必须在文档中复制上述版权声明,
// 此条件列表和以下免责声明
// 本条件列表和以下免责声明。
//
// * 未经明确的事先书面许可,不得使用版权持有者的名称来认可或推广源自本软件的产品。
//
//
// 本软件由版权持有者和贡献者“按原样”提供,
// 任何明示或暗示的保证,包括但不限于适销性和特定用途适用性的暗示保证,均不承担。
//
// 在任何情况下,英特尔公司或贡献者均不对任何直接、
// 间接、附带、特殊、惩戒性或后果性损害负责
// (包括但不限于采购替代商品或服务;
// 使用、数据或利润损失;或业务中断),无论其原因如何,
// 以及基于任何责任理论,无论是合同、严格责任,
// 或侵权(包括疏忽或其他)以任何方式因使用本软件而引起的损害,即使已被告知此类损害的可能性。
//
//
//M*/
#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 )
{
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;
}
如果数组没有元素,则返回 true。
int64_t int64
n 维密集数组类
定义 mat.hpp:830
uchar * data
指向数据的指针
定义 mat.hpp:2167
用于指定图像或矩形大小的模板类。
Definition types.hpp:335
用于从视频文件、图像序列或摄像机进行视频捕获的类。
Definition videoio.hpp:772
std::string String
定义 cvstd.hpp:151
std::shared_ptr< _Tp > Ptr
Definition 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)
将窗口调整到指定大小。
CV_EXPORTS_W bool imwrite(const String &filename, InputArray img, const std::vector< int > &params=std::vector< int >())
将图像保存到指定文件。
void cvtColor(InputArray src, OutputArray dst, int code, int dstCn=0, AlgorithmHint hint=cv::ALGO_HINT_DEFAULT)
将图像从一个颜色空间转换为另一个颜色空间。
void resize(InputArray src, OutputArray dst, Size dsize, double fx=0, double fy=0, int interpolation=INTER_LINEAR)
调整图像大小。
int main(int argc, char *argv[])
定义 highgui_qt.cpp:3
定义 core.hpp:107
STL 命名空间。
StructuredLightPattern 构造函数的参数。
定义 graycodepattern.hpp:77

解释

首先,必须生成要投射的图案图像。由于图像数量是投影仪分辨率的函数,因此必须使用投影仪的宽度和高度设置 GrayCodePattern 类的参数。通过这种方式,可以调用 generate 方法:它会用计算出的图案图像填充一个 Mat 向量。

....
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 );

例如,使用默认投影仪分辨率 (1024 x 768),需要投射 40 张图像:20 张用于常规彩色图案(10 张用于列序列,10 张用于行序列),20 张用于颜色反转图案,其中反转图案图像的结构与原始图像相同,但颜色相反。这提供了一种有效的方法,可以在解码步骤中轻松确定每个像素在被照亮时(最高值)和未被照亮时(最低值)的强度值。

随后,为了识别阴影区域(即像素未被投影仪光线照亮,因此没有编码信息的两个图像区域),3DUNDERWORLD 算法会根据每个相机捕捉到的白色和黑色图像,为两个相机视图计算一个阴影掩模。因此,需要用两个相机投射并捕捉另外两张图像。

// 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 );

因此,最终的投射序列按以下方式投射:首先是列及其反转序列,然后是行及其反转序列,最后是白色和黑色图像。

一旦图案图像生成,它们必须使用全屏选项进行投射:图像必须填充所有投射区域,否则将无法充分利用投影仪的全分辨率,而 3DUNDERWORLD 的实现正是基于此条件。

// 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 );

此时,可以使用我们的数码相机通过 libgphoto2 库(最近已包含在 OpenCV 中)捕捉图像:请记住在构建 OpenCV 时,在 Cmake.list 中启用 gPhoto2 选项。

// 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;
}

两台相机必须以相同的分辨率工作,并且必须禁用自动对焦选项,在整个采集过程中保持相同的焦距。投影仪可以放置在两台相机之间。

然而,在进行图案采集之前,必须对相机进行校准。一旦完成校准,相机不应有任何移动,否则将需要重新校准。

将相机和投影仪连接到计算机后,可以启动 cap_pattern 演示,将保存图像的路径以及投影仪的宽度和高度作为参数传入,并注意使用与校准相同的焦距和相机设置。

此时,要用两台相机采集图像,用户可以按任意键。

// 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
...
}

如果捕获的图像良好(用户必须确保投影的图案能从两个相机中看到),用户可以按回车键保存它们,否则按任何其他键可以重新拍摄。

// 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;
}
}

当所有图案图像都已为两个相机保存后,采集结束。然后用户可以使用 GrayCodePattern 类的 decode 方法重建捕获场景的 3D 模型(参见下一教程)。