ランダムな値と周囲のゼロを含む2D numpy配列があるとします。
例「傾斜した長方形」:
import numpy as np
from skimage import transform
img1 = np.zeros((100,100))
img1[25:75,25:75] = 1.
img2 = transform.rotate(img1, 45)
ここで、すべての非ゼロデータの最小の境界矩形を見つけたいです。例えば:
a = np.where(img2 != 0)
bbox = img2[np.min(a[0]):np.max(a[0])+1, np.min(a[1]):np.max(a[1])+1]
最速この結果を達成する方法は何でしょうか?私がいる場合、np.where関数はかなり時間がかかるので、より良い方法があると確信しています1000x1000データセットを使用します。
編集:3Dでも動作するはずです...
np.any
を使用してすべての非ゼロ値のインデックスを見つけるのではなく、np.where
を使用してゼロ以外の値を含む行と列を1Dベクトルに減らすことで、実行時間をほぼ半減できます。
def bbox1(img):
a = np.where(img != 0)
bbox = np.min(a[0]), np.max(a[0]), np.min(a[1]), np.max(a[1])
return bbox
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
いくつかのベンチマーク:
%timeit bbox1(img2)
10000 loops, best of 3: 63.5 µs per loop
%timeit bbox2(img2)
10000 loops, best of 3: 37.1 µs per loop
このアプローチを3Dケースに拡張するには、各軸のペアに沿って縮小を実行するだけです。
def bbox2_3D(img):
r = np.any(img, axis=(1, 2))
c = np.any(img, axis=(0, 2))
z = np.any(img, axis=(0, 1))
rmin, rmax = np.where(r)[0][[0, -1]]
cmin, cmax = np.where(c)[0][[0, -1]]
zmin, zmax = np.where(z)[0][[0, -1]]
return rmin, rmax, cmin, cmax, zmin, zmax
itertools.combinations
を使用して軸の一意の各組み合わせを反復処理して削減を実行することにより、これを[〜#〜] n [〜#〜]次元に一般化するのは簡単です。 :
import itertools
def bbox2_ND(img):
N = img.ndim
out = []
for ax in itertools.combinations(reversed(range(N)), N - 1):
nonzero = np.any(img, axis=ax)
out.extend(np.where(nonzero)[0][[0, -1]])
return Tuple(out)
元の境界ボックスの角の座標、回転角、回転の中心がわかっている場合、対応する アフィン変換行列 を計算することにより、変換された境界ボックスの角の座標を直接取得できます=そして、入力座標で点を打つ:
def bbox_rotate(bbox_in, angle, centre):
rmin, rmax, cmin, cmax = bbox_in
# bounding box corners in homogeneous coordinates
xyz_in = np.array(([[cmin, cmin, cmax, cmax],
[rmin, rmax, rmin, rmax],
[ 1, 1, 1, 1]]))
# translate centre to Origin
cr, cc = centre
cent2ori = np.eye(3)
cent2ori[:2, 2] = -cr, -cc
# rotate about the Origin
theta = np.deg2rad(angle)
rmat = np.eye(3)
rmat[:2, :2] = np.array([[ np.cos(theta),-np.sin(theta)],
[ np.sin(theta), np.cos(theta)]])
# translate from Origin back to centre
ori2cent = np.eye(3)
ori2cent[:2, 2] = cr, cc
# combine transformations (rightmost matrix is applied first)
xyz_out = ori2cent.dot(rmat).dot(cent2ori).dot(xyz_in)
r, c = xyz_out[:2]
rmin = int(r.min())
rmax = int(r.max())
cmin = int(c.min())
cmax = int(c.max())
return rmin, rmax, cmin, cmax
これは、小さなサンプル配列にnp.any
を使用するよりもわずかに高速であることがわかります。
%timeit bbox_rotate([25, 75, 25, 75], 45, (50, 50))
10000 loops, best of 3: 33 µs per loop
ただし、このメソッドの速度は入力配列のサイズに依存しないため、大きな配列の場合は非常に高速になります。
変換アプローチを3Dに拡張するのは少し複雑です。回転には3つの異なるコンポーネント(x軸、y軸、z軸)がありますが、基本的な方法は同じです。 :
def bbox_rotate_3d(bbox_in, angle_x, angle_y, angle_z, centre):
rmin, rmax, cmin, cmax, zmin, zmax = bbox_in
# bounding box corners in homogeneous coordinates
xyzu_in = np.array(([[cmin, cmin, cmin, cmin, cmax, cmax, cmax, cmax],
[rmin, rmin, rmax, rmax, rmin, rmin, rmax, rmax],
[zmin, zmax, zmin, zmax, zmin, zmax, zmin, zmax],
[ 1, 1, 1, 1, 1, 1, 1, 1]]))
# translate centre to Origin
cr, cc, cz = centre
cent2ori = np.eye(4)
cent2ori[:3, 3] = -cr, -cc -cz
# rotation about the x-axis
theta = np.deg2rad(angle_x)
rmat_x = np.eye(4)
rmat_x[1:3, 1:3] = np.array([[ np.cos(theta),-np.sin(theta)],
[ np.sin(theta), np.cos(theta)]])
# rotation about the y-axis
theta = np.deg2rad(angle_y)
rmat_y = np.eye(4)
rmat_y[[0, 0, 2, 2], [0, 2, 0, 2]] = (
np.cos(theta), np.sin(theta), -np.sin(theta), np.cos(theta))
# rotation about the z-axis
theta = np.deg2rad(angle_z)
rmat_z = np.eye(4)
rmat_z[:2, :2] = np.array([[ np.cos(theta),-np.sin(theta)],
[ np.sin(theta), np.cos(theta)]])
# translate from Origin back to centre
ori2cent = np.eye(4)
ori2cent[:3, 3] = cr, cc, cz
# combine transformations (rightmost matrix is applied first)
tform = ori2cent.dot(rmat_z).dot(rmat_y).dot(rmat_x).dot(cent2ori)
xyzu_out = tform.dot(xyzu_in)
r, c, z = xyzu_out[:3]
rmin = int(r.min())
rmax = int(r.max())
cmin = int(c.min())
cmax = int(c.max())
zmin = int(z.min())
zmax = int(z.max())
return rmin, rmax, cmin, cmax, zmin, zmax
here の回転行列式を使用して上記の関数を本質的に変更しました。テストケースを作成する時間がまだないので、注意して使用してください。
N次元配列の境界ボックスを計算するアルゴリズムは次のとおりです。
def get_bounding_box(x):
""" Calculates the bounding box of a ndarray"""
mask = x == 0
bbox = []
all_axis = np.arange(x.ndim)
for kdim in all_axis:
nk_dim = np.delete(all_axis, kdim)
mask_i = mask.all(axis=Tuple(nk_dim))
dmask_i = np.diff(mask_i)
idx_i = np.nonzero(dmask_i)[0]
if len(idx_i) != 2:
raise ValueError('Algorithm failed, {} does not have 2 elements!'.format(idx_i))
bbox.append(slice(idx_i[0]+1, idx_i[1]+1))
return bbox
次のように2D、3Dなどの配列で使用できます。
In [1]: print((img2!=0).astype(int))
...: bbox = get_bounding_box(img2)
...: print((img2[bbox]!=0).astype(int))
...:
[[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 1 1 1 1 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 1 1 1 1 1 1 0 0 0 0 0 0 0]
[0 0 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0]
[0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0]
[0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0]
[0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0]
[0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0]
[0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0]
[0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0]
[0 0 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0]
[0 0 0 0 0 0 0 1 1 1 1 1 1 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 1 1 1 1 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]]
[[0 0 0 0 0 0 1 1 0 0 0 0 0 0]
[0 0 0 0 0 1 1 1 1 0 0 0 0 0]
[0 0 0 0 1 1 1 1 1 1 0 0 0 0]
[0 0 0 1 1 1 1 1 1 1 1 0 0 0]
[0 0 1 1 1 1 1 1 1 1 1 1 0 0]
[0 1 1 1 1 1 1 1 1 1 1 1 1 0]
[1 1 1 1 1 1 1 1 1 1 1 1 1 1]
[1 1 1 1 1 1 1 1 1 1 1 1 1 1]
[0 1 1 1 1 1 1 1 1 1 1 1 1 0]
[0 0 1 1 1 1 1 1 1 1 1 1 0 0]
[0 0 0 1 1 1 1 1 1 1 1 0 0 0]
[0 0 0 0 1 1 1 1 1 1 0 0 0 0]
[0 0 0 0 0 1 1 1 1 0 0 0 0 0]
[0 0 0 0 0 0 1 1 0 0 0 0 0 0]]
np.diff
およびnp.nonzero
1人による呼び出しnp.where
の方が良いかもしれません。
np.where
をnp.argmax
に置き換えてブールマスクを操作することで、もう少しパフォーマンスを絞ることができました。
def bbox(img): img =(img> 0) rows = np.any(img、axis = 1) cols = np.any (img、axis = 0) rmin、rmax = np.argmax(rows)、img.shape [0]-1-np.argmax(np.flipud(rows)) cmin、 cmax = np.argmax(cols)、img.shape [1]-1-np.argmax(np.flipud(cols)) は、rmin、rmax、cmin、cmaxを返します
これは、同じベンチマークで、上記のbbox2ソリューションよりも約10µs高速でした。 argmaxの結果を使用してゼロ以外の行と列を見つける方法もあり、np.any
を使用して余分な検索を行わないようにする必要がありますが、これにはいくつかのトリッキーなインデックス作成が必要になる場合があります単純なベクトル化されたコードで効率的に作業します。