現役JDと学ぶ画像処理入門①〜openCV入門〜

openCV入門(画像の読み込み、表示、保存、線形変換、拡大縮小)

目次

初めまして!東京大学理科三類1年の広部ゆりかです。
好きな食べ物はチョコとラーメン、嫌いな食べ物は味噌汁、マイブームはメンズピアスを眺めることです。
只今機械学習の勉強をしておりまして、ご縁があってIMACEL Academyを運営なさっているLPixel様でインターンをさせて頂けることになり、ここで記事を書かせて頂けることになりました!
まだまだひよっこですがどんどん勉強して記事を更新していこうと思うのでよろしくお願いします!
さて、そんな私の記念すべき第1回目の記事では、オープンソースのコンピューター・ビジョン・ライブラリであるopenCV (http://opencv.org/) の基本的な使い方を説明していきたいと思います。
使用言語はpyhtonで、numpyやmatplotlibなどのライブラリを使っていきます。
openCVのインストール方法についてはIMACEL Academyの記事でもやり方がまとめられていたので、インストールは完了済みという前提で話を進めていこうと思います。

目次

今回は以下のような基本的な操作についてまとめます。

  ①画像の読み込み&表示&保存
  ②アフィン変換による画像の平行移動&回転移動
  ③resizeメソッドを用いた画像の拡大&縮小

①画像の読み込み&表示&保存

Lenna

今日はこの子!色々なところで使われちゃっているLennaちゃんの画像を使って遊んでいきたいと思います!どうせなら可愛い子の写真を使った方が楽しいですもんね!
まずは必要なライブラリをインポートします。
import cv2
import numpy as np    #画像の情報が収められたarrayをいじるためにnumpyを用いる。
from matplotlib import pyplot as plt    #画像の表示に用いる。
%matplotlib inline
import.py

画像を読み込みます

image = cv2.imread('Lenna.jpg')
imread.py

ここでimreadの二つ目の変数に1を入れるとカラー(デフォルト、アルファチャンネルは読み込めない)、0を入れるとグレースケール、-1を入れるとアルファチャンネル込みのそのままの画像が読み込めるみたいです。

image.shape
shape.py
次に上のようにすると、(773, 2890, 3)などと出力され、読み込まれたイメージがndarray(縦、横、channel(RGB,色相のこと))で格納されていることが分かります。つまりこれから画像に処理を行いたい時は、このndarrayの数値をいじっていくことになります。
画像をウィンドウまたはpython notebookで表示していきます
#ウィンドウで表示
#ウィンドウ名を決定
Window_name = 'Lenna'

#ウィンドウの作成 (ウィンドウ名、ウィンドウの表示形式)
cv2.namedWindow(Window_name, cv2.WINDOW_AUTOSIZE)

#ウィンドウに画像表示 (ウィンドウ名、表示する画像の配列)
cv2.imshow(Window_name, image)

#キーが押されるまで画像を表示したままにする
#0→無限、0以上→指定ミリ秒表示
cv2.waitKey(0)

#ウィンドウの破棄 (ウィンドウ名)
cv2.destroyWindow(Window_name)
window.py
#ipython notebookで表示
plt.imshow(image)
ipython notebook.py
さて、可愛いLennaちゃんを表示できたでしょうか??
これは余談なのですが、深夜に一人でPCをいじっているとごく稀に下のような真っ青なLennaちゃんが現れ、その目をじっと見つめていると...??という都市伝説があるそうな...

Lenna

...なんて、嘘、嘘です!!そっとブラウザを閉じないでください!!!
実はOpenCVで画像を読み込むとRGB(レッド、グリーン、ブルーの配列)がBGR(ブルー、グリーン、レッド)の順に読み込まれるため、赤と青が反転したような画像になってしまうのです!
お詫びにちゃんと直し方も書いておきます。
#画像の色空間を変換。(入力ファイル名、(出力ファイル名)、色空間の変換コード)
#cv2.COLOR_BGR2RGBはBGR to RGB (BGRからRGBに変換)という意味。BGR2GRAYなどとするとグレースケール(白黒画像)になる。
rgb_image = cv2.cvtColor(image,cv2.COLOR_BGR2RGB)
plt.imshow(rgb_image)
cvtColor.py

Lenna

はい!Lennaちゃんが元どおりになったはずです!
最後に画像の保存方法です。
#(保存時のファイル名、保存する画像配列)
cv2.imwrite('new_lenna.jpg',image)
imwrite.py

アフィン変換による画像の平行移動&回転移動

アフィン変換を用いた画像の平行移動と回転移動を行います。
数学的な説明は後にして、とりあえず実装を行なっていきましょう!

まずは平行移動からです。
warpAffineメソッドの使い方に注目です!
#縦幅、横幅をそれぞれ取り出す
height, width = image.shape[:2]

#変換行列の指定 横にa,縦にb動かしたい時は[ 1, 0, a],[ 0, 1, b]とする。(詳しくは後ほど!)
M = np.float32([[1,0,100],[0,1,50]])

#画像を平行移動 (入力画像の配列、変換行列、出力画像サイズ)
moved_image  =  cv2.warpAffine(rgb_image, M, (width, height))
plt.imshow(moved_image)
moved.py

moved_image

続いて回転移動です。
#回転の中心を決定 ここでは画像の中心に合わせている
center = (int(width/2),int(height/2))

#変換行列の決定 (回転の中心、回転角度、拡大率)
rotMat = cv2.getRotationMatrix2D(center, 15, 1)

#画像を回転 (入力画像の配列、変換行列、出力画像サイズ)
rotated_image = cv2.warpAffine(rgb_image, rotMat, (width,height))
plt.imshow(rotated_image)
rotate.py

rotated_image

ここで試しにrotMatの中身を見てみると、2行3列の行列が表示されるはずです。これがwarpAffineメソッドに代入された変換行列の正体です!
つまり、このメソッドは元のimageの行列にいいかんじの行列をかけあわせて、目的の行列を出力していた、ということになります。この変換をアフィン変換と呼び、これからその中身について少し詳しく見ていきたいと思います。

(x,y)を原点中心にθ回転させた点を(x',y')とします。
求めたいのは二点の関係式です

(x',y')と(x,y)の間には左の関係式が成り立ちます。

上の関係式より、[x,y,1]に掛け合わせるべき行列は左のようになります。
ここで3行目は[0,0,1]で固定なので上の二行だけを指定すればよく、warpAffineメソッドの2つ目の引数にはこの行列を渡せば良いということになります。
平行移動の時にM =[[1,0,100],[0,1,50]]としたのは、上の式にθ=0,a=100,b=50を代入したわけです。
すっきりしていただけたでしょうか??

③resizeメソッドを用いた画像の拡大&縮小

では最後に画像の拡大と縮小をさらっとやって終わりにしましょう!
まずは画像の拡大から!
#画像を拡大 (入力画像の配列、出力サイズ、fx =横軸方向拡大率、fy=縦軸方向拡大率、interpolation =補間方法の指定)
zoomed_image = cv2.resize(rgb_image, None, fx=2.0, fy=2.0, interpolation = cv2.INTER_LINEAR )
plt.imshow(zoomed_image)
zoom!.py

zoomed_image

はい、簡単ですね!ちなみに最後の引数のinterpolationは、私もよくわからないのですが公式サイトによると
・INTER_NEAREST:最近傍補間
・INTER_LINEAR:バイリニア補間(デフォルト)
・INTER_AREA:ピクセル領域の関係を利用したリサンプリング.画像を大幅に縮小する場合は,モアレを避けることができる良い手法です.しかし,画像を拡大する場合は, INTER_NEAREST メソッドと同様になります
・INTER_CUBIC:4x4 の近傍領域を利用するバイキュービック補間
・INTER_LANCZOS4:8x8 の近傍領域を利用する Lanczos法の補間
だそうです!デフォはINTER_LINEARなそうなので特に気にしないで良いと思います.
詳しくは以下の記事をご覧ください.
上の拡大の例では出力サイズをNone(0とかでも良い)にして拡大率fx、fyを指定しましたが、代わりに出力サイズを指定することもできます。
そちらの方法で今度は縮小を試してみましょう
#出力サイズの指定。割った際にfloatになってしまうのでint()を用いて整数にしましょう
size = (int(width*0.5), int(height*0.5))
#画像を拡大 (変換する前の画像配列、出力サイズ, interpolation = 補間方法の指定 )
compressed_image = cv2.resize(rgb_image, size, interpolation = cv2.INTER_CUBIC) 
plt.imshow(compressed_image)
compressed.py

最後にNumpyを用いた拡大の方法です。
#引き伸ばした画像を切り取る
#元の画像と引き伸ばした画像の縦、横を取り出す
height_1, width_1 = image.shape[:2]
height_2, width_2 = zoomed_image.shape[:2]

# x、y軸それぞれの切り取り開始点の決定
x =  int((width_2-width_1)/2)
y =  int((height_2-height_1)/2)

#切り取り開始点から元の画像分のサイズを切り取る
#[y:y+height_1]は、yの位置からy+height_1の位置までを指定して抜き出す、という意味。画像データをただの配列として扱えるところがポイント!
crop_image = zoomed_image[y:y+height_1, x:x+width_1]
plt.imshow(crop_image)
crop.py

crop_image

さて、Lennaちゃんの顔がドアップになってちょっとドキドキしたところで、今回の講座は終わりにしたいと思います!
最後までお付き合いくださりありがとうございました。次回もお楽しみに〜😆