前のチュートリアル: OpenCVによる画像のスキャン、ルックアップテーブル、処理時間の計測方法
次のチュートリアル: 画像に対する演算
| 原著者 | Bernát Gábor |
| 互換性 | OpenCV >= 3.0 |
行列に対するマスク演算は非常にシンプルである。基本的な考え方は、画像中の各ピクセルの値をマスク行列(カーネルとも呼ばれる)に従って再計算することである。このマスクには、近傍のピクセル(および対象ピクセル自身)が新しいピクセル値にどの程度影響を与えるかを調整する値が格納されている。数学的な観点では、指定した値を用いた加重平均を計算していることになる。
画像のコントラスト強調手法の問題を考えてみよう。基本的には、画像の各ピクセルに対して次の式を適用したい。
\[I(i,j) = 5*I(i,j) - [ I(i-1,j) + I(i+1,j) + I(i,j-1) + I(i,j+1)]\]
\[\iff I(i,j)*M, \text{where } M = \bordermatrix{ _i\backslash ^j & -1 & 0 & +1 \cr -1 & 0 & -1 & 0 \cr 0 & -1 & 5 & -1 \cr +1 & 0 & -1 & 0 \cr }\]
1つ目の表記は数式を用いたもので、2つ目はマスクを用いて1つ目を簡潔にした版である。マスクを使うには、マスク行列の中心(上記ではゼロ・ゼロのインデックスで示される位置)を計算したいピクセルに合わせ、重なった行列の値とピクセル値を掛け合わせて合計する。やっていることは同じであるが、大きな行列の場合は後者の表記の方がはるかに見通しがよい。
このソースコードは こちら からダウンロードできる。または OpenCV ソースコードライブラリのサンプルディレクトリ samples/cpp/tutorial_code/core/mat_mask_operations/mat_mask_operations.cpp を参照すること。
このソースコードは こちら からダウンロードできる。または OpenCV ソースコードライブラリのサンプルディレクトリ samples/java/tutorial_code/core/mat_mask_operations/MatMaskOperations.java を参照すること。
このソースコードは こちら からダウンロードできる。または OpenCV ソースコードライブラリのサンプルディレクトリ samples/python/tutorial_code/core/mat_mask_operations/mat_mask_operations.py を参照すること。
それでは、基本的なピクセルアクセス方法を使う場合とfilter2D()関数を使う場合の両方で、これをどのように実現できるか見てみよう。
これを行う関数を次に示す。
まず、入力画像のデータがunsigned char形式であることを確認する。これにはCV_Assert関数(マクロ)を用いる。これは内部の式がfalseのときにエラーをスローする。
まず、入力画像のデータがunsigned 8ビット形式であることを確認する。
まず、入力画像のデータがunsigned 8ビット形式であることを確認する。
入力と同じサイズ・同じ型の出力画像を生成する。格納方法のセクションで述べたように、チャンネル数に応じて1つまたは複数のサブカラムを持つことがある。
これらをポインタを介して走査するため、要素の総数はこの数値に依存する。
ピクセルへのアクセスにはプレーンなC言語の[]演算子を用いる。複数の行に同時にアクセスする必要があるため、それぞれの行(前の行、現在の行、次の行)に対するポインタを取得する。さらに計算結果を保存する先を指すポインタも必要である。あとは[]演算子で適切な要素にアクセスするだけである。出力ポインタを前に進めるには、各演算のたびにこれを(1バイト分)増やすだけでよい。
画像の境界では、上記の表記は存在しないピクセル位置(マイナス1・マイナス1など)を指してしまう。これらの点では数式が未定義となる。単純な解決策は、これらの点ではカーネルを適用せず、たとえば境界上のピクセルをゼロに設定することである。
複数の行や列にアクセスする必要があるが、これは現在の中心(i,j)に1を加算または減算することで実現できる。続いて総和を求め、その新しい値をResult行列に格納する。
画像の境界では、上記の表記は存在しないピクセル位置((-1,-1)など)を指してしまう。これらの点では数式が未定義となる。単純な解決策は、これらの点ではカーネルを適用せず、たとえば境界上のピクセルをゼロに設定することである。
複数の行や列にアクセスする必要があるが、これは現在の中心(i,j)に1を加算または減算することで実現できる。続いて総和を求め、その新しい値をResult行列に格納する。
このようなフィルタの適用は画像処理で非常によく行われるため、OpenCVにはマスク(場所によってはカーネルとも呼ばれる)の適用を担う関数が用意されている。これを使うには、まずマスクを保持するオブジェクトを定義する必要がある。
次にfilter2D()関数を呼び出し、入力画像、出力画像、および使用するカーネルを指定する。
この関数にはさらに、カーネルの中心を指定する5番目の省略可能な引数、Kに格納する前にフィルタ済みピクセルへ任意の値を加算するための6番目の引数、そして演算が未定義となる領域(境界)でどう処理するかを決める7番目の引数がある。
この関数はより短く、冗長さも少ない。さらにいくつかの最適化が施されているため、通常は手書きの方法よりも高速である。たとえば筆者の環境でのテストでは、後者が13ミリ秒しかかからなかったのに対し、前者は約31ミリ秒かかった。かなりの差である。
例:
プログラムを実際に実行している様子は、YouTubeチャンネルで確認できる。
