numpyの2次元配列の非ゼロ領域を囲む四角形の情報を取得する方法について理解する

本記事は、Stackoverflowにに記載されているnumpyの2次元配列の非ゼロ領域を囲む四角形の情報を取得する方法について理解を深めるための記事です。

2. numpyの2次元配列の非ゼロ領域を囲む四角形の情報を取得する方法

以下のURLを参照してください。関数自体は非常にコンパクトで5行くらいで記載されています。本記事では、この5行くらいのコードについて理解を深めるために解説を行います。

stackoverflow.com

上記によると、2次元配列の非ゼロ領域を囲む四角形の情報を取得するコードは以下になります。

def bbox2(img):
    rows = np.any(img, axis=1)
    cols = np.any(img, axis=0)
    rmin, rmax = np.where(rows)[0][[0, -1]]
    cmin, cmax = np.where(cols)[0][[0, -1]]

    return rmin, rmax, cmin, cmax

3. 解説

なぜ、上記のコードで非ゼロ領域を囲む四角形の情報が得られるかを解説します。

以下の図1にアルゴリズムの動作をグラフで表しました。np.anyを使って行と列を圧縮しています。np.anyを使うと非ゼロをみつけたら計算が打ち切られるため限界まで高速化できていると思います。行方向に圧縮したものは行のサマリ(要約)に、列方向に圧縮したものは列のサマリであると解釈するとわかりやすいかもしれません。そして、その行のサマリの左端と右端、列のサマリの上端と下端の位置が我々が欲する四角形の情報です。それらをnp.whereを使用して求めています。ここではnp.whereは引数で与えられた1次元配列のTrueのインデックス番号を取得するために使用しています。np.whereの戻り値として2次元配列が戻ってきますが、インデックス番号0しか使わないため

np.where(x)[0]

のように0を指定しています。また、

np.where(x)[0][0]
np.where(x)[0][-1]

のようにインデックスに0と-1を指定しているのは左端と右端、上端と下端を得るためです。ちなみに、ここで使用しているnp.whereはnp.nonzeroと同じです*1。最終的に図1の右下にある式が得られます。

f:id:keimina:20190110192941j:plain
図1

図1の右下にある式とStackoverflowにある式とでは見た目が違うので(実際の処理内容は同じですが)式を変形して同じになるか確認します。以下の図2のように、どんどんワンライナーでコードの行数を圧縮します。すると最終的にStackoverflowの回答と同じような形の式が得られます。

f:id:keimina:20190109221605j:plain
図2

4. 注意点

np.whereで得ようとしている配列(2.に記載した関数bbox2の引数img)がすべて0やFalseのときインデックスエラーになるはずですので注意が必要かと思います。

以上です。おやすみなさい。

*1:詳細は公式サイトを参照してください https://docs.scipy.org/doc/numpy-1.15.0/reference/generated/numpy.where.html