目的
このチュートリアルでは、GrayCodePattern クラスを使って次のことを行う方法を学ぶ:
- グレイコードパターンを生成する。
- グレイコードパターンを投影する。
- 投影されたグレイコードパターンを撮影する。
強調しておくべき重要な点として、GrayCodePattern クラスは実際には [127] で説明されている 3DUNDERWORLD アルゴリズムを実装しており、これはステレオ方式に基づいている。すなわち、スキャン対象物の3Dモデルを再構成するには、投影されたパターンを2つの異なる視点から同時に撮影する必要がある。したがって、1組の取得データは、パターンシーケンスの各画像について各カメラが撮影した画像で構成される。
コード
#include <iostream>
#include <stdio.h>
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 )
{
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() << " pattern images + 2 images for shadows mask computation to acquire with both cameras"
<< 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;
}
Designed for command line parsing.
Definition utility.hpp:890
n-dimensional dense array class
Definition mat.hpp:840
uchar * data
pointer to the data
Definition mat.hpp:2206
Template class for specifying the size of an image or rectangle.
Definition types.hpp:335
Class for video capturing from video files, image sequences or cameras.
Definition videoio.hpp:786
std::string String
Definition cvstd.hpp:151
std::shared_ptr< _Tp > Ptr
Definition cvstd_wrapper.hpp:23
void imshow(const String &winname, InputArray mat)
Displays an image in the specified window.
int waitKey(int delay=0)
Waits for a pressed key.
void namedWindow(const String &winname, int flags=WINDOW_AUTOSIZE)
Creates a window.
void moveWindow(const String &winname, int x, int y)
Moves the window to the specified position.
void resizeWindow(const String &winname, int width, int height)
Resizes the window to the specified size.
bool imwrite(const String &filename, InputArray img, const std::vector< int > ¶ms=std::vector< int >())
Saves an image to a specified file.
void cvtColor(InputArray src, OutputArray dst, int code, int dstCn=0, AlgorithmHint hint=cv::ALGO_HINT_DEFAULT)
Converts an image from one color space to another.
int main(int argc, char *argv[])
Definition highgui_qt.cpp:3
Parameters of StructuredLightPattern constructor.
Definition 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枚であり、反転パターン画像とは元の画像と同じ構造で色だけを反転した画像である。これにより、デコード段階で各ピクセルが照らされているとき (最大値) と照らされていないとき (最小値) の強度値を簡単に求めるための効果的な方法が提供される。
続いて、影領域 (プロジェクタの光で照らされず、したがってコード情報が存在しない2枚の画像中の領域) を特定するために、3DUNDERWORLD アルゴリズムは、各カメラで撮影した白画像と黒画像をもとに、2つのカメラ視点それぞれについて影マスクを計算する。そのため、両方のカメラでさらに2枚の画像を投影して撮影する必要がある:
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;
}
2台のカメラは同じ解像度で動作しなければならず、オートフォーカスオプションを無効にして、取得の間ずっと同じフォーカスを維持しなければならない。プロジェクタはカメラの中間に配置できる。
ただし、パターンの取得に進む前に、カメラをキャリブレーションしなければならない。キャリブレーションを実施したら、カメラを動かしてはならない。さもなければ再キャリブレーションが必要になる。
カメラとプロジェクタをコンピュータに接続したら、画像を保存するパスとプロジェクタの幅・高さをパラメータとして与えて 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;
...
}
撮影した画像が良好であれば (投影されたパターンが2台のカメラから見えていることをユーザが確認する)、ユーザはエンターキーを押して保存でき、そうでなければ他の任意のキーを押して撮り直すことができる。
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モデルを再構成できる (次のチュートリアルを参照)。