NumPy は Python で行列を扱うためのライブラリですが、その ndarray 型オブジェクトは OpenCV における画像データとしてそのまま利用できます。画像は縦×横の2次元配列として扱え、RGBA など複数チャンネルの場合でも 3 次元配列で表現できるため、配列演算が得意な NumPy を活用することで、通常の Python のループによるアクセスよりも圧倒的に高速に処理できます。
以前書いた記事の文書量が多くなってきたので、記事を分けます。
各チャンネルが同一か確認
import cv2
import numpy as np
# 配列の比較
# ファイルの読み込み(RGB)
src = cv2.imread("12-35.jpg")
# チャンネル分離
blur = src[:,:,0]
green = src[:,:,1]
red = src[:,:,2]
# 比較
if (np.all(blur == green) and np.all(green == red)):
print("true")
else:
print("false")
blur == greenのように配列同士を比較することができますが、各要素ごとに比較しその結果のブール値(真偽)の配列として返ります。np.all()で配列のすべての要素が真か偽かを返します。サンプルコードではRGBの3チャンネルがありますので、条件式をを2つ書いていますが、もっと効率的な方法があると思います。
np.all()全ての要素がTureか判定
NumPy 配列のすべての要素が True(真)かどうかを判定する関数です。
基本的な使い方
import numpy as np
a = np.array([True, True, True])
np.all(a) # → True
1つでも False が含まれると False になります。
a = np.array([True, False, True])
np.all(a) # → False
数値配列の場合
数値配列は 0 → False、それ以外 → True と判断されます。
np.all([1, 2, 3]) # → True
np.all([1, 0, 3]) # → False
多次元配列での使い方(axis 指定)
例えば 2 次元配列:
a = np.array([
[1, 2, 3],
[4, 0, 6]
])
全要素が True?
np.all(a) # → False(0 があるため)
行ごとに判定(axis=1)
np.all(a, axis=1)
# → [ True, False ]
最初の行は1, 2, 3で、全て0では無いのでTrue
次の行は4, 0, 6で、0が含まれているのでFalseになります。
列ごとに判定(axis=0)
np.all(a, axis=0)
# → [ True, False, True ]
最初の列は1,4でTrue
次の列は2,0でFalse
最後の列は3,6でTrueになります。
全ての要素が255か判定
aがグレースケールの場合、真っ白な画像かの判定になります。
np.all(a == 255)
3×3カーネルで畳み込み演算
import numpy as np
def conv3x3_same(img, k):
"""
8ビットグレースケール画像 (uint8) に対する3x3畳み込み(sameサイズ)
- 出力サイズは img と同じ
- 端は元画像のまま残す
"""
h, w = img.shape
# 出力画像:まず元の画像をコピーする
out = img.copy()
# カーネルの展開
k00, k01, k02 = k[0]
k10, k11, k12 = k[1]
k20, k21, k22 = k[2]
# ---- スライスによる畳み込み ----
acc = (
img[0:h-2, 0:w-2] * k00 +
img[0:h-2, 1:w-1] * k01 +
img[0:h-2, 2:w ] * k02 +
img[1:h-1, 0:w-2] * k10 +
img[1:h-1, 1:w-1] * k11 +
img[1:h-1, 2:w ] * k12 +
img[2:h , 0:w-2] * k20 +
img[2:h , 1:w-1] * k21 +
img[2:h , 2:w ] * k22
)
# 結果を書き込む(1〜h-2, 1〜w-2)
out[1:h-1, 1:w-1] = np.clip(acc, 0, 255).astype(np.uint8)
return out
使用例
・平均化カーネル
kernel = np.array([
[ 1/9, 1/9, 1/9],
[ 1/9, 1/9, 1/9],
[ 1/9, 1/9, 1/9]
], dtype=float)
・シャープ化カーネル
kernel = np.array([
[ 0, -1, 0],
[-1, 5, -1],
[ 0, -1, 0]
], dtype=float)
呼び出し:
out = conv3x3_same(img, kernel)
・カーネルの展開
k00, k01, k02 = k[0]
kの最初の行の各項目の値を、k00,k01,k02に順番に代入している式。
・畳み込み
img[0:h-2, 0:w-2] * k00 +
スライスで切り出した領域(img の一部)に対して、カーネル要素 k00 を要素ごとに乗算した結果を返します。
続く k01〜k22 の各スライスも同様に計算され、それらがすべて要素ごとに加算されることで、3×3 の畳み込み(積和演算)が成立します。
・参考二重ループ版
for y in range(1, h-1):
for x in range(1, w-1):
out[y, x] = (
img[y-1, x-1] * k00 +
img[y-1, x ] * k01 +
img[y-1, x+1] * k02 +
img[y , x-1] * k10 +
img[y , x ] * k11 +
img[y , x+1] * k12 +
img[y+1, x-1] * k20 +
img[y+1, x ] * k21 +
img[y+1, x+1] * k22
)
・結果を書き込む
np.clip(acc, 0, 255) は値を画像として扱える範囲(0〜255)に収め、
.astype(np.uint8) によって 8 ビットの画像形式に変換しています。
3×3カーネルで畳み込み演算(低速版)
二重ループを使った低速版
import numpy as np
def blur3x3_slow(img):
"""
8ビットグレースケール画像 (uint8) に対する 3x3 ぼかし処理(低速版)。
・2重ループで素直に処理
・中心画素は3x3の平均をとる
・端の1ピクセルはそのままコピー
"""
h, w = img.shape
out = img.copy() # 出力(sameサイズ)
# 1〜h-2, 1〜w-2 の範囲だけ3x3を評価(境界はそのまま)
for y in range(1, h-1):
for x in range(1, w-1):
# 3x3領域を取得
region = img[y-1:y+2, x-1:x+2]
# 平均値(ぼかし)
avg = np.sum(region) / 9.0
# 書き込み(0〜255に収める)
out[y, x] = int(avg) # または np.clip(int(avg), 0, 255)
return out
np.sum()/9.0をnp.np.median()に置き換えるとメディアンフィルター


コメント