OpenCV

【C++】OpenCVサンプルコード:画像読み込み・エッジ検出・カメラキャプチャ・顔検出

C++とOpenCVを使ったサンプルコードを用意していて、画像読み込み、カメラキャプチャ、エッジ検出、動画再生、顔検出などを手軽に試せるので、開発や学習に活用できます。

ライブラリと共通設定

OpenCVを用いた画像処理プログラムを作成する際には、まず必要なライブラリのインクルードと基本的な設定を行います。

これにより、コードの可読性や保守性が向上し、エラーの発生を抑えることができます。

OpenCVヘッダーのインクルード

OpenCVの機能を利用するためには、opencv2/opencv.hppヘッダーをインクルードします。

このヘッダーには、画像処理に必要な基本的なクラスや関数がすべて含まれています。

#include <opencv2/opencv.hpp>

この一行をプログラムの先頭に記述するだけで、画像の読み込み、表示、変換、エッジ検出、顔検出など、多くの機能を利用できるようになります。

名前空間とUsing宣言

OpenCVの関数やクラスはcv名前空間に属しています。

毎回cv::を付けてアクセスするのは冗長になるため、using namespace cv;を記述しておくと便利です。

using namespace cv;

ただし、using namespaceは名前の衝突や可読性の低下を招く場合もあるため、必要に応じて使用します。

特に小規模なサンプルコードや学習用のプログラムでは積極的に使うことが推奨されます。

エラーハンドリング

画像や動画の読み込み、カスケード分類器のロードなど、外部リソースに依存する処理ではエラーが発生しやすいため、適切なエラーハンドリングを行います。

例えば、画像の読み込みに失敗した場合はMatオブジェクトが空になるため、そのチェックを行います。

cv::Mat img = imread("画像ファイルのパス");
if (img.empty()) {
    std::cerr << "画像の読み込みに失敗しました。" << std::endl;
    return -1;
}

同様に、カスケード分類器のロードも失敗した場合はエラーを通知し、プログラムを終了させます。

cv::CascadeClassifier face_cascade;
if (!face_cascade.load("haarcascade_frontalface_default.xml")) {
    std::cerr << "カスケード分類器の読み込みに失敗しました。" << std::endl;
    return -1;
}

これらのエラーチェックは、プログラムの安定性を高めるだけでなく、問題の早期発見にも役立ちます。

これらの設定を行うことで、OpenCVを使った画像処理プログラムの土台が整います。

画像読み込み

画像をプログラム内で扱うためには、まず画像ファイルを読み込む必要があります。

OpenCVではimread関数を用いて、指定したパスの画像をcv::Matオブジェクトに格納します。

画像ファイルの取得(imread)

imread関数は、画像ファイルのパスと読み込み方法を引数に取り、画像データをcv::Mat型で返します。

一般的な使い方は以下の通りです。

cv::Mat img = imread("画像ファイルのパス", cv::IMREAD_COLOR);

cv::IMREAD_COLORはカラー画像として読み込む指定です。

その他にも、グレースケールで読み込むcv::IMREAD_GRAYSCALEや、アルファチャンネルも含めて読み込むcv::IMREAD_UNCHANGEDなどのオプションがあります。

画像のパスは絶対パスまたは相対パスで指定します。

パスの指定ミスやファイルが存在しない場合、imreadは空のMatを返します。

エラー時のメッセージ表示

画像の読み込みに失敗した場合は、Matオブジェクトのempty()メソッドを使って判定します。

失敗時にはエラーメッセージを標準エラー出力に出力し、プログラムを終了させるのが一般的です。

if (img.empty()) {
    std::cerr << "画像の読み込みに失敗しました。" << std::endl;
    return -1;
}

これにより、ファイルの存在確認やパスの誤りを早期に検知でき、デバッグやトラブルシューティングが容易になります。

ウィンドウ作成と表示(imshow)

画像を画面に表示するには、imshow関数を使います。

まず、namedWindow関数でウィンドウを作成し、その後imshowで画像を表示します。

namedWindow("画像表示", WINDOW_AUTOSIZE);
imshow("画像表示", img);
waitKey(0);
destroyWindow("画像表示");

namedWindowの第2引数にはウィンドウのサイズ調整方法を指定します。

WINDOW_AUTOSIZEは画像のサイズに合わせてウィンドウを自動調整します。

ウィンドウサイズの調整

ウィンドウのサイズはnamedWindowの第2引数で制御します。

代表的な設定は以下の通りです。

定数説明
WINDOW_AUTOSIZE画像のサイズに合わせてウィンドウを自動調整します。
WINDOW_NORMALウィンドウサイズをユーザが変更可能です。
WINDOW_FREERATIOウィンドウのアスペクト比を維持しながらリサイズ可能です。

WINDOW_NORMALを指定すれば、表示後にウィンドウのサイズを手動で変更できます。

namedWindow("調整可能なウィンドウ", WINDOW_NORMAL);
imshow("調整可能なウィンドウ", img);

複数画像の同時表示

複数の画像を同時に表示したい場合は、それぞれの画像に対して異なるウィンドウを作成し、imshowを呼び出します。

cv::Mat img1 = imread("画像1のパス");
cv::Mat img2 = imread("画像2のパス");
namedWindow("画像1", WINDOW_AUTOSIZE);
namedWindow("画像2", WINDOW_AUTOSIZE);
imshow("画像1", img1);
imshow("画像2", img2);
waitKey(0);
destroyAllWindows();

これにより、複数のウィンドウに異なる画像を表示でき、比較や分析に便利です。

これらの操作を組み合わせることで、画像の読み込みと表示を効率的に行うことができ、次の処理へスムーズに進める準備が整います。

エッジ検出

エッジ検出は、画像内の輪郭や境界線を抽出するための基本的な画像処理技術です。

OpenCVでは、Canny関数を用いて効率的にエッジを検出できます。

エッジ検出の前には、画像を適切に前処理し、ノイズを除去することが重要です。

グレースケール化(cvtColor)

カラー画像は3つの色成分(B、G、R)を持ち、エッジ検出には通常、輝度情報だけを用います。

そのため、まずカラー画像をグレースケール画像に変換します。

// カラー画像をグレースケールに変換
cv::cvtColor(frame, gray, cv::COLOR_BGR2GRAY);

cvtColor関数は、第一引数に入力画像、第二引数に出力画像、第三引数に変換タイプを指定します。

cv::COLOR_BGR2GRAYは、BGRカラー画像をグレースケールに変換するための定数です。

ノイズ除去(GaussianBlur)

エッジ検出はノイズに敏感なため、事前に画像のノイズを除去します。

Gaussianフィルタを用いた平滑化処理が一般的です。

// ガウシアンブラーによるノイズ除去
cv::GaussianBlur(gray, blurred, cv::Size(5, 5), 1.5);

GaussianBlurの第1引数は入力画像、第2引数は出力画像です。

Size(5, 5)はカーネルのサイズを示し、1.5は標準偏差(σ)です。

カーネルサイズは奇数の正方形を選び、値が大きいほど平滑化効果が高まります。

Cannyエッジ検出(Canny)

ノイズ除去後の画像に対して、Canny関数を用いてエッジを抽出します。

// Cannyエッジ検出
cv::Canny(blurred, edges, minVal, maxVal, kernelSize);
  • blurred: 入力画像(平滑化済み)
  • edges: 出力のエッジ画像(白黒二値画像)
  • minVal: 最小閾値
  • maxVal: 最大閾値
  • kernelSize: Sobel演算子のカーネルサイズ(奇数)

最小/最大閾値

minValmaxValはエッジの強さを制御します。

minVal以下の勾配は無視され、maxVal以上の勾配は確実にエッジとして検出されます。

閾値の調整により、エッジの検出結果が変わるため、画像に応じて適切な値を選びます。

// 例:閾値の設定
int minThreshold = 50;
int maxThreshold = 150;

カーネルサイズ

kernelSizeはSobel演算子のカーネルのサイズです。

一般的には3や5を指定します。

大きい値にするとエッジの検出が滑らかになりますが、細かいエッジは見逃す可能性があります。

// 例:カーネルサイズ
int kernelSize = 3;

エッジマップの出力

Canny関数の出力は二値画像となり、エッジ部分は白(255)、それ以外は黒(0)となります。

これをそのまま表示したり、他の画像処理に利用したりします。

// エッジ画像の表示
imshow("エッジ検出結果", edges);

エッジ検出結果は、輪郭抽出や物体認識、画像解析の前処理として広く利用されます。

適切な閾値や前処理を行うことで、より正確なエッジ抽出が可能となります。

これらのステップを組み合わせることで、画像内の重要な境界線を抽出し、次の画像処理や解析に役立てることができます。

カメラキャプチャ

カメラから映像をリアルタイムで取得し、画像処理や解析を行うためには、OpenCVのVideoCaptureクラスを利用します。

これにより、Webカメラや外部カメラデバイスからの映像を簡単に取り込むことが可能です。

VideoCaptureの初期化

VideoCaptureのインスタンスを作成し、カメラデバイスを指定します。

デバイス番号は通常0から始まり、複数のカメラが接続されている場合は番号を変えて指定します。

cv::VideoCapture cap(0); // 0はデフォルトのカメラデバイスを指定
if (!cap.isOpened()) {
    std::cerr << "カメラの起動に失敗しました。" << std::endl;
    return -1;
}

isOpened()メソッドで、カメラの起動成功を確認します。

失敗した場合はエラーメッセージを出力し、プログラムを終了させます。

キャプチャ設定(解像度/FPS)

カメラの解像度やフレームレートは、setメソッドを使って設定できます。

これにより、必要な映像品質や処理速度に合わせて調整可能です。

// 解像度の設定(幅1920、高さ1080)
cap.set(cv::CAP_PROP_FRAME_WIDTH, 1920);
cap.set(cv::CAP_PROP_FRAME_HEIGHT, 1080);
// FPSの設定(例:30フレーム/秒)
cap.set(cv::CAP_PROP_FPS, 30);

ただし、カメラやドライバによっては設定が反映されない場合もあります。

設定後にgetメソッドで実際の値を確認すると良いでしょう。

double width = cap.get(cv::CAP_PROP_FRAME_WIDTH);
double height = cap.get(cv::CAP_PROP_FRAME_HEIGHT);
double fps = cap.get(cv::CAP_PROP_FPS);
std::cout << "解像度: " << width << "x" << height << ", FPS: " << fps << std::endl;

フレームの連続取得

カメラから映像を連続的に取得し、リアルタイム処理を行うには、ループ処理を用います。

>>演算子またはreadメソッドを使ってフレームを取得します。

cv::Mat frame;
while (true) {
    cap >> frame; // フレームを取得
    if (frame.empty()) {
        std::cerr << "フレームの取得に失敗しました。" << std::endl;
        break;
    }
    // 取得したフレームに対して画像処理を行う
    // 例:ウィンドウに表示
    imshow("カメラ映像", frame);
    // キー入力待ち(1ミリ秒待機)
    if (cv::waitKey(1) == 27) { // ESCキーで終了
        break;
    }
}

cap >> frame;は、カメラから次のフレームを取得し、frameに格納します。

フレームが空の場合は、何らかのエラーやカメラの停止を示します。

キー入力判定

リアルタイム処理中に特定のキー入力を検知して処理を制御できます。

waitKey関数は、指定したミリ秒だけキー入力を待ち、そのキーコードを返します。

int key = cv::waitKey(1);
if (key == 27) { // ESCキー
    break;
} else if (key == 'q') {
    // 'q'キーで終了
    break;
}

これにより、ユーザが任意のキーを押したときにループを抜けたり、特定の処理を行ったりできます。

リアルタイム処理時の注意点

  • waitKeyの待ち時間は短く設定し、処理負荷を抑えることが重要です。一般的には1ミリ秒や0ミリ秒に設定します
  • 画像処理は高速に行う必要があるため、処理時間を計測し、最適化を行います
  • カメラの設定やドライバの仕様によっては、解像度やFPSが希望通りにならない場合もあります。必要に応じて調整や確認を行います
  • ループ内で例外やエラーが発生した場合の例外処理も考慮します

リソース解放(release, destroyAllWindows)

カメラやウィンドウのリソースは、使用後に解放します。

releaseメソッドを呼び出すことで、カメラデバイスを解放し、他のアプリケーションや次回起動時に問題が起きないようにします。

cap.release(); // カメラの解放
cv::destroyAllWindows(); // すべてのウィンドウを閉じる

これらの操作は、プログラムの最後に必ず行うことが推奨されます。

特に、複数のウィンドウやデバイスを扱う場合は、適切なリソース管理が重要です。

これらのポイントを押さえることで、安定したカメラキャプチャとリアルタイム映像処理が実現できます。

顔検出

顔検出は、画像や映像内から人の顔を自動的に識別し、矩形で囲む処理です。

OpenCVでは、Haarカスケード分類器を用いて効率的に顔を検出できます。

検出の精度を高めるためには、前処理やパラメータ調整、パフォーマンス向上の工夫が重要です。

前処理

顔検出の前に画像の前処理を行うことで、検出精度を向上させることが可能です。

特に、照明条件や画像の質に左右されやすいため、適切な前処理を施します。

グレースケール変換

カラー画像は色情報を持ちますが、顔検出には輝度情報だけを用いるため、まずカラー画像をグレースケールに変換します。

cv::cvtColor(img, gray, cv::COLOR_BGR2GRAY);

これにより、計算負荷を軽減し、検出の安定性を向上させます。

ヒストグラム平坦化(equalizeHist)

照明条件の変動により顔のコントラストが低下する場合、ヒストグラム平坦化を行うと画像のコントラストを改善できます。

cv::equalizeHist(gray, equalized);

これにより、顔の特徴がより明瞭になり、検出の精度が向上します。

平滑化フィルタ

ノイズや細かいディテールを除去し、顔の輪郭を強調するために平滑化フィルタを適用します。

GaussianBlurが一般的です。

cv::GaussianBlur(equalized, blurred, cv::Size(5, 5), 1.5);

これにより、誤検出を減らし、検出結果の安定性を高めます。

カスケード分類器のロード(CascadeClassifier)

顔検出には、OpenCVに付属するHaarカスケード分類器を利用します。

XMLファイルをロードし、顔検出器を準備します。

cv::CascadeClassifier face_cascade;
if (!face_cascade.load("haarcascade_frontalface_default.xml")) {
    std::cerr << "カスケード分類器の読み込みに失敗しました。" << std::endl;
    return -1;
}

XMLファイルはOpenCVのインストールディレクトリ内に含まれており、パスを正確に指定してください。

顔検出の実行(detectMultiScale)

前処理済みの画像に対して、detectMultiScaleメソッドを呼び出し、顔候補を検出します。

std::vector<cv::Rect> faces;
face_cascade.detectMultiScale(
    gray, // 入力画像
    faces, // 検出結果の矩形リスト
    1.1,   // スケールファクター(画像縮小率)
    3,     // 最小隣接矩形数
    0,     // 検出オプション
    cv::Size(30, 30) // 最小顔サイズ
);

パラメータ設定

  • scaleFactor: 画像を縮小していく倍率。値が小さいほど検出精度は上がるが処理負荷も増加
  • minNeighbors: 検出候補の矩形の最小隣接数。値が大きいほど誤検出が減ります
  • minSize: 検出する顔の最小サイズ。小さすぎると誤検出やノイズも検出されやすくなります

これらのパラメータは、画像の特性や用途に応じて調整します。

検出結果の描画(rectangle)

検出された顔の矩形に対して、rectangle関数を用いて枠線を描画します。

for (size_t i = 0; i < faces.size(); i++) {
    cv::rectangle(
        img, // 元画像
        faces[i], // 検出された顔の矩形
        cv::Scalar(0, 255, 0), // 線の色(緑)
        2 // 線の太さ
    );
}

線色と線幅

  • 線色はcv::Scalar(B, G, R)で指定します。顔検出結果を見やすくするために、色や太さを調整します
  • 線幅は2や3が一般的ですが、用途に応じて変更可能です

複数顔への対応

detectMultiScaleは複数の顔を検出できるため、検出結果のリストをループしてすべての顔に矩形を描画します。

for (const auto& face : faces) {
    cv::rectangle(img, face, cv::Scalar(255, 0, 0), 2);
}

これにより、画像内の複数の顔を一度に可視化できます。

パフォーマンス向上の工夫

顔検出の処理は計算コストが高いため、パフォーマンスを向上させる工夫が必要です。

ROIの活用

画像の一部分だけを対象に検出を行うことで、処理時間を短縮できます。

例えば、顔が出やすい範囲だけを切り出して検出します。

cv::Rect roi_rect(100, 100, 200, 200);
cv::Mat roi = gray(roi_rect);
std::vector<cv::Rect> faces_roi;
face_cascade.detectMultiScale(roi, faces_roi, 1.1, 3, 0, cv::Size(30, 30));
// 検出結果の座標を元画像に合わせて調整
for (auto& face : faces_roi) {
    face.x += roi_rect.x;
    face.y += roi_rect.y;
    cv::rectangle(img, face, cv::Scalar(0, 255, 255), 2);
}

マルチスレッド処理

複数の顔検出を並列処理することで、処理速度を向上させることも可能です。

OpenCVのparallel_for_やC++標準のスレッドライブラリを利用して、検出処理を分散させます。

// 例:複数のROIに対して並列処理を行う
// 実装例は省略しますが、適切なスレッド管理と同期が必要です。

これらの工夫により、リアルタイム性を確保しつつ高精度な顔検出を実現できます。

まとめ

この記事では、OpenCVを使った画像処理の基本から、画像の読み込み、エッジ検出、カメラ映像のキャプチャ、顔検出の方法まで詳しく解説しました。

各処理の具体的なコード例とポイントを押さえることで、実践的な画像処理プログラムの作成に役立ちます。

これらの知識を応用して、さまざまな画像解析やリアルタイム映像処理に挑戦できます。

関連記事

Back to top button
目次へ