5. OpenCV: 物体追跡#

5.1. はじめに#

レポートは テンプレートファイル を使用して作成してください。また、ファイル名は 「(7桁の学籍番号)_第x回_画像処理レポート.docx」 (xの部分は何回目の課題なのかを記入)に変更してください。

課題作成上の注意

課題を作成する際には、プログラムは別に .py ファイルで作成して、本レポートと一緒に圧縮したうえで提出してください。また、Jupyter Notebook形式のファイル (拡張子が.ipynb)のものは受け付けません。

加えて、プログラムを添付したのみで内容に関する説明や結果に関する考察のないもの、単なる結果の羅列になっているもの(またはそのように見えるもの)は採点しませんのでご注意ください。

5.1.1. Lucas-Kanade法の実装#

まずは、各自でオプティカル・フローの検出を行う動画を撮影しよう (長くても3秒くらいの動画にすること)。

動画が撮影できたら、適当なウェブサイト等を使って動画を連番画像に変換しておく (video to image sequence などと検索すると良い)。

それを適当なフォルダに画像を格納して、OpenCVから画像を読み込み、簡単のためにグレースケールかつ浮動小数での表現に変換しておく。

import cv2
import numpy as np

img1 = cv2.imread('data/robot/frames_0001.jpg', cv2.IMREAD_COLOR)
img2 = cv2.imread('data/robot/frames_0002.jpg', cv2.IMREAD_COLOR)

gray1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
gray1 = (gray1 / 255.0).astype(np.float32)
gray2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
gray2 = (gray2 / 255.0).astype(np.float32)

画像が読み込めたら、オプティカル・フローの拘束方程式に現れる \(I_x\)\(I_y\)\(I_t\) を計算する。これれは単純に\(x\), \(y\), \(t\) の各方向に沿った勾配を計算するだけで良い。

なお、\(x\), \(y\) の勾配については、ノイズの影響を低減するためにSobelフィルタを使用して計算するのが良いだろう。

I_x = cv2.Sobel(gray1, -1, 1, 0, ksize=3)
I_y = cv2.Sobel(gray1, -1, 0, 1, ksize=3)
I_t = gray2 - gray1

ここで、とある点 \((x, y)\) におけるオプティカル・フローを計算しよう。

まずは \((x, y)\) の周囲の画素を取り出す。

w_size = 11 // 2  # 11x11の領域を取り出す
x, y = 100, 100  # オプティカル・フローを求める画素

I_x_w = I_x[y - w_size : y + w_size + 1, x - w_size : x + w_size + 1]
I_y_w = I_y[y - w_size : y + w_size + 1, x - w_size : x + w_size + 1]
I_t_w = I_t[y - w_size : y + w_size + 1, x - w_size : x + w_size + 1]

周辺の画素について \(I_x\), \(I_y\)\(I_t\) の値が取り出せたら、これらの画素が共通のフロー \((u, v)\) を持つと仮定して、拘束方程式を定義する。

# 方程式を定義
I_x_w = I_x_w.reshape((-1, 1))
I_y_w = I_y_w.reshape((-1, 1))
I_t_w = I_t_w.reshape((-1, 1))

AA = np.concatenate((I_x_w, I_y_w), axis=1)
bb = I_t_w

AA_t_AA = AA.T @ AA
AA_t_bb = AA.T @ bb

あとは得られた連立方程式 \(\mathbf{A}^\top \mathbf{A} \mathbf{x} = -\mathbf{A}^\top \mathbf{b}\) を解けば良いのだが、行列 \(\mathbf{A}^\top \mathbf{A}\) が逆行列を持たないケースも存在するため、小さな値を対角成分に持つ対角行列を足し算して、必ず解がもとまるようにしておく。

この操作は、以下の損失関数を最小化する問題を解くことと同義である。

\[ \min_{u, v} \sum_{i=1}^N \left( I_x u + I_y v + I_t \right)^2 + \lambda \left( u^2 + v^2 \right) \]

ここで、\(\lambda\) は正則化項の重みを表すパラメータである。

# 小さな値を足し算
lmbda = 1.0e-6
AA_t_AA += lmbda * np.eye(2)

# 線形問題を解く
uv = np.linalg.solve(AA_t_AA, -AA_t_bb)

同様の操作を一定の間隔でサンプルされた画素に対して行ったら、得られたフローを可視化してみよう。

フローの可視化には、OpenCVの cv2.arrowedLine を使用すると良いだろう。以下のプログラムでは変数 uvs がフローベクトルを要素に持つ配列として定義されている。

以上の処理を連続する2枚の画像全てに対して行って、結果を可視化してみよう。その上で、Lucas-Kanade法のオプティカル・フロー検出の精度を確認してみると良い。