画像の基本処理

目的

Learn to:

  • 画素値のアクセス及び変更方法
  • 画像の属性情報の取得
  • 画像中の注目領域(ROI)の設定
  • 画像の分割と統合

このセクションで使用するほとんどすべての処理はOpenCVよりもNumpyに関係する. 最適化されたOpenCVのコードを書くにはNumpyの知識が必要である.

( このページに載せてあるコードはそれぞれ数行程度のコードばかりなので,Pythonのターミナル上で実行した結果を載せている.)

画素値のアクセスと変更方法

まず初めにカラー画像を読み込もう:

>>> import cv2
>>> import numpy as np

>>> img = cv2.imread('messi5.jpg')

ある画素の行と列の座標を指定することで画素値にアクセスできる.BGR画像の画素値は青,緑,赤の色成分の値の配列,グレースケール画像の画素値は明るさを返す.

>>> px = img[100,100]
>>> print (px)
[157 166 200]

# accessing only blue pixel
>>> blue = img[100,100,0]
>>> print (blue)
157

同様のアクセス方法を使えば,画素値の変更ができる.

>>> img[100,100] = [255,255,255]
>>> print (img[100,100])
[255 255 255]

Warning

Numpyは配列計算を高速におこなうために最適化されたライブラリである.そのため,画素値の取得や変更を各画素に対して行う処理は処理時間がかかってしまうので推奨されない.

Note

上述した方法は配列の領域(例えば最初の5列と最後の3行)を選択する時によく使われる.各画素へのアクセスに関してはNumpyの配列を扱う array.item()array.itemset() を使うと良い.しかし,これらの関数は常にスカラー値を返すため,青,緑,赤の全画素値にアクセスするには array.item() を全ての色成分に対して行う必要がある.

よりよい画素へのアクセス方法と変更方法 :

# accessing RED value
>>> img.item(10,10,2)
59

# modifying RED value
>>> img.itemset((10,10,2),100)
>>> img.item(10,10,2)
100

画像の属性情報の取得

画像の属性情報とは,列の数,行の数,チャンネル数(色相数),画像データの型,画素数などを意味する.

画像の形状は img.shape によって取得できる.返戻値は 行数,列数,チャンネル数(カラー画像であれば)のtupleである:

>>> print (img.shape)
(342, 548, 3)

Note

グレースケール画像であれば返戻値のtupleは行数と列数しか含まない. そのため,この方法は画像がグレースケールかカラーか調べる良い方法と言える.

合計画素数は img.size で調べられる :

>>> print(img.size)
562248

画像データのデータ型は img.dtype によって得られる:

>>> print(img.dtype)
uint8

Note

img.dtype はデバッグの際に非常に重要になる.なぜならOpenCV-Pythonで実装していく上で生じる多くのエラーが不正なデータ型によって発生するからである.

画像中の注目領域(ROI)

画像の特定の領域に対して何らかの処理をする必要が生じることがある.画像中から目を検出するなら,まずは画像全体に対して顔検出を行い,次に検出した顔の内部で目の検出をするだろう.この方法は精度の向上をすると共に,(目の検出は画像中の一部の領域のみに適用すればよくなるため)パフォーマンスの向上にもつながる.

注目領域(ROI)の指定にはNumpyのインデックスを使う.ここでは,画像中のボールの位置を選択し別の場所にコピーする:

>>> ball = img[280:340, 330:390]
>>> img[273:333, 100:160] = ball

結果は以下のようになる: (コード)

Image ROI

画像の色成分の分割と統合

画像の青,緑,赤成分は必要であれば独立した色成分へと分割できる.独立した色成分を統合してBGR画像をもう一度作ることも可能である:

>>> b,g,r = cv2.split(img)
>>> img = cv2.merge((b,g,r))

もしくは以下のように,ある色成分だけ抽出することができる:

>>> b = img[:,:,0]

画像中の赤の色成分だけ全て0に設定したいが,その他の色成分と分割はしたくないと仮定する.Numpyのインデックスを使えばより速く実現できる.

>>> img[:,:,2] = 0

Warning

cv2.split() は(処理速度の観点で)計算コストの大きい処理になるため,必要な時のみ実行するようにしよう.Numpyのインデックスは効率がよいので,可能ならこれを使うようにしよう.

画像の境界領域を作る(パディング)

画像にフォトフレームのような境界線を引くには cv2.copyMakeBorder() 関数を使う.しかし,画像のconvolutionやゼロパディングといった処理もある.この関数の引数を以下に示す:

  • src - 入力画像

  • top, bottom, left, right - 境界の各方向に対する線幅

  • borderType - 追加する境界の種類を指定するためのフラグ.以下のタイプがある:
    • cv2.BORDER_CONSTANT - 単一色の境界を追加する.次の引数で色の指定をする.
    • cv2.BORDER_REFLECT - 鏡に写したかのように境界を追加する.例えば,以下のような境界が得られる: fedcba|abcdefgh|hgfedcb
    • cv2.BORDER_REFLECT_101cv2.BORDER_DEFAULT - cv2.BORDER_REFLECT と同じだが,微妙に違う : gfedcb|abcdefgh|gfedcba
    • cv2.BORDER_REPLICATE - 最後の要素が繰り返し現れる: aaaaaa|abcdefgh|hhhhhhh
    • cv2.BORDER_WRAP - 説明がうまくできないが,こんな風になる : cdefgh|abcdefgh|abcdefg
  • value - フラグが cv2.BORDER_CONSTANT の時に指定する境界の色

次のコードは上記の全パターンを試すコードである: (コード, 画像)

import cv2
import numpy as np
from matplotlib import pyplot as plt

BLUE = [255,0,0]

img1 = cv2.imread('opencv_logo.png')

replicate = cv2.copyMakeBorder(img1,10,10,10,10,cv2.BORDER_REPLICATE)
reflect = cv2.copyMakeBorder(img1,10,10,10,10,cv2.BORDER_REFLECT)
reflect101 = cv2.copyMakeBorder(img1,10,10,10,10,cv2.BORDER_REFLECT_101)
wrap = cv2.copyMakeBorder(img1,10,10,10,10,cv2.BORDER_WRAP)
constant= cv2.copyMakeBorder(img1,10,10,10,10,cv2.BORDER_CONSTANT,value=BLUE)

plt.subplot(231),plt.imshow(img1,'gray'),plt.title('ORIGINAL')
plt.subplot(232),plt.imshow(replicate,'gray'),plt.title('REPLICATE')
plt.subplot(233),plt.imshow(reflect,'gray'),plt.title('REFLECT')
plt.subplot(234),plt.imshow(reflect101,'gray'),plt.title('REFLECT_101')
plt.subplot(235),plt.imshow(wrap,'gray'),plt.title('WRAP')
plt.subplot(236),plt.imshow(constant,'gray'),plt.title('CONSTANT')

plt.show()

以下に示す結果を見てみよう。画像はmatplotlibを使って表示しているため,青と赤の色成分が入れ替わっている): (注: 実は上のコードでは若干違った結果になる。添付したコードと上のコードとを比較してみよ)

Border Types

補足資料

課題