???「Structure from Motion するために2つの画像の特徴点を取って対応点を見つけて〜」
特徴点? 対応点? 何を仰るのだこの御仁は。
同僚に突然こんなふうに話しかけられたときに戸惑わないために、画像処理の超基本を書いておきます。 OpenCV python を使います。
1. 画像データ is 何
画像データは行列(多次元配列)です。
以下の画像(320x180ピクセル)を読み込んで、サイズを表示してみます。OpenCV で読み込んが画像データは numpy array になります。
import cv2
# 画像を読み込んで形を表示
image = cv2.imread("image.png")
print(image.shape)
# -> (180, 320, 3)
# (縦, 横, 色)という3次元配列
# グレースケールに変換
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
print(gray_image.shape)
# -> (180, 320)
# グレースケールは1色なので2次元配列になる
2. 行列 is 何
行列はある演算規則の定まった2次元配列です(一般的に多次元になるとテンソルといいます)。数学のさまざまな分野で出てきます。
行列で遊ぼう1: 基本演算
numpy で行列を作って遊んでみます。
import numpy as np
a1 = np.array([
[1, 2],
[3, 4]
])
a2 = np.array([
[5, 6],
[7, 8]
])
# 行列の足し算は、要素同士の足し算
a1 + a2
# [[ 6 8]
# [10 12]]
# 行列の引き算は、要素同士の引き算
a1 - a2
# [[-4 -4]
# [-4 -4]]
# 行列のかけ算は、ちょっと複雑(演算規則は省略)
np.dot(a1, a2)
# [[19 22]
# [43 50]]
# "1" っぽい役割のもの
# 「単位行列」といいます
e = np.array([
[1, 0],
[0, 1]
])
# 任意の行列とかけ算すると…
np.array_equal(
np.dot(e, a1),
a1
)
# -> True
np.array_equal(
np.dot(a1, e),
a1
)
# -> True
# "1 * x = x * 1 = x" みたいなのが成り立つ!
# わり算は?
# 逆数が定義できればいいよね
# 逆数とは: "x * x^(-1) = x^(-1) * x = 1" が成り立つとき x^(-1) が x の逆数
a1_inverse = np.linalg.inv(a1)
# 行列 a1 とその逆行列の積が単位行列になる!
np.dot(a1_inverse, a1).astype(np.int32) # 見やすさのため整数型に変換
# [[1 0]
# [0 1]]
np.dot(a1, a1_inverse).astype(np.int32)
# [[1 0]
# [0 1]]
# わり算(の代わりに逆数をかける)
np.dot(a2, a1_inverse)
行列で遊ぼう2: フィルタによる畳み込み
# 5x5 のグレースケール画像の行列
image = np.array([
[ 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0],
[ 0, 0,255,255, 0],
[ 0, 0,255,255, 0],
[ 0, 0, 0, 0, 0],
]).astype(np.float32)
# フィルタによる畳込み
# 何もしないカーネル
id_kernel = np.array([
[0, 0, 0],
[0, 1, 0],
[0, 0, 0],
]).astype(np.float32)
# 元の image と変わらない
cv2.filter2D(image, -1, id_kernel).astype(np.int32)
# [[ 0 0 0 0 0]
# [ 0 0 0 0 0]
# [ 0 0 255 255 0]
# [ 0 0 255 255 0]
# [ 0 0 0 0 0]]
# 1ピクセル右にずらすカーネル
right_kernel = np.array([
[0, 0, 0],
[1, 0, 0],
[0, 0, 0],
]).astype(np.float32)
cv2.filter2D(image, -1 ,right_kernel).astype(np.int32)
# [[ 0 0 0 0 0]
# [ 0 0 0 0 0]
# [ 0 0 0 255 255]
# [ 0 0 0 255 255]
# [ 0 0 0 0 0]]
# 画像の微分は実はフィルタによる畳み込み
d_x = np.array([
[-1, 0, 1],
[-1, 0, 1],
[-1, 0, 1],
]).astype(np.float32)
d_y = np.array([
[-1,-1,-1],
[ 0, 0, 0],
[ 1, 1, 1]
]).astype(np.float32)
cv2.filter2D(image, -1, d_x).astype(np.int32)
# [[ 0 0 0 0 0]
# [ 0 255 255 -255 0]
# [ 0 510 510 -510 0]
# [ 0 510 510 -510 0]
# [ 0 510 510 -510 0]]
cv2.filter2D(image, -1, d_y).astype(np.int32)
# [[ 0 0 0 0 0]
# [ 0 255 510 510 510]
# [ 0 255 510 510 510]
# [ 0 -255 -510 -510 -510]
# [ 0 0 0 0 0]]
行列で遊ぼう3: ガウシアンフィルタ
ガウシアンフィルタで画像の「ぼかし」ができます。
## 5x5のグレースケール画像
image = np.array([
[ 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0],
[ 0, 0,100,100, 0],
[ 0, 0,100,100, 0],
[ 0, 0, 0, 0, 0],
]).astype(np.float32)
# ガウシアンフィルタ
gaussian_kernel = np.array([
[1, 2, 1],
[2, 4, 2],
[1, 2, 1]
]).astype(np.float32) / 16
gauss_image = cv2.filter2D(image, -1 , gaussian_kernel).astype(np.int32)
print(gauss_image)
# [[ 0 0 0 0 0]
# [ 0 6 18 18 12]
# [ 0 18 56 56 37]
# [ 0 18 56 56 37]
# [ 0 12 37 37 25]]
特徴点 is 何
- コーナーなど画像内の特徴的な点
- 物体追跡や対応点検出で使う
Harris コーナー検出器
特徴点検出のやり方としては、画像の各点ごとに Harris 行列を計算してそれを判別関数に入れてコーナーがあるかどうか判別します。内部のアルゴリズムは省略。
import numpy as np
import cv2
# 5x5 のグレースケール画像
image = np.array([
[ 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,255,255,255,255,255],
[ 0, 0, 0, 0, 0,255,255,255,255,255],
[ 0, 0, 0, 0, 0,255,255,255,255,255],
]).astype(np.float32)
# コーナー検出
dst = cv2.cornerHarris(image,2,3,0.04)
# 二値化処理
threshold = (dst > 0.01 * dst.max()).astype(np.int32)
print(threshold)
# [[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 1 1 0 0 0]
# [0 0 0 0 0 1 1 0 0 0]
# [0 0 0 0 0 0 0 0 0 0]]
特徴量記述子
特徴点だけでは座標しかわからないので、対応点検出のためにはピクセルの周辺情報を含んだ「特徴記述子」が必要となる。
- ある点の周辺の画像情報を次元を落としたベクトルにする
- SIFTなら128次元のベクトル
- 記述子はスケール、回転、明度などに対してロバスト(変更に強い性質)でなければならない
- ある記述子と別の記述子が「近い」といえるために距離を定義する
- 特徴量記述子のマッチングを効率よく行うアルゴリズムを作る
- 近似最近傍探索(FLANN based matcher)など
くたびれたからこれでおしまい。