OpenCVとNumPyの画像座標系

2020年10月25日

Pythonでコンピュータビジョンや画像処理を試したことがある人は、OpenCVやNumPyなどの画像操作用ライブラリのことをよくご存知でしょう。ライブラリはそれぞれ、独自の特徴や長所短所がありますが、最も重要なのは、画像の処理や操作方法がライブラリごとに異なる場合があることです。

これらのライブラリを利用するときに注意すべき重要な概念の一つに、画像内の点を定義する座標系があります。このような概念を理解しておけば、画像操作用のアルゴリズムを作成し、使用する際に重宝します。例えば、特徴抽出やデータ拡張手法のコンポーネントを作成したり理解したりするためにも役立ちます。

さらに、異なる画像操作用の座標系を持つ複数のライブラリを使う必要がある場合にもこれらの概念を知っていると便利です。一例を挙げると、ディープラーニングモデルが出力したバウンディングボックス座標を解析する場合を想像してみてください。モデルは、ターゲットライブラリで使用できる座標系とは異なる座標系を用いて出力するかもしれません。この場合、ターゲットライブラリの座標系に合わせてバウンディングボックス座標を手動で変換する必要があるのです。

この記事では、OpenCVとNumPyの座標系を使って画像を表現する方法、そして一つの座標系から別の座標系に変換する方法を解説していきます。

 

座標系

座標系を用いて画像を表現する方法と様々な関連用語について簡単に復習しておきましょう。

座標系は、参照点を基準として任意の点の位置を定義するために用いられます。2Dデカルト座標系では、各点が順序付き座標ペア(x, y)で表されます。ここで、xとyは、垂直に交わる二つの軸(通常、X軸およびY軸とラベル付けされます)からの符号付き距離を示します。順序付きペアと呼ばれるのは、順序が重要だからです(つまり(y, x)は別の点を表します)。軸が交差する点は原点と呼ばれ、(0, 0)で示されます。

図1

同様に、3Dデカルト座標系には、相互に垂直に交わる三つの軸(X軸、Y軸、Z軸)があり、各点が順序付きの三つの座標(x, y, z)で表されます。より詳しい情報についてはこちらをご覧ください

画像を表現する一般的な方法の一つは、多次元配列を利用するものです。この表現方法では、画像の最小単位はピクセルです。この場合、画像が二つの空間次元を持っており、一つの軸は画像の幅に沿い、別の軸が高さに沿っています。また、両軸は互いに垂直に交わっています。上記の互いに垂直に交わる二つの軸は、2Dデカルト座標系の軸と見ることもできます。この場合、各ピクセルは、2Dデカルト座標系で表示される点に対応しています。このような座標系を画像座標系と呼びます。異なるライブラリでは、画像座標系の特定のコンポーネント(原点の位置や座標の順序、軸の方向など)の定義が異なる場合があります。

この記事では、OpenCVとNumPyの画像座標系に絞って、NumPy画像座標系をOpenCV画像座標系に変換する方法について見ていきましょう。 

 

OpenCVとNumPyの画像座標系

OpenCV画像座標系の原点は、画像の左上隅にあり、X軸は画像の幅に沿い、Y軸は画像の高さに沿っています。画像の各ピクセルは、空間座標(x, y)で表され、xはX軸に沿った値、yはY軸に沿った値です。

図2

NumPy画像座標系の原点は、画像の左上隅にあり、C軸は画像の幅に沿い、R軸は画像の高さに沿っています。画像の各ピクセルは、空間座標(c, r)で表され、cはC軸に沿った値、rはR軸に沿った値です。

図3

図2と図3から、OpenCV画像座標系のX軸、Y軸はそれぞれ、NumPy画像座標系のC軸、R軸に一致しているのが明らかです。どちらの画像座標系でも原点は同じピクセルです。

ここまでは特に問題ありませんが、この二つの座標系の間で座標を変換する際は、理解しておくべき微妙な違いがあります。

OpenCV画像座標系で(x, y)の座標で示される任意の点Pについて考えてみましょう。 NumPy画像座標系では、同じ点が(c, r)で表されます。つまり、(x, y)==(c, r)(あるいは(x == c)および(y == r))の場合、両方の座標は同じ点を示しています。

次に、点Pで画像操作が必要であり、画像がNumPy配列imgに格納されているとしましょう。OpenCVの画像操作関数を利用する場合は通常、座標情報を(x, y)の順序で提供する必要があります。一方、NumPyを利用して点Pの要素にアクセスするためには、img[r, c]を実行する必要があります。

NumPy配列imgをインデックス付けするための次元の順序がc, rではなくr, cになっていることにお気づきになったでしょうか。このように、両方のライブラリを使用して作業する場合は、座標情報の順序に注意する必要があります。

実例を使ってOpenCVとNumPyを利用してみると、このことがもっとよく理解できます。次のセクションでは、両方のライブラリを使用する必要があり、一方の座標系から別の座標系に座標を変換しなければならないトイプロブレムについて考えてみましょう。

 

OpenCVとNumPyとの間の座標変換事例

次のようにトイプロブレムを定義します。

問題文: 下図に示される入力画像の白いピクセルの周りにそれぞれ赤い正方形を一つ描きます。各正方形の辺の長さは6ピクセルで、白いピクセルはそれぞれの正方形の重心に位置するようにします。

図4

上図の入力画像および出力画像のサイズはかなり小さいのですが(幅=50ピクセル、高さ=25ピクセル)、視覚的な分析と説明を容易にするために拡大して表示しています。入力画像と出力画像で表示されている「小さな白い正方形」は実際、個々の白いピクセルです。

 

ソリューション

白いピクセルは五つあるので、ボックスを五つ描く必要があります。入力画像がNumPy配列imgに格納されているとすると、次のようなステップでこの問題を解くことができます。

  • ステップ1: NumPyを利用して白いピクセルの座標を取得します。これによって、各正方形の重心の座標が取得できます。
  • ステップ2: 重心の座標と辺の長さを利用して、各正方形の左上および右下の座標を計算します。
  • ステップ3: 左上および右下の座標を利用し、OpenCVを用いて各正方形を描きます。

まず、ステップ1から始めましょう。以下のコードスニペットを利用して白いピクセルのNumPy座標を求めます。これに関する詳細な説明は、NumPyで実装する画像処理の記事をご覧ください。

mask = np.all(img == [255255255], axis = -1)rows, cols = mask.nonzero()

変数rowsおよびcolsには、全ての白いピクセルの座標情報が含まれています。具体的に言うと、rows[i]とcols[i]が、i番目の白いピクセルの行および列情報を提供します(iは、0、1、2、3、4のいずれかです)。白いピクセルは五つあるので、rowsとcolsにはそれぞれ五つの値が含まれています。これでステップ1は終了です。

次に、ステップ2とステップ3をまとめて考えてみましょう。左上の座標と右下の座標がわかれば、OpenCVのcv2.rectangleを用いて正方形を描くことができます。各正方形の左上の座標と右下の座標は、重心の座標と辺の長さを用いて計算できます。

重心の座標が(x, y)、辺の長さがAだとすると、左上の座標は(x – A/2, y – A/2)、右下の座標は(x + A/2, y + A/2)で求めることができます。下図はこれを図示したものです。

図5

上記の計算はかなりシンプルですが、重心の情報はNumPyを利用して計算されており、NumPy座標系表示であることに注意してください。一方、cv2.rectangle関数を利用する際は、OpenCV座標系による左上の座標と右下の座標が必要です。

ここで、前のセクションで学んだ概念が役立ちます。OpenCV座標系で(x, y)、NumPy座標系で(c, r)と表される点があったとしましょう。

既にご説明したように、(x, y)==(c, r)の場合、両者は同じ点を示しています。つまり、必要なx座標は全て変数colsに格納されており、必要なy座標は全て変数rowsに格納されているのです。x座標とy座標がどのように格納されているのかがわかれば、左上と右下の座標を計算するためのコードを書くのは簡単です。以下のコードスニペットは、ここまでご説明した上記の二つのステップを実行する方法を示しています。

for xy in zip(cols, rows):    top_left = (int(x - side_length / 2), int(y - side_length / 2))    bot_right = (int(x + side_length / 2), int(y + side_length / 2))    img = cv2.rectangle(        img, top_left, bot_right,        color = (0, 0, 255), thickness = 1

これで、トイプロブレムは解決できました。以上は単なるトイプロブレムでしたが、このような方法でOpenCVとNumPyを学んでおけば、より実用的な問題に拡張できます。例えば、対象オブジェクト(リンゴなど)の重心とサイズをOpenCV画像座標系を用いて表示するアルゴリズムがあったとしましょう。NumPyインデクシングを用いてリンゴを囲む長方形の領域を切り取る場合には、NumPy座標系で隅の座標を取得する必要があります。このコードにご興味があれば、colabノートブックをご覧ください。

本記事では、OpenCVとNumPyの座標系を使って画像を表現する方法について見てきました。さらに、一つの座標系から別の座標系に点を変換する方法もご紹介いたしました。冒頭でお伝えしたように、これらの概念は画像操作を理解し、実装する上で役立ちます。 例えば、カスタム画像ベースのデータ拡張アルゴリズム(特定の点を中心としてオブジェクトを回転させるなど)をゼロから実装したい場合にも、座標系の知識が重要です。また、ハフ変換などの特徴抽出手法を理解したり実装したりする際も、座標系の知識が必要です。さらに、画像操作で他のライブラリを使用したいときでも、これらの概念に精通しておけば、どのような座標系の間の微妙の違いについても考えることができるでしょう。

※ 本記事は2020年10月15日、弊社英語ブログに掲載された寄稿記事に基づいたものです。

 

AI向け教師データサービスLionbridge.ai

当社は20年以上に渡るAIプロジェクトの実績を持ち、データ作成・アノテーションサービスを提供しております。データサイエンティストや言語学者を含み、100万人のアノテーターが登録されているので、大規模なAIプロジェクトも迅速且つ正確に仕上げます。アノテーターは秘密保持契約に署名することが義務付けられており、データ保護のためにオンサイトスタッフやリモートスタッフを派遣し、アノテーターにお客様ご指定のツールを利用してもらうこともできます。必要に応じて案件に特化した秘密保持契約も作成できるので、データの安全性も保証しております。ご相談・無料トライアルはこちらから。

 
AI向け教師データの作成やアノテーションサービスを提供し、研究開発をサポートします。

メディア掲載結果

    AI・機械学習の最新情報をお届けします!

    Lionbridge AIのブログで紹介している事例記事やトレンドニュースといったビジネスに役立つ情報はもちろん、オープンデータセット集なども合わせてメール配信しております。