Cv2.Split / Cv2.Merge の使い方【OpenCV/C#(OpenCvSharp)】〜チャンネルを分解・結合する〜

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

Cv2.Split / Cv2.Merge の使い方【OpenCV/C#(OpenCvSharp)】〜チャンネルを分解・結合する〜

Cv2.Split は BGR などのマルチチャンネル画像を各チャンネルの Mat に分解し、Cv2.Merge は複数の単チャンネル Mat を1枚のマルチチャンネル画像に結合します。特定チャンネルだけ処理を加えてから再合成する、という実務でよく出るパターンの基本関数です。

最小の使い方はこうなります。

Mat[] channels = Cv2.Split(src);   // BGR → B, G, R の3枚に分解
// channels[2] が R チャンネル
Cv2.Merge(channels, dst);          // 3枚を再合成

動作環境

  • OpenCvSharp4(OpenCV 4.x 系)
  • NuGet: OpenCvSharp4 + OpenCvSharp4.runtime.win(Windows 実行時)
  • .NET 8 コンソールアプリ(トップレベルステートメント)

環境構築がまだの方は環境構築ガイドを先にどうぞ。


基本の使い方

BGR 画像を Split で分解し、B チャンネルだけゼロにしてから Merge で再合成するサンプルです。

using OpenCvSharp;

// 64x64 の BGR カラー画像を生成(赤 = (B:0, G:0, R:255))
using var src = new Mat(64, 64, MatType.CV_8UC3, new Scalar(0, 0, 255));

// --- Split: チャンネルごとの Mat 配列を得る ---
Mat[] channels = Cv2.Split(src);
// channels[0] = B, channels[1] = G, channels[2] = R

try
{
    // B チャンネルをゼロクリア(R チャンネルは変更しない)
    channels[0].SetTo(Scalar.All(0));

    // --- Merge: チャンネルを再合成 ---
    using var dst = new Mat();
    Cv2.Merge(channels, dst);

    // 各チャンネルの代表画素値を表示
    Console.WriteLine($"src  [0,0] = {src.At<Vec3b>(0, 0)}");
    Console.WriteLine($"dst  [0,0] = {dst.At<Vec3b>(0, 0)}");
    Console.WriteLine($"B channel mean = {Cv2.Mean(channels[0]).Val0}");
    Console.WriteLine($"G channel mean = {Cv2.Mean(channels[1]).Val0}");
    Console.WriteLine($"R channel mean = {Cv2.Mean(channels[2]).Val0}");

    // ファイルに保存して確認
    Cv2.ImWrite("split_merge_result.png", dst);
    Console.WriteLine("split_merge_result.png を保存しました");
}
finally
{
    // Split が返す Mat[] は自分で Dispose する
    foreach (var ch in channels)
        ch.Dispose();
}

実行すると以下のように表示されます。

src  [0,0] = (0, 0, 255)
dst  [0,0] = (0, 0, 255)
B channel mean = 0
G channel mean = 0
R channel mean = 255
split_merge_result.png を保存しました

コード解説

行の処理 ポイント
Cv2.Split(src) 戻り値は Mat[]。要素数は src.Channels() と一致する
channels[0].SetTo(...) 分解後の Mat は独立しているので直接書き換え可能
Cv2.Merge(channels, dst) 入力配列の順序が出力のチャンネル順になる
foreach の Dispose Split が返す各 Mat はネイティブメモリを持つため解放必須

引数と戻り値

Cv2.Split

Mat[] Cv2.Split(Mat src)
void  Cv2.Split(Mat src, out Mat[] mv)  // 別オーバーロード
引数 型 説明
src Mat 入力マルチチャンネル画像(1〜4ch)
戻り値 Mat[] チャンネル数と同じ長さの単チャンネル Mat 配列

Cv2.Merge

void Cv2.Merge(Mat[] mv, Mat dst)
void Cv2.Merge(IEnumerable<Mat> mv, Mat dst)
引数 型 説明
mv Mat[] / IEnumerable<Mat> 結合する単チャンネル Mat の配列(全要素のサイズ・型が同一であること)
dst Mat 出力マルチチャンネル画像(mv.Length チャンネル)

実践例

例1: Hチャンネルだけ処理して色相を回転させる(HSV)

HSV に変換してから Split し、H チャンネルに一定値を加算して色相をシフトする典型パターンです。

using OpenCvSharp;

// サンプル画像を読み込む(BGR)
using var src = Cv2.ImRead("input.png");
if (src.Empty()) { Console.WriteLine("画像が読み込めません"); return; }

// BGR → HSV
using var hsv = new Mat();
Cv2.CvtColor(src, hsv, ColorConversionCodes.BGR2HSV);

// チャンネル分解
Mat[] channels = Cv2.Split(hsv);

try
{
    // H チャンネル(0〜179)を 90 加算して色相を 180° 回転
    // 飽和演算では 180 を超えるので、convertTo + 剰余 or AddWeighted で調整
    using var hShifted = new Mat();
    Cv2.Add(channels[0], new Mat(channels[0].Size(), channels[0].Type(), Scalar.All(90)), hShifted);

    // 179 を超えた分を折り返す(0〜179 にクランプ)
    // OpenCV の HSV は H=0〜179 なので 180 で mod
    using var hMod = new Mat();
    Cv2.Subtract(hShifted,
        new Mat(hShifted.Size(), hShifted.Type(), Scalar.All(0)), hMod); // コピー
    // 簡易折り返し: 180以上の画素を -180
    using var mask = new Mat();
    Cv2.Threshold(hShifted, mask, 179, 255, ThresholdTypes.Binary);
    using var correction = new Mat(hShifted.Size(), hShifted.Type(), Scalar.All(180));
    hShifted.CopyTo(hMod);
    Cv2.Subtract(hMod, correction, hMod, mask);

    // 差し替えて Merge
    channels[0].Dispose();
    channels[0] = hMod.Clone();

    using var hsvShifted = new Mat();
    Cv2.Merge(channels, hsvShifted);

    // HSV → BGR に戻す
    using var dst = new Mat();
    Cv2.CvtColor(hsvShifted, dst, ColorConversionCodes.HSV2BGR);

    Cv2.ImWrite("hue_shifted.png", dst);
    Console.WriteLine("hue_shifted.png を保存しました");
}
finally
{
    foreach (var ch in channels) ch.Dispose();
}

例2: R チャンネルのヒストグラムを計算する

Split で取り出した単チャンネル Mat をそのまま Cv2.CalcHist に渡せます。チャンネルごとのヒストグラム分析でよく使うパターンです。詳しくは Cv2.CalcHist の使い方【OpenCV/C#(OpenCvSharp)】〜ヒストグラムを計算する〜も参照してください。

using OpenCvSharp;

using var src = Cv2.ImRead("input.png");
if (src.Empty()) { Console.WriteLine("画像が読み込めません"); return; }

Mat[] channels = Cv2.Split(src);  // channels[0]=B, [1]=G, [2]=R

try
{
    // R チャンネル(channels[2])のヒストグラム
    using var hist = new Mat();
    int[] histSize = { 256 };
    Rangef[] ranges = { new Rangef(0, 256) };
    Cv2.CalcHist(
        images: new[] { channels[2] },
        channels: new[] { 0 },       // 単チャンネルなので 0
        mask: null,
        hist: hist,
        dims: 1,
        histSize: histSize,
        ranges: ranges
    );

    // ヒストグラムの最大ビン値を取示
    Cv2.MinMaxLoc(hist, out _, out double maxVal);
    Console.WriteLine($"R チャンネル ヒストグラム最大値: {maxVal}");
}
finally
{
    foreach (var ch in channels) ch.Dispose();
}

つまずきポイント

⚠️ Cv2.Split が返す Mat[] は自分で Dispose する

Cv2.Split が返す Mat[] の各要素はネイティブメモリを持つため、使い終わったら確実に Dispose しなければなりません。using 構文は配列要素には直接使えないため、try/finally で foreach Dispose するか、ヘルパーメソッドでラップするのが安全です。

// NG: channels が GC まかせで Dispose されない
Mat[] channels = Cv2.Split(src);
Cv2.Merge(channels, dst);
// → ネイティブメモリリーク

// OK: finally で確実に解放
try { ... }
finally { foreach (var ch in channels) ch.Dispose(); }

⚠️ Merge に渡す Mat はサイズ・型が完全一致していること

Cv2.Merge は渡すすべての Mat のサイズと要素型(MatType)が同一でなければなりません。サイズや型が異なると実行時例外が発生します。Split 後に一部チャンネルを Clone して差し替えるときは型変換しないよう注意してください。

⚠️ 4チャンネル(BGRA)画像の Split 結果は4要素

PNG など BGRA 形式で読み込んだ場合、Cv2.Split の戻り配列は [B, G, R, A] の4要素になります。3要素前提のコードに渡すと Cv2.Merge でチャンネル数ミスマッチになるため、src.Channels() で要素数を確認してから処理してください。


関連する関数


まとめ

Cv2.Split で BGR 画像を単チャンネル Mat に分解し、処理後に Cv2.Merge で再合成するパターンは色空間操作・チャンネル別フィルタリングの基本です。Split が返す Mat[] の各要素は必ず Dispose し、Merge に渡す Mat のサイズ・型を揃えることが実装上の要点です。

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

本ブログを運営するスワローインキュベートは、OpenCV ベースのなりすまし判定SDK/APIを開発しています。SDK は C#/.NET 8 に正式対応しており、OpenCvSharp を使うプロジェクトからそのまま組み込めます。

→ 顔認証のなりすまし対策 設計チェックリスト(当ブログ)
→ なりすまし判定SDK/APIの詳細を見る
→ API仕様書・サンプルコード

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