【C++】OpenCVを使った視差計算で3D再構築と距離計測を実現する手法
C++とOpenCVで視差計算を行うと、左右の整列された画像から視差マップが得られ、各画素の位置ずれから深度情報を求めることができます。
カメラキャリブレーションと画像の整列後、cv::StereoBM
やcv::StereoSGBM
などの手法を利用して視差を算出し、3D再構築や距離計測に活用できる手法です。
カメラキャリブレーション
カメラキャリブレーションでは、コンピュータビジョンの処理において非常に大切な役割を果たすため、内部パラメータや外部パラメータの正確な取得が求められます。
ここでは、精度の高い計測のために各種パラメータを調整する方法を詳しく説明します。
内部パラメータの取得
内部パラメータは、カメラ固有の特徴として焦点距離や主点の位置、歪み係数などが含まれます。
撮影時に発生するレンズの歪みを正しく補正することで、後続の視差計算が正確に実施できる環境を整えます。
カメラマトリックスの設定と歪み係数の算出
カメラマトリックスは、画像座標と実際の3次元座標を対応付けるための変換行列です。
例えば、
レンズの歪みも同時に計算することが重要で、放射歪みやタンジェンシャル歪みを補正するパラメータが利用されます。
OpenCVのcv::calibrateCamera
関数を用いると、これらパラメータを効率よく求めることができます。
内部パラメータを取得する際は、下記のような手順でサンプルコードを書き進めると分かりやすいです。
#include <opencv2/opencv.hpp>
#include <vector>
#include <string>
int main() {
// キャリブレーションパターン用画像のファイルパスを用意する
std::vector<std::string> imageFiles = {"pattern1.png", "pattern2.png", "pattern3.png"};
// 3D空間上のキャリブレーションパターンの座標を定義する(チェスボードの場合)
std::vector<cv::Point3f> objectPoints;
int numCornersX = 9;
int numCornersY = 6;
float squareSize = 25.0f; // 単位はミリメートル
for (int y = 0; y < numCornersY; y++) {
for (int x = 0; x < numCornersX; x++) {
objectPoints.push_back(cv::Point3f(x * squareSize, y * squareSize, 0));
}
}
std::vector<std::vector<cv::Point3f>> objectPointsVec;
std::vector<std::vector<cv::Point2f>> imagePointsVec;
for (const auto& file : imageFiles) {
cv::Mat image = cv::imread(file);
if (image.empty()) {
continue;
}
cv::Mat gray;
cv::cvtColor(image, gray, cv::COLOR_BGR2GRAY);
std::vector<cv::Point2f> corners;
bool found = cv::findChessboardCorners(gray, cv::Size(numCornersX, numCornersY), corners);
if (found) {
cv::cornerSubPix(gray, corners, cv::Size(11,11), cv::Size(-1,-1),
cv::TermCriteria(cv::TermCriteria::EPS + cv::TermCriteria::COUNT, 30, 0.1));
objectPointsVec.push_back(objectPoints);
imagePointsVec.push_back(corners);
}
}
cv::Mat cameraMatrix, distCoeffs;
std::vector<cv::Mat> rvecs, tvecs;
// キャリブレーション実施
cv::calibrateCamera(objectPointsVec, imagePointsVec, cv::Size(640,480),
cameraMatrix, distCoeffs, rvecs, tvecs);
// 修正済みカメラパラメータを表示
std::cout << "カメラマトリックス:\n" << cameraMatrix << std::endl;
std::cout << "歪み係数:\n" << distCoeffs << std::endl;
return 0;
}
カメラマトリックス:
[fx, 0, cx;
0, fy, cy;
0, 0, 1]
歪み係数:
[k1, k2, p1, p2, k3]
上記のコードでは、チェスボードパターンを利用してキャリブレーション画像から内部パラメータを求める流れを示しています。
各パラメータがどのように設定されるかを理解することは、視差計算後の精度向上に繋がります。
キャリブレーションパターンの選定
キャリブレーションパターンを選ぶ際は、チェスボードや円形パターンがよく利用されます。
安定して交差点が検出できるパターンを選択すると計算精度が向上します。
以下の点に注意すれば選定が容易です。
- 高いコントラストのパターンを使用する
- 各画像で十分な視野をカバーできる大きさを持つ
- 撮影角度により誤認識を避けるため、歪みが最小限に抑えられるもの
これらのポイントを押さえたパターンの選択は、後続のキャリブレーション処理の安定性向上に大変役立ちます。
外部パラメータの推定
外部パラメータは左右のカメラ間の位置関係を示し、画像から正しい3D情報を抽出するための基盤となります。
ここでは、左右カメラの回転と平行移動に関する基本的な考え方と各画像取得の方法について説明します。
左右カメラ間の回転と平行移動
左右カメラで撮影される画像は、カメラ配置のずれによって生じる回転や平行移動の影響を受けます。
これを正しく推定することで、ステレオ画像の整列が容易になります。
OpenCVのcv::stereoCalibrate
関数を使用する場合、回転行列(R)と並進ベクトル(T)が求められ、これらが左右カメラ間の正確な位置関係を示します。
計算されたパラメータは後に整列処理で利用されるため、精度の確認が重要です。
キャリブレーション画像の取得手法
キャリブレーション用の画像は、様々な角度や距離から撮影することが望ましいです。
画像の取得時には以下のポイントに気をつけるとよいでしょう。
- 異なる照明条件下で撮影する
- 複数の角度や距離をカバーする
- 高品質な画像を使用する
これにより、計算結果が偏ることなく、バラつきの少ないパラメータが得られます。
実際の取得手法としては、同期可能なカメラセットアップを用いると便利です。
画像整列
左右の画像が正確に整列されないと、視差計算の際に誤差が累積する可能性があるため、画像整列は欠かせないプロセスです。
ここでは、基本的な理論と具体的な整列処理の流れを説明します。
ステレオ整合の基本
左右画像の整列は、後の視差計算において対応点を正確に見つけるための土台を作ります。
画像整列がスムーズに行われると、視差マップの精度も向上し、深度推定や3D再構築が正確に実施できます。
画像整列の必要性と目的
画像整列の目的は、左右カメラで撮影された画像の視点差を補正し、同じ平面上に映し出すことです。
これにより、各画素ごとの対応が明確になり、視差値が正しく計算されます。
整列が不十分な場合、視差マップに誤差が含まれてしまい、後続の3D再構築に影響を及ぼす恐れがあります。
射影変換アルゴリズムの概要
画像整列には、射影変換(ホモグラフィー変換)がよく活用されます。
変換行列を求める際に必要な入力は、キャリブレーションによって得られた内部パラメータや外部パラメータです。
これにより、各画像が共通平面に投影され、左右の画像間で点対応が取りやすくなります。
OpenCVのcv::stereoRectify
関数は、この処理を効率的に実現するためのツールとして利用できます。
整列手法の具体的プロセス
画像整列は、キャリブレーション・パラメータを適用して画像補正を施すステップと、実際の画像補正処理の流れに分かれます。
画像補正処理の流れ
画像補正処理を進める際は、以下のようなステップで実施します。
- キャリブレーション画像から得られたパラメータをもとに、各画像の内部歪みを補正
- 射影変換を適用して、左右画像の平面整列を実施
- 補正後の画像に対して、視差計算用の前処理を適用
これらのステップにより、双方の画像間で同じ視点から捉えられたかのようなデータを生成します。
実例に基づく整列方法
具体的な実例として、以下のコードのようにcv::stereoRectify
とcv::initUndistortRectifyMap
を組み合わせると、利便性が高くなります。
#include <opencv2/opencv.hpp>
int main() {
cv::Mat cameraMatrixL, distCoeffsL, cameraMatrixR, distCoeffsR;
cv::Mat R, T, R1, R2, P1, P2, Q;
// ここではキャリブレーション済みパラメータを読み込むと仮定する
// (変数 cameraMatrixL, distCoeffsL, cameraMatrixR, distCoeffsR, R, T が正しく初期化されている)
cv::stereoRectify(cameraMatrixL, distCoeffsL, cameraMatrixR, distCoeffsR,
cv::Size(640,480), R, T, R1, R2, P1, P2, Q);
cv::Mat mapL1, mapL2, mapR1, mapR2;
cv::initUndistortRectifyMap(cameraMatrixL, distCoeffsL, R1, P1, cv::Size(640,480), CV_32FC1, mapL1, mapL2);
cv::initUndistortRectifyMap(cameraMatrixR, distCoeffsR, R2, P2, cv::Size(640,480), CV_32FC1, mapR1, mapR2);
// 補正処理後、視差計算用の画像が得られる
cv::Mat leftImage = cv::imread("left_image.png");
cv::Mat rightImage = cv::imread("right_image.png");
cv::Mat rectifiedLeft, rectifiedRight;
cv::remap(leftImage, rectifiedLeft, mapL1, mapL2, cv::INTER_LINEAR);
cv::remap(rightImage, rectifiedRight, mapR1, mapR2, cv::INTER_LINEAR);
cv::imshow("Rectified Left", rectifiedLeft);
cv::imshow("Rectified Right", rectifiedRight);
cv::waitKey(0);
return 0;
}
(左側および右側の整列された画像が表示されます)
上記のコードでは、あらかじめ求めたカメラパラメータをもとに左右の画像を整列し、視差計算前の準備段階が完了します。
各関数の役割と変数名に注意すれば、プロセス全体がより明確に理解できるでしょう。
視差計算手法
視差計算は、各画像の対応点を検出し、奥行き情報を抽出するための中心的な処理です。
ここでは、StereoBM
とStereoSGBM
を中心に、視差マップを生成する方法について説明します。
StereoBMの活用
StereoBM
は、比較的単純なブロックマッチング手法で視差計算を行います。
計算速度も高速なため、リアルタイム処理が要求される場面でよく利用されます。
ただし、細かいディテール部の視差計算には制限があるため、パラメータ設定に注意が必要です。
ブロックサイズの選定ポイント
ブロックサイズは、視差計算時に使用されるウィンドウのサイズを示します。
一般的に、大きすぎると局所的な変化に弱く、細部の情報を失う可能性がある一方で、小さすぎるとノイズに影響を受けやすくなります。
以下の点が選定のポイントとなります。
- 被写体のテクスチャが豊富な場面なら小さめのウィンドウを
- 均一な部分が多い場合は大きめのウィンドウが安定的に計算可能
パラメータ調整によって、最適なブロックサイズを見極めることが推奨されます。
視差範囲(numDisparities)の調整
視差範囲は、探索する視差の幅を示すパラメータです。
物体の距離に応じて調整が必要となり、遠近感が大きい場面では広い視差範囲が必要です。
設定例として、numDisparities = 16
や 32
などが用いられることが多いです。
設定が不適切な場合、視差マップがクリアにならず、計測結果に影響が出てしまうため、実験を通して適切な値を見つけることが大切です。
StereoSGBMの適用
StereoSGBM
は、複数の視点情報を統合して精度の高い視差計算を実現する手法です。
StereoBM
に比べると計算量が多い反面、エッジ付近や細部の視差がより正確に求められる利点があります。
複数視点の統合プロセス
StereoSGBM
では、動的計画法を含む手法で各視点の比較を行い、全体的に滑らかな視差マップを生成します。
処理中には、局所的なブロックの一致度だけでなく、隣接ピクセル間の整合性も考慮される点が特徴です。
このプロセスにより、不連続な境界部分でも自然な表現が可能になります。
複数アルゴリズム間の比較結果
複数のアルゴリズムを比較すると、StereoBM
はシンプルさと高速処理が魅力的ですが、複雑なシーンではStereoSGBM
が有利な場合が多いです。
具体的には、以下のようなリストが参考になります。
StereoBM
- 高速かつシンプルな処理
- 均一な領域での精度は良好
- 複雑なエッジ部分では不足の可能性
StereoSGBM
- 高精度な視差計算が可能
- 複雑なエッジや細部の処理に強い
- 計算量が多く、リアルタイム処理には工夫が必要
この比較を参考に、シーンに合わせたアルゴリズムを選択すると良いでしょう。
パラメータ最適化の留意事項
StereoSGBM
を使用する際は、各種パラメータ(例えば、minDisparity
、numDisparities
、blockSize
、P1
、P2
)の最適化が不可欠です。
サンプルコードの例として、下記に簡単な設定例を示します。
#include <opencv2/opencv.hpp>
int main() {
cv::Mat leftGray = cv::imread("left_image.png", cv::IMREAD_GRAYSCALE);
cv::Mat rightGray = cv::imread("right_image.png", cv::IMREAD_GRAYSCALE);
if (leftGray.empty() || rightGray.empty()) {
std::cerr << "画像読み込みエラー" << std::endl;
return -1;
}
int minDisparity = 0;
int numDisparities = 16;
int blockSize = 5;
int P1 = 8 * leftGray.channels() * blockSize * blockSize;
int P2 = 32 * leftGray.channels() * blockSize * blockSize;
cv::Ptr<cv::StereoSGBM> sgbm = cv::StereoSGBM::create(minDisparity,
numDisparities,
blockSize,
P1,
P2,
3,
10,
100,
32,
cv::StereoSGBM::MODE_SGBM);
cv::Mat disparitySGBM;
sgbm->compute(leftGray, rightGray, disparitySGBM);
cv::Mat disp8U;
disparitySGBM.convertTo(disp8U, CV_8U, 255/(numDisparities*16.));
cv::imshow("Disparity SGBM", disp8U);
cv::waitKey(0);
return 0;
}
(視差マップがウィンドウに表示されます)
上記のサンプルコードでは、各パラメータの設定と視差計算の流れが丁寧に記されており、調整ポイントも明示されています。
調整の際は、シーンや対象物の性質に応じた実測データを元に微調整することが推奨されます。
深度マップ算出
視差マップから深度情報を導出する過程は、3次元情報を正確に抽出するための重要なステップです。
正確な深度マップを得ることで、3D再構築や距離計測など、さらに応用範囲が広がります。
視差と深度の関係
視差と深度の関係は、カメラ間距離(B)と焦点距離(f)を用いて次のような式で表されます。
この数式は、視差が小さいほど対象物が遠くにあると認識でき、深度計算の基盤として使われる。
数式を利用すると、理論上の深度マップが求められるが、実際の計算ではノイズ除去や補正が必要な場合が多く、各パラメータの調整が欠かせないでしょう。
カメラパラメータの実装例
実装例として、計算された視差マップとキャリブレーション時に求めたパラメータを用いた深度計算のコード例を示します。
#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
// 仮のパラメータ; 実際はキャリブレーションにより求めた値を使用
double baseline = 60.0; // カメラ間距離:ミリメートル
double focalLength = 800.0; // 焦点距離
cv::Mat disparity = cv::imread("disparity.png", cv::IMREAD_GRAYSCALE);
if (disparity.empty()) {
std::cerr << "視差画像読み込みエラー" << std::endl;
return -1;
}
cv::Mat depthMap(disparity.size(), CV_32F);
for (int y = 0; y < disparity.rows; y++) {
for (int x = 0; x < disparity.cols; x++) {
float disp = static_cast<float>(disparity.at<uchar>(y, x));
if (disp <= 0.0f) {
depthMap.at<float>(y, x) = 0.0f;
} else {
depthMap.at<float>(y, x) = static_cast<float>(baseline * focalLength / disp);
}
}
}
cv::imshow("Depth Map", depthMap/255.0f);
cv::waitKey(0);
return 0;
}
(深度マップがウィンドウに表示されます)
このコード例では視差がゼロでない場合のみ深度を計算しており、各ピクセルごとに処理するシンプルなアプローチとなっています。
簡単なループ処理で視差画像から深度情報を作り出すことができ、後続処理でさらに高度な解析も可能になります。
深度マップ改善手法
深度マップは、視差計算の不整合や画像ノイズの影響を受けやすいため、改善のためのノイズ除去や平滑化処理が大切になります。
ここでは、基本的な改善手法について記述します。
ノイズ除去と平滑化のアプローチ
ノイズ除去にはガウシアンフィルタやメディアンフィルタがよく使われます。
これらを組み合わせると、画像の詳細は保持しながら、不要なノイズ成分を効果的に除去できます。
さらに、ビレットフィルタなどのエッジ保持型平滑化アルゴリズムも利用すれば、エッジ部の情報が失われにくくなり、深度マップの品質が向上します。
- ガウシアンフィルタ:画像全体のノイズを平均化して除去
- メディアンフィルタ:局所的な外れ値を効果的に除去
- ビレットフィルタ:エッジ部分を保持しつつ平滑化
これらアルゴリズムの組み合わせにより、深度マップの視認性と実用性がアップする可能性が高いでしょう。
補正処理における調整ポイント
補正処理では、フィルタサイズやカーネルの選択、処理回数など、さまざまなパラメータ調整が求められます。
実際には、以下の点が重要となります。
- 処理対象の画像解像度に合わせたパラメータチューニング
- エッジ部と平坦部分での異なるフィルタリング手法の適用
- 実測値を元にした最適な閾値の設定
適切な調整を行えば、視差計算から抽出された深度情報が、より正確に3次元空間へ反映されるようになります。
応用事例の紹介
視差計算と深度マップ生成の技術は、3D再構築や距離計測システムなど、幅広い応用分野で利用できるため、ここではその具体例をいくつか紹介します。
3D再構築への実装例
視差計算を利用して3D再構築を実現する際は、得られた深度マップをもとにポイントクラウドデータを生成します。
画像の各ピクセルが持つ深度情報から、対象物の立体情報が抽出できるため、ARやロボットビジョン、医療画像解析などで活用されています。
ポイントクラウド生成のプロセス
ポイントクラウドは、画像の各画素を3次元空間上の点として表現する方法です。
キャリブレーションパラメータや深度マップが適切に連携されると、下記のような流れで点群データが生成されます。
- 画像の各ピクセルに対応する3次元座標を計算
- 計算された座標をクラウドデータに変換
- 生成したポイントクラウドを可視化や解析に応用
このプロセスを効率よく実装するために、例えばPCL(Point Cloud Library)との連携を検討するのも一つの方法です。
再構築精度向上の工夫
再構築の精度は、キャリブレーション精度や視差計算のパラメータ設定に大きく依存します。
以下の工夫が推奨されます。
- 複数角度からの画像を統合して、より詳細な深度情報を補完する
- ノイズ除去や補正処理を適用して、計測誤差を抑える
- 適切な視差アルゴリズムを選定し、リアルタイム処理を実現する
これらの工夫を反映させると、再構築された3Dモデルの精度と信頼性が向上する可能性があります。
距離計測システムへの応用事例
距離計測システムでは、視差計算から得た深度情報を直接的に利用して、物体間の距離を計測することが可能です。
工場の自動検査システムや自動運転システムなど、さまざまな場面で応用されています。
測距精度とシステム信頼性の向上
実際のシステムでは、測距精度を高めるための以下の点に重点を置きます。
- 高精度なキャリブレーションによるパラメータ補正の徹底
- リアルタイム処理を可能にするパラメータとハードウェアの最適化
- 複数フレームからのデータ統合による精度向上
このようなアプローチにより、各用途で求められる信頼性の高い測距システムを構築することが可能となります。
実用環境での評価事例
現実の運用環境下では、照明や背景の変動、カメラの配置誤差などにより誤差が発生することがあります。
しかし、十分なキャリブレーションと整列が行われれば、実際の測定結果においても安定した数値が得られるケースが多数報告されています。
フィールドテストを重ねながら、補正アルゴリズムや各種パラメータの最適化を進めると、実用システムとしての評価が高くなる傾向がみられます。
性能評価と最適化
視差計算および深度推定のアルゴリズムは、計算精度や処理速度がシステム全体のパフォーマンスに大きな影響を与えます。
評価方法とその最適化手法について、具体的な検証のポイントを示します。
計算精度の評価方法
計算精度を評価するためには、複数の指標と再現性を確認することが必要です。
主に以下の方法が採用されます。
- 定量評価:実測値と理論値の偏差を計算し、誤差率を算出する
- 視覚評価:生成された深度マップやポイントクラウドの質を人間の目で確認する
- 統計的検証:複数フレーム間での平均誤差や分散を求める
実際の評価では、均一な条件下でキャリブレーションと視差計算を行い、一定の再現性が確認できるかどうかをチェックすることが大切です。
評価指標と再現性の確認
評価指標としては、誤差の平均値やピーク信号対雑音比(PSNR)などが利用されます。
これらの指標により、計測システムの信頼性を定量的に評価できるため、アルゴリズムの最適化に役立てることができます。
実測データを用いた検証
実測データを利用した検証は、実際の応用時に近い環境下での評価となります。
例えば、特定の距離に配置したターゲットを撮影し、計算された深度値と実距離を比較する手法が一般的です。
これにより、システムの誤差範囲や再現性が明確に把握されます。
実装上の最適化アプローチ
視差計算や深度推定のアルゴリズム最適化は、リアルタイム処理を求めるシステムにとっては必須の要素です。
最適化には、各処理段階のパラメータ調整と、並列処理利用などが挙げられます。
パラメータ調整の工夫
パラメータ調整は、各アルゴリズムの性能向上に直結します。
例えば、StereoSGBM
のパラメータを逐次的に調整し、処理速度と計算精度のバランスを取る方法や、動的なパラメータ変更による環境適応が挙げられます。
実験を繰り返すことで、最適な値の探索が行われる点が重要です。
処理速度向上の取り組み
処理速度向上のためには、GPUを利用した並列処理や、マルチスレッド化を進めるとよいです。
また、画像サイズの縮小や、必要な領域のみを処理するROI(Region of Interest)を活用することで、計算負荷を軽減する工夫も有効です。
これにより、リアルタイム処理の要件を満たす仕組みが構築できるでしょう。
まとめ
ここまで紹介した各プロセスや技術について、全体の流れが見えてきます。
内部パラメータと歪み補正、左右カメラ間の外部パラメータ、画像整列、視差計算、深度マップ算出、そして応用事例と最適化手法にわたって、柔らかい表現で各工程のポイントを説明してきました。
各処理は細かい調整が必要な反面、実際の計測や再構築に直結するため、実用的なシステムを構築する際の重要な基盤となります。
実装の際には、各サンプルコードやパラメータの値を参考にしながら、具体的な環境や用途に合わせたチューニングを行ってみるとよいでしょう。