Numpyのndarray型を使ったOpenCVの画像データ覚書

python コンピュータ
python

Pythonで画像データを操作する際numpyライブラリのndarray型を使います。
一見普通の配列と同じようにも思えますが、配列を操作に便利な機能が沢山あるようなので少しずつ調べて学びたいと思います。

ndarray型の初期化

初期値を指定しない

import numpy as np

a = np.ndarray(shape=(3,3), dtype='uint8')

print(a)

#[[48 22 206]
# [4 1 0]
# [0 0 20]]
引数shapeの(高さ,幅[,チャンネル数])で配列の要素数を指定
dtypeで要素の型を指定
今回のサンプルは符号なし整数8ビット、3×3の2次元配列が出来上がります。
主にグレースケールの画像に使います。
カラーの場合shapeのチャンネル数に3、透明度を含む場合4をセットします。
ndarrayは初期値を指定しない為、初期化された配列の値は不定で、実行するたびに結果が異なりました。

 

0で初期化

import numpy as np

b = np.zeros((3,3), dtype='uint8')
print(b)
#[[0 0 0]
# [0 0 0]
# [0 0 0]]
引数はndarrayと同じ値をセットしましたが、zerosの場合初期値が0でセットされます。
この配列を画像として保存すると真っ黒な画像ファイルが出来上がります。

任意の値で初期化

グレースケール

import numpy as np

c = np.full(shape=(3,3), fill_value=255, dtype='uint8')
print(c)
[[255 255 255]
 [255 255 255]
 [255 255 255]]
fill_valueに配列の要素の初期価値をセットします。サンプルでは255で初期化しました。

カラー(透明度付き)

import numpy as np

c = np.full(shape=(3,3,4), fill_value=(255,255,255,0), dtype='uint8')
print(c)
[[[255 255 255   0]
  [255 255 255   0]
  [255 255 255   0]]

 [[255 255 255   0]
  [255 255 255   0]
  [255 255 255   0]]

 [[255 255 255   0]
  [255 255 255   0]
  [255 255 255   0]]]
fill_valueに配列をセットしました。

既存のndarrayオブジェクトの構造をコピー

0で初期化

import numpy as np

c = np.full(shape=(3,3,4), fill_value=(255,255,255,0), dtype='uint8')
d = np.zeros_like(c)
print(d)
[[[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]]]

任意の数値で初期化

import numpy as np

a = np.zeros(shape=(3,3), dtype='uint8')
b = np.full_like(a, 255)
print(b)
[[255 255 255]
 [255 255 255]
 [255 255 255]]

既存のndarrayオブジェクトをコピー

import numpy as np

org = np.zeros(shape=(3,3), dtype='uint8')
clon = org.copy()
clon[1][1] = np.uint8(255)
print(clon)
#[[  0   0   0]
# [  0 255   0]
# [  0   0   0]]
# コピー先を書き換えても
print(org)
#[[0 0 0]
# [0 0 0]
# [0 0 0]]
# コピー元(オリジナル)は変更されない
コピー先を書き換えてもコピー元が変更されていないので、コピー元とコピー先は別のオブジェクトになっていることが、確認出来る。

チャンネル

チャンネル配列の並びはBGR、アルファチャンネル付きでBGRA

import cv2
import numpy as np

img = np.full(shape=(64,64,3), fill_value=(255,0,0), dtype='uint8')

cv2.imwrite('blur.png', img)

import cv2
import numpy as np

img = np.full(shape=(64,64,3), fill_value=(0,255,0), dtype='uint8')

cv2.imwrite('green.png', img)

import cv2
import numpy as np

img = np.full(shape=(64,64,3), fill_value=(0,0,255), dtype='uint8')

cv2.imwrite('red.png', img)

アルファチャンネル

import cv2
import numpy as np

img = np.full(shape=(64,64,4), fill_value=(0,0,0,255), dtype='uint8')

cv2.imwrite('black.png', img)

アルファチャンネル:0透明~255不透明

チャンネルの分離

import cv2
import numpy as np

h = 3
w = 3
img = np.full(shape=(h,w,4), fill_value=(127,127,255,255), dtype='uint8')

blur = img[:,:,0]
green = img[:,:,1]
red = img[:,:,2]
alpha = img[:,:,3]
print(blur)
#[[127 127 127]
# [127 127 127]
# [127 127 127]]
print(green)
#[[127 127 127]
# [127 127 127]
# [127 127 127]]
print(red)
#[[255 255 255]
# [255 255 255]
# [255 255 255]]
print(alpha)
#[[255 255 255]
# [255 255 255]
# [255 255 255]]

チャンネルの結合

import cv2
import numpy as np

h = 128
w = 64
r = np.full(shape=(h,w), fill_value=255, dtype='uint8')
g = np.full(shape=(h,w), fill_value=127, dtype='uint8')
b = np.full(shape=(h,w), fill_value=127, dtype='uint8')
a = np.full(shape=(h,w), fill_value=255, dtype='uint8')

img = np.stack(arrays=(b,g,r, a), axis=-1)

print(img)

cv2.imwrite('pink.png', img)

グレースケールの配列同士の加算

import cv2
import numpy as np

h = 3
w = 3
a = np.full(shape=(h,w), fill_value=128, dtype='uint8')
b = np.full(shape=(h,w), fill_value=128, dtype='uint8')

# uint8をint64にキャストして加算
c = np.add(a.astype(np.int64), b.astype(np.int64))

# uint8に収まらない数値を置き換え
c[c>255] = 255
c[c<0] = 0

# int64をuint8にキャスト
c = c.astype(np.uint8)
print(c)
#[[255 255 255]
# [255 255 255]
# [255 255 255]]
配列同士は普通にc = a + bの様に計算することが出来ます。
上記のサンプルの128+128では答えが256となってしまいuint8で表現できる最大値255超えてしまいます。
エラーではありませんが結果が0となります。
これだと都合が悪い場合があるので、uint8の最大値(最小値)を超える計算結果になる場合、置き換えるようにしています。
もっと良い方法がありそうですが、見つけることが出来ません。

グレースケール同士の減算

import cv2
import numpy as np

# 配列の各要素を指定して初期化
th = np.array([[0,255,0],[255,0,255],[0,255,0]], np.uint8)
print(th)
#[[  0 255   0]
# [255   0 255]
# [  0 255   0]]

# 配列の要素を全て128で初期化
gray = np.full((3,3), 128, np.uint8)
print(gray)
#[[128 128 128]
# [128 128 128]
# [128 128 128]]

# 反転
nth = np.bitwise_not(th)
print(nth)
#[[255   0 255]
# [  0 255   0]
# [255   0 255]]

# int64にキャストして減算しょり
result64 = gray.astype(np.int64) - nth.astype(np.int64)
print(result64)
#[[-127  128 -127]
# [ 128 -127  128]
# [-127  128 -127]]

# 負の数を0に置き換え
result64 = np.where(result64<0, 0, result64)
# uint8へキャスト
result = result64.astype(np.uint8)
print(result)
#[[  0 128   0]
# [128   0 128]
# [  0 128   0]]
引く数が大きい場合マイナスなりますので、念のため一度負の数も扱えるint64型にキャストしてから計算しています。
画像データを加算や減算してどうなるかと言いますと、2つの画像の合成することになります。一定数を全てのピクセルに加減算すればグレーケール画像では濃淡が変化することに成ります。

各チャンネルが同一か確認

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つ書いていますが、もっと効率的な方法があると思います。

コメント