はじめに
画像認識や物体検出においては、学習データとなる画像を大量に用意する必要があります。
しかし、十分な量のデータが用意できないということはよくあります。
そういった場合には、元データに画像処理を施すことで、データを増やす「データ増幅(data augumentation)」を行うのが一般的です。
データ増幅では、
- 変形(平行移動、回転、せん断)
- 色の変換(グレースケール、明るさ調整)
- ノイズの付与、ぼかし
といった画像処理を行います。
本記事では、画像の変形を行うアフィン変換についてまとめてみました。
参考になったUdemyの講座
- 講座のタイトル
【Pythonで学ぶ】OpenCVでの画像処理入門 - 講座のURL
https://www.udemy.com/share/1020LuAkIYcVxWTXo=/ - 受講して良かったポイント
OpenCVでの画像処理について、基本から丁寧に学ぶことができた。
また、実際に手を動かしてコードを書くことで、Pythonで画像処理を実装する方法が一通り身についた。
画像処理は、結果がすぐ目に見えてわかるため、楽しみながら受講できると思います。
補足
- 本記事および講座では適宜数式を使用していますが、十分に理解できなくても問題なく学習を進めることができます。
- 本記事では、Google Colaboratoryを使用しています。Udemy講座では、ローカル環境でJupyter Notebookを使用しています。若干異なる部分がありますのでご注意下さい。
- 本記事作成にあたり、以下も参考にさせて頂きました。
内容
OpenCVとは
OpenCVとは、オープンソースの画像処理ライブラリです。
主な特徴は以下になります。
- Intel社が開発
- 商用利用可能※
※ただし、SIFT, SURFなど、特許の関係で一部使用できないものもある - マルチプラットフォーム
Linux, Mac, Windowsで使用可能 - 多言語対応
Python, C++, Java - 画像処理から機械学習まで豊富なコンテンツが用意されている
画像の表示
OpenCVを使用して画像を表示するには、まず以下をインポートします。
※Google Colaboratoryのため、cv2_imshowをインポート。そうでない場合、cv2_imshowのインポートは不要です。
import cv2
from google.colab.patches import cv2_imshow
画像を表示するには、cv2_imshowを使用します。
※Google Colaboratoryのため、cv2_imshowを使用。そうでない場合は、cv2.imshowを使用します。
img = cv2.imread('/path/to/image')
cv2_imshow(img)
画像の情報を見てみましょう。
img.shape
# (375, 375, 3)
img.shapeの結果は、順番に画像の
height(高さ)、width(幅)、色の種類($RGB$の3色)
を表しています。
画像の任意の場所の画素値は、座標を指定して取得します。
※height, widthの順に指定することに注意
img[100, 200]
# array([176, 207, 200], dtype=uint8)
画素値が$B, G, R$の配列として取得できました。
※OpenCVでは$RGB$ではなく$BGR$の順番で扱うことに注意して下さい。
アフィン変換
アフィン変換とは、回転や平行移動、せん断変形などを表す線形変換のことです。
アフィン変換は、以下の式で表されます。
\begin{pmatrix}
x' \\
y'
\end{pmatrix}
=
\begin{pmatrix}
a & b \\
c & d
\end{pmatrix}
\begin{pmatrix}
x \\
y
\end{pmatrix}
+
\begin{pmatrix}
t_x \\
t_y
\end{pmatrix}
ここで、元の画像の座標を$(x, y)$、アフィン変換後の座標を$(x', y')$としました。
$a, b, c, d$で表された第1項が回転やせん断変形を、$t_x, t_y$で表された第2項が平行移動を表します。
上記の式は、以下のように書くこともできます。
これを、同次座標といいます。
\begin{pmatrix}
x' \\
y' \\
1
\end{pmatrix}
=
\begin{pmatrix}
a & b & t_x\\
c & d & t_y\\
0 & 0 & 1
\end{pmatrix}
\begin{pmatrix}
x\\
y\\
1
\end{pmatrix}
以下では、行列計算を行うためにnumpyをインポートしておきます。
import numpy as np
また、画像の高さ$h$と幅$w$を取得しておきます。
h, w = img.shape[:2]
平行移動
平行移動は、以下の式で表されます。
\begin{pmatrix}
x' \\
y' \\
1
\end{pmatrix}
=
\begin{pmatrix}
1 & 0 & t_x\\
0 & 1 & t_y\\
0 & 0 & 1
\end{pmatrix}
\begin{pmatrix}
x\\
y\\
1
\end{pmatrix}
平行移動には、warpAffine()関数を使用します。
t_x = 20
t_y = 60
mat1 = np.float32([[1, 0, t_x], [0, 1, t_y]])
img1 = cv2.warpAffine(img, mat1, (w, h))
imgs = cv2.hconcat([img, img1])
cv2_imshow(imgs)
回転
回転は以下の式で表されます。
\begin{pmatrix}
x' \\
y' \\
1
\end{pmatrix}
=
\begin{pmatrix}
\cos\theta & -\sin\theta & 0\\
\sin\theta & \cos\theta & 0\\
0 & 0 & 1
\end{pmatrix}
\begin{pmatrix}
x\\
y\\
1
\end{pmatrix}
回転にも、warpAffine()関数を使用できます。
theta = np.deg2rad(10)
mat = np.float32([[np.cos(theta), -np.sin(theta), 0], [np.sin(theta), np.cos(theta), 0]])
img1 = cv2.warpAffine(img, mat, (w, h))
imgs = cv2.hconcat([img, img1])
cv2_imshow(imgs)
回転移動は、角度を決めラジアンに直し、$\cos\theta, \sin\theta$という形にするのが少し面倒です。また、回転の中心を任意の位置に変えたいということもあります。
回転移動に関しては、便利な関数getRotationMatrix2Dが用意されています。
こちらを使用してみます。
mat1 = cv2.getRotationMatrix2D((w/2, h/2), 10, 1)
img1 = cv2.warpAffine(img, mat1, (w, h))
imgs = cv2.hconcat([img, img1])
cv2_imshow(imgs)
さらに、90°回転と180°回転については、もっと簡単に書くことができます。
rotate関数を使用します。
時計回りに90°回転させる場合、引数をcv2.ROTATE_90_CLOCKWISEとします。
img1 = cv2.rotate(img, cv2.ROTATE_90_CLOCKWISE)
img2 = cv2.rotate(img1, cv2.ROTATE_90_CLOCKWISE)
img3 = cv2.rotate(img2, cv2.ROTATE_90_CLOCKWISE)
imgs = cv2.hconcat([img, img1, img2, img3])
cv2_imshow(imgs)
反時計回りに90°回転させる場合、引数をcv2.ROTATE_90_COUNTERCLOCKWISEとします。
img1 = cv2.rotate(img, cv2.ROTATE_90_COUNTERCLOCKWISE)
img2 = cv2.rotate(img1, cv2.ROTATE_90_COUNTERCLOCKWISE)
img3 = cv2.rotate(img2, cv2.ROTATE_90_COUNTERCLOCKWISE)
imgs = cv2.hconcat([img, img1, img2, img3])
cv2_imshow(imgs)
180°回転させる場合、引数をcv2.ROTATE_180とします。
img1 = cv2.rotate(img, cv2.ROTATE_180)
imgs = cv2.hconcat([img, img1])
cv2_imshow(imgs)
せん断
せん断は以下の式で表されます。
\begin{pmatrix}
x' \\
y' \\
1
\end{pmatrix}
=
\begin{pmatrix}
1 & \tan\theta_1 & 0\\
\tan\theta_2 & 1 & 0\\
0 & 0 & 1
\end{pmatrix}
\begin{pmatrix}
x\\
y\\
1
\end{pmatrix}
ここで$\theta_1, \theta_2$は、それぞれ
$y$軸方向から$x$軸方向への傾き、$x$軸方向から$y$軸方向への傾き
を表す角度です。
まずは、$x$軸方向へのせん断。
theta2 = np.deg2rad(0)
theta1 = np.deg2rad(10)
mat = np.float32([[1, np.tan(theta1), 0], [np.tan(theta2), 1, 0]])
img1 = cv2.warpAffine(img, mat, (w, h))
theta1 = np.deg2rad(20)
mat = np.float32([[1, np.tan(theta1), 0], [np.tan(theta2), 1, 0]])
img2 = cv2.warpAffine(img, mat, (w, h))
theta1 = np.deg2rad(30)
mat = np.float32([[1, np.tan(theta1), 0], [np.tan(theta2), 1, 0]])
img3 = cv2.warpAffine(img, mat, (w, h))
imgs = cv2.hconcat([img, img1, img2, img3])
cv2_imshow(imgs)
次に、$y$軸方向へのせん断。
theta1 = np.deg2rad(0)
theta2 = np.deg2rad(10)
mat = np.float32([[1, np.tan(theta1), 0], [np.tan(theta2), 1, 0]])
img1 = cv2.warpAffine(img, mat, (w, h))
theta2 = np.deg2rad(20)
mat = np.float32([[1, np.tan(theta1), 0], [np.tan(theta2), 1, 0]])
img2 = cv2.warpAffine(img, mat, (w, h))
theta2 = np.deg2rad(30)
mat = np.float32([[1, np.tan(theta1), 0], [np.tan(theta2), 1, 0]])
img3 = cv2.warpAffine(img, mat, (w, h))
imgs = cv2.hconcat([img, img1, img2, img3])
cv2_imshow(imgs)
$x$軸、$y$軸方向のせん断を組み合わせることももちろん可能。
theta1 = np.deg2rad(5)
theta2 = np.deg2rad(10)
mat = np.float32([[1, np.tan(theta1), 0], [np.tan(theta2), 1, 0]])
img1 = cv2.warpAffine(img, mat, (w, h))
cv2_imshow(img1)
まとめ
OpenCVを使用したアフィン変換について説明しました。
図形の幾何学的な変形により、少ないデータを水増しすることができます。
また、今回紹介したアフィン変換に加え、その他図形の変形、色の変換、ぼかしなどといった画像処理を使用することで、さらに多くの量やバリエーションに富んだデータを用意することができます。
これらの方法についても、冒頭でご紹介したUdemyの講座
【Pythonで学ぶ】OpenCVでの画像処理入門
では説明されていますので、興味のある方は是非受講してみて下さい!
画像認識や物体検出において、学習データとして画像を用意することは地味で大変な作業です。
機械学習・ディープラーニングというと、最先端だったりスマートな技術のほうにどうしても目が向きがちです。
しかし、このデータを準備する作業を真面目にしっかりとやらないと、精度が思うように出なかったり、予想外の推論をしてしまったりということが起こります。
ですので、地味で大変な作業ですが、しっかりと取り組む必要があると考えています。
そういった部分で、AIエンジニアの方々にとって、少しでもお役に立てれば幸いです。