OpenCV

【C++】OpenCVチュートリアル: 基本操作とサンプルで実践する画像処理

C++とOpenCVを組み合わせて、画像の読み込みや表示、色空間変換、リサイズ、クロッピング、保存などを手軽に行える方法を紹介します。

Visual StudioやCMakeでの初期設定から主要モジュールの使い方までをわかりやすくお伝えします。

基本データ構造

OpenCVを使った画像処理を理解するためには、まず基本的なデータ構造について知ることが重要です。

特に、画像や行列を表現するためのcv::Matクラスと、GPUを活用した高速処理を可能にするUMatGpuMatについて詳しく解説します。

cv::Mat

cv::MatはOpenCVの中核をなすクラスであり、画像や行列データを格納するために広く使われています。

多次元のデータを効率的に管理し、さまざまな画像処理操作に対応できる柔軟性を持っています。

メモリレイアウト

cv::Matのメモリレイアウトは、連続したメモリブロックにデータが格納される構造になっています。

画像の各ピクセルは、行と列の順に並び、各ピクセルの色成分(例:BGR)も連続して格納されます。

具体的には、cv::Matは次のような情報を持ちます。

  • データポインタ:実際のピクセルデータが格納されているメモリの先頭アドレス
  • 行数と列数:画像の縦横のピクセル数
  • チャンネル数:色成分の数(例:3チャンネルのBGR)
  • データ型:ピクセル値の型(例:CV_8UC3は8ビット符号なし3チャンネル)

cv::Matのデータは、stepという値で行ごとのバイト数を管理しており、これにより行間のスキップや部分的なアクセスも容易です。

要素アクセス

cv::Matの要素にアクセスする方法は複数あります。

最も基本的な方法は、at<T>()メンバ関数を使うことです。

例として、画像の特定のピクセルにアクセスする場合は次のようになります。

cv::Vec3b pixel = image.at<cv::Vec3b>(y, x);

ここで、cv::Vec3bは3つの8ビット符号なし整数のベクトル型で、BGRの各成分を表します。

また、画像の一部にアクセスしたい場合は、ROI(Region of Interest)を使って部分範囲を抽出できます。

cv::Rect roi(50, 50, 100, 100);
cv::Mat subImage = image(roi);

at<T>()

at<T>()は、型安全にピクセルにアクセスできる便利な関数です。

Tはピクセルのデータ型を指定します。

  • 例:カラー画像のピクセルにアクセス
cv::Vec3b pixel = image.at<cv::Vec3b>(y, x);
  • 例:グレースケール画像のピクセルにアクセス
uchar grayPixel = image.at<uchar>(y, x);

この方法は、範囲外アクセスを防ぐために範囲チェックも行います。

ptr<T>()

ptr<T>()は、特定の行の先頭アドレスを取得し、効率的にピクセルにアクセスするために使います。

cv::Vec3b* rowPtr = image.ptr<cv::Vec3b>(y);
cv::Vec3b pixel = rowPtr[x];

この方法は、ループ処理や大量のピクセルアクセスに適しており、at<T>()よりも高速です。

ROI操作

ROI(Region of Interest)は、画像の一部分だけを操作したいときに便利です。

cv::Rectを使って範囲を指定し、その範囲をcv::Matとして抽出します。

cv::Rect roi(50, 50, 100, 100); // x=50, y=50から幅100、高さ100の範囲
cv::Mat subImage = image(roi);

これにより、subImageは元の画像の一部を指す新しいcv::Matオブジェクトとなり、その範囲だけに対して処理を行えます。

UMat / GpuMat

OpenCVは、画像処理の高速化を目的として、GPUを活用したデータ構造も提供しています。

これらは、cv::UMatcv::cuda::GpuMatです。

特徴と使い分け

特徴cv::UMatcv::cuda::GpuMat
用途OpenCVの関数とシームレスに連携CUDAを利用したGPU専用のデータ構造
パフォーマンス自動的に最適なデバイス(CPU/GPU)を選択明示的にGPU上で処理を行うため高速化が期待できる
使い勝手OpenCVの関数に渡すだけで自動的に最適化CUDAの知識とプログラミングが必要
データの移動自動的にデバイス間のデータ移動を管理明示的にupload()download()を使う必要がある

cv::UMatは、OpenCVの関数に渡すだけで自動的に最適なデバイス(CPUまたはGPU)で処理を行います。

これにより、コードの変更なしに高速化が期待できるため、手軽にGPUアクセラレーションを利用したい場合に便利です。

一方、cv::cuda::GpuMatは、CUDAのプログラミングに特化したデータ構造であり、GPU上での高度な並列処理やカスタムカーネルの実行に適しています。

GpuMatは、明示的にデータのアップロードやダウンロードを行う必要があり、より詳細な制御が可能です。

これらの基本的なデータ構造を理解しておくことで、OpenCVを使った画像処理の効率化や最適化が可能となります。

次のステップでは、これらのデータ構造を用いた具体的な画像操作例について解説します。

画像の入出力

OpenCVを使った画像処理の基本は、画像の読み込みと保存です。

これらの操作は、cv::imreadcv::imwrite関数を用いて行います。

これらの関数は、画像データをファイルからメモリに読み込む、またはメモリからファイルに書き出す役割を果たします。

さらに、画像のメモリ内でのエンコードやデコードも重要な操作であり、画像データのフォーマット変換や通信に役立ちます。

画像読み込み

cv::imread関数は、指定したパスの画像ファイルを読み込み、cv::Matオブジェクトとして返します。

画像のフォーマットはJPEG、PNG、BMPなど、多くの一般的な画像形式に対応しています。

#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
    // 画像ファイルのパスを指定
    std::string filename = "sample.jpg";
    // 画像を読み込み
    cv::Mat image = cv::imread(filename);
    // 画像の読み込みに失敗した場合の処理
    if (image.empty()) {
        std::cout << "画像の読み込みに失敗しました: " << filename << std::endl;
        return -1;
    }
    // 画像の表示
    cv::imshow("読み込んだ画像", image);
    cv::waitKey(0);
    return 0;
}

この例では、imreadはデフォルトでカラー画像として画像を読み込みます。

画像が存在しない、またはサポートされていないフォーマットの場合、cv::Matは空empty()となるため、そのチェックを行います。

imreadのフラグ設定

cv::imreadには、画像の読み込み方法を制御するためのフラグを設定できます。

これにより、カラー画像、グレースケール画像、またはアルファチャンネル付きの画像として読み込むことが可能です。

フラグ内容備考
cv::IMREAD_COLORカラー画像として読み込み(デフォルト)cv::imread("file.jpg", cv::IMREAD_COLOR)3チャンネルのBGR画像として読み込む
cv::IMREAD_GRAYSCALEグレースケール画像として読み込みcv::imread("file.jpg", cv::IMREAD_GRAYSCALE)1チャンネルの画像として読み込む
cv::IMREAD_UNCHANGED画像のアルファチャンネルも含めてそのまま読み込みcv::imread("file.png", cv::IMREAD_UNCHANGED)透明度情報も保持される

例として、アルファチャンネル付きのPNG画像を読み込む場合は次のように記述します。

cv::Mat image = cv::imread("transparent.png", cv::IMREAD_UNCHANGED);

画像保存

cv::imwriteは、cv::Matに格納された画像データをファイルに書き出す関数です。

保存できる画像フォーマットは、ファイル名の拡張子に応じて自動的に選択されます。

#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
    // 既存の画像を読み込み
    cv::Mat image = cv::imread("sample.jpg");
    if (image.empty()) {
        std::cout << "画像の読み込みに失敗しました。" << std::endl;
        return -1;
    }
    // 画像を保存
    bool result = cv::imwrite("output.png", image);
    if (result) {
        std::cout << "画像を保存しました。" << std::endl;
    } else {
        std::cout << "画像の保存に失敗しました。" << std::endl;
    }
    return 0;
}

imwriteは、保存時に追加のオプションを指定することも可能です。

これにより、圧縮率や品質設定を調整できます。

imwriteのオプション

画像の保存時に指定できるオプションは、フォーマットによって異なります。

一般的な例としてJPEGの品質設定を示します。

#include <opencv2/opencv.hpp>
#include <vector>
int main() {
    cv::Mat image = cv::imread("sample.jpg");
    if (image.empty()) {
        return -1;
    }
    // JPEGの品質を設定(0〜100)
    std::vector<int> params;
    params.push_back(cv::IMWRITE_JPEG_QUALITY);
    params.push_back(90); // 高品質
    // 画像を保存
    cv::imwrite("compressed.jpg", image, params);
    return 0;
}

この例では、JPEGの圧縮品質を90に設定しています。

PNGの場合は圧縮レベルを指定できます。

// PNGの圧縮レベル(0〜9)
params.clear();
params.push_back(cv::IMWRITE_PNG_COMPRESSION);
params.push_back(3); // 中程度の圧縮
cv::imwrite("compressed.png", image, params);

メモリ内エンコード / デコード

画像のエンコードとデコードは、画像データをファイルに保存するだけでなく、メモリ内でのフォーマット変換や通信に役立ちます。

  • エンコードcv::imencodeを使って、cv::Matを特定のフォーマットのバッファに変換します
  • デコードcv::imdecodeを使って、バッファからcv::Matに変換します

例:画像をJPEG形式にエンコード

#include <opencv2/opencv.hpp>
#include <vector>
#include <iostream>
int main() {
    cv::Mat image = cv::imread("sample.jpg");
    if (image.empty()) {
        return -1;
    }
    std::vector<uchar> buffer; // メモリバッファ
    std::vector<int> params;
    params.push_back(cv::IMWRITE_JPEG_QUALITY);
    params.push_back(80); // 80%の品質
    // 画像をJPEG形式にエンコード
    bool success = cv::imencode(".jpg", image, buffer, params);
    if (success) {
        std::cout << "画像をメモリ内にエンコードしました。" << std::endl;
    }
    // バッファから画像にデコード
    cv::Mat decodedImage = cv::imdecode(buffer, cv::IMREAD_COLOR);
    if (decodedImage.empty()) {
        std::cout << "デコードに失敗しました。" << std::endl;
        return -1;
    }
    cv::imshow("デコードされた画像", decodedImage);
    cv::waitKey(0);
    return 0;
}

この操作は、画像をネットワーク越しに送信したり、メモリ内で画像フォーマットを変換したりする際に非常に便利です。

これらの入出力操作を理解し、適切に使いこなすことで、画像処理の幅が広がります。

次のステップでは、画像の基本的な操作や変換について詳しく解説します。

表示とウィンドウ操作

OpenCVを使った画像処理の結果を確認するためには、画像をウィンドウに表示し、ウィンドウの操作を行う必要があります。

namedWindowimshowwaitKey、そしてウィンドウの位置やサイズを調整する関数を理解しておくと、より効率的に画像の確認やインタラクションが可能となります。

namedWindow / imshow

cv::namedWindowは、新しいウィンドウを作成し、その名前を指定します。

これにより、複数のウィンドウを管理したり、ウィンドウの属性を設定したりできます。

#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
    // ウィンドウの作成
    cv::namedWindow("画像表示", cv::WINDOW_AUTOSIZE);
    // 画像の読み込み
    cv::Mat image = cv::imread("sample.jpg");
    if (image.empty()) {
        std::cout << "画像の読み込みに失敗しました。" << std::endl;
        return -1;
    }
    // 画像をウィンドウに表示
    cv::imshow("画像表示", image);
    // キー入力待ち
    cv::waitKey(0);
    return 0;
}

cv::imshowは、指定したウィンドウに画像を表示します。

ウィンドウが存在しない場合は自動的に作成されますが、namedWindowで事前に設定しておくと、ウィンドウの属性を細かく制御できます。

waitKeyによる制御

cv::waitKeyは、指定したミリ秒だけキー入力を待ちます。

引数に0を指定すると無限に待ち続け、何らかのキーが押されるまで停止します。

cv::waitKey(0);

この関数は、画像を表示した後に次の処理に進むための同期ポイントとしても使われます。

例えば、アニメーションやインタラクティブな操作を行う場合は、ループ内でwaitKeyを呼び出し、ユーザーの入力を待ちます。

また、waitKeyは返り値として押されたキーのASCIIコードを返すため、特定のキー入力に応じて処理を分岐させることも可能です。

int key = cv::waitKey(0);
if (key == 'q') {
    // qキーが押された場合の処理
}

ウィンドウ制御

ウィンドウの位置やサイズを調整するための関数も用意されています。

これにより、複数の画像を並べて比較したり、特定の位置にウィンドウを配置したりできます。

moveWindow

cv::moveWindowは、指定したウィンドウを画面上の特定の座標に移動させる関数です。

#include <opencv2/opencv.hpp>
int main() {
    cv::namedWindow("画像", cv::WINDOW_AUTOSIZE);
    cv::Mat image = cv::imread("sample.jpg");
    if (image.empty()) {
        return -1;
    }
    cv::imshow("画像", image);
    // ウィンドウを画面の左上座標(100, 50)に移動
    cv::moveWindow("画像", 100, 50);
    cv::waitKey(0);
    return 0;
}

この例では、ウィンドウが画面の左上から横に100ピクセル、縦に50ピクセルの位置に移動します。

resizeWindow

cv::resizeWindowは、既存のウィンドウのサイズを変更します。

ウィンドウの自動サイズ調整を無効にして、任意の大きさに設定したい場合に便利です。

#include <opencv2/opencv.hpp>
int main() {
    cv::namedWindow("画像", cv::WINDOW_NORMAL);
    cv::Mat image = cv::imread("sample.jpg");
    if (image.empty()) {
        return -1;
    }
    cv::imshow("画像", image);
    // ウィンドウのサイズを幅800、高さ600に変更
    cv::resizeWindow("画像", 800, 600);
    cv::waitKey(0);
    return 0;
}

cv::WINDOW_NORMALを指定しておくと、ウィンドウのサイズ変更が可能になります。

resizeWindowを使うことで、画像の縦横比を維持しながらウィンドウの大きさを調整したり、見やすいサイズに変更したりできます。

これらのウィンドウ操作を駆使することで、画像処理の結果を見やすく整理し、インタラクティブな操作も可能となります。

次のステップでは、より詳細なウィンドウの制御やインタラクションについて解説します。

ピクセル演算

画像処理において、ピクセル単位での演算は基本的かつ重要な操作です。

これにより、画像の明るさ調整やマスク処理、合成などさまざまな効果を実現できます。

OpenCVでは、算術演算とビット演算の両方をサポートしており、効率的にピクセルごとの演算を行うことが可能です。

算術演算

算術演算は、画像の輝度や色の調整に広く使われます。

OpenCVでは、cv::addcv::subtractcv::addWeightedといった関数を用いて、画像同士の加算・減算や重み付け合成を行います。

add / subtract

cv::addは、2つの画像の対応するピクセル値を加算します。

オーバーフローを防ぐために、結果は最大値(例:255)にクリップされます。

#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
    cv::Mat img1 = cv::imread("image1.jpg");
    cv::Mat img2 = cv::imread("image2.jpg");
    if (img1.empty() || img2.empty()) {
        return -1;
    }
    cv::Mat resultAdd, resultSub;
    // 画像の加算
    cv::add(img1, img2, resultAdd);
    // 画像の減算
    cv::subtract(img1, img2, resultSub);
    cv::imshow("加算結果", resultAdd);
    cv::imshow("減算結果", resultSub);
    cv::waitKey(0);
    return 0;
}

この例では、2つの画像を加算・減算し、それぞれ結果を表示しています。

2枚の画像が同じサイズ・同じチャネルである必要があります。

cv::subtractは、対応するピクセル値を引き算します。

負の値は0にクリップされるため、結果は常に非負となります。

addWeighted

cv::addWeightedは、2つの画像を重み付けて合成します。

これは、画像のブレンドや透明度調整に便利です。

#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
    cv::Mat img1 = cv::imread("image1.jpg");
    cv::Mat img2 = cv::imread("image2.jpg");
    if (img1.empty() || img2.empty()) {
        return -1;
    }
    cv::Mat blended;
    // 画像の重み付け合成
    double alpha = 0.7; // img1の重み
    double beta = 0.3;  // img2の重み
    double gamma = 0.0; // 輝度の付加値
    cv::addWeighted(img1, alpha, img2, beta, gamma, blended);
    cv::imshow("ブレンド画像", blended);
    cv::waitKey(0);
    return 0;
}

この例では、img1img2をそれぞれ70%、30%の割合で合成しています。

2枚の画像が同じサイズ・同じチャネルである必要があります。

gammaは全体の明るさ調整に使います。

ビット演算

ビット演算は、画像のマスク処理や論理演算に用いられます。

cv::bitwise_andcv::bitwise_orcv::bitwise_xorcv::bitwise_notを使って、ピクセルごとに論理演算を行います。

bitwise_and / or / xor / not

これらの関数は、2つの画像またはマスクに対して論理演算を行います。

  • AND (cv::bitwise_and):両方の画像の対応ピクセルが1の場合に1となる
  • OR (cv::bitwise_or):いずれかの画像の対応ピクセルが1の場合に1となる
  • XOR (cv::bitwise_xor):片方だけが1の場合に1となる
  • NOT (cv::bitwise_not):画像のピクセル値を反転させる
#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
    cv::Mat img1 = cv::imread("mask1.png", cv::IMREAD_GRAYSCALE);
    cv::Mat img2 = cv::imread("mask2.png", cv::IMREAD_GRAYSCALE);
    if (img1.empty() || img2.empty()) {
        return -1;
    }
    cv::Mat andResult, orResult, xorResult, notResult;
    // AND演算
    cv::bitwise_and(img1, img2, andResult);
    // OR演算
    cv::bitwise_or(img1, img2, orResult);
    // XOR演算
    cv::bitwise_xor(img1, img2, xorResult);
    // NOT演算(img1の反転)
    cv::bitwise_not(img1, notResult);
    cv::imshow("AND結果", andResult);
    cv::imshow("OR結果", orResult);
    cv::imshow("XOR結果", xorResult);
    cv::imshow("NOT結果", notResult);
    cv::waitKey(0);
    return 0;
}

この例では、2つのマスク画像に対して論理演算を行い、それぞれの結果を表示しています。

2枚の画像が同じサイズ・同じチャネルである必要があります。

cv::bitwise_notは、画像のピクセル値を反転させるため、白黒のマスク画像の逆転に便利です。

ピクセル演算は、画像の明るさ調整やマスク処理、画像合成など、多彩な画像処理の基礎となる操作です。

これらを駆使して、より高度な画像処理や解析を行います。

色空間変換

画像処理において、色空間の変換は非常に重要な操作です。

異なる色空間を使うことで、画像の特徴抽出や解析、視覚的な表現を効果的に行うことができます。

OpenCVでは、cv::cvtColor関数を用いてさまざまな色空間間の変換を簡単に実現できます。

BGR ⇔ グレースケール

カラー画像は通常、BGR(Blue-Green-Red)の3チャンネルで表現されます。

一方、グレースケール画像は1チャンネルで、輝度情報のみを持ちます。

色空間変換を行うことで、カラー画像から輝度情報だけを抽出したグレースケール画像を作成できます。

#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
    // 画像の読み込み(カラー画像)
    cv::Mat colorImage = cv::imread("sample.jpg");
    if (colorImage.empty()) {
        return -1;
    }
    // BGRからグレースケールへ変換
    cv::Mat grayImage;
    cv::cvtColor(colorImage, grayImage, cv::COLOR_BGR2GRAY);
    // 表示
    cv::imshow("カラー画像", colorImage);
    cv::imshow("グレースケール画像", grayImage);
    cv::waitKey(0);
    return 0;
}

この例では、cv::cvtColorの第3引数にcv::COLOR_BGR2GRAYを指定して、カラー画像をグレースケールに変換しています。

BGR ⇔ HSV / HLS

HSV(Hue, Saturation, Value)やHLS(Hue, Lightness, Saturation)は、色相や彩度、明度を分離して表現できる色空間です。

これにより、色の抽出や色相に基づくフィルタリングが容易になります。

#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
    cv::Mat bgrImage = cv::imread("sample.jpg");
    if (bgrImage.empty()) {
        return -1;
    }
    // BGRからHSVへ変換
    cv::Mat hsvImage;
    cv::cvtColor(bgrImage, hsvImage, cv::COLOR_BGR2HSV);
    // BGRからHLSへ変換
    cv::Mat hlsImage;
    cv::cvtColor(bgrImage, hlsImage, cv::COLOR_BGR2HLS);
    // 表示
    cv::imshow("BGR画像", bgrImage);
    cv::imshow("HSV画像", hsvImage);
    cv::imshow("HLS画像", hlsImage);
    cv::waitKey(0);
    return 0;
}

HSVやHLSは、色相を基準にした色抽出や、特定の色範囲をマスクする際に非常に便利です。

カラーマップ適用

カラーマップは、画像の輝度情報に色付けを行い、視覚的にわかりやすくするために使われます。

OpenCVでは、cv::applyColorMap関数を使ってさまざまなカラーマップを適用できます。

#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
    // グレースケール画像の読み込み
    cv::Mat grayImage = cv::imread("gray_sample.jpg", cv::IMREAD_GRAYSCALE);
    if (grayImage.empty()) {
        return -1;
    }
    // カラーマップの適用(例:JETマップ)
    cv::Mat colorMappedImage;
    cv::applyColorMap(grayImage, colorMappedImage, cv::COLORMAP_JET);
    // 表示
    cv::imshow("グレースケール画像", grayImage);
    cv::imshow("カラーマップ適用後", colorMappedImage);
    cv::waitKey(0);
    return 0;
}

cv::applyColorMapには、多数のプリセットカラーマップ(例:COLORMAP_JET, COLORMAP_HOT, COLORMAP_COOLなど)が用意されており、画像の特徴を強調したり、視覚的な判別を容易にしたりするのに役立ちます。

色空間変換は、画像の解析や視覚的表現を向上させるための基本操作です。

適切な色空間を選択し、変換を行うことで、画像処理の幅が広がります。

次のステップでは、これらの変換を応用した具体的な処理例について解説します。

幾何変換

画像の幾何学的な変形は、画像の位置や形状を変更するために頻繁に使用されます。

OpenCVでは、リサイズ、アフィン変換、透視変換、回転・拡大縮小などの操作を行うための関数が用意されています。

これらの操作を理解し適切に使いこなすことで、画像の整列や補正、特殊効果の実現が可能となります。

リサイズ

リサイズは、画像の縦横のピクセル数を変更する基本的な操作です。

cv::resize関数を使って、指定したサイズや倍率に画像を拡大・縮小します。

#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
    cv::Mat src = cv::imread("sample.jpg");
    if (src.empty()) {
        return -1;
    }
    cv::Mat dst;
    // 画像を縦横ともに2倍に拡大
    cv::resize(src, dst, cv::Size(), 2.0, 2.0, cv::INTER_LINEAR);
    // 画像を幅300、高さ200にリサイズ
    cv::resize(src, dst, cv::Size(300, 200), 0, 0, cv::INTER_AREA);
    cv::imshow("リサイズ後(拡大)", dst);
    cv::waitKey(0);
    return 0;
}
拡大後に縮小された画像

cv::resizeの第3引数にcv::Size()を指定し、倍率を第4・5引数に設定することで、拡大縮小を行います。

補間方法はcv::INTER_LINEARcv::INTER_AREAなどから選択します。

アフィン変換

アフィン変換は、平行線を平行に保ったまま画像を変形させる操作です。

平行移動、回転、拡大縮小、せん断などを組み合わせて行います。

getAffineTransform / warpAffine

cv::getAffineTransformは、3つの対応点を指定して変換行列を計算します。

cv::warpAffineは、その行列を用いて画像に変換を適用します。

#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
    cv::Mat src = cv::imread("sample.jpg");
    if (src.empty()) {
        return -1;
    }
    // 変換前の3点
    std::vector<cv::Point2f> srcTri = {
        cv::Point2f(0, 0),
        cv::Point2f(src.cols - 1, 0),
        cv::Point2f(0, src.rows - 1)
    };
    // 変換後の3点(例:右下にシフト)
    std::vector<cv::Point2f> dstTri = {
        cv::Point2f(50, 50),
        cv::Point2f(src.cols - 50, 50),
        cv::Point2f(50, src.rows - 50)
    };
    // アフィン変換行列の計算
    cv::Mat affineMat = cv::getAffineTransform(srcTri, dstTri);
    // 変換の適用
    cv::Mat dst;
    cv::warpAffine(src, dst, affineMat, src.size());
    cv::imshow("アフィン変換結果", dst);
    cv::waitKey(0);
    return 0;
}

この例では、3つの点を対応させてアフィン変換行列を作成し、画像を変形しています。

透視変換

透視変換は、遠近感を持つ画像の変形に用いられます。

平行線が収束する点を操作できるため、画像の歪み補正やパースペクティブ効果の付与に適しています。

getPerspectiveTransform / warpPerspective

cv::getPerspectiveTransformは、4つの対応点から変換行列を計算し、cv::warpPerspectiveはその行列を使って画像に変換を適用します。

#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
    cv::Mat src = cv::imread("sample.jpg");
    if (src.empty()) {
        return -1;
    }
    // 変換前の4点
    std::vector<cv::Point2f> srcQuad = {
        cv::Point2f(0, 0),
        cv::Point2f(src.cols - 1, 0),
        cv::Point2f(src.cols - 1, src.rows - 1),
        cv::Point2f(0, src.rows - 1)
    };
    // 変換後の4点(例:四角を台形に歪める)
    std::vector<cv::Point2f> dstQuad = {
        cv::Point2f(50, 50),
        cv::Point2f(src.cols - 50, 30),
        cv::Point2f(src.cols - 30, src.rows - 50),
        cv::Point2f(70, src.rows - 30)
    };
    // 透視変換行列の計算
    cv::Mat perspectiveMat = cv::getPerspectiveTransform(srcQuad, dstQuad);
    // 変換の適用
    cv::Mat dst;
    cv::warpPerspective(src, dst, perspectiveMat, src.size());
    cv::imshow("透視変換結果", dst);
    cv::waitKey(0);
    return 0;
}

この例では、四角形の画像を台形に歪める透視変換を行っています。

回転 / 拡大縮小

画像の回転や拡大縮小は、cv::getRotationMatrix2Dcv::warpAffineを組み合わせて行います。

#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
    cv::Mat src = cv::imread("sample.jpg");
    if (src.empty()) {
        return -1;
    }
    // 回転中心と角度
    cv::Point2f center(src.cols / 2.0F, src.rows / 2.0F);
    double angle = 45.0; // 45度回転
    double scale = 1.0;  // 拡大縮小倍率
    // 回転行列の取得
    cv::Mat rotMat = cv::getRotationMatrix2D(center, angle, scale);
    // 変換の適用
    cv::Mat dst;
    cv::warpAffine(src, dst, rotMat, src.size());
    cv::imshow("回転結果", dst);
    cv::waitKey(0);
    return 0;
}

この例では、画像の中心を軸に45度回転させています。

拡大縮小もscaleパラメータで調整可能です。

これらの幾何変換を駆使することで、画像の位置調整や形状補正、特殊効果の付与など、多彩な画像操作が実現します。

次のステップでは、これらの変換を応用した高度な画像処理例について解説します。

フィルタリング

画像のノイズ除去や平滑化、エッジの強調などを目的としたフィルタリングは、画像処理の基本的な操作です。

OpenCVでは、さまざまな種類のフィルタが用意されており、それぞれの特徴に応じて使い分けることが重要です。

ここでは、代表的な平均化フィルタ、ガウシアンフィルタ、メディアンフィルタ、そしてバイラテラルフィルタについて詳しく解説します。

平均化フィルタ

平均化フィルタは、画像の各ピクセルを、その周囲のピクセルの平均値に置き換えることで、ノイズを除去し、画像を平滑化します。

cv::blur関数を用いて実現します。

#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
    cv::Mat src = cv::imread("noisy_image.jpg");
    if (src.empty()) {
        return -1;
    }
    cv::Mat dst;
    // 3x3の平均化フィルタを適用
    cv::blur(src, dst, cv::Size(3, 3));
    cv::imshow("平均化フィルタ後", dst);
    cv::waitKey(0);
    return 0;
}

このフィルタは計算が高速で、ノイズ除去に効果的ですが、画像のエッジもぼやけてしまうため、エッジを保持したい場合には他のフィルタを選択します。

ガウシアンフィルタ

ガウシアンフィルタは、重み付けされた平均化を行い、中心に近いピクセルほど高い重みを持つため、より自然な平滑化が可能です。

cv::GaussianBlur関数を使います。

#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
    cv::Mat src = cv::imread("noisy_image.jpg");
    if (src.empty()) {
        return -1;
    }
    cv::Mat dst;
    // 5x5のガウシアンカーネルを適用
    cv::GaussianBlur(src, dst, cv::Size(5, 5), 1.5);
    cv::imshow("ガウシアンフィルタ後", dst);
    cv::waitKey(0);
    return 0;
}

ガウシアンフィルタは、エッジをある程度保持しながらノイズを除去できるため、多くの画像処理シナリオで広く使われています。

メディアンフィルタ

メディアンフィルタは、対象ピクセルの周囲のピクセル値の中央値を取ることで、特に「塩と胡椒」ノイズの除去に優れています。

cv::medianBlurを用います。

#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
    cv::Mat src = cv::imread("salt_and_pepper_noise.jpg");
    if (src.empty()) {
        return -1;
    }
    cv::Mat dst;
    // 3x3のメディアンフィルタを適用
    cv::medianBlur(src, dst, 3);
    cv::imshow("メディアンフィルタ後", dst);
    cv::waitKey(0);
    return 0;
}

このフィルタは、エッジをほとんど損なわずにノイズを除去できるため、特にノイズの種類に応じて使い分けると良いです。

Bilateralフィルタ

バイラテラルフィルタは、空間的な近さと色の類似性の両方を考慮して平滑化を行います。

エッジを保持しながらノイズ除去ができるため、写真の美化や詳細を残した平滑化に適しています。

cv::bilateralFilterを使います。

#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
    cv::Mat src = cv::imread("photo.jpg");
    if (src.empty()) {
        return -1;
    }
    cv::Mat dst;
    // パラメータ:d=9, sigmaColor=75, sigmaSpace=75
    cv::bilateralFilter(src, dst, 9, 75, 75);
    cv::imshow("バイラテラルフィルタ後", dst);
    cv::waitKey(0);
    return 0;
}

このフィルタは計算コストが高いため、処理時間に注意が必要ですが、エッジを損なわずに滑らかな画像を作り出すことができます。

これらのフィルタは、それぞれの特性を理解し、目的に応じて使い分けることが重要です。

ノイズ除去や画像の平滑化だけでなく、エッジの保持や詳細の強調など、多彩な画像処理に役立ちます。

次のステップでは、これらのフィルタを応用した高度な画像処理例について解説します。

エッジ検出と輪郭抽出

画像のエッジ検出と輪郭抽出は、画像解析や物体認識、形状解析において重要な役割を果たします。

エッジは画像内の輝度変化が大きい部分を示し、輪郭は物体の境界線を表します。

OpenCVでは、さまざまなエッジ検出アルゴリズムと輪郭抽出の関数が用意されており、これらを適切に使い分けることで、画像の特徴抽出や解析を効率的に行えます。

Sobel / Scharr

Sobelフィルタは、画像の勾配を計算し、エッジの方向と強さを検出します。

cv::Sobel関数を用いて、X方向とY方向の勾配をそれぞれ計算し、エッジの情報を得ることができます。

#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
    cv::Mat src = cv::imread("sample.jpg", cv::IMREAD_GRAYSCALE);
    if (src.empty()) {
        return -1;
    }
    cv::Mat gradX, gradY, absGradX, absGradY, edgeImage;
    // X方向の勾配
    cv::Sobel(src, gradX, CV_16S, 1, 0, 3);
    cv::convertScaleAbs(gradX, absGradX);
    // Y方向の勾配
    cv::Sobel(src, gradY, CV_16S, 0, 1, 3);
    cv::convertScaleAbs(gradY, absGradY);
    // 合成
    cv::addWeighted(absGradX, 0.5, absGradY, 0.5, 0, edgeImage);
    cv::imshow("Sobelエッジ", edgeImage);
    cv::waitKey(0);
    return 0;
}

Scharrフィルタは、Sobelの改良版で、より高精度なエッジ検出が可能です。

cv::Scharr関数を使います。

cv::Scharr(src, gradX, CV_16S, 1, 0);
cv::Scharr(src, gradY, CV_16S, 0, 1);

Laplacian

Laplacianフィルタは、二階微分を計算し、エッジを強調します。

cv::Laplacian関数を用いて、画像の二次微分を求め、エッジ部分を抽出します。

#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
    cv::Mat src = cv::imread("sample.jpg", cv::IMREAD_GRAYSCALE);
    if (src.empty()) {
        return -1;
    }
    cv::Mat laplacian, absLaplacian;
    cv::Laplacian(src, laplacian, CV_16S, 3);
    cv::convertScaleAbs(laplacian, absLaplacian);
    cv::imshow("Laplacianエッジ", absLaplacian);
    cv::waitKey(0);
    return 0;
}

Laplacianは、エッジの検出に敏感であり、ノイズに弱いため、事前に平滑化を行うことが推奨されます。

Cannyエッジ検出

Cannyアルゴリズムは、エッジ検出の中でも最も広く使われている方法の一つです。

ノイズ除去、勾配計算、ヒステリシス閾値処理のステップを経て、精度の高いエッジを抽出します。

#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
    cv::Mat src = cv::imread("sample.jpg", cv::IMREAD_GRAYSCALE);
    if (src.empty()) {
        return -1;
    }
    cv::Mat edges;
    //閾値は適宜調整
    cv::Canny(src, edges, 50, 150);
    cv::imshow("Cannyエッジ", edges);
    cv::waitKey(0);
    return 0;
}

閾値の設定次第でエッジの検出結果が変わるため、画像に応じて調整が必要です。

輪郭抽出

輪郭抽出は、画像内の物体の境界線を抽出する操作です。

cv::findContours関数を使って、画像から輪郭を検出します。

findContours

#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
    cv::Mat src = cv::imread("binary_mask.png", cv::IMREAD_GRAYSCALE);
    if (src.empty()) {
        return -1;
    }
    std::vector<std::vector<cv::Point>> contours;
    std::vector<cv::Vec4i> hierarchy;
    // 輪郭抽出
    cv::findContours(src, contours, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE);
    // 輪郭を描画
    cv::Mat contourImage = cv::Mat::zeros(src.size(), CV_8UC3);
    for (size_t i = 0; i < contours.size(); i++) {
        cv::Scalar color = cv::Scalar(0, 255, 0);
        cv::drawContours(contourImage, contours, (int)i, color, 2);
    }
    cv::imshow("輪郭抽出", contourImage);
    cv::waitKey(0);
    return 0;
}

approxPolyDP

輪郭の近似化に使われる関数で、複雑な輪郭を簡略化します。

#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
    cv::Mat src = cv::imread("contour.png", cv::IMREAD_GRAYSCALE);
    if (src.empty()) {
        return -1;
    }
    std::vector<std::vector<cv::Point>> contours;
    cv::findContours(src, contours, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE);
    std::vector<std::vector<cv::Point>> approxContours(contours.size());
    for (size_t i = 0; i < contours.size(); i++) {
        cv::approxPolyDP(contours[i], approxContours[i], 3, true);
    }
    cv::Mat approxImage = cv::Mat::zeros(src.size(), CV_8UC3);
    for (size_t i = 0; i < approxContours.size(); i++) {
        cv::drawContours(approxImage, approxContours, (int)i, cv::Scalar(0, 0, 255), 2);
    }
    cv::imshow("近似輪郭", approxImage);
    cv::waitKey(0);
    return 0;
}

drawContours

輪郭を画像に描画する関数です。

色や太さを指定して、視覚的に輪郭を強調できます。

#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
    cv::Mat src = cv::imread("contour.png");
    if (src.empty()) {
        return -1;
    }
    std::vector<std::vector<cv::Point>> contours;
    cv::findContours(src, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
    // 輪郭を赤色で描画
    cv::drawContours(src, contours, -1, cv::Scalar(0, 0, 255), 2);
    cv::imshow("輪郭描画", src);
    cv::waitKey(0);
    return 0;
}

エッジ検出と輪郭抽出は、画像の形状や構造を理解するための重要な技術です。

これらを適切に使い分けることで、画像認識や解析の精度を向上させることができます。

次のステップでは、これらの技術を応用した高度な画像解析例について解説します。

モルフォロジー

モルフォロジー処理は、画像の形状や構造を解析・操作するための基本的な技術です。

特に、ノイズ除去や対象の抽出、形状の強調などに効果的です。

OpenCVでは、膨張、収縮、オープニング、クロージング、形態学的勾配などの操作を行う関数が用意されています。

これらを適切に使い分けることで、画像の前処理や特徴抽出を効率的に行えます。

膨張 / 収縮

膨張(Dilation)は、対象の白色部分を拡大させる操作です。

ノイズ除去や穴埋め、対象の拡大に利用されます。

一方、収縮(Erosion)は、対象の白色部分を縮小させる操作で、ノイズ除去や細線化に適しています。

#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
    cv::Mat src = cv::imread("binary_mask.png", cv::IMREAD_GRAYSCALE);
    if (src.empty()) {
        return -1;
    }
    // 膨張
    cv::Mat dilated;
    cv::Mat element = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3));
    cv::dilate(src, dilated, element);
    // 収縮
    cv::Mat eroded;
    cv::erode(src, eroded, element);
    cv::imshow("膨張", dilated);
    cv::imshow("収縮", eroded);
    cv::waitKey(0);
    return 0;
}

オープニング / クロージング

オープニング(Opening)は、収縮の後に膨張を行う操作で、ノイズ除去や小さな対象の除去に効果的です。

クロージング(Closing)は、膨張の後に収縮を行い、対象の穴埋めや細部の平滑化に使われます。

#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
    cv::Mat src = cv::imread("noisy_image.png", cv::IMREAD_GRAYSCALE);
    if (src.empty()) {
        return -1;
    }
    cv::Mat opening, closing;
    cv::Mat element = cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(5, 5));
    // オープニング
    cv::morphologyEx(src, opening, cv::MORPH_OPEN, element);
    // クロージング
    cv::morphologyEx(src, closing, cv::MORPH_CLOSE, element);
    cv::imshow("オープニング", opening);
    cv::imshow("クロージング", closing);
    cv::waitKey(0);
    return 0;
}

形態学的勾配

形態学的勾配は、膨張と収縮の差分を計算し、画像のエッジや輪郭を抽出します。

エッジ検出や形状の特徴抽出に有効です。

#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
    cv::Mat src = cv::imread("binary_mask.png", cv::IMREAD_GRAYSCALE);
    if (src.empty()) {
        return -1;
    }
    cv::Mat gradient;
    cv::Mat element = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3));
    // 形態学的勾配の計算
    cv::morphologyEx(src, gradient, cv::MORPH_GRADIENT, element);
    cv::imshow("形態学的勾配", gradient);
    cv::waitKey(0);
    return 0;
}

モルフォロジー処理は、画像の形状やノイズの性質に応じて適切に選択・組み合わせることで、画像の前処理や特徴抽出の精度を向上させることができます。

次のステップでは、これらの操作を応用した高度な画像解析例について解説します。

描画とテキスト

画像に対して図形や文字を描画する操作は、画像の注釈付けや視覚的な説明に不可欠です。

OpenCVでは、cv::linecv::rectanglecv::circlecv::ellipsecv::putTextなどの関数を用いて、さまざまな図形や文字を画像上に描画できます。

これらの関数を駆使することで、画像の内容をわかりやすく伝えることが可能です。

図形描画

line

cv::lineは、画像上に直線を描画します。

始点と終点の座標、色、線の太さを指定します。

#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
    // 白背景の画像作成
    cv::Mat img = cv::Mat::zeros(400, 400, CV_8UC3);
    img.setTo(cv::Scalar(255, 255, 255));
    // 線の描画(赤色、太さ2ピクセル)
    cv::line(img, cv::Point(50, 50), cv::Point(350, 350), cv::Scalar(0, 0, 255), 2);
    cv::imshow("線の描画", img);
    cv::waitKey(0);
    return 0;
}

この例では、画像の左上から右下へ赤い直線を引いています。

rectangle

cv::rectangleは、矩形を描画します。

左上の座標と右下の座標、色、線の太さ、塗りつぶしの有無を指定します。

#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
    cv::Mat img = cv::Mat::zeros(400, 400, CV_8UC3);
    img.setTo(cv::Scalar(255, 255, 255));
    // 矩形の描画(青色、線の太さ3)
    cv::rectangle(img, cv::Point(100, 100), cv::Point(300, 300), cv::Scalar(255, 0, 0), 3);
    // 塗りつぶし
    cv::rectangle(img, cv::Point(50, 50), cv::Point(150, 150), cv::Scalar(0, 255, 0), cv::FILLED);
    cv::imshow("矩形描画", img);
    cv::waitKey(0);
    return 0;
}

この例では、青色の枠と緑色の塗りつぶし矩形を描いています。

circle / ellipse

cv::circleは円を描き、cv::ellipseは楕円を描きます。

中心座標、半径または軸長、角度、色、線の太さを指定します。

#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
    cv::Mat img = cv::Mat::zeros(400, 400, CV_8UC3);
    img.setTo(cv::Scalar(255, 255, 255));
    // 円の描画(緑色、半径50)
    cv::circle(img, cv::Point(200, 200), 50, cv::Scalar(0, 255, 0), 3);
    // 楕円の描画(紫色、長軸80、短軸40、角度30度)
    cv::ellipse(img, cv::Point(200, 200), cv::Size(80, 40), 30, 0, 360, cv::Scalar(255, 0, 255), 2);
    cv::imshow("円と楕円", img);
    cv::waitKey(0);
    return 0;
}

これにより、円と楕円を画像上に描画できます。

テキスト描画

putText

cv::putTextは、画像に文字列を描画します。

文字列、位置、フォント、サイズ、色、太さ、線種を指定します。

#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
    cv::Mat img = cv::Mat::zeros(400, 600, CV_8UC3);
    img.setTo(cv::Scalar(255, 255, 255));
    // 文字列の描画
    cv::putText(img, "OpenCV Drawing", cv::Point(50, 200), cv::FONT_HERSHEY_SIMPLEX, 2, cv::Scalar(0, 0, 0), 3);
    cv::imshow("テキスト描画", img);
    cv::waitKey(0);
    return 0;
}

この例では、「OpenCV Drawing」という文字列を画像中央付近に大きく描いています。

フォントやサイズ、色を調整することで、見やすく効果的な注釈を付けることが可能です。

これらの描画機能を駆使することで、画像に対して視覚的な情報を付加し、解析やプレゼンテーションをよりわかりやすく行うことができます。

次のステップでは、これらの描画技術を応用した高度な画像編集例について解説します。

動画処理

動画処理は、連続した画像(フレーム)を扱うことで、動画の再生、編集、解析を行います。

OpenCVでは、cv::VideoCapturecv::VideoWriterを用いて、動画の読み込み・書き出しやフレームの処理を効率的に行うことが可能です。

これらのクラスを理解し、適切に使いこなすことで、動画編集やリアルタイム処理の幅が広がります。

VideoCapture

ファイル入力

cv::VideoCaptureは、動画ファイルやカメラからの映像をキャプチャするためのクラスです。

動画ファイルを読み込む場合は、ファイルパスを指定します。

#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
    // 動画ファイルのパス
    cv::VideoCapture cap("sample_video.mp4");
    if (!cap.isOpened()) {
        std::cerr << "動画ファイルを開けませんでした。" << std::endl;
        return -1;
    }
    cv::Mat frame;
    while (true) {
        // フレームの読み込み
        if (!cap.read(frame)) {
            break; // 動画の終端
        }
        cv::imshow("動画再生", frame);
        if (cv::waitKey(30) >= 0) break; // 30ms待ち、キー入力で停止
    }
    cap.release();
    cv::destroyAllWindows();
    return 0;
}

この例では、動画ファイルを順次読み込み、ウィンドウに表示しています。

カメラ入力

カメラからの映像を取得する場合は、cv::VideoCaptureにカメラのデバイス番号(通常は0)を指定します。

#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
    // カメラデバイス番号0を指定
    cv::VideoCapture cap(0);
    if (!cap.isOpened()) {
        std::cerr << "カメラを開けませんでした。" << std::endl;
        return -1;
    }
    cv::Mat frame;
    while (true) {
        cap >> frame; // フレームの取得
        if (frame.empty()) break;
        cv::imshow("カメラ映像", frame);
        if (cv::waitKey(30) >= 0) break; // 30ms待ち、キー入力で停止
    }
    cap.release();
    cv::destroyAllWindows();
    return 0;
}

このコードは、カメラからのライブ映像をリアルタイムで表示します。

実行できてDLL参照エラーも起きないのに表示できない場合、セキュリティソフトが干渉している場合があります。

VideoWriter

コーデック設定

cv::VideoWriterは、フレームを動画ファイルに書き出すためのクラスです。

コーデックやフレームレート、解像度を設定して動画を保存します。

#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
    // コーデックの設定(例:XVID)
    int fourcc = cv::VideoWriter::fourcc('X', 'V', 'I', 'D');
    double fps = 30.0;
    cv::Size frameSize(640, 480);
    // VideoWriterの作成
    cv::VideoWriter writer("output.avi", fourcc, fps, frameSize);
    if (!writer.isOpened()) {
        std::cerr << "動画ファイルの書き込みに失敗しました。" << std::endl;
        return -1;
    }
    // 例:静止画を何枚か書き出す
    for (int i = 0; i < 100; ++i) {
        cv::Mat frame(frameSize, CV_8UC3, cv::Scalar(0, 0, 255)); // 赤色のフレーム
        cv::putText(frame, "Frame " + std::to_string(i), cv::Point(50, 50),
                    cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(255, 255, 255), 2);
        writer.write(frame);
    }
    writer.release();
    return 0;
}

この例では、赤色の静止画を連続して書き込み、動画ファイルを作成しています。

コーデック設定

コーデックは、動画の圧縮方式を指定します。

cv::VideoWriter::fourcc関数を使って、FourCCコードを生成します。

一般的なコーデックには以下があります。

コーデックFourCCコード備考
MJPG‘M’, ‘J’, ‘P’, ‘G’JPEG圧縮、互換性高い
XVID‘X’, ‘V’, ‘I’, ‘D’高圧縮率、広く使われる
MP4V‘M’, ‘P’, ‘4’, ‘V’MP4形式に対応

コーデックの選択は、用途や保存容量、再生環境に応じて適切に行います。

フレームループ

動画のフレーム処理は、通常ループ内で行います。

cap.read()cap >> frameでフレームを取得し、必要な処理を行った後、imshowで表示します。

#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
    cv::VideoCapture cap("sample_video.mp4");
    if (!cap.isOpened()) {
        std::cerr << "動画を開けませんでした。" << std::endl;
        return -1;
    }
    cv::Mat frame;
    while (true) {
        cap >> frame; // フレームの取得
        if (frame.empty()) break;
        // ここに画像処理や解析のコードを記述
        cv::imshow("動画処理", frame);
        if (cv::waitKey(30) >= 0) break; // 30ms待ち、キー入力で停止
    }
    cap.release();
    cv::destroyAllWindows();
    return 0;
}

このループは、動画の全フレームを順次処理し、リアルタイムまたはバッチ処理を実現します。

動画処理は、映像の解析や編集、リアルタイム処理において非常に重要です。

これらの基本操作を理解し、適切に組み合わせることで、多彩な動画アプリケーションを構築できます。

特徴量検出とマッチング

画像認識や3D再構築、画像登録など、多くのコンピュータビジョンタスクにおいて、特徴点の検出とそれらのマッチングは重要な役割を果たします。

OpenCVでは、コーナー検出、特徴記述子、そしてそれらのマッチングを行うための多彩なアルゴリズムが提供されています。

これらを理解し適切に利用することで、画像間の対応点抽出や物体認識の精度を向上させることが可能です。

コーナー検出

Harris

Harrisコーナー検出は、画像内のコーナー(角)を検出する古典的な手法です。

局所的な輝度変化を利用し、コーナーの位置を特定します。

#include <iostream>
#include <opencv2/opencv.hpp>

int main() {
    // カラーで読み込む
    cv::Mat src = cv::imread("sample.png", cv::IMREAD_COLOR);
    if (src.empty()) {
        return -1;
    }

    // グレースケールに変換してコーナー検出に使用
    cv::Mat gray;
    cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY);

    // Harrisコーナー検出
    cv::Mat dst, dstNorm, dstNormScaled;
    dst = cv::Mat::zeros(gray.size(), CV_32FC1);
    cv::cornerHarris(gray, dst, 2, 1, 0.05);

    // 正規化
    cv::normalize(dst, dstNorm, 0, 255, cv::NORM_MINMAX);
    cv::convertScaleAbs(dstNorm, dstNormScaled);

    // コーナーをマーク(カラー画像上に赤い点を描画)
    for (int i = 0; i < dstNorm.rows; i++) {
        for (int j = 0; j < dstNorm.cols; j++) {
            if ((int)dstNorm.at<float>(i, j) > 200) {
                cv::circle(src, cv::Point(j, i), 5, cv::Scalar(0, 0, 255), 2);
            }
        }
    }

    cv::imshow("Harrisコーナー", src);
    cv::waitKey(0);
    return 0;
}

この例では、閾値を超えたコーナー点に赤い円を描画しています。

Shi-Tomasi

Shi-Tomasiは、コーナー検出の改良版で、最も良い特徴点を選択します。

cv::goodFeaturesToTrack関数を使います。

#include <iostream>
#include <opencv2/opencv.hpp>

int main() {
    // カラーで読み込む
    cv::Mat src = cv::imread("sample.png", cv::IMREAD_COLOR);
    if (src.empty()) {
        return -1;
    }

    // グレースケールに変換してコーナー検出に使用
    cv::Mat gray;
    cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY);

    std::vector<cv::Point2f> corners;
    int maxCorners = 100;
    double qualityLevel = 0.01;
    double minDistance = 10;
    // Shi-Tomasiコーナー検出
    cv::goodFeaturesToTrack(gray, corners, maxCorners, qualityLevel,
                            minDistance);
    // 検出したコーナーにマーク
    cv::Mat display;
    cv::cvtColor(gray, display, cv::COLOR_GRAY2BGR);
    for (size_t i = 0; i < corners.size(); i++) {
        cv::circle(display, corners[i], 5, cv::Scalar(0, 255, 0), 2);
    }
    cv::imshow("Shi-Tomasiコーナー", display);
    cv::waitKey(0);
    return 0;
}

この方法は、良質な特徴点を効率的に抽出でき、追跡やマッチングに適しています。

特徴記述子

ORB / SIFT / SURF

特徴点の位置だけでなく、その局所的なパターンを記述するための特徴記述子も重要です。

OpenCVでは、ORB、SIFT、SURFといった代表的な記述子が利用可能です。

  • ORB:高速で特許非対応の特徴記述子。リアルタイムアプリケーションに適しています
  • SIFT:高精度だが計算コストが高く、特許の関係で制限があったが、OpenCVの最新バージョンでは無料で利用可能です
  • SURF:SIFTに似た高性能な記述子だが、特許の関係で制限があります
#include <opencv2/opencv.hpp>
#include <vector>
int main() {
    cv::Mat img1 = cv::imread("image1.jpg", cv::IMREAD_GRAYSCALE);
    cv::Mat img2 = cv::imread("image2.jpg", cv::IMREAD_GRAYSCALE);
    if (img1.empty() || img2.empty()) {
        return -1;
    }
    // ORB検出器
    cv::Ptr<cv::ORB> orb = cv::ORB::create();
    std::vector<cv::KeyPoint> keypoints1, keypoints2;
    cv::Mat descriptors1, descriptors2;
    // 特徴点と記述子の抽出
    orb->detectAndCompute(img1, cv::noArray(), keypoints1, descriptors1);
    orb->detectAndCompute(img2, cv::noArray(), keypoints2, descriptors2);
    // 以降、マッチング処理へ
    return 0;
}

これらの記述子は、検出した特徴点の局所パターンを表現し、マッチングの精度を向上させます。

マッチング

BFMatcher / FLANN

特徴点のマッチングは、記述子間の距離を計算し、最も類似した点を対応付ける操作です。

OpenCVでは、Brute-Force(BF)マッチャーと、より高速なFLANN(Fast Library for Approximate Nearest Neighbors)マッチャーが利用できます。

#include <opencv2/opencv.hpp>
#include <vector>

int main() {
    // 画像の読み込み(グレースケール)
    cv::Mat img1 = cv::imread("image1.jpg", cv::IMREAD_GRAYSCALE);
    cv::Mat img2 = cv::imread("image2.jpg", cv::IMREAD_GRAYSCALE);
    if (img1.empty() || img2.empty()) {
        std::cerr << "画像が読み込めませんでした。" << std::endl;
        return -1;
    }

    // ORBによる特徴点検出と記述子の計算
    cv::Ptr<cv::ORB> orb = cv::ORB::create();
    std::vector<cv::KeyPoint> keypoints1, keypoints2;
    cv::Mat descriptors1, descriptors2;
    orb->detectAndCompute(img1, cv::noArray(), keypoints1, descriptors1);
    orb->detectAndCompute(img2, cv::noArray(), keypoints2, descriptors2);

    // マッチャーの作成
    cv::BFMatcher matcher(cv::NORM_HAMMING);
    std::vector<cv::DMatch> matches;
    matcher.match(descriptors1, descriptors2, matches);

    // マッチ結果を描画
    cv::Mat img_matches;
    cv::drawMatches(img1, keypoints1, img2, keypoints2, matches, img_matches);

    // 結果の表示
    cv::imshow("Matches", img_matches);
    cv::waitKey(0);

    return 0;
}

また、cv::FlannBasedMatcherは、大規模な特徴量セットに対して高速に近似的なマッチングを行います。

#include <iostream>
#include <opencv2/opencv.hpp>
#include <vector>

int main() {
    // 画像読み込み
    cv::Mat img1 = cv::imread("image1.jpg", cv::IMREAD_GRAYSCALE);
    cv::Mat img2 = cv::imread("image2.jpg", cv::IMREAD_GRAYSCALE);
    if (img1.empty() || img2.empty()) {
        std::cerr << "画像が読み込めませんでした。" << std::endl;
        return -1;
    }

    // ORB検出器で特徴点と記述子を取得
    cv::Ptr<cv::ORB> orb = cv::ORB::create();
    std::vector<cv::KeyPoint> keypoints1, keypoints2;
    cv::Mat descriptors1, descriptors2;
    orb->detectAndCompute(img1, cv::noArray(), keypoints1, descriptors1);
    orb->detectAndCompute(img2, cv::noArray(), keypoints2, descriptors2);

    // FLANNに合わせてfloat型に変換
    cv::Mat descriptors1_float, descriptors2_float;
    descriptors1.convertTo(descriptors1_float, CV_32F);
    descriptors2.convertTo(descriptors2_float, CV_32F);

    // FLANNによるマッチング
    cv::FlannBasedMatcher matcher;
    std::vector<cv::DMatch> matches;
    matcher.match(descriptors1_float, descriptors2_float, matches);

    // マッチ結果を描画
    cv::Mat img_matches;
    cv::drawMatches(img1, keypoints1, img2, keypoints2, matches, img_matches);

    // 表示
    cv::imshow("FLANN Matches", img_matches);
    cv::waitKey(0);

    return 0;
}

これらのマッチング手法を適切に選択し、特徴点の対応付けを行うことで、画像間の幾何関係や物体認識の精度を向上させることができます。

特徴量検出とマッチングは、画像解析の基礎技術です。

これらを理解し、適切に組み合わせることで、多様なコンピュータビジョンタスクに応用可能です。

テンプレートマッチング

テンプレートマッチングは、画像内の特定のパターンや部分を検出するための基本的な手法です。

cv::matchTemplate関数を用いて、テンプレート画像と探索対象画像の類似度を計算し、最も一致する位置を特定します。

これにより、特定の物体やパターンの位置を効率的に検出できます。

matchTemplate

cv::matchTemplateは、テンプレート画像と探索画像の局所的な類似度を計算します。

計算結果は、類似度マップ(結果画像)として出力され、各ピクセルはテンプレートと探索画像の対応位置の類似度を表します。

#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
    cv::Mat img = cv::imread("search_image.jpg", cv::IMREAD_GRAYSCALE);
    cv::Mat templ = cv::imread("template.jpg", cv::IMREAD_GRAYSCALE);
    if (img.empty() || templ.empty()) {
        return -1;
    }
    cv::Mat result;
    // マッチング手法の選択
    cv::matchTemplate(img, templ, result, cv::TM_CCOEFF_NORMED);
    // 最良一致点の検出
    double minVal, maxVal;
    cv::Point minLoc, maxLoc;
    cv::minMaxLoc(result, &minVal, &maxVal, &minLoc, &maxLoc);
    // 最良点に矩形を描画
    cv::rectangle(img, maxLoc, cv::Point(maxLoc.x + templ.cols, maxLoc.y + templ.rows),
                  cv::Scalar(0, 0, 255), 2);
    cv::imshow("マッチング結果", img);
    cv::waitKey(0);
    return 0;
}

この例では、cv::matchTemplateの第4引数にcv::TM_CCOEFF_NORMEDを指定し、正規化された相関係数法を用いています。

手法の選択

cv::matchTemplateは複数の手法をサポートしており、用途や画像の性質に応じて選択します。

手法説明特徴
cv::TM_SQDIFF差の二乗和小さい値が良い一致を示す
cv::TM_SQDIFF_NORMED正規化差の二乗和小さい値が良い一致を示す
cv::TM_CCORR相関係数大きい値が良い一致を示す
cv::TM_CCORR_NORMED正規化相関係数大きい値が良い一致を示す
cv::TM_CCOEFF相関係数大きい値が良い一致を示す
cv::TM_CCOEFF_NORMED正規化相関係数大きい値が良い一致を示す

一般的には、cv::TM_CCOEFF_NORMEDcv::TM_CCORR_NORMEDが広く使われており、ノイズや照明変化に対して比較的頑健です。

結果解析

cv::matchTemplateの出力は、類似度マップです。

最も高い値または最も低い値を持つ位置を検出し、最良のマッチング位置を特定します。

double minVal, maxVal;
cv::Point minLoc, maxLoc;
// 最小値・最大値とその位置を取得
cv::minMaxLoc(result, &minVal, &maxVal, &minLoc, &maxLoc);
// どちらを使うかは手法による
// 例:正規化相関の場合はmaxLocが最良位置
cv::Point matchLoc = maxLoc;
// マッチング位置に矩形を描画
cv::rectangle(img, matchLoc, cv::Point(matchLoc.x + templ.cols, matchLoc.y + templ.rows),
              cv::Scalar(0, 255, 0), 2);

また、閾値を設定して、類似度が一定以上の候補だけを抽出することも可能です。

double threshold = 0.8;
for (int y = 0; y < result.rows; y++) {
    for (int x = 0; x < result.cols; x++) {
        if (result.at<float>(y, x) >= threshold) {
            cv::rectangle(img, cv::Point(x, y),
                          cv::Point(x + templ.cols, y + templ.rows),
                          cv::Scalar(255, 0, 0), 2);
        }
    }
}

このように、結果の解析と閾値設定により、複数の候補やノイズに対して柔軟に対応できます。

テンプレートマッチングは、特定のパターンや物体の位置検出に有効な手法です。

適切な手法選択と結果解析を行うことで、精度の高い検出を実現できます。

オプティカルフロー

オプティカルフローは、動画や連続画像において、各ピクセルの動きを推定する技術です。

動きの方向や速度を計算することで、物体追跡や動き解析、背景差分など多くの応用に利用されます。

OpenCVでは、Lucas-Kanade法とFarneback法の2つの代表的なアルゴリズムが提供されており、それぞれの特徴と使い方について解説します。

Lucas-Kanade法

Lucas-Kanade法は、局所的な動きの推定に適した古典的なアルゴリズムです。

小さな領域内での動きを仮定し、ピラミッド構造を用いることで、広範囲の動きにも対応可能です。

calcOpticalFlowPyrLK

cv::calcOpticalFlowPyrLKは、Lucas-Kanade法のピラミッド版を実装した関数です。

特徴点の動きを追跡するのに適しており、事前に検出した特徴点の追跡に使われます。

#include <opencv2/opencv.hpp>
#include <vector>
#include <iostream>
int main() {
    cv::Mat prevImg = cv::imread("frame1.jpg", cv::IMREAD_GRAYSCALE);
    cv::Mat nextImg = cv::imread("frame2.jpg", cv::IMREAD_GRAYSCALE);
    if (prevImg.empty() || nextImg.empty()) {
        return -1;
    }
    // 特徴点の検出(例:Shi-Tomasi)
    std::vector<cv::Point2f> prevPts;
    cv::goodFeaturesToTrack(prevImg, prevPts, 100, 0.3, 7);
    // 追跡結果格納用
    std::vector<cv::Point2f> nextPts;
    std::vector<uchar> status;
    std::vector<float> err;
    // オプティカルフローの計算
    cv::calcOpticalFlowPyrLK(prevImg, nextImg, prevPts, nextPts, status, err);
    // 結果の描画
    cv::Mat display;
    cv::cvtColor(prevImg, display, cv::COLOR_GRAY2BGR);
    for (size_t i = 0; i < prevPts.size(); i++) {
        if (status[i]) {
            cv::line(display, prevPts[i], nextPts[i], cv::Scalar(0, 255, 0), 2);
            cv::circle(display, nextPts[i], 3, cv::Scalar(0, 0, 255), -1);
        }
    }
    cv::imshow("Lucas-Kanade Optical Flow", display);
    cv::waitKey(0);
    return 0;
}

この例では、特徴点の追跡結果を線と点で可視化しています。

2つの画像のサイズが一致している必要があります。

statusが1の場合、その点は追跡成功を示します。

Farneback法

Farneback法は、密なオプティカルフロー推定に適したアルゴリズムです。

画像全体の動き場を推定し、各ピクセルの動きベクトルを計算します。

動きの滑らかさや連続性を仮定し、全体的な動きの解析に向いています。

#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
    cv::Mat prevFrame = cv::imread("frame1.jpg", cv::IMREAD_GRAYSCALE);
    cv::Mat nextFrame = cv::imread("frame2.jpg", cv::IMREAD_GRAYSCALE);
    if (prevFrame.empty() || nextFrame.empty()) {
        return -1;
    }
    cv::Mat flow;
    // Farneback法による密なフロー推定
    cv::calcOpticalFlowFarneback(prevFrame, nextFrame, flow, 0.5, 3, 15, 3, 5, 1.2, 0);
    // フローの可視化
    cv::Mat flowParts[2];
    cv::split(flow, flowParts);
    cv::Mat magnitude, angle;
    cv::cartToPolar(flowParts[0], flowParts[1], magnitude, angle, true);
    // 角度をヒートマップに変換
    cv::Mat hsv[3], hsvImage, bgrImage;
    hsv[0] = angle;
    cv::normalize(magnitude, magnitude, 0, 1, cv::NORM_MINMAX);
    hsv[1] = cv::Mat::ones(angle.size(), CV_32F);
    hsv[2] = magnitude;
    cv::merge(hsv, 3, hsvImage);
    hsvImage.convertTo(hsvImage, CV_8U, 255);
    cv::cvtColor(hsvImage, bgrImage, cv::COLOR_HSV2BGR);
    cv::imshow("Farneback Optical Flow", bgrImage);
    cv::waitKey(0);
    return 0;
}

この例では、動きの角度と大きさをHSV色空間に変換し、視覚的に動き場を表現しています。

2つの画像のサイズが一致している必要があります。

Farneback法は、背景動きや全体の動き解析に適しています。

オプティカルフローは、動きの解析や追跡、背景差分など多くの応用に役立ちます。

Lucas-Kanade法は局所的な動きの追跡に、Farneback法は全体的な動きの推定に適しており、用途に応じて使い分けることが重要です。

カメラキャリブレーションと歪み補正

カメラキャリブレーションは、カメラの内部パラメータ(焦点距離、光学中心、歪み係数など)を推定し、画像の歪みを補正するための重要な工程です。

歪み補正により、画像の幾何学的な歪みを除去し、正確な計測や画像解析を可能にします。

OpenCVでは、チェスボードパターンを用いたキャリブレーション手法と、歪み補正の関数が提供されています。

チェスボード検出

findChessboardCorners

cv::findChessboardCornersは、画像内のチェスボードパターンのコーナー点を検出します。

検出されたコーナーは、キャリブレーションの入力として使用されます。

#include <opencv2/opencv.hpp>
#include <vector>
#include <iostream>
int main() {
    cv::Mat img = cv::imread("chessboard.jpg");
    if (img.empty()) {
        return -1;
    }
    cv::Mat gray;
    cv::cvtColor(img, gray, cv::COLOR_BGR2GRAY);
    // チェスボードのコーナー検出
    std::vector<cv::Point2f> corners;
    bool found = cv::findChessboardCorners(gray, cv::Size(9, 6), corners,
                                             cv::CALIB_CB_ADAPTIVE_THRESH | cv::CALIB_CB_NORMALIZE_IMAGE);
    if (found) {
        // コーナーを描画
        cv::drawChessboardCorners(img, cv::Size(9, 6), corners, found);
        cv::imshow("チェスボード検出", img);
        cv::waitKey(0);
    } else {
        std::cout << "チェスボードのコーナーが検出できませんでした。" << std::endl;
    }
    return 0;
}

この例では、9×6のチェスボードコーナーを検出し、画像に描画しています。

calibrateCamera

cv::calibrateCameraは、複数の画像からカメラの内部パラメータと歪み係数を推定します。

事前に検出したチェスボードのコーナーと、実世界の座標を対応付けて計算します。

#include <opencv2/opencv.hpp>
#include <vector>
#include <iostream>
int main() {
    // 画像ごとのコーナー点と対応する3D座標
    std::vector<std::vector<cv::Point3f>> objectPoints; // 実世界座標
    std::vector<std::vector<cv::Point2f>> imagePoints;  // 画像座標
    // 例:1枚目の画像のコーナー点
    std::vector<cv::Point3f> objPts;
    for (int i = 0; i < 6; i++) {
        for (int j = 0; j < 9; j++) {
            objPts.push_back(cv::Point3f(j, i, 0));
        }
    }
    objectPoints.push_back(objPts);
    // 画像から検出したコーナー点
    std::vector<cv::Point2f> imgPts;
    // 例:findChessboardCornersで検出した点を設定
    // 省略:実際には複数画像から検出した点を格納
    // カメラ行列と歪み係数の推定
    cv::Mat cameraMatrix, distCoeffs;
    std::vector<cv::Mat> rvecs, tvecs;
    cv::calibrateCamera(objectPoints, imagePoints, cv::Size(640, 480),
                        cameraMatrix, distCoeffs, rvecs, tvecs);
    std::cout << "カメラ行列:\n" << cameraMatrix << std::endl;
    std::cout << "歪み係数:\n" << distCoeffs << std::endl;
    return 0;
}

この例では、複数画像のコーナー点からカメラの内部パラメータと歪み係数を推定しています。

undistort

cv::undistortは、推定されたカメラパラメータを用いて、歪んだ画像を補正します。

#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
    cv::Mat distorted = cv::imread("distorted.jpg");
    if (distorted.empty()) {
        return -1;
    }
    // 事前に推定したカメラ行列と歪み係数
    cv::Mat cameraMatrix = (cv::Mat_<double>(3,3) << 1000, 0, 320,
                                                      0, 1000, 240,
                                                      0, 0, 1);
    cv::Mat distCoeffs = (cv::Mat_<double>(5,1) << -0.2, 0.1, 0, 0, 0);
    cv::Mat undistorted;
    cv::undistort(distorted, undistorted, cameraMatrix, distCoeffs);
    cv::imshow("歪み補正後", undistorted);
    cv::waitKey(0);
    return 0;
}

この操作により、歪みの少ない正確な画像を得ることができます。

カメラキャリブレーションと歪み補正は、計測や画像解析の精度向上に不可欠です。

正確なキャリブレーションを行い、歪み補正を適用することで、より信頼性の高い画像処理結果を得ることが可能です。

DNNモジュール

OpenCVのDNN(Deep Neural Network)モジュールは、事前に学習済みの深層学習モデルを読み込み、画像や映像に対して推論を行うための機能を提供します。

これにより、画像分類、物体検出、セグメンテーションなどの高度なタスクを比較的容易に実現できます。

モデル読み込みと設定

モデルの読み込みには、cv::dnn::readNetFromXXX関数を使用します。

モデルの種類に応じて、Caffe、TensorFlow、Darknet(YOLO)、ONNXなどのフォーマットに対応した関数があります。

#include <opencv2/dnn.hpp>
#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
    // Caffeモデルの読み込み例
    cv::dnn::Net net = cv::dnn::readNetFromCaffe("deploy.prototxt", "weights.caffemodel");
    if (net.empty()) {
        std::cerr << "モデルの読み込みに失敗しました。" << std::endl;
        return -1;
    }
    // 必要に応じて推論のバックエンドやターゲットを設定
    net.setPreferableBackend(cv::dnn::DNN_BACKEND_DEFAULT);
    net.setPreferableTarget(cv::dnn::DNN_TARGET_CPU);
    return 0;
}

モデルの読み込み後、推論に適した入力サイズや前処理のパラメータを設定します。

推論実行

画像を入力として、前処理を行った後、net.forward()を呼び出して推論を行います。

#include <opencv2/dnn.hpp>
#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
    // 画像の読み込み
    cv::Mat img = cv::imread("input.jpg");
    if (img.empty()) {
        return -1;
    }
    // 前処理:リサイズ、正規化
    cv::Mat blob = cv::dnn::blobFromImage(img, 1.0, cv::Size(224, 224),
                                            cv::Scalar(104, 117, 123), false, false);
    // ネットワークに入力
    cv::dnn::Net net = cv::dnn::readNetFromCaffe("deploy.prototxt", "weights.caffemodel");
    net.setInput(blob);
    // 推論実行
    cv::Mat output = net.forward();
    // 出力結果の処理は次のセクションで
    return 0;
}

この例では、画像を適切なサイズにリサイズし、平均値の正規化を行った後、推論を実行しています。

出力処理

推論結果は、多くの場合、分類スコアや検出バウンディングボックスの情報として出力されます。

出力の形式はモデルによって異なるため、モデルの仕様に応じて解析します。

例:画像分類の場合

// 出力はクラススコアのベクトル
cv::Point classIdPoint;
double confidence;
cv::minMaxLoc(output.reshape(1, 1), 0, &confidence, 0, &classIdPoint);
int classId = classIdPoint.x;
std::cout << "予測クラスID: " << classId << ", 信頼度: " << confidence << std::endl;

例:物体検出の場合(YOLO)

// 出力は検出結果のリスト
for (int i = 0; i < output.rows; ++i) {
    float confidence = output.at<float>(i, 2);
    if (confidence > 0.5) {
        int left = static_cast<int>(output.at<float>(i, 3) * img.cols);
        int top = static_cast<int>(output.at<float>(i, 4) * img.rows);
        int right = static_cast<int>(output.at<float>(i, 5) * img.cols);
        int bottom = static_cast<int>(output.at<float>(i, 6) * img.rows);
        cv::rectangle(img, cv::Point(left, top), cv::Point(right, bottom), cv::Scalar(0, 255, 0), 2);
    }
}
cv::imshow("検出結果", img);
cv::waitKey(0);

出力の解析は、モデルの種類とタスクに応じて適切に行う必要があります。

OpenCVのDNNモジュールを活用することで、深層学習モデルを簡単に組み込み、リアルタイムやバッチ処理で高度な画像解析を実現できます。

モデルの選択と出力解析を適切に行うことが、高精度なアプリケーション構築の鍵となります。

GPUアクセラレーション

OpenCVのCUDAモジュールは、GPUの並列処理能力を活用して、画像処理の高速化を実現します。

これにより、大規模な画像データやリアルタイム処理において、処理時間を大幅に短縮できます。

CUDA対応のGPUを持つ環境で、cv::cuda名前空間の機能を利用して、画像処理を効率化します。

ただし、CUDAに対応したOpenCVをビルドする必要があります。

cudaモジュール

cv::cudaモジュールは、GPU上で動作するさまざまな画像処理関数やデータ構造を提供します。

これには、画像の読み書き、フィルタリング、変換、特徴抽出など、多彩な機能が含まれています。

#include <opencv2/opencv.hpp>
#include <opencv2/cudaimgproc.hpp>
#include <opencv2/cudaarithm.hpp>
#include <iostream>
int main() {
    // GPUが利用可能か確認
    int numDevices = cv::cuda::getCudaEnabledDeviceCount();
    if (numDevices == 0) {
        std::cerr << "CUDA対応GPUが見つかりません。" << std::endl;
        return -1;
    }
    // デバイスの設定
    cv::cuda::setDevice(0);
    // 画像の読み込み(CPU側)
    cv::Mat img = cv::imread("sample.jpg");
    if (img.empty()) {
        return -1;
    }
    // 画像をGPUメモリにアップロード
    cv::cuda::GpuMat gpuImg;
    gpuImg.upload(img);
    // 例:GPU上でのグレースケール変換
    cv::cuda::GpuMat gpuGray;
    cv::cuda::cvtColor(gpuImg, gpuGray, cv::COLOR_BGR2GRAY);
    // 結果をダウンロード
    cv::Mat gray;
    gpuGray.download(gray);
    cv::imshow("GPUグレースケール", gray);
    cv::waitKey(0);
    return 0;
}

この例では、GPUを用いて画像のカラー変換を高速に行っています。

GpuMat操作

cv::cuda::GpuMatは、GPUメモリ上に格納される画像データを表すクラスです。

CPUのcv::Matと異なり、GPU上での高速処理を可能にします。

  • アップロードupload()メソッドでCPU側のcv::MatからGPU側のGpuMatへデータを転送
  • ダウンロードdownload()メソッドでGPU側のGpuMatからCPU側のcv::Matへデータを取得
  • 操作cv::cudaの関数を用いて、GPU上で直接画像処理を行う
// 例:画像のアップロードとダウンロード
cv::Mat cpuImg = cv::imread("sample.jpg");
cv::cuda::GpuMat gpuImg;
gpuImg.upload(cpuImg);
// 何らかの処理(例:平滑化)
cv::cuda::blur(gpuImg, gpuImg, cv::Size(5, 5));
// ダウンロード
cv::Mat result;
gpuImg.download(result);

GPUメモリ上での操作は、データの転送コストを除けば、処理速度の向上に直結します。

主要関数利用例

GPUを用いた画像処理の代表的な関数例を示します。

#include <opencv2/opencv.hpp>
#include <opencv2/cudaimgproc.hpp>
#include <opencv2/cudaarithm.hpp>
int main() {
    cv::Mat img = cv::imread("sample.jpg");
    if (img.empty()) return -1;
    cv::cuda::GpuMat gpuImg, gpuGray, gpuEdge;
    // GPUにアップロード
    gpuImg.upload(img);
    // グレースケール変換
    cv::cuda::cvtColor(gpuImg, gpuGray, cv::COLOR_BGR2GRAY);
    // ガウシアンブラー
    cv::cuda::GaussianBlur(gpuGray, gpuGray, cv::Size(7, 7), 1.5);
    // Cannyエッジ検出
    cv::cuda::Canny(gpuGray, gpuEdge, 50, 150);
    // 結果をダウンロード
    cv::Mat edge;
    gpuEdge.download(edge);
    cv::imshow("GPUエッジ検出", edge);
    cv::waitKey(0);
    return 0;
}

この例では、GPU上での画像の平滑化とエッジ検出を行い、結果を取得しています。

これにより、処理時間を大幅に短縮でき、リアルタイム処理に適しています。

GPUアクセラレーションを活用することで、大規模データやリアルタイムアプリケーションの性能を向上させることが可能です。

適切な関数とデータ管理を行い、効率的な画像処理を実現しましょう。

パフォーマンス最適化

画像処理やコンピュータビジョンのアプリケーションでは、処理速度の向上が重要です。

OpenCVは、並列処理やメモリ管理、非同期処理などの最適化技術をサポートしており、これらを適切に活用することで、リアルタイム処理や大規模データの効率的な処理が可能となります。

並列処理

TBB / OpenMP

TBB(Threading Building Blocks)OpenMPは、C++の並列処理を容易にするライブラリです。

OpenCVはこれらのライブラリと連携して、画像処理の並列化をサポートしています。

  • TBBは、タスクベースの並列処理を実現し、動的な負荷分散やスケジューリングに優れています。OpenCVは、cv::parallel_for_を通じてTBBと連携し、ループ処理の並列化を行います
#include <opencv2/opencv.hpp>
#include <tbb/parallel_for.h>
#include <tbb/blocked_range.h>
void processBlock(int start, int end, cv::Mat& image) {
    for (int i = start; i < end; ++i) {
        // 例:画像の各行に対して処理
        // ここに処理内容を記述
    }
}
int main() {
    cv::Mat image = cv::Mat::zeros(1000, 1000, CV_8UC3);
    int numThreads = cv::getNumberOfCPUs();
    cv::parallel_for_(cv::Range(0, image.rows), [&](const cv::Range& range) {
        processBlock(range.start, range.end, image);
    });
    return 0;
}
  • OpenMPは、#pragma ompディレクティブを用いて簡単にループの並列化が可能です
#include <opencv2/opencv.hpp>
#include <omp.h>
int main() {
    cv::Mat image = cv::Mat::zeros(1000, 1000, CV_8UC3);
    #pragma omp parallel for
    for (int i = 0; i < image.rows; ++i) {
        // 例:各行に対して並列処理
        // ここに処理内容を記述
    }
    return 0;
}

これらの技術を用いることで、CPUの複数コアを効率的に活用し、処理時間を短縮できます。

メモリ管理

効率的なメモリ管理は、パフォーマンス最適化の基本です。

OpenCVでは、画像データのメモリ割り当てと解放を自動化していますが、大規模な処理やGPUとの連携では、明示的なメモリ管理も重要です。

  • メモリプール:頻繁に画像の生成と破棄を行う場合、メモリプールを利用してメモリの再利用を促進し、オーバーヘッドを削減します
  • バッファの再利用:一時的な画像や結果を格納するバッファを使い回すことで、メモリの断片化や無駄な割り当てを防ぎます
cv::Mat buffer1, buffer2;
// 既存のバッファを再利用
cv::resize(inputImage, buffer1, cv::Size(640, 480));
cv::GaussianBlur(buffer1, buffer2, cv::Size(5, 5), 1.5);
  • GPUメモリ管理cv::cuda::GpuMatを使用する場合、不要になったGPUメモリはrelease()を呼び出して解放します
cv::cuda::GpuMat gpuMat;
gpuMat.upload(cpuMat);
// 処理後
gpuMat.release();

適切なメモリ管理は、メモリリークやパフォーマンス低下を防ぎ、安定した動作を実現します。

非同期処理

非同期処理は、処理の待ち時間を削減し、アプリケーションの応答性を向上させるために有効です。

OpenCVは、cv::AsyncArraycv::parallel_for_といった非同期処理の仕組みを提供しています。

  • 非同期画像読み込み・書き込みcv::imreadAsync()cv::imwriteAsync()は、バックグラウンドで画像の入出力を行い、メインスレッドの処理を妨げません
  • 非同期処理の例
#include <opencv2/opencv.hpp>
#include <future>
int main() {
    auto future = std::async(std::launch::async, []() {
        cv::Mat img = cv::imread("large_image.jpg");
        // 画像処理
        cv::GaussianBlur(img, img, cv::Size(15, 15), 0);
        cv::imwrite("processed.jpg", img);
    });
    // 他の処理を並行して実行
    // ...
    future.get(); // 非同期処理の完了を待つ
    return 0;
}
  • GPUの非同期処理cv::cuda::Streamを用いて、GPU処理の非同期実行や並列化を行います
cv::cuda::Stream stream;
cv::cuda::GpuMat gpuImg, gpuResult;
gpuImg.upload(input, stream);
cv::cuda::GaussianBlur(gpuImg, gpuResult, cv::Size(5, 5), 1.5, 0, stream);
stream.waitForCompletion();

非同期処理を適切に設計することで、処理待ち時間を削減し、全体のパフォーマンスを向上させることが可能です。

これらの最適化技術を組み合わせて活用することで、OpenCVアプリケーションの処理速度と効率性を大きく向上させることができます。

適切な並列化、メモリ管理、非同期処理を導入し、より高速で安定したシステムを構築しましょう。

まとめ

この記事では、OpenCVを用いた画像・動画処理の基本技術と最適化手法について解説しました。

画像処理の基本操作から、GPUアクセラレーションや並列処理、メモリ管理、非同期処理まで、多彩な技術を理解し活用することで、処理速度の向上や高精度な解析が可能となります。

これらを駆使して、効率的で高性能なコンピュータビジョンアプリケーションを構築できます。

関連記事

Back to top button
目次へ