cv::Mat ROI で画像の一部を切り出す方法【OpenCV/C++】

OpenCV for C++
📌 準備: OpenCV の環境構築がまだの方はこちら → C++ 環境構築ガイドC#(OpenCvSharp)セットアップ

cv::Mat ROI で画像の一部を切り出す方法【OpenCV/C++】

cv::Mat の ROI(Region of Interest)を使うと、画像の特定領域を切り出して処理できます。cv::Rect[] 演算子に渡すだけで取得でき、コピーなしのビュー操作が可能です。

cv::Mat roi = src(cv::Rect(x, y, width, height));

実務では「検出した物体領域だけを後段の処理に渡す」「特定エリアにマスクをかける」といった場面で毎日使う操作です。


動作環境

  • OpenCV 4.x
  • C++17
g++ -std=c++17 main.cpp `pkg-config --cflags --libs opencv4` -o main

基本の使い方

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

int main()
{
    // 画像読み込み
    cv::Mat src = cv::imread("input.jpg");
    if (src.empty()) {
        std::cerr << "画像を読み込めませんでした" << std::endl;
        return -1;
    }

    // ROI 領域を定義: 左上(100, 50)、幅200、高さ150
    cv::Rect roi_rect(100, 50, 200, 150);

    // 画像サイズをはみ出さないか確認
    if ((roi_rect & cv::Rect(0, 0, src.cols, src.rows)) != roi_rect) {
        std::cerr << "ROI が画像範囲外です" << std::endl;
        return -1;
    }

    // ROI をビューとして取得(コピーではない)
    cv::Mat roi = src(roi_rect);

    // ROI を独立したコピーとして保存したい場合は clone()
    cv::Mat roi_copy = src(roi_rect).clone();

    // ROI に直接書き込む(元画像 src も変わる)
    roi = cv::Scalar(0, 0, 255); // 赤で塗りつぶし

    cv::imwrite("output_roi_view.jpg", src);       // 元画像に反映されている
    cv::imwrite("output_roi_copy.jpg", roi_copy);  // こちらは変化なし

    std::cout << "ROI サイズ: "
              << roi.cols << "x" << roi.rows << std::endl;

    return 0;
}

実行結果:

ROI サイズ: 200x150

output_roi_view.jpg を確認すると、座標 (100, 50) から幅200×高さ150 の矩形が赤く塗りつぶされています。output_roi_copy.jpg は切り出し時点のピクセルをそのまま保持しており、変化していません。

各行のポイント

コード 説明
src(roi_rect) ビュー取得。元データを共有するため高速・メモリ効率◎
src(roi_rect).clone() 独立コピー。元画像と切り離して扱いたいときに使う
roi = cv::Scalar(...) ROI への代入は元画像に反映される
範囲チェック & 演算子 cv::Rect 同士の AND で交差矩形を得てはみ出し検出

引数と戻り値

ROI 取得には cv::Mat::operator() を使います。

引数 説明
roi cv::Rect 切り出す矩形(左上座標 + 幅・高さ)
戻り値 説明
ROI ビュー cv::Mat 元データを共有するサブマトリクス

cv::Rect の構造

cv::Rect(int x, int y, int width, int height)
// x, y : 左上角の座標(ピクセル単位)
// width : 幅(右方向)
// height: 高さ(下方向)

⚠️ cv::Rect の終端座標は x + widthy + height であり、width 自体は含まれる列数です。Python の NumPy スライスとは 1 ずれないため混同しないよう注意してください。


実践例

例1: 検出領域だけを別ファイルに保存する

物体検出などで得た cv::Rect を使って、検出ごとに切り出し画像を保存する典型パターンです。

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

int main()
{
    cv::Mat src = cv::imread("scene.jpg");
    if (src.empty()) {
        std::cerr << "画像を読み込めませんでした" << std::endl;
        return -1;
    }

    // 検出結果を模擬(実際は検出器から取得)
    std::vector<cv::Rect> detections = {
        cv::Rect(30,  20, 100, 80),
        cv::Rect(200, 50, 120, 90),
    };

    cv::Rect img_rect(0, 0, src.cols, src.rows);

    for (int i = 0; i < static_cast<int>(detections.size()); ++i) {
        // 画像境界内にクランプ
        cv::Rect safe = detections[i] & img_rect;
        if (safe.area() == 0) continue;

        // clone() で独立コピーを作成して保存
        cv::Mat chip = src(safe).clone();
        std::string filename = "chip_" + std::to_string(i) + ".jpg";
        cv::imwrite(filename, chip);
        std::cout << filename << " を保存しました ("
                  << chip.cols << "x" << chip.rows << ")" << std::endl;
    }

    return 0;
}

実行結果:

chip_0.jpg を保存しました (100x80)
chip_1.jpg を保存しました (120x90)

例2: ROI に別画像を合成する(上書き貼り付け)

ROI はビューなので、別の cv::MatcopyTo すれば元画像に直接貼り付けられます。

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

int main()
{
    cv::Mat base = cv::imread("base.jpg");
    cv::Mat patch = cv::imread("patch.jpg");

    if (base.empty() || patch.empty()) {
        std::cerr << "画像を読み込めませんでした" << std::endl;
        return -1;
    }

    // 貼り付け先の矩形(patch のサイズに合わせる)
    cv::Rect paste_rect(50, 50, patch.cols, patch.rows);

    // はみ出しチェック
    cv::Rect img_rect(0, 0, base.cols, base.rows);
    if ((paste_rect & img_rect) != paste_rect) {
        std::cerr << "貼り付け範囲が base 画像を超えています" << std::endl;
        return -1;
    }

    // ROI ビューに copyTo で上書き
    patch.copyTo(base(paste_rect));

    cv::imwrite("composited.jpg", base);
    std::cout << "合成完了" << std::endl;

    return 0;
}

base(paste_rect)base と同じデータを参照するため、copyTo の結果は base に即座に反映されます。


つまずきポイント

✅ ROI はコピーではなくビュー

cv::Mat roi = src(cv::Rect(0, 0, 100, 100));
roi = cv::Scalar(0); // src の左上 100x100 が黒くなる

roi に代入や描画をすると元画像も変わります。元画像を保持したまま ROI だけ加工したい場合は必ず .clone() でコピーを取得してください。これは C++ の cv::Mat のシャロウコピー設計に由来するもので、Python の NumPy スライスと同じ挙動です。

⚠️ 範囲外アクセスは即クラッシュ

Python では配列スライスが自動クランプされますが、OpenCV C++ では cv::Rect が画像サイズをはみ出すと assertion エラー(またはアクセス違反) でクラッシュします。実装例で示したように roi_rect & img_rect でクランプするか、事前に座標を std::clamp で制限してください。

⚠️ 型・チャンネル不一致による assertion

ROI に copyTo や描画関数を適用する際、貼り付け元と ROI のチャンネル数や深度(CV_8UC3 vs CV_8UC1 など)が異なると cv::Exception が発生します。事前に patch.type() == base.type() を確認する習慣をつけてください。


関連する関数

  • cv::Mat::clone() — ROI を独立コピーに変換する際に必須
  • cv::Mat::copyTo() — ROI への貼り付けや、マスク付きコピーに使用
  • cv::Rect — ROI 矩形の定義。& 演算子によるクランプも活用できる
  • cv::resize() — 切り出した ROI をリサイズする後処理でセットで使うことが多い

まとめ

cv::Mat の ROI は src(cv::Rect(...)) 一行で取得でき、ビュー操作によりメモリコピーなしに特定領域を加工できます。ただしビューであるため元画像が変わる点と範囲外アクセスでクラッシュする点が C++ 固有のハマりどころです。独立コピーが必要なら .clone()、元画像への合成には copyTo() を組み合わせてください。

🛠 画像処理のプロが開発するSDK/API

本ブログを運営するスワローインキュベートは、OpenCV ベースのなりすまし判定SDK/API(C++製・OpenCV 4.10)を開発しています。顔認証システムへの組み込み実績多数。

→ なりすまし判定SDK/APIの詳細を見る
→ API仕様書・サンプルコード

タイトルとURLをコピーしました