【C++】OpenCVを活用した画像変換の基本操作と応用テクニック
C++とOpenCVで実装すると、画像の読み込みから変換まで各機能を手軽に利用できるため、高効率な画像処理が可能になります。
例えば、cv::cvtColor
で色空間を変更し、cv::getRotationMatrix2D
とcv::warpAffine
で回転変換、cv::resize
でサイズ調整する操作が容易に実施できます。
画像入出力の基本操作
画像読み込み方法
OpenCVでは、画像の読み込みがとても簡単にできるので、まず手始めに画像を読み込む方法を紹介します。
以下のサンプルコードでは、cv::imread
を使って画像を読み込み、画像が正しく読み込まれたかどうかをチェックしています。
#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
// 画像ファイルのパス
std::string imageFile = "sample.jpg";
// 画像をカラーで読み込みます
cv::Mat img = cv::imread(imageFile, cv::IMREAD_COLOR);
if (img.empty()) { // 画像が読み込めなかった場合
std::cerr << "画像の読み込みに失敗しました。" << std::endl;
return -1;
}
// 画像が正しく読み込まれたことを知らせるメッセージ
std::cout << "画像の読み込みに成功しました。" << std::endl;
return 0;
}
画像の読み込みに成功しました。
画像保存方法
画像読み込みと同じくらい、処理後の画像を保存することも基本操作のひとつです。
以下のサンプルコードでは、cv::imwrite
を使って画像をファイルに保存する方法を示しています。
#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
cv::Mat img = cv::imread("sample.jpg", cv::IMREAD_COLOR);
if (img.empty()) {
std::cerr << "画像の読み込みに失敗しました。" << std::endl;
return -1;
}
// 画像を保存する際のファイル名
std::string outputFile = "output.jpg";
// 画像を保存します。保存形式はファイル拡張子で決まります。
if (cv::imwrite(outputFile, img)) {
std::cout << "画像の保存に成功しました。" << std::endl;
} else {
std::cerr << "画像の保存に失敗しました。" << std::endl;
}
return 0;
}
画像の保存に成功しました。
利用可能な画像フォーマット
OpenCVは多くの画像フォーマットに対応しており、以下のようなフォーマットで画像の読み込みや保存が可能です。
- JPEG (.jpg、.jpeg)
- PNG (.png)
- BMP (.bmp)
- TIFF (.tiff、.tif)
- WebP (.webp)
これらのフォーマットは、用途や目的に応じて選ぶと良いでしょう。
たとえば、透過情報が必要な場合はPNG、ファイルサイズを小さく保ちたい場合はJPEGなど、目的に合わせた選択が大切です。
色空間変換
BGRとRGBの基礎
OpenCVでは、画像がBGR(青・緑・赤)の順序で格納されるのが標準です。
一方で、他のライブラリなどではRGB(赤・緑・青)の順序が使われることが多いため、変換が必要になることがあります。
BGRとRGBの入れ替えは、cv::cvtColor
を使うことで簡単に実現できます。
グレースケール変換
カラー画像からグレースケールに変換する操作は、画像処理の基本です。
一般的に、cv::COLOR_BGR2GRAY
を指定して、変換が行われます。
#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
cv::Mat colorImg = cv::imread("sample.jpg", cv::IMREAD_COLOR);
if (colorImg.empty()) {
std::cerr << "カラー画像の読み込みに失敗しました。" << std::endl;
return -1;
}
// BGRからグレースケールへ変換
cv::Mat grayImg;
cv::cvtColor(colorImg, grayImg, cv::COLOR_BGR2GRAY);
// 変換結果の表示メッセージ
std::cout << "グレースケール画像への変換が完了しました。" << std::endl;
return 0;
}
グレースケール画像への変換が完了しました。
明度調整のポイント
グレースケール画像では、明度調整が重要です。
明るさやコントラストを変更することで、画像の雰囲気を大きく変えられます。
補正方法としては、画像全体に対して線形補正を行ったり、ヒストグラム平坦化を利用したりする方法があります。
たとえば、明るさの調整は以下のように実施できます。
- 各ピクセルの値に一定の値を加算する
(ここで は調整値)
HSVおよびLabへの変換
RGBやBGRの他にも、HSV(色相・彩度・明度)空間やLab色空間への変換も画像解析ではよく利用されます。
HSVでは、色相や彩度、明度を直感的に扱うことができ、Labでは人間の色覚に近い表現が可能です。
#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
cv::Mat img = cv::imread("sample.jpg", cv::IMREAD_COLOR);
if (img.empty()) {
std::cerr << "画像の読み込みに失敗しました。" << std::endl;
return -1;
}
// BGRからHSVへ変換
cv::Mat hsvImg;
cv::cvtColor(img, hsvImg, cv::COLOR_BGR2HSV);
// HSV画像への変換完了メッセージ
std::cout << "HSV色空間への変換が完了しました。" << std::endl;
return 0;
}
HSV色空間への変換が完了しました。
変換後の色調整
色空間変換後は、各チャンネルの調整が可能です。
例えばHSVの場合、特定の色相範囲を強調したり、彩度を上げたり下げたりすることで、目的に合わせた画像に調整できるので、画像解析やフィルター処理の前処理としても効果的です。
また、Lab変換後は、輝度や色の差異を個別に扱えるため、補正やエッジ検出などの応用処理がしやすくなります。
幾何学変換
回転変換
画像の回転は、視点変換や補正のためによく使用します。
OpenCVでは、回転行列を生成し、アフィン変換を用いて回転を実現できます。
回転行列の生成
画像の中心を基準に任意の角度で回転させる場合、cv::getRotationMatrix2D
を使用します。
回転角やスケールを指定することで、柔軟な変換が可能です。
#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
cv::Mat img = cv::imread("sample.jpg", cv::IMREAD_COLOR);
if (img.empty()) {
std::cerr << "画像の読み込みに失敗しました。" << std::endl;
return -1;
}
// 画像の中心座標を計算
cv::Point2f center(img.cols / 2.0f, img.rows / 2.0f);
// 45度回転し、拡大率は1.0とする回転行列の作成
cv::Mat rotMat = cv::getRotationMatrix2D(center, 45.0, 1.0);
// 回転後も元のサイズに収まるように設定
cv::Mat rotatedImg;
cv::warpAffine(img, rotatedImg, rotMat, img.size());
// 回転が完了したことを出力
std::cout << "画像の回転変換が完了しました。" << std::endl;
return 0;
}
画像の回転変換が完了しました。
回転後画像サイズの調整
回転によって、元の画像サイズからはみ出す場合があるので、必要に応じて新しいサイズを計算する必要があります。
新しいサイズは、画像の角度と幅・高さから以下のように計算できる
といった数式を参考に実装すると、画像がはみ出さず全体を収められる調整が可能です。
拡大・縮小変換
画像のサイズを変更する際は、cv::resize
が頻繁に使用されます。
拡大や縮小に合わせて、適切な補間アルゴリズムを選ぶことが重要です。
#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
cv::Mat img = cv::imread("sample.jpg", cv::IMREAD_COLOR);
if (img.empty()) {
std::cerr << "画像の読み込みに失敗しました。" << std::endl;
return -1;
}
// 拡大・縮小の比率を指定し、ここでは幅と高さを50%に縮小
double scaleX = 0.5, scaleY = 0.5;
cv::Mat resizedImg;
cv::resize(img, resizedImg, cv::Size(), scaleX, scaleY, cv::INTER_LINEAR);
std::cout << "画像のリサイズが完了しました。" << std::endl;
return 0;
}
画像のリサイズが完了しました。
補間アルゴリズムの選択
拡大・縮小を行う際には、以下のアルゴリズムから選択することができます。
cv::INTER_NEAREST
:最もシンプルで高速だが、画質が粗くなりやすいcv::INTER_LINEAR
:一般的な補間方法で、通常は十分な画質cv::INTER_CUBIC
:滑らかな画像に仕上げたい場合に適しているが、計算量が多い
使用する画像や目的に応じて適切な補間方法を選ぶと良いです。
拡大と縮小時の注意点
拡大する場合、元画像の解像度が低いと画像がぼやける可能性があります。
また、縮小する場合は、細部が失われることがあるので、どのような処理が必要かを検討しながらサイズ変更を実施してください。
トリミング(Crop)の実施
画像の特定部分だけを利用するために、トリミングはよく利用されます。
OpenCVでは、cv::Rect
を利用して領域を指定し、画像の一部を抽出します。
ROIの設定方法
ROI(Region of Interest)の設定は、画像の左上座標、幅、高さを指定することでおこないます。
具体的な座標の設定方法は、画像のサイズに基づいて計算すると便利です。
切り出し範囲の決定
下記のサンプルコードは、画像の中央部分を切り出す例です。
#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
cv::Mat img = cv::imread("sample.jpg", cv::IMREAD_COLOR);
if (img.empty()) {
std::cerr << "画像の読み込みに失敗しました。" << std::endl;
return -1;
}
// 画像の中央部分を抽出するためのROI設定
int x = img.cols / 4;
int y = img.rows / 4;
int width = img.cols / 2;
int height = img.rows / 2;
cv::Rect roi(x, y, width, height);
// ROIを適用して切り出し
cv::Mat croppedImg = img(roi);
std::cout << "画像のトリミングが完了しました。" << std::endl;
return 0;
}
画像のトリミングが完了しました。
アフィン変換と射影変換
アフィン変換の基礎
アフィン変換は、平行移動や回転、拡大縮小などの操作をひとまとめにしておこなうための方法です。
アフィン変換を使うと、複数の変形を同時におこなえるので、画像加工の幅が広がります。
平行移動と回転の組み合わせ
平行移動と回転の組み合わせによって、画像内の特定のオブジェクトの傾きを修正することができます。
下記のサンプルコードは、画像の平行移動と回転を一度におこなう例です。
#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
cv::Mat img = cv::imread("sample.jpg", cv::IMREAD_COLOR);
if (img.empty()) {
std::cerr << "画像の読み込みに失敗しました。" << std::endl;
return -1;
}
// 平行移動のパラメータ(x方向に50ピクセル、y方向に30ピクセル移動)
cv::Mat transMat = (cv::Mat_<double>(2,3) << 1, 0, 50, 0, 1, 30);
// アフィン変換の適用前に回転行列を作成(30度回転、中心は画像中心)
cv::Point2f center(img.cols/2.0f, img.rows/2.0f);
cv::Mat rotMat = cv::getRotationMatrix2D(center, 30, 1.0);
// まず回転をおこない、次に平行移動をおこなう
cv::Mat imgRotated;
cv::warpAffine(img, imgRotated, rotMat, img.size());
cv::Mat imgTransformed;
cv::warpAffine(imgRotated, imgTransformed, transMat, img.size());
std::cout << "平行移動と回転を組み合わせた変換が完了しました。" << std::endl;
return 0;
}
平行移動と回転を組み合わせた変換が完了しました。
傾き補正の適用方法
画像内の傾きを補正する際、アフィン変換を利用すると効率的です。
傾きの角度を算出し、その角度分だけ反対方向に回転させることで、水平な画像に調整できます。
具体的には、検出したエッジまたはラインの角度情報から適切な回転角を算出し、cv::warpAffine
で変換します。
射影変換の基本
射影変換は、視点変換とも呼ばれ、たとえば斜めから撮影された画像を正面から見た視点に変換する場合に利用します。
射影変換では、4点対応を使って画像の各点の位置を算出し、変換行列を生成して適用します。
パースペクティブ変換の原理
パースペクティブ変換では、遠近感を持つ画像の変形を数式で表現するため、変換行列は
例えば、画像内の4点
座標変換の計算方法
変換行列
ここで、変換後の座標は
こうして得た新しい座標に基づいて、画像全体を射影変換することが可能になります。
エラー処理とパフォーマンス向上
例外処理の実装ポイント
OpenCVの関数は、入力画像のフォーマットやサイズが期待と違う場合にエラーが発生することがあります。
そのため、画像操作ごとにエラー処理を行うことを心がけてください。
例えば、ファイルの読み込み後に画像が空かどうかをチェックすることで、以降の処理での予期せぬクラッシュを防ぐことができます。
また、try-catch文を利用する場合もあるので、ライブラリが例外を送出する場合の対応も忘れずに実装してください。
高速処理のための最適化戦略
大量の画像を処理するときは、パフォーマンスの最適化が必要になります。
以下のポイントに注意するとよいでしょう。
メモリ管理の改善策
- 不要になった画像データは速やかに解放します
- 画像のコピーを避け、参照やポインタを活用します
- 必要な領域だけを処理することで、メモリの使用量を抑えます
並列処理の検討
OpenCVは、スレッドを活用して処理を高速化するための関数も提供しているため、マルチスレッドの利用を検討してみるのもよい選択です。
たとえば、複数の画像を一括で処理するときは、OpenMPやTBBを利用して並列処理することがパフォーマンス向上につながります。
実装上の留意点
OpenCVバージョン間の相違
OpenCVは頻繁にアップデートされるため、バージョンごとにAPIの変更が行われることがあります。
コードを書く際は、使用するバージョンのドキュメントを参照し、関数や定数の互換性に注意してください。
バージョンが異なる環境での動作確認も、事前に実施すると安心です。
マルチプラットフォーム対応の注意点
複数のOSでOpenCVを使用するケースが増えているので、プラットフォームごとにライブラリのインストール方法や依存関係が異なる場合があります。
開発環境のセットアップ方法やビルドシステム(CMakeなど)を決め、柔軟に対応できる仕組みを整えておくと、トラブルが少なくなります。
また、ファイルパスの指定やフォルダの区切り文字についても、環境毎に工夫が必要な場合があるので注意してください。
まとめ
今回の内容では、OpenCVを使った画像変換の各基本操作を柔らかい表現で解説しました。
読み込みや保存、色空間変換、幾何学変換、さらにはアフィン変換・射影変換に関する実装ポイントを取り上げ、さらにエラー処理やパフォーマンス向上、実装上の留意点まで幅広く紹介しました。
各サンプルコードを参考に、実際のプロジェクトで試してみると、画像処理の幅が広がること間違いありません。
今後の開発の際に、これらの基礎知識がお役に立つことを願っています。