Piksel Takibi, Optik Akış, Lucas Kanade Algoritması
Hareket halindeki bir kameranın aldığı görüntülerdeki herhangi bir pikseli nasıl takip ederiz?
Matematiksel olarak temsil etmek gerekirse, zamana göre değişen 2 boyutlu görüntüyü bir fonksiyon olarak düşünelim, ki bu fonksiyonun değerleri ayrıksal olarak, imajın ta kendisi. Bir $I(x(t),y(t),t)$ fonksiyonu piksel değerlerini veriyor. Bu fonksiyonda $x,y$ ekran kordinatlarına tekabül ediyor, $t$ ise zaman, $1,2,..$ gibi değerleri indeks değerleri var, mesela $I(100,200,1)$, bize 1. video karesindeki $x=100,y=200$ kordinatlarındaki piksel değerini verecek.
$x,y$ değişkenleri parametrize edildi, bir noktayı takip etmek istiyoruz çünkü, ve $t$'ye göre bu takip edilen noktanın $x,y$ kordinatları belli bir gidişat yönünde değişiyor.
Şu faraziyeyi yaparak takip problemimizi kolaylaştırabiliriz. Diyelim ki takip edilen bir nokta, görüldüğü her karede aynı piksel rengindedir. Bu çok sıradışı bir faraziye değil, resim karelerinden bir araba geçiyor mesela, ve bu arabanın üzerindeki piksellerin renkleri, en azından iki kare arasında değişmiyor. Işık seviyesi, gölgede olma, vs. gibi durumlarda biraz değişebilir, fakat basitleştirme amacıyla bu faraziye geçerlidir.
Bir diğer faraziye, kameralar hareket ettiklerinde alınan iki görüntü arasındaki tüm piksellerin yer değişimi genellikle aynı yönde olmasıdır. Bu değişim yönünü $$ vektörü olarak görebiliriz, ve bu değişkenler iki görüntü arasındaki değişimde tüm pikseller için aynı olacaktır. Bu da normal, kamerayı alıp mesela sağa doğru hareket ettiriyoruz, ve görüntüdeki tüm pikseller sola doğru gidiyorlar.
Tüm bunları modelimizde nasıl kullanırız?
Takip edilen nokta her karede aynı renkte ise, şu ifade doğru demektir
$$ I(x(t),y(t),t) = \textrm{ sabit } $$
Eğer bu fonksiyonun zamana göre türevini alırsak
$$ \frac{d \ I(x(t),y(t),t)}{dt} = 0$$
sonucu gelir. Eşitliğin sağı sıfır, çünkü bir sabitin türevini aldık. Sol tarafa Zincirleme Kanununu uygularsak,
$$ \frac{\partial I}{\partial x}\frac{dx}{dt} + \frac{\partial I}{\partial y}\frac{dy}{dt} + \frac{\partial I}{\partial t} = 0 $$
Bu formülde $dx/dt$ ve $dy/dt$, hareket halindeki (zaman geçerken) noktanın sonsuz küçüklükteki yer değimi. Ayrıksal bağlamda arka arkaya iki kare içindeki yer değişimi. O zaman,
$$ \frac{dx}{dt}, \frac{dy}{dt} = u, v $$
Alttakiler ise mesafesel (spatial) gradyanlardır, bunların nasıl hesaplanacağını çok iyi biliyoruz!
$$ \frac{\partial I}{\partial x}, \frac{\partial I}{\partial y} $$
Alttaki ise resim karelerinin zamana göre türevidir.
$$ \frac{\partial I}{\partial t} $$
Daha derli toplu olarak göstermek gerekirse ana formül nihai olarak şöyle
$$ I_x u + I_y v + I_t = 0 $$
ya da
$$ \nabla I \cdot = -I_t $$
Şimdi $u,v$'nin hesaplanmasına gelelim. Üstteki formülü bir veri noktası için yazmak yeterli değil. Ama bu formülü hem takip ettiğimiz, hem de onun etrafındaki pikseller için yazarsak (onların yer değişimi de aynı değil mi?), ve bu sistemi çözersek, sonuca varabiliriz.
İki tane bilinmeyenimiz var, ama böylece pek çok formül elde ediyoruz. Veriler gürültülü olduğu için, aslında bilinmeyenden "daha fazla" formül elde etmek iyi, bu tür denklem sistemlerine "çok eşitliğe sahip (overdetermined)" denir, ve böyle tür sistemler En Az Kareler (Least Squares) ile çözülür. Tüm bunları biraraya koyunca şu ortaya çıkar.
$$ \left[\begin{array}{cc} I_x(p_1) & I_y(p_1) \\ I_x(p_2) & I_y(p_1) \\ \vdots & \vdots \\ I_x(p_k) & I_y(p_k) \end{array}\right] \left[\begin{array}{r} u \\ v \end{array}\right] = - \left[\begin{array}{c} I_t(p_1) \\ I_t(p_2) \\ \vdots \\ I_t(p_k) \end{array}\right] $$
Gradyanların belli noktalarda hesaplandığını unutmayalım, o sebeple $p_1, p_2$ gibi piksel noktalarını bu fonksiyonlara geçiyoruz.
Bu sistemi
$$ A \ d = b $$
olarak gösterebiliriz, ki $d = $. Sol tarafı $A^T$ ile çarpalım
$$ A^TA \ d = A^Tb $$
Eğer $A^TA$'nin matris tersini iki tarafla çarparsak, $d$ yanlız kalır, ve sonuç elde edilir.
Bu denklemi Python Numpy'da pinv
kullanarak çözeriz.
Test için üç tane resim kullandık, bu resimlerden flow1-bw-0.png
başlangıç resmi, bu resmin ortasındaki objeleri GIMP kullanarak elle kopyaladık,
bir üst sağ çapraza doğru, bir alt sol çapraza doğru, ve iki yeni resim elde
ettik (upright.png
, dleft.png
). Takip edilen nokta gri dörtgenin
alt sol köşesinde. Lucas Kanade algoritması bu noktayı takip ederek, yeşil ile
işaretledi.
import scipy.signal as si
from PIL import Image
def gauss_kern():
h1 = 15
h2 = 15
x, y = np.mgrid[0:h2, 0:h1]
x = x-h2/2
y = y-h1/2
sigma = 1.5
g = np.exp( -( x**2 + y**2 ) / (2*sigma**2) );
return g / g.sum()
def deriv(im1, im2):
g = gauss_kern()
Img_smooth = si.convolve(im1,g,mode='same')
fx,fy=np.gradient(Img_smooth)
ft = si.convolve2d(im1, 0.25 * np.ones((2,2))) + \
si.convolve2d(im2, -0.25 * np.ones((2,2)))
fx = fx[0:fx.shape[0]-1, 0:fx.shape[1]-1]
fy = fy[0:fy.shape[0]-1, 0:fy.shape[1]-1];
ft = ft[0:ft.shape[0]-1, 0:ft.shape[1]-1];
return fx, fy, ft
import warnings
warnings.simplefilter("ignore", np.ComplexWarning)
im1 = np.asarray(Image.open('flow1-bw-0.png'))
im2 = np.asarray(Image.open("upright.png"))
fx, fy, ft = deriv(im1, im2)
print fx[:5]
[[ 34.37477011 45.94010835 51.877951 ..., 53.83264716 51.877951
45.94010835]
[ 26.01168277 34.76327322 39.25648957 ..., 40.73562489 39.25648957
34.76327322]
[ 11.72919465 15.67546405 17.70154632 ..., 18.36851839 17.70154632
15.67546405]
[ 3.51803959 4.70167857 5.30937909 ..., 5.50942984 5.30937909
4.70167857]
[ 0.6961225 0.93033183 1.05057892 ..., 1.09016341 1.05057892
0.93033183]]
import scipy.signal as si
from PIL import Image
import numpy.linalg as lin
def lk(im1, im2, i, j, window_size) :
fx, fy, ft = deriv(im1, im2)
halfWindow = np.floor(window_size/2)
curFx = fx[i-halfWindow-1:i+halfWindow,
j-halfWindow-1:j+halfWindow]
curFy = fy[i-halfWindow-1:i+halfWindow,
j-halfWindow-1:j+halfWindow]
curFt = ft[i-halfWindow-1:i+halfWindow,
j-halfWindow-1:j+halfWindow]
curFx = curFx.T
curFy = curFy.T
curFt = curFt.T
curFx = curFx.flatten(order='F')
curFy = curFy.flatten(order='F')
curFt = -curFt.flatten(order='F')
A = np.vstack((curFx, curFy)).T
U = np.dot(np.dot(lin.pinv(np.dot(A.T,A)),A.T),curFt)
return U[0], U[1]
def test(image1,image2,output):
x=165
y=95
win=50
im1 = np.asarray(Image.open(image1))
im2 = np.asarray(Image.open(image2))
u, v = lk(im1, im2, x, y, win)
plt.imshow(im1, cmap='gray')
plt.hold(True)
plt.plot(x,y,'+r');
# 3 ile carptik cunku vektor degisimi iyi hesaplandi ama
# grafikleme icin cok ufakti, ikinci yesil nokta iyi gozuksun
# diye onu biraz buyuttuk
plt.plot(x+u*3,y+v*3,'og')
plt.savefig(output)
test('flow1-bw-0.png','dleft.png','lk_1.png')
test('flow1-bw-0.png','upright.png', 'lk_2.png')
Bu matematiksel modele alternatif bir bakış şöyle olabilir. İki imaj karesi içinde birincisine $I(x,y)$ ikincisine $H(x,y)$ diyelim, burada $t$ üzerinden parametrizasyon olmasın; $x,y$ pikselinin $H$ içinde $u,v$ kadar yer değişiminden sonra, bu noktaların $I$'de geldiği yerdeki grilik değerinin aynı olduğunu (yine) farzediyoruz. Sonra $I(x+u,y+v)$'nin birinci dereceden Taylor Açılımını yapıyoruz,
$$ I(x+u,y+v) = I(x,y) + \frac{\partial I}{\partial x}u + \frac{\partial I}{\partial y}v + ... $$
ya da
$$ I(x+u,y+v) \approx I(x,y) + \frac{\partial I}{\partial x}u + \frac{\partial I}{\partial y}v $$
Grilik aynılığını ise şöyle belirtebiliriz
$$ I(x+u,y+v) - H(x,y) = 0$$
Taylor açılımını üstteki formülde $I$ yerine geçirelim
$$ I(x,y) + \frac{\partial I}{\partial x}u + \frac{\partial I}{\partial y}v - H(x,y) = 0 $$
$H$'in yerini değiştirelim
$$ I(x,y) - H(x,y) + I_xu + I_yv = 0$$
Şu ifade $I(x,y) - H(x,y)$ nedir? Bunlar iki imajın, sonrası ve öncesi arasındaki fark değil midir? O zaman bu hesabı imajın zamana göre alınmış türevi olarak görebiliriz, yani $I_t = I(x,y) - H(x,y)$. Yerine koyalım
$$ I_t + I_xu + I_yv = 0$$
$$ I_xu + I_yv = -I_t $$
Böylece aynı denkleme erişmiş olduk. Bu aslında normal, birinci dereceden Taylor açılımı ile tam diferansiyel denklemi (ve Zincirleme Kanununu) birbiriyle çok yakından alakalı.
Ufak Piksel Değişimleri
Konu hakkında bir nokta daha şu; Lucas-Kanade yöntemi 1. derece Taylor açılımı kulladığı için ufak piksel değişimleri için geçerlidir, çünkü Taylor açılımı yerel bir noktaya çok yakın bölgelerde bir fonksiyona yakın sonuçlar verir. Bu da aklımızda bulunsun.e
OpenCV
OpenCV ile optik akış kullanımı altta görülüyor.
import pandas as pd, zipfile
import numpy as np
import cv2
def draw_flow(img, flow, step=16):
h, w = img.shape[:2]
y, x = np.mgrid[step/2:h:step, step/2:w:step].reshape(2,-1).astype(int)
fx, fy = flow[y,x].T
lines = np.vstack([x, y, x+fx, y+fy]).T.reshape(-1, 2, 2)
lines = np.int32(lines + 0.5)
vis = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
cv2.polylines(vis, lines, 0, (0, 255, 0))
for (x1, y1), (x2, y2) in lines:
cv2.circle(vis, (x1, y1), 1, (0, 255, 0), -1)
return vis
prevgray = cv2.imread('106.jpg', cv2.IMREAD_GRAYSCALE)
gray = cv2.imread('107.jpg', cv2.IMREAD_GRAYSCALE)
flow = cv2.calcOpticalFlowFarneback(prevgray, gray, None, 0.5, 3, 15, 3, 5, 1.2, 0)
cv2.imwrite('pde_lk_01.png', draw_flow(gray, flow))
Kaynaklar
[1] Collins, Introduction to Computer Vision, http://www.cse.psu.edu/~rtc12/CSE486/
[2] Khurram Hassan-Shafique, CAP 5415 Lecture Notes, Spring 2003
[3] Suhr, Kanade-Lucas-Tomasi (KLT) Feature Tracker Feature Tracker, http://web.yonsei.ac.kr/jksuhr/articles/Kanade-Lucas-Tomasi%20Tracker.pdf
Yukarı