OpenCV 4.11.0
开源计算机视觉库
加载中…
搜索中…
无匹配项
灰码图案采集教程

目标

本教程将指导您如何使用GrayCodePattern类:

  • 生成灰码图案。
  • 投影灰码图案。
  • 采集投影的灰码图案。

需要强调的是,GrayCodePattern类实际上实现了文献[123]中描述的3DUNDERWORLD算法,该算法基于立体视觉方法:如果要重建被扫描物体的3D模型,则需要同时从两个不同的视角采集投影图案。因此,一个采集集由每个相机对图案序列中每个图像所采集的图像组成。

代码

/*M///////////////////////////////////////////////////////////////////////////////////////
//
// 重要提示:下载、复制、安装或使用前请阅读。
//
// 下载、复制、安装或使用软件即表示您同意本许可协议。
// 如果你不同意本许可协议,请勿下载、安装、
// 复制或使用该软件。
//
//
// 许可协议
// 适用于开源计算机视觉库
//
// 版权所有 (C) 2015,OpenCV 基金会,保留所有权利。
// 第三方版权归其各自所有者所有。
//
// 允许以源代码和二进制形式重新分发和使用,无论是否修改,
// 只要满足以下条件:
//
// * 源代码的重新分发必须保留上述版权声明、
// 此条件列表以及以下免责声明。
//
// * 二进制形式的重新分发必须在文档中复制上述版权声明、
// 此条件列表以及以下免责声明
// 和/或与分发一起提供的其他材料。
//
// * 未经事先明确的书面许可,不得使用版权所有者的名称来认可或推广
// 源于此软件的衍生产品。
//
// 此软件由版权所有者和贡献者“按现状”提供,并且
// 任何明示或暗示的担保,包括但不限于对适销性和特定用途适用性的暗示
// 担保均被排除在外。
// 英特尔公司或贡献者在任何情况下均不对任何直接的、
// 间接的、偶然的、特殊的、惩罚性的或间接的损害负责
// (包括但不限于:替代商品或服务的采购;
// 使用、数据或利润损失;或业务中断)无论其成因如何
// 以及任何责任理论,无论是基于合同、严格责任、
// 或侵权行为(包括疏忽或其他)以任何方式产生于
// 使用本软件,即使已被告知此类损害的可能性。
//
//M*/
#include <iostream>
#include <stdio.h>
using namespace cv;
using namespace std;
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 )
{
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; -1;
}
// 使用参数设置 GraycodePattern
Ptr<structured_light::GrayCodePattern> graycode = structured_light::GrayCodePattern::create( params );
// 图案存储
vector<Mat> pattern;
graycode->generate( pattern );
cout << pattern.size() << " 张图案图像 + 2 张用于阴影掩码计算的图像,需要用两个摄像头采集"
<< endl;
// 生成用于阴影掩码计算的全白和全黑图像
Mat white;
Mat black;
graycode->getImagesForShadowMasks( black, white );
pattern.push_back( white );
pattern.push_back( black );
// 在第二个显示器(投影仪的显示器)上设置图案窗口
namedWindow( "图案窗口", WINDOW_NORMAL );
resizeWindow( "图案窗口", params.width, params.height );
moveWindow( "图案窗口", params.width + 316, -20 );
setWindowProperty( "图案窗口", WND_PROP_FULLSCREEN, WINDOW_FULLSCREEN );
// 使用 libgphoto2 打开摄像头 1
VideoCapture cap1( CAP_GPHOTO2 );
if( !cap1.isOpened() )
{
// 检查 cam1 是否打开
cout << "cam1 未打开!" << endl;
help();
return -1; -1;
}
// 打开摄像头 2
VideoCapture cap2( 1 );
if( !cap2.isOpened() )
{
// 检查 cam2 是否打开
cout << "cam2 未打开!" << endl;
help();
return -1; -1;
}
// 关闭自动对焦
cap1.set( CAP_PROP_SETTINGS, 1 );
cap2.set( CAP_PROP_SETTINGS, 1 );
int i = 0;
while( i < (int) pattern.size() )
{
cout << "等待保存图像编号 " << i + 1 << endl << "按任意键采集照片" << endl;
imshow( "图案窗口", pattern[i] );
Mat frame1;
Mat frame2;
cap1 >> frame1; // 从摄像头 1 获取新帧
cap2 >> frame2; // 从摄像头 2 获取新帧
if( ( frame1.data ) && ( frame2.data ) )
{
Mat tmp;
cout << "cam 1 尺寸: " << Size( ( int ) cap1.get( CAP_PROP_FRAME_WIDTH ), ( int ) cap1.get( CAP_PROP_FRAME_HEIGHT ) )
<< endl;
cout << "cam 2 尺寸: " << Size( ( int ) cap2.get( CAP_PROP_FRAME_WIDTH ), ( int ) cap2.get( CAP_PROP_FRAME_HEIGHT ) )
<< endl;
cout << "cam 1 变焦: " << cap1.get( CAP_PROP_ZOOM ) << endl << "cam 2 变焦: " << cap2.get( CAP_PROP_ZOOM )
<< endl;
cout << "cam 1 对焦: " << cap1.get( CAP_PROP_FOCUS ) << endl << "cam 2 对焦: " << cap2.get( CAP_PROP_FOCUS )
<< endl;
cout << "按回车键保存照片,按其他键重新采集照片" << endl;
namedWindow( "cam1", WINDOW_NORMAL );
resizeWindow( "cam1", 640, 480 );
namedWindow( "cam2", WINDOW_NORMAL );
resizeWindow( "cam2", 640, 480 );
// 移动cam2窗口,以便同时查看cam1的图像
moveWindow( "cam2", 640 + 75, 0 );
// 调整图像大小以避免高分辨率图像出现问题,以灰度显示图像
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 );
// 按回车键保存输出
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 和 cam2 图片编号 " << i + 1 << " 保存成功" << endl << endl;
i++;
}
else
{
cout << "pattern cam1 和 cam2 图片编号 " << i + 1 << " 保存失败" << endl << endl << "请重试,并检查路径"<< endl << endl;
}
}
// 按Esc键关闭程序
if( key == 27 )
{
cout << "正在关闭程序" << endl;
}
}
else
{
cout << "无帧数据,等待新帧" << endl;
}
}
// 相机将在VideoCapture析构函数中自动反初始化
return -1; 0;
}
用于命令行解析。
定义 utility.hpp:890
n维密集数组类
定义 mat.hpp:829
uchar * data
指向数据的指针
定义 mat.hpp:2157
用于指定图像或矩形大小的模板类。
定义 types.hpp:335
用于从视频文件、图像序列或相机捕获视频的类。
定义 videoio.hpp:766
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)
将窗口大小调整为指定大小。
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 );
....
// 使用参数设置 GraycodePattern
Ptr<structured_light::GrayCodePattern> graycode = structured_light::GrayCodePattern::create( params );
// 图案存储
vector<Mat> pattern;
graycode->generate( pattern );

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

随后,为了识别阴影区域(即两个图像中像素没有被投影仪光线照亮,因此没有代码信息的区域),3DUNDERWORLD算法根据每个相机捕获的白色和黑色图像计算两个相机视图的阴影掩码。因此,需要投影和捕获另外两张图像,分别用两个相机进行。

// 生成用于阴影掩码计算的全白和全黑图像
Mat white;
Mat black;
graycode->getImagesForShadowMasks( black, white );
pattern.push_back( white );
pattern.push_back( black );

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

生成图案图像后,必须使用全屏选项进行投影:图像必须充满整个投影区域,否则无法充分利用投影仪的全分辨率,而3DUNDERWORLD的实现依赖于此条件。

// 在第二个显示器(投影仪的显示器)上设置图案窗口
namedWindow( "图案窗口", WINDOW_NORMAL );
resizeWindow( "图案窗口", params.width, params.height );
moveWindow( "图案窗口", params.width + 316, -20 );
setWindowProperty( "图案窗口", WND_PROP_FULLSCREEN, WINDOW_FULLSCREEN );

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

// 使用 libgphoto2 打开摄像头 1
VideoCapture cap1( CAP_GPHOTO2 );
if( !cap1.isOpened() )
{
// 检查 cam1 是否打开
cout << "cam1 未打开!" << endl;
help();
return -1; -1;
}
// 打开摄像头 2
VideoCapture cap2( 1 );
if( !cap2.isOpened() )
{
// 检查 cam2 是否打开
cout << "cam2 未打开!" << endl;
help();
return -1; -1;
}

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

但是,在进行图案采集之前,必须对相机进行校准。校准完成后,相机不得移动,否则需要重新校准。

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

此时,要使用两个摄像头采集图像,用户可以按任意键。

// 关闭自动对焦
cap1.set( CAP_PROP_SETTINGS, 1 );
cap2.set( CAP_PROP_SETTINGS, 1 );
int i = 0;
while( i < (int) pattern.size() )
{
cout << "等待保存图像编号 " << i + 1 << endl << "按任意键采集照片" << endl;
imshow( "Pattern Window", pattern[i] );
Mat frame1;
Mat frame2;
cap1 >> frame1; // 从摄像头 1 获取新帧
cap2 >> frame2; // 从摄像头 2 获取新帧
...
}

如果捕获的图像良好(用户必须确保两个摄像头都能看到投影的图案),用户可以按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 和 cam2 图片编号 " << i + 1 << " 保存成功" << endl << endl;
i++;
}
else
{
cout << "pattern cam1 和 cam2 图片编号 " << i + 1 << " 保存失败" << endl << endl << "请重试,并检查路径"<< endl << endl;
}
}

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