OpenCV

【C++】OpenCVで実現する画像クロッピングの基本手順と実装例

C++とOpenCVを使うと、画像の特定領域を簡単に切り出せます。

画像はimreadで取り込み、Rectで切り抜き範囲を決め、cloneで独立した画像として処理できます。

imshowimwriteを使って加工結果をそのまま確認でき、実装がしやすい点が魅力です。

画像の読み込み

imread関数の基本利用

OpenCVのimread関数を使って画像ファイルを読み込むと、画像処理の準備が整います。

画像ファイルのパスを指定するだけでなく、オプションのフラグを設定することでグレースケールやカラーとして読み込む設定も可能です。

下記のサンプルコードは、画像ファイル"image.jpg"をカラー画像として読み込む例です。

#include <iostream>
#include <opencv2/opencv.hpp>
int main() {
    // 画像ファイルパスを指定して読み込み
    cv::Mat image = cv::imread("image.jpg", cv::IMREAD_COLOR);
    // 読み込み失敗の場合にエラーメッセージを表示
    if (image.empty()) {
        std::cerr << "画像の読み込みに失敗しました。ファイルパスや形式を確認してください。" << std::endl;
        return -1;
    }
    // 成功した場合は、画像の情報を表示
    std::cout << "画像のサイズ: " << image.cols << "x" << image.rows << std::endl;
    return 0;
}
画像のサイズ: 640x480

画像ファイルパスの指定

画像ファイルのパスは、実行ファイルと同じディレクトリに画像がある場合はファイル名だけを記述することができます。

異なるディレクトリにある場合は、相対パスまたは絶対パスを指定する必要があります。

パスの指定ミスが原因で画像が読み込めないことも多くあるため、パスは正確に記述するよう注意が必要です。

ロード失敗時のエラーチェック

画像の読み込みがうまくいかない場合、プログラムが想定しない動作をする可能性があるため、image.empty()を使って失敗時に速やかにエラー処理を行うことが推奨されます。

エラーチェックを実装することで、後続の処理で不具合が発生するリスクを軽減できます。

ROIの設定

cv::Rectによる領域指定

ROI(Region of Interest、関心領域)を設定するには、cv::Rectクラスを使います。

cv::Rectは画像内の矩形領域を指定するためのオブジェクトで、左上の座標と幅、高さの情報を格納します。

座標とサイズの設定方法

ROIを設定する際は、画像の左上隅を原点(0, 0)として座標を指定します。

たとえば、画像の左上から幅200ピクセル、高さ140ピクセルの領域を切り出すには以下のように記述します。

#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
    cv::Mat image = cv::imread("image.jpg", cv::IMREAD_COLOR);
    if (image.empty()) {
        std::cerr << "画像の読み込みに失敗しました。" << std::endl;
        return -1;
    }
    // 左上の座標(0, 0)から幅200、高さ140の矩形を定義
    cv::Rect roi(0, 0, 200, 140);
    cv::Mat croppedImage = image(roi);
    std::cout << "切り出した画像のサイズ: " << croppedImage.cols << "x" << croppedImage.rows << std::endl;
    return 0;
}
切り出した画像のサイズ: 200x140

指定範囲の注意点

指定したROIが画像の範囲を超えてしまうと、実行時にエラーが発生する可能性があります。

画像サイズとROIのサイズ、位置が適切かどうかは事前にチェックすると安心です。

たとえば、次のようなリストで注意点を整理できます。

  • ROIの左上座標が負の値でないこと
  • ROIの幅と高さが元画像のサイズを超えないこと

独立コピーと参照の違い

ROIを設定して得られる画像は、元のcv::Matの一部を参照する形になっています。

そのため、元画像に対する変更が切り出した画像に影響する場合があります。

独立したコピーが必要な場合は、clone関数を用います。

clone関数の利用

clone関数を使うと、元画像から選択した領域の完全なコピーを作成できます。

これにより、クロッピング後に元画像を変更しても切り出し画像に影響が及ばなくなります。

#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
    cv::Mat image = cv::imread("image.jpg", cv::IMREAD_COLOR);
    if (image.empty()) {
        std::cerr << "画像の読み込みに失敗しました。" << std::endl;
        return -1;
    }
    cv::Rect roi(0, 0, 200, 140);
    // ROIで参照を取得し、clone関数で独立したコピーを作成
    cv::Mat croppedImage = image(roi).clone();
    std::cout << "独立した切り出し画像のサイズ: " << croppedImage.cols << "x" << croppedImage.rows << std::endl;
    return 0;
}
独立した切り出し画像のサイズ: 200x140

メモリ管理のポイント

cv::Matは画像データを共有する仕組みがあるため、参照とコピーの使い分けが重要です。

コピーを多用するとメモリの使用量が増える可能性があるので、必要な場合にのみcloneを利用するのがよいでしょう。

画像クロッピングの実装

クロッピング処理の流れ

画像クロッピング処理は、まずROIを設定し、その領域を選択して新たな画像オブジェクトに編集内容を反映させる手順です。

この流れをしっかり把握しておくと、後の応用もしやすくなります。

ROI適用による切り出し

設定したROIを用いて、元画像から切り出す場合は以下のように記述します。

ROIをそのまま利用すると、元画像とデータを共有するため、元画像を変更すると切り出し画像にも影響が出ます。

#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
    cv::Mat image = cv::imread("image.jpg", cv::IMREAD_COLOR);
    if (image.empty()) {
        std::cerr << "画像の読み込みに失敗しました。" << std::endl;
        return -1;
    }
    cv::Rect roi(50, 50, 100, 100);  // 適当な位置とサイズのROIを設定
    cv::Mat croppedImage = image(roi);
    cv::imshow("クロッピング結果", croppedImage);
    cv::waitKey(0);
    return 0;
}
(ウィンドウに100x100ピクセルの切り出し画像が表示されます)

結果画像の生成

ROI適用後、生成された切り出し画像は新たな画像処理や保存など、さまざまな用途に使用できます。

画像処理パイプラインに組み合わせる場合は、後続処理との連携に注意してコードを記述するとよいでしょう。

複数ROIによる処理

複数の異なるROIを順番に処理する場合、各ROIごとに切り出し操作を連続して実施することが可能です。

複数領域を扱うことで、画像内部の特定領域だけを抽出し、解析や表示を行うシナリオが実現できます。

連続的切り出しの工夫

複数のROIを扱う際には、ループ処理を用いることで効率的に実装することができるでしょう。

以下の例では、画像のグリッドごとにROIを定義し、各領域を順に抽出しています。

#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>
int main() {
    cv::Mat image = cv::imread("image.jpg", cv::IMREAD_COLOR);
    if (image.empty()) {
        std::cerr << "画像の読み込みに失敗しました。" << std::endl;
        return -1;
    }
    // グリッド状に分割するための設定
    int gridRows = 2;
    int gridCols = 2;
    int roiWidth = image.cols / gridCols;
    int roiHeight = image.rows / gridRows;
    std::vector<cv::Mat> croppedImages;
    for (int i = 0; i < gridRows; ++i) {
        for (int j = 0; j < gridCols; ++j) {
            cv::Rect roi(j * roiWidth, i * roiHeight, roiWidth, roiHeight);
            // cloneを使って独立したコピーを作成
            cv::Mat cropped = image(roi).clone();
            croppedImages.push_back(cropped);
        }
    }
    // 各切り出し画像のサイズを表示
    for (size_t idx = 0; idx < croppedImages.size(); ++idx) {
        std::cout << "画像" << idx << " のサイズ: "
                  << croppedImages[idx].cols << "x"
                  << croppedImages[idx].rows << std::endl;
    }
    return 0;
}
画像0 のサイズ: 320x240
画像1 のサイズ: 320x240
画像2 のサイズ: 320x240
画像3 のサイズ: 320x240

画像出力と保存

imshow関数での表示

切り出した画像は、imshow関数を利用してウィンドウに表示することができます。

ウィンドウの設定でウィンドウサイズの固定やリサイズの有無を決めることが可能です。

表示が完了したら、waitKey関数でユーザーの入力を待つ仕組みを取り入れるとよいでしょう。

ウィンドウ設定のポイント

imshowで表示する際のウィンドウ名はユニークな名前を設定すると、複数ウィンドウを同時に扱う場合にも混乱がなくなります。

また、ウィンドウのリサイズ機能を使って表示サイズに合わせた表示ができるように工夫することもおすすめです。

#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
    cv::Mat image = cv::imread("image.jpg", cv::IMREAD_COLOR);
    if (image.empty()) {
        std::cerr << "画像の読み込みに失敗しました。" << std::endl;
        return -1;
    }
    // ROIに基づいて切り出した画像の表示
    cv::Rect roi(30, 30, 150, 150);
    cv::Mat croppedImage = image(roi).clone();
    // ウィンドウ名に「Cropped Image」を設定
    cv::namedWindow("Cropped Image", cv::WINDOW_AUTOSIZE);
    cv::imshow("Cropped Image", croppedImage);
    cv::waitKey(0);
    return 0;
}
(ウィンドウに150x150ピクセルの切り出し画像が表示されます)

imwrite関数での保存

処理後の画像をファイルに保存するには、imwrite関数を利用できます。

画像の拡張子に応じて保存形式が変わるため、用途に合わせたファイル形式を選択することが重要です。

出力形式と圧縮設定

出力形式に応じてJPEG、PNGなどの形式が使用可能です。

JPEGの場合は圧縮率を設定するオプションもあり、圧縮度を細かく調整することでファイルサイズと画質のバランスを取ることができます。

下記のコードはJPEG画像を保存する例です。

#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>
int main() {
    cv::Mat image = cv::imread("image.jpg", cv::IMREAD_COLOR);
    if (image.empty()) {
        std::cerr << "画像の読み込みに失敗しました。" << std::endl;
        return -1;
    }
    cv::Rect roi(20, 20, 200, 200);
    cv::Mat croppedImage = image(roi).clone();
    // JPEG圧縮のパラメータ設定(例:品質を90%に設定)
    std::vector<int> compression_params;
    compression_params.push_back(cv::IMWRITE_JPEG_QUALITY);
    compression_params.push_back(90);
    if (!cv::imwrite("cropped_image.jpg", croppedImage, compression_params)) {
        std::cerr << "画像の保存に失敗しました。" << std::endl;
        return -1;
    }
    std::cout << "画像の保存が完了しました。" << std::endl;
    return 0;
}
画像の保存が完了しました。

エラーハンドリング

入力画像の検証方法

画像処理の初期段階で、入力画像の読み込みが正しく行われたかどうかの確認は非常に大切です。

不正なファイル形式や存在しないファイルパスが原因で、処理が途中で失敗することを防ぐための実装が求められます。

ファイル存在確認と形式チェック

画像ファイルの存在確認は、imread関数の返り値であるcv::Matオブジェクトの.empty()メソッドを利用することで実現できます。

また、画像の形式が想定内であるかどうかも、画像のチャンネル数や型でチェックすると安心です。

たとえば、カラー画像の場合はチャンネル数が3であることが求められます。

クロッピング範囲の検証

クロッピング処理を実行する前に、指定したROIが元画像の範囲を超えていないか、または不正なパラメータが設定されていないかを確認することが推奨されます。

これにより、実行時エラーを防ぐだけでなく、意図しない画像部分が切り出されることを防止できます。

座標超過の対策

ROIの座標や幅、高さが元画像のサイズ内に収まっているか確認するチェック処理を追加すると、座標超過によるエラーを避けやすくなります。

条件分岐や例外処理で、異常が検出された場合はエラーメッセージを表示し、安全にプログラムを終了するのが望ましいです。

エラーメッセージ管理

エラーメッセージを一元管理して、ユーザーに分かりやすく伝える設計が大切です。

ログ出力を活用すると、後から問題箇所を特定する際の助けになるでしょう。

適切なメッセージとエラーハンドリングの組み合わせが、プログラムの信頼性を高めます。

パフォーマンスとメモリ管理

効率的なクロッピング処理

画像処理の性能向上は、大きな画像や連続処理を行う場合に重要なポイントです。

ROIの切り出しでは、cv::Matの参照を活用することで不要なコピーを回避できることが多いです。

必要な場合だけcloneを利用することで、余分なメモリ割り当てを防げます。

参照利用と不要コピーの回避

参照をそのまま利用する場合、コピー処理が省略されメモリ使用量が抑えられます。

しかし、元の画像に変更を加える操作がある場合は、cloneによる独立コピーを利用する必要があります。

画像のサイズや用途に応じて使い分ける設計が推奨されます。

高解像度画像への最適化

高解像度画像を扱う場合、メモリ使用量や処理速度に注意が必要です。

クロッピング処理だけでなく、後続の画像処理全体でパフォーマンスに影響が及ぶ可能性が高いため、処理の最適化に努めましょう。

メモリ使用量の把握

画像のメモリ使用量は、画像のピクセル数やカラーチャンネル数によって決まるため、事前に計算すると効率的です。

たとえば、画像のメモリサイズは

\[\text{メモリサイズ} = \text{幅} \times \text{高さ} \times \text{チャンネル数} \times \text{ビット深度}\]

と計算でき、これを参考に最適な処理方法を選択することができます。

処理速度改善策

処理速度を改善するためには、ROIによる必要な部分のみの処理や、並列処理の導入などが検討できます。

特に、複数の領域に対する処理をループやマルチスレッド化で実装すると、全体のパフォーマンス向上に寄与します。

応用的なクロッピング手法

動的なROI選択(マウス連動)

ユーザーがマウス操作でクロッピング範囲を指定できる仕組みを取り入れると、インタラクティブなアプリケーションが実現できます。

OpenCVのイベントコールバックを利用して、クリックやドラッグの動作を感知し、ROIを動的に設定する方法があります。

ユーザー操作との連携

ユーザー操作に対応するには、setMouseCallback関数を用いると良いでしょう。

下記のサンプルコードは、マウス操作でROIの開始点と終了点を指定する簡単な例です。

#include <opencv2/opencv.hpp>
#include <iostream>
cv::Point startPoint, endPoint;
bool drawing = false;
cv::Rect roi;
void mouseHandler(int event, int x, int y, int flags, void* param) {
    cv::Mat* image = reinterpret_cast<cv::Mat*>(param);
    if (event == cv::EVENT_LBUTTONDOWN) {
        drawing = true;
        startPoint = cv::Point(x, y);
    } else if (event == cv::EVENT_MOUSEMOVE && drawing) {
        endPoint = cv::Point(x, y);
    } else if (event == cv::EVENT_LBUTTONUP) {
        drawing = false;
        endPoint = cv::Point(x, y);
        roi = cv::Rect(startPoint, endPoint);
        // rectangle関数でROIを画像に可視化
        cv::Mat temp = image->clone();
        cv::rectangle(temp, roi, cv::Scalar(0, 0, 255), 2);
        cv::imshow("Dynamic ROI", temp);
    }
}
int main() {
    cv::Mat image = cv::imread("image.jpg", cv::IMREAD_COLOR);
    if (image.empty()) {
        std::cerr << "画像の読み込みに失敗しました。" << std::endl;
        return -1;
    }
    cv::namedWindow("Dynamic ROI");
    cv::setMouseCallback("Dynamic ROI", mouseHandler, &image);
    while (true) {
        cv::imshow("Dynamic ROI", image);
        if (cv::waitKey(30) == 27) {  // Escキーで終了
            break;
        }
    }
    return 0;
}
(ウィンドウに画像が表示され、ユーザー操作でROIが赤枠で表示されます)

複数領域の同時切り出し

複数のROIを同時に選択し、各領域ごとにクロッピングを行う手法もあります。

これにより、大きな画像から複数の興味深い領域を一括処理することが可能になります。

たとえば、顔検出後に各顔領域を抽出するといった用途に応用できます。

アルゴリズム拡張事例

複数領域切り出しは、画像解析や機械学習の前処理に適したテクニックです。

各ROIごとに独立した処理パイプラインを構築するため、アルゴリズム自体も柔軟な設計が可能です。

処理結果はリストやベクターに格納し、後続の解析に利用するとスムーズに連携できるでしょう。

まとめ

今回紹介した内容を参考に、C++とOpenCVを活用した画像クロッピングを実装する際のポイントを整理できたと思います。

画像の読み込みからROI設定、処理の流れ、出力保存、エラーハンドリングまで、一連の作業を丁寧に実装することで、柔軟かつ安全な画像処理プログラムが作成できます。

各項目に記載したサンプルコードを実際に試し、用途に合わせた最適な実装方法を見つけられると嬉しいです。

関連記事

Back to top button